物联网学习之旅:微信小程序控制STM32(二)--ESP8266连接mqtt服务端
ESP8266连接mqtt服务端
ESP8266连接mqtt服务端
微信小程序控制STM32的第二步,就是要编写ESP8266固件,来连接已经搭建好的物联网服务器。也有别的方式,只要你足够了解mqtt协议,可以通过使用ESP8266原生的AT指令来连接,因为ESP8266连接mqtt服务端中mqtt协议也是通过建立TCP连接,发送相应的协议报文来连接服务端。但是乐鑫官方提供的SDK中含有连接mqtt服务端的实例代码,要做的就是根据自己的功能要求去修改代码。
我修改的固件是ESP8266与STM32之间进行串口通信,以透传的方式互相传递数据,然后ESP8266作为中转把数据发到相应的目的地。
- 硬件准备
我用的是ESP8266-01这款模块
ESP8266有UART 下载模式和Flash 运行模式 ,接线不一样的地方就是在下载模式时GPIO0要接低电平,运行模式时悬空即可,每次运行模式和下载模式切换都需要重启一下模块,才能第二次烧写或者运行。
我是采用这样的连接方式来烧写固件,运行程序时把GPIO0拔掉,然后拔掉esp8266的vcc线,然后又插上,重启一下就可以了。烧写的时候把GPIO0接共地端,然后拔掉esp8266的vcc线,然后又插上,重启一下,就可以烧写程序了。至于我为什么要用这么复杂的接线方式来烧写程序,那是因为买这个esp8266调试器的时候,不懂,图便宜(根本也是因为我太穷了,哈哈哈),然而这个用来运行调试是最合适的,直接插上去即可。不过这样也能烧写固件。也可以用TTL转串口来烧写,都是可以的,原理都一样。 - 搭建ESP8266开发环境
进入安信可ESP8266 系列模组专题,按照开发环境搭建的步骤搭建开发环境。
点击蓝色文字下载ESP8266_NONOS_SDK-v3.0.0,也可以选择在乐鑫官网上下载最新版本的SDK,根据需要下载开发文档,我用到了这两个文档。
值得注意的是,本项目用到的是esp_mqtt_proj这个例程,所以在构建工程文件时,需要将examples/esp_mqtt_proj目录工程目录的顶层文件中,其它的按照安信可提供的步骤搭建即可,编译没有错误之后,其实example文件夹就可以删除掉了,这个项目只用到mqtt部分,其它例程删掉后编译仍然是成功的。 - 编写ESP8266固件
打开编译通过的工程,需要修改的有这几个地方
1)user_main.c中修改
void mqttConnectedCb(uint32_t *args)
{
MQTT_Client* client = (MQTT_Client*)args;
mqttclient=client;
INFO("MQTT: Connected\r\n");
MQTT_Subscribe(client, "Topic0", 0); //订阅Topic0主题,用于接收微信小程序的数据
MQTT_Publish(client, "Topic1", "hello1", 6, 0, 0); //发布消息
//Topic0用于下行通道,Topic1用于上行通道
}
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);
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;
os_printf("%s\r\n",dataBuf); //串口打印接收到的消息
os_free(topicBuf);
os_free(dataBuf);
}
MQTT_Client* mqttclient; //此指针会用在uart.c中
2)uart.c中修改
通过user_main.c文件中的uart_init()定位到uart.c文件,然后对这里面进行修改。首先需要引入user_main.c定义的“MQTT_Client* mqttclient;”,这样下面在串口处理函数中,当从串口接收完数据之后,通过引入的mqtt客户端指针,将数据发到特定的主题,然后别忘了引入“mqtt.h”,因为这里要用到这里面的函数,将数据发送出去。
extern MQTT_Client* mqttclient;
最后修改串口中断处理函数。
LOCAL void
uart0_rx_intr_handler(void *para)
{
/* uart0 and uart1 intr combine togther, when interrupt occur, see reg 0x3ff20020, bit2, bit0 represents
* uart1 and uart0 respectively
*/
uint8_t RcvChar[256];
uint8_t uart_no = UART0;//UartDev.buff_uart_no;
uint8_t fifo_len = 0;
uint8_t buf_idx = 0;
uint8_t temp, cnt;
if (UART_FRM_ERR_INT_ST == (READ_PERI_REG(UART_INT_ST(uart_no)) & UART_FRM_ERR_INT_ST)) {
DBG1("FRM_ERR\r\n");
WRITE_PERI_REG(UART_INT_CLR(uart_no), UART_FRM_ERR_INT_CLR);
} else if (UART_RXFIFO_FULL_INT_ST == (READ_PERI_REG(UART_INT_ST(uart_no)) & UART_RXFIFO_FULL_INT_ST)) {
DBG("f");
uart_rx_intr_disable(UART0);
WRITE_PERI_REG(UART_INT_CLR(UART0), UART_RXFIFO_FULL_INT_CLR);
system_os_post(uart_recvTaskPrio, 0, 0);
} else if (UART_RXFIFO_TOUT_INT_ST == (READ_PERI_REG(UART_INT_ST(uart_no)) & UART_RXFIFO_TOUT_INT_ST)) { //检测到是超时中断
fifo_len=(READ_PERI_REG(UART_STATUS(UART0))>>UART_RXFIFO_CNT_S)&UART_RXFIFO_CNT; //获取数据长度
buf_idx=0;
for(buf_idx=0;buf_idx<fifo_len;buf_idx++){
RcvChar[buf_idx]=READ_PERI_REG(UART_FIFO(UART0))&0xFF; //将数据储存起来
}
if(RcvChar[0]=='A'){ //接收到询问是否初始化完成的消息
if(mqttState==1){ //初始化完成了
os_printf("1111\r\n"); //串口发送初始化完成,如果发送字母就会乱码
}else{
os_printf("2222\r\n");
}
}
if(RcvChar[0]=='{'){
MQTT_Publish(mqttclient, "Topic1", RcvChar, fifo_len, 0, 0); //把接收到的数据发送到mqtt服务端
}
if(RcvChar[0]=='T'){
os_printf("\r\ntime:%s\r\n", time);
}
os_memset(RcvChar,0,256); //清除数据
WRITE_PERI_REG(UART_INT_CLR(UART0), UART_RXFIFO_TOUT_INT_CLR); //清除超时中断状态
} else if (UART_TXFIFO_EMPTY_INT_ST == (READ_PERI_REG(UART_INT_ST(uart_no)) & UART_TXFIFO_EMPTY_INT_ST)) {
DBG("e");
CLEAR_PERI_REG_MASK(UART_INT_ENA(UART0), UART_TXFIFO_EMPTY_INT_ENA);
#if UART_BUFF_EN
tx_start_uart_buffer(UART0);
#endif
//system_os_post(uart_recvTaskPrio, 1, 0);
WRITE_PERI_REG(UART_INT_CLR(uart_no), UART_TXFIFO_EMPTY_INT_CLR);
} else if (UART_RXFIFO_OVF_INT_ST == (READ_PERI_REG(UART_INT_ST(uart_no)) & UART_RXFIFO_OVF_INT_ST)) {
WRITE_PERI_REG(UART_INT_CLR(uart_no), UART_RXFIFO_OVF_INT_CLR);
DBG1("RX OVF!!\r\n");
}
}
问为什么要这样改,官方文档中做出了说明,参考esp8266技术参考手册,串口中断状态分为:接收 full 中断、接收溢出中断、接收超时中断 tout、发送 fifo 空中断、流量量控制状态中断。我只对接收超时中断进行了配置,因为这个足够满足需求,若考虑全面,可以适当增加。手册中也有一个中断处理函数的例程,参照这个修改。
上面代码的注释也写的很清楚了。
3)修改mqtt_config.h
/*只有在持有人标识和之前不同的情况下才会更新系统参数,这就是为什么在配置文件这里修改MQTThost等内容时,烧进去的程序没有变化*/
#define CFG_HOLDER 0x00FF55A4 /* Change this value to load default configurations */
#define MQTT_HOST "0.0.0.0" //改成自己服务器的公网IP
#define MQTT_PORT 1883 // the listening port of your MQTT server or MQTT broker
#define MQTT_CLIENT_ID "esp8266" // 自定义的clientID
#define MQTT_USER "admin" // your MQTT login name, if MQTT server allow anonymous login,any string is OK, otherwise, please input valid login name which you had registered
#define MQTT_PASS "public" // you MQTT login password, same as above
#define STA_SSID "**********" //填自己的SSID
#define STA_PASS "**********" // 你的AP/路由器的密码
记得要修改CFG_HOLDER,随便更改某一个十六进制数就行,否则出现修改的参数不生效的后果,我也是一步一步采坑过来的。
不过还有一个坑,我现在才说,目的就是为了经历遇到问题,解决问题的过程。遇到问题之后自己解决,这样可以学到更多的东西。修改完中断处理函数之后,调试时,似乎根本就不会进入修改好的中断处理函数,找了各种原因:是不是没有配置好串口的相关寄存器啊,是不是mqttclient不能传递到另一个文件中使用啊,等等等。最后找到答案,ESP8266 NONOS_SDK-3.0 开发中官方例程 UART0串口进入不了中断问题。因为工程构建得有问题,esp_mqtt_proj目录中虽然有uart.h头文件,但是根本不含有uart.c文件,我们所修改的不过是没有引入到项目中的uart.c文件。所以我的解决方式是:
1)将driver_lib下的driver复制到esp_mqtt_proj项目文件夹内
2)在Makefile文件中加入相应的库文件目录。
把对应的二进制文件烧录进esp8266
4. 调试效果
1)连接好ESP8266和串口调试器,插到电脑USB口上。
2)打开串口调试助手(安信可官网开发工具清单中下载)。
这样表示mqtt服务端连接成功。
3)模拟一下微信小程序与ESP8266之间进行通信,首先在MQTTBox上添加mqtt客户端用以模拟微信小程序端,配置如下:
在MQTTBox上发布消息
可以看到esp8266串口中打印出来了消息,如果esp8266接在STM32上,STM32只需要读取串口数据就可以获取到微信小程序发来的数据了、
esp8266转发串口数据(AT+RST)
可以看到微信小程序订阅的Topic1主题,接收到了消息。
这样这个环节就OK了。
更新内容
问题:在最开始做这个项目的时候,有一个问题没注意,后来调试之后,突然发现一个问题,当Esp8266和STM32同时上电之后,不会发送上线消息,然后微信小程序就不会检测到STM32是否上线,进而无法控制STM32。这是因为在上电后,stm32和esp8266各自执行自己的代码,没有联系,所以STM32初始化过程中发送在线信息时,ESP8266还没有连接上服务器,然后向串口发送在线信息时,ESP8266不会转发到主题上去。
解决方法:更改STM32程序,在STM32初始化时,向ESP8266串口发送询问数据,询问ESP8266是否连接上服务器。更新ESP8266固件,当串口接收到询问数据后,在串口返回初始化完毕的状态。
更改后的代码贴在下面。
user_main.c
void user_init(void)
{
uart_init(BIT_RATE_115200, BIT_RATE_115200);
os_delay_us(60000);
uart_rx_intr_disable(UART0); //先关闭串口中断,待连接上服务器后再开启,防止在连接服务器时,不断收到询问是否初始化完成的数据
CFG_Load();
os_printf("\r\nSDKversion:%s\r\n",system_get_sdk_version());
MQTT_InitConnection(&mqttClient, sysCfg.mqtt_host, sysCfg.mqtt_port, sysCfg.security);
MQTT_InitClient(&mqttClient, sysCfg.device_id, sysCfg.mqtt_user, sysCfg.mqtt_pass, sysCfg.mqtt_keepalive, 1);
MQTT_InitLWT(&mqttClient, "/lwt", "offline", 0, 0);
MQTT_OnConnected(&mqttClient, mqttConnectedCb);
MQTT_OnDisconnected(&mqttClient, mqttDisconnectedCb);
MQTT_OnPublished(&mqttClient, mqttPublishedCb);
MQTT_OnData(&mqttClient, mqttDataCb);
WIFI_Connect(sysCfg.sta_ssid, sysCfg.sta_pwd, wifiConnectCb);
INFO("\r\nSystem started ...\r\n");
}
在mqtt.c里面mqtt_tcpclient_recv函数,当订阅成功之后表示连接服务器成功,表示可以接收询问数据了
case MQTT_MSG_TYPE_SUBACK:
if (client->mqtt_state.pending_msg_type == MQTT_MSG_TYPE_SUBSCRIBE && client->mqtt_state.pending_msg_id == msg_id){
mqttState=1;
os_delay_us(60000);
uart_rx_intr_enable(UART0);
}
break;
个人能力有限,有错误的地方欢迎指正,有问题也可以提,可以一起探讨一下
更多推荐
所有评论(0)