标题直接 Buff 点满…

阅读指引

如果你是 0 基础选手,建议先阅读以下内容掌握基本的 FreeRTOS 工程知识:

  1. C 语言基本语法
  2. STM32 工程创建,详看
  3. FreeRTOS 的移植,详看

本文主要分为三个部分,每个部分需要准备的工具如下:

  1. 阿里云物联网平台注册:需要阿里云账号。
  2. 模拟 MQTT 客户端发送与订阅消息:软件 MQTT.fx,阿里云提供的 MQTT_Password 工具。
  3. ESP8266 固件烧录:串口助手软件,需要材料 ESP8266、USB-TTL、杜邦线若干。
  4. 软件实现:Keil 软件、STM32CubeMX(使用 HAL)、STM32、DHT11、杜邦线若干、OLED(可选)、LED 灯、ST-Link

一些阅读前的提示:

  1. 阿里云相关产品新用户只能免费试用 1 个月
  2. 这里不涉及 MQTT 的软件实现,MQTT 功能是通过固件烧录的
  3. 如果只是单纯实现温湿度监控及点灯的话,FreeRTOS 其实不是必须的

阿里云物联网平台

添加产品物模型

首先你得拥有一个阿里云账号,然后开通物联网平台:

image.png

进入管理控制台:

image.png

创建一个实例(前提需实名认证):

image.png

公共实例开通免费,但据说可能联网不佳。标准实例可以免费开通试用 1 个月。

进入实例详情:

image.png

创建一个产品:

image.png

创建设备:

image.png

下面添加一个物模型:

image.png

image.png

image.png

使用 IoT Studio 绘制界面

回到物联网平台,查看增值服务。如果没有 IoT Studio,可以在选购增值服务中购买。IoT Studio 可以试用 1 个月。

image.png

进入 IoT Studio,选择你的实例,新建 Web 应用:

image.png

image.png

绘制一个像样的页面:

image.png

仪表盘数据源配置:

image.png

image.png

折线图数据源配置:

image.png

按钮交互配置:

image.png

这时点击预览的话仪表盘会显示没有数据,可继续下一章节阅读。

MQTT.fx 模拟客户端发布与订阅消息

安装 MQTT_Password 工具包:如何计算MQTT签名参数_物联网平台(IoT)-阿里云帮助中心 (aliyun.com)

安装 MQTT.fx:MQTT.fx® Download (softblade.de)

回到阿里云物联网平台控制台>设备>查看,把三元素复制下来:

image.png

使用 MQTT_Password 计算签名:

image.png

除了此处的 Clientid 是需要填入的设备信息 Clientid,其他所有地方说的 Client ID 都是指 MQTT 连接的 Client ID 具体值就是计算结果的 mqttClientid

打开 MQTT.fx,配置:

image.png

image.png

Broker Address 为你的 ProductID+.iot-as-mqtt.cn-shanghai.aliyuncs.com

链接。然后尝试发布消息和订阅消息:

image.png

连接后阿里云物联网平台将显示在线:

image.png

尝试在 /sys/k10qu3ushCq/mqtt_stm32/thing/event/property/post 发布一个 json 数据:

1
{"params":{"temperature":42,"Humidity":22},"version":"1.0.0"}

image.png

结果在 IoT Studio 网页应用就会显示数据:

image.png

/sys/k10qu3ushCq/mqtt_stm32/thing/service/property/set 中订阅,点击开关按钮将收到消息:

image.png

ESP8266 固件烧录及串口调试

首先,固件烧录部分详看:ESP8266 烧写 AT MQTT 固件

ESP8266 连上串口后测试 AT 命令(记得加 \r\n):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 设置热点模式
AT+CWMODE=1

# 链接手机热点(或 WIFI)
AT+CWJAP="<热点名称>","<热点密码>"

# MQTT配置。注意,逗号前加反斜杠。ClientID+用户名+密码
AT+MQTTUSERCFG=0,1,"mqtt_stm32|securemode=2\,signmethod=hmacsha1\,timestamp=6606|","mqtt_stm32&k1xxxxxxxq","5DExxxxxxC2",0,0,""

# 连接 MQTT Broker
AT+MQTTCONN=0,"k1xxxxxxxq.iot-as-mqtt.cn-shanghai.aliyuncs.com",1883,0

# 订阅 MQTT主题
AT+MQTTSUB=0,"/sys/k10qu3ushCq/mqtt_stm32/thing/service/property/set",0

# 发送数据
AT+MQTTPUB=0,"/sys/k10qu3ushCq/mqtt_stm32/thing/event/property/post","{\"params\":{\"temperature\":42\,\"Humidity\":22}\,\"version\":\"1.0.0\"}",0,0

测试发送时间上行成功后,可以继续测试 LED 开关命令。在 IoT Studio 点击按钮,串口助手将回显消息。

软件实现思路

整个 STM32 工程比较庞杂。本节介绍主要的具体思路,并展示重要代码过程。

主要思路:

  1. STM32 串口 1 用于向电脑发送数据,printf 重写为向串口 1 发送数据。详看:串口驱动程序之「发送数据」
  2. 串口 2 用于链接 ESP8266,需要实现不定长数据的收发。详看:串口驱动程序之「不定长字符串的接收」
  3. 使用 DHT11 检测环境温湿度。详看:DHT11 驱动程序
  4. OLED 模块作为温湿度数据的实时显示。详看:OLED 驱动程序
  5. 一个任务指示灯,单纯用来指示 FreeRTOS 任务有没有卡死
  6. 一个 LED 灯用来应答阿里云发来的数据

以下代码主要解析任务分配以及设备初始化的编写。标准库和 HAL 具体实现略有不同(LED 用的端口、不定长字符串接收的实现方式),请以具体配置为准。

标准库

main.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
#include "stm32f10x.h"                  // Device header
#include "gpio_out.h"
#include "key.h"
#include "OLED.h"
#include "timer.h"
#include "SysTick.h"
#include "FreeRTOS.h"
#include "task.h"
#include <limits.h>
#include <string.h>
#include "dht11.h"
#include "esp8266.h"
#include "ALi_MQTT_Temp_Humi_LED_Device.h"

//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);


//任务优先级
#define LED5_TASK_PRIO 2
//任务堆栈大小
#define LED5_STK_SIZE 50
//任务句柄
TaskHandle_t LED5Task_Handler;
//任务函数
void led5_task(void *pvParameters);

//任务优先级
#define MAIN_TASK_PRIO 3
//任务堆栈大小
#define MAIN_STK_SIZE 50
//任务句柄
TaskHandle_t MainTask_Handler;
//任务函数
void main_task(void *pvParameters);

//任务优先级
#define WIFI_INIT_TASK_PRIO 4
//任务堆栈大小
#define WIFI_INIT_STK_SIZE 128
//任务句柄
TaskHandle_t WifiInitTask_Handler;
//任务函数
void wifi_init_task(void *pvParameters);

//任务优先级
#define WIFI_TASK_PRIO 5
//任务堆栈大小
#define WIFI_STK_SIZE 128
//任务句柄
TaskHandle_t WifiTask_Handler;
//任务函数
void wifi_task(void *pvParameters);

//任务优先级
#define TEST_ON_TASK_PRIO 6
//任务堆栈大小
#define TEST_ON_STK_SIZE 128
//任务句柄
TaskHandle_t TestOnTask_Handler;
//任务函数
void test_on_task(void *pvParameters);

//任务优先级
#define TEST_OFF_TASK_PRIO 6
//任务堆栈大小
#define TEST_OFF_STK_SIZE 128
//任务句柄
TaskHandle_t TestOffTask_Handler;
//任务函数
void test_off_task(void *pvParameters);

// LED灯
#define LED5 GPIO_Pin_5

// ESP8266 RESET
//#define ESP8266_RESET GPIO_Pin_7 // 低电平 reset


/*******************************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
int main()
{
SysTick_Init(72);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组4
GPIO_OUT_Init(LED5); // FreeRTOS任务指示灯

AMTHL_Init();


//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}

//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区

//创建任务指示灯任务
xTaskCreate((TaskFunction_t )led5_task,
(const char* )"led5_task",
(uint16_t )LED5_STK_SIZE,
(void* )NULL,
(UBaseType_t )LED5_TASK_PRIO,
(TaskHandle_t* )&LED5Task_Handler);
//创建main任务
xTaskCreate((TaskFunction_t )main_task,
(const char* )"main_task",
(uint16_t )MAIN_STK_SIZE,
(void* )NULL,
(UBaseType_t )MAIN_TASK_PRIO,
(TaskHandle_t* )&MainTask_Handler);
//创建wifi任务
xTaskCreate((TaskFunction_t )wifi_init_task,
(const char* )"wifi_init_task",
(uint16_t )WIFI_INIT_STK_SIZE,
(void* )NULL,
(UBaseType_t )WIFI_INIT_TASK_PRIO,
(TaskHandle_t* )&WifiInitTask_Handler);

vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}



void led5_task(void *pvParameters)
{
while(1)
{
GPIO_OUT_Set(LED5,1);
vTaskDelay(200);

GPIO_OUT_Set(LED5,0);
vTaskDelay(800);

}
}

extern unsigned int rec_data[4];
void main_task(void *pvParameters)
{
while(1)
{
DHT11_REC_Data();
OLED_ShowNum(2,7,rec_data[2],2);
OLED_ShowNum(2,10,rec_data[3],2);
OLED_ShowNum(3,7,rec_data[0],2);
OLED_ShowNum(3,10,rec_data[1],2);
Delay_ms(2000);// 延时2s 否则DHT11数据不稳定
}
}

// 一次性任务
void wifi_init_task(void *pvParameters)
{

AMTHL_Connet();
xTaskCreate((TaskFunction_t )wifi_task,
(const char* )"wifi_task",
(uint16_t )WIFI_STK_SIZE,
(void* )NULL,
(UBaseType_t )WIFI_TASK_PRIO,
(TaskHandle_t* )&WifiTask_Handler);
xTaskCreate((TaskFunction_t )test_on_task,
(const char* )"test_on_task",
(uint16_t )TEST_ON_STK_SIZE,
(void* )NULL,
(UBaseType_t )TEST_ON_TASK_PRIO,
(TaskHandle_t* )&TestOnTask_Handler);
xTaskCreate((TaskFunction_t )test_off_task,
(const char* )"test_off_task",
(uint16_t )TEST_OFF_STK_SIZE,
(void* )NULL,
(UBaseType_t )TEST_OFF_TASK_PRIO,
(TaskHandle_t* )&TestOffTask_Handler);

vTaskDelete(WifiInitTask_Handler);
}

void wifi_task(void *pvParameters)
{
vTaskDelay(2000);

while(1)
{
AMTHL_SendMessage();
vTaskDelay(10000);
}
}

// 两个任务以防任务偏向
void test_on_task(void *pvParameters)
{
while(1)
{
if(AMTHL_LED_ON()) // 消除抖动
Delay_ms(1000);
vTaskDelay(50);
}
}

void test_off_task(void *pvParameters)
{
while(1)
{
if(AMTHL_LED_OFF()) // 消除抖动
Delay_ms(1000);
vTaskDelay(50);
}
}

设备:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include "stm32f10x.h"                  // Device header
#include "ALi_MQTT_Temp_Humi_LED_Device.h"

#include "serial.h"
#include "OLED.h"
#include "esp8266.h"
#include "SysTick.h"
#include "dht11.h"
#include "gpio_out.h"

#define WIFI_SSID "ssid"
#define WIFI_PASSWORD "ssidpswd"
#define MQTT_CLIENT_ID "mqtt_stm32|securemode=2\\,signmethod=hmacsha1\\,timestamp=106|"
#define MQTT_USER_NAME "mqtt_stm32&kxxxxCq"
#define MQTT_PASSWD "5DE3xxxxxxxxxEA4C2"
#define BROKER_ASDDRESS "kxxxxxCq.iot-as-mqtt.cn-shanghai.aliyuncs.com"
#define SUB_TOPIC "/sys/kxxxxxq/mqtt_stm32/thing/service/property/set"
#define PUB_TOPIC "/sys/kxxxxxq/mqtt_stm32/thing/event/property/post"
#define JSON_FORMAT "{\\\"params\\\":{\\\"temperature\\\":%u\\,\\\"Humidity\\\":%u}\\,\\\"version\\\":\\\"1.0.0\\\"}"

#define SIGNAL_LED GPIO_Pin_6
#define MQTTPUB_BUF_SIZE 256
extern unsigned int rec_data[4]; // DHT11
extern USART_Buffer ESP8266_Buffer;
void AMTHL_Init(void){
OLED_Init();
Serial_Init(115200); // 波特率115200
ESP8266_Init(115200);
GPIO_OUT_Init(SIGNAL_LED);

OLED_Clear();
OLED_ShowString(1,1,"DHT11 Data");
OLED_ShowString(2,1,"Temp:");
OLED_ShowString(2,9,".");
OLED_ShowString(3,1,"Humi:");
OLED_ShowString(3,9,".");
}

void AMTHL_Connet(void){
printf("1. Set Station Mode.\r\n");
ESP8266_Cmd("AT+CWMODE=1","OK",500);
Delay_ms(1000);

printf("2. Close ESP8266 Echo.\r\n");
ESP8266_Cmd("ATE0","OK",500);
Delay_ms(1000);

printf("3. No Auto Connect WIFI.\r\n");
ESP8266_Cmd("AT+CWAUTOCONN=0\r\n","OK",500);
Delay_ms(1000);

printf("4. Start Connecting.\r\n");
ESP8266_Cmd("AT+CWJAP=\""WIFI_SSID"\",\"" WIFI_PASSWORD "\"","OK",200);
Delay_ms(5000);
ESP8266_Cmd("AT+MQTTUSERCFG=0,1,\"" MQTT_CLIENT_ID "\",\"" MQTT_USER_NAME "\",\"" MQTT_PASSWD "\",0,0,\"\"","OK",200);
Delay_ms(3000);
ESP8266_Cmd("AT+MQTTCONN=0,\"" BROKER_ASDDRESS "\",1883,0","OK",200);
Delay_ms(3000);
ESP8266_Cmd("AT+MQTTSUB=0,\"" SUB_TOPIC "\",0","OK",200);
Delay_ms(3000);
}

void AMTHL_SendMessage(void){
char buf[MQTTPUB_BUF_SIZE];
unsigned int temp,humi;
temp = rec_data[2];
humi = rec_data[0];
sprintf(buf,"AT+MQTTPUB=0,\"" PUB_TOPIC "\",\"" JSON_FORMAT "\",0,0",temp,humi);
ESP8266_Cmd(buf,"OK",200);

printf("Temp: %u degree, Humi: %u %%",temp,humi);
}

uint8_t AMTHL_LED_ON(void){
if (ESP8266_Buffer.FinishFlag){
if(ESP8266_Check_Str(ESP8266_Buffer.Body,"{\"LED\":1}")){
GPIO_OUT_Set(SIGNAL_LED,0); // 开灯
return 1;
}
}
return 0;
}

uint8_t AMTHL_LED_OFF(void){
if (ESP8266_Buffer.FinishFlag){
if(ESP8266_Check_Str(ESP8266_Buffer.Body,"{\"LED\":0}")){
GPIO_OUT_Set(SIGNAL_LED,1); // 关灯
return 1;
}
}
return 0;
}

HAL 库

任务配置:

image.png

freertos.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/* Includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "task.h"
#include "main.h"
#include "cmsis_os.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "OLED.h"
#include "dht11.h"
#include "usart.h"
#include "esp8266.h"
#include "ali_device.h"
/* USER CODE END Includes */


/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */
extern uint8_t temperature;
extern uint8_t humidity;
/* USER CODE END Variables */

// 省略自动生成内容


void led_task_func(void *argument)
{
/* USER CODE BEGIN led_task_func */
/* Infinite loop */
for(;;)
{
// 任务指示灯
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);
osDelay(200);
HAL_GPIO_TogglePin(GPIOC,GPIO_PIN_13);
osDelay(800);
}
/* USER CODE END led_task_func */
}


void detect_task_finc(void *argument)
{
/* USER CODE BEGIN detect_task_finc */

/* Infinite loop */
for(;;)
{
if(AMTHL_LED_ON())
osDelay(500);
if(AMTHL_LED_OFF())
osDelay(500);
osDelay(50);
}
/* USER CODE END detect_task_finc */
}


void ali_mqtt_task_func(void *argument)
{
/* USER CODE BEGIN ali_mqtt_task_func */
AMTHL_Connet();
/* Infinite loop */
for(;;)
{
// 顺带显示
DHT11_Read_Data(&humidity,&temperature);
OLED_ShowNum(2,7,temperature,2);
OLED_ShowNum(3,7,humidity,2);
AMTHL_SendMessage();
osDelay(2000);
}
/* USER CODE END ali_mqtt_task_func */
}

设备:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

#include "cmsis_os.h"
#include "usart.h"
#include "esp8266.h"
#include "dht11.h"

#include "OLED.h"

#include "ali_device.h"

#define WIFI_SSID "ssid"
#define WIFI_PASSWORD "ssidpswd"
#define MQTT_CLIENT_ID "mqtt_stm32|securemode=2\\,signmethod=hmacsha1\\,timestamp=106|"
#define MQTT_USER_NAME "mqtt_stm32&kxxxxCq"
#define MQTT_PASSWD "5DE3xxxxxxxxxEA4C2"
#define BROKER_ASDDRESS "kxxxxxCq.iot-as-mqtt.cn-shanghai.aliyuncs.com"
#define SUB_TOPIC "/sys/kxxxxxq/mqtt_stm32/thing/service/property/set"
#define PUB_TOPIC "/sys/kxxxxxq/mqtt_stm32/thing/event/property/post"
#define JSON_FORMAT "{\\\"params\\\":{\\\"temperature\\\":%u\\,\\\"Humidity\\\":%u}\\,\\\"version\\\":\\\"1.0.0\\\"}"


#define MQTTPUB_BUF_SIZE 256
char buf[MQTTPUB_BUF_SIZE];

extern uint8_t temperature;
extern uint8_t humidity;

//#define MQTTPUB_BUF_SIZE 256

//extern unsigned int rec_data[4]; // DHT11
//extern USART_Buffer ESP8266_Buffer;
void AMTHL_Init(void){
OLED_Clear();
OLED_ShowString(1,1,"DHT11");
OLED_ShowString(2,1,"temp:");
OLED_ShowString(2,10,"^C");
OLED_ShowString(3,1,"humi:");
OLED_ShowString(3,10,"%");

}

void AMTHL_Connet(void){
printf("1. Set Station Mode.\r\n");
ESP8266_Cmd((uint8_t*)"AT+CWMODE=1",(uint8_t*)"OK",100);
osDelay(100);

printf("2. Close ESP8266 Echo.\r\n");
ESP8266_Cmd((uint8_t*)"ATE0",(uint8_t*)"OK",100);
osDelay(100);

printf("3. No Auto Connect WIFI.\r\n");
ESP8266_Cmd((uint8_t*)"AT+CWAUTOCONN=0\r\n",(uint8_t*)"OK",500);
osDelay(100);

printf("*** Start Connecting. ***\r\n");
ESP8266_Cmd((uint8_t*)"AT+CWJAP=\""WIFI_SSID"\",\"" WIFI_PASSWORD "\"",(uint8_t*)"OK",1000);
osDelay(5000);
ESP8266_Cmd((uint8_t*)"AT+MQTTUSERCFG=0,1,\"" MQTT_CLIENT_ID "\",\"" MQTT_USER_NAME "\",\"" MQTT_PASSWD "\",0,0,\"\"",(uint8_t*)"OK",200);
osDelay(3000);
ESP8266_Cmd((uint8_t*)"AT+MQTTCONN=0,\"" BROKER_ASDDRESS "\",1883,0",(uint8_t*)"OK",200);
osDelay(3000);
ESP8266_Cmd((uint8_t*)"AT+MQTTSUB=0,\"" SUB_TOPIC "\",0",(uint8_t*)"OK",200);
osDelay(3000);
}

void AMTHL_SendMessage(void){

sprintf(buf,"AT+MQTTPUB=0,\"" PUB_TOPIC "\",\"" JSON_FORMAT "\",0,0",temperature,humidity);
ESP8266_Cmd((uint8_t*)buf,(uint8_t*)"OK",200);

printf("Temp: %u degree, Humi: %u %%",temperature,humidity);
}

uint8_t AMTHL_LED_ON(void){
if (UART2_Rx_flg){
if(ESP8266_Check_Str(RxBuf,(uint8_t*)"{\"LED\":1}")){
HAL_GPIO_WritePin(GPIOA,SIGNAL_LED,(GPIO_PinState)0); // 开灯
return 1;
}
}
return 0;
}

uint8_t AMTHL_LED_OFF(void){
if (UART2_Rx_flg){
if(ESP8266_Check_Str(RxBuf,(uint8_t*)"{\"LED\":0}")){
HAL_GPIO_WritePin(GPIOA,SIGNAL_LED,(GPIO_PinState)1); // 关灯
return 1;
}
}
return 0;
}

本文参考