教你用STM32獲取新冠疫情數據(RT-Thread版)

2021-12-29 電子電路開發學習
文章目錄

上一篇文章我們使用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左右。

下載LOG

RT-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疫情監控】獲取裸機版本的工程源碼,我會把下載連結發給你。

更多

相關焦點

  • RPlidar A1軟體包使用教程,RT-Thread 連接 RPlidar A1 雷射雷達
    在這個時間之前,大家只要把作品分享到自己的博客/其他論壇,並提交作品連結至📫郵箱:andychen@rt-thread.com,即可參與評選!請務必報名,否則會影響評選!雷射雷達基本上已經成為無人駕駛的必備傳感器了,當然,它的成本也並不低,這篇文檔將會介紹如何利用 RT-Thread 獲取思嵐 RPLidar A1 的掃描數據。
  • RT-Thread智能車目標識別系統連載教程——連接 ROS 小車控制(5)
    這篇文章假定大家都已經會用 RT-Thread 的 env 工具下載軟體包,生成項目上傳固件到 stm32 上,並且熟悉 Ubuntu 的基本使用。可以看到上面默認的串口就是 USART2,這樣我們就可以生成對應的工程了:1pkgs --update2scons --target=mdk5 -s如果我們打開 Keil 項目,首先需要把 main.c 修改為 main.cpp,因為 rosserial 很多數據格式的定義都是用
  • DS18B20 數字溫度傳感器的使用和基於RT-Thread作業系統的實現
    可以在總線上只掛載一個DS18B20時使用其的ROM指令來獲取這64位的序列號。二、通信信號類型的介紹和對應代碼的實現所有的單總線器件要求採用嚴格的信號時序,以保證數據的完整性。DS18B20 共有 6 種信號類型:復位脈衝、應答脈衝、寫 0、寫 1、讀 0 和讀 1。所有這些信號,除了應答脈衝以外,都由主機發出同步信號。
  • RT-Thread Studio V1.1.3更新發布啦!
    或者你曾經手中拿著僅有的一個CMSIS DAP調試器,看著安裝好的RT-Thread Studio嘆氣說,要是能支持CMSIS DAP調試該多好呀。告訴大家一個好消息,這兩個新功能在新發布的Thread Studio V1.1.3版本同時支持啦!QEMU是一個支持跨平臺的虛擬機,它可以虛擬很多開發板。
  • 【實戰】基於TensorRT 加速YOLO系列以及其他加速算法實戰與對比
    tensorRT加速反而慢了一些,使用cpp版快了3倍,如果是使用YOLOV5 X的大模型,加速效果會更明顯。But if you are proficient in tensorrt, please check the readme of the model you want directly.我的配置是cuda 10.1 cudnn 7 ,用同樣的方式配置好自己的版本,下載了tensorRT 6.0.1 的deb包,並且安裝了。
  • 乾貨 | 教你在Win10的ubuntu子系統下編譯micropython
    先確認一下Windows10系統的版本,如果是LTSB版(Long Time Service Branch 企業長期服務版),就需要更換到企業版。如果還沒有安裝過Linux子系統功能,首先需要在控制面板的「程序和功能」中允許適用於Linux的Windows子系統功能,並重新啟動計算機。
  • 高級進階筆記-基於STM32單片機的精彩設計實例合集
    回復「高階進階筆記」獲取單片機中級到高級開掛教程  文 | 華維(微信:546945621)  個人原創   全文約939字,閱讀大約需要2分鐘大家好,我是華維遠見的大明。不會stm32,那你還不好意思來應聘硬體工程師?stm32的學習資料多是零零散散的,電路城先將stm32資料做了一個整合,也將於STM32單片機的精彩設計實例做了一個匯總,適合新手迅速上手,也適合高手做創作。1、(畢設)基於STM32多功能MP3設計(原理圖+PCB+原始碼)還記得你第一個MP3的樣子嗎,還記得它有哪些功能嗎?
  • 騰訊二面:ThreadLocal比FastThreadLocal慢在哪裡?
    當調用ftl.get()方法獲取值時,直接從數組獲取返回,如return array[index],如下圖:該方法直接從ftlt線程獲取threadLocalMap,還沒有則創建一個InternalThreadLocalMap實例並設置進去,然後返回。
  • 深入分析ThreadLocal
    軟引用(Soft Reference):當虛擬機內存充足時觸發GC不會回收軟引用對象,但當內存不足且觸發GC時則會回收軟引用對象及佔用的內存。弱引用(Weak Reference):當GC觸發時不管虛擬機內存是否充足,都會回收弱引用對象及內存。
  • 手把手教你使用yolov5訓練自己的數據集並用TensorRT加速
    使用你的GPU允許的最大的--batch-size(16 GB設備的batch大小顯示)。整個數據集的壓縮包有18G。訓練自己的數據集首先是收集數據,可以找開源數據集,也可以自己爬蟲。我訓練的是反光衣模型,用的是磊哥開源的數據(反光衣數據)。
  • Thread的join方法原理
    眾所周知,「Java的鎖其實本質上是對象鎖」,因為我們前面調用的是thread.join(),所以這裡的「鎖」對象其實thread這個對象。那這裡wait釋放的是thread這個對象鎖。我們把上面的main方法簡單改一下,用另一個線程是佔住thread這個對象鎖,就比較直觀了:public static void main(String[] args) throws InterruptedException {    Thread thread = new
  • 【STM32+cubemx】0014 HAL庫開發:電源控制(三種低功耗模式:sleep、stop、standby)
    1)stm32的低功耗模式簡介stm32有三種低功耗模式,功耗依次降低:睡眠模式(sleep mode),停止模式(stop mode),待機模式(standby mode)。睡眠模式:只有內核時鐘關閉,外設仍在運行;可以通過任意一個中斷或喚醒事件喚醒;喚醒後回到睡眠的位置向後執行。
  • 技術培訓 | STM32G0新一代入門利器性能介紹及STM32安全技術深度解析
    STM32 新成員 STM32G0 系列,是更加高效、可靠、易用的入門級利器。
  • STM32最小系統板分享
    8月的電子設計大賽,可能用到stm32。
  • 教你如何下載國際版的刺激戰場
    感覺都變得沒有以前的好了,紛紛都想去玩國際版的「刺激戰場」,但卻不知道怎麼下載。今天扎比就來教教小夥伴們。1、關注本公眾號「JaBi扎比」,後臺回復「帳號」獲取美區 Apple ID 的帳號與密碼。3、登錄剛才在本公眾號「JaBi扎比」獲取的美區帳號,輸入完成後點擊下方的「登錄」按鈕。
  • 在NVIDIA Drive PX上利用TensorRT 3 進行快速INT8推理
    雖然這條新指令提供了更快的計算速度,但在以這種簡化的INT8格式表示深度神經網絡的權值和激活度方面存在重大挑戰。如表1所示,與FP32或FP16相比,INT8的可表示值的動態範圍和粒度受到了很大的限制。
  • 飛哥教你使用異步編程提升服務性能
    JDK5的Future只能用輪詢或者阻塞的方式獲取結果,caller端處理比較繁瑣。Guava的ListenableFuture,特別是JDK8的CompletableFuture,則是完整實現了Promise風格的異步API。
  • Java Thread 你不得不知的那些事
    如果你不恰當的使用了ReentrantLock或者ReentrantReadWriteLock類,就有可能陷入BLOCKED狀態,這個也是我們調優中經常會遇到的情況,解決方案也很簡單,找到等待上鎖的地址,分析是否發生了Thread starvation。       至於TIME_WAITING狀態,官方文檔也講解的比較好,即你在調用下面方法時,線程會進入該狀態。
  • bilibili_api,僅用 3 行代碼獲取B站(彈幕、評論、用戶)數據
    作者丨天作來源丨天作之程(jhtmtzzc)今天介紹一個獲取B站數據的Python擴展庫-bilibili_api可以獲取的數據包括:這次用「Running這是之前獲取彈幕的過程:1、彈幕數據接口https://comment.bilibili.com/123072475.xml (一個固定的url地址 + 視頻的cid + .xml)2、利用Request模塊,獲取數據