news 2026/10/17 2:56:06

iMX6ULL 驱动工程搭建 + LED 灯驱动

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
iMX6ULL 驱动工程搭建 + LED 灯驱动

一、驱动工程搭建

1、用户应用编程 VS 内核驱动编程差异

应用程序:运行在用户空间;

驱动:属于内核级别编程,代码属于操作系统内核一部分。

  • 核心问题:如何在已经运行的大型内核代码中插入、执行我们自己的代码;
  • 驱动本质最终还是操作寄存器,原理和裸机一致;区别是内核下要做物理地址→虚拟地址映射。

2、驱动两种加载方式

方式 1:静态编译进内核

  • 编写驱动.c源码;
  • 修改Kconfig、Makefile;
  • make menuconfig勾选驱动,<*代表编译进 zImage;
  • 驱动代码被打包进 zImage 镜像,zImage 体积变大;
  • 必须重新烧写内核、重启开发板,驱动才能生效。

方式 2:动态模块 ko(推荐调试使用)

  • 编写驱动.c源码;
  • 修改 Makefile,obj‑m += xxx.o,执行make modules生成.ko模块文件;
  • ko 独立于 zImage 镜像,不修改内核镜像;
  • 开发板 Linux 运行后手动加载卸载:

insmod xxx.ko:加载内核模块

lsmod:查看已经加载的内核模块

rmmod xxx:卸载内核模块

优点:不需要重启板子,调试效率高。

3、内核模块基础宏

宏功能说明
module_init(func)模块加载入口函数,insmod 的时候执行
module_exit(func)模块卸载入口函数,rmmod 的时候执行
MODULE_LICENSE("GPL")许可证声明,必须写,否则内核报警告,部分接口不可用
printk()内核打印函数,代替应用层 printf,不能打印浮点数
pr_info(fmt,...)带日志等级的打印,普通提示信息
pr_err(fmt,...)带日志等级的打印,打印错误信息
#include <linux/init.h> #include <linux/module.h> #include <linux/printk.h> static int __init mymodule_init(void) { pr_info("mymodule_init ok\n"); return 0; } static void __exit mymodule_exit(void) { pr_info("mymodule_exit byebye\n"); } module_init(mymodule_init); module_exit(mymodule_exit); MODULE_LICENSE("GPL");

4、两种工程组织方式

① 放在内核源码树内部开发(drivers 目录下新增驱动)

步骤:

  • 在drivers/char新建mydevtest.c驱动源码;
  • 修改同目录Kconfig,添加配置项
config MYMODULE tristate "mymodule inner kernel test" default y help driver test kconfig makefile
  • 修改同目录Makefile:obj‑$(CONFIG_MYMODULE) += myhello.o
  • make menuconfig选中对应模块;
  • make modules生成 ko;
  • 将 ko 拷贝到 nfs 的 rootfs 目录,开发板 insmod 加载测试。

Kconfig:控制 menuconfig 图形配置界面;Makefile 根据 CONFIG_xxx 变量决定是否编译文件。

② 源码树外部独立工程(实际项目推荐)

不改动内核源码,驱动工程和内核源码分开放。 目录结构示例

├── app # 用户应用程序 │ ├── app.c │ └── Makefile ├── drv # 内核驱动代码 │ ├── .clangd │ ├── compile_commands.json #软链接指向内核源码生成的该文件,用于vscode代码补全 │ ├── drv.c │ └── Makefile └── Makefile #顶层总Makefile

drv/Makefile示例

MODULE_NAME:= drv KER_DIR := /home/linux/imx6ull/linux‑imx‑rel_imx_4.1.15_2.1.0_ga_alientek CUR_DIR := $(shell pwd) obj‑m += $(MODULE_NAME).o all: make -C $(KER_DIR) M=$(CUR_DIR) modules cp $(MODULE_NAME).ko ~/nfs/rootfs clean: make -C $(KER_DIR) M=$(CUR_DIR) clean distclean: rm $(MODULE_NAME).ko make -C $(KER_DIR) M=$(CUR_DIR) distclean

app/Makefile示例

SRC:=app.c OBJ:=app CC:=arm‑linux‑gnueabihf‑gcc all: $(CC) $(SRC) -o $(OBJ) clean: rm $(OBJ)

顶层 Makefile

all: make -C ./app all make -C ./drv all clean: make -C ./app clean make -C ./drv clean

前置条件:内核源码必须完整编译过一次,外部模块编译依赖内核编译产物。

5、字符设备驱动基础框架

Linux 驱动三大分类

(1)字符设备:按字节流顺序读写;GPIO、I2C、SPI、UART、LED、按键、LCD、摄像头;占驱动 95% 以上;

(2)块设备:按块读写;SD、EMMC、Flash 存储设备;随机访问,吞吐量大;

(3)网络设备:wifi、蓝牙、CAN。

字符设备开发步骤

1)alloc_chrdev_region():动态分配设备号(推荐);register_chrdev_region()静态注册已知设备号;

2)cdev_alloc()/cdev_init():初始化字符设备结构体,绑定file_operations;

3)cdev_add():将字符设备注册进内核;

4)file_operations结构体:实现open/read/write/release,用户态系统调用最终会回调这些内核函数;

5)点两种方创建设备节式

  • 手动:mknod /dev/led0 c 主设备号 次设备号
  • 自动:class_create()+device_create(),sysfs 自动生成 /dev 设备文件;

6)卸载的时候依次执行cdev_del()、unregister_chrdev_region()、device_destroy()、class_destroy()。

字符设备 API功能
cdev_init初始化 cdev 结构体,绑定 file_operations
cdev_alloc动态申请 cdev 结构体内存
cdev_add把 cdev 注册到内核,驱动正式生效
cdev_del从内核删除 cdev 设备
alloc_chrdev_region动态分配主次设备号
unregister_chrdev_region释放已经注册的设备号

/proc/devices可以查看系统所有已经注册的字符设备主设备号。

file_operations 说明

应用层调用 open/read/write/close,内核就会调用驱动中file_operations对应的函数指针。

6、sysfs 虚拟文件系统

sysfs 是内存虚拟文件系统,不占用磁盘存储,挂载在/sys。

sysfs 重要目录
目录作用
/sys/devices真实物理设备
/sys/bus总线,devices 子目录存放设备,drivers 存放对应驱动
/sys/class设备分类,按设备类型分组(tty、led、input 等)
/sys/module已经加载的内核模块

驱动中使用 sysfs:

  • class_create():创建设备类;
  • device_create():创建设备,自动生成/dev/xxx节点;
  • __ATTR宏定义设备属性;device_create_file()创建 sys 属性文件;
  • 用户空间直接cat /sys/xxx/xxx读,echo value > /sys/xxx/xxx写,用于调试硬件,不用写专门应用程序。

注意:内核属性文件权限不能设置 0666,只能使用 0664。show函数返回读取字符串长度;store函数必须返回传入数据长度,否则应用层会阻塞。

相关 API

函数功能
class_create创建设备类目录
class_destroy销毁设备类
device_create创建设备,自动生成 /dev 节点
device_destroy销毁设备
__ATTR初始化设备属性结构体
device_create_file生成 sys 属性文件
device_remove_file删除 sys 属性文件

7、misc 杂项设备(主设备号固定 10)

属于字符设备的简化封装,主设备号固定为 10,自动分配次设备号,自动创建设备节点。

可以省去alloc_chrdev_region、cdev_init、cdev_add、手动 mknod、class/device 的部分代码。

misc API功能
misc_register()注册杂项设备
misc_deregister()注销杂项设备

二、LED 灯驱动知识点

1、硬件原理图要点

LED:低电平点亮,高电平熄灭;串联 510Ω 限流电阻。 引脚:LED0 接到 GPIO1_IO03。

2、裸机操作需要的寄存器

  • IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03:引脚复用功能寄存器;
  • IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03:引脚电气特性配置;
  • GPIO1_DR:GPIO 数据寄存器;写 0 输出低电平,写 1 输出高电平;
  • GPIO1_GDIR:GPIO 方向寄存器;bit 置 1 配置输出模式。

⚠️内核中不能直接操作物理寄存器地址!必须做物理地址→虚拟地址映射。

3、内存映射相关 API(内核访问硬件寄存器)

头文件:#include <linux/io.h>

函数功能说明
ioremap(phys, size)把物理地址映射成内核虚拟地址,返回虚拟地址指针
iounmap(virt_addr)解除映射
devm_ioremap(phys,size)资源托管映射;设备卸载驱动自动 iounmap 释放,推荐优先使用,防止内存泄漏
devm_ioremap_resource()检查资源冲突,设备树驱动优先使用

寄存器读写函数(32 位 SOC,imx6ull 寄存器都是 32bit)

  • readl(addr):读取 32 位寄存器;
  • writel(value, addr):写 32 位寄存器。

4、用户空间 ↔ 内核空间数据拷贝

驱动 read/write 函数中,不能直接解引用用户空间指针,必须使用专用拷贝函数。

copy_from_user(kbuf, ubuf, size)用户空间数据拷贝到内核空间

copy_to_user(ubuf, kbuf, size)内核空间数据拷贝到用户空间

5、两套 LED 驱动实现思路

思路 1:直接硬编码寄存器地址(不使用设备树)
  1. ioremap 映射 4 个寄存器物理地址得到虚拟地址;
  2. writel 配置引脚复用、PAD 电气属性;
  3. writel 设置 GPIO 方向为输出;
  4. writel 写 DR 寄存器控制输出高低电平,实现 LED 亮灭;
  5. 在 file_operations 的 write 函数接收用户层传进来的亮灭值,操作寄存器;
  6. 模块 exit 中 iounmap 释放映射。

缺点:硬件换板子,寄存器地址变了,驱动代码必须修改,可移植性差。

思路 2:设备树方式 LED 驱动(工程实际推荐,设备与驱动分离)

Linux3.x 之后引入设备树思想:硬件资源(寄存器、引脚、中断)写在 dts 设备树,驱动写通用逻辑;换硬件只改 dts,不用修改驱动 c 代码。

设备树相关文件后缀

后缀含义
.dts板级设备树,对应开发板硬件配置
.dtsi公共头文件,多个 dts 可以 include 引用
.dtbDTC 工具编译 dts/dtsi 得到的二进制,uboot 加载传递给内核

6、编译与测试完整流程

  • 修改 dts,配置 led 节点(pinctrl+gpio);
  • 编译 dtb,放到 tftpboot;
  • 编写驱动 c 代码,使用外部 Makefile 编译生成 ko;
  • ko 拷贝到 nfs 共享 rootfs;
  • 开发板网络启动,insmod led_drv.ko加载驱动
  • 测试方式二选一
    • 应用程序 open /dev/xxx,write 写 1/0 控制亮灭;
    • sysfs 节点:echo 1 > /sys/devices/virtual/LED/led0/led_state;cat /sys/devices/virtual/LED/led0/led_state;
  • rmmod led_drv卸载驱动。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/16 11:32:57

Python + JSON Schema 实现工单结构化输出与本地复核

客服系统最危险的自动化&#xff0c;不是模型答错一句话&#xff0c;而是把“退款 9999 元”当成真指令一路执行。结构化输出&#xff08;Structured Outputs&#xff09;能要求模型按 JSON Schema 返回字段&#xff1b;但它不是业务真相。你还需要第二道本地门&#xff1a;金额…

作者头像 李华
网站建设 2026/10/16 10:12:12

声发射信号离散程度分析:用MATLAB滑窗计算变异系数CV的工程实践

手里有一批声发射检测数据&#xff0c;需要快速评估材料在不同加载阶段信号的离散程度&#xff0c;我第一个想到的就是算变异系数CV值。声发射&#xff08;Acoustic Emission&#xff0c;AE&#xff09;信号的振铃计数、幅值、能量这些参数&#xff0c;单看均值只能反映“平均水…

作者头像 李华
网站建设 2026/10/17 4:18:47

BrewUI 装不上?先别重装系统,90% 的报错都藏在这 5 个地方

BrewUI 装不上&#xff1f;先别重装系统&#xff0c;90% 的报错都藏在这 5 个地方 【免费下载链接】BrewUI &#x1f4fa; Homebrews official macOS GUI 项目地址: https://gitcode.com/GitHub_Trending/br/BrewUI BrewUI 是 Homebrew 官方的 macOS 原生图形界面&#…

作者头像 李华
网站建设 2026/10/16 15:41:31

Kubernetes内存驱逐工具:智能检测与Pod驱逐实战

1. 项目缘起&#xff1a;为什么需要一套内存驱逐工具干过K8s生产运维的兄弟应该都有这种经历&#xff1a;半夜被告警电话叫醒&#xff0c;打开监控一看&#xff0c;某个节点Memory Pressure持续亮红&#xff0c;Pod各种EvictionFailed、OOMKilled&#xff0c;业务方在群里疯狂刷…

作者头像 李华
网站建设 2026/10/16 6:08:10

编译原理NFA转DFA实现:Python子集构造法详解与避坑指南

简介&#xff1a;这份资源面向学习编译原理、需要动手实践词法分析环节的高校学生与自学者&#xff0c;聚焦非确定有限自动机&#xff08;NFA&#xff09;到确定有限自动机&#xff08;DFA&#xff09;的转换这一编译器设计关键步骤。压缩包共3个文件&#xff0c;以py脚本、txt…

作者头像 李华