八、(正点原子)Linux内核定时器实验

        定时器是我们最常用到的功能,一般用来完成定时功能,本章我们就来学习一下 Linux 内核提供的定时器 API 函数,通过这些定时器 API 函数我们可以完成很多要求定时的应用。 Linux内核也提供了短延时函数,比如微秒、纳秒、毫秒延时函数,本章我们就来学习一下这些和时间有关的功能。

一、Linux时间管理和内核定时器介绍

        1、内核时间管理

        在FreeRTOS或者UCOS中需要一个硬件定时器提供系统时钟,一般使用Systick作为系统的时钟源。同理, Linux 要运行,也是需要一个系统时钟,Linux内核会选择Cortex-A7内核中的一个通用定时器来作为Linux系统的时钟源,对于Linux驱动编写来说,我们不需要深入研究怎么选择时钟源,怎么配置时钟源,Linux内核编写者已经编写好了,我们只需要会使用相应的API函数即可。

        Linux 内核中有大量的函数需要时间管理,比如周期性的调度程序、延时程序、对于我们驱动编写者来说最常用的定时器。硬件定时器提供时钟源,时钟源的频率可以设置, 设置好以后就周期性的产生定时中断,系统使用定时中断来计时。中断周期性产生的频率就是系统频率,也叫做节拍率(tick rate)(有的资料也叫系统频率),比如 1000Hz, 100Hz 等等说的就是系统节拍率。系统节拍率是可以设置的,单位是 Hz,我们在编译 Linux 内核的时候可以通过图形化界面设置系统节拍率,按照如下路径打开配置界面:

> Kernel Features-> Timer frequency (<choice> [=y])

         Linux 内核会使用 CONFIG_HZ 来设置自己的系统时钟。打开文件 include/asm-generic/param.h,有如下内容:

        HZ:表示1s时间内进入中断的次数,也叫做节拍数。通常说为节拍率。

        选择高节拍率和低节拍率的优缺点 :

        ①、高节拍率会提高系统时间精度,如果采用 100Hz 的节拍率,时间精度就是 10ms,采用
1000Hz 的话时间精度就是 1ms,精度提高了 10 倍。高精度时钟的好处有很多,对于那些对时间要求严格的函数来说,能够以更高的精度运行,时间测量也更加准确。

        ②、高节拍率会导致中断的产生更加频繁,频繁的中断会加剧系统的负担, 1000Hz 和100Hz的系统节拍率相比,系统要花费 10 倍的“精力”去处理中断。中断服务函数占用处理器的时间增加,但是现在的处理器性能都很强大,所以采用 1000Hz 的系统节拍率并不会增加太大的负载压力。根据自己的实际情况,选择合适的系统节拍率,我们全部采用默认的 100Hz 系统节拍率。
        Linux内核使用全局变量jiffies来记录系统从启动以来的系统节拍数,系统启动会将jiffies初始化为0,当然jiffies很重要,这也是我们设置定时器定时时间就是使用jiffies来完成的。jiffies 定义在文件 include/linux/jiffies.h 中,定义如下:

        jiffies_64jiffies是同一个东西, jiffies_64用于64位系统,jiffies用于32位系统,为了兼容不同的硬件,其实jiffies就是jiffies_64的低32位。

        前面说了 HZ 表示每秒的节拍数, jiffies 表示系统运行的节拍数,所以 jiffies/HZ 就是系统运行时间,单位为秒。不管是 32 位还是 64 位的 jiffies,都有溢出的风险,溢出以后会重新从 0 开始计数,相当于绕回来了,因此有些资料也将这个现象也叫做绕回。假如 HZ 为最大值 1000 的时候, 32 位的 jiffies 只需要 49.7 天就发生了绕回,对于 64 位的 jiffies 来说大概需要5.8 亿年才能绕回,因此 jiffies_64 的绕回忽略不计。处理 32 位 jiffies 的绕回显得尤为重要,Linux 内核提供了几个 API 函数来处理绕回,如下如:

函数描述(unkown为jiffies,known通常是对比的值)
time_after(unkown, known) unkown 超过 known 的话, time_after 函数返回真,否则返回假。
time_before(unkown, known)如果 unkown 没有超过 known 的话 time_before 函数返回真,否则返回假
time_after_eq(unkown, known)time_after_eq 函数和 time_after 函数一样,多了判断等于这个条件。
time_before_eq(unkown, known)time_before_eq 函数和 time_before 函数也类似,多了判断等于这个条件。
 

         比如我们要判断有没有超时,可以使用如下代码:

unsigned long timeout;
timeout = jiffies + (2 * HZ); /* 超时的时间点 *//* 判断有没有超时 */
if(time_before(jiffies, timeout)) {/* 超时未发生 */
} else {/* 超时发生 */
}

         Linux内核提供了几个jiffies和ms、us、ns之间的转换函数:

函数描述
int jiffies_to_msecs(const unsigned long j)jiffies转换成ms
int jiffies_to_usecs(const unsigned long j)jiffies转换成us
u64 jiffies_to_nsecs(const unsigned long j)jiffies转换成ns
long msecs_to_jiffies(const unsigned int m)ms转换成jiffies
long usecs_to_jiffies(const unsigned int u)us转换成jiffies
unsigned long nsecs_to_jiffies(u64 n)ns转换成jiffies

      2、内核定时器

        定时器是一个很常用的功能,需要周期性处理的工作都要用到定时器。 Linux 内核定时器
采用系统时钟来实现,并不是6ull里面的硬件定时器。 Linux 内核定时器使用很简单,只需要提供超时时间(相当于定时值)和定时处理函数即可,当超时时间到了以后设置的定时处理函数就会执行,和我们使用硬件定时器的套路一样,只是使用内核定时器不需要做一大堆的寄存器初始化工作。在使用内核定时器的时候要注意一点,内核定时器并不是周期性运行的,超时以后就会自动关闭,因此如果想要实现周期性定时,那么就需要在定时处理函数中重新开启定时器。
        Linux 内核使用 timer_list 结构体表示内核定时器, timer_list 定义在文件include/linux/timer.h 中,定义如下:

struct timer_list {/** All fields that change during normal runtime grouped to the* same cacheline*/struct list_head entry;unsigned long expires;                 /* 定时器超时时间,单位是节拍数 */struct tvec_base *base;void (*function)(unsigned long);       /* 定时处理函数 */unsigned long data;                    /* 要传递给 function 函数的参数 */int slack;#ifdef CONFIG_TIMER_STATSint start_pid;void *start_site;char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEPstruct lockdep_map lockdep_map;
#endif
};

         要使用内核定时器首先要先定义一个 timer_list 变量,表示定时器, tiemr_list 结构体的
expires 成员变量表示超时时间,单位为节拍数。比如我们现在需要定义一个周期为 2 秒的定时
器,那么这个定时器的超时时间就是 jiffies+msecs_to_jiffies(2000)function 就是定时器超时以后的定时处理函数,当定时时间到了以后,就会跳转到function执行。

        定义好定时器后,还需要API函数(定义在linux/timer.h)来初始化定时器:

        ①、init_timer函数

        init_timer 函数负责初始化 timer_list 类型变量,函数原型:

#define init_timer(timer)						\__init_timer((timer), 0)#define __init_timer(_timer, _flags)					\init_timer_key((_timer), (_flags), NULL, NULL)void init_timer_key(struct timer_list *timer, unsigned int flags,const char *name, struct lock_class_key *key)

        timer:要初始化的定时器。

        ②、add_timer函数

         用于向 Linux 内核注册定时器,使用 add_timer 函数向内核注册定时器以后,定时器就会开始运行,函数原型如下:

extern void add_timer(struct timer_list *timer);

         timer:要初始化的定时器。

        ③、del_timer函数

        用于删除一个定时器,不管定时器有没有被激活,都可以使用此函数删除。在多处理器系统上,定时器可能会在其他的处理器上运行,因此在调用 del_timer 函数删除定时器之前要先等待其他处理器的定时处理器函数退出,函数原型:

extern int del_timer(struct timer_list * timer);

         timer:要初始化的定时器。

        返回值:0,定时器没被激活,1,定时已经激活。

        ④、del_timer_sync函数

        函数是 del_timer 函数的同步版,会等待其他处理器使用完定时器再删除,del_timer_sync 不能使用在中断上下文中。函数原型:

extern int try_to_del_timer_sync(struct timer_list *timer);

         timer:要初始化的定时器。

        返回值:0,定时器没被激活,1,定时已经激活。

        ⑤、mod_timer函数

        用于修改定时值,如果定时器还没有激活的话, mod_timer 函数会激活定时器!函数原型如下:

extern int mod_timer(struct timer_list *timer, unsigned long expires);

        timer:要修改超时时间的定时器。

        expires:修改后的超时时间。

        返回值:0,调用 mod_timer 函数前定时器未被激活; 1,调用 mod_timer 函数前定时器已被激活。

        3、内核定时器的使用流程

struct timer_list timer; /* 定义定时器 *//* 定时器回调函数 */
void function(unsigned long arg)
{/** 定时器处理代码*//* 如果需要定时器周期性运行的话就使用 mod_timer* 函数重新设置超时值并且启动定时器。*/mod_timer(&dev->timertest, jiffies + msecs_to_jiffies(2000));    /* 修改超时时间为2s */
}/* 初始化函数 */
void init(void)
{init_timer(&timer); /* 初始化定时器 */timer.function = function; /* 设置定时处理函数 */timer.expires=jffies + msecs_to_jiffies(2000);/* 超时时间 2 秒 */timer.data = (unsigned long)&dev; /* 将设备结构体作为参数 */add_timer(&timer); /* 启动定时器 */
}/* 退出函数 */void exit(void)
{del_timer(&timer); /* 删除定时器 *//* 或者使用 */del_timer_sync(&timer);
}

4、Linux内核短延时函数

        有时候我们需要在内核中实现短延时,尤其是在 Linux 驱动中。 Linux 内核提供了毫秒、微秒和纳秒延时函数,如表:

函数描述
void ndelay(unsigned long nsecs)ms、us、ns延时函数
void udelay(unsigned long usecs)
void mdelay(unsigned long mseces)

二、unlocked_ioctl和compat_ioctl

        1、简介

        unlocked_ioctl\compat_ioctlfile_operation 结构体中的两个函数,一、(正点原子)字符设备驱动_正点原子字符驱动-CSDN博客这里有介绍

        unlocked_ioctl函数提供对于设备的控制功能,与应用程序中的 ioctl 函数对应。简单点来说,当用户空间应用程序调用 ioctl函数向驱动发送控制信息,驱动程序会执行unlocked_ioctl这个函数

long (*unlocked_ioctl) (struct file *filep, unsigned int cmd, unsigned long arg);

         filep:设备文件名。

        cmd:应用程序发送过来的命令信息。后面我们会仔细说一下这个CMD命令如何创建。

        arg:应用程序发过来的参数。

         compat_ioctl函数的功能与unlocked_ioctl函数一样,区别在于64 位系统上,32 位的应用程序调用将会使用此函数。在 32 位的系统上运行 32 位的应用程序调用的是unlocked_ioctl

long (*compat_ioctl) (struct file *filep, unsigned int cmd, unsigned long arg);

     2、ioctl函数CMD命令

        在linux内核中有帮助手册:linux/Documentation/ioctl/ioctl-decoding.txt这个文档中有介绍CMD这个命令:

        这个CMD是一个32位的。31~30位是方向位,_IOR是向驱序读,_IOW是向驱动写。29~16位是用户空间向内核空间传输控制信息的数据大小,15~8位表示类型,驱动的标识位,一个特殊字符(ASCII)代表不同的一个驱动。7~0位就是不同的控制功能。

        参考:ioctl函数详解(参数详解,驱动unlocked_ioctl使用、命令码如何封装)-CSDN博客

三、Linux定时器实验编写

        1、驱动程序编写

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/ide.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/errno.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/delay.h>
#include <linux/semaphore.h>
#include <asm/ioctls.h>/* 设备名称和个数 */
#define TIMER_CNT           1
#define TIMER_NAME          "timer"/* 命令宏 */
#define OPEN_CMD            _IO('E', 1)
#define CLOSE_CMD           _IO('E', 2)
#define SET_PERIOD_CMD      _IOW('E', 3, int)    /* timer结构体 */
typedef struct timer_dev {dev_t devid;                        /* 设备号 */int major;                          /* 主设备号 */int minor;                          /* 次设备号 */struct cdev dev;                    /* 设备 */struct class *class;                /* 类 */struct device *device;              /* 类的设备 */struct device_node  *nd;             /* 设备树节点 */int led_gpio;                       /* LED的GPIO编号 */struct timer_list timer;            /* 定时器 */int timerperiod;                    /* 定时器周期 */spinlock_t lock;                    /* 自旋锁 */}timer_dev;
timer_dev timer;static int timer_open (struct inode *inode, struct file *filep)
{filep->private_data = &timer;       /* 设置私有数据 */timer.timerperiod = 500;           /* 设置定时时间为1s */return 0;
}static long timer_unlocked_ioctlioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{int ret = 0;timer_dev *dev = filep->private_data;    /* 获取私有数据 */unsigned int timerperod = 0;unsigned long flag = 0;unsigned long value = 0;switch (cmd) {case OPEN_CMD:         /*  打开定时器 */spin_lock_irqsave(&dev->lock,flag);        /* 自锁 */timerperod = dev->timerperiod;spin_unlock_irqrestore(&dev->lock,flag);   /* 解锁 */mod_timer(&dev->timer,jiffies + msecs_to_jiffies(timerperod));break;case CLOSE_CMD:         /* 关闭定时器 */del_timer(&dev->timer);break;case SET_PERIOD_CMD:     /*  修改定时器的周期  */ret = copy_from_user(&value, (int *)arg, sizeof(int));if(ret < 0) {return -EFAULT;}spin_lock_irqsave(&dev->lock,flag);dev->timerperiod = value;spin_unlock_irqrestore(&dev->lock,flag);mod_timer(&dev->timer,jiffies + msecs_to_jiffies(value));break;}return 0;}/* 设备文件操作集合 */
const struct file_operations timer_opts = {.owner = THIS_MODULE,.open = timer_open,.unlocked_ioctl = timer_unlocked_ioctlioctl,
};/* LED灯初始化 */
int led_init(timer_dev * ptimer)
{int ret = 0;timer_dev *dev = ptimer;/* 获取LED节点和信息 */dev->nd = of_find_node_by_path("/gpioled");/* 得到GPIO的编号 */dev->led_gpio = of_get_named_gpio(dev->nd, "led-gpio", 0);if(dev->led_gpio < 0) {ret = -EINVAL;printk("fail get gpio\r\n");goto fail_getgpio;}/* 申请GPIO */ret = gpio_request(dev->led_gpio, "led_gpio");if(ret) {printk("fail gpio request\r\n");ret = -EBUSY;goto fail_request;}/* 设置GPIO输入输出 */ret = gpio_direction_output(dev->led_gpio, 1);           /* 输出模式 给1关灯,默认关灯 */if(ret){printk("fail gpio set output\r\n");ret = -EBUSY;goto fail_setout;}return 0;
fail_setout:gpio_free(dev->led_gpio);
fail_request:
fail_getgpio:return ret;
}/* 定时器定时时间到回调函数 */
void  timer_timerout (unsigned long arg)
{static int status = 1;unsigned long flags;int timerperiod  = 0;timer_dev *dev = (timer_dev *)arg;/* 设置LED灯电平 */status = !status;gpio_set_value(dev->led_gpio,status);spin_lock_irqsave(&dev->lock,flags);                    /* 自锁 */timerperiod = dev->timerperiod;spin_unlock_irqrestore(&dev->lock, flags);               /* 解锁 */mod_timer(&dev->timer, jiffies + msecs_to_jiffies(timerperiod));
}/* 入口函数 */
static int __init timer_init(void)
{int ret = 0;/* 初始化自旋锁 */spin_lock_init(&timer.lock);timer.timerperiod = 500;/* 注册设备号 */timer.major = 0;if(timer.major) {       /* 指定设备号 */timer.devid = MKDEV(timer.major,0);ret = register_chrdev_region(timer.devid, TIMER_CNT, TIMER_NAME);}else {                 /* 没有指定设备号 */ret = alloc_chrdev_region(&timer.devid, 0, TIMER_CNT, TIMER_NAME);timer.major = MAJOR(timer.devid);timer.minor = MINOR(timer.devid);}if(ret < 0) {printk("fail devid\r\n");goto fail_devid;}printk("major = %d,minor = %d\r\n",timer.major,timer.minor);            /* 打印设备号 *//* 注册设备 */timer.dev.owner = THIS_MODULE;cdev_init(&timer.dev, &timer_opts);ret = cdev_add(&timer.dev, timer.devid, TIMER_CNT);if(ret < 0) {printk("fail dev\r\n");goto fail_dev;}/* 自动创建节点信息 */timer.class = class_create(THIS_MODULE, TIMER_NAME);if(IS_ERR(timer.class)) {ret = PTR_ERR(timer.class);printk("fail class\r\n");goto fail_class;}timer.device = device_create(timer.class, NULL, timer.devid, NULL, TIMER_NAME);if(IS_ERR(timer.device)) {ret = PTR_ERR(timer.device);printk("fail device\r\n");goto fail_device;}/* 初始化LED灯 */ret = led_init(&timer);if(ret < 0) {printk("fail led init\r\n");goto fail_led_init;}/* 初始化定时器 */init_timer(&timer.timer);timer.timer.data = (unsigned long) &timer;timer.timer.function = timer_timerout;mod_timer(&timer.timer, jiffies + msecs_to_jiffies(timer.timerperiod));return 0;fail_led_init:
fail_device:class_destroy(timer.class);
fail_class:cdev_del(&timer.dev);
fail_dev:unregister_chrdev_region(timer.devid, TIMER_CNT);
fail_devid:return ret;
}/* 出口函数 */
static void __exit timer_exit(void)
{/* 关灯 */gpio_set_value(timer.led_gpio, 1);/* 删除定时器 */del_timer_sync(&timer.timer);/* 注销GPIO */gpio_free(timer.led_gpio);/* 删除类的设备 */device_destroy(timer.class, timer.devid);/* 删除类 */class_destroy(timer.class);/* 删除设备 */cdev_del(&timer.dev);/* 删除设备号 */unregister_chrdev_region(timer.devid, TIMER_CNT);printk("timer exit\r\n");
}/* 注册入口和出口函数 */
module_init(timer_init);
module_exit(timer_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZhangXueGuo");

        2、应用程序编写 

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>/* 命令宏 */
#define OPEN_CMD            _IO('E', 1)
#define CLOSE_CMD           _IO('E', 2)
#define SET_PERIOD_CMD      _IOW('E', 3, int)  /**  main主程序 *  argc:argv数字个数,一般指传递给函数的参数数量*  argv:具体的参数内容,一般都是字符串格式*  return:0表示成功* 
*/
int main(int argc, char *argv[])
{int fd,ret;char *FileName;int cmd,arg;unsigned char str[100];/* 判断使用命令参数是否正确 */if(argc != 2){printf("命令使用错误!\r\n");ret = -1;goto fail_open;}/* 打开程序 */FileName = argv[1];fd = open(FileName,O_RDWR);if(fd < 0){printf("应用程序打开设备文件失败!\r\n");ret = fd;goto fail_open;}while(1) {printf("Please input CMD:");ret = scanf("%d",&cmd);if(ret != 1) {gets(str);}if(cmd == 1) {ret = ioctl(fd, OPEN_CMD, &arg);} else if(cmd == 2) {ret = ioctl(fd, CLOSE_CMD, &arg);} else if(cmd == 3) {printf("Input period:");ret = scanf("%d",&arg);if(ret != 1) { gets(str);}ret = ioctl(fd, SET_PERIOD_CMD, &arg);}}/* 关闭文件 */close(fd);return 0;fail_open:return ret;
}

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

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

相关文章

【Linux基础】SSH登录

SSH简介 安全外壳协议&#xff08;Secure Shell Protocol&#xff0c;简称SSH&#xff09;是一种加密的网络传输协议&#xff0c;可在不安全的网络中为网络服务提供安全的传输环境。 SSH通过在网络中建立安全隧道来实现SSH客户端与服务器之间的连接。 SSH最常见的用途是远程登…

LeetCode 算法:二叉树的最大深度 c++

原题链接&#x1f517;&#xff1a;二叉树的最大深度 难度&#xff1a;简单⭐️ 题目 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,…

【高考选专业 | 家长篇】2024,计算机何去何从?小P老师带你看

目录 2024年&#xff0c;计算机相关专业还值得选择吗&#xff1f;1.行业竞争现状2.专业前景分析 2024年&#xff0c;计算机相关专业还值得选择吗&#xff1f; 随着2024年高考落幕&#xff0c;数百万高三学生又将面临人生中的重要抉择&#xff1a;选择大学专业。有人欢喜&#x…

操作系统真象还原:用户进程

第11章-用户进程 这是一个网站有所有小节的代码实现&#xff0c;同时也包含了Bochs等文件 11.1 为什么要有任务状态TSS Linux 任务切换未采用 Intel 的做法&#xff0c;而是用了一套自己的方法&#xff0c;只是用了 TSS 的一小部分功能。 操作系统最直接控制的就是 CPU&…

ubuntu22.04笔记: 更换为阿里源

没有按照LTS 版本 会遇到下面问题&#xff1a; 参考&#xff1a;https://zhuanlan.zhihu.com/p/691625646 Ubuntu 22.04代号为&#xff1a;jammy Ubuntu 20.04代号为&#xff1a;focal Ubuntu 19.04代号为&#xff1a;disco Ubuntu 18.04代号为&#xff1a;bionic Ubuntu …

对于C++ 程序员来说,35岁魔咒是否存在?

大家常说程序员职业生涯会在35岁左右遇到所谓的“35岁魔咒”。这意味着在这个年龄段&#xff0c;程序员可能会面临就业不稳定或职业发展的挑战。对于C程序员来说&#xff0c;这个问题更加引人关注。 随着时间的推移&#xff0c;技术行业不断演进&#xff0c;新的编程语言层出不…

vue上传文件拿到File,下载文件到本地

vue中使用upload组件上传pdf文件&#xff0c;拿到File内容后&#xff0c;下载pdf文件到本地vue中根据url下载pdf文件到本地 File文件内容的格式 注意&#xff1a;如果使用iview的upload组件上&#xff0c;要获取File文件&#xff0c;需要在before-upload钩子上获取 async down…

Ubuntu iso 镜像下载 步骤截图说明

Ubuntu镜像下载&#xff0c;在这个网址&#xff1a; Enterprise Open Source and Linux | Ubuntu 步骤如下图所示&#xff1a; 1、登入网址 2、点击Get Ubuntu 3、点击Download Ubuntu Desktop 后续点击Downloadload 24.04 LTS直接下载就行 如果需要下载其它版本&#xf…

STM32学习之一:什么是STM32

目录 1.什么是STM32 2.STM32命名规则 3.STM32外设资源 4. STM32的系统架构 5. 从0到1搭建一个STM32工程 学习stm32已经很久了&#xff0c;因为种种原因&#xff0c;也有很久一段时间没接触过stm32了。等我捡起来的时候&#xff0c;发现很多都已经忘记了&#xff0c;重新捡…

【驱动篇】龙芯LS2K0300之按键驱动

实验过程 实验目的&#xff1a; 在龙芯开发板上面验证GPIO按键的输入过程 ① 根据原理图连接按键板 ② 将4个i2c引脚的功能复用为GPIO ③ 注册input设备驱动&#xff0c;绑定中断处理函数&#xff0c;使用定时器消抖 原理图 4个按键引脚&#xff1a;CPU_I2C0_SCL -> G…

艺体培训机构管理系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;教师管理&#xff0c;学员管理&#xff0c;活动管理&#xff0c;课程管理&#xff0c;选课信息管理 前台账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;论…

【会议征稿,ACM出版】2024年图像处理、智能控制与计算机工程国际学术会议(IPICE 2024,8月9-11)

2024年图像处理、智能控制与计算机工程国际学术会议&#xff08;IPICE 2024&#xff09;将于2024年8月9-11日在中国福州举行。本届会议由阳光学院、福建省空间信息感知与智能处理重点实验室、空间数据挖掘与应用福建省高校工程研究中心联合主办。 会议主要围绕图像处理、智能控…

分布式定时任务系列10:XXL-job源码分析之路由策略

传送门 分布式定时任务系列1&#xff1a;XXL-job安装 分布式定时任务系列2&#xff1a;XXL-job使用 分布式定时任务系列3&#xff1a;任务执行引擎设计 分布式定时任务系列4&#xff1a;任务执行引擎设计续 分布式定时任务系列5&#xff1a;XXL-job中blockingQueue的应用 …

Go语言的诞生背景

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

Linux操作系统处理器调度基本准则和实现

1&#xff0c;基本概念 在多道程序系统中&#xff0c;进程的数量往往多于处理机的个数&#xff0c;进程争用处理机的情况就在所难免。处理机调度是对处理机进行分配&#xff0c;就是从就绪队列中&#xff0c;按照一定的算法&#xff08;公平、低效&#xff09;选择一个进程并将…

mysql学习——SQL中的DDL和DML

SQL中的DDL和DML DDL数据库操作&#xff1a;表操作 DML添加数据修改数据删除数据 学习黑马MySQL课程&#xff0c;记录笔记&#xff0c;用于复习。 DDL DDL&#xff1a;Data Definition Language&#xff0c;数据定义语言&#xff0c;用来定义数据库对象(数据库&#xff0c;表&…

C语言入门课程学习笔记8:变量的作用域递归函数宏定义交换变量

C语言入门课程学习笔记8 第36课 - 变量的作用域与生命期&#xff08;上&#xff09;第37课 - 变量的作用域与生命期&#xff08;下&#xff09;实验—局部变量的作用域实验-变量的生命期 第38课 - 函数专题练习第39课 - 递归函数简介实验小结 第40课 - C 语言中的宏定义实验小结…

基于Java的学生成绩管理系统

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;Java技术&#xff0c;B/S结构 工具&#xff1a;MyEclipse&#xff0c;MySQL 系统展示 首页 个人中…

基于YOLOv5+pyqt5的跌倒检测系统(含pyqt页面、训练好的模型)

简介 跌倒是老年人和身体不便者常见的意外事故&#xff0c;及时检测和处理跌倒事件对于保障他们的安全至关重要。为了提高对跌倒事件的监控效率&#xff0c;我们开发了一种基于YOLOv5目标检测模型的跌倒检测系统。本报告将详细介绍该系统的实际应用与实现&#xff0c;包括系统…

虚拟机IP地址频繁变化的解决方法

勾八动态分配IP&#xff0c;让我在学习redis集群的时候&#xff0c;配置很多的IP地址&#xff0c;但是由于以下原因导致我IP频繁变动&#xff0c;报错让我烦恼&#xff01;&#xff01;&#xff01;&#xff01; 为什么虚拟机的IP地址会频繁变化&#xff1f; 虚拟机IP地址频繁…