Skip to content

Instantly share code, notes, and snippets.

@sonnny
Created January 5, 2024 14:00
Show Gist options
  • Select an option

  • Save sonnny/2f5a259889945e367403acbd45f8e132 to your computer and use it in GitHub Desktop.

Select an option

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
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