STM32与MQTT(一)STM32+ESP8266连接TCP服务器


0. 前言

本系列不讲MQTT协议具体的原理,只说怎么使用STM32+ESP8266在裸机状态下使用HAL库连接华为云MQTT服务器

1. 开发前准备

1.1 准备工作

  1. STM32F103C8T6核心板
  2. 正点原子ATK-ESP-01模块(也可以使用其他ESP8266模块)
  3. 开发环境:STM32CubeIDE
  4. 串口调试助手
  5. 参考作者 张竞豪 的文章,写的真的很好

1.2 说明

  1. 使用资源说明
使用资源 功能
USART1 DBG信息打印
USART2 连接ESP8266模块进行配置与网络通信
DMA1_Channel6 USART2_Rx的DMA用作与接收数据
PB15 ESP8266模块复位引脚
PC13 板载LED灯
  1. 正点原子ATK-ESP-01模块使用请下载资料,就算使用其他的ESP8266模块也可使用正点原子的资料,主要参考资料中对于ESP8266的AT指令集的说明。
  2. 使用STM32CubeIDE开发,集成环境,个人觉得在STM32开发上比MDK环境好用
  3. 使用野火的fireTool,网络调试,串口调试一应俱全
  4. 虽然我没有私信联系这位作者,但是我很感谢他,基本上是跟着他文章做下来的

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 工程配置

使用资源一览,时钟树使用外部晶振,直接拉满就可以
配置图1
DMA1_Channel6 配置直接使用默认的就可以
配置图2
让STM32CubeIDE内置的CubeMX在生成代码时按照库的方式来生成
配置图3
扩大栈堆
配置图4

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--) {
   
	
  • 16
    点赞
  • 111
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sin1111yi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值