diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 5e9b0e5..028d789 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -25,7 +25,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: prawnblaster-firmware-${{ github.sha }} - path: build/prawnblaster/*.uf2 + path: build_*/prawnblaster/*.uf2 - name: Create release if: (github.event_name == 'push' && contains(github.ref, '/tags')) @@ -35,5 +35,7 @@ jobs: prerelease: false files: | LICENSE.txt - build/prawnblaster/prawnblaster.uf2 - build/prawnblaster/prawnblasteroverclock.uf2 + build_rp2040/prawnblaster/prawnblaster_rp2040.uf2 + build_rp2350/prawnblaster/prawnblaster_rp2350.uf2 + build_rp2040/prawnblaster/prawnblaster_rp2040_overclock.uf2 + build_rp2350/prawnblaster/prawnblaster_rp2350_overclock.uf2 diff --git a/.gitignore b/.gitignore index a007fea..76a2601 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -build/* +build*/* +pico_sdk_import.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 902f06b..12039f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,9 +1,9 @@ -cmake_minimum_required(VERSION 3.13) +cmake_minimum_required(VERSION 3.17) # Pull in PICO SDK (must be before project) include(pico_sdk_import.cmake) -project(pico_examples C CXX ASM) +project(prawnblaster C CXX ASM) set(CMAKE_C_STANDARD 11) set(CMAKE_CXX_STANDARD 17) @@ -11,6 +11,5 @@ set(CMAKE_CXX_STANDARD 17) # Initialize the SDK pico_sdk_init() -# Add blink example +# Add project directory add_subdirectory(prawnblaster) - diff --git a/docker-compose.yaml b/docker-compose.yaml index 4771131..12b2fa9 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,11 +1,30 @@ name: prawnblaster services: - buildfirmware: + firmware_base: build: dockerfile: docker/Dockerfile - command: /bin/bash -c 'cmake .. && make' + image: prawnblaster/build-firmware volumes: - .:/prawnblaster - working_dir: /prawnblaster/build + command: /bin/bash -c 'cp /pico/pico-sdk/external/pico_sdk_import.cmake /prawnblaster/' + container_name: firmware_base + + build_rp2040_firmware: + image: prawnblaster/build-firmware + command: /bin/bash -c 'cmake .. -D PICO_PLATFORM=rp2040 && make' + volumes: + - .:/prawnblaster + working_dir: /prawnblaster/build_rp2040 + init: true + depends_on: + - firmware_base + build_rp2350_firmware: + image: prawnblaster/build-firmware + command: /bin/bash -c 'cmake .. -D PICO_PLATFORM=rp2350 && make' + volumes: + - .:/prawnblaster + working_dir: /prawnblaster/build_rp2350 init: true + depends_on: + - firmware_base diff --git a/docker/Dockerfile b/docker/Dockerfile index 4ed746c..dcef6e6 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -6,21 +6,23 @@ ARG APT_MIRROR="mirror://mirrors.ubuntu.com/mirrors.txt" # ARG APT_MIRROR="http://mirror.aarnet.edu.au/pub/ubuntu/archive/" # Configure mirror. Pass --build-arg APT_MIRROR= to set a mirror if this is slow -RUN sed -i "s#htt[p|ps]://archive.ubuntu.com/ubuntu/#$APT_MIRROR#g" /etc/apt/sources.list +RUN sed -i "s#htt[p|ps]://archive.ubuntu.com/ubuntu/#$APT_MIRROR#g" /etc/apt/sources.list.d/ubuntu.sources # Install packages RUN \ apt update && \ apt install -y git python3 && \ - apt install -y cmake gcc-arm-none-eabi libnewlib-arm-none-eabi build-essential + # For Pico SDK + apt install -y cmake gcc-arm-none-eabi libnewlib-arm-none-eabi libstdc++-arm-none-eabi-newlib build-essential # Install Pico SDK into a new stage FROM base as buildtools +# Install Pico SDK RUN \ mkdir -p /pico/ && \ cd /pico/ && \ - git clone https://github.com/raspberrypi/pico-sdk.git --branch 1.5.1 && \ + git clone https://github.com/raspberrypi/pico-sdk.git --branch 2.0.0 && \ cd pico-sdk/ && \ git submodule update --init && \ cd / diff --git a/pico_sdk_import.cmake b/pico_sdk_import.cmake deleted file mode 100644 index 63a80ec..0000000 --- a/pico_sdk_import.cmake +++ /dev/null @@ -1,62 +0,0 @@ -# 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}) \ No newline at end of file diff --git a/prawnblaster/CMakeLists.txt b/prawnblaster/CMakeLists.txt index fed3b9e..2b59fb3 100644 --- a/prawnblaster/CMakeLists.txt +++ b/prawnblaster/CMakeLists.txt @@ -1,30 +1,49 @@ -add_executable(prawnblaster - prawnblaster.cpp - fast_serial.c - ) - -pico_generate_pio_header(prawnblaster ${CMAKE_CURRENT_LIST_DIR}/pseudoclock.pio) - - -# Pull in our pico_stdlib which aggregates commonly used features -target_link_libraries(prawnblaster pico_stdlib hardware_pio pico_multicore pico_unique_id hardware_clocks hardware_dma tinyusb_device tinyusb_board) -target_include_directories(prawnblaster PRIVATE .) - -# create map/bin/hex/uf2 file etc. -pico_add_extra_outputs(prawnblaster) - -add_executable(prawnblasteroverclock - prawnblaster.cpp - fast_serial.c - ) - -pico_generate_pio_header(prawnblasteroverclock ${CMAKE_CURRENT_LIST_DIR}/pseudoclock.pio) - -set_target_properties(prawnblasteroverclock PROPERTIES COMPILE_DEFINITIONS PRAWNBLASTER_OVERCLOCK=1) - -# Pull in our pico_stdlib which aggregates commonly used features -target_link_libraries(prawnblasteroverclock pico_stdlib hardware_pio pico_multicore pico_unique_id hardware_clocks hardware_dma tinyusb_device tinyusb_board) -target_include_directories(prawnblasteroverclock PRIVATE .) - -# create map/bin/hex/uf2 file etc. -pico_add_extra_outputs(prawnblasteroverclock) +set(overclocks 0;1) + +foreach (overclock IN LISTS overclocks) + # Compute firmware name + set(firmware_name prawnblaster) + if(PICO_PLATFORM MATCHES "^rp2350") + set(firmware_name "${firmware_name}_rp2350") + else() + set(firmware_name "${firmware_name}_${PICO_PLATFORM}") + endif() + if(overclock) + set(firmware_name "${firmware_name}_overclock") + endif() + + add_executable(${firmware_name} prawnblaster.cpp fast_serial.c) + + pico_generate_pio_header(${firmware_name} ${CMAKE_CURRENT_LIST_DIR}/pseudoclock.pio) + + # Pass in number of instructions to firmware as a compiler definition + set(num_instructions 30000) + if(PICO_PLATFORM MATCHES "^rp2350") + set(num_instructions 60000) + endif() + target_compile_definitions(${firmware_name} PUBLIC "PRAWNBLASTER_NUM_INSTRUCTIONS=${num_instructions}") + + # Pass in board type to firmware as a compiler definition. Note that PICO_BOARD is passed in by the SDK, but it's passed in a string which isn't valid and so I can't use it... + # This is also, to some extent, a duplicate of the above PRAWNBLASTER_NUM_INSTRUCTIONS but I think it makes sense to keep these seperate. + if (PICO_BOARD STREQUAL "pico") + target_compile_definitions(${firmware_name} PUBLIC "PRAWNBLASTER_PICO_BOARD=1") + elseif (PICO_BOARD STREQUAL "pico2") + target_compile_definitions(${firmware_name} PUBLIC "PRAWNBLASTER_PICO_BOARD=2") + else () + message(FATAL_ERROR "Unsupported PICO_BOARD") + endif() + + + # Pass in overclock state to firmware as a compiler definition + if(overclock) + target_compile_definitions(${firmware_name} PUBLIC "PRAWNBLASTER_OVERCLOCK=1") + endif() + + # Pull in our pico_stdlib which aggregates commonly used features + target_link_libraries(${firmware_name} pico_stdlib hardware_pio pico_multicore pico_unique_id hardware_clocks hardware_dma tinyusb_device tinyusb_board) + target_include_directories(${firmware_name} PRIVATE .) + + # create map/bin/hex/uf2 file etc. + pico_add_extra_outputs(${firmware_name}) + +endforeach() diff --git a/prawnblaster/prawnblaster.cpp b/prawnblaster/prawnblaster.cpp index a0dfd50..20ed8d0 100644 --- a/prawnblaster/prawnblaster.cpp +++ b/prawnblaster/prawnblaster.cpp @@ -37,20 +37,22 @@ extern "C"{ } #ifndef PRAWNBLASTER_OVERCLOCK -const char VERSION[16] = "1.1.0"; +const char VERSION[16] = "1.2.0"; #else -const char VERSION[16] = "1.1.0-overclock"; +const char VERSION[16] = "1.2.0-overclock"; #endif //PRAWNBLASTER_OVERCLOCK int DEBUG; // Can't seem to have this be used to define array size even though it's a constant -const unsigned int max_instructions = 30000; -const unsigned int max_waits = 400; +constexpr unsigned int max_instructions = PRAWNBLASTER_NUM_INSTRUCTIONS; +constexpr unsigned int instruction_array_size = 2 * max_instructions + 8; +constexpr unsigned int max_waits = 400; +constexpr unsigned int wait_array_size = max_waits + 4; // max_instructions*2 + 8 -unsigned int instructions[60008]; +unsigned int instructions[instruction_array_size]; // max_waits + 4 -unsigned int waits[404]; +unsigned int waits[wait_array_size]; #define SERIAL_BUFFER_SIZE 256 char readstring[SERIAL_BUFFER_SIZE] = ""; @@ -246,7 +248,7 @@ bool configure_pseudoclock_pio_sm(pseudoclock_config *config, uint prog_offset, break; } } - else + else if (pio_to_use == pio1) { switch (config->sm) { @@ -266,6 +268,28 @@ bool configure_pseudoclock_pio_sm(pseudoclock_config *config, uint prog_offset, break; } } +#if PRAWNBLASTER_PICO_BOARD == 2 + else if (pio_to_use == pio2) + { + switch (config->sm) + { + case 0: + instruction_dreq = DREQ_PIO2_TX0; + break; + case 1: + instruction_dreq = DREQ_PIO2_TX1; + break; + case 2: + instruction_dreq = DREQ_PIO2_TX2; + break; + case 3: + instruction_dreq = DREQ_PIO2_TX3; + break; + default: + break; + } + } +#endif channel_config_set_dreq(&instruction_c, instruction_dreq); dma_channel_configure( @@ -305,7 +329,7 @@ bool configure_pseudoclock_pio_sm(pseudoclock_config *config, uint prog_offset, break; } } - else + else if (pio_to_use == pio1) { switch (config->sm) { @@ -325,6 +349,28 @@ bool configure_pseudoclock_pio_sm(pseudoclock_config *config, uint prog_offset, break; } } +#if PRAWNBLASTER_PICO_BOARD == 2 + else if (pio_to_use == pio2) + { + switch (config->sm) + { + case 0: + waits_dreq = DREQ_PIO2_RX0; + break; + case 1: + waits_dreq = DREQ_PIO2_RX1; + break; + case 2: + waits_dreq = DREQ_PIO2_RX2; + break; + case 3: + waits_dreq = DREQ_PIO2_RX3; + break; + default: + break; + } + } +#endif channel_config_set_dreq(&waits_c, waits_dreq); // change the default to increment write address and leave read constant @@ -694,7 +740,9 @@ void measure_freqs(void) uint f_clk_peri = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_PERI); uint f_clk_usb = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_USB); uint f_clk_adc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_ADC); +#ifdef CLOCKS_FC0_SRC_VALUE_CLK_RTC uint f_clk_rtc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_RTC); +#endif fast_serial_printf("pll_sys = %dkHz\r\n", f_pll_sys); fast_serial_printf("pll_usb = %dkHz\r\n", f_pll_usb); @@ -703,7 +751,9 @@ void measure_freqs(void) fast_serial_printf("clk_peri = %dkHz\r\n", f_clk_peri); fast_serial_printf("clk_usb = %dkHz\r\n", f_clk_usb); fast_serial_printf("clk_adc = %dkHz\r\n", f_clk_adc); +#ifdef CLOCKS_FC0_SRC_VALUE_CLK_RTC fast_serial_printf("clk_rtc = %dkHz\r\n", f_clk_rtc); +#endif } void resus_callback(void) @@ -723,13 +773,17 @@ void loop() { fast_serial_read_until(readstring, 256, '\n'); int local_status = get_status(); - if (strncmp(readstring, "version", 7) == 0) + if (strncmp(readstring, "status", 6) == 0) + { + fast_serial_printf("run-status:%d clock-status:%d\r\n", local_status, clock_status); + } + else if (strncmp(readstring, "version", 7) == 0) { fast_serial_printf("version: %s\r\n", VERSION); } - else if (strncmp(readstring, "status", 6) == 0) + else if (strncmp(readstring, "board", 5) == 0) { - fast_serial_printf("run-status:%d clock-status:%d\r\n", local_status, clock_status); + fast_serial_printf("board: pico%d\r\n", PRAWNBLASTER_PICO_BOARD); } else if (strncmp(readstring, "debug on", 8) == 0) { @@ -959,7 +1013,7 @@ void loop() else if (strncmp(readstring, "setclock", 8) == 0) { unsigned int src; // 0 = internal, 1=GPIO pin 20, 2=GPIO pin 22 - unsigned int freq; // in Hz (up to 133 MHz) + unsigned int freq; // in Hz (up to 133 or 150 MHz depending on board) int parsed = sscanf(readstring, "%*s %u %u", &src, &freq); if (parsed < 2) { @@ -979,8 +1033,14 @@ void loop() { #ifndef PRAWNBLASTER_OVERCLOCK - // Do validation checking on values provided +# if PRAWNBLASTER_PICO_BOARD == 1 if (freq > 133 * MHZ) +# elif PRAWNBLASTER_PICO_BOARD == 2 + if (freq > 150 * MHZ) +# else +# error "Unsupported PICO_BOARD" +# endif // PICO_BOARD + // Do validation checking on values provided { fast_serial_printf("Invalid clock frequency specified\r\n"); return; @@ -1018,6 +1078,7 @@ void loop() { fast_serial_printf("invalid request\r\n"); } +#if PRAWNBLASTER_PICO_BOARD == 1 else if (core != 0 && core != 1) { fast_serial_printf("You must specify either 0 for PIO0 or 1 for PIO1\r\n"); @@ -1027,6 +1088,17 @@ void loop() pio_to_use = core == 0 ? pio0 : pio1; fast_serial_printf("ok\r\n"); } +#elif PRAWNBLASTER_PICO_BOARD == 2 + else if (core != 0 && core != 1 && core != 2) + { + fast_serial_printf("You must specify either 0 for PIO0 or 1 for PIO1 or 2 for PIO2\r\n"); + } + else + { + pio_to_use = core == 0 ? pio0 : (core == 1 ? pio1 : pio2); + fast_serial_printf("ok\r\n"); + } +#endif } else if (strncmp(readstring, "hwstart", 7) == 0) { diff --git a/readme.md b/readme.md index c6a75fd..197da14 100644 --- a/readme.md +++ b/readme.md @@ -2,6 +2,10 @@ The PrawnBlaster firmware turns the $5 Raspberry Pi Pico microcontroller board into a [_labscript suite_](https://github.com/labscript-suite) pseudoclock device. +The firmware is compatible with both the Pico 2 board (based on the RP2350 chip) and the original Pico board (based on the RP2040 chip). +**We recommend ensuring you have the [official Pico 2 board](https://www.raspberrypi.com/products/raspberry-pi-pico-2/) with the RP2350 chip for best performance.** +The [original Pico](https://www.raspberrypi.com/products/raspberry-pi-pico/) (RP2040 chip) supports half the number of instructions and has a slower maximum clock speed. + ## What is a pseudoclock device? A pseudoclock is a device that can be programmed to output a variable frequency clock. The entire sequence of clock pulses is programmed into internal memory (over serial) and then executed on command (in order to achieve precise timing). @@ -10,7 +14,7 @@ In the case of the PrawnBlaster, you can program in a series of instructions con ## Features/Specifications * Only costs $5 (a tenth of the cost of the [PineBlaster](https://github.com/labscript-suite/pineblaster)). -* Support for up to 30000 instructions (4 times the PineBlaster) spread evenly across 1 to 4 independent pseudoclocks (also 4 times the PineBlaster). +* Support for up to 60000 instructions (8 times the PineBlaster) spread evenly across 1 to 4 independent pseudoclocks (also 4 times the PineBlaster). * Each instruction can be repeated up to 4294967295 times (2^32-1). * Minimum pulse half-period 50ns (if internal clock running at 100MHz). * Maximum pulse half-period ~42.9 seconds (if internal clock running at 100MHz). @@ -20,15 +24,17 @@ In the case of the PrawnBlaster, you can program in a series of instructions con * Support for timeouts on those waits (with maximum length 42.9 seconds). * Ability to internally monitor the length of those waits and report them over the serial connection at the end of the instruction execution. * Support for indefinite waits until retrigger (Note: the PrawnBlaster will not report the length of indefinite waits). -* Support for referencing to an external clock source to synchronise with other devices (officially limited to 50MHz on the Pico but testing has shown it works up to 125MHz, see [#6](https://github.com/labscript-suite/PrawnBlaster/issues/6)). +* Support for referencing to an external clock source to synchronise with other devices (officially limited to 50MHz on the Pico but testing has shown it works up to 133MHz, see [#6](https://github.com/labscript-suite/PrawnBlaster/issues/6)). Note 1: The half-period is the time a clock pulse stays high. All clock pulses produced by the PrawnBlaster have a 50-50 duty cycle. -Note 2: The internal clock can be configured to run at up to 133 MHz. We set it to a default of 100 MHz in order to produce nice round numbers. +Note 2: The internal clock can be configured to run at up to 150 MHz (Pico 2 - RP2350) or 133 MHz (Pico - RP2040). We set it to a default of 100 MHz in order to produce nice round numbers. You can increase the clock frequency at runtime (via a serial command) which scales the timing specifications accordingly. +Note 3: The original Pico (RP2040) only supports up to 30000 instructions (4 times the PineBlaster). + ## How to flash the firmware -Download the latest [prawnblaster.uf2 file](https://github.com/labscript-suite/PrawnBlaster/releases/latest/download/prawnblaster.uf2). +Download the latest prawnblaster.uf2 file: [Pico - RP2040](https://github.com/labscript-suite/PrawnBlaster/releases/latest/download/prawnblaster_rp2040.uf2), [Pico 2 - RP2350](https://github.com/labscript-suite/PrawnBlaster/releases/latest/download/prawnblaster_rp2350.uf2). On your Raspberry Pi Pico, hold down the "bootsel" button while plugging the Pico into USB port on a PC (that must already be turned on). The Pico should mount as a mass storage device (if it doesn't, try again or consult the Pico documentation). Drag and drop the `.uf2` file into the mounted mass storage device. @@ -65,6 +71,7 @@ Communication during buffered execution is allowed (it is handled by a separate Note: the commands are only read if terminated with `\r\n`. * `version`: Responds with a string containing the firmware version. +* `board`: Responds with a string containing the board version (`pico1` or `pico2`). * `status`: Responds with a string containing the PrawnBlaster status in the format `run-status: clock-status:` where the `run-status` integer is `0=manual-mode, 1=transitioning to buffered-execution, 2=buffered-execution, 3=abort requested, 4=currently aborting buffered execution, 5=last buffered-execution aborted, 6=transitioning to manual-mode`. `clock-status` is either 0 (for internal clock) or 1 (for external clock). * `getfreqs`: Responds with a multi-line string containing the current operating frequencies of various clocks (you will be most interested in `pll_sys` and `clk_sys`). Multiline string ends with `ok\n`. * `abort`: Prematurely ends buffered-execution. @@ -83,7 +90,7 @@ Note: the commands are only read if terminated with `\r\n`. * `getinpin `: Gets the currently set trigger input pin for pseudoclock `pseudoclock`. Returns either an integer corresponding to the set pin or `default` to indicate it will try and use the default pin as defined above for `setinpin`. See FAQ below for more details on what happens if it can't use the default. * `getoutpin `: Gets the currently set trigger output pin for pseudoclock `pseudoclock`. Returns either an integer corresponding to the set pin or `default` to indicate it will use try and use the default pin as defined above for `setoutpin`. See FAQ below for more details on what happens if it can't use the default. * `debug `: Turns on extra debug messages printed over serial. `state` should be `on` or `off` (no string quotes required). -* `setpio `: Sets whether the PrawnBlaster should use pio0 or pio1 in the RP2040 chip (both have 4 state machines). Defaults to `0` (pio0) on powerup. May be useful if your particular board shows different timing behaviour (on the sub 10ns scale) between the PIO cores and you care about this level of precision. Otherwise you can leave this as the default. +* `setpio `: Sets whether the PrawnBlaster should use pio0, pio1, or pio2 in the RP2040/RP2350 chip (each have 4 state machines). Defaults to `0` (pio0) on powerup. May be useful if your particular board shows different timing behaviour (on the sub 10ns scale) between the PIO cores and you care about this level of precision. Otherwise you can leave this as the default. The RP2040 only supports pio0 and pio1. * `program`: Equivalent to disconnecting the Pico, holding down the "bootsel" button, and reconnecting the Pico. Places the Pico into firmware flashing mode; the PrawnBlaster serial port should disappear and the Pico should mount as a mass storage device. ## Reconfiguring the internal clock. @@ -91,7 +98,7 @@ The clock frequency (and even source) can be reconfigured at runtime (it is init To do this, you send the command `setclock `, where the parameters are: * mode: `0` means use the internal reference source and PLL. `1` means use an external source (bypassing the PLL) fed in on GPIO 20. `2` means use an external source (bypassing the PLL fed in on GPIO 22). -* freq: The clock frequency, in Hz (max 133000000). +* freq: The clock frequency, in Hz (max 133000000 for RP2040 boards and 150000000 for RP2350 boards). For example, to set the internal frequency to 125 MHz, you would send: @@ -119,7 +126,7 @@ This directly clocks the cores running the PrawnBlaster firmware, so minimum pul An external clock of 50MHz means a minimum half-period of `5/50MHz=100ns`. An external clock of 100MHz means a minimum half-period of `5/100MHz=50ns`. -Note: Officially, the documentation for the Pico says external clock sources can only be up to 50MHz. We have successfully tested up to 125MHz (see [#6](https://github.com/labscript-suite/PrawnBlaster/issues/6)). +Note: Officially, the documentation for the Pico says external clock sources can only be up to 50MHz. We have successfully tested up to 133MHz (see [#6](https://github.com/labscript-suite/PrawnBlaster/issues/6)). We recommend you personally verify the output of the PrawnBlaster is as expected if running from an external reference above 50MHz. ## Compiling the firmware @@ -142,6 +149,12 @@ Note once the docker container is built, you can run step 5 as many times as you You do not need to rebuild the container, even if you make changes to the PrawnBlaster source code. You only need to rebuild the docker container if you modify the `build/docker/Dockerfile` file. +By default, running `docker compose up` builds the all variations of the firmware. +If you only want to build for a specific board, run either `docker compose up build_rp2040_firmware` or `docker compose up build_rp2350_firmware`. + +The firmware will be located in `build_rp2xxx/prawnblaster/prawnblaster_rp2xxx[_overclock].uf2` where `rp2xxx` will be either `rp2040` or `rp2350`. +The firmware supporting overclocking will have the `_overclock` suffix. + ## FAQ: ### Why is it called a "PrawnBlaster"? @@ -172,7 +185,7 @@ If you want to find out what pins, you should ### Can I overclock the Pico board? Yes, some people have reported being able to significantly overclock their Raspberry Pi Pico boards. -While we do not recommend you do so (nor will we be liable for any damage to your Pico or attached devices should you overclock it), we have provided a version of the PrawnBlaster firmware with no upper limit to the clock frequency [here](https://github.com/labscript-suite/PrawnBlaster/releases/latest/download/prawnblasteroverclock.uf2). +While we do not recommend you do so (nor will we be liable for any damage to your Pico or attached devices should you overclock it), we have provided a version of the PrawnBlaster firmware with no upper limit to the clock frequency here: [pico - RP2040](https://github.com/labscript-suite/PrawnBlaster/releases/latest/download/prawnblaster_rp2040_overclock.uf2), [pico 2 - RP2350](https://github.com/labscript-suite/PrawnBlaster/releases/latest/download/prawnblaster_rp2350_overclock.uf2)). Use at your own risk! We provide no support for the overclockable firmware. If you need to tweak any other operating parameters of the Pico in order to achieve a stable overclock, you will need to manually modify the firmware source code and recompile it with those changes. @@ -183,10 +196,12 @@ There are more details in [#5](https://github.com/labscript-suite/PrawnBlaster/i If you are using the PrawnBlaster outside of the labscript suite, and want each pseudoclock to be triggered by a different GPIO pin, then you should be aware there seems to be a +/-10ns fluctuation in trigger response which may lead pseudoclocks to become out of sync with each other by 20ns per trigger. -### Can I use other RP2040 boards? +### Can I use other RP2040/RP235x boards? Maybe - it depends what pins are available. +You will also (most likely) need to recompile the firmware for these boards (and may need to adjust build options defined in various configuration files). In general it should work as long as it has 9 GPIO pins available for use (and not hardwired to a peripheral) and one of them is pin 20/22. Without pin 20 or 22, you can't externally reference the board. And with less GPIO pins you can't have 4 independent pseudoclocks each with an independent trigger. If you only need 1 trigger, you can get away with 1 pin for a trigger, 1 pin for each pseudoclock output you want (somewhere between 1 and 4) and 1 pin (either GPIO 20 or 22) for externally referencing if you need it. -But unless you have a strong reason to get another RP2040 based board, we suggest sticking with the standard Pico (Which is usually the cheaper option anyway). +But unless you have a strong reason to get another RP2040/RP235x based board, we suggest sticking with the standard Rapsberry Pi Pico or Pico 2 (Which is usually the cheaper option anyway). +As we are a volunteer project, we can only offer minimal support for other potentially compatible boards.