STM32嵌入式开发笔记——自动测量系统STM32F103C8+ESP8266+DHT22
最近接触了一些STM32嵌入式开发的工作。本人在此方面完全是门外汉,所以通过博客来记录自己的工作内容,也希望专业人士看到后能够给出一些指点。
该项目是基于STM32系列芯片的自动测温测湿系统,主要作为初学者的学习项目。项目开发采用的是基于HAL库的STM32CubeIDE,初衷是可以快速上手,并且代码方便移植到不同型号的芯片。开发中需要用到的调试软件包括XCOM串口调试助手、网络调试助手。
一、 系统功能与原理
- DHT22温湿度传感器以3s为周期采集数据,并生成对应的温湿度信息。
- 温湿度信息(字符串)通过USB虚拟串口发送至PC,温湿度数据通过ESP8266wifi模块发送至PC。
- ESP8266建立Wifi连接的模式有两种。
- a) ESP8266作为主机打开wifi,PC加入该wifi与ESP8266建立连接。
- b) ESP8266和PC都作为客户端加入同一个WiFi,然后ESP8266找到PC的地址建立连接。
- USB虚拟串口可将调试信息发送至PC,并从PC接收信息直接转发到ESP8266,因此可以在PC中直接发送AT指令。
图1. Stm32温湿度测量系统原理图
二、 系统硬件需求
工作指标 | 工作指标 | 备注 |
---|
Stm32F103C8T6开发板 | 3.3V供电,主频72MHz | 需要支持至少一个usart接口用于连接WiFi模块。需要虚拟USB接口以输入调试指令和输出调试信息。一个GPIO引脚用于连接温度传感器。 | ESP8266-01 WiFi模块 | Vcc需要稳定3.3V供电,峰值电流可达500mA,需确保电源模块的稳定性和功率指标。空闲时电流20mA。 | 01型号需要在EN和RST引脚接10k上拉电阻。可用改进型号(ESP8266-01S)替代,则不需要额外接EN和RST引脚。 | DHT22温度传感器(AM2302) | Vcc 3.3-6V供电,经测试3.3v可正常工作。工作电流1mA。 | 采样周期>2s,每次采样后返回的是上一次采样的数据。Vcc和Gnd切勿接反,否则烧毁。 |
三、 系统模块介绍与设置
Stm32F103C8T6开发板
该开发板自带LED,由PA1引脚通过GPIO控制。DHT22使用PA6引脚进行单总线传输。PA1和PA6的GPIO需要单独设置。开启ST-Link调试需要在SYS->Debug 中选择Serial wire ,CubeMx将自动设置PA13、PA14引脚。
其他用到的引脚:DHT22所需的自定义微秒级延时将用到TIM4计时器,开启后自动配置PD0,PD1引脚。开发板自带的USB接口,使用PA11,PA12引脚。开发板自带的ESP8266WIFI模块接口,对应USART2,使用PA2、PA3引脚。开发板的原理图与其他功能详见附件。
为了使各个模块更加独立,本系统开发时勾选了CubeMx下 project manager /Code generator /generate peripheral initialization as a pair of ‘.c/.h’ files per peripheral 。设置以后每个外设都将生成独立的源码,方便管理。
图2. Stm32CubeMx中Stm32F103C8的引脚和Debug设置
虚拟USB串口
虚拟USB串口用于单片机与PC的通信。配置完成后,可由PC上的串口通信助手打开与单片机的连接,并收发调试信息。
CubeMx配置
虚拟USB串口功能需要打开Connectivity 的USB Device(FS) ,并在Middleware 中打开USB虚拟串口:Communication Device Class (Virtual Port Com)
图3. 虚拟USB串口设置。
配置成功后,将stm32开发版的microUSB接口连接PC后,可以在串口调试助手中看到相应的COM并可以打开连接。
修改usbd_cdc_if.c ,重写USB串口的接收函数CDC_Receive_FS ,使其将收到的数据转发给huart2串口(ESP8266)。USB串口的发送函数CDC_Transmit_FS 保持默认。
在usbd_cdc_if.c中重写USB串口的接收函数CDC_Receive_FS
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
usbRxLen=Len[0];
for (int i=0;i<usbRxLen;i++){
UserRxBufferFS[i]=Buf[i];
}
CDC_Transmit_FS(UserRxBufferFS,usbRxLen);
HAL_UART_Transmit_DMA(&huart2 ,(uint8_t*)UserRxBufferFS,usbRxLen);
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
return (USBD_OK);
}
单片机重启后有时候需要重新插拔USB接口,才能让windows系统重新检测串口连接状态,在调试时非常不方便。因此我们可以写一个模拟USB重新插拔的函数,代替手动插拔的操作。其原理是通过拉低再拉高D+引脚的电平,模拟插拔的动作。注意:模拟插拔的函数必须放在USB初始化之前,因为该函数内将USB相关引脚配置为GPIO模式,而USB初始化函数会在之后进行其他配置。
在usb_device.c中实现USB_Plug_Init
void USB_Plug_Init(void){
GPIO_InitTypeDef GPIO_InitStruct;
__HAL_RCC_GPIOA_CLK_ENABLE();
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_RESET);
GPIO_InitStruct.Pin = GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12,GPIO_PIN_RESET);
HAL_Delay(65);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_12,GPIO_PIN_SET);
HAL_Delay(65);
}
在MX_USB_DEVICE_Init的开头调用USB_Plug_Init
void MX_USB_DEVICE_Init(void)
{
USB_Plug_Init();
....
}
ESP8266模块
ESP8266模块用于建立与PC的无线连接。本系统中配置了两种连接模式。一种是ESP8266以AP热点模式启动,作为服务器建立自身的wifi。PC作为客户端连入该wifi后,可由网络调试助手连接服务器并发送数据,ESP8266可以收到多个客户端发送的数据,以及向指定的客户端发送数据。第二种方式是ESP8266以Station模式启动,作为客户端加入其他wifi。当PC和ESP8266位于同意wifi下时,利用网络调试助手打开TCP Server后,ESP8266可以通过PC的ip地址连接PC开设的服务器,并于服务器进行收发通讯。在该模式下ESP8266将打开透传模式,将所有USART2串口收到的数据直接发送到服务器。
通过修改main.c中ESP初始化函数的参数,可以选择芯片启动后的两种连接模式。用户也可以通过USB串口发送AT指令来手动配置ESP8266。
接线
本系统采用ESP-01型号,如图3所示,EN和RST引脚需要接10k上拉电阻。由于Stm32开发板与ESP模块需要互传数据,因此收发引脚应对应相接。Stm32开发板的RXD引脚应接ESP的TXD引脚,TXD引脚接ESP的RXD引脚。由于ESP8266在发送大量数据时可能会产生较大的工作电流和功耗,因此建议采用外部稳压3.3v电源单独供电。本文采用STM32开发板的3.3v引脚进行供电,经测试可以收发少量数据。 本文采用的Stm32F103C8开发板上提供了ESP8266的专用接口,按照开发板原理图对应接线即可。该接口占用PA2和PA3引脚,默认对应USART2串口。
图4. ESP-01模块的接线示意图。
CubeMX配置
配置Usart2引脚,模式设为Asynchronous ,并打开global interrupt 。由于需要用到DMA收发,还需要手动设置DMA收发通道,如图5所示。 图5. ESP8266所需的USART2串口配置。
该USART串口除了收发数据外,还需要传输AT指令 (尤其是接收)。默认的DMA接收、中断接收和堵塞接收都只支持固定长度的接收,因此在接收不定长的AT指令时,一旦指令长度和预设长度不符,将发生发生错误:
- 指令长度大于预设长度时,会提前触发接收完成中断进入回调函数,导致指令接收不完整。
- 指令长度小于预设长度时,无法触发接收完成中断,进而无法进入中断回调函数来处理数据。同时,下一次接收的数据的前一部分将会被当成上一次接收中未完成的部分,导致第二次接收数据不完整。
为避免这一问题,需要实现任意长度的数据接收功能(参考博客)。思路:连续接收结束后会进入空闲中断。可以自定义空闲中断的处理方式,连续接收结束后,在空闲中断回调函数中处理缓存中的数据,并重置寄存器。
为了使用空闲中断,在USART2的初始化函数中使能空闲中断: __HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE) 。之后,在USART2的中断处理中加入空闲中断处理,调用自己定义的回调函数。
usart.c 中修改MX_USART2_UART_Init 函数
void MX_USART2_UART_Init(void)
{
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart2) != HAL_OK)
{
Error_Handler();
}
__HAL_UART_ENABLE_IT(&huart2, UART_IT_IDLE);
}
在USART2的中断处理中加入空闲中断处理,调用自己定义的回调函数
void USART2_IRQHandler(void)
{
if(__HAL_UART_GET_FLAG(&huart2, UART_FLAG_IDLE) != RESET)
{
USART2_DMA_RxCpltCallback();
}
HAL_UART_IRQHandler(&huart2);
}
此外还需要自己实现中断回调函数,此处命名为USART2_DMA_RxCpltCallback 。在DMA接收结束时,会进入空闲中断,此时调用该回调函数以处理接收到的数据。首先重置寄存器,然后将收到的数据发送到usb串口,并清空接收缓存。除此之外,还需要重写收发错误回调函数USART2_UART_ErrorCallback ,从而在发生预料之外的错误时重新打开DMA接收功能,否则USART2串口将无法再次接收数据。该函数是__weak函数,在没有重写时将由默认函数代替。
usart.c 中定义所需的变量
volatile uint8_t recv_end_flag;
volatile uint8_t rx_len;
extern uint8_t USART2_RxBuffer[];
usart.c 中实现USART2_DMA_RxCpltCallback
void USART2_DMA_RxCpltCallback(){
uint32_t temp;
__HAL_UART_CLEAR_IDLEFLAG(&huart2);
temp = huart2.Instance->SR;
temp = huart2.Instance->DR;
HAL_UART_DMAStop(&huart2);
temp = hdma_usart2_rx.Instance->CNDTR;
recv_end_flag = 1;
CDC_Transmit_FS(USART2_RxBuffer,sizeof(USART2_RxBuffer));
memset(USART2_RxBuffer,0,sizeof(USART2_RxBuffer));
rx_len=0;
recv_end_flag=0;
HAL_UART_Receive_DMA(&huart2,USART2_RxBuffer,USART2_RxBUFFER_SIZE);
}
usart.c 中重写USART2_UART_ErrorCallback
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart)
{
if(huart == &huart2)
{
__HAL_UNLOCK(huart);
memset(&huart2,0,sizeof(UART_HandleTypeDef) );
HAL_UART_MspDeInit(&huart2);
MX_DMA_Init();
MX_USART2_UART_Init();
HAL_UART_Receive_DMA(&huart2,(uint8_t*)USART2_RxBuffer,USART2_RxBUFFER_SIZE);
}
}
ESP8266功能封装
配置USART2串口后,还需要针对ESP8266的功能进行封装,例如初始化、处理AT指令、向外发送数据等。ESP8266.h 中还需要指定ESP所用到的UART串口和相应的接收缓存。
ESP8266.h 中提供了一系列接口,并在此处定义ESP所用的usart接口组、缓存大小、缓存。移植时需要修改此处的定义。
#ifndef INC_ESP8226_H_
#define INC_ESP8226_H_
#include "stm32f1xx_hal.h"
#define ESP_USART huart2
#define ESP_BUFFER_SIZE USART2_RxBUFFER_SIZE
#define ESP_RxBuffer USART2_RxBuffer
void ESP_Send_cmd(char cmd[],uint32_t waittime, uint32_t delay);
void ESP_Send_Data_TT(uint8_t* data, uint16_t len);
void ESP_Send_Data_4Bytes(uint8_t* data);
void ESP_Init(uint8_t mode);
void ESP_Init_TT();
#endif
在main函数中调用ESP_Init(1) ,可以将esp配置为station模式。默认连接的wifi和密码需要自己修改,TCP服务器地址(PC)192.168.0.196,端口8080。默认自动开启透传。调用ESP_Init(2) 将会配置ESP为AP模式(服务器模式),wifi名:TCP_Server,密码12345678,TCP地址192.168.4.1,端口8080。
调用ESP_Send_cmd 可以向ESP8266发送AT指令,并根据参数执行等待和延时。
DHT22温湿度传感器
DHT22温湿度传感器可同时测量温度和湿度数据,精确到小数点后一位。传感器采用单总线模式传输数据,占用Stm32一个GPIO引脚。接收数据时,先由Stm32发送一个激活信号,然后将GPIO引脚设置为接收,收到DHT22回应后,读取40位的数据。前0-15位为温度数值10,16-31位为湿度数据10,32-39位为校验位。前32位数据相加后应与最后8位数据相等。DHT22的测量周期应大于2s(系统中为3s),每次测量时返回的是上一次测量的数据。
接线
DHT22温湿度传感器的接线如图6所示。因为传感器内置上拉电阻,数据引脚可直接连接Stm32引脚,Vcc接3.3v-6v电压。本系统采用stm32的3.3v引脚供电,经测试传感器工作正常。 图6. DHT22接线示意图。
CubeMx配置
DHT22采用单总线方式传输数据,因此CubeMx中只需要配置一个GPIO引脚(图7)。默认GPIO模式设为输出:Output open drain 。
图7. PA6 GPIO设置。
DHT22功能封装
DHT22相关功能的实现和接口分别在DHT22.c 、DHT22.h 中定义。
DHT22.h 定义了引脚和错误代码,以及提供了以下接口:
DHT22_Init() :初始化DHT引脚,设为输出uint8_t DHT22_Get_Data(uint16_t *temp,uint16_t *humi) :从头开始读取数据,包括向DHT发送开始接收数据的信号,获取响应,读取、校验数据void ONE_WIRE_Start() :发送开始接收数据的信号uint8_t ONE_WIRE_Response() :获取DHT的响应uint64_t DHT22_ReadData(void) : 读取40位的原始数据void DHT22_THMsg(uint16_t Temp, uint16_t Humi, char THMsg[30]) :根据相应的温度适度数据,生成固定格式的字符串信息供调试使用。格式:Temp:00000000, Humi:00000000\r\n
#include "stm32f1xx_hal.h"
#define DHT_PORT GPIOA
#define DHT_PIN GPIO_PIN_6
#define DHT_ERR_RESPONSE_FAILED 1
#define DHT_ERR_READ_BIT_FAILED 2
#define DHT_ERR_DATA_CHKSUM_FAILED 3
void DHT22_Init();
void ONE_WIRE_Start();
uint8_t ONE_WIRE_Response();
void DHT22_THMsg(uint16_t Temp, uint16_t Humi, char THMsg[30]);
uint8_t DHT22_Get_Data(uint16_t *temp,uint16_t *humi);
uint64_t DHT22_ReadData(void);
四、 系统运行示例
此处将展示系统通过模式1方式与PC交换数据的过程。
-
按图8接线。将PC连接到指定wifi,打开网络调试助手,设置本地主机端口并开启TCP Server。 图8. 系统实物图。 -
将wifi名称和密码预先写好在ESP8266.c 中的对应指令中,编译并烧录到单片机中。 -
将USB连接到PC,在XCOM中打开串口连接,即可看到单片机发送至PC的调试信息,如图9所示。 -
与此同时,网络调试助手也能看到单片机成功连接TCP Server的信息,并且每3秒会收到一个4字节的数据,前2字节对应温度数据,后2字节对应湿度(图10)。例如第一条数据中,温度312,16进制即为0138,湿度477,16进制为01DD。数据一致,证明系统运行正常。 -
PC作为TCP Server也可以通过网络调试助手发送信息到单片机,单片机会将收到的信息转发到USB串口。
图9. XCOM串口调试助手收到的调试信息。 图10. 网络调试助手收到的调试信息。
在XCOM中可以向USB串口发送AT指令,单片机会将AT指令转发至ESP8266。首先发送+++ (不需要\r\n字符)关闭透传模式,否则无法接受AT指令。然后发送AT ,可以收到返回的信息AT OK ,即测试AT指令正确。发送AT+RST 可以看到ESP成功重置,如图11所示。调试信息中的乱码是由于关闭透传之后,4字节的温湿度数据会被转发到USB串口,对应的ASCLL码乱码。
按下stm32开发板的复位按钮,能够重置单片机并让程序从头运行。此时,需要在复位后重新打开串口,才能接收到ESP8266的初始化信息。
图11. USB串口发送AT指令。
|