天气太冷不想出被窝?来DIY一个离线语音控制器[通俗易懂]

天气太冷不想出被窝?来DIY一个离线语音控制器[通俗易懂]天气太冷不想出被窝?来DIY一个离线语音控制器点击上方“Embeded小飞哥”,选择“置顶/星标公众号”干货福利,第一时间送达!成就一番伟业的唯一途径就是热爱自己的事业。如果你还没能找到让自己热爱的事业,继续寻找,不要放弃。跟随自己的心,总有一天你会找到的。——乔布斯  你去关灯,你去,你去,。。我去。。小伙伴们有没有在天气寒冷时候,想去关灯,却离不开心爱的被窝的经历呢,有的话,跟着小飞哥一起来DIY一个离线语音控制器,有了它,我们

大家好,又见面了,我是你们的朋友全栈君。

 

成就一番伟业的唯一途径就是热爱自己的事业。如果你还没能找到让自己热爱的事业,继续寻找,不要放弃。跟随自己的 心,总有一天你会找到的。 ——乔布斯

  你去关灯,你去,你去,。。我去。。小伙伴们有没有在天气寒冷时候,想去关灯,却离不开心爱的被窝的经历呢,有的话,跟着小飞哥一起来DIY一个离线语音控制器,有了它,我们就可以安稳的卧在暖和的被窝了,来,干!

完成目标

  • HAL库串口使用

  • 常用串口接收及数据协议解析

  • 接收离线语音控制模块数据、解析,实现相应的控制

硬件环境

  • STM32F407ZGT6(或其他主控板)

  • 海凌科HLK-V20离线语音控制模块

 

海凌科HLK-V20离线语音控制模块

  • 电容式驻极体话筒(咪头)  

  • 杜邦线,LED灯,实验用到3个LED灯,面包板(非必备)

软件环境

  • keil5

  • cubemx

1 离线语音模块控制器

1.1 模块简介

  在某宝购买的,只需要9.9元,语音识别固定,支持57条语音,基本的是够用了,基本的风扇控制、灯控制、电饭煲控制、温度控制等都具备,基本上比较全面。

1.2 模块特性

处理内核:

  • 32bit RISC 内核, 运行频率 240M

  • 支持 DSP 指令集以及 FPU 浮点运算单元

  • FFT 加速器: 最大支持 1024 点复数 FFT/IFFT 运算或者是2048 点的实数 FFT/IFFT 运算

  • 定制化语音算法算子

存储:

  • 内置高速 SRAM

  • 内置 2MB FLASH

音频输入输出:

  • 支持 1 路模拟 Mic 输入

  • 支持双声道 DAC 输出

  • 持 I2S input/output

供电和时钟:

  • 内置 5V 转 3.3V, 3.3V 转 1.2V LDO 为芯片供电

  • RC 12MHz 时钟源和 PLL 锁相环时钟源

  • 置 POR(Power on Reset) , 低电压检测和看门狗

系统功能框图:

 

系统功能框图

引脚介绍:

    模块有 16 个引脚, 包括功放输出、 差分输入与串口。 具体定义说明见下表。

引脚介绍

引脚介绍

 

机械尺寸:

 

机械尺寸

功能描述

  HLK-V20模块是可以控制自身IO的,有几个IO可以通过语音控制使用,实现基本的控制功能,具体使用如下:

  • 模块唤醒后, 使用命令词” 打开空调” ,对应模块上 B7 引脚, 使用命令词” 关闭空调” ,B7 输出对应电平

  • 使用命令词” 打开灯光” , 对应模块上 B6 引脚, 使用命令词” 关闭灯光” , B6 输出对应电平

  • 使用命令词” 打开开关” ,对应模块上 B2 引脚, 使用命令词” 关闭开关” , B2 输出对应电平

  为了方便后续扩展功能,本次小飞哥使用的是外接MCU,通过读取HLK-V20模块串口输出数据,解析进行相应的控制,此办法在后续的扩展功能中比较方便,建议大家有条件的可以采用这种办法应用实例如下:

    模块可作为主控, 运用在语音控制 LED 灯, 语音控制继电器等场景。 模块可以应用在以下场景: 声控吊灯、 声控壁灯、 声控浴霸、 声控开关、 声控射灯、 声控 吸顶灯、 声控台灯、 墙壁开关、 酒店控制面板、 LED 台灯、 面板、 晾衣机、 电动窗帘、 风扇、 智能门锁、 扫地机、 智能台灯、 智能空调、 智能茶壶、 故事机、 智能窗帘、 智能风扇、 音控音 箱、 车载音控。

1.3 模块输出数据协议格式分析

  采用外接MCU的控制方法,那就必须首先对模块输出的数据协议格式进行分析,正确解析数据之后才能实现我们的功能,厂家出厂是有一套固定协议的,如果需要更改为自己的协议,需要厂家定制,估计走量的,暂时用厂家的就可以啦。 协议格式说明:

天气太冷不想出被窝?来DIY一个离线语音控制器[通俗易懂]   Payload 为唤醒与命令词对应的 action 标识, 唤醒对应的 action 为 wakeup_uni, 命令词对应的 action 详见后面介绍,对于我们来说,最重要的是解析出来action数据,根据action数据内容具体来定制我们要控制的设备。   下图为语音唤醒与命令词对应的串口数据(hex 格式), 8 条数据分别表示为唤醒、 打开台灯、 关闭台灯、 打开空调、 关闭空调、 打开浴霸、 关闭浴霸、 退出识别状态 天气太冷不想出被窝?来DIY一个离线语音控制器[通俗易懂]   关于提到的action字段,参考厂家提供的离线命令词与播报答复列表,简单列举一些:

    红框里面是模块收到语音控制命令之后,串口输出的数据,我们只需要把这部分数据解析出来,知道当前是什么指令,然后控制相应的设备即可。

2 软件实现

  主要用到串口1、串口3,定时器7、定时器3,串口1用于调试信息打印,定时器3用于PWM控制灯光亮度,实现调光功能,串口3用于接收模块串口输出数据、解析数据,定时7用于控制串口数据接收超时,下面来小飞哥来一步步介绍实现过程。

2.1 cubemx配置

  时钟配置,参考上us延时3种实现方法一文,就不再做详细介绍了。

串口配置:

  串口1配置,主要配置下图红框中的几项即可,开启接收中断,中断优先级可以选择默认的即可,波特率115200。 串口1配置   串口2配置,基本同串口1配置,也是主要配置下图红框中的几项即可,开启接收中断,中断优先级可以选择默认的即可,波特率115200。

串口2配置

串口2配置

定时器配置:

  定时器3配置,定时器3时钟为 84M/84=1Mhz,重装载值 500,所以 PWM 频率为 1M/500=2Khz。     定时器7配置,定时7配置为1ms周期,后面串口超时时间具体在配置。

定时器7配置

定时器7配置

  配置完之后的中断开启情况如下图:

  中断开启情况   配置完之后的IO使用情况如下图: IO使用情况

2.2 PWM简介

  脉冲宽度调制(PWM),是英文“Pulse Width Modulation” 的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点,就是对脉冲宽度的控制,PWM 原理如图 所示:

PWM 原理示意图   上图就是一个简单的 PWM 原理示意图。图中,我们假定定时器工作在向上计数 PWM模式,且当 CNT<CCRx 时,输出 0,当 CNT>=CCRx 时输出 1。那么就可以得到如上的 PWM示意图:当 CNT 值小于 CCRx 的时候, IO 输出低电平(0),当 CNT 值大于等于 CCRx 的时候,IO 输出高电平(1),当 CNT 达到 ARR 值的时候,重新归零,然后重新向上计数,依次循环。改变 CCRx 的值,就可以改变 PWM 输出的占空比,改变 ARR 的值,就可以改变 PWM 输出的频率,这就是 PWM 输出的原理,后面调光用到的就是这个原理,玩过PWM呼吸灯的小伙伴肯定就比较熟悉了,正点原子讲的也比较详细了,小飞哥就不啰嗦了。

2.3 代码编写

硬件连接

  • 主控MCU与HLK-V20连接

STM32 HLK-V20
VCC-5V VCC
GND GND
RX(PA3) TX
  • HLK-V20yu 扬声器连接

扬声器 HLK-V20
任意端 SP+
任意端 SP-
  • 主控MCU与受控设备连接

STM32 受控设备
PB0(TIM3-CH3) 台灯LED正极
GND 台灯LED负极
PF9 开灯LED正极
GND 开灯LED负极
PB5 红灯LED正极
GND 红灯LED负极

  以上接法仅仅是演示使用,实际使用还是配合继电器使用,能达到实际使用需求,无奈小飞哥手里就一个几年前买的继电器,还坏掉了,这次只能演示用了,后续会画板子开源出来,希望大家多多关注小飞哥公众号。

   cubemx基本配置完成了,各段配置代码如下:

串口2代码编写

  初始化部分由cubemx配置完成,就不啰嗦了,配置的正确,就不会有问题。

typedef struct {
 char Rxbuff[100];
 uint8_t RxData;
 uint8_t RxCnt;
 uint8_t RxTimCnt;
 uint8_t RxRecFlag;
 uint8_t RxEndFlag;
}uart2_para;
uart2_para Voice_RevPara;

/**
  * @brief  串口中断回调函数
  *         
  * @param  
  * @param  
  * @retval none
  */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
 if(huart->Instance==USART1)
 {
   Voice_RecUartCallBack();
 }
 else if(huart->Instance==USART2)
 {
   Voice_RecUartCallBack();
 }
}

/**
  * @brief  语音模块串口中断回调函数
  *         
  * @param  none
  * @param  none
  * @retval none
  */
void Voice_RecUartCallBack(void) 
{
  Voice_RevPara.RxTimCnt = 0;
  Voice_RevPara.RxRecFlag = 1;     
  Voice_RevPara.Rxbuff[Voice_RevPara.RxCnt] = Voice_RevPara.RxData;
  Voice_RevPara.RxCnt++;
  if(Voice_RevPara.RxCnt>=100){
   Voice_RevPara.RxCnt=0;
  }
  HAL_UART_Receive_IT(&huart2,&Voice_RevPara.RxData,1);
} 
/**
  * @brief  语音模块串口接收数据协议解析
  *         
  * @param  none
  * @param  none
  * @retval error status
  */

uint16_t PWM_Value;

int Voice_DataFrame(void)
{
 if(Voice_RevPara.RxEndFlag)
 {
  //判断数据头
  if(memcmp(Voice_RevPara.Rxbuff,"uArTcP",strlen("uArTcP")))
  {
   return Err_Head;
  }
  else       //判断具体是什么指令
  {
   if(!memcmp(Voice_RevPara.Rxbuff+16,"wakeup_uni",strlen("wakeup_uni")))
   {
    printf("the meseeage is wake up           唤醒模块\r\n");
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"openElectricfan",strlen("openElectricfan")))  //打开风扇,对应离线命令词与播报答复列表序号1功能
   {
    printf("the meseeage is openElectricfan   打开风扇\r\n");
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"closElectricfan",strlen("closElectricfan")))  //关闭风扇,对应离线命令词与播报答复列表序号2功能
   {
    printf("the meseeage is openElectricfan   关闭风扇\r\n");
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"AdjustGearmin",strlen("AdjustGearmin")))   //风扇调到最小,对应离线命令词与播报答复列表序号3功能
   {
    printf("the meseeage is AdjustGearmin     风扇调到最小\r\n");
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"AdjustGearmax",strlen("AdjustGearmax")))   //风扇调到最大,对应离线命令词与播报答复列表序号4功能
   {
    printf("the meseeage is AdjustGearmax     风扇调到最大\r\n");
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"settiCLonehonor",strlen("settiCLonehonor")))  //定时一小时关灯,对应离线命令词与播报答复列表序号5功能
   {
    printf("the meseeage is settiCLonehonor   定时一小时关灯\r\n");
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"settiOPonehonor",strlen("settiOPonehonor")))  //定时一小时开灯,对应离线命令词与播报答复列表序号6功能
   {
    printf("the meseeage is settiOPonehonor   定时一小时开灯\r\n");
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"volumeUpUni",strlen("volumeUpUni")))    //音量增大,对应离线命令词与播报答复列表序号7功能
   {
    printf("the meseeage is volumeUpUni      音量增大\r\n");
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"volumeDownUni",strlen("volumeDownUni")))   //音量减小,对应离线命令词与播报答复列表序号8功能
   {
    printf("the meseeage is volumeDownUni     音量减小\r\n");
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"openkongtiao",strlen("openkongtiao")))    //打开空调,对应离线命令词与播报答复列表序号9功能
   {
    printf("the meseeage is openkongtiao      打开空调\r\n");
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"closekongtiao",strlen("closekongtiao")))   //关闭空调,对应离线命令词与播报答复列表序号10功能
   {
    printf("the meseeage is closekongtiao     关闭空调\r\n");
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"zhidongmos",strlen("zhidongmos")))     //自动模式,对应离线命令词与播报答复列表序号11功能
   {
    printf("the meseeage is zhidongmos        自动模式\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"zhilenmos",strlen("zhilenmos")))      //制冷模式,对应离线命令词与播报答复列表序号12功能
   {
    printf("the meseeage is zhilenmos       制冷模式\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"zhirmos",strlen("zhirmos")))         //制热模式,对应离线命令词与播报答复列表序号13功能
   {
    printf("the meseeage is zhirmos           制热模式\r\n");   
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"shenggaowendu",strlen("shenggaowendu")))    //升高温度,对应离线命令词与播报答复列表序号14功能
   {
    printf("the meseeage is shenggaowendu     升高温度\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"jiandiwendu",strlen("jiandiwendu")))    //降低温度,对应离线命令词与播报答复列表序号15功能
   {
    printf("the meseeage is jiandiwendu       降低温度\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"turnon",strlen("turnon")))       //打开台灯,对应离线命令词与播报答复列表序号16功能
   {
    PWM_Value = 100;
    printf("the meseeage is turnon          打开台灯\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"turnoff",strlen("turnoff")))       //关闭台灯,对应离线命令词与播报答复列表序号17功能
   {
    PWM_Value = 0;
    printf("the meseeage is turnoff           关闭台灯\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"openled",strlen("openled")))       //打开灯,对应离线命令词与播报答复列表序号18功能
   {
    Relay_Ctrl(Relay_Ctrl_OPEN);
    RedLed_Ctrl(0);
    printf("the meseeage is openled           打开灯\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"closeled",strlen("closeled")))      //关闭灯,对应离线命令词与播报答复列表序号19功能
   {
    RedLed_Ctrl(0);
    Relay_Ctrl(Relay_Ctrl_CLOSE);
    printf("the meseeage is closeled          关闭灯\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"setmaxld",strlen("setmaxld")))      //调到最亮,对应离线命令词与播报答复列表序号20功能
   {   
    PWM_Value=500;
    printf("the meseeage is setmaxld          调到最亮\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"setminld",strlen("setminld")))      //调到最暗,对应离线命令词与播报答复列表序号21功能
   {
    PWM_Value = 10;
    printf("the meseeage is setminld          调到最暗\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"zengjialiangdu",strlen("zengjialiangdu")))   //增加亮度,对应离线命令词与播报答复列表序号22功能
   {
    
    if(PWM_Value>=500)
     PWM_Value = 500;
    else    
     PWM_Value+=100;
    printf("the meseeage is zengjialiangdu    增加亮度\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"jianxiaoliangdu",strlen("jianxiaoliangdu")))   //减小亮度,对应离线命令词与播报答复列表序号23功能
   { 
    if(PWM_Value<=50)
    {
     PWM_Value = 50;
    }
    else
    PWM_Value-=100;
    printf("the meseeage is jianxiaoliangdu   减小亮度\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"setcolorred",strlen("setcolorred")))     //灯调为红色,对应离线命令词与播报答复列表序号24功能
   {
    RedLed_Ctrl(1);
    Relay_Ctrl(Relay_Ctrl_CLOSE);
   printf("the meseeage is setcolorred       灯调为红色\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"dakaiyuba",strlen("dakaiyuba")))      //打开浴霸,对应离线命令词与播报答复列表序号25功能
   {
    printf("the meseeage is dakaiyuba       打开浴霸\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"guanbiyuba",strlen("guanbiyuba")))     //关闭浴霸,对应离线命令词与播报答复列表序号26功能
   {
    printf("the meseeage is guanbiyuba       关闭浴霸\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"dakaizaoming",strlen("dakaizaoming")))    //打开照明,对应离线命令词与播报答复列表序号27功能
   {
    printf("the meseeage is dakaizaoming      打开照明\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"guanbizhaoming",strlen("guanbizhaoming")))   //关闭照明,对应离线命令词与播报答复列表序号28功能
   {
    printf("the meseeage is dakaiyuba       关闭照明\r\n");  
   }

   else if(!memcmp(Voice_RevPara.Rxbuff+16,"dakainuanqi",strlen("dakainuanqi")))     //打开暖气,对应离线命令词与播报答复列表序号29功能
   {
    printf("the meseeage is dakainuanqi       打开暖气\r\n");  
   }

    else if(!memcmp(Voice_RevPara.Rxbuff+16,"guanbinuanq",strlen("guanbinuanq")))   //关闭暖气,对应离线命令词与播报答复列表序号30功能
   {
    printf("the meseeage is guanbinuanq       关闭暖气\r\n");  
   }

   else if(!memcmp(Voice_RevPara.Rxbuff+16,"dakaicfeng",strlen("dakaicfeng")))     //打开吹风,对应离线命令词与播报答复列表序号31功能
   {
    printf("the meseeage is dakaicfeng       打开吹风\r\n");  
   } 
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"guanbicfeng",strlen("guanbicfeng")))     //关闭吹风,对应离线命令词与播报答复列表序号32功能
   {
    printf("the meseeage is guanbinuanq       关闭吹风\r\n");  
   }  

   else if(!memcmp(Voice_RevPara.Rxbuff+16,"dakaiyingsji",strlen("dakainuanqi")))    //打开饮水机,对应离线命令词与播报答复列表序号33功能
   {
    printf("the meseeage is dakainuanqi       打开饮水机\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"guanbiyingsji",strlen("guanbiyingsji")))     //关闭饮水机,对应离线命令词与播报答复列表序号34功能
   {
    printf("the meseeage is guanbiyingsji     关闭饮水机\r\n");  
   }

   else if(!memcmp(Voice_RevPara.Rxbuff+16,"kaishichus",strlen("kaishichus")))     //开始出水,对应离线命令词与播报答复列表序号35功能
   {
    printf("the meseeage is kaishichus       开始出水\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"tingzchus",strlen("tingzchus")))      //停止出水,对应离线命令词与播报答复列表序号36功能
   {
    printf("the meseeage is tingzchus       停止出水\r\n");  
   }

   else if(!memcmp(Voice_RevPara.Rxbuff+16,"dakdianfangbao",strlen("dakdianfangbao")))   //打开电饭煲,对应离线命令词与播报答复列表序号37功能
   {
    printf("the meseeage is dakdianfangbao    打开电饭煲\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"guabdianfangbao",strlen("guabdianfangbao")))   //关闭电饭煲,对应离线命令词与播报答复列表序号38功能
   {
    printf("the meseeage is guabdianfangbao   关闭电饭煲\r\n");  
   }

   else if(!memcmp(Voice_RevPara.Rxbuff+16,"baowenmos",strlen("baowenmos")))      //保温模式,对应离线命令词与播报答复列表序号39功能
   {
    printf("the meseeage is baowenmos       保温模式\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"baotangmos",strlen("baotangmos")))     //煲汤模式,对应离线命令词与播报答复列表序号40功能
   {
    printf("the meseeage is baotangmos       煲汤模式\r\n");  
   } 
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"zhufangmos",strlen("zhufangmos")))     //煮饭模式,对应离线命令词与播报答复列表序号41功能
   {
    printf("the meseeage is zhufangmos       煮饭模式\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"zhuzhoumos",strlen("zhuzhoumos")))     //煮粥模式,对应离线命令词与播报答复列表序号42功能
   {
    printf("the meseeage is baotangmos      煮粥模式\r\n");  
   }

   else if(!memcmp(Voice_RevPara.Rxbuff+16,"dakaikaiguan",strlen("dakaikaiguan")))    //打开开关,对应离线命令词与播报答复列表序号43功能
   {
    printf("the meseeage is dakaikaiguan      打开开关\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"guanbikaiguan",strlen("guanbikaiguan")))    //关闭开关,对应离线命令词与播报答复列表序号44功能
   {
    printf("the meseeage is guanbikaiguan     关闭开关\r\n");  
   }

   else if(!memcmp(Voice_RevPara.Rxbuff+16,"dakaishaj",strlen("dakaishaj")))      //打开杀菌,对应离线命令词与播报答复列表序号45功能
   {
    printf("the meseeage is dakaishaj       打开杀菌\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"guanbishaj",strlen("guanbishaj")))     //关闭杀菌,对应离线命令词与播报答复列表序号46功能
   { 
    printf("the meseeage is guanbishaj       关闭杀菌\r\n");  
   }

   else if(!memcmp(Voice_RevPara.Rxbuff+16,"dakaihonggan",strlen("dakaihonggan")))    //打开烘干,对应离线命令词与播报答复列表序号47功能
   {
   printf("the meseeage is dakaihonggan      打开烘干\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"guanbihonggan",strlen("guanbihonggan")))    //关闭烘干,对应离线命令词与播报答复列表序号48功能
   {
   printf("the meseeage is guanbishaj      关闭烘干\r\n");  
   }

   else if(!memcmp(Voice_RevPara.Rxbuff+16,"dakaichuchou",strlen("dakaichuchou")))    //打开除臭,对应离线命令词与播报答复列表序号49功能
   {
    printf("the meseeage is dakaichuchou      打开除臭\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"guanbichuchou",strlen("guanbichuchou")))    //关闭除臭,对应离线命令词与播报答复列表序号50功能
   {
    printf("the meseeage is guanbichuchou     关闭除臭\r\n");  
   }

   else if(!memcmp(Voice_RevPara.Rxbuff+16,"volumeMaxUni",strlen("volumeMaxUni")))    //最大音量,对应离线命令词与播报答复列表序号51功能
   {
    printf("the meseeage is volumeMaxUni      最大音量\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"volumeMinUni",strlen("volumeMinUni")))    //最小音量,对应离线命令词与播报答复列表序号52功能
   {
    printf("the meseeage is volumeMinUni      最小音量\r\n");  
   }

   else if(!memcmp(Voice_RevPara.Rxbuff+16,"settiOonehonor",strlen("settiOonehonor")))   //一小时后开机,对应离线命令词与播报答复列表序号53功能
   {
    printf("the meseeage is settiOonehonor    一小时后开机\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"settiConehonor",strlen("settiConehonor")))   //一小时后关机,对应离线命令词与播报答复列表序号54功能
   {
    printf("the meseeage is settiConehonor    一小时后关机\r\n");  
   }

   else if(!memcmp(Voice_RevPara.Rxbuff+16,"tempset20",strlen("tempset20")))      //温度设置为20度,对应离线命令词与播报答复列表序号55功能
   {
    printf("the meseeage is tempset20         温度设置为20度\r\n");  
   }
   else if(!memcmp(Voice_RevPara.Rxbuff+16,"TempSet15",strlen("TempSet15")))      //温度设置为15度,对应离线命令词与播报答复列表序号56功能
   {
    printf("the meseeage is TempSet15         温度设置为15度\r\n");  
   }

   else if(!memcmp(Voice_RevPara.Rxbuff+16,"exitUni",strlen("exitUni")))       //退下,对应离线命令词与播报答复列表序号57功能
   {
    printf("the meseeage is exitUni           退下,再见\r\n");  
   }
   __HAL_TIM_SetCompare(&htim3,TIM_CHANNEL_3,PWM_Value);    //blue
   commandfinish();
   }
  }
 return Err_None;
}

/**
  * @brief  语音控制继电器开关,高电平触发
  *         
  * @param  Switch
  * @param  
  * @retval none
  */
void Relay_Ctrl(uint8_t Switch)
{
 if(Switch)
 {
  HAL_GPIO_WritePin(Relay_Ctr_GPIO_Port,Relay_Ctr_Pin,GPIO_PIN_SET);
 }
 else
 {
  HAL_GPIO_WritePin(Relay_Ctr_GPIO_Port,Relay_Ctr_Pin,GPIO_PIN_RESET);
 }
}
/**
  * @brief  语音控制继电器开关,切换红灯,高电平触发
  *         
  * @param  Switch
  * @param  
  * @retval none
  */
void RedLed_Ctrl(uint8_t Switch)
{
 if(Switch)
 {
  HAL_GPIO_WritePin(led_run_GPIO_Port,led_run_Pin,GPIO_PIN_SET);
 }
 else
 {
  HAL_GPIO_WritePin(led_run_GPIO_Port,led_run_Pin,GPIO_PIN_RESET);
 }
}

定时器代码编写:

/**
  * @brief  语音模块串口接收初始化
  *         
  * @param  none
  * @param  none
  * @retval none
  */
void  Voice_RecUartInit(void)
{
  HAL_UART_Receive_IT(&huart2,&Voice_RevPara.RxData,1);

  HAL_TIM_Base_Start(&htim7);
  HAL_TIM_Base_Start_IT(&htim7);
}
/*
  * @brief  定时器中断回调函数
  *         
  * @param  
  * @param  
  * @retval none
  */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
 if(htim->Instance==TIM2)
 {
  
 }
 else if(htim->Instance==TIM3)
 {
  
 }
 else if(htim->Instance==TIM7)
 {
  Voice_RecTimCallBack(100);
 }
}

/**
  * @brief  语音模块串口接收定时器接收超时函数
  *         
  * @param  timeout
  * @param  
  * @retval none
  */
void Voice_RecTimCallBack(uint16_t timeout)  //timeout ms
{
  if(Voice_RevPara.RxRecFlag){     //有数据到来
   if(Voice_RevPara.RxTimCnt>=timeout){  //判断定时器是否超时,100ms

   Voice_RevPara.RxTimCnt = 0;    //定时器计数器清零,计数器为人为设置,1ms+1
   Voice_RevPara.RxRecFlag = 0;   

   Voice_RevPara.RxEndFlag = 1;    //接接收完成标志
   }
  Voice_RevPara.RxTimCnt++;
  }
}
/**
  * @brief  语音模块串口命令处理完成
  *         
  * @param  none
  * @param  none
  * @retval none
  */
void commandfinish(void)
{
  Voice_RevPara.RxEndFlag = 0;
  Voice_RevPara.RxCnt = 0;
  memset(Voice_RevPara.Rxbuff,0,sizeof(Voice_RevPara.Rxbuff));
}

   上述代码主要实现的是串口接收一帧数据,通过定时器超时判断一帧数据的结束,超时时间为100ms,超时之后对数据帧进行判断、解析是不是需要的数据,这是一种比较常用的方法,简单有效,当然,当一包数据是错误的时候,会耽误时间。数据接收完成之后,先对数据头进行解析,判断数据是不是我们需要的包,数据头正确之后,在对action字段进行解析,解析出我们需要的内容,也即是控制内容。这种接收办法在数据包错误的时候,会耽误一包数据的时间,如果我们开始接收时就对数据头进行判断,数据头正确继续接收,错误直接丢掉,知道收到正确的数据头之后才开始接收后面数据,这样做,会在出错的情况下节省通讯时间。

  本次要分享的内容就要结束啦,希望对大家有帮助,让这个冬天不再寒冷,更多精彩内容,欢迎各位加群一起交流,获取本次离线语音控制的源码!

  如果你觉得对自己有帮助的话,给个赞,点个关注,点个在看,感谢前进的道路上有你的陪伴!图片欢迎大家关注Embeded小飞哥,让我快点遇到优秀的你,然后一起变得更加优秀,加油!!!小飞哥微信号:w974762670,加好友进群一起交流呀!

 

演示视频见公众号:

源码加群获取哈

天气太冷不想出被窝?来DIY一个离线语音控制器[通俗易懂]

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/152507.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(1)


相关推荐

  • 加多宝为什么会输给王老吉_加多宝王老吉占比

    加多宝为什么会输给王老吉_加多宝王老吉占比如果评选世界营销战最激烈案例,2012年爆发的王老吉与加多宝之间的品牌大战一定榜上有名。那是一场由一个小小红罐引发的“血战”。一、缘起加、王之争的来龙动脉我就不细说了,大概起因是若干年前国企广药将其旗下的传统凉茶品牌“王老吉”部分(红罐)租给了与王老吉凉茶创始者后人有关的香港鸿道集团旗下的加多宝集团。这种做法本来十分普遍没啥特别,但没想到的是加多宝的市场运作能力超强,竟然在短短几年内将一种原来…

    2022年10月26日
  • 国内免费域名注册_域名解析服务器是什么

    国内免费域名注册_域名解析服务器是什么ZeroNet最大的特点就是去中心化,无需服务器,由匈牙利开发者使用python开发,支持多个平台。建立好网站内容并同步到网络节点,有用户访问网站则自动存储上传,访问地址统一为127.0.0.1,不怕被墙或者拦截,采用Bitcoin加密技术传输内容保证数据安全。目前ZeroNet上已有相当健全的论坛和博客,安装客户端(你也可以简单的理解为浏览器)输入地址即可访问。想要建立的自己的网站也很…

  • Spring笔记(2)

    Spring笔记(2)

    2021年11月11日
  • Android第三方文件选择器:aFileChooser

    Android第三方文件选择器:aFileChooser

  • 一级域名301重定向到www二级域名

    一级域名301重定向到www二级域名301重定向有利于百度的搜索例如一个域名www.test.com如果不做设置会产生4个网址,(1)test.com(2)www.test.com(3)test.com/default.html(4)www.test.com/default.html会导致网址的PR值被分散.htaccess设置Options+FollowSymLinksIndexIgnore*/*…

  • 如何在java中输出保留两位小数「建议收藏」

    如何在java中输出保留两位小数「建议收藏」在输出时用以下的代码:System.out.println(String.format(“%.2f”,sum));其中”%.2f”为保留两位小数,sum为要输出的数字。当然,用print输出也是可以的:System.out.print(String.format(“%.2f”,sum));…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号