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

文章目录

嵌入式Linux开发整体过程

Linux启动过程

基于pinctrl和gpio的点灯

一、修改设备树

二、编写驱动程序

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

2.编写驱动层API

三、编写应用程序

四、整体过程

完结!!!!!

嵌入式Linux开发整体过程

移植uboot

移植Linux(包含修改设备树)

构建根文件系统

根据设备树编写驱动程序

简单写一个应用程序去测试驱动程序

Linux启动过程

Linux系统要启动就必须需要一个bootloader 程序,也就说芯片上电以后先运行一段bootloader程序。这段bootloader程序会先初始化DDR等外设,然后将Linux内核从flash(NAND,NOR FLASH, SD, MMC等)拷贝到DDR中,最后启动Linux内核。

U-Boot是使用最广泛的bootloader,uboot其实就是一个功能十分丰富的裸机程序,丰富到什么程度呢?uboot已经支持液晶屏、网络、usb等高级功能。

将移植好的uboot程序下载到芯片,上电后执行uboot,此时还没有启动内核,运行的是uboot这个裸机程序,但是此时我们可以做很多事情,比如操作内存、网络连接、读取文件等(通过串口进行交互)。

在运行uboot裸机程序时我们就可以将移植好的Linux和设备树拷贝到DRAM中,常见的有两种,其一是将Linux和设备树下载到EMMC或NAND中,uboot将其拷贝到DRAM中去;其二是uboot通过网络(nfs或者tftp)下载Linux和设备树到DRAM中。(不管用什么方法,目的就是将Linux和设备树存到DRAM中去就行),然后用bootz(boot、bootm)启动Linux(uboot会传一些参数给Linux)。

基于pinctrl和gpio的点灯

工作概述

修改设备树,添加led设备节点(pinctrl配置信息、gpio配置信息)

编写驱动程序

编写应用程序

整体过程

一、修改设备树

添加led设备对应的pinctrl节点

在imx6ull-alientek-emmc.dts设备树文件中的iomuxc节点下的imx6ull-evk子节点中添加led的pinctrl配置信息(主要是说明led管脚复用为什么类型,电气属性)。如下:

pinctrl-led: ledgrp {

fsl, pins = <

MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0

>;

}

pinctrl-led: ledgrp {

fsl, pins = <

MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0

>;

}

添加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这个设备用到的是哪个管脚。

/* 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编号 */

};

重新编译设备树,并用新的设备树启动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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值