汇编修炼之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时候,弹错了,那就很酸爽了。
程序很容易上天的。


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




友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。