ARM知识点三和串口代码的编写流程

ARM的一些常见问题

  1. ARM 体系结构的主要特点是什么?
  • 精简指令集 (RISC):ARM 采用 RISC 结构,指令集较小且简单,执行效率高。相比于复杂指令集 (CISC),RISC 更强调每条指令的执行速度。
  • 低功耗设计:ARM 处理器通过简化指令集和减少每条指令的处理步骤,达到了低功耗的效果,适用于移动设备和嵌入式系统。
  • Thumb 和 Thumb-2 指令集:ARM 支持 16 位 Thumb 指令集,它能够提高代码密度,特别适合对内存有限的嵌入式系统。
  • 多核和对称多处理 (SMP):现代 ARM 处理器支持多核架构,并且多核处理器可以并行处理多个任务。
  • 向量浮点单元 (VFP) 和 NEON:ARM 处理器通常集成 VFP(向量浮点运算单元)和 NEON 技术来加速多媒体和信号处理任务。
  1. ARM 的中断处理机制是怎样的?
    ARM 的中断机制是通过中断向量表来处理的,不同的中断类型有不同的优先级。主要的中断处理流程如下:
  • 中断请求 (IRQ):常规外设或定时器产生的中断请求。
  • 快速中断 (FIQ):高优先级的中断请求,常用于紧急任务处理。FIQ 有独立的寄存器组来加快中断响应速度。
  • 中断向量表:中断向量表存储在一个固定的地址(如地址 0x00000000 或 0xFFFF0000),每种中断类型有一个固定的入口地址,处理器通过跳转到相应的中断向量表位置来响应中断。
  • 嵌套中断:在 ARM 架构中,通过管理中断屏蔽和优先级,可以支持嵌套中断,即在处理中断的过程中可以响应更高优先级的中断。
  1. ARM Cortex-M 系列的主要特点是什么?
    ARM Cortex-M 系列是专为嵌入式系统设计的,主要特点包括:
  • 基于 ARMv7-M 或 ARMv8-M 指令集。
  • 低功耗,非常适合电池供电的嵌入式设备。
  • 支持快速中断响应,通过 NVIC(嵌套向量中断控制器)实现高效的中断管理,最多支持 240 个中断通道。
  • 内置调试支持:如 DWT(数据观察点和跟踪)和 ITM(指令跟踪宏单元)。
  • 浮点运算单元 (FPU):Cortex-M4 和 Cortex-M7 支持硬件浮点运算,适合需要大量数值计算的应用。
  1. ARM 处理器的总线接口有哪些?
    ARM 处理器主要使用以下总线接口技术:
  • AMBA (Advanced Microcontroller Bus Architecture):AMBA 是 ARM 提出的总线协议,常用的总线类型包括:
  • AHB (Advanced High-performance Bus):主要用于高性能外围设备和高速数据传输。
  • APB (Advanced Peripheral Bus):用于低速外围设备,如 GPIO、串口等。
  • AXI (Advanced eXtensible Interface):用于高性能内存和外设之间的数据传输,支持并行和流水线操作。
  1. 如何在 ARM 体系结构中实现内存映射I/O?
    内存映射I/O 是 ARM 处理器中外设与内存地址空间共享的一种机制,具体步骤如下:
  • 内存映射:每个外设都分配有固定的内存地址,处理器可以通过读写这些内存地址来控制外设。
  • 地址分配:不同外设的地址范围通常在芯片手册中指定。例如,GPIO 的控制寄存器可能映射到一个特定的地址段。
  • 访问外设:通过普通的 ldr 和 str 指令,ARM 处理器可以访问这些内存映射的外设。
  1. ARM中的异常处理机制是什么?
    ARM 处理器的异常处理机制与中断类似,但处理的是软件错误或其他需要特殊处理的事件。常见的异常包括:
  • 复位异常 (Reset):设备复位时触发。
  • 未定义指令异常 (Undefined Instruction):执行未定义的指令时触发。
  • 数据预取异常 (Data Abort/Prefetch Abort):发生内存访问错误或非法地址访问时触发。
  • 软件中断 (SWI):用于用户态调用内核服务(类似于系统调用)。
  1. ARM 的虚拟内存管理是如何实现的?
    ARM 处理器支持虚拟内存管理,通过以下方式实现:
  • MMU (Memory Management Unit):用于将虚拟地址转换为物理地址,并提供访问控制。
  • 分页机制:ARM 使用分页来管理内存,将内存划分为固定大小的页,虚拟地址通过页表映射到物理地- 址。
  • TLB (Translation Lookaside Buffer):TLB 用于缓存最近使用的页表项,减少内存访问时的转换开销。

安装交叉编译工具

  1. 拷贝 gcc-4.6.4.tar.xz 到 Linux 环境,并解压
tar -xvf gcc-4.6.4.tar.xz
  1. 将整个 GCC 目录安装到系统
    • 方法:将 gcc-4.6.4/bin 目录添加到 PATH 环境变量
    • 修改 ~/.bashrc 文件,添加以下内容,在文件底部添加
    export PATH=$PATH:/home/lsf/source/gcc-4.6.4/bin
    
    • source ~/.bashrc 使修改立即生效
  2. 测试安装
    终端中输入 arm-none- 并使用 Tab 键自动补齐,检查工具是否可用
  3. 注意
    • 若是 64 位系统,可能需要安装 32 位支持库
sudo apt-get install lib32ncurses5 lib32z1

接口技术

  • CPU: 狭义上指的是 核, 广义上指的是 整个芯片
    • 狭义上的 CPU:指处理器的 核心 (core) 部分,负责执行指令、处理数据和控制计算过程。它包括了运算逻辑单元 (ALU)、寄存器、控制单元等,核心的任务是执行软件的指令集。
    • 广义上的 CPU:可以包括整个芯片,除了核心以外还包括缓存 (cache)、总线控制器、内存管理单元 (MMU) 等部件。它不仅仅是执行指令的逻辑单元,还包括一些周边的控制逻辑和接口。
  • SOC : system on chip 片上系统, 核+接口
    • SoC 可以看作是一个集成了 CPU、接口以及其他外设的单芯片系统,适合于嵌入式系统和移动设备应用
  • 裸机编程: 没有操作系统的编程,比如 单片机
    • STM32:基于 ARM Cortex-M 核心,广泛应用于工业控制和消费电子。
    • 51 单片机:经典的8位单片机,适合学习与小型应用。

裸机编程示例图

在这里插入图片描述
在这里插入图片描述
- 核心流程:
ldr 指令:CPU 使用 ldr(load register)指令,从存储器或外设的寄存器读取数据到寄存器。
str 指令:CPU 使用 str(store register)指令,将寄存器的数据写回存储器或外设的寄存器中。
- 硬件,分为两部分 核心板+外设板
- 核心板:能让CPU正常运行的最小系统,主要组件:CPU,flash,RAM,电源,串口,晶振
- 外设板: 外设板通常包含了许多外部设备,这些设备通过各种接口与核心板的 CPU 连接,用于扩展系统的功能。例如:扬声器、SD卡接口、ADC、HDMI
- 核心板和外设板通过接口(如 GPIO、I2C、SPI、UART 等)进行连接和通信。

操作硬件一般步骤(以 LED 为例)

  1. 从电路板上找到该硬件
    • 找到你想控制的硬件设备。例如:LED2
    • 记下该设备的标号(如 LED2),方便后续在电路图和芯片手册中查找相关信息
  2. 打开电路图,找到该硬件的连接信息
    • 打开硬件的 电路原理图,找到 LED2 相关的部分
    • 在电路图中查找 LED2 的位置,并查看该 LED 是如何连接到 CPU 的管脚上。例如,你可能会发现 LED2 连接到 CPU 的 GPX2_7 引脚
  3. 阅读芯片手册 (datasheet),找到相关控制寄存器
    • 打开对应的 芯片手册 (datasheet),找到涉及控制 GPX2_7 引脚的章节,通常是在 GPIO(通用输入输出)控制器章节中
    • GPX2_7 表示 GPIO X 组的第 7 个引脚,你需要找到该引脚的寄存器配置方式
    寄存器的相关配置:
    GPX2CON(引脚配置寄存器):配置该引脚的模式(输入/输出)。
    寄存器地址:0x11000C40。
    操作说明:设置 [31:28] 位为 0001,表示将 GPX2_7 设置为输出模式。
    GPX2DAT(数据寄存器):用于控制引脚的高低电平(控制开/关)。
    寄存器地址:0x11000C44。
    操作说明:[7] 位控制 GPX2_7 的输出状态。设置该位为 1 则为高电平(LED 点亮),设置为 0 则为低电平(LED 熄灭)。
    
    • 编写代码,进行编程和测试
      • 代码在start.s中写,通过Makefile文件编译,取bin文件烧录进去开发板即可
      • 在这里插入图片描述
.text
@@@ 通过 ARM 裸机编程直接控制硬件,具体任务是通过按键控制 LED 的亮灭,即当按键按下时点亮 LED,松开按键时熄灭 LED。
@@ 配置 GPX2_7 为输出模式
ldr r0,=0x11000c40        @ 加载 GPX2CON 地址
ldr r2,[r0]               @ 读取 GPX2CON 内容到 r2
bic r2,r2,#0xF0000000     @ 清除 GPX2_7 的控制位
orr r2,r2,#0x10000000     @ 设置 GPX2_7 为输出模式 (0001)
str r2,[r0]               @ 将修改后的值写回 GPX2CON

@@ 配置 K2 为输入模式 (GPX1_1)
ldr r0,=0x11000c20        @ 加载 GPX1CON 地址
ldr r2,[r0]               @ 读取 GPX1CON 内容到 r2
bic r2,r2,#0xF0           @ 清除 GPX1_1 的控制位
str r2,[r0]               @ 直接清除后默认是输入模式 (0000)

loop:
	ldr r0,=0x11000c24    @ 加载 GPX1DAT 地址 (检测按键输入)
	ldr r2,[r0]           @ 读取 GPX1DAT
	and r2,r2,#2          @ 检查 GPX1_1 状态,检查按键是否按下
	cmp r2,#0
	bleq  light_led_fun    @ 如果按下按键,调用点亮 LED 函数
	blne  off_led_fun      @ 如果没有按下,调用熄灭 LED 函数
	
	bl sdelay             @ 延时
	b loop                @ 继续循环

@@ 点亮 LED
light_led_fun:
	ldr r0,=0x11000c44    @ 加载 GPX2DAT 地址
	ldr r2,[r0]           @ 读取 GPX2DAT
	orr r2,r2,#0x80       @ 设置 GPX2_7 为高电平,点亮 LED
	str r2,[r0]           @ 写回 GPX2DAT
	mov pc,lr             @ 返回

@@ 熄灭 LED
off_led_fun:
	ldr r0,=0x11000c44    @ 加载 GPX2DAT 地址
	ldr r2,[r0]           @ 读取 GPX2DAT
	bic r2,r2,#0x80       @ 清除 GPX2_7 高电平,熄灭 LED
	str r2,[r0]           @ 写回 GPX2DAT
	mov pc,lr             @ 返回

@@ 延时函数
sdelay:
	ldr r10,=0xFFFF       @ 加载一个较大的值,用于延时
delay_loop:
	sub r10,r10,#1        @ 自减
	cmp r10,#0            @ 检查是否达到 0
	bgt delay_loop        @ 如果没有到 0,继续延时

	mov pc,lr             @ 返回
.end

-

GPIO章节

  1. 概述
  • GPIO(General Purpose Input/Output,通用输入输出端口) 是嵌入式系统中用于控制和管理芯片外部管脚的模块。
  • GPIO 模块允许用户根据需要将芯片的管脚配置为输入或输出,并通过这些管脚与外部设备进行交互(例如,控制 LED、读取传感器信号等)。
  • 多功能引脚:GPIO 引脚可以配置为多个功能,不仅限于通用输入输出,还可以配置为串口、USB、I2C、SPI 等特定功能。
  1. GPIO 管脚的分组管理
  • 为了有效管理芯片上众多管脚,GPIO 管脚通常按组分类,不同组的 GPIO 管脚通过寄存器进行独立控制。例如:
    GPA:组 A 管脚,如 GPA_0, GPA_1。
    GPB:组 B 管脚。
    GPX1, GPX2:组 X 的第 1 和第 2 组管脚。
    GPD:组 D 管脚,依次类推。
  • 每组 GPIO 通常包含 16 个引脚,可以独立配置为输入或输出,每组都有自己的控制寄存器。
  1. GPIO 的基本功能
  • 输出功能
  • 输入功能
  1. GPIO的其他功能
  • 串口(UART):用于串行通信。
  • USB 接口:用于外设通信。
  • I2C/SPI 接口:用于与传感器或其他外部设备的同步通信。
  • 中断引脚:部分 GPIO 可以配置为中断引脚,当管脚电平变化时触发中断信号,通知处理器做出响应。
  1. 使用示例在资源文件里面,开发板为fs4412

串口通信

串口(Serial)是一种常见的通信方式,它通过串行传输数据,即一次一位地发送或接收数据。相比之下,并口(Parallel)一次可以发送多个数据位,但由于并口需要更多的引脚,所以在嵌入式系统中较少使用。

基本概念

  • 串口控制器:负责管理串口通信的硬件模块,控制数据的发送与接收。
  • 串行通信:数据按位顺序依次传输,通常用于设备之间的数据交换,常见于嵌入式系统、传感器等应用。
  • 并行通信:数据一次性发送多位数据,虽然速度快,但需要更多的引脚和布线,较少在现代嵌入式系统中使用。
缺点
  • 速度较慢:相比并行通信,串行通信速度较慢,因为它一次只能传输一位数据。
  • 数据量较小:串口通信一次发送的数据量较小,通常是字节级别的数据。
  • 误码率问题:由于信号在传输过程中可能受到噪声、干扰等影响,导致数据误码。
  • 同步问题:串行通信需要发送方和接收方的时钟保持同步,如果不同步,可能会导致数据丢失。

串口通信代码编写流程

  1. 找到板子上的串口硬件
    • 在电路板上找到串口接口硬件,例如 COM2 或 CON7,这些端口通常连接到处理器的串行通信接口上,用于发送和接收数据
  2. 在电路图中查找对应的 GPIO 引脚
    • 根据硬件端口的编号(如 COM2/CON7),打开电路原理图,查找该端口连接到芯片的哪些 GPIO 管脚。
      例如:在电路图中查找到 COM2 连接到芯片的 GPA1_0 和 GPA1_1 引脚。
  3. 将 GPIO 配置为串口模式
    • 打开芯片的 datasheet(数据手册),查找 GPIO 章节,找到对应的管脚配置寄存器,并将这些 GPIO 管脚配置为串口模式。
    • 例如:对于 GPA1_0 和 GPA1_1,通过配置寄存器将其设置为串口的 TX(发送)和 RX(接收)引脚。
GPA1CON = 0x11400020;        // GPA1CON 寄存器地址
GPA1CON &= ~(0xFF);          // 清除 GPA1_0 和 GPA1_1 的原有配置
GPA1CON |= (0x22);           // 配置 GPA1_0 和 GPA1_1 为串口模式 (0x2)
  1. 配置串口寄存器
    • 在 UART 串口章节 中,找到串口控制器的相关寄存器,配置波特率、停止位、数据位等参数。
    • 配置串口寄存器的步骤
      • ULCON2:配置数据宽度、停止位、校验方式。
      • UCON2:配置接收和发送的模式(轮询模式或中断模式)。
      • UTRSTAT2:用于检测发送和接收状态,判断是否可以继续发送或接收数据。
      • UTXH2 和 URXH2:发送和接收数据的寄存器。
      • UBRDIV2 和 UFRACVAL2:配置波特率。
  2. 波特率计算
    • 根据波特率和系统时钟(SCLK_UART),需要配置波特率寄存器 UBRDIV2 和 UFRACVAL2。
    • 波特率公式
      D I V _ V A L = ( S C L K _ U A R T / ( 波特率 × 16 ) ) – 1 DIV\_VAL = (SCLK\_UART / (波特率 × 16)) – 1 DIV_VAL=(SCLK_UART/(波特率×16))–1
      例如:波特率为 115200 bps,SCLK_UART 为 100 MHz
DIV_VAL = (100000000 / (115200 × 16)) - 1 = 54.253 - 1 = 53.253
UBRDIVn = 53  // 整数部分
UFRACVALn = 4 // 小数部分(0.253 × 16 = 4)
串口与PC通信代码的编写
  1. 编写main.c
//嵌入式系统中发送 "hello world" 字符串
/*
	## GPIO章节  中将管脚配置为串口模式 
		GPA1CON  0x11400020		[3:0]=0x2   [7:4]=0x2
	## UART串口章节, 配置寄存器			波特率 停止位 数据值 校验位   ..... 
		ULCON2	0x13820000		[1:0] = 0x3		8bit data width
								[2] = 0			1 stop bit
								[5:3]=000    	no parity
								[6] = 0			no IR
		UCON2	0x13820004		[1:0]=0x01		poll for recv 
								[3:2]=0x01		poll for send
		UTRSTAT2	0x13820010	x=[0]   1,接收buf有了数据   0-nodata
								x=[1]   ,1 可以继续发送了	0-数据还在发送中
		UTXH2	0x13820020		[7:0]=data;
		URXH2	0x13820024		data=[7:0];

		UBRDIV2		0x13820028		53
		UFRACVAL2 	0x1382002C		4
*/

// GPIO 和 UART 寄存器的地址定义
#define	GPA1CON  	*(volatile long*)0x11400020	// GPA1CON GPIO 引脚控制寄存器
#define	ULCON2		*(volatile long*)0x13820000	// ULCON2 串口控制寄存器,设置数据宽度、校验位等
#define	UCON2		*(volatile long*)0x13820004	// UCON2 串口控制寄存器,设置发送和接收模式
#define	UTRSTAT2	*(volatile long*)0x13820010	// UTRSTAT2 串口状态寄存器,检查是否可以发送和接收
#define	UTXH2		*(volatile long*)0x13820020	// UTXH2 串口发送数据寄存器
#define	URXH2		*(volatile long*)0x13820024	// URXH2 串口接收数据寄存器

#define	UBRDIV2		*(volatile long*)0x13820028	// 波特率整数部分寄存器
#define	UFRACVAL2 	*(volatile long*)0x1382002C	// 波特率小数部分寄存器

// 初始化 UART 串口
void uart_init(void)
{
	// 配置 GPA1_0 和 GPA1_1 为串口模式
	GPA1CON = GPA1CON & ~0xF;        // 清除 GPA1_0 的原有配置
	GPA1CON = GPA1CON | (1<<1);      // 设置 GPA1_0 为 UART TX (串口发送)

	GPA1CON = GPA1CON & ~(0xF<<4);   // 清除 GPA1_1 的原有配置
	GPA1CON = GPA1CON | (1<<5);      // 设置 GPA1_1 为 UART RX (串口接收)

	// 配置 ULCON2,设置为 8 位数据宽度、1 位停止位,无校验
	ULCON2 = ULCON2 | 0x3;           // 设置数据宽度为 8 位
	ULCON2 = ULCON2 & ~(1<<2);       // 设置 1 个停止位
	ULCON2 = ULCON2 &  ~(7<<3);      // 禁用校验位 (no parity)
	ULCON2 = ULCON2 &  ~(1<<6);      // 禁用红外模式 (no IR)

	// 配置 UCON2,设置为轮询模式
	UCON2 &= ~(3);                   // 清除原有配置
	UCON2 |= 1<<0;                   // 设置为轮询接收模式
	UCON2 &= ~(3<<2);                // 清除原有发送配置
	UCON2 |= 1<<2;                   // 设置为轮询发送模式

	// 设置波特率为 115200
	UBRDIV2 = 53;                    // 设置波特率的整数部分
	UFRACVAL2 = 4;                   // 设置波特率的小数部分
}

// 发送单个字符
void putc(char ch)
{
	// 等待直到发送缓冲区为空
	while(  ( UTRSTAT2 & (1<<1)  ) == 0 );
	// 向发送寄存器中写入数据
	UTXH2 = ch;
}

// 发送字符串
void puts(char *s)
{
	int i = 0;
	// 循环发送字符串中的每个字符
	while(s[i]) {
		putc(s[i]);  // 发送单个字符
		i++;
	}
}

// 简单的延时函数,延时 ms 毫秒
void mysleep(int ms)
{
	while(ms--) {
		int num = 0x1FFF/2;  // 简单的延时循环
		while(num--);
	}
}

// 主函数
void main(void)
{
	uart_init();  // 初始化 UART

	/*
		在 Linux 系统中,\n 表示回车和换行
		在 Windows 系统中,\n 表示换行,\r 表示回到行首
	*/
	
	while(1) {
		puts("hello world\r\n");  // 通过串口发送 "hello world" 字符串并换行
		mysleep(200);             // 延时 200 毫秒
	}

	return;
}

  1. 编写start.S
  • 目的是为嵌入式系统中的主程序(如串口代码)提供一个基本的运行环境。
  • 启动代码的作用是对处理器和堆栈等进行初始化,并最终跳转到主程序的入口点。
.global  delay1s                @ 全局定义 delay1s 延时函数
.text                           @ 定义文本段(代码段)
.global _start                  @ 全局定义 _start 入口函数

_start:                         @ 程序入口点,程序从此开始执行
    b      reset                @ 跳转到 reset 处(复位处理)
    ldr    pc, _undefined_instruction  @ 加载 undefined_instruction 异常处理器的地址
    ldr    pc, _software_interrupt    @ 加载 software_interrupt 异常处理器的地址
    ldr    pc, _prefetch_abort        @ 加载 prefetch_abort 异常处理器的地址
    ldr    pc, _data_abort            @ 加载 data_abort 异常处理器的地址
    ldr    pc, _not_used              @ 加载未使用异常的处理器地址
    ldr    pc, _irq                   @ 加载中断请求 (IRQ) 异常处理器的地址
    ldr    pc, _fiq                   @ 加载快速中断 (FIQ) 异常处理器的地址

_undefined_instruction: .word _undefined_instruction  @ 定义未定义指令异常处理器的地址
_software_interrupt:    .word _software_interrupt     @ 定义软件中断异常处理器的地址
_prefetch_abort:        .word _prefetch_abort         @ 定义指令预取异常处理器的地址
_data_abort:            .word _data_abort             @ 定义数据访问异常处理器的地址
_not_used:              .word _not_used               @ 定义未使用异常处理器的地址
_irq:                   .word _irq                    @ 定义 IRQ 中断异常处理器的地址
_fiq:                   .word _fiq                    @ 定义 FIQ 快速中断异常处理器的地址

reset:
    ldr    r0,=0x40008000        @ 设置异常向量表基地址为 0x40008000
    mcr    p15, 0, r0, c12, c0, 0  @ 写入 Vector Base Address Register (VBAR),重新定位异常向量表

init_stack:
    ldr    r0,=stacktop          @ 将栈顶地址加载到 r0

    /******** svc 模式栈 ********/
    mov    sp,r0                 @ 将栈顶指针加载到 sp,svc 模式的栈
    sub    r0,#128*4             @ 减少 512 字节空间用于 IRQ 模式栈

    /******** irq 模式栈 ********/
    msr    cpsr,#0xd2            @ 切换到 IRQ 模式(CPSR 位设置)
    mov    sp,r0                 @ 设置 IRQ 模式的栈指针
    sub    r0,#128*4             @ 减少 512 字节空间用于 FIQ 模式栈

    /******** fiq 模式栈 ********/
    msr    cpsr,#0xd1            @ 切换到 FIQ 模式
    mov    sp,r0                 @ 设置 FIQ 模式的栈指针
    sub    r0,#0                 @ FIQ 模式使用 0 字节的栈空间

    /******** abort 模式栈 ********/
    msr    cpsr,#0xd7            @ 切换到 Abort 模式
    mov    sp,r0                 @ 设置 Abort 模式的栈指针
    sub    r0,#0                 @ Abort 模式使用 0 字节的栈空间

    /******** undefined 模式栈 ********/
    msr    cpsr,#0xdb            @ 切换到 Undefined 模式
    mov    sp,r0                 @ 设置 Undefined 模式的栈指针
    sub    r0,#0                 @ Undefined 模式使用 0 字节的栈空间

    /******** sys 和 usr 模式栈 ********/
    msr    cpsr,#0x10            @ 切换到用户模式 (SYS/USR)
    mov    sp,r0                 @ 设置用户模式的栈指针
                                @ 为用户模式分配 1024 字节的栈空间

    b      main                  @ 跳转到 main 函数,进入主程序

delay1s:                         @ 定义 1 秒延时函数
    ldr    r4,=0x1ffffff         @ 将一个大数加载到 r4,用作计数器
delay1s_loop:
    sub    r4,r4,#1              @ 每次循环 r4 减 1
    cmp    r4,#0                 @ 检查 r4 是否为 0
    bne    delay1s_loop          @ 如果 r4 不为 0,继续循环
    mov    pc,lr                 @ 返回调用函数

.align 4                         @ 地址对齐

/****  swi_interrupt handler  ****/ 
@ 异常处理中断的处理器可以在此处实现

.data                            @ 定义数据段

stack:
    .space 4*512                 @ 为栈分配 512 字节的空间
stacktop:
    .word stack+4*512            @ 定义栈顶指针的初始位置

.end                             @ 程序结束标志
  1. 编写 Makefile
    定义编译链接规则和清除不必要的东西
    用于编译 ARM 裸机程序。它使用了 arm-none-linux-gnueabi 工具链来编译汇编和 C 代码,链接生成 ELF 可执行文件,并生成二进制文件。
# 目标名称 all 是默认目标,当执行 'make' 时会首先执行该目标中的指令
all:
	# 使用 arm-none-linux-gnueabi-gcc 编译 start.S 文件为目标文件 start.o
	# -fno-builtin: 禁用 GCC 的内置函数,避免使用标准库函数
	# -nostdinc: 不使用标准头文件,适合裸机编程
	# -c: 仅编译为目标文件,不进行链接
	arm-none-linux-gnueabi-gcc -fno-builtin -nostdinc -c -o start.o start.S

	# 使用 arm-none-linux-gnueabi-gcc 编译 main.c 文件为目标文件 main.o
	# 这里同样不使用内置函数和标准头文件,适合裸机编程
	arm-none-linux-gnueabi-gcc -fno-builtin -nostdinc -c -o main.o main.c

	# 链接 start.o 和 main.o 目标文件,生成 main.elf 可执行文件
	# -Tmap.lds: 使用自定义的链接脚本 map.lds 定义内存布局
	# main.elf: 最终生成的可执行文件
	arm-none-linux-gnueabi-ld start.o main.o -Tmap.lds -o main.elf

	# 使用 arm-none-linux-gnueabi-objcopy 将 ELF 文件转换为纯二进制文件 main.bin
	# -O binary: 指定输出格式为二进制文件
	arm-none-linux-gnueabi-objcopy -O binary main.elf main.bin

	# 使用 arm-none-linux-gnueabi-objdump 反汇编 ELF 文件,生成汇编代码文件 main.dis
	# -D: 反汇编整个 ELF 文件
	# 反汇编的输出被保存到 main.dis 文件中,便于阅读
	arm-none-linux-gnueabi-objdump -D main.elf > main.dis

# clean 目标用于清理生成的中间文件和最终文件
clean:
	# 删除所有备份文件、目标文件、ELF 文件、二进制文件和反汇编文件
	# -f: 忽略文件不存在的错误
	# *.bak: 删除所有扩展名为 .bak 的备份文件
	# *.o: 删除所有目标文件
	# main.elf: 删除生成的 ELF 可执行文件
	# main.bin: 删除生成的二进制文件
	# main.dis: 删除生成的反汇编文件
	rm -rf *.bak start.o main.o main.elf main.bin main.dis

  1. 加入map.lds
    • map.lds 链接脚本 定义了程序在内存中的布局,指定了代码段、数据段和未初始化数据段的起始地址和内容来源。
    • 这份脚本非常适用于嵌入式系统开发,特别是当你需要精确控制程序的各个部分在内存中的位置时。
    • 代码如下
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm") 
# 指定生成文件的输出格式为 "elf32-littlearm",适用于 32 位 ARM 的小端格式
# "elf32-littlearm" 出现在三个位置,分别对应目标文件、二进制文件和目标机器的 ELF 格式

OUTPUT_ARCH(arm)
# 指定目标架构为 ARM,确保链接器知道处理的是 ARM 处理器的指令集

ENTRY(_start)
# 指定程序的入口地址为 _start,这是程序执行的起始点,通常定义在汇编文件 start.S 中

SECTIONS
{
	. = 0x40008000;  
	# 设置程序在内存中的起始地址为 0x40008000,通常是裸机编程中设置的 RAM 基地址
	# 所有代码段、数据段都从这个地址开始布局

	. = ALIGN(4);
	# 将当前地址对齐到 4 字节边界,以确保对齐要求满足 ARM 处理器的要求
	# 对齐有助于提高内存访问效率,避免非对齐访问引发错误

	.text      :
	{
		start.o(.text)
		# 将 start.o 文件中的 .text 段(代码段)加载到当前地址处
		*(.text)
		# 加载所有目标文件中的 .text 段(代码段),确保代码在合适的内存位置
	}

	. = ALIGN(4);
	# 再次对齐到 4 字节边界,以确保段之间的正确对齐

    .data : 
	{ *(.data) }
	# 将所有目标文件中的 .data 段(数据段)加载到当前地址
	# .data 段通常包含已初始化的全局变量和静态变量

    . = ALIGN(4);
	# 再次对齐到 4 字节边界,确保 bss 段的正确对齐

    .bss :
    { *(.bss) }
	# 将所有目标文件中的 .bss 段(未初始化的全局变量和静态变量)加载到当前地址
	# .bss 段通常不占用文件大小,它只分配未初始化数据所需的空间
}
  1. make生成bin(二进制文件)烧录进入开发板即可,成功时会发送hello world 给PC
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值