上一篇使用C语言代码来驱动LED,之前我们是手动设置的每一个寄存器的地址,但是这样的效率太低,而且很麻烦。此时我们注意到同属于 GPIO_CCGRx 这一类的寄存器地址,他们之间都相差 4 个字节。
我们要利用这一特性,将之前的代码改为标准的驱动格式。
目录
一、start.s 汇编文件
二、结构体内存对齐规则
三、register.h 文件
完整 register.h
四、led.c 文件
1、引用方法
2、完整代码
这里就不再赘述,详情可以参考:C 代码驱动LED
这里建议先了解结构体内存对齐规则的前两点:结构体内存对齐规则
以下面这个结构体为例:
typedef struct
{
volatile uint32_t CCGR0;
volatile uint32_t CCGR1;
volatile uint32_t CCGR2;
volatile uint32_t CCGR3;
volatile uint32_t CCGR4;
volatile uint32_t CCGR5;
volatile uint32_t CCGR6;
} CCM_CCGR_Type;
结构体对齐规则第一点:第一个成员要放在与结构体起始位置偏移量为0的位置。CCGR0 占4个字节,保存在相对于结构体起始位置偏移量为 0 的位置。
结构体对齐规则第二点:第二个成员要放在偏移量为“ 对齐数的整数倍 ” 的位置。Linux环境下,对齐数 = 变量自身所占空间大小。
CCGR1占4个字节,保存的位置是在偏移量为 4 的整数倍的位置。首先看偏移量为 0,已经保存了CCGR0;然后再看偏移量为 4,还是空的,CCGR1 就可以保存在这个位置。
由此我们可以发现一个规律,因为每个寄存器的地址固定是 4 个字节,只要我们给定结构体的起始位置,里面的寄存器会自动分配地址,而且分配的地址正好就跟我们手动赋值的效果一样(这是刚好利用了结构体对齐规则的特性)。
注意:如果中间存在地址断开的情况,需要手动占位,或者单独给寄存器地址声明为宏
时钟相关结构体
定义一个结构体,这个结构体包含与时钟相关的寄存器,结构体重命名为 CCM_CCGR_Type
typedef struct
{
volatile uint32_t CCGR0;
volatile uint32_t CCGR1;
volatile uint32_t CCGR2;
volatile uint32_t CCGR3;
volatile uint32_t CCGR4;
volatile uint32_t CCGR5;
volatile uint32_t CCGR6;
} CCM_CCGR_Type;
CCGR0 的地址为 0x20C4068,这也将作为结构体的起始地址,那要如何把这个地址当做是结构体地址呢?强制转化为 CCM_CCGR_Type* 类型,指针类型会告诉编译器,这个地址占了多大空间。
比如 char* 表示告诉指针,这个地址指向的内容占了一个字节;CCM_CCGR_Type* 表示这个地址指向的内容占了 28 个字节。访问结构体中的成员时,便是由起始地址向后访问。
#define CCM_CCGR ((CCM_CCGR_Type*)0x20C4068)
后面我们只需要使用 CCM_CCGR 便可以访问结构体成员了。
IOMUX 相关寄存器地址
和上面定义CCGR结构体一样,每个地址都强制类型转换成对应的结构体指针类型。
我们会发现 IO_MUX 的地址并不连续,除非自己补上IO_MUX 其他无关寄存器。实际上IOMUX包含了其他很多寄存器,只不过驱动LED不需要那么多寄存器。为了和其他地方统一,这里依然封装成结构体的形式。
typedef struct
{
volatile unsigned int GPIO1_IO03;
} IOMUX_SW_MUX_Type;
typedef struct
{
volatile unsigned int GPIO1_IO03;
} IOMUX_SW_PAD_Type;
// 注意: 这里定义的是一个结构体指针,不需要解引用
#define IOMUX_SW_MUX ((IOMUX_SW_MUX_Type*)0x020E0068) // 设置IO复用
#define IOMUX_SW_PAD ((IOMUX_SW_PAD_Type*)0x020E02F4) // 设置电气属性
GPIO1 相关寄存器地址
和上面定义 CCGR 结构体一样,每个地址都强制类型转换成对应的结构体指针类型。
除了GPIO1外,还有GPIO2、GPIO3,这里仅列举出GPIO1的
typedef struct
{
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;
} GPIO1_Type;
// 注意: 这里定义的是一个结构体指针,不需要解引用
#define GPIO1 ((GPIO1_Type*)0x209C000) // GPIO1_Type 结构体地址(寄存器GPIO1_DR的地址)
完整 register.h
#ifndef _register_h
#define _register_h
typedef unsigned int uint32_t;
typedef struct
{
volatile uint32_t CCGR0;
volatile uint32_t CCGR1;
volatile uint32_t CCGR2;
volatile uint32_t CCGR3;
volatile uint32_t CCGR4;
volatile uint32_t CCGR5;
volatile uint32_t CCGR6;
} CCM_CCGR_Type;
typedef struct
{
volatile unsigned int GPIO1_IO03;
} IOMUX_SW_MUX_Type;
typedef struct
{
volatile unsigned int GPIO1_IO03;
} IOMUX_SW_PAD_Type;
typedef struct
{
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;
} GPIO1_Type;
#define CCM_CCGR ((CCM_CCGR_Type*)0x20C4068) // CCM_CCGR 结构体地址(寄存器 CCGR0 的地址)
#define IOMUX_SW_MUX ((IOMUX_SW_MUX_Type*)0x020E0068) // 设置IO复用
#define IOMUX_SW_PAD ((IOMUX_SW_PAD_Type*)0x020E02F4) // 设置电气属性
#define GPIO1 ((GPIO1_Type*)0x209C000) // GPIO1_Type 结构体地址(寄存器GPIO1_DR的地址)
#endif
以访问时钟寄存器 CCGR0 为例,我们在register.h 的最后定义了宏,这个宏可以看做是结构体指针,我们直接使用宏来调用内部成员。
// CCM_CCGR 是在 register.h 里定义的宏
// CCGR0 是结构体 CCM_CCGR_Type 的成员
CCM_CCGR->CCGR0 = 0xffffffff;
#include "register.h"
void clk_enable(); // 时钟源初始化
void led_init(); // 设置IO复用为GPIO、初始化GPIO
void delay_short(unsigned int n); // 短时延时
void delay(unsigned int n); // 延时
void led_on(); // 点灯
void led_off(); // 熄灯
int main(void)
{
/* 1、初始化时钟源 */
clk_enable();
/* 2、初始化LED */
led_init();
while (1)
{
led_on();
delay(500);
led_off();
delay(500);
}
return 0;
}
void clk_enable()
{
CCM_CCGR->CCGR0 = 0xffffffff;
CCM_CCGR->CCGR1 = 0xffffffff;
CCM_CCGR->CCGR2 = 0xffffffff;
CCM_CCGR->CCGR3 = 0xffffffff;
CCM_CCGR->CCGR4 = 0xffffffff;
CCM_CCGR->CCGR5 = 0xffffffff;
CCM_CCGR->CCGR6 = 0xffffffff;
}
void led_init()
{
/* 1、设置GPIO复用 */
IOMUX_SW_MUX->GPIO1_IO03 = 0x5;
/* 2、设置GPIO电气属性 */
IOMUX_SW_PAD->GPIO1_IO03 = 0x10B0;
/* 3、GPIO 设为输出 */
GPIO1->GDIR = 0x00000008;
}
void delay(unsigned int n)
{
while (n--)
{
delay_short(0x7ff);
}
}
void delay_short(unsigned int n)
{
while(n--) {}
}
void led_on()
{
GPIO1->DR &= (~0x08);
}
void led_off()
{
GPIO1->DR |= 0x08;
}