• 正文
    • 复用GPIO
    • 驱动源码myled.c编写
    • 完整的驱动myled.c示例源码
    • 编译
    • 编写测试应用源码led_app.c
    • 编译应用
    • 测试
  • 相关推荐
申请入驻 产业图谱

飞凌嵌入式ElfBoard ELF 1板卡-Pinctrl和GPIO子系统之LED驱动

03/27 08:55
224
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

例程代码路径: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次。

点赞
收藏
评论
分享
加入交流群
举报

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录