搜索

[uCOS/RTOS] 【RTOS】基于RT-Thread的“数码小精灵“设计

[复制链接]
867|0
 楼主 | 2020-6-29 16:57 | 显示全部楼层 |阅读模式
本帖最后由 hbzjt2011 于 2020-6-29 17:16 编辑

【RT-Thread作品秀】基于RT-Thread的“数码小精灵“设计

作者:hbzjt2011
一、概述
随着近年来智能化设备的不断增长,平板电脑、智能数字音视频播放器、移动数码相机等各类数码产品,正越来越受到城乡居民欢迎。特别是“云生活”让人们对数码产品有了更多需求,加上互联网技术、5G技术、智能化新产品、新型分期消费模式等的出现,让数码产品消费热潮涌动。
本设计为基于RT-Thread的“数码小精灵”设计,硬件采用了以BK7252为主控芯片的麻雀一号开发板开发。BK7252 , 是一款高性能 WiFi 模块,采用高集成的无线射频芯片,内部集成 2.4GHz Wi-Fi 1T1R先进技术,支持摄像头图像输出,拥有最佳的功耗性能、射频性能、稳定性、通用性和可靠性,适用于各种应用和不同产品需求。模块内部拥有 512KB 内嵌 RAM 和4Mbyte Flash 空间,CPU 主频高达 180Mhz。

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



作品整体图
二、RT-Thread使用情况概述
本设计基于麻雀一号开发板SDK进行开发,其RT-Thread为3.1.0版本。
352545ef9aa6b6e596.png
图1RT-Thread版本
在RT-Thread系统上的设备注册列表,其中主要使用了rtc,sd0,w0,sound,uart1等设备。使用到的RT-Thread组件包括了FinSH控制台,虚拟文件系统,POSIX接口。在软件包上面涉及到网络工具及NetUtils,WebClient,cJSON,EasyFlash,rt_ota,TJpgDec,Player等。
388785ef9aa7485c11.png
图2设备注册列表
三、硬件框架
458835ef9aa7ec3285.png
系统硬件框图
麻雀一号开发板外设资源丰富,但资源相当丰富,集成 WiFi、BLE、摄像头、音频扬声器、MIC 录音、TF 卡座、五向按键、还有一个 1.44 寸的 LCD 屏,使用常见的 TypeC 接口作为供电和调试串口,预留支持锂电池供电接口。
本设计的人机交互部分主要利用了开发板的五向按键和LCD显示屏,其中按键用于功能选择,数据刷新以及音乐播放控制功能。
普通模式下:
←:音乐播放
⭕:天气和疫情数据刷新
→:相机拍照
音乐播放模式下:
↑:音量+
↓:音量-
←:下一曲
⭕:停止播放
设备联网采用WIFI连接,上电自动连接网络。数据显示LCD进行显示,同时设备接有SD进行相机拍照的存储。此外照片同时可以通过WIFI上传至OneNet云平台,进行远端网页或者手机等智能终端进行查看。
四、软件框架说明
系统软件流程框图如下图所示,设备上电后启动RT-Thread操作系统,同时进行相关硬件设备的初始化操作,如LCD显示屏,音频扬声器,以及挂在SD卡到文件系统等操作,之后进行设备的网络连接。网络连接后输入应用程序启动命令进行程序启动,LCD显示欢迎界面,接下来用户可以通过五向按键进行功能选择,主要是天气疫情数据更新显示、MP3播放器功能以及数码相机的功能。相机拍照后会进行本地SD卡数据存储以及OneNet云端存储。
648055ef9aaadb32bb.png
软件整体流程框图
五、软件模块说明
1.      设备联网
设备联网主要使用到了RT-Thread组件中的wlan驱动程序实现,上电初始化完成后在主程序中查询wlan无线设备,并根据用户配置的SSID和PASSWORD进行WIFI网络的连接。该部分的具体代码实现如下所示:
  1. static int iot_station_connect(char *ssid, char *passwd)
  2. {
  3.     rt_err_t result = RT_EOK;
  4.     struct rt_wlan_info info;
  5.     struct rt_wlan_device *wlan;
  6.     rt_tick_t tick = 0;

  7.     wlan = (struct rt_wlan_device *)rt_device_find(WIFI_DEVICE_STA_NAME);
  8.     if (!wlan)
  9.     {
  10.         rt_kprintf("no wlan:%s device\n", WIFI_DEVICE_STA_NAME);
  11.         return -1;
  12.     }

  13.     result = rt_wlan_init(wlan, WIFI_STATION);
  14.     rt_wlan_register_event_handler(wlan, WIFI_EVT_STA_CONNECTED, iot_wlan_sta_connected_event);
  15.     rt_wlan_register_event_handler(wlan, WIFI_EVT_STA_DISCONNECTED, iot_wlan_sta_disconnected_event);

  16.     rt_wlan_info_init(&info, WIFI_STATION, SECURITY_WPA2_AES_PSK, ssid);
  17.     result = rt_wlan_connect(wlan, &info, passwd);
  18.     rt_wlan_info_deinit(&info);

  19.     return result;
  20. }
复制代码

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

  1. #define THREAD_PRIORITY         25
  2. #define THREAD_STACK_SIZE       2048
  3. #define THREAD_TIMESLICE        5

  4. /* 指向信号量的指针 */
  5. static rt_thread_t tid1 = RT_NULL;
  6. /* 线程 1 的入口函数 */
  7. static void ntcthread1_entry(void * parameter)
  8. {
  9.   while ((1))
  10.   {
  11.     time_t cur_time = ntp_sync_to_rtc();

  12.     if (cur_time)
  13.     {
  14.       rt_kprintf("Get local time from NTP server: %s", ctime((const time_t *) &cur_time));
  15.       rt_kprintf("The system time is updated. Timezone is %d.\n", NTP_TIMEZONE);
  16.       break; //删除线程
  17.     }
  18.     else
  19.     {
  20.       rt_thread_mdelay(1000);
  21.     }
  22.   }
  23. }

  24. void NTCThreadInit(void)
  25. {
  26.   if (tid1 != RT_NULL)
  27.   {
  28.     rt_kprintf("ntc thread still run\n");
  29.     return;
  30.   }
  31.   rt_kprintf("NTC thread init\n");

  32.   /* 创建线程 1,名称是 thread1,入口是 thread1_entry*/
  33.   tid1 = rt_thread_create("NTC",
  34.                           ntcthread1_entry, RT_NULL,
  35.                           THREAD_STACK_SIZE,
  36.                           THREAD_PRIORITY, THREAD_TIMESLICE);

  37.   /* 如果获得线程控制块,启动这个线程 */
  38.   if (tid1 != RT_NULL)
  39.     rt_thread_startup(tid1);
  40. }
复制代码

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

  1. #define GET_HEADER_BUFSZ        1024        //头部大小
  2. #define GET_RESP_BUFSZ          1024        //响应缓冲区大小
  3. #define GET_URL_LEN_MAX         256         //网址最大长度
  4. #define GET_URI                 "http://www.weather.com.cn/data/sk/%s.html" //获取天气的 API
  5. #define AREA_ID                 "101090701" //河北沧州地区 ID

  6. /* 天气数据解析 */
  7. void weather_data_parse(rt_uint8_t *data)
  8. {
  9.     uint8_t temp[100];
  10.     cJSON *root = RT_NULL, *object = RT_NULL, *item = RT_NULL;

  11.     root = cJSON_Parse((const char *)data);
  12.     if (!root)
  13.     {
  14.         rt_kprintf("No memory for cJSON root!\n");
  15.         return;
  16.     }
  17.     object = cJSON_GetObjectItem(root, "weatherinfo");

  18.     item = cJSON_GetObjectItem(object, "city");
  19.     rt_kprintf("\ncity    :%s ", item->valuestring);
  20.     lcd_clear(BLACK);
  21.     // rt_sprintf(temp,"城市:  %s",item->valuestring);
  22.     // lcd_disp_str_en_ch(0,0,temp,BLACK,WHITE);

  23.     item = cJSON_GetObjectItem(object, "temp");
  24.     rt_kprintf("\ntemp    :%s ", item->valuestring);
  25.     rt_sprintf(temp,"温度:  %s",item->valuestring);
  26.     lcd_disp_str_en_ch(0,20,temp,BLACK,WHITE);

  27.     item = cJSON_GetObjectItem(object, "WD");
  28.     rt_kprintf("\nwd      :%s ", item->valuestring);

  29.     item = cJSON_GetObjectItem(object, "WS");
  30.     rt_kprintf("\nws      :%s ", item->valuestring);
  31.     // rt_sprintf(temp,"风力:  %s",item->valuestring);
  32.     // lcd_disp_str_en_ch(0,40,temp,BLACK,WHITE);

  33.     item = cJSON_GetObjectItem(object, "SD");
  34.     rt_kprintf("\nsd      :%s ", item->valuestring);
  35.     rt_sprintf(temp,"湿度:  %s",item->valuestring);
  36.     lcd_disp_str_en_ch(0,40,temp,BLACK,WHITE);

  37.     item = cJSON_GetObjectItem(object, "time");
  38.     rt_kprintf("\ntime    :%s \n", item->valuestring);

  39.     item = cJSON_GetObjectItem(object, "AP");
  40.     rt_kprintf("\nap      :%s ", item->valuestring);
  41.     rt_sprintf(temp,"气压:  %s",item->valuestring);
  42.     lcd_disp_str_en_ch(0,60,temp,BLACK,WHITE);

  43.     item = cJSON_GetObjectItem(object, "WSE");
  44.     rt_kprintf("\nwse      :%s ", item->valuestring);
  45.     rt_sprintf(temp,"风力:  %s",item->valuestring);
  46.     lcd_disp_str_en_ch(0,80,temp,BLACK,WHITE);

  47.     if (root != RT_NULL)
  48.         cJSON_Delete(root);
  49. }
  50. void get_weather(int argc, char **argv)
  51. {
  52.     rt_uint8_t *buffer = RT_NULL;
  53.     int resp_status;
  54.     struct webclient_session *session = RT_NULL;
  55.     char *weather_url = RT_NULL;
  56.     int content_length = -1, bytes_read = 0;
  57.     int content_pos = 0;
  58.     char *city_name = rt_calloc(1,255);

  59.     /* 为 weather_url 分配空间 */
  60.     weather_url = rt_calloc(1, GET_URL_LEN_MAX);
  61.     if (weather_url == RT_NULL)
  62.     {
  63.         rt_kprintf("No memory for weather_url!\n");
  64.         goto __exit;
  65.     }

  66.     if(argc == 1)
  67.     {
  68.         strcpy(city_name, AREA_ID);
  69.     }
  70.     else if (argc == 2)
  71.     {
  72.         strcpy(city_name, argv[1]);
  73.     }

  74.     /* 拼接 GET 网址 */
  75.     rt_snprintf(weather_url, GET_URL_LEN_MAX, GET_URI, city_name);

  76.     /* 创建会话并且设置响应的大小 */
  77.     session = webclient_session_create(GET_HEADER_BUFSZ);
  78.     if (session == RT_NULL)
  79.     {
  80.         rt_kprintf("No memory for get header!\n");
  81.         goto __exit;
  82.     }

  83.     /* 发送 GET 请求使用默认的头部 */
  84.     if ((resp_status = webclient_get(session, weather_url)) != 200)
  85.     {
  86.         rt_kprintf("webclient GET request failed, response(%d) error.\n", resp_status);
  87.         goto __exit;
  88.     }

  89.     /* 分配用于存放接收数据的缓冲 */
  90.     buffer = rt_calloc(1, GET_RESP_BUFSZ);
  91.     if (buffer == RT_NULL)
  92.     {
  93.         rt_kprintf("No memory for data receive buffer!\n");
  94.         goto __exit;
  95.     }

  96.     content_length = webclient_content_length_get(session);
  97.     if (content_length < 0)
  98.     {
  99.         /* 返回的数据是分块传输的. */
  100.         do
  101.         {
  102.             bytes_read = webclient_read(session, buffer, GET_RESP_BUFSZ);
  103.             if (bytes_read <= 0)
  104.             {
  105.                 break;
  106.             }
  107.         }while (1);
  108.     }
  109.     else
  110.     {
  111.         do
  112.         {
  113.             bytes_read = webclient_read(session, buffer,
  114.                                         content_length - content_pos > GET_RESP_BUFSZ ?
  115.                                         GET_RESP_BUFSZ : content_length - content_pos);
  116.             if (bytes_read <= 0)
  117.             {
  118.                 break;
  119.             }
  120.             content_pos += bytes_read;
  121.         }while (content_pos < content_length);
  122.     }

  123.     /* 天气数据解析 */
  124.     weather_data_parse(buffer);

  125. __exit:
  126.     /* 释放网址空间 */
  127.     if (weather_url != RT_NULL)
  128.         rt_free(weather_url);
  129.     /* 关闭会话 */
  130.     if (session != RT_NULL)
  131.         webclient_close(session);
  132.     /* 释放缓冲区空间 */
  133.     if (buffer != RT_NULL)
  134.         rt_free(buffer);
  135.     if(city_name != RT_NULL)
  136.         rt_free(city_name);
  137. }

  138. #ifdef FINSH_USING_MSH
  139. #include <finsh.h>
  140. MSH_CMD_EXPORT_ALIAS(get_weather, wt, wt [CityName]  webclient GET request test);
  141. #endif /* FINSH_USING_MSH */


  142. #define GET_FY2020
  143. #ifdef GET_FY2020
  144. #define GET_FY2020_HEADER_BUFSZ        (1024)        //头部大小
  145. #define GET_FY2020_RESP_BUFSZ          (1024)       //响应缓冲区大小
  146. #define GET_FY2020_URI         "http://www.dzyong.top:3005/yiqing/total" //疫情数据 API



  147. /* 疫情数据解析 */
  148. void fy2020_data_parse(rt_uint8_t *data)
  149. {
  150.     uint8_t temp[100];
  151.     cJSON *root = RT_NULL, *object = RT_NULL, *item = RT_NULL;

  152.     root = cJSON_Parse((const char *)data);
  153.     if (!root)
  154.     {
  155.         rt_kprintf("No memory for cJSON root!\n");
  156.         return;
  157.     }

  158.     cJSON *dataArray = cJSON_GetObjectItem(root,"data");  //取数组
  159.     int arraySize = cJSON_GetArraySize(dataArray);        //取数组大小
  160.     cJSON *dataList = dataArray->child;
  161.     while(dataList != RT_NULL)
  162.     {
  163.         rt_kprintf("\ndiagnosed    :%d \n", cJSON_GetObjectItem(dataList,"diagnosed")->valueint);
  164.         rt_kprintf("\ndeath    :%d \n", cJSON_GetObjectItem(dataList,"death")->valueint);
  165.         rt_kprintf("\ncured    :%d \n", cJSON_GetObjectItem(dataList,"cured")->valueint);
  166.         rt_kprintf("\ndate    :%s \n", cJSON_GetObjectItem(dataList,"date")->valuestring);
  167.         //LCD屏打印信息
  168.         rt_sprintf(temp,"累计确诊:  %d",cJSON_GetObjectItem(dataList,"diagnosed")->valueint);
  169.         lcd_disp_str_en_ch(0,120,temp,BLACK,WHITE);
  170.         rt_sprintf(temp,"累计死亡:  %d",cJSON_GetObjectItem(dataList,"death")->valueint);
  171.         lcd_disp_str_en_ch(0,140,temp,BLACK,WHITE);
  172.         rt_sprintf(temp,"累计治愈:  %d",cJSON_GetObjectItem(dataList,"cured")->valueint);
  173.         lcd_disp_str_en_ch(0,160,temp,BLACK,WHITE);
  174.         rt_sprintf(temp,"更新时间:  %s",cJSON_GetObjectItem(dataList,"date")->valuestring);
  175.         lcd_disp_str_en_ch(0,180,temp,BLACK,WHITE);
  176.         dataList = dataList->next;
  177.     }

  178. }
  179. void get_fy2020_data(void)
  180. {
  181.     rt_uint8_t *buffer = RT_NULL;
  182.     int resp_status;
  183.     struct webclient_session *session = RT_NULL;
  184.     int content_length = -1, bytes_read = 0;
  185.     int content_pos = 0;
  186.     int index;

  187.     /* 创建会话并且设置响应的大小 */
  188.     session = webclient_session_create(GET_FY2020_HEADER_BUFSZ);
  189.     if (session == RT_NULL)
  190.     {
  191.         rt_kprintf("No memory for get header!\n");
  192.         goto __exit;
  193.     }

  194.     /* 发送 GET 请求使用默认的头部 */
  195.     if ((resp_status = webclient_get(session, GET_FY2020_URI)) != 200)
  196.     {
  197.         rt_kprintf("webclient GET request failed, response(%d) error.\n", resp_status);
  198.         goto __exit;
  199.     }

  200.     /* 分配用于存放接收数据的缓冲 */
  201.     buffer = rt_calloc(1, GET_FY2020_RESP_BUFSZ);
  202.     if (buffer == RT_NULL)
  203.     {
  204.         rt_kprintf("No memory for data receive buffer!\n");
  205.         goto __exit;
  206.     }

  207.     content_length = webclient_content_length_get(session);
  208.     if (content_length < 0)
  209.     {
  210.          rt_kprintf("webclient GET request type is chunked.\n");
  211.         /* 返回的数据是分块传输的. */
  212.         do
  213.         {
  214.             bytes_read = webclient_read(session, buffer, GET_FY2020_RESP_BUFSZ);
  215.             if (bytes_read <= 0)
  216.             {
  217.                 break;
  218.             }
  219.             for (index = 0; index < bytes_read; index++)
  220.             {
  221.                 rt_kprintf("%c", buffer[index]);
  222.             }
  223.         }while (1);
  224.     }
  225.     else
  226.     {
  227.         do
  228.         {
  229.             bytes_read = webclient_read(session, buffer,
  230.                                         content_length - content_pos > GET_FY2020_RESP_BUFSZ ?
  231.                                         GET_FY2020_RESP_BUFSZ : content_length - content_pos);
  232.             if (bytes_read <= 0)
  233.             {
  234.                 break;
  235.             }
  236.             content_pos += bytes_read;
  237.         }while (content_pos < content_length);
  238.     }

  239.     for (index = 0; index < bytes_read; index++)
  240.     {
  241.         rt_kprintf("%c", buffer[index]);
  242.     }
  243.     /* 肺炎数据解析 */
  244.     fy2020_data_parse(buffer);

  245. __exit:
  246.     /* 关闭会话 */
  247.     if (session != RT_NULL)
  248.         webclient_close(session);
  249.     /* 释放缓冲区空间 */
  250.     if (buffer != RT_NULL)
  251.         rt_free(buffer);
  252. }

  253. #ifdef FINSH_USING_MSH
  254. #include <finsh.h>
  255. MSH_CMD_EXPORT(get_fy2020_data,get_fy2020_data);
  256. #endif /* FINSH_USING_MSH */
复制代码



4.     音乐播放模块
该部分的功能主要通过使用player软件包的接口函数实现,包括音乐播放,停止,歌曲切换以及音量控制等功能。该部分的部分代码如下图所示:
  1.   LCDShowMusic();
  2.                 rt_sprintf(music,"/sd/music/%d.mp3",count);
  3.                 rt_kprintf(" key left is press ...\r\n");
  4.                 rt_kprintf("//////////////////////////// player_play \n");
  5.                 player_stop();
  6.                 player_set_uri(music);
  7.                 player_play();
  8.                 player_status = 1;
  9.                 count++;
  10.                 if(count >= 8)
  11.                     count = 0;
  12.                 rt_kprintf("//////////////////////////// player_play end \n");
复制代码


5.     相机功能实现
该部分主要通过IPC事件获取相机采集图像数据,并将采集到的图像数据经过TJpgDec软件包进行解码后在LCD进行显示,同时会将相机数据通过写文件的形式存储到SD卡,通过HTTP协议推送到OneNet云平台。
  1. //手动拍照
  2. void take_photo(void)
  3. {
  4.     //创建摄像头接收一帧图片的事件
  5.     session.event = rt_event_create("vt_event", RT_IPC_FLAG_FIFO);

  6.    
  7.     camera_start(); //开启摄像头传输照片


  8.     tvideo_capture(1);
  9.     rt_event_recv(session.event, SEND_FRAME_EVENT, RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, RT_NULL);

  10.     //实时显示图片到 LCD 屏上
  11.     //添加保存照片到本地文件的功能
  12.     //保存文件到 sd 卡指定路径
  13.     int fd, res;
  14.     time_t cur_time;
  15.     struct tm *cur_tm;
  16.     char time_now[50];

  17.     /* output current time */
  18.     cur_time = time(RT_NULL);

  19.     cur_tm = localtime(&cur_time);
  20.     //rt_memset(time_now, 0, sizeof(time_now));
  21.     rt_sprintf(time_now, "%04d-%02d-%02d-%02d-%02d-%02d",cur_tm->tm_year + 1900, cur_tm->tm_mon + 1, cur_tm->tm_mday, cur_tm->tm_hour, cur_tm->tm_min, cur_tm->tm_sec);

  22.     rt_sprintf(file_name, "/sd/images/%s.jpg", time_now);
  23.     //rt_sprintf(file_name, "/sd/images/%s.jpg", time_now);
  24.     //保存文件到 sd 卡指定路径
  25.     rt_kprintf("name = %s \n", file_name);
  26.     fd = open(file_name, O_WRONLY | O_CREAT);
  27.     if (fd >= 0)
  28.     {
  29.         write(fd, session.buf, session.total_len);
  30.         close(fd);
  31.         rt_kprintf("save %s ok!!!\n", file_name);
  32.         res = Decode_Jpg(file_name);
  33.         rt_kprintf("res = %d\n", res);
  34.     }
  35.     else
  36.     {
  37.         rt_kprintf("save pic failed!!!\n");
  38.     }

  39.     //拍照数据上传
  40.     webclient_post_pic(session.buf, session.total_len);  
  41.     tvideo_capture(0);
  42.    

  43. }
  44. MSH_CMD_EXPORT(take_photo,take_photo);
复制代码



六、演示效果
1.     设备上电等待设备初始化完毕,完成网络连接;
144415ef9ac227edff.png

2.     运行应用程序,LCD显示欢迎界面:
339785ef9ac2be04d5.png

3.     此时按中键进行天气和疫情数据更新显示:
91575ef9ac3ea1e6c.png

4.     按左键进行音乐播放,同时可以进行歌曲切换和音量调整等操作:
221695ef9ac5bc792e.png


5.     相机拍照功能,按右键进行相机拍照,拍照会进行本低存储和云端存储;

509635ef9ac6730536.png
133205ef9ac730c94f.png
LCD和云端显示数据
582185ef9ac88513f4.png
943005ef9ac90c3531.png
665395ef9ac97ed091.png
820595ef9ac9df2d4d.png
历史照片数据显示
七、代码地址
基于RT-Thread的“数码小精灵“设计.pdf (1.63 MB, 下载次数: 13)

使用特权

评论回复
扫描二维码,随时随地手机跟帖
您需要登录后才可以回帖 登录 | 注册

本版积分规则

我要发帖 我要提问 投诉建议 申请版主

快速回复

您需要登录后才可以回帖
登录 | 注册
高级模式

论坛热帖

关闭

热门推荐上一条 /5 下一条

在线客服 快速回复 返回顶部 返回列表