RP2040下的I2S Slave Out,PIO状态机(二)

        昨晚继续写接口,依旧是找DS辅助,因为自己实在是没有太多的时间和精力去研究这些事情,首先,我这个参考的框架来源于这个开源工程:

https://github.com/malacalypse/rp2040_i2s_example

        大家可以先下载这个工程看一下,它提供了几个模式,Master out,Slave in,Slave bidi,但是唯独没有Slave Out模式。因此,我不得不自己研究如何写这个模式。参考也主要是这个工程。这个工程主要是给pico sdk用的,需要用CMake,如果是用Arduino开发,只需要把i2s.h i2s.c i2s.pio三个文件放进你的工程即可。但是在编译之前,你需要先处理一下这个pio文件。pio文件是汇编和C语言的内联接口,需要一个特殊的工具来编译成.h头文件才能使用,这个工具叫pioasm,包含在pico sdk内,如果是Arduino,可以在这个目录去找找:

C:\Users\你的账号\AppData\Local\Arduino15\packages\rp2040\tools\pqt-pioasm

        这个工具使用也非常简单,用这个命令即可:

pioasm i2s.pio i2s.pio.h        

        即把pio文件编译成h头文件。然后就可以编译这个工程了。我们昨天生成的代码,也是放进i2s.pio内,然后编程一次,编译成i2s.pio.h。然后编译整个工程,就可以发现这个库已经可以成功编译了。

        接下来,需要继续给状态机增加初始化和dma控制的代码。我们照旧找ds给我辅助。我是这样给DS 提要求的:

        它分析完三个文件之后,直接给了我结论。

要将 I2S 从机输出功能整合到现有代码中,请按照以下步骤操作:

1. 在 i2s.h 中添加声明

// 在 pio_i2s 结构体后添加
void i2s_program_start_out_slave(PIO pio, const i2s_config* config, void (*dma_handler)(void), pio_i2s* i2s);

2. 在 i2s.c 中添加实现

// 在文件顶部添加声明
extern const struct pio_program i2s_slave_out_program;
static void dma_out_double_buffer_init(pio_i2s* i2s, void (*dma_handler)(void));

// 在文件末尾添加新函数
void i2s_program_start_out_slave(PIO pio, const i2s_config* config, void (*dma_handler)(void), pio_i2s* i2s) {
    if (((uint32_t)i2s & 0x7) != 0) {
        panic("pio_i2s argument must be 8-byte aligned!");
    }
    
    uint offset = 0;
    i2s->pio = pio;
    i2s->sm_mask = 0;
    i2s->sm_dout = pio_claim_unused_sm(pio, true);
    i2s->sm_mask |= (1u << i2s->sm_dout);

    // 添加从机输出程序
    offset = pio_add_program(pio, &i2s_slave_out_program);
    i2s_out_slave_init(pio, i2s->sm_dout, offset, config->dout_pin, config->bit_depth);

    // 仅初始化输出DMA
    dma_out_double_buffer_init(i2s, dma_handler);
    pio_enable_sm_mask_in_sync(i2s->pio, i2s->sm_mask);
}

static void dma_out_double_buffer_init(pio_i2s* i2s, void (*dma_handler)(void)) {
    // 仅初始化输出通道
    i2s->dma_ch_out_ctrl = dma_claim_unused_channel(true);
    i2s->dma_ch_out_data = dma_claim_unused_channel(true);

    i2s->out_ctrl_blocks[0] = i2s->output_buffer;
    i2s->out_ctrl_blocks[1] = &i2s->output_buffer[STEREO_BUFFER_SIZE];

    // 输出控制通道
    dma_channel_config c = dma_channel_get_default_config(i2s->dma_ch_out_ctrl);
    channel_config_set_read_increment(&c, true);
    channel_config_set_write_increment(&c, false);
    channel_config_set_ring(&c, false, 3);
    channel_config_set_transfer_data_size(&c, DMA_SIZE_32);
    dma_channel_configure(i2s->dma_ch_out_ctrl, &c, 
                         &dma_hw->ch[i2s->dma_ch_out_data].al3_read_addr_trig,
                         i2s->out_ctrl_blocks, 
                         1, false);

    // 输出数据通道
    c = dma_channel_get_default_config(i2s->dma_ch_out_data);
    channel_config_set_read_increment(&c, true);
    channel_config_set_write_increment(&c, false);
    channel_config_set_chain_to(&c, i2s->dma_ch_out_ctrl);
    channel_config_set_dreq(&c, pio_get_dreq(i2s->pio, i2s->sm_dout, true));

    dma_channel_configure(i2s->dma_ch_out_data,
                         &c,
                         &i2s->pio->txf[i2s->sm_dout],
                         NULL,
                         STEREO_BUFFER_SIZE,
                         false);

    // 设置DMA中断
    dma_channel_set_irq0_enabled(i2s->dma_ch_out_data, true);
    irq_set_exclusive_handler(DMA_IRQ_0, dma_handler);
    irq_set_enabled(DMA_IRQ_0, true);

    dma_channel_start(i2s->dma_ch_out_ctrl);
}

3. 在 i2s.pio 中确保包含输出程序

确认文档3中的以下部分已存在:

// 确保包含从机输出程序
extern const struct pio_program i2s_slave_out_program;
static inline void i2s_out_slave_init(PIO pio, uint sm, uint offset, uint dout_pin, uint bit_depth);

4. 使用示例

// 配置参数
i2s_config config = i2s_config_default;
config.dout_pin = 15;  // 数据输出引脚
config.bit_depth = 32; // 位深度

// 初始化结构体(8字节对齐)
__attribute__((aligned(8))) pio_i2s i2s_instance;

// 启动从机输出
i2s_program_start_out_slave(pio0, &config, dma_handler, &i2s_instance);

关键修改说明:

  1. ​独立初始化函数​​:

    • 新增 i2s_program_start_out_slave 专门处理从机输出模式
    • 复用 sm_dout 字段存储输出状态机ID
  2. ​DMA优化​​:

    • 新增 dma_out_double_buffer_init 仅处理输出DMA
    • 输出控制通道管理双缓冲切换
    • 输出数据通道链接到PIO TX FIFO
  3. ​引脚配置​​:

    • 自动推导时钟引脚:DOUT+1 (BCK), DOUT+2 (LRCK)
    • 符合标准I2S硬件布局
  4. ​中断处理​​:

    • DMA中断在输出缓冲区切换时触发
    • 用户需实现 dma_handler 填充下一帧数据

​注意事项​​:

  1. 确保 pio_i2s 结构体8字节对齐(使用 __attribute__((aligned(8)))
  2. 输出缓冲区大小由 STEREO_BUFFER_SIZE 控制(默认约1ms数据)
  3. 外部主设备需提供BCK和LRCK时钟信号

        我对DS生成的代码肯定是保有疑虑的,于是一行一行的检查,碰到不太明白的地方让DS解释,我分析完,发现这次它照着模板生成的代码的质量挺高的,居然可以基本上不修改,直接使用。于是我把这些代码整合进i2s.h和i2s.cpp文件内。一次性顺利的编译通过。

        然后我写了一个简单的测试代码,测试dma的回调是否正常。

#ifndef _SINE_WAVE_
#define _SINE_WAVE_
#include "Common.h"


// 3. DMA 中断处理函数
void dma_handler(void) 
{
    Serial.printf( "dma\n" );
}


class CSineWave
{
    private:


    public:

        inline CSineWave()
        {
        }


        inline ~CSineWave()
        {
        }

        inline bool Initiate()
        {
            i2s_config config = i2s_config_default;
            config.sck_enable = false;      // 禁用 SCK(外部提供时钟)
            config.dout_pin = 7;            // 数据输出引脚
            config.bit_depth = 16;          // 16位

            __attribute__((aligned(8))) pio_i2s i2s_inst;

            i2s_program_start_out_slave(pio0, &config, dma_handler, &i2s_inst);
            return true;
        }
};


#endif

        代码非常简单,就是创建状态机,然后关联dma到状态机,并设置dma中断,编译之后,一次性执行成功,dma成功回调。

        今天就到这儿,晚上回去继续写代码,接下来就是准备正弦波并把正确的数据送入dma缓冲区,然后硬件上调试rp2040的输出。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值