【裸机驱动LED】使用汇编代码驱动LED(四)—— 驱动格式开发篇

上一篇使用C语言代码来驱动LED,之前我们是手动设置的每一个寄存器的地址,但是这样的效率太低,而且很麻烦。此时我们注意到同属于 GPIO_CCGRx 这一类的寄存器地址,他们之间都相差 4 个字节。

【裸机驱动LED】使用汇编代码驱动LED(四)—— 驱动格式开发篇_第1张图片

我们要利用这一特性,将之前的代码改为标准的驱动格式。


目录

一、start.s 汇编文件

二、结构体内存对齐规则

三、register.h 文件

完整 register.h

四、led.c 文件

1、引用方法

2、完整代码


一、start.s 汇编文件

这里就不再赘述,详情可以参考: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 的位置。

【裸机驱动LED】使用汇编代码驱动LED(四)—— 驱动格式开发篇_第2张图片

结构体对齐规则第二点:第二个成员要放在偏移量为“ 对齐数的整数倍 ” 的位置。Linux环境下,对齐数 = 变量自身所占空间大小。

CCGR1占4个字节,保存的位置是在偏移量为 4 的整数倍的位置。首先看偏移量为 0,已经保存了CCGR0;然后再看偏移量为 4,还是空的,CCGR1 就可以保存在这个位置。

【裸机驱动LED】使用汇编代码驱动LED(四)—— 驱动格式开发篇_第3张图片

由此我们可以发现一个规律,因为每个寄存器的地址固定是 4 个字节,只要我们给定结构体的起始位置,里面的寄存器会自动分配地址,而且分配的地址正好就跟我们手动赋值的效果一样(这是刚好利用了结构体对齐规则的特性)。

【裸机驱动LED】使用汇编代码驱动LED(四)—— 驱动格式开发篇_第4张图片

注意:如果中间存在地址断开的情况,需要手动占位,或者单独给寄存器地址声明为宏

三、register.h 文件

时钟相关结构体

定义一个结构体,这个结构体包含与时钟相关的寄存器,结构体重命名为 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

四、led.c 文件

1、引用方法

以访问时钟寄存器 CCGR0 为例,我们在register.h 的最后定义了宏,这个宏可以看做是结构体指针,我们直接使用宏来调用内部成员。

// CCM_CCGR 是在 register.h 里定义的宏
// CCGR0 是结构体 CCM_CCGR_Type 的成员
CCM_CCGR->CCGR0 = 0xffffffff;

2、完整代码

#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;
}

你可能感兴趣的:(#,裸机开发,汇编,驱动开发)