MQTT移植参考韦东山老师视频课:
https://video.100ask.net/p/t_pc/course_pc_detail/big_column/p_627a3ae7e4b01a4851fd107f
硬件基于洋桃IOT开发板,STM32F103C8T6;
软件基于FreeRTOS_ESP8266_MQTT_SourceCode
,已经在CubeMX上配置好FreeRTOS;
MQTT使用杰杰作者的MATT源码mqttclient
,地址是“https://github.com/jiejieTop/mqttclient.git”
STM32F103_RTOS_MQTT
工程在MCU上测试ESP8266,连接WIFI,连接TCP服务器并收发数据:ESP8266 AT命令:
1、WIFI模式配置,AP模式
AT+CWMODE=1
2、列出当前热点
AT+CWLAP
5、接入热点
AT+CWJAP_DEF="TP-LINK_4522","1234567890"
4、设置连接模式
AT+CIPMUX=0
5、创建TCP连接
AT+CIPSTART="TCP","112.125.89.8",44324
6、发送TCP数据
AT+CIPSEND=5
7、断开TCP连接
AT+CIPCLOSE
8、断开热点
AT+CWQAP
mqttclient
源码目录复制到FreeRTOS_ESP8266_MQTT
工程mqttclient
源码中保留‘common’、‘mqtt’、‘mqttclient’、‘network’、‘platform’目录‘common’中仅添加mqtt_list.c和random.c文件;‘network’中仅添加nettype_tcp.c、nettype_tls.c、network.c文件
将mqttclient
源码增加到工程中后对工程进行编译,记录编译报错及解决过程:
头文件"mqtt_config.h"在test目录下,注释后继续编译
自定义MQTT_NETWORK_TYPE_NO_TLS宏,再次编译
韦东山老师移植完成的代码中没有找到lwip系列头文件被注释掉了,注释后继续编译
包含头文件<stdio.h>,再次编译
同样增加一个socklen_t的定义,再次编译
删除此文件中的各个接口,后期使用ESP8266 AT命令来实现各个接口,再次编译
在此宏定义的2个文件处修改一下,再次编译
将plooc_class.h文件路径添加到工程中,再次编译
在plooc_class.h中自己定义增加宏定义PLOOC_CFG_REMOVE_MEMORY_LAYOUT_BOUNDARY___USE_WITH_CAUTION___
后编译通过
参考文章:https://blog.csdn.net/studyingdda/article/details/135428348?spm=1001.2014.3001.5501
ESP8266驱动程序分成4层,用于隔离底层硬件和网络层:
ESP8266 AT命令发送和接收处理代码中有2个任务,2个信号量:
不使用CubeMX自动生成的USART3_IRQHandler代码,自己定义uart3的中断接收函数
stm32_uart3.h代码:
#ifndef _STM32_UART3_H
#define _STM32_UART3_H
void USART3_Write(char *buf, int len);
void USART3_Read(char *c, int timeout);
#endif
stm32_uart3.c代码:
#include "driver_usart.h"
#include <stdio.h>
#include <ring_buffer.h>
static ring_buffer uart3_buffer; //创建uart3的环形缓冲buffer
extern UART_HandleTypeDef huart3;
void USART3_IRQHandler(void)
{
/* 如果发生的是RX中断
* 把数据读出来, 存入环形buffer
*/
uint32_t isrflags = READ_REG(huart3.Instance->SR);
uint32_t cr1its = READ_REG(huart3.Instance->CR1);
char c;
/* UART in mode Receiver -------------------------------------------------*/
/* USART_SR_RXNE接收不为空; USART_CR1_RXNEIE使能接收中断. */
if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
{
c = huart3.Instance->DR; //将DR数据寄存器中的数据放入变量c
ring_buffer_write(c, &uart3_buffer); //将c写入环形缓冲区
return;
}
}
void USART3_Write(char *buf, int len)
{
int i = 0;
while (i < len)
{
/* 等待数据发送寄存器空 */
while ((huart3.Instance->SR & USART_SR_TXE) == 0);
huart3.Instance->DR = buf[i];
i++;
}
}
void USART3_Read(char *c, int timeout)
{
while (1)
{
if (0 == ring_buffer_read((unsigned char *)c, &uart3_buffer))
return;
else
{
}
}
}
ring_buffer.h代码
/* Copyright (s) 2019 深圳百问网科技有限公司
* All rights reserved
*
* 文件名称:ring_buffer.h
* 摘要:
*
* 修改历史 版本号 Author 修改内容
*--------------------------------------------------
* 2021.8.21 v01 百问科技 创建文件
*--------------------------------------------------
*/
#ifndef __RING_BUFFER_H
#define __RING_BUFFER_H
#include "stm32f1xx_hal.h"
#define BUFFER_SIZE 1024 /* 环形缓冲区的大小 */
typedef struct
{
volatile unsigned int pW; /* 写地址 */
volatile unsigned int pR; /* 读地址 */
unsigned char buffer[BUFFER_SIZE]; /* 缓冲区空间 */
} ring_buffer;
/*
* 函数名:void ring_buffer_init(ring_buffer *dst_buf)
* 输入参数:dst_buf --> 指向目标缓冲区
* 输出参数:无
* 返回值:无
* 函数作用:初始化缓冲区
*/
extern void ring_buffer_init(ring_buffer *dst_buf);
/*
* 函数名:void ring_buffer_write(unsigned char c, ring_buffer *dst_buf)
* 输入参数:c --> 要写入的数据
* dst_buf --> 指向目标缓冲区
* 输出参数:无
* 返回值:无
* 函数作用:向目标缓冲区写入一个字节的数据,如果缓冲区满了就丢掉此数据
*/
extern void ring_buffer_write(unsigned char c, ring_buffer *dst_buf);
/*
* 函数名:int ring_buffer_read(unsigned char *c, ring_buffer *dst_buf)
* 输入参数:c --> 指向将读到的数据保存到内存中的地址
* dst_buf --> 指向目标缓冲区
* 输出参数:无
* 返回值:读到数据返回0,否则返回-1
* 函数作用:从目标缓冲区读取一个字节的数据,如果缓冲区空了返回-1表明读取失败
*/
extern int ring_buffer_read(unsigned char *c, ring_buffer *dst_buf);
#endif /* __RING_BUFFER_H */
ring_buffer.c代码
/* Copyright (s) 2019 深圳百问网科技有限公司
* All rights reserved
*
* 文件名称:ring_buffer.c
* 摘要:
*
* 修改历史 版本号 Author 修改内容
*--------------------------------------------------
* 2021.8.21 v01 百问科技 创建文件
*--------------------------------------------------
*/
#include "ring_buffer.h"
/*
* 函数名:void ring_buffer_init(ring_buffer *dst_buf)
* 输入参数:dst_buf --> 指向目标缓冲区
* 输出参数:无
* 返回值:无
* 函数作用:初始化缓冲区
*/
void ring_buffer_init(ring_buffer *dst_buf)
{
/* 唤醒缓冲区初始化,将读写指针设置为0 */
dst_buf->pW = 0;
dst_buf->pR = 0;
}
/*
* 函数名:void ring_buffer_write(unsigned char c, ring_buffer *dst_buf)
* 输入参数:c --> 要写入的数据
* dst_buf --> 指向目标缓冲区
* 输出参数:无
* 返回值:无
* 函数作用:向目标缓冲区写入一个字节的数据,如果缓冲区满了就丢掉此数据
*/
void ring_buffer_write(unsigned char c, ring_buffer *dst_buf)
{
/* 获取环形缓冲区写指针的下一个位置 */
int i = (dst_buf->pW + 1) % BUFFER_SIZE;
/*
如果环形缓冲区写指针的下一个位置和读指针不相等代表环形缓冲区未写满,若写满则数据直
接丢弃 */
if(i != dst_buf->pR) // 环形缓冲区没有写满
{
/* 将字符C写到唤醒缓冲区写指针的位置 */
dst_buf->buffer[dst_buf->pW] = c;
/* 将环形缓冲区的写指针更新为下一个写位置 */
dst_buf->pW = i;
}
}
/*
* 函数名:int ring_buffer_read(unsigned char *c, ring_buffer *dst_buf)
* 输入参数:c --> 指向将读到的数据保存到内存中的地址
* dst_buf --> 指向目标缓冲区
* 输出参数:无
* 返回值:读到数据返回0,否则返回-1
* 函数作用:从目标缓冲区读取一个字节的数据,如果缓冲区空了返回-1表明读取失败
*/
int ring_buffer_read(unsigned char *c, ring_buffer *dst_buf)
{
/* 如果环形缓冲区当前的读指针与写指针位置相同表示当前环形缓冲区为空 */
if(dst_buf->pR == dst_buf->pW)
{
return -1;
}
else
{
/* 将当前环形缓冲区读指针位置的数据传给字符c的地址 */
*c = dst_buf->buffer[dst_buf->pR];
/* 将环形缓冲区读指针的位置更新为下一个读位置 */
dst_buf->pR = (dst_buf->pR + 1) % BUFFER_SIZE;
return 0;
}
}
driver_usart.h代码
/* Copyright (s) 2019 深圳百问网科技有限公司
* All rights reserved
*
* 文件名称:driver_usart.h
* 摘要:
*
* 修改历史 版本号 Author 修改内容
*--------------------------------------------------
* 2020.6.6 v01 百问科技 创建文件
*--------------------------------------------------
*/
#ifndef __DRIVER_USART_H
#define __DRIVER_USART_H
#include "stm32f1xx_hal.h"
/*
* 函数名:void EnableDebugIRQ(void)
* 输入参数:无
* 输出参数:无
* 返回值:无
* 函数作用:使能USART1的中断
*/
extern void EnableDebugIRQ(void);
extern void EnableUART3IRQ(void);
/*
* 函数名:void DisableDebugIRQ(void)
* 输入参数:无
* 输出参数:无
* 返回值:无
* 函数作用:失能USART1的中断
*/
extern void DisableDebugIRQ(void);
#endif /* __DRIVER_USART_H */
driver_usart.h代码
/* Copyright (s) 2019 深圳百问网科技有限公司
* All rights reserved
*
* 文件名称:driver_usart.c
* 摘要:
*
* 修改历史 版本号 Author 修改内容
*--------------------------------------------------
* 2021.8.21 v01 百问科技 创建文件
*--------------------------------------------------
*/
#include "driver_usart.h"
#include "usart.h"
#include "main.h"
#include "ring_buffer.h"
#include <stdio.h>
static volatile uint8_t txcplt_flag = 0; // 发送完成标志,1完成0未完成
static volatile uint8_t rxcplt_flag = 0; // 接收完成标志,1完成0未完成
static volatile uint8_t rx_data = 0;
/*
* 函数名:void EnableDebugIRQ(void)
* 输入参数:无
* 输出参数:无
* 返回值:无
* 函数作用:使能USART1的中断
*/
void EnableDebugIRQ(void)
{
HAL_NVIC_SetPriority(USART1_IRQn, 0, 1); // 设置USART1中断的优先级
HAL_NVIC_EnableIRQ(USART1_IRQn); // 使能USART1的中断
__HAL_UART_ENABLE_IT(&huart1, UART_IT_TC | UART_IT_RXNE); // 使能USRAT1的发送和接收中断
}
void EnableUART3IRQ(void)
{
HAL_NVIC_SetPriority(USART3_IRQn, 15, 0); // 设置USART3中断的优先级
HAL_NVIC_EnableIRQ(USART3_IRQn); // 使能USART3的中断
huart3.Instance->SR &= ~(USART_SR_RXNE); //清除数据接收寄存器,否则使能中断后会立刻进入一次中断
__HAL_UART_ENABLE_IT(&huart3, UART_IT_RXNE); // 使能USRAT3的接收中断
}
/*
* 函数名:void DisableDebugIRQ(void)
* 输入参数:无
* 输出参数:无
* 返回值:无
* 函数作用:失能USART1的中断
*/
void DisableDebugIRQ(void)
{
__HAL_UART_DISABLE_IT(&huart1, UART_IT_TC | UART_IT_RXNE); // 失能USRAT1的发送和接收中断
HAL_NVIC_DisableIRQ(USART1_IRQn); // 失能USART1的中断
}
/*
* 函数名:int fputc(int ch, FILE *f)
* 输入参数:ch --> 要输出的数据
* 输出参数:无
* 返回值:无
* 函数作用:printf/putchar 标准输出函数的底层输出函数
*/
int fputc(int ch, FILE *f)
{
txcplt_flag = 0;
HAL_UART_Transmit_IT(&huart1, (uint8_t*)&ch, 1);
while(txcplt_flag==0);
return ch;
}
/*
* 函数名:int fgetc(FILE *f)
* 输入参数:
* 输出参数:无
* 返回值:接收到的数据
* 函数作用:scanf/getchar 标准输出函数的底层输出函数
*/
int fgetc(FILE *f)
{
char c = 0;
while(ring_buffer_read((unsigned char *)&c, &test_buffer) != 0);
return c;
}
/*
* 函数名:void USART1_IRQHandler(void)
* 输入参数:无
* 输出参数:无
* 返回值:无
* 函数作用:USART1的中断服务函数
*/
void USART1_IRQHandler(void)
{
unsigned char c = 0;
if((USART1->SR &(1<<5)) != 0) // 判断USART1的状态寄存器的第五位即RXNE位是否被置位
{
c = USART1->DR; // RXNE=1,表明DR寄存器有值,就将它读出来保存到临时变量中;
ring_buffer_write(c, &test_buffer); // 将数据保存到环形缓冲区中
}
HAL_UART_IRQHandler(&huart1); // HAL库中的UART统一中断服务函数,通过形参判断是要处理谁的中断
}
/*
* 函数名:void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
* 输入参数:huart --> UART的设备句柄,用以指明UART设备是哪一个UART
* 输出参数:无
* 返回值:无
* 函数作用:HAL库中的UART接收完成回调函数
*/
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1) // 判断进来的是否是USART1这个UART设备
{
rxcplt_flag = 1; // 进入此回调函数表明接收指定长度的数据已经完成,将标志置一
}
}
/*
* 函数名:void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
* 输入参数:huart --> UART的设备句柄,用以指明UART设备是哪一个UART
* 输出参数:无
* 返回值:无
* 函数作用:HAL库中的UART发送完成回调函数
*/
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1) // 判断进来的是否是USART1这个UART设备
{
txcplt_flag = 1; // 进入此回调函数表明发送指定长度的数据已经完成,将标志置一
}
}
在main函数中使用USART3_Write(“AT\r\n”, 4);来测试MCU UART3能否与ESP8266正常通信:
AT
// 1. 配置 WiFi 模式
AT+CWMODE=3 // softAP+station mode
// 2. 连接路由?
AT+CWJAP="SSID","password" // SSID and password of router
// 3. 查询 ESP8266 设备的 IP 地址
AT+CIFSR
// 响应
+CIFSR:APIP,"192.168.4.1"
+CIFSR:APMAC,"1a:fe:34:a5:8d:c6"
+CIFSR:STAIP,"192.168.3.133"
+CIFSR:STAMAC,"18:fe:34:a5:8d:c6"
OK
// 4. ESP8266 设备作为 TCP client 连接到服务器
AT+CIPSTART="TCP","192.168.3.116",8080 //protocol, server IP and port
// 5. ESP8266 设备向服务器?发送数据
AT+CIPSEND=4 // set date length which will be sent, such as 4 bytes
>test // enter the data, no CR
// 响应
Recv 4 bytes
SEND OK
// 6. 当 ESP8266 设备接收到服务器?发来的数据,将提示如下信息:
+IPD,n:xxxxxxxxxx // received n bytes, data=xxxxxxxxxxx
at_uart_hal.h代码:
#ifndef _AT_UART_H
#define _AT_UART_H
void HAL_AT_Send(char *buf, int len);
void HAL_AT_Secv(char *c, int timeout);
#endif
at_uart_hal.c代码:
#include <stm32_uart3.h>
void HAL_AT_Send(char *buf, int len)
{
USART3_Write(buf, len);
}
void HAL_AT_Secv(char *c, int timeout)
{
/* 从环形缓冲区中得到数据 */
/* 无数据则阻塞 */
USART3_Read(c, timeout);
}
driver_usart.h代码:
#ifndef _STM32_UART3_H
#define _STM32_UART3_H
void USART3_Write(char *buf, int len);
void UART3_Lock_Init(void); //线程安全: USART3_Read没有读到数据时先挂起,读到数据后其他接口才能够调用USART3_Read
void USART3_Read(char *c, int timeout);
#endif
driver_usart.c代码:
#include "driver_usart.h"
#include <stdio.h>
#include <platform_mutex.h>
#include <ring_buffer.h>
static ring_buffer uart3_buffer; //创建uart3的环形缓冲buffer
extern UART_HandleTypeDef huart3;
static platform_mutex_t uart_recv_mutex;
void USART3_IRQHandler(void)
{
/* 如果发生的是RX中断
* 把数据读出来, 存入环形buffer
*/
uint32_t isrflags = READ_REG(huart3.Instance->SR);
uint32_t cr1its = READ_REG(huart3.Instance->CR1);
char c;
/* UART in mode Receiver -------------------------------------------------*/
/* USART_SR_RXNE接收不为空; USART_CR1_RXNEIE使能接收中断. */
if (((isrflags & USART_SR_RXNE) != RESET) && ((cr1its & USART_CR1_RXNEIE) != RESET))
{
c = huart3.Instance->DR; //将DR数据寄存器中的数据放入变量c
ring_buffer_write(c, &uart3_buffer); //将c写入环形缓冲区
platform_mutex_unlock_from_isr(&uart_recv_mutex);
return;
}
}
void UART3_Lock_Init(void)
{
platform_mutex_init(&uart_recv_mutex);
platform_mutex_lock(&uart_recv_mutex); // mutex = 0
ring_buffer_init(&uart3_buffer);
}
void USART3_Write(char *buf, int len)
{
int i = 0;
while (i < len)
{
/* 等待数据发送寄存器空 */
while ((huart3.Instance->SR & USART_SR_TXE) == 0);
huart3.Instance->DR = buf[i];
i++;
}
}
void USART3_Read(char *c, int timeout)
{
while (1)
{
if (0 == ring_buffer_read((unsigned char *)c, &uart3_buffer))
return;
else
{
platform_mutex_lock_timeout(&uart_recv_mutex, timeout);
}
}
}
其中platform_mutex_lock_timeout函数和platform_mutex_unlock_from_isr函数在杰杰MQTT代码中没有实现,因此自己实现:
platform_mutex.h代码:
/*
* @Author: jiejie
* @Github: https://github.com/jiejieTop
* @Date: 2019-12-15 18:31:33
* @LastEditTime: 2020-04-27 17:04:46
* @Description: the code belongs to jiejie, please keep the author information and source code according to the license.
*/
#ifndef _PLATFORM_MUTEX_H_
#define _PLATFORM_MUTEX_H_
#include "FreeRTOS.h"
#include "semphr.h"
typedef struct platform_mutex {
SemaphoreHandle_t mutex;
} platform_mutex_t;
int platform_mutex_init(platform_mutex_t* m);
int platform_mutex_lock(platform_mutex_t* m);
int platform_mutex_lock_timeout(platform_mutex_t* m, int timeout); /* DCA add */
int platform_mutex_trylock(platform_mutex_t* m);
int platform_mutex_unlock(platform_mutex_t* m);
int platform_mutex_unlock_from_isr(platform_mutex_t* m); /* DCA add */
int platform_mutex_destroy(platform_mutex_t* m);
#endif
platform_mutex.c代码:
/*
* @Author: jiejie
* @Github: https://github.com/jiejieTop
* @Date: 2019-12-15 18:27:19
* @LastEditTime: 2020-04-27 22:22:27
* @Description: the code belongs to jiejie, please keep the author information and source code according to the license.
*/
#include "platform_mutex.h"
int platform_mutex_init(platform_mutex_t* m)
{
m->mutex = xSemaphoreCreateMutex();
return 0;
}
int platform_mutex_lock(platform_mutex_t* m)
{
return xSemaphoreTake(m->mutex, portMAX_DELAY);
}
/* DCA add */
int platform_mutex_lock_timeout(platform_mutex_t* m, int timeout)
{
return xSemaphoreTake(m->mutex, timeout);
}
int platform_mutex_trylock(platform_mutex_t* m)
{
return xSemaphoreTake(m->mutex, 0);
}
int platform_mutex_unlock(platform_mutex_t* m)
{
return xSemaphoreGive(m->mutex);
}
/* DCA add */
int platform_mutex_unlock_from_isr(platform_mutex_t* m)
{
static BaseType_t xHigherPriorityTaskWoken = pdFALSE;
xSemaphoreGiveFromISR(m->mutex, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
return pdTRUE;
}
int platform_mutex_destroy(platform_mutex_t* m)
{
vSemaphoreDelete(m->mutex);
return 0;
}
at_command.c代码:
#include "at_command.h"
#include <platform_mutex.h>
#include <at_uart_hal.h>
#include <string.h>
#include <stdio.h>
#include <ring_buffer.h>
#include <mqttclient.h>
#define AT_CMD_TIMOUT 1000
#define AT_RESP_LEN 200
static ring_buffer g_packet_buffer; //存放网络接收数据payload的唤醒buffer, +IPD,len:data中的data
static platform_mutex_t at_ret_mutex; //发送AT命令或者网络数据后上锁,数据解析任务完成ESP8266响应数据解析后释放互斥锁
static platform_mutex_t at_packet_mutex; //读取网络接收数据为空时阻塞,数据解析任务完成网络接收数据解析后释放互斥锁
static int g_at_status; //发送AT命令后的状态,OK、ERROR、TIMEOUT
static char g_at_resp[AT_RESP_LEN]; //AT命令正确响应后保存ESP8266的返回值数据
/* status
* 0 - ok
* -1 - err
* -2 - timeout
*/
void SetATStatus(int status) //设置AT命令发送后的ESP8266响应状态,AT_OK或者AT_ERR
{
g_at_status = status;
}
int GetATStatus(void) //得到AT命令发送后的ESP8266响应状态,AT_OK或者AT_ERR
{
return g_at_status;
}
int ATInit(void) //初始化等待AT命令返回的互斥锁、读取网络数据的互斥锁和存放网络接收数据的环形缓冲区
{
platform_mutex_init(&at_ret_mutex);
platform_mutex_lock(&at_ret_mutex); // mutex = 0
platform_mutex_init(&at_packet_mutex);
platform_mutex_lock(&at_packet_mutex); // mutex = 0
ring_buffer_init(&g_packet_buffer);
return 0;
}
int ATSendData(unsigned char *buf, int len, int timeout) //AT+CIPSEND后发送数据的函数
{
int ret;
int err;
/* 发送网络数据 */
HAL_AT_Send((char *)buf, len);
/* 等待结果
* 1 : 成功得到mutex
* 0 : 超时返回
*/
ret = platform_mutex_lock_timeout(&at_ret_mutex, timeout); //等待数据解析任务解析完成释放互斥锁或超时
if (ret) //成功得到互斥锁
{
/* 判断返回值 */
/* 存储resp */
err = GetATStatus();
return err;
}
else //超时等待
{
return AT_TIMEOUT;
}
}
int ATReadData(unsigned char *c, int timeout) //读取网络数据的函数
{
int ret;
do {
if (0 == ring_buffer_read((unsigned char *)c, &g_packet_buffer)) //读到一个字符的网络接收数据
return AT_OK;
else
{
ret = platform_mutex_lock_timeout(&at_packet_mutex, timeout); //网络数据的环形buffer为空,等待数据解析任务解析完毕后释放互斥锁或者超时
if (0 == ret) //超时等待
return AT_TIMEOUT;
}
} while (ret == 1);
return 0;
}
/* eg. buf = "AT+CIPMODE=1"
* timeout : ms
*/
int ATSendCmd(char *buf, char *resp, int resp_len, int timeout) //发送AT命令的函数
{
int ret;
int err;
/* 发送AT命令 */
HAL_AT_Send(buf, strlen(buf));
HAL_AT_Send("\r\n", 2);
/* 等待结果
* 1 : 成功得到mutex
* 0 : 超时返回
*/
ret = platform_mutex_lock_timeout(&at_ret_mutex, timeout); //发送AT命令后等待数据解析任务解析完ESP8266返回的数据并释放互斥锁
vTaskDelay(200);
if (ret) //成功获取互斥锁,即接收到ESP8266响应的数据
{
/* 判断返回值 */
/* 存储resp */
err = GetATStatus();
if (!err && resp) //resp不为空, resp: 发送AT命令后期望的返回值
{
/* 比较实际返回值与期望返回值,若期望返回值的长度大于最大buffer长度则使用buffer的最大使用长度 */
memcpy(resp, g_at_resp, resp_len > AT_RESP_LEN ? AT_RESP_LEN : resp_len);
}
return err;
}
else
{
return AT_TIMEOUT;
}
}
#if 0
static int GetCIPSENDResult(char *buf)
{
if (g_cur_cmd && strstr(g_cur_cmd, "AT+CIPSEND=") && (buf[0] == '>'))
return 1;
else
return 0;
}
#endif
static int GetSpecialATString(char *buf) //获取特殊返回值, IPD是接收到网络数据的数据头
{
if (strstr(buf, "+IPD,"))
return 1;
else
return 0;
}
static void ProcessSpecialATString(char *buf) //处理网络接收数据函数
{
int i = 0;
int len = 0;
/* +IPD,78:xxxxxxxxxx */
{
/* 解析出长度 */
i = 0;
while (1)
{
HAL_AT_Secv(&buf[i], (int)portMAX_DELAY);
if (buf[i] == ':')
{
break;
}
else
{
len = len * 10 + (buf[i] - '0'); //将接收的到网络数据长度由字符转换为整型
}
i++;
}
/* 读取真正的网络数据 */
i = 0;
while (i < len)
{
HAL_AT_Secv(&buf[i], (int)portMAX_DELAY);
if (i < AT_RESP_LEN)
{
/* 把数据放入环形buffer */
ring_buffer_write(buf[i], &g_packet_buffer);
/* wake up */
/* 解锁唤醒使用ATReadData读网络数据的任务 */
platform_mutex_unlock(&at_packet_mutex);
}
i++;
}
}
}
void ATRecvParser( void * params) //解析ESP8266返回数据的任务
{
char buf[AT_RESP_LEN]; //接收ESP8266数据的buffer
int i = 0;
while (1)
{
/* 读取WIFI模块发来的数据: 使用阻塞方式 */
HAL_AT_Secv(&buf[i], (int)portMAX_DELAY);
buf[i+1] = '\0';
/* 解析结果 */
/* 1. 何时解析?
* 1.1 收到"\r\n"
* 1.2 收到特殊字符: "+IPD,"
*/
if (i && (buf[i-1] == '\r') && (buf[i] == '\n'))
{
/* 得到了回车换行 */
/* 2. 怎么解析 */
if (strstr(buf, "OK\r\n"))
{
/* 记录数据 */
memcpy(g_at_resp, buf, i);
SetATStatus(AT_OK);
platform_mutex_unlock(&at_ret_mutex);
i = 0;
}
else if (strstr(buf, "ERROR\r\n"))
{
SetATStatus(AT_ERR);
platform_mutex_unlock(&at_ret_mutex);
i = 0;
}
else if (strstr(buf, "Recv"))
{
SetATStatus(AT_OK);
platform_mutex_unlock(&at_ret_mutex);
i = 0;
}
#if 0
else if (GetCIPSENDResult(buf))
{
SetATStatus(AT_OK);
platform_mutex_unlock(&at_ret_mutex);
i = 0;
}
#endif
i = 0;
}
else if (GetSpecialATString(buf))
{
ProcessSpecialATString(buf);
i = 0;
}
else
{
i++;
}
if (i >= AT_RESP_LEN)
i = 0;
}
}
/* 以下代码是参考MqttClient源码中test例程: */
static void topic1_handler(void* client, message_data_t* msg)
{
(void) client;
MQTT_LOG_I("-----------------------------------------------------------------------------------");
MQTT_LOG_I("%s:%d %s()...\r\ntopic: %s\r\nmessage:%s", __FILE__, __LINE__, __FUNCTION__, msg->topic_name, (char*)msg->message->payload);
MQTT_LOG_I("-----------------------------------------------------------------------------------");
}
void MQTT_Client_Task(void *Param)
{
int err;
mqtt_client_t *client = NULL;
mqtt_message_t msg;
memset(&msg, 0, sizeof(msg));
mqtt_log_init();
client = mqtt_lease();
mqtt_set_port(client, "xxx"); //MQTT服务器端口号
mqtt_set_host(client, "xxx.xxx.xxx.xxx"); //MQTT服务器地址
mqtt_set_client_id(client, random_string(10));
mqtt_set_user_name(client, random_string(10));
mqtt_set_password(client, random_string(10));
mqtt_set_clean_session(client, 0);
if (0 != mqtt_connect(client))
{
printf("mqtt_connect err\r\n");
vTaskDelete(NULL);
}
err = mqtt_subscribe(client, "mcu_test1", QOS0, topic1_handler);
if (err)
{
printf("mqtt_subscribe topic1 err\r\n");
}
err = mqtt_subscribe(client, "topic2", QOS1, NULL);
if (err)
{
printf("mqtt_subscribe topic2 err\r\n");
}
err = mqtt_subscribe(client, "topic3", QOS2, NULL);
if (err)
{
printf("mqtt_subscribe topic3 err\r\n");
}
msg.payload = "Hello world!";
msg.qos = 0;
msg.payloadlen = strlen(msg.payload);
while (1) {
/* 每间隔5s向主题mcu_test1发送一次"Hello world!" */
mqtt_publish(client, "mcu_test1", &msg);
printf("mqtt_publish mcu_test OK\r\n");
vTaskDelay(5000);
}
}
at_command.h代码:
#ifndef __AT_COMMAND_H
#define __AT_COMMAND_H
#define AT_OK 0
#define AT_ERR -1
#define AT_TIMEOUT -2
int ATInit(void);
void ATRecvParser( void * params);
void MQTT_Client_Task(void *Param);
/* eg. buf = "AT+CIPMODE=1"
* timeout : ms
*/
int ATSendCmd(char *buf, char *resp, int resp_len, int timeout);
int ATSendData(unsigned char *buf, int len, int timeout);
int ATReadData(unsigned char *c, int timeout);
int ATReadPacket(char *buf, int len, int *resp_len, int timeout);
#endif
platform_net_socket.c代码:
/*
* @Author: jiejie
* @Github: https://github.com/jiejieTop
* @Date: 2020-01-10 23:45:59
* @LastEditTime: 2020-04-25 17:50:58
* @Description: the code belongs to jiejie, please keep the author information and source code according to the license.
*/
#include "mqtt_log.h"
#include "platform_net_socket.h"
#include "at_command.h"
#define TEST_SSID "DESKTOP-NKFGPI3" //WIFI名
#define TEST_PASSWD "0V6u77{3" //WIFI密码
/* return : < 0 , err
* 0 : ok
*/
int platform_net_socket_connect(const char *host, const char *port, int proto)
{
int err;
char cmd[100];
while (1)
{
err = ATSendCmd("AT+CIPCLOSE", NULL, 0, 2000);
/* 1. 配置 WiFi 模式 */
err = ATSendCmd("AT+CWMODE=3", NULL, 0, 2000);
if (err)
{
printf("AT+CWMODE=3 err = %d\n", err);
//return err;
}
/* 2. 连接路由器 */
/* 2.1 先断开 */
err = ATSendCmd("AT+CWQAP", NULL, 0, 2000);
if (err)
{
printf("disconnect AP err = %d\n", err);
//return err;
}
/* 2.2 再连接 */
err = ATSendCmd("AT+CWJAP=\"" TEST_SSID "\",\"" TEST_PASSWD "\"", NULL, 0, 200000);
if (err)
{
printf("connect AP err = %d\n", err);
//return err;
continue;
}
/* 3. 连接到服务器 */
if (proto == PLATFORM_NET_PROTO_TCP)
{
/* AT+CIPSTART="TCP","192.168.3.116",8080 */
sprintf(cmd, "AT+CIPSTART=\"TCP\",\"%s\",%s", host, port);
}
else
{
sprintf(cmd, "AT+CIPSTART=\"UDP\",\"%s\",%s", host, port);
}
err = ATSendCmd(cmd, NULL, 0, 2000);
if (err)
{
printf("%s err = %d\n", cmd, err);
continue;
}
if (!err)
break;
}
return 0;
}
/* 暂时不需要用到的函数 */
#if 0
int platform_net_socket_recv(int fd, void *buf, size_t len, int flags)
{
return 0;
}
#endif
/* 返回得到的字节数 */
int platform_net_socket_recv_timeout(int fd, unsigned char *buf, int len, int timeout)
{
int i = 0;
int err;
/* 读数据, 失败则阻塞 */
while (i < len)
{
err = ATReadData(&buf[i], timeout);
if (err)
{
return 0;
}
i++;
}
return len;
}
/* 暂时不需要用到的函数 */
#if 0
int platform_net_socket_write(int fd, void *buf, size_t len)
{
return 0;
}
#endif
int platform_net_socket_write_timeout(int fd, unsigned char *buf, int len, int timeout)
{
int err;
char cmd[20];
sprintf(cmd, "AT+CIPSEND=%d", len);
err = ATSendCmd(cmd, NULL, 0, timeout);
if (err)
{
printf("%s err = %d, timeout = %d\n", cmd, err, timeout);
return err;
}
err = ATSendData(buf, len, timeout);
if (err)
{
printf("ATSendData err = %d\n", err);
return err;
}
return len;
}
int platform_net_socket_close(int fd)
{
return ATSendCmd("AT+CIPCLOSE", NULL, 0, 2000);
}
/* 暂时不需要用到的函数 */
#if 0
int platform_net_socket_set_block(int fd)
{
return 0;
}
int platform_net_socket_set_nonblock(int fd)
{
return 0;
}
int platform_net_socket_setsockopt(int fd, int level, int optname, const void *optval, socklen_t optlen)
{
return 0;
}
#endif
主要记录调试过程中遇到的几个坑:
- 堆栈空间分配不足导致MQTT_Client_Task任务无法运行、mqtt_yield_thread无法创建
- mqtt_yield_thread中无法连接Server
- 无法订阅主题和无法接收消息问题
此处调试很久发现一直解决不了,最后索性注释掉,注释掉后发现是可以正常连接服务器并发布消息,但是无法订阅主题和接收消息!
其中无法理解的是为什么在MQTT_Client_Task任务中(优先级24)创建了mqtt_yield_thread任务(优先级5)后,就直接去运行mqtt_yield_thread了,没能继续运行platform_thread_init下的打印以及设置state状态的位置。
调试完成的代码以及杰杰的kawaii mqttclient源码:https://download.csdn.net/download/studyingdda/88741258