硬件驱动——I.MX6ULL裸机启动(2)
一.GNU常用工具和说明
1.gcc:功能:编译器
作用:将C/C++等高级语言源代码转换为汇编代码,再将汇编代码转换为目标文件,支
持多种编程语言和目标平台,提供丰富分优化选项和编译控制选项
2.ld:功能:链接器
作用:将多个目标文件链接成可执行文件,解析和处理符号引用,进行地址分配和重定位,
处理库文件的链接
3.objcopy:功能:目标文件格式转换器
作用:在不同格式的目标文件之间进行转换,将ELF格式转换为纯二进制格式(.bin)
从目标文件中提取特定的段(sections),修改目标文件的内容,常用于嵌入
式开发中的固件生成
4.objdump:功能:目标文件分析器/反汇编器
作用:将机器码反汇编为汇编代码,查看目标文件的详情信息,分析程序的执行指令
显示各种文件格式信息
二.在C语言环境下进行LED操作
1.volatile关键字:
作用:告诉编译器该变量的值随时可能发生变化,禁止编译器进行优化
2.将C语言和ARM语言连接起来
代码:
.global _start_start:ldr pc, =_reset_handlerldr pc, =_undefine_handlerldr pc, =_svc_handlerldr pc, =_prefetch_abort_handlerldr pc, =_data_abort_handlerldr pc, =_reserved_handlerldr pc, =_irq_handlerldr pc, =_fiq_handler_undefine_handler:ldr pc, =_undefine_handler_svc_handler:ldr pc, =_svc_handler_prefetch_abort_handler:ldr pc, =_prefetch_abort_handler_data_abort_handler:ldr pc, =_data_abort_handler_reserved_handler:ldr pc, =_reserved_handler_irq_handler:ldr pc, =_irq_handler_fiq_handler:ldr pc, =_fiq_handler_reset_handler:mrs r0,cpsrbic r0, r0, #0x1Forr r0, r0, #0x12 //irp(10010)msr cpsr, r0ldr sp, =0x86000000mrs r0,cpsrbic r0, r0, #0x1Forr r0, r0, #0x1F //system(11111)msr cpsr, r0ldr sp, =0x84000000bl _bss_clear b main //跳转至main函数当中_bss_clear:ldr r0, =__bss_startldr r2, =__bss_end
loop:mov r1, #0str r1, [r0]add r0, r0, #4cmp r0, r2blt loopbx lrfinished:b finished
//定义所有引脚的地址
// #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)))// #define SW_MUX_CTL_PAD_GPIO1_IO03 (*((volatile unsigned int *)(0x020E0068)))
// #define SW_PAD_CTL_PAD_GPIO1_IO03 (*((volatile unsigned int *)(0x020E02F4)))
// #define GPIO1_DR (*((volatile unsigned int *)(0x0209C000)))
// #define GPIO1_GDOR (*((volatile unsigned int *)(0x0209C004)))// struct GPIO_Type_t
// {
// volatile unsigned int DR;
// volatile unsigned int GDIR;
// volatile unsigned int PSR;
// volatile unsigned int ICR1;
// volatile unsigned int ICR2;
// volatile unsigned int IMR;
// volatile unsigned int ISR;
// volatile unsigned int EDGE_SEL;
// };// #define GPIO1 ((struct GPIO_Type_t *)(0x0209C000))
// #define GPIO2 ((struct GPIO_Type_t *)(0x020AC000))#include "led.h"void enable_clocks(void) //将所有时钟都打开
{CCM->CCGR0 = 0xFFFFFFFF;CCM->CCGR1 = 0xFFFFFFFF;CCM->CCGR2 = 0xFFFFFFFF;CCM->CCGR3 = 0xFFFFFFFF;CCM->CCGR4 = 0xFFFFFFFF;CCM->CCGR5 = 0xFFFFFFFF;CCM->CCGR6 = 0xFFFFFFFF;
}void delay(unsigned int n)
{while(n--); //进行延时}
int main(void)
{enable_clocks();init_led();
// led_on();while(1){led_nor();delay(0xFFFFF);}return 0;
}
#include "led.h"void init_led(void)
{IOMUXC_SetPinMux(IOMUXC_GPIO1_IO03_GPIO1_IO03,0); //IO复用模块 配置GPIO3的功能IOMUXC_SetPinConfig(IOMUXC_GPIO1_IO03_GPIO1_IO03,0x10B0); //配置GPIO3的电气属性GPIO1->GDIR |= (1 << 3); //配置GPIO1的引脚为输出方式
}void led_on(void)
{GPIO1->DR &= ~(1 << 3); //配置GPIO1的引脚为低电平}void led_off(void)
{GPIO1->DR |= (1 << 3); //配置GPIO1的引脚为高电平}
void led_nor(void)
{GPIO1->DR ^= (1 << 3); //配置GPIO1的引脚在高低电平之间相互切换}
#ifndef __LED_H__
#define __LED_H__#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"extern void init_led();
extern void led_on();
extern void led_off();
extern void led_nor();
#endif
3.利用IMX6ULL自己的芯片的库函数
代码如上,后缀u是无符号的意思
三.内存段
1. .bss段
(1)用于存放未初始化或初始化为0的数据
(2) 在运行时会被自动清零
-典型例子:
static int bss_var; // 静态变量(全局或局部)
int explicit_var = 0; // 显式初始化为0的全局变量
2. COMMON段
(1) 用于存放未初始化的非静态全局变量
(2) 链接时才确定最终大小和位置
(3)允许多个目标文件定义同名符号
典型例子:
int common_var; // 未初始化的非静态全局变量
3. .data段
(1) 用于存放已初始化的全局变量和静态变量(非零值)
(2)需要在程序文件中保存实际的初始值
典型例子:
int global_var = 100; // 初始化为非零值
int global_array[] = {1,2,3}; // 初始化为非零数组
4. .rodata段(只读数据段)
(1)存放程序的只读数据
(2) 运行时受保护,不可修改
(3)包含的数据类型:
1. 字符串常量
2. const修饰的全局变量
3. 全局只读数组
4. switch跳转表
5. 浮点数常量
典型例子:
const int MAX_VALUE = 100; // const全局变量
char* str = "Hello World"; // 字符串常量
const int lookup[] = {1,2,3}; // 只读数组
5.各段的特点比较
| 段名 | 初始化 | 运行时可写 | 链接特性 |
|---------|--------|------------|----------|
| .bss | 自动清零 | 可写 | 编译时确定大小 |
| COMMON | 自动清零 | 可写 | 链接时确定大小 |
| .data | 需要初始值 | 可写 | 编译时确定大小 |
| .rodata | 需要初始值 | 只读 | 编译时确定大小 |
6. 在链接脚本中的定义
SECTIONS
{
.text : { ... } // 代码段
.rodata ALIGN(4) : {*(.rodata*)} // 只读数据段
.data ALIGN(4) : {*(.data)} // 已初始化数据段
.bss ALIGN(4) : {*(.bss) *(COMMON)} // 未初始化数据段
}
7.关于段名中的通配符说明
在链接脚本中,经常会看到类似`*(.rodata*)`这样的写法,这里包含两个`*`通配符,它们有不同的含义:
1. 第一个`*`:
- 表示收集所有输入文件中的相关段
- 如果不加这个`*`,就只会收集特定文件的段
2. 第二个`*`(如`rodata*`中的`*`):
- 用于匹配所有以`.rodata`开头的段名
- 实际编译时会产生多种rodata相关的段,例如:
- `.rodata`:基本的只读数据段
- `.rodata.str1.1`:长度为1字节对齐的字符串常量
- `.rodata.str1.4`:长度为4字节对齐的字符串常量
- `.rodata.cst8`:8字节常量(如double类型的常量)
- `.rodata.cst4`:4字节常量(如float类型的常量)
如果只写`*(.rodata)`而不是`*(.rodata*)`:
- 只会收集严格命名为`.rodata`的段
- 其他相关的只读数据段(如`.rodata.str1.1`等)会被遗漏
- 这些被遗漏的数据可能导致程序无法正常工作
这种通配符的使用是链接脚本中的常见做法,类似的还有:
- `*(.text*)`:收集所有代码相关的段
- `*(.data*)`:收集所有数据相关的段
8.链接脚本:
SECTIONS
{. = 0x87800000;.text :{obj/start.o*(.text)}.rodata ALIGN(4) : {*(.rodata*)} .data ALIGN(4) : {*(.data)}. = ALIGN(4) ;__bss_start = .;.bss ALIGN(4) :{*(.bss) *(COMMON)}__bss_end = .;
}
代码解释:
规定程序加载到内存的起始地址(0x87800000),确保与硬件的内存布局匹配;
保证启动代码(start.o)最先执行,这是嵌入式系统启动的必要条件;
合理安排代码、只读数据、初始化数据、未初始化数据的内存位置;
定义 BSS 段的起始和结束符号,供启动代码完成 “未初始化变量清零” 的工作。
四.bps工程管理
Makefile:
target = ledcross_compiler = arm-linux-gnueabihf-cc = $(cross_compiler)gcc
ld = $(cross_compiler)ld
objcopy = $(cross_compiler)objcopy
objdump = $(cross_compiler)objdumpincdirs = bsp imx6ull
srcdirs = bsp projectinclude = $(patsubst %, -I%, $(incdirs))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))
sobjs = $(patsubst %, obj/%, $(sfilenodir:.S=.o))objs = $(cobjs) $(sobjs)VPATH = $(srcdirs)$(target).bin : $(objs)$(ld) -Timx6ull.lds -o$(target).elf $^$(objcopy) -O binary -S -g $(target).elf $@$(objdump) -D $(target).elf > $(target).dis$(sobjs) : obj/%.o : %.S@mkdir -p obj$(cc) -Wall -nostdlib -c $(include) -o $@ $<$(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
五.蜂鸣器实验
源码:
#include "beep.h"
#include "led.h"void enable_clocks(void) //将所有时钟都打开
{CCM->CCGR0 = 0xFFFFFFFF;CCM->CCGR1 = 0xFFFFFFFF;CCM->CCGR2 = 0xFFFFFFFF;CCM->CCGR3 = 0xFFFFFFFF;CCM->CCGR4 = 0xFFFFFFFF;CCM->CCGR5 = 0xFFFFFFFF;CCM->CCGR6 = 0xFFFFFFFF;
}void delay(unsigned int n)
{while(n--); //进行延时}
int main(void)
{enable_clocks();init_beep();init_led();while(1){led_nor();beep_nor();delay(0xFFFFF);}return 0;
}
#include "beep.h"void init_beep(void)
{IOMUXC_SetPinMux(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01,0); //IO复用模块,选择相应的功能IOMUXC_SetPinConfig(IOMUXC_SNVS_SNVS_TAMPER1_GPIO5_IO01,0x10B0); //配置蜂鸣器相应的电气属性GPIO5->GDIR |= (1 << 1); //将GPIO5的引脚设置为输出模式
}void beep_on(void)
{GPIO5->DR &= ~(1 << 1); //将GPIO5设置为低电平
}void beep_off(void)
{GPIO5->DR |= (1 << 1); //将GPIO5设置为高电平}void beep_nor(void)
{GPIO5->DR ^= (1 << 1); //将GPIO5设置为高低电平切换}
#ifndef __DEEP_H__
#define __DEEP_H__#include "MCIMX6Y2.h"
#include "fsl_iomuxc.h"extern void init_beep(void);
extern void beep_on(void);
extern void beep_off(void);
extern void beep_nor(void);
#endif
start.S和Makefile同上述LED灯和Makefile