STM32智能家居语音系统

news2024/9/24 9:24:47

简介

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2105287.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

免邀请码下载时代已来临,Xinstall为您保驾护航

在App推广的道路上&#xff0c;邀请码下载一直是一个让人头疼的问题。用户需要输入繁琐的邀请码才能完成下载&#xff0c;这不仅增加了用户的操作难度&#xff0c;也影响了App的推广效果。然而&#xff0c;随着Xinstall的免邀请码下载功能的推出&#xff0c;这一问题终于得到了…

VISION TRANSFORMER ADAPTER FORDENSE PREDICTIONS

总结 提出了一种新范式&#xff1a; 开发了 Vision Transformer Adapter (ViT-Adapter)&#xff0c;通过引入无预训练的适配器将视觉特定的归纳偏差&#xff08;例如局部空间信息&#xff09;引入普通 ViT&#xff0c;适应密集预测任务。 保留 ViT 灵活性&#xff1a; 与设计…

Linux日志-lastlog日志

作者介绍&#xff1a;简历上没有一个精通的运维工程师。希望大家多多关注作者&#xff0c;下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 Linux 系统中的日志是记录系统活动和事件的重要工具&#xff0c;它们可以帮助管理员监视系统状态、调查问题以及了解系统运行…

【动态规划专栏】专题一总结

本专栏内容为&#xff1a;算法学习专栏&#xff0c;分为优选算法专栏&#xff0c;贪心算法专栏&#xff0c;动态规划专栏以及递归&#xff0c;搜索与回溯算法专栏四部分。 通过本专栏的深入学习&#xff0c;你可以了解并掌握算法。 &#x1f493;博主csdn个人主页&#xff1a;小…

电脑错误mfc140.dll丢失怎么办?mfc140.dll丢失如何修复?

在使用基于Microsoft Visual Studio 2015开发的应用程序时&#xff0c;可能会遇到个别组件影响整体功能的情况&#xff0c;其中“mfc140.dll丢失”错误就是常见的一个技术障碍。这个DLL文件属于Microsoft Foundation Class (MFC) Library&#xff0c;它对Windows应用程序的运行…

基于vue框架的朝阳保险公司营销管理系统0wamc(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;用户,保险信息,保险类型,订单信息,赔偿信息,联系我们,购买指南,到期提醒 开题报告内容 基于Vue框架的朝阳保险公司营销管理系统的开题报告 一、研究背景 随着保险行业的快速发展和市场竞争的日益激烈&#xff0c;传统的管理模式已难以…

恶意代码分析 | Lab1

前言 穿插virus分析的学习&#xff0c;毕竟逆向技术最后要用在攻防中。 Lab1就没必要动态分析了&#xff0c;静态学学写法。 Lab01-01.exe 前面是内存映射技术&#xff0c;对内存进行修改操作。 将Kernel32.dll和Lab01-01.dll的内存都Map出来&#xff0c;便于后续更改&…

有没有性价比高一些的开放式耳机推荐?盘点四款高性价比蓝牙耳机

在正式介绍几款高性价比产品&#xff0c;先来和各位聊聊什么是开放式耳机。开放式耳机是目前比较流行的耳机种类&#xff0c;其特点是通过采用海绵状的微孔发泡塑料制作透声耳垫&#xff0c;不堵塞耳道&#xff0c;使得声音可以泄露&#xff0c;同时佩戴者也能听到外界声音 。 …

民宿酒店预订系统V1.0.8

多门店民宿酒店预订管理系统&#xff0c;快速部署属于自己民宿酒店的预订小程序&#xff0c;包含预订、退房、WIFI连接、吐槽、周边信息等功能。提供全部无加密源代码&#xff0c;支持私有化部署。 V1.0.8修复房间预订状态无法筛选的问题 修复房间预订状态无法筛选的问题 修复…

为自闭症儿童提供全方位支持的自闭症全托管机构

在自闭症儿童的世界里&#xff0c;每一个细微的进步都凝聚着家庭与社会的无尽努力与期盼。星启帆自闭症儿童康复机构&#xff0c;作为一所致力于为自闭症儿童提供全方位支持的全托管机构&#xff0c;正以其专业的服务、温馨的环境和全面的康复计划&#xff0c;成为众多家庭信赖…

UE4_后期处理_后期处理材质及后期处理体积二

效果&#xff1a; 步骤&#xff1a; 1、创建后期处理材质,并设置参数。 2、回到主界面&#xff0c;找到需要发光的物体的细节面板。 渲染自定义深度通道&#xff0c;默认自定义深度模具值为10&#xff08;需要修改此值&#xff0c;此值影响物体的亮度&#xff09;。 3、添加…

pytest压力测试:不断发送数据,直到发现数据丢失

示例场景 假设有一个 send_data 函数接受数据并返回成功或失败的状态。 创建一个测试用例&#xff0c;通过逐步增加数据量来测试这个函数&#xff0c;直到返回失败为止。 步骤 定义压力测试函数 定义一个函数。不断发送数据&#xff0c;直到发现数据丢失。 创建 pytest 测试…

PHP一站式班级解决方案班级管家系统小程序源码

一站式班级解决方案 —— 班级管家系统 &#x1f393;【开篇&#xff1a;班级管理的烦恼&#xff0c;你中招了吗&#xff1f;】&#x1f393; 作为班主任或班级管理者&#xff0c;你是否经常为繁琐的班级事务而头疼&#xff1f;从日常通知的发布到作业的收集&#xff0c;从班…

ChatGPT写文章时,如何去除生硬的Ai味?

仅做分享&#xff0c;侵删 在使用AI进行写作时&#xff0c;常常会发现生成的文章带有明显的“机器味”&#xff0c;一眼就能看出是由AI生成的。这是许多希望借助AI进行自媒体创作的小伙伴们面临的一个主要问题。AI生成的文章往往过于书面化&#xff0c;缺乏人情味&#xff0c;导…

葡萄检测-目标检测数据集(包括VOC格式、YOLO格式)

葡萄检测-目标检测数据集&#xff08;包括VOC格式、YOLO格式&#xff09; 数据集&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1YMwAaSJc8H5SI0f8RVSidw?pwdiygs 提取码&#xff1a;iygs 数据集信息介绍&#xff1a; 共有1646 张图像和一一对应的标注文件 标注文…

matlab实现kaiser窗+时域采样序列(不管原信号拉伸成什么样子)是一样的,变到频谱后再采样就是一样的频域序列。

下图窗2的频谱在周期化的时候应该是2&#xff08;w-k*pi/T&#xff09;我直接对2w减得写错了 可见这两个kaiser窗频谱不一样&#xff0c;采样间隔为2T的窗&#xff0c;频谱压缩2倍&#xff0c;且以原采样频率的一半周期化。 但是这两个不同的kaiser窗在频域采样点的值使完全一…

MySQL复习4

触发器 触发器&#xff08;trigger&#xff09;是 MySQL 提供给程序员和数据分析员来保证数据完整性的一种方法&#xff0c;他是与表时间相关的特殊存储过程&#xff0c;他的执行不是由程序调用&#xff0c;也不是手动启动&#xff0c;而是由事件来触发&#xff0c;比如当时对…

2024-09-04作业

作业 代码 #include <iostream> using namespace std; class Animal { private: string narrator; public: Animal(){} Animal(string narrator):narrator(narrator) {} virtual void perform() { cout << "讲解员解说中&…

神策SDK不支持Windows客户端全埋点,怎么实现用户统计分析?

本文将介绍&#xff0c;ClkLog针对神策不支持全埋点的客户端实现用户访问基础统计分析 1。 客户遇到的问题 ClkLog的用户访问基础统计分析功能是基于神策SDK的全埋点来实现的。 我们遇到有些客户是使用C、C#等语言来开发的客户端&#xff0c;然而神策此类SDK&#xff08;如C, C…

activiti PROC_DEF_ID_超64位解决方案

默认得id是这样的 如果前面的code长了&#xff0c;就会出问题&#xff0c;而且手动修改activiti相关表里面的长度也不行&#xff0c;所有改采用雪花算法&#xff0c;把后面的uuid了&#xff0c;修改后 这也64的长度也够了&#xff0c;注意只对新发布的流程有效 <dependency…