├── .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 |
--------------------------------------------------------------------------------