DDZZ669 发表于 2022-5-8 22:52

【平头哥RVB2601创意应用开发】实践7-U8g2库显示网络天气和时间

<p cid="n2" mdtype="paragraph">本篇介绍了RVB2601连网获取天气以及RTC功能,将天气和时间显示到OLED屏幕中。</p>

<p cid="n2" mdtype="paragraph"></p>

<h1 cid="n3" mdtype="heading">1 RTC时钟功能</h1>

<p cid="n4" mdtype="paragraph">参考YOC文档:<a href="https://yoc.docs.t-head.cn/yocbook/Chapter3-AliOS/CSI%E8%AE%BE%E5%A4%87%E9%A9%B1%E5%8A%A8%E6%8E%A5%E5%8F%A3/CSI1/RTC.html">https://yoc.docs.t-head.cn/yocbook/Chapter3-AliOS/CSI%E8%AE%BE%E5%A4%87%E9%A9%B1%E5%8A%A8%E6%8E%A5%E5%8F%A3/CSI1/RTC.html</a></p>

<p cid="n5" mdtype="paragraph">RTC(Real_Time Clock)实时时钟为系统提供可靠的时间基准, 一般有独立的晶振和电源,保证主电源掉电时还可以工作。 RTC 一般可以提供日历格式的时间。</p>

<p cid="n6" mdtype="paragraph">虽然RVB2601的RTC没有配置电池,掉电后时间就没了,但RVB2601的连网功能,可以在每次开机时,通过获取网络时间,来为RTC重新校准时间。</p>

<h2 cid="n7" mdtype="heading">1.1 RTC时间结构体</h2>

<h3 cid="n8" mdtype="heading">csi_rtc_time_t</h3>

<figure cid="n9" mdtype="table">
<table>
        <thead>
                <tr cid="n10" mdtype="table_row">
                        <th>成员</th>
                        <th>类型</th>
                        <th>说明</th>
                </tr>
        </thead>
        <tbody>
                <tr cid="n14" mdtype="table_row">
                        <td>tm_sec</td>
                        <td>int</td>
                        <td>秒 取值范围</td>
                </tr>
                <tr cid="n18" mdtype="table_row">
                        <td>tm_min</td>
                        <td>int</td>
                        <td>分 取值范围</td>
                </tr>
                <tr cid="n22" mdtype="table_row">
                        <td>tm_hour</td>
                        <td>int</td>
                        <td>小时 取值范围</td>
                </tr>
                <tr cid="n26" mdtype="table_row">
                        <td>tm_mday</td>
                        <td>int</td>
                        <td>天 取值范围</td>
                </tr>
                <tr cid="n30" mdtype="table_row">
                        <td>tm_mon</td>
                        <td>int</td>
                        <td>月 取值范围</td>
                </tr>
                <tr cid="n34" mdtype="table_row">
                        <td>tm_year</td>
                        <td>int</td>
                        <td>年 取值范围</td>
                </tr>
                <tr cid="n38" mdtype="table_row">
                        <td>tm_wday</td>
                        <td>int</td>
                        <td>周 取值范围</td>
                </tr>
                <tr cid="n42" mdtype="table_row">
                        <td>tm_yday</td>
                        <td>int</td>
                        <td>一年中的第几天 取值范围</td>
                </tr>
        </tbody>
</table>
</figure>

<p cid="n46" mdtype="paragraph">例:设置2020年1月13日23点59分59秒则如下配置:</p>

<pre>
<code class="language-cpp">rtctime.tm_year = 120; //2020-1900 = 120
rtctime.tm_mon = 0;    //0-11
rtctime.tm_mday = 13;  //1-31
rtctime.tm_hour = 23;  //0-23
rtctime.tm_min = 59;   //0-59
rtctime.tm_sec = 59;   //0-59</code></pre>

<h2 cid="n48" mdtype="heading">1.2 API函数</h2>

<p cid="n49" mdtype="paragraph">RTC的CSI接口在用户对接时是否必须适配的说明如下所示:</p>

<figure cid="n50" mdtype="table">
<table>
        <thead>
                <tr cid="n51" mdtype="table_row">
                        <th>函数</th>
                        <th>功能</th>
                        <th>是否必须适配</th>
                </tr>
        </thead>
        <tbody>
                <tr cid="n55" mdtype="table_row">
                        <td><strong>csi_rtc_init</strong></td>
                        <td>初始化</td>
                        <td><strong>必须</strong></td>
                </tr>
                <tr cid="n59" mdtype="table_row">
                        <td><strong>csi_rtc_uninit</strong></td>
                        <td>反初始化</td>
                        <td><strong>必须</strong></td>
                </tr>
                <tr cid="n63" mdtype="table_row">
                        <td><strong>csi_rtc_set_time</strong></td>
                        <td>设置时间(需要同步时间)</td>
                        <td><strong>必须</strong></td>
                </tr>
                <tr cid="n67" mdtype="table_row">
                        <td>csi_rtc_set_time_no_wait</td>
                        <td>设置时间(不需要同步时间)</td>
                        <td>非必须</td>
                </tr>
                <tr cid="n71" mdtype="table_row">
                        <td><strong>csi_rtc_get_time</strong></td>
                        <td>获取当前时间</td>
                        <td><strong>必须</strong></td>
                </tr>
                <tr cid="n75" mdtype="table_row">
                        <td>csi_rtc_get_alarm_remaining_time</td>
                        <td>获取距离闹钟剩余时间</td>
                        <td>非必须</td>
                </tr>
                <tr cid="n79" mdtype="table_row">
                        <td>csi_rtc_set_alarm</td>
                        <td>设置闹钟</td>
                        <td>非必须</td>
                </tr>
                <tr cid="n83" mdtype="table_row">
                        <td>csi_rtc_cancel_alarm</td>
                        <td>取消闹钟</td>
                        <td>非必须</td>
                </tr>
                <tr cid="n87" mdtype="table_row">
                        <td>csi_rtc_is_running</td>
                        <td>获取工作状态</td>
                        <td>非必须</td>
                </tr>
        </tbody>
</table>
</figure>

<h2 cid="n91" mdtype="heading">主要函数</h2>

<p cid="n92" mdtype="paragraph">参考官方文档,编写RTC时间获取任务。</p>

<pre>
<code class="language-cpp">static void rtc_clock_task(void * arg)
{
    csi_rtc_time_t now_time;
    int last_sec = -1;
   
    rtc_clock_init();
   
    if(rtc_clock_queue_init() != 0)
 {
       LOGD(TAG, "creat queue:rtc_clock_queue failed\r\n ");
 }
   
    LOGD(TAG, "GET weather_update_done_sem \r\n ");
    aos_sem_wait(&amp;weather_update_done_sem, AOS_WAIT_FOREVER);

    while(1)
    {   
      if(CSI_OK != csi_rtc_get_time(&amp;g_rtc, &amp;now_time))
      {
            LOGD(TAG, "===%s, %d", __FUNCTION__, __LINE__);
            return ;      
      }
      else
      {
            if (now_time.tm_sec != last_sec)
            {
                now_time.tm_year += 1900;
                now_time.tm_mon += 1;
                LOGD(TAG, "Time: %d-%d-%d%d:%d:%d\n", now_time.tm_year, now_time.tm_mon,
                        now_time.tm_mday, now_time.tm_hour, now_time.tm_min, now_time.tm_sec);
               
                aos_queue_send(&amp;rtc_clock_queue, &amp;now_time, sizeof(csi_rtc_time_t));
               
                last_sec = now_time.tm_sec;
            }
      }
      aos_msleep(300);   
    }
}</code></pre>

<p cid="n94" mdtype="paragraph">注意这里我没300ms获取一次RTC时间,当发现&ldquo;秒&rdquo;数据变化时,通过消息队列发送给OLED模块使其更新时间,这样,OLED只需1s更新一次即可,避免不必要的循环刷新OLED显示。</p>

<h1 cid="n95" mdtype="heading">2 NTP网络授时</h1>

<p cid="n96" mdtype="paragraph">参考YOC文档:<a href="https://yoc.docs.t-head.cn/yocbook/Chapter5-%E7%BB%84%E4%BB%B6/%E7%BD%91%E7%BB%9C%E7%BB%84%E4%BB%B6/NTP.html">https://yoc.docs.t-head.cn/yocbook/Chapter5-%E7%BB%84%E4%BB%B6/%E7%BD%91%E7%BB%9C%E7%BB%84%E4%BB%B6/NTP.html</a></p>

<p cid="n97" mdtype="paragraph">文档里关于此部分的介绍较少,并且已有网友分享使用NTP时有许多坑,本篇暂不使用NTP功能。</p>

<p cid="n98" mdtype="paragraph">那想要获取实际的时间,怎么办呢?在使用下面的连网获取天气的过程中,发现在获取天气时,同时会获取到一个天气更新的时间(实测发现,网上的天气信息差不多是每隔十多分钟更新一次),所以,可以使用这个时间作为RTC的校准时间(虽然会有几分钟的偏差,但作为测试使用还是可以的)。</p>

<h1 cid="n99" mdtype="heading">3 连网获取天气</h1>

<h2 cid="n100" mdtype="heading">3.1 获取天气信息</h2>

<p cid="n101" mdtype="paragraph">获取网络天气,已经有许多网友尝试过了,我参考这篇:<a href="https://bbs.eeworld.com.cn/thread-1201500-1-1.html" spellcheck="false">【平头哥RVB2601创意应用开发】RVB2601之获取天气小工具</a></p>

<p cid="n102" mdtype="paragraph">需要在高德开发平台上注册开发者账号,通过自己Key,以及通过http协议获取网络天气,并通过json数据解析获取具体的天气和温度以及更新时间等信息。</p>

<p cid="n103" mdtype="paragraph">按照网友的做法,通过连上网时获取一起天气,以及后续的按键发送信号量,触发一次网络天气获取,然后对json格式的天气数据进行解析。</p>

<pre>
<code class="language-cpp">static void parse_http_data(char *http_data)
{
   app_weather_t app_weather_data;

   cJSON *root;
   cJSON *status;
   cJSON *lives;
   cJSON *tmp_obj;
   cJSON *live_obj;
   char *str_tmp;
   int size;

   root = cJSON_Parse(http_data);
   if(root)
 {
       LOGD(TAG, "========&gt;JSON_data:\n%s\n", cJSON_Print(root));
       status = cJSON_GetObjectItem(root, STATUS);
       if(status)
     {
           LOGD(TAG, "status:%d,%s\r\n",atoi(status-&gt;valuestring), status-&gt;valuestring);
           lives = cJSON_GetObjectItem(root, LIVES);
           if(lives)
         {
               size = cJSON_GetArraySize(lives);
               LOGD(TAG, "!!size:%d\r\n",size);
               for(int i=0;i&lt;size;i++)
             {
                   live_obj = cJSON_GetArrayItem(lives, i);
                   if(live_obj)
                 {
                       tmp_obj = cJSON_GetObjectItem(live_obj, WEATHER);
                       str_tmp = translate_weather_type_in_ascii(tmp_obj-&gt;valuestring);
                       sprintf(app_weather_data.info, "%s", str_tmp);
                       free(str_tmp);
                       LOGD(TAG, "weather: %s\r\n",app_weather_data.info);

                       tmp_obj = cJSON_GetObjectItem(live_obj, TIME);
                       sprintf(app_weather_data.time, "%s", tmp_obj-&gt;valuestring);
                       LOGD(TAG, "data: %s\r\n",app_weather_data.time);
                        
                        int year, mon, day, h, m, s;
                        sscanf(tmp_obj-&gt;valuestring, "%d-%d-%d %d:%d:%d", &amp;year, &amp;mon, &amp;day, &amp;h, &amp;m, &amp;s);
                        LOGD(TAG, "year:%d mon:%d day:%d h:%d m:%d s:%d\r\n", year, mon, day, h, m, s);
                        rtc_update_time(year, mon, day, h, m, s); //更新RTC时间
                        aos_sem_signal(&amp;weather_update_done_sem); //通知RTC时间已被同步
                                               
                       tmp_obj = cJSON_GetObjectItem(live_obj, TEMP);
                       app_weather_data.temperature = atoi(tmp_obj-&gt;valuestring);
                       LOGD(TAG, "temperature: %d\r\n",app_weather_data.temperature);
                 }
             }
         }
     }
 }
   cJSON_Delete(root);
    event_publish(EVENT_WEATHER_UPDATE, &amp;app_weather_data); //发布天气更新的时间
}</code></pre>

<p cid="n105" mdtype="paragraph">这里我增加了一些功能:</p>

<ul cid="n106" data-mark="-" mdtype="list">
        <li cid="n107" mdtype="list_item">
        <p cid="n108" mdtype="paragraph">在每次触发天气更新后,解析到时间数据后,更新一下RTC的校准时间,这样每次连网更新天气时,时间也被更新了</p>
        </li>
        <li cid="n109" mdtype="list_item">
        <p cid="n110" mdtype="paragraph">在每次触发天气更新后,更新完RTC的校准时间后,再触发一个信号量,表示RTC时间已被同步,这个的作用是用来在刚开始的时候,还没连上网之前,因为时间没同步,没法显示正确的时间,这时OLED屏幕上就可以先显示等待WIFI连接之类的提示,等RTC时间校准之后,再显示时间和天气</p>
        </li>
        <li cid="n111" mdtype="list_item">
        <p cid="n112" mdtype="paragraph">在每次触发天气更新后,解析到天气数据后,改为主题推送的方式来让OLED模块来更新天气数据(这位网友之前使用的是消息队列的方式)</p>
        </li>
</ul>

<h2 cid="n113" mdtype="heading">3.2 OLED显示时间和天气</h2>

<p cid="n114" mdtype="paragraph">参考网上的一篇天气时间的UI设计:<a href="https://www.arduino.cn/thread-98403-1-1.html">https://www.arduino.cn/thread-98403-1-1.html</a></p>

<p cid="n115" mdtype="paragraph">利用U8g2库来显示不同大小的字体,以及天气图标(U8g2的字体库中自带了一些天气图标,可以直接用),另外,U8g2库也是有汉字库的,只是汉字库占用的空间比较大,使用之后就编译出错了,那就先不显示汉字吧。</p>

<p cid="n116" mdtype="paragraph">UI界面的主要显示函数:</p>

<pre>
<code class="language-cpp">void testShowTimeAndWeather(u8g2_t *u8g2, csi_rtc_time_t *now_time)
{
    u8g2_ClearBuffer(u8g2);
   
    int year = now_time-&gt;tm_year;
    int month = now_time-&gt;tm_mon;
    int day = now_time-&gt;tm_mday;
    int hour = now_time-&gt;tm_hour;
    int minute = now_time-&gt;tm_min;
    int sec = now_time-&gt;tm_sec;
   
    //时分
    char str_big_time[] = "";
    my_strcat(str_big_time, hour);
    strcat(str_big_time,":");
    my_strcat(str_big_time, minute);
    u8g2_SetFont(u8g2, u8g2_font_logisoso24_tf );
    u8g2_DrawStr(u8g2, 0, 34, str_big_time);

    //秒
    char str_small_sec[] = "";
    my_strcat(str_small_sec, sec);
    u8g2_SetFont(u8g2, u8g2_font_wqy12_t_chinese1 );
    u8g2_DrawStr(u8g2, 72, 34, str_small_sec);

    //日期
    char str_date[] = "";
    char str_temp;
    itoa(year,str_temp,10);
    strcat(str_date,str_temp);
    strcat(str_date,"-");
    my_strcat(str_date, month);
    strcat(str_date,"-");
    my_strcat(str_date, day);
    u8g2_SetFont(u8g2,u8g2_font_wqy12_t_chinese1);
    u8g2_DrawStr(u8g2, 0, 54, str_date);

    //分割线
    u8g2_DrawLine(u8g2, 88, 2, 88, 61);
   
    //天气
    u8g2_SetFont(u8g2, u8g2_font_open_iconic_weather_4x_t );
    u8g2_DrawStr(u8g2, 96, 34, icon_index);
    u8g2_SetFont(u8g2, u8g2_font_wqy16_t_chinese1);
    char b_b_tmp;
    itoa(g_iTemperature, b_b_tmp, 10);
    strcat(b_b_tmp," C");
    u8g2_DrawStr(u8g2, 96, 55, b_b_tmp);
 
    u8g2_SendBuffer(u8g2);
    aos_msleep(1000);
}</code></pre>

<h1 cid="n118" mdtype="heading">4 测试效果</h1>

<p><iframe __idm_id__="761857" allowfullscreen="true" frameborder="0" height="450" src="//player.bilibili.com/player.html?bvid=1T44y1g7Ky&amp;page=1" style="background:#eee;margin-bottom:10px;" width="700"></iframe><br />
&nbsp;</p>

<h1 cid="n119" mdtype="heading">5 总结</h1>

<p cid="n120" mdtype="paragraph">本篇介绍了RVB2601连网获取天气,并更新RTC时间,以及最终将天气图形,温度数据和时间日期显示到OLED屏幕上。</p>

<p>&nbsp;</p>

lugl4313820 发表于 2022-5-9 06:54

<p>辛苦了,这帖子写得工整,让人看了有特舒服的感觉,知识面介绍也非常丰富,感谢分享。</p>
页: [1]
查看完整版本: 【平头哥RVB2601创意应用开发】实践7-U8g2库显示网络天气和时间