#rp2350 — Public Fediverse posts
Live and recent posts from across the Fediverse tagged #rp2350, aggregated by home.social.
-
While probing a GPIO pin on my oscilloscope, I noticed it toggled between 2.4V and 3.3V. Using another reference pin, toggling at exactly the same time swinged correctly between 0V and 3.3V
I think I might have damaged a pin on my #rp2350 :( #debugging #signals #embedded #pico2
-
While probing a GPIO pin on my oscilloscope, I noticed it toggled between 2.4V and 3.3V. Using another reference pin, toggling at exactly the same time swinged correctly between 0V and 3.3V
I think I might have damaged a pin on my #rp2350 :( #debugging #signals #embedded #pico2
-
RP Pico Streamline Board - RP2354 - RP Pico #1 rying out the new Streamline MCU RP2354 board. A dive into what is Raspberry Pico RP2350, WHY am I making this video and finally - how easy it is to use. #GettingStarted #Tutorial #Streamline #RP2350 #STM32World www.youtube.com/watch?v=7JMz...
RP Pico Streamline Board - RP2... -
RP Pico Streamline Board - RP2354 - RP Pico #1
rying out the new Streamline MCU RP2354 board. A dive into what is Raspberry Pico RP2350, WHY am I making this video (on a channel named STM32World) and finally - how easy it is to use.
-
RP Pico Streamline Board - RP2354 - RP Pico #1
rying out the new Streamline MCU RP2354 board. A dive into what is Raspberry Pico RP2350, WHY am I making this video (on a channel named STM32World) and finally - how easy it is to use.
-
RP Pico Streamline Board - RP2354 - RP Pico #1
rying out the new Streamline MCU RP2354 board. A dive into what is Raspberry Pico RP2350, WHY am I making this video (on a channel named STM32World) and finally - how easy it is to use.
-
RP Pico Streamline Board - RP2354 - RP Pico #1
rying out the new Streamline MCU RP2354 board. A dive into what is Raspberry Pico RP2350, WHY am I making this video (on a channel named STM32World) and finally - how easy it is to use.
-
RP Pico Streamline Board - RP2354 - RP Pico #1
rying out the new Streamline MCU RP2354 board. A dive into what is Raspberry Pico RP2350, WHY am I making this video (on a channel named STM32World) and finally - how easy it is to use.
-
USB stick with an #RP2350 and microSD slot. Plug into any computer — serial interface to a private pocket computer, can access it from any machine and any OS without having to write C interface to specific OSes
-
TEASER New Streamline Blades, RP2354, STM32C551, RS-485 and Ethernet Just a short teaser video explaining 4 new blades fresh of the production line. #CurrentMakers #StreamLine #STM32C5xx #RP2350 #Ethernet #RS485 www.youtube.com/watch?v=LwX6...
TEASER New Streamline Blades, ... -
TEASER New Streamline Blades, RP2354, STM32C551, RS-485 and Ethernet
Just a short teaser video explaining 4 new blades fresh of the production line. In this batch, there's a RS-485 and an Ethernet peripheral as well as two new MCU blades: A STM32C551 and a RP2354 (yes!).
#CurrentMakers #StreamLine #STM32C5xx #RP2350 #Ethernet #RS485
-
I must say that esp-idf is a significantly more pleasant experience than pico-sdk and co. I’m not saying anything about the rust embedded ecosystem, because rust just makes me want to throw the computer out the window, even with #llms. I’m sure it’s nice, but it’s not for me.
I wonder how much of the “pleasantness” of the experience is just how nice it is to have a system serial and flasher and monitoring / debugging tool all built in without having to press little buttons or (sigh…) having to plug unplug usb cables (maybe I’m also just too dumb?)
C++ freertos and global static memory pools yooo here I come. I’m a bit disappointed by ulisp (no offense to the project, I was just expecting something different), so I guess I gotta knuckle down now.
But despite my ranting, dang embedded ecosystem is so different these days. I will never be able to shed the ptsd of having 18 microchip silicon errata printouts and figuring out which Keil avr c bugs are actually a problem, while I bit bang my firmware out of /dev/lpt0
-
One ROM Web: Browser Programming for Retro ROM Replacements
#OneROM #OneROMWeb #PiersRocks #Commodore64 #C64 #RetroComputing #EPROM #RP2350 #WebUSB #VintageComputing
https://theoasisbbs.com/one-rom-web-browser-programming-for-retro-rom-replacements/?fsp_sid=6410 -
A simple breakout for the Pimoroni #PGA2350 module.
https://diyelectromusic.com/2026/05/04/pga2350-breakout-pcb-design/
-
PGA2350 Breakout PCB Build Guide
Here are the build notes for my PGA2350 Breakout PCB Design.
Warning! I strongly recommend using old or second hand equipment for your experiments. I am not responsible for any damage to expensive instruments!
If you are new to electronics and microcontrollers, see the Getting Started pages.
Bill of Materials
- PGA2350 Breakout PCB (GitHub link below).
- Pimoroni PGA2350.
- SMT micro USB socket (see photos and PCB for footprint).
- 2x 2-pin tactile button switches.
- Double row, round pin header sockets.
- Double row, extended round pin header pins.
- Pin header pins or sockets as required for the breakout.
Reminder: This board is NOT compatible with the similar form-factor Waveshare Core 2350B.
Build Steps
Taking a typical “low to high” soldering approach, this is the suggested order of assembly:
- The micro USB socket.
- The PGA2350 round header pin sockets.
- Switches.
- Pin headers.
I’ve chosen to use pins for the breakout, but pin header sockets would be fine too.
Whilst it makes sense to fit the USB socket first anyway, it is also the most tricky part of the PCB, so if that gets messed up there isn’t much point in carrying on!
And it was really tricky for me. I’ve soldered them for power before, but this time I needed the data lines connected too. Going slowly, using lots of flux and a magnifying glass seemed to do the trick. I soldered a single case pin connection to hold everything in place whilst attempting the main connections, then tested everything for continuity and lack of shorts before moving on.
When cutting pin header sockets for the PGA2350 socket, they may need filing down on the ends slightly to fit snugly with no bending or forcing when arranged in the square.
Unfortunately the footprint for the buttons didn’t quite match the buttons I had. The holes are slightly too small. I opted to file down the legs of the buttons slightly and then apply a bit more force to get them in place. They went in well enough for me in the end.
Soldering pins to the PGA2350 itself was quite a hairy moment too. The labels look really cool, but they are not silkscreen, they are gaps in the mask I think. Regardless, solder will stick to them if not careful…
Here are some build photos.
Testing
I recommend performing the general tests described here: PCBs coupled with a thorough visual inspection.
As already mentioned, the USB socket should be checked for continuity and shorts before soldering anything else in place.
When confident everything seems ok, then the board can be connected by USB to a PC and it power up in BOOT mode and the standard RP2350 boot drive should be visible.
PCB Errata
There are the following issues with this PCB:
- As already mentioned the footprints for the buttons have holes that are too small.
Closing Thoughts
The button issue was an annoyance but ok. But I really need to find a better micro USB footprint to use as that was really quite tricky to get right.
The PGA2350, once it has all its pins soldered in place, requires a fair bit of force to fit into the socket, but it isn’t too bad. Getting it back out however isn’t so easy. It can be done with some gentle leverage in the corners, taking care not to bend any pins either on the PGA2350 itself or the breadout board.
But once all assembled and up and running, it seems to work pretty well.
Kevin
#pcb #pga2350 #rp2350 -
PGA2350 Breakout PCB Design
I’m wanting to do some experimenting with the Pimoroni PGA2350 which is a really neat, 25x25mm, RP2350 board with all 48 GPIO pins of the larger RP2350B bought out to two rows arranged in a square. But it isn’t very breadboard friendly.
More recently there are now DIP based breakouts that also support the RP2350B and all 48 GPIO, but eventually I’d like to use a RP2350B in a design of my own and the PGA2350 is such a good form factor to use for that. I’m not ready to attempt a SMD design using a bare RP2350 itself just yet.
So I had a need to be able to experiment with a PGA2350 and hence decided to create this simple breakout board.
Warning: There is a Waveshare RP2350 module (the Waveshare Core 2350B) in the same form factor, but this MUST NOT BE USED. The pinouts are slightly different but most significantly, some of the power and GND overlap with GPIO on the Pimoroni board. I might re-spin a version of this board for the Waveshare at some point…
Warning! I strongly recommend using old or second hand equipment for your experiments. I am not responsible for any damage to expensive instruments!
If you are new to electronics and microcontrollers, see the Getting Started pages.
The Circuit
There is little more than the PGA2350 and some headers. There just a USB socket for communications and power, and buttons for BOOT and RESET.
I had to create my own KiCAD symbol for the PGA2350 however, but it is very similar to the symbol for the RP2350 itself.
I’ve also included some additional headers for GND, 3V3 and 5V (hanging off VBUS).
PCB Design
I’ve used the micro-USB socket footprint I’ve used in the past. I did think about using a micro-USB breakout board to make soldering easier, but wanted it to look a bit neater than that.
I also had to create a PGA2350 footprint to go with my symbol, but that wasn’t too difficult. Essentially setting the grid size to 2.54mm and keep adding pins. Numbering them took a while as I wanted the pin numbers to match the GPIO numbers as far as possible. There was probably an easier way to do this in KiCAD, but I often just work within what I know.
Then I added silkscreen GPIO numbers to all the breakout pin headers to make it easier to use.
I choose simple button footprints that I thought would match the two-pin buttons I have (spoiler: they didn’t. I had to bodge something together, but I’ll come to that when I write up the build guide).
Closing Thoughts
Pimoroni were selling pins and sockets in the PGA format, but they are now end-of-life. I’ve also found some 10×10, 68-pin PGA sockets (there are four additional pins, one on each inside corner) which I did wonder about using, but in the end I opted for a simpler footprint and will use dual-row, round pin, pin header sockets to mount the PGA2350.
I’m pretty sure that will work out ok.
Kevin
#pcb #pga2350 #rp2350 -
Somehow ... studying the rp2040 PIO state machines has led to me playing TIS-100. https://en.wikipedia.org/wiki/TIS-100 And loving it. If you need some gamified assembly coding in your life, take a look. It's been out for a decade... but maybe you missed it like I did. ;)
-
Awesome idea: use Pi Pico as USB sound card + DSP! Equalizer, crossover, room compensation, etc.
License: GPL v3.https://hackaday.com/2026/04/29/digital-signal-processing-on-the-pi-pico/
-
I'd love feedback on my latest #OpenBoardsGuide piece:
https://chaos.social/@index@openboardsguide.com/116460917759658156
I think our editor @straithe pushed me to new heights on this one! I'd love to hear your feedback! 💗
-
So here's where the project sits at the end of day 1 - It's a #Blackberry Q10 keyboard working through a #PiPico #RP2350 controller. I'm making a physical keyboard case for my phone because I'm outrageously tired of virtual keyboards.
That's a PMOD implementation of the Q10 keyboard by Solder Party (shout-out to @arturo182) - I don't think you can buy these anymore but I bought two of them years ago and squirreled them away for the day I'd have a suitable microcontroller for the project.
-
Okay so, to upload new firmware to a #SpotPear #RP2350 Core-A using the #Arduino IDE, you need to:
- Install the RP2350/2040 boards from here: https://github.com/earlephilhower/arduino-pico
- Select the "Generic RP2350" board
- Hold the microscopic BOOT button in while you plug it into USB
- Select the "UF2 Board" under "uf2conv ports" instead of "Serial ports"
- Hit upload - it should do that and reboot your board with the new firmwareThere's a WS2812 LED on port 25 for status/disco: https://spotpear.com/wiki/Raspberry-Pi-Pico-2-RP2350-Core-A.html
-
Pico 2 C64 Brings Frodo-Based Emulation to FRANK and Murmulator
#Commodore64 #C64 #RetroComputing #FRANKC64 #Frodo4 #RP2350 #RaspberryPiPico2 #Murmulator #FRANKM2
https://theoasisbbs.com/pico-2-c64-brings-frodo-based-emulation-to-frank-and-murmulator/?fsp_sid=5043 -
Pico 2 C64 Brings Frodo-Based Emulation to FRANK and Murmulator
#Commodore64 #C64 #RetroComputing #FRANKC64 #Frodo4 #RP2350 #RaspberryPiPico2 #Murmulator #FRANKM2
https://theoasisbbs.com/pico-2-c64-brings-frodo-based-emulation-to-frank-and-murmulator/?fsp_sid=5043 -
Pico 2 C64 Brings Frodo-Based Emulation to FRANK and Murmulator
#Commodore64 #C64 #RetroComputing #FRANKC64 #Frodo4 #RP2350 #RaspberryPiPico2 #Murmulator #FRANKM2
https://theoasisbbs.com/pico-2-c64-brings-frodo-based-emulation-to-frank-and-murmulator/?fsp_sid=5043 -
Pico 2 C64 Brings Frodo-Based Emulation to FRANK and Murmulator
#Commodore64 #C64 #RetroComputing #FRANKC64 #Frodo4 #RP2350 #RaspberryPiPico2 #Murmulator #FRANKM2
https://theoasisbbs.com/pico-2-c64-brings-frodo-based-emulation-to-frank-and-murmulator/?fsp_sid=5043 -
Pico 2 C64 Brings Frodo-Based Emulation to FRANK and Murmulator
#Commodore64 #C64 #RetroComputing #FRANKC64 #Frodo4 #RP2350 #RaspberryPiPico2 #Murmulator #FRANKM2
https://theoasisbbs.com/pico-2-c64-brings-frodo-based-emulation-to-frank-and-murmulator/?fsp_sid=5043 -
RE: https://oldbytes.space/@thelastpsion/116345290344668595
And the results are in!
- Pico C
- Rust
- Free Pascal
- Joint with Pico C++, MicroZig and Yarg.
So, what am I going to do? Well, I'm leaning towards porting the existing Arduino C code to the Pico C SDK. I'm currently using one class for encapsulation and abstraction, but I could replace that with structs and static functions. I'm not a great C programmer, but I'm pretty comfortable with it, so it makes sense
However, before I do that, I see a bigger challenge of getting a good setup without using VS Code. I've been using #NeoVim for a while now, and I'd like to get a comfortable setup using that on #Linux.
So, I'm going to try to build Blinky projects for at least Pico C, Rust and Free Pascal, using Linux and NeoVim. Hopefully this will give me a better feel for how well these languages actually suit me. I've never done any Rust before, either, so that's going to be quite the learning curve!
If I have time, I'm going to give Yarg a go, too, because I think the premise is really cool. If I'm on a roll, I'll try #MicroZig too.
And if I really feel like I have the capacity, I'll port the code to one of these other languages.
I'm acutely aware of all the other projects I've given myself to do, such as the SIBO SDK and other small Psion-related projects, not to mention $dayjob and $reallife. So we'll see how things go!
#Pascal #FreePascal #ObjectPascal #RustLang #YargLang #RaspberryPiPico #PiPico #PiPico2 #RP2040 #RP2350
-
RE: https://oldbytes.space/@thelastpsion/116345290344668595
Only a couple of hours left for this poll.
C is clearly out in front for my embedded project, but Rust and Pascal aren't far behind!
-
PIO on the Raspberry Pi Pico – Part 2
Having got all the theory out of the way in PIO on the Raspberry Pi Pico now is the time to actually start programming. Whilst I have the option of using the C/C++ SDK or one of the Python variants, I’m particularly interested in getting it going from within the Arduino environment, just because that is where I do pretty much all of my other microcontroller messing about.
I’m not using the official Arduino for Pico support though, I’m using Earl Philhower’s version from here: https://github.com/earlephilhower/arduino-pico
Pico Arduino Getting Started
Before getting too far into PIO land, there are a few things to note about using the unofficial Arduino Pico core with the Raspberry Pi Pico.
On first boot, hold down the BOOT switch and the Pico will be detected as a “UF2 Board”. This will allow the first upload to take place (more here). I’ve selected “Raspberry Pi Pico” or “Raspberry Pi Pico 2” as appropriate for the board.
Prior to the first download, the configuration should set the Debug Port to Serial. Then once the first sketch is downloaded the board can be redetected via a serial link which will allow both Serial.print() and automatic reset on download of new sketches.
Aside: there are three serial ports (more here):
- Serial – the USB serial port – the one used here
- Serial1 – UART0
- Serial2 – UART1
Here is a simple starter program to make sure everything is working:
void setup() {
Serial.begin(9600);
pinMode (LED_BUILTIN, OUTPUT);
}
unsigned counter;
void loop() {
Serial.println(counter);
counter++;
delay(1000);
digitalWrite (LED_BUILTIN, (counter & 1));
}Assuming everything is working, every second the LED will flash on or off and the counter value will be printed to the serial monitor.
Hello PIO
I’m starting off with a simple pulse on a GPIO pin and will be using the online PIO assembler from https://wokwi.com/tools/pioasm to build it.
My PIO Source:
.program pulse
.wrap_target
set pins, 1 [3] // 4 cycles
set pins, 0 [11] // 12 cycles
.wrap
% c-sdk {
static inline void pulse_program_init(PIO pio, uint sm, uint offset, uint pin) {
pio_sm_config c = pulse_program_get_default_config(offset);
// set_base=pin, count=1
sm_config_set_set_pins(&c, pin, 1);
pio_gpio_init(pio, pin);
// pins_base=pin, pin_count=1, is_out=true
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
// 440 Hz pulse over 16 cycles
float div = (float)clock_get_hz(clk_sys) / (440.0 * 16.0);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}The online assembler turns the above into the following, which is pasted into a pulse_pio.h file within an Arduino sketch.
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// ----- //
// pulse //
// ----- //
#define pulse_wrap_target 0
#define pulse_wrap 1
static const uint16_t pulse_program_instructions[] = {
// .wrap_target
0xe301, // 0: set pins, 1 [3]
0xeb00, // 1: set pins, 0 [11]
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program pulse_program = {
.instructions = pulse_program_instructions,
.length = 2,
.origin = -1,
};
static inline pio_sm_config pulse_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + pulse_wrap_target, offset + pulse_wrap);
return c;
}
static inline void pulse_program_init(PIO pio, uint sm, uint offset, uint pin) {
pio_sm_config c = pulse_program_get_default_config(offset);
// set_base=pin, count=1
sm_config_set_set_pins(&c, pin, 1);
pio_gpio_init(pio, pin);
// pins_base=pin, pin_count=1, is_out=true
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
// 440 Hz pulse over 16 cycles
float div = (float)clock_get_hz(clk_sys) / (440.0 * 16.0);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
#endifAdding the appropriate additional PIO initialisation code to my previous test sketch now gives me the following complete code:
#include <PIOProgram.h>
#include "pulse_pio.h"
#define PULSE_PIN 2
void setup() {
Serial.begin(9600);
pinMode (LED_BUILTIN, OUTPUT);
PIO pio;
uint sm, offset;
if (!pio_claim_free_sm_and_add_program(&pulse_program, &pio, &sm, &offset)) {
for (;;) {
Serial.print("No PIO or SM");
delay(10000);
}
}
pulse_program_init(pio, sm, offset, PULSE_PIN);
}
unsigned counter;
void loop() {
Serial.println(counter);
counter++;
delay(1000);
digitalWrite (LED_BUILTIN, (counter & 1));
}Notes:
- As I’m using “set” in the pio program I need to use the “set” group of pins in the various API calls – hence the use of sm_config_set_set_pins() which configures which pins to use with the set command. In this case, just one pin determined by the “pin” parameter.
- I’m using the wait [] instructions to put the pulse HIGH for 4 cycles and LOW for 12 cycles, giving 16 cycles in total.
- The waiting cycles have to account for the single cycle of the actual executed instruction, hence using [3] and [11].
- When setting the clock divisor, I’m dividing the system frequency by my required frequency * 16 as there are 16 cycles in the complete program.
- When I had a single cycle HIGH and 3 cycles LOW and then used the value 440.0 * 4.0 I wasn’t getting an accurate frequency (I was getting ~1.9K rather than 440). I’m guessing (I haven’t done the maths) this was overflowing the integer part of the divisor maybe.
The PIO and state machine used are allocated dynamically by the system using pio_claim_free_sm_and_add_program(). The first version had hard-coded PIO 0, state machine 0:
PIO pio = pio0;
int sm = 0;
uint offset = pio_add_program(pio, &pulse_program);The final result can be seen on the oscilloscope trace below.
Conclusion
I’ve now been through the theory and a real, albeit simple, application and am feeling like I understand a lot more what is going on now. I still am somewhat bewildered by the huge array of API calls and do feel like they could be grouped together somehow to make them more accessible to people who haven’t swallowed the entire chip datasheet and SDK guidebooks…
But yes, I’m slowly starting to feel like I’m getting to grips with PIO a bit more now. I want to do something that now grabs some input from the GPIO and sticks it into memory, ideally using the DMA system, so that is probably where I’ll go next.
Kevin
#pio #raspberryPiPico #rp2040 #rp2350 -
PIO on the Raspberry Pi Pico – Part 2
Having got all the theory out of the way in PIO on the Raspberry Pi Pico now is the time to actually start programming. Whilst I have the option of using the C/C++ SDK or one of the Python variants, I’m particularly interested in getting it going from within the Arduino environment, just because that is where I do pretty much all of my other microcontroller messing about.
I’m not using the official Arduino for Pico support though, I’m using Earl Philhower’s version from here: https://github.com/earlephilhower/arduino-pico
Pico Arduino Getting Started
Before getting too far into PIO land, there are a few things to note about using the unofficial Arduino Pico core with the Raspberry Pi Pico.
On first boot, hold down the BOOT switch and the Pico will be detected as a “UF2 Board”. This will allow the first upload to take place (more here). I’ve selected “Raspberry Pi Pico” or “Raspberry Pi Pico 2” as appropriate for the board.
Prior to the first download, the configuration should set the Debug Port to Serial. Then once the first sketch is downloaded the board can be redetected via a serial link which will allow both Serial.print() and automatic reset on download of new sketches.
Aside: there are three serial ports (more here):
- Serial – the USB serial port – the one used here
- Serial1 – UART0
- Serial2 – UART1
Here is a simple starter program to make sure everything is working:
void setup() {
Serial.begin(9600);
pinMode (LED_BUILTIN, OUTPUT);
}
unsigned counter;
void loop() {
Serial.println(counter);
counter++;
delay(1000);
digitalWrite (LED_BUILTIN, (counter & 1));
}Assuming everything is working, every second the LED will flash on or off and the counter value will be printed to the serial monitor.
Some PIO API Principles
Pin groups are an important idea when dealing with PIO state machines.
There are four different sets of pins that can be enacted upon via the PIO system: OUT, IN, SET and Sidestep. Each has its own group of API functions. For example, to set the start and number of pins in a specific group there are the following functions (from here):
pio_sm_set_out_pins (PIO pio, uint sm, uint out_base, uint out_count)
pio_sm_set_set_pins (PIO pio, uint sm, uint set_base, uint set_count)
pio_sm_set_in_pins (PIO pio, uint sm, uint in_base)
pio_sm_set_sideset_pins (PIO pio, uint sm, uint sideset_base)
pio_sm_set_jmp_pin (PIO pio, uint sm, uint pin)Oh, and there is a special “jmp” pin too which is kind of a 5th group all on its own, independent of the other four.
This pattern is mirrored for other PIO functions too, for example, the sm_config_ range of functions to update a state machine configuration:
sm_config_set_xxx_pin_base (pio_sm_config *c, uint xxx_base)
sm_config_set_xxx_pin_count (pio_sm_config *c, uint xxx_count)
sm_config_set_xxx_pins (pio_sm_config *c, uint xxx_base, uint xxx_count)
xxx = out, in, set, sidestepSo each of the pin groups has a set of APIs functions that act directly on the state machine (pio_sm_set_xxx as described first) and a set that acts on a state machine’s configuration (sm_config_set_xxx as described above).
There is also a third set that encodes actual PIO instructions related to the pin groups, although this isn’t a direct one-for-one with the previous two, as it relates to the actual PIO instruction set itself.
Some examples:
pio_encode_in (enum pio_src_dest src, uint count) // in src,cnt
pio_encode_out (enum pio_src_dest dest, uint count) // out dst,cnt
pio_encode_set (enum pio_src_dest dest, uint value) // set dst,val
pio_encode_sideset (uint sideset_bit_count, uint value)
pio_encode_jmp_pin (uint addr) // jmp pin,adrNote it is worth adding at this point that although there are API functions for all of the above, it is also possible to directly set state machine parameters and operational configuration using direct access to state machine registers. For more, see section “11.7 List of Registers” in the RP2350 datasheet.
For some information on the various layers in the SDK, see “2.3 SDK Library Structure” in the Raspberry Pi Pico C/C++ SDK.
Hello PIO
I’m starting off with a simple pulse on a GPIO pin and will be using the online PIO assembler from https://wokwi.com/tools/pioasm to build it.
My PIO Source:
.program pulse
.wrap_target
set pins, 1 [3] // 4 cycles
set pins, 0 [11] // 12 cycles
.wrap
% c-sdk {
static inline void pulse_program_init(PIO pio, uint sm, uint offset, uint pin) {
pio_sm_config c = pulse_program_get_default_config(offset);
// set_base=pin, count=1
sm_config_set_set_pins(&c, pin, 1);
pio_gpio_init(pio, pin);
// pins_base=pin, pin_count=1, is_out=true
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
// 440 Hz pulse over 16 cycles
float div = (float)clock_get_hz(clk_sys) / (440.0 * 16.0);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}The online assembler turns the above into the following, which is pasted into a pulse_pio.h file within an Arduino sketch.
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// ----- //
// pulse //
// ----- //
#define pulse_wrap_target 0
#define pulse_wrap 1
static const uint16_t pulse_program_instructions[] = {
// .wrap_target
0xe301, // 0: set pins, 1 [3]
0xeb00, // 1: set pins, 0 [11]
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program pulse_program = {
.instructions = pulse_program_instructions,
.length = 2,
.origin = -1,
};
static inline pio_sm_config pulse_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + pulse_wrap_target,
offset + pulse_wrap);
return c;
}
static inline void pulse_program_init(PIO pio, uint sm, uint offset, uint pin) {
pio_sm_config c = pulse_program_get_default_config(offset);
// set_base=pin, count=1
sm_config_set_set_pins(&c, pin, 1);
pio_gpio_init(pio, pin);
// pins_base=pin, pin_count=1, is_out=true
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
// 440 Hz pulse over 16 cycles
float div = (float)clock_get_hz(clk_sys) / (440.0 * 16.0);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
#endifAdding the appropriate additional PIO initialisation code to my previous test sketch now gives me the following complete code:
#include <PIOProgram.h>
#include "pulse_pio.h"
#define PULSE_PIN 2
void setup() {
Serial.begin(9600);
pinMode (LED_BUILTIN, OUTPUT);
PIO pio;
uint sm, offset;
if (!pio_claim_free_sm_and_add_program(&pulse_program,
&pio, &sm, &offset)) {
for (;;) {
Serial.print("No PIO or SM");
delay(10000);
}
}
pulse_program_init(pio, sm, offset, PULSE_PIN);
}
unsigned counter;
void loop() {
Serial.println(counter);
counter++;
delay(1000);
digitalWrite (LED_BUILTIN, (counter & 1));
}Notes:
- As I’m using “set” in the pio program I need to use the “set” group of pins in the various API calls – hence the use of sm_config_set_set_pins() which configures which pins to use with the set command. In this case, just one pin determined by the “pin” parameter.
- I’m using the wait [] instructions to put the pulse HIGH for 4 cycles and LOW for 12 cycles, giving 16 cycles in total.
- The waiting cycles have to account for the single cycle of the actual executed instruction, hence using [3] and [11].
- When setting the clock divisor, I’m dividing the system frequency by my required frequency * 16 as there are 16 cycles in the complete program.
- When I had a single cycle HIGH and 3 cycles LOW and then used the value 440.0 * 4.0 I wasn’t getting an accurate frequency (I was getting ~1.9K rather than 440). I’m guessing (I haven’t done the maths) this was overflowing the integer part of the divisor maybe.
The PIO and state machine used are allocated dynamically by the system using pio_claim_free_sm_and_add_program(). The first version had hard-coded PIO 0, state machine 0:
PIO pio = pio0;
int sm = 0;
uint offset = pio_add_program(pio, &pulse_program);The final result can be seen on the oscilloscope trace below.
PIO API Organisation
The PIO hardware documentation describes two modules for PIO APIs:
- sm_config – everything related to setting up state machines prior to setting them running. All functions prefaced with sm_config_set_xxxx().
- pio_instructions – ways to actually run state machine instructions at run time. All functions are prefaced with pio_encode_xxxx().
But there are also the direct state machine APIs which have the form pio_(sm|get|gpio|other)_. These appear to have several functional groups as follows:
- PIO control: manage instances of PIOs and state machines within them.
- Programs and instruction memory: add, remove, write to, determine offsets in, and so on.
- GPIO pins: configure, attach to state machines, set ranges, etc.
- State machines: set parameters associated with state machines; configure IRQs; clock divider; control the sm; set details for execution such as pins, wrap, shifts, and so on.
- Data interface to state machines: push and pull data; access fifos; etc.
Unfortunately there isn’t a particularly consistent API naming convention that I’ve spotted for the pio_ functions apart from an awful lot of them start pio_sm_. Apart from the state machines functions that don’t…
Conclusion
I’ve now been through the theory and a real, albeit simple, application and am feeling like I understand a lot more what is going on now. I still am somewhat bewildered by the huge array of API calls and do feel like they could be grouped together somehow to make them more accessible to people who haven’t swallowed the entire chip datasheet and SDK guidebooks…
But yes, I’m slowly starting to feel like I’m getting to grips with PIO a bit more now. I want to do something that now grabs some input from the GPIO and sticks it into memory, ideally using the DMA system, so that is probably where I’ll go next.
Kevin
#pio #raspberryPiPico #rp2040 #rp2350 -
PIO on the Raspberry Pi Pico – Part 2
Having got all the theory out of the way in PIO on the Raspberry Pi Pico now is the time to actually start programming. Whilst I have the option of using the C/C++ SDK or one of the Python variants, I’m particularly interested in getting it going from within the Arduino environment, just because that is where I do pretty much all of my other microcontroller messing about.
I’m not using the official Arduino for Pico support though, I’m using Earl Philhower’s version from here: https://github.com/earlephilhower/arduino-pico
Pico Arduino Getting Started
Before getting too far into PIO land, there are a few things to note about using the unofficial Arduino Pico core with the Raspberry Pi Pico.
On first boot, hold down the BOOT switch and the Pico will be detected as a “UF2 Board”. This will allow the first upload to take place (more here). I’ve selected “Raspberry Pi Pico” or “Raspberry Pi Pico 2” as appropriate for the board.
Prior to the first download, the configuration should set the Debug Port to Serial. Then once the first sketch is downloaded the board can be redetected via a serial link which will allow both Serial.print() and automatic reset on download of new sketches.
Aside: there are three serial ports (more here):
- Serial – the USB serial port – the one used here
- Serial1 – UART0
- Serial2 – UART1
Here is a simple starter program to make sure everything is working:
void setup() {
Serial.begin(9600);
pinMode (LED_BUILTIN, OUTPUT);
}
unsigned counter;
void loop() {
Serial.println(counter);
counter++;
delay(1000);
digitalWrite (LED_BUILTIN, (counter & 1));
}Assuming everything is working, every second the LED will flash on or off and the counter value will be printed to the serial monitor.
Some PIO API Principles
Pin groups are an important idea when dealing with PIO state machines.
There are four different sets of pins that can be enacted upon via the PIO system: OUT, IN, SET and Sidestep. Each has its own group of API functions. For example, to set the start and number of pins in a specific group there are the following functions (from here):
pio_sm_set_out_pins (PIO pio, uint sm, uint out_base, uint out_count)
pio_sm_set_set_pins (PIO pio, uint sm, uint set_base, uint set_count)
pio_sm_set_in_pins (PIO pio, uint sm, uint in_base)
pio_sm_set_sideset_pins (PIO pio, uint sm, uint sideset_base)
pio_sm_set_jmp_pin (PIO pio, uint sm, uint pin)Oh, and there is a special “jmp” pin too which is kind of a 5th group all on its own, independent of the other four.
This pattern is mirrored for other PIO functions too, for example, the sm_config_ range of functions to update a state machine configuration:
sm_config_set_xxx_pin_base (pio_sm_config *c, uint xxx_base)
sm_config_set_xxx_pin_count (pio_sm_config *c, uint xxx_count)
sm_config_set_xxx_pins (pio_sm_config *c, uint xxx_base, uint xxx_count)
xxx = out, in, set, sidestepSo each of the pin groups has a set of APIs functions that act directly on the state machine (pio_sm_set_xxx as described first) and a set that acts on a state machine’s configuration (sm_config_set_xxx as described above).
There is also a third set that encodes actual PIO instructions related to the pin groups, although this isn’t a direct one-for-one with the previous two, as it relates to the actual PIO instruction set itself.
Some examples:
pio_encode_in (enum pio_src_dest src, uint count) // in src,cnt
pio_encode_out (enum pio_src_dest dest, uint count) // out dst,cnt
pio_encode_set (enum pio_src_dest dest, uint value) // set dst,val
pio_encode_sideset (uint sideset_bit_count, uint value)
pio_encode_jmp_pin (uint addr) // jmp pin,adrNote it is worth adding at this point that although there are API functions for all of the above, it is also possible to directly set state machine parameters and operational configuration using direct access to state machine registers. For more, see section “11.7 List of Registers” in the RP2350 datasheet.
Hello PIO
I’m starting off with a simple pulse on a GPIO pin and will be using the online PIO assembler from https://wokwi.com/tools/pioasm to build it.
My PIO Source:
.program pulse
.wrap_target
set pins, 1 [3] // 4 cycles
set pins, 0 [11] // 12 cycles
.wrap
% c-sdk {
static inline void pulse_program_init(PIO pio, uint sm, uint offset, uint pin) {
pio_sm_config c = pulse_program_get_default_config(offset);
// set_base=pin, count=1
sm_config_set_set_pins(&c, pin, 1);
pio_gpio_init(pio, pin);
// pins_base=pin, pin_count=1, is_out=true
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
// 440 Hz pulse over 16 cycles
float div = (float)clock_get_hz(clk_sys) / (440.0 * 16.0);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}The online assembler turns the above into the following, which is pasted into a pulse_pio.h file within an Arduino sketch.
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// ----- //
// pulse //
// ----- //
#define pulse_wrap_target 0
#define pulse_wrap 1
static const uint16_t pulse_program_instructions[] = {
// .wrap_target
0xe301, // 0: set pins, 1 [3]
0xeb00, // 1: set pins, 0 [11]
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program pulse_program = {
.instructions = pulse_program_instructions,
.length = 2,
.origin = -1,
};
static inline pio_sm_config pulse_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + pulse_wrap_target, offset + pulse_wrap);
return c;
}
static inline void pulse_program_init(PIO pio, uint sm, uint offset, uint pin) {
pio_sm_config c = pulse_program_get_default_config(offset);
// set_base=pin, count=1
sm_config_set_set_pins(&c, pin, 1);
pio_gpio_init(pio, pin);
// pins_base=pin, pin_count=1, is_out=true
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
// 440 Hz pulse over 16 cycles
float div = (float)clock_get_hz(clk_sys) / (440.0 * 16.0);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
#endifAdding the appropriate additional PIO initialisation code to my previous test sketch now gives me the following complete code:
#include <PIOProgram.h>
#include "pulse_pio.h"
#define PULSE_PIN 2
void setup() {
Serial.begin(9600);
pinMode (LED_BUILTIN, OUTPUT);
PIO pio;
uint sm, offset;
if (!pio_claim_free_sm_and_add_program(&pulse_program, &pio, &sm, &offset)) {
for (;;) {
Serial.print("No PIO or SM");
delay(10000);
}
}
pulse_program_init(pio, sm, offset, PULSE_PIN);
}
unsigned counter;
void loop() {
Serial.println(counter);
counter++;
delay(1000);
digitalWrite (LED_BUILTIN, (counter & 1));
}Notes:
- As I’m using “set” in the pio program I need to use the “set” group of pins in the various API calls – hence the use of sm_config_set_set_pins() which configures which pins to use with the set command. In this case, just one pin determined by the “pin” parameter.
- I’m using the wait [] instructions to put the pulse HIGH for 4 cycles and LOW for 12 cycles, giving 16 cycles in total.
- The waiting cycles have to account for the single cycle of the actual executed instruction, hence using [3] and [11].
- When setting the clock divisor, I’m dividing the system frequency by my required frequency * 16 as there are 16 cycles in the complete program.
- When I had a single cycle HIGH and 3 cycles LOW and then used the value 440.0 * 4.0 I wasn’t getting an accurate frequency (I was getting ~1.9K rather than 440). I’m guessing (I haven’t done the maths) this was overflowing the integer part of the divisor maybe.
The PIO and state machine used are allocated dynamically by the system using pio_claim_free_sm_and_add_program(). The first version had hard-coded PIO 0, state machine 0:
PIO pio = pio0;
int sm = 0;
uint offset = pio_add_program(pio, &pulse_program);The final result can be seen on the oscilloscope trace below.
PIO API Organisation
The PIO hardware documentation describes two modules for PIO APIs:
- sm_config – everything related to setting up state machines prior to setting them running. All functions prefaced with sm_config_set_xxxx().
- pio_instructions – ways to actually run state machine instructions at run time. All functions are prefaced with pio_encode_xxxx().
But there are also the direct state machine APIs which have the form pio_(sm|get|gpio|other)_. These appear to have several functional groups as follows:
- PIO control: manage instances of PIOs and state machines within them.
- Programs and instruction memory: add, remove, write to, determine offsets in, and so on.
- GPIO pins: configure, attach to state machines, set ranges, etc.
- State machines: set parameters associated with state machines; configure IRQs; clock divider; control the sm; set details for execution such as pins, wrap, shifts, and so on.
- Data interface to state machines: push and pull data; access fifos; etc.
Unfortunately there isn’t a particularly consistent API naming convention that I’ve spotted for the pio_ functions apart from an awful lot of them start pio_sm_. Apart from the state machines functions that don’t…
Conclusion
I’ve now been through the theory and a real, albeit simple, application and am feeling like I understand a lot more what is going on now. I still am somewhat bewildered by the huge array of API calls and do feel like they could be grouped together somehow to make them more accessible to people who haven’t swallowed the entire chip datasheet and SDK guidebooks…
But yes, I’m slowly starting to feel like I’m getting to grips with PIO a bit more now. I want to do something that now grabs some input from the GPIO and sticks it into memory, ideally using the DMA system, so that is probably where I’ll go next.
Kevin
#pio #raspberryPiPico #rp2040 #rp2350 -
PIO on the Raspberry Pi Pico – Part 2
Having got all the theory out of the way in PIO on the Raspberry Pi Pico now is the time to actually start programming. Whilst I have the option of using the C/C++ SDK or one of the Python variants, I’m particularly interested in getting it going from within the Arduino environment, just because that is where I do pretty much all of my other microcontroller messing about.
I’m not using the official Arduino for Pico support though, I’m using Earl Philhower’s version from here: https://github.com/earlephilhower/arduino-pico
Pico Arduino Getting Started
Before getting too far into PIO land, there are a few things to note about using the unofficial Arduino Pico core with the Raspberry Pi Pico.
On first boot, hold down the BOOT switch and the Pico will be detected as a “UF2 Board”. This will allow the first upload to take place (more here). I’ve selected “Raspberry Pi Pico” or “Raspberry Pi Pico 2” as appropriate for the board.
Prior to the first download, the configuration should set the Debug Port to Serial. Then once the first sketch is downloaded the board can be redetected via a serial link which will allow both Serial.print() and automatic reset on download of new sketches.
Aside: there are three serial ports (more here):
- Serial – the USB serial port – the one used here
- Serial1 – UART0
- Serial2 – UART1
Here is a simple starter program to make sure everything is working:
void setup() {
Serial.begin(9600);
pinMode (LED_BUILTIN, OUTPUT);
}
unsigned counter;
void loop() {
Serial.println(counter);
counter++;
delay(1000);
digitalWrite (LED_BUILTIN, (counter & 1));
}Assuming everything is working, every second the LED will flash on or off and the counter value will be printed to the serial monitor.
Some PIO API Principles
Pin groups are an important idea when dealing with PIO state machines.
There are four different sets of pins that can be enacted upon via the PIO system: OUT, IN, SET and Sidestep. Each has its own group of API functions. For example, to set the start and number of pins in a specific group there are the following functions (from here):
pio_sm_set_out_pins (PIO pio, uint sm, uint out_base, uint out_count)
pio_sm_set_set_pins (PIO pio, uint sm, uint set_base, uint set_count)
pio_sm_set_in_pins (PIO pio, uint sm, uint in_base)
pio_sm_set_sideset_pins (PIO pio, uint sm, uint sideset_base)
pio_sm_set_jmp_pin (PIO pio, uint sm, uint pin)Oh, and there is a special “jmp” pin too which is kind of a 5th group all on its own, independent of the other four.
This pattern is mirrored for other PIO functions too, for example, the sm_config_ range of functions to update a state machine configuration:
sm_config_set_xxx_pin_base (pio_sm_config *c, uint xxx_base)
sm_config_set_xxx_pin_count (pio_sm_config *c, uint xxx_count)
sm_config_set_xxx_pins (pio_sm_config *c, uint xxx_base, uint xxx_count)
xxx = out, in, set, sidestepSo each of the pin groups has a set of APIs functions that act directly on the state machine (pio_sm_set_xxx as described first) and a set that acts on a state machine’s configuration (sm_config_set_xxx as described above).
There is also a third set that encodes actual PIO instructions related to the pin groups, although this isn’t a direct one-for-one with the previous two, as it relates to the actual PIO instruction set itself.
Some examples:
pio_encode_in (enum pio_src_dest src, uint count) // in src,cnt
pio_encode_out (enum pio_src_dest dest, uint count) // out dst,cnt
pio_encode_set (enum pio_src_dest dest, uint value) // set dst,val
pio_encode_sideset (uint sideset_bit_count, uint value)
pio_encode_jmp_pin (uint addr) // jmp pin,adrNote it is worth adding at this point that although there are API functions for all of the above, it is also possible to directly set state machine parameters and operational configuration using direct access to state machine registers. For more, see section “11.7 List of Registers” in the RP2350 datasheet.
Hello PIO
I’m starting off with a simple pulse on a GPIO pin and will be using the online PIO assembler from https://wokwi.com/tools/pioasm to build it.
My PIO Source:
.program pulse
.wrap_target
set pins, 1 [3] // 4 cycles
set pins, 0 [11] // 12 cycles
.wrap
% c-sdk {
static inline void pulse_program_init(PIO pio, uint sm, uint offset, uint pin) {
pio_sm_config c = pulse_program_get_default_config(offset);
// set_base=pin, count=1
sm_config_set_set_pins(&c, pin, 1);
pio_gpio_init(pio, pin);
// pins_base=pin, pin_count=1, is_out=true
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
// 440 Hz pulse over 16 cycles
float div = (float)clock_get_hz(clk_sys) / (440.0 * 16.0);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}The online assembler turns the above into the following, which is pasted into a pulse_pio.h file within an Arduino sketch.
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// ----- //
// pulse //
// ----- //
#define pulse_wrap_target 0
#define pulse_wrap 1
static const uint16_t pulse_program_instructions[] = {
// .wrap_target
0xe301, // 0: set pins, 1 [3]
0xeb00, // 1: set pins, 0 [11]
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program pulse_program = {
.instructions = pulse_program_instructions,
.length = 2,
.origin = -1,
};
static inline pio_sm_config pulse_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + pulse_wrap_target, offset + pulse_wrap);
return c;
}
static inline void pulse_program_init(PIO pio, uint sm, uint offset, uint pin) {
pio_sm_config c = pulse_program_get_default_config(offset);
// set_base=pin, count=1
sm_config_set_set_pins(&c, pin, 1);
pio_gpio_init(pio, pin);
// pins_base=pin, pin_count=1, is_out=true
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
// 440 Hz pulse over 16 cycles
float div = (float)clock_get_hz(clk_sys) / (440.0 * 16.0);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
#endifAdding the appropriate additional PIO initialisation code to my previous test sketch now gives me the following complete code:
#include <PIOProgram.h>
#include "pulse_pio.h"
#define PULSE_PIN 2
void setup() {
Serial.begin(9600);
pinMode (LED_BUILTIN, OUTPUT);
PIO pio;
uint sm, offset;
if (!pio_claim_free_sm_and_add_program(&pulse_program, &pio, &sm, &offset)) {
for (;;) {
Serial.print("No PIO or SM");
delay(10000);
}
}
pulse_program_init(pio, sm, offset, PULSE_PIN);
}
unsigned counter;
void loop() {
Serial.println(counter);
counter++;
delay(1000);
digitalWrite (LED_BUILTIN, (counter & 1));
}Notes:
- As I’m using “set” in the pio program I need to use the “set” group of pins in the various API calls – hence the use of sm_config_set_set_pins() which configures which pins to use with the set command. In this case, just one pin determined by the “pin” parameter.
- I’m using the wait [] instructions to put the pulse HIGH for 4 cycles and LOW for 12 cycles, giving 16 cycles in total.
- The waiting cycles have to account for the single cycle of the actual executed instruction, hence using [3] and [11].
- When setting the clock divisor, I’m dividing the system frequency by my required frequency * 16 as there are 16 cycles in the complete program.
- When I had a single cycle HIGH and 3 cycles LOW and then used the value 440.0 * 4.0 I wasn’t getting an accurate frequency (I was getting ~1.9K rather than 440). I’m guessing (I haven’t done the maths) this was overflowing the integer part of the divisor maybe.
The PIO and state machine used are allocated dynamically by the system using pio_claim_free_sm_and_add_program(). The first version had hard-coded PIO 0, state machine 0:
PIO pio = pio0;
int sm = 0;
uint offset = pio_add_program(pio, &pulse_program);The final result can be seen on the oscilloscope trace below.
PIO API Organisation
The PIO hardware documentation describes two modules for PIO APIs:
- sm_config – everything related to setting up state machines prior to setting them running. All functions prefaced with sm_config_set_xxxx().
- pio_instructions – ways to actually run state machine instructions at run time. All functions are prefaced with pio_encode_xxxx().
But there are also the direct state machine APIs which have the form pio_(sm|get|gpio|other)_. These appear to have several functional groups as follows:
- PIO control: manage instances of PIOs and state machines within them.
- Programs and instruction memory: add, remove, write to, determine offsets in, and so on.
- GPIO pins: configure, attach to state machines, set ranges, etc.
- State machines: set parameters associated with state machines; configure IRQs; clock divider; control the sm; set details for execution such as pins, wrap, shifts, and so on.
- Data interface to state machines: push and pull data; access fifos; etc.
Unfortunately there isn’t a particularly consistent API naming convention that I’ve spotted for the pio_ functions apart from an awful lot of them start pio_sm_. Apart from the state machines functions that don’t…
Conclusion
I’ve now been through the theory and a real, albeit simple, application and am feeling like I understand a lot more what is going on now. I still am somewhat bewildered by the huge array of API calls and do feel like they could be grouped together somehow to make them more accessible to people who haven’t swallowed the entire chip datasheet and SDK guidebooks…
But yes, I’m slowly starting to feel like I’m getting to grips with PIO a bit more now. I want to do something that now grabs some input from the GPIO and sticks it into memory, ideally using the DMA system, so that is probably where I’ll go next.
Kevin
#pio #raspberryPiPico #rp2040 #rp2350 -
PIO on the Raspberry Pi Pico – Part 2
Having got all the theory out of the way in PIO on the Raspberry Pi Pico now is the time to actually start programming. Whilst I have the option of using the C/C++ SDK or one of the Python variants, I’m particularly interested in getting it going from within the Arduino environment, just because that is where I do pretty much all of my other microcontroller messing about.
I’m not using the official Arduino for Pico support though, I’m using Earl Philhower’s version from here: https://github.com/earlephilhower/arduino-pico
Pico Arduino Getting Started
Before getting too far into PIO land, there are a few things to note about using the unofficial Arduino Pico core with the Raspberry Pi Pico.
On first boot, hold down the BOOT switch and the Pico will be detected as a “UF2 Board”. This will allow the first upload to take place (more here). I’ve selected “Raspberry Pi Pico” or “Raspberry Pi Pico 2” as appropriate for the board.
Prior to the first download, the configuration should set the Debug Port to Serial. Then once the first sketch is downloaded the board can be redetected via a serial link which will allow both Serial.print() and automatic reset on download of new sketches.
Aside: there are three serial ports (more here):
- Serial – the USB serial port – the one used here
- Serial1 – UART0
- Serial2 – UART1
Here is a simple starter program to make sure everything is working:
void setup() {
Serial.begin(9600);
pinMode (LED_BUILTIN, OUTPUT);
}
unsigned counter;
void loop() {
Serial.println(counter);
counter++;
delay(1000);
digitalWrite (LED_BUILTIN, (counter & 1));
}Assuming everything is working, every second the LED will flash on or off and the counter value will be printed to the serial monitor.
Some PIO API Principles
Pin groups are an important idea when dealing with PIO state machines.
There are four different sets of pins that can be enacted upon via the PIO system: OUT, IN, SET and Sidestep. Each has its own group of API functions. For example, to set the start and number of pins in a specific group there are the following functions (from here):
pio_sm_set_out_pins (PIO pio, uint sm, uint out_base, uint out_count)
pio_sm_set_set_pins (PIO pio, uint sm, uint set_base, uint set_count)
pio_sm_set_in_pins (PIO pio, uint sm, uint in_base)
pio_sm_set_sideset_pins (PIO pio, uint sm, uint sideset_base)
pio_sm_set_jmp_pin (PIO pio, uint sm, uint pin)Oh, and there is a special “jmp” pin too which is kind of a 5th group all on its own, independent of the other four.
This pattern is mirrored for other PIO functions too, for example, the sm_config_ range of functions to update a state machine configuration:
sm_config_set_xxx_pin_base (pio_sm_config *c, uint xxx_base)
sm_config_set_xxx_pin_count (pio_sm_config *c, uint xxx_count)
sm_config_set_xxx_pins (pio_sm_config *c, uint xxx_base, uint xxx_count)
xxx = out, in, set, sidestepSo each of the pin groups has a set of APIs functions that act directly on the state machine (pio_sm_set_xxx as described first) and a set that acts on a state machine’s configuration (sm_config_set_xxx as described above).
There is also a third set that encodes actual PIO instructions related to the pin groups, although this isn’t a direct one-for-one with the previous two, as it relates to the actual PIO instruction set itself.
Some examples:
pio_encode_in (enum pio_src_dest src, uint count) // in src,cnt
pio_encode_out (enum pio_src_dest dest, uint count) // out dst,cnt
pio_encode_set (enum pio_src_dest dest, uint value) // set dst,val
pio_encode_sideset (uint sideset_bit_count, uint value)
pio_encode_jmp_pin (uint addr) // jmp pin,adrNote it is worth adding at this point that although there are API functions for all of the above, it is also possible to directly set state machine parameters and operational configuration using direct access to state machine registers. For more, see section “11.7 List of Registers” in the RP2350 datasheet.
For some information on the various layers in the SDK, see “2.3 SDK Library Structure” in the Raspberry Pi Pico C/C++ SDK.
Hello PIO
I’m starting off with a simple pulse on a GPIO pin and will be using the online PIO assembler from https://wokwi.com/tools/pioasm to build it.
My PIO Source:
.program pulse
.wrap_target
set pins, 1 [3] // 4 cycles
set pins, 0 [11] // 12 cycles
.wrap
% c-sdk {
static inline void pulse_program_init(PIO pio, uint sm, uint offset, uint pin) {
pio_sm_config c = pulse_program_get_default_config(offset);
// set_base=pin, count=1
sm_config_set_set_pins(&c, pin, 1);
pio_gpio_init(pio, pin);
// pins_base=pin, pin_count=1, is_out=true
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
// 440 Hz pulse over 16 cycles
float div = (float)clock_get_hz(clk_sys) / (440.0 * 16.0);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}The online assembler turns the above into the following, which is pasted into a pulse_pio.h file within an Arduino sketch.
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// ----- //
// pulse //
// ----- //
#define pulse_wrap_target 0
#define pulse_wrap 1
static const uint16_t pulse_program_instructions[] = {
// .wrap_target
0xe301, // 0: set pins, 1 [3]
0xeb00, // 1: set pins, 0 [11]
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program pulse_program = {
.instructions = pulse_program_instructions,
.length = 2,
.origin = -1,
};
static inline pio_sm_config pulse_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + pulse_wrap_target,
offset + pulse_wrap);
return c;
}
static inline void pulse_program_init(PIO pio, uint sm, uint offset, uint pin) {
pio_sm_config c = pulse_program_get_default_config(offset);
// set_base=pin, count=1
sm_config_set_set_pins(&c, pin, 1);
pio_gpio_init(pio, pin);
// pins_base=pin, pin_count=1, is_out=true
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
// 440 Hz pulse over 16 cycles
float div = (float)clock_get_hz(clk_sys) / (440.0 * 16.0);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
#endifAdding the appropriate additional PIO initialisation code to my previous test sketch now gives me the following complete code:
#include <PIOProgram.h>
#include "pulse_pio.h"
#define PULSE_PIN 2
void setup() {
Serial.begin(9600);
pinMode (LED_BUILTIN, OUTPUT);
PIO pio;
uint sm, offset;
if (!pio_claim_free_sm_and_add_program(&pulse_program,
&pio, &sm, &offset)) {
for (;;) {
Serial.print("No PIO or SM");
delay(10000);
}
}
pulse_program_init(pio, sm, offset, PULSE_PIN);
}
unsigned counter;
void loop() {
Serial.println(counter);
counter++;
delay(1000);
digitalWrite (LED_BUILTIN, (counter & 1));
}Notes:
- As I’m using “set” in the pio program I need to use the “set” group of pins in the various API calls – hence the use of sm_config_set_set_pins() which configures which pins to use with the set command. In this case, just one pin determined by the “pin” parameter.
- I’m using the wait [] instructions to put the pulse HIGH for 4 cycles and LOW for 12 cycles, giving 16 cycles in total.
- The waiting cycles have to account for the single cycle of the actual executed instruction, hence using [3] and [11].
- When setting the clock divisor, I’m dividing the system frequency by my required frequency * 16 as there are 16 cycles in the complete program.
- When I had a single cycle HIGH and 3 cycles LOW and then used the value 440.0 * 4.0 I wasn’t getting an accurate frequency (I was getting ~1.9K rather than 440). I’m guessing (I haven’t done the maths) this was overflowing the integer part of the divisor maybe.
The PIO and state machine used are allocated dynamically by the system using pio_claim_free_sm_and_add_program(). The first version had hard-coded PIO 0, state machine 0:
PIO pio = pio0;
int sm = 0;
uint offset = pio_add_program(pio, &pulse_program);The final result can be seen on the oscilloscope trace below.
PIO API Organisation
The PIO hardware documentation describes two modules for PIO APIs:
- sm_config – everything related to setting up state machines prior to setting them running. All functions prefaced with sm_config_set_xxxx().
- pio_instructions – ways to actually run state machine instructions at run time. All functions are prefaced with pio_encode_xxxx().
But there are also the direct state machine APIs which have the form pio_(sm|get|gpio|other)_. These appear to have several functional groups as follows:
- PIO control: manage instances of PIOs and state machines within them.
- Programs and instruction memory: add, remove, write to, determine offsets in, and so on.
- GPIO pins: configure, attach to state machines, set ranges, etc.
- State machines: set parameters associated with state machines; configure IRQs; clock divider; control the sm; set details for execution such as pins, wrap, shifts, and so on.
- Data interface to state machines: push and pull data; access fifos; etc.
Unfortunately there isn’t a particularly consistent API naming convention that I’ve spotted for the pio_ functions apart from an awful lot of them start pio_sm_. Apart from the state machines functions that don’t…
Conclusion
I’ve now been through the theory and a real, albeit simple, application and am feeling like I understand a lot more what is going on now. I still am somewhat bewildered by the huge array of API calls and do feel like they could be grouped together somehow to make them more accessible to people who haven’t swallowed the entire chip datasheet and SDK guidebooks…
But yes, I’m slowly starting to feel like I’m getting to grips with PIO a bit more now. I want to do something that now grabs some input from the GPIO and sticks it into memory, ideally using the DMA system, so that is probably where I’ll go next.
Kevin
#pio #raspberryPiPico #rp2040 #rp2350 -
Been toying with the idea of using the rp2350b in a pci slot theoretically the pio can handle the 33Mhz bus but then you are only left with 12 gpio for everything else. Don't know the pci spec but Id guess it requires at least a handful of other gpio.
Maybe it could be used as a usb port or a spi sd card reader.
I don't even have a PC with a pci slot but I can dream.
#rp2350 #pci -
Poll for a bit of fun. Feel free to boost!
I've got a #RaspberryPiPico project written in #Arduino C. It talks to a Psion SSD and dumps the contents over USB serial. It can (theoretically) act as a full controller to tag an SSD, too.
I feel like I should port it to the proper Pico SDK. But I also know there are other options. And I'm intrigued to know what Fedi would do.
What should I rewrite this firmware in?
-
PIO on the Raspberry Pi Pico
Every time I’ve started to approach the use of the programmable IO (PIO) subsystem on the RP2040 or RP2350 (as used on the Raspberry Pi Pico), I’ve found myself essentially starting from scratch again and the examples quite opaque to me.
So this time as I’ve worked through it yet again, I’ve decided to write it all down 🙂
Here are some existing tutorials and projects that talk about getting going with the PIO:
- RP2350 Datasheet – “Chapter 11: PIO”
- Raspberry Pi Pico-series C/C++ SDK – “Chapter 3: Using Programmable I/O (PIO)”.
- Hackspace Magazine Issue 39 – Raspberry Pi Pico Programmable I/O (page 40).
- Stephen Smith’s “I/O Co-processing on the Raspberry Pi Pico”
- PIO Examples: https://github.com/raspberrypi/pico-examples?tab=readme-ov-file#pio
Assembling PIO Code
The PIO has its own bespoke micro-instruction set that is very similar to many types of assembly language and it requires its own pio assembler to process it. The basic sequence is as follows:
- PIO -> pioasm -> C header file -> include in C/C++ project and build
There are options for writing PIO in both Micropython and Circuitpython, which I have done in the past, but I’m sticking with the C route here. This requires the pioasm to take PIO code and produce a C header file that can then be included in a C project.
To use the RP2040/2350 Arduino environment, it is necessary to process PIO independently and then add the C file to the Arduino project. The Raspberry Pi C/C++ SDK can process PIO files directly as part of the main build.
There is also an option to use hardware SDK functions for dynamic creation of PIO code at runtime. The functions are a series of pio_encode_XX() functions representing the different PIO instructions as listed here: https://www.raspberrypi.com/documentation/pico-sdk/hardware.html#group_pio_instructions
There are two other novel approaches I found so far too:
- Wokwi online PIO assembler: https://wokwi.com/tools/pioasm
- Piersrocks’ runtime PIO assembler: https://github.com/piersfinlayson/apio
The first is an online editing environment that creates the required processed PIO related code for the C/C++ SDK or Python which can then be included in your build environment as required.
The second is an alternative run-time approach that uses a range of C macros to allow the “assembling” of PIO code as part of the run-time execution. It does this by directly creating the HEX equivalents of PIO instructions, thereby effectively assembling in the fly. This means that the PIO code can be customised to the specific run-time situation.
At this stage I’m not sure what it gives over using the pio_encode_ SDK functions directly. I do note however there is an equivalent PIO emulator which means this approach will run equally well on real hardware or in emulation. I’ve bookmarked this to come back to at some point.
Running PIO Code
Regardless of how the PIO instructions become code, to use them requires setting up and configuring the PIO state machines at run time as part of a project. A common approach is to include an initialisation function within the PIO code itself that is destined for passing straight through to the C/C++ SDK. This will have access to all definitions used within the PIO code and also allows the appropriate configuration information to remain encapsulated with the code.
But I have to admit I find there is an awful lot of assumed “magic” going on when configuring and getting running PIO programs and state machines. And whilst there are plenty of examples to study, I don’t find that they are written so as to teach. Consequently, I’ve noted the following as “reminders to self” on how to read some of the examples. It doesn’t help that the SDK function list is very long and there are several ways to achieve the same things.
Taking the PIO PWM code from the pico_examples as a starting point (https://github.com/raspberrypi/pico-examples/tree/master/pio/pwm), I’ve added in some comments containing the full function prototypes for some of the calls to make them a bit easier to walk through.
pwm.pio:
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
; Side-set pin 0 is used for PWM output
.pio_version 0 // only requires PIO version 0
.program pwm
.side_set 1 opt
pull noblock side 0 ; Pull from FIFO to OSR if available, else copy X to OSR.
mov x, osr ; Copy most-recently-pulled value back to scratch X
mov y, isr ; ISR contains PWM period. Y used as counter.
countloop:
jmp x!=y noset ; Set pin high if X == Y, keep the two paths length matched
jmp skip side 1
noset:
nop ; Single dummy cycle to keep the two paths the same length
skip:
jmp y-- countloop ; Loop until Y hits 0, then pull a fresh PWM value from FIFO
% c-sdk {
static inline void pwm_program_init(PIO pio, uint sm, uint offset, uint pin) {
// static void pio_gpio_init (PIO pio, uint pin)
pio_gpio_init(pio, pin);
// int pio_sm_set_consecutive_pindirs (PIO pio, uint sm, uint pins_base, uint pin_count, bool is_out)
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
// A piece of pioasm "magic" based on .program pwm (see following notes)
pio_sm_config c = pwm_program_get_default_config(offset);
// static void sm_config_set_sideset_pins (pio_sm_config *c, uint sideset_base)
sm_config_set_sideset_pins(&c, pin);
// int pio_sm_init (PIO pio, uint sm, uint initial_pc, const pio_sm_config *config)
pio_sm_init(pio, sm, offset, &c);
}
%}And its associated C code pwm.c:
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "pwm.pio.h"
// Write `period` to the input shift register
void pio_pwm_set_period(PIO pio, uint sm, uint32_t period) {
pio_sm_set_enabled(pio, sm, false);
pio_sm_put_blocking(pio, sm, period);
pio_sm_exec(pio, sm, pio_encode_pull(false, false));
pio_sm_exec(pio, sm, pio_encode_out(pio_isr, 32));
pio_sm_set_enabled(pio, sm, true);
}
// Write `level` to TX FIFO. State machine will copy this into X.
void pio_pwm_set_level(PIO pio, uint sm, uint32_t level) {
pio_sm_put_blocking(pio, sm, level);
}
int main() {
stdio_init_all();
#ifndef PICO_DEFAULT_LED_PIN
#warning pio/pwm example requires a board with a regular LED
puts("Default LED pin was not defined");
#else
// todo get free sm
PIO pio = pio0;
int sm = 0;
uint offset = pio_add_program(pio, &pwm_program);
printf("Loaded program at %d\n", offset);
pwm_program_init(pio, sm, offset, PICO_DEFAULT_LED_PIN);
pio_pwm_set_period(pio, sm, (1u << 16) - 1);
int level = 0;
while (true) {
printf("Level = %d\n", level);
pio_pwm_set_level(pio, sm, level * level);
level = (level + 1) % 256;
sleep_ms(10);
}
#endif
}There are a few key things to remember to make sense of these examples:
- The offset that is talked about is (I believe) the location within the shared 32 instruction program area and is used to refer back to the installed PIO program. It is returned from pio_add_program().
- A PIO .program directive becomes a default C like directive on processing by pioasm. This results in two obscure bits of “magic” coding going on meaning in this case that “.program pwm” in the PIO file becomes “pwm_program” in C/C++:
- pio_program_t pwm_program is a C structure which can then be referenced from the C code as shown in the line pio_add_program(pio, &pwm_program).
- static inline pio_sm_config pwm_program_get_default_config(uint offset) is a C function based on pio_get_default_sm_config() that returns the PIO configuration for the specific PIO program in question – in this case of course the pwm program.
- The use of .side_step opt means that not every PIO instruction has to have a side step instruction too.
- The PIO refers to an abstract group of pins, but it is the configuration which is part of the C/C++ SDK that determines which pins are used.
- The %c-sdk { … %} pairing signifies that this part of the PIO code will be passed straight onto the C/C++ SDK.
- There are multiple ways of initialising GPIO pins and directions. In this example it doesn’t use pindirs in the PIO code but uses pio_sm_set_consecutive_pindirs() in the C code.
- This example uses hardcoded references to PIO 0 and SM 0, but in many cases the PIO and SM would be chosen dynamically using API calls such as the following:
- pio_claim_free_sm_and_add_program ()
- pio_claim_free_sm_and_add_program_for_gpio_range()
- pio_claim_unused_sm()
- Each PIO program has a default configuration associated with it which can be updated. A typical pattern is shown here where the default configuration is grabbed using (in this case) pwm_program_get_default_config() and then updated by passing into following SDK calls.
- The state machine is finally set running using pio_sm_init();
There is one additional mix of techniques that is worth pulling out here. In the C code the function pio_pwm_set_period() is used to update the PWM period which it has to do by passing it into the SM via the FIFO. It is using some SM manipulation routines and then some inline, run-time PIO code, to achieve this.
void pio_pwm_set_period(PIO pio, uint sm, uint32_t period) {
pio_sm_set_enabled(pio, sm, false);
pio_sm_put_blocking(pio, sm, period);
pio_sm_exec(pio, sm, pio_encode_pull(false, false));
pio_sm_exec(pio, sm, pio_encode_out(pio_isr, 32));
pio_sm_set_enabled(pio, sm, true);
}Again some pretty confusing API calls, especially giving this is meant to be an example, but essentially what is going on (I think) is:
Disable the statemachine by using pio_sm_set_enabled(... false).
Push the period value into the TX FIFO, blocking if full to wait for it to be empty.
Execute two direct PIO instructions using pio_sm_exec():
This uses pio_encode_pull and pio_encode_out to run the following PIO code:
pull noblock ; non-blocking pull
out isr, 32 ; out 32 bits to the interrupt shift register
Re-enable he state machine using pio_sm_set_enabled(... true).By default anything sent to the FIFO is written to the X register and used to set the duty cycle of the PWM. But this code creates some temporary PIO code to receive the contents of the FIFO and put it into ISR instead. Of course it has to temporarily suspend the execution of the stored PIO code in order to do this.
I really dislike the nomenclature of “set enabled (false)” as an API approach. I’d much prefer to see something like pio_sm_enable() and pio_sm_disable() myself. I suppose they haven’t done this due to the large increase in API functions it creates.
I guess this is personal preference, but I do find that it adds to the opaqueness of much of the example code when it doesn’t read naturally.
So To Recap…
Writing PIO code can be done at build time (from Python or C/C++ using pioasm or an online assembler) or run time (using pio_encode_ functions or maybe APIO).
pioasm bridges the gap between PIO code and C/C++ including creating two magic C/C++ constructs: pwm_program for the code and pwm_program_get_default_config() to return the created PIO configuration.
PIO and SMs can be allocated by the system using a range of “claim” functions. There are 2 PIOs on the RP2040 and 3 on the RP2350, each with its own 32 instruction program memory and each with four state machines.
It can be useful to include an initialisation routine, that configures and starts the PIO program, within the PIO code for use from the C/C++ code using % c-sdk { … %}.
The PIO program is added into the system and given an offset in instruction memory using pio_add_program.
PIO code is very dense and often the functionality cannot be seen from the PIO code itself as it is defined by the PIO configuration – e.g. pins to use, frequency of execution, direction of shifts and so on.
I’ve not touched on it here, but the use of PIO and DMA (direct memory access) often go hand in hand to create completely CPU-free means of getting data in and out of a RP2040/RP2350 system. A really good example of this is Piers Rocks’ OneROM (see this video for a brilliant summary of how this works: https://www.youtube.com/watch?v=Y8RODQZM2HY).
Finally I need to remember that ISR stands for Input Shift Register and not Interrupt Service Routine…
Kevin
#pio #raspberryPiPico #rp2040 #rp2350 -
PIO on the Raspberry Pi Pico
Every time I’ve started to approach the use of the programmable IO (PIO) subsystem on the RP2040 or RP2350 (as used on the Raspberry Pi Pico), I’ve found myself essentially starting from scratch again and the examples quite opaque to me.
So this time as I’ve worked through it yet again, I’ve decided to write it all down 🙂
Here are some existing tutorials and projects that talk about getting going with the PIO:
- RP2350 Datasheet – “Chapter 11: PIO”
- Raspberry Pi Pico-series C/C++ SDK – “Chapter 3: Using Programmable I/O (PIO)”.
- Hackspace Magazine Issue 39 – Raspberry Pi Pico Programmable I/O (page 40).
- Stephen Smith’s “I/O Co-processing on the Raspberry Pi Pico”
- PIO Examples: https://github.com/raspberrypi/pico-examples?tab=readme-ov-file#pio
Assembling PIO Code
The PIO has its own bespoke micro-instruction set that is very similar to many types of assembly language and it requires its own pio assembler to process it. The basic sequence is as follows:
- PIO -> pioasm -> C header file -> include in C/C++ project and build
There are options for writing PIO in both Micropython and Circuitpython, which I have done in the past, but I’m sticking with the C route here. This requires the pioasm to take PIO code and produce a C header file that can then be included in a C project.
To use the RP2040/2350 Arduino environment, it is necessary to process PIO independently and then add the C file to the Arduino project. The Raspberry Pi C/C++ SDK can process PIO files directly as part of the main build.
There is also an option to use hardware SDK functions for dynamic creation of PIO code at runtime. The functions are a series of pio_encode_XX() functions representing the different PIO instructions as listed here: https://www.raspberrypi.com/documentation/pico-sdk/hardware.html#group_pio_instructions
There are two other novel approaches I found so far too:
- Wokwi online PIO assembler: https://wokwi.com/tools/pioasm
- Piersrocks’ runtime PIO assembler: https://github.com/piersfinlayson/apio
The first is an online editing environment that creates the required processed PIO related code for the C/C++ SDK or Python which can then be included in your build environment as required.
The second is an alternative run-time approach that uses a range of C macros to allow the “assembling” of PIO code as part of the run-time execution. It does this by directly creating the HEX equivalents of PIO instructions, thereby effectively assembling in the fly. This means that the PIO code can be customised to the specific run-time situation.
At this stage I’m not sure what it gives over using the pio_encode_ SDK functions directly. I do note however there is an equivalent PIO emulator which means this approach will run equally well on real hardware or in emulation. I’ve bookmarked this to come back to at some point.
Running PIO Code
Regardless of how the PIO instructions become code, to use them requires setting up and configuring the PIO state machines at run time as part of a project. A common approach is to include an initialisation function within the PIO code itself that is destined for passing straight through to the C/C++ SDK. This will have access to all definitions used within the PIO code and also allows the appropriate configuration information to remain encapsulated with the code.
But I have to admit I find there is an awful lot of assumed “magic” going on when configuring and getting running PIO programs and state machines. And whilst there are plenty of examples to study, I don’t find that they are written so as to teach. Consequently, I’ve noted the following as “reminders to self” on how to read some of the examples. It doesn’t help that the SDK function list is very long and there are several ways to achieve the same things.
Taking the PIO PWM code from the pico_examples as a starting point (https://github.com/raspberrypi/pico-examples/tree/master/pio/pwm), I’ve added in some comments containing the full function prototypes for some of the calls to make them a bit easier to walk through.
pwm.pio:
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
; Side-set pin 0 is used for PWM output
.program pwm
.side_set 1 opt
pull noblock side 0 ; Pull from FIFO to OSR if available,
; else copy X to OSR.
mov x, osr ; Copy most-recently-pulled value
; back to scratch X
mov y, isr ; ISR contains PWM period.
; Y used as counter.
countloop:
jmp x!=y noset ; Set pin high if X == Y,
; keep the two paths length matched
jmp skip side 1
noset:
nop ; Single dummy cycle to keep
; the two paths the same length
skip:
jmp y-- countloop ; Loop until Y hits 0,
; then pull a fresh PWM value from FIFO
% c-sdk {
static inline void pwm_program_init(PIO pio, uint sm, uint offset, uint pin) {
// static void pio_gpio_init (PIO pio, uint pin)
pio_gpio_init(pio, pin);
// int pio_sm_set_consecutive_pindirs
// (PIO pio, uint sm, uint pins_base, uint pin_count, bool is_out)
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
// A piece of pioasm "magic" based on .program pwm
// (see following notes)
pio_sm_config c = pwm_program_get_default_config(offset);
// static void sm_config_set_sideset_pins
// (pio_sm_config *c, uint sideset_base)
sm_config_set_sideset_pins(&c, pin);
// int pio_sm_init (PIO pio, uint sm,
// uint initial_pc, const pio_sm_config *config)
pio_sm_init(pio, sm, offset, &c);
}
%}The above PIO code assembles down into the following (this is using the online PIO assembler):
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// --- //
// pwm //
// --- //
#define pwm_wrap_target 0
#define pwm_wrap 6
static const uint16_t pwm_program_instructions[] = {
// .wrap_target
0x9080, // 0: pull noblock side 0
0xa027, // 1: mov x, osr
0xa046, // 2: mov y, isr
0x00a5, // 3: jmp x != y, 5
0x1806, // 4: jmp 6 side 1
0xa042, // 5: nop
0x0083, // 6: jmp y--, 3
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program pwm_program = {
.instructions = pwm_program_instructions,
.length = 7,
.origin = -1,
};
static inline pio_sm_config pwm_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + pwm_wrap_target,
offset + pwm_wrap);
sm_config_set_sideset(&c, 2, true, false);
return c;
}
static inline void pwm_program_init(PIO pio, uint sm, uint offset, uint pin) {
// static void pio_gpio_init (PIO pio, uint pin)
pio_gpio_init(pio, pin);
// int pio_sm_set_consecutive_pindirs
// (PIO pio, uint sm, uint pins_base, uint pin_count, bool is_out)
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
// A piece of pioasm "magic" based on .program pwm
// (see following notes)
pio_sm_config c = pwm_program_get_default_config(offset);
// static void sm_config_set_sideset_pins
// (pio_sm_config *c, uint sideset_base)
sm_config_set_sideset_pins(&c, pin);
// int pio_sm_init (PIO pio, uint sm,
// uint initial_pc, const pio_sm_config *config)
pio_sm_init(pio, sm, offset, &c);
}
#endifSome key points to note about this “assembled” code:
- Notice the appearance of pwm_program_get_default_config() which was already referenced in the passed-through C code.
- Notice also that the “get” function has the side effect of actually setting some state machine configuration parameters in the returned configuration too! Worse for me, it isn’t actually part of the configuration at this stage – it is just in the configuration that is returned by the function…
- The engineer in me hates this… I hate seeing a function that implies a benign function (“get”) that actually changes something critical… by I digress.
- A key parameter that gets set are the wrap parameters via sm_config_set_wrap(). This has to be updated once the actual code offset in the PIO instruction memory is known, hence I guess why this is set at run time.
- The patched in parameters (wrap and sideset in this case) won’t actually take effect until the configuration is finally set in the provided pass-through C code via the call to pio_sm_init().
Here is its associated C code pwm.c:
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "pwm.pio.h"
// Write `period` to the input shift register
void pio_pwm_set_period(PIO pio, uint sm, uint32_t period) {
pio_sm_set_enabled(pio, sm, false);
pio_sm_put_blocking(pio, sm, period);
pio_sm_exec(pio, sm, pio_encode_pull(false, false));
pio_sm_exec(pio, sm, pio_encode_out(pio_isr, 32));
pio_sm_set_enabled(pio, sm, true);
}
// Write `level` to TX FIFO. State machine will copy this into X.
void pio_pwm_set_level(PIO pio, uint sm, uint32_t level) {
pio_sm_put_blocking(pio, sm, level);
}
int main() {
stdio_init_all();
#ifndef PICO_DEFAULT_LED_PIN
#warning pio/pwm example requires a board with a regular LED
puts("Default LED pin was not defined");
#else
// todo get free sm
PIO pio = pio0;
int sm = 0;
uint offset = pio_add_program(pio, &pwm_program);
printf("Loaded program at %d\n", offset);
pwm_program_init(pio, sm, offset, PICO_DEFAULT_LED_PIN);
pio_pwm_set_period(pio, sm, (1u << 16) - 1);
int level = 0;
while (true) {
printf("Level = %d\n", level);
pio_pwm_set_level(pio, sm, level * level);
level = (level + 1) % 256;
sleep_ms(10);
}
#endif
}There are a few key things to remember to make sense of these examples:
- The offset that is talked about is the location within the shared 32 instruction program area and is used to refer back to the installed PIO program. It is returned from pio_add_program().
- A PIO .program directive becomes a default C like directive on processing by pioasm. This results in two obscure bits of “magic” coding going on meaning in this case that “.program pwm” in the PIO file becomes “pwm_program” in C/C++:
- pio_program_t pwm_program is a C structure which can then be referenced from the C code as shown in the line pio_add_program(pio, &pwm_program).
- static inline pio_sm_config pwm_program_get_default_config(uint offset) is a C function based on pio_get_default_sm_config() that returns the PIO configuration for the specific PIO program in question – in this case of course the pwm program (see previous notes).
- The use of .side_step opt means that not every PIO instruction has to have a side step instruction too.
- The PIO refers to an abstract group of pins, but it is the configuration which is part of the C/C++ SDK that determines which pins are used.
- The %c-sdk { … %} pairing signifies that this part of the PIO code will be passed straight onto the C/C++ SDK.
- There are multiple ways of initialising GPIO pins and directions. In this example it doesn’t use pindirs in the PIO code but uses pio_sm_set_consecutive_pindirs() in the C code.
- This example uses hardcoded references to PIO 0 and SM 0, but in many cases the PIO and SM would be chosen dynamically using API calls such as the following:
- pio_claim_free_sm_and_add_program ()
- pio_claim_free_sm_and_add_program_for_gpio_range()
- pio_claim_unused_sm()
- Each PIO program has a default configuration associated with it which can be updated. A typical pattern is shown here where the default configuration is grabbed using (in this case) pwm_program_get_default_config() and then updated by passing into SDK calls that follow.
- The state machine is finally set running with the required configuration using pio_sm_init();
There is one additional mix of techniques that is worth pulling out here. In the C code the function pio_pwm_set_period() is used to update the PWM period which it has to do by passing it into the SM via the FIFO. It is using some SM manipulation routines and then some inline, run-time PIO code, to achieve this.
void pio_pwm_set_period(PIO pio, uint sm, uint32_t period) {
pio_sm_set_enabled(pio, sm, false);
pio_sm_put_blocking(pio, sm, period);
pio_sm_exec(pio, sm, pio_encode_pull(false, false));
pio_sm_exec(pio, sm, pio_encode_out(pio_isr, 32));
pio_sm_set_enabled(pio, sm, true);
}Again some pretty confusing API calls, especially giving this is meant to be an example, but essentially what is going on (I think) is:
Disable the statemachine by using pio_sm_set_enabled(... false).
Push the period value into the TX FIFO, blocking if full to wait for it to be empty.
Execute two direct PIO instructions using pio_sm_exec():
This uses pio_encode_pull and pio_encode_out to run the following PIO code:
pull noblock ; non-blocking pull
out isr, 32 ; out 32 bits to the interrupt shift register
Re-enable he state machine using pio_sm_set_enabled(... true).By default anything sent to the FIFO is written to the X register and used to set the duty cycle of the PWM. But this code creates some temporary PIO code to receive the contents of the FIFO and put it into ISR instead. Of course it has to temporarily suspend the execution of the stored PIO code in order to do this.
I really dislike the nomenclature of “set enabled (false)” as an API approach. I’d much prefer to see something like pio_sm_enable() and pio_sm_disable() myself. I suppose they haven’t done this due to the large increase in API functions it creates.
I guess this is personal preference, but I do find that it adds to the opaqueness of much of the example code when it doesn’t read naturally.
So To Recap…
Writing PIO code can be done at build time (from Python or C/C++ using pioasm or an online assembler) or run time (using pio_encode_ functions or maybe APIO).
pioasm bridges the gap between PIO code and C/C++ including creating two magic C/C++ constructs: pwm_program for the code and pwm_program_get_default_config() to return the created PIO configuration.
PIO and SMs can be allocated by the system using a range of “claim” functions. There are 2 PIOs on the RP2040 and 3 on the RP2350, each with its own 32 instruction program memory and each with four state machines.
It can be useful to include an initialisation routine, that configures and starts the PIO program, within the PIO code for use from the C/C++ code using % c-sdk { … %}.
The PIO program is added into the system and given an offset in instruction memory using pio_add_program.
PIO code is very dense and often the functionality cannot be seen from the PIO code itself as it is defined by the PIO configuration – e.g. pins to use, frequency of execution, direction of shifts and so on.
I’ve not touched on it here, but the use of PIO and DMA (direct memory access) often go hand in hand to create completely CPU-free means of getting data in and out of a RP2040/RP2350 system. A really good example of this is Piers Rocks’ OneROM (see this video for a brilliant summary of how this works: https://www.youtube.com/watch?v=Y8RODQZM2HY).
Finally I need to remember that ISR stands for Input Shift Register and not Interrupt Service Routine…
Kevin
#pio #raspberryPiPico #rp2040 #rp2350 -
PIO on the Raspberry Pi Pico
Every time I’ve started to approach the use of the programmable IO (PIO) subsystem on the RP2040 or RP2350 (as used on the Raspberry Pi Pico), I’ve found myself essentially starting from scratch again and the examples quite opaque to me.
So this time as I’ve worked through it yet again, I’ve decided to write it all down 🙂
Here are some existing tutorials and projects that talk about getting going with the PIO:
- RP2350 Datasheet – “Chapter 11: PIO”
- Raspberry Pi Pico-series C/C++ SDK – “Chapter 3: Using Programmable I/O (PIO)”.
- Hackspace Magazine Issue 39 – Raspberry Pi Pico Programmable I/O (page 40).
- Stephen Smith’s “I/O Co-processing on the Raspberry Pi Pico”
- PIO Examples: https://github.com/raspberrypi/pico-examples?tab=readme-ov-file#pio
Assembling PIO Code
The PIO has its own bespoke micro-instruction set that is very similar to many types of assembly language and it requires its own pio assembler to process it. The basic sequence is as follows:
- PIO -> pioasm -> C header file -> include in C/C++ project and build
There are options for writing PIO in both Micropython and Circuitpython, which I have done in the past, but I’m sticking with the C route here. This requires the pioasm to take PIO code and produce a C header file that can then be included in a C project.
To use the RP2040/2350 Arduino environment, it is necessary to process PIO independently and then add the C file to the Arduino project. The Raspberry Pi C/C++ SDK can process PIO files directly as part of the main build.
There is also an option to use hardware SDK functions for dynamic creation of PIO code at runtime. The functions are a series of pio_encode_XX() functions representing the different PIO instructions as listed here: https://www.raspberrypi.com/documentation/pico-sdk/hardware.html#group_pio_instructions
There are two other novel approaches I found so far too:
- Wokwi online PIO assembler: https://wokwi.com/tools/pioasm
- Piersrocks’ runtime PIO assembler: https://github.com/piersfinlayson/apio
The first is an online editing environment that creates the required processed PIO related code for the C/C++ SDK or Python which can then be included in your build environment as required.
The second is an alternative run-time approach that uses a range of C macros to allow the “assembling” of PIO code as part of the run-time execution. It does this by directly creating the HEX equivalents of PIO instructions, thereby effectively assembling in the fly. This means that the PIO code can be customised to the specific run-time situation.
At this stage I’m not sure what it gives over using the pio_encode_ SDK functions directly. I do note however there is an equivalent PIO emulator which means this approach will run equally well on real hardware or in emulation. I’ve bookmarked this to come back to at some point.
Running PIO Code
Regardless of how the PIO instructions become code, to use them requires setting up and configuring the PIO state machines at run time as part of a project. A common approach is to include an initialisation function within the PIO code itself that is destined for passing straight through to the C/C++ SDK. This will have access to all definitions used within the PIO code and also allows the appropriate configuration information to remain encapsulated with the code.
But I have to admit I find there is an awful lot of assumed “magic” going on when configuring and getting running PIO programs and state machines. And whilst there are plenty of examples to study, I don’t find that they are written so as to teach. Consequently, I’ve noted the following as “reminders to self” on how to read some of the examples. It doesn’t help that the SDK function list is very long and there are several ways to achieve the same things.
Taking the PIO PWM code from the pico_examples as a starting point (https://github.com/raspberrypi/pico-examples/tree/master/pio/pwm), I’ve added in some comments containing the full function prototypes for some of the calls to make them a bit easier to walk through.
pwm.pio:
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
; Side-set pin 0 is used for PWM output
.program pwm
.side_set 1 opt
pull noblock side 0 ; Pull from FIFO to OSR if available,
; else copy X to OSR.
mov x, osr ; Copy most-recently-pulled value
; back to scratch X
mov y, isr ; ISR contains PWM period.
; Y used as counter.
countloop:
jmp x!=y noset ; Set pin high if X == Y,
; keep the two paths length matched
jmp skip side 1
noset:
nop ; Single dummy cycle to keep
; the two paths the same length
skip:
jmp y-- countloop ; Loop until Y hits 0,
; then pull a fresh PWM value from FIFO
% c-sdk {
static inline void pwm_program_init(PIO pio, uint sm, uint offset, uint pin) {
// static void pio_gpio_init (PIO pio, uint pin)
pio_gpio_init(pio, pin);
// int pio_sm_set_consecutive_pindirs
// (PIO pio, uint sm, uint pins_base, uint pin_count, bool is_out)
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
// A piece of pioasm "magic" based on .program pwm
// (see following notes)
pio_sm_config c = pwm_program_get_default_config(offset);
// static void sm_config_set_sideset_pins
// (pio_sm_config *c, uint sideset_base)
sm_config_set_sideset_pins(&c, pin);
// int pio_sm_init (PIO pio, uint sm,
// uint initial_pc, const pio_sm_config *config)
pio_sm_init(pio, sm, offset, &c);
}
%}The above PIO code assembles down into the following (this is using the online PIO assembler):
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// --- //
// pwm //
// --- //
#define pwm_wrap_target 0
#define pwm_wrap 6
static const uint16_t pwm_program_instructions[] = {
// .wrap_target
0x9080, // 0: pull noblock side 0
0xa027, // 1: mov x, osr
0xa046, // 2: mov y, isr
0x00a5, // 3: jmp x != y, 5
0x1806, // 4: jmp 6 side 1
0xa042, // 5: nop
0x0083, // 6: jmp y--, 3
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program pwm_program = {
.instructions = pwm_program_instructions,
.length = 7,
.origin = -1,
};
static inline pio_sm_config pwm_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + pwm_wrap_target,
offset + pwm_wrap);
sm_config_set_sideset(&c, 2, true, false);
return c;
}
static inline void pwm_program_init(PIO pio, uint sm, uint offset, uint pin) {
// static void pio_gpio_init (PIO pio, uint pin)
pio_gpio_init(pio, pin);
// int pio_sm_set_consecutive_pindirs
// (PIO pio, uint sm, uint pins_base, uint pin_count, bool is_out)
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
// A piece of pioasm "magic" based on .program pwm
// (see following notes)
pio_sm_config c = pwm_program_get_default_config(offset);
// static void sm_config_set_sideset_pins
// (pio_sm_config *c, uint sideset_base)
sm_config_set_sideset_pins(&c, pin);
// int pio_sm_init (PIO pio, uint sm,
// uint initial_pc, const pio_sm_config *config)
pio_sm_init(pio, sm, offset, &c);
}
#endifSome key points to note about this “assembled” code:
- Notice the appearance of pwm_program_get_default_config() which was already referenced in the passed-through C code.
- Notice also that the “get” function has the side effect of actually setting some state machine configuration parameters in the returned configuration too! Worse for me, it isn’t actually part of the configuration at this stage – it is just in the configuration that is returned by the function…
- The engineer in me hates this… I hate seeing a function that implies a benign function (“get”) that actually changes something critical… by I digress.
- A key parameter that gets set are the wrap parameters via sm_config_set_wrap(). This has to be updated once the actual code offset in the PIO instruction memory is known, hence I guess why this is set at run time.
- The patched in parameters (wrap and sideset in this case) won’t actually take effect until the configuration is finally set in the provided pass-through C code via the call to pio_sm_init().
Here is its associated C code pwm.c:
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "pwm.pio.h"
// Write `period` to the input shift register
void pio_pwm_set_period(PIO pio, uint sm, uint32_t period) {
pio_sm_set_enabled(pio, sm, false);
pio_sm_put_blocking(pio, sm, period);
pio_sm_exec(pio, sm, pio_encode_pull(false, false));
pio_sm_exec(pio, sm, pio_encode_out(pio_isr, 32));
pio_sm_set_enabled(pio, sm, true);
}
// Write `level` to TX FIFO. State machine will copy this into X.
void pio_pwm_set_level(PIO pio, uint sm, uint32_t level) {
pio_sm_put_blocking(pio, sm, level);
}
int main() {
stdio_init_all();
#ifndef PICO_DEFAULT_LED_PIN
#warning pio/pwm example requires a board with a regular LED
puts("Default LED pin was not defined");
#else
// todo get free sm
PIO pio = pio0;
int sm = 0;
uint offset = pio_add_program(pio, &pwm_program);
printf("Loaded program at %d\n", offset);
pwm_program_init(pio, sm, offset, PICO_DEFAULT_LED_PIN);
pio_pwm_set_period(pio, sm, (1u << 16) - 1);
int level = 0;
while (true) {
printf("Level = %d\n", level);
pio_pwm_set_level(pio, sm, level * level);
level = (level + 1) % 256;
sleep_ms(10);
}
#endif
}There are a few key things to remember to make sense of these examples:
- The offset that is talked about is the location within the shared 32 instruction program area and is used to refer back to the installed PIO program. It is returned from pio_add_program().
- A PIO .program directive becomes a default C like directive on processing by pioasm. This results in two obscure bits of “magic” coding going on meaning in this case that “.program pwm” in the PIO file becomes “pwm_program” in C/C++:
- pio_program_t pwm_program is a C structure which can then be referenced from the C code as shown in the line pio_add_program(pio, &pwm_program).
- static inline pio_sm_config pwm_program_get_default_config(uint offset) is a C function based on pio_get_default_sm_config() that returns the PIO configuration for the specific PIO program in question – in this case of course the pwm program (see previous notes).
- The use of .side_step opt means that not every PIO instruction has to have a side step instruction too.
- The PIO refers to an abstract group of pins, but it is the configuration which is part of the C/C++ SDK that determines which pins are used.
- The %c-sdk { … %} pairing signifies that this part of the PIO code will be passed straight onto the C/C++ SDK.
- There are multiple ways of initialising GPIO pins and directions. In this example it doesn’t use pindirs in the PIO code but uses pio_sm_set_consecutive_pindirs() in the C code.
- This example uses hardcoded references to PIO 0 and SM 0, but in many cases the PIO and SM would be chosen dynamically using API calls such as the following:
- pio_claim_free_sm_and_add_program ()
- pio_claim_free_sm_and_add_program_for_gpio_range()
- pio_claim_unused_sm()
- Each PIO program has a default configuration associated with it which can be updated. A typical pattern is shown here where the default configuration is grabbed using (in this case) pwm_program_get_default_config() and then updated by passing into SDK calls that follow.
- The state machine is finally set running with the required configuration using pio_sm_init();
There is one additional mix of techniques that is worth pulling out here. In the C code the function pio_pwm_set_period() is used to update the PWM period which it has to do by passing it into the SM via the FIFO. It is using some SM manipulation routines and then some inline, run-time PIO code, to achieve this.
void pio_pwm_set_period(PIO pio, uint sm, uint32_t period) {
pio_sm_set_enabled(pio, sm, false);
pio_sm_put_blocking(pio, sm, period);
pio_sm_exec(pio, sm, pio_encode_pull(false, false));
pio_sm_exec(pio, sm, pio_encode_out(pio_isr, 32));
pio_sm_set_enabled(pio, sm, true);
}Again some pretty confusing API calls, especially giving this is meant to be an example, but essentially what is going on (I think) is:
Disable the statemachine by using pio_sm_set_enabled(... false).
Push the period value into the TX FIFO, blocking if full to wait for it to be empty.
Execute two direct PIO instructions using pio_sm_exec():
This uses pio_encode_pull and pio_encode_out to run the following PIO code:
pull noblock ; non-blocking pull
out isr, 32 ; out 32 bits to the interrupt shift register
Re-enable he state machine using pio_sm_set_enabled(... true).By default anything sent to the FIFO is written to the X register and used to set the duty cycle of the PWM. But this code creates some temporary PIO code to receive the contents of the FIFO and put it into ISR instead. Of course it has to temporarily suspend the execution of the stored PIO code in order to do this.
I really dislike the nomenclature of “set enabled (false)” as an API approach. I’d much prefer to see something like pio_sm_enable() and pio_sm_disable() myself. I suppose they haven’t done this due to the large increase in API functions it creates.
I guess this is personal preference, but I do find that it adds to the opaqueness of much of the example code when it doesn’t read naturally.
So To Recap…
Writing PIO code can be done at build time (from Python or C/C++ using pioasm or an online assembler) or run time (using pio_encode_ functions or maybe APIO).
pioasm bridges the gap between PIO code and C/C++ including creating two magic C/C++ constructs: pwm_program for the code and pwm_program_get_default_config() to return the created PIO configuration.
PIO and SMs can be allocated by the system using a range of “claim” functions. There are 2 PIOs on the RP2040 and 3 on the RP2350, each with its own 32 instruction program memory and each with four state machines.
It can be useful to include an initialisation routine, that configures and starts the PIO program, within the PIO code for use from the C/C++ code using % c-sdk { … %}.
The PIO program is added into the system and given an offset in instruction memory using pio_add_program.
PIO code is very dense and often the functionality cannot be seen from the PIO code itself as it is defined by the PIO configuration – e.g. pins to use, frequency of execution, direction of shifts and so on.
I’ve not touched on it here, but the use of PIO and DMA (direct memory access) often go hand in hand to create completely CPU-free means of getting data in and out of a RP2040/RP2350 system. A really good example of this is Piers Rocks’ OneROM (see this video for a brilliant summary of how this works: https://www.youtube.com/watch?v=Y8RODQZM2HY).
Finally I need to remember that ISR stands for Input Shift Register and not Interrupt Service Routine…
Kevin
#pio #raspberryPiPico #rp2040 #rp2350 -
https://www.europesays.com/ie/405938/ Lilbits: Samsung Galaxy Z Fold 8 Wide, Razer Blade 16, and more fallout from Chuwi’s processor mixup #chuwi #CpuZ #Éire #esp32 #foldables #GalazyZFold8Wide #GamingLaptop #GoogleTV #IE #Ireland #lilbits #MediaStreamer #Mobile #onn #Onn4kPro #PantherLake #picoz89 #Razer #RazerBlade16 #rp2350 #SamsungGalaxyZFold8Wide #Technology #z80
-
Lilbits: Samsung Galaxy Z Fold 8 Wide, Razer Blade 16, and more fallout from Chuwi’s processor mixup
Two recent laptops from Chinese PC maker Chuwi that were supposed to ship with AMD Ryzen 5 7430U processors actually had Ryzen 5 5500U chips inside. That means instead of getting a processor with a Zen 3 CPU, customers were getting notebooks with chips featuring AMD’s older Zen 2 architecture.
But the only way to prove this was to actually open up the computer and remove the CPU cooler to look […]
#chuwi #cpuZ #esp32 #foldables #galazyZFold8Wide #gamingLaptop #googleTv #lilbits #mediaStreamer #onn #onn4kPro #pantherLake #picoz89 #razer #razerBlade16 #rp2350 #samsungGalaxyZFold8Wide #z80 Read more: https://liliputing.com/lilbits-samsung-galaxy-z-fold-8-wide-razer-blade-16-and-more-fallout-from-chuwis-processor-mixup/ -
Lilbits: Samsung Galaxy Z Fold 8 Wide, Razer Blade 16, and more fallout from Chuwi’s processor mixup
Two recent laptops from Chinese PC maker Chuwi that were supposed to ship with AMD Ryzen 5 7430U processors actually had Ryzen 5 5500U chips inside. That means instead of getting a processor with a Zen 3 CPU, customers were getting notebooks with chips featuring AMD’s older Zen 2 architecture.
But the only way to prove this was to actually open up the computer and remove the CPU cooler to look […]
#chuwi #cpuZ #esp32 #foldables #galazyZFold8Wide #gamingLaptop #googleTv #lilbits #mediaStreamer #onn #onn4kPro #pantherLake #picoz89 #razer #razerBlade16 #rp2350 #samsungGalaxyZFold8Wide #z80 Read more: https://liliputing.com/lilbits-samsung-galaxy-z-fold-8-wide-razer-blade-16-and-more-fallout-from-chuwis-processor-mixup/ -
Lilbits: Samsung Galaxy Z Fold 8 Wide, Razer Blade 16, and more fallout from Chuwi’s processor mixup
Two recent laptops from Chinese PC maker Chuwi that were supposed to ship with AMD Ryzen 5 7430U processors actually had Ryzen 5 5500U chips inside. That means instead of getting a processor with a Zen 3 CPU, customers were getting notebooks with chips featuring AMD’s older Zen 2 architecture.
But the only way to prove this was to actually open up the computer and remove the CPU cooler to look […]
#chuwi #cpuZ #esp32 #foldables #galazyZFold8Wide #gamingLaptop #googleTv #lilbits #mediaStreamer #onn #onn4kPro #pantherLake #picoz89 #razer #razerBlade16 #rp2350 #samsungGalaxyZFold8Wide #z80 Read more: https://liliputing.com/lilbits-samsung-galaxy-z-fold-8-wide-razer-blade-16-and-more-fallout-from-chuwis-processor-mixup/ -
Lilbits: Samsung Galaxy Z Fold 8 Wide, Razer Blade 16, and more fallout from Chuwi’s processor mixup
Two recent laptops from Chinese PC maker Chuwi that were supposed to ship with AMD Ryzen 5 7430U processors actually had Ryzen 5 5500U chips inside. That means instead of getting a processor with a Zen 3 CPU, customers were getting notebooks with chips featuring AMD’s older Zen 2 architecture.
But the only way to prove this was to actually open up the computer and remove the CPU cooler to look […]
#chuwi #cpuZ #esp32 #foldables #galazyZFold8Wide #gamingLaptop #googleTv #lilbits #mediaStreamer #onn #onn4kPro #pantherLake #picoz89 #razer #razerBlade16 #rp2350 #samsungGalaxyZFold8Wide #z80 Read more: https://liliputing.com/lilbits-samsung-galaxy-z-fold-8-wide-razer-blade-16-and-more-fallout-from-chuwis-processor-mixup/ -
Last week we exhibited at Embedded World in Nuremberg.
This video presents some of the demos from our booth, running on embedded devices with Slint 👇
https://www.youtube.com/shorts/3w64sO7fjFM
#embedded #Slint #EmbeddedWorld #RP2350 #ESP32 #ESP32S3 #ESP32P4 #Renesas #toradex
-
Last week we exhibited at Embedded World in Nuremberg.
This video presents some of the demos from our booth, running on embedded devices with Slint 👇
https://www.youtube.com/shorts/3w64sO7fjFM
#embedded #Slint #EmbeddedWorld #RP2350 #ESP32 #ESP32S3 #ESP32P4 #Renesas #toradex
-
Last week we exhibited at Embedded World in Nuremberg.
This video presents some of the demos from our booth, running on embedded devices with Slint 👇
https://www.youtube.com/shorts/3w64sO7fjFM
#embedded #Slint #EmbeddedWorld #RP2350 #ESP32 #ESP32S3 #ESP32P4 #Renesas #toradex
-
Last week we exhibited at Embedded World in Nuremberg.
This video presents some of the demos from our booth, running on embedded devices with Slint 👇
https://www.youtube.com/shorts/3w64sO7fjFM
#embedded #Slint #EmbeddedWorld #RP2350 #ESP32 #ESP32S3 #ESP32P4 #Renesas #toradex
-
Last week we exhibited at Embedded World in Nuremberg.
This video presents some of the demos from our booth, running on embedded devices with Slint 👇
https://www.youtube.com/shorts/3w64sO7fjFM
#embedded #Slint #EmbeddedWorld #RP2350 #ESP32 #ESP32S3 #ESP32P4 #Renesas #toradex
-
Pondering what is the easiest way to get enough PIO driven GPIO to watch a 16-bit address, 8-bit data bus.
So, def RP2350 (esp as 5V tolerant), but maybe one of those Pimoroni "XL" Pico things, or maybe a PGA2350 or Pico Stamp, or something like that...
Anyone any experiences of these larger Pico-like things (or know of any others)?
-
FRANK OS 1.0 porta l’esperienza dei desktop anni ’90 nel mondo dei microcontroller con finestre sovrapponibili, applicazioni integrate e un’interfaccia che richiama Windows 95. #FRANKOS #FreeRTOS #RP2350 #Microcontroller