ARM汇编学习笔记(四):i.MX6ULL裸机开发:从汇编点灯到C语言BSP工程管理

前言

在上一篇文章中,我们完成了汇编语言的LED点灯实验。今天我们将进一步优化,用C语言重写LED驱动,引入SDK头文件,编写蜂鸣器驱动,并使用BSP工程管理方式组织代码,最后通过链接脚本控制程序布局。

本文涉及的知识点较多,建议按顺序阅读:

  1. volatile关键字与寄存器地址定义
  2. C语言点灯实现
  3. Makefile优化
  4. 寄存器访问方式优化(结构体封装)
  5. SDK头文件的使用
  6. 蜂鸣器驱动编写
  7. BSP工程管理
  8. 链接脚本编写

一、volatile关键字

在嵌入式开发中,volatile是一个非常重要的关键字。它告诉编译器:

这个变量的值可能会被程序之外的因素改变,每次访问都必须从内存中重新读取,不能优化。

对于寄存器操作,必须使用volatile修饰,否则编译器可能会将多次读写优化掉,导致程序行为异常。

c 复制代码
#define P2 *((volatile unsigned char*)0x80)

二、C语言点灯实现

2.1 定义寄存器地址

方式一:宏定义直接映射

c 复制代码
#define CCM_CCGR0 *((unsigned int *)0x020C4068)
#define CCM_CCGR1 *((unsigned int *)0x020C406C)
#define CCM_CCGR2 *((unsigned int *)0x020C4070)
#define CCM_CCGR3 *((unsigned int *)0x020C4074)
#define CCM_CCGR4 *((unsigned int *)0x020C4078)
#define CCM_CCGR5 *((unsigned int *)0x020C407C)
#define CCM_CCGR6 *((unsigned int *)0x020C4080)

#define IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03    *((unsigned int *)0x020E0068)
#define IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03    *((unsigned int *)0x020E02F4)
#define GPIO1_DR                            *((unsigned int *)0x0209C000)
#define GPIO1_GDIR                          *((unsigned int *)0x0209C004)

2.2 编写驱动函数

c 复制代码
void clock_init(void)
{
    CCM_CCGR0 = 0xFFFFFFFF;
    CCM_CCGR1 = 0xFFFFFFFF;
    CCM_CCGR2 = 0xFFFFFFFF;
    CCM_CCGR3 = 0xFFFFFFFF;
    CCM_CCGR4 = 0xFFFFFFFF;
    CCM_CCGR5 = 0xFFFFFFFF;
    CCM_CCGR6 = 0xFFFFFFFF;
}

void led_init(void)
{
    IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = 0x05;
    IOMUXC_SW_PAD_CTL_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 led_delay(unsigned int time)
{
    while (time--);
}

三、优化Makefile

makefile 复制代码
COMPLITER = arm-linux-gnueabihf-
CC = $(COMPLITER)gcc
LD = $(COMPLITER)ld
OBJCOPY = $(COMPLITER)objcopy
OBJDUMP = $(COMPLITER)objdump

OBJS = start.o main.o
TARGET = led

%.o : %.S
	$(CC) -c $^ -o $@ -g

%.o : %.c
	$(CC) -c $^ -o $@ -g

$(TARGET).bin : $(OBJS)
	$(LD) -Ttext 0x87800000 $^ -o $(TARGET).elf
	$(OBJCOPY) -O binary -S -g $(TARGET).elf $@
	$(OBJDUMP) -D $(TARGET).elf > $(TARGET).dis

clean:
	rm $(OBJS) $(TARGET).elf $(TARGET).bin $(TARGET).dis -f

load:
	./imxdownload $(TARGET).bin /dev/sdb

注意:原文件中COMPLITER拼写有误,正确应为COMPILER,这里保留原样以便对照,实际使用建议修正。


四、优化寄存器访问方式

当寄存器数量较多时,使用宏定义会显得冗长。我们可以用结构体封装一组连续的寄存器:

c 复制代码
struct GPIO_t
{
    unsigned int DR;
    unsigned int DGIR;
    unsigned int PSR;
    unsigned int ICR1;
    unsigned int ICR2;
    unsigned int IMR;
    unsigned int ISR;
    unsigned int EDGE_SEL;
};
#define GPIO1 (*((struct GPIO_t *)0x0209C000))

这样访问寄存器就像访问结构体成员一样自然:GPIO1->DRGPIO1->GDIR

小提示:结构体成员DGIR应为GDIR(General Direction Register),原笔记中拼写有误,实际开发中需以参考手册为准。


五、使用SDK头文件

5.1 SDK简介

NXP官方提供了i.MX6ULL的SDK包,路径为IMAX6ULL/SDK/。虽然完整的SDK是一个IDE(类似Keil),需要额外的下载器、仿真器,但在裸机开发中,我们只需要它的头文件即可。

5.2 构建新工程

  1. 新建文件夹led_sdk
  2. 拷贝led_c中的start.Smain.cMakefile
  3. 拷贝SDK目录下所有头文件到新工程
  4. 熟悉关键头文件,如GPIO_Type

5.3 利用SDK编写点灯程序

c 复制代码
void clock_init(void)
{
    CCM->CCGR0 = 0xFFFFFFFF;
    CCM->CCGR1 = 0xFFFFFFFF;
    CCM->CCGR2 = 0xFFFFFFFF;
    CCM->CCGR3 = 0xFFFFFFFF;
    CCM->CCGR4 = 0xFFFFFFFF;
    CCM->CCGR5 = 0xFFFFFFFF;
    CCM->CCGR6 = 0xFFFFFFFF;
}

void led_init(void)
{
    IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0);
    IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0x10B0);
    GPIO1->GDIR |= (1 << 3);
}

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

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

void led_flicker(void)
{
    GPIO1->DR ^= (1 << 3);
}

使用SDK后,代码可读性和可维护性大幅提升。


六、蜂鸣器驱动编写

蜂鸣器电路使用S8550 PNP型三极管 作为开关,基极低电平导通

c 复制代码
/* beep.c */
void beep_init(void)
{
    IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0); /* 根据实际引脚修改 */
    IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0x10B0);
    GPIO1->GDIR |= (1 << 3);
    beep_off();
}

void beep_on(void)
{
    GPIO1->DR &= ~(1 << 3);  /* PNP:低电平导通 */
}

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

七、BSP工程管理

随着外设增多,代码需要模块化管理。推荐如下目录结构:

复制代码
project/          存放必要程序
    main.c
    start.S
imx6ull/          存放NXP提供的i.MX6ULL头文件
    cc.h
    core_ca7.h
    fsl_common.h
    fsl_iomuxc.h
    MCIMX6Y2.h
bsp/              存放硬件外设相关功能模块
    led.c  led.h
    beep.c beep.h
Makefile          需要遍历目录

Makefile需要支持多目录编译,可通过wildcardVPATH等实现。


八、链接脚本

链接脚本imx6ull.lds在链接阶段为连接器提供"蓝图"。关键点:启动代码必须在进入C语言第一条指令前,将.bss.COMMON段清零

ld 复制代码
SECTIONS
{
    . = 0x87800000;
    .text :
    {
        obj/start.o
        *(.text)
    }
    .rodata ALIGN(4) : {*(.rodata*)}
    .data ALIGN(4) : {*(.data)}

    __bss_start = .;
    .bss ALIGN(4) : {*(.bss) *(.COMMON)}
    __bss_end = .;
}

对应在start.S中需要添加清零.bss段的代码:

asm 复制代码
ldr r0, =__bss_start
ldr r1, =__bss_end
mov r2, #0
bss_loop:
    cmp r0, r1
    strlt r2, [r0], #4
    blt bss_loop

九、总结

本文从汇编点灯进阶到C语言驱动,核心要点:

知识点 说明
volatile 防止编译器优化寄存器访问
寄存器定义 宏定义或结构体封装
SDK头文件 只用头文件,不用完整IDE
BSP管理 按project/imx6ull/bsp分层
链接脚本 指定入口、段布局、bss清零

下一步可以尝试:

  • 按键输入驱动
  • 中断控制器配置
  • 时钟树详细配置

如果本文对你有帮助,欢迎点赞、收藏、评论三连!有任何问题也可以在评论区交流。


参考

  • NXP i.MX6ULL参考手册
  • 正点原子/野火i.MX6ULL裸机教程
相关推荐
sunoo-2291 小时前
【ARM嵌入式学习笔记Day5】一文打通GPIO中断全链路:从硬件引脚→GIC→汇编入口→C语言处理
arm开发·笔记·学习·中断·gic·裸机开发
祖力551 小时前
ARM中断(IMX6ULL中断、GIC通用中断控制器)
嵌入式硬件·arm·imx6ull·中断·gic·通用中断控制器
傲世仙尊1 小时前
预处理详解-define宏井号条件编译一篇过
linux·c语言
老王爱玩车2 小时前
第6讲:数组和函数实践-----控制台扫雷
c语言·开发语言·学习
是隼人2 小时前
buuctf-pwn [NewStarCTF 2023 公开赛道]orw&rop(shellcode+orw)题解(学习过程持续更新)
c语言·学习·安全·pwn入门·ctf入门
傲世仙尊2 小时前
从test.c到test.exe-编译和链接的全过程
c语言
PiaoKe___4 小时前
云手机原理与 Python 自动化实战:ADB 批量控制、任务调度与落地建议
服务器·arm开发·python·自动化
aramae11 小时前
模拟实现strcmp()(C语言)
c语言·开发语言·后端
AOI小白新手上路12 小时前
韦东山 01_ARM架构与编程_2-1_处理器的区分MCU_MPU_AP_内容总结
嵌入式硬件