Linux -- 字符设备驱动--LED的驱动开发

本文详细介绍了Linux驱动开发的基础步骤,包括驱动框架的三个阶段、如何操作硬件(如使用ioremap映射寄存器)、与应用程序的数据传输,以及字符设备的注册与注销、模块加载和卸载。还讨论了设备号的分配策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

初级框架

驱动框架一阶段

我们怎样去点亮一个 LED 呢?分为三步:

  1. 看原理图确定引脚,确定引脚输出什么电平才能点亮/熄灭 LED
  2. 看主芯片手册,确定寄存器操作方法:哪些寄存器?哪些位?地址是?
  3. 编写驱动:先写框架,再写硬件操作的代码
注意 :在芯片手册中确定的寄存器地址被称为 物理地址 ,在 Linux 内核中无法直接使用。
需要使用内核提供的 ioremap 把物理地址映射为 虚拟地址 ,使用虚拟地址。
ioremap 函数的使用:

编写驱动程序的套路:

  • 确定主设备号,也可以让内核分配;
  • 定义自己的 file_operations 结构体;
  • 实现对应的 drv_open/drv_read/drv_write 等函数,填入 file_operations 结构体;
  • file_operations 结构体告诉内核:register_chrdev
  • 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数;
  • 有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用 unregister_chrdev
  • 其他完善:提供设备信息,自动创建设备节点:class_create, device_create
驱动怎么操作硬件?
通过 ioremap 映射寄存器的物理地址得到虚拟地址,读写虚拟地址。驱动层访问硬件外设寄存器依靠的是 ioremap 函数去映射到寄存器地址,然后开始控制寄存器。
驱动怎么和 APP 传输数据?
通过 copy_to_user copy_from_user 2 个函数。

驱动框架二阶段:分层思想

  • 上层实现硬件无关的操作,比如注册字符设备驱动:leddrv.c
  • 下层实现硬件相关的操作,比如 board_A.c 实现单板 A LED 操作

驱动框架三阶段:分离

引脚操作那么有规律,并且这是跟主芯片相关的,那可以针对该芯片写出比较通用的硬件操作代码。
比如 board_A.c 使用芯片 chipY ,那就可以写出: chipY_gpio.c ,它实现 芯片 Y GPIO 操作,适用于芯片 Y 的所有 GPIO 引脚。
使用时,我们只需要在 board_A_led.c 中指定使用哪一个引脚即可。程序结构如下:
以面向对象的思想,在 board_A_led.c 中实现 led_resouce 结构体,它定 义“资源”──要用哪一个引脚。
chipY_gpio.c 中仍是实现 led_operations 结构体,它要写得更完善,支持所有 GPIO
总结:
程序仍分为上下结构:
上层 leddrv.c 向内核注册 file_operations 结构体;
下层 chip_demo_gpio.c 提供 led_operations 结构体来操作硬件。
下层的代码分为 2 个:
  1. chip_demo_gpio.c 实现通用的 GPIO 操作,
  2. board_A_led.c 指定使用哪个 GPIO,即“资源”,也就是硬件的引脚信息,它实现一个 led_resource 结构体,并提供访问函数

进阶框架

一、驱动模块的加载和卸载

Linux 驱动有两种运行方式,

第一种就是将驱动编译进 Linux 内核中,这样当 Linux 内核启动的时候就会自动运行驱动程序。

第二种就是将驱动编译成模块(Linux 下模块扩展名为.ko),在Linux 内核启动以后使用“insmod”命令加载驱动模块。在调试驱动的时候一般都选择将其编译为模块,这样我们修改驱动以后只需要编译一下驱动代码即可,不需要编译整个 Linux 代码。而且在调试的时候只需要加载或者卸载驱动模块即可,不需要重启整个系统。

总之,将驱动编译为模块最大的好处就是方便开发,当驱动开发完成,确定没有问题以后就可以将驱动编译进Linux 内核中,当然也可以不编译进 Linux 内核中,具体看自己的需求。

module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数

二、字符设备注册与注销

对于字符设备驱动而言,当驱动模块加载成功以后需要注册字符设备,同样,卸载驱动模
块的时候也需要注销掉字符设备。字符设备的注册和注销函数原型如下所示 :
static inline int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major, const char *name)
register_chrdev 函数用于注册字符设备,此函数一共有三个参数,这三个参数的含义如下:
major 主设备号, Linux 下每个设备都有一个设备号,设备号分为主设备号和次设备号两部分,主设备号可以系统分配也可以自己指定。
name :设备名字,指向一串字符串。
fops 结构体 file_operations 类型指针,指向设备的操作函数集合变量。
unregister_chrdev 函数用户注销字符设备,此函数有两个参数,这两个参数含义如下:
major 要注销的设备对应的主设备号。
name 要注销的设备对应的设备名。
一般字符设备的注册在驱动模块的入口函数 xxx_init 中进行,字符设备的注销在驱动模块的出口函数 xxx_exit 中进行。

三、实现设备具体操作函数

file_operations 结构体就是设备的具体操作函数

四、添加LICENSE和作者信息

最后我们需要在驱动中加入 LICENSE 信息和作者信息,其中 LICENSE 是必须添加的,否则的话编译的时候会报错,作者信息可以添加也可以不添加。LICENSE 和作者信息的添加使用 如下两个函数:
MODULE_LICENSE() //添加模块 LICENSE 信息
MODULE_AUTHOR() //添加模块作者信息
至此,字符设备驱动开发的完整步骤就讲解完了,而且也编写好了一个完整的字符设备驱
动模板,以后字符设备驱动开发都可以在此模板上进行。

补充:

1、Linux设备号

为了方便管理, Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。
Linux 提供了 一个名为 dev_t 的数据类型表示设备号, dev_t 定义在文件 include/linux/types.h 里面,dev_t 其实就是 unsigned int 类型,是一个 32 位的数据类型。这 32 位的数据构成了主和次设备号两部分设备号,其中高 12 位为主设备号,低 20 位为次设备号。因此 Linux系统中主设备号范围为 0~4095,所以在选择主设备号的时候一定不要超过这个范围。

2.设备号的分配

1 、静态分配设备号
注 册字符设备的时候需要给设备指定一个设备号,这个设备号可以是驱动开发者静态的指定一个 设备号,比如选择 200 这个主设备号。有一些常用的设备号已经被 Linux 内核开发者给分配了,具体分配的内容可以查看文档 Documentation/devices.txt 。并不是说内核开发者已经分配掉的主设备号我们就不能用了,具体能不能用还得看我们的硬件平台运行过程中有没有使用这个主设备号,使用“cat /proc/devices ”命令即可查看当前系统中所有已经使用了的设备号。
2 、动态分配设备号
Linux 社区推荐使用动态分配设备号,在注册字符设备之前先申请一个设备号,系统会自动给你一个没有被使用的设备号,这样就避免了冲突。卸载驱动的时候释放掉这个设备号即可,设备号的申请函数如下:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

### RT-DETRv3 网络结构分析 RT-DETRv3 是一种基于 Transformer 的实时端到端目标检测算法,其核心在于通过引入分层密集正监督方法以及一系列创新性的训练策略,解决了传统 DETR 模型收敛慢和解码器训练不足的问题。以下是 RT-DETRv3 的主要网络结构特点: #### 1. **基于 CNN 的辅助分支** 为了增强编码器的特征表示能力,RT-DETRv3 引入了一个基于卷积神经网络 (CNN) 的辅助分支[^3]。这一分支提供了密集的监督信号,能够与原始解码器协同工作,从而提升整体性能。 ```python class AuxiliaryBranch(nn.Module): def __init__(self, in_channels, out_channels): super(AuxiliaryBranch, self).__init__() self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1) self.bn = nn.BatchNorm2d(out_channels) def forward(self, x): return F.relu(self.bn(self.conv(x))) ``` 此部分的设计灵感来源于传统的 CNN 架构,例如 YOLO 系列中的 CSPNet 和 PAN 结构[^2],这些技术被用来优化特征提取效率并减少计算开销。 --- #### 2. **自注意力扰动学习策略** 为解决解码器训练不足的问题,RT-DETRv3 提出了一种名为 *self-att 扰动* 的新学习策略。这种策略通过对多个查询组中阳性样本的标签分配进行多样化处理,有效增加了阳例的数量,进而提高了模型的学习能力和泛化性能。 具体实现方式是在训练过程中动态调整注意力权重分布,确保更多的高质量查询可以与真实标注 (Ground Truth) 进行匹配。 --- #### 3. **共享权重解编码器分支** 除了上述改进外,RT-DETRv3 还引入了一个共享权重的解编码器分支,专门用于提供密集的正向监督信号。这一设计不仅简化了模型架构,还显著降低了参数量和推理时间,使其更适合实时应用需求。 ```python class SharedDecoderEncoder(nn.Module): def __init__(self, d_model, nhead, num_layers): super(SharedDecoderEncoder, self).__init__() decoder_layer = nn.TransformerDecoderLayer(d_model=d_model, nhead=nhead) self.decoder = nn.TransformerDecoder(decoder_layer, num_layers=num_layers) def forward(self, tgt, memory): return self.decoder(tgt=tgt, memory=memory) ``` 通过这种方式,RT-DETRv3 实现了高效的目标检测流程,在保持高精度的同时大幅缩短了推理延迟。 --- #### 4. **与其他模型的关系** 值得一提的是,RT-DETRv3 并未完全抛弃经典的 CNN 技术,而是将其与 Transformer 结合起来形成混合架构[^4]。例如,它采用了 YOLO 系列中的 RepNCSP 模块替代冗余的多尺度自注意力层,从而减少了不必要的计算负担。 此外,RT-DETRv3 还借鉴了 DETR 的一对一匹配策略,并在此基础上进行了优化,进一步提升了小目标检测的能力。 --- ### 总结 综上所述,RT-DETRv3 的网络结构主要包括以下几个关键组件:基于 CNN 的辅助分支、自注意力扰动学习策略、共享权重解编码器分支以及混合编码器设计。这些技术创新共同推动了实时目标检测领域的发展,使其在复杂场景下的表现更加出色。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值