Keil MDK STM32系列(八) STM32F4基于HAL的PWM和定时器输出音频
Keil MDK STM32系列
- Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发
- Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401开发
- Keil MDK STM32系列(三) 基于标准外设库SPL的STM32F407开发
- Keil MDK STM32系列(四) 基于抽象外设库HAL的STM32F401开发
- Keil MDK STM32系列(五) 使用STM32CubeMX创建项目基础结构
- Keil MDK STM32系列(六) 基于HAL的ADC模数转换
- Keil MDK STM32系列(七) 基于HAL的PWM和定时器
- Keil MDK STM32系列(八) 基于HAL的PWM和定时器输出音频
- Keil MDK STM32系列(九) 基于HAL和FatFs的FAT格式SD卡TF卡读写
方式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系列(八) STM32F4基于HAL的PWM和定时器输出音频的更多相关文章
- Keil MDK STM32系列(七) STM32F4基于HAL的PWM和定时器
Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...
- Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发
Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...
- Keil MDK STM32系列(九) 基于HAL和FatFs的FAT格式SD卡TF卡读写
Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...
- Keil MDK STM32系列(四) 基于抽象外设库HAL的STM32F401开发
Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...
- Keil MDK STM32系列(六) 基于抽象外设库HAL的ADC模数转换
Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...
- Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401开发
Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...
- Keil MDK STM32系列(三) 基于标准外设库SPL的STM32F407开发
Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...
- Keil MDK STM32系列(五) 使用STM32CubeMX创建项目基础结构
Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...
- SQL Server 2008空间数据应用系列八:基于Bing Maps(Silverlight)的空间数据存储
原文:SQL Server 2008空间数据应用系列八:基于Bing Maps(Silverlight)的空间数据存储 友情提示,您阅读本篇博文的先决条件如下: 1.本文示例基于Microsoft S ...
随机推荐
- Mac 下安装Phonegap开发环境
Mac 下安装Phonegap开发环境 2014.09.11 星期四 评论 0 条 阅读 5,613 次 作者:野草 标签:phonegap ios mac 什么是Phonegap呢?Phon ...
- [源码解析] PyTorch 分布式(17) --- 结合DDP和分布式 RPC 框架
[源码解析] PyTorch 分布式(17) --- 结合DDP和分布式 RPC 框架 目录 [源码解析] PyTorch 分布式(17) --- 结合DDP和分布式 RPC 框架 0x00 摘要 0 ...
- Sysenter/Kifastcallentry hook 检测与恢复
关于Sysenter.Kifastcallentry.中断之类的内核入口hook技术早就烂大街了,可是对hook的检测与恢复代码却是寥寥无几,一切抛开代码将原理的行为都是耍流氓. 下面以Sysente ...
- 逻辑判断(Power Query 之 M 语言)
逻辑真:true 逻辑假:false 与函数:and true and true,结果为TRUE true and false,结果为FALSE false and false,结果为FALSE 或函 ...
- 批处理文件 .bat 并行Arcpy脚本提高效率的思路-提升版
目录 前言 Arcpy脚本 导入库 函数定义 循环实现 批处理(.bat)脚本 前言 我在之前的博客园博客里,阐述了如何编写Arcpy脚本,如何利用Windows bat批处理脚本同时打开多个cmd窗 ...
- LuoguP7071 [CSP-J2020] 优秀的拆分 题解
Content 给定一个数 \(n\),求是否能够拆分成 \(2\) 的正整数次幂的和的形式,并给出具体方案. 数据范围:\(1\leqslant n\leqslant 10^7\). Solutio ...
- LuoguP7259 [COCI2009-2010#3] SORT 题解
Content 请编写一个"频率排序器".输入一个 长度为 \(n\) 的数列 \(A=\{a_1,a_2,\dots,a_n\}\),要求: 按照每个数的出现次数降序排列. 如果 ...
- Django ModelForm表单验证
ModelForm 在使用Model和Form时,都需要对字段进行定义并指定类型,通过ModelForm则可以省去From中字段的定义 应用场景:定制model admin 的时候可以使用.适用于小业 ...
- MySQL数据导入报错:Got a packet bigger than‘max_allowed_packet’bytes的问题
修改my.cnf,需重启mysql. 在 [MySQLd] 部分添加一句(如果存在,调整其值就可以): max_allowed_packet=512M 查找MySql的配置文件my.cnf所在路径参考 ...
- 海康威视Java SDK拉流(一)初始化SDK
19年的时候做了一个视频分析的产品,用户使用的安防摄像机基本的都是海康大华宇视,今天写一下关于Java调用海康威视摄像机的demo,当时也踩了很多坑.写个博客记录一下 测试环境: 系统:Centos ...