Skip to content

Instantly share code, notes, and snippets.

@joshka
Last active October 14, 2022 19:32
Show Gist options
  • Save joshka/8bb618dcda96f3dd940602bf41eb96af to your computer and use it in GitHub Desktop.
Save joshka/8bb618dcda96f3dd940602bf41eb96af to your computer and use it in GitHub Desktop.
8/Any pin PIO based UART TX code for a Raspberry Pi Pico

Raspberry Pi Pico PIO 8 Pin UART TX

This code creates a UART TX on 8 sequential pins of the Raspberry Pi Pico. The main use case being MIDI splitter devices

The second 16 port implementation should work for any number of pins, but just acts as a pure copy to those pins instead of having individual control over every pin of every byte. Hence while less flexible, the controlling software is simpler (just send a uart byte and it's copied to every pin).

Only tested in the simulator so far. https://wokwi.com/projects/344345628967436882

Used https://wokwi.com/tools/pioasm to go from uart_tx.pio.h to uart_tx.h

PulseView output: image

Sketch: image

License: MIT

{
"version": 1,
"author": "Joshka",
"editor": "wokwi",
"parts": [
{
"type": "wokwi-pi-pico",
"id": "pico",
"top": 134.58,
"left": 163.93,
"attrs": { "env": "arduino-community" }
},
{
"type": "wokwi-logic-analyzer",
"id": "logic1",
"top": 0,
"left": 0,
"rotate": 270,
"attrs": { "bufferSize": 3000 }
}
],
"connections": [
[ "pico:GP0", "$serialMonitor:RX", "", [] ],
[ "pico:GP1", "$serialMonitor:TX", "", [] ],
[ "pico:GND.1", "logic1:GND", "black", [ "h0" ] ],
[ "pico:GP2", "logic1:D0", "green", [ "h0" ] ],
[ "pico:GP3", "logic1:D1", "green", [ "h0" ] ],
[ "pico:GP4", "logic1:D2", "green", [ "h0" ] ],
[ "pico:GP5", "logic1:D3", "green", [ "h0" ] ],
[ "pico:GP6", "logic1:D4", "green", [ "h0" ] ],
[ "pico:GP7", "logic1:D5", "green", [ "h0" ] ],
[ "pico:GP8", "logic1:D6", "green", [ "h0" ] ],
[ "pico:GP9", "logic1:D7", "green", [ "h0" ] ]
],
"serialMonitor": { "display": "auto", "newline": "lf" }
}
// A test harness for 8 pin UART TX on a single PIO State Machine
// Test project https://wokwi.com/projects/344410676217774675
// uart_tx.h source
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "uart_tx.h"
// start at pin 2, so if we want we have UART0 on GP0 and GP1
const uint pin = 2;
// NOTE the simulator doesn't seem to hit this exactly (on my 2015 macbook)
// It ends up being slow by about 6% YMMV
const uint baud = 31250;
const PIO pio = pio0;
uint sm;
void setup() {
uint offset = pio_add_program(pio, &uart_tx_program);
sm = pio_claim_unused_sm(pio, true);
uart_tx_program_init(pio, sm, offset, pin, baud);
}
// Some test messages
uint8_t messages[12] = {
0x80, 0x3C, 0x00, // Note Off Channel 1, Middle C, Velocity 0
0x90, 0x3C, 0x7F, // Note On Channel 1, Middle C, Velocity 127
0x80, 0x3D, 0x00, // Note Off Channel 1, Middle C, Velocity 0
0x90, 0x3D, 0x7F // Note On Channel 1, Middle C, Velocity 127
};
uint counter = 0;
void loop() {
// pull 3 message bytes off
sendByte(counter++);
sendByte(counter++);
sendByte(counter++);
// delay(1);
// delayMicroseconds(100);
}
// Send the exact same byte to all outputs
void sendByte(uint idx) {
uint8_t m = messages[idx];
uint32_t low = (
((m >> 0 & 0x01) << 0) +
((m >> 1 & 0x01) << 8) +
((m >> 2 & 0x01) << 16) +
((m >> 3 & 0x01) << 24)
) * 0xFF;
uint32_t high = (
((m >> 4 & 0x01) << 0) +
((m >> 5 & 0x01) << 8) +
((m >> 6 & 0x01) << 16) +
((m >> 7 & 0x01) << 24)
) * 0xFF;
uart_tx_program_putc(pio, sm, low, high);
}
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// ------- //
// uart_tx //
// ------- //
#define uart_tx_wrap_target 0
#define uart_tx_wrap 11
static const uint16_t uart_tx_program_instructions[] = {
// .wrap_target
0x80a0, // 0: pull block
0xa103, // 1: mov pins, null [1]
0x6108, // 2: out pins, 8 [1]
0x6108, // 3: out pins, 8 [1]
0x6108, // 4: out pins, 8 [1]
0x6008, // 5: out pins, 8
0x80a0, // 6: pull block
0x6108, // 7: out pins, 8 [1]
0x6108, // 8: out pins, 8 [1]
0x6108, // 9: out pins, 8 [1]
0x6108, // 10: out pins, 8 [1]
0xa00b, // 11: mov pins, !null
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program uart_tx_program = {
.instructions = uart_tx_program_instructions,
.length = 12,
.origin = -1,
};
static inline pio_sm_config uart_tx_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + uart_tx_wrap_target, offset + uart_tx_wrap);
return c;
}
static inline void uart_tx_program_init(PIO pio, uint sm, uint offset, uint pin, uint baud) {
const int PIN_COUNT = 8;
const int PIN_MASK = 0xFF;
// the number of PIO cycles that it takes to output a single bit
const uint cycles_per_bit = 2;
// the clock divider (number of CPU cycles that each PIO cycle takes)
float div = (float)clock_get_hz(clk_sys) / (baud * cycles_per_bit);
// initialize the pins to high indicating the stop bit
pio_sm_set_set_pins(pio, sm, pin, PIN_COUNT);
pio_sm_set_pindirs_with_mask(pio, sm, 0xFFFFFFFF, PIN_MASK << pin);
pio_sm_set_pins_with_mask(pio, sm, 0xFFFFFFFF, PIN_MASK << pin);
for (int i = 0; i < PIN_COUNT; i++) {
pio_gpio_init(pio, pin + i);
}
pio_sm_config c = uart_tx_program_get_default_config(offset);
sm_config_set_out_shift(&c, true, false, 32); // shift to right, no autopull
sm_config_set_out_pins(&c, pin, PIN_COUNT); // Set 8 pins starting at `pin`
sm_config_set_set_pins(&c, pin, PIN_COUNT); // Set 8 pins starting at `pin`
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c); // Load config
pio_sm_set_enabled(pio, sm, true); // Start state machine
}
static inline void uart_tx_program_putc(PIO pio, uint sm, uint32_t b1, uint32_t b2) {
pio_sm_put_blocking(pio, sm, b1);
pio_sm_put_blocking(pio, sm, b2);
}
#endif
// a multi pin uart that send 8 bits at a time by treating the buffer as
// transposed (i.e. byte0 provides LSB0 of all pins, byte1 -> LSB1 etc.)
// buffer is 32 bits, so for 8 x 8-bit pushes, we need two full buffers
// to fill a single UART byte (start-b7-b6-b5-b4-b3-b2-b1-b0-stop)
// only tested at https://wokwi.com/tools/pioasm so far, not on a real
// device see also https://wokwi.com/projects/344410676217774675
.program uart_tx
pull
mov pins, null [1] ; start bit (0)
out pins, 8 [1]
out pins, 8 [1]
out pins, 8 [1]
out pins, 8
pull
out pins, 8 [1]
out pins, 8 [1]
out pins, 8 [1]
out pins, 8 [1]
mov pins, !null ; stop bit (1)
% c-sdk {
static inline void uart_tx_program_init(PIO pio, uint sm, uint offset, uint pin, uint baud) {
const int PIN_COUNT = 8;
const int PIN_MASK = 0xFF;
// the number of PIO cycles that it takes to output a single bit
const uint cycles_per_bit = 2;
// the clock divider (number of CPU cycles that each PIO cycle takes)
float div = (float)clock_get_hz(clk_sys) / (baud * cycles_per_bit);
// initialize the pins to high indicating the stop bit
pio_sm_set_set_pins(pio, sm, pin, PIN_COUNT);
pio_sm_set_pindirs_with_mask(pio, sm, 0xFFFFFFFF, PIN_MASK << pin);
pio_sm_set_pins_with_mask(pio, sm, 0xFFFFFFFF, PIN_MASK << pin);
for (int i = 0; i < PIN_COUNT; i++) {
pio_gpio_init(pio, pin + i);
}
pio_sm_config c = uart_tx_program_get_default_config(offset);
sm_config_set_out_shift(&c, true, false, 32); // shift to right, no autopull
sm_config_set_out_pins(&c, pin, PIN_COUNT); // Set 8 pins starting at `pin`
sm_config_set_set_pins(&c, pin, PIN_COUNT); // Set 8 pins starting at `pin`
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c); // Load config
pio_sm_set_enabled(pio, sm, true); // Start state machine
}
static inline void uart_tx_program_putc(PIO pio, uint sm, uint32_t b1, uint32_t b2) {
pio_sm_put_blocking(pio, sm, b1);
pio_sm_put_blocking(pio, sm, b2);
}
%}
// A test harness for 16 pin UART TX on a single PIO State Machine
// Test project https://wokwi.com/projects/344345628967436882
// uart_tx.h source
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "uart_tx16.h"
// start at pin 2, so if we want we have UART0 on GP0 and GP1
const uint pin = 2;
// NOTE the simulator doesn't seem to hit this exactly (on my 2015 macbook)
// It ends up being slow by about 6% YMMV
const uint baud = 31250;
const PIO pio = pio0;
uint sm;
void setup() {
uint offset = pio_add_program(pio, &uart_tx_program);
sm = pio_claim_unused_sm(pio, true);
uart_tx_program_init(pio, sm, offset, pin, baud);
}
// Some test messages
uint8_t messages[12] = {
0x80, 0x3C, 0x00, // Note Off Channel 1, Middle C, Velocity 0
0x90, 0x3C, 0x7F, // Note On Channel 1, Middle C, Velocity 127
0x80, 0x3D, 0x00, // Note Off Channel 1, Middle C, Velocity 0
0x90, 0x3D, 0x7F // Note On Channel 1, Middle C, Velocity 127
};
uint counter = 0;
void loop() {
// pull 3 message bytes off
sendByte(counter++);
sendByte(counter++);
sendByte(counter++);
counter %= 12;
// delay(1);
// delayMicroseconds(100);
}
void sendByte(uint idx) {
uint8_t m = messages[idx];
uart_tx_program_putc(pio, sm, (uint32_t)m);
}
// a multi pin uart that send 16 bits at a time by copying each bit LSB to the output
// different from the previous version in that this just plain copies a normal byte,
// rather than having a larger buffer that can set every pin differently.
// so the input is a single byte in the buffer, rather than one byte per bit
// 16 isn't at all special in this, it should work from 1 to whatever number of
// consecutive pins you have available (pico has 0-22)
// only tested at https://wokwi.com/tools/pioasm so far, not on a real
// device see also https://wokwi.com/projects/344410676217774675
// This version https://wokwi.com/projects/344453034004185682
.program uart_tx
pull [2]
mov pins, null ; output start bit to all pins
set y, 7 ; y is bit counter 7 to 0
loopbits:
out x, 1 ; get LSB from OSR
jmp !x, zerobit
onebit:
mov pins, !null ; output one to all pins
jmp y--, loopbits
jmp end
zerobit:
mov pins, null ; output zero to all pins
jmp y--, loopbits
nop
end:
nop
mov pins, !null ; output stop bit to all pins
% c-sdk {
static inline void uart_tx_program_init(PIO pio, uint sm, uint offset, uint pin, uint baud) {
const int PIN_COUNT = 16;
const uint32_t PIN_MASK = 0xFFFF;
// the number of PIO cycles that it takes to output a single bit
const uint cycles_per_bit = 4;
// the clock divider (number of CPU cycles that each PIO cycle takes)
float div = (float)clock_get_hz(clk_sys) / (baud * cycles_per_bit);
// initialize the pins to high indicating the stop bit
pio_sm_set_set_pins(pio, sm, pin, PIN_COUNT);
pio_sm_set_pindirs_with_mask(pio, sm, 0xFFFFFFFF, PIN_MASK << pin);
pio_sm_set_pins_with_mask(pio, sm, 0xFFFFFFFF, PIN_MASK << pin);
for (int i = 0; i < PIN_COUNT; i++) {
pio_gpio_init(pio, pin + i);
}
pio_sm_config c = uart_tx_program_get_default_config(offset);
sm_config_set_out_shift(&c, true, false, 32); // shift to right, no autopull
sm_config_set_out_pins(&c, pin, PIN_COUNT); // Set 8 pins starting at `pin`
sm_config_set_set_pins(&c, pin, PIN_COUNT); // Set 8 pins starting at `pin`
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c); // Load config
pio_sm_set_enabled(pio, sm, true); // Start state machine
}
static inline void uart_tx_program_putc(PIO pio, uint sm, uint32_t b1) {
pio_sm_put_blocking(pio, sm, b1);
}
%}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment