1.led进阶
1.volatile关键字
C 语言编译器会对代码进行优化,若某变量未被显式修改,可能会被编译器认为 “值不变” 并缓存到寄存器中。此时必须用volatile关键字,可以避免编译器忽略的部分语句
2.定义寄存器地址
1.直接定义单个寄存器
//GPIO相关寄存器
#define GPIO1_DR *((volatile unsigned int *)0x0209C000) // 数据寄存器:控制引脚输出高低电平
#define GPIO1_GDIR *((volatile unsigned int *)0x0209C004) // 方向寄存器:配置引脚为输出
// 引脚复用与电气属性寄存器(GPIO1_IO03引脚)
#define IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 *((volatile unsigned int*)0x020E0068) // 引脚功能复用:选择GPIO功能
#define IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 *((volatile unsigned int*)0x020E02F4) // 引脚电气属性
// 时钟使能寄存器(CCM:时钟控制模块)
#define CCM_CCGR0 *((volatile unsigned int *)0x020C4068)
#define CCM_CCGR1 *((volatile unsigned int *)0x020C406C)
#define CCM_CCGR2 *((volatile unsigned int *)0x020C4070)
#define CCM_CCGR3 *((volatile unsigned int *)0x020C4074)
#define CCM_CCGR4 *((volatile unsigned int *)0x020C4078)
#define CCM_CCGR5 *((volatile unsigned int *)0x020C407C)
#define CCM_CCGR6 *((volatile unsigned int *)0x020C4080)
2.结构体封装:优化寄存器访问
// 定义GPIO外设寄存器结构体
struct GPIO_t
{
unsigned int DR; // 0x00:数据寄存器
unsigned int GDIR; // 0x04:方向寄存器
unsigned int PSR; // 0x08:状态寄存器
unsigned int ICR1; // 0x0C:
unsigned int ICR2; // 0x10:
unsigned int IMR; // 0x14:
unsigned int ISR; // 0x18:
unsigned int EDGE_SEL;
#define GPIO1 (*((volatile struct GPIO_t *)0x0209C000)) // 后续访问更直观:GPIO1.DR = ...; GPIO1.GDIR |= ...;且更简洁,可以直接对整组进行宏定义
3.初始化及led相关函数
1.时钟初始化:打开外设时钟
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;
}
2. LED 初始化
void led_init(void)
{
// 引脚复用
IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = 0x05;
// 电气属性配置
IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 = 0x10B0;
//GPIO方向:将第3位(对应IO03)设为1,配置为输出模式
GPIO1.GDIR |= (1 << 3);
}
3. LED 控制与延时函数
// LED点亮:DR寄存器第3位清0(
void led_on(void)
{
GPIO1.DR &= ~(1 << 3);
}
// LED熄灭:DR寄存器第3位置1
void led_off(void)
{
GPIO1.DR |= (1 << 3);
}
// LED闪烁:DR寄存器第3位翻转(异或操作:相同为0,不同为1)
void led_flicker(void)
{
GPIO1.DR ^= (1 << 3);
}
// 简单延时函数
void led_delay(unsigned int time)
{
while (time--);
}
4.主函数
int main(void)
{
clock_init(); // 第一步:使能时钟
led_init(); // 第二步:初始化LED
while (1) // 死循环:持续闪烁
{
led_flicker(); // 翻转LED状态
led_delay(0xFFFFFFF); // 延时
}
}
4.makefile优化
COMPLITER = arm-linux-gnueabihf-
CC = $(COMPLITER)gcc # C语言编译器
LD = $(COMPLITER)ld # 链接器
OBJCOPY = $(COMPLITER)objcopy # 目标文件格式转换工具
OBJDUMP = $(COMPLITER)objdump # 反汇编工具
OBJS = start.o main.o # 需要编译的目标文件列表,start.o是汇编启动文件,main.o是C语言主程序
TARGET = led # 目标文件,后期可代替使用
# 汇编文件编译规则:将 .S 文件编译为 .o 目标文件
%.o : %.S
$(CC) -c $^ -o $@ -g # -c 表示只编译不链接,-g 表示生成调试信息
# C文件编译规则:将 .c 文件编译为 .o 目标文件
%.o : %.c
$(CC) -c $^ -o $@ -g
$(TARGET).bin : $(OBJS)
$(LD) -Ttext 0x87800000 $^ -o $(TARGET).elf # 链接生成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
2.SDK的使用
1.SDK 文件准备
核心文件
MCIMX6Y2.h
:I.MX6ULL 寄存器基地址与结构体定义(核心)。fsl_iomuxc.h
:引脚复用配置工具函数(如IOMUXC_SetPinMux
)。fsl_common.h
:通用宏定义与工具函数。core_ca7.h
:ARM Cortex-A7 内核相关定义
2. 基于 SDK 重构 LED 驱动
#include "led.h"
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
// 时钟初始化:使用SDK的CCM结构体
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. 引脚复用:IOMUXC_SetPinMux(引脚功能, 复用选项) IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0);
// 2. 电气属性:IOMUXC_SetPinConfig(引脚功能, 配置值) IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_GPIO1_IO03, 0x10B0);
// 3. GPIO方向:SDK的GPIO1结构体
GPIO1->GDIR |= (1 << 3);
}
// LED控制函数
void led_on(void)
{
GPIO1->DR &= ~(1 << 3);
}
void led_off(void)
{
GPIO1->DR |= (1 << 3);
}
void led_nor(void)
{
GPIO1->DR ^= (1 << 3);
}
3.蜂鸣器驱动
#include "beep.h"
#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"
void beep_init(void)
{
GPIO1_IO04 IOMUXC_SetPinMux(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01, 0); // 复用
IOMUXC_SetPinConfig(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01, 0x10B0); // 电气属性
GPIO1->GDIR |= (1 << 1); // 配置为输出
beep_off();
}
void beep_on(void)
{
GPIO5->DR &= ~(1 << 1); // 导通蜂鸣器
}
void beep_off(void)
{
GPIO5->DR |= (1 << 1); // 关闭蜂鸣器
}
void beep_nor(void)
{
GPIO5->DR ^= (1 << 1);//控制蜂鸣器发声
}
3.BSP 工程目录结构
1.bsp工程目录的划分
按 “功能模块化” 划分目录,便于后续扩展
led_sdk/ # 工程根目录
├── project/ # 核心代码(入口)
│ ├── main.c # 主函数
│ └── start.S # 汇编启动文件
├── imx6ull/ # SDK头文件
│ ├── MCIMX6Y2.h
│ ├── fsl_iomuxc.h
│ ├── fsl_common.h
│ └── core_ca7.h
├── bsp/
│ ├── led/ # LED驱动模块
│ │ ├── led.c
│ │ └── led.h
│ └── beep/ # 蜂鸣器驱动模块
│ ├── beep.c
│ └── beep.h
├── Makefile # 编译脚本
└── imx6ull.lds # 链接脚本
2.makefile工程配置
核心:
1.makefile核心代码
# 目标文件名(最终生成的二进制文件前缀)
target = led
# 交叉编译器前缀(指定为 ARM 架构编译器)
cross_compiler = arm-linux-gnueabihf-
# 定义编译工具链(使用交叉编译器)
cc = $(cross_compiler)gcc # C 编译器
ld = $(cross_compiler)ld # 链接器
objcopy = $(cross_compiler)objcopy # 目标文件格式转换工具
objdump = $(cross_compiler)objdump # 目标文件反汇编工具
# 头文件目录(bsp 和 imx6ull 文件夹)
incdirs = bsp imx6ull
# 源文件目录(bsp 和 project 文件夹)
srcdirs = bsp project
# 生成头文件包含参数(-I 选项指定)
include = $(patsubst %, -I%, $(incdirs))
# 查找所有源文件(.c 和 .S 汇编文件)
cfiles = $(foreach dir, $(srcdirs), $(wildcard $(dir)/*.c)) # 所有 C 文件
sfiles = $(foreach dir, $(srcdirs), $(wildcard $(dir)/*.S)) # 所有汇编文件
# 提取文件名(去掉路径)
cfilenodir = $(notdir $(cfiles))
sfilenodir = $(notdir $(sfiles))
# 定义目标文件路径(放在 obj 文件夹下)
cobjs = $(patsubst %, obj/%, $(cfilenodir:.c=.o)) # C 文件对应的 .o 文件
sobjs = $(patsubst %, obj/%, $(sfilenodir:.S=.o)) # 汇编文件对应的 .o 文件
objs = $(cobjs) $(sobjs) # 所有目标文件集合
# 指定源文件搜索路径(make 会在这些目录找源文件)
VPATH = $(srcdirs)
# 生成 led.bin(最终二进制文件):依赖所有目标文件
$(target).bin : $(objs)
$(ld) -Timx6ull.lds -o$(target).elf $^ # 用链接脚本 imx6ull.lds 链接生成 elf 文件
$(objcopy) -O binary -S -g $(target).elf $@ # 转换 elf 为二进制文件
$(objdump) -D $(target).elf > $(target).dis # 生成反汇编文件
# 编译汇编文件:将 .S 编译为 obj 目录下的 .o
$(sobjs) : obj/%.o : %.S
@mkdir -p obj # 确保 obj 目录存在(@ 表示不显示命令本身)
$(cc) -Wall -nostdlib -c $(include) -o $@ $< # -nostdlib 不链接标准库
# 编译 C 文件:将 .c 编译为 obj 目录下的 .o
$(cobjs) : obj/%.o : %.c
@mkdir -p obj
$(cc) -Wall -nostdlib -c $(include) -o $@ $< # -Wall 显示所有警告,-c 只编译不链接
# 伪目标:清除编译产物
.PHONY : clean
clean:
rm -rf $(objs) $(target).elf $(target).bin $(target).dis # 删除目标文件和输出文件
# 伪目标:下载程序到设备(使用 imxdownload 工具)
load:
./../imxdownload $(target).bin /dev/sdb # 将 bin 文件下载到 /dev/sdb 设备
2.链接脚本(imx6ull.lds)
SECTIONS
{
. = 0x87800000; # 代码加载地址(I.MX6ULL SD启动地址)
.text : { # 代码段:先放启动文件
obj/start.o
*(.text)
}
.rodata ALIGN(4) : {*(.rodata*)} # 只读数据段(4字节对齐)
.data ALIGN(4) : {*(.data)} # 已初始化数据段
__bss_start = .; # BSS段起始地址
.bss ALIGN(4) : {*(.bss) *(COMMON)} # 未初始化数据段
__bss_end = .; # BSS段结束地址
}
3.BSS 段初始化
未初始化全局变量存.bss段需清 0
_bss_init:
ldr r0, =__bss_start # r0 = BSS起始地址
ldr r1, =__bss_end # r1 = BSS结束地址
mov r2, #0 # r2 = 0(用于清0)
loop:
str r2, [r0] # 把0写入r0指向的内存
add r0, #4 # 地址+4(32位对齐)
cmp r0, r1 # 比较是否到末尾
blt loop # 没到就继续循环
bx lr # 返回,之后进main