例程代码路径:ELF 1开发板资料包\03-例程源码\03-2 驱动例程源码\04_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 // 包含模块相关函数的头文件 #include // 包含文件系统相关函数的头文件 #include // 包含用户空间数据访问函数的头文件 #include //包含字符设备头文件 #include //包含设备节点相关的头文件 #include //包含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: %d\n", ret); return ret; } major=MAJOR(dev_num); minor=MINOR(dev_num); printk(KERN_INFO "major number: %d\n",major); printk(KERN_INFO "minor number: %d\n",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 class\n"); 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 device\n"); 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 // 包含模块相关函数的头文件 #include // 包含文件系统相关函数的头文件 #include // 包含用户空间数据访问函数的头文件 #include //包含字符设备头文件 #include #include #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: %d\n", ret); return ret; } major=MAJOR(dev_num); minor=MINOR(dev_num); printk(KERN_INFO "major number: %d\n",major); printk(KERN_INFO "minor number: %d\n",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 class\n"); 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 device\n"); 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 #include #include #include #include #include #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次。 |
234 浏览 0 评论
迅为RK3568开发板helloworld 驱动实验-驱动编写
484 浏览 0 评论
飞凌嵌入式ElfBoard ELF 1板卡-platform总线驱动简单示例
635 浏览 0 评论
智能配电新纪元:基于飞凌嵌入式T536核心板的DTU解决方案
819 浏览 0 评论
飞凌嵌入式ElfBoard ELF 1板卡-Linux系统中的中断之按键中断驱动
1582 浏览 0 评论