Linux下LED灯驱动原理

  Linux 下的任何外设驱动,最终都是要配置相应的硬件寄存器。所以本章的 LED 灯驱动最终也是对 I.MX6ULL 的 IO 口进行配置,与裸机实验不同的是,在 Linux 下编写驱动要符合 Linux的驱动框架。

1.地址映射

  在编写驱动之前,我们需要先简单了解一下 MMU,MMU 全称叫做 Memory Manage Unit,也就是内存管理单元。在老版本的 Linux 中要求处理器必须有 MMU,但是现在Linux 内核已经支持无 MMU 的处理器了。 MMU 主要完成的功能如下:
①、完成虚拟空间到物理空间的映射。
②、内存保护,设置存储器的访问权限,设置虚拟存储空间的缓冲特性。
  我们重点来看一下第①点,也就是虚拟空间到物理空间的映射,也叫做地址映射。首先了解两个地址概念:虚拟地址(VA,Virtual Address)、物理地址(PA, Physcical Address)。对于 32 位的处理器来说,虚拟地址范围是 2^32=4GB,我们的开发板上有 512MB 的 DDR3,这 512MB 的内存就是物理内存,经过 MMU 可以将其映射到整个 4GB 的虚拟空间。
  Linux 内核启动的时候会初始化 MMU,设置好内存映射,设置好以后 CPU 访问的都是虚拟地址。比如 I.MX6ULL 的 GPIO1_IO03 引脚的复用寄存器IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 的地址为 0X020E0068。如果没有开启 MMU 的话直接向 0X020E0068 这个寄存器地址写入数据就可以配置 GPIO1_IO03 的复用功能。现在开启了 MMU,并且设置了内存映射,因此就不能直接向 0X020E0068 这个地址写入数据了。我们必须得到 0X020E0068 这个物理地址在 Linux 系统里面对应的虚拟地址,这里就涉及到了物理内存和虚拟内存之间的转换,需要用到两个函数: ioremap 和 iounmap。
ioremap 函数
  ioremap 函 数 用 于 获 取 指 定 物 理 地 址 空 间 对 应 的 虚 拟 地 址 空 间 , 定 义 在arch/arm/include/asm/io.h 文件中,定义如下:

#define ioremap(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE)
void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size, unsigned int mtype){return arch_ioremap_caller(phys_addr, size, mtype,
__builtin_return_address(0));}

  ioremap 是个宏,有两个参数: cookie 和 size,真正起作用的是函数__arm_ioremap,此函数有三个参数和一个返回值,这些参数和返回值的含义如下:
  phys_addr:要映射给的物理起始地址。
  size:要映射的内存空间大小。
  mtype: ioremap 的类型,可以选择 MT_DEVICE、MT_DEVICE_NONSHARED、MT_DEVICE_CACHED 和MT_DEVICE_WC, ioremap 函数选择 MT_DEVICE。
  返回值: __iomem 类型的指针,指向映射后的虚拟空间首地址。
iounmap 函数
  卸载驱动的时候需要使用 iounmap 函数释放掉 ioremap 函数所做的映射, iounmap 函数原型如下:

void iounmap (volatile void __iomem *addr)

  iounmap 只有一个参数 addr,此参数就是要取消映射的虚拟地址空间首地址。

2. I/O 内存访问函数

  这里说的 I/O 是输入/输出的意思,并不是我们学习单片机的时候讲的 GPIO 引脚。这里涉及到两个概念: I/O 端口和 I/O 内存。当外部寄存器或内存映射到 IO 空间时,称为 I/O 端口。当外部寄存器或内存映射到内存空间时,称为 I/O 内存。但是对于 ARM 来说没有 I/O 空间这个概念,因此 ARM 体系下只有 I/O 内存(可以直接理解为内存)。使用 ioremap 函数将寄存器的物理地址映射到虚拟地址以后,我们就可以直接通过指针访问这些地址,但是 Linux 内核不建议这么做,而是推荐使用一组操作函数来对映射后的内存进行读写操作。

2.1 读操作函数

  读操作函数有以下几个:

u8 readb(const volatile void __iomem *addr)
u16 readw(const volatile void __iomem *addr)
u32 readl(const volatile void __iomem *addr)

  readb、 readw 和 readl 这三个函数分别对应 8bit、 16bit 和 32bit 读操作,参数 addr 就是要读取写内存地址,返回值就是读取到的数据。

2.2 写操作函数

  写操作函数有如下几个:

void writeb(u8 value, volatile void __iomem *addr)
void writew(u16 value, volatile void __iomem *addr)
void writel(u32 value, volatile void __iomem *addr)

  writeb、 writew 和 writel 这三个函数分别对应 8bit、 16bit 和 32bit 写操作,参数 value 是要写入的数值, addr 是要写入的地址。

程序编写

  我们要编写 Linux 下的 LED 灯驱动,可以通过应用程序对开发板上的LED 灯进行开关操作。
led.c

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>#define LED_MAJOR      200     /* 主设备号 */
#define LED_NAME        "led"     /* 设备名字 */#define LEDOFF    0               /* 关灯 */
#define LEDON   1               /* 开灯 *//* 寄存器物理地址 */
#define CCM_CCGR1_BASE              (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE      (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE      (0X020E02F4)
#define GPIO1_DR_BASE               (0X0209C000)
#define GPIO1_GDIR_BASE             (0X0209C004)/* 映射后的寄存器虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;/** @description       : LED打开/关闭* @param - sta   : LEDON(0) 打开LED,LEDOFF(1) 关闭LED* @return           : 无*/
void led_switch(u8 sta)
{u32 val = 0;if(sta == LEDON) {val = readl(GPIO1_DR);val &= ~(1 << 3);   writel(val, GPIO1_DR);}else if(sta == LEDOFF) {val = readl(GPIO1_DR);val|= (1 << 3);  writel(val, GPIO1_DR);}
}/** @description      : 打开设备* @param - inode     : 传递给驱动的inode* @param - filp   : 设备文件,file结构体有个叫做private_data的成员变量*                       一般在open的时候将private_data指向设备结构体。* @return             : 0 成功;其他 失败*/
static int led_open(struct inode *inode, struct file *filp)
{return 0;
}/** @description      : 从设备读取数据 * @param - filp  : 要打开的设备文件(文件描述符)* @param - buf    : 返回给用户空间的数据缓冲区* @param - cnt  : 要读取的数据长度* @param - offt  : 相对于文件首地址的偏移* @return             : 读取的字节数,如果为负值,表示读取失败*/
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{return 0;
}/** @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;retvalue = copy_from_user(databuf, buf, cnt);if(retvalue < 0) {printk("kernel write failed!\r\n");return -EFAULT;}ledstat = databuf[0];     /* 获取状态值 */if(ledstat == LEDON) { led_switch(LEDON);      /* 打开LED灯 */} else if(ledstat == LEDOFF) {led_switch(LEDOFF); /* 关闭LED灯 */}return 0;
}/** @description      : 关闭/释放设备* @param - filp   : 要关闭的设备文件(文件描述符)* @return             : 0 成功;其他 失败*/
static int led_release(struct inode *inode, struct file *filp)
{return 0;
}/* 设备操作函数 */
static struct file_operations led_fops = {.owner = THIS_MODULE,.open = led_open,.read = led_read,.write = led_write,.release =    led_release,
};/** @description : 驱动出口函数* @param       : 无* @return       : 无*/
static int __init led_init(void)
{int retvalue = 0;u32 val = 0;/* 初始化LED *//* 1、寄存器地址映射 */IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);/* 2、使能GPIO1时钟 */val = readl(IMX6U_CCM_CCGR1);val &= ~(3 << 26);   /* 清楚以前的设置 */val |= (3 << 26);   /* 设置新值 */writel(val, IMX6U_CCM_CCGR1);/* 3、设置GPIO1_IO03的复用功能,将其复用为*    GPIO1_IO03,最后设置IO属性。*/writel(5, SW_MUX_GPIO1_IO03);/*寄存器SW_PAD_GPIO1_IO03设置IO属性*bit 16:0 HYS关闭*bit [15:14]: 00 默认下拉*bit [13]: 0 kepper功能*bit [12]: 1 pull/keeper使能*bit [11]: 0 关闭开路输出*bit [7:6]: 10 速度100Mhz*bit [5:3]: 110 R0/6驱动能力*bit [0]: 0 低转换率*/writel(0x10B0, SW_PAD_GPIO1_IO03);/* 4、设置GPIO1_IO03为输出功能 */val = readl(GPIO1_GDIR);val &= ~(1 << 3);    /* 清除以前的设置 */val |= (1 << 3);    /* 设置为输出 */writel(val, GPIO1_GDIR);/* 5、默认关闭LED */val = readl(GPIO1_DR);val |= (1 << 3);    writel(val, GPIO1_DR);/* 6、注册字符设备驱动 */retvalue = register_chrdev(LED_MAJOR, LED_NAME, &led_fops);if(retvalue < 0){printk("register chrdev failed!\r\n");return -EIO;}return 0;
}/** @description  : 驱动出口函数* @param       : 无* @return       : 无*/
static void __exit led_exit(void)
{/* 取消映射 */iounmap(IMX6U_CCM_CCGR1);iounmap(SW_MUX_GPIO1_IO03);iounmap(SW_PAD_GPIO1_IO03);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);/* 注销字符设备驱动 */unregister_chrdev(LED_MAJOR, LED_NAME);
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("jiajia2020");

  led_switch 函数,用于控制开发板上的 LED 灯亮灭,当参数 sta 为 LEDON(0) 的时候打开 LED 灯, sta 为 LEDOFF(1)的时候关闭 LED 灯。led_write 函数,实现对 LED 灯的开关操作,当应用程序调用 write 函数向 led 设备写数据的时候此函数就会执行。首先通过函数 copy_from_user 获取应用程序发送过来的操作信息(打开还是关闭 LED),最后根据应用程序的操作信息来打开或关闭 LED 灯。驱动入口函数led_init,此函数实现了 LED 的初始化工作,通过 ioremap 函数获取物理寄存器地址映射后的虚拟地址,得到寄存器对应的虚拟地址以后就可以完成相关初始化工作了。比如是能 GPIO1 时钟、设置 GPIO1_IO03 复用功能、配置 GPIO1_IO03的属性等等。最后,最重要的一步!使用 register_chrdev 函数注册 led 这个字符设备。
  然后是驱动出口函数 led_exit,首先使用函数 iounmap 取消内存映射,最后使用函数 unregister_chrdev 注销 led 这个字符设备。最后,使用 module_init 和 module_exit 这两个函数指定 led 设备驱动加载和卸载
函数并添加 LICENSE 和作者信息。
ledApp.c
  编写测试 APP, led 驱动加载成功以后手动创建/dev/led 节点,应用 APP 通过操作 /dev/led 文件来完成对 LED 设备的控制。向/dev/led 文件写 0 表示关闭 LED 灯,写 1 表示打开 LED 灯。

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"#define LEDOFF     0
#define LEDON   1/** @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;
}

  最终编译驱动程序和APP,而后加载驱动,创建设备节点,最后使用ledApp软件来测试驱动是否正常。至此,我们LED驱动程序编写完成。

嵌入式Linux开发17——LED驱动开发相关推荐

  1. 嵌入式linux ASoC架构声卡驱动开发

    嵌入式linux ASoC架构声卡驱动开发 文章目录 嵌入式linux ASoC架构声卡驱动开发 需求分析 ASoC架构下声卡驱动代码结构 codec驱动 snd_soc_register_codec ...

  2. linux cached释放_正点原子Linux第四十一章嵌入式Linux LED驱动开发实验

    1)资料下载:点击资料即可下载 2)对正点原子Linux感兴趣的同学可以加群讨论:935446741 3)关注正点原子公众号,获取最新资料更新 第四十一章嵌入式Linux LED驱动开发实验 上一章我 ...

  3. 【正点原子Linux连载】第四十一章 嵌入式Linux LED驱动开发实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  4. 【正点原子MP157连载】第二十一章 嵌入式Linux LED驱动开发实验-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7

    1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频 ...

  5. Linux下按键设备驱动开发以及对中断的上半部分和下半部分详细介绍

    文章目录 一.编写并且加载设备树插件 (1)检测管脚是否占用 (2)添加设备树插件 (3)加载设备树插件 二.中断相关函数 (1)request_irq中断注册函数 (2)free_irq中断注销函数 ...

  6. 【吃灰板子捡起来】LED驱动开发实验

    LED驱动开发实验 一.地址映射 二.内存访问 三.应用源码 四.驱动源码 五.实验总结 一.地址映射 #include <asm/io.h>#define ioremap(cookie, ...

  7. 全志V3S开发板LED驱动

    全志V3S开发板LED驱动 本文用的是全志开发板,在此开发板环境是店家提供,之前看了一片文章是利用编译内核的方式得到.ko文件,本文采用韦东山老师的方式make并且点亮LED. 1.由于v3ssdk目 ...

  8. S3C6410开发板LED驱动代码分析及测试代码分析

    在本文中,我们对S3C6410开发板LED驱动代码的实现过程进行分析,然后通过一个实例对LED进行控制.在本文的资源中包含了设备驱动的源码和测试的源码. 一.设备驱动源码分析 设备驱动主要实现了模块的 ...

  9. Linux驱动开发1:驱动开发与裸机开发的区别

    Linux驱动开发1:驱动开发与裸机开发的区别 1.裸机驱动开发回顾: 裸机驱动开发是非常底层的,跟寄存器打交道,有些MCU为了方便我们开发,提供了一些库,让我们通过调用API函数来间接的实现利用寄存 ...

最新文章

  1. Java中父类构造方法对子类构造方法的影响(不是一句话可以说清的)
  2. Collections集合工具类的方法_sort(List,Comparator)
  3. 用C/C++开发《Photoshop》图像处理软件
  4. 计算机系统是连续系统,连续系统的计算机模拟
  5. 计算机学报在线阅读,面向目标检测与姿态估计的联合文法模型计算机学报.pdf...
  6. .net 裁剪图片(不压缩)
  7. ML、DL、CNN学习记录2
  8. 一加手机回应“滤镜透视”争议:为消除担忧 暂时禁用该滤镜
  9. 大数据之-Hadoop3.x_MapReduce_ReduceJoin案例Reducer_案例_Debug调试---大数据之hadoop3.x工作笔记0132
  10. Java从0开始之Java环境搭建
  11. 计算机职业素养论文1500字,职业素养课后感想1500字
  12. 管家机器人先生txt_《管家机器人先生》主角墨青如玉最新章节章节目录
  13. android system image,android systemimage默認大小以及如何修改
  14. 浅谈微信小程序的发展
  15. 图灵测试其实已经过时了
  16. 回顾+纪念:离开帝都的第一年
  17. Bootstrap框架----标签Tag输入用法--Bootstrap-tagsinput
  18. 情景式领导力学习(1) - 介绍
  19. 为什么传统的验证码不再安全
  20. matlab 矩阵 列 逆序,matlab对矩阵/向量的常用操作(拼接矩阵、向量逆序、改变矩阵形状、求行阶梯形矩阵、提取矩阵的一部分等)...

热门文章

  1. 安卓移动开发——用AsyncTask来实现计时器和进度条
  2. php 根据出生日月判断星座
  3. Bootstrap 的网格系统(Grid System)
  4. 量化分析师的Python日记【第6天:数据处理的瑞士军刀pandas下篇
  5. IT搬砖员如何认识能力圈并如何突破自我
  6. 【Kubernetes 018】cfssl创建证书并结合RBAC的RoleBinding配置新用户config文件操作详解
  7. C练题笔记之:Leetcode-12. 整数转罗马数字
  8. spring boot 项目在启动时调用接口
  9. C练题笔记之:Leetcode-1455. 检查单词是否为句中其他单词的前缀
  10. 遮罩层——通过阴影弱化背景的四种方案