嵌入式Linux点灯-pinctrl和gpio子系统

嵌入式Linux开发整体过程

  1. 移植uboot
  2. 移植Linux(包含修改设备树)
  3. 构建根文件系统
  4. 根据设备树编写驱动程序
  5. 简单写一个应用程序去测试驱动程序

Linux启动过程

  1. Linux系统要启动就必须需要一个bootloader 程序,也就说芯片上电以后先运行一段bootloader程序。这段bootloader程序会先初始化DDR等外设,然后将Linux内核从flash(NAND,NOR FLASH, SD, MMC等)拷贝到DDR中,最后启动Linux内核。
  2. U-Boot是使用最广泛的bootloader,uboot其实就是一个功能十分丰富的裸机程序,丰富到什么程度呢?uboot已经支持液晶屏、网络、usb等高级功能。
  3. 将移植好的uboot程序下载到芯片,上电后执行uboot,此时还没有启动内核,运行的是uboot这个裸机程序,但是此时我们可以做很多事情,比如操作内存、网络连接、读取文件等(通过串口进行交互)。
  4. 在运行uboot裸机程序时我们就可以将移植好的Linux和设备树拷贝到DRAM中,常见的有两种,其一是将Linux和设备树下载到EMMC或NAND中,uboot将其拷贝到DRAM中去;其二是uboot通过网络(nfs或者tftp)下载Linux和设备树到DRAM中。(不管用什么方法,目的就是将Linux和设备树存到DRAM中去就行),然后用bootz(boot、bootm)启动Linux(uboot会传一些参数给Linux)。

基于pinctrl和gpio的点灯

工作概述

  1. 修改设备树,添加led设备节点(pinctrl配置信息、gpio配置信息)
  2. 编写驱动程序
  3. 编写应用程序
  4. 整体过程

一、修改设备树

  1. 添加led设备对应的pinctrl节点
    在imx6ull-alientek-emmc.dts设备树文件中的iomuxc节点下的imx6ull-evk子节点中添加led的pinctrl配置信息(主要是说明led管脚复用为什么类型,电气属性)。如下:
pinctrl-led: ledgrp {
        fsl, pins = <
            MX6UL_PAD_GPIO1_IO03__GPIO1_IO03    0x10B0
        >;
}
  1. 添加led设备节点
    在imx6ull-alientek-emmc.dts设备树文件中的根节点下添加led设备节点,这个节点就是整体描述led这个外设的。如下:
gpioled {
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "atkalpha-gpioled";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl-led>;
        led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
        status = "okay";
    }
//第6行:说明gpioled这个设备用到的管脚的配置信息
//第7行:这个其实就是gpio子系统,说明gpioled这个设备用到的是GPIO1_IO3,且低电平有效。
//其实pinctrl-led节点中也隐含了gpioled这个设备用到的是哪个管脚。
  1. 重新编译设备树,并用新的设备树启动Linux

二、编写驱动程序

用结构体组织gpioled设备属性

/* gpioled设备结构体 */
struct gpioled_dev{
    dev_t devid;            /* 设备号   */
    struct cdev cdev;       /* cdev     */
    struct class *class;    /* 类        */
    struct device *device;  /* 设备    */
    int major;              /* 主设备号   */
    int minor;              /* 次设备号   */
    struct device_node  *nd; /* 设备节点 */
    int led_gpio;           /* led所使用的GPIO编号        */
};

1.驱动进入/退出函数编写

第7行:获取gpioled设备节点(后面就可以获取这个节点的属性)

/* 1、获取设备节点:gpioled */
gpioled.nd = of_find_node_by_path("/gpioled")

第16行:获取GPIO编号(后面操作gpioled都是通过这个编号进行的)

/* 2、 获取设备树中的gpio属性,得到LED所使用的LED编号 */
gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);

第24行:设置gpioled为输出,并输出高电平

/* 3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯 */
ret = gpio_direction_output(gpioled.led_gpio, 1);

后面就是创建字符设备、设备号这些工作了。和没用pinctrl、gpio子系统相比,此处更加简洁,没用pinctrl和gpio子系统时,还需在入口函数中读取设备树中gpioled的寄存器,然后重映射、初始化,对gpioled的操作也是要基于寄存器的,十分低效(和裸机开发没什么区别)。

static int __init led_init(void)
{
    int ret = 0;

    /* 设置LED所使用的GPIO */
    /* 1、获取设备节点:gpioled */
    gpioled.nd = of_find_node_by_path("/gpioled");
    if(gpioled.nd == NULL) {
        printk("gpioled node not find!\r\n");
        return -EINVAL;
    } else {
        printk("gpioled node find!\r\n");
    }

    /* 2、 获取设备树中的gpio属性,得到LED所使用的LED编号 */
    gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
    if(gpioled.led_gpio < 0) {
        printk("can't get led-gpio");
        return -EINVAL;
    }
    printk("led-gpio num = %d\r\n", gpioled.led_gpio);

    /* 3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯 */
    ret = gpio_direction_output(gpioled.led_gpio, 1);
    if(ret < 0) {
        printk("can't set gpio!\r\n");
    }

    /* 注册字符设备驱动 */
    /* 1、创建设备号 */
    if (gpioled.major) {        /*  定义了设备号 */
        gpioled.devid = MKDEV(gpioled.major, 0);
        register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
    } else {                        /* 没有定义设备号 */
        alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);  /* 申请设备号 */
        gpioled.major = MAJOR(gpioled.devid);   /* 获取分配号的主设备号 */
        gpioled.minor = MINOR(gpioled.devid);   /* 获取分配号的次设备号 */
    }
    printk("gpioled major=%d,minor=%d\r\n",gpioled.major, gpioled.minor);   
    
    /* 2、初始化cdev */
    gpioled.cdev.owner = THIS_MODULE;
    cdev_init(&gpioled.cdev, &gpioled_fops);
    
    /* 3、添加一个cdev */
    cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);

    /* 4、创建类 */
    gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
    if (IS_ERR(gpioled.class)) {
        return PTR_ERR(gpioled.class);
    }

    /* 5、创建设备 */
    gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
    if (IS_ERR(gpioled.device)) {
        return PTR_ERR(gpioled.device);
    }
    return 0;
}

static void __exit led_exit(void)
{
    /* 注销字符设备驱动 */
    cdev_del(&gpioled.cdev);/*  删除cdev */
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */

    device_destroy(gpioled.class, gpioled.devid);
    class_destroy(gpioled.class);
}

module_init(led_init);
module_exit(led_exit);

2.编写驱动层API

static struct file_operations gpioled_fops = {
    .owner = THIS_MODULE,
    .open = led_open,
    .read = led_read,
    .write = led_write,
    .release =  led_release,
};

看下主要的write函数:
第14行:filp->private_data就是gpioled这个设备
第25行:使led_gpio(这是在入口函数中获取的gpioled设备管脚的管脚号)这个管脚输出为低,对gpioled进行操作都是基于led_gpio这个管脚号。

/*
 * @description     : 向设备写数据 
 * @param - filp    : 设备文件,表示打开的文件描述符
 * @param - buf     : 要写给设备写入的数据
 * @param - cnt     : 要写入的数据长度
 * @param - offt    : 相对于文件首地址的偏移
 * @return          : 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
    int retvalue;
    unsigned char databuf[1];
    unsigned char ledstat;
    struct gpioled_dev *dev = filp->private_data;

    retvalue = copy_from_user(databuf, buf, cnt);
    if(retvalue < 0) {
        printk("kernel write failed!\r\n");
        return -EFAULT;
    }

    ledstat = databuf[0];       /* 获取状态值 */

    if(ledstat == LEDON) {  
        gpio_set_value(dev->led_gpio, 0);   /* 打开LED灯 */
    } else if(ledstat == LEDOFF) {
        gpio_set_value(dev->led_gpio, 1);   /* 关闭LED灯 */
    }
    return 0;
}

三、编写应用程序

/*
 * @description     : main主程序
 * @param - argc    : argv数组元素个数
 * @param - argv    : 具体参数
 * @return          : 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
    int fd, retvalue;
    char *filename;
    unsigned char databuf[1];
    
    if(argc != 3){
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];

    /* 打开led驱动 */
    fd = open(filename, O_RDWR);
    if(fd < 0){
        printf("file %s open failed!\r\n", argv[1]);
        return -1;
    }

    databuf[0] = atoi(argv[2]); /* 要执行的操作:打开或关闭 */

    /* 向/dev/led文件写入数据 */
    retvalue = write(fd, databuf, sizeof(databuf));
    if(retvalue < 0){
        printf("LED Control Failed!\r\n");
        close(fd);
        return -1;
    }

    retvalue = close(fd); /* 关闭文件 */
    if(retvalue < 0){
        printf("file %s close failed!\r\n", argv[1]);
        return -1;
    }
    return 0;
}

四、整体过程

将驱动程序编译成模块,使其可以动态加入到Linux内核中去

KERNELDIR := ../inux-alientk (Linux源码目录)
CURRENT_PATH := $(shell pwd)

obj-m := gpioled.o

build: kernel_modules

kernel_modules:
    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

clean:
    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

启动uboot,通过网络下载Linux和设备树到DRAM中去,然后启动Linux系统

tftp 80800000 zImage    /* linux 镜像文件 */
tftp 83000000 imx6ull-alientek-emmc.dtb    /* 编译后的设备树文件 */
bootz 80800000 - 83000000    /* 启动linux */

通过串口进行交互。加载驱动模块

insmod gpioled.ko
或
depmod
modprobe gpioled.ko

用应用程序测试驱动(点灯)

./ledApp /dev/gpioled 1    /* 关闭led */
./ledApp /dev/gpioled 0    /* 打开led */

卸载驱动模块

rmmod module.ko

完结!!!!!

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值