[经验分享] 【平头哥RVB2601创意应用开发】使用体验08 -- RTC应用

sonicfirr   2022-4-16 10:15 楼主

NTP授时成功后,创意项目继续启用RTC,利用NTP获取的时间初始化RTC,以便在系统运行期间可以更快速的获得本地时间。

1CSI2接口

AOS APIhttps://yoc.docs.t-head.cn/yocbook/Chapter3-AliOS/)中包含两套设备驱动接口CSI2CSI1,通过对RVB2601SDK查看,发现板子实现的是CSI2接口。

 

image-20220416101241-1.png

 

image-20220416101241-2.png

 

image-20220416101241-3.png

8-1 文档中的CSI2接口和SDK中的RTC实现

 

依据文档说明,本项目使用了RTC的三个功能:初始化csi_rtc_init(),设置时间csi_rtc_set_time()和获取时间csi_rtc_get_time()

 

image-20220416101241-4.png

 8-2 文档中的csi_rtc_init()描述

 

image-20220416101241-5.png

8-3 文档中的csi_rtc_set_time()描述

 

image-20220416101241-6.png

8-4 文档中的csi_rtc_get_time()描述

 

2newlib包中的time

RVB2601SDK中,newlib包提供了C标准库。说来惭愧,本人搞了多年嵌入式,C语言是主要生产工具,但是C标准库却是比较陌生,尤其是time库,因此这里也走了一点弯路(因为不熟悉,也搞不清楚是不是bug,所以不敢称为“踩坑”)。

之前修正的NTP功能,有获取网络时间并调用settimeofday()的语句,通过网上检索了解到这是设置系统时间戳的函数。还有gettimeofday()则是获取系统时间,于是想在ntp_sync_time()调用后,通过gettimeofday()即获得本地时间。但是,这样操作得到的时间是错误的,于是又联想到向平头哥反馈NTP bug时,技术小哥给了一个官方修正NTP bug的操作文档,其中包含有对time库的修改。所以,按照文档修改了一下SDK,结果依然不成功。

所以,最后放弃了使用gettimeofday()的思路,当然time库中还是用到了几个API,可以更方便的进行时间格式转换。API具体有:类型重定义time_t、结构体struct tm、本地时间函数localtime()和时间格式化函数strftime()

数据结构类型定义和函数声明都位于../newlib/v7.4.3/include/time.h”头文件中,具体如下。

 

//by author. omit other codes
typedef int32_t  time_t;         /* Holds time in seconds */

struct tm
{
  int tm_sec;     /* Seconds (0-61, allows for leap seconds) */
  int tm_min;     /* Minutes (0-59) */
  int tm_hour;    /* Hours (0-23) */
  int tm_mday;    /* Day of the month (1-31) */
  int tm_mon;     /* Month (0-11) */
  int tm_year;    /* Years since 1900 */
  int tm_wday;    /* Day of the week (0-6) */
  int tm_yday;    /* Day of the year (0-365) */
  int tm_isdst;   /* Non-0 if daylight savings time is in effect */
};

size_t strftime(char *s, size_t max, const char *format, const struct tm *tm);

inline struct tm *localtime(const time_t *tim_p) {return gmtime(tim_p);}

3RTC实现与NTP修改

本例的思路是:系统启动进行RTC初始化和网络初始化,在获得IP地址的事件回调中请求NTP授时,并将收到的时间值设置到RTC中,之后激活定时器每个一段时间读取RTC并显示读取值。

注意12节中展示的结构体csi_rtc_time_tstruct tm,两者相差一个成员,即struct tm有分量“int tm_isdst”,于是本例采用建立联合体来保存时间数据结构,以同时适配CSI RTC接口和time C库。

另外,本例仿照NTP的原API,编写了可NTP授时同时设置RTC的函数。联合体类型定义和函数声明放在了“../ntp/v7.4.3/include/ntp.h”中,其完整代码如下。

 

#ifndef __NTP_H__
#define __NTP_H__

#include <drv/rtc.h>

#ifdef __cplusplus
extern "C"
{
#endif

int ntp_sync_time(char *server);
int aita_ntp_sync_time(char *server); //by author. ntp sync & set rtc function

typedef struct {
        csi_rtc_time_t rtc;
        int            isdst;
} aita_rtc_t;

typedef union {
        struct tm  libc_tm;
        aita_rtc_t rtc_tm;
} aita_tm_u;

extern aita_tm_u aita_dt;

#ifdef __cplusplus
}
#endif
#endif

 

对应的ntp.c,增加的代码如下。

 

#define AITA_LOCAL 8*3600
aita_tm_u aita_dt;
void aita_print_ntp(struct ntphdr *ntp) {
    time_t time;
//by author. get ntp time, set into rtc then show datetime once
    time = ntohl(ntp->ntp_recvts.intpart) - JAN_1970 + AITA_LOCAL;  //with timezone offset
        char tbuf[64];
        aita_dt.libc_tm = *(localtime(&time));                          //set system datetime variable
        extern csi_rtc_t aita_rtc;                                      //rtc device descriptor
        csi_rtc_set_time(&aita_rtc, &(aita_dt.rtc_tm.rtc));             //set datetime variable into rtc
    strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %T", &(aita_dt.libc_tm));//datetime to string
        printf("aita datetime: %s\n", tbuf);
}

int aita_ntp_sync_time(char *server) {
    char               buf[BUFSIZE];
    size_t             nbytes;
    int                sockfd, maxfd1;
    struct sockaddr_in servaddr = {0,};
    fd_set             readfds;
    struct timeval     timeout, recvtv, tv, rcvtimeout = {3, 0};
    double             offset;

    servaddr.sin_family = AF_INET;
    servaddr.sin_port   = htons(NTP_PORT);

    if (server == NULL) {
        servaddr.sin_addr.s_addr = inet_host("ntp1.aliyun.com");
        LOGD(TAG, "ntp1.aliyun.com\t---------------------> aita_ntp_sync_time()\n");
    } else {
        servaddr.sin_addr.s_addr = inet_host(server);
        LOGD(TAG, "%s\t---------------------> aita_ntp_sync_time()\n", server);
    }

    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        LOGE(TAG, "socket error\t---------------------> aita_ntp_sync_time()\n");
        return -1;
    }
    setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &rcvtimeout, sizeof(struct timeval));
    if (connect(sockfd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr)) != 0) {
        LOGE(TAG, "connect error\t---------------------> aita_ntp_sync_time()\n");
        close(sockfd);
        return -errno;
    }

    nbytes = BUFSIZE;
    if (get_ntp_packet(buf, &nbytes) != 0) {
        LOGE(TAG, "construct ntp request errorr\t---------------------> aita_ntp_sync_time()\n");
        close(sockfd);
        return -1;
    }
    send(sockfd, buf, nbytes, 0);

    FD_ZERO(&readfds);
    FD_SET(sockfd, &readfds);
    maxfd1 = sockfd + 1;

    timeout.tv_sec  = TIMEOUT;
    timeout.tv_usec = 0;

    if (select(maxfd1, &readfds, NULL, NULL, &timeout) > 0) {
        if (FD_ISSET(sockfd, &readfds)) {
            if ((nbytes = recv(sockfd, buf, BUFSIZE, 0)) < 0) {
                LOGE(TAG, "recv error\t---------------------> aita_ntp_sync_time()\n");
                close(sockfd);
                return -1;
            }

            aita_print_ntp((struct ntphdr *) buf);
            gettimeofday(&recvtv, NULL);
            offset = get_offset((struct ntphdr *)buf, &recvtv);

            gettimeofday(&tv, NULL);
            tv.tv_sec += (int)offset;
            tv.tv_usec += offset - (int)offset;

            if (settimeofday(&tv, NULL) != 0) {
                LOGE(TAG, "set time error\t---------------------> aita_ntp_sync_time()\n");
                close(sockfd);
                return -1;
            }
        }
    } else {
        close(sockfd);
                printf("select body error!\t---------------------> aita_ntp_sync_time()\n");
        return -1;
    }
    close(sockfd);
        printf("whole done!\t---------------------> aita_ntp_sync_time()\n");
    return 0;
}

 

案例基于Hello World Demo”,init.c中定义相关初始化和功能函数,相较前几篇代码变动较多,这里直接给出全部代码。

 

#include <stdbool.h>
#include <stdlib.h>

#include <aos/aos.h>
#include <aos/cli.h>
#include <aos/kv.h>
#include <yoc/yoc.h>
#include <yoc/partition.h>
#include <yoc/netmgr.h>
#include <yoc/netmgr_service.h>
#include <devices/devicelist.h>
#include <devices/w800.h>
#include <uservice/eventid.h>
#include <ntp.h>

#include "board.h"
#include "app_init.h"
#include "drv/pin.h"
#include "drv/rtc.h"
#include "../w800_api.h"

const char *TAG = "INIT";

#ifndef CONSOLE_UART_IDX
#define CONSOLE_UART_IDX 0
#endif


/* --------------------by AITA Mr.Fei, global variables--------------------- */


void board_yoc_init()
{
    board_init();
	event_service_init(NULL); //initialize event service for network events
    console_init(CONSOLE_UART_IDX, 115200, 128);
    ulog_init();
    aos_set_log_level(AOS_LL_DEBUG);    
    LOGI(TAG, "Build:%s,%s\n",__DATE__, __TIME__);
	aita_InitRTC();	
	
    /* load partition & init kv function */
	aita_InitKV();
	
	/* set cycle timer for polling job */
	aita_InitTimer();

	/* initialize wifi network */
	aita_InitNetwork();

    board_cli_init();
}


/* --------------------by AITA Mr.Fei, key-value functions------------------ */
//project's global variables
#define ENDIAN_LIMIT   32 //endian counts limit
char GID[9];              //gateway id
char EID[ENDIAN_LIMIT][9];//endians' id
int  endian_count = 1;    //actually endian counts
int  period = 10;         //polling period -- minutes
int  timeout = 15;        //timeout of waiting USR220 resp -- seconds
int  repeat = 3;          //repeat times when timeout
//Init partition & kv function
int aita_InitKV(void) {
	char buf[10] = {0}; 
	int ret = -1;
	
	ret = partition_init();
	if (ret > 0) {
		LOGI(TAG, "find %d partitions\n", ret);
	} else {		 
		LOGE(TAG, "partition init failed\n");
		return ret;
	}
	
	ret = aos_kv_init("kv");
	if(ret == 0) {
		LOGE(TAG, "kv init done\n");
	} else {
		LOGE(TAG, "kv init failed\n");
		return -1;
	}
		
	ret = aos_kv_getstring("endian_count", &buf, 10);
	if(ret > 0) {
		endian_count = atoi(buf);
		endian_count = (endian_count<ENDIAN_LIMIT) ? endian_count : ENDIAN_LIMIT;
		printf("endian_count: %d\n", endian_count);
		char eid[5] = "EIDx";
		for(int i=0; i<endian_count; i++) {
			eid[3] = i+0x30;
			aos_kv_getstring(eid, EID[i], 9);
			printf("EID No.%d : %s\n", i, EID[i]);
		}	
	} else {
		LOGE(TAG, "Endian Count load failed\n");
		return ret;
	}
	
	ret = aos_kv_getstring("period", &buf, 10);
	if(ret > 0) {
		period = atoi(buf);
		printf("period: %d\n", period);
	} else {
		LOGE(TAG, "period load failed\n");
		return ret;
	}
	ret = aos_kv_getstring("timeout", &buf, 10);
	if(ret > 0) {
		timeout = atoi(buf);
		printf("timeout: %d\n", timeout);
	} else {
		LOGE(TAG, "timeout load failed\n");
		return ret;
	}
	ret = aos_kv_getstring("repeat", &buf, 10);
	if(ret > 0) {
		repeat = atoi(buf);
		printf("repeat: %d\n", repeat);
	} else {
		LOGE(TAG, "repeat load failed\n");
		return ret;
	}
	return 0;
}


/* --------------------by AITA Mr.Fei, timer functions---------------------- */
#define PERIOD_FACTOR  1000
aos_timer_t polling_timer;//timer for polling
//timer callback
void pollingTimerCb(void *arg1, void *arg2) {
	aita_ShowRTC();
}
//Init and startup periodic timer for polling job
void aita_InitTimer(void) {
	int ret = -1;
	LOGI(TAG, "initialize timer for polling job!\n");
	
	ret = aos_timer_new_ext(&polling_timer, pollingTimerCb, "args", period*PERIOD_FACTOR, 1, 0);
	if(ret != 0) {
		LOGE(TAG, "polling_timer create failed\n");
	}
}


/* --------------------by AITA Mr.Fei, network functions-------------------- */
//newwork macros
#define WIFI_SSID      "your ssid"
#define WIFI_PSW       "you password"
#define GW_IP          "192.168.31.77"
#define GW_PORT        1234
#define ONE_HTTP_IP    "183.230.40.33"
#define ONE_HTTP_PORT  80

//netmgr_hdl_t actually is void *
netmgr_hdl_t app_netmgr_hdl; //net manager handler for w800
//network event callback function
static void network_event(uint32_t event_id, const void *param, void *context) {
    switch(event_id) {
		case EVENT_NETMGR_GOT_IP: {
			LOGD(TAG, "EVENT_NETMGR_GOT_IP\n");
			printf("start polling_timer. periodic: %d min\n", period);			
			aita_RequestNTP();
			aos_timer_start(&polling_timer);
			break;
		}
		case EVENT_NETMGR_NET_DISCON: {
			LOGD(TAG, "EVENT_NETMGR_NET_DISCON\n");
			printf("ready to restart wifi connection...\n");
			netmgr_reset(app_netmgr_hdl, 30);
			break;
		}	
	}
}
//w800 input callback function
void aita_w800in_cb(int linkid, void *data, size_t len, char remote_ip[16], uint16_t remote_ports) {
	uint8_t* d;
	d = (uint8_t *)data;
	printf("linkid: %d\n", linkid);
	printf("len: %d\n", len);
	printf("remote_ip: %s\n", remote_ip);
	printf("remote_port: %d\n", remote_ports);
	printf("data:%s\n", d);
}
//network init: open w800, 
void aita_InitNetwork(void) {
	/*
	 * by AITA Mr.Fei
	 * w800 interface struct
	 */
    w800_wifi_param_t w800_param;
    w800_param.reset_pin      = PA21;
    w800_param.baud           = 1*1000000;
    w800_param.cs_pin         = PA15;
    w800_param.wakeup_pin     = PA25;
    w800_param.int_pin        = PA22;
    w800_param.channel_id     = 0;
    w800_param.buffer_size    = 4*1024;
	/*
	 * by AITA Mr.Fei
	 * from w800_devops.c(drv_wifi_at_w800 pack),
	 * include w800_module_init(but there is no task, so create a new one) & driver_register.
	 */
    wifi_w800_register(NULL, &w800_param);
	/*
	 * by AITA Mr.Fei
	 * register uservice for wifi, 
	 * init netmgr_uservice struct then return the pointer
	 */
    app_netmgr_hdl = netmgr_dev_wifi_init();
    if(app_netmgr_hdl) {
		//init uservice for wifi
        utask_t *task = utask_new("netmgr", 2 * 1024, QUEUE_MSG_COUNT, AOS_DEFAULT_APP_PRI);
        netmgr_service_init(task);
		//join AP, WIFI_SSID & WIFI_PSW both are Macro
        netmgr_config_wifi(app_netmgr_hdl, WIFI_SSID, 10, WIFI_PSW, 10);
		//init w800 AT parser
		w800_module_init(task, &w800_param);

		//start uservice
        netmgr_start(app_netmgr_hdl);
		//subscribe events which is managed by netmgr uservice
        event_subscribe(EVENT_NETMGR_GOT_IP, network_event, NULL);
        event_subscribe(EVENT_NETMGR_NET_DISCON, network_event, NULL);
    }
}
//tcp client test func
void aita_tcpclienttest(void) {
	char buf[] = "hello from RVB2601 by W800\n";
	
	int ret = -1;	
	ret = w800_connect_remote(0, NET_TYPE_TCP_CLIENT, GW_IP, GW_PORT);
	if(ret < 0) return;
	
	int stat;
	w800_get_status(&stat);  //[  16.360]<D>w800_api link status 2 已获取到ip
	LOGD(TAG, "connect status: %d\n", stat);

	w800_send_data(buf, sizeof(buf), 3000);
}
//ntp test func
void aita_RequestNTP(void) {	
	int ret = -1;
    for(int i = 0; i < 2; i++) {
        ret = aita_ntp_sync_time("cn.pool.ntp.org");
        if (ret == 0) {
            LOGD(TAG, "sync success\n");
            break;
        }
    }

    if(ret < 0) {
        LOGE(TAG, "sync error\n");
    }
	
	//register input event callback for w800 module
	w800_packet_input_cb_register(&aita_w800in_cb);
}


/* --------------------by AITA Mr.Fei, rtc functions------------------------ */
csi_rtc_t aita_rtc;
void aita_InitRTC(void) {
	csi_error_t ret;
	ret = csi_rtc_init(&aita_rtc, 0);
	
	if(ret == CSI_OK) printf("initialize RTC done!\n");
	else              printf("initialize RTC return: %d\n", ret);
}

void aita_ShowRTC(void) {
	csi_error_t ret;
	char tbuf[64];
	ret = csi_rtc_get_time(&aita_rtc, &(aita_dt.rtc_tm.rtc));
    strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %T", &(aita_dt.libc_tm));
	printf("show aita datetime in RTC: %s\n", tbuf);
}

 

image-20220416101241-7.png

 8-5 RTC应用效果

 

 

本帖最后由 sonicfirr 于 2022-4-16 10:22 编辑

回复评论 (7)

好想借你的NTP用一下,这项目整得没想法了!可否

点赞  2022-4-16 11:16
引用: lugl4313820 发表于 2022-4-16 11:16 好想借你的NTP用一下,这项目整得没想法了!可否

别客气,咱就是为了开源来的

点赞  2022-4-16 11:53

4楼 UUC 

有点疑问:图8-3 文档中的csi_rtc_set_time()描述  tm_mon 取值范围[0,11],为何不是[1,12] 

没有特别的幸运,就要特别的努力
点赞  2022-4-16 19:13
引用: UUC 发表于 2022-4-16 19:13 有点疑问:图8-3 文档中的csi_rtc_set_time()描述  tm_mon 取值范围[0,11],为何不是[1,12]  ...

有些RTC寄存器就是这么设计的,具体为什么得问官方了。不过这不影响RTC的使用。

点赞  2022-4-17 08:04
引用: UUC 发表于 2022-4-16 19:13 有点疑问:图8-3 文档中的csi_rtc_set_time()描述  tm_mon 取值范围[0,11],为何不是[1,12]  ...

我想这就是一种习惯吧,从0开始编号,C标准库中时间结构体struct tm也是从0编号月份。

点赞  2022-4-17 09:18

7楼 UUC 

引用: wangerxian 发表于 2022-4-17 08:04 有些RTC寄存器就是这么设计的,具体为什么得问官方了。不过这不影响RTC的使用。

好的,了解了,谢谢

没有特别的幸运,就要特别的努力
点赞  2022-4-17 12:58

8楼 UUC 

引用: sonicfirr 发表于 2022-4-17 09:18 我想这就是一种习惯吧,从0开始编号,C标准库中时间结构体struct tm也是从0编号月份。

好的,了解了,谢谢

没有特别的幸运,就要特别的努力
点赞  2022-4-17 12:59
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复