不积小流,无以成江海。
分类: 嵌入式
2023-09-20 10:00:52
脉冲宽度调制(pwm),pulse width modulation,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。 stm32 的定时器除了 tim6 和 7。其他的定时器都可以用来产生 pwm 输出。其中高级定时器 tim1 和 tim8 可以同时产生多达 7 路的 pwm 输出。而通用定时器也能同时产生多达 4 路的 pwm 输出。
脉冲宽度调制模式可以产生一个由timx_arr寄存器确定频率、由timx_ccrx寄存器确定占空比的信号。
高级控制定时器(tim1和tim8)由一个16位的自动装载计数器组成,它由一个可编程的预分频器驱动。 它适合多种用途,包含测量输入信号的脉冲宽度(输入捕获),或者产生输出波形(输出比较、 pwm、嵌入死区时间的互补pwm等)。 使用定时器预分频器和rcc时钟控制预分频器,可以实现脉冲宽度和波形周期从几个微秒到几个毫秒的调节。 高级控制定时器(tim1和tim8)和通用定时器(timx)是完全独立的,它们不共享任何资源。
tim1和tim8功能描述 :
可编程高级控制定时器的主要部分是一个16位计数器和与其相关的自动装载寄存器。这个计数器可以向上计数、向下计数或者向上向下双向计数。此计数器时钟由预分频器分频得到。 计数器、自动装载寄存器和预分频器寄存器可以由软件读写,即使计数器还在运行读写仍然有效。
在timx_ccmrx寄存器中的ocxm位写入’110’(pwm模式1)或’111’(pwm模式2),能够独立地设置每个ocx输出通道产生一路pwm。
必须通过设置timx_ccmrx寄存器的ocxpe位使能相应的预装载寄存器,{banned}最佳后还要设置timx_cr1寄存器的arpe位,(在向上计数或中心对称模式中) 使能自动重装载的预装载寄存器。 仅当发生一个更新事件的时候,预装载寄存器才能被传送到影子寄存器,因此在计数器开始计数之前,必须通过设置timx_egr寄存器中的ug位来初始化所有的寄存器。
ocx的极性可以通过软件在timx_ccer寄存器中的ccxp位设置,它可以设置为高电平有效或低电平有效。ocx的输出使能通过(timx_ccer和timx_bdtr寄存器中)ccxe、ccxne、 moe、ossi和ossr位的组合控制。
在pwm模式(模式1或模式2)下,timx_cnt和timx_ccrx始终在进行比较,(依据计数器的计数方向)以确定是否符合timx_ccrx≤timx_cnt或者timx_cnt≤timx_ccrx。
捕获/比较模式寄存器(timx_ccmr1/2):该寄存器总共有 2 个,timx _ccmr1 和 timx _ccmr2。timx_ccmr1 控制 ch1 和 2,而 timx_ccmr2 控制 ch3 和 4。
控制寄存器 1(timx_cr1) :
事件产生寄存器(timx_egr) :
捕获/比较使能寄存器(timx_ccer) :
自动重装载寄存器(timx_arr) :arr包含了将要装载入实际的自动重装载寄存器的值。
详情参考stm32中文参考手册。
本文主要是利用 tim1 的 ch1、ch2、ch3、ch4 产生四路 pwm 输出,下面我们介绍通过库函数来配置该功能的步骤。
要使用 tim1,我们必须先开启 tim1 的时钟。 这里我们还要配置 pe9、pe11、pe13、 pe14 为复用输出,这是因为 tim1_ch1 通道重映射到 pe9、tim1_ch2 通道重映射到 pe11、tim1_ch3 通道重映射到 pe13、tim1_ch4 通道重映射到 pe14,此时,pe9、pe11、pe13、 pe14属于复用功能输出。
端口重映射:
为了使不同器件封装的外设 io 功能数量达到{banned}最佳优,可以把一些复用功能重新映射到其他一 些引脚上。stm32中有很多内置外设的输入输出引脚都具有重映射(remap)的功能。在 stm32 中引入了外设引脚重映射的概念,即一个外设的引脚除了具有默认的端口外,还可以通过设置重映射寄存器的方式,把这个外设的引脚映射到其它的端口。 stm32 的重映射控制是由复用重映射和调试 io 配置寄存器(afio_mapr)控制的。简单的讲就是把管脚的外设功能映射到另一个管脚,但不是可以随便映射的,具体对应关系《stm32 中文参考手册 v10》的 p116 页。
上图是截取的中文参考手册中的重映射表,从表中可以看出,默认情况下,tim1部分重映射的时候引脚位 pa8,pa9,pa10,pa11,同时我们可以将 tim1_ch1 通道重映射到 pe9、tim1_ch2 通道重映射到 pe11、tim1_ch3 通道重映射到 pe13、tim1_ch4 通道重映射到 pe14, 所以重映射还要使能 afio 功能时钟,然后要调用重映射函数。
详细步骤为:
(1)库函数使能 tim1 时钟的方法是:
rcc_apb1periphclockcmd(rcc_apb1periph_tim1, enable); //使能定时器1时钟
这里我们需要用到外设的重映射功能,所以需要用到rcc_apb2periph_afio。
(2)库函数使能 gpio 时钟的方法是:
rcc_apb2periphclockcmd(rcc_apb2periph_gpioe, enable); //使能gpio外设时钟
(3)库函数使能 afio 时钟的方法是:
rcc_apb2periphclockcmd(rcc_apb2periph_afio, enable); //使能复用时钟
(4)开启重映射:
在库函数函数里面设置重映射的函数是:
void gpio_pinremapconfig(uint32_t gpio_remap, functionalstate newstate);//{banned}中国第一个入口参数可以理解为设置重映射的类型
比如 tim1完全重映射入口参数为 gpio_fullremap_tim1,所以 tim1 完全重映射的库函数实现方法是:
gpio_pinremapconfig(gpio_fullremap_tim1, enable);
这样就将tim1的四个通道重新映射到管脚 pe9、pe11、pe13、 pe14 。
rcc_apb1periphclockcmd函数是用来开启或关闭对应的apb1外设时钟,
rcc_apb2periphclockcmd函数是用来开启或关闭对应的apb2外设时钟,
apb1 上面连接的是低速外设,包括电源接口、 备份接口、can、usb、i2c1、i2c2、uart2、uart3 等等,apb2 上面连接的是高速外设包 括 uart1、spi1、timer1、adc1、adc2、所有普通 io 口(pa~pe)、第二功能 io 口等。对应外设的选择可以参考下图。
rcc_apb1periph值:
rcc_apb2periph值:
//设置pe引脚为复用输出功能,输出pwm脉冲波形
gpio_initstructure.gpio_pin = gpio_pin_9|gpio_pin_11|gpio_pin_13|gpio_pin_14; //tim1_ch1-4
gpio_initstructure.gpio_mode = gpio_mode_af_pp; //推挽输出
gpio_initstructure.gpio_speed = gpio_speed_50mhz;
gpio_init(gpioe, &gpio_initstructure);//初始化gpioe
在开启了 tim1 的时钟之后,我们要设置 arr 和 psc 两个寄存器的值来控制输出 pwm 的周期。在库函数中,定时器的初始化参数是通过初始化函数 tim_timebaseinit 实现的:
void tim_timebaseinit(tim_typedef*timx, tim_timebaseinittypedef* tim_timebaseinitstruct);
{banned}中国第一个参数是确定是哪个定时器。
第二个参数是定时器初始化参数结构体指针,结构体类型为 tim_timebaseinittypedef,下面我们看看这个结构体的定义:
typedef struct {
uint16_t tim_prescaler;
uint16_t tim_countermode;
uint16_t tim_period;
uint16_t tim_clockdivision;
uint8_t tim_repetitioncounter;
} tim_timebaseinittypedef;
这个结构体一共有 5 个成员变量,对于通用定时器只有前面四个参数有用, {banned}最佳后一个参数tim_repetitioncounter 是高级定时器才有用的。
{banned}中国第一个参数 tim_prescaler 是用来设置分频系数。
第二个参数 tim_countermode 是用来设置计数方式,可以设置为向上计数, 向下计数方式还有中央对齐计数方式,比较常用的是向上计数模式 tim_countermode_up 和向下计数模式 tim_countermode_down。
第三个参数是设置自动重载计数周期值。
第四个参数是用来设置时钟分频因子。
调用的格式为:
//初始化tim1
tim_timebasestructure.tim_period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
tim_timebasestructure.tim_prescaler =psc; //设置用来作为timx时钟频率除数的预分频值
tim_timebasestructure.tim_clockdivision = 0; //设置时钟分割:tdts = tck_tim
tim_timebasestructure.tim_countermode = tim_countermode_up; //tim向上计数模式
tim_timebaseinit(tim1, &tim_timebasestructure); //根据tim_timebaseinitstruct中指定的参数初始化timx的时间基数单位
我们要设置 tim1_ch1、tim1_ch2、tim1_ch3、tim1_ch4 为 pwm 模式(默认是冻结的),在库函数中,pwm 通道设置是通过函数 tim_oc1init()~tim_oc4init()来设置的,不同的通道的设置函数不一样,这里我们以通道 1为例,所使用的函数是 tim_oc1init()。
void tim_oc2init(tim_typedef* timx, tim_ocinittypedef* tim_ocinitstruct);
tim_ocinittypedef 的定义:
typedef struct {
uint16_t tim_ocmode;
uint16_t tim_outputstate;
uint16_t tim_outputnstate;
uint16_t tim_pulse;
uint16_t tim_ocpolarity;
uint16_t tim_ocnpolarity;
uint16_t tim_ocidlestate;
uint16_t tim_ocnidlestate;
} tim_ocinittypedef;
参数 tim_ocmode 设置模式是 pwm 还是输出比较,这里我们是 pwm 模式。
参数 tim_outputstate 用来设置比较输出使能,也就是使能 pwm 输出到端口。
参数 tim_ocpolarity 用来设置极性是高还是低。
其他的参数 tim_outputnstate,tim_ocnpolarity,tim_ocidlestate 和 tim_ocnidlestate 是高级定时器 tim1 和 tim8 才用到的。
要实现我们上面提到的场景,方法是:
//初始化tim1 channel1-4 pwm模式
tim_ocinitstructure.tim_ocmode = tim_ocmode_pwm1; //选择定时器模式:tim脉冲宽度调制模式1,计数值<自动重装载值时,输出高电平
tim_ocinitstructure.tim_outputstate = tim_outputstate_enable; //比较输出使能
tim_ocinitstructure.tim_ocpolarity = tim_ocpolarity_high; //输出极性:tim输出比较极性高
tim_oc1init(tim1, &tim_ocinitstructure); //根据t指定的参数初始化外设tim1 oc1
tim_oc2init(tim1, &tim_ocinitstructure); //根据t指定的参数初始化外设tim1 oc2
tim_oc3init(tim1, &tim_ocinitstructure); //根据t指定的参数初始化外设tim1 oc3
tim_oc4init(tim1, &tim_ocinitstructure); //根据t指定的参数初始化外设tim1 oc4
tim1 捕获/比较模式寄存器 1(timx_ccmr1):
tim1 捕获/比较寄存器 1(timx_ccr1):
简单来说:没有预加载寄存器,这次修改的值,立马会被执行。而有了预加载寄存器,这次修改值会等到这次执行完后,才去执行。
tim_oc1preloadconfig(tim1, tim_ocpreload_enable); //使能tim1在ccr1上的预装载寄存器
tim_oc2preloadconfig(tim1, tim_ocpreload_enable); //使能tim1在ccr2上的预装载寄存器
tim_oc3preloadconfig(tim1, tim_ocpreload_enable); //使能tim1在ccr3上的预装载寄存器
tim_oc4preloadconfig(tim1, tim_ocpreload_enable); //使能tim1在ccr4上的预装载寄存器
tim1 控制寄存器 1(timx_cr1):
自动装载寄存器是预先装载的,写或读自动重装载寄存器将访问预装载寄存器。根据在 timx_cr1寄存器中的自动装载预装载使能位(arpe)的设置,预装载寄存器的内容被立即或在每次的更新事件uev时传送到影子寄存器。仅当发生一个更新事件的时候,预装载寄存器才能被传送到影子寄存器。
在完成以上设置了之后,我们需要使能 tim1。
tim_cmd(tim1, enable); // 使能 tim1
普通定时器在完成以上设置了之后,就可以输出 pwm 了,但是高级定时器,我们还需要使能刹车和死区寄存器( tim1_bdtr )的 moe 位,以使能整个 ocx (即 pwm )输出。库函数的设置函数为:
tim_ctrlpwmoutputs(tim1,enable);// moe 主输出使能
{banned}最佳后,在经过以上设置之后,pwm 其实已经开始输出了,只是其占空比和频率都是固定的,而我们通过修改 tim1_ccr1 则可以控制 ch1 的输出占空比。在库函数中,修改 tim1_ccr1 占空比的函数是:
void tim_setcompare1(tim_typedef* timx, uint16_t compare2);
对于其他通道,分别有一个函数名字,函数格式为 tim_setcomparex(x=1,2,3,4)。
附代码如下:
/*=======================================================
* 函 数:void tim1_pwm_init(u16 arr,u16 psc)
* 功 能:使用高级定时器tim1产生4路pwm输出
* 参 数:无
* 返回值:无
=======================================================*/
void tim1_pwm_init(u16 arr,u16 psc)
{
gpio_inittypedef gpio_initstructure;
tim_timebaseinittypedef tim_timebasestructure;
tim_ocinittypedef tim_ocinitstructure;
rcc_apb2periphclockcmd(rcc_apb2periph_tim1, enable); //使能定时器1时钟
rcc_apb2periphclockcmd(rcc_apb2periph_afio, enable); //复用时钟使能
rcc_apb2periphclockcmd(rcc_apb2periph_gpioe, enable); //使能gpio外设时钟
gpio_pinremapconfig(gpio_fullremap_tim1, enable);
//设置pe引脚为复用输出功能,输出pwm脉冲波形
gpio_initstructure.gpio_pin = gpio_pin_9|gpio_pin_11|gpio_pin_13|gpio_pin_14; //tim1_ch1-4
gpio_initstructure.gpio_mode = gpio_mode_af_pp; //推挽输出
gpio_initstructure.gpio_speed = gpio_speed_50mhz;
gpio_init(gpioe, &gpio_initstructure);//初始化gpioe
//初始化tim1
tim_timebasestructure.tim_period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
tim_timebasestructure.tim_prescaler =psc; //设置用来作为timx时钟频率除数的预分频值
tim_timebasestructure.tim_clockdivision = 0; //设置时钟分割:tdts = tck_tim
tim_timebasestructure.tim_countermode = tim_countermode_up; //tim向上计数模式
tim_timebasestructure.tim_repetitioncounter=0;
tim_timebaseinit(tim1, &tim_timebasestructure); //根据tim_timebaseinitstruct中指定的参数初始化timx的时间基数单位
//初始化tim1 channel1-4 pwm模式
tim_ocinitstructure.tim_ocmode = tim_ocmode_pwm1; //选择定时器模式:tim脉冲宽度调制模式1,计数值<自动重装载值时,输出高电平
tim_ocinitstructure.tim_outputstate = tim_outputstate_enable; //比较输出使能
tim_ocinitstructure.tim_ocpolarity = tim_ocpolarity_high; //输出极性:tim输出比较极性高
tim_oc1init(tim1, &tim_ocinitstructure); //根据t指定的参数初始化外设tim1 oc1
tim_oc2init(tim1, &tim_ocinitstructure); //根据t指定的参数初始化外设tim1 oc2
tim_oc3init(tim1, &tim_ocinitstructure); //根据t指定的参数初始化外设tim1 oc3
tim_oc4init(tim1, &tim_ocinitstructure); //根据t指定的参数初始化外设tim1 oc4
tim_oc1preloadconfig(tim1, tim_ocpreload_enable); //使能tim1在ccr1上的预装载寄存器
tim_oc2preloadconfig(tim1, tim_ocpreload_enable); //使能tim1在ccr2上的预装载寄存器
tim_oc3preloadconfig(tim1, tim_ocpreload_enable); //使能tim1在ccr3上的预装载寄存器
tim_oc4preloadconfig(tim1, tim_ocpreload_enable); //使能tim1在ccr4上的预装载寄存器
tim_cmd(tim1, enable); //使能tim1
tim_ctrlpwmoutputs(tim1,enable);////moe 主输出使能,高级定时器需要添加
}
参考资料:
stm32f1开发指南(精英版)-库函数版本_v1.0.pdf
stm32中文参考手册_v10.pdf