Keil MDK STM32系列(八) STM32F4基於HAL的PWM和定時器輸出音頻


Keil MDK STM32系列

方式1: 通過PWM和TIM輸出音頻

機制

  • 音頻使用一個預生成的的8bit無符號數組, 采樣率為8KHz
  • 輸出包含兩部分, 一部分是TIM2產生連續的PWM, PWM分辨率設置為256, 正好對應8bit PCM采樣
  • 輸出的第二部分是TIM3產生的定時中斷, 中斷的頻率正好是8KHz, 每次中斷都修改一次PWM的占空比
  • 通過調節PWM頻率可以調節輸出音質, PWM頻率越高音質越好(諧振頻率越遠離音頻)
  • 通過調節PWM分辨率可以調節音量, PWM分辨率越高, 音量越低

配置STM32CubeMX

選擇芯片STM32F401CCU6, 創建新項目

系統時鍾

  • System Core -> SYS-> Debug: Serial Wire
  • System Core -> RCC-> High Speed Clock (HSE): Crystal/Ceramic Resonator 啟用外接高速晶振
  • Clock Configuration: (配置為最高84MHz)選擇外部晶振, 連接HSE和PLLCLK, 在HCLK上輸入84回車, 軟件會自動調節各節點倍數

PWM(使用TIM2)

  • Timers -> TIM2
  • Clock Source: Internel Clock, 使用系統的時鍾源
    • Channel1: PWM Generation CH1
    • Counter Settings PWM頻率 = 84MHz / (Perscaler + 1) / (Counter Period + 1)
      • Perscaler: 0
      • Counter Mode: Up
      • Counter Period: 255
      • Internal Clock Division(CKD): No Division
      • auto-reload preload: Enable
    • Trigger Output
      • Master/Slave Mode (MSM bit): Disable
      • Trigger Event Selection: Reset (UG bit from TIMx_EGR)
  • PWM Generation Channel 1
    • Mode: PWM mode 1
    • Pulse: 0
    • Output compare perload: Enable
    • Fast Mode: Disable
    • CH Polarity: High

8KHz定時中斷(使用TM3)

  • Timers -> TIM3
  • 勾選 Internal Clock
  • Counter Settings
    • Prescaler: 0
    • Counter Mode: Up
    • Counter Period: 10499 # 10500 = 84MHz / 8KHz
    • Internal Clock Division (CKD): No division
    • auto-reload preload: Disable
  • Trigger Output (TRGO) Parameters
    • Master/Slave Mode (MSM bit): Disable
    • Trigger Envent Selection: Reset
  • NVIC Settings
    • TIM3 global interrupt: Enable

代碼修改

通過STM32CubeMX生成代碼后, 需要對main.c添加代碼

/* USER CODE BEGIN PV */
uint8_t pwm_buf[] = {125, 125, ..., 126, 125}; // 這里是一個長數組, 可以自己通過工具生成
uint8_t *start = pwm_buf, *end = pwm_buf, *lb = pwm_buf, *rb = (pwm_buf + 27451); // 27451是數組長度
/* USER CODE END PV */

main函數

int main(void)
{
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_TIM2_Init();
  MX_TIM3_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
  HAL_TIM_PWM_Start(&htim2,TIM_CHANNEL_1);
  HAL_TIM_Base_Start_IT(&htim3);
  /* USER CODE END 2 */

  while (1)
  {
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }
}

添加定時器中斷處理函數

/* USER CODE BEGIN 4 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if(htim->Instance==TIM3)
  {
    __HAL_TIM_SetCompare(&htim2, TIM_CHANNEL_1, *start++);
    if (start == rb) {
      start = lb;
    }
  }
}
/* USER CODE END 4 */

輸出效果演示

https://www.bilibili.com/video/BV1pb4y1177L

方式2: 通過PWM+DMA

通過配置成DMA的方式, 可以省掉一個定時器, 並且不需要主進程介入而直接將數組賦值給PWM.

這里有個需要注意的地方, STM32F401的各個TIMx計數器位寬不同, TIM2,TIM5是32bit, 其它的都是16bit, 而STM32F103的TIMx全是16bit位寬的. 之前在這個問題上困惑了很長時間, 后來費了不少工夫測試, 加上對比其它項目代碼的配置才找到原因.

在設置DMA時, DMA_HandleTypeDef.Init.PeriphDataAlignment要與TIMx的計數器位寬一致, 如果沒設置成一致會導致PWM輸出錯誤.
而MemDataAlignment要與數組的數據類型一致, 實際上也要設置成對應的位寬.

根據ST的手冊如果勾選了FIFO, 可以設置為其它位寬, 系統會自動補位, 但是實際測試並不能, 無論如何調整FIFOThreshold, MemBurst, 音頻的前半部分都是錯誤的, 只能播放后半部分. 原因待查.

配置STM32CubeMX

選擇芯片STM32F401CCU6, 創建新項目

系統時鍾

  • System Core -> SYS-> Debug: Serial Wire
  • System Core -> RCC-> High Speed Clock (HSE): Crystal/Ceramic Resonator 啟用外接高速晶振
  • Clock Configuration: (配置為最高84MHz)選擇外部晶振, 連接HSE和PLLCLK, 在HCLK上輸入84回車, 軟件會自動調節各節點倍數

PWM(使用TIM3)

  • Timers -> TIM3
  • Internel Clock: 勾選, 使用系統的時鍾源
  • Channel1: PWM Generation CH1
  • Counter Settings PWM頻率 = 84MHz / (Perscaler + 1) / (Counter Period + 1)
    • Perscaler: 40
    • Counter Mode: Up
    • Counter Period: 255
    • Internal Clock Division(CKD): No Division
    • auto-reload preload: Enable
  • Trigger Output
    • Master/Slave Mode (MSM bit): Disable
    • Trigger Event Selection: Reset (UG bit from TIMx_EGR)
  • PWM Generation Channel 1
    • Mode: PWM mode 1
    • Pulse: 0
    • Output compare perload: Enable
    • Fast Mode: Disable
    • CH Polarity: High

DMA Settings: Add

  • DMA Request: TIM3_CH1/Trig
  • Stream: DMA1 Stream4
  • Direction: Memory To Peripheral
  • Priority: High
  • Mode: Circular
  • Increment Address: Peripheral[不勾選], Memory[勾選]
  • Use Fifo: 不勾選
  • Data Width: Peripheral[Half Word], Memory[Half Word]

代碼修改

只需要在main.c中添加變量和啟動方法

/* USER CODE BEGIN PV */
uint16_t pwm_buffer[] = {125, 125, 128, ...};
/* USER CODE END PV */

//...

MX_TIM3_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start_DMA(&htim3, TIM_CHANNEL_1, (uint32_t *)pwm_buffer, 27452);
/* USER CODE END 2 */

在PA6上就能觀察到PWM, 接上喇叭能聽到輸出. 這種方式因為基頻8KHz就在人耳的聽覺范圍內, 會有持續的明顯的高頻聲, 通過增加RC低通濾波能改善但是無法消除, 最好的方式還是將基頻提升到20KHz以上, 這樣基本上就不會被人耳感知了.

參考


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM