首发:Rice 嵌入式开发技术分享
作者:RiceDIY
摘要
- 这个桌面小盒子是之前的东西,一直放着没有整理好。最近有空了就把他整理整理。
- 小盒子主要用来显示时间和天气预报,功能比较简单,其实还有很多可以玩的,懒得弄,所以就把最简单的整理出来。
- 软件是基于rt-thread, UI采用lvgl。
功能主要分为两个部分
第一部分--功能
功能部分主要分为两个部分,一个是NTP获取实时时间,一个是天气等信息。
- 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);
}
`
- 获取天气信息,这个也比较简单,通过调用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
- UI方面我使用了LVGL,这个开始比较方便的UI框架,可移植性搞。网上的教程也比较多。
- 由于rt-thread的LVGL软件包比较旧,而且有些东西没有移植好,所以我自己根据我的平台重新移植一份。
- 根据LVGL移植流程,需要对接porting下源文件的API回调。主要包含,disp(显示)、indev(输入设备,如触摸,按键)、fs(文件系统)。而本项目只对接disp(显示)、fs(文件系统, 读取SD卡的图片资源)
- 其中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);
}
`
- 这个盒子的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;
}
`
效果:
- 一个简单的桌面小工具:
- 放在桌面,和PM2.5来实时监测工作环境。
关注微信公众号『Rice嵌入式开发技术分享』,后台回复“微信”添加作者微信,备注”入群“,便可邀请进入技术交流群。
推荐阅读
更多嵌入式技术干货请关注Rice 嵌入式开发技术分享