Initial Release
This commit is contained in:
18
pio/CMakeLists.txt
Normal file
18
pio/CMakeLists.txt
Normal 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 ()
|
||||
11
pio/addition/CMakeLists.txt
Normal file
11
pio/addition/CMakeLists.txt
Normal 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
35
pio/addition/addition.c
Normal 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
27
pio/addition/addition.pio
Normal 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
15
pio/apa102/CMakeLists.txt
Normal 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
69
pio/apa102/apa102.c
Normal 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
89
pio/apa102/apa102.pio
Normal 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
|
||||
11
pio/differential_manchester/CMakeLists.txt
Normal file
11
pio/differential_manchester/CMakeLists.txt
Normal 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)
|
||||
43
pio/differential_manchester/differential_manchester.c
Normal file
43
pio/differential_manchester/differential_manchester.c
Normal 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));
|
||||
}
|
||||
102
pio/differential_manchester/differential_manchester.pio
Normal file
102
pio/differential_manchester/differential_manchester.pio
Normal 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);
|
||||
}
|
||||
%}
|
||||
15
pio/hello_pio/CMakeLists.txt
Normal file
15
pio/hello_pio/CMakeLists.txt
Normal 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
38
pio/hello_pio/hello.c
Normal 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
34
pio/hello_pio/hello.pio
Normal 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
16
pio/hub75/CMakeLists.txt
Normal 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
45
pio/hub75/Readme.md
Normal 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
78
pio/hub75/hub75.c
Normal 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
122
pio/hub75/hub75.pio
Normal 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;
|
||||
}
|
||||
%}
|
||||
BIN
pio/hub75/mountains_128x64.png
Normal file
BIN
pio/hub75/mountains_128x64.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
1027
pio/hub75/mountains_128x64_rgb565.h
Normal file
1027
pio/hub75/mountains_128x64_rgb565.h
Normal file
File diff suppressed because it is too large
Load Diff
16
pio/i2c/CMakeLists.txt
Normal file
16
pio/i2c/CMakeLists.txt
Normal 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
144
pio/i2c/i2c.pio
Normal 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
42
pio/i2c/i2c_bus_scan.c
Normal 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
139
pio/i2c/pio_i2c.c
Normal 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
31
pio/i2c/pio_i2c.h
Normal 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
|
||||
9
pio/logic_analyser/CMakeLists.txt
Normal file
9
pio/logic_analyser/CMakeLists.txt
Normal 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)
|
||||
125
pio/logic_analyser/logic_analyser.c
Normal file
125
pio/logic_analyser/logic_analyser.c
Normal 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);
|
||||
}
|
||||
11
pio/manchester_encoding/CMakeLists.txt
Normal file
11
pio/manchester_encoding/CMakeLists.txt
Normal 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)
|
||||
43
pio/manchester_encoding/manchester_encoding.c
Normal file
43
pio/manchester_encoding/manchester_encoding.c
Normal 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));
|
||||
}
|
||||
94
pio/manchester_encoding/manchester_encoding.pio
Normal file
94
pio/manchester_encoding/manchester_encoding.pio
Normal 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);
|
||||
}
|
||||
%}
|
||||
14
pio/pio_blink/CMakeLists.txt
Normal file
14
pio/pio_blink/CMakeLists.txt
Normal 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
34
pio/pio_blink/blink.c
Normal 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
34
pio/pio_blink/blink.pio
Normal 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
11
pio/pwm/CMakeLists.txt
Normal 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
46
pio/pwm/pwm.c
Normal 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
31
pio/pwm/pwm.pio
Normal 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
29
pio/spi/CMakeLists.txt
Normal 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
68
pio/spi/pio_spi.c
Normal 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
24
pio/spi/pio_spi.h
Normal 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
168
pio/spi/spi.pio
Normal 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
155
pio/spi/spi_flash.c
Normal 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
77
pio/spi/spi_loopback.c
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
35
pio/squarewave/CMakeLists.txt
Normal file
35
pio/squarewave/CMakeLists.txt
Normal 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)
|
||||
4
pio/squarewave/generated/squarewave.hex
Normal file
4
pio/squarewave/generated/squarewave.hex
Normal file
@@ -0,0 +1,4 @@
|
||||
e081
|
||||
e101
|
||||
e000
|
||||
0001
|
||||
38
pio/squarewave/generated/squarewave.pio.h
Normal file
38
pio/squarewave/generated/squarewave.pio.h
Normal 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
|
||||
|
||||
37
pio/squarewave/generated/squarewave_wrap.pio.h
Normal file
37
pio/squarewave/generated/squarewave_wrap.pio.h
Normal 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
|
||||
|
||||
72
pio/squarewave/squarewave.c
Normal file
72
pio/squarewave/squarewave.c
Normal 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;
|
||||
|
||||
}
|
||||
13
pio/squarewave/squarewave.pio
Normal file
13
pio/squarewave/squarewave.pio
Normal 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`
|
||||
|
||||
19
pio/squarewave/squarewave_fast.pio
Normal file
19
pio/squarewave/squarewave_fast.pio
Normal 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
|
||||
|
||||
19
pio/squarewave/squarewave_wrap.pio
Normal file
19
pio/squarewave/squarewave_wrap.pio
Normal 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
|
||||
11
pio/st7789_lcd/CMakeLists.txt
Normal file
11
pio/st7789_lcd/CMakeLists.txt
Normal 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)
|
||||
BIN
pio/st7789_lcd/raspberry_256x256.png
Normal file
BIN
pio/st7789_lcd/raspberry_256x256.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
8195
pio/st7789_lcd/raspberry_256x256_rgb565.h
Normal file
8195
pio/st7789_lcd/raspberry_256x256_rgb565.h
Normal file
File diff suppressed because it is too large
Load Diff
148
pio/st7789_lcd/st7789_lcd.c
Normal file
148
pio/st7789_lcd/st7789_lcd.c
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
57
pio/st7789_lcd/st7789_lcd.pio
Normal file
57
pio/st7789_lcd/st7789_lcd.pio
Normal 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))
|
||||
;
|
||||
}
|
||||
%}
|
||||
16
pio/uart_rx/CMakeLists.txt
Normal file
16
pio/uart_rx/CMakeLists.txt
Normal 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
60
pio/uart_rx/uart_rx.c
Normal 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
94
pio/uart_rx/uart_rx.pio
Normal 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;
|
||||
}
|
||||
|
||||
%}
|
||||
11
pio/uart_tx/CMakeLists.txt
Normal file
11
pio/uart_tx/CMakeLists.txt
Normal 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
27
pio/uart_tx/uart_tx.c
Normal 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
61
pio/uart_tx/uart_tx.pio
Normal 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
35
pio/ws2812/CMakeLists.txt
Normal 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)
|
||||
112
pio/ws2812/generated/ws2812.pio.h
Normal file
112
pio/ws2812/generated/ws2812.pio.h
Normal 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
|
||||
|
||||
46
pio/ws2812/generated/ws2812.py
Normal file
46
pio/ws2812/generated/ws2812.py
Normal 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
100
pio/ws2812/ws2812.c
Normal 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
85
pio/ws2812/ws2812.pio
Normal 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);
|
||||
}
|
||||
%}
|
||||
341
pio/ws2812/ws2812_parallel.c
Normal file
341
pio/ws2812/ws2812_parallel.c
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user