news 2026/1/29 22:03:22

【Linux驱动篇】LED驱动开发实验

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Linux驱动篇】LED驱动开发实验

文章目录

  • 【Linux驱动篇】LED驱动开发实验
    • 1 简介
      • 1.1 地址映射
        • 1.1.1 ioremap函数
        • 1.1.2 iounmap函数
      • 1.2 IO内存访问函数
        • 1.2.1 读操作函数
        • 1.2.2 写操作函数
    • 2 硬件原理分析
    • 3 实验程序编写
      • 3.1 新建工程
      • 3.1 LED灯驱动程序编写
      • 3.2 编写测试APP
      • 3.3 编写Makefile
    • 4 编译测试
      • 4.1 编译
        • 4.1.1 编译驱动程序
        • 4.1.2 编译测试APP
      • 4.2 运行测试

【Linux驱动篇】LED驱动开发实验

1 简介

Linux 下的任何外设驱动,最终都是要配置相应的硬件寄存器,而硬件寄存器又会在Linux中映射到内存中使用,因此我们操作内存即可实现配置硬件寄存器。

1.1 地址映射

MMU 全称叫做 Memory Manage Unit,也就是内存管理单元。主要完成的功能如下:

  • 完成虚拟空间到物理空间的映射。
    • 虚拟地址:对于 32 位的处理器来说,虚拟地址范围是 2^32=4GB,
    • 物理地址:开发板上有 512MB 的 DDR3,这 512MB 的内存就是物理内存
  • 内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性。

经过 MMU 可以将开发板的物理地址映射到整个 4GB 的虚拟空间,如下所示:

Linux 内核启动的时候会初始化 MMU,设置好内存映射,设置好以后 CPU 访问的都是虚拟地址。

举个例子:I.MX6ULL 的 GPIO1_IO03 引脚的复用寄存IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 的地址为 0X020E0068,如果没有开启MMU的话,可以直接完该寄存器里面写入数据即可配置GPIO1_IO03的复用功能,但是开启了MMU,设置了内存映射,就不能直接写入了,需要得到Linux系统内对应的虚拟地址,然后写入。这里涉及到两个函数:ioremap 和 iounmap

1.1.1 ioremap函数

用于获取指 定 物 理 地 址 空 间 对 应 的 虚 拟 地 址 空 间, 定 义 在arch/arm/include/asm/io.h 文件中,定义如下:

#defineioremap(cookie,size)__arm_ioremap((cookie),(size),MT_DEVICE)void__iomem*__arm_ioremap(phys_addr_tphys_addr,size_tsize,unsignedintmtype){returnarch_ioremap_caller(phys_addr,size,mtype,__builtin_return_address(0));}

其中:

  • ioremap 是个宏,有两个参数:cookie 和 size,真正起作用的是函数__arm_ioremap。
  • phys_addr:要映射的物理起始地址。
  • size:要映射的内存空间大小。
  • mtype:ioremap 的类型,可以选择 MT_DEVICE、MT_DEVICE_NONSHARED、MT_DEVICE_CACHED 和 MT_DEVICE_WC,ioremap 函数选择 MT_DEVICE。
  • 返回值:__iomem 类型的指针,指向映射后的虚拟空间首地址。

假如我们要获取 I.MX6ULL 的 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 寄存器对应的虚拟地址,使用如下:

#define SW_MUX_GPIO1_IO03_BASE (0X020E0068) static void __iomem* SW_MUX_GPIO1_IO03; SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);

其中:

  • 宏 SW_MUX_GPIO1_IO03_BASE 是寄存器物理地址
  • SW_MUX_GPIO1_IO03 是映射后的虚拟地址
  • 对于 I.MX6ULL 来说一个寄存器是 4 字节(32 位)的,因此映射的内存长度为 4

映射完成以后直接对 SW_MUX_GPIO1_IO03 进行读写操作即可

1.1.2 iounmap函数

卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射,iounmap 函数原型如下:

voidiounmap(volatilevoid__iomem*addr)

其中:

  • addr: 要取消映射的虚拟地址空间首地址。

假如我们想要取消IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 寄存器的地址映射,使用如下代码:

iounmap(SW_MUX_GPIO1_IO03);

1.2 IO内存访问函数

当外部寄存器或内存映射到 IO 空间时,称为 I/O 端口。当外部寄存器或内存映射到内存空间时,称为 I/O 内存。

对于 ARM 来说没有 I/O 空间这个概念,因此 ARM 体系下只有 I/O 内存(可以直接理解为内存)。

Linux提供了一组操作函数对映射后的内存进行读写操作。

1.2.1 读操作函数

有如下几个:

u8readb(constvolatilevoid__iomem*addr)u16readw(constvolatilevoid__iomem*addr)u32readl(constvolatilevoid__iomem*addr)

其中:

  • readb、readw 和 readl 这三个函数分别对应 8bit、16bit 和 32bit 读操作
  • addr 就是要读取写内存地址
  • 返回值就是读取到的数据。
1.2.2 写操作函数

有如下几个:

u8writeb(u8 value,volatilevoid__iomem*addr)u16writew(u8 value,volatilevoid__iomem*addr)u32writel(u8 value,volatilevoid__iomem*addr)

其中:

  • writeb、writew 和 writel 这三个函数分别对应 8bit、16bit 和 32bit 写操作
  • value 是要写入的数值
  • addr 是要写入的地址。

2 硬件原理分析

I.MX6U-ALPHA开发板上有一个LED灯,原理图如下:

LED0 接到了 GPIO_3 上,GPIO_3 就是 GPIO1_IO03,当 GPIO1_IO03输出低电平(0)的时候发光二极管 LED0 就会导通点亮,当 GPIO1_IO03 输出高电平(1)的时候发光二极管 LED0 不会导通,因此 LED0 也就不会点亮。

3 实验程序编写

3.1 新建工程

新建工程,其目录结构如下所示:

pzs@pzs-jammy:~/linux/drivers$ tree ./2_led/ -a ./2_led/ ├── ledApp.c ├── led.c ├── Makefile └── .vscode └── c_cpp_properties.json

其中:

  • c_cpp_properties.json: 配置vscode的头文件引入。参考【Linux驱动篇】字符设备驱动开发
  • Makefile: 编译工程
  • led.c:驱动程序
  • ledApp.c: 驱动测试程序

3.1 LED灯驱动程序编写

在 led.c 里面输入如下内容:

#include<linux/types.h>#include<linux/kernel.h>#include<linux/delay.h>#include<linux/ide.h>#include<linux/init.h>#include<linux/module.h>#include<linux/errno.h>#include<linux/gpio.h>#include<asm/mach/map.h>#include<asm/uaccess.h>#include<asm/io.h>#defineLED_MAJOR200/* major number */#defineLED_NAME"led"/* driver name */#defineLEDOFF0/* LED off */#defineLEDON1/* LED on *//* Register physical address */#defineCCM_CCGR1_BASE(0X020C406C)#defineSW_MUX_GPIO1_IO03_BASE(0X020E0068)#defineSW_PAD_GPIO1_IO03_BASE(0X020E02F4)#defineGPIO1_DR_BASE(0X0209C000)#defineGPIO1_GDIR_BASE(0X0209C004)/* Pointer to the virtual address of the mapped register */staticvoid__iomem*IMX6U_CCM_CCGR1;staticvoid__iomem*SW_MUX_GPIO1_IO03;staticvoid__iomem*SW_PAD_GPIO1_IO03;staticvoid__iomem*GPIO1_DR;staticvoid__iomem*GPIO1_GDIR;/** * @brief: Switch the LED on/off * @param state: The state of the LED, LEDOFF or LEDON */voidled_switch(u8 state){u32 val=0;if(state==LEDON){val=readl(GPIO1_DR);val&=~(1<<3);writel(val,GPIO1_DR);}elseif(state==LEDOFF){val=readl(GPIO1_DR);val|=(1<<3);writel(val,GPIO1_DR);}}/** * @description: led_open - open function * @param - inode: inode of device file * @param - filp: device file * @return: 0 on success, -1 on failure */staticintled_open(structinode*inode,structfile*filp){printk("led open!\r\n");return0;}/** * @description: led_read - read function * @param - filp: device file * @param - buf: user buffer * @param - cnt: count of bytes to read * @param - offt: offset of file * @return: count of bytes read */staticssize_tled_read(structfile*filp,char__user*buf,size_tcnt,loff_t*offt){printk("led read!\r\n");return0;}/** * @description: led_write - write function * @param - filp: device file * @param - buf: user buffer * @param - cnt: count of bytes to write * @param - offt: offset of file * @return: count of bytes written */staticssize_tled_write(structfile*filp,constchar__user*buf,size_tcnt,loff_t*offt){intretvalue=0;unsignedchardatabuf[1];unsignedcharledstat;printk("led write!\r\n");/* copy user data to kernel buffer */retvalue=copy_from_user(databuf,buf,cnt);if(retvalue<0){printk("kernel write failed!\r\n");return-EFAULT;}/* get led status */ledstat=databuf[0];/* switch led */led_switch(ledstat);return0;}/** * @description: led_release - release function * @param - inode: inode of device file * @param - filp: device file * @return: 0 on success, -1 on failure */staticintled_release(structinode*inode,structfile*filp){printk("led release!\r\n");return0;}staticstructfile_operationsled_fops={.owner=THIS_MODULE,.open=led_open,.read=led_read,.write=led_write,.release=led_release};/** * @description: led_init - initialization function * @return: 0 on success, -1 on failure */staticint__initled_init(void){intretvalue=0;u32 val=0;/* Init LED *//* Register address mapping */IMX6U_CCM_CCGR1=ioremap(CCM_CCGR1_BASE,0x4);SW_MUX_GPIO1_IO03=ioremap(SW_MUX_GPIO1_IO03_BASE,0x4);SW_PAD_GPIO1_IO03=ioremap(SW_PAD_GPIO1_IO03_BASE,0x4);GPIO1_DR=ioremap(GPIO1_DR_BASE,0x4);GPIO1_GDIR=ioremap(GPIO1_GDIR_BASE,0x4);/* Enable GPIO1 clock */val=readl(IMX6U_CCM_CCGR1);val&=~(3<<26);/* Clear bits 26 and 27 */val|=(3<<26);/* Set bits 26 and 27 */writel(val,IMX6U_CCM_CCGR1);/* Set GPIO1_IO03 as GPIO output */writel(5,SW_MUX_GPIO1_IO03);/* Set GPIO1_IO03 as GPIO push-pull output */writel(0x10B0,SW_PAD_GPIO1_IO03);/* Set GPIO1_IO03 as output */val=readl(GPIO1_GDIR);val&=~(1<<3);val|=(1<<3);writel(val,GPIO1_GDIR);/* Switch LED off */led_switch(LEDOFF);retvalue=register_chrdev(LED_MAJOR,LED_NAME,&led_fops);if(retvalue<0){printk("led register failed!\r\n");return-EIO;}printk("led register success!\r\n");return0;}/** * @description: led_exit - exit function * @brief: unregister chrdev */staticvoid__exitled_exit(void){iounmap(IMX6U_CCM_CCGR1);iounmap(SW_MUX_GPIO1_IO03);iounmap(SW_PAD_GPIO1_IO03);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);unregister_chrdev(LED_MAJOR,LED_NAME);printk("led unregister success!\r\n");}/* module init and exit */module_init(led_init);module_exit(led_exit);/* module information */MODULE_LICENSE("GPL");MODULE_AUTHOR("pzs");

3.2 编写测试APP

功能:编写测试 APP,led 驱动加载成功以后手动创建/dev/led 节点,应用 APP 通过操作/dev/led文件来完成对 LED 设备的控制。向/dev/led 文件写 0 表示关闭 LED 灯,写 1 表示打开 LED 灯。

实现:在ledApp.c 文件里面输入如下内容:

#include"stdio.h"#include"unistd.h"#include"sys/types.h"#include"sys/stat.h"#include"fcntl.h"#include"stdlib.h"#include"string.h"#defineLEDOFF0#defineLEDON1/** * @brief: Main function * @param argc: The number of command line arguments * @param argv: An array of command line arguments * @return: 0 on success, -1 on failure */intmain(intargc,char*argv[]){intfd,retvalue;char*filename;unsignedchardatabuf[1];if(argc!=3){printf("usage: %s <device_path> <operation>\n",argv[0]);return-1;}filename=argv[1];fd=open(filename,O_RDWR);if(fd<0){printf("open %s failed!\n",filename);return-1;}databuf[0]=atoi(argv[2]);retvalue=write(fd,databuf,sizeof(databuf));if(retvalue<0){printf("write %s failed!\n",filename);close(fd);return-1;}/* close device file */retvalue=close(fd);if(retvalue<0){printf("close %s failed!\n",filename);return-1;}return0;}

3.3 编写Makefile

编写Makefile用于编译驱动程序:

KERNELDIR := /home/pzs/linux/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga CURRENT_PATH := $(shell pwd) ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- obj-m := led.o build: kernel_modules kernel_modules: $(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules clean: $(MAKE) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

4 编译测试

4.1 编译

4.1.1 编译驱动程序

输入如下命令编译出驱动模块文件:

pzs@pzs-jammy:~/linux/drivers/2_led$ make make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C /home/pzs/linux/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga M=/home/pzs/linux/drivers/2_led modules make[1]: Entering directory '/home/pzs/linux/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga' CC [M] /home/pzs/linux/drivers/2_led/led.o Building modules, stage 2. MODPOST 1 modules CC /home/pzs/linux/drivers/2_led/led.mod.o LD [M] /home/pzs/linux/drivers/2_led/led.ko make[1]: Leaving directory '/home/pzs/linux/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga'

编译成功以后就会生成一个名为“led.ko”的驱动模块文件。

4.1.2 编译测试APP

输入如下命令编译测试 ledApp.c 测试程序:

pzs@pzs-jammy:~/linux/drivers/2_led$ arm-linux-gnueabihf-gcc ledApp.c -o ledApp

编译成功以后就会生成 ledApp 这个应用程序。

4.2 运行测试

参考【Linux驱动篇】字符设备驱动开发从NFS启动根文件系统,然后将编译出来的led.ko和ledApp这两个文件拷贝到开发板的rootfs/lib/modules/4.1.15目录中:

pzs@pzs-jammy:~/linux/drivers/2_led$ sudo cp ./led.ko ./ledApp ~/linux/nfs/ubuntu_rootfs/lib/modules/4.1.15/

在开发板,输入如下命令加载 led.ko 驱动模块:

root@alientek_imx6ul:/home/pzs# cd /lib/modules/4.1.15/ root@alientek_imx6ul:/lib/modules/4.1.15# modprobe led

驱动加载成功以后创建“/dev/led”设备节点,命令如下:

root@alientek_imx6ul:/lib/modules/4.1.15# mknod /dev/led c 200 0

驱动节点创建成功以后就可以使用 ledApp 软件来测试驱动是否工作正常,输入如下命令打开 LED 灯:

root@alientek_imx6ul:/lib/modules/4.1.15# ./ledApp /dev/led 1

输入如下命令关闭 LED 灯:

root@alientek_imx6ul:/lib/modules/4.1.15# ./ledApp /dev/led 0

如果要卸载驱动的话输入如下命令即可:

root@alientek_imx6ul:/lib/modules/4.1.15# rmmod led.ko

至此,我们成功编写了第一个真正的 Linux 驱动设备程序。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/1/25 23:20:54

【2025最新】基于SpringBoot+Vue的物资综合管理系统管理系统源码+MyBatis+MySQL

摘要 随着企业规模的扩大和信息化程度的提高&#xff0c;物资管理成为企业运营中不可或缺的重要环节。传统物资管理方式依赖人工操作&#xff0c;效率低下且容易出错&#xff0c;难以满足现代企业对物资高效调配、实时监控和数据分析的需求。物资综合管理系统的开发旨在解决这些…

作者头像 李华
网站建设 2026/1/24 21:48:18

数学梗图数据集分析报告:999张高质量数学主题幽默图片资源

数学梗图数据集分析报告 引言与背景 在当代数字内容生态中&#xff0c;视觉化的教育资源扮演着越来越重要的角色。数学作为基础学科&#xff0c;常常被视为抽象和难以理解的领域&#xff0c;而通过幽默的视觉表达方式可以有效降低学习门槛&#xff0c;激发学习兴趣。本数据集包…

作者头像 李华
网站建设 2026/1/28 0:17:14

AI核心知识59——大语言模型之Mamba(简洁且通俗易懂版)

Mamba 是大语言模型领域中一个极具颠覆性的新架构。如果说 Transformer&#xff08;ChatGPT 背后的架构&#xff09;是目前的“武林盟主”&#xff0c;那么 Mamba 就是那个试图挑战盟主地位的“绝世高手”。它的核心目标只有一个&#xff1a;解决 Transformer 在处理“超长文本…

作者头像 李华