anni_zzg 发表于 2022-5-25 10:54

【平头哥RVB2601创意应用开发】五。使用NTP授时RTC时钟

本帖最后由 anni_zzg 于 2022-5-25 11:42 编辑

<p>搞定RTC时钟后,开始想通过串口设计时间,后来知道可以使用NTP授时,心中一亮,那就开干吧。</p>

<p>&nbsp; &nbsp; &nbsp;<span style="font-size:20px;"><strong>一。 先下载安装NTP组件。</strong></span></p>

<p>&nbsp;前面的测试都是在ch2601_helloworld工程中进行的,所以NTP功能也在这个工程中操作。</p>

<p>首先在打开工程的任意一个组件上右键点击,选View Details Web... 进入组件资源界面</p>

<p>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;</p>

<p>输入你要查看的组件NTP,然后搜索,安装</p>

<p><strong><span style="font-size:20px;"><span style="font-family:宋体;">二。代码实现。</span></span></strong></p>

<p>&nbsp; &nbsp; <strong>(1)网络设置</strong>,可以参考ch2601_webplayer_demo这个工程,最起码它的网络功能问题不大(UDP有点问题)</p>

<p>&nbsp; &nbsp; &nbsp;在init.c文件中添加如下代码</p>

<pre>
<code class="language-cpp">netmgr_hdl_t app_netmgr_hdl;

void w800_in(int linkid, void* data, size_t len, char remote_ip, uint16_t remote_ports)
{
    uint8_t* d;
    d = (uint8_t*)data;
    printf("recv data:%s", d);
}

static void network_init()
{
    w800_wifi_param_t w800_param;
    /* init wifi driver and network */
    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;

    wifi_w800_register(NULL, &amp;w800_param);
    app_netmgr_hdl = netmgr_dev_wifi_init(); //无线设备初始化

    if(app_netmgr_hdl) {
        utask_t* task = utask_new("netmgr", 2 * 1024, QUEUE_MSG_COUNT, AOS_DEFAULT_APP_PRI);
        netmgr_service_init(task);                                       //服务初始化
        netmgr_config_wifi(app_netmgr_hdl, "CMCC-GEFm", 9, "24r37ph4", 8); //无线配置
        // w800_packet_input_cb_register(&amp;w800_in);//初始化输入回调函数
        netmgr_start(app_netmgr_hdl); //使能网络设备
    }
}

void board_yoc_init()
{
    board_init();                              // 板级初始化
    event_service_init(NULL);                  // 发布订阅服务初始化
    console_init(CONSOLE_UART_IDX, 115200, 512); // 串口初始化
    ulog_init();                                 // 日志初始化
    aos_set_log_level(AOS_LL_DEBUG);             // 配置默认日志打印级别

    int ret = partition_init(); // 分区初始化
    if(ret &lt;= 0) {
        LOGE(TAG, "partition init failed");
    } else {
        LOGI(TAG, "find %d partitions", ret);
    }

    aos_kv_init("kv"); // kv文件系统初始化,可用于保存网络ssid&amp;psk

    network_init(); // 网络初始化

    board_cli_init(); // 命令行初始化并注册默认的命令
}</code></pre>

<p>main.c 添加如下代码:&nbsp;</p>

<pre>
<code class="language-cpp">static void network_event(uint32_t event_id, const void* param, void* context)
{
    switch(event_id) {
    case EVENT_NETMGR_GOT_IP:
        // LOGD(TAG, "net got ip");
        ntp_sync_time(NULL);
        break;
    case EVENT_NETMGR_NET_DISCON:
        LOGD(TAG, "net disconnect");
        if((int)param == NET_DISCON_REASON_DHCP_ERROR) {
          netmgr_reset(netmgr_get_handle("wifi"), 30); // 本次网络连接失败,30s后自动重连
        }

        break;
    }

    app_exception_event(event_id);
}

int main(void)
{
    board_yoc_init();
    LOGD(TAG, "%s\n", aos_get_app_version());
    // uart0_init();
    oled_init();
    led_init();
    key_init();
    timer_init();
    aita_InitTimer();
    rtc_init();
    IIC_Init();
    SGP30_Init();
    LED_RED_OFF;
    LED_GREEN_OFF;
    LED_BLUE_OFF;
    aos_timer_start(&amp;polling_timer);

    event_subscribe(EVENT_NETMGR_GOT_IP, network_event, NULL); //订阅服务
    event_subscribe(EVENT_NETMGR_NET_DISCON, network_event, NULL);
    return 0;
}</code></pre>

<p><strong>(2) RTC时钟的添加</strong></p>

<pre>
<code class="language-cpp">aos_timer_t polling_timer; // timer for polling

void pollingTimerCb(void* arg1, void* arg2)
{
    csi_rtc_time_t rtc;

    csi_rtc_get_time(&amp;aita_rtc, &amp;rtc);

    strftime(oled_buf, sizeof(oled_buf), "%Y-%m-%d", &amp;rtc);
    show_string(32, 0, oled_buf);

    strftime(oled_buf, sizeof(oled_buf), "%T", &amp;rtc);
    show_string(40, 16, oled_buf);

    SHT30_read_result();
    SGP30_read_result();

    strftime(oled_buf, sizeof(oled_buf), "%Y-%m-%d %T", &amp;rtc);

    LOGD(TAG, "%s\n", oled_buf);
}
// 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(&amp;polling_timer, pollingTimerCb, "args", 1000, 1, 0);
    if(ret != 0) {
        LOGE(TAG, "polling_timer create failed\n");
    }
}
void rtc_init(void)
{
    csi_error_t ret;
    ret = csi_rtc_init(&amp;aita_rtc, 0);
    if(ret == CSI_OK)
        printf("csi_rtc OK\n");
    else
        printf("csi_rtc wrong\n");
}</code></pre>

<p><strong>(3) NTP功能添加</strong></p>

<pre>
<code class="language-cpp">/* ntpclient.c */
#include &lt;aos/aos.h&gt;
#include &lt;sys/select.h&gt;
#include &lt;lwip/netdb.h&gt;
#include &lt;arpa/inet.h&gt;
#include &lt;drv/rtc.h&gt;

static const char* TAG = "NTP";

#ifndef CONFIG_NTP_CTS_ZONE
#define CTS_ZONE 8
#else
#define CTS_ZONE CONFIG_NTP_CTS_ZONE
#endif

#define VERSION_3 3
#define VERSION_4 4

#define MODE_CLIENT 3
#define MODE_SERVER 4

// NTP protocol Content
#define NTP_LI 0
#define NTP_VN VERSION_3
#define NTP_MODE MODE_CLIENT
#define NTP_STRATUM 0
#define NTP_POLL 4
#define NTP_PRECISION -6

#define NTP_HLEN 48

#define NTP_PORT 123

#define TIMEOUT 3

#define BUFSIZE 128

#define JAN_1970 0x83aa7e80

#define NTP_CONV_FRAC32(x) (uint64_t)((x) * ((uint64_t)1 &lt;&lt; 32))
#define NTP_REVE_FRAC32(x) ((double)((double)(x) / ((uint64_t)1 &lt;&lt; 32)))

#define NTP_CONV_FRAC16(x) (uint32_t)((x) * ((uint32_t)1 &lt;&lt; 16))
#define NTP_REVE_FRAC16(x) ((double)((double)(x) / ((uint32_t)1 &lt;&lt; 16)))

#define USEC2FRAC(x) ((uint32_t)NTP_CONV_FRAC32((x) / 1000000.0))
#define FRAC2USEC(x) ((uint32_t)NTP_REVE_FRAC32((x)*1000000.0))

#define NTP_LFIXED2DOUBLE(x)                                        \
    ((double)(ntohl(((struct l_fixedpt*)(x))-&gt;intpart) - JAN_1970 + \
            FRAC2USEC(ntohl(((struct l_fixedpt*)(x))-&gt;fracpart)) / 1000000.0))

struct s_fixedpt {
    uint16_t intpart;
    uint16_t fracpart;
};

struct l_fixedpt {
    uint32_t intpart;
    uint32_t fracpart;
};

struct ntphdr {
    unsigned int ntp_mode : 3;
    unsigned int ntp_vn : 3;
    unsigned int ntp_li : 2;

    uint8_t ntp_stratum;
    uint8_t ntp_poll;
    int8_t ntp_precision;
    struct s_fixedpt ntp_rtdelay;
    struct s_fixedpt ntp_rtdispersion;
    uint32_t ntp_refid;
    struct l_fixedpt ntp_refts;
    struct l_fixedpt ntp_orits;
    struct l_fixedpt ntp_recvts;
    struct l_fixedpt ntp_transts;
};

in_addr_t inet_host(const char* host)
{
    in_addr_t saddr;
    struct hostent* hostent;

    if((saddr = inet_addr(host)) == INADDR_NONE) {
        if((hostent = gethostbyname(host)) == NULL) {
          return INADDR_NONE;
        }

        // memcpy(&amp;saddr, hostent-&gt;h_addr_list, hostent-&gt;h_length);

        saddr = *((unsigned long*)hostent-&gt;h_addr_list);
    }

    return saddr;
}

int get_ntp_packet(void* buf, size_t* size)
{
    struct ntphdr* ntp;
    struct timeval tv;

    if(!size || *size &lt; NTP_HLEN) {
        return -1;
    }

    memset(buf, 0, *size);

    ntp = (struct ntphdr*)buf;
    ntp-&gt;ntp_li = NTP_LI;
    ntp-&gt;ntp_vn = NTP_VN;
    ntp-&gt;ntp_mode = NTP_MODE;
    ntp-&gt;ntp_stratum = NTP_STRATUM;
    ntp-&gt;ntp_poll = NTP_POLL;
    ntp-&gt;ntp_precision = NTP_PRECISION;

    gettimeofday(&amp;tv, NULL);
    ntp-&gt;ntp_transts.intpart = htonl(tv.tv_sec + JAN_1970);
    ntp-&gt;ntp_transts.fracpart = htonl(USEC2FRAC(tv.tv_usec));

    *size = NTP_HLEN;

    return 0;
}

double get_offset(const struct ntphdr* ntp, const struct timeval* recvtv)
{
    double t1, t2, t3, t4;

    t1 = NTP_LFIXED2DOUBLE(&amp;ntp-&gt;ntp_orits);
    t2 = NTP_LFIXED2DOUBLE(&amp;ntp-&gt;ntp_recvts);
    t3 = NTP_LFIXED2DOUBLE(&amp;ntp-&gt;ntp_transts);
    t4 = recvtv-&gt;tv_sec + recvtv-&gt;tv_usec / 1000000.0;

    return ((t2 - t1) + (t3 - t4)) / 2;
}

struct tm tm_val;
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-&gt;ntp_recvts.intpart) - JAN_1970 + CTS_ZONE * 3600;
    ; // with timezone offset
    char tbuf;
    tm_val = *(localtime(&amp;time));
    extern csi_rtc_t aita_rtc; // rtc device descriptor
    csi_rtc_set_time(&amp;aita_rtc, &amp;tm_val);
    strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %T", &amp;tm_val);
    printf("aita datetime: %s\n", tbuf);
}

static int _ntp_sync_time(char* server)
{
    char buf;
    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) {
        // 1.cn.pool.ntp.org is more reliable
        servaddr.sin_addr.s_addr = inet_host("ntp1.aliyun.com");
        LOGD(TAG, "ntp1.aliyun.com");
    } else {
        servaddr.sin_addr.s_addr = inet_host(server);
        LOGD(TAG, "%s", server);
    }
    printf("---------------------&gt;check server done! _ntp_sync_time()\n");

    if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) &lt; 0) {
        // LOGE(TAG, "socket error");
        return -1;
    }

    setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &amp;rcvtimeout, sizeof(struct timeval));

    if(connect(sockfd, (struct sockaddr*)&amp;servaddr, sizeof(struct sockaddr)) != 0) {
        // LOGE(TAG, "connect error");
        close(sockfd);
        return -errno;
    }
    printf("---------------------&gt;connect server done! _ntp_sync_time()\n");

    nbytes = BUFSIZE;

    if(get_ntp_packet(buf, &amp;nbytes) != 0) {
        // LOGE(TAG, "construct ntp request errorr");
        close(sockfd);
        return -1;
    }

    send(sockfd, buf, nbytes, 0); //发送一个UDP数据包
    printf("---------------------&gt;send request pack done! _ntp_sync_time()\n");

    FD_ZERO(&amp;readfds);      //将集合readfds清零
    FD_SET(sockfd, &amp;readfds); //将sockfd加入集合
    maxfd1 = sockfd + 1;

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

    if(select(maxfd1, &amp;readfds, NULL, NULL, &amp;timeout) &gt; 0) {
        if(FD_ISSET(sockfd, &amp;readfds)) {
          if((nbytes = recv(sockfd, buf, BUFSIZE, 0)) &lt; 0) {
                // LOGE(TAG, "recv error");
                close(sockfd);
                return -1;
          }

          // printf("nbytes = %d\n", nbytes);
          // print_ntp((struct ntphdr *) buf);
          aita_print_ntp((struct ntphdr*)buf);
          gettimeofday(&amp;recvtv, NULL);
          offset = get_offset((struct ntphdr*)buf, &amp;recvtv);
          gettimeofday(&amp;tv, NULL);
// TODO: ctime has some problem

// char tbuf;
// strftime(tbuf, sizeof(tbuf), "%Y-%m-%d %T", localtime(&amp;tv.tv_sec));
// memset(&amp;tv, 0, sizeof(tv));
// tbuf = ctime((time_t *)&amp;tv.tv_sec);
// LOGD(TAG, "system time1:%s", tbuf);
#if 1

          tv.tv_sec += (int)offset;
          tv.tv_usec += offset - (int)offset;

          if(settimeofday(&amp;tv, NULL) != 0) {
                LOGE(TAG, "set time error");
                close(sockfd);
                return -1;
          }
          LOGD(TAG, "---------------------&gt;settimeofday done! _ntp_sync_time()\n");

// TODO: ctime has some problem
// LOGD(TAG, "ntp time:\t%s", ctime((time_t *) &amp;tv.tv_sec));
#endif
        }
    } else {
        close(sockfd);
        LOGD(TAG, "---------------------&gt;select body error! _ntp_sync_time()\n");
        return -1;
    }

    close(sockfd);
    LOGD(TAG, "---------------------&gt;whole done! _ntp_sync_time()\n");
    return 0;
}
int ntp_sync_time(char* server)
{
    int ret = -1;
    for(int i = 0; i &lt; 2; i++) {
        ret = _ntp_sync_time(server);
        if(ret == 0) {
          LOGD(TAG, "sync success");
          break;
        }
    }

    if(ret &lt; 0) {
        LOGE(TAG, "sync error");
    }

    return ret;
}
</code></pre>

<p>(<strong>4)修改UDP连接功能</strong>,UDP的通信不但需要远程IP,远程端口,还需要本地的端口,本地端口自己制定一个大于1024的端口值。</p>

<pre>
<code class="language-cpp">int32_t udp_local_port = 1338;

int w800_connect_remote(int id, net_conn_e type, char *srvname, uint16_t port)
{
    int ret = -1;
    int ret_id;

    if (g_net_status &lt; NET_STATUS_GOTIP) {
      LOGE(TAG, "net status error\n");
      return -1;
    }

    aos_mutex_lock(&amp;g_cmd_mutex, AOS_WAIT_FOREVER);

    atparser_clr_buf(g_atparser_uservice_t);

    switch (type) {
      case NET_TYPE_TCP_SERVER:
            /* TCP Server can NOT ignore lport */
            break;

      case NET_TYPE_UDP_UNICAST:
                        printf("UDP_UNICAST mode\n");
                        ret = atparser_send(g_atparser_uservice_t, "AT+CIPSTART=%d,%s,%s,%d,%d", id, "udp_unicast", srvname, port, udp_local_port);
            break;
      case NET_TYPE_TCP_CLIENT:
            ret = atparser_send(g_atparser_uservice_t, "AT+CIPSTART=%d,%s,%s,%d", id, "tcp_client", srvname, port);
            break;

      default:
            LOGE(TAG, "type=%d err!", type);
            return -1;

    }

    if (ret == 0) {
      ret = -1;

      if ((atparser_recv(g_atparser_uservice_t, "OK\n") == 0) \
            &amp;&amp; (atparser_recv(g_atparser_uservice_t, "+EVENT=CONNECT,%d\n", &amp;ret_id) == 0)) {
            if (ret_id == id) {
                ret = 0;
            }
      }
    }

    atparser_cmd_exit(g_atparser_uservice_t);

    aos_mutex_unlock(&amp;g_cmd_mutex);

    return ret;
}</code></pre>

<p><strong>(5)显示部分参考我的经验分享三和四。</strong></p>

<p><span style="font-size:20px;"><span style="font-family:宋体;"><strong>三。 总结。</strong></span></span></p>

<p>NTP的实现走过了不少的坑,当初网络通了,可是NTP一直不通,不知道怎么下手了。还好RVB2601活动群分享了网友sonicfirr(OCC ID:firr)的RTC应用示例&nbsp;<a href="https://bbs.eeworld.com.cn/thread-1199207-1-1.html">【平头哥RVB2601创意应用开发】使用体验06 -- NTP授时 - 平头哥RISC-V RVB2601活动专区 - 电子工程世界-论坛 (eeworld.com.cn)</a>,本程序中去掉了sonicfirr定义的aita_rtc_t和aita_tm_u两个结构体。经验证csi_rtc_time_t和时间函数 tm是可以转换使用的。</p>

<p><strong><span style="font-size:20px;"><span style="font-family:宋体;">四。实验结果</span></span></strong></p>

<p>&nbsp; &nbsp; (1)通过设置&nbsp;netmgr_config_wifi(app_netmgr_hdl, <span style="color:#27ae60;">&quot;CMCC-GEFm&quot;, 9, &quot;24r37ph4&quot;, 8</span>); //配置成自己的WIFI,如下,说明得到自己的IP地址,WIFI网络连接成功。</p>

<p>(2)授时,并读取RTC</p>

<p>&nbsp; &nbsp;</p>

<p>(3)OLED结果展示</p>

<p>&nbsp;</p>

lugl4313820 发表于 2022-5-25 21:57

确认可以了吗?是不是平头哥修正了?我还一直没有弄通列!

anni_zzg 发表于 2022-6-2 14:34

lugl4313820 发表于 2022-5-25 21:57
确认可以了吗?是不是平头哥修正了?我还一直没有弄通列!

<p>可以了老弟,正常使用没问题</p>

<p>&nbsp;</p>

lugl4313820 发表于 2022-6-2 16:10

anni_zzg 发表于 2022-6-2 14:34
可以了老弟,正常使用没问题

&nbsp;

<p>明天发你的工程我学习一下。</p>
页: [1]
查看完整版本: 【平头哥RVB2601创意应用开发】五。使用NTP授时RTC时钟