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

参考

Keil MDK STM32系列(八) STM32F4基于HAL的PWM和定时器输出音频的更多相关文章

  1. Keil MDK STM32系列(七) STM32F4基于HAL的PWM和定时器

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  2. Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  3. Keil MDK STM32系列(九) 基于HAL和FatFs的FAT格式SD卡TF卡读写

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  4. Keil MDK STM32系列(四) 基于抽象外设库HAL的STM32F401开发

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  5. Keil MDK STM32系列(六) 基于抽象外设库HAL的ADC模数转换

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  6. Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401开发

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  7. Keil MDK STM32系列(三) 基于标准外设库SPL的STM32F407开发

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  8. Keil MDK STM32系列(五) 使用STM32CubeMX创建项目基础结构

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  9. SQL Server 2008空间数据应用系列八:基于Bing Maps(Silverlight)的空间数据存储

    原文:SQL Server 2008空间数据应用系列八:基于Bing Maps(Silverlight)的空间数据存储 友情提示,您阅读本篇博文的先决条件如下: 1.本文示例基于Microsoft S ...

随机推荐

  1. ORALE 误删表 Flashback 恢复表

    昨天因为种种原因误删了很多表(160多个),一下炸了锅. 我知道影响很大,第一时间想到使用 Flashback 恢复过来.Flashback 听过很久,但是没真正上手过,恢复之后发现使用起来也很简单. ...

  2. EhCache简单入门

    一 介绍 EhCache 是一个纯Java的进程内缓存框架,具有快速.精干等特点,是Hibernate中默认CacheProvider.Ehcache是一种广泛使用的开源Java分布式缓存.主要面向通 ...

  3. CF946B Weird Subtraction Process 题解

    Content 有两个数 \(a,b\),执行如下操作: 如果 \(a,b\) 中有一个数是 \(0\),结束操作,否则跳到操作 \(2\). 如果 \(a\geqslant 2b\),那么 \(a\ ...

  4. 开发webpart时建立图像文件夹和CSS,js文件夹

    如图所示:是通过添加映射来完成,做好之后,把图像拷到文件夹时,当ascx文件里需要用到图像时,直接把图像拖到ascx文件里的位置.这样就知道该图像的路径 了.

  5. libevent源码学习(9):事件event

    目录在event之前需要知道的event_baseevent结构体创建/注册一个event向event_base中添加一个event设置event的优先级激活一个event删除一个event获取指定e ...

  6. 分享 NET 5.x 自定义文件日志实现 原汁原味

    下面直接贴出实现代码 FileLoggerProvider /// <summary> /// 文件记录器提供商 /// </summary> public class Fil ...

  7. centos7使用Dockerfile运行mysql库并初始化数据

    Dockerfile文件(文件名一定要这个) FROM mysql:5.7 WORKDIR /docker-entrypoint-initdb.d ENV LANG=C.UTF-8 ADD test. ...

  8. MFC屏蔽按键ESC、ENTER、Alt+F4

    1.重写 重写下面的函数 virtual BOOL PreTranslateMessage(MSG* pMsg); 2.函数体 BOOL Cfile_trans_codeDlg::PreTransla ...

  9. Sum Of Gcd(hdu 4676)

    Sum Of Gcd Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65535/32768 K (Java/Others)Total ...

  10. Saving Beans(hud3037)

    Saving Beans Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Tota ...