diff --git a/pio/CMakeLists.txt b/pio/CMakeLists.txt index 12fd67983..f01067864 100644 --- a/pio/CMakeLists.txt +++ b/pio/CMakeLists.txt @@ -12,6 +12,7 @@ if (NOT PICO_NO_HARDWARE) add_subdirectory(onewire) add_subdirectory(pio_blink) add_subdirectory(pwm) + add_subdirectory(ppm) add_subdirectory(quadrature_encoder) add_subdirectory(spi) add_subdirectory(squarewave) diff --git a/pio/ppm/CMakeLists.txt b/pio/ppm/CMakeLists.txt new file mode 100644 index 000000000..4b2779413 --- /dev/null +++ b/pio/ppm/CMakeLists.txt @@ -0,0 +1,11 @@ +add_executable(pio_ppm) + +pico_generate_pio_header(pio_ppm ${CMAKE_CURRENT_LIST_DIR}/ppm.pio) + +target_sources(pio_ppm PRIVATE ppm.c) + +target_link_libraries(pio_ppm PRIVATE pico_stdlib hardware_pio hardware_clocks) +pico_add_extra_outputs(pio_ppm) + +# add url via pico_set_program_url +example_auto_set_url(pio_ppm) \ No newline at end of file diff --git a/pio/ppm/ppm.c b/pio/ppm/ppm.c new file mode 100644 index 000000000..b8fcc5941 --- /dev/null +++ b/pio/ppm/ppm.c @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +/* + * 8-channel Radio Control PPM example + * Output on GPIO3 + * + + * synchro | ch0 value 1-2ms | ch1 value 1-2ms |...| ch8 value 1-2ms | + * >=5ms _______ _______ _______ _______ + * _______...__| 0.5ms |_____________| 0.5ms |___________|...| 0.5ms |____________| 0.5ms |_... + * + */ +#include + +#include "pico/stdlib.h" +#include "hardware/clocks.h" +#include "hardware/pio.h" +#include "ppm.pio.h" + +void set_value_and_log(uint channel, uint value_usec) { + printf("Channel %d is set to value %5.3f ms\r\n", channel, (float)value_usec / 1000.0f); + ppm_set_value(channel, value_usec); + +} +int main() { + setup_default_uart(); + uint pin = 3; + ppm_program_init(pio0, pin); + while (1) { + set_value_and_log(1, 1100); + set_value_and_log(8, 1800); + sleep_ms(1000); + set_value_and_log(1, 1700); + set_value_and_log(8, 1200); + sleep_ms(1000); + } +} + diff --git a/pio/ppm/ppm.pio b/pio/ppm/ppm.pio new file mode 100644 index 000000000..caef2d154 --- /dev/null +++ b/pio/ppm/ppm.pio @@ -0,0 +1,83 @@ +; +; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. +; +; SPDX-License-Identifier: BSD-3-Clause +; + +.program ppm +.side_set 1 + + set pindirs, 1 side 0; Set pin to output +.wrap_target + out x, 16 side 0 +delay0: + jmp x-- delay0 side 0 + out x, 16 side 1 +delay1: + jmp x-- delay1 side 1 + .wrap + +% c-sdk { +#include "hardware/pio.h" +#include "hardware/clocks.h" +#include "ppm.pio.h" + + +#define PPM_CHANNELS (8) +struct { + volatile PIO pio; + // 16 MSB - strobe length in microsec + // 16 LSB - gap length in microsec + volatile uint32_t ch_values[PPM_CHANNELS + 1]; + volatile uint value_idx; +} ppm_data; + +void ppm_handler() { + while (!pio_sm_is_tx_fifo_full(ppm_data.pio, 0)) { + pio_sm_put(ppm_data.pio, 0, ppm_data.ch_values[ppm_data.value_idx]); + ppm_data.value_idx = (ppm_data.value_idx + 1) % (PPM_CHANNELS + 1); + } +} + +// 500 microseconds strobe + gap = value (microseconds) +#define BUILD_CH_VALUE(value_us) (((500 - 1) << 16) + (value_us - 500 - 1)) + +static inline void ppm_program_init(PIO pio, uint pin) { + ppm_data.pio = pio == NULL ? pio0 : pio; + ppm_data.ch_values[0] = BUILD_CH_VALUE(5500); + // Neutral position (1500us) by default + for (int i = 1; i <= PPM_CHANNELS; ++i) + ppm_data.ch_values[i] = BUILD_CH_VALUE(1500); + ppm_data.value_idx = 0; + uint offset = pio_add_program(ppm_data.pio, &ppm_program); + pio_gpio_init(ppm_data.pio, pin); + pio_sm_set_consecutive_pindirs(pio, 0, pin, 1, true); + pio_sm_config pio_conf = ppm_program_get_default_config(offset); + sm_config_set_sideset_pins(&pio_conf, pin); + sm_config_set_set_pins(&pio_conf, 0, 1); + sm_config_set_out_shift(&pio_conf, true, true, 0); + sm_config_set_fifo_join(&pio_conf, PIO_FIFO_JOIN_TX); + pio_sm_init(ppm_data.pio, 0, offset, &pio_conf); + pio_sm_set_clkdiv(ppm_data.pio, 0, (float) clock_get_hz(clk_sys) / 1000000); //1MHz + pio_sm_clear_fifos(ppm_data.pio, 0); + pio_sm_restart(ppm_data.pio, 0); + + pio_set_irq0_source_enabled(ppm_data.pio, PIO_INTR_SM0_TXNFULL_LSB, true); + irq_set_exclusive_handler(PIO0_IRQ_0, ppm_handler); + irq_set_enabled(PIO0_IRQ_0, true); + pio_sm_set_enabled(ppm_data.pio, 0, true); +} + +/** Sets channel value + * + * @channel_number RC channel number [1..PPM_CHANNELS] + * @value_usec channel vaue [1000, 2000] + */ +static inline void ppm_set_value(uint channel_number, uint value_usec) { + valid_params_if(PIO, channel_number <= PPM_CHANNELS); + valid_params_if(PIO, channel_number >= 1); + valid_params_if(PIO, value_usec <= 2000); + valid_params_if(PIO, value_usec >= 1000); + ppm_data.ch_values[channel_number] = BUILD_CH_VALUE(value_usec); +} +%}