IMX6ULL裸机开发——点亮LED灯
一、汇编LED实验
1.GPIO详解
1.1 STM32 GPIO
STM32初始化GPIO有以下步骤
- 使能指定GPIO的时钟
- 初始化GPIO,比如输出功能、上拉、速度等等。
- IO复用
- 设置GPIO输出高电平和低电平
1.2 IO命名
打开IMUX6ULL参考手册的32章 IOMUX Controlled(IOMUXC)。
在IOMUXC Memory Map/Register Definition下,IOMUXC下,形如"IOMUXC_SW_MUC_CTL_PAD_GPIO1_IO00"就是GPIO命名。
1.3 IO复用配置
以GPIO01为例,查看PAD,寄存器32位,但只用到5位,其中bit0~bit3用来设置复用功能,GPIO01一共可以复用9种功能IO。
1.4 IO属性配置
GPIO有两个相关的寄存器:MUX和PAD。而MUX用来配置复用功能。
PAD也是32位寄存器,但是只用到17位。PAD主要是用来配置速度,驱动能力,压摆率等等。
1.5 GPIO配置
当IO用作GPIO时,需要设置的寄存器有8个。
DR、GDIR、PSR、ICR1、ICR2、EDGE_SEL、IMR和ISR。
DR 数据集寄存器
DR寄存器是32位,一个GPIO组最大有32给IO,故DR的每个位都对应一个IO。
若设置GPIO1_IO00输出高电平,那么设置GPIO1.DR=1。
GDIR 方向寄存器
用来设置GPIO的工作方式,输入/输出。
例如,要设置GPIO1_IO00为输入,则GPIO1.GDIR=0。
PSR 状态寄存器
ICR 中断控制寄存器
IMR 中断屏蔽寄存器
ISR 中断状态寄存器
1.6 GPIO时钟使能
参考 Clock Controller Module(CCM)
只关注外设时钟使能寄存器,CCGR。
CCGR0~CCGR6这7个寄存器,控制着IMUX6U的所有外设时钟开关。
CCM_CCGR0也是32位寄存器,每2位控制一个外设的时钟。例如,bit31:30控制GPIO2的外设时钟。
2.小节
IMU6U的IO作为GPIO使用,需要以下几步
- 使能GPIO对应的时钟。
- 设置寄存器MUX_PAD,设置IO复用功能,使其复用成GPIO功能。
- 设置寄存器PAD_PAD,设置IO的上下拉、速度等等。
- 设置GPIO的8个寄存器,例如设置输入/输出,中断等。
3.程序编写
1、使能外设时钟
查看芯片手册CCM_CCGR0,address为20C_4068h
则要对地址0x020c4068写入数据0xffffffff,使能所有外设时钟。
而CCGR也是32位寄存器,也就是4字节,故相邻寄存器间的偏移量位0x04(地址编排的单位是字节)。
那么CCGR1的地址为0x020C406C,然后写入数据0xffffffff,使能所有外设时钟。同理其他的CCGR。
2、IO复用成GPIO
查看芯片手册IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03
address为20E_0068h
ALT5:复用成GPIO1,0101
故对0x020e0068写入0x5即可。
3、配置IO的电气属性
查看芯片手册IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03
address为20E_02F4h
对IO做以下配置:
bit0: 0 低速。
bit5:3 110 R0/6驱动能力,R0为260欧姆电阻。
bit7:6 10 100MHz速度
bit11: 0 关闭开路输出
bit12: 1 使能pull/kepper
bit13: 0 kepper
bit15:14 00 100K下拉
bit16: 0 关闭hys
也就是:0 00 0 1 0 000 10 110 00 0
换算成16进制:0x10B0
将0x10B0写入0x020e02f4。
4、设置GPIO的寄存器
查看参考手册中GPIO Memory Map/Register
GPIO1_DR的地址为:209_C000
打开LED,只要让DR的值为0即可,即对0x0209c000写入0.
GPIO1_GDIR的地址为:基地址+4h,即209_C004
设置GPIO1_IO03为输出,也就是GDIR的3bit设置为1,即0x8。
对0x0209c0000写入0x8。
二、C语言LED实验
1.汇编搭建C环境
1.1 设置处理器模式
CPSR状态寄存器的M[4:0]后5位用来设置处理器模式,先将这5位置0,再与0x13求或,此时为SVC模式。
1.2 设置栈地址
开发板的DDR3的地址范围为:0X80000000~0X90000000(256MB)
0x100000000也就是231b=211Mb=2^8MB=256MB
栈的大小为2MB,也就是栈地址0x80200000。
然后设置跳转到main函数。
2.C语言编写
2.1 main.h
首先的宏定义:
ifndef __MAIN_H
define __MAIN_H
用来保护重复定义。
地址的定义类型:volatile unsigned int *
定义CCM相关寄存器地址
定义IOMUX相关寄存器地址
定义GPIO1相关寄存器地址
2.2 main.c
定义函数clk_enable
使能所有的时钟
将main.h中定义好的CCM_CCGR0~6,全部赋值为0xffffffff.
定义函数led_init
初始化IO复用,即MUX=0x5
设置IO电气属性,即PAD=0x10B0
初始化GPIO为输出模式,GDIR=0X8
设置GPIO的输出为低电平,DR=0x0
设置闪烁灯
函数led_on,DR的bit3清0
DR &= ~(1<<3)
1<<3也就是1000,然后取反0111,再求与,其他位与1求与不变。
函数led_off,DR的bit3置1
DR |=(1<<3)
函数delay_short和delay用来设置延时大小。
主函数
- 使能时钟
- 初始化led
- 死循环中,关led,延时,开led,延时
三、驱动开发格式
1.编写结构体
将同属于一个外设的所有寄存器编写到一个结构体中,
2.定义寄存器组的基地址
寻找第一个成员变量的地址,结构体会以4字节的大小顺序寻址,从而间接的定义了结构体所有的成员变量的地址。
案例 imx6ul.h
//外设寄存器组的基地址
#define CCM_BASE (0x020c4000)
#define GPIO1_BASE (0X0209C000)
//CCM 寄存器结构体定义
typedef struct
{
volatile unsigned int CCR; //第一个成员变量
...
volatile unsigned int CCGR0;
...
volatile unsigned int CCGR6;
} CCM_Type;
//IOMUX 寄存器组
typedef struct
{
volatile unsigned int BOOT_MODE0;
...;
volatile unsigned int CSI_DATA07;
} IOMUX_SW_MUX_Type;
typedef struct
{
volatile unsigned int DRAM_ADDR00;
...;
volatile unsigned int GRP_DDR_TYPE;
} IOMUX_SW_PAD_Type;
//GPIO 寄存器结构体
typedef struct{
volatile unsigned int DR;
...;
volatile unsigned int EDGE_SEL;
} GPIO_Type;
//外设指针
#define CCM ((CCM_Type *)CCM_BASE)
#define IOMUX_SW_MUX ((IOMUX_SW_Type *)IOMUX_SW_MUX_BASE)
#define IOMUX_SW_PAD ((IOMUX_SW_Type *)IOMUX_SW_PAD_BASE)
#define GPIO1 ((GPIO_Type *)GPIO1_BASE)
...
在编写寄存器组的结构体的时候注意寄存器的地址是否连续,即有一些保留地址。
于是要写:volatile unsigned int RESERVED_3[1]
3.主函数
#include "imu6ul.h"
//使能所有时钟
void clk_enable(void){
CCM->CCGR0 = 0XFFFFFFFF;
CCM->CCGR1 = 0XFFFFFFFF;
...;
}
//初始化LED对应的GPIO
void led_init(void){
//初始化IO复用
IOMUX_SW_MUX->GPIO1_IO03 = 0X5;
//配置IO电气属性
IOMUX_SW_PAD->GPIO1_IO03 = 0X10B0;
//初始化GPIO
GPIO1->GDIR = 0X8; //输出模式
GPIO1->DR &= ~(1<<3); //设置输出位低电平
}
//打开LED灯
void led_on(void){
//将DR的bit3清0
GPIO1->DR &= ~(1<<<3);
}
//关闭LED灯
void led_off(void){
//将DR的bit3置1
GPIO1->DR |=(1<<3);
}
//短时间延时函数
void delay_short(volatile unsigned n){
while(n--){}
}
//延时函数
void delay(volatile unsigned n){
while(n--){
delay_short(0x7ff); //大约1ms
}
}
//main函数
int main(void){
clk_enable(); //使能时钟
led_init(); //初始化LED
while(1){
led_off();
delay(500);
led_on();
delay(500);
}
return 0;
}
四、官方SDK
1.SDK包
与寄存器定义相关的文件:
fsl_common.h
fsl_iomuxc.h
MCIMX6Y2.h
2.创建 cc.h 文件
cc.h文件存放一些SDK库文件需要的数据类型
#ifndef __CC_H
#define __CC_H
#define __I volatile
#define __O volatile
#define __IO volatile
#define ON 1
#define OFF 0
typedef signed char int8_t;
typedef signed short int int16_t;
...;
#endif
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)
static inline void IOMUXC_SetPinConfig(uint32_t muxRegister,
uint32_t muxMode,
uint32_t inputRegister,
uint32_t inputDaisy,
uint32_t configRegister,
uint32_t configValue)
IO复用的函数
IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03,0),传入寄存器地址和IO使能,其他参数可以不用设置。
IO电气设置的函数
IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_GPIO1_IO03,0X10B0)
而IOMUXC_GPIO1_IO03_GPIO1_IO03是fsl_iomuxc.h定义的宏。
而GPIO1_IO03 有9个宏定义,例如IOMUXC_GPIO1_IO03_I2C1_SDA
五、BSP工程管理实验
1.结构
一般来说工程结构应该如下:
-bsp
-imx6ul
-obj
-project
其中bsp用来存放驱动文件
imx6ul存放芯片有关的文件,例如SDK文件
obj存放编译生成的.o文件
project存放start.S和main.c文件
将上一章的实验中的:
cc.h fsl_common.h fsl_iomuxc.h MCIMX6Y2.h
拷贝到文件imx6ul中
将start.S和main.c存放到project中
将函数clk_enable led_init delay写到bsp中
2.各文件创建
2.1 imx6ul.h
#ifndef __IMX6UL_H
#define __IMX6UL_H
#include "cc.h"
#include "MCIMX6Y2.h"
#include "fsl_common.h"
#include "fsl_iomuxc.h"
#endif
该文件只是引用了一些芯片相关的头文件。
2.2 编写led驱动代码
bsp_led.h
#ifndef __BSP_LED_H
#define __BSP_LED_H
#include "imx6ul.h"
#define LED0 0
//函数声明
void led_init(void);
void led_switch(int led, int status);
#endif
bsp_led.c
#include "bsp_led.h"
void led_init(void){
//IO复用
IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03,0);
//IO属性
IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_GPIO1_IO03,0X10B0);
//GPIO设置为输出
GPIO->GDIR |= (1<<3);
//GPIO输出为低电平
GPIO->DR &= ~(1<<3);
}
void led_switch(int led, int status)
{
switch(led){
case LED0:
if(status == ON)
GPIO1->DR &= ~(1<<3);
else if(status == OFF)
GPIO1->DR |= (1<<3);
break;
}
}
2.3 编写时钟驱动
bsp_clk.h
#ifndef __BSP_CLK_H
#define __BSO_CLK_H
#include "imx6ul.h"
void clk_enable(void)
#endif
bsp_clk.c
#include "bsp_clk.h"
void clk_enable(void){
CCM->CCG0 = 0XFFFFFFFF;
CCM->CCG1 = 0XFFFFFFFF;
CCM->CCG2 = 0XFFFFFFFF;
CCM->CCG3 = 0XFFFFFFFF;
CCM->CCG4 = 0XFFFFFFFF;
CCM->CCG5 = 0XFFFFFFFF;
CCM->CCG6 = 0XFFFFFFFF;
}
2.4 编写延时驱动
bsp_delay.h
#ifndef __BSP_DELAY_H
#define __BSP_DELAY_H
#include "imx6ul.h"
void delay(volatile unsigned int n);
#endif
bsp_delay.c
#include "bsp_delay.h"
void delay_short(volatile unsigned int n){
while(n--){}
}
void delay(volatile unsigned int n){
while(n--){
delay_short(0x7ff);
}
}
2.5 修改main.c
#include "bsp_clk.h"
#include "bsp_led.h"
#include "bsp_delay.h"
int main(void){
clk_enable();
led_init();
while(1){
led_switch(LED0,0N);
delay(500);
led_switch(LED0,OFF);
delay(500);
}
return 0;
}
作者:年轮不改