You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

STM32 Cortex-M4 I2S 24位DMA模式下高效交换半字的实现方法

解决STM32 Cortex-M4 I2S DMA 24位模式下半字顺序冲突问题

我之前在做STM32 Cortex-M4的24位I2S音频传输时,刚好碰到过完全一样的坑——DMA默认会先发送低半字,但芯片手册要求I2S必须先发高半字,而且I2S数据寄存器只有16位,必须把24位数据拆成两个半字发送。下面分享几种高效的实现方式,不管是用C语言还是ARM汇编都能完美解决:

C语言实现方案

1. 位掩码+移位操作(最直观,编译后效率拉满)

这种方法直接通过位操作交换32位变量里的两个半字,代码简洁,而且编译器会自动优化成高效的指令:

#include <stdint.h>

// 交换32位变量中的两个16位半字
uint32_t swap_halfwords(uint32_t val) {
    // 取出高半字并移到低位置,取出低半字并移到高位置,再合并
    return ((val & 0xFFFF0000) >> 16) | ((val & 0x0000FFFF) << 16);
}

比如你有一个24位数据0x00ABCDEF(有效24位是0xABCDEF),拆分后的高半字是0xABCD、低半字是0x00EF(补高位0凑16位),用这个函数处理后,32位变量会变成0x00EFABCD,这样DMA按16位宽度读取时,会先取0xABCD(原来的高半字),再取0x00EF(原来的低半字),完全符合I2S的要求。

2. 联合(Union)类型转换(可读性更强)

如果你觉得位操作不够直观,可以用C语言的联合来绑定32位变量和两个16位成员,直接交换成员位置:

#include <stdint.h>

typedef union {
    uint32_t full_word;
    uint16_t half_words[2]; // half_words[0] = 低半字,half_words[1] = 高半字(小端模式)
} HalfwordSwapper;

uint32_t swap_halfwords_union(uint32_t val) {
    HalfwordSwapper swapper;
    swapper.full_word = val;
    // 交换两个半字的存储位置
    uint16_t temp = swapper.half_words[0];
    swapper.half_words[0] = swapper.half_words[1];
    swapper.half_words[1] = temp;
    return swapper.full_word;
}

注意:Cortex-M4默认是小端模式,所以half_words[0]对应内存中的低地址(低半字),half_words[1]对应高地址(高半字),交换后就能让DMA按正确顺序读取。

ARM汇编实现方案(极致性能)

如果你的应用对性能要求极高(比如在实时中断中处理大量音频数据),直接用ARM汇编实现是最优解,只需要3条指令就能完成交换:

; 函数功能:交换32位寄存器中的两个16位半字
; 输入:R0 = 待交换的32位值
; 输出:R0 = 交换后的32位值
swap_halfwords_asm:
    MOV R1, R0, LSR #16   ; 将R0的高半字右移16位到R1
    MOV R0, R0, LSL #16   ; 将R0的低半字左移16位到高位置
    ORR R0, R0, R1        ; 合并R0的高半字(原低半字)和R1的低半字(原高半字)
    BX LR                 ; 返回

这段汇编代码没有任何冗余操作,执行周期极短,适合对延迟敏感的场景。

额外优化小贴士

  • 批量预处理:如果是传输连续的音频数据流,建议在DMA启动前一次性完成所有数据的半字交换,不要在DMA回调或中断中实时处理,避免占用CPU资源影响其他任务。
  • 编译器优化:开启-O2-O3优化后,C语言的位操作代码会被编译成和汇编几乎完全一样的指令,所以不需要刻意写汇编,除非你需要精确控制指令序列。
  • 字节序验证:上面的方案都是基于Cortex-M4的小端模式设计的,如果你的系统是大端模式(非常罕见),需要调整半字的索引顺序。

内容的提问来源于stack exchange,提问作者Tobi

火山引擎 最新活动