汇编修炼之STM32F407探索者-按键控制LED

2019-07-20 13:05发布

本文是个人随笔,可能有点前言不搭后语。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时候,弹错了,那就很酸爽了。
程序很容易上天的。


好吧。本来还想写写小知识的,文章有字数限制了。。。
搞得我丢了一大截。。。
@原子哥
可以放松点字数限制嘛`




友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。
该问题目前已经被作者或者管理员关闭, 无法添加新回复
6条回答
NULLFF
1楼-- · 2019-07-20 16:25
 精彩回答 2  元偷偷看……
liuchang
2楼-- · 2019-07-20 21:20
liuchang 发表于 2017-7-25 17:23
如果完全是自己写的, 还是很牛叉的...

我对自己的要求是: 能看懂简单的汇编即可(调试的时候可能需要),  ...

之前也是这么想的,后来磨蹭了一年多,才发现,想深入点搞嵌入式系统。还是得回头学汇编
PS:文中就一处定义参考了别的大神内容。
panjunming9
3楼-- · 2019-07-21 00:27
NULLFF 发表于 2017-7-25 20:19
如果这样写定义:
[mw_shl_code=asm,true]RCC               EQU        0x40021000
;RCC registers base ...

啊啊啊,懂了,这样做挺巧妙地
NULLFF
4楼-- · 2019-07-21 02:19
 精彩回答 2  元偷偷看……
NULLFF
5楼-- · 2019-07-21 03:06
NULLFF 发表于 2017-7-25 20:29
这就是为什么LDR,STR指令可以支持偏移寻址的原因了。
当然,我也是初学者,这只是己见,也不完全对,供LZ ...

哇。好东西,学习一下,我对于汇编做点大一点的项目完全不会
NULLFF
6楼-- · 2019-07-21 06:50
NULLFF 发表于 2017-7-26 08:59
好吧,再罗说一句,汇编的意义是将同样功能的代码以高的效率和低的空间占用实现出来,或是做一些高级语言做 ...

学习了。

一周热门 更多>