STM32如何实现可调频率、占空比的PWM波形,且可指定输出脉冲个数?
读者朋友“*imYan*”问:
pwm实现频率可调和占空比可调后怎么来实现输出10个脉冲呢?我这边看有门控或者单脉冲加重复计数,黄老师平时用的什么方法?
我的回答:
使用两个TIM定时器:一个输出可调频率、占空比的PWM,一个对输出PWM脉冲计数(计时)。
1.门控方式能实现,但需要复杂的配置和计算,不推荐。
2.脉冲计数是比较实际,也是比较简单的方式;
对输出PWM脉冲计数(计时)方法有多种:
1.IO中断计数,或同步定时中断计数:用另外一个定时器,按照相同频率中断计数(类似IO中断);
2.由PWM频率和脉冲个数,计算输出全部所需的时间,使用定时中断,关闭输出PWM;
3.利用定时器外部脉冲触发(外部时钟模式2功能),计数个数为所需脉冲个数(10个脉冲),则关闭输出PWM;
·
·
·
STM32的TIM定时器少则五六个,多则二十个。 可能许多初学者觉得:那么多定时器用的完吗? 那么多不是浪费吗?
这么说吧,STM32的定时器功能非常强大,之所以有那么多定时器,原因在于使用定时器的地方有许多,本文要讲的这个例子只是很基础的一个例子。
当然,可能很多人想问:利用阻塞延时,控制IO高低变化输出PWM这种方式就行啦,也很简单。其实,这种方法的弊端很大。
1.输出的PWM可能存在误差;
2.对整个系统的实时性可能有影响;
·
·
·
所以不建议使用该方法。
使用两个定时器配合输出可调频率、占空比的PWM波形,且可指定输出脉冲个数的方法和原理其实不难。
输出PWM的方法就是使用TIM定时器自带有的PWM模式即可完成。主要难点在于还要控制指定输出脉冲的个数。
对于如何控制输出指定脉冲个数,下面大概说下三种方法:
1.脉冲中断计数法
IO中断,或者定时器同步(脉冲)中断。
定时器同步(脉冲)中断简单的说,就是利用定时器同时产生一个相同频率(或者说波形)的中断信号,在中断里面对其累计,累加个数为指定输出波形个数则关闭PWM波形的输出,同时关闭中断计数。
比如:我输出10个波形,10次中断(每次+1)之后,关闭输出。
它的原理,大致如下图:
此方法建议在输出高频PWM时不要使用,频繁中断对系统实时性也是有一定影响。 建议低于1KHz的PWM才使用此方法。
2.定时中断法
基于上面第一种,不适合高频PWM脉冲中断。经过思考,我们是否可以将多次中断的时间累加,只响应一次中断。
原理就是把定时的时间设定为单个脉冲的n倍(n个脉冲),只使用一次中断。
它的原理,大致如下图:
看图片中的提示,建议这个地方使用一个32位的定时器,这个值可能很大。
3.脉冲触发法
此方法可以避免上面两种方法中不足的地方, 相对上面两对实用性更强。电路上面,需要将PWM输出的波形,连接到另一个定时器的ETR引脚。
它的原理没什么特殊的,就是和我们常用的定时更新中断类似,只是输入信号改成PWM脉冲波形(默认为内部时钟CK_INT 如:36M)。
下面章节我就以该方法(第3种方法),PWM波形作为定时器的输入时钟的方式,用代码给大家讲述一下。
上面说过,使用PWM作为另一个定时器的输入时钟,即可达到对PWM计数的功能。
请参看手册中TIM定时器时钟选择章节。
1.输出PWM配置
/************************************************
函数名称 : PWM_TIM_Configuration
功 能 : PWM输出定时器配置
参 数 : 无
返 回 值 : 无
作 者 : strongerHuang
*************************************************/
void PWM_TIM_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
/* 时钟配置 */
RCC_APB1PeriphClockCmd(PWM_TIM_CLK, ENABLE);
RCC_AHB1PeriphClockCmd(PWM_TIM_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = PWM_TIM_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(PWM_TIM_GPIO_PORT, &GPIO_InitStructure);
/* 映射配置 */
GPIO_PinAFConfig(PWM_TIM_GPIO_PORT, PWM_TIM_SOURCE, PWM_TIM_AF);
/* 时基配置 */
TIM_TimeBaseStructure.TIM_Prescaler = PWM_PRESCALER; //预分频值
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
TIM_TimeBaseStructure.TIM_Period = 0xFFFF; //定时周期
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //分频因子
TIM_TimeBaseInit(PWM_TIMx, &TIM_TimeBaseStructure);
/* PWM模式配置 */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //PWM1模式
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //使能输出
TIM_OCInitStructure.TIM_Pulse = 0xFFFF; //脉宽值
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //输出极性
PWM_TIM_OCxInit(PWM_TIMx, &TIM_OCInitStructure);
TIM_Cmd(PWM_TIMx, DISABLE);
}
初始化频率和占空比填充的值是最大值,即TIM_Period = 0xFFFF;TIM_Pulse = 0xFFFF; 实际没有使能定时器(输出的配置见下面函数接口)
2.选择外部时钟,定时中断配置
/************************************************
函数名称 : CNT_TIM_Configuration
功 能 : 计时定时器配置
参 数 : 无
返 回 值 : 无
作 者 : strongerHuang
*************************************************/
void CNT_TIM_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
/* 时钟配置 */
RCC_APB1PeriphClockCmd(CNT_TIM_CLK, ENABLE);
RCC_AHB1PeriphClockCmd(CNT_TIM_GPIO_CLK, ENABLE);
GPIO_InitStructure.GPIO_Pin = CNT_TIM_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(CNT_TIM_GPIO_PORT, &GPIO_InitStructure);
/* 映射配置 */
GPIO_PinAFConfig(CNT_TIM_GPIO_PORT, CNT_TIM_SOURCE, CNT_TIM_AF);
/* NVIC配置 */
NVIC_InitStructure.NVIC_IRQChannel = CNT_TIM_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = CNT_TIM_Priority;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
/* 使用外部时钟源 */
TIM_ETRClockMode2Config(CNT_TIMx, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_Inverted, 0);
/* 时基配置 */
TIM_TimeBaseStructure.TIM_Prescaler = 0; //预分频值
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
TIM_TimeBaseStructure.TIM_Period = 0xFFFF; //定时周期
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //分频因子
TIM_TimeBaseInit(CNT_TIMx, &TIM_TimeBaseStructure);
TIM_ClearFlag(CNT_TIMx, TIM_FLAG_Update);
TIM_ITConfig(CNT_TIMx, TIM_IT_Update, ENABLE); //使能"更新"中断
TIM_Cmd(CNT_TIMx, DISABLE);
}
和常规的不同点在于: 使用外部时钟源
TIM_ETRClockMode2Config(CNT_TIMx, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_Inverted, 0);
注意检测(捕获)极性TIM_ExtTRGPolarity_Inverted,一般PWM都是高电平为脉冲波形,下降沿才算一个波形的计数。
3.输出PWM函数接口
/************************************************
函数名称 : PWM_Output
功 能 : 输出PWM
参 数 : Frequency --- 频率
Dutycycle --- 占空比(12代表占空比为12%)
NumPulse --- 脉冲个数
返 回 值 : 无
作 者 : strongerHuang
*************************************************/
void PWM_Output(uint32_t Frequency, uint32_t Dutycycle, uint32_t NumPulse)
{
uint32_t pwm_period;
uint32_t pwm_pulse;
/* 输出PWM */
pwm_period = PWM_CK_CNT/Frequency - 1; //计算出计数周期(决定输出的频率)
pwm_pulse = (pwm_period + 1)*Dutycycle / 100; //计算出脉宽值(决定PWM占空比)
TIM_Cmd(PWM_TIMx, DISABLE); //失能TIM
TIM_SetCounter(PWM_TIMx, 0); //计数清零
TIM_SetAutoreload(PWM_TIMx, pwm_period); //更改频率
PWM_TIM_SetComparex(PWM_TIMx, pwm_pulse); //更改占空比
TIM_Cmd(PWM_TIMx, ENABLE); //使能TIM
/* 脉冲个数计时 */
TIM_Cmd(CNT_TIMx, DISABLE);
TIM_SetCounter(CNT_TIMx, 0);
TIM_SetAutoreload(CNT_TIMx, NumPulse-1); //设置中断更新数
TIM_ClearFlag(CNT_TIMx, TIM_FLAG_Update);
TIM_Cmd(CNT_TIMx, ENABLE);
}
void PWM_Output(uint32_t Frequency, uint32_t Dutycycle, uint32_t NumPulse);
我们只需要调用该函数接口就可以实现指定个数PWM输出了。中途不用软件参数,输出结束时自动响应定时中断,关闭定时器。
中断接口函数
/************************************************
函数名称 : CNT_TIM_IRQHandler
功 能 : 计时中断
参 数 : 无
返 回 值 : 无
作 者 : strongerHuang
*************************************************/
void CNT_TIM_IRQHandler(void)
{
if(TIM_GetITStatus(CNT_TIMx, TIM_IT_Update) != RESET)
{
TIM_ClearITPendingBit(CNT_TIMx, TIM_IT_Update);
TIM_Cmd(PWM_TIMx, DISABLE); //关闭PWM输出
TIM_Cmd(CNT_TIMx, DISABLE); //关闭计数
}
}
为方便大家,提供了一个简单裸机程序:
int main(void)
{
System_Initializes(); while(1)
{
LED_TOGGLE(); //LED变化
Delay(5); //延时(约240ms)
PWM_Output(1000, 20, 10); //1KHz, 20%占空比, 10个脉冲
}
}
main函数中实现效果:间隔240ms(软件延时不精确)输出10个PWM波形
波形具体情况:输出1KHz, 20%占空比, 10个脉冲精确的PWM波形
下载地址(STM32F401为例工程,STM32其他芯片类似):
链接:https://pan.baidu.com/s/10GPPxCky8SZmU9S9pleqJg
密码:4jf3
推荐阅读:
2.STM32F4_TIM输出PWM波形(可调频率、占空比)
置顶公众号,不怕找不到我;
记得给我点赞哦!
微信搜索“strongerHuang” 或者扫描下面二维码、关注,在我的底部菜单查看更多精彩内容!
长按识别二维码 关注