STM32与MQTT(一)STM32+ESP8266连接TCP服务器
0. 前言
本系列不讲MQTT协议具体的原理,只说怎么使用STM32+ESP8266在裸机状态下使用HAL库连接华为云MQTT服务器
1. 开发前准备
1.1 准备工作
- STM32F103C8T6核心板
- 正点原子ATK-ESP-01模块(也可以使用其他ESP8266模块)
- 开发环境:STM32CubeIDE
- 串口调试助手
- 参考作者 张竞豪 的文章,写的真的很好
1.2 说明
- 使用资源说明
使用资源 | 功能 |
---|---|
USART1 | DBG信息打印 |
USART2 | 连接ESP8266模块进行配置与网络通信 |
DMA1_Channel6 | USART2_Rx的DMA用作与接收数据 |
PB15 | ESP8266模块复位引脚 |
PC13 | 板载LED灯 |
- 正点原子ATK-ESP-01模块使用请下载资料,就算使用其他的ESP8266模块也可使用正点原子的资料,主要参考资料中对于ESP8266的AT指令集的说明。
- 使用STM32CubeIDE开发,集成环境,个人觉得在STM32开发上比MDK环境好用
- 使用野火的fireTool,网络调试,串口调试一应俱全
- 虽然我没有私信联系这位作者,但是我很感谢他,基本上是跟着他文章做下来的
2. 开发过程
2.1 USART1与USART2串口使用
2.1.1 两个串口使用上的区别
请耐心看完以下内容
使用USART1时,考虑到在设计中只用作DBG信息打印,因此对其接收没有要求,甚至不需要接收,在这里我没有用到USART1的接收功能
使用USART2时,首先要搞清楚,USART2是如何通过ESP8266连接网络,并进行通信的。类比HC-05等的蓝牙模块,这类模块通过串口线连接到串口上,配置并连接完成后,对下层透明,下层不需要关心协议如何实现,只对其连接的串口发送信息,而将信息加上头部、解包或无线传输等功能完全由该芯片(模块)实现。所以类比HC-05等蓝牙模块,我们对ESP8266模块要做的事情非常简单:正确配置,连接网络,使能发送。
由于USART2需要发送与接收数据,显然,发送数据的长度是本地可控的,简单使用HAL库函数即可完成,但是接收数据的长度是不可控的,其完全由配置过程中ESP8266返回的数据和配置完成后ESP8266接收到的数据长度指定,即接收到的数据是不定长的,为了实现不定长实时数据接收,在这里我们使用DMA+中断的方法(参考文章找不到了,但是能搜到的方法大致相同,我也会贴出我用到的方法)。
2.1.2 工程配置
使用资源一览,时钟树使用外部晶振,直接拉满就可以
DMA1_Channel6 配置直接使用默认的就可以
让STM32CubeIDE内置的CubeMX在生成代码时按照库的方式来生成
扩大栈堆
2.1.3 修改 “usart.h”
打开生成后的工程中usart.h
在usart.h中加入需要用到的变量
/* 对照注释提示插入代码,不在注释包括出来的
* 区域内的代码在重新生成代码时会被清除
*/
/* USER CODE BEGIN Private defines */
// 用户代码开始
#define USART1_MAX_SENDLEN 1024
#define USART1_MAX_RECVLEN 1024
#define USART2_MAX_SENDLEN 1024
#define USART2_MAX_RECVLEN 1024
// 用户代码结束
/* USER CODE END Private defines */
void MX_USART1_UART_Init(void); // CubeMX生成的代码
void MX_USART2_UART_Init(void); // CubeMX生成的代码
/* USER CODE BEGIN Prototypes */
// 用户代码开始
extern uint8_t USART1_TxBUF[USART1_MAX_SENDLEN];// usart1 发送数据缓存区
extern uint8_t USART1_RxBUF[USART1_MAX_RECVLEN];// usart1 接收数据缓存区
extern uint8_t USART2_TxBUF[USART2_MAX_SENDLEN];// usart2 发送数据缓存区
extern uint8_t USART2_RxBUF[USART2_MAX_RECVLEN];// usart2 接收数据缓存区
extern volatile uint8_t USART2_RxLen;// usart2 接收数据长度
extern volatile uint8_t USART2_RecvEndFlag;// usart2 接收数据完成标志位
// 对usart1发送数据
void u1_printf(char *fmt, ...);
// 我不喜欢将对usart2发送数据也命名为printf,因为实际上并没有对我print
void u2_transmit(char *fmt, ...);
// 用户代码结束
/* USER CODE END Prototypes */
2.1.4 修改 “usart.c”
在 MX_USART2_UART_Init(void) 函数中添加以下代码,开启IDLE中断,并使能DMA接收
在最后添加第2步中声明的两个函数的函数体
void MX_USART2_UART_Init(void) {
//......
/* USER CODE BEGIN USART2_Init 2 */
__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);
HAL_UART_Receive_DMA(&huart2, USART2_RxBUF, USART2_MAX_RECVLEN);
/* USER CODE END USART2_Init 2 */
//......
}
/**********************分割线**********************/
/* USER CODE BEGIN 1 */
// 用户代码开始
void u1_printf(char *fmt, ...) {
uint16_t i;
va_list ap;
va_start(ap, fmt);
vsprintf((char*) USART1_TxBUF, fmt, ap);
va_end(ap);
i = strlen((const char*) USART1_TxBUF);
HAL_UART_Transmit(&huart1, USART1_TxBUF, i, 100);
memset(USART1_TxBUF, 0, USART1_MAX_SENDLEN);
}
void u2_transmit(char *fmt, ...) {
uint16_t i, j;
va_list ap;
va_start(ap, fmt);
vsprintf((char*) USART2_TxBUF, fmt, ap);
va_end(ap);
// 排除掉发送信息中所有的'\00'使有效信息可以发送
// 但是仍要考虑到,MQTT报文等内容中可能含有'\00'
// 这个时候就需要直接使用HAL_UART_Transmit()函数发送
// 而放弃使用u2_transmit()
for (i = 0; i < USART2_MAX_SENDLEN; i++) {
j = i + 1;
if (USART2_TxBUF[i] == '\00') {
for (; j < USART2_MAX_SENDLEN; j++) {
USART2_TxBUF[j - 1] = USART2_TxBUF[j];
}
}
}
i = strlen((const char*) USART2_TxBUF);
HAL_UART_Transmit(&huart2, USART2_TxBUF, i, 100);
memset(USART2_TxBUF, 0, USART2_MAX_SENDLEN);
memset(USART2_RxBUF, 0, USART2_MAX_RECVLEN);
USART2_RecvEndFlag = 0;
}
// 用户代码结束
/* USER CODE END 1 */
2.1.5 修改 “stm32f1xx_it.c”
在中断部分加入代码,完成不定长数据接收,引入所需的头文件后,需要对 USART2_IRQHandler() 作如下修改,引入DMA中断,当DMA接收完成后将USART2_RxLen设置为接收数据的长度,USART2_RecvEndFlag标志置1
/**
* @brief This function handles USART2 global interrupt.
*/
void USART2_IRQHandler(void)
{
/* USER CODE BEGIN USART2_IRQn 0 */
uint32_t tmp_flag = 0;
uint32_t temp;
tmp_flag = __HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE);
if ((tmp_flag != RESET)) {
__HAL_UART_CLEAR_IDLEFLAG(&huart2);
temp = huart2.Instance->SR;
temp = huart2.Instance->DR;
HAL_UART_DMAStop(&huart2);
temp = hdma_usart2_rx.Instance->CNDTR;
USART2_RxLen = USART2_MAX_RECVLEN - temp;
USART2_RecvEndFlag = 1;
}
/* USER CODE END USART2_IRQn 0 */
HAL_UART_IRQHandler(&huart2);
/* USER CODE BEGIN USART2_IRQn 1 */
/* USER CODE END USART2_IRQn 1 */
}
2.1.6 设置监听
完成上述步骤后,如果需要获取USART2接收到的数据只需要在需要获取数据的位置监听 USART2_RecvEndFlag 的值,当其为1时,读取USART2_RxBUF的内容即可,示例:
/* 在timeout时间内进行监听,
* 如果监听到USART2_RecvEndFlag为1则对数据进行处理
*/
while (timeout--) {