Created
January 5, 2024 14:00
-
-
Save sonnny/2f5a259889945e367403acbd45f8e132 to your computer and use it in GitHub Desktop.
pico pio demo of issuing irq and wait until program responds, also demo of passing pointer from main to other file to update
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| demo of pico decoding nes gamepad | |
| demo of pico issuing irq to main program | |
| main program responds and clears the irq | |
| source file from https://codeberg.org/chipfire/rppico-pio-gamepad | |
| /**************************** | |
| * gptest.c | |
| **************************/ | |
| #include <stdio.h> | |
| #include "pico/stdlib.h" | |
| #include "nes_gamepad.h" | |
| int main() { | |
| uint gpState; | |
| // first set the proper clock | |
| set_sys_clock_khz(141600, true); | |
| // initialize stdio so we can print something via USB | |
| stdio_init_all(); | |
| sleep_ms(2000); | |
| if(gamepadStart(pio0, 6, 8, 7, &gpState) != 0) { | |
| printf("Oops: failed to initialize gamepad!\n"); | |
| while(1) {} | |
| } | |
| while(1) { | |
| sleep_ms(200); | |
| // print current state | |
| if((gpState & 0xff) == 0xff) { | |
| printf("no buttons pressed\n"); | |
| } else { | |
| printf("buttons pressed: "); | |
| if(!(gpState & 0x80)) | |
| printf("A "); | |
| if(!(gpState & 0x40)) | |
| printf("B "); | |
| if(!(gpState & 0x20)) | |
| printf("select "); | |
| if(!(gpState & 0x10)) | |
| printf("start "); | |
| if(!(gpState & 0x8)) | |
| printf("Up "); | |
| if(!(gpState & 0x4)) | |
| printf("Down "); | |
| if(!(gpState & 0x2)) | |
| printf("Left "); | |
| if(!(gpState & 0x1)) | |
| printf("Right "); | |
| printf("\n"); | |
| } | |
| } | |
| return 0; | |
| } | |
| /******************************* | |
| * nes_gamepad.c | |
| ***************************/ | |
| #include <stdio.h> | |
| #include "hardware/gpio.h" | |
| #include "hardware/pio.h" | |
| #include "hardware/irq.h" | |
| #include "build/nes_gamepad.pio.h" | |
| uint pioProgOffset; | |
| int pioSm; | |
| PIO pioUsed; | |
| uint *gpState; | |
| void gamepadIrqHandler() { | |
| // the interrupt handler, not much exiting stuff going on here. | |
| // We just need to clear the interrupt and read the new data from the SM FIFO. | |
| // clear interrupt first | |
| pio_interrupt_clear(pioUsed, 0); | |
| // then read current status word from SM FIFO and write it to the configured destination. | |
| *gpState = pio_sm_get(pioUsed, pioSm); | |
| } | |
| int gamepadStart(PIO pio, uint dataPin, uint clkPin, uint psPin, uint *gpStateVar) { | |
| gpState = NULL; | |
| if(gpStateVar == NULL) | |
| return -1; | |
| // try to get a PIO SM, returns -1 if none is available. | |
| pioSm = pio_claim_unused_sm(pio, false); | |
| // if no SM was available, fail | |
| if(pioSm < 0) | |
| return -1; | |
| // check whether the PIO has sufficient program space available. If not, fail. | |
| if(!pio_can_add_program(pio, &NES_controller_interface_program)) | |
| return -1; | |
| // for debugging | |
| // printf("Pins assigned: %u %u %u SM used: %d\n", dataPin, clkPin, psPin, pioSm); | |
| pioUsed = pio; | |
| gpState = gpStateVar; | |
| pioProgOffset = pio_add_program(pio, &NES_controller_interface_program); | |
| nesControllerProgramInit(pio, (uint) pioSm, pioProgOffset, dataPin, clkPin, psPin); | |
| // set up interrupt handler and source. Making the IRQ configurable would be nice. | |
| irq_set_exclusive_handler(pio_get_index(pio) ? PIO1_IRQ_0 : PIO0_IRQ_0, gamepadIrqHandler); | |
| irq_set_enabled(pio_get_index(pio) ? PIO1_IRQ_0 : PIO0_IRQ_0, true); | |
| irq_set_priority(pio_get_index(pio) ? PIO1_IRQ_0 : PIO0_IRQ_0, PICO_LOWEST_IRQ_PRIORITY); | |
| pio_set_irq0_source_enabled(pio, pis_interrupt0 + pioSm, true); | |
| // enable the SM, that also starts the program | |
| pio_sm_set_enabled(pio, pioSm, true); | |
| return 0; | |
| } | |
| // can be used for debugging | |
| uint getPioProgOff() { | |
| return pioProgOffset; | |
| } | |
| uint getPioSm() { | |
| return pioSm; | |
| } | |
| /**************************** | |
| * nes_gamepad.h | |
| ************************/ | |
| #include "hardware/pio.h" | |
| #ifndef NES_GAMEPAD_H__ | |
| #define NES_GAMEPAD_H__ | |
| int gamepadStart(PIO pio, uint dataPin, uint clkPin, uint psPin, uint *gpStateVar); | |
| uint getPioProgOff(); | |
| uint getPioSm(); | |
| #endif | |
| /****************************** | |
| * nes_gamepad.pio | |
| ****************************/ | |
| .program NES_controller_interface | |
| .side_set 1 | |
| ; every instruction ins delayed another three cycles so we can get a really slow clock. | |
| .wrap_target | |
| ; first assert P/S and toggle the clock once to latch data | |
| set PINS, 1 side 0 [3] | |
| ; set counter register | |
| set X, 7 side 1 [3] | |
| ; deassert P/S to switch to shift register mode | |
| set PINS, 0 side 1 [3] | |
| readBits: | |
| ; read one bit, then jump back until we've read 8 | |
| in PINS, 1 side 0 [3] | |
| jmp X-- readBits side 1 [3] | |
| ; notify CPU that new data has arrived | |
| irq 0 side 1 [3] | |
| .wrap | |
| % c-sdk { | |
| static inline void nesControllerProgramInit(PIO pio, uint sm, uint offset, uint dataPin, uint clkPin, uint psPin) { | |
| // initialize GPIO pins. This changes their function to PIO. | |
| // The pins are used as follows: | |
| // dataPin is only read from (in) | |
| // clkPin is controlled via side set | |
| // psPin is controlled using set | |
| pio_gpio_init(pio, dataPin); | |
| pio_gpio_init(pio, clkPin); | |
| pio_gpio_init(pio, psPin); | |
| // configure pin directions | |
| pio_sm_set_pindirs_with_mask(pio, sm, (1 << clkPin) | (1 << psPin), (1 << dataPin) | (1 << clkPin) | (1 << psPin)); | |
| pio_sm_config c = NES_controller_interface_program_get_default_config(offset); | |
| // configure input, set and sideset pins. Only for set more than one pin can be configured at once. | |
| sm_config_set_in_pins(&c, dataPin); | |
| sm_config_set_set_pins(&c, psPin, 1); | |
| sm_config_set_sideset_pins(&c, clkPin); | |
| // configure sideset to use 1 bit, be mandatory and control pin values. | |
| // NOTE: that's already done by get_default_config(), not required here. | |
| //sm_config_set_sideset(&c, 1, false, false); | |
| // join FIFOs as we only need the RX FIFO | |
| sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX); | |
| // set the clock divider to 35400 so the gamepad state is read every 20 ms (50 Hz) | |
| // each readout takes 20 instructions, leading to 80 cycles of the SM clock, i.e., | |
| // each clock cycle must last 250 us (4 kHz). To get a 4 kHz clock from the 141.6 MHz | |
| // system clock we must divide it by 35,400. | |
| sm_config_set_clkdiv_int_frac(&c, 35400, 0); | |
| // output shift register isn't used so we don't need to configure it. | |
| // configure the input shift register: Shift left, enable auto push, the register is | |
| // considered full after eight bits have been shifted in. | |
| sm_config_set_in_shift(&c, false, true, 8); | |
| // initialize SM | |
| pio_sm_init(pio, sm, offset, &c); | |
| // the PIO program isn't started here, that's done by gamepadStart(). | |
| } | |
| %} | |
| /*************************** | |
| * CMakeLists.txt | |
| ************************/ | |
| # this is from pico-examples' root directory | |
| cmake_minimum_required(VERSION 3.12) | |
| message($ENV{PICO_SDK_PATH}) | |
| # Pull in SDK (must be before project) | |
| #include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake) | |
| include(pico_sdk_import.cmake) | |
| message($ENV{PICO_SDK_PATH}) | |
| project(pico_gamepad C CXX ASM) | |
| set(CMAKE_C_STANDARD 11) | |
| set(CMAKE_CXX_STANDARD 17) | |
| if (PICO_SDK_VERSION_STRING VERSION_LESS "1.3.0") | |
| message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.3.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") | |
| endif() | |
| # Initialize the SDK | |
| pico_sdk_init() | |
| add_compile_options(-Wall | |
| -Wno-format # int != int32_t as far as the compiler is concerned because gcc has int32_t as long int | |
| -Wno-unused-function # we have some for the docs that aren't called | |
| -Wno-maybe-uninitialized | |
| ) | |
| # this is the source specific part merged from hello_pio and hello_dma | |
| add_executable(gptest) | |
| pico_generate_pio_header(gptest ${CMAKE_CURRENT_LIST_DIR}/nes_gamepad.pio) | |
| target_sources(gptest PRIVATE gptest.c nes_gamepad.c) | |
| pico_enable_stdio_usb(gptest 1) | |
| target_link_libraries(gptest PRIVATE | |
| pico_stdlib | |
| hardware_pio | |
| hardware_dma | |
| hardware_irq | |
| ) | |
| pico_add_extra_outputs(gptest) | |
| # add url via pico_set_program_url | |
| #example_auto_set_url(paltest) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment