欢迎淘宝搜索飞灵科技,我司相关新产品陆续上线

实现过程

根据上面介绍,我们需要这样一个时钟来记录系统时间,即记录至1970-01-01以来的ns 数:

  1. 精度为ns级别。
  2. 位数足够长,不会发生回绕, 通常为64bits。
  3. 支持计数频率调整。

在硬件上,很难找到一个这样的Timer。 所以首先我们需要用软件虚拟一个Timer。然后周期性的校准这个虚拟Timer。

我们需要分成两个部分来实现这个目标:

  • 构造一个64bits的虚拟时钟。
  • 编写一个周期性的校准程序。

构造一个64bits的虚拟时钟。

为了构造一个64bits的虚拟时钟,我们需要一个处在循环计数模式(准确的讲,应该处在输入捕获模式,因为后面介绍的校准机制需要捕获功能)的硬件时钟。

我们构造以下结构来抽象一个硬件Timers。

struct cyclecounter {u64 (*read)(const struct cyclecounter *cc);u64 mask;u32 mult;u32 shift;
};@read: 读取硬件Timer 的cycle 值的回调函数。
@mask: 硬件Timer的位数。
@mult: 硬件Timer 的cycle 值转化为ns的multiplier。
@shift: 硬件Timer 的cycle 值转化为ns的divisor 。

使用上面的硬件Timer,我们来构造一个虚拟的64Bits时钟。

struct timecounter {const struct cyclecounter *cc;u64 cycle_last;u64 nsec;u64 mask;u64 frac;
};@cc: 硬件Timer的抽象实例
@cycle_last: 上一次读取的硬件Timer 的cycle值
@nsec: 至1970-01-01以来的ns 数
@mask: 不满1ns的小数部分的累加值的bit位掩码。
@frac: 不满1ns的小数部分的累加值。

除了上面的数据结构体之外,我们还需要一些辅助的API来运行这个Timer。

void timecounter_init(struct timecounter *tc, const struct cyclecounter *cc, u64 start_tstamp)
{tc->cc = cc;tc->cycle_last = cc->read(cc);tc->nsec = start_tstamp;tc->mask = (1ULL << cc->shift) - 1;tc->frac = 0;
}u64 timecounter_read(struct timecounter *tc)
{u64 nsec;u64 cycle_now, cycle_delta;/* read cycle counter: */cycle_now = tc->cc->read(tc->cc);cycle_delta = (cycle_now - tc->cycle_last) & tc->cc->mask;nsec= (cycle_delta * tc->cc->mult) + tc->frac;tc->frac = nsec & tc->mask;nsec = nsec >> tc->cc->shift;tc->cycle_last = cycle_now;nsec += tc->nsec;tc->nsec = nsec;return nsec;
}u64 timecounter_cyc2time(struct timecounter *tc, u64 cycle_tstamp)
{u64 delta = (cycle_tstamp - tc->cycle_last) & tc->cc->mask;u64 nsec = tc->nsec, frac = tc->frac;u64 ns;if (delta > tc->cc->mask / 2) {delta = (tc->cycle_last - cycle_tstamp) & tc->cc->mask;nsec -= ((delta * tc->cc->mult) - frac) >> tc->cc->shift;} else {nsec += ((delta * tc->cc->mult) + frac) >> tc->cc->shift;}return nsec;
}void timecounter_adjtime(struct timecounter *tc, s64 delta)
{tc->nsec += delta;
}@timecounter_init: 初始化或重置虚拟Timer。
@timecounter_read:读取虚拟Timer的ns值。并且更新虚拟Timer。
@timecounter_cyc2time: 将读到的一个硬件Timer的cycle 值转换为Timer的时间。在校准程序里需要此函数计算ppm。
@timecounter_adjtime: 调整虚拟Timer的ns数。

从timecounter_read 函数可以看出,我们在硬件Timer计数的一个周期内,必须调用一次这个函数,否则造成计数错误。

假如我们硬件Timer 时钟是100MHz的32bits时钟。则一个计数周期为 10ns * pow(2,32) / pow(10,9) = 42.94967296秒, 即在42.9秒内,应用软件必须调用timecounter_read 读取一次时间值。

在STM32上,仅仅有两个32Bits的timer, 在其他MCU上,大多是16bits的Timer。对于16bits的Timer,一个计数周期为 10ns * pow(2,16) / pow(10,9) = 0.00065536秒, 即0.65ms。 这个时间对CPU来说是太频繁了,并且容易丢失周期。

有两种方法解决这个问题,一种是减低Timer的时钟,例如使用1MHz,则计数周期变为65ms。但精度随之变低。

另一种是使用硬件Timer的overflow 中断。计数值回绕时产生中断。在中断函数里,我们可以调用timecounter_read函数来更新Timer。但在主程序在调用此函数中时,如果被overflow 中断,可能会造成更新错误。同理,timecounter_read 函数是非线程安全的。即在多任务环境中,需要加锁保护。 另一种方法是通过在中断函数里递增周期数变量来扩展一个硬件Timer的位数,代码如下:

u32 timer_overflow_num = 0;
void Timer_overflow_irq()
{timer_overflow_num++;
}struct cyclecounter {u64 (*read)(const struct cyclecounter *cc);u64 (*read_from_timer)(const struct cyclecounter *cc);u64 mask;u32 last_ overflow_num;u32 mult;u32 shift;
};@last_ overflow_num: 记录上一次读时timer_overflow_num 的值。
@read_from_timer: 从硬件Timer读取cycle 的回调函数。

现在我们拦截struct timecounter 从struct cyclecounter中的读取硬件Timer cycle的 read函数,并重实现如下:

u64 timer_read_with_overflow(const struct cyclecounter *cc)
{u32 overflow_num = timer_overflow_num;u64 cycle = cc->read_from_timer(cc);u32 overflow_num_again = timer_overflow_num;if (overflow_num != overflow_num_again) {cycle = cc->read_from_timer(cc);}cycle += ((overflow_num_again - cc->last_ overflow_num) & 0xFFFFFFFF - 1) * (cc->mask + 1);cc->last_ overflow_num = overflow_num_again;return cycle;
}

通过上面扩展,我们的硬件timer位数增加了32bits。

虽然我们扩展了硬件Timer的位数,但并不是说我的一个计数周期变长了,就可以长时间的不去调用timecounter_read 函数了。两个原因:

  • 如果两次读取的间隔很长,间隔值会在浮点转定点过程中,造成溢出从而产生错误。
  • 晶振在随着时间,温度等环境在缓慢变化,长时间不更新的话,造成误差累积。

我们根据具体使用case,实现了虚拟Timer后,再来构造一个统一的API接口给用户程序使用:

int time_adjfine(struct timecounter *p, long scaled_ppm);
int time_adjtime(struct timecounter *p, s64 delta);
int time_gettime(struct timecounter *p, struct timeval*ts);
int time_settime(struct timecounter *p, const struct timeval*ts);@time_adjfine: 微调Timer的multiplier/shift
@time_adjtime: 增加delta ns 到struct timecounter 中的ns值。
@time_gettime: 获取struct timecounter 中的ns值。
@time_settime: 重置struct timecounter 中的ns值。
int time_gettime(struct timecounter *p, struct timeval*ts)
{u64 ns;# Lock for Multiple Threadsns = timecounter_read(p);# Unock for Multiple Threadsns_to_timeval(ns,ts);return 0;
}int time_settime(struct timecounter *p, struct timeval*ts)
{u64 ns = timeval_to_ns(ts);# Lock for Multiple Threadstimecounter_init(p, &hardware_cc, ns);# Unock for Multiple Threadsreturn 0;
}int time_adjfine(struct timecounter *p, long scaled_ppm)
{s64 clkrate;clkrate = (s64)scaled_ppm * HARDWARE_CC_MULT_NUM;clkrate = clkrate / HARDWARE_CC_MULT_DEM;# Lock for Multiple Threadstimecounter_read(p);p->mult = HARDWARE_CC_MULT + clkrate;# Unock for Multiple Threadsreturn 0;
}int time_adjtime(struct timecounter *p, s64 delta)
{# Lock for Multiple Threadstimecounter_adjtime(p, delta);# Unock for Multiple Threadsreturn 0;
}

后续。。。

如何在MCU上通过ToD+PPS 获取同步时间(二)相关推荐

  1. mac 恢复模式获取密码_如何在Mac上为所有网站获取暗模式

    mac 恢复模式获取密码 Khamosh Pathak Khamosh Pathak Now that macOS Mojave has a dark mode, wouldn't it be nea ...

  2. linux 取文件字节数,如何在Linux上的C中获取文件中的字符数(而不是字节数)

    我想获取文件中的字符数.字符我的意思是"真正的"字符,而不是字节.假设我知道文件编码. 我尝试使用mbstowcs()但它不起作用,因为它使用系统区域设置(或使用setlocale ...

  3. element-ui中el-upload上传音频文件获取音频时间长度

    html代码: <el-uploadid="audioUpload"class="avatar-uploader":action="upLoad ...

  4. 获取当前日期的上一个月,获取当前时间

    // 获取当前日期的上一个月 export function getlastMonth(date) {const dattiem = GetDateTime();var arr = dattiem.s ...

  5. 如何在MCU中使用二进制库(动态库)

    关注+星标公众号,不错过精彩内容转自 | 工程师的废纸篓 作为一个嵌入式软件攻城狮,提起库首先会想到静态库和动态库.静态库一般以.a为后缀,动态库以.so为后缀(Win系统.DLL). 库类型 说明 ...

  6. 如何在linux上安装sqlite数据库

    如何在linux上安装sqlite数据库 一.下载 二.解压 三.配置(configure) 四.编译和安装 五.执行sqlite3程序 六.测试代码 一.下载 首先要先下载sqlite3源码包 链接 ...

  7. 在红帽Linux上安装samba服务,如何在linux上安装配置samba服务器

    如何在linux上安装配置samba服务器 更新时间:2019-10-29 22:40 最满意答案 1.首先需要登入安装了Linux系统的计算机,安装Samba.Fedora发行版一般使用yum工具安 ...

  8. 华泰股票交易接口如何获取实时数据和同步时间数据?

    下面直接分享华泰股票交易接口如何获取实时数据和同步时间数据? 首先.获取实时数据 python的函数库非常丰富,httplib具备获取API接口数据的功能. API返回参数是json格式的,可以用非标 ...

  9. mac java模拟器_iOS学习——如何在mac上获取开发使用的模拟器的资源以及模拟器中每个应用的应用沙盒...

    如题,本文主要研究如何在mac上获取开发使用的模拟器的资源以及模拟器中每个应用的应用沙盒.做过安卓开发的小伙伴肯定很方便就能像打开资源管理器一样查看我们写到手机本地或应用中的各种资源,但是在iOS开发 ...

最新文章

  1. linux的那些破事
  2. android 开发框架 怎么使用,Android快速开发框架dyh详解(二)---控件层的使用
  3. html选择字段至左边的列表,css – 如何将列表项显示为保留从左到右顺序的列?...
  4. EPSON机器人SPLE+语言_简单实例
  5. 抽象工厂模式:实现ASP.NET访问不同数据库
  6. 实现手机左右滑屏效果
  7. dedecms mysql 支持_安装dedecms MySQL 支持 不支持无法使用本系统 GD 支持Off解决办法...
  8. jquery字符串转数组
  9. 新增一个主键自增长_MyBatis 示例-主键回填
  10. 用反射简化 asp.net 报表的一点总结
  11. python异常处理_Python学习点滴04 - 学会异常处理(2)
  12. 用python编写一个求偶数阶乘的函数_一行Python代码写阶乘函数
  13. 随想录(文件系统的第一个用户程序shell)
  14. python 入门基础-如何学习Python,以及新手如何入门?
  15. 用Ultra ISO制作启动U盘装系统
  16. 小米手机解锁,root
  17. 代写品牌故事-品牌故事的结构
  18. 头脑极度开放:前额皮层大战杏仁核
  19. NICE的Verilog代码
  20. 面试题目:2个鸡蛋100层楼问题

热门文章

  1. NodeJs代码调试(inspector+chrome) 实现谷歌浏览器调试后台nodejs代码
  2. 经济管理 第2章 微观市场机制分析
  3. 猴子偷桃问题(C语言实现)
  4. 寻找双胞胎数 c语言,征求好的算法:输出十万以内的双胞胎素数
  5. .NET的未来-广州.NET俱乐部学生分会
  6. 用css设置文字的描边效果
  7. javaScript设计模式-创建型设计模式
  8. 面向初学者的 MQL4 语言系列之2——MQL4语言入门深入
  9. 2019全球人工智能产品应用博览会将于今年5月在苏州举办
  10. 基于stm32微控制器的绘图机器人设计