Linux驱动开发(13):输入子系统–按键输入实验
计算机的输入设备繁多,有按键、鼠标、键盘、触摸屏、游戏手柄等等,Linux内核为了能够将所有的输入设备进行统一的管理, 设计了输入子系统。为上层应用提供了统一的抽象层,各个输入设备的驱动程序只需上报产生的输入事件即可。
下面以按键中断为例讲解输入子系统的使用。 本章配套源码和设备树插件位于“ ~/linux_driver/input_sub_system ”目录下。
1. 输入子系统简介
linux为了统一各个输入设备,将输入子系统分为了Drivers(驱动层)、Input Core(输入子系统核心层)、 handlers(事件处理层)三部分。
-
Drivers主要实现对硬件设备的读写访问,设置中断,并将硬件产生的事件转为Input Core定义的规范提交给Handlers;
-
Input Core起到承上启下的作用,为Drivers提供了规范及接口,并通知Handlers对事件进行处理;
-
Handlers并不涉及硬件方面的具体操作,是一个纯软件层,包含了不同的解决方案,如按键、键盘、鼠标、游戏手柄等。
最终所有输入设备的输入信息将被抽象成以下结构体:
struct input_event结构体 (内核源码/include/uapi/linux/input.h):
//输入事件
struct input_event{struct timeval time; //事件产生的时间__u16 type; //输入设备的类型,鼠标、键盘、触摸屏__u16 code;__s16 value;
}
-
time :事件产生的时间。
-
type :输入设备的类型。
-
code :根据设备类型的不同而含义不同,如果设备类型是按键,code表示为按键值(如第几个按键等)。
-
value:根据设备类型的不同而含义不同。如果设备类型是按键,value表示的是松开或者按下。
本章目的是编写一个基于输入子系统和中断的按键驱动程序,重点在于了解Input Core为我们提供了哪些接口,并了解如何将 按键信息以事件上报。
input子系统Input Core实现代码是“ 内核源码/drivers/input/input.c ”以及“ 内核源码/include/linux/input.h ”两个文件 为我们提供了注册输入子系统的API,通过操作这些API就可以实现输入事件的注册、初始化、上报、注销等等工作。 下面我们介绍输入子系统常用的API接口及数据结构。
1.1. input_dev结构体
在输入子系统中input_dev代表一个具体的输入设备,后面将会根据具体的设备来初始化这个结构体,结构体成员介绍如下: (input_dev参数很多,有些不需要我们手动配置,所以这里只列出和介绍常用的参数,完整内容位于input.h文件)。
input_dev结构体 (内核源码/include/linux/input.h):
struct input_dev {const char *name; //提供给用户的输入设备的名称const char *phys; //提供给编程者的设备节点的名称const char *uniq; //指定唯一的ID号struct input_id id; //输入设备标识IDunsigned long propbit[BITS_TO_LONGS(INPUT_PROP_CNT)];unsigned long evbit[BITS_TO_LONGS(EV_CNT)]; //指定设备支持的事件类型unsigned long keybit[BITS_TO_LONGS(KEY_CNT)]; //记录支持的键值unsigned long relbit[BITS_TO_LONGS(REL_CNT)]; //记录支持的相对坐标位图unsigned long absbit[BITS_TO_LONGS(ABS_CNT)]; //记录支持的绝对坐标位图/*----------以下结构体成员省略----------------*/
};
结构体成员中最重要的是 evbit、keybit、relbit 等数组,这些数组设置了设备输入事件的类型和键值。
-
evbit:用于指定支持的事件类型,这要根据实际输入设备能够产生的事件来选择,可选选项如下所示。
输入子系统事件类型 (内核源码/include/uapi/linux/input-event-codes.h):
#define EV_SYN 0x00 //同步事件
#define EV_KEY 0x01 //用于描述键盘、按钮或其他类似按键的设备。
#define EV_REL 0x02 //用于描述相对位置变化,例如鼠标移动
#define EV_ABS 0x03 //用于描述绝对位置变化,例如触摸屏的触点坐标
#define EV_MSC 0x04 //其他事件类型
#define EV_SW 0x05 //用于描述二进制开关类型的设备,例如拨码开关。
#define EV_LED 0x11
#define EV_SND 0x12
#define EV_REP 0x14
#define EV_FF 0x15
#define EV_PWR 0x16
#define EV_FF_STATUS 0x17
#define EV_MAX 0x1f
#define EV_CNT (EV_MAX+1)
上面代码中前几个宏定义较为常用的输入事件类型,介绍如代码后面所示。 完整的介绍可以参考内核源码目录下的“ 内核源码/Documentation/input/event-codes.rst ”内核文档。 很明显,我们这章节要使用的按键的事件类型应该使用 EV_KEY 。
-
keybit:记录支持的键值,“键值”在程序中用于区分不同的按键,可选“键值”如下所示。
输入子系统—按键键值 (内核源码/include/uapi/linux/input-event-codes.h):
#define KEY_RESERVED 0
#define KEY_ESC 1
#define KEY_1 2
#define KEY_2 3
#define KEY_3 4
#define KEY_4 5
/*-----------以下内容省略-------------*/
可以看出“键值”就是一些数字。只要实际设备与按键对应即可。例如本章的按键可以使用KEY_1、也可以使用KEY_4等。
-
relbit、absbit:这两个参数和上面的keybit都和参数evbit有关,如果evbit中只选择了EV_KEY, 那么我们就不需要设置relbit(相对坐标)和absbit(绝对坐标)以及后面省略成员的内容。这些内容使用到时再具体介绍。 使用不同的输入事件类型需要设备不同的
总之,input_dev结构体成员很多,但是对应到一个具体的输入设备,只需要设置自己用到的其中一两个属性。
1.2. input_dev结构体的申请和释放
一个input_dev结构体代表了一个输入设备,它实际会占输入子系统的一个次设备号。 input子系统为我们提供了申请和释放input_dev结构体的函数。 由于input_dev结构体的成员很多,初始化过程也相对麻烦,一般都使用input子系统为我们提供的接口函数来 申请和释放input_dev结构体,如下所示。
input_dev申请函数 (内核源码/drivers/input/input.c):
struct input_dev *input_allocate_device(void)
{static atomic_t input_no = ATOMIC_INIT(-1);struct input_dev *dev;dev = kzalloc(sizeof(*dev), GFP_KERNEL); //动态内存申请if (dev) {dev->dev.type = &input_dev_type;dev->dev.class = &input_class; //dev->dev为struct device类型结构体device_initialize(&dev->dev); //初始化dev->dev结构体内部成员mutex_init(&dev->mutex); //初始化互斥锁spin_lock_init(&dev->event_lock); //初始化自旋锁timer_setup(&dev->timer, NULL, 0); //初始化定时器INIT_LIST_HEAD(&dev->h_list); //初始化handle链表节点INIT_LIST_HEAD(&dev->node); //初始化输入设备链表节点dev_set_name(&dev->dev, "input%lu",(unsigned long)atomic_inc_return(&input_no)); //设置设备名称__module_get(THIS_MODULE);}return dev;
}
EXPORT_SYMBOL(input_allocate_device);
我们只需要知道如何调用这个函数来申请input_dev即可,想要更深入学习的同学们可以尝试去分析整个输入子系统的实现源码, 对于输入子系统的源码分析就可以写一篇很长的文章了,这里并不展开详细的源码分析。
input_dev释放函数 (内核源码/drivers/input/input.c):
void input_free_device(struct input_dev *dev)
申请和释放函数接口比较简单。申请函数input_allocate_device执行成功后会返回申请得到的input_dev结构体的地址, 如果失败,返回NULL。释放函数input_free_device只有一个参数dev,用于指定要释放的input_dev结构体。
1.3. 注册和注销input_dev结构体
input_dev申请成功后,我们需要根据自己的实际输入设备配置input_dev结构体,具体配置在实验代码编写部分会详细说明, 配置完成后还要使用注册和注销函数将input_dev注册到输入子系统。注册和注销函数如下:
input_dev注册函数(内核源码/drivers/input/input.c):
int input_register_device(struct input_dev *dev)
参数: dev:struct input_dev类型指针
返回值:
-
成功: 0
-
失败: 返回非0值
input_register_device函数将输入设备(input_dev)注册到输入子系统的核心层。 该函数使用需要注意以下几点
-
使用该函数注册的input_dev必须是使用input_allocate_device函数申请得到的。
-
注册之前需要根据实际输入设备配置好input_dev结构体。
-
如果注册失败必须调用input_free_device函数释放input_dev结构体。
-
如果注册成功,在函数退出时只需要使用input_unregister_device函数注销input_dev结构体不需要再调用 input_free_device函数释放input_dev结构体。
input_dev注销函数 (内核源码/drivers/input/input.c):
void input_unregister_device(struct input_dev *dev)
input_unregister_device是注销函数,输入子系统的资源是有限的,不使用是应当注销。 调用input_unregister_device注销函数之后就不必调用input_free_device函数释放input_dev。
1.4. 上报事件函数和上报结束函数
以按键为例,按键按下后需要使用上报函数向输入子系统核心层上报按键事件,并且上报后还要发送上报结束信息。函数定义如下所示。
通用的上报事件函数 (内核源码include/linux/input.h):
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
参数:
-
dev,指定输入设备(input_dev结构体)。
-
type,事件类型。我们在根据实际输入设备配置input_dev结构体时会设置input_dev-> evbit参数, 用于设置输入设备能够产生的事件类型(可能是多个)。上报事件时要从“能够产生”的这些事件类型中选择。
-
code,编码。以按键为例,按键的编码就是我们设置的按键键值。
-
value,指定事件的值。
返回值: 无
上报按键事件及发送上报结束事件 (内核源码include/linux/input.h):
static inline void input_sync(struct input_dev *dev)
{input_event(dev, EV_SYN, SYN_REPORT, 0);
}static inline void input_report_key(struct input_dev *dev, unsigned int code, int value)
{input_event(dev, EV_KEY, code, !!value);
}
input子系统为不同的输入事件函数提供了不同的函数接口,这些函数接口只是对input_event函数进行简单的封装, 具体的参数参照input_event函数。input_report_key用于上报按键事件,input_sync用于发送同步信号,表示上报结束。
2. 输入子系统实验
本小节以按键为例介绍输入子系统的具体使用方法。本实验在上一章“中断实验”基础上完成。结合源码介绍如下。
2.1. 设备树插件实现
设备树插件与上一章的“按键中断实验”使用的设备树插件几乎相同,我们只需要将中断类型修改为“上升和下降沿触发”。 修改部分如下所示。
/dts-v1/;
/plugin/;
#include "../imx6ul-pinfunc.h"
#include "dt-bindings/interrupt-controller/irq.h"
#include "dt-bindings/gpio/gpio.h"/ {fragment@0 {target-path = "/";__overlay__ {button_interrupt {compatible = "button_interrupt";pinctrl-names = "default";pinctrl-0 = <&pinctrl_button>;button_gpio = <&gpio5 1 GPIO_ACTIVE_LOW>;status = "okay";interrupt-parent = <&gpio5>;interrupts = <1 IRQ_TYPE_EDGE_BOTH>; //设备树修改内容,将上升沿触发修改为双边沿触发};};};fragment@1 {target = <&iomuxc>;__overlay__ {pinctrl_button: buttongrp {fsl,pins = <MX6UL_PAD_SNVS_TAMPER1__GPIO5_IO01 0x10b0>;};};};
};
修改内容很简单只是将原来中断的触发方式修改为双边沿触发,其他的设备树内容不变。
2.2. 驱动程序实现
2.2.1. 驱动入口函数
驱动入口函数如下所示。
static int __init button_driver_init(void)
{int error;printk(KERN_ERR "button_driver_init \n");/*-----------第一部分-------------*//*获取按键 设备树节点*/button_device_node = of_find_node_by_path("/button_interrupt");if (NULL == button_device_node){printk(KERN_ERR "of_find_node_by_path error!");return -1;}/*获取按键使用的GPIO*/button_GPIO_number = of_get_named_gpio(button_device_node, "button_gpio", 0);if (0 == button_GPIO_number){printk(KERN_ERR"of_get_named_gpio error");return -1;}/*申请GPIO , 记得释放*/error = gpio_request(button_GPIO_number, "button_gpio");if (error < 0){printk(KERN_ERR "gpio_request error");gpio_free(button_GPIO_number);return -1;}error = gpio_direction_input(button_GPIO_number); //设置引脚为输入模式/*获取中断号*/interrupt_number = irq_of_parse_and_map(button_device_node, 0);printk(KERN_ERR "\n interrupt_number = %d \n", interrupt_number);/*申请中断, 记得释放*/error = request_irq(interrupt_number, button_irq_hander, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "button_interrupt", NULL);if (error != 0){printk(KERN_ERR "request_irq error");gpio_free(button_GPIO_number);free_irq(interrupt_number, NULL);return -1;}/*-----------第二部分-------------*//*申请输入子系统结构体*/button_input_dev = input_allocate_device();if (NULL == button_input_dev){printk(KERN_ERR "input_allocate_device error");return -1;}button_input_dev->name = BUTTON_NAME;/*-----------第三部分-------------*//*设置要使用的输入事件类型*/button_input_dev->evbit[0] = BIT_MASK(EV_KEY);input_set_capability(button_input_dev, EV_KEY, KEY_1); //标记设备能够触发的事件/*-----------第四部分-------------*//*注册输入设备*/error = input_register_device(button_input_dev);if (0 != error){printk(KERN_ERR "input_register_device error");gpio_free(button_GPIO_number);free_irq(interrupt_number, NULL);input_unregister_device(button_input_dev);return -1;}return 0;
}
驱动入口函数完成基本的初始化工作,结合代码各部分介绍如下:
-
第9-49行:这部分和“中断实验”相同,依次执行获取设备树节点、获取GPIO、申请GPIO、获取中断号、申请中断, 需要注意的是这里中断类型为“上升和下降沿触发”。
-
第50-58行:申请输入子系统结构体,申请得到的input_dev结构体代表了一个输入设备, 下面要根据实际的输入设备设置这个结构体。
-
第62行:设置输入事件类型。input_dev参数很多,其中最主要的是事件类型和事件对应的code。 evbit每一位代表了一种事件类型,为1则表示支持,0表示不支持。例如我们这里要支持“按键”事件, 那么就要将EV_KEY(等于0x01)位置1。内核提供了帮助宏BIT_MASK帮助我们开启某一“事件”。
-
第63行:设置支持的事件类型之后还要设置与之对应的“事件值”,内核文档中称为code。以按键为例,就是为按键选择键值 (在程序中通过键值区分不同的按键),input_dev->keybit参数用于选择键值,例如在驱动中有6个按键,那么就要使能6个键值, 同样input_dev->keybit每一位代表一个键值,我们可以直接设置某一位使能对应的键值, 不过内核提供了很多帮助宏或函数帮助我们设置键值(也可用于设置其他类型事件的code), 我们在程序中使用的是input_set_capability函数。:
-
第67-75行:注册输入设备。注册成功后,输入设备被添加到输入子系统内核层,系统能够接受来自该设备的输入事件。 需要注意的是如果注册失败需要注销之前申请的资源然后退出
input_set_capability函数,原型如下:
input_set_capability函数 (内核源码/drivers/input/input.c):
void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int code)
{switch (type) {case EV_KEY:__set_bit(code, dev->keybit);break;case EV_REL:__set_bit(code, dev->relbit);break;case EV_ABS:input_alloc_absinfo(dev);if (!dev->absinfo)return;__set_bit(code, dev->absbit);break;case EV_MSC:__set_bit(code, dev->mscbit);break;case EV_SW:__set_bit(code, dev->swbit);break;case EV_LED:__set_bit(code, dev->ledbit);break;case EV_SND:__set_bit(code, dev->sndbit);break;case EV_FF:__set_bit(code, dev->ffbit);break;case EV_PWR:/* do nothing */break;default:pr_err("input_set_capability: unknown type %u (code %u)\n",type, code);dump_stack();return;}__set_bit(type, dev->evbit);
}
参数:
-
dev:指定要设置的input_dev结构体,也就是要设置的输入设备,
-
type:设置输入类型,可以看到,函数实现中根据type设置不同的input_dev结构体参数。例如type =EV_KEY, 那么设置的是input_dev->keybit,也就是键值。
-
code:不同类型的输入信号含义不同,如果是按键,则表示的是要设置的按键的键值。
返回值: 无
2.2.2. 驱动出口函数
出口函数主要完成驱动退出前的清理工作,很简单,代码如下:
static void __exit button_driver_exit(void)
{pr_info("button_driver_exit\n");/*释放申请的引脚,和中断*/gpio_free(button_GPIO_number);free_irq(interrupt_number, NULL);/*释放输入子系统相关内容*/input_unregister_device(button_input_dev);
}
-
第6-7行:释放申请的引脚和中断
-
第10行:释放申请的输入子系统
2.2.3. 中断服务函数
中断服务函数中我们读取按键输入引脚的状态判断按键是按下还是松开。代码如下所示。
static irqreturn_t button_irq_hander(int irq, void *dev_id)
{int button_satus = 0;/*读取按键引脚的电平,根据读取得到的结果输入按键状态*/button_satus = gpio_get_value(button_GPIO_number);if(0 == button_satus){input_report_key(button_input_dev, KEY_1, 0);input_sync(button_input_dev);}else{input_report_key(button_input_dev, KEY_1, 1);input_sync(button_input_dev);}return IRQ_HANDLED;
}
-
第7行:读取按键对应引脚的电平。
-
第8-18行:根据按键引脚状态向系统上报按键事件。
2.3. 测试应用程序实现
测试应用程序中读取按键键值,打印按键状态。具体代码如下所示。
struct input_event button_input_event;int main(int argc, char *argv[])
{int error = -20;/*打开文件*/int fd = open("/dev/input/event1", O_RDONLY);if (fd < 0){printf("open file : /dev/input/event1 error!\n");return -1;}printf("wait button down... \n");printf("wait button down... \n");do{/*读取按键状态*/error = read(fd, &button_input_event, sizeof(button_input_event));if (error < 0){printf("read file error! \n");}/*判断并打印按键状态*/if((button_input_event.type == 1) && (button_input_event.code == 2)){if(button_input_event.value == 0){printf("button up\n");}else if(button_input_event.value == 1){printf("button down\n");}}} while (1);printf("button Down !\n");/*关闭文件*/error = close(fd);if (error < 0){printf("close file error! \n");}return 0;
}
-
第1行:申请一个input_event类型的结构体变量,如我们在本章开头前所说的所有的输入设备传递的信息都会以事件的形式上报。
-
第8行:这里的打开的文件 /dev/input/event1 是输入子系统为我们生成的输入设备设备,即我们使用的按键。
-
第21行:读取按键信息,read函数没有读取到上报输入事件则将一直等待。
-
第27-37行:根据获取读取到的信息判断按键的状态。
测试应用程序的内容很简单,基本是按照打开文件、读取状态、判断状态并打印状态。
2.4. 实验准备
在板卡上的部分GPIO可能会被系统占用,在使用前请根据需要修改 /boot/uEnv.txt 文件, 可注释掉某些设备树插件的加载,重启系统,释放相应的GPIO引脚。
如本节实验中,可能在鲁班猫系统中默认使能了 KEY
的设备功能, 用在了GPIO子系统。引脚被占用后,设备树可能无法再加载或驱动中无法再申请对应的资源。
方法参考如下:
取消 KEY
设备树插件,以释放系统对应KEY资源,操作如下:
dtoverlay=/usr/lib/linux-image-4.19.35-imx6/overlays/imx-fire-button-interrupt.dtbo
如若运行代码时出现“Device or resource busy”或者运行代码卡死等等现象, 请按上述情况检查并按上述步骤操作。
如出现 Permission denied
或类似字样,请注意用户权限,大部分操作硬件外设的功能,几乎都需要root用户权限,简单的解决方案是在执行语句前加入sudo或以root用户运行程序。
2.4.1. 编译设备树插件
将 linux_driver/input_sub_system/imx-fire-button-interrupt-overlay.dts
拷贝到 内核源码/arch/arm/boot/dts/overlays
目录下, 并修改同级目录下的Makefile,追加 imx-fire-button-interrupt.dtbo
编译选项。然后执行如下命令编译设备树插件:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- npi_v7_defconfigmake ARCH=arm -j4 CROSS_COMPILE=arm-linux-gnueabihf- dtbs
编译成功后生成同名的设备树插件文件(imx-fire-button-interrupt.dtbo)位于 内核源码/arch/arm/boot/dts/overlays
目录下。
2.4.2. 编译驱动程序
将 linux_driver/input_sub_system 拷贝到内核源码同级目录,执行里面的MakeFile,生成input_sub_system.ko。
2.4.3. 编译应用程序
将 linux_driver/input_sub_system/test_app 目录中执行里面的MakeFile,生成test_app。
2.5. 下载验证
编译驱动和应用程序并拷贝到开发板,这里就不再赘述如何拷贝文件了。 在加载模块之前,先查看 /boot/uEnv.txt 文件是否加载了板子上原有的与按键相关设备树插件。 如果之前开启了按键相关的设备树插件,记得先屏蔽掉。并添加输入子系统的设备树插件。
在上图所示代码前添加’#’以注销掉与按键相关的设备树插件,并 reboot
重启开发板。
使用insmod命令加载驱动,如下所示:
此时会在“/dev/input”目录下生成设备节点文件。
驱动加载成功后直接运行测试应用程序命令“./test_app”.测试程序运行后等待按键按下,此时按下开发板的“KEY”按键, 终端会输出按键状态,如下所示。
相关文章:

Linux驱动开发(13):输入子系统–按键输入实验
计算机的输入设备繁多,有按键、鼠标、键盘、触摸屏、游戏手柄等等,Linux内核为了能够将所有的输入设备进行统一的管理, 设计了输入子系统。为上层应用提供了统一的抽象层,各个输入设备的驱动程序只需上报产生的输入事件即可。 下…...

微服务篇-微服务保护:使用 Sentinel 来实现请求限流、线程隔离、服务熔断和 Fallback 备用方案的使用
🔥博客主页: 【小扳_-CSDN博客】 ❤感谢大家点赞👍收藏⭐评论✍ 文章目录 1.0 微服务保护 1.1 请求限流方案 1.2 线程隔离方案 1.3 服务熔断方案 2.0 Sentinel 2.1 Sentinel 安装 2.2 微服务整合 3.0 Sentinel-请求限流 4.0 Sentinel-线程隔离…...

vscode 排除文件夹搜索
排除的文件夹 node_modules/,dist/...
设计模式学习之——装饰者模式
装饰者模式(Decorator Pattern)是一种结构型设计模式,它允许你动态地向一个现有的对象添加新的行为,同时又不改变其结构。 一、定义与特点 定义:装饰者模式动态地将责任附加到对象上。若要扩展功能,装饰者…...
【Vulkan入门】10-CreatePipeline
目录 先叨叨Git信息关键代码TestPipeline::Initialize() 编译运行 先叨叨 到上篇为止已经创建了FrameBuffer和RenderPass。建立Pipeline的先决条件已经具备。本篇就来创建Pipeline。 Git信息 repository: https://gitee.com/J8_series/easy-car-uitag: 10-CreatePipelineurl…...

C++11 (一)
一、 C11的发展历史 C11是C 的第二个主要版本,并且是从 C98 起的最重要更新。 它引入了大量更改,标准化了既有实践,并改进了对C程序员可用的抽象。在它最终由IS0在2011年8月12日采纳前,人们曾使用名称“C0x”,因为它曾…...

系统性能优化
一、概述 性能优化的目标:是提高系统或应用程序的响应时间、吞吐量、cpu、内存、磁盘IO、网络、流量、JVM、Tomcat、DB等方面的性能指标。 性能优化需要有一些技巧:对于整个产品或项目而言,比如可以从前端优化、后端优化、架构优化、高并发…...

IMX6ULL开发板挂载 Ubuntu 的 NFS 目录,并以交叉编译得到的hello程序进行测试
首先参考博文 https://blog.csdn.net/wenhao_ir/article/details/144404637 使得IMX6ULL开发板、PC机上的USB网卡、VMware中的Ubuntu能互相Ping 通 然后开始将Ubuntu 的 NFS 目录挂载到Ubuntu中。 为什么挂载? 答:其实是把 Ubuntu中的某个目录通过NFS网…...
Xcode模拟器运行报错:The request was denied by service delegate
Xcode模拟器运行报错:The request was denied by service delegate 造成的原因: (1)新的苹果M系列芯片的Mac电脑 (2)此电脑首次安装启动Xcode的应用程序 (3)此电脑未安装Rosetta 2 解决方法: …...

ubuntu18.04配置实时内核
ubuntu系统:18.04 当前内核:5.4.0-84-generic 待安装实时内核: 5.6.19-rt11 1、查看当前版本 uname -r 2、下载内核与补丁 一种方式从官网自己下载 官方内核下载地址官方补丁下载地址阿里镜像内核下载地址(速度快࿰…...

Unity中Mesh重叠顶点合并参考及其应用
在Unity中,如果将一个模型文件(比如从max里面导出一个fbx文件)导入到编辑器中之后,Unity会把所有在原来在面列表中公用的顶点复制一份,保证每个三角形使用的顶点都是单独的,不与其它三角形共用顶点…...

倚光科技助力自由曲面设计与加工
近年来,自由曲面因其在光学、汽车、航空航天等领域的广泛应用,受到设计师和工程师的高度关注。自由曲面作为一种具有更高自由度的非球面透镜,能够在光学系统中实现更加精确的光线控制,优化像差校正,并且在满足功能需求…...

PWM调节DCDC参数计算原理
1、动态电压频率调整DVFS SOC芯片的核电压、GPU电压、NPU电压、GPU电压等,都会根据性能和实际应用场景来进行电压和频率的调整。 即动态电压频率调整DVFS(Dynamic Voltage and Frequency scaling),优化性能和功耗。 比如某SOC在…...

[Pro Git#3] 远程仓库 | ssh key | .gitignore配置
目录 1. 分布式版本控制系统的概念 2. 实际使用中的“中央服务器” 3. 远程仓库的理解 4. 新建远程仓库 5. 克隆远程仓库 6. 设置SSH Key 实验 一、多用户协作与公钥管理 二、克隆后的本地与远程分支对应 三、向远程仓库推送 四、拉取远程仓库更新 五、配置Git忽略…...

Freertos任务切换
一、操作系统进行任务切换的时机: 采用信号量实现任务的互斥: 二、FreeRTOS 任务切换场合 PendSV 中断的时候提到了上下文(任务)切换被触发的场合: ● 可以执行一个系统调用 ● 系统滴答定时器(SysTick)中断。 1、执行系统调用 执行系统…...
go开发中interface和方法接收器的使用
Go 语言中的接口和方法接收器学习 Go 中的 interface 就像是一个神奇的魔法杖,能让你轻松地将不同的类型拉到同一个阵营里。与其他语言的接口不同,Go 的接口无需显式声明“我实现了你”,只要你满足了接口规定的方法,Go 就会自动认…...

vue3-tp8-Element:对话框实现
效果 参考框架 Dialog 对话框 | Element Plus 具体实现 一、建立view页面 /src/views/TestView.vue 二、将路径写入路由 /src/router/index.js import { createRouter, createWebHistory } from vue-router import HomeView from ../views/HomeView.vueconst router create…...

高中数学:随机变量-正态分布
文章目录 一、连续性随机变量二、大致图像三、正态分布图像及解析式图像特点均值与方差公式正态分布各区域概率 一、连续性随机变量 二、大致图像 三、正态分布图像及解析式 图像特点 均值与方差公式 正态分布各区域概率...

游戏引擎学习第47天
仓库: https://gitee.com/mrxiao_com/2d_game 昨天我们花了一点时间来修复一个问题,但基本上是在修复这个问题的过程中,我们决定添加一个功能,那就是在屏幕上控制多个实体。所以如果我有一个手柄,我可以添加另一个角色࿰…...

Git 仓库托管教程
git远程仓库 常用的远程仓库-->托管服务:github、码云、gitlab等 github需要魔法上网,速度较慢因为在国外且仅仅支持Git,如果不是Git项目是不支持的;码云--gitee国内的代码托管平台,服务器在国内速度快一些&#…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...

el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
C++.OpenGL (10/64)基础光照(Basic Lighting)
基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...

IoT/HCIP实验-3/LiteOS操作系统内核实验(任务、内存、信号量、CMSIS..)
文章目录 概述HelloWorld 工程C/C配置编译器主配置Makefile脚本烧录器主配置运行结果程序调用栈 任务管理实验实验结果osal 系统适配层osal_task_create 其他实验实验源码内存管理实验互斥锁实验信号量实验 CMISIS接口实验还是得JlINKCMSIS 简介LiteOS->CMSIS任务间消息交互…...

Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...