RTThreadIoTOS · 2020年11月05日

【RT-Thread 开源作品秀】基于 RT-Thread 的“数码小精灵”设计与实现

作者:赵俊涛

一、概述

随着近年来智能化设备的不断增长,平板电脑、智能数字音视频播放器、移动数码相机等各类数码产品,正越来越受到城乡居民欢迎。特别是“云生活”让人们对数码产品有了更多需求,加上互联网技术、5G 技术、智能化新产品、新型分期消费模式等的出现,让数码产品消费热潮涌动。

本设计为基于 RT-Thread 的“数码小精灵”设计,硬件采用了以 BK7252 为主控芯片的麻雀一号开发板开发。BK7252 , 是一款高性能 WiFi 模块,采用高集成的无线射频芯片,内部集成 2.4GHz Wi-Fi 1T1R 先进技术,支持摄像头图像输出,拥有最佳的功耗性能、射频性能、稳定性、通用性和可靠性,适用于各种应用和不同产品需求。模块内部拥有 512KB 内嵌 RAM 和 4Mbyte Flash 空间,CPU 主频高达 180Mhz。

image.png

本设计在此基础上主要实现了以下功能,包括天气和疫情数据更新显示、MP3 音乐播放器以及数码拍照相机的功能。其中 MP3 播放器具有音量调节,播放/停止控制和歌曲切换的功能;数码相机将拍照图片进行 LCD 屏显示,同 时具有 SD 卡存储和 OneNet 云平台存储功能。该产品可以充当家庭数码助理的角色,因此取名“数码小精灵”。同时本作品完成的过程中参考了部分网络资料和网友的思路,在此一并表示感谢。

二、RT-Thread 使用情况概述

本设计基于麻雀一号开发板 SDK 进行开发,其 RT-Thread 为 3.1.0 版本。

image.png

图 1 RT-Thread 版本

在 RT-Thread 系统上的设备注册列表,其中主要使用了 rtc,sd0,w0,sound,uart1 等设备。使用到的 RT-Thread 组件包括了 FinSH 控制台,虚拟文件系统,POSIX 接口。在软件包上面涉及到网络工具及 NetUtils,WebClient,cJSON,EasyFlash,rt\_ota,TJpgDec,Player 等。

image.png

图2设备注册列表

三、硬件框架

image.png

麻雀一号开发板外设资源丰富,但资源相当丰富,集成 WiFi、BLE、摄像头、音频扬声器、MIC 录音、TF 卡座、五向按键、还有一个 1.44 寸的 LCD 屏,使用常见的 TypeC 接口作为供电和调试串口,预留支持锂电池供电接口。

本设计的人机交互部分主要利用了开发板的五向按键和 LCD 显示屏,其中按键用于功能选择,数据刷新以及音乐播放控制功能。

普通模式下:“ ←”: 音乐播放  、“⭕”:天气和疫情数据刷新  、 “→”:相机拍照

音乐播放模式下:“↑”:音量加 、“↓”:音量减 、“←”:下一曲 、“⭕”:停止播放

设备联网采用 WIFI 连接,上电自动连接网络。数据显示 LCD 进行显示,同时设备接有 SD 进行相机拍照的存储。此外照片同时可以通过 WIFI 上传至OneNet 云平台,进行远端网页或者手机等智能终端进行查看。

四、软件框架说明

系统软件流程框图如下图所示,设备上电后启动 RT-Thread 操作系统,同 时进行相关硬件设备的初始化操作,如 LCD 显示屏,音频扬声器,以及挂在SD 卡到文件系统等操作,之后进行设备的网络连接。网络连接后输入应用程序启动命令进行程序启动,LCD 显示欢迎界面,接下来用户可以通过五向按键进 行功能选择,主要是天气疫情数据更新显示、MP3 播放器功能以及数码相机的功能。相机拍照后会进行本地 SD 卡数据存储以及 OneNet 云端存储。

image.png

五、软件模块说明

1. 设备联网

设备联网主要使用到了 RT-Thread 组件中的 wlan 驱动程序实现,上电初始化完成后在主程序中查询 wlan 无线设备,并根据用户配置的 SSID 和PASSWORD 进行 WIFI 网络的连接。该部分的具体代码实现如下所示:

 1static int iot_station_connect(char *ssid, char *passwd) { 2rt_err_t result = RT_EOK; 3struct rt_wlan_info info; 4struct rt_wlan_device *wlan; 5rt_tick_t tick = 0; 6wlan = (struct rt_wlan_device *)rt_device_find(WIFI_DEVICE_STA 7_NAME); 8if (!wlan) 9{10rt_kprintf("no wlan:%s device\n", WIFI_DEVICE_STA_NAME);11return -1; }12result = rt_wlan_init(wlan, WIFI_STATION);13rt_wlan_register_event_handler(wlan, WIFI_EVT_STA_CONNECTED, i14ot_wlan_sta_connected_event);15rt_wlan_register_event_handler(wlan, WIFI_EVT_STA_DISCONNECTED16, iot_wlan_sta_disconnected_event);17rt_wlan_info_init(&info, WIFI_STATION, SECURITY_WPA2_AES_PSK,18ssid);19result = rt_wlan_connect(wlan, &info, passwd);20rt_wlan_info_deinit(&info);21return result;22}

2. NTP 网络时间同步

网络时间同步主要思路就是通过调用网络工具包中的 ntp 同步函数来实现,通过创建独立时间同步线程,达到定时同步网络时间的目的。该部分使用时需要启用 RT-Thread 中的 RTC 功能。相关代码如下:

 1{ 2time_t cur_time = ntp_sync_to_rtc(); 3if (cur_time) 4{ 5rt_kprintf("Get local time from NTP server: %s", ctime((const t 6ime_t*) &cur_time)); 7rt_kprintf("The system time is updated. Timezone is %d.\n", NTP 8_TIMEZONE); 9} }10static rt_thread_t tid1 = RT_NULL;11static void ntcthread1_entry(void * parameter) {12while ((1))13{14time_t cur_time = ntp_sync_to_rtc();15if (cur_time)16{17rt_kprintf("Get local time from NTP server: %s", ctime((const tim18e_t *) &cur_time));19rt_kprintf("The system time is updated. Timezone is %d.\n", NTP_T20IMEZONE);21break; }22else23{24rt_thread_mdelay(1000);25} } }26void NTCThreadInit(void) {27if (tid1 != RT_NULL)28{29rt_kprintf("ntc thread still run\n");30return; }31rt_kprintf("NTC thread init\n");32tid1 = rt_thread_create("NTC",33ntcthread1_entry, RT_NULL,34THREAD_STACK_SIZE,35THREAD_PRIORITY, THREAD_TIMESLICE);36if (tid1 != RT_NULL)37rt_thread_startup(tid1);38}

3. 天气疫情数据更新

该部分主要是利用了 webclient 工具包的功能,通过调用天气和疫情数据API 接口获取相关 Json 数据,并利用 CJson 工具包进行返回 Json 数据的解析。最后通过 LCD 进行数据显示。相关代码如下:

  1#define GET_URI "http://www.weather.com.cn/data/sk/%s.html" //  2获取天气的 API  3#define GET_FY2020_URI "http://www.dzyong.top:3005/yiqing/total" //疫情  4数据 API  5void get_weather(int argc, char **argv) {  6rt_uint8_t *buffer = RT_NULL;  7int resp_status;  8struct webclient_session *session = RT_NULL;  9char *weather_url = RT_NULL; 10int content_length = -1, bytes_read = 0; 11int content_pos = 0; 12char *city_name = rt_calloc(1,255); 13/* 为 weather_url 分配空间 */ 14weather_url = rt_calloc(1, GET_URL_LEN_MAX); 15if (weather_url == RT_NULL) 16{ 17rt_kprintf("No memory for weather_url!\n"); 18goto __exit; 19} 20if(argc == 1) { 21strcpy(city_name, AREA_ID); 22} 23else if (argc == 2) { 24strcpy(city_name, argv[1]); 25} 26/* 拼接 GET 网址 */ 27rt_snprintf(weather_url, GET_URL_LEN_MAX, GET_URI, city_name); 28/* 创建会话并且设置响应的大小 */ 29session = webclient_session_create(GET_HEADER_BUFSZ); 30if (session == RT_NULL) 31{ 32rt_kprintf("No memory for get header!\n"); 33goto __exit; 34} 35/* 发送 GET 请求使用默认的头部 */ 36if ((resp_status = webclient_get(session, weather_url)) != 200) { 37rt_kprintf("webclient GET request failed, response(%d) error.\n", resp_ 38status); 39goto __exit; 40} 41/* 分配用于存放接收数据的缓冲 */ 42buffer = rt_calloc(1, GET_RESP_BUFSZ); 43if (buffer == RT_NULL) 44{ 45rt_kprintf("No memory for data receive buffer!\n"); 46goto __exit; 47} 48content_length = webclient_content_length_get(session); 49if (content_length < 0) { 50/* 返回的数据是分块传输的. */ 51do 52{ 53bytes_read = webclient_read(session, buffer, GET_RESP_BUFSZ); 54if (bytes_read <= 0) { 55break; } }while (1); } 56else 57{ 58do 59{ 60bytes_read = webclient_read(session, buffer, 61content_length - content_pos > GET_RESP 62_BUFSZ ? 63GET_RESP_BUFSZ : content_length - conte 64nt_pos); 65if (bytes_read <= 0) { 66break; } 67content_pos += bytes_read; 68}while (content_pos < content_length); 69} 70/* 天气数据解析 */ 71weather_data_parse(buffer); 72__exit: 73/* 释放网址空间 */ 74if (weather_url != RT_NULL) 75rt_free(weather_url); 76/* 关闭会话 */ 77if (session != RT_NULL) 78webclient_close(session); 79/* 释放缓冲区空间 */ 80if (buffer != RT_NULL) 81rt_free(buffer); 82if(city_name != RT_NULL) 83rt_free(city_name); 84} 85/* 天气数据解析 */ 86void weather_data_parse(rt_uint8_t *data) { 87uint8_t temp[100]; 88cJSON *root = RT_NULL, *object = RT_NULL, *item = RT_NULL; 89root = cJSON_Parse((const char *)data); 90if (!root) 91{ 92rt_kprintf("No memory for cJSON root!\n"); 93return; } 94object = cJSON_GetObjectItem(root, "weatherinfo"); 95item = cJSON_GetObjectItem(object, "city"); 96rt_kprintf("\ncity :%s ", item->valuestring); 97lcd_clear(BLACK); 98item = cJSON_GetObjectItem(object, "temp"); 99rt_kprintf("\ntemp :%s ", item->valuestring);100rt_sprintf(temp,"温度: %s",item->valuestring);101lcd_disp_str_en_ch(0,20,temp,BLACK,WHITE);102item = cJSON_GetObjectItem(object, "WD");103rt_kprintf("\nwd :%s ", item->valuestring);104item = cJSON_GetObjectItem(object, "WS");105rt_kprintf("\nws :%s ", item->valuestring);106item = cJSON_GetObjectItem(object, "SD");107rt_kprintf("\nsd :%s ", item->valuestring);108rt_sprintf(temp,"湿度: %s",item->valuestring);109lcd_disp_str_en_ch(0,40,temp,BLACK,WHITE);110item = cJSON_GetObjectItem(object, "time");111rt_kprintf("\ntime :%s \n", item->valuestring);112item = cJSON_GetObjectItem(object, "AP");113rt_kprintf("\nap :%s ", item->valuestring);114rt_sprintf(temp,"气压: %s",item->valuestring);115lcd_disp_str_en_ch(0,60,temp,BLACK,WHITE);116item = cJSON_GetObjectItem(object, "WSE");117rt_kprintf("\nwse :%s ", item->valuestring);118rt_sprintf(temp,"风力: %s",item->valuestring);119lcd_disp_str_en_ch(0,80,temp,BLACK,WHITE);120if (root != RT_NULL)121cJSON_Delete(root);122}123/* 疫情数据解析 */124void fy2020_data_parse(rt_uint8_t *data) {125uint8_t temp[100];126cJSON *root = RT_NULL, *object = RT_NULL, *item = RT_NULL;127root = cJSON_Parse((const char *)data);128if (!root)129{130rt_kprintf("No memory for cJSON root!\n");131return; }1324. 音乐播放模块133该部分的功能主要通过使用 player 软件包的接口函数实现,包括音乐播134放,停止,歌曲切换以及音量控制等功能。该部分的部分代码如下图所示:135cJSON *dataArray = cJSON_GetObjectItem(root,"data"); //取数组136int arraySize = cJSON_GetArraySize(dataArray); //取数组大小137cJSON *dataList = dataArray->child;138while(dataList != RT_NULL)139{140rt_kprintf("\ndiagnosed :%d \n", cJSON_GetObjectItem(dataList,"diagn141osed")->valueint);142rt_kprintf("\ndeath :%d \n", cJSON_GetObjectItem(dataList,"death")->143valueint);144rt_kprintf("\ncured :%d \n", cJSON_GetObjectItem(dataList,"cured")->145valueint);146rt_kprintf("\ndate :%s \n", cJSON_GetObjectItem(dataList,"date")->va147luestring);148//LCD 屏打印信息149rt_sprintf(temp,"累计确150诊: %d",cJSON_GetObjectItem(dataList,"diagnosed")->valueint);151lcd_disp_str_en_ch(0,120,temp,BLACK,WHITE);152rt_sprintf(temp,"累计死153亡: %d",cJSON_GetObjectItem(dataList,"death")->valueint);154lcd_disp_str_en_ch(0,140,temp,BLACK,WHITE);155rt_sprintf(temp,"累计治156愈: %d",cJSON_GetObjectItem(dataList,"cured")->valueint);157lcd_disp_str_en_ch(0,160,temp,BLACK,WHITE);158rt_sprintf(temp,"更新时159间: %s",cJSON_GetObjectItem(dataList,"date")->valuestring);160lcd_disp_str_en_ch(0,180,temp,BLACK,WHITE);161dataList = dataList->next; 162}

4. 音乐播放模块

该部分的功能主要通过使用 player 软件包的接口函数实现,包括音乐播放,停止,歌曲切换以及音量控制等功能。该部分的部分代码如下图所示:

 1LCDShowMusic(); 2rt_sprintf(music,"/sd/music/%d.mp3",count); 3rt_kprintf(" key left is press ...\r\n"); 4rt_kprintf("//////////////////////////// player_play \n"); 5player_stop(); 6player_set_uri(music); 7player_play(); 8player_status = 1; 9count++;10if(count >= 8)11count = 0;12rt_kprintf("//////////////////////////// player_play end \n");

5. 相机功能实现

该部分主要通过 IPC 事件获取相机采集图像数据,并将采集到的图像数据经过 TJpgDec 软件包进行解码后在 LCD 进行显示,同时会将相机数据通过写文件的形式存储到 SD 卡,通过 HTTP 协议推送到 OneNet 云平台。

 1void take_photo(void) //手动拍照 2{ 3//创建摄像头接收一帧图片的事件 4session.event = rt_event_create("vt_event", RT_IPC_FLAG_FIFO); 5camera_start(); //开启摄像头传输照片 6tvideo_capture(1); 7rt_event_recv(session.event, SEND_FRAME_EVENT, RT_EVENT_FLAG_OR | RT_EVE 8NT_FLAG_CLEAR, RT_WAITING_FOREVER, RT_NULL); 9int fd, res;10time_t cur_time;11struct tm *cur_tm;12char time_now[50]; //保存文件到 sd 卡指定路径13/* output current time */14cur_time = time(RT_NULL);15cur_tm = localtime(&cur_time);16rt_sprintf(time_now, "%04d-%02d-%02d-%02d-%02d-%02d",cur_tm->tm_year + 191700, cur_tm->tm_mon + 1, cur_tm->tm_mday, cur_tm->tm_hour, cur_tm->tm_min, cur18_tm->tm_sec);19rt_sprintf(file_name, "/sd/images/%s.jpg", time_now);20rt_kprintf("name = %s \n", file_name);21fd = open(file_name, O_WRONLY | O_CREAT);22if (fd >= 0) {23write(fd, session.buf, session.total_len);24close(fd);25rt_kprintf("save %s ok!!!\n", file_name);26res = Decode_Jpg(file_name);27rt_kprintf("res = %d\n", res);28}29else30{31rt_kprintf("save pic failed!!!\n");32}33//拍照数据上传34webclient_post_pic(session.buf, session.total_len);35tvideo_capture(0);36}37MSH_CMD_EXPORT(take_photo,take_photo);

六、演示效果

image.png

七、代码地址

?链接:https://gitee.com/hb\_zhaojuntao/DigitalElves.git

**麻雀一号购买链接:https://m.tb.cn/h.4ccA1wD?sm=37cdbc

原文链接:https://mp.weixin.qq.com/s/EX3A45XXCGIfGLpSJ4mnfg
转载已获授权,禁止二转
推荐阅读
关注数
8072
内容数
181
小而美的物联网操作系统,经过14年的累积发展,RT-Thread 已经拥有一个国内最大的嵌入式开源社区,同时被广泛应用于能源、车载、医疗、消费电子等多个行业,累积装机量超过4亿台,成为国人自主开发、国内最成熟稳定和装机量最大的开源 RTOS。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息