电子说
步骤1:材料
对于此项目,您将需要
Raspberry pi 3
七个LEDs
七个220欧姆电阻
一个10k欧姆电阻
一个按钮
您还需要计算机设置程序才能以裸机方式处理raspberry pi 3。查看我以前的指导,以了解如何为使用pi裸机设置环境。
此项目从开始到结束大概需要3-4个小时。
好,我们开始吧!!!!
步骤2:电路
要构建此模块,我们需要一行6个LED分别连接到GPIO 20-25。我们还需要一个连接到GPIO 27的指示灯。该指示灯将向我们指示是否已按下按钮。最后,我们需要连接按钮。按钮的一侧将连接到3.3v,另一侧连接到下拉引脚。我们将使用GPIO 17连接到按钮。 GPIO 17将成为我们的输入引脚。 GPIO 17连接到一个10k欧姆的电阻,该电阻连接到地(GND)。我们这样做是为了确保GPIO 17始终设置为低电平。如果不是,则该引脚有可能在高点和低点之间放弃,从而给我们带来随机的结果。为了避免这种情况,我们可以使用电阻较大的电阻将引脚下拉至0v。
在面包板上设置电路是一个简单的过程。请遵循上面的电路图。将引线的短边连接到GND,将长边连接到220欧姆电阻。对其他5个不同的led重复此操作。这样一来,您总共应该连接六个LED。从一端开始,将第一个LED的正极连接到GPIO 20,然后将下一个引脚连接到GPIO 21,依此类推。..最后一个LED应该连接到GPIO 25。
对于指示灯led连接led的短端连接到GND,长端连接到220 ohm电阻,然后将电阻连接到GPIO 17。
将按钮连接到试验板。我使用的按钮上有四个连接。我们将只使用底部的两个。将一端连接到正极轨,另一端连接到10k欧姆电阻。将GPIO 17连接到10k电阻。按下按钮时,它将GPIO 17连接至将引脚设置为高电平的正极。
将3.3v引脚连接至正极,并将GND引脚连接至负极。
最后将正轨和负轨连接在一起,以便可以使用两侧。
步骤3:代码:简介
编码部分是从上一个闪烁的项目中已经学到的东西建立的。
该项目中的两个新事物是,我们设置了堆栈框架的使用方式,以便我们可以模拟高级功能,并进行设置使用系统时钟等待特定毫秒数的等待函数。
堆栈是一种常见的编程结构。它实际上是一个地址序列,可用于临时存储内容。堆栈具有两个基本功能
您可以将项目推入堆栈顶部
,也可以将项目从堆栈顶部弹出
可以按不同的版本设置堆栈,但是对于该项目,我们将坚持默认配置。当您将某些东西推入堆栈时,它会放在顶部。当下一件物品被推入堆栈时,新物品将成为顶部,而旧物品将位于其下方。希望您以前曾经使用过堆栈,如果没有的话,可以在网上找到更多更深入的解释。幸运的是,Arm有一个push和pop指令,因此使用堆栈很容易组装。
我们将要探索的第二件事是访问系统计时器,这样我们可以将程序延迟一定的时间。
第4步:代码:堆栈/堆栈框架
设置要使用的堆栈就像一行代码一样简单。由于链接器的设置方式(kernel.ld),我们在编译时将所有代码插入0x8000之后。因此,我们需要将此值移到sp寄存器中。
mov sp,#0x8000
将其添加到代码顶部并使用堆栈应该没问题。
现在进入堆栈框架。堆栈框架基本上是当我们预留堆栈的一部分以用于过程调用时。这使我们能够实现高级语言(如Java和C ++)使用的功能。这些语言使用堆栈来跟踪函数调用。我们可以在汇编中做同样的事情。
堆栈框架:
要将函数调用模拟为高级语言,我们将保留寄存器我们想通过将它们放置在堆栈上来使用,然后如果需要将其用于变量,我们还可以在堆栈中预留本地内存。在函数末尾,我们必须破坏堆栈帧并将堆栈重置为以前的状态。
通常,如果函数需要返回任何内容,则应将其放在r0中。/p》
如果函数接受参数,则应将其放在函数分支的前面。
堆栈框架:SETUP
我们通过设置堆栈框架开始该功能。
push {r7,lr}
mov r7,sp
push {}
ldr,[r7,#8] @第一个参数与r7的偏移量为#8
然后我们通过清理堆栈框架来结束堆栈框架
pop
pop {r7,lr}
。此过程使我们无需更改任何寄存器即可调用函数。它还允许在函数内调用函数,因为堆栈框架将始终保留lr,因此允许进行多个级别的函数调用。
=======================================
stack frame visual
=======================================
0x0 | 《=sp
---------------------------------------
0x4 |
---------------------------------------
0x8 |
---------------------------------------
0x12|
---------------------------------------
0x16|
---------------------------------------
0x20|
---------------------------------------
0x24|
---------------------------------------
0x28|
======================================= Example Function with one argument.
_______________________________________ push {r1} @argument r1=0x1234
b ex_func =======================================
stack frame visual
=======================================
0x0 | 0x1234 《=sp The argument is pushed onto the stack
---------------------------------------
0x4 |
---------------------------------------
0x8 |
---------------------------------------
0x12|
---------------------------------------
0x16|
---------------------------------------
0x20|
---------------------------------------
0x24|
---------------------------------------
0x28|
======================================= ex_func:
push {r7,lr}
mov r7,sp @r7 will equal 0x8
=======================================
stack frame visual
=======================================
0x0 | 0x1234 The argument is pushed onto the stack
---------------------------------------
0x4 | lr
---------------------------------------
0x8 | r7 《=sp
---------------------------------------
0x12|
---------------------------------------
0x16|
---------------------------------------
0x20|
---------------------------------------
0x24|
---------------------------------------
0x28|
======================================= push {r1-r3} @using r1,r2,and r3 so we preserve them
=======================================
stack frame visual
=======================================
0x0 | 0x1234 The argument is pushed onto the stack
---------------------------------------
0x4 | lr
---------------------------------------
0x8 | r7
---------------------------------------
0x12| r3 saved register
---------------------------------------
0x16| r2 saved register
---------------------------------------
0x20| r1 《=sp saved register
---------------------------------------
0x24|
---------------------------------------
0x28|
=======================================
pop r1,[r7,#8] @remember r7=0x8, the old sp. To access the argument at 0x0 we need to go up by 8
@thats why we do [r7,#8] which is the same as putting the value at 0x0 (0x1234) into r1 sub sp,sp,#8 @moves sp down to create local memory
=======================================
stack frame visual
=======================================
0x0 | 0x1234 The argument is pushed onto the stack
---------------------------------------
0x4 | lr
---------------------------------------
0x8 | r7
---------------------------------------
0x12| r3 saved register
---------------------------------------
0x16| r2 saved register
---------------------------------------
0x20| r1 saved register
---------------------------------------
0x24| empty
---------------------------------------
0x28| empty 《=sp
======================================= To end the function we need to move the value to be returned if any into ro
步骤5:代码:系统计时器
要使用系统计时器设置延迟,我将其编写在一个单独的文件中,因此也可以在以后的项目中使用。为此,我们基本上需要访问计时器并获取初始时间戳。一旦有了,我们将再次访问时间戳。我们将从最初的时间戳中减去第二个时间戳,并将其与所需的值进行比较。
算法非常简单,最困难的部分是访问正确的寄存器。
计时器的基地址为0x3f003000
计时器的lo字的偏移量为0x4
计时器的高位字的偏移量为0x8
尝试编写自己的等待函数!如果需要参考,请附上我的代码。
步骤6:代码:获取输入
对于该项目,我使用了GPLEV0寄存器。本质上,它保持引脚0-31的状态。我们正在使用GPIO 17作为输入。要将此引脚设置为输入,我们必须访问FSEL1寄存器并清除GPIO 17专用的三位。
GPLEV0偏移量0x34
FSEL1偏移量0x04
通过掩码将GPIO 17设置为输入0xFF1FFFFF
我们还需要将位20-27设置为输出。
FSEL2偏移量0x08
将掩码设置为20到27以输出0x249249
由于我们使用的是输出,我们还需要访问GPSET0寄存器以打开引脚,而GPCLR0寄存器以关闭引脚
GPSET0偏移量0x1c
GPCLR0偏移量0x28
由于等待功能以微秒为单位,因此您可以使用以下几个值
半秒0x7a120 = 500,000微秒= 1/2秒
四分之一秒0x3d090 = 250,000微秒= 1/4秒
秒的八分之一0x1e848 = 125,000微秒= 1/8秒
好,让我们开始编码!
mov r0, @return
上面的代码设置了我们的代码,因此它可以正常运行。
Then we need to undo the stack frame
下一步我设置基址的地址和我们将要使用的偏移量。
Get rid of local memory created
在这里,我设置了一些以后将要使用的有用符号。
add sp,sp,#8
=======================================
stack frame visual
=======================================
0x0 | 0x1234 The argument is pushed onto the stack
---------------------------------------
0x4 | lr
---------------------------------------
0x8 | r7
---------------------------------------
0x12| r3 saved register
---------------------------------------
0x16| r2 saved register
---------------------------------------
0x20| r1 《=sp saved register
---------------------------------------
0x24| empty
---------------------------------------
0x28| empty
=======================================
出于可读性考虑,我为寄存器的目的设置了一些符号。
Replace saved registers
要将此引脚设置为输入,首先要获得适当的偏移,然后再加载掩码;最后,我将掩码写回到寄存器中。
pop {r1-r3}
=======================================
stack frame visual
=======================================
0x0 | 0x1234 The argument is pushed onto the stack
---------------------------------------
0x4 | lr
---------------------------------------
0x8 | r7 《=sp
---------------------------------------
0x12| r3
---------------------------------------
0x16| r2
---------------------------------------
0x20| r1
---------------------------------------
0x24| empty
---------------------------------------
0x28| empty
=======================================
我做的和输入一样,只是使用了不同的偏移量和掩码。
Replace r7 and link register
接下来,我开始主程序循环。我首先描述我要程序执行的操作。
pop {r7,lr}
=======================================
stack frame visual
=======================================
0x0 | 0x1234 The argument is pushed onto the stack
---------------------------------------
0x4 | lr 《=sp
---------------------------------------
0x8 | r7
---------------------------------------
0x12| r3
---------------------------------------
0x16| r2
---------------------------------------
0x20| r1
---------------------------------------
0x24| empty
---------------------------------------
0x28| empty
=======================================
我以一个小的等待值开始循环。
Finally, we need to clean up after the argument
add sp,sp,#4
=======================================
stack frame visual
=======================================
0x0 | 0x1234 《=sp
---------------------------------------
0x4 | lr
---------------------------------------
0x8 | r7
---------------------------------------
0x12| r3
---------------------------------------
0x16| r2
---------------------------------------
0x20| r1
---------------------------------------
0x24| empty
---------------------------------------
0x28| empty
=======================================
mov pc,lr Return
在这里,我正在检查引脚17的状态,该函数将返回r0中指定引脚的状态。我的所有功能都将在末尾列出。
Notice that nothing is overwritten in the stack. We simply move the stack pointer back to it‘s original place.
我检查返回值,然后跳转到一个功能,该功能可以打开指示灯并循环或关闭指示灯
如果按下该按钮,它将GPIO17连接到3.3v,因此将其设置为高电平。因此,如果按下按钮,输入功能将返回1,从而指示器打开并且LED的环路上升,从GPIO20到GPIO 26接通。
基本上就是这样。接下来,我将介绍在input_loop中调用的函数。
步骤7:代码:函数定义
首先,我们有get输入
b main
.section .text
main:
mov sp,#0x8000
我的函数有一个参数,即GPIO引脚的编号。该功能使用引脚号创建一个掩码来测试GPLEV0中的位。 tst执行逻辑“与”并设置标志。如果and返回true,则未设置零标志。
r1:0000_1000
r2:0000_1000
tst r2,r1
结果:未设置零标志
结果返回到r0。
.equ BASE_ADDR,0x3f200000 @Base address
.equ GPFSEL0, 0x0
.equ GPFSEL1, 0x04 @FSEL1 register offset | use to select GPIO 10-19 and set input/output/alt func
.equ GPFSEL2, 0x08 @FSEL2 register offset | use to select GPIO 20-29 and set input/output/alt func
.equ GPSET0, 0x1c @GPSET0 register offset| use to set GPIO’s logic level high(3.3v)
.equ GPCLR0, 0x28 @GPCLR0 register offset| use to set GPIO‘s logic level low(0v)
.equ GPLEV0, 0x34 @GPIO level offset | use to read current level of pin(on/off)[high/low]{3.3v/0v}
接下来,我有两个函数可以打开指示器并关闭指示灯。当指示灯打开时,该功能将分支并通过GPIO引脚循环。当指示灯熄灭时,该功能将通过GPIO引脚分支和向下循环。
.equ CLEAR_BITS21_23,0xFF1FFFFF @mask to clear bits 21 through 23 | use to set GPIO 17 to input
.equ SET_20_27,0x249249 @mask to set bits 20 through 27 | use to set GPIO 20-27 to output
.equ SET_BIT27,0x8000000 @mask to set bits 27 | use to set GPIO 27 to high(3.3v) or low(0v) GPIO 27 is indicator light
.equ half_second, 0x7a120 @hex value for half a second in microseconds
.equ quarter_second, 0x3d090 @hex value for quarter of a second in microseconds
.equ eighth_second,0x1e848 @hex value for eigth of a second in microseconds
前两个功能设置向上或向下循环功能。他们只是确保在循环开始之前将计数器设置为正确的数字。 loop_up和loop_down函数在本质上彼此相同。一个循环通过以GPIO 20开头并以GPIO 26结尾的引脚,然后循环通过以GPIO 26开头并以GPIO 20结尾的引脚。
循环位置i或j移入r0和然后调用turn_on函数。这会根据传递到r0的数字打开一个引脚。然后,循环将等待八分之一秒,然后再关闭引脚并递增或递减计数器。
base .req r1 @Sets symbol base to refer to r1: can use base and r1 interchangeably base《=》r1
ldr base,=BASE_ADDR @base = 0x3f20000, load base with the base address of peripheralsoffset .req r2 @Sets symbol offset to refer to r2: can use offset and r2 interchangeably offset《=》r2
mask .req r3 @Sets symbol mask to refer to r3 mask《=》r3
i .req r4 @Sets symbol i to refer to r4 i《=》r4
j .req r5 @Sets symbol j to refer to r5 j《=》r5
return .req r0 @return 《=》 r0
turn_on和turn_of函数。
步骤8:将它们放在一起。
现在,您已经编写了所有代码文件,您需要生成一个二进制kernel.img。我设置了一个简单的makefile,将其吐出来。只需下载它,然后将第4行的代码变量更改为文件名即可。
如果您无法使代码正常工作,请下载并编译我的代码,然后将kernel.img放到pi上。如果可以,那么如果不返回电路步骤并尝试重建电路,则代码可能存在问题。
全部0条评论
快来发表一下你的评论吧 !