LED

  1. 无操作系统的LED 驱动 (裸机类似STM32)
  2. linux操作系统的LED驱动
  3. 设备树下的 platform 驱动编写
    1. 在设备树中创建设备节点
    2. platform驱动程序编写

1.使能时钟
2.初始化GPIO,比如输出功能,上/下拉,速度等。
3.是否IO复用,如果要将 IO 作为其它外设引脚使用的话就需要设置 IO 的复用功能。
4.设置GPIO输出高低电平

硬件配置:

参考IMX6ULL芯片手册<< IMX6ULL 参考手册.pdf >>

​ I.MX6ULL 的 IO 分为两类:SNVS 域的和通用的。SNVS( Secure Non-Volatile Storage)为安全的非易失性存储的IO,

本质上都是一样的。

I.MX6ULL 的 IO 命名与IO配置

​ “IOMUXC**_YY_**MUC_CTL_PADXX_XX

​ 前面的“YY”有 MUXPAD 两种命名,分别配置不同功能。
如:GPIO1_IO00有

IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00       //用来配置 GPIO1_IO00 复用功能
IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO00    //是来配置 GPIO1_IO00,包括速度设置、驱动能力设置、压摆率设置等等

​ 后面的“XX_XX”就是 GPIO 命名,如GPIO0_IO0、UART1_TX_DATA、JTAG_MOD等

GPIO配置

​ 上述是配置IO,GPIO 是 IO 众多复用功能中的一种,需要配置GPIO的一些功能。参考<< IMX6UL 参考手册 >>的“Chapter 26 G

eneral Purpose Input/Ouput(GPIO)”:

GPIO

当 IO 用作 GPIO 的时候需要设置的寄存器,一共有八个:DR、GDIR、PSR、ICR1、ICR2、EDGE_SEL、IMR 和 ISR.

其中 DR为数据寄存器。设置高低电平,若输出高电平 则GPIO.DR = 1;

​ GDIR为工作方向,即输入/输出

​ PSR 寄存器也是一个 GPIO 对应一个位,读取相应的位即可获取对应的 GPIO 的状态,也就是 GPIO 的高低电平值

I.MX6U GPIO 时钟使能

​ 参考钟《I.MX6UL 参考手册》“Chapter 18: Clock Controller Module(CCM)”,CMM 有 CCM_CCGR0~CCM_CCGR6 这 7 个寄存器,这 7 个寄存器控制着 I.MX6U 的所有外设时钟开关, 其中CCM_CCGR1的 bit27:26 位控制GPIO1的时钟使能。

CCM_CCGR1

LED0原理图:

LED0原理图

  1. DSO 数字信号输出,用来控制 LED。

  2. R9 510RR11 510R

    • R9、R11 是电阻编号。
    • 510R 表示 510 欧姆,用于限流保护 LED。
  3. DCDC 3V3
    这是 3.3V 电源,来自某个 DC-DC 转换器,给 LED 电路供电。

  4. GND
    地线。

  5. PWR
    可能是一个电源指示或电源控制引脚。

  6. GPIO 3 即GPIO1_IO03为控制LED0引脚

    ​ 图中三角形加箭头为发光二极管,代表正负极,发光二极管只能从阳极流向负极,当 GPIO1_IO03 输出低电平(0)的时候发光二极管 LED0 就会导通点亮,当 GPIO1_IO03 输出高电平(1)的时候发光二极管 LED0 不会导通,因此 LED0 也就不会点亮。所以 LED0 的亮灭取决于 GPIO1_IO03 的输出电平,输出 0 就亮,输出 1 就灭。

按照 8.1 小节中讲的,我们需要对 GPIO1_IO03 做如下设置:

  1. 寄存器映射

1****、使能 GPIO1 时钟

GPIO1 的时钟由 CCM_CCGR1 的 bit27 和 bit26 这两个位控制,将这两个位都设置位 11 即

可。本教程所有例程已经将 I.MX6U 的所有外设时钟都已经打开了,因此这一步可以不用做。

2****、设置 GPIO1_IO03 的复用功能

找到 GPIO1_IO03 的复用寄存器“IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03”的地址为

0X020E0068,然后设置此寄存器,将 GPIO1_IO03 这个 IO 复用为 GPIO 功能,也就是 ALT5。

3****、配置 GPIO1_IO03

找到 GPIO1_IO03 的配置寄存器“IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03”的地址为

0X020E02F4,根据实际使用情况,配置此寄存器。

4****、设置 GPIO

我们已经将 GPIO1_IO03 复用为了 GPIO 功能,所以我们需要配置 GPIO。找到 GPIO3 对

应的 GPIO 组寄存器地址,在《IMX6ULL 参考手册》的 1357 页,如图 8.3.1 所示

无操作系统的LED 驱动 (裸机类似STM32)

ToVirtual() 的作用是当系统启动了硬件 MMU 之后,根据物理地址和虚拟地址的映射关系,将寄存器的物理地址转化为虚拟地址

 1 #define reg_gpio_ctrl *(volatile int *)(ToVirtual(GPIO_REG_CTRL))  //指针指向的值 将物理地址转换为虚拟地址
 2 #define reg_gpio_data *(volatile int *)(ToVirtual(GPIO_REG_DATA))
 3 /* 初始化 LED */
 4 void LightInit(void)
 5 {
 6     reg_gpio_ctrl |= (1 << n); /* 设置 GPIO 为输出 */
 7 }
 8 
 9 /* 点亮 LED */
10 void LightOn(void)
11 {
12     reg_gpio_data |= (1 << n); /* 在 GPIO 上输出高电平 */
13 }
14 
15 /* 熄灭 LED */
16 void LightOff(void)
17 {
18 reg_gpio_data &= ~ (1 << n); /* 在 GPIO 上输出低电平 */
19 }

linux操作系统的LED驱动

LED为字符设备

结构体 file_operations、cdev,

Linux 内核模块声明用的 MODULE_AUTHOR、MODULE_LICENSE、module_init、module_

exit,

字符设备注册、分配和注销的函数 register_chrdev_region()、alloc_chrdev_

region()、unregister_chrdev_region() 等。

驱动中包含 light_init ()、light_cleanup ()、light_read()、light_write() 等函数

 1    #include .../* 包含内核中的多个头文件 */
  
 2  #define reg_gpio_ctrl *(volatile int *)(ToVirtual(GPIO_REG_CTRL))  //指针指向的值 将物理地址转换为虚拟地址
 2     #define reg_gpio_data *(volatile int *)(ToVirtual(GPIO_REG_DATA))
 3 /* 初始化 LED */
 4 void light_init(void)
 5 {
 6         reg_gpio_ctrl |= (1 << n); /* 设置 GPIO 为输出 */
 7 }
 8 
 9 /* 点亮 LE1    ```````````````````````````` */
10 void light_on(void)
11 {
12         reg_gpio_data |= (1 << n); /* 在 GPIO 上输出高电平 */
13 }
14 
15 /* 熄灭 LED */
16 void light_off(void)
17 {
18         reg_gpio_data &= ~ (1 << n); /* 在 GPIO 上输出低电平 */
19 }
     
     /* 设备结构体 */
 3     struct light_dev {
 4         struct cdev cdev; /* 字符设备 cdev 结构体 */
 5         unsigned char vaule; /* LED 亮时为 1,熄灭时为 0,用户可读写此值 */
 6     };
 7     struct light_dev *light_devp; 
 8     int light_major = LIGHT_MAJOR; 
 9     MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
 10 MODULE_LICENSE("Dual BSD/GPL");
 11 /* 打开和关闭函数 */
 12 int light_open(struct inode *inode, struct f ile *filp) 
 13 {
 14     struct light_dev *dev; 
 15     /* 获得设备结构体指针 */
 16     dev = container_of(inode->i_cdev, struct light_dev, cdev); 
 17     /* 让设备结构体作为设备的私有信息 */
 18     filp->private_data = dev; 
 19     return 0; 
 20 }
 21 int light_release(struct inode *inode, struct f ile *f ilp) 
 22 {
 23     return 0; 
 24 }
 25 /* 读写设备 : 可以不需要 */
 26 ssize_t light_read(struct f ile *f ilp, char __user *buf, size_t count, loff_t *f_pos) 
 28 {
 29     struct light_dev *dev = filp->private_data; /* 获得设备结构体 */
 30     if (copy_to_user(buf, &(dev->value), 1)) 
 31         return -EFAULT; 
 32     return 1; 
 33 }

 34 ssize_t light_write(struct f ile *f ilp, const char __user *buf, size_t count, loff_t *f_pos)
      {
 37         struct light_dev *dev = f ilp->private_data; 
 38     if (copy_from_user(&(dev->value), buf, 1)) 
 39         return -EFAULT; 
 40     /* 根据写入的值点亮和熄灭 LED */
 41     if (dev->value == 1) 
 42         light_on();
 43     else
 44         light_off();
 45     return 1; 
 46 }

 47 /* ioctl 函数 */
 48 int light_ioctl(struct inode *inode, struct f ile *f ilp, unsigned int cmd, unsigned long arg) 
 50 {
 51     struct light_dev *dev = f ilp->private_data; 
 52     switch (cmd) {
 53     case LIGHT_ON: 
 54         dev->value = 1; 
 55         light_on();
 56         break; 
 57     case LIGHT_OFF: 
 58         dev->value = 0; 
 59         light_off();
 60         break; 
 61     default: 
 62         /* 不能支持的命令 */
 63         return -ENOTTY; 
 64     }
 65     return 0; 
 66 }
 67 struct file_operations light_fops = {
 68     .owner = THIS_MODULE, 
 69     .read = light_read, 
 70     .write = light_write, 
 71     .ioctl = light_ioctl, 
 72     .open = light_open, 
 73     .release = light_release, 
 74 };

 75 /* 设置字符设备 cdev 结构体 */
    static void light_setup_cdev(struct light_dev *dev, int index) 
 77 {
 78     int err, devno = MKDEV(light_major, index); 
 79     cdev_init(&dev->cdev, &light_fops); 
 80     dev->cdev.owner = THIS_MODULE; 
 81     dev->cdev.ops = &light_fops; 
 82     err = cdev_add(&dev->cdev, devno, 1); 
 83     if (err) 
 84         printk(KERN_NOTICE "Error %d adding LED%d", err, index); 
 85 }

 86 /* 模块加载函数 */
 87 int light_init(void) 
 88 {
 89     int result; 
 90     dev_t dev = MKDEV(light_major, 0); 
 91     /* 申请字符设备号 */
 92     if (light_major) 
 93         result = register_chrdev_region(dev, 1, "LED");
 94     else {
 95         result = alloc_chrdev_region(&dev, 0, 1, "LED");
 96         light_major = MAJOR(dev); 
 97     }    
 98     if (result < 0) 
 99         return result; 
100     /* 分配设备结构体的内存 */
101     light_devp = kmalloc(sizeof(struct light_dev), GFP_KERNEL); 
102     if (!light_devp) {
103         result = -ENOMEM; 
104         goto fail_malloc; 
105     }
106     memset(light_devp, 0, sizeof(struct light_dev)); 
107     light_setup_cdev(light_devp, 0); 
108     light_gpio_init();
109     return 0; 
110     fail_malloc: 
111         unregister_chrdev_region(dev, light_devp); 
112     return result; 
113 }

114 /* 模块卸载函数 */
115 void light_cleanup(void) 
116 {
117     cdev_del(&light_devp->cdev); /* 删除字符设备结构体 */
118     kfree(light_devp); /* 释放在 light_init 中分配的内存 */
119     unregister_chrdev_region(MKDEV(light_major, 0), 1); /* 删除字符设备 */
120 }
121 module_init(light_init); 
122 module_exit(light_cleanup);

设备树下的 platform 驱动编写

在设备树中创建设备节点

毫无疑问,肯定要先在设备树中创建设备节点来描述设备信息,重点是要设置好 compatible 属性的值,因为 platform 总线需要通过设备节点的 compatible 属性值来匹配驱动!这点要切记。比如,我们可以编写如下所示的设备节点来描述我们本章实验要用到的 LED 这个设备: linux/arc/arm/boot/dts/imx6ull-alientek-emmc.dts

gpioled {
2 #address-cells = <1>;
3 #size-cells = <1>;
4 compatible = "atkalpha-gpioled";
5 pinctrl-names = "default";
6 pinctrl-0 = <&pinctrl_led>;
7 led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
8 status = "okay";
9 };

platform驱动程序编写

1 #include <linux/types.h>
2 #include <linux/kernel.h>
3 #include <linux/delay.h>
4 #include <linux/ide.h>
5 #include <linux/init.h>
6 #include <linux/module.h>
7 #include <linux/errno.h>
8 #include <linux/gpio.h>
9 #include <linux/cdev.h>
10 #include <linux/device.h>
11 #include <linux/of_gpio.h>
12 #include <linux/semaphore.h>
13 #include <linux/timer.h>
14 #include <linux/irq.h>
15 #include <linux/wait.h>
16 #include <linux/poll.h>
17 #include <linux/fs.h>
18 #include <linux/fcntl.h>
19 #include <linux/platform_device.h>
20 #include <asm/mach/map.h>
21 #include <asm/uaccess.h>
22 #include <asm/io.h>
23 /***************************************************************
24 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
25 文件名 : leddriver.c
26 作者 : 左忠凯
27 版本 : V1.0
28 描述 : 设备树下的 platform 驱动
29 其他 : 无
30 论坛 : www.openedv.com
31 日志 : 初版 V1.0 2019/8/13 左忠凯创建
32 ***************************************************************/
33 #define LEDDEV_CNT 1 /* 设备号长度 */
34 #define LEDDEV_NAME "dtsplatled" /* 设备名字 */
35 #define LEDOFF 0
36 #define LEDON 1
37 
38 /* leddev 设备结构体 */
39 struct leddev_dev{
     dev_t devid; /* 设备号 */
     struct cdev cdev; /* cdev */
     struct class *class; /* 类 */
     struct device *device; /* 设备 */
     int major; /* 主设备号 */ 
     struct device_node *node; /* LED 设备节点 */
     int led0; /* LED 灯 GPIO 标号 */
47 };
48 
49 struct leddev_dev leddev; /* led 设备 */
51 /*
52 * @description : LED 打开/关闭
53 * @param - sta : LEDON(0) 打开 LED,LEDOFF(1) 关闭 LED
54 * @return : 无
55 */
56 void led0_switch(u8 sta)
57 {
     if (sta == LEDON )
         gpio_set_value(leddev.led0, 0);
     else if (sta == LEDOFF)
         gpio_set_value(leddev.led0, 1);
62 }
63 
64 /*
65 * @description : 打开设备
66 * @param – inode : 传递给驱动的 inode
67 * @param - filp : 设备文件,file 结构体有个叫做 private_data 的成员变量
68 * 一般在 open 的时候将 private_data 指向设备结构体。
69 * @return : 0 成功;其他 失败
70 */
71 static int led_open(struct inode *inode, struct file *filp)
72 {
     filp->private_data = &leddev; /* 设置私有数据 */
     return 0;
75 }
76 
77 /*
78 * @description : 向设备写数据
79 * @param - filp : 设备文件,表示打开的文件描述符
80 * @param - buf : 要写给设备写入的数据
81 * @param - cnt : 要写入的数据长度
82 * @param – offt : 相对于文件首地址的偏移
83 * @return : 写入的字节数,如果为负值,表示写入失败
84 */
85 static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
86 {
     int retvalue;
     unsigned char databuf[2];
     unsigned char ledstat;
     
     retvalue = copy_from_user(databuf, buf, cnt);
     if(retvalue < 0) {    
     printk("kernel write failed!\r\n");
         return -EFAULT;}
 
     ledstat = databuf[0];
     if (ledstat == LEDON) {
         led0_switch(LEDON);
         } else if (ledstat == LEDOFF) {
             led0_switch(LEDOFF);}
    return 0;
    }
106
107 /* 设备操作函数 */
108 static struct file_operations led_fops = {
     .owner = THIS_MODULE,
     .open = led_open,
     .write = led_write,
112 };
113
114 /*
115 * @description : flatform 驱动的 probe 函数,当驱动与
116 * 设备匹配以后此函数就会执行
117 * @param - dev : platform 设备
118 * @return : 0,成功;其他负值,失败
119 */
120 static int led_probe(struct platform_device *dev)
121 { 
     printk("led driver and device was matched!\r\n");
     /* 1、设置设备号 */
     if (leddev.major) {
         leddev.devid = MKDEV(leddev.major, 0);
         register_chrdev_region(leddev.devid, LEDDEV_CNT,LEDDEV_NAME);
         } 
     else {
         alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT,LEDDEV_NAME);
          leddev.major = MAJOR(leddev.devid);
         }
131
132 /* 2、注册设备 */
133 cdev_init(&leddev.cdev, &led_fops);
134 cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);
135
136 /* 3、创建类 */
137 leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);
138 if (IS_ERR(leddev.class)) {
139 return PTR_ERR(leddev.class);
140 }
141
142 /* 4、创建设备 */
143 leddev.device = device_create(leddev.class, NULL, leddev.devid,
NULL, LEDDEV_NAME);
144 if (IS_ERR(leddev.device)) {
145 return PTR_ERR(leddev.device);
146 }
147
148 /* 5、初始化 IO */ 
149 leddev.node = of_find_node_by_path("/gpioled");
150 if (leddev.node == NULL){
151 printk("gpioled node nost find!\r\n");
152 return -EINVAL;
153 }
154 
155 leddev.led0 = of_get_named_gpio(leddev.node, "led-gpio", 0);
156 if (leddev.led0 < 0) {
157 printk("can't get led-gpio\r\n");
158 return -EINVAL;
159 }
160
161 gpio_request(leddev.led0, "led0");
162 gpio_direction_output(leddev.led0, 1); /*设置为输出,默认高电平 */
163 return 0;
164 }
165
166 /*
167 * @description : remove 函数,移除 platform 驱动的时候此函数会执行
168 * @param - dev : platform 设备
169 * @return : 0,成功;其他负值,失败
170 */
171 static int led_remove(struct platform_device *dev)
172 {
173 gpio_set_value(leddev.led0, 1); /* 卸载驱动的时候关闭 LED */
174
175 cdev_del(&leddev.cdev); /* 删除 cdev */
176 unregister_chrdev_region(leddev.devid, LEDDEV_CNT);
177 device_destroy(leddev.class, leddev.devid);
178 class_destroy(leddev.class);
179 return 0;
180 }
181
182 /* 匹配列表 */
183 static const struct of_device_id led_of_match[] = {
184 { .compatible = "atkalpha-gpioled" },
185 { /* Sentinel */ }
186 };
187
188 /* platform 驱动结构体 */
189 static struct platform_driver led_driver = {
190 .driver = {
191 .name = "imx6ul-led", /* 驱动名字,用于和设备匹配 */
192 .of_match_table = led_of_match, /* 设备树匹配表 */
193 },
194 .probe = led_probe,
195 .remove = led_remove,
196 };
197 
198 /*
199 * @description : 驱动模块加载函数
200 * @param : 无
201 * @return : 无
202 */
203 static int __init leddriver_init(void)
204 {
205 return platform_driver_register(&led_driver);
206 }
207
208 /*
209 * @description : 驱动模块卸载函数
210 * @param : 无
211 * @return : 无
212 */
213 static void __exit leddriver_exit(void)
214 {
215 platform_driver_unregister(&led_driver);
216 }
217
218 module_init(leddriver_init);
219 module_exit(leddriver_exit);
220 MODULE_LICENSE("GPL");
221 MODULE_AUTHOR("zuozhongkai");

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 351134995@qq.com

×

喜欢就点赞,疼爱就打赏