C 语言实现 I.MX6ULL 点灯(续上一篇)、SDK、deep及bsp工程管理
目录
一、汇编点灯转 C 语言实现
1. 关键字:volatile
2. 寄存器地址定义(两种方式)
(1)直接宏定义地址
(2)结构体封装寄存器(优化访问)
3. 核心功能代码
(1)时钟初始化:打开所有时钟门
(2)LED 初始化:引脚复用 + GPIO 方向配置
(3)LED 控制:亮、灭、闪烁
二、SDK 库文件
1. SDK文件选择
2. 基于 SDK 的点灯程序优化
三、BSP 工程管理与构建
1. 工程目录结构(模块化管理)
2. 蜂鸣器裸机驱动(S8550 PNP 三极管)
3. Makefile 优化(多目录编译)
4. 链接脚本(imx6ull.lds)
1.链接脚本的作用
2.各段及存储数据类型
一、汇编点灯转 C 语言实现
1. 关键字:volatile
- 作用:告诉编译器,被修饰的变量值可能会被程序之外的因素(如硬件寄存器、中断服务函数)意外修改,禁止编译器对该变量进行优化
在访问硬件寄存器时必须使用,确保每次对寄存器的读写都是直接操作物理地址,而非操作缓存值
2. 寄存器地址定义(两种方式)
(1)直接宏定义地址
通过#define
将寄存器物理地址强制转换为对应类型的指针,直接访问寄存器:
(2)结构体封装寄存器(优化访问)
将同组寄存器(如 GPIO1 的 DR、GDIR、PSR 等)封装为结构体,通过结构体指针映射到基地址:
3. 核心功能代码
(1)时钟初始化:打开所有时钟门
I.MX6ULL 外设默认时钟关闭,需通过 CCM 寄存器开启对应外设时钟,此处为简化操作,直接打开所有时钟门:
(2)LED 初始化:引脚复用 + GPIO 方向配置
- 引脚复用:将
GPIO1_IO03
配置为 GPIO 功能(复用值 0x05) - 电气属性:配置引脚驱动能力、上下拉等(0x10B0 为常用配置)
- GPIO 方向:将
GPIO1_IO03
设为输出模式(GDIR 寄存器对应位写 1)
void led_init(void)
{// 方式1:直接操作寄存器(无SDK)IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = 0x05; // 引脚复用为GPIOIOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 = 0x10B0; // 配置引脚电气属性GPIO1_GDIR |= (1 << 3); // GPIO1_IO03设为输出(第3位写1)// 方式2:结构体访问(优化后)// IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = 0x05;// IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 = 0x10B0;// GPIO1->GDIR |= (1 << 3);
}
(3)LED 控制:亮、灭、闪烁
通过操作 GPIO1 的 DR 寄存器(数据寄存器)控制引脚电平:
- LED灯亮:对应位写 0(拉低电平,假设 LED 为共阳极)
- LED灯灭:对应位写 1(拉高电平)
- LED灯闪烁:对应位异或(电平翻转)
// LED点亮
void led_on(void)
{GPIO1->DR &= ~(1 << 3); // 第3位清0(拉低电平)
}// LED熄灭
void led_off(void)
{GPIO1->DR |= (1 << 3); // 第3位置1(拉高电平)
}// LED闪烁(电平翻转)
void led_flicker(void)
{GPIO1->DR ^= (1 << 3); // 第3位异或(0变1,1变0)
}// 延时函数(软件延时,时间与参数time相关)
void led_delay(unsigned int time)
{while (time--);
}
二、SDK 库文件
1. SDK文件选择
- SDK(Software Development Kit):NXP 提供的 I.MX6ULL 开发工具包,包含完整 IDE(需额外设备:下载器、仿真器)和底层驱动头文件;
- 移植核心:仅使用 SDK 中的头文件(无需完整 IDE),路径为
IMAX6ULL/SDK/
,关键头文件包括:cc.h
:时钟相关定义;core_ca7.h
:ARM Cortex-A7 内核相关定义;fsl_common.h
:通用工具函数定义;fsl_iomuxc.h
:引脚复用配置函数定义;MCIMX6Y2.h
:I.MX6ULL 寄存器映射结构体定义。
2. 基于 SDK 的点灯程序优化
SDK 提供了封装好的引脚配置函数(如IOMUXC_SetPinMux
、IOMUXC_SetPinConfig
),简化寄存器操作:
// 时钟初始化(SDK中CCM已封装为结构体,可通过CCM->CCGRx访问)
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;
}// LED初始化(有SDK)
void led_init(void)
{// 1. 引脚复用:将GPIO1_IO03配置为GPIO功能,第2个参数为ALT引脚(0表示无ALT)IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0);// 2. 引脚电气属性配置:驱动能力、上下拉等(0x10B0为标准配置)IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0x10B0);// 3. GPIO方向配置:设为输出GPIO1->GDIR |= (1 << 3);
}
注意:
时钟使能:I.MX6ULL外设时钟默认关闭,初始化外设前必须开启对应时钟门;
三、BSP 工程管理与构建
1. 工程目录结构(模块化管理)
将代码按功能拆分到不同目录,提高可维护性,目录结构如下:
2. 蜂鸣器实现(S8550 PNP 三极管)
- 原理:S8550 为 PNP 型三极管,基极高电平时导通,控制蜂鸣器发声;
- 核心代码:与 LED 驱动逻辑类似,仅需修改对应引脚:
//beep.h#ifndef _BEEP_H_
#define _BEEP_H_extern void beep_init(void);
extern void beep_on(void);
extern void beep_off(void);
extern void beep_nor(void);#endif//beep.c#include "beep.h"
#include "fsl_iomuxc.h"
#include "MCIMX6Y2.h"void beep_init(void)
{IOMUXC_SetPinMux(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01 , 0); //复用功能IOMUXC_SetPinConfig(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01 , 0x10B0); //电气特性 GPIO5->GDIR |= (1 << 1); //引脚方向配置beep_off();
}void beep_on(void) //蜂鸣器发声
{GPIO5->DR &= ~(1 << 1); //置0
}void beep_off(void) //蜂鸣器停止发声
{GPIO5->DR |= (1 << 1); //置1
}void beep_nor(void) //蜂鸣器交替发声
{GPIO5->DR ^= (1 << 1);
}//main.c 实现LED灯和蜂鸣器的共同操作#include "fsl_iomuxc.h"
#include "MCIMX6Y2.h"
#include "led.h"
#include "beep.h"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_delay(unsigned int t)
{while(t--);
}int main(void)
{clock_init();led_init();beep_init();while (1){led_nor();beep_nor();led_delay(0x7FFFF);}
}
3. Makefile 优化(多目录编译)
支持多目录(project、bsp/led、bsp/beep)编译,自动生成目标文件(obj 目录),并通过链接脚本生成 bin、elf、dis 文件:
target = led// 定义交叉编译器前缀
cross_compiler = arm-linux-gnueabihf-// 定义编译工具
cc = $(cross_compiler)gcc // 编译器
ld = $(cross_compiler)ld // 链接器
objcopy = $(cross_compiler)objcopy // 格式转换工具
objdump = $(cross_compiler)objdump // 反汇编工具// 头文件和源文件目录
incdirs = bsp imx6ull // 头文件搜索目录
srcdirs = bsp project // 源文件搜索目录// 生成头文件包含参数
include = $(patsubst %, -I%, $(incdirs))// 查找所有C和汇编源文件
cfiles = $(foreach dir, $(srcdirs), $(wildcard $(dir)/*.c))
sfiles = $(foreach dir, $(srcdirs), $(wildcard $(dir)/*.S))// 处理文件名(去路径)
cfilenodir = $(notdir $(cfiles))
sfilenodir = $(notdir $(sfiles))// 生成目标文件路径
cobjs = $(patsubst %, obj/%, $(cfilenodir:.c=.o)) // C文件对应的.o
sobjs = $(patsubst %, obj/%, $(sfilenodir:.S=.o)) // 汇编文件对应的.o
objs = $(cobjs) $(sobjs) // 所有目标文件// 源文件搜索路径
VPATH = $(srcdirs)// 生成bin文件(依赖所有目标文件)
$(target).bin : $(objs)$(ld) -Timx6ull.lds -o$(target).elf $^ // 链接生成elf$(objcopy) -O binary -S -g $(target).elf $@ // 转换为bin$(objdump) -D $(target).elf > $(target).dis // 生成反汇编// 汇编文件编译规则
$(sobjs) : obj/%.o : %.S@mkdir -p obj$(cc) -Wall -nostdlib -c $(include) -o $@ $<// C文件编译规则
$(cobjs) : obj/%.o : %.c@mkdir -p obj $(cc) -Wall -nostdlib -c $(include) -o $@ $<// 清理编译产物
.PHONY : clean
clean:rm -rf $(objs) $(target).elf $(target).bin $(target).dis// 下载程序到SD卡
load:../imxdownload $(target).bin /dev/sdb
总结:
工具(前缀为 arm-linux-gnueabihf-
)作用 gcc
(编译器)将源代码(.c/.S)编译为目标文件(.o):预处理→编译→汇编,生成机器码 ld
(链接器)按链接脚本,将多个目标文件(.o)合并为可执行文件(.elf) objcopy
(目标文件拷贝工具)将.elf 文件转换为二进制文件(.bin),便于烧写至开发板 Flash/RAM objdump
(目标文件反汇编工具)对.elf 文件反汇编,生成.dis 文件,用于调试(查看指令与地址对应关系) 辅助工具(如 imxdownload
)专用烧写工具,将.bin 文件烧写到开发板存储设备(如 SD 卡 /dev/sdb)
4. 链接脚本(imx6ull.lds)
指定代码加载地址(I.MX6ULL 常用 0x87800000),定义各段(text、rodata、data、bss)的排列顺序,并标记 BSS 段起始 / 结束地址(用于启动文件初始化 BSS 段)
SECTIONS
{. = 0x87800000; // 代码加载基地址(I.MX6ULL DDR起始地址)// 代码段(text):存放启动文件、主程序代码.text :{obj/start.o // 启动文件优先加载(需先初始化栈、BSS段)*(._text) // 所有文件的text段} // 只读数据段(rodata):存放常量,4字节对齐.rodata ALIGN(4) : {*(.rodata*)}// 已初始化数据段(data):存放初始化过的全局变量,4字节对齐.data ALIGN(4) : {*(.data)}// 未初始化数据段(bss):存放未初始化的全局变量,需在启动文件中清0__bss_start = .; // BSS段起始地址.bss ALIGN(4) : {*(.bss) *(COMMON)} // 所有bss段和COMMON段__bss_end = .; // BSS段结束地址
}
注意:
需要在启动代码中加入跳转链接
BSS 段初始化:启动文件(start.S)需在跳转到 main 前,将__bss_start到__bss_end的内存清0
1.链接脚本的作用
链接脚本(上文中的imx6ull.lds
)的核心作用是定义程序的内存布局,使链接器将多个目标文件(.o)合并为可执行文件(.elf) 的配置文件,核心是定义 “内存布局” 和 “段的排列规则”(不同段对应程序中不同类型的数据),确保代码和数据加载到芯片指定的内存地址(如上文中的-Ttext 0x87800000),达成硬件成功运行的要求
2.各段及存储数据类型
常见段名 | 存储数据类型 |
---|---|
.text | 代码段:存放可执行指令(如 C 函数、汇编指令),只读 |
.data | 数据段:存放已初始化且非零的全局变量 / 静态变量,可读可写 |
.bss | 未初始化数据段:存放未初始化或初始化为零的全局变量 / 静态变量,运行时由系统清零 |
.rodata | 只读数据段:存放只读常量(如字符串常量、const 修饰的全局变量) |
.stack | 栈区:用于函数调用时保存局部变量、函数参数、返回地址,由编译器自动管理 |
.heap | 堆区:用于动态内存分配(如malloc 申请的内存),需手动管理 |