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

cqcqwind   2022-5-11 12:04 楼主

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

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

  1.       通过CDK在工程文件中,加入ntp的package。
  2.       在NTP.C代码中, 加入以下函数
    int mbre_simple_ntp_proc(char *server, int32_t *totalSeconds)
    {
        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);
    	
    	//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)) < 0) {
            LOGE(TAG, "socket error");
            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");
            close(sockfd);
            return -errno;
        }
    
        nbytes = BUFSIZE;
    
        if (get_ntp_packet(buf, &nbytes) != 0) {
            LOGE(TAG, "construct ntp request errorr");
            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");
                    close(sockfd);
                    return -1;
                }
    			
    			if(nbytes < 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[40] << 24;
    					  secsSince1900 |= (unsigned long)buf[41] << 16;
    					  secsSince1900 |= (unsigned long)buf[42] << 8;
    					  secsSince1900 |= (unsigned long)buf[43];
    					  //减去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;
    }

     

  3. 调用逻辑非常简单, 获取ntp时间后,记下获取点的机器毫秒数。 后面使用起来就可直接计算了。 这个和使用RTC的本质是一样的,但简单了很多。
    //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", &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;

     

  4. 以下给出arduino使用的time库里头的相关处理代码,很容易计算得到年,月,日,小时,分钟等数据

    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))>0) && !((1970+(Y))%4) && ( ((1970+(Y))%100) || !((1970+(Y))%400) ) )
    
    static  const 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 &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)) <= 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<12; month++) {
        if (month==1) { // february
          if (LEAP_YEAR(year)) {
            monthLength=29;
          } else {
            monthLength=28;
          }
        } else {
          monthLength = monthDays[month];
        }
        
        if (time >= monthLength) {
          time -= monthLength;
        } else {
            break;
        }
      }
      tm.Month = month + 1;  // jan is month 1  
      tm.Day = time + 1;     // day of month
    }
    

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

回复评论 (1)

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

 

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

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

玄铁工具链开源:
https://github.com/T-head-Semi/xuantie-gnu-toolchain?spm=a2cl5.25269445.0.0.5da5180fmPCxYb

点赞  2022-5-11 18:47
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复