成就一番伟业的唯一途径就是热爱自己的事业。如果你还没能找到让自己热爱的事业,继续寻找,不要放弃。跟随自己的 心,总有一天你会找到的。——乔布斯
你去关灯,你去,你去,。。我去。。小伙伴们有没有在天气寒冷时候,想去关灯,却离不开心爱的被窝的经历呢,有的话,跟着小飞哥一起来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的控制方法,那就必须首先对模块输出的数据协议格式进行分析,正确解析数据之后才能实现我们的功能,厂家出厂是有一套固定协议的,如果需要更改为自己的协议,需要厂家定制,估计走量的,暂时用厂家的就可以啦。协议格式说明:
Payload 为唤醒与命令词对应的 action 标识, 唤醒对应的 action 为 wakeup_uni, 命令词对应的 action 详见后面介绍,对于我们来说,最重要的是解析出来action数据,根据action数据内容具体来定制我们要控制的设备。 下图为语音唤醒与命令词对应的串口数据(hex 格式), 8 条数据分别表示为唤醒、 打开台灯、 关闭台灯、 打开空调、 关闭空调、 打开浴霸、 关闭浴霸、 退出识别状态
关于提到的action字段,参考厂家提供的离线命令词与播报答复列表,简单列举一些:
红框里面是模块收到语音控制命令之后,串口输出的数据,我们只需要把这部分数据解析出来,知道当前是什么指令,然后控制相应的设备即可。
2 软件实现
主要用到串口1、串口3,定时器7、定时器3,串口1用于调试信息打印,定时器3用于PWM控制灯光亮度,实现调光功能,串口3用于接收模块串口输出数据、解析数据,定时7用于控制串口数据接收超时,下面来小飞哥来一步步介绍实现过程。
2.1 cubemx配置
时钟配置,参考上us延时3种实现方法一文,就不再做详细介绍了。
串口配置:
串口1配置,主要配置下图红框中的几项即可,开启接收中断,中断优先级可以选择默认的即可,波特率115200。
串口2配置,基本同串口1配置,也是主要配置下图红框中的几项即可,开启接收中断,中断优先级可以选择默认的即可,波特率115200。
串口2配置
定时器配置:
定时器3配置,定时器3时钟为 84M/84=1Mhz,重装载值 500,所以 PWM 频率为 1M/500=2Khz。
定时器7配置,定时7配置为1ms周期,后面串口超时时间具体在配置。
定时器7配置
配置完之后的中断开启情况如下图:
配置完之后的IO使用情况如下图:
2.2 PWM简介
脉冲宽度调制(PWM),是英文“Pulse Width Modulation” 的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点,就是对脉冲宽度的控制,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负极 |
以上接法仅仅是演示使用,实际使用还是配合继电器使用,能达到实际使用需求,无奈小飞哥手里就一个几年前买的继电器,还坏掉了,这次只能演示用了,后续会画板子开源出来,希望大家多多关注小飞哥公众号。
串口2代码编写
初始化部分由cubemx配置完成,就不啰嗦了,配置的正确,就不会有问题。
代码语言:javascript复制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 唤醒模块rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"openElectricfan",strlen("openElectricfan"))) //打开风扇,对应离线命令词与播报答复列表序号1功能
{
printf("the meseeage is openElectricfan 打开风扇rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"closElectricfan",strlen("closElectricfan"))) //关闭风扇,对应离线命令词与播报答复列表序号2功能
{
printf("the meseeage is openElectricfan 关闭风扇rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"AdjustGearmin",strlen("AdjustGearmin"))) //风扇调到最小,对应离线命令词与播报答复列表序号3功能
{
printf("the meseeage is AdjustGearmin 风扇调到最小rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"AdjustGearmax",strlen("AdjustGearmax"))) //风扇调到最大,对应离线命令词与播报答复列表序号4功能
{
printf("the meseeage is AdjustGearmax 风扇调到最大rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"settiCLonehonor",strlen("settiCLonehonor"))) //定时一小时关灯,对应离线命令词与播报答复列表序号5功能
{
printf("the meseeage is settiCLonehonor 定时一小时关灯rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"settiOPonehonor",strlen("settiOPonehonor"))) //定时一小时开灯,对应离线命令词与播报答复列表序号6功能
{
printf("the meseeage is settiOPonehonor 定时一小时开灯rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"volumeUpUni",strlen("volumeUpUni"))) //音量增大,对应离线命令词与播报答复列表序号7功能
{
printf("the meseeage is volumeUpUni 音量增大rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"volumeDownUni",strlen("volumeDownUni"))) //音量减小,对应离线命令词与播报答复列表序号8功能
{
printf("the meseeage is volumeDownUni 音量减小rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"openkongtiao",strlen("openkongtiao"))) //打开空调,对应离线命令词与播报答复列表序号9功能
{
printf("the meseeage is openkongtiao 打开空调rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"closekongtiao",strlen("closekongtiao"))) //关闭空调,对应离线命令词与播报答复列表序号10功能
{
printf("the meseeage is closekongtiao 关闭空调rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"zhidongmos",strlen("zhidongmos"))) //自动模式,对应离线命令词与播报答复列表序号11功能
{
printf("the meseeage is zhidongmos 自动模式rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"zhilenmos",strlen("zhilenmos"))) //制冷模式,对应离线命令词与播报答复列表序号12功能
{
printf("the meseeage is zhilenmos 制冷模式rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"zhirmos",strlen("zhirmos"))) //制热模式,对应离线命令词与播报答复列表序号13功能
{
printf("the meseeage is zhirmos 制热模式rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"shenggaowendu",strlen("shenggaowendu"))) //升高温度,对应离线命令词与播报答复列表序号14功能
{
printf("the meseeage is shenggaowendu 升高温度rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"jiandiwendu",strlen("jiandiwendu"))) //降低温度,对应离线命令词与播报答复列表序号15功能
{
printf("the meseeage is jiandiwendu 降低温度rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"turnon",strlen("turnon"))) //打开台灯,对应离线命令词与播报答复列表序号16功能
{
PWM_Value = 100;
printf("the meseeage is turnon 打开台灯rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"turnoff",strlen("turnoff"))) //关闭台灯,对应离线命令词与播报答复列表序号17功能
{
PWM_Value = 0;
printf("the meseeage is turnoff 关闭台灯rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"openled",strlen("openled"))) //打开灯,对应离线命令词与播报答复列表序号18功能
{
Relay_Ctrl(Relay_Ctrl_OPEN);
RedLed_Ctrl(0);
printf("the meseeage is openled 打开灯rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"closeled",strlen("closeled"))) //关闭灯,对应离线命令词与播报答复列表序号19功能
{
RedLed_Ctrl(0);
Relay_Ctrl(Relay_Ctrl_CLOSE);
printf("the meseeage is closeled 关闭灯rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"setmaxld",strlen("setmaxld"))) //调到最亮,对应离线命令词与播报答复列表序号20功能
{
PWM_Value=500;
printf("the meseeage is setmaxld 调到最亮rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"setminld",strlen("setminld"))) //调到最暗,对应离线命令词与播报答复列表序号21功能
{
PWM_Value = 10;
printf("the meseeage is setminld 调到最暗rn");
}
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 增加亮度rn");
}
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 减小亮度rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"setcolorred",strlen("setcolorred"))) //灯调为红色,对应离线命令词与播报答复列表序号24功能
{
RedLed_Ctrl(1);
Relay_Ctrl(Relay_Ctrl_CLOSE);
printf("the meseeage is setcolorred 灯调为红色rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"dakaiyuba",strlen("dakaiyuba"))) //打开浴霸,对应离线命令词与播报答复列表序号25功能
{
printf("the meseeage is dakaiyuba 打开浴霸rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"guanbiyuba",strlen("guanbiyuba"))) //关闭浴霸,对应离线命令词与播报答复列表序号26功能
{
printf("the meseeage is guanbiyuba 关闭浴霸rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"dakaizaoming",strlen("dakaizaoming"))) //打开照明,对应离线命令词与播报答复列表序号27功能
{
printf("the meseeage is dakaizaoming 打开照明rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"guanbizhaoming",strlen("guanbizhaoming"))) //关闭照明,对应离线命令词与播报答复列表序号28功能
{
printf("the meseeage is dakaiyuba 关闭照明rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"dakainuanqi",strlen("dakainuanqi"))) //打开暖气,对应离线命令词与播报答复列表序号29功能
{
printf("the meseeage is dakainuanqi 打开暖气rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"guanbinuanq",strlen("guanbinuanq"))) //关闭暖气,对应离线命令词与播报答复列表序号30功能
{
printf("the meseeage is guanbinuanq 关闭暖气rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"dakaicfeng",strlen("dakaicfeng"))) //打开吹风,对应离线命令词与播报答复列表序号31功能
{
printf("the meseeage is dakaicfeng 打开吹风rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"guanbicfeng",strlen("guanbicfeng"))) //关闭吹风,对应离线命令词与播报答复列表序号32功能
{
printf("the meseeage is guanbinuanq 关闭吹风rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"dakaiyingsji",strlen("dakainuanqi"))) //打开饮水机,对应离线命令词与播报答复列表序号33功能
{
printf("the meseeage is dakainuanqi 打开饮水机rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"guanbiyingsji",strlen("guanbiyingsji"))) //关闭饮水机,对应离线命令词与播报答复列表序号34功能
{
printf("the meseeage is guanbiyingsji 关闭饮水机rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"kaishichus",strlen("kaishichus"))) //开始出水,对应离线命令词与播报答复列表序号35功能
{
printf("the meseeage is kaishichus 开始出水rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"tingzchus",strlen("tingzchus"))) //停止出水,对应离线命令词与播报答复列表序号36功能
{
printf("the meseeage is tingzchus 停止出水rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"dakdianfangbao",strlen("dakdianfangbao"))) //打开电饭煲,对应离线命令词与播报答复列表序号37功能
{
printf("the meseeage is dakdianfangbao 打开电饭煲rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"guabdianfangbao",strlen("guabdianfangbao"))) //关闭电饭煲,对应离线命令词与播报答复列表序号38功能
{
printf("the meseeage is guabdianfangbao 关闭电饭煲rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"baowenmos",strlen("baowenmos"))) //保温模式,对应离线命令词与播报答复列表序号39功能
{
printf("the meseeage is baowenmos 保温模式rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"baotangmos",strlen("baotangmos"))) //煲汤模式,对应离线命令词与播报答复列表序号40功能
{
printf("the meseeage is baotangmos 煲汤模式rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"zhufangmos",strlen("zhufangmos"))) //煮饭模式,对应离线命令词与播报答复列表序号41功能
{
printf("the meseeage is zhufangmos 煮饭模式rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"zhuzhoumos",strlen("zhuzhoumos"))) //煮粥模式,对应离线命令词与播报答复列表序号42功能
{
printf("the meseeage is baotangmos 煮粥模式rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"dakaikaiguan",strlen("dakaikaiguan"))) //打开开关,对应离线命令词与播报答复列表序号43功能
{
printf("the meseeage is dakaikaiguan 打开开关rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"guanbikaiguan",strlen("guanbikaiguan"))) //关闭开关,对应离线命令词与播报答复列表序号44功能
{
printf("the meseeage is guanbikaiguan 关闭开关rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"dakaishaj",strlen("dakaishaj"))) //打开杀菌,对应离线命令词与播报答复列表序号45功能
{
printf("the meseeage is dakaishaj 打开杀菌rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"guanbishaj",strlen("guanbishaj"))) //关闭杀菌,对应离线命令词与播报答复列表序号46功能
{
printf("the meseeage is guanbishaj 关闭杀菌rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"dakaihonggan",strlen("dakaihonggan"))) //打开烘干,对应离线命令词与播报答复列表序号47功能
{
printf("the meseeage is dakaihonggan 打开烘干rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"guanbihonggan",strlen("guanbihonggan"))) //关闭烘干,对应离线命令词与播报答复列表序号48功能
{
printf("the meseeage is guanbishaj 关闭烘干rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"dakaichuchou",strlen("dakaichuchou"))) //打开除臭,对应离线命令词与播报答复列表序号49功能
{
printf("the meseeage is dakaichuchou 打开除臭rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"guanbichuchou",strlen("guanbichuchou"))) //关闭除臭,对应离线命令词与播报答复列表序号50功能
{
printf("the meseeage is guanbichuchou 关闭除臭rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"volumeMaxUni",strlen("volumeMaxUni"))) //最大音量,对应离线命令词与播报答复列表序号51功能
{
printf("the meseeage is volumeMaxUni 最大音量rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"volumeMinUni",strlen("volumeMinUni"))) //最小音量,对应离线命令词与播报答复列表序号52功能
{
printf("the meseeage is volumeMinUni 最小音量rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"settiOonehonor",strlen("settiOonehonor"))) //一小时后开机,对应离线命令词与播报答复列表序号53功能
{
printf("the meseeage is settiOonehonor 一小时后开机rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"settiConehonor",strlen("settiConehonor"))) //一小时后关机,对应离线命令词与播报答复列表序号54功能
{
printf("the meseeage is settiConehonor 一小时后关机rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"tempset20",strlen("tempset20"))) //温度设置为20度,对应离线命令词与播报答复列表序号55功能
{
printf("the meseeage is tempset20 温度设置为20度rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"TempSet15",strlen("TempSet15"))) //温度设置为15度,对应离线命令词与播报答复列表序号56功能
{
printf("the meseeage is TempSet15 温度设置为15度rn");
}
else if(!memcmp(Voice_RevPara.Rxbuff 16,"exitUni",strlen("exitUni"))) //退下,对应离线命令词与播报答复列表序号57功能
{
printf("the meseeage is exitUni 退下,再见rn");
}
__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);
}
}
定时器代码编写:
代码语言:javascript复制/**
* @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小飞哥,让我快点遇到优秀的你,然后一起变得更加优秀,加油!!!