Keil MDK STM32系列(八) STM32F4基于HAL的PWM和定时器输出音频

发布者:古宝奇缘最新更新时间:2022-08-15 来源: csdn关键字:Keil  MDK  STM32系列  HAL  PWM  定时器 手机看文章 扫描二维码
随时随地手机看文章

方式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以上, 这样基本上就不会被人耳感知了.


参考

详细说明了STM32的DMA工作方式 https://vivonomicon.com/2019/07/05/bare-metal-stm32-programming-part-9-dma-megamix/

DMA+PWM的位宽讨论 https://community.st.com/s/question/0D50X0000C6bAMdSQM/hal-timers-dma-method-enforces-4bytes-alignment-why-

另一个位宽相关的讨论 https://community.st.com/s/question/0D50X0000B45uUx/generation-of-pwm-wave-with-dma


关键字:Keil  MDK  STM32系列  HAL  PWM  定时器 引用地址:Keil MDK STM32系列(八) STM32F4基于HAL的PWM和定时器输出音频

上一篇:Keil MDK STM32系列(七) STM32F4基于HAL的PWM和定时器
下一篇:Keil MDK STM32系列(九) 基于HAL和FatFs的FAT格式SD卡TF卡读写

小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved