最近有需求捕获天地飞ET07遥控器接收端的PWM波并测量占空比以获取当前摇杆位置(PS:本版本天地飞遥控器有7路PWM波2路PPM/W.BUS协议,本文只捕获其中4路PWM,后续更新W.BUS协议版本更加方便实用)
程序思路为在直连模式下先将通道设置为上升沿捕获,在进入中断后将其改为下降沿捕获,在上升沿和下降沿中断时分别获取当前定时器CNT计数器值据此统计高电平的持续时间并计算一个周期的时间长度(PS:还要统计定时器更新中断次数,并在中断内分别保存当前更新中断次数才可以计算出以上数据)
程序现象如下:(PS:遥控器摇杆占空比为5.5-7.6-9.7,代表摇杆在最下方在最中间和最上方)
天地飞遥控器PWM捕获
1、首先将接收器信号输出端与开发板选好的定时器引脚连接
本例选择的是定时器3,F407引脚复用图如下:
2、配置GPIO及TIM定时器
PS:若不在程序最上方定义结构体时要注意是否开启了C99代码支持,否则会编译报错
//配置相应的GPIO并复用引脚
void GPIO_Config(void)
{
RCC_AHB1PeriphClockCmd (RCC_AHB1Periph_GPIOB, ENABLE); //GPIOB使能
//设置引脚复用,PS:引脚复用方法一次只能复用一个引脚
GPIO_PinAFConfig(GPIOB,GPIO_PinSource6,GPIO_AF_TIM4);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource7,GPIO_AF_TIM4);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource8,GPIO_AF_TIM4);
GPIO_PinAFConfig(GPIOB,GPIO_PinSource9,GPIO_AF_TIM4);
GPIO_InitTypeDef GPIO_InitStructure;
//开启引脚复用模式
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
//设置为推挽输出
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
//选择定时器4引脚
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9;
//将引脚下拉,引脚尽量不设置浮空,否则会出现不稳定电平
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_DOWN;
//设置GPIO引脚速度
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//手动拉低所有引脚电平,可不写
GPIO_ResetBits(GPIOB,GPIO_Pin_6 | GPIO_Pin_7 | GPIO_Pin_8 | GPIO_Pin_9);
}
3、配置定时器参数
PS:配置时要注意自己开发板定时器所在的总线及时钟频率,定时器参数根据要捕获的PWM波频率调整,本文捕获的波形为50Hz
//配置对应定时器
void TIM_Config(void)
{
//开启对应定时器的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);
TIM_TimeBaseInitTypeDef TIM_InitStructure;
//设置时钟分割
TIM_InitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//设置定时器为向上计数模式
TIM_InitStructure.TIM_CounterMode = TIM_CounterMode_Up;
//设置自动重装载值为65536
TIM_InitStructure.TIM_Period = 65536-1;
//设置定时器分频值为84,f407定时器默认频率为168MHZ设置为84方便计算
TIM_InitStructure.TIM_Prescaler = 84-1;
//设置为每次重装载数据时都产生中断 PS:设置为N时N+1次重装载产生一次中断
TIM_InitStructure.TIM_RepetitionCounter = 0;
//初始化定时器
TIM_TimeBaseInit(TIM4,&TIM_InitStructure);
TIM_ICInitTypeDef TIM_ICInitStructure;
//选择定时器通道1
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
//设置滤波大小
TIM_ICInitStructure.TIM_ICFilter = 0xFF;
//设置触发模式为上升沿触发
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
//设置时钟分割
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
//选择连接模式为直连
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInit(TIM4,&TIM_ICInitStructure);
//同理设置通道2-4
//选择定时器通道2
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;
TIM_ICInit(TIM4,&TIM_ICInitStructure);
//选择定时器通道3
TIM_ICInitStructure.TIM_Channel = TIM_Channel_3;
TIM_ICInit(TIM4,&TIM_ICInitStructure);
//选择定时器通道4
TIM_ICInitStructure.TIM_Channel = TIM_Channel_4;
TIM_ICInit(TIM4,&TIM_ICInitStructure);
//设置定时器中断触发使能
TIM_ITConfig(TIM4,TIM_IT_Update | TIM_IT_CC1 | TIM_IT_CC2 | TIM_IT_CC3 | TIM_IT_CC4,ENABLE);
//清除因初始化产生的中断
TIM_ClearITPendingBit(TIM4,TIM_IT_Update | TIM_IT_CC1 | TIM_IT_CC2 | TIM_IT_CC3 | TIM_IT_CC4);
//使能定时器4
TIM_Cmd(TIM4,ENABLE);
}
4、配置NVIC中断控制器
//配置NVIC中断使能
void TIM_NVIC_Config(void)
{
//设置NVIC中断分组,PS:一个程序中只能设置一次分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitTypeDef NVIC_InitStructure;
//配置需要中断响应的定时器
NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
//设置中断抢占优先级分组
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
//设置中断响应优先级分组
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
}
5、封装代码调用方法
//开始定时器捕获方法
void StartTIM_Capture(void){
//开启NVIC
TIM_NVIC_Config();
//配置定时器
TIM_Config();
//配置GPIO
GPIO_Config();
}
6、配置数据保存数组及占空比保存结构体
//标志位数组 ,数据为0表示捕获上升沿,数据为1表示捕获下降沿
uint8_t TIMCH_FLAG[4] = {0};
//统计不同通道上升沿时计时器值
float TIMCH_UP[4] = {0};
//统计不同通道下降沿时计时器值
float TIMCH_DOWN[4] = {0};
//统计计数器溢出次数
uint32_t TIMCH_IT_UPDATE_COUNT[1] = {0};
//统计不同通道上升沿CNT计数器溢出次数
uint32_t TIMCH_IT_UP_COUNT[4] = {0};
//统计不同通道下降沿CNT计数器溢出次数
uint32_t TIMCH_IT_DOWN_COUNT[4] = {0};
//保存不同通道一个周期的计数值大小
float TIMCH_IT_NUM[4] = {0};
//保存不同通道高电平持续时间
float TIMCH_HIGH_TIME[4]={0};
//存放捕获到的PWM占空比,尽量放在h文件中,此处为方便讲解
typedef struct PWM_CH {
float PWM_0;
float PWM_1;
float PWM_2;
float PWM_3;
} P;
//用于保存不同通道的占空比
P test ={0,0,0,0};
7、在中断方法中捕获当前数据并保存到上列数组中计算当前占空比
//处理中断信息
void TIM4_IRQHandler(void)
{
//处理定时器产生的更新中断
if(TIM_GetITStatus(TIM4,TIM_IT_Update)==SET){
TIM_ClearITPendingBit(TIM4,TIM_IT_Update);
//C定时器4NT计数累加
TIMCH_IT_UPDATE_COUNT[0]+=1;
}
//处理不同通道产生的上升沿或下降沿中断
if(TIM_GetITStatus(TIM4,TIM_IT_CC1)==SET){
TIM_ClearITPendingBit(TIM4,TIM_IT_CC1);
if(TIMCH_FLAG[0] == 0){
//调用封装方法获取当前PWM占空比
(&test)->PWM_0 = rtPwmValue(0,TIM_GetCapture1(TIM4));
//将定时器设置为下降沿捕获
TIM_OC1PolarityConfig(TIM4,TIM_ICPolarity_Falling);
TIMCH_FLAG[0] = 1;
}
else
{
TIMCH_DOWN[0] = TIM_GetCapture1(TIM4);
//计算下降沿时的高电平时间并更新此时的更新次数
setDownValue(0,TIMCH_IT_UPDATE_COUNT[0]);
//将定时器设置为上升沿捕获
TIM_OC1PolarityConfig(TIM4,TIM_ICPolarity_Rising);
TIMCH_FLAG[0] = 0;
}
}
//处理不同通道产生的上升沿或下降沿中断
if(TIM_GetITStatus(TIM4,TIM_IT_CC2)==SET){
TIM_ClearITPendingBit(TIM4,TIM_IT_CC2);
if(TIMCH_FLAG[1] == 0){
//调用封装方法获取当前PWM占空比
(&test)->PWM_1 = rtPwmValue(1,TIM_GetCapture2(TIM4));
//将定时器设置为下降沿捕获
TIM_OC2PolarityConfig(TIM4,TIM_ICPolarity_Falling);
TIMCH_FLAG[1] = 1;
}
else
{
TIMCH_DOWN[1] = TIM_GetCapture2(TIM4);
//计算下降沿时的高电平时间并更新此时的更新次数
setDownValue(1,TIMCH_IT_UPDATE_COUNT[0]);
//将定时器设置为上升沿捕获
TIM_OC2PolarityConfig(TIM4,TIM_ICPolarity_Rising);
TIMCH_FLAG[1] = 0;
}
}
//处理不同通道产生的上升沿或下降沿中断
if(TIM_GetITStatus(TIM4,TIM_IT_CC3)==SET){
TIM_ClearITPendingBit(TIM4,TIM_IT_CC3);
if(TIMCH_FLAG[2] == 0){
//调用封装方法获取当前PWM占空比
(&test)->PWM_2 = rtPwmValue(2,TIM_GetCapture3(TIM4));
//将定时器设置为下降沿捕获
TIM_OC3PolarityConfig(TIM4,TIM_ICPolarity_Falling);
TIMCH_FLAG[2] = 1;
}
else
{
TIMCH_DOWN[2] = TIM_GetCapture3(TIM4);
//计算下降沿时的高电平时间并更新此时的更新次数
setDownValue(2,TIMCH_IT_UPDATE_COUNT[0]);
//将定时器设置为上升沿捕获
TIM_OC3PolarityConfig(TIM4,TIM_ICPolarity_Rising);
TIMCH_FLAG[2] = 0;
}
}
//处理不同通道产生的上升沿或下降沿中断
if(TIM_GetITStatus(TIM4,TIM_IT_CC4)==SET){
TIM_ClearITPendingBit(TIM4,TIM_IT_CC4);
if(TIMCH_FLAG[3] == 0){
//调用封装方法获取当前PWM占空比
(&test)->PWM_3 = rtPwmValue(3,TIM_GetCapture4(TIM4));
//将定时器设置为下降沿捕获
TIM_OC4PolarityConfig(TIM4,TIM_ICPolarity_Falling);
TIMCH_FLAG[3] = 1;
}
else
{
TIMCH_DOWN[3] = TIM_GetCapture4(TIM4);
//计算下降沿时的高电平时间并更新此时的更新次数
setDownValue(3,TIMCH_IT_UPDATE_COUNT[0]);
//将定时器设置为上升沿捕获
TIM_OC4PolarityConfig(TIM4,TIM_ICPolarity_Rising);
TIMCH_FLAG[3] = 0;
}
}
}
封装上升沿计算函数
/*
* 函数名:rtPwmValue
* 描述 :根据传入参数计算不同通道的占空比
* 输入 :ch:通道号,cnt:当前计数器值
* 输出 : PWM占空比
* 调用 :外部调用
*/
float rtPwmValue(uint8_t ch, uint16_t Cnt)
{
float PWM = 0;
//计算一个周期的计数值
TIMCH_IT_NUM[ch]=(float)Cnt-TIMCH_UP[ch]+(TIMCH_IT_UPDATE_COUNT[0]-TIMCH_IT_UP_COUNT[ch])*65536;
//当产生上升沿时更新上升沿计数器溢出次数
TIMCH_IT_UP_COUNT[ch] = TIMCH_IT_UPDATE_COUNT[0];
TIMCH_UP[ch]=Cnt;
if(TIMCH_IT_UP_COUNT[ch]>=TIMCH_IT_DOWN_COUNT[ch]){
//将当前计数值相减并计算占空比
PWM = (float)TIMCH_HIGH_TIME[ch]/TIMCH_IT_NUM[ch];
}
return PWM;
}
封装下降沿计算函数
/*
* 函数名:setDownValue
* 描述 :根据传入通道信息计算下降沿时高电平时间
* 输入 :ch:通道号,updateCount:当前更新中断次数
* 输出 : 无
* 调用 :外部调用
*/
void setDownValue(uint8_t ch, uint32_t updateCount)
{
if(TIMCH_DOWN[0]-TIMCH_UP[0]>0){
//计算高电平持续时间并乘以100方便计算百分比
TIMCH_HIGH_TIME[ch]=(float)(TIMCH_DOWN[ch]-TIMCH_UP[ch])*100;
}else{
//计算高电平持续时间
TIMCH_HIGH_TIME[ch]=(float)(TIMCH_DOWN[ch]-TIMCH_UP[ch]+(updateCount-TIMCH_IT_DOWN_COUNT[ch])*65536)*100;
}
//当产生下降沿时更新下降沿计数器溢出次数
TIMCH_IT_DOWN_COUNT[ch] = updateCount;
}
8、在主函数中调用捕获方法
int main(void)
{
StartTIM_Capture();
while(1)
{
}
}
9、烧录验证
9、小结
定时器捕获四路PWM波并计算占空比程序到此就结束了,有问题可以在评论区反馈或者私信也可