STM32智能家居语音系统

简介

基于STM32构建的智能家居语音控制系统,采用ESP8266(01s)作为关键的WiFi模块。这一系统通过机智云开发平台实现与ESP8266的高效通信,遵循平台规范的协议,确保数据的可靠传输。系统支持WIFI_AIRLINK_MODE,实现一键智能配网,简化用户的设置过程。

通过专用的手机APP,用户可以实时监测家中的环境参数,包括温度、湿度、气体浓度和光照强度等。此外,用户还可以便捷地控制家中的灯光与风扇开关,将智能家居的便捷性与舒适性完美结合,提升了现代居住空间的智能化体验。该系统不仅提升了居家生活的便利性,更通过语音控制功能赋予用户更为人性化的交互方式,助力构建更加智能、环保的家庭环境。

功能演示

stm32的智能家居语音系统

材料

1、STM32F103C8T6最小系统板。

2、ESP8266 01s 模块。

3、SNR8016智能语音模块。

4、DHT11温湿度模块。

5、0.96寸OLED屏幕(I2C通信)。

6、MQ-2烟雾气敏传感器。

7、5v继电器。

8、GY-30数字光照传感器。

9、步进电机28BYJ48+ULN2003驱动板。

10、200欧、4.7K电阻(0603封装)。

11、LED灯(绿色、蓝色、黄色)。

12、蜂鸣器。

13、USB 5V供电模块。

14、三极管、开关二极管。

原理图

实现流程

main函数:

int main ( void )

{

/* 系统时钟树配置 */

RCC_Configuration();

/* 中断系统配置 */

NVIC_Configuration();

/* 初始化硬件延时, 使用滴答定时器完成 */

delay_init();

/* 板级初始化 */

app_BspInit( &SysParamHandle );

Key2_long_press_down_handle();

/* 主循环 */

while ( 1 )

{

app_Process( &SysParamHandle );

delay_ms(1000);

}

}

函数讲解

1、初始化系统时钟,配置时钟树,系统时钟配置为72MHZ。

2、配置NVIC中断为 NVIC_PriorityGroup_1,1 bits for pre-emption priority 3 bits for subpriority */。

3、初始化硬件延时,配置滴答定时器的时钟,实现毫秒延时功能。

4、板级初始化,初始化各个外设。

5、主循环:app_Process( &SysParamHandle );处理事件和任务。

总结:这里最关键的有两个函数, app_BspInit( &SysParamHandle );以及app_Process( &SysParamHandle );我们围绕这两个分析一下。

SysParam_t结构体

typedef struct SysParamType

{

ePageIDType PageID;

eModeType eMode;

u8 u8EventReg;

u8 u8GizwistCode;

u8 u8KeyCode;

u8 u8TrashStatusBits;
u8 u8CurtainFlg;
u16 u16Lightness;
float fMqValue;

bool blWarn;

u32 u32SensorTim;
u32 u32LcdUpdateTim;
u32 u32SyncDataTim;
u32 u32CycleWarnTim;

eMotorDutyCycle eMotorStat;

DHT11_Data_TypeDef *psDHT11DataHandle;

LD3322Handle_t *psLdHandle;

sKeyHandle_t *p_keyhandle;
dataPoint_t *p_DataPoint;

}SysParam_t;

这个结构体记录的所有传感器的变量,每一个都十分的重要。

TIME_HandleType;结构体

typedef struct{

uint16_t ucSupportCnt; /*!< 注册的定时回调函数的数目 */
TIME_FunType tCallFun[SUPPORT_FUN_MAX_NUM]; /*!< 注册的定时回调函数 */
uint32_t ui1msTic; /*!< 1ms定时 */
uint32_t uimsDelayTic; /*!< 1ms定时 */

} TIME_HandleType;

这个结构体记录了注册进入定时器回调函数的数目,超时事件,以及对应的回调函数。

app_BspInit( &SysParamHandle );板级初始化

void app_BspInit( SysParam_t *p_SysParamHandle )

{

/* debug log uart config */

DEBUG_UART_Config( );

DEBUG_LOG("Bsp init\n");

/* 上电延时,等待各个模块运行稳定 */

delay_ms( 200 );

/* 初始化定时器3, 中断频率 1000hz */

FML_TIME_Init();

/*****************************************************************************************************************/

/* *************************注册系统时间片处理函数************************************************************** */

/*****************************************************************************************************************/

FML_TIME_Register(Sys_timer_handle, 1); //系统时间片,1tick = 1ms

FML_TIME_Start(Sys_timer_handle); //开启定时

/*****************************************************************************************************************/

/* *************************** 初始化IO设备 ******************************************************************** */

/*****************************************************************************************************************/

/* 配置按键引脚 */

//Key_GPIO_Config(p_SysParamHandle->p_keyhandle);

/* LED Fan IO 初始化 */

LED_Init( );

BEEP_Init();

Moto_Init();

/*****************************************************************************************************************/

/* ************************* 主模块开机运行检测 ***************************************************************** */

/******************************************************************************************************************/

/* dht11 初始化 */

DHT11_Init();

/* 语音模块初始化 */

ld3322_init( );

RegisterLd3322(p_SysParamHandle->psLdHandle, CmdList, CMD_NUM); /*!< 注册指令参数表 */

Switch_GPIO_Config( );

/* 机智云SDK初始化 */

Gizwits_Init();

/* 0.96OLED初始化 */

OLED_Init();

BH1750_Init();

ADC_Sensor_Init();

/*!< 延时确保初始页面错误 */

delay_ms(300);

/* OLED显示初始画面 */

OLED_InitPage( );

}

函数讲解:

1、DEBUG_UART_Config以及DEBUG_LOG,这些是串口3发出的调试信息,我们这里忽略不理。

2、 FML_TIME_Init();初始化定时器2, 中断频率 1000hz,1ms进一次中断服务函数。这个中断大有用处,类似FreeRTOS的时间片轮转,每过1ms进行执行一次任务,这里首先进入中断,会判断是否有回调函数注册,有的话,就每毫秒去执行他一次,这里用于定时,因为传感器数据如果放在while里,不断的读,会导致读的太快,这很容易出现问题,并且也没有这个必要。

3、注册Sys_timer_handle回调函数,每过一毫秒执行一次。

static void Sys_timer_handle( void )

{

u32SysTick++;

static u32 warnLed_tick = 0;

/* 键盘时间片 */

if (SysParamHandle.p_keyhandle->u32_keyscan_time > 0)

{

SysParamHandle.p_keyhandle->u32_keyscan_time--;

}

if (SysParamHandle.u32SensorTim > 0)

{

SysParamHandle.u32SensorTim--;

}

if (SysParamHandle.u32LcdUpdateTim > 0)

{

SysParamHandle.u32LcdUpdateTim--;

}

if (SysParamHandle.u32SyncDataTim > 0)

{

SysParamHandle.u32SyncDataTim--;

}

if (SysParamHandle.u32CycleWarnTim > 0)

{

SysParamHandle.u32CycleWarnTim--;

}

if (u32SysTick - warnLed_tick > 500)

{

warnLed_tick = u32SysTick;

if (SysParamHandle.blWarn == TRUE)

{

WARN_LED_FLASH;

}

else

{

WARN_LED_OFF;

}

}

/* 继电器 */

gizTimerMs();

}

4、各个基本传感器外设的初始化。

5、RegisterLd3322(p_SysParamHandle->psLdHandle, CmdList, CMD_NUM); /*!< 注册指令参数表 */,需要把CmdList里面的指令以及回调函数注册到p_SysParamHandle结构体里面的psLdHandle,语音模块在发送对应指令给STM32之后,可以对应特定的指令执行对应的回调函数,比如:开灯/关灯。

/* 指令列表 */

CmdHandle_t CmdList[CMD_NUM] =

{

"开灯", TurnONLight,

"关灯", TurnOffLight,

"开风扇", TurnOnAirCondit,

"关风扇", TurnOffAirCondit,

"开窗帘", OpenCurtain,

"关窗帘", CloseCurtain,

"自动模式", SetAutoMode,

"手动模式", SetManualMode,

"热点配网", Key2_short_press_up_handle,

"一键配网", Key2_long_press_down_handle,

};

5、机智云SDK初始化 。

void Gizwits_Init( void )

{

uart1_init(9600);

gizwitsInit();

gizwits_dataPoint_Init();

extern GizwitsCallback gfunCallback;

gfunCallback = GizwitsDataEventHandle;

}

首先初始化串口1,用于STM32与ESP8266进行通信。

void gizwitsInit(void)

{

pRb.rbCapacity = RB_MAX_LEN;

pRb.rbBuff = rbBuf;

if(0 == rbCreate(&pRb))

{

GIZWITS_LOG("rbCreate Success \n");

}

else

{

GIZWITS_LOG("rbCreate Faild \n");

}

memset((uint8_t *)&gizwitsProtocol, 0, sizeof(gizwitsProtocol_t));

}

之后创建一个环形BUFF,来接收ESP8266发来的数据帧。

gizwitsProtocol,这里有个结构体,是机智云协议中很重要的一个结构体。

/** Protocol main and very important struct */
typedef struct
{
uint8_t issuedFlag; ///< P0 action type
uint8_t protocolBuf[MAX_PACKAGE_LEN]; ///< Protocol data handle buffer
uint8_t transparentBuff[MAX_PACKAGE_LEN]; ///< Transparent data storage area
uint32_t transparentLen; ///< Transmission data length

uint32_t sn; ///< Message SN
uint32_t timerMsCount; ///< Timer Count
protocolWaitAck_t waitAck; ///< Protocol wait ACK data structure

eventInfo_t issuedProcessEvent; ///< Control events
eventInfo_t wifiStatusEvent; ///< WIFI Status events
eventInfo_t NTPEvent; ///< NTP events
eventInfo_t moduleInfoEvent; ///< Module Info events

dataPointFlags_t waitReportDatapointFlag; ///< Store the data points to be reported flag
uint8_t reportData[sizeof(gizwitsElongateP0Form_t)]; ///< Reporting actual data , Max , Can hold all datapoints value
uint32_t reportDataLen; ///< Reporting actual data length

dataPoint_t gizCurrentDataPoint; ///< Current device datapoints status
dataPoint_t gizLastDataPoint; ///< Last device datapoints status
moduleStatusInfo_t wifiStatusData; ///< WIFI signal intensity
protocolTime_t TimeNTP; ///< Network time information
#if MODULE_TYPE
gprsInfo_t gprsInfoNews;
#else
moduleInfo_t wifiModuleNews; ///< WIFI module Info
#endif

}gizwitsProtocol_t;

gizwitsProtocol_t 结构体是一个复杂的数据结构,用于管理和处理与设备通信相关的各种信息和状态。它主要用于在智能设备(如基于STM32的智能家居系统)和云平台(如机智云)之间进行协议数据的处理和传输。以下是结构体中各个字段的主要功能:

1.uint8_t issuedFlag: 这个字段表示当前操作的类型(例如,发送的命令或请求的类型)。通常用于标识不同的协议操作或数据类型。

2.uint8_t protocolBuf[MAX_PACKAGE_LEN]: 这是一个缓冲区,用于存储协议数据的处理。它接收和存储从网络接收到的数据包,或准备要发送的数据包。

3.uint8_t transparentBuff[MAX_PACKAGE_LEN]: 这是一个透明数据存储区,用于处理不经过协议解析的数据。它可能用于存储原始数据,以便在需要时直接传输或处理。

4.uint32_t transparentLen: 这个字段记录了透明数据的传输长度。它用于标记transparentBuff中数据的实际大小,以确保数据的完整传输和处理。

5.uint32_t sn: 消息序列号,用于标识每一条消息。它可以用于追踪消息的顺序和处理状态,以及用于消息的确认(ACK)机制。

6.uint32_t timerMsCount: 计时器计数器,用于追踪时间间隔。它可以用于超时检测或定时任务的执行。

7.protocolWaitAck_t waitAck: 这是一个数据结构,用于管理和存储等待确认(ACK)消息的相关信息。它帮助处理消息确认机制,确保消息的可靠传输。

8.eventInfo_t issuedProcessEvent: 控制事件的数据结构,用于记录和处理设备控制相关的事件,例如用户操作或状态变更。

9.eventInfo_t wifiStatusEvent: 记录WiFi状态事件的信息,例如连接状态、信号强度等,用于监控WiFi连接情况。

10.eventInfo_t NTPEvent: 记录网络时间协议(NTP)事件的信息,通常用于同步网络时间。

11.eventInfo_t moduleInfoEvent: 记录模块信息事件,例如设备的状态或配置变化。

12.dataPointFlags_t waitReportDatapointFlag: 存储待报告数据点的标志。用于指示哪些数据点需要被报告或发送。

13.uint8_t reportData[sizeof(gizwitsElongateP0Form_t)]: 用于存储实际报告的数据。它能够容纳所有数据点的值,准备将数据发送到云平台或其他系统。

14.uint32_t reportDataLen: 报告数据的实际长度,用于标记reportData中数据的大小。

15.dataPoint_t gizCurrentDataPoint: 当前设备数据点的状态,记录设备当前的所有传感器和控制点的实时数据。

16.dataPoint_t gizLastDataPoint: 上一次设备数据点的状态,用于与当前数据进行比较,以检测变化或执行必要的操作。

17.moduleStatusInfo_t wifiStatusData: WiFi信号强度的信息,用于监控和优化WiFi连接质量。

18.protocolTime_t TimeNTP: 网络时间信息,用于同步设备时间,确保时间戳的准确性。

19.#if MODULE_TYPE:

20.gprsInfo_t gprsInfoNews: 如果设备使用GPRS模块,该字段存储GPRS模块的信息。

#else:

21.moduleInfo_t wifiModuleNews: 如果设备使用WiFi模块,该字段存储WiFi模块的信息。

总的来说,gizwitsProtocol_t 结构体是一个综合性的数据管理和处理结构,涵盖了协议处理、数据存储、事件管理和模块信息等方面的功能,为智能设备与云平台的高效通信提供了支持。

void gizwits_dataPoint_Init( void )

{

memset((uint8_t*)&currentDataPoint, 0, sizeof(dataPoint_t));

/** Warning !!! DataPoint Variables Init , Must Within The Data Range **/

}

/** User Area Device State Structure */

typedef struct {

bool valuelight;

bool valuefan;

uint32_t valuetemperature;

uint32_t valuehumi;

} dataPoint_t;

初始化currentDataPoint这个结构体,这个是用来APP显示实时数据的值,我们需要不断的更新里面的值,便于APP显示。

GizwitsDataEventHandle

static void GizwitsDataEventHandle ( int code, void *pvParam )
{
DEBUG_LOG("**********GizwitsDataEventHandle***********\n");
dataPoint_t *psCurrentDP = (dataPoint_t *)pvParam;
switch (code)
{
case EVENT_light:
if (psCurrentDP->valuelight == 1)
{
TurnONLight( );
}
else
{
TurnOffLight( );
}
break;
case EVENT_fan:
if (psCurrentDP->valuefan== 1)
{
TurnOnAirCondit( );
}
else
{
TurnOffAirCondit( );
}
break;
case WIFI_NTP:
break;
default:
/* 按键事件 - 置位 */
//SetBit(SysParamHandle.u8EventReg, Gizwits_EVENT_BIT);
break;
}
}

这个就明显了,回调函数,这样子我们就可以在收到对应事件后,处理对应任务,比如APP关灯事件到来,我们单片机回调TurnOffLight()函数。

6、OLED显示初始画面。

void OLED_InitPage(void)

{

/* 清屏 */

OLED_CLS();

OLED_ShowCnAndAsciiStr(0, 0, "温度:", 2);

OLED_ShowCnAndAsciiStr(0, 2, "湿度:", 2);

OLED_ShowCnAndAsciiStr(0, 4, "光照强度:", 2);

OLED_ShowCnAndAsciiStr(0, 6, "烟雾浓度:", 2);

}

app_Process( SysParam_t *p_SysParamHandle )

while循环中不断轮询的函数,最为关键的一个函数。

void app_Process( SysParam_t *p_SysParamHandle )

{

/***************** 底层驱动 **********************/

/* 传感器数据过去 - 线程 */

drv_Sensor_Handle( p_SysParamHandle );

/* ld3320语音识别模块 - 线程 */

drv_Ld3322_Handle(p_SysParamHandle->psLdHandle);

/***************** 中间层 **********************/

/* 机智云云端协议处理 - 任务 */

gizwitsHandle( p_SysParamHandle->p_DataPoint );

/* 机智云数据同步 - 任务 */

app_SyncData_Task( p_SysParamHandle );

/* oled参数更新 - 任务 */

app_OledUpdateParam( p_SysParamHandle );

/***************** 应用层事件 **********************/

/* 系统控制任务 - 线程 */

app_Ctl_Task( p_SysParamHandle );

/* 机智云事件处理 - 线程 */

app_GizwitsDataEvent_Handle( p_SysParamHandle );

}

/* 传感器数据过去 - 线程 */

void drv_Sensor_Handle( SysParam_t *p_SysParamHandle )

{

if (p_SysParamHandle->u32SensorTim == 0)

{

p_SysParamHandle->u32SensorTim = 200;

p_SysParamHandle->u16Lightness = bh_data_read( ); //!< 读取光照强度

get_mq2_value( &p_SysParamHandle->fMqValue ); //!< 读取烟雾浓度

DHT11_Read_TempAndHumidity( p_SysParamHandle->psDHT11DataHandle ); //!< 读取温湿度

}

}

200s来进行读取一次传感器数据,把数据更新到p_SysParamHandle结构体。

/* ld3320语音识别模块 - 线程 */

void drv_Ld3322_Handle( LD3322Handle_t *psLdHandle )

{

uint16_t index;

if (psLdHandle->bl_rev_cmd_flg == CMD_REV_OK)

{

psLdHandle->bl_rev_cmd_flg = CMD_REV_NO_OK;

for (index=0; index<psLdHandle->u16_cmd_num; index++)

{

if (!strcmp(psLdHandle->pCmdTable[index].cmd_str, psLdHandle->pu8_rev_cmd_buf))

{

psLdHandle->pCmdTable[index].funCallback();

DEBUG_LOG("Ld3322 check OK\n");

break;

}

}

if (index == psLdHandle->u16_cmd_num)

{

DEBUG_LOG("Ld3322 Erorr\n");

}

User_MemSet(psLdHandle->pu8_rev_cmd_buf, 0x0, 30);

}

我们在进行语音命令被语音模块识别之后,语音模块会通过串口发送对应的指令给我们单片机,我们单片机根据指令,执行对应指令的回调函数。

/* 机智云云端协议处理 - 任务 */

int32_t gizwitsHandle(dataPoint_t *currentData)

{

int8_t ret = 0;

#ifdef PROTOCOL_DEBUG

uint16_t i = 0;

#endif

uint8_t ackData[RB_MAX_LEN];

uint16_t protocolLen = 0;

uint32_t ackLen = 0;

protocolHead_t *recvHead = NULL;

char *didPtr = NULL;

uint16_t offset = 0;

if(NULL == currentData)

{

GIZWITS_LOG("GizwitsHandle Error , Illegal Param\n");

return -1;

}

/*resend strategy*/

gizProtocolAckHandle();

ret = gizProtocolGetOnePacket(&pRb, gizwitsProtocol.protocolBuf, &protocolLen);

if(0 == ret)

{

GIZWITS_LOG("Get One Packet!\n");

#ifdef PROTOCOL_DEBUG

GIZWITS_LOG("WiFi2MCU[%4d:%4d]: ", gizGetTimerCount(), protocolLen);

for(i=0; i<protocolLen;i++)

{

GIZWITS_LOG("%02x ", gizwitsProtocol.protocolBuf[i]);

}

GIZWITS_LOG("\n");

#endif

recvHead = (protocolHead_t *)gizwitsProtocol.protocolBuf;

switch (recvHead->cmd)

{

case CMD_GET_DEVICE_INTO:

gizProtocolGetDeviceInfo(recvHead);

break;

case CMD_ISSUED_P0:

GIZWITS_LOG("flag %x %x \n", recvHead->flags[0], recvHead->flags[1]);

//offset = 1;

if(0 == gizProtocolIssuedProcess(didPtr, gizwitsProtocol.protocolBuf+sizeof(protocolHead_t)+offset, protocolLen-(sizeof(protocolHead_t)+offset+1), ackData, &ackLen))

{

gizProtocolIssuedDataAck(recvHead, ackData, ackLen,recvHead->flags[1]);

GIZWITS_LOG("AckData : \n");

}

break;

case CMD_HEARTBEAT:

gizProtocolCommonAck(recvHead);

break;

case CMD_WIFISTATUS:

gizProtocolCommonAck(recvHead);

gizProtocolModuleStatus((protocolWifiStatus_t *)recvHead);

break;

case ACK_REPORT_P0:

case ACK_WIFI_CONFIG:

case ACK_SET_DEFAULT:

case ACK_NINABLE_MODE:

case ACK_REBOOT_MODULE:

gizProtocolWaitAckCheck(recvHead);

break;

case CMD_MCU_REBOOT:

gizProtocolCommonAck(recvHead);

GIZWITS_LOG("report:MCU reboot!\n");

gizProtocolReboot();

break;

case CMD_ERROR_PACKAGE:

break;

case ACK_PRODUCTION_TEST:

gizProtocolWaitAckCheck(recvHead);

GIZWITS_LOG("Ack PRODUCTION_MODE success \n");

break;

case ACK_GET_NTP:

gizProtocolWaitAckCheck(recvHead);

gizProtocolNTP(recvHead);

GIZWITS_LOG("Ack GET_UTT success \n");

break;

case ACK_ASK_MODULE_INFO:

gizProtocolWaitAckCheck(recvHead);

gizProtocolModuleInfoHandle(recvHead);

GIZWITS_LOG("Ack GET_Module success \n");

break;

default:

gizProtocolErrorCmd(recvHead,ERROR_CMD);

GIZWITS_LOG("ERR: cmd code error!\n");

break;

}

}

else if(-2 == ret)

{

//Check failed, report exception

recvHead = (protocolHead_t *)gizwitsProtocol.protocolBuf;

gizProtocolErrorCmd(recvHead,ERROR_ACK_SUM);

GIZWITS_LOG("ERR: check sum error!\n");

return -2;

}

switch(gizwitsProtocol.issuedFlag)

{

case ACTION_CONTROL_TYPE:

gizwitsProtocol.issuedFlag = STATELESS_TYPE;

gizwitsEventProcess(&gizwitsProtocol.issuedProcessEvent, (uint8_t *)&gizwitsProtocol.gizCurrentDataPoint, sizeof(dataPoint_t));

memset((uint8_t *)&gizwitsProtocol.issuedProcessEvent,0x0,sizeof(gizwitsProtocol.issuedProcessEvent));

break;

case WIFI_STATUS_TYPE:

gizwitsProtocol.issuedFlag = STATELESS_TYPE;

gizwitsEventProcess(&gizwitsProtocol.wifiStatusEvent, (uint8_t *)&gizwitsProtocol.wifiStatusData, sizeof(moduleStatusInfo_t));

memset((uint8_t *)&gizwitsProtocol.wifiStatusEvent,0x0,sizeof(gizwitsProtocol.wifiStatusEvent));

break;

case ACTION_W2D_TRANSPARENT_TYPE:

gizwitsProtocol.issuedFlag = STATELESS_TYPE;

gizwitsEventProcess(&gizwitsProtocol.issuedProcessEvent, (uint8_t *)gizwitsProtocol.transparentBuff, gizwitsProtocol.transparentLen);

break;

case GET_NTP_TYPE:

gizwitsProtocol.issuedFlag = STATELESS_TYPE;

gizwitsEventProcess(&gizwitsProtocol.NTPEvent, (uint8_t *)&gizwitsProtocol.TimeNTP, sizeof(protocolTime_t));

memset((uint8_t *)&gizwitsProtocol.NTPEvent,0x0,sizeof(gizwitsProtocol.NTPEvent));

break;

case GET_MODULEINFO_TYPE:

gizwitsProtocol.issuedFlag = STATELESS_TYPE;

gizwitsEventProcess(&gizwitsProtocol.moduleInfoEvent, (uint8_t *)&gizwitsProtocol.wifiModuleNews, sizeof(moduleInfo_t));

memset((uint8_t *)&gizwitsProtocol.moduleInfoEvent,0x0,sizeof(moduleInfo_t));

break;

default:

break;

}

gizDevReportPolicy(currentData);

return 0;

}

● 首先是一些局部变量的初始化,比较重要的是:"protocolHead_t *recvHead = NULL;"它的作用是保存解析出来的协议包头。

● 然后是协议的重发机制,它的作用是对发送后的协议数据进行超时判断,超时200ms进行重发,重发上限为三次:

gizProtocolAckHandle();

● 接下来程序会从环形缓冲区中抓取一包的数据,

ret = gizProtocolGetOnePacket(&pRb, gizwitsProtocol.protocolBuf, &protocolLen);

● 当我们获得到一整包的数据,就会进入下面的if判断逻辑,进行协议的解析。

if(0 == ret)

{

}

● 这里保存了接收到的协议包头:

recvHead = (protocolHead_t *)gizwitsProtocol.protocolBuf;

● 然后是各协议命令的处理流程:

其中完成了《机智云 - 设备串口通讯协议》中相关的协议处理,如下:

● 协议判断完成后是一个状态机的判断,用来完成对应协议命令的处理:

例如在P0协议处理函数(gizProtocolIssuedProcess)中,当我们完成了控制型协议的解析,会让 issuedFlag = 1,如下:

case ACTION_CONTROL_TYPE:

gizwitsProtocol.issuedFlag = STATELESS_TYPE;

gizwitsEventProcess(&gizwitsProtocol.issuedProcessEvent, (uint8_t *)&gizwitsProtocol.gizCurrentDataPoint, sizeof(dataPoint_t));

memset((uint8_t *)&gizwitsProtocol.issuedProcessEvent,0x0,sizeof(gizwitsProtocol.issuedProcessEvent));

break;

然后会执行如下的处理,执行gizwitsEventProcess函数

gizwitsEventProcess 中,完成了对应控制型事件的处理,其他状态的issuedFlag 同理。

● 之后是一个数据上报判断机制,主要执行了gizCheckReport函数

static void gizDevReportPolicy(dataPoint_t *currentData)

{

static uint32_t lastRepTime = 0;

uint32_t timeNow = gizGetTimerCount();

uint8_t *waitReportDataPtr = NULL;

if((1 == gizCheckReport(currentData, (dataPoint_t *)&gizwitsProtocol.gizLastDataPoint)))

{

GIZWITS_LOG("changed, report data\n");

if(0 == gizDataPoints2ReportData(currentData,gizwitsProtocol.reportData,(uint32_t *)&gizwitsProtocol.reportDataLen))

{

gizReportData(ACTION_REPORT_DEV_STATUS, gizwitsProtocol.reportData, gizwitsProtocol.reportDataLen); }

memcpy((uint8_t *)&gizwitsProtocol.gizLastDataPoint, (uint8_t *)currentData, sizeof(dataPoint_t));

}

if((0 == (timeNow % (600000))) && (lastRepTime != timeNow))

{

GIZWITS_LOG("Info: 600S report data\n");

memset((uint8_t *)&gizwitsProtocol.waitReportDatapointFlag,0xFF,DATAPOINT_FLAG_LEN);

if(0 == gizDataPoints2ReportData(currentData,gizwitsProtocol.reportData,(uint32_t *)&gizwitsProtocol.reportDataLen))

{

gizReportData(ACTION_REPORT_DEV_STATUS, gizwitsProtocol.reportData, gizwitsProtocol.reportDataLen); }

memcpy((uint8_t *)&gizwitsProtocol.gizLastDataPoint, (uint8_t *)currentData, sizeof(dataPoint_t));

lastRepTime = timeNow;

}

free(waitReportDataPtr);

}

这段代码定义了一个 gizDevReportPolicy 函数,用于处理设备的数据报告。下面是详细解释:

总结:gizwitsHandle函数执行了很多功能,解析环形BUFF里面的数据帧,解析出数据帧里面的命令,根据命令去执行对应的回调函数,并且根据协议要求,发送回对应数据给ESP8266。

/* 机智云数据同步 - 任务 */

void app_SyncData_Task( SysParam_t *p_SysParamHandle )
{
if (p_SysParamHandle->u32SyncDataTim == 0)
{
p_SysParamHandle->u32SyncDataTim = 500;

p_SysParamHandle->p_DataPoint->valuelight = LIGHT_SWUTCH_STAT;
p_SysParamHandle->p_DataPoint->valuefan = AIRCONDI_SWUTCH_STAT;
p_SysParamHandle->p_DataPoint->valuetemperature = p_SysParamHandle->psDHT11DataHandle->temp_int;
p_SysParamHandle->p_DataPoint->valuehumi = p_SysParamHandle->psDHT11DataHandle->humi_int;

}
}

将最新的传感器数据更新到 p_DataPoint结构体 。

总结:这里就是代码的基本实现思路,至于如何读取传感器数据,这些细节的东西就不去涉及了。

机智云平台搭建以及代码移植

机智云平台非常的方便,我们设置好对应的参数之后,他会自动给我们生成可以移植到STM32中的代码,我们只需要移植进去我们的工程,并且进行对应的修改,就可以实现STM32与机智云APP的互联。

主要材料准备
STM32F103C8T6板子

ESP8266模块

***1.***APP制作

1.1 首先利用网上的一些物联网自助开发平台去制作APP,这里我选用机智云。浏览器搜索 机智云 ,然后进入官网,如下图。

1.2 进入官网后点击右上角的 开发者中心 。

1.3 大部分同学还没注册过机智云账号,所以先去注册一个。

1.4 注册完成后,登录账号就进入了下方界面,点击左侧"智能产品"栏的"+创建",开始制作APP。

1.5 点击"+创建"后,就来到下图中的界面进行APP的类型选择(照着下图操作)。我们点击 照明 ,再点击 球泡灯 ,然后点击 自定义方案 ,最后点击灯。(这些操作只是先给APP选个模板而已,到时候功能可以不跟控灯功能相关)

1.6 完成步骤1.5后,我们就相当于选好了APP的模板类型,接下来会弹出下方的界面,我们只要按照下图中红色框一样设置就行了(产品名称可以自己取),按图片操作完后,点击界面底部的创建。

1.7 完成步骤1.6之后,就自动来到了下图的界面。这里我们开始给APP添加内容,点击下图的 去编辑。

然后参考下图红色框框部分进行填写(这里我们先实现往APP里添加一个窗开关的内容);

在 标识名 这一项,我们取名字的时候尽量取得"清楚",就是一眼就知道是什么意思。因为后面机智云自动生成的单片机程序代码里表示窗开关的的变量名就是根据这个标识名生成的。显示名称:就是等会APP上会显示的文字,比如我们填写"窗开关",等会生成的APP上就会有个地方标注文字"窗开关",然后我们继续填写下面的读写类型和数据类型,让APP上"窗开关"的文字旁边显示个按键,这样我们一眼就这个按键是用来控制窗开关的。

如果只是起显示数值作用属于只读,比如显示温度值和湿度值。

数据类型:窗的状态有"开"和"关"两种状态,所以数据类型是布尔值类型(也就是0和1)。

填写好标识名、显示名称、读写类型、数据类型后点击界面下方的 确定。


1.8 完成上面的步骤后,就自动来到了下图的界面。这个时候我们的 只控制窗开关的APP 已经做好了。

2.给ESP8266模块烧录固件

2.1 首先在机智云官网下载ESP8266固件到本地电脑上。

ESP8266 GAgent固件下载地址:https://devdocs.gizwits.com/download.html#166419072645267?1672219764470

2.2 确认下载的固件

2.3设备烧录。

完成上面步骤后,我们以管理员的方式打开资料里提供的烧录工具:

点击OK

2.4 烧录设置。

**3.**代码移植

现在APP制作完毕,ESP8266固件也烧录好了,接下来就是进入编写STM32程序的环节。(内部复杂的代码原理,有兴趣的去机智云官网学习,这里只是教大家怎么用,以最快的速度做出自己想要的毕设)

3.1 机智云能够根据我们刚才制作的APP,自动生成APP与STM32通讯的代码工程,这一点机智云官方有说明(下图)。看不懂就不理它,反正等会我们会把自动生成的代码里关于APP与STM32通信相关的代码移植进我们自己的工程里边实现与APP相互通讯。

进行下方两张图的操作进行代码自动生成。

首先

然后


3.2 完成上图操作后,等待一会,代码就会自动生成完毕,我们将其下载下来,并解压后得到以下文件。我们只需要用到Gizwits文件夹和Utils文件夹里的内容。

将机智云自动生成代码工程里的Gizwits文件夹和Utils文件夹两个文件夹复制到资料(获取方法在文章底部)里提供的基础工程里边。基础工程内容就是在第九章的代码工程基础上多增加了两个串口功能(一个串口负责打印信息,一个串口负责与ESP8266进行通讯)和一个实现1ms定时的定时器功能。

这里说明一下,基础工程需要具备什么条件呢?答案是:必须得有两个串口功能和实现1ms定时的定时器功能。这一点可以看下图,机智云官网上有提出。所以,如果不想用资料提供的基础工程做移植操作,而是想用你自己的工程来做移植操作的,只要你的项目还空出两个串口可以用,以及有个定时器,就可以继续按下文进行操作。

我们首先要根据我们产品的需求,制定出我们需要的APP界面,因为我们使用的是机智云的代码,我们首先要烧录机智云的官方固件给esp8266,然后再去生成移植代码,在移植代码的基础上,移植或者在基础上重新修改。里面包含很多个文件:

协议API介绍:

程序实现原理:

协议解析后,将P0数据区的有效数据点生成对应的数据点事件,再按事件处理数据点。

数据点转换事件的说明:

根据协议P0数据区的attr_flags位判断出有效数据点,并将其转化成对应的数据点事件,然后在事件处理函数中(gizwitsEventProcess)完成事件的处理。

程序初始化

数据协议结构体的定义

结构体dataPoint_t ,代码位置: MCU_STM32xxx_source\Gizwits\gizwits_protocol.h

说明:结构体dataPoint_t作用是存储用户区的设备状态信息,用户根据云端定义的数据点向其对应的数据位赋值后便不需关心数据的转换。

attrFlags_t、attrVals_t ,代码位置: MCU_STM32xxx_source\Gizwits\gizwits_protocol.h

dataPoint_t 为应用层数据结构,开发者需要了解并会使用(具体使用方式请查看:**"2.7.1 只读型数据的获取"**一节)。

attrFlags_t、attrVals_t、devStatus_t为通信层数据结构,开发者需要结合通讯协议进行理解。我们这里只要移植,无需过多纠结,但是我们要知道,我们这里要用的四个值:灯、风扇、温度、湿度。

用户程序初始化

接下来看用户初始化相关代码(位置:gizwits_product.cuserInit() 函数):

其中完成了定时器、串口的初始化(详情查看2.3.4、2.3.5两节),以及一个环形缓冲区的初始化。

最后是一个通信处理模块结构体的变量的初始化,该变量为通信模块的全局变量:

其定义的位置:Gizwits\gizwits_protocol.c

定时器使用

定时器中断函数,代码位置:MCU_STM32xxx_source\Gizwits\gizwits_product.c

注:在该中断函数内我们完成了周期为1ms的定时累加计数。

串口的使用

串口中断函数,位置:MCU_STM32xxx_source\Gizwits\gizwits_product.c

配置模式说明

设备需要进入配置模式才能进行联网,并与云端进行通信,在本示例工程中是通过按键触发进入相应的配置模式。

Wifi 配置接口说明:

/**

  • @brief WiFi配置接口

  • 用户可以调用该接口使WiFi模组进入相应的配置模式或者复位模组

  • @param[in] mode 配置模式选择:0x0, 模组复位 ;0x01, SoftAp模式 ;0x02, AirLink模式

  • @return 错误命令码
    */
    ·int32_t gizwitsSetMode(uint8_t mode)

程序中触发逻辑位置:MCU_STM32xxx_source\User\main.c

这些API函数都是机智云给我们封装好的了,我们只需要调用就ok。

协议处理函数的实现

位置:MCU_STM32xxx_source\Gizwits\gizwits_protocol.c中gizwitsHandle() 函数:

其余协议处理函数功能如下所示:

● 协议判断完成后是一个状态机的判断,用来完成对应协议命令的处理:

例如在P0协议处理函数(gizProtocolIssuedProcess)中,当我们完成了控制型协议的解析,会让 issuedFlag = 1,如下:

然后会执行如下的处理,执行gizwitsEventProcess函数

gizwitsEventProcess 中,完成了对应控制型事件的处理,其他状态的issuedFlag 同理。

● 之后是一个数据上报判断机制,主要执行了gizCheckReport函数

gizCheckReport函数的作用用来判断当前与上次上报数据的一致性,如果符合上报条件便上报,上报条件要符合协议"4.9 设备MCU向WiFi模组主动上报当前状态"中的描述。

4.总结

我们项目的要求是测出各个传感器数据以及用APP去控制风扇和灯和显示出传感器数据,前者是我们STM32底层工程以及处理好了,我们根据我们的需求制作搭建好机智云平台之后,生成移植代码,我们只需要把他的代码按需求移植到STM32基础程序里面,并且编写好UserHandle回调函数(处理我们收到APP的开关灯......事件该怎么办),并且将传感器的数值与机智云数据同步,这样子,我们就可以实现STM32与APP的搭建。

SNR8016VR_DEV智能语音模块

智能公元平台

开发平台地址 http://smartpi.cn/#/

1、创建产品

2、配置语音模块

具体操作流程可以去看官方的教程,这里我只列举我这里需要用到。

配置串口

唤醒词自定义

命令词自定义

控制详情

收到对应的命令词就发送对应数据给STM32,STM32根据收到的命令执行对应的回调函数。

其他配置

3、生成SDK固件烧录

总结

基于STM32构建的智能家居语音控制系统的大概思路就是这样了,模块到联网,希望大家看了有所收获。

相关推荐
scan122 分钟前
单片机串口接收状态机STM32
stm32·单片机·串口·51·串口接收
Qingniu011 小时前
【青牛科技】应用方案 | RTC实时时钟芯片D8563和D1302
科技·单片机·嵌入式硬件·实时音视频·安防·工控·储能
Mortal_hhh2 小时前
VScode的C/C++点击转到定义,不是跳转定义而是跳转声明怎么办?(内附详细做法)
ide·vscode·stm32·编辑器
深圳市青牛科技实业有限公司2 小时前
【青牛科技】应用方案|D2587A高压大电流DC-DC
人工智能·科技·单片机·嵌入式硬件·机器人·安防监控
Mr.谢尔比3 小时前
电赛入门之软件stm32keil+cubemx
stm32·单片机·嵌入式硬件·mcu·信息与通信·信号处理
LightningJie3 小时前
STM32中ARR(自动重装寄存器)为什么要减1
stm32·单片机·嵌入式硬件
鹿屿二向箔3 小时前
STM32外设之SPI的介绍
stm32
西瓜籽@4 小时前
STM32——毕设基于单片机的多功能节能窗控制系统
stm32·单片机·课程设计
faec1135 小时前
2024年双十一有什么好物推荐?双十一必买好物清单大揭秘
智能家居
远翔调光芯片^138287988726 小时前
远翔升压恒流芯片FP7209X与FP7209M什么区别?做以下应用市场摄影补光灯、便携灯、智能家居(调光)市场、太阳能、车灯、洗墙灯、舞台灯必看!
科技·单片机·智能家居·能源