Repository: GitJer/Some_RPI-Pico_stuff Branch: main Commit: 0041b97507cc Files: 162 Total size: 363.6 KB Directory structure: gitextract_myyc33ud/ ├── Button-debouncer/ │ ├── CMakeLists.txt │ ├── README.md │ ├── button_debounce.cpp │ ├── button_debounce.h │ ├── button_debounce.pio │ ├── main.cpp │ └── micropython_integrated/ │ ├── README.md │ ├── button_debounce.cpp │ ├── button_debounce.h │ ├── button_debounce.pio.h │ ├── debounce.cpp │ ├── debouncemodule.c │ ├── debouncemodule.h │ └── micropython.cmake ├── CMakeLists.txt ├── HCSR04/ │ ├── CMakeLists.txt │ ├── HCSR04.cpp │ ├── HCSR04.pio │ └── README.md ├── LICENSE ├── Limited_1_wire/ │ ├── CMakeLists.txt │ ├── README.md │ ├── onewire.cpp │ └── onewire.pio ├── PwmIn/ │ ├── CMakeLists.txt │ ├── PwmIn.cpp │ ├── PwmIn.pio │ ├── PwmIn_4pins/ │ │ ├── CMakeLists.txt │ │ ├── PWM4.cpp │ │ ├── PwmIn.cpp │ │ ├── PwmIn.h │ │ └── PwmIn.pio │ └── README.md ├── README.md ├── Rotary_encoder/ │ ├── CMakeLists.txt │ ├── README.md │ ├── pio_rotary_encoder.cpp │ └── pio_rotary_encoder.pio ├── Rotational_shift_ISR/ │ ├── CMakeLists.txt │ ├── README.md │ ├── rotational_shift_ISR.cpp │ └── rotational_shift_ISR.pio ├── SBUS/ │ ├── CMakeLists.txt │ ├── README.md │ ├── SBUS.cpp │ ├── SBUS.pio │ └── gpio_invert/ │ ├── CMakeLists.txt │ └── SBUS.cpp ├── Two_sm_one_disabled/ │ ├── CMakeLists.txt │ ├── README.md │ ├── two_sm_one_disabled.cpp │ └── two_sm_one_disabled.pio ├── Two_sm_one_disabled_with_irq/ │ ├── CMakeLists.txt │ ├── README.md │ ├── two_sm_one_disabled_with_irq.cpp │ └── two_sm_one_disabled_with_irq.pio ├── Two_sm_simple/ │ ├── CMakeLists.txt │ ├── README.md │ ├── two_sm_simple.cpp │ └── two_sm_simple.pio ├── Value_communication_between_two_sm_via_pins/ │ ├── CMakeLists.txt │ ├── README.md │ ├── value_communication_between_two_sm_via_pins.cpp │ └── value_communication_between_two_sm_via_pins.pio ├── Z80/ │ ├── CMakeLists.txt │ ├── README.md │ ├── Z80.c │ └── Z80.pio ├── blow_out_a_LED/ │ ├── CMakeLists.txt │ ├── README.md │ └── blow_led.cpp ├── button_matrix_4x4/ │ ├── 4x4_button_matrix.cpp │ ├── 4x4_button_matrix.pio │ ├── CMakeLists.txt │ └── README.md ├── count_pulses_with_pause/ │ ├── CMakeLists.txt │ ├── README.md │ ├── count_pulses_with_pause.cpp │ └── count_pulses_with_pause.pio ├── example_auto_set_url.cmake ├── handy_bits_and_pieces/ │ └── README.md ├── ledpanel/ │ ├── CMakeLists.txt │ ├── README.md │ ├── ledpanel.c │ ├── ledpanel.h │ ├── ledpanel.pio │ └── ledpanel_worker.c ├── multiplication/ │ ├── CMakeLists.txt │ ├── README.md │ ├── multiplier.cpp │ └── multiplier.pio ├── pico_sdk_import.cmake ├── sm_to_dma_to_buffer/ │ ├── CMakeLists.txt │ ├── README.md │ ├── sm_to_dma_to_buffer.cpp │ └── sm_to_dma_to_buffer.pio ├── sm_to_dma_to_sm_to_dma_to_buffer/ │ ├── CMakeLists.txt │ ├── README.md │ ├── sm_to_dma_to_sm_to_dma_to_buffer.cpp │ └── sm_to_dma_to_sm_to_dma_to_buffer.pio ├── state_machine_emulator/ │ ├── README.md │ ├── config.py │ ├── emulation.py │ ├── examples/ │ │ ├── button_debounce/ │ │ │ ├── c_program │ │ │ ├── pin_program │ │ │ └── pio_program.pio.h │ │ ├── in_shift/ │ │ │ ├── c_program │ │ │ ├── pin_program │ │ │ └── pio_program.pio.h │ │ ├── irq_set_and_clear/ │ │ │ ├── c_program │ │ │ ├── pin_program │ │ │ └── pio_program.pio.h │ │ ├── ledpanel/ │ │ │ ├── c_program │ │ │ ├── ledpanel.pio.h │ │ │ └── pin_program │ │ ├── multiplication/ │ │ │ ├── c_program │ │ │ ├── pin_program │ │ │ └── pio_program.pio.h │ │ ├── push_pull_auto/ │ │ │ ├── c_program │ │ │ ├── pin_program │ │ │ └── push_pull.pio.h │ │ ├── rotational_shift/ │ │ │ ├── c_program │ │ │ ├── pin_program │ │ │ └── pio_program.pio.h │ │ ├── side_step/ │ │ │ ├── README.md │ │ │ ├── c_program │ │ │ ├── pin_program │ │ │ └── pio_program.pio.h │ │ ├── square_wave/ │ │ │ ├── c_program │ │ │ ├── pin_program │ │ │ └── pio_program.pio.h │ │ └── stepper/ │ │ ├── c_program │ │ ├── pin_program │ │ └── pio_program.pio.h │ ├── interface/ │ │ ├── __init__.py │ │ ├── _interface_item.py │ │ ├── _left_frame.py │ │ ├── _mid_frame.py │ │ ├── _output_frame.py │ │ ├── _right_frame.py │ │ ├── _toolbar.py │ │ └── _tooltips.py │ ├── main.py │ └── state_machine/ │ ├── __init__.py │ ├── _do_sideset.py │ ├── _execute_instructions.py │ ├── _push_pull.py │ ├── _set_all_GPIO.py │ └── _time_step.py ├── subroutines/ │ ├── CMakeLists.txt │ ├── README.md │ ├── subroutine.cpp │ └── subroutine.pio ├── two_pio_programs_one_file/ │ ├── CMakeLists.txt │ ├── README.md │ ├── two_p_one_f.cpp │ ├── two_p_one_f.pio │ └── two_p_one_f.pio.h └── ws2812_led_strip_120/ ├── CMakeLists.txt ├── README.md ├── ws2812_led_strip_120.c └── ws2812_led_strip_120.pio ================================================ FILE CONTENTS ================================================ ================================================ FILE: Button-debouncer/CMakeLists.txt ================================================ add_executable(pio_button_debounce) pico_generate_pio_header(pio_button_debounce ${CMAKE_CURRENT_LIST_DIR}/button_debounce.pio) target_sources(pio_button_debounce PRIVATE button_debounce.cpp main.cpp) target_link_libraries(pio_button_debounce PRIVATE pico_stdlib hardware_pio ) pico_add_extra_outputs(pio_button_debounce) # add url via pico_set_program_url example_auto_set_url(pio_button_debounce) ================================================ FILE: Button-debouncer/README.md ================================================ # Button debouncer using the Raspberry Pico PIO ## Update The new version allows the use of all 8 PIO state machines, and thus debounce up to 8 gpio. It also allows setting of the debounce time between 0.5 to 30 ms. ## Original text When using a GPIO to read noisy input, such as a mechanical button, it may happen that the signal read by the microcontroller rapidly switches back and forth, which may lead to false detection of several button presses where only one was intended. To prevent this, some hardware solutions exist as well as software solutions. This project is a software debouncer that makes sure that only after the input signal has stabilized, the code will read the new value. The downside of debouncers is that they usually cost some processing time to function. For Arduino a simple debouncer can be found [here](https://www.arduino.cc/en/Tutorial/BuiltInExamples/Debounce). The nice thing about the [Raspberry Pico](https://www.raspberrypi.org/documentation/pico/getting-started) is that it has 8 programmable IO (PIO) blocks that work independent of the main cores. I wanted to try my hand at programming the PIO and decided to do something I hadn't already seen as an example: a debouncer. The debouncer runs on one of the state machines of a PIO instance (there are two PIO instances and each has 4 state machines.) The c++ code contains a class that starts the PIO code and lets the user code read the debounced pin state. The PIO code is as follows: jmp pin isone ; executed only once: is the pin currently 0 or 1? iszero: wait 1 pin 0 ; the pin is 0, wait for it to become 1 set x 31 ; prepare to test the pin for 31 times checkzero: jmp pin stillone ; check if the pin is still 1 jmp iszero ; if the pin has returned to 0, start over stillone: jmp x-- checkzero ; decrease the time to wait, or the pin has definitively become 1 isone: wait 0 pin 0 ; the pin is 1, wait for it to become 0 set x 31 ; prepare to test the pin for 31 times checkone: jmp pin isone ; if the pin has returned to 1, start over jmp x-- checkone ; decrease the time to wait jmp iszero ; the pin has definitively become 0 ## Explanation of PIO code * Start with the assumption that the pin is in a steady state. If it is currently 1, then go to 'isone'; if it is currently 0, then go to 'iszero' * The code after 'isone' works as follows: * Since the pin is 1 wait for a change to 0 * If that happens, set 31 into the x scratch register * This is the amount of 'time' the debouncer will wait before switching over. The actual amount of time is also dependent on the clock divisor, and the fact that two jmp statements are executed for a test. Edit: in the updated version the debounce time can be set in ms. * The program keeps checking if the input changes back to 1; and if so, start over at 'isone' * If the input does not change back, complete the loop of counting down from 31 * If the x scratch register becomes 0, the signal has definitively switched to 0; start from 'iszero' * The branch of 'iszero' works similarly, but is structured a little bit differently because the `jmp pin` statement jumps on 1, not 0 There is one more important aspect to consider: the user code needs to read the debounced pin. So, somehow information from the PIO state machine has to go to the user code. I have considered several options: * Use the FIFO much like the [uart_rx](https://github.com/raspberrypi/pico-examples/tree/master/pio/uart_rx) example code * Use the FIFO, but from the PIO code make it empty (IN NULL) for a 0 and fill it with something to indicate a 1, and test the FIFO content with pio_sm_get_rx_fifo_level, or pio_sm_is_rx_fifo_empty * Use an interrupt, e.g. PIO0_IRQ_0 for a 0, and PIO0_IRQ_1 for a 1 * Use an interesting approach where no explicit communication between the user code and PIO code is used at all! The option I chose is based on the fact that the user code can know where the PIO state machine program counter is during execution via `pio_sm_get_pc`. The debouncer code has a clear split between the part where the debounced value is 0 and the part where it is 1: If (offset+1 <= pc < offset+isone) the value is 0, if (pc >= offset+isone) the value is 1, where offset is the starting point of the program in the PIO memory, and pc is the program counter. This approach keeps the code very small, and reading the debounced state is very quick. ## Does it actually work Yes, it does. To show that the debouncer works, I have used another microcontroller to generate a repeating signal that bounces up and down a couple of times. In the figure below I have used a slightly different PIO code that sets a GPIO to 0 or 1 depending on the debounced state. ![](debounce_test.png) The blue line is the raw input signal. It bounces up and down a couple of times between point (a) and (b). The yellow line is the debounced pin state. It clearly follows the blue line, but skips over the bounces. If the debouncing time is chosen too small the yellow line simply follows the blue line including the bounces, but with a slight delay. ================================================ FILE: Button-debouncer/button_debounce.cpp ================================================ #include #include "pico/stdlib.h" #include "hardware/pio.h" #include "hardware/clocks.h" #include "button_debounce.pio.h" #include "button_debounce.h" // indicate if errors and warnings should print a message // comment out for no messages #define PRINT_ERRORS // indicator that something is not used or not set #define UNUSED -10 /* * class that debounces gpio using the PIO state machines. * up to 8 gpios can be debounced at any one time. * the debounce time for each gpio can be set individually, default it is set a 10ms. */ Debounce::Debounce(void) { // indicate that currently there are no gpios debounced for (int i = 0; i < 32; i++) { gpio_debounced[i] = UNUSED; pio_debounced[i] = (PIO)NULL; sm_debounced[i] = UNUSED; offset[i] = UNUSED; // conf[i] = (pio_sm_config) 0; } num_of_debounced = 0; pio0_already_set = UNUSED; pio1_already_set = UNUSED; } /* * Request to debounce the gpio * @param gpio: the gpio that needs to be debounced * the value must be [0, 28] excluding 23, 24 and 25. */ int Debounce::debounce_gpio(uint gpio) { // check if the gpio is valid if ((gpio > 28) || gpio == 23 || gpio == 24 || gpio == 25) { #ifdef PRINT_ERRORS printf("debounce error: gpio should be 0 to 28 excluding 23, 24 and 25\n"); #endif return -1; } // check that the gpio is unused if (gpio_debounced[gpio] != UNUSED) { #ifdef PRINT_ERRORS printf("debounce warning: gpio is already debounced\n"); #endif return -1; } // check if there are still sm available (there are 8, but other programs could also be using sm, which is checked later) if (num_of_debounced == 8) { #ifdef PRINT_ERRORS printf("debounce error: max 8 gpios can be debounced (no state machine available)\n"); #endif return -1; } // Find a pio and sm: // start with trying to use pio0 PIO pio = pio0; // claim a state machine, no panic if non is available uint sm = pio_claim_unused_sm(pio, false); // check if this is a valid sm if (sm == -1) { // pio0 did not deliver a sm, try pio1 pio = pio1; // claim a state machine, no panic if non is available sm = pio_claim_unused_sm(pio, false); // check if this is a valid sm if (sm == -1) { // also no sm from pio1, return an error #ifdef PRINT_ERRORS printf("debounce error: no state machine available\n"); #endif return -1; } } pio_debounced[gpio] = pio; sm_debounced[gpio] = sm; gpio_debounced[gpio] = gpio; num_of_debounced += 1; // check if the pio program has already been loaded: if ((pio_debounced[gpio] == pio0) && (pio0_already_set != UNUSED)) { offset[gpio] = offset[pio0_already_set]; conf[gpio] = conf[pio0_already_set]; } else if ((pio_debounced[gpio] == pio1) && (pio1_already_set != UNUSED)) { offset[gpio] = offset[pio1_already_set]; conf[gpio] = conf[pio1_already_set]; } else { // load the pio program into the pio memory offset[gpio] = pio_add_program(pio_debounced[gpio], &button_debounce_program); // make a sm config conf[gpio] = button_debounce_program_get_default_config(offset[gpio]); // set the initial clkdiv to 10ms sm_config_set_clkdiv(&conf[gpio], 10.); if (pio_debounced[gpio] == pio0) pio0_already_set = gpio; else pio1_already_set = gpio; } // set the 'wait' gpios sm_config_set_in_pins(&conf[gpio], gpio); // for WAIT, IN // set the 'jmp' gpios sm_config_set_jmp_pin(&conf[gpio], gpio); // for JMP // init the pio sm with the config pio_sm_init(pio_debounced[gpio], sm_debounced[gpio], offset[gpio], &conf[gpio]); // enable the sm pio_sm_set_enabled(pio_debounced[gpio], sm_debounced[gpio], true); return 0; }; /* * Request to debounce the gpio * @param gpio: the gpio that needs to be debounced * the value must be a uint in the range [0, 28] excluding 23, 24 and 25. * @param debounce_time: the float debounce_time in milliseconds in the range [0.5, 30.] */ int Debounce::set_debounce_time(uint gpio, float debounce_time) { // check if the gpio is valid if ((gpio > 28) || gpio == 23 || gpio == 24 || gpio == 25) { #ifdef PRINT_ERRORS printf("debounce error: gpio should be 0 to 28 excluding 23, 24 and 25\n"); #endif return -1; } if (debounce_time < 0.5) { #ifdef PRINT_ERRORS printf("debounce error: debounce time must be > 0 ms\n"); #endif return -1; } if (debounce_time > 30) { #ifdef PRINT_ERRORS printf("debounce error: the maximum is 30 ms, if you need longer debounce times: instructions are in the code\n"); #endif return -1; } /* calculate clkdiv based on debounce time: Note: the resulting debounce time will not be very precise, but probably within 5 to 10% In the pio code it becomes clear that the debounce time is about 31*2 = 62 instructions. The time in seconds when a clkdiv is applied then becomes: clkdiv * 62 / 125000000 In microseconds this is: clkdiv * 62/125, which is about half the clkdiv value. Conversely: the clkdiv for a given debounce_time in miliseconds is: 2 * 1000 * debounce_time The minimum clkdiv value is 1, the corresponding debounce time is about 500 microseconds The maximum clkdiv value is 65535, the corresponding debounce time is about 33 milliseconds If a longer debounce time is required, the pio code must be adapted to add some pauses. This is indicated in the pio code. */ // stop the sm pio_sm_set_enabled(pio_debounced[gpio], sm_debounced[gpio], false); // calculate the clkdiv (see explanation above) float clkdiv = 2. * debounce_time * 1000.; // check that the clkdiv has a valid value if (clkdiv < 1.0) clkdiv = 1.0; else if (clkdiv > 65535.) clkdiv = 65535.; // set the clkdiv for both pio sm_config_set_clkdiv(&conf[gpio], clkdiv); // do the init of the pio/sm pio_sm_init(pio_debounced[gpio], sm_debounced[gpio], offset[gpio], &conf[gpio]); // enable the sm pio_sm_set_enabled(pio_debounced[gpio], sm_debounced[gpio], true); return 0; }; /* * Read the current value of the debounced the gpio * @param gpio: the gpio whose value (low, high) is read * the gpio must have previously been debounced using debounce_gpio() */ int Debounce::read(uint gpio) { // check if the gpio is valid if ((gpio > 28) || gpio == 23 || gpio == 24 || gpio == 25) { #ifdef PRINT_ERRORS printf("debounce error: gpio should be 0 to 28 excluding 23, 24 and 25\n"); #endif return -1; } // check that this gpio is indeed being debounced if (gpio_debounced[gpio] == UNUSED) { #ifdef PRINT_ERRORS printf("debounce error: gpio is not debounced\n"); #endif return -1; } // read the program counter uint pc = pio_sm_get_pc(pio_debounced[gpio], sm_debounced[gpio]); // if it is at or beyond the "wait 0 pin 0" it has value 1, else 0 // in the pio code a public define called 'border' is set at that position if (pc >= (offset[gpio] + button_debounce_border)) return 1; else return 0; }; /* * undebounce a previously debounced gpio * @param gpio: the gpio that is no longer going to be debounced * the gpio must have previously been debounced using debounce_gpio() */ int Debounce::undebounce_gpio(uint gpio) { // check if the gpio is valid if ((gpio > 28) || gpio == 23 || gpio == 24 || gpio == 25) { #ifdef PRINT_ERRORS printf("debounce error: gpio should be 0 to 28 excluding 23, 24 and 25\n"); #endif return -1; } // check that this gpio is indeed being debounced if (gpio_debounced[gpio] == UNUSED) { #ifdef PRINT_ERRORS printf("debounce error: gpio is not debounced\n"); #endif return -1; } // disable the pio pio_sm_set_enabled(pio_debounced[gpio], sm_debounced[gpio], false); // save pio, sm and offset to - if possible - unclaim the sm and remove the program from pio memory PIO pio_used = pio_debounced[gpio]; uint sm_used = sm_debounced[gpio]; int offset_used = offset[gpio]; // indicate that the gpio is not debounced gpio_debounced[gpio] = UNUSED; pio_debounced[gpio] = (PIO)NULL; sm_debounced[gpio] = UNUSED; offset[gpio] = UNUSED; // unclaim the sm pio_sm_unclaim(pio_used, sm_used); // if this is the last gpio of a pio: remove the program, set pioX_already_set to UNUSED int i; for (i = 0; i < 32; i++) { // check if the pio is still in use (i.e. one of the sm belongs to this pio) if (pio_debounced[i] == pio_used) break; } // if i==32 it means that no other debounced gpio uses this pio if (i == 32) { // remove the program pio_remove_program(pio_used, &button_debounce_program, offset_used); // indicate that the pio (either pio0 or pio1) is not set if (pio_used == pio0) pio0_already_set = UNUSED; else pio1_already_set = UNUSED; } // there is one less gpio being debounced num_of_debounced--; return 0; } ================================================ FILE: Button-debouncer/button_debounce.h ================================================ #include #include "pico/stdlib.h" #include "hardware/pio.h" #include "hardware/clocks.h" #include "button_debounce.pio.h" /* * class that debounces gpio using the PIO state machines. * up to 8 gpios can be debounced at any one time. * the debounce time for each gpio can be set individually, default it is set a 10ms. */ class Debounce { public: /* * constructor to debounce gpios */ Debounce(void); /* * Request to debounce the gpio * @param gpio: the gpio that needs to be debounced * the value must be [0, 28] excluding 23, 24 and 25. */ int debounce_gpio(uint gpio); /* * set the debounce time for a gpio * @param gpio: the gpio for which the debounce time will be set * the gpio must have previously been debounced using debounce_gpio() * @param debounce_time: the float debounce_time in milliseconds in the range [0.5, 30.] */ int set_debounce_time(uint gpio, float debounce_time); /* * Read the current value of the debounced the gpio * @param gpio: the gpio whose value (low, high) is read * the gpio must have previously been debounced using debounce_gpio() */ int read(uint gpio); /* * undebounce (rebounce?) a previously debounced gpio * @param gpio: the gpio that is no longer going to be debounced * the gpio must have previously been debounced using debounce_gpio() */ int undebounce_gpio(uint gpio); private: // the number of instantiated buttons to debounce uint num_of_debounced = 0; // Note: yes, the below way of doing things is somewhat wastefull, but handy // for each gpio indicate if it is debounced int gpio_debounced[32]; // for each gpio the PIO used to debounce PIO pio_debounced[32]; // for each gpio the sm used to debounce int sm_debounced[32]; // for each gpio the location of the pio program in the memory int offset[32]; // for each gpio the configurations of the pio sm pio_sm_config conf[32]; // flags to indicate the pio has already been set (and by which gpio) int pio0_already_set, pio1_already_set; }; ================================================ FILE: Button-debouncer/button_debounce.pio ================================================ ; Debounce a gpio ; Explanation: ; - start with the assumption that the gpio is in a steady state. ; If it is currently 1, then go to 'isone'; if it is currently 0, then go to 'iszero' ; - the branch of 'isone' works as follows: ; wait for a change to 0 ; if that happens, set 31 into the x scratch register ; this is the amount of 'time' the debouncer will wait before switching over ; the actual amount of time is also dependent on the clock divisor ; the program keeps checking if the input changes back to 1, if so, start over at 'isone' ; if the input does not change back, complete the loop of counting down from 31 ; if the x scratch register becomes 0, the signal has definitively switched to 0: ; start from 'iszero' ; - the branch of 'iszero' works similarly, but note that a jmp pin statement always jumps on 1, not 0 ; - if (offset+1 <= pc < offset+isone) the value is 0, if (pc >= offset+isone) the value is 1 ; - The border between 0 and 1 in the code is taken as 'isone' which is made public as 'button_debounce_border' .program button_debounce jmp pin isone ; executed only once: is the gpio currently 0 or 1? iszero: wait 1 pin 0 ; the gpio is 0, wait for it to become 1 set x 31 ; prepare to test the gpio for 31 * 2 clock cycles checkzero: ; nop [31] ; possible location to add some pauses if longer debounce times are needed ; Note: also insert a pause right after 'checkone' below jmp pin stillone; check if the gpio is still 1 jmp iszero ; if the gpio has returned to 0, start over stillone: jmp x-- checkzero; the decrease the time to wait, or decide it has definitively become 1 isone: wait 0 pin 0 ; the gpio is 1, wait for it to become 0 set x 31 ; prepare to test the gpio for 31 * 2 clock cycles checkone: ; nop [31] ; possible location to add some pauses if longer debounce times are needed ; Note: also insert a pause right after 'checkzero' above jmp pin isone ; if the gpio has returned to 1, start over jmp x-- checkone; decrease the time to wait jmp iszero ; the gpio has definitively become 0 ; the c-code must know where the border between 0 and 1 is in the code: .define public border isone ================================================ FILE: Button-debouncer/main.cpp ================================================ #include #include "pico/stdlib.h" #include "button_debounce.h" /* This code shows how to use the button debouncer that uses the PIO state machines. It shows all functionality: Instantiate the debouncer, e.g.: Debounce debouncer; Request to debounce the gpio, e.g. gpio 3: debouncer.debounce_gpio(3) set the debounce time for a gpio, e.g. set to 1ms: debouncer.set_debounce_time(3, 1); Read the current value of the debounced the gpio, e.g. gpio 3: int v = debouncer.read(3); undebounce (rebounce?) a previously debounced gpio, e.g. gpio 3: debouncer.undebounce_gpio(3); This example code first debounces gpio 3 to 10, then in an infinite loop reads the current value of the debounced gpios. During this loop one by one the gpios are undebounced and then one by one debounced again. */ int main() { // necessary for printf stdio_init_all(); printf("Start of debounce example\n"); // instantiate the debouncer Debounce debouncer; // debounce 8 gpio debouncer.debounce_gpio(3); debouncer.debounce_gpio(4); debouncer.debounce_gpio(5); debouncer.debounce_gpio(6); debouncer.debounce_gpio(7); debouncer.debounce_gpio(8); debouncer.debounce_gpio(9); debouncer.debounce_gpio(10); // set different debounce times // Note: an external puls generator that can vary the puls widts and an logic analyser // was used during testing to verify that this indeed works. // debouncer.set_debounce_time(3, 1); // debouncer.set_debounce_time(4, 2); // debouncer.set_debounce_time(5, 3); // debouncer.set_debounce_time(6, 4); // debouncer.set_debounce_time(7, 5); // debouncer.set_debounce_time(8, 6); // debouncer.set_debounce_time(9, 7); // debouncer.set_debounce_time(10, 8); // infinite loop that continues to show the debounced value of the gpios // in the first part of the loop the debounced gpios are one by one UNdebounced (rebounced?) // in the second part of the loop the gpios are again all debounced (redebounced?) int sm; while (true) { tight_loop_contents(); // loop that reads and prints the current values of the debounced gpios (or print an X if not debounced) // one by one the gpios are undebounced for (int stop_debounce = 3; stop_debounce <= 10; stop_debounce++) { printf("\nValue:\t"); for (int gpio = 3; gpio <= 10; gpio++) { int v = debouncer.read(gpio); if (v != -1) printf("%d\t", v); else printf("X\t"); } sleep_ms(250); // one by one UNdebounce the gpios debouncer.undebounce_gpio(stop_debounce); } // loop that reads and prints the current values of the debounced gpios (or print an X if not debounced) // one by one the gpios are debounced again for (int debounce_again = 3; debounce_again <= 10; debounce_again++) { printf("\nValue:\t"); for (int gpio = 3; gpio <= 10; gpio++) { int v = debouncer.read(gpio); if (v != -1) printf("%d\t", v); else printf("X\t"); } sleep_ms(250); // one by one debounce the gpios debouncer.debounce_gpio(debounce_again); } } } ================================================ FILE: Button-debouncer/micropython_integrated/README.md ================================================ # Button debouncer using the Raspberry Pico PIO integrated into MicroPython ## Note This is a version of the button debounce program that has to be compiled into MicroPython. This is not what I want, but this worked and I'm still working on a version that is an external .mpy file that can be imported. ## How-to compile this code into MicroPython This is a how-to for integrating cpp programs such as the button_debounce class into MicroPython. Starting point: - there is a working micropython environment for the RPI Pico - there is a working cpp program (in this case: button_debounce) Go to the rp2 port in the micropython directory: ``` cd /YOUR_PATH/micropython/ports/rp2 ``` Test that compiling it produces a working micropython binary: ``` make clean make ls -l build-PICO/ ``` The file 'firmware.uf2' should be present (and just built) The module to be integrated in micropython will be located in the examples directory ``` cd /YOUR_PATH/micropython/examples/usercmodule mkdir button_debounce cd button_debounce ``` **The next text isn't needed if you use the files in this github repository! Skip to 'Add to overall cmake file for user-c-modules'** Copy the cpp files to this directory. Do not forget to include the generated .pio.h file if needed: ``` cp /PATH_TO_ORIGINAL_CPP_CODE/button_debounce.* . ``` Now make the following files: ``` touch debounce.cpp touch debouncemodule.h touch debouncemodule.c touch micropython.cmake ``` The content of these files is: debounce.cpp: ``` extern "C" { #include #include "button_debounce.h" Debounce debounce; mp_obj_t debounce_gpio(mp_obj_t mp_gpio) { const auto gpio = mp_obj_get_int(mp_gpio); return mp_obj_new_int(debounce.debounce_gpio(gpio)); } mp_obj_t set_debounce_time(mp_obj_t mp_gpio, mp_obj_t mp_debounce_time) { const auto gpio = mp_obj_get_int(mp_gpio); const auto debounce_time = mp_obj_get_float(mp_debounce_time); return mp_obj_new_int(debounce.set_debounce_time(gpio, debounce_time)); } mp_obj_t read(mp_obj_t mp_gpio) { const auto gpio = mp_obj_get_int(mp_gpio); return mp_obj_new_int(debounce.read(gpio)); } mp_obj_t undebounce_gpio(mp_obj_t mp_gpio) { const auto gpio = mp_obj_get_int(mp_gpio); return mp_obj_new_int(debounce.undebounce_gpio(gpio)); } } ``` debouncemodule.h: ``` // Include MicroPython API. #include "py/runtime.h" // Declare the function we'll make available in Python as cppexample.cppfunc(). extern mp_obj_t debounce_gpio(mp_obj_t mp_gpio); extern mp_obj_t set_debounce_time(mp_obj_t mp_gpio, mp_obj_t mp_debounce_time); extern mp_obj_t read(mp_obj_t mp_gpio); extern mp_obj_t undebounce_gpio(mp_obj_t mp_gpio); ``` debouncemodule.c: ``` #include // Define a Python reference to the function we'll make available. // See example.cpp for the definition. STATIC MP_DEFINE_CONST_FUN_OBJ_1(debounce_gpio_obj, debounce_gpio); STATIC MP_DEFINE_CONST_FUN_OBJ_2(set_debounce_time_obj, set_debounce_time); STATIC MP_DEFINE_CONST_FUN_OBJ_1(read_obj, read); STATIC MP_DEFINE_CONST_FUN_OBJ_1(undebounce_gpio_obj, undebounce_gpio); // Define all properties of the module. // Table entries are key/value pairs of the attribute name (a string) // and the MicroPython object reference. // All identifiers and strings are written as MP_QSTR_xxx and will be // optimized to word-sized integers by the build system (interned strings). STATIC const mp_rom_map_elem_t debounce_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_debounce) }, { MP_ROM_QSTR(MP_QSTR_debounce_gpio), MP_ROM_PTR(&debounce_gpio_obj) }, { MP_ROM_QSTR(MP_QSTR_set_debounce_time), MP_ROM_PTR(&set_debounce_time_obj) }, { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&read_obj) }, { MP_ROM_QSTR(MP_QSTR_undebounce_gpio), MP_ROM_PTR(&undebounce_gpio_obj) }, }; STATIC MP_DEFINE_CONST_DICT(debounce_module_globals, debounce_module_globals_table); // Define module object. const mp_obj_module_t debounce_user_cmodule = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t *)&debounce_module_globals, }; // Register the module to make it available in Python. // Note: the "1" in the third argument means this module is always enabled. // This "1" can be optionally replaced with a macro like MODULE_CPPEXAMPLE_ENABLED // which can then be used to conditionally enable this module. MP_REGISTER_MODULE(MP_QSTR_debounce, debounce_user_cmodule, 1); ``` micropython.cmake: ``` add_library(usermod_debounce INTERFACE) target_sources(usermod_debounce INTERFACE ${CMAKE_CURRENT_LIST_DIR}/button_debounce.cpp ${CMAKE_CURRENT_LIST_DIR}/debounce.cpp ${CMAKE_CURRENT_LIST_DIR}/debouncemodule.c ) target_include_directories(usermod_debounce INTERFACE ${CMAKE_CURRENT_LIST_DIR} ) target_link_libraries(usermod INTERFACE usermod_debounce) ``` ## Add to overall cmake file for user-c-modules In the directory with usercmodules add the button_debounce to the micropython.cmake file (you can use any editor, I used vi): ``` vi /YOUR_PATH/micropython/examples/usercmodule/micropython.cmake ``` This file now looks like: ``` # This top-level micropython.cmake is responsible for listing # the individual modules we want to include. # Paths are absolute, and ${CMAKE_CURRENT_LIST_DIR} can be # used to prefix subdirectories. # Add the C example. # include(${CMAKE_CURRENT_LIST_DIR}/cexample/micropython.cmake) # Add the CPP example. # include(${CMAKE_CURRENT_LIST_DIR}/cppexample/micropython.cmake) # Add the module. include(${CMAKE_CURRENT_LIST_DIR}/button_debounce/micropython.cmake) ``` ## Compiling Go to the rp2 port in the micropython directory and make with the option to include the user_c_modules: ``` cd /YOUR_PATH/micropython/ports/rp2 make USER_C_MODULES=../../examples/usercmodule/micropython.cmake ``` This should result in a firmware.uf2 that includes the button_debounce module. ## Running The firmware must be uploaded to the pico (turn off, button pressed, turn on, file explorer pops up, move firmware.uf2 into it). Thonny can be used to interact with micropython. I made the following script and uploaded it to the pico: ``` import debounce import time debounce.start() debounce.debounce_gpio(15) while True: print(debounce.read(15)) time.sleep(0.1) ``` Note the 'debounce.start'. This is due to a problem I haven't solved yet: micropython loads the module with 'import debounce' but at a restart of a script using it, it doesn't run the constructor of the Debounce class again, therefore not initializing the variables. Thus, I've made a function 'start' to explicitly initialize the class variables. ================================================ FILE: Button-debouncer/micropython_integrated/button_debounce.cpp ================================================ #include #include "pico/stdlib.h" #include "hardware/pio.h" #include "hardware/clocks.h" #include "button_debounce.pio.h" #include "button_debounce.h" // FIXME: micropython doesn't let the printf go through, so might as well not print // indicate if errors and warnings should print a message // comment out for no messages // #define PRINT_ERRORS // indicator that something is not used or not set #define UNUSED -10 /* * class that debounces gpio using the PIO state machines. * up to 8 gpios can be debounced at any one time. * the debounce time for each gpio can be set individually, default it is set a 10ms. */ // FIXME: micropython caused problems with the constructor, so now I've an // empty constructor and the user needs to explicitly start the debounce // with 'start' Debounce::Debounce(void) {} /* * start must be used to initialize the variables */ void Debounce::start(void) { // indicate that currently there are no gpios debounced for (int i = 0; i < 32; i++) { gpio_debounced[i] = UNUSED; pio_debounced[i] = (PIO)NULL; sm_debounced[i] = UNUSED; offset[i] = UNUSED; // conf[i] = (pio_sm_config) 0; } num_of_debounced = 0; pio0_already_set = UNUSED; pio1_already_set = UNUSED; has_started = true; } /* * Request to debounce the gpio * @param gpio: the gpio that needs to be debounced * the value must be [0, 28] excluding 23, 24 and 25. */ int Debounce::debounce_gpio(uint gpio) { if (has_started==false) { #ifdef PRINT_ERRORS printf("debounce error: the module needs to be started first with .start()\n"); #endif return -1; } // check if the gpio is valid if ((gpio > 28) || gpio == 23 || gpio == 24 || gpio == 25) { #ifdef PRINT_ERRORS printf("debounce error: gpio should be 0 to 28 excluding 23, 24 and 25\n"); #endif return -1; } // check that the gpio is unused if (gpio_debounced[gpio] != UNUSED) { #ifdef PRINT_ERRORS printf("debounce warning: gpio is already debounced\n"); #endif return -1; } // check if there are still sm available (there are 8, but other programs could also be using sm, which is checked later) if (num_of_debounced == 8) { #ifdef PRINT_ERRORS printf("debounce error: max 8 gpios can be debounced (no state machine available)\n"); #endif return -1; } // Find a pio and sm: // start with trying to use pio0 PIO pio = pio0; // claim a state machine, no panic if non is available int sm = pio_claim_unused_sm(pio, false); // check if this is a valid sm if (sm == -1) { // pio0 did not deliver a sm, try pio1 pio = pio1; // claim a state machine, no panic if non is available sm = pio_claim_unused_sm(pio, false); // check if this is a valid sm if (sm == -1) { // also no sm from pio1, return an error #ifdef PRINT_ERRORS printf("debounce error: no state machine available\n"); #endif return -1; } } pio_debounced[gpio] = pio; sm_debounced[gpio] = sm; gpio_debounced[gpio] = gpio; num_of_debounced += 1; // check if the pio program has already been loaded: if ((pio_debounced[gpio] == pio0) && (pio0_already_set != UNUSED)) { offset[gpio] = offset[pio0_already_set]; conf[gpio] = conf[pio0_already_set]; } else if ((pio_debounced[gpio] == pio1) && (pio1_already_set != UNUSED)) { offset[gpio] = offset[pio1_already_set]; conf[gpio] = conf[pio1_already_set]; } else { // load the pio program into the pio memory offset[gpio] = pio_add_program(pio_debounced[gpio], &button_debounce_program); // make a sm config conf[gpio] = button_debounce_program_get_default_config(offset[gpio]); // set the initial clkdiv to 10ms sm_config_set_clkdiv(&conf[gpio], 10.); if (pio_debounced[gpio] == pio0) pio0_already_set = gpio; else pio1_already_set = gpio; } // set the 'wait' gpios sm_config_set_in_pins(&conf[gpio], gpio); // for WAIT, IN // set the 'jmp' gpios sm_config_set_jmp_pin(&conf[gpio], gpio); // for JMP // init the pio sm with the config pio_sm_init(pio_debounced[gpio], sm_debounced[gpio], offset[gpio], &conf[gpio]); // enable the sm pio_sm_set_enabled(pio_debounced[gpio], sm_debounced[gpio], true); return 0; }; /* * Request to debounce the gpio * @param gpio: the gpio that needs to be debounced * the value must be a uint in the range [0, 28] excluding 23, 24 and 25. * @param debounce_time: the float debounce_time in milliseconds in the range [0.5, 30.] */ int Debounce::set_debounce_time(uint gpio, float debounce_time) { // check that the module has been started if (has_started==false) { #ifdef PRINT_ERRORS printf("debounce error: the module needs to be started first with .start()\n"); #endif return -1; } // check if the gpio is valid if ((gpio > 28) || gpio == 23 || gpio == 24 || gpio == 25) { #ifdef PRINT_ERRORS printf("debounce error: gpio should be 0 to 28 excluding 23, 24 and 25\n"); #endif return -1; } if (debounce_time < 0.5) { #ifdef PRINT_ERRORS printf("debounce error: debounce time must be > 0 ms\n"); #endif return -1; } if (debounce_time > 30) { #ifdef PRINT_ERRORS printf("debounce error: the maximum is 30 ms, if you need longer debounce times: instructions are in the code\n"); #endif return -1; } /* calculate clkdiv based on debounce time: Note: the resulting debounce time will not be very precise, but probably within 5 to 10% In the pio code it becomes clear that the debounce time is about 31*2 = 62 instructions. The time in seconds when a clkdiv is applied then becomes: clkdiv * 62 / 125000000 In microseconds this is: clkdiv * 62/125, which is about half the clkdiv value. Conversely: the clkdiv for a given debounce_time in miliseconds is: 2 * 1000 * debounce_time The minimum clkdiv value is 1, the corresponding debounce time is about 500 microseconds The maximum clkdiv value is 65535, the corresponding debounce time is about 33 milliseconds If a longer debounce time is required, the pio code must be adapted to add some pauses. This is indicated in the pio code. */ // stop the sm pio_sm_set_enabled(pio_debounced[gpio], sm_debounced[gpio], false); // calculate the clkdiv (see explanation above) float clkdiv = 2. * debounce_time * 1000.; // check that the clkdiv has a valid value if (clkdiv < 1.0) clkdiv = 1.0; else if (clkdiv > 65535.) clkdiv = 65535.; // set the clkdiv for both pio sm_config_set_clkdiv(&conf[gpio], clkdiv); // do the init of the pio/sm pio_sm_init(pio_debounced[gpio], sm_debounced[gpio], offset[gpio], &conf[gpio]); // enable the sm pio_sm_set_enabled(pio_debounced[gpio], sm_debounced[gpio], true); return 0; }; /* * Read the current value of the debounced the gpio * @param gpio: the gpio whose value (low, high) is read * the gpio must have previously been debounced using debounce_gpio() */ int Debounce::read(uint gpio) { // check that the module has been started if (has_started==false) { #ifdef PRINT_ERRORS printf("debounce error: the module needs to be started first with .start()\n"); #endif return -1; } // check if the gpio is valid if ((gpio > 28) || gpio == 23 || gpio == 24 || gpio == 25) { #ifdef PRINT_ERRORS printf("debounce error: gpio should be 0 to 28 excluding 23, 24 and 25\n"); #endif return -1; } // check that this gpio is indeed being debounced if (gpio_debounced[gpio] == UNUSED) { #ifdef PRINT_ERRORS printf("debounce error: gpio is not debounced\n"); #endif return -1; } // read the program counter int pc = pio_sm_get_pc(pio_debounced[gpio], sm_debounced[gpio]); // if it is at or beyond the "wait 0 pin 0" it has value 1, else 0 // in the pio code a public define called 'border' is set at that position if (pc >= (offset[gpio] + button_debounce_border)) return 1; else return 0; }; /* * undebounce a previously debounced gpio * @param gpio: the gpio that is no longer going to be debounced * the gpio must have previously been debounced using debounce_gpio() */ int Debounce::undebounce_gpio(uint gpio) { // check that the module has been started if (has_started==false) { #ifdef PRINT_ERRORS printf("debounce error: the module needs to be started first with .start()\n"); #endif return -1; } // check if the gpio is valid if ((gpio > 28) || gpio == 23 || gpio == 24 || gpio == 25) { #ifdef PRINT_ERRORS printf("debounce error: gpio should be 0 to 28 excluding 23, 24 and 25\n"); #endif return -1; } // check that this gpio is indeed being debounced if (gpio_debounced[gpio] == UNUSED) { #ifdef PRINT_ERRORS printf("debounce error: gpio is not debounced\n"); #endif return -1; } // disable the pio pio_sm_set_enabled(pio_debounced[gpio], sm_debounced[gpio], false); // save pio, sm and offset to - if possible - unclaim the sm and remove the program from pio memory PIO pio_used = pio_debounced[gpio]; uint sm_used = sm_debounced[gpio]; int offset_used = offset[gpio]; // indicate that the gpio is not debounced gpio_debounced[gpio] = UNUSED; pio_debounced[gpio] = (PIO)NULL; sm_debounced[gpio] = UNUSED; offset[gpio] = UNUSED; // unclaim the sm pio_sm_unclaim(pio_used, sm_used); // if this is the last gpio of a pio: remove the program, set pioX_already_set to UNUSED int i; for (i = 0; i < 32; i++) { // check if the pio is still in use (i.e. one of the sm belongs to this pio) if (pio_debounced[i] == pio_used) break; } // if i==32 it means that no other debounced gpio uses this pio if (i == 32) { // remove the program pio_remove_program(pio_used, &button_debounce_program, offset_used); // indicate that the pio (either pio0 or pio1) is not set if (pio_used == pio0) pio0_already_set = UNUSED; else pio1_already_set = UNUSED; } // there is one less gpio being debounced num_of_debounced--; return 0; } ================================================ FILE: Button-debouncer/micropython_integrated/button_debounce.h ================================================ //#include "py/runtime.h" #include "button_debounce.pio.h" /* * class that debounces gpio using the PIO state machines. * up to 8 gpios can be debounced at any one time. * the debounce time for each gpio can be set individually, default it is set a 10ms. */ class Debounce { public: /* * constructor to debounce gpios */ Debounce(void); /* * start the debounce class * FIXME: This is due to the way micropython starts a module (when * restarting it doesn't run the constructor again) */ void start(void); /* * Request to debounce the gpio * @param gpio: the gpio that needs to be debounced * the value must be [0, 28] excluding 23, 24 and 25. */ int debounce_gpio(uint gpio); /* * set the debounce time for a gpio * @param gpio: the gpio for which the debounce time will be set * the gpio must have previously been debounced using debounce_gpio() * @param debounce_time: the float debounce_time in milliseconds in the range [0.5, 30.] */ int set_debounce_time(uint gpio, float debounce_time); /* * Read the current value of the debounced the gpio * @param gpio: the gpio whose value (low, high) is read * the gpio must have previously been debounced using debounce_gpio() */ int read(uint gpio); /* * undebounce (rebounce?) a previously debounced gpio * @param gpio: the gpio that is no longer going to be debounced * the gpio must have previously been debounced using debounce_gpio() */ int undebounce_gpio(uint gpio); private: // indicate that the instantiated class has been started // FIXME: this due to the micropython problem with starting a module and not running the constructor again bool has_started = false; // the number of instantiated buttons to debounce uint num_of_debounced = 0; // Note: yes, the below way of doing things is somewhat wastefull, but handy // for each gpio indicate if it is debounced int gpio_debounced[32]; // for each gpio the PIO used to debounce PIO pio_debounced[32]; // for each gpio the sm used to debounce int sm_debounced[32]; // for each gpio the location of the pio program in the memory int offset[32]; // for each gpio the configurations of the pio sm pio_sm_config conf[32]; // flags to indicate the pio has already been set (and by which gpio) int pio0_already_set, pio1_already_set; }; ================================================ FILE: Button-debouncer/micropython_integrated/button_debounce.pio.h ================================================ // -------------------------------------------------- // // This file is autogenerated by pioasm; do not edit! // // -------------------------------------------------- // #pragma once #if !PICO_NO_HARDWARE #include "hardware/pio.h" #endif // --------------- // // button_debounce // // --------------- // #define button_debounce_wrap_target 0 #define button_debounce_wrap 10 #define button_debounce_border 6 static const uint16_t button_debounce_program_instructions[] = { // .wrap_target 0x00c6, // 0: jmp pin, 6 0x20a0, // 1: wait 1 pin, 0 0xe03f, // 2: set x, 31 0x00c5, // 3: jmp pin, 5 0x0001, // 4: jmp 1 0x0043, // 5: jmp x--, 3 0x2020, // 6: wait 0 pin, 0 0xe03f, // 7: set x, 31 0x00c6, // 8: jmp pin, 6 0x0048, // 9: jmp x--, 8 0x0001, // 10: jmp 1 // .wrap }; #if !PICO_NO_HARDWARE static const struct pio_program button_debounce_program = { .instructions = button_debounce_program_instructions, .length = 11, .origin = -1, }; static inline pio_sm_config button_debounce_program_get_default_config(uint offset) { pio_sm_config c = pio_get_default_sm_config(); sm_config_set_wrap(&c, offset + button_debounce_wrap_target, offset + button_debounce_wrap); return c; } #endif ================================================ FILE: Button-debouncer/micropython_integrated/debounce.cpp ================================================ extern "C" { #include #include "button_debounce.h" #include #include "py/obj.h" Debounce debounce; // FIXME: this is due to micropython not running the constructor again at re-importing the module mp_obj_t start() { // mp_printf(&mp_plat_print, "start\n"); debounce.start(); return mp_obj_new_int(1); } mp_obj_t debounce_gpio(mp_obj_t mp_gpio) { const auto gpio = mp_obj_get_int(mp_gpio); // mp_printf(&mp_plat_print, "debounce_gpio\n"); return mp_obj_new_int(debounce.debounce_gpio(gpio)); } mp_obj_t set_debounce_time(mp_obj_t mp_gpio, mp_obj_t mp_debounce_time) { const auto gpio = mp_obj_get_int(mp_gpio); const auto debounce_time = mp_obj_get_float(mp_debounce_time); // mp_printf(&mp_plat_print, "set_debounce_time\n"); return mp_obj_new_int(debounce.set_debounce_time(gpio, debounce_time)); } mp_obj_t read(mp_obj_t mp_gpio) { const auto gpio = mp_obj_get_int(mp_gpio); // mp_printf(&mp_plat_print, "read\n"); return mp_obj_new_int(debounce.read(gpio)); } mp_obj_t undebounce_gpio(mp_obj_t mp_gpio) { const auto gpio = mp_obj_get_int(mp_gpio); // mp_printf(&mp_plat_print, "undebounce_gpio\n"); return mp_obj_new_int(debounce.undebounce_gpio(gpio)); } } ================================================ FILE: Button-debouncer/micropython_integrated/debouncemodule.c ================================================ #include // Define a Python reference to the function we'll make available. // See example.cpp for the definition. // FIXME: start shouldn't be necessary (see other files) STATIC MP_DEFINE_CONST_FUN_OBJ_0(start_obj, start); STATIC MP_DEFINE_CONST_FUN_OBJ_1(debounce_gpio_obj, debounce_gpio); STATIC MP_DEFINE_CONST_FUN_OBJ_2(set_debounce_time_obj, set_debounce_time); STATIC MP_DEFINE_CONST_FUN_OBJ_1(read_obj, read); STATIC MP_DEFINE_CONST_FUN_OBJ_1(undebounce_gpio_obj, undebounce_gpio); // Define all properties of the module. // Table entries are key/value pairs of the attribute name (a string) // and the MicroPython object reference. // All identifiers and strings are written as MP_QSTR_xxx and will be // optimized to word-sized integers by the build system (interned strings). STATIC const mp_rom_map_elem_t debounce_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_debounce) }, // FIXME: start shouldn't be necessary (see other files) { MP_ROM_QSTR(MP_QSTR_start), MP_ROM_PTR(&start_obj) }, { MP_ROM_QSTR(MP_QSTR_debounce_gpio), MP_ROM_PTR(&debounce_gpio_obj) }, { MP_ROM_QSTR(MP_QSTR_set_debounce_time), MP_ROM_PTR(&set_debounce_time_obj) }, { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&read_obj) }, { MP_ROM_QSTR(MP_QSTR_undebounce_gpio), MP_ROM_PTR(&undebounce_gpio_obj) }, }; STATIC MP_DEFINE_CONST_DICT(debounce_module_globals, debounce_module_globals_table); // Define module object. const mp_obj_module_t debounce_user_cmodule = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t *)&debounce_module_globals, }; // Register the module to make it available in Python. // Note: the "1" in the third argument means this module is always enabled. // This "1" can be optionally replaced with a macro like MODULE_CPPEXAMPLE_ENABLED // which can then be used to conditionally enable this module. MP_REGISTER_MODULE(MP_QSTR_debounce, debounce_user_cmodule, 1); ================================================ FILE: Button-debouncer/micropython_integrated/debouncemodule.h ================================================ // Include MicroPython API. #include "py/runtime.h" // Declare the function we'll make available in Python as cppexample.cppfunc(). // FIXME: start shouldn't be necessary (see other files) extern mp_obj_t start(); extern mp_obj_t debounce_gpio(mp_obj_t mp_gpio); extern mp_obj_t set_debounce_time(mp_obj_t mp_gpio, mp_obj_t mp_debounce_time); extern mp_obj_t read(mp_obj_t mp_gpio); extern mp_obj_t undebounce_gpio(mp_obj_t mp_gpio); ================================================ FILE: Button-debouncer/micropython_integrated/micropython.cmake ================================================ add_library(usermod_debounce INTERFACE) target_sources(usermod_debounce INTERFACE ${CMAKE_CURRENT_LIST_DIR}/button_debounce.cpp ${CMAKE_CURRENT_LIST_DIR}/debounce.cpp ${CMAKE_CURRENT_LIST_DIR}/debouncemodule.c ) target_include_directories(usermod_debounce INTERFACE ${CMAKE_CURRENT_LIST_DIR} ) target_link_libraries(usermod INTERFACE usermod_debounce) ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.12) # Pull in SDK (must be before project) include(pico_sdk_import.cmake) project(pico_examples C CXX ASM) set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) set(PICO_EXAMPLES_PATH ${PROJECT_SOURCE_DIR}) # Initialize the SDK pico_sdk_init() include(example_auto_set_url.cmake) add_subdirectory(blow_out_a_LED) add_subdirectory(Button-debouncer) add_subdirectory(button_matrix_4x4) add_subdirectory(count_pulses_with_pause) add_subdirectory(HCSR04) add_subdirectory(ledpanel) add_subdirectory(Limited_1_wire) add_subdirectory(multiplication) add_subdirectory(PwmIn) add_subdirectory(Rotary_encoder) add_subdirectory(Rotational_shift_ISR) add_subdirectory(SBUS) add_subdirectory(sm_to_dma_to_buffer) add_subdirectory(sm_to_dma_to_sm_to_dma_to_buffer) add_subdirectory(subroutines) add_subdirectory(two_pio_programs_one_file) add_subdirectory(Two_sm_one_disabled) add_subdirectory(Two_sm_one_disabled_with_irq) add_subdirectory(Two_sm_simple) add_subdirectory(Value_communication_between_two_sm_via_pins) add_subdirectory(ws2812_led_strip_120) ================================================ FILE: HCSR04/CMakeLists.txt ================================================ add_executable(HCSR04) pico_generate_pio_header(HCSR04 ${CMAKE_CURRENT_LIST_DIR}/HCSR04.pio) target_sources(HCSR04 PRIVATE HCSR04.cpp) target_link_libraries(HCSR04 PRIVATE pico_stdlib hardware_pio ) pico_add_extra_outputs(HCSR04) # add url via pico_set_program_url example_auto_set_url(HCSR04) ================================================ FILE: HCSR04/HCSR04.cpp ================================================ #include #include "pico/stdlib.h" #include "hardware/pio.h" #include "HCSR04.pio.h" // class that sets up and reads the HCSR04 // The HCSR04 works by giving it a 10 us pulse on its Trigger pin // The distance to an object is represented by the length of the pulse on its Echo pin class HCSR04 { public: // constructor // input = pin connected to the 'Echo' pin of the HCSR04. // ! NOTE: USE A VOLTAGE DIVIDER FOR THE INPUT (i.e. the Echo pin of the HCSR04) // to go from 5V (which is needed by the HCSR04 module) to 3.3V // output = pin connected to the 'Trig' pin of the HCSR04. HCSR04(uint input, uint output) { // pio 0 is used pio = pio0; // state machine 0 sm = 0; // configure the used pins pio_gpio_init(pio, input); pio_gpio_init(pio, output); // load the pio program into the pio memory uint offset = pio_add_program(pio, &HCSR04_program); // make a sm config pio_sm_config c = HCSR04_program_get_default_config(offset); // set the 'in' pins, also used for 'wait' sm_config_set_in_pins(&c, input); // set the 'jmp' pin sm_config_set_jmp_pin(&c, input); // set the output pin to output pio_sm_set_consecutive_pindirs(pio, sm, output, 1, true); // set the 'set' pins sm_config_set_set_pins(&c, output, 1); // set shift direction sm_config_set_in_shift(&c, false, false, 0); // init the pio sm with the config pio_sm_init(pio, sm, offset, &c); // enable the sm pio_sm_set_enabled(pio, sm, true); } // read the distance to an object in cm (0 cm means invalid measurement) float read(void) { // value is used to read from the sm RX FIFO uint32_t clock_cycles = 0; // clear the FIFO: do a new measurement pio_sm_clear_fifos(pio, sm); // give the sm some time to do a measurement and place it in the FIFO sleep_ms(100); // check that the FIFO isn't empty if (pio_sm_is_rx_fifo_empty(pio, sm)) { return 0; } // read one data item from the FIFO // Note: every test for the end of the echo puls takes 2 pio clock ticks, // but changes the 'timer' by only one clock_cycles = 2 * pio_sm_get(pio, sm); // using // - the time for 1 pio clock tick (1/125000000 s) // - speed of sound in air is about 340 m/s // - the sound travels from the HCSR04 to the object and back (twice the distance) // we can calculate the distance in cm by multiplying with 0.000136 float cm = (float)clock_cycles * 0.000136; return (cm); } private: // the pio instance PIO pio; // the state machine uint sm; }; int main() { // needed for printf stdio_init_all(); // the instance of the HCSR04 (Echo pin = 14, Trig pin = 15) HCSR04 my_HCSR04(14, 15); // infinite loop to print distance measurements while (true) { // read the distance sensor and print the result float cm = my_HCSR04.read(); printf("cm = %f\n", cm); sleep_ms(100); } } ================================================ FILE: HCSR04/HCSR04.pio ================================================ ; Description of the used algorithm ; ; start: ; ; Give a puls to the HCSR04 Trig pin to start the measurement ; According to the datasheet the pulse should be 10us long. ; Assume the Trig pin is currently 0, set it to 1, wait 10 us, set it to 0 ; A length of 10 us, with a clock of 125MHz this is 1250 ticks, or binary: 10011100010. ; Assuming that the 10 us doesn't have to be very precise, round it down to 10011000000. ; The delay can be achieved by: ; ; set x 19 ; set x to 10011 (and clear the higher bits) ; mov ISR x ; copy x to ISR ; in NULL 6 ; shift in 6 more 0 bits ; mov x ISR ; move the ISR to x (which now contains 10011000000) ; delay1: ; jmp x-- delay1 ; count down to 0: a delay of (about) 10 us ; ; This results in a delay of about 10us. ; ; The distance to the object is encoded in the length of the pulse on the Echo pin ; Read the Echo pin (USE A VOLTAGE DIVIDER) wait until the input pulse becomes high ; Set the value 0xFFFFFFFF in x; this is the start of the 'timer'. This can be achieved by ; mov x ~NULL ; ; Now the value in x is decremented in a loop and each time the Echo pin is tested. ; If the Echo pin is 0, the value (0xFFFFFFFF - x) represents the length of the echo pulse. ; Note: each decrement of x and a test of the Echo pin is 2 pio clock cycles. ; Push the bit-inversed value of x, i.e. (0xFFFFFFFF - x) into the Rx FIFO ; ; According to the HCSR04 datasheet, we have to wait for 60ms before starting another measurement ; Use the same trick as above to create a delay. But now with values: ; 7500000 clock cycles = 11100100111000011100000, round down to 11100 + 18 * 0 ; ; Go back to start .program HCSR04 .wrap_target ; give a puls to the HCSR04 Trigger pin set pins 1 ; set the trigger to 1 ; delay for 10 us (the length of the trigger pulse) set x 19 ; set x to 10011 (and clear the higher bits) mov ISR x ; copy x to ISR in NULL 6 ; shift in 6 more 0 bits mov x ISR ; move the ISR to x (which now contains 10011000000) delay1: jmp x-- delay1 ; count down to 0: a delay of (about) 10 us set pins 0 ; make the trigger 0 again, completing the trigger pulse ; wait 1 pin 0 ; wait for the echo pin to rise ; start a counting loop to measure the length of the echo pulse mov x ~NULL ; start with the value 0xFFFFFFFF timer: jmp x-- test ; count down jmp timerstop ; timer has reached 0, stop count down test: jmp pin timer ; test if the echo pin is still 1, if so, continue counting down timerstop: ; echo pulse is over (or timer has reached 0) mov ISR ~x ; move the bit-inversed value in x to the ISR push noblock ; push the ISR into the Rx FIFO ; delay for 60ms (advice from datasheet to prevent triggering on echos) set x 28 ; set x to 11100 mov ISR x ; copy x to ISR in NULL 18 ; shift in 18 more bits mov x ISR ; move the ISR to x delay2: jmp x-- delay2 ; delay (about) 60 ms .wrap ; start over ================================================ FILE: HCSR04/README.md ================================================ # HC-SR04 using the PIO code The HC-SR04 is an ultrasonic distance measurement unit ([datasheet](https://cdn.sparkfun.com/datasheets/Sensors/Proximity/HCSR04.pdf)). Basically, it works by sending a puls to the Trig pin, and measuring the pulse on the Echo pin. The length of the Echo pulse represents the distance to an object in front of the device. The HC-SR04 uses 5V while the Pico uses 3V3. Thus the Echo pin requires a [resistive divider](https://hackaday.com/2016/12/05/taking-it-to-another-level-making-3-3v-and-5v-logic-communicate-with-level-shifters/) to be save. ## The algorithm The pio code to read the HC-SR04 has the following steps: * Give a pulse on the Trig pin of the HC-SR04 to start the measurement. The datasheet indicates that the length of this pulse should be 10 us * Measure the length of the pulse on the Echo pin * Wait 60 ms before making a new measurement to be sure that no echos from previous measurements are found Setting and reading pins are (if you've done it before) straight forward, but making and reading pulses of specific length weren't for me. Making the HC-SR04 algorithm work involves two types of timing: * a delay to make the trigger pulse * measuring the length of the echo pulse * a delay to ensure no echos of a previous measurement are received The PIO doesn't seem to have timers, but each instruction takes one pio clock cycle, and these can be used to measure time. ## Delay in PIO According to the datasheet, the trigger pulse should be 10 us. The pio clock runs at 125 MHz, so, the pulse should be 1250 cycles long. Here it is assumed that the clock divider is set to 1. If this has a different value, some of the timing calculations below will change. Creating the required delay can be achieved in the following way: * 1250 cycles in binary is 10011100010 * assuming the pulse width doesn't need to be very precise, this can be rounded down to 10011000000 * this can be split into the 5 most significant digits (10011) and 6 additional 0 bits * place this number in the x register in the following way (note that for this to work correctly, the shift direction needs to be set with`sm_config_set_in_shift(&c, false, false, 0);`): ``` pio set x 19 ; set x to 10011 (and clear the higher bits) mov ISR x ; copy x to ISR in NULL 6 ; shift in 6 more 0 bits mov x ISR ; move the ISR to x (which now contains 10011000000) ``` * Count down to 0 in a tight loop using: ``` pio delay1: jmp x-- delay1 ``` This results in a delay of almost 10 us. This same approach can be used to create a clock-cycle precise delay for any number between 0x00000000 and 0xFFFFFFFF. This may involve setting x and shifting it into the ISR, 5 bits at a time, several times. If the timing doesn't have to be precise, rounding down (or up) after the 5 most significant bits and shifting in further 0's, as done above, can save instructions. The second delay that is needed is 60 ms. The same trick as above is used to create this delay. But now with the following values: 7500000 clock cycles are needed. In binary this is 11100100111000011100000. This is rounded down to 11100 + 18 * 0. ## Measuring duration of a pulse To measure the duration of some event, in this case the length of the Echo pulse which represents the distance to an object, can be done by starting the x (or y) scratch register with 0xFFFFFFFF and counting down, testing for the stop criterion each iteration. The counting down loop with test for the stop criterion looks like this: ```pio timer: jmp x-- test ; count down jmp timerstop ; timer has reached 0, stop count down test: jmp pin timer ; test if the echo pin is still 1, if so, continue counting down timerstop: ; echo pulse is over (or timer has reached 0) mov ISR ~x ; move the bit-inverted value of x to the ISR push noblock ; push the ISR into the RX FIFO ``` If x isn't 0, the statement `jmp x-- test` jumps to testing for the stop criterion (the Echo pin going low). If the stop criterion is true, or if the x register is 0 when arriving at the jmp statement, the bit-inverted value of x is moved to the ISR, which makes it positive, and is then pushed to the Rx FIFO. ## Calculation of distance In the C/C++ code the value in the x scratch register is received. Each loop has two instructions (`jmp` with decrement, and `jmp` testing the pin) the amount of pio clock cycles becomes `2 * pio_sm_get(pio, sm)`. For the calculation of the distance three more things are needed: * one pio clock cycle takes 1 / 125 MHz = 0.000000008 s * The speed of sound in air is about 340 m/s = 34000 cm/s * The sound travels from the HC-SR04 to the object and back (twice the distance) If those are factored in, the code to calculate the distance in cm becomes: ``` c clock_cycles = 2*pio_sm_get(pio, sm); cm = (float)clock_cycles * 0.000136; ``` ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2021 GitJer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Limited_1_wire/CMakeLists.txt ================================================ add_executable(onewire) pico_generate_pio_header(onewire ${CMAKE_CURRENT_LIST_DIR}/onewire.pio) target_sources(onewire PRIVATE onewire.cpp) target_link_libraries(onewire PRIVATE pico_stdlib hardware_pio ) pico_add_extra_outputs(onewire) # add url via pico_set_program_url example_auto_set_url(onewire) ================================================ FILE: Limited_1_wire/README.md ================================================ # 1-wire protocol for one device This code is a pio implementation of the [1-wire protocol](https://en.wikipedia.org/wiki/1-Wire). The C++ implementation is rather limited: it is meant for only one device (the DS18B20 temperature sensor) and uses the default settings (12-bit conversion). To make this I have used the [datasheet](https://datasheets.maximintegrated.com/en/ds/DS18B20.pdf) as well as the implementation of [Paul Stoffregen](https://github.com/PaulStoffregen/OneWire) and [this pio implementation](https://www.raspberrypi.org/forums/viewtopic.php?t=304511#p1851030). One of the interesting elements is that it uses the same pin for set, out, sideset, out, and in! (I should have included a 'jmp pin' somewhere. :) ================================================ FILE: Limited_1_wire/onewire.cpp ================================================ /* This code uses pio code to read a single DS18B20 temperature sensor. It is not suited to read more than one sensor: it does not use the parts of the 1-wire protocol that allowes more than one sensor. */ #include #include "pico/stdlib.h" #include "hardware/pio.h" #include "onewire.pio.h" // the pin to which the sensor is connected. // Note that an external pullup resistor of 4.7k is needed on this pin #define OW_PIN 15 // --------------------------------------------------------------------------------- // The 1-Wire CRC scheme is described in Maxim Application Note 27: // "Understanding and Using Cyclic Redundancy Checks with Maxim iButton Products" // // Dow-CRC using polynomial X^8 + X^5 + X^4 + X^0 // Tiny 2x16 entry CRC table created by Arjen Lentz // See http://lentz.com.au/blog/calculating-crc-with-a-tiny-32-entry-lookup-table // // Copied from https://github.com/PaulStoffregen/OneWire/blob/master/OneWire.cpp static const uint8_t dscrc2x16_table[] = { 0x00, 0x5E, 0xBC, 0xE2, 0x61, 0x3F, 0xDD, 0x83, 0xC2, 0x9C, 0x7E, 0x20, 0xA3, 0xFD, 0x1F, 0x41, 0x00, 0x9D, 0x23, 0xBE, 0x46, 0xDB, 0x65, 0xF8, 0x8C, 0x11, 0xAF, 0x32, 0xCA, 0x57, 0xE9, 0x74}; // --------------------------------------------------------------------------------- class OneWire { public: OneWire(uint onewire_pin) { pio = pio0; // state machine 0 sm = 0; // configure the used pins pio_gpio_init(pio, OW_PIN); // load the pio programs into the pio memory offset_wait = pio_add_program(pio, &onewire_wait_program); offset_reset = pio_add_program(pio, &onewire_reset_program); offset_write_byte = pio_add_program(pio, &onewire_write_byte_program); offset_read_byte = pio_add_program(pio, &onewire_read_byte_program); // make a sm config pio_sm_config c = pio_get_default_sm_config(); // set the set pin sm_config_set_set_pins(&c, OW_PIN, 1); // set the out pin sm_config_set_out_pins(&c, OW_PIN, 1); // configure side set to be 1 pin, optional (hence 2 bits) and controls pindirs sm_config_set_sideset(&c, 2, true, true); sm_config_set_sideset_pins(&c, OW_PIN); // set the in pin sm_config_set_in_pins(&c, OW_PIN); // set shift to right: bits shifted by 'in' are ordered as least // significant bit (LSB) first, no autopush/pull sm_config_set_in_shift(&c, true, false, 0); sm_config_set_out_shift(&c, true, false, 0); // one clock cycle is 10 us sm_config_set_clkdiv(&c, 1250); // init the pio sm with the config, start with the wait program pio_sm_init(pio, sm, offset_wait, &c); // enable the sm pio_sm_set_enabled(pio, sm, true); } int reset() { // start the reset program and check if a sensor (worker) responds pio_sm_exec(pio, sm, offset_reset); // read the return value: 0 means there are at least one workers int no_workders_detected = pio_sm_get_blocking(pio, sm); if (no_workders_detected == 1) // indeed: no workers detected return -1; return 1; } int reset_and_check() { // this method checks if there are workers (i.e. DS18B20 sensors) // and then - assuming only one is present - checks if it can read // this sensor properly by asking for its unique ID and doing a CRC. // start the reset program pio_sm_exec(pio, sm, offset_reset); // read the return value: 0 means there are (at least one) workers int no_workders_detected = pio_sm_get_blocking(pio, sm); if (no_workders_detected == 1) // indeed: no workers detected return -1; else { // for stability reasons give it some time sleep_ms(10); // send the command to get the address of the worker send_byte(0x33); // for stability reasons give it some time sleep_ms(10); // read the results read_bytes(8); // the eighth byte is the crc if (crc8(results, 7) != results[7]) { printf("crc of ROM is wrong!!!!!!!!!!!\n"); return -1; } return 1; } } void send_byte(uint32_t to_send) { // put the byte in the TxFIFO pio_sm_put(pio, sm, to_send); // start the sm program that writes a byte pio_sm_exec(pio, sm, offset_write_byte); } uint32_t read_byte() { // start the sm program that reads a byte pio_sm_exec(pio, sm, offset_read_byte); // wait for the result return pio_sm_get_blocking(pio, sm) >> 24; } void read_bytes(int n) { // read n bytes from the sensor for (int i = 0; i < n; i++) results[i] = (uint8_t)read_byte() & 0xFF; } float read_temperature() { // put the sensor in a known state int workers = reset(); if (workers < 0) return -1; // for stability reasons give it some time sleep_ms(10); // address the family (not one specific sensor) send_byte(0xCC); // for stability reasons give it some time sleep_ms(5); // ask for a temperature conversion send_byte(0x44); // a temperature conversion at 12 bit resolution takes 750ms sleep_ms(800); // put the sensor in a known state workers = reset(); if (workers < 0) return -1; // for stability reasons give it some time sleep_ms(10); // address the family (not one specific sensor) send_byte(0xCC); // for stability reasons give it some time sleep_ms(5); // ask for the results send_byte(0xBE); // for stability reasons give it some time sleep_ms(10); // read the results read_bytes(9); // the ninth byte is the crc if (crc8(results, 8) != results[8]) { printf("crc of data is wrong!!!!!!!!!!!\n"); return -1; } // convert the temperature results to a float // the bits in results[0] indicate temperature as follows: // bit 7 to 0: 2^3, 2^2, 2^1, 2^0, 2^-1, 2^-2, 2^-3, 2^-4 uint8_t Tlow = results[0]; float T = 0; for (int bit = 0; bit < 8; bit++) if ((Tlow & 1 << bit) > 0) T += 1 << bit; // above counting started as if 2^-4 is 1, so correct by dividing by 16 T /= 16.; // the bits in results[1] indicate temperature as follows: // bit 7 to 0: S, S, S, S, S, 2^6, 2^5, 2^4; where S = sign // note that negative numbers start at -128 uint8_t Thigh = results[1]; for (int bit = 0; bit < 3; bit++) if ((Thigh & 1 << bit) > 0) T += 1 << (bit + 4); // check the sign using bit 3 if ((Thigh & 1 << 3) > 0) T = -128.+T; return T; } // --------------------------------------------------------------------------------- // Copied (and adapted) from https://github.com/PaulStoffregen/OneWire/blob/master/OneWire.cpp uint8_t crc8(const uint8_t *addr, uint8_t len) { uint8_t crc = 0; while (len--) { crc = *addr++ ^ crc; // just re-using crc as intermediate crc = dscrc2x16_table[(crc & 0x0f)] ^ dscrc2x16_table[16 + ((crc >> 4) & 0x0f)]; } return crc; } // --------------------------------------------------------------------------------- private: // the pio instance PIO pio; // the state machine uint sm; // the offsets (origins of the PIO programs) uint offset_wait; uint offset_reset; uint offset_write_byte; uint offset_read_byte; // the result of reading the sensor uint8_t results[9]; }; int main() { // needed for printf stdio_init_all(); // the instance of the OneWire OneWire DS18B20(OW_PIN); // reset and determine if there are workers int workers = DS18B20.reset_and_check(); if (workers > 0) while (true) printf("Temperature = %f\n", DS18B20.read_temperature()); else while (true) ; } ================================================ FILE: Limited_1_wire/onewire.pio ================================================ ; ; It is assumed that an external pull up resistor (4.7k) is present on the pin with the OneWire sensor ; ; ------------------------------------------------------ ; WAIT ; When starting the sm, it should do nothing. .program onewire_wait onewire_wait: jmp onewire_wait ; ------------------------------------------------------ ; RESET .program onewire_reset ; side-set controls the direction .side_set 1 opt pindirs onewire_reset: ; side_set to input and wait until the line goes high, just in case it isn't already 1 wait 1 PIN 0 side 0 ; side_set to output and pull line low and delay for > 480 us set PINS 0 [7] side 1 ; wait > 480 us (at 10us per step), here use 490 us set x 4 [5] reset_wait_loop: jmp x-- reset_wait_loop [6] ; side_set to input, delay for 80us nop [7] side 0 ; read the value of the line (1 = no slaves, 0 = at least one slave) in PINS 1 push ; wait until the line goes high after the slaves release the line wait 1 PIN 0 ; end by doing nothing in a loop reset_stop: jmp reset_stop ; ------------------------------------------------------ ; WRITE BYTE .program onewire_write_byte .side_set 1 opt pindirs ; get the byte to send pull ; set counter to 8 bits set x 7 write_byte_loop: ; master pulls low for 10 us set PINS 0 side 1 ; write whatever bit is shifted out of the OSR, keep it for 40us out PINS 1 [3] ; the last part (10 us) must be high TODO: jmp also takes 10 us (would not be needed) set PINS 1 ; do 8 bits in total jmp x-- write_byte_loop ; end by doing nothing in a loop write_byte_stop: jmp x-- write_byte_stop ; ------------------------------------------------------ ; READ BYTE .program onewire_read_byte .side_set 1 opt pindirs ; set counter to 8 bits set x 7 ; clear the ISR mov ISR NULL read_byte_loop: ; master pulls low for 10 us set PINS 0 side 1 ; set master to read (instruction takes 10 us) nop side 0 ; sample the line and shift right into the ISR (LSB is read first) in PINS 1 ; there is still some time to wait: about 50 us nop [4] ; do 8 bits in total jmp x-- read_byte_loop ; push it in the RxFIFO push ; end by doing nothing in a loop read_byte_stop: jmp read_byte_stop ================================================ FILE: PwmIn/CMakeLists.txt ================================================ add_executable(PwmIn) pico_generate_pio_header(PwmIn ${CMAKE_CURRENT_LIST_DIR}/PwmIn.pio) target_sources(PwmIn PRIVATE PwmIn.cpp) target_link_libraries(PwmIn PRIVATE pico_stdlib hardware_pio ) pico_add_extra_outputs(PwmIn) # add url via pico_set_program_url example_auto_set_url(PwmIn) ================================================ FILE: PwmIn/PwmIn.cpp ================================================ #include #include "pico/stdlib.h" #include "hardware/pio.h" #include "PwmIn.pio.h" // class that sets up and reads PWM pulses: PwmIn. It has three functions: // read_period (in seconds) // read_pulsewidth (in seconds) // read_dutycycle (between 0 and 1) // read pulsewidth, period, and calculate the dutycycle class PwmIn { public: // constructor // input = pin that receives the PWM pulses. PwmIn(uint input) { // pio 0 is used pio = pio0; // state machine 0 sm = 0; // configure the used pins pio_gpio_init(pio, input); // load the pio program into the pio memory uint offset = pio_add_program(pio, &PwmIn_program); // make a sm config pio_sm_config c = PwmIn_program_get_default_config(offset); // set the 'jmp' pin sm_config_set_jmp_pin(&c, input); // set the 'wait' pin (uses 'in' pins) sm_config_set_in_pins(&c, input); // set shift direction sm_config_set_in_shift(&c, false, false, 0); // init the pio sm with the config pio_sm_init(pio, sm, offset, &c); // enable the sm pio_sm_set_enabled(pio, sm, true); } // read_period (in seconds) float read_period(void) { read(); // one clock cycle is 1/125000000 seconds return (period * 0.000000008); } // read_pulsewidth (in seconds) float read_pulsewidth(void) { read(); // one clock cycle is 1/125000000 seconds return (pulsewidth * 0.000000008); } // read_dutycycle (between 0 and 1) float read_dutycycle(void) { read(); return ((float)pulsewidth / (float)period); } // read pulsewidth and period for one pulse void read_PWM(float *readings) { read(); *(readings + 0) = (float)pulsewidth * 0.000000008; *(readings + 1) = (float)period * 0.000000008; *(readings + 2) = ((float)pulsewidth / (float)period); } private: // read the period and pulsewidth void read(void) { // clear the FIFO: do a new measurement pio_sm_clear_fifos(pio, sm); // wait for the FIFO to contain two data items: pulsewidth and period while (pio_sm_get_rx_fifo_level(pio, sm) < 2) ; // read pulse width from the FIFO pulsewidth = pio_sm_get(pio, sm); // read period from the FIFO period = pio_sm_get(pio, sm) + pulsewidth; // the measurements are taken with 2 clock cycles per timer tick pulsewidth = 2 * pulsewidth; // calculate the period in clock cycles: period = 2 * period; } // the pio instance PIO pio; // the state machine uint sm; // data about the PWM input measured in pio clock cycles uint32_t pulsewidth, period; }; int main() { // needed for printf stdio_init_all(); // the instance of the PwmIn PwmIn my_PwmIn(14); // the array with the results float pwm_reading[3]; // infinite loop to print PWM measurements while (true) { my_PwmIn.read_PWM(pwm_reading); if (pwm_reading[0] >= 0.) { printf("pw=%.8f \tp=%.8f \tdc=%.8f\n", pwm_reading[0], pwm_reading[1], pwm_reading[2]); } sleep_ms(100); } } ================================================ FILE: PwmIn/PwmIn.pio ================================================ .program PwmIn ; algorithm: ; loop: ; reset y, the 'timer' for the pulsewidth (high period) ; reset x, the 'timer' for the low period. (high + low = period) ; wait for a 0, then wait for a 1: this is the rising edge ; loop: ; decrement y timer ; test for falling edge ; y timer value is the pulse width (actually, (0xFFFFFFFF - y)*2/125MHz is the pulse width) ; loop: ; test for rising edge ; decrement x timer ; x timer is the low period (actually, (0xFFFFFFFF - x)*2/125MHz is the low period) ; push both y and x to the Rx FIFO .wrap_target start: mov y ~NULL ; start with the value 0xFFFFFFFF mov x ~NULL ; start with the value 0xFFFFFFFF wait 0 pin 0 ; wait for a 0 wait 1 pin 0 ; wait for a 1, now we really have the rising edge timer_hp: ; loop for high period jmp y-- test ; count down for pulse width jmp start ; timer has reached 0, stop count down of pulse, restart test: jmp pin timer_hp ; test if the pin is still 1, if so, continue counting down timer_lp: ; loop for low period jmp pin timerstop ; if the pin has become 1, the period is over, stop count down jmp x-- timer_lp ; if not: count down jmp start ; timer has reached 0, stop count down of low period, restart timerstop: mov ISR ~y ; move the value ~y to the ISR: the high period (pulsewidth) (0xFFFFFFFF-y) push noblock ; push the ISR into the Rx FIFO mov ISR ~x ; move the value ~x to the ISR: the low period (0xFFFFFFFF-x) push noblock ; push the ISR into the Rx FIFO .wrap ================================================ FILE: PwmIn/PwmIn_4pins/CMakeLists.txt ================================================ add_executable(PWM4) pico_generate_pio_header(PWM4 ${CMAKE_CURRENT_LIST_DIR}/PwmIn.pio) target_sources(PWM4 PRIVATE PWM4.cpp PwmIn.cpp) target_link_libraries(PWM4 PRIVATE pico_stdlib hardware_pio hardware_pwm hardware_gpio ) pico_add_extra_outputs(PWM4) ================================================ FILE: PwmIn/PwmIn_4pins/PWM4.cpp ================================================ #include #include "pico/stdlib.h" #include "pico/time.h" #include "PwmIn.h" #define NUM_OF_PINS 4 int main() { // needed for printf stdio_init_all(); printf("PwmIn on 4 pins\n"); // set PwmIn uint pin_list[NUM_OF_PINS] = {14, 15, 18, 19}; PwmIn my_PwmIn(pin_list, NUM_OF_PINS); while (true) { // adviced empty (for now) function of sdk tight_loop_contents(); // translate pwm of input to output float PW_0 = my_PwmIn.read_PW(0); float PW_1 = my_PwmIn.read_PW(1); float PW_2 = my_PwmIn.read_PW(2); float PW_3 = my_PwmIn.read_PW(3); printf("PW_0=%f PW_1=%f PW_2=%f PW_3=%f\n", PW_0, PW_1, PW_2, PW_3); sleep_ms(100); } } ================================================ FILE: PwmIn/PwmIn_4pins/PwmIn.cpp ================================================ #include #include "pico/stdlib.h" #include "hardware/pio.h" #include "hardware/irq.h" #include "PwmIn.h" #include "PwmIn.pio.h" // class that reads PWM pulses from up to 4 pins PwmIn::PwmIn(uint *pin_list, uint num_of_pins) { _num_of_pins = num_of_pins; // pio 0 is used pio = pio0; // load the pio program into the pio memory uint offset = pio_add_program(pio, &PwmIn_program); // start num_of_pins state machines for (int i = 0; i < num_of_pins; i++) { // prepare state machine i pulsewidth[i] = 0; period[i] = 0; // configure the used pins (pull down, controlled by PIO) gpio_pull_down(pin_list[i]); pio_gpio_init(pio, pin_list[i]); // make a sm config pio_sm_config c = PwmIn_program_get_default_config(offset); // set the 'jmp' pin sm_config_set_jmp_pin(&c, pin_list[i]); // set the 'wait' pin (uses 'in' pins) sm_config_set_in_pins(&c, pin_list[i]); // set shift direction sm_config_set_in_shift(&c, false, false, 0); // init the pio sm with the config pio_sm_init(pio, i, offset, &c); // enable the sm pio_sm_set_enabled(pio, i, true); } // set the IRQ handler irq_set_exclusive_handler(PIO0_IRQ_0, pio_irq_handler); // enable the IRQ irq_set_enabled(PIO0_IRQ_0, true); // allow irqs from the low 4 state machines pio0_hw->inte0 = PIO_IRQ0_INTE_SM0_BITS | PIO_IRQ0_INTE_SM1_BITS | PIO_IRQ0_INTE_SM2_BITS | PIO_IRQ0_INTE_SM3_BITS ; }; // read the period and pulsewidth void PwmIn::read_PWM(float *readings, uint pin) { if (pin < _num_of_pins) { // determine whole period period[pin] += pulsewidth[pin]; // the measurements are taken with 2 clock cycles per timer tick // hence, it is 2*0.000000008 *(readings + 0) = (float)pulsewidth[pin] * 2 * 0.000000008; *(readings + 1) = (float)period[pin] * 2 * 0.000000008; *(readings + 2) = ((float)pulsewidth[pin] / (float)period[pin]); pulsewidth[pin] = 0; period[pin] = 0; } }; // read only the duty cycle float PwmIn::read_DC(uint pin) { return ((float)pulsewidth[pin] / (float)period[pin]); } // read only the period float PwmIn::read_P(uint pin) { // the measurements are taken with 2 clock cycles per timer tick // hence, it is 2*0.000000008 return ((float)period[pin] * 0.000000016); } float PwmIn::read_PW(uint pin) { // the measurements are taken with 2 clock cycles per timer tick // hence, it is 2*0.000000008 return ((float)pulsewidth[pin] * 0.000000016); }; uint32_t PwmIn::pulsewidth[4]; uint32_t PwmIn::period[4]; PIO PwmIn::pio; ================================================ FILE: PwmIn/PwmIn_4pins/PwmIn.h ================================================ #ifndef PwmIn_H #define PwmIn_H #include #include "pico/stdlib.h" #include "hardware/pio.h" #include "PwmIn.h" #include "PwmIn.pio.h" // class that reads PWM pulses on max 4 pins class PwmIn { public: // constructor PwmIn(uint *pin_list, uint num_of_pin); // read pulsewidth and period for one pulse void read_PWM(float *readings, uint pin); // read only the pulsewidth float read_PW(uint pin); // read only the duty cycle float read_DC(uint pin); // read only the period float read_P(uint pin); private: // set the irq handler static void pio_irq_handler() { int state_machine = -1; // check which IRQ was raised: for (int i = 0; i < 4; i++) { if (pio0_hw->irq & 1<irq = 1 << i; // read pulse width from the FIFO pulsewidth[i] = pio_sm_get(pio, i); // read low period from the FIFO period[i] = pio_sm_get(pio, i); // clear interrupt pio0_hw->irq = 1 << i; } } } // the pio instance static PIO pio; // the pins and number of pins uint _num_of_pins; // data about the PWM input measured in pio clock cycles static uint32_t pulsewidth[4], period[4]; }; #endif ================================================ FILE: PwmIn/PwmIn_4pins/PwmIn.pio ================================================ .program PwmIn ; algorithm: ; loop: ; reset y, the 'timer' for the pulsewidth (high period) ; reset x, the 'timer' for the low period. (high + low = period) ; wait for a 0, then wait for a 1: this is the rising edge ; loop: ; decrement y timer ; test for falling edge ; y timer value is the pulse width (actually, (0xFFFFFFFF - x)*2/125MHz is the pulse width) ; loop: ; test for rising edge ; decrement x timer ; x timer is the low period (actually, (0xFFFFFFFF - x)*2/125MHz is the low period) ; push both y and x to the Rx FIFO ; notify via relative IRQ .wrap_target start: mov y ~NULL ; start with the value 0xFFFFFFFF mov x ~NULL ; start with the value 0xFFFFFFFF wait 0 pin 0 ; wait for a 0 wait 1 pin 0 ; wait for a 1, now we really have the rising edge timer_hp: ; loop for high period jmp y-- test ; count down for pulse width jmp start ; timer has reached 0, stop count down of pulse, restart test: jmp pin timer_hp ; test if the pin is still 1, if so, continue counting down timer_lp: ; loop for low period jmp pin timerstop ; if the pin has become 1, the period is over, stop count down jmp x-- timer_lp ; if not: count down jmp start ; timer has reached 0, stop count down of low period, restart timerstop: mov ISR ~y ; move the value ~y to the ISR: the high period (pulsewidth) (0xFFFFFFFF-x) push noblock ; push the ISR into the Rx FIFO mov ISR ~x ; move the value ~x to the ISR: the low period (0xFFFFFFFF-x) push noblock ; push the ISR into the Rx FIFO irq 0 rel ; notify the c-program via IRQ .wrap ================================================ FILE: PwmIn/README.md ================================================ # PWM input using the Raspberry Pi Pico PIO # UPDATE: There was a problem with getting the PwmIn to read more than one pin. So, I've made an update. This update can read pwm signals from up to 4 pins (you could probably go up to 8 if pio1 is also used). It uses (relative) irq in the pio code to signal the c-code that new data is available. See the directory PwmIn_4pins. # ORIGINAL: Most microcontrollers have hardware to produce Pulse Width Modulation (PWM) signals. But sometimes it is useful to be able to read PWM signals and determine the period, pulse width and duty cycle. (Warning: questionable statement) Although the RP2040 seems to be capable of measuring pulse width and period, this takes more CPU cycles than letting a PIO state machine take care of it. Based on the method to measure pulses with PIO code as described [here](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/HCSR04), a PWM Input can be made. ## Algorithm In pseudo-code the algorithm is as follows: ``` loop: reset y, the 'timer' for the pulsewidth (high period) reset x, the 'timer' for the low period. (high + low = period) wait for a 0, then wait for a 1: this is the rising edge loop: decrement y timer test for falling edge y timer value is the pulse width (actually, (0xFFFFFFFF - y)*2/125MHz is the pulse width) loop: test for rising edge decrement x timer x timer is the low period (actually, (0xFFFFFFFF - x)*2/125MHz is the low period) push both y and x to the Rx FIFO ``` ================================================ FILE: README.md ================================================ # Content This repository contains bits and pieces that I made while trying to figure out how the Raspberry Pi Pico state machines and the Programmable Input/Output (PIO) work. [Here](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/handy_bits_and_pieces) I have written down the reusable 'tricks' I use in several of the projects listed below. The following projects are contained in this repository: ## State machine emulator The problem with the state machines is that debuggers do not give the insight I need when writing code for a sm. I typically write some code, upload it to the pico and find that it doesn't do what I want it to do. Instead of guessing what I do wrong, I would like to see the values of e.g. the registers when the sm is executing. So, I made an [emulator](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/state_machine_emulator). ## Two independently running state machines [This](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/Two_sm_simple) is just an example of two state machines running independently. Nothing special about it, but I had to do it. ## Two independently running state machines, one gets disabled temporarily [This](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/Two_sm_one_disabled) is an example of two state machines running independently, but one gets disabled temporarily. ## Two independently running state machines, synchronized via irq, one gets disabled temporarily [This](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/Two_sm_one_disabled_with_irq) is an example of two state machines synchronized via setting and clearing an irq, one gets disabled temporarily. ## State machine writes into a buffer via DMA [This](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/sm_to_dma_to_buffer) is an example of a state machine using DMA (Direct Memory Access) to write into a buffer. ## State Machine -> DMA -> State Machine -> DMA -> Buffer [This](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/sm_to_dma_to_sm_to_dma_to_buffer) is an example where one state machine writes via DMA to another state machine whose output is put into a buffer via another DMA channel. ## Communicating values between state machines The [RP2040 Datasheet](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) states that "State machines can not communicate data". Or can they ... Yes they can, in several ways, [including via GPIO pins](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/Value_communication_between_two_sm_via_pins). ## Use the ISR for rotational shifting Normally if the ISR shifts via the `IN` instruction, the bits that come out of the ISR go to cyber space, never to be heard from again. Sometimes it is handy to have rotational shifting. [Right shifting works fine, but left shifting needs some trickery](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/Rotational_shift_ISR). ## 4x4 button matrix using PIO code [This code](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/button_matrix_4x4) reads a 4x4 button matrix using PIO code for the Raspberry Pico and returns the button pressed. ## Button debouncer using PIO code When using a GPIO to read noisy input, such as a mechanical button, a [software debouncer](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/Button-debouncer) makes sure that only after the input signal has stabilized, the code will read the new value. ## PWM input using PIO code Most microcontrollers have hardware to produce Pulse Width Modulation (PWM) signals. But sometimes it is useful to be able to [read PWM signals](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/PwmIn) and determine the period, pulse width and duty cycle. ## Rotary encoder using PIO code [This software](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/Rotary_encoder) reads an optical rotary encoder with very clean signals on its output using PIO code. ## HC-SR04 using the PIO code [This code](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/HCSR04) reads the HC-SR04, an ultrasonic distance measurement unit. ## Ws2812 led strip with 120 pixels [This code](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/ws2812_led_strip_120) is my take on how to control a ws2812 led strip with 120 pixels ## multiply two numbers [This code](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/multiplication) multiplies two numbers. ## Two pio programs in one file I wanted to see how I could use two pio programs in one file and use them from within the c/c++ program, see [here.](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/two_pio_programs_one_file) ## 1-wire protocol for one device [This code](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/Limited_1_wire) is a pio implementation of the 1-wire protocol. ## Blow out a(n) LED [This code](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/blow_out_a_LED) is a remake of the wonderfull little thingy made by Paul Dietz: blow on a LED to make it go out! Really! ## Counting pulses in a pulse train separated by a pause [This code](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/count_pulses_with_pause) can be used for protocols where the data is encoded by a number of pulses in a pulse train followed by a pause. E.g. the LMT01 temperature sensor uses this, [see](https://www.reddit.com/r/raspberrypipico/comments/nis1ew/made_a_pulse_counter_for_the_lmt01_temperature/). ## Subroutines in pioasm [This code](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/subroutines) shows that subroutines in pioasm can be a thing and can - in some cases - be used to do more with the limited memory space than is possible with just writing the code in one program. ## Read the SBUS protocol with (and without!) pio code The SBUS protocol is typically used in Radio Controlled cars, drones, etc. If you want to read this protocol from a RC receiver in order to manipulate the data before setting motors and servos, you can use [this code](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/SBUS). ## LED panel using PIO state machine and Direct Memory Access [This code](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/ledpanel) shows how a pio state machine can drive led panels. It is made for two 64x64 led panels connected to form a 64 row x 128 column panel. There are 16 brightness levels for each Red, Green and Blue of each pixel, allowing many colors. In its present form it updates the panel at about 144 Hz at standard clock settings (=125MHz.) ================================================ FILE: Rotary_encoder/CMakeLists.txt ================================================ add_executable(pio_rotary_encoder) pico_generate_pio_header(pio_rotary_encoder ${CMAKE_CURRENT_LIST_DIR}/pio_rotary_encoder.pio) target_sources(pio_rotary_encoder PRIVATE pio_rotary_encoder.cpp) target_link_libraries(pio_rotary_encoder PRIVATE pico_stdlib hardware_pio ) pico_enable_stdio_usb(pio_rotary_encoder 0) pico_enable_stdio_uart(pio_rotary_encoder 1) pico_add_extra_outputs(pio_rotary_encoder) # add url via pico_set_program_url example_auto_set_url(pio_rotary_encoder) ================================================ FILE: Rotary_encoder/README.md ================================================ # Rotary encoder for Raspberry Pi Pico using PIO code This software reads a rotary encoder with the Raspberry Pi Pico using PIO code. The rotary encoder that is used is an optical encoder with very clean signals on its output, called A and B, so debouncing of these signals is unnecessary. The specific encoder I use gives 600 pulses per 360 degrees rotation. For each pulse, 4 signal changes are measured, see the next figure from [wikipedia](https://en.wikipedia.org/wiki/Rotary_encoder#/media/File:Quadrature_Diagram.svg), so in total 2400 transitions are made for a full rotation. I have also tried a cheap simple rotary encoder, but only when turning very slowly does the result make any sense. ## Other code This isn't the first code to read a rotary encoder using the PIO, see e.g. [pimoroni-pico](https://github.com/pimoroni/pimoroni-pico/blob/encoder-pio/drivers/encoder-pio/encoder.pio), which does timing and rudimentary debouncing. And it lets you choose non-consecutive pins. But this code uses interrupts in the PIO code to signal rotations to the C++ code. And maybe this can best be considered as an exercise on how to make something useful with: - 19 `jmp` instructions (one hidden in a `mov exec`) - 3 `in` instructions - 2 `irq` instructions - 1 `out` instruction - 1 `mov` instruction (well ... technically 2) But seriously, I wanted to play with a jump table in PIO code. This means setting the `.origin` to make sure the jump table is at a fixed position in instruction memory (0), and setting the initial program counter with `pio_sm_init(pio, sm, 16, &c)` to start at instruction location 16, i.e. after the jump table. It also means that the `mov exec` instruction is used to make a jump to the jump table. ## Explanation of PIO code The first 15 addresses are a list of jumps, forming a jump table, that is later used to raise either an IRQ that signals a clockwise rotation or an IRQ to signal a counter clockwise rotation. See below. The important steps are explained in the figure below. There the program line number, the contents of the Output Shift Register (OSR) and the Input Shift Register (ISR) are shown. The program starts at line 16, which is only used as an initialization of the program: the two pins (whose values are denoted as A and B) of the rotary encoder are read into the ISR. The two rightmost bits are now AB. The other bits in the ISR do not play a role at this moment and are denoted as 'x'. The next step, line 17, is the actual start of the program: the content of the ISR is copied to the OSR. The two bits AB are to be used as the previous values, and are therefore denoted as A' and B'. Line 18 clears the ISR because the zero valued bits are needed later. Then, in two steps, the ISR gets shifted in the old values, A'B' from the OSR, and reads in two new readings of the rotary encoder (AB). Now, at line 20, the 16 left most bits of the ISR contain: 000000000000A'B'AB. According to the [datasheet of the rp2040](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) an unconditional jmp instruction without delay or side-set is, apart from the address to jump to, all zeros. And that is exactly what is now contained in the ISR: a jmp instruction to address 0A'B'AB. This is always an address in the first 16 program addresses (0-15). The PIO has an instruction to interpret the content of the ISR as an instruction and execute it: on line 20 the `mov exec ISR` does exactly this. ## Jump table The first 16 instructions form a jump table. The addresses represent the 12 legal transitions and 4 error transitions that can be found in the output of the rotary encoder. As described above, the address that is jumped to is represented by the two old values and the two current values of the output: A'B'AB. The 16 transitions are: ``` A'B'A B = meaning; action -------------------------------------------------------------------------------------- 0 0 0 0 = transition from 00 to 00 = no change in reading; do a jump to line 17 0 0 0 1 = transition from 00 to 01 = clockwise rotation; do a jump to CW 0 0 1 0 = transition from 00 to 10 = counter clockwise rotation; do a jump to CCW 0 0 1 1 = transition from 00 to 11 = error; do a jump to line 17 0 1 0 0 = transition from 01 to 00 = counter clockwise rotation; do a jump to CCW 0 1 0 1 = transition from 01 to 01 = no change in reading; do a jump to line 17 0 1 1 0 = transition from 01 to 10 = error; do a jump to line 17 0 1 1 1 = transition from 01 to 11 = clockwise rotation; do a jump to CW 1 0 0 0 = transition from 10 to 00 = clockwise rotation; do a jump to CW 1 0 0 1 = transition from 10 to 01 = error; do a jump to line 17 1 0 1 0 = transition from 10 to 10 = no change in reading; do a jump to line 17 1 0 1 1 = transition from 10 to 11 = counter clockwise rotation; do a jump to CCW 1 1 0 0 = transition from 11 to 00 = error; do a jump to line 17 1 1 0 1 = transition from 11 to 01 = counter clockwise rotation; do a jump to CCW 1 1 1 0 = transition from 11 to 10 = clockwise rotation; do a jump to CW 1 1 1 1 = transition from 11 to 11 = no change in reading; do a jump to line 17 ``` The jump to `CW` is a piece of code that sets `irq 0` and then jumps to line 17, the jump to `CCW` sets `irq 1` and then jumps to line 17. In the C++ code, the `irq 0` causes a counter called `rotation` to be increased, the `irq 1` causes it to be decreased. ================================================ FILE: Rotary_encoder/pio_rotary_encoder.cpp ================================================ #include #include "pico/stdlib.h" #include "hardware/pio.h" #include "hardware/irq.h" #include "pio_rotary_encoder.pio.h" // class to read the rotation of the rotary encoder class RotaryEncoder { public: // constructor // rotary_encoder_A is the pin for the A of the rotary encoder. // The B of the rotary encoder has to be connected to the next GPIO. RotaryEncoder(uint rotary_encoder_A) { uint8_t rotary_encoder_B = rotary_encoder_A + 1; // pio 0 is used PIO pio = pio0; // state machine 0 uint8_t sm = 0; // configure the used pins as input with pull up pio_gpio_init(pio, rotary_encoder_A); gpio_set_pulls(rotary_encoder_A, true, false); pio_gpio_init(pio, rotary_encoder_B); gpio_set_pulls(rotary_encoder_B, true, false); // load the pio program into the pio memory uint offset = pio_add_program(pio, &pio_rotary_encoder_program); // make a sm config pio_sm_config c = pio_rotary_encoder_program_get_default_config(offset); // set the 'in' pins sm_config_set_in_pins(&c, rotary_encoder_A); // set shift to left: bits shifted by 'in' enter at the least // significant bit (LSB), no autopush sm_config_set_in_shift(&c, false, false, 0); // set the IRQ handler irq_set_exclusive_handler(PIO0_IRQ_0, pio_irq_handler); // enable the IRQ irq_set_enabled(PIO0_IRQ_0, true); pio0_hw->inte0 = PIO_IRQ0_INTE_SM0_BITS | PIO_IRQ0_INTE_SM1_BITS; // init the sm. // Note: the program starts after the jump table -> initial_pc = 16 pio_sm_init(pio, sm, 16, &c); // enable the sm pio_sm_set_enabled(pio, sm, true); } // set the current rotation to a specific value void set_rotation(int _rotation) { rotation = _rotation; } // get the current rotation int get_rotation(void) { return rotation; } private: static void pio_irq_handler() { // test if irq 0 was raised if (pio0_hw->irq & 1) { rotation = rotation - 1; } // test if irq 1 was raised if (pio0_hw->irq & 2) { rotation = rotation + 1; } // clear both interrupts pio0_hw->irq = 3; } // the pio instance PIO pio; // the state machine uint sm; // the current location of rotation static int rotation; }; // Initialize static member of class Rotary_encoder int RotaryEncoder::rotation = 0; int main() { // needed for printf stdio_init_all(); // the A of the rotary encoder is connected to GPIO 16, B to GPIO 17 RotaryEncoder my_encoder(16); // initialize the rotatry encoder rotation as 0 my_encoder.set_rotation(0); // infinite loop to print the current rotation while (true) { printf("rotation=%d\n", my_encoder.get_rotation()); } } ================================================ FILE: Rotary_encoder/pio_rotary_encoder.pio ================================================ .program pio_rotary_encoder .wrap_target .origin 0 ; The jump table has to start at 0 ; it contains the correct jumps for each of the 16 ; combination of 4 bits formed by A'B'AB ; A = current reading of pin_A of the rotary encoder ; A' = previous reading of pin_A of the rotary encoder ; B = current reading of pin_B of the rotary encoder ; B' = previous reading of pin_B of the rotary encoder jmp read ; 0000 = from 00 to 00 = no change in reading jmp CW ; 0001 = from 00 to 01 = clockwise rotation jmp CCW ; 0010 = from 00 to 10 = counter clockwise rotation jmp read ; 0011 = from 00 to 11 = error jmp CCW ; 0100 = from 01 to 00 = counter clockwise rotation jmp read ; 0101 = from 01 to 01 = no change in reading jmp read ; 0110 = from 01 to 10 = error jmp CW ; 0111 = from 01 to 11 = clockwise rotation jmp CW ; 1000 = from 10 to 00 = clockwise rotation jmp read ; 1001 = from 10 to 01 = error jmp read ; 1010 = from 10 to 10 = no change in reading jmp CCW ; 1011 = from 10 to 11 = counter clockwise rotation jmp read ; 1100 = from 11 to 00 = error jmp CCW ; 1101 = from 11 to 01 = counter clockwise rotation jmp CW ; 1110 = from 11 to 10 = clockwise rotation jmp read ; 1111 = from 11 to 11 = no change in reading pc_start: ; this is the entry point for the program in pins 2 ; read the current values of A and B and use ; them to initialize the previous values (A'B') read: mov OSR ISR ; the OSR is (after the next instruction) used to shift ; the two bits with the previous values into the ISR out ISR 2 ; shift the previous value into the ISR. This also sets ; all other bits in the ISR to 0 in pins 2 ; shift the current value into the ISR ; the 16 LSB of the ISR now contain 000000000000A'B'AB ; this represents a jmp instruction to the address A'B'AB mov exec ISR ; do the jmp encoded in the ISR CW: ; a clockwise rotation was detected irq 0 ; signal a clockwise rotation via an IRQ jmp read ; jump to reading the current values of A and B CCW: ; a counter clockwise rotation was detected irq 1 ; signal a counter clockwise rotation via an IRQ ; jmp read ; jump to reading the current values of A and B. ; the jmp isn't needed because of the .wrap, and the first ; statement of the program happens to be a jmp read .wrap ================================================ FILE: Rotational_shift_ISR/CMakeLists.txt ================================================ add_executable(rotational_shift_ISR) pico_generate_pio_header(rotational_shift_ISR ${CMAKE_CURRENT_LIST_DIR}/rotational_shift_ISR.pio) target_sources(rotational_shift_ISR PRIVATE rotational_shift_ISR.cpp) target_link_libraries(rotational_shift_ISR PRIVATE pico_stdlib hardware_pio ) pico_add_extra_outputs(rotational_shift_ISR) # add url via pico_set_program_url example_auto_set_url(rotational_shift_ISR) ================================================ FILE: Rotational_shift_ISR/README.md ================================================ # Use the ISR for rotational shifting Normally if the ISR shifts via the `IN` instruction, the bits that come out of the ISR go to cyber space, never to be heard from again. Sometimes it is handy to have rotational shifting. The [figure below](https://en.wikipedia.org/wiki/Circular_shift#Applications) shows a rotational right shift of one bit. Note that the Least Significant Bit (LSB) shifts to the Most Significant Bit (MSB). ![](rotational_shifting.png) In PIO code the ISR can be used to shift bits in the direction given by the c-command `sm_config_set_in_shift(&c, direction, false, 0)` where direction=true means shift to the right, and direction=false means shift to the left. The `IN` instruction has an option to shift bits into the ISR from the ISR itself via `IN ISR ` instruction. This opens up the possibility of making it shift rotationally. The algorithm used by `IN ISR ` is as follows: shift the ISR for `` bits in the direction specified by `sm_config_set_in_shift`, and shift in the LSBs of the original ISR value. ## Right shifting For right shifting this works just fine, it basically comes down to every bit that would normally go to cyber space is saved and shifted in as the MSB. In the figure below the two steps are shown for eight bits (the ISR has 32) and the instruction `IN ISR 2`: ![](right_shift.png) On the top the ISR has the bit value 00110010, in the middle it has shifted two bits, and on the bottom the two LSBs of the original ISR are shifted in the same order as the MSBs (these are the same as the two bits shifted out in the blue circle). This results in a bit value 10001100, which is exactly a two bits rotational shift of the ISR. ## Left shifting For left-shifting, however, this does not work well, consider the following example: ![](left_shift.png) The ISR at the start has a bit value of 01010010, and after shifting to the left, the two LSBs of the original ISR are shifted in on the left. These are not the two bits that were shifted out (blue circle)! So, no rotational shifting to the left. It is possible to obtain true rotational left shifting. The trick is that the direction of shifting must be set to right shifting: `sm_config_set_in_shift(&c, true, false, 0)` and the following PIO code can then be used to left shift the ISR for two bits (other `` are of course also possible): ``` mov ISR :: ISR in ISR 2 mov ISR :: ISR ``` In the above PIO code, the `::` symbols reverses the bit order in the ISR, effectively turning the correctly working right shift into a left-shift. ================================================ FILE: Rotational_shift_ISR/rotational_shift_ISR.cpp ================================================ #include #include #include "pico/stdlib.h" #include "hardware/pio.h" #include "rotational_shift_ISR.pio.h" // handy function to print the value of 'number' in bits void printBits(uint32_t number) { // go through each of the bits, starting with the Most Significant Bit (MSB) for (int i = 31; i >= 0; i--) { // test of that bit is set, print "1" if set, or "0" if not if ((number & (1 << i)) > 0) { printf("1"); } else { printf("0"); } } } int main() { // needed for printf stdio_init_all(); // pio 0 is used PIO pio = pio0; // state machine 0 and 1 uint sm = 0; // load the sm0 program into the pio memory uint offset = pio_add_program(pio, &rotational_shift_ISR_program); // make a sm config pio_sm_config c = rotational_shift_ISR_program_get_default_config(offset); // set shift direction // NOTE: IT IS SET TO SHIFT RIGHT! EVEN THOUGH THE PIO CODE SHIFTS RIGHT AND LEFT sm_config_set_in_shift(&c, true, false, 0); // init the pio sm with the config pio_sm_init(pio, sm, offset, &c); // enable the sm pio_sm_set_enabled(pio, sm, true); while (true) { // push a random number into the Tx FIFO pio_sm_put(pio, sm, rand()); // read it back (first wait until something is written into the Rx FIFO) while (pio_sm_is_rx_fifo_empty(pio, sm)) ; printf("S)\t"); printBits(pio_sm_get(pio, sm)); printf("\n"); // let the PIO code do 32 shifts to the right for (int i = 1; i <= 32; i++) { while (pio_sm_is_rx_fifo_empty(pio, sm)) ; printf("R%d)\t", i); printBits(pio_sm_get(pio, sm)); printf("\n"); } // let the PIO code do 32 shifts to the left for (int i = 1; i <= 32; i++) { while (pio_sm_is_rx_fifo_empty(pio, sm)) ; printf("L%d)\t", i); printBits(pio_sm_get(pio, sm)); printf("\n"); } printf("----------------------------------------------------------\n"); } } ================================================ FILE: Rotational_shift_ISR/rotational_shift_ISR.pio ================================================ /* I was playing with the instruction `IN ISR ` and wanted to make a rotational shifter. For right-shifting this works fine, but for left-shifting two instructions `mov ISR :: ISR` are required. */ .program rotational_shift_ISR .wrap_target pull block ; get a value to shift around mov ISR OSR ; put it in the ISR mov y ISR ; save the ISR push block ; push the starting value back to the user program mov ISR y ; restore the ISR set x 31 ; prepare to do a full rotation to the right loop_right: in ISR 1 ; shift to right mov y ISR ; save the ISR push block ; the ISR is cleared mov ISR y ; restore the ISR jmp x-- loop_right set x 31 ; prepare to do a full rotation to the left loop_left: mov ISR :: ISR ; reverse bit order in ISR in ISR 1 ; shift right (but after the next reverse its actually left) mov ISR :: ISR ; reverse bit order in ISR mov y ISR ; save the ISR push block ; the ISR is cleared mov ISR y ; restore the ISR jmp x-- loop_left .wrap ================================================ FILE: SBUS/CMakeLists.txt ================================================ add_executable(SBUS) pico_generate_pio_header(SBUS ${CMAKE_CURRENT_LIST_DIR}/SBUS.pio) target_sources(SBUS PRIVATE SBUS.cpp) target_link_libraries(SBUS PRIVATE pico_stdlib hardware_pio ) pico_add_extra_outputs(SBUS) ================================================ FILE: SBUS/README.md ================================================ # Read the SBUS protocol with (and without!) pio code The SBUS protocol is typically used in Radio Controlled cars, drones, etc. If you want to read this protocol from a RC receiver in order to manipulate the data before setting motors and servos, you can use this code. The SBUS protocol is basically an uart Rx with inverted input, 100000 baud rate, a parity bit, and two stop bits, see [here](https://github.com/bolderflight/sbus). The basis for the PIO code is the RPI pico example for [pio rx](https://github.com/raspberrypi/pico-examples/blob/master/pio/uart_rx/uart_rx.pio). The parsing of the received data is done following to [this](https://platformio.org/lib/show/5622/Bolder%20Flight%20Systems%20SBUS). It even does parity checking in the pio code! ## Update Since the SBUS protocol is "normal" uart I originally started looking at using the hardware uart, but couldn't find an invert option. Turns out you have to invert the GPIO used for input or output. The updated code that doesn't use pio but uart hardware is [here](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/SBUS/gpio_invert). ================================================ FILE: SBUS/SBUS.cpp ================================================ /* Read SBUS protocol, typically used in Radio Controlled cars, drones, etc. SBUS data packets consists of 25 8-bit bytes starting with 0x0F and ending with two 0x00 The signal is inverted, has an even parity bit and two stop-bits, see: https://github.com/bolderflight/sbus */ #include #include "pico/stdlib.h" #include "hardware/pio.h" #include "hardware/clocks.h" #include "SBUS.pio.h" // The baud rate for the SBUS protocol is 100000 #define SERIAL_BAUD 100000 // The RC receiver is attached to pin 5 #define PIO_RX_PIN 5 // the max amount of data in a SBUS data packet #define MAX_DATA_ITEMS 25 // function to decode the received SBUS data to actual steering commands void decode(uint8_t *data) { uint16_t channels[16]; // see https://platformio.org/lib/show/5622/Bolder%20Flight%20Systems%20SBUS channels[0] = (uint16_t)((data[0] | data[1] << 8) & 0x07FF); channels[1] = (uint16_t)((data[1] >> 3 | data[2] << 5) & 0x07FF); channels[2] = (uint16_t)((data[2] >> 6 | data[3] << 2 | data[4] << 10) & 0x07FF); channels[3] = (uint16_t)((data[4] >> 1 | data[5] << 7) & 0x07FF); channels[4] = (uint16_t)((data[5] >> 4 | data[6] << 4) & 0x07FF); channels[5] = (uint16_t)((data[6] >> 7 | data[7] << 1 | data[8] << 9) & 0x07FF); channels[6] = (uint16_t)((data[8] >> 2 | data[9] << 6) & 0x07FF); channels[7] = (uint16_t)((data[9] >> 5 | data[10] << 3) & 0x07FF); channels[8] = (uint16_t)((data[11] | data[12] << 8) & 0x07FF); channels[9] = (uint16_t)((data[12] >> 3 | data[13] << 5) & 0x07FF); channels[10] = (uint16_t)((data[13] >> 6 | data[14] << 2 | data[15] << 10) & 0x07FF); channels[11] = (uint16_t)((data[15] >> 1 | data[16] << 7) & 0x07FF); channels[12] = (uint16_t)((data[16] >> 4 | data[17] << 4) & 0x07FF); channels[13] = (uint16_t)((data[17] >> 7 | data[18] << 1 | data[19] << 9) & 0x07FF); channels[14] = (uint16_t)((data[19] >> 2 | data[20] << 6) & 0x07FF); channels[15] = (uint16_t)((data[20] >> 5 | data[21] << 3) & 0x07FF); for (int i = 0; i < 16; i++) { printf("%d \t", channels[i]); } printf("\n"); } // Main function int main() { // needed for printf stdio_init_all(); // Set up the state machine to receive RC SBUS data PIO pio = pio0; uint sm = 0; uint offset = pio_add_program(pio, &sbus_program); pio_sm_config c = sbus_program_get_default_config(offset); // configure the pin to receive the SBUS data pio_sm_set_consecutive_pindirs(pio, sm, PIO_RX_PIN, 1, false); pio_gpio_init(pio, PIO_RX_PIN); gpio_pull_down(PIO_RX_PIN); sm_config_set_in_pins(&c, PIO_RX_PIN); // for WAIT, IN sm_config_set_jmp_pin(&c, PIO_RX_PIN); // for JMP // Shift to right, autopull disabled sm_config_set_in_shift(&c, true, false, 32); // Shift to left, autopull disabled sm_config_set_out_shift(&c, false, 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 * SERIAL_BAUD); sm_config_set_clkdiv(&c, div); // init and enable the sm pio_sm_init(pio, sm, offset, &c); pio_sm_set_enabled(pio, sm, true); uint8_t index = 0; uint8_t data[MAX_DATA_ITEMS]; // continuously get the SBUS data from the pio and decode it while (true) { // Note: // Although there is sufficient time for receiving and decoding because the clkdiv // and join (see above), too much printing will cause data loss while (pio_sm_is_rx_fifo_empty(pio, sm)) tight_loop_contents(); uint8_t data_item = pio_sm_get(pio, sm) >> 24; // search for 0x0f: the start marker if (data_item == 0x0f) index = 0; else if (index < MAX_DATA_ITEMS) { // test if the first end marker is read if (data_item == 0) { // read the second end marker while (pio_sm_is_rx_fifo_empty(pio, sm)) tight_loop_contents(); data_item = pio_sm_get(pio, sm) >> 24; // the second end marker should also be 0 if (data_item != 0) printf("Error, second end marker not found\n"); else // if all data is received, decode it if (index == 22) decode(data); } else // if not start or end marker, add it to the received data data[index++] = data_item; } else printf("error\n"); } } ================================================ FILE: SBUS/SBUS.pio ================================================ .program sbus ; Start with the situation that the number of ones received is zero (an even number) ; Read the 8 data bits and the parity bit (so, 9 bits in total) ; The even parity bit has to make the total number of ones odd! ; The code starts in the top half (the wrong parity part); if a one is observed it switches ; to the bottom half (the correct parity part). If again a one is observed, it switches back to the top half. ; Etc, switching back and forth every time a one is observed. ; the bits arrive LSB first, thus right-shift it into the ISR .wrap_target start: wait 1 pin 0 ; Stall until start bit is asserted set x, 8 [10] ; read 8 data bits plus the parity bit. The parity bit should make a odd total of ones. ; delay 12 cycles incl wait, set (8 cycles for the start bit, then 4 to get halfway the first data bit) even_ones: in pins, 1 ; Shift data bit into ISR mov OSR ISR ; test if it is a one or zero: copy it to the OSR and left-shift one bit out out y 1 jmp !y even_ones_1 ; jump if y == 0, the number of ones doesn't change jmp odd_ones_2 ; the number of ones did change! Go to the bottom part (correct parity) even_ones_1: jmp x-- even_ones [3] ; Loop 9 times (8 data bits + the parity bit), each loop iteration is 8 cycles ; no need to wait for the stop bits, just stop already, the parity is wrong jmp end_and_restart even_ones_2: ; the only reason this is here is the [2] compared to [3] a couple of lines back ; because after the "jmp !y" an extra jmp is made, which costs 1 cycle jmp x-- even_ones [2] ; Loop 9 times (8 data bits + the parity bit), each loop iteration is 8 cycles ; no need to wait for the stop bits, just stop already, the parity is wrong jmp end_and_restart ;--------------------------------------------------------- ; If the reading of 9 bits ends above: the parity is wrong ; ; If the reading of 9 bits ends below: the parity is correct ;--------------------------------------------------------- odd_ones: in pins, 1 ; Shift data bit into ISR mov OSR ISR ; copy it to the OSR to get that bit into the y register out y 1 ; jmp !y odd_ones_1 ; jump if y == 0, the number of ones doesn't change jmp even_ones_2 ; the number of ones did change! Go to the top part (wrong parity) odd_ones_1: jmp x-- odd_ones [3] ; Loop 9 times (8 data bits + the parity bit), each loop iteration is 8 cycles ; all data has been read, no need to wait for the stop bits. The parity is correct correct: mov OSR ISR ; the parity bit is still in ISR, remove it: copy to OSR, left-shift the parity bit out out NULL 1 mov ISR ~OSR ; invert the data in the OSR and copy it to the ISR push ; push the ISR to the Rx FIFO end_and_restart: wait 0 pin 0 ; wait for line to return to idle state. jmp start ; Don't push data if we didn't see correct framing. odd_ones_2: jmp x-- odd_ones [2] ; Loop 9 times (8 data bits + the parity bit), each loop iteration is 8 cycles jmp correct ; all data has been read, no need to wait for the stop bits. The parity is correct ================================================ FILE: SBUS/gpio_invert/CMakeLists.txt ================================================ add_executable(SBUS) target_sources(SBUS PRIVATE SBUS.cpp) target_link_libraries(SBUS PRIVATE pico_stdlib hardware_uart ) pico_add_extra_outputs(SBUS) ================================================ FILE: SBUS/gpio_invert/SBUS.cpp ================================================ /* Read SBUS protocol, typically used in Radio Controlled cars, drones, etc. SBUS data packets consists of 25 8-bit bytes starting with 0x0F and ending with two 0x00 The signal is inverted, has an even parity bit and two stop-bits, see: https://github.com/bolderflight/sbus */ #include #include "pico/stdlib.h" #include "hardware/uart.h" // the max amount of data in a SBUS data packet (including start marker 0x0F and end marker 0x00) #define MAX_DATA_ITEMS 25 // the baud rate for the SBUS protocol is 100000 #define SERIAL_BAUD 100000 // the RC receiver is attached to pin 5 #define RX_PIN 5 // the UART output (not used) // #define TX_PIN 4 // the uart (there are two: uart0 and uart1, pin5 belongs to uart1) #define UART_ID uart1 // number of data bits; SBUS has 8 #define DATA_BITS 8 // number of stop bits; SBUS has 2 #define STOP_BITS 2 // parity setting; SBUS has even parity #define PARITY UART_PARITY_EVEN // function to decode the received SBUS data to actual steering commands void decode(uint8_t *data) { // the SBUS data encodes 16 channels uint16_t channels[16]; // see https://platformio.org/lib/show/5622/Bolder%20Flight%20Systems%20SBUS channels[0] = (uint16_t)((data[0] | data[1] << 8) & 0x07FF); channels[1] = (uint16_t)((data[1] >> 3 | data[2] << 5) & 0x07FF); channels[2] = (uint16_t)((data[2] >> 6 | data[3] << 2 | data[4] << 10) & 0x07FF); channels[3] = (uint16_t)((data[4] >> 1 | data[5] << 7) & 0x07FF); channels[4] = (uint16_t)((data[5] >> 4 | data[6] << 4) & 0x07FF); channels[5] = (uint16_t)((data[6] >> 7 | data[7] << 1 | data[8] << 9) & 0x07FF); channels[6] = (uint16_t)((data[8] >> 2 | data[9] << 6) & 0x07FF); channels[7] = (uint16_t)((data[9] >> 5 | data[10] << 3) & 0x07FF); channels[8] = (uint16_t)((data[11] | data[12] << 8) & 0x07FF); channels[9] = (uint16_t)((data[12] >> 3 | data[13] << 5) & 0x07FF); channels[10] = (uint16_t)((data[13] >> 6 | data[14] << 2 | data[15] << 10) & 0x07FF); channels[11] = (uint16_t)((data[15] >> 1 | data[16] << 7) & 0x07FF); channels[12] = (uint16_t)((data[16] >> 4 | data[17] << 4) & 0x07FF); channels[13] = (uint16_t)((data[17] >> 7 | data[18] << 1 | data[19] << 9) & 0x07FF); channels[14] = (uint16_t)((data[19] >> 2 | data[20] << 6) & 0x07FF); channels[15] = (uint16_t)((data[20] >> 5 | data[21] << 3) & 0x07FF); for (int i = 0; i < 16; i++) { printf("%d \t", channels[i]); } printf("\n"); } // Main function int main() { // needed for printf stdio_init_all(); // initialize the uart uart_init(UART_ID, SERIAL_BAUD); // set the gpio function to uart gpio_set_function(RX_PIN, GPIO_FUNC_UART); // set data bits, stop_bits and the parity of the uart uart_set_format(UART_ID, DATA_BITS, STOP_BITS, PARITY); // set the input gpio pin to inverted gpio_set_inover(RX_PIN, GPIO_OVERRIDE_INVERT); // index of read SBUS data item uint8_t index = 0; // array to store read SBUS data uint8_t data[MAX_DATA_ITEMS]; // continuously get the SBUS data from the pio and decode it while (true) { // read a SBUS data item uint8_t data_item = (uint8_t)uart_getc(UART_ID); // store it data[index++] = data_item; // check for the start markter if (data_item == 0x0F) { // if start marker has been found: decode the existing SBUS data decode(data); // start over index = 0; } } } ================================================ FILE: Two_sm_one_disabled/CMakeLists.txt ================================================ add_executable(two_sm_one_disabled) pico_generate_pio_header(two_sm_one_disabled ${CMAKE_CURRENT_LIST_DIR}/two_sm_one_disabled.pio) target_sources(two_sm_one_disabled PRIVATE two_sm_one_disabled.cpp) target_link_libraries(two_sm_one_disabled PRIVATE pico_stdlib hardware_pio ) pico_add_extra_outputs(two_sm_one_disabled) # add url via pico_set_program_url example_auto_set_url(two_sm_one_disabled) ================================================ FILE: Two_sm_one_disabled/README.md ================================================ # Two independently running state machines, one gets disabled temporarily This is an example of two state machines (sm) running independently, but one, sm1, gets disabled temporarily. The sm0 repeatedly counts up from 0 to 0xFFFFFFFF (actually, it counts down!), the other repeatedly counts down from 31. They both send their current values to the c-program via their Rx FIFOs. In the c-program, sm1 gets disabled for a number of iterations. The output is somewhat interesting: ``` 0 = 293088 1 = 31 ; 0 = 293089 1 = 30 ; 0 = 293090 1 = 29 ; 0 = 293091 1 = 28 <--- ; Here the sm1 is disabled, but it still has 4 words in the Rx FIFO 0 = 293092 1 = 27 <--- ; 0 = 293093 1 = 26 <--- ; 0 = 293094 1 = 25 <--- ; Last valid Rx FIFO data 0 = 293095 1 = 28 <--- ; Here the Rx FIFO of sm1 is empty. Oddly, we get 28, not 25 0 = 293096 1 = 28 <--- ; 0 = 293097 1 = 28 <--- ; 0 = 293098 1 = 28 <--- ; 0 = 293099 1 = 28 <--- ; 0 = 293100 1 = 28 <--- ; 0 = 293101 1 = 24 ; The sm is started again, and as expected continues where it was stopped 0 = 293102 1 = 23 ; 0 = 293103 1 = 22 ; 0 = 293104 1 = 21 ; ``` While sm0 keeps counting up, sm1 is disabled. At that moment there are still 4 data items in the FIFO. After these 4 have been read and printed, reading the FIFO keeps giving data (28, the entry at the time of disabling sm1). I know you should always check whether there is still valid data in the FIFO before reading it, but hey now we know what happens. ================================================ FILE: Two_sm_one_disabled/two_sm_one_disabled.cpp ================================================ #include #include "pico/stdlib.h" #include "hardware/pio.h" #include "two_sm_one_disabled.pio.h" int main() { // needed for printf stdio_init_all(); // pio 0 is used PIO pio = pio0; // state machine 0 and 1 uint sm0 = 0; uint sm1 = 1; // load the sm0 program into the pio memory uint offset0 = pio_add_program(pio, &tester0_program); // load the sm1 program into the pio memory uint offset1 = pio_add_program(pio, &tester1_program); // make a sm config pio_sm_config c0 = tester0_program_get_default_config(offset0); pio_sm_config c1 = tester1_program_get_default_config(offset1); // init the pio sm0 with the config pio_sm_init(pio, sm0, offset0, &c0); pio_sm_init(pio, sm1, offset1, &c1); // enable the sm pio_sm_set_enabled(pio, sm0, true); pio_sm_set_enabled(pio, sm1, true); // infinite loop. But after 100 times normal printf, sm1 is disabled for 10 iterations and then enabled again int i = 0; while (true) { i++; if (i < 100) { // normal printing 100 times printf("0 = %d \t\t1 = %d\n", pio_sm_get(pio, sm0), pio_sm_get(pio, sm1)); } else if (i < 110) { // sm1 is disabled, printing for 10 times pio_sm_set_enabled(pio, sm1, false); printf("0 = %d \t\t1 = %d <---\n", pio_sm_get(pio, sm0), pio_sm_get(pio, sm1)); } else { // enable sm1 again, and repeat counting pio_sm_set_enabled(pio, sm1, true); i = 0; } } } ================================================ FILE: Two_sm_one_disabled/two_sm_one_disabled.pio ================================================ .program tester0 start0: mov x ~NULL ; start with 0xFFFFFFFF push_it0: mov ISR ~x ; push the value into the Rx FIFO ; (make it look like counting up) push block jmp x-- push_it0 ; count down jmp start0 .program tester1 start1: set x 31 ; start with 31 push_it1: mov ISR x ; push the x value into the Rx FIFO push block jmp x-- push_it1 ; count down jmp start1 ================================================ FILE: Two_sm_one_disabled_with_irq/CMakeLists.txt ================================================ add_executable(two_sm_one_disabled_with_irq) pico_generate_pio_header(two_sm_one_disabled_with_irq ${CMAKE_CURRENT_LIST_DIR}/two_sm_one_disabled_with_irq.pio) target_sources(two_sm_one_disabled_with_irq PRIVATE two_sm_one_disabled_with_irq.cpp) target_link_libraries(two_sm_one_disabled_with_irq PRIVATE pico_stdlib hardware_pio ) pico_add_extra_outputs(two_sm_one_disabled_with_irq) # add url via pico_set_program_url example_auto_set_url(two_sm_one_disabled_with_irq) ================================================ FILE: Two_sm_one_disabled_with_irq/README.md ================================================ # Two independently running state machines, synchronized via irq, one is temporarily disabled This is an example of two state machines (sm) synchronizing their execution via setting and clearing an irq. At some point sm1 gets disabled. Because they are synchronized, sm0 also stops. One sm repeatedly counts up from 0 to 0xFFFFFFFF (actually, it counts down!), the other repeatedly counts down from 31. They both send their current values to the c-program via their Rx FIFOs. ``` 0 = 198 1 = 24 ; 0 = 199 1 = 23 ; 0 = 200 1 = 22 ; 0 = 201 1 = 21 <--- ; The sm1 is disabled 0 = 202 1 = 20 <--- ; The sm0 stops producing output (irq wait) 0 = 199 1 = 19 <--- ; 0 = 199 1 = 18 <--- ; Last valid sm1 Rx FIFO data 0 = 199 1 = 21 <--- ; 0 = 199 1 = 21 <--- ; 0 = 199 1 = 21 <--- ; 0 = 199 1 = 21 <--- ; 0 = 199 1 = 21 <--- ; 0 = 199 1 = 21 <--- ; 0 = 203 1 = 17 ; The sm1 started again, sm0 follows 0 = 204 1 = 16 ; 0 = 205 1 = 15 ; ``` ================================================ FILE: Two_sm_one_disabled_with_irq/two_sm_one_disabled_with_irq.cpp ================================================ #include #include "pico/stdlib.h" #include "hardware/pio.h" #include "two_sm_one_disabled_with_irq.pio.h" int main() { // needed for printf stdio_init_all(); // pio 0 is used PIO pio = pio0; // state machine 0 and 1 uint sm0 = 0; uint sm1 = 1; // load the sm0 program into the pio memory uint offset0 = pio_add_program(pio, &tester0_program); // load the sm1 program into the pio memory uint offset1 = pio_add_program(pio, &tester1_program); // make a sm config pio_sm_config c0 = tester0_program_get_default_config(offset0); pio_sm_config c1 = tester1_program_get_default_config(offset1); // init the pio sm0 with the config pio_sm_init(pio, sm0, offset0, &c0); pio_sm_init(pio, sm1, offset1, &c1); // enable the sm pio_sm_set_enabled(pio, sm0, true); pio_sm_set_enabled(pio, sm1, true); // infinite loop. But after 100 times normal printf, sm1 is disabled for 10 iterations and then enabled again int i = 0; while (true) { i++; if (i < 100) { // normal printing 100 times printf("0 = %d \t\t1 = %d\n", pio_sm_get(pio, sm0), pio_sm_get(pio, sm1)); } else if (i < 110) { // sm1 is disabled, printing for 10 times pio_sm_set_enabled(pio, sm1, false); printf("0 = %d \t\t1 = %d <---\n", pio_sm_get(pio, sm0), pio_sm_get(pio, sm1)); } else { // enable sm1 again, and repeat counting pio_sm_set_enabled(pio, sm1, true); i = 0; } } } ================================================ FILE: Two_sm_one_disabled_with_irq/two_sm_one_disabled_with_irq.pio ================================================ .program tester0 start0: mov x ~NULL ; start with 0xFFFFFFFF push_it0: mov ISR ~x ; push the value into the Rx FIFO (make it look like counting up) push block irq wait 0 ; set irq 0 and wait for it to clear jmp x-- push_it0 ; count down jmp start0 .program tester1 start1: set x 31 ; start with 31 push_it1: mov ISR x ; push the value into the Rx FIFO push block irq clear 0 ; clear irq 0 jmp x-- push_it1 ; count down jmp start1 ================================================ FILE: Two_sm_simple/CMakeLists.txt ================================================ add_executable(two_sm_simple) pico_generate_pio_header(two_sm_simple ${CMAKE_CURRENT_LIST_DIR}/two_sm_simple.pio) target_sources(two_sm_simple PRIVATE two_sm_simple.cpp) target_link_libraries(two_sm_simple PRIVATE pico_stdlib hardware_pio ) pico_add_extra_outputs(two_sm_simple) # add url via pico_set_program_url example_auto_set_url(two_sm_simple) ================================================ FILE: Two_sm_simple/README.md ================================================ # Two independently running state machines This is just an example of two state machines (sm) running independently. Nothing special about it, but I had to do it. One sm repeatedly counts up from 0 to 0xFFFFFFFF (actually, it counts down!), the other repeatedly counts down from 31. They both send their current values to the c-program via their Rx FIFOs. ================================================ FILE: Two_sm_simple/two_sm_simple.cpp ================================================ #include #include "pico/stdlib.h" #include "hardware/pio.h" #include "two_sm_simple.pio.h" int main() { // needed for printf stdio_init_all(); // pio 0 is used PIO pio = pio0; // state machine 0 and 1 uint sm0 = 0; uint sm1 = 1; // load the sm0 program into the pio memory uint offset0 = pio_add_program(pio, &tester0_program); // load the sm1 program into the pio memory uint offset1 = pio_add_program(pio, &tester1_program); // make a sm config pio_sm_config c0 = tester0_program_get_default_config(offset0); pio_sm_config c1 = tester1_program_get_default_config(offset1); // init the pio sm0 with the config pio_sm_init(pio, sm0, offset0, &c0); pio_sm_init(pio, sm1, offset1, &c1); // enable the sm pio_sm_set_enabled(pio, sm0, true); pio_sm_set_enabled(pio, sm1, true); while (true) { printf("0 = %d \t\t1 = %d\n", pio_sm_get(pio, sm0), pio_sm_get(pio, sm1)); } } ================================================ FILE: Two_sm_simple/two_sm_simple.pio ================================================ .program tester0 start0: mov x ~NULL ; start at 0xFFFFFFFF and count down push_it0: mov ISR ~x ; make it appear that it counts up push block ; push the current value into the Rx FIFO jmp x-- push_it0 jmp start0 .program tester1 start1: set x 31 ; start at 31 push_it1: mov ISR x push block ; push the current value into the Rx FIFO jmp x-- push_it1 jmp start1 ================================================ FILE: Value_communication_between_two_sm_via_pins/CMakeLists.txt ================================================ add_executable(value_communication_between_two_sm_via_pins) pico_generate_pio_header(value_communication_between_two_sm_via_pins ${CMAKE_CURRENT_LIST_DIR}/value_communication_between_two_sm_via_pins.pio) target_sources(value_communication_between_two_sm_via_pins PRIVATE value_communication_between_two_sm_via_pins.cpp) target_link_libraries(value_communication_between_two_sm_via_pins PRIVATE pico_stdlib hardware_pio ) pico_add_extra_outputs(value_communication_between_two_sm_via_pins) # add url via pico_set_program_url example_auto_set_url(value_communication_between_two_sm_via_pins) ================================================ FILE: Value_communication_between_two_sm_via_pins/README.md ================================================ # Sending values between state machines The [RP2040 Datasheet](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) states that "State machines can not communicate data". Or can they ... Of course they can, but not directly from within the state machines (sm). They can communicate via their FIFOs if the c program reads the Rx FIFO of one sm and puts it into the Tx FIFO of the other sm. They can even do this via DMA, which is [another example](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/sm_to_dma_to_sm_to_dma_to_buffer) in this repository. But they can also communicate using the GPIO pins. In this example, one sm sets a GPIO pin to a value, and the other reads that pin to obtain the value. They use an irq to make sure that they work synchronized. In this example only one GPIO pin is used, but they could of course use more pins to communicate more bits at one time. ================================================ FILE: Value_communication_between_two_sm_via_pins/value_communication_between_two_sm_via_pins.cpp ================================================ #include #include "pico/stdlib.h" #include "hardware/pio.h" #include "value_communication_between_two_sm_via_pins.pio.h" int main() { // needed for printf stdio_init_all(); // pio 0 is used PIO pio = pio0; // state machine 0 and 1 uint sm0 = 0; uint sm1 = 1; // use pin 15 for both output of sm0 and input to sm1 uint pin = 15; pio_gpio_init(pio, pin); pio_sm_set_consecutive_pindirs(pio, sm0, pin, 1, true); // These pins are used by sm0 and sm1. // As it turns out, the pin needs to be set once: to output! // even though sm1 uses them as input. // load the sm0 program into the pio memory uint offset0 = pio_add_program(pio, &tester0_program); // load the sm1 program into the pio memory uint offset1 = pio_add_program(pio, &tester1_program); // make a sm config pio_sm_config c0 = tester0_program_get_default_config(offset0); pio_sm_config c1 = tester1_program_get_default_config(offset1); // set shift direction sm_config_set_in_shift(&c0, false, false, 0); // set shift direction sm_config_set_in_shift(&c1, false, false, 0); // sm0 produces output using 'set', sm1 reads it using 'in' // set the 'set' pins for sm0 sm_config_set_set_pins(&c0, pin, 1); // set the 'in' pins for sm1 sm_config_set_in_pins(&c1, pin); // init the pio sm0 with config c0 pio_sm_init(pio, sm0, offset0, &c0); // init the pio sm1 with config c1 pio_sm_init(pio, sm1, offset1, &c1); // enable the state machines pio_sm_set_enabled(pio, sm0, true); pio_sm_set_enabled(pio, sm1, true); // end with infinite loop of printing what is send by sm0 and received by sm1 while (true) { printf("value send by sm0 = %d\n", pio_sm_get(pio, sm0)); printf("value received by sm1 = %d\n", pio_sm_get(pio, sm1)); } } ================================================ FILE: Value_communication_between_two_sm_via_pins/value_communication_between_two_sm_via_pins.pio ================================================ .program tester0 .wrap_target set pins 0 ; set output on pin to 0 (this is read by tester1, see below) mov ISR NULL ; signal this to the c++ program via the Rx FIFO push block irq wait 0 ; wait for the other sm to read the value of the pin set pins 1 ; the same as above, but now for the value 1 set x 1 ; push 1 into the ISR and then Rx FIFO mov ISR x push block irq wait 0 .wrap .program tester1 .wrap_target wait irq 0 ; wait for the other sm to set a value on the pin in pins 1 ; read the value push block ; signal this to the c++ program via the FIFO irq clear 0 ; clear the irq .wrap ================================================ FILE: Z80/CMakeLists.txt ================================================ add_executable(Z80) pico_generate_pio_header(Z80 ${CMAKE_CURRENT_LIST_DIR}/Z80.pio) target_sources(Z80 PRIVATE Z80.c) target_link_libraries(Z80 PRIVATE pico_stdlib hardware_pio hardware_irq hardware_vreg ) ================================================ FILE: Z80/README.md ================================================ # Z80 read and write from bus This is a WIP. It uses pio code to read and write to a Z80 bus. The information needed to write this code was provided by [siriokds](https://github.com/siriokds). ================================================ FILE: Z80/Z80.c ================================================ #include "stdio.h" #include "pico/stdlib.h" #include "hardware/pio.h" #include "hardware/vreg.h" // the .pio.h file also defines (in this order): D0 (8 bits), A0 (8 bits), RW, WR, DIR, OE #include "Z80.pio.h" // PIO and state machine PIO pio; uint sm_rd; uint sm_wr; uint offset_rd; uint offset_wr; // Configure the pio state machine void configure_pio_sm() { // pio 0 is used pio = pio0; // state machine 0 is used. sm_rd = 0; sm_wr = 1; // load the sm_rd program into the pio memory offset_rd = pio_add_program(pio, &Z80_read_program); // load the sm program into the pio memory offset_wr = pio_add_program(pio, &Z80_write_program); // make a sm_rd config pio_sm_config smc_rd = Z80_read_program_get_default_config(offset_rd); // make a sm_wr config pio_sm_config smc_wr = Z80_write_program_get_default_config(offset_wr); // function select: this allows pio to set output on a gpio for (int i = D0; i <= OE; i++) pio_gpio_init(pio, i); // TODO: for testing purposes, set data and address lines to 0 via pull down, normally this would be set externally for (int i = D0; i < RD; i++) gpio_set_pulls(i, false, true); // set initial pindirs: D0 - D7 are (also) output pio_sm_set_consecutive_pindirs(pio, sm_rd, D0, 8, true); pio_sm_set_consecutive_pindirs(pio, sm_wr, D0, 8, true); // set initial pindirs: A0 - A7 are input pio_sm_set_consecutive_pindirs(pio, sm_rd, A0, 8, false); pio_sm_set_consecutive_pindirs(pio, sm_wr, A0, 8, false); // set initial pindirs: RD, WR are input pio_sm_set_consecutive_pindirs(pio, sm_rd, RD, 1, false); pio_sm_set_consecutive_pindirs(pio, sm_wr, RD, 1, false); pio_sm_set_consecutive_pindirs(pio, sm_rd, WR, 1, false); pio_sm_set_consecutive_pindirs(pio, sm_wr, WR, 1, false); // set initial pindirs: DIR and OE are output pio_sm_set_consecutive_pindirs(pio, sm_rd, DIR, 1, true); pio_sm_set_consecutive_pindirs(pio, sm_wr, DIR, 1, true); pio_sm_set_consecutive_pindirs(pio, sm_rd, OE, 1, true); pio_sm_set_consecutive_pindirs(pio, sm_wr, OE, 1, true); // pio 'in' pins: inputs start at the first data bit (D0) sm_config_set_in_pins(&smc_rd, D0); // pio 'out' pins: data D0-D7 can also be output sm_config_set_out_pins(&smc_rd, D0, 8); // pio 'set' pins: DIR (LSB) and OE (MSB) sm_config_set_set_pins(&smc_rd, DIR, 2); // Reading from RxFIFO: Shift to left, autopull disabled sm_config_set_in_shift(&smc_rd, false, false, 32); // Writing to TxFIFO: Shift to right, autopull disabled sm_config_set_out_shift(&smc_rd, true, false, 32); // pio 'in' pins: inputs start at the first data bit (D0) sm_config_set_in_pins(&smc_wr, D0); // pio 'out' pins: data D0-D7 can also be output sm_config_set_out_pins(&smc_wr, D0, 8); // pio 'set' pins: DIR (LSB) and OE (MSB) sm_config_set_set_pins(&smc_wr, DIR, 2); // Reading from RxFIFO: Shift to left, autopull disabled sm_config_set_in_shift(&smc_wr, false, false, 32); // Writing to TxFIFO: Shift to right, autopull disabled sm_config_set_out_shift(&smc_wr, true, false, 32); // set clock to about 4Mhz TODO: set correct frequency // sm_config_set_clkdiv(&smc, 31); // sm_config_set_clkdiv(&smc_rd, 16); // sm_config_set_clkdiv(&smc_wr, 16); // init the pio sm with the config pio_sm_init(pio, sm_rd, offset_rd, &smc_rd); pio_sm_init(pio, sm_wr, offset_wr, &smc_wr); // enable the sm pio_sm_set_enabled(pio, sm_rd, true); pio_sm_set_enabled(pio, sm_wr, true); } // in the current version the address bus is only 8 bits // the first half is ROM, the second half is RAM uint8_t ROM[128]; uint8_t RAM[128]; int main() { // set the voltage a bit higher than default vreg_set_voltage(0b1100); // 1.15v // overclock to 270MHz set_sys_clock_khz(270000, true); // fill the ROM and RAM with numbers uint8_t num; for (num=0; num<128; num++) { ROM[num] = num; RAM[num] = 128+num; } // needed for printf stdio_init_all(); // initialize the state machine configure_pio_sm(); // print the gpio assignments printf("D0=%d\n", D0); printf("A0=%d\n", A0); printf("RD=%d\n", RD); printf("WR=%d\n", WR); printf("DIR=%d\n", DIR); printf("OE=%d\n", OE); // a counter to simulate the data in memory (or after some calculations) uint16_t counter = 0; // the received data from pio is a combination of the data and the address uint32_t addr_data; // the data part uint8_t data; // the address part uint16_t address; // used for checking whether the sm has returned to the default state uint8_t current_pc; // start in the default situation // Note: if this is in reality set by an external system (e.g. via pullup/down resistors, ths can be removed) pio_sm_exec(pio, sm_rd, offset_rd + Z80_read_offset_set_default); pio_sm_exec(pio, sm_wr, offset_wr + Z80_write_offset_set_default); uint bitoffs; const uint32_t mask = PIO_FLEVEL_RX0_BITS >> PIO_FLEVEL_RX0_LSB; while (1) { // test if there is something in RxFIFO of sm0 (a read) bitoffs = PIO_FLEVEL_RX0_LSB + sm_rd * (PIO_FLEVEL_RX1_LSB - PIO_FLEVEL_RX0_LSB); if ((pio->flevel >> bitoffs) & mask > 0) { // get the data from the RxFIFO addr_data = pio->rxf[sm_rd]; // the lowest 8 bits are the data and the next 8 bits are the address // data = addr_data & 0xFF; address = addr_data >> 8; // determine if it is ROM or RAM, then get the value from ROM or RAM if (address<128) { data = ROM[address]; printf("read ROM address %d results in %d\n", address, data); } else { data = RAM[address-128]; printf("read RAM address %d results in %d\n", address-128, data); } // send the data to the pio for writing it to the data bits pio->txf[sm_rd] = data; } // test if there is something in RxFIFO of sm1 (a write) bitoffs = PIO_FLEVEL_RX0_LSB + sm_wr * (PIO_FLEVEL_RX1_LSB - PIO_FLEVEL_RX0_LSB); if ((pio->flevel >> bitoffs) & mask > 0) { // get the data from the RxFIFO addr_data = pio->rxf[sm_wr]; data = addr_data & 0xFF; address = addr_data >> 8; if (address<128) { printf("ROM not writable\n"); } else { RAM[address-128] = data; printf("Wrote %d to RAM address %d\n", data, address-128); } } } } ================================================ FILE: Z80/Z80.pio ================================================ ; This pio programm allows a Z80 bus to: ; - read from the RPI pico by providing an address (read_data) ; - write a value to the RPI pico to a memory address (write_data) ; set the pin assignment: ; The starting point of the 8 bit Data D0 - D7 .define PUBLIC D0 2 ; The starting point of the 16 bit Address A0 - A15 .define PUBLIC A0 (D0 + 8) ; Read bus flag .define PUBLIC RD (A0 + 8 + 0) ; Write bus flag .define PUBLIC WR (A0 + 8 + 1) ; Direction of level shifter .define PUBLIC DIR (A0 + 8 + 2) ; Output enable of level shifter .define PUBLIC OE (A0 + 8 + 3) .program Z80_read public set_default: ; set pins: MSB = OE, LSB = DIR, so 0b10 is OE=1 and DIR=0 ; set the default: OE=1 and DIR=0 set pins 0b10 ; wait for RD to become 0 wait 0 GPIO RD ; read the data (8 bits) and addres (8 bits) in pins 16 ; push the data and address to the RxFIFO push ; get the new data to set as output pull block ; set the data bits on the bus out pins 8 ; set OE=0 and DIR=0 set pins 0b00 ; wait for RD to become 1, then wait 3 cycles wait 1 GPIO RD [3] ; go back to the default state jmp set_default .program Z80_write public set_default: ; set pins: MSB = OE, LSB = DIR, so 0b10 is OE=1 and DIR=0 ; set the default: OE=1 and DIR=0 set pins 0b10 ; wait for WR to become 0 wait 0 GPIO WR ; set OE=0 and DIR=0 set pins 0b00 ; read the data (8 bits) and addres (8 bits) in pins 16 ; push the data and address to the RxFIFO push ; wait for WR to become 1, then wait 3 cycles wait 1 GPIO WR [3] ; go back to the default state jmp set_default ================================================ FILE: blow_out_a_LED/CMakeLists.txt ================================================ add_executable(blow_led) target_sources(blow_led PRIVATE blow_led.cpp) target_link_libraries(blow_led PRIVATE pico_stdlib hardware_adc hardware_dma ) ================================================ FILE: blow_out_a_LED/README.md ================================================ # Blow out a(n) LED This is remake of the wonderful little thingy made by Paul Dietz. See this [Hackaday article](https://hackaday.com/2018/08/21/an-led-you-can-blow-out-with-no-added-sensor/) and the [github code](https://github.com/paulhdietz/LEDSensors/blob/master/_07_BlowOutLED/_07_BlowOutLED.ino). It lights a LED and when you blow on it, it switches off for one second, then lights again. Imagine blowing out a LED! From the hackaday article: "Turning the LED on warms it up and blowing on it cools it off, causing measurable changes in the voltage drop across the device. The change isn’t much — only a handful of millivolts — but the effect is consistent and can be measured. " Where his code is rather simple and elegant, mine is convoluted. Why? Because I wanted to play with the DMA sniffer! The DMA sniffer allows summing of all values that pass through the DMA. So, there is no need to sum the values yourself afterwards. The code does the following: - it turns the LED on for at least 1s to warm it up - it starts a repeat timer to call "repeating_timer_callback" - in that callback the adc is read NUM_ADC_SAMPLES times through dma, the samples are summed by the dma sniffer - if the read summed value deviates sufficiently from the average, a flag is set to switch the LED off - an array of obtained summed values is kept up to date to determine the average of the sums - in the main loop the flag is checked and when set the LED goes off for 1s and then 1s on to warm it up ================================================ FILE: blow_out_a_LED/blow_led.cpp ================================================ /* This is remake of the wonderful little thingy made by Paul Dietz. See: https://hackaday.com/2018/08/21/an-led-you-can-blow-out-with-no-added-sensor/ https://github.com/paulhdietz/LEDSensors/blob/master/_07_BlowOutLED/_07_BlowOutLED.ino From the hackaday article: "Turning the LED on warms it up and blowing on it cools it off, causing measurable changes in the voltage drop across the device. The change isn’t much — only a handful of millivolts — but the effect is consistent and can be measured. " Where his code is rather simple and elegant, mine is convoluted. Why? Because I wanted to play with the DMA sniffer! The DMA sniffer allows summing of all values that pass through the DMA. So, there is no need to sum the values yourself afterwards. The code does the following: - it turns the LED on for at least 1s to warm it up - it starts a repeat timer to call "repeating_timer_callback" - in that callback the adc is read NUM_ADC_SAMPLES times through dma, the samples are summed by the dma sniffer - an array of the obtained sums is kept up to date to determine the average of the sums - if the read summed value deviates sufficiently from the average, a flag is set to switch the LED off - in the main loop the flag is checked and when set the LED goes off for 1s and then 1s on to warm it up */ #include #include #include "pico/stdlib.h" #include "hardware/gpio.h" #include "hardware/adc.h" #include "hardware/dma.h" // LED Connections: PLUS pin - resistor - ADC pin - LED - GND #define PLUS 22 // pin for ADC goes between resistor and LED #define MEASURE 26 // Number of adc samples to take. Only used in dma_channel_configure #define NUM_ADC_SAMPLES 512 // Number of samples to determine the average #define NUM_AV_SAMPLES 64 // Minimum jump for blow out // depends on the resistor value and type of LED #define BLOWN 400 // the array with NUM_ADC_SAMPLES summed adc samples float summed_adc_values[NUM_AV_SAMPLES]; // keeps track of where in the summed_adc_values array the new value is to be written uint8_t sample_num; // dma channel number uint dma_chan; // the total of the summed_adc_values array float total; // flag to indicate it the led should be turned off bool light_out; // the variable into which the dma channel dumps all adc readings! uint32_t temp; // the callback function that is called automatically (see add_repeating_timer_ms below) bool repeating_timer_callback(struct repeating_timer *t) { // start the sniff sum at 0 dma_hw->sniff_data = 0; // start the dma_channel. NOTE: it doesn't write to a buffer, it writes to a single variable // the samples are summed by the sniffer functionality! dma_channel_set_write_addr(dma_chan, &temp, true); // wait for it to finish dma_channel_wait_for_finish_blocking(dma_chan); // check whether the deviations of the current sample deviates more than BLOWN from the average // the value of BLOWN depends on many things, e.g. resistor, type of LED, NUM_AV_SAMPLES, and NUM_ADC_SAMPLES // the print statement can be used to determine which BLOWN value will work for you // printf("%f\n", (total / 64.) - summed_adc_values[sample_num]); if (((total / 64.) - dma_hw->sniff_data) > BLOWN) light_out = true; // save the summed adc values in the summed_adc_values array // total is the sum of all elements in that array total -= summed_adc_values[sample_num]; summed_adc_values[sample_num] = dma_hw->sniff_data; total += summed_adc_values[sample_num]; // make the array a ring if (++sample_num == NUM_AV_SAMPLES) sample_num = 0; return true; } int main() { // needed for printf stdio_init_all(); // switch on the LED and let it warm up gpio_init(PLUS); gpio_set_dir(PLUS, GPIO_OUT); gpio_put(PLUS, 1); sleep_ms(1000); // initialize some variables light_out = false; sample_num = 0; total = 0; for (int i = 0; i < NUM_AV_SAMPLES; i++) summed_adc_values[i] = 0; // ========== ADC setup =================== adc_init(); adc_fifo_setup( true, // Write each completed conversion to the sample FIFO true, // Enable DMA data request (DREQ) 1, // DREQ (and IRQ) asserted when at least 1 sample present false, // disable ERR bit false // Do not shift each sample to 8 bits when pushing to FIFO, i.e. keep it 12 bit resolution ); // Divisor of 0 -> full speed. adc_set_clkdiv(0); // start the adc adc_run(true); // ========== DMA setup =================== // Set up the DMA to start transferring data as soon as it appears in FIFO dma_chan = dma_claim_unused_channel(true); dma_channel_config cfg = dma_channel_get_default_config(dma_chan); // data size is 32 bits (it should be at least 12) channel_config_set_transfer_data_size(&cfg, DMA_SIZE_32); // Reading from AND WRITING to a constant address channel_config_set_read_increment(&cfg, false); channel_config_set_write_increment(&cfg, false); // Pace transfers based on availability of ADC samples channel_config_set_dreq(&cfg, DREQ_ADC); // configure sniff to automatically sum (mode=0xf) all the data dma_sniffer_enable(dma_chan, 0xf, true); // enable sniff channel_config_set_sniff_enable(&cfg, true); // configure the channel dma_channel_configure(dma_chan, &cfg, NULL, // dst &adc_hw->fifo, // src NUM_ADC_SAMPLES, // transfer count false // start immediately ); // ===========TIMER setup ================= struct repeating_timer timer; add_repeating_timer_ms(10, repeating_timer_callback, NULL, &timer); // ======================================== while (true) { if (light_out) { // switch the LED off gpio_put(PLUS, 0); sleep_ms(1000); // switch it back on and let it warm up gpio_put(PLUS, 1); sleep_ms(1000); // clear flag light_out = false; } } } ================================================ FILE: button_matrix_4x4/4x4_button_matrix.cpp ================================================ #include #include "pico/stdlib.h" #include "hardware/pio.h" #include "4x4_button_matrix.pio.h" // class that sets up and reads the 4x4 button matrix class button_matrix_4x4 { public: // constructor // base_input is the starting gpio for the 4 input pins // base_output is the starting gpio for the 4 output pins button_matrix_4x4(uint base_input, uint base_output) { // pio 0 is used pio = pio0; // state machine 0 sm = 0; // configure the used pins for (int i = 0; i < 4; i++) { // output pins pio_gpio_init(pio, base_output + i); // input pins with pull down pio_gpio_init(pio, base_input + i); gpio_pull_down(base_input + i); } // load the pio program into the pio memory uint offset = pio_add_program(pio, &button_matrix_program); // make a sm config pio_sm_config c = button_matrix_program_get_default_config(offset); // set the 'in' pins sm_config_set_in_pins(&c, base_input); // set the 4 output pins to output pio_sm_set_consecutive_pindirs(pio, sm, base_output, 4, true); // set the 'set' pins sm_config_set_set_pins(&c, base_output, 4); // set shift such that bits shifted by 'in' end up in the lower 16 bits sm_config_set_in_shift(&c, 0, 0, 0); // init the pio sm with the config pio_sm_init(pio, sm, offset, &c); // enable the sm pio_sm_set_enabled(pio, sm, true); } // read the 4x4 matrix int read(void) { // value is used to read from the fifo uint32_t value = 0; // clear the FIFO, we only want a currently pressed key pio_sm_clear_fifos(pio, sm); // give the sm some time to fill the FIFO if a key is being pressed sleep_ms(1); // check that the FIFO isn't empty if (pio_sm_is_rx_fifo_empty(pio, sm)) { return -1; } // read one data item from the FIFO value = pio_sm_get(pio, sm); // translate from a bit position in value to a key number from 0 to 15. for (int i = 0; i < 16; i++) { // test a bit to see if it is set if ((value & (0x1 << i)) != 0) { //the bit is set -> return 'i' as the key number return i; } } return -1; } private: // the pio instance PIO pio; // the state machine uint sm; }; int main() { // needed for printf stdio_init_all(); // the instance of the button matrix: input gpio: 10, 11, 12, and 13, output gpio: 18, 19, 20, and 21 button_matrix_4x4 my_matrix(10, 18); // infinite loop to print pressed keys while (true) { // read the matrix of buttons int key = my_matrix.read(); if (key >= 0) { // a key was pressed: print its number printf("key pressed = %d\n", key); } sleep_ms(1000); } } ================================================ FILE: button_matrix_4x4/4x4_button_matrix.pio ================================================ ; in the c-program the 'set' pins are the 4 output pins to the 4x4 button matrix ; in the c-program the 'in' pins are the 4 input pins from the 4x4 button matrix, these are pulled_down .program button_matrix start: set pins 1 [31] ; set 0001 on the 4 output pins (activate first row) and wait for the signal to stabilize in pins 4 ; shift the input pins into the ISR set pins 2 [31] ; set 0010 on the 4 output pins (activate second row) and wait for the signal to stabilize in pins 4 ; shift the input pins into the ISR set pins 4 [31] ; set 0100 on the 4 output pins (activate third row) and wait for the signal to stabilize in pins 4 ; shift the input pins into the ISR set pins 8 [31] ; set 1000 on the 4 output pins (activate fourth row) and wait for the signal to stabilize in pins 4 ; shift the input pins into the ISR mov x ISR ; copy the ISR into the x scratch register jmp !x start ; if the x contains 0, no key was pressed, start over push noblock ; a key was pressed, push the ISR into the RX FIFO jmp start ; start over ================================================ FILE: button_matrix_4x4/CMakeLists.txt ================================================ add_executable(4x4_button_matrix) pico_generate_pio_header(4x4_button_matrix ${CMAKE_CURRENT_LIST_DIR}/4x4_button_matrix.pio) target_sources(4x4_button_matrix PRIVATE 4x4_button_matrix.cpp) target_link_libraries(4x4_button_matrix PRIVATE pico_stdlib hardware_pio ) pico_add_extra_outputs(4x4_button_matrix) # add url via pico_set_program_url example_auto_set_url(4x4_button_matrix) ================================================ FILE: button_matrix_4x4/README.md ================================================ # 4x4 button matrix This code reads a 4x4 button matrix using PIO code for the Raspberry Pico. In the image below a simple 4x4 button matrix is shown. Four of its pins are connected to the rows (from the PIO code perspective these are outputs) and the other 4 are connected to its columns (used as pulled down inputs). ![](4x4_button_matrix.jpg) The PIO code alternatingly sets one row to HIGH and the others to LOW. The four column pins are read by the 'in pins 4' instruction. If no button is pressed, all columns read LOW, i.e. '0000' is shifted into the ISR. If a button is pressed in the row that is HIGH, the corresponding column is also read as HIGH. If, after all 4 rows have had their turn of being set to HIGH, the ISR contains only zeros, no key was pressed. If it isn't zero, a button was pressed (at least one), and the ISR is pushed into the state machine's RX FIFO. In the C/C++ code, when reading the RX FIFO it first needs to be cleared in order to not read old key presses. The consequence is that the state machine needs a little time (1 ms in the code) to fill the FIFO if a key is being pressed. If the FIFO is still empty, no key is pressed. If the FIFO does contain a value, the key is available as a bit in some position in the lower 16 bits. This is translated into a key value from 0 to 15. ================================================ FILE: count_pulses_with_pause/CMakeLists.txt ================================================ add_executable(count_pulses_with_pause) pico_generate_pio_header(count_pulses_with_pause ${CMAKE_CURRENT_LIST_DIR}/count_pulses_with_pause.pio) target_sources(count_pulses_with_pause PRIVATE count_pulses_with_pause.cpp) target_link_libraries(count_pulses_with_pause PRIVATE pico_stdlib hardware_pio ) pico_add_extra_outputs(count_pulses_with_pause) # add url via pico_set_program_url # example_auto_set_url(count_pulses_with_pause) ================================================ FILE: count_pulses_with_pause/README.md ================================================ # Counting pulses in a pulse train separated by a pause This class can be used for protocols where the data is encoded by a number of pulses in a pulse train followed by a pause. E.g. the LMT01 temperature sensor uses this, [see](https://www.reddit.com/r/raspberrypipico/comments/nis1ew/made_a_pulse_counter_for_the_lmt01_temperature/). ================================================ FILE: count_pulses_with_pause/count_pulses_with_pause.cpp ================================================ #include #include "pico/stdlib.h" #include "hardware/irq.h" #include "hardware/pio.h" #include "count_pulses_with_pause.pio.h" /* This class can be used for protocols where the data is encoded by a number of pulses in a pulse train followed by a pause. E.g. the LMT01 temperature sensor uses this (see https://www.reddit.com/r/raspberrypipico/comments/nis1ew/made_a_pulse_counter_for_the_lmt01_temperature/) The class itself only starts the state machine and, when called, read_pulses() gives the data the state machine has put in the Rx FIFO */ class count_pulses_with_pause { public: // input = pin that receives the pulses. count_pulses_with_pause(uint input) { // pio 0 is used pio = pio0; // state machine 0 sm = 0; // configure the used pin pio_gpio_init(pio, input); // load the pio program into the pio memory uint offset = pio_add_program(pio, &count_pulses_with_pause_program); // make a sm config pio_sm_config c = count_pulses_with_pause_program_get_default_config(offset); // set the 'jmp' pin sm_config_set_jmp_pin(&c, input); // set the 'wait' pin (uses 'in' pins) sm_config_set_in_pins(&c, input); // set shift direction sm_config_set_in_shift(&c, false, false, 0); // init the pio sm with the config pio_sm_init(pio, sm, offset, &c); // enable the sm pio_sm_set_enabled(pio, sm, true); } // read the number of pulses in a pulse train uint32_t read_pulses(void) { // clear the FIFO: do a new measurement pio_sm_clear_fifos(pio, sm); // wait for the FIFO to contain a data item while (pio_sm_get_rx_fifo_level(pio, sm) < 1) ; // read the Rx FIFO and return the value: the number of pulses measured return (pio_sm_get(pio, sm)); } private: // the pio instance PIO pio; // the state machine uint sm; }; int main() { // needed for printf stdio_init_all(); // the instance of the count_pulses_with_pause. Note the input pin is 28 in this example count_pulses_with_pause my_count_pulses_with_pause(28); // infinite loop to print pulse measurements while (true) { int num_pulses = my_count_pulses_with_pause.read_pulses(); printf("number of pulses = %zu\n", num_pulses); sleep_ms(100); } } ================================================ FILE: count_pulses_with_pause/count_pulses_with_pause.pio ================================================ .program count_pulses_with_pause ; algorithm: ; This PIO program counts the number of pulses in a pulse train. ; In the settings used below, two pulse trains must be separated by at least 40ms ; ; There are two counters: ; One counts the number of pulses observed in a pulse train (the pulse_counter, placed in the y-register) ; One counts how much time has passed after a pulse (the time_counter, placed in the x-register) ; ; If the time_counter has measured 40ms it is assumed the pulse train has ended. ; Then pulse_counter is placed in the Rx FIFO and the pio program starts over. ; ; 'Measuring' the 40ms is done by counting how many instructions have been executed ; in the loop that measures if a pulse is present on the input pin. ; The RPI pico is assumed to run at 125MHz, so 40ms is 5000000 instruction steps. ; The test loop is 2 instructions (jmp PIN and jmp x--), so the time_counter must be 2500000. ; Since the 40ms doesn't need to be precise, this can be approximated by placing 19 in the x and shifting in 17 zeros. ; 2500000 in binary is 1001100010010110100000, which is approximately 10011 with 17 zeros, 10011 is 19 in decimal. start: ; set pulse_counter to 0 (counting negatively!) mov y ~NULL start_time_counter: ; set time_counter to 10011 << 17 set x 19 mov ISR x in NULL 17 mov x ISR test: ; test if there is a pulse (a 1 on the pin) jmp PIN during_pulse ; no pulse is currently observed: decrement time_counter and test again jmp x-- test ; time_counter has reached 0: a pause has happened ; place the pulse counter in the Rx FIFO mov ISR ~y push ; start over jmp start during_pulse: ; a pulse is in progress, wait till the pulse is over wait 0 PIN 0 ; increase the number of pulses observed jmp y-- during_pulse2 ; assume y never reaches 0 during_pulse2: ; restart the time counter jmp start_time_counter ================================================ FILE: example_auto_set_url.cmake ================================================ set(PICO_EXAMPLE_URL_BASE "https://github.com/raspberrypi/pico-examples/tree/HEAD") macro(example_auto_set_url TARGET) file(RELATIVE_PATH URL_REL_PATH "${PICO_EXAMPLES_PATH}" "${CMAKE_CURRENT_LIST_DIR}") pico_set_program_url(${TARGET} "${PICO_EXAMPLE_URL_BASE}/${URL_REL_PATH}") endmacro() ================================================ FILE: handy_bits_and_pieces/README.md ================================================ # Handy bits and pieces of PIO code On this page I list some of the things that I found out while making the projects. If you know of better ways of doing things, or if you have additional 'tricks', please let me know! ## Setting the x (or y) scratch registers to 0xFFFFFFFF The x register can be used for counting, but PIO code only has one instruction (`jmp x-- label`) that can actually subtract one from the value in x. Setting the x (or y) registers to a value from 0 to 31 can be done with the `set` command. That command has 5 bits for a number. For counters it is handy to have much higher values to start with. One approach is to start the x with the highest value available in 32 bits: 0xFFFFFFFF and count down. This can be achieved with the instruction: ``` mov x ~NULL ``` If the C-program needs a number counting down from 0xFFFFFFFF to 0, you can use: ``` start: mov x ~NULL ; set the x register to 0xFFFFFFFF push_it: mov ISR x ; copy x to the ISR push block ; wait for the C-program to read it from the Rx FIFO jmp x-- push_it ; decrement x and loop if not zero jmp start ; x = 0 -> start over ``` ## Counting up instead of down As stated above, PIO only has one instruction to count down: `jmp x-- label` (which can also use y). Counting up isn't possible. However, bitwise inverting the value in x results in its complement. Thus we can start a loop at 0xFFFFFFFF and count down, but use the inverted value in x, which results in counting from 0 to 0xFFFFFFFF. If the C-program needs a number counting up from 0 to 0xFFFFFFFF, you can use: ``` start: mov x ~NULL ; set the x register to 0xFFFFFFFF push_it: mov ISR ~x ; copy its complement to the ISR push block ; wait for the C-program to read it from the Rx FIFO jmp x-- push_it ; decrement x and loop if not zero jmp start ; x = 0 -> start over ``` Note the tilde '~' before the x in the line `mov ISR ~x`. ## Delay If you need to have a small delay in PIO code the delay_value can be set to a number of cycles between 0 and 31. If, however, a (much) larger delay is required, the following can be used: A counter in the x (or y) register can be started at the required number of delay cycles, and then counted down in a tight loop. Note that you need to subtract the amount of cycles that setting up the counter takes. This same approach can be used to create a clock-cycle precise delay for any number between 0x00000000 and 0xFFFFFFFF. This can be done by setting x and shifting it into the ISR, 5 bits at a time, several times. For example, a delay loop of 682 instructions is needed (in binary: 1010101010), this can be achieved in the following way: ``` set x 21 ; in binary 10101, the first 5 bits of 1010101010 mov ISR x ; copy x into the ISR set x 10 ; in binary 01010, the 5 least significant bits of 1010101010 in x 5 ; shift the least significant 5 bits of x into the ISR mov x ISR ; place the ISR in x as the start value of the counter delay_loop: ; the actual delay loop jmp x-- delay_loop ``` Note that setting up the starting value in the x register took 5 instructions, so in total this will result in a delay of 682+5 = 687 clock cycles. Also note that left-shifting is used. If the timing doesn't have to be precise, rounding down (or up) after the 5 most significant bits and shifting in further 0's can save instructions. For example if a delay of approximately 7500000 clock cycles is needed (in binary 11100100111000011100000), using only the first 5 most significant digits and setting the rest (18 bits) to 0, may suffice. This results in: ``` set x 28 ; set x to 11100 mov ISR x ; copy x into the ISR in NULL 18 ; shift in 18 more 0 bits mov x ISR ; move the ISR to x delay_loop: ; the delay loop jmp x-- delay_loop ``` ## Measuring 'time' To measure the duration of some event, can be done by starting the x (or y) scratch register with 0xFFFFFFFF and counting down, testing for the stop criterion each iteration. The counting down loop with test for the stop criterion looks like this: ```pio mov x ~NULL ; start with 0xFFFFFFFF timer: jmp x-- test ; count down, and jump to testing jmp timerstop ; timer has reached 0, stop count down test: jmp pin timer ; <---- insert the test criterion here timerstop: ; The test criterion was met (or timer has reached 0) mov ISR ~x ; move the bit inverted value of x to the ISR push noblock ; push the ISR into the RX FIFO ``` The `jmp x-- test` tests if x is zero, and if not decrements it and jumps to testing for the stop criterion (here the `jmp` pin going low). If the criterion fails, a jmp is made to `timer`. If the criterion is met, or if the x register becomes 0, the bit-inverted value of x is moved to the ISR, and is then pushed to the Rx FIFO. Here the counting down and the test itself only take two instructions (`jmp x-- test` and `jmp pin timer`). Therefore the resolution of measuring the time with this code is 2 * pio clock tick, which for the standard clock is `2 / 125 MHz = 0.000000016` seconds. If the test takes more than one instruction, the resolution becomes lower. ## Change shifting direction in PIO code When configuring a sm from c-code, you can configure the shift direction of the ISR: for left shifting: ``` sm_config_set_in_shift(&c, false, false, 0); ``` for right shifting: ``` sm_config_set_in_shift(&c, true, false, 0); ``` If you need to change the shifting direction in PIO code, you can use: ``` mov ISR :: ISR in ISR 1 mov ISR :: ISR ``` The `::` symbol reverses the bit order. In this example, shifting is only for one bit, but other bit counts are also possible: `in ISR Bit_count`, with Bit_count between 1 and 32, 32 is encoded as 00000. ================================================ FILE: ledpanel/CMakeLists.txt ================================================ add_executable(ledpanel) pico_generate_pio_header(ledpanel ${CMAKE_CURRENT_LIST_DIR}/ledpanel.pio) target_sources(ledpanel PRIVATE ledpanel.c ledpanel_worker.c) target_link_libraries(ledpanel PRIVATE pico_stdlib hardware_pio hardware_dma hardware_irq hardware_interp pico_multicore ) ================================================ FILE: ledpanel/README.md ================================================ # LED panel using PIO state machine and Direct Memory Access This code shows how a pio state machine can drive led panels. It is made for two 64x64 led panels connected to form a 64 row x 128 column panel. There are 16 brightness levels for each Red, Green and Blue of each pixel, allowing many colors. In its present form it updates the panel at about 144 Hz at standard clock settings (=125MHz.) The main function has code for three test patterns. There are many, many concepts that require explanation, and I do a poor job of it in the code and header file. Some of the things that I would like to (but do not) explain in detail here are: * it uses two cores: core 0 generates images, core 1 transcodes it into a data format suitable for sending it to the PIO state machine (sm). * after transcoding, the sm continuously sends the data to the ledpanel. It uses Direct Memory Access (DMA) for this. * double buffering is used for generating images (but not for the transcoded data structure!) * care has been taken that every generated image is displayed. * the images have 4 bits for each color (green, blue, red), it uses a sort of Pulse Width Modulation (PWM) to get different brightness levels for the colors and thus each pixel can have 16 * 16 * 16 = 4096 colors. See [here](https://www.galliumstudio.com/2020/04/07/stm32-episode-3-color-tricks-with-led-panels/) for an explanation of the PWM approach. * there is also an overall brightness setting. It takes values from 0 (dimmest) to 7 (brightest). * brightness ultimately comes down to how long a delay loop is in the sm code. * the file 'ledpanel.c' contains the code for core 0. It mostly consists of some simple (and simplistic) functions to make the images. In the main function three example functions are given. * the file 'ledpanel_worker.c' contains the code for core 1: transcoding and controlling the sm via DMA. * the sm outputs the address and color bits to the ledpanel and executes the delay loop that determines the brightness. * I used the interpolator hardware! This must be one of the few examples that uses it. And I do not use it for what it was intended for: it just reorders some bits, see the (somewhat vague) explanation in 'ledpanel_worker.c'. This image shows the first of the three example animations in the main function: a skewed rainbow that shifts position with time: ![](ledpanels.jpg) ## Some words about the timing of the various functions In the figure below the data of three logical analyzer channels are shown (many thanks to [Saleae](https://www.saleae.com/)) for the first example in the main function (the skewed rainbow): The top line shows when the DMA interrupt is busy. This interrupt restarts the DMA channel such that the sm can send data to the ledpanel. The restarting of the DMA takes very little time, so only a peak is visible in the figure. The important thing is the time between those peaks (the 1 in the figure). At the highest brightness level that I use, the peaks are 6.93 ms apart, this translates into a display update frequency of 144 Hz. The bottom line is the process generating the images on core 0. It is clear that generating images is the slowest process, see 3 in the figure. Generating the rainbow image costs more than 55ms. The channel in the middle is the transcoding function on core 1. It takes almost 7ms to transcode an image, see 2 in the figure. ![](ledpanel_timing_1.png) In the figure below the same channels are shown for the second example in the main function: red and blue gradients overlapping for 8 overall brightness levels. At the lowest brightness level the updating of the ledpanel (top channel) takes 590 us, at 1 in the figure, while one brightness level higher takes 640 us (at 2 in the figure). At the highest brightness level it is 6.93 ms. Generating the figure takes almost 6 ms, the pulse at 4 in the figure. Note that in the code there is a 1 second pause between generating images at different brightness levels. ![](ledpanel_timing_2.png) In the figure below the timing for the third example in the main function (three moving lines) is shown. Generating a new image takes little time: 1.15 ms, see 1 in the figure. The transcoding, 2 in the figure, is continuously busy. The code has been made such that all images are displayed: core 0 has to wait for core 1 to finish transcoding. ![](ledpanel_timing_3.png) ================================================ FILE: ledpanel/ledpanel.c ================================================ #include "stdio.h" #include "pico/stdlib.h" #include "hardware/pio.h" #include "hardware/dma.h" #include "hardware/irq.h" #include "hardware/interp.h" #include "ledpanel.pio.h" #include "pico/multicore.h" #include "ledpanel.h" // Overall brightness: a number from 0 (dim) to 7 (brightest) uint overall_brightness = 7; // the image to be displayed (a pointer) and the two actual variables for double buffering uint currently_drawing = 1; uint (*image)[num_of_displays * columns_of_display]; uint image1[rows_of_display][num_of_displays * columns_of_display]; uint image2[rows_of_display][num_of_displays * columns_of_display]; // 0 = no image ready or processing finished // 1 = producer (core 0, in this file) has finished producing image 1 // 2 = producer (core 0)) has finished producing image 2 uint image_ready = 0; uint image_processing = 0; /****************************************************************************** * some simple routines to draw the image for the test patterns *****************************************************************************/ // set the color + brightness for a pixel void set_pixel(uint row, uint column, uint c, uint brightness) { // leave the other two colors untouched to allow mixing if (c == R) // Red: do not shift image[row][column] |= brightness; else if (c == G) // Green: shift over 8 bits image[row][column] |= brightness << 8; else // Blue: shift 4 bits image[row][column] |= brightness << 4; } // set the individual colors + brightnesses for a pixel void set_pixel_c(uint row, uint column, uint R_brightness, uint G_brightness, uint B_brightness) { image[row][column] = R_brightness | G_brightness << 8 | B_brightness << 4; } // draw a horizontal line void row_line(uint row, uint column_start, uint column_end, uint c, uint brightness) { for (uint column = column_start; column < column_end; column++) set_pixel(row, column, c, brightness); } // draw a vertical line void column_line(uint column, uint row_start, uint row_end, uint c, uint brightness) { for (uint row = row_start; row < row_end; row++) set_pixel(row, column, c, brightness); } // make an empty image void clear_image() { for (uint x = 0; x < rows_of_display; x++) for (uint y = 0; y < num_of_displays * columns_of_display; y++) image[x][y] = 0; } // color wheel (from Adafruit strand test) // Input a value 0 to 255 to get a color value. // The colors returned are a transition r - g - b - back to r. void wheel(uint wheel_pos, uint *RC, uint *GC, uint *BC) { if (wheel_pos < 85) { *RC = wheel_pos * 3; *GC = 255 - wheel_pos * 3; *BC = 0; } else if (wheel_pos < 170) { wheel_pos -= 85; *BC = wheel_pos * 3; *RC = 255 - wheel_pos * 3; *GC = 0; } else { wheel_pos -= 170; *GC = wheel_pos * 3; *BC = 255 - wheel_pos * 3; *RC = 0; } } // switch image buffers to be drawn (dubbelbuffering) // However, wait if the process on core1 is still busy with a buffer void switch_buffer() { image_ready = currently_drawing; if (currently_drawing == 1) { while (image_processing == 2) ; image = image2; currently_drawing = 2; } else { while (image_processing == 1) ; image = image1; currently_drawing = 1; } } /****************************************************************************** * the main function: initializes everything and starts a loop of test patterns *****************************************************************************/ int main() { // start the dubbel buffer with image1 image = image1; currently_drawing = 1; // TODO: remove (only for testing purposes) gpio_init(timing_pin_build_image); gpio_set_dir(timing_pin_build_image, GPIO_OUT); // needed for printf stdio_init_all(); // start with an empty image clear_image(); // Start core 1. Core 1 continuously transcodes the image // to a format that can be used with the pio code on the // state machine.. multicore_launch_core1(core1_worker); // show the test patterns forever while (true) { // /* // // Test pattern 3: make the rainbow pattern that shifts with time // for (int current_t = 0; current_t < 255; current_t++) { gpio_put(timing_pin_build_image, 1); // TODO: remove (only for testing purposes) uint r, g, b; // make a rainbow pattern for (uint row = 0; row < rows_of_display; row++) for (uint column = 0; column < num_of_displays * columns_of_display; column++) { wheel((current_t + row + column) % 256, &r, &g, &b); // Note: green is a bit too strong on my panels -> lower the multiplication factor set_pixel_c(row, column, (int)(r * 0.0627), (int)(g * 0.04), (int)(b * 0.0627)); } gpio_put(timing_pin_build_image, 0); // TODO: remove (only for testing purposes) // signal to core 1 that the image is ready, and switch image buffer switch_buffer(); } // */ // /* // // Test pattern 2: draw red and blue planes with 16 different intensity levels and change the overall brightness // // change the overall brightness for (uint b = 0; b <= 7; b++) { gpio_put(timing_pin_build_image, 1); // TODO: remove (only for testing purposes) clear_image(); uint prev_fraction = 0; uint next_fraction = 0; // Red color planes with brightness in 16 levels (4 bits) for (uint fraction = 1; fraction <= 16; fraction++) { next_fraction = (int)((float)fraction / 16. * (num_of_displays * columns_of_display)); for (uint current_y = prev_fraction; current_y < next_fraction; current_y++) column_line(current_y, 0, rows_of_display, R, fraction - 1); prev_fraction = next_fraction; } prev_fraction = 0; // Blue color planes with brightness in 16 levels (4 bits) for (uint fraction = 1; fraction <= 16; fraction++) { next_fraction = (int)((float)fraction / 16. * rows_of_display); for (uint current_x = prev_fraction; current_x < next_fraction; current_x++) row_line(current_x, 0, num_of_displays * columns_of_display, B, fraction - 1); prev_fraction = next_fraction; } overall_brightness = b; gpio_put(timing_pin_build_image, 0); // TODO: remove (only for testing purposes) // signal to core 1 that the image is ready, and switch image buffer switch_buffer(); // pause 1 seconds sleep_ms(1000); } // */ // /* // // Test pattern 1: moving lines // // repeat this test pattern a number of times for (int count = 0; count < 5; count++) { for (uint current_t = 0; current_t < num_of_displays * columns_of_display; current_t++) { gpio_put(timing_pin_build_image, 1); // TODO: remove (only for testing purposes) uint current_x = current_t % rows_of_display; uint current_y = current_t; // prepare an empty image clear_image(); // draw red, green and blue lines at high brightness column_line(current_y, 0, rows_of_display, R, 15); column_line((current_y + 64) % (num_of_displays * columns_of_display), 0, rows_of_display, G, 15); row_line(current_x, 0, num_of_displays * columns_of_display, B, 15); gpio_put(timing_pin_build_image, 0); // TODO: remove (only for testing purposes) // signal to core 1 that the image is ready, and switch image buffer switch_buffer(); } } // */ } } ================================================ FILE: ledpanel/ledpanel.h ================================================ #ifndef LEDPANELH #define LEDPANELH /* This code shows how a pio state machine can work with led panels. It is made for two 64x64 led panels connected to form a 64 row x 128 column panel. There are 16 brightness levels for each Red, Green and Blue of each pixel, allowing many colors. In addition there is a 8 level overall brightness. In its present form it updates the panel at about 144 Hz at standard clock settings (=125MHz.) Concept of operation The user prepares an image. In this code this happens on core 0. The main function has code for three test patterns, that each produce several images to make a small animation. These images are transcoded on core 1 to a datastructure suitable for the pio state machine (sm) to control the display. The pio state machine (sm) continuously updates the display via direct memory access (DMA). */ #include "stdio.h" #include "pico/stdlib.h" #include "hardware/pio.h" #include "hardware/dma.h" #include "hardware/irq.h" #include "ledpanel.pio.h" #include "pico/multicore.h" /****************************************************************************** * Display settings *****************************************************************************/ // The display configuration: // I use two 64x64 ledpanels. to use a smaller configuration: just let part // of the image blank (0 brightness) // Do not change (the PIO code assumes this configuration hard coded): #define num_of_displays 2 #define columns_of_display 64 #define rows_of_display 64 #define half_rows_of_display 32 /* The pin assignment has been chosen such that the PIO sm can use 'out PINS' to immediately set both the pixel data and address data to the pins panel: pin assignment: cable (=mirror of pin assignment): ------------- ------------- ------------- | R1 | G1 | | 2 | 3 | | 3 | 2 | ------------- ------------- ------------- | B1 | GND | | 4 | GND | | GND | 4 | ------------- ------------- ------------- | R2 | G2 | | 5 | 6 | | 6 | 5 | ------------- ------------- ------------- B2 | E | 7 | 12 | | 12 | 7 || ------------- ------------- ------------- A | B | 8 | 9 | | 9 | 8 || ------------- ------------- ------------- | C | D | | 10 | 11 | | 11 | 10 | ------------- ------------- ------------- | CLK | LAT | | 14 | 13 | | 13 | 14 | ------------- ------------- ------------- | OE | GND | | 15 | GND | | GND | 15 | ------------- ------------- ------------- Note: OE = output enable = inverse of blank */ // The pin assignments #define panel_r1 2 #define panel_g1 3 #define panel_b1 4 #define panel_r2 5 #define panel_g2 6 #define panel_b2 7 #define address_a 8 #define address_b 9 #define address_c 10 #define address_d 11 #define address_e 12 #define latch 13 #define clock_p 14 #define blank 15 // TODO: remove (only for testing purposes) #define timing_pin_DMA 16 #define timing_pin_convert 17 #define timing_pin_build_image 18 /****************************************************************************** * Image and its encoding for the state machine *****************************************************************************/ /* Color and brightness Each pixel in a ledpanel has three LEDS: a red, green and blue LED. The panel works by setting a 0 or 1 for each RGB LED for each pixel for one row and letting them shine for a little bit of time. Each color for each pixel in this code has 4 bits. This means that each row is written 4 times, where the 4th bit of each color is written first, then the 3th, etc. This results in a (sort of) 'Pulse Width Modulation' (PWM), see https://www.galliumstudio.com/2020/04/07/stm32-episode-3-color-tricks-with-led-panels/. Note that here I've done the 'pwm' per row and not for a whole frame. The time the LEDs are on is the longest for the 4th bit and the shortest for the 1st bit. Setting the overall brightness: The variable 'overall_brightness' controls overall brightness in addition to the 16 brightness levels of each red, green, and blue pixel values. It does this by bit-shifting the delay time to higher values. It can take on values from 0 to 7. Taking the 4 bits encoding of the color, and shifting it by a maximum of 7 bits results in a 11-bit number. This number is used as the counter for the delay loop. If a higher brightness value is needed, the pio program can be changed: it has a delay loop with three 'nop [3]' statements (do nothing for 4 clock cycles). More 'nop' statements can be added. But this will also lower the update rate of the display. */ extern uint overall_brightness; // Just for ease of use: assign numbers 0, 1 and 2 to Red, Green, and Blue #define R 0 #define G 1 #define B 2 /* The image to be displayed The RGB values for each pixel are stored in an image variable: image[row][column]=RGB_brightness For each R, G and B 4 bits are used: Red (no shift), Blue (shift 4 bits), Green (shift 8 bits). So the values in image look like: g4, g3, g2, g1, b4, b3, b2, b1, r4, r3, r2, r1 where g4 is the highest brightness bit of green, g1 is the lowest brightness bit of green, similar for blue and red Double buffering is used, so two image variables: */ extern uint image1[rows_of_display][num_of_displays * columns_of_display]; extern uint image2[rows_of_display][num_of_displays * columns_of_display]; // the pointer to one of the two above image variables extern uint (*image)[num_of_displays * columns_of_display]; /* Both cores of the Pico are used: core 0: generates images core 1: processes images to be send to the PIO state machine (SM), and controls the DMA to the SM Some coordination is required to make sure that all images that are prepared by core 0 are actually displayed by core 1. Also some coordination is needed to make sure that the image data is only overwritten by core 0 after it has been processed for displaying by core 1. */ // function to be called from core 0 extern void core1_worker(); // indicates which image is ready (set by core 0) extern uint image_ready; // indicates which image is being processed on core 1 extern uint image_processing; #endif ================================================ FILE: ledpanel/ledpanel.pio ================================================ .program ledpanel ; side_set PINS: ; MSB: blank (0=screen on, 1=screen off) ; clock ; LSB: latch .side_set 3 ; I have two panels of 64 columns -> there are 128 pixels per row ; output 128 address/pixel data items (set x to 127) mov ISR ~NULL side 0b100 ; shift in 25 0s from left to right (i.e. right shift has to be set in c-program) in NULL 25 side 0b100 ; ISR is now 127 .wrap_target ; start x at 127 mov x ISR side 0b000 get_data: ; set the data on the pins (autopull for 'out' is enabled) ; data = 5 address bits + RGB (row) + RGB (row+32) = 11 bits ; start the clock pulse, screen off (needs 2 clock cycles, determined experimentally) out PINS 11 side 0b110 [2] ; end clock pulse, screen off ; check if all items for this row have been done jmp x-- get_data side 0b100 ; start the latch pulse, screen on (needs 2 clock cycles, determined experimentally) ; set x to the number of delay loops = brightness level ; note: to not disturb the autopull the brightness level (11 bits) is set twice ; which is a nice coincidence because the 0b001 needs to be set for two clock cycles! out x 11 side 0b001 out x 11 side 0b001 ; start the delay loop and end the latch pulse, screen on delay: nop [3] side 0b000 ; note: three nops with 3 delay cycles seems sufficient for my panels nop [3] side 0b000 ; if more brightness is needed you can add more nops here, but nop [3] side 0b000 ; that will lower the update rate (now 144 Hz) ; decrement x for another round of delays jmp x-- delay side 0b000 ; the delay loop has finished: start a new row .wrap ================================================ FILE: ledpanel/ledpanel_worker.c ================================================ #include "stdio.h" #include "pico/stdlib.h" #include "hardware/pio.h" #include "hardware/dma.h" #include "hardware/irq.h" #include "ledpanel.pio.h" #include "pico/multicore.h" #include "hardware/interp.h" #include "ledpanel.h" // local (i.e. core 1) pointer to the image variable that contains the image information uint (*image_to_encode)[num_of_displays * columns_of_display]; // the image is encoded for output to the sm in the variable "encoded_image" // Because of the construction of the led panel, you always send (x,y) and (x+32, y) pixels. // Additionally, with each set of pixels the address is given. // So, from MSB to LSB: E, F, D, C, B, A, g(row+32), b(row+32), r(row+32), g(row), b(row), r(row) // where E,F,D,C,B and A encode the row address. // and where g, b and r are bits for a brightness level, each color is encoded with 4 bits. See the // loop 'for (int b = 3; b >= 0; b--)' in the code below. // This is 11 bits, so: two of these fit in a uint32_t (with room to spare: 10 bits) // The size of the transcoded image for tranmission to the sm follows from: // 128 lines // 32 rows (two rows are transmitted simultaneously) // 4 brightness level // 2 pixels (and the address) per uint32_t // -> 128*32*4 / 2 = 8192 // additionally the overall brightness is set for each of the 32 rows and each of the 4 brightness levels // -> 8192 + 32*4 = 8320 #define MAX_ITEMS 8320 uint32_t encoded_image[MAX_ITEMS]; // number of items to send to the sm via dma uint num_of_items_to_dma; // encode the image to be suitable for sending it to the sm void encode_image() { gpio_put(timing_pin_convert, 1);// TODO: remove (only for testing purposes) // variable to hold the data to be send to the sm uint32_t address_and_pixels; // counter to keep track of the number of items to be send uint num_of_items = 0; /* YES YES YES! I finally found a good use for the interpolator hardware!!!! It may not be what it was intended for, but hey: it works! And it is faster than a lot of 'and', 'bit shift' and 'or' operations! The following bit shifting/reordering is needed to transcode the RGB values as stored in the image variables to the format required by the PIO sm to send to the ledpanel: The images contain pixel information in the format: g4, g3, g2, g1, b4, b3, b2, b1, r4, r3, r2, r1 but the sm needs the bits to be in a different order: for the highest brightness bits (b=3): g4, b4, r4 down to the lowest brightness color information (b=0): g1, b1, r1 So, the interpolator is configured as follows: - the input is the pixel color information shifted over b bits - The Base 2 variable contains the b'th bit for red see the lines in the code below: uint value = image_to_encode[row][i] >> b; interp0->base[2] = value & 0x01; so, the first bit in interpolator's 'Result 2' is the b'th bit for red - Lane 0 shifts the pixel info three bits right and masks the result in such a way that the b'th bit of blue is now the second bit in 'Result 2' - Lane 1 shifts the pixel info 6 bits right and masks the result in such a way that the b'th bit of green is now the third bit in 'Result 2' */ // Initialise lane 0 on interp0 on this core interp_config cfg0 = interp_default_config(); // shift right 3 bits interp_config_set_shift(&cfg0, 3); // mask lets bit 1 (the 2nd bit) pass interp_config_set_mask(&cfg0, 1, 1); interp_set_config(interp0, 0, &cfg0); // Initialise lane 1 on interp0 on this core interp_config cfg1 = interp_default_config(); // shift right 6 bits interp_config_set_shift(&cfg1, 6); // mask lets bit 2 (the 3rd bit) pass interp_config_set_mask(&cfg1, 2, 2); interp_set_config(interp0, 1, &cfg1); // 64 rows, but row i and i+1 are drawn simultaneously -> 32 rows for (uint8_t row = 0; row < half_rows_of_display; row++) { // 4 brightness level bits for color in each pixel for (int b = 3; b >= 0; b--) { // all columns for (uint8_t i = 0; i < num_of_displays * columns_of_display; i += 2) { // ledpanel displays put the x and x+32 rows on the display at the same time. // Additionally, two pixels fit in one byte to be displayed. So, code the y and y+1 pixels // pixel (x,y) uint value = image_to_encode[row][i] >> b; // printf("1 image_to_encode=%d value=%d\n", image_to_encode[row][i], value); interp0->base[2] = value & 0x01; interp0->accum[0] = value; interp0->accum[1] = value; address_and_pixels = interp0->peek[2]; // pixel (x+32, y) value = image_to_encode[row + 32][i] >> b; // printf("2 image_to_encode=%d value=%d\n", image_to_encode[row + 32][i], value); interp0->base[2] = value & 0x01; interp0->accum[0] = value; interp0->accum[1] = value; address_and_pixels |= interp0->peek[2] << 3; // add the row address address_and_pixels |= row << 6; // pixel (x,y+1) value = image_to_encode[row][i + 1] >> b; // printf("3 image_to_encode=%d value=%d\n",image_to_encode[row][i + 1], value); interp0->base[2] = value & 0x01; interp0->accum[0] = value; interp0->accum[1] = value; address_and_pixels |= interp0->peek[2] << 11; // pixel (x+32, y+1) value = image_to_encode[row + 32][i + 1] >> b; // printf("4 image_to_encode=%d value=%d\n",image_to_encode[row + 32][i + 1], value); interp0->base[2] = value & 0x01; interp0->accum[0] = value; interp0->accum[1] = value; address_and_pixels |= interp0->peek[2] << 14; // add the row address address_and_pixels |= row << 17; // add the data to the encoded image array to be sent to the pio sm if (num_of_items < MAX_ITEMS) encoded_image[num_of_items++] = address_and_pixels; } // This controlls the overall brightness of the panels // In order not to disturb the 22 bit autopull, it is sent twice address_and_pixels = 1 << (overall_brightness + b); address_and_pixels |= address_and_pixels << 11; if (num_of_items < MAX_ITEMS) encoded_image[num_of_items++] = address_and_pixels; } } // set the number of dataitems to be sent to the sm via dma num_of_items_to_dma = num_of_items; gpio_put(timing_pin_convert, 0);// TODO: remove (only for testing purposes) } /****************************************************************************** * configuration of the sm and dma *****************************************************************************/ // variables for the pio and state machine (sm) to be used PIO pio; uint sm; // the sm program offset and sm configuration uint sm_offset; pio_sm_config smc; // the dma channel uint dma_chan; // configure the state machine void configure_pio_sm() { // pio to use pio = pio0; // set the GPIO to be used by the pio for (uint pin = panel_r1; pin <= blank; pin++) pio_gpio_init(pio, pin); // state machine to use sm = 0; // load the sm program into the pio memory sm_offset = pio_add_program(pio, &ledpanel_program); // make a sm config smc = ledpanel_program_get_default_config(sm_offset); // set pindirs all pins are under control of the PIO sm as output pio_sm_set_consecutive_pindirs(pio, sm, panel_r1, 14, true); // set out pin (r1, g1, b1, r2, g2, b2, a, b, c, d, e) sm_config_set_out_pins(&smc, panel_r1, 11); // set side-set pins (latch, clock, blank) sm_config_set_sideset_pins(&smc, latch); // set the correct ISR shift direction (see the .pio file) sm_config_set_in_shift(&smc, true, false, 0); // set autopull for out: NOTE: one set of pixels is 11 bits, // there are two sets per doubleword (32 bits) send to the Tx FIFO sm_config_set_out_shift(&smc, true, true, 22); // init the pio sm with the config pio_sm_init(pio, sm, sm_offset, &smc); // enable the state machines pio_sm_set_enabled(pio, sm, true); } // interrupt handler for the direct memory access void dma_handler() { gpio_put(timing_pin_DMA, 1);// TODO: remove (only for testing purposes) // Clear the interrupt request dma_hw->ints0 = 1u << dma_chan; // set the number of data items to transfer dma_channel_set_trans_count(dma_chan, num_of_items_to_dma, false); // set the read address, this will trigger the DMA to start again dma_channel_set_read_addr(dma_chan, encoded_image, true); gpio_put(timing_pin_DMA, 0);// TODO: remove (only for testing purposes) } // configure the direct memory access void configure_dma() { // init variable to hold the number of items to send via dma num_of_items_to_dma = 0; // Get free DMA channels, panic() if there are none dma_chan = dma_claim_unused_channel(true); // make DMA configs dma_channel_config dma_conf = dma_channel_get_default_config(dma_chan); // transfer uint32_t channel_config_set_transfer_data_size(&dma_conf, DMA_SIZE_32); // the buffer increment of read pointer channel_config_set_read_increment(&dma_conf, true); // the TxFIFO is fixed in memory -> no increment write pointer channel_config_set_write_increment(&dma_conf, false); // let the sm of pio determine the speed channel_config_set_dreq(&dma_conf, pio_get_dreq(pio, sm, true)); // configure the dma channel to write to sm TxFIFO from buffer dma_channel_configure(dma_chan, &dma_conf, &pio0_hw->txf[0], NULL, 0, false); // enable IRQ for the channel dma_channel_set_irq0_enabled(dma_chan, true); // set the handler irq_set_exclusive_handler(DMA_IRQ_0, dma_handler); // enable IRQ irq_set_enabled(DMA_IRQ_0, true); } void core1_worker() { // TODO: remove (only for testing purposes) gpio_init(timing_pin_DMA); gpio_set_dir(timing_pin_DMA, GPIO_OUT); gpio_init(timing_pin_convert); gpio_set_dir(timing_pin_convert, GPIO_OUT); // wait for an image to be finished by core 0 while (image_ready == 0) ; if (image_ready == 1) { // the image in variable image1 is ready for transcoding image_to_encode = image1; // indicate that image1 is what this core is working on image_processing = image_ready; } else { // the image in variable image2 is ready for transcoding image_to_encode = image2; // indicate that image2 is what this core is working on image_processing = image_ready; } // prepare the sm configure_pio_sm(); // prepare the dma configure_dma(); // convert the image to data for the pio sm encode_image(); // start the dma dma_handler(); image_processing = 0; while (true) { // if image1 is ready if (image_ready == 1) { // the image in variable image1 is ready for transcoding // indicate that image1 is what this core is working on image_processing = image_ready; // indicate that core 0 can continue with generating a new image image_ready = 0; image_to_encode = image1; // do the encoding encode_image(); // indicate transcoding has finished image_processing = 0; } else if (image_ready == 2) { // the image in variable image2 is ready for transcoding // indicate that image2 is what this core is working on image_processing = image_ready; image_ready = 0; // indicate that core 0 can continue with generating a new image image_to_encode = image2; // do the encoding encode_image(); // indicate transcoding has finished image_processing = 0; } } } ================================================ FILE: multiplication/CMakeLists.txt ================================================ add_executable(multiplier) pico_generate_pio_header(multiplier ${CMAKE_CURRENT_LIST_DIR}/multiplier.pio) target_sources(multiplier PRIVATE multiplier.cpp) target_link_libraries(multiplier PRIVATE pico_stdlib hardware_pio ) pico_add_extra_outputs(multiplier) # add url via pico_set_program_url example_auto_set_url(multiplier) ================================================ FILE: multiplication/README.md ================================================ # multiply two numbers In the [RP2040 datasheet](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf) there is an example of how a sm can be used to add two numbers. If you can add, you can multiply! So, this is a pio program that can multiply. Not very useful, but interesting nonetheless. If the two numbers to be multiplied are called m1 and m2, this multiplication works by adding (actually subtracting) one for m2 times m1 times. A possibly better implementation would perform the multiplication in a different way: by shifting and adding. ================================================ FILE: multiplication/multiplier.cpp ================================================ #include #include "pico/stdlib.h" #include "hardware/pio.h" #include "multiplier.pio.h" // the pio instance PIO pio; // the state machine uint sm; void pio_mul(int a, int b) { pio_sm_put(pio, sm, a); pio_sm_put(pio, sm, b); printf("%d * %d = %d\n", a, b, pio_sm_get_blocking(pio, sm)); } int main() { // needed for printf stdio_init_all(); // pio 0 is used pio = pio0; // state machine 0 sm = 0; // load the pio program into the pio memory uint offset = pio_add_program(pio, &multiplier_program); // make a sm config pio_sm_config c = multiplier_program_get_default_config(offset); // init the pio sm with the config pio_sm_init(pio, sm, offset, &c); // enable the sm pio_sm_set_enabled(pio, sm, true); pio_mul(1, 1); pio_mul(0, 1); pio_mul(5, 0); pio_mul(1, 2); pio_mul(2, 1); pio_mul(3, 2); pio_mul(2, 3); pio_mul(4, 5); pio_mul(5, 4); pio_mul(1, 10); pio_mul(5, 100); pio_mul(12, 12); pio_mul(100, 101); pio_mul(1001, 1000); while (true) { } } ================================================ FILE: multiplication/multiplier.pio ================================================ .program multiplier ; overall approach: ; get the two numbers to be multiplied, called m1 and m2 ; m2 is saved in OSR, m1 is saved in ISR ; use the (negative) y as counter ; repeat m2 times: ; repeat m1 times: ; subtract one from y .wrap_target start: pull block ; wait until data is available mov x OSR ; get m1 mov ISR x ; save m1 in the ISR; this value is used m2 times pull block ; get m2 mov y OSR ; test if m2 = 0, if so -> return a 0 jmp y-- not_zero; if m2 > 0, subtract 1 and go to not_zero mov ISR NULL ; m2 == 0, return a 0 as answer push jmp start ; restart not_zero: mov OSR y ; write back m2-1 in the OSR mov y ~NULL ; initialize the y register to 0xFFFFFFFF next_x: ; start the actual multiplication by counting m2 times m1 times subtracting one from y jmp x-- sub_one ; count down x, and if still x > 0 - also decrement y ; if x==0, m1 has been counted down, see if OSR > 0 (i.e. m2), if so, go again mov x OSR ; get the saved value of m2 from the OSR jmp x-- save_m2 ; if x>0 save the new value of m2 and start subtracting x (and also y) ; nothing more to do, m2 has been counted down to 0, return the answer mov ISR ~y push .wrap sub_one: ; subtract one from y jmp y-- next_x ; y is (as it should) > 0 -> continue the multiplication mov ISR ~NULL ; y has become 0, only happens for large numbers -> give 0xFFFFFFFF as answer push jmp start save_m2: ; save m2 and switch to m1 in x mov OSR x ; save the x (m2) in the OSR mov x ISR ; start over with m1 jmp next_x ================================================ FILE: pico_sdk_import.cmake ================================================ # This is a copy of /external/pico_sdk_import.cmake # This can be dropped into an external project to help locate this SDK # It should be include()ed prior to project() if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH)) set(PICO_SDK_PATH $ENV{PICO_SDK_PATH}) message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')") endif () if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT)) set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT}) message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')") endif () if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH)) set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH}) message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')") endif () set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK") set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable") set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK") if (NOT PICO_SDK_PATH) if (PICO_SDK_FETCH_FROM_GIT) include(FetchContent) set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR}) if (PICO_SDK_FETCH_FROM_GIT_PATH) get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}") endif () FetchContent_Declare( pico_sdk GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk GIT_TAG master ) if (NOT pico_sdk) message("Downloading Raspberry Pi Pico SDK") FetchContent_Populate(pico_sdk) set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR}) endif () set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE}) else () message(FATAL_ERROR "SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git." ) endif () endif () get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}") if (NOT EXISTS ${PICO_SDK_PATH}) message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found") endif () set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake) if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE}) message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK") endif () set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE) include(${PICO_SDK_INIT_CMAKE_FILE}) ================================================ FILE: sm_to_dma_to_buffer/CMakeLists.txt ================================================ add_executable(sm_to_dma_to_buffer) pico_generate_pio_header(sm_to_dma_to_buffer ${CMAKE_CURRENT_LIST_DIR}/sm_to_dma_to_buffer.pio) target_sources(sm_to_dma_to_buffer PRIVATE sm_to_dma_to_buffer.cpp) target_link_libraries(sm_to_dma_to_buffer PRIVATE pico_stdlib hardware_pio hardware_dma ) pico_add_extra_outputs(sm_to_dma_to_buffer) # add url via pico_set_program_url example_auto_set_url(sm_to_dma_to_buffer) ================================================ FILE: sm_to_dma_to_buffer/README.md ================================================ # State machine writes into a buffer via DMA This is an example of a state machine (sm) using DMA (Direct Memory Access) to write into a buffer. The sm0 counts up from 0, and sends its current value into the Rx FIFO. A DMA channel reads the Rx FIFO and puts it into a buffer (of length 100). ================================================ FILE: sm_to_dma_to_buffer/sm_to_dma_to_buffer.cpp ================================================ #include #include "pico/stdlib.h" #include "hardware/pio.h" #include "hardware/dma.h" #include "sm_to_dma_to_buffer.pio.h" int main() { // buffer to write to uint32_t buffer[100]; // needed for printf stdio_init_all(); // pio 0 is used PIO pio = pio0; // state machine 0 uint sm0 = 0; // load the sm0 program into the pio memory uint offset0 = pio_add_program(pio, &sm_to_dma_to_buffer_program); // make a sm config pio_sm_config smc0 = sm_to_dma_to_buffer_program_get_default_config(offset0); // init the pio sm0 with the config pio_sm_init(pio, sm0, offset0, &smc0); // disable the sm pio_sm_set_enabled(pio, sm0, false); // make sure the FIFOs are empty pio_sm_clear_fifos(pio, sm0); // Get a free DMA channel, panic() if there are none int dma_chan = dma_claim_unused_channel(true); // make a default dma config dma_channel_config dma_conf = dma_channel_get_default_config(dma_chan); // transfer uint32_t channel_config_set_transfer_data_size(&dma_conf, DMA_SIZE_32); // a FIFO is read -> no increment of read pointer channel_config_set_read_increment(&dma_conf, false); // a buffer in memory is written to -> increment write pointer channel_config_set_write_increment(&dma_conf, true); // let the sm0 of pio determine the speed channel_config_set_dreq(&dma_conf, pio_get_dreq(pio, sm0, false)); // configure the dma channel to read 100 uint32_t from sm0 to the buffer dma_channel_configure(dma_chan, &dma_conf, buffer, // Destinatinon pointer: the buffer in memory &pio->rxf[sm0], // Source pointer: the output of sm0 100, // Number of transfers true // Start immediately ); // enable the sm pio_sm_set_enabled(pio, sm0, true); // let the dma channel do its stuff dma_channel_wait_for_finish_blocking(dma_chan); // print the result in the buffer for (int i = 0; i < 100; i++) { printf("buffer = %d\n", buffer[i]); } // endless loop to end this program while (true) ; } ================================================ FILE: sm_to_dma_to_buffer/sm_to_dma_to_buffer.pio ================================================ .program sm_to_dma_to_buffer start0: mov x ~NULL ; start with 0xFFFFFFFF push_it0: mov ISR ~x ; push the value into the Rx FIFO ; (make it look like counting up) push jmp x-- push_it0 ; count down jmp start0 ================================================ FILE: sm_to_dma_to_sm_to_dma_to_buffer/CMakeLists.txt ================================================ add_executable(sm_to_dma_to_sm_to_dma_to_buffer) pico_generate_pio_header(sm_to_dma_to_sm_to_dma_to_buffer ${CMAKE_CURRENT_LIST_DIR}/sm_to_dma_to_sm_to_dma_to_buffer.pio) target_sources(sm_to_dma_to_sm_to_dma_to_buffer PRIVATE sm_to_dma_to_sm_to_dma_to_buffer.cpp) target_link_libraries(sm_to_dma_to_sm_to_dma_to_buffer PRIVATE pico_stdlib hardware_pio hardware_dma ) pico_add_extra_outputs(sm_to_dma_to_sm_to_dma_to_buffer) # add url via pico_set_program_url example_auto_set_url(sm_to_dma_to_sm_to_dma_to_buffer) ================================================ FILE: sm_to_dma_to_sm_to_dma_to_buffer/README.md ================================================ # State Machine -> DMA -> State Machine -> DMA -> Buffer This is an example where one state machine writes via DMA to another state machine whose output is put into a buffer via another DMA channel. The sm0 counts up from 0, and sends its current value into its Rx FIFO. A DMA channel reads this Rx FIFO and puts it into the Tx FIFO of sm1. The sm1 does nothing more than put what it receives into its Rx FIFO. A DMA channel reads the sm1 Rx FIFO and puts the values into a buffer (of length 100). In the code the chain is as follows: ``` sm0 -> dma_chan_2 -> sm1 -> dma_chan_1 -> buffer ``` I had some problems getting this to work. The main reason was to set the data request (DREQ) signal correctly. It turns out to depend on which sm is the slowest. That one has to determine the speed. In the PIO code sm0 runs a program (tester0) that is 3 instructions long while sm1 runs a program (tester1) that is 4 instructions. Therefore, sm1 should determine the DREQ: ``` channel_config_set_dreq(&dma_conf_2, pio_get_dreq(pio, sm1, true)); ``` If I artificially make tester0 longer, e.g. by uncommenting the ';[31]', then sm0 should determine the DREQ: ``` channel_config_set_dreq(&dma_conf_2, pio_get_dreq(pio, sm0, false)); ``` In addition, starting the state machines and DMA channels in the correct order is important. In this case: ``` // enable the state machines pio_sm_set_enabled(pio, sm1, true); pio_sm_set_enabled(pio, sm0, true); // start the dma channel from sm0 to sm1: start producing data dma_channel_start(dma_chan_2); // wait for dma channel from sm1 to the buffer to finish dma_channel_wait_for_finish_blocking(dma_chan_1); ``` ================================================ FILE: sm_to_dma_to_sm_to_dma_to_buffer/sm_to_dma_to_sm_to_dma_to_buffer.cpp ================================================ /* The goal of this program is to show how DMA can be used to transfer data from one state machine to the next and from that second state machine to a buffer. state machine 0 (sm0) produces numbers from 0 counting up. state machien 1 (sm1) accepts input and immedeately pushes it as output In between sm0 and sm1 there is a DMA channel, and between sm1 and the buffere there is another DMA channel: sm0 -> dma_chan_2 -> sm1 -> dma_chan_1 -> buffer */ #include #include "pico/stdlib.h" #include "hardware/pio.h" #include "hardware/dma.h" #include "sm_to_dma_to_sm_to_dma_to_buffer.pio.h" int main() { uint32_t num_samples = 1000; // buffer to write to uint32_t buffer[num_samples]; // needed for printf stdio_init_all(); // pio 0 is used PIO pio = pio0; // state machines 0 and 1 uint sm0 = 0; uint sm1 = 1; // load the sm0 program into the pio memory uint offset0 = pio_add_program(pio, &tester0_program); // load the sm1 program into the pio memory uint offset1 = pio_add_program(pio, &tester1_program); // make a sm config pio_sm_config smc0 = tester0_program_get_default_config(offset0); pio_sm_config smc1 = tester1_program_get_default_config(offset1); // init the pio sm0 with the config pio_sm_init(pio, sm0, offset0, &smc0); pio_sm_init(pio, sm1, offset1, &smc1); // disable the state machines pio_sm_set_enabled(pio, sm0, false); pio_sm_set_enabled(pio, sm1, false); // clear the FIFOs pio_sm_clear_fifos(pio, sm0); pio_sm_clear_fifos(pio, sm1); // DMA channel from sm1 to a buffer in memory // NOTES: // - this channel is paced (dreq) by reading output from sm1 (is_tx is false) // - this channel may start immediately // Get a free DMA channel, panic() if there are none int dma_chan_1 = dma_claim_unused_channel(true); // make a dma config dma_channel_config dma_conf_1 = dma_channel_get_default_config(dma_chan_1); // transfer uint32_t channel_config_set_transfer_data_size(&dma_conf_1, DMA_SIZE_32); // a FIFO is read -> no increment of read pointer channel_config_set_read_increment(&dma_conf_1, false); // a buffer in memory is written to -> increment write pointer channel_config_set_write_increment(&dma_conf_1, true); // let the sm0 of pio determine the speed channel_config_set_dreq(&dma_conf_1, pio_get_dreq(pio, sm1, false)); // configure the dma channel to read num_samples uint32_t from sm1 to the buffer dma_channel_configure(dma_chan_1, &dma_conf_1, buffer, // Destinatinon pointer &pio->rxf[sm1], // Source pointer num_samples, // Number of transfers true // Start immediately ); // DMA channel from sm0 output to sm1 input // NOTES: // - this channel is paced (dreq) by writing to sm1 (is_tx is true). // The PIO program of sm1 is 4 instructions, sm0 is 3 instructions // If sm0 is made slower than sm1, then dreq should depend on sm0 (is_tx is false) // - this channel should not start immediately. First enable the state machines // Get a free channel, panic() if there are none int dma_chan_2 = dma_claim_unused_channel(true); // make a dma config dma_channel_config dma_conf_2 = dma_channel_get_default_config(dma_chan_2); // transfer uint32_t channel_config_set_transfer_data_size(&dma_conf_2, DMA_SIZE_32); // a FIFO is read -> no increment of read pointer channel_config_set_read_increment(&dma_conf_2, false); // a FIFO is written to -> no increment of write pointer channel_config_set_write_increment(&dma_conf_2, false); // let writing to sm1 determine the speed if sm1 is slower than sm0 channel_config_set_dreq(&dma_conf_2, pio_get_dreq(pio, sm1, true)); // let writing to sm0 determine the speed if sm0 is slower than sm1 // channel_config_set_dreq(&dma_conf_2, pio_get_dreq(pio, sm0, false)); // configure the dma channel to read num_samples uint32_t from sm0 to sm1 dma_channel_configure(dma_chan_2, &dma_conf_2, &pio->txf[sm1], // Destinatinon pointer &pio->rxf[sm0], // Source pointer num_samples, // Number of transfers false // Start immediately ); // enable the state machines pio_sm_set_enabled(pio, sm1, true); pio_sm_set_enabled(pio, sm0, true); // start the dma channel from sm0 to sm1: start producing data dma_channel_start(dma_chan_2); // wait for dma channel from sm1 to the buffer to finish dma_channel_wait_for_finish_blocking(dma_chan_1); // print the result in the buffer for (int i = 0; i < num_samples; i++) { printf("buffer = %d\n", buffer[i]); } while (true) ; } ================================================ FILE: sm_to_dma_to_sm_to_dma_to_buffer/sm_to_dma_to_sm_to_dma_to_buffer.pio ================================================ // Note: // program tester1 is 4 instructions (intentionally not using .wrap) while program tester0 is only 3 instructions per loop. // These program lengths influences the dreq settings of the c program // If tester1 is slower than tester0: // // let writing to sm1 determine the speed // channel_config_set_dreq(&dma_conf_2, pio_get_dreq(pio, sm1, true)); // If tester0 is slower than tester1 (remove the ';' before [31] in tester0): // // let writing to sm0 determine the speed if sm0 is slower than sm1 // channel_config_set_dreq(&dma_conf_2, pio_get_dreq(pio, sm0, false)); .program tester0 // a simple counter producing output starting from 0 start0: mov x ~NULL ; set x to 0xFFFFFFFF push_it0: mov ISR ~x ; set the ISR to ~x (bitwise invert) push ;[31] ; push to FIFO (optionally make tester0 slower than tester1, the dreq needs to change) jmp x-- push_it0 ; decrease x and loop jmp start0 .program tester1 // pull and immediately push. Indeed, it does nothing! start1: pull ; pull the Tx FIFO mov ISR OSR ; do nothing but copy the OSR to the ISR push ; push the Rx FIFO jmp start1 ; loop ================================================ FILE: state_machine_emulator/README.md ================================================ # Emulator of a Raspberry Pi Pico PIO state machine ## What does it do This emulator allows you to step through the workings of a RPI Pico PIO state machine running PIO code. It is intended for running test cases and gain insight into how the code works. This code takes a .pio.h file generated by pioasm as input and emulates how a PIO state machine (sm) would execute it. Besides the .pio.h file there are two more input files: * a file describing the 'C'-statements (called c_program) such as configuring of pins and putting data into the Tx FIFO. Note that these are not the actual C/C++ statements, but much simplified versions of them. And not all of them, only some (see below.) * a file describing the externally driven GPIO pins (called pin_program, see below) The user can obtain insights into the workings of the pio code through a GUI which shows all (?) the relevant sm internal information. ##### Why? The problem with the RPi Pico PIO state machines (sm) is that the debugger supplied with the vscode distribution for the Pico does not give the insight I need when writing code for a sm. I typically write some code, upload it to the Pico and find that it doesn't do what I want it to do. Instead of guessing what I do wrong, I want to see the values of e.g. the registers when the sm is executing. And I liked making the emulator. ## What does it not do Since this emulator is meant for studying how pio code is executed for a given test case (the combination of c_program and pin_program), it only emulates one sm. You can set which of the 8 sm it is (in config.py) because interrupts use this number. It also uses only one PIO (i.e. 32 instruction memory locations) running one program. If you want to study several PIO programs, you'll have to make separate test cases. Other things you might expect, but aren't implemented or used: * It only uses non-blocking 'C'-statements to 'put' data into the TxFIFO and and 'get' data from the RxFIFO, * 'out exec' and 'mov exec' aren't implemented, * No FIFO joining, * 'origin' is not used; all pio code starts at memory location 0, * It (mostly) doesn't use the registers ([RP2040 datasheet](htts://rptl.io/rp2040-datasheet) section 3.7). ## Is this the only means to study pio code? No, there are several options. * Use explicit statements in PIO code to export values (e.g. via MOV ISR X, PUSH) but this influences your code, * You could insert 'EXEC' commands from C/C++, but this still won't give you a deep insight. Or you can program the pio, look at its output (GPIO or your print messages) and try to figure out why it doesn't do what you want it to do. This is hard work and the exact reason why I built this emulator. There are other debuggers and emulators: * You can use GDB which can give some sm parameters, but not all (e.g. no X- and Y-registers) * [VisualGDB](https://visualgdb.com/tutorials/raspberry/pico/pio/debugger/) looks awesome but seems to involve a bit of work (I haven't tried it) * [WOKWI](https://wokwi.com/tools/pioasm), see e.g. the [HackadayU video](https://www.youtube.com/watch?v=LIA9wpt7N60) where it is used. * [soundpaint rp2040pio](https://github.com/soundpaint/rp2040pio) * [NathanY3G rp2040-pio-emulator](https://github.com/NathanY3G/rp2040-pio-emulator), installable via [pip](https://pypi.org/project/rp2040-pio-emulator/) There may be others that I don't know of. Even though it is a lot of work, building this emulator is quite instructive and fun (if you're into such things.) ## Workflow When working on a project, I typically have an IDE (in my case vscode) with the project files, the pin_program and c_program files opened. Additionally, I have the emulator open with the .pio.h file of the project When changing the .pio code, I use the IDE function 'build' to have pioasm generate a new .pio.h file. Then I press reLoad in the emulator, and study the emulation output. When changing the C/C++ code that uses the pio code, I have to decide if the c_program also needs to change. Often, this will not be the case because the c_program and pin_program act more like test cases than that they mimic the real C/C++ code or real signals applied to the pins. ## How does it work Using python3, after reading and parsing the input files, it emulates the sm (and the c-statements, and the externally driven GPIOs) for 500 steps, which can of course be changed in the file config.py, and stores all the state variables at each step into a variable. Then the GUI is started which allows you to step back and forth through the results. Run it with: ```python main.py pio_program.pio.h pin_program c_program``` For example (1 line): ```python main.py examples/multiplication/pio_program.pio.h examples/multiplication/pin_program examples/multiplication/c_program``` Or simply with: ```python main.py examples/multiplication``` Note: at a given time step, first the c-program and pin-program for that time step are executed, then the PIO statement. So, if the pin-program sets a pin, the PIO sees the pin as set. After the emulation, the GUI looks similar to the image below. ![](emulator_screenshot_annotations.png) ## GUI The GUI consists of serveral sections: ##### Externally driven pins In this section the pin-program is shown. The highlighted text has just been executed. The statements should be placed in a file called pin_program. The format of the statements is: ```timestep, GPIOs, state``` where: * ```timestep``` is the timestep at which an externally driven pin is changed * ```GPIOs``` can be either 'GPIOx' with x from 0 to 31, or 'all' * ```state``` can be '-1' for not driven externally, '0' for externally driven low, or '1' for externally driven high. For example, a pin_program file can look like this: ``` 0, all, -1 # GPIOs are not externally driven (=default, so not necessary) 10, GPIO2, 1 # starting at t=10 GPIO2 is externally driven high 15, GPIO2, 0 # starting at t=15 GPIO2 is externally driven low 20, GPIO2, -1 # starting at t=20 GPIO2 is no longer externally driven ``` The just executed pin statement is highlighted. ##### State machine status This section shows variables from the internals of the sm: * 'clock' is the current time step (actually, this is not strictly sm internal) * 'pc' is the program counter of the just executed pio statement * 'delay' is the amount of time steps still to be delayed due to a pio code delay option * 'status' is the fill state of the RxFIFO or TxFIFO * 'OSR shift counter' indicates when an autopull will occur * 'ISR shift counter' indicates when an autopush will occur * 'OSR' is the value in the Output Shift Register * 'ISR' is the value in the Input Shift Register * 'X' is the value in the X Register * 'Y' is the value in the Y Register * 'IRQ (sm=5)' indicates which IRQ is set. For some pio statements the IRQ depends on the sm (the Pico has 8), in this example it is sm 5, the default is sm 0. ##### Toolbar The toolbar presents a number of buttons to control the emulator: * 'reLoad' reloads all the files and restarts the emulation. This is handy for repeated compiling-emulating sessions: after compiling the pio code, just hit reLoad (or the 'l' or 'L' key) * 'Restart' goes back to t=0 (keybindings: 'r' or 'R') * 'back 50' goes back 50 time steps * 'back 10' goes back 10 time steps (keybinding: down arrow) * 'back' goes back one time step (keybinding: left arrow) * 'step' goes forward one time step (keybinding: right arrow) * 'step 10' goes forward 10 time steps (keybinding: up arrow) * 'step 50' goes forward 50 time steps * 'Quit' stops the emulator and GUI (keybindings: 'q' or 'Q') ##### PIO code This is the pio code that was obtained from the '*.pio.h' file (the * indicates that the name before .pio.h doesn't matter). This file should be generated by the pioasm tool, not hand-crafted! The just executed line of pio code is highlighted. In the .pio.h files generated by pioasm some statements are placed that are picked up by the emulator. The following information is used: * ```#define tester_wrap_target``` * ```#define tester_wrap``` * the compiled statements in ```..._program_instructions[]``` * the .length in ```..._program[]``` * the '```sm_config_set_sideset```' statement in ```..._program_get_default_config[]``` * note: offset is not supported * setting wrap parameters is done using the #define statements mentioned above * sideset: sideset_base is supported, the other settings (sideset_count, sideset_opt, and sideset_pindirs) are set by parsing the .pio.h file ##### Output (get RxFIFO) From the 'C' program you can issue a 'get' statement. This removes one item from the RxFIFO and writes it as output. The just produced output is highlighted. ##### FIFOs These are the two FIFOs, each with 4 data items. The 'C' program can 'put' data in the TxFIFO, and 'get' it out of the RxFIFO. ##### 'C'-statements The important C/C++ statements for controlling the sm are supported in a simplified form. The statements should be placed in a file called 'c_program'. The format of the statements is as follows: ```timestep, command, argument``` where: * ```timestep``` is the timestep at which the command should be executed * ```command``` see below * ```argument``` the argument for the command Have a look at the examples to see how these commands work The currently supported c-statements in the file c_program are: * set_base, set_count sets the 'set' pins to use, see [C/C++ SDK](https://rptl.io/pico-c-sdk) 4.1.15.5.55. * out_base, out_count sets the 'out' pins to use, see [C/C++ SDK](https://rptl.io/pico-c-sdk) 4.1.15.5.51. * sideset_base sets the 'sideset' pins to use, see [C/C++ SDK](https://rptl.io/pico-c-sdk) 4.1.15.5.56, and 4.1.16.3.13. Note that sideset_count , sideset_pindirs, and sideset_opt are set by parsing the .pio.h file (.side_set directive in pio code), but sideset_base isn't, you must set it in the c_program file. * in_base sets the 'in' pins to use, see [C/C++ SDK](https://rptl.io/pico-c-sdk) 4.1.15.5.50. * jmp_pin sets the 'jmp' pin to use, see [C/C++ SDK](https://rptl.io/pico-c-sdk) 4.1.16.3.7. * out_shift_right, out_shift_autopull, pull_threshold sets how the out shifting is handled, see [C/C++ SDK](https://rptl.io/pico-c-sdk) 4.1.16.3.10. * in_shift_right, in_shift_autopush, push_threshold sets how the in shifting is handled, see [C/C++ SDK](https://rptl.io/pico-c-sdk) 4.1.16.3.6. * put, get put data into the TxFIFO (non-blocking) or get data from the RxFIFO and show it in the output frame of the GUI. See [C/C++ SDK](https://rptl.io/pico-c-sdk) 4.1.15.5.42. and 4.1.15.5.30. * get_pc get the program counter and put it in the output frame of the GUI, see [C/C++ SDK](https://rptl.io/pico-c-sdk) 4.1.15.5.32. * set_N sets a register that allows monitoring the RxFIFO and TxFIFO levels via the mov statement, see [RP2040 datasheet](https://rptl.io/rp2040-datasheet) at section 3.4.8 (the mov statement) and section 3.7 (Registers). * status_sel selects which FIFO's level should be checked (0 = TxFIFO, 1 = RxFIFO) * dir_out sets the pindir of a pin to output * dir_in sets the pindir of a pin to input * dir_non unset the pindir of a pin ##### Settings These are some of the register settings that can influence how the sm functions. ##### GPIO status The GPIO can be changed and used by the sm in different ways. In the section 'Pin configuration' the following information is found: * GPIO This shows the the GPIOs on the outside of the Pico. It is determined in the following way: * the lowest priority are the 'out' and 'set' values (if the pindir indicates the GPIO is an output) * mid priority is the sideset (if sideset is used for pins, and, if the pindir is set to output) * highest priority are the externally driven pins. These win. If an external input is provided to a pin that is configured as output, a warning is given. This situation may potentially destroy the Pico. The state of the pins is given as follows: * . means not set * 0 means low * 1 means high * GPIO ext This indicates if a pin is externally driven (configured by the pin-program). A '.' means not driven, '0' means driven low, and '1' means driven high. * pindir Indicates whether the pin is configured as Input '1', or as Output '0'. * sideset vals Gives the values of the pins set by sideset. * sideset pins Gives the pins that are going to be set via sideset. 'B' means the base, 'C' means count. So, if sideset is configured with pin 3 as base and a count of 4, this would look like: ```00000000000000000000000CCCB000``` * out vals Gives the values of the pins set by out * out pins Gives the pins that are going to be set via out. 'B' means the base, 'C' means count. So, if out is configured with pin 3 as base and a count of 4, this would look like: ```00000000000000000000000CCCB000``` * set vals Gives the values of the pins set by set * set pins Gives the pins that are going to be set via set. 'B' means the base, 'C' means count. So, if set is configured with pin 3 as base and a count of 4, this would look like: ```00000000000000000000000CCCB000``` * in pins Indicates with a 'B' which pin is the base for the in operation * jmp pin Indicates with a 'B' which pin is the jmp-pin ##### Warning and errors In this part of the GUI warning will appear at the time step they were observed during the emulation. ## What could be improved Several things, you tell me! Some things that might be improved: * not all functionality is implemented (search for TODO in the code), and what is implemented may be buggy and inefficient * there are 30 accessible GPIOs on the Pico, but in the emulator there are 32 * the emulator can't be started without pin_program, c_program, or - of course - the *.pio.h files ## Does it contain bugs? Yes, be warned! If you find any, please let met know so I can improve the code. ## Examples I have included a number of examples to show that it works. For an explanation I refer to: * [button debounce](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/Button-debouncer) * [multiplication](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/multiplication) * [rotational shift](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/Rotational_shift_ISR) * [led panel](https://github.com/GitJer/Some_RPI-Pico_stuff/tree/main/ledpanel) * [square wave](https://datasheets.raspberrypi.org/rp2040/rp2040-datasheet.pdf), from the RP2040 Datasheet * [stepper motor](https://www.youtube.com/watch?v=UJ4JjeCLuaI) by Tinker Tech Trove * side_step * in_shift ================================================ FILE: state_machine_emulator/config.py ================================================ SAVE_TEST_DATA = False EMULATION_STEPS = 500 STATEMACHINE_NUMBER = 0 ================================================ FILE: state_machine_emulator/emulation.py ================================================ from copy import deepcopy class emulation: """ This class controls the emulation of the sm of a RP2040 """ def __init__(self, state_machine, pin_program, c_program): """ init makes the list with the emulation results (output) and the variables needed for highlights in the GUI """ # the emulator for the RP2040: self.state_machine = state_machine # the (user) changes made to the pins: self.pin_program = pin_program # the user c-program that influences the PIO and sm self.c_program = c_program # the output the emulator produces based on the c_program; this is used for the GUI self.output = [] # lines to be highlighted in the GUI parts for e.g. the c-statements and pin-settings self.emulation_highlight_pin_program = [] self.emulation_highlight_c_program = [] self.emulation_output_c_program = [] self.emulation_highlight_output_c_program = [] def emulate(self, number_of_steps): """ emulate a number of steps """ for step in range(number_of_steps): # prepare for warning messages self.warning_messages = [] # first execute the pin and c_program statements warnings = self.execute_pin_and_c_program() if warnings: self.warning_messages.extend(warnings) # then run the PIO code in the emulator warnings = self.state_machine.time_step() if warnings: self.warning_messages.extend(warnings) # copy the current state and append it to the list with all data self.output.append((deepcopy(self.state_machine.GPIO_data), deepcopy(self.state_machine.vars), deepcopy(self.state_machine.settings), deepcopy(self.state_machine.sm_irq), deepcopy(self.emulation_highlight_pin_program), deepcopy(self.emulation_highlight_c_program), deepcopy(self.emulation_highlight_output_c_program), deepcopy(self.warning_messages) )) # start with a clean list of to-be-highlighted parts self.emulation_highlight_pin_program = [] self.emulation_highlight_c_program = [] self.emulation_highlight_output_c_program = [] def bit_string(self, value): """ function to produce a string with the binary representation of a value """ return str().join(["0" if (value & (1 << i)) == 0 else "1" for i in reversed(range(32))]) def execute_pin_and_c_program(self): """ executes the c-program and pin settings as specified by the c- and pin-program """ # get the current time from the state_machine (= system level clock) time = self.state_machine.clock # warnings warning_messages = [] # set all the GPIO's according to the pin states for index, g in enumerate(self.pin_program): if g[0] == time: # TODO? wasteful to go through the whole list each time. Keep an index or something (but then again, 500 steps takes almost no time ... so why bother) # all pin_statements for this time step are to be highlighted self.emulation_highlight_pin_program.append(index) # handle the GPIO and 'all' statements if 'GPIO' in g[1]: gpio = int(g[1].replace('GPIO', '')) self.state_machine.GPIO_data["GPIO_external"][gpio] = int(g[2]) # also set the real GPIO since external always wins! self.state_machine.GPIO_data["GPIO"][gpio] = int(g[2]) elif 'all' in g[1]: for gpio in range(32): self.state_machine.GPIO_data["GPIO_external"][gpio] = int(g[2]) # also set the real GPIO since external always wins! self.state_machine.GPIO_data["GPIO"][gpio] = int(g[2]) else: # should already have been filtered out when parsing the file, but anyway: warning_messages.append("Warning: unknown pin_program statement"+str(g[0])+str(g[1])+str(g[2])+"\n") # set c_program settings # go through the c-program, and at the right time, execute the statements for index, c in enumerate(self.c_program): if c[0] == time: # TODO? wasteful to go through the whole list each time. Keep an index or something (but then again, 500 steps takes almost no time ... so why bother) # all c_statements for this time step are to be highlighted in the GUI self.emulation_highlight_c_program.append(index) # handle all possible c_statements if c[1] == 'put': # place a value in the Tx FIFO if self.state_machine.vars["TxFIFO_count"] < 4: # there is still room in the TxFIFO: add the value c[2] and increase the number of items in the TxFIFO self.state_machine.vars["TxFIFO"][self.state_machine.vars["TxFIFO_count"]] = c[2] self.state_machine.vars["TxFIFO_count"] += 1 # make sure the 'status' is correct if self.state_machine.settings['status_sel'] == 0: if self.state_machine.vars["TxFIFO_count"] < self.state_machine.settings['FIFO_level_N']: self.state_machine.vars["status"] = 0xFFFFFFFF; # binary all ones else: self.state_machine.vars["status"] = 0; # binary all zeroes elif c[1] == 'get': # get a value from the Rx FIFO, and put it in output if self.state_machine.vars["RxFIFO_count"] > 0: # there are items in RxFIFO self.emulation_highlight_output_c_program.append( len(self.emulation_output_c_program)) self.emulation_output_c_program.append( str(time) + " : " + str(self.state_machine.vars["RxFIFO"][0]) + " = " + self.bit_string(self.state_machine.vars["RxFIFO"][0])) # shift FIFO entries 1 to 4 back one place for i in range(0, 3): self.state_machine.vars["RxFIFO"][i] = self.state_machine.vars["RxFIFO"][i+1] # set the last entry to 0 (this may not happen in reality!) self.state_machine.vars["RxFIFO"][3] = 0 # there is now one less item in the Rx FIFO self.state_machine.vars["RxFIFO_count"] -= 1 # make sure the 'status' is correct if self.state_machine.settings['status_sel'] == 1: if self.state_machine.vars["RxFIFO_count"] < self.state_machine.settings['FIFO_level_N']: self.state_machine.vars["status"] = 0xFFFFFFFF; # binary all ones else: self.state_machine.vars["status"] = 0; # binary all zeroes elif c[1] in ['set_base', 'set_count', 'in_base', 'jmp_pin', 'sideset_base', 'sideset_count', 'sideset_opt', 'sideset_pindirs', 'out_base', 'out_count', 'out_shift_right', 'out_shift_autopull', 'pull_threshold', 'in_shift_right', 'in_shift_autopush', 'push_threshold']: self.state_machine.settings[c[1]] = c[2] elif c[1] == 'get_pc': # note: the c_program is executed before an emulation step. Thus get_pc shows the previous pc in the gui self.emulation_highlight_output_c_program.append(len(self.emulation_output_c_program)) self.emulation_output_c_program.append(str(time) + " : " + "pc=" + str(self.state_machine.vars["pc"])) elif c[1] == 'set_pc': # note: this should (maybe) only be used at t=0, the c_program is executed before an emulation step. Thus set_pc can set the starting point # note: If not set explicitly, pc = -1 at the start, so the pc first adds 1 to start at 0. Here the 1 must first be subtracted self.state_machine.vars["pc"] = c[2]-1 elif c[1] == 'irq': # clear the bit in c[2]. Note that in c++, when clearing an irq, you have to set the corresponding bit with: # pio0_hw->irq = 1<=6 240, GPIO0, 0 280, GPIO0, 1 320, GPIO0, 0 360, GPIO0, 1 ================================================ FILE: state_machine_emulator/examples/button_debounce/pio_program.pio.h ================================================ // -------------------------------------------------- // // This file is autogenerated by pioasm; do not edit! // // -------------------------------------------------- // #if !PICO_NO_HARDWARE #include "hardware/pio.h" #endif // --------------- // // button_debounce // // --------------- // #define button_debounce_wrap_target 0 #define button_debounce_wrap 10 static const uint16_t button_debounce_program_instructions[] = { // .wrap_target 0x00c6, // 0: jmp pin, 6 0x20a0, // 1: wait 1 pin, 0 0xe03f, // 2: set x, 31 0x00c5, // 3: jmp pin, 5 0x0001, // 4: jmp 1 0x0043, // 5: jmp x--, 3 0x2020, // 6: wait 0 pin, 0 0xe03f, // 7: set x, 31 0x00c6, // 8: jmp pin, 6 0x0048, // 9: jmp x--, 8 0x0001, // 10: jmp 1 // .wrap }; #if !PICO_NO_HARDWARE static const struct pio_program button_debounce_program = { .instructions = button_debounce_program_instructions, .length = 11, .origin = -1, }; static inline pio_sm_config button_debounce_program_get_default_config(uint offset) { pio_sm_config c = pio_get_default_sm_config(); sm_config_set_wrap(&c, offset + button_debounce_wrap_target, offset + button_debounce_wrap); return c; } #endif ================================================ FILE: state_machine_emulator/examples/in_shift/c_program ================================================ # timestamp, command, arguments of command 0, in_base, 0 0, in_shift_right, 0 9, in_base, 10 9, in_shift_right, 1 ================================================ FILE: state_machine_emulator/examples/in_shift/pin_program ================================================ # timestamp, pinnumber, state # timestamp 0 is the first clock # pinnumber = all means all GPIOs # pinnumber = GPIOx means pin x # state = -1 means not driven externally # state = 0 means driven low externally # state = 1 means driven high externally 0, all, -1 0, GPIO0, 1 0, GPIO1, 1 0, GPIO2, 1 0, GPIO3, 1 1, GPIO0, 0 1, GPIO1, 0 1, GPIO2, 0 1, GPIO3, 0 2, GPIO0, 1 2, GPIO1, 1 2, GPIO2, 1 2, GPIO3, 0 3, GPIO0, 0 3, GPIO1, 0 3, GPIO2, 0 3, GPIO3, 1 10, GPIO10, 0 10, GPIO11, 0 10, GPIO12, 0 10, GPIO13, 0 11, GPIO10, 1 11, GPIO11, 1 11, GPIO12, 1 11, GPIO13, 1 12, GPIO10, 1 12, GPIO11, 0 12, GPIO12, 0 12, GPIO13, 0 13, GPIO10, 0 13, GPIO11, 1 13, GPIO12, 1 13, GPIO13, 1 ================================================ FILE: state_machine_emulator/examples/in_shift/pio_program.pio.h ================================================ // -------------------------------------------------- // // This file is autogenerated by pioasm; do not edit! // // -------------------------------------------------- // #pragma once #if !PICO_NO_HARDWARE #include "hardware/pio.h" #endif // ------ // // tester // // ------ // #define tester_wrap_target 0 #define tester_wrap 0 static const uint16_t tester_program_instructions[] = { // .wrap_target 0x4004, // 0: in pins, 4 // .wrap }; #if !PICO_NO_HARDWARE static const struct pio_program tester_program = { .instructions = tester_program_instructions, .length = 1, .origin = -1, }; static inline pio_sm_config tester_program_get_default_config(uint offset) { pio_sm_config c = pio_get_default_sm_config(); sm_config_set_wrap(&c, offset + tester_wrap_target, offset + tester_wrap); return c; } #endif ================================================ FILE: state_machine_emulator/examples/irq_set_and_clear/c_program ================================================ # timestamp, command, arguments of command 10, irq, 0 12, irq, 1 14, irq, 2 16, irq, 3 18, irq, 4 20, irq, 5 22, irq, 6 24, irq, 7 ================================================ FILE: state_machine_emulator/examples/irq_set_and_clear/pin_program ================================================ # timestamp, pinnumber, state # timestamp 0 is the first clock # pinnumber = all means all GPIOs # pinnumber = GPIOx means pin x # state = -1 means not driven externally # state = 0 means driven low externally # state = 1 means driven high externally 0, all, -1 ================================================ FILE: state_machine_emulator/examples/irq_set_and_clear/pio_program.pio.h ================================================ // -------------------------------------------------- // // This file is autogenerated by pioasm; do not edit! // // -------------------------------------------------- // #pragma once #if !PICO_NO_HARDWARE #include "hardware/pio.h" #endif // ------ // // tester // // ------ // #define tester_wrap_target 0 #define tester_wrap 15 static const uint16_t tester_program_instructions[] = { // .wrap_target 0xc000, // 0: irq nowait 0 0xc001, // 1: irq nowait 1 0xc002, // 2: irq nowait 2 0xc003, // 3: irq nowait 3 0xc004, // 4: irq nowait 4 0xc005, // 5: irq nowait 5 0xc006, // 6: irq nowait 6 0xc007, // 7: irq nowait 7 0xc020, // 8: irq wait 0 0xc021, // 9: irq wait 1 0xc022, // 10: irq wait 2 0xc023, // 11: irq wait 3 0xc024, // 12: irq wait 4 0xc025, // 13: irq wait 5 0xc026, // 14: irq wait 6 0xc027, // 15: irq wait 7 // .wrap }; #if !PICO_NO_HARDWARE static const struct pio_program tester_program = { .instructions = tester_program_instructions, .length = 16, .origin = -1, }; static inline pio_sm_config tester_program_get_default_config(uint offset) { pio_sm_config c = pio_get_default_sm_config(); sm_config_set_wrap(&c, offset + tester_wrap_target, offset + tester_wrap); return c; } #endif ================================================ FILE: state_machine_emulator/examples/ledpanel/c_program ================================================ # timestamp, command [, argument of command] 0, sideset_base, 13 0, out_shift_right, True 0, out_shift_autopull, True 0, pull_threshold, 22 0, out_base, 0 0, out_count, 11 0, put, 4194303 8, put, 4194303 16, put, 4194303 24, put, 4194303 32, put, 4194303 40, put, 4194303 48, put, 4194303 56, put, 4194303 64, put, 4194303 72, put, 4194303 80, put, 4194303 88, put, 4194303 96, put, 4194303 104, put, 4194303 112, put, 4194303 120, put, 4194303 140, put, 4194303 160, put, 4194303 180, put, 4194303 181, put, 4194303 182, put, 4194303 183, put, 4194303 184, put, 4194303 185, put, 4194303 186, put, 4194303 187, put, 4194303 188, put, 4194303 189, put, 4194303 ================================================ FILE: state_machine_emulator/examples/ledpanel/ledpanel.pio.h ================================================ // -------------------------------------------------- // // This file is autogenerated by pioasm; do not edit! // // -------------------------------------------------- // #pragma once #if !PICO_NO_HARDWARE #include "hardware/pio.h" #endif // -------- // // ledpanel // // -------- // #define ledpanel_wrap_target 2 #define ledpanel_wrap 10 static const uint16_t ledpanel_program_instructions[] = { 0xb0cb, // 0: mov isr, !null side 4 0x5079, // 1: in null, 25 side 4 // .wrap_target 0xa026, // 2: mov x, isr side 0 0x7a0b, // 3: out pins, 11 side 6 [2] 0x1043, // 4: jmp x--, 3 side 4 0x642b, // 5: out x, 11 side 1 0x642b, // 6: out x, 11 side 1 0xa342, // 7: nop side 0 [3] 0xa342, // 8: nop side 0 [3] 0xa342, // 9: nop side 0 [3] 0x0047, // 10: jmp x--, 7 side 0 // .wrap }; #if !PICO_NO_HARDWARE static const struct pio_program ledpanel_program = { .instructions = ledpanel_program_instructions, .length = 11, .origin = -1, }; static inline pio_sm_config ledpanel_program_get_default_config(uint offset) { pio_sm_config c = pio_get_default_sm_config(); sm_config_set_wrap(&c, offset + ledpanel_wrap_target, offset + ledpanel_wrap); sm_config_set_sideset(&c, 3, false, false); return c; } #endif ================================================ FILE: state_machine_emulator/examples/ledpanel/pin_program ================================================ # timestamp, pinnumber, state # timestamp 0 is the first clock # pinnumber = all means all GPIOs # pinnumber = GPIOx means pin x # state = -1 means not driven externally # state = 0 means driven low externally # state = 1 means driven high externally 0, all, -1 ================================================ FILE: state_machine_emulator/examples/multiplication/c_program ================================================ # timestamp, command, arguments of command 0, put, 5 0, put, 3 55, get 56, put, 7 56, put, 8 225, get ================================================ FILE: state_machine_emulator/examples/multiplication/pin_program ================================================ # timestamp, pinnumber, state # timestamp 0 is the first clock # pinnumber = all means all GPIOs # pinnumber = GPIOx means pin x # state = -1 means not driven externally # state = 0 means driven low externally # state = 1 means driven high externally 0, all, -1 ================================================ FILE: state_machine_emulator/examples/multiplication/pio_program.pio.h ================================================ // -------------------------------------------------- // // This file is autogenerated by pioasm; do not edit! // // -------------------------------------------------- // #if !PICO_NO_HARDWARE #include "hardware/pio.h" #endif // ------ // // tester // // ------ // #define tester_wrap_target 0 #define tester_wrap 15 static const uint16_t tester_program_instructions[] = { // .wrap_target 0x80a0, // 0: pull block 0xa027, // 1: mov x, osr 0xa0c1, // 2: mov isr, x 0x80a0, // 3: pull block 0xa047, // 4: mov y, osr 0x0089, // 5: jmp y--, 9 0xa0c3, // 6: mov isr, null 0x8020, // 7: push block 0x0000, // 8: jmp 0 0xa0e2, // 9: mov osr, y 0xa04b, // 10: mov y, !null 0x0050, // 11: jmp x--, 16 0xa027, // 12: mov x, osr 0x0054, // 13: jmp x--, 20 0xa0ca, // 14: mov isr, !y 0x8020, // 15: push block // .wrap 0x008b, // 16: jmp y--, 11 0xa0cb, // 17: mov isr, !null 0x8020, // 18: push block 0x0000, // 19: jmp 0 0xa0e1, // 20: mov osr, x 0xa026, // 21: mov x, isr 0x000b, // 22: jmp 11 }; #if !PICO_NO_HARDWARE static const struct pio_program tester_program = { .instructions = tester_program_instructions, .length = 23, .origin = -1, }; // sm_config_set_sideset(&c, 2, true, true); static inline pio_sm_config tester_program_get_default_config(uint offset) { pio_sm_config c = pio_get_default_sm_config(); sm_config_set_wrap(&c, offset + tester_wrap_target, offset + tester_wrap); return c; } #endif ================================================ FILE: state_machine_emulator/examples/push_pull_auto/c_program ================================================ 0, out_shift_right, True 0, out_shift_autopull, True 0, pull_threshold, 10 0, in_shift_right, False 0, in_shift_autopush, True 0, push_threshold, 3 20, put, 31 30, get 31, get 40, put, 63 50, get 51, get 60, put, 95 70, get 71, get 80, put, 127 90, get 91, get 100, put, 159 105, get 106, get 110, put, 191 115, get 116, get 120, put, 223 125, get 126, get 140, put, 225 145, get 146, get 160, put, 287 165, get 166, get 180, put, 319 185, get 186, get 200, put, 351 205, get 206, get ================================================ FILE: state_machine_emulator/examples/push_pull_auto/pin_program ================================================ 0, all, 0 ================================================ FILE: state_machine_emulator/examples/push_pull_auto/push_pull.pio.h ================================================ // -------------------------------------------------- // // This file is autogenerated by pioasm; do not edit! // // -------------------------------------------------- // #pragma once #if !PICO_NO_HARDWARE #include "hardware/pio.h" #endif // --------- // // push_pull // // --------- // #define push_pull_wrap_target 0 #define push_pull_wrap 1 static const uint16_t push_pull_program_instructions[] = { // .wrap_target 0x6025, // 0: out x, 5 0x4023, // 1: in x, 3 // .wrap }; #if !PICO_NO_HARDWARE static const struct pio_program push_pull_program = { .instructions = push_pull_program_instructions, .length = 2, .origin = -1, }; static inline pio_sm_config push_pull_program_get_default_config(uint offset) { pio_sm_config c = pio_get_default_sm_config(); sm_config_set_wrap(&c, offset + push_pull_wrap_target, offset + push_pull_wrap); return c; } #endif ================================================ FILE: state_machine_emulator/examples/rotational_shift/c_program ================================================ # timestamp, command, arguments of command 0, put, 1234567890 10, get 15, get 20, get 25, get 30, get 35, get 40, get 45, get 50, get 60, get 65, get 70, get 75, get 80, get 85, get 90, get 95, get 100, get 105, get 110, get 115, get 120, get 125, get 130, get 135, get 140, get 145, get 150, get 160, get 165, get 170, get 175, get 180, get 185, get 190, get 195, get 200, get 205, get 210, get 215, get 220, get 225, get 230, get 235, get 240, get 245, get 250, get 260, get 265, get 270, get 275, get 280, get 285, get 290, get 295, get 300, get 305, get 310, get 315, get 320, get 325, get 330, get 335, get 340, get 345, get 350, get 360, get 365, get 370, get 375, get 380, get 385, get 390, get ================================================ FILE: state_machine_emulator/examples/rotational_shift/pin_program ================================================ # timestamp, pinnumber, state # timestamp 0 is the first clock # pinnumber = all means all GPIOs # pinnumber = GPIOx means pin x # state = -1 means not driven externally # state = 0 means driven low externally # state = 1 means driven high externally 0, all, -1 ================================================ FILE: state_machine_emulator/examples/rotational_shift/pio_program.pio.h ================================================ // -------------------------------------------------- // // This file is autogenerated by pioasm; do not edit! // // -------------------------------------------------- // #if !PICO_NO_HARDWARE #include "hardware/pio.h" #endif // -------------------- // // rotational_shift_ISR // // -------------------- // #define rotational_shift_ISR_wrap_target 0 #define rotational_shift_ISR_wrap 18 static const uint16_t rotational_shift_ISR_program_instructions[] = { // .wrap_target 0x80a0, // 0: pull block 0xa0c7, // 1: mov isr, osr 0xa046, // 2: mov y, isr 0x8020, // 3: push block 0xa0c2, // 4: mov isr, y 0xe03f, // 5: set x, 31 0x40c1, // 6: in isr, 1 0xa046, // 7: mov y, isr 0x8020, // 8: push block 0xa0c2, // 9: mov isr, y 0x0046, // 10: jmp x--, 6 0xe03f, // 11: set x, 31 0xa0d6, // 12: mov isr, ::isr 0x40c1, // 13: in isr, 1 0xa0d6, // 14: mov isr, ::isr 0xa046, // 15: mov y, isr 0x8020, // 16: push block 0xa0c2, // 17: mov isr, y 0x004c, // 18: jmp x--, 12 // .wrap }; #if !PICO_NO_HARDWARE static const struct pio_program rotational_shift_ISR_program = { .instructions = rotational_shift_ISR_program_instructions, .length = 19, .origin = -1, }; static inline pio_sm_config rotational_shift_ISR_program_get_default_config(uint offset) { pio_sm_config c = pio_get_default_sm_config(); sm_config_set_wrap(&c, offset + rotational_shift_ISR_wrap_target, offset + rotational_shift_ISR_wrap); return c; } #endif ================================================ FILE: state_machine_emulator/examples/side_step/README.md ================================================ # The sidestep also works with two GPIO as output: ## pio program: ``` .program tester .side_set 2 opt .wrap_target nop side 0b01 nop nop side 0b10 .wrap ``` ## c program: ``` 0, sideset_base, 4 ``` ================================================ FILE: state_machine_emulator/examples/side_step/c_program ================================================ # timestamp, command, arguments of command 0, sideset_base, 4 ================================================ FILE: state_machine_emulator/examples/side_step/pin_program ================================================ # timestamp, pinnumber, state # timestamp 0 is the first clock # pinnumber = all means all GPIOs # pinnumber = GPIOx means pin x # state = -1 means not driven externally # state = 0 means driven low externally # state = 1 means driven high externally 0, all, -1 ================================================ FILE: state_machine_emulator/examples/side_step/pio_program.pio.h ================================================ // -------------------------------------------------- // // This file is autogenerated by pioasm; do not edit! // // -------------------------------------------------- // #pragma once #if !PICO_NO_HARDWARE #include "hardware/pio.h" #endif // ------ // // tester // // ------ // #define tester_wrap_target 0 #define tester_wrap 2 static const uint16_t tester_program_instructions[] = { // .wrap_target 0xb042, // 0: nop side 0 0xa042, // 1: nop 0xb842, // 2: nop side 1 // .wrap }; #if !PICO_NO_HARDWARE static const struct pio_program tester_program = { .instructions = tester_program_instructions, .length = 3, .origin = -1, }; static inline pio_sm_config tester_program_get_default_config(uint offset) { pio_sm_config c = pio_get_default_sm_config(); sm_config_set_wrap(&c, offset + tester_wrap_target, offset + tester_wrap); sm_config_set_sideset(&c, 2, true, false); return c; } #endif ================================================ FILE: state_machine_emulator/examples/square_wave/c_program ================================================ # timestamp, command, arguments of command (typically pio, sm, arguments) 0, set_base, 0 0, set_count, 1 ================================================ FILE: state_machine_emulator/examples/square_wave/pin_program ================================================ # timestamp, pinnumber, state # timestamp 0 is the first clock # pinnumber = all means all GPIOs # pinnumber = GPIOx means pin x # state = -1 means not driven externally # state = 0 means driven low externally # state = 1 means driven high externally 0, all, -1 ================================================ FILE: state_machine_emulator/examples/square_wave/pio_program.pio.h ================================================ // -------------------------------------------------- // // 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 ================================================ FILE: state_machine_emulator/examples/stepper/c_program ================================================ # timestamp, command, arguments of command 0, out_base, 0 0, out_count, 4 0, put, 100 0, put, 2216789025 0, out_shift_right, 1 ================================================ FILE: state_machine_emulator/examples/stepper/pin_program ================================================ # timestamp, pinnumber, state # timestamp 0 is the first clock # pinnumber = all means all GPIOs # pinnumber = GPIOx means pin x # state = -1 means not driven externally # state = 0 means driven low externally # state = 1 means driven high externally 0, all, -1 ================================================ FILE: state_machine_emulator/examples/stepper/pio_program.pio.h ================================================ // This example is taken from: // https://github.com/tinkertechtrove/pico-pi-playing/blob/main/pio-steppers/test_motor4.py // -------------------------------------------------- // // This file is autogenerated by pioasm; do not edit! // // -------------------------------------------------- // #pragma once #if !PICO_NO_HARDWARE #include "hardware/pio.h" #endif // ------ // // tester // // ------ // #define tester_wrap_target 0 #define tester_wrap 9 static const uint16_t tester_program_instructions[] = { // .wrap_target 0x80a0, // 0: pull block 0xa027, // 1: mov x, osr 0x80a0, // 2: pull block 0xa047, // 3: mov y, osr 0x0029, // 4: jmp !x, 9 0x00e7, // 5: jmp !osre, 7 0xa0e2, // 6: mov osr, y 0x7f04, // 7: out pins, 4 [31] 0x0045, // 8: jmp x--, 5 0xc000, // 9: irq nowait 0 // .wrap }; #if !PICO_NO_HARDWARE static const struct pio_program tester_program = { .instructions = tester_program_instructions, .length = 10, .origin = -1, }; static inline pio_sm_config tester_program_get_default_config(uint offset) { pio_sm_config c = pio_get_default_sm_config(); sm_config_set_wrap(&c, offset + tester_wrap_target, offset + tester_wrap); return c; } #endif ================================================ FILE: state_machine_emulator/interface/__init__.py ================================================ from tkinter import Tk from threading import Thread class Emulator_Interface(Thread): """ This class builds the GUI to show the results of the emulation of the sm of a RP2040 """ # Imported methods from ._toolbar import build_toolbar, step_callback, quit_callback, step_10_callback, step_50_callback, restart_callback, step_10_back_callback, step_50_back_callback, step_back_callback, reload_callback, enable_disable_buttons from ._left_frame import build_left_frame, update_left_frame from ._mid_frame import build_mid_frame, update_mid_frame from ._right_frame import build_right_frame, update_right_frame from ._output_frame import build_output_frame, update_output_frame def __init__(self, program_definitions, pin_program, c_program, emulation_results, emulation_output_c_program): """ build and run the GUI """ # the flag that can signal to the main function to reload all files self.reload_flag = False # the data that needs to be displayed self.program_definitions = program_definitions self.pio_program = program_definitions['pio_program'] self.pin_program = pin_program self.c_program = c_program self.emulation_results = emulation_results self.emulation_output_c_program = emulation_output_c_program # the current time and max time (for checking jumps of +1, +10 and +50) self.current_clock = 0 self.max_clock = len(self.emulation_results) # Make the window self.root = Tk() # Make the title that will appear in the top left self.root.wm_title("Raspberry PI PICO PIO Emulation") # set background color to white self.root.config(background="#FFFFFF") # this is just a simple activity to allow ctrl-c in a terminal to function # TODO: doesn't work? self.after_id = self.root.after(50, self.check) # make the frames self.build_toolbar() self.build_left_frame() self.build_mid_frame() self.build_right_frame() self.build_output_frame() # update the display to reflect the data of self.current_clock=0 self.update_display() # start the monitoring and updating self.root.mainloop() def check(self): """ this is just a simple activity to allow ctrl-c to function """ self.after_id = self.root.after(50, self.check) # 50 stands for 50 ms. def __del__(self): """ clean up at exit """ self.root.after_cancel(self.after_id) try: self.root.quit() self.root.destroy() except: pass def get_reload_flag(self): return self.reload_flag def update_display(self): """ update the display at every step to show the emulation results for the new step """ self.update_left_frame() self.update_mid_frame() self.update_right_frame() self.update_output_frame() ================================================ FILE: state_machine_emulator/interface/_interface_item.py ================================================ from tkinter import Text, Label, Listbox from interface._tooltips import CreateToolTip """ All the classes here are an element of the GUI. They all have a setup (__init__), a method to make a string to be used and update. """ class Interface_Item: def __init__(self, frame, display_name, row, col, width): self.display_name = display_name self.row = row self.col = col self.width = width # make the Text widget for the name of the variable to be displayed label = Text(frame, height=1, width=12) label.insert("end", self.display_name) label.configure(font="TkFixedFont", state="disabled") label.grid(row=self.row, column=self.col, padx=(5, 5), sticky='W') # make the Text widget for the value of the variable self.value_label = Text(frame, height=1, width=self.width) self.value_label.insert("end", self.value_string(0)) self.value_label.configure(font="TkFixedFont", state="disabled") self.value_label.grid(row=self.row, column=self.col, padx=(5, 5), sticky='E') # make tags to be used in showing which characters have changed, and which have not self.value_label.tag_config("changed", foreground="Blue") self.value_label.tag_config("unchanged", foreground="Black") def update(self, clock): # save the current value old_value = self.value_label.get("1.0", "end").strip() # allow the widget text to be changed, and delete the existing content self.value_label.configure(state="normal") self.value_label.delete("1.0", "end") # determine the new text for the widget, and insert it new_value = self.value_string(clock) self.value_label.insert("end", new_value) # disallow changing the text, justify right self.value_label.configure(state="disabled") self.value_label.tag_configure("j_right", justify='right') self.value_label.tag_add("j_right", 1.0, "end") # now apply colors, using tags "changed and "unchanged", e.g. to the last 32 characters (the binary representation) len_string = min(len(new_value), len(old_value), 32) for i in range(-1,-len_string-1, -1): if old_value[i] != new_value[i]: self.value_label.tag_add("changed", f"{1}.{len(new_value)+i}") else: self.value_label.tag_add("unchanged", f"{1}.{len(new_value)+i}") class Var_Bits_32(Interface_Item): def __init__(self, display_name, var_name, frame, row, col, var, var_index): self.var_name = var_name self.var = var self.var_index = var_index super().__init__(frame, display_name, row, col, 45) def value_string(self, clock): value_string = str(self.var[clock][self.var_index][self.var_name] & 0xFFFFFFFF) + " = " value = self.var[clock][self.var_index][self.var_name] # extend the value_string based on bits in 'value' for i in reversed(range(32)): value_string += "0" if (value & (1 << i)) == 0 else "1" return value_string class Pin_Settings_32(Interface_Item): def __init__(self, display_name, base_name, count_name, frame, row, col, var, var_index): self.base_name = base_name self.count_name = count_name self.var = var self.var_index = var_index super().__init__(frame, display_name, row, col, 40) def value_string(self, clock): base = self.var[clock][self.var_index][self.base_name] count = 0 if self.count_name: count = self.var[clock][self.var_index][self.count_name] # dirty hack: sideset_count includes the sideset_opt bit, but this does not set a pin! if self.count_name == "sideset_count" and self.var[clock][self.var_index]["sideset_opt"]: count -= 1 value_string_list = ['.' for i in range(32)] if base >= 0: value_string_list[31-base] = 'B' for i in range(count-1): value_string_list[31-(base+1+i) % 32] = 'C' value_string = ''.join(value_string_list) return value_string class Var_List_IRQ(Interface_Item): def __init__(self, display_name, frame, row, col, var, var_index): self.var = var self.var_index = var_index super().__init__(frame, display_name, row, col, 40) def value_string(self, clock): value_string = "" for v in reversed(self.var[clock][self.var_index]): value_string += "1" if v==1 else "0" if v==0 else "." return value_string class Var_List(Interface_Item): def __init__(self, display_name, frame, row, col, var, var_index): self.var = var self.var_index = var_index super().__init__(frame, display_name, row, col, 40) def value_string(self, clock): value_string = "" for v in reversed(self.var[clock][0][self.var_index]): value_string += "1" if v==1 else "0" if v==0 else "." return value_string class Interface_Item_Listbox_Bits: def __init__(self, display_name, var_name, frame, row, col, var, clock): self.display_name = display_name self.var_name = var_name self.var = var label = Label(frame, text=display_name + ' \u24D8') CreateToolTip(label, \ "The data in TxFIFO is transmitted from the normal core to the state machine.\n" "The data in RxFIFO is transmitted from the state machine to the normal core. ") label.grid(row=row, column=col, padx=(5, 5), sticky='W') self.value_listbox = Listbox(frame, height=4, width=45, justify="right", exportselection=0) for index in range(4): self.value_listbox.insert("end", self.value_string(index, clock)) self.value_listbox.grid(row=row+1, column=0, padx=(5, 5)) def update(self, clock): for index in range(4): self.value_listbox.delete(0) for index in range(4): self.value_listbox.insert("end", self.value_string(index, clock)) def value_string(self, index, clock): value_string = str(self.var[clock][1][self.var_name][index] & 0xFFFFFFFF) + " = " value = self.var[clock][1][self.var_name][index] for i in reversed(range(32)): value_string += "0" if (value & (1 << i)) == 0 else "1" return value_string class Interface_Item_Listbox_Time: def __init__(self, display_name, frame, row, col, var): self.display_name = display_name self.var = var label = Label(frame, text=display_name) label.grid(row=row, column=col, padx=(5, 5), sticky='W') self.value_listbox = Listbox(frame, height=13, width=45, exportselection=0) for index in range(len(var)): self.value_listbox.insert("end", self.value_string(index)) self.value_listbox.grid(row=row+1, column=0, padx=(5, 5)) def update(self): for index in range(4): self.value_listbox.delete(0) for index in range(4): self.value_listbox.insert("end", self.value_string(index)) def value_string(self, index): value_string = str(self.var[index][0]) + " : " + self.var[index][1] first = True for l in self.var[index][2:]: value_string += "=" if first else ", " first = False value_string += str(l) return value_string ================================================ FILE: state_machine_emulator/interface/_left_frame.py ================================================ from tkinter import Frame def build_left_frame(self): """ build the panel on the left with the c-program, pin_program, and the TxFIFO and RxFIFO """ from interface._interface_item import Interface_Item_Listbox_Bits, Interface_Item_Listbox_Time # on the left side self.left_frame = Frame(self.root, width=375, height=725) self.left_frame.grid(row=1, column=0, padx=0, pady=2) self.left_frame.grid_propagate(0) # start on row=2 with placing widgets grid_row = 2 # pin states program self.pin_program_listbox = Interface_Item_Listbox_Time("Highlight is just executed pin-program", self.left_frame, grid_row, 0, self.pin_program) grid_row += 2 # c program self.c_program_listbox = Interface_Item_Listbox_Time("Highlight is just executed C-program", self.left_frame, grid_row, 0, self.c_program) grid_row += 2 # TxFIFO self.TxFIFO_listbox = Interface_Item_Listbox_Bits("TxFIFO", "TxFIFO", self.left_frame, grid_row, 0, self.emulation_results, self.current_clock) grid_row += 2 # RxFIFO self.RxFIFO_listbox = Interface_Item_Listbox_Bits("RxFIFO", "RxFIFO", self.left_frame, grid_row, 0, self.emulation_results, self.current_clock) def update_left_frame(self): """ update the left frame """ # update the Tx and Rx FIFOs self.TxFIFO_listbox.update(self.current_clock) self.RxFIFO_listbox.update(self.current_clock) # highlight the just executed pin_program statements self.pin_program_listbox.value_listbox.selection_clear(0, "end") # make sure the first highlighted item is visible by using 'see' first = True for index in self.emulation_results[self.current_clock][4]: self.pin_program_listbox.value_listbox.selection_set(index) if first: self.pin_program_listbox.value_listbox.see(index) first = False # highlight the just executed c_program statements self.c_program_listbox.value_listbox.selection_clear(0, "end") # make sure the first highlighted item is visible by using 'see' first = True for index in self.emulation_results[self.current_clock][5]: self.c_program_listbox.value_listbox.selection_set(index) if first: self.c_program_listbox.value_listbox.see(index) first = False ================================================ FILE: state_machine_emulator/interface/_mid_frame.py ================================================ from tkinter import Frame, Label, Text from config import STATEMACHINE_NUMBER def build_mid_frame(self): """ build the panel with the sm variables and settings """ from interface._interface_item import Var_Bits_32, Var_List_IRQ, Var_List, Pin_Settings_32 from interface._tooltips import CreateToolTip # In the middle self.mid_frame = Frame(self.root, width=460, height=725) self.mid_frame.grid(row=1, column=1, padx=0, pady=2) self.mid_frame.grid_propagate(0) grid_row = 2 # make a separate frame for some data items (clock, pc, delay, status) self.clk_pc_delay_stat_frame = Frame(self.mid_frame, width=460, height=25) self.clk_pc_delay_stat_frame.grid(row=grid_row, column=1, padx=0, pady=5) self.clk_pc_delay_stat_frame.grid_propagate(0) self.clk_pc_delay_stat_frame.grid_columnconfigure(index=0, weight=1) self.clk_pc_delay_stat_frame.grid_columnconfigure(index=1, weight=1) self.clk_pc_delay_stat_frame.grid_columnconfigure(index=2, weight=1) self.clk_pc_delay_stat_frame.grid_columnconfigure(index=3, weight=1) inset_col=0 # clock self.clock_label = Label(self.clk_pc_delay_stat_frame, text="clock=0", font=("Arial", 14)) self.clock_label.grid(row=1, column=inset_col, padx=(5, 5), sticky='W') inset_col += 1 # PIO pc self.pc_label = Label(self.clk_pc_delay_stat_frame, text="pc=0") self.pc_label.grid(row=1, column=inset_col, padx=(5, 5), sticky='W') inset_col += 1 # Wait cycles to complete self.delay_label = Label(self.clk_pc_delay_stat_frame, text="delay=0") self.delay_label.grid(row=1, column=inset_col, padx=(5, 5), sticky='W') inset_col += 1 # status self.status_label = Label(self.clk_pc_delay_stat_frame, text="status=0") self.status_label.grid(row=1, column=inset_col, padx=(5, 5), sticky='W') grid_row += 1 # make a separate frame for some data items (OSR and ISR shift counters) self.OSR_ISR_shift_counters_frame = Frame(self.mid_frame, width=460, height=25) self.OSR_ISR_shift_counters_frame.grid(row=grid_row, column=1, padx=0, pady=0) self.OSR_ISR_shift_counters_frame.grid_propagate(0) self.OSR_ISR_shift_counters_frame.grid_columnconfigure(index=0, weight=1) self.OSR_ISR_shift_counters_frame.grid_columnconfigure(index=1, weight=1) self.OSR_ISR_shift_counters_frame.grid_columnconfigure(index=2, weight=0) self.OSR_ISR_shift_counters_frame.grid_columnconfigure(index=3, weight=0) inset_col=0 # OSR shift counter self.OSR_shift_counter_label = Label(self.OSR_ISR_shift_counters_frame, text="OSR shift counter=0") self.OSR_shift_counter_label.grid(row=1, column=inset_col, padx=(5, 5), sticky='W') inset_col += 1 # ISR shift counter self.ISR_shift_counter_label = Label(self.OSR_ISR_shift_counters_frame, text="ISR shift counter=0") self.ISR_shift_counter_label.grid(row=1, column=inset_col, padx=(5, 5), sticky='W') # Now place the other data items in rows grid_row += 1 # OSR self.OSR_label = Var_Bits_32("OSR", "OSR", self.mid_frame, grid_row, 1, self.emulation_results, 1) grid_row += 1 # ISR self.ISR_label = Var_Bits_32("ISR", "ISR", self.mid_frame, grid_row, 1, self.emulation_results, 1) grid_row += 1 # x self.X_label = Var_Bits_32("X", "x", self.mid_frame, grid_row, 1, self.emulation_results, 1) grid_row += 1 # y self.Y_label = Var_Bits_32("Y", "y", self.mid_frame, grid_row, 1, self.emulation_results, 1) grid_row += 1 # empty row self.empty_label1 = Label(self.mid_frame) self.empty_label1.grid(row=grid_row, column=1, padx=(5, 5), sticky='W') grid_row += 1 # IRQ self.IRQ_label = Var_List_IRQ("IRQ (sm="+str(STATEMACHINE_NUMBER)+")", self.mid_frame, grid_row, 1, self.emulation_results, 3) grid_row += 1 # pin configuration section label self.pin_config_label = Label(self.mid_frame, text="\nPin configuration: \u24D8") self.pin_config_label.grid(row=grid_row, column=1, padx=(5, 5), sticky='W') self.pin_config_label_ttp = CreateToolTip(self.pin_config_label, \ "The GPIO output (first row below) is determined by (lowest priority) 'out' and 'set', or (mid priority) 'sideset'.\n" "The 'out', 'set' and 'sideset' also require pindir to be an output (a '0' in the third row below).\n" "The highest priority is an external signal (GPIO ext). This wins from any internal signal (and may damage the RPI pico!)") grid_row += 1 # GPIO self.GPIO_label = Var_List("GPIO", self.mid_frame, grid_row, 1, self.emulation_results, "GPIO") grid_row += 1 # GPIO_external self.GPIO_ext_label = Var_List("GPIO ext", self.mid_frame, grid_row, 1, self.emulation_results, "GPIO_external") grid_row += 1 # GPIO pindirs self.GPIO_pindirs_label = Var_List("pindir", self.mid_frame, grid_row, 1, self.emulation_results, "GPIO_pindirs") grid_row += 1 # SIDESET_pins self.sideset_vals_label = Var_List("sideset vals", self.mid_frame, grid_row, 1, self.emulation_results, "GPIO_sideset") grid_row += 1 self.sideset_pins_label = Pin_Settings_32("sideset pins", "sideset_base", "sideset_count", self.mid_frame, grid_row, 1, self.emulation_results, 2) grid_row += 1 # OUT_pins self.out_vals_label = Var_List("out vals", self.mid_frame, grid_row, 1, self.emulation_results, "GPIO_out") grid_row += 1 self.out_pins_label = Pin_Settings_32("out pins", "out_base", "out_count", self.mid_frame, grid_row, 1, self.emulation_results, 2) grid_row += 1 # SET_pins self.set_vals_label = Var_List("set vals", self.mid_frame, grid_row, 1, self.emulation_results, "GPIO_set") grid_row += 1 self.set_pins_label = Pin_Settings_32("set pins", "set_base", "set_count", self.mid_frame, grid_row, 1, self.emulation_results, 2) grid_row += 1 # IN_pins self.in_pins_label = Pin_Settings_32("in pins", "in_base", None, self.mid_frame, grid_row, 1, self.emulation_results, 2) grid_row += 1 # JMP_PIN self.jmp_pin_label = Pin_Settings_32("jmp pin", "jmp_pin", None, self.mid_frame, grid_row, 1, self.emulation_results, 2) grid_row += 1 # settings temp = Label(self.mid_frame, text="\nSettings:") temp.grid(row=grid_row, column=1, padx=(5, 5), sticky='W') grid_row += 1 # place the settings that are to be displayed settings = self.emulation_results[0][2] self.list_of_settings_to_be_displayed = ["in_shift_right", "in_shift_autopush", "push_threshold", "out_shift_right", "out_shift_autopull", "pull_threshold", "sideset_opt", "sideset_pindirs"] self.settings_labels = [None]*len(self.list_of_settings_to_be_displayed) for i, s in enumerate(self.list_of_settings_to_be_displayed): label = Text(self.mid_frame, height=1, width=20) label.insert("end", s) label.configure(font="TkFixedFont", state="disabled") label.grid(row=grid_row, column=1, padx=(5, 5), sticky='W') self.settings_labels[i] = Text(self.mid_frame, height=1, width=35) self.settings_labels[i].insert("end", str(settings[s])) self.settings_labels[i].configure(font="TkFixedFont", state="disabled") self.settings_labels[i].grid(row=grid_row, column=1, padx=(5, 5), sticky='E') # make tags to be used in showing which characters have changed, and which have not self.settings_labels[i].tag_config("changed", foreground="Blue") self.settings_labels[i].tag_config("unchanged", foreground="Black") grid_row += 1 def update_mid_frame(self): """ update the frame with the sm variables and settings """ vars = self.emulation_results[self.current_clock][1] self.clock_label['text'] = "clock=" + str(self.current_clock) self.pc_label['text'] = "pc=" + str(vars['pc']) self.delay_label['text'] = "delay=" + str(vars['delay']) self.status_label['text'] = "status=" + str(vars['status']) self.OSR_shift_counter_label['text'] = "OSR shift counter="+str(vars['OSR_shift_counter']) self.ISR_shift_counter_label['text'] = "ISR shift counter="+str(vars['ISR_shift_counter']) self.OSR_label.update(self.current_clock) self.ISR_label.update(self.current_clock) self.X_label.update(self.current_clock) self.Y_label.update(self.current_clock) self.GPIO_label.update(self.current_clock) self.GPIO_ext_label.update(self.current_clock) self.GPIO_pindirs_label.update(self.current_clock) self.IRQ_label.update(self.current_clock) self.in_pins_label.update(self.current_clock) self.out_pins_label.update(self.current_clock) self.out_vals_label.update(self.current_clock) self.set_vals_label.update(self.current_clock) self.set_pins_label.update(self.current_clock) self.sideset_pins_label.update(self.current_clock) self.sideset_vals_label.update(self.current_clock) self.jmp_pin_label.update(self.current_clock) # update settings settings = self.emulation_results[self.current_clock][2] for i, s in enumerate(self.list_of_settings_to_be_displayed): # save the current value for comparison with the new value old_value = self.settings_labels[i].get("1.0", "end").strip() # allow the widget text to be changed, and delete the existing content self.settings_labels[i].configure(state="normal") self.settings_labels[i].delete("1.0", "end") # determine the new text for the widget, and insert it self.settings_labels[i].insert("end", str(settings[s])) # disallow changing the text self.settings_labels[i].configure(state="disabled") # apply tags to give color to the changed data items if old_value != str(settings[s]): self.settings_labels[i].tag_add("changed", '1.0', '1.'+str(len(str(settings[s])))) else: self.settings_labels[i].tag_add("unchanged", '1.0', '1.'+str(len(str(settings[s])))) ================================================ FILE: state_machine_emulator/interface/_output_frame.py ================================================ from tkinter import Frame, Label, Listbox, Text def build_output_frame(self): """ build the frame with the output (of c-statements) of the emulation """ # the output frame self.output_frame = Frame(self.root, width=425, height=725) self.output_frame.grid(row=1, column=3, padx=0, pady=2) self.output_frame.grid_propagate(0) # program output label self.program_label = Label(self.output_frame, text="Highlight is just produced output from RxFIFO:") self.program_label.grid(row=2, column=3, padx=(5, 5), sticky='W') # program output is in a listbox self.output_listbox = Listbox(self.output_frame, height=26, width=50, justify="right", exportselection=0) self.output_listbox.grid(row=3, column=3, padx=(5, 5), sticky='E') # put the c-program into the listbox (in update_output_frame the current line will be highlighted (selected) in the GUI) for line in self.emulation_output_c_program: self.output_listbox.insert("end", line) # messages label self.program_label = Label(self.output_frame, text="Messages") self.program_label.grid(row=4, column=3, padx=(5, 5), sticky='W') # messages self.messages_text = Text(self.output_frame, height=11, width=50, exportselection=0) self.messages_text.grid(row=5, column=3, padx=(5, 5), sticky='E') def update_output_frame(self): """ update the panel with the output (of c-statements) """ # highlight the just executed output self.output_listbox.selection_clear(0, "end") for index in self.emulation_results[self.current_clock][6]: self.output_listbox.selection_set(index) self.output_listbox.see(index) # put the messages produced at the current clock here self.messages_text.delete("1.0", "end") for line in self.emulation_results[self.current_clock][7]: self.messages_text.insert("end", "-> " + line) ================================================ FILE: state_machine_emulator/interface/_right_frame.py ================================================ from tkinter import Frame, Label, Listbox def build_right_frame(self): """ build the panel with the pio program """ # on the right side self.right_frame = Frame(self.root, width=305, height=725) # self.right_frame = Frame(self.root, width=345, height=675) self.right_frame.grid(row=1, column=2, padx=0, pady=2) self.right_frame.grid_propagate(0) # self.right_frame.grid_columnconfigure(1, weight=1) self.program_label = Label(self.right_frame, text="Highlight is just executed pio-code:") self.program_label.grid(row=1, column=2, padx=(5, 5), sticky='W') # PIO program self.code_listbox = Listbox(self.right_frame, height=38, width=40) for index, p in enumerate(self.pio_program): if index == self.program_definitions['pio_program_wrap']: self.code_listbox.insert("end", p[1] + ' (wrap)') elif index == self.program_definitions['pio_program_wrap_target']: self.code_listbox.insert("end", p[1] + ' (wrap target)') elif index == self.program_definitions['pio_program_origin']: self.code_listbox.insert("end", p[1] + ' (origin)') else: self.code_listbox.insert("end", p[1]) self.code_listbox.grid(row=2, column=2, padx=(5, 5), sticky='E') def update_right_frame(self): """ update the panel on the right with the pio program """ self.code_listbox.selection_clear(0, "end") self.code_listbox.selection_set(self.emulation_results[self.current_clock][1]['pc']) ================================================ FILE: state_machine_emulator/interface/_toolbar.py ================================================ from tkinter import Frame, Button """ build the toolbar with associated call back functions and key bindings """ def enable_disable_buttons(self): """ enable or disable buttons and keybindings depending on time step and max time step """ # disable 50 step back button if not possible self.step_50_back_button["state"] = "disabled" if self.current_clock < 50 else "normal" # disable 10 step back button and keybinding if not possible, otherwise enable self.step_10_back_button["state"] = "disabled" if self.current_clock < 10 else "normal" # disable step back button and keybinding if not possible, otherwise enable self.step_back_button["state"] = "disabled" if self.current_clock == 0 else "normal" # disable step button and keybinding if not possible, otherwise enable self.step_button["state"] = "normal" if self.current_clock < self.max_clock-1 else "disabled" # disable 10 step button if not possible self.step_10_button["state"] = "normal" if self.current_clock < self.max_clock-10 else "disabled" # disable 50 step button if not possible self.step_50_button["state"] = "normal" if self.current_clock < self.max_clock-51 else "disabled" def reload_callback(self, event): """ if the 'reload' button is pressed, start from the beginning, reloading all files """ self.reload_flag = True self.__del__() def restart_callback(self, event): """ if the 'restart' button is pressed, start from t=0 """ self.current_clock = 0 self.enable_disable_buttons() self.update_display() def quit_callback(self, event): """ if the 'quit' button is pressed, quit the program """ self.__del__() def step_50_back_callback(self): """ take 50 steps back in time (if possible) """ if self.current_clock >= 50: self.current_clock -= 50 else: self.current_clock = 0 self.enable_disable_buttons() self.update_display() def step_10_back_callback(self, event): """ take 10 steps back in time (if possible) """ if self.current_clock >= 10: self.current_clock -= 10 else: self.current_clock = 0 self.enable_disable_buttons() self.update_display() def step_back_callback(self, event): """ take one step back in time (if possible) """ self.current_clock -= 1 if self.current_clock > 0 else 0 self.enable_disable_buttons() self.update_display() def step_callback(self, event): """ take one step forward in time (if possible) """ self.current_clock += 1 if self.current_clock < self.max_clock-1 else 0 self.enable_disable_buttons() self.update_display() def step_10_callback(self, event): """ take 10 steps forward in time (if possible) """ if self.current_clock < self.max_clock-10: self.current_clock += 10 else: self.current_clock = self.max_clock-1 self.enable_disable_buttons() self.update_display() def step_50_callback(self): """ take 50 steps forward in time (if possible) """ if self.current_clock < self.max_clock-50: self.current_clock += 50 else: self.current_clock = self.max_clock-1 self.enable_disable_buttons() self.update_display() def build_toolbar(self): """ build the toolbar """ # build the toolbar self.toolbar = Frame(self.root, borderwidth=2, relief='raised') self.toolbar.grid(row=0, columnspan=4, padx=0, pady=2, sticky="EW") # build the buttons in the toolbar self.reload_button = Button(self.toolbar, text="reLoad", command=lambda: self.reload_callback(None)) self.reload_button.pack(side="left", fill="none") self.restart_button = Button(self.toolbar, text="Restart", command=lambda: self.restart_callback(None)) self.restart_button.pack(side="left", fill="none") self.step_50_back_button = Button(self.toolbar, text="back 50", command=lambda: self.step_50_back_callback()) self.step_50_back_button.pack(side="left", fill="none") self.step_10_back_button = Button(self.toolbar, text="back 10\u2193", command=lambda: self.step_10_back_callback(None)) self.step_10_back_button.pack(side="left", fill="none") self.step_back_button = Button(self.toolbar, text="back\u2190", command=lambda: self.step_back_callback(None)) self.step_back_button.pack(side="left", fill="none") self.step_button = Button(self.toolbar, text="step\u2192", command=lambda: self.step_callback(None)) self.step_button.pack(side="left", fill="none") self.step_10_button = Button(self.toolbar, text="step 10\u2191", command=lambda: self.step_10_callback(None)) self.step_10_button.pack(side="left", fill="none") self.step_50_button = Button(self.toolbar, text="step 50", command=lambda: self.step_50_callback()) self.step_50_button.pack(side="left", fill="none") self.quit_button = Button(self.toolbar, text="Quit", command=lambda: self.quit_callback(None)) self.quit_button.pack(side="right", fill="none") # prepare key binding and unbinding of back button and step button (in 'enable_disable_buttons()') self.root.bind('', self.step_10_back_callback) self.root.bind('', self.step_back_callback) self.root.bind('', self.step_callback) self.root.bind('', self.step_10_callback) # bind key 'q' and 'Q' to quit self.root.bind('q', self.quit_callback) self.root.bind('Q', self.quit_callback) # bind key 'r' and 'R' to restart self.root.bind('r', self.restart_callback) self.root.bind('R', self.restart_callback) # bind key 'l' and 'L' to reload self.root.bind('l', self.reload_callback) self.root.bind('L', self.reload_callback) # enable or disable buttons depending on the current time step and max time step self.enable_disable_buttons() ================================================ FILE: state_machine_emulator/interface/_tooltips.py ================================================ # thanks to crxguy52 and Stevoisiak: # https://stackoverflow.com/questions/3221956/how-do-i-display-tooltips-in-tkinter from tkinter import Toplevel, Label class CreateToolTip(object): """ create a tooltip for a given widget """ def __init__(self, widget, text='widget info'): self.waittime = 500 #milliseconds self.wraplength = 180 #pixels self.widget = widget self.text = text self.widget.bind("", self.enter) self.widget.bind("", self.leave) self.widget.bind("", self.leave) self.id = None self.tw = None def enter(self, event=None): self.schedule() def leave(self, event=None): self.unschedule() self.hidetip() def schedule(self): self.unschedule() self.id = self.widget.after(self.waittime, self.showtip) def unschedule(self): id = self.id self.id = None if id: self.widget.after_cancel(id) def showtip(self, event=None): x = y = 0 x, y, cx, cy = self.widget.bbox("insert") x += self.widget.winfo_rootx() + 150 y += self.widget.winfo_rooty() + 20 # creates a toplevel window self.tw = Toplevel(self.widget) # Leaves only the label and removes the app window self.tw.wm_overrideredirect(True) self.tw.wm_geometry("+%d+%d" % (x, y)) label = Label(self.tw, text=self.text, justify='left', background="#ffffff", relief='solid', borderwidth=1, wraplength = self.wraplength) label.pack(ipadx=1) def hidetip(self): tw = self.tw self.tw= None if tw: tw.destroy() ================================================ FILE: state_machine_emulator/main.py ================================================ """ TODO: - check the 'status' with 'set_N' and 'status_sel' with the actual hardware! Wait! not just the status, but many, many things should be tested with the pico hardware - "FIFO IRQs" Figure 38. in rp2040-datasheet.pdf - start without c-program or pin-program - try except around emulation - make the way statements in c_program are made exactly the same as how they appear in the GUI - use the pio assembler to read raw programs and compile them (i.e. read prog.pio instead of prog.pio.h) """ from sys import argv, exc_info from os import getcwd, path, chdir from glob import glob from interface import Emulator_Interface from emulation import emulation from config import EMULATION_STEPS, SAVE_TEST_DATA from state_machine import state_machine """ This code emulates a state machine of a RP4020 It provides a GUI to step through the emulation results. """ def process_file_pio_h(filename, c_program): """ read and parse a pioasm generated header file """ pio_program = list() pio_program_length = None pio_program_origin = -1 # note: not actually used pio_program_wrap_target = 0 pio_program_wrap = None try: with open(filename, 'r') as pio_file: line = pio_file.readline() while line: if line[0] == '/' and line[1] == '/': pass elif ".length" in line: d = line.strip().split('=') pio_program_length = int(d[1].replace(',', '')) elif ".origin" in line: # note: origin is not actually used d = line.strip().split('=') pio_program_origin = int(d[1].replace(',', '')) elif "#define" in line: if "wrap_target" in line: d = line.strip().split(' ') pio_program_wrap_target = int(d[2]) elif "wrap" in line: d = line.strip().split(' ') pio_program_wrap = int(d[2]) elif "static const uint16_t" in line: line = pio_file.readline() while '};' not in line: d = line.strip().split(', //') if len(d) == 2: pio_program.append(d) line = pio_file.readline() elif "static inline pio_sm_config" in line: # add to the c-program at t=0 line2 = pio_file.readline() while '}' not in line2: if 'sm_config_set_sideset' in line2: parts = line2.split(',') a1 = int(parts[1].strip()) c_program.append([0, 'sideset_count', a1]) a2 = parts[2].strip().lower() == 'true' c_program.append([0, 'sideset_opt', a2]) a3 = parts[3].split(')')[0].strip().lower() == 'true' c_program.append([0, 'sideset_pindirs', a3]) line2 = pio_file.readline() line = pio_file.readline() if len(pio_program) != pio_program_length: print("Warning: length specification in pio file doesn't match actual length, continuing anyway") if len(pio_program) > 32: print("Warning: program too long, continuing anyway") except IOError as e: print("I/O Error reading pio program file:", e.errno, e.strerror) except: print("Error reading pio program file:", exc_info()[0]) return pio_program, pio_program_length, pio_program_origin, pio_program_wrap_target, pio_program_wrap def process_file_pin_program(filename, pin_program): """ read the pin program file and parse it """ try: allowed_parts1 = ['GPIO'+str(i) for i in range(32)] allowed_parts1.append('all') with open(filename, 'r') as pin_program_file: for line in pin_program_file: # remove all # characters and all text after it (i.e. comments) line = line.split("#", 1)[0] # check if the line still has some content if line.strip(): parts = line.split(',') parts[1] = parts[1].strip() if parts[1] in allowed_parts1: parts[0] = int(parts[0]) # time at which the command should be run parts[2] = int(parts[2]) # argument of the command pin_program.append(parts) else: print("Warning: Unknown command in pin_program: ", parts, 'continuing anyway') except IOError as e: print("I/O Error reading pin program file:", e.errno, e.strerror) except: print("Error reading pin program file:", exc_info()[0]) def process_file_c_program(filename, c_program): """ read the c program file and parse it """ try: with open(filename, 'r') as c_program_file: for line in c_program_file: # remove all # characters and all text after it (i.e. comments) line = line.split("#", 1)[0] # check if the line still has some content if line.strip(): # a "c-instruction" has two or three parts: # timestamp, command, and possibly an argument of the command # the timestamp and argument must be integers/bool, the command is a (stripped) string parts = line.strip().split(',') parts[1] = parts[1].strip() # check if the command is valid if parts[1].strip() in ['put', 'get', 'set_base', 'set_count', 'in_base', 'jmp_pin', 'sideset_base', 'out_base', 'out_count', 'out_shift_right', 'out_shift_autopull', 'pull_threshold', 'in_shift_right', 'in_shift_autopush', 'push_threshold', 'get_pc', 'set_pc', 'irq', 'set_N', 'status_sel', 'dir_out', 'dir_in', 'dir_non']: parts[0] = int(parts[0]) parts[1] = parts[1] if len(parts) == 3: parts[2] = parts[2].strip() # convert strings "True" and "False" to boolean, otherwise it is an int if parts[2] in ["True", "true", "Yes", "yes"]: parts[2] = True elif parts[2] in ["False", "false", "No", "no"]: parts[2] = False else: parts[2] = int(parts[2]) c_program.append(parts) else: print("Warning: Unknown command in c_program: ", parts, "continuing anyway") except IOError as e: print("I/O Error reading c program file:", e.errno, e.strerror) except: print("Error reading c program file:", exc_info()[0]) def print_usage(): """ print the usage of this program """ print("Usage:") print("python3", argv[0], "file.pio.h pin_program c_program") print("or:") print("python3", argv[0], "directory_of_files") exit() if __name__ == "__main__": # process arguments if len(argv) == 4: # read the three arguments: .pio.h pin-program c-program pio_h_filename = argv[1] pin_program_filename = argv[2] c_program_filename = argv[3] elif len(argv) == 2: # read one argument: the directory with the files .pio.h pin-program c-program dir_of_files = argv[1] # if the first character isn't a '/', add the full path if dir_of_files[0] != '/': dirname=getcwd() dir_of_files = path.join(dirname, dir_of_files) # if the last character isn't a '/', add it if dir_of_files[-1] != '/': dir_of_files += '/' chdir(dir_of_files) pio_file = glob("*.pio.h") pin_file = glob("pin_program") c_file = glob("c_program") if (len(pio_file)==1 and len(pin_file)==1 and len(c_file)==1): pio_h_filename = dir_of_files + pio_file[0] pin_program_filename = dir_of_files + pin_file[0] c_program_filename = dir_of_files + c_file[0] else: print_usage() else: print_usage() # flag to indicate that files need to be (re)loaded. This is used when the user pushes the reload button in the GUI load_files = True while load_files: load_files = False # the pio program (with associated data) is a dict program_definitions = dict() # the c_program and pin_program are lists c_program = list() pin_program = list() # process the pio.h file (which may already contribute to the c_program) pio_program, pio_program_length, pio_program_origin, pio_program_wrap_target, pio_program_wrap = process_file_pio_h(pio_h_filename, c_program) program_definitions['pio_program'] = pio_program program_definitions['pio_program_length'] = pio_program_length program_definitions['pio_program_origin'] = pio_program_origin # note: not used program_definitions['pio_program_wrap_target'] = pio_program_wrap_target program_definitions['pio_program_wrap'] = pio_program_wrap # process the c_program process_file_c_program(c_program_filename, c_program) # process the pin_program process_file_pin_program(pin_program_filename, pin_program) # make the RP2040 emulation (and it will make the PIO and sm) my_state_machine = state_machine(program_definitions) my_emulation = emulation(my_state_machine, pin_program, c_program) # do the emulation my_emulation.emulate(EMULATION_STEPS) if SAVE_TEST_DATA: print("program_definitions:") print(program_definitions) print("pin_program:") print(pin_program) print("c_program:") print(c_program) print("my_emulation.output:") print(my_emulation.output) print("my_emulation.emulation_output_c_program:") print(my_emulation.emulation_output_c_program) else: # show the interface my_Emulator_Interface = Emulator_Interface(program_definitions, pin_program, c_program, my_emulation.output, my_emulation.emulation_output_c_program) # check if a reload was requested load_files = my_Emulator_Interface.get_reload_flag() ================================================ FILE: state_machine_emulator/state_machine/__init__.py ================================================ from config import STATEMACHINE_NUMBER class state_machine: """ this class emulates a state machine (sm) """ from ._time_step import time_step from ._push_pull import push_to_RxFIFO, pull_from_TxFIFO from ._do_sideset import do_sideset from ._set_all_GPIO import set_all_GPIO from ._execute_instructions import execute_instruction, execute_jmp, execute_wait, execute_in, execute_out, execute_push, execute_pull, execute_mov, execute_irq, execute_set def __init__(self, program_definitions): """ make the variables (such as x and y registers) and settings and make some class variables needed for correct execution """ # Note: the Pico has two PIOs with 4 state machines each, but this emulator only has one state machine! # You can still set the state_machine_number (in config.py) because it is used in some irq operations # the sm number (used in irq calculations) self.sm_number = STATEMACHINE_NUMBER # the pio program and important parameters self.program = program_definitions['pio_program'] self.wrap_target = program_definitions['pio_program_wrap_target'] self.wrap = program_definitions['pio_program_wrap'] # RP2040 clock self.clock = 0 # define the irq self.sm_irq = [0 for _ in range(8)] # data related to the GPIO (the GPIO themselves, the pindir, external driven GPIO, out, set and sideset) self.GPIO_data = {} # the Pico has 32 GPIO self.GPIO_data["GPIO"] = [-1 for _ in range(32)] # pindir = 1 means Input (default); the '1' looks like an 'I' from 'Input' # pindir = 0 means output; the '0' looks like an 'O' from 'Output' self.GPIO_data["GPIO_pindirs"] = [1 for _ in range(32)] # externally driven pins self.GPIO_data["GPIO_external"] = [-1 for _ in range(32)] # pins driven by 'out' self.GPIO_data["GPIO_out"] = [-1 for _ in range(32)] # pins driven by 'set' self.GPIO_data["GPIO_set"] = [-1 for _ in range(32)] # pins driven by 'sideset' self.GPIO_data["GPIO_sideset"] = [-1 for _ in range(32)] # the variables used by the sm self.vars = {} self.vars["RxFIFO"] = [0 for _ in range(4)] self.vars["RxFIFO_count"] = 0 self.vars["TxFIFO"] = [0 for _ in range(4)] self.vars["TxFIFO_count"] = 0 self.vars["x"] = 0 self.vars["y"] = 0 self.vars["ISR"] = 0 self.vars["ISR_shift_counter"] = 0 self.vars["OSR"] = 0 self.vars["OSR_shift_counter"] = 32 self.vars["pc"] = -1 # before executing any instruction a '+= 1' is done in time_step() self.vars["delay"] = 0 self.vars["status"] = -1 # settings that control how sm functions are executed self.settings = {} self.settings["in_shift_right"] = True # right self.settings["in_shift_autopush"] = False self.settings["push_threshold"] = 32 # false self.settings["out_shift_right"] = False # left (True = right) self.settings["out_shift_autopull"] = False self.settings["pull_threshold"] = 32 # false self.settings["in_base"] = -1 # indicate it is not set self.settings["jmp_pin"] = -1 # indicate it is not set self.settings["set_base"] = -1 # indicate it is not set self.settings["set_count"] = 0 # no pins assigned self.settings["out_base"] = -1 # indicate it is not set self.settings["out_count"] = 0 # no pins assigned self.settings["sideset_base"] = -1 # indicate it is not set self.settings["sideset_count"] = 0 # no pins assigned self.settings["sideset_opt"] = False self.settings["sideset_pindirs"] = False self.settings["FIFO_level_N"] = 0 self.settings["status_sel"] = 0 # 0 => status ~0x0 if TxFIFO_count < FIFO_LEVEL_N # 1 => status ~0x0 if RxFIFO_count < FIFO_LEVEL_N # for e.g. jmp the pc should not be updated after an execution step since it is set by the jmp # also for e.g. wait statements the pc should remain unchanged self.skip_increase_pc = False # clear flag # for some statements the delay should be postponed to after the instruction (e.g. wait) has finished self.delay_delay = False # the jmp statement changes the vars["pc"] immediately when issued. But I want to keep the 'old' pc until it is time to change the pc in 'time_step()' self.jmp_to = -1 # not set # flags that indicate whether the sm is waiting because either pull or push is stalling (TxFIFO empty / RxFIFO full) self.pull_is_stalling = False self.push_is_stalling = False # indicates if the irq instruction is already waiting for clearing of the irq self.irq_is_waiting = False ================================================ FILE: state_machine_emulator/state_machine/_do_sideset.py ================================================ def do_sideset(self, instruction_delay_sideset): """ handle sideset """ # TODO: is 3.5.1. fully covered? Especially the 4 points at the end of the section. # determine and execute the (optional) sideset # if sideset is optional the MSB is set when sideset should be executed: if self.settings["sideset_opt"]: # check if the MSB is set if instruction_delay_sideset & 0x10: # do the side step for sideset_count bits after the MSB # note: sideset_count includes the optional bit! for i in range(self.settings["sideset_count"]-1): test_ss_bit = 4-i-1 value = 1 if instruction_delay_sideset & (1 << test_ss_bit) else 0 # do the side step for sideset_base + i if self.settings["sideset_pindirs"]: self.GPIO_data["GPIO_pindirs"][(self.settings["sideset_base"] + i) % 32] = value else: self.GPIO_data["GPIO_sideset"][(self.settings["sideset_base"] + i) % 32] = value else: # sideset is mandatory for i in range(self.settings["sideset_count"]): test_ss_bit = 5-i-1 # if the bit is set, the GPIO (or pindir) has to be set; if not: clear the GPIO/pindir value = 1 if instruction_delay_sideset & (1 << test_ss_bit) else 0 # do the side step for sideset_base + i if self.settings["sideset_pindirs"]: self.GPIO_data["GPIO_pindirs"][(self.settings["sideset_base"] + i) % 32] = value else: self.GPIO_data["GPIO_sideset"][(self.settings["sideset_base"] + i) % 32] = value self.set_all_GPIO() # TODO: is this correct? the datasheet says that the sideset is done before the instruction. ================================================ FILE: state_machine_emulator/state_machine/_execute_instructions.py ================================================ def execute_instruction(self, instruction): """ Execute the PIO instruction """ # get the three bits that encode the instruction type instruction_type = (instruction & 0xE000) >> 13 # get the five delay/side-set bits instruction_delay_sideset = (instruction & 0x1F00) >> 8 # determine the (optional) delay # the bits for delay is 5 minus the number of sideset pins # NOTE: the number of sideset pins specified in the pio.h file includes the opt-bit bits_for_delay = 5 - self.settings["sideset_count"] # delay is the bits_for_delay LSB of instruction_del_ss self.vars["delay"] = instruction_delay_sideset & ((1 << bits_for_delay) - 1) self.do_sideset(instruction_delay_sideset) is_pull = 1 if (instruction & (1 << 7)) > 0 else 0 # determine which function to execute based on the instruction_type if instruction_type == 0: # its a 'jmp' self.execute_jmp(instruction) elif instruction_type == 1: # its a 'wait' self.execute_wait(instruction) elif instruction_type == 2: # its a 'in' self.execute_in(instruction) elif instruction_type == 3: # its a 'out' self.execute_out(instruction) elif instruction_type == 4 and is_pull: # its a 'pull' self.execute_pull(instruction) elif instruction_type == 4 and not is_pull: # its a 'push' self.execute_push(instruction) elif instruction_type == 5: # its a 'mov' self.execute_mov(instruction) elif instruction_type == 6: # its a 'irq' self.execute_irq(instruction) elif instruction_type == 7: # its a 'set' self.execute_set(instruction) else: # its an error! self.sm_warning_messages.append("Warning: unknown instruction, continuing\n") # when not pull or mov, do autopull (if enabled) (datasheet 3.5.4): if self.settings["out_shift_autopull"] and not ((instruction_type == 4 and is_pull) or (instruction_type == 5)): if self.vars["OSR_shift_counter"] >= self.settings["pull_threshold"]: if self.vars["TxFIFO_count"] > 0: self.pull_from_TxFIFO() self.pull_is_stalling = False def execute_jmp(self, instruction): """ execute a jmp instruction """ # get instruction parameters jmp_condition = (instruction & 0x00E0) >> 5 addr = instruction & 0x001F do_jump = False if jmp_condition == 0: # always do_jump = True elif jmp_condition == 1: # !x if self.vars["x"] == 0: do_jump = True elif jmp_condition == 2: # x-- if self.vars["x"] != 0: do_jump = True # x-- self.vars["x"] = (self.vars["x"] - 1) & 0xffffffff elif jmp_condition == 3: # !y if self.vars["y"] == 0: do_jump = True elif jmp_condition == 4: # y-- if self.vars["y"] != 0: do_jump = True # y-- self.vars["y"] = (self.vars["y"] - 1) & 0xffffffff elif jmp_condition == 5: # x!=y if self.vars["y"] != self.vars["x"]: do_jump = True elif jmp_condition == 6: # pin if self.settings["jmp_pin"] == -1: self.sm_warning_messages.append("Warning: 'jmp_pin' isn't set before use in JMP instruction, continuing\n") if self.GPIO_data["GPIO"][self.settings["jmp_pin"]] == 1: do_jump = True elif jmp_condition == 7: # !OSRE if self.vars["OSR_shift_counter"] < 32: do_jump = True if do_jump: # save the address to jump to for when the pc is set for the new instruction self.jmp_to = addr # the address is given by self.jmp_to, so, no increase of the pc self.skip_increase_pc = True def execute_wait(self, instruction): """ execute a wait instruction """ # get instruction parameters polarity = 1 if (instruction & (1 << 7)) > 0 else 0 source = (instruction & 0x0060) >> 5 index = instruction & 0x1F is_not_met = False if source == 0: # GPIO if self.GPIO_data["GPIO"][index] != polarity: is_not_met = True elif source == 1: # pin if self.settings["in_base"] == -1: self.sm_warning_messages.append("Warning: 'in_base' isn't set before use in WAIT instruction, continuing\n") if self.GPIO_data["GPIO"][(self.settings["in_base"]+index) % 32] != polarity: is_not_met = True elif source == 2: # IRQ MSB = 1 if (instruction & (1 << 4)) > 0 else 0 if MSB: irq = (instruction & 0x07 + self.sm_number) % 4 else: irq = instruction & 0x07 if self.sm_irq[irq] != polarity: is_not_met = True else: self.sm_warning_messages.append("Warning: WAIT has unknown source, continuing\n") if is_not_met: # condition has not been met, keep waiting self.skip_increase_pc = True # wait with the delay until condition has been met self.delay_delay = True def execute_in(self, instruction): """ execute an in instruction """ # rp2040-datasheet.pdf 3.2.4 Stalling: An IN instruction when autopush is enabled, ISR reaches its shift threshold, and the RxFIFO is full if self.push_is_stalling: self.sm_warning_messages.append("Warning: push is stalling in IN\n") return # get instruction parameters source = (instruction & 0x00E0) >> 5 bit_count = instruction & 0x001F if bit_count == 0: bit_count = 32 value = 0 mask = (1 << bit_count) - 1 # get data from the source if source == 0: # PINS if self.settings["in_base"] == -1: self.sm_warning_messages.append("Warning: 'in_base' isn't set before use in IN instruction, continuing\n") for pin in range(bit_count): value |= (self.GPIO_data["GPIO"][(self.settings["in_base"] + pin) % 32] << pin) elif source == 1: # X value = self.vars["x"] & mask elif source == 2: # Y value = self.vars["y"] & mask elif source == 3: # NULL value = 0 elif source == 4: # reserved self.sm_warning_messages.append("Warning: IN has unknown source, continuing\n") return elif source == 5: # reserved self.sm_warning_messages.append("Warning: IN has unknown source, continuing\n") return elif source == 6: # ISR value = self.vars["ISR"] & mask elif source == 7: # OSR value = self.vars["OSR"] & mask else: # Error self.sm_warning_messages.append("Warning: IN has unknown source, continuing\n") return # shift data into the ISR if self.settings["in_shift_right"]: # shift right self.vars["ISR"] >>= bit_count self.vars["ISR"] |= value << (32-bit_count) else: # shift left self.vars["ISR"] <<= bit_count self.vars["ISR"] |= value # make sure it the ISR stays 32 bit self.vars["ISR"] &= 0xFFFFFFFF # adjust the shift counter self.vars["ISR_shift_counter"] += bit_count if (self.vars["ISR_shift_counter"]) > 32: self.vars["ISR_shift_counter"] = 32 # (if enabled) autopush or stall if self.settings["in_shift_autopush"] and (self.vars["ISR_shift_counter"] >= self.settings["push_threshold"]): if self.vars["RxFIFO_count"] < 4: self.push_to_RxFIFO() self.push_is_stalling = False else: # block: do not go to next instruction self.skip_increase_pc = True self.delay_delay = True self.push_is_stalling = True def execute_out(self, instruction): """ execute an out instruction """ # do autopull (datasheet 3.5.4): if self.settings["out_shift_autopull"]: if self.vars["OSR_shift_counter"] >= self.settings["pull_threshold"]: if self.vars["TxFIFO_count"] > 0: self.pull_from_TxFIFO() # stall self.skip_increase_pc = True self.delay_delay = True self.pull_is_stalling = True self.sm_warning_messages.append("Warning: pull is stalling in OUT\n") return # get instruction parameters destination = (instruction & 0x00E0) >> 5 bit_count = instruction & 0x001F if bit_count == 0: bit_count = 32 # shift to the right if self.settings["out_shift_right"]: # take the bit_count LSB mask = (1 << bit_count)-1 value = self.vars["OSR"] & mask # shift the OSR bit_count to the right self.vars["OSR"] >>= bit_count else: # shift to the left # take the bit_count MSB by making a mask and shifting it left mask = (1 << bit_count)-1 # shift them (32-bit_count) to the left mask <<= (32-bit_count) # get the bits from the OSR value = self.vars["OSR"] & mask # shift value back value >>= (32-bit_count) # shift the OSR bit_count to the left self.vars["OSR"] <<= bit_count # make sure it the OSR stays 32 bit self.vars["OSR"] &= 0xFFFFFFFF # adjust the shift counter self.vars["OSR_shift_counter"] += bit_count # put the result in the destination if destination == 0: # PINS if self.settings["out_base"] == -1: self.sm_warning_messages.append("Warning: 'out_base' isn't set before use in OUT instruction, continuing\n") for pin in range(bit_count): self.GPIO_data["GPIO_out"][(self.settings["out_base"] + pin) % 32] = 1 if value & (1 << pin) else 0 elif destination == 1: # X self.vars["x"] = value elif destination == 2: # Y self.vars["y"] = value elif destination == 3: # NULL pass elif destination == 4: # PINDIRS if self.settings["out_base"] == -1: self.sm_warning_messages.append("Warning: 'out_base' isn't set before use in OUT instruction, continuing\n") for pin in range(bit_count): self.GPIO_data["GPIO_pindirs"][(self.settings["out_base"] + pin) % 32] = 1 if value & (1 << pin) else 0 elif destination == 5: # PC # save the address to jump to for when the pc is set for the new instruction self.jmp_to = value & 0x1F # the address is given, so, no increase of one for the pc self.skip_increase_pc = True elif destination == 6: # ISR self.vars["ISR"] = value self.vars["ISR_shift_counter"] += bit_count elif destination == 7: # EXEC # TODO? self.sm_warning_messages.append("Warning: OUT EXEC functionality isn't implemented, continuing\n") else: # Error self.sm_warning_messages.append("Warning: OUT has unknown destination, continuing\n") return def execute_push(self, instruction): """ execute a push instruction """ # get instruction parameters ifF = 1 if (instruction & (1 << 6)) > 0 else 0 Blk = 1 if (instruction & (1 << 5)) > 0 else 0 # check if there is space in the FIFO if self.vars["RxFIFO_count"] < 4: if ifF: # only push if counter >= the threshold if (self.vars["ISR_shift_counter"] >= self.settings["push_threshold"]): self.push_to_RxFIFO() else: # push independent of counter and threshold self.push_to_RxFIFO() else: # there's no space to push: block or continue? if Blk: # blocking: do not go to next instruction self.skip_increase_pc = True self.delay_delay = True self.push_is_stalling = True else: # continue, but clear ISR self.push_is_stalling = False self.vars["ISR"] = 0 def execute_pull(self, instruction): """ execute a pull instruction """ # get instruction parameters ifE = 1 if (instruction & (1 << 6)) > 0 else 0 Blk = 1 if (instruction & (1 << 5)) > 0 else 0 if self.vars["TxFIFO_count"] != 0: if ifE: # only pull if counter is larger than threshold if (self.vars["OSR_shift_counter"] >= self.settings["pull_threshold"]): self.pull_from_TxFIFO() else: # pull independent of counter and threshold self.pull_from_TxFIFO() else: # there's no data to pull: block or continue if Blk: # blocking: do not go to next instruction self.skip_increase_pc = True self.delay_delay = True self.pull_is_stalling = True else: # "A non-blocking PULL on an empty FIFO has # the same effect as MOV OSR, X" self.vars["OSR"] = self.vars["x"] self.pull_is_stalling = False self.sm_warning_messages.append("Note: a non-blocking PULL on an empty FIFO has the same effect as 'MOV OSR, X', continuing\n") def execute_mov(self, instruction): """ execute a mov instruction """ # get instruction parameters destination = (instruction & 0x00E0) >> 5 operation = (instruction & 0x0018) >> 3 source = (instruction & 0x0007) # the value to be moved value = -1 # get the source (i.e. set 'value') if source == 0: # PINS if self.settings["in_base"] == -1: self.sm_warning_messages.append("Warning: 'in_base' isn't set before use in MOV instruction, continuing\n") value = 0 for pin in range(32): if self.GPIO_data["GPIO"][(self.settings["in_base"] + pin) % 32] == -1: self.sm_warning_messages.append(str("Warning: a pin ("+str((self.settings["in_base"] + pin) % 32)+") with undefined state is read, 0 is used, continuing\n")) # not necessary to 'or' value with a 0 else: value |= (self.GPIO_data["GPIO"][(self.settings["in_base"] + pin) % 32] << pin) elif source == 1: # X value = self.vars["x"] elif source == 2: # Y value = self.vars["y"] elif source == 3: # NULL value = 0 elif source == 4: # reserved self.sm_warning_messages.append("Warning: MOV has unknown source, continuing\n") return elif source == 5: # status value = self.vars["status"] elif source == 6: # ISR value = self.vars["ISR"] elif source == 7: # OSR value = self.vars["OSR"] else: # Error self.sm_warning_messages.append("Warning: MOV has unknown source, continuing\n") # apply the operation on value if operation == 1: # invert (bitwise complement) value = 0xFFFFFFFF - value elif operation == 2: # bit-reversed reversed_value = 0 for i in range(32): reversed_value <<= 1 reversed_value |= value & 1 value >>= 1 value = reversed_value # place value in destination # get the source (i.e. set 'value') # PINS TODO: check if it is correct that only out_count bits are output (not 32) if destination == 0: if self.settings["out_base"] == -1: self.sm_warning_messages.append("Warning: 'out_base' isn't set before use in MOV instruction, continuing\n") if self.settings["out_count"] == -1: self.sm_warning_messages.append("Warning: 'out_count' isn't set before use in MOV instruction, continuing\n") for pin in range(self.settings["out_count"]): self.GPIO_data["GPIO_out"][(self.settings["out_base"] + pin) % 32] = 1 if value & (1 << pin) else 0 # self.set_GPIO('out', (self.settings["out_base"] + pin) % 32, value & (1 << pin)) elif destination == 1: # X self.vars["x"] = value elif destination == 2: # Y self.vars["y"] = value elif destination == 3: # reserved self.sm_warning_messages.append("Warning: MOV has unknown destination, continuing\n") return elif destination == 4: # EXEC # TODO? self.sm_warning_messages.append("Warning: MOV EXEC functionality isn't implemented, continuing\n") pass elif destination == 5: # PC # save the address to jump to for when the pc is set for the new instruction self.jmp_to = value & 0x1F # the address is given, so, no increase of one for the pc self.skip_increase_pc = True elif destination == 6: # ISR self.vars["ISR"] = value self.vars["ISR_shift_counter"] = 0 elif destination == 7: # OSR self.vars["OSR"] = value self.vars["OSR_shift_counter"] = 0 else: # Error self.sm_warning_messages.append("Warning: MOV has unknown destination, continuing\n") return def execute_irq(self, instruction): """ execute an irq instruction """ # get instruction parameters Clr = 1 if (instruction & (1 << 6)) > 0 else 0 Wait = 1 if (instruction & (1 << 5)) > 0 else 0 MSB = 1 if (instruction & (1 << 4)) > 0 else 0 # add sm number and do modulo 4 if MSB is set if MSB: irq = (instruction & 0x07 + self.sm_number) % 4 else: irq = instruction & 0x07 # the irq statement is already waiting for clearing if self.irq_is_waiting: # check if irq is set, if so, wait if self.sm_irq[irq] == 1: self.skip_increase_pc = True self.delay_delay = True else: self.irq_is_waiting = False return if Clr: # clear the irq self.sm_irq[irq] = 0 else: # set the irq self.sm_irq[irq] = 1 if Wait: self.irq_is_waiting = True self.skip_increase_pc = True self.delay_delay = True def execute_set(self, instruction): """ execute a set instruction """ # get instruction parameters destination = (instruction & 0x00E0) >> 5 data = instruction & 0x001F if destination == 0: # PINS if self.settings["set_base"] == -1: self.sm_warning_messages.append("Warning: 'set_base' isn't set before use in SET instruction, continuing\n") if self.settings["set_count"] == -1: self.sm_warning_messages.append("Warning: 'set_count' isn't set before use in SET instruction, continuing\n") else: for pin in range(self.settings["set_count"]): self.GPIO_data["GPIO_set"][(self.settings["set_base"] + pin) % 32] = 1 if data & (1 << pin) else 0 # self.set_GPIO('set', ((self.settings["set_base"] + pin) % 32), data & (1 << pin)) elif destination == 1: # X self.vars["x"] = data elif destination == 2: # Y self.vars["y"] = data elif destination == 4: # PINDIRS if self.settings["set_base"] == -1: self.sm_warning_messages.append("Warning: 'set_base' isn't set before use in SET instruction, continuing\n") if self.settings["set_count"] == -1: self.sm_warning_messages.append("Warning: 'set_count' isn't set before use in SET instruction, continuing\n") for pin in range(self.settings["set_count"]): self.GPIO_data["GPIO_pindirs"][(self.settings["set_base"] + pin) % 32] = 1 if data & (1 << pin) else 0 else: self.sm_warning_messages.append("Warning: SET has unknown destination, continuing\n") ================================================ FILE: state_machine_emulator/state_machine/_push_pull.py ================================================ def push_to_RxFIFO(self): """ push data to the RxFIFO (it has already been checked that there is space!) """ # place the new item after the data already in the FIFO self.vars["RxFIFO"][self.vars["RxFIFO_count"]] = self.vars["ISR"] # there is now one more item self.vars["RxFIFO_count"] += 1 # clear the shift counter and the ISR itself self.vars["ISR_shift_counter"] = 0 self.vars["ISR"] = 0 def pull_from_TxFIFO(self): """ pull data from TxFIFO (it has already been checked that there is data) """ # pull the first item in the TxFIFO and place it in the OSR self.vars["OSR"] = self.vars["TxFIFO"][0] # shift the whole TxFIFO for t in range(self.vars["TxFIFO_count"]-1): self.vars["TxFIFO"][t] = self.vars["TxFIFO"][t+1] # set the now open space in the TxFIFO to 0 self.vars["TxFIFO"][self.vars["TxFIFO_count"]-1] = 0 # there is now one less data item in the TxFIFO self.vars["TxFIFO_count"] -= 1 # the number of bits shifted out of the OSR is 0 self.vars["OSR_shift_counter"] = 0 ================================================ FILE: state_machine_emulator/state_machine/_set_all_GPIO.py ================================================ def set_all_GPIO(self): """ sets the GPIO according to the changes in the time step; there is a specific priority order to out, set, sideset and external """ # first 'out' and 'set' values (lowest priority) for pin in range(self.settings["out_base"], self.settings["out_count"] + self.settings["out_base"]): if self.GPIO_data["GPIO_out"][pin] != -1: if self.GPIO_data["GPIO_pindirs"][pin] == 0: # pindir must be an output (0) self.GPIO_data["GPIO"][pin] = self.GPIO_data["GPIO_out"][pin] else: self.sm_warning_messages.append("Warning: GPIO "+str(pin)+" set by 'out' is not an output, continuing\n") for pin in range(self.settings["set_base"], self.settings["set_count"]+self.settings["set_base"]): if self.GPIO_data["GPIO_set"][pin] != -1: if self.GPIO_data["GPIO_pindirs"][pin] == 0: # pindir must be an output (0) self.GPIO_data["GPIO"][pin] = self.GPIO_data["GPIO_set"][pin] else: self.sm_warning_messages.append("Warning: GPIO "+str(pin)+" set by 'set' is not an output, continuing\n") # now sideset, medium priority # first check if the GPIO or the pindirs have to be set (here only GPIO) if not self.settings["sideset_pindirs"]: # dirty hack: sideset_count includes the sideset_opt bit, but this does not set a pin! count_sideset = self.settings["sideset_count"] if self.settings["sideset_opt"]: count_sideset -= 1 # now set the pins for pin in range(self.settings["sideset_base"], count_sideset + self.settings["sideset_base"]): if self.GPIO_data["GPIO_sideset"][pin] != -1: if self.GPIO_data["GPIO_pindirs"][pin] == 0: # pindir must be an output (0) self.GPIO_data["GPIO"][pin] = self.GPIO_data["GPIO_sideset"][pin] else: self.sm_warning_messages.append("Warning: GPIO "+str(pin)+" set by 'sideset' is not an output, continuing\n") # finally, externally driven pins, highest priority for pin in range(32): if self.GPIO_data["GPIO_external"][pin] != -1: # external input always wins self.GPIO_data["GPIO"][pin] = self.GPIO_data["GPIO_external"][pin] if self.GPIO_data["GPIO_pindirs"][pin] != 1: # the pin is configured as an output! Warning! self.sm_warning_messages.append("Warning: external input applied to GPIO "+str(pin)+" but it is configured as output (external wins!), continuing\n") ================================================ FILE: state_machine_emulator/state_machine/_time_step.py ================================================ def time_step(self): """ emulate one time step """ # prepare for warning messages self.sm_warning_messages = [] # flag to indicate we're dealing with a delayed delay skip_due_to_delay_delay = False # check if delay is active: for some instructions (wait, irq) delay needs to wait till after the instruction has finished if not self.delay_delay: # check if a delay is being executed: if so, do nothing if self.vars["delay"] > 0: self.vars["delay"] -= 1 skip_due_to_delay_delay = True # if delayed delay: skip normal pc increase and instruction execution if skip_due_to_delay_delay == False: self.delay_delay = False # check if (e.g. due to a jmp or wait) the pc does not need to be changed if self.skip_increase_pc: # yes: do not increase the pc self.skip_increase_pc = False # clear flag if self.jmp_to >= 0: # the skip is due to a jmp, set pc to the address to jmp to self.vars["pc"] = self.jmp_to self.jmp_to = -1 # not set else: # add one to the program counter self.vars["pc"] += 1 # check if the pc should wrap if self.vars["pc"] == self.wrap+1: self.vars["pc"] = self.wrap_target # get the new instruction and execute it instruction = int(self.program[self.vars["pc"]][0], 16) # execute the instruction self.execute_instruction(instruction) # set the 'status' depending on RxFIFO or TxFIFO count if self.settings['status_sel'] == 0: # check TxFIFO level if self.vars["TxFIFO_count"] < self.settings['FIFO_level_N']: self.vars["status"] = -1; # binary all ones else: self.vars["status"] = 0; # binary all zeroes else: # self.settings['status_sel'] == 1 # check RxFIFO level if self.vars["RxFIFO_count"] < self.settings['FIFO_level_N']: self.vars["status"] = -1; # binary all ones else: self.vars["status"] = 0; # binary all zeroes # set the GPIOs according to the changes in this time step # note: even when skip_due_to_delay_delay is True, there may still be # changes due to the pin_ or c_program self.set_all_GPIO() # increase the system level clock self.clock += 1 # return warnings return self.sm_warning_messages ================================================ FILE: subroutines/CMakeLists.txt ================================================ add_executable(subroutine) pico_generate_pio_header(subroutine ${CMAKE_CURRENT_LIST_DIR}/subroutine.pio) target_sources(subroutine PRIVATE subroutine.cpp) target_link_libraries(subroutine PRIVATE pico_stdlib hardware_pio ) pico_add_extra_outputs(subroutine) ================================================ FILE: subroutines/README.md ================================================ # Subroutines in PIO code ## What? Subroutines? There's no such thing in pioasm That is true, but you can make something that works like subroutines. What is needed is a way to specify a return address, and for the subroutine to actually jump to that return address. Specifying a return address can be achieved by using the labels in the pio code. For this to work it must be made sure that the pio program starts at address 0 in the pio memory. This is achieved by starting with `.origin 0`. The return address can be stored in any of the registers, in the example below x is used, in the [example code](https://github.com/GitJer/Some_RPI-Pico_stuff/blob/main/subroutines/subroutine.pio) the ISR is used, but OSR and y are just as good. You only need to remember to not use the register with the return address in the subroutine. At the end of the subroutine the return address is used to jump back to the main code. This can be done in several ways (assuming the OSR is used for the return address): - In the example below the property is used that a jmp to an absolute address is all 0's plus the return address in the last 5 bits. Then `mov exec OSR` can be used to execute that jump. - In the same way `out exec 32` can be used. - It is also possible to directly jump to the return address by placing the return address in the program counter using `mov PC OSR`. - In the same way `out PC 5` can be used. In the example below two subroutines are used: sub_set_one and sub_set_zero. The return address is in x and jumping to that address is done by `mov PC x`. ``` .program subroutine ; START THE PROGRAM AT ADDRESS 0 .origin 0 start: ; set the return address in x, then in OSR set x ret1 ; jump to subroutine to set the pin to 1 jmp sub_set_one ret1: ; set the return address in x, then OSR set x start ; jump to subroutine to set the pin to 0 jmp sub_set_zero sub_set_one: ; set pin to 1 set PINS 1 ; return to the address set in OSR mov PC x sub_set_zero: ; set pin to 0 set PINS 0 ; return to the address set in OSR mov PC x ``` ## Is it useful? In the PIO memory there is only room for 32 instructions, so using subroutines which require some overhead in the number of instructions (setting up the return address, jumping to the subroutine, jumping to the return address) quickly costs more than just writing what you want as one piece of code. In the example above it would clearly be better to just make a loop that toggles a pin. In the [example code](https://github.com/GitJer/Some_RPI-Pico_stuff/blob/main/subroutines/subroutine.pio) a somewhat more elaborate subroutine is used: a (nonsensical) protocol that sends 5 bits and then pauses for a specified number of instructions before returning to the main program. It produces: In that code, the subroutine consists of 7 instructions and it is called 5 times. Setting up and jumping to it costs 5 instructions. In total all 32 instruction memory locations are used. If one would program the functional parts (sending 5 bits and pausing) directly it would cost about 41 instructions, which would not fit in memory. So, this is a proof of principle that using subroutines can - in some cases - be used to do more with the limited memory space than is possible with just writing the code in one program. ================================================ FILE: subroutines/subroutine.cpp ================================================ /* This is the C SDK code needed to start the accompanying pio code. It only defines a pin (for out and set) as output, and starts the sm */ #include #include "pico/stdlib.h" #include "hardware/pio.h" #include "subroutine.pio.h" int main() { // the pio instance PIO pio = pio0; // the state machine uint sm = 0; // needed for printf stdio_init_all(); int tx_pin = 16; // let pio control tx_pin pio_gpio_init(pio, tx_pin); // load the pio program into the pio memory uint offset = pio_add_program(pio, &subroutine_program); // make a sm config pio_sm_config c = subroutine_program_get_default_config(offset); // set the 'out' pin sm_config_set_out_pins(&c, tx_pin, 1); // set the 'set' pin sm_config_set_set_pins(&c, tx_pin, 1); // set the pin to output pio_sm_set_consecutive_pindirs(pio, sm, tx_pin, 1, true); // OUT shifts to right, no autopull sm_config_set_out_shift(&c, true, false, 32); // just for testing purposes (I have a crappy logic analyzer): slow down the clock sm_config_set_clkdiv(&c, 10); // init the pio sm with the config pio_sm_init(pio, sm, offset, &c); // enable the sm pio_sm_set_enabled(pio, sm, true); // do nothing while (1) ; } ================================================ FILE: subroutines/subroutine.pio ================================================ ; This is a proof of principle of using subroutines in PIO code. .program subroutine ; START THE PROGRAM AT ADDRESS 0 .origin 0 start: ; set the return address in x, then in ISR set x ret1 mov ISR x ; set data to send in x set x 0b11111 ; set pause time after sending bits in y set y 6 ; jump to subroutine send_data jmp send_data ret1: ; set the return address in x, then in ISR set x ret2 mov ISR x ; set data to send in x set x 0b11011 ; set pause time after sending bits in y set y 12 ; jump to subroutine send_data jmp send_data ret2: ; set the return address in x, then in ISR set x ret3 mov ISR x ; set data to send in x set x 0b10101 ; set pause time after sending bits in y set y 18 ; jump to subroutine send_data jmp send_data ret3: ; set the return address in x, then in ISR set x ret4 mov ISR x ; set data to send in x set x 0b01110 ; set pause time after sending bits in y set y 24 ; jump to subroutine send_data jmp send_data ret4: ; set the return address in x, then in ISR set x start mov ISR x ; set data to send in x set x 0b10001 ; set pause time after sending bits in y set y 30 ; jump to subroutine send_data jmp send_data ; The subroutine to send bits and pause for some time ; ; The return address used is in ISR ; The data to be send is in x ; The pause time is in y send_data: ; data to send is put in the OSR for shifting mov OSR x ; send 5 bits, x is a counter set x, 4 bitloop: ; shift out 1 bit from OSR to pin out pins 1 ; afterwards make the pin 0 set pins 0 ; do all 5 bits jmp x-- bitloop ; pause y+1 steps after sending the bits pause: jmp y-- pause ; return to the address set in ISR mov exec ISR ================================================ FILE: two_pio_programs_one_file/CMakeLists.txt ================================================ add_executable(two_p_one_f) pico_generate_pio_header(two_p_one_f ${CMAKE_CURRENT_LIST_DIR}/two_p_one_f.pio) target_sources(two_p_one_f PRIVATE two_p_one_f.cpp) target_link_libraries(two_p_one_f PRIVATE pico_stdlib hardware_pio ) pico_add_extra_outputs(two_p_one_f) # add url via pico_set_program_url example_auto_set_url(two_p_one_f) ================================================ FILE: two_pio_programs_one_file/README.md ================================================ This example contains two pio programs. It was actually part of getting the 1-wire protocol working, and that requires an external pull up resistor (e.g. 10k) on the pin used by both set and side-set. But for now, this is mostly an example of how two pio programs in one .pio file can be used. The first program produces a short 0 (two cycles) and a long 1 (nine cycles) on the output the second program produces a short 1 (two cycles) and a long 0 (nine cycles) on the output Note that in the generated file two_p_one_f.pio.h file the two programs are considered completely separate and independent of where in memory they will end up! Even the 'jmp again1' and 'jmp again2' are both encoded as a 'jmp 0'. This means that when loading a program into pio memory, some trans-coding is necessary to make the jmp statements point to the right memory address. ================================================ FILE: two_pio_programs_one_file/two_p_one_f.cpp ================================================ #include #include "pico/stdlib.h" #include "hardware/pio.h" #include "two_p_one_f.pio.h" #define TEST_PIN 16 /* This program shows how you can use two pio programs in one file (two_p_one_f) For this test it is assumed that the TEST_PIN is externally pulled high via e.g. a 10k resistor */ int main() { // needed for printf stdio_init_all(); // the pio instance is 0 PIO pio; pio = pio0; // the state machine instance is 0 uint sm; sm = 0; // configure the used pin pio_gpio_init(pio, TEST_PIN); // load the pio programs into the pio memory uint offset_1 = pio_add_program(pio, &two_p_one_f_1_program); uint offset_2 = pio_add_program(pio, &two_p_one_f_2_program); // make the sm config for both programs pio_sm_config c = pio_get_default_sm_config(); // set the pin used by set sm_config_set_set_pins(&c, TEST_PIN, 1); // sideset (bitcount=2: one bit + one bit because sideset is optional, optional=true, pindirs=true) sm_config_set_sideset(&c, 2, true, true); // set the pin used by sideset (same pin as for SET, two lines above) sm_config_set_sideset_pins(&c, TEST_PIN); // one clock cycle is 10 us sm_config_set_clkdiv(&c, 1250); // init the pio sm with the config, start with two_p_one_f_1 pio_sm_init(pio, sm, offset_1, &c); // enable the sm pio_sm_set_enabled(pio, sm, true); while (true) { // switch to two_p_one_f_1 pio_sm_exec(pio, sm, 0x0000 + offset_1); sleep_ms(1000); // switch to two_p_one_f_2 pio_sm_exec(pio, sm, 0x0000 + offset_2); sleep_ms(1000); } } ================================================ FILE: two_pio_programs_one_file/two_p_one_f.pio ================================================ ; ; This file contains two pio programs. ; It is assumed that an external pull up resistor is present on the pin used by both set and side-set ; ; The first program produces a short 0 (two cycles) and a long 1 (nine cycles) on the output ; the second program produces a short 1 (two cycles) and a long 0 (nine cycles) on the output ; ; Note that in the generated file two_p_one_f.pio.h file the two programs are ; considered completely separate and independent of where in memory they will end up! ; Even the 'jmp again1' and 'jmp again2' are both encoded as a 'jmp 0'. This means ; that when loading a program into pio memory, some trans-coding is necessary to make ; the jmp statements point to the right memory address. ; first program .program two_p_one_f_1 ; side-set controls the direction .side_set 1 opt pindirs again1: ; set direction to output (side 1), and set output value to 0 (set pins 0) set pins 0 side 1 [1] ; set direction to input, if external pull-up is used, pin will float to 1 nop side 0 ; keep input for some time nop [6] ; start over jmp again1 ; second program .program two_p_one_f_2 ; side-set controls the direction .side_set 1 opt pindirs again2: ; set to output, and set the output to 0 set pins 0 side 1 nop [7] ; set to input, if external pullup is used, expect line to float up nop side 0 jmp again2 ================================================ FILE: two_pio_programs_one_file/two_p_one_f.pio.h ================================================ // -------------------------------------------------- // // This file is autogenerated by pioasm; do not edit! // // -------------------------------------------------- // #pragma once #if !PICO_NO_HARDWARE #include "hardware/pio.h" #endif // ------------- // // two_p_one_f_1 // // ------------- // #define two_p_one_f_1_wrap_target 0 #define two_p_one_f_1_wrap 3 static const uint16_t two_p_one_f_1_program_instructions[] = { // .wrap_target 0xf900, // 0: set pins, 0 side 1 [1] 0xb042, // 1: nop side 0 0xa642, // 2: nop [6] 0x0000, // 3: jmp 0 // .wrap }; #if !PICO_NO_HARDWARE static const struct pio_program two_p_one_f_1_program = { .instructions = two_p_one_f_1_program_instructions, .length = 4, .origin = -1, }; static inline pio_sm_config two_p_one_f_1_program_get_default_config(uint offset) { pio_sm_config c = pio_get_default_sm_config(); sm_config_set_wrap(&c, offset + two_p_one_f_1_wrap_target, offset + two_p_one_f_1_wrap); sm_config_set_sideset(&c, 2, true, true); return c; } #endif // ------------- // // two_p_one_f_2 // // ------------- // #define two_p_one_f_2_wrap_target 0 #define two_p_one_f_2_wrap 3 static const uint16_t two_p_one_f_2_program_instructions[] = { // .wrap_target 0xf800, // 0: set pins, 0 side 1 0xa742, // 1: nop [7] 0xb042, // 2: nop side 0 0x0000, // 3: jmp 0 // .wrap }; #if !PICO_NO_HARDWARE static const struct pio_program two_p_one_f_2_program = { .instructions = two_p_one_f_2_program_instructions, .length = 4, .origin = -1, }; static inline pio_sm_config two_p_one_f_2_program_get_default_config(uint offset) { pio_sm_config c = pio_get_default_sm_config(); sm_config_set_wrap(&c, offset + two_p_one_f_2_wrap_target, offset + two_p_one_f_2_wrap); sm_config_set_sideset(&c, 2, true, true); return c; } #endif ================================================ FILE: ws2812_led_strip_120/CMakeLists.txt ================================================ add_executable(ws2812_led_strip_120) pico_generate_pio_header(ws2812_led_strip_120 ${CMAKE_CURRENT_LIST_DIR}/ws2812_led_strip_120.pio) target_sources(ws2812_led_strip_120 PRIVATE ws2812_led_strip_120.c) target_link_libraries(ws2812_led_strip_120 PRIVATE pico_stdlib hardware_pio ) pico_add_extra_outputs(ws2812_led_strip_120) # add url via pico_set_program_url example_auto_set_url(ws2812_led_strip_120) ================================================ FILE: ws2812_led_strip_120/README.md ================================================ # Ws2812 led strip with 120 pixels This is my take on how to control a ws2812 led strip with 120 pixels. It differs from the [pio example code](https://github.com/raspberrypi/pico-examples/tree/master/pio/ws2812) for the ws2812 in two ways. ## clkdiv I wanted to have the sm run at its normal speed (125 MHz). The timing restrictions of the ws2812 chip then requires to use delay cycles. It happens that the maximum (31) is just enough delay to make it work. In the pio example code, a side set is used to toggle the GPIO that drives the signal to the ws2812 pixels. This results in neat code, but if side set is used, however, less bits are left over to specify a delay. In this case not enough to make it work. ## reset period To make the ws2812 accept a new set of pixel data, a reset period has to be used. In my code I count how many pixel RGB values have been sent out and then start a delay loop. The led strip I use has 120 pixels, so, the pio code is specific to that amount of pixels. The delay loop is such that it waits long enough for the reset of the ws2812. ## Dithering I wanted to try dithering for low brightness values to make the transitions more smooth. In the code I have used a simple table with values to use for dithering. I have no idea how others do this, so maybe there are more clever ways. In the code first normal brightness changes are programmed, followed by dithered brightness changes. ================================================ FILE: ws2812_led_strip_120/ws2812_led_strip_120.c ================================================ #include #include #include #include "pico/stdlib.h" #include "hardware/pio.h" #include "hardware/clocks.h" #include "ws2812_led_strip_120.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 all_red() { for (uint i = 0; i < 120; ++i) put_pixel(urgb_u32(0x80, 0, 0)); } void all_green() { for (uint i = 0; i < 120; ++i) put_pixel(urgb_u32(0, 0x80, 0)); } void all_blue() { for (uint i = 0; i < 120; ++i) put_pixel(urgb_u32(0, 0, 0x80)); } void white(int level) { for (uint i = 0; i < 120; ++i) put_pixel(urgb_u32(level, level, level)); } const int PIN_TX = 2; int main() { // needed for printf stdio_init_all(); // the pio instance PIO pio; // the state machine uint sm; // pio 0 is used pio = pio0; // state machine 0 sm = 0; // configure the used pins pio_gpio_init(pio, PIN_TX); // load the pio program into the pio memory uint offset = pio_add_program(pio, &ws2812_led_strip_120_program); // make a sm config pio_sm_config c = ws2812_led_strip_120_program_get_default_config(offset); // set the 'set' pin sm_config_set_set_pins(&c, PIN_TX, 1); // set the pindirs to output pio_sm_set_consecutive_pindirs(pio, sm, PIN_TX, 1, true); // set out shift direction sm_config_set_out_shift(&c, false, true, 24); // set in shift direction sm_config_set_in_shift(&c, false, false, 0); // join the FIFOs sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); // init the pio sm with the config pio_sm_init(pio, sm, offset, &c); // enable the sm pio_sm_set_enabled(pio, sm, true); // int maxRows = 11; // int maxCols = 10; // bool transition[11][10] = { // {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // 10 // {0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, // 9 // {0, 0, 0, 0, 1, 0, 0, 0, 0, 1}, // 8 // {0, 0, 0, 1, 0, 0, 1, 0, 0, 1}, // 7 // {0, 0, 1, 0, 1, 0, 0, 1, 0, 1}, // 6 // {0, 1, 0, 1, 0, 1, 0, 1, 0, 1}, // 5 // {0, 1, 0, 1, 1, 0, 1, 0, 1, 1}, // 4 // {0, 1, 1, 0, 1, 1, 0, 1, 1, 1}, // 3 // {0, 1, 1, 1, 1, 0, 1, 1, 1, 1}, // 2 // {0, 1, 1, 1, 1, 1, 1, 1, 1, 1}, // 1 // {1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, // 0 // }; int maxRows = 13; int maxCols = 12; bool transition[13][12] = { {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // 12 {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // 11 {1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0}, // 10 {1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0}, // 9 {1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0}, // 8 {1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0}, // 7 {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0}, // 6 {0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1}, // 5 {0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1}, // 4 {0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1}, // 3 {0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1}, // 2 {0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, // 1 {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, // 0 }; int maxLevel = 20; while (1) { // without dithering for (int level = 0; level < maxLevel; level++) // next loop is just to make it equal to the dithered version below for (int row = 0; row < maxRows * maxCols; row++) white(level); for (int level = maxLevel; level > 0; level--) // next loop is just to make it equal to the dithered version below for (int row = maxRows * maxCols - 1; row >= 0; row--) white(level); // with dithering for (int level = 0; level < maxLevel; level++) for (int row = 0; row < maxRows; row++) for (int col = 0; col < maxCols; col++) white(level + transition[row][col]); for (int level = maxLevel; level > 0; level--) for (int row = maxRows - 1; row >= 0; row--) for (int col = 0; col < maxCols; col++) white(level + transition[row][col]); } } ================================================ FILE: ws2812_led_strip_120/ws2812_led_strip_120.pio ================================================ ; This code sends pixel color data for ws2812 leds for a led strip of 120 leds. ; Note: the data send from the c-code must contain 120 * RGB data. ; It assumes the out-shift is set to right, such that the MSB of the RGB data is send first ; It assumes the in-shift is set to left, such that the counters work correctly ; ; ; for the ws2812 a zero bit looks like this: ; ; |---| ; | | ; -------------- ; ; a one bit looks like: ; ; |-------| ; | | ; -------------- ; ; So, a zero consists of 1/3 high and then 2/3 low; a one consists of 2/3 high and then 1/3 low ; ; One pixel requires 24 bits to encode RGB with 8 bits each ; the led strip I use has 120 pixels ; after all pixels have been sent, wait a reset period to make the strip start again ; the code below works as follows: ; 1) make an outer loop that runs 2880 times (120 pixels * 24 bits) ; 2) get the bytes with RGB data from the c-program, ; shift them in one by one and send the correct ws2812 bit-encoding ; 3) after all 2880 bits have been send to the pixels, wait a reset time (393216 * 1/125000000) = 3ms .program ws2812_led_strip_120 .define T0 30 start: ; 120 times 24 (my led strip has 120 leds, 24 bits per pixel) = 2880 ; but note that the '0'th loop is also executed -> 2879 is needed ; fill y with 2880 (101101000000) and subtract one to get 2879 (101100111111) set y 22 ; set y to 10110 (the 5 MSB of 2880) mov ISR y ; copy them to the ISR set y 16 ; set y to 10000 (the next 5 MSB of 2880) in y 5 ; shift them in the ISR (note: left_in_shift is required) in NULL 2 ; shift 2 0 into the ISR, now it contains 2880 mov y ISR ; copy it into y jmp y-- loop ; decrease one to get 2879 loop: out x, 1 ; shift in one bit in x; note: this is blocking, ; it must be blocking while the pins is 0. set pins 1 [T0] ; pins is set to high; this is the first 1/3, always high jmp !x send_zero ; if a low must be sent, jump. jmp end_with_zero [T0+1] ; keep high for 31 c (now 2/3 of the time frame for one bit is high) send_zero: set pins 0 [T0+1] ; set low for 31 c (the middle 1/3 is low) end_with_zero: set pins 0 [T0] ; set low for 31 c (including the out x, 1 = 31c). The last 1/3 is always low jmp y-- loop ; if not all of the 2880 bits have been sent, send another ; delay 12288 clock cycles (about 100 us) set y 24 ; binary = 11000 mov ISR y in NULL 9 ; shift in 9 zero bits mov y ISR ; y now contains 11000000000000 = 12288 delay_loop: ; the actual delay loop jmp y-- delay_loop jmp start ; start again for a new set of pixel data