2025年7月


问题现象(CubeMX + CLion + CMake)

使用 CubeMX 生成工程、勾选 DSP 中间件后,能正常 #include "arm_math.h",但调用 arm_sin_f32() 等函数时报如下错误:

undefined reference to 'arm_sin_f32'

问题根源

  • arm_math.h 只是头文件,声明了函数,但没有实现。
  • STM32CubeMX 默认只生成了:

    Middlewares/ST/ARM/DSP/Inc/     ✅ 头文件
    Middlewares/ST/ARM/DSP/Lib/     ❌ 预编译库(未自动链接)

    没有包含源码 Source/ 或自动链接静态库 .a,因此函数找不到实现,链接失败。


解决方法:链接预编译静态库

步骤 1:确认 .a 文件路径

打开目录:

Middlewares/ST/ARM/DSP/Lib/

找到类似:

libarm_cortexM4lf_math.a

解释:

  • cortexM4:适用于 Cortex-M4 内核(比如 STM32F4、G4、L4 等)
  • lf:表示 支持硬件浮点(link with FPU)
  • 还有其他版本如 libarm_cortexM4l_math.a(soft-float)

步骤 2:在 CMakeLists.txt 中链接库

add_executable(...) 后添加:

target_link_libraries(${PROJECT_NAME}.elf
    ${CMAKE_SOURCE_DIR}/Middlewares/ST/ARM/DSP/Lib/libarm_cortexM4lf_math.a
)

步骤 3:添加 FPU 支持编译和链接参数

target_compile_options(${PROJECT_NAME}.elf PRIVATE
    -mfloat-abi=hard -mfpu=fpv4-sp-d16
)

target_link_options(${PROJECT_NAME}.elf PRIVATE
    -mfloat-abi=hard -mfpu=fpv4-sp-d16
)

注意事项

  • 如果用的是其他内核(如 Cortex-M7),要将 CM4 改为 CM7
  • 如果 .a 是 soft-float 版本(不含 f),-mfloat-abi 要设为 softfp

尝试在MSPM0G3507实现6路ADC DMA采样。

TI单片机的ADC DMA与FIFO高度绑定,一开始不了解FIFO,以为只是单纯的先进先出队列,后在使用时发现数据错乱的问题。
翻阅数据手册发现

ADC 的 FIFO 总是将两个样本打包成一个 32 位的 Word 数据。当你读取 FIFODAT 寄存器(无论是 CPU 还是 DMA),你总是会得到一个 32 位的 Word

也就是说DMA读取的FIFO结果不能直接使用,需要处理,分离高16位与低16位。

分离示例

void extract_samples(void)
{
    for (int i = 0; i < ADC_FIFO_WORD_COUNT; ++i) {
        uint32_t word = gADCWordBuffer[i];
        gADCSamples[i * 2]     = word & 0xFFFF;
        if ((i * 2 + 1) < ADC_CHANNEL_COUNT) {
            gADCSamples[i * 2 + 1] = (word >> 16) & 0xFFFF;
        }
    }
}

另外sysconfig工具中DMA Samples Count比较有迷惑性。官方文档中对 DMA Samples Count 的定义:

Set number of ADC results to be transferred on a DMA trigger.

它不是指“DMA 一次搬运多少数据”,而是:ADC FIFO 每累计 N 个样本,就触发一次 DMA 请求,而不是采完一个完整序列才触发。

实际应用中与adc采样通道数相同即可(其实只要有值就可以)

Transfer Size项才是真真有用的,因为FIFO将两个样本打包成一个 32 位的 Word 数据,所以Transfer Size应该为样本数的一半。一般建议在主程序调用DL_DMA_setTransferSize实现

全功能示例代码

#include "ti_msp_dl_config.h"

#define ADC_CHANNEL_COUNT 6
#define ADC_FIFO_WORD_COUNT ((ADC_CHANNEL_COUNT + 1) / 2)  // 每个Word打包2个样本

volatile uint32_t gADCWordBuffer[ADC_FIFO_WORD_COUNT]; // 每个word包含两个样本
volatile uint16_t gADCSamples[ADC_CHANNEL_COUNT];      // 提取后的样本数组

void extract_samples(void)
{
    for (int i = 0; i < ADC_FIFO_WORD_COUNT; ++i) {
        uint32_t word = gADCWordBuffer[i];
        gADCSamples[i * 2]     = word & 0xFFFF;
        if ((i * 2 + 1) < ADC_CHANNEL_COUNT) {
            gADCSamples[i * 2 + 1] = (word >> 16) & 0xFFFF;
        }
    }
}

int main(void)
{
    SYSCFG_DL_init();

    // 设置 DMA
    DL_DMA_setSrcAddr(DMA, DMA_CH0_CHAN_ID, (uint32_t) DL_ADC12_getFIFOAddress(ADC12_0_INST));
    DL_DMA_setDestAddr(DMA, DMA_CH0_CHAN_ID, (uint32_t) gADCWordBuffer);
    DL_DMA_setTransferSize(DMA, DMA_CH0_CHAN_ID, ADC_FIFO_WORD_COUNT);

    // 启用 DMA
    DL_DMA_enableChannel(DMA, DMA_CH0_CHAN_ID);

    // 启动 ADC 转换
    DL_ADC12_startConversion(ADC12_0_INST);

    while (1) {
        // 每次 DMA 更新完 gADCWordBuffer,手动提取 16位样本
        extract_samples();
        // 你可以在这里使用 gADCSamples[0..2] 的数据

        __NOP();  // 仅用于调试打断点
    }
}