4. GPIO引脚

最简单的事是点亮LED,墨星stm32上没有内置LED,所以,我们需要用杜邦线外接一个LEDs。用USB线与板连接,并按照之前的教程。

此教程中我们将使用引脚 P7 。将LED的正极连接到A1(即较长脚),负极接到GND。

../../_images/moxing-led.jpg

我们会通过在REPL中点亮LED,输入以下内容:

>>> from pyb import Pin
>>> myled = Pin("A1",Pin.OUT_PP)
>>> myled.value(1)
>>> myled.value(0)

这些指令会打开和关闭LED。

这些步骤非常顺畅,但是我们仍希望这一过程实现自动化。在板上用您最喜欢的文本编辑器打开MAIN.PY文件。 将以下行写入或粘贴到文件中。若您对python尚不熟悉,请确保您没有在缩进上出错,因为这很重要!:

from pyb import Pin
myled = Pin("A1",Pin.OUT_PP)
while True:
    myled.value(1)
    pyb.delay(1000)
    myled.value(0)
    pyb.delay(1000)

使用软复位(点击CTRL-D)自动运行脚本。 墨星stm32将会重新开始,您应看到绿色灯持续闪烁。成功,建立邪恶机器人军队大业的第一步已经完成了! 若您厌倦了恼人的闪光,在您的终端上点击CTRL-C即可停止其运行。

这个代码有何用?首先我们需要一些术语。Python是一种面向对象的语言,python中几乎所有内容都是 , 当您创建某一类的实例时,即得到一个 对象 。每一类都有与之相关的 方法 。方法(又称成员函数)是用于与对象交互或控制对象。

第一行代码我们从pyb模块引入了Pin类,第二行创建一个我们称为myled的对象。我们创建对象时,它需要两个参数,第一个对应板上的引脚,第二个对应模式。 pyb.Pin类有3个我们将会用到的重要的成员函数:value()。我们用到的另外一个函数是pyb.delay()。 这需要等待给定时长(以毫秒为单位)。我们创建LED对象后,其语句为True:创建一个在开关之间切换的无限循环,并等待1秒钟。

练习:尝试更改LED切换的时间并打开另外一个LED。

练习:直接用REPL连接到墨星stm32,创建一个pyb.Pin对象,并使用.value(1)方法置高电平。

4.1. 呼吸灯LED

目前为止,我们仅使用了单色LED,而实际上LED可以控制亮和暗。我们首先为每个LED创建一个对象,这样就能实现对LED的控制。我们通过创建一个LED的列表完成这一步骤。:

from pyb import Pin, Timer

p = Pin('A1') # P1 has TIM1, CH2N
tim = Timer(2, freq=1000)
led = tim.channel(2, Timer.PWM, pin=p)
led.pulse_width_percent(50)

这个最后一行代码,调用了pulse_width_percent这个函数,它的参数,是调整亮度的百分比,如果是100,就是全亮;如果是0,那就是全灭;如果是50,就是亮度一半。

下一步,我们将建立一个无限循环,通过循环,控制LED的亮度。

while True:
  for i in range(100):
    led.pulse_width_percent(i)
    pyb.delay(10)

在这里,我们写了一个循环,i会从0一直增长到99,一共100次循环,然后,led的亮度会从0一直增长到100,也就是有灭到亮。

4.2. 按键

此教程中我们将使用引脚 A2 。将按键的一端连接到A2,负极接到GND。

../../_images/moxing-button.jpg

代码:

>>> from pyb import Pin
>>> button = Pin("A2", Pin.IN, Pin.PULL_UP)
>>> button.value()
1

这里,我们创建了一个button对象,每次调用.value()方法时,就会把引脚的值返回,当不按下的时候,显示的是1,当按下的时候,显示的就是0

4.3. 中断

开关是一个非常简单的对象,但其有一个高级特性: irq 函数。 回调函数设置在按下开关时运行的内容,并使用中断。 建议您在了解中断运行前,先从使用示例开始。尝试在提示符中运行以下指令:

>>> button.irq(trigger=Pin.IRQ_FALLING, handler=lambda t:print('press!'))

这一指令要求在每次按下开关时打印 press! 。继续进行并尝试:按下USR开关并查看电脑上的输出。 注意:打印将中断您正在输入的内容,且该打印是异步运行的中断例程的一个示例。

您可将任何函数(需要t参数)传递给开关回调。上面我们使用Python的 lambda 特性创建了一个匿名函数。但是我们也可以使用:

>>> def f(t):
...   lambda t:print('press!')
...
>>> button.irq(trigger=Pin.IRQ_FALLING, handler=f)

这创建了一个名为 f 的函数,并将此函数赋值给开关回调。 当您的函数比 lambda 所允许的更复杂时,您可以以这种方式进行。

注意:您的回调函数不能分配任何内存(例如:他们无法创建一个元组或列表)。 回调函数应相对简单。若您需要制作一个列表,请预先创建并将其存储在全局变量中(或使其成为本地并将其关闭)。 若您需要进行较长且复杂的运算,则使用回调设置一个flag,其他代码会对其应答。

4.4. 中断的技术细节

我们来理顺与开关回调相关的细节。当您使用 button.irq 记录一个函数时,开关则在开关连接的引脚上设置一个外部中断触发(下降沿)。 这也就意味着微控制器会监听引脚的任何变化,且以下情况将会出现:

  1. 按下开关时,引脚就发生了变化(引脚从低到高),且微控制器会记录这一变化。
  2. 微控制器结束执行当前机器指令,停止执行,并保存当前状态(将寄存器推到堆栈上)。这将产生的影响是暂停编码,例如您正在运行的Python脚本。
  3. 微控制器开始执行与开关的外部触发相关的特殊中断处理程序。此中断处理程序获取您使用 sw.callback() 记录的函数并执行。
  4. 您的回调函数始终执行,直至完成为止,并将控制返还给开关中断处理程序。
  5. 开关中断处理程序返回,微控制器被告知中断已被处理。
  6. 微控制器恢复在步骤2中保存的状态。
  7. 执行在开始时运行的代码。除暂停外,这一代码不会注意到已被中断。

多个中断同时进行时,以上步骤的进行则会略有复杂。在这种情况下,具有最高优先级的中断首先发生,然后其他按优先级顺序排列。开关中断设置在最低优先级。

4.5. 扩展阅读

使用硬件中断的更多信息,请参见 writing interrupt handlers.