实现伟大事业的唯一途径就是热爱自己的事业。如果你还没有找到自己喜欢的事业,继续寻找,不要放弃。跟随你自己 心,总有一天你会发现的。
??你去关灯,你去,你去,。。我去。。。朋友们有没有想在寒冷的天气里关灯却离不开心爱的被子的经历?如果是这样,跟着小飞哥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
供电及时钟:
系统功能框图:
系统功能框图
引脚介绍:
??模块有 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用于调试信息打印PWM控制照明亮度,实现调光功能。串口3用于接收模块串口输出数据和分析数据,定期7用于控制串口数据接收加班。让我们一步一步地介绍实现过程。
2.1 cubemx配置
??参考时钟配置us延迟三种实现方法一文,不再详细介绍。
串口配置:
??串口1配置,主要配置下图红框中的几项,打开接收中断,中断优先级可选择默认,波特率11520。 ??串口2配置,基本与串口1配置相同,也是下图红框中的主要配置。打开接收中断,可以默认选择中断优先级,波特率115200。
串口2配置
定时器配置:
??定时器3配置,定时器3时钟 84M/84=1Mhz,重装载值 500,所以 PWM 频率为 1M/500=2Khz。 ??定时器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负极 |
以上接法仅仅是演示使用,实际使用还是配合继电器使用,能达到实际使用需求,无奈小飞哥手里就一个几年前买的继电器,还坏掉了,这次只能演示用了,后续会画板子开源出来,希望大家多多关注小飞哥公众号。
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,加好友进群一起交流呀!
演示视频见公众号:
源码加群获取哈