├── .devcontainer └── devcontainer.json ├── .github └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── BuildVersion.h.in ├── CMakeLists.txt ├── GameBoyHeader.c ├── GameBoyHeader.h ├── GbDma.c ├── GbDma.h ├── GbRtc.c ├── GbRtc.h ├── GlobalDefines.h ├── LICENSE.md ├── MyBoard.h ├── Readme.md ├── RomStorage.c ├── RomStorage.h ├── cmakeHelpers ├── bin2h.cmake └── executebin2h.cmake ├── docs ├── 2024-01-27-backspace-bamberg.odp ├── 2024-01-27-backspace-bamberg.pdf ├── Flash Layout.drawio └── ReadDma.drawio ├── gameboy_bus.pio ├── gb-bootloader ├── CMakeLists.txt ├── bootloader.c ├── giraffe_4color_data.c ├── giraffe_4color_data.h ├── giraffe_4color_map.c └── giraffe_4color_map.h ├── gb-vblankhook ├── CMakeLists.txt ├── gbSaveGameVBlankHook.asm └── hardware.inc ├── libs ├── git-commit-tracking │ ├── CMakeLists.txt │ ├── LICENSE │ ├── git_commit.c.in │ ├── git_commit.h │ └── git_watcher.cmake └── pico-littlefs │ ├── CMakeLists.txt │ ├── LICENSE.md │ ├── lfs.c │ ├── lfs.h │ ├── lfs_pico_hal.c │ ├── lfs_pico_hal.h │ ├── lfs_util.c │ └── lfs_util.h ├── linkerscript.ld ├── main.c ├── mbc.c ├── mbc.h ├── tusb_config.h ├── usb_descriptors.c ├── usb_descriptors.h ├── webusb.c ├── webusb.h ├── ws2812b_spi.c └── ws2812b_spi.h /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "image": "shilga/raspberry-pi-pico-gbdk-docker:v0.4", 3 | "name": "GBCartridgeDevContainer", 4 | "customizations": { 5 | "vscode": { 6 | "extensions": [ 7 | "llvm-vs-code-extensions.vscode-clangd", 8 | "chris-hock.pioasm", 9 | "donaldhays.rgbds-z80", 10 | "akiramiyakoda.cppincludeguard" 11 | ] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This starter workflow is for a CMake project running on a single platform. There is a different starter workflow if you need cross-platform coverage. 2 | # See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml 3 | name: CI - Build on changes in Docker container 4 | 5 | on: 6 | push: 7 | branches: [ "master", "dev"] 8 | pull_request: 9 | branches: [ "master" ] 10 | 11 | env: 12 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 13 | BUILD_TYPE: Release 14 | 15 | jobs: 16 | build: 17 | # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. 18 | # You can convert this to a matrix build if you need cross-platform coverage. 19 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 20 | runs-on: ubuntu-latest 21 | container: 22 | image: shilga/raspberry-pi-pico-gbdk-docker:v0.4 23 | 24 | steps: 25 | - uses: actions/checkout@v3 26 | 27 | - name: Configure CMake 28 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 29 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 30 | run: | 31 | echo "GITHUB_WORKSPACE=$GITHUB_WORKSPACE" >> $GITHUB_ENV 32 | echo 'github.workspace = ${{ github.workspace }}' 33 | echo "GITHUB_WORKSPACE = $GITHUB_WORKSPACE" 34 | echo 'runner.workspace = ${{ runner.workspace }}' 35 | echo "RUNNER_WORKSPACE = $RUNNER_WORKSPACE" 36 | cmake -B $GITHUB_WORKSPACE/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} 37 | 38 | - name: Build 39 | # Build your program with the given configuration 40 | run: | 41 | git config --global --add safe.directory "$GITHUB_WORKSPACE" 42 | echo "GITHUB_WORKSPACE=$GITHUB_WORKSPACE" >> $GITHUB_ENV 43 | echo "GITHUB_SHA=$GITHUB_SHA" >> $GITHUB_ENV 44 | echo 'github.workspace = ${{ github.workspace }}' 45 | echo "GITHUB_WORKSPACE = $GITHUB_WORKSPACE" 46 | echo 'runner.workspace = ${{ runner.workspace }}' 47 | echo "RUNNER_WORKSPACE = $RUNNER_WORKSPACE" 48 | cmake --build $GITHUB_WORKSPACE/build --config ${{env.BUILD_TYPE}} 49 | 50 | - name: Archive artifacts 51 | uses: actions/upload-artifact@v4 52 | with: 53 | name: rp2040-gameboy-cartridge-binaries-${{env.GITHUB_SHA}} 54 | path: | 55 | ${{env.GITHUB_WORKSPACE}}/build/*.bin 56 | ${{env.GITHUB_WORKSPACE}}/build/*.elf 57 | ${{env.GITHUB_WORKSPACE}}/build/*.elf.map 58 | ${{env.GITHUB_WORKSPACE}}/build/*.hex 59 | ${{env.GITHUB_WORKSPACE}}/build/*.uf2 60 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # This starter workflow is for a CMake project running on a single platform. There is a different starter workflow if you need cross-platform coverage. 2 | # See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-multi-platform.yml 3 | name: Build release 4 | 5 | on: 6 | push: 7 | tags: 8 | - '[0-9]+.[0-9]+.[0-9]+*' # Push events to any matching semantic tag. For example, 1.10.1 or 2.0.0 or 3.0.0-alpha. 9 | # For more details, see https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet 10 | 11 | env: 12 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 13 | BUILD_TYPE: Release 14 | 15 | jobs: 16 | build: 17 | runs-on: ubuntu-latest 18 | container: 19 | image: shilga/raspberry-pi-pico-gbdk-docker:v0.4 20 | 21 | steps: 22 | - uses: actions/checkout@v3 23 | 24 | - name: Configure CMake 25 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 26 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 27 | run: | 28 | echo "GITHUB_WORKSPACE=$GITHUB_WORKSPACE" >> $GITHUB_ENV 29 | echo 'github.workspace = ${{ github.workspace }}' 30 | echo "GITHUB_WORKSPACE = $GITHUB_WORKSPACE" 31 | echo 'runner.workspace = ${{ runner.workspace }}' 32 | echo "RUNNER_WORKSPACE = $RUNNER_WORKSPACE" 33 | cmake --fresh -B $GITHUB_WORKSPACE/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_TIME_PROVIDED_VERSION=${{ github.ref_name }} 34 | 35 | - name: Build 36 | # Build your program with the given configuration 37 | run: | 38 | git config --global --add safe.directory "$GITHUB_WORKSPACE" 39 | echo "GITHUB_WORKSPACE=$GITHUB_WORKSPACE" >> $GITHUB_ENV 40 | echo "GITHUB_SHA=$GITHUB_SHA" >> $GITHUB_ENV 41 | echo 'github.workspace = ${{ github.workspace }}' 42 | echo "GITHUB_WORKSPACE = $GITHUB_WORKSPACE" 43 | echo 'runner.workspace = ${{ runner.workspace }}' 44 | echo "RUNNER_WORKSPACE = $RUNNER_WORKSPACE" 45 | cmake --build $GITHUB_WORKSPACE/build --config ${{env.BUILD_TYPE}} 46 | 47 | - name: Package artifacts 48 | run: | 49 | cd ${{env.GITHUB_WORKSPACE}}/build 50 | tar cvzf rp2040-gb-cartridge-${{ github.ref_name }}.tgz *.bin *.elf *.elf.map *.hex *.uf2 51 | 52 | - name: Upload artifact 53 | uses: actions/upload-artifact@v4 54 | with: 55 | name: rp2040-gameboy-cartridge-${{ github.ref_name }} 56 | path: | 57 | ${{env.GITHUB_WORKSPACE}}/build/*.tgz 58 | 59 | create_release: 60 | name: Create release 61 | runs-on: ubuntu-latest 62 | needs: build 63 | permissions: 64 | contents: write 65 | outputs: 66 | upload_url: ${{ steps.create_release.outputs.upload_url }} 67 | steps: 68 | - name: Download Artifact 69 | uses: actions/download-artifact@v4 70 | with: 71 | merge-multiple: true 72 | 73 | - name: list files 74 | run: ls 75 | 76 | - name: Release 77 | if: startsWith(github.ref, 'refs/tags/') 78 | id: create_release 79 | uses: softprops/action-gh-release@v1 80 | env: 81 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 82 | with: 83 | prerelease: false 84 | draft: true 85 | tag_name: ${{ github.ref_name }} 86 | name: ${{ github.ref_name }} 87 | files: "*.tgz" 88 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | generated 3 | .cache 4 | .vscode 5 | -------------------------------------------------------------------------------- /BuildVersion.h.in: -------------------------------------------------------------------------------- 1 | #ifndef BDBB39EB_F2C3_4AEF_AE59_9D2345106EEE 2 | #define BDBB39EB_F2C3_4AEF_AE59_9D2345106EEE 3 | 4 | #define RP2040_GB_CARTRIDGE_VERSION_MAJOR @rp2040-gb-cartridge_VERSION_MAJOR@ 5 | #define RP2040_GB_CARTRIDGE_VERSION_MINOR @rp2040-gb-cartridge_VERSION_MINOR@ 6 | #define RP2040_GB_CARTRIDGE_VERSION_PATCH @rp2040-gb-cartridge_VERSION_PATCH@ 7 | #define RP2040_GB_CARTRIDGE_BUILD_VERSION_TYPE '@BUILD_VERSION_TYPE@' 8 | 9 | #define RP2040_GB_CARTRIDGE_BUILD_TIMESTAMP @BUILD_TIMESTAMP@ 10 | 11 | #endif /* BDBB39EB_F2C3_4AEF_AE59_9D2345106EEE */ 12 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | if (NOT DEFINED BUILD_TIME_PROVIDED_VERSION) 4 | set(BUILD_TIME_PROVIDED_VERSION 255.255.255) 5 | set(BUILD_VERSION_TYPE U) 6 | else () 7 | set(BUILD_VERSION_TYPE R) 8 | endif() 9 | message("BUILD_TIME_PROVIDED_VERSION=${BUILD_TIME_PROVIDED_VERSION} BUILD_VERSION_TYPE=${BUILD_VERSION_TYPE}") 10 | 11 | string(TIMESTAMP BUILD_TIMESTAMP "%s" UTC) 12 | 13 | include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake) 14 | 15 | project(rp2040-gb-cartridge C CXX ASM) 16 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 17 | 18 | project(${PROJECT_NAME} VERSION ${BUILD_TIME_PROVIDED_VERSION}) 19 | 20 | configure_file(BuildVersion.h.in BuildVersion.h) 21 | 22 | set(CMAKE_C_STANDARD 11) 23 | set(CMAKE_CXX_STANDARD 17) 24 | set(PICO_BOARD_HEADER_DIRS ${CMAKE_SOURCE_DIR}) 25 | set(PICO_BOARD MyBoard) 26 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 27 | set(CMAKE_HELPERS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/cmakeHelpers) 28 | 29 | pico_sdk_init() 30 | 31 | add_subdirectory(gb-bootloader) 32 | add_subdirectory(gb-vblankhook) 33 | 34 | add_subdirectory(libs/pico-littlefs) 35 | target_compile_definitions(littlefs-lib PUBLIC LFS_NO_MALLOC) 36 | 37 | add_subdirectory(libs/git-commit-tracking) 38 | 39 | add_executable(${PROJECT_NAME} 40 | main.c 41 | GbDma.c 42 | GbRtc.c 43 | mbc.c 44 | webusb.c 45 | usb_descriptors.c 46 | RomStorage.c 47 | GameBoyHeader.c 48 | ws2812b_spi.c 49 | ) 50 | 51 | file(MAKE_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/generated) 52 | 53 | pico_generate_pio_header(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}/gameboy_bus.pio OUTPUT_DIR ${CMAKE_CURRENT_LIST_DIR}/generated) 54 | 55 | target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_LIST_DIR}) 56 | 57 | pico_enable_stdio_usb(${PROJECT_NAME} 0) 58 | pico_enable_stdio_uart(${PROJECT_NAME} 1) 59 | # pico_set_printf_implementation(${PROJECT_NAME} none) 60 | pico_set_linker_script(${PROJECT_NAME} ${CMAKE_CURRENT_LIST_DIR}/linkerscript.ld) 61 | 62 | #Target that removes the CMake variable BUILD_TIMESTAMP from cache. 63 | #this forces CMake to rerun. 64 | add_custom_target( 65 | clear_cache 66 | COMMAND ${CMAKE_COMMAND} -U BUILD_TIMESTAMP ${CMAKE_BINARY_DIR} 67 | ) 68 | 69 | add_dependencies(${PROJECT_NAME} 70 | clear_cache 71 | BootloaderGb 72 | gbSaveGameVBlankHook 73 | ) 74 | 75 | target_link_libraries(${PROJECT_NAME} 76 | pico_stdlib 77 | pico_bootsel_via_double_reset 78 | hardware_dma 79 | hardware_uart 80 | hardware_i2c 81 | hardware_spi 82 | hardware_gpio 83 | hardware_pio 84 | hardware_clocks 85 | littlefs-lib 86 | tinyusb_device 87 | tinyusb_board 88 | cmake_git_commit_tracking 89 | ) 90 | 91 | pico_add_extra_outputs(${PROJECT_NAME}) 92 | -------------------------------------------------------------------------------- /GameBoyHeader.c: -------------------------------------------------------------------------------- 1 | /* RP2040 GameBoy cartridge 2 | * Copyright (C) 2023 Sebastian Quilitz 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "GameBoyHeader.h" 19 | #include 20 | 21 | uint8_t GameBoyHeader_readRamBankCount(const uint8_t *gameptr) { 22 | static const uint8_t LOOKUP[] = {0, 0, 1, 4, 16, 8}; 23 | const uint8_t value = gameptr[0x0149]; 24 | 25 | if (value <= sizeof(LOOKUP)) { 26 | return LOOKUP[value]; 27 | } 28 | 29 | return 0xFF; 30 | } 31 | 32 | bool GameBoyHeader_hasRtc(const uint8_t *gameptr) { 33 | const uint8_t cartridgeType = gameptr[0x0147]; 34 | 35 | if ((cartridgeType == 0x0F) || (cartridgeType == 0x10)) { 36 | return true; 37 | } else { 38 | return false; 39 | } 40 | } 41 | 42 | uint8_t GameBoyHeader_readMbc(const uint8_t *gameptr) { 43 | uint8_t mbc = 0xFF; 44 | 45 | switch (gameptr[0x0147]) { 46 | case 0x00: 47 | mbc = 0; 48 | break; 49 | case 0x01: 50 | case 0x02: 51 | case 0x03: 52 | mbc = 1; 53 | break; 54 | case 0x05: 55 | case 0x06: 56 | case 0x07: 57 | mbc = 2; 58 | break; 59 | case 0x0F: 60 | case 0x10: 61 | case 0x11: 62 | case 0x12: 63 | case 0x13: 64 | mbc = 3; 65 | break; 66 | case 0x19: 67 | case 0x1A: 68 | case 0x1B: 69 | case 0x1C: 70 | case 0x1D: 71 | case 0x1E: 72 | mbc = 5; 73 | break; 74 | } 75 | 76 | return mbc; 77 | } 78 | 79 | bool GameBoyHeader_hasColorSupport(const uint8_t *gameptr) { 80 | const uint8_t cgbFlag = gameptr[0x0143]; 81 | return (cgbFlag & 0x80) == 0x80; 82 | } 83 | -------------------------------------------------------------------------------- /GameBoyHeader.h: -------------------------------------------------------------------------------- 1 | /* RP2040 GameBoy cartridge 2 | * Copyright (C) 2023 Sebastian Quilitz 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #ifndef B8D95B66_116E_427A_B1E9_B1BF5AFC49E5 19 | #define B8D95B66_116E_427A_B1E9_B1BF5AFC49E5 20 | 21 | #include 22 | #include 23 | 24 | uint8_t GameBoyHeader_readRamBankCount(const uint8_t *gameptr); 25 | 26 | bool GameBoyHeader_hasRtc(const uint8_t *gameptr); 27 | 28 | uint8_t GameBoyHeader_readMbc(const uint8_t *gameptr); 29 | 30 | bool GameBoyHeader_hasColorSupport(const uint8_t *gameptr); 31 | 32 | #endif /* B8D95B66_116E_427A_B1E9_B1BF5AFC49E5 */ 33 | -------------------------------------------------------------------------------- /GbDma.c: -------------------------------------------------------------------------------- 1 | /* RP2040 GameBoy cartridge 2 | * Copyright (C) 2024 Sebastian Quilitz 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "GlobalDefines.h" 25 | #include "hardware/address_mapped.h" 26 | 27 | #define DMA_CHANNEL_CMD_EXECUTOR 0 28 | #define DMA_CHANNEL_CMD_LOADER 1 29 | #define DMA_CHANNEL_MEMORY_ACCESSOR 2 30 | 31 | #define DMA_CHANNEL_ROM_LOWER_REQUESTOR 3 32 | #define DMA_CHANNEL_RAM_READ_REQUESTOR 4 33 | #define DMA_CHANNEL_RAM_WRITE_REQUESTOR 5 34 | 35 | int _dmaChannelRomHigherDirectSsiPioAddrLoader = -1; 36 | int _dmaChannelRomHigherDirectSsiBaseAddrLoader = -1; 37 | int _dmaChannelRomHigherDirectSsiFlashRequester = -1; 38 | int _dmaChannelRomHigherDirectSsiPioDataLoader = -1; 39 | 40 | /* 41 | * as the DMA needs to transfer the address of this register a variable(pointer) 42 | * is needed that holds the address of the register 43 | */ 44 | volatile io_wo_32 *_txFifoWriteData = &(pio0->txf[SMC_GB_WRITE_DATA]); 45 | volatile io_ro_32 *_rxFifoRamWrite = &(pio1->rxf[SMC_GB_RAM_WRITE]); 46 | 47 | /* 48 | * variable that can be used to let a DMA dummy transfer data 49 | */ 50 | volatile uint32_t _devNull; 51 | volatile uint32_t *_devNullPtr = &_devNull; 52 | 53 | struct DmaCommand { 54 | const volatile void *read_addr; 55 | volatile void *write_addr; 56 | }; 57 | 58 | /* clang-format off */ 59 | 60 | static volatile uint32_t LOWER_ROM_READ_DMA_CTRL = (DMA_CHANNEL_ROM_LOWER_REQUESTOR << DMA_CH2_CTRL_TRIG_CHAIN_TO_LSB) | DMA_CH2_CTRL_TRIG_HIGH_PRIORITY_BITS | DMA_CH2_CTRL_TRIG_EN_BITS | (DMA_CH2_CTRL_TRIG_TREQ_SEL_VALUE_PERMANENT << DMA_CH2_CTRL_TRIG_TREQ_SEL_LSB); 61 | static struct DmaCommand LOWER_ROM_READ[] = { 62 | { &LOWER_ROM_READ_DMA_CTRL, &(dma_hw->ch[DMA_CHANNEL_MEMORY_ACCESSOR].al1_ctrl) }, // load the settings for this transfer into the MEMORY_ACCESOR_DMA control register 63 | { &_txFifoWriteData, &(dma_hw->ch[DMA_CHANNEL_MEMORY_ACCESSOR].write_addr) }, // load the addr of the tx-fifo of the write data PIO-SM into the write register of MEMORY_ACCESSOR_DMA 64 | { &(pio0->rxf[SMC_GB_ROM_LOW]), &(dma_hw->ch[DMA_CHANNEL_MEMORY_ACCESSOR].read_addr) }, // load the addr from the rx-fifo of the PIO-SM triggering this transfer 65 | { &rom_low_base, hw_set_alias_untyped(&(dma_hw->ch[DMA_CHANNEL_MEMORY_ACCESSOR].al3_read_addr_trig)) }, // load the base addr, write it into the read-addr of the READ_DMA, or-ing it with the addr received and trigger the READ_DMA transfer 66 | { NULL, NULL} 67 | }; 68 | 69 | static volatile uint32_t RAM_READ_DMA_CTRL = (DMA_CHANNEL_RAM_READ_REQUESTOR << DMA_CH2_CTRL_TRIG_CHAIN_TO_LSB) | DMA_CH2_CTRL_TRIG_HIGH_PRIORITY_BITS | DMA_CH2_CTRL_TRIG_EN_BITS | (DMA_CH2_CTRL_TRIG_TREQ_SEL_VALUE_PERMANENT << DMA_CH2_CTRL_TRIG_TREQ_SEL_LSB); 70 | static struct DmaCommand RAM_READ[] = { 71 | { &RAM_READ_DMA_CTRL, &(dma_hw->ch[DMA_CHANNEL_MEMORY_ACCESSOR].al1_ctrl) }, // load the settings for this transfer into the MEMORY_ACCESOR_DMA control register 72 | { &_txFifoWriteData, &(dma_hw->ch[DMA_CHANNEL_MEMORY_ACCESSOR].write_addr) }, // load the addr of the tx-fifo of the write data PIO-SM into the write register of MEMORY_ACCESSOR_DMA 73 | { &(pio1->rxf[SMC_GB_RAM_READ]), &(dma_hw->ch[DMA_CHANNEL_MEMORY_ACCESSOR].read_addr) }, // load the addr from the rx-fifo of the PIO-SM triggering this transfer 74 | { &ram_base, hw_set_alias_untyped(&(dma_hw->ch[DMA_CHANNEL_MEMORY_ACCESSOR].al3_read_addr_trig)) }, // load the base addr, write it into the read-addr of the MEMORY_ACCESSOR_DMA, or-ing it with the addr received and trigger the MEMORY_ACCESSOR_DMA transfer 75 | { NULL, NULL} 76 | }; 77 | 78 | static volatile uint32_t RAM_WRITE_DMA_CTRL = (DMA_CHANNEL_RAM_WRITE_REQUESTOR << DMA_CH2_CTRL_TRIG_CHAIN_TO_LSB) | DMA_CH2_CTRL_TRIG_HIGH_PRIORITY_BITS | DMA_CH2_CTRL_TRIG_EN_BITS | (DREQ_PIO1_RX3 << DMA_CH2_CTRL_TRIG_TREQ_SEL_LSB); 79 | static struct DmaCommand RAM_WRITE[] = { 80 | { &RAM_WRITE_DMA_CTRL, &(dma_hw->ch[DMA_CHANNEL_MEMORY_ACCESSOR].al1_ctrl) }, // setup MEMORY_ACCESSOR_DMA for this write to RAM transaction 81 | { &(pio1->rxf[SMC_GB_RAM_WRITE]), &(dma_hw->ch[DMA_CHANNEL_MEMORY_ACCESSOR].write_addr) }, // load the addr from the rx-fifo of the PIO-SM triggering this transfer into the write addr of MEMORY_ACCESSOR_DMA 82 | { &ram_base, hw_set_alias_untyped(&(dma_hw->ch[DMA_CHANNEL_MEMORY_ACCESSOR].write_addr)) }, // load the base addr into the write addr of MEMORY_ACCESSOR_DMA, or-ing it with the addr already there (received from rx-fifo) 83 | { &_rxFifoRamWrite, &(dma_hw->ch[DMA_CHANNEL_MEMORY_ACCESSOR].al3_read_addr_trig) }, // load the addr of the rx-fifo which will have the data to be written to RAM into the read register of MEMORY_ACCESSOR_DMA and trigger it's transfer 84 | { NULL, NULL} 85 | }; 86 | 87 | struct DmaCommand RAM_DUMMY_READ[] = { 88 | { &RAM_READ_DMA_CTRL, &(dma_hw->ch[DMA_CHANNEL_MEMORY_ACCESSOR].al1_ctrl) }, // load the settings for this transfer into the MEMORY_ACCESOR_DMA control register 89 | { &_devNullPtr, &(dma_hw->ch[DMA_CHANNEL_MEMORY_ACCESSOR].write_addr) }, // load the addr of the tx-fifo of the write data PIO-SM into the write register of MEMORY_ACCESSOR_DMA 90 | { &(pio1->rxf[SMC_GB_RAM_READ]), &(dma_hw->ch[DMA_CHANNEL_MEMORY_ACCESSOR].read_addr) }, // load the addr from the rx-fifo of the PIO-SM triggering this transfer 91 | { &ram_base, hw_set_alias_untyped(&(dma_hw->ch[DMA_CHANNEL_MEMORY_ACCESSOR].al3_read_addr_trig)) }, // load the base addr, write it into the read-addr of the MEMORY_ACCESSOR_DMA, or-ing it with the addr received and trigger the MEMORY_ACCESSOR_DMA transfer 92 | { NULL, NULL} 93 | }; 94 | 95 | static struct DmaCommand RAM_DUMMY_WRITE[] = { 96 | { &RAM_WRITE_DMA_CTRL, &(dma_hw->ch[DMA_CHANNEL_MEMORY_ACCESSOR].al1_ctrl) }, // setup MEMORY_ACCESSOR_DMA for this write to RAM transaction 97 | { &(pio1->rxf[SMC_GB_RAM_WRITE]), &_devNull }, // load the addr from the rx-fifo of the PIO-SM triggering this transfer into devNUll 98 | { &_devNullPtr, &(dma_hw->ch[DMA_CHANNEL_MEMORY_ACCESSOR].write_addr) }, // load the addr of _devNull into write addr of MEMORY_ACCESSOR_DMA 99 | { &_rxFifoRamWrite, &(dma_hw->ch[DMA_CHANNEL_MEMORY_ACCESSOR].al3_read_addr_trig) }, // load the addr of the rx-fifo which will have the data to be written to RAM into the read register of MEMORY_ACCESSOR_DMA and trigger it's transfer 100 | { NULL, NULL} 101 | }; 102 | 103 | struct DmaCommand RTC_READ[] = { 104 | { &RAM_READ_DMA_CTRL, &(dma_hw->ch[DMA_CHANNEL_MEMORY_ACCESSOR].al1_ctrl) }, // load the settings for this transfer into the MEMORY_ACCESOR_DMA control register 105 | { &_txFifoWriteData, &(dma_hw->ch[DMA_CHANNEL_MEMORY_ACCESSOR].write_addr) }, // load the addr of the tx-fifo of the write data PIO-SM into the write register of MEMORY_ACCESSOR_DMA 106 | { &(pio1->rxf[SMC_GB_RAM_READ]), &_devNull }, // dummy read the addr from the rx-fifo of the PIO-SM triggering this transfer 107 | { &_rtcLatchPtr, &(dma_hw->ch[DMA_CHANNEL_MEMORY_ACCESSOR].al3_read_addr_trig) }, // load the current rtc register addr, write it into the read-addr of the MEMORY_ACCESSOR_DMA and trigger the MEMORY_ACCESSOR_DMA transfer 108 | { NULL, NULL} 109 | }; 110 | 111 | static struct DmaCommand RTC_WRITE[] = { 112 | { &RAM_WRITE_DMA_CTRL, &(dma_hw->ch[DMA_CHANNEL_MEMORY_ACCESSOR].al1_ctrl) }, // setup MEMORY_ACCESSOR_DMA for this write to RAM transaction 113 | { &(pio1->rxf[SMC_GB_RAM_WRITE]), &_devNull }, // dummy load the addr from the rx-fifo of the PIO-SM triggering this transfer 114 | { &_rtcRealPtr, &(dma_hw->ch[DMA_CHANNEL_MEMORY_ACCESSOR].write_addr) }, // load the current rtc register addr into the write addr of MEMORY_ACCESSOR_DMA 115 | { &_rxFifoRamWrite, &(dma_hw->ch[DMA_CHANNEL_MEMORY_ACCESSOR].al3_read_addr_trig) }, // load the addr of the rx-fifo which will have the data to be written to RAM into the read register of MEMORY_ACCESSOR_DMA and trigger it's transfer 116 | { NULL, NULL} 117 | }; 118 | 119 | /* clang-format on */ 120 | 121 | static volatile void *_lowerRomReadCommands = &LOWER_ROM_READ[0]; 122 | static volatile void *_ramReadCommands = &RAM_READ[0]; 123 | static volatile void *_ramWriteCommands = &RAM_WRITE[0]; 124 | 125 | static void setup_read_dma_method2(PIO pio, unsigned sm, PIO pio_write_data, 126 | unsigned sm_write_data, 127 | const volatile void *read_base_addr); 128 | 129 | void GbDma_Setup() { 130 | dma_channel_claim(DMA_CHANNEL_CMD_EXECUTOR); 131 | dma_channel_claim(DMA_CHANNEL_CMD_LOADER); 132 | dma_channel_claim(DMA_CHANNEL_MEMORY_ACCESSOR); 133 | // dma_channel_claim(DMA_CHANNEL_ROM_LOWER_REQUESTOR); 134 | dma_channel_claim(DMA_CHANNEL_RAM_READ_REQUESTOR); 135 | dma_channel_claim(DMA_CHANNEL_RAM_WRITE_REQUESTOR); 136 | 137 | /* 138 | * Setup the DMA which acts as the CMD_LOADER. It will be initially triggered 139 | * by one of the triggering DMAs. They are triggered by the PIO-SM DREQ and 140 | * will load the DmaCommand for the transaction into the the read_register of 141 | * this DMA. This DMA loads the current command into the CMD_EXECUTOR and 142 | * triggers it. It will be triggered again by the CMD_EXECUTOR until all 143 | * commands are executed (NULL-entry). 144 | */ 145 | dma_channel_config c = dma_channel_get_default_config(DMA_CHANNEL_CMD_LOADER); 146 | channel_config_set_transfer_data_size(&c, DMA_SIZE_32); 147 | channel_config_set_read_increment(&c, true); 148 | channel_config_set_write_increment(&c, true); 149 | channel_config_set_ring( 150 | &c, true, 3); // wrap the write every 2 words (after each command) 151 | dma_channel_configure(DMA_CHANNEL_CMD_LOADER, &c, 152 | &dma_hw->ch[DMA_CHANNEL_CMD_EXECUTOR] 153 | .al2_read_addr, // Initial write address 154 | NULL, // dummy will be replaced by requestor on dreq 155 | 2, // Halt after each control block 156 | false // Don't start yet 157 | ); 158 | 159 | /* 160 | * Setup the DMA which acts as the CMD_EXECUTOR: IT will receive it's commands 161 | * and triggers from CMD_LOADER. Only it's read and write address changes with 162 | * each command. Those are always 32-bit wide and only one word long. 163 | * CMD_EXECUTOR chains to CMD_LOADER to trigger the loading of the next 164 | * command. 165 | */ 166 | c = dma_channel_get_default_config(DMA_CHANNEL_CMD_EXECUTOR); 167 | channel_config_set_read_increment(&c, false); 168 | channel_config_set_transfer_data_size(&c, DMA_SIZE_32); 169 | // Trigger DMA_CHANNEL_CMD_LOADER needs to load the next command on finish 170 | channel_config_set_chain_to(&c, DMA_CHANNEL_CMD_LOADER); 171 | dma_channel_configure(DMA_CHANNEL_CMD_EXECUTOR, &c, 172 | NULL, // will be set by CMD_LOADER 173 | NULL, // will be set by CMD_LOADER 174 | 1, // always transfer one word 175 | false // Don't start yet. 176 | ); 177 | 178 | /* 179 | * The DMA wich acts as MEMORY_ACCESOR is completely setup and trigger by the 180 | * CMD_EXECUTOR based on the command chain received from CMD_LOADER. It's only 181 | * necessary the transfer count here, as everything else is done by the 182 | * commands. 183 | */ 184 | dma_channel_set_trans_count(DMA_CHANNEL_MEMORY_ACCESSOR, 1, false); 185 | 186 | /* 187 | * Setup all the REQUESTOR DMAs for each PIO-SM the DMAs above serve. Each 188 | * REQUSTOR DMA is setup to wait for a DREQ from a PIO state machine. After it 189 | * gets it's DREQ it loads the command chain needed to serve this request into 190 | * the CMD_LOADER and triggers it. This starts the whole chain of commands 191 | * which will eventually lead to the serving of the necessary memory transfer 192 | * from an into the FIFOs of the state machine. 193 | */ 194 | // dma_channel_get_default_config(DMA_CHANNEL_ROM_LOWER_REQUESTOR); 195 | // channel_config_set_read_increment(&c, false); 196 | // channel_config_set_transfer_data_size(&c, DMA_SIZE_32); 197 | // channel_config_set_dreq(&c, pio_get_dreq(pio0, SMC_GB_ROM_LOW, false)); 198 | // dma_channel_configure( 199 | // DMA_CHANNEL_ROM_LOWER_REQUESTOR, &c, 200 | // &(dma_hw->ch[DMA_CHANNEL_CMD_LOADER].al3_read_addr_trig), 201 | // &_lowerRomReadCommands, 202 | // 1, // always transfer one word (pointer) 203 | // true // trigger (wait for dreq) 204 | // ); 205 | 206 | setup_read_dma_method2(pio0, SMC_GB_ROM_LOW, pio0, SMC_GB_WRITE_DATA, 207 | &rom_low_base); 208 | 209 | dma_channel_get_default_config(DMA_CHANNEL_RAM_READ_REQUESTOR); 210 | channel_config_set_read_increment(&c, false); 211 | channel_config_set_transfer_data_size(&c, DMA_SIZE_32); 212 | channel_config_set_dreq(&c, pio_get_dreq(pio1, SMC_GB_RAM_READ, false)); 213 | dma_channel_configure( 214 | DMA_CHANNEL_RAM_READ_REQUESTOR, &c, 215 | &(dma_hw->ch[DMA_CHANNEL_CMD_LOADER].al3_read_addr_trig), 216 | &_ramReadCommands, 217 | 1, // always transfer one word (pointer) 218 | true // trigger (wait for dreq) 219 | ); 220 | 221 | dma_channel_get_default_config(DMA_CHANNEL_RAM_WRITE_REQUESTOR); 222 | channel_config_set_read_increment(&c, false); 223 | channel_config_set_transfer_data_size(&c, DMA_SIZE_32); 224 | channel_config_set_dreq(&c, pio_get_dreq(pio1, SMC_GB_RAM_WRITE, false)); 225 | dma_channel_configure( 226 | DMA_CHANNEL_RAM_WRITE_REQUESTOR, &c, 227 | &(dma_hw->ch[DMA_CHANNEL_CMD_LOADER].al3_read_addr_trig), 228 | &_ramWriteCommands, 229 | 1, // always transfer one word (pointer) 230 | true // trigger (wait for dreq) 231 | ); 232 | } 233 | 234 | void GbDma_SetupHigherDmaDirectSsi() { 235 | _dmaChannelRomHigherDirectSsiPioAddrLoader = dma_claim_unused_channel(true); 236 | _dmaChannelRomHigherDirectSsiBaseAddrLoader = dma_claim_unused_channel(true); 237 | _dmaChannelRomHigherDirectSsiFlashRequester = dma_claim_unused_channel(true); 238 | _dmaChannelRomHigherDirectSsiPioDataLoader = dma_claim_unused_channel(true); 239 | 240 | dma_channel_config c = dma_channel_get_default_config( 241 | _dmaChannelRomHigherDirectSsiPioAddrLoader); 242 | channel_config_set_read_increment(&c, false); 243 | channel_config_set_transfer_data_size(&c, DMA_SIZE_32); 244 | channel_config_set_dreq(&c, pio_get_dreq(pio0, SMC_GB_ROM_HIGH, false)); 245 | channel_config_set_chain_to(&c, _dmaChannelRomHigherDirectSsiBaseAddrLoader); 246 | dma_channel_configure(_dmaChannelRomHigherDirectSsiPioAddrLoader, &c, 247 | &(dma_hw->sniff_data), &(pio0->rxf[SMC_GB_ROM_HIGH]), 248 | 1, // always transfer one word (pointer) 249 | false // do not trigger yet, will be done after all the 250 | // other DMAs are setup 251 | ); 252 | 253 | c = dma_channel_get_default_config( 254 | _dmaChannelRomHigherDirectSsiBaseAddrLoader); 255 | channel_config_set_read_increment(&c, false); 256 | channel_config_set_transfer_data_size(&c, DMA_SIZE_32); 257 | channel_config_set_sniff_enable(&c, true); 258 | channel_config_set_chain_to(&c, _dmaChannelRomHigherDirectSsiFlashRequester); 259 | dma_channel_configure(_dmaChannelRomHigherDirectSsiBaseAddrLoader, &c, 260 | &_devNull, &rom_high_base_flash_direct, 261 | 1, // always transfer one word (pointer) 262 | false // will be triggered by PioAddrLoader 263 | ); 264 | dma_sniffer_enable(_dmaChannelRomHigherDirectSsiBaseAddrLoader, 265 | DMA_SNIFF_CTRL_CALC_VALUE_SUM, true); 266 | 267 | c = dma_channel_get_default_config( 268 | _dmaChannelRomHigherDirectSsiFlashRequester); 269 | channel_config_set_read_increment(&c, false); 270 | channel_config_set_transfer_data_size(&c, DMA_SIZE_32); 271 | channel_config_set_chain_to(&c, _dmaChannelRomHigherDirectSsiPioDataLoader); 272 | dma_channel_configure(_dmaChannelRomHigherDirectSsiFlashRequester, &c, 273 | &(ssi_hw->dr0), &(dma_hw->sniff_data), 274 | 1, // always transfer one word (pointer) 275 | false // will be triggered by BaseAddrLoader 276 | ); 277 | 278 | c = dma_channel_get_default_config( 279 | _dmaChannelRomHigherDirectSsiPioDataLoader); 280 | channel_config_set_read_increment(&c, false); 281 | channel_config_set_transfer_data_size(&c, DMA_SIZE_8); 282 | channel_config_set_dreq(&c, DREQ_XIP_SSIRX); 283 | channel_config_set_chain_to(&c, _dmaChannelRomHigherDirectSsiPioAddrLoader); 284 | dma_channel_configure(_dmaChannelRomHigherDirectSsiPioDataLoader, &c, 285 | &(pio0->txf[SMC_GB_WRITE_DATA]), &(ssi_hw->dr0), 286 | 1, // always transfer one byte 287 | false // will be triggered by FlashRequester 288 | ); 289 | 290 | // Do not trigger the first stage dma yet. This will be done only after SSI is 291 | // in the correct mode 292 | } 293 | 294 | void __no_inline_not_in_flash_func(GbDma_StartDmaDirect)() { 295 | dma_hw->multi_channel_trigger = 1 296 | << _dmaChannelRomHigherDirectSsiPioAddrLoader; 297 | } 298 | 299 | static void setup_read_dma_method2(PIO pio, unsigned sm, PIO pio_write_data, 300 | unsigned sm_write_data, 301 | const volatile void *read_base_addr) { 302 | unsigned dma1, dma2, dma3; 303 | dma_channel_config cfg; 304 | 305 | dma1 = dma_claim_unused_channel(true); 306 | dma2 = dma_claim_unused_channel(true); 307 | dma3 = dma_claim_unused_channel(true); 308 | 309 | // Set up DMA2 first (it's not triggered until DMA1 does so) 310 | cfg = dma_channel_get_default_config(dma2); 311 | channel_config_set_read_increment(&cfg, false); 312 | // write increment defaults to false 313 | // dreq defaults to DREQ_FORCE 314 | channel_config_set_transfer_data_size(&cfg, DMA_SIZE_8); 315 | channel_config_set_high_priority(&cfg, true); 316 | dma_channel_set_trans_count(dma2, 1, false); 317 | dma_channel_set_write_addr(dma2, &(pio_write_data->txf[sm_write_data]), 318 | false); 319 | channel_config_set_chain_to(&cfg, dma1); 320 | dma_channel_set_config(dma2, &cfg, false); 321 | 322 | // Set up DMA1 and trigger it 323 | cfg = dma_channel_get_default_config(dma1); 324 | channel_config_set_read_increment(&cfg, false); 325 | // write increment defaults to false 326 | channel_config_set_dreq(&cfg, pio_get_dreq(pio, sm, false)); 327 | // transfer size defaults to 32 328 | channel_config_set_high_priority(&cfg, true); 329 | dma_channel_set_trans_count(dma1, 1, false); 330 | dma_channel_set_read_addr(dma1, &(pio->rxf[sm]), false); 331 | dma_channel_set_write_addr(dma1, &(dma_hw->ch[dma2].read_addr), false); 332 | channel_config_set_chain_to(&cfg, dma3); 333 | dma_channel_set_config(dma1, &cfg, true); 334 | 335 | cfg = dma_channel_get_default_config(dma3); 336 | channel_config_set_read_increment(&cfg, false); 337 | // write increment defaults to false 338 | // dreq defaults to DREQ_FORCE 339 | // transfer size defaults to 32 340 | channel_config_set_high_priority(&cfg, true); 341 | dma_channel_set_trans_count(dma3, 1, false); 342 | dma_channel_set_read_addr(dma3, read_base_addr, false); 343 | dma_channel_set_write_addr( 344 | dma3, hw_set_alias_untyped(&(dma_hw->ch[dma2].al3_read_addr_trig)), 345 | false); 346 | // channel_config_set_chain_to(&cfg, dma2); 347 | dma_channel_set_config(dma3, &cfg, false); 348 | } 349 | 350 | void __no_inline_not_in_flash_func(GbDma_EnableSaveRam)() { 351 | _ramReadCommands = &RAM_READ[0]; 352 | _ramWriteCommands = &RAM_WRITE[0]; 353 | } 354 | 355 | void __no_inline_not_in_flash_func(GbDma_DisableSaveRam)() { 356 | _ramReadCommands = &RAM_DUMMY_READ[0]; 357 | _ramWriteCommands = &RAM_DUMMY_WRITE[0]; 358 | } 359 | 360 | void __no_inline_not_in_flash_func(GbDma_EnableRtc)() { 361 | _ramReadCommands = &RTC_READ[0]; 362 | _ramWriteCommands = &RAM_DUMMY_WRITE[0]; // write needs special handling 363 | } 364 | -------------------------------------------------------------------------------- /GbDma.h: -------------------------------------------------------------------------------- 1 | /* RP2040 GameBoy cartridge 2 | * Copyright (C) 2024 Sebastian Quilitz 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #ifndef D2C8524D_9F5F_4D9E_BD87_3A37DED846AC 19 | #define D2C8524D_9F5F_4D9E_BD87_3A37DED846AC 20 | 21 | #include 22 | 23 | void GbDma_Setup(); 24 | void GbDma_SetupHigherDmaDirectSsi(); 25 | 26 | void GbDma_StartDmaDirect(); 27 | 28 | void GbDma_EnableSaveRam(); 29 | void GbDma_DisableSaveRam(); 30 | void GbDma_EnableRtc(); 31 | 32 | #endif /* D2C8524D_9F5F_4D9E_BD87_3A37DED846AC */ 33 | -------------------------------------------------------------------------------- /GbRtc.c: -------------------------------------------------------------------------------- 1 | #include "GbRtc.h" 2 | #include "GlobalDefines.h" 3 | #include 4 | 5 | #include 6 | 7 | #define SECS_PER_MIN 60UL 8 | #define SECS_PER_HOUR 3600UL 9 | #define SECS_PER_DAY (SECS_PER_HOUR * 24UL) 10 | #define SECS_PER_YEAR (SECS_PER_DAY * 365UL) 11 | #define SECS_YR_2000 946684800UL /* the time at the start of y2k */ 12 | 13 | // leap year calculator expects year argument as years offset from 1970 14 | #define LEAP_YEAR(Y) \ 15 | (((1970 + (Y)) > 0) && !((1970 + (Y)) % 4) && \ 16 | (((1970 + (Y)) % 100) || !((1970 + (Y)) % 400))) 17 | 18 | static volatile uint8_t _registerMasks[] = {0x3f, 0x3f, 0x1f, 0xff, 0xc1}; 19 | static uint8_t _currentRegister = 0; 20 | 21 | static uint64_t _lastMilli = 0; 22 | static uint32_t _millies = 0; 23 | 24 | static uint64_t _lastTimestampTick = 0; 25 | 26 | static inline void GbRtc_processTick(); 27 | 28 | void __no_inline_not_in_flash_func(GbRtc_WriteRegister)(uint8_t val) { 29 | const uint8_t oldHalt = g_rtcReal.reg.status.halt; 30 | 31 | g_rtcReal.asArray[_currentRegister] = val & _registerMasks[_currentRegister]; 32 | 33 | if (_currentRegister == 0) { 34 | _lastMilli = time_us_64(); 35 | _millies = 0; 36 | } 37 | 38 | if (oldHalt && !g_rtcReal.reg.status.halt) { 39 | _lastMilli = time_us_64(); 40 | } 41 | } 42 | 43 | void __no_inline_not_in_flash_func(GbRtc_ActivateRegister)(uint8_t reg) { 44 | if (reg >= sizeof(_registerMasks)) { 45 | return; 46 | } 47 | 48 | _rtcLatchPtr = &g_rtcLatched.asArray[reg]; 49 | _currentRegister = reg; 50 | } 51 | 52 | void __no_inline_not_in_flash_func(GbRtc_PerformRtcTick)() { 53 | uint64_t now = time_us_64(); 54 | 55 | if (!g_rtcReal.reg.status.halt) { 56 | if ((now - _lastMilli) > 1000U) { 57 | _lastMilli += 1000; 58 | _millies++; 59 | } 60 | 61 | if (_millies >= 1000) { 62 | _millies = 0; 63 | GbRtc_processTick(); 64 | } 65 | } 66 | 67 | if ((now - _lastTimestampTick) > 1000000U) { 68 | g_rtcTimestamp++; 69 | _lastTimestampTick = now; 70 | } 71 | } 72 | 73 | static inline void GbRtc_processTick() { 74 | uint8_t registerToMask = 0; 75 | 76 | g_rtcReal.reg.seconds++; 77 | g_rtcReal.asArray[registerToMask] &= _registerMasks[registerToMask]; 78 | registerToMask++; 79 | 80 | if (g_rtcReal.reg.seconds == 60) { 81 | g_rtcReal.reg.seconds = 0; 82 | g_rtcReal.reg.minutes++; 83 | 84 | g_rtcReal.asArray[registerToMask] &= _registerMasks[registerToMask]; 85 | registerToMask++; 86 | 87 | if (g_rtcReal.reg.minutes == 60) { 88 | g_rtcReal.reg.minutes = 0; 89 | g_rtcReal.reg.hours++; 90 | 91 | g_rtcReal.asArray[registerToMask] &= _registerMasks[registerToMask]; 92 | registerToMask++; 93 | } 94 | 95 | if (g_rtcReal.reg.hours == 24) { 96 | g_rtcReal.reg.hours = 0; 97 | g_rtcReal.reg.days++; 98 | 99 | g_rtcReal.asArray[registerToMask] &= _registerMasks[registerToMask]; 100 | registerToMask++; 101 | 102 | if (g_rtcReal.reg.days == 0) { 103 | if (g_rtcReal.reg.status.days_high) { 104 | g_rtcReal.reg.status.days_carry = 1; 105 | } 106 | g_rtcReal.reg.status.days_high++; 107 | } 108 | } 109 | } 110 | } 111 | 112 | void GbRtc_advanceToNewTimestamp(uint64_t newTimestamp) { 113 | uint64_t diff; 114 | if (newTimestamp > g_rtcTimestamp) { 115 | diff = newTimestamp - g_rtcTimestamp; 116 | 117 | while (diff > 0) { 118 | GbRtc_processTick(); 119 | diff--; 120 | } 121 | } 122 | 123 | g_rtcTimestamp = newTimestamp; 124 | 125 | _millies = 0; 126 | } 127 | 128 | static const uint8_t monthDays[] = {31, 28, 31, 30, 31, 30, 129 | 31, 31, 30, 31, 30, 31}; 130 | 131 | // taken from https://github.com/PaulStoffregen/Time/blob/master/Time.cpp 132 | uint64_t makeTime(const struct TimePoint *tp) { 133 | // note year argument is offset from 1970 (see macros in time.h to convert to 134 | // other formats) 135 | int i; 136 | uint64_t seconds; 137 | 138 | // seconds from 1970 till 1 jan 00:00:00 of the given year 139 | seconds = tp->Year * (SECS_PER_DAY * 365); 140 | for (i = 0; i < tp->Year; i++) { 141 | if (LEAP_YEAR(i)) { 142 | seconds += SECS_PER_DAY; // add extra days for leap years 143 | } 144 | } 145 | 146 | // add days for this year, months start from 1 147 | for (i = 0; i < tp->Month; i++) { 148 | if ((i == 1) && LEAP_YEAR(tp->Year)) { 149 | seconds += SECS_PER_DAY * 29; 150 | } else { 151 | seconds += SECS_PER_DAY * monthDays[i]; 152 | } 153 | } 154 | seconds += (tp->Day) * SECS_PER_DAY; 155 | seconds += tp->Hour * SECS_PER_HOUR; 156 | seconds += tp->Minute * SECS_PER_MIN; 157 | seconds += tp->Second; 158 | return seconds; 159 | } 160 | void breakTime(uint64_t timeInput, struct TimePoint *tp) { 161 | // break the given timestamp into components 162 | // this is a more compact version of the C library localtime function 163 | // note that year is offset from 1970 !!! 164 | 165 | uint8_t year; 166 | uint8_t month, monthLength; 167 | uint32_t time; 168 | unsigned long days; 169 | 170 | time = (uint32_t)timeInput; 171 | tp->Second = time % 60; 172 | time /= 60; // now it is minutes 173 | tp->Minute = time % 60; 174 | time /= 60; // now it is hours 175 | tp->Hour = time % 24; 176 | time /= 24; // now it is days 177 | 178 | year = 0; 179 | days = 0; 180 | while ((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) { 181 | year++; 182 | } 183 | tp->Year = year; // year is offset from 1970 184 | 185 | days -= LEAP_YEAR(year) ? 366 : 365; 186 | time -= days; // now it is days in this year, starting at 0 187 | 188 | days = 0; 189 | month = 0; 190 | monthLength = 0; 191 | for (month = 0; month < 12; month++) { 192 | if (month == 1) { // february 193 | if (LEAP_YEAR(year)) { 194 | monthLength = 29; 195 | } else { 196 | monthLength = 28; 197 | } 198 | } else { 199 | monthLength = monthDays[month]; 200 | } 201 | 202 | if (time >= monthLength) { 203 | time -= monthLength; 204 | } else { 205 | break; 206 | } 207 | } 208 | tp->Month = month; 209 | tp->Day = time; 210 | } 211 | -------------------------------------------------------------------------------- /GbRtc.h: -------------------------------------------------------------------------------- 1 | #ifndef ADCD92E4_74E6_47EB_87C7_018CDAC9B005 2 | #define ADCD92E4_74E6_47EB_87C7_018CDAC9B005 3 | 4 | #include 5 | 6 | struct __attribute__((packed)) TimePoint { 7 | uint8_t Second; 8 | uint8_t Minute; 9 | uint8_t Hour; 10 | uint8_t Day; 11 | uint8_t Month; 12 | uint8_t Year; // offset from 1970; 13 | }; 14 | 15 | void GbRtc_WriteRegister(uint8_t val); 16 | void GbRtc_ActivateRegister(uint8_t reg); 17 | void GbRtc_PerformRtcTick(); 18 | void GbRtc_advanceToNewTimestamp(uint64_t newTimestamp); 19 | 20 | uint64_t makeTime(const struct TimePoint *tp); 21 | void breakTime(uint64_t timeInput, struct TimePoint *tp); 22 | 23 | #endif /* ADCD92E4_74E6_47EB_87C7_018CDAC9B005 */ 24 | -------------------------------------------------------------------------------- /GlobalDefines.h: -------------------------------------------------------------------------------- 1 | /* RP2040 GameBoy cartridge 2 | * Copyright (C) 2023 Sebastian Quilitz 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #ifndef A6E4EABE_18C1_4BCB_A021_7C59DEE53104 19 | #define A6E4EABE_18C1_4BCB_A021_7C59DEE53104 20 | 21 | #include 22 | #include 23 | 24 | #define WS2812_PIN 27 25 | 26 | #define PIN_GB_RESET 26 27 | #define PIN_GB_BUS_EN 29 28 | 29 | #define SMC_GB_MAIN 0 30 | #define SMC_GB_DETECT_A14 1 31 | #define SMC_GB_RAM_READ 2 32 | #define SMC_GB_RAM_WRITE 3 33 | 34 | #define SMC_GB_ROM_LOW 0 35 | #define SMC_GB_ROM_HIGH 1 36 | #define SMC_GB_WRITE_DATA 2 37 | #define SMC_GB_A15LOW_A14IRQS 3 38 | 39 | #define GB_MBC2_RAM_SIZE 0x200U 40 | #define GB_RAM_BANK_SIZE 0x2000U 41 | #define GB_ROM_BANK_SIZE 0x4000U 42 | /* 16 banks = 128K of RAM enough for MBC3 (32K) and MBC5*/ 43 | #define GB_MAX_RAM_BANKS 16 44 | 45 | #define ROM_STORAGE_FLASH_START_ADDR 0x00020000 46 | #define MAX_BANKS 888 47 | #define MAX_BANKS_PER_ROM 0x200 48 | 49 | /* 50 | * With the current implementation the list of ROMs can 51 | * use 4k of the saveram. With 20 characters per string 52 | * that gives around 200 possible games. Use 180 to be 53 | * save as also other information is stored in that area. 54 | */ 55 | #define MAX_ALLOWED_ROMS 180 56 | 57 | extern const volatile uint8_t *volatile ram_base; 58 | extern const volatile uint8_t *volatile rom_low_base; 59 | extern volatile uint32_t rom_high_base_flash_direct; 60 | 61 | extern volatile uint8_t *_rtcLatchPtr; 62 | extern volatile uint8_t *_rtcRealPtr; 63 | 64 | extern uint8_t memory[]; 65 | extern uint8_t ram_memory[]; 66 | extern uint8_t memory_vblank_hook_bank[]; 67 | extern uint8_t memory_vblank_hook_bank2[]; 68 | 69 | struct RomInfo { 70 | const uint8_t *firstBank; 71 | uint16_t speedSwitchBank; 72 | uint8_t numRamBanks; 73 | uint8_t mbc; 74 | uint16_t numRomBanks; 75 | char name[17]; 76 | }; 77 | 78 | extern uint8_t g_numRoms; 79 | extern bool g_hardwareSupportsDoubleSpeed; 80 | 81 | extern const uint8_t *g_loadedRomBanks[MAX_BANKS_PER_ROM]; 82 | extern uint32_t g_loadedDirectAccessRomBanks[MAX_BANKS_PER_ROM]; 83 | extern struct RomInfo g_loadedRomInfo; 84 | 85 | void setSsi8bit(); 86 | void setSsi32bit(); 87 | void loadDoubleSpeedPio(uint16_t bank, uint16_t addr); 88 | void storeSaveRamToFile(const struct RomInfo *shortRomInfo); 89 | void restoreSaveRamFromFile(const struct RomInfo *shortRomInfo); 90 | int restoreRtcFromFile(const struct RomInfo *romInfo); 91 | void storeRtcToFile(const struct RomInfo *romInfo); 92 | 93 | struct __attribute__((packed)) GbRtc { 94 | uint8_t seconds; 95 | uint8_t minutes; 96 | uint8_t hours; 97 | uint8_t days; 98 | union { 99 | struct { 100 | uint8_t days_high : 1; 101 | uint8_t reserved : 5; 102 | uint8_t halt : 1; 103 | uint8_t days_carry : 1; 104 | }; 105 | uint8_t asByte; 106 | } status; 107 | }; 108 | union GbRtcUnion { 109 | struct GbRtc reg; 110 | uint8_t asArray[5]; 111 | }; 112 | 113 | extern volatile union GbRtcUnion g_rtcReal; 114 | extern volatile union GbRtcUnion g_rtcLatched; 115 | extern uint64_t g_rtcTimestamp; 116 | extern uint64_t g_globalTimestamp; 117 | extern uint8_t g_flashSerialNumber[8]; 118 | extern char g_serialNumberString[]; 119 | 120 | /* taken from 121 | * https://github.com/tihmstar/libgeneral/blob/master/include/libgeneral/macros.h.in 122 | */ 123 | #define ASSURE(a) \ 124 | do { \ 125 | if ((a) == 0) { \ 126 | err = -(__LINE__); \ 127 | goto error; \ 128 | } \ 129 | } while (0) 130 | 131 | #define PRINTASSURE(cond, errstr...) \ 132 | do { \ 133 | if ((cond) == 0) { \ 134 | err = -(__LINE__); \ 135 | printf(errstr); \ 136 | goto error; \ 137 | } \ 138 | } while (0) 139 | 140 | #endif /* A6E4EABE_18C1_4BCB_A021_7C59DEE53104 */ 141 | -------------------------------------------------------------------------------- /MyBoard.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. 3 | * 4 | * SPDX-License-Identifier: BSD-3-Clause 5 | */ 6 | 7 | // ----------------------------------------------------- 8 | // NOTE: THIS HEADER IS ALSO INCLUDED BY ASSEMBLER SO 9 | // SHOULD ONLY CONSIST OF PREPROCESSOR DIRECTIVES 10 | // ----------------------------------------------------- 11 | 12 | // This header may be included by other board headers as "boards/pico.h" 13 | 14 | #ifndef _BOARDS_PICO_H 15 | #define _BOARDS_PICO_H 16 | 17 | // For board detection 18 | //#define RASPBERRYPI_PICO 19 | 20 | // --- UART --- 21 | #ifndef PICO_DEFAULT_UART 22 | #define PICO_DEFAULT_UART 0 23 | #endif 24 | #ifndef PICO_DEFAULT_UART_TX_PIN 25 | #define PICO_DEFAULT_UART_TX_PIN 0 26 | #endif 27 | #ifndef PICO_DEFAULT_UART_RX_PIN 28 | #define PICO_DEFAULT_UART_RX_PIN 1 29 | #endif 30 | 31 | // --- LED --- 32 | #ifndef PICO_DEFAULT_LED_PIN 33 | #define PICO_DEFAULT_LED_PIN 25 34 | #endif 35 | // no PICO_DEFAULT_WS2812_PIN 36 | 37 | // --- I2C --- 38 | #ifndef PICO_DEFAULT_I2C 39 | #define PICO_DEFAULT_I2C 0 40 | #endif 41 | #ifndef PICO_DEFAULT_I2C_SDA_PIN 42 | #define PICO_DEFAULT_I2C_SDA_PIN 4 43 | #endif 44 | #ifndef PICO_DEFAULT_I2C_SCL_PIN 45 | #define PICO_DEFAULT_I2C_SCL_PIN 5 46 | #endif 47 | 48 | // --- SPI --- 49 | #ifndef PICO_DEFAULT_SPI 50 | #define PICO_DEFAULT_SPI 0 51 | #endif 52 | #ifndef PICO_DEFAULT_SPI_SCK_PIN 53 | #define PICO_DEFAULT_SPI_SCK_PIN 18 54 | #endif 55 | #ifndef PICO_DEFAULT_SPI_TX_PIN 56 | #define PICO_DEFAULT_SPI_TX_PIN 19 57 | #endif 58 | #ifndef PICO_DEFAULT_SPI_RX_PIN 59 | #define PICO_DEFAULT_SPI_RX_PIN 16 60 | #endif 61 | #ifndef PICO_DEFAULT_SPI_CSN_PIN 62 | #define PICO_DEFAULT_SPI_CSN_PIN 17 63 | #endif 64 | 65 | // --- FLASH --- 66 | 67 | #define PICO_BOOT_STAGE2_CHOOSE_W25Q080 1 68 | 69 | #ifndef PICO_FLASH_SPI_CLKDIV 70 | #define PICO_FLASH_SPI_CLKDIV 2 71 | #endif 72 | 73 | #ifndef PICO_FLASH_SIZE_BYTES 74 | #define PICO_FLASH_SIZE_BYTES (16 * 1024 * 1024) 75 | #endif 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Firmware for the RP2040 based Gameboy cartridge 2 | Utilizing the power of PIO to create a cheap flashcartridge for the gameboy 3 | 4 | ## How to put ROMs on the cartridge 5 | Connect the cartridge via USB with a PC. Use Chromium or Chrome browser (only those two support WebUSB atm) and open the [Webapp](https://croco.x-pantion.de) to connect to the cartridge. 6 | 7 | ## How to build 8 | Open repository in VS Code with the [Dev Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) 9 | addon installed. VS Code should ask if it shall open the workspace in the devcontainer. 10 | 11 | Open terminal in VS Code and build with CMake: 12 | ```mkdir build 13 | cd build 14 | cmake .. 15 | make 16 | ``` 17 | 18 | Alternatively install the PICO-SDK somewhere on your system and use CMake to build as above. 19 | 20 | ## How to flash the firmware 21 | Double tap the reset button. It should bring up the cartridge in the Pico Bootloader. Copy the UF2 file on to the virtual drive. 22 | 23 | Alternatively fire the cartridge up in a Gameboy. In the ROM selector menu press SELECT (opens info screen) and press START on that screen. 24 | This will also trigger the cartridge to the build in RP2040 bootloader mode. 25 | 26 | ## How does it work? Short answer please 27 | The RP2040 has 8 PIO state-machines which are like small co-processors which can run arbitrary code. They are designed to be efficient in IO operations. 28 | Which is what they are mostly doing in this cartridge. They follow the Gameboy cartridge parallel bus interface and detect if the cartridge needs 29 | to spit out or receive some data. Those requests are forwarded to the ARM core through the FIFOs. There a cascade of DMAs will happily handle those 30 | requests. To put this in the correct view: The RP2040 has 12 DMA channels and 8 PIO state-machine. The code in this repository uses all of those. 31 | Well, one of the PIO state-machines is used to drive the LED. 32 | 33 | ## How do savegames work? 34 | In original cartridges the savegames had been stored in a RAM which was using a coin cell battery to hold the data while the gameboy is switched off. 35 | This has pros and cons. One of the biggest cons is that the savegame is lost if the battery dies. There was a big discussion on heartbroken users 36 | as the savegame of their beloved childhood Pokemon Blue/Red/Yellow was gone after 20 years. One has to remember that they started selling in 1998. 37 | The biggest pro on the savegame RAM is that the save is instantaneous. And that is what the games expect. They do not wait for the save to be done. 38 | 39 | Why this explanation? The QSPI flash on the RP2040 cartridge might not have those battery limitations on one side, but writing something to a flash 40 | needs to follow a procedure of slowly erasing and then writing. Kinda crazy eh? 30 years later and our new tech is to slow to store things in time. 41 | 42 | There is a RGB LED on the cartridge. If it starts lighting in red that means the Gameboy has written something to the emulated RAM on the cartridge. 43 | Sadly most games do this already at startup although the savegame is not changed at all. So the LED is merely a reminder that the user needs to save 44 | the game. In order to do this the user of the RP2040 cartridge has to hit the button which is on the cartridge. This will reset the cartridge and on 45 | startup it will find the unsaved data and write it to the flash. The LED will light green to confirm this has happened. 46 | 47 | There is also the possibility to manage the savegames via the WebUSB interface. 48 | 49 | I have the idea to write some mechanism to hook into the VBlank interrupt of the Gameboy ROM to store the savegame with some Gameboy button 50 | combination. That would ease up things as no reset would be needed. But this is just an plan/idea for now. 51 | 52 | ## Known limitations 53 | - Gameboy Color games in double speed mode currently do not work on the GBA. Timings are very tight in this mode and it's unsure if there is ever 54 | a solution to this problem. It works fine on the normal GBC though. (Tested on 3 different consoles) 55 | - Not all Gameboy Color ROMs are guaranteed to work. In theory all ROMs should work, but in practice the the cartridge has to detect if the GBC 56 | switched to double speed mode. This algorithm is not perfect and might need some adjustment. If a ROM is not working drop a hint through issues. 57 | - As the RP2040 needs to be overclocked to achieve fastest reading speeds from the flash the power draw is higher than from an orignal cartridge. 58 | This could mean the power supply of the original Gameboy can't handle the cartridge. Especially if combined with a fancy IPS screen. 59 | But if you have installed an IPS screen you should consider upgrading the power supply anyway to get rid of the noise on the speakers. 60 | 61 | ## What else can it do? 62 | Well, in the end this is open to imagination. The Gameboy has got a powerful co-processor which has an USB interface. Maybe use the Gameboy as 63 | a controller? An interesting example is the actually the Bootloader of this project. The RP2040 and the Bootloader communicate via shared RAM. 64 | 128k of the RP2040 RAM are shared with the Gameboy. All the heavy lifting is done by the PIO and DMAs. The second core of the RP2040 is completely 65 | unused, the first core is only switching banks while a Gameboy game is running. Lots of unused CPU power. 66 | 67 | ## Does LSDJ work? 68 | Yes. 128k saves are supported. With the newest releases it also runs on the GBC. 69 | 70 | ## How can I get the needed hardware? 71 | Find the Kicad project files in this [repo](https://github.com/shilga/rp2040-gameboy-cartridge). You can also order a pre-build cartridge over at 72 | [RetroReiZ](https://retroreiz.de/shop/retro-emulators/handhelds/croco-cartridge-multi-flashcartridge-fuer-gameboy-classic-color-roms/) who offer a 73 | lot of other useful Gameboy mods. You can also get one from [Tindie](https://www.tindie.com/products/32710/). 74 | 75 | ## Discord 76 | Find the Discord server to get help or discuss about this project. 77 | -------------------------------------------------------------------------------- /RomStorage.c: -------------------------------------------------------------------------------- 1 | /* RP2040 GameBoy cartridge 2 | * Copyright (C) 2023 Sebastian Quilitz 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "RomStorage.h" 19 | 20 | #include "GameBoyHeader.h" 21 | 22 | #include "lfs.h" 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | #include 33 | #include 34 | 35 | #include "GlobalDefines.h" 36 | #include "lfs_pico_hal.h" 37 | 38 | #define SetBit(A, k) (A[(k) / 32] |= (1 << ((k) % 32))) 39 | #define ClearBit(A, k) (A[(k) / 32] &= ~(1 << ((k) % 32))) 40 | #define TestBit(A, k) (A[(k) / 32] & (1 << ((k) % 32))) 41 | 42 | #define RomBankToPointer(BANK) \ 43 | ((const uint8_t *)((BANK * GB_ROM_BANK_SIZE) + \ 44 | ROM_STORAGE_FLASH_START_ADDR + 0x13000000)) 45 | 46 | #define RomBankToDirectSsi(BANK) \ 47 | ((((BANK * GB_ROM_BANK_SIZE) + ROM_STORAGE_FLASH_START_ADDR) << 8) | 0xA0) 48 | 49 | #define TRANSFER_CHUNK_SIZE 32 50 | #define CHUNKS_PER_BANK (GB_ROM_BANK_SIZE / TRANSFER_CHUNK_SIZE) 51 | 52 | #define ROMINFO_FILE_MAGIC 0xCAFEBABE 53 | 54 | static lfs_t *_lfs = NULL; 55 | static uint8_t _lfsFileBuffer[LFS_CACHE_SIZE]; 56 | struct lfs_file_config _fileconfig = {.buffer = _lfsFileBuffer}; 57 | lfs_file_t _ramTransferFile; 58 | 59 | static uint32_t 60 | _usedBanksFlags[28]; // 888 banks, 32 per uint32, 1 bit for each bank 61 | static uint16_t _usedBanks = 0; 62 | 63 | struct __attribute__((__packed__)) RomInfoFile { 64 | uint32_t magic; 65 | char name[17]; 66 | uint16_t numBanks; 67 | uint16_t speedSwitchBank; 68 | uint16_t banks[MAX_BANKS_PER_ROM]; 69 | } _romInfoFile; 70 | 71 | static uint8_t _bankBuffer[GB_ROM_BANK_SIZE]; 72 | static uint16_t _lastTransferredBank = 0xFFFF; 73 | static uint16_t _lastTransferredChunk = 0xFFFF; 74 | static char _fileNameBuffer[25] = "/roms/"; 75 | static char _filenamebufferSaves[40] = "saves/"; 76 | 77 | static size_t _ramBytesTransferred = 0; 78 | static size_t _ramBytesToTransfer = 0; 79 | 80 | static bool _romTransferActive = false; 81 | static bool _ramTransferActive = false; 82 | 83 | static int readRomInfoFile(lfs_file_t *file) { 84 | int lfs_err = 0; 85 | lfs_err = lfs_file_read(_lfs, file, &_romInfoFile, 86 | offsetof(struct RomInfoFile, speedSwitchBank)); 87 | if (lfs_err != offsetof(struct RomInfoFile, speedSwitchBank)) { 88 | printf("Error reading header %d\n", lfs_err); 89 | return lfs_err; 90 | } 91 | 92 | if (_romInfoFile.magic == ROMINFO_FILE_MAGIC) { 93 | lfs_err = lfs_file_read(_lfs, file, &_romInfoFile.speedSwitchBank, 94 | sizeof(uint16_t)); 95 | } else { 96 | _romInfoFile.speedSwitchBank = 0xFFFFU; 97 | } 98 | 99 | lfs_err = lfs_file_read(_lfs, file, &_romInfoFile.banks, 100 | _romInfoFile.numBanks * sizeof(uint16_t)); 101 | if (lfs_err != _romInfoFile.numBanks * sizeof(uint16_t)) { 102 | printf("Error reading banks %d\n", lfs_err); 103 | return lfs_err; 104 | } 105 | 106 | return 0; 107 | } 108 | 109 | int RomStorage_init(lfs_t *lfs) { 110 | int err = 0; 111 | lfs_dir_t dir = {}; 112 | lfs_file_t file = {}; 113 | struct lfs_info lfsInfo = {}; 114 | int lfs_err = 0; 115 | 116 | _lfs = lfs; 117 | 118 | memset(_usedBanksFlags, 0, sizeof(_usedBanksFlags)); 119 | g_numRoms = 0; 120 | _usedBanks = 0; 121 | 122 | lfs_err = lfs_mkdir(_lfs, "/roms"); 123 | PRINTASSURE((lfs_err == LFS_ERR_OK) || (lfs_err == LFS_ERR_EXIST), 124 | "Error creating roms directory %d\n", lfs_err); 125 | 126 | lfs_err = lfs_dir_open(_lfs, &dir, "/roms"); 127 | ASSURE(lfs_err == LFS_ERR_OK); 128 | 129 | lfs_err = lfs_dir_read(_lfs, &dir, &lfsInfo); 130 | while (lfs_err > 0) { 131 | if (lfsInfo.type == LFS_TYPE_REG) { 132 | char filename[32]; 133 | snprintf(filename, sizeof(filename), "/roms/%s", lfsInfo.name); 134 | printf("Reading %s\n", filename); 135 | lfs_err = 136 | lfs_file_opencfg(_lfs, &file, filename, LFS_O_RDONLY, &_fileconfig); 137 | PRINTASSURE(lfs_err == LFS_ERR_OK, "Error opening file %d\n", lfs_err); 138 | 139 | lfs_err = readRomInfoFile(&file); 140 | lfs_file_close(_lfs, &file); 141 | ASSURE(lfs_err == LFS_ERR_OK); 142 | 143 | for (size_t i = 0; i < _romInfoFile.numBanks; i++) { 144 | SetBit(_usedBanksFlags, _romInfoFile.banks[i]); 145 | _usedBanks++; 146 | } 147 | 148 | printf("Added %d used banks\n", _romInfoFile.numBanks); 149 | g_numRoms++; 150 | } 151 | 152 | lfs_err = lfs_dir_read(_lfs, &dir, &lfsInfo); 153 | } 154 | 155 | printf("%d banks of 888 in use\n", _usedBanks); 156 | 157 | error: 158 | lfs_dir_close(_lfs, &dir); 159 | return err; 160 | } 161 | 162 | int RomStorage_loadRomInfo(uint32_t rom, struct RomInfo *outRomInfo) { 163 | int err = 0; 164 | lfs_dir_t dir = {}; 165 | lfs_file_t file = {}; 166 | struct lfs_info lfsInfo = {}; 167 | int lfs_err = 0; 168 | bool romFound = false; 169 | 170 | ASSURE(rom < g_numRoms); 171 | 172 | lfs_err = lfs_dir_open(_lfs, &dir, "/roms"); 173 | ASSURE(lfs_err == LFS_ERR_OK); 174 | 175 | lfs_err = lfs_dir_read(_lfs, &dir, &lfsInfo); 176 | while ((lfs_err > 0) && !romFound) { 177 | if (lfsInfo.type == LFS_TYPE_REG && rom-- == 0) { 178 | char filename[32]; 179 | snprintf(filename, sizeof(filename), "/roms/%s", lfsInfo.name); 180 | printf("RomStorage_loadRomInfo %s\n", filename); 181 | lfs_err = 182 | lfs_file_opencfg(_lfs, &file, filename, LFS_O_RDONLY, &_fileconfig); 183 | PRINTASSURE(lfs_err == LFS_ERR_OK, "Error opening file %d\n", lfs_err); 184 | 185 | lfs_err = readRomInfoFile(&file); 186 | 187 | lfs_file_close(_lfs, &file); 188 | ASSURE(lfs_err == LFS_ERR_OK); 189 | 190 | memcpy(outRomInfo->name, lfsInfo.name, 16); 191 | outRomInfo->name[16] = 0; 192 | outRomInfo->numRomBanks = _romInfoFile.numBanks; 193 | outRomInfo->firstBank = RomBankToPointer(_romInfoFile.banks[0]); 194 | outRomInfo->numRamBanks = 195 | GameBoyHeader_readRamBankCount(outRomInfo->firstBank); 196 | outRomInfo->mbc = GameBoyHeader_readMbc(outRomInfo->firstBank); 197 | outRomInfo->speedSwitchBank = _romInfoFile.speedSwitchBank; 198 | 199 | romFound = true; 200 | } 201 | 202 | lfs_err = lfs_dir_read(_lfs, &dir, &lfsInfo); 203 | } 204 | 205 | ASSURE(romFound); 206 | 207 | error: 208 | lfs_dir_close(_lfs, &dir); 209 | return err; 210 | } 211 | 212 | int RomStorage_StartNewRomTransfer(uint16_t num_banks, uint16_t speedSwitchBank, 213 | const char *name) { 214 | bool bank_allocated; 215 | size_t current_search_bank = 0; 216 | struct lfs_info lfsInfo; 217 | int lfs_err; 218 | 219 | if ((g_numRoms + 1) > MAX_ALLOWED_ROMS) { 220 | printf("That is more ROMs as can be handled\n"); 221 | return -1; 222 | } 223 | 224 | if ((num_banks + _usedBanks) > MAX_BANKS) { 225 | printf("Not enough free banks for new ROM\n"); 226 | return -1; 227 | } 228 | 229 | memcpy(&_fileNameBuffer[6], name, 17); 230 | lfs_err = lfs_stat(_lfs, _fileNameBuffer, &lfsInfo); 231 | if (lfs_err >= 0) { 232 | printf("%s already exists\n", _fileNameBuffer); 233 | return -1; 234 | } 235 | 236 | _romInfoFile.magic = ROMINFO_FILE_MAGIC; 237 | _romInfoFile.numBanks = num_banks; 238 | _romInfoFile.speedSwitchBank = speedSwitchBank; 239 | memcpy(_romInfoFile.name, name, sizeof(_romInfoFile.name) - 1); 240 | _romInfoFile.name[sizeof(_romInfoFile.name) - 1] = 0; 241 | 242 | for (size_t i = 0; i < num_banks; i++) { 243 | bank_allocated = false; 244 | while (!bank_allocated) { 245 | assert(current_search_bank < MAX_BANKS); 246 | 247 | if (!TestBit(_usedBanksFlags, current_search_bank)) { 248 | SetBit(_usedBanksFlags, current_search_bank); 249 | _romInfoFile.banks[i] = current_search_bank; 250 | bank_allocated = true; 251 | } 252 | current_search_bank++; 253 | } 254 | } 255 | 256 | printf("Allocated %d banks for new ROM %s\n", num_banks, name); 257 | printf("ROM uses bank %d for speed switch\n", speedSwitchBank); 258 | 259 | for (size_t i = 0; i < num_banks; i++) { 260 | uint32_t flashAddr = (_romInfoFile.banks[i] * GB_ROM_BANK_SIZE) + 261 | ROM_STORAGE_FLASH_START_ADDR; 262 | 263 | printf("Erasing bank %d @%x\n", _romInfoFile.banks[i], flashAddr); 264 | uint32_t ints = save_and_disable_interrupts(); 265 | flash_range_erase(flashAddr, GB_ROM_BANK_SIZE); 266 | restore_interrupts(ints); 267 | } 268 | 269 | _romTransferActive = true; 270 | _lastTransferredChunk = 0xFFFF; 271 | _lastTransferredBank = 0xFFFF; 272 | 273 | return 0; 274 | } 275 | 276 | int RomStorage_TransferRomChunk(uint16_t bank, uint16_t chunk, 277 | const uint8_t data[32]) { 278 | 279 | lfs_file_t file; 280 | int lfs_err; 281 | 282 | if (!_romTransferActive) { 283 | return -1; 284 | } 285 | 286 | if (_lastTransferredBank == 0xFFFF) { 287 | if (bank != 0) { 288 | printf("transfer needs to start with first bank\n"); 289 | return -1; 290 | } 291 | } else { 292 | if (_lastTransferredChunk == 0xFFFF) { 293 | if (bank != (_lastTransferredBank + 1)) { 294 | printf("bank out of order\n"); 295 | return -1; 296 | } 297 | } else if (bank != _lastTransferredBank) { 298 | printf("new bank only allowed if old is finished\n"); 299 | return -1; 300 | } 301 | } 302 | 303 | if (_lastTransferredChunk == 0xFFFF) { 304 | if (chunk != 0) { 305 | printf("transfer needs to start with first chunk\n"); 306 | return -1; 307 | } 308 | } else if (chunk != (_lastTransferredChunk + 1)) { 309 | printf("chunk is out of order\n"); 310 | return -1; 311 | } 312 | 313 | memcpy(&_bankBuffer[chunk * TRANSFER_CHUNK_SIZE], data, TRANSFER_CHUNK_SIZE); 314 | 315 | _lastTransferredBank = bank; 316 | _lastTransferredChunk = chunk; 317 | 318 | if (chunk == (CHUNKS_PER_BANK - 1)) { 319 | _lastTransferredChunk = 0xFFFF; 320 | printf("Transfer of bank %d completed\n", bank); 321 | 322 | uint32_t flashAddr = (_romInfoFile.banks[bank] * GB_ROM_BANK_SIZE) + 323 | ROM_STORAGE_FLASH_START_ADDR; 324 | printf("Writing bank %d @%x\n", _romInfoFile.banks[bank], flashAddr); 325 | uint32_t ints = save_and_disable_interrupts(); 326 | flash_range_program(flashAddr, _bankBuffer, GB_ROM_BANK_SIZE); 327 | restore_interrupts(ints); 328 | 329 | if (bank == (_romInfoFile.numBanks - 1)) { 330 | printf("Transfer of ROM completed\n"); 331 | 332 | lfs_err = lfs_file_opencfg(_lfs, &file, _fileNameBuffer, 333 | LFS_O_WRONLY | LFS_O_CREAT | LFS_O_EXCL, 334 | &_fileconfig); 335 | 336 | lfs_err = lfs_file_write(_lfs, &file, &_romInfoFile, 337 | offsetof(struct RomInfoFile, banks)); 338 | if (lfs_err < 0) { 339 | printf("Error writing header %d\n", lfs_err); 340 | return -1; 341 | } 342 | 343 | lfs_err = lfs_file_write(_lfs, &file, &_romInfoFile.banks, 344 | _romInfoFile.numBanks * sizeof(uint16_t)); 345 | if (lfs_err < 0) { 346 | printf("Error writing bank info %d\n", lfs_err); 347 | return -1; 348 | } 349 | 350 | lfs_err = lfs_file_close(_lfs, &file); 351 | if (lfs_err < 0) { 352 | printf("Error closing file %d\n", lfs_err); 353 | return -1; 354 | } 355 | 356 | RomStorage_init(_lfs); // reinit to reload ROM info 357 | 358 | _romTransferActive = false; 359 | } 360 | } 361 | 362 | return 0; 363 | } 364 | 365 | uint16_t RomStorage_GetNumUsedBanks() { return _usedBanks; } 366 | 367 | int RomStorage_DeleteRom(uint8_t rom) { 368 | int err = 0; 369 | int lfs_err = 0; 370 | struct RomInfo romInfo = {}; 371 | 372 | if (rom >= g_numRoms) { 373 | return -1; 374 | } 375 | 376 | ASSURE(!RomStorage_loadRomInfo(rom, &romInfo)); 377 | 378 | memcpy(&_fileNameBuffer[6], romInfo.name, 17); 379 | memcpy(&_filenamebufferSaves[6], romInfo.name, 17); 380 | 381 | printf("Deleting ROM %d, %s\n", rom, _fileNameBuffer); 382 | printf("Deleting savegame %s\n", _filenamebufferSaves); 383 | 384 | lfs_err = lfs_remove(_lfs, _filenamebufferSaves); 385 | if (lfs_err < 0) { 386 | printf("Error deleting savegame file %d\n", lfs_err); 387 | } 388 | 389 | lfs_err = lfs_remove(_lfs, _fileNameBuffer); 390 | PRINTASSURE(lfs_err >= 0, "Error deleting ROM file %d\n", lfs_err); 391 | 392 | error: 393 | err = RomStorage_init(_lfs); // reinit to reload ROM info 394 | return err; 395 | } 396 | 397 | int RomStorage_StartRamDownload(uint8_t rom) { 398 | int err = 0; 399 | int lfs_err; 400 | 401 | if (_romTransferActive) { 402 | return -1; 403 | } 404 | 405 | if (_ramTransferActive) { 406 | return -2; 407 | } 408 | 409 | if (rom >= g_numRoms) { 410 | return -3; 411 | } 412 | 413 | ASSURE(!RomStorage_loadRomInfo(rom, &g_loadedRomInfo)); 414 | 415 | if ((g_loadedRomInfo.numRamBanks == 0) && (g_loadedRomInfo.mbc != 2)) { 416 | return -3; 417 | } 418 | 419 | _ramTransferActive = true; 420 | 421 | memcpy(&_filenamebufferSaves[6], g_loadedRomInfo.name, 17); 422 | 423 | printf("Loading savefile %d, %s\n", rom, _filenamebufferSaves); 424 | 425 | lfs_err = lfs_file_opencfg(_lfs, &_ramTransferFile, _filenamebufferSaves, 426 | LFS_O_RDONLY, &_fileconfig); 427 | 428 | PRINTASSURE(lfs_err == LFS_ERR_OK, "Error opening file %d\n", lfs_err); 429 | 430 | _ramBytesTransferred = 0; 431 | _ramBytesToTransfer = g_loadedRomInfo.mbc == 2 432 | ? GB_MBC2_RAM_SIZE 433 | : g_loadedRomInfo.numRamBanks * GB_RAM_BANK_SIZE; 434 | 435 | error: 436 | return err; 437 | } 438 | 439 | int RomStorage_GetRamDownloadChunk(uint8_t data[32], uint16_t *bank, 440 | uint16_t *chunk) { 441 | int lfs_err; 442 | 443 | lfs_err = lfs_file_read(_lfs, &_ramTransferFile, data, 32); 444 | 445 | if (lfs_err != 32) { 446 | printf("Error reading RAM chunk %d\n", lfs_err); 447 | return -4; 448 | } 449 | 450 | *bank = _ramBytesTransferred / GB_RAM_BANK_SIZE; 451 | *chunk = (_ramBytesTransferred - (*bank * GB_RAM_BANK_SIZE)) / 32; 452 | 453 | _ramBytesTransferred += 32; 454 | 455 | if (_ramBytesTransferred >= _ramBytesToTransfer) { 456 | printf("RAM transfer finished\n"); 457 | 458 | lfs_file_close(_lfs, &_ramTransferFile); 459 | 460 | _ramBytesTransferred = 0; 461 | _ramTransferActive = false; 462 | } 463 | 464 | return 0; 465 | } 466 | 467 | int RomStorage_StartRamUpload(uint8_t rom) { 468 | int err = 0; 469 | int lfs_err; 470 | 471 | if (_romTransferActive) { 472 | return -1; 473 | } 474 | 475 | if (_ramTransferActive) { 476 | return -2; 477 | } 478 | 479 | if (rom >= g_numRoms) { 480 | return -3; 481 | } 482 | 483 | ASSURE(!RomStorage_loadRomInfo(rom, &g_loadedRomInfo)); 484 | 485 | if ((g_loadedRomInfo.numRamBanks == 0) && (g_loadedRomInfo.mbc != 2)) { 486 | return -3; 487 | } 488 | 489 | _ramTransferActive = true; 490 | 491 | memcpy(&_filenamebufferSaves[6], g_loadedRomInfo.name, 17); 492 | 493 | printf("Opening savefile %d, %s\n", rom, _filenamebufferSaves); 494 | 495 | lfs_err = lfs_file_opencfg(_lfs, &_ramTransferFile, _filenamebufferSaves, 496 | LFS_O_WRONLY | LFS_O_CREAT, &_fileconfig); 497 | 498 | PRINTASSURE(lfs_err == LFS_ERR_OK, "Error opening file %d\n", lfs_err); 499 | 500 | _ramBytesTransferred = 0; 501 | _ramBytesToTransfer = g_loadedRomInfo.mbc == 2 502 | ? GB_MBC2_RAM_SIZE 503 | : g_loadedRomInfo.numRamBanks * GB_RAM_BANK_SIZE; 504 | 505 | error: 506 | return err; 507 | } 508 | 509 | int RomStorage_TransferRamUploadChunk(uint16_t bank, uint16_t chunk, 510 | const uint8_t data[32]) { 511 | int lfs_err; 512 | 513 | uint16_t expected_bank = _ramBytesTransferred / GB_RAM_BANK_SIZE; 514 | uint16_t expected_chunk = 515 | ((_ramBytesTransferred - (expected_bank * GB_RAM_BANK_SIZE)) / 32) + 1; 516 | 517 | if (expected_chunk == GB_RAM_BANK_SIZE / 32) { // Here start the next bank 518 | expected_chunk = 0; 519 | expected_bank += 1; 520 | } 521 | 522 | if (expected_bank != bank) { 523 | printf("Wrong bank received, expected %d, got %d\n", expected_bank, bank); 524 | } 525 | 526 | if (expected_chunk != chunk) { 527 | printf("Wrong bank received, expected %d, got %d\n", expected_chunk, bank); 528 | } 529 | 530 | lfs_err = lfs_file_write(_lfs, &_ramTransferFile, data, 32); 531 | 532 | if (lfs_err != 32) { 533 | printf("Error reading RAM chunk %d\n", lfs_err); 534 | return -4; 535 | } 536 | 537 | _ramBytesTransferred += 32; 538 | 539 | if (_ramBytesTransferred >= _ramBytesToTransfer) { 540 | printf("RAM transfer finished\n"); 541 | 542 | lfs_file_close(_lfs, &_ramTransferFile); 543 | 544 | _ramBytesTransferred = 0; 545 | _ramTransferActive = false; 546 | } 547 | 548 | return 0; 549 | } 550 | 551 | const int RomStorage_LoadRom(uint8_t rom) { 552 | int lfs_err; 553 | lfs_file_t file; 554 | 555 | if (rom >= g_numRoms) { 556 | return -1; 557 | } 558 | 559 | if (_romTransferActive) { 560 | return -2; 561 | } 562 | 563 | RomStorage_loadRomInfo(rom, &g_loadedRomInfo); 564 | 565 | memcpy(&_fileNameBuffer[6], g_loadedRomInfo.name, 17); 566 | 567 | printf("Loading ROM %d, %s\n", rom, _fileNameBuffer); 568 | 569 | lfs_err = lfs_file_opencfg(_lfs, &file, _fileNameBuffer, LFS_O_RDONLY, 570 | &_fileconfig); 571 | if (lfs_err != LFS_ERR_OK) { 572 | printf("Error opening file %d\n", lfs_err); 573 | return -3; 574 | } 575 | 576 | lfs_err = readRomInfoFile(&file); 577 | 578 | lfs_file_close(_lfs, &file); 579 | 580 | if (lfs_err != LFS_ERR_OK) { 581 | return -4; 582 | } 583 | 584 | for (size_t i = 0; i < _romInfoFile.numBanks; i++) { 585 | g_loadedRomBanks[i] = RomBankToPointer(_romInfoFile.banks[i]); 586 | g_loadedDirectAccessRomBanks[i] = RomBankToDirectSsi(_romInfoFile.banks[i]); 587 | } 588 | 589 | return 0; 590 | } 591 | -------------------------------------------------------------------------------- /RomStorage.h: -------------------------------------------------------------------------------- 1 | /* RP2040 GameBoy cartridge 2 | * Copyright (C) 2023 Sebastian Quilitz 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #ifndef D05D89A4_C956_401B_9A8D_564BE51C4811 19 | #define D05D89A4_C956_401B_9A8D_564BE51C4811 20 | 21 | #include "GlobalDefines.h" 22 | #include 23 | #include 24 | 25 | int RomStorage_init(lfs_t *lfs); 26 | 27 | int RomStorage_loadRomInfo(uint32_t game, struct RomInfo *outShortRomInfo); 28 | 29 | int RomStorage_StartNewRomTransfer(uint16_t num_banks, uint16_t speedSwitchBank, 30 | const char *name); 31 | 32 | int RomStorage_TransferRomChunk(uint16_t bank, uint16_t chunk, 33 | const uint8_t data[32]); 34 | 35 | uint16_t RomStorage_GetNumUsedBanks(); 36 | 37 | int RomStorage_DeleteRom(uint8_t rom); 38 | 39 | int RomStorage_StartRamUpload(uint8_t rom); 40 | 41 | int RomStorage_GetRamDownloadChunk(uint8_t data[32], uint16_t *bank, 42 | uint16_t *chunk); 43 | 44 | int RomStorage_StartRamDownload(uint8_t rom); 45 | 46 | int RomStorage_TransferRamUploadChunk(uint16_t bank, uint16_t chunk, 47 | const uint8_t data[32]); 48 | 49 | const int RomStorage_LoadRom(uint8_t rom); 50 | 51 | #endif /* D05D89A4_C956_401B_9A8D_564BE51C4811 */ 52 | -------------------------------------------------------------------------------- /cmakeHelpers/bin2h.cmake: -------------------------------------------------------------------------------- 1 | # copied from https://gist.github.com/sivachandran/3a0de157dccef822a230 2 | 3 | include(CMakeParseArguments) 4 | 5 | # Function to wrap a given string into multiple lines at the given column position. 6 | # Parameters: 7 | # VARIABLE - The name of the CMake variable holding the string. 8 | # AT_COLUMN - The column position at which string will be wrapped. 9 | function(WRAP_STRING) 10 | set(oneValueArgs VARIABLE AT_COLUMN) 11 | cmake_parse_arguments(WRAP_STRING "${options}" "${oneValueArgs}" "" ${ARGN}) 12 | 13 | string(LENGTH ${${WRAP_STRING_VARIABLE}} stringLength) 14 | math(EXPR offset "0") 15 | 16 | while(stringLength GREATER 0) 17 | 18 | if(stringLength GREATER ${WRAP_STRING_AT_COLUMN}) 19 | math(EXPR length "${WRAP_STRING_AT_COLUMN}") 20 | else() 21 | math(EXPR length "${stringLength}") 22 | endif() 23 | 24 | string(SUBSTRING ${${WRAP_STRING_VARIABLE}} ${offset} ${length} line) 25 | set(lines "${lines}\n${line}") 26 | 27 | math(EXPR stringLength "${stringLength} - ${length}") 28 | math(EXPR offset "${offset} + ${length}") 29 | endwhile() 30 | 31 | set(${WRAP_STRING_VARIABLE} "${lines}" PARENT_SCOPE) 32 | endfunction() 33 | 34 | # Function to embed contents of a file as byte array in C/C++ header file(.h). The header file 35 | # will contain a byte array and integer variable holding the size of the array. 36 | # Parameters 37 | # SOURCE_FILE - The path of source file whose contents will be embedded in the header file. 38 | # VARIABLE_NAME - The name of the variable for the byte array. The string "_SIZE" will be append 39 | # to this name and will be used a variable name for size variable. 40 | # HEADER_FILE - The path of header file. 41 | # APPEND - If specified appends to the header file instead of overwriting it 42 | # NULL_TERMINATE - If specified a null byte(zero) will be append to the byte array. This will be 43 | # useful if the source file is a text file and we want to use the file contents 44 | # as string. But the size variable holds size of the byte array without this 45 | # null byte. 46 | # Usage: 47 | # bin2h(SOURCE_FILE "Logo.png" HEADER_FILE "Logo.h" VARIABLE_NAME "LOGO_PNG") 48 | function(BIN2H) 49 | set(options APPEND NULL_TERMINATE) 50 | set(oneValueArgs SOURCE_FILE VARIABLE_NAME HEADER_FILE) 51 | cmake_parse_arguments(BIN2H "${options}" "${oneValueArgs}" "" ${ARGN}) 52 | 53 | # reads source file contents as hex string 54 | file(READ ${BIN2H_SOURCE_FILE} hexString HEX) 55 | string(LENGTH ${hexString} hexStringLength) 56 | 57 | # appends null byte if asked 58 | if(BIN2H_NULL_TERMINATE) 59 | set(hexString "${hexString}00") 60 | endif() 61 | 62 | # wraps the hex string into multiple lines at column 32(i.e. 16 bytes per line) 63 | wrap_string(VARIABLE hexString AT_COLUMN 32) 64 | math(EXPR arraySize "${hexStringLength} / 2") 65 | 66 | # adds '0x' prefix and comma suffix before and after every byte respectively 67 | string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1, " arrayValues ${hexString}) 68 | # removes trailing comma 69 | string(REGEX REPLACE ", $" "" arrayValues ${arrayValues}) 70 | 71 | # converts the variable name into proper C identifier 72 | string(MAKE_C_IDENTIFIER "${BIN2H_VARIABLE_NAME}" BIN2H_VARIABLE_NAME) 73 | string(TOUPPER "${BIN2H_VARIABLE_NAME}" BIN2H_VARIABLE_NAME) 74 | 75 | # declares byte array and the length variables 76 | set(arrayDefinition "const unsigned char ${BIN2H_VARIABLE_NAME}[] = { ${arrayValues} };") 77 | set(arraySizeDefinition "const size_t ${BIN2H_VARIABLE_NAME}_SIZE = ${arraySize};") 78 | 79 | set(declarations "${arrayDefinition}\n\n${arraySizeDefinition}\n\n") 80 | if(BIN2H_APPEND) 81 | file(APPEND ${BIN2H_HEADER_FILE} "${declarations}") 82 | else() 83 | file(WRITE ${BIN2H_HEADER_FILE} "${declarations}") 84 | endif() 85 | endfunction() 86 | -------------------------------------------------------------------------------- /cmakeHelpers/executebin2h.cmake: -------------------------------------------------------------------------------- 1 | include("${CMAKE_CURRENT_LIST_DIR}/bin2h.cmake") 2 | 3 | bin2h( 4 | SOURCE_FILE ${SOURCE_FILE} 5 | HEADER_FILE ${HEADER_FILE} 6 | VARIABLE_NAME ${VARIABLE_NAME} 7 | ) -------------------------------------------------------------------------------- /docs/2024-01-27-backspace-bamberg.odp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shilga/rp2040-gameboy-cartridge-firmware/dc8f60b47a7336ead7b4784e7c7235b6943efe6f/docs/2024-01-27-backspace-bamberg.odp -------------------------------------------------------------------------------- /docs/2024-01-27-backspace-bamberg.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shilga/rp2040-gameboy-cartridge-firmware/dc8f60b47a7336ead7b4784e7c7235b6943efe6f/docs/2024-01-27-backspace-bamberg.pdf -------------------------------------------------------------------------------- /docs/Flash Layout.drawio: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /docs/ReadDma.drawio: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /gameboy_bus.pio: -------------------------------------------------------------------------------- 1 | /* RP2040 GameBoy cartridge 2 | * Copyright (C) 2023 Sebastian Quilitz 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | .define public PIN_CLK 0 19 | .define public PIN_RD 1 20 | .define public PIN_A15 17 21 | .define public PIN_A14 16 22 | .define public PIN_A13 15 23 | .define public PIN_AD_BASE 2 24 | .define public PIN_DATA_BASE 18 25 | 26 | .define public PIN_UART_TX 28 27 | 28 | .define public SYSCLK_MHZ 266 29 | .define public DELAY_COUNT_ADDR_READ 30/(266/SYSCLK_MHZ) 30 | 31 | .program gameboy_bus 32 | .side_set 1 opt 33 | a15_high: 34 | irq set 4 side 1 35 | .wrap_target 36 | in pins 1 ; shift in RD pin 37 | mov y isr ; store rd pin in y 38 | in pins 17 ; shift rd pin and address into ISR 39 | in null 15 ; fill up ISR to trigger auto push 40 | wait 0 gpio PIN_CLK 41 | jmp !y idle[7] ; Y holds read pin, skip to idle on reads 42 | in pins 25 ; sample read rd pin, addr pins and data pins 43 | in null 24 ; 7+17=24 shit read and addr pins out of the isr to only leave the data, trigger auto push 44 | idle: 45 | public entry_point: 46 | mov isr null side 0 ; Clear ISR 47 | wait 1 gpio PIN_CLK ; wait for clk 48 | 49 | set y DELAY_COUNT_ADDR_READ[6] 50 | loop: 51 | jmp y-- loop[1] ; delay to let adress pins become available 52 | jmp pin a15_high ; if A15 is high jump to high area notification 53 | irq set 5 side 1 ; set irq for A15 low, though right now nobody needs it 54 | .wrap ; wrap back to beginning 55 | 56 | 57 | 58 | .program gameboy_bus_double_speed 59 | .side_set 1 opt 60 | a15_high: 61 | irq set 4 side 1 62 | .wrap_target 63 | in pins 1 ; shift in RD pin 64 | mov y isr ; store rd pin in y 65 | in pins 17 ; shift rd pin and address into ISR 66 | in null 15 ; fill up ISR to trigger auto push 67 | wait 0 gpio PIN_CLK 68 | jmp !y idle[7] ; Y holds read pin, skip to idle on reads 69 | in pins 25 ; sample read rd pin, addr pins and data pins 70 | in null 24 ; 7+17=24 shit read and addr pins out of the isr to only leave the data, trigger auto push 71 | idle: 72 | public entry_point: 73 | mov isr null side 0 ; Clear ISR 74 | wait 1 gpio PIN_CLK ; wait for clk 75 | 76 | set y 14[3] 77 | loop: 78 | jmp y-- loop ; delay to let adress pins become available 79 | jmp pin a15_high ; if A15 is high jump to high area notification 80 | irq set 5 side 1 ; set irq for A15 low, though right now nobody needs it 81 | .wrap ; wrap back to beginning 82 | 83 | 84 | .program gameboy_bus_detect_a14 85 | idle: 86 | .wrap_target 87 | wait 1 irq 4 88 | jmp pin idle ; jmp back to idle if A14 is high 89 | irq set 6 90 | .wrap ; wrap back to beginning 91 | 92 | 93 | 94 | .program gameboy_bus_ram 95 | public read_wrap_target: 96 | jmp y-- idle_data ; Y=Rnw - skip the FIFO push on write cycles (RnW=0) 97 | in null 19 ; shift fixed part of ARM address (held in x) into ISR and trigger auto push 98 | 99 | public entry_point: ; *** Default Entry Point *** 100 | public write_wrap_target: 101 | idle_data: 102 | mov isr null ; Clear ISR 103 | wait 1 irq 6 104 | jmp pin continue ; jmp back to idle if A13 is high 105 | jmp idle_data 106 | continue: 107 | in pins 1 ; shift in RD pin 108 | mov y isr ; store rd pin in y 109 | 110 | in pins 14 ; shift rd and A0 to A12 pins into ISR 111 | public read_wrap: ; *** READ state machine wraps to read_wrap_target *** 112 | 113 | jmp !y idle_data ; Y=Rnw - skip the FIFO push on read cycles (RnW=1) 114 | in null 19 ; shift fixed part of ARM address (held in x) into ISR and trigger auto push 115 | 116 | wait 0 gpio PIN_CLK [7] ; wait for clk 117 | in pins 25 ; sample read rd pin, addr pins and data pins 118 | in null 24 ; 7+17=24 shit read and addr pins out of the isr to only leave the data 119 | public write_wrap: ; *** WRITE state machine wraps to write_wrap_target *** 120 | 121 | 122 | .program gameboy_bus_write_to_data 123 | .side_set 1 opt 124 | .wrap_target 125 | pull block ; pull the data from the Tx FIFO into the OSR 126 | out pins 8 side 0 ; output 8 bits of data 127 | mov osr ~NULL ; OSR=FFFFFFFF 128 | out pindirs 8 ; start driving the data bus 129 | wait 0 gpio PIN_CLK ; wait for clk 130 | mov osr NULL 131 | wait 1 gpio PIN_CLK[3] ; wait for clk 132 | out pindirs 8 ; stop driving the data bus 133 | .wrap ; wrap back to beginning 134 | 135 | 136 | .program gameboy_bus_detect_a15_low_a14_irqs 137 | a14_high: 138 | irq set 1 139 | .wrap_target 140 | wait 0 gpio PIN_CLK 141 | public entry_point: 142 | wait 1 gpio PIN_CLK[2] 143 | mov isr null 144 | in pins 1 ; shift in a15 145 | mov y isr ; store a15 pin in y 146 | jmp !y hdma_read ; GBC has the HDMA read which does not strobe A15 147 | wait 0 gpio PIN_A15 148 | continue_hdma_read: 149 | jmp pin a14_high ; if A14 is high 150 | irq set 0 ; if a A14 is low 151 | .wrap 152 | hdma_read: 153 | jmp continue_hdma_read [30] 154 | 155 | 156 | .program gameboy_bus_rom_low 157 | .side_set 1 opt 158 | idle: 159 | wait 1 irq 0 rel ; will wait on irq 0 on SM0 (low) and irq 1 on SM1 (high) 160 | jmp pin idle ; on reads back to idle 161 | 162 | in pins 14 side 1 ; shift A0 to A13 pins into ISR and auto push 163 | .wrap 164 | 165 | 166 | .program gameboy_bus_rom_high 167 | .side_set 1 opt 168 | idle: 169 | wait 1 irq 0 rel ; will wait on irq 0 on SM0 (low) and irq 1 on SM1 (high) 170 | jmp pin idle ; on reads back to idle 171 | 172 | in pins 14 side 1 ; shift A0 to A13 pins into ISR and auto push 173 | in null 8 174 | .wrap 175 | 176 | % c-sdk { 177 | 178 | void gameboy_bus_program_init(PIO pio, uint sm, uint offset) { 179 | pio_sm_config c = gameboy_bus_program_get_default_config(offset); 180 | sm_config_set_jmp_pin (&c, PIN_A15); 181 | sm_config_set_in_pins(&c, PIN_AD_BASE-1); 182 | sm_config_set_in_shift( &c, true, true, 32); // shift right=true, auto-push=true 183 | 184 | sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX); 185 | 186 | sm_config_set_sideset_pins(&c, PIN_UART_TX); 187 | pio_sm_set_consecutive_pindirs(pio, sm, PIN_UART_TX, 1, true); 188 | 189 | pio_sm_init(pio, sm, offset + gameboy_bus_offset_entry_point, &c); 190 | } 191 | 192 | void gameboy_bus_detect_a14_program_init(PIO pio, uint sm, uint offset) { 193 | pio_sm_config c = gameboy_bus_detect_a14_program_get_default_config(offset); 194 | sm_config_set_jmp_pin (&c, PIN_A14); 195 | 196 | pio_sm_init(pio, sm, offset, &c); 197 | } 198 | 199 | void gameboy_bus_ram_read_program_init(PIO pio, uint sm, uint offset) { 200 | pio_sm_config c = gameboy_bus_ram_program_get_default_config(offset); 201 | sm_config_set_jmp_pin (&c, PIN_A13); 202 | sm_config_set_in_pins(&c, PIN_AD_BASE-1); 203 | sm_config_set_in_shift( &c, true, true, 32); // shift right=true, auto-push=true 204 | 205 | 206 | // sm_config_set_sideset_pins(&c, PIN_UART_TX); 207 | // pio_sm_set_consecutive_pindirs(pio, sm, PIN_UART_TX, 1, true); 208 | 209 | sm_config_set_wrap(&c, offset + gameboy_bus_ram_offset_read_wrap_target, offset + gameboy_bus_ram_offset_read_wrap - 1); 210 | 211 | pio_sm_init(pio, sm, offset + gameboy_bus_ram_offset_entry_point, &c); 212 | } 213 | 214 | 215 | void gameboy_bus_ram_write_program_init(PIO pio, uint sm, uint offset) { 216 | pio_sm_config c = gameboy_bus_ram_program_get_default_config(offset); 217 | sm_config_set_jmp_pin (&c, PIN_A13); 218 | sm_config_set_in_pins(&c, PIN_AD_BASE-1); 219 | sm_config_set_in_shift( &c, true, true, 32); // shift right=true, auto-push=true 220 | 221 | 222 | // sm_config_set_sideset_pins(&c, PIN_UART_TX); 223 | // pio_sm_set_consecutive_pindirs(pio, sm, PIN_UART_TX, 1, true); 224 | 225 | sm_config_set_wrap(&c, offset + gameboy_bus_ram_offset_write_wrap_target, offset + gameboy_bus_ram_offset_write_wrap - 1); 226 | 227 | 228 | pio_sm_init(pio, sm, offset + gameboy_bus_ram_offset_entry_point, &c); 229 | } 230 | 231 | void gameboy_bus_write_to_data_program_init(PIO pio, uint sm, uint offset) { 232 | pio_sm_config c = gameboy_bus_write_to_data_program_get_default_config(offset); 233 | sm_config_set_out_pins(&c, PIN_DATA_BASE, 8); 234 | sm_config_set_out_shift(&c, true, false, 8); // shift right=true, auto-pull=false 235 | 236 | sm_config_set_sideset_pins(&c, PIN_UART_TX); 237 | // pio_sm_set_consecutive_pindirs(pio, sm, PIN_UART_TX, 1, true); 238 | 239 | pio_sm_init(pio, sm, offset, &c); 240 | } 241 | 242 | void gameboy_bus_detect_a15_low_a14_irqs_init(PIO pio, uint sm, uint offset) { 243 | pio_sm_config c = gameboy_bus_detect_a15_low_a14_irqs_program_get_default_config(offset); 244 | sm_config_set_jmp_pin (&c, PIN_A14); 245 | sm_config_set_in_pins(&c, PIN_A15); 246 | sm_config_set_in_shift( &c, true, false, 32); // shift right=false, auto-push=false 247 | pio_sm_init(pio, sm, offset + gameboy_bus_detect_a15_low_a14_irqs_offset_entry_point, &c); 248 | } 249 | 250 | void gameboy_bus_rom_low_program_init(PIO pio, uint sm, uint offset) { 251 | pio_sm_config c = gameboy_bus_rom_low_program_get_default_config(offset); 252 | sm_config_set_jmp_pin (&c, PIN_RD); 253 | sm_config_set_in_pins(&c, PIN_AD_BASE); 254 | sm_config_set_in_shift( &c, false, true, 14); // shift right=false, auto-push=true 255 | 256 | 257 | sm_config_set_sideset_pins(&c, PIN_UART_TX); 258 | pio_sm_set_consecutive_pindirs(pio, sm, PIN_UART_TX, 1, true); 259 | 260 | pio_sm_init(pio, sm, offset, &c); 261 | } 262 | 263 | void gameboy_bus_rom_high_program_init(PIO pio, uint sm, uint offset) { 264 | pio_sm_config c = gameboy_bus_rom_high_program_get_default_config(offset); 265 | sm_config_set_jmp_pin (&c, PIN_RD); 266 | sm_config_set_in_pins(&c, PIN_AD_BASE); 267 | sm_config_set_in_shift( &c, false, true, 22); // shift right=false, auto-push=true 268 | 269 | 270 | sm_config_set_sideset_pins(&c, PIN_UART_TX); 271 | //pio_sm_set_consecutive_pindirs(pio, sm, PIN_UART_TX, 1, true); 272 | 273 | pio_sm_init(pio, sm, offset, &c); 274 | } 275 | 276 | %} -------------------------------------------------------------------------------- /gb-bootloader/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | set(GBDK_LCC $ENV{GBDK_PATH}/bin/lcc) 4 | 5 | add_custom_command( 6 | OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/bootloader.gb 7 | DEPENDS bootloader.c 8 | COMMAND ${GBDK_LCC} -Wa-l -Wl-m -Wl-j -Wm-p -Wm-yc -Wm-yt2 -Wm-ya1 9 | -o ${CMAKE_CURRENT_BINARY_DIR}/bootloader.gb 10 | ${CMAKE_CURRENT_LIST_DIR}/bootloader.c 11 | ${CMAKE_CURRENT_LIST_DIR}/giraffe_4color_data.c 12 | ${CMAKE_CURRENT_LIST_DIR}/giraffe_4color_map.c 13 | ) 14 | 15 | add_custom_command( 16 | OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/gbbootloader.h 17 | DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/bootloader.gb 18 | COMMAND ${CMAKE_COMMAND} 19 | -DSOURCE_FILE="${CMAKE_CURRENT_BINARY_DIR}/bootloader.gb" 20 | -DHEADER_FILE="${CMAKE_CURRENT_BINARY_DIR}/gbbootloader.h" 21 | -DVARIABLE_NAME="GB_BOOTLOADER" 22 | -P ${CMAKE_HELPERS_DIR}/executebin2h.cmake 23 | ) 24 | 25 | add_custom_target(BootloaderGb ALL 26 | DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/gbbootloader.h 27 | ) 28 | -------------------------------------------------------------------------------- /gb-bootloader/bootloader.c: -------------------------------------------------------------------------------- 1 | /* RP2040 GameBoy cartridge 2 | * Copyright (C) 2023 Sebastian Quilitz 3 | * Copyright (C) 2024 Tihmstar 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation; either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "giraffe_4color_data.h" 32 | #include "giraffe_4color_map.h" 33 | 34 | #ifdef DMG_TEST_MODE_ON_CBG 35 | #define DEVICE_SUPPORTS_COLOR (0) 36 | #else 37 | #define DEVICE_SUPPORTS_COLOR (_cpu == CGB_TYPE) 38 | #endif // DMG_TEST_MODE_ON_CBG 39 | 40 | #define MENU_GAME_MENU 1 41 | #define MENU_SYSTEM_INFO 2 42 | #define MENU_RGB_TESTER 3 43 | #define MENU_GAME_SETTINGS 4 44 | #define MENU_RTC_SETTINGS 5 45 | 46 | #define MAX_GAMES_RENDER_NUM 16 47 | #define CHARS_PER_ROW 20 48 | 49 | #define ROW_RTC_REAL 12 50 | 51 | #define SMEM_ADDR_LED_CONTROL ((UBYTE *)(0xB010)) 52 | #define SMEM_ADDR_RP2040_BOOTLOADER ((UBYTE *)(0xB011)) 53 | #define SMEM_ADDR_GAME_MODE_SELECTOR ((UBYTE *)(0xB000)) 54 | #define SMEM_ADDR_GAME_SELECTOR ((UBYTE *)(0xB001)) 55 | #define SMEM_ADDR_GAME_CONTROL ((UBYTE *)(0xB002)) 56 | #define SMEM_ADDR_GAMEBOY_CPU ((UBYTE *)(0xB003)) 57 | 58 | #define SMEM_GAME_START_MAGIC 42 59 | 60 | #define DMG_BKG_SELECTED_PALETTE \ 61 | DMG_PALETTE(DMG_BLACK, DMG_LITE_GRAY, DMG_DARK_GRAY, DMG_WHITE); 62 | #define DMG_BKG_NORMAL_PALETTE \ 63 | DMG_PALETTE(DMG_WHITE, DMG_LITE_GRAY, DMG_DARK_GRAY, DMG_BLACK); 64 | #define DMG_TILE_NORMAL_PALETTE \ 65 | DMG_PALETTE(DMG_WHITE, DMG_DARK_GRAY, DMG_LITE_GRAY, DMG_BLACK); 66 | #define DMG_TILE_SELECTED_PALETTE \ 67 | DMG_PALETTE(DMG_WHITE, DMG_DARK_GRAY, DMG_LITE_GRAY, DMG_WHITE); 68 | 69 | #define COLOR_BLACK 3 70 | #define COLOR_WHITE 2 71 | 72 | struct TimePoint { 73 | uint8_t Second; 74 | uint8_t Minute; 75 | uint8_t Hour; 76 | uint8_t Day; 77 | uint8_t Month; 78 | uint8_t Year; // offset from 1970; 79 | }; 80 | 81 | struct SharedGameboyData { 82 | uint16_t git_sha1_l; 83 | uint16_t git_sha1_h; 84 | uint8_t git_status; 85 | char buildType; 86 | uint8_t versionMajor; 87 | uint8_t versionMinor; 88 | uint8_t versionPatch; 89 | struct TimePoint timePoint; 90 | uint8_t number_of_roms; 91 | char rom_names[]; 92 | }; 93 | 94 | const palette_color_t backgroundpalette[] = { 95 | RGB_WHITE, RGB_YELLOW, RGB_BROWN, RGB_BLACK, 96 | RGB_BLACK, RGB_YELLOW, RGB_BROWN, RGB_WHITE, 97 | }; 98 | 99 | const palette_color_t spritepalette[] = { 100 | RGB_BROWN, RGB_YELLOW, RGB_GREEN, RGB_BLACK, 101 | RGB_BROWN, RGB_YELLOW, RGB_GREEN, RGB_WHITE, 102 | }; 103 | 104 | static uint8_t gCurrentInput = 0; 105 | static uint8_t gForceDrawScreen = 1; 106 | static uint8_t gHighlightOffset = 1; 107 | static uint8_t gCursor = 0; 108 | static uint8_t gPageCursor = 0; 109 | static uint8_t gLastSelectedGame = 0; 110 | static uint8_t gLastSelectedGamePage = 0; 111 | static uint8_t gSelectedMode = 0xFF; 112 | 113 | static uint8_t gDMGHighlightLine = 0xFF; 114 | 115 | struct SharedGameboyData *s_sharedData = (struct SharedGameboyData *)(0xA000); 116 | #define s_GamesCount s_sharedData->number_of_roms 117 | 118 | void sanitizeRTCReal(struct TimePoint *rtc); 119 | 120 | void startGame(uint8_t game, uint8_t mode); 121 | 122 | uint8_t getRomInfoByteForIndex(uint8_t idx) { 123 | char *pRomData = s_sharedData->rom_names; 124 | for (uint8_t i = 0; i < s_GamesCount; ++i) { 125 | if (i == idx) { 126 | return *pRomData; 127 | } else { 128 | pRomData++; 129 | } 130 | pRomData += strlen(pRomData) + 1; 131 | } 132 | return 0; 133 | } 134 | 135 | char *getRomNameForIndex(uint8_t idx) { 136 | char *pRomNames = s_sharedData->rom_names; 137 | for (uint8_t i = 0; i < s_GamesCount; ++i) { 138 | pRomNames++; 139 | if (i == idx) { 140 | return pRomNames; 141 | } 142 | pRomNames += strlen(pRomNames) + 1; 143 | } 144 | return NULL; 145 | } 146 | 147 | uint8_t buttonPressed(uint8_t key) { 148 | if (gCurrentInput & key) { 149 | waitpadup(); 150 | 151 | return TRUE; 152 | } 153 | return FALSE; 154 | } 155 | 156 | void selectTile(uint8_t x, uint8_t y, uint8_t sprite) { 157 | set_sprite_prop(sprite, y == gDMGHighlightLine ? S_PALETTE : 0); 158 | set_sprite_tile(sprite, get_bkg_tile_xy(x, y)); 159 | x = (x << 3) + 8; 160 | y = (y << 3) + 16; 161 | move_sprite(sprite, x, y); 162 | } 163 | 164 | void resetSelection(void) { 165 | move_sprite(0, 0, 0); 166 | move_sprite(1, 0, 0); 167 | move_sprite(2, 0, 0); 168 | move_sprite(3, 0, 0); 169 | } 170 | 171 | void highlightLine(uint8_t idx) { 172 | #define ATTR 1 173 | static uint8_t attrs[CHARS_PER_ROW] = { 174 | ATTR, ATTR, ATTR, ATTR, ATTR, ATTR, ATTR, ATTR, ATTR, ATTR, 175 | ATTR, ATTR, ATTR, ATTR, ATTR, ATTR, ATTR, ATTR, ATTR, ATTR}; 176 | if (DEVICE_SUPPORTS_COLOR) { 177 | set_bkg_attributes(0, idx, CHARS_PER_ROW, 1, attrs); 178 | } 179 | gDMGHighlightLine = idx; 180 | #undef ATTR 181 | } 182 | 183 | void resetHighlights(void) { 184 | static uint8_t attrs[CHARS_PER_ROW * MAX_GAMES_RENDER_NUM] = {0}; 185 | if (DEVICE_SUPPORTS_COLOR) { 186 | set_bkg_attributes(0, 1, CHARS_PER_ROW, MAX_GAMES_RENDER_NUM, attrs); 187 | } 188 | resetSelection(); 189 | gDMGHighlightLine = 0xFF; 190 | } 191 | 192 | void renderGamelist(uint8_t first, uint8_t selected) { 193 | const char *spacer_selected = selected < 9 ? " " : ""; 194 | const char *spacer_games = s_GamesCount < 9 ? " " : ""; 195 | 196 | char *pRomNames = s_sharedData->rom_names; 197 | 198 | gotoxy(0, 0); 199 | 200 | if (s_GamesCount == 0) { 201 | printf("*** Games ***"); 202 | } else { 203 | printf("*** Games %s%d/%s%d ***", spacer_selected, selected + 1, 204 | spacer_games, s_GamesCount); 205 | } 206 | 207 | if (s_GamesCount != 0) { 208 | // loop through all game titles and display them 209 | for (uint8_t i = 0; i < s_GamesCount; ++i) { 210 | pRomNames += 1; // first byte of every ROMInfo contains RTC info 211 | if (i >= first) { 212 | uint8_t renderIDX = i - first; 213 | if (renderIDX >= MAX_GAMES_RENDER_NUM) 214 | break; 215 | gotoxy(0, renderIDX + 1); 216 | printf("%s", pRomNames); 217 | for (UINT8 z = posx(); z < CHARS_PER_ROW; z++) { 218 | putchar(' '); 219 | } 220 | } 221 | pRomNames += strlen(pRomNames) + 1; 222 | } 223 | } else { 224 | gotoxy(0, 2); 225 | printf("No games found"); 226 | } 227 | } 228 | 229 | void moveCursor(uint8_t limit) { 230 | if (buttonPressed(J_UP)) { 231 | if (gCursor > 0) { 232 | gCursor--; 233 | if (gCursor < gPageCursor) { 234 | gPageCursor--; 235 | } 236 | } 237 | gForceDrawScreen = 1; 238 | 239 | } else if (buttonPressed(J_DOWN)) { 240 | if (gCursor < limit - 1) { 241 | gCursor++; 242 | if (gCursor > gPageCursor + MAX_GAMES_RENDER_NUM - 1) { 243 | gPageCursor++; 244 | } 245 | } 246 | gForceDrawScreen = 1; 247 | 248 | } else if (buttonPressed(J_LEFT) && limit > MAX_GAMES_RENDER_NUM) { 249 | if (gCursor > 0) { 250 | if (gCursor >= MAX_GAMES_RENDER_NUM) 251 | gCursor -= MAX_GAMES_RENDER_NUM; 252 | else 253 | gCursor = 0; 254 | 255 | if (gPageCursor >= MAX_GAMES_RENDER_NUM) 256 | gPageCursor -= MAX_GAMES_RENDER_NUM; 257 | else 258 | gPageCursor = 0; 259 | } 260 | gForceDrawScreen = 1; 261 | 262 | } else if (buttonPressed(J_RIGHT) && limit > MAX_GAMES_RENDER_NUM) { 263 | if (gCursor < limit - 1) { 264 | if (gCursor + MAX_GAMES_RENDER_NUM < limit) 265 | gCursor += MAX_GAMES_RENDER_NUM; 266 | else 267 | gCursor = limit - 1; 268 | 269 | if (gPageCursor + (MAX_GAMES_RENDER_NUM << 1) < limit) 270 | gPageCursor += MAX_GAMES_RENDER_NUM; 271 | else 272 | gPageCursor = limit - MAX_GAMES_RENDER_NUM; 273 | } 274 | gForceDrawScreen = 1; 275 | } 276 | 277 | if (gForceDrawScreen && limit != 0) { 278 | resetHighlights(); 279 | highlightLine(gCursor - gPageCursor + gHighlightOffset); 280 | } 281 | } 282 | 283 | uint8_t drawscreenGameMenu(void) { 284 | if (buttonPressed(J_SELECT)) { 285 | return MENU_SYSTEM_INFO; 286 | } 287 | 288 | if (s_GamesCount) { 289 | gCursor = gLastSelectedGame; 290 | gPageCursor = gLastSelectedGamePage; 291 | 292 | if (buttonPressed(J_START)) { 293 | return MENU_GAME_SETTINGS; 294 | } else if (buttonPressed(J_A)) { 295 | if (getRomInfoByteForIndex(gCursor) == 1) { 296 | return MENU_RTC_SETTINGS; 297 | } else { 298 | startGame(gCursor, 0xff); 299 | } 300 | } 301 | 302 | moveCursor(s_GamesCount); 303 | } 304 | gLastSelectedGame = gCursor; 305 | gLastSelectedGamePage = gPageCursor; 306 | 307 | if (gForceDrawScreen) 308 | renderGamelist(gPageCursor, gCursor); 309 | return MENU_GAME_MENU; 310 | } 311 | 312 | uint8_t drawscreenSystemInfo(void) { 313 | 314 | if (buttonPressed(J_SELECT)) { 315 | return MENU_GAME_MENU; 316 | 317 | } else if (buttonPressed(J_START)) { 318 | gotoxy(0, 14); 319 | printf("Started RP2040 BTLD"); 320 | wait_vbl_done(); 321 | disable_interrupts(); 322 | *SMEM_ADDR_RP2040_BOOTLOADER = 1; 323 | wait_vbl_done(); 324 | while (1) { 325 | wait_vbl_done(); 326 | } 327 | 328 | } else if (buttonPressed(J_RIGHT)) { 329 | return MENU_RGB_TESTER; 330 | } 331 | 332 | if (gForceDrawScreen) { 333 | set_bkg_tiles(0, 0, 20, 18, giraffe_4color_map); 334 | gotoxy(0, 0); 335 | printf("*** Sysinfo ***"); 336 | gotoxy(0, 15); 337 | printf("Croco Cartridge"); 338 | gotoxy(0, 16); 339 | printf("Ver %hu.", (uint8_t)s_sharedData->versionMajor); 340 | printf("%hu.", (uint8_t)s_sharedData->versionMinor); 341 | printf("%hu ", (uint8_t)s_sharedData->versionPatch); 342 | printf("%c", (char)s_sharedData->buildType); 343 | gotoxy(0, 17); 344 | printf("rev %X%X", s_sharedData->git_sha1_h, s_sharedData->git_sha1_l); 345 | if (s_sharedData->git_status) { 346 | printf(" dirty"); 347 | } 348 | } 349 | 350 | return MENU_SYSTEM_INFO; 351 | } 352 | 353 | uint8_t drawscreenRGBTester(void) { 354 | moveCursor(4); 355 | 356 | if (buttonPressed(J_A)) { 357 | *SMEM_ADDR_LED_CONTROL = gCursor; 358 | 359 | } else if (buttonPressed(J_SELECT)) { 360 | *SMEM_ADDR_LED_CONTROL = 0; 361 | return MENU_GAME_MENU; 362 | 363 | } else if (buttonPressed(J_LEFT)) { 364 | *SMEM_ADDR_LED_CONTROL = 0; 365 | return MENU_SYSTEM_INFO; 366 | } 367 | 368 | if (gForceDrawScreen) { 369 | gotoxy(0, 0); 370 | printf("*** Test LED ***"); 371 | printf("Off\n"); 372 | printf("Red\n"); 373 | printf("Green\n"); 374 | printf("Blue\n"); 375 | } 376 | 377 | return MENU_RGB_TESTER; 378 | } 379 | 380 | inline void drawscreenGameSettingsUI(void) { 381 | gotoxy(0, 0); 382 | printf("*** Game Options ***"); 383 | 384 | gotoxy(0, 2); 385 | printf("%s", getRomNameForIndex(gLastSelectedGame)); 386 | 387 | gotoxy(0, 4); 388 | } 389 | 390 | uint8_t drawscreenGameSettingsSavegameHook(void) { 391 | gHighlightOffset = 5; 392 | moveCursor(3); 393 | 394 | if (buttonPressed(J_B)) { 395 | return MENU_GAME_MENU; 396 | } else if (buttonPressed(J_START)) { 397 | if (getRomInfoByteForIndex(gLastSelectedGame) == 1) { 398 | gSelectedMode = gCursor; 399 | return MENU_RTC_SETTINGS; 400 | } else { 401 | startGame(gLastSelectedGame, gCursor); 402 | } 403 | } 404 | 405 | if (gForceDrawScreen) { 406 | gSelectedMode = 0xFF; 407 | drawscreenGameSettingsUI(); 408 | printf("[Savegame Hook]"); 409 | gotoxy(2, 5); 410 | printf("Off"); 411 | gotoxy(2, 6); 412 | printf("Mode 1 (sel + b)"); 413 | gotoxy(2, 7); 414 | printf("Mode 2 (sel + dwn)"); 415 | } 416 | 417 | return MENU_GAME_SETTINGS; 418 | } 419 | 420 | uint8_t drawscreenGameSettingsRTC(void) { 421 | static uint8_t selectionX; // addresses fields from right to left 422 | struct TimePoint *real_rtc = &s_sharedData->timePoint; 423 | uint8_t gameStart = 0; 424 | uint8_t *modval = (uint8_t *)real_rtc; 425 | 426 | if (gForceDrawScreen) { 427 | selectionX = 1; 428 | } 429 | 430 | if (buttonPressed(J_B)) { 431 | return MENU_GAME_MENU; 432 | } else if (buttonPressed(J_START)) { 433 | gameStart = 1; 434 | } else if (buttonPressed(J_A)) { 435 | gameStart = 1; 436 | } else if (buttonPressed(J_UP)) { 437 | uint8_t oldval = ++modval[selectionX]; 438 | sanitizeRTCReal(real_rtc); 439 | if (modval[selectionX] == oldval - 1) { 440 | modval[selectionX] = 0; 441 | } 442 | 443 | gForceDrawScreen = 1; 444 | } else if (buttonPressed(J_DOWN)) { 445 | modval[selectionX]--; 446 | sanitizeRTCReal(real_rtc); 447 | 448 | gForceDrawScreen = 1; 449 | } else if (buttonPressed(J_LEFT)) { 450 | 451 | if (selectionX < 5) { 452 | selectionX++; 453 | } 454 | 455 | gForceDrawScreen = 1; 456 | } else if (buttonPressed(J_RIGHT)) { 457 | if (selectionX > 1) { 458 | selectionX--; 459 | } 460 | gForceDrawScreen = 1; 461 | } 462 | 463 | if (gameStart) { 464 | startGame(gLastSelectedGame, gSelectedMode); 465 | } 466 | 467 | if (gForceDrawScreen) { 468 | drawscreenGameSettingsUI(); 469 | printf("[RTC config]"); 470 | 471 | gotoxy(0, 10); 472 | printf("Real:"); 473 | gotoxy(1, 11); 474 | printf("YYYY MM DD HH MM"); 475 | gotoxy(1, 12); 476 | printf("%d ", real_rtc->Year + 1970); 477 | if (real_rtc->Month < 9) 478 | putchar('0'); 479 | printf("%d ", real_rtc->Month + 1); 480 | if (real_rtc->Day < 9) 481 | putchar('0'); 482 | printf("%d ", real_rtc->Day + 1); 483 | if (real_rtc->Hour < 10) 484 | putchar('0'); 485 | printf("%d ", real_rtc->Hour); 486 | if (real_rtc->Minute < 10) 487 | putchar('0'); 488 | printf("%d ", real_rtc->Minute); 489 | 490 | resetHighlights(); 491 | 492 | selectTile(18 - selectionX * 3, ROW_RTC_REAL, 0); 493 | selectTile(19 - selectionX * 3, ROW_RTC_REAL, 1); 494 | if (selectionX == 5) { 495 | selectTile(1, 12, 2); 496 | selectTile(2, 12, 3); 497 | } 498 | } 499 | 500 | return MENU_RTC_SETTINGS; 501 | } 502 | 503 | void drawscreen(void) { 504 | static uint8_t curScreen = MENU_GAME_MENU; 505 | uint8_t nextScreen = 0; 506 | 507 | switch (curScreen) { 508 | case MENU_GAME_SETTINGS: 509 | nextScreen = drawscreenGameSettingsSavegameHook(); 510 | break; 511 | 512 | case MENU_RTC_SETTINGS: 513 | nextScreen = drawscreenGameSettingsRTC(); 514 | break; 515 | 516 | case MENU_RGB_TESTER: 517 | nextScreen = drawscreenRGBTester(); 518 | break; 519 | 520 | case MENU_SYSTEM_INFO: 521 | nextScreen = drawscreenSystemInfo(); 522 | break; 523 | 524 | case MENU_GAME_MENU: 525 | default: 526 | nextScreen = drawscreenGameMenu(); 527 | break; 528 | } 529 | if (nextScreen != curScreen) { 530 | resetHighlights(); 531 | cls(); 532 | curScreen = nextScreen; 533 | gCursor = 0; 534 | gPageCursor = 0; 535 | gHighlightOffset = 1; 536 | gForceDrawScreen = 1; 537 | } else { 538 | gForceDrawScreen = 0; 539 | } 540 | } 541 | 542 | void scanline_isr(void) { 543 | if (gDMGHighlightLine != 0xFF) { 544 | if (((LY_REG + 1) >> 3) == gDMGHighlightLine) { 545 | BGP_REG = DMG_BKG_SELECTED_PALETTE; 546 | LYC_REG = ((gDMGHighlightLine + 1) << 3) - 1; 547 | return; 548 | } else { 549 | LYC_REG = (gDMGHighlightLine << 3) - 1; 550 | } 551 | } 552 | rBGP = DMG_BKG_NORMAL_PALETTE; 553 | } 554 | 555 | void main(void) { 556 | ENABLE_RAM_MBC1; 557 | 558 | *SMEM_ADDR_GAMEBOY_CPU = _cpu; 559 | 560 | if (DEVICE_SUPPORTS_COLOR) { 561 | set_bkg_palette(0, 2, &backgroundpalette[0]); 562 | set_sprite_palette(0, 2, &spritepalette[0]); 563 | } else { 564 | CRITICAL { 565 | STAT_REG = STATF_LYC; 566 | LYC_REG = 0; 567 | add_LCD(scanline_isr); 568 | } 569 | set_interrupts(IE_REG | LCD_IFLAG); 570 | 571 | OBP0_REG = DMG_TILE_NORMAL_PALETTE; 572 | OBP1_REG = DMG_TILE_SELECTED_PALETTE; 573 | } 574 | 575 | font_init(); 576 | // use pallete 2 as background as sprites have transparent background 577 | font_color(3, 2); 578 | font_t curFont = font_load(font_ibm); 579 | { 580 | uint8_t fontData[102 * 16]; 581 | get_bkg_data(0, 102, fontData); 582 | set_sprite_data(0, 102, fontData); 583 | } 584 | SHOW_SPRITES; 585 | 586 | font_init(); 587 | font_color(3, 0); // use std pallete 0 as background 588 | curFont = font_load(font_ibm); 589 | font_set(curFont); 590 | 591 | mode(M_TEXT_OUT | M_NO_SCROLL); 592 | 593 | set_bkg_data(102, 70, giraffe_4color_data); 594 | 595 | DISPLAY_ON; 596 | 597 | while (1) { 598 | vsync(); 599 | 600 | gCurrentInput = joypad(); 601 | 602 | drawscreen(); 603 | } // endless while 604 | } 605 | 606 | void sanitizeRTCReal(struct TimePoint *rtc) { 607 | if (rtc->Month > 11) 608 | rtc->Month = 11; 609 | 610 | uint8_t maxDay; 611 | if (rtc->Month == 1) { 612 | uint16_t year = 1970 + rtc->Year; 613 | if ((year & 3) == 0) { 614 | // divisible by 4 615 | if ((year % 100) == 0) { 616 | if ((year % 400) == 0) { 617 | // leap year 618 | maxDay = 28; 619 | } else { 620 | // not leap year 621 | maxDay = 27; 622 | } 623 | } else { 624 | // leap year 625 | maxDay = 28; 626 | } 627 | } else { 628 | // not leap year 629 | maxDay = 27; 630 | } 631 | } else if (((rtc->Month < 7) && (rtc->Month & 1)) || 632 | ((rtc->Month >= 7) && (rtc->Month & 1) == 0)) { 633 | maxDay = 29; 634 | } else { 635 | maxDay = 30; 636 | } 637 | if (rtc->Day > maxDay) 638 | rtc->Day = maxDay; 639 | if (rtc->Hour > 23) 640 | rtc->Hour = 23; 641 | if (rtc->Minute > 59) 642 | rtc->Minute = 59; 643 | } 644 | 645 | void startGame(uint8_t game, uint8_t mode) { 646 | *SMEM_ADDR_GAME_MODE_SELECTOR = mode; 647 | *SMEM_ADDR_GAME_SELECTOR = game; 648 | DISPLAY_OFF; 649 | *SMEM_ADDR_GAME_CONTROL = SMEM_GAME_START_MAGIC; 650 | while (1) { 651 | vsync(); 652 | } 653 | } 654 | -------------------------------------------------------------------------------- /gb-bootloader/giraffe_4color_data.c: -------------------------------------------------------------------------------- 1 | /* 2 | Generated By GameBoyPngConverter v2.0.0 3 | 4 | Tiles data 5 | 6 | Number of Tiles : 70 7 | */ 8 | 9 | const unsigned char giraffe_4color_data[] = { 10 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 11 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x01,0x01,0x01, 12 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC, 13 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 14 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF8,0xF8,0xF8,0xF8,0xF8,0xF8, 15 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x03, 16 | 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 17 | 0xC3,0xFF,0xC3,0xFF,0xC3,0xFF,0xC3,0xFF,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x00,0x00, 18 | 0x8E,0x8F,0x8E,0x8F,0x8E,0x8F,0xF1,0xFE,0xF1,0xFE,0xF1,0xFE,0xF3,0xFC,0x7F,0x70, 19 | 0x1F,0xE0,0x1F,0xE0,0x3F,0xC0,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00, 20 | 0x87,0x7F,0x87,0x7F,0x87,0x7F,0x80,0x7F,0x80,0x7F,0xC0,0x3F,0xE1,0x1E,0xFF,0x00, 21 | 0x1C,0x1F,0x1C,0x1F,0x1C,0x1F,0xFC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xE0,0xE0, 22 | 0x38,0xF8,0x38,0xF8,0x38,0xF8,0x38,0xF8,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0x00,0x00, 23 | 0x00,0x00,0x00,0x00,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03, 24 | 0x7F,0x70,0x7F,0x70,0xFF,0x80,0xF1,0x80,0xF1,0x80,0xF1,0x80,0xFF,0x8E,0xFF,0x8E, 25 | 0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00, 26 | 0xFF,0x00,0xFF,0x00,0xFF,0x00,0xF8,0x00,0xF8,0x00,0xF8,0x00,0xFF,0x07,0xFF,0x07, 27 | 0xE0,0xE0,0xE0,0xE0,0xFC,0x1C,0xFC,0x1C,0xFC,0x1C,0xFC,0x1C,0xFC,0x1C,0xFC,0x1C, 28 | 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03, 29 | 0xFF,0x8E,0xFF,0x81,0xFF,0x81,0xFF,0x81,0xFF,0xFE,0xFF,0xFE,0xFF,0xFE,0xFF,0x80, 30 | 0xFF,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00, 31 | 0xFF,0x07,0xFF,0xF8,0xFF,0xF8,0xFF,0xF8,0xFF,0x07,0xFF,0x07,0xFF,0x07,0xFF,0x00, 32 | 0xFC,0x1C,0xFC,0x1C,0xFC,0x1C,0xFC,0x1C,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0xFC,0x1C, 33 | 0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x03,0x00,0x00,0x00,0x00, 34 | 0xFF,0x80,0xFF,0x80,0xFF,0x8E,0xFF,0x8E,0xFF,0x8E,0xFF,0x8E,0x7F,0x70,0x7F,0x70, 35 | 0xFF,0x00,0xFF,0x00,0xFF,0x07,0xFF,0x07,0xFF,0x07,0xFF,0x07,0xFF,0x00,0xFF,0x00, 36 | 0xFC,0x1C,0xFC,0x1C,0xFC,0x1C,0xFC,0x1C,0xFC,0x1C,0xFC,0x1C,0xE0,0xE0,0xE0,0xE0, 37 | 0x7F,0x70,0x0F,0x0E,0x0F,0x0E,0x0F,0x0E,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01, 38 | 0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, 39 | 0xFF,0x00,0xFF,0x07,0xFF,0x07,0xFF,0x07,0xF8,0xF8,0xF8,0xF8,0xF8,0xF8,0xF8,0xF8, 40 | 0xE0,0xE0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 41 | 0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01, 42 | 0xC7,0xF8,0xC3,0xFC,0xC3,0xFC,0xC3,0xFC,0xC7,0xF8,0xC7,0xF8,0xFF,0xC0,0xFF,0xC0, 43 | 0xF8,0x38,0xF8,0x38,0xF8,0x38,0xF8,0x38,0xF8,0x38,0xF8,0x38,0xF8,0x38,0xF8,0x38, 44 | 0xFF,0xC0,0xC7,0xF8,0xC7,0xF8,0xC3,0xFC,0xC3,0xFC,0xC3,0xFC,0xC3,0xFC,0xC3,0xFC, 45 | 0x00,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x0E,0x0F,0x0E,0x0F,0x0E,0x0F, 46 | 0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x07,0xFF,0x07,0xFF,0x07,0xFF, 47 | 0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80, 48 | 0x01,0x01,0x01,0x01,0x01,0x01,0x0E,0x0F,0x0E,0x0F,0x0E,0x0F,0x0E,0x0F,0x0E,0x0F, 49 | 0xFF,0xC0,0xFF,0xC0,0xFF,0xC0,0x3F,0xC0,0x3F,0xC0,0x1F,0xE0,0x1F,0xE0,0x07,0xF8, 50 | 0xC7,0x3F,0x87,0x7F,0x87,0x7F,0x80,0x7F,0xC0,0x3F,0xE0,0x1F,0xFF,0x00,0xFF,0x00, 51 | 0xE0,0xE0,0xE0,0xE0,0xE0,0xE0,0xFF,0x1F,0xFF,0x1F,0xFF,0x1F,0xF0,0x0F,0xE0,0x1F, 52 | 0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x3C,0xC3,0x38,0xC7, 53 | 0x0E,0x0F,0x0E,0x0F,0x0E,0x0F,0x01,0x01,0x01,0x01,0x01,0x01,0xFE,0xFE,0xFE,0xFE, 54 | 0x07,0xFF,0x07,0xFF,0x07,0xFF,0xF8,0xF8,0xF8,0xF8,0xF8,0xF8,0x00,0x00,0x00,0x00, 55 | 0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 56 | 0x0E,0x0F,0x0E,0x0F,0x0E,0x0F,0x0E,0x0F,0x0E,0x0F,0x0F,0x0E,0x0F,0x0E,0x0F,0x0E, 57 | 0x03,0xFC,0x03,0xFC,0x1F,0xE0,0x1F,0xE0,0x1F,0xE0,0xFF,0x00,0xFF,0x00,0xFF,0x00, 58 | 0xE0,0x1F,0xF0,0x0F,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00, 59 | 0x38,0xC7,0x38,0xC7,0xF8,0x07,0xF8,0x07,0xFC,0x03,0xFF,0x00,0xFF,0x00,0xFF,0x00, 60 | 0xFE,0xFE,0xFE,0xFE,0x0E,0xFE,0x0E,0xFE,0x0E,0xFE,0xFF,0x01,0xFF,0x01,0xFF,0x01, 61 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0, 62 | 0x0F,0x0E,0x0F,0x0E,0x0F,0x0E,0x0F,0x0E,0x0F,0x0E,0x0E,0x0F,0x0E,0x0F,0x01,0x01, 63 | 0xF3,0x0C,0xE3,0x1C,0xE3,0x1C,0xE3,0x1C,0x83,0x7F,0x03,0xFF,0x03,0xFF,0xC3,0xFF, 64 | 0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x80,0xFF,0x80,0xFF,0x80,0xFF,0x80, 65 | 0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0xE0,0xFF,0xE0,0xFF,0xE0,0xFF,0xFF, 66 | 0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x00,0xFF,0x07,0xFF,0x07,0xFF,0x07,0xFF,0xFF, 67 | 0xFF,0x01,0xFF,0x01,0xFF,0x01,0xFF,0x01,0xFF,0x01,0xFF,0x01,0xFF,0x01,0xFF,0x01, 68 | 0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0, 69 | 0xC3,0xFF,0xC3,0xFF,0xC3,0xFF,0xC3,0xFF,0xC3,0xFF,0xFF,0xC3,0xFF,0xC3,0xFF,0xC3, 70 | 0xFF,0x80,0xFF,0x80,0xFF,0x80,0xFF,0x80,0xFF,0x80,0xFD,0x82,0xFC,0x83,0xF8,0x87, 71 | 0xFF,0xFF,0xFF,0xFF,0xE1,0xE1,0xE1,0xE1,0xE1,0xE1,0xE1,0xE1,0xE1,0xE1,0xE1,0xE1, 72 | 0xFF,0xFF,0xFF,0xFF,0xFF,0xC7,0xFF,0xC7,0xFF,0xC7,0xFF,0xC7,0xFF,0xC7,0xFF,0xC7, 73 | 0xFF,0x01,0xFF,0x01,0xFE,0x0E,0xFE,0x0E,0xFE,0x0E,0xFE,0x0E,0xFE,0x0E,0xFE,0x0E, 74 | 0xC0,0xC0,0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 75 | 0xFF,0xC3,0xFF,0xC3,0xFF,0xC3,0xFF,0xC3,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x00,0x00, 76 | 0xF8,0x87,0xF8,0x87,0xFC,0x83,0xFC,0x83,0x7F,0x7F,0x7F,0x7F,0x7F,0x7F,0x00,0x00, 77 | 0xE1,0xE1,0xE1,0xE1,0xE1,0xE1,0xE1,0xE1,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 78 | 0xFF,0xC7,0xFF,0xC7,0xFF,0xC7,0xFF,0xC7,0x38,0x38,0x38,0x38,0x38,0x38,0x00,0x00, 79 | 0xFE,0x0E,0xFE,0x0E,0xFE,0x0E,0xFE,0x0E,0xF0,0xF0,0xF0,0xF0,0xF0,0xF0,0x00,0x00 80 | }; -------------------------------------------------------------------------------- /gb-bootloader/giraffe_4color_data.h: -------------------------------------------------------------------------------- 1 | #ifndef E4782DD3_AA8F_4FD4_B440_FEE5E836FA72 2 | #define E4782DD3_AA8F_4FD4_B440_FEE5E836FA72 3 | 4 | extern const unsigned char giraffe_4color_data[]; 5 | 6 | #endif /* E4782DD3_AA8F_4FD4_B440_FEE5E836FA72 */ 7 | -------------------------------------------------------------------------------- /gb-bootloader/giraffe_4color_map.c: -------------------------------------------------------------------------------- 1 | /* 2 | Generated By GameBoyPngConverter v2.0.0 3 | 4 | Tiles map 5 | 6 | TileMap Size : 20 x 18 7 | */ 8 | 9 | const unsigned char giraffe_4color_map[] = { 10 | 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 11 | 0x66, 0x66, 0x66, 0x66, 0x66, 0x67, 0x68, 0x67, 0x69, 0x6a, 0x6b, 0x6a, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 12 | 0x66, 0x66, 0x66, 0x66, 0x66, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 13 | 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x73, 0x74, 0x75, 0x76, 0x77, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 14 | 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 15 | 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x7d, 0x7e, 0x75, 0x7f, 0x80, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 16 | 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x81, 0x82, 0x83, 0x84, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 17 | 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x85, 0x86, 0x87, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 18 | 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x85, 0x88, 0x87, 0x66, 0x66, 0x89, 0x8a, 0x8b, 0x66, 0x66, 0x66, 0x66, 0x66, 19 | 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x66, 0x66, 0x66, 0x66, 0x66, 20 | 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x94, 0x95, 0x75, 0x96, 0x97, 0x98, 0x99, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 21 | 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xa0, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 22 | 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x85, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 23 | 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x6c, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 24 | 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 25 | 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 26 | 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 27 | 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66 28 | }; 29 | -------------------------------------------------------------------------------- /gb-bootloader/giraffe_4color_map.h: -------------------------------------------------------------------------------- 1 | #ifndef B8B88911_2FB2_4877_96F3_8DC8DE08CD7F 2 | #define B8B88911_2FB2_4877_96F3_8DC8DE08CD7F 3 | 4 | extern const unsigned char giraffe_4color_map[]; 5 | 6 | #endif /* B8B88911_2FB2_4877_96F3_8DC8DE08CD7F */ 7 | -------------------------------------------------------------------------------- /gb-vblankhook/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | set(RGBASM rgbasm) 4 | set(RGBLINK rgblink) 5 | 6 | add_custom_command( 7 | OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/gbSaveGameVBlankHook.o 8 | DEPENDS gbSaveGameVBlankHook.asm 9 | COMMAND ${RGBASM} -L -o ${CMAKE_CURRENT_BINARY_DIR}/gbSaveGameVBlankHook.o gbSaveGameVBlankHook.asm 10 | WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} 11 | ) 12 | 13 | add_custom_command( 14 | OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/gbSaveGameVBlankHook.gb 15 | DEPENDS gbSaveGameVBlankHook.o 16 | COMMAND ${RGBLINK} -x -o gbSaveGameVBlankHook.gb gbSaveGameVBlankHook.o 17 | ) 18 | 19 | add_custom_command( 20 | OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/gbSaveGameVBlankHook.h 21 | DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/gbSaveGameVBlankHook.gb 22 | COMMAND ${CMAKE_COMMAND} 23 | -DSOURCE_FILE="${CMAKE_CURRENT_BINARY_DIR}/gbSaveGameVBlankHook.gb" 24 | -DHEADER_FILE="${CMAKE_CURRENT_BINARY_DIR}/gbSaveGameVBlankHook.h" 25 | -DVARIABLE_NAME="GB_VBLANK_HOOK" 26 | -P ${CMAKE_HELPERS_DIR}/executebin2h.cmake 27 | ) 28 | 29 | add_custom_target(gbSaveGameVBlankHook ALL 30 | DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/gbSaveGameVBlankHook.h 31 | ) 32 | -------------------------------------------------------------------------------- /gb-vblankhook/gbSaveGameVBlankHook.asm: -------------------------------------------------------------------------------- 1 | INCLUDE "hardware.inc" 2 | 3 | SECTION "addresses", ROM0[0] 4 | ; store the jump adresses here so the cartridge can find them 5 | dw vblank_handler 6 | dw trigger_saving 7 | 8 | SECTION "VBlank", ROM0[$40] 9 | push af 10 | 11 | ; select the joypad buttons 12 | ld a,$10 13 | ldh [rP1],a 14 | jp vblank_handler 15 | 16 | SECTION "hook", ROM0[$50] 17 | vblank_handler: 18 | ; wait for joypad lines to settle 19 | push hl 20 | pop hl 21 | push hl 22 | pop hl 23 | 24 | ; read joypad and check for start or select being pressed 25 | ldh a,[rP1] 26 | cpl 27 | and $0c 28 | call nz,read_from_joypad 29 | 30 | ; reset joypad 31 | ld a,$30 32 | ldh [rP1],a 33 | 34 | pop af 35 | 36 | jp $40 ; jump to original vblank handler 37 | 38 | read_from_joypad: 39 | push bc 40 | push hl 41 | 42 | ; read buttons again and store them in higher nibble of b 43 | ldh a,[rP1] 44 | cpl 45 | and $0f 46 | swap a 47 | ld b,a 48 | 49 | ; read the directional pads and xor them together with b in a 50 | ld a,$20 51 | ld [rP1],a 52 | ld a,[rP1] 53 | ld a,[rP1] 54 | cpl 55 | and $0f 56 | xor a, b ; the higher nibble of b contains the buttons 57 | 58 | call check_for_save_trigger 59 | 60 | pop hl 61 | pop bc 62 | 63 | ret 64 | 65 | 66 | check_for_save_trigger: 67 | ; we now that select is being held, or we would not have ended up here 68 | 69 | ; check for b 70 | bit 5,a 71 | 72 | ; check for down 73 | ; bit 3,a 74 | jp nz, trigger_saving 75 | 76 | do_nothing: 77 | ret 78 | 79 | 80 | SECTION "trigger_save", ROM0[$100] 81 | trigger_saving: 82 | ; the cartridge will recognize that we jumped here and start 83 | ; storing the saveram to flash. We just need to idle here a bit 84 | 85 | ; prepare to read from buttons again 86 | ld a,$10 87 | ldh [rP1],a 88 | 89 | ; wait for beginning of vblank and switch off screen 90 | ld hl,rLCDC 91 | screen_off_loop: 92 | ld a,[rLY] 93 | cp $90 94 | jr nz, screen_off_loop 95 | res 7,[hl] 96 | 97 | ; wait until the cartridge has written the savegame to flash 98 | ; the cartridge will actually change the byte at the rom location 99 | ; after the storing ist done 100 | wait_for_game_save: 101 | ld a,[$1FF] 102 | cp $AA 103 | jr nz, wait_for_game_save 104 | 105 | wait_for_buttons_release: 106 | ; read joypad and check for start or select being pressed 107 | ldh a,[rP1] 108 | cpl 109 | and $0c 110 | jr nz, wait_for_buttons_release 111 | 112 | ; turn the screen back on 113 | ld hl,rLCDC 114 | set 7,[hl] 115 | 116 | ; wait for vblank - so game always gets full vblank time 117 | wait_vbl: 118 | ld a,[rLY] 119 | cp $90 120 | jr nz,wait_vbl 121 | 122 | ret 123 | -------------------------------------------------------------------------------- /libs/git-commit-tracking/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.2...3.27) 2 | project(cmake_git_commit_tracking 3 | LANGUAGES C) 4 | 5 | # Define the two required variables before including 6 | # the source code for watching a git repository. 7 | set(PRE_CONFIGURE_FILE "git_commit.c.in") 8 | set(POST_CONFIGURE_FILE "${CMAKE_CURRENT_BINARY_DIR}/git_commit.c") 9 | include(git_watcher.cmake) 10 | 11 | # Create a library out of the compiled post-configure file. 12 | # 13 | # Note that the include is a system include. This was done 14 | # so downstream projects don't suffer from warnings on a 15 | # 3rdparty library. 16 | add_library(${PROJECT_NAME} STATIC ${POST_CONFIGURE_FILE}) 17 | target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) 18 | add_dependencies(${PROJECT_NAME} check_git) 19 | 20 | # The C99 standard is only required because we're using . 21 | # This could be removed if it's a problem for users, but would require the 22 | # cmake configure() commands to translate true/false literals to 1/0. 23 | set_property(TARGET ${PROJECT_NAME} PROPERTY C_STANDARD 99) -------------------------------------------------------------------------------- /libs/git-commit-tracking/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Andrew Hardin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /libs/git-commit-tracking/git_commit.c.in: -------------------------------------------------------------------------------- 1 | #include "git_commit.h" 2 | 3 | bool git_IsPopulated() { 4 | return @GIT_RETRIEVED_STATE@; 5 | } 6 | bool git_AnyUncommittedChanges() { 7 | return @GIT_IS_DIRTY@; 8 | } 9 | const char* git_AuthorName() { 10 | return "@GIT_AUTHOR_NAME@"; 11 | } 12 | const char* git_AuthorEmail() { 13 | return "@GIT_AUTHOR_EMAIL@"; 14 | } 15 | const char* git_CommitSHA1() { 16 | return "@GIT_HEAD_SHA1@"; 17 | } 18 | uint32_t git_CommitSHA1Short() { 19 | return 0x@GIT_HEAD_SHA1_SHORT@; 20 | } 21 | const char* git_CommitDate() { 22 | return "@GIT_COMMIT_DATE_ISO8601@"; 23 | } 24 | const char* git_CommitSubject() { 25 | return "@GIT_COMMIT_SUBJECT@"; 26 | } 27 | const char* git_CommitBody() { 28 | return "@GIT_COMMIT_BODY@"; 29 | } 30 | const char* git_Describe() { 31 | return "@GIT_DESCRIBE@"; 32 | } 33 | const char* git_Branch() { 34 | return "@GIT_BRANCH@"; 35 | } 36 | -------------------------------------------------------------------------------- /libs/git-commit-tracking/git_commit.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | // git.h 3 | // https://raw.githubusercontent.com/andrew-hardin/cmake-git-version-tracking/master/git.h 4 | // 5 | // Released under the MIT License. 6 | // https://raw.githubusercontent.com/andrew-hardin/cmake-git-version-tracking/master/LICENSE 7 | 8 | #include 9 | #include 10 | 11 | #ifdef __cplusplus 12 | #define GIT_VERSION_TRACKING_EXTERN_C_BEGIN extern "C" { 13 | #define GIT_VERSION_TRACKING_EXTERN_C_END } 14 | #else 15 | #define GIT_VERSION_TRACKING_EXTERN_C_BEGIN 16 | #define GIT_VERSION_TRACKING_EXTERN_C_END 17 | #endif 18 | 19 | // Don't mangle the C function names if included in a CXX file. 20 | GIT_VERSION_TRACKING_EXTERN_C_BEGIN 21 | 22 | /// Is the metadata populated? 23 | // 24 | /// We may not have metadata if there wasn't a .git directory 25 | /// (e.g. downloaded source code without revision history). 26 | bool git_IsPopulated(); 27 | 28 | /// Were there any uncommitted changes that won't be reflected 29 | /// in the CommitID? 30 | bool git_AnyUncommittedChanges(); 31 | 32 | /// The commit author's name. 33 | const char* git_AuthorName(); 34 | 35 | /// The commit author's email. 36 | const char* git_AuthorEmail(); 37 | 38 | /// The commit SHA1. 39 | const char* git_CommitSHA1(); 40 | 41 | /// The commit SHA1 (short). 42 | uint32_t git_CommitSHA1Short(); 43 | 44 | /// The ISO8601 commit date. 45 | const char* git_CommitDate(); 46 | 47 | /// The commit subject. 48 | const char* git_CommitSubject(); 49 | 50 | /// The commit body. 51 | const char* git_CommitBody(); 52 | 53 | /// The commit describe. 54 | const char* git_Describe(); 55 | 56 | /// The symbolic reference tied to HEAD. 57 | const char* git_Branch(); 58 | 59 | GIT_VERSION_TRACKING_EXTERN_C_END 60 | #undef GIT_VERSION_TRACKING_EXTERN_C_BEGIN 61 | #undef GIT_VERSION_TRACKING_EXTERN_C_END 62 | 63 | #ifdef __cplusplus 64 | 65 | /// This is a utility extension for C++ projects. 66 | /// It provides a "git" namespace that wraps the 67 | /// C methods in more(?) ergonomic types. 68 | /// 69 | /// This is header-only in an effort to keep the 70 | /// underlying static library C99 compliant. 71 | 72 | 73 | // We really want to use std::string_view if it appears 74 | // that the compiler will support it. If that fails, 75 | // revert back to std::string. 76 | #define GIT_VERSION_TRACKING_CPP_17_STANDARD 201703L 77 | #if __cplusplus >= GIT_VERSION_TRACKING_CPP_17_STANDARD 78 | #define GIT_VERSION_USE_STRING_VIEW 1 79 | #else 80 | #define GIT_VERSION_USE_STRING_VIEW 0 81 | #endif 82 | 83 | 84 | #if GIT_VERSION_USE_STRING_VIEW 85 | #include 86 | #include 87 | #else 88 | #include 89 | #endif 90 | 91 | namespace git { 92 | 93 | #if GIT_VERSION_USE_STRING_VIEW 94 | using StringOrView = std::string_view; 95 | #else 96 | typedef std::string StringOrView; 97 | #endif 98 | 99 | namespace internal { 100 | 101 | /// Short-hand method for initializing a std::string or std::string_view given a C-style const char*. 102 | inline const StringOrView InitString(const char* from_c_interface) { 103 | #if GIT_VERSION_USE_STRING_VIEW 104 | return StringOrView { from_c_interface, std::strlen(from_c_interface) }; 105 | #else 106 | return std::string(from_c_interface); 107 | #endif 108 | } 109 | 110 | } // namespace internal 111 | 112 | inline bool IsPopulated() { 113 | return git_IsPopulated(); 114 | } 115 | inline bool AnyUncommittedChanges() { 116 | return git_AnyUncommittedChanges(); 117 | } 118 | inline const StringOrView& AuthorName() { 119 | static const StringOrView kValue = internal::InitString(git_AuthorName()); 120 | return kValue; 121 | } 122 | inline const StringOrView AuthorEmail() { 123 | static const StringOrView kValue = internal::InitString(git_AuthorEmail()); 124 | return kValue; 125 | } 126 | inline const StringOrView CommitSHA1() { 127 | static const StringOrView kValue = internal::InitString(git_CommitSHA1()); 128 | return kValue; 129 | } 130 | inline const StringOrView CommitDate() { 131 | static const StringOrView kValue = internal::InitString(git_CommitDate()); 132 | return kValue; 133 | } 134 | inline const StringOrView CommitSubject() { 135 | static const StringOrView kValue = internal::InitString(git_CommitSubject()); 136 | return kValue; 137 | } 138 | inline const StringOrView CommitBody() { 139 | static const StringOrView kValue = internal::InitString(git_CommitBody()); 140 | return kValue; 141 | } 142 | inline const StringOrView Describe() { 143 | static const StringOrView kValue = internal::InitString(git_Describe()); 144 | return kValue; 145 | } 146 | inline const StringOrView Branch() { 147 | static const StringOrView kValue = internal::InitString(git_Branch()); 148 | return kValue; 149 | } 150 | 151 | } // namespace git 152 | 153 | 154 | // Cleanup our defines to avoid polluting. 155 | #undef GIT_VERSION_USE_STRING_VIEW 156 | #undef GIT_VERSION_TRACKING_CPP_17_STANDARD 157 | 158 | #endif // __cplusplus 159 | -------------------------------------------------------------------------------- /libs/git-commit-tracking/git_watcher.cmake: -------------------------------------------------------------------------------- 1 | # git_watcher.cmake 2 | # https://raw.githubusercontent.com/andrew-hardin/cmake-git-version-tracking/master/git_watcher.cmake 3 | # 4 | # Released under the MIT License. 5 | # https://raw.githubusercontent.com/andrew-hardin/cmake-git-version-tracking/master/LICENSE 6 | 7 | 8 | # This file defines a target that monitors the state of a git repo. 9 | # If the state changes (e.g. a commit is made), then a file gets reconfigured. 10 | # Here are the primary variables that control script behavior: 11 | # 12 | # PRE_CONFIGURE_FILE (REQUIRED) 13 | # -- The path to the file that'll be configured. 14 | # 15 | # POST_CONFIGURE_FILE (REQUIRED) 16 | # -- The path to the configured PRE_CONFIGURE_FILE. 17 | # 18 | # GIT_STATE_FILE (OPTIONAL) 19 | # -- The path to the file used to store the previous build's git state. 20 | # Defaults to the current binary directory. 21 | # 22 | # GIT_WORKING_DIR (OPTIONAL) 23 | # -- The directory from which git commands will be run. 24 | # Defaults to the directory with the top level CMakeLists.txt. 25 | # 26 | # GIT_EXECUTABLE (OPTIONAL) 27 | # -- The path to the git executable. It'll automatically be set if the 28 | # user doesn't supply a path. 29 | # 30 | # GIT_FAIL_IF_NONZERO_EXIT (OPTIONAL) 31 | # -- Raise a FATAL_ERROR if any of the git commands return a non-zero 32 | # exit code. This is set to TRUE by default. You can set this to FALSE 33 | # if you'd like the build to continue even if a git command fails. 34 | # 35 | # GIT_IGNORE_UNTRACKED (OPTIONAL) 36 | # -- Ignore the presence of untracked files when detecting if the 37 | # working tree is dirty. This is set to FALSE by default. 38 | # 39 | # DESIGN 40 | # - This script was designed similar to a Python application 41 | # with a Main() function. I wanted to keep it compact to 42 | # simplify "copy + paste" usage. 43 | # 44 | # - This script is invoked under two CMake contexts: 45 | # 1. Configure time (when build files are created). 46 | # 2. Build time (called via CMake -P). 47 | # The first invocation is what registers the script to 48 | # be executed at build time. 49 | # 50 | # MODIFICATIONS 51 | # You may wish to track other git properties like when the last 52 | # commit was made. There are two sections you need to modify, 53 | # and they're tagged with a ">>>" header. 54 | 55 | # Short hand for converting paths to absolute. 56 | macro(PATH_TO_ABSOLUTE var_name) 57 | get_filename_component(${var_name} "${${var_name}}" ABSOLUTE) 58 | endmacro() 59 | 60 | # Check that a required variable is set. 61 | macro(CHECK_REQUIRED_VARIABLE var_name) 62 | if(NOT DEFINED ${var_name}) 63 | message(FATAL_ERROR "The \"${var_name}\" variable must be defined.") 64 | endif() 65 | PATH_TO_ABSOLUTE(${var_name}) 66 | endmacro() 67 | 68 | # Check that an optional variable is set, or, set it to a default value. 69 | macro(CHECK_OPTIONAL_VARIABLE_NOPATH var_name default_value) 70 | if(NOT DEFINED ${var_name}) 71 | set(${var_name} ${default_value}) 72 | endif() 73 | endmacro() 74 | 75 | # Check that an optional variable is set, or, set it to a default value. 76 | # Also converts that path to an abspath. 77 | macro(CHECK_OPTIONAL_VARIABLE var_name default_value) 78 | CHECK_OPTIONAL_VARIABLE_NOPATH(${var_name} ${default_value}) 79 | PATH_TO_ABSOLUTE(${var_name}) 80 | endmacro() 81 | 82 | CHECK_REQUIRED_VARIABLE(PRE_CONFIGURE_FILE) 83 | CHECK_REQUIRED_VARIABLE(POST_CONFIGURE_FILE) 84 | CHECK_OPTIONAL_VARIABLE(GIT_STATE_FILE "${CMAKE_CURRENT_BINARY_DIR}/git-state-hash") 85 | CHECK_OPTIONAL_VARIABLE(GIT_WORKING_DIR "${CMAKE_SOURCE_DIR}") 86 | CHECK_OPTIONAL_VARIABLE_NOPATH(GIT_FAIL_IF_NONZERO_EXIT TRUE) 87 | CHECK_OPTIONAL_VARIABLE_NOPATH(GIT_IGNORE_UNTRACKED FALSE) 88 | 89 | # Check the optional git variable. 90 | # If it's not set, we'll try to find it using the CMake packaging system. 91 | if(NOT DEFINED GIT_EXECUTABLE) 92 | find_package(Git QUIET REQUIRED) 93 | endif() 94 | CHECK_REQUIRED_VARIABLE(GIT_EXECUTABLE) 95 | 96 | 97 | set(_state_variable_names 98 | GIT_RETRIEVED_STATE 99 | GIT_HEAD_SHA1 100 | GIT_HEAD_SHA1_SHORT 101 | GIT_IS_DIRTY 102 | GIT_AUTHOR_NAME 103 | GIT_AUTHOR_EMAIL 104 | GIT_COMMIT_DATE_ISO8601 105 | GIT_COMMIT_SUBJECT 106 | GIT_COMMIT_BODY 107 | GIT_DESCRIBE 108 | GIT_BRANCH 109 | # >>> 110 | # 1. Add the name of the additional git variable you're interested in monitoring 111 | # to this list. 112 | ) 113 | 114 | 115 | 116 | # Macro: RunGitCommand 117 | # Description: short-hand macro for calling a git function. Outputs are the 118 | # "exit_code" and "output" variables. The "_permit_git_failure" 119 | # variable can locally override the exit code checking- use it 120 | # with caution. 121 | macro(RunGitCommand) 122 | execute_process(COMMAND 123 | "${GIT_EXECUTABLE}" ${ARGV} 124 | WORKING_DIRECTORY "${_working_dir}" 125 | RESULT_VARIABLE exit_code 126 | OUTPUT_VARIABLE output 127 | ERROR_VARIABLE stderr 128 | OUTPUT_STRIP_TRAILING_WHITESPACE) 129 | if(NOT exit_code EQUAL 0 AND NOT _permit_git_failure) 130 | set(ENV{GIT_RETRIEVED_STATE} "false") 131 | 132 | # Issue 26: git info not properly set 133 | # 134 | # Check if we should fail if any of the exit codes are non-zero. 135 | # Most methods have a fall-back default value that's used in case of non-zero 136 | # exit codes. If you're feeling risky, disable this safety check and use 137 | # those default values. 138 | if(GIT_FAIL_IF_NONZERO_EXIT ) 139 | string(REPLACE ";" " " args_with_spaces "${ARGV}") 140 | message(FATAL_ERROR "${stderr} (${GIT_EXECUTABLE} ${args_with_spaces})") 141 | endif() 142 | endif() 143 | endmacro() 144 | 145 | 146 | 147 | # Function: GetGitState 148 | # Description: gets the current state of the git repo. 149 | # Args: 150 | # _working_dir (in) string; the directory from which git commands will be executed. 151 | function(GetGitState _working_dir) 152 | 153 | # This is an error code that'll be set to FALSE if the 154 | # RunGitCommand ever returns a non-zero exit code. 155 | set(ENV{GIT_RETRIEVED_STATE} "true") 156 | 157 | # Get whether or not the working tree is dirty. 158 | if (GIT_IGNORE_UNTRACKED) 159 | set(untracked_flag "-uno") 160 | else() 161 | set(untracked_flag "-unormal") 162 | endif() 163 | RunGitCommand(status --porcelain ${untracked_flag}) 164 | if(NOT exit_code EQUAL 0) 165 | set(ENV{GIT_IS_DIRTY} "false") 166 | else() 167 | if(NOT "${output}" STREQUAL "") 168 | set(ENV{GIT_IS_DIRTY} "true") 169 | else() 170 | set(ENV{GIT_IS_DIRTY} "false") 171 | endif() 172 | endif() 173 | 174 | # There's a long list of attributes grabbed from git show. 175 | set(object HEAD) 176 | RunGitCommand(show -s "--format=%H" ${object}) 177 | if(exit_code EQUAL 0) 178 | set(ENV{GIT_HEAD_SHA1} ${output}) 179 | endif() 180 | 181 | RunGitCommand(show -s "--format=%h" ${object}) 182 | if(exit_code EQUAL 0) 183 | set(ENV{GIT_HEAD_SHA1_SHORT} ${output}) 184 | endif() 185 | 186 | RunGitCommand(show -s "--format=%an" ${object}) 187 | if(exit_code EQUAL 0) 188 | set(ENV{GIT_AUTHOR_NAME} "${output}") 189 | endif() 190 | 191 | RunGitCommand(show -s "--format=%ae" ${object}) 192 | if(exit_code EQUAL 0) 193 | set(ENV{GIT_AUTHOR_EMAIL} "${output}") 194 | endif() 195 | 196 | RunGitCommand(show -s "--format=%ci" ${object}) 197 | if(exit_code EQUAL 0) 198 | set(ENV{GIT_COMMIT_DATE_ISO8601} "${output}") 199 | endif() 200 | 201 | RunGitCommand(show -s "--format=%s" ${object}) 202 | if(exit_code EQUAL 0) 203 | # Escape \ 204 | string(REPLACE "\\" "\\\\" output "${output}") 205 | # Escape quotes 206 | string(REPLACE "\"" "\\\"" output "${output}") 207 | set(ENV{GIT_COMMIT_SUBJECT} "${output}") 208 | endif() 209 | 210 | RunGitCommand(show -s "--format=%b" ${object}) 211 | if(exit_code EQUAL 0) 212 | if(output) 213 | # Escape \ 214 | string(REPLACE "\\" "\\\\" output "${output}") 215 | # Escape quotes 216 | string(REPLACE "\"" "\\\"" output "${output}") 217 | # Escape line breaks in the commit message. 218 | string(REPLACE "\r\n" "\\r\\n\\\r\n" safe "${output}") 219 | if(safe STREQUAL output) 220 | # Didn't have windows lines - try unix lines. 221 | string(REPLACE "\n" "\\n\\\n" safe "${output}") 222 | endif() 223 | else() 224 | # There was no commit body - set the safe string to empty. 225 | set(safe "") 226 | endif() 227 | set(ENV{GIT_COMMIT_BODY} "${safe}") 228 | else() 229 | set(ENV{GIT_COMMIT_BODY} "") # empty string. 230 | endif() 231 | 232 | # Get output of git describe 233 | RunGitCommand(describe --always ${object}) 234 | if(NOT exit_code EQUAL 0) 235 | set(ENV{GIT_DESCRIBE} "unknown") 236 | else() 237 | set(ENV{GIT_DESCRIBE} "${output}") 238 | endif() 239 | 240 | # Convert HEAD to a symbolic ref. This can fail, in which case we just 241 | # set that variable to HEAD. 242 | set(_permit_git_failure ON) 243 | RunGitCommand(symbolic-ref --short -q ${object}) 244 | unset(_permit_git_failure) 245 | if(NOT exit_code EQUAL 0) 246 | set(ENV{GIT_BRANCH} "${object}") 247 | else() 248 | set(ENV{GIT_BRANCH} "${output}") 249 | endif() 250 | 251 | # >>> 252 | # 2. Additional git properties can be added here via the 253 | # "execute_process()" command. Be sure to set them in 254 | # the environment using the same variable name you added 255 | # to the "_state_variable_names" list. 256 | 257 | endfunction() 258 | 259 | 260 | 261 | # Function: GitStateChangedAction 262 | # Description: this function is executed when the state of the git 263 | # repository changes (e.g. a commit is made). 264 | function(GitStateChangedAction) 265 | foreach(var_name ${_state_variable_names}) 266 | set(${var_name} $ENV{${var_name}}) 267 | endforeach() 268 | configure_file("${PRE_CONFIGURE_FILE}" "${POST_CONFIGURE_FILE}" @ONLY) 269 | endfunction() 270 | 271 | 272 | 273 | # Function: HashGitState 274 | # Description: loop through the git state variables and compute a unique hash. 275 | # Args: 276 | # _state (out) string; a hash computed from the current git state. 277 | function(HashGitState _state) 278 | set(ans "") 279 | foreach(var_name ${_state_variable_names}) 280 | string(SHA256 ans "${ans}$ENV{${var_name}}") 281 | endforeach() 282 | set(${_state} ${ans} PARENT_SCOPE) 283 | endfunction() 284 | 285 | 286 | 287 | # Function: CheckGit 288 | # Description: check if the git repo has changed. If so, update the state file. 289 | # Args: 290 | # _working_dir (in) string; the directory from which git commands will be ran. 291 | # _state_changed (out) bool; whether or no the state of the repo has changed. 292 | function(CheckGit _working_dir _state_changed) 293 | 294 | # Get the current state of the repo. 295 | GetGitState("${_working_dir}") 296 | 297 | # Convert that state into a hash that we can compare against 298 | # the hash stored on-disk. 299 | HashGitState(state) 300 | 301 | # Issue 14: post-configure file isn't being regenerated. 302 | # 303 | # Update the state to include the SHA256 for the pre-configure file. 304 | # This forces the post-configure file to be regenerated if the 305 | # pre-configure file has changed. 306 | file(SHA256 ${PRE_CONFIGURE_FILE} preconfig_hash) 307 | string(SHA256 state "${preconfig_hash}${state}") 308 | 309 | # Check if the state has changed compared to the backup on disk. 310 | if(EXISTS "${GIT_STATE_FILE}") 311 | file(READ "${GIT_STATE_FILE}" OLD_HEAD_CONTENTS) 312 | if(OLD_HEAD_CONTENTS STREQUAL "${state}") 313 | # State didn't change. 314 | set(${_state_changed} "false" PARENT_SCOPE) 315 | return() 316 | endif() 317 | endif() 318 | 319 | # The state has changed. 320 | # We need to update the state file on disk. 321 | # Future builds will compare their state to this file. 322 | file(WRITE "${GIT_STATE_FILE}" "${state}") 323 | set(${_state_changed} "true" PARENT_SCOPE) 324 | endfunction() 325 | 326 | 327 | 328 | # Function: SetupGitMonitoring 329 | # Description: this function sets up custom commands that make the build system 330 | # check the state of git before every build. If the state has 331 | # changed, then a file is configured. 332 | function(SetupGitMonitoring) 333 | add_custom_target(check_git 334 | ALL 335 | DEPENDS ${PRE_CONFIGURE_FILE} 336 | BYPRODUCTS 337 | ${POST_CONFIGURE_FILE} 338 | ${GIT_STATE_FILE} 339 | COMMENT "Checking the git repository for changes..." 340 | COMMAND 341 | ${CMAKE_COMMAND} 342 | -D_BUILD_TIME_CHECK_GIT=TRUE 343 | -DGIT_WORKING_DIR=${GIT_WORKING_DIR} 344 | -DGIT_EXECUTABLE=${GIT_EXECUTABLE} 345 | -DGIT_STATE_FILE=${GIT_STATE_FILE} 346 | -DPRE_CONFIGURE_FILE=${PRE_CONFIGURE_FILE} 347 | -DPOST_CONFIGURE_FILE=${POST_CONFIGURE_FILE} 348 | -DGIT_FAIL_IF_NONZERO_EXIT=${GIT_FAIL_IF_NONZERO_EXIT} 349 | -DGIT_IGNORE_UNTRACKED=${GIT_IGNORE_UNTRACKED} 350 | -P "${CMAKE_CURRENT_LIST_FILE}") 351 | endfunction() 352 | 353 | 354 | 355 | # Function: Main 356 | # Description: primary entry-point to the script. Functions are selected based 357 | # on whether it's configure or build time. 358 | function(Main) 359 | if(_BUILD_TIME_CHECK_GIT) 360 | # Check if the repo has changed. 361 | # If so, run the change action. 362 | CheckGit("${GIT_WORKING_DIR}" changed) 363 | if(changed OR NOT EXISTS "${POST_CONFIGURE_FILE}") 364 | GitStateChangedAction() 365 | endif() 366 | else() 367 | # >> Executes at configure time. 368 | SetupGitMonitoring() 369 | endif() 370 | endfunction() 371 | 372 | # And off we go... 373 | Main() 374 | -------------------------------------------------------------------------------- /libs/pico-littlefs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(littlefs-lib) 2 | 3 | add_library(littlefs-lib STATIC lfs.c lfs_util.c lfs_pico_hal.c) 4 | 5 | target_include_directories(littlefs-lib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) 6 | target_link_libraries(littlefs-lib PUBLIC hardware_flash) 7 | -------------------------------------------------------------------------------- /libs/pico-littlefs/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022, The littlefs authors. 2 | Copyright (c) 2017, Arm Limited. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | - Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | - Redistributions in binary form must reproduce the above copyright notice, this 10 | list of conditions and the following disclaimer in the documentation and/or 11 | other materials provided with the distribution. 12 | - Neither the name of ARM nor the names of its contributors may be used to 13 | endorse or promote products derived from this software without specific prior 14 | written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 23 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /libs/pico-littlefs/lfs_pico_hal.c: -------------------------------------------------------------------------------- 1 | /* Copyright (C) 1883 Thomas Edison - All Rights Reserved 2 | * You may use, distribute and modify this code under the 3 | * terms of the BSD 3 clause license, which unfortunately 4 | * won't be written for another century. 5 | * 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | * 8 | * A little flash file system for the Raspberry Pico 9 | * 10 | */ 11 | 12 | #include 13 | #include 14 | 15 | #include "hardware/flash.h" 16 | #include "hardware/regs/addressmap.h" 17 | #include "hardware/sync.h" 18 | #if LIB_PICO_MULTICORE 19 | #include "pico/mutex.h" 20 | #endif 21 | 22 | #include "lfs.h" 23 | #include "lfs_pico_hal.h" 24 | 25 | #define FS_SIZE (2 * 1024 * 1024) 26 | 27 | #define LOOK_AHEAD_SIZE 32 28 | 29 | uint8_t readBuffer[LFS_CACHE_SIZE]; 30 | uint8_t programBuffer[LFS_CACHE_SIZE]; 31 | uint8_t lookaheadBuffer[LOOK_AHEAD_SIZE] __attribute__((aligned(4))); 32 | 33 | static int pico_hal_read(const struct lfs_config *c, lfs_block_t block, 34 | lfs_off_t off, void *buffer, lfs_size_t size); 35 | static int pico_hal_prog(const struct lfs_config *c, lfs_block_t block, 36 | lfs_off_t off, const void *buffer, lfs_size_t size); 37 | static int pico_hal_erase(const struct lfs_config *c, lfs_block_t block); 38 | static int pico_hal_sync(const struct lfs_config *c); 39 | 40 | static int pico_lock(void); 41 | static int pico_unlock(void); 42 | 43 | // configuration of the filesystem is provided by this struct 44 | // for Pico: prog size = 256, block size = 4096, so cache is 8K 45 | // minimum cache = block size, must be multiple 46 | struct lfs_config pico_cfg = { 47 | // block device operations 48 | .read = pico_hal_read, 49 | .prog = pico_hal_prog, 50 | .erase = pico_hal_erase, 51 | .sync = pico_hal_sync, 52 | #if LIB_PICO_MULTICORE 53 | .lock = pico_lock, 54 | .unlock = pico_unlock, 55 | #endif 56 | // block device configuration 57 | .read_size = 1, 58 | .prog_size = FLASH_PAGE_SIZE, 59 | .block_size = FLASH_SECTOR_SIZE, 60 | .block_count = FS_SIZE / FLASH_SECTOR_SIZE, 61 | .cache_size = LFS_CACHE_SIZE, 62 | .lookahead_size = LOOK_AHEAD_SIZE, 63 | .block_cycles = 500, 64 | .read_buffer = readBuffer, 65 | .prog_buffer = programBuffer, 66 | .lookahead_buffer = lookaheadBuffer}; 67 | 68 | // Pico specific hardware abstraction functions 69 | 70 | // file system offset in flash 71 | const char *FS_BASE = (char *)(PICO_FLASH_SIZE_BYTES - FS_SIZE); 72 | 73 | static int pico_hal_read(const struct lfs_config *c, lfs_block_t block, 74 | lfs_off_t off, void *buffer, lfs_size_t size) { 75 | assert(block < c->block_count); 76 | assert(off + size <= c->block_size); 77 | // read flash via XIP mapped space 78 | memcpy(buffer, 79 | FS_BASE + XIP_NOCACHE_NOALLOC_BASE + (block * c->block_size) + off, 80 | size); 81 | return LFS_ERR_OK; 82 | } 83 | 84 | static int pico_hal_prog(const struct lfs_config *c, lfs_block_t block, 85 | lfs_off_t off, const void *buffer, lfs_size_t size) { 86 | assert(block < c->block_count); 87 | // program with SDK 88 | uint32_t p = (uint32_t)FS_BASE + (block * c->block_size) + off; 89 | uint32_t ints = save_and_disable_interrupts(); 90 | flash_range_program(p, buffer, size); 91 | restore_interrupts(ints); 92 | return LFS_ERR_OK; 93 | } 94 | 95 | static int pico_hal_erase(const struct lfs_config *c, lfs_block_t block) { 96 | assert(block < c->block_count); 97 | // erase with SDK 98 | uint32_t p = (uint32_t)FS_BASE + block * c->block_size; 99 | uint32_t ints = save_and_disable_interrupts(); 100 | flash_range_erase(p, c->block_size); 101 | restore_interrupts(ints); 102 | return LFS_ERR_OK; 103 | } 104 | 105 | static int pico_hal_sync(const struct lfs_config *c) 106 | { 107 | return LFS_ERR_OK; 108 | } 109 | 110 | #if LIB_PICO_MULTICORE 111 | 112 | static recursive_mutex_t fs_mtx; 113 | 114 | static int pico_lock(void) { 115 | recursive_mutex_enter_blocking(&fs_mtx); 116 | return LFS_ERR_OK; 117 | } 118 | 119 | static int pico_unlock(void) { 120 | recursive_mutex_exit(&fs_mtx); 121 | return LFS_ERR_OK; 122 | } 123 | #endif 124 | 125 | // utility functions 126 | 127 | const char *pico_errmsg(int err) { 128 | static const struct { 129 | int err; 130 | char *text; 131 | } mesgs[] = {{LFS_ERR_OK, "No error"}, 132 | {LFS_ERR_IO, "Error during device operation"}, 133 | {LFS_ERR_CORRUPT, "Corrupted"}, 134 | {LFS_ERR_NOENT, "No directory entry"}, 135 | {LFS_ERR_EXIST, "Entry already exists"}, 136 | {LFS_ERR_NOTDIR, "Entry is not a dir"}, 137 | {LFS_ERR_ISDIR, "Entry is a dir"}, 138 | {LFS_ERR_NOTEMPTY, "Dir is not empty"}, 139 | {LFS_ERR_BADF, "Bad file number"}, 140 | {LFS_ERR_FBIG, "File too large"}, 141 | {LFS_ERR_INVAL, "Invalid parameter"}, 142 | {LFS_ERR_NOSPC, "No space left on device"}, 143 | {LFS_ERR_NOMEM, "No more memory available"}, 144 | {LFS_ERR_NOATTR, "No data/attr available"}, 145 | {LFS_ERR_NAMETOOLONG, "File name too long"}}; 146 | 147 | for (int i = 0; i < sizeof(mesgs) / sizeof(mesgs[0]); i++) 148 | if (err == mesgs[i].err) 149 | return mesgs[i].text; 150 | return "Unknown error"; 151 | } -------------------------------------------------------------------------------- /libs/pico-littlefs/lfs_pico_hal.h: -------------------------------------------------------------------------------- 1 | #ifndef LFS_PICO_HAL_H 2 | #define LFS_PICO_HAL_H 3 | 4 | #include "lfs.h" 5 | 6 | #include 7 | 8 | #define LFS_CACHE_SIZE (FLASH_SECTOR_SIZE / 4) 9 | 10 | extern struct lfs_config pico_cfg; 11 | 12 | #endif /* LFS_PICO_HAL_H */ 13 | -------------------------------------------------------------------------------- /libs/pico-littlefs/lfs_util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * lfs util functions 3 | * 4 | * Copyright (c) 2022, The littlefs authors. 5 | * Copyright (c) 2017, Arm Limited. All rights reserved. 6 | * SPDX-License-Identifier: BSD-3-Clause 7 | */ 8 | #include "lfs_util.h" 9 | 10 | // Only compile if user does not provide custom config 11 | #ifndef LFS_CONFIG 12 | 13 | 14 | // Software CRC implementation with small lookup table 15 | uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size) { 16 | static const uint32_t rtable[16] = { 17 | 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 18 | 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 19 | 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 20 | 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c, 21 | }; 22 | 23 | const uint8_t *data = buffer; 24 | 25 | for (size_t i = 0; i < size; i++) { 26 | crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 0)) & 0xf]; 27 | crc = (crc >> 4) ^ rtable[(crc ^ (data[i] >> 4)) & 0xf]; 28 | } 29 | 30 | return crc; 31 | } 32 | 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /libs/pico-littlefs/lfs_util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * lfs utility functions 3 | * 4 | * Copyright (c) 2017, Arm Limited. All rights reserved. 5 | * SPDX-License-Identifier: BSD-3-Clause 6 | */ 7 | #ifndef LFS_UTIL_H 8 | #define LFS_UTIL_H 9 | 10 | // System includes 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #ifndef LFS_NO_MALLOC 17 | #include 18 | #endif 19 | #ifndef LFS_NO_ASSERT 20 | #include 21 | #endif 22 | 23 | #if !defined(LFS_NO_DEBUG) || !defined(LFS_NO_WARN) || !defined(LFS_NO_ERROR) || \ 24 | defined(LFS_YES_TRACE) 25 | #include 26 | #endif 27 | 28 | #ifdef __cplusplus 29 | extern "C" 30 | { 31 | #endif 32 | 33 | 34 | // Macros, may be replaced by system specific wrappers. Arguments to these 35 | // macros must not have side-effects as the macros can be removed for a smaller 36 | // code footprint 37 | 38 | // Logging functions 39 | #ifndef LFS_TRACE 40 | #ifdef LFS_YES_TRACE 41 | #define LFS_TRACE_(fmt, ...) \ 42 | printf("%s:%d:trace: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 43 | #define LFS_TRACE(...) LFS_TRACE_(__VA_ARGS__, "") 44 | #else 45 | #define LFS_TRACE(...) 46 | #endif 47 | #endif 48 | 49 | #ifndef LFS_DEBUG 50 | #ifndef LFS_NO_DEBUG 51 | #define LFS_DEBUG_(fmt, ...) \ 52 | printf("%s:%d:debug: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 53 | #define LFS_DEBUG(...) LFS_DEBUG_(__VA_ARGS__, "") 54 | #else 55 | #define LFS_DEBUG(...) 56 | #endif 57 | #endif 58 | 59 | #ifndef LFS_WARN 60 | #ifndef LFS_NO_WARN 61 | #define LFS_WARN_(fmt, ...) \ 62 | printf("%s:%d:warn: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 63 | #define LFS_WARN(...) LFS_WARN_(__VA_ARGS__, "") 64 | #else 65 | #define LFS_WARN(...) 66 | #endif 67 | #endif 68 | 69 | #ifndef LFS_ERROR 70 | #ifndef LFS_NO_ERROR 71 | #define LFS_ERROR_(fmt, ...) \ 72 | printf("%s:%d:error: " fmt "%s\n", __FILE__, __LINE__, __VA_ARGS__) 73 | #define LFS_ERROR(...) LFS_ERROR_(__VA_ARGS__, "") 74 | #else 75 | #define LFS_ERROR(...) 76 | #endif 77 | #endif 78 | 79 | // Runtime assertions 80 | #ifndef LFS_ASSERT 81 | #ifndef LFS_NO_ASSERT 82 | #define LFS_ASSERT(test) assert(test) 83 | #else 84 | #define LFS_ASSERT(test) 85 | #endif 86 | #endif 87 | 88 | // Min/max functions for unsigned 32-bit numbers 89 | static inline uint32_t lfs_max(uint32_t a, uint32_t b) { 90 | return (a > b) ? a : b; 91 | } 92 | 93 | static inline uint32_t lfs_min(uint32_t a, uint32_t b) { 94 | return (a < b) ? a : b; 95 | } 96 | 97 | // Align to nearest multiple of a size 98 | static inline uint32_t lfs_aligndown(uint32_t a, uint32_t alignment) { 99 | return a - (a % alignment); 100 | } 101 | 102 | static inline uint32_t lfs_alignup(uint32_t a, uint32_t alignment) { 103 | return lfs_aligndown(a + alignment-1, alignment); 104 | } 105 | 106 | // Find the smallest power of 2 greater than or equal to a 107 | static inline uint32_t lfs_npw2(uint32_t a) { 108 | return 32 - __builtin_clz(a-1); 109 | } 110 | 111 | // Count the number of trailing binary zeros in a 112 | // lfs_ctz(0) may be undefined 113 | static inline uint32_t lfs_ctz(uint32_t a) { 114 | return __builtin_ctz(a); 115 | } 116 | 117 | // Count the number of binary ones in a 118 | static inline uint32_t lfs_popc(uint32_t a) { 119 | return __builtin_popcount(a); 120 | } 121 | 122 | // Find the sequence comparison of a and b, this is the distance 123 | // between a and b ignoring overflow 124 | static inline int lfs_scmp(uint32_t a, uint32_t b) { 125 | return (int)(unsigned)(a - b); 126 | } 127 | 128 | // Convert between 32-bit little-endian and native order 129 | static inline uint32_t lfs_fromle32(uint32_t a) { 130 | return a; 131 | } 132 | 133 | static inline uint32_t lfs_tole32(uint32_t a) { 134 | return lfs_fromle32(a); 135 | } 136 | 137 | // Convert between 32-bit big-endian and native order 138 | static inline uint32_t lfs_frombe32(uint32_t a) { 139 | return __builtin_bswap32(a); 140 | } 141 | 142 | static inline uint32_t lfs_tobe32(uint32_t a) { 143 | return lfs_frombe32(a); 144 | } 145 | 146 | // Calculate CRC-32 with polynomial = 0x04c11db7 147 | uint32_t lfs_crc(uint32_t crc, const void *buffer, size_t size); 148 | 149 | // Deallocate memory, only used if buffers are not provided to littlefs 150 | static inline void lfs_free(void *p) { 151 | #ifndef LFS_NO_MALLOC 152 | free(p); 153 | #else 154 | (void)p; 155 | #endif 156 | } 157 | 158 | static inline void* lfs_malloc() { 159 | #ifndef LFS_NO_MALLOC 160 | 161 | #else 162 | return NULL; 163 | #endif 164 | } 165 | 166 | 167 | #ifdef __cplusplus 168 | } /* extern "C" */ 169 | #endif 170 | 171 | #endif -------------------------------------------------------------------------------- /linkerscript.ld: -------------------------------------------------------------------------------- 1 | /* Based on GCC ARM embedded samples. 2 | Defines the following symbols for use by code: 3 | __exidx_start 4 | __exidx_end 5 | __etext 6 | __data_start__ 7 | __preinit_array_start 8 | __preinit_array_end 9 | __init_array_start 10 | __init_array_end 11 | __fini_array_start 12 | __fini_array_end 13 | __data_end__ 14 | __bss_start__ 15 | __bss_end__ 16 | __end__ 17 | end 18 | __HeapLimit 19 | __StackLimit 20 | __StackTop 21 | __stack (== StackTop) 22 | */ 23 | 24 | MEMORY 25 | { 26 | FLASH(rx) : ORIGIN = 0x10000000, LENGTH = 128k 27 | ROMSTORAGE(rx) : ORIGIN = 0x10020000, LENGTH = 14208k 28 | FILESYSTEM(rx) : ORIGIN = 0x10E00000, LENGTH = 2048k 29 | RAM(rwx) : ORIGIN = 0x20000000, LENGTH = 128k 30 | SAVERAM(rwx) : ORIGIN = 0x2001E000, LENGTH = 128k 31 | SCRATCH_X(rwx) : ORIGIN = 0x20040000, LENGTH = 4k 32 | SCRATCH_Y(rwx) : ORIGIN = 0x20041000, LENGTH = 4k 33 | } 34 | 35 | ENTRY(_entry_point) 36 | 37 | SECTIONS 38 | { 39 | /* Second stage bootloader is prepended to the image. It must be 256 bytes big 40 | and checksummed. It is usually built by the boot_stage2 target 41 | in the Raspberry Pi Pico SDK 42 | */ 43 | 44 | .flash_begin : { 45 | __flash_binary_start = .; 46 | } > FLASH 47 | 48 | .boot2 : { 49 | __boot2_start__ = .; 50 | KEEP (*(.boot2)) 51 | __boot2_end__ = .; 52 | } > FLASH 53 | 54 | ASSERT(__boot2_end__ - __boot2_start__ == 256, 55 | "ERROR: Pico second stage bootloader must be 256 bytes in size") 56 | 57 | /* The second stage will always enter the image at the start of .text. 58 | The debugger will use the ELF entry point, which is the _entry_point 59 | symbol if present, otherwise defaults to start of .text. 60 | This can be used to transfer control back to the bootrom on debugger 61 | launches only, to perform proper flash setup. 62 | */ 63 | 64 | .text : { 65 | __logical_binary_start = .; 66 | KEEP (*(.vectors)) 67 | KEEP (*(.binary_info_header)) 68 | __binary_info_header_end = .; 69 | KEEP (*(.reset)) 70 | /* TODO revisit this now memset/memcpy/float in ROM */ 71 | /* bit of a hack right now to exclude all floating point and time critical (e.g. memset, memcpy) code from 72 | * FLASH ... we will include any thing excluded here in .data below by default */ 73 | *(.init) 74 | *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a: *timer.c.obj *printf.c.obj *stdio.c.obj *stdio_uart.c.obj) .text*) 75 | *(.fini) 76 | /* Pull all c'tors into .text */ 77 | *crtbegin.o(.ctors) 78 | *crtbegin?.o(.ctors) 79 | *(EXCLUDE_FILE(*crtend?.o *crtend.o) .ctors) 80 | *(SORT(.ctors.*)) 81 | *(.ctors) 82 | /* Followed by destructors */ 83 | *crtbegin.o(.dtors) 84 | *crtbegin?.o(.dtors) 85 | *(EXCLUDE_FILE(*crtend?.o *crtend.o) .dtors) 86 | *(SORT(.dtors.*)) 87 | *(.dtors) 88 | 89 | *(.eh_frame*) 90 | . = ALIGN(4); 91 | } > FLASH 92 | 93 | .rodata : { 94 | *(EXCLUDE_FILE(*libgcc.a: *libc.a:*lib_a-mem*.o *libm.a: *timer.c.obj *printf.c.obj *stdio.c.obj *stdio_uart.c.obj) .rodata*) 95 | . = ALIGN(4); 96 | *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.flashdata*))) 97 | . = ALIGN(4); 98 | } > FLASH 99 | 100 | .ARM.extab : 101 | { 102 | *(.ARM.extab* .gnu.linkonce.armextab.*) 103 | } > FLASH 104 | 105 | __exidx_start = .; 106 | .ARM.exidx : 107 | { 108 | *(.ARM.exidx* .gnu.linkonce.armexidx.*) 109 | } > FLASH 110 | __exidx_end = .; 111 | 112 | /* Machine inspectable binary information */ 113 | . = ALIGN(4); 114 | __binary_info_start = .; 115 | .binary_info : 116 | { 117 | KEEP(*(.binary_info.keep.*)) 118 | *(.binary_info.*) 119 | } > FLASH 120 | __binary_info_end = .; 121 | . = ALIGN(4); 122 | 123 | /* End of .text-like segments */ 124 | __etext = .; 125 | 126 | .ram_vector_table (COPY): { 127 | *(.ram_vector_table) 128 | } > RAM 129 | 130 | .data : { 131 | __data_start__ = .; 132 | *(vtable) 133 | 134 | *(.time_critical*) 135 | 136 | /* remaining .text and .rodata; i.e. stuff we exclude above because we want it in RAM */ 137 | *(.text*) 138 | . = ALIGN(4); 139 | *(.rodata*) 140 | . = ALIGN(4); 141 | 142 | *(.data*) 143 | 144 | . = ALIGN(4); 145 | *(.after_data.*) 146 | . = ALIGN(4); 147 | /* preinit data */ 148 | PROVIDE_HIDDEN (__mutex_array_start = .); 149 | KEEP(*(SORT(.mutex_array.*))) 150 | KEEP(*(.mutex_array)) 151 | PROVIDE_HIDDEN (__mutex_array_end = .); 152 | 153 | . = ALIGN(4); 154 | /* preinit data */ 155 | PROVIDE_HIDDEN (__preinit_array_start = .); 156 | KEEP(*(SORT(.preinit_array.*))) 157 | KEEP(*(.preinit_array)) 158 | PROVIDE_HIDDEN (__preinit_array_end = .); 159 | 160 | . = ALIGN(4); 161 | /* init data */ 162 | PROVIDE_HIDDEN (__init_array_start = .); 163 | KEEP(*(SORT(.init_array.*))) 164 | KEEP(*(.init_array)) 165 | PROVIDE_HIDDEN (__init_array_end = .); 166 | 167 | . = ALIGN(4); 168 | /* finit data */ 169 | PROVIDE_HIDDEN (__fini_array_start = .); 170 | *(SORT(.fini_array.*)) 171 | *(.fini_array) 172 | PROVIDE_HIDDEN (__fini_array_end = .); 173 | 174 | *(.jcr) 175 | . = ALIGN(4); 176 | /* All data end */ 177 | __data_end__ = .; 178 | } > RAM AT> FLASH 179 | 180 | .uninitialized_data (NOLOAD): { 181 | . = ALIGN(4); 182 | *(.uninitialized_data*) 183 | *(.noinit.*) 184 | } > RAM 185 | 186 | .noinit_gb_ram (NOLOAD): { 187 | . = ALIGN(4); 188 | *(.noinit_gb_ram.*) 189 | } > SAVERAM 190 | 191 | /* Start and end symbols must be word-aligned */ 192 | .scratch_x : { 193 | __scratch_x_start__ = .; 194 | *(.scratch_x.*) 195 | . = ALIGN(4); 196 | __scratch_x_end__ = .; 197 | } > SCRATCH_X AT > FLASH 198 | __scratch_x_source__ = LOADADDR(.scratch_x); 199 | 200 | .scratch_y : { 201 | __scratch_y_start__ = .; 202 | *(.scratch_y.*) 203 | . = ALIGN(4); 204 | __scratch_y_end__ = .; 205 | } > SCRATCH_Y AT > FLASH 206 | __scratch_y_source__ = LOADADDR(.scratch_y); 207 | 208 | .bss : { 209 | . = ALIGN(4); 210 | __bss_start__ = .; 211 | *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.bss*))) 212 | *(COMMON) 213 | . = ALIGN(4); 214 | __bss_end__ = .; 215 | } > RAM 216 | 217 | .heap (COPY): 218 | { 219 | __end__ = .; 220 | end = __end__; 221 | *(.heap*) 222 | __HeapLimit = .; 223 | } > RAM 224 | 225 | /* .stack*_dummy section doesn't contains any symbols. It is only 226 | * used for linker to calculate size of stack sections, and assign 227 | * values to stack symbols later 228 | * 229 | * stack1 section may be empty/missing if platform_launch_core1 is not used */ 230 | 231 | /* by default we put core 0 stack at the end of scratch Y, so that if core 1 232 | * stack is not used then all of SCRATCH_X is free. 233 | */ 234 | .stack1_dummy (COPY): 235 | { 236 | *(.stack1*) 237 | } > SCRATCH_X 238 | .stack_dummy (COPY): 239 | { 240 | *(.stack*) 241 | } > SCRATCH_Y 242 | 243 | .flash_end : { 244 | __flash_binary_end = .; 245 | } > FLASH 246 | 247 | /* stack limit is poorly named, but historically is maximum heap ptr */ 248 | __StackLimit = ORIGIN(RAM) + LENGTH(RAM); 249 | __StackOneTop = ORIGIN(SCRATCH_X) + LENGTH(SCRATCH_X); 250 | __StackTop = ORIGIN(SCRATCH_Y) + LENGTH(SCRATCH_Y); 251 | __StackOneBottom = __StackOneTop - SIZEOF(.stack1_dummy); 252 | __StackBottom = __StackTop - SIZEOF(.stack_dummy); 253 | PROVIDE(__stack = __StackTop); 254 | 255 | /* Check if data + heap + stack exceeds RAM limit */ 256 | ASSERT(__StackLimit >= __HeapLimit, "region RAM overflowed") 257 | 258 | ASSERT( __binary_info_header_end - __logical_binary_start <= 256, "Binary info must be in first 256 bytes of the binary") 259 | /* todo assert on extra code */ 260 | } 261 | 262 | -------------------------------------------------------------------------------- /mbc.h: -------------------------------------------------------------------------------- 1 | #ifndef FAAE3125_340E_4959_9C48_AA11DF5F4BE0 2 | #define FAAE3125_340E_4959_9C48_AA11DF5F4BE0 3 | 4 | #include 5 | 6 | void loadGame(uint8_t mode); 7 | 8 | #endif /* FAAE3125_340E_4959_9C48_AA11DF5F4BE0 */ 9 | -------------------------------------------------------------------------------- /tusb_config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2019 Ha Thach (tinyusb.org) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | */ 25 | 26 | #ifndef _TUSB_CONFIG_H_ 27 | #define _TUSB_CONFIG_H_ 28 | 29 | #ifdef __cplusplus 30 | extern "C" { 31 | #endif 32 | 33 | //-------------------------------------------------------------------- 34 | // COMMON CONFIGURATION 35 | //-------------------------------------------------------------------- 36 | 37 | // defined by board.mk 38 | #ifndef CFG_TUSB_MCU 39 | #error CFG_TUSB_MCU must be defined 40 | #endif 41 | 42 | // RHPort number used for device can be defined by board.mk, default to port 0 43 | #ifndef BOARD_DEVICE_RHPORT_NUM 44 | #define BOARD_DEVICE_RHPORT_NUM 0 45 | #endif 46 | 47 | // RHPort max operational speed can defined by board.mk 48 | // Default to Highspeed for MCU with internal HighSpeed PHY (can be port specific), otherwise FullSpeed 49 | #ifndef BOARD_DEVICE_RHPORT_SPEED 50 | #if (CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || \ 51 | CFG_TUSB_MCU == OPT_MCU_NUC505 || CFG_TUSB_MCU == OPT_MCU_CXD56) 52 | #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_HIGH_SPEED 53 | #else 54 | #define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED 55 | #endif 56 | #endif 57 | 58 | // Device mode with rhport and speed defined by board.mk 59 | #if BOARD_DEVICE_RHPORT_NUM == 0 60 | #define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) 61 | #elif BOARD_DEVICE_RHPORT_NUM == 1 62 | #define CFG_TUSB_RHPORT1_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED) 63 | #else 64 | #error "Incorrect RHPort configuration" 65 | #endif 66 | 67 | #ifndef CFG_TUSB_OS 68 | #define CFG_TUSB_OS OPT_OS_NONE 69 | #endif 70 | 71 | // CFG_TUSB_DEBUG is defined by compiler in DEBUG build 72 | // #define CFG_TUSB_DEBUG 0 73 | 74 | /* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. 75 | * Tinyusb use follows macros to declare transferring memory so that they can be put 76 | * into those specific section. 77 | * e.g 78 | * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) 79 | * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) 80 | */ 81 | #ifndef CFG_TUSB_MEM_SECTION 82 | #define CFG_TUSB_MEM_SECTION 83 | #endif 84 | 85 | #ifndef CFG_TUSB_MEM_ALIGN 86 | #define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) 87 | #endif 88 | 89 | //-------------------------------------------------------------------- 90 | // DEVICE CONFIGURATION 91 | //-------------------------------------------------------------------- 92 | 93 | #ifndef CFG_TUD_ENDPOINT0_SIZE 94 | #define CFG_TUD_ENDPOINT0_SIZE 64 95 | #endif 96 | 97 | //------------- CLASS -------------// 98 | #define CFG_TUD_CDC 0 99 | #define CFG_TUD_MSC 0 100 | #define CFG_TUD_HID 0 101 | #define CFG_TUD_MIDI 0 102 | #define CFG_TUD_VENDOR 1 103 | 104 | // Vendor FIFO size of TX and RX 105 | // If not configured vendor endpoints will not be buffered 106 | #define CFG_TUD_VENDOR_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) 107 | #define CFG_TUD_VENDOR_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) 108 | 109 | 110 | #ifdef __cplusplus 111 | } 112 | #endif 113 | 114 | #endif /* _TUSB_CONFIG_H_ */ 115 | -------------------------------------------------------------------------------- /usb_descriptors.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2019 Ha Thach (tinyusb.org) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | */ 25 | 26 | #include "tusb.h" 27 | #include "usb_descriptors.h" 28 | #include "GlobalDefines.h" 29 | 30 | /* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug. 31 | * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC. 32 | * 33 | * Auto ProductID layout's Bitmap: 34 | * [MSB] MIDI | HID | MSC | CDC [LSB] 35 | */ 36 | #define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) ) 37 | #define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ 38 | _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) ) 39 | 40 | //--------------------------------------------------------------------+ 41 | // Device Descriptors 42 | //--------------------------------------------------------------------+ 43 | tusb_desc_device_t const desc_device = 44 | { 45 | .bLength = sizeof(tusb_desc_device_t), 46 | .bDescriptorType = TUSB_DESC_DEVICE, 47 | .bcdUSB = 0x0210, // at least 2.1 or 3.x for BOS & webUSB 48 | 49 | .bDeviceClass = 0, 50 | .bDeviceSubClass = 0, 51 | .bDeviceProtocol = 0, 52 | .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, 53 | 54 | .idVendor = 0x2E8A, // Raspberry Foundation 55 | .idProduct = 0x107F, // Allocated Product ID for this project https://github.com/raspberrypi/usb-pid/commit/7348deef80a85d155ddba314879453577d52d23b 56 | .bcdDevice = 0x0100, 57 | 58 | .iManufacturer = 0x01, 59 | .iProduct = 0x02, 60 | .iSerialNumber = 0x03, 61 | 62 | .bNumConfigurations = 0x01 63 | }; 64 | 65 | // Invoked when received GET DEVICE DESCRIPTOR 66 | // Application return pointer to descriptor 67 | uint8_t const * tud_descriptor_device_cb(void) 68 | { 69 | return (uint8_t const *) &desc_device; 70 | } 71 | 72 | //--------------------------------------------------------------------+ 73 | // Configuration Descriptor 74 | //--------------------------------------------------------------------+ 75 | enum 76 | { 77 | ITF_NUM_VENDOR=0, 78 | ITF_NUM_TOTAL 79 | }; 80 | 81 | #define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_VENDOR_DESC_LEN) 82 | 83 | 84 | #define EPNUM_VENDOR 2 85 | 86 | 87 | uint8_t const desc_configuration[] = 88 | { 89 | // Config number, interface count, string index, total length, attribute, power in mA 90 | TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100), 91 | 92 | // Interface number, string index, EP notification address and size, EP data address (out, in) and size. 93 | //TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, 0x81, 8, EPNUM_CDC, 0x80 | EPNUM_CDC, TUD_OPT_HIGH_SPEED ? 512 : 64), 94 | 95 | // Interface number, string index, EP Out & IN address, EP size 96 | TUD_VENDOR_DESCRIPTOR(ITF_NUM_VENDOR, 4, EPNUM_VENDOR, 0x80 | EPNUM_VENDOR, TUD_OPT_HIGH_SPEED ? 512 : 64) 97 | }; 98 | 99 | // Invoked when received GET CONFIGURATION DESCRIPTOR 100 | // Application return pointer to descriptor 101 | // Descriptor contents must exist long enough for transfer to complete 102 | uint8_t const * tud_descriptor_configuration_cb(uint8_t index) 103 | { 104 | (void) index; // for multiple configurations 105 | return desc_configuration; 106 | } 107 | 108 | //--------------------------------------------------------------------+ 109 | // BOS Descriptor 110 | //--------------------------------------------------------------------+ 111 | 112 | /* Microsoft OS 2.0 registry property descriptor 113 | Per MS requirements https://msdn.microsoft.com/en-us/library/windows/hardware/hh450799(v=vs.85).aspx 114 | device should create DeviceInterfaceGUIDs. It can be done by driver and 115 | in case of real PnP solution device should expose MS "Microsoft OS 2.0 116 | registry property descriptor". Such descriptor can insert any record 117 | into Windows registry per device/configuration/interface. In our case it 118 | will insert "DeviceInterfaceGUIDs" multistring property. 119 | GUID is freshly generated and should be OK to use. 120 | https://developers.google.com/web/fundamentals/native-hardware/build-for-webusb/ 121 | (Section Microsoft OS compatibility descriptors) 122 | */ 123 | 124 | #define BOS_TOTAL_LEN (TUD_BOS_DESC_LEN + TUD_BOS_WEBUSB_DESC_LEN + TUD_BOS_MICROSOFT_OS_DESC_LEN) 125 | 126 | #define MS_OS_20_DESC_LEN 162 127 | 128 | // BOS Descriptor is required for webUSB 129 | uint8_t const desc_bos[] = 130 | { 131 | // total length, number of device caps 132 | TUD_BOS_DESCRIPTOR(BOS_TOTAL_LEN, 2), 133 | 134 | // Vendor Code, iLandingPage 135 | TUD_BOS_WEBUSB_DESCRIPTOR(VENDOR_REQUEST_WEBUSB, 1), 136 | 137 | // Microsoft OS 2.0 descriptor 138 | TUD_BOS_MS_OS_20_DESCRIPTOR(MS_OS_20_DESC_LEN, VENDOR_REQUEST_MICROSOFT) 139 | }; 140 | 141 | uint8_t const * tud_descriptor_bos_cb(void) 142 | { 143 | return desc_bos; 144 | } 145 | 146 | 147 | uint8_t const desc_ms_os_20[] = 148 | { 149 | // Set header: length, type, windows version, total length 150 | U16_TO_U8S_LE(0x000A), U16_TO_U8S_LE(MS_OS_20_SET_HEADER_DESCRIPTOR), U32_TO_U8S_LE(0x06030000), U16_TO_U8S_LE(MS_OS_20_DESC_LEN), 151 | 152 | // MS OS 2.0 Compatible ID descriptor: length, type, compatible ID, sub compatible ID 153 | U16_TO_U8S_LE(0x0014), U16_TO_U8S_LE(MS_OS_20_FEATURE_COMPATBLE_ID), 'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00, 154 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // sub-compatible 155 | 156 | // MS OS 2.0 Registry property descriptor: length, type 157 | U16_TO_U8S_LE(MS_OS_20_DESC_LEN-0x0A-0x14), U16_TO_U8S_LE(MS_OS_20_FEATURE_REG_PROPERTY), 158 | U16_TO_U8S_LE(0x0007), U16_TO_U8S_LE(0x002A), // wPropertyDataType, wPropertyNameLength and PropertyName "DeviceInterfaceGUIDs\0" in UTF-16 159 | 'D', 0x00, 'e', 0x00, 'v', 0x00, 'i', 0x00, 'c', 0x00, 'e', 0x00, 'I', 0x00, 'n', 0x00, 't', 0x00, 'e', 0x00, 160 | 'r', 0x00, 'f', 0x00, 'a', 0x00, 'c', 0x00, 'e', 0x00, 'G', 0x00, 'U', 0x00, 'I', 0x00, 'D', 0x00, 's', 0x00, 0x00, 0x00, 161 | U16_TO_U8S_LE(0x0050), // wPropertyDataLength 162 | //bPropertyData: {056f3de1-4747-4b9b-a958-47737449b154}. 163 | '{', 0x00, '0', 0x00, '5', 0x00, '6', 0x00, 'F', 0x00, '3', 0x00, 'D', 0x00, 'E', 0x00, '1', 0x00, '-', 0x00, 164 | '4', 0x00, '7', 0x00, '4', 0x00, '7', 0x00, '-', 0x00, '4', 0x00, 'B', 0x00, '9', 0x00, 'B', 0x00, '-', 0x00, 165 | 'A', 0x00, '9', 0x00, '5', 0x00, '8', 0x00, '-', 0x00, '4', 0x00, '7', 0x00, '7', 0x00, '3', 0x00, '7', 0x00, 166 | '4', 0x00, '4', 0x00, '9', 0x00, 'B', 0x00, '1', 0x00, '5', 0x00, '4', 0x00, '}', 0x00, 0x00, 0x00, 0x00, 0x00 167 | }; 168 | 169 | TU_VERIFY_STATIC(sizeof(desc_ms_os_20) == MS_OS_20_DESC_LEN, "Incorrect size"); 170 | 171 | //--------------------------------------------------------------------+ 172 | // String Descriptors 173 | //--------------------------------------------------------------------+ 174 | 175 | // array of pointer to string descriptors 176 | char const* string_desc_arr [] = 177 | { 178 | (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) 179 | "x-pantion", // 1: Manufacturer 180 | "Croco Cartridge", // 2: Product 181 | g_serialNumberString, // 3: Serials, should use chip ID 182 | "TinyUSB WebUSB" // 5: Vendor Interface 183 | }; 184 | 185 | static uint16_t _desc_str[32]; 186 | 187 | // Invoked when received GET STRING DESCRIPTOR request 188 | // Application return pointer to descriptor, whose contents must exist long enough for transfer to complete 189 | uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) 190 | { 191 | (void) langid; 192 | 193 | uint8_t chr_count; 194 | 195 | if ( index == 0) 196 | { 197 | memcpy(&_desc_str[1], string_desc_arr[0], 2); 198 | chr_count = 1; 199 | }else 200 | { 201 | // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. 202 | // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors 203 | 204 | if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) return NULL; 205 | 206 | const char* str = string_desc_arr[index]; 207 | 208 | // Cap at max char 209 | chr_count = strlen(str); 210 | if ( chr_count > 31 ) chr_count = 31; 211 | 212 | // Convert ASCII string into UTF-16 213 | for(uint8_t i=0; i. 16 | */ 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | 28 | #include 29 | 30 | #include "BuildVersion.h" 31 | #include "GameBoyHeader.h" 32 | #include "GlobalDefines.h" 33 | #include "device/usbd.h" 34 | #include "usb_descriptors.h" 35 | 36 | #include "BuildVersion.h" 37 | #include "RomStorage.h" 38 | 39 | static bool web_serial_connected = false; 40 | 41 | static uint8_t command_buffer[64]; 42 | 43 | #define URL "croco.x-pantion.de" 44 | 45 | const tusb_desc_webusb_url_t desc_url = {.bLength = 3 + sizeof(URL) - 1, 46 | .bDescriptorType = 47 | 3, // WEBUSB URL type 48 | .bScheme = 1, // 0: http, 1: https 49 | .url = URL}; 50 | 51 | static void webserial_task(void); 52 | static void handle_command(uint8_t command); 53 | static int handle_device_info_command(uint8_t buff[63]); 54 | static int handle_device_serial_id_command(uint8_t buff[63]); 55 | static int handle_new_rom_command(uint8_t buff[63]); 56 | static int handle_rom_upload_command(uint8_t buff[63]); 57 | static int handle_request_rom_info_command(uint8_t buff[63]); 58 | static int handle_delete_rom_command(uint8_t buff[63]); 59 | static int handle_request_savegame_download_command(uint8_t buff[63]); 60 | static int handle_savegame_transmit_chunk_command(uint8_t buff[63]); 61 | static int handle_request_savegame_upload_command(uint8_t buff[63]); 62 | static int handle_savegame_received_chunk_command(uint8_t buff[63]); 63 | static int handle_rtc_download_command(uint8_t buff[63]); 64 | static int handle_rtc_upload_command(uint8_t buff[63]); 65 | 66 | void usb_start() { tusb_init(); } 67 | 68 | void usb_shutdown() { tud_disconnect(); } 69 | 70 | void usb_run() { 71 | tud_task(); 72 | webserial_task(); 73 | } 74 | 75 | void webserial_task(void) { 76 | if (web_serial_connected) { 77 | if (tud_vendor_available()) { 78 | uint8_t buf[1]; 79 | uint32_t count = tud_vendor_read(buf, sizeof(buf)); 80 | if (count) { 81 | // printf("webserial: 0x%x\n", buf[0]); 82 | 83 | handle_command(buf[0]); 84 | } 85 | } 86 | } 87 | } 88 | 89 | //--------------------------------------------------------------------+ 90 | // Device callbacks 91 | //--------------------------------------------------------------------+ 92 | 93 | // Invoked when device is mounted 94 | void tud_mount_cb(void) {} 95 | 96 | // Invoked when device is unmounted 97 | void tud_umount_cb(void) {} 98 | 99 | // Invoked when usb bus is suspended 100 | // remote_wakeup_en : if host allow us to perform remote wakeup 101 | // Within 7ms, device must draw an average of current less than 2.5 mA from bus 102 | void tud_suspend_cb(bool remote_wakeup_en) { (void)remote_wakeup_en; } 103 | 104 | // Invoked when usb bus is resumed 105 | void tud_resume_cb(void) {} 106 | 107 | //--------------------------------------------------------------------+ 108 | // WebUSB use vendor class 109 | //--------------------------------------------------------------------+ 110 | 111 | // Invoked when a control transfer occurred on an interface of this class 112 | // Driver response accordingly to the request and the transfer stage 113 | // (setup/data/ack) return false to stall control endpoint (e.g unsupported 114 | // request) 115 | bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage, 116 | tusb_control_request_t const *request) { 117 | // nothing to with DATA & ACK stage 118 | if (stage != CONTROL_STAGE_SETUP) 119 | return true; 120 | 121 | switch (request->bmRequestType_bit.type) { 122 | case TUSB_REQ_TYPE_VENDOR: 123 | switch (request->bRequest) { 124 | case VENDOR_REQUEST_WEBUSB: 125 | // match vendor request in BOS descriptor 126 | // Get landing page url 127 | return tud_control_xfer(rhport, request, (void *)(uintptr_t)&desc_url, 128 | desc_url.bLength); 129 | 130 | case VENDOR_REQUEST_MICROSOFT: 131 | if (request->wIndex == 7) { 132 | // Get Microsoft OS 2.0 compatible descriptor 133 | uint16_t total_len; 134 | memcpy(&total_len, desc_ms_os_20 + 8, 2); 135 | printf("MS descriptor requested\n"); 136 | 137 | return tud_control_xfer(rhport, request, 138 | (void *)(uintptr_t)desc_ms_os_20, total_len); 139 | } else { 140 | return false; 141 | } 142 | 143 | default: 144 | break; 145 | } 146 | break; 147 | 148 | case TUSB_REQ_TYPE_CLASS: 149 | if (request->bRequest == 0x22) { 150 | // Webserial simulate the CDC_REQUEST_SET_CONTROL_LINE_STATE (0x22) to 151 | // connect and disconnect. 152 | web_serial_connected = (request->wValue != 0); 153 | printf("web_serial_connected %d\n", web_serial_connected); 154 | 155 | // response with status OK 156 | return tud_control_status(rhport, request); 157 | } 158 | break; 159 | 160 | default: 161 | break; 162 | } 163 | 164 | // stall unknown request 165 | return false; 166 | } 167 | 168 | static void handle_command(uint8_t command) { 169 | int response_length = 0; 170 | 171 | switch (command) { 172 | case 1: 173 | uint16_t usedBanks = RomStorage_GetNumUsedBanks(); 174 | command_buffer[1] = g_numRoms; 175 | command_buffer[2] = (usedBanks >> 8) & 0xFF; 176 | command_buffer[3] = usedBanks & 0xFF; 177 | command_buffer[4] = (MAX_BANKS >> 8) & 0xFF; 178 | command_buffer[5] = MAX_BANKS & 0xFF; 179 | response_length = 5; 180 | break; 181 | case 2: 182 | response_length = handle_new_rom_command(&command_buffer[1]); 183 | break; 184 | case 3: 185 | response_length = handle_rom_upload_command(&command_buffer[1]); 186 | break; 187 | case 4: 188 | response_length = handle_request_rom_info_command(&command_buffer[1]); 189 | break; 190 | case 5: 191 | response_length = handle_delete_rom_command(&command_buffer[1]); 192 | break; 193 | case 6: 194 | response_length = 195 | handle_request_savegame_download_command(&command_buffer[1]); 196 | break; 197 | case 7: 198 | response_length = 199 | handle_savegame_transmit_chunk_command(&command_buffer[1]); 200 | break; 201 | case 8: 202 | response_length = 203 | handle_request_savegame_upload_command(&command_buffer[1]); 204 | break; 205 | case 9: 206 | response_length = 207 | handle_savegame_received_chunk_command(&command_buffer[1]); 208 | break; 209 | case 10: 210 | response_length = handle_rtc_download_command(&command_buffer[1]); 211 | break; 212 | case 11: 213 | response_length = handle_rtc_upload_command(&command_buffer[1]); 214 | break; 215 | case 253: 216 | response_length = handle_device_serial_id_command(&command_buffer[1]); 217 | break; 218 | case 254: 219 | response_length = handle_device_info_command(&command_buffer[1]); 220 | break; 221 | default: 222 | printf("webusb: unknown command\n"); 223 | response_length = -1; 224 | break; 225 | } 226 | 227 | if (response_length < 0) { 228 | command_buffer[0] = 0xFF; 229 | response_length = 1; 230 | } else { 231 | command_buffer[0] = command; 232 | response_length += 1; 233 | } 234 | 235 | tud_vendor_write(command_buffer, response_length); 236 | tud_vendor_flush(); 237 | } 238 | 239 | static int handle_device_info_command(uint8_t buff[63]) { 240 | uint32_t git_sha1 = git_CommitSHA1Short(); 241 | buff[0] = 4; // featureStep 242 | buff[1] = 1; // hwVersion 243 | buff[2] = RP2040_GB_CARTRIDGE_VERSION_MAJOR; 244 | buff[3] = RP2040_GB_CARTRIDGE_VERSION_MINOR; 245 | buff[4] = RP2040_GB_CARTRIDGE_VERSION_PATCH; 246 | buff[5] = RP2040_GB_CARTRIDGE_BUILD_VERSION_TYPE; 247 | buff[6] = (git_sha1 >> 24) & 0xFF; 248 | buff[7] = (git_sha1 >> 16) & 0xFF; 249 | buff[8] = (git_sha1 >> 8) & 0xFF; 250 | buff[9] = git_sha1 & 0xFF; 251 | buff[10] = git_AnyUncommittedChanges(); 252 | return 11; 253 | } 254 | 255 | static int handle_device_serial_id_command(uint8_t buff[63]) { 256 | memcpy(buff, g_flashSerialNumber, sizeof(g_flashSerialNumber)); 257 | return sizeof(g_flashSerialNumber); 258 | } 259 | 260 | static int handle_new_rom_command(uint8_t buff[63]) { 261 | uint16_t num_banks, speedSwitchBank; 262 | 263 | uint32_t count = tud_vendor_read(buff, 21); 264 | if (count != 21) { 265 | printf("wrong number of bytes for new rom command\n"); 266 | return -1; 267 | } 268 | 269 | num_banks = (buff[0] << 8) + buff[1]; 270 | speedSwitchBank = (buff[19] << 8) + buff[20]; 271 | buff[18] = 0; // force zero terminate received string 272 | 273 | buff[0] = 0; 274 | if (RomStorage_StartNewRomTransfer(num_banks, speedSwitchBank, 275 | (char *)&buff[sizeof(uint16_t)]) < 0) { 276 | buff[0] = 1; 277 | } 278 | 279 | return 1; 280 | } 281 | 282 | static int handle_rom_upload_command(uint8_t buff[63]) { 283 | uint16_t bank, chunk; 284 | 285 | uint32_t count = tud_vendor_read(buff, 36); 286 | if (count != 36) { 287 | printf("wrong number of bytes for rom chunk\n"); 288 | return -1; 289 | } 290 | 291 | bank = (buff[0] << 8) + buff[1]; 292 | chunk = (buff[2] << 8) + buff[3]; 293 | 294 | buff[0] = 0; 295 | if (RomStorage_TransferRomChunk(bank, chunk, &buff[sizeof(uint16_t) * 2]) < 296 | 0) { 297 | buff[0] = 1; 298 | } 299 | 300 | return 1; 301 | } 302 | 303 | static int handle_request_rom_info_command(uint8_t buff[63]) { 304 | uint32_t count = tud_vendor_read(buff, 1); 305 | if (count != 1) { 306 | printf("wrong number of bytes for rom info command\n"); 307 | return -1; 308 | } 309 | 310 | const uint8_t requestedRom = buff[0]; 311 | 312 | if (requestedRom >= g_numRoms) { 313 | return -1; 314 | } 315 | 316 | { 317 | struct RomInfo romInfo = {}; 318 | if (RomStorage_loadRomInfo(requestedRom, &romInfo)) 319 | return -1; 320 | 321 | memcpy(buff, &romInfo.name, 17); 322 | buff[17] = romInfo.numRamBanks; 323 | buff[18] = romInfo.mbc; 324 | buff[19] = (romInfo.numRomBanks >> 8) & 0xFF; 325 | buff[20] = romInfo.numRomBanks & 0xFF; 326 | } 327 | 328 | return 21; 329 | } 330 | 331 | static int handle_delete_rom_command(uint8_t buff[63]) { 332 | uint32_t count = tud_vendor_read(buff, 1); 333 | if (count != 1) { 334 | printf("wrong number of bytes for rom delete command\n"); 335 | return -1; 336 | } 337 | 338 | const uint8_t requestedRom = buff[0]; 339 | 340 | buff[0] = 0; 341 | if (RomStorage_DeleteRom(requestedRom) < 0) { 342 | buff[0] = 1; 343 | } 344 | 345 | return 1; 346 | } 347 | 348 | static int handle_request_savegame_download_command(uint8_t buff[63]) { 349 | uint32_t count = tud_vendor_read(buff, 1); 350 | if (count != 1) { 351 | printf("wrong number of bytes for rom delete command\n"); 352 | return -1; 353 | } 354 | 355 | const uint8_t requestedRom = buff[0]; 356 | 357 | buff[0] = 0; 358 | if (RomStorage_StartRamDownload(requestedRom) < 0) { 359 | buff[0] = 1; 360 | } 361 | 362 | return 1; 363 | } 364 | 365 | static int handle_savegame_transmit_chunk_command(uint8_t buff[63]) { 366 | uint16_t bank, chunk; 367 | 368 | if (RomStorage_GetRamDownloadChunk(&buff[4], &bank, &chunk) < 0) { 369 | return -1; 370 | } 371 | 372 | buff[0] = (bank >> 8) & 0xFFU; 373 | buff[1] = bank & 0xFFU; 374 | buff[2] = (chunk >> 8) & 0xFFU; 375 | buff[3] = chunk & 0xFFU; 376 | 377 | return 36; 378 | } 379 | 380 | static int handle_request_savegame_upload_command(uint8_t buff[63]) { 381 | uint32_t count = tud_vendor_read(buff, 1); 382 | if (count != 1) { 383 | printf("wrong number of bytes for savegame upload command\n"); 384 | return -1; 385 | } 386 | 387 | const uint8_t requestedRom = buff[0]; 388 | 389 | buff[0] = 0; 390 | if (RomStorage_StartRamUpload(requestedRom) < 0) { 391 | buff[0] = 1; 392 | } 393 | 394 | return 1; 395 | } 396 | 397 | static int handle_savegame_received_chunk_command(uint8_t buff[63]) { 398 | uint16_t bank, chunk; 399 | 400 | uint32_t count = tud_vendor_read(buff, 36); 401 | if (count != 36) { 402 | printf("wrong number of bytes for savegame chunk, got %u\n", count); 403 | return -1; 404 | } 405 | 406 | bank = (buff[0] << 8) + buff[1]; 407 | chunk = (buff[2] << 8) + buff[3]; 408 | 409 | buff[0] = 0; 410 | if (RomStorage_TransferRamUploadChunk(bank, chunk, 411 | &buff[sizeof(uint16_t) * 2]) < 0) { 412 | buff[0] = 1; 413 | } 414 | 415 | return 1; 416 | } 417 | 418 | static int handle_rtc_download_command(uint8_t buff[63]) { 419 | uint16_t bank, chunk; 420 | size_t offset; 421 | 422 | uint32_t count = tud_vendor_read(buff, 1); 423 | if (count != 1) { 424 | printf("wrong number of bytes for rtc download command, got %u\n", count); 425 | return -1; 426 | } 427 | 428 | const uint8_t requestedRom = buff[0]; 429 | 430 | if (requestedRom >= g_numRoms) { 431 | return -1; 432 | } 433 | 434 | struct RomInfo romInfo = {}; 435 | if (RomStorage_loadRomInfo(requestedRom, &romInfo)) { 436 | return -1; 437 | } 438 | 439 | if (!GameBoyHeader_hasRtc(romInfo.firstBank)) { 440 | return -2; 441 | } 442 | 443 | if (restoreRtcFromFile(&romInfo) < 0) { 444 | return -3; 445 | } 446 | 447 | memset(&buff[1], 0, 48); 448 | offset = 1; 449 | 450 | for (size_t i = 0; i < sizeof(struct GbRtc); i++) { 451 | buff[offset] = g_rtcReal.asArray[i]; 452 | buff[offset + (sizeof(struct GbRtc) * 4)] = g_rtcLatched.asArray[i]; 453 | offset += 4; 454 | } 455 | 456 | offset += 20; 457 | 458 | buff[offset++] = g_rtcTimestamp & 0xFF; 459 | buff[offset++] = (g_rtcTimestamp >> 8) & 0xFF; 460 | buff[offset++] = (g_rtcTimestamp >> 16) & 0xFF; 461 | buff[offset++] = (g_rtcTimestamp >> 24) & 0xFF; 462 | buff[offset++] = (g_rtcTimestamp >> 32) & 0xFF; 463 | buff[offset++] = (g_rtcTimestamp >> 40) & 0xFF; 464 | buff[offset++] = (g_rtcTimestamp >> 48) & 0xFF; 465 | buff[offset++] = (g_rtcTimestamp >> 56) & 0xFF; 466 | 467 | return 48 + 1; 468 | } 469 | 470 | static int handle_rtc_upload_command(uint8_t buff[63]) { 471 | uint16_t bank, chunk; 472 | size_t offset; 473 | 474 | uint32_t count = tud_vendor_read(buff, 48 + 1); 475 | if (count != 49) { 476 | printf("wrong number of bytes for rtc download command, got %u\n", count); 477 | return -1; 478 | } 479 | 480 | const uint8_t requestedRom = buff[0]; 481 | 482 | if (requestedRom >= g_numRoms) { 483 | return -1; 484 | } 485 | 486 | struct RomInfo romInfo = {}; 487 | if (RomStorage_loadRomInfo(requestedRom, &romInfo)) { 488 | return -1; 489 | } 490 | 491 | if (!GameBoyHeader_hasRtc(romInfo.firstBank)) { 492 | return -2; 493 | } 494 | 495 | offset = 1; 496 | 497 | for (size_t i = 0; i < sizeof(struct GbRtc); i++) { 498 | g_rtcReal.asArray[i] = buff[offset]; 499 | g_rtcLatched.asArray[i] = buff[offset + (sizeof(struct GbRtc) * 4)]; 500 | offset += 4; 501 | } 502 | 503 | offset += 20; 504 | 505 | memcpy(&g_rtcTimestamp, &buff[offset], sizeof(uint64_t)); 506 | 507 | storeRtcToFile(&romInfo); 508 | 509 | return 1; 510 | } 511 | -------------------------------------------------------------------------------- /webusb.h: -------------------------------------------------------------------------------- 1 | /* RP2040 GameBoy cartridge 2 | * Copyright (C) 2023 Sebastian Quilitz 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #ifndef E061E307_529F_4C99_8F82_77C4236D159B 19 | #define E061E307_529F_4C99_8F82_77C4236D159B 20 | 21 | void usb_start(); 22 | void usb_shutdown(); 23 | void usb_run(); 24 | 25 | #endif /* E061E307_529F_4C99_8F82_77C4236D159B */ 26 | -------------------------------------------------------------------------------- /ws2812b_spi.c: -------------------------------------------------------------------------------- 1 | /* RP2040 GameBoy cartridge 2 | * Copyright (C) 2024 Sebastian Quilitz 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #include "ws2812b_spi.h" 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #define TARGET_BAUDRATE 2800000 25 | 26 | #define WS2812_FILL_BUFFER(COLOR) \ 27 | { \ 28 | uint8_t mask01 = 0x40; \ 29 | uint8_t mask10 = 0x80; \ 30 | for (uint8_t mask = 0xc0; mask; mask >>= 2) { \ 31 | if ((COLOR & mask) == mask) { \ 32 | *ptr++ = 0xEE; \ 33 | } else if ((COLOR & mask) == mask01) { \ 34 | *ptr++ = 0x8E; \ 35 | } else if ((COLOR & mask) == mask10) { \ 36 | *ptr++ = 0xE8; \ 37 | } else { \ 38 | *ptr++ = 0x88; \ 39 | } \ 40 | mask01 >>= 2; \ 41 | mask10 >>= 2; \ 42 | } \ 43 | } 44 | 45 | static uint8_t _buffer[16]; 46 | spi_inst_t *_spi = NULL; 47 | 48 | void ws2812b_spi_init(spi_inst_t *spi) { 49 | reset_block(spi == spi0 ? RESETS_RESET_SPI0_BITS : RESETS_RESET_SPI1_BITS); 50 | unreset_block_wait(spi == spi0 ? RESETS_RESET_SPI0_BITS 51 | : RESETS_RESET_SPI1_BITS); 52 | spi_set_baudrate(spi, TARGET_BAUDRATE); 53 | spi_set_format(spi, 16, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST); 54 | hw_set_bits(&spi_get_hw(spi)->cr1, SPI_SSPCR1_SSE_BITS); // enable 55 | _spi = spi; 56 | } 57 | 58 | void __no_inline_not_in_flash_func(ws2812b_setRgb)(uint8_t r, uint8_t g, 59 | uint8_t b) { 60 | uint8_t *ptr = (uint8_t *)&_buffer[0]; 61 | WS2812_FILL_BUFFER(g); 62 | WS2812_FILL_BUFFER(r); 63 | WS2812_FILL_BUFFER(b); 64 | 65 | for (size_t i = 0; i < sizeof(_buffer); i += 2) { 66 | spi_get_hw(_spi)->dr = 67 | ((uint32_t)_buffer[i] << 8) | ((uint32_t)_buffer[i + 1]); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /ws2812b_spi.h: -------------------------------------------------------------------------------- 1 | /* RP2040 GameBoy cartridge 2 | * Copyright (C) 2024 Sebastian Quilitz 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation; either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see . 16 | */ 17 | 18 | #ifndef A9FD9FC9_43F8_4ADF_936A_6ADF1E1699A5 19 | #define A9FD9FC9_43F8_4ADF_936A_6ADF1E1699A5 20 | 21 | #include 22 | 23 | void ws2812b_spi_init(spi_inst_t *spi); 24 | void ws2812b_setRgb(uint8_t r, uint8_t g, uint8_t b); 25 | 26 | #endif /* A9FD9FC9_43F8_4ADF_936A_6ADF1E1699A5 */ 27 | --------------------------------------------------------------------------------