??夏天来了,空调简直就是我们的救命神器,但是我总是担心找不到遥控器,所以我花了一天一夜的时间用单片机做了一个语音识别空调遥控器,24小时放在空调下面,再也不怕遥控器找不到了。~除了遥控器最基本的功能外,还可以添加自己定义的功能,比如循环开半小时,然后关半小时,用于节电;或根据环境温度调节空调温度等~
1 红外遥控原理
??红外遥控是一种无线、非接触控制技术,具有抗干扰能力强、信息传输可靠、功耗低、成本低、易于实现等显著优点。广泛应用于许多电子设备,特别是家用电器,并越来越多地应用于计算机系统。 ?? 红外遥控发射电路由红外接收二极管、三极管或硅光电池组成,将红外发射器发射的红外光转换为相应的电信号,然后发送后放大器。 ??发射机一般由指令键(或操作杆)、指令编码系统、调制电路、驱动电路、发射电路等部件组成。当按下指令键或推动操作杆时,指令编码电路产生所需的指令编码信号,指令编码信号调制载波,然后通过发射电路向外发射调制的指令编码信号。 接收电路一般由接收电路、放大电路、调制电路、指令翻译电路、驱动电路、执行电路(机构)等组成。接收电路接收发射器发出的编码指令信号,放大后发送解调电路。解调电路解调已调制的指令编码信号,即恢复为编码信号。指令翻译器翻译编码指令信号,最后由驱动电路驱动执行电路,实现各种指令的操作控制(机构)。 ??目前,红外遥控编码应用广泛:NEC Protocol 的 PWM(脉冲宽度调制)和 Philips RC-5 Protocol 的 PPM(脉冲位置调制)。这里我抓取了我自己奥克斯空调遥控器的指令波形,发现使用的是NEC协议脉宽调制。 ??NEC 代码的位置定义:脉冲对应 560us 连续载波,逻辑 1 传输需要 2.25ms(560us脉冲 1680us 低电平),逻辑 0 的传输需要 1.125ms(560us 脉冲 560us 低电平)。当接收到脉冲时,遥控接收头为低电平,无脉冲时为高电平。这样,我们在接收头端收到的信号是:逻辑 1 应该是 560us 低 1680us 高,逻辑 0 应该是 560us 低 560us 高。数据均采用8bit传输方式,低位在前高位在后。 ??需要注意的是,接收器在这里解码的高电平实际上是灭绝的;低电平是发射器的亮起状态。而且发射器端部需要38KHz载波驱动不是简单的高电平驱动。必须注意这一点。
2 红外遥控器的生产工艺
2.1 解码遥控指令
??解码遥控指令是不必要的,但这一步可以加深对协议的理解,方便后续代码的编写。如果您可以在互联网上找到您复制的遥控器的协议代码,则可以省略此步骤。 ??首先,用逻辑分析仪抓取红外接收器解调的信号,大致了解协议格式。 ??例如,上图中红色长方形中的数据为0b1110110,这个数据是0,因为它处于低位。x6F,黄色长方形中的数据为0xE0.这一步主要是用单片机解码时验证结果的正确性。 ??根据不同功能下的指令差异,可以分析每个字节的每个代表。例如,我的AUX空调遥控器的命令分析如下。主流空调品牌协议应在网上找到。
2.2 编写单片机解码代码
??我的红外接收器和发射器是淘宝随便买的,单片机是华大的HC32L130,当然以自己选择,成本越低越好。因为驱动红外发射器的载波只需要38KHz,对单片机的时钟频率要求也不是很高。 ??考虑到协议是PWM调制,所以需要分析占空比来确定每个代表的含义,但其实这个人的含义只需要看高电平的时间,所以用PWM捕获捕获上升沿,捕获后立即将捕获类型改为下降沿捕获。除时钟频率外,两次捕获之间的计数器值为高电平时间。除了SYNC除了信号,每8个bit存储在数组中的字节数据buffer中。由于AUX遥控器为13byte收到13个指令byte然后打印主函数中接收到的数据并重置buffer。代码示例如下:
void PwmCaptureInit(void) {
stc_pcacfg_t PcaInitStruct; stc_gpio_cfg_t GpioInitStruct; Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio, TRUE); Sysctrl_SetPeripheralGate(SysctrlPeripheralPca, TRUE); //PA07设置为PCA_CH1 GpioInitStruct.enDrv = GpioDrvH; GpioInitStruct.enDir = GpioDirIn; Gpio_Init(GpioPortA, GpioPin7, &GpioInitStruct); Gpio_SetAfMode(GpioPortA,GpioPin7,GpioAf2); PcaInitStruct.pca_clksrc = PcaPclkdiv32; //32MHz / 32 = 1MHz PcaInitStruct.pca_cidl = FALSE; PcaInitStruct.pca_ecom = PcaEcomDisble;
PcaInitStruct.pca_capp = PcaCappEnable; //上升沿捕获
PcaInitStruct.pca_capn = PcaCapnDisable; //禁止下降沿捕获
PcaInitStruct.pca_mat = PcaMatDisable;
PcaInitStruct.pca_tog = PcaTogDisable;
PcaInitStruct.pca_pwm = PcaPwm8bitDisable;
PcaInitStruct.pca_epwm = PcaEpwmDisable;
Pca_M1Init(&PcaInitStruct);
Pca_ClrItStatus(PcaCcf1);
Pca_ConfModulexIt(PcaModule1, TRUE);
EnableNvic(PCA_IRQn, IrqLevel3, TRUE);
Pca_StartPca(TRUE);
}
uint8_t remoteStatus = 0;
uint16_t captureValue = 0; //下降沿时计数器的值
uint8_t captureCount = 0; //按键按下的次数
uint8_t cmdBuf[13] = {
0 };//每条命令有13bytes
uint8_t cmdNum = 0;//当前是第几条命令
uint8_t bitCnt = 0;
void Pca_IRQHandler(void)
{
M0P_TIM0_MODE0->M0CR &= ~1; //停止并清零定时器
M0P_TIM0_MODE0->CNT = 0;
if(Pca_GetItStatus(PcaCcf1) != FALSE)
{
if(Gpio_GetInputIO(GpioPortA, GpioPin7) == TRUE)//捕获到上升沿后,标记捕获状态并切换为下降沿捕获
{
M0P_PCA->CCAPM1_f.CAPP = 0;
M0P_PCA->CCAPM1_f.CAPN = 1; //切换为下降沿捕获
Pca_SetCnt(0);
remoteStatus |= 0X10;
}
else //捕获到下降沿,判断当前接收到的是什么指令码,并回到上升沿捕获
{
captureValue = Pca_GetCcap(PcaModule1);
M0P_PCA->CCAPM1_f.CAPP = 1;
M0P_PCA->CCAPM1_f.CAPN = 0; //切换为上升沿捕获
if(remoteStatus & 0X10) //前面捕获到上升沿
{
if(remoteStatus & 0X80) //当前命令存在合法的SYNC码头
{
if(captureValue > 300 && captureValue < 800) //560us
{
cmdBuf[cmdNum] >>= 1; //接收到0,右移一位
bitCnt++;
}
else if(captureValue > 1400 && captureValue < 1900) //1680us
{
cmdBuf[cmdNum] >>= 1; //接收到1,左移一位并存在最高位
cmdBuf[cmdNum] |= 0x80;
bitCnt++;
}
else if(captureValue > 2200 && captureValue < 2700) //2.5ms,按键次数增加
{
captureCount++;
remoteStatus &= 0XF0; //清空计时器
}
if(bitCnt==8)
{
bitCnt = 0;
cmdNum++;
}
}
else if(captureValue > 4200 && captureValue < 4700) //4.5ms SYNC
{
remoteStatus |= 0X80; //SYNC状态位置1
captureCount = 0; //清除按键次数计数器
}
}
remoteStatus &= ~0x10; //等待下一次上升沿捕获
}
}
Pca_ClrItStatus(PcaCcf1);
Pca_ClrItStatus(PcaCf);
M0P_TIM0_MODE0->M0CR |= 1; //定时器重新开始计数
}
2.3 单片机编码发送代码编写
那么到这里就可以按照协议来发送我们的命令了。注意驱动红外二极管要用38KHz的载波驱动。因为这个载波频率不需要特别精准,所以我只是用软件模拟了一个方波。需要精准的PWM驱动可以用定时器/硬件PWM实现。
void RemoteGpioInit(void) { stc_gpio_cfg_t GpioInitStruct; DDL_ZERO_STRUCT(GpioInitStruct); Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio, TRUE); //PB9作为红外输出data脚 GpioInitStruct.enDrv = GpioDrvH; GpioInitStruct.enDir = GpioDirOut; Gpio_Init(GpioPortB, GpioPin9, &GpioInitStruct); } void IRSend_1(void) { uint32_t cnt = 0x2C; while(cnt--) //38KHz,560us { M0P_GPIO->PBOUT ^= 0x200; __nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop(); __nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop(); __nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop(); __nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop(); __nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop(); __nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop(); __nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop(); __nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop(); __nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop(); __nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop(); __nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop(); __nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop(); __nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop(); __nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop(); __nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop(); __nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop(); __nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop(); __nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop(); __nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop(); __nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop(); __nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop(); __nop();__nop();__nop(