本文目的是主要介绍通过STM32F103C8T6使用TIM3和TIM4,分别输出一个PWM波形,PWM的占空比随时间变化,去驱动外接的一个LED以及最小开发板上已焊接的LED(固定接在 PC13 GPIO端口),实现2个 LED呼吸灯的效果。
前言
※ 在stm32CubeMX环境配置下,实现LED周期闪烁,可参考博客进行学习:https://blog.csdn.net/qq_52199251/article/details/127574010
※ 本文主要讲解采用使用TIM3和TIM4,分别输出一个PWM波形,PWM的占空比随时间变化,去驱动外接的一个LED以及最小开发板上已焊接的LED(固定接在 PC13 GPIO端口),实现2个 LED呼吸灯的效果。
天气渐冷,记得保暖,还有堆雪人

(一)需求分析
※ 使用TIM3和TIM4,分别输出一个PWM波形,PWM的占空比随时间变化,去驱动外接的一个LED以及最小开发板上已焊接的LED(固定接在 PC13 GPIO端口),实现2个 LED呼吸灯的效果。
所需工具:
1、芯片: STM32F103C8T6
2、STM32CubeMx软件
3、IDE: MDK-Keil软件
4、STM32F1xx/STM32F4xxHAL库
(二)PWM介绍
1.关于PWM
1.1 PWM含义
● PWM(Pulse Width Modulation)即脉冲宽度调制
,简称脉宽调制。它是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术;它是一种模拟控制方式,根据相应载荷的变化来调制晶体管基极或MOS管栅极的偏置,来实现晶体管或MOS管导通时间的改变,从而实现开关稳压电源输出的改变。
1.2 PWM基本原理
● PWM就是对逆变电路开关器件的通断进行控制,使输出端得到一系列幅值相等的脉冲,用这些脉冲来代替正弦波或所需要的波形。也可以这样理解,PWM是一种对模拟信号电平进行数字编码的方法
。通过高分辨率计数器的使用,方波的占空比被调制用来对一个具体模拟信号的电平进行编码。PWM信号仍然是数字的,因为在给定的任何时刻,满幅值的直流供电要么完全有(ON),要么完全无(OFF)。电压或电流源是以一种通(ON)或断(OFF)的重复脉冲序列被加到模拟负载上去的。只要带宽足够,任何模拟值都可以使用 PWM 进行编码。
● 在STM32单片机中,可以使用定时器的输出比较功能来产生PWM波:
● 即PWM模式可以产生一个由TIMx_ARR寄存器确定频率、由TIMx_CCRx寄存器确定占空比的信号。其框图如下图所示:
1.3 PWM优点及应用范围
※ 由于其控制简单、灵活和动态响应好等优点
而成为电力电子技术应用最广泛的控制方式,其应用领域包括测量,通信, 功率控制与变换,电动机控制、伺服控制、调光、开关电源,甚至某些音频放大器,因此学习PWM具有十分重要的现实意义。
2.PWM参数配置
2.1 PWM频率
● 是指1秒钟内信号从高电平到低电平再回到高电平的次数(一个周期);
● 也就是说一秒钟PWM有多少个周期
● 单位: Hz
● 表示方式: 50Hz 100Hz
2.2 PWM周期
● T=1/f
● 周期=1/频率
● 50Hz = 20ms 一个周期
● 如果频率为50Hz ,也就是说一个周期是20ms 那么一秒钟就有 50次PWM周期
2.3 PWM占空比
● 是一个脉冲周期内,高电平的时间与整个周期时间的比例
● 单位: % (0%-100%)
● 表示方式:20%
周期: 一个脉冲信号的时间 1s内测周期次数等于频率
脉宽时间: 高电平时间
上图中 脉宽时间占总周期时间的比例,就是占空比
比方说周期的时间是10ms,脉宽时间是8ms 那么低电平时间就是2ms 总的占空比 8/(8+2)= 80%
这就是占空比为80%的脉冲信号
而我们知道PWM就是脉冲宽度调制 通过调节占空比,就可以调节脉冲宽度(脉宽时间) 而频率 就是单位时间内脉冲信号的次数,频率越大
以20Hz 占空比为80% 举例 就是1秒钟之内输出了20次脉冲信号 每次的高电平时间为40ms
我们换更详细点的图
上图中,周期为T
T1为高电平时间
T2 为低电平时间
※ 假设周期T为 1s 那么频率就是 1Hz 那么高电平时间0.5s ,低电平时间0.5s 总的占空比就是 0.5 /1 =50%
3.PWM产生方式
※ 通过STM32控制板,有两种方式能产生PWM,第一是利用普通IO口输出PWM,第二种是利用定时器的PWM的IO口或复用IO口。
3.1 普通IO口与PWM口
3.1.1 普通IO口
● 一般能够输出PWM的端口都会在主要功能那一栏出现CHx的标志,而普通定时器没有出现这种标志。如图所示,上面的红框就是普通的定时器,不是专用的PWM端口。
3.1.2 PWM端口
● STM32 的定时器除了 TIM6 和 7。其他的定时器都可以用来产生 PWM 输出。其中高级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达 4路的 PWM 输出,这样,STM32 最多可以同时产生 30 路 PWM 输出。
3.1.3 两者区别
🎈 一般而言,尽量选用PWM口进行PWM输出,因为普通IO口模拟PWM的输出频率越高,进入定时器中断的次数就越快,中断间隔的时间越短,如果再有其他类型的中断也要处理时,会因为中断的优先级嵌套等待响应,影响控制精度,PWM输出误差增大,也会影响其他如ADC等中断处理,甚至会较出现单片机逻辑出错,死机或者跑飞的情况。
🎈 普通IO也可以输出PWM,只是产生PWM一般用转用芯片(开关电源上用的较多)或者单片机的PWM内置模块如定时器,很小直接用MCU的IO口线直接输出因为那样太耗MCU资源了。
3.1.4 STM32f103c8t6的PWM口
因为自己在用stm32最小系统,因此在此贴出其PWM口配置。
注意:不是所有的芯片都有重映像功能的,STM32f103c8t6这四个定时器就不需要重映像。
代码如下:
详细配置如下:
TIM1_CH1->PA8;
TIM1_CH2->PA9;
TIM1_CH3->PA10;
TIM1_CH4->PA11;
TIM2_CH1->PA0;
TIM2_CH2->PA1;
TIM2_CH3->PA2;
TIM2_CH4->PA3;
TIM3_CH1->PA6;
TIM3_CH2->PA7;
TIM3_CH3->PB0;
TIM3_CH4->PB1;
TIM4_CH1->PB6;
TIM4_CH1->PB7;
TIM4_CH1->PB8;
TIM4_CH1->PB9;
4.普通IO口产生PWM
※ 方法:定时器中断
● 普通IO口如何产生一个pwm?其实就是通过一个高低电平周期性的变化。这种思想很重要,确定频率就可以确定周期(T=1/f)也就是在一个周期内产生pwm的时间可以确定下来了。
● 如何改变占空比?确定了时间,高电平的时间不就是想要的占空比么,比如要产生一个频率1khz,占空比为70%的pwm,根据频率我们知道了周期为1ms,产生一个占空比为70%的不就是0.7ms的时间给高电平么,(我们用定时器中断的方式,使0.1ms产生一次中断,计数中断次数,中断处理函数前七次中断都给高电平就ok了。
参考链接:https://blog.csdn.net/m0_51095029/article/details/116426226
代码如下:
//#include "stm32f10x.h"
#include "stdio.h"
#include "usart.h"
#include "tim.h"
#include "led.h"
#include "misc.h"
void TIM2_NVIC(void);
void TIM2_Init(void);
unsigned char ucLed;
unsigned char ucLCK;
int main(void)
{
SysTick_Config(72000); // 定时1ms(HCLK = 72MHz)
TIM2_Init();
TIM2_NVIC();
LED_Init();
while(1)
{
LED_Disp(ucLed);
}
}
//unsigned int count;
//中断服务函数
unsigned int count=0;
unsigned int i;
void TIM2_IRQHandler(void)
{ count++;//计中断次数
if(TIM_GetITStatus(TIM2,TIM_IT_Update) != RESET)
{
if(count%10<i)//i:改变占空比值,这里i为变量,且让占空比一直变化
ucLed =0xff;//高电平
else
ucLed =0;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
void TIM2_NVIC(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断分组
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;//设置TIM2中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//抢占优先级为0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//响应优先级为1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能中断源
NVIC_Init(&NVIC_InitStructure);
}
void TIM2_Init(void)//0.1ms定时
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
// RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeBaseInitStruct.TIM_Prescaler = (2-1);//预分屏系数
TIM_TimeBaseInitStruct.TIM_Period = (36000/10)-1;//自动重载计数周期值
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;//设置为向上计数方式
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;//时钟分频系数
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStruct);
TIM_ClearFlag(TIM2,TIM_FLAG_Update);//清除中断
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//使能TIM2的更新中断
TIM_Cmd(TIM2, ENABLE);//使能TIM2定时器
}
//此函数必须有
void SysTick_Handler(void)
{
//ucLCK++;
if(ucLCK++%500==0)
{
i++;
if(i==10)
i=0;
}
}
5.PWM口产生PWM
※ 方法:比较匹配+溢出中断
寄存器:
5.1计数器当前寄存器CNT
5.2预分频寄存器PSC
5.3自动重装载寄存器PSC
5.4控制寄存器 CR1
※ 控制 PWM 的输出寄存器:CCMR1/2、CCER、CCR1~4、BDTR
(1)捕获/比较模式寄存器( TIMx_CCMR1/2)
(2)捕获/比较使能寄存器( TIMx_CCER)
(3)捕获/比较寄存器( TIMx_CCR1~4)
(4)刹车和死区寄存器( TIMx_BDTR)
● 有关这几个寄存器的学习内容请点击下面的链接
原文链接:https://blog.csdn.net/wwt18811707971/article/details/74906149
6.PWM工作原理
原理框图如下:
A,TIMx_CCMR1寄存器的OC1M[2:0]位,设置输出模式控制器
110:PWM模式1
111:PWM模式2
B,计数器值TIMx_CNT与通道1捕获比较寄存器CCR1进行比较,通过比较结果输出有效电平和无效电平
OC1REF=0 无效电平
OC1REF=1 无效电平
C,通过输出模式控制器产生的信号
TIMx_CCER寄存器的CC1P位,设置输入/捕获通道1输出极性
0:高电平有效
1:低电平有效
D,TIMx_CCER:CC1E位控制输出使能电路,信号由此输出到对应引脚
0:关闭
1:开启
占空比原理:
🎈如图为向上计数:
定时器重装载值为ARR,比较值CCRx
t时刻对计数器值和比较值进行比较
如果计数器值小于CCRx值,输出低电平
如果计数器值大于CCRx值,输出高电平
🎈PWM的一个周期
定时器从0开始向上计数
当0-t1段,定时器计数器TIMx_CNT值小于CCRx值,输出低电平
t1-t2段,定时器计数器TIMx_CNT值大于CCRx值,输出高电平
当TIMx_CNT值达到ARR时,定时器溢出,重新向上计数…循环此过程
至此一个PWM周期完成
🎈影响因素
ARR : 决定PWM周期(在时钟频率一定的情况下,当前为默认内部时钟CK_INT)
CCRx : 决定PWM占空比(高低电平所占整个周期比例)
7.PWM配置过程
简易配置过程如下:
8.“牛刀小试”——PWM输出示例
※ 这里给出一个简单的PWM输出示例,帮助各位幸运儿
便于理解与实操。
/*
***************************************************
实验功能:
通过比较和溢出功能,模拟pwm输出,在PC8-PC15输出可变pwm
***************************************************
*/
#include "stdio.h"
#include "led.h"
#include "misc.h"
void TIM2_Config(uint16_t duty2);
unsigned char ucLed;
unsigned int Compare2=100;
unsigned int ulTick;
int main(void)
{
SysTick_Config(72000); // 定时1ms(HCLK = 72MHz)
TIM2_Config(999/4); //25%
TIM_SetCompare2(TIM2,Compare2);//直接调节占空比函数
LED_Init();
while(1)
{
if(ulTick%1000==0)
{
Compare2+=50;
if(Compare2>999)
Compare2=100;
TIM_SetCompare2(TIM2,Compare2);
}
}
}
//输出比较
void TIM2_Config(uint16_t duty1) //TIM2 CH2
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
//RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeBaseStructure.TIM_Period = 999; //1KHz //自动重装载值
TIM_TimeBaseStructure.TIM_Prescaler = 71; //预分频系数
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;//时钟分频系数
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //基本定时器配置
TIM_ClearFlag(TIM2,TIM_FLAG_Update);//清除中断
TIM_ITConfig(TIM2,TIM_IT_Update|TIM_IT_CC2,ENABLE);//使能TIM2的更新中断
TIM_PrescalerConfig(TIM2,71, TIM_PSCReloadMode_Immediate); // TIM2 预分频值 预分频重载模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2; //选择定时器模式,脉冲宽度调制模式2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Disable;//禁用比较输出功能
TIM_OCInitStructure.TIM_Pulse = duty1;//占空比
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_Low;//设置比较输出有效电平
TIM_OC2Init(TIM2, &TIM_OCInitStructure);//通道二
TIM_OC2PreloadConfig(TIM2,TIM_OCPreload_Enable);//使能寄存器TIM_CCMR1
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断分组
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;//设置TIM2中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;//抢占优先级为0
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;//响应优先级为0
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//使能中断源
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2, ENABLE);//使能TIM2定时计数器
}
//中断处理函数
void TIM2_IRQHandler(void)
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update) != RESET) //溢出中断
{
ucLed = 0x00; //关闭所有LED
LED_Disp(ucLed);
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
else if(TIM_GetITStatus(TIM2,TIM_IT_CC2) != RESET) //比较匹配
{
ucLed = 0xff;//打开所有LED
LED_Disp(ucLed);
TIM_ClearITPendingBit(TIM2,TIM_IT_CC2);
}
}
//此函数必须有
void SysTick_Handler(void)
{
ulTick++;
}
(三)STM32F103C8T6下工程实现
1.工程新创
● 点击ACCESS TO MCU SELECTOR
● 找到自己所用的芯片型号,并进行建立。
2.引脚排列和配置
2.1 配置RCC与SYS
2.1.1 配置RCC
● 点System Cor
,选择RCC
,在右侧弹出的菜单栏中选Crystal/Ceramic Resonator
:
● 发现此时芯片高亮。
2.1.2 配置SYS
● 选择调试接口,点System Cor
,选择SYS
。在右侧弹出的菜单栏中选Serial Wire
:
● 发现此时芯片高亮。
3.配置TIM3和TIM4
3.1 配置TIM3
① 选Internal Clock(内部时钟)
② 通道1选择:PWM Generation CH1(PWM输出通道1)
③ Prtscaler (定时器分频系数) : 71
④ Counter Mode(计数模式):Up(向上计数模式)
⑤ Counter Period(自动重装载值) : 500
⑥ CKD(时钟分频因子) :No Division (不分频 )
3.2 配置TIM4
● 因为TIM4的设置与TIM3类似。
● 发现芯片部分引脚高亮。
4.配置时钟
● 配置为72MHz。
5.项目文件配置
●项目文件配置如下
6.最后生成项目即可
(四)Keil编译程序
1.代码编写
● 用keil打开刚刚在CubeMX生成的项目文件。
2.代码编写
2.1 占空比变量
● 在main.c文件中编写占空比代码。
uint16_t duty_num = 10; //定义变量储存占空比
2.2 TIM3和TIM4代码编写
● 代码如下。
HAL_TIM_PWM_Start(&htim3,TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim4,TIM_CHANNEL_1);
2.3 While循环代码编写
● 在while循环中加入如下代码。
//每隔50毫秒,占空比加10,如果超过500(也就是PWM周期),自动变成0.
HAL_Delay(50);
duty_num = duty_num + 10;
if(duty_num > 500)
{
duty_num = 0;
}
__HAL_TIM_SetCompare(&htim4,TIM_CHANNEL_4,duty_num);
__HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_3,duty_num);
3.编译文件
● 发现没有报错
(五)烧录实现
1.硬件搭建
● 硬件电路搭建如下:
2.打开FlyMcu
3.呼吸灯效果实现
● 可以发现成功实现了两个LED灯呼吸的效果。
呼吸灯
(六)PWM波形观察
1.配置参数
● 配置参数如下
2.观察PWM波形
● 观察效果如下:
(七)总结
本文介绍了通过STM32F103C8T6采用TIM3和TIM4,分别输出一个PWM波形,PWM的占空比随时间变化,去驱动外接的一个LED以及最小开发板上已焊接的LED(固定接在 PC13 GPIO端口),实现2个 LED呼吸灯的效果。
本次实验操作并不太难,不仅掌握了pwm波形的工作原理以及各参数的含义,还掌握了如何去实现LED呼吸灯,为后面嵌入式开发奠定了基础,同时也让自己对嵌入式的理解进步。
记得爱护自己。
寄语:天气逐渐变冷
记得穿厚衣,喝热水
别冷着了
因为啊,你是父母的心头肉,是好朋友的希冀,还望善自珍重!

(八)参考文献
[1]https://blog.csdn.net/qq_45237293/article/details/111997424
[2]https://blog.csdn.net/zmhDD/article/details/111942507
[3]https://blog.csdn.net/as480133937/article/details/103439546
[4]https://blog.csdn.net/qq_53112972/article/details/127577995