STM32F103 PWM配置
在《STM32F103定时器配置》中我们介绍了PWM的产生原理,本节介绍介绍如何编码实现PWM的输出。
一、PWM相关寄存器
TIMx如果要产生PWM,除了我们上一节提到的如下寄存器:
- 控制寄存器(
TIMx_CR1); DMA/中断使能寄存器(TIMx_DIER);- 预分频寄存器(
TIMx_PSC); - 从模式控制寄存器(
TIMx_SMCR); - 自动重装载寄存器(
TIMx_ARR); - 状态寄存器(
TIMx_SR);
还需要使用到:
- 捕获/比较模式寄存器(
TIMx_CCM1/2); - 捕获/比较使能寄存器(
TIMx_CCER); - 捕获/比较寄存器(
TIMx_CCR1~4); - 刹车和死区寄存器(
TIMx_BDTR)(只有高级定时器用到)。
注意:这里捕获指的是输入捕获,比较指的时输出比较。
1.1 捕获/比较模式寄存器(TIMx_CCMR1/2)
捕获/比较模式寄存器一共有两个:
TIMx_CCMR1:控制通道1和2;TIMx_CCMR2:控制通道3和4;
这里以 TIMx_CCM1寄存器为例进行介绍:
1.2 捕获/比较使能寄存器(TIMx_CCER)
TIMx_CCER寄存器每4位描述一个通道;
其中:
CCxE:输入/捕获x输出使能位,我们需要重点关注;CCxP:输入/捕获x输出极性。
1.3 捕获/比较寄存器(TIMx_CCR1~4)
捕获/比较模式寄存器一共有4个,依次用于描述每一个通道,这里以TIMx_CCR1为例;
1.4 刹车和死区寄存器(TIMx_BDTR)
TIMx_BDTR寄存器需要重点关注位15主输出使能位;
二、PWM生成源码
2.1 PWM初始化步骤
PWM生成配置流程如下:
(1) TIMx时钟使能:通过配置RCC_APB1ENR/RCC_APB2ENR寄存器使能TIMx时钟;
(2) GPIO功能复用:配置GPIO(比如TIM1通道1为PA8)为复用功能推挽输出模式;
(3) 配置TIMx时基单元;
- 配置
TIMx_ARR寄存器自动重装载的值; - 配置
TIMx_PSC寄存器预分频系数;
(4) 配置PWM相关寄存器;
-
配置
TIMx_CCMRx寄存器:- 输入捕获/比较输出选择(
CCxS):配置为比较输出(用于实现PWM输出功能); - 输出比较模式(
CCxM):配置为PWM模式1或者PWM模式2;
- 输入捕获/比较输出选择(
-
配置
TIMx_CCER寄存器:- 输入捕获/比较输出使能(
CCxE):即开启比较输出功能,这样才能输出PWM;
- 输入捕获/比较输出使能(
-
针对高级定时器配置
TIMx_BDTR寄存器位15:使能PWM输出;
(5) 允许TIMx工作:配置TIMx_CR1位0;
(6) 修改TIMx_CCRx寄存器可以改变占空比。
2.2 源码实现
2.2.1 PWM初始化
PWM初始化函数TIM_PWM_Init定义如下:
/**************************************************************************************************************
*
* Description: 高级定时器1和8 APB2预分频系数=1 则计数器的时钟频率为 APB2 否则APB2*2
* 通用定时器2~7 APB1预分频系数=1 则计数器的时钟频率为 APB1 否则APB1*2
* Parameter : timx TIMER1~TIMER5 8
DEFAULT_PSC 默认预分频系数
计数器的时钟频率 = Fclk/(PSC[15:0]+1) Fclk单位MHZ
time 中断时间 = (ARR+1)/计数器的时钟频率 单位us
中断频率 = Fclk*1000/((PSC+1)*(ARR+1)) 单位kHZ
* frequent 频率: 1~360 单位khz
Channel 通道 PWM_CH1~4
*
**************************************************************************************************************/
void TIM_PWM_Init(TIMn timn,u8 frequent,PWM_CHANNEL Channel) //PWM初始化
{
u16 arr; //存放自动重装载的值
u8 fclk; //存放定时器时钟频率 MHZ
u16 DEFAULT_PSC = 1; //默认预分频系数为1
u8 REMAP=0x00; //映射情况 0x00默认 0x01 0x02: 0x03 自己设置
if(timn==0||timn==7) //定时器1或8
{
fclk = 72; //默认APB2 1倍频
}
else
{
fclk =72; //默认APB1 2倍频
}
arr = fclk*1000/(DEFAULT_PSC+1)/frequent-1;
//****************************************************************************************
if(timn==0) //定时器1
{
RCC->APB2ENR |=1<<11; //高级定时器1时钟使能
TIM1->BDTR|=1<<15; //主输出(pwm)使能(必须)
if(REMAP==0x00) //默认情况下
{
switch(Channel)
{
case PWM_CH1:
gpio_init(PA8,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ 主输出
gpio_init(PB13,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ 互补输出
break;
case PWM_CH2:
gpio_init(PA9,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
gpio_init(PB14,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH3:
gpio_init(PA10,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
gpio_init(PB15,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH4:
gpio_init(PA11,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
}
}
else if(REMAP==0x01) //部分映射
{
RCC->APB2ENR |= 1<<0; //使能I/O复用时钟
AFIO->MAPR &=~(0x3<<6); //位7:6清零
AFIO->MAPR |= REMAP<<6; //TIM1部分映射
switch(Channel)
{
case PWM_CH1:
gpio_init(PA8,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
gpio_init(PA7,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH2:
gpio_init(PA9,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
gpio_init(PB0,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH3:
gpio_init(PA10,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
gpio_init(PB1,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH4:
gpio_init(PA11,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
}
}
else if(REMAP==0x03) //完全映射
{
RCC->APB2ENR |= 1<<0; //使能I/O复用时钟
AFIO->MAPR &=~(0x3<<6); //位7:6清零
AFIO->MAPR |= REMAP<<6; //TIM1完全映射
switch(Channel)
{
case PWM_CH1:
gpio_init(PE9,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
gpio_init(PE8,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH2:
gpio_init(PE11,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
gpio_init(PE10,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH3:
gpio_init(PE13,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
gpio_init(PE12,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH4:
gpio_init(PE14,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
}
}
else
{
ASSERT(0);
}
}
//********************************************************************************************
else if(timn==1) //定时器2
{
RCC->APB1ENR |=1<<0; //定时器2时钟使能
if(REMAP==0x00) //默认情况下
{
switch(Channel)
{
case PWM_CH1:
gpio_init(PA0,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH2:
gpio_init(PA1,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH3:
gpio_init(PA2,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH4:
gpio_init(PA3,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
}
}
else if(REMAP==0x01) //部分映射
{
RCC->APB2ENR |= 1<<0; //使能I/O复用时钟
AFIO->MAPR &=~(0x3<<8); //位9:8清零
AFIO->MAPR |= REMAP<<8; //TIM2部分映射
switch(Channel)
{
case PWM_CH1:
gpio_init(PA15,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH2:
gpio_init(PB3,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH3:
gpio_init(PA2,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH4:
gpio_init(PA3,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
}
}
else if(REMAP==0x02) //部分映射
{
RCC->APB2ENR |= 1<<0; //使能I/O复用时钟
AFIO->MAPR &=~(0x3<<8); //位9:8清零
AFIO->MAPR |= REMAP<<8; //TIM2部分映射
switch(Channel)
{
case PWM_CH1:
gpio_init(PA0,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH2:
gpio_init(PA1,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH3:
gpio_init(PB10,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH4:
gpio_init(PB11,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
}
}
else if(REMAP==0x03) //完全映射
{
RCC->APB2ENR |= 1<<0; //使能I/O复用时钟
AFIO->MAPR &=~(0x3<<8); //位9:8清零
AFIO->MAPR |= REMAP<<8; //TIM2部分映射
switch(Channel)
{
case PWM_CH1:
gpio_init(PA15,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH2:
gpio_init(PB3,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH3:
gpio_init(PB10,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH4:
gpio_init(PB11,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
}
}
else
{
ASSERT(0);
}
}
//*****************************************************************************************************************
else if(timn==2) //定时器3
{
REMAP=0x03;
RCC->APB1ENR |=1<<1; //定时器3时钟使能
if(REMAP==0x00) //默认情况下
{
switch(Channel)
{
case PWM_CH1:
gpio_init(PA6,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH2:
gpio_init(PA7,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH3:
gpio_init(PB0,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH4:
gpio_init(PB1,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
}
}
else if(REMAP==0x02) //部分映射
{
RCC->APB2ENR |= 1<<0; //使能I/O复用时钟
AFIO->MAPR &=~(0x3<<10); //位11:10清零
AFIO->MAPR |= REMAP<<10; //TIM3部分映射
switch(Channel)
{
case PWM_CH1:
gpio_init(PB4,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH2:
gpio_init(PB5,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH3:
gpio_init(PB0,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH4:
gpio_init(PB1,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
}
}
else if(REMAP==0x03) //完全映射
{
RCC->APB2ENR |= 1<<0; //使能I/O复用时钟
AFIO->MAPR &=~(0x3<<10); //位11:10清零
AFIO->MAPR |= REMAP<<10; //TIM3部分映射
switch(Channel)
{
case PWM_CH1:
gpio_init(PC6,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH2:
gpio_init(PC7,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH3:
gpio_init(PC8,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH4:
gpio_init(PC9,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
}
}
else
{
ASSERT(0);
}
}
//*********************************************************************************************************
else if(timn==3) //定时器4
{
RCC->APB1ENR |=1<<2; //定时器4时钟使能
if(REMAP==0x00) //默认情况下
{
switch(Channel)
{
case PWM_CH1:
gpio_init(PB6,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH2:
gpio_init(PB7,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH3:
gpio_init(PB8,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH4:
gpio_init(PB9,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
}
}
else if(REMAP==0x01) //完全映射
{
RCC->APB2ENR |= 1<<0; //使能I/O复用时钟
AFIO->MAPR |= REMAP<<12; //TIM4部分映射
switch(Channel)
{
case PWM_CH1:
gpio_init(PD12,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH2:
gpio_init(PD13,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH3:
gpio_init(PD14,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH4:
gpio_init(PD15,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
}
}
else
{
ASSERT(0);
}
}
//***********************************************************************************************************
else if(timn==4) //定时器5
{
RCC->APB1ENR |=1<<3; //定时器5时钟使能
switch(Channel)
{
case PWM_CH1:
gpio_init(PA0,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH2:
gpio_init(PA1,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH3:
gpio_init(PA2,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH4:
gpio_init(PA3,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
}
}
//********************************************************************************************************
else if(timn==7)
{
RCC->APB2ENR |=1<<13; //定时器8时钟使能
TIM8->BDTR|=1<<15; //主输出(pwm)使能(必须)
switch(Channel)
{
case PWM_CH1:
gpio_init(PC6,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
gpio_init(PA7,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH2:
gpio_init(PC7,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
gpio_init(PB0,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH3:
gpio_init(PC8,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
gpio_init(PB1,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
case PWM_CH4:
gpio_init(PC9,GPO_MULPUSH_PULL_50,HIGH); //复用推挽输出,最大速度50MHZ
break;
}
}
else
{
ASSERT(0);
}
TIMx[timn]->PSC = DEFAULT_PSC; //预分频值
TIMx[timn]->ARR = arr; //重新装载的值
switch(Channel)
{
case PWM_CH1:
TIMx[timn]->CCMR1 |=0x07<<4; //PWM2输出模式设置
//TIMx[timn]->CCMR1 |=0X01<<3; //输出比较预装载使能
//TIMx[timn]->CR1 = 1<<7; //自动重装在预装载允许位
TIMx[timn]->CCER |= 0x0F; //低电平有效 CH1使能 如是TIM1则互补输出使能
TIMx[timn]->CCR1 =0; //初始化占空比为0
break;
case PWM_CH2:
TIMx[timn]->CCMR1 |=0x07<<12; //PWM2输出模式设置
//TIMx[timn]->CCMR1 |=0X01<<3; //输出比较预装载使能
//TIMx[timn]->CR1 = 1<<7; //自动重装在预装载允许位
TIMx[timn]->CCER |= 0x0F<<4; //低电平有效 CH2使能 如是TIM1则互补输出使能
TIMx[timn]->CCR2 =0; //初始化占空比为0
break;
case PWM_CH3:
TIMx[timn]->CCMR2 |=0x07<<4; //PWM3输出模式设置
//TIMx[timn]->CCMR1 |=0X01<<3; //输出比较预装载使能
//TIMx[timn]->CR1 = 1<<7; //自动重装在预装载允许位
TIMx[timn]->CCER |= 0x0F<<8; //低电平有效 CH3使能 如是TIM1则互补输出使能
TIMx[timn]->CCR3 =0; //初始化占空比为0
break;
case PWM_CH4:
TIMx[timn]->CCMR2 |=0x07<<12; //PWM4输出模式设置
//TIMx[timn]->CCMR1 |=0X01<<3; //输出比较预装载使能 1:更新事件时装载
//TIMx[timn]->CR1 = 1<<7; //自动重装在预装载允许位
TIMx[timn]->CCER = 0x03<<12; //低电平有效 CH2使能
TIMx[timn]->CCR4 =0; //初始化占空比为0
break;
}
//TIMx[timn]->DIER = 1<<0; //允许更新(溢出)中断 UIE=1;
TIMx[timn]->CR1 = 1<<0; //使能计数器 开始计数
}
2.2.2 PWM占空比调节
PWM占空比调节函数:
/**************************************************************************************************************
*
* Description: PWM设置
* Parameter : timx TIMER1~TIMER5 8
DEFAULT_PSC 默认预分频系数
Channel 通道 PWM_CH1~4
duty 占空比 0~100
*
**************************************************************************************************************/
void TIM_PWM_Duty(TIMn timn,u8 duty,PWM_CHANNEL Channel) //PWM占空比设置
{
u16 arr;
u16 temp; //CCRx装载值
arr = TIMx[timn]->ARR;
temp = duty*arr/100;
if(Channel==PWM_CH1)
TIMx[timn]->CCR1 =temp; //设置占空比
else if(Channel==PWM_CH2)
TIMx[timn]->CCR2 =temp; //设置占空比
else if(Channel==PWM_CH3)
TIMx[timn]->CCR3 =temp; //设置占空比
else if(Channel==PWM_CH4)
TIMx[timn]->CCR4 =temp; //设置占空比
else
{
ASSERT(0);
}
}
2.3 实现功能
由于LED1连接的是PA8引脚,而这个引脚正好是TIM1的CH1的输出引脚,因此我们可以通过调节占空比,来使得LED1越来越亮。
2.3.1 main函数实现
int main()
{
u8 i=100;
STM32_Clock_Init(9); //系统时钟初始化
STM32_NVIC_Init(2,USART1_IRQn,0,1); //串口中断优先级初始化,其中包括中断使能
usart_init(USART_1,115200); //串口1初始化,波特率115200 映射到PA9 PA10
// LED1初始化
gpio_init(PA8,GPO_SpeedMax_50,HIGH); //PA8接入LED1
TIM_PWM_Init(TIMER1, 100, PWM_CH1); // PWM初始化,频率为100kHz
TIM_PWM_Duty(TIMER1, 50, PWM_CH1);
while(1)
{
for(i=100;i>0;i--) //LED1越来越亮
{
TIM_PWM_Duty(TIMER1, i, PWM_CH1);
if(i%10==0)
{
printf("duty: %d\n",i);
}
delay_ms(15);
}
}
}
2.3.3 测试
编译程序并下载测试:

可以通过串口查看当前输出的占空比。
此外,如果有示波器或者逻辑分析器,可以更直观的查看PA8引脚输出的波形。
这里我们将PA8引脚连接到逻辑分析仪的通道0;
这里一个时钟周期为10us,换算成频率就是10kHz,与我们程序设置的一致。
三、源码下载
源码下载路径:stm32f103。

浙公网安备 33010602011771号