参考文章:https://doc.embedfire.com/linux/imx6/driver/zh/latest/bare_metal/gcc_led.html LED灯源码程序包含两部分内容,第一部分在汇编文件中设置“栈地址”并执行跳转命令跳转到main函数执行C代码。 第二部分在C文件中使led灯不断闪烁。 将“汇编点亮led灯的源码led.S复制到”更名为start.S。 在start.S的基础上增加“栈”设置和执行跳转指令,如下所示。 C语言版LED bringup代码:
/***********************第一部分*********************/ .text //代码段 .align 2 //设置字节对齐 .global _start //定义全局变量 _start: //程序的开始 b reset //跳转到reset标号处 reset: bic r0, r0, #0x1f /* 将 r0 的低 5 位清零,也就是 cpsr 的 M0~M4 */ orr r0, r0, #0x13 /* r0 或上 0x13,表示使用 SVC 模式 */ msr cpsr, r0 /* 将 r0 的数据写入到 cpsr_c 中 */ /***********************第二部分*********************/ ldr sp, =0x84000000 //设置栈地址64M b main //跳转到main函数 /***********************第三部分*******************/ /*跳转到light_led函数*/ // bl light_led /*进入死循环*/ loop: b loop
c语言代码:
// 时钟控制寄存器 #define CCM_CCGR1 (volatile unsigned long*)0x20C406C //GPIO1_04复用功能选择寄存器 #define IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04 (volatile unsigned long*)0x20E006C //PAD属性设置寄存器 #define IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04 (volatile unsigned long*)0x20E02F8 //GPIO方向设置寄存器 #define GPIO1_GDIR (volatile unsigned long*)0x0209C004 //GPIO输出状态寄存器 #define GPIO1_DR (volatile unsigned long*)0x0209C000 #define uint32_t unsigned int /*简单延时函数*/ void delay(uint32_t count) { volatile uint32_t i = 0; for (i = 0; i < count; ++i) { __asm("NOP"); /* 调用nop空指令 */ } } int main() { *(CCM_CCGR1) = 0xFFFFFFFF; //开启GPIO1的时钟 *(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04) = 0x5; //设置PAD复用功能为GPIO *(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04) = 0x1F838; //设置PAD属性 *(GPIO1_GDIR) = 0x10; //设置GPIO为输出模式 *(GPIO1_DR) = 0x0; //设置输出电平为低电平 while(1) // 不断改变RGB红灯所在引脚的高低电平 { *(GPIO1_DR) = 0x0; delay(0xFFFFF); *(GPIO1_DR) = 1<<4; delay(0xFFFFF); } return 0; }
编写链接脚本: 写好的代码(无论是汇编还是C语言)都要经过编译、汇编、链接等步骤生成二进 制文件或者可供下载的文件。在编译阶编译器会对每个源文件进行语法检查并生成对应的汇编语言, 汇编是将汇编文件转化为机器码。在上一章我们使用”arm-none-eabi-gcc -g -c led.S -o led.o”命令 完成了源码的编译、汇编工作,生成了.o文件。编译和汇编是针对单个源文件, 也就编译完成后一个源文件(.c,.S或.s)对应一个.o文件。程序链接阶段就会将这些.o链接成一个文件。 链接脚本的作用就是告诉编译器怎么链接这些文件,比如那个文件放在最前面,程序的代码段、数据段、bss段分别放在什么位置等等。 链接脚本可参考:https://www.cnblogs.com/lethe1203/p/18077801 c语言bringup led的链接脚本如下:
ENTRY(_start) SECTIONS { . = 0x87800000; . = ALIGN(4); .text : { start.o (.text) *(.text) } . = ALIGN(4); .data : { *(.data) } . = ALIGN(4); .bss : { *(.bss) } }
- 第1行,ENTRY(_start) 用于指定程序的入口,ENTRY( )是设置入口地址的命令, “_start”是程序的入口,本章的led程序的入口地址位于start.S的“_start”标号处。
- 第2行,定义SECTIONS。SECTIONS可以理解为是一块区域,我们在这块区域排布我们的代码, 链接时链接器就会按照这里的指示链接我们的代码。
- 第3行,“.”运算符代表当前位置。 我们在SECTION的最开始使用“.= 0x87800000”就是将链接起始地址设置为0x87800000。
- 第5行,设置字节对齐。这里同样用到了“.”运算符,它表示从当前位置开始执行四字节对齐。
- 第6行,定义代码段。“.text :”用于定义代码段,固定的语法要求,我们按照要求写即可。 在“{}”中指定那些内容放在代码段。
- 第8-9行,将start.o中的代码放到代码段的最前面。start.S是启动代码应当首先被执行, 所以通常情况下要把它放到代码段的最前面,其他源文件的代码按照系统默认的排放顺序即可, 通配符“*”在这里表示其他剩余所有的.o文件。
- 第12-16行,设置数据段。同设置代码段类似,首先设置字节对齐,然后定义代码段。在代码段里使用“*”通配符, 将所有源文件中的代码添加到这个代码段中。
- 第18-22行,设置BSS段。设置方法与设置数据段完全相同