如何组合一排LED并使它们闪烁序列

电子说

1.3w人已加入

描述

步骤1:材料

对于此项目,您将需要

Raspberry pi 3

七个LEDs

七个220欧姆电阻

一个10k欧姆电阻

一个按钮

您还需要计算机设置程序才能以裸机方式处理raspberry pi 3。查看我以前的指导,以了解如何为使用pi裸机设置环境。

此项目从开始到结束大概需要3-4个小时。

好,我们开始吧!!!!

步骤2:电路

led

要构建此模块,我们需要一行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:代码:简介

led

编码部分是从上一个闪烁的项目中已经学到的东西建立的。

该项目中的两个新事物是,我们设置了堆栈框架的使用方式,以便我们可以模拟高级功能,并进行设置使用系统时钟等待特定毫秒数的等待函数。

堆栈是一种常见的编程结构。它实际上是一个地址序列,可用于临时存储内容。堆栈具有两个基本功能

您可以将项目推入堆栈顶部

,也可以将项目从堆栈顶部弹出

可以按不同的版本设置堆栈,但是对于该项目,我们将坚持默认配置。当您将某些东西推入堆栈时,它会放在顶部。当下一件物品被推入堆栈时,新物品将成为顶部,而旧物品将位于其下方。希望您以前曾经使用过堆栈,如果没有的话,可以在网上找到更多更深入的解释。幸运的是,Arm有一个push和pop指令,因此使用堆栈很容易组装。

我们将要探索的第二件事是访问系统计时器,这样我们可以将程序延迟一定的时间。

第4步:代码:堆栈/堆栈框架

led

设置要使用的堆栈就像一行代码一样简单。由于链接器的设置方式(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:代码:系统计时器

led

要使用系统计时器设置延迟,我将其编写在一个单独的文件中,因此也可以在以后的项目中使用。为此,我们基本上需要访问计时器并获取初始时间戳。一旦有了,我们将再次访问时间戳。我们将从最初的时间戳中减去第二个时间戳,并将其与所需的值进行比较。

算法非常简单,最困难的部分是访问正确的寄存器。

计时器的基地址为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上。如果可以,那么如果不返回电路步骤并尝试重建电路,则代码可能存在问题。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分