Picod is a daemon program that runs on the Raspberry Pico (http://abyz.me.uk/picod/index.html). It uses serial communication (either UART or USB) to communicate with a host. Functionally it is more or less equivalent to having a Firmata sketch on an Arduino microprocessor or the PiGPIO daemon on a Raspberry Pi. The communication protocol is specific to Picod. It uses eight bits and CRC checks. The original comes with a Python library. Just like PiGPIO it has an elaborate callback mechanism for Python. And just like the PiGPIO driver for Pharo this has here been implemented using announcements.
First you will have to put the picod.uf2 file on the Pico. The driver is started with something like:
myPico := PicodDriver new connectOnPort: '/dev/ttyACM0'.
Optionally you can include baudRate:
, which defaults to 230400 and doesn't make much sense for a USB serial port anyway
Some commands/request have no return value. Commands that do return a value have two flavours (where this makes sense). Either the result is returned by the method itself, or the result is returned in an announcement (PicodResultAvailable
). For example:
v := pico analogRead: channelNr
returns the value, while
pico analogRead: channelNr wait: false
returns immediately and the result will be transmitted in an announcement when it becomes available.
Results are instances of PicodResult
and contain the operations code, the status and the actual result bytes. A status of 0 means OK. Wait:true calls return the value only.
For testing you can do:
myPico statusCheck: true. "This will write the text for non-zero result codes to the Transcript"
Unlike some other drivers (e.g. Firmata) PicodDriver does not have the concept of mode. You must however free a pin (or more pins) from its present function before you can perform another type of operation:
- leave digital I/O mode with: #closeGpio:
- leave pwm/servo mode with: #servoPWMClose:
- leave analog input mode with: #analogClose:
Some digital I/O operations are specified for a number of pins at once. Internally the pins are represented by single bits in a 64-bit word, but here we use arrays of gpio numbers. So we have
myPico digitalWrite: 25 value: 1. "set pin 25 to 1; 25 happens to be the on-board LED"
myPico digitalWrite: #(1 3 5) values: #(1 1 0). "set pins 1 3 and 5 to 1, 1 and 0 respectively"
level := myPico digitalRead: 2.
myPico closeGpios: #(0 2). "frees pins 0 and 2 for other uses"
Pull up or down can be set for pins. We have four methods that operate on collections of pins: #pullsNone: #pullsUp: #pullsDown and #pullsBoth:
#pullForPin:
returns the pull value for an individual pin. Before you can change the pull resistors you must first open the pins you want to modify with#openGpios:
.
All gpio's are capable of outputting servo or PWM signals, but not fully independently. You will have to read the docs. Methods are:
myPico pwmOnPin: 12 value: 30 frequency: 1000. "30% pwm at 1000 Hz"
myPico servoOnPin: 13 pulseWidth: 1000. "1000 microsecond pulse (90 degrees) at 50 Hz"
myPico servoPWMClose: 13. "Free gpio 13 for other uses"
voltage := myPico analogRead: 3 "read analog channel 3"
reads channel 3 adc, which happens to be connected to a voltage divider between VSYS and ground;
To free the gpio pin for other uses, you have to execute
myPico analogClose: 3
The RP2040 has two I2C channels, 0 and 1. First a channel must be opened and associated with the the gpio's to be used as sda and scl:
myPico i2cOpenChannel: channel sda: sdapin scl: sclPin baudRate: aNumber
BaudRate usually is 100000. Channel is 0 or 1 and each channel allows only certain combinations of pins (see comments). After that you can read and write bytes to and from that channel. A special selector noStop
is available to keep the Pico from releasing the bus, so you can write a register address and follow up by reading the data. I2C operations have been objectified in the class PicodI2CConnection
.
An instance of PicodI2CConnection
is specific for one device and is determined by its I2C channel and the device bus address:
myI2CDevice := myPico i2cOpenConnectionOn: channel i2cAddres: anI2CAddress
where channel (0 or 1) should have been opened before and anI2CAddress is de device address (0-255).
Now you have the operations that are also available in the Firmata and PiGPIO drivers like:
a := myI2CDevice readByteAt: 0. "read 8 bits at register 0"
myI2CDevice writeWordAt: 2 data: 16r3AC5. "write 16r3AC5 to register 2, lowest byte first"
myI2CDevice writeWordBigEndianAt: 2 data: 16r3AC5. "same but high byte first"
A device is closed with #close
. Only by closing the channel with #i2cCloseChannel:
you free the associated gpios. This is only possible when there are no more active i2cConnections on that channel; otherwise use i2cCloseChannelForced:
to close all devices for you.
Picod supports a number of asynchronous operations. All are implemented using the announcement framework.
#pinsToWatch
: is the method to specify which pins can generate an announcement on level change. There is only one type of announcement, PicodPinChange
with accessors pinNr
, newLevel
and tick
. The latter is a timestamp with an accuracy of some microseconds that rolls over every 72 minutes.
You can also get a notification (PicodWatchdog
) that is triggered when a pin does not change during a specified period. The watchdog is set with #setWatchdogOnPin:timeout:
. The watchdog timer starts after the first level change.
A command/request can specify wait: false
. In that case an announcement, PicodResultAvailable
will be triggered with one method, result
that returns the ```PicodResult `` (see above). The result contains the request code that generated this result.
Events stem from UART, I2C, GPIO, SPI operations. You can specify which event to watch. This is not yet implemented.
To subscribe to announcements we have for example:
myPico when: PicodPinChange do: [:ann | ('pin ', ann pinNr printString, ' has changed') traceCr.].
This driver needs SerialPort to communicate with the Pico. Since over more than two years ago the VM does not come with the Serial plugin, so in pharo 9 and 10 this driver did not work. Now there is an FFI-based implementation by Pablo Tesone (https://github.com/tesonep/SerialPort). Here we use my fork of this repo that contains a small tweak (DTR enable) to make it work for the combination of Pico and Windows.
Load with:
Metacello new
baseline: 'PicodDriver';
repository: 'github://robvanlopik/picod-pharo:main';
load