Initial Release

This commit is contained in:
graham sanderson
2021-01-20 10:49:34 -06:00
commit 46078742c7
245 changed files with 21157 additions and 0 deletions

18
pio/CMakeLists.txt Normal file
View File

@@ -0,0 +1,18 @@
if (NOT PICO_NO_HARDWARE)
add_subdirectory(addition)
add_subdirectory(apa102)
add_subdirectory(differential_manchester)
add_subdirectory(hello_pio)
add_subdirectory(hub75)
add_subdirectory(i2c)
add_subdirectory(logic_analyser)
add_subdirectory(manchester_encoding)
add_subdirectory(pio_blink)
add_subdirectory(pwm)
add_subdirectory(spi)
add_subdirectory(squarewave)
add_subdirectory(st7789_lcd)
add_subdirectory(uart_rx)
add_subdirectory(uart_tx)
add_subdirectory(ws2812)
endif ()

View File

@@ -0,0 +1,11 @@
add_executable(pio_addition)
pico_generate_pio_header(pio_addition ${CMAKE_CURRENT_LIST_DIR}/addition.pio)
target_sources(pio_addition PRIVATE addition.c)
target_link_libraries(pio_addition PRIVATE pico_stdlib hardware_pio)
pico_add_extra_outputs(pio_addition)
# add url via pico_set_program_url
example_auto_set_url(pio_addition)

35
pio/addition/addition.c Normal file
View File

@@ -0,0 +1,35 @@
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdlib.h>
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "addition.pio.h"
// Pop quiz: how many additions does the processor do when calling this function
uint32_t do_addition(PIO pio, uint sm, uint32_t a, uint32_t b) {
pio_sm_put_blocking(pio, sm, a);
pio_sm_put_blocking(pio, sm, b);
return pio_sm_get_blocking(pio, sm);
}
int main() {
stdio_init_all();
PIO pio = pio0;
uint sm = 0;
uint offset = pio_add_program(pio, &addition_program);
addition_program_init(pio, sm, offset);
printf("Doing some random additions:\n");
for (int i = 0; i < 10; ++i) {
uint a = rand() % 100;
uint b = rand() % 100;
printf("%u + %u = %u\n", a, b, do_addition(pio, sm, a, b));
}
}

27
pio/addition/addition.pio Normal file
View File

@@ -0,0 +1,27 @@
.program addition
; Pop two 32 bit integers from the TX FIFO, add them together, and push the
; result to the TX FIFO. Autopush/pull should be disabled as we're using
; explicit push and pull instructions.
;
; This program uses the two's complement identity x + y == ~(~x - y)
pull
mov x, ~osr
pull
mov y, osr
jmp test ; this loop is equivalent to the following C code:
incr: ; while (y--)
jmp x-- test ; x--;
test: ; This has the effect of subtracting y from x, eventually.
jmp y-- incr
mov isr, ~x
push
% c-sdk {
static inline void addition_program_init(PIO pio, uint sm, uint offset) {
pio_sm_config c = addition_program_get_default_config(offset);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}

15
pio/apa102/CMakeLists.txt Normal file
View File

@@ -0,0 +1,15 @@
add_executable(pio_apa102)
pico_generate_pio_header(pio_apa102 ${CMAKE_CURRENT_LIST_DIR}/apa102.pio)
target_sources(pio_apa102 PRIVATE apa102.c)
target_link_libraries(pio_apa102 PRIVATE
pico_stdlib
hardware_pio
)
pico_add_extra_outputs(pio_apa102)
# add url via pico_set_program_url
example_auto_set_url(pio_apa102)

69
pio/apa102/apa102.c Normal file
View File

@@ -0,0 +1,69 @@
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include <math.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "apa102.pio.h"
#define PIN_CLK 2
#define PIN_DIN 3
#define N_LEDS 150
#define SERIAL_FREQ (5 * 1000 * 1000)
// Global brightness value 0->31
#define BRIGHTNESS 16
void put_start_frame(PIO pio, uint sm) {
pio_sm_put_blocking(pio, sm, 0u);
}
void put_end_frame(PIO pio, uint sm) {
pio_sm_put_blocking(pio, sm, ~0u);
}
void put_rgb888(PIO pio, uint sm, uint8_t r, uint8_t g, uint8_t b) {
pio_sm_put_blocking(pio, sm,
0x7 << 29 | // magic
(BRIGHTNESS & 0x1f) << 24 | // global brightness parameter
(uint32_t) b << 16 |
(uint32_t) g << 8 |
(uint32_t) r << 0
);
}
#define TABLE_SIZE (1 << 8)
uint8_t wave_table[TABLE_SIZE];
int main() {
stdio_init_all();
PIO pio = pio0;
uint sm = 0;
uint offset = pio_add_program(pio, &apa102_mini_program);
apa102_mini_program_init(pio, sm, offset, SERIAL_FREQ, PIN_CLK, PIN_DIN);
for (int i = 0; i < TABLE_SIZE; ++i)
wave_table[i] = powf(sinf(i * M_PI / TABLE_SIZE), 5.f) * 255;
uint t = 0;
while (true) {
put_start_frame(pio, sm);
for (int i = 0; i < N_LEDS; ++i) {
put_rgb888(pio, sm,
wave_table[(i + t) % TABLE_SIZE],
wave_table[(2 * i + 3 * 2) % TABLE_SIZE],
wave_table[(3 * i + 4 * t) % TABLE_SIZE]
);
}
put_end_frame(pio, sm);
sleep_ms(10);
++t;
}
}

89
pio/apa102/apa102.pio Normal file
View File

@@ -0,0 +1,89 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program apa102_mini
.side_set 1
; This is really just a TX-only SPI. CLK is side-set pin 0, DIN is OUT pin 0.
; Autopull enabled, threshold 32.
;
; Every word (32 bits) written to the FIFO will be shifted out in its entirety, MSB-first.
out pins, 1 side 0 ; Stall here when no data (still asserts clock low)
nop side 1
% c-sdk {
#include "hardware/clocks.h"
static inline void apa102_mini_program_init(PIO pio, uint sm, uint offset,
uint baud, uint pin_clk, uint pin_din) {
pio_sm_set_pins_with_mask(pio, sm, 0, (1u << pin_clk) | (1u << pin_din));
pio_sm_set_pindirs_with_mask(pio, sm, ~0u, (1u << pin_clk) | (1u << pin_din));
pio_gpio_init(pio, pin_clk);
pio_gpio_init(pio, pin_din);
pio_sm_config c = apa102_mini_program_get_default_config(offset);
sm_config_set_out_pins(&c, pin_din, 1);
sm_config_set_sideset_pins(&c, pin_clk);
// Shift to right, autopull with threshold 32
sm_config_set_out_shift(&c, false, true, 32);
// Deeper FIFO as we're not doing any RX
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
// We transmit 1 bit every 2 execution cycles
float div = (float)clock_get_hz(clk_sys) / (2 * baud);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}
.program apa102_rgb555
; Alternative program to unpack two RGB555 pixels from a FIFO word and transmit.
; This makes it easier to DMA large buffers without processor involvement.
; OSR: shift to right
; ISR: shift to right
; To set brightness, set ISR to bit-reverse of 5-bit brightness,
; followed by 111. (00...00_b0b1b2b3b4_111)
; DMA pixel format is 0RRRRRGGGGGBBBBB x2 (15 bpp, 2px per FIFO word)
; APA102 command structure:
; increasing time ---->>
; | byte 3 | byte 2 | byte 1 | byte 0 |
; |7 0|7 0|7 0|7 0|
; -------------------------------------
; Pixel |111bbbbb|BBBBBBBB|GGGGGGGG|RRRRRRRR|
; Start Frame |00000000|00000000|00000000|00000000|
; Stop Frame |11111111|11111111|11111111|11111111|
.wrap_target
public pixel_out:
; pixel_out formats an APA102 colour command in the ISR.
; bit_run shifts 32 bits out of the ISR, with clock.
pull ifempty
set x, 2
colour_loop:
in osr, 5
out null, 5
in null, 3
jmp x-- colour_loop
in y, 8
mov isr, ::isr ; reverse for msb-first wire order
out null, 1
public bit_run:
; in isr, n rotates ISR by n bits (right rotation only)
; Use this to perform out shifts from ISR, via mov pins
set x, 31
bit_out:
set pins, 0
mov pins, isr [6]
set pins, 1
in isr, 1 [6]
jmp x-- bit_out
.wrap

View File

@@ -0,0 +1,11 @@
add_executable(pio_differential_manchester)
pico_generate_pio_header(pio_differential_manchester ${CMAKE_CURRENT_LIST_DIR}/differential_manchester.pio)
target_sources(pio_differential_manchester PRIVATE differential_manchester.c)
target_link_libraries(pio_differential_manchester PRIVATE pico_stdlib hardware_pio)
pico_add_extra_outputs(pio_differential_manchester)
# add url via pico_set_program_url
example_auto_set_url(pio_differential_manchester)

View File

@@ -0,0 +1,43 @@
/**
* 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 "differential_manchester.pio.h"
// Differential serial transmit/receive example
// Need to connect a wire from GPIO2 -> GPIO3
const uint pin_tx = 2;
const uint pin_rx = 3;
int main() {
stdio_init_all();
PIO pio = pio0;
uint sm_tx = 0;
uint sm_rx = 1;
uint offset_tx = pio_add_program(pio, &differential_manchester_tx_program);
uint offset_rx = pio_add_program(pio, &differential_manchester_rx_program);
printf("Transmit program loaded at %d\n", offset_tx);
printf("Receive program loaded at %d\n", offset_rx);
// Configure state machines, set bit rate at 5 Mbps
differential_manchester_tx_program_init(pio, sm_tx, offset_tx, pin_tx, 125.f / (16 * 5));
differential_manchester_rx_program_init(pio, sm_rx, offset_rx, pin_rx, 125.f / (16 * 5));
pio_sm_set_enabled(pio, sm_tx, false);
pio_sm_put_blocking(pio, sm_tx, 0);
pio_sm_put_blocking(pio, sm_tx, 0x0ff0a55a);
pio_sm_put_blocking(pio, sm_tx, 0x12345678);
pio_sm_set_enabled(pio, sm_tx, true);
for (int i = 0; i < 3; ++i)
printf("%08x\n", pio_sm_get_blocking(pio, sm_rx));
}

View File

@@ -0,0 +1,102 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program differential_manchester_tx
.side_set 1 opt
; Transmit one bit every cycles. In each bit period:
; - A '0' is encoded as a transition at the start of the bit period
; - A '1' is encoded as a transition at the start *and* in the middle
;
; Side-set bit 0 must be mapped to the data output pin.
; Autopull must be enabled.
public start:
initial_high:
out x, 1 side 1 ; Start of bit period: always assert transition
jmp !x high_0 [6] ; Test the data bit we just shifted out of OSR
high_1:
jmp initial_high side 0 [7] ; For `1` bits, also transition in the middle
high_0:
jmp initial_low [7] ; Otherwise, the line is stable in the middle
initial_low:
out x, 1 side 0 ; Always shift 1 bit from OSR to X so we can
jmp !x low_0 [6] ; branch on it. Autopull refills OSR for us.
low_1:
jmp initial_low side 1 [7] ; If there are two transitions, return to
low_0: ; initial_low on the next bit. If just one,
jmp initial_high [7] ; the initial line state is flipped!
% c-sdk {
static inline void differential_manchester_tx_program_init(PIO pio, uint sm, uint offset, uint pin, float div) {
pio_sm_set_pins_with_mask(pio, sm, 0, 1u << pin);
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
pio_gpio_init(pio, pin);
pio_sm_config c = differential_manchester_tx_program_get_default_config(offset);
sm_config_set_sideset_pins(&c, pin);
sm_config_set_out_shift(&c, true, true, 32);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset + differential_manchester_tx_offset_start, &c);
// Execute a blocking pull so that we maintain the initial line state until data is available
pio_sm_exec(pio, sm, pio_encode_pull(false, true));
pio_sm_set_enabled(pio, sm, true);
}
%}
.program differential_manchester_rx
; Assumes line is idle low
; One bit is 16 cycles. In each bit period:
; - A '0' is encoded as a transition at time 0
; - A '1' is encoded as a transition at time 0 and a transition at time T/2
;
; The IN mapping and the JMP pin select must both be mapped to the GPIO used for
; RX data. Autopush must be enabled.
public start:
initial_high: ; Find rising edge at start of bit period
wait 1 pin, 0 [11] ; Delay to eye of second half-period (i.e 3/4 of way
jmp pin high_0 ; through bit) and branch on RX pin high/low.
high_1:
in x, 1 ; Second transition detected (a `1` data symbol)
jmp initial_high
high_0:
in y, 1 [1] ; Line still high, no centre transition (data is `0`)
; Fall-through
.wrap_target
initial_low: ; Find falling edge at start of bit period
wait 0 pin, 0 [11] ; Delay to eye of second half-period
jmp pin low_1
low_0:
in y, 1 ; Line still low, no centre transition (data is `0`)
jmp initial_high
low_1: ; Second transition detected (data is `1`)
in x, 1 [1]
.wrap
% c-sdk {
static inline void differential_manchester_rx_program_init(PIO pio, uint sm, uint offset, uint pin, float div) {
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false);
pio_gpio_init(pio, pin);
pio_sm_config c = differential_manchester_rx_program_get_default_config(offset);
sm_config_set_in_pins(&c, pin); // for WAIT
sm_config_set_jmp_pin(&c, pin); // for JMP
sm_config_set_in_shift(&c, true, true, 32);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
// X and Y are set to 0 and 1, to conveniently emit these to ISR/FIFO.
pio_sm_exec(pio, sm, pio_encode_set(pio_x, 1));
pio_sm_exec(pio, sm, pio_encode_set(pio_y, 0));
pio_sm_set_enabled(pio, sm, true);
}
%}

View File

@@ -0,0 +1,15 @@
add_executable(hello_pio)
pico_generate_pio_header(hello_pio ${CMAKE_CURRENT_LIST_DIR}/hello.pio)
target_sources(hello_pio PRIVATE hello.c)
target_link_libraries(hello_pio PRIVATE
pico_stdlib
hardware_pio
)
pico_add_extra_outputs(hello_pio)
# add url via pico_set_program_url
example_auto_set_url(hello_pio)

38
pio/hello_pio/hello.c Normal file
View File

@@ -0,0 +1,38 @@
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico/stdlib.h"
#include "hardware/pio.h"
// Our assembled program:
#include "hello.pio.h"
int main() {
// Choose which PIO instance to use (there are two instances)
PIO pio = pio0;
// Our assembled program needs to be loaded into this PIO's instruction
// memory. This SDK function will find a location (offset) in the
// instruction memory where there is enough space for our program. We need
// to remember this location!
uint offset = pio_add_program(pio, &hello_program);
// Find a free state machine on our chosen PIO (erroring if there are
// none). Configure it to run our program, and start it, using the
// helper function we included in our .pio file.
uint sm = pio_claim_unused_sm(pio, true);
hello_program_init(pio, sm, offset, PICO_DEFAULT_LED_PIN);
// The state machine is now running. Any value we push to its TX FIFO will
// appear on the LED pin.
while (true) {
// Blink
pio_sm_put_blocking(pio, sm, 1);
sleep_ms(500);
// Blonk
pio_sm_put_blocking(pio, sm, 0);
sleep_ms(500);
}
}

34
pio/hello_pio/hello.pio Normal file
View File

@@ -0,0 +1,34 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program hello
; Repeatedly get one word of data from the TX FIFO, stalling when the FIFO is
; empty. Write the least significant bit to the OUT pin group.
loop:
pull
out pins, 1
jmp loop
% c-sdk {
static inline void hello_program_init(PIO pio, uint sm, uint offset, uint pin) {
pio_sm_config c = hello_program_get_default_config(offset);
// Map the state machine's OUT pin group to one pin, namely the `pin`
// parameter to this function.
sm_config_set_out_pins(&c, pin, 1);
// Set this pin's GPIO function (connect PIO to the pad)
pio_gpio_init(pio, pin);
// Set the pin direction to output at the PIO
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
// Load our configuration, and jump to the start of the program
pio_sm_init(pio, sm, offset, &c);
// Set the state machine running
pio_sm_set_enabled(pio, sm, true);
}
%}

16
pio/hub75/CMakeLists.txt Normal file
View File

@@ -0,0 +1,16 @@
add_executable(pio_hub75)
pico_generate_pio_header(pio_hub75 ${CMAKE_CURRENT_LIST_DIR}/hub75.pio)
target_sources(pio_hub75 PRIVATE hub75.c)
target_compile_definitions(pio_hub75 PRIVATE
PICO_DEFAULT_UART_TX_PIN=28
PICO_DEFAULT_UART_RX_PIN=29
)
target_link_libraries(pio_hub75 PRIVATE pico_stdlib hardware_pio)
pico_add_extra_outputs(pio_hub75)
# add url via pico_set_program_url
example_auto_set_url(pio_hub75)

45
pio/hub75/Readme.md Normal file
View File

@@ -0,0 +1,45 @@
HUB75E Pinout:
```
/-----\
R0 | o o | G0
B0 | o o | GND
R1 | o o | G1
B1 \ o o | E
A / o o | B
C | o o | D
CLK | o o | STB
OEn | o o | GND
\-----/
```
Wiring:
```
Must be contiguous, in order:
R0 - GPIO0
G0 - GPIO1
B0 - GPIO2
R1 - GPIO3
G1 - GPIO4
B1 - GPIO5
Must be contiguous, somewhat ok to change order:
A - GPIO6
B - GPIO7
C - GPIO8
D - GPIO9
E - GPIO10
Can be anywhere:
CLK - GPIO11
Must be contiguous, in order:
STB - GPIO12
OEn - GPIO13
```
This is a 1/32nd scan panel. The inputs A, B, C, D, E select one of 32 rows, starting at the top and working down (assuming the first pixel to be shifted is the one on the left of the screen, even though this is the "far end" of the shift register). R0, B0, G0 contain pixel data for the upper half of the screen. R1, G1, B1 contain pixel data for the lower half of the screen, which is scanned simultaneously with the upper half.
Image credit for mountains_128x64.png: Paul Gilmore, found on [this wikimedia page](https://commons.wikimedia.org/wiki/File:Mountain_lake_dam.jpg)

78
pio/hub75/hub75.c Normal file
View File

@@ -0,0 +1,78 @@
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/gpio.h"
#include "hardware/pio.h"
#include "hub75.pio.h"
#include "mountains_128x64_rgb565.h"
#define DATA_BASE_PIN 0
#define DATA_N_PINS 6
#define ROWSEL_BASE_PIN 6
#define ROWSEL_N_PINS 5
#define CLK_PIN 11
#define STROBE_PIN 12
#define OEN_PIN 13
#define WIDTH 128
#define HEIGHT 64
static inline uint32_t gamma_correct_565_888(uint16_t pix) {
uint32_t r_gamma = pix & 0xf800u;
r_gamma *= r_gamma;
uint32_t g_gamma = pix & 0x07e0u;
g_gamma *= g_gamma;
uint32_t b_gamma = pix & 0x001fu;
b_gamma *= b_gamma;
return (b_gamma >> 2 << 16) | (g_gamma >> 14 << 8) | (r_gamma >> 24 << 0);
}
int main() {
stdio_init_all();
PIO pio = pio0;
uint sm_data = 0;
uint sm_row = 1;
uint data_prog_offs = pio_add_program(pio, &hub75_data_rgb888_program);
uint row_prog_offs = pio_add_program(pio, &hub75_row_program);
hub75_data_rgb888_program_init(pio, sm_data, data_prog_offs, DATA_BASE_PIN, CLK_PIN);
hub75_row_program_init(pio, sm_row, row_prog_offs, ROWSEL_BASE_PIN, ROWSEL_N_PINS, STROBE_PIN);
static uint32_t gc_row[2][WIDTH];
const uint16_t *img = (const uint16_t*)mountains_128x64;
while (1) {
for (int rowsel = 0; rowsel < (1 << ROWSEL_N_PINS); ++rowsel) {
for (int x = 0; x < WIDTH; ++x) {
gc_row[0][x] = gamma_correct_565_888(img[rowsel * WIDTH + x]);
gc_row[1][x] = gamma_correct_565_888(img[((1u << ROWSEL_N_PINS) + rowsel) * WIDTH + x]);
}
for (int bit = 0; bit < 8; ++bit) {
hub75_data_rgb888_set_shift(pio, sm_data, data_prog_offs, bit);
for (int x = 0; x < WIDTH; ++x) {
pio_sm_put_blocking(pio, sm_data, gc_row[0][x]);
pio_sm_put_blocking(pio, sm_data, gc_row[1][x]);
}
// Dummy pixel per lane
pio_sm_put_blocking(pio, sm_data, 0);
pio_sm_put_blocking(pio, sm_data, 0);
// SM is finished when it stalls on empty TX FIFO
hub75_wait_tx_stall(pio, sm_data);
// Also check that previous OEn pulse is finished, else things can get out of sequence
hub75_wait_tx_stall(pio, sm_row);
// Latch row data, pulse output enable for new row.
pio_sm_put_blocking(pio, sm_row, rowsel | (100u * (1u << bit) << 5));
}
}
}
}

122
pio/hub75/hub75.pio Normal file
View File

@@ -0,0 +1,122 @@
.program hub75_row
; side-set pin 0 is LATCH
; side-set pin 1 is OEn
; OUT pins are row select A-E
;
; Each FIFO record consists of:
; - 5-bit row select (LSBs)
; - Pulse width - 1 (27 MSBs)
;
; Repeatedly select a row, pulse LATCH, and generate a pulse of a certain
; width on OEn.
.side_set 2
.wrap_target
out pins, 5 [7] side 0x2 ; Deassert OEn, output row select
out x, 27 [7] side 0x3 ; Pulse LATCH, get OEn pulse width
pulse_loop:
jmp x-- pulse_loop side 0x0 ; Assert OEn for x+1 cycles
.wrap
% c-sdk {
static inline void hub75_row_program_init(PIO pio, uint sm, uint offset, uint row_base_pin, uint n_row_pins, uint latch_base_pin) {
pio_sm_set_consecutive_pindirs(pio, sm, row_base_pin, n_row_pins, true);
pio_sm_set_consecutive_pindirs(pio, sm, latch_base_pin, 2, true);
for (uint i = row_base_pin; i < row_base_pin + n_row_pins; ++i)
pio_gpio_init(pio, i);
pio_gpio_init(pio, latch_base_pin);
pio_gpio_init(pio, latch_base_pin + 1);
pio_sm_config c = hub75_row_program_get_default_config(offset);
sm_config_set_out_pins(&c, row_base_pin, n_row_pins);
sm_config_set_sideset_pins(&c, latch_base_pin);
sm_config_set_out_shift(&c, true, true, 32);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
static inline void hub75_wait_tx_stall(PIO pio, uint sm) {
uint32_t txstall_mask = 1u << (PIO_FDEBUG_TXSTALL_LSB + sm);
pio->fdebug = txstall_mask;
while (!(pio->fdebug & txstall_mask))
tight_loop_contents();
}
%}
.program hub75_data_rgb888
.side_set 1
; Each FIFO record consists of a RGB888 pixel. (This is ok for e.g. an RGB565
; source which has been gamma-corrected)
;
; Even pixels are sent on R0, G0, B0 and odd pixels on R1, G1, B1 (typically
; these are for different parts of the screen, NOT for adjacent pixels, so the
; frame buffer must be interleaved before passing to PIO.)
;
; Each pass through, we take bit n, n + 8 and n + 16 from each pixel, for n in
; {0...7}. Therefore the pixels need to be transmitted 8 times (ouch) to build
; up the full 8 bit value for each channel, and perform bit-planed PWM by
; varying pulse widths on the other state machine, in ascending powers of 2.
; This avoids a lot of bit shuffling on the processors, at the cost of DMA
; bandwidth (which we have loads of).
; Might want to close your eyes before you read this
public entry_point:
.wrap_target
public shift0:
pull side 0 ; gets patched to `out null, n` if n nonzero (otherwise the PULL is required for fencing)
in osr, 1 side 0 ; shuffle shuffle shuffle
out null, 8 side 0
in osr, 1 side 0
out null, 8 side 0
in osr, 1 side 0
out null, 32 side 0 ; Discard remainder of OSR contents
public shift1:
pull side 0 ; gets patched to out null, n if n is nonzero (otherwise PULL required)
in osr, 1 side 1 ; Note this posedge clocks in the data from the previous iteration
out null, 8 side 1
in osr, 1 side 1
out null, 8 side 1
in osr, 1 side 1
out null, 32 side 1
in null, 26 side 1 ; Note we are just doing this little manoeuvre here to get GPIOs in the order
mov pins, ::isr side 1 ; R0, G0, B0, R1, G1, B1. Can go 1 cycle faster if reversed
.wrap
; Note that because the clock edge for pixel n is in the middle of pixel n +
; 1, a dummy pixel at the end is required to clock the last piece of genuine
; data. (Also 1 pixel of garbage is clocked out at the start, but this is
; harmless)
% c-sdk {
static inline void hub75_data_rgb888_program_init(PIO pio, uint sm, uint offset, uint rgb_base_pin, uint clock_pin) {
pio_sm_set_consecutive_pindirs(pio, sm, rgb_base_pin, 6, true);
pio_sm_set_consecutive_pindirs(pio, sm, clock_pin, 1, true);
for (uint i = rgb_base_pin; i < rgb_base_pin + 6; ++i)
pio_gpio_init(pio, i);
pio_gpio_init(pio, clock_pin);
pio_sm_config c = hub75_data_rgb888_program_get_default_config(offset);
sm_config_set_out_pins(&c, rgb_base_pin, 6);
sm_config_set_sideset_pins(&c, clock_pin);
sm_config_set_out_shift(&c, true, true, 24);
// ISR shift to left. R0 ends up at bit 5. We push it up to MSB and then flip the register.
sm_config_set_in_shift(&c, false, false, 32);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
pio_sm_init(pio, sm, offset, &c);
pio_sm_exec(pio, sm, offset + hub75_data_rgb888_offset_entry_point);
pio_sm_set_enabled(pio, sm, true);
}
// Patch a data program at `offset` to preshift pixels by `shamt`
static inline void hub75_data_rgb888_set_shift(PIO pio, uint sm, uint offset, uint shamt) {
uint16_t instr;
if (shamt == 0)
instr = pio_encode_pull(false, true); // blocking PULL
else
instr = pio_encode_out(pio_null, shamt);
pio->instr_mem[offset + hub75_data_rgb888_offset_shift0] = instr;
pio->instr_mem[offset + hub75_data_rgb888_offset_shift1] = instr;
}
%}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because it is too large Load Diff

16
pio/i2c/CMakeLists.txt Normal file
View File

@@ -0,0 +1,16 @@
add_executable(pio_i2c_bus_scan)
pico_generate_pio_header(pio_i2c_bus_scan ${CMAKE_CURRENT_LIST_DIR}/i2c.pio)
target_sources(pio_i2c_bus_scan PRIVATE
i2c_bus_scan.c
pio_i2c.c
pio_i2c.h
)
target_link_libraries(pio_i2c_bus_scan PRIVATE pico_stdlib hardware_pio)
pico_add_extra_outputs(pio_i2c_bus_scan)
# add url via pico_set_program_url
example_auto_set_url(pio_i2c_bus_scan)

144
pio/i2c/i2c.pio Normal file
View File

@@ -0,0 +1,144 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program i2c
.side_set 1 opt pindirs
; TX Encoding:
; | 15:10 | 9 | 8:1 | 0 |
; | Instr | Final | Data | NAK |
;
; If Instr has a value n > 0, then this FIFO word has no
; data payload, and the next n + 1 words will be executed as instructions.
; Otherwise, shift out the 8 data bits, followed by the ACK bit.
;
; The Instr mechanism allows stop/start/repstart sequences to be programmed
; by the processor, and then carried out by the state machine at defined points
; in the datastream.
;
; The "Final" field should be set for the final byte in a transfer.
; This tells the state machine to ignore a NAK: if this field is not
; set, then any NAK will cause the state machine to halt and interrupt.
;
; Autopull should be enabled, with a threshold of 16.
; Autopush should be enabled, with a threshold of 8.
; The TX FIFO should be accessed with halfword writes, to ensure
; the data is immediately available in the OSR.
;
; Pin mapping:
; - Input pin 0 is SDA, 1 is SCL (if clock stretching used)
; - Jump pin is SDA
; - Side-set pin 0 is SCL
; - Set pin 0 is SDA
; - OUT pin 0 is SDA
; - SCL must be SDA + 1 (for wait mapping)
;
; The OE outputs should be inverted in the system IO controls!
; (It's possible for the inversion to be done in this program,
; but costs 2 instructions: 1 for inversion, and one to cope
; with the side effect of the MOV on TX shift counter.)
do_nack:
jmp y-- entry_point ; Continue if NAK was expected
irq wait 0 rel ; Otherwise stop, ask for help
do_byte:
set x, 7 ; Loop 8 times
bitloop:
out pindirs, 1 [7] ; Serialise write data (all-ones if reading)
nop side 1 [2] ; SCL rising edge
wait 1 pin, 1 [4] ; Allow clock to be stretched
in pins, 1 [7] ; Sample read data in middle of SCL pulse
jmp x-- bitloop side 0 [7] ; SCL falling edge
; Handle ACK pulse
out pindirs, 1 [7] ; On reads, we provide the ACK.
nop side 1 [7] ; SCL rising edge
wait 1 pin, 1 [7] ; Allow clock to be stretched
jmp pin do_nack side 0 [2] ; Test SDA for ACK/NAK, fall through if ACK
public entry_point:
.wrap_target
out x, 6 ; Unpack Instr count
out y, 1 ; Unpack the NAK ignore bit
jmp !x do_byte ; Instr == 0, this is a data record.
out null, 32 ; Instr > 0, remainder of this OSR is invalid
do_exec:
out exec, 16 ; Execute one instruction per FIFO word
jmp x-- do_exec ; Repeat n + 1 times
.wrap
% c-sdk {
#include "hardware/clocks.h"
#include "hardware/gpio.h"
static inline void i2c_program_init(PIO pio, uint sm, uint offset, uint pin_sda, uint pin_scl) {
assert(pin_scl == pin_sda + 1);
pio_sm_config c = i2c_program_get_default_config(offset);
// IO mapping
sm_config_set_out_pins(&c, pin_sda, 1);
sm_config_set_set_pins(&c, pin_sda, 1);
sm_config_set_in_pins(&c, pin_sda);
sm_config_set_sideset_pins(&c, pin_scl);
sm_config_set_jmp_pin(&c, pin_sda);
sm_config_set_out_shift(&c, false, true, 16);
sm_config_set_in_shift(&c, false, true, 8);
float div = (float)clock_get_hz(clk_sys) / (32 * 100000);
sm_config_set_clkdiv(&c, div);
// Try to avoid glitching the bus while connecting the IOs. Get things set
// up so that pin is driven down when PIO asserts OE low, and pulled up
// otherwise.
gpio_pull_up(pin_scl);
gpio_pull_up(pin_sda);
uint32_t both_pins = (1u << pin_sda) | (1u << pin_scl);
pio_sm_set_pins_with_mask(pio, sm, both_pins, both_pins);
pio_sm_set_pindirs_with_mask(pio, sm, both_pins, both_pins);
pio_gpio_init(pio, pin_sda);
gpio_set_oeover(pin_sda, GPIO_OVERRIDE_INVERT);
pio_gpio_init(pio, pin_scl);
gpio_set_oeover(pin_scl, GPIO_OVERRIDE_INVERT);
pio_sm_set_pins_with_mask(pio, sm, 0, both_pins);
// Clear IRQ flag before starting
hw_clear_bits(&pio->inte0, 1u << sm);
hw_clear_bits(&pio->inte1, 1u << sm);
pio->irq = 1u << sm;
// Configure and start SM
pio_sm_init(pio, sm, offset + i2c_offset_entry_point, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}
.program set_scl_sda
.side_set 1 opt
; Assemble a table of instructions which software can select from, and pass
; into the FIFO, to issue START/STOP/RSTART. This isn't intended to be run as
; a complete program.
set pindirs, 0 side 0 [7] ; SCL = 0, SDA = 0
set pindirs, 1 side 0 [7] ; SCL = 0, SDA = 1
set pindirs, 0 side 1 [7] ; SCL = 1, SDA = 0
set pindirs, 1 side 1 [7] ; SCL = 1, SDA = 1
% c-sdk {
// Define order of our instruction table
enum {
I2C_SC0_SD0 = 0,
I2C_SC0_SD1,
I2C_SC1_SD0,
I2C_SC1_SD1
};
%}

42
pio/i2c/i2c_bus_scan.c Normal file
View File

@@ -0,0 +1,42 @@
#include <stdio.h>
#include "pico/stdlib.h"
#include "pio_i2c.h"
#define PIN_SDA 2
#define PIN_SCL 3
bool reserved_addr(uint8_t addr) {
return (addr & 0x78) == 0 || (addr & 0x78) == 0x78;
}
int main() {
stdio_init_all();
PIO pio = pio0;
uint sm = 0;
uint offset = pio_add_program(pio, &i2c_program);
i2c_program_init(pio, sm, offset, PIN_SDA, PIN_SCL);
printf("\nPIO I2C Bus Scan\n");
printf(" 0 1 2 3 4 5 6 7 8 9 A B C D E F\n");
for (int addr = 0; addr < (1 << 7); ++addr) {
if (addr % 16 == 0) {
printf("%02x ", addr);
}
// Perform a 0-byte read from the probe address. The read function
// returns a negative result NAK'd any time other than the last data
// byte. Skip over reserved addresses.
int result;
if (reserved_addr(addr))
result = -1;
else
result = pio_i2c_read_blocking(pio, sm, addr, NULL, 0);
printf(result < 0 ? "." : "@");
printf(addr % 16 == 15 ? "\n" : " ");
}
printf("Done.\n");
return 0;
}

139
pio/i2c/pio_i2c.c Normal file
View File

@@ -0,0 +1,139 @@
/**
* Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pio_i2c.h"
const int PIO_I2C_ICOUNT_LSB = 10;
const int PIO_I2C_FINAL_LSB = 9;
const int PIO_I2C_DATA_LSB = 1;
const int PIO_I2C_NAK_LSB = 0;
bool pio_i2c_check_error(PIO pio, uint sm) {
return !!(pio->irq & (1u << sm));
}
void pio_i2c_resume_after_error(PIO pio, uint sm) {
pio_sm_drain_tx_fifo(pio, sm);
pio_sm_exec(pio, sm, (pio->sm[sm].execctrl & PIO_SM0_EXECCTRL_WRAP_BOTTOM_BITS) >> PIO_SM0_EXECCTRL_WRAP_BOTTOM_LSB);
pio->irq = 1u << sm;
}
void pio_i2c_rx_enable(PIO pio, uint sm, bool en) {
if (en)
hw_set_bits(&pio->sm[sm].shiftctrl, PIO_SM0_SHIFTCTRL_AUTOPUSH_BITS);
else
hw_clear_bits(&pio->sm[sm].shiftctrl, PIO_SM0_SHIFTCTRL_AUTOPUSH_BITS);
}
static inline void pio_i2c_put16(PIO pio, uint sm, uint16_t data) {
while (pio_sm_is_tx_fifo_full(pio, sm))
;
*(io_rw_16 *)&pio->txf[sm] = data;
}
// If I2C is ok, block and push data. Otherwise fall straight through.
void pio_i2c_put_or_err(PIO pio, uint sm, uint16_t data) {
while (pio_sm_is_tx_fifo_full(pio, sm))
if (pio_i2c_check_error(pio, sm))
return;
if (pio_i2c_check_error(pio, sm))
return;
*(io_rw_16 *)&pio->txf[sm] = data;
}
uint8_t pio_i2c_get(PIO pio, uint sm) {
return (uint8_t)pio_sm_get(pio, sm);
}
void pio_i2c_start(PIO pio, uint sm) {
pio_i2c_put_or_err(pio, sm, 1u << PIO_I2C_ICOUNT_LSB); // Escape code for 2 instruction sequence
pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD0]); // We are already in idle state, just pull SDA low
pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC0_SD0]); // Also pull clock low so we can present data
}
void pio_i2c_stop(PIO pio, uint sm) {
pio_i2c_put_or_err(pio, sm, 2u << PIO_I2C_ICOUNT_LSB);
pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC0_SD0]); // SDA is unknown; pull it down
pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD0]); // Release clock
pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD1]); // Release SDA to return to idle state
};
void pio_i2c_repstart(PIO pio, uint sm) {
pio_i2c_put_or_err(pio, sm, 3u << PIO_I2C_ICOUNT_LSB);
pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC0_SD1]);
pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD1]);
pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC1_SD0]);
pio_i2c_put_or_err(pio, sm, set_scl_sda_program_instructions[I2C_SC0_SD0]);
}
static void pio_i2c_wait_idle(PIO pio, uint sm) {
// Finished when TX runs dry or SM hits an IRQ
pio->fdebug = 1u << (PIO_FDEBUG_TXSTALL_LSB + sm);
while (!(pio->fdebug & 1u << (PIO_FDEBUG_TXSTALL_LSB + sm) || pio_i2c_check_error(pio, sm)))
tight_loop_contents();
}
int pio_i2c_write_blocking(PIO pio, uint sm, uint8_t addr, uint8_t *txbuf, uint len) {
int err = 0;
pio_i2c_start(pio, sm);
pio_i2c_rx_enable(pio, sm, false);
pio_i2c_put16(pio, sm, (addr << 2) | 1u);
while (len && !pio_i2c_check_error(pio, sm)) {
if (!pio_sm_is_tx_fifo_full(pio, sm)) {
--len;
pio_i2c_put_or_err(pio, sm, (*txbuf++ << PIO_I2C_DATA_LSB) | ((len == 0) << PIO_I2C_FINAL_LSB) | 1u);
}
}
pio_i2c_stop(pio, sm);
pio_i2c_wait_idle(pio, sm);
if (pio_i2c_check_error(pio, sm)) {
err = -1;
pio_i2c_resume_after_error(pio, sm);
pio_i2c_stop(pio, sm);
}
return err;
}
int pio_i2c_read_blocking(PIO pio, uint sm, uint8_t addr, uint8_t *rxbuf, uint len) {
int err = 0;
pio_i2c_start(pio, sm);
pio_i2c_rx_enable(pio, sm, true);
while (!pio_sm_is_rx_fifo_empty(pio, sm))
(void)pio_i2c_get(pio, sm);
pio_i2c_put16(pio, sm, (addr << 2) | 3u);
uint32_t tx_remain = len; // Need to stuff 0xff bytes in to get clocks
bool first = true;
while ((tx_remain || len) && !pio_i2c_check_error(pio, sm)) {
if (tx_remain && !pio_sm_is_tx_fifo_full(pio, sm)) {
--tx_remain;
pio_i2c_put16(pio, sm, (0xffu << 1) | (tx_remain ? 0 : (1u << PIO_I2C_FINAL_LSB) | (1u << PIO_I2C_NAK_LSB)));
}
if (!pio_sm_is_rx_fifo_empty(pio, sm)) {
if (first) {
// Ignore returned address byte
(void)pio_i2c_get(pio, sm);
first = false;
}
else {
--len;
*rxbuf++ = pio_i2c_get(pio, sm);
}
}
}
pio_i2c_stop(pio, sm);
pio_i2c_wait_idle(pio, sm);
if (pio_i2c_check_error(pio, sm)) {
err = -1;
pio_i2c_resume_after_error(pio, sm);
pio_i2c_stop(pio, sm);
}
return err;
}

31
pio/i2c/pio_i2c.h Normal file
View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) 2021 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PIO_I2C_H
#define _PIO_I2C_H
#include "i2c.pio.h"
// ----------------------------------------------------------------------------
// Low-level functions
void pio_i2c_start(PIO pio, uint sm);
void pio_i2c_stop(PIO pio, uint sm);
void pio_i2c_repstart(PIO pio, uint sm);
bool pio_i2c_check_error(PIO pio, uint sm);
void pio_i2c_resume_after_error(PIO pio, uint sm);
// If I2C is ok, block and push data. Otherwise fall straight through.
void pio_i2c_put_or_err(PIO pio, uint sm, uint16_t data);
uint8_t pio_i2c_get(PIO pio, uint sm);
// ----------------------------------------------------------------------------
// Transaction-level functions
int pio_i2c_write_blocking(PIO pio, uint sm, uint8_t addr, uint8_t *txbuf, uint len);
int pio_i2c_read_blocking(PIO pio, uint sm, uint8_t addr, uint8_t *rxbuf, uint len);
#endif

View File

@@ -0,0 +1,9 @@
add_executable(pio_logic_analyser)
target_sources(pio_logic_analyser PRIVATE logic_analyser.c)
target_link_libraries(pio_logic_analyser PRIVATE pico_stdlib hardware_pio hardware_dma)
pico_add_extra_outputs(pio_logic_analyser)
# add url via pico_set_program_url
example_auto_set_url(pio_logic_analyser)

View File

@@ -0,0 +1,125 @@
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
// PIO logic analyser example
//
// This program captures samples from a group of pins, at a fixed rate, once a
// trigger condition is detected (level condition on one pin). The samples are
// transferred to a capture buffer using the system DMA.
//
// 1 to 32 pins can be captured, at a sample rate no greater than system clock
// frequency.
#include <stdio.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "hardware/dma.h"
// Some logic to analyse:
#include "hardware/structs/pwm.h"
const uint CAPTURE_PIN_BASE = 16;
const uint CAPTURE_PIN_COUNT = 2;
const uint CAPTURE_N_SAMPLES = 96;
void logic_analyser_init(PIO pio, uint sm, uint pin_base, uint pin_count, float div) {
// Load a program to capture n pins. This is just a single `in pins, n`
// instruction with a wrap.
uint16_t capture_prog_instr = pio_encode_in(pio_pins, pin_count);
struct pio_program capture_prog = {
.instructions = &capture_prog_instr,
.length = 1,
.origin = -1
};
uint offset = pio_add_program(pio, &capture_prog);
// Configure state machine to loop over this `in` instruction forever,
// with autopush enabled.
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_in_pins(&c, pin_base);
sm_config_set_wrap(&c, offset, offset);
sm_config_set_clkdiv(&c, div);
sm_config_set_in_shift(&c, true, true, 32);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
pio_sm_init(pio, sm, offset, &c);
}
void logic_analyser_arm(PIO pio, uint sm, uint dma_chan, uint32_t *capture_buf, size_t capture_size_words,
uint trigger_pin, bool trigger_level) {
pio_sm_set_enabled(pio, sm, false);
pio_sm_clear_fifos(pio, sm);
dma_channel_config c = dma_channel_get_default_config(dma_chan);
channel_config_set_read_increment(&c, false);
channel_config_set_write_increment(&c, true);
channel_config_set_dreq(&c, pio_get_dreq(pio, sm, false));
dma_channel_configure(dma_chan, &c,
capture_buf, // Destinatinon pointer
&pio->rxf[sm], // Source pointer
capture_size_words, // Number of transfers
true // Start immediately
);
pio_sm_exec(pio, sm, pio_encode_wait_gpio(trigger_level, trigger_pin));
pio_sm_set_enabled(pio, sm, true);
}
void print_capture_buf(const uint32_t *buf, uint pin_base, uint pin_count, uint32_t n_samples) {
// Display the capture buffer in text form, like this:
// 00: __--__--__--__--__--__--
// 01: ____----____----____----
printf("Capture:\n");
for (int pin = 0; pin < pin_count; ++pin) {
printf("%02d: ", pin + pin_base);
for (int sample = 0; sample < n_samples; ++sample) {
uint bit_index = pin + sample * pin_count;
bool level = !!(buf[bit_index / 32] & 1u << (bit_index % 32));
printf(level ? "-" : "_");
}
printf("\n");
}
}
int main() {
stdio_init_all();
printf("PIO logic analyser example\n");
uint32_t capture_buf[(CAPTURE_PIN_COUNT * CAPTURE_N_SAMPLES + 31) / 32];
PIO pio = pio0;
uint sm = 0;
uint dma_chan = 0;
logic_analyser_init(pio, sm, CAPTURE_PIN_BASE, CAPTURE_PIN_COUNT, 1.f);
printf("Arming trigger\n");
logic_analyser_arm(pio, sm, dma_chan, capture_buf, //;
(CAPTURE_PIN_COUNT * CAPTURE_N_SAMPLES + 31) / 32,
CAPTURE_PIN_BASE, true);
printf("Starting PWM example\n");
// PWM example: -----------------------------------------------------------
gpio_set_function(CAPTURE_PIN_BASE, GPIO_FUNC_PWM);
gpio_set_function(CAPTURE_PIN_BASE + 1, GPIO_FUNC_PWM);
// Topmost value of 3: count from 0 to 3 and then wrap, so period is 4 cycles
pwm_hw->slice[0].top = 3;
// Divide frequency by two to slow things down a little
pwm_hw->slice[0].div = 4 << PWM_CH0_DIV_INT_LSB;
// Set channel A to be high for 1 cycle each period (duty cycle 1/4) and
// channel B for 3 cycles (duty cycle 3/4)
pwm_hw->slice[0].cc =
(1 << PWM_CH0_CC_A_LSB) |
(3 << PWM_CH0_CC_B_LSB);
// Enable this PWM slice
pwm_hw->slice[0].csr = PWM_CH0_CSR_EN_BITS;
// ------------------------------------------------------------------------
dma_channel_wait_for_finish_blocking(dma_chan);
print_capture_buf(capture_buf, CAPTURE_PIN_BASE, CAPTURE_PIN_COUNT, CAPTURE_N_SAMPLES);
}

View File

@@ -0,0 +1,11 @@
add_executable(pio_manchester_encoding)
pico_generate_pio_header(pio_manchester_encoding ${CMAKE_CURRENT_LIST_DIR}/manchester_encoding.pio)
target_sources(pio_manchester_encoding PRIVATE manchester_encoding.c)
target_link_libraries(pio_manchester_encoding PRIVATE pico_stdlib hardware_pio)
pico_add_extra_outputs(pio_manchester_encoding)
# add url via pico_set_program_url
example_auto_set_url(pio_manchester_encoding)

View File

@@ -0,0 +1,43 @@
/**
* 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 "manchester_encoding.pio.h"
// Manchester serial transmit/receive example. This transmits and receives at
// 10 Mbps if sysclk is 120 MHz.
// Need to connect a wire from GPIO2 -> GPIO3
const uint pin_tx = 2;
const uint pin_rx = 3;
int main() {
stdio_init_all();
PIO pio = pio0;
uint sm_tx = 0;
uint sm_rx = 1;
uint offset_tx = pio_add_program(pio, &manchester_tx_program);
uint offset_rx = pio_add_program(pio, &manchester_rx_program);
printf("Transmit program loaded at %d\n", offset_tx);
printf("Receive program loaded at %d\n", offset_rx);
manchester_tx_program_init(pio, sm_tx, offset_tx, pin_tx, 1.f);
manchester_rx_program_init(pio, sm_rx, offset_rx, pin_rx, 1.f);
pio_sm_set_enabled(pio, sm_tx, false);
pio_sm_put_blocking(pio, sm_tx, 0);
pio_sm_put_blocking(pio, sm_tx, 0x0ff0a55a);
pio_sm_put_blocking(pio, sm_tx, 0x12345678);
pio_sm_set_enabled(pio, sm_tx, true);
for (int i = 0; i < 3; ++i)
printf("%08x\n", pio_sm_get_blocking(pio, sm_rx));
}

View File

@@ -0,0 +1,94 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program manchester_tx
.side_set 1 opt
; Transmit one bit every 12 cycles. a '0' is encoded as a high-low sequence
; (each part lasting half a bit period, or 6 cycles) and a '1' is encoded as a
; low-high sequence.
;
; Side-set bit 0 must be mapped to the GPIO used for TX.
; Autopull must be enabled -- this program does not care about the threshold.
; The program starts at the public label 'start'.
.wrap_target
do_1:
nop side 0 [5] ; Low for 6 cycles (5 delay, +1 for nop)
jmp get_bit side 1 [3] ; High for 4 cycles. 'get_bit' takes another 2 cycles
do_0:
nop side 1 [5] ; Output high for 6 cycles
nop side 0 [3] ; Output low for 4 cycles
public start:
get_bit:
out x, 1 ; Always shift out one bit from OSR to X, so we can
jmp !x do_0 ; branch on it. Autopull refills the OSR when empty.
.wrap
% c-sdk {
static inline void manchester_tx_program_init(PIO pio, uint sm, uint offset, uint pin, float div) {
pio_sm_set_pins_with_mask(pio, sm, 0, 1u << pin);
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
pio_gpio_init(pio, pin);
pio_sm_config c = manchester_tx_program_get_default_config(offset);
sm_config_set_sideset_pins(&c, pin);
sm_config_set_out_shift(&c, true, true, 32);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset + manchester_tx_offset_start, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}
.program manchester_rx
; Assumes line is idle low, first bit is 0
; One bit is 12 cycles
; a '0' is encoded as 10
; a '1' is encoded as 01
;
; Both the IN base and the JMP pin mapping must be pointed at the GPIO used for RX.
; Autopush must be enabled.
; Before enabling the SM, it should be placed in a 'wait 1, pin` state, so that
; it will not start sampling until the initial line idle state ends.
start_of_0: ; We are 0.25 bits into a 0 - signal is high
wait 0 pin 0 ; Wait for the 1->0 transition - at this point we are 0.5 into the bit
in y, 1 [8] ; Emit a 0, sleep 3/4 of a bit
jmp pin start_of_0 ; If signal is 1 again, it's another 0 bit, otherwise it's a 1
.wrap_target
start_of_1: ; We are 0.25 bits into a 1 - signal is 1
wait 1 pin 0 ; Wait for the 0->1 transition - at this point we are 0.5 into the bit
in x, 1 [8] ; Emit a 1, sleep 3/4 of a bit
jmp pin start_of_0 ; If signal is 0 again, it's another 1 bit otherwise it's a 0
.wrap
% c-sdk {
static inline void manchester_rx_program_init(PIO pio, uint sm, uint offset, uint pin, float div) {
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false);
pio_gpio_init(pio, pin);
pio_sm_config c = manchester_rx_program_get_default_config(offset);
sm_config_set_in_pins(&c, pin); // for WAIT
sm_config_set_jmp_pin(&c, pin); // for JMP
sm_config_set_in_shift(&c, true, true, 32);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
// X and Y are set to 0 and 1, to conveniently emit these to ISR/FIFO.
pio_sm_exec(pio, sm, pio_encode_set(pio_x, 1));
pio_sm_exec(pio, sm, pio_encode_set(pio_y, 0));
// Assume line is idle low, and first transmitted bit is 0. Put SM in a
// wait state before enabling. RX will begin once the first 0 symbol is
// detected.
pio_sm_exec(pio, sm, pio_encode_wait_pin(1, 0) | pio_encode_delay(2));
pio_sm_set_enabled(pio, sm, true);
}
%}

View File

@@ -0,0 +1,14 @@
add_executable(pio_blink)
# by default the header is generated into the build dir
pico_generate_pio_header(pio_blink ${CMAKE_CURRENT_LIST_DIR}/blink.pio)
# however, alternatively you can choose to generate it somewhere else (in this case in the source tree for check in)
#pico_generate_pio_header(pio_blink ${CMAKE_CURRENT_LIST_DIR}/blink.pio OUTPUT_DIR ${CMAKE_CURRENT_LIST_DIR})
target_sources(pio_blink PRIVATE blink.c)
target_link_libraries(pio_blink PRIVATE pico_stdlib hardware_pio)
pico_add_extra_outputs(pio_blink)
# add url via pico_set_program_url
example_auto_set_url(pio_blink)

34
pio/pio_blink/blink.c Normal file
View File

@@ -0,0 +1,34 @@
/**
* 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 "blink.pio.h"
void blink_pin_forever(PIO pio, uint sm, uint offset, uint pin, uint freq);
int main() {
setup_default_uart();
// todo get free sm
PIO pio = pio0;
uint offset = pio_add_program(pio, &blink_program);
printf("Loaded program at %d\n", offset);
blink_pin_forever(pio, 0, offset, 0, 3);
blink_pin_forever(pio, 1, offset, 6, 4);
blink_pin_forever(pio, 2, offset, 11, 1);
}
void blink_pin_forever(PIO pio, uint sm, uint offset, uint pin, uint freq) {
blink_program_init(pio, sm, offset, pin);
pio_sm_set_enabled(pio, sm, true);
printf("Blinking pin %d at freq %d\n", pin, freq);
pio->txf[sm] = 24000000 / freq;
}

34
pio/pio_blink/blink.pio Normal file
View File

@@ -0,0 +1,34 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
; SET pin 0 should be mapped to your LED GPIO
.program blink
pull block
out y, 32
.wrap_target
mov x, y
set pins, 1 ; Turn LED on
lp1:
jmp x-- lp1 ; Delay for (x + 1) cycles, x is a 32 bit number
mov x, y
set pins, 0 ; Turn LED off
lp2:
jmp x-- lp2 ; Delay for the same number of cycles again
.wrap ; Blink forever!
% c-sdk {
// this is a raw helper function for use by the user which sets up the GPIO output, and configures the SM to output on a particular pin
void blink_program_init(PIO pio, uint sm, uint offset, uint pin) {
pio_gpio_init(pio, pin);
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
pio_sm_config c = blink_program_get_default_config(offset);
sm_config_set_set_pins(&c, pin, 1);
pio_sm_init(pio, sm, offset, &c);
}
%}

11
pio/pwm/CMakeLists.txt Normal file
View File

@@ -0,0 +1,11 @@
add_executable(pio_pwm)
pico_generate_pio_header(pio_pwm ${CMAKE_CURRENT_LIST_DIR}/pwm.pio)
target_sources(pio_pwm PRIVATE pwm.c)
target_link_libraries(pio_pwm PRIVATE pico_stdlib hardware_pio)
pico_add_extra_outputs(pio_pwm)
# add url via pico_set_program_url
example_auto_set_url(pio_pwm)

46
pio/pwm/pwm.c Normal file
View File

@@ -0,0 +1,46 @@
/**
* 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();
// 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, 25);
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);
}
}

31
pio/pwm/pwm.pio Normal file
View File

@@ -0,0 +1,31 @@
;
; 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) {
pio_gpio_init(pio, pin);
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
pio_sm_config c = pwm_program_get_default_config(offset);
sm_config_set_sideset_pins(&c, pin);
pio_sm_init(pio, sm, offset, &c);
}
%}

29
pio/spi/CMakeLists.txt Normal file
View File

@@ -0,0 +1,29 @@
add_executable(pio_spi_flash)
pico_generate_pio_header(pio_spi_flash ${CMAKE_CURRENT_LIST_DIR}/spi.pio)
target_sources(pio_spi_flash PRIVATE
spi_flash.c
pio_spi.c
pio_spi.h
)
target_link_libraries(pio_spi_flash PRIVATE pico_stdlib hardware_pio)
pico_add_extra_outputs(pio_spi_flash)
example_auto_set_url(pio_spi_flash)
add_executable(pio_spi_loopback)
pico_generate_pio_header(pio_spi_loopback ${CMAKE_CURRENT_LIST_DIR}/spi.pio)
target_sources(pio_spi_loopback PRIVATE
spi_loopback.c
pio_spi.c
pio_spi.h
)
target_link_libraries(pio_spi_loopback PRIVATE pico_stdlib hardware_pio)
pico_add_extra_outputs(pio_spi_loopback)
example_auto_set_url(pio_spi_loopback)

68
pio/spi/pio_spi.c Normal file
View File

@@ -0,0 +1,68 @@
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pio_spi.h"
// Just 8 bit functions provided here. The PIO program supports any frame size
// 1...32, but the software to do the necessary FIFO shuffling is left as an
// exercise for the reader :)
//
// Likewise we only provide MSB-first here. To do LSB-first, you need to
// - Do shifts when reading from the FIFO, for general case n != 8, 16, 32
// - Do a narrow read at a one halfword or 3 byte offset for n == 16, 8
// in order to get the read data correctly justified.
void __time_critical_func(pio_spi_write8_blocking)(const pio_spi_inst_t *spi, const uint8_t *src, size_t len) {
size_t tx_remain = len, rx_remain = len;
// Do 8 bit accesses on FIFO, so that write data is byte-replicated. This
// gets us the left-justification for free (for MSB-first shift-out)
io_rw_8 *txfifo = (io_rw_8 *) &spi->pio->txf[spi->sm];
io_rw_8 *rxfifo = (io_rw_8 *) &spi->pio->rxf[spi->sm];
while (tx_remain || rx_remain) {
if (tx_remain && !pio_sm_is_tx_fifo_full(spi->pio, spi->sm)) {
*txfifo = *src++;
--tx_remain;
}
if (rx_remain && !pio_sm_is_rx_fifo_empty(spi->pio, spi->sm)) {
(void) *rxfifo;
--rx_remain;
}
}
}
void __time_critical_func(pio_spi_read8_blocking)(const pio_spi_inst_t *spi, uint8_t *dst, size_t len) {
size_t tx_remain = len, rx_remain = len;
io_rw_8 *txfifo = (io_rw_8 *) &spi->pio->txf[spi->sm];
io_rw_8 *rxfifo = (io_rw_8 *) &spi->pio->rxf[spi->sm];
while (tx_remain || rx_remain) {
if (tx_remain && !pio_sm_is_tx_fifo_full(spi->pio, spi->sm)) {
*txfifo = 0;
--tx_remain;
}
if (rx_remain && !pio_sm_is_rx_fifo_empty(spi->pio, spi->sm)) {
*dst++ = *rxfifo;
--rx_remain;
}
}
}
void __time_critical_func(pio_spi_write8_read8_blocking)(const pio_spi_inst_t *spi, uint8_t *src, uint8_t *dst,
size_t len) {
size_t tx_remain = len, rx_remain = len;
io_rw_8 *txfifo = (io_rw_8 *) &spi->pio->txf[spi->sm];
io_rw_8 *rxfifo = (io_rw_8 *) &spi->pio->rxf[spi->sm];
while (tx_remain || rx_remain) {
if (tx_remain && !pio_sm_is_tx_fifo_full(spi->pio, spi->sm)) {
*txfifo = *src++;
--tx_remain;
}
if (rx_remain && !pio_sm_is_rx_fifo_empty(spi->pio, spi->sm)) {
*dst++ = *rxfifo;
--rx_remain;
}
}
}

24
pio/spi/pio_spi.h Normal file
View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef _PIO_SPI_H
#define _PIO_SPI_H
#include "hardware/pio.h"
#include "spi.pio.h"
typedef struct pio_spi_inst {
PIO pio;
uint sm;
uint cs_pin;
} pio_spi_inst_t;
void pio_spi_write8_blocking(const pio_spi_inst_t *spi, const uint8_t *src, size_t len);
void pio_spi_read8_blocking(const pio_spi_inst_t *spi, uint8_t *dst, size_t len);
void pio_spi_write8_read8_blocking(const pio_spi_inst_t *spi, uint8_t *src, uint8_t *dst, size_t len);
#endif

168
pio/spi/spi.pio Normal file
View File

@@ -0,0 +1,168 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
; These programs implement full-duplex SPI, with a SCK period of 4 clock
; cycles. A different program is provided for each value of CPHA, and CPOL is
; achieved using the hardware GPIO inversion available in the IO controls.
;
; Transmit-only SPI can go twice as fast -- see the ST7789 example!
.program spi_cpha0
.side_set 1
; Pin assignments:
; - SCK is side-set pin 0
; - MOSI is OUT pin 0
; - MISO is IN pin 0
;
; Autopush and autopull must be enabled, and the serial frame size is set by
; configuring the push/pull threshold. Shift left/right is fine, but you must
; justify the data yourself. This is done most conveniently for frame sizes of
; 8 or 16 bits by using the narrow store replication and narrow load byte
; picking behaviour of RP2040's IO fabric.
; Clock phase = 0: data is captured on the leading edge of each SCK pulse, and
; transitions on the trailing edge, or some time before the first leading edge.
out pins, 1 side 0 [1] ; Stall here on empty (sideset proceeds even if
in pins, 1 side 1 [1] ; instruction stalls, so we stall with SCK low)
.program spi_cpha1
.side_set 1
; Clock phase = 1: data transitions on the leading edge of each SCK pulse, and
; is captured on the trailing edge.
out x, 1 side 0 ; Stall here on empty (keep SCK deasserted)
mov pins, x side 1 [1] ; Output data, assert SCK (mov pins uses OUT mapping)
in pins, 1 side 0 ; Input data, deassert SCK
% c-sdk {
#include "hardware/gpio.h"
static inline void pio_spi_init(PIO pio, uint sm, uint prog_offs, uint n_bits,
float clkdiv, bool cpha, bool cpol, uint pin_sck, uint pin_mosi, uint pin_miso) {
pio_sm_config c = cpha ? spi_cpha1_program_get_default_config(prog_offs) : spi_cpha0_program_get_default_config(prog_offs);
sm_config_set_out_pins(&c, pin_mosi, 1);
sm_config_set_in_pins(&c, pin_miso);
sm_config_set_sideset_pins(&c, pin_sck);
// Only support MSB-first in this example code (shift to left, auto push/pull, threshold=nbits)
sm_config_set_out_shift(&c, false, true, n_bits);
sm_config_set_in_shift(&c, false, true, n_bits);
sm_config_set_clkdiv(&c, clkdiv);
// MOSI, SCK output are low, MISO is input
pio_sm_set_pins_with_mask(pio, sm, 0, (1u << pin_sck) | (1u << pin_mosi));
pio_sm_set_pindirs_with_mask(pio, sm, (1u << pin_sck) | (1u << pin_mosi), (1u << pin_sck) | (1u << pin_mosi) | (1u << pin_miso));
pio_gpio_init(pio, pin_mosi);
pio_gpio_init(pio, pin_miso);
pio_gpio_init(pio, pin_sck);
// The pin muxes can be configured to invert the output (among other things
// and this is a cheesy way to get CPOL=1
gpio_set_outover(pin_sck, cpol ? GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL);
// SPI is synchronous, so bypass input synchroniser to reduce input delay.
hw_set_bits(&pio->input_sync_bypass, 1u << pin_miso);
pio_sm_init(pio, sm, prog_offs, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}
; SPI with Chip Select
; -----------------------------------------------------------------------------
;
; For your amusement, here are some SPI programs with an automatic chip select
; (asserted once data appears in TX FIFO, deasserts when FIFO bottoms out, has
; a nice front/back porch).
;
; The number of bits per FIFO entry is configured via the Y register
; and the autopush/pull threshold. From 2 to 32 bits.
;
; Pin assignments:
; - SCK is side-set bit 0
; - CSn is side-set bit 1
; - MOSI is OUT bit 0 (host-to-device)
; - MISO is IN bit 0 (device-to-host)
;
; This program only supports one chip select -- use GPIO if more are needed
;
; Provide a variation for each possibility of CPHA; for CPOL we can just
; invert SCK in the IO muxing controls (downstream from PIO)
; CPHA=0: data is captured on the leading edge of each SCK pulse (including
; the first pulse), and transitions on the trailing edge
.program spi_cpha0_cs
.side_set 2
.wrap_target
bitloop:
out pins, 1 side 0x0 [1]
in pins, 1 side 0x1
jmp x-- bitloop side 0x1
out pins, 1 side 0x0
mov x, y side 0x0 ; Reload bit counter from Y
in pins, 1 side 0x1
jmp !osre bitloop side 0x1 ; Fall-through if TXF empties
nop side 0x0 [1] ; CSn back porch
public entry_point: ; Must set X,Y to n-2 before starting!
pull ifempty side 0x2 [1] ; Block with CSn high (minimum 2 cycles)
.wrap ; Note ifempty to avoid time-of-check race
; CPHA=1: data transitions on the leading edge of each SCK pulse, and is
; captured on the trailing edge
.program spi_cpha1_cs
.side_set 2
.wrap_target
bitloop:
out pins, 1 side 0x1 [1]
in pins, 1 side 0x0
jmp x-- bitloop side 0x0
out pins, 1 side 0x1
mov x, y side 0x1
in pins, 1 side 0x0
jmp !osre bitloop side 0x0
public entry_point: ; Must set X,Y to n-2 before starting!
pull ifempty side 0x2 [1] ; Block with CSn high (minimum 2 cycles)
nop side 0x0 [1]; CSn front porch
.wrap
% c-sdk {
#include "hardware/gpio.h"
static inline void pio_spi_cs_init(PIO pio, uint sm, uint prog_offs, uint n_bits, float clkdiv, bool cpha, bool cpol,
uint pin_sck, uint pin_mosi, uint pin_miso) {
pio_sm_config c = cpha ? spi_cpha1_cs_program_get_default_config(prog_offs) : spi_cpha0_cs_program_get_default_config(prog_offs);
sm_config_set_out_pins(&c, pin_mosi, 1);
sm_config_set_in_pins(&c, pin_miso);
sm_config_set_sideset_pins(&c, pin_sck);
sm_config_set_out_shift(&c, false, true, n_bits);
sm_config_set_in_shift(&c, false, true, n_bits);
sm_config_set_clkdiv(&c, clkdiv);
pio_sm_set_pins_with_mask(pio, sm, (2u << pin_sck), (3u << pin_sck) | (1u << pin_mosi));
pio_sm_set_pindirs_with_mask(pio, sm, (3u << pin_sck) | (1u << pin_mosi), (3u << pin_sck) | (1u << pin_mosi) | (1u << pin_miso));
pio_gpio_init(pio, pin_mosi);
pio_gpio_init(pio, pin_miso);
pio_gpio_init(pio, pin_sck);
pio_gpio_init(pio, pin_sck + 1);
gpio_set_outover(pin_sck, cpol ? GPIO_OVERRIDE_INVERT : GPIO_OVERRIDE_NORMAL);
hw_set_bits(&pio->input_sync_bypass, 1u << pin_miso);
uint entry_point = prog_offs + (cpha ? spi_cpha1_cs_offset_entry_point : spi_cpha0_cs_offset_entry_point);
pio_sm_init(pio, sm, entry_point, &c);
pio_sm_exec(pio, sm, pio_encode_set(pio_x, n_bits - 2));
pio_sm_exec(pio, sm, pio_encode_set(pio_y, n_bits - 2));
pio_sm_set_enabled(pio, sm, true);
}
%}

155
pio/spi/spi_flash.c Normal file
View File

@@ -0,0 +1,155 @@
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include "pico/stdlib.h"
#include "pio_spi.h"
// This example uses PIO to erase, program and read back a SPI serial flash
// memory.
#define PIN_MISO 16
#define PIN_MOSI 17
#define PIN_SCK 18
#define PIN_CS 19
// ----------------------------------------------------------------------------
// Generic serial flash code
#define FLASH_PAGE_SIZE 256
#define FLASH_SECTOR_SIZE 4096
#define FLASH_CMD_PAGE_PROGRAM 0x02
#define FLASH_CMD_READ 0x03
#define FLASH_CMD_STATUS 0x05
#define FLASH_CMD_WRITE_EN 0x06
#define FLASH_CMD_SECTOR_ERASE 0x20
#define FLASH_STATUS_BUSY_MASK 0x01
void flash_read(const pio_spi_inst_t *spi, uint32_t addr, uint8_t *buf, size_t len) {
uint8_t cmd[4] = {
FLASH_CMD_READ,
addr >> 16,
addr >> 8,
addr
};
gpio_put(spi->cs_pin, 0);
pio_spi_write8_blocking(spi, cmd, 4);
pio_spi_read8_blocking(spi, buf, len);
gpio_put(spi->cs_pin, 1);
}
void flash_write_enable(const pio_spi_inst_t *spi) {
uint8_t cmd = FLASH_CMD_WRITE_EN;
gpio_put(spi->cs_pin, 0);
pio_spi_write8_blocking(spi, &cmd, 1);
gpio_put(spi->cs_pin, 1);
}
void flash_wait_done(const pio_spi_inst_t *spi) {
uint8_t status;
do {
gpio_put(spi->cs_pin, 0);
uint8_t cmd = FLASH_CMD_STATUS;
pio_spi_write8_blocking(spi, &cmd, 1);
pio_spi_read8_blocking(spi, &status, 1);
gpio_put(spi->cs_pin, 1);
} while (status & FLASH_STATUS_BUSY_MASK);
}
void flash_sector_erase(const pio_spi_inst_t *spi, uint32_t addr) {
uint8_t cmd[4] = {
FLASH_CMD_SECTOR_ERASE,
addr >> 16,
addr >> 8,
addr
};
flash_write_enable(spi);
gpio_put(spi->cs_pin, 0);
pio_spi_write8_blocking(spi, cmd, 4);
gpio_put(spi->cs_pin, 1);
flash_wait_done(spi);
}
void flash_page_program(const pio_spi_inst_t *spi, uint32_t addr, uint8_t data[]) {
flash_write_enable(spi);
uint8_t cmd[4] = {
FLASH_CMD_PAGE_PROGRAM,
addr >> 16,
addr >> 8,
addr
};
gpio_put(spi->cs_pin, 0);
pio_spi_write8_blocking(spi, cmd, 4);
pio_spi_write8_blocking(spi, data, FLASH_PAGE_SIZE);
gpio_put(spi->cs_pin, 1);
flash_wait_done(spi);
}
// ----------------------------------------------------------------------------
// Example program
void printbuf(const uint8_t buf[FLASH_PAGE_SIZE]) {
for (int i = 0; i < FLASH_PAGE_SIZE; ++i)
printf("%02x%c", buf[i], i % 16 == 15 ? '\n' : ' ');
}
int main() {
stdio_init_all();
puts("PIO SPI Example");
pio_spi_inst_t spi = {
.pio = pio0,
.sm = 0,
.cs_pin = PIN_CS
};
gpio_init(PIN_CS);
gpio_put(PIN_CS, 1);
gpio_set_dir(PIN_CS, GPIO_OUT);
uint offset = pio_add_program(spi.pio, &spi_cpha0_program);
printf("Loaded program at %d\n", offset);
pio_spi_init(spi.pio, spi.sm, offset,
8, // 8 bits per SPI frame
31.25f, // 1 MHz @ 125 clk_sys
false, // CPHA = 0
false, // CPOL = 0
PIN_SCK,
PIN_MOSI,
PIN_MISO
);
uint8_t page_buf[FLASH_PAGE_SIZE];
const uint32_t target_addr = 0;
flash_sector_erase(&spi, target_addr);
flash_read(&spi, target_addr, page_buf, FLASH_PAGE_SIZE);
puts("After erase:");
printbuf(page_buf);
for (int i = 0; i < FLASH_PAGE_SIZE; ++i)
page_buf[i] = i;
flash_page_program(&spi, target_addr, page_buf);
flash_read(&spi, target_addr, page_buf, FLASH_PAGE_SIZE);
puts("After program:");
printbuf(page_buf);
flash_sector_erase(&spi, target_addr);
flash_read(&spi, target_addr, page_buf, FLASH_PAGE_SIZE);
puts("Erase again:");
printbuf(page_buf);
return 0;
}

77
pio/spi/spi_loopback.c Normal file
View File

@@ -0,0 +1,77 @@
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdlib.h>
#include <stdio.h>
#include "pico/stdlib.h"
#include "pio_spi.h"
// This program instantiates a PIO SPI with each of the four possible
// CPOL/CPHA combinations, with the serial input and output pin mapped to the
// same GPIO. Any data written into the state machine's TX FIFO should then be
// serialised, deserialised, and reappear in the state machine's RX FIFO.
#define PIN_SCK 18
#define PIN_MOSI 16
#define PIN_MISO 16 // same as MOSI, so we get loopback
#define BUF_SIZE 20
void test(const pio_spi_inst_t *spi) {
static uint8_t txbuf[BUF_SIZE];
static uint8_t rxbuf[BUF_SIZE];
printf("TX:");
for (int i = 0; i < BUF_SIZE; ++i) {
txbuf[i] = rand() >> 16;
rxbuf[i] = 0;
printf(" %02x", (int) txbuf[i]);
}
printf("\n");
pio_spi_write8_read8_blocking(spi, txbuf, rxbuf, BUF_SIZE);
printf("RX:");
bool mismatch = false;
for (int i = 0; i < BUF_SIZE; ++i) {
printf(" %02x", (int) rxbuf[i]);
mismatch = mismatch || rxbuf[i] != txbuf[i];
}
if (mismatch)
printf("\nNope\n");
else
printf("\nOK\n");
}
int main() {
stdio_init_all();
pio_spi_inst_t spi = {
.pio = pio0,
.sm = 0
};
float clkdiv = 31.25f; // 1 MHz @ 125 clk_sys
uint cpha0_prog_offs = pio_add_program(spi.pio, &spi_cpha0_program);
uint cpha1_prog_offs = pio_add_program(spi.pio, &spi_cpha1_program);
for (int cpha = 0; cpha <= 1; ++cpha) {
for (int cpol = 0; cpol <= 1; ++cpol) {
printf("CPHA = %d, CPOL = %d\n", cpha, cpol);
pio_spi_init(spi.pio, spi.sm,
cpha ? cpha1_prog_offs : cpha0_prog_offs,
8, // 8 bits per SPI frame
clkdiv,
cpha,
cpol,
PIN_SCK,
PIN_MOSI,
PIN_MISO
);
test(&spi);
sleep_ms(10);
}
}
}

View File

@@ -0,0 +1,35 @@
add_executable(pio_squarewave)
pico_generate_pio_header(pio_squarewave ${CMAKE_CURRENT_LIST_DIR}/squarewave.pio)
pico_generate_pio_header(pio_squarewave ${CMAKE_CURRENT_LIST_DIR}/squarewave_wrap.pio)
pico_generate_pio_header(pio_squarewave ${CMAKE_CURRENT_LIST_DIR}/squarewave_fast.pio)
target_sources(pio_squarewave PRIVATE squarewave.c)
target_link_libraries(pio_squarewave PRIVATE pico_stdlib hardware_pio)
pico_add_extra_outputs(pio_squarewave)
# add url via pico_set_program_url
example_auto_set_url(pio_squarewave)
# generate .hex file and .pio.h file for the RP2040 datasheet (to make sure
# the datasheet always shows the output of the latest pioasm version)
add_custom_target(pio_squarewave_datasheet DEPENDS
Pioasm
${CMAKE_CURRENT_LIST_DIR}/generated/squarewave.hex
${CMAKE_CURRENT_LIST_DIR}/generated/squarewave.pio.h
${CMAKE_CURRENT_LIST_DIR}/generated/squarewave_wrap.pio.h
)
add_custom_command(OUTPUT ${CMAKE_CURRENT_LIST_DIR}/generated/squarewave.hex
DEPENDS ${CMAKE_CURRENT_LIST_DIR}/squarewave.pio
COMMAND Pioasm -o hex ${CMAKE_CURRENT_LIST_DIR}/squarewave.pio ${CMAKE_CURRENT_LIST_DIR}/generated/squarewave.hex
)
add_custom_command(OUTPUT ${CMAKE_CURRENT_LIST_DIR}/generated/squarewave.pio.h
DEPENDS ${CMAKE_CURRENT_LIST_DIR}/squarewave.pio
COMMAND Pioasm ${CMAKE_CURRENT_LIST_DIR}/squarewave.pio ${CMAKE_CURRENT_LIST_DIR}/generated/squarewave.pio.h
)
add_custom_command(OUTPUT ${CMAKE_CURRENT_LIST_DIR}/generated/squarewave_wrap.pio.h
DEPENDS ${CMAKE_CURRENT_LIST_DIR}/squarewave_wrap.pio
COMMAND Pioasm ${CMAKE_CURRENT_LIST_DIR}/squarewave_wrap.pio ${CMAKE_CURRENT_LIST_DIR}/generated/squarewave_wrap.pio.h
)
add_dependencies(pio_squarewave pio_squarewave_datasheet)

View File

@@ -0,0 +1,4 @@
e081
e101
e000
0001

View File

@@ -0,0 +1,38 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// ---------- //
// squarewave //
// ---------- //
#define squarewave_wrap_target 0
#define squarewave_wrap 3
static const uint16_t squarewave_program_instructions[] = {
// .wrap_target
0xe081, // 0: set pindirs, 1
0xe101, // 1: set pins, 1 [1]
0xe000, // 2: set pins, 0
0x0001, // 3: jmp 1
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program squarewave_program = {
.instructions = squarewave_program_instructions,
.length = 4,
.origin = -1,
};
static inline pio_sm_config squarewave_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + squarewave_wrap_target, offset + squarewave_wrap);
return c;
}
#endif

View File

@@ -0,0 +1,37 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// --------------- //
// squarewave_wrap //
// --------------- //
#define squarewave_wrap_wrap_target 1
#define squarewave_wrap_wrap 2
static const uint16_t squarewave_wrap_program_instructions[] = {
0xe081, // 0: set pindirs, 1
// .wrap_target
0xe101, // 1: set pins, 1 [1]
0xe100, // 2: set pins, 0 [1]
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program squarewave_wrap_program = {
.instructions = squarewave_wrap_program_instructions,
.length = 3,
.origin = -1,
};
static inline pio_sm_config squarewave_wrap_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + squarewave_wrap_wrap_target, offset + squarewave_wrap_wrap);
return c;
}
#endif

View File

@@ -0,0 +1,72 @@
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
// Output a 12.5 MHz square wave (if system clock frequency is 125 MHz).
//
// Note this program is accessing the PIO registers directly, for illustrative
// purposes. We pull this program into the datasheet so we can talk a little
// about PIO's hardware register interface. The `hardware_pio` SDK library
// provides simpler or better interfaces for all of these operations.
//
// _*This is not best practice! I don't want to see you copy/pasting this*_
//
// For a minimal example of loading and running a program using the SDK
// functions (which is what you generally want to do) have a look at
// `hello_pio` instead. That example is also the subject of a tutorial in the
// SDK book, which walks you through building your first PIO program.
#include "pico/stdlib.h"
#include "hardware/pio.h"
// Our assembled program:
#include "squarewave.pio.h"
int main() {
// Pick one PIO instance arbitrarily. We're also arbitrarily picking state
// machine 0 on this PIO instance (the state machines are numbered 0 to 3
// inclusive).
PIO pio = pio0;
/// \tag::load_program[]
// Load the assembled program directly into the PIO's instruction memory.
// Each PIO instance has a 32-slot instruction memory, which all 4 state
// machines can see. The system has write-only access.
for (int i = 0; i < count_of(squarewave_program_instructions); ++i)
pio->instr_mem[i] = squarewave_program_instructions[i];
/// \end::load_program[]
/// \tag::clock_divider[]
// Configure state machine 0 to run at sysclk/2.5. The state machines can
// run as fast as one instruction per clock cycle, but we can scale their
// speed down uniformly to meet some precise frequency target, e.g. for a
// UART baud rate. This register has 16 integer divisor bits and 8
// fractional divisor bits.
pio->sm[0].clkdiv = (uint32_t) (2.5f * (1 << 16));
/// \end::clock_divider[]
/// \tag::setup_pins[]
// There are five pin mapping groups (out, in, set, side-set, jmp pin)
// which are used by different instructions or in different circumstances.
// Here we're just using SET instructions. Configure state machine 0 SETs
// to affect GPIO 0 only; then configure GPIO0 to be controlled by PIO0,
// as opposed to e.g. the processors.
pio->sm[0].pinctrl =
(1 << PIO_SM0_PINCTRL_SET_COUNT_LSB) |
(0 << PIO_SM0_PINCTRL_SET_BASE_LSB);
gpio_set_function(0, GPIO_FUNC_PIO0);
/// \end::setup_pins[]
/// \tag::start_sm[]
// Set the state machine running. The PIO CTRL register is global within a
// PIO instance, so you can start/stop multiple state machines
// simultaneously. We're using the register's hardware atomic set alias to
// make one bit high without doing a read-modify-write on the register.
hw_set_bits(&pio->ctrl, 1 << (PIO_CTRL_SM_ENABLE_LSB + 0));
/// \end::start_sm[]
return 0;
}

View File

@@ -0,0 +1,13 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program squarewave
set pindirs, 1 ; Set pin to output
again:
set pins, 1 [1] ; Drive pin high and then delay for one cycle
set pins, 0 ; Drive pin low
jmp again ; Set PC to label `again`

View File

@@ -0,0 +1,19 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
; Note that if you modify squarewave.c to include this program, you'll also
; need to set the wrap registers yourself. This would be handled for you by
; squarewave_program_get_default_config().
.program squarewave_fast
; Like squarewave_wrap, but remove the delay cycles so we can run twice as fast.
set pindirs, 1 ; Set pin to output
.wrap_target
set pins, 1 ; Drive pin high
set pins, 0 ; Drive pin low
.wrap

View File

@@ -0,0 +1,19 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
; Note that if you modify squarewave.c to include this program, you'll also
; need to set the wrap registers yourself. This would be handled for you by
; squarewave_program_get_default_config().
.program squarewave_wrap
; Like squarewave, but use the state machine's .wrap hardware instead of an
; explicit jmp. This is a free (0-cycle) unconditional jump.
set pindirs, 1 ; Set pin to output
.wrap_target
set pins, 1 [1] ; Drive pin high and then delay for one cycle
set pins, 0 [1] ; Drive pin low and then delay for one cycle
.wrap

View File

@@ -0,0 +1,11 @@
add_executable(pio_st7789_lcd)
pico_generate_pio_header(pio_st7789_lcd ${CMAKE_CURRENT_LIST_DIR}/st7789_lcd.pio)
target_sources(pio_st7789_lcd PRIVATE st7789_lcd.c)
target_link_libraries(pio_st7789_lcd PRIVATE pico_stdlib hardware_pio hardware_interp)
pico_add_extra_outputs(pio_st7789_lcd)
# add url via pico_set_program_url
example_auto_set_url(pio_st7789_lcd)

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

File diff suppressed because it is too large Load Diff

148
pio/st7789_lcd/st7789_lcd.c Normal file
View File

@@ -0,0 +1,148 @@
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include <math.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "hardware/gpio.h"
#include "hardware/interp.h"
#include "st7789_lcd.pio.h"
#include "raspberry_256x256_rgb565.h"
#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 240
#define IMAGE_SIZE 256
#define LOG_IMAGE_SIZE 8
#define PIN_DIN 0
#define PIN_CLK 1
#define PIN_CS 2
#define PIN_DC 3
#define PIN_RESET 4
#define PIN_BL 5
#define SERIAL_CLK_DIV 1.f
// Format: cmd length (including cmd byte), post delay in units of 5 ms, then cmd payload
// Note the delays have been shortened a little
static const uint8_t st7789_init_seq[] = {
1, 20, 0x01, // Software reset
1, 10, 0x11, // Exit sleep mode
2, 2, 0x3a, 0x55, // Set colour mode to 16 bit
2, 0, 0x36, 0x00, // Set MADCTL: row then column, refresh is bottom to top ????
5, 0, 0x2a, 0x00, 0x00, 0x00, 0xf0, // CASET: column addresses from 0 to 240 (f0)
5, 0, 0x2b, 0x00, 0x00, 0x00, 0xf0, // RASET: row addresses from 0 to 240 (f0)
1, 2, 0x21, // Inversion on, then 10 ms delay (supposedly a hack?)
1, 2, 0x13, // Normal display on, then 10 ms delay
1, 2, 0x29, // Main screen turn on, then wait 500 ms
0 // Terminate list
};
static inline void lcd_set_dc_cs(bool dc, bool cs) {
sleep_us(1);
gpio_put_masked((1u << PIN_DC) | (1u << PIN_CS), !!dc << PIN_DC | !!cs << PIN_CS);
sleep_us(1);
}
static inline void lcd_write_cmd(PIO pio, uint sm, const uint8_t *cmd, size_t count) {
st7789_lcd_wait_idle(pio, sm);
lcd_set_dc_cs(0, 0);
st7789_lcd_put(pio, sm, *cmd++);
if (count >= 2) {
st7789_lcd_wait_idle(pio, sm);
lcd_set_dc_cs(1, 0);
for (size_t i = 0; i < count - 1; ++i)
st7789_lcd_put(pio, sm, *cmd++);
}
st7789_lcd_wait_idle(pio, sm);
lcd_set_dc_cs(1, 1);
}
static inline void lcd_init(PIO pio, uint sm, const uint8_t *init_seq) {
const uint8_t *cmd = init_seq;
while (*cmd) {
lcd_write_cmd(pio, sm, cmd + 2, *cmd);
sleep_ms(*(cmd + 1) * 5);
cmd += *cmd + 2;
}
}
static inline void st7789_start_pixels(PIO pio, uint sm) {
uint8_t cmd = 0x2c; // RAMWR
lcd_write_cmd(pio, sm, &cmd, 1);
lcd_set_dc_cs(1, 0);
}
int main() {
stdio_init_all();
PIO pio = pio0;
uint sm = 0;
uint offset = pio_add_program(pio, &st7789_lcd_program);
st7789_lcd_program_init(pio, sm, offset, PIN_DIN, PIN_CLK, SERIAL_CLK_DIV);
gpio_init(PIN_CS);
gpio_init(PIN_DC);
gpio_init(PIN_RESET);
gpio_init(PIN_BL);
gpio_set_dir(PIN_CS, GPIO_OUT);
gpio_set_dir(PIN_DC, GPIO_OUT);
gpio_set_dir(PIN_RESET, GPIO_OUT);
gpio_set_dir(PIN_BL, GPIO_OUT);
gpio_put(PIN_CS, 1);
gpio_put(PIN_RESET, 1);
lcd_init(pio, sm, st7789_init_seq);
gpio_put(PIN_BL, 1);
// Other SDKs: static image on screen, lame, boring
// Pico SDK: spinning image on screen, bold, exciting
// Lane 0 will be u coords (bits 8:1 of addr offset), lane 1 will be v
// coords (bits 16:9 of addr offset), and we'll represent coords with
// 16.16 fixed point. ACCUM0,1 will contain current coord, BASE0/1 will
// contain increment vector, and BASE2 will contain image base pointer
#define UNIT_LSB 16
interp_config lane0_cfg = interp_default_config();
interp_config_set_shift(&lane0_cfg, UNIT_LSB - 1); // -1 because 2 bytes per pixel
interp_config_set_mask(&lane0_cfg, 1, 1 + (LOG_IMAGE_SIZE - 1));
interp_config_set_add_raw(&lane0_cfg, true); // Add full accumulator to base with each POP
interp_config lane1_cfg = interp_default_config();
interp_config_set_shift(&lane1_cfg, UNIT_LSB - (1 + LOG_IMAGE_SIZE));
interp_config_set_mask(&lane1_cfg, 1 + LOG_IMAGE_SIZE, 1 + (2 * LOG_IMAGE_SIZE - 1));
interp_config_set_add_raw(&lane1_cfg, true);
interp_set_config(interp0, 0, &lane0_cfg);
interp_set_config(interp0, 1, &lane1_cfg);
interp0->base[2] = (uint32_t) raspberry_256x256;
float theta = 0.f;
float theta_max = 2.f * (float) M_PI;
while (1) {
theta += 0.02f;
if (theta > theta_max)
theta -= theta_max;
int32_t rotate[4] = {
cosf(theta) * (1 << UNIT_LSB), -sinf(theta) * (1 << UNIT_LSB),
sinf(theta) * (1 << UNIT_LSB), cosf(theta) * (1 << UNIT_LSB)
};
interp0->base[0] = rotate[0];
interp0->base[1] = rotate[2];
st7789_start_pixels(pio, sm);
for (int y = 0; y < SCREEN_HEIGHT; ++y) {
interp0->accum[0] = rotate[1] * y;
interp0->accum[1] = rotate[3] * y;
for (int x = 0; x < SCREEN_WIDTH; ++x) {
uint16_t colour = *(uint16_t *) (interp0->pop[2]);
st7789_lcd_put(pio, sm, colour >> 8);
st7789_lcd_put(pio, sm, colour & 0xff);
}
}
}
}

View File

@@ -0,0 +1,57 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program st7789_lcd
.side_set 1
; This is just a simple clocked serial TX. At 125 MHz system clock we can
; sustain up to 62.5 Mbps.
; Data on OUT pin 0
; Clock on side-set pin 0
.wrap_target
out pins, 1 side 0 ; stall here if no data (clock low)
nop side 1
.wrap
% c-sdk {
// For optimal use of DMA bandwidth we would use an autopull threshold of 32,
// but we are using a threshold of 8 here (consume 1 byte from each FIFO entry
// and discard the remainder) to make things easier for software on the other side
static inline void st7789_lcd_program_init(PIO pio, uint sm, uint offset, uint data_pin, uint clk_pin, float clk_div) {
pio_gpio_init(pio, data_pin);
pio_gpio_init(pio, clk_pin);
pio_sm_set_consecutive_pindirs(pio, sm, data_pin, 1, true);
pio_sm_set_consecutive_pindirs(pio, sm, clk_pin, 1, true);
pio_sm_config c = st7789_lcd_program_get_default_config(offset);
sm_config_set_sideset_pins(&c, clk_pin);
sm_config_set_out_pins(&c, data_pin, 1);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
sm_config_set_clkdiv(&c, clk_div);
sm_config_set_out_shift(&c, false, true, 8);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
// Making use of the narrow store replication behaviour on RP2040 to get the
// data left-justified (as we are using shift-to-left to get MSB-first serial)
static inline void st7789_lcd_put(PIO pio, uint sm, uint8_t x) {
while (pio_sm_is_tx_fifo_full(pio, sm))
;
*(volatile uint8_t*)&pio->txf[sm] = x;
}
// SM is done when it stalls on an empty FIFO
static inline void st7789_lcd_wait_idle(PIO pio, uint sm) {
uint32_t sm_stall_mask = 1u << (sm + PIO_FDEBUG_TXSTALL_LSB);
pio->fdebug = sm_stall_mask;
while (!(pio->fdebug & sm_stall_mask))
;
}
%}

View File

@@ -0,0 +1,16 @@
add_executable(pio_uart_rx)
pico_generate_pio_header(pio_uart_rx ${CMAKE_CURRENT_LIST_DIR}/uart_rx.pio)
target_sources(pio_uart_rx PRIVATE uart_rx.c)
target_link_libraries(pio_uart_rx PRIVATE
pico_stdlib
pico_multicore
hardware_pio
)
pico_add_extra_outputs(pio_uart_rx)
# add url via pico_set_program_url
example_auto_set_url(pio_uart_rx)

60
pio/uart_rx/uart_rx.c Normal file
View File

@@ -0,0 +1,60 @@
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/multicore.h"
#include "hardware/pio.h"
#include "hardware/uart.h"
#include "uart_rx.pio.h"
// This program
// - Uses UART1 (the spare UART, by default) to transmit some text
// - Uses a PIO state machine to receive that text
// - Prints out the received text to the default console (UART0)
// This might require some reconfiguration on boards where UART1 is the
// default UART.
#define SERIAL_BAUD PICO_DEFAULT_UART_BAUD_RATE
#define HARD_UART_INST uart1
// You'll need a wire from GPIO4 -> GPIO3
#define HARD_UART_TX_PIN 4
#define PIO_RX_PIN 3
// Ask core 1 to print a string, to make things easier on core 0
void core1_main() {
const char *s = (const char *) multicore_fifo_pop_blocking();
uart_puts(HARD_UART_INST, s);
}
int main() {
// Console output (also a UART, yes it's confusing)
setup_default_uart();
printf("Starting PIO UART RX example\n");
// Set up the hard UART we're going to use to print characters
uart_init(HARD_UART_INST, SERIAL_BAUD);
gpio_set_function(HARD_UART_TX_PIN, GPIO_FUNC_UART);
// Set up the state machine we're going to use to receive them.
PIO pio = pio0;
uint sm = 0;
uint offset = pio_add_program(pio, &uart_rx_program);
uart_rx_program_init(pio, sm, offset, PIO_RX_PIN, SERIAL_BAUD);
// Tell core 1 to print some text to uart1 as fast as it can
multicore_launch_core1(core1_main);
const char *text = "Hello, world from PIO! (Plus 2 UARTs and 2 cores, for complex reasons)\n";
multicore_fifo_push_blocking((uint32_t) text);
// Echo characters received from PIO to the console
while (true) {
char c = uart_rx_program_getc(pio, sm);
putchar(c);
}
}

94
pio/uart_rx/uart_rx.pio Normal file
View File

@@ -0,0 +1,94 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program uart_rx_mini
; Minimum viable 8n1 UART receiver. Wait for the start bit, then sample 8 bits
; with the correct timing.
; IN pin 0 is mapped to the GPIO used as UART RX.
; Autopush must be enabled, with a threshold of 8.
wait 0 pin 0 ; Wait for start bit
set x, 7 [10] ; Preload bit counter, delay until eye of first data bit
bitloop: ; Loop 8 times
in pins, 1 ; Sample data
jmp x-- bitloop [6] ; Each iteration is 8 cycles
% c-sdk {
#include "hardware/clocks.h"
#include "hardware/gpio.h"
static inline void uart_rx_mini_program_init(PIO pio, uint sm, uint offset, uint pin, uint baud) {
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false);
pio_gpio_init(pio, pin);
gpio_pull_up(pin);
pio_sm_config c = uart_rx_mini_program_get_default_config(offset);
sm_config_set_in_pins(&c, pin); // for WAIT, IN
// Shift to right, autopush enabled
sm_config_set_in_shift(&c, true, true, 8);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
// SM transmits 1 bit per 8 execution cycles.
float div = (float)clock_get_hz(clk_sys) / (8 * baud);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}
.program uart_rx
; Slightly more fleshed-out 8n1 UART receiver which handles framing errors and
; break conditions more gracefully.
; IN pin 0 and JMP pin are both mapped to the GPIO used as UART RX.
start:
wait 0 pin 0 ; Stall until start bit is asserted
set x, 7 [10] ; Preload bit counter, then delay until halfway through
bitloop: ; the first data bit (12 cycles incl wait, set).
in pins, 1 ; Shift data bit into ISR
jmp x-- bitloop [6] ; Loop 8 times, each loop iteration is 8 cycles
jmp pin good_stop ; Check stop bit (should be high)
irq 4 rel ; Either a framing error or a break. Set a sticky flag,
wait 1 pin 0 ; and wait for line to return to idle state.
jmp start ; Don't push data if we didn't see good framing.
good_stop: ; No delay before returning to start; a little slack is
push ; important in case the TX clock is slightly too fast.
% c-sdk {
static inline void uart_rx_program_init(PIO pio, uint sm, uint offset, uint pin, uint baud) {
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, false);
pio_gpio_init(pio, pin);
gpio_pull_up(pin);
pio_sm_config c = uart_rx_program_get_default_config(offset);
sm_config_set_in_pins(&c, pin); // for WAIT, IN
sm_config_set_jmp_pin(&c, pin); // for JMP
// Shift to right, autopull disabled
sm_config_set_in_shift(&c, true, false, 32);
// Deeper FIFO as we're not doing any TX
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
// SM transmits 1 bit per 8 execution cycles.
float div = (float)clock_get_hz(clk_sys) / (8 * baud);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
static inline char uart_rx_program_getc(PIO pio, uint sm) {
// 8-bit read from the uppermost byte of the FIFO, as data is left-justified
io_rw_8 *rxfifo_shift = (io_rw_8*)&pio->rxf[sm] + 3;
while (pio_sm_is_rx_fifo_empty(pio, sm))
tight_loop_contents();
return (char)*rxfifo_shift;
}
%}

View File

@@ -0,0 +1,11 @@
add_executable(pio_uart_tx)
pico_generate_pio_header(pio_uart_tx ${CMAKE_CURRENT_LIST_DIR}/uart_tx.pio)
target_sources(pio_uart_tx PRIVATE uart_tx.c)
target_link_libraries(pio_uart_tx PRIVATE pico_stdlib hardware_pio)
pico_add_extra_outputs(pio_uart_tx)
# add url via pico_set_program_url
example_auto_set_url(pio_uart_tx)

27
pio/uart_tx/uart_tx.c Normal file
View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "uart_tx.pio.h"
int main() {
// We're going to use PIO to print "Hello, world!" on the same GPIO which we
// normally attach UART0 to.
const uint PIN_TX = 0;
// This is the same as the default UART baud rate on Pico
const uint SERIAL_BAUD = 115200;
PIO pio = pio0;
uint sm = 0;
uint offset = pio_add_program(pio, &uart_tx_program);
uart_tx_program_init(pio, sm, offset, PIN_TX, SERIAL_BAUD);
while (true) {
uart_tx_program_puts(pio, sm, "Hello, world! (from PIO!)\n");
sleep_ms(1000);
}
}

61
pio/uart_tx/uart_tx.pio Normal file
View File

@@ -0,0 +1,61 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program uart_tx
.side_set 1 opt
; An 8n1 UART transmit program.
; OUT pin 0 and side-set pin 0 are both mapped to UART TX pin.
pull side 1 [7] ; Assert stop bit, or stall with line in idle state
set x, 7 side 0 [7] ; Preload bit counter, assert start bit for 8 clocks
bitloop: ; This loop will run 8 times (8n1 UART)
out pins, 1 ; Shift 1 bit from OSR to the first OUT pin
jmp x-- bitloop [6] ; Each loop iteration is 8 cycles.
% c-sdk {
#include "hardware/clocks.h"
static inline void uart_tx_program_init(PIO pio, uint sm, uint offset, uint pin_tx, uint baud) {
// Tell PIO to initially drive output-high on the selected pin, then map PIO
// onto that pin with the IO muxes.
pio_sm_set_pins_with_mask(pio, sm, 1u << pin_tx, 1u << pin_tx);
pio_sm_set_pindirs_with_mask(pio, sm, 1u << pin_tx, 1u << pin_tx);
pio_gpio_init(pio, pin_tx);
pio_sm_config c = uart_tx_program_get_default_config(offset);
// OUT shifts to right, no autopull
sm_config_set_out_shift(&c, true, false, 32);
// We are mapping both OUT and side-set to the same pin, because sometimes
// we need to assert user data onto the pin (with OUT) and sometimes
// assert constant values (start/stop bit)
sm_config_set_out_pins(&c, pin_tx, 1);
sm_config_set_sideset_pins(&c, pin_tx);
// We only need TX, so get an 8-deep FIFO!
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
// SM transmits 1 bit per 8 execution cycles.
float div = (float)clock_get_hz(clk_sys) / (8 * baud);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
static inline void uart_tx_program_putc(PIO pio, uint sm, char c) {
pio_sm_put_blocking(pio, sm, (uint32_t)c);
}
static inline void uart_tx_program_puts(PIO pio, uint sm, const char *s) {
while (*s)
uart_tx_program_putc(pio, sm, *s++);
}
%}

35
pio/ws2812/CMakeLists.txt Normal file
View File

@@ -0,0 +1,35 @@
add_executable(pio_ws2812)
# generate the header file into the source tree as it is included in the RP2040 datasheet
pico_generate_pio_header(pio_ws2812 ${CMAKE_CURRENT_LIST_DIR}/ws2812.pio OUTPUT_DIR ${CMAKE_CURRENT_LIST_DIR}/generated)
target_sources(pio_ws2812 PRIVATE ws2812.c)
target_link_libraries(pio_ws2812 PRIVATE pico_stdlib hardware_pio)
pico_add_extra_outputs(pio_ws2812)
# add url via pico_set_program_url
example_auto_set_url(pio_ws2812)
add_executable(pio_ws2812_parallel)
pico_generate_pio_header(pio_ws2812_parallel ${CMAKE_CURRENT_LIST_DIR}/ws2812.pio OUTPUT_DIR ${CMAKE_CURRENT_LIST_DIR}/generated)
target_sources(pio_ws2812_parallel PRIVATE ws2812_parallel.c)
target_compile_definitions(pio_ws2812_parallel PRIVATE
PIN_DBG1=3)
target_link_libraries(pio_ws2812_parallel PRIVATE pico_stdlib hardware_pio hardware_dma)
pico_add_extra_outputs(pio_ws2812_parallel)
# add url via pico_set_program_url
example_auto_set_url(pio_ws2812_parallel)
# Additionally generate python and hex pioasm outputs for inclusion in the RP2040 datasheet
add_custom_target(pio_ws2812_datasheet DEPENDS Pioasm ${CMAKE_CURRENT_LIST_DIR}/generated/ws2812.py)
add_custom_command(OUTPUT ${CMAKE_CURRENT_LIST_DIR}/generated/ws2812.py
DEPENDS ${CMAKE_CURRENT_LIST_DIR}/ws2812.pio
COMMAND Pioasm -o python ${CMAKE_CURRENT_LIST_DIR}/ws2812.pio ${CMAKE_CURRENT_LIST_DIR}/generated/ws2812.py
)
add_dependencies(pio_ws2812 pio_ws2812_datasheet)

View File

@@ -0,0 +1,112 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// ------ //
// ws2812 //
// ------ //
#define ws2812_wrap_target 0
#define ws2812_wrap 3
#define ws2812_T1 2
#define ws2812_T2 5
#define ws2812_T3 3
static const uint16_t ws2812_program_instructions[] = {
// .wrap_target
0x6221, // 0: out x, 1 side 0 [2]
0x1123, // 1: jmp !x, 3 side 1 [1]
0x1400, // 2: jmp 0 side 1 [4]
0xa442, // 3: nop side 0 [4]
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program ws2812_program = {
.instructions = ws2812_program_instructions,
.length = 4,
.origin = -1,
};
static inline pio_sm_config ws2812_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + ws2812_wrap_target, offset + ws2812_wrap);
sm_config_set_sideset(&c, 1, false, false);
return c;
}
#include "hardware/clocks.h"
static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin, float freq, bool rgbw) {
pio_gpio_init(pio, pin);
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
pio_sm_config c = ws2812_program_get_default_config(offset);
sm_config_set_sideset_pins(&c, pin);
sm_config_set_out_shift(&c, false, true, rgbw ? 32 : 24);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
int cycles_per_bit = ws2812_T1 + ws2812_T2 + ws2812_T3;
float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
#endif
// --------------- //
// ws2812_parallel //
// --------------- //
#define ws2812_parallel_wrap_target 0
#define ws2812_parallel_wrap 3
#define ws2812_parallel_T1 2
#define ws2812_parallel_T2 5
#define ws2812_parallel_T3 3
static const uint16_t ws2812_parallel_program_instructions[] = {
// .wrap_target
0x6020, // 0: out x, 32
0xa10b, // 1: mov pins, !null [1]
0xa401, // 2: mov pins, x [4]
0xa103, // 3: mov pins, null [1]
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program ws2812_parallel_program = {
.instructions = ws2812_parallel_program_instructions,
.length = 4,
.origin = -1,
};
static inline pio_sm_config ws2812_parallel_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + ws2812_parallel_wrap_target, offset + ws2812_parallel_wrap);
return c;
}
#include "hardware/clocks.h"
static inline void ws2812_parallel_program_init(PIO pio, uint sm, uint offset, uint pin_base, uint pin_count, float freq) {
for(uint i=pin_base; i<pin_base+pin_count; i++) {
pio_gpio_init(pio, i);
}
pio_sm_set_consecutive_pindirs(pio, sm, pin_base, pin_count, true);
pio_sm_config c = ws2812_parallel_program_get_default_config(offset);
sm_config_set_out_shift(&c, true, true, 32);
sm_config_set_out_pins(&c, pin_base, pin_count);
sm_config_set_set_pins(&c, pin_base, pin_count);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
int cycles_per_bit = ws2812_parallel_T1 + ws2812_parallel_T2 + ws2812_parallel_T3;
float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
#endif

View File

@@ -0,0 +1,46 @@
# -------------------------------------------------- #
# This file is autogenerated by pioasm; do not edit! #
# -------------------------------------------------- #
import rp2
from machine import Pin
# ------ #
# ws2812 #
# ------ #
ws2812_T1 = 2
ws2812_T2 = 5
ws2812_T3 = 3
@rp2.asm_pio(sideset_init=pico.PIO.OUT_HIGH, out_init=pico.PIO.OUT_HIGH, out_shiftdir=1)
def ws2812():
wrap_target()
label("0")
out(x, 1) .side(0) [2] # 0
jmp(not_x, "3") .side(1) [1] # 1
jmp("0") .side(1) [4] # 2
label("3")
nop() .side(0) [4] # 3
wrap()
# --------------- #
# ws2812_parallel #
# --------------- #
ws2812_parallel_T1 = 2
ws2812_parallel_T2 = 5
ws2812_parallel_T3 = 3
@rp2.asm_pio()
def ws2812_parallel():
wrap_target()
out(x, 32) # 0
mov(pins, not null) [1] # 1
mov(pins, x) [4] # 2
mov(pins, null) [1] # 3
wrap()

100
pio/ws2812/ws2812.c Normal file
View File

@@ -0,0 +1,100 @@
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include <stdlib.h>
#include "pico/stdlib.h"
#include "hardware/pio.h"
#include "hardware/clocks.h"
#include "ws2812.pio.h"
static inline void put_pixel(uint32_t pixel_grb) {
pio_sm_put_blocking(pio0, 0, pixel_grb << 8u);
}
static inline uint32_t urgb_u32(uint8_t r, uint8_t g, uint8_t b) {
return
((uint32_t) (r) << 8) |
((uint32_t) (g) << 16) |
(uint32_t) (b);
}
void pattern_snakes(uint len, uint t) {
for (uint i = 0; i < len; ++i) {
uint x = (i + (t >> 1)) % 64;
if (x < 10)
put_pixel(urgb_u32(0xff, 0, 0));
else if (x >= 15 && x < 25)
put_pixel(urgb_u32(0, 0xff, 0));
else if (x >= 30 && x < 40)
put_pixel(urgb_u32(0, 0, 0xff));
else
put_pixel(0);
}
}
void pattern_random(uint len, uint t) {
if (t % 8)
return;
for (int i = 0; i < len; ++i)
put_pixel(rand());
}
void pattern_sparkle(uint len, uint t) {
if (t % 8)
return;
for (int i = 0; i < len; ++i)
put_pixel(rand() % 16 ? 0 : 0xffffffff);
}
void pattern_greys(uint len, uint t) {
int max = 100; // let's not draw too much current!
t %= max;
for (int i = 0; i < len; ++i) {
put_pixel(t * 0x10101);
if (++t >= max) t = 0;
}
}
typedef void (*pattern)(uint len, uint t);
const struct {
pattern pat;
const char *name;
} pattern_table[] = {
{pattern_snakes, "Snakes!"},
{pattern_random, "Random data"},
{pattern_sparkle, "Sparkles"},
{pattern_greys, "Greys"},
};
const int PIN_TX = 0;
int main() {
//set_sys_clock_48();
stdio_init_all();
puts("WS2812 Smoke Test");
// todo get free sm
PIO pio = pio0;
int sm = 0;
uint offset = pio_add_program(pio, &ws2812_program);
ws2812_program_init(pio, sm, offset, PIN_TX, 800000, true);
int t = 0;
while (1) {
int pat = rand() % count_of(pattern_table);
int dir = (rand() >> 30) & 1 ? 1 : -1;
puts(pattern_table[pat].name);
puts(dir == 1 ? "(forward)" : "(backward)");
for (int i = 0; i < 1000; ++i) {
pattern_table[pat].pat(150, t);
sleep_ms(10);
t += dir;
}
}
}

85
pio/ws2812/ws2812.pio Normal file
View File

@@ -0,0 +1,85 @@
;
; Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
;
; SPDX-License-Identifier: BSD-3-Clause
;
.program ws2812
.side_set 1
.define public T1 2
.define public T2 5
.define public T3 3
.lang_opt python sideset_init = pico.PIO.OUT_HIGH
.lang_opt python out_init = pico.PIO.OUT_HIGH
.lang_opt python out_shiftdir = 1
.wrap_target
bitloop:
out x, 1 side 0 [T3 - 1] ; Side-set still takes place when instruction stalls
jmp !x do_zero side 1 [T1 - 1] ; Branch on the bit we shifted out. Positive pulse
do_one:
jmp bitloop side 1 [T2 - 1] ; Continue driving high, for a long pulse
do_zero:
nop side 0 [T2 - 1] ; Or drive low, for a short pulse
.wrap
% c-sdk {
#include "hardware/clocks.h"
static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin, float freq, bool rgbw) {
pio_gpio_init(pio, pin);
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
pio_sm_config c = ws2812_program_get_default_config(offset);
sm_config_set_sideset_pins(&c, pin);
sm_config_set_out_shift(&c, false, true, rgbw ? 32 : 24);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
int cycles_per_bit = ws2812_T1 + ws2812_T2 + ws2812_T3;
float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}
.program ws2812_parallel
.define public T1 2
.define public T2 5
.define public T3 3
.wrap_target
out x, 32
mov pins, !null [T1-1]
mov pins, x [T2-1]
mov pins, null [T3-2]
.wrap
% c-sdk {
#include "hardware/clocks.h"
static inline void ws2812_parallel_program_init(PIO pio, uint sm, uint offset, uint pin_base, uint pin_count, float freq) {
for(uint i=pin_base; i<pin_base+pin_count; i++) {
pio_gpio_init(pio, i);
}
pio_sm_set_consecutive_pindirs(pio, sm, pin_base, pin_count, true);
pio_sm_config c = ws2812_parallel_program_get_default_config(offset);
sm_config_set_out_shift(&c, true, true, 32);
sm_config_set_out_pins(&c, pin_base, pin_count);
sm_config_set_set_pins(&c, pin_base, pin_count);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
int cycles_per_bit = ws2812_parallel_T1 + ws2812_parallel_T2 + ws2812_parallel_T3;
float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}

View File

@@ -0,0 +1,341 @@
/**
* Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pico/stdlib.h"
#include "pico/sem.h"
#include "hardware/pio.h"
#include "hardware/dma.h"
#include "hardware/irq.h"
#include "ws2812.pio.h"
#define FRAC_BITS 4
#define PIN_TX 0
CU_REGISTER_DEBUG_PINS(timing)
CU_SELECT_DEBUG_PINS(timing)
// horrible temporary hack to avoid changing pattern code
static uint8_t *current_string_out;
static bool current_string_4color;
static inline void put_pixel(uint32_t pixel_grb) {
*current_string_out++ = pixel_grb & 0xffu;
*current_string_out++ = (pixel_grb >> 8u) & 0xffu;
*current_string_out++ = (pixel_grb >> 16u) & 0xffu;
if (current_string_4color) {
*current_string_out++ = 0; // todo adjust?
}
}
static inline uint32_t urgb_u32(uint8_t r, uint8_t g, uint8_t b) {
return
((uint32_t) (r) << 8) |
((uint32_t) (g) << 16) |
(uint32_t) (b);
}
void pattern_snakes(uint len, uint t) {
for (uint i = 0; i < len; ++i) {
uint x = (i + (t >> 1)) % 64;
if (x < 10)
put_pixel(urgb_u32(0xff, 0, 0));
else if (x >= 15 && x < 25)
put_pixel(urgb_u32(0, 0xff, 0));
else if (x >= 30 && x < 40)
put_pixel(urgb_u32(0, 0, 0xff));
else
put_pixel(0);
}
}
void pattern_random(uint len, uint t) {
if (t % 8)
return;
for (int i = 0; i < len; ++i)
put_pixel(rand());
}
void pattern_sparkle(uint len, uint t) {
if (t % 8)
return;
for (int i = 0; i < len; ++i)
put_pixel(rand() % 16 ? 0 : 0xffffffff);
}
void pattern_greys(uint len, uint t) {
int max = 100; // let's not draw too much current!
t %= max;
for (int i = 0; i < len; ++i) {
put_pixel(t * 0x10101);
if (++t >= max) t = 0;
}
}
void pattern_solid(uint len, uint t) {
t = 1;
for (int i = 0; i < len; ++i) {
put_pixel(t * 0x10101);
}
}
int level = 8;
void pattern_fade(uint len, uint t) {
uint shift = 4;
uint max = 16; // let's not draw too much current!
max <<= shift;
uint slow_t = t / 32;
slow_t = level;
slow_t %= max;
static int error;
slow_t += error;
error = slow_t & ((1u << shift) - 1);
slow_t >>= shift;
slow_t *= 0x010101;
for (int i = 0; i < len; ++i) {
put_pixel(slow_t);
}
}
typedef void (*pattern)(uint len, uint t);
const struct {
pattern pat;
const char *name;
} pattern_table[] = {
{pattern_snakes, "Snakes!"},
{pattern_random, "Random data"},
{pattern_sparkle, "Sparkles"},
{pattern_greys, "Greys"},
// {pattern_solid, "Solid!"},
// {pattern_fade, "Fade"},
};
#define VALUE_PLANE_COUNT (8 + FRAC_BITS)
// we store value (8 bits + fractional bits of a single color (R/G/B/W) value) for multiple
// strings, in bit planes. bit plane N has the Nth bit of each string.
typedef struct {
// stored MSB first
uint32_t planes[VALUE_PLANE_COUNT];
} value_bits_t;
// Add FRAC_BITS planes of e to s and store in d
void add_error(value_bits_t *d, const value_bits_t *s, const value_bits_t *e) {
uint32_t carry_plane = 0;
// add the FRAC_BITS low planes
for (int p = VALUE_PLANE_COUNT - 1; p >= 8; p--) {
uint32_t e_plane = e->planes[p];
uint32_t s_plane = s->planes[p];
d->planes[p] = (e_plane ^ s_plane) ^ carry_plane;
carry_plane = (e_plane & s_plane) | (carry_plane & (s_plane ^ e_plane));
}
// then just ripple carry through the non fractional bits
for (int p = 7; p >= 0; p--) {
uint32_t s_plane = s->planes[p];
d->planes[p] = s_plane ^ carry_plane;
carry_plane &= s_plane;
}
}
typedef struct {
uint8_t *data;
uint data_len;
uint frac_brightness; // 256 = *1.0;
} string_t;
// takes 8 bit color values, multiply by brightness and store in bit planes
void transform_strings(string_t **strings, uint num_strings, value_bits_t *values, uint value_length,
uint frac_brightness) {
for (uint v = 0; v < value_length; v++) {
memset(&values[v], 0, sizeof(values[v]));
for (int i = 0; i < num_strings; i++) {
if (v < strings[i]->data_len) {
// todo clamp?
uint32_t value = (strings[i]->data[v] * strings[i]->frac_brightness) >> 8u;
value = (value * frac_brightness) >> 8u;
for (int j = 0; j < VALUE_PLANE_COUNT && value; j++, value >>= 1u) {
if (value & 1u) values[v].planes[VALUE_PLANE_COUNT - 1 - j] |= 1u << i;
}
}
}
}
}
void dither_values(const value_bits_t *colors, value_bits_t *state, const value_bits_t *old_state, uint value_length) {
for (uint i = 0; i < value_length; i++) {
add_error(state + i, colors + i, old_state + i);
}
}
#define MAX_LENGTH 100
// requested colors * 4 to allow for WRGB
static value_bits_t colors[MAX_LENGTH * 4];
// double buffer the state of the string, since we update next version in parallel with DMAing out old version
static value_bits_t states[2][MAX_LENGTH * 4];
// example - string 0 is RGB only
static uint8_t string0_data[MAX_LENGTH * 3];
// example - string 1 is WRGB
static uint8_t string1_data[MAX_LENGTH * 4];
string_t string0 = {
.data = string0_data,
.data_len = sizeof(string0_data),
.frac_brightness = 0x40,
};
string_t string1 = {
.data = string1_data,
.data_len = sizeof(string1_data),
.frac_brightness = 0x100,
};
string_t *strings[] = {
&string0,
&string1,
};
// bit plane content dma channel
#define DMA_CHANNEL 0
// chain channel for configuring main dma channel to output from disjoint 8 word fragments of memory
#define DMA_CB_CHANNEL 1
#define DMA_CHANNEL_MASK (1u << DMA_CHANNEL)
#define DMA_CB_CHANNEL_MASK (1u << DMA_CB_CHANNEL)
#define DMA_CHANNELS_MASK (DMA_CHANNEL_MASK | DMA_CB_CHANNEL_MASK)
// start of each value fragment (+1 for NULL terminator)
static uintptr_t fragment_start[MAX_LENGTH * 4 + 1];
// posted when it is safe to output a new set of values
static struct semaphore reset_delay_complete_sem;
// alarm handle for handling delay
alarm_id_t reset_delay_alarm_id;
int64_t reset_delay_complete(alarm_id_t id, void *user_data) {
reset_delay_alarm_id = 0;
sem_release(&reset_delay_complete_sem);
// no repeat
return 0;
}
void __isr dma_complete_handler() {
if (dma_hw->ints0 & DMA_CHANNEL_MASK) {
// clear IRQ
dma_hw->ints0 = DMA_CHANNEL_MASK;
// when the dma is complete we start the reset delay timer
DEBUG_PINS_SET(timing, 4);
if (reset_delay_alarm_id) cancel_alarm(reset_delay_alarm_id);
reset_delay_alarm_id = add_alarm_in_us(400, reset_delay_complete, NULL, true);
}
}
void dma_init(PIO pio, uint sm) {
dma_claim_mask(DMA_CHANNELS_MASK);
// main DMA channel outputs 8 word fragments, and then chains back to the chain channel
dma_channel_config channel_config = dma_channel_get_default_config(DMA_CHANNEL);
channel_config_set_dreq(&channel_config, pio_get_dreq(pio, sm, true));
channel_config_set_chain_to(&channel_config, DMA_CB_CHANNEL);
channel_config_set_irq_quiet(&channel_config, true);
dma_channel_configure(DMA_CHANNEL,
&channel_config,
&pio->txf[sm],
NULL, // set by chain
8, // 8 words for 8 bit planes
false);
// chain channel sends single word pointer to start of fragment each time
dma_channel_config chain_config = dma_channel_get_default_config(DMA_CB_CHANNEL);
dma_channel_configure(DMA_CB_CHANNEL,
&chain_config,
&dma_channel_hw_addr(
DMA_CHANNEL)->al3_read_addr_trig, // ch DMA config (target "ring" buffer size 4) - this is (read_addr trigger)
NULL, // set later
1,
false);
irq_set_exclusive_handler(DMA_IRQ_0, dma_complete_handler);
dma_channel_set_irq0_enabled(DMA_CHANNEL, true);
irq_set_enabled(DMA_IRQ_0, true);
}
void output_strings_dma(value_bits_t *bits, uint value_length) {
DEBUG_PINS_SET(timing, 3);
for (uint i = 0; i < value_length; i++) {
fragment_start[i] = (uintptr_t) bits[i].planes; // MSB first
}
fragment_start[value_length] = 0;
dma_channel_hw_addr(DMA_CB_CHANNEL)->al3_read_addr_trig = (uintptr_t) fragment_start;
DEBUG_PINS_CLR(timing, 3);
}
int main() {
//set_sys_clock_48();
stdio_init_all();
puts("WS2812 parallel");
#if PIN_TX != 3
gpio_debug_pins_init();
#endif
// todo get free sm
PIO pio = pio0;
int sm = 0;
uint offset = pio_add_program(pio, &ws2812_parallel_program);
ws2812_parallel_program_init(pio, sm, offset, PIN_TX, count_of(strings), 800000);
sem_init(&reset_delay_complete_sem, 1, 1); // initially posted so we don't block first time
dma_init(pio, sm);
int t = 0;
while (1) {
int pat = rand() % count_of(pattern_table);
int dir = (rand() >> 30) & 1 ? 1 : -1;
if (rand() & 1) dir = 0;
puts(pattern_table[pat].name);
puts(dir == 1 ? "(forward)" : dir ? "(backward)" : "(still)");
int brightness = 0;
uint current = 0;
for (int i = 0; i < 1000; ++i) {
int n = 64;
DEBUG_PINS_SET(timing, 1);
current_string_out = string0.data;
current_string_4color = false;
pattern_table[pat].pat(n, t);
current_string_out = string1.data;
current_string_4color = true;
pattern_table[pat].pat(n, t);
DEBUG_PINS_CLR(timing, 1);
DEBUG_PINS_SET(timing, 2);
transform_strings(strings, count_of(strings), colors, n * 4, brightness);
DEBUG_PINS_CLR(timing, 2);
DEBUG_PINS_SET(timing, 1);
dither_values(colors, states[current], states[current ^ 1], n * 4);
DEBUG_PINS_CLR(timing, 1);
sem_acquire_blocking(&reset_delay_complete_sem);
DEBUG_PINS_CLR(timing, 4);
output_strings_dma(states[current], n * 4);
current ^= 1;
t += dir;
brightness++;
if (brightness == (0x20 << FRAC_BITS)) brightness = 0;
}
memset(&states, 0, sizeof(states)); // clear out errors
}
}