DFSDM入坑体验随手记.

/ 2评 / 4

乱写一通的笔记.
先配置SAI PLL时钟.

    if (   (Frequency == AUDIO_FREQUENCY_11K)
        || (Frequency == AUDIO_FREQUENCY_22K)
        || (Frequency == AUDIO_FREQUENCY_44K) )
  {
    /* Configure PLLSAI prescalers */
    /* SAI clock config
    PLLSAI1_VCO= 8 Mhz * PLLSAI1N = 8 * 24 = VCO_192M
    SAI_CK_x = PLLSAI1_VCO/PLLSAI1P = 192/17 = 11.294 Mhz */
    RCC_ExCLKInitStruct.PeriphClockSelection    = RCC_PERIPHCLK_SAI1;
    RCC_ExCLKInitStruct.PLLSAI1.PLLSAI1N        = 24;
    RCC_ExCLKInitStruct.PLLSAI1.PLLSAI1P        = 17;
    RCC_ExCLKInitStruct.PLLSAI1.PLLSAI1ClockOut = RCC_PLLSAI1_SAI1CLK;
    RCC_ExCLKInitStruct.Sai1ClockSelection      = RCC_SAI1CLKSOURCE_PLLSAI1;
  }
  else /* AUDIO_FREQUENCY_8K, AUDIO_FREQUENCY_16K, AUDIO_FREQUENCY_48K, AUDIO_FREQUENCY_96K */
  {
    /* SAI clock config
    PLLSAI1_VCO= 8 Mhz * PLLSAI1N = 8 * 43 = VCO_344M
    SAI_CK_x = PLLSAI1_VCO/PLLSAI1P = 344/7 = 49.142 Mhz */
    RCC_ExCLKInitStruct.PeriphClockSelection    = RCC_PERIPHCLK_SAI1;
    RCC_ExCLKInitStruct.PLLSAI1.PLLSAI1N        = 43;
    RCC_ExCLKInitStruct.PLLSAI1.PLLSAI1P        = 7;
    RCC_ExCLKInitStruct.PLLSAI1.PLLSAI1ClockOut = RCC_PLLSAI1_SAI1CLK;
    RCC_ExCLKInitStruct.Sai1ClockSelection      = RCC_SAI1CLKSOURCE_PLLSAI1;
  }

然后配置Channel和Filter.先是HAL_DFSDM_ChannelInit,再HAL_DFSDM_FilterInit.其中假设我们是96K的音频频率,SAI_CK是49.142MHz(实际上最理想49.152MHz),16Bit的分辨率,再由于配置了10倍的OverSample.按照程序除以32就是1.536MHz,这个数除以16Bit就是96K采样率.倒推就是(49.15200 MHz) / (96 kHz * 16) = 32分频,32就是这么得到的.然后后面配了个TIM8做触发?

static uint8_t AUDIO_DFSDMx_Init(uint32_t AudioFreq)
{
  /*####CHANNEL 2####*/
  hAudioIn.hDfsdmLeftChannel.Init.OutputClock.Activation   = ENABLE;
  hAudioIn.hDfsdmLeftChannel.Init.OutputClock.Selection    = DFSDM_CHANNEL_OUTPUT_CLOCK_AUDIO;
  /* Set the DFSDM clock OUT audio frequency configuration */
  hAudioIn.hDfsdmLeftChannel.Init.OutputClock.Divider      = DFSDMClockDivider(AudioFreq);
  hAudioIn.hDfsdmLeftChannel.Init.Input.Multiplexer        = DFSDM_CHANNEL_EXTERNAL_INPUTS;
  hAudioIn.hDfsdmLeftChannel.Init.Input.DataPacking        = DFSDM_CHANNEL_STANDARD_MODE;
  hAudioIn.hDfsdmLeftChannel.Init.Input.Pins               = DFSDM_CHANNEL_SAME_CHANNEL_PINS;
  /* Request to sample stable data for LEFT micro on Rising edge */
  hAudioIn.hDfsdmLeftChannel.Init.SerialInterface.Type     = DFSDM_CHANNEL_SPI_RISING;
  hAudioIn.hDfsdmLeftChannel.Init.SerialInterface.SpiClock = DFSDM_CHANNEL_SPI_CLOCK_INTERNAL;
  hAudioIn.hDfsdmLeftChannel.Init.Awd.FilterOrder          = DFSDM_CHANNEL_SINC1_ORDER;
  hAudioIn.hDfsdmLeftChannel.Init.Awd.Oversampling         = 10;
  hAudioIn.hDfsdmLeftChannel.Init.Offset                   = 0;
  hAudioIn.hDfsdmLeftChannel.Init.RightBitShift            = DFSDMRightBitShift(AudioFreq);
  hAudioIn.hDfsdmLeftChannel.Instance                      = DFSDM1_Channel2;
    /* Init the DFSDM Channel */
  if (HAL_DFSDM_ChannelInit(&hAudioIn.hDfsdmLeftChannel) != HAL_OK)
  {
    return AUDIO_ERROR;
  }
  /*####FILTER 0####*/
  BSP_AUDIO_hDfsdmLeftFilter.Init.RegularParam.Trigger         = DFSDM_FILTER_SW_TRIGGER;
  BSP_AUDIO_hDfsdmLeftFilter.Init.RegularParam.FastMode        = ENABLE;
  BSP_AUDIO_hDfsdmLeftFilter.Init.RegularParam.DmaMode         = ENABLE;
  BSP_AUDIO_hDfsdmLeftFilter.Init.InjectedParam.Trigger        = DFSDM_FILTER_SW_TRIGGER;
  BSP_AUDIO_hDfsdmLeftFilter.Init.InjectedParam.ScanMode       = DISABLE;
  BSP_AUDIO_hDfsdmLeftFilter.Init.InjectedParam.DmaMode        = DISABLE;
  BSP_AUDIO_hDfsdmLeftFilter.Init.InjectedParam.ExtTrigger     = DFSDM_FILTER_EXT_TRIG_TIM8_TRGO;
  BSP_AUDIO_hDfsdmLeftFilter.Init.InjectedParam.ExtTriggerEdge = DFSDM_FILTER_EXT_TRIG_BOTH_EDGES;
  BSP_AUDIO_hDfsdmLeftFilter.Init.FilterParam.SincOrder        = DFSDMFilterOrder(AudioFreq);
  /* Set the DFSDM Filters Oversampling to have correct sample rate */
  BSP_AUDIO_hDfsdmLeftFilter.Init.FilterParam.Oversampling     = DFSDMOverSampling(AudioFreq);
  BSP_AUDIO_hDfsdmLeftFilter.Init.FilterParam.IntOversampling  = 1;
  BSP_AUDIO_hDfsdmLeftFilter.Instance                          = AUDIO_DFSDMx_LEFT_FILTER;
    /* Init the DFSDM Filter */
  if (HAL_DFSDM_FilterInit(&BSP_AUDIO_hDfsdmLeftFilter) != HAL_OK)
  {
    return AUDIO_ERROR;
  }
  /* Configure regular channel */
  if (HAL_DFSDM_FilterConfigRegChannel(&BSP_AUDIO_hDfsdmLeftFilter,
                                      DFSDM_CHANNEL_2,
                                      DFSDM_CONTINUOUS_CONV_ON) != HAL_OK)
  {
    return AUDIO_ERROR;
  }
  return AUDIO_OK;
}

注册了3个CallBack,分别是传输完成中断,传输一半中断,传输错误中断.AudioRecorder_Error_Callback + AudioRecorder_HalfTransfer_CallBack + AudioRecorder_TransferComplete_CallBack,ERROR就是设置一个ERROR标志.传一半和传完都是给一个信号量给系统.

/**
  * @brief  Callback invoked when second half of the record buffer has been
  *         filled in.
  * @param  None
  * @retval None
  */
static void AudioRecorder_TransferComplete_CallBack(void)
{
  if(hAudioRecorder.state == AUDIORECORDER_START)
  {
    if (osMessagePut(hAudioRecorder.osMsgId, RECORD_BUFFER_OFFSET_FULL, 0) != osOK)
    {
      Error_Handler();
    }
  }
}
/**
  * @brief  Callback invoked when first half of the record buffer has been
  *         filled in.
  * @param  None
  * @retval None
  */
static void AudioRecorder_HalfTransfer_CallBack(void)
{
  if(hAudioRecorder.state == AUDIORECORDER_START)
  {
    if (osMessagePut(hAudioRecorder.osMsgId, RECORD_BUFFER_OFFSET_HALF, 0) != osOK)
    {
      Error_Handler();
    }
  }
}

之前就说过他是启动一个叫AudioRecorder_Thread的任务.所以,实际上就是任务里面做接受的.收消息函数.

其中事件就两个:RECORD_BUFFER_OFFSET_HALF + RECORD_BUFFER_OFFSET_FULL.我简单抽取了一下程序.

case RECORD_BUFFER_OFFSET_HALF:
  if (AudioRecorder_QspiProgram ((uint8_t*)hAudioRecorder.buffer,
								 hAudioRecorder.RecordTotalSize,
								 RECORD_BUFFER_SIZE,
								 &NbBytesWritten) != 0)
  {
	Error_Handler();
  }
  hAudioRecorder.RecordTotalSize += NbBytesWritten;
  break;
case RECORD_BUFFER_OFFSET_FULL:
  if (AudioRecorder_QspiProgram ((uint8_t*)&hAudioRecorder.buffer[RECORD_BUFFER_SIZE/2],
								 hAudioRecorder.RecordTotalSize,
								 RECORD_BUFFER_SIZE,
								 &NbBytesWritten) != 0)
  {
	Error_Handler();
  }
  hAudioRecorder.RecordTotalSize += NbBytesWritten;
  /* Toggle the green led */
  BSP_LED_Toggle(LED_GREEN);
  break;

总体来说就是传过来就写入,但是写入为什么是hAudioRecorder.buffer和hAudioRecorder.buffer/2呢,如果已经传了一半, 那么就是从0 到 一半的数据都是有效的,如果全部传完,那么就是从一半到最后就是有效,RecordTotalSize是写入数据位置指针.但是哪里开始录呢?那就得看AudioRecorderExec -> AudioRecorder_Run.

static void AudioRecorder_Run(void)
{
  JOYState_TypeDef JOYState = JOY_NONE;
  /* Start audio recorder */
  if (AudioRecorder_Start(RECORD_SAMPLE_RATE) != AUDIORECORDER_ERROR_NONE)
  {
    Error_Handler();
  }
  /* Audio recorder event loop */
  do
  {
    JOYState = kMenu_GetEvent(osWaitForever);
  } while (JOYState != JOY_LEFT );
  /* Stop audio recorder */
  if (AudioRecorder_Stop() != AUDIORECORDER_ERROR_NONE)
  {
    Error_Handler();
  }
}

实际上一个AudioRecorder_Start,一个AudioRecorder_Stop.AudioRecorder_Start内实际调用BSP_AUDIO_IN_Record,然后是HAL_DFSDM_FilterRegularStart_DMA,实际上录音到LeftRecBuff,那pRecBuf在哪里呢?原来也有HAL_DFSDM_FilterRegConvCpltCallback + HAL_DFSDM_FilterRegConvHalfCpltCallback + HAL_DFSDM_FilterErrorCallback,看名字知道最后一个是处理错误的,前面两个就是半传输和完全传输,既然DFSDM也有半输出和所有传输,但是这个跟之前的传输一半传输完成好像没交集啊,不是的,他注册了.因为下面代码.

  /* Set Callback function pointers */
  BSP_AUDIO_IN_RegisterCallbacks(AudioRecorder_Error_Callback,
                                 AudioRecorder_HalfTransfer_CallBack,
                                 AudioRecorder_TransferComplete_CallBack);

在中断内,把东西算完了,然后buf填好,回调之前设定的传输一半/完成函数.

/**
  * @brief  Half regular conversion complete callback.
  * @param  hdfsdm_filter : DFSDM filter handle.
  * @retval None
  */
void HAL_DFSDM_FilterRegConvHalfCpltCallback(DFSDM_Filter_HandleTypeDef *hdfsdm_filter)
{
  uint32_t index;
  uint32_t recbufsize = (hAudioIn.RecSize/DEFAULT_AUDIO_IN_CHANNEL_NBR);
  for(index = 0; index < (recbufsize/2); index++)
  {
    hAudioIn.pRecBuf[index] = (uint16_t)(SaturaLH((hAudioIn.LeftRecBuff[index] >> 8), -32768, 32767));
  }
  /* Invoke the registered 'HalfTransfer' callback function (if any) */
  if (hAudioIn.CbHalfTransfer != (Audio_CallbackTypeDef)NULL)
  {
    hAudioIn.CbHalfTransfer();
  }
}

如何STOP,那就开DMA.HAL_DFSDM_FilterRegularStop_DMA,然后把用到的buf释放.在FreeRTOS里面是pvPortMalloc和vPortFree作为申请释放函数.
实际上录音模块虽然非常复杂,但是处理起来没那么难.参数微调各种,慢慢研究.
全面总结:配置时钟->配置CH->配置Filter->开始DMA->......->关闭DMA(停止)

  1. Damon说道:

    大神您好,麻烦问一下Board support package 中 函数uint8_t BSP_AUDIO_IN_Record(uint16_t *pbuf, uint32_t size)是怎么工作的?比如我要录20秒音频,用BSP_AUDIO_IN_Init初始化完,调用BSP_AUDIO_IN_Record,记入20秒音频,是不是每次调用BSP_AUDIO_IN_Record 就仅仅只记入一个sample吗,然后20s的音频就需要不断调用这个函数N次(基于系统时钟)?如果是这样的话,每次调用这个函数,指针pbuf要自加吗?是自加4吗?

    • TaterLi说道:

      @Damon 传递进去是个地址,不会改变你穿进去的地址,你可以理解成传递了一个数组,然后他给数组里面填数据,至于填多少数据,DMA的半空中断决定.

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注