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

        我最近在折腾PCM5122这个DAC,目的是为了输出正弦波,用2040做控制,但是2040没有任何I2S的官方完整实现,或则说,官方提供的I2S,只有Master out模式,其实I2S有很多模式,但我需要的是最特殊的一种,就是Slave Out。即PCM5122通过晶振获得MCK,然后由PCM5122分频得到BCK和LRCK,输出给RP2040,RP2040再输出数据给PCM5122。这个模式非常罕见,甚至网上连参考代码都没有,于是没办法,只能用DeepSeek帮我搞开发。 

        不得不说,DeepSeek是真的有点中二,为了开发这段汇编代码,并不是说我提了需求,他就给我答案,他给了我很多次代码,但是DS生成的代码,你必须非常小心,甚至必须完全吃透这个代码的逻辑,然后你就会发现非常多的Bug。我把DS生成的第一版代码和最终的代码都贴出来,大家可以看看。
 

.program i2s_slave_out
.side_set 1 opt

; 声明引脚映射(独立指定)
#define BCLK_PIN 8
#define LRCLK_PIN 9
#define DOUT_PIN 7

.wrap_target
    ; 等待BCK下降沿(外部时钟同步)
    wait 0 pin BCLK_PIN      ; 下降沿检测 [8](@ref)
    
    ; 检测LRCK变化(声道切换)
    jmp pin LRCLK_PIN right_channel
left_channel:
    out pins, 1              ; 输出左声道数据(MSB优先)[8](@ref)
    jmp next_bit
right_channel:
    out pins, 1              ; 输出右声道数据
next_bit:
    
    ; 等待BCK上升沿(数据稳定)
    wait 1 pin BCLK_PIN      ; 上升沿检测 [8](@ref)
.wrap

这是DS生成的第一版代码,初看貌似没啥问题,但是经不住仔细推敲。比如,它用了side_set,这个属于RP2040的特殊优化方法,在输出时钟的时候,需要同时输出BCK和LRCK,因此用side_set 可以直接设置BCK和LRCK的状态,但是Slave out模式内,BCK和LRCK是外部输入,只需要读就行了,不需要同步,所以,它的第二行就是废的。

        其次,它用了.wrap_target 和 .wrap,循环执行Slave Out,但事实上,这样写是没办法处理任何数据的,因为时钟是外部输入,必须由引导程序,当正确的下降沿出现,才正式开始传输数据。另外,他直接用了out pins 1,都不拉数据?反正我是被DS干懵了,我一条错误一条错误的给它指出。它倒是认错认得快,但是质量依旧不高。我连续整了一个多小时,最终的代码如下:

.program i2s_slave_out

;---- 强制全周期同步 (规避相位错位) ----
global_sync:
    wait 1 pin 1                ; 阻塞至 LRCK=1 (右声道)
    wait 0 pin 1                ; 再阻塞至 LRCK=0 (左声道起始)

;---- 左声道主循环 ----
left_channel_entry:
    pull noblock                ; 从 TX FIFO 加载左声道数据到 OSR
    wait 0 pin 0                ; 等待 BCK=0 下降沿 (防抖动)
    wait 1 pin 0                ; 等待 BCK=1 上升沿 (I2S 预备输出时刻)
    wait 0 pin 0                ; 等待 BCK=0 下降沿 (I2S 输出时刻)

left_channel_loop:
    out pins, 1                 ; 输出 1 位数据 (OSR → DOUT)
    wait 1 pin 0                ; 保持至 BCK 上升沿 (数据稳定)
    wait 0 pin 0                ; 等待 BCK 下降沿 (LRCK切换时刻)
    jmp pin right_channel_entry ; 检测 LRCK=1 则跳右声道
    jmp left_channel_loop

;---- 右声道入口 ----
right_channel_entry:
    pull noblock                ; 加载右声道数据
    wait 0 pin 0                ; 等待 BCK=0 下降沿 (防抖动)
    wait 1 pin 0                ; 等待 BCK=1 上升沿 (I2S 预备输出时刻)
    wait 0 pin 0                ; 等待 BCK 下降沿

right_channel_loop:
    out pins, 1
    wait 1 pin 0                ; 保持 BCK 上升沿 (数据稳定)
    wait 0 pin 0                ; 等待 BCK 下降沿 (LRCK切换时刻)
    jmp pin right_channel_loop  ; 检测 LRCK=0 则跳左声道
    jmp left_channel_entry


现在是把汇编搞定了,另外增加一个C语言的初始化接口函数:

/**
 * @brief 简化版 I2S 从模式初始化(时钟引脚连续布局)
 * @param pio        PIO 实例 (pio0/pio1)
 * @param sm         状态机编号 (0-3)
 * @param offset     PIO 程序加载偏移地址
 * @param dout_pin   数据输出引脚(BCK/LRCK 自动为 dout_pin+1/dout_pin+2)
 * @param bit_depth  音频位宽 (16/24/32)
 */
static inline void i2s_slave_out_init_simplified(
    PIO pio, uint sm, uint offset,
    uint dout_pin, uint bit_depth
) {
    // 自动推导连续时钟引脚(硬件设计常见布局)
    uint bck_pin = dout_pin + 1;   // BCK 紧邻 DOUT
    uint lrck_pin = dout_pin + 2;  // LRCK 紧邻 BCK

    // 初始化所有引脚功能
    pio_gpio_init(pio, dout_pin);  // DOUT
    pio_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); 
    sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);    // 合并 FIFO

    // 设置引脚方向:仅 DOUT 为输出,时钟引脚为输入
    uint32_t pin_mask = (1u << dout_pin) | (1u << bck_pin) | (1u << lrck_pin);
    pio_sm_set_consecutive_pindirs(pio, sm, dout_pin, 1, true);   // DOUT 输出
    pio_sm_set_consecutive_pindirs(pio, sm, bck_pin, 2, false);   // BCK/LRCK 输入

    // 应用配置并启动状态机
    pio_sm_init(pio, sm, offset, &c);
}

        生成这个接口函数的过程,DS的表现也是一言难尽。这儿不在敷言,接下来将继续写其他代码,包括DMA传输数据什么的,试试这个状态机能否正确执行。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值