写在前面
最近在群里有看到有小伙伴在问九齐的硬件IR模块,突然想起来我好像用过,找了半天才把项目找出来,写篇博客记录一下方便下次使用和寻找。
按道理来说九齐所有内置硬件IR模块的MCU都是可以用类似的方法配置和使用的,举一反三哈。
快速导航
- 写在前面
- 发码
- 寄存器介绍
- 红外管端代码
- 解码
- 解码逻辑
- 接收端代码
发码
寄存器介绍
其实就是用到这个IRCR寄存器,通过配置IRCSEL可以选择通过PB1的什么电平来输出IR波。
红外管端代码
代码也很简单,全部贴出来自取吧:
/* =========================================================================
* Project: 发送端
* File: main.c
* Description: 051D
*
*
* Author: Pd
* Version: V1.0
* Date: 2022/4/20
=========================================================================*/
#include <ny8.h>
#include "ny8_constant.h"
#define u16 unsigned int
#define u8 unsigned char
void delay_us(int);
void delay_ms(int);
/* =======================================================================*/
#define LED PORTBbits.PB5 //红外LED
/* =======================================================================*/
u16 t_200ms=0;
u8 f_send=0;
u8 f_start=0;
u8 t_start=0;
u8 send_data=0x89;
u8 i=0;
u8 j=0;
/* =======================================================================*/
void delay_us(int count) //@16M 2T 2.5us
{
for(;count>0;count--);
}
void delay_ms(int count) //@16M 2T
{
int i;for(;count>0;count--){for(i=0;i<=330;i++);}
}
void sys_init(void)
{
PCON &= ~(1<<3); //关闭LVR 需要在IC_CONFIG里面设置寄存器配置
}
/* =======================================================================*/
void io_init(void)
{
IOSTB|=((1<<4)|(1<<3)|(1<<2)|(1<<0)); //B4输入
IOSTB&=~(1<<1); //B1输出
IOSTB&=~(1<<2); //B2输出
IOSTB&=~(1<<5); //B5输出
BPHCON=0; //开启B口所有上拉电阻
PB1=0;LED=0;
}
void timer1_init(void)
{
T1CR1=0X02; //当下溢发生,定时器 1 初始值从TMR1 寄存器被重新加载并继续下数
T1CR2=0X03; //分频
TMR1 =123;
INTE |=(1<<3); //开启T1中断
T1CR1 |=0x01; //开启T1
}
void ir_init(void)
{
IRCR=0X05;
}
/* =======================================================================*/
void isr(void) __interrupt(0)
{
if(INTFbits.T1IF) //定时器1中断 //500us
{
INTFbits.T1IF=0;
t_200ms++;
if(t_200ms>=1000)
{
t_200ms=0;
f_send=1;
f_start=1;
}
if(f_send==1)
{
if(f_start==1)
{
t_start++;
if(t_start<22)
{
PB1=0;
}
else
{
PB1=1;
}
}
if(t_start>=30)
{
t_start=0;
f_start=0;
}
}
}
}
void main(void)
{
io_init();
delay_ms(50);
timer1_init();
ir_init();
INTF = 0; //清除所有中断标志
DISI(); //关闭总中断
ENI(); //开启总中断
while(1)
{
if(f_send==1 && f_start==0)
{
j++;
if(j<=8)
{
i=send_data&0x01;
if(i==0)
{
PB1=1;
delay_us(500);
PB1=0;
delay_us(500);
PB1=1;
}
else
{
PB1=1;
delay_us(500);
PB1=0;
delay_us(1500);
PB1=1;
}
send_data=send_data>>1;
}
if(j==9) {j=0;send_data=0x89;f_send=0;}
}
}
}
代码是比较简单的,当时写的协议忘记是通用协议还是私有协议了。
无非就是先发头码,然后发数据。
用硬件IR的好处就是不再需要自己通过硬件PWM去配初值了,不过局限性也很明显,这个硬件IR模块也就只支持38K和57K,而且亲测误差是比较大的,38K量出来实际好像到40多K了。如果要求特别精确,还是只能使用硬件PWM去调。
解码
解码逻辑
实在是忘记了当时的协议了,自己读了下代码推测发射端是,数据“1”是由0.5ms的低电平+1.5ms的38K载波,数据“0”是由0.5ms的低电平+0.5ms的38K载波。
看接收端硬件是将接收头的输出脚接了上拉电阻,也就是说接收头的空闲状态,输入到MCU的电平信号是高电平,当有接收到发射端的38K信号之后,输出一个低电平给到MCU。
那么对于接收端MCU的IO来说:
数据“0” = 0.5ms的高电平 + 0.5ms的低电平 (因为没有收到38K载波,IO会被上拉到高电平。收到载波之后会被拉低)
数据“1” = 0.5ms的高电平 + 1.5ms的低电平
两者的区别就是低电平的时间,那么只需要判定低电平的时间就可以判断出发射端发过来的数据是什么。
那么我的处理方式是开启IO中断,在进入中断后判断IO信号,如果IO信号为0,那么开启计数,当下次进中断判断IO如果为高电平,说明载波数据已经结束,这时候就可以判断低电平(载波时间)是多少,就可以判断出发射端是要传输“0”还是“1”了。
接收端代码
/* =========================================================================
* Project: 接收端
* File: main.c
* Description: 051D
*
*
* Author: Pd
* Version: V1.0
* Date: 2022/4/22
=========================================================================*/
#include <ny8.h>
#include "ny8_constant.h"
#define u16 unsigned int
#define u8 unsigned char
void delay_us(int);
void delay_ms(int);
/* =======================================================================*/
#define REC PORTBbits.PB5 //红外接收
#define LED PORTBbits.PB4 //RGB-12V
#define GLED PORTBbits.PB0 //指示灯 收到---0 没收到---1(高电平有效)
/* =======================================================================*/
u8 f_rec=0;
u8 f_head=0;
u8 f_no_rec=0;
u8 f_receive=0;
u8 f_error=0;
u8 f_bit_end=0;
u8 t_error=0;
u16 t_no_rec=0;
u8 t_head1=0;
u8 t_bit_end=0;
u16 t_receive=0;
u8 rec_data=0;
u8 get_data=0;
u8 bit_receive=0;
u8 t_over_time=0;
u16 t_low_time=0;
u8 f_rece_38k=0;
u8 i=0;
u8 j=0;
u8 z=1;
/* =======================================================================*/
void delay_us(int count) //@16M 2T 2.5us
{
for(;count>0;count--);
}
void delay_ms(int count) //@16M 2T
{
int i;for(;count>0;count--){for(i=0;i<=330;i++);}
}
void sys_init(void)
{
PCON &= ~(1<<3); //关闭LVR 需要在IC_CONFIG里面设置寄存器配置
}
/* =======================================================================*/
void io_init(void)
{
IOSTB|=((1<<5)|(1<<3)|(1<<2)); //B4输入
IOSTB&=~((1<<4)|(1<<0)); //B4输出
BPHCON=0; //开启B口所有上拉电阻
REC=1;LED=1;GLED=0;
}
void timer1_init(void)
{
T1CR1=0X02; //当下溢发生,定时器 1 初始值从TMR1 寄存器被重新加载并继续下数
T1CR2=0X00; //2分频
TMR1 =100;
INTE |=(1<<3); //开启T1中断
T1CR1 |=0x01; //开启T1
}
void pb_init(void)
{
BWUCON|=(1<<5); //开启PB5电平变化中断
INTE |=(1<<1); //开启PB中断
}
/* =======================================================================*/
void isr(void) __interrupt(0)
{
if(INTFbits.T1IF) //定时器1中断 //50us
{
INTFbits.T1IF=0;
if(f_rece_38k==1 && REC==0)
{
t_low_time++;
}
if(t_receive<=25000) //不停的计时,当超时之后指示灯亮
{
t_receive++; //每次收到一帧正确的数据时候清零超时计数器
}
if(t_receive>=10000 && LED==1)
{
LED=0;
GLED=1; //没受到数据 指示灯亮提醒
f_head=0;
t_low_time=0;
bit_receive=0;
rec_data=0;
i=0;j=0;z=0;
}
}
if(INTFbits.PBIF) //PB中断
{
INTFbits.PBIF=0;
if(REC==0)
{
f_rece_38k=1;
}
else
{
if(f_head==0)
{
if(t_low_time>180 && t_low_time<480)
{
f_head=1;
t_low_time=0;
bit_receive=0;
rec_data=0;
i=0;j=0;z=0;
}
else
{
f_head=0;
t_low_time=0;
f_rece_38k=0;
}
if(t_low_time>1000)
{
t_error++;
if(t_error>=10)
{
f_error=1;
LED=0;
GLED=1; //没收到数据 指示灯亮提醒
}
}
}
else
{
rec_data=rec_data<<1;
if(t_low_time>=50 && t_low_time<=200)
{
rec_data|=0x01; //收到的数据是1
t_low_time=0;
}
else if(t_low_time>=20 && t_low_time<50)
{
t_low_time=0; //收到的数据是0
}
else if(t_low_time>200 || t_low_time<20) //收到的数据电平持续时间不符合规范,推测是出错了
{
f_head=0;
t_low_time=0;
bit_receive=0;
rec_data=0;
t_error++; //错误次数超过10次开始报警
if(t_error>=10)
{
f_error=1;
LED=0;
GLED=1; //没收到数据 指示灯亮提醒
}
}
bit_receive++;
if(bit_receive>=8)
{
f_head=0;
bit_receive=0;
f_rece_38k=0;
t_low_time=0;
i=rec_data&0x01;
j=rec_data&0x10;
j=j>>4;
z=rec_data&0x80;
z=z>>7;
if(i==1 && j==1 && z==1)
{
f_error=0;
t_error=0;
t_receive=0;
LED=1;
GLED=0; //受到数据 指示灯灭
}
rec_data=0;
}
}
}
}
}
void main(void)
{
io_init();
delay_ms(50);
pb_init();
timer1_init();
INTF = 0; //清除所有中断标志
DISI(); //关闭总中断
ENI(); //开启总中断
while(1)
{
}
}
代码比较垃圾,接受端解码的校验部分不懂当时是怎样设计的,读者自行忽略吧。。