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 引用
.dtb DTC 工具编译 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卸载驱动。
相关推荐
hahaha60162 小时前
FPGA+ARM实现AWB的全流程
人工智能·嵌入式硬件·算法·计算机视觉·fpga开发
ACP广源盛139246256732 小时前
Type‑C 扩展坞方案选型笔记|国产 DP1.4 转 HDMI2.0 桥接芯片 GSV2201S @ACP评估
c语言·开发语言·笔记·硬件架构·硬件工程·国产芯片
沐欣工作室_lvyiyi2 小时前
超市智能货架系统设计与实现(论文+源码)
c语言·单片机·课程设计·电子信息工程毕业设计·黑龙江工商学院·广州工商学院·成都工业学院
老孟说禅2 小时前
硬件测试 - 测试用例设计:测试需求分析、测试点提取、测试用例编写方法
单片机·嵌入式硬件·智能硬件
C++ 老炮儿的技术栈2 小时前
static的作用
c语言·c++·人工智能·单片机·c
逐光者9333 小时前
STM32通信协议——I2C
stm32·单片机·嵌入式硬件
千秋岁rr3 小时前
15.PWR电源控制
单片机·嵌入式硬件
额额额对了3 小时前
嵌入式 Linux 启动程序文件
开发语言·嵌入式硬件·php
独孤九剑打醒他3 小时前
【原创开源·修订版】源-栅-漏-栅-源横向双栅MOS:从“被误解的短路”到“电流路径多值逻辑与顶层供电架构”
前端·嵌入式硬件·架构·开源·硬件工程