STM32C542开发(4)----输出PWM及修改频率与占空比

STM32C542开发.4--输出PWM及修改频率与占空比

概述

定时器是 MCU 中非常常用的外设之一,可以用于周期定时、PWM 输出、输入捕获、输出比较、电机控制等场景。本文基于 STM32C542CCT6,使用 STM32CubeMX2 配置 TIM1,并通过 HAL2 自带函数修改 PSC 和 ARR,从而改变定时器输出频率。同时介绍多通道 PWM 的基本使用方式。

需要样片的可以加群申请:925643491 / 615061293 。

视频教学

https://www.bilibili.com/video/BV1tajG6oEy2/

样品申请

https://www.wjx.top/vm/OhcKxJk.aspx#

源码下载

硬件准备

首先需要准备一个开发板,这里我准备的是自己绘制的开发板,需要的可以进行申请。

主控为STM32C542CCT6

参考程序

https://github.com/CoreMaker-lab/STM32C542_SENSOR

https://gitee.com/CoreMaker/STM32C542_SENSOR

生成STM32CUBEMX2

用STM32CUBEMX2生成例程,这里使用MCU为STM32C542CCT6。

  1. 打开 STM32CubeMX2 后,进入 Home 首页
  2. 点击 MCU,基于具体芯片型号创建工程

在 MCU name 中输入 STM32C542CCT6,选择对应的 STM32C5 芯片型号后,点击 Continue 进入下一步工程配置。

填写工程名称和保存路径后,点击 "Automatically Download, Install & Create Project",STM32CubeMX2 会自动下载所需软件包并创建工程。

STM32CubeMX2 提示 Project Successfully Created 后,点击右下角 "Launch Project" 进入工程配置界面。

时钟树配置

  1. 点击左侧外设配置入口,进入 Peripherals 配置界面
  2. 在 System 分类下选择 RCC,用于配置系统时钟源
  3. HSE Source 选择 Crystal/ceramic resonator,启用外部高速晶振
  4. LSE Source 选择 Crystal/ceramic resonator,启用外部低速晶振
  1. 点击左侧 Clock 图标,进入时钟树配置界面
  2. HSE OSC:设置外部高速晶振频率,这里配置为 24 MHz
  3. PSI Mux / PLL:选择并配置 PLL 时钟源,用于倍频生成系统主频
  4. System Mux:选择系统时钟来源,当前系统主频配置为 144 MHz

DEBUG配置

在 Peripherals 中选择 Cortex → DEBUG,将 Mode 配置为 Single-wire trace asynchronous,用于后续程序下载、在线调试和 Trace 调试功能。

TIM1基础参数

使用如下2个管脚进行PWM输出。

在配置 TIM1 的 PSC 和 ARR 之前,需要先确认 TIM1 的输入时钟频率。因为 PWM 频率计算需要用到 TIM 的输入时钟,如果时钟频率判断错误,最终计算出来的 PWM 频率也会不准确。这里 TIM1 Clock = 144 MHz

TIM1 的配置过程如下:

c 复制代码
1. 点击左侧 Peripherals 外设配置入口
2. 在 Timers 分类中选择 TIM1
3. TIM1 显示 Active,表示 TIM1 外设已经被使能
4. Clock Source 选择 Internal clock source,使用内部时钟作为 TIM1 计数时钟
5. 在 Channel(s) 中使能需要使用的输出通道,例如 Channel 1 和 Channel 3

在 Channel(s) 配置区域中,选择 Channel 1,并进行如下配置:

  1. Channel 1 显示 Active,表示 TIM1_CH1 已经被使能
  2. Use channel 选择 Enabled,使能该通道
  3. Channel direction 选择 Output,表示该通道作为输出通道使用
  4. Output compare mode 选择 PWM mode 1,用于输出 PWM 波形
  5. Pulse 对应 CCR1,用于设置 PWM 占空比
  6. GPIO pin 选择 Enabled,使 PWM 信号输出到对应 GPIO 引脚
  7. Polarity 选择 Active high,表示 PWM 有效电平为高电平
  8. Idle state 选择 Inactive,表示空闲状态下输出为无效电平
  9. Complementary output channel CH1N 保持 Disabled,本文不使用互补输出

在 Channel(s) 配置区域中选择 Channel 3,并进行如下配置:

  1. Channel 3 显示 Active,表示 TIM1_CH3 已经被使能
  2. Use channel 选择 Enabled,使能该通道
  3. Channel direction 选择 Output,表示该通道作为输出通道使用
  4. Output compare mode 选择 PWM mode 1,用于输出 PWM 波形
  5. Pulse 对应 CCR3,用于设置 TIM1_CH3 的占空比
  6. GPIO pin 选择 Enabled,使 PWM 信号输出到对应 GPIO 引脚
  7. Polarity 选择 Active high,表示 PWM 有效电平为高电平
  8. Idle state 选择 Inactive,表示空闲状态下输出为无效电平
  9. Complementary output channel CH3N 未使用,本文只使用普通 PWM 输出

当前配置如下:

c 复制代码
TIM1_CH1 -> PB8
TIM1_CH3 -> PA5

其中,TIM1_CH1 对应 PB8,TIM1_CH3 对应 PA5。两个引脚的配置方式基本一致:

c 复制代码
Mode        -> Alternate
Pull        -> No pull-up and no pull-down
Speed       -> Low
Output type -> Push pull
EXTI        -> Disabled

生成项目

  1. 修改配置后,左下角会提示 Click to save,需要先保存当前工程配置
  2. 点击左侧 Project settings,进入工程生成设置页面
  3. 在 IDE Project Generation 中选择工程格式和工具链,本例选择 CMake + GCC,然后点击 Generate IDE project 生成工程

导入STM32CubeIDE

  1. 打开 STM32CubeIDE,点击菜单栏 File
  2. 选择 Import...,准备导入 STM32CubeMX2 生成的 CMake 工程
  1. 在 Import 窗口中展开 Import STM32 Project
  2. 选择 STM32 CMake Project
  3. 点击 Next,进入 CMake 工程路径选择页面
  1. Project name:填写导入到 STM32CubeIDE 中显示的工程名称
  2. Source directory:选择 STM32CubeMX2 生成的 CMake 工程目录
  3. 点击 Next,继续完成工程导入
  1. Toolchain:选择 MCU ARM GCC,表示使用 ARM GCC 工具链进行编译
  2. MCU:确认芯片型号为 STM32C542CCTx,与前面 STM32CubeMX2 中选择的 MCU 保持一致
  3. CPU/Core:确认内核为 Cortex-M33,Core 为 0
  4. 点击 Finish,完成 CMake 工程导入

PWM mode 工作原理

查看 RM0522 中 PWM mode 章节可以看到,PWM 模式用于产生一个周期性输出信号。该信号的频率由 TIM_ARR 自动重装载寄存器决定,占空比由 TIM_CCRx 捕获/比较寄存器决定。

文档中说明,PWM mode 可以在每个通道上独立选择。也就是说,一个 TIM 外设可以有多个 PWM 输出通道,例如:

TIM1_CH1

TIM1_CH2

TIM1_CH3

TIM1_CH4

每个通道都可以配置为 PWM mode 1 或 PWM mode 2。本文使用的是 PWM mode 1。

对于 PWM mode 1,在向上计数模式下,可以这样理解:

当 TIM_CNT < TIM_CCRx 时,PWM 输出为有效电平;

当 TIM_CNT >= TIM_CCRx 时,PWM 输出为无效电平。

由于前面在 STM32CubeMX2 中将 PWM 输出极性设置为 Active high,所以这里的有效电平就是高电平。也就是说,在一个 PWM 周期内,TIM_CCRx 的值越大,高电平持续时间越长,占空比越大。

普通 PWM mode 1 的波形主要由一个比较寄存器控制,例如:

TIM1_CH1 -> CCR1

在这种模式下,PWM 频率由 ARR 决定,占空比由 CCR1 决定,适合 LED 调光、蜂鸣器、普通 PWM 输出等基础应用。

使用的频率计算公式为:

c 复制代码
PWM频率 = TIM输入时钟 / ((PSC + 1) × (ARR + 1))

占空比计算公式为:

c 复制代码
占空比 = CCR / (ARR + 1) × 100%

定义

本文实验基于 STM32C542CCT6,使用 STM32CubeMX2 配置 TIM1 的 PWM 输出。实验中使用 TIM1 的两个输出通道:

c 复制代码
TIM1_CH1 -> PB8
TIM1_CH3 -> PA5

其中 TIM1_CH1 和 TIM1_CH3 都配置为 PWM mode 1,并通过 HAL2 自带函数动态修改 PWM 频率和占空比。

代码中主要包含以下头文件:

c 复制代码
#include <stdio.h>
#include "mx_tim1.h"

定义了 TIM1 的输入时钟,TIM1 的时钟为 144 MHz,因此后续计算 PWM 频率时使用 144 MHz 作为输入时钟。

如果希望输出 1000Hz PWM,建议将 ARR 设置为 999。

这样一个 PWM 周期包含:

ARR + 1 = 999 + 1 = 1000

个计数点,方便计算占空比。

PWM 频率计算公式如下:

c 复制代码
PWM频率 = TIM1_CLK / ((PSC + 1) × (ARR + 1))

当:

c 复制代码
TIM1_CLK = 144 MHz
PSC = 143
ARR = 999

则:

c 复制代码
PWM频率 = 144,000,000 / ((143 + 1) × (999 + 1))
        = 144,000,000 / (144 × 1000)
        = 1000 Hz

添加下面代码。

c 复制代码
/* Private define ------------------------------------------------------------*/
#define TIM1_CLK_HZ        144000000UL

/*
 * 本实验固定 ARR = 9999
 * 这样一个 PWM 周期为 10000 个计数点,方便计算占空比
 */
#define TIM1_PWM_ARR       9999UL

/* Private function prototypes -----------------------------------------------*/
static hal_status_t TIM1_SetPSC_ARR(uint32_t psc, uint32_t arr);
static hal_status_t TIM1_SetFrequency(uint32_t freq_hz);
static hal_status_t TIM1_SetChannelDuty(hal_tim_channel_t channel,
                                        uint32_t arr,
                                        uint32_t duty_percent);
static hal_status_t TIM1_PWM_Start_CH1_CH3(void);

修改 PSC 和 ARR

代码中封装了 TIM1_SetPSC_ARR() 函数,用于修改 TIM1 的预分频值和自动重装载值:该函数主要做了三件事:

  1. 通过 mx_tim1_gethandle() 获取 TIM1 句柄
  2. 使用 HAL_TIM_SetPrescaler() 修改 TIM1_PSC
  3. 使用 HAL_TIM_SetPeriod() 修改 TIM1_ARR
  4. 使用 HAL_TIM_SetCounter() 将计数器清零
c 复制代码
/**
  * @brief  使用 HAL2 自带函数修改 TIM1 PSC 和 ARR
  * @param  psc: TIM1 预分频值
  * @param  arr: TIM1 自动重装载值
  * @retval HAL status
  */
static hal_status_t TIM1_SetPSC_ARR(uint32_t psc, uint32_t arr)
{
    hal_tim_handle_t *htim1 = mx_tim1_gethandle();

    if (htim1 == NULL)
    {
        return HAL_ERROR;
    }

    /*
     * 使用 HAL2 自带函数修改 PSC 和 ARR
     * PSC 影响计数频率
     * ARR 影响 PWM 周期
     */
    if (HAL_TIM_SetPrescaler(htim1, psc) != HAL_OK)
    {
        return HAL_ERROR;
    }

    if (HAL_TIM_SetPeriod(htim1, arr) != HAL_OK)
    {
        return HAL_ERROR;
    }

    /*
     * 修改参数后,将计数器清零,方便观察 PWM 输出变化
     */
    if (HAL_TIM_SetCounter(htim1, 0) != HAL_OK)
    {
        return HAL_ERROR;
    }

    return HAL_OK;
}

根据目标频率自动计算 PSC

为了使用更加方便,代码进一步封装了 TIM1_SetFrequency() 函数。该函数输入目标频率,然后根据当前 ARR 自动计算 PSC。

计算公式如下:

c 复制代码
PWM频率 = TIM1_CLK / ((PSC + 1) × (ARR + 1))

反推 PSC:

c 复制代码
PSC + 1 = TIM1_CLK / (PWM频率 × (ARR + 1))

例如设置:

c 复制代码
TIM1_SetFrequency(1000);

当 TIM1_CLK = 144MHz,ARR = 999 时,计算得到:

c 复制代码
PSC = 143

最终 PWM 输出频率就是 1000Hz。

c 复制代码
/**
  * @brief  根据目标频率自动计算 PSC,并设置 TIM1 频率
  * @param  freq_hz: 目标 PWM 频率,单位 Hz
  * @retval HAL status
  */
static hal_status_t TIM1_SetFrequency(uint32_t freq_hz)
{
    uint32_t arr = TIM1_PWM_ARR;
    uint32_t psc;

    if (freq_hz == 0U)
    {
        return HAL_INVALID_PARAM;
    }

    /*
     * PWM 频率 = TIM1_CLK / ((PSC + 1) * (ARR + 1))
     *
     * 反推:
     * PSC + 1 = TIM1_CLK / (PWM频率 * (ARR + 1))
     */
    psc = TIM1_CLK_HZ / (freq_hz * (arr + 1U));

    if (psc == 0U)
    {
        psc = 1U;
    }

    psc = psc - 1U;

    return TIM1_SetPSC_ARR(psc, arr);
}

设置通道占空比

PWM 的占空比由 CCRx,也就是 Pulse 值决定。

代码中封装了 TIM1_SetChannelDuty() 函数,用于设置某一路 TIM 通道的占空比。

占空比计算公式如下:

c 复制代码
占空比 = Pulse / (ARR + 1) × 100%

所以反过来:

c 复制代码
Pulse = (ARR + 1) × Duty / 100

例如,当:

c 复制代码
ARR = 999
Duty = 25%

则:

c 复制代码
Pulse = (999 + 1) × 25 / 100 = 250

此时 TIM1_CH1 的占空比为 25%。

如果:

c 复制代码
ARR = 999
Duty = 75%

则:

c 复制代码
Pulse = (999 + 1) × 75 / 100 = 750

此时对应通道的占空比为 75%。

c 复制代码
/**
  * @brief  设置 TIM1 某一个通道的占空比
  * @param  channel: HAL_TIM_CHANNEL_1 / HAL_TIM_CHANNEL_3 等
  * @param  arr: 当前 ARR 值
  * @param  duty_percent: 占空比,范围 0~100
  * @retval HAL status
  */
static hal_status_t TIM1_SetChannelDuty(hal_tim_channel_t channel,
                                        uint32_t arr,
                                        uint32_t duty_percent)
{
    hal_tim_handle_t *htim1 = mx_tim1_gethandle();
    uint32_t pulse;

    if (htim1 == NULL)
    {
        return HAL_ERROR;
    }

    if (duty_percent > 100U)
    {
        duty_percent = 100U;
    }

    /*
     * 占空比 = Pulse / (ARR + 1) * 100%
     *
     * 所以:
     * Pulse = (ARR + 1) * Duty / 100
     */
    pulse = ((arr + 1U) * duty_percent) / 100U;

    /*
     * 使用 HAL2 自带函数修改 CCRx / Pulse
     *
     * TIM1_CH1 -> CCR1
     * TIM1_CH3 -> CCR3
     */
    if (HAL_TIM_OC_SetCompareUnitPulse(
            htim1,
            hal_tim_oc_channel_to_compare_unit(channel),
            pulse) != HAL_OK)
    {
        return HAL_ERROR;
    }

    return HAL_OK;
}

启动 PWM 输出

完成频率和占空比设置后,需要启动 TIM1 的输出通道和计数器。 这里分别启动了:

TIM1_CH1

TIM1_CH3

TIM1 计数器

其中 HAL_TIM_OC_StartChannel() 用于启动指定的输出通道,HAL_TIM_Start() 用于启动 TIM1 计数器。

c 复制代码
/**
  * @brief  启动 TIM1 CH1 和 CH3 PWM 输出
  * @retval HAL status
  */
static hal_status_t TIM1_PWM_Start_CH1_CH3(void)
{
    hal_tim_handle_t *htim1 = mx_tim1_gethandle();

    if (htim1 == NULL)
    {
        return HAL_ERROR;
    }

    /*
     * 启动 TIM1_CH1 输出
     */
    if (HAL_TIM_OC_StartChannel(htim1, HAL_TIM_CHANNEL_1) != HAL_OK)
    {
        return HAL_ERROR;
    }

    /*
     * 启动 TIM1_CH3 输出
     */
    if (HAL_TIM_OC_StartChannel(htim1, HAL_TIM_CHANNEL_3) != HAL_OK)
    {
        return HAL_ERROR;
    }

    /*
     * 启动 TIM1 计数器
     */
    if (HAL_TIM_Start(htim1) != HAL_OK)
    {
        return HAL_ERROR;
    }

    return HAL_OK;
}

主程序

c 复制代码
/**
  * brief:  The application entry point.
  * retval: none but we specify int to comply with C99 standard
  */
int main(void)
{
  /** System Init: this code placed in targets folder initializes your system.
    * It calls the initialization (and sets the initial configuration) of the peripherals.
    * You can use STM32CubeMX to generate and call this code or not in this project.
    * It also contains the HAL initialization and the initial clock configuration.
    */
  if (mx_system_init() != SYSTEM_OK)
  {
    return (-1);
  }
  else
  {


    /*
     * TIM1_CH1 -> PB8
     * TIM1_CH3 -> PA5
     *
     * 当前配置:
     * TIM1_CLK = 144 MHz
     * ARR = 9999
     */

    /*
     * 设置 PWM 频率为 1Hz
     *
     * PWM频率 = 144MHz / ((PSC + 1) * (ARR + 1))
     * ARR = 9999
     * 当 freq = 1Hz 时,PSC = 14399
     */
    if (TIM1_SetFrequency(1) != HAL_OK)
    {
        printf("TIM1_SetFrequency failed\r\n");
    }

    /*
     * 设置多通道占空比
     *
     * CH1 = 25%
     * CH3 = 75%
     */
    if (TIM1_SetChannelDuty(HAL_TIM_CHANNEL_1, TIM1_PWM_ARR, 25) != HAL_OK)
    {
        printf("TIM1 CH1 duty set failed\r\n");
    }

    if (TIM1_SetChannelDuty(HAL_TIM_CHANNEL_3, TIM1_PWM_ARR, 75) != HAL_OK)
    {
        printf("TIM1 CH3 duty set failed\r\n");
    }

    /*
     * 启动 TIM1 CH1 和 CH3 PWM 输出
     */
    if (TIM1_PWM_Start_CH1_CH3() != HAL_OK)
    {
        printf("TIM1 PWM start failed\r\n");
    }
    HAL_Delay(5000);
    while (1)
    {
        printf("TIM1 PWM: 1000Hz, CH1=25%%, CH3=75%%\r\n");

        /*
         * 设置 TIM1 PWM 频率为 1000Hz
         *
         * TIM1_CLK = 144MHz
         * ARR = 999
         * PSC = 143
         *
         * PWM频率 = 144MHz / ((143 + 1) * (999 + 1))
         *         = 1000Hz
         */
        TIM1_SetFrequency(1000);

        /*
         * CH1 输出 25% 占空比
         * CH3 输出 75% 占空比
         */
        TIM1_SetChannelDuty(HAL_TIM_CHANNEL_1, TIM1_PWM_ARR, 25);
        TIM1_SetChannelDuty(HAL_TIM_CHANNEL_3, TIM1_PWM_ARR, 75);

        HAL_Delay(3000);
//
//        printf("TIM1 PWM: 1000Hz, CH1=50%%, CH3=50%%\r\n");
//
//        TIM1_SetFrequency(1000);
//        TIM1_SetChannelDuty(HAL_TIM_CHANNEL_1, TIM1_PWM_ARR, 50);
//        TIM1_SetChannelDuty(HAL_TIM_CHANNEL_3, TIM1_PWM_ARR, 50);
//
//        HAL_Delay(3000);
//
//        printf("TIM1 PWM: 1000Hz, CH1=75%%, CH3=25%%\r\n");
//
//        TIM1_SetFrequency(1000);
//        TIM1_SetChannelDuty(HAL_TIM_CHANNEL_1, TIM1_PWM_ARR, 75);
//        TIM1_SetChannelDuty(HAL_TIM_CHANNEL_3, TIM1_PWM_ARR, 25);
//
//        HAL_Delay(3000);
    }
  }
} /* end main */

演示

使用示波器或逻辑分析仪测量 PB8 和 PA5,可以看到两路 PWM 的频率相同,先输出1Hz的25%和75%+百分比的PWM。

后续输出1kHz的25%和75%+百分比的PWM。