Rice我叫加饭? · 2021年03月26日

基于RT-THREAD的桌面小工具

首发:Rice 嵌入式开发技术分享
作者:RiceDIY

摘要

  • 这个桌面小盒子是之前的东西,一直放着没有整理好。最近有空了就把他整理整理。
  • 小盒子主要用来显示时间和天气预报,功能比较简单,其实还有很多可以玩的,懒得弄,所以就把最简单的整理出来。
  • 软件是基于rt-thread, UI采用lvgl。

功能主要分为两个部分

第一部分--功能

功能部分主要分为两个部分,一个是NTP获取实时时间,一个是天气等信息。
  1. NTP比较简单,RTT提供了相关API。代码如下:

`void get_local_time(void)
{
    time_t now;
    now = time(RT_NULL);
    tab_info.cur_tm = localtime(&now);
    rt_kprintf("time: %2d:%2d", tab_info.cur_tm->tm_hour, tab_info.cur_tm->tm_min);
}
`

  1. 获取天气信息,这个也比较简单,通过调用tianqiapi既可以获得。代码如下:

`#define GET_WEATHER_URI "http://www.tianqiapi.com/api/?version=v6&cityid=101280601&appid=65251531&appsecret=Yl2bzCYb"

struct weather_info
{
    char *response;
    cJSON *root;
    cJSON *date;
    cJSON *cur_temp;
    cJSON *humidity;
    cJSON *wea_img;
};

int rp_weather_info_get(void)
{
    char *uri = RT_NULL;

    uri = web_strdup(GET_WEATHER_URI);

    if (webclient_request(uri, RT_NULL, RT_NULL, (unsigned char **)&info.response) < 0)
    {
        rt_kprintf("get weather fail!\n");
        return -RT_ERROR;
    }

    info.root = cJSON_Parse(info.response);

    if(info.root != RT_NULL)
    {
        info.date = cJSON_GetObjectItem(info.root, "date");
        info.cur_temp = cJSON_GetObjectItem(info.root, "tem");
        info.humidity = cJSON_GetObjectItem(info.root, "humidity");
        info.wea_img = cJSON_GetObjectItem(info.root, "wea_img");

         rt_kprintf("date : %s, cur_temp : %s, humidity : %s, wea_img : %s\n",
                   info.date->valuestring, info.cur_temp->valuestring,
                   info.humidity->valuestring, info.wea_img->valuestring);
    }

    return RT_EOK;
}

void rp_weather_info_free(void)
{
    if (info.response != RT_NULL)
    {
        web_free(info.response);
    }

    if(info.root != RT_NULL)
    {
        cJSON_Delete(info.root);
        info.root = RT_NULL;
    }
}

`

第二部分--UI

  1. UI方面我使用了LVGL,这个开始比较方便的UI框架,可移植性搞。网上的教程也比较多。
  2. 由于rt-thread的LVGL软件包比较旧,而且有些东西没有移植好,所以我自己根据我的平台重新移植一份。
  3. 根据LVGL移植流程,需要对接porting下源文件的API回调。主要包含,disp(显示)、indev(输入设备,如触摸,按键)、fs(文件系统)。而本项目只对接disp(显示)、fs(文件系统, 读取SD卡的图片资源)

image.png

  • 其中fs的代码,文件系统我对接的dfs\_posix的接口,因为我只使用读文件操作,所以直接对接部分接口,代码如下:

`static lv_fs_res_t fs_open(lv_fs_drv_t  drv, void  file_p, const char * path, lv_fs_mode_t mode)
{
    lv_fs_res_t res = LV_FS_RES_NOT_IMP;
    int fd = 0;

    if(mode == LV_FS_MODE_RD)
    {
        if((fd = open(path, O_RDONLY)) > 0)
        {
            (file_t )file_p = fd;
            res = LV_FS_RES_OK;
        }
        else
        {
            res = LV_FS_RES_NOT_EX;
        }
    }
    return res;
}

static lv_fs_res_t fs_close (lv_fs_drv_t  drv, void  file_p)
{
    lv_fs_res_t res = LV_FS_RES_UNKNOWN;

    int fd = (file_t )file_p;
    if (close(fd) == 0)
    {
        res = LV_FS_RES_OK;
    }

    return res;
}

static lv_fs_res_t fs_read (lv_fs_drv_t  drv, void  file_p, void  buf, uint32_t btr, uint32_t  br)
{
    lv_fs_res_t res = LV_FS_RES_UNKNOWN;

    int fd = (file_t )file_p;
    int read_bytes = read(fd, buf, btr);
    if (read_bytes >= 0)
    {
        *br = read_bytes;
        res = LV_FS_RES_OK;
    }

    return res;
}

static lv_fs_res_t fs_seek (lv_fs_drv_t  drv, void  file_p, uint32_t pos)
{
    lv_fs_res_t res = LV_FS_RES_UNKNOWN;

    int fd = (file_t )file_p;
    if (lseek(fd, pos, SEEK_SET) >= 0)
    {
        res = LV_FS_RES_OK;
    }

    return res;
}

static lv_fs_res_t fs_tell (lv_fs_drv_t  drv, void  file_p, uint32_t * pos_p)
{
    lv_fs_res_t res = LV_FS_RES_UNKNOWN;

    int fd = (file_t )file_p;
    off_t pos = lseek(fd, 0, SEEK_CUR);
    if (pos >= 0)
    {
        *pos_p = pos;
        res = LV_FS_RES_OK;
    }

    return res;
}

void lv_port_fs_init(void)
{
    lv_fs_drv_t fs_drv;

    fs_init();
    lv_fs_drv_init(&fs_drv);

    fs_drv.file_size = sizeof(file_t);
    fs_drv.letter = 'S';
    fs_drv.open_cb = fs_open;
    fs_drv.close_cb = fs_close;
    fs_drv.read_cb = fs_read;
    fs_drv.seek_cb = fs_seek;
    fs_drv.tell_cb = fs_tell;

    lv_fs_drv_register(&fs_drv);
}
`

  • disp代码,因为是软件SPI,所以我采用的是整屏刷新。

`static void disp_init(void)
{

}

static void disp_flush(lv_disp_drv_t  disp_drv, const lv_area_t  area, lv_color_t * color_p)
{
    int32_t x;
    int32_t y;

    rt_uint8_t frame_buffer = (rt_uint8_t )psram_malloc(240  240  2);
    if(frame_buffer !=RT_NULL)
    {

        for(y = area->y1; y <= area->y2; y++)
        {
            for(x = area->x1; x <= area->x2; x++)
            {
                frame_buffer[(y  240  2)+(x * 2)] = color_p->full >> 8;
                frame_buffer[(y  240  2)+(x * 2 + 1)] = color_p->full;
                color_p++;
            }
        }

        lcd_show_page((rt_uint8_t *)frame_buffer);

        psram_free(frame_buffer);
        frame_buffer = RT_NULL;
    }

    lv_disp_flush_ready(disp_drv);
}

void lv_port_disp_init(void)
{
    lv_disp_drv_t disp_drv;

    disp_init();

    static lv_disp_buf_t draw_buf_dsc;
    static lv_color_t draw_buf[LV_HOR_RES_MAX * LV_VER_RES_MAX];
    lv_disp_buf_init(&draw_buf_dsc, draw_buf, NULL, LV_HOR_RES_MAX * LV_VER_RES_MAX);

    lv_disp_drv_init(&disp_drv);

    disp_drv.hor_res = LV_HOR_RES_MAX;
    disp_drv.ver_res = LV_VER_RES_MAX;

    disp_drv.flush_cb = disp_flush;

    disp_drv.buffer = &draw_buf_dsc;

    lv_disp_drv_register(&disp_drv);
}
`

  1. 这个盒子的UI设计比较简单,主要实现在main\_tab\_hander中实现,会先调用同步一下时间和天气信息。然后创建了一个task,来更新页面信息。

`void get_main_tab_info(void)
{
    char hour_h[2] = {'0', '\0'};
    char hour_l[2] = {'0', '\0'};
    char min_h[2] = {'0', '\0'};
    char min_l[2] = {'0', '\0'};

    time_t now;
    now = time(RT_NULL);
    tab_info.cur_tm = localtime(&now);

    if(tab_info.cur_tm->tm_min == 0)
    {
        rp_weather_info_get();
        rt_sprintf(tab_info.date, "%s", info.date->valuestring);
        rt_sprintf(tab_info.temp, "T: %s C", info.cur_temp->valuestring);
        rt_sprintf(tab_info.humi, "H: %s", info.humidity->valuestring);
        rt_sprintf(tab_info.wea_img, "S:/sd/weather_img/%s.bin", info.wea_img->valuestring);
        rp_weather_info_free();
        lv_label_set_text(date_label, tab_info.date);
        lv_label_set_text(temp_label, tab_info.temp);
        lv_label_set_text(humi_label, tab_info.humi);
        lv_img_set_src(weather_img, tab_info.wea_img);
    }

    hour_h[0] = (tab_info.cur_tm->tm_hour/10) + '0';
    hour_l[0] = (tab_info.cur_tm->tm_hour%10) + '0';
    min_h[0] = (tab_info.cur_tm->tm_min/10) + '0';
    min_l[0] = (tab_info.cur_tm->tm_min%10) + '0';

    lv_label_set_text(time_label[0], hour_h);
    lv_label_set_text(time_label[1], hour_l);
    lv_label_set_text(time_label[2], min_h);
    lv_label_set_text(time_label[3], min_l);
}

void time_task_handle(lv_task_t* task)
{
    task = NULL;
    lv_led_toggle(time_led[0]);
    lv_led_toggle(time_led[1]);
    get_main_tab_info();
}

int main_tab_hander(lv_obj_t parent, char name)
{
    rp_weather_info_get();

    rt_sprintf(tab_info.date, "%s", info.date->valuestring);
    rt_sprintf(tab_info.temp, "T: %s C", info.cur_temp->valuestring);
    rt_sprintf(tab_info.humi, "H: %s", info.humidity->valuestring);
    rt_sprintf(tab_info.wea_img, "S:/sd/weather_img/%s.bin", info.wea_img->valuestring);

    rp_weather_info_free();

    //日期
    static lv_style_t date_style;
    lv_style_init(&date_style);
    lv_style_set_text_font(&date_style, LV_STATE_DEFAULT, &lv_font_montserrat_20);
    lv_style_set_text_color(&date_style, LV_STATE_DEFAULT, LV_COLOR_WHITE);

    date_label = lv_label_create(parent, NULL);
    lv_obj_add_style(date_label, LV_LABEL_PART_MAIN, &date_style);
    lv_obj_align(date_label, NULL, LV_ALIGN_IN_TOP_MID, -30, 5);
    lv_label_set_recolor(date_label, true);
    lv_label_set_text(date_label, tab_info.date);

    //时间
    time_cont = lv_cont_create(parent, NULL);
    lv_obj_clean_style_list(time_cont, LV_OBJ_PART_MAIN);
    lv_obj_set_size(time_cont, 180, 70);
    lv_obj_align(time_cont, NULL, LV_ALIGN_IN_TOP_MID, 0, 30);
    lv_obj_set_style_local_bg_color(time_cont, LV_STATE_DEFAULT, LV_STATE_DEFAULT, LV_COLOR_MAKE(20, 20, 20));

    for (int i = 0; i < (sizeof(time_led) / sizeof(time_led[0])); i++)
    {
        time_led[i] = lv_led_create(time_cont, NULL);
        lv_obj_set_size(time_led[i], 8, 10);
        lv_obj_align(time_led[i], NULL, LV_ALIGN_CENTER, 0, ((i == 0) ? (-10) : (10)));
    }

    static lv_style_t time_style;
    lv_style_init(&time_style);
    lv_style_set_text_font(&time_style, LV_STATE_DEFAULT, &lv_font_montserrat_48);
    lv_style_set_text_color(&time_style, LV_STATE_DEFAULT, LV_COLOR_WHITE);

    const lv_coord_t time_coord[4] = { -60, -25, 25, 60 };
    for (int i = 0; i < (sizeof(time_label) / sizeof(time_label[0])); i++)
    {
        time_label[i] = lv_label_create(time_cont, NULL);
        lv_obj_add_style(time_label[i], LV_LABEL_PART_MAIN, &time_style);
        lv_label_set_text(time_label[i], "0");
        lv_obj_align(time_label[i], NULL, LV_ALIGN_CENTER, time_coord[i], 0);
    }

    weather_img = lv_img_create(parent, NULL);
    lv_obj_align(weather_img, NULL, LV_ALIGN_IN_BOTTOM_MID, -90, -90);
    lv_img_set_src(weather_img, tab_info.wea_img);

    //温湿度
    static lv_style_t humiture_style;
    lv_style_init(&humiture_style);
    lv_style_set_text_font(&humiture_style, LV_STATE_DEFAULT, &lv_font_montserrat_20);
    lv_style_set_text_color(&humiture_style, LV_STATE_DEFAULT, LV_COLOR_WHITE);

    temp_label = lv_label_create(parent, NULL);
    lv_obj_add_style(temp_label, LV_LABEL_PART_MAIN, &humiture_style);
    lv_obj_align(temp_label, NULL, LV_ALIGN_IN_BOTTOM_MID, 40, -70);
    // lv_label_set_recolor(temp_label, true);
    lv_label_set_text(temp_label, tab_info.temp);

    humi_label = lv_label_create(parent, NULL);
    lv_obj_add_style(humi_label, LV_LABEL_PART_MAIN, &humiture_style);
    lv_obj_align(humi_label, NULL, LV_ALIGN_IN_BOTTOM_MID, 40, -30);
    // lv_label_set_recolor(humi_label, true);
    lv_label_set_text(humi_label, tab_info.humi);

    time_task = lv_task_create(time_task_handle, 500, LV_TASK_PRIO_MID, NULL);
    time_task_handle(time_task);
    return 0;
}
`

效果:

  • 一个简单的桌面小工具:

image.png

  • 放在桌面,和PM2.5来实时监测工作环境。

image.png

关注微信公众号『Rice嵌入式开发技术分享』,后台回复“微信”添加作者微信,备注”入群“,便可邀请进入技术交流群。

image.png
推荐阅读

更多嵌入式技术干货请关注Rice 嵌入式开发技术分享
推荐阅读
关注数
1761
内容数
51
一个周末很无聊的嵌入式软件工程师,写写经验,写写总结。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息