昨天发了一篇:用STM32做个东京奥运奖牌榜,有很多朋友留言说要搞一个疫情数据的监控平台,这不今天安排上了!
目 录
使用到的软件包
0.RT-Thread Studio的下载和安装
1.硬件准备
2.新建工程
3.添加LED闪烁功能
4.添加ESP8266软件包
5.疫情数据的获取
6.疫情数据的解析
7.疫情数据的显示
8.开源地址
最终的显示效果:这是2020年写的一篇文章,所以数据是去年的!大家别误会了!


整个流程下来,如果顺利的话,可以在2个小时内完成。
使用到的软件包at device:用于ESP8266配网
webclient:用于发送HTTPS请求
mbdetls:用于HTTPS加密
cJSON:用于JSON数据解析
一站式的 RT-Thread 开发工具,通过简单易用的图形化配置系统以及丰富的软件包和组件资源,让物联网开发变得简单和高效。

支持多种芯片,STM32全系列
支持创建裸机工程、RT-Thread Nano和Master工程
强大的代码编辑功能,基于Eclipse框架
免费无版权限制,基于开源Eclipse和ARM-GCC编译器。
支持多种仿真器,J-Link,ST-link等,支持在线调试,变量观察。
SDK管理器,图形化配置RT-Thread软件包,同步RT-Thread最新版本。
集成Putty串口终端工具
更多的使用教程:
https://www.rt-thread.org/page/studio.html
目前最新版本为1.1.3版本,支持3种下载方式,我们选择最后一个下载方式,从RT-Thread 官网服务器上下载。
下载地址:
http://117.143.63.254:9012/www/studio/download/RT-Thread%20Studio-v1.1.3-setup-x86_64_20200731-2100.exe

安装过程和常用的软件安装方法一样,选择安装路径,然后Next就行了。
1.硬件准备开发板用的是我在大四时自己设计的STM32开发板——NiceDay,基于STM32F103RET主控。
这是我设计的第二块板子(第一块是毕业设计两轮平衡车主板),是在大四快毕业时,毕设实物和论文完成之后还有点时间,就设计了这款板子,最开始是准备做桌面天气时钟的。

RT-Thread Studio支持创建裸机工程、包含RT-Thread Nano版本的工程和包含Master版本的工程。这里,我们选择创建RT-Thread 项目
,即包含完整版RT-Thread的工程。

工程支持基于芯片创建工程,或者基于已有的BSP创建,这里使用的是我自己设计的开发板,所以选择基于芯片,选择芯片型号:STM32F103RE
,调试串口选择串口1,调试器选择J-Link,SWD接口。

创建完成之后,直接按Ctrl+B编译整个工程,第一次编译时间会长一点,如果修改很少,下次再进行编译就会很快了,可以看到无警告无错误。

使用SWD接口连接JLink调试器和开发板,开发板上电,直接点击下载按钮,也可以使用快捷键Ctrl+Alt+D下载

底部可以看到下载信息,从LOG来看,下载的程序文件是Bin文件,比较,擦除,编程,验证,复位整个流程耗时13s左右。

RT-Thread Studio是自带Putty串口终端的,点击终端图标:

选择串口号、波特率、文字编码方式等。

底部切换到终端窗口,可以看到串口终端输出信息:

这样,不到5分钟,一个基于STM32F103RET6的工程模板就创建好了,包含RT-Thread完整版操作系统,整个过程不需要写一行代码,完全图形化配置。
3.添加LED闪烁功能作为单片机点灯小能手,RT-Thread下如何点灯是必须掌握的。打开RT-Thread组件图形化配置界面,可以看到默认开启了PIN和串口设备驱动的。

在main.c文件中添加LED闪烁功能。包含头文件和添加宏定义
#include
#include
#define LED_RED_PIN GET_PIN(A, 7)
#define LED_BLUE_PIN GET_PIN(A,6)
int main(void)
{
int count = 1;
rt_pin_mode(LED_RED_PIN, PIN_MODE_OUTPUT);
rt_pin_mode(LED_BLUE_PIN, PIN_MODE_OUTPUT);
while (count++)
{
rt_pin_write(LED_BLUE_PIN, PIN_LOW);
rt_pin_write(LED_RED_PIN, PIN_LOW);
rt_thread_mdelay(100);
rt_pin_write(LED_BLUE_PIN, PIN_HIGH);
rt_pin_write(LED_RED_PIN, PIN_HIGH);
rt_thread_mdelay(100);
}
return RT_EOK;
}
重新编译,下载。可以看到LED闪烁起来了。工程默认是使用内部RC作为输入时钟,所以无论你的板子是8M还是12M,都可以正常闪烁。我的开发板是8M晶体,这里我们配置使用外部HSE作为输入时钟。
打开drivers->stm32f1xx_hal_conf.h
文件,修改HSE_VALUE宏定义为8M。

打开drivers->drv_clk.c
文件:

配置PLL时钟源为HSE,并设置倍频系数为9。


这里根据实际板子晶体频率来设置,如果是12M晶体,倍频系数应该设置为6,如果是16M,需要参考时钟树,先2倍分频,然后9倍倍频。
#include
void system_clock_config(int target_freq_Mhz)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
/** Initializes the CPU, AHB and APB busses clocks
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
........
//9倍频
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; //8*9=72M
........
}
这样就修改为外部8M晶体作为PLL时钟源,再次编译下载,和之前的现象是一样的。
4.添加ESP8266软件包联网设备,我们选择的是ESP8266-01S,如果看过上一篇疫情监控三部曲——在STM32F103 MCU上实现(裸机版),里面介绍了如何配置ESP8266 GET HTTPS请求, 配置工作模式 > 连接WiFi > 与服务器建立SSL连接 > 发送GET请求获取数据
等等,整个流程固定而繁琐,那么能不能封装成一个模块,直接拿来使用呢?

这里就要介绍RT-Thread的AT Device软件包了,
AT device 软件包是由 RT-Thread AT 组件针对不同 AT 设备的移植文件和示例代码组成,目前支持的 AT 设备有:ESP8266、ESP32、M26、MC20、RW007、MW31、SIM800C、W60X 、SIM76XX、A9/A9G、BC26 、AIR720、ME3616、M6315、BC28、EC200X、M5311系列设备等,目前上述设备都完成对 AT socket
功能的移植,及设备通过 AT 命令实现标准 socket 编程接口,完成 socket 通讯的功能,具体功能介绍可参考 《RT-Thread 编程指南》AT 命令章节 。https://www.rt-thread.org/document/site/programming-manual/at/at/
简单的说,就是我只需要调用这个软件包,然后修改WiFi账号和密码,就可以直接配置ESP8266联网了。
由于AT Device依赖于libc组件,所以在添加AT Device软件包之前,先开启libc。
在RT-Thread Settings
中点击libc灰色图标,变成彩色说明已经开启。

添加AT Device软件包,点击立即添加

在弹出的软件包中心,搜索at_device
,然后点击添加,添加到当前工程。

在at_device软件包上右键,选择详细配置:

在弹出的页面,选择我们使用的WiFi模块类型,乐鑫的ESP8266系列,并配置WiFi账号和密码,WiFi模块所连接的串口号。

点击保存之后,工程会重新进行配置,添加相应的软件包文件到当前工程,重新生成Makefile文件,rtconfig文件等等。
虽然我们在at_device配置中选择了uart2作为at_device设备连接的串口。但此时串口2并没有开启,还需要我们手动使能。
打开drivers->board.h
文件,通过宏定义的方式使能串口2。
#define BSP_USING_UART2
#define BSP_UART2_TX_PIN "PA2"
#define BSP_UART2_RX_PIN "PA3"
这样就开启了UART2的片上外设,Ctrl + B
重新进行编译,时间会有些长,编译完成之后,可以看到flash文件大小明显比之前大了。

Ctrl + Alt + D
重新下载运行,打开串口终端:

可以看到,UART2初始化成功,WiFi连接成功。说明我们的串口模块已经可以正常工作了。提示[E/at.clnt] execute command (AT+CIPDNS_CUR?) failed!
失败信息,是因为当前ESP8266的固件版本不支持AT+CIPDNS_CUR?
这条命令,把固件升级到最新版本就好了。这个不影响后面的操作,所以就不用在意这个了。
测试一下ifconfig和ping命令,都是正常的。

在RT-Thread Studio中配置ESP8266模块联网,整个流程只写了3行代码,可以说是非常的快速方便。
5.疫情数据的获取WiFi模块连接上互联网之后,就可以连接GET疫情数据的API接口https://lab.isaaclin.cn/nCoV/api/overall
,然后读取返回的疫情数据。
在上一篇的裸机工程中,是通过先和服务器建立SSL连接,然后发送GET HTTPS请求,获取到的返回数据,那RT-Thread有没有这样功能的软件包呢?这里就需要添加另一个软件包webclient
。
WebClient 软件包是 RT-Thread 自主研发的,基于 HTTP 协议的客户端的实现,它提供设备与 HTTP Server 的通讯的基本功能。WebClient 软件包功能特点如下:
支持 IPV4/IPV6 地址;
支持 GET/POST 请求方法;
支持文件的上传和下载功能;
支持 HTTPS 加密传输;
完善的头部数据添加和处理方式。
和添加at_device一样,在软件包中心中搜索webclient
,

然后添加到当前工程,右键进行配置,由于我们的https://lab.isaaclin.cn/nCoV/api/overall
这个疫情数据接口是HTTPS类型的,根据软件包使用手册,我们需要选择TLS模式中的 MbedTLS。勾选添加GET和POST示例。

保存配置,看一下当前已经添加了哪些功能,可以看到有一些组件我们并没有去打开,但是已经被开启了,这是因为有些软件包是会依赖一些组件的,当使能软件包时,一些依赖的组件也被同时使能。

Ctrl + B
编译,Ctrl + Alt + D
下载运行。在终端输入web_get_test
测试GET请求功能。

可以看到,执行get命令之后,会返回一个字符串,那么GET的是哪个地址呢?打开packages->webclient-v2.1.2->samples->webclient_get_sample.c
文件,

可以看到GET的是这个地址:http://www.rt-thread.com/service/rt-thread.txt
,我们用电脑上的浏览器访问一下:

经过实际测试发现,GET HTTPS请求,还需要使能软件模拟RTC这个组件,否则会报assertion failed at function:gettimeofday, line number:19
错误。

我们重新写一个获取疫情数据的函数,并导出到MSH。
usr_ncov.c文件内容
//usr_ncov.c
#include "usr_ncov.h"
int get_NCOV_Data(void)
{
char *uri = RT_NULL;
struct webclient_session* session = RT_NULL;
uint8_t *buffer = RT_NULL;
int index, ret = 0;
int bytes_read, resp_status;
int content_length = -1;
int buffer_size = 1600;
uri = web_strdup(API_NCOV);
rt_kprintf("start get api: %s\r\n", API_NCOV);
if(uri != RT_NULL)
{
buffer = (unsigned char *) web_malloc(buffer_size);
if (buffer == RT_NULL)
{
rt_kprintf("no memory for receive buffer.\n");
ret = -RT_ENOMEM;
goto __exit;
}
/* create webclient session and set header response size */
session = webclient_session_create(buffer_size);
if (session == RT_NULL)
{
ret = -RT_ENOMEM;
goto __exit;
}
/* send GET request by default header */
if ((resp_status = webclient_get(session, uri)) != 200)
{
rt_kprintf("webclient GET request failed, response(%d) error.\n", resp_status);
ret = -RT_ERROR;
goto __exit;
}
rt_kprintf("webclient get response data: \n");
content_length = webclient_content_length_get(session);
if (content_length valueint;
dataChina.currentConfirmedIncr = cJSON_GetObjectItem(result, "currentConfirmedIncr")->valueint;
dataChina.confirmedCount = cJSON_GetObjectItem(result, "confirmedCount")->valueint;
dataChina.confirmedIncr = cJSON_GetObjectItem(result, "confirmedIncr")->valueint;
dataChina.curedCount = cJSON_GetObjectItem(result, "curedCount")->valueint;
dataChina.curedIncr = cJSON_GetObjectItem(result, "curedIncr")->valueint;
dataChina.deadCount = cJSON_GetObjectItem(result, "deadCount")->valueint;
dataChina.deadIncr = cJSON_GetObjectItem(result, "deadIncr")->valueint;
rt_kprintf("**********china ncov data**********\n");
rt_kprintf("%-23s: %8d, %-23s: %8d\n", "currentConfirmedCount", dataChina.currentConfirmedCount, "currentConfirmedIncr", dataChina.currentConfirmedIncr);
rt_kprintf("%-23s: %8d, %-23s: %8d\n", "confirmedCount", dataChina.confirmedCount, "confirmedIncr", dataChina.confirmedIncr);
rt_kprintf("%-23s: %8d, %-23s: %8d\n", "curedCount", dataChina.curedCount, "curedIncr", dataChina.curedIncr);
rt_kprintf("%-23s: %8d, %-23s: %8d\n", "deadCount", dataChina.deadCount, "deadIncr", dataChina.deadIncr);
/* global data parse */
global = cJSON_GetObjectItem(result, "globalStatistics");
if(global->type == cJSON_Object)
{
dataGlobal.currentConfirmedCount = cJSON_GetObjectItem(global, "currentConfirmedCount")->valueint;
dataGlobal.currentConfirmedIncr = cJSON_GetObjectItem(global, "currentConfirmedIncr")->valueint;
dataGlobal.confirmedCount = cJSON_GetObjectItem(global, "confirmedCount")->valueint;
dataGlobal.confirmedIncr = cJSON_GetObjectItem(global, "confirmedIncr")->valueint;
dataGlobal.curedCount = cJSON_GetObjectItem(global, "curedCount")->valueint;
dataGlobal.curedIncr = cJSON_GetObjectItem(global, "curedIncr")->valueint;
dataGlobal.deadCount = cJSON_GetObjectItem(global, "deadCount")->valueint;
dataGlobal.deadIncr = cJSON_GetObjectItem(global, "deadIncr")->valueint;
rt_kprintf("\n**********global ncov data**********\n");
rt_kprintf("%-23s: %8d, %-23s: %8d\n", "currentConfirmedCount", dataGlobal.currentConfirmedCount, "currentConfirmedIncr", dataGlobal.currentConfirmedIncr);
rt_kprintf("%-23s: %8d, %-23s: %8d\n", "confirmedCount", dataGlobal.confirmedCount, "confirmedIncr", dataGlobal.confirmedIncr);
rt_kprintf("%-23s: %8d, %-23s: %8d\n", "curedCount", dataGlobal.curedCount, "curedIncr", dataGlobal.curedIncr);
rt_kprintf("%-23s: %8d, %-23s: %8d\n", "deadCount", dataGlobal.deadCount, "deadIncr", dataGlobal.deadIncr);
} else return 1;
/* 毫秒级时间戳转字符串 */
updateTime = (time_t )(cJSON_GetObjectItem(result, "updateTime")->valuedouble / 1000);
updateTime += 8 * 60 * 60; /* UTC8校正 */
time = localtime(&updateTime);
/* 格式化时间 */
strftime(dataChina.updateTime, 20, "%m-%d %H:%M", time);
rt_kprintf("update: %s\r\n", dataChina.updateTime);/* 06-24 11:21 */
//数据在LCD显示
//gui_show_ncov_data(dataChina, dataGlobal);
} else return 1;
} else return 1;
rt_kprintf("\nparse complete \n");
}
else
{
rt_kprintf("JSON format error:%s\n", cJSON_GetErrorPtr()); //输出json格式错误信息
return 1;
}
cJSON_Delete(root);
return ret;
}
在数据接收完成之后,对JSON数据进行解析。

由于RT-Thread Studio使用的HAL库,所以LCD的GPIO初始化函数需要修改一下:
void lcd_gpio_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_AFIO_CLK_ENABLE();
__HAL_AFIO_REMAP_SWJ_NOJTAG();
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStructure.Pull = GPIO_PULLUP;
GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStructure.Pin = GPIO_PIN_9 | GPIO_PIN_8 | GPIO_PIN_7 | GPIO_PIN_6;
HAL_GPIO_Init(GPIOC, &GPIO_InitStructure); //GPIOC
GPIO_InitStructure.Pin = GPIO_PIN_8; //背光引脚PA8
HAL_GPIO_Init(GPIOA, &GPIO_InitStructure); //GPIOC
GPIO_InitStructure.Pin = GPIO_PIN_All;
HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_9 | GPIO_PIN_8 | GPIO_PIN_7 | GPIO_PIN_6, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_All, GPIO_PIN_SET);
}
延时函数换成:
rt_thread_mdelay(nms);
还有一点,在Keil中,文字编码选择GBK编码,1个汉字占用2个字节,而RT-Thread Studio为UTF-8编码,1个汉字占用3个字节,汉字显示函数需要调整:
void gui_show_chn(uint16_t x0, uint16_t y0, char *chn)
{
uint8_t idx = 0;
uint8_t* code[3]; //UTF-8:国=E59BBD
uint8_t size = sizeof(FONT_16X16_TABLE) / sizeof(FONT_16X16_TABLE[0]);
/* 遍历汉字,获取索引 */
for(idx = 0; idx
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?


微信扫码登录