书接上文
还是从STM32对比过来进行学习。C语言类比到汇编到底要怎么转换代码思路。
STM32指令
在C语言中,对于代码操作,我们只需要关心变量就可以了,对我们来说,变量都在在RAM中,不需要知道寄存器地址。
例如
int a,b;
a = b;
这是典型的C语言写法,把b的值赋给a,但是在汇编语言中,情况就变得不一样了,需要对寄存器进行操作。
汇编指令
我先写一下汇编中的实现代码,然后在进行分析讲解
假设a的地址为0x20,b的地址为0x30
LDR R0,=0x30
LDR R1,[R0]
LDR R0,=0x20
STR R1,[R0]
上面的一段汇编指令,主要用到了LDR和STR
然后来介绍这两个指令
LDR指令
LDR主要用于从存储加载数据到寄存器 Rx中, LDR也可以将一个立即数加载到寄存器 Rx中, LDR加载立即数的时候要使用“ “=”,而不是 “#”。在嵌入式开发中 LDR最常用的就是读取 CPU的寄存器值,比如 I.MX6UL有个寄存器 GPIO1_GDIR,其地址为 0X0209C004,我们现在要读取这个寄存器中的数据,示例代码如下:
LDR R0, =0X0209C004 @将寄存器地址0X0209C004加载到R0中,即R0=0X0209C004
LDR R1, [R0] @读取地址0X0209C004中的数据到R1寄存器中
上述代码就是读取寄存器 GPIO1_GDIR中的值,读取到的寄存器值保存在 R1寄存器中,
上面代码中 offset是 0,也就是没有用到 offset。
STR指令
LDR是从存储器读取数据, STR就是将数据写入到存储器中,同样以 I.MX6UL寄存器
GPIO1_GDIR为例,现在我们要配置寄存器 GPIO1_GDIR的值为 0X2000002,示例代码如下
LDR R0, =0X0209C004 @将寄存器地址0X0209C004加载到R0中,即R0=0X0209C004
LDR R1, =0X20000002 @R1保存要写入到寄存器的值,即R1=0X20000002
STR R1, [R0] @将R1中的值写入到R0中所保存的地址中
LDR和 STR都是按照字进行读取和写入的,也就是操作的 32位数据,如果要按照字节、
半字进行操作的话可以在指令“ LDR”后面加上 B或 H,比如按字节操作的指令就是 LDRB和
STRB,按半字操作的指令就是 LDRH和 STRH。
对于我们刚才的代码,现在就可以很清楚的知道
LDR R0,=0x30
LDR R1,[R0]
LDR R0,=0x20
STR R1,[R0]
R0首先得到b的地址,现在R0 = 0x30,通过间接寻址符号[],[R0]表示0x30地址的值,也就是b的值,存储到R1中,现在R1 等于 b的值
然后R0得到a的地址,现在a 等于 [R0],通过STR指令,将数据写入到存储器,也就是R1的数据写入到[R0]中,R1存储的是b的值,也就完成了b到a的赋值操作。
解释一下[],这个中括号的问题,这个括号有什么意义?
[ ]表示是间接寻址,bx和[bx]的区别是,前者操作数就是bx中存放的数,后者操作数是以bx中存放的数为地址的单元中的数。
比如bx中存放的数是40F6H
40F6H、40F7H两个单元中存放的数是22H、23H,则
mov ax,[bx]; @2223H传送到ax中
mov ax,bx; @40F6H传送到ax中
[bx]把bx存放的数当作地址,找到地址40F6H,传送了地址指向的两个字节22H、23H
编写驱动
有了上面的简单认识,现在就可以在VScode上面敲写代码了。
新建一个“.s”格式的文件,我的文件名叫做“GPIOs.s”还另存为了一个“GPIOS”的工作区。
使能时钟
首先是时钟的初始化,在
中找到18.6.23这一节,可以看到
这里是配置时钟的寄存器,我们要把全部的时钟都使能,需要找到CCM_CCGR0寄存器的地址,往下拉,在这个位置:
Address: 20C_4000h base + 68h offset = 20C_4068h
这里有一个地方,地址 20c4068h 最后的 h 是表示16进制,所以地址在使用时候要写成
020c4068
所以,我们在代码中就可以这样来把CCGR寄存器全部置1
ldr r0, =0x020c4068 @CCGR0
ldr r1, =0xffffffff @要向CCGR0写入的数据
str r1, [r0] @将0xffffffff写入到CCGR0
ldr r0, =0x020c406c @CCGR1
str r1, [r0] @将0xffffffff写入到CCGR1
ldr r0, =0x020c4070 @CCGR2
str r1, [r0] @将0xffffffff写入到CCGR2
ldr r0, =0x020c4074 @CCGR3
str r1, [r0] @将0xffffffff写入到CCGR3
ldr r0, =0x020c4078 @CCGR4
str r1, [r0] @将0xffffffff写入到CCGR4
ldr r0, =0x020c407c @CCGR5
str r1, [r0] @将0xffffffff写入到CCGR5
ldr r0, =0x020c4080 @CCGR6
str r1, [r0] @将0xffffffff写入到CCGR6
IO复用 配置
与时钟的配置思路相同,先找到对应寄存器的地址,然后对相应的位置进行赋值操作。
在
找到36.4.135 的IOMUXC_SW_MUX_CTL_PAD_GPIO03的寄存器地址
Address: 20E_0000h base + 22Ch offset = 20E_022Ch
对于模式的选择,可以看到我们需要的GPIO1_IO03是101 = 5,也就是给寄存器地址20e022ch赋值0x00000101就可以了。
/*IO复用 配置GPIO1_IO03 PIN的复用为GPIO,也就是设置寄存器
*IOMUXC_SW_MUX_CTL_PAD_GPIO03 = 5
*IOMUXC_SW_MUX_CTL_PAD_GPIO03 地址为0x20e022ch
*/
ldr r0, =0x020e022c @IOMUXC_SW_MUX_CTL_PAD_GPIO03
ldr r1, =0x5 @要向IOMUXC_SW_MUX_CTL_PAD_GPIO03写入的数据
str r1, [r0] @将0x00000101 = 5写入到IOMUXC_SW_MUX_CTL_PAD_GPIO03
电气属性 配置
配置GPIO1_IO03的电气属性,依旧是
在
找到36.4.379 的IOMUXC_SW_PAD_CTL_PAD_GPIO03寄存器位置
电气参数配置从下往上看
/* 配置GPIO01_IO3的电气属性 也就是寄存器:
* IOMUXC_SW_PAD_CTL_PAD_GPIO03
* IOMUXC_SW_MUX_CTL_PAD_GPIO03 寄存器地址为0x20e05fch
*
* bit0: 0 低速率
* bit5:3 110 R0/6驱动能力
* bit7:6 10 100MHz速度
* bit11: 0 关闭开路输出
* bit12: 1 使能pull/kepper
* bit15:14: 00 默认100K下拉
* bit16: 0 关闭hys
*/
ldr r0, =0x020e05fc
ldr r1, =0x0x10b0
str r1, [r0] @将0x0x10b0写入到IOMUXC_SW_PAD_CTL_PAD_GPIO03
地址怎么计算呢,拿出微软自带的计算器,切换到程序员模式,手动点就可以了,恩,没错,就是辣么机智。
配置GPIO功能
在
找到GPIO章节地址在28.5 GPIO Memory Map/Register Definition中找到
GPIO1_GDIR寄存器地址为209c004
GPIOx_GDIR用来配置接口是输入还是输出,GPIO1_IO03在第三个位置,0-31位的GPIOx也就是00000000000000000000000001000,对应到HEX,也就是8
所以寄存器的值为0x000000008 = 0x8
/* 设置GPIO
* 设置GPIO1_GDIR寄存器,设置GPIO1_GPIO03为输出
* GPIO1_GDIR寄存器地址为209c004,设置GPIO1_GDIR寄存器bit3为1
* 也就是设置GPIO1_IO03为输出
*/
ldr r0, = 0x0209c004
ldr r1, = 0x8
str r1, [r0]
下面设置GPIO1_DR,也是先找到地址,DR就可以配置高低电平了
/* 设置GPIO1_IO03 为1
* GPIO1_DR寄存器地址为0x0209c000
*/
ldr r0, = 0x0209c00
ldr r1, 1
str r1, [r0]
驱动代码
.global _start @全局标号
_start:
/* 使能所有外设时钟 */
ldr r0, = 0x020c4068 @CCGR0
ldr r1, = 0xffffffff @要向CCGR0写入的数据
str r1, [r0] @将0xffffffff写入到CCGR0
ldr r0, = 0x020c406c @CCGR1
str r1, [r0] @将0xffffffff写入到CCGR1
ldr r0, = 0x020c4070 @CCGR2
str r1, [r0] @将0xffffffff写入到CCGR2
ldr r0, = 0x020c4074 @CCGR3
str r1, [r0] @将0xffffffff写入到CCGR3
ldr r0, = 0x020c4078 @CCGR4
str r1, [r0] @将0xffffffff写入到CCGR4
ldr r0, = 0x020c407c @CCGR5
str r1, [r0] @将0xffffffff写入到CCGR5
ldr r0, = 0x020c4080 @CCGR6
str r1, [r0] @将0xffffffff写入到CCGR6
/* IO复用 配置GPIO1_IO03 PIN的复用为GPIO,也就是设置寄存器
* IOMUXC_SW_MUX_CTL_PAD_GPIO03 = 5
* IOMUXC_SW_MUX_CTL_PAD_GPIO03 地址为0x20e022ch
*/
ldr r0, = 0x020e022c @IOMUXC_SW_MUX_CTL_PAD_GPIO03
ldr r1, = 0x5 @要向IOMUXC_SW_MUX_CTL_PAD_GPIO03写入的数据
str r1, [r0] @将0x00000101 = 5写入到IOMUXC_SW_MUX_CTL_PAD_GPIO03
/* 配置GPIO01_IO3的电气属性 也就是寄存器:
* IOMUXC_SW_PAD_CTL_PAD_GPIO03
* IOMUXC_SW_MUX_CTL_PAD_GPIO03 寄存器地址为0x20e05fch
*
* bit0: 0 低速率
* bit5:3 110 R0/6驱动能力
* bit7:6 10 100MHz速度
* bit11: 0 关闭开路输出
* bit12: 1 使能pull/kepper
* bit15:14: 00 默认100K下拉
* bit16: 0 关闭hys
*/
ldr r0, = 0x020e05fc
ldr r1, = 0x10b0
str r1, [r0] @将0x0x10b0写入到IOMUXC_SW_PAD_CTL_PAD_GPIO03
/* 设置GPIO
* 设置GPIO1_GDIR寄存器,设置GPIO1_GPIO03为输出
* GPIO1_GDIR寄存器地址为209c004,设置GPIO1_GDIR寄存器bit3为1
* 也就是设置GPIO1_IO03为输出
*/
ldr r0, = 0x0209c004
ldr r1, = 0x8
str r1, [r0]
/* 设置GPIO1_IO03 为1
* GPIO1_DR寄存器地址为0x0209c000
*/
ldr r0, = 0x0209c000
ldr r1, = 0x1
str r1, [r0]
loop: @b loop 在73 74行死循环
b loop
汇编驱动编写好了,下一步就是编译了,下篇文章见。