# 项目说明
本项目主控采用梁山派,实现智能窗帘开关。
V2版本是采用的表贴接插件。
V3版本是采用排针直插,成本更低,咪头、语音模块、喇叭都是可插拔的,方便重复利用,可怜的穷人啊,只能出此下策省钱了。
整体架构如下:
[https://q0qlud8od33.feishu.cn/mindnotes/MH57be8h2mjE9Onw7jBcDWwcnSb?from=from_copylink](https://q0qlud8od33.feishu.cn/mindnotes/MH57be8h2mjE9Onw7jBcDWwcnSb?from=from_copylink)
![image.png](//image.lceda.cn/pullimage/AP31wS9PdW7R9BS9lmrSTom0rtBhzDpoe3Xt3liD.png)
# 开源协议
Public Domain
# 项目相关功能
## 技术要求
(1)雨滴传感器模块:用于检测雨滴;
(2)光线强度传感器模块:用于检测光线强度;
(3)红外接收模块,用于遥控控制;
(4)步进电机模块:用于窗户窗帘驱动。
## 技术指标
(1)可以通过红外遥控与语音设置是否打开自动模式;
(2)在自动模式下,当光照强度很高时,自动展开窗帘;当光照强度很低时,自动关闭窗帘;
(3)在自动模式下,当检测到大量雨滴时,自动展开窗帘,此优先级比光照检测高;
(4)在任何时候,可以通过红外遥控或语音命令展开与关闭窗帘,并关闭自动模式;
(5)主控芯片接收到数据后,驱动电机运转,现实智能窗帘窗户系统。
# 项目属性
本项目基于立创梁山派开发板,参考相关开源模块整理:
https://lceda001.feishu.cn/wiki/Fv2ewkfwviJ6FOk39DBcg21znod
为本人首次公开的学习项目,项目未曾在别的比赛中获奖。
# 项目进度
8/15-8/31 确定方案
9/1-9/14 原理图设计、PCB绘制
9/15-9/28 开发环境搭建、驱动编写
9/29-10/6 焊接与调试
10/6- 整理项目与撰写文档
# 电路设计说明
## 电源设计
采用外部排针,可以即插即用。可以接通用的电池接口。电路设计如下:
![image.png](//image.lceda.cn/pullimage/bNBMXAzfbkXPQmm6Y5RqF25zzR3O1nnXBGUfUVaf.png)
## 红外接口
红外线接收头感应到有红外光就输出低电平,没有感应到红外光就输出高电平。因此,我们只要检测OUT端,是否输出低电平,就可以知道是否有接收到红外数据。
这里选择接入PF7,没有什么特殊要求,使用普通的GPIO即可。电路设计如下:
![image.png](//image.lceda.cn/pullimage/d0tlMh0irnFzCbve287wnQaV0TkxR2mTf1H4CSGW.png)
## 语音识别
HLK-V20是海凌科电子针对大量纯离线控制场景和产品推出的高性能纯离线语音识别模块,可广泛且快速的应用于智能家居、各类智能小家电、86盒、玩具、灯具、工业、 医疗、物联网、汽车、安防与照明等需要语音操控的产品。 HLK-V20支持150条本地指令离线识别,可自由定制唤醒词、命令词与应答播报词,具有丰富的外围接口。电路设计如下:
![image.png](//image.lceda.cn/pullimage/pCI2OZvXGkZMLzI7iyBSgpykSaE9GdCaCeFZ6vVB.png)
## 步进电机
采用二相四线式步进电机,二相指的是有两个线圈,四线指的是每一个线圈有两根线。其中A+与A-为一相,B+与B-为一相。
四拍方式的转动顺序:【A+】->【B+】->【A-】->【B-】。
八拍方式的转动顺序:【A+】->【A+B+】->【B+】->【B+A-】->【A-】->【A-B-】->【B-】->【B-A+】。
电路设计如下:
![image.png](//image.lceda.cn/pullimage/AppqdjQc22M94215NHpAsRpObfPdYdX1nI0icelk.png)
## 光强检测电路
光照检测功能是通过光敏电阻进行识别。光敏电阻是一种特殊的电阻器,它随着光照强度的升高,电阻值会迅速降低,其在无光照时,几乎呈高阻状态,因此暗时电阻很大。采用的主板上的ADC,选取PF6管脚进行检测。电路设计如下:
![image.png](//image.lceda.cn/pullimage/vQM5xgBDTV3wu9mUHrrF1gJOq4b7qhPBP7edOxQY.png)
## 雨滴检测
雨滴传感器常见的工作原理是通过检测水滴的导电性来判断是否下雨。它是利用两个电极之间的电导性变化来测量水滴的存在。这两个电极之间会有一个空气间隙,正常状态下是断路状态。当水滴接触到电极上时,水滴的导电性会导致电流通过水滴形成电流回路,从而改变电极之间的电阻值。通过测量电阻值的变化,就可以判断是否有水滴存在。采用的主板上的ADC,选取PF8管脚进行检测。电路设计如下:
![image.png](//image.lceda.cn/pullimage/90LxOfPjByZBfmXdq5UMGsPrs1zp9ALrKVmZqVTV.png)
雨滴检测板,自己画的PCB。做了两个版本,这里展示的是较大的这个。
![image.png](//image.lceda.cn/pullimage/jp4rQsJm4DekmNMRo01ZV19bnEQREhy5t1S2lP0X.png)
## 炫彩指示灯
添加指示灯,指示窗帘开关正在运行。电路设计如下:
![image.png](//image.lceda.cn/pullimage/OuIX8msb2Asu0GYXDGln7Ko4QWwhZjU0sJoyX6RE.png)
# 程序设计与调试
## 雨滴与光照采集
代码实现
```
#include "bsp_adc.h"
void raindrop_and_light_config(void)
{
//使能引脚时钟
rcu_periph_clock_enable(BSP_RAINDROP_GPIO_RCU);
rcu_periph_clock_enable(BSP_LIGHT_GPIO_RCU);
//使能ADC时钟
rcu_periph_clock_enable(BSP_ADC_RCU);
//配置ADC时钟
adc_clock_config(ADC_ADCCK_PCLK2_DIV4);
//配置引脚为模拟输入模式
gpio_mode_set(BSP_RAINDROP_GPIO_PORT, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, BSP_RAINDROP_GPIO_PIN);
gpio_mode_set(BSP_LIGHT_GPIO_PORT, GPIO_MODE_ANALOG, GPIO_PUPD_NONE, BSP_LIGHT_GPIO_PIN);
//配置ADC为独立模式
adc_sync_mode_config(ADC_SYNC_MODE_INDEPENDENT);
//使能扫描模式
adc_special_function_config(BSP_ADC, ADC_SCAN_MODE, ENABLE);
//数据右对齐
adc_data_alignment_config(BSP_ADC, ADC_DATAALIGN_RIGHT);
//ADC0设置为12位分辨率
adc_resolution_config(BSP_ADC, ADC_RESOLUTION_12B);
//ADC0设置为规则组 一共使用 1 个通道
adc_channel_length_config(BSP_ADC,ADC_REGULAR_CHANNEL, 1);
//ADC外部触发禁用, 即只能使用软件触发
adc_external_trigger_config(BSP_ADC, ADC_REGULAR_CHANNEL, EXTERNAL_TRIGGER_DISABLE);
//ADC0使能
adc_enable(BSP_ADC);
//开启ADC自校准
adc_calibration_enable(BSP_ADC);
}
/**********************************************************
* 函 数 名 称:Get_ADC_Value
* 函 数 功 能:读取ADC值
* 传 入 参 数:ADC_CHANNEL_x=要采集的通道
* 函 数 返 回:测量到的值
* 作 者:LC
* 备 注:无
**********************************************************/
unsigned int Get_ADC_Value(uint8_t ADC_CHANNEL_x)
{
unsigned int adc_value = 0;
//设置采集通道
adc_regular_channel_config(BSP_ADC, 0, ADC_CHANNEL_x, ADC_SAMPLETIME_15);
//开始软件转换
adc_software_trigger_enable(BSP_ADC, ADC_REGULAR_CHANNEL);
//等待 ADC 采样完成
while ( adc_flag_get(BSP_ADC, ADC_FLAG_EOC) == RESET )
{
;
}
//读取采样值
adc_value = adc_regular_data_read(BSP_ADC);
//返回采样值
return adc_value;
}
```
调试效果如下:
![image.png](//image.lceda.cn/pullimage/b1tR3J7W02YGPNacDDDDuRFHTMzLI3CphO005jqC.png)
## 电机驱动
实现代码
```
/********************************************************************************
* 文 件 名: bsp_stepper_motor.c
* 版 本 号: 初版
* 修改作者: LC
* 修改日期: 2023年04月06日
* 功能介绍:
******************************************************************************
* 注意事项:
*********************************************************************************/
#include "bsp_stepper_motor.h"
#include "systick.h"
#include "stdio.h"
#include "math.h"
uint8_t phasecw[8] = {0x08, 0x0c, 0x04, 0x06, 0x02, 0x03, 0x01, 0x09};
uint8_t phaseccw[8] = {0x09, 0x01, 0x03, 0x02, 0x06, 0x04, 0x0c, 0x08};
uint8_t motor_cw_flag = 0;
uint8_t motor_ccw_flag = 0;
uint16_t step_count = 0;
/******************************************************************
* 函 数 名 称:stepper_motor_timer_config
* 函 数 说 明:步进电机脉冲更新频率定时器初始化
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:设置为2ms,即每2ms更新一次脉冲
******************************************************************/
void stepper_motor_timer_config(void)
{
/* 一个周期的时间T = 1/f, 定时时间time = T * 周期
设预分频值位pre,周期位per
time = (pre + 1) * (per + 1) / psc_clk
*/
timer_parameter_struct timere_initpara; // 定义定时器结构体
/* 开启时钟 */
rcu_periph_clock_enable(RCU_TIMER5); // 开启定时器时钟
/* CK_TIMERx = 4 x CK_APB1 = 4x50M = 200MHZ */
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4); // 配置定时器时钟
timer_deinit(TIMER5); // 复位定时器
/* 配置定时器参数 */
timere_initpara.prescaler = 2000-1; // 时钟预分频值 0-65535
timere_initpara.alignedmode = TIMER_COUNTER_EDGE; // 边缘对齐
timere_initpara.counterdirection = TIMER_COUNTER_UP; // 向上计数
timere_initpara.period = 200-1; // 周期
timere_initpara.clockdivision = TIMER_CKDIV_DIV1; // 分频因子
timere_initpara.repetitioncounter = 0; // 重复计数器 0-255
timer_init(TIMER5,&timere_initpara); // 初始化定时器
/* 配置中断优先级 */
nvic_irq_enable(TIMER5_DAC_IRQn,1,2); // 设置中断优先级为 3,2
/* 使能中断 */
timer_interrupt_enable(TIMER5,TIMER_INT_UP); // 使能更新事件中断
/* 使能定时器 */
timer_enable(TIMER5);
}
/******************************************************************
* 函 数 名 称:stepper_motor_config
* 函 数 说 明:对步进电机引脚初始化
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void stepper_motor_config(void)
{
rcu_periph_clock_enable(AP_RCU); // 开启时钟
rcu_periph_clock_enable(AM_RCU); // 开启时钟
rcu_periph_clock_enable(BP_RCU); // 开启时钟
rcu_periph_clock_enable(BM_RCU); // 开启时钟
/* 配置A+推挽输出模式 上拉模式 */
gpio_mode_set(AP_PORT,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,AP_PIN);
gpio_output_options_set(AP_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,AP_PIN);
/* 配置A-推挽输出模式 上拉模式 */
gpio_mode_set(AM_PORT,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,AM_PIN);
gpio_output_options_set(AM_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,AM_PIN);
/* 配置B+推挽输出模式 上拉模式 */
gpio_mode_set(BP_PORT,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,BP_PIN);
gpio_output_options_set(BP_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BP_PIN);
/* 配置B-推挽输出模式 上拉模式 */
gpio_mode_set(BM_PORT,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,BM_PIN);
gpio_output_options_set(BM_PORT,GPIO_OTYPE_PP,GPIO_OSPEED_50MHZ,BM_PIN);
AP(0);
BP(0);
AM(0);
BM(0);
}
//顺时针,转动顺序:a+ b+ a- b-
void motor_cw(void)
{
static uint8_t i=0;
//开启了顺时针动作
if( motor_cw_flag == 1 )
{
AP ( ( phasecw[i] >> 3 ) & 0x01 );
BP ( ( phasecw[i] >> 2 ) & 0x01 );
AM ( ( phasecw[i] >> 1 ) & 0x01 );
BM ( ( phasecw[i] >> 0 ) & 0x01 );
//拍数增加
i = ( i + 1 ) % 8;
//记录当前步数
step_count++;
}
}
//逆时针,转动顺序:b- a- b+ a+
void motor_ccw( void )
{
static uint8_t i=0;
//如果开启了逆时针动作
if( motor_ccw_flag == 1 )
{
AP ( ( phaseccw[i] >> 3 ) & 0x01 );
BP ( ( phaseccw[i] >> 2 ) & 0x01 );
AM ( ( phaseccw[i] >> 1 ) & 0x01 );
BM ( ( phaseccw[i] >> 0 ) & 0x01 );
i=(i+1)%8;
//记录当前步数
if( step_count <= 1 ) step_count = 1;
step_count--;
}
}
//获取当前行进步数
uint16_t get_step_count(void)
{
return step_count;
}
//设置当前行进步数
void set_step_count(uint16_t num)
{
step_count = num;
}
/************************************************
函数名称 : BSP_TIMER_IRQHandler
功 能 : 基本定时器中断服务函数
参 数 : 无
返 回 值 : 无
作 者 : LC
*************************************************/
void TIMER5_DAC_IRQHandler(void)
{
/* 这里是定时器中断 */
if(timer_interrupt_flag_get(TIMER5,TIMER_INT_FLAG_UP) == SET)
{
timer_interrupt_flag_clear(TIMER5,TIMER_INT_FLAG_UP); // 清除中断标志位
//顺时针旋转
motor_cw();
//逆时针旋转
motor_ccw();
}
}
```
电机实现转动:
[https://www.bilibili.com/video/BV1Pw41117Hf/?vd_source=24f1befd6441a33d7b240715cb07c7b5](https://www.bilibili.com/video/BV1Pw41117Hf/?vd_source=24f1befd6441a33d7b240715cb07c7b5)
## 红外驱动
实现代码
```
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: LCKFB
* 修改日期: 2023年06月12日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#include "bsp_ir_receiver.h"
#include "bsp_usart.h"
#include "stdio.h"
#include "systick.h"
typedef struct INFRARED_DATA{
uint8_t AddressCode; //地址码
uint8_t AddressInverseCode; //地址反码
uint8_t CommandCode; //命令码
uint8_t CommandInverseCode; //命令反码
}_INFRARED_DATA_STRUCT_;
_INFRARED_DATA_STRUCT_ InfraredData;
//红外引脚初始化
void infrared_goio_config(void)
{
//开启引脚时钟
rcu_periph_clock_enable(IR_RCU);
//开启系统配置时钟
rcu_periph_clock_enable(RCU_SYSCFG);
//配置引脚为上拉输入模式
gpio_mode_set(IR_PORT, GPIO_MODE_INPUT, GPIO_PUPD_PULLUP, IR_PIN);
/* 使能NVIC中断 中断分组为2位抢占优先级,2位子优先级 */
nvic_irq_enable(EXTI_IRQ,2U,2U); // 抢占优先级2,子优先级2
/* 连接中断线到GPIO */
syscfg_exti_line_config(EXTI_SOURCE_PORT,EXTI_SOURCE_PIN);
/* 初始化中断线下降沿触发 */
exti_init(EXTI_X,EXTI_INTERRUPT,EXTI_TRIG_FALLING);
/* 使能中断 */
exti_interrupt_enable(EXTI_X);
/* 清除中断标志位 */
exti_interrupt_flag_clear(EXTI_X);
}
//获取红外低电平时间
//以微秒us作为时间参考
void get_infrared_low_time( uint32_t *low_time )
{
uint32_t time_val = 0;
while( gpio_input_bit_get(IR_PORT, IR_PIN) == 0 )
{
if( time_val>= 500 )
{
*low_time = time_val;
return;
}
delay_1us(20);
time_val++;
}
*low_time = time_val;
}
//获取红外高电平时间
//以微秒us作为时间参考
void get_infrared_high_time(uint32_t *high_time)
{
uint32_t time_val = 0;
while( gpio_input_bit_get(IR_PORT, IR_PIN) == 1 )
{
if( time_val >= 250 )
{
*high_time = time_val;
return;
}
delay_1us(20);
time_val++;
}
*high_time = time_val;
}
/******************************************************************
* 函 数 名 称:guide_and_repeat_code_judgment
* 函 数 说 明:引导 和 重复 码 判断
* 函 数 形 参:无
* 函 数 返 回:1:不是引导码 2:重复码 0:引导码
* 作 者:LC
* 备 注:以20微秒us作为时间参考
引导码:由一个 9ms 的低电平和一个 4.5ms 的高电平组成
重复码:由一个 9ms 的低电平和一个 2.5ms 的高电平组成
******************************************************************/
uint8_t guide_and_repeat_code_judgment(void)
{
uint32_t out_time=0;
get_infrared_low_time(&out_time);
//time>10ms time <8ms
if((out_time > 500) || (out_time < 400))
{
return 1;
}
get_infrared_high_time(&out_time);
// x>5ms 或者 x<2ms
if((out_time > 250) || (out_time < 100))
{
return 1;
}
//如果是重复码 2ms < time < 3ms
if((out_time > 100) && (out_time < 150))
{
return 2;
}
return 0;
}
//红外数据是否正确判断
uint8_t infrared_data_true_judgment(uint8_t *value)
{
//判断地址码是否正确
if( value[0] != (uint8_t)(~value[1]) ) return 0;
//判断命令码是否正确
if( value[2] != (uint8_t)(~value[3]) ) return 1;
printf("%x %x %x %x\r\n",value[0],value[1],value[2],value[3]);
//保存正确数据
InfraredData.AddressCode = value[0];
InfraredData.AddressInverseCode = value[1];
InfraredData.CommandCode = value[2];
InfraredData.CommandInverseCode = value[3];
}
//接收红外数据
void receiving_infrared_data(void)
{
uint16_t group_num = 0,data_num = 0;
uint32_t time=0;
uint8_t bit_data = 0;
uint8_t ir_value[5] = {0};
uint8_t guide_and_repeat_code = 0;
//等待引导码
guide_and_repeat_code = guide_and_repeat_code_judgment();
//如果不是引导码则结束解析
if( guide_and_repeat_code == 1 )
{
printf("err\r\n");
return;
}
//共有4组数据
//地址码+地址反码+命令码+命令反码
for(group_num = 0; group_num < 4; group_num++ )
{
//接收一组8位的数据
for( data_num = 0; data_num < 8; data_num++ )
{
//接收低电平
get_infrared_low_time(&time);
//如果不在0.56ms内的低电平,数据错误
if((time > 60) || (time < 20))
{
return ;
}
time = 0;
//接收高电平
get_infrared_high_time(&time);
//如果是在1200us<t<2000us范围内则判断为1
if((time >=60) && (time < 100))
{
bit_data = 1;
}
//如果是在200us<t<1000us范围内则判断为0
else if((time >=10) && (time < 50))
{
bit_data = 0;
}
//groupNum表示第几组数据
ir_value[ group_num ] <<= 1;
//接收的第1个数为高电平;在第二个for循环中,数据会向右移8次
ir_value[ group_num ] |= bit_data;
//用完时间要重新赋值
time=0;
}
}
//判断数据是否正确,正确则保存数据
infrared_data_true_judgment(ir_value);
}
//获取红外发送过来的命令
uint8_t get_infrared_command(void)
{
return InfraredData.CommandCode;
}
//清除红外发送过来的数据
void clear_infrared_command(void)
{
InfraredData.CommandCode = 0x00;
}
void EXTI5_9_IRQHandler(void)
{
if(exti_interrupt_flag_get(EXTI_X) == SET) // 中断标志位为1
{
if(gpio_input_bit_get(IR_PORT,IR_PIN) == RESET) // 如果是低电平
{
//接收一次红外数据
receiving_infrared_data();
}
exti_interrupt_flag_clear(EXTI_X); // 清中断标志位
}
}
```
调试红外接收验证结果
[https://www.bilibili.com/video/BV1MB4y1Z7mM/?vd_source=24f1befd6441a33d7b240715cb07c7b5](https://www.bilibili.com/video/BV1MB4y1Z7mM/?vd_source=24f1befd6441a33d7b240715cb07c7b5)
![image.png](//image.lceda.cn/pullimage/iY2K9FcbhTvDdrbyJRba81TEvJoSykI6dRIh7WBc.png)
## 语音模块实现
这里需要注意的是:
![image.png](//image.lceda.cn/pullimage/pAfTEnE2DO2qqFkhZgrHROGY9RiOVxBxwQZnBflA.png)
这个注意事项坑了我。
我的下载步骤如下:
1-**连接B6,B7,GND;**
**2-点击下载;**
**3-给模块通电。**
**必须以上三步骤,否则无法下载。通电的瞬间会下载,并且有下载进度提示。**
代码驱动实现
```
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: LCKFB
* 修改日期: 2023年06月12日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#include "bsp_voice.h"
#include "stdio.h"
#include "string.h"
unsigned char hlk_rx_buff[HLK_RX_LEN_MAX];
unsigned char hlk_rx_flag = 0;
unsigned char hlk_rx_len = 0;
unsigned char rx_data = 0;
unsigned char rx_flag = 0;
/************************************************************
* 函数名称:hlk_usart_init
* 函数说明:语音识别模块的初始化
* 型 参:bund=串口波特率
* 返 回 值:无
* 备 注:波特率根据你设置的内容来决定
*************************************************************/
void hlk_usart_init(unsigned int bund)
{
/* 使能 HLK_USART 的时钟 */
rcu_periph_clock_enable(RCU_HLK_USART);
/* 使能时钟 */
rcu_periph_clock_enable(RCU_HLK_TX);
rcu_periph_clock_enable(RCU_HLK_RX);
/* 配置引脚为复用功能 */
gpio_af_set(PORT_HLK_TX, BSP_HLK_AF, GPIO_HLK_TX);
/* 配置引脚为复用功能 */
gpio_af_set(PORT_HLK_RX, BSP_HLK_AF, GPIO_HLK_RX);
/* 配置TX引脚为复用上拉模式 */
gpio_mode_set(PORT_HLK_TX, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_HLK_TX);
/* 配置RX引脚为复用上拉模式 */
gpio_mode_set(PORT_HLK_RX, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_HLK_RX);
/* 配置PA2引脚为为输出模式 */
gpio_output_options_set(PORT_HLK_TX, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_HLK_TX);
/* 配置PA3引脚为为输出模式 */
gpio_output_options_set(PORT_HLK_RX, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_HLK_RX);
/* 设置HLK_USART的波特率为115200 */
usart_baudrate_set(HLK_USART, bund);
/* 设置HLK_USART的校验位为无 */
usart_parity_config(HLK_USART, USART_PM_NONE);
/* 设置HLK_USART的数据位为8位 */
usart_word_length_set(HLK_USART, USART_WL_8BIT);
/* 设置HLK_USART的停止位为1位 */
usart_stop_bit_set(HLK_USART, USART_STB_1BIT);
/* 使能串口1 */
usart_enable(HLK_USART);
/* 使能HLK_USART传输 */
usart_transmit_config(HLK_USART, USART_TRANSMIT_ENABLE);
/* 使能HLK_USART接收 */
usart_receive_config(HLK_USART, USART_RECEIVE_ENABLE);
/* 使能HLK_USART接收中断标志位 */
usart_interrupt_enable(HLK_USART, USART_INT_RBNE);
/* 使能HLK_USART空闲中断标志位 */
usart_interrupt_enable(HLK_USART, USART_INT_IDLE);
/* 配置中断优先级 */
nvic_irq_enable(HLK_USART_IRQ, 2, 2);
}
/******************************************************************
* 函 数 名 称:hlk_usart_send_bit
* 函 数 说 明:向HLK语音识别模块发送单个字符
* 函 数 形 参:ch=字符
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void hlk_usart_send_bit(unsigned char ch)
{
//发送字符
usart_data_transmit(HLK_USART, ch);
// 等待发送数据缓冲区标志自动置位
while(RESET == usart_flag_get(HLK_USART, USART_FLAG_TBE) );
}
/******************************************************************
* 函 数 名 称:hlk_usart_send_string
* 函 数 说 明:向HLK模块发送字符串
* 函 数 形 参:str=发送的字符串
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void hlk_usart_send_string(unsigned char *str)
{
while( str && *str ) // 地址为空或者值为空跳出
{
hlk_usart_send_bit(*str++);
}
}
//清除串口接收的数据
/******************************************************************
* 函 数 名 称:clear_hlk_rx_buff
* 函 数 说 明:清除HLK发过来的数据
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void clear_hlk_rx_buff(void)
{
unsigned char i = HLK_RX_LEN_MAX-1;
while(i)
{
hlk_rx_buff[i--] = 0;
}
hlk_rx_len = 0;
hlk_rx_flag = 0;
}
/**********************************************************
* 函 数 名 称:voice_anakysis_data
* 函 数 功 能:解析语音识别模块发送过来的数据
* 传 入 参 数:无
* 函 数 返 回:1=接收到语音数据 0=没有接收到语音数据
* 作 者:LC
* 备 注:假设 AA 03 55 则 printf("automatic mode\r\n");
**********************************************************/
unsigned char voice_anakysis_data(void)
{
unsigned char ret = 0;
if( rx_flag == 1 )//接收到语音命令
{
rx_flag = 0;
switch( rx_data )//根据语音命令确定对应的动作
{
case 0x01://开窗帘
printf("open curtain\r\n");
break;
case 0x02://关窗帘
printf("close curtain\r\n");
break;
case 0x03://自动模式
printf("automatic mode\r\n");
break;
case 0x04://手动模式
printf("manual mode\r\n");
break;
}
ret = 1;
//清除当前数据
clear_hlk_rx_buff();
}
return ret;
}
/******************************************************************
* 函 数 名 称:HLK_USART_IRQHandler
* 函 数 说 明:连接HLK的串口中断服务函数
* 函 数 形 参:无
* 函 数 返 回:无
* 作 者:LC
* 备 注:无
******************************************************************/
void HLK_USART_IRQHandler(void)
{
if(usart_interrupt_flag_get(HLK_USART,USART_INT_FLAG_RBNE) != RESET) // 接收缓冲区不为空
{
//接收数据
hlk_rx_buff[ hlk_rx_len ] = usart_data_receive(HLK_USART);
#if DEBUG
//测试,查看接收到了什么数据
printf("%c", hlk_rx_buff[ hlk_rx_len ]);
#endif
//0XAA X 0X55
if( hlk_rx_buff[hlk_rx_len] == 0X55 )//接收到帧尾
{
if( hlk_rx_buff[hlk_rx_len-2] == 0XAA)//接收到帧头,确定数据格式正确
{
rx_data = hlk_rx_buff[hlk_rx_len-1];//接收数据
rx_flag = 1;
}
}
//接收长度限制
hlk_rx_len = ( hlk_rx_len + 1 ) % HLK_RX_LEN_MAX;
}
if(usart_interrupt_flag_get(HLK_USART,USART_INT_FLAG_IDLE) == SET) // 检测到空闲中断
{
usart_data_receive(HLK_USART); // 必须要读,读出来的值不能要
hlk_rx_buff[hlk_rx_len] = '\0'; //字符串结尾补 '\0'
hlk_rx_flag = 1; // 接收完成
}
}
```
调试不同语音输出结果
[https://www\.bilibili\.com/video/BV1Z84y117Xz/?pop\_share=1&vd\_source=24f1befd6441a33d7b240715cb07c7b5](https://www.bilibili.com/video/BV1Z84y117Xz/?pop_share=1&vd_source=24f1befd6441a33d7b240715cb07c7b5)
![image.png](//image.lceda.cn/pullimage/SPaqiYBzIsz4uPCn4zcjZPWVCQhhR7WN8emLsG0G.png)
## 系统实现
main函数实现
```
/********************************************************************************
* 测试硬件:立创·梁山派开发板GD32F470ZGT6 使用主频200Mhz 晶振25Mhz
* 版 本 号: V1.0
* 修改作者: LCKFB
* 修改日期: 2023年08月04日
* 功能介绍:
******************************************************************************
* 梁山派软硬件资料与相关扩展板软硬件资料官网全部开源
* 开发板官网:www.lckfb.com
* 技术支持常驻论坛,任何技术问题欢迎随时交流学习
* 立创论坛:club.szlcsc.com
* 其余模块移植手册:https://dri8c0qdfb.feishu.cn/docx/EGRVdxunnohkrNxItYTcrwAnnHe
* 关注bilibili账号:【立创开发板】,掌握我们的最新动态!
* 不靠卖板赚钱,以培养中国工程师为己任
*********************************************************************************/
#include "gd32f4xx.h"
#include "systick.h"
#include <stdio.h>
#include "bsp_usart.h"
#include "bsp_adc.h"
#include "bsp_stepper_motor.h"
#include "bsp_ir_receiver.h"
#include "bsp_ir_receiver.h"
#include "bsp_voice.h"
#include "bsp_mode_control.h"
int main(void)
{
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2); // 优先级分组
//滴答定时器初始化 1us
systick_config();
//串口0初始化 调试
usart_gpio_config(115200U);
//语音识别模块引脚初始化
hlk_usart_init(9600U);
//雨滴与光照识别引脚初始化
raindrop_and_light_config();
//红外接收引脚初始化
infrared_goio_config();
//步进电机初始化
stepper_motor_config();
stepper_motor_timer_config();
//步进电机复位
curtain_reset();
while(1)
{
//语音命令识别操作:窗帘控制与模式切换
voice_anakysis_data();
//红外命令识别操作:窗帘控制与模式切换
infrared_command_judgment();
//模式选择与对应模式的操作
mode_select();
}
}
```
功能验证:
自动模式下光照开关:
[https://www.bilibili.com/video/BV1LC4y1572N/?vd_source=24f1befd6441a33d7b240715cb07c7b5](https://www.bilibili.com/video/BV1LC4y1572N/?vd_source=24f1befd6441a33d7b240715cb07c7b5)
![image.png](//image.lceda.cn/pullimage/SzyGbHfmuy7HkLo0ROdmAay62ayzC8osqX3icnki.png)
自动雨量控制开关验证
![image.png](//image.lceda.cn/pullimage/MyWKWOjIV8jTCQTnBwI7YehxFXJG5fTji8HZ9zG5.png)
语音控制四种状态
[https://www\.bilibili\.com/video/BV1Z84y117Xz/?pop\_share=1&vd\_source=24f1befd6441a33d7b240715cb07c7b5](https://www.bilibili.com/video/BV1Z84y117Xz/?pop_share=1&vd_source=24f1befd6441a33d7b240715cb07c7b5)
![image.png](//image.lceda.cn/pullimage/PQJmdSogPFlJ9zTc20NM9yX2XIU4sBpiCQjNwVIn.png)
红外控制四种模式
[https://www.bilibili.com/video/BV1MB4y1Z7mM/?vd_source=24f1befd6441a33d7b240715cb07c7b5](https://www.bilibili.com/video/BV1MB4y1Z7mM/?vd_source=24f1befd6441a33d7b240715cb07c7b5)
![image.png](//image.lceda.cn/pullimage/HS3jWanYGkfuuQEefY6qmNNY3woJ2CELi0CtHw9b.png)
通过串口打印数据,以及电机转动情况,验证功能OK。