昨晚继续写接口,依旧是找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);关键修改说明:
独立初始化函数:
- 新增
i2s_program_start_out_slave专门处理从机输出模式- 复用
sm_dout字段存储输出状态机IDDMA优化:
- 新增
dma_out_double_buffer_init仅处理输出DMA- 输出控制通道管理双缓冲切换
- 输出数据通道链接到PIO TX FIFO
引脚配置:
- 自动推导时钟引脚:
DOUT+1(BCK),DOUT+2(LRCK)- 符合标准I2S硬件布局
中断处理:
- DMA中断在输出缓冲区切换时触发
- 用户需实现
dma_handler填充下一帧数据注意事项:
- 确保
pio_i2s结构体8字节对齐(使用__attribute__((aligned(8))))- 输出缓冲区大小由
STEREO_BUFFER_SIZE控制(默认约1ms数据)- 外部主设备需提供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的输出。
1958

被折叠的 条评论
为什么被折叠?



