这是一个简单的
PWM
原理示意图。
图中,我们假定定时器工作在向上计数
PWM模式,且当 CNT<CCRx
时,输出
0
,当
CNT>=CCRx
时输出
1
。那么就可以得到如上的
PWM 示意图:当 CNT
值小于
CCRx
的时候,
IO
输出低电平
(0)
,当
CNT
值大于等于
CCRx
的时候, IO 输出高电平
(1)
,当
CNT
达到
ARR
值的时候,重新归零,然后重新向上计数,依次循环。 改变 CCRx
的值,就可以改变
PWM
输出的占空比,改变
ARR
的值,就可以改变
PWM
输出的 频率,这就是 PWM
输出的原理。
一、
使 STM32
的通用定时器
TIMx
产生
PWM 输出,要用到三个寄存器来控制PWM,这三个寄存器分别为捕获 /比较模式寄存器( TIMx_CCMR1/2 ) 、 捕 获 / 比 较 使 能 寄 存 器 ( TIMx_CCER ) 、 捕 获 / 比 较 寄 存 器TIMx_CCR1~4)。
首先是捕获/
比较模式寄存器(
TIMx_CCMR1/2
),该寄存器总共有
2
个,
TIMx _CCMR1
和
TIMx _CCMR2
。
TIMx_CCMR1
控制
CH1
和
2
,而
TIMx_CCMR2
控制
CH3
和
4
。该寄存器
的各位描述如图
所示:
这里需要说明的是模式设置位 OCxM
,此部分由
3
位组成。 总共可以配置成 7
种模式,我们使用的是
PWM
模式,所以这
3
位必须设置为
110/111
。这两种 PWM 模式的区别就是输出电平的极性相反。
接下来,介绍捕获
/
比较使能寄存器(
TIMx_CCER
),该寄存器控制着各个输入输出 通道的开关。
只用到了 CC1E
位,该位是输入
/
捕获
2
输出使能位,要想PWM 从
IO
口输出,这个位必须设置为
1
,所以我们需要设置该位为
1。
最后,捕获
/
比较寄存器(
TIMx_CCR1~4
),该寄存器总共有
4
个,对应
4 个输通道 CH1~4
。
在输出模式下,该寄存器的值与
CNT (计数器)
的值比较,根据比较结果产生相应动作。利用这点, 我们通过修改这个寄存器的值,就可以控制 PWM
的输出脉宽了。
现在我使用的是 TIM3 的通道 1
,所以需要修改
TIM3_CCR1
以实现脉宽控制
DS6
的亮度。
现在要利用 TIM3
的
CH1
输出
PWM
来控制
DS6
的亮度,但是
TIM3_CH1
默认是接在
PA6
上面的,而我们的
DS6
接在
PC6
上面,如果普通
MCU
,可能就只能用飞线把
PA6
飞到
PC6
上来实现了,不过,我们用的是
STM32
,它比较高级,可以通过重映射(AFIO)功能,把
TIM3_CH1 映射到 PC6 上。STM32
的重映射控制是由复用重映射和调试
IO
配置寄存器(
AFIO_MAPR)控制的。下面是TIM3_REMAP[1:0]
重映射控制表
下面是PWM.C文件
TIM_HandleTypeDef TIM3_Handler; //定时器句柄
TIM_OC_InitTypeDef TIM3_CH1Handler; //定时器 3 通道 1 句柄
//TIM3 PWM 部分初始化
//arr:自动重装值。
//psc:时钟预分频数
//定时器溢出时间计算方法:Tout=((arr+1)*(psc+1))/Ft us.
//Ft=定时器工作频率,单位:Mhz
void TIM3_PWM_Init(u16 arr,u16 psc)
{
TIM3_Handler.Instance=TIM3; //定时器 3
TIM3_Handler.Init.Prescaler=psc; //定时器分频
TIM3_Handler.Init.CounterMode=TIM_COUNTERMODE_UP;//向上计数模式
TIM3_Handler.Init.Period=arr; //自动重装载值
TIM3_Handler.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
TIM3_Handler.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
//使能自动重载
HAL_TIM_PWM_Init(&TIM3_Handler); //初始化 PWM
TIM3_CH1Handler.OCMode=TIM_OCMODE_PWM1; //模式选择 PWM1
TIM3_CH1Handler.Pulse=arr/2;
//设置比较值,此值用来确定占空比,默认比较值为自动重装载值的一半,即占空比为
50%
TIM3_CH1Handler.OCPolarity=TIM_OCPOLARITY_LOW; //输出比较极性为低
HAL_TIM_PWM_ConfigChannel(&TIM3_Handler,
&TIM3_CH1Handler,TIM_CHANNEL_1);//配置 TIM3 通道 1
HAL_TIM_PWM_Start(&TIM3_Handler,TIM_CHANNEL_1);//开启 PWM 通道 1
}
//定时器底层驱动,时钟使能,引脚配置
//此函数会被 HAL_TIM_PWM_Init()调用
//htim:定时器句柄
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
GPIO_InitTypeDef GPIO_Initure;
if(htim->Instance==TIM3)
{
__HAL_RCC_TIM3_CLK_ENABLE(); //使能定时器 3
__HAL_AFIO_REMAP_TIM3_ENABLE(); //TIM3 通道引脚完全重映射使能
__HAL_RCC_GPIOC_CLK_ENABLE(); //开启 GPIOC 时钟
GPIO_Initure.Pin=GPIO_PIN_6; //PC6
GPIO_Initure.Mode=GPIO_MODE_AF_PP; //复用推挽输出
GPIO_Initure.Pull=GPIO_PULLUP; //上拉
GPIO_Initure.Speed=GPIO_SPEED_HIGH; //高速
HAL_GPIO_Init(GPIOC,&GPIO_Initure);
}
}
//设置 TIM3 通道 1 的占空比
//compare:比较值
void TIM_SetTIM3Compare1(u32 compare)
{
TIM3->CCR1=compare;
}
主函数main.c
int main(void)
{
u16 led6pwmval=0;
u8 dir=1;
HAL_Init(); //初始化 HAL 库
Stm32_Clock_Init(RCC_PLL_MUL9); //设置时钟,72M
delay_init(72); //初始化延时函数
uart_init(9600); //初始化串口
LED_Init(); //初始化 LED
TIM3_PWM_Init(899,0); //不分频。PWM 频率=72000/(899+1)=80Khz
while(1)
{
delay_ms(10);
if(dir)led6pwmval++; //dir==1 led6pwmval 递增
else led6pwmval--; //dir==0 led6pwmval 递减
if(led6pwmval>300)dir=0; //led6pwmval 到到 300 后,方向改为递减
if(led6pwmval==0)dir=1; //led6pwmval 递减到 0 后,方向改为递增
TIM_SetTIM3Compare1(led6pwmval);//修改比较值,修改占空比
}
实验总结:从死循环函数可以看出,我们将 led6pwmval这个值设置为PWM比较值,也就 是通过 led6pwmval 来控制 PWM 的占空比,然后控制 led6pwmval 的值从 0 变到 300,然后又 从 300 变到 0,如此循环,因此 DS6 的亮度也会跟着从暗变到亮,然后又从亮变到暗。至于这 里的值,我们为什么取 300,是因为 PWM 的输出占空比达到这个值的时候,我们的 LED 亮度 变化就不大了(虽然最大值可以设置到 899),因此设计过大的值在这里是没必要的。
实验现象:如果没有错误,我们将看 DS6 不停的由暗变到亮,然后又从 亮变到暗。每个过程持续时间大概为 3 秒钟左右。