本文共 4909 字,大约阅读时间需要 16 分钟。
学习嵌入式linux很久了,一直也没有真正的撸代码,心中很是愧疚,愧对于韦东山老师的教诲 “光说不练假把式”。故有了今天的这篇笔记或者说是博客。希望能给后来者提供一些帮助,希望能给未来的自己一个参考。本例基于野火的i.mx6ull pro 开发板。 文中提到的文档可以去gitee 下载得到。
项目地址 : https://gitee.com/jeasonb/fire_imx6ullpro_led_char_dev
参考链接:https://www.cnblogs.com/xiaojiang1025/p/6181833.html
字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备,常见的字符设备包括鼠标、键盘、显示器、串口等等,当我们执行ls -l /dev的时候,就能看到大量的设备文件,c就是字符设备,b就是块设备,网络设备没有对应的设备文件。编写一个外部模块的字符设备驱动,除了要实现编写一个模块所需要的代码之外,还需要编写作为一个字符设备的代码。(引自上述的地址)
1. 字符设备驱动框架
字符设备驱动是什么?我不去细说,也不去班门弄斧。照我个人的理解字符设备就是一个linux系统提供给用户的接口,用户可以通过使用文本操作的方式使得用户的应用程序和系统的驱动程序去进行交互。字符设备是有一定的套路的,看多了大佬们写驱动也无外乎那几个函数。
static int __init chip_xxx_init(void); // 驱动的入口 函数
上面的函数就是驱动的入口函数,就是当我们去加载这个驱动的时候会去调用这个驱动。他的用途是创建设备节点,以及可能存在的设备的初始化。
例如在韦东山老师的 s3c2440的驱动视频中就进行了① 设备类的创建 ②设备的创建
这一部分我个人的理解是这样的,例如 一条 I2C 总线上挂了两个设备,那我肯定会去创建一个I2Cx 代表这个I2C 总线,然后上面挂的什么设备还需要单独的去创建设备,例如AP3216C 和MPU6050以及AT24C02 在一条总线上。 那么在进行设备的初始化阶段我就应该去设置I2C的配置,如果使用硬件的,那么就应该去设置 IO 、 I2C 控制器等等 。在LED 驱动中,这里需要去进行虚拟地址映射,将驱动程序内的虚拟地址映射到真实的物理地址(寄存器)上。
static int s3c24xx_leds_open (struct inode *node, struct file *file);
这个是函数会在应用程序open 这个设备文件的时候会去进行调用的,在这时应该去进行一些设备的初始化,例如配置mpu 6050 或者是配置 AP3216C 等等。
对于LED的实验,这一步需要去配置LED的GPIO的初始化,例如 配置时钟、配置IO方向,压摆率等等的参数。
static int s3c24xx_leds_read(struct file *filp, char __user *buff, size_t count, loff_t *offp);
read 函数的作用就是从驱动函数中读取数据。
static ssize_t s3c24xx_leds_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
write 函数的作用是去写入数据,再本例中将使用这个函数将LED的状态写入到驱动里面。
2. 硬件操作分析
2.1 开时钟操作
通过原理图我们先确定需要进行操作的IO 是哪几个通过原理图我猜 红色的LED 连接到了 GPIO1_IO04 上,绿色的LED 连接到了GPIO1_IO09 ,蓝色的LED连接到了IOMUXC_SW_MUX_CTL_PAD_CSI_VSYNC 。
野火这边的这个原理图感觉不是很详细,我需要根据pwmx 和 另外的名称对应起来猜测是哪一个具体的引脚,不过还好,也不是很难。接下来就是设置这些引脚的复用功能了,第一阶段先都搞成简单的 数字输出即可。
参考韦东山老师的代码,我们不难找到关于时钟的设置。时钟设置需要参考《IMX6ULRM(用户手册).pdf》Chapter 18 Clock Controller Module (CCM) 大约在文档的第625 页。我们需要操作的时钟控制寄存器大约在700 页前后。 GPIO 等外设的时钟配置 占时钟配置寄存器中的两个bit ,其中不同的bit 的含义如下:我们需要的显然是打开之后整个时钟一直工作。 因为不能保证其他用户是否使用这个设备,所以我们的驱动程序中可以去打开时钟,但是却不能去主动关闭这个时钟! 所以对于这个时钟的操作就是在驱动加载的时候打开时钟,仅此而已。对于单个的时钟的寄存器,我们只需要映射一个寄存器,其操作的代码如下:
static volatile unsigned int *CCM_CCGR1;// 声明一个变量 CCM_CCGR1 = ioremap(0x20C406C, 4); // 虚拟地址映射到真实地址*CCM_CCGR1 |= (3<<26); // 这里可以参考到寄存器的表 gpio1 clock (gpio1_clk_enable)位于CCM_CCGR1[27–26]
2.2 iomux的操作
在进行IO复用功能选择之前需要先分析IO多路复用器的功能,i.mx6ull的多路复用器支持将IO 功能复用成非常多的功能。关于这一部分的介绍。NXP 的芯片用户手册有非常详细的说明! 请参考《IMX6ULRM(用户手册).pdf》Chapter 32 IOMUX Controller (IOMUXC) 文档大约1469 页的位置,从这一页开始有非常详细的介绍。先分析一个红色的led: 他的IOmux 如下:最简单的办法就是直接单个寄存器进行映射,但是这样之后的开发会变得困难,所以韦东山老师的代码中添加了一些巧妙的用法。 在代码中先定义了一个结构体,结构体的名称就是 iomux,这个结构体的原型为
struct iomux { volatile unsigned int unnames[23]; volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00; /* offset 0x5c*/ volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO01; // volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO02; volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03; volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04; volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO05; volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO06; volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO07; volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO08; volatile unsigned int IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO09;};
上述代码使用的是基址加偏移的方案去实现的,基址就是IOMUXC 的起始地址。在用户手册中定义如下:偏移值的确定是需要根据实际的寄存器位置来进行确定的,其中偏移值5C 是由以下原因得来的
对应于前面的空值的设置 为 0x5c / 4 = 92/4 = 23 也就是说相对于偏移的基址的前23个寄存器是和IO 复用无关的寄存器,所以这里选择设置一个数组来将这些空间占用掉。这样之后的指针寻址时就能寻址到实际的物理地址的地址。例如struct iomux *IOMUX;IOMUX = ioremap(0x20e0000, sizeof(struct iomux));IOMUX->IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04 = 5; //上述的IOMUX->IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO00 就是真实的地址 0x020E005c
下面的图片解释了为什么IOMUX->IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04 = 5; 的值是5
2.3 GPIO操作
在GPIO相关的操作章节,我们不难找到GPIO的寄存器的数据表,部分数据表如下图所示
图中寄存器以及寄存器的命名都非常的规范,而且也给出了详细的介绍,例如 GPIO_data_register 就是我们熟知的数据寄存器,要向输出状态,就需要向这个寄存器写入对应的数据即可。还是和之前的IOMUXC的用法一样,在韦东山老师提供的代码中有将寄存器映射成结构体指针的方法,其基本的结构体如下:struct imx6ull_gpio { volatile unsigned int dr; // GPIO data register (GPIO1_DR)----------数据寄存器 volatile unsigned int gdir; // GPIO direction register (GPIO1_GDIR)---方向寄存器 volatile unsigned int psr; // GPIO pad status register (GPIO1_PSR)---状态寄存器 volatile unsigned int icr1; // GPIO interrupt configuration register1 (GPIO1_ICR1) volatile unsigned int icr2; // GPIO interrupt configuration register2 (GPIO1_ICR2) volatile unsigned int imr; // GPIO interrupt mask register (GPIO1_IMR) volatile unsigned int isr; // GPIO interrupt status register (GPIO1_ISR) volatile unsigned int edge_sel;// GPIO edge select register (GPIO1_EDGE_SEL)};
以下提供了 向寄存器内写入GPIO1_IO04 状态的写法:
static struct imx6ull_gpio *gpio1;gpio1 = ioremap(0x209C000, sizeof(struct imx6ull_gpio));gpio1->dr |= (1<<4); // 输出高电平gpio1->dr &= ~(1<<4); // 输出低电平
剩下的就是将韦老师的代码改一下就可以用了,整个的LED的驱动代码也就到此结束了。
整个的工程我会放到gitee,需要的小伙伴请自行下载。
https://gitee.com/jeasonb/fire_imx6ullpro_led_char_dev
转载地址:https://jeason.blog.csdn.net/article/details/112723902 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!