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_PeripheralDataSize和DMA_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




