Mizar32/PIO
Introduction
[edit | edit source]PIO means Programmable Input/Output and this is the simplest way of controlling and measuring digital voltage levels on the pins of the AVR32 processor pins connected to the bus connectors.
To use a GPIO pin as a PIO, you must first set the pin to be an input or an output. If you set it as an input, you can then check the input voltage to see whether it has a low or a high value coming into it, for example to check the position of a switch. If you set it to be an output, you can program it to output a low voltage or a high voltage to control lights, motors or other circuits.
For PIO pins that are inputs, you can also ask that, when the voltage on that pin changes from 0 to 1 or from 1 to 0, this will generate an interrupt. When this happens, the processor will stop what it is doing, run a special piece of code called an interrupt routine, and when it has finished doing that it will go back and continue what it was doing when the interrupt happened.
Lastly, each pin has an optional pull-up resistor that can be enabled so that, if nothing is connected to a pin that is an input, it will float up to logic "1" instead of waving up and down at random. This is the usual way to connect switches or pushbuttons: you program a pull-up resistor on the pin, then connect the switch between the pin and zero volts so that when contact is closed you will read a value of 0, and when the contact is open you will read a value of 1.
Hardware view
[edit | edit source]Any of the AT32UC3A chip's peripheral pins can be read as a digital input or set as an output and programmed to 0 or 1 logic level.
When a pin is set to be an output, a 0 logic output connects the pin to 0 volts, while a logic 1 ("high") value puts 3.3 volts on it with a maximum current supply or drain of 4 milliamperes for both states.
When they are set to be inputs, a voltage level from 0.0 to 0.8 volts reads as a "0" (low) input, and a voltage level from 2.0 volts to 5.0 volts reads as a "1" (high) input. Values from 0.8 to 2.0 volts may read as high or low and are not certain.
Some pins of the Mizar32 can only be used as programmable I/O pins because they are not used for anything else. Others carry signals to various peripheral devices but, if those devices are not being used, the pins can be used as PIO pins instead.
Other pins are critical to the correct functioning of the processor, for example those used to access the SDRAM, oscillators and other on-board circuitry; if you use those as PIO pins the board will probably crash and need its reset button pressing.
Dedicated PIO pins
[edit | edit source]Pin | Name | Bus pin | eLua name | PicoLisp |
---|---|---|---|---|
PA2 | GPIO2 | BUS5 pin 11 | pio.PA_2 |
'PA_2
|
PA7 | GPIO7 | BUS5 pin 12 | pio.PA_7 |
'PA_7
|
PB17 | GPIO49 | BUS5 pin 8 | pio.PB_17 |
'PB_17
|
PB18 | GPIO50 | BUS5 pin 9 | pio.PB_18 |
'PB_18
|
PB29 | GPIO61 | On-board LED | pio.PB_29 |
'PB_29
|
PB30 | GPIO62 | BUS6 pin 9 | pio.PB_30 |
'PB_30
|
PB31 | GPIO63 | BUS6 pin 10 | pio.PB_31 |
'PB_31
|
PX16 | GPIO88 | User button | pio.PX_16 |
'PX_16
|
PX19 | GPIO85 | BUS6 pin 12 | pio.PX_19 |
'PX_19
|
PX22 | GPIO82 | BUS6 pin 11 | pio.PX_22 |
'PX_22
|
PX33 | GPIO71 | BUS5 pin 10 | pio.PX_33 |
'PX_33
|
Optional PIO pins
[edit | edit source]Unused ADC, PWM, SPI or UART pins can also be used as PIO pins. See those sections for the relevant pin names. This brings the total number of usable PIOs to 66.
Software view
[edit | edit source]The PIO
Alcor6L module allows you to set any pin as a logic input or to set it as an output and put 0v or 3.3V on it and you can enable a pull-up resistor on any pin.
Driving a pin as an output
[edit | edit source]This example lights the on-board LED.
In eLua:
led = pio.PB_29 pio.pin.setdir( pio.OUTPUT, led ) pio.pin.setlow( led )
In PicoLisp:
(setq led 'PB_29) (pio-pin-setdir *pio-output* led) (pio-pin-setlow led)
Note that, straight after a reset, the LED lights up at line 2 because the reset state is that all pins are low and the onboard LED lights when the signal is low. To set a pin as an output whose value starts high, you need to call pio.pin.sethigh()
before calling pio.pin.setdir()
.
Reading the voltage on a pin as an input (eLua)
[edit | edit source]Here is an example of reading one PIO pin and driving another in response to it. We will read the pin connected to the onboard user button and, as long as it is pressed down, we will make the on-board LED flicker.
-- 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
Reading the voltage on a pin as an input (PicoLisp)
[edit | edit source]In PicoLisp, here is how to do it.
# 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)
Programmable pull-up resistors
[edit | edit source]The user button's electrical circuit is quite simple: when the button is pressed, it connects its PIO pin to zero volts, giving a low input value, and there is a resistor connected between the PIO pin and 3.3 volts so that if the button is not pressed the PIO pin is gently pulled up to 3.3V and reads as a high value.
Other PIO pins that are not connected to anything, if you program them as inputs, will pick up random noise from the surrounding environment and give values that are sometimes high and sometimes low. The programmable pull-up resistors are a way to ensure that, if no signal is connected to an input pin, it will gently be pulled up to a high value instead of floating randomly.
This example turns one of the unused GPIO pins into a PIO input, but ensures that, if nothing is physically connected to it, it will always return a high value of 1
.
If you remove the pio.pin.setpull()
line from the following code (on my test board, at least), it prints mostly zeroes but if you touch the underside of the Mizar32 board the value flickers between 0 and 1. With the pio.pin.setpull()
line included, the input value is always 1 unless you connect the pin it to a GND pin (e.g. BUS5 pin 14) with a piece of wire.
In 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
In 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)) )
Although Alcor6L also has a similar primitive PULLDOWN
, the AVR32 chip used in the Mizar32 does not have programmable pull-down resistors in its hardware, so using this will provoke an error message.
To disable the pull-up resistor again, you use
Language | Example |
---|---|
eLua | pio.pin.setpull(pin, pio.NOPULL)
|
PicoLisp | (pio-pin-setpull pin *pio-nopull*)
|
Open-collector outputs
[edit | edit source]Another use for the programmable pull-up resistors is to implement "open-collector outputs". In this scheme of things, a pin can be in one of two states: either it is being driven as a 0 output or it being read as an input. The pull-up resistor ensures that, if no one is driving it as an output, everyone will read it as a high value. This is used when several computers need to communicate over a single signal wire in such a way that any computer can talk with any other one without needing a master-slave relationship or a way to negotiate who is controlling the bus. Using this system, any computer can read the wire to see if its value is high or low, and any computer can drive the wire to a low value, to be read by all the others. This is different from driving a wire as a high or low output because if one computer is driving it high and another is driving it low at the same time, that could damage the computers in question and would certainly result in garbled communication.
A simple example would be a system to turn a house light on and off in a way that can be activated by any one of several switch units. The I2C bus, instead, is an advanced example that uses open-collector outputs on its signal wires so that any one of the computers on an I2C bus to talk with any other one without ever causing conflicting signals on the bus wires.
The following code implements an open-collector output on a PIO pin, giving one function to configure it as an OC pin, one function to drive it as a low output and one to set it as an input and tell you what value is on the wire at the moment:
In 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
In 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) )
Interrupts
[edit | edit source]Please note: There is currently no support for interrupt handling in PicoLisp. See issue #12. You can however use interrupts in eLua.
You can arrange for your own Lua function to be called whenever the logic level changes on a GPIO pin, without having to keep checking the pin all the time.
You can ask for your interrupt function to be called either when the logic level on a GPIO pin goes from 0
to 1
, when it goes from 1
to 0
or both.
The following example creates an interrupt function and arranges for it to be called every time the user button is pressed. The circuitry of the user button is such that its GPIO pin is high when the button is not pressed and low when it is pressed, so we ask for our interrupt routine to be called when the logic level of the pin goes from high to low.
-- 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 )
Any GPIO pin can be made to react to edge-based interrupts, whether they are inputs, outputs or have some completely different function.
You can enable the positive edge interrupt function for a pin, the negative edge interrupt function, or both. If you enable both, you can either make both kinds of interrupt be handled by the same function, or by two different functions.
Decoding pin numbers
[edit | edit source]Functions like pio.PB_29
return numbers that are used internally in eLua to identify the GPIO pins. You do not need to know anything about the values of these numbers except in one circumstance: when you have several edge-triggered interrupts enabled on different GPIO pins at the same time.
You see, you can only tell cpu.set_int_handler()
to call a single function for all positive edge interrupts and one other function (or the same one) for all negative-edge interrupts, but you can find out which GPIO pin caused the interrupt by inspecting the parameter that is passed to your interrupt-handling function, which we have called resnum
in the code examples above.
This is the "resource number", the internal number that eLua uses to represent the pin, and it is the same as the value returned by pio.PX_16
and so on.
So you can say, in your interrupt function,
if resnum == button ...
or
if resnum == pio.PA_3 ...
however, if you have enabled many pin interrupts and wish to decode it into a port number and a pin number, you can do so using
port, pin = pio.decode( resnum )
For pins on Port A, port
will be 0
and pin
will be from 0 to 31.
For pins on Port B, port
will be 1
and pin
will be from 0 to 31.
For pins on Port C, port
will be 2
and pin
will be from 0 to 5.
For pins on Port X, port
will be 23
and pin
will be from 0 to 39.
Further reading
[edit | edit source]- The Open collector article on Wikipedia
- The Atmel AT32UC3A datasheet Chapter 22: General-Purpose Input/Output Controller (GPIO)
- The PIO section of the eLua manual for details of all eLua
pio.*()
functions