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. Linux core 文件浅析

    浅析Linux下core文件 当我们的程序崩溃时,内核有可能把该程序当前内存映射到core文件里,方便程序员找到程序出现问题的地方.最常出 现的,几乎所有C程序员都出现过的错误就是"段错误& ...

  2. set env export区别

    set env export区别 set,env和export这三个命令都可以用来显示shell变量,区别[root@localhost root]# aaa=bbb[root@localhost r ...

  3. [BUUCTF]REVERSE——[GWCTF 2019]xxor

    [GWCTF 2019]xxor 附件 步骤: 无壳,64位ida载入 程序很简单,首先让我们输入一个字符串,然后进行中间部分的操作,最后需要满足44行的if判断,看一下sub_400770函数 得到 ...

  4. [BUUCTF]PWN——inndy_rop

    inndy_rop 附件 步骤: 例行检查,32位,开启了nx保护 本地调试运行没看出个啥,直接上ida,一开始f5会报错, 找到报错提示的位置,点击option–>general调出如图的界面 ...

  5. Kubernetes 集群无损升级实践 转至元数据结尾

    一.背景 活跃的社区和广大的用户群,使 Kubernetes 仍然保持3个月一个版本的高频发布节奏.高频的版本发布带来了更多的新功能落地和 bug 及时修复,但是线上环境业务长期运行,任何变更出错都可 ...

  6. CF1080B Margarite and the best present 题解

    Content 有 \(t\) 次询问,每次询问给定两个整数 \(l,r\),求 \(\sum\limits_{i=l}^r (-1)^i\times i\). 数据范围:\(1\leqslant t ...

  7. JAVA比较两个版本号的大小

    /** * 比较版本号的大小 (两个版本号格式应尽量相同) * * @param v1 版本号1 * @param v2 版本号2 * @return 正数:v1大 负数:v2大 0:相等 */ pu ...

  8. mybatis基于注解的sql中空字符串判断

    @Select("<script>" + "select c.id from dwzsk_content c " + "WHERE c.` ...

  9. JAVA通过身份证号码获取出生日期、年龄、性别

    JAVA验证身份证号码是否正确:https://www.cnblogs.com/pxblog/p/12038278.html /** * 通过身份证号码获取出生日期(birthday).年龄(age) ...

  10. Android NDK开发篇:如何使用JNI中的global reference和local reference

    JNI提供了一些实例和数组类型(jobject.jclass.jstring.jarray等)作为不透明的引用供本地代码使用.本地代码永远不会直接操作引用指向的VM内部的数据内容.要进行这些操作,必须 ...