RP2040下的I2S Slave Out,PIO状态机(四)
晚上回家后,进行硬件的实际调试,测试的时候我发现一个问题,输出有点不对劲,BCK和LRCK都是正确的,我用192k的fs,16bit的位深,按道理,发送数据的pin上的信号频率应该是介于BCK和LRCK之间的,但是DOUT的频率示波器测试大概是2K多一点,BCK是6.144Mhz,LRCK是192K,这个数据明显不对。我检查了线路,没有明显的缺陷,我想大概率问题还是在代码上。我又仔细检查了代码。我发现一个问题,DMA的双缓冲区是INT32类型的,而我需要发送的是16位数据。在初始化状态机的代码里面我发现这样的信息:
static inline void i2s_out_slave_init( PIO pio, uint sm, uint offset, uint dout_pin, uint bit_depth)
{// 自动推导连续时钟引脚(硬件设计常见布局)uint bck_pin = dout_pin + 1; // BCK 紧邻 DOUTuint lrck_pin = dout_pin + 2; // LRCK 紧邻 BCK// 初始化所有引脚功能pio_gpio_init(pio, dout_pin); // DOUTpio_gpio_init(pio, bck_pin); // BCK(输入)pio_gpio_init(pio, lrck_pin); // LRCK(输入)// 核心配置(与标准实现一致)pio_sm_config c = i2s_slave_out_program_get_default_config(offset);sm_config_set_out_pins(&c, dout_pin, 1); // DOUT 绑定sm_config_set_in_pins(&c, bck_pin); // BCK 为时钟源sm_config_set_jmp_pin(&c, lrck_pin); // LRCK 用于声道同步sm_config_set_out_shift(&c, false, false, bit_depth); // 左移(MSB先出),禁用 autopullsm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); // 合并 FIFO// 应用配置状态机pio_sm_init(pio, sm, offset, &c);// 统一设置初始电平和引脚方向pio_sm_set_pins_with_mask( pio, sm, 0, (7u << dout_pin) ); // zero outputpio_sm_set_pindirs_with_mask( pio, sm, (1u << dout_pin), (7u << dout_pin) );
}
其中一行代码如下:
sm_config_set_out_shift(&c, false, false, bit_depth); // 左移(MSB先出),禁用 autopull
这里面有注释,MSB先出,发送16位就终止,这个是I2S标准的格式,意思就是先从高位发送,如果我给的16位有效值都在低位,那岂不是根本发不出去?我想了一下,这个环节我的确还没处理,我想了一下,我应该可以在创建正弦表的时候处理这个问题。我改了一下代码,在生成正弦值之后,把数据左移16位,把有效位放在高16位上,代码如下:
// 生成正弦波表(预计算优化)
bool CSineWave::GenerateSineTable()
{if( bit_depth == 0 ) {return false;}uint shift = 32 - bit_depth;const double amplitude = static_cast<double>( ( uint32_t(1) << (bit_depth - 1) ) - 1 );for( uint32_t i = 0; i < SINE_TABLE_LENGTH; ++ i ) {int32_t value = static_cast<int32_t>( std::round(amplitude * sin( 2.0 * M_PI * static_cast<double>(i) / SINE_TABLE_LENGTH ) ) );sine_table[i] = value << shift; }return true;
}
编译之后,再次测试,OUTPIN输出在示波器上显示出正确的频率。