sweetyy、

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

嵌入式之ARM指令系统(通用指令32位)

嵌入式之ARM指令系统(通用指令32位)

一、汇编指令类别

1. ARM汇编指令(ARM公司定)
	一条汇编指令唯一对应一条机器指令 RISC 
		MOV R0, #5  => 010101...
		MOV R0,R1
		ADD R2,R1,R0


	操作码: 表示是何种操作  "指令助记符"
			MOV 表示 移动 
			ADD 表示 加法

			....
	操作数:
			结果操作数: 用来保存计算的结果 目的操作数 前置
			运算操作数:	
				第一操作数 R
				第二操作数 				
	
	指令是用来“运算”:运算符
					 操作数	

2. 伪指令(由编译器厂商定的,keil环境下有keil的伪指令,
		GNU环境有gnu的伪指令)
		
	keil环境下常用的伪指令:
		数据 C
		int a = 5; 
		int b[10];
		int c[10] = {2,3};
		
		data_a				//顶格写的 标号,标号表示一个地址,编译器可以读到
			DCD 5		//DCD X : 分配4bytes空间,并且把X的值填入此处
									
		data_b
			SPACE 40   //SPACE X : 在此处开辟 X 个字节的空间,内容不定。
				
		data_c
			DCD 2
			DCD 3
			SPACE 8*4		//40-2*4=32		
			
		标号必须顶格,DCD是四个字节,DCW是两个字节。标号顶格、指令tab跳格

		"段":  分区域
			代码段 这个区域都是代码---ROM 0x08000000---
			数据段 这个区域存放的是全局的数据 
			堆栈段 stack
			...
			那么在keil下面如何定义“段”呢?					
				AREA 段名1, 段的属性1, 段的属性2, ...
			eg:		AREA  mstack, DATA, READWRITE
				
				AREA 段名2 。。。
			eg:AREA RESET , DATA, READONLY					
				END
			段名:自己定义,规定的定义
			段的属性1CODE  代码段
				CODE32  thumb-2代码
				DATA  数据段
				NOINIT 不初始化
				
			属性2:
			READWRITE   只读可写				。。。
			READONLY  只读
			
			属性3
			ALIGN=3   8字节对齐(23次幂对齐)
						Cortex-M4要求代码必须是 8字节对齐,否则编译不会通过;	
						PRESERVE8 指令,表示后续如果没有特殊说明,都是采用 8字节对齐
						当有C语言和汇编并存时,C语言的编译后的指令也需要8字节对齐,
						这时这条指令可以实现。
3. 宏指令(同上,由编译器厂商指定)
由编译器厂商写的,keil环境下有keil的宏指令 。。。	在keil中需要顶格		
		stack_size  EQU  0x200 ; // #define  stack_size 0x200

4.keil环境下的汇编语句格式:		
	lable   指令     ;注释		
		lable:顶格写,标识这一行的地址			
		指令:ARM指令,伪指令				
		; 到行末,都是注释部分。

练习:
一个启动 .s文件(start.s),至少需要包含三个段:

				;//堆栈段										
				; //中断向量表
				
				; //代码段

①新建.s文件

②写入代码

stack_size EQU  0x200	
; define stack
	AREA mystack, DATA, READWRITE
stack_start
	SPACE stack_size
stack_end
	PRESERVE8  
		
;define vectors
	AREA RESET, DATA, READONLY
vectors
	DCD  stack_end  ;stack-top
	DCD  test_start	;begin code
vectors_end	  


;define code
	AREA mycode, CODE, READONLY,ALIGN=3
test_start 
	
	
	B .		;while(1)	B表示跳转,B .表示原地循环
	END		

参考代码:(作业,带上注释)

二、ARM指令的寻址方式(自学)

三、ARM指令的格式

​ ARM指令的基本格式
{}{S} , {, }
​ <>内的必须要的 {} 可选的

opcode: operator code 操作码,指令助记符,表示哪条指令
	如:	
		MOV
		ADD
		SUB
		LDR/STR
		...

cond: condition 条件。该指令执行的条件。如果省略不写,
	则表示该条件指令无条件执行(必须执行)
	
	if (r1 == r2)
	{
		r0 = 250;  // MOV R0, #250
	}
	=>
		CMP R1, R2;   => if R1 == R2 , R1 - R2结果为0,
						 xPSR.Z == 1
						 
		//MOV(xPSR.Z == 1) R0, #250
		//相当于MOVEQ R0, #250
		
			EQ "equal"  => 检查 xPSR.Z == 1
		 
			条件码			    含义			              测试的标志(xPSR中的标志位)
			EQ				Equal(相等)     			        Z==1
			NE				Not Equal(不相等)      		   Z==0
			CS/HS			Carry Set (C == 1)
							unsigned Higher or Same  	     C == 1
							a>=b 				
			CC/LO			Carry Clear(C == 0)			     C == 0
							a<b		
			MI				MInous(负数)				        N == 1				
			PL				Positive or Zero(非负数)	       N == 0
											
			VS				V Set(溢出)				        V == 1				
			VC				V Clear(没溢出)			       V == 0
			
			HI				unsigned Higher			      (C == 1) && (Z == 0)																
			LS				unsigned Lower or Same	      (C == 0) || (Z == 1)
															
			GE				signed Greater or Equal          N == V
							>=		1 1000 11000						
		
			LT				Less Than					     N != V
							 <
							 
			GT				Greater Than				 (N == V) && (Z == 0)
								>
			LE				Less than or Equal			 (Z==1) ||(N != V)	

CS/HS CC/LO 针对无符号数,但是有符号数,CMP 正,正或CMP负,负也满足。如果CMP 一正一负,则C标志位不满足



S:  Status 表示该指令执行结果是否影响xPSR(程序状态寄存器)的标志位
	
	如:
		MOV R0, #0; -> 不会影响任何状态标志
		MOVS R0, #0 ; ->会影响状态标志位
		
	有一些指令如: CMP, CMN, TEQ ... 这些不加S也影响状态标志位
			因为这些指令,不保存运算结果,只影响状态标志位

Rd: Register Desatiation 目标寄存器
	用来保存运算的结果
operand1:	第一个操作数

operand2:2个操作数(有些指令没有第二个操作数).
操作数有如下形式:
		(1) #immer_8r 立即数(常量表达式)
				立即数 -> 常数
			立即数的生成是有要求的:
			by 0-255 shifted left by 0-23 or duplicated in all, odd or even bytes		
				ADD R0, R1, #250		
				ADD R0, R1, #0x10
				ADD R0, R1, #(1 << 3) | (1 <<4)
		为了避免立即数不合规	
		建议大家这样用:
		LDR R0, =666666
		(2) Rm 寄存器
			操作数可以是一个寄存器。
			如:
				ADD R0, R1, R2;
					R1 + R2 -> R0
					
		(3) Rm,shift 寄存器移位方式 
			操作数可以是一个寄存器加移位方式				
			算术移位符号参与,逻辑移位无符号,符号位不动,由于计算机的巧妙设计,符号位就是最高数据位
				算术右移:右边的低位直接干掉,左边全部补符号位。
				逻辑移位:无论是左移还是右移全部补0.左移相当于乘2,右除2
				
				C语言的中的移位是算术移位还是逻辑移位?
				//有符号数:算术移位    无符号数:逻辑移位
				int
					-1 >> 30   ?       //C语言中是算术移位,结果还很-1
					-1u >> 30  ?       //3
					1 00001->1 11111
					例如-8,补码1 1000,逻辑左移3位:1 1000 000[符号位不动,后面加30]
					                 逻辑右移3位:1 000 1000[符号位不动,前面加30]
					                 算术左移3位:1 0 111
					                 算术右移3位:1 111 1
					typeof(-1) => int
					typeof(-1u) => unsigned int
			
				LSL #n  
					Logic Shift Left 逻辑左移n位,
					Logic逻辑移位,无论是左移还是右移,空出的位,都补0
				LSR #n
					Logic Shift Right 逻辑右移n位,
					
					在移出的位为0的情况下, 
						LSL #n 就相当于在原寄存器值 乘以 2的n次方
						LSR #n 就相当于在原寄存器值 除以 2的n次方
				
				ASR #n 算术右移n位
					算术移位,不应该改变它的符号。
					最高n位补符号位的值.
					
				ASL #n  没有。 => LSL逻辑左移				
			
				ROR #n		Rotate Right 循环右移
					把右边移的n位,补到最左边。
					
				RRX 带扩展的循环右移1位
					带C(xPSR.C) 那个位			
			
				type Rs
					type为上述移位方式的一种,如: LSL, LSR,ASR, ROR, RRX...
					Rs偏移量寄存器,低8位有效,要移的bit位数保存在Rs中。
代码测试:
(1)MOV R1,#3 ;r13
MOV R2,#1 ;r2
1
LSL R2,R1 ;r2==8 1<<3
​ (2) ADD R0, R1, R2, LSR #2;(2)和(1)组合测试
​ r13
​ r2
8
​ r0==5
​ (3) R0<--2 R0*12--->R2
​ 写出代码!内卷 指令字节数少 2x12 2x4x3 2<<2


R=2
12R=4R+8R
​ =(R<<2)+8R //R左移两位后,此时等于4R
​ =4R+(4R)<<1

四、ARM指令(UAL)

1. ARM存储器访问指令
	用来在存储器(Memory,) 《-》寄存器之间传递数据


	把数据从存储器 -> 寄存器  加载  Loader  LDR
	把数据从寄存器 -> 存储器  存储  Store   STR

​ 没有一条指令可以实现存储器到存储器的交换、只能通过寄存器。
​ 例如: MA1--3
​ MA2--6
​ ①实现两者交换:LDR-LDR STR-STR
​ ②实现MA1->MA2 LDR-STR
​ ③实现数据加法:LDR-LDR-ADD-STR

(1)
​ LDR{cond} {S} {B/H} Rd, <地址> //从地址到Rd
​ STR{cond} {B/H} Rd, <地址> //从Rd到地址

​ LDR加载,把存储器<地址>中的内容加载到 寄存器Rd中
​ STR存储,把寄存器Rd中的内容,存储到存储器<地址>中去

​ 问题一: 没有直接的指令在寄存器和存储器之间传递数据,怎么办?
​ //LDR先加载到寄存器,STR再从寄存器存到存储器。
​ 问题二:存储器地址从哪里开始?
​ //从0x20000000开始,写代码时定义的比它大就行
​ 问题三:存储器地址如何表示?

​ //正确方法:0x20001000->Rn 【Rn】就是存储器0x20001000的地址空间 ​ 问题四:存储地址增加如何表示?直接+?(4个字节,相当于int,所以增4)
​ 正确示例:

​ NOTE:
​ (1)B: Byte一个字节, H: Half word半字(两个字节), 如果省略,默认为4个字节
​ B/H决定加载/存储多少个字节。

​ S: Signed把地址的那个变量,当作是一个有符号的。
​ 如果没有S就把地址的那个变量,当作是一个无符号的。
​ 有符号来说,高位全部补符号位,
​ 如果是无符号的,高位全部补0

​ (2)地址确定方式: 基址寄存器 + 偏移量
​ 地址值肯定需要在一个寄存器中
​ 即:
​ 地址值

				[Rn]				Rn

​ [Rn, 偏移量] Rn + 偏移量 Rn值不变
​ [Rn, 偏移量]! Rn + 偏移量 Rn值+偏移量
​ [Rn], 偏移量 Rn Rn值=Rn + 偏移量

​ []内表示存储器的地址值 ,
​ 如果有!或,偏移量在[]外边,则表示做完后,基址值自动增加偏移量
​ 偏移量有以下3种方式:
​ 立即数:
​ 如: LDR R1, [R0, #0x12]
​ [R0+0x12] -> R1
​ 寄存器
​ 如: LDR R1, [R0, R2]
​ [R0+R2] -> R1
​ 寄存器及移位常数
​ 如: LDR R1, [R0, R2, LSL #2]
​ [R0 + R2 << 2] -> R1

​ 任务1:
​ 给存储器0x20001000单元写入一个整数(-2).

​ 任务2:将0x20001000单元的数值转到0x20001008。自己完成!
​ 练习:
​ (1)
​ char ch =0x80; //编译时刻或运行时刻,为ch分配一个存储器空间 0x2000 1000,char是有符号
​ int a; //编译时刻或运行时刻,为a分配一个存储器空间 0x2000 1004,int a是给a一个地址
​ a = ch;

​ (2)
​ unsigned char ch =0x80; //编译时刻或运行时刻,为ch分配一个存储器空间 0x2000 1000
​ int a; //编译时刻或运行时刻,为a分配一个存储器空间 0x2000 1004
​ a = ch;

​ (2) 多寄存器存取
​ 在一个连续的存储器地址上,进行多个寄存器的存取。
​ 加载 LDR 多 Multi

​ 多寄存器加载 LDM Loader Multi
​ 多寄存器存储 STM Store Multi

​ LDM{cond}<模式> Rn{!}, reglist
​ STM{cond}<模式> Rn{!}, reglist

​ <1>这两条指令只是通过一个寄存器 Rn指定了一个存储器的地址,
​ 存储器的多个地址,是连续增加,还是连续递减呢?
​ 由<模式>来指定:
​ 注意:无论是哪种模式,低地址都是对应编号低的寄存器(32位,所以+-4)
​ IA: Incrememt After() 每次传送后地址自动增加(+4) ,先放数后加地址<-----
​ DB: Decrement Before 每次传送前地址自动减少(-4) ,先减4,后取数<-----
​ IB: Increment Before 每次传送前地址先自动增加(+4)
​ DA:Decrement After 每次传送后地址自动减少(-4),先取数后减4
​ ARM Cortex M4只使用IA, DB

​ <2>reglist: 表示寄存器列表,可以包含多个寄存器,
​ 使用","隔开,寄存器由小到大排列(编译系统会自动按序号排)
​ 如: {R1,R3,R4,R5}
​ {R1,R3-R5}
​ <3> ! 可加可不加
​ 加: 表示最后存储器的地址写入到Rn中去。(保护现场)
​ 不加: 最后Rn的值不变

​ 任务:将R0-R3放到存储器单元0x20000200开始的递减连续单元存放,然后再恢复
​ 参考代码:

        Ⅰ、 空递增方式

​ 【R3】=0x2000100c,0c原本没有东西,所以要先减4,再从存储器取数放到寄存器

恢复现场后,【R3】变为0x20001000
 Ⅱ、满递减方式

​ (3) 堆栈操作:堆栈的低地址对应编号低的寄存器
​ 压栈: PUSH
​ 出栈: POP

​ "栈"就是一块内存,上面那两条指令,并没有指定内存地址,
​ PUSH, POP用的地址寄存器是SP

​ 堆栈有四种类型:
​ A: add 递增堆栈 D: Dec递减堆栈
​ SP堆栈 栈顶指针,
​ 栈顶指针可以保存元素 -> 满堆栈 Full
​ 也可以指向空(不保存元素) ->空堆栈 Empty

​ EA: 空递增 // 相当于STMIA--LDMDB
​ PUSH X //先放数后加4,最后栈顶指向空。
​ X -> [SP]
​ sp ++

​ POP x //取数时先减4,后取数,因为一开始指向空。
​ sp--
​ [sp] -> x

​ FA: 满递增 // 相当于STMIB--LDMDA,
​ PUSH X //先加4,后放数,最后栈顶指向数,不为空
​ sp ++
​ x -> [SP]

​ POP x //先取数,再减4,因为开始栈顶指向满,指向数据
​ [sp] -> x
​ sp --

​ ED: 空递减 // 相当于STMDA--LDMIB
​ PUSH X //先放数,后减4,最后栈顶指向空
​ x -> [sp]
​ SP--
​ POP x //取数时,先加4再取,因为一开始栈顶指向空
​ sp++
​ [sp] -> x

​ FD: 满递减 // 相当于STMDB--LDMIA
​ PUSH X //先减4,再放数,最后栈顶指向满,指向数据
​ sp--
​ x -> [sp]

​ POP x //先取数,再加4
​ [sp] -> x
​ sp++
<----- ARM采用的堆栈形式是: FD满递减堆栈

​ 例子: C函数的实现
​ “现场保护” :
​ “现场恢复”
​ PUSH X
​ sp-- //0x20000200
​ x -> [sp]
​ //PUSH {R0-R3}
​ 0x20000200-4 0x200001FC R3
​ 0x200001F8 R2
​ 0x200001F4 R1
​ 0x200001F0 R0
​ POP x
​ [sp] -> x
​ sp++
​ //POP {R0-R3}
​ //SP--0x200001F0 R0-->R1--->R2--->R3 SP 0x20000200

​ (堆栈的低地址对应编号低的寄存器,编号大的先入栈,而不是最后写的,后进先出,小编号R0最后进,最先出。所以写程序一般按顺序,小编号在前,大的在后。)

2. ARM数据处理指令  -> 对寄存器内容操作

​ //如果想对存储器的内容进行操作,将数据导出到寄存器中进行操作,最后再放回存储器

​ 数据传送指令
​ 算术运算指令
​ 逻辑运算指令
​ 比较指令
​ (1) 数据传送指令
​ MOV{cond}{S} Rd, operand2 ; Rd <-- operand2
​ MVN{cond}{S} Rd, operand2 ; Rd <--- ~operand2(取反)

​ S: 表示结果影响xPSR状态标志位
​ 第二操作数 operand2 -> Rd
​ 如:
​ MOV R0, #3
​ MOV R1, R0
​ MOV R2, R1, LSL #2
​ -----

​ (2) 算术运算: 加减
​ ADD{cond}{S} Rd, Rn, operand2; Rd <--- Rn + operand2
​ ADC{cond}{S} Rd, Rn, operand2; Rd <--- Rn + operand2 + xPSR.C

​ 例子:
​ 32位的寄存器,如何实现64bits 加法 a + b -> c
​ //分成32+32
​ a : R1(高32位) R0(低32位)
b : R3 R2
​ c : R5 R4
​ ADDS R4, R2, R0 //低32位的和,一定要加S,带标志位
​ ADCS R5, R3, R1 //低32位和的进位结果以及高32位的和。带进位(第31位)的加法,实现4位相加

​ SUB{cond}{S} Rd, Rn, operand2; Rd <--- Rn - operand2
​ SBC{cond}{S} Rd, Rn, operand2; Rd <--- Rn - operand2 - !xPSR.C 带借位的减法

​ 例:32位的寄存器,如何实现64bits的减法? a - b -> c
​ a : R1 R0
​ b : R3 R2
​ c : R5 R4
​ SUBS R4, R0, R2 ; 此时 if R0 - R2产生的借位 -> xPSR.C == 0
​ SBCS R5, R1, R3 ; R1 - R3 - 借位
​ 借位: !xPSR.C(取反,因为减法有借位是c=0)

​ RSB 逆向减法指令
​ Reserve
​ RSB{cond}{S} Rd, Rn, operand2; operand2 - Rn -> Rd
​ RSC{cond}{S} Rd, Rn, operand2; operand2 - Rn - !xPSR.C -> Rd 带借位的逆向减法

(3) 逻辑运算指令 (按位)
AND{cond}{S} Rd, Rn, operand2; AND 与, Rn & operand2 -> Rd 按位与
ORR{cond}{S} Rd, Rn, operand2; OR 或, Rn | operand2 -> Rd 按位或
EOR{cond}{S} Rd, Rn, operand2; EOR 异或 Rn ^ operand2 -> Rd 按位异或 (相同为0,不同为1)
//Rd,目的寄存器,Rn表示寄存器,说明第一操作数只能是寄存器,operand2第二操作数可以是三种

			Bit Clear 位清零,把一个指定寄存器的中,指定的bit位给清掉
		    BIC{cond}{S} Rd, Rn, operand2;  Rn & (~operand2) -> Rd
				把Rn中 operand2中的为1的哪些位置上的bit位清零。

​ BIC置零 OPP置1

​ 练习:
​ ①R0 低4位清零


​ ②R0 清零
法一: 法二:(两操作数一样,后面的蓝 色部分也可省略)
​ ​ ③低四位清零
​ 法一: 法二: R0由0x29A变成了0x290
​ //0xf是000000……1111(32位),取反变成111111…… 0000,相与后低四位变0 法三: ​ ④取寄存器R0中的b7-b10清零
​ ​法一:
​ 法二: ​ ⑤、高四位清零
​ 法一: 法二:
⑥把第26、21、1位清零
⑦取出第7-10位

⑧把第4-8位置1
(4) 比较指令: 不需要加S,直接影响xPSR中的标志位。运算结果不保存。
     CMP Rn, operand2; 比较Rn与operand2的大小  Rn - operand2
			if Rn == operand2 
			CMP Rn, operand2
				xPSR.Z == 1 => EQ	
				
		CMN Rn, operand2; 比较Rn与operand2的大小, Rn + operand2(负数比较)
​ (5)TST Rn, operand2 ; Rn & operand2
​ //用来测试Rn中特定的bit位是否为1,一般测定一位,测试多位的话,只要有一个位为1,z就为0
​ Rn & operand2 => xPSR.Z == 1
​ => 说明operand2中为1的哪些bit的,在
​ Rn都为0
​ 测试位是1,Z0
​ 测试位是0,Z
1
​ 例子:
​ 测试R0中的第2和第3bit位,是否为1
​ 【注意:数据位一般是从0开始数的,所以最高位是31位,0位是最低位】
测试结果z=0 ​ (6)TEQ Rn, operand2 ; Rn ^ operand2 测试是否相等
​ Rn == operand2 => Rn ^ operand2 == 0 => xPSR.Z == 1

​ 3. 乘法指令
​ 略

​ 4.分支指令:用来实现代码的跳转
​ 有两种方式可以实现程序的跳转
​ (1) 分支指令
​ B lable ; lable -> PC, 不带返回的跳转 ,跳转到label,去了就不再回来

​ BL lable ; 过程调用,函数调用 带返回的
​ 把下一条指令的地址 -> LR
​ lable ->
	(2) 直接向PC寄存器赋值
		MOV PC, LR
		MOV PC, #0x80000000	

5、杂项指令

​ (1)MRS Rd, xPSR
​ xPSR: APSR, IPSR, EPSR
​ 程序状态寄存器的值 赋值给Rd
​ MOVS R0,#-1
​ MRS R0,APSR ;R0==0x80000000
​ (2)MSR xPSR, Rd
​ 将通用寄存器Rd的值,赋值给程序状态寄存器
​ 6.伪指令:
​ 伪指令,机器不识别,但是可以表示程序员或编译器的某种想要的操作。
​ 编译器会把伪指令变成一条或多条合适的机器指令。

​ (1) NOP
​ No Operation
​ 空操作,不产生任何实际的效果,但是占用机器周期。

​ (1)
​ NOP
​ NOP
​ (2)

​ NOP
​ <-> MOV R0, R0
​ MOV R1, R1
​ MOV R2, R2
​ .....

​ (2) LDR{cond} Rd, =expr
​ LDR伪指令用于加载表达式expr的值,到寄存器Rd中去。
​ expr可以为一个任意的常数,或标号,或常量表达式...
​ LDR Rd, =expr
​ =>
​ expr: (1<< 2)

​ <1> 如果expr是一个合法的立即数
​ LDR Rd, =expr
​ <=> MOV Rd, #expr

​ <2>解决立即数不合规的问题,可以直接给出存储器的地址
​ LDR R0,=0x12345678
​ LDR R0,=0x20001000

​ <3>标号(地址)
​ ①LDR Rd, =data1
​ data1
​ DCD 0x12345678

​ (2)int i=4;
​ ;先定义数据段
​ AREA mydata,DATA,READWRITE
​ data_i
​ SPACE 4
​ ;标号指向数据空间的地址
​ MOV R0,#4
​ LDR R1,=data_i
​ STR R0,[R1]

​ 任务:R0--2 R0*24--->data1(放到数据段1),并能查看

​ 分析:最小的启动代码应该包括堆栈、中断向量、代码,今天的要求是定义数据段,如何定义段? AREA name, type, READWRITE,然后定义大小。最后在代码段写代码,LDR Rd, =data1

实现代码:

stack_size EQU  0x200	
vector_size EQU 0x400
; define stack
	AREA mystack, DATA, READWRITE
stack_start
	SPACE stack_size
stack_end
	PRESERVE8  
		
;define vectors
	AREA RESET, DATA, READONLY
vectors
	DCD  stack_end  ;stack-top
	DCD  code_start	;begin code
vectors_end	  

;define data
	AREA mydata, DATA,READONLY
data_s
	SPACE 4

;define code
	AREA mycode, CODE, READONLY,ALIGN=3
code_start PROC
	MOV R0,#2  ;0000 0010 =0x0002
	LSL R0,#3  ;8R0=0001 0000 =0x0010
	ADD R0,R0,R0,LSL #1 ;8R0+16R0=24R0=0011 0000=0x30
	LDR R1,=data_s
	STR R0,[R1]
    B .
	END	

扩展:如果定义了两个数据段

posted on   sweetyy、  阅读(110)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示