什么?单片机还在裸奔?ESP8266纯串口透传,助力设备上云端

项目有些久远,前年的机器人上需要的功能,当时是需要将STM32上的数据上传到服务器,比如机器人的速度,行驶距离,是否在拍照等等。便于管理者在PC或者手机上了解机器人的工作状态,同时可以远程下发指令给机器人,控制其完成相应动作。因为所有的逻辑判断和控制都在服务器或者STM32上面,作为中间的无线模块仅仅需要上传STM32的数据并接收服务器下发的指令即可,所以这里对WiFi模块的要求不高,仅仅需要它作为透传功能即可。当时在选型的时候试过好几款WiFi模块,最终敲定了安信可的ESP8266,价格便宜,开发简单,但是搭建环境是真的不容易,深受其害。选择好模块就该考虑使用AT指令还是使用SDK开发,AT指令固然简单,但是局限性非常大。如果使用AT指令,我那开发控制端的同事估计就要跳脚了,代码里需要写一大堆的AT指令,如果功能改变,指令代码就需要重写,烦不胜烦。如果使用SDK开发,控制端只需发送简单的数据就行,完全不用考虑其他任何东西,ESP8266完全当做一个中转站,相对应的我的工作就会繁重,但是,我屈服了,选择使用SDK。
于是就有了下面基于NONOS 2.0的ESP8266串口透传。主要有以下几个功能:
 

  1. 纯串口透传,接收MCU串口数据,直接通过MQTT上传到服务器,接收服务器数据下发给MCU。
  2. smartconfig+airkiss配网,随意使用,场景丰富。
  3. 最多储存5个WIFI账号和密码,自动寻找网络连接。
  4. 按键配网,长按重新配网,前一次WiFi自动储存,添加配网指示灯。
  5. OTA空中升级(待验证)

从程序的入口开始:

//程序入口

void ICACHE_FLASH_ATTR user_init(void)

{

        uart_init(115200, 115200);

        os_delay_us(60000);

        keyInit();

        set_uart_cb(uart_cb);



        PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U, FUNC_GPIO12); //GPIO12初始化

        GPIO_OUTPUT_SET(GPIO_ID_PIN(12), 0);//低电平



    get_mac();//获取MAC地址



        wifi_set_opmode(STATION_MODE); 

        //设置wifi信息存储数量,最大为5个

        wifi_station_ap_number_set(2);



    mqtt_init();



        set_wifistate_cb(wifi_connect_cb, wifi_disconnect_cb);

}

程序的入口先进行串口初始化和按键的初始化,以及LED的初始化。串口要初始化波特率,按键初始化配网按键,用于短按配网,长按重新配网,LED只要用于判断模块是否进入配网模式以及是否配网完成。
初始化完成后会首先读取MAC地址,该地址是唯一的,每个模块都不一样,用于填充进主题中,便于服务器区分不同设备,用于多台量产设备的使用,在连接MQTT服务器时会自动填充。

每连接一次WiFi都会将WiFi信息保存在模块内部,每次上电都会自动扫描暴露的WiFi,直接连接,就像手机的WIFI连接,目前最大支持五个WiFi信息的保存,超过5个会剔除最早的WiFi信息,通过短按D5(GPIO14)可进入配网模式。 

/**

 *         按键短按回调

 */

LOCAL void ICACHE_FLASH_ATTR key1ShortPress(void) {



        start_smartconfig(smartconfig_cd);

        INFO("start_smartconfig\n");

}

/**

 *         按键长按回调

 */

LOCAL void ICACHE_FLASH_ATTR key1LongPress(void) {



        start_smartconfig(smartconfig_cd);

        INFO("start_smartconfig\n");

}

/**

 *         按键初始化

 */

LOCAL void ICACHE_FLASH_ATTR keyInit(void) {



        //设置按键数量

        set_key_num(1);

        //长按、短按的按键回调

        key_add(D5, NULL, key1ShortPress);

        key_add(D5, NULL, key1LongPress);



}



由于找不到最新的代码。这里的长按我没做处理,应该是断开WiFi重新进入配网模式, 或者软复位模块,再进入start_smartconfig()函数:

/**

* 开始Smartconfig配置  

* @param  cd: Smartconfig状态回调

* @retval None

*/

void ICACHE_FLASH_ATTR start_smartconfig(smartconfig_cd_t cd) {

        smartconfig_flag = 1;

        smartconfig_set_type(SC_TYPE_ESPTOUCH_AIRKISS); //SC_TYPE_ESPTOUCH,SC_TYPE_AIRKISS,SC_TYPE_ESPTOUCH_AIRKISS

        wifi_station_disconnect();

        wifi_set_opmode(STATION_MODE);

        finish_cd = cd;

        smartconfig_start(smartconfig_done);

        os_timer_disarm(&OS_Timer_Wifichange);        // 关闭定时器



        if(connect_flag == 1){

                w_disconnect();

                connect_flag = 0;

        }



        os_timer_disarm(&OS_Timer_SM);        // 关闭定时器

        os_timer_setfn(&OS_Timer_SM, (os_timer_func_t *) sm_wait_time, NULL);// 设置定时器

        os_timer_arm(&OS_Timer_SM, 1000, 1);  // 使能定时器

}


smartconfig_set_type();函数可选3个参数:分别是:SC_TYPE_ESPTOUCHSC_TYPE_AIRKISSSC_TYPE_ESPTOUCH_AIRKISS
第一个是smartconfig配网(手机APP),第二个是airkiss配网(微信公众号),最后一个两者都可以。进入该函数会调用smartconfig_start();该函数会调用smartconfig_done()函数进行配网,配网成功后会点亮LED灯。

smartconfig_set_type();函数可选3个参数:分别是SC_TYPE_ESPTOUCH、SC_TYPE_AIRKISS和SC_TYPE_ESPTOUCH_AIRKISS
第一个是smartconfig配网(手机APP),第二个是airkiss配网(微信公众号),最后一个两者都可以。进入该函数会调用smartconfig_start();,该函数会调用smartconfig_done()函数进行配网,配网成功后会点亮LED灯。

/**

 * Smartconfig 状态处理 

 * @param  status: 状态

 * @param  *pdata: AP数据

 * @retval None

 */

void ICACHE_FLASH_ATTR

smartconfig_done(sc_status status, void *pdata) {

        switch (status) {

        case SC_STATUS_WAIT:

                INFO("SC_STATUS_WAIT\n");

                break;

        case SC_STATUS_FIND_CHANNEL:

                INFO("SC_STATUS_FIND_CHANNEL\n");

                break;

        case SC_STATUS_GETTING_SSID_PSWD:

                INFO("SC_STATUS_GETTING_SSID_PSWD\n");

                sc_type *type = pdata;

                if (*type == SC_TYPE_ESPTOUCH) {

                        INFO("SC_TYPE:SC_TYPE_ESPTOUCH\n");

                } else {

                        INFO("SC_TYPE:SC_TYPE_AIRKISS\n");

                }

                break;

        case SC_STATUS_LINK:

                INFO("SC_STATUS_LINK\n");

                sm_comfig_status = SM_STATUS_GETINFO;

                struct station_config *sta_conf = pdata;

                wifi_station_set_config(sta_conf);

                wifi_station_disconnect();

                wifi_station_connect();

                break;

        case SC_STATUS_LINK_OVER:

                sm_comfig_status = SM_STATUS_FINISH;

                INFO("SC_STATUS_LINK_OVER\n");

                if (pdata != NULL) {

                        //SC_TYPE_ESPTOUCH

                        uint8 phone_ip[4] = { 0 };

                        os_memcpy(phone_ip, (uint8*) pdata, 4);

                        INFO("Phone ip: %d.%d.%d.%d\n", phone_ip[0], phone_ip[1],

                                        phone_ip[2], phone_ip[3]);

                } else {

                        //SC_TYPE_AIRKISS - support airkiss v2.0

                        airkiss_start_discover();

                }

                smartconfig_stop();

                smartconfig_flag = 0;

                connect_flag = 0;

                os_timer_disarm(&OS_Timer_SM);        // 关闭定时器

                finish_cd(sm_comfig_status);

                os_timer_arm(&OS_Timer_Wifichange, 3000, 1);  // 使能定时器

                break;

        }



}



/**

 *         WIFI连接回调

 */

void wifi_connect_cb(void){



        INFO("wifi connect!\r\n");

        os_printf("----- WiFi连接成功,打开绿灯---\r\n");

        GPIO_OUTPUT_SET(GPIO_ID_PIN(12), 1);

        MQTT_Connect(&mqttClient);

}



/**

 *         WIFI断开回调

 */

void wifi_disconnect_cb(void){

        INFO("wifi disconnect!\r\n");

        os_printf("----- WiFi断开,关闭绿灯---\r\n");

        GPIO_OUTPUT_SET(GPIO_ID_PIN(12), 0);

        MQTT_Disconnect(&mqttClient);

}连接MQTT服务器

网络连接成功以后可以开始MQTT的初始化,初始化包涵一系列的连接初始化回调,连接成功或不成功回调,主题订阅发布回调等等。
/**

 *         MQTT初始化

 */

void ICACHE_FLASH_ATTR mqtt_init(void) {



        MQTT_InitConnection(&mqttClient, MQTT_HOST, MQTT_PORT, DEFAULT_SECURITY);

        MQTT_InitClient(&mqttClient, mac_str, MQTT_USER,MQTT_PASS, MQTT_KEEPALIVE, 1);

        MQTT_InitLWT(&mqttClient, lwt_topic, LWT_MESSAGE, 0, 0);

        MQTT_OnConnected(&mqttClient, mqttConnectedCb);

        MQTT_OnDisconnected(&mqttClient, mqttDisconnectedCb);

        MQTT_OnPublished(&mqttClient, mqttPublishedCb);

        MQTT_OnData(&mqttClient, mqttDataCb);

}



void ICACHE_FLASH_ATTR

MQTT_InitConnection(MQTT_Client *mqttClient, uint8_t* host, uint32_t port, uint8_t security)

{

        uint32_t temp;

        INFO("MQTT_InitConnection\r\n");

        os_memset(mqttClient, 0, sizeof(MQTT_Client));

        temp = os_strlen(host);

        mqttClient->host = (uint8_t*)os_zalloc(temp + 1);

        os_strcpy(mqttClient->host, host);

        mqttClient->host[temp] = 0;

        mqttClient->port = port;

        mqttClient->security = security;



}



void ICACHE_FLASH_ATTR

MQTT_InitClient(MQTT_Client *mqttClient, uint8_t* client_id, uint8_t* client_user, uint8_t* client_pass, uint32_t keepAliveTime, uint8_t cleanSession)

{

        uint32_t temp;

        INFO("MQTT_InitClient\r\n");

        os_printf("CD MQTT_InitClient++++++++++++++++++++++\n");

        os_memset(&mqttClient->connect_info, 0, sizeof(mqtt_connect_info_t));



        temp = os_strlen(client_id);

        mqttClient->connect_info.client_id = (uint8_t*)os_zalloc(temp + 1);

        os_strcpy(mqttClient->connect_info.client_id, client_id);

        mqttClient->connect_info.client_id[temp] = 0;



        if (client_user)

        {

                temp = os_strlen(client_user);

                mqttClient->connect_info.username = (uint8_t*)os_zalloc(temp + 1);

                os_strcpy(mqttClient->connect_info.username, client_user);

                mqttClient->connect_info.username[temp] = 0;

        }



        if (client_pass)

        {

                temp = os_strlen(client_pass);

                mqttClient->connect_info.password = (uint8_t*)os_zalloc(temp + 1);

                os_strcpy(mqttClient->connect_info.password, client_pass);

                mqttClient->connect_info.password[temp] = 0;

        }





        mqttClient->connect_info.keepalive = keepAliveTime;

        mqttClient->connect_info.clean_session = cleanSession;



        mqttClient->mqtt_state.in_buffer = (uint8_t *)os_zalloc(MQTT_BUF_SIZE);

        mqttClient->mqtt_state.in_buffer_length = MQTT_BUF_SIZE;

        mqttClient->mqtt_state.out_buffer =  (uint8_t *)os_zalloc(MQTT_BUF_SIZE);

        mqttClient->mqtt_state.out_buffer_length = MQTT_BUF_SIZE;

        mqttClient->mqtt_state.connect_info = &mqttClient->connect_info;



        mqtt_msg_init(&mqttClient->mqtt_state.mqtt_connection, mqttClient->mqtt_state.out_buffer, mqttClient->mqtt_state.out_buffer_length);



        QUEUE_Init(&mqttClient->msgQueue, QUEUE_BUFFER_SIZE);



        system_os_task(MQTT_Task, MQTT_TASK_PRIO, mqtt_procTaskQueue, MQTT_TASK_QUEUE_SIZE);

        system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)mqttClient);

}

WiFi连接成功和失败会触发不同的回调函数:

/**

 *         MQTT连接回调

 */

void mqttConnectedCb(uint32_t *args) {

        MQTT_Client* client = (MQTT_Client*) args;



        INFO("MQTT: Connected\r\n");

        MQTT_Publish(client, birth_topic, BIRTH_MESSAGE, os_strlen(BIRTH_MESSAGE), 0,0);

        MQTT_Subscribe(client,ota_topic, 0);

        if(updata_status_check()){

                MQTT_Publish(client, ota_topic, "updata_finish", os_strlen("updata_finish"), 0,0);

        }

}

/**



 *         MQTT断开连接回调

 */

void mqttDisconnectedCb(uint32_t *args) {

        MQTT_Client* client = (MQTT_Client*) args;

        INFO("MQTT: Disconnected\r\n");

}



/**

 *         MQTT发布消息回调

 */

void mqttPublishedCb(uint32_t *args) {

        MQTT_Client* client = (MQTT_Client*) args;

        INFO("MQTT: Published\r\n");

}

串口透传:
当模块的WiFi和MQTT服务器都连接上之后,模块就开始监听串口和服务器的数据,如果串口有数据过来便转发到服务器或者进行OTA升级,如果服务器有指令下发就转发给串口。

/**

 *         MQTT接收数据回调(用于OTA升级和串口透传)

 */

void mqttDataCb(uint32_t *args, const char* topic, uint32_t topic_len,

                const char *data, uint32_t data_len) {

        char *topicBuf = (char*) os_zalloc(topic_len + 1), *dataBuf =

                        (char*) os_zalloc(data_len + 1);



        uint8 *pdata = (uint8*)data;

        uint16 len = data_len;

        uart0_tx_buffer(pdata, len);//串口输出



        MQTT_Client* client = (MQTT_Client*) args;



        os_memcpy(topicBuf, topic, topic_len);

        topicBuf[topic_len] = 0;



        os_memcpy(dataBuf, data, data_len);

        dataBuf[data_len] = 0;



//        INFO("Receive topic: %s, data: %s \r\n", topicBuf, dataBuf);



        //data = {"url"="http://yourdomain.com:9001/ota/"}

        if (os_strcmp(topicBuf, ota_topic) == 0) {

                char url_data[200];

                if(get_josn_str(dataBuf,"url",url_data)){

//            INFO("ota_start\n");

            ota_upgrade(url_data,ota_finished_callback);

                }

        }



        os_free(topicBuf);

        os_free(dataBuf);







}



/**

 *         ota升级回调

 */

void ICACHE_FLASH_ATTR ota_finished_callback(void * arg) {

        struct upgrade_server_info *update = arg;

        if (update->upgrade_flag == true) {

                INFO("OTA  Success ! rebooting!\n");

                system_upgrade_reboot();

        } else {

                INFO("OTA Failed!\n");

        }

}

其他问题:
连接的服务器地址,端口号等信息需要写在代码里烧录进模块,这些信息在在mqtt_config.h文件中定义。


上电后可以在串口助手看到打印的MAC地址:

按下配网按键(GPIO14接地),进入配网模式,使用APP或者微信公众号将信息发给模块便可联网,联网后自动连接MQTT服务器。
 
至此连接完成,后续只需要串口发数据给模块,便可在服务器收到信息,服务器下发指令,单片机串口也可以接收到数据。但是要记得订阅主题哦。该透传代码烧录完成可搭配任意MCU的串口使用。非常便捷。由于项目期较远,可能介绍的不是很详细,需要的大大们可以回帖获取源码。自行查看。
 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
玩转ESP8266 SDK编程【实例】, 第1集(公开):套件介绍 观看地址:http://v.youku.com/v_show/id_XMTUzNzM3ODg3Mg==.html 操作演示:http://pan.baidu.com/s/1dEGjwVv(视频演示全家福) 第2集(加密):组装WIFI模块,在面包板上搭建实验电路,下载厂家固件并调试 观看地址:http://v.youku.com/v_show/id_XMTUzNzM3NzY1Mg==.html (密码:021295) 操作演示:http://v.youku.com/v_show/id_XMTUzNjkzMDc5Mg==.html 第3集(加密):搭建开发环境,编写第一个程序:串口打印字符 观看地址:http://v.youku.com/v_show/id_XMTUzNzM3NzQ2NA==.html (密码:036370) 第4集(加密):LED闪烁(控制WIFI模块的GPIO口) 观看地址:http://v.youku.com/v_show/id_XMTUzODQ4Mzc4NA==.html (密码:048426) 操作演示:http://v.youku.com/v_show/id_XMTUzNjg2NjQzNg==.html 第5集(加密):按键控制LED(读取GPIO口的状态) 观看地址:http://v.youku.com/v_show/id_XMTUzODQ4Mzc1Mg==.html (密码:054524) 操作演示:http://v.youku.com/v_show/id_XMTUzNjg1MDU0MA==.html 第6集(加密):WIFI连接(把WIFI模块作为服务器,设置为AP模式) 观看地址:http://v.youku.com/v_show/id_XMTU0NjIyMjUxNg==.html (密码:064663) 第7集(加密):UDP通信(把WIFI模块作为服务器,UDP_Service) 观看地址:http://v.youku.com/v_show/id_XMTU0NjIyMjE2NA==.html (密码:071725) 第8集(加密):TCP通信(把WIFI模块作为服务器,TCP_Service,并用电脑控制LED) 观看地址:http://v.youku.com/v_show/id_XMTU0NjIyMTQwNA==.html (密码:082890) 第9集(加密):WIFI连接(把WIFI模块作为客户端,设置为STATION模式) 观看地址:http://v.youku.com/v_show/id_XMTU0NjIyMTM2OA==.html (密码:097974) 第10集(加密):UDP通信(把WIFI模块作为客户端,UDP_Client) 观看地址:http://v.youku.com/v_show/id_XMTU0NjIyMTM2NA==.html (密码:103054) 第11集(加密):TCP通信(把WIFI模块作为客户端,TCP_Client,并用手机控制LED) 观看地址:http://v.youku.com/v_show/id_XMTU0NjM3MDUwOA==.html (密码:112997) 操作演示:http://v.youku.com/v_show/id_XMTUzNjg1MDQ4OA==.html 第12集(加密):渐变灯(PWM) 观看地址:http://v.youku.com/v_show/id_XMTU1MTg5Mjg2NA==.html (密码:122826) 操作演示:http://v.youku.com/v_show/id_XMTUzNjg1MDQ2NA==.html 第13集(加密):七彩灯(PWM控制,并用手机控制LED) 观看地址:http://v.youku.com/v_show/id_XMTU1MTg5MjgwNA==.html (密码:133741) 操作演示:http://v.youku.com/v_show/id_XMTUzNjg1MDEwOA==.html 第14集(加密):光控灯(ADC) 观看地址:http://v.youku.com/v_show/id_XMTU1NDE0NTg5Mg==.html (密码:145693) 操作演示:http://v.youku.com/v_show/id_XMTUzNjg1MDAyMA==.html 第15集(加密):串口通讯(UART中断) 观看地址:http://v.youku.com/v_show/id_XMTU1NDE0NTg4MA==.html (密码:156528

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值