所学来自百问网
目录
[1. LED原理](#1. LED原理)
[2. 普适的GPIO引脚操作方法](#2. 普适的GPIO引脚操作方法)
[2.1 GPIO模块的一般结构](#2.1 GPIO模块的一般结构)
[2.2 GPIO框图](#2.2 GPIO框图)
[2.3 寄存器的操作](#2.3 寄存器的操作)
[2.3.1 一般的操作方式](#2.3.1 一般的操作方式)
[2.3.2 高效的操作方式](#2.3.2 高效的操作方式)
[3. 基于IMX6UL_6ULL的GPIO操作方法](#3. 基于IMX6UL_6ULL的GPIO操作方法)
[3.1 GPIO框图](#3.1 GPIO框图)
[3.2 CCM](#3.2 CCM)
[3.3 IOMUXC](#3.3 IOMUXC)
[3.4 GPIO模块内部](#3.4 GPIO模块内部)
[3.5 读写GPIO](#3.5 读写GPIO)
[4. LED驱动程序框架](#4. LED驱动程序框架)
[5. 基于IMX6UL_6ULL的LED操作方法](#5. 基于IMX6UL_6ULL的LED操作方法)
[6. 基于IMX6UL_6ULL的LED驱动程序](#6. 基于IMX6UL_6ULL的LED驱动程序)
[6.1 驱动代码](#6.1 驱动代码)
[6.2 应用代码](#6.2 应用代码)
[6.3 Makefile](#6.3 Makefile)
[6.4 效果](#6.4 效果)
1. LED原理
LED 样子有很多种,像插脚的,贴片的。
点亮LED需要通电源,同时为了保护LED,加个电阻减小电流。
控制LED灯的亮灭,可以手动开关LED,但在电子系统中,不可能让人来控制开关,通过编程,利用芯片的引脚去控制开关。
LED 的驱动方式,常见的有四种。
1.使用引脚输出3.3V点亮LED,输出0V熄灭LED。
2.使用引脚拉低到0V点亮LED,输出3.3V熄灭LED。
有的芯片为了省电等原因,其引脚驱动能力不足,这时可以使用三极管驱动。
3.使用引脚输出1.2V点亮LED,输出0V熄灭LED。
4.使用引脚输出0V点亮LED,输出1.2V熄灭LED。
由此,主芯片引脚输出高电平/低电平,即可改变LED状态,而无需关注GPIO 引脚输出的是3.3V还是1.2V。所以简称输出1或0:
-
逻辑1-->高电平
-
逻辑0-->低电平
2. 普适的GPIO引脚操作方法
GPIO: General-purpose input/output,通用的输入输出口
2.1 GPIO模块的一般结构
有多组GPIO,每组有多个GPIO
**使能:**电源/时钟
**模式(Mode):**引脚可用于GPIO或其他功能(引脚复用)
**方向:**引脚Mode设置为GPIO时,可以继续设置它是输出引脚,还是输入引脚
数值:
◼ 对于输出引脚,可以设置寄存器让它输出高、低电平
◼ 对于输入引脚,可以读取寄存器得到引脚的当前电平
2.2 GPIO框图
图解:
主芯片存在多种GPIO(0~N),每组GPIO有多个GPIO引脚(0~m)
若想让引脚输出高/低电平,操作步骤如下:
**1.使能模块:**通过Power/Clock control去给GPIO模块提供电源或时钟
**2.引脚模式选择:**通过多路选择器(IO_MUX)获取数据,数据可能来自GPIO模块也可能来自串口(UART)模块
**3.引脚方向:**输入模式还是输出模式
4.输出电平:
-
输出模式:设置寄存器
-
输入模式:读取寄存器
2.3 寄存器的操作
2.3.1 一般的操作方式
由上图可知,设置电平模式需要操作寄存器
以数据寄存器为例:
图解:
bit0 ~ bitM为寄存器的位,gpio0_0 ~ gpio0_m为引脚
设置引脚的电平的操作步骤:
1.读取寄存器的值 val = data_reg
2.修改所要设置引脚的电平的位,此处修改bit0 val = val | 0x01
3.将修改后的值写入寄存器 data_reg = val
**注意:**操作寄存器的原则,不能影响其他位的值
2.3.2 高效的操作方式
此方式取决于芯片中是否有设置寄存器和清零寄存器
设置寄存器和清零寄存器配合使用,直接修改寄存器的位的值,对其他寄存器的位不影响
设置寄存器的操作方式:
赋值1则设置为1,0则不受影响
修改bit0,只需直接让set_reg = 1即可
修改bit0和bit2,让set_reg = 1 | (1 << 2)即可
清零寄存器的操作方法:
赋值1则设置为0,0则不受影响
修改bit0,让clk_reg = 1即可让bit0设置为0
修改bit0和bit2,让clk_reg = 1 | (1 << 2)即可让bit0和bit2为0
3. 基于IMX6UL_6ULL的GPIO操作方法
3.1 GPIO框图
图解:
名词 | 解释 |
---|---|
CCM | Clock Controller Module (时钟控制模块) |
IOMUXC | IOMUX Controller,IO 复用控制器 |
GPIO | General-purpose input/output,通用的输入输出口 |
IMX6UL_6ULL有五组GPIO,每组引脚数分别如下:
-
GPIO1有32个引脚:GPIO1_IO0~GPIO1_IO31;
-
GPIO2有22个引脚:GPIO2_IO0~GPIO2_IO21;
-
GPIO3有29个引脚:GPIO3_IO0~GPIO3_IO28;
-
GPIO4有29个引脚:GPIO4_IO0~GPIO4_IO28;
-
GPIO5有12个引脚:GPIO5_IO0~GPIO5_IO11;
3.2 CCM
**作用:**用于设置是否向GPIO模块提供时钟
CCM_CCGR寄存器中某2位的取值含义如下:
**00:**该GPIO模块全程被关闭
**01:**该GPIO模块在CPU run mode情况下是使能的;在WAIT或STOP 模式下,关闭
**10:**保留
**11:**该GPIO模块全程使能
GPIO2时钟控制:
**图解:**即控制CCM中的CCGR1的CG15的31-30这两位使能GPIO2,取值如图4.3
GPIO1、GPIO5时钟控制:
GPIO3时钟控制:
GPIO4时钟控制:
3.3 IOMUXC
**作用:**设置引脚的模式(Mode、功能)
对于某个/某组引脚,IOMUXC中有2个寄存器用来设置它:
选择功能:
a) IOMUXC_SW_MUX_CTL_PAD_ < PADNAME>:Mux pad xxx,选择某个pad的功能
b) IOMUXC_SW_MUX_CTL_GRP_< GROUP NAME>:Mux grp xxx,选择某组引脚的功能
某个引脚,或是某组预设的引脚,都有8个可选的模式(alternate (ALT) MUX_MODE)。
如:
设置上下拉电阻等参数:
a) IOMUXC_SW_PAD_CTL_PAD_ <PAD_NAME>:pad pad xxx,设置某 个pad的参数
b) IOMUXC_SW_PAD_CTL_GRP_< GROUP NAME>:pad grp xxx,设 置某组引脚的参数
如:
3.4 GPIO模块内部
3.5 读写GPIO
1.设置CCM_CCGRx寄存器中某位使能对应的GPIO模块 // 默认是使能 的,上图省略了
2.设置IOMUX来选择引脚用于GPIO
3.设置GPIOx_GDIR中某位为0,把该引脚设置为输入功能
4.读GPIOx_DR或GPIOx_PSR得到某位的值(读GPIOx_DR返回的是 GPIOx_PSR的值)
1.设置CCM_CCGRx寄存器中某位使能对应的GPIO模块 // 默认是使能 的,上图省略了
2.设置IOMUX来选择引脚用于GPIO
3.设置GPIOx_GDIR中某位为1,把该引脚设置为输出功能
4.写GPIOx_DR某位的值
**注意:**可以设置该引脚的 loopback 功能,这样就可以从 GPIOx_PSR 中读到引脚的真实电平;你从 GPIOx_DR 中读回的只是上次设置的 值,它并不能反应引脚的真实电平,比如可能因为硬件故障导致该引脚跟地短路了,你通过设置GPIOx_DR让它输出高电平并不会起效果。
4. LED驱动程序框架
图解:
-
应用程序(APP):用户通过APP发起文件或设备的操作请求,如打开(open)、读取(read)、写入(write)和控制(ioctl)等。
-
C库:这些操作请求首先被传递给C库,C库提供了高级别的API接口,如fopen、fread、fwrite和ioctl等,这些接口封装了底层细节,简化了应用程序的开发。
-
内核(Kernel):C库中的系统调用(svc)将请求传递给内核。内核中的SVC异常处理机制负责解析这些调用,并转换为内核级别的函数执行,如drv_open、drv_read、drv_write和drv_ioctl,这些函数专门处理与驱动程序的交互。
-
驱动(Driver):驱动程序是内核与硬件之间的桥梁。它接收来自内核的指令,执行具体的硬件操作,如打开设备、从设备读取数据、向设备写入数据或控制设备行为等。
-
硬件(Hardware):最终,驱动程序与硬件直接交互,完成数据的实际读取、写入或控制操作。
-
数据拷贝:在数据从硬件传输到应用程序或从应用程序传输到硬件的过程中,涉及到内核空间与用户空间之间的数据拷贝。copy_to_user函数用于将数据从内核空间拷贝到用户空间,而copy_from_user则用于将数据从用户空间拷贝到内核空间。
5. 基于IMX6UL_6ULL的LED操作方法
原理图:
使能GPIO5:
设置GPIO5_3为GPIO模式:
设置GPIO5_3为输出:
设置GPIO5_3的输出电平:
6. 基于IMX6UL_6ULL的LED驱动程序
6.1 驱动代码
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/device.h>
#include <asm/io.h>
static int major;
static struct class *led_class;
// 寄存器物理地址
// 寄存器地址:0x02290000 + 0x14
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3; // 设置GPIO5_3的模式
// 寄存器地址:0x020AC004
static volatile unsigned int *GPIO5_GDIR; // 设置GPIO5_3的方向
// 寄存器地址:0x020AC000
static volatile unsigned int *GPIO5_DR; // 设置GPIO5_3的电平
ssize_t led_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
char val;
int err;
// 获取数据
err = copy_from_user(&val,buf,1);
// 设置寄存器 0/1
if(val)
{
// 设置LED打开 低电平
*GPIO5_DR &= ~(1 << 3);
}else{
// 设置LED关闭 高电平
*GPIO5_DR |= (1 << 3);
}
return 1;
}
int led_open (struct inode *node, struct file *file)
{
// 使能gpio5 默认使能 时钟也默认使能
// 配置gpio5_3引脚为GPIO
*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~0xf;
*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= 0x5;
*GPIO5_GDIR |= (1 << 3);
return 0;
}
static struct file_operations led_fops =
{
.owner = THIS_MODULE,
.open = led_open,
.write = led_write,
};
// 入口函数
static int __init led_init(void)
{
printk("%s %s %d\n",__FILE__,__FUNCTION__,__LINE__);
major = register_chrdev(0,"led_drv",&led_fops);
// ioremap 映射 获得虚拟地址
IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x02290000 + 0x14,4);
GPIO5_GDIR = ioremap(0x020AC004,4);
GPIO5_DR = ioremap(0x020AC000,4);
led_class = class_create(THIS_MODULE,"myled");
device_create(led_class,NULL,MKDEV(major,0),NULL,"myled"); /* dev/myled */
return 0;
}
// 出口函数
static void __exit led_exit(void)
{
iounmap(IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3);
iounmap(GPIO5_GDIR);
iounmap(GPIO5_DR);
device_destroy(led_class,MKDEV(major,0));
class_destroy(led_class);
unregister_chrdev(major,"led_drv");
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
6.2 应用代码
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
// ledtest /dev/myled on
// ledtest /dev/myled off
int main(int argc,char **argv)
{
int fd;
char status = 0;
if(argc != 3)
{
printf("Usage: %s <dev> <on|off>\n",argv[0]);
printf(" eg: %s /dev/myled on\n",argv[0]);
printf(" eg: %s /dev/myled off\n",argv[0]);
return -1;
}
// open
fd = open(argv[1],O_RDWR);
if(fd < 0)
{
printf("can not open %s\n",argv[0]);
return -1;
}
// write
if(strcmp(argv[2],"on") == 0)
{
status = 1;
}
write(fd,&status,1);
return 0;
}
6.3 Makefile
KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88
all:
make -C $(KERN_DIR) M=`pwd` modules
$(CROSS_COMPILE)gcc -o led_test led_test.c
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
rm -f led_test
obj-m += led_drv.o
6.4 效果
Linux:
开发板:
转载驱动并查看
打开led
关闭led