NXP JN5169 使用红外发送 / 接收
一、红外发送
1、红外发射二极管原理图
红外发射模块
红外接收二极管为 HX1838
2、JN5169 发射端代码
红外发送是定时器 2 的一种特殊特性,它使用定时器产生波形用于红外远程控制应用。
远程控制协议(比如说 Philips RC-6)使用编码的位流将 On-Off Key(OOK)调制应用于载波信号。红外发送器能够适应各种远程控制协议,这些协议具有不同载波频率、载波占空比和数据位编码要求。红外发送器使用定时器 2 来产生一个可编程的载波波形,这个波形通过 RAM 中存储的可编程位序列进行 OOK 调制。最后得到的波形输出到相关的定时器 2 输出管脚。
注意:一个典型的红外 LED 要求至少 15mA 的驱动电流。由于标准的数字输出不具备这种驱动能力,因此将会要求使用外部晶体管或 LED 驱动器来提供这种电流。
OOK调制波形的示例如下图所示。
在上图这个示例中,周期性载波信号和二进制位模式 1011 的逻辑与产生最终的 OOK 调制波形,其中每个数据位的周期等于载波周期的 3 倍。
NEC 协议发送代码(JN5169 DIO12 连接红外发射模块)
uint32 TransmissionLengthInBits = 0; //传输序列的长度,以位为单位(1到4096)
uint32 BufferAddress[4]; //发送缓冲区
/**
* JN5169 红外发送 API 是先发送MSB,最后是LSB
* 而NEC协议是先发送LSB,最后是MSB
* 所以需要反转数据位,从MSB->LSB到LSB->MSB, 所有的Bit都必须反转
*/
uint8 u8ReverseData(uint8 data)
{
uint8 temp = data;
uint8 len = 1 * 8 - 1;
for (data >>= 1; data; data >>= 1){
temp <<= 1;
temp |= data & 1;
len--;
}
temp <<= len;
return (temp);
}
/**
* 设置发送序列,处理数据
* 在NEC协议中,逻辑1为2.25ms,脉冲时间560us(2.25ms / 560us = 4);逻辑0为1.12ms,脉冲时间560us(1.12ms / 560us = 2)
* 所以我们要发送逻辑1需要设置为0b1000,逻辑0为0b10
*/
void vSetSendSequence(uint32 *dat)
{
uint32 send_code1, send_code2, send_code3, send_code4;
uint8 address1, address2, command1, command2;
address1 = 0x40; //用户码
address2 = (~address1) & 0x000000FF; //用户反码0xbf
command1 = 0x56; //数据码
command2 = (~command1) & 0x000000FF; //数据反码0xa9
address1 = u8ReverseData(address1); //反转比特位
address2 = u8ReverseData(address2);
command1 = u8ReverseData(command1);
command2 = u8ReverseData(command2);
/**
* NEC协议引导码是是9ms的高电平脉冲,其后是4.5ms的低电平
* 高电平数据位,9ms / 560us = 16
* 低电平数据位,4.5ms / 560us = 8
* 逻辑1需要设置为0b1000,逻辑0为0b10
*/
send_code1 = 0b11111111111111110000000000000000;//9ms的高电平脉冲,其后是4.5ms的低电平
//0x40(0b0100 0000)反转比特位后为(0b0000 0010)
//转为发送数据后为10 10 10 10 10 10 1000 10
send_code1 = 0b11111111111111110000000010101010;
send_code2 = 0b10101000100000000000000000000000;
//0xBF(0b1011 1111)反转比特位后为(0b1111 1101)
//转为发送数据后为1000 1000 1000 1000 1000 1000 10 1000
send_code2 = 0b10101000101000100010001000100010;
send_code3 = 0b00101000000000000000000000000000;
//0x56(0b0101 0110)反转比特位后为(0b0110 1010)
//转为发送数据后为10 1000 1000 10 1000 10 1000 10
send_code3 = 0b00101000101000100010100010100010;
//0xA9(0b1010 1001)反转比特位后为(0b10010101)
//转为发送数据后为1000 10 10 1000 10 1000 10 1000
//因为HX1838红外接收是反相输出,所需多余位补1,HX1838输出低电平
send_code4 = 0b10001010100010100010100011111111;//最后八位补1
*dat++ = send_code1;
*dat++ = send_code2;
*dat++ = send_code3;
*dat++ = send_code4;
TransmissionLengthInBits = 4 * 32;//总发送长度,单位为位bit
}
PRIVATE void vCbSysCtrl(uint32 u32Device, uint32 u32ItemBitmap)
{
if (E_AHI_DEVICE_INFRARED == u32Device) { //红外发送完成中断
if (E_AHI_INFRARED_TX_MASK == u32ItemBitmap) { //
vPrintf("红外数据发送成功!\n");
}
}
}
void vInitInfrared(void)
{
bool status;
/**
* NEC协议
* JN5169 使用 OOK 调制波形
* 调制 38kHz 载波,载波周期 = 1 / 38000 * 1000000 = 26.31us
* 所以 u16Lo = 载波周期 / 定时器时钟周期 = 26.31us / 250ns = 105
* 如果我们需要调制载波占空比为 30%
* 所以低电平时间 u16Hi = (载波周期 * 70% )/ 定时器时钟周期 = (26.31us * 70%) / 250ns = 74
* 载波频率 = 1 / 载波周期 = 1 / (26.31 / 1000000) = 38.008 kHz
* 在NEC协议中,逻辑1为2.25ms,脉冲时间560us(2.25ms / 560us = 4);逻辑0为1.12ms,脉冲时间560us(1.12ms / 560us = 2)
* 所以我们可以确定NEC协议中位周期为 560us
* u16BitPeriodsInCarrierPeriods = 位周期 / 载波周期 = 560 / 26.31 = 21
*/
status = bAHI_InfraredEnable(2, //使用时钟预分频值来递减分频外设时钟并产生定时器时钟,2 (定时器时钟周期 = 2 ^ u8Prescale / 16MHz = 4/16MHz = 250ns)
74, //在启动定时器后载波变高之前的定时器时钟周期数,这定义了载波低电平的持续时间,载波低电平持续时间 = 74x250ns = 18.5μs
105, //在启动定时器后载波再次变低之前的定时器时钟周期数,这定义了载波周期,载波周期 = 105x250ns = 26.25μs, 即,频率 = 38.095kHz
21, //以载波周期为单位的位周期,位周期 = 21×26.25μs = 551μs
FALSE, //输出信号极性,输出不反转极性
TRUE); //开启发送完成中断
if(status){
vPrintf("红外发送器配置成功!\n");
}
else{
vPrintf("红外发送器配置失败!\n");
}
vAHI_InfraredRegisterCallback(vCbSysCtrl);//中断回调函数
}
PUBLIC void AppColdStart(void)
{
bool status;
/*等待系统时钟切换为外部32MHz晶振*/
while (bAHI_GetClkSource() == TRUE);
vAHI_WatchdogStop();
(void) u32AHI_Init();
vUartInit();
vAHI_DelayXms(2000);
vPrintf("System init...\n");
vSetSendSequence(BufferAddress);
vInitInfrared();
while (1) {
status = bAHI_InfraredStart(BufferAddress, TransmissionLengthInBits);//开启发送
if(status){
vPrintf("参数有效,启动发送!\n");
vPrintf("TransmissionLengthInBits = %d \n", TransmissionLengthInBits);
}
else{
vPrintf("参数无效,关闭发送!\n");
}
vAHI_DelayXms(1000);
}
}
PUBLIC void AppWarmStart(void)
{
AppColdStart();
}
效果图:
3、STC15W408AS 接收端代码
NEC 协议接收代码(STC15W408AS P3.2 连接红外接收模块)
#ifndef uchar
#define uchar unsigned char
#endif
#ifndef uint
#define uint unsigned int
#endif
//IRCOM[0] --- 存放用户码 00H
//IRCOM[1] --- 存放用户反码 BfH
//IRCOM[2] --- 存放数据码
//IRCOM[3] --- 存放数据反码
sbit IRIN = P3 ^ 2; //红外接收器数据线,外部中断0
uchar flag = 1;
uchar IRCOM[7];
void init_int0(void)
{
INT0 = 1;
IT0 = 1; //设置INT0的中断类型 (1:仅下降沿 0:上升沿和下降沿)
EX0 = 1; //使能INT0中断
}
void exint0(void) interrupt 0
{
uchar i, j, N;
uint k;
EX0 = 0;
Delay2ms();
if(IRIN == 1) {
EX0 = 1;
return;
}
k = 100;
//确认IR信号出现
while(!IRIN && k) { //等IR变为高电平,跳过9ms的前导低电平信号。
Delay200us();
k--;
}
k = 100;
while(IRIN && k) { //等 IR 变为低电平,跳过4.5ms的前导高电平信号。
Delay200us();
k--;
}
for(i = 0; i < 4; i++) { //收集四组数据
for(j = 0; j < 8; j++) { //每组数据有8位
k = 100;
while(!IRIN && k) { //等 IR 变为高电平
Delay200us();
k--;
}
k = 100;
N = 0;
while(IRIN && k) { //计算IR高电平时长
Delay200us();
N++;
k--;
if(N >= 30) {
EX0 = 1;
return; //0.14ms计数过长自动离开。
}
} //高电平计数完毕
IRCOM[i] = IRCOM[i] >> 1; //数据最高位补“0”
if(N >= 8) {
IRCOM[i] = IRCOM[i] | 0x80; //数据最高位补“1”
}
N = 0;
}
}
if(IRCOM[2] != ~IRCOM[3]) {
EX0 = 1;
return;
}
IRCOM[5] = IRCOM[2] & 0x0F; //取键码的低四位
IRCOM[6] = IRCOM[2] >> 4; //右移4次,高四位变为低四位
if(IRCOM[5] > 9) {
IRCOM[5] = IRCOM[5] + 0x37;
} else
IRCOM[5] = IRCOM[5] + 0x30;
if(IRCOM[6] > 9) {
IRCOM[6] = IRCOM[6] + 0x37;
} else
IRCOM[6] = IRCOM[6] + 0x30;
flag = 0;
EX0 = 1;
}
void main()
{
Init_Uart();
init_int0();
EA = 1;
IRIN = 1;
flag = 1;
Delay500ms();
Delay500ms();
Delay500ms();
Delay500ms();
printf("System init...\n");
while(1){
if(!flag){
flag = 1;
printf("IRCOM[0] = %x ", IRCOM[0]);
printf("IRCOM[1] = %x ", IRCOM[1]);
printf("IRCOM[2] = %x ", IRCOM[2]);
printf("IRCOM[3] = %x \n", IRCOM[3]);
}
}
}
效果图:
二、红外接收
红外接收二极管为 HX1838
红外键盘为(遵循 NEC 协议)
遥控器键位码
示例代码:
#define IRIN (1 << 3) //红外接收器数据线
uint8 IRCOM[7]; //储存命令和键码
//获取当前引脚的输入电平
uint8 vGet_Bit(uint32 dio)
{
uint8 io;
if (dio > 0xFF) {
dio = dio >> 8;
}
io = u8AHI_DioReadByte(FALSE);//DIO0-7
if ((io & dio) == dio) {
return (1);
} else {
return (0);
}
}
PRIVATE void vCbSysCtrl(uint32 u32Device, uint32 u32ItemBitmap)
{
uint8 i, j, N;
uint16 k;
if (E_AHI_DEVICE_SYSCTRL == u32Device) {
if (E_AHI_DIO3_INT == u32ItemBitmap) {
vAHI_DioInterruptEnable(0, IRIN);//关中断
//vAHI_DelayXms(2);
if (vGet_Bit(IRIN) == 1) {
//vPrintf("Error0!\n");
vAHI_DioInterruptEdge(0, IRIN); //下降沿触发
vAHI_DioInterruptEnable(IRIN, 0);
for (i = 0; i < 7; i++) {
IRCOM[i] = 0x00;
}
return;
}
k = 100;
//确认IR信号出现
while (vGet_Bit(IRIN) == 0 && k) { //等IR变为高电平,跳过9ms的前导低电平信号。
vAHI_DelayXus(200);
k--;
}
k = 100;
while (vGet_Bit(IRIN) == 1 && k) { //等 IR 变为低电平,跳过4.5ms的前导高电平信号。
vAHI_DelayXus(200);
k--;
}
for (i = 0; i < 7; i++) {
IRCOM[i] = 0x00;
}
for (i = 0; i < 4; i++) { //收集四组数据
for (j = 0; j < 8; j++) { //每组数据有8位
k = 100;
while (vGet_Bit(IRIN) == 0 && k) { //等 IR 变为高电平
vAHI_DelayXus(200);
k--;
}
k = 100;
N = 0;
while (vGet_Bit(IRIN) == 1 && k) { //计算IR高电平时长
vAHI_DelayXus(200);
N++;
k--;
if (N >= 30) {
//vPrintf("Error1!\n");
vAHI_DioInterruptEdge(0, IRIN); //下降沿触发
vAHI_DioInterruptEnable(IRIN, 0);
for (i = 0; i < 7; i++) {
IRCOM[i] = 0x00;
}
return;//0.14ms计数过长自动离开。
}
} //高电平计数完毕
IRCOM[i] = IRCOM[i] >> 1; //数据最高位补“0”
if (N >= 8) {
IRCOM[i] = IRCOM[i] | 0x80; //数据最高位补“1”
}
N = 0;
}
}
if (IRCOM[2] == ((~IRCOM[3]) & 0x000000FF)) {
IRCOM[5] = IRCOM[2] & 0x0F; //取键码的低四位
IRCOM[6] = IRCOM[2] >> 4; //右移4次,高四位变为低四位
if (IRCOM[5] > 9) {
IRCOM[5] = IRCOM[5] + 0x37;
} else {
IRCOM[5] = IRCOM[5] + 0x30;
}
if (IRCOM[6] > 9) {
IRCOM[6] = IRCOM[6] + 0x37;
} else {
IRCOM[6] = IRCOM[6] + 0x30;
}
vPrintf("Receive Key Code: %c%c\n", IRCOM[6], IRCOM[5]);
}
for (i = 0; i < 7; i++) {
IRCOM[i] = 0x00;
}
vAHI_DioInterruptEdge(0, IRIN); //下降沿触发
vAHI_DioInterruptEnable(IRIN, 0);
}
}
}
//初始化DIO
void vInitDio(void)
{
//上拉输入
vAHI_DioSetDirection(IRIN, 0);
vAHI_DioSetPullup(IRIN, 0);
vAHI_DioInterruptEdge(0, IRIN); //下降沿触发
vAHI_DioInterruptEnable(IRIN, 0);
vAHI_SysCtrlRegisterCallback(vCbSysCtrl);
}
uint8 vGetDioBit(uint32 dio)
{
uint8 bit;
for(bit = 0; bit < 32; bit++){
if(dio & 0x01){
break;
}
dio = dio >> 1;
}
return (bit);
}
PUBLIC void AppColdStart(void)
{
/*等待系统时钟切换为外部32MHz晶振*/
while (bAHI_GetClkSource() == TRUE);
vAHI_WatchdogStop();
(void) u32AHI_Init();
vUartInit();
vInitDio();
vAHI_DelayXms(2000);
vPrintf("System init...\n");
vPrintf("IRIN DIO: DIO%d\n", vGetDioBit(IRIN));
while (1) {
}
}
PUBLIC void AppWarmStart(void)
{
AppColdStart();
}
效果图: