上一篇文章我們使用STM32F103 MCU裸機開發的方式實現了疫情監控平臺。這次我們玩點高端的,使用RT-Thread Studio來實現同樣的功能,一起來看看吧!
文章目錄
使用到的軟體包
0.RT-Thread Studio的下載和安裝
1.硬體準備
2.新建工程
3.添加LED閃爍功能
4.添加ESP8266軟體包
5.疫情數據的獲取
6.疫情數據的解析
7.疫情數據的顯示
開源地址
最終的顯示效果:
後臺回復【RTT疫情監控】獲取基於RT-Thread的工程源碼,或者回復【STM32疫情監控】獲取裸機版本的工程源碼
有效文件就這9個,其他的就全是圖形化配置:
有效文件整個流程下來,如果順利的話,可以在2個小時內完成。
使用到的軟體包at device:用於ESP8266配網
webclient:用於發送HTTPS請求
mbdetls:用於HTTPS加密
cJSON:用於JSON數據解析
0.RT-Thread Studio的下載和安裝一站式的 RT-Thread 開發工具,通過簡單易用的圖形化配置系統以及豐富的軟體包和組件資源,讓物聯網開發變得簡單和高效。
RT-Thread Studio支持多種晶片,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主控。 這是我設計的第二塊板子(第一塊是畢業設計兩輪平衡車主板),是在大四快畢業時,畢設實物和論文完成之後還有點時間,就設計了這款板子,最開始是準備做桌面天氣時鐘的。
開發板2.新建工程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左右。
下載LOGRT-Thread Studio是自帶Putty串口終端的,點擊終端圖標:
終端按鈕選擇串口號、波特率、文字編碼方式等。
配置終端底部切換到終端窗口,可以看到串口終端輸出信息:
串口終端這樣,不到5分鐘,一個基於STM32F103RET6的工程模板就創建好了,包含RT-Thread完整版作業系統,整個過程不需要寫一行代碼,完全圖形化配置。
3.添加LED閃爍功能作為單片機點燈小能手,RT-Thread下如何點燈是必須掌握的。打開RT-Thread組件圖形化配置界面,可以看到默認開啟了PIN和串口設備驅動的。
圖形化配置界面在main.c文件中添加LED閃爍功能。包含頭文件和添加宏定義
#include <board.h>
#include <rtdevice.h>
#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 <rtdbg.h>
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請求獲取數據等等,整個流程固定而繁瑣,那麼能不能封裝成一個模塊,直接拿來使用呢?
esp8266這裡就要介紹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模塊所連接的串口號。
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命令之後,會返回一個字符串,那麼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錯誤。
使能RTC我們重新寫一個獲取疫情數據的函數,並導出到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 < 0)
{
rt_kprintf("webclient GET request type is chunked.\n");
do
{
bytes_read = webclient_read(session, buffer, buffer_size);
if (bytes_read <= 0)
break;
for (index = 0; index < bytes_read; index++)
{
rt_kprintf("%c", buffer[index]);
}
} while (1);
rt_kprintf("\n");
}
else
{
/* 讀取伺服器響應的數據 */
bytes_read = webclient_read(session, buffer, content_length);
rt_kprintf("data length:%d\n", bytes_read);
buffer[bytes_read] = '\0';
rt_kprintf("\n\n %s \n\n", buffer);
// rt_kprintf("parse data\r\n");
// parseData(buffer); //解析函數
rt_kprintf("\n");
}
__exit:
if (session)
webclient_close(session);
if (buffer)
web_free(buffer);
}
else
rt_kprintf("api error: %s\n", API_NCOV);
return ret;
}
MSH_CMD_EXPORT(get_NCOV_Data, get api ncov);
usr_ncov.h文件內容
#ifndef APPLICATIONS_USR_NCOV_H_
#define APPLICATIONS_USR_NCOV_H_
#include <webclient.h>
#include <rtdevice.h>
#include <rtthread.h>
#define API_NCOV "https://lab.isaaclin.cn/nCoV/api/overall"
int get_NCOV_Data(void);
#endif /* APPLICATIONS_USR_NCOV_H_
重新編譯,下載,運行。在終端運行這個命令:
命令獲取疫情數據可以看到獲取到了返回的數據,長度1366個字節。下一步就是對這個JSON數據進行解析,獲取到我們想要的疫情數據。
6.疫情數據的解析API返回的數據是JSON格式的,關於JSON的介紹和解析,可以查看使用cJSON庫解析和構建JSON字符串。數據的解析使用的開源小巧的cJSON解析庫,我們可以在軟體包管理中心直接添加:
添加cJSON在進行解析之前,先來分析一下JSON原始數據的格式:results鍵的值是一個數組,數組只有一個JSON對象,獲取這個對象對應鍵的值可以獲取到國內現存和新增確診人數、累計和新增死亡人數,累計和新增治癒人數等數據。
全球疫情數據保存在globalStatistics鍵裡,它的值是一個JSON對象,對象僅包含簡單的鍵值對,這些鍵的值,就是全球疫情數據,其中updateTime鍵的值是更新時間,這是毫秒級UNIX時間戳,可以轉換為標準北京時間。
{
"results": [{
"currentConfirmedCount": 509,
"currentConfirmedIncr": 16,
"confirmedCount": 85172,
"confirmedIncr": 24,
"suspectedCount": 1899,
"suspectedIncr": 4,
"curedCount": 80015,
"curedIncr": 8,
"deadCount": 4648,
"deadIncr": 0,
"seriousCount": 106,
"seriousIncr": 9,
"globalStatistics": {
"currentConfirmedCount": 4589839,
"confirmedCount": 9746927,
"curedCount": 4663778,
"deadCount": 493310,
"currentConfirmedIncr": 281,
"confirmedIncr": 711,
"curedIncr": 424,
"deadIncr": 6
},
"updateTime": 1593227489355
}],
"success": true
}
先定義了結構體NCOV_DATA,用於存儲國內和全球疫情數據:
struct NCOV_DATA{
int currentConfirmedCount;
int currentConfirmedIncr;
int confirmedCount;
int confirmedIncr;
int curedCount;
int curedIncr;
int deadCount;
int deadIncr;
int seriousCount;
int seriousIncr;
char updateTime[20];
};
對應的解析函數:
#include <cJSON.h>
struct NCOV_DATA dataChina = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "06-13 16:22"};;
struct NCOV_DATA dataGlobal = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL};
int parseData(uint8_t *str)
{
int ret = 0;
cJSON *root, *result_arr;
cJSON *result, *global;
time_t updateTime;
struct tm *time;
root = cJSON_Parse((const char *)str); //創建JSON解析對象,返回JSON格式是否正確
if (root != 0)
{
rt_kprintf("JSON format ok, start parse!!!\n");
result_arr = cJSON_GetObjectItem(root, "results");
if(result_arr->type == cJSON_Array)
{
// rt_kprintf("result is array\n");
result = cJSON_GetArrayItem(result_arr, 0);
if(result->type == cJSON_Object)
{
// rt_kprintf("result_arr[0] is object\n");
/* china data parse */
dataChina.currentConfirmedCount = cJSON_GetObjectItem(result, "currentConfirmedCount")->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數據進行解析。
解析結果7.疫情數據的顯示數據解析出來之後,剩下的就簡單了,把上一篇文章中9341的驅動文件移植過來就好了。
液晶屏使用的是3.2寸 LCD,IL9341驅動晶片,320*240解析度,16位並口。由於屏幕解析度比較低,可顯示的內容有限,所以只是顯示了最基本的幾個疫情數據。為了減小程序大小,GUI只實現了基本的畫點,畫線函數,字符的顯示,採用的是部分字符取模,只對程序中用到的漢字和字符進行取模。為了增強可移植性,程序中並沒有使用外置SPI Flash存儲整個字庫。
由於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 < size; idx++)
{
code[0] = FONT_16X16_TABLE[idx].chn;
code[1] = FONT_16X16_TABLE[idx].chn + 1;
code[2] = FONT_16X16_TABLE[idx].chn + 2;
//漢字內碼一致
if(!(strcmp(code[0], chn) || strcmp(code[1], chn+1) || strcmp(code[2], chn+2)))
{
gui_show_F16X16_Char(x0, y0, idx, WHITE);
return;
// break;
}
}
}
疫情數據顯示函數:
void gui_show_ncov_data(struct NCOV_DATA china, struct NCOV_DATA global)
{
uint8_t y0 = 20;
lcd_clear(BLACK);
gui_show_bar();
gui_drawLine(0, 18, 320, DIR_X, WHITE);
gui_drawLine(0, 38, 320, DIR_X, WHITE);
gui_drawLine(0, 138, 320, DIR_X, WHITE);
gui_drawLine(0, 158, 320, DIR_X, WHITE);
gui_drawLine(0, 220, 320, DIR_X, WHITE);
/* "國內疫情" */
gui_show_chn_string(128, y0, "國內疫情");
gui_show_line_data(40, "現存確診:", china.currentConfirmedCount, "較昨日:", china.currentConfirmedIncr);
gui_show_line_data(60, "累計確診:", china.confirmedCount, "較昨日:", china.confirmedIncr);
gui_show_line_data(80, "累計治癒:", china.curedCount, "較昨日:", china.curedIncr);
gui_show_line_data(100, "現存重症:", china.seriousCount, "較昨日:", china.seriousIncr);
gui_show_line_data(120, "累計死亡:", china.deadCount, "較昨日:", china.deadIncr);
/* 全球疫情 */
gui_show_chn_string(128, 140, "全球疫情");
gui_show_line_data(160, "現存確診:", global.currentConfirmedCount, "較昨日:", global.currentConfirmedIncr);
gui_show_line_data(180, "累計治癒:", global.curedCount, "較昨日:", global.curedIncr);
gui_show_line_data(200, "累計死亡:", global.deadCount, "較昨日:", global.deadIncr);
gui_show_chn_string(160, 222, "更新於:");
gui_show_F8X16_String(230, 222, china.updateTime, GREEN);
}
代碼已經開源,地址在文末,歡迎大家參與,豐富這個小項目的功能!
如果GitHub下載速度太慢,可以關注我的公眾號,電子電路開發學習(ID: MCU149),在後臺回復【RTT疫情監控】獲取基於RT-Thread的工程源碼,或者回復【STM32疫情監控】獲取裸機版本的工程源碼,我會把下載連結發給你。
更多