本文是个人随笔,可能有点前言不搭后语。hehe...
说的也不定对,望注意~~~
好吧,
想搞个DIY操作系统,懂点汇编是必须滴~e。。。我个人呢,之前在YQ也算是学习过一点汇编吧。
当时是半懂不懂的。懵懵懂懂的记得mov,bic这些指令符号。
但是更复杂的就不懂了。
比如说啊,
bl和b的区别啊。
bne和blne啊(也是刚刚才会用)。
重复使用bl对lr影响啊
。。。
,对于上面的问题我现在还是都不太懂。
懂得可以解答一下,万谢哈~
对于下文呢,我就写写汇编对c语言的翻译吧~
嗯。回归正题。
我们单片机 实现 按键 控制 LED这个功能的具体步骤是啥呢?
下面说明可以对照代码来看~
1。上电。。。程序从flash读到ram中,并从Reset_Handler开始运行。
2。配置单片机时钟Stm32_Clock_Init。(初次尝试可省略)
3。初始化LED和KEY控制口IO寄存器
4。while循环读取按键IO状态
详解-->
1。1关于一堆乱七八糟定义
[mw_shl_code=c,true]
;--------------------------Start--------------------------
;------------------声明代码运行空间-----------------------
;------------------声明代码运行地址-----------------------
;------------------声明本文件代码类型---------------------
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
;数据段
AREA RESET, DATA, READONLY
__Vectors DCD __initial_sp ;TOP of stack
DCD Reset_Handler ;Reset Handler
;代码段
AREA |.text|, CODE, READONLY
THUMB
REQUIRE8
PRESERVE8
ENTRY
;--------------------------end--------------------------[/mw_shl_code]
名词解析:
AREA STACK, NOINIT, READWRITE, ALIGN=3
AREA中文翻译是范围,顾名思义,是定义下面跟着的那堆东西存放的地址
段是独立的、指定的、不可见的代码或数据块,它们由链接器处理。格式如下:AREA 段名,段属性1,段属性2,段属性3。。。AREA STACK, NOINIT, READWRITE, ALIGN=3NOINIT: = NO Init,不初始化。READWRITE : 可读,可写。ALIGN =3 : 2^3 对齐,即8字节对齐。SPACE命令:SPACE 命令保留一个用零填充的存储器块。所以整段的意思为:分配一个STACK段,该段不初始化,可读写,按8字节对齐。分配一个大小为Stack_Size的存储空间,并使栈顶的地址为__initial_sp。
这部分解析是在网上看到。借用一下下哈。原文链接:
http://www.cnblogs.com/WeyneChen/p/4860764.html
1。2外部添加的子函数
[mw_shl_code=c,true]
;-------------------外部函数------------------
IMPORT LED_Init
IMPORT LED_ON
IMPORT LED_OFF
IMPORT LED_EOR
IMPORT DELAY_MS
IMPORT DELAY_500MS
IMPORT KEY_Init
IMPORT KEY_READ
IMPORT Stm32_Clock_Init
;-------------------外部变量------------------
IMPORT KEY_NUM[/mw_shl_code]
IMPORT和global还有EXPORT
这三个关系有点意思
IMPORT:表示添加外部声明的函数或者变量,在需要使用到外部函数时候使用该声明
global :是声明该变量为外部可以使用的变量
EXPORT:声明该函数为外部可以调用的函数
IMPORT+global :全局变量
IMPORT+EXPORT:全局函数
1。3主函数,类似于C的main??
[mw_shl_code=applescript,true];--------------------------start--------------------------
;--------------------------函数--------------------------
Reset_Handler PROC
BL Stm32_Clock_Init
BL LED_Init ;BL,带链接的跳转
BL KEY_Init
;B Main ;C语言的main
;BL LED_ON
Mainloop
MOV R0,#10 ;延时10ms
BL DELAY_MS ;相当于delay_ms(10);R0是参数
;按键数值读取
BL KEY_READ ;key_read()
;判断KEY_NUM数值
LDR R0,=KEY_NUM ;判断全局变量
LDR R1,[R0]
CMP R1,#0x01 ;if(key_num==0x01)
BLEQ LED_EOR ;led异或取反函数
;BL DELAY_500MS
;BL LED_EOR
B Mainloop ;无条件跳转
ENDP
ALIGN
END
;--------------------------END--------------------------[/mw_shl_code]
嗯,程序从 ENTRY后面开始执行,ENTRY是程序的入口,一个.s只能有一个ENTRY,但是不同.s可以有ENTRY但是编译会警报。好神奇。。。 ENTRY后面,我跟的就是Reset_Handler
PROC和ENDP必须成对出现,类似于c语言的花括号{},但是用法也有点不一样。
ALIGN是字节对齐
每个.s后面必须跟着END,不能顶格写哦。不然报错。。。
2。1系统时钟那些事儿
[mw_shl_code=applescript,true]
;-----------------------start-----------------------
;
; 外部调用文件
INCLUDE register.s
;-----------------------end-----------------------
AREA SYSTEM_CODE, CODE, READONLY ;与主文件的AREA不同在于CODE和|.text|
;|.text|指程序运行部分
;CODE指代码存放
Stm32_Clock_Init PROC
EXPORT Stm32_Clock_Init
PUSH {R0-R7,LR}
LDR R0,=RCC_CR
LDR R1,[R0]
ORR R1,R1,#Bit0 ;RCC->CR|=0x00000001;//设置HISON,开启内部高速RC振荡
STR R1,[R0]
LDR R0,=RCC_CFGR
LDR R1,[R0]
MOV R1,#0x00 ;RCC->CFGR=0x00000000;//CFGR清零
STR R1,[R0] [/mw_shl_code]
额,这个太长了。挑个头出来讲讲。
INCLUDE register.s
相当于C语言的#include "register.s"
register.s里面是啥呢?
是我们的寄存器地址啊
举个栗子:
; PF9引脚GPIOF_ODR
LED0 EQU 0x40021414
有人又问了,
EQU又是啥。
其实就是相当于#define LED0 0x40021414
当然啦,我们操作的都是地址啊,寄存器啊,就不是c语言那样(*(unsigned int *)0x40021414)
自己翻翻M4的中文手册可以找到这个地址的。或者用右键Go to defineition在stm32f4xx.h里面找找
PS,又扯远了。回归正传。
看到子函数
Stm32_Clock_Init PROC
EXPORT Stm32_Clock_Init
PUSH {R0-R7,LR}
我们这样写有什么好处呢。
PUSH {R0-R7,LR}可以将之前使用过的通用寄存器压栈,防止之前使用的数据遭到破坏
顺便保存LR指针,LR指针在
ARM指令集下是PC指针地址减4,在
Thumb指令集下是PC指针地址减2
理解为LR是PC的备份吧~~
这样就是把Stm32_Clock_Init作为一个子函数模块来对待,
减少程序的耦合性,
当然,代码耦合性降低的少少代价是牺牲部分性能。各优各劣,还请看官自己把握啦
LDR R0,=RCC_CR
LDR R1,[R0]
ORR R1,R1,#Bit0 ;RCC->CR|=0x00000001;//设置HISON,开启内部高速RC振荡
STR R1,[R0]
这个部分可以作为一个模块来使用。其实,你们看下面的寄存器初始化都是差不多的啦。
ORR是ARM汇编或指令,将R1与0x01进行或运算,就是将BIT0位置1.
如果想像 GPIOA&=~(0x01<<1);这样使用。我们可以用BIC指令。
举个栗子:
BIC R0,R1,#0x02 ;&=~(0x01<<1)将R1的BIT1位置0,并且把R1的数值赋予R0
BIC刚好与ORR相反,他是将位置0的~~
2.2关于MOV的操作界限
[mw_shl_code=applescript,true] LDR R0,=RCC_CR
LDR R1,[R0]
BIC R1,#Bit16 ;RCC->CR&=0xFEF6FFFF;//HSEON清零
BIC R1,#Bit19 ;CSSON清零
BIC R1,#Bit24 ; PLLON清零
STR R1,[R0]
LDR R0,=RCC_PLLCFGR
LDR R1,[R0]
MOV R1,#0x00 ;清空
ORR R1,#0x10 ;RCC->
LLCFGR=0x24003010; //PLLCFGR恢复复位值
ORR R1,#0x3000 ;由于MOV特性,我们把32位数据分拆为0xff 0xff00 0xff0000 0xff000000
ORR R1,#0x24000000
STR R1,[R0] [/mw_shl_code]
当时我用汇编移植原子哥的C语言代码M4系统Stm32_Clock_Init时钟初始化时候,遇到个问题,耽搁了好久
就是MOV R1,#0x24003010编译错误
我一看编译器报错:
..Corecore.s(94): error: A1871E: Immediate 0x24003010 cannot be represented by 0-255 shifted left by 0-23 or duplicated in all, odd or even bytes
百度翻译:不能表示0-255留下的24或复制转移,奇数或偶数字节
什么鬼啊。。。
然后我还是上网查了查,总结了一下,0xff ff ff ff里面不能少于6个0(贫道的歪解哈,别执着)
就是0xff
00 00 00是可以的,可是0xff
f0 00 00就不行。
好吧,
人不能被泡尿憋死。。。
想了想,走曲线救国路线~
用或运算。8位8位这样分成四次或上去。哈哈,感觉无敌了。。。
MOV R1,#0x00 ;把寄存器里面清空先
ORR R1,#0x10 ; RCC->
LLCFGR=0x24003010; //PLLCFGR恢复复位值
ORR R1,#0x3000 ;由于MOV特性,我们把32位数据分拆为0xff 0xff00 0xff0000 0xff000000
ORR R1,#0x24000000
好吧,这种方法用下去,果然,编译不报错啦~~~
对了,不要问我这样做行不行!
男人嘛,怎么可能不行!?
(用仿真看寄存器地址里面的数值是已经改变的,可以咯)
好吧。又解决一个问题~~~~~哈哈
PS:不知道还有没有更好的方法,万望各路看官指点哈~
3。1按键和LED的声明什么的
led的配置没啥好讲的,跟前面差不多。就主要写写按键算啦···
[mw_shl_code=applescript,true]
;-----------------------start-----------------------
;
; 外部调用文件
INCLUDE register.s
;-----------------------end-----------------------
AREA KEY_DATA, DATA, READWRITE
KEY_NUM DCD 0x00 ;DCD划分全局变量
;KEY_NUM SPACE 0x01 ;SPACE是划分数组
;声明为外部可以调用的全局变量
global KEY_NUM
;-----------------------start-----------------------
;
; 配置代码存放区域
AREA KEY_CODE, CODE, READONLY ;与主文件的AREA不同在于CODE和|.text|
;|.text|指程序运行部分
;CODE指代码存放
;-----------------------end-----------------------
IMPORT LED_ON
IMPORT LED_EOR[/mw_shl_code]
看看这里,
KEY_NUM DCD 0x00 ;DCD划分全局变量
DCD相当于定义全局变量,后面跟着的好像是初始化数字大小??
space就是相当于C语言的数组定义了。后面跟着的数据表示长度~
3。2
[mw_shl_code=applescript,true];-----------------------start-----------------------
;
; 按键读取
; IO口 : PA0
; 参数 : none
; 返回值 : none
; 全局 : KEY_NUM
KEY_READ PROC
EXPORT KEY_READ
PUSH {R0-R7, LR}
;清空KEY_NUM数值
LDR R0,=KEY_NUM
LDR R1,[R0]
MOV R1,#0x00
STR R1,[R0]
;;-----------------KEY1----------------
LDR R0,=GPIOA_IDR
LDR R1,[r0]
AND R1,#0x01 ;相当于---> (GPIOA_IDR&0x01)
CMP R1,#0x01 ;判断是否等于1
BLEQ KEY1_TOUCH_AN ;---啊啊啊,之前不知道beq是b和eq的组合
;---bl和eq也能组合使用的~~
;;-----------------KEY2----------------
;----------------EXIT--------------
B KEY_EXIT
;;-----------------KEY1_Loop----------------
KEY1_TOUCH_AN
PUSH {R0-R7, LR}
mov r0,#20 ;延时20ms
bl DELAY_MS ;消抖
KEY1_TOUCH_LOOP ;松手检测
LDR R0,=GPIOA_IDR
LDR R1,[r0]
AND R1,#0x01 ;相当于---> (GPIOA_IDR&0x01)
CMP R1,#0x01 ;判断是否等于0
beq KEY1_TOUCH_LOOP ;while(GPIOA_IDR&0x01)
LDR R0,=KEY_NUM ;相当于---> (KEY_NUM = 0x01)
LDR R1,[R0]
MOV R1,#1
STR R1,[R0]
POP {R0-R7,PC}
;;-----------------KEY2_Loop----------------
;;;;;;;;;;;假设返回
KEY_EXIT
POP {R0-R7, PC}
ENDP
;''''''''''''''''''''''''''代码END'''''''''''''''''''''''''''''
ALIGN
END[/mw_shl_code]
好吧,按键这里比较cao蛋
一来呢。想写成模块化。
而来,想写得浪一点。
然后看起来就长了。
其实,代码是非常容易理解的
我是按照51单片机时候的按键点流水灯写的。我展示下伪代码哈:
[mw_shl_code=applescript,true]int key_num = 0x00;
while(1)
{
delay_ms(10);
key_read();
if(key_num==0x01)
{
led_eor();
}
}
key_read()
{
key_num = 0x00;
if(PA1==0)
{
delay_ms(20);//
while(PA1==0);
key_num = 0x01;
}
}[/mw_shl_code]
为了汇编代码的内敛性,用了二次压栈,
这里要注意的是,栈这东西。
一进一出,进了不出会占用栈空间,或者干扰其他模块的堆栈。
一般不会很严重,但是如果弹栈给PC时候,弹错了,那就很酸爽了。
程序很容易上天的。
好吧。本来还想写写小知识的,文章有字数限制了。。。
搞得我丢了一大截。。。
@原子哥
可以放松点字数限制嘛`
之前也是这么想的,后来磨蹭了一年多,才发现,想深入点搞嵌入式系统。还是得回头学汇编
PS:文中就一处定义参考了别的大神内容。
啊啊啊,懂了,这样做挺巧妙地
哇。好东西,学习一下,我对于汇编做点大一点的项目完全不会
学习了。
一周热门 更多>