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. Numpy.frompyfunc()将计算单个值的函数转化为计算数组中每个元素的函数

    Numpy.frompyfunc()将计算单个值的函数转化为计算数组中每个元素的函数 不再通过遍历,对数组中的元素进行运算,利用frompyfunc()将计算单个值的函数转化为计算数组中每个元素的函数 ...

  2. CF1455A Strange Functions 题解

    Content 定义一个函数 \(f(x)\) 为 \(x\) 翻转并去掉前导零之后的数,现在有 \(t\) 组询问,每组询问给定一个整数 \(n\),请求出对于所有的 \(1\leqslant x\ ...

  3. RegExp正则表达式(三)–js中正则表达式的定义

    在js中,RegExp正则表达式的定义有两种方式:一种是普通方式,另一种是构造函数方式.无论是那种定义正则表达式的方式,它们都会返回RegExp对象. 普通方式定义正则表达式的格式 语法: var 变 ...

  4. java 数据类型:ArrayList;LinkList性能分析

    各种线性表的性能分析. java提供的List就是一个线性表接口,ArrayList和LinkedList是线性表的两种实现.基于数组的线性表和基于链表的线性表. 一般来说,我们无需理会ArrayLi ...

  5. org.apache.jasper.runtime.ELContextImpl cannot be cast to org.apache.jasper.el.ELContextImpl

    org.apache.jasper.runtime.ELContextImpl cannot be cast to org.apache.jasper.el.ELContextImpl错误怎么解决: ...

  6. ligerui的jquery.validate验证需要添加validate="{required:true,minlength:8,equalTo:'#newpassword'}"

    ligerui的jquery.validate验证需要添加validate="{required:true,minlength:8,equalTo:'#newpassword'}"

  7. 大小端(内存、寄存器、CPU)

    CPU能进行32位操作,关键是寄存器有32位,数据总线也有32位. 为了表示方便,我们可以把32位寄存器从左到右,与内存中一个双字的四个字节地址从低到高对应. 如果CPU把寄存器的左端定义为高位,则带 ...

  8. centos使用docker 安装 rabbitMq 消息队列

    1.拉取镜像 docker pull rabbitmq:3-management 如果出现报错: Get https://registry-1.docker.io/v2/: net/http: req ...

  9. vsp配合Qt5开发,解决virtual void * __cdecl PopDialogManger::qt_metacast

    Qt错误提示 virtual void * __cdecl PopDialogManger::qt_metacast(char const*)"(?qt_metacast@PopDialog ...

  10. 【LeetCode】1464. 数组中两元素的最大乘积 Maximum Product of Two Elements in an Array (Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客:http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 暴力 找最大次大 日期 题目地址:https://le ...