最近几天的学习了STM32输入捕获输入捕获的相关知识,为了巩固自己学习的知识特意制作一辆有三个超声波组成的4轮避障小车来加深对输入捕获的理解。
输入捕获模式可以用来测量脉冲宽度或者测量频率。 我们以测量脉宽为例,用一个简图来说明输入捕获的原理,如图 15.1.1 所示:
如图 15.1.1 所示,就是输入捕获测量高电平脉宽的原理,假定定时器工作在向上计数模式,图中 t1~t2 时间,就是我们需要测量的高电平时间。测量方法如下:首先设置定时器通道 x 为上升沿捕获,这样, t1 时刻,就会捕获到当前的 CNT 值,然后立即清零 CNT,并设置通道 x为下降沿捕获,这样到 t2 时刻,又会发生捕获事件,得到此时的 CNT 值,记为 CCRx2。 这样,根据定时器的计数频率,我们就可以算出 t1~t2 的时间,从而得到高电平脉宽。
在 t1~t2 之间,可能产生 N 次定时器溢出,这就要求我们对定时器溢出,做处理,防止高电平太长,导致数据不准确。如图 15.1.1所示,t1~t2之间,CNT计数的次数等于:N*ARR+CCRx2,有了这个计数次数,再乘以 CNT 的计数周期,即可得到 t2-t1 的时间长度,即高电平持续时间。输入捕获的原理。
STM32F1 的定时器,除了 TIM6 和 TIM7,其他定时器都有输入捕获功能。 STM32F1 的输入捕获,简单的说就是通过检测 TIMx_CHx 上的边沿信号,在边沿信号发生跳变(比如上升沿/下降沿)的时候,将当前定时器的值(TIMx_CNT)存放到对应的通道的捕获/比较寄存器(TIMx_CCRx)里面,完成一次捕获。同时还可以配置捕获时是否触发中断/DMA 等。
接下来,我们介绍我们需要用到的一些寄存器配置,需要用到的寄存器有: TIMx_ARR、TIMx_PSC、 TIMx_CCMR1、 TIMx_CCER、 TIMx_DIER、 TIMx_CR1、 TIMx_CCR1 ,我们这里针对性的介绍这几个寄存器的配置。
捕获/比较模式寄存器 1: TIMx_CCMR1
该寄存器的各位描述如图 15.1.2 所示:
当在输入捕获模式下使用的时候,对应图 15.1.2 的第二行描述,从图中可以看出,TIMx_CCMR1 明显是针对 2 个通道的配置,低八位[7: 0]用于捕获/比较通道 1 的控制,而高八位[15: 8]则用于捕获/比较通道 2 的控制,因为 TIMx 还有 CCMR2 这个寄存器,所以可以知道CCMR2 是用来控制通道 3 和通道 4。
捕获/比较使能寄存器: TIMx_CCER
这里我们以通道1为例,具体的参考中文手册。我们要用到这个寄存器的最低 2 位, CC1E 和 CC1P 位。这两个位的描述如图 15.1.4所示:
所以,要使能输入捕获,必须设置 CC1E=1,而 CC1P 则根据自己的需要来配置。
DMA/中断使能寄存器: TIMx_DIER
我们需要用到中断来处理捕获数据,所以必须开启通道 1 的捕获比较中
断,即 CC1IE 设置为 1。
控制寄存器: TIMx_CR1
我们只用到了它的最低位,也就是用来使能定时器的。
捕获/比较寄存器 1: TIMx_CCR1
该寄存器用来存储捕获发生时, TIMx_CNT的值,我们从 TIMx_CCR1 就可以读出通道 1 捕获发生时刻的 TIMx_CNT 值,通过两次捕获(一次上升沿捕获,一次下降沿捕获)的差值,就可以计算出高电平脉冲的宽度(注意,对于脉宽太长的情况,还要计算定时器溢出的次数)
注意以上寄存器的配置均以通道1为例,具体的配置看中文参考书册。
这里我们用的是HC-SR04模块,此模块性能稳定,测度距离精确,模块高精度,盲区小。采用IO口TRIG触发测距,给至少10us的高电平信号;模块自动发送8个40khz的方波,自动检测是否有信号返回;有信号返回,通过IO口ECHO输出一个高电平,高电平持续的时间就是超声波从发射到返回的时间。测试距离=(高电平时间*声速(340M/S))/2;本模块使用方法简单,一个控制口发一个10US以上的高电平,就可以在接收口等待高电平输出。一有输出就可以开定时器计时,当此口变为低电平时就可以读定时器的值,此时就为此次测距的时间,方可算出距离。如此不断的周期测,即可以达到你移动测量的值。
3个超声波分别安装在正前方左边和右边各一个,具体情况见下图:
在主函数里调用测距函数,分别计算出障碍物离小车前方、左边、右边的距离lenth1、lenth2、lenth3,如果前方距离大于20cm并且左边和右边的距离大于10厘米则前进(GO)负责判断左边和右边的距离。如果左边距离小于10cm(lenth2<10),则右转(YOU)。如果右边的距离小于10cm(lenth3<10),则左转(ZUO)。其它情况下则是:后退——>延时500ms——>左拐——>延时200ms。值得注意的是:小车在超声波测距时要保证小车处于静止状态为了避免小车在测距时小车依旧在运动从而增加误差。同时利用PWM调速尽量让小车速度慢下来,来减小误差。
1、超声波测距初识化
这里我们用的是定时2(TIM2)的通道2、3、4,在这段程序是对定时器2的4个通道进行初始化。在工程文件下新建一个HC6的文件,新建一个hc6.c和hc6.h文件。期中hc6.c代码如下:
#include "hc6.h"
#include "sys.h"
void HCSR04_Init(u16 arr,u16 psc)
{
RCC->APB1ENR|=1<<0; //TIM2时钟使能
RCC->APB2ENR|=1<<2; //使能PORTA时钟
RCC->APB2ENR|=1<<3; //使能PORTB时钟
RCC->APB2ENR|=1<<4; //使能PORTC时钟
GPIOA->CRL&=0XFFFF0000;//PA0~3清除之前设置
GPIOA->CRL|=0X00008888;//PA0~3浮空输入
GPIOA->ODR|=0<<0;
GPIOA->ODR|=0<<1; //PA1下拉
GPIOA->ODR|=0<<2;
GPIOA->ODR|=0<<3;
GPIOB->CRL&=0X000FFFFF;
GPIOB->CRL|=0X33300000;
GPIOB->ODR|=1<<7; //PB7 输出高
GPIOB->ODR|=1<<6;
GPIOB->ODR|=1<<5;
TIM2->ARR=arr; //设定计数器自动重装值
TIM2->PSC=psc; //预分频器
TIM2->CCMR1|=1<<0; //CC1S=01 选择输入端IC1映射到TI1
TIM2->CCMR1|=1<<4; //IC1F=0001 配置滤波器 以Fck_int采样,两个事件后有效
TIM2->CCMR1|=0<<2; //IC1PS=00 配置输入分频,不分频
TIM2->CCER|=0<<1; //CC1P=0 上升沿捕获
TIM2->CCER|=1<<0; //CC1E=1 允许捕获计数器的值到捕获寄存器中
TIM2->CCMR1|=1<<8; //CC2S=01 选择输入端IC1映射到TI1
TIM2->CCMR1|=1<<12; //IC2F=0001 配置滤波器 以Fck_int采样,两个事件后有效
TIM2->CCMR1|=0<<10; //IC2PS=00 配置输入分频,不分频
TIM2->CCER|=0<<5; //CC2P=0 上升沿捕获
TIM2->CCER|=1<<4; //CC2E=1 允许捕获计数器的值到捕获寄存器中
TIM2->CCMR2|=1<<0; //CC3S=01 选择输入端IC1映射到TI1
TIM2->CCMR2|=1<<4; //IC3F=0001 配置滤波器 以Fck_int采样,两个事件后有效
TIM2->CCMR2|=0<<2; //IC3PS=00 配置输入分频,不分频
TIM2->CCER|=0<<9; //CC3P=0 上升沿捕获
TIM2->CCER|=1<<8; //CC3E=1 允许捕获计数器的值到捕获寄存器中
TIM2->CCMR2|=1<<8; //CC4S=01 选择输入端IC1映射到TI1
TIM2->CCMR2|=1<<12; //IC4F=0001 配置滤波器 以Fck_int采样,两个事件后有效
TIM2->CCMR2|=0<<10; //IC4PS=00 配置输入分频,不分频
TIM2->CCER|=0<<13; //CC4P=0 上升沿捕获
TIM2->CCER|=1<<12; //CC4E=1 允许捕获计数器的值到捕获寄存器中
TIM2->DIER|=1<<1;
TIM2->DIER|=1<<2; //允许捕获中断
TIM2->DIER|=1<<3; //允许捕获中断
TIM2->DIER|=1<<4; //允许捕获中断
TIM2->DIER|=1<<0; //允许更新中断
TIM2->CR1|=0X01; //使能定时器2
MY_NVIC_Init(2,0,TIM2_IRQn,2);//抢占2,子优先级0,组2
}
u8 TIM2CH1_CAPTURE_STA=0; //输入捕获状态
u16 TIM2CH1_CAPTURE_VAL; //输入捕获值
u8 TIM2CH2_CAPTURE_STA=0; //输入捕获状态
u16 TIM2CH2_CAPTURE_VAL; //输入捕获值
u8 TIM2CH3_CAPTURE_STA=0; //输入捕获状态
u16 TIM2CH3_CAPTURE_VAL; //输入捕获值
u8 TIM2CH4_CAPTURE_STA=0; //输入捕获状态
u16 TIM2CH4_CAPTURE_VAL; //输入捕获值
//定时器2中断服务程序
void TIM2_IRQHandler(void)
{
u16 tsr;
tsr=TIM2->SR;
if((TIM2CH1_CAPTURE_STA&0X80)==0)//还未成功捕获
{
if(tsr&0X01)//溢出
{
if(TIM2CH1_CAPTURE_STA&0X40)//已经捕获到高电平了
{
if((TIM2CH1_CAPTURE_STA&0X3F)==0X3F)//高电平太长了
{
TIM2CH1_CAPTURE_STA|=0X80;//标记成功捕获了一次
TIM2CH1_CAPTURE_VAL=0XFFFF;
}else TIM2CH1_CAPTURE_STA++;
}
}
if(tsr&0x02)//捕获1发生捕获事件
{
if(TIM2CH1_CAPTURE_STA&0X40) //捕获到一个下降沿
{
TIM2CH1_CAPTURE_STA|=0X80; //标记成功捕获到一次高电平脉宽
TIM2CH1_CAPTURE_VAL=TIM2->CCR1;//获取当前的捕获值
TIM2->CCER&=~(1<<1); //CC1P=0 设置为上升沿捕获
}else //还未开始,第一次捕获上升沿
{
TIM2CH1_CAPTURE_VAL=0;
TIM2CH1_CAPTURE_STA=0X40; //标记捕获到了上升沿
TIM2->CNT=0; //计数器清空
TIM2->CCER|=1<<1; //CC1P=1 设置为下降沿捕获
}
}
}
if((TIM2CH2_CAPTURE_STA&0X80)==0)//还未成功捕获
{
if(tsr&0X01)//溢出
{
if(TIM2CH2_CAPTURE_STA&0X40)//已经捕获到高电平了
{
if((TIM2CH2_CAPTURE_STA&0X3F)==0X3F)//高电平太长了
{
TIM2CH2_CAPTURE_STA|=0X80;//标记成功捕获了一次
TIM2CH2_CAPTURE_VAL=0XFFFF;
}else TIM2CH2_CAPTURE_STA++;
}
}
if(tsr&0x04)//捕获1发生捕获事件
{
if(TIM2CH2_CAPTURE_STA&0X40) //捕获到一个下降沿
{
TIM2CH2_CAPTURE_STA|=0X80; //标记成功捕获到一次高电平脉宽
TIM2CH2_CAPTURE_VAL=TIM2->CCR2;//获取当前的捕获值
TIM2->CCER&=~(1<<5); //CC1P=0 设置为上升沿捕获
}else //还未开始,第一次捕获上升沿
{
TIM2CH2_CAPTURE_VAL=0;
TIM2CH2_CAPTURE_STA=0X40; //标记捕获到了上升沿
TIM2->CNT=0; //计数器清空
TIM2->CCER|=1<<5; //CC1P=1 设置为下降沿捕获
TIM2->CR1|=0x01;
}
}
}
if((TIM2CH3_CAPTURE_STA&0X80)==0)//还未成功捕获
{
if(tsr&0X01)//溢出
{
if(TIM2CH3_CAPTURE_STA&0X40)//已经捕获到高电平了
{
if((TIM2CH3_CAPTURE_STA&0X3F)==0X3F)//高电平太长了
{
TIM2CH3_CAPTURE_STA|=0X80;//标记成功捕获了一次
TIM2CH3_CAPTURE_VAL=0XFFFF;
}else TIM2CH3_CAPTURE_STA++;
}
}
if(tsr&0x08)//捕获1发生捕获事件
{
if(TIM2CH3_CAPTURE_STA&0X40) //捕获到一个下降沿
{
TIM2CH3_CAPTURE_STA|=0X80; //标记成功捕获到一次高电平脉宽
TIM2CH3_CAPTURE_VAL=TIM2->CCR3;//获取当前的捕获值
TIM2->CCER&=~(1<<9); //CC1P=0 设置为上升沿捕获
}else //还未开始,第一次捕获上升沿
{
TIM2CH3_CAPTURE_VAL=0;
TIM2CH3_CAPTURE_STA=0X40; //标记捕获到了上升沿
TIM2->CNT=0;
TIM2CH3_CAPTURE_VAL=TIM2->CCR3; //计数器清空
TIM2->CCER|=1<<9; //CC1P=1 设置为下降沿捕获
TIM2->CR1|=0x01;
}
}
}
if((TIM2CH4_CAPTURE_STA&0X80)==0)//还未成功捕获
{
if(tsr&0X01)//溢出
{
if(TIM2CH4_CAPTURE_STA&0X40)//已经捕获到高电平了
{
if((TIM2CH4_CAPTURE_STA&0X3F)==0X3F)//高电平太长了
{
TIM2CH4_CAPTURE_STA|=0X80;//标记成功捕获了一次
TIM2CH4_CAPTURE_VAL=0XFFFF;
}else TIM2CH4_CAPTURE_STA++;
}
}
if(tsr&0x10)//捕获1发生捕获事件
{
if(TIM2CH4_CAPTURE_STA&0X40) //捕获到一个下降沿
{
TIM2CH4_CAPTURE_STA|=0X80; //标记成功捕获到一次高电平脉宽
TIM2CH4_CAPTURE_VAL=TIM2->CCR4;//获取当前的捕获值
TIM2->CCER&=~(1<<13); //CC1P=0 设置为上升沿捕获
}else //还未开始,第一次捕获上升沿
{
TIM2CH4_CAPTURE_VAL=0;
TIM2CH4_CAPTURE_STA=0X40; //标记捕获到了上升沿
TIM2->CNT=0; //计数器清空
TIM2->CCER|=1<<13; //CC1P=1 设置为下降沿捕获
TIM2->CR1|=0x01;
}
}
}
TIM2->SR=0;//清除中断标志位
}
hc6.h代码如下:
#ifndef __HC6_H
#define __HC6_H
#include "sys.h"
void HCSR04_Init(u16 arr,u16 psc);
#endif
2、电机模块初始化
这里我们用的是定时3和定时器1,在工程文件下新建一个MOTER的文件夹,新建一个moter.c和moter.h文件。期中moter.c代码如下:
#include "moter.h"
#include "sys.h"
#include "delay.h"
void TIM_PWM1_Init(u16 arr,u16 psc)
{
//此部分需手动修改IO口设置
RCC->APB1ENR|=1<<1; //TIM3时钟使能
RCC->APB2ENR|=1<<2; //GPIOA使能
RCC->APB2ENR|=1<<3; //GPIOB使能
RCC->APB2ENR|=1<<4;
GPIOA->CRL&=0X00FFFFFF;
GPIOA->CRL|=0XBB000000;
GPIOC->CRH&=0X000FFFFF;
GPIOC->CRH|=0X33300000;
GPIOB->CRL&=0XFFFFF000; //PB(0)是AIN1 PB(1)是AIN2
GPIOB->CRL|=0X00000333;
TIM3->ARR=arr; //设定计数器3自动重装值
TIM3->PSC=psc; //预分频器设置
TIM3->CCMR1|=6<<4; //CH1 PWM2模式
TIM3->CCMR1|=1<<3; //CH1预装载使能
TIM3->CCMR1|=6<<12; //CH2 PWM2模式
TIM3->CCMR1|=1<<11; //CH2预装载使能
TIM3->CCER|=1<<0; //OC1 输出使能
TIM3->CCER|=1<<4; //OC1 输出使能
TIM3->CR1=0x0080; //ARPE使能
TIM3->CR1|=0x01; //使能定时器3
RCC->APB2ENR|=1<<11; //TIM1定时器使能
GPIOA->CRH&=0XFFFF0FF0;
GPIOA->CRH|=0X0000B00B;
GPIOB->CRH&=0X0000FFFF;
GPIOB->CRH|=0X33330000;
TIM1->ARR=arr; //设定计数器自动重装值
TIM1->PSC=psc; //预分频器设置
TIM1->CCMR1|=6<<4; //CH1 PWM2模式
TIM1->CCMR1|=1<<3; //CH1预装载使能
TIM1->CCMR2|=6<<12; //CH4 PWM2模式
TIM1->CCMR2|=1<<11; //CH4预装载使能
TIM1->CCER|=1<<0; //OC1 输出使能
TIM1->CCER|=1<<12; //OC4 输出使能
TIM1->BDTR|=1<<15; //MOE 主输出使能
TIM1->CR1=0x0080; //ARPE使能
TIM1->CR1|=0x01; //使能定时器1
STBY=1;
STBY1=1;
}
void GO(u16 a,u16 b)
{
AIN1=0;
AIN2=1;
BIN1=0;
BIN2=1;
AIN3=1;
AIN4=0;
BIN3=1;
BIN4=0;
TIM3->CCR1=a;//右上
TIM3->CCR2=b;//左上
TIM1->CCR1=a;//右下
TIM1->CCR4=b;//左下
}
void STOP(void)
{
AIN1=0;
AIN2=0;
BIN1=0;
BIN2=0;
AIN3=0;
AIN4=0;
BIN3=0;
BIN4=0;
}
void HOU(u16 a,u16 b)
{
AIN1=1;
AIN2=0;
BIN1=1;
BIN2=0;
AIN3=0;
AIN4=1;
BIN3=0;
BIN4=1;
TIM3->CCR1=a;//右上
TIM3->CCR2=b;//左上
TIM1->CCR1=a;//右下
TIM1->CCR4=b;//左下
}
void YOU(u16 a,u16 b)
{
AIN1=1;
AIN2=0;
BIN1=0;
BIN2=1;
AIN3=0;
AIN4=1;
BIN3=1;
BIN4=0;
TIM3->CCR1=a;//右上
TIM3->CCR2=b;//左上
TIM1->CCR1=a;//右下
TIM1->CCR4=b;//左下
}
void ZUO(u16 a,u16 b)
{
AIN1=0;
AIN2=1;
BIN1=1;
BIN2=0;
AIN3=1;
AIN4=0;
BIN3=0;
BIN4=1;
TIM3->CCR1=a;//右上
TIM3->CCR2=b;//左上
TIM1->CCR1=a;//右下
TIM1->CCR4=b;//左下
}
上面这段代码主要包括定时器PWM的初始化,还有定义了5个函数。前后左右对应有两个参数a和b,通过改变这两个参数就可以控制电机的速度。同时也能保证左边和右边的两个轮子的速度相同。
moter.h电机如下:
#ifndef __MOTER_H
#define __MOTER_H
#include "sys.h"
#define AIN1 PBout(0)
#define AIN2 PBout(1)
#define BIN1 PCout(13)
#define BIN2 PCout(14)
#define STBY PCout(15)
#define AIN3 PBout(15)
#define AIN4 PBout(14)
#define BIN3 PBout(13)
#define BIN4 PBout(12)
#define STBY1 PAout(12)
void TIM_PWM1_Init(u16 arr,u16 psc);
void STOP(void);
void GO(u16 a,u16 b);
void HOU(u16 a,u16 b);
void YOU(u16 a,u16 b);
void ZUO(u16 a,u16 b);
#endif
3.主函数如下:
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "hc6.h"
#include "moter.h"
#define Trig1 PBout(7) // PB7
#define Trig2 PBout(6)
#define Trig3 PBout(5)
extern u8 TIM2CH2_CAPTURE_STA; //输入捕获状态
extern u16 TIM2CH2_CAPTURE_VAL; //输入捕获值
extern u8 TIM2CH3_CAPTURE_STA; //输入捕获状态
extern u16 TIM2CH3_CAPTURE_VAL; //输入捕获值
extern u8 TIM2CH4_CAPTURE_STA; //输入捕获状态
extern u16 TIM2CH4_CAPTURE_VAL; //输入捕获值
u32 DIS_Init(u8 *STA,u16 VAL)//定义计算距离函数
{
u32 temp;
u32 lenth;
if((*STA)&0X80)//成功捕获到了一次高电平
{
temp=(*STA)&0X3F;
temp*=65536; //溢出时间总和
temp+=VAL; //得到总的高电平时间
lenth=temp*0.017; //计算长度
*STA=0; //开启下一次捕获
}
return lenth;
}
int main(void)
{
u32 lenth1;
u32 lenth2;
u32 lenth3;
Stm32_Clock_Init(9); //系统时钟设置
delay_init(72); //延时初始化
uart_init(72,9600); //串口初始化
HCSR04_Init(0XFFFF,72-1);//以1Mhz的频率计数
TIM_PWM1_Init(899,0);
while(1)
{
Trig1=1;
delay_us(20); //输入一个20us的高电平
Trig1=0;
lenth1=DIS_Init(&TIM2CH2_CAPTURE_STA,TIM2CH2_CAPTURE_VAL);
delay_us(20);
Trig2=1;
delay_us(20); //输入一个20us的高电平
Trig2=0;
lenth2=DIS_Init(&TIM2CH3_CAPTURE_STA,TIM2CH3_CAPTURE_VAL);
delay_us(20);
Trig3=1;
delay_us(20);
Trig3=0;
lenth3=DIS_Init(&TIM2CH4_CAPTURE_STA,TIM2CH4_CAPTURE_VAL);
if((lenth1>30)&&(lenth3>10)&&(lenth2>10))
{
GO(400,400);
}
else if(lenth3<10)
{
ZUO(300,300);
delay_ms(20);
}
else if(lenth2<10)
{
YOU(300,300);
delay_ms(20);
}
else
{
HOU();
delay_ms(500);
ZUO(300,300);
delay_ms(300);
}
STOP();
delay_ms(20);
}
}
主函数主要调用相关的文件和函数,同时定义了一个测距函数**u32 DIS_Init(u8 *STA,u16 VAL)**返回值是所测得的距离。同时每次调用以后要使得TIM2CHx_CAPTURE_STA的值为0所以传递的是TIM2CHx_CAPTURE_STA的地址,同时定义函数的参数为指针变量来使得为0。在while(1)里实现了小车的测距,同时根据相关的距离来判断小车的运动状态。