I.MX6U 裸机开发6. 结构体编程风格使用寄存器

  • 一、STM32 库函数的开发风格
  • 二、代码编写
  • 1. 清除 bss 段
  • 2. 完整启动文件
  • 3. 定义寄存器结构体
  • 4. main.c
  • 使能时钟:
  • 初始化led,
  • 开关led
  • 完整的main.c
  • 5. Makefile
  • 6. 链接文件


I.MX6U 裸机开发6. 结构体编程风格使用寄存器_嵌入式硬件

一、STM32 库函数的开发风格

STM32库函数通过将寄存器定义为结构体的方式,提供了一种高效且易于维护的编程风格。
在STM32库中,给每个外设会对应定义一个结构体,结构体的每个成员变量对应一个寄存器。例如,GPIO外设的寄存器定义如下:

typedef struct
{
  __IO uint32_t MODER;    /*!< GPIO 端口模式寄存器,               地址偏移: 0x00 */
  __IO uint32_t OTYPER;   /*!< GPIO 端口输出类型寄存器,          地址偏移: 0x04 */
  __IO uint32_t OSPEEDR;  /*!< GPIO 端口输出速度寄存器,          地址偏移: 0x08 */
  __IO uint32_t PUPDR;    /*!< GPIO 端口上拉/下拉寄存器,         地址偏移: 0x0C */
  __IO uint32_t IDR;      /*!< GPIO 端口输入数据寄存器,          地址偏移: 0x10 */
  __IO uint32_t ODR;      /*!< GPIO 端口输出数据寄存器,          地址偏移: 0x14 */
  __IO uint32_t BSRR;     /*!< GPIO 端口位设置/重置寄存器,       地址偏移: 0x18 */
  __IO uint32_t LCKR;     /*!< GPIO 端口配置锁定寄存器,          地址偏移: 0x1C */
  __IO uint32_t AFR[2];   /*!< GPIO 端口复用功能寄存器,          地址偏移: 0x20-0x24 */
} GPIO_TypeDef;

然后,通过定义指向这些结构体的指针,可以方便地访问和操作寄存器。例如:

#define GPIOA               ((GPIO_TypeDef *) GPIOA_BASE)
#define GPIOB               ((GPIO_TypeDef *) GPIOB_BASE)

这样做有以下好处:

  1. 代码可读性高:使用结构体定义寄存器后,代码更加直观。例如,设置GPIOA的模式寄存器可以写成GPIOA->MODER = 0x00000000;,一目了然。
  2. 易于维护:当寄存器地址或结构发生变化时,只需修改结构体定义或基地址定义,其他代码无需改动,极大地提高了代码的可维护性。
  3. 减少错误:通过结构体成员访问寄存器,可以避免直接使用魔法数(硬编码的地址),减少了出错的可能性。
  4. 便于调试:结构体的使用使得调试工具能够更好地解析和显示寄存器的内容,方便开发人员进行调试。
  5. 代码复用性高:结构体定义的寄存器可以在不同的项目中复用,减少了重复代码的编写,提高了开发效率。

二、代码编写

1. 清除 bss 段

BSS 段用于存储程序中未初始化的全局变量和静态变量。

下面定义两个全局符号 _bss_start 和 _bss_end,并将它们分别与 __bss_start 和 __bss_end 关联起来。
这些符号通常用于标识 BSS 段的起始和结束位置。

.global _bss_start
_bs_start:
    .word __bss_start

.global _bss_end
_bss_end:
    .word __bss_end

下面是清除的过程 :

/** 清除 bss 段 */
    LDR R0, =__bss_start    /** 获取 bss 段的起始地址 */
    LDR R1, =__bss_end      /** 获取 bss 段的结束地址 */
    MOV R2, #0              /** 设置清零的值 */
    B bss_loop              /** 跳转到 bss_loop */
bss_loop:
    STMIA R0!, {R2}         /** 将 R2 的值存储到 R0 指向的地址,然后 R0 += 4 */
    CMP R0, R1              /** 比较 R0 和 R1 的值, 即有没有到结尾 */
    BLE bss_loop            /** 如果 R0 <= R1,跳转到 bss_loop */
bss_end:

2. 完整启动文件

.global _start
.global _bss_start
_bs_start:
    .word __bss_start

.global _bss_end
_bss_end:
    .word __bss_end


_start:
    /** 设置处理进入 SVC 模式 */
    MRS R0, CPSR        /** 读取当前的 CPSR */
    BIC R0, R0, #0x1F   /** 清除 CPSR 的低 5 位 */
    ORR R0, R0, #0x13   /** 设置 CPSR 为 SVC 模式 */
    MSR CPSR, R0        /** 设置 CPSR */

    /** 清除 bss 段 */
    LDR R0, =__bss_start    /** 获取 bss 段的起始地址 */
    LDR R1, =__bss_end      /** 获取 bss 段的结束地址 */
    MOV R2, #0              /** 设置清零的值 */
    B bss_loop              /** 跳转到 bss_loop */
bss_loop:
    STMIA R0!, {R2}         /** 将 R2 的值存储到 R0 指向的地址,然后 R0 += 4 */
    CMP R0, R1              /** 比较 R0 和 R1 的值, 即有没有到结尾 */
    BLE bss_loop            /** 如果 R0 <= R1,跳转到 bss_loop */
bss_end:
    /** 设置堆栈 */
    LDR SP, =0x80200000
    B main              /* 跳转到C语言main函数*/

3. 定义寄存器结构体

通过 《IMX6ULL参考手册》 P 658,可以看到相关结构体的地址:

I.MX6U 裸机开发6. 结构体编程风格使用寄存器_嵌入式硬件_02

如下进行定义:

/* GPIO寄存器结构体定义 */
typedef struct
{
    __IO uint32_t CCR;
    __IO uint32_t CCDR;
    __IO uint32_t CSR;
    __IO uint32_t CCSR;
    __IO uint32_t CACRR;
    __IO uint32_t CBCDR;
    __IO uint32_t CBCMR;
    __IO uint32_t CSCMR1;
    __IO uint32_t CSCMR2;
    __IO uint32_t CSCDR1;
    __IO uint32_t CS1CDR;
    __IO uint32_t CS2CDR;
    __IO uint32_t CDCDR;
    __IO uint32_t CHSCCDR;
    __IO uint32_t CSCDR2;
    __IO uint32_t CSCDR3;
    __IO uint32_t reserved1[2];
    __IO uint32_t CDHIPR;
    __IO uint32_t reserved2[2];
    __IO uint32_t CLPCR;
    __IO uint32_t CISR;
    __IO uint32_t CIMR;
    __IO uint32_t CCOSR;
    __IO uint32_t CGPR;
    __IO uint32_t CCGR0;
    __IO uint32_t CCGR1;
    __IO uint32_t CCGR2;
    __IO uint32_t CCGR3;
    __IO uint32_t CCGR4;
    __IO uint32_t CCGR5;
    __IO uint32_t CCGR6;
__IO uint32_t reserved3[1];
    __IO uint32_t CMEOR;
} CCM_Type;

类似的对IOMUX_SW_MUX 、CCM_ANALOG、 IOMUX_SW_PAD进行定义,完整文件参考正点原子教程代码。

4. main.c

对前一章的内容进行修改

使能时钟:

int enable_clock(void){
    CCM_CCGR1 = 0xffffffff;
    CCM_CCGR2 = 0xffffffff;
    CCM_CCGR3 = 0xffffffff;
    CCM_CCGR4 = 0xffffffff;
    CCM_CCGR5 = 0xffffffff;
    CCM_CCGR6 = 0xffffffff;

    return 0;
}

改为:

int enable_clock(void){
    CCM->CCGR1 = 0xffffffff;
    CCM->CCGR2 = 0xffffffff;
    CCM->CCGR3 = 0xffffffff;
    CCM->CCGR4 = 0xffffffff;
    CCM->CCGR5 = 0xffffffff;
    CCM->CCGR6 = 0xffffffff;

    return 0;
}

初始化led,

/**
 * 初始化LED
 * @return
 */
 void init_led(void){
    IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = 0x5; // 设置复用为GPIO1_IO03
    IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 = 0x10B0;  // 设置电气属性
    GPIO1_GDIR |= (1<<3);   // 设置为输出
}

改为:

/**
 * 初始化LED
 * @return
 */
void init_led(void){
    IOMUX_SW_MUX->GPIO1_IO03 = 0x5; // 设置复用为GPIO1_IO03
    IOMUX_SW_PAD->GPIO1_IO03 = 0x10B0;  // 设置电气属性
    GPIO1->GDIR |= (1 << 3);   // 设置为输出
}

开关led

void led_on(void){
    GPIO1_DR &= ~(1<<3);
}
void led_off(void){
    GPIO1_DR |= (1<<3);
}

改为:

void led_on(void){
    GPIO1->DR &= ~(1 << 3);
}

void led_off(void){
    GPIO1->DR |= (1 << 3);
}

完整的main.c

#include "imx6u.h"

/**
 * 使能时钟
 * @return
 */
int enable_clock(void){
    CCM->CCGR1 = 0xffffffff;
    CCM->CCGR2 = 0xffffffff;
    CCM->CCGR3 = 0xffffffff;
    CCM->CCGR4 = 0xffffffff;
    CCM->CCGR5 = 0xffffffff;
    CCM->CCGR6 = 0xffffffff;

    return 0;
}

/**
 * 初始化LED
 * @return
 */
void init_led(void){
    IOMUX_SW_MUX->GPIO1_IO03 = 0x5; // 设置复用为GPIO1_IO03
    IOMUX_SW_PAD->GPIO1_IO03 = 0x10B0;  // 设置电气属性
    GPIO1->GDIR |= (1 << 3);   // 设置为输出
}

void led_on(void){
    GPIO1->DR &= ~(1 << 3);
}

void led_off(void){
    GPIO1->DR |= (1 << 3);
}

void delay_short(volatile unsigned int n){
    while(n--);
}

void delay(volatile unsigned int n){
    while(n--){
        delay_short(0x7ff);
    }
}

int main(void){
    /** 初始化LED **/
    enable_clock();

    // 初始化LED
    init_led();

    while(1){
        led_on();
        delay(500);
        led_off();
        delay(500);
    }

    return 0;
}

5. Makefile

首先定义几个符号:

objs 	:= start.o main.o
ld   	:= arm-linux-gnueabihf-ld
cc   	:= arm-linux-gnueabihf-gcc
objcopy := arm-linux-gnueabihf-objcopy
objdump := arm-linux-gnueabihf-objdump

注意和前节区别 , 这里赋值使用了冒号等于。在 Makefile 中,objs := start.o main.o 和 objs = start.o main.o 的区别如下:

  • := 是立即展开赋值符号,表示在赋值时立即展开右边的值,并将其赋给左边的变量。也就是说,objs 的值在赋值时就已经确定,不会再改变。
  • = 是延迟展开赋值符号,表示在每次使用变量时才展开右边的值。也就是说,objs 的值在每次使用时都会重新计算。
    在大多数情况下,这两者的效果是相同的,但在某些复杂的场景下,使用 := 可以避免一些意外的结果

6. 链接文件

与上节一致。

运行编译烧录:

make clean
make
make download

将SD卡安装到开发板,可以看到LED1闪烁,与前一篇文件效果相同。