Linux驱动开发基础(LED驱动)

所学来自百问网

目录

[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驱动程序框架

图解:

  1. 应用程序(APP):用户通过APP发起文件或设备的操作请求,如打开(open)、读取(read)、写入(write)和控制(ioctl)等。

  2. C库:这些操作请求首先被传递给C库,C库提供了高级别的API接口,如fopen、fread、fwrite和ioctl等,这些接口封装了底层细节,简化了应用程序的开发。

  3. 内核(Kernel):C库中的系统调用(svc)将请求传递给内核。内核中的SVC异常处理机制负责解析这些调用,并转换为内核级别的函数执行,如drv_open、drv_read、drv_write和drv_ioctl,这些函数专门处理与驱动程序的交互。

  4. 驱动(Driver):驱动程序是内核与硬件之间的桥梁。它接收来自内核的指令,执行具体的硬件操作,如打开设备、从设备读取数据、向设备写入数据或控制设备行为等。

  5. 硬件(Hardware):最终,驱动程序与硬件直接交互,完成数据的实际读取、写入或控制操作。

  6. 数据拷贝:在数据从硬件传输到应用程序或从应用程序传输到硬件的过程中,涉及到内核空间与用户空间之间的数据拷贝。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

相关推荐
xiaozhiwise27 分钟前
Makefile 之 自动化变量
linux
意疏3 小时前
【Linux 篇】Docker 的容器之海与镜像之岛:于 Linux 系统内探索容器化的奇妙航行
linux·docker
BLEACH-heiqiyihu3 小时前
RedHat7—Linux中kickstart自动安装脚本制作
linux·运维·服务器
一只爱撸猫的程序猿3 小时前
一个简单的Linux 服务器性能优化案例
linux·mysql·nginx
ahadee4 小时前
蓝桥杯每日真题 - 第19天
c语言·vscode·算法·蓝桥杯
Theliars4 小时前
C语言之字符串
c语言·开发语言
Reese_Cool4 小时前
【数据结构与算法】排序
java·c语言·开发语言·数据结构·c++·算法·排序算法
lantiandianzi4 小时前
基于单片机的多功能跑步机控制系统
单片机·嵌入式硬件
哔哥哔特商务网4 小时前
高集成的MCU方案已成电机应用趋势?
单片机·嵌入式硬件