出差一周,刚回来又陪家人过端午,今天有空,继续上期的工作,首先完成多彩云灯的红外接收模块,因为我计划第一步实现红外遥控器控制多彩云灯,第二步实现
手机端控制,不能一口吃成胖子
红外接收思路
了解一下红外信号的基本构成,看图
图片中就是一个常见红外遥控发出的信号,1和0的区别在于低电平持续的时间不同,一个正常的遥控指令包含起始码,2字节地址正、反码,2字节操作正、反码,停止位
实现接收的思路就是将一个红外接收头连接到IO口上,在GoKit上我将它连接在A0口,实际就是PA0,然后使用外部中断,一旦接收到上升沿就开始进入外部中断,然后统计两个上升沿之间的时间,通过判断时间的长短确定是起始码还是0或1,完成32个bit接收时判断正反码是否匹配,如果匹配,那么就接收到一个正确的遥控按键值。
使用微信宠物那个工程,官网可以下载。尽量不修改原来的代码,因为以后还要做手机端开,增加红外接收部分即可。
从以上思路,需要完成的工作包括一下内容:
1、硬件连接,这个就是把红外接收头焊接或其他方式连接到板上A0口,实现5V供电。
2、配置IO口,实现外部中断配置,我使用的外部中断0。
3、实现中断服务程序。
4、使用Sys
tick作为红外接收的脉冲计时器。
修改延时代码
原来的工程代码还是做了一些修改,主要是延时部分,因为GoKit使用了SysTick作延时控制,实现方式不是我想要的。因为我也想使用SysTick来计时,因此我先修改原来的代码,来看一下原来的部分代码
void Delay_us(uint32_t nus)
{
uint32_t temp;
SysTick->LOAD = nus*fac_us;
SysTick->VAL=0x00;
SysTick->CTRL = 0x01;
do
{
temp = SysTick->CTRL;
}
while(temp & 0x01 && !(temp &(1<<16)));
SysTick->CTRL = 0x00;
SysTick->VAL = 0x00;
}
这个是延时us的原代码,中文注释拷贝不出来。
SysTick是一个24位的递减计时器,到0的时候会产生异常,并重装载。
源代码将其时钟配置为1M,每减1表示1us,以上代码可以看出,调用这个函数,设置一个值,当减到0的时候表示完成延时,在原来的系统中没有问题,但是中断需要计时的情况就会产生冲突,这个函数的清除操作会破坏中断的计时。
为此,我作了一些修改,使得原来的代码调用这个函数的时候,时间大致不会有问题,又能与中断共用计时器。
来看一下代码:
void Delay_Init(uint8_t SYSCLK)
{
SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
fac_us=SYSCLK/8;
fac_ms =(u16)fac_us*1000;
SysTick->LOAD = 0x00FFFFFF;
SysTick->VAL=0x00;
SysTick->CTRL = 0x01;
}
初始化部分将重装载值设为最大的24位为1,一旦减到0就开始重头来过,默认初始化完成就启动SysTick,速率还是1M不变,每减1表示1us。
void Delay_us(uint32_t nus)
{
uint32_t temp;
uint32_t delay_time;
uint32_t time1;
uint32_t time2;
delay_time = nus*fac_us;
time1 = SysTick->VAL;
do
{
time2 = SysTick->VAL;
if( time1 >= time2 )
{
temp = time1 - time2;
}
else
{
temp = 0x00FFFFFF - time2 + time1;
}
}
while( temp < delay_time );
}
us的延时函数改了,调用函数的时候记录一个时刻,运行时不停读取另一个时刻,时间达到延时要求的时候就退出,全程不会修改SysTick的寄存器,如果这时候中断也使用这样的计时方式,那么相互不会影响。
void Delay_ms(uint16_t nms)
{
Delay_us(1000 * nms);
}
延时1ms我就简单粗暴的直接调用上面的us函数,延时乘以1000就是ms了。
简单测试一下,这样改原来的系统运行没有问题。
接收实现代码
数据初始化部分,将红外接收的标志等数据清零。
void IR_RemoteInit(void)
{
IR_Remote.ID = IR_RemoteID
IR_Remote.KeyVal = IR_DataNull;
IR_Remote.RepetCnt = 0;
IR_Remote.RxDoneFlag = FALSE;
IR_Remote.RxStartFlag = FALSE;
IR_Remote.HeadFlag = FALSE;
IR_Remote.BitCnt = 0;
IR_Remote.RxBit = 0;
IR_Remote.LastTime = 0;
IR_Remote.CurrentTime = 0;
IR_Remote.PulseTime = 0;
IR_Remote.RxTimeOut = 0;
}
硬件初始化,将红外接收使用PA0口配置为外部中断,值得注意的是,红外接收头没有上拉,也就是传说中的开漏输出,所以一定要将信号的IO口配置为内部上拉,注意中断优先级不要与原来系统使用的冲突了。
void IR_Remote_GPIOInit(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
IR_RemoteInit();
RCC_APB2PeriphClockCmd(GPIO_IR_Remote_CLK, ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStructure.NVIC_IRQChannel = IR_Remote_EXTI_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_IR_Remote_PIN;
GPIO_Init(GPIO_IR_Remote_PORT, &GPIO_InitStructure);
GPIO_EXTILineConfig(GPIO_IR_Remote_PortSource, GPIO_IR_Remote_PinSource);
EXTI_InitStructure.EXTI_Line = IR_Remote_EXTI_Line;
EXTI_InitStructure.EXTI_Mode = IR_Remote_EXTI_Mode;
EXTI_InitStructure.EXTI_Trigger = IR_Remote_EXTI_Trigger;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
printf("IR remote Init OKrn");
}
中断服务程序,第一次进中断需要特别处理,首先接收起始码,时间关系与前面的图不一样,因为我这遥控器不知道是什么厂家的,没有资料,我通过调试确定了起始码,0和1大致的时间范围,然后识别出所有的按键,正反码都是正确匹配的。调试的时候可以加一些打印信息,正式的就不要再中断里面打印了,太费时间。
void IR_DecodeISR(void)
{
if(GPIO_ReadInputDataBit( GPIO_IR_Remote_PORT, GPIO_IR_Remote_PIN ) )
{
if( IR_Remote.RxStartFlag == FALSE )
{
IR_Remote.LastTime = SysTick->VAL;
IR_Remote.CurrentTime = SysTick->VAL;
IR_Remote.RxStartFlag = TRUE;
IR_Remote.RxTimeOut = IR_TIME_OUT_VAL_MS;
return;
}
IR_Remote.LastTime = IR_Remote.CurrentTime;
IR_Remote.CurrentTime = SysTick->VAL;
if(IR_Remote.CurrentTime <= IR_Remote.LastTime)
{
IR_Remote.PulseTime = IR_Remote.LastTime - IR_Remote.CurrentTime;
}
else
{
IR_Remote.PulseTime = 0x00FFFFFF - IR_Remote.CurrentTime + IR_Remote.LastTime;
}
if( IR_Remote.HeadFlag )
{
if((IR_Remote.PulseTime > 0x4500) && (IR_Remote.PulseTime < 0x5200)) //time = 2.25ms, cnt = 17@128us; bit 1 code
{
IR_Remote.RxBit |= 0x01;
IR_Remote.BitCnt++;
}
else if((IR_Remote.PulseTime > 0x2500) && (IR_Remote.PulseTime < 0x2A00)) //time = 1.125ms, cnt = 8@128us, bit 0 code
{
IR_Remote.RxBit &= 0xFFFFFFFE;
IR_Remote.BitCnt++;
}
else //error pulse, reset
{
IR_RemoteInit();
}
if(IR_Remote.PulseTime)
{
IR_Remote.PulseTime = 0;
if( IR_Remote.BitCnt >= 32)
{
IR_Remote.UserCode = (IR_Remote.RxBit >> 16 ) & 0xFF;
IR_Remote.UserCodeInv = (IR_Remote.RxBit >> 24 ) & 0xFF;
IR_Remote.DataCode = (IR_Remote.RxBit >> 0 ) & 0xFF;
IR_Remote.DataCodeInv = (IR_Remote.RxBit >> 8 ) & 0xFF;
IR_Remote.RxDoneFlag = FALSE;
if( ( IR_Remote.UserCode + IR_Remote.UserCodeInv == 0xFF )
&&( IR_Remote.UserCode == IR_Remote.ID ) )
{
if( IR_Remote.DataCode + IR_Remote.DataCodeInv == 0xFF )
{
IR_Remote.RxDoneFlag = TRUE;
SEND_MSG(IR_REMOTE_RX_DONE_MSG);
//printf("IR_Remote.DataCode = 0x%02xrn", IR_Remote.DataCode); //debug
}
}
IR_Remote.HeadFlag = FALSE;
IR_Remote.BitCnt = 0;
}
else
{
IR_Remote.RxBit <<= 1;
}
}
}
else
{
if(( IR_Remote.PulseTime > 0xB000) && (IR_Remote.PulseTime < 0xB500 )) //time = (9+4.5)ms, cnt = 105@128us header code
{
IR_Remote.HeadFlag = TRUE;
IR_Remote.BitCnt = 0;
IR_Remote.RxBit = 0;
}
else
{
IR_Remote.HeadFlag = FALSE;
}
IR_Remote.BitCnt = 0;
//IR_Remote.RxBit = 0;
IR_Remote.PulseTime = 0;
}
}
else
{
//do nothing
}
}
补一下接收和接口图