【Linux笔记】LED驱动程序

前言

学STM32我们从点灯开始,学Linux驱动我们自然也要点个灯来玩玩,尽量在从这些基础例程中榨取知识,细抠、细抠,为之后更复杂的知识打好基础。

与硬件无关的LED驱动

回顾hello驱动程序,我们的根据实际需求对其进行写字符串与读字符串操作。这里我们当然也要根据实际来思考我们的LED驱动程序。

在STM32点灯的时候,一般输出低电平点灯,输出高电平灭灯。在嵌入Linux操作系统的情况下,我们自然也要想到有个写1/0的思想。

类比我们上一篇的hello程序:

我们的LED程序自然要写入的数据为0/1来点亮、熄灭LED。

这里我们做的实验室与硬件无关的LED实验:我们的驱动程序在收到应用程序发送过来的0时打印led on、收到1时打印led off

模仿上一篇的hello程序,我们修改得到的与硬件无关的LED程序(核心部分)如下:

LED应用程序:

LED驱动程序:

加载led驱动模块及运行应用程序:

与硬件有关的LED驱动

上面那一节分享的是与硬件无关的LED驱动实验,主要是为了理清LED驱动的大体思路。这里我们再加入与硬件有关的相关操作以构造与硬件有关的LED驱动程序。

我们在进行STM32的裸机编程的时候,对一些外设进行配置其实就是操作一些地址的过程,这些外设地址在芯片手册中可以看到:

这是地址映射图,这里图中只是列出的外设的边界地址,每个外设又有很多寄存器,这些寄存器的地址都是对外设基地址进行偏移得到的。

同样的,对于NXP的IMX6ULL芯片来说,也是有类似这样的地址的:

此时我们要编写Linux系统下的led驱动,涉及到硬件操作的地方操作的并不是这些地址(物理地址),而是操作系统给我们提供的地址(虚拟地址)。

操作系统根据物理地址来给我们生成一个虚拟地址,我们的led驱动操控这个地址就是间接的操控物理地址。

至于这两个地址是怎么联系起来的,里面个原理我们暂且不展开。我们从函数层面来看,内核给我们提供了ioremap 函数,这个函数可以把物理地址映射为虚拟地址。

这个函数在内核文件arch/arm/include/asm/io.h  中:

左右滑动查看全部代码>>>

void __iomem *ioremap(resource_size_t res_cookie, size_t size);

  • res_cookie:要映射给的物理起始地址 。
  • size:要映射的内存空间大小。
  • 返回值:指向映射后的虚拟空间首地址。

ioremap函数相对应的函数为:

void iounmap (volatile void __iomem *addr)

  • addr:要取消映射的虚拟地址空间首地址。

地址映射完成之后,我们可以直接通过指针来访问虚拟地址,如:

*GPIO5_DR &= ~(1 << 3); /* GPIO5_IO03输出低电平 */
*GPIO5_DR |= (1 << 3); /* GPIO5_IO03输出高电平 */

这里简单介绍一下i.MX 6ULL的GPIO。对于i.MX 6ULL来说,以数字来给IO端口(组别)命令,GPIO5为第五组,所以GPIO5_IO03为第五组端口的第3个引脚。

而STM32中是以大写字母来表示端口(组别),如PA3表示A端口的第3个引脚。

i.MX 6ULL有 5 组 GPIO(GPIO1~ GPIO5),每组引脚最多有 32 个:

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;

地址映射完成之后,我们不仅可以通过指针来访问虚拟地址,而且还可以使用内核给我们提供的一些读写函数:

/* 写操作函数 */
void writeb(u8 value, volatile void __iomem *addr);
void writew(u16 value, volatile void __iomem *addr);
void writel(u32 value, volatile void __iomem *addr);
/* 读操作函数 */
u8 readb(const volatile void __iomem *addr);
u16 readw(const volatile void __iomem *addr);
u32 readl(const volatile void __iomem *addr);

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

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

此时我们可以把上一节的led_init函数led_drv_write函数进行修改:

与STM32一样,对于i.MX 6ULL的GPIO外设来说,也有很多寄存器:

上面我们只是点一个灯,如果是要点多个灯呢?那就得操控多个GPIO。如果进行地址映射的写法还像上面那样,代码就会显得很臃肿。

回想一下我们STM32,GPIO外设通过结构体来管理它的寄存器:

这里的__IO是个宏,代表的是C语言的关键字volatile ,为了防止编译器对我们的一些硬件操作进行优化,从而得不到想要的结果。比如:

/* 假设REG为寄存器的地址 */
uint32 *REG;
*REG = 0;/* 点灯 */
*REG = 1;/* 灭灯 */

此时若是REG不加volatile进行修饰,则点灯操作将被优化掉,只执行灭灯操作。关于volatile关键字更多的解释可以查看往期笔记:《来看一看volatile关键字》

在这里,我们也可以模仿STM32那样子,用一个结构体来对i.MX 6ULL的GPIO的寄存器进行管理,如:

struct GPIO_RegDef
{
volatile unsigned int DR;
volatile unsigned int GDIR;
volatile unsigned int PSR;
volatile unsigned int ICR1;
volatile unsigned int ICR2;
volatile unsigned int IMR;
volatile unsigned int ISR;
volatile unsigned int EDGE_SEL;
};

结构体里的成员排序是要按照特定顺序来的:

因为这些寄存器都是相对于GPIO外设的基地址作偏移得到的,比如:

不能打乱顺序,否则就不能正确访问到对应的寄存器了。用结构体进行管理之后,我们就可以用类似下面的方式进行映射:

struct GPIO_RegDef *GPIO5 = ioremap(0x20AC000, sizeof(struct GPIO_RegDef));

然后就可以向STM32那样来操控GPIO寄存器,如:

GPIO5->DR &= ~(1 << 3); /* GPIO5_IO03输出低电平 */
GPIO5->DR |= (1 << 3); /* GPIO5_IO03输出高电平 */

LED驱动(升级版)

上一节我们分享的LED驱动是一个常规的LED驱动,只能适用于我们当前的开发版,所以是一个专用的LED驱动程序。

若是换了另一块板,led所连接的gpio引脚可能不一样了,我们就修改我们的驱动程序led_drv.c里与寄存器相关的操作。

有没有更好的办法不用再修改我们的led_drv.c驱动程序了?

若是led_drv.c不用再修改了,那么这个led_drv.c驱动就是一个通用的驱动程序了。具体可查看韦东山老师的《嵌入式Linux应用开发完全手册第2版》第五篇第3~7节进行学习

下面来简单地梳理一下:

由于篇幅问题,具体的部分就不贴出来了。

之前的笔记中:《C语言、嵌入式重点知识:回调函数》中我们也有提到通用专用的含义,可以了解了解加深对这两个词的认识。

这里我们学到了很重要的思想软件分层的思想及技巧,但也只是点了一下,未来的路还很长,需要持续学习,继续提高。

最后

以上就是本次的分享,如有错误,欢迎指出!谢谢

(0)

相关推荐

  • 【转】位带操作的原理及使用

    位带操作原理 把每个比特膨胀(映射)为一个32位的字,当访问这些字的时候就达到了访问比特的目的,比如说BSRR寄存器有32个位,那么可以映射到32个地址上,我们去访问(读-改-写)这32个地址就达到访 ...

  • UC头条:我眼中的ZYNQ

    ZYNQ是一个神一样的存在,硬件设计师不需要绞尽脑汁去思考嵌入式应用如何移植BSP:同样软件工程师也不需要深入弄清楚硬件的实现原理和架构.听起来像是两全其美, 你觉得呢? ZYNQ到底好用吗?该如何用 ...

  • VsCode设置ESP32工具链+刨根问底点灯

    unplash 已下软件都是要提前准备的,还有Python环境,不低于3.6 https://cmake.org/ 首先下载cmake 下载中 选择所有用户 这就是装好的页面了 https://git ...

  • 【Linux笔记】LED驱动实验(总线设备驱动模型)

    前言 继续来点灯~学了一段时间的嵌入式Linux发现LED程序挺香的.. 从LED程序中我们可以榨取很多知识:基本的驱动框架.驱动的简单分层.驱动的分层+分离思想.总线设备驱动模型.设备树等. 这大多 ...

  • 【竺】Linux笔记2——netstat命令

    netstat 功能说明:显示网络状态. 语 法:netstat [-acCeFghilMnNoprstuvVwx] [-A<网络类型>][--ip] 补充说明:利用netstat指令可让 ...

  • 【竺】Linux笔记3——ps -ef|grep详解

    ps命令将某个进程显示出来 grep命令是查找 中间的|是管道命令 是指ps命令与grep同时执行 PS是LINUX下最常用的也是非常强大的进程查看命令 grep命令是查找,是一种强大的文本搜索工具, ...

  • 【竺】Linux笔记1——基础命令

    Linux是做什么的? 一般用来做服务器端的操作系统, 服务器:提供服务.数据处理.安全 操作系统=开机之后进入的操作环境预览 电脑=硬件+操作系统(软件) 硬件=鼠标.键盘.显示屏.耳机.cpu.主 ...

  • Linux笔记【003】| Linux系统目录结构与基本命令

    一.Linux系统目录结构 linux的文件系统是采用级层式的树状目录结构,在此结构中最上层是根目录"/",然后在此目录下再创建其它的目录.在linux世界里.一切皆文件. 以下是 ...

  • Linux笔记【002】| 远程登录服务器软件:MobXterm与FileZilla

    一.登录服务器的软件--MobXterm 在实际开发或者计算的时候可以使用一些第三方的工具对远程的服务器进行控制.目前常用的Linux远程登录工具有:putty.xshell.secureCRT等等. ...

  • Linux笔记【001】| 初识Linux

    一.Linux 简介 Linux 内核最初只是由芬兰人林纳斯·托瓦兹(Linus Torvalds)在赫尔辛基大学上学时出于个人爱好而编写的. Linux 是一套免费使用和自由传播的类 Unix 操作 ...

  • Linux笔记【004】| 文件/文件夹的基本操作命令

    一.文件操作命令 1.创建 命令:touch 语法:#touch 文件的名字文件名可以是一个完整的路径 如果后面的参数文件名指定了路径,则表示在指定的路径下创建:如果只是传递一个文件名,则表示在当前目 ...

  • Linux笔记【005】| vim编辑器使用教程

    Vim是Linux下一款编辑器软件,它的地位等同于windows下的notepad(记事本).其功能上要比windows的记事本要强上很多倍,这个vim在开发行业来说,有一个称号"编辑器中的 ...