从STM32的定时器到Linux上的时间相关服务

news/2024/12/21 20:18:04/文章来源:https://www.cnblogs.com/lilpig/p/18403977

难题:在baremetal上实现按钮点击、长按事件

起因是因为我想用stm32加几个按钮只做一个我自己的控制器,我可以通过按钮执行一些功能。

硬件是如何和CPU通信的呢?CPU上支出几个GPIO引脚,这些引脚可以配置为输入模式和输出模式,并且都有两种状态——高电平和低电平。硬件连接这些引脚,你编写在CPU上运行的程序,去给这些引脚写电平值或从这些引脚中读电平值,就可以实现和硬件的交互。

对于按钮来说,我们假设它连接某一个被我们配置成输入模式的引脚,我们的程序不断的读这个引脚的电平,若为低电平,则认为按钮处于按下状态,若为高电平,则按钮处于放开状态,则我们得到了如下的伪代码:

bool is_pressed() {return readbit(button_pin) == 0;
}void main() {while (1) {if (is_pressed()) {// do something...}}
}

但是我们要的是点击事件和长按事件,代码中的按下,是一个持续的事件,用户不松手就一直是按下。而点击和长按事件是一个瞬时触发的事件,用户点击、松开,我们需要判断这中间的间隔时间长度,若大于多少,则认为是长按,否则是点击。于是我们有了下面的伪代码:

// 最后一次按下时间
uint64_t last_pressed_time = NULL;void main() {while (1) {// 如果按下,且最后一次按下时间没设置if (is_pressed() && last_pressed_time == NULL) {// 最后一次按下时间 = 当前时间last_pressed_time = get_time();} else {// 计算当前时间和最后一次按下时间的差值uint64_t diff = get_time() - last_pressed_time;if (diff > LONG_CLK_SPAN) {// 长按} else {// 点击}last_pressed_time = NULL;}}
}

注意,我们上面的伪代码为了清晰忽略了模块化和很多细节,比如物理硬件中的电平抖动,这并不是重点。

在上面的代码中,核心就是get_time,它是一个时间值,无论是什么样的时间,只要它具有以下性质:

  1. 随物理时间单调增长:顺序的两次调用get_time,后一次一定大于等于前一次
  2. 基本均匀:即若我们多次调用get_time,每次之间隔了x秒,对于每一个返回值依次和前一次调用得到的时间相减,得到一组y,代表每两次调用之间的时间差,每一个y都不会相差太大,最起码是可被程序参考的。

熟悉了写运行在操作系统上的程序的同学们会想,这有什么值得思考的?我用Java的System.currentTimeMillis,我用Linux的time()都能获取到时间,但是你忽略了那些是平台和OS给你提供了服务,在baremetal上,你什么都没有,你要自己考虑如何提供这样一个服务。

墙上时钟和单调时钟

软件世界的时钟分为两种:

  • 墙上时钟:顾名思义,即和现实世界时间有关的时钟。比如linux的time、java的System.currentTimeMillis。其特性是可回拨,如果你在程序中第一次调用这些功能和第二次之间将系统时钟回拨,则可能出现第二次获得的时间在第一次之前的情况。所以严格来说它不适合我们说的按钮点击事件的实现。
  • 单调时钟:一般是系统启动开始到现在的一个逻辑时间值,和物理世界无关,不会回拨。也是本篇讨论的重点。

baremetal上如何实现时钟(stm32f103c8t6)

构建时钟树

时钟源:硬件时钟/晶振

时钟源通常是一个可以以固定频率震荡的硬件,也就可以以固定频率生成数字脉冲信号发给下游系统。在STM32中,有四个时钟源:

  • HSI:内部高速时钟(8MHz),不稳定
  • HSE:外部高速时钟,外部晶振电路提供
  • LSI:内部低速时钟(40KHz)
  • LSE:外部低速时钟

时钟源为整个系统提供计时功能,包括我们刚刚提到的需要时间服务的软件、各种需要以周期性频率协同步调的硬件等。

倍频器/分频器

时钟源是固定频率的,而不同的使用场景可能需要不同的频率,此时,倍频器/分频器电路可以做到将原始频率乘以一个系数或除以一个系数,再分给下游。

定时器电路

定时器电路被设计成这样:上游提供的时钟脉冲(源自于时钟源,经过多次倍频/分频)发生多少次(装载值),发送一次时钟中断给CPU。

定时器通常具有可配置的分频器和可配置的装载值,这让我们可以通过软件灵活控制我们接到时钟中断的频率。

假设上游提供的时钟脉冲频率是10KHz,则你可以配置定时器的装载值为9999,此时每当脉冲发生一次,定时器的装载值-1,最后当它变成0,发送时钟中断给CPU。此时,我们的中断函数就会在每1ms被CPU调用。

img

图片来自b站keysking,需要详细了解这其中的硬件细节的可以去看它的视频。

构建逻辑时钟

// clock.c
uint64_t __LOGIC_TIME = 0;void init_logic_clock() {// 初始化timer电路,配置成1ms一次中断
}// 假设这个是我们的时钟中断函数
// 1ms会被调用一次
void tim_irqhandler() {__LOGIC_TIME++;
}uint64_t get_time() {return __LOGIC_TIME;
}

Linux时间相关服务

通过单片机对硬件实现时钟服务有一个基本了解之后,我们就又有了疑问。对于Linux这样的通用系统,它是如何利用硬件时钟的,又是向应用提供了怎样的服务?

调度器和jiffies

jiffies和我们刚刚的__LOGIC_TIME差不多,其作用是记录系统启动以来发生的时钟中断次数,也是一个逻辑时钟。

在Linux中,可以使用如下指令查看配置的时钟中断频率:

~ -> cat /boot/config-xxxx  | grep 'CONFIG_HZ='
CONFIG_HZ=1000

在linux2.6开始被设置为1000,之前都是100。

内核代码分析

在Linux0.11内核代码的kernel/system_call.s中,使用汇编语言定义了时钟中断的处理函数:

.align 2
timer_interrupt:push %dspush %espush %fspushl %edxpushl %ecxpushl %ebxpushl %eaxmovl $0x10,%eaxmov %ax,%dsmov %ax,%esmovl $0x17,%eaxmov %ax,%fs# 递增jiffiesincl jiffiesmovb $0x20,%al		outb %al,$0x20      movl CS(%esp),%eaxandl $3,%eax	pushl %eaxcall do_timer		# 'do_timer(long CPL)' does everything fromaddl $4,%espjmp ret_from_sys_call

我们可以在此处看到一些关键信息:

  • 递增了jiffies
  • 调用了do_timer
void do_timer(long cpl)
{// 如果当前特权级(cpl)为-1,则将内核代码运行时间stime递增;if (cpl)current->utime++;elsecurrent->stime++;if (next_timer) { // 如果有定时器链表next_timer->jiffies--; // 定时器链表的jiffies递减while (next_timer && next_timer->jiffies <= 0) { // 如果当前定时器的jiffies已经为0void (*fn)(void);fn = next_timer->fn;next_timer->fn = NULL;next_timer = next_timer->next;(fn)(); // 调用定时器函数}}if (current_DOR & 0xf0)do_floppy_timer();if ((--current->counter)>0) return;current->counter=0;if (!cpl) return;// 执行调度schedule();
}

从上面的代码中,我们可以看到linux中维护了一个定时器功能,它将全部的定时器组装成为一个链表,定时器的jiffies属性代表多少个时钟中断后它将执行。

在时钟中断的C语言代码最后,执行了schedule函数,它是Linux进行线程调度的核心函数,即执行线程的上下文切换,用于实现并发执行。

此处的定时器供内核内部类似在未来某个时间点执行的任务或驱动程序定时轮询等使用,不给应用层使用。

总结,linux使用时钟中断进行:

  1. jiffies的更新
  2. 内核内部定时器函数调度
  3. 应用程序线程上下文切换

未完...

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/794395.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

自定义界面扫码,满足应用个性化定制需求

二维码识别技术已经成为我们日常生活中不可或缺的一部分,广泛应用于支付、交通、餐饮、生活服务以及智能家居等领域。它不仅是移动应用的重要流量入口,更是连接线上线下世界的桥梁。 不同的App在扫码界面的设计上各展其特色,从页面元素到交互方式,都体现了开发者对用户体验…

zabbix 打通LDAP登录

` dn=dc=localdomain,dc=com 搜索属性=uid 绑定DN=cn=admin,dc=localdomain,dc=com`

tarjan—算法的神(一)

本篇包含 tarjan 求强连通分量、边双连通分量、割点 部分, tarjan 求点双连通分量、桥(割边)在下一篇。伟大的 Robert Tarjan 创造了众多被人们所熟知的算法及数据结构,最著名的如:(本文的)连通性相关的 tarjan 算法,Splay-Tree,Toptree,tarjan 求 lca 等等。 注:有…

gitlab代码恢复

gitlab代码恢复背景: 从gitlab的web端页面下载了代码。需要恢复到新的gitlab上。下面操作以vonebaas-c-fabric-explorer-front工程为例 已经下载好vonebaas-c-fabric-explorer-front工程的zip包:操作步骤 原来的git地址是:http://xxx.git.com:9002/ 新的git地址是:http://1…

Vidful.ai:免费在线AI视频生成器

免费在线AI视频生成器Vidful.ai,无需下载或复杂操作,轻松将文字和图片转换为高质量、电影级视频!免费在线AI视频生成器Vidful.ai,无需下载或复杂操作,轻松将文字和图片转换为高质量、电影级视频! 网址:https://vidful.ai/ 为什么选择Vidful.ai?免费在线平台:无需下载、…

“数据守护,商业共赢” — 华企盾招商会议圆满落幕

在数字化浪潮席卷全球的今天,数据安全已成为企业可持续发展的基石。为了共同探讨数据防护的新策略,推动行业生态的健康发展,我司于2024年9月6日成功举办了一场以“数据守护,商业共赢”为主题的招商会议。此次会议汇聚了来自各行各业的精英人士,共同探讨如何有效应对数据安…

【优技教育】Oracle 19c OCP 082题库(第16题)- 2024年修正版

【优技教育】Oracle 19c OCP 082题库(Q 16题)- 2024年修正版 考试科目:1Z0-082 考试题量:90 通过分数:60% 考试时间:150min 本文为(CUUG 原创)整理并解析,转发请注明出处,禁止抄袭及未经注明出处的转载。 原文地址:http://www.cuug.com/index.php?s=/home/article/deta…

Canvas绘制图片合成样式

效果图web * {margin: 0;padding: 0;}.container {position: relative;width: 328px;height: 328px;margin: 100px auto;}.container img {position: absolute;width: 328px;height: 328px;}#canvas {position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);…

Axure的画图规范说明

Axure的画图规范说明自定义网页整体尺寸:1366x768;色值:# F8F9FD 顶部一级导航:1366x48;色值:# 1D8FFF 左右布局,左侧边导航:768x194;色值:# 191A23 侧边导航栏宽高:194x768 左侧一级菜单宽高:194x40/边距:16,字号:14,色值:#FFFFFF,图标:14x14,二级导航宽194w,…

解决vscode终端输出中文乱码问题图文教程

由于系统终端默认编码为GBK,所以需要修改为UTF-8 方法一 打开cmd输入chcp查看编码格式,查看以及修改如下图所示:方法二