目錄
一、匯編點燈轉 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 申請的內存),需手動管理 |