zoukankan      html  css  js  c++  java
  • Keil MDK STM32系列(八) STM32F4基于HAL的PWM和定时器输出音频

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

    参考

  • 相关阅读:
    JavaScript之HTML DOM Event
    JavaScript 之 Function
    JavaScript 之 "for"的衍生对象
    Javascript object.constructor属性与面向对象编程(oop)
    前端发展简史
    基于MPI的大规模矩阵乘法问题
    Spark-shell错误:Missing Python executable 'python', defaulting to ...
    外文期刊论文的写法精概——“终极八股文大法!!!”
    Java静态方法为什么不能访问非静态方法
    API & Web API
  • 原文地址:https://www.cnblogs.com/milton/p/15361189.html
Copyright © 2011-2022 走看看