STM32F401RET6使用DMA接收I2S麦克风数据时内存无更新问题
问题描述
我正尝试将STM32F401RET6 Nucleo-64微控制器与Adafruit I2S麦克风以单声道配置对接,为实现该功能,我希望启用DMA功能。
我使用STM32 Cube IDE中的设备配置工具激活I2S3,配置参数如下:
I2S3配置
- 全双工主模式
- 传输模式:主接收模式
- 通信标准:MSB优先(左对齐)
- 数据与帧格式:32位帧承载24位数据
- 选定音频频率:48 kHz
- 时钟源:I2S PLL时钟
- 时钟极性:低
- 主时钟输出禁用
DMA配置
- SPI_RX3,DMA 1 流2,外设到内存,高优先级
- FIFO阈值设为满,数据宽度为半字,突发大小为单次
- NVIC设置中已启用DMA 1 流2和SPI3的中断
随后我使用代码生成工具自动生成初始代码,并对初始代码做了如下修改:
- 设置GPIO_PULL_DOWN,确保三态状态下始终读入0
我已使用示波器采集麦克风输出的数字数据波形,波形显示正常:声音触发麦克风时,最高有效位会出现对应变化,据此我判断问题出在数据读取格式环节。单声道采集所需的100kΩ电阻已按数据手册要求接入电路。
我在main.c程序中调用HAL库函数HAL_I2S_Receive_DMA,尝试填充长度为500的采样数组。
main.c代码如下:
/* Includes ------------------------------------------------------------------*/ #include "main.h" I2S_HandleTypeDef hi2s3; DMA_HandleTypeDef hdma_spi3_rx; /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_I2S3_Init(void); static void MX_DMA_Init(void); int main(void) { HAL_Init(); /* Configure the system clock */ SystemClock_Config(); /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_I2S3_Init(); MX_DMA_Init(); /* Infinite loop */ HAL_StatusTypeDef retval; // return value volatile int16_t data[500] = {0}; int16_t data_shifted[500]; while (1) { retval = HAL_I2S_Receive_DMA(&hi2s3, data, 500); // for(short i=0; i<500; i++){ // data_shifted[i] = data[i] >> 14; // } HAL_Delay(1000); } } void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; /* Configure the main internal regulator output voltage */ __HAL_RCC_PWR_CLK_ENABLE(); __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2); /* Initializes the RCC Oscillators according to the specified parameters in the RCC_OscInitTypeDef structure. */ RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI; RCC_OscInitStruct.HSIState = RCC_HSI_ON; RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI; RCC_OscInitStruct.PLL.PLLM = 8; RCC_OscInitStruct.PLL.PLLN = 84; RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; RCC_OscInitStruct.PLL.PLLQ = 4; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) { Error_Handler(); } /* Initializes the CPU, AHB and APB buses clocks */ RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK) { Error_Handler(); } } static void MX_I2S3_Init(void) { hi2s3.Instance = SPI3; hi2s3.Init.Mode = I2S_MODE_MASTER_RX; hi2s3.Init.Standard = I2S_STANDARD_MSB; hi2s3.Init.DataFormat = I2S_DATAFORMAT_24B; hi2s3.Init.MCLKOutput = I2S_MCLKOUTPUT_DISABLE; hi2s3.Init.AudioFreq = I2S_AUDIOFREQ_48K; hi2s3.Init.CPOL = I2S_CPOL_LOW; hi2s3.Init.ClockSource = I2S_CLOCK_PLL; hi2s3.Init.FullDuplexMode = I2S_FULLDUPLEXMODE_ENABLE; if (HAL_I2S_Init(&hi2s3) != HAL_OK) { Error_Handler(); } } // Enable DMA controller clock static void MX_DMA_Init(void) { /* DMA controller clock enable */ __HAL_RCC_DMA1_CLK_ENABLE(); /* DMA interrupt init */ /* DMA1_Stream2_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA1_Stream2_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA1_Stream2_IRQn); } /*GPIO Initialization Function */ static void MX_GPIO_Init(void) { /* GPIO Ports Clock Enable */ __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); } /* This function is executed in case of error occurrence. */ void Error_Handler(void) { /* Error_Handler_Debug */ /* User can add his own implementation to report the HAL error return state */ __disable_irq(); while (1) { } } #ifdef USE_FULL_ASSERT /** Reports the name of the source file and the source line number where the assert_param error has occurred. */ void assert_failed(uint8_t *file, uint32_t line) { /* User can add his own implementation to report the file name and line number, ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */ } #endif /* USE_FULL_ASSERT */
我对代码进行调试时,在main.c第34行设置断点,发现微控制器内存中采样数组的数值未发生对应更新,所有值始终为0,两个DMA状态标志均被置1。我猜测问题与时序相关,但目前尚未找到解决方法。
问题排查与解决方案
你遇到的问题由几处配置和代码逻辑错误共同导致,按优先级修改即可:
外设初始化顺序错误
HAL库要求DMA初始化必须早于使用DMA的外设初始化,你当前的初始化顺序是MX_GPIO_Init() → MX_I2S3_Init() → MX_DMA_Init(),I2S初始化时DMA还未配置,无法完成句柄绑定。调整顺序为MX_DMA_Init() → MX_GPIO_Init() → MX_I2S3_Init()即可。缺少I2S引脚复用配置
你的MX_GPIO_Init()函数仅开启了GPIO端口时钟,完全没有配置I2S3对应的SCK、WS、SD引脚的复用功能。STM32F4的I2S3默认引脚为PC10(SCK)、PC11(WS)、PC12(SD),需要在GPIO初始化中把这三个引脚配置为复用推挽模式,复用功能选择AF6_SPI3。缺少中断处理函数实现
你已经开启了DMA1流2和SPI3的中断,但没有实现对应的中断服务函数,HAL库的DMA传输逻辑无法正常触发。需要在stm32f4xx_it.c中添加对应的中断函数,内部调用HAL库的处理接口:
void DMA1_Stream2_IRQHandler(void) { HAL_DMA_IRQHandler(&hdma_spi3_rx); } void SPI3_IRQHandler(void) { HAL_I2S_IRQHandler(&hi2s3); }
循环调用DMA接收函数逻辑错误
你在while循环中每秒重复调用一次HAL_I2S_Receive_DMA(),第一次调用启动DMA传输后,未等传输完成就再次调用会打断正在进行的传输,也无法正确处理传输完成事件。建议添加传输完成标志位,在DMA接收完成回调函数中置位标志,主循环判断标志置位后再处理数据、重新启动DMA传输。冗余全双工配置修正(可选)
你仅使用I2S接收功能,不需要开启全双工模式,关闭全双工配置可避免不必要的逻辑冲突。
内容的提问来源于stack exchange,提问作者IsaacNuketon

