当前位置: 首页 > news >正文

【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 驱动设备程序。

http://www.cnnetsun.cn/news/141981.html

相关文章:

  • 【2025最新】基于SpringBoot+Vue的物资综合管理系统管理系统源码+MyBatis+MySQL
  • 数学梗图数据集分析报告:999张高质量数学主题幽默图片资源
  • 【毕业设计】SpringBoot+Vue+MySQL 美食信息推荐系统平台源码+数据库+论文+部署文档
  • AI核心知识59——大语言模型之Mamba(简洁且通俗易懂版)
  • SpringBoot+Vue 流浪动物救助平台平台完整项目源码+SQL脚本+接口文档【Java Web毕设】
  • SpringBoot+Vue 手机销售网站管理平台源码【适合毕设/课设/学习】Java+MySQL
  • DPJ-138 基于单片机的指纹密码锁系统设计(源代码+proteus仿真)
  • SpringBoot+Vue 流浪动物救助平台管理平台源码【适合毕设/课设/学习】Java+MySQL
  • 【2025最新】基于SpringBoot+Vue的考试系统管理系统源码+MyBatis+MySQL
  • 企业级流浪动物救助平台管理系统源码|SpringBoot+Vue+MyBatis架构+MySQL数据库【完整版】
  • 物资综合管理系统信息管理系统源码-SpringBoot后端+Vue前端+MySQL【可直接运行】
  • MLX 有多快?在 8 个苹果硅芯片和 4 个 CUDA GPU 上的全面基准测试
  • 生产就绪特性-从开发到部署的完整解决方案
  • 【前端知识点总结】Promise的介绍
  • 2026年河北省职业院校技能大赛“网络系统管理”(高职组)系统服务-Linux部署样题
  • 当 AI 写论文遭遇 “答辩级拷问”:9 款主流工具的生死考验
  • 科研人的 “数据魔咒”:明明数据在手,却挖不出核心结论
  • [特殊字符] 写论文软件哪个好?先看毕业党最在意的 4 大核心标准
  • 历年贵州大学计算机保研复试机试真题
  • AI产业融合纵深发展,治理创新护航智能未来
  • 生成式AI重构内容生态,人机协同定义创作新范式
  • 软件世界的契约:理解开源协议的逻辑与边界
  • vue和springboot框架开发的小程序 智能包裹配送服务管理系统_q3k407ra
  • C 语言输入与输出(I/O)详解
  • 软件测试成本的多维解析与优化路径
  • 5-脱氧-L-阿拉伯糖—结构独特的稀有单糖,药物设计与合成化学的宝贵砌块 CAS:13039-56-0
  • 2-乙酰胺基-1,3,4,6-四-O-乙酰基-2-脱氧-5-硫代-α-D-吡喃葡萄糖 —— 糖化学与药物研发的关键砌块 CAS:67561-97-1
  • 群体分析如何改变你的客户洞察
  • 别再为BGM被下架了,可以生成带声音且无版权素材的AI,真的来了
  • vue和springboot框架开发的校园商店零售管理系统_pt87nuk3