一、关键概念
1、定时功能:假设内部时钟CK_INT(TIMxCLK from RCC)为 72Mhz,定时时间为 10MS。
配置CK_CNT时钟来源为 内部时钟CK_INT,然后根据定时时间10MS,配置CK_CNT的预分频为 72 - 1(一个CK_CNT周期 1us),配置计数器溢出值为 10000 - 1(10MS溢出);配置计数器为Upcounting mode,循环模式,并使能Update中断。
软件启动计数器后,每个CK_CNT脉冲驱动计数器加1,当计数达到设定的溢出值9999时(10MS定时到了),计数器Overflow 并触发Update中断(软件在中断函数中处理定时超时),然后计数器从0开始重新计数。
2、统计TIMx_ETR引脚 输入的脉冲个数:
配置CK_CNT时钟来源为 TIMx_ETR引脚,CK_CNT的预分频为1,计数器溢出值设置为最大65535。配置计数器为Upcounting mode,循环模式,并使能Update中断。
软件启动计数器后,每个CK_CNT脉冲(即来自TIMx_ETR引脚的脉冲)驱动计数器加1,当计数达到设定的溢出值 65535时,计数器Overflow 并触发Update中断(在中断函数中记录计数器Overflow次数),然后计数器从0开始重新计数。软件读计数器当前计数值,并结合计数器Overflow次数,可计算出TIMx_ETR引脚输入的脉冲个数。
3、测量TIMx_CH1引脚 输入的PWM信号的频率和占空比:假设内部时钟CK_INT(TIMxCLK from RCC)为 72Mhz,输入PWM的频率范围是 1Khz(period = 1MS,1%占空比时高电平周期为 10us) ~ 100Khz (period = 10us,1%占空比时高电平周期为 0.1us)。
配置CK_CNT时钟来源为 内部时钟CK_INT,配置CK_CNT的预分频为 7 - 1(一个CK_CNT周期 ≈ 0.1us),配置计数器溢出值为 20000 - 1(2MS溢出)。配置计数器为Upcounting mode,循环模式,并使能Update中断。配置CC1 channel为输入 且输入引脚为TIMx_CH1,输入信号不分频、不滤波,输入信号的上升沿触发CC1捕获,并使能 CC1 interrupt;配置CC2 channel为输入 且输入引脚为 TIMx_CH1,输入信号不分频、不滤波,输入信号的下降沿触发CC2捕获。配置 Slave mode为 Reset mode,且 TIMx_CH1信号的上升沿触发 Counter reset。
软件每次在需要计算PWM频率和占空比时,都要启动一次PWM测量功能(或者用一个定时器定期启动,这样PWM频率和占空比就能定期自动更新)。软件启动计数器 并使能 CC1、CC2 channel后:
当 Counter overflow时,触发Update interrupt,在中断函数中设置 PWM频率为0,占空比为 0%(读TIMx_CH1引脚为低电平)或 100%(读TIMx_CH1引脚为高电平),最后停止计数器 并禁能CC1、CC2 channel;
当TIMx_CH1产生下降沿时, 寄存器CCR2 捕获计数器当前计数值;
当TIMx_CH1产生上升沿时, 寄存器CCR1 捕获计数器当前计数值,然后Counter reset,并触发CC1 interrupt,在中断函数中记录TIMx_CH1上升沿的次数,若次数为2(2次上升沿能保证CCR1是PWM周期,CCR2是一个PWM周期中高电平的时间),则设置PWM周期为(CCR1 * 0.1us),占空比为 CCR2 / CCR1,最后停止计数器 并禁能CC1、CC2 channel。
4、输出指定频率和占空比的PWM波形:假设内部时钟CK_INT(TIMxCLK from RCC)为 72Mhz,TIMx_CH1引脚输出PWM 且频率为100Khz(period = 10us)、占空比为10%,TIMx_CH1引脚高电平有效。
配置CK_CNT时钟来源为 内部时钟CK_INT,配置CK_CNT的预分频为 72 - 1(一个CK_CNT周期 为1us),配置计数器溢出值为 10 - 1(10us溢出)。配置计数器为Upcounting mode,循环模式。配置CC1 channel为输出 且 输出端高电平有效 且模式为 PWM mode1,计数器比较值CCR1为 1。
软件启动计数器 并使能 CC1 channel后,每个CK_CNT脉冲驱动计数器加1,当计数达到设定的溢出值时,计数器Overflow 并从0开始重新计数,在这个过程中,定时器通过不断比较 计数器当前值 和 计数器比较值CCR1,在TIMx_CH1引脚输出一个频率为100Khz、占空比为10%的PWM波形。
二、程序开发流程
1、TIM3定时功能
/* 函数功能:启动 timer3. 此接口供外部模块调用. */
void timer3_start(void)
{
CEN bit = 1;
}
/* 函数功能:停止 timer3. 此接口供外部模块调用. */
void timer3_stop(void)
{
CEN bit = 0;
}
/* 函数功能:设置timer3定时时间、超时回调函数, 以及超时后是否停止. 此接口供外部模块调用.
返回值:0, 设置成功; 非0, 设置失败
*/
int timer3_set(uint16_t timeout_us, void (*cb)(void), bool one_shot_flag)
{
timer3->cb = cb;
TIM3_ARR = timeout_us - 1; //一个CK_CNT周期 已配置为1us
OPM bit = one_shot_flag == TRUE ? 1 : 0;
TIM3_CNT = 0;
return 0;
}
static void timer3_irq_handler(void)
{
if(UIF标志置位)
{
清零UIF标志;
if(timer3->cb)
{
timer3->cb();
}
}
}
static void timer3_init(void)
{
ECE bit = 0, SMS[2:0] = 0; //配置CK_CNT时钟来源为 内部时钟CK_INT
DIR bit = 0, CMS[1:0] = 0; //计数器为Upcounting mode
TIM3_PSC = 72 - 1; //一个CK_CNT周期为 1us (CK_INT频率为 72Mhz)
UIE bit = 1; //使能Update interrupt
在NVIC中设置timer3中断优先级并使能中断;
}
2、统计TIM3_ETR引脚输入的脉冲个数
static uint32_t g_overflow_cnt;
/* 函数功能:读 TIM3_ETR引脚输入的脉冲个数. 此接口供外部模块调用 */
uint32_t timer3_ETR_pulse_read(void)
{
uint32_t cnt = g_overflow_cnt * (TIM3_ARR + 1) + (TIM3_CNT + 1);
interrupt_disable();
g_overflow_cnt = 0;
interrupt_enable();
TIM3_CNT = 0;
return cnt;
}
static void timer3_irq_handler(void)
{
if(UIF标志置位)
{
清零UIF标志;
(g_overflow_cnt)++;
}
}
static void timer3_init(void)
{
ECE bit = 1; //配置CK_CNT时钟来源为 TIM3_ETR引脚
DIR bit = 0, CMS[1:0] = 0; //计数器为Upcounting mode
OPM bit = 0; //计数器为循环模式
TIM3_PSC = 0; //CK_CNT时钟不分频
TIM3_ARR = 65535;
UIE bit = 1; //使能Update interrupt
ETPS[1:0] = 0; //TIM3_ETR输入信号不分频
ETP bit = 0; //TIM3_ETR输入上升沿有效
ETF[3:0] = 0; //TIM3_ETR输入信号不滤波
配置TIM3_ETR引脚为上拉输入;
在NVIC中设置timer3中断优先级并使能中断;
CEN bit = 1; //Start Counter
}
3、测量TIM3_CH1引脚 输入的PWM信号的频率和占空比
输入PWM的频率范围是 1Khz(period = 1MS,1%占空比时高电平周期为 10us) ~ 100Khz (period = 10us,1%占空比时高电平周期为 0.1us)
static uint8_t g_cc1_rising_edge_cnt;
static uint32_t g_pwm_frequency; // unit: 1hz
static uint8_t g_pwm_duty; // unit: 1%
/* 函数功能:读 TIM3_CH1引脚输入的PWM频率和占空比. 此接口供外部模块调用 */
void timer3_pwm_frequency_duty_read(uint32_t *frequency, uint8_t *duty)
{
if(frequency)
*frequency = g_pwm_frequency;
if(duty)
*duty = g_pwm_duty;
}
static void timer3_measure_pwm_start(void)
{
if(CEN bit == 1)
return;
CEN bit = 1; //Start Counter
CC1E bit = 1; //使能CC1 capture
CC2E bit = 1; //使能CC2 capture
}
static void timer3_measure_pwm_stop(void)
{
CEN bit = 0; //Stop Counter
CC1E bit = 0; //禁能CC1 capture
CC2E bit = 0; //禁能CC2 capture
g_cc1_rising_edge_cnt = 0;
}
static void timer3_irq_handler(void)
{
if(CC1IF标志置位) //TIM3 CC1 capture
{
清零CC1IF标志;
(g_cc1_rising_edge_cnt)++;
if(g_cc1_rising_edge_cnt >= 2) //period = TIM3_CCR1, duty = TIM3_CCR2 / TIM3_CCR1
{
if(TIM3_CCR1 == 0)
{
g_pwm_frequency = 0;
duty = 0;
}
else
{
g_pwm_frequency = 1000000 / (TIM3_CCR1 * 0.1);
g_pwm_duty = (TIM3_CCR2 * 100) / TIM3_CCR1;
}
timer3_measure_pwm_stop(); //CC1 capture中断不能一直使能, 否则若PWM频率太快会让CPU卡死在TIM3中断
}
}
if(UIF标志置位) //TIM3 Overflow
{
清零UIF标志;
g_pwm_frequency = 0;
if(TIM3_CH1引脚 为高电平)
{
g_pwm_duty = 100;
}
else
{
g_pwm_duty = 0;
}
timer3_measure_pwm_stop();
}
}
static void timer3_init(void)
{
ECE bit = 0, SMS[2:0] = 0b100; //配置CK_CNT时钟来源为内部时钟CK_INT, 且Slave mode为 Reset mode
TS[2:0] = 0b101; //TIMx_CH1信号的上升沿触发Counter reset
DIR bit = 0, CMS[1:0] = 0; //计数器为Upcounting mode
OPM bit = 0; //计数器为循环模式
TIM3_PSC = 6; //一个CK_CNT周期 ≈ 0.1us (CK_INT频率为 72Mhz)
TIM3_ARR = 20000 - 1; //计数器 Overflow周期:2MS
UIE bit = 1; //使能Update interrupt
CC1S[1:0] = 0b01; //TIM3 CC1 channel为输入 且输入引脚为TIM3_CH1
IC1PSC[1:0] = 0; //TIM3 CC1 channel信号不分频
IC1F[3:0] = 0; //TIM3 CC1 channel信号不滤波
CC1P bit = 0; //CC1捕获上升沿
CC1IE bit = 1; //使能CC1 capture中断
CC2S[1:0] = 0b10; //TIM3 CC2 channel为输入 且输入引脚为TIM3_CH1
IC2PSC[1:0] = 0; //TIM3 CC2 channel信号不分频
IC2F[3:0] = 0; //TIM3 CC2 channel信号不滤波
CC2P bit = 1; //CC2捕获下降沿
配置TIM3_CH1引脚为上拉输入;
在NVIC中设置timer3中断优先级并使能中断;
//TIM4定期启动TIM3测量PWM, 这样PWM的频率和占空比就可以自动更新.如果不是定期启动, 而是一直启动, 则CPU可能卡死在TIM3中断
timer4_set(TIMOUT_1S, timer3_measure_pwm_start, ONE_SHOT_DISABLE);
timer4_start();
}
4、用TIM3_CH1引脚 输出指定频率和占空比的PWM波形
/* 函数功能:启动TIM3_CH1引脚输出PWM. 此接口供外部模块调用 */
void timer3_pwm_start(void)
{
CC1E bit = 1; //使能CC1 channel
CEN bit = 1; //Start Counter
}
/* 函数功能:停止TIM3_CH1引脚输出PWM. 此接口供外部模块调用 */
void timer3_pwm_stop(void)
{
CC1E bit = 0; //禁能CC1 channel
CEN bit = 0; //Stop Counter
}
/* 函数功能:设置 TIM3_CH1引脚输出的PWM, period:PWM周期, 单位 1ns, pulse:PWM高电平时间, 单位 1ns. 此接口供外部模块调用 */
void timer3_pwm_set(uint32_t period, uint32_t pulse)
{
uint64_t tim_clock, psc;
tim_clock = 72; //内部时钟CK_INT为 72Mhz
period = (unsigned long long)period * tim_clock / 1000ULL;
psc = period / 65535 + 1;
TIM3_PSC = psc - 1; //配置预分频TIM3_PSC
period = period / psc;
if(period < 2)
{
period = 2;
}
TIM3_ARR = period - 1; //配置TIM3_ARR
pulse = (unsigned long long)pulse * tim_clock / psc / 1000ULL;
TIM3_CCR1 = pulse; //配置TIM3_CCR1
TIM3_CNT = 0;
UG bit = 1; //软件触发Update event, 以便让TIM3更新各寄存器
}
static void timer3_init(void)
{
ECE bit = 0, SMS[2:0] = 0; //配置CK_CNT时钟来源为内部时钟CK_INT
DIR bit = 0, CMS[1:0] = 0; //计数器为Upcounting mode
OPM bit = 0; //计数器为循环模式
TIM3_PSC = 71; //一个CK_CNT周期 = 1us (CK_INT频率为 72Mhz)
TIM3_ARR = 1000 - 1; //计数器 Overflow周期:1MS
ARPE bit = 1; //使能寄存器TIM3_ARR的preload功能
CC1S[1:0] = 0; //TIM3 CC1 channel为输出
CC1P bit = 0; //TIMx_CH1引脚高电平有效
OC1M[2:0] = 0b110; //TIM3 CC1 channel为 PWM mode1
OC1PE bit = 1; //使能寄存器TIM3_CCR1的preload功能
配置TIM3_CH1引脚为复用推挽输出;
}
三、注意事项
1、UDIS bit 使用场景
软件写 preload register时,先置位UDIS bit ,然后写preload register,最后再清零UDIS bit,这样可以避免以下两个动作产生冲突:
updating shadow register from preload register
writing preload register
2、URS bit 使用场景
使用CC1和 CC2通道测量输入PWM波形的频率和占空比时,CC1通道采样PWM上升沿,CC2通道采样PWM下降沿,Slave mode为Reset mode 并选择 CC1通道的信号为 TRGI,当CC1通道发生捕获时,TRGI信号会置位UG标志,此时如果UIE标志为1,则会触发 Update interrupt,换句话说,每次CC1通道发生捕获,都会触发Update interrupt,结果就是CPU会频繁响应Update interrupt。
在这种场景下,软件可以配置 URS bit为0,这样当UIE标志为1时,Counter overflow/underflow时会触发 Update interrupt,但CC1通道发生捕获时,不会触发 Update interrupt。
3、容易混淆的概念:
① 触发Counter reset 条件:软件置位UG标志,或Slave mode为Reset mode时(SMS bits = 100),TRGI信号置位UG标志。
Upcounting mode或 Center-aligned mode:reset时,Counter值更新为 0
Downcounting mode:reset时,Counter值更新为ARR值(the autoreload is updated before the counter is reloaded)
② 计数器溢出时,更新Counter值的四种情况:
Upcounting mode & Counter overflow:Counter值更新为 0
Downcounting mode & Counter underflow:Counter值更新为ARR值(the autoreload is updated before the counter is reloaded)
Center-aligned mode & Counter overflow:Counter值更新为ARR值(the autoreload is updated before the counter is reloaded)
Center-aligned mode & Counter underflow:Counter值更新为 0
③ 触发Update event条件:UDIS标志为0时,(Counter overflow/underflow) || (软件或 TRGI信号 置位UG标志)。
PS:Update event触发时,preload register的值自动更新到 shadow register
④ 触发UIF标志置位条件:
Counter overflow/underflow
or URS标志为0时,软件或 TRIG信号 置位 UG标志
PS:UIE标志、UDE标志为1时,UIF标志置位会触发 Update interrupt和 Update DMA request
4、用OCx输出PWM时
PWM1 mode + Downcounting mode无法输出 0%占空比,
PWM2 mode + Downcounting mode无法输出 100%占空比。
5、用OCx输出PWM时,如果想在停止输出PWM时控制 OCx电平,可将CC1 channel的输出模式 从PWM mode 改为 force mode。
6、使用TIMx输出PWM时,建议使能TIMx_CCRx 和 TIMx_ARR的preload功能,否则当修改寄存器时,会影响上一个输出的PWM波形。
7、关于高级定时器TIM1&TIM8:
有Repetition Counter Register:配合计数器的One-pulse mode,可控制在CCx channel上输出的PWM的周期数量,比如控制CC1 channel输出 5个周期的波形。
一个OCxREF信号可同时控制 OCx、OCxN互补输出:如果只使用OCx、OCxN当中一个引脚输出PWM,注意OCx、OCxN不能同时使能,并且需使能CCx的Main output。
参考资料
[1] STM32F103xx datasheet.
[2] STM32F10xxx reference manual.