【平头哥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(&weather_update_done_sem, AOS_WAIT_FOREVER);
while(1)
{
if(CSI_OK != csi_rtc_get_time(&g_rtc, &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(&rtc_clock_queue, &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时间,当发现“秒”数据变化时,通过消息队列发送给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, "========>JSON_data:\n%s\n", cJSON_Print(root));
status = cJSON_GetObjectItem(root, STATUS);
if(status)
{
LOGD(TAG, "status:%d,%s\r\n",atoi(status->valuestring), status->valuestring);
lives = cJSON_GetObjectItem(root, LIVES);
if(lives)
{
size = cJSON_GetArraySize(lives);
LOGD(TAG, "!!size:%d\r\n",size);
for(int i=0;i<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->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->valuestring);
LOGD(TAG, "data: %s\r\n",app_weather_data.time);
int year, mon, day, h, m, s;
sscanf(tmp_obj->valuestring, "%d-%d-%d %d:%d:%d", &year, &mon, &day, &h, &m, &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(&weather_update_done_sem); //通知RTC时间已被同步
tmp_obj = cJSON_GetObjectItem(live_obj, TEMP);
app_weather_data.temperature = atoi(tmp_obj->valuestring);
LOGD(TAG, "temperature: %d\r\n",app_weather_data.temperature);
}
}
}
}
}
cJSON_Delete(root);
event_publish(EVENT_WEATHER_UPDATE, &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->tm_year;
int month = now_time->tm_mon;
int day = now_time->tm_mday;
int hour = now_time->tm_hour;
int minute = now_time->tm_min;
int sec = now_time->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&page=1" style="background:#eee;margin-bottom:10px;" width="700"></iframe><br />
</p>
<h1 cid="n119" mdtype="heading">5 总结</h1>
<p cid="n120" mdtype="paragraph">本篇介绍了RVB2601连网获取天气,并更新RTC时间,以及最终将天气图形,温度数据和时间日期显示到OLED屏幕上。</p>
<p> </p>
<p>辛苦了,这帖子写得工整,让人看了有特舒服的感觉,知识面介绍也非常丰富,感谢分享。</p>
页:
[1]