diff --git a/README.md b/README.md index f43a7339a..a2312c7e4 100644 --- a/README.md +++ b/README.md @@ -329,6 +329,7 @@ App|Description [pio_ws2812](pio/ws2812) | Example of driving a string of WS2812 addressable RGB LEDs. [pio_ws2812_parallel](pio/ws2812) | Examples of driving multiple strings of WS2812 addressable RGB LEDs efficiently. [pio_addition](pio/addition) | Add two integers together using PIO. Only around 8 billion times slower than Cortex-M0+. +[pio_rs422_tx](pio/rs422_tx) | Modifies the pio_uart_tx sample to send "Hello, world!" with RS-422 transmit functionality without an external IC. ### PWM diff --git a/pio/CMakeLists.txt b/pio/CMakeLists.txt index 023bd4ea8..6cc93b33d 100644 --- a/pio/CMakeLists.txt +++ b/pio/CMakeLists.txt @@ -14,6 +14,7 @@ if (TARGET hardware_pio) add_subdirectory_exclude_platforms(pwm) add_subdirectory_exclude_platforms(quadrature_encoder) add_subdirectory_exclude_platforms(quadrature_encoder_substep) + add_subdirectory_exclude_platforms(rs422_tx) add_subdirectory_exclude_platforms(spi) add_subdirectory_exclude_platforms(squarewave) add_subdirectory_exclude_platforms(st7789_lcd) diff --git a/pio/rs422_tx/CMakeLists.txt b/pio/rs422_tx/CMakeLists.txt new file mode 100644 index 000000000..cba7be988 --- /dev/null +++ b/pio/rs422_tx/CMakeLists.txt @@ -0,0 +1,11 @@ +add_executable(pio_rs422_tx) + +pico_generate_pio_header(pio_rs422_tx ${CMAKE_CURRENT_LIST_DIR}/rs422_tx.pio) + +target_sources(pio_rs422_tx PRIVATE rs422_tx.c) + +target_link_libraries(pio_rs422_tx PRIVATE pico_stdlib hardware_pio) +pico_add_extra_outputs(pio_rs422_tx) + +# add url via pico_set_program_url +example_auto_set_url(pio_rs422_tx) diff --git a/pio/rs422_tx/rs422_tx.c b/pio/rs422_tx/rs422_tx.c new file mode 100644 index 000000000..6e203c973 --- /dev/null +++ b/pio/rs422_tx/rs422_tx.c @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "pico/stdlib.h" +#include "hardware/pio.h" +#include "rs422_tx.pio.h" + +// Defines the GPIO pins for PIO communication. +// PIO_TXP_PIN is the positive pin. +// PIO_TXM_PIN is the negative pin and must be the next pin in sequence. +#define PIO_TXP_PIN 0 +#define PIO_TXM_PIN 1 + +// Check the pin is compatible with the platform +#if (PIO_TXM_PIN) != (PIO_TXP_PIN + 1) +#error "PIO_TXM_PIN must be the next pin after PIO_TXP_PIN." +#endif +#if (PIO_TXM_PIN) >= NUM_BANK0_GPIOS +#error "Pin number exceeds available GPIOs (30 on RP2040/RP2350A, 48 on RP2350B)" +#endif +#if NUM_BANK0_GPIOS > 30 +// On RP2350B, ensure both pins are in the same PIO range +#if !((PIO_TXP_PIN >= 0 && PIO_TXM_PIN < 32) || (PIO_TXP_PIN >= 16 && PIO_TXM_PIN < 48)) +#error "On RP2350B, both pins must be in the same PIO range: [0-31] or [16-47]" +#endif +#endif + +int main() { + // This is the same as the default UART baud rate on Pico + const uint SERIAL_BAUD = 115200; + + PIO pio; + uint sm; + uint offset; + + // This will find a free pio and state machine for our program and load it for us + // We use pio_claim_free_sm_and_add_program_for_gpio_range (for_gpio_range variant) + // so we will get a PIO instance suitable for addressing gpios >= 32 if needed and supported by the hardware + bool success = pio_claim_free_sm_and_add_program_for_gpio_range(&rs422_tx_program, &pio, &sm, &offset, PIO_TXP_PIN, 2, true); + hard_assert(success); + + rs422_tx_program_init(pio, sm, offset, PIO_TXP_PIN, SERIAL_BAUD); + + while (true) { + rs422_tx_program_puts(pio, sm, "Hello, world! (from PIO!)\r\n"); + sleep_ms(1000); + } + + // This will free resources and unload our program + pio_remove_program_and_unclaim_sm(&rs422_tx_program, pio, sm, offset); +} diff --git a/pio/rs422_tx/rs422_tx.pio b/pio/rs422_tx/rs422_tx.pio new file mode 100644 index 000000000..a536ed5c6 --- /dev/null +++ b/pio/rs422_tx/rs422_tx.pio @@ -0,0 +1,75 @@ +; +; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. +; +; SPDX-License-Identifier: BSD-3-Clause +; +.pio_version 0 // only requires PIO version 0 + +.program rs422_tx +.side_set 2 opt + +; An 8n1 UART transmit program. +; OUT pin 0-1 are mapped to RS422 TX+ and TX- pin. + + pull side 0b01 [3] ; Assert stop bit, or stall with line in idle state + nop + nop + nop + nop + set x, 7 side 0b10 [3] ; Preload bit counter, assert start bit + nop + nop + nop +bitloop: ; This loop will run 8 times (8n1) + out y, 1 ; Shift 1 bit from OSR to the Y register + jmp !y put0 +put1: + jmp cont side 0b01 [3] +put0: + nop side 0b10 [3] +cont: + nop + jmp x-- bitloop + + +% c-sdk { +#include "hardware/clocks.h" + +static inline void rs422_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_consecutive_pindirs(pio, sm, pin_tx, 2, true); + for ( uint i = 0;i<2;i++ ) { + pio_gpio_init(pio, pin_tx + i); + } + + pio_sm_config c = rs422_tx_program_get_default_config(offset); + + // OUT shifts to right, no autopull + sm_config_set_out_shift(&c, true, false, 32); + + // We use side-set to control the RS422 differential pins + // Data bits are determined by conditional jumps based on OUT to Y register + 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 rs422_tx_program_putc(PIO pio, uint sm, char c) { + pio_sm_put_blocking(pio, sm, (uint32_t)c); +} + +static inline void rs422_tx_program_puts(PIO pio, uint sm, const char *s) { + while (*s) + rs422_tx_program_putc(pio, sm, *s++); +} + +%}