ARM裸机开发:C语言点亮LED

举报
JeckXu666 发表于 2022/01/17 23:11:34 2022/01/17
【摘要】 文章目录 ARM裸机开发:C语言点亮LED一、硬件平台:二、汇编搭建C开发环境2.1 STM32启动代码2.2 I.MUX 启动代码 三、C语言驱动程序四、实验现象 ARM裸机开发...

ARM裸机开发:C语言点亮LED

一、硬件平台:

正点原子I.MX6U阿尔法开发板

_533488159_IMG_20210803_235508_1628006109000_xg_0

二、汇编搭建C开发环境

使用C语言进行软件开发,首先需要使用汇编搭建C语言运行环境用汇编来初始化一下 C 语言环境,比如初始化 DDR、 设置堆栈指针 SP 等等,当这些工作都做完以后就可以进入 C 语言环境,也就是运行 C 语言代 码,一般都是进入 main 函数。所以我们有两部分文件要做:

  • 汇编文件:汇编文件只是用来完成 C 语言环境搭建

  • C 语言文件:C 语言文件就是完成我们的业务层代码的,其实就是我们实际例程要完成的功能

下面我们分析汇编文件的编写:

2.1 STM32启动代码

了解 I.MUX 汇编启动代码前,我们先看一下 STM32 的启动代码是如何编写的;在 STM32 中,启动文件 startup_stm32f10x_hd.s 就是完成 C 语言环境搭建的,代码主要分为三个部分:

首先设置堆和栈的大小

Stack_Size      EQU     0x00000400

                AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp
                                                  
; <h> Heap Configuration
;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Heap_Size       EQU     0x00000200

                AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE   Heap_Size
__heap_limit

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

然后初始化中断向量表

; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY
                EXPORT  __Vectors
                EXPORT  __Vectors_End
                EXPORT  __Vectors_Size

__Vectors       DCD     __initial_sp               ; Top of Stack
                DCD     Reset_Handler              ; Reset Handler
                DCD     NMI_Handler                ; NMI Handler
                DCD     HardFault_Handler          ; Hard Fault Handler
                DCD     MemManage_Handler          ; MPU Fault Handler
                DCD     BusFault_Handler           ; Bus Fault Handler
                DCD     UsageFault_Handler         ; Usage Fault Handler
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     SVC_Handler                ; SVCall Handler
                DCD     DebugMon_Handler           ; Debug Monitor Handler
                DCD     0                          ; Reserved
                DCD     PendSV_Handler
                ;...省略

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

中断向量表初始化完成后调用复位中断,先声明然后调用时钟初始化,

; Reset handler
Reset_Handler   PROC
                EXPORT  Reset_Handler             [WEAK]
                IMPORT  __main
				;寄存器版本代码,因为没有用到SystemInit函数,所以注释掉以下代码为防止报错!
				;库函数版本代码,建议加上这里(外部必须实现SystemInit函数),以初始化stm32时钟等。
                IMPORT  SystemInit			
                LDR     R0, =SystemInit	
                BLX     R0                  
                LDR     R0, =__main
                BX      R0
                ENDP

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

时钟初始化函数使用C语言进行编写,配置STM外设时钟

20211024174224

时钟初始化完成后,调用 编译器生成的 __main 函数进行运行C语言 main 函数前的一些初始化__main() 中可以看出有两个大的函数:

__scatterload():负责把RW/RO输出段从装载域地址复制到运行域地址,并完成了ZI运行域的初始化工作。

__rt_entry(): 负责初始化堆栈,完成库函数的初始化,最后自动跳转向main()函数。

其主要功能为:

  • 完成全局/静态变量的初始化工作
  • 初始化堆栈
  • 库函数的初始化
  • 程序的跳转,进入main()函数

以及编写一些异常处理中断函数

; Dummy Exception Handlers (infinite loops which can be modified)

NMI_Handler     PROC
                EXPORT  NMI_Handler                [WEAK]
                B       .
                ENDP
HardFault_Handler\
                PROC
                EXPORT  HardFault_Handler          [WEAK]
                B       .
                ENDP
MemManage_Handler\
                PROC
                EXPORT  MemManage_Handler          [WEAK]
                B       .
                ENDP
BusFault_Handler\
                PROC
                EXPORT  BusFault_Handler           [WEAK]
                B       .
                ENDP
                ;...省略

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

以上基本就是 STM32 启动代码的执行流程

2.2 I.MUX 启动代码

I.MUX 启动代码与 STM32 相似,我们需要初始化堆栈准备C语言运行环境,本节暂时不需要初始化堆栈向量表,编写启动汇编代码如下:

ARM 汇编指令参考这篇文章:ARM 汇编基础

@编写全局标号
.global _start
_start:

	@设置CPSR寄存器使CPU进入SVC模式
	mrs r0,   cpsr
	bic r0,   r0, #0x1f
	orr r0,   r0, #0x13
	msr cpsr, r0
	@设置堆栈指针
	ldr sp, =0x80200000
	@跳转到main函数
	b main @

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

注意:设置 SVC 模式下的 SP 指针=0X80200000,因为 I.MX6U-ALPHA 开发板上的 DDR3 地址范围是0X80000000~0XA0000000(512MB) 或 者 0X80000000~0X90000000(256MB),不管是 512MB 版本还是 256MB 版本的,其 DDR3 起始地址都是 0X80000000。由于 Cortex-A7 的堆栈是向下增长的,所以将 SP 指针设置为 0X80200000,因此 SVC 模式的栈大小 0X80200000-0X80000000=0X200000=2MB

一般在堆栈 SP 指针直接指向 DDR 的地址之前是需要初始化 DDR 的,但是 IMUX Boot Rom 在一开始就已经读取 DCD 的配置参数进行初始化了,所以这里我们不用再初始化

以上就是汇编的初始化代码了,在汇编初始化完成后,下一步就是跳转到 main 函数运行 c语言的代码

三、C语言驱动程序

C语言驱动程序就是直接使用C语言来对 IMUX 的底层寄存器进行操作,关于使用的寄存器地址,可以参考我之前的文章整理:汇编驱动LED实验,我们将用到的寄存器进行封装,用宏定义替换,此处我直接使用正点原子的宏定义头文件:

20211027224418

先看一下开发板上LED的接口,GPIO1的3脚

20211028112915

然后我们编写 main.c 文件代码

先编写时钟使能代码

void CLK_ENA()

{

	CCM_CCGR0 = 0xffffffff;

	CCM_CCGR1 = 0xffffffff;

	CCM_CCGR2 = 0xffffffff;

	CCM_CCGR3 = 0xffffffff;

	CCM_CCGR4 = 0xffffffff;

	CCM_CCGR5 = 0xffffffff;

	CCM_CCGR6 = 0xffffffff;

}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

再编写 LED IO 口初始化代码

void led_init()
{
	//设置寄存器 SW_MUX_GPIO1_IO03_BASE 的 MODE 为5
	SW_MUX_GPIO1_IO03 = 0x5;
    //模式配置
    //bit 16:0 HYS关闭
    //bit [15:14]: 00 默认下拉
    //bit [13]: 0 kepper功能
    //bit [12]: 1 pull/keeper使能
    //bit [11]: 0 关闭开路输出
    //bit [7:6]: 10 速度100Mhz
    //bit [5:3]: 110 R0/6驱动能力
    //bit [0]: 0 低转换率
	SW_PAD_GPIO1_IO03 = 0x10b0;
	//设置GPIO为输出
	GPIO1_GDIR = 0X0000008;
    //初始化输出为0
	GPIO1_DR = 0x0;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

在开头添加一个宏定义用于控制 GPIO1的3脚电平,设置电平使用: 或置位,与清零

#define LED_ON() (GPIO1_DR &= ~(1<<3))
#define LED_OFF() (GPIO1_DR |= (1<<3))

  
 
  • 1
  • 2

编写延时函数

void delay(volatile unsigned int n)
{
	while(n--)
	{
		volatile unsigned int i = 0x7ff;
		while(i--);
	}
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

编写主函数,初始化外设后,延时点亮LED灯

int main(void)

{
	CLK_ENA();
	led_init();		

	while(1)		

	{	
		LED_OFF();
		delay(1000);	
		LED_ON();		
		delay(1000);		
	}
	return 0;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

代码编写完成,需要编写编译链接 Makefile 脚本

# 定义目标变量
objs := start.o main.o
# 生成bin文件
ledc.bin: $(objs)
	# 依次读取第一个依赖文件进行链接
	arm-linux-gnueabihf-ld -Timx6ul.lds -o ledc.elf $^
	# 链接文件转二进制
	arm-linux-gnueabihf-objcopy -O binary -S ledc.elf $@
	# 链接文件生成反汇编文件
	arm-linux-gnueabihf-objdump -D -m arm ledc.elf > ledc.dis
#生成编译文件
%.o:%.s
	arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<
%.o:%.S
	arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<
%.o:%.c
	arm-linux-gnueabihf-gcc -Wall -nostdlib -c -O2 -o $@ $<、
#清除编译文件
clean:
	rm -rf *.o ledc.bin ledc.elf ledc.dis

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

$ 表示执行一个 Makefile 函数, $@ 依次取出目标文件用于执行,$< 依次取出依赖文件用于执行

% 表示变量成员通配符

在上面代码进行链接的时候,使用到了imux6ul.lds 链接文件,使链接器按照其规则进行链接,我们一般编译出来的代码 都包含在 text、data、bss 和 rodata 这四个段内,链接规则就是定义如何链接代码具体的位置

链接规则如下

# 关键字
SECTIONS{
	# “.”在链接脚本里面叫做定位计数器,默认的定位计数器为 0,此处我们定义起始地址为 0X87800000
	. = 0X87800000;
    # “.text”是代码段名,后面的冒号是语法要求
	.text :
	{
		start.o 
		main.o 
		*(.text)
        # “*(.text)”中的“*”是通配符,表示所有输入文件的.text段都放到“.text”
	}
	# 只读数据段 (4字节对齐)
	.rodata ALIGN(4) : {*(.rodata*)}     
	# 数据段 (4字节对齐)
	.data ALIGN(4)   : { *(.data) }   
    
    # .bss 段是定义了但是没有被初始化的变量,我们需要手动
    # 对.bss 段的变量清零的,因此我们需要知道 .bss 段的	   
    # 起始和结束地址
	__bss_start = .;    
	.bss ALIGN(4)  : { *(.bss)  *(COMMON) }    
	__bss_end = .;
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

关于各段的区别,可以参考我之前的文章 C语言:内存四区

到这代码基本编写完成了,下面我们进行编译

20211028125407

编译成功后,下载到SD卡上

20211028125827

详细下载细节可参考我之前的文章:ARM裸机开发:I.MX6UL 程序编译下载(SD卡)

四、实验现象

将SD卡插到开发板上启动,可以看到 LED 在周期性闪烁,这里就不插图了

文章来源: blog.csdn.net,作者:JeckXu666,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/qq_45396672/article/details/121012233

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。