跳转到内容

Mizar32/PIO

来自Wikibooks,开放世界中的开放书籍

PIO 代表可编程输入/输出,这是控制和测量连接到总线连接器的 AVR32 处理器引脚上的数字电压电平的最简单方法。

要将 GPIO 引脚用作 PIO,您必须首先将引脚设置为输入或输出。如果将其设置为输入,则可以检查输入电压以查看其是否具有进入它的低或高值,例如检查开关的位置。如果将其设置为输出,则可以对其进行编程以输出低电压或高电压来控制灯、电机或其他电路。

对于作为输入的 PIO 引脚,您还可以要求当该引脚上的电压从 0 变化到 1 或从 1 变化到 0 时,这将产生一个中断。发生这种情况时,处理器将停止其正在执行的操作,运行一段称为中断例程的特殊代码,并在完成该操作后,它将返回并继续在中断发生时正在执行的操作。

最后,每个引脚都有一个可选的上拉电阻,可以启用它,以便如果没有任何东西连接到作为输入的引脚,它将漂浮到逻辑“1”,而不是随机上下波动。这是连接开关或按钮的常用方法:您在引脚上编程一个上拉电阻,然后将开关连接到引脚和零伏之间,以便当触点闭合时,您将读取 0 值,而当触点断开时,您将读取 1 值。

硬件视图

[编辑 | 编辑源代码]

AT32UC3A 芯片的任何外设引脚都可以作为数字输入读取或设置为输出,并编程为 0 或 1 逻辑电平。

当引脚设置为输出时,0 逻辑输出将引脚连接到 0 伏,而逻辑 1(“高”)值则在其上施加 3.3 伏,两种状态的最大电流供应或汲取均为 4 毫安。

当它们设置为输入时,0.0 到 0.8 伏的电压电平读取为“0”(低)输入,而 2.0 伏到 5.0 伏的电压电平读取为“1”(高)输入。0.8 到 2.0 伏之间的值可能读取为高或低,并且不确定。

Mizar32 的某些引脚只能用作可编程 I/O 引脚,因为它们不用于其他任何用途。其他引脚将信号传输到各种外设,但如果未使用这些设备,则可以将引脚用作 PIO 引脚。

其他引脚对于处理器的正常运行至关重要,例如用于访问 SDRAM、振荡器和其他板载电路的引脚;如果您将这些用作 PIO 引脚,则电路板可能会崩溃并需要按其重置按钮。

专用PIO引脚

[编辑 | 编辑源代码]
引脚 名称 总线引脚 eLua名称 PicoLisp
PA2 GPIO2 BUS5 引脚 11 pio.PA_2 'PA_2
PA7 GPIO7 BUS5 引脚 12 pio.PA_7 'PA_7
PB17 GPIO49 BUS5 引脚 8 pio.PB_17 'PB_17
PB18 GPIO50 BUS5 引脚 9 pio.PB_18 'PB_18
PB29 GPIO61 板载 LED pio.PB_29 'PB_29
PB30 GPIO62 BUS6 引脚 9 pio.PB_30 'PB_30
PB31 GPIO63 BUS6 引脚 10 pio.PB_31 'PB_31
PX16 GPIO88 用户按钮 pio.PX_16 'PX_16
PX19 GPIO85 BUS6 引脚 12 pio.PX_19 'PX_19
PX22 GPIO82 BUS6 引脚 11 pio.PX_22 'PX_22
PX33 GPIO71 BUS5 引脚 10 pio.PX_33 'PX_33

可选PIO引脚

[编辑 | 编辑源代码]

未使用的 ADCPWMSPIUART 引脚也可以用作 PIO 引脚。请参阅这些部分以获取相关的引脚名称。这将可用 PIO 的总数增加到 66 个。

软件视图

[编辑 | 编辑源代码]

PIO Alcor6L 模块允许您将任何引脚设置为逻辑输入或将其设置为输出并在其上放置 0v 或 3.3V,并且您可以在任何引脚上启用上拉电阻。

驱动引脚作为输出

[编辑 | 编辑源代码]

此示例点亮板载 LED。

在 eLua 中

led = pio.PB_29
pio.pin.setdir( pio.OUTPUT, led )
pio.pin.setlow( led )

在 PicoLisp 中

(setq led 'PB_29)
(pio-pin-setdir *pio-output* led)
(pio-pin-setlow led)

请注意,在复位后立即,LED 在第 2 行亮起,因为复位状态是所有引脚都为低电平,并且当信号为低电平时,板载 LED 会亮起。要设置一个初始值为高的输出引脚,您需要在调用 pio.pin.setdir() 之前调用 pio.pin.sethigh()

读取引脚上的电压作为输入 (eLua)

[编辑 | 编辑源代码]

这是一个读取一个 PIO 引脚并根据它驱动另一个引脚的示例。我们将读取连接到板载用户按钮的引脚,并且只要按下它,我们就将使板载 LED 闪烁。

-- Make the Mizar32 on-board LED flicker as long as the user button is held
led = pio.PB_29
button = pio.PX_16

-- A function to give a short delay of 1/10th of a second
function delay()
  tmr.delay( 0, 100000 )
end

-- Main program

-- First, make sure the LED starts in the "off" position
pio.pin.sethigh( led )
-- Now enable input/output pins
pio.pin.setdir( pio.OUTPUT, led )
pio.pin.setdir( pio.INPUT, button )

while true do
  -- If the button is pressed down...
  if pio.pin.getval( button ) == 0 then
    -- ... turn the on-board LED on and off again
    pio.pin.setlow( led )  
    delay()
    pio.pin.sethigh( led )  
    delay()
  end
end

读取引脚上的电压作为输入 (PicoLisp)

[编辑 | 编辑源代码]

在 PicoLisp 中,以下是如何执行此操作。

# A simple program which demonstrates
# the usage of user-buttons.
 
# declare pins
(setq led 'PB_29 button 'PX_16)

# a simple delay function
(de delay (t)
   (tmr-delay 0 t) )

# make sure the LED starts in
# the "off" position and enable
# input/output pins
(de init-pins ()
   (pio-pin-sethigh led)
   (pio-pin-setdir *pio-output* led)
   (pio-pin-setdir *pio-input* button) )

# And now, the main loop
(de prog-loop ()
   (init-pins)
   (loop
      (if (= 0 (pio-pin-getval button))
         (pio-pin-setlow led)
         (delay 100000)
         (pio-pin-sethigh led)
         (delay 100000) ) ) )

(prog-loop)

可编程上拉电阻

[编辑 | 编辑源代码]

用户按钮的电路非常简单:当按下按钮时,它将其 PIO 引脚连接到零伏,提供低输入值,并且在 PIO 引脚和 3.3 伏之间连接了一个电阻,因此如果未按下按钮,PIO 引脚将被轻轻拉高到 3.3V 并读取为高值。

如果一些PIO引脚没有连接任何东西,并且您将它们编程为输入,它们会拾取周围环境的随机噪声,并给出时高时低的值。可编程上拉电阻可以确保,如果没有信号连接到输入引脚,它会轻轻地被拉高到高电平,而不是随机漂浮。

此示例将一个未使用的GPIO引脚转换为PIO输入,但确保如果没有任何物理连接到它,它将始终返回高电平1

如果您从以下代码中删除pio.pin.setpull()行(至少在我的测试板上),它会打印出大部分为零的值,但是如果您触摸Mizar32板的底部,则值会在0和1之间闪烁。包含pio.pin.setpull()行后,输入值始终为1,除非您使用一段电线将引脚连接到GND引脚(例如BUS5引脚14)。

在 eLua 中

pin = pio.PA_2    -- Stabilise GPIO2 (connector BUS5 pin 11)
pio.pin.setdir( pio.INPUT, pin )
pio.pin.setpull( pio.PULLUP, pin )
while true do
  io.write( pio.pin.getval( pin ) )
end

在 PicoLisp 中

(setq pin 'PA_2) # Stabilize GPIO2 (connector BUS5 pin 11)
(pio-pin-setdir *pio-input* pin)
(pio-pin-setpull *pio-pullup* pin)
(loop
   (prinl (pio-pin-getval pin)) )

虽然Alcor6L也有类似的原始PULLDOWN,但Mizar32中使用的AVR32芯片在其硬件中没有可编程下拉电阻,因此使用它会引发错误消息。

要再次禁用上拉电阻,您可以使用

语言 示例
eLua pio.pin.setpull(pin, pio.NOPULL)
PicoLisp (pio-pin-setpull pin *pio-nopull*)

开漏输出

[编辑 | 编辑源代码]

可编程上拉电阻的另一个用途是实现“开漏输出”。在这种方案中,引脚可以处于两种状态之一:要么被驱动为0输出,要么被读取为输入。上拉电阻确保,如果没有人将其驱动为输出,则每个人都会将其读取为高电平。当多台计算机需要通过一根信号线进行通信时,可以使用这种方式,这样任何一台计算机都可以与任何其他计算机通信,而无需主从关系或协商谁控制总线。使用此系统,任何计算机都可以读取线路以查看其值是高还是低,并且任何计算机都可以将线路驱动到低电平,供所有其他计算机读取。这与将线路驱动为高或低输出不同,因为如果一台计算机将其驱动为高电平,而另一台计算机同时将其驱动为低电平,则可能会损坏相关的计算机,并且肯定会导致通信混乱。

一个简单的例子是,以一种可以由多个开关单元中的任何一个激活的方式来打开和关闭房屋照明。相反,I2C总线是一个高级示例,它在其信号线上使用开漏输出,以便I2C总线上的任何一台计算机都可以与任何其他计算机通信,而不会在总线线上导致冲突信号。

以下代码在PIO引脚上实现了开漏输出,提供一个函数将其配置为OC引脚,一个函数将其驱动为低输出,以及一个函数将其设置为输入并告诉您此时线路上的值。

在 eLua 中

-- Turn a PIO pin into an open-collector output.
-- Call this function once before using the other two to drive and read the pin.
function oc_setup( pin )
  -- OC output starts as an input, not driving the bus wire
  pio.pin.setdir( pio.INPUT, pin )
  -- arrange that, when it is an input and no one is driving it, it will float high
  pio.pin.setpull( pio.PULLUP, pin )
  -- and that when we set it as an output, it will drive a low value
  pio.pin.setlow( pin )
end

-- Drive a low output value onto the pin.
-- The low output value is already programmed during setup()
-- so we only need to enable it as an output.
function oc_drive_low( pin )
  pio.pin.setdir( pio.OUTPUT, pin )
end

-- Make the pin an input and return the value on the bus wire
function oc_read( pin )
  pio.pin.setdir( pio.INPUT, pin )
  return pio.pin.getval( pin )
end

在 PicoLisp 中

# Turn a PIO pin into an open-collector output.
# Call this function once before using the other
# two to drive and read the pin.

(de oc-setup (pin)
  # OC output starts as an input, not driving
  # the bus wire
  (pio-pin-setdir *pio-input* pin)
  # Arrange that, when it is an input and no
  # one is driving it, it will float high
  (pio-pin-setpull *pio-pullup* pin)
  # and that when we set it as an output, it will drive a low value
  (pio-pin-setlow pin) )

# Drive a low output value onto the pin.
# The low output value is already programmed during setup()
# so we only need to enable it as an output.
(de oc-drive-low (pin)
  (pio-pin-setdir *pio-output* pin) )

# Make the pin an input and return the value on the bus wire
(de oc-read (pin)
  (pio-pin-setdir *pio-input* pin)
  (pio-pin-getval pin) )

请注意:PicoLisp目前不支持中断处理。请参阅问题 #12。但是,您可以在eLua中使用中断。

您可以安排在GPIO引脚的逻辑电平发生变化时调用您自己的Lua函数,而无需一直检查引脚。

您可以要求在GPIO引脚的逻辑电平从0变为1时调用您的中断函数,从1变为0时调用,或者两者都调用。

以下示例创建一个中断函数,并安排在每次按下用户按钮时调用它。用户按钮的电路设计使得当按钮未按下时其GPIO引脚为高电平,按下时为低电平,因此我们要求在引脚的逻辑电平从高电平变为低电平时调用我们的中断例程。

-- Example program to show the use of Lua interrupts on PIO edges.
-- This flashes the on-board LED every time the user button is depressed.

-- Which PIO pins are the button and the LED connected to?
button = pio.PX_16
led    = pio.PB_29

function when_pressed( resnum )
  -- flash the onboard LED
  pio.pin.setlow(led)
  for i=1,10000 do end  -- for about 1/100th of a second
  pio.pin.sethigh(led)
end

-- Enable the LED as an output, starting in the "off" state.
-- and the button as an input. 
pio.pin.sethigh( led )    -- off
pio.pin.setdir( pio.OUTPUT, led )
pio.pin.setdir( pio.INPUT, button )

-- Set our interrupt handing function and make it sensitive to a
-- change from high to low of the PIO pin connected to the user button.
cpu.set_int_handler( cpu.INT_GPIO_NEGEDGE, when_pressed )
cpu.sei( cpu.INT_GPIO_NEGEDGE, button )

-- Do something for about ten seconds while the test runs 
for i=1,10000000 do end

-- disable the interrupt
cpu.cli(  cpu.INT_GPIO_NEGEDGE, button )
-- and remove our interrupt handler function.
cpu.set_int_handler( cpu.INT_GPIO_NEGEDGE, nil )

任何GPIO引脚都可以设置为对基于边沿的中断做出反应,无论它们是输入、输出还是具有完全不同的功能。

您可以为引脚启用正边沿中断功能、负边沿中断功能或两者。如果启用两者,则可以使两种中断都由同一个函数处理,也可以由两个不同的函数处理。

解码引脚编号

[编辑 | 编辑源代码]

诸如pio.PB_29之类的函数返回在eLua内部用于识别GPIO引脚的数字。除了在一种情况下,您不需要了解这些数字的值:当您同时在不同的GPIO引脚上启用了多个边沿触发的中断时。

您只能告诉cpu.set_int_handler()为所有正边沿中断调用单个函数,以及为所有负边沿中断调用另一个函数(或同一个函数),但是您可以通过检查传递给您的中断处理函数的参数来找出哪个GPIO引脚导致了中断,在上面的代码示例中,我们将其称为resnum

这是“资源编号”,eLua用于表示引脚的内部编号,它与pio.PX_16等返回的值相同。

因此,您可以在中断函数中说:

if resnum == button ...

或者

if resnum == pio.PA_3 ...

但是,如果您启用了许多引脚中断并希望将其解码为端口号和引脚号,则可以使用

port, pin = pio.decode( resnum )

对于端口A上的引脚,port将为0pin将从0到31。
对于端口B上的引脚,port将为1pin将从0到31。
对于端口C上的引脚,port将为2pin将从0到5。
对于端口X上的引脚,port将为23pin将从0到39。

进一步阅读

[编辑 | 编辑源代码]
华夏公益教科书