例程代码路径:ELF 1开发板资料包3-例程源码3-2 驱动例程源码4_Pinctrl和GPIO子系统myled
前面章节的驱动,没有对实际的硬件进行操作,主要通过一些打印信了解了驱动的加载流程和调用流程。本章节开始,以实际操作硬件为例进行讲解,操作之前需要先解压一份从NXP官网下载的源码,避免加载驱动时获取不到硬件资源。如果有使用git进行管理源码,只需要新建一个分支,然后切换到第一次提交的commit号即可。
首先使用GPIO子系统API函数编写一个用户层能够控制LED_Y亮灭的驱动。
复用GPIO
(一)查看原理图和引脚复用表格,可以得到LED_Y由GPIO1_18控制,所以我们需要配置GPIO1_18引脚为输出,而且能够在用户空间控制它输出高电平还是低电平。
(二)在设备树中的IOMUX中添加引脚的复用,设备树路径:linux-imx-imx_4.1.15_2.0.0_ga/arch/arm/boot/dts/imx6ull-elf1-emmc.dts,将其复用成GPIO功能,并检查设备树中是否有其它的地方也用到了此引脚,如果用到了就需要将其屏蔽掉,避免复用冲突。
pinctrl_hog_1: hoggrp-1 { fsl,pins = < MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */ MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059 /* SD1 VSELECT */ MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059 /* SD1 RESET */ MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0x17059 /*LED_Y*/ >; }; |
添加后效果如下:
(三)编译设备树
. /opt/fsl-imx-x11/4.1.15-2.0.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi elf@ubuntu:~/work/linux-imx-imx_4.1.15_2.0.0_ga$ make dtbs |
编译生成的设备树文件为imx6ull-elf1-emmc.dtb,参考《01-0 ELF1、ELF1S开发板_快速启动手册_V1》4.4节单独更新设备树。
驱动源码myled.c编写
(一)头文件引用
#include <linux/module.h> // 包含模块相关函数的头文件 #include <linux/fs.h> // 包含文件系统相关函数的头文件 #include <linux/uaccess.h> // 包含用户空间数据访问函数的头文件 #include <linux/cdev.h> //包含字符设备头文件 #include <linux/device.h> //包含设备节点相关的头文件 #include <linux/gpio.h> //包含gpio操作函数的相关头文件 |
(二)创建相关宏定义
#define DEVICE_NAME "mydevice" // 设备名称 #define LED_IOC_MAGIC 'k' //定义ioctl幻数 #define SET_LED_ON _IO(LED_IOC_MAGIC, 0) //定义LED开命令 #define SET_LED_OFF _IO(LED_IOC_MAGIC, 1) //定义LED关命令 #define GPIO_LED_PIN_NUM 18 //定义操作的GPIO编号 |
(1)ioctl幻数
ioctl幻数是一个用于区分ioctl命令的标识符。它是一个唯一的整数值,并且通常使用ASCII字符来表示。幻数的目的是确保ioctl命令的唯一性,以免不同设备的命令发生冲突。
(2)_IO宏定义
_IO宏定义用于创建无参数的ioctl命令代码。
_IO(LED_IOC_MAGIC, 0),LED_IOC_MAGIC表示定义的幻数,0或者1表示具体的命令代码。
(3)GPIO编号
在imx6ull上确定GPIO编号的公式为:GPIOn_IOx=(n-1)*32+x。
因为选择的引脚为GPIO1_IO18,所以通过(n-1)*32+x计算得到的编号为18。
(三)相关变量定义
static dev_t dev_num; //分配的设备号 struct cdev my_cdev; //字符设备指针 int major; //主设备号 int minor; //次设备号 static struct class *my_led; static struct device *my_device; |
(四)mydevice_init(void)函数的实现
static int __init mydevice_init(void) { int ret; //释放之前申请的GPIO,避免申请失败 gpio_free(GPIO_LED_PIN_NUM); // 申请GPIO,并判断是否申请成功 if (gpio_request(GPIO_LED_PIN_NUM, "led_run")) { printk("request %s gpio faile n", "led_run"); return -1; } // 注册字符设备驱动程序 ret = alloc_chrdev_region(&dev_num,0,1,DEVICE_NAME); if (ret < 0) { printk(KERN_ALERT "Failed to allocate device number: %dn", ret); return ret; } major=MAJOR(dev_num); minor=MINOR(dev_num); printk(KERN_INFO "major number: %dn",major); printk(KERN_INFO "minor number: %dn",minor);
my_cdev.owner = THIS_MODULE; cdev_init(&my_cdev,&fops); cdev_add(&my_cdev,dev_num,1);
// 创建设备类 my_led = class_create(THIS_MODULE, "my_led"); if (IS_ERR(my_led)) { pr_err("Failed to create classn"); return PTR_ERR(my_led); } // 创建设备节点并关联到设备类 my_device = device_create(my_led, NULL, MKDEV(major, minor), NULL, "my_device"); if (IS_ERR(my_device)) { pr_err("Failed to create devicen"); class_destroy(my_led); return PTR_ERR(my_device); } printk(KERN_INFO "Device registered successfully.n"); return 0; } |
主要添加了gpio_free()和gpio_request()函数的调用,gpio_free()函数原型如下:
void gpio_free(unsigned int gpio); |
(1)参数说明
gpio:要释放的GPIO引脚的编号。
(2)返回值
无返回值。
(3)函数功能
gpio_free()函数释放先前请求的GPIO引脚,并将其解除分配。释放后的GPIO引脚可以被其他驱动程序或设备重新请求使用。
gpio_request()函数原型如下:
int gpio_request(unsigned int gpio, const char *label); |
(1)参数说明
gpio:要请求的GPIO引脚的编号。
label:用于标识该GPIO引脚的描述字符串。
(2)返回值
成功时,返回0表示请求成功。
失败时,返回负数错误代码,表示请求失败。
(3)函数功能
gpio_request()函数请求一个GPIO引脚并进行相关的配置,以便将其用于后续的操作。
它会检查GPIO引脚是否已经被其他驱动程序或内核使用,如果被占用则请求失败。
请求成功后,该GPIO引脚将被锁定,使其他驱动程序无法再次请求该引脚。
(五)mydevice_exit(void)函数的实现
static void __exit mydevice_exit(void) { // 在这里执行驱动程序的清理操作 //释放申请的GPIO资源 gpio_free(GPIO_LED_PIN_NUM); // 销毁设备节点 device_destroy(my_led, MKDEV(major, minor)); // 销毁设备类 class_destroy(my_led); // 删除字符设备 cdev_del(&my_cdev); // 注销字符设备驱动程序 unregister_chrdev(0, DEVICE_NAME);
printk(KERN_INFO "Device unregistered.n"); } |
(六)定义fops结构体
static struct file_operations fops = { .owner = THIS_MODULE, .open = device_open, .release = device_release, .unlocked_ioctl = myled_ioctl, }; |
此处用到了unlocked_ioctl函数,unlocked_ioctl函数是Linux内核中用于设备控制和通信的重要函数之一。它允许用户空间程序与设备驱动程序进行交互,发送特定的命令和参数来执行设备相关的操作。
unlocked_ioctl()函数原型如下:
long unlocked_ioctl(struct file *filp, unsigned int cmd, unsigned long arg); |
(1)参数说明
filp:指向struct file结构的指针,表示要执行ioctl操作的文件。
cmd:请求代码,用于指定要执行的具体操作。请求代码通常是一个整数,可以是预定义的常量或自定义的命令。
arg:参数,根据具体的请求代码而定。
(2)返回值
unlocked_ioctl函数返回一个long类型的值,通常用于表示操作的成功与否,返回值为负数表示出现错误。
(七)device_open()函数的实现:
static int device_open(struct inode *inode, struct file *file) { // 在这里处理设备打开的操作 //设置GPIO引脚为输出模式,并将其初始化为高电平 gpio_direction_output(GPIO_LED_PIN_NUM, 1); printk(KERN_INFO "This is device_open.n"); return 0; } |
gpio_direction_output()函数原型如下:
int gpio_direction_output(unsigned int gpio, int value); |
(1)参数说明
gpio:要配置的GPIO引脚的编号。
value:设置GPIO引脚的初始输出值,0表示低电平,非零表示高电平。
(2)返回值
成功时,返回0表示设置成功。
失败时,返回负数错误代码,表示设置失败。
(3)函数功能
gpio_direction_output()函数将指定的GPIO引脚配置为输出模式,并设置其初始输出值。
在配置为输出模式后,可以使用其他函数(如gpio_set_value())来更改GPIO引脚的输出值。
(八)myled_ioctl()函数的实现
static long myled_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
switch (cmd) { case SET_LED_ON: // 设置GPIO引脚为低电平 gpio_set_value(GPIO_LED_PIN_NUM, 0); break;
case SET_LED_OFF: //设置GPIO引脚为高电平 gpio_set_value(GPIO_LED_PIN_NUM, 1); break;
default: return -ENOTTY; }
return 0; } |
gpio_set_value()函数原型如下:
void gpio_set_value(unsigned int gpio, int value); |
(1)参数说明
gpio:要设置的GPIO引脚的编号。
value:要设置的输出值,0表示低电平,非零表示高电平。
(2)返回值
无返回值。
(3)函数功能
gpio_set_value()函数用于设置指定GPIO引脚的输出值。该函数会将GPIO引脚配置为输出模式(如果尚未配置),并设置其输出值为指定的值。
(九)device_release()函数的实现:
static int device_release(struct inode *inode, struct file *file) { // 在这里处理设备关闭的操作 //设置GPIO引脚为输出模式,并将置为高电平 gpio_direction_output(GPIO_LED_PIN_NUM, 1); printk(KERN_INFO "This is device_release.n");
return 0; } |
完整的驱动myled.c示例源码
#include <linux/module.h> // 包含模块相关函数的头文件 #include <linux/fs.h> // 包含文件系统相关函数的头文件 #include <linux/uaccess.h> // 包含用户空间数据访问函数的头文件 #include <linux/cdev.h> //包含字符设备头文件 #include <linux/device.h> #include <linux/gpio.h>
#define DEVICE_NAME "mydevice" // 设备名称 #define LED_IOC_MAGIC 'k' #define SET_LED_ON _IO(LED_IOC_MAGIC, 0) #define SET_LED_OFF _IO(LED_IOC_MAGIC, 1) #define GPIO_LED_PIN_NUM 18 static dev_t dev_num; //分配的设备号 struct cdev my_cdev; //字符设备指针 int major; //主设备号 int minor; //次设备号 static struct class *my_led; static struct device *my_device;
static int device_open(struct inode *inode, struct file *file) { // 在这里处理设备打开的操作 //设置GPIO引脚为输出模式,并将其初始化为高电平 gpio_direction_output(GPIO_LED_PIN_NUM, 1); printk(KERN_INFO "This is device_open.n"); return 0; }
static int device_release(struct inode *inode, struct file *file) { // 在这里处理设备关闭的操作 gpio_direction_output(GPIO_LED_PIN_NUM, 1); printk(KERN_INFO "This is device_release.n");
return 0; }
static long myled_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
switch (cmd) { case SET_LED_ON: // 设置GPIO引脚为低电平 gpio_set_value(GPIO_LED_PIN_NUM, 0); break;
case SET_LED_OFF: //设置GPIO引脚为高电平 gpio_set_value(GPIO_LED_PIN_NUM, 1); break;
default: return -ENOTTY; }
return 0; }
static struct file_operations fops = { .owner = THIS_MODULE, .open = device_open, .release = device_release, .unlocked_ioctl = myled_ioctl, }; static int __init mydevice_init(void) { int ret;
// 在这里执行驱动程序的初始化操作 //释放之前申请的GPIO,避免申请失败 gpio_free(GPIO_LED_PIN_NUM); if (gpio_request(GPIO_LED_PIN_NUM, "led_run")) { printk("request %s gpio faile n", "led_run"); return -1; } // 注册字符设备驱动程序 ret = alloc_chrdev_region(&dev_num,0,1,DEVICE_NAME); if (ret < 0) { printk(KERN_ALERT "Failed to allocate device number: %dn", ret); return ret; } major=MAJOR(dev_num); minor=MINOR(dev_num); printk(KERN_INFO "major number: %dn",major); printk(KERN_INFO "minor number: %dn",minor);
my_cdev.owner = THIS_MODULE; cdev_init(&my_cdev,&fops); cdev_add(&my_cdev,dev_num,1);
// 创建设备类 my_led = class_create(THIS_MODULE, "my_led"); if (IS_ERR(my_led)) { pr_err("Failed to create classn"); return PTR_ERR(my_led); } // 创建设备节点并关联到设备类 my_device = device_create(my_led, NULL, MKDEV(major, minor), NULL, "my_device"); if (IS_ERR(my_device)) { pr_err("Failed to create devicen"); class_destroy(my_led); return PTR_ERR(my_device); } printk(KERN_INFO "Device registered successfully.n"); return 0; } static void __exit mydevice_exit(void) { // 在这里执行驱动程序的清理操作 //释放申请的GPIO资源 gpio_free(GPIO_LED_PIN_NUM); // 销毁设备节点 device_destroy(my_led, MKDEV(major, minor)); // 销毁设备类 class_destroy(my_led); // 删除字符设备 cdev_del(&my_cdev); // 注销字符设备驱动程序 unregister_chrdev(0, DEVICE_NAME);
printk(KERN_INFO "Device unregistered.n"); }
module_init(mydevice_init); module_exit(mydevice_exit);
MODULE_LICENSE("GPL"); // 指定模块的许可证信息 MODULE_AUTHOR("Your Name"); // 指定模块的作者信息 MODULE_DESCRIPTION("A simple character device driver"); // 指定模块的描述信息 |
编译
复制7.4.3驱动中的Makefile文件,将其中的copy_form_user.o修改为myled.o,效果如下:
. /opt/fsl-imx-x11/4.1.15-2.0.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi elf@ubuntu:~/work/test/04_Pinctrl和GPIO子系统/myled$ make |
编译成ko模块并拷贝到开发板中。
编写测试应用源码led_app.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/ioctl.h> #include <errno.h> #include <fcntl.h>
#define DEV_NAME "/dev/my_device" #define LED_IOC_MAGIC 'k' #define SET_LED_ON _IO(LED_IOC_MAGIC, 0) #define SET_LED_OFF _IO(LED_IOC_MAGIC, 1)
int main(int argc, char *argv[]) { int i; int fd = 0; fd = open (DEV_NAME, O_RDONLY); if (fd < 0) { perror("Open "DEV_NAME" Failed!n"); exit(1); }
for (i=0; i<5; i++) { ioctl(fd, SET_LED_ON); sleep(1); ioctl(fd, SET_LED_OFF); sleep(1); }
close(fd); return 0; } |
编译应用
. /opt/fsl-imx-x11/4.1.15-2.0.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi elf@ubuntu:~/work/test/04_Pinctrl和GPIO子系统/led_app$ $CC led_app.c -o led_app |
将编译生成的应用拷贝到开发板中。
测试
root@ELF1:~# insmod myled.ko major number: 245 minor number: 0 Device registered successfully. root@ELF1:~# ./led_app This is device_open. This is device_release. root@ELF1:~# rmmod myled.ko |
执行led_app后,黄色LED灯循环闪烁5次。