第一句子网 - 唯美句子、句子迷、好句子大全
第一句子网 > Linux 驱动开发 五:Linux LED驱动开发

Linux 驱动开发 五:Linux LED驱动开发

时间:2024-07-05 05:51:12

相关推荐

Linux 驱动开发 五:Linux LED驱动开发

Linux下的任何外设驱动,最终都是要配置相应的硬件寄存器。所以本章的LED灯驱动最终也是对I.MX6ULLIO口进行配置,与裸机实验不同的是,在Linux下编写驱动要符合Linux的驱动框架。

一、地址映射

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

在编写驱动之前,我们需要先简单了解一下MMU这个神器,MMU全称叫做Memory Manage Unit,也就是内存管理单元。在老版本的Linux中要求处理器必须有MMU,但是现在Linux内核已经支持无MMU的处理器了。MMU主要完成的功能如下:

1、完成虚拟空间到物理空间的映射。

2、内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性。

物理内存和虚拟内存之间的转换,需要用到两个函数:ioremapiounmap

1、ioremap

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

#define ioremap(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE)void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size, unsigned int mtype){return arch_ioremap_caller(phys_addr, size, mtype, __builtin_return_address(0));}

2、iounmap

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

void iounmap (volatile void __iomem *addr)

二、I/O内存访问函数

这里说的I/O输入/输出的意思。这里涉及到两个概念I/O端口I/O内存

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

外部寄存器内存映射到内存空间时,称为I/O内存

但是对于ARM来说没有I/O空间这个概念,因此ARM体系下只有I/O内存(可以直接理解为内存)。使用ioremap函数将寄存器的物 理地址映射到虚拟地址以后,我们就可以直接通过指针访问这些地址,但是Linux内核不建议这么做,而是推荐使用一组操作函数来对映射后的内存进行读写操作。

1、读操作函数

u8 readb(const volatile void __iomem *addr)u16 readw(const volatile void __iomem *addr)u32 readl(const volatile void __iomem *addr)

readbreadwreadl这三个函数分别对应8bit16bit32bit读操作,参数addr就是要读取写内存地址,返回值就是读取到的数据。

2、写操作函数

void writeb(u8 value, volatile void __iomem *addr)void writew(u16 value, volatile void __iomem *addr)void writel(u32 value, volatile void __iomem *addr)

writebwritewwritel这三个函数分别对应8bit16bit32bit写操作,参数value是要写入的数值,addr是要写入的地址。

三、硬件原理图分析

/OnlyLove_/article/details/121757151

四、实验程序编写

1、工程配置

创建工程文件c_cpp_properties.json,并添加头文件路径。

{"configurations": [{"name": "Linux","includePath": ["${workspaceFolder}/**","/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga/include","/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga/arch/arm/include","/home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_ga/arch/arm/include/generated"],"defines": [],"compilerPath": "/usr/bin/gcc","cStandard": "gnu17","cppStandard": "gnu++14","intelliSenseMode": "linux-gcc-x64"}],"version": 4}

2、搭建驱动框架

1、led.c

#include "linux/init.h"#include "linux/module.h"#include "linux/fs.h"#include "linux/types.h"// struct inode 声明在 linux/fs.h 中// struct file 声明在 linux/fs.h 中int led_open (struct inode *i, struct file *f){// printk 声明在 linux/printk.h 中printk("led open!\r\n");return 0;}int led_release (struct inode *i, struct file *f){printk("led release!\r\n");return 0;}// ssize_t 定义在 linux/types.h 中// __user 定义在 linux/compiler.h 中// size_t 定义在 linux/types.h 中// loff_t 定义在 linux/types.h 中ssize_t led_read (struct file *f, char __user *b, size_t c, loff_t * l){printk("led read!\r\n");return 0;}ssize_t led_write (struct file *f, const char __user *b, size_t c, loff_t *l){printk("led write!\r\n");return 0;}// 声明在 linux/fs.h 头文件中static struct file_operations fops = {.open = led_open,.release = led_release,.read = led_read,.write = led_write,};/* 驱动入口函数 */static int __init led_init(void){/* 入口函数具体内容 */int retvalue = 0;// 声明在 linux/fs.h 头文件中retvalue = register_chrdev(200,"chrdev",&fops);if(retvalue < 0){/* 字符设备注册失败 */}return 0;}/* 驱动出口函数 */static void __exit led_exit(void){/* 出口函数具体内容 */// 声明在 linux/fs.h 头文件中unregister_chrdev(200,"chrdev");}// 声明在 linux/init.h 头文件中/* 将上面两个函数指定为驱动的入口和出口函数 */module_init(led_init);module_exit(led_exit);// 声明在 linux/module.h 头文件中MODULE_LICENSE("GPL");

2、makefile

KERNELDIR := /home/onlylove/linux/linux/lq_linux/linux-imx-rel_imx_4.1.15_2.1.0_gaCURRENT_PATH := $(shell pwd)obj-m := led.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modulesclean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

3、LED 初始化

1、映射相关寄存器地址

/* 寄存器物理地址 */#define CCM_CCGR1_BASE(0X020C406C)#define SW_MUX_GPIO1_IO03_BASE(0X020E0068)#define SW_PAD_GPIO1_IO03_BASE(0X020E02F4)#define GPIO1_DR_BASE(0X0209C000)#define GPIO1_GDIR_BASE(0X0209C004)/* 映射后的寄存器虚拟地址指针 */static void __iomem *IMX6U_CCM_CCGR1;static void __iomem *SW_MUX_GPIO1_IO03;static void __iomem *SW_PAD_GPIO1_IO03;static void __iomem *GPIO1_DR;static void __iomem *GPIO1_GDIR;

以上通过define定义物理地址,通过变量保存虚拟地址。物理地址和虚拟地址映射通过open函数完成,代码如下:

int led_open (struct inode *i, struct file *f){// printk 声明在 linux/printk.h 中printk("led open!\r\n");/* 寄存器地址映射 */IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);return 0;}

取消地址映射通过close函数完成,代码如下:

int led_release (struct inode *i, struct file *f){printk("led release!\r\n");/* 取消映射 */iounmap(IMX6U_CCM_CCGR1);iounmap(SW_MUX_GPIO1_IO03);iounmap(SW_PAD_GPIO1_IO03);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);return 0;}

2、初始化

初始化包括配置时钟、引脚复用、引脚电平。

初始化代码在open函数中完成,添加初始化程序后代码如下:

int led_open (struct inode *i, struct file *f){u32 val = 0;// printk 声明在 linux/printk.h 中printk("led open!\r\n");/* 1、寄存器地址映射 */IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);/* 2、使能GPIO1时钟 */val = readl(IMX6U_CCM_CCGR1);val &= ~(3 << 26);/* 清楚以前的设置 */val |= (3 << 26);/* 设置新值 */writel(val, IMX6U_CCM_CCGR1);/* 3、设置GPIO1_IO03的复用功能,将其复用为* GPIO1_IO03,最后设置IO属性。*/writel(5, SW_MUX_GPIO1_IO03);/*寄存器SW_PAD_GPIO1_IO03设置IO属性*bit 16:0 HYS关闭*bit [15:14]: 00 默认下拉*bit [13]: 0 kepper功能*bit [12]: 1 pull/keeper使能*bit [11]: 0 关闭开路输出*bit [7:6]: 10 速度100Mhz*bit [5:3]: 110 R0/6驱动能力*bit [0]: 0 低转换率*/writel(0x10B0, SW_PAD_GPIO1_IO03);/* 4、设置GPIO1_IO03为输出功能 */val = readl(GPIO1_GDIR);val &= ~(1 << 3);/* 清除以前的设置 */val |= (1 << 3);/* 设置为输出 */writel(val, GPIO1_GDIR);/* 5、默认关闭LED */val = readl(GPIO1_DR);val |= (1 << 3);writel(val, GPIO1_DR);return 0;}

3、控制 LED 灭亮

控制灯的灭亮,由write函数完成。

1、从用户空间拷贝数据

extern inline longcopy_from_user(void *to, const void __user *from, long n){return __copy_tofrom_user(to, (__force void *)from, n, from);}/** to:内核空间数据存储缓存* from:指向用户空间数据缓存区* n:拷贝数据长度*/

2、LED 亮灭控制

#define LEDOFF 0/* 关灯 */#define LEDON 1/* 开灯 *//** @description: LED打开/关闭* @param - sta : LEDON(0) 打开LED,LEDOFF(1) 关闭LED* @return : 无*/void led_switch(u8 sta){u32 val = 0;if(sta == LEDON) {val = readl(GPIO1_DR);val &= ~(1 << 3);writel(val, GPIO1_DR);}else if(sta == LEDOFF) {val = readl(GPIO1_DR);val|= (1 << 3);writel(val, GPIO1_DR);}}

3、write 代码

/** @description: 向设备写数据 * @param - f : 设备文件,表示打开的文件描述符* @param - b : 要写给设备写入的数据* @param - c : 要写入的数据长度* @param - l : 相对于文件首地址的偏移* @return : 写入的字节数,如果为负值,表示写入失败*/ssize_t led_write (struct file *f, const char __user *b, size_t c, loff_t *l){int retvalue;unsigned char databuf[1];unsigned char ledstat;printk("led write!\r\n");retvalue = copy_from_user(databuf, b, c);if(retvalue < 0) {printk("kernel write failed!\r\n");return -EFAULT;}ledstat = databuf[0];/* 获取状态值 */if(ledstat == LEDON) {led_switch(LEDON);/* 打开LED灯 */} else if(ledstat == LEDOFF) {led_switch(LEDOFF);/* 关闭LED灯 */}return 0;}

4、驱动代码

#include "linux/init.h"#include "linux/module.h"#include "linux/fs.h"#include "linux/types.h"#include "asm/io.h"#include "asm/uaccess.h"#define LED_MAJOR200/* 主设备号 */#define LED_NAME"led" /* 设备名字 */#define LEDOFF 0/* 关灯 */#define LEDON 1/* 开灯 *//* 寄存器物理地址 */#define CCM_CCGR1_BASE(0X020C406C)#define SW_MUX_GPIO1_IO03_BASE(0X020E0068)#define SW_PAD_GPIO1_IO03_BASE(0X020E02F4)#define GPIO1_DR_BASE(0X0209C000)#define GPIO1_GDIR_BASE(0X0209C004)/* 映射后的寄存器虚拟地址指针 */static void __iomem *IMX6U_CCM_CCGR1;static void __iomem *SW_MUX_GPIO1_IO03;static void __iomem *SW_PAD_GPIO1_IO03;static void __iomem *GPIO1_DR;static void __iomem *GPIO1_GDIR;/** @description: LED打开/关闭* @param - sta : LEDON(0) 打开LED,LEDOFF(1) 关闭LED* @return : 无*/void led_switch(u8 sta){u32 val = 0;if(sta == LEDON) {val = readl(GPIO1_DR);val &= ~(1 << 3);writel(val, GPIO1_DR);}else if(sta == LEDOFF) {val = readl(GPIO1_DR);val|= (1 << 3);writel(val, GPIO1_DR);}}// struct inode 声明在 linux/fs.h 中// struct file 声明在 linux/fs.h 中int led_open (struct inode *i, struct file *f){u32 val = 0;// printk 声明在 linux/printk.h 中printk("led open!\r\n");/* 1、寄存器地址映射 */IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);/* 2、使能GPIO1时钟 */val = readl(IMX6U_CCM_CCGR1);val &= ~(3 << 26);/* 清楚以前的设置 */val |= (3 << 26);/* 设置新值 */writel(val, IMX6U_CCM_CCGR1);/* 3、设置GPIO1_IO03的复用功能,将其复用为* GPIO1_IO03,最后设置IO属性。*/writel(5, SW_MUX_GPIO1_IO03);/*寄存器SW_PAD_GPIO1_IO03设置IO属性*bit 16:0 HYS关闭*bit [15:14]: 00 默认下拉*bit [13]: 0 kepper功能*bit [12]: 1 pull/keeper使能*bit [11]: 0 关闭开路输出*bit [7:6]: 10 速度100Mhz*bit [5:3]: 110 R0/6驱动能力*bit [0]: 0 低转换率*/writel(0x10B0, SW_PAD_GPIO1_IO03);/* 4、设置GPIO1_IO03为输出功能 */val = readl(GPIO1_GDIR);val &= ~(1 << 3);/* 清除以前的设置 */val |= (1 << 3);/* 设置为输出 */writel(val, GPIO1_GDIR);/* 5、默认关闭LED */val = readl(GPIO1_DR);val |= (1 << 3);writel(val, GPIO1_DR);return 0;}int led_release (struct inode *i, struct file *f){printk("led release!\r\n");/* 取消映射 */iounmap(IMX6U_CCM_CCGR1);iounmap(SW_MUX_GPIO1_IO03);iounmap(SW_PAD_GPIO1_IO03);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);return 0;}// ssize_t 定义在 linux/types.h 中// __user 定义在 linux/compiler.h 中// size_t 定义在 linux/types.h 中// loff_t 定义在 linux/types.h 中ssize_t led_read (struct file *f, char __user *b, size_t c, loff_t * l){printk("led read!\r\n");return 0;}/** @description: 向设备写数据 * @param - f : 设备文件,表示打开的文件描述符* @param - b : 要写给设备写入的数据* @param - c : 要写入的数据长度* @param - l : 相对于文件首地址的偏移* @return : 写入的字节数,如果为负值,表示写入失败*/ssize_t led_write (struct file *f, const char __user *b, size_t c, loff_t *l){int retvalue;unsigned char databuf[1];unsigned char ledstat;printk("led write!\r\n");retvalue = copy_from_user(databuf, b, c);if(retvalue < 0) {printk("kernel write failed!\r\n");return -EFAULT;}ledstat = databuf[0];/* 获取状态值 */if(ledstat == LEDON) {led_switch(LEDON);/* 打开LED灯 */} else if(ledstat == LEDOFF) {led_switch(LEDOFF);/* 关闭LED灯 */}return 0;}// 声明在 linux/fs.h 头文件中static struct file_operations fops = {.open = led_open,.release = led_release,.read = led_read,.write = led_write,};/* 驱动入口函数 */static int __init led_init(void){/* 入口函数具体内容 */int retvalue = 0;// 声明在 linux/fs.h 头文件中retvalue = register_chrdev(LED_MAJOR,LED_NAME,&fops);if(retvalue < 0){/* 字符设备注册失败 */}return 0;}/* 驱动出口函数 */static void __exit led_exit(void){/* 出口函数具体内容 */// 声明在 linux/fs.h 头文件中unregister_chrdev(LED_MAJOR,LED_NAME);}// 声明在 linux/init.h 头文件中/* 将上面两个函数指定为驱动的入口和出口函数 */module_init(led_init);module_exit(led_exit);// 声明在 linux/module.h 头文件中MODULE_LICENSE("GPL");

5、应用程序代码

#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include "stdio.h"int main(int argc, char *argv[]){int fd = 0, retvalue = 0;char writebuf[1] = "";fd = open(argv[1],O_RDWR);if(fd < 0){printf("Can't open file %s\r\n", argv[1]);return -1;}writebuf[0] = atoi(argv[2]);// 打开 ledwrite(fd, writebuf, 1);retvalue = close(fd);if(retvalue < 0){printf("Can't close file %s\r\n", argv[1]);return -1;}return 0;}

编译应用程序指令如下:

arm-linux-gnueabihf-gcc led_app.c -o led_app

6、验证

1、加载驱动

加载驱动

/ # lsbinetcled_app linuxrc procsbintmpdevled.ko libmntrootsysusr/ # insmod led.ko/ #/ # lsmodModule Size Used by Tainted: Gled 2274 0

加载驱动也可以使用modprobe命令。

查看驱动加载情况

/ # cat /proc/devicesCharacter devices:1 mem4 /dev/vc/04 tty5 /dev/tty5 /dev/console5 /dev/ptmx7 vcs10 misc13 input29 fb81 video4linux89 i2c90 mtd116 alsa128 ptm136 pts180 usb189 usb_device200 led207 ttymxc216 rfcomm226 drm249 ttyLP250 iio251 watchdog252 ptp253 pps254 rtcBlock devices:1 ramdisk259 blkext7 loop8 sd31 mtdblock65 sd66 sd67 sd68 sd69 sd70 sd71 sd128 sd129 sd130 sd131 sd132 sd133 sd134 sd135 sd179 mmc/ #

2、创建设备文件

命令如下所示:

mknod /dev/led c 200 0

查看创建结果:

/ # ls /dev/autofs ram11tty36bus ram12tty37console ram13tty38cpu_dma_latencyram14tty39dri ram15tty4fb0 ram2tty40fullram3tty41fuseram4tty42hwrngram5tty43i2c-0ram6tty44i2c-1ram7tty45inputram8tty46kmsgram9tty47led random tty48loop-control rtc0tty49loop0snd tty5loop1tty tty50loop2tty0tty51loop3tty1tty52loop4tty10tty53loop5tty11tty54loop6tty12tty55loop7tty13tty56mem tty14tty57memory_bandwidth tty15tty58mmcblk0 tty16tty59mmcblk0p1 tty17tty6mmcblk1 tty18tty60mmcblk1boot0 tty19tty61mmcblk1boot1 tty2tty62mmcblk1p1 tty20tty63mmcblk1p2 tty21tty7mmcblk1rpmb tty22tty8mxc_asrc tty23tty9network_latencytty24ttymxc0network_throughput tty25ttymxc1nulltty26ubi_ctrlpps0tty27urandompps1tty28vcsptmxtty29vcs1ptp0tty3vcsaptp1tty30vcsa1pts tty31video0pxp_devicetty32watchdogram0tty33watchdog0ram1tty34zeroram10tty35⚌A-⚌/ #

通过以上日志可以确定驱动文件加载成功。

3、运行应用程序

点亮LED,指令如下:

./led_app /dev/led 1

熄灭LED,指令如下:

./led_app /dev/led 1

4、测试

执行app程序日志如下:

/ # ./led_app /dev/led 1led open!led write!led release!/ # ./led_app /dev/led 0led open!led write!led release!/ #

通过观察开发板上LED灯状态,可以确定LED可以正常打开和关闭。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。