STM32:使用 DMA 时 I2S 输入不起作用

Posted

技术标签:

【中文标题】STM32:使用 DMA 时 I2S 输入不起作用【英文标题】:STM32: I2S input not working when using DMA 【发布时间】:2021-12-31 00:36:38 【问题描述】:

我正在尝试在单声道设置中将“STM32F401RET6 Nucleo-64”微控制器与Adafruit I2S microphone 连接起来。为了完成这项任务,我想启用 DMA。

我使用STM32 Cube IDE中的Device Configuration Tool来激活I2S3,使用以下参数:

I2S3

全双工主控 传输模式:模式主接收; 通信标准:MSB 优先(左对齐); 数据和帧格式:32 位帧上的 24 位数据; 所选音频频率:48 kHz; 时钟源:I2S PLL 时钟; 时钟极性:低; 主时钟输出已禁用。

DMA

SPI_RX3,DMA 1 流 2,外设到内存,高优先级; FIFO,阈值 = 满,数据宽度 = 半字,突发大小 = 单; 在 NVIC 设置中,为 DMA 1 Stream 2 和 SPI3 启用了中断。

接下来,使用代码生成器工具自动生成起始代码。对此起始代码进行了一些更改:

设置 GPIO_PULL_DOWN 使三态始终读入 0;

我已经使用示波器绘制了来自麦克风的数字数据波形。这似乎是正确的,即声音触发了麦克风,这在最重要的位中是可见的。如果我是正确的,这使得错误在于将数据读入正确的格式。 要执行单声道测量,数据表指出应该使用 100k Ohm 电阻器,这在我的设置中提供。

在我的 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 行设置断点时,微控制器内存不会相应地更新其值。所有值都保持为零。两个 DMA 状态标志都设置为 1。 我猜这个问题与时间有关,但我直到现在才能够解决这个问题。

Link to complete source code on GitHub

Link to HAL_I2S_Receive_DMA function on GitHub

提前致谢。

【问题讨论】:

你在没有 DMA 的情况下解决了任务吗?这可能不是您的问题,但函数 HAL_I2S_Receive_DMA 在收到任何内容之前返回,注释掉的循环将没有数据可处理。使用HAL_I2S_GetState 检查传输状态,或回调 - HAL_I2S_RxCpltCallback/HAL_I2S_ErrorCallback 到目前为止,没有 DMA,我无法解决任务。我启用了 DMA,因为我认为 CPU 将忙于处理来自麦克风数据的中断,因为它以 48kHz 音频采样率和 3.3MHz 位时钟工作。然而,这只是一个假设。 【参考方案1】:

DMA 必须在 ADC 外设之前初始化,但生成的代码以错误的顺序执行初始化。众所周知,当前 STM32CubeIDE 的默认配置存在小缺陷。尝试改变

MX_GPIO_Init();
MX_I2S3_Init();
MX_DMA_Init(); // wrong order

MX_GPIO_Init();
MX_DMA_Init(); // right order
MX_I2S3_Init();

要进行永久性更改,

    打开“项目管理器”(引脚排列/时钟配置附近的选项卡) 项目管理器 → 高级设置 → 生成的函数调用 使用此列表附近的小箭头按钮将 MX_DMA_Init() 移到上方 MX_I2S3_Init() 保存项目,生成的代码将有正确的顺序

【讨论】:

感谢您的建议!我相应地更改了顺序(在代码和项目管理器中)。不幸的是,它并没有解决问题,尽管它肯定是解决方案的一部分。因此,我会继续尝试这个正确的顺序(MX_DMA_Init 在 MX_I2S3_Init 之前)。【参考方案2】:

根据我的经验,

HAL_I2S_Receive_DMA()函数是配置地址 (如果你学习LL API,你需要手动设置源/目标地址和数据长度:[LL_DMA_SetDataLength()])

所以,你可以在 While(1) 循环之前移动它。

如果要读取/处理缓冲区“数据”,可以使用 dma 中断回调函数,在 HAL API 中是:HAL_I2S_RxHalfCpltCallback(), HAL_I2S_RxCpltCallback()

================================================ ======================= 更新:

// init method and buff
...
xx_init()
volatile int16_t data[500] = 0;
int16_t data_shifted[500];
...
// 
HAL_I2S_Receive_DMA(&hi2s3, &data[0], 250);
while(1)
  if (FLAG_half)
    FLAG_half=0;
    // add your code: data shift [0:250]... 
  
  if (FLAG_comp)
    FLAG_comp=0;
    // add your code: data shift [250:500]... 
  


 // end while

 end main

HAL_I2S_RxHalfCpltCallback() 
  FLAG_half=1;

HAL_I2S_RxCpltCallback() 
  FLAG_comp=1;

【讨论】:

LL API函数为:LL_DMA_ConfigAddresses()和LL_DMA_SetDataLength(),分别对应HAL_I2S_Receive_DMA()。 首先:对于迟到的回复,我深表歉意。我怀疑这种方法是否能解决问题:this stm32f4xx_hal_i2s code comment 表示必须使用 HAL_I2S_Receive_DMA() 来使用 DMA 接收数据。该函数然后调用 HAL_I2S_RxHalfCpltCallback() 和 HAL_I2S_RxCpltCallback()。 我有用于从 I2S 接收麦克风数据的更新代码。 DMA 使用循环模式。 HAL_I2S_Receive_DMA() 似乎是一个初始化方法,它告诉 DMA 将数据保存在哪里。 (&数据[0])。然后我们可以使用中断回调函数来做一些工作,就像我展示的那样。注意你的数据长度是500,所以一半是250。你可能需要在HAL_I2S_Receive_DMA()中设置250

以上是关于STM32:使用 DMA 时 I2S 输入不起作用的主要内容,如果未能解决你的问题,请参考以下文章

STM32F0 DMA“输入溢出”

STM32中DMA有啥好处

高效交换半字

配置时无法访问STM32 DMA寄存器

STM32 DMA简述

STM32CubeMX使用之DMA方式使用ADCADC使用遇到的问题