Using the I/O pins
The Raspberry Pi Pico has 26 input/output pins which can be controlled from within the BASIC program with 3 of these supporting a high speed ADC (Analog to Digital Converter).
An I/O pin is referred to by its pin number and this can be the number (eg,, 2) or its GP number (eg,, GP1).
Digital Inputs
A digital input is the simplest type of input configuration. If the input voltage is higher than 2.5V the logic level will be true (numeric value of 1) and anything below 0.65V will be false (numeric value of 0). The inputs use a Schmitt trigger input so anything in between these levels will retain the previous logic level.
Note that the maximum voltage on the RP2040 (ie, the Raspberry Pi Pico) I/O pins is 3.3V. Level shifting will be required if a device uses 5V levels for signalling. The Raspberry Pi Pico 2 using the RP2350 can tolerate 5V (while powered) so, in this case, level shifting is not required for signals up to 5V.
In your BASIC program you would set the input as a digital input and use the PIN()
function to get its level.
For example:
SETPIN GP4, DIN
IF PIN(GP4) = 1 THEN PRINT "High"
The SETPIN
command configures pin GP4
as a digital input and the PIN()
function will return the value of that
pin (the number 1
if the pin is high). The IF
command will then execute the command after the THEN
statement if the input was high. If the input pin was low the program would just continue with the next line in
the program.
The SETPIN
command also recognises a couple of options that will connect an internal resistor from the input
to either the supply or ground. This is called a "pullup" or "pulldown" resistor and is handy when connecting to
a switch as it saves having to install an external resistor to place a voltage across the contacts. Due to a
hardware issue with the RP2350 processor it is recommended that an external resistor of 8,2K or less be used if
a pulldown is required on that processor.
Analog Inputs
Pins marked as ADC can be configured to measure the voltage on the pin. The input range is from zero to 3.3V
and the PIN()
function will return the voltage. For example:
> SETPIN 31, AIN
> PRINT PIN(31)
2.345
>
You will need a voltage divider if you want to measure voltages greater than 3.3V. For small voltages you may need an amplifier to bring the input voltage into a reasonable range for measurement.
The measurement uses 3.3V power supply to the CPU as its reference and it is assumed that this is exactly
3.3V. This value can be changed with the OPTION VCC
command. In order to get the best possible reading,
the analogue input is sampled 10 times. The values are then sorted and the top 2 and bottom 2 discarded and the
remaining 6 averaged.
If you want the direct reading from the ADC you can use the raw mode by using the ARAW
option to the SETPIN
command:
SETPIN pinno,ARAW
In this case a value between 0 and 4095 will be returned based on a single sample.
The ADC commands provide an alternate method of recording analog inputs and are intended for high speed recording of many readings into an array.
Counting Inputs
Any four pins can be used as counting inputs to measure frequency, period or just count pulses on the input.
The pins used for this function can be configured using the OPTION COUNT
command but, if not changed,
will default to GP6, GP7, GP8 and GP9.
As an example, the following will print the frequency of the signal on pin GP7:
> SETPIN GP7, FIN
> PRINT PIN(GP7)
110374
>
In this case the frequency is 110.374 kHz.
By default the gate time is one second which is the length of time that MMBasic will use to count the number
of cycles on the input and this means that the reading is updated once a second with a resolution of 1 Hz. By
specifying a third argument to the SETPIN
command it is possible to specify an alternative gate time between
10 ms and 100000 ms. Shorter times will result in the readings being updated more frequently but the value
returned will have a lower resolution. The PIN()
function will always scale the returned number as the
frequency in Hz regardless of the gate time used.
For example, the following will set the gate time to 10ms with a corresponding loss of resolution:
> SETPIN GP7, FIN, 10
> PRINT PIN(GP7)
110300
>
For accurate measurement of signals less than 10 Hz it is generally better to measure the period of the signal.
When set to this mode the PicoMite firmware will measure the number of milliseconds between sequential rising edges of the input signal. The value is updated on the low to high transition so if your signal has a period of (say) 100 seconds you should be prepared to wait that amount of time before the PIN()
function will return
an updated value.
The count pins can also count the number of pulses on their input. When a pin is configured as a counter (for
example, SETPIN 7,CIN
) the counter will be reset to zero and PicoMite firmware will then count every
transition from a low to high voltage. The counter can be reset to zero again by executing PIN(7) = 0
.
Counting inputs are accurate up to about 200KHz at the default processors frequency. A minimum pulse width
of about 40nS is needed to trigger the counter. The RP2350 also has the option of configuring GP1 as an
extremely fast frequency counting pin (see the SETPIN GP1, FFIN
command).
Digital Outputs
All I/O pins can be configured as a digital output using the DOUT
parameter to the SETPIN
command. For
example:
SETPIN GP15, DOUT
This means that when an output pin is set to logic low it will pull its output to zero and when set high it will pull its output to 3.3V. In MMBasic this is done with the PIN
command. For example PIN(GP15) = 0
will set pin GP15
to low while PIN(GP15) = 1
will set it high.
Pulse Width Modulation
The PWM (Pulse Width Modulation) command allows the PicoMite firmware to generate square waves with a program controlled duty cycle.
By varying the duty cycle you can generate a program controlled voltage output for use in controlling external devices that require an analog input (power supplies, motor controllers, etc). The PWM outputs are also useful for driving servos and for generating a sound output via a small transducer.
RP2040 The PWM outputs consists of up to 8 channels (numbered 0 to 7) with each channel having two outputs (A and B). For each channel the frequency can be selected and for each output a different duty cycle can be set. Up to 16 pins can be configured as PWM outputs using the SETPIN
command.
RP2350 The RP2350 supports up to 12 PWM channels numbered 0 to 11) and up to 24 pins can be configured as PWM outputs using the SETPIN
command.
Communications Interfaces (Serial, SPI and I2C)
These are described in the appendices at the rear of this manual. Before these interfaces can be used the pins that are to be used for the relevant signals must be configured using the SETPIN
command.
Some devices such as an SD Card, LCD panels, touch, etc also use SPI or I2C interfaces and the pins used for these must similarly be configured using the OPTION SYSTEM
command before they can be used.
Interrupts
Interrupts are a handy way of dealing with an event that can occur at an unpredictable time. An example is when the user presses a button. In your program you could insert code after each statement to check to see if the button has been pressed but an interrupt makes for a cleaner and more readable program.
When an interrupt occurs MMBasic will execute a defined subroutine and when finished return to the main program. The main program is completely unaware of the interrupt and will carry on as normal.
Any I/O pin that can be used as a digital input can be configured to generate an interrupt using the SETPIN
command with up to ten interrupts active at any one time. Interrupts can be set up to occur on a rising or falling digital input signal (or both) and will cause an immediate branch to the specified user defined subroutine. The target can be the same or different for each interrupt. Return from an interrupt is via the END SUB
or EXIT SUB
commands. Note that no parameters can be passed to the subroutine however within the interrupt calls to other subroutines and functions are allowed.
If two or more interrupts occur at the same time they will be processed in order of the interrupts as defined below. During the processing of an interrupt all other interrupts are disabled until the interrupt subroutine returns. During an interrupt (and at all times) the value of the interrupt pin can be accessed using the PIN() function.
Interrupts can occur at any time but they are disabled during INPUT
statements. Also interrupts are not
recognised during some long hardware related operations (eg, the TEMPR()
function, LCD
drawing
commands, and SD
access commands) although they will be recognised if they are still present when the
operation has finished. When using interrupts the main program is completely unaffected by the interrupt
activity unless a variable used by the main program is changed during the interrupt.
Because interrupts run in the background they can cause difficult to diagnose bugs. Keep in mind the following factors when using interrupts:
-
Interrupts are only checked by MMBasic at the completion of each command, and they are not latched by the hardware. This means that an interrupt that lasts for a short time can be missed, especially when the program is executing commands that take some time to execute. Most commands will execute in under 15µs however some commands such as the
TEMPR()
function can take up to 200ms so it is possible for an interrupt to occur and vanish within this window and thus not be recognised. -
When inside an interrupt all other interrupts are blocked so your interrupts should be short and exit as soon as possible. For example, never use
PAUSE
inside an interrupt. If you have some lengthy processing to do you should simply set a flag and immediately exit the interrupt, then your main program loop can detect the flag and do whatever is required. -
The subroutine that the interrupt calls (and any other subroutines or functions called by it) should always be exclusive to the interrupt. If you must call a subroutine that is also used by an interrupt you must disable the interrupt first (you can reinstate it after you have finished with the subroutine).
-
+Remember to disable an interrupt when you have finished needing it – background interrupts can cause strange and non-intuitive bugs.
In addition to interrupts generated by the change in state of an I/O pin, an interrupt can also be generated by other sections of MMBasic including timers and communications ports and the above notes also apply to them. The list of all these interrupts (in high to low priority ranking) is:
- PID control loops
- ON KEY individual
- ON KEY general
- ON PS2
- PIO RX FIFO
- PIO TX FIFO
- PIO RX DMA completion
- PIO TX DMA completion
- GUI Int Down
- GUI Int Up
- Sprite collision
- WebMite: TCP receive
- WebMite: MQTT compete
- WebMite: UDP receive
- USB Game Controller/USB or PS2 Mouse/Wii controller
- ADC completion
- I2C Slave Rx
- I2C Slave Tx
- I2C2 Slave Rx
- I2C2 Slave Tx
- WAV Finished
- COM1: Serial Port
- COM2: Serial Port
- IR Receive
- Keypad
- Interrupt command/CSub Interrupt
- I/O Pin Interrupts in order of definition
- Tick Interrupts (1 to 4 in that order)
As an example: If an ON KEY
interrupt occurred at the same time as a COM1: interrupt the ON KEY
interrupt
subroutine would be executed first and then, when the interrupt subroutine finished, the COM1: interrupt
subroutine would then be executed.