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

STM32F746ZGTX SPI接收DMA获取垃圾数据问题求助

解决STM32F746 SPI DMA采集ADC数据出现垃圾数据的问题

我之前在STM32项目里也碰到过几乎一模一样的SPI DMA问题,给你梳理几个最可能的排查点,按顺序检查应该能解决你的问题:

1. 确认DMA通道与SPI的映射关系完全正确

STM32F7的外设和DMA通道是严格绑定的,比如STM32F746ZGTX的SPI1:

  • RX对应DMA2 Stream0,Channel3
  • TX对应DMA2 Stream5,Channel3
    如果初始化DMA时写错了通道或流号,数据根本无法正确传输,直接就是垃圾值。一定要对照芯片参考手册的DMA映射表核对,别凭记忆乱写。

2. 严格遵循初始化顺序

正确的初始化流程必须是:

  • 先配置SPI的主模式参数(时钟极性、相位、数据宽度、NSS模式等,要和轮询时完全一致)
  • 再初始化DMA的RX和TX通道
  • 最后开启SPI的DMA请求(SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx | SPI_I2S_DMAReq_Tx, ENABLE);
    如果先开DMA再初始化SPI,很可能会触发错误的初始传输,导致数据混乱。

3. 保证SPI与DMA的数据宽度完全匹配

假设你的ADC输出是16位数据:

  • SPI要配置为SPI_DataSize_16b
  • DMA的DMA_PeripheralDataSizeDMA_MemoryDataSize都要设为DMA_PeripheralDataSize_HalfWord
    如果一边是8位一边是16位,数据会被错位拼接,自然变成垃圾值。

4. 检查DMA传输的内存地址和长度

  • 存储接收数据的内存必须是全局变量或静态变量,不能用栈上的局部变量——函数执行完后栈内存会被释放,读取时就是随机值。
  • DMA的DMA_BufferSize要和实际需要传输的数据长度完全一致,而且TX和RX的长度必须相等(因为SPI是全双工,发多少字节就要收多少字节)。

5. 正确配置DMA模式与中断

  • 单次采集建议用DMA_Mode_Normal,循环模式要注意每次传输完成后重置计数器,避免重复触发导致数据覆盖。
  • 必须开启DMA传输完成中断,在中断回调里处理数据,同时一定要清除DMA_FLAG_TC(传输完成标志位),不然下次传输可能无法正常触发。
  • 注意:DMA模式下,SPI的RXNE/TXE标志会由硬件自动处理,不要手动去清除,否则会干扰DMA的传输逻辑。

6. 硬件时序与CS控制不能出错

  • 如果是手动控制CS引脚,必须在DMA传输开始前拉低CS,传输完成后再拉高,中途不能跳变——轮询时你可能是手动控制这个时序,但DMA模式下容易忽略,导致ADC没有正确响应,返回垃圾数据。
  • 确认SPI时钟频率在ADC的允许范围内,虽然轮询时正常,但DMA的传输时序更严格,过高的时钟可能导致ADC采样错误。

参考初始化代码片段

下面是一个简化的示例,你可以对照调整:

// 全局变量存储数据
uint16_t adc_tx_buffer[8]; // 存储要发送的控制字节
uint16_t adc_rx_buffer[8]; // 存储接收的ADC数据

void SPI_DMA_Init(void) {
    // 1. 初始化SPI(主模式,和轮询参数一致)
    SPI_InitTypeDef SPI_InitStruct;
    SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
    SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    SPI_InitStruct.SPI_DataSize = SPI_DataSize_16b;
    SPI_InitStruct.SPI_CPOL = SPI_CPOL_High;
    SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge;
    SPI_InitStruct.SPI_NSS = SPI_NSS_Soft; // 手动控制CS
    SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;
    SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_Init(SPI1, &SPI_InitStruct);

    // 2. 初始化DMA RX通道
    DMA_InitTypeDef DMA_Init_RX;
    DMA_Init_RX.DMA_Channel = DMA_Channel_3;
    DMA_Init_RX.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR;
    DMA_Init_RX.DMA_Memory0BaseAddr = (uint32_t)adc_rx_buffer;
    DMA_Init_RX.DMA_DIR = DMA_DIR_PeripheralToMemory;
    DMA_Init_RX.DMA_BufferSize = 8;
    DMA_Init_RX.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_Init_RX.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_Init_RX.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    DMA_Init_RX.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_Init_RX.DMA_Mode = DMA_Mode_Normal;
    DMA_Init_RX.DMA_Priority = DMA_Priority_High;
    DMA_Init(DMA2_Stream0, &DMA_Init_RX);

    // 3. 初始化DMA TX通道
    DMA_InitTypeDef DMA_Init_TX;
    DMA_Init_TX.DMA_Channel = DMA_Channel_3;
    DMA_Init_TX.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR;
    DMA_Init_TX.DMA_Memory0BaseAddr = (uint32_t)adc_tx_buffer;
    DMA_Init_TX.DMA_DIR = DMA_DIR_MemoryToPeripheral;
    DMA_Init_TX.DMA_BufferSize = 8;
    DMA_Init_TX.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_Init_TX.DMA_MemoryInc = DMA_MemoryInc_Enable;
    DMA_Init_TX.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
    DMA_Init_TX.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
    DMA_Init_TX.DMA_Mode = DMA_Mode_Normal;
    DMA_Init_TX.DMA_Priority = DMA_Priority_Medium;
    DMA_Init(DMA2_Stream5, &DMA_Init_TX);

    // 4. 开启SPI DMA请求和DMA中断
    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx | SPI_I2S_DMAReq_Tx, ENABLE);
    DMA_ITConfig(DMA2_Stream0, DMA_IT_TC, ENABLE);
    NVIC_EnableIRQ(DMA2_Stream0_IRQn);

    // 启用SPI
    SPI_Cmd(SPI1, ENABLE);
}

// DMA传输完成中断回调
void DMA2_Stream0_IRQHandler(void) {
    if (DMA_GetITStatus(DMA2_Stream0, DMA_IT_TCIF0) != RESET) {
        DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TCIF0);
        
        // 在这里处理采集到的ADC数据
        process_adc_data(adc_rx_buffer);

        // 如果需要重复采集,重置DMA计数器并重启传输
        DMA_Cmd(DMA2_Stream0, DISABLE);
        DMA_SetCurrDataCounter(DMA2_Stream0, 8);
        DMA_Cmd(DMA2_Stream0, ENABLE);
        
        DMA_Cmd(DMA2_Stream5, DISABLE);
        DMA_SetCurrDataCounter(DMA2_Stream5, 8);
        DMA_Cmd(DMA2_Stream5, ENABLE);

        // 手动控制CS:传输完成后拉高
        GPIO_SetBits(GPIOA, GPIO_Pin_4);
    }
}

// 启动一次采集的函数
void start_adc_sampling(void) {
    // 填充控制字节到tx buffer
    for (int i = 0; i < 8; i++) {
        adc_tx_buffer[i] = 0x0001; // 替换成你的ADC控制字节
    }

    // 拉低CS,启动传输
    GPIO_ResetBits(GPIOA, GPIO_Pin_4);
    DMA_Cmd(DMA2_Stream0, ENABLE);
    DMA_Cmd(DMA2_Stream5, ENABLE);
}

按照上面的步骤排查和调整,应该就能解决DMA获取垃圾数据的问题了。

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

火山引擎 最新活动