项目有些久远,前年的机器人上需要的功能,当时是需要将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空中升级(待验证) 从程序的入口开始:
//程序入口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);}/*** 按键长按回调*/local void icache_flash_attr key1longpress(void) { start_smartconfig(smartconfig_cd); info(start_smartconfig);}/*** 按键初始化*/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_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_attrsmartconfig_done(sc_status status, void *pdata) { switch (status) { case sc_status_wait: info(sc_status_wait); break; case sc_status_find_channel: info(sc_status_find_channel); break; case sc_status_getting_ssid_pswd: info(sc_status_getting_ssid_pswd); sc_type *type = pdata; if (*type == sc_type_esptouch) { info(sc_type:sc_type_esptouch); } else { info(sc_type:sc_type_airkiss); } break; case sc_status_link: info(sc_status_link); 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); 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, 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!); os_printf(----- wifi连接成功,打开绿灯---); gpio_output_set(gpio_id_pin(12), 1); mqtt_connect(&mqttclient);}/*** wifi断开回调*/void wifi_disconnect_cb(void){ info(wifi disconnect!); os_printf(----- wifi断开,关闭绿灯---); 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_attrmqtt_initconnection(mqtt_client *mqttclient, uint8_t* host, uint32_t port, uint8_t security){ uint32_t temp; info(mqtt_initconnection); 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_attrmqtt_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); os_printf(cd mqtt_initclient++++++++++++++++++++++); 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); 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);}/*** mqtt发布消息回调*/void mqttpublishedcb(uint32_t *args) { mqtt_client* client = (mqtt_client*) args; info(mqtt: published);} 串口透传:
当模块的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 , 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); 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!); system_upgrade_reboot(); } else { info(ota failed!); }} 其他问题:连接的服务器地址,端口号等信息需要写在代码里烧录进模块,这些信息在在mqtt_config.h文件中定义。
上电后可以在串口助手看到打印的mac地址:
按下配网按键(gpio14接地),进入配网模式,使用app或者微信公众号将信息发给模块便可联网,联网后自动连接mqtt服务器。
至此连接完成,后续只需要串口发数据给模块,便可在服务器收到信息,服务器下发指令,单片机串口也可以接收到数据。但是要记得订阅主题哦。该透传代码烧录完成可搭配任意mcu的串口使用。非常便捷。由于项目期较远,可能介绍的不是很详细,需要的大大们可以点击阅读原文回帖获取源码。自行查看。
原文标题:什么?单片机还在裸奔?esp8266纯串口透传,助力设备上云端
文章出处:【微信公众号:嵌入式arm】欢迎添加关注!文章转载请注明出处。
iPhone8什么时候上市?最新消息:自带BUG!iPhone8因黑科技太多竟然难产了?要推迟发布
东芝推出一款不一样的AR眼镜主要用于工作
星形三角形降压启动电路
逆天华为2016三部经典华为荣Magic、华为荣耀8、华为Mate9
基于三级通信组网的设计过程中的语音通话实施方案
ESP8266纯串口透传,助力设备上云端
功放理论基础:电源滤波电容的作用
车联网是噱头还是未来之光?会给我们的生活带来什么样的变化?
什么是G.652光缆
披着情怀的外衣来忽悠?诺基亚6京东首发仅1699元
阿里巴巴最强AI来袭:机器人客服的语调自然,人机对话有温度
无极无感电容解析
铠侠和西部数据推出第六代162层3D闪存技术
如何挑选适合自己项目的工业显示器
制造二维TMD晶体管面临的挑战
中国8月启动TD-LTE技术试验:不会与TD抢食
物联网技术的出现有什么价值
NVIDIA人工智能时代有机会威胁数据中心霸主Intel
如何构建低功耗自动驾驶系统?
拓邦股份继续加码锂电业务 3340万加码圆柱电池