STM32C542开发.4--输出PWM及修改频率与占空比
- 概述
- 视频教学
- 样品申请
- 源码下载
- 硬件准备
- 参考程序
- 生成STM32CUBEMX2
- 时钟树配置
- DEBUG配置
- TIM1基础参数
- 生成项目
- 导入STM32CubeIDE
- [PWM mode 工作原理](#PWM mode 工作原理)
- 定义
- [修改 PSC 和 ARR](#修改 PSC 和 ARR)
- [根据目标频率自动计算 PSC](#根据目标频率自动计算 PSC)
- 设置通道占空比
- [启动 PWM 输出](#启动 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。
- 打开 STM32CubeMX2 后,进入 Home 首页
- 点击 MCU,基于具体芯片型号创建工程

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

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

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

时钟树配置
- 点击左侧外设配置入口,进入 Peripherals 配置界面
- 在 System 分类下选择 RCC,用于配置系统时钟源
- HSE Source 选择 Crystal/ceramic resonator,启用外部高速晶振
- LSE Source 选择 Crystal/ceramic resonator,启用外部低速晶振

- 点击左侧 Clock 图标,进入时钟树配置界面
- HSE OSC:设置外部高速晶振频率,这里配置为 24 MHz
- PSI Mux / PLL:选择并配置 PLL 时钟源,用于倍频生成系统主频
- 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,并进行如下配置:
- Channel 1 显示 Active,表示 TIM1_CH1 已经被使能
- Use channel 选择 Enabled,使能该通道
- Channel direction 选择 Output,表示该通道作为输出通道使用
- Output compare mode 选择 PWM mode 1,用于输出 PWM 波形
- Pulse 对应 CCR1,用于设置 PWM 占空比
- GPIO pin 选择 Enabled,使 PWM 信号输出到对应 GPIO 引脚
- Polarity 选择 Active high,表示 PWM 有效电平为高电平
- Idle state 选择 Inactive,表示空闲状态下输出为无效电平
- Complementary output channel CH1N 保持 Disabled,本文不使用互补输出

在 Channel(s) 配置区域中选择 Channel 3,并进行如下配置:
- Channel 3 显示 Active,表示 TIM1_CH3 已经被使能
- Use channel 选择 Enabled,使能该通道
- Channel direction 选择 Output,表示该通道作为输出通道使用
- Output compare mode 选择 PWM mode 1,用于输出 PWM 波形
- Pulse 对应 CCR3,用于设置 TIM1_CH3 的占空比
- GPIO pin 选择 Enabled,使 PWM 信号输出到对应 GPIO 引脚
- Polarity 选择 Active high,表示 PWM 有效电平为高电平
- Idle state 选择 Inactive,表示空闲状态下输出为无效电平
- 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

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

导入STM32CubeIDE
- 打开 STM32CubeIDE,点击菜单栏 File
- 选择 Import...,准备导入 STM32CubeMX2 生成的 CMake 工程

- 在 Import 窗口中展开 Import STM32 Project
- 选择 STM32 CMake Project
- 点击 Next,进入 CMake 工程路径选择页面

- Project name:填写导入到 STM32CubeIDE 中显示的工程名称
- Source directory:选择 STM32CubeMX2 生成的 CMake 工程目录
- 点击 Next,继续完成工程导入

- Toolchain:选择 MCU ARM GCC,表示使用 ARM GCC 工具链进行编译
- MCU:确认芯片型号为 STM32C542CCTx,与前面 STM32CubeMX2 中选择的 MCU 保持一致
- CPU/Core:确认内核为 Cortex-M33,Core 为 0
- 点击 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 的预分频值和自动重装载值:该函数主要做了三件事:
- 通过 mx_tim1_gethandle() 获取 TIM1 句柄
- 使用 HAL_TIM_SetPrescaler() 修改 TIM1_PSC
- 使用 HAL_TIM_SetPeriod() 修改 TIM1_ARR
- 使用 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。

