cqcqwind 发表于 2022-5-11 12:04

【平头哥RVB2601创意应用开发】动态加载MBRE 一种秒级精度的NTP快速实现代码

<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;NTP是IOT MCU中常用的功能, RVB2601也提供了NTP的package供使用。由于SDK的完善性还存在一定问题,7.4.3的NTP和系统RTC模块之间的衔接会导致卡死之类的现象。</p>

<p>&nbsp; &nbsp; &nbsp; &nbsp; 其实,由于2601开发板的RTC并没有自带电源,因此断电后, RTC无法保存时间。所以,这个RTC在使用中存在一定限制。 同时SDK的一些缺陷也给NTP和GETTIME类的API带来了麻烦。 因NTP的实际精度本来就受网络条件的影响,精度范围在几十ms到500ms之间(参见<a href="https://www.vfe.ac.cn/NewsDetail-2332.aspx" target="_blank">NTP精度的介绍</a>),故此,对于日常应用(例如电子钟等)而言,一个秒级的时钟已经足够使用了。以下介绍一个秒级NTP的快速实现代码,供坛友参考。</p>

<ol>
        <li>&nbsp; &nbsp; &nbsp; 通过CDK在工程文件中,加入ntp的package。</li>
        <li>&nbsp; &nbsp; &nbsp; 在NTP.C代码中, 加入以下函数
        <pre>
<code class="language-cpp">int mbre_simple_ntp_proc(char *server, int32_t *totalSeconds)
{
    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);
       
        //MBRE初始化返回值,错误情况下totaoSeconds值为0
        if(!totalSeconds) return -1;
        *totalSeconds = 0;

    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);
    }

    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;
    }

    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);

    FD_ZERO(&amp;readfds);
    FD_SET(sockfd, &amp;readfds);
    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;
            }
                       
                        if(nbytes &lt; 43) return -1;

                        //MBRE简单获取中国(8时区)的NTP秒数(自1900开始)
                        {
                                      unsigned long secsSince1900, u1, u2;
                                          // convert four bytes starting at location 40 to a long integer
                                          secsSince1900 = (unsigned long)buf &lt;&lt; 24;
                                          secsSince1900 |= (unsigned long)buf &lt;&lt; 16;
                                          secsSince1900 |= (unsigned long)buf &lt;&lt; 8;
                                          secsSince1900 |= (unsigned long)buf;
                                          //减去1900秒数起始;同时加上了8时区的秒数偏移
                                          *totalSeconds = secsSince1900 - 2208988800UL + 8 * 3600UL;
                                          
                        }
      } else {
            LOGE(TAG, "select timeout");
            return -1;
      }
    } else {
      LOGE(TAG, "FD_ISSET");
      close(sockfd);
      return -1;
    }
    close(sockfd);
       
    return 0;
}</code></pre>

        <p>&nbsp;</p>
        </li>
        <li>调用逻辑非常简单, 获取ntp时间后,记下获取点的机器毫秒数。 后面使用起来就可直接计算了。 这个和使用RTC的本质是一样的,但简单了很多。
        <pre>
<code class="language-cpp">//C文件中的引用处extern说明
extern int mbre_simple_ntp_proc(char *server, int32_t *totalSeconds);


...

int32_t   totalSeconds   = 0;
int32_t   startMicroSecs   = 0;

int       rtn;

//调用函数,返回成功后,totalSeconds存放的是自1900年起的秒数值
rtn = mbre_simple_ntp_proc("ntp1.aliyun.com", &amp;totalSeconds);

//记下获取ntp的毫秒时间
startMicroSecs = csi_tick_get_ms();

if(0 == rtn)printf("从1900年起算的秒数 = %d\r\n", totalSeconds);
else          printf("NTP失败,错误码 %d\r\n", rtn);

...
//在任何需要获取当前时间的地方, 只需要根据当前系统ms数,计算出与获取ntp的偏移即可得到有效秒数

int32_t currentMicroSecs = csi_tick_get_ms();

int32_t currentSecsSince1900 = totalSeconds+(currentMicroSecs- startMicroSecs)/1000;</code></pre>

        <p>&nbsp;</p>
        </li>
        <li>
        <p>以下给出arduino使用的time库里头的相关处理代码,很容易计算得到年,月,日,小时,分钟等数据</p>

        <pre>
<code class="language-cpp">typedef struct{
uint8_t Second;
uint8_t Minute;
uint8_t Hour;
uint8_t Wday;   // day of week, sunday is day 1
uint8_t Day;
uint8_t Month;
uint8_t Year;   // offset from 1970;
}         tmElements_t, TimeElements, *tmElementsPtr_t;


/* 从系统时间转换为年、月、日、时、分、秒的实际代码 */

// 润年润月计算
#define LEAP_YEAR(Y)   ( ((1970+(Y))&gt;0) &amp;&amp; !((1970+(Y))%4) &amp;&amp; ( ((1970+(Y))%100) || !((1970+(Y))%400) ) )

staticconst uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts months from 1, this array starts from 0


void breakTime(time_t timeInput, tmElements_t &amp;tm){
// break the given time_t into time components
// this is a more compact version of the C library localtime function
// note that year is offset from 1970 !!!

uint8_t year;
uint8_t month, monthLength;
uint32_t time;
unsigned long days;

time = (uint32_t)timeInput;
tm.Second = time % 60;
time /= 60; // now it is minutes
tm.Minute = time % 60;
time /= 60; // now it is hours
tm.Hour = time % 24;
time /= 24; // now it is days
tm.Wday = ((time + 4) % 7) + 1;// Sunday is day 1

year = 0;
days = 0;
while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) &lt;= time) {
    year++;
}
tm.Year = year; // year is offset from 1970

days -= LEAP_YEAR(year) ? 366 : 365;
time-= days; // now it is days in this year, starting at 0

days=0;
month=0;
monthLength=0;
for (month=0; month&lt;12; month++) {
    if (month==1) { // february
      if (LEAP_YEAR(year)) {
      monthLength=29;
      } else {
      monthLength=28;
      }
    } else {
      monthLength = monthDays;
    }
   
    if (time &gt;= monthLength) {
      time -= monthLength;
    } else {
      break;
    }
}
tm.Month = month + 1;// jan is month 1
tm.Day = time + 1;   // day of month
}
</code></pre>

        <p>在arduino的相关库中,提供了很多对时间操作,以及其他功能性开发的方便的现成库,如果铁头哥系列能够像国内IOT MCU其他家(如乐x)一样,提供arduino的适配库,那对于IOT APP开发将是一个非常重要的助力,毕竟如果只是简单连接传感器的话,很多芯片就足够了。 国内IOT MCU的明显优势就是更强的FLASH和RAM,在IOT这个应用为王的领域,有相对成熟扩展支撑,想必会对整个阿里MCU生态起到积极的推进作用。</p>
        </li>
</ol>

lugl4313820 发表于 2022-5-11 18:47

<p>确实,IOT,现在百花齐放,最近发现他家好多都是开源的</p>

<p>&nbsp;</p>

<p>玄铁开源E906开源信息:<br />
https://github.com/T-head-Semi/opene906?spm=a2cl5.25269445.0.0.5da5180fmPCxYb</p>

<p>YoC开源:<br />
https://github.com/T-head-Semi/open-yoc?spm=a2cl5.25269445.0.0.5da5180fmPCxYb</p>

<p>玄铁工具链开源:<br />
https://github.com/T-head-Semi/xuantie-gnu-toolchain?spm=a2cl5.25269445.0.0.5da5180fmPCxYb</p>
页: [1]
查看完整版本: 【平头哥RVB2601创意应用开发】动态加载MBRE 一种秒级精度的NTP快速实现代码