在上一章中,我们参考 ST 官方给 STM32 编写的 stm32f10x.h 来自行编写 I.MX6U 的寄存器定义文件。自己编写这些寄存器定义不仅费时费力,没有任何意义,而且很容易写错,幸好NXP 官方为 I.MX6ULL 编写了 SDK 包,在 SDK 包里面 NXP 已经编写好了寄存器定义文件,所以我们可以直接移植 SDK 包里面的文件来用。虽然 NXP 是为 I.MX6ULL 编写的 SDK 包,但是 I.MX6UL 也是可以使用的!本章我们就来讲解如何移植 SDK 包里面重要的文件,方便我们的开发。
1、官网SDK下载
I.MX6ULL 的 SDK 包在 NXP 官网下载,下载界面如图所示:
将下载下来的SDK_2.2_MCIM6ULL_RFP_Win.exe文件安装完成以后的SDK如图所示:
所有的例程都在 boards 这个文件夹里面。我们重点是需要 SDK 包里面与寄存器定义相关的文件,一共需要如下三个文件:
fsl_common.h:位置为 SDK_2.2_MCIM6ULL\devices\MCIMX6Y2\drivers\fsl_common.h。
fsl_iomuxc.h: 位置为 SDK_2.2_MCIM6ULL\devices\MCIMX6Y2\drivers\fsl_iomuxc.h。
MCIMX6Y2.h: 位置为 SDK_2.2_MCIM6ULL\devices\MCIMX6Y2\MCIMX6YH2.h。
整个 SDK 包我们就需要上面这三个文件,把这三个文件准备好,我们后面移植要用。
2、 硬件原理分析
可以看出,LED0 接到了 GPIO_3 上,GPIO_3 就是 GPIO1_IO03,当 GPIO1_IO03输出低电平(0)的时候发光二极管 LED0 就会导通点亮,当 GPIO1_IO03 输出高电平(1)的时候发光二极管 LED0 不会导通,因此 LED0 也就不会点亮。所以 LED0 的亮灭取决于 GPIO1_IO03的输出电平,输出 0 就亮,输出 1 就灭。
3、 SDK 文件移植
新建一个工程,将 fsl_common.h、fsl_iomuxc.h 和 MCIMX6Y2.h 这三个文件拷贝到工程中,需要对其不需要的地方进行删减,修改完成以后的工程目录如图所示:
4、创建 cc.h 文件
新建一个名为 cc.h 的头文件,cc.h 里面存放一些 SDK 库文件需要使用到的数据类型,在cc.h 里面输入如下代码:
cpp
#ifndef __CC_H
#define __CC_H
/*
* 自定义一些数据类型供库文件使用
*/
#define __I volatile
#define __O volatile
#define __IO volatile
typedef signed char int8_t;
typedef signed short int int16_t;
typedef signed int int32_t;
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
typedef signed char s8;
typedef signed short int s16;
typedef signed int s32;
typedef signed long long int s64;
typedef unsigned char u8;
typedef unsigned short int u16;
typedef signed int s32;
typedef signed long long int s64;
typedef unsigned char u8;
typedef unsigned short int u16;
typedef unsigned int u32;
typedef unsigned long long int u64;
#endif
在 cc.h 文件中我们定义了很多的数据类型,因为有些第三方库会用到这些变量类型。
5、编写实验代码
新建 start.S 和 main.c 这两个文件,start.S 文件的内容和上一章一样,直接复制过来就可以,创建完成以后工程目录如图所示:
在 main.c 中输入如下所示代码:
cpp
#include "fsl_common.h"
#include "fsl_iomuxc.h"
#include "MCIMX6Y2.h"
/*
* @description : 使能I.MX6U所有外设时钟
* @param : 无
* @return : 无
*/
void clk_enable(void)
{
CCM->CCGR0 = 0XFFFFFFFF;
CCM->CCGR1 = 0XFFFFFFFF;
CCM->CCGR2 = 0XFFFFFFFF;
CCM->CCGR3 = 0XFFFFFFFF;
CCM->CCGR4 = 0XFFFFFFFF;
CCM->CCGR5 = 0XFFFFFFFF;
CCM->CCGR6 = 0XFFFFFFFF;
}
/*
* @description : 初始化LED对应的GPIO
* @param : 无
* @return : 无
*/
void led_init(void)
{
/* 1、初始化IO复用 */
IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03,0); /* 复用为GPIO1_IO0 */
/* 2、、配置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 低转换率
*/
IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_GPIO1_IO03,0X10B0);
/* 3、初始化GPIO,设置GPIO1_IO03设置为输出 */
GPIO1->GDIR |= (1 << 3);
/* 4、设置GPIO1_IO03输出低电平,打开LED0 */
GPIO1->DR &= ~(1 << 3);
}
/*
* @description : 打开LED灯
* @param : 无
* @return : 无
*/
void led_on(void)
{
/* 将GPIO1_DR的bit3清零 */
GPIO1->DR &= ~(1<<3);
}
/*
* @description : 关闭LED灯
* @param : 无
* @return : 无
*/
void led_off(void)
{
/* 将GPIO1_DR的bit3置1 */
GPIO1->DR |= (1<<3);
}
/*
* @description : 短时间延时函数
* @param - n : 要延时循环次数(空操作循环次数,模式延时)
* @return : 无
*/
void delay_short(volatile unsigned int n)
{
while(n--){}
}
/*
* @description : 延时函数,在396Mhz的主频下
* 延时时间大约为1ms
* @param - n : 要延时的ms数
* @return : 无
*/
void delay(volatile unsigned int n)
{
while(n--)
{
delay_short(0x7ff);
}
}
/*
* @description : mian函数
* @param : 无
* @return : 无
*/
int main(void)
{
clk_enable(); /* 使能所有的时钟 */
led_init(); /* 初始化led */
while(1) /* 死循环 */
{
led_off(); /* 关闭LED */
delay(500); /* 延时500ms */
led_on(); /* 打开LED */
delay(500); /* 延时500ms */
}
return 0;
}
和上一章一样,main.c 有 7 个函数,这 7 个函数的含义都一样,只是本例程我们使用的是移植好的 NXP 官方 SDK 里面的寄存器定义。main.c 文件的这 7 个函数的内容都很简单,前面都讲过很多次了,我们重点来看一下 led_init 函数中的第 31 行和第 43 行,这两行的内容如下:
cpp
IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0);
IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0X10B0);
这 里 使 用 了 两 个 函 数 IOMUXC_SetPinMux 和 IOMUXC_SetPinConfig , 其 中 函 数IOMUXC_SetPinMux 是用来设置 IO 复 用 功 能 的 , 最 终 肯 定 设 置 的 是 寄 存 器"IOMUXC_SW_MUX_CTL_PAD_XX"。函数 IOMUXC_SetPinConfig 设置的是 IO 的上下拉、速度等的,也就是寄存器"IOMUXC_SW_PAD_CTL_PAD_XX",所以上面两个函数其实就是上一章中的:
IOMUX_SW_MUX->GPIO1_IO03 = 0X5;
IOMUX_SW_PAD->GPIO1_IO03 = 0X10B0;
函数 IOMUXC_SetPinMux 在文件 fsl_iomuxc.h 中定义,函数源码如下:
static inline void IOMUXC_SetPinMux(uint32_t muxRegister,
uint32_t muxMode,
uint32_t inputRegister,
uint32_t inputDaisy,
uint32_t configRegister,
uint32_t inputOnfield)
{
*((volatile uint32_t *)muxRegister) =
IOMUXC_SW_MUX_CTL_PAD_MUX_MODE(muxMode) |
IOMUXC_SW_MUX_CTL_PAD_SION(inputOnfield);
if (inputRegister)
{
*((volatile uint32_t *)inputRegister) =
IOMUXC_SELECT_INPUT_DAISY(inputDaisy);
}
}
函数 IOMUXC_SetPinMux 有 6 个参数,这 6 个参数的函数如下:
muxRegister : IO 的 复 用 寄 存 器 地 址 , 比 如 GPIO1_IO03 的 IO 复 用 寄 存 器SW_MUX_CTL_PAD_GPIO1_IO03 的地址为 0X020E0068。
muxMode: IO 复用值,也就是 ALT0~ALT8,对应数字 0~8,比如要将 GPIO1_IO03 设置为 GPIO 功能的话此参数就要设置为 5。
inputRegister: 外设输入 IO 选择寄存器地址,有些 IO 在设置为其他的复用功能以后还需要设置 IO 输入寄存器,比如 GPIO1_IO03 要复用为 UART1_RX 的话还需要设置寄存器UART1_RX_DATA_SELECT_INPUT,此寄存器地址为 0X020E0624。
inputDaisy: 寄存器 inputRegister 的值,比如 GPIO1_IO03 要作为 UART1_RX 引脚的话此参数就是 1。
configRegister: 未使用,函数 IOMUXC_SetPinConfig 会使用这个寄存器。
inputOnfield : IO 软件输入使能,以 GPIO1_IO03 为例就是寄存器SW_MUX_CTL_PAD_GPIO1_IO03 的 SION 位(bit4)。如果需要使能 GPIO1_IO03 的软件输入功能的话此参数应该为 1,否则的话就为 0。
IOMUXC_SetPinMux 的函数体很简单,就是根据参数对寄存器 muxRegister 和inputRegister进行赋值。在"示例代码"中的 31 行使用此函数将 GPIO1_IO03 的复用功能设置为GPIO,如下:
IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0);
其中IOMUXC_GPIO1_IO03_GPIO1_IO03 是个宏,在文件fsl_iomuxc.h 中有定义,NXP 的 SDK 库将一个 IO 的所有复用功能都定义了一个宏,比如GPIO1_IO03 就有如下 9 个宏定义:
IOMUXC_GPIO1_IO03_I2C1_SDA
IOMUXC_GPIO1_IO03_GPT1_COMPARE3
IOMUXC_GPIO1_IO03_USB_OTG2_OC
IOMUXC_GPIO1_IO03_USDHC1_CD_B
IOMUXC_GPIO1_IO03_GPIO1_IO03
IOMUXC_GPIO1_IO03_CCM_DI0_EXT_CLK
IOMUXC_GPIO1_IO03_SRC_TESTER_ACK
IOMUXC_GPIO1_IO03_UART1_RX
IOMUXC_GPIO1_IO03_UART1_TX
上面 9 个宏定义分别对应着 GPIO1_IO03 的九种复用功能,比如复用为 GPIO 的宏定义就是:
#define IOMUXC_GPIO1_IO03_GPIO1_IO03 0x020E0068U, 0x5U, 0x00000000U,
0x0U, 0x020E02F4U
将这个宏带入到"示例代码"的 31 行以后就是:
IOMUXC_SetPinMux (0x020E0068U, 0x5U, 0x00000000U, 0x0U, 0x020E02F4U, 0);
接下来看一下函数 IOMUXC_SetPinConfig,此函数同样在文件 fsl_iomuxc.h 中有定义,函数源码如下:
static inline void IOMUXC_SetPinConfig(uint32_t muxRegister,
uint32_t muxMode,
uint32_t inputRegister,
uint32_t inputDaisy,
uint32_t configRegister,
uint32_t configValue)
{
if (configRegister)
{
*((volatile uint32_t *)configRegister) = configValue;
}
}
函数 IOMUXC_SetPinConfig 有 6 个参数,其中前五个参数和函数 IOMUXC_SetPinMux 一样,但是此函数只使用了参数 configRegister 和 configValue,cofigRegister 参数是 IO 配置寄存器地址,比如 GPIO1_IO03 的 IO 配置寄存器为 IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03,其地址为 0X020E02F4,参数 configValue 就是要写入到寄存器 configRegister 的值。同理,"示例代码"的 43 行展开以后就是:
IOMUXC_SetPinConfig(0x020E0068U, 0x5U, 0x00000000U, 0x0U, 0x020E02F4U, 0X10B0);
main.c 就讲到这里,基本和上一章一样,只是我们使用了 NXP 官方写好的寄存器定义,以后就可以使用这两个函数来方便的配置 IO 的复用功能和 IO 配置。
6、编写 Makefile 和链接脚本
新建 Makefile 文件,Makefile 文件内容如下:
cpp
CROSS_COMPILE ?= arm-linux-gnueabihf-
NAME ?= ledc
CC := $(CROSS_COMPILE)gcc
LD := $(CROSS_COMPILE)ld
OBJCOPY := $(CROSS_COMPILE)objcopy
OBJDUMP := $(CROSS_COMPILE)objdump
OBJS := start.o main.o
$(NAME).bin:$(OBJS)
$(LD) -Timx6ul.lds -o $(NAME).elf $^
$(OBJCOPY) -O binary -S $(NAME).elf $@
$(OBJDUMP) -D -m arm $(NAME).elf > $(NAME).dis
%.o:%.s
$(CC) -Wall -nostdlib -c -O2 -o $@ $<
%.o:%.S
$(CC) -Wall -nostdlib -c -O2 -o $@ $<
%.o:%.c
$(CC) -Wall -nostdlib -c -O2 -o $@ $<
clean:
rm -rf *.o $(NAME).bin $(NAME).elf $(NAME).dis
本章实验的 Makefile 文件是在第6章中的 Makefile 上修改的,只是使用到了变量。链接脚本 imx6ul.lds 的内容和上一章一样,可以直接使用上一章的链接脚本文件。
7、编译下载
使用 Make 命令编译代码,编译成功以后使用软件 imxdownload 将编译完成的 ledc.bin 文件生成可执行的img文件。
cpp
make
./imxdownload2 ledc.bin
利用Win32DiskImager软件将load.img执行文件写入SD卡,SD卡插入开发板上即可正常运行。如果代码运行正常的话 LED0 就会以 500ms 的时间间隔亮灭。