搜索
12下一页
返回列表 发新帖本帖赏金 110.00元(功能说明)我要提问

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

[复制链接]
877|26
 楼主 | 2021-1-12 12:00 | 显示全部楼层 |阅读模式
本帖最后由 呐咯密密 于 2021-1-15 16:10 编辑

#申请原创#  @21小跑堂 @21小跑堂
项目有些久远,前年的机器人上需要的功能,当时是需要将STM32上的数据上传到服务器,比如机器人的速度,行驶距离,是否在拍照等等。便于管理者在PC或者手机上了解机器人的工作状态,同时可以远程下发指令给机器人,控制其完成相应动作。因为所有的逻辑判断和控制都在服务器或者STM32上面,作为中间的无线模块仅仅需要上传STM32的数据并接收服务器下发的指令即可,所以这里对WiFi模块的要求不高,仅仅需要它作为透传功能即可。当时在选型的时候试过好几款WiFi模块,最终敲定了安信可的ESP8266,价格便宜,开发简单,但是搭建环境是真的不容易,深受其害。选择好模块就该考虑使用AT指令还是使用SDK开发,AT指令固然简单,但是局限性非常大。如果使用AT指令,我那开发控制端的同事估计就要跳脚了,代码里需要写一大堆的AT指令,如果功能改变,指令代码就需要重写,烦不胜烦。如果使用SDK开发,控制端只需发送简单的数据就行,完全不用考虑其他任何东西,ESP8266完全当做一个中转站,相对应的我的工作就会繁重,但是,我屈服了,选择使用SDK。
于是就有了下面基于NONOS 2.0的ESP8266串口透传。主要有以下几个功能:
  • 纯串口透传,接收MCU串口数据,直接通过MQTT上传到服务器,接收服务器数据下发给MCU。
  • smartconfig+airkiss配网,随意使用,场景丰富。
  • 最多储存5个WIFI账号和密码,自动寻找网络连接。
  • 按键配网,长按重新配网,前一次WiFi自动储存,添加配网指示灯。
  • OTA空中升级(待验证)
从程序的入口开始:

  1. //程序入口
  2. void ICACHE_FLASH_ATTR user_init(void)
  3. {
  4.         uart_init(115200, 115200);
  5.         os_delay_us(60000);
  6.         keyInit();
  7.         set_uart_cb(uart_cb);

  8.         PIN_FUNC_SELECT(PERIPHS_IO_MUX_MTDI_U, FUNC_GPIO12); //GPIO12初始化
  9.         GPIO_OUTPUT_SET(GPIO_ID_PIN(12), 0);//低电平

  10.     get_mac();//获取MAC地址

  11.         wifi_set_opmode(STATION_MODE);
  12.         //设置wifi信息存储数量,最大为5个
  13.         wifi_station_ap_number_set(2);

  14.     mqtt_init();

  15.         set_wifistate_cb(wifi_connect_cb, wifi_disconnect_cb);
  16. }
复制代码


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

MAC.png
每连接一次WiFi都会将WiFi信息保存在模块内部,每次上电都会自动扫描暴露的WiFi,直接连接,就像手机的WIFI连接,目前最大支持五个WiFi信息的保存,超过5个会剔除最早的WiFi信息,通过短按D5(GPIO14)可进入配网模式。
  1. /**
  2. *         按键短按回调
  3. */
  4. LOCAL void ICACHE_FLASH_ATTR key1ShortPress(void) {

  5.         start_smartconfig(smartconfig_cd);
  6.         INFO("start_smartconfig\n");
  7. }
  8. /**
  9. *         按键长按回调
  10. */
  11. LOCAL void ICACHE_FLASH_ATTR key1LongPress(void) {

  12.         start_smartconfig(smartconfig_cd);
  13.         INFO("start_smartconfig\n");
  14. }
  15. /**
  16. *         按键初始化
  17. */
  18. LOCAL void ICACHE_FLASH_ATTR keyInit(void) {

  19.         //设置按键数量
  20.         set_key_num(1);
  21.         //长按、短按的按键回调
  22.         key_add(D5, NULL, key1ShortPress);
  23.         key_add(D5, NULL, key1LongPress);

  24. }

复制代码
由于找不到最新的代码。这里的长按我没做处理,应该是断开WiFi重新进入配网模式, 或者软复位模块,再进入start_smartconfig()函数:
  1. /**
  2. * 开始Smartconfig配置  
  3. * @param  cd: Smartconfig状态回调
  4. * @retval None
  5. */
  6. void ICACHE_FLASH_ATTR start_smartconfig(smartconfig_cd_t cd) {
  7.         smartconfig_flag = 1;
  8.         smartconfig_set_type(SC_TYPE_ESPTOUCH_AIRKISS); //SC_TYPE_ESPTOUCH,SC_TYPE_AIRKISS,SC_TYPE_ESPTOUCH_AIRKISS
  9.         wifi_station_disconnect();
  10.         wifi_set_opmode(STATION_MODE);
  11.         finish_cd = cd;
  12.         smartconfig_start(smartconfig_done);
  13.         os_timer_disarm(&OS_Timer_Wifichange);        // 关闭定时器

  14.         if(connect_flag == 1){
  15.                 w_disconnect();
  16.                 connect_flag = 0;
  17.         }

  18.         os_timer_disarm(&OS_Timer_SM);        // 关闭定时器
  19.         os_timer_setfn(&OS_Timer_SM, (os_timer_func_t *) sm_wait_time, NULL);// 设置定时器
  20.         os_timer_arm(&OS_Timer_SM, 1000, 1);  // 使能定时器
  21. }
复制代码

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

  1. /**
  2. * Smartconfig 状态处理
  3. * @param  status: 状态
  4. * @param  *pdata: AP数据
  5. * @retval None
  6. */
  7. void ICACHE_FLASH_ATTR
  8. smartconfig_done(sc_status status, void *pdata) {
  9.         switch (status) {
  10.         case SC_STATUS_WAIT:
  11.                 INFO("SC_STATUS_WAIT\n");
  12.                 break;
  13.         case SC_STATUS_FIND_CHANNEL:
  14.                 INFO("SC_STATUS_FIND_CHANNEL\n");
  15.                 break;
  16.         case SC_STATUS_GETTING_SSID_PSWD:
  17.                 INFO("SC_STATUS_GETTING_SSID_PSWD\n");
  18.                 sc_type *type = pdata;
  19.                 if (*type == SC_TYPE_ESPTOUCH) {
  20.                         INFO("SC_TYPE:SC_TYPE_ESPTOUCH\n");
  21.                 } else {
  22.                         INFO("SC_TYPE:SC_TYPE_AIRKISS\n");
  23.                 }
  24.                 break;
  25.         case SC_STATUS_LINK:
  26.                 INFO("SC_STATUS_LINK\n");
  27.                 sm_comfig_status = SM_STATUS_GETINFO;
  28.                 struct station_config *sta_conf = pdata;
  29.                 wifi_station_set_config(sta_conf);
  30.                 wifi_station_disconnect();
  31.                 wifi_station_connect();
  32.                 break;
  33.         case SC_STATUS_LINK_OVER:
  34.                 sm_comfig_status = SM_STATUS_FINISH;
  35.                 INFO("SC_STATUS_LINK_OVER\n");
  36.                 if (pdata != NULL) {
  37.                         //SC_TYPE_ESPTOUCH
  38.                         uint8 phone_ip[4] = { 0 };
  39.                         os_memcpy(phone_ip, (uint8*) pdata, 4);
  40.                         INFO("Phone ip: %d.%d.%d.%d\n", phone_ip[0], phone_ip[1],
  41.                                         phone_ip[2], phone_ip[3]);
  42.                 } else {
  43.                         //SC_TYPE_AIRKISS - support airkiss v2.0
  44.                         airkiss_start_discover();
  45.                 }
  46.                 smartconfig_stop();
  47.                 smartconfig_flag = 0;
  48.                 connect_flag = 0;
  49.                 os_timer_disarm(&OS_Timer_SM);        // 关闭定时器
  50.                 finish_cd(sm_comfig_status);
  51.                 os_timer_arm(&OS_Timer_Wifichange, 3000, 1);  // 使能定时器
  52.                 break;
  53.         }

  54. }

  55. /**
  56. *         WIFI连接回调
  57. */
  58. void wifi_connect_cb(void){

  59.         INFO("wifi connect!\r\n");
  60.         os_printf("----- WiFi连接成功,打开绿灯---\r\n");
  61.         GPIO_OUTPUT_SET(GPIO_ID_PIN(12), 1);
  62.         MQTT_Connect(&mqttClient);
  63. }

  64. /**
  65. *         WIFI断开回调
  66. */
  67. void wifi_disconnect_cb(void){
  68.         INFO("wifi disconnect!\r\n");
  69.         os_printf("----- WiFi断开,关闭绿灯---\r\n");
  70.         GPIO_OUTPUT_SET(GPIO_ID_PIN(12), 0);
  71.         MQTT_Disconnect(&mqttClient);
  72. }
复制代码




连接MQTT服务器

网络连接成功以后可以开始MQTT的初始化,初始化包涵一系列的连接初始化回调,连接成功或不成功回调,主题订阅发布回调等等。
  1. /**
  2. *         MQTT初始化
  3. */
  4. void ICACHE_FLASH_ATTR mqtt_init(void) {

  5.         MQTT_InitConnection(&mqttClient, MQTT_HOST, MQTT_PORT, DEFAULT_SECURITY);
  6.         MQTT_InitClient(&mqttClient, mac_str, MQTT_USER,MQTT_PASS, MQTT_KEEPALIVE, 1);
  7.         MQTT_InitLWT(&mqttClient, lwt_topic, LWT_MESSAGE, 0, 0);
  8.         MQTT_OnConnected(&mqttClient, mqttConnectedCb);
  9.         MQTT_OnDisconnected(&mqttClient, mqttDisconnectedCb);
  10.         MQTT_OnPublished(&mqttClient, mqttPublishedCb);
  11.         MQTT_OnData(&mqttClient, mqttDataCb);
  12. }

  13. void ICACHE_FLASH_ATTR
  14. MQTT_InitConnection(MQTT_Client *mqttClient, uint8_t* host, uint32_t port, uint8_t security)
  15. {
  16.         uint32_t temp;
  17.         INFO("MQTT_InitConnection\r\n");
  18.         os_memset(mqttClient, 0, sizeof(MQTT_Client));
  19.         temp = os_strlen(host);
  20.         mqttClient->host = (uint8_t*)os_zalloc(temp + 1);
  21.         os_strcpy(mqttClient->host, host);
  22.         mqttClient->host[temp] = 0;
  23.         mqttClient->port = port;
  24.         mqttClient->security = security;

  25. }

  26. void ICACHE_FLASH_ATTR
  27. MQTT_InitClient(MQTT_Client *mqttClient, uint8_t* client_id, uint8_t* client_user, uint8_t* client_pass, uint32_t keepAliveTime, uint8_t cleanSession)
  28. {
  29.         uint32_t temp;
  30.         INFO("MQTT_InitClient\r\n");
  31.         os_printf("CD MQTT_InitClient++++++++++++++++++++++\n");
  32.         os_memset(&mqttClient->connect_info, 0, sizeof(mqtt_connect_info_t));

  33.         temp = os_strlen(client_id);
  34.         mqttClient->connect_info.client_id = (uint8_t*)os_zalloc(temp + 1);
  35.         os_strcpy(mqttClient->connect_info.client_id, client_id);
  36.         mqttClient->connect_info.client_id[temp] = 0;

  37.         if (client_user)
  38.         {
  39.                 temp = os_strlen(client_user);
  40.                 mqttClient->connect_info.username = (uint8_t*)os_zalloc(temp + 1);
  41.                 os_strcpy(mqttClient->connect_info.username, client_user);
  42.                 mqttClient->connect_info.username[temp] = 0;
  43.         }

  44.         if (client_pass)
  45.         {
  46.                 temp = os_strlen(client_pass);
  47.                 mqttClient->connect_info.password = (uint8_t*)os_zalloc(temp + 1);
  48.                 os_strcpy(mqttClient->connect_info.password, client_pass);
  49.                 mqttClient->connect_info.password[temp] = 0;
  50.         }


  51.         mqttClient->connect_info.keepalive = keepAliveTime;
  52.         mqttClient->connect_info.clean_session = cleanSession;

  53.         mqttClient->mqtt_state.in_buffer = (uint8_t *)os_zalloc(MQTT_BUF_SIZE);
  54.         mqttClient->mqtt_state.in_buffer_length = MQTT_BUF_SIZE;
  55.         mqttClient->mqtt_state.out_buffer =  (uint8_t *)os_zalloc(MQTT_BUF_SIZE);
  56.         mqttClient->mqtt_state.out_buffer_length = MQTT_BUF_SIZE;
  57.         mqttClient->mqtt_state.connect_info = &mqttClient->connect_info;

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

  59.         QUEUE_Init(&mqttClient->msgQueue, QUEUE_BUFFER_SIZE);

  60.         system_os_task(MQTT_Task, MQTT_TASK_PRIO, mqtt_procTaskQueue, MQTT_TASK_QUEUE_SIZE);
  61.         system_os_post(MQTT_TASK_PRIO, 0, (os_param_t)mqttClient);
  62. }
复制代码


WiFi连接成功和失败会触发不同的回调函数:
  1. /**
  2. *         MQTT连接回调
  3. */
  4. void mqttConnectedCb(uint32_t *args) {
  5.         MQTT_Client* client = (MQTT_Client*) args;

  6.         INFO("MQTT: Connected\r\n");
  7.         MQTT_Publish(client, birth_topic, BIRTH_MESSAGE, os_strlen(BIRTH_MESSAGE), 0,0);
  8.         MQTT_Subscribe(client,ota_topic, 0);
  9.         if(updata_status_check()){
  10.                 MQTT_Publish(client, ota_topic, "updata_finish", os_strlen("updata_finish"), 0,0);
  11.         }
  12. }
  13. /**

  14. *         MQTT断开连接回调
  15. */
  16. void mqttDisconnectedCb(uint32_t *args) {
  17.         MQTT_Client* client = (MQTT_Client*) args;
  18.         INFO("MQTT: Disconnected\r\n");
  19. }

  20. /**
  21. *         MQTT发布消息回调
  22. */
  23. void mqttPublishedCb(uint32_t *args) {
  24.         MQTT_Client* client = (MQTT_Client*) args;
  25.         INFO("MQTT: Published\r\n");
  26. }
复制代码


串口透传:
当模块的WiFi和MQTT服务器都连接上之后,模块就开始监听串口和服务器的数据,如果串口有数据过来便转发到服务器或者进行OTA升级,如果服务器有指令下发就转发给串口。
  1. /**
  2. *         MQTT接收数据回调(用于OTA升级和串口透传)
  3. */
  4. void mqttDataCb(uint32_t *args, const char* topic, uint32_t topic_len,
  5.                 const char *data, uint32_t data_len) {
  6.         char *topicBuf = (char*) os_zalloc(topic_len + 1), *dataBuf =
  7.                         (char*) os_zalloc(data_len + 1);

  8.         uint8 *pdata = (uint8*)data;
  9.         uint16 len = data_len;
  10.         uart0_tx_buffer(pdata, len);//串口输出

  11.         MQTT_Client* client = (MQTT_Client*) args;

  12.         os_memcpy(topicBuf, topic, topic_len);
  13.         topicBuf[topic_len] = 0;

  14.         os_memcpy(dataBuf, data, data_len);
  15.         dataBuf[data_len] = 0;

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

  17.         //data = {"url"="http://yourdomain.com:9001/ota/"}
  18.         if (os_strcmp(topicBuf, ota_topic) == 0) {
  19.                 char url_data[200];
  20.                 if(get_josn_str(dataBuf,"url",url_data)){
  21. //            INFO("ota_start\n");
  22.             ota_upgrade(url_data,ota_finished_callback);
  23.                 }
  24.         }

  25.         os_free(topicBuf);
  26.         os_free(dataBuf);



  27. }

  28. /**
  29. *         ota升级回调
  30. */
  31. void ICACHE_FLASH_ATTR ota_finished_callback(void * arg) {
  32.         struct upgrade_server_info *update = arg;
  33.         if (update->upgrade_flag == true) {
  34.                 INFO("OTA  Success ! rebooting!\n");
  35.                 system_upgrade_reboot();
  36.         } else {
  37.                 INFO("OTA Failed!\n");
  38.         }
  39. }
复制代码



其他问题:
连接的服务器地址,端口号等信息需要写在代码里烧录进模块,这些信息在在mqtt_config.h文件中定义。
微信图片_20210112110039.png
上电后可以在串口助手看到打印的MAC地址:
微信图片_20210112110648.png
按下配网按键(GPIO14接地),进入配网模式,使用APP或者微信公众号将信息发给模块便可联网,联网后自动连接MQTT服务器。
微信图片_20210112115221.png 微信图片_20210112120946.png
至此连接完成,后续只需要串口发数据给模块,便可在服务器收到信息,服务器下发指令,单片机串口也可以接收到数据。但是要记得订阅主题哦。该透传代码烧录完成可搭配任意MCU的串口使用。非常便捷。由于项目期较远,可能介绍的不是很详细,需要的大大们可以回帖获取源码。自行查看。
游客,如果您要查看本帖隐藏内容请回复



使用特权

评论回复

打赏榜单

lhkjg 打赏了 10.00 元 2021-02-04

21小跑堂 打赏了 100.00 元 2021-01-15
理由:恭喜通过原创奖审核!请多多加油哦!

相关帖子

| 2021-1-15 14:51 | 显示全部楼层
感谢分享!

使用特权

评论回复
| 2021-1-16 19:37 | 显示全部楼层
学习学习

使用特权

评论回复
| 2021-1-16 22:12 | 显示全部楼层
谢谢分享!

使用特权

评论回复
| 2021-1-17 10:24 | 显示全部楼层
感谢分享!求抱大佬大佬

使用特权

评论回复
| 2021-1-17 21:11 | 显示全部楼层
学习学习

使用特权

评论回复
| 2021-1-17 21:22 | 显示全部楼层
学习了学习了!

使用特权

评论回复
| 2021-1-18 19:35 | 显示全部楼层

使用特权

评论回复
| 2021-1-20 16:05 | 显示全部楼层
看看,学习学习!!!!!!!!!!!

使用特权

评论回复
| 2021-1-22 15:00 | 显示全部楼层
请教一下,MQTT是如何实现的?有现成的程序?

使用特权

评论回复

评论

呐咯密密 2021-1-22 15:11 回复TA
可以下载查看源代码,程序我已用于产品,稳定的 
| 2021-1-22 15:13 | 显示全部楼层
laocuo1142 发表于 2021-1-22 15:00
请教一下,MQTT是如何实现的?有现成的程序?

MQTT是程序上的吧?

使用特权

评论回复
| 2021-1-24 00:53 | 显示全部楼层
非常有用的资料

使用特权

评论回复
| 2021-1-25 15:49 | 显示全部楼层
感谢分享

使用特权

评论回复
| 2021-1-25 16:49 | 显示全部楼层
  不错的想法,有一定的参考意义,在做一个4G Mqtt方案!

使用特权

评论回复
| 2021-1-25 19:36 | 显示全部楼层
感谢分享!

使用特权

评论回复
| 2021-1-26 16:50 | 显示全部楼层
学习一下

使用特权

评论回复
| 2021-1-27 13:25 | 显示全部楼层
多谢分享1

使用特权

评论回复
| 2021-1-28 15:43 | 显示全部楼层

非常有用的资料!

使用特权

评论回复
| 2021-1-28 21:28 | 显示全部楼层
不错

使用特权

评论回复
| 2021-2-2 11:24 | 显示全部楼层
为什么一定要回复

使用特权

评论回复

评论

呐咯密密 2021-2-2 11:44 回复TA
私心作祟,骗个回复而已 
扫描二维码,随时随地手机跟帖
12下一页
返回列表 发新帖 本帖赏金 110.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

我要发帖 我要提问 投诉建议 申请版主

快速回复

您需要登录后才可以回帖
登录 | 注册
高级模式

论坛热帖

关闭

热门推荐上一条 /6 下一条

在线客服 快速回复 返回顶部 返回列表