DIY:制作一个语音识别的空调遥控器

夏天来了,空调对我们来说简直是救命稻草,但对于失去一切的我来说,因为找不到遥控器,我一直很担心,所以我花了一天一夜的时间用单片机制作了一个语音识别空调遥控器。把它放在空调下24小时,从现在开始,我再也不怕找不到遥控器了~因为除了遥控器最基本的功能外,你甚至可以添加自己定义的功能。例如,打开半小时,然后关闭半小时,以节省电力。或根据环境温度调整空调温度等。

[En]

When summer comes, the air conditioner is simply a life-saving device for us, but for me, who lost everything, I was always worried because I couldn’t find the remote control, so I spent a day and a night making a voice recognition air conditioner remote control with a single-chip microcomputer. Put it under the air conditioner for 24 hours, from now on, I am no longer afraid that the remote control can not be found ~ because in addition to the most basic functions of the remote control, you can even add your own defined functions. For example, turn it on for half an hour and then turn it off for half an hour to save electricity. Or adjust the air conditioning temperature according to the ambient temperature and so on.

效果图:

DIY:制作一个语音识别的空调遥控器

; 1 红外遥控器原理

红外遥控是一种无线非接触式控制技术,具有抗干扰能力强、信息传输可靠、低功耗、低成本、易于实现等显著优点。它被广泛应用于许多电子设备,特别是家用电器中,并越来越多地应用于计算机系统中。

[En]

Infrared remote control is a kind of wireless and contactless control technology, which has many remarkable advantages, such as strong anti-interference ability, reliable information transmission, low power consumption, low cost, easy to realize and so on. It is widely used in many electronic devices, especially household appliances, and is more and more used in computer systems.

红外遥控器的发射电路利用红外发光二极管发射调制的红外光波;红外接收电路由红外接收二极管、晶体管或硅光电池组成,将红外发射器发出的红外光转换成相应的电信号,然后送到后置放大器。

[En]

The transmitting circuit of infrared remote control uses infrared light emitting diodes to emit modulated infrared light waves; the infrared receiving circuit is composed of infrared receiving diodes, transistors or silicon photocells, which convert the infrared light emitted by the infrared transmitter into corresponding electrical signals, and then send to the post amplifier.

发射机一般由指令键(或操作杆)、指令编码系统、调制电路、驱动电路、发射电路等几部分组成。当按下指令键或推动操作杆时,指令编码电路产生所需的指令编码信号,指令编码信号对载波进行调制,再由驱动电路进行功率放大后由发射电路向外发射经调制定的指令编码信号。
接收电路一般由接收电路、放大电路、调制电路、指令译码电路、驱动电路、执行电路(机构)等组成。接收电路接收发射机发送的调制编码指令信号,并放大解调电路。解调电路对调制后的指令编码信号进行解调,即恢复为编码信号。指令译码器对编码后的指令信号进行译码,最后驱动电路驱动执行电路,实现对各种指令的运行控制(机制)。

[En]

The receiving circuit is generally composed of receiving circuit, amplifying circuit, modulation circuit, instruction decoding circuit, driving circuit, execution circuit (mechanism) and so on. The receiving circuit receives the modulated coding instruction signal sent by the transmitter and amplifies the demodulation circuit. The demodulation circuit demodulates the modulated instruction coding signal, that is, it is restored to the coded signal. The instruction decoder decodes the encoded instruction signal, and finally the driving circuit drives the execution circuit to realize the operation control (mechanism) of various instructions.

红外遥控的编码目前广泛使用的是:NEC Protocol 的 PWM(脉冲宽度调制)和 Philips RC-5 Protocol 的 PPM(脉冲位置调制)。这里我抓取了我自己奥克斯空调遥控器的指令波形,发现使用的是NEC协议脉宽调制。

DIY:制作一个语音识别的空调遥控器
NEC 码的位定义:一个脉冲对应 560us 的连续载波,一个逻辑 1 传输需要 2.25ms(560us脉冲+1680us 低电平),一个逻辑 0 的传输需要 1.125ms(560us 脉冲+560us 低电平)。而遥控接收头在收到脉冲的时候为低电平,在没有脉冲的时候为高电平,这样,我们在接收头端收到的信号为:逻辑 1 应该是 560us 低+1680us 高,逻辑 0 应该是 560us 低+560us 高。数据均采用8bit传输方式,低位在前高位在后。
DIY:制作一个语音识别的空调遥控器
需要注意的是,这里接收器解码出的高电平,实际上发射器是灭掉状态;低电平才是发射器亮起状态。而且发射器端是要用38KHz的载波驱动的,并不是简单的高电平驱动。这一点一定要注意。

2 红外遥控器的制作流程

2.1 遥控器指令解码

不需要对远程控制指令进行解码,但这一步可以加深对协议的理解,方便后续代码的编写。如果你能在网上找到你复制的遥控器的协议编码,你可以省略这一步。

[En]

Decoding the remote control instructions is not necessary, but this step can deepen the understanding of the protocol and facilitate the writing of subsequent code. If you can find the protocol coding of the remote control you copied online, you can omit this step.

首先,使用逻辑分析仪从红外接收端抓取解调信号,对协议格式有一个大致的了解。

[En]

First of all, use the logic analyzer to grab the demodulated signal from the infrared receiver and get a general understanding of the protocol format.

DIY:制作一个语音识别的空调遥控器
比如上图中红色长方形内的数据为0b11110110,因为是低位在前,所以这个数据为0x6F,同理黄色长方形内的数据为0xE0。这个步骤主要是为了后面用单片机进行解码时,对结果正确性进行校验。
根据不同功能下的指令差异,可以分析出每个字节的每一位代表的含义。比如我的AUX空调遥控器的命令分析如下。主流的空调品牌的协议应该在网上可以找得到。
DIY:制作一个语音识别的空调遥控器

; 2.2 单片机解码代码编写

我的红外接收器和发射器是淘宝随便买的,单片机用的是华大的HC32L130,当然这些都是可以自己选择的,成本越低越好。因为驱动红外发射器用的载波只需要38KHz,对单片机的时钟频率要求也不是很高。
考虑到由于协议为PWM调制,所以需要分析占空比来确定每一位代表的含义,但实际上只需要看高电平的时间就可以确定这一位的含义,所以采用PWM捕获来捕获上升沿,捕获到之后马上把捕获类型改为下降沿捕获。这样两次捕获之间的计数器的值除以时钟频率就是高电平的时间。除了SYNC信号外,每8个bit为一个字节数据,数据存放在一个数组buffer中。由于AUX遥控器为13byte一条指令,所以接收到13byte之后在主函数里把接收到的数据打印出来并重置buffer。代码示例如下:

初始化函数:

void PwmCaptureInit(void)
{
    stc_pcacfg_t  PcaInitStruct;
    stc_gpio_cfg_t GpioInitStruct;

    Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio, TRUE);
    Sysctrl_SetPeripheralGate(SysctrlPeripheralPca, TRUE);

    GpioInitStruct.enDrv  = GpioDrvH;
    GpioInitStruct.enDir  = GpioDirIn;
    Gpio_Init(GpioPortA, GpioPin7, &GpioInitStruct);
    Gpio_SetAfMode(GpioPortA,GpioPin7,GpioAf2);

    PcaInitStruct.pca_clksrc = PcaPclkdiv32;
    PcaInitStruct.pca_cidl   = FALSE;
    PcaInitStruct.pca_ecom   = PcaEcomDisable;
    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 };
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)
                {
                    if(captureValue > 300 && captureValue < 800)
                    {
                        cmdBuf[cmdNum] >>= 1;
                        bitCnt++;
                    }
                    else if(captureValue > 1400 && captureValue < 1900)
                    {
                        cmdBuf[cmdNum]  >>= 1;
                        cmdBuf[cmdNum]  |= 0x80;
                        bitCnt++;
                    }
                    else if(captureValue > 2200 && captureValue < 2700)
                    {
                        captureCount++;
                        remoteStatus &= 0XF0;
                    }
                    if(bitCnt==8)
                    {
                        bitCnt = 0;
                        cmdNum++;
                    }
                }
                else if(captureValue > 4200 && captureValue < 4700)
                {
                    remoteStatus |= 0X80;
                    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);

    GpioInitStruct.enDrv  = GpioDrvH;
    GpioInitStruct.enDir  = GpioDirOut;
    Gpio_Init(GpioPortB, GpioPin9, &GpioInitStruct);
}

void IRSend_1(void)
{
    uint32_t cnt = 0x2C;
    while(cnt--)
    {
        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();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();__nop();
        __nop();__nop();
    }
}

void IRSend_0(void)
{
    M0P_GPIO->PBOUT &= ~0x200;
    delay10us(30);
}

发送代码:

void RemoteSend(uint8_t* data,uint16_t len)
{
    REMOTE_SEND_SYNC;
    while(len--)
    {
        RemoteSendByte(*data);
        data++;
    }
    REMOTE_SEND_END;
}

void RemoteSendByte(uint8_t data)
{
    int i=0;
    for(;i<8;i++)
    {
        if(data & 0x01)
        {
            REMOTE_SEND_1;
        }
        else
        {
            REMOTE_SEND_0;
        }
        data >>= 1;
    }
}

到目前为止,红外收发环境已经建立。

[En]

So far, the infrared transceiver environment has been set up.

2.4 语音识别模块配置

这里我选择的语音识别模块为天问TW-ASR,这个模块我用起来感觉还是很容易上手的。可以图形化编程,语音识别也比较准。在模块定制的编程工具上写好你要识别的语音,并把语音匹配成功的行为设置好(比如通过串口发送数据)。

DIY:制作一个语音识别的空调遥控器

; 2.5 单片机串口模块配置+红外发射

语音模块识别成功后,通过串口将指令发送给单片机。我们接收单片机发送的指令,每13个字节判断接收指令的含义,并通过红外二极管将相应的指令发送给空调,从而完成了一套完整的语音识别遥控方案!

[En]

After the successful recognition of the speech module, the instructions are sent to the single-chip microcomputer through the serial port. We receive the instructions sent by the single-chip microcomputer, judge the meaning of the received instructions every 13 bytes, and send the corresponding commands to the air conditioner through infrared diodes, so a complete set of speech recognition remote control scheme is completed!

串口初始化:

void UartInit(int baud)
{
    stc_uart_cfg_t  stcCfg;
    stc_uart_baud_t stcBaud;
    stc_gpio_cfg_t stcGpioCfg;

    DDL_ZERO_STRUCT(stcGpioCfg);
    DDL_ZERO_STRUCT(stcCfg);
    DDL_ZERO_STRUCT(stcBaud);

    Sysctrl_SetPeripheralGate(SysctrlPeripheralUart1,TRUE);

    stcCfg.enRunMode        = UartMskMode1;
    stcCfg.enStopBit        = UartMsk1bit;
    stcCfg.enMmdorCk        = UartMskDataOrAddr;
    stcCfg.stcBaud.u32Baud  = baud;
    stcCfg.stcBaud.enClkDiv = UartMsk8Or16Div;
    stcCfg.stcBaud.u32Pclk  = Sysctrl_GetPClkFreq();
    Uart_Init(M0P_UART1, &stcCfg);

    Uart_ClrStatus(M0P_UART1,UartRC);
    Uart_ClrStatus(M0P_UART1,UartTC);
    Uart_EnableIrq(M0P_UART1,UartRxIrq);
    EnableNvic(UART1_IRQn, IrqLevel2, TRUE);

    Sysctrl_SetPeripheralGate(SysctrlPeripheralGpio,TRUE);
    stcGpioCfg.enDir = GpioDirOut;
    Gpio_Init(GpioPortA,GpioPin2,&stcGpioCfg);
    Gpio_SetAfMode(GpioPortA,GpioPin2,GpioAf1);

    stcGpioCfg.enDir = GpioDirIn;
    Gpio_Init(GpioPortA,GpioPin3,&stcGpioCfg);
    Gpio_SetAfMode(GpioPortA,GpioPin3,GpioAf1);
}

串口中断:

char uartRecvDataBuf[20];
uint16_t uartRecvDataNum;
uint8_t uartRxFlg=0;
void Uart1_IRQHandler(void)
{
    if(Uart_GetStatus(M0P_UART1, UartRC))
    {
        uartRecvDataBuf[uartRecvDataNum++] = Uart_ReceiveData(M0P_UART1);
        uartRxFlg = 1;
    }
    Uart_ClrStatus(M0P_UART1, UartRC);
}

数据解析:

#define COMMAND_AMOUNT  36
char* commands[COMMAND_AMOUNT] =
{
    "TURN ON\r\n","TURN OFF\r\n",
    "TEMP -1\r\n","TEMP +1\r\n",
    "SPEED 1\r\n","SPEED 2\r\n","SPEED 3\r\n","SPEED 4\r\n","SPEED 5\r\n","SPEED AUTO\r\n",
    "MODE HEAT\r\n","MODE COLD\r\n","MODE AUTO\r\n",
    "WINDLR ON\r\n","WINDLR OFF\r\n","WINDUD ON\r\n","WINDUD OFF\r\n",
    "TIMEON 1\r\n","TIMEON 2\r\n","TIMEON 3\r\n","TIMEON 4\r\n",
    "TIMEOFF 0.5\r\n","TIMEOFF 1\r\n","TIMEOFF 1.5\r\n","TIMEOFF 2\r\n","TIMEOFF 2.5\r\n",
    "TIMEOFF 3\r\n","TIMEOFF 3.5\r\n","TIMEOFF 4\r\n","TIMEOFF 4.5\r\n","TIMEOFF 5\r\n",
    "TIMEOFF 5.5\r\n","TIMEOFF 6\r\n",
    "TIMEONOFF  1 1\r\n","TIMEONOFF  0.5 0.5\r\n","TIMEONOFF  1 0.5\r\n",
};
void AnalyzeCommand(void)
{
    int i=0;

    for(i=0;i<COMMAND_AMOUNT;i++)
    {
        if(strcmp(uartRecvDataBuf,commands[i]) == 0)
        {
            SendCommand(i);
        }
    }
}

指令发送:

uint8_t RemoteCommand[13] = {0xc3,0x8f,0xe0,0x00,0xe0,0x00,0x20,0x00,0x80,0x00,0x00,0x45,0x41};

void SendCommand(uint16_t cmdNum)
{
    uint16_t i = 0;
    uint8_t tmp = 0;

    switch(cmdNum)
    {
        case 0:
            RemoteCommand[9] |= 1<<5;
            RemoteCommand[11] = 0x45;
            break;
        case 1:
            RemoteCommand[9] &= ~(1<<5);
            RemoteCommand[11] = 0x45;
            break;
        case 2:
            tmp = (RemoteCommand[1]>>3) - 1;
            RemoteCommand[1] &= ~(0xf8);
            RemoteCommand[1] |= tmp<<3;
            RemoteCommand[11] = 0x41;
            break;
        case 3:
            tmp = (RemoteCommand[1]>>3) + 1;
            RemoteCommand[1] &= ~(0xf8);
            RemoteCommand[1] |= tmp<<3;
            RemoteCommand[11] = 0x40;
            break;
        case 4:
            RemoteCommand[4] &= ~(0xe0);
            RemoteCommand[4] |= 0x3 << 5;
            RemoteCommand[11] = 0x44;
            break;
        case 5:
            RemoteCommand[4] &= ~(0xe0);
            RemoteCommand[4] |= 0x2 << 5;
            RemoteCommand[11] = 0x44;
            break;
        case 6:
            RemoteCommand[4] &= ~(0xe0);
            RemoteCommand[4] |= 0x1 << 5;
            RemoteCommand[11] = 0x44;
            break;
        case 7:
            RemoteCommand[4] &= ~(0xe0);
            RemoteCommand[4] |= 0x6 << 5;
            RemoteCommand[11] = 0x44;
            break;
        case 8:
            RemoteCommand[4] &= ~(0xe0);
            RemoteCommand[4] |= 0x5 << 5;
            RemoteCommand[11] = 0x44;
            break;
        case 9:
            RemoteCommand[4] &= ~(0xe0);
            RemoteCommand[4] |= 0x7 << 5;
            RemoteCommand[11] = 0x44;
            break;
        case 10:
            RemoteCommand[6] &= ~(0xe0);
            RemoteCommand[6] |= 0x80;
            RemoteCommand[11] = 0x46;
            break;
        case 11:
            RemoteCommand[6] &= ~(0xe0);
            RemoteCommand[6] |= 0x20;
            RemoteCommand[11] = 0x46;
            break;
        case 12:
            RemoteCommand[6] &= ~0xe0;
            RemoteCommand[6] |= 0x00;
            RemoteCommand[11] = 0x46;
            break;
        case 13:
            RemoteCommand[2] &= ~0xe0;
            RemoteCommand[11] = 0x43;
            break;
        case 14:
            RemoteCommand[2] |= 0xe0;
            RemoteCommand[11] = 0x43;
            break;
        case 15:
            RemoteCommand[1] &= ~0x3;
            RemoteCommand[11] = 0x42;
            break;
        case 16:
            RemoteCommand[1] |= 0x3;
            RemoteCommand[11] = 0x42;
            break;
        case 17:
        case 18:
        case 19:
        case 20:
            RemoteCommand[5] &= ~0x1F;
            RemoteCommand[4] &= ~0x1F;
            RemoteCommand[4] |= cmdNum - 16;
            RemoteCommand[11] = 0x4d;
            break;
        case 21: case 23: case 25: case 27: case 29: case 31:
            RemoteCommand[5] |= 0x1F;
            RemoteCommand[4] &= ~0x1F;
            RemoteCommand[4] |= (cmdNum - 21) / 2;
            RemoteCommand[11] = 0x4d;
            break;
        case 22: case 24: case 26: case 28: case 30: case 32:
            RemoteCommand[5] &= ~0x1F;
            RemoteCommand[4] &= ~0x1F;
            RemoteCommand[4] |= (cmdNum - 20) / 2;
            RemoteCommand[11] = 0x4d;
            break;
        case 33:
        case 34:
        case 35:
        default:
            return;
    }
    RemoteCommand[12] = 0;
    for(i = 0; i < 12; i++)
        RemoteCommand[12] += RemoteCommand[i];
    RemoteSend(RemoteCommand, 13);
}

到此为止,一个完整的语音识别空调遥控器就完成了!需要更多资料可以关注公众号:嵌入式付呱呱,后台留言即可!

Original: https://blog.csdn.net/XiaoMing_/article/details/125465670
Author: 嵌入式付呱呱
Title: DIY:制作一个语音识别的空调遥控器

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/527213/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球