STM32F1-输入捕获
以下部分内容来自正点原子的书籍
红外遥控是一种无线、非接触控制技术,具有抗干扰能力强,信息传输可靠,功耗低,成本低,易实现等显著优点,被诸多电子设备特别是家用电器广泛采用,并越来越多的应用到计算机系统中。由于红外线遥控不具有像无线电遥控那样穿过障碍物去控制被控对象的能力,所以,在设
计红外线遥控器时,不必要像无线电遥控器那样,每套(发射器和接收器)要有不同的遥控频率或编码(否则,就会隔墙控制或干扰邻居的家用电器),所以同类产品的红外线遥控器,可以有相同的遥控频率或编码,而不会出现遥控信号“串门”的情况。这对于大批量生产以及在家用
电器上普及红外线遥控提供了极大的方面。由于红外线为不可见光,因此对环境影响很小,再由红外光波动波长远小于无线电波的波长,所以红外线遥控不会影响其他家用电器,也不会影响临近的无线电设备。
红外遥控的编码目前广泛使用的是: NEC Protocol 的 PWM(脉冲宽度调制)和 PhilipsRC-5 Protocol 的 PPM(脉冲位置调制)。 ALIENTEK 战舰 STM32 开发板配套的遥控器使用
的是 NEC 协议,其特征如下:
1、 8 位地址和 8 位指令长度;
2、地址和命令 2 次传输(确保可靠性)
3、 PWM 脉冲位置调制,以发射红外载波的占空比代表“0”和“1”;
4、载波频率为 38Khz;
5、位时间为 1.125ms 或 2.25ms;
NEC 码的位定义:一个脉冲对应 560us 的连续载波,一个逻辑 1 传输需要 2.25ms(560us脉冲+1680us 低电平),一个逻辑 0 的传输需要 1.125ms(560us 脉冲+560us 低电平)。而遥控接收头在收到脉冲的时候为低电平,在没有脉冲的时候为高电平,这样,我们在接收头端收到的信号为:逻辑 1 应该是 560us 低+1680us 高,逻辑 0 应该是 560us 低+560us 高。NEC 遥控指令的数据格式为:同步码头、地址码、地址反码、控制码、控制反码。同步码由一个 9ms 的低电平和一个 4.5ms 的高电平组成,地址码、地址反码、控制码、控制反码均是8 位数据格式。按照低位在前,高位在后的顺序发送。采用反码是为了增加传输的可靠性(可用于校验)。
关于红外解码的规则,上面已经解释的很清楚了。
关于输入捕获的配置,在输入捕获的那篇总结里有说明。这里需要说明的一点是,输入捕获定时器的溢出时间最好在10ms以上,因为前导码有9ms,为了计算方便,在捕获数据的时候尽量不要溢出吧。
本实验用的是正点原子的精英板,输入捕获用的是TIM4的CH4,连接在PB9上。在TIM4中断函数里,其中有个数组是用来存放捕获到的时间的,项目中估计没有人会这么浪费内存去写程序,但是作为一篇总结这样做还是挺好的。数组的前面两个元素存放的是前导码的时间,第三个开始存放的是接收到的红外高电平时间,对于低电平这里没存储,存储也没用,在接收完控制码反码后就开始解码,然后把地址码、地址反码、命令码和命令反码发送到电脑,还有把前导码和后面数据部分的高电平捕获事件也发送到电脑。下面是电脑串口接收到的一组数据;
address_code=0
address_anti_code=255
Order_code=162
Order_anti_code=93
cc4_time[0]=9101
cc4_time[1]=4463
cc4_time[2]=509
cc4_time[3]=509
cc4_time[4]=508
cc4_time[5]=510
cc4_time[6]=508
cc4_time[7]=511
cc4_time[8]=510
cc4_time[9]=512
cc4_time[10]=1618
cc4_time[11]=1614
cc4_time[12]=1615
cc4_time[13]=1617
cc4_time[14]=1617
cc4_time[15]=1615
cc4_time[16]=1615
cc4_time[17]=1617
cc4_time[18]=1615
cc4_time[19]=509
cc4_time[20]=1613
cc4_time[21]=510
cc4_time[22]=511
cc4_time[23]=509
cc4_time[24]=1617
cc4_time[25]=509
cc4_time[26]=510
cc4_time[27]=1616
cc4_time[28]=509
cc4_time[29]=1616
cc4_time[30]=1615
cc4_time[31]=1616
cc4_time[32]=510
cc4_time[33]=1617
看这数据,输入捕获的时间还是挺准的,在本实验中有个小插曲,就是之前打算每捕获一个高/低电平就把捕获的时间通过串口发送到电脑(现在那两个printf还在,在捕获中断处理函数里,只是被注释掉了),结果却发现捕获的时间不对,后来查明原因才知道是因为串口发送数据的时间太长,影响到捕获时间计时,所以现在等所有数据都采集完后才调用串口发送数据,这样就可以了。关于这个程序还有另外一个问题,就是按太快外遥控的话单片机并不能及时接受数据,极限大概是一秒钟两次吧,再快的话就会有些数据没有接受到。算了,不纠结这个问题了。
代码如下;
#include "sys.h"
#include "delay.h"
#include "stdio.h"
/************************************************************
红外接收头接在PB9。TIM4,CH4
开发板用的是正点原子的精英板
****************************************************************/
void init__uart1()
{
GPIO_InitTypeDef GPIO_InitStruct;
USART_InitTypeDef USART_InitStruct;
// 串口IO配置,PA9,PA10
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//IO时钟打开
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;//IO方式具体看《中文手册》8.1.11章节
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN_FLOATING;//IO方式具体看《中文手册》8.1.11章节
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_10;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
//配置串口1
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//打开串口时钟
USART_InitStruct.USART_BaudRate=115200; //波特率115200
USART_InitStruct.USART_HardwareFlowControl=USART_HardwareFlowControl_None; //无硬件流
USART_InitStruct.USART_Mode=USART_Mode_Rx|USART_Mode_Tx; //接收和发送都使能
USART_InitStruct.USART_Parity=USART_Parity_No; //无奇偶校验
USART_InitStruct.USART_StopBits=USART_StopBits_1; //停止位1位
USART_InitStruct.USART_WordLength=USART_WordLength_8b; //数据长度8位
USART_Init(USART1,&USART_InitStruct);
USART_Cmd(USART1,ENABLE); //配置完成后一定要记得使能串口
}
void init_TIM4()
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_ICInitTypeDef TIM_ICInitStruct;
NVIC_InitTypeDef NVIC_InitStruct;
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);//IO时钟打开
GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPD;
GPIO_InitStruct.GPIO_Pin=GPIO_Pin_9;
GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;//APB1时钟1分频
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;//计数器向上计数
TIM_TimeBaseInitStruct.TIM_Period=10000;//1000;//10ms溢出一次
TIM_TimeBaseInitStruct.TIM_Prescaler=72-1;//计数器时钟为1mhz,1us
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;//意思是计数器溢出多少次才会产生一次中断
TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStruct );
TIM_ICInitStruct.TIM_Channel=TIM_Channel_4;//要捕获的是通道4,在PB9
TIM_ICInitStruct.TIM_ICFilter=0x03;//IC4F=0011 8个定时器时钟滤波
TIM_ICInitStruct.TIM_ICPolarity=TIM_ICPolarity_Falling;//因为没有码的时候是高电平,所以从下降沿开始捕获
TIM_ICInitStruct.TIM_ICPrescaler=TIM_ICPSC_DIV1;//对输入的频率进行分频
TIM_ICInitStruct.TIM_ICSelection=TIM_ICSelection_DirectTI;//把输入通道映射到TI1,也就是TIM4的通道4
TIM_ICInit(TIM4,&TIM_ICInitStruct);
NVIC_InitStruct.NVIC_IRQChannel=TIM4_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=2;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=0;
NVIC_Init(&NVIC_InitStruct);
TIM_ITConfig(TIM4,TIM_IT_Update|TIM_IT_CC4,ENABLE);//要打开两个中断,一个是捕获通道的使能,另一个是定时器的更新中断
TIM_Cmd(TIM4,ENABLE);
}
int main(void)
{
delay_init(); //延时函数初始化
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
init__uart1();//串口1初始化
init_TIM4();//这里是利用TIM4的通道4捕获的
while(1)
{ }//等待输入信号
}
//中断处理函数
void TIM4_IRQHandler()
{
static uint8_t cc4_status=0;//当前是否在接收数据,0=不接收,1=接收中
static uint8_t IC_statue=0;//捕获状态,当前是下降沿捕获还是上升沿捕获
static uint16_t cc4_time[34];//捕获到的时间,单位是us,数组的前两i=(0-1)是前导码,i=(2-33)为真正的编码
static uint8_t i=0;//循环变量
uint8_t address_code=0;//地址码
uint8_t address_anti_code=0;//地址反码
uint8_t Order_code=0;//命令码
uint8_t Order_anti_code=0;//命令反码
if(TIM_GetITStatus(TIM4,TIM_IT_Update)==1)//定时器计时1ms溢出
{
TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
}
if(TIM_GetITStatus(TIM4,TIM_IT_CC4)==1)//捕获通道中断
{
TIM_ClearITPendingBit(TIM4, TIM_IT_CC4); //清除中断标志位
if(IC_statue==1)//低电平捕获完成
{
IC_statue=0;
TIM_OC4PolarityConfig(TIM4,TIM_ICPolarity_Falling);//把下次触发捕获的条件改位上升沿触发
if(cc4_status==0)//只有在接收前导码的时候低电平才有用
cc4_time[0]=TIM_GetCapture4(TIM4);
TIM_SetCounter(TIM4,0);//重置计数器,从0开始计数器
//printf("LOW_time=%d us",cc4_time[i]);//把本次捕获到的时间通过串口打印到电脑上
}
else//高电平捕获完成
{
i++;//因为是先++,然后再接收数据的,所以解码的时候要从i=2开始
IC_statue=1;
TIM_OC4PolarityConfig(TIM4,TIM_ICPolarity_Rising);把下次触发捕获的条件改位下降沿触发
if(cc4_status==0)//只有在接收前导码的时候低电平才有用
cc4_time[1]=TIM_GetCapture4(TIM4);
else
cc4_time[i]=TIM_GetCapture4(TIM4);
TIM_SetCounter(TIM4,0);//重置计数器,从0开始计数器
//printf(" HIGH_time=%d us\r\n",cc4_time[i]);//把本次捕获到的时间通过串口打印到电脑上
}
//处理接收到的数据,解码
if(cc4_status==0)//等待接收数据
{
if((cc4_time[0]>8500)&&(cc4_time[0]<9500)&&(cc4_time[1]>4000)&&(cc4_time[1]<5000))//判断同步码头,9ms低电平+4.5ms高电平
cc4_status=1;//开始捕获
i=1;
}
else//数据接收中
{
if(i>33)//数据接收完成,这里对重复码不处理
{
for(i=2;i<10;i++)//地址码解码
{
address_code<<=1;//数据从低位开始接收
if(cc4_time[i]>1120)//高电平1680us为1
address_code|=1;
else//高电平560为0
address_code|=0;
}
for(i=10;i<18;i++)//地址反码解码
{
address_anti_code<<=1;//数据从低位开始接收
if(cc4_time[i]>1120)//高电平1680us为1
address_anti_code|=1;
else//高电平560为0
address_anti_code|=0;
}
for(i=18;i<26;i++)//控制码解码
{
Order_code<<=1;//数据从低位开始接收
if(cc4_time[i]>1120)//高电平1680us为1
Order_code|=1;
else//高电平560为0
Order_code|=0;
}
for(i=26;i<34;i++)//控制反码解码
{
Order_anti_code<<=1;//数据从低位开始接收
if(cc4_time[i]>1120)//高电平1680us为1
Order_anti_code|=1;
else//高电平560为0
Order_anti_code|=0;
}
//解码完成,发送到电脑
printf("address_code=%d\r\n",address_code);//发送地址码
printf("address_anti_code=%d\r\n",address_anti_code);//发送地址反码
printf("Order_code=%d\r\n",Order_code);//发送控制码
printf("Order_anti_code=%d\r\n",Order_anti_code);//发送控制反码
for(i=0;i<34;i++)
printf("cc4_time[%d]=%d\r\n",i,cc4_time[i]);//发送每次接收到的高电平的时间
printf("\r\n");//每两次输出之间分隔一行
cc4_status=0;//开始等待下一次捕获
}
}
}
}
//重定义fputc函数 ,想要使用printf函数得添加这个函数
int fputc(int ch, FILE *f)
{
while((USART1->SR&0X40)==0);//循环发送,直到发送完毕
USART1->DR = (u8) ch;
return ch;
}