首页 > 系统相关 >从STM32的定时器到Linux上的时间相关服务

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

时间:2024-09-09 11:49:34浏览次数:3  
标签:定时器 引脚 Linux timer jiffies STM32 time 时钟

难题:在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 %ds
	push %es
	push %fs
	pushl %edx
	pushl %ecx
	pushl %ebx
	pushl %eax
	movl $0x10,%eax
	mov %ax,%ds
	mov %ax,%es
	movl $0x17,%eax
	mov %ax,%fs
    # 递增jiffies
	incl jiffies
	movb $0x20,%al		
	outb %al,$0x20      
	movl CS(%esp),%eax
	andl $3,%eax	
	pushl %eax
	call do_timer		# 'do_timer(long CPL)' does everything from
	addl $4,%esp
	jmp ret_from_sys_call

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

  • 递增了jiffies
  • 调用了do_timer
void do_timer(long cpl)
{

    // 如果当前特权级(cpl)为-1,则将内核代码运行时间stime递增;
	if (cpl)
		current->utime++;
	else
		current->stime++;

	if (next_timer) { // 如果有定时器链表
		next_timer->jiffies--; // 定时器链表的jiffies递减
		while (next_timer && next_timer->jiffies <= 0) { // 如果当前定时器的jiffies已经为0
			void (*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. 应用程序线程上下文切换

未完...

标签:定时器,引脚,Linux,timer,jiffies,STM32,time,时钟
From: https://www.cnblogs.com/lilpig/p/18403977

相关文章

  • 深入探索嵌入式 Linux
    摘要:本文深入探究嵌入式Linux。首先回顾其发展历程,从早期尝试到克服诸多困难逐渐成熟。接着阐述其体系结构,涵盖硬件、内核、文件系统和应用层。开发环境方面包括交叉编译工具链、调试工具和集成开发环境。在应用领域,广泛应用于消费电子、工业控制、汽车电子和智能家居等领域。......
  • linux中unattended-upgr是什么
    linux中unattended-upgr是什么,如何关闭ldcaws于2024-06-1714:09:47发布阅读量1.2k收藏2点赞数4分类专栏:Linux文章标签:linux版权Linux专栏收录该内容25篇文章2订阅订阅专栏unattended-upgrades是Ubuntu操作系统自动更新软件包的一个进程。它默认开启,定期检查可......
  • linux安装python3(源代码安装)
    相关软件安装python3安装源代码安装下载python3源码包比如说python3.9.10:https://www.python.org/ftp/python/3.9.10/Python-3.9.10.tar.xz#通过wget下载wgethttps://www.python.org/ftp/python/3.9.10/Python-3.9.10.tar.xz#进行源代码包的解压(xz使用J解压,gz使用......
  • linux用户权限
    用户权限管理关于linux用户现代操作系统一般属于多用户的操作系统,也就是说,同一台机器可以为多个用户建立账户,一般这些用户都是为普通用户,这些普通用户能同时登录这台计算机,计算机对这些用户分配一定的资源。普通用户在所分配到的资源内进行各自的操作,相互之间不受影响。但是这......
  • linux命令补充
    linux命令补充加/解压命令tar命令:进行文件的压缩和文件的解压。对于不同的文件解压可以参考中文文档(linux中文手册)。重要参数:压缩包:-c(直接打包不会节省空间,使用压缩工具可以减少文件大小,即下面的-z,-j,-J命令)、解压包:-x、后面输入文件:-f、gz(gzip压缩文件):-z、bz(bzip压缩......
  • Linux系统与高效进程控制的实战技巧
    Linux系统与高效进程控制的实战技巧Linux,作为一种开源的Unix-like操作系统内核,自1991年由芬兰程序员LinusTorvalds首次发布以来,已成为全球范围内广泛使用的操作系统之一。其强大的功能、灵活的配置以及高度的可定制性,使得Linux在服务器、嵌入式系统、超级计算机等多个领域......
  • Linux 上自动下载 Docker 依赖并离线安装的完整指南
    Linux上自动下载Docker依赖并离线安装的完整指南这篇指南详细讲解了如何在CentOS7.9系统上,通过yum命令自动下载Docker的所有依赖包,并将其打包成tar文件以供离线安装。文中包括了添加Docker软件源、更新yum缓存、指定路径下载依赖包、打包rpm文件,以及最终在无网络环境下......
  • 一文讲清,常用通信协议IIC,SPI,串口,基于STM32
    目录一、通讯的基本概念1.串行通讯2.并行通讯3.传输模式(单工、半双工、全双工)二、常见通讯协议(串口、IIC、SPI)1.串口(1)UART和USART的区别是什么?(2)UART(TTL、RS232、RS485)(3)基于STM32的HAL库的串口配置2.IIC(1)物理层(2)协议层(3)软件模拟IIC通讯代码(4)有关IIC面试的问题(5)硬......
  • STM32常用数据采集滤波算法
    例如,STM32进行滤波处理时,主要目的是处理数据采集过程中可能产生的噪声和尖刺信号。这些噪声可能来自电源干扰、传感器自身的不稳定性或其他外部因素。1.一阶互补滤波方法:取a=0~1,本次滤波结果=(1-a)本次采样值+a上次滤波结果优点:对周期性干扰具有良好的抑制作用适用于波动频率......
  • linux手动安装nodejs
    1.下载nodejs的安装文件,要选择编译好的  预构建二进制文件 这样就不用自己再编译安装了 https://nodejs.org/zh-cn/download/prebuilt-binaries2.下载完成后使用如下命令解压缩文件1)使用xz-dnode-v18.20.4-linux-x64.tar.xz 解压缩得到node-v18.20.4-linux......