使用官方SDK库编写IMX6UL的LED灯驱动(超详细原理分析)

       1.文章说明

        在我们编写驱动的时候需要使用到寄存器,但是往往自己编写寄存器的时候特别繁琐,容易出错,而且还需要对同一个寄存器反复定义,所以为了开发的科学性我们使用NXP官方专门为IMU6UL提供的SDK包来编写IMX6UL的第一个LED灯驱动。

        在此前先说明,这里我是关于SDK的包我已经参照正点原子的视频全部移植完成了,在之后需要使用的文件我都会放在链接,如果想要自己重新移植一次我也会将SDK的安装包放在文件中,然后可以惨参考正点原子的SDK使用和移植教程,但是这里我就不讲解具体的移植过程了。

        2.编写LED驱动

        1.SDK库移植

        现在我们就正式讲解如何使用SDK库编写LED灯的驱动,首先在Linux中的Vscode新建一个工程文件,然后通过filezilla传输软件,将这三个已经移植好的文件传输到我们新建的工程文件中。

        然后在Vscode中打开工程文件,我们移植的三个头文件都应该在文件栏的左侧

        在我们移植好的三个库中分别是fsl_common.h   fsl_iomuxch.h   MCIMX6Y2.h,但需要注意的是在这三个头文件中都没有对于数据类型的定义所以我们需要自己手写一个数据类型的定义,我们打开上面的终端,然后新建终端,然后确认自己在这个文件夹的路径下,

        输入命令行,创建一个cc.h的文件夹

        然后写入数据类型的代码

        接下来我们的库已经移植过来,并且不会出现任何问题,然后创建一个main.h的文件在里面编写LED的驱动,使用如下命令创建main.h文件夹

        打开创建好的main.h,首先引用我们移植过来的库,也就是fsl_common.h   fsl_iomuxch.h   MCIMX6Y2.h这三个头文件,其中包含了我们所有寄存器的宏定义,以及硬件资源的访问接口,使得用户可以直接操作处理器的寄存器和外设等。所以必须先引用文件。

        2.LED时钟初始化

         然后我们将思路整理一下,需要使用的绝大数外设模块在使用之前都是需要先使能时钟,外设模块需要时钟信号等相关原因,所以我们第一步都是先初始化时钟,那么我们看到我IMX6uL的参考手册,CCM(Clock Controller Module) Clock Gating Register,叫做CCM通用寄存器,也就是时钟控制模块通用寄存器,主要作用是管理和控制系统中外设的时钟信号。通过设置这些寄存器中的相应位,可以开启或关闭特定外设的时钟。

                在这里CCM通用寄存器有四种模式,通过在不同模式下开启或关闭时钟来减少功耗。

1. 00  时钟在所有模式下关闭,停止进入硬件握手,也就是将此寄存器禁用

        时钟完全关闭,无论系统处于运行模式、等待模式还是停止模式。这可以最大限度地节省功耗,但如果该模块在这些模式中需要运行,则不能使用。

2. 01  时钟在运行模式下关闭,但在等待和停止模式下关闭

        时钟在系统运行模式(Run Mode)下开启,但在等待模式(Wait Mode)和停止模式(Stop Mode)下关闭。这适用于那些在低功耗模式下不需要工作的模块。

3. 10  不适用,这两位做保留位

        保留位的主要作用是确保硬件和软件的兼容性、灵活性和未来的可扩展性。

4.  11  时钟在所有模式下开启,除了停止模式

        时钟在运行模式和等待模式下保持开启,但在停止模式下关闭。适用于需要在大多数时间都保持作用的模块,但在系统完全停止时可以关闭节省功耗      

        

        每个寄存器通常都是32位宽,如下图每个CG位字段控制一个特定外设的时钟状态,这些位字段都是由两位组成,也就是每两位控制一个CG位,一个CG位控制一个特定外设的时钟状态,比如下方的31-30(CG15)就是控制的GPIO2_CLOCKS(GPIO2_CLK_ENABLE)外设的时钟状态,两位也对应上文所描写的模式00、01、10、11

        然后可以在手册中看到,在IMX6UL上一共有6个CCM通用寄存器,他们分别控制着对应的外设,在这里为了新手适应简化初始化流程,我们将他们全部使能,模式选择全开启模式,也就是将每一个CG位全部都置1,总共是16个CG位,每个CG位由两位控制。所以一共有32位,也就是32个1

        在SDK库中这些寄存器已经被全部封装好了, 直接使用CCM指向结构体指针使用->访问就可以了。分别写入CCM_CCGR全部置1,32个1转换位位16进制对应0xFFFFFFFF,所以直接按照下方代码就可以全部初始化时钟,并没有想象中的难。

        初始化时钟结束后,就是对引脚进行初始化,首先我们要去查找原理图,LED0 接到了 GPIO_3 上,GPIO_3 就是 GPIO1_IO03,所以我们就要对GPIO1_IO03进行初始化

        在SDK库中对于IO口初始化的函数之一是 IOMUXC_SetPinMux用来设置引脚的复用模式和输入选择,这里面的参数是宏定义好的,我们只需要知道怎么使用,比如这里我们需要将引脚复用为GPIO1_IO03

        直接将需要复用引脚在fsl_iomuxc.h中搜索引脚,然后找到它的宏定义复制到这个函数的参数当中如下图,然后就设置成功了

        这个地方简单讲解一下是如何设置的,首先这个函数对应的是下图的此寄存器,这个寄存器的作用是设置引脚复用,在图中看出MUX_MODE由四位控制下面的0101就是控制GPIO1_IO03的复用,写入的值就应该是0101,前四位决定额是寄存器的模式。第五位可以设置0和1,设置1就是强制使能,无论MUX_MODE功能如何,强制选择复用模式的输入路径。就是强制使用Pad GPIO1_IO03的输入路径 ,所以这里不使能直接输入路径由功能决定。所以这里只需要写入复用GPIO1_IO03的参数0101
        下面IOMUXC_GPIO1_IO03__GPIO1_IO03宏定义的参数中,第二个的值是0X5 (十六进制)=0101 (二进制)相对应,所以也就是变相的的设置这个寄存器。 

        

        再次整理一下思路,设置好引脚过后,就应该更具体的配置这一个引脚,也就是现在要设置这个引脚GPIO1_IO03的电气属性,设置电气属性的函数是IOMUXC_SetPinConfig。跳转能看到它的底层定义,传入的参数依然是宏定义好的IOMUXC_GPIO1_IO03_GPIO1_IO03。末尾传入的参数是0X10B0,这个的值也是根据对应的设置电气属性的寄存器来的。

        这个寄存器是专门设置电气属性的,也是32位

        下面解释一下各个字位段设置了什么参数,首先

        第零位设置压摆率,设置0是低压摆率,1是高压摆率,摆率是指电压或电流变化的速率。慢速摆率适用于对信号变化速度要求不高的场合,而快速摆率则适用于需要快速响应的场合。这里没有特殊要求就选择置1

        然后1-2位作为保留位,不需要设置,3-5位设置 GPIO1_IO03 引脚的输出驱动强度。驱动强度是指引脚能够驱动的电流或电压的大小,可以看到上面都是在除以电阻,也就是减少电阻导致电流增强,它直接影响到引脚的负载能力。具体作用例如,在高速数据传输或高电流驱动的场合,需要选择较高的驱动强度;而在低速数据传输或小电流驱动的场合,则可以选择较低的驱动强度。无特殊要求这里写入011

        6-7位用于设置 GPIO1_IO03 引脚的速度。速度是指引脚能够达到的最高工作频率。通过选择不同的速度值,可以优化引脚的电气特性,以满足不同的应用需求。例如,在高速数据传输或高频信号处理的场合,需要选择较高的速度值;而在低速数据传输或低频信号处理的场合,则可以选择较低的速度值。这里就选择中速100Mhz就可,输入数值10

        8-10位作保留位,11位输入0就是禁用开漏输出,选择1就是启用开漏输出开漏输出是指引脚可以在不提供上拉电阻的情况下输出低电平,而高电平则取决于外部上拉电阻。通过选择不同的开漏使能状态,可以优化引脚的电气特性,以满足不同的应用需求。例如,在需要与其他设备共享信号线的场合,可以选择启用开漏输出,以减少冲突。11位选择关闭开路输出,设置为0.

        12位输入0拉/保持功能被禁用,输入1拉/保持功能被启用使在没有外部信号输入的情况下,引脚也会被配置为保持特定的电平(通常为高电平)。这有助于保持引脚的电平稳定,防止由于外部干扰导致的电平漂移。这里使能保持功能输出1

        13位输入0是设置保持模式,设置1表示上拉模式 上拉模式保持模式是指在引脚未连接到其他电路时,通过一个保持电阻将引脚电位保持在逻辑高电平(如3.3V或5V)。上拉模式是指在引脚未连接到其他电路时,通过一个上拉电阻将引脚电位拉高至逻辑高电平。这里设置为0

        14-15位    00 PUS_0_100K_Ohm_Pull_Down — 100K 欧姆下拉 01 PUS_1_47K_Ohm_Pull_Up — 47K 欧姆上拉 10 PUS_2_100K_Ohm_Pull_Up — 100K 欧姆上拉 11 PUS_3_22K_Ohm_Pull_Up — 22K 欧姆上拉 。GPIO1_IO03 引脚的上拉或下拉电阻值。上拉电阻会在引脚处于高阻态时将其拉高至逻辑高电平,而下拉电阻则会在引脚处于高阻态时将其拉低至逻辑低电平。这里设置默认下拉00

        16位选择1迟滞功能禁用,选择0启用迟滞功能当迟滞功能启用时,引脚的输入电平变化会经过一个迟滞电路,这有助于减少由于电噪声或电压漂移引起的误触发。通过选择不同的迟滞使能状态,可以优化引脚的电气特性,以满足不同的应用需求。这个不作过多讲解,这里默认关闭选择0

        17-31位都是保留位,此寄存器的每一位我们都做了详细介绍与配置所以,从高位往低位排组成的数据是 0000 0000 0000 0000 0001 0000 1011 0000 无论进制为多少,在后面填0完全无意义,所以只需要写1 0000 1011 0000 前面的便会默认填零所以1 0000 1011 0000 转换为16进制便是0x10B0,真正的数据是1 0000 1011 0000 = 10B0但是为了让方便我们知道,以及编译器知道前面加上0x表示这是一个16进制的数。所以上面的最后一个参数我们填入0x10B0

        将IO的复用以及电气属性设置好过后,就要开始对GPIO引脚的初始化,GPIOx_GDIR是专门初始化引脚的信号IO的信号方向,这个函数是被宏定义了,宏定义的内容是此寄存器的地址。

        前面没有提到,其实每个寄存器下面的Address就是它对应的内存地址,这个寄存器与上面的不同,表格的意思是首先我这个地址是0x0209C004就说明了我设置的是GPIO1,其次在32位每一位都控制着一个IO口,在这里例如我们是GPIO1_IO03那么我们就需要对应到第三位
        这个寄存器设置1就是输出引脚为输出,设置0就是将它设置成输入。那么如上面我们要设置GPIO_IO0为输出引脚,则是将这个寄存器的第三位设置为1,这个寄存器的值是
0000 0000 0000 0000 0000 0000 0000 1000 转换为16进制便是 0x0000008,输入0x8和0x0000008都可以,但是这里方便以后会有其他数值,方便观察,这里就不省去前面的0,
        在之后我们都是采取位运算的方式。表达式 |= (1 << 3); 是一个位运算表达式。它使用了位或赋值运算符 |=,这相当于 |= 操作符左边的变量与 (1 << 3) 的结果进行位或操作,并将结果赋值给左边的变量。位运算中,<< 表示左移操作,它将操作数的二进制表示向左移动指定的位数,并在左侧填充零。对于 (1 << 3),我们将 1(其二进制表示为 00000001)向左移动 3 位。最后结果也就是0000 1000在移动了三位了过后,相当于最后转换成16进制的结果仍然是8
        在对于驱动的编写,位运算是需要时刻使用到的,所以建议不太熟悉的情况下,需要去专门学习一下位运算。


        设置完成引脚的输入输出后,就需要开始设置引脚的输出电平了,选择将我们的引脚初始化为高点平输出还是低电平输出,与上面的GPIO_GDIR一样,一个位控制一个IO口,这里输出低电平,只需要将所有的置0就可以了,所以就是0x0。

        所以将第三位给置0,依然用位运算表示1 << 3 表示将 1 左移 3 位,得到 1000,然后,~运算符对这个结果进行按位取反,即 0 变为 1,1 变为 0,所以结果应该是 0111。相当于将第三位给置0了。然后这个就是我们的整个关于GPIO1_IO03引脚初始化的步骤,现在我们的所有初始化就已经全部完成,那么下一步就是开始编写实验代码,也就是如何点亮LED

        我们要实现的效果是,一个闪烁灯,也就是先点亮LED灯,然后延时过后灯熄灭然后延时过后再次点亮LED灯,根据这个思路我们现在开始编写函数。

        这是LED灯亮的函数和上面的那个初始化函数一样,其实他的本质效果都是首先使用GPIO1_DR这个输出电平设置寄存器来控制,引脚的输出电平,所以这里我们需要LED灯亮我们就需要输入低电平,将这里控制IO03的也就是此寄存器的第三位置0,依然使用位运算,这个的计算上面已经讲过,这里不多赘述

        

        然后LED灯熄灭的函数我们继续编写,也就是控制这个寄存器的第三位变成1,和上面灯亮的数据相反,计算出来的结果是1000,刚好就是将第三位置1,所以LED关闭的函数也编写好了。

        下面是编写dalay的延时函数,首先先实现函数的短延时,函数的参数是一个名为 n 的变量,它是一个无符号整数,并且被标记为 volatilevolatile 关键字的作用是告诉编译器不要对这个变量的值进行优化。在嵌入式编程中,由于外部硬件可能会改变这个变量的值,因此标记为 volatile 可以确保代码的准确执行。函数的主体是一个 while 循环,循环的条件是 n 是否大于 0。在每次循环迭代中,n 的值会减 1,直到 n 变为 0,循环结束。这个函数的延迟时间是非常短的,因为它是通过循环来实现的。

        然后编写长延时函数,IMX6UL的是396Mhz主频,所以更具我们的计算delay_short(0x7ff)刚好对应延时一毫秒,这里不过多赘述原理,也就是对这个函数输入多少的参数,它就可以延时多少秒。

        至此所有的函数都已经编写完成,接下来开始编写主函数,和STM32一样这里需要将我们刚刚写好的初始化函数在主函数中应用,只不过这一次初始化函数是编写的

        既然要实现反复亮灭,那么就肯定要在循环中反复执行,写一个while(1)让程序一直在里面跑,然后首先灯灭,延时500ms后灯亮,再延时500毫秒后灯再次灭,我们的实验效果就完成了,也就是我们的代码编写完成了。

        但是,并没有我们想象中的那么简单,这里只是将我们的代码部分编写好了,我们还要通过GCC交叉编译将我们的代码编译通过读卡器烧写到我们的SD卡中去,所以要引入Makefile文件这个概念,但是这一期文章已经比较长了,加上Makefile的知识点也非常的繁杂,所以我会使用一篇博客专门来讲解如何烧写我们的程序

        这个时候我们只是编写好了C语言实验程序的编写,但是我们还没有编写启动文件也就是Start.S, 文件通常包含从复位向量开始执行的代码,这通常是系统的第一个执行步骤。与32的启动文件也是一个道理,对硬件的初始化,跳转到主函数,系统环境设置。下面接着编写此实验,IMX6UL的启动文件。
        在当前文件下再创建一个名字为Start.S的文件夹名字,在这里面编写启动文件
        首先.global 伪指令用于声明一个标号为全局标号,这意味着这个标号可以在程序的任何地方被引用。全局标号通常用于定义程序的入口点或用于跨模块引用。在这里声明了_start为全局标号,意味着无论在汇编语言程序的哪个部分,你都可以使用 _start这个名字来引用这个标号。
        在 Linux 系统中, _start 是一个特殊的名字,它指向程序的入口点。当程序被加载和执行时,处理器会从  _start 标号所指向的位置开始执行。因此,在嵌入式系统中,这个标号通常被设置为从复位向量开始执行的代码,即启动代码。
        程序从我们下方的_start开始执行,将当前的程序状态寄存器(CPSR)的内容加载到寄存器 r0 中,将 r0 寄存器中的低 5 位清零,即清除了中断掩码位,然后设置程序进入SVC(超级管理员模式),设置栈指针加载为指定的内存地址 0X80200000,因为MX6U-ALPHA 开发板上的 DDR3 地址范围是 0X80000000~0XA0000000(512MB),由于 Cortex-A7 的堆栈是向下增长的,所以将 SP 指针设置为 0X80200000, 因此 SVC 模式的栈大小 0X80200000-0X80000000=0X200000=2MB所以这里使用2MB的栈空间用来做开发,因为栈指针的起始地址设置在0X80200000

        以上的C语言环境搭建好后,就可以执行C语言实验程序了b main: 这条指令是一个无条件跳转指令,它将程序的执行转移到 main 函数,这是 C 或 C++ 程序的入口点。从这里就开始执行C语言代码了。Start.S的文件也编写好了。

       下一篇文章通过Makefile来编写我们的代码。可以参考一下左边的文件列表,防止新手出错,可以看到除了Malefile,链接文件.lds,imxdownloa可执行文件我们其余的实验代码都已经编写完成。下一篇文章也会具体讲解这些文件的作用。

        这一期对于LED驱动的编写就讲到这里了,如果有问题欢迎各位向博主指出,感谢阅读。

  • 20
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值