├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── bin ├── bitstream_to_uf2.py └── uartbridge-breakout.py ├── boards └── riffpga.h ├── buildit.sh ├── firmware ├── README.md └── riffpga_generic.uf2 ├── images ├── riffpga_arch.png ├── riffpga_basichookup.jpg ├── riffpga_dnd.png ├── riffpga_slots.png ├── riffpga_statedump.png └── riffpga_terminal.png └── src ├── bitstream.c ├── bitstream.h ├── board.c ├── board.h ├── board_config.c ├── board_config.h ├── board_config_defaults.h ├── board_defs.h ├── board_includes.h ├── cdc_interface.c ├── cdc_interface.h ├── clock_pwm.c ├── clock_pwm.h ├── compile_date.h ├── config_defaults ├── efab_explain.h ├── generic.h ├── psydmi.h └── sys_version.h ├── debug.h ├── driver_state.h ├── fpga.c ├── fpga.h ├── ghostfat.c ├── io_inputs.c ├── io_inputs.h ├── main.c ├── msc_disk.c ├── sui ├── commands │ ├── clocking.c │ ├── clocking.h │ ├── config.c │ ├── config.h │ ├── dump.c │ ├── dump.h │ ├── fpga.c │ ├── fpga.h │ ├── io.c │ ├── io.h │ ├── rp_system.c │ ├── rp_system.h │ ├── uart.c │ └── uart.h ├── sui_command.c ├── sui_command.h ├── sui_handler.c ├── sui_handler.h ├── sui_util.c └── sui_util.h ├── tusb_config.h ├── uart_bridge.c ├── uart_bridge.h ├── uf2.h └── usb_descriptors.c /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | # source graphs 3 | *.dia 4 | 5 | # Prerequisites 6 | *.d 7 | 8 | # Object files 9 | *.o 10 | *.ko 11 | *.obj 12 | *.elf 13 | 14 | # Linker output 15 | *.ilk 16 | *.map 17 | *.exp 18 | 19 | # Precompiled Headers 20 | *.gch 21 | *.pch 22 | 23 | # Libraries 24 | *.lib 25 | *.a 26 | *.la 27 | *.lo 28 | 29 | # Shared objects (inc. Windows DLLs) 30 | *.dll 31 | *.so 32 | *.so.* 33 | *.dylib 34 | 35 | # Executables 36 | *.exe 37 | *.out 38 | *.app 39 | *.i*86 40 | *.x86_64 41 | *.hex 42 | 43 | # Debug files 44 | *.dSYM/ 45 | *.su 46 | *.idb 47 | *.pdb 48 | 49 | # Kernel Module Compile Results 50 | *.mod* 51 | *.cmd 52 | .tmp_versions/ 53 | modules.order 54 | Module.symvers 55 | Mkfile.old 56 | dkms.conf 57 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.17) 2 | 3 | # To build: 4 | # mkdir build 5 | # cd build 6 | # cmake -DPICO_SDK_PATH=/path/to/pico-sdk -DTARGET_GENERIC=ON .. 7 | # note: valid targets are currently 8 | # TARGET_GENERIC 9 | # TARGET_PSYDMI and 10 | # TARGET_EFABLESS_EXPLAIN 11 | # ... probably a better way to do this. 12 | 13 | # Initialise pico_sdk from installed location 14 | # (note this can come from environment, CMake cache etc) 15 | OPTION(PICO_SDK_PATH "Path to pico SDK") 16 | 17 | 18 | 19 | OPTION(TARGET_PSYDMI "Build for PsyDMI" OFF) 20 | OPTION(TARGET_GENERIC "Build generic" OFF) 21 | OPTION(TARGET_EFABLESS_EXPLAIN "Build for efab explain" OFF) 22 | 23 | 24 | set(CMAKE_C_STANDARD 11) 25 | set(CMAKE_CXX_STANDARD 17) 26 | 27 | 28 | 29 | 30 | set(FAMILY rp2040) 31 | set(BOARD pico_sdk) 32 | set(TINYUSB_FAMILY_PROJECT_NAME_PREFIX "tinyusb_dev_") 33 | # Provide an LWIP path for net_lwip_webserver in case the module does not exist in tinyusb 34 | set(TINYUSB_LWIP_PATH ${PICO_LWIP_PATH}) 35 | # Some examples use this, and we need to set this here due to a bug in the TinyUSB CMake config 36 | set(TOP ${PICO_TINYUSB_PATH}) 37 | 38 | 39 | 40 | set(PICO_BOARD_HEADER_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/boards) 41 | set(PICO_BOARD riffpga CACHE STRING "Board type") 42 | 43 | # Pull in Raspberry Pi Pico SDK (must be before project) 44 | include("${PICO_SDK_PATH}/external/pico_sdk_import.cmake") 45 | 46 | if (PICO_SDK_VERSION_STRING VERSION_LESS "1.4.0") 47 | message(FATAL_ERROR "Raspberry Pi Pico SDK version 1.4.0 (or later) required. Your version is ${PICO_SDK_VERSION_STRING}") 48 | endif() 49 | 50 | if (TARGET_EFABLESS_EXPLAIN) 51 | add_compile_definitions(TARGET_EFABLESS_EXPLAIN) 52 | set(PROJ_SUFFIX "efabless") 53 | endif(TARGET_EFABLESS_EXPLAIN) 54 | 55 | if (TARGET_GENERIC) 56 | add_compile_definitions(TARGET_GENERIC) 57 | set(PROJ_SUFFIX "generic") 58 | endif(TARGET_GENERIC) 59 | 60 | if (TARGET_PSYDMI) 61 | add_compile_definitions(TARGET_PSYDMI) 62 | set(PROJ_SUFFIX "psydmi") 63 | endif(TARGET_PSYDMI) 64 | 65 | set(PROJNAME "riffpga_${PROJ_SUFFIX}") 66 | set(EXENAME "riffpga_${PROJ_SUFFIX}") 67 | project(${PROJNAME} C CXX ASM) 68 | 69 | # Initialise the Raspberry Pi Pico SDK 70 | pico_sdk_init() 71 | 72 | 73 | 74 | add_executable(${EXENAME}) 75 | target_include_directories(${EXENAME} PRIVATE src) 76 | # Example source 77 | target_sources(${EXENAME} PUBLIC 78 | ${CMAKE_CURRENT_SOURCE_DIR}/src/main.c 79 | ${CMAKE_CURRENT_SOURCE_DIR}/src/msc_disk.c 80 | ${CMAKE_CURRENT_SOURCE_DIR}/src/usb_descriptors.c 81 | ${CMAKE_CURRENT_SOURCE_DIR}/src/ghostfat.c 82 | ${CMAKE_CURRENT_SOURCE_DIR}/src/board.c 83 | ${CMAKE_CURRENT_SOURCE_DIR}/src/cdc_interface.c 84 | ${CMAKE_CURRENT_SOURCE_DIR}/src/bitstream.c 85 | ${CMAKE_CURRENT_SOURCE_DIR}/src/fpga.c 86 | ${CMAKE_CURRENT_SOURCE_DIR}/src/board_config.c 87 | ${CMAKE_CURRENT_SOURCE_DIR}/src/uart_bridge.c 88 | ${CMAKE_CURRENT_SOURCE_DIR}/src/clock_pwm.c 89 | ${CMAKE_CURRENT_SOURCE_DIR}/src/io_inputs.c 90 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sui/sui_util.c 91 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sui/sui_command.c 92 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sui/sui_handler.c 93 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sui/commands/clocking.c 94 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sui/commands/config.c 95 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sui/commands/dump.c 96 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sui/commands/fpga.c 97 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sui/commands/io.c 98 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sui/commands/uart.c 99 | ${CMAKE_CURRENT_SOURCE_DIR}/src/sui/commands/rp_system.c 100 | 101 | ) 102 | 103 | # In addition to pico_stdlib required for common PicoSDK functionality, add dependency on tinyusb_device 104 | # for TinyUSB device support and tinyusb_board for the additional board support library used by the example 105 | target_link_libraries(${EXENAME} PUBLIC pico_stdlib 106 | pico_unique_id tinyusb_device tinyusb_board 107 | hardware_clocks 108 | hardware_pwm 109 | hardware_spi hardware_flash 110 | hardware_uart 111 | hardware_watchdog 112 | ) 113 | 114 | # Uncomment this line to enable fix for Errata RP2040-E5 (the fix requires use of GPIO 15) 115 | #target_compile_definitions(dev_hid_composite PUBLIC PICO_RP2040_USB_DEVICE_ENUMERATION_FIX=1) 116 | 117 | pico_add_extra_outputs(${EXENAME}) 118 | 119 | -------------------------------------------------------------------------------- /bin/bitstream_to_uf2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | Created on Dec 17, 2024 4 | 5 | @author: Pat Deegan 6 | @copyright: Copyright (C) 2024 Pat Deegan, https://psychogenic.com 7 | ''' 8 | 9 | import sys 10 | import os.path 11 | import argparse 12 | import struct 13 | UF2UtilsPresent = True 14 | try: 15 | from uf2utils.file import UF2File 16 | from uf2utils.family import Family 17 | from uf2utils.block import Header, DataBlock 18 | from uf2utils.constants import Flags 19 | except ModuleNotFoundError: 20 | UF2UtilsPresent = False 21 | 22 | import random 23 | 24 | class UF2Settings: 25 | 26 | def __init__(self, name:str, 27 | familyDesc:str, 28 | familyId:int, 29 | magicstart:int, 30 | magicend:int): 31 | self.name = name 32 | self.description = familyDesc 33 | self.boardFamily = familyId 34 | self.magicStart1 = magicstart 35 | self.magicEnd = magicend 36 | 37 | TargetOptions = { 38 | 'generic': UF2Settings( 39 | name='Generic', 40 | familyDesc='Generic/Sample build', 41 | familyId=0x6e2e91c1, 42 | magicstart=0x951C0634, 43 | magicend= 0x1C73C401), 44 | 45 | 'efabless': UF2Settings( 46 | name='EfabExplain', 47 | familyDesc='Efabless ASIC Sim 2', 48 | familyId=0xefab1e55, 49 | magicstart=0x951C0634, 50 | magicend= 0x1C73C401), 51 | 52 | 'psydmi': UF2Settings( 53 | name='PsyDMI', 54 | familyDesc='PsyDMI driver', 55 | familyId=0xD31B02AD, 56 | magicstart=0x951C0634, 57 | magicend=0x1C73C401) 58 | } 59 | 60 | # these values must be in agreement with the 61 | # riffpga settings running on the target board 62 | base_bitstream_storage_address_kb = 544 63 | reserved_kb_for_bitstream_slot = 512 64 | 65 | metadata_start1_offset = 0x42 66 | metadata_payload_header = "RFMETA" 67 | metadata_payload_version = "01" 68 | metadata_proj_name_maxlen = 23 69 | 70 | factoryreset_start1_offset = 0xdead 71 | factoryreset_payload_header = "RFRSET" 72 | 73 | 74 | 75 | # derived values 76 | page_blocks = 4 # 4 k per page, this has to align for flash reasons 77 | reserved_pages_for_bitstream_slot = int(reserved_kb_for_bitstream_slot/page_blocks) 78 | base_page = int(base_bitstream_storage_address_kb/page_blocks) 79 | 80 | 81 | def get_args(): 82 | parser = argparse.ArgumentParser( 83 | description='Convert bitstream .bin to .uf2 file to use with riffpga', 84 | epilog='Copy the resulting UF2 over to the mounted FPGAUpdate drive') 85 | 86 | targetList = list(TargetOptions.keys()) 87 | 88 | parser.add_argument('--target', required=False, type=str, 89 | choices=targetList, 90 | default=targetList[0], 91 | help=f'Target board [{targetList[0]}]') 92 | parser.add_argument('--slot', required=False, type=int, 93 | default=1, 94 | help='Slot (1-3) [1]') 95 | parser.add_argument('--name', required=False, type=str, 96 | default='', 97 | help='Pretty name for bitstream') 98 | parser.add_argument('--autoclock', required=False, type=int, 99 | default=0, 100 | help='Auto-clock preference for project, in Hz [10-40M]') 101 | 102 | 103 | parser.add_argument('--appendslot', required=False, 104 | action='store_true', 105 | help='Append to slot to output file name') 106 | 107 | parser.add_argument('--factoryreset', required=False, 108 | action='store_true', 109 | help='Ignore other --args, just create a factory reset packet of death') 110 | 111 | parser.add_argument('infile', 112 | help='input bitstream') 113 | parser.add_argument('outfile', help='output UF2 file') 114 | 115 | return parser.parse_args() 116 | 117 | 118 | def get_payload_contents(infilepath:str): 119 | 120 | # return whatever you want in here 121 | with open(infilepath, 'rb') as infile: 122 | bts = infile.read() 123 | 124 | return bts 125 | 126 | def get_new_uf2(settings:UF2Settings): 127 | 128 | myBoard = Family(id=settings.boardFamily, name=settings.name, description=settings.description) 129 | uf2 = UF2File(board_family=myBoard.id, fill_gaps=False, magic_start=settings.magicStart1, 130 | magic_end=settings.magicEnd) 131 | # uf2.header.flags is already Flags.FamilyIDPresent 132 | # you may want to add more, but if you do, probably good 133 | # to preserve the FamilyIDPresent bit 134 | 135 | return uf2 136 | 137 | 138 | def get_metadata_block(settings:UF2Settings, flash_address:int, bitstreamSize:int, autoclock:int, 139 | filename:str, bitstreamName:str=None): 140 | if bitstreamName is None or not len(bitstreamName): 141 | extsplit = os.path.splitext(filename) 142 | if extsplit and len(extsplit) > 1: 143 | bitstreamName = os.path.basename(filename).replace(extsplit[1], '') 144 | else: 145 | bitstreamName = os.path.basename(filename) 146 | 147 | bsnamelenmax = metadata_proj_name_maxlen 148 | bsnamelen = len(bitstreamName) 149 | if bsnamelen > bsnamelenmax: 150 | bitstreamName = bitstreamName[:bsnamelenmax] 151 | bsnamelen = bsnamelenmax 152 | 153 | bsnameArray = bytes(bitstreamName, encoding='ascii') 154 | if bsnamelen < bsnamelenmax: 155 | bsnameArray += bytearray(bsnamelenmax - bsnamelen) 156 | 157 | metaheader = f'{metadata_payload_header}{metadata_payload_version}' 158 | 159 | # struct is 160 | # char HEADER[6] 161 | # uint32 size 162 | # uint8 namelen 163 | # char name[metadata_proj_name_maxlen] 164 | # uint32 clock_hz 165 | 166 | payload = bytes(metaheader, encoding='ascii') 167 | payload += struct.pack(' 4: 212 | print("Select a slot between 1-4") 213 | sys.exit(-1) 214 | 215 | if len(args.name) > metadata_proj_name_maxlen: 216 | print(f'Name can only be up to {metadata_proj_name_maxlen} characters. Will truncate.') 217 | 218 | if args.autoclock: 219 | if args.autoclock < 10 or args.autoclock > 60e6: 220 | print("Auto-clocking only supports rates between 10Hz and 60MHz") 221 | sys.exit(-3) 222 | 223 | 224 | slotidx = args.slot - 1 225 | payload_bytes = get_payload_contents(args.infile) 226 | 227 | # stick it somewhere within its slot... 228 | 229 | 230 | 231 | uf2sets = TargetOptions[args.target] 232 | 233 | uf2 = get_new_uf2(uf2sets) 234 | 235 | 236 | # figure out a start address for the bitstream. 237 | # we have a little room to play, important thing is to page-align. 238 | 239 | # number of pages this infile requires, plus a teeny bit of slack 240 | pages_required = int(len(payload_bytes)/(4*1024)) + 4 241 | 242 | # base page for this slot 243 | lowest_page_for_slot = base_page + (reserved_pages_for_bitstream_slot * slotidx) 244 | # actual start page we'll use, randomized, with a teeny bit of slack on the front as well 245 | start_page = lowest_page_for_slot + random.randint(4, reserved_pages_for_bitstream_slot-pages_required) 246 | # actual start address, based on page 247 | start_offset = start_page*page_blocks*1024 248 | 249 | # append a data block for meta information 250 | uf2.append_datablock(get_metadata_block(uf2sets, start_offset, 251 | len(payload_bytes), args.autoclock, 252 | args.infile, args.name)) 253 | uf2.append_payload(payload_bytes, 254 | start_offset=start_offset, 255 | block_payload_size=256) 256 | 257 | if args.appendslot: 258 | fnameext = os.path.splitext(args.outfile) 259 | outfilename = f'{fnameext[0]}_{args.slot}{fnameext[1]}' 260 | else: 261 | outfilename = args.outfile 262 | 263 | uf2.to_file(outfilename) 264 | print(f"\n\nGenerated UF2 for slot {args.slot}, starting at address {hex(start_offset)} with size {len(payload_bytes)}") 265 | print(f"It now available at {outfilename}\n") 266 | 267 | 268 | if __name__ == "__main__": 269 | main() 270 | 271 | -------------------------------------------------------------------------------- /bin/uartbridge-breakout.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | ''' 3 | If you setup the system with a "weird" breakout for the 4 | UART bridge, rather than the default "ESCAPE ESCAPE ESCAPE", 5 | this script will shoot over the write bytes to get out of 6 | bridge-mode. 7 | ''' 8 | # serial port 9 | serial_port = '/dev/ttyACM0' 10 | # breakout sequence 11 | breakout = b'\x1b\x03\x04\x0a' 12 | 13 | import serial 14 | import time 15 | 16 | def do_breakout(): 17 | try: 18 | ser = serial.Serial(serial_port) 19 | except: 20 | print(f"Could not open serial port {serial_port}? Aborting.") 21 | return 22 | print(f"Writing breakout sequence ({breakout}) to {serial_port}") 23 | ser.write(breakout) 24 | time.sleep(0.5) 25 | 26 | if __name__ == '__main__': 27 | do_breakout() 28 | 29 | -------------------------------------------------------------------------------- /boards/riffpga.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 | // pico_cmake_set PICO_PLATFORM=rp2040 15 | 16 | #ifndef _BOARDS_RIFFPGA_H 17 | #define _BOARDS_RIFFPGA_H 18 | 19 | // For board detection 20 | #define RIFFPGA_BOARD 21 | 22 | #include "../src/board_config_defaults.h" 23 | 24 | // --- UART --- 25 | #ifndef PICO_DEFAULT_UART 26 | #define PICO_DEFAULT_UART 0 27 | #endif 28 | #ifndef PICO_DEFAULT_UART_TX_PIN 29 | #define PICO_DEFAULT_UART_TX_PIN 0 30 | #endif 31 | #ifndef PICO_DEFAULT_UART_RX_PIN 32 | #define PICO_DEFAULT_UART_RX_PIN 1 33 | #endif 34 | 35 | 36 | // --- I2C --- 37 | #ifndef PICO_DEFAULT_I2C 38 | #define PICO_DEFAULT_I2C 0 39 | #endif 40 | #ifndef PICO_DEFAULT_I2C_SDA_PIN 41 | #define PICO_DEFAULT_I2C_SDA_PIN 4 42 | #endif 43 | #ifndef PICO_DEFAULT_I2C_SCL_PIN 44 | #define PICO_DEFAULT_I2C_SCL_PIN 5 45 | #endif 46 | 47 | // --- SPI --- 48 | #ifndef PICO_DEFAULT_SPI 49 | #define PICO_DEFAULT_SPI 0 50 | #endif 51 | #ifndef PICO_DEFAULT_SPI_SCK_PIN 52 | #define PICO_DEFAULT_SPI_SCK_PIN 18 53 | #endif 54 | #ifndef PICO_DEFAULT_SPI_TX_PIN 55 | #define PICO_DEFAULT_SPI_TX_PIN 19 56 | #endif 57 | #ifndef PICO_DEFAULT_SPI_RX_PIN 58 | #define PICO_DEFAULT_SPI_RX_PIN 16 59 | #endif 60 | #ifndef PICO_DEFAULT_SPI_CSN_PIN 61 | #define PICO_DEFAULT_SPI_CSN_PIN 17 62 | #endif 63 | 64 | // --- FLASH --- 65 | 66 | #define PICO_BOOT_STAGE2_CHOOSE_W25Q080 1 67 | 68 | #ifndef PICO_FLASH_SPI_CLKDIV 69 | #define PICO_FLASH_SPI_CLKDIV 2 70 | #endif 71 | 72 | // pico_cmake_set_default PICO_FLASH_SIZE_BYTES = (2 * 1024 * 1024) 73 | #ifndef PICO_FLASH_SIZE_BYTES 74 | #define PICO_FLASH_SIZE_BYTES (4 * 1024 * 1024) 75 | #endif 76 | // Drive high to force power supply into PWM mode (lower ripple on 3V3 at light loads) 77 | #define PICO_SMPS_MODE_PIN 23 78 | 79 | #ifndef PICO_RP2040_B0_SUPPORTED 80 | #define PICO_RP2040_B0_SUPPORTED 1 81 | #endif 82 | 83 | // The GPIO Pin used to read VBUS to determine if the device is battery powered. 84 | #ifndef PICO_VBUS_PIN 85 | #define PICO_VBUS_PIN 24 86 | #endif 87 | 88 | // The GPIO Pin used to monitor VSYS. Typically you would use this with ADC. 89 | // There is an example in adc/read_vsys in pico-examples. 90 | #ifndef PICO_VSYS_PIN 91 | #define PICO_VSYS_PIN 29 92 | #endif 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /buildit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | PICO_SDK_PATH=/path/to/pico-sdk 3 | # TARG=TARGET_GENERIC 4 | TARG=TARGET_EFABLESS_EXPLAIN 5 | #TARG=TARGET_PSYDMI 6 | mkdir build 7 | cd build 8 | 9 | cmake -DPICO_SDK_PATH=$PICO_SDK_PATH -D${TARG}=ON .. 10 | make 11 | -------------------------------------------------------------------------------- /firmware/README.md: -------------------------------------------------------------------------------- 1 | # riffpga pre-compiled firmware sample 2 | 3 | This is a pre-compiled version of riffpga. You can put an RP2040, like Pi Pico boards, into bootmode and drop this onto the RPI-RP2 drive. 4 | 5 | It is built with the defaults from config_defaults/generic.h so, if you want to wire it up to an FPGA, follow the various _PIN_XYZ_ defines in there. 6 | 7 | -------------------------------------------------------------------------------- /firmware/riffpga_generic.uf2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psychogenic/riffpga/355ff2c57f7475c11e659b1eb8308e7a264bdcc2/firmware/riffpga_generic.uf2 -------------------------------------------------------------------------------- /images/riffpga_arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psychogenic/riffpga/355ff2c57f7475c11e659b1eb8308e7a264bdcc2/images/riffpga_arch.png -------------------------------------------------------------------------------- /images/riffpga_basichookup.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psychogenic/riffpga/355ff2c57f7475c11e659b1eb8308e7a264bdcc2/images/riffpga_basichookup.jpg -------------------------------------------------------------------------------- /images/riffpga_dnd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psychogenic/riffpga/355ff2c57f7475c11e659b1eb8308e7a264bdcc2/images/riffpga_dnd.png -------------------------------------------------------------------------------- /images/riffpga_slots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psychogenic/riffpga/355ff2c57f7475c11e659b1eb8308e7a264bdcc2/images/riffpga_slots.png -------------------------------------------------------------------------------- /images/riffpga_statedump.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psychogenic/riffpga/355ff2c57f7475c11e659b1eb8308e7a264bdcc2/images/riffpga_statedump.png -------------------------------------------------------------------------------- /images/riffpga_terminal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/psychogenic/riffpga/355ff2c57f7475c11e659b1eb8308e7a264bdcc2/images/riffpga_terminal.png -------------------------------------------------------------------------------- /src/bitstream.c: -------------------------------------------------------------------------------- 1 | /* 2 | * bitstream.c, part of the riffpga project 3 | * 4 | * Created on: Dec 18, 2024 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #include "bitstream.h" 23 | #include "board_defs.h" 24 | #include "board.h" 25 | #include "debug.h" 26 | #include "fpga.h" 27 | #include "board_config.h" 28 | #include "driver_state.h" 29 | 30 | // define BS_DEBUG_ENABLE 31 | #ifdef BS_DEBUG_ENABLE 32 | #define BS_DEBUG_ENDLN() DEBUG_ENDLN() 33 | #define BS_DEBUG_BUF(b, len) DEBUG_BUF(b, len) 34 | #define BS_DEBUG(s) DEBUG(s) 35 | #define BS_DEBUG_LN(s) DEBUG_LN(s) 36 | 37 | #define BS_DEBUG_U32(v) DEBUG_U32(v) 38 | #define BS_DEBUG_U32_LN(v) DEBUG_U32_LN(v) 39 | #define BS_DEBUG_U16(v) DEBUG_U16(v) 40 | 41 | #define BS_DEBUG_U16_LN(v) DEBUG_U16_LN(v) 42 | #define BS_DEBUG_U8(v) DEBUG_U8(v) 43 | #define BS_DEBUG_U8_LN(v) DEBUG_U8_LN(v) 44 | #else 45 | 46 | #define BS_DEBUG_ENDLN() 47 | #define BS_DEBUG_BUF(b, len) 48 | #define BS_DEBUG(s) 49 | #define BS_DEBUG_LN(s) 50 | 51 | #define BS_DEBUG_U32(v) 52 | #define BS_DEBUG_U32_LN(v) 53 | #define BS_DEBUG_U16(v) 54 | 55 | #define BS_DEBUG_U16_LN(v) 56 | #define BS_DEBUG_U8(v) 57 | #define BS_DEBUG_U8_LN(v) 58 | #endif 59 | 60 | static Bitstream_Marker_State bs_marker_state = { 0 }; 61 | 62 | void bs_init(void) { 63 | memset(&bs_marker_state, 0, sizeof(bs_marker_state)); 64 | } 65 | 66 | UF2_Block* bs_info() { 67 | return &(bs_marker_state.info); 68 | } 69 | 70 | const Bitstream_Marker_State* bs_marker_get(void) { 71 | return &bs_marker_state; 72 | } 73 | 74 | 75 | const Bitstream_Settings * bs_settings_get(void) { 76 | return &(bs_marker_state.settings); 77 | } 78 | 79 | uint32_t bs_file_size(void) { 80 | return bs_marker_state.settings.size; 81 | } 82 | uint32_t bs_uf2_file_size(void) { 83 | return bs_marker_state.settings.uf2_file_size; 84 | } 85 | 86 | bool bs_have_checked_for_marker(void) { 87 | return bs_marker_state.have_checked; 88 | } 89 | 90 | void bs_clear_size_check_flag(void) { 91 | bs_marker_state.have_checked = false; 92 | } 93 | 94 | // pass it an array of Bitstream_Slot_Content[POSITION_SLOTS_ALLOWED] 95 | // returns num found 96 | uint8_t bs_slot_contents(Bitstream_Slot_Content *contents) { 97 | uint8_t num_found = 0; 98 | Bitstream_Marker_State markercheck; 99 | for (uint8_t i = 0; i < POSITION_SLOTS_ALLOWED; i++) { 100 | if (!bs_load_marker(i, &markercheck)) { 101 | 102 | contents[i].info.namelen = 0; 103 | contents[i].found = false; 104 | } else { 105 | 106 | num_found++; 107 | contents[i].found = true; 108 | memcpy(&contents[i].info, &markercheck.settings.user_info, sizeof(contents[i].info)); 109 | } 110 | } 111 | return num_found; 112 | } 113 | 114 | uint32_t bs_load_marker(uint8_t slot, Bitstream_Marker_State *into) { 115 | 116 | BoardConfigPtrConst bc = boardconfig_get(); 117 | board_flash_read(boardconfig_bs_marker_address_for(slot), &(into->info), 118 | sizeof(into->info)); 119 | 120 | into->have_checked = true; 121 | into->settings.size = 0; 122 | into->settings.uf2_file_size = 0; 123 | if (!(into->info.magicStart0 == UF2_MAGIC_START0 124 | && into->info.magicStart1 == bc->bs_marker.magic_start 125 | && into->info.familyID == bc->bs_marker.family_id 126 | && into->info.magicEnd == bc->bs_marker.magic_end)) { 127 | return 0; 128 | } 129 | 130 | BS_DEBUG("Have size info! "); 131 | // extract that UF2 data payload into a nice struct 132 | // yes, I know I could just cast 133 | memcpy(&into->settings, into->info.data, sizeof(into->settings)); 134 | 135 | // store the "file size" we will declare such that 136 | // the host can download the entire UF2, basically 137 | // 512 bytes per block 138 | into->settings.uf2_file_size = into->info.numBlocks * 512; 139 | 140 | return into->settings.size; // bitstream_size; 141 | 142 | } 143 | uint32_t bs_check_for_marker(void) { 144 | BoardConfigPtrConst bc = boardconfig_get(); 145 | Bitstream_Marker_State statecheck; 146 | 147 | bs_marker_state.have_checked = true; 148 | bs_marker_state.settings.size = 0; 149 | bs_marker_state.settings.uf2_file_size = 0; 150 | 151 | if (bs_load_marker(boardconfig_selected_bitstream_slot(), &statecheck)) { 152 | // ok we found it! 153 | memcpy(&bs_marker_state, &statecheck, sizeof(Bitstream_Marker_State)); 154 | return bs_marker_state.settings.size; 155 | } 156 | // no worky 157 | BS_DEBUG_LN("BAD SIZE MARK"); 158 | BS_DEBUG_U32_LN(statecheck.info.magicStart0); 159 | BS_DEBUG_U32_LN(statecheck.info.magicStart1); 160 | BS_DEBUG_U32_LN(statecheck.info.magicEnd); 161 | 162 | memset(&bs_marker_state.info, 0, sizeof(bs_marker_state.info)); 163 | return 0; 164 | } 165 | 166 | void bs_write_marker(uint32_t num_blocks, uint32_t bitstream_size, 167 | uint32_t address_start, Bitstream_MetaInfo *info) { 168 | bs_write_marker_to_slot(boardconfig_selected_bitstream_slot(), num_blocks, 169 | bitstream_size, address_start, info); 170 | 171 | } 172 | 173 | void bs_write_marker_to_slot(uint8_t slotidx, uint32_t num_blocks, 174 | uint32_t bitstream_size, uint32_t address_start, Bitstream_MetaInfo *info) { 175 | 176 | BoardConfigPtrConst bc = boardconfig_get(); 177 | bs_marker_state.info.magicStart0 = UF2_MAGIC_START0; 178 | bs_marker_state.info.magicStart1 = bc->bs_marker.magic_start; 179 | bs_marker_state.info.magicEnd = bc->bs_marker.magic_end; 180 | bs_marker_state.info.familyID = bc->bs_marker.family_id; 181 | bs_marker_state.info.flags = UF2_FLAG_FAMILYID | UF2_FLAG_NOFLASH; 182 | bs_marker_state.info.blockNo = 1; 183 | bs_marker_state.info.numBlocks = num_blocks; 184 | 185 | 186 | Bitstream_Settings bs_settings; 187 | bs_settings.size = bitstream_size; 188 | bs_settings.start_address = address_start; 189 | memcpy(&bs_settings.user_info, info, sizeof(bs_settings.user_info)); 190 | 191 | memcpy(bs_marker_state.info.data, &bs_settings, sizeof(bs_settings)); 192 | 193 | CDCWRITEFLUSH(); BS_DEBUG("Writing bs mrk len: "); 194 | BS_DEBUG_U32(total_len); 195 | BS_DEBUG(" start: "); 196 | BS_DEBUG_U32(address_start); 197 | if (bs_settings.user_info.namelen) { 198 | BS_DEBUG(" for "); 199 | BS_DEBUG_BUF(bs_settings.user_info.name, bs_settings.user_info.namelen); 200 | } 201 | BS_DEBUG("\r\n"); 202 | 203 | CDCWRITEFLUSH(); 204 | 205 | board_flash_write(boardconfig_bs_marker_address_for(slotidx), 206 | &bs_marker_state.info, sizeof(bs_marker_state.info)); 207 | board_flash_pages_erased_clear(); 208 | } 209 | 210 | void bs_erase_slot(uint8_t slot) { 211 | Bitstream_Marker_State empty = { 0 }; 212 | board_flash_pages_erased_clear(); 213 | board_flash_write(boardconfig_bs_marker_address_for(slot), &empty.info, 214 | sizeof(bs_marker_state.info)); 215 | 216 | board_flash_pages_erased_clear(); 217 | } 218 | void bs_erase_all(void) { 219 | for (uint8_t i = 0; i < POSITION_SLOTS_NUM; i++) { 220 | bs_erase_slot(i); 221 | } 222 | } 223 | 224 | bool bs_program_fpga(bs_prog_yield_cb cb) { 225 | 226 | uint8_t xfer_block[FLASH_SPI_XFER_BLOCKSIZE]; 227 | 228 | if (!bs_have_checked_for_marker()) { 229 | if (!bs_check_for_marker()) { 230 | BS_DEBUG_LN("No bitstream in flash?"); 231 | return false; 232 | } 233 | } 234 | 235 | fpga_enter_programming_mode(); 236 | uint32_t cur_addr = bs_marker_state.settings.start_address; 237 | uint32_t end_addr = bs_marker_state.settings.start_address + bs_marker_state.settings.size; 238 | BS_DEBUG("FLSH prog "); BS_DEBUG_U32(cur_addr); BS_DEBUG("-"); BS_DEBUG_U32_LN(end_addr); 239 | 240 | uint16_t xfer_size; 241 | uint32_t total_xfered = 0; 242 | #ifdef BS_DEBUG_ENABLE 243 | uint32_t bytes_sum = 0; 244 | #endif 245 | while (cur_addr < end_addr) { 246 | xfer_size = FLASH_SPI_XFER_BLOCKSIZE; 247 | if ((cur_addr + xfer_size) >= end_addr) { 248 | xfer_size = (uint16_t) (end_addr - cur_addr); 249 | } 250 | 251 | if (cb != NULL) { 252 | cb(); 253 | } 254 | if (xfer_size) { 255 | /* 256 | BS_DEBUG_U32(cur_addr); 257 | BS_DEBUG(" "); 258 | BS_DEBUG_U32_LN(xfer_size); 259 | cb(); 260 | */ 261 | board_flash_read(cur_addr, xfer_block, xfer_size); 262 | fpga_spi_write(xfer_block, xfer_size); 263 | // FORCE_BS_DEBUG_BUF(xfer_block, xfer_size); 264 | // sleep_ms(FLASH_SPI_XFER_BLOCKSIZE * 2); 265 | 266 | cur_addr += xfer_size; 267 | total_xfered += xfer_size; 268 | #ifdef BS_DEBUG_ENABLE 269 | for (uint16_t i=0; i 60000000)) { 287 | DEBUG("Programmed FPGA but user specified invalid auto-clock "); 288 | DEBUG_U32_LN(autoclockhz); 289 | } else { 290 | if (MainDriverState.clocking_manually) { 291 | DEBUG_LN("Have auto-clock configed but clocking manually"); 292 | } else { 293 | boardconfig_set_autoclock_hz(autoclockhz); 294 | } 295 | } 296 | } 297 | 298 | 299 | return (total_xfered > 0); 300 | 301 | } 302 | -------------------------------------------------------------------------------- /src/bitstream.h: -------------------------------------------------------------------------------- 1 | /* 2 | * bitstream.h, part of the riffpga project 3 | * 4 | * Functionality related to the binary bitstream stored in 5 | * a slot in flash, as well as the related meta-data. 6 | * 7 | * Created on: Dec 18, 2024 8 | * Author: Pat Deegan 9 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 10 | * 11 | * This program is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation, either version 3 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program. If not, see . 23 | */ 24 | 25 | #ifndef SRC_BITSTREAM_H_ 26 | #define SRC_BITSTREAM_H_ 27 | 28 | #include "board_defs.h" 29 | 30 | #include "uf2.h" 31 | 32 | 33 | typedef void (*bs_prog_yield_cb)(void); 34 | 35 | /* 36 | * Bitstream_MetaInfo 37 | * contents of meta info UF2 data block 38 | * passed within written UF2 bin files 39 | */ 40 | typedef struct RIF_PACKED_STRUCT bitstream_metainfo_struct { 41 | uint32_t bssize; 42 | uint8_t namelen; 43 | char name[BITSTREAM_NAME_MAXLEN]; 44 | uint32_t clock_hz; 45 | } Bitstream_MetaInfo; 46 | 47 | 48 | 49 | typedef struct bitstream_metainfo_payloadstruct { 50 | uint8_t header[8]; // RFMETA01 51 | Bitstream_MetaInfo info; 52 | } Bitstream_MetaInfo_Payload; 53 | 54 | 55 | typedef struct bsslotstatestruct { 56 | bool found; 57 | Bitstream_MetaInfo info; 58 | } Bitstream_Slot_Content; 59 | 60 | 61 | 62 | void bs_init(void); 63 | 64 | bool bs_have_checked_for_marker(void); 65 | void bs_clear_size_check_flag(void); 66 | 67 | /* performs check for a valid size marker in flash, 68 | * returns actual bitstream size if found, 0 otherwise. 69 | */ 70 | uint32_t bs_check_for_marker(void); 71 | 72 | /* if check_for_info succeeded, 73 | * the info returns the block itself, 74 | * and uf2_file_size() returns the UF2 file 75 | * size to declare (which is bigger, because of meta data) 76 | */ 77 | UF2_Block * bs_info(void); 78 | uint32_t bs_uf2_file_size(void); 79 | uint32_t bs_file_size(void); 80 | 81 | 82 | bool bs_program_fpga(bs_prog_yield_cb cb); 83 | 84 | 85 | // pass it an array of Bitstream_Slot_Content[POSITION_SLOTS_ALLOWED] 86 | // returns num found 87 | uint8_t bs_slot_contents(Bitstream_Slot_Content * contents); 88 | 89 | 90 | void bs_write_marker(uint32_t num_blocks, uint32_t bitstream_size, 91 | uint32_t address_start, Bitstream_MetaInfo *info); 92 | 93 | void bs_write_marker_to_slot(uint8_t slot, uint32_t num_blocks, uint32_t bitstream_size, 94 | uint32_t address_start, Bitstream_MetaInfo *info); 95 | 96 | void bs_erase_slot(uint8_t slot); 97 | void bs_erase_all(void); 98 | 99 | typedef struct bitstream_settings_struct { 100 | uint32_t size; 101 | uint32_t uf2_file_size; 102 | uint32_t start_address; 103 | Bitstream_MetaInfo user_info; 104 | } Bitstream_Settings; 105 | 106 | typedef struct bitstream_state_struct { 107 | bool have_checked; 108 | UF2_Block info; 109 | Bitstream_Settings settings; 110 | } Bitstream_Marker_State; 111 | 112 | const Bitstream_Marker_State * bs_marker_get(void); 113 | const Bitstream_Settings * bs_settings_get(void); 114 | 115 | uint32_t bs_load_marker(uint8_t slot, Bitstream_Marker_State * into); 116 | 117 | 118 | 119 | #endif /* SRC_BITSTREAM_H_ */ 120 | -------------------------------------------------------------------------------- /src/board.c: -------------------------------------------------------------------------------- 1 | /* 2 | * board.c, part of the riffpga project 3 | * Author: Pat Deegan 4 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #include "board_includes.h" 21 | #include "board.h" 22 | #include "debug.h" 23 | #include "cdc_interface.h" 24 | 25 | const uint8_t *flash_read_access = (const uint8_t*) (XIP_BASE); 26 | /* note: each page is 4k */ 27 | #define MAX_NUM_PAGES 192 28 | 29 | typedef struct page_state_struct { 30 | int16_t index; 31 | uint8_t erased; 32 | uint16_t blocks_written; 33 | } PageState; 34 | static PageState pages_erased[MAX_NUM_PAGES] = { 0 }; 35 | static uint32_t size_uf2_written = 0; 36 | static uint32_t uf2_start_address = 0; 37 | 38 | //define BRD_DEBUG_ENABLE 39 | #ifdef BRD_DEBUG_ENABLE 40 | #define BRD_DEBUG_ENDLN() DEBUG_ENDLN() 41 | #define BRD_DEBUG_BUF(b, len) DEBUG_BUF(b, len) 42 | #define BRD_DEBUG(s) DEBUG(s) 43 | #define BRD_DEBUG_LN(s) DEBUG_LN(s) 44 | 45 | #define BRD_DEBUG_U32(v) DEBUG_U32(v) 46 | #define BRD_DEBUG_U32_LN(v) DEBUG_U32_LN(v) 47 | #define BRD_DEBUG_U16(v) DEBUG_U16(v) 48 | 49 | #define BRD_DEBUG_U16_LN(v) DEBUG_U16_LN(v) 50 | #define BRD_DEBUG_U8(v) DEBUG_U8(v) 51 | #define BRD_DEBUG_U8_LN(v) DEBUG_U8_LN(v) 52 | #else 53 | 54 | #define BRD_DEBUG_ENDLN() 55 | #define BRD_DEBUG_BUF(b, len) 56 | #define BRD_DEBUG(s) 57 | #define BRD_DEBUG_LN(s) 58 | 59 | #define BRD_DEBUG_U32(v) 60 | #define BRD_DEBUG_U32_LN(v) 61 | #define BRD_DEBUG_U16(v) 62 | 63 | #define BRD_DEBUG_U16_LN(v) 64 | #define BRD_DEBUG_U8(v) 65 | #define BRD_DEBUG_U8_LN(v) 66 | #endif 67 | 68 | void board_reboot(void) { 69 | watchdog_enable(REBOOT_DELAY_MS, 1); 70 | // while(1) {} 71 | 72 | } 73 | 74 | void board_gpio_init(void) { 75 | 76 | } 77 | 78 | // Initialize flash for DFU 79 | void board_flash_init(void) { 80 | board_size_written_clear(); 81 | 82 | } 83 | static uint8_t pages_erased_cache_index_for(uint16_t page) { 84 | 85 | for (uint8_t i = 0; i < MAX_NUM_PAGES; i++) { 86 | if (pages_erased[i].index < 0) { 87 | // shouldn't happen 88 | return MAX_NUM_PAGES - 1; 89 | } 90 | if (pages_erased[i].index == page) { 91 | return i; 92 | } 93 | } 94 | // shouldn't happen 95 | return MAX_NUM_PAGES - 1; 96 | } 97 | static bool page_was_erased(uint16_t page) { 98 | 99 | for (uint8_t i = 0; i < MAX_NUM_PAGES; i++) { 100 | if (pages_erased[i].index < 0) { 101 | return 0; 102 | } 103 | if (pages_erased[i].index == page) { 104 | return pages_erased[i].erased; 105 | } 106 | } 107 | return 0; 108 | } 109 | 110 | static uint16_t page_for(uint32_t addr) { 111 | uint16_t pid = (uint16_t) (addr >> 12); 112 | return pid; 113 | } 114 | static uint16_t page_blockmask_for(uint32_t req_addr, uint32_t len) { 115 | uint16_t pid = page_for(req_addr); 116 | 117 | uint32_t page_start_address = (((uint32_t)pid) << 12); 118 | 119 | uint32_t addr = req_addr; 120 | uint16_t write_block_size = 256; 121 | uint16_t mask = 0; 122 | while (addr < (req_addr + len)) { 123 | 124 | mask |= 1 << (uint8_t)((addr - page_start_address)/write_block_size); 125 | 126 | addr += write_block_size; 127 | 128 | } 129 | /* 130 | BRD_DEBUG("BMsk: "); 131 | BRD_DEBUG_U32(addr); 132 | BRD_DEBUG("/"); 133 | BRD_DEBUG_U32(page_start_address); 134 | BRD_DEBUG(", "); 135 | BRD_DEBUG_U32(len); 136 | BRD_DEBUG(": "); 137 | BRD_DEBUG_U16_LN(mask); 138 | */ 139 | return mask; 140 | 141 | } 142 | 143 | static bool has_been_programmed(uint32_t req_addr, uint32_t len) { 144 | uint16_t page = page_for(req_addr); 145 | uint16_t blocks_to_write = page_blockmask_for(req_addr, len); 146 | 147 | uint8_t idx = pages_erased_cache_index_for(page); 148 | if (pages_erased[idx].blocks_written & blocks_to_write) { 149 | return true; 150 | } 151 | return false; 152 | } 153 | 154 | static void register_programmed(uint32_t req_addr, uint32_t len) { 155 | uint16_t page = page_for(req_addr); 156 | uint16_t blocks_written = page_blockmask_for(req_addr, len); 157 | 158 | uint8_t idx = pages_erased_cache_index_for(page); 159 | pages_erased[idx].blocks_written |= blocks_written; 160 | BRD_DEBUG("Pg "); 161 | BRD_DEBUG_U16(page); 162 | BRD_DEBUG(" wrt "); 163 | BRD_DEBUG_U16_LN(pages_erased[idx].blocks_written); 164 | 165 | 166 | if (pages_erased[idx].blocks_written == 0xffff) { 167 | // we *had* erased, but now everything's been written over 168 | // this is no longer to be considered erased. 169 | pages_erased[idx].erased = 0; 170 | BRD_DEBUG("All filled!"); 171 | } 172 | 173 | } 174 | 175 | uint32_t board_first_written_address(void) { 176 | return uf2_start_address; 177 | } 178 | int16_t board_first_written_page(void) { 179 | int16_t first_page = 0x7fff; 180 | bool found_pages = false; 181 | for (uint8_t i = 0; i < MAX_NUM_PAGES; i++) { 182 | if (pages_erased[i].index >= 0) { 183 | if (pages_erased[i].index < first_page) { 184 | first_page = pages_erased[i].index; 185 | found_pages = true; 186 | } 187 | } 188 | } 189 | 190 | if (found_pages) { 191 | return first_page; 192 | } 193 | return -1; 194 | } 195 | 196 | uint32_t page_address_from_index(uint16_t page) { 197 | uint32_t addr = ((uint32_t) page) << 12; 198 | return addr; 199 | } 200 | 201 | static void mark_as_erased(uint16_t page) { 202 | 203 | for (uint8_t i = 0; i < MAX_NUM_PAGES; i++) { 204 | if (pages_erased[i].index < 0) { 205 | pages_erased[i].index = page; 206 | pages_erased[i].erased = 1; 207 | pages_erased[i].blocks_written = 0; 208 | BRD_DEBUG("Mark page 0x"); BRD_DEBUG_U16(page); BRD_DEBUG(" erased @ "); BRD_DEBUG_U8_LN(i); 209 | CDCWRITEFLUSH(); 210 | 211 | return; 212 | } 213 | } 214 | } 215 | 216 | // Get size of flash 217 | uint32_t board_flash_size(void) { 218 | return BOARD_FLASH_SIZE; 219 | } 220 | 221 | // Read from flash 222 | void board_flash_read(uint32_t addr, void *buffer, uint32_t len) { 223 | // BRD_DEBUG("flash read: "); 224 | // BRD_DEBUG_U32_LN(addr); 225 | uint32_t offset = addr; 226 | memcpy(buffer, (void const*) &(flash_read_access[offset]), len); 227 | 228 | } 229 | 230 | static void call_flash_page_erase(void *param) { 231 | uint16_t *page = (uint16_t*) param; 232 | 233 | uint32_t offset = page_address_from_index(*page); 234 | BRD_DEBUG("Erasing @"); BRD_DEBUG_U32_LN(offset); 235 | flash_range_erase(offset, FLASH_SECTOR_SIZE); 236 | } 237 | 238 | static void call_flash_range_program(void *param) { 239 | uint32_t offset = ((uintptr_t*) param)[0]; 240 | const uint8_t *data = (const uint8_t*) ((uintptr_t*) param)[1]; 241 | const uint32_t *size = (const uint32_t*) ((uintptr_t*) param)[2]; 242 | // BRD_DEBUG("Range prog @"); 243 | // BRD_DEBUG_U32(offset); 244 | // BRD_DEBUG(" sz:"); 245 | // BRD_DEBUG_U32_LN(*size); 246 | 247 | flash_range_program(offset, data, *size); 248 | } 249 | 250 | bool board_flash_write(uint32_t addr, void const *data, uint32_t len) { 251 | (void) data; 252 | (void) len; 253 | // BRD_DEBUG("flash write: "); 254 | // BRD_DEBUG_U32_LN(addr); 255 | uint32_t *lenptr = &len; 256 | int rc; 257 | uint16_t page_index = page_for(addr); 258 | if (!page_was_erased(page_index)) { 259 | rc = flash_safe_execute(call_flash_page_erase, (void*) (&page_index), 260 | UINT32_MAX); 261 | if (rc == PICO_OK) { 262 | mark_as_erased(page_index); 263 | } else { 264 | BRD_DEBUG_LN("ERR ERASE"); 265 | CDCWRITESTRING("\r\nFlash Page errase error!\r\n"); 266 | return 0; 267 | } 268 | } 269 | if (addr < uf2_start_address) { 270 | uf2_start_address = addr; 271 | } 272 | 273 | if (has_been_programmed(addr, len)) { 274 | CDCWRITESTRING("\r\nWRN: DOUBLE write @ 0x"); 275 | cdc_write_u32_ln(addr); 276 | 277 | BRD_DEBUG("Has already been written! "); 278 | BRD_DEBUG_U32(addr); 279 | BRD_DEBUG(" len "); 280 | BRD_DEBUG_U32_LN(len); 281 | 282 | } 283 | 284 | 285 | uintptr_t params[] = { addr, (uintptr_t) data, (uintptr_t) lenptr }; 286 | rc = flash_safe_execute(call_flash_range_program, params, UINT32_MAX); 287 | if (rc == PICO_OK) { 288 | // BRD_DEBUG_LN("Wrote!"); 289 | register_programmed(addr, len); 290 | size_uf2_written += len; 291 | } else { 292 | CDCWRITESTRING("\r\nWrite fail!! @0x"); 293 | cdc_write_u32_ln(addr); 294 | } 295 | 296 | return 1; 297 | 298 | } 299 | 300 | uint32_t board_size_written(void) { 301 | return size_uf2_written; 302 | } 303 | 304 | void board_flash_pages_erased_clear(void) { 305 | 306 | for (uint8_t i = 0; i < MAX_NUM_PAGES; i++) { 307 | pages_erased[i].index = -1; 308 | } 309 | 310 | } 311 | void board_size_written_clear(void) { 312 | size_uf2_written = 0; 313 | // set to highest possible 314 | uf2_start_address = 0xffffffff; 315 | 316 | // clear out pages erased cache 317 | board_flash_pages_erased_clear(); 318 | 319 | } 320 | 321 | // Flush/Sync flash contents 322 | void board_flash_flush(void) { 323 | 324 | BRD_DEBUG_LN("FLUSH CALLED!!!! WE DONE"); 325 | board_flash_pages_erased_clear(); 326 | sleep_ms(250); 327 | } 328 | -------------------------------------------------------------------------------- /src/board.h: -------------------------------------------------------------------------------- 1 | /* 2 | * board.h, part of the riffpga project 3 | * 4 | * Board stuff, mostly related to flash memory. 5 | * 6 | * Author: Pat Deegan 7 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 8 | * 9 | * This program is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with this program. If not, see . 21 | */ 22 | 23 | #ifndef BOARDSPECIFIC_FUNCTIONS_H 24 | #define BOARDSPECIFIC_FUNCTIONS_H 1 25 | 26 | #include "board_includes.h" 27 | #include "board_defs.h" 28 | 29 | void board_gpio_init(void); 30 | 31 | 32 | 33 | // Initialize flash for DFU 34 | void board_flash_init(void); 35 | 36 | // Get size of flash 37 | uint32_t board_flash_size(void); 38 | 39 | uint32_t board_size_written(void); 40 | void board_size_written_clear(void); 41 | void board_flash_pages_erased_clear(void); 42 | 43 | // returns first written page address, or -1 if none 44 | int16_t board_first_written_page(void); 45 | uint32_t page_address_from_index(uint16_t page_index); 46 | uint32_t board_first_written_address(void); 47 | 48 | 49 | 50 | // Read from flash 51 | void board_flash_read (uint32_t addr, void* buffer, uint32_t len); 52 | 53 | // Write to flash, len is uf2's payload size (often 256 bytes) 54 | bool board_flash_write(uint32_t addr, void const* data, uint32_t len); 55 | 56 | // Flush/Sync flash contents 57 | void board_flash_flush(void); 58 | 59 | void board_reboot(void); 60 | 61 | 62 | 63 | 64 | 65 | #endif 66 | -------------------------------------------------------------------------------- /src/board_config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * board_config.h, part of the riffpga project 3 | * 4 | * General configuration for target PCB. This is in essence a fat 5 | * struct that is stored as a blob on the flash and used to tweak 6 | * the behaviour and experience. 7 | * 8 | * The default values for this fat blob come from one of the 9 | * config_defaults/*.h files, which is how you support a variety 10 | * of hardware/layouts. 11 | * 12 | * Created on: Dec 20, 2024 13 | * Author: Pat Deegan 14 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 15 | * 16 | * This program is free software: you can redistribute it and/or modify 17 | * it under the terms of the GNU General Public License as published by 18 | * the Free Software Foundation, either version 3 of the License, or 19 | * (at your option) any later version. 20 | * 21 | * This program is distributed in the hope that it will be useful, 22 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 23 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24 | * GNU General Public License for more details. 25 | * 26 | * You should have received a copy of the GNU General Public License 27 | * along with this program. If not, see . 28 | */ 29 | 30 | #ifndef SRC_BOARD_CONFIG_STRUCTS_H_ 31 | #define SRC_BOARD_CONFIG_STRUCTS_H_ 32 | 33 | 34 | 35 | 36 | #include "board_defs.h" 37 | 38 | 39 | #define BOARD_NAME_CHARS 23 40 | #define POSITION_SLOTS_NUM 4 41 | #define POSITION_SLOTS_ALLOWED 3 42 | 43 | // 4*4 = 16 bytes 44 | typedef struct RIF_PACKED_STRUCT uf2_magic_struct { 45 | 46 | uint32_t magic_start; // start1 47 | uint32_t magic_end; 48 | uint32_t family_id; 49 | uint32_t res1; // reserved 50 | 51 | } UF2_MagicInfo; 52 | 53 | 54 | // 5*4 = 20 bytes 55 | typedef struct RIF_PACKED_STRUCT storage_slot_struct { 56 | uint32_t slot_start_address[POSITION_SLOTS_NUM]; 57 | uint16_t res1; 58 | uint8_t selected_slot; 59 | uint8_t res2; 60 | } StorageSlotInfo; 61 | 62 | typedef struct RIF_PACKED_STRUCT version_info_struct { 63 | uint8_t conf_struct; // 4 bytes. 64 | uint8_t major; 65 | uint8_t minor; 66 | uint8_t patchlevel; 67 | } VersionInfo ; 68 | 69 | // 4*4 = 16 bytes 70 | typedef struct RIF_PACKED_STRUCT fpga_pwm_drive_struct { 71 | uint32_t freq_hz; 72 | uint32_t top; 73 | uint32_t div; 74 | uint16_t duty; 75 | uint8_t enabled; 76 | uint8_t pin; 77 | } FPGA_PWM; 78 | 79 | // 12 bytes 80 | #define UART_BREAKOUT_SEQUENCE_CHARS_MAX 3 81 | 82 | typedef struct RIF_PACKED_STRUCT uart_config_struct { 83 | uint32_t baud; 84 | uint8_t enabled; 85 | uint8_t uartnum; 86 | uint8_t pin_tx; 87 | uint8_t pin_rx; 88 | uint8_t breakout_sequence[UART_BREAKOUT_SEQUENCE_CHARS_MAX + 1]; 89 | } UART_Bridge; 90 | // 12 bytes 91 | typedef struct RIF_PACKED_STRUCT spi_config_struct { 92 | uint32_t rate; 93 | uint8_t cs_inverted; 94 | uint8_t phase; 95 | uint8_t polarity; 96 | uint8_t order; 97 | uint8_t pin_cs; 98 | uint8_t pin_sck; 99 | uint8_t pin_miso; 100 | uint8_t pin_mosi; 101 | } SPIConfig; 102 | 103 | // 16 bytes 104 | typedef struct RIF_PACKED_STRUCT fpga_cram_config { 105 | SPIConfig spi; // 12 106 | uint8_t res1; 107 | uint8_t pin_done; 108 | uint8_t pin_reset; 109 | uint8_t reset_inverted; 110 | } FPGACRAMConfig; 111 | 112 | typedef enum switchfunctionenum { 113 | SwitchFunctionNOTSET=0, 114 | SwitchFunctionReset=1, 115 | SwitchFunctionClocking=2, 116 | SwitchFunctionUser=3 117 | } SwitchFunction; 118 | 119 | typedef struct RIF_PACKED_STRUCT userswitchinfostruct { 120 | uint8_t function; 121 | uint8_t pin; 122 | uint8_t pull; 123 | uint8_t inverted; 124 | uint8_t irq_edge; 125 | uint8_t res1; 126 | uint16_t res2; 127 | } UserSwitch; 128 | 129 | #define BOARD_MAX_NUM_SWITCHES 4 130 | #define BOARD_MAX_NUM_INPUTS 16 131 | 132 | // 24 bytes 133 | typedef struct RIF_PACKED_STRUCT syssettingsstruct { 134 | uint32_t clock_freq_hz; 135 | uint8_t auto_reset_on_newer_version; 136 | uint8_t fpga_reset_external_trigger; 137 | uint8_t reserved; 138 | uint8_t num_inputs; 139 | uint8_t input_io[BOARD_MAX_NUM_INPUTS]; 140 | } SystemSettings; 141 | 142 | 143 | typedef struct RIF_PACKED_STRUCT board_config_struct { 144 | char board_name[BOARD_NAME_CHARS+1]; // 24 bytes 145 | 146 | VersionInfo version; // 4 bytes 147 | 148 | SystemSettings system; // 24 bytes 149 | 150 | UF2_MagicInfo bin_download; // 16 151 | StorageSlotInfo bin_position; // 20 152 | UF2_MagicInfo bs_marker; // 16 153 | StorageSlotInfo bs_marker_position; // 20 154 | FPGACRAMConfig fpga_cram; // 16 155 | FPGA_PWM clocking[2]; // 16*2 = 32 156 | 157 | UART_Bridge uart_bridge; // 12 158 | 159 | UserSwitch switches[BOARD_MAX_NUM_SWITCHES];// 8*4 = 32 160 | uint8_t user_app_data[8]; // 8, free to use by applications, won't be touched by low-level 161 | uint8_t reserved[64]; // 64 for future expansions, without impact to user payload below 162 | // ----- 163 | // 264, so 476 - 264 = 212 free bytes in payload 164 | } BoardConfig ; 165 | 166 | typedef const BoardConfig const * BoardConfigPtrConst; 167 | 168 | void boardconfig_init(void); 169 | void boardconfig_factoryreset(bool erase_bitstreams); 170 | 171 | BoardConfigPtrConst boardconfig_get(void); 172 | 173 | bool boardconfig_version_outdated(void); 174 | bool boardconfig_version_mismatch(void); 175 | 176 | 177 | 178 | 179 | 180 | uint32_t boardconfig_bin_startoffset(void); 181 | 182 | uint32_t boardconfig_bin_startoffset(void); 183 | uint32_t boardconfig_bs_marker_address(void); 184 | uint32_t boardconfig_bs_marker_address_for(uint8_t slotidx); 185 | 186 | void boardconfig_set_systemclock_hz(uint32_t v); 187 | void boardconfig_autoclock_enable(); 188 | void boardconfig_autoclock_disable(); 189 | void boardconfig_set_autoclock_hz(uint32_t v); 190 | 191 | 192 | 193 | uint8_t boardconfig_selected_bitstream_slot(void); 194 | void boardconfig_set_bitstream_slot(uint8_t s); 195 | 196 | void boardconfig_uartbridge_enable(); 197 | void boardconfig_uartbridge_disable(); 198 | void boardconfig_set_uartbridge_baudrate(uint32_t v); 199 | 200 | 201 | void boardconfig_write(void); 202 | void boardconfig_dump(void); 203 | 204 | 205 | 206 | FPGA_PWM * boardconfig_autoclocking(uint8_t idx); 207 | uint32_t boardconfig_autoclocking_achieved(uint8_t idx); 208 | 209 | 210 | #endif /* SRC_BOARD_CONFIG_H_ */ 211 | -------------------------------------------------------------------------------- /src/board_config_defaults.h: -------------------------------------------------------------------------------- 1 | /* 2 | * board_config_defaults.h, part of the riffpga project 3 | * 4 | * Created on: Jan 6, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #ifndef BOARD_CONFIG_DEFAULTS_H_ 23 | #define BOARD_CONFIG_DEFAULTS_H_ 24 | 25 | 26 | #ifdef TARGET_GENERIC 27 | #include "config_defaults/generic.h" 28 | #elif defined(TARGET_EFABLESS_EXPLAIN) 29 | #include "config_defaults/efab_explain.h" 30 | #elif defined(TARGET_PSYDMI) 31 | #include "config_defaults/psydmi.h" 32 | #else 33 | #error "provide a TARGET_* for config defaults" 34 | #endif 35 | 36 | 37 | #ifndef PICO_DEFAULT_LED_PIN 38 | #define PICO_DEFAULT_LED_PIN PIN_RP_LED 39 | #endif 40 | 41 | #ifndef BOARD_TUD_MAX_SPEED 42 | #define BOARD_TUD_MAX_SPEED 1 43 | #endif 44 | #endif /* BOARD_CONFIG_DEFAULTS_H_ */ 45 | -------------------------------------------------------------------------------- /src/board_defs.h: -------------------------------------------------------------------------------- 1 | /* 2 | * board_defs.h, part of the riffpga project 3 | * 4 | * Defines used internally. 5 | * 6 | * 7 | * Author: Pat Deegan 8 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 9 | * 10 | * 11 | * This program is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation, either version 3 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with this program. If not, see . 23 | */ 24 | 25 | 26 | #ifndef _BOARD_DEFINITIONS_H_ 27 | #define _BOARD_DEFINITIONS_H_ 28 | 29 | #include "board_includes.h" 30 | #include "board_config_defaults.h" 31 | #include "config_defaults/sys_version.h" 32 | 33 | #define RIF_PACKED_STRUCT __attribute__((packed)) 34 | 35 | #define TINYUF2_CONST 36 | 37 | #define BOARD_FLASH_SIZE (512 * 1024) 38 | #define BOARD_FLASH_ADDR_ZERO 0 39 | // Comes from #define hardware_flash/include/hardware/flash.h FLASH_SECTOR_SIZE (4*1024) 40 | 41 | #define FLASH_SPI_XFER_BLOCKSIZE 256 /* keep it short so we stay responsive */ 42 | 43 | 44 | #define UF2_MAGIC_START0 0x0A324655UL // "UF2\n" 45 | // bitstreams may have a meta-information block 46 | // it's differentiated using a start1 that's offset 47 | // from the BIN_UF2_MAGIC_START1 by this amount 48 | // You can see this in action in the 49 | // bin/bitstream_to_uf2.py packager 50 | #define BIN_UF2_METABLOCK_START1DELTA 0x42 51 | #define BIN_UF2_METABLOCK_PAYLOADHEADER "RFMETA" 52 | #define BIN_UF2_FACTORYRESET_START1DELTA 0xdead 53 | #define BIN_UF2_FACTORYRESET_PAYLOADHEADER "RFRSET" 54 | 55 | 56 | #define FLASH_RESET_DELAY_MS 2 57 | #define DEBUG_START_PROGRAM_DELAY_MS 500 58 | #define REBOOT_DELAY_MS 550 59 | 60 | 61 | #define UF2_VERSION BOARD_VERSION_STR 62 | #define UF2_PRODUCT_NAME "riffpga" 63 | #define UF2_BOARD_ID BOARD_NAME 64 | #define UF2_INDEX_URL "https://psychogenic.com/riffpga" 65 | #define UF2_VOLUME_LABEL DRIVE_VOLUME_LABEL 66 | //define TINYUF2_FAVICON_HEADER 67 | 68 | 69 | 70 | 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /src/board_includes.h: -------------------------------------------------------------------------------- 1 | /* 2 | * board_includes.h, part of the riffpga project 3 | * 4 | * Created on: Dec 19, 2024 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #ifndef SRC_BOARD_INCLUDES_H_ 23 | #define SRC_BOARD_INCLUDES_H_ 24 | 25 | 26 | #include "tusb.h" 27 | #include 28 | #include "pico/stdlib.h" 29 | #include "hardware/clocks.h" 30 | #include "hardware/spi.h" 31 | #include "pico/flash.h" 32 | #include "hardware/flash.h" 33 | #include "hardware/watchdog.h" 34 | 35 | // #include "pico/time.h" 36 | 37 | 38 | 39 | 40 | #endif /* SRC_BOARD_INCLUDES_H_ */ 41 | -------------------------------------------------------------------------------- /src/cdc_interface.c: -------------------------------------------------------------------------------- 1 | /* 2 | * cdc_interface.c, part of the riffpga project 3 | * Author: Pat Deegan 4 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | #include "cdc_interface.h" 20 | 21 | #include "tusb.h" 22 | #include "bsp/board_api.h" 23 | volatile bool wbusy = 0; 24 | 25 | static char digits_buffer[12]; 26 | 27 | int32_t cdc_read_char(void) { 28 | return tud_cdc_read_char(); 29 | } 30 | 31 | uint32_t cdc_available(void) { 32 | return tud_cdc_available(); 33 | } 34 | 35 | void cdc_write_char(char c) { 36 | if (!tud_cdc_ready()) { 37 | return; 38 | } 39 | 40 | tud_cdc_write_char(c); 41 | } 42 | 43 | void cdc_write(const char *s, uint32_t len) { 44 | if (!tud_cdc_ready()) { 45 | return; 46 | } 47 | while (wbusy) { 48 | } 49 | wbusy = 1; 50 | tud_cdc_write_flush(); // empty any pending 51 | tud_cdc_write(s, len); 52 | 53 | if (len >= CDC_THROTTLE_WRITES_OVER) { 54 | uint32_t tnowTarg = board_millis() + (len / CDC_THROTTLE_RATIO_MS); 55 | while (tnowTarg > board_millis()) { 56 | 57 | } 58 | 59 | } 60 | wbusy = 0; 61 | } 62 | 63 | void cdc_write_debug(const char *s, uint32_t len) { 64 | cdc_write(s, len); 65 | } 66 | 67 | bool cdc_write_busy() { 68 | return wbusy; 69 | 70 | } 71 | 72 | void cdc_write_u32(uint32_t v) { 73 | uint8_t nc = u32_to_hexstr(v, digits_buffer); 74 | cdc_write(digits_buffer, nc); 75 | } 76 | void cdc_write_u16(uint16_t v) { 77 | uint8_t nc = u16_to_hexstr(v, digits_buffer); 78 | cdc_write(digits_buffer, nc); 79 | 80 | } 81 | void cdc_write_u8(uint8_t v) { 82 | uint8_t nc = u8_to_hexstr(v, digits_buffer); 83 | cdc_write(digits_buffer, nc); 84 | 85 | } 86 | 87 | void cdc_write_u8_leadingzeros(uint8_t v) { 88 | uint8_t nc = u8_to_hexstr(v, digits_buffer); 89 | if (nc < 2) { 90 | cdc_write("0", 1); 91 | } 92 | cdc_write(digits_buffer, nc); 93 | 94 | } 95 | 96 | void cdc_write_u32_ln(uint32_t v) { 97 | 98 | uint8_t nc = u32_to_hexstr(v, digits_buffer); 99 | digits_buffer[nc] = '\r'; 100 | digits_buffer[nc + 1] = '\n'; 101 | cdc_write(digits_buffer, nc + 2); 102 | } 103 | void cdc_write_u16_ln(uint16_t v) { 104 | 105 | uint8_t nc = u16_to_hexstr(v, digits_buffer); 106 | digits_buffer[nc] = '\r'; 107 | digits_buffer[nc + 1] = '\n'; 108 | cdc_write(digits_buffer, nc + 2); 109 | } 110 | void cdc_write_u8_ln(uint8_t v) { 111 | uint8_t nc = u8_to_hexstr(v, digits_buffer); 112 | digits_buffer[nc] = '\r'; 113 | digits_buffer[nc + 1] = '\n'; 114 | cdc_write(digits_buffer, nc + 2); 115 | 116 | } 117 | 118 | static uint8_t u_to_decstr(uint32_t value, char *buffer) { 119 | const char decDigits[] = "0123456789"; 120 | char tmp_buffer[12] = { 0 }; 121 | if (value == 0) { 122 | buffer[0] = '0'; 123 | buffer[1] = '\0'; 124 | return 1; 125 | } 126 | uint8_t charidx = 0; 127 | while (value > 0) { 128 | tmp_buffer[charidx] = decDigits[value % 10]; 129 | value = value / 10; 130 | charidx++; 131 | } 132 | 133 | uint8_t i = 0; 134 | for (int8_t d = charidx - 1; d >= 0; d--) { 135 | buffer[i] = tmp_buffer[d]; 136 | i++; 137 | } 138 | buffer[i] = '\0'; 139 | return i; 140 | } 141 | 142 | static uint8_t u_to_hexstr(uint32_t value, char *buffer, size_t len) { 143 | const char hexDigits[] = "0123456789ABCDEF"; 144 | size_t i; 145 | bool started_digits = 0; 146 | uint8_t charidx = 0; 147 | for (i = 0; i < len; i++) { 148 | buffer[charidx] = 149 | hexDigits[(value >> (((len * 4) - 4) - (i * 4))) & 0xF]; 150 | 151 | if (!started_digits) { 152 | if (buffer[charidx] != '0') { 153 | started_digits = 1; 154 | } 155 | } 156 | if (started_digits) { 157 | charidx++; 158 | } 159 | } 160 | if (!charidx) { 161 | // never got anything but 0 162 | charidx++; 163 | } 164 | buffer[charidx] = '\0'; 165 | return charidx; 166 | } 167 | 168 | void cdc_write_dec_u32(uint32_t v) { 169 | uint8_t nc = u_to_decstr(v, digits_buffer); 170 | cdc_write(digits_buffer, nc); 171 | } 172 | void cdc_write_dec_u16(uint16_t v) { 173 | uint8_t nc = u_to_decstr(v, digits_buffer); 174 | cdc_write(digits_buffer, nc); 175 | } 176 | void cdc_write_dec_u8(uint8_t v) { 177 | uint8_t nc = u_to_decstr(v, digits_buffer); 178 | cdc_write(digits_buffer, nc); 179 | } 180 | 181 | void cdc_write_dec_u32_ln(uint32_t v) { 182 | uint8_t nc = u_to_decstr(v, digits_buffer); 183 | digits_buffer[nc] = '\r'; 184 | digits_buffer[nc + 1] = '\n'; 185 | cdc_write(digits_buffer, nc + 2); 186 | } 187 | void cdc_write_dec_u16_ln(uint16_t v) { 188 | 189 | uint8_t nc = u_to_decstr(v, digits_buffer); 190 | digits_buffer[nc] = '\r'; 191 | digits_buffer[nc + 1] = '\n'; 192 | cdc_write(digits_buffer, nc + 2); 193 | } 194 | void cdc_write_dec_u8_ln(uint8_t v) { 195 | 196 | uint8_t nc = u_to_decstr(v, digits_buffer); 197 | digits_buffer[nc] = '\r'; 198 | digits_buffer[nc + 1] = '\n'; 199 | cdc_write(digits_buffer, nc + 2); 200 | } 201 | 202 | uint8_t u32_to_hexstr(uint32_t value, char *buffer) { 203 | return u_to_hexstr(value, buffer, 8); 204 | } 205 | uint8_t u16_to_hexstr(uint16_t value, char *buffer) { 206 | return u_to_hexstr((uint32_t) value, buffer, 4); 207 | } 208 | uint8_t u8_to_hexstr(uint8_t value, char *buffer) { 209 | return u_to_hexstr((uint32_t) value, buffer, 2); 210 | } 211 | -------------------------------------------------------------------------------- /src/cdc_interface.h: -------------------------------------------------------------------------------- 1 | /* 2 | * cdc_interface.h, part of the riffpga project 3 | * 4 | * Functions related to the serial terminal device. 5 | * 6 | * Author: Pat Deegan 7 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 8 | * 9 | * This program is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with this program. If not, see . 21 | */ 22 | 23 | #ifndef CDC_INTERFACE_H 24 | #define CDC_INTERFACE_H 1 25 | 26 | #include "board_includes.h" 27 | 28 | #define CDCWRITESTRING(s) cdc_write(s, strlen(s)) 29 | #define CDCWRITECHAR(c) cdc_write_char(c); 30 | #define CDCWRITEFLUSH() tud_cdc_n_write_flush(0) 31 | 32 | 33 | #define CDC_THROTTLE_WRITES_OVER 10 34 | #define CDC_THROTTLE_RATIO_MS 6 35 | void cdc_write_char(char c); 36 | 37 | void cdc_write(const char * s, uint32_t len); 38 | int32_t cdc_read_char(void); 39 | uint32_t cdc_available(void); 40 | 41 | 42 | void cdc_write_u32(uint32_t v); 43 | void cdc_write_u16(uint16_t v); 44 | void cdc_write_u8(uint8_t v); 45 | void cdc_write_u8_leadingzeros(uint8_t v); 46 | 47 | void cdc_write_u32_ln(uint32_t v); 48 | void cdc_write_u16_ln(uint16_t v); 49 | void cdc_write_u8_ln(uint8_t v); 50 | 51 | 52 | void cdc_write_dec_u32(uint32_t v); 53 | void cdc_write_dec_u16(uint16_t v); 54 | void cdc_write_dec_u8(uint8_t v); 55 | 56 | void cdc_write_dec_u32_ln(uint32_t v); 57 | void cdc_write_dec_u16_ln(uint16_t v); 58 | void cdc_write_dec_u8_ln(uint8_t v); 59 | 60 | bool cdc_write_busy(); 61 | void cdc_write_debug(const char * s, uint32_t len); 62 | 63 | 64 | uint8_t u32_to_hexstr(uint32_t value, char* buffer); 65 | uint8_t u16_to_hexstr(uint16_t value, char* buffer); 66 | uint8_t u8_to_hexstr(uint8_t value, char* buffer); 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /src/clock_pwm.c: -------------------------------------------------------------------------------- 1 | /* 2 | * clock_pwm.c, part of the riffpga project 3 | * 4 | * Created on: Jan 6, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #include "hardware/pwm.h" 23 | #include "board_includes.h" 24 | #include "debug.h" 25 | #include "clock_pwm.h" 26 | 27 | // Returns: floor((16*F + offset) / div16) 28 | // Avoids overflow in the numerator that would occur if 29 | // 16*F + offset > 2**32 30 | // F + offset/16 > 2**28 = 268435456 (approximately, due to flooring) 31 | uint32_t get_slice_hz(uint32_t offset, uint32_t div16) { 32 | uint32_t source_hz = clock_get_hz(clk_sys); 33 | if (source_hz + offset / 16 > 268000000) { 34 | return (16 * (uint64_t) source_hz + offset) / div16; 35 | } else { 36 | return (16 * source_hz + offset) / div16; 37 | } 38 | } 39 | 40 | // Returns 16*F / denom, rounded. 41 | uint32_t get_slice_hz_round(uint32_t div16) { 42 | return get_slice_hz(div16 / 2, div16); 43 | } 44 | 45 | // Returns ceil(16*F / denom). 46 | uint32_t get_slice_hz_ceil(uint32_t div16) { 47 | return get_slice_hz(div16 - 1, div16); 48 | } 49 | 50 | void clock_once(FPGA_PWM *pwmconf) { 51 | DEBUG_LN("clock-once"); 52 | if (pwmconf->enabled) { 53 | DEBUG_LN("clock-once called but pwm enabled... disabling."); 54 | clock_pwm_disable(pwmconf); 55 | gpio_set_function(pwmconf->pin, GPIO_FUNC_NULL); 56 | gpio_set_dir(pwmconf->pin, GPIO_OUT); 57 | 58 | gpio_put(pwmconf->pin, 0); 59 | 60 | } 61 | gpio_put(pwmconf->pin, 1); 62 | sleep_us(50); 63 | gpio_put(pwmconf->pin, 0); 64 | sleep_us(50); 65 | 66 | } 67 | bool clock_pwm_enable(FPGA_PWM *pwmconf) { 68 | 69 | uint slice = pwm_gpio_to_slice_num(pwmconf->pin); 70 | uint8_t channel = pwm_gpio_to_channel(pwmconf->pin); 71 | // Select PWM function for given GPIO. 72 | gpio_set_function(pwmconf->pin, GPIO_FUNC_PWM); 73 | pwm_config defconf = pwm_get_default_config(); 74 | 75 | pwm_init(slice, &defconf, true); 76 | // pwm_set_enabled(slice, true); 77 | 78 | pwmconf->enabled = 1; 79 | DEBUG_LN("PWM enable"); 80 | return true; 81 | } 82 | void clock_pwm_disable(FPGA_PWM *pwmconf) { 83 | 84 | uint slice = pwm_gpio_to_slice_num(pwmconf->pin); 85 | // gpio_set_function(gpio, GPIO_FUNC_PWM); 86 | pwm_set_enabled(slice, false); 87 | pwmconf->enabled = 0; 88 | return; 89 | 90 | } 91 | 92 | bool clock_pwm_set_freq3(uint32_t freq_hz, FPGA_PWM *pwmconf) { 93 | uint slice_num = pwm_gpio_to_slice_num(pwmconf->pin); 94 | uint chan = pwm_gpio_to_channel(pwmconf->pin); 95 | uint32_t clock = clock_get_hz(clk_sys); 96 | uint32_t divider16 = clock / freq_hz / 4096 + (clock % (freq_hz * 4096) != 0); 97 | 98 | 99 | 100 | if (divider16 < 16) { 101 | divider16 = 16; 102 | } 103 | 104 | 105 | if (divider16 >= 256 * 16) { 106 | 107 | DEBUG_LN("freq too small"); 108 | return false; 109 | } 110 | 111 | uint32_t wrap = clock * 16 / divider16 / freq_hz - 1; 112 | 113 | 114 | 115 | pwmconf->top = wrap; 116 | pwmconf->div = divider16; 117 | pwmconf->freq_hz = freq_hz; 118 | 119 | 120 | 121 | pwm_set_clkdiv_int_frac(slice_num, pwmconf->div / 16, pwmconf->div & 0xF); 122 | pwm_set_wrap(slice_num, pwmconf->top); 123 | pwm_set_chan_level(slice_num, chan, pwmconf->top / 2); 124 | pwm_set_enabled(slice_num, true); 125 | 126 | 127 | #ifdef DEBUG_OUTPUT_ENABLED 128 | DEBUG("PWM configured with:\r\n\t top:"); 129 | cdc_write_dec_u32_ln(pwmconf->top); 130 | DEBUG("\t div:"); 131 | cdc_write_dec_u32_ln(pwmconf->div); 132 | DEBUG("\t level:"); 133 | cdc_write_dec_u32_ln(wrap/2); 134 | DEBUG("\t freq:"); 135 | cdc_write_dec_u32(pwmconf->freq_hz); 136 | DEBUG(" at sysclk "); 137 | cdc_write_dec_u32_ln(clock); 138 | CDCWRITEFLUSH(); 139 | 140 | #endif 141 | if (wrap) { 142 | return true; 143 | } 144 | return false; 145 | } 146 | 147 | 148 | #define PWM_TOP_MAX 65534 149 | bool clock_pwm_set_freq(uint32_t freq_hz, FPGA_PWM *pwmconf) { 150 | uint slice_num = pwm_gpio_to_slice_num(pwmconf->pin); 151 | uint chan = pwm_gpio_to_channel(pwmconf->pin); 152 | 153 | uint32_t source_hz = clock_get_hz(clk_sys); 154 | uint32_t divider16; 155 | uint32_t wrap = 0; 156 | if ((source_hz + freq_hz / 2) / freq_hz < PWM_TOP_MAX) { 157 | // If possible (based on the formula for TOP below), use a DIV of 1. 158 | // This also prevents overflow in the DIV calculation. 159 | divider16 = 16; 160 | 161 | // Same as get_slice_hz_round() below but canceling the 16s 162 | // to avoid overflow for high freq. 163 | wrap = (source_hz + freq_hz / 2) / freq_hz - 1; 164 | } else { 165 | // Otherwise, choose the smallest possible DIV for maximum 166 | // duty cycle resolution. 167 | // Constraint: 16*F/(div16*freq) < TOP_MAX 168 | // So: 169 | divider16 = get_slice_hz_ceil(PWM_TOP_MAX * freq_hz); 170 | 171 | // Set TOP as accurately as possible using rounding. 172 | wrap = get_slice_hz_round(divider16 * freq_hz) - 1; 173 | } 174 | 175 | 176 | if (divider16 < 16) { 177 | divider16 = 16; 178 | } 179 | 180 | 181 | if (divider16 >= 256 * 16) { 182 | 183 | DEBUG_LN("freq too small"); 184 | return false; 185 | } 186 | 187 | if (wrap < 2) { 188 | if (divider16 == 16) { 189 | CDCWRITESTRING("Frequency too high -- setting to max"); 190 | wrap = 2; 191 | } 192 | } 193 | 194 | 195 | pwmconf->top = wrap; 196 | pwmconf->div = divider16; 197 | pwmconf->freq_hz = freq_hz; 198 | 199 | 200 | 201 | pwm_set_clkdiv_int_frac(slice_num, pwmconf->div / 16, pwmconf->div & 0xF); 202 | pwm_set_wrap(slice_num, pwmconf->top); 203 | pwm_set_chan_level(slice_num, chan, pwmconf->top / 2); 204 | pwm_set_enabled(slice_num, true); 205 | 206 | 207 | #ifdef DEBUG_OUTPUT_ENABLED 208 | DEBUG("PWM configured with:\r\n\t top:"); 209 | cdc_write_dec_u32_ln(pwmconf->top); 210 | DEBUG("\t div:"); 211 | cdc_write_dec_u32_ln(pwmconf->div); 212 | DEBUG("\t level:"); 213 | cdc_write_dec_u32_ln(wrap/2); 214 | DEBUG("\t freq:"); 215 | cdc_write_dec_u32(pwmconf->freq_hz); 216 | DEBUG(" at sysclk "); 217 | cdc_write_dec_u32_ln(source_hz); 218 | CDCWRITEFLUSH(); 219 | 220 | #endif 221 | 222 | return true; 223 | } 224 | 225 | 226 | float clock_pwm_freq_achieved(FPGA_PWM * pwmconf) { 227 | float fact = pwmconf->top * ((pwmconf->div/16.00f) + ((1.0 * (pwmconf->div & 0xF))/16.00)); 228 | return clock_get_hz(clk_sys)/fact; 229 | 230 | } 231 | 232 | 233 | bool clock_pwm_set_freq2(uint32_t freq_hz, FPGA_PWM *pwmconf) { 234 | // Set the frequency, making "top" as large as possible for maximum resolution. 235 | // Maximum "top" is set at 65534 to be able to achieve 100% duty with 65535. 236 | #define TOP_MAX 65534 237 | 238 | uint slice = pwm_gpio_to_slice_num(pwmconf->pin); 239 | uint8_t channel = pwm_gpio_to_channel(pwmconf->pin); 240 | 241 | uint32_t source_hz = clock_get_hz(clk_sys); 242 | uint32_t div16; 243 | uint32_t top; 244 | 245 | if ((source_hz + freq_hz / 2) / freq_hz < TOP_MAX) { 246 | // If possible (based on the formula for TOP below), use a DIV of 1. 247 | // This also prevents overflow in the DIV calculation. 248 | div16 = 16; 249 | 250 | // Same as get_slice_hz_round() below but canceling the 16s 251 | // to avoid overflow for high freq. 252 | top = (source_hz + freq_hz / 2) / freq_hz - 1; 253 | } else { 254 | // Otherwise, choose the smallest possible DIV for maximum 255 | // duty cycle resolution. 256 | // Constraint: 16*F/(div16*freq) < TOP_MAX 257 | // So: 258 | div16 = get_slice_hz_ceil(TOP_MAX * freq_hz); 259 | 260 | // Set TOP as accurately as possible using rounding. 261 | top = get_slice_hz_round(div16 * freq_hz) - 1; 262 | } 263 | 264 | if (div16 < 16) { 265 | DEBUG_LN("freq too large"); 266 | return false; 267 | } else if (div16 >= 256 * 16) { 268 | 269 | DEBUG_LN("freq too small"); 270 | return false; 271 | } 272 | 273 | pwmconf->div = div16; 274 | pwmconf->top = top; 275 | pwmconf->enabled = 1; 276 | pwmconf->freq_hz = freq_hz; 277 | uint16_t duty_u16 = pwmconf->duty; 278 | uint32_t cc = (duty_u16 * (top + 1) + 0xffff / 2) / 0xffff; 279 | pwm_config config = pwm_get_default_config(); 280 | 281 | 282 | 283 | pwm_set_wrap(slice, top); 284 | pwm_set_chan_level(slice, channel, cc); 285 | pwm_set_enabled(slice, true); 286 | 287 | #ifdef DEBUG_OUTPUT_ENABLED 288 | DEBUG("PWM configured with:\r\n\t top:"); 289 | cdc_write_dec_u32_ln(pwmconf->top); 290 | DEBUG("\t div:"); 291 | cdc_write_dec_u32_ln(pwmconf->div); 292 | DEBUG("\t level:"); 293 | cdc_write_dec_u32_ln(cc); 294 | DEBUG("\t div:"); 295 | cdc_write_dec_u32_ln(pwmconf->div); 296 | DEBUG("\t freq:"); 297 | cdc_write_dec_u32(pwmconf->freq_hz); 298 | DEBUG(" at sysclk "); 299 | cdc_write_dec_u32_ln(source_hz); 300 | 301 | #endif 302 | 303 | 304 | return true; 305 | 306 | } 307 | -------------------------------------------------------------------------------- /src/clock_pwm.h: -------------------------------------------------------------------------------- 1 | /* 2 | * clock_pwm.h, part of the riffpga project 3 | * 4 | * "Auto-clocking" and manual clocking of FPGA 5 | * 6 | * Created on: Jan 6, 2025 7 | * Author: Pat Deegan 8 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 9 | * 10 | * This program is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | */ 23 | 24 | #ifndef CLOCK_PWM_H_ 25 | #define CLOCK_PWM_H_ 26 | 27 | 28 | #include "board_config.h" 29 | 30 | void clock_once(FPGA_PWM * pwmconf); 31 | 32 | bool clock_pwm_enable(FPGA_PWM * pwmconf); 33 | void clock_pwm_disable(FPGA_PWM * pwmconf); 34 | bool clock_pwm_set_freq(uint32_t freq_hz, FPGA_PWM * pwmconf); 35 | float clock_pwm_freq_achieved(FPGA_PWM * pwmconf); 36 | 37 | 38 | 39 | 40 | #endif /* CLOCK_PWM_H_ */ 41 | -------------------------------------------------------------------------------- /src/compile_date.h: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) Henry Gabryjelski 5 | * Copyright (c) Ha Thach for Adafruit Industries 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | */ 25 | 26 | #ifndef COMPILE_DATE_H 27 | #define COMPILE_DATE_H 28 | 29 | // Help enable build to be deterministic. 30 | // Allows Ghostfat to generate 100% reproducible images across compilations. 31 | // Reproducible builds are also important for other reasons. 32 | // See generally, https://reproducible-builds.org/ 33 | #ifndef COMPILE_DATE 34 | #define COMPILE_DATE __DATE__ 35 | #endif 36 | #ifndef COMPILE_TIME 37 | #define COMPILE_TIME __TIME__ 38 | #endif 39 | 40 | #define COMPILE_YEAR_INT ((( \ 41 | (COMPILE_DATE [ 7u] - '0') * 10u + \ 42 | (COMPILE_DATE [ 8u] - '0')) * 10u + \ 43 | (COMPILE_DATE [ 9u] - '0')) * 10u + \ 44 | (COMPILE_DATE [10u] - '0')) 45 | 46 | #define COMPILE_MONTH_INT ( \ 47 | (COMPILE_DATE [2u] == 'n' && COMPILE_DATE [1u] == 'a') ? 1u /*Jan*/ \ 48 | : (COMPILE_DATE [2u] == 'b' ) ? 2u /*Feb*/ \ 49 | : (COMPILE_DATE [2u] == 'r' && COMPILE_DATE [1u] == 'a') ? 3u /*Mar*/ \ 50 | : (COMPILE_DATE [2u] == 'r' ) ? 4u /*Apr*/ \ 51 | : (COMPILE_DATE [2u] == 'y' ) ? 5u /*May*/ \ 52 | : (COMPILE_DATE [2u] == 'n' ) ? 6u /*Jun*/ \ 53 | : (COMPILE_DATE [2u] == 'l' ) ? 7u /*Jul*/ \ 54 | : (COMPILE_DATE [2u] == 'g' ) ? 8u /*Aug*/ \ 55 | : (COMPILE_DATE [2u] == 'p' ) ? 9u /*Sep*/ \ 56 | : (COMPILE_DATE [2u] == 't' ) ? 10u /*Oct*/ \ 57 | : (COMPILE_DATE [2u] == 'v' ) ? 11u /*Nov*/ \ 58 | : 12u /*Dec*/ ) 59 | 60 | #define COMPILE_DAY_INT ( \ 61 | (COMPILE_DATE [4u] == ' ' ? 0 : COMPILE_DATE [4u] - '0') * 10u + \ 62 | (COMPILE_DATE [5u] - '0') \ 63 | ) 64 | 65 | // __TIME__ expands to an eight-character string constant 66 | // "23:59:01", or (if cannot determine time) "??:??:??" 67 | #define COMPILE_HOUR_INT ( \ 68 | (COMPILE_TIME [0u] == '?' ? 0 : COMPILE_TIME [0u] - '0') * 10u \ 69 | + (COMPILE_TIME [1u] == '?' ? 0 : COMPILE_TIME [1u] - '0') ) 70 | 71 | #define COMPILE_MINUTE_INT ( \ 72 | (COMPILE_TIME [3u] == '?' ? 0 : COMPILE_TIME [3u] - '0') * 10u \ 73 | + (COMPILE_TIME [4u] == '?' ? 0 : COMPILE_TIME [4u] - '0') ) 74 | 75 | #define COMPILE_SECONDS_INT ( \ 76 | (COMPILE_TIME [6u] == '?' ? 0 : COMPILE_TIME [6u] - '0') * 10u \ 77 | + (COMPILE_TIME [7u] == '?' ? 0 : COMPILE_TIME [7u] - '0') ) 78 | 79 | 80 | #define COMPILE_DOS_DATE ( \ 81 | ((COMPILE_YEAR_INT - 1980u) << 9u) | \ 82 | ( COMPILE_MONTH_INT << 5u) | \ 83 | ( COMPILE_DAY_INT << 0u) ) 84 | 85 | #define COMPILE_DOS_TIME ( \ 86 | ( COMPILE_HOUR_INT << 11u) | \ 87 | ( COMPILE_MINUTE_INT << 5u) | \ 88 | ( COMPILE_SECONDS_INT << 0u) ) 89 | 90 | #endif // COMPILE_DATE_H 91 | -------------------------------------------------------------------------------- /src/config_defaults/efab_explain.h: -------------------------------------------------------------------------------- 1 | /* 2 | * efab_explain.h, part of the riffpga project 3 | * 4 | * Created on: Jan 6, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #ifndef CONFIG_DEFAULTS_EFAB_EXPLAIN_H_ 23 | #define CONFIG_DEFAULTS_EFAB_EXPLAIN_H_ 24 | 25 | #define BOARD_NAME "ChipIgnite Explain" 26 | #define DRIVE_VOLUME_LABEL "FPGAUPDATE" 27 | 28 | // should default to 120MHz for safety with 29 | // tinyUSB bitbanging 30 | #define BOARD_SYSTEM_CLOCK_FREQ_HZ 120000000ULL 31 | 32 | 33 | #define PIN_RP_LED 25 34 | 35 | 36 | #define PIN_FPGA_RESET 15 37 | #define PIN_FPGA_SPI_MISO 12 38 | #define PIN_FPGA_SPI_CS 13 39 | #define PIN_FPGA_SPI_SCK 14 40 | #define PIN_FPGA_SPI_MOSI 11 41 | #define PIN_FPGA_PROG_DONE 16 42 | 43 | #define PIN_AUTOCLOCK1 10 44 | #define PIN_AUTOCLOCK2 18 /* NC */ 45 | 46 | #define PIN_UART_BRIDGE_TX 8 47 | #define PIN_UART_BRIDGE_RX 9 48 | 49 | /* 50 | * FPGA_RESET_EXTERNALLY_TRIGGERED 51 | * Set to 1 if FPGA may be reset externally 52 | * and this may be monitored on PIN_FPGA_RESET 53 | */ 54 | #define FPGA_RESET_EXTERNALLY_TRIGGERED 1 55 | #define FPGA_PROG_DONE_LEVEL 1 /* 1==HIGH means program success */ 56 | 57 | 58 | #define AUTOCLOCK1_DEFAULT_FREQ 10000000UL 59 | #define AUTOCLOCK1_DEFAULT_ENABLE 1 60 | 61 | #define AUTOCLOCK2_DEFAULT_FREQ 1000000UL 62 | #define AUTOCLOCK2_DEFAULT_ENABLE 0 63 | 64 | 65 | 66 | #define FPGA_RESET_INVERTED 1 67 | #define FLASH_SPI_CS_INVERTED 1 68 | #define FLASH_SPI_POLARITY 0 69 | #define FLASH_SPI_PHASE 0 70 | #define FLASH_SPI_BAUDRATE 4000000UL 71 | 72 | #define BIN_UF2_MAGIC_START1 0x951C0634UL // Randomly selected 73 | #define BIN_UF2_MAGIC_END 0x1C73C401UL // Ditto 74 | #define BIN_UF2_FAMILY_ID 0xefab1e55UL 75 | 76 | 77 | 78 | // size marker wrapped in UF2 79 | #define BITSTREAM_UF2_MAGIC_START1 0x951C0512UL 80 | #define BITSTREAM_UF2_MAGIC_END 0xE4D951C0UL 81 | #define BITSTREAM_UF2_FAMILY_ID 0x951C0FA3UL 82 | 83 | 84 | // board config wrapped in UF2 85 | #define BOARDCONF_UF2_MAGIC_START1 0x951C057EUL 86 | #define BOARDCONF_UF2_MAGIC_END 0xC401061EUL 87 | #define BOARDCONF_UF2_FAMILY_ID 0xB0A2DC0FUL 88 | 89 | 90 | 91 | 92 | #define UART_BRIDGE_BAUDRATE 115200 93 | #define UART_BRIDGE_ENABLED 0 94 | #define UART_BRIDGE_DEVICEIDX 1 95 | #define UART_BRIDGE_PIN_TX 8 96 | #define UART_BRIDGE_PIN_RX 9 97 | 98 | // ESC ESC ESC 99 | #define UART_BRIDGE_ESCAPE_SEQUENCE0 0x1b 100 | #define UART_BRIDGE_ESCAPE_SEQUENCE1 0x1b 101 | #define UART_BRIDGE_ESCAPE_SEQUENCE2 0x1b 102 | 103 | 104 | 105 | #define USER_SWITCH_IDX_RESET 0 106 | #define USER_SWITCH_IDX_CLOCKING 1 107 | #define USER_SWITCH_IDX_USR1 2 108 | #define USER_SWITCH_IDX_USR2 3 109 | 110 | #define USER_SWITCH_RESET_PIN 15 111 | #define USER_SWITCH_RESET_FUNCTION 0 /* 1 == Reset, 0 == Not set */ 112 | #define USER_SWITCH_RESET_INVERT 1 113 | #define USER_SWITCH_RESET_PULL 0 114 | #define USER_SWITCH_RESET_EDGE GPIO_IRQ_EDGE_FALL 115 | 116 | 117 | #define USER_SWITCH_CLOCK_PIN 24 118 | #define USER_SWITCH_CLOCK_FUNCTION 2 /* 2 == Clocking */ 119 | #define USER_SWITCH_CLOCK_INVERT 0 120 | #define USER_SWITCH_CLOCK_PULL 0 121 | #define USER_SWITCH_CLOCK_EDGE GPIO_IRQ_EDGE_RISE 122 | 123 | 124 | #define USER_SWITCH_USR1_PIN 0 /* DNE */ 125 | #define USER_SWITCH_USR1_FUNCTION 0 /* 0 == NOTSET */ 126 | #define USER_SWITCH_USR1_INVERT 0 127 | #define USER_SWITCH_USR1_PULL 0 128 | #define USER_SWITCH_USR1_EDGE 0 129 | #define USER_SWITCH_USR2_PIN 0 /* DNE */ 130 | #define USER_SWITCH_USR2_FUNCTION 0 /* 0 == NOTSET */ 131 | #define USER_SWITCH_USR2_INVERT 0 132 | #define USER_SWITCH_USR2_PULL 0 133 | #define USER_SWITCH_USR2_EDGE 0 134 | 135 | #define SYSTEM_NEWER_VERSION_AUTO_RESET 1 136 | #define SYSTEM_INPUTS_NUM 8 137 | #define SYSTEM_INPUTS_IO_LIST 0,1,2,3,4,5,6,7 138 | 139 | 140 | 141 | /* 142 | * Flash storage: 143 | * We leave the first 0.5M for RP code. 144 | * After that we have: 145 | * 2 pages/slot for markers (much more than we need but the space is reserved) 146 | * then a 512k block of flash space/slot 147 | */ 148 | #define BITSTREAM_MANAGEMENT_START (512 * 1024) 149 | #define MARKER_RESERVED_SPACE (4*2*1024) 150 | #define MARKER_SLOT_ADDRESS(slotidx) (BITSTREAM_MANAGEMENT_START + ( (slotidx) * MARKER_RESERVED_SPACE)) 151 | #define BOARD_CONFIG_FLASHADDRESS MARKER_SLOT_ADDRESS(0) - MARKER_RESERVED_SPACE 152 | 153 | // bitstream storage base address -- assumes 4 slots available, 0-3 154 | #define FLASH_TARGET_OFFSETSTART MARKER_SLOT_ADDRESS(4) 155 | #define BITSTREAM_SLOT_RESERVED_SPACE (512 * 1024) 156 | #define FLASH_STORAGE_STARTADDRESS(slotidx) (FLASH_TARGET_OFFSETSTART + ( (slotidx) * BITSTREAM_SLOT_RESERVED_SPACE)) 157 | 158 | 159 | 160 | 161 | #endif /* CONFIG_DEFAULTS_EFAB_EXPLAIN_H_ */ 162 | -------------------------------------------------------------------------------- /src/config_defaults/generic.h: -------------------------------------------------------------------------------- 1 | /* 2 | * board_config_defaults.h, part of the riffpga project 3 | * 4 | * *One* of the config_defaults/*.h files winds up being 5 | * included in the project and setting up the defaults for 6 | * the target board. From that point on, these are in the 7 | * BoardConfig struct that stored in flash and may be edited 8 | * using commands etc. 9 | * 10 | * The contents here apply 11 | * * on fresh chips 12 | * * on factoryreset 13 | * * on burning firmware with a higher revision (subject to 14 | * SYSTEM_NEWER_VERSION_AUTO_RESET) 15 | * 16 | * All the pin numbers within are RP2 GPIO numbers, so e.g. setting 17 | * #define PIN_WHATEVER 10 18 | * would mean GPIO10 on an RP2040 (physical pin 13 on the QFN) 19 | * 20 | * Created on: Dec 20, 2024 21 | * Author: Pat Deegan 22 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 23 | * 24 | * This program is free software: you can redistribute it and/or modify 25 | * it under the terms of the GNU General Public License as published by 26 | * the Free Software Foundation, either version 3 of the License, or 27 | * (at your option) any later version. 28 | * 29 | * This program is distributed in the hope that it will be useful, 30 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 31 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 32 | * GNU General Public License for more details. 33 | * 34 | * You should have received a copy of the GNU General Public License 35 | * along with this program. If not, see . 36 | */ 37 | 38 | #ifndef SRC_BOARD_CONFIG_DEFAULTS_GENERIC_H_ 39 | #define SRC_BOARD_CONFIG_DEFAULTS_GENERIC_H_ 40 | 41 | /* 42 | * The name of this board 43 | */ 44 | #define BOARD_NAME "rif-generic" 45 | 46 | /* 47 | * DRIVE_VOLUME_LABEL: what gets mounted as 48 | * the drive name 49 | */ 50 | #define DRIVE_VOLUME_LABEL "FPGAUPDATE" 51 | 52 | 53 | // Remember: RP2 pins -- as GPIO# 54 | 55 | /* 56 | * Some LED connected to the RP--this is useful to 57 | * provide info about state program slot/unprogrammed. 58 | */ 59 | #define PIN_RP_LED 25 60 | 61 | /* 62 | * Something we use to reset the FPGA. 63 | * Should have pull-up/pull-down as appropriate to 64 | * allow operation by default 65 | */ 66 | #define PIN_FPGA_RESET 4 67 | /* 68 | * Assuming Lattice like CRAM that's basically 69 | * just SPI, here. 70 | * SPI needs to be a valid set of SPI lines 71 | * for the RP2. See 1.4.3. GPIO Functions 72 | * in https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf 73 | * 74 | * The CS pin, in this case, or whatever the mechanism is 75 | * to tell the FPGA that it is a *slave* should have pull-up/pull-down 76 | * as appropriate to keep it in this mode by default. 77 | */ 78 | #define PIN_FPGA_SPI_MISO 0 79 | #define PIN_FPGA_SPI_CS 1 80 | #define PIN_FPGA_SPI_SCK 2 81 | #define PIN_FPGA_SPI_MOSI 3 82 | // CDONE-type pin, see FPGA_PROG_DONE_LEVEL below 83 | #define PIN_FPGA_PROG_DONE 9 84 | 85 | 86 | 87 | // only autoclock1 is currently used 88 | #define PIN_AUTOCLOCK1 5 /* the RP2 pin we use for clocking the FPGA */ 89 | #define PIN_AUTOCLOCK2 6 /* reserved for future, ignored for now */ 90 | 91 | 92 | // freq is in Hz 93 | #define AUTOCLOCK1_DEFAULT_FREQ 12000000UL 94 | #define AUTOCLOCK1_DEFAULT_ENABLE 1 95 | 96 | #define AUTOCLOCK2_DEFAULT_FREQ 1000000UL 97 | #define AUTOCLOCK2_DEFAULT_ENABLE 0 98 | 99 | // RP2 clock frequency 100 | // TinyUSB wants it to be 120M but it's 101 | // worked fine from 100 to 126 so far... still 102 | #define BOARD_SYSTEM_CLOCK_FREQ_HZ 120000000UL 103 | 104 | 105 | 106 | /* 107 | * FPGA_RESET_EXTERNALLY_TRIGGERED 108 | * Set to 1 if FPGA may be reset externally 109 | * and this may be monitored on PIN_FPGA_RESET 110 | */ 111 | #define FPGA_RESET_EXTERNALLY_TRIGGERED 0 112 | #define FPGA_RESET_INVERTED 1 113 | 114 | #define FLASH_SPI_CS_INVERTED 1 /* 1==CS LOW means selected */ 115 | #define FLASH_SPI_POLARITY 0 116 | #define FLASH_SPI_PHASE 0 117 | #define FLASH_SPI_BAUDRATE 4000000UL 118 | 119 | 120 | /* 121 | * If you have a "Done" pin hooked-up, 122 | * FPGA_PROG_DONE_LEVEL lets the system know what means "programmed". 123 | * If you don't, and want to ignore all that, comment out the #define 124 | * and the code will just assume the FPGA's been programmed as 125 | * appropriate. 126 | */ 127 | #define FPGA_PROG_DONE_LEVEL 1 /* 1==HIGH on PIN_FPGA_PROG_DONE means program success */ 128 | 129 | /* 130 | * UART bridge defaults. 131 | * The UART bridge, when enabled, will shuttle all the 132 | * data coming in over to the FPGA through UART_BRIDGE_PIN_TX. 133 | * Anything coming to it over UART_BRIDGE_PIN_RX gets sent back 134 | * over the USB serial connection. 135 | * The way to get out of this mode: see UART_BRIDGE_ESCAPE_SEQUENCE* 136 | */ 137 | #define UART_BRIDGE_BAUDRATE 115200 138 | #define UART_BRIDGE_ENABLED 0 /* probably best to default to 0==off */ 139 | 140 | // The TX/RX pin, as seen from the RP2's point of view 141 | #define UART_BRIDGE_PIN_TX 8 /* an RP2 pin you can use as a UART TX */ 142 | #define UART_BRIDGE_PIN_RX 9 /* an RP2 pin you can use as a UART RX */ 143 | 144 | // UART_BRIDGE_DEVICEIDX -- the RP uart0/uart1 index we are 145 | // using. Set the index according to the selected pins 146 | #define UART_BRIDGE_DEVICEIDX 1 147 | 148 | /* 149 | * UART_BRIDGE_ESCAPE_* 150 | * We need a way to break out of the uart 151 | * bridge. Getting a sequence of these chars 152 | * should do it. Choose something you are 153 | * unlikely to transmit through the bridge. 154 | */ 155 | #define UART_BRIDGE_ESCAPE_SEQUENCE0 0x1b 156 | #define UART_BRIDGE_ESCAPE_SEQUENCE1 0x1b 157 | #define UART_BRIDGE_ESCAPE_SEQUENCE2 0x1b 158 | 159 | 160 | 161 | 162 | /* 163 | * UF2 magic numbers and family -- let's us 164 | * ignore irrelevant data, can be any uint32_t 165 | * 166 | * BIN_UF2_* are for the bitstream binary file, 167 | * the thing that gets sent over to configure the 168 | * FPGA. 169 | */ 170 | #define BIN_UF2_MAGIC_START1 0x951C0634UL // Randomly selected 171 | #define BIN_UF2_MAGIC_END 0x1C73C401UL // Ditto 172 | #define BIN_UF2_FAMILY_ID 0x6e2e91c1UL 173 | 174 | 175 | 176 | 177 | /* 178 | * We have 4 slots for switches on the PCB, connected 179 | * to the RP2 (other than the BOOT switch). 180 | * 181 | * Calling them RESET, CLOCKING, USR1/2 here for 182 | * convenience, but you can set the _FUNCTION to anything. 183 | * 184 | * If there's a clock in there (..._FUCTION == 2), then 185 | * it may be held down on boot to force "manual clocking" 186 | * mode, during which each click will clock once. 187 | * 188 | */ 189 | #define USER_SWITCH_IDX_RESET 0 190 | #define USER_SWITCH_IDX_CLOCKING 1 191 | #define USER_SWITCH_IDX_USR1 2 192 | #define USER_SWITCH_IDX_USR2 3 193 | 194 | #define USER_SWITCH_RESET_PIN 15 195 | #define USER_SWITCH_RESET_FUNCTION 1 /* 1 == Reset */ 196 | #define USER_SWITCH_RESET_INVERT 1 197 | #define USER_SWITCH_RESET_PULL 0 198 | #define USER_SWITCH_RESET_EDGE GPIO_IRQ_EDGE_FALL 199 | 200 | 201 | #define USER_SWITCH_CLOCK_PIN 24 202 | #define USER_SWITCH_CLOCK_FUNCTION 2 /* 2 == Clocking */ 203 | #define USER_SWITCH_CLOCK_INVERT 0 204 | #define USER_SWITCH_CLOCK_PULL 0 205 | #define USER_SWITCH_CLOCK_EDGE GPIO_IRQ_EDGE_RISE 206 | 207 | 208 | #define USER_SWITCH_USR1_PIN 0 /* DNE */ 209 | #define USER_SWITCH_USR1_FUNCTION 0 /* 0 == NOTSET */ 210 | #define USER_SWITCH_USR1_INVERT 0 211 | #define USER_SWITCH_USR1_PULL 0 212 | #define USER_SWITCH_USR1_EDGE 0 213 | #define USER_SWITCH_USR2_PIN 0 /* DNE */ 214 | #define USER_SWITCH_USR2_FUNCTION 0 /* 0 == NOTSET */ 215 | #define USER_SWITCH_USR2_INVERT 0 216 | #define USER_SWITCH_USR2_PULL 0 217 | #define USER_SWITCH_USR2_EDGE 0 218 | 219 | 220 | /* 221 | * SYSTEM_NEWER_VERSION_AUTO_RESET 222 | * If this is 1, then burning a version of this system 223 | * with a greater major/minor/patch will automatically 224 | * "factory reset" the config data, using whatever the 225 | * defaults are (as specified here) 226 | */ 227 | #define SYSTEM_NEWER_VERSION_AUTO_RESET 1 228 | 229 | 230 | /* 231 | * SYSTEM_INPUTS_NUM/SYSTEM_INPUTS_IO_LIST 232 | * RP2 GPIO that's treated as a list of inputs which may be 233 | * read/acted upon as a single value 234 | */ 235 | #define SYSTEM_INPUTS_NUM 4 236 | #define SYSTEM_INPUTS_IO_LIST 16,17,18,19 237 | 238 | 239 | 240 | 241 | /* 242 | * The remaining UF2 magiks are used internally, you 243 | * needn't change them, but may if you please. 244 | */ 245 | // size marker wrapped in UF2 246 | #define BITSTREAM_UF2_MAGIC_START1 0x951C0512UL 247 | #define BITSTREAM_UF2_MAGIC_END 0xE4D951C0UL 248 | #define BITSTREAM_UF2_FAMILY_ID 0x951C0FA3UL 249 | 250 | 251 | 252 | // board config wrapped in UF2 253 | #define BOARDCONF_UF2_MAGIC_START1 0x951C057EUL 254 | #define BOARDCONF_UF2_MAGIC_END 0xC401061EUL 255 | #define BOARDCONF_UF2_FAMILY_ID 0xB0A2DC0FUL 256 | 257 | 258 | 259 | /* 260 | * Flash storage: 261 | * We leave the first 0.5M for RP code. 262 | * After that we have: 263 | * 2 pages/slot for markers (much more than we need but the space is reserved) 264 | * then a 512k block of flash space/slot 265 | * 266 | * Mess with this if you dare, huh. 267 | */ 268 | #define BITSTREAM_MANAGEMENT_START (512 * 1024) 269 | #define MARKER_RESERVED_SPACE (4*2*1024) 270 | #define MARKER_SLOT_ADDRESS(slotidx) (BITSTREAM_MANAGEMENT_START + ( (slotidx) * MARKER_RESERVED_SPACE)) 271 | #define BOARD_CONFIG_FLASHADDRESS MARKER_SLOT_ADDRESS(0) - MARKER_RESERVED_SPACE 272 | 273 | // bitstream storage base address -- assumes 4 slots available, 0-3 274 | #define FLASH_TARGET_OFFSETSTART MARKER_SLOT_ADDRESS(4) 275 | #define BITSTREAM_SLOT_RESERVED_SPACE (512 * 1024) 276 | #define FLASH_STORAGE_STARTADDRESS(slotidx) (FLASH_TARGET_OFFSETSTART + ( (slotidx) * BITSTREAM_SLOT_RESERVED_SPACE)) 277 | 278 | 279 | 280 | 281 | #endif /* SRC_BOARD_CONFIG_DEFAULTS_H_ */ 282 | -------------------------------------------------------------------------------- /src/config_defaults/psydmi.h: -------------------------------------------------------------------------------- 1 | /* 2 | * psydmi.h, part of the riffpga project 3 | * 4 | * Created on: Jan 18, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #ifndef CONFIG_DEFAULTS_PSYDMI_H_ 23 | #define CONFIG_DEFAULTS_PSYDMI_H_ 24 | 25 | #define BOARD_NAME "PsyDMI" 26 | #define DRIVE_VOLUME_LABEL "PSYDMI" 27 | 28 | 29 | // RP2 clock frequency 30 | // TinyUSB wants it to be 120M but it's 31 | // worked fine from 100 to 126 so far... still 32 | #define BOARD_SYSTEM_CLOCK_FREQ_HZ 120000000ULL 33 | 34 | 35 | #define PIN_RP_LED 6 36 | 37 | #define PIN_FPGA_RESET 15 38 | #define PIN_FPGA_SPI_MISO 12 39 | #define PIN_FPGA_SPI_CS 13 40 | #define PIN_FPGA_SPI_SCK 14 41 | #define PIN_FPGA_SPI_MOSI 11 42 | #define PIN_FPGA_PROG_DONE 16 43 | 44 | #define PIN_AUTOCLOCK1 10 45 | #define PIN_AUTOCLOCK2 18 /* NC */ 46 | 47 | 48 | #define PIN_UART_BRIDGE_TX 8 49 | #define PIN_UART_BRIDGE_RX 9 50 | 51 | 52 | 53 | #define AUTOCLOCK1_DEFAULT_FREQ 12000000UL 54 | #define AUTOCLOCK1_DEFAULT_ENABLE 1 55 | 56 | #define AUTOCLOCK2_DEFAULT_FREQ 1000000UL 57 | #define AUTOCLOCK2_DEFAULT_ENABLE 0 58 | 59 | 60 | #define FPGA_RESET_INVERTED 1 61 | #define FLASH_SPI_CS_INVERTED 1 62 | #define FLASH_SPI_POLARITY 0 63 | #define FLASH_SPI_PHASE 0 64 | #define FLASH_SPI_BAUDRATE 1000000UL 65 | 66 | #define BIN_UF2_MAGIC_START1 0x951C0634UL // Randomly selected 67 | #define BIN_UF2_MAGIC_END 0x1C73C401UL // Ditto 68 | #define BIN_UF2_FAMILY_ID 0xD31B02ADUL 69 | 70 | 71 | /* 72 | * FPGA_RESET_EXTERNALLY_TRIGGERED 73 | * Set to 1 if FPGA may be reset externally 74 | * and this may be monitored on PIN_FPGA_RESET 75 | */ 76 | #define FPGA_RESET_EXTERNALLY_TRIGGERED 0 77 | 78 | #define FPGA_PROG_DONE_LEVEL 1 /* 1==HIGH means program success */ 79 | 80 | 81 | // size marker wrapped in UF2 82 | #define BITSTREAM_UF2_MAGIC_START1 0x951C0512UL 83 | #define BITSTREAM_UF2_MAGIC_END 0xE4D951C0UL 84 | #define BITSTREAM_UF2_FAMILY_ID 0x951C0FA3UL 85 | 86 | 87 | 88 | // board config wrapped in UF2 89 | #define BOARDCONF_UF2_MAGIC_START1 0x951C057EUL 90 | #define BOARDCONF_UF2_MAGIC_END 0xC401061EUL 91 | #define BOARDCONF_UF2_FAMILY_ID 0xB0A2DC0FUL 92 | 93 | 94 | 95 | #define UART_BRIDGE_BAUDRATE 115200 96 | #define UART_BRIDGE_ENABLED 0 97 | #define UART_BRIDGE_DEVICEIDX 1 98 | #define UART_BRIDGE_PIN_TX 8 99 | #define UART_BRIDGE_PIN_RX 9 100 | 101 | // ESC ESC ESC 102 | #define UART_BRIDGE_ESCAPE_SEQUENCE0 0x1b 103 | #define UART_BRIDGE_ESCAPE_SEQUENCE1 0x1b 104 | #define UART_BRIDGE_ESCAPE_SEQUENCE2 0x1b 105 | 106 | 107 | 108 | #define USER_SWITCH_IDX_RESET 0 109 | #define USER_SWITCH_IDX_CLOCKING 1 110 | #define USER_SWITCH_IDX_USR1 2 111 | #define USER_SWITCH_IDX_USR2 3 112 | 113 | #define USER_SWITCH_RESET_PIN 15 114 | #define USER_SWITCH_RESET_FUNCTION 0 /* 1 == Reset */ 115 | #define USER_SWITCH_RESET_INVERT 1 116 | #define USER_SWITCH_RESET_PULL 0 117 | #define USER_SWITCH_RESET_EDGE GPIO_IRQ_EDGE_FALL 118 | 119 | 120 | #define USER_SWITCH_CLOCK_PIN 5 121 | #define USER_SWITCH_CLOCK_FUNCTION 2 /* 2 == Clocking */ 122 | #define USER_SWITCH_CLOCK_INVERT 0 123 | #define USER_SWITCH_CLOCK_PULL 0 124 | #define USER_SWITCH_CLOCK_EDGE GPIO_IRQ_EDGE_RISE 125 | 126 | 127 | #define USER_SWITCH_USR1_PIN 0 /* DNE */ 128 | #define USER_SWITCH_USR1_FUNCTION 0 /* 0 == NOTSET */ 129 | #define USER_SWITCH_USR1_INVERT 0 130 | #define USER_SWITCH_USR1_PULL 0 131 | #define USER_SWITCH_USR1_EDGE 0 132 | #define USER_SWITCH_USR2_PIN 0 /* DNE */ 133 | #define USER_SWITCH_USR2_FUNCTION 0 /* 0 == NOTSET */ 134 | #define USER_SWITCH_USR2_INVERT 0 135 | #define USER_SWITCH_USR2_PULL 0 136 | #define USER_SWITCH_USR2_EDGE 0 137 | 138 | 139 | #define SYSTEM_NEWER_VERSION_AUTO_RESET 1 140 | #define SYSTEM_INPUTS_NUM 0 141 | #define SYSTEM_INPUTS_IO_LIST /* 0,1,2,3,4,5,6,7 */ 142 | 143 | 144 | 145 | /* 146 | * Flash storage: 147 | * We leave the first 0.5M for RP code. 148 | * After that we have: 149 | * 2 pages/slot for markers (much more than we need but the space is reserved) 150 | * then a 512k block of flash space/slot 151 | */ 152 | #define BITSTREAM_MANAGEMENT_START (512 * 1024) 153 | #define MARKER_RESERVED_SPACE (4*2*1024) 154 | #define MARKER_SLOT_ADDRESS(slotidx) (BITSTREAM_MANAGEMENT_START + ( (slotidx) * MARKER_RESERVED_SPACE)) 155 | #define BOARD_CONFIG_FLASHADDRESS MARKER_SLOT_ADDRESS(0) - MARKER_RESERVED_SPACE 156 | 157 | // bitstream storage base address -- assumes 4 slots available, 0-3 158 | #define FLASH_TARGET_OFFSETSTART MARKER_SLOT_ADDRESS(4) 159 | #define BITSTREAM_SLOT_RESERVED_SPACE (512 * 1024) 160 | #define FLASH_STORAGE_STARTADDRESS(slotidx) (FLASH_TARGET_OFFSETSTART + ( (slotidx) * BITSTREAM_SLOT_RESERVED_SPACE)) 161 | 162 | 163 | #endif /* CONFIG_DEFAULTS_PSYDMI_H_ */ 164 | -------------------------------------------------------------------------------- /src/config_defaults/sys_version.h: -------------------------------------------------------------------------------- 1 | /* 2 | * sys_version.h, part of the riffpga project 3 | * 4 | * Incrementing this version will automatically overwrite the config 5 | * blob on targets with the set of defaults. 6 | * 7 | * Author: Pat Deegan 8 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 9 | * 10 | * This program is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see . 22 | */ 23 | 24 | #ifndef CONFIG_DEFAULTS_SYSVERSION_H_ 25 | #define CONFIG_DEFAULTS_SYSVERSION_H_ 26 | 27 | 28 | #define BOARD_VERSION_MAJOR 1 29 | #define BOARD_VERSION_MINOR 1 30 | #define BOARD_VERSION_PATCH 5 31 | 32 | #define BRD_STR_HELPER(x) #x 33 | #define BRD_STR(x) BRD_STR_HELPER(x) 34 | 35 | #define BOARD_VERSION_STR BRD_STR(BOARD_VERSION_MAJOR) "." \ 36 | BRD_STR(BOARD_VERSION_MINOR) "." \ 37 | BRD_STR(BOARD_VERSION_PATCH) 38 | 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /src/debug.h: -------------------------------------------------------------------------------- 1 | /* 2 | * debug.h, part of the riffpga project 3 | * Author: Pat Deegan 4 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see . 18 | */ 19 | 20 | #ifndef ASICSIM_DEBUG_H_ 21 | #define ASICSIM_DEBUG_H_ 22 | 23 | #include "cdc_interface.h" 24 | 25 | //define DEBUG_OUTPUT_ENABLED 26 | 27 | // define FORCE_DEBUG_BUF(b, len) cdc_write(b, len) 28 | #ifdef DEBUG_OUTPUT_ENABLED 29 | #define DEBUG_ENDLN() cdc_write("\n",1) 30 | #define DEBUG_BUF(b, len) cdc_write(b, len) 31 | #define DEBUG(s) cdc_write(s, sizeof(s)) 32 | #define DEBUG_LN(s) cdc_write(s, sizeof(s)) ; DEBUG_ENDLN() 33 | 34 | #define DEBUG_U32(v) cdc_write_u32(v) 35 | #define DEBUG_U32_LN(v) DEBUG_U32(v); DEBUG_ENDLN() 36 | #define DEBUG_U16(v) cdc_write_u16(v) 37 | #define DEBUG_U16_LN(v) DEBUG_U16(v); DEBUG_ENDLN() 38 | #define DEBUG_U8(v) cdc_write_u8(v) 39 | #define DEBUG_U8_LN(v) DEBUG_U8(v); DEBUG_ENDLN() 40 | 41 | #else 42 | 43 | #define DEBUG_ENDLN() 44 | #define DEBUG_BUF(b, len) 45 | #define DEBUG(s) 46 | #define DEBUG_LN(s) 47 | 48 | #define DEBUG_U32(v) 49 | #define DEBUG_U32_LN(v) 50 | #define DEBUG_U16(v) 51 | #define DEBUG_U16_LN(v) 52 | #define DEBUG_U8(v) 53 | #define DEBUG_U8_LN(v) 54 | 55 | #endif /* DEBUG_OUTPUT_ENABLED */ 56 | 57 | 58 | #endif 59 | -------------------------------------------------------------------------------- /src/driver_state.h: -------------------------------------------------------------------------------- 1 | /* 2 | * driver_state.h, part of the ASICSim2 project 3 | * 4 | * Created on: Jan 30, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #ifndef DRIVER_STATE_H_ 23 | #define DRIVER_STATE_H_ 24 | 25 | #include "board_includes.h" 26 | 27 | typedef struct driverstatestruct { 28 | 29 | uint32_t blink_interval_ms; 30 | bool have_programmed; 31 | bool immediate_led_blink; 32 | bool clocking_manually; 33 | } DriverState; 34 | 35 | extern DriverState MainDriverState; 36 | 37 | 38 | 39 | 40 | #endif /* DRIVER_STATE_H_ */ 41 | -------------------------------------------------------------------------------- /src/fpga.c: -------------------------------------------------------------------------------- 1 | /* 2 | * fpga.c, part of the riffpga project 3 | * 4 | * Created on: Dec 18, 2024 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #include "board_defs.h" 23 | #include "board_config.h" 24 | #include "board_config_defaults.h" 25 | #include "fpga.h" 26 | #include "debug.h" 27 | 28 | typedef struct fpga_state_struct { 29 | 30 | bool is_init; 31 | bool is_programmed; 32 | bool in_reset; 33 | uint8_t pin_reset; 34 | uint8_t pin_reset_dir; 35 | bool reset_switch_enabled; 36 | 37 | bool spi_tx_started; 38 | uint8_t spi_idx; 39 | 40 | } FPGA_State; 41 | 42 | static volatile FPGA_State fpgastate = { 0 }; 43 | 44 | #define SPIDEVICE(fpgastate) (fpgastate.spi_idx == 0 ? spi0 : spi1) 45 | 46 | #define FPGA_DEBUG_ENABLE 47 | #ifdef FPGA_DEBUG_ENABLE 48 | #define FPGA_DEBUG_ENDLN() DEBUG_ENDLN() 49 | #define FPGA_DEBUG_BUF(b, len) DEBUG_BUF(b, len) 50 | #define FPGA_DEBUG(s) DEBUG(s) 51 | #define FPGA_DEBUG_LN(s) DEBUG_LN(s) 52 | 53 | #define FPGA_DEBUG_U32(v) DEBUG_U32(v) 54 | #define FPGA_DEBUG_U32_LN(v) DEBUG_U32_LN(v) 55 | #define FPGA_DEBUG_U16(v) DEBUG_U16(v) 56 | 57 | #define FPGA_DEBUG_U16_LN(v) DEBUG_U16_LN(v) 58 | #define FPGA_DEBUG_U8(v) DEBUG_U8(v) 59 | #define FPGA_DEBUG_U8_LN(v) DEBUG_U8_LN(v) 60 | #else 61 | 62 | #define FPGA_DEBUG_ENDLN() 63 | #define FPGA_DEBUG_BUF(b, len) 64 | #define FPGA_DEBUG(s) 65 | #define FPGA_DEBUG_LN(s) 66 | 67 | #define FPGA_DEBUG_U32(v) 68 | #define FPGA_DEBUG_U32_LN(v) 69 | #define FPGA_DEBUG_U16(v) 70 | 71 | #define FPGA_DEBUG_U16_LN(v) 72 | #define FPGA_DEBUG_U8(v) 73 | #define FPGA_DEBUG_U8_LN(v) 74 | #endif 75 | 76 | volatile bool _fpga_extrst_done = false; 77 | 78 | volatile bool fpga_external_reset(void) { 79 | return _fpga_extrst_done; 80 | } 81 | 82 | void fpga_external_reset_handled(void) { 83 | _fpga_extrst_done = false; 84 | } 85 | 86 | void gpio_int_callback(uint gpio, uint32_t event_mask) { 87 | // single, useless, centralized callback 88 | // using gpio_add_raw_irq_handler to have a more 89 | // fine-grained system 90 | 91 | } 92 | 93 | void reset_button_release(void) { 94 | uint32_t irqmask = gpio_get_irq_event_mask(fpgastate.pin_reset); 95 | if (irqmask) { 96 | gpio_acknowledge_irq(fpgastate.pin_reset, irqmask); 97 | if (fpgastate.is_init && fpgastate.reset_switch_enabled 98 | && !fpgastate.in_reset) { 99 | CDCWRITESTRING("\r\nRESET switch"); 100 | _fpga_extrst_done = true; 101 | } 102 | fpgastate.reset_switch_enabled = true; 103 | } 104 | } 105 | static void fpga_set_reset_pin_dir(uint8_t dir) { 106 | fpgastate.pin_reset_dir = dir; 107 | gpio_set_dir(fpgastate.pin_reset, dir); 108 | } 109 | static void fpga_reset_monitor_enable(BoardConfigPtrConst bc, bool do_enable) { 110 | 111 | if (do_enable == true) { 112 | fpga_set_reset_pin_dir(GPIO_IN); 113 | } 114 | 115 | fpgastate.reset_switch_enabled = false; 116 | if (bc->fpga_cram.reset_inverted) { 117 | 118 | gpio_set_irq_enabled(bc->fpga_cram.pin_reset, GPIO_IRQ_EDGE_RISE, 119 | do_enable); 120 | } else { 121 | gpio_set_irq_enabled(bc->fpga_cram.pin_reset, GPIO_IRQ_EDGE_FALL, 122 | do_enable); 123 | 124 | } 125 | 126 | } 127 | void fpga_init(void) { 128 | 129 | uint8_t spi0_scks[] = { 2, 6, 18, 22, 0xff }; 130 | 131 | BoardConfigPtrConst bc = boardconfig_get(); 132 | 133 | uint8_t i = 0; 134 | fpgastate.spi_idx = 1; 135 | while (spi0_scks[i] != 0xff) { 136 | if (bc->fpga_cram.spi.pin_sck == spi0_scks[i]) { 137 | fpgastate.spi_idx = 0; 138 | } 139 | i++; 140 | } 141 | 142 | spi_init(SPIDEVICE(fpgastate), bc->fpga_cram.spi.rate); 143 | // NO: do it manual style gpio_set_function(PIN_FPGA_SPI_CS, GPIO_FUNC_SPI); 144 | gpio_set_function(bc->fpga_cram.spi.pin_miso, GPIO_FUNC_SPI); 145 | gpio_set_function(bc->fpga_cram.spi.pin_mosi, GPIO_FUNC_SPI); 146 | gpio_set_function(bc->fpga_cram.spi.pin_sck, GPIO_FUNC_SPI); 147 | 148 | spi_set_format(SPIDEVICE(fpgastate), 8, bc->fpga_cram.spi.polarity, 149 | bc->fpga_cram.spi.phase, 150 | bc->fpga_cram.spi.order /* unused... must be MSB */); 151 | 152 | fpgastate.pin_reset = bc->fpga_cram.pin_reset; 153 | gpio_init(bc->fpga_cram.pin_reset); 154 | 155 | fpga_reset(true); 156 | 157 | if (bc->system.fpga_reset_external_trigger) { 158 | // setup the IRQ handler 159 | gpio_set_irq_enabled_with_callback(bc->fpga_cram.pin_reset, 160 | GPIO_IRQ_EDGE_RISE, true, gpio_int_callback); 161 | gpio_add_raw_irq_handler(bc->fpga_cram.pin_reset, reset_button_release); 162 | 163 | } else { 164 | fpga_set_reset_pin_dir(GPIO_OUT); 165 | 166 | } 167 | 168 | #ifdef FPGA_PROG_DONE_LEVEL 169 | gpio_init(bc->fpga_cram.pin_done); 170 | gpio_set_dir(bc->fpga_cram.pin_done, GPIO_IN); 171 | #endif 172 | 173 | // NO: do it manual style gpio_set_function(PIN_FPGA_SPI_CS, GPIO_FUNC_SPI); 174 | 175 | gpio_init(bc->fpga_cram.spi.pin_cs); 176 | gpio_set_dir(bc->fpga_cram.spi.pin_cs, GPIO_OUT); 177 | gpio_put(bc->fpga_cram.spi.pin_cs, bc->fpga_cram.spi.cs_inverted); 178 | 179 | fpgastate.is_init = true; 180 | 181 | } 182 | 183 | void fpga_FPGA_DEBUG_spi_pins(void) { 184 | 185 | uint8_t dummyData[5] = { 0xaa, 0x55, 0x12, 0x33, 0xaa }; 186 | BoardConfigPtrConst bc = boardconfig_get(); 187 | spi_deinit(SPIDEVICE(fpgastate)); 188 | 189 | // NO: do it manual style gpio_set_function(PIN_FPGA_SPI_CS, GPIO_FUNC_SPI); 190 | gpio_set_function(bc->fpga_cram.spi.pin_miso, GPIO_FUNC_NULL); 191 | gpio_set_function(bc->fpga_cram.spi.pin_mosi, GPIO_FUNC_NULL); 192 | gpio_set_function(bc->fpga_cram.spi.pin_sck, GPIO_FUNC_NULL); 193 | 194 | gpio_init(bc->fpga_cram.spi.pin_sck); 195 | gpio_set_dir(bc->fpga_cram.spi.pin_sck, GPIO_OUT); 196 | 197 | gpio_init(bc->fpga_cram.spi.pin_mosi); 198 | gpio_set_dir(bc->fpga_cram.spi.pin_mosi, GPIO_OUT); 199 | 200 | for (uint8_t i = 0; i < 3; i++) { 201 | gpio_put(bc->fpga_cram.spi.pin_cs, 0); 202 | gpio_put(bc->fpga_cram.spi.pin_sck, 1); 203 | gpio_put(bc->fpga_cram.spi.pin_mosi, 1); 204 | gpio_put(bc->fpga_cram.spi.pin_sck, 0); 205 | gpio_put(bc->fpga_cram.spi.pin_cs, 1); 206 | gpio_put(bc->fpga_cram.spi.pin_mosi, 0); 207 | 208 | } 209 | 210 | fpga_init(); 211 | 212 | fpga_enter_programming_mode(); 213 | 214 | fpga_spi_write(dummyData, 5); // DUMMY BYTE 215 | fpga_exit_programming_mode(); 216 | 217 | } 218 | 219 | bool fpga_external_reset_applied(void) { 220 | 221 | if (fpgastate.pin_reset_dir == GPIO_IN) { 222 | 223 | BoardConfigPtrConst bc = boardconfig_get(); 224 | if (bc->fpga_cram.reset_inverted) { 225 | return gpio_get(bc->fpga_cram.pin_reset) == 0; 226 | } else { 227 | return gpio_get(bc->fpga_cram.pin_reset) == 1; 228 | } 229 | } 230 | 231 | return false; 232 | } 233 | 234 | bool fpga_is_in_reset(void) { 235 | 236 | if (fpgastate.in_reset == true) { 237 | return true; 238 | } 239 | 240 | if (fpga_external_reset_applied()) { 241 | return true; 242 | } 243 | 244 | return false; 245 | } 246 | void fpga_reset(bool set_to) { 247 | BoardConfigPtrConst bc = boardconfig_get(); 248 | if (set_to) { 249 | FPGA_DEBUG_LN("Resetting FPGA"); 250 | fpgastate.reset_switch_enabled = false; 251 | 252 | fpgastate.in_reset = true; 253 | fpgastate.is_programmed = false; 254 | if (bc->system.fpga_reset_external_trigger) { 255 | fpga_reset_monitor_enable(bc, false); // disable IRQ 256 | fpga_set_reset_pin_dir(GPIO_OUT); 257 | } 258 | gpio_put(bc->fpga_cram.pin_reset, !bc->fpga_cram.reset_inverted); 259 | } else { 260 | 261 | FPGA_DEBUG_LN("FPGA Reset RELEASE"); 262 | 263 | // about to release from reset, ensure we tell it it's in slave mode 264 | gpio_put(bc->fpga_cram.spi.pin_cs, !bc->fpga_cram.spi.cs_inverted); 265 | // now release 266 | gpio_put(bc->fpga_cram.pin_reset, bc->fpga_cram.reset_inverted); 267 | if (bc->system.fpga_reset_external_trigger) { 268 | fpga_reset_monitor_enable(bc, true); // set to input and enable IRQ 269 | } 270 | 271 | fpgastate.in_reset = false; 272 | } 273 | 274 | sleep_ms(FLASH_RESET_DELAY_MS); 275 | } 276 | bool fpga_in_reset(void) { 277 | return fpgastate.in_reset; 278 | } 279 | 280 | bool fpga_is_init(void) { 281 | return fpgastate.is_init; 282 | } 283 | bool fpga_is_programmed(void) { 284 | 285 | #ifdef FPGA_PROG_DONE_LEVEL 286 | BoardConfigPtrConst bc = boardconfig_get(); 287 | if (fpgastate.is_programmed == false) { 288 | return false; 289 | } 290 | 291 | if (gpio_get(bc->fpga_cram.pin_done) == FPGA_PROG_DONE_LEVEL) { 292 | return true; 293 | } 294 | 295 | return false; 296 | 297 | #else 298 | return fpgastate.is_programmed; 299 | #endif 300 | } 301 | 302 | void fpga_set_programmed(bool set_to) { 303 | fpgastate.is_programmed = set_to; 304 | } 305 | 306 | void fpga_enter_programming_mode(void) { 307 | BoardConfigPtrConst bc = boardconfig_get(); 308 | uint8_t dummy_byte = 0; 309 | fpga_reset(true); 310 | 311 | fpga_spi_transaction_begin(); // cs goes low 312 | FPGA_DEBUG_LN("doing progmode reset "); 313 | sleep_ms(8); 314 | fpga_reset(false); // we disable reset, fpga acts as slave 315 | sleep_ms(6); // minimum of 1200us here! 316 | // send 8 dummy clocks 317 | gpio_put(bc->fpga_cram.spi.pin_cs, bc->fpga_cram.spi.cs_inverted); // release 318 | 319 | fpga_spi_write(&dummy_byte, 1); // DUMMY BYTE 320 | // back to low 321 | gpio_put(bc->fpga_cram.spi.pin_cs, !bc->fpga_cram.spi.cs_inverted); // select 322 | } 323 | 324 | void fpga_exit_programming_mode(void) { 325 | uint8_t dummy_bytes[6] = { 0 }; 326 | fpga_spi_write(dummy_bytes, sizeof(dummy_bytes)); 327 | 328 | fpga_spi_transaction_end(); 329 | } 330 | 331 | void fpga_spi_transaction_begin(void) { 332 | BoardConfigPtrConst bc = boardconfig_get(); 333 | gpio_put(bc->fpga_cram.spi.pin_cs, !bc->fpga_cram.spi.cs_inverted); // select 334 | 335 | fpgastate.spi_tx_started = true; 336 | asm volatile("nop \n nop \n nop"); 337 | } 338 | void fpga_spi_transaction_end(void) { 339 | 340 | BoardConfigPtrConst bc = boardconfig_get(); 341 | gpio_put(bc->fpga_cram.spi.pin_cs, bc->fpga_cram.spi.cs_inverted); // release 342 | 343 | fpgastate.spi_tx_started = false; 344 | } 345 | 346 | void fpga_spi_write(uint8_t *bts, size_t len) { 347 | bool end_tx = false; 348 | if (!fpgastate.spi_tx_started) { 349 | end_tx = true; 350 | fpga_spi_transaction_begin(); 351 | } 352 | 353 | while (spi_is_busy(SPIDEVICE(fpgastate))) { 354 | FPGA_DEBUG_LN("spi bzzy"); 355 | } 356 | spi_write_blocking(SPIDEVICE(fpgastate), bts, len); 357 | 358 | if (end_tx) { 359 | fpga_spi_transaction_end(); 360 | } 361 | } 362 | -------------------------------------------------------------------------------- /src/fpga.h: -------------------------------------------------------------------------------- 1 | /* 2 | * fpga.h, part of the riffpga project 3 | * 4 | * Created on: Dec 18, 2024 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #ifndef SRC_FPGA_H_ 23 | #define SRC_FPGA_H_ 24 | 25 | #include "board_includes.h" 26 | 27 | void fpga_init(void); 28 | bool fpga_is_init(void); 29 | 30 | bool fpga_is_in_reset(void); 31 | 32 | volatile bool fpga_external_reset(void); 33 | void fpga_external_reset_handled(void); 34 | 35 | 36 | 37 | void fpga_debug_spi_pins(void); 38 | 39 | void fpga_enter_programming_mode(void); 40 | void fpga_exit_programming_mode(void); 41 | 42 | bool fpga_is_programmed(void); 43 | void fpga_set_programmed(bool set_to); 44 | void fpga_reset(bool set_to); /* true==in reset */ 45 | bool fpga_in_reset(void); 46 | bool fpga_external_reset_applied(void); 47 | 48 | 49 | void fpga_spi_transaction_begin(void); 50 | void fpga_spi_transaction_end(void); 51 | void fpga_spi_write(uint8_t * bts, size_t len); 52 | 53 | 54 | 55 | #endif /* SRC_FPGA_H_ */ 56 | -------------------------------------------------------------------------------- /src/io_inputs.c: -------------------------------------------------------------------------------- 1 | /* 2 | * io_inputs.c, part of the riffpga project 3 | * 4 | * Created on: Jan 22, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #include "board_includes.h" 23 | 24 | #include "io_inputs.h" 25 | #include "board_config.h" 26 | #include "debug.h" 27 | 28 | typedef struct switchconfstruct { 29 | bool enabled; 30 | uint8_t function; 31 | uint8_t pin; 32 | uint8_t inverted; 33 | } SwitchConfigCache; 34 | 35 | static SwitchConfigCache _sw_config[BOARD_MAX_NUM_SWITCHES] = { 0 }; 36 | static uint8_t _sw_manual_clock_idx = 0; 37 | 38 | static volatile bool _sw_interrupt[BOARD_MAX_NUM_SWITCHES] = { false }; 39 | 40 | // static char event_str[128]; 41 | static void sw_interrupt_triggered(void) { 42 | 43 | for (uint8_t i = 0; i < BOARD_MAX_NUM_SWITCHES; i++) { 44 | uint32_t irqmask = gpio_get_irq_event_mask(_sw_config[i].pin); 45 | 46 | gpio_acknowledge_irq(_sw_config[i].pin, irqmask); 47 | if (irqmask) { 48 | // handle 49 | if (irqmask & GPIO_IRQ_EDGE_RISE) { 50 | _sw_interrupt[i] = true; 51 | } 52 | } 53 | } 54 | } 55 | 56 | volatile bool io_switch_interrupted(uint8_t idx) { 57 | return _sw_interrupt[idx]; 58 | } 59 | void io_switch_interrupt_clear(uint8_t idx) { 60 | _sw_interrupt[idx] = false; 61 | } 62 | 63 | bool io_manualclock_switch_interrupted() { 64 | return io_switch_interrupted(_sw_manual_clock_idx); 65 | 66 | } 67 | void io_manualclock_switch_interrupt_clear() { 68 | io_switch_interrupt_clear(_sw_manual_clock_idx); 69 | } 70 | 71 | bool io_switch_state(uint8_t idx) { 72 | if (!_sw_config[idx].inverted) { 73 | return gpio_get(_sw_config[idx].pin) ? true : false; 74 | } 75 | 76 | return gpio_get(_sw_config[idx].pin) ? false : true; 77 | 78 | } 79 | bool io_manualclock_switch_state() { 80 | if (_sw_config[_sw_manual_clock_idx].enabled == false) { 81 | DEBUG_LN("NOT ENABLED!"); 82 | return false; 83 | } 84 | return io_switch_state(_sw_manual_clock_idx); 85 | } 86 | 87 | uint8_t io_switches_init(void) { 88 | 89 | BoardConfigPtrConst bconf = boardconfig_get(); 90 | uint8_t num = 0; 91 | for (uint8_t i = 0; i < BOARD_MAX_NUM_SWITCHES; i++) { 92 | _sw_config[i].function = bconf->switches[i].function; 93 | if (bconf->switches[i].function == SwitchFunctionNOTSET) { 94 | _sw_config[i].enabled = false; 95 | continue; 96 | } 97 | 98 | if (bconf->switches[i].function == SwitchFunctionClocking) { 99 | _sw_manual_clock_idx = i; 100 | } 101 | _sw_config[i].inverted = bconf->switches[i].inverted; 102 | _sw_config[i].enabled = true; 103 | _sw_config[i].pin = bconf->switches[i].pin; 104 | 105 | gpio_init(_sw_config[i].pin); 106 | gpio_set_dir(_sw_config[i].pin, GPIO_IN); 107 | //gpio_set_irq_enabled_with_callback(_sw_config[i].pin, GPIO_IRQ_EDGE_RISE | GPIO_IRQ_EDGE_FALL, 108 | // true, sw_interrupt_triggered); 109 | 110 | gpio_add_raw_irq_handler(_sw_config[i].pin, sw_interrupt_triggered); 111 | 112 | gpio_set_irq_enabled(_sw_config[i].pin, GPIO_IRQ_EDGE_RISE, true); 113 | num++; 114 | } 115 | 116 | return num; 117 | 118 | } 119 | 120 | void io_inputs_init(void) { 121 | 122 | BoardConfigPtrConst bconf = boardconfig_get(); 123 | 124 | if (!bconf->system.num_inputs) { 125 | 126 | return; 127 | } 128 | 129 | for (uint8_t i; i < bconf->system.num_inputs; i++) { 130 | gpio_init(bconf->system.input_io[i]); 131 | gpio_set_dir(bconf->system.input_io[i], GPIO_IN); 132 | gpio_pull_up(bconf->system.input_io[i]); 133 | } 134 | 135 | } 136 | uint16_t io_inputs_value(void) { 137 | uint16_t retVal = 0; 138 | BoardConfigPtrConst bconf = boardconfig_get(); 139 | 140 | if (!bconf->system.num_inputs) { 141 | DEBUG_LN("No inputs configured!"); 142 | return 0; 143 | } 144 | 145 | for (uint8_t i; i < bconf->system.num_inputs; i++) { 146 | if (gpio_get(bconf->system.input_io[i])) { 147 | retVal |= (1 << (i)); 148 | } 149 | 150 | } 151 | 152 | return retVal; 153 | 154 | } 155 | -------------------------------------------------------------------------------- /src/io_inputs.h: -------------------------------------------------------------------------------- 1 | /* 2 | * io_inputs.h, part of the riffpga project 3 | * 4 | * Created on: Jan 22, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #ifndef IO_INPUTS_H_ 23 | #define IO_INPUTS_H_ 24 | 25 | #include "board_includes.h" 26 | 27 | // returns the number of switches 28 | // available 29 | uint8_t io_switches_init(void); 30 | bool io_switch_state(uint8_t idx); 31 | bool io_manualclock_switch_state(); 32 | volatile bool io_manualclock_switch_interrupted(); 33 | void io_manualclock_switch_interrupt_clear(); 34 | 35 | volatile bool io_switch_interrupted(uint8_t idx); 36 | void io_switch_interrupt_clear(uint8_t idx); 37 | 38 | 39 | 40 | 41 | void io_inputs_init(void); 42 | uint16_t io_inputs_value(void); 43 | 44 | 45 | 46 | #endif /* IO_INPUTS_H_ */ 47 | -------------------------------------------------------------------------------- /src/main.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 "board_config.h" 27 | #include "board_includes.h" 28 | #include "bsp/board_api.h" 29 | #include "tusb.h" 30 | #include "cdc_interface.h" 31 | #include "uf2.h" 32 | #include "debug.h" 33 | #include "board.h" 34 | #include "bitstream.h" 35 | #include "fpga.h" 36 | #include "board_config.h" 37 | #include "clock_pwm.h" 38 | #include "sui/sui_handler.h" 39 | #include "uart_bridge.h" 40 | #include "io_inputs.h" 41 | 42 | #include "driver_state.h" 43 | 44 | DriverState MainDriverState; 45 | 46 | 47 | // for dev emergencies, lol: 48 | // define FORCE_FACTORY_RESET 49 | 50 | 51 | //--------------------------------------------------------------------+ 52 | // MACRO CONSTANT TYPEDEF PROTYPES 53 | //--------------------------------------------------------------------+ 54 | 55 | #define BASE_FPGA_PROG_DELAY 500 56 | #define SLOT_BLINKTIME_ON_MS 5 57 | #define SLOT_BLINKTIME_OFF_MS 300 58 | #define FPGA_UNPROGRAMMED_BLINKTIME_MS 250 59 | enum { 60 | BLINK_NOT_MOUNTED = 750, BLINK_MOUNTED = 2500, BLINK_SUSPENDED = 4000, 61 | }; 62 | 63 | void led_blinking_task(void); 64 | void cdc_task(void); 65 | 66 | void run_tasks(void) { 67 | tud_task(); // tinyusb device task 68 | cdc_task(); 69 | led_blinking_task(); 70 | } 71 | 72 | void setup(void) { 73 | board_init(); 74 | board_flash_init(); 75 | uf2_init(); 76 | 77 | boardconfig_init(); 78 | #ifdef FORCE_FACTORY_RESET 79 | { 80 | #else 81 | if (boardconfig_version_mismatch() == true) { 82 | #endif 83 | // we reset -- no guarantee that bitstreams 84 | // slots are safe, so erase those too 85 | boardconfig_factoryreset(true); 86 | } 87 | 88 | BoardConfigPtrConst bconf = boardconfig_get(); 89 | boardconfig_set_systemclock_hz(bconf->system.clock_freq_hz); 90 | 91 | // init device stack on configured roothub port 92 | tud_init(BOARD_TUD_RHPORT); 93 | if (board_init_after_tusb) { 94 | board_init_after_tusb(); 95 | } 96 | fpga_init(); 97 | fpga_reset(true); 98 | 99 | io_inputs_init(); 100 | io_switches_init(); 101 | 102 | if (io_manualclock_switch_state()) { 103 | CDCWRITESTRING("Requested MANUAL clocking\r\n"); 104 | boardconfig_set_autoclock_hz(0); 105 | gpio_init(bconf->clocking[0].pin); 106 | gpio_set_dir(bconf->clocking[0].pin, GPIO_OUT); 107 | MainDriverState.clocking_manually = true; 108 | } else { 109 | DEBUG_LN("no manual clocking\r\n"); 110 | if (bconf->clocking[0].enabled && bconf->clocking[0].freq_hz > 10) { 111 | boardconfig_set_autoclock_hz(bconf->clocking[0].freq_hz); 112 | } 113 | } 114 | CDCWRITEFLUSH(); 115 | } 116 | /*------------- MAIN -------------*/ 117 | int main(void) { 118 | setup(); 119 | uint8_t attempts = 0; 120 | while (1) { 121 | run_tasks(); 122 | if (MainDriverState.clocking_manually) { 123 | if (io_manualclock_switch_interrupted()) { 124 | if (!io_manualclock_switch_state()) { 125 | MainDriverState.immediate_led_blink = true; 126 | CDCWRITESTRING("\r\nCLOCK (manual)\r\n"); 127 | io_manualclock_switch_interrupt_clear(); 128 | CDCWRITEFLUSH(); 129 | clock_once(boardconfig_autoclocking(0)); 130 | } 131 | } 132 | } 133 | if (fpga_external_reset() && !fpga_external_reset_applied()) { 134 | if (MainDriverState.have_programmed) { 135 | CDCWRITESTRING("\r\nExternal RST detected\r\n"); 136 | } 137 | MainDriverState.have_programmed = false; 138 | fpga_external_reset_handled(); 139 | attempts = 0; 140 | CDCWRITEFLUSH(); 141 | continue; 142 | } 143 | 144 | run_tasks(); 145 | if (!MainDriverState.have_programmed) { 146 | if ((fpga_external_reset_applied() == false) 147 | && board_millis() 148 | > (BASE_FPGA_PROG_DELAY 149 | + DEBUG_START_PROGRAM_DELAY_MS)) { 150 | MainDriverState.have_programmed = true; 151 | 152 | if (!bs_have_checked_for_marker()) { 153 | DEBUG("Checking for bitstream..."); 154 | if (bs_check_for_marker()) { 155 | DEBUG_LN("Got it!"); 156 | } else { 157 | DEBUG_LN("Not found."); 158 | } 159 | } 160 | 161 | run_tasks(); 162 | 163 | if (bs_file_size()) { 164 | DEBUG("Have bitstream, "); DEBUG_U32(bs_file_size()); DEBUG_LN(" bytes. Program..."); 165 | bs_program_fpga(run_tasks); 166 | DEBUG_LN("Done."); 167 | sleep_ms(100); 168 | run_tasks(); 169 | if (fpga_is_programmed()) { 170 | attempts = 0; 171 | } else { 172 | attempts++; 173 | if (attempts < 3) { 174 | MainDriverState.have_programmed = false; 175 | } 176 | } 177 | } else { 178 | DEBUG_LN("No bitstream found"); 179 | } 180 | 181 | } 182 | } 183 | } 184 | } 185 | 186 | //--------------------------------------------------------------------+ 187 | // Device callbacks 188 | //--------------------------------------------------------------------+ 189 | 190 | // Invoked when device is mounted 191 | void tud_mount_cb(void) { 192 | MainDriverState.blink_interval_ms = BLINK_MOUNTED; 193 | } 194 | 195 | // Invoked when device is unmounted 196 | void tud_umount_cb(void) { 197 | MainDriverState.blink_interval_ms = BLINK_NOT_MOUNTED; 198 | } 199 | 200 | // Invoked when usb bus is suspended 201 | // remote_wakeup_en : if host allow us to perform remote wakeup 202 | // Within 7ms, device must draw an average of current less than 2.5 mA from bus 203 | void tud_suspend_cb(bool remote_wakeup_en) { 204 | (void) remote_wakeup_en; 205 | MainDriverState.blink_interval_ms = BLINK_SUSPENDED; 206 | } 207 | 208 | // Invoked when usb bus is resumed 209 | void tud_resume_cb(void) { 210 | MainDriverState.blink_interval_ms = 211 | tud_mounted() ? BLINK_MOUNTED : BLINK_NOT_MOUNTED; 212 | } 213 | 214 | //--------------------------------------------------------------------+ 215 | // USB CDC 216 | //--------------------------------------------------------------------+ 217 | 218 | void tud_and_blink_tasks(void) { 219 | tud_task(); 220 | led_blinking_task(); 221 | tud_task(); 222 | } 223 | 224 | void cdc_task(void) { 225 | static uint8_t breakout_sequence_idx = 0; 226 | #define BRIDGE_RX_BUFLEN 40 227 | uint8_t from_bridge_rx_chars[40]; 228 | 229 | BoardConfigPtrConst bconf = boardconfig_get(); 230 | if (!bconf->uart_bridge.enabled) { 231 | sui_handle_request(tud_cdc_write, tud_cdc_read_char, tud_cdc_available, 232 | tud_and_blink_tasks); 233 | return; 234 | } 235 | 236 | // UART bridge is enabled 237 | uint8_t uart_tx_count = 0; 238 | while (tud_cdc_available()) { 239 | int32_t c = tud_cdc_read_char(); 240 | bool tx_char = true; 241 | if (c < 0) { 242 | continue; 243 | } 244 | if (c == bconf->uart_bridge.breakout_sequence[breakout_sequence_idx]) { 245 | breakout_sequence_idx++; 246 | if (!bconf->uart_bridge.breakout_sequence[breakout_sequence_idx]) { 247 | // we've matched the whole sequence! 248 | uart_bridge_disable(); 249 | boardconfig_uartbridge_disable(); 250 | CDCWRITESTRING("\r\nUART Bridge teardown\r\n"); 251 | CDCWRITEFLUSH(); 252 | tx_char = false; 253 | } 254 | } else { 255 | breakout_sequence_idx = 0; 256 | } 257 | 258 | if (tx_char == true) { 259 | uart_bridge_putc_raw(c); 260 | uart_tx_count++; 261 | } 262 | } 263 | if (uart_tx_count) { 264 | uart_bridge_tx_flush(); 265 | } 266 | 267 | uint8_t uart_rx_count = 0; 268 | 269 | while (uart_bridge_is_readable()) { 270 | from_bridge_rx_chars[uart_rx_count] = uart_bridge_getc(); 271 | uart_rx_count += 1; 272 | 273 | if (uart_rx_count >= (BRIDGE_RX_BUFLEN - 1)) { 274 | if (tud_cdc_write_available() < uart_rx_count) { 275 | tud_cdc_write_flush(); 276 | } 277 | tud_cdc_write(from_bridge_rx_chars, uart_rx_count); 278 | uart_rx_count = 0; 279 | return; // for later 280 | } 281 | 282 | } 283 | 284 | if (uart_rx_count) { 285 | // write any pending bits 286 | tud_cdc_write(from_bridge_rx_chars, uart_rx_count); 287 | tud_cdc_write_flush(); 288 | } 289 | 290 | } 291 | 292 | // Invoked when cdc when line state changed e.g connected/disconnected 293 | void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) { 294 | (void) itf; 295 | (void) rts; 296 | 297 | // TODO set some indicator 298 | if (dtr) { 299 | // Terminal connected 300 | } else { 301 | // Terminal disconnected 302 | } 303 | } 304 | 305 | // Invoked when CDC interface received data from host 306 | void tud_cdc_rx_cb(uint8_t itf) { 307 | (void) itf; 308 | } 309 | 310 | //--------------------------------------------------------------------+ 311 | // BLINKING TASK 312 | //--------------------------------------------------------------------+ 313 | 314 | void led_blinking_task(void) { 315 | static uint8_t led_blink_count = 0; 316 | static uint32_t next_blink_ms = 0; 317 | static bool led_state = false; 318 | 319 | BoardConfigPtrConst bconf = boardconfig_get(); 320 | 321 | // Blink every interval ms 322 | uint32_t tnow = board_millis(); 323 | if (MainDriverState.immediate_led_blink) { 324 | MainDriverState.immediate_led_blink = false; 325 | } else if (next_blink_ms > tnow) 326 | return; // not enough time 327 | 328 | if (fpga_is_programmed() == false) { 329 | next_blink_ms = tnow + FPGA_UNPROGRAMMED_BLINKTIME_MS; 330 | } else { 331 | 332 | if (led_blink_count 333 | < (2 * (1 + boardconfig_selected_bitstream_slot()))) { 334 | 335 | if (led_blink_count == 0) { 336 | led_state = 1; 337 | } 338 | led_blink_count++; 339 | if (led_state == 0) { 340 | next_blink_ms = SLOT_BLINKTIME_OFF_MS + tnow; 341 | } else { 342 | next_blink_ms = SLOT_BLINKTIME_ON_MS + tnow; 343 | } 344 | } else { 345 | led_blink_count = 0; 346 | led_state = 0; 347 | next_blink_ms = MainDriverState.blink_interval_ms + tnow; 348 | } 349 | } 350 | 351 | board_led_write(led_state); 352 | led_state = 1 - led_state; // toggle 353 | } 354 | -------------------------------------------------------------------------------- /src/msc_disk.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2018 Ha Thach for Adafruit Industries 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 | #include "tusb.h" 26 | #include "uf2.h" 27 | #include "debug.h" 28 | 29 | //--------------------------------------------------------------------+ 30 | // MACRO TYPEDEF CONSTANT ENUM 31 | //--------------------------------------------------------------------+ 32 | #define DEBUG_SPEED_TEST 0 33 | 34 | 35 | static WriteState _wr_state = {0}; 36 | 37 | //--------------------------------------------------------------------+ 38 | // tinyusb callbacks 39 | //--------------------------------------------------------------------+ 40 | 41 | // Invoked when received SCSI_CMD_INQUIRY 42 | // Application fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively 43 | void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) { 44 | (void) lun; 45 | 46 | const char vid[] = "Psychogenic Technologies"; 47 | const char pid[] = "FPGA Update Drive"; 48 | const char rev[] = "1.0"; 49 | 50 | 51 | memcpy(vendor_id, vid, strlen(vid)); 52 | memcpy(product_id, pid, strlen(pid)); 53 | memcpy(product_rev, rev, strlen(rev)); 54 | } 55 | 56 | // Invoked when received Test Unit Ready command. 57 | // return true allowing host to read/write this LUN e.g SD card inserted 58 | bool tud_msc_test_unit_ready_cb(uint8_t lun) { 59 | (void) lun; 60 | return true; 61 | } 62 | 63 | // Callback invoked when received an SCSI command not in built-in list below 64 | // - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, MODE_SENSE6, REQUEST_SENSE 65 | // - READ10 and WRITE10 has their own callbacks 66 | int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, uint16_t bufsize) { 67 | void const* response = NULL; 68 | uint16_t resplen = 0; 69 | 70 | // most scsi handled is input 71 | bool in_xfer = true; 72 | 73 | switch (scsi_cmd[0]) { 74 | case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL: 75 | // Host is about to read/write etc ... better not to disconnect disk 76 | resplen = 0; 77 | break; 78 | 79 | default: 80 | // Set Sense = Invalid Command Operation 81 | tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00); 82 | 83 | // negative means error -> tinyusb could stall and/or response with failed status 84 | return -1; 85 | break; 86 | } 87 | 88 | // return resplen must not larger than bufsize 89 | if (resplen > bufsize) resplen = bufsize; 90 | 91 | if (response && (resplen > 0)) { 92 | if (in_xfer) { 93 | memcpy(buffer, response, resplen); 94 | } else { 95 | // SCSI output 96 | } 97 | } 98 | 99 | return resplen; 100 | } 101 | 102 | // Callback invoked when received READ10 command. 103 | // Copy disk's data to buffer (up to bufsize) and return number of copied bytes. 104 | int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize) { 105 | (void) lun; 106 | memset(buffer, 0, bufsize); 107 | /* 108 | DEBUG("read10cb lba:"); 109 | DEBUG_U32(lba); 110 | DEBUG(" off:"); 111 | DEBUG_U32(offset); 112 | DEBUG(" bufsz:"); 113 | DEBUG_U32_LN(bufsize); 114 | */ 115 | // since we return block size each, offset should always be zero 116 | TU_ASSERT(offset == 0, -1); 117 | 118 | uint32_t count = 0; 119 | 120 | while (count < bufsize) { 121 | uf2_read_block(lba, buffer); 122 | 123 | lba++; 124 | buffer += 512; 125 | count += 512; 126 | } 127 | 128 | return (int32_t)count; 129 | } 130 | 131 | // Callback invoked when received WRITE10 command. 132 | // Process data in buffer to disk's storage and return number of written bytes 133 | int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize) { 134 | (void) lun; 135 | (void) offset; 136 | 137 | uint32_t count = 0; 138 | 139 | 140 | while (count < bufsize) { 141 | // Consider non-uf2 block write as successful 142 | // only break if write_block is busy with flashing (return 0) 143 | 144 | if (0 == uf2_write_block(lba, buffer, &_wr_state)) break; 145 | 146 | lba++; 147 | buffer += 512; 148 | count += 512; 149 | } 150 | 151 | return (int32_t)count; 152 | } 153 | 154 | // Callback invoked when WRITE10 command is completed (status received and accepted by host). 155 | void tud_msc_write10_complete_cb(uint8_t lun) { 156 | (void) lun; 157 | static bool first_write = true; 158 | 159 | // abort the DFU, uf2 block failed integrity check 160 | if (_wr_state.aborted) { 161 | // aborted and reset 162 | // TODO:FIXME indicator_set(STATE_WRITING_FINISHED); 163 | DEBUG_LN("WRITE ABORTED!"); 164 | } else if (_wr_state.numBlocks) { 165 | // Start LED writing pattern with first write 166 | if (first_write) { 167 | #if DEBUG_SPEED_TEST 168 | _write_ms = esp_log_timestamp(); 169 | #endif 170 | 171 | first_write = false; 172 | DEBUG_LN("First write"); 173 | // TODO:FIXME indicator_set(STATE_WRITING_STARTED); 174 | } 175 | 176 | // All block of uf2 file is complete --> complete DFU process 177 | if (_wr_state.numWritten >= _wr_state.numBlocks) { 178 | // DEBUG_LN("mscw done"); 179 | #if DEBUG_SPEED_TEST 180 | uint32_t const wr_byte = _wr_state.numWritten*256; 181 | _write_ms = esp_log_timestamp()-_write_ms; 182 | printf("written %u bytes in %.02f seconds.\r\n", wr_byte, _write_ms / 1000.0F); 183 | printf("Speed : %.02f KB/s\r\n", (wr_byte / 1000.0F) / (_write_ms / 1000.0F)); 184 | #endif 185 | } 186 | } 187 | } 188 | 189 | // Invoked when received SCSI_CMD_READ_CAPACITY_10 and SCSI_CMD_READ_FORMAT_CAPACITY to determine the disk size 190 | // Application update block count and block size 191 | void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size) { 192 | (void) lun; 193 | 194 | *block_count = CFG_UF2_NUM_BLOCKS; 195 | *block_size = 512; 196 | } 197 | 198 | // Invoked when received Start Stop Unit command 199 | // - Start = 0 : stopped power mode, if load_eject = 1 : unload disk storage 200 | // - Start = 1 : active mode, if load_eject = 1 : load disk storage 201 | bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) { 202 | (void) lun; 203 | (void) power_condition; 204 | 205 | if (load_eject) { 206 | if (start) { 207 | // load disk storage 208 | uf2_init(); 209 | } else { 210 | // unload disk storage 211 | } 212 | } 213 | 214 | return true; 215 | } 216 | 217 | -------------------------------------------------------------------------------- /src/sui/commands/clocking.c: -------------------------------------------------------------------------------- 1 | /* 2 | * clocking.c, part of the riffpga project 3 | * 4 | * Created on: Jan 23, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #include "sui/commands/clocking.h" 23 | 24 | #include "board_config.h" 25 | #include "cdc_interface.h" 26 | #include "bitstream.h" 27 | #include "driver_state.h" 28 | 29 | void cmd_set_sys_clock_hz(SUIInteractionFunctions *funcs) { 30 | const char *prompt = "\r\nEnter value [Hz]: "; 31 | CDCWRITESTRING("Sys Clock now: "); 32 | cdc_write_dec_u32_ln(clock_get_hz(clk_sys)); 33 | CDCWRITEFLUSH(); 34 | 35 | uint32_t setting = sui_prompt_for_integer(prompt, strlen(prompt), 36 | funcs->write, funcs->read, funcs->avail, funcs->wait); 37 | 38 | if (setting > 1000) { 39 | boardconfig_set_systemclock_hz(setting); 40 | } 41 | } 42 | 43 | void cmd_set_autoclock_hz(SUIInteractionFunctions *funcs) { 44 | const char *prompt = "\r\nEnter value [Hz]: "; 45 | CDCWRITESTRING("Auto clock now: "); 46 | 47 | BoardConfigPtrConst bc = boardconfig_get(); 48 | 49 | if (bc->clocking[0].enabled) { 50 | CDCWRITESTRING("ENABLED @ "); 51 | cdc_write_dec_u32(bc->clocking[0].freq_hz); 52 | CDCWRITESTRING(" Hz"); 53 | 54 | } else { 55 | CDCWRITESTRING("DISABLED "); 56 | } 57 | 58 | uint32_t setting = sui_prompt_for_integer(prompt, strlen(prompt), 59 | funcs->write, funcs->read, funcs->avail, funcs->wait); 60 | 61 | if (setting < 10) { 62 | CDCWRITESTRING("Values < 10, use manual.\r\n"); 63 | } else { 64 | boardconfig_set_autoclock_hz(setting); 65 | CDCWRITESTRING("Setting auto-clocking\r\n"); 66 | 67 | } 68 | /* this isn't working quite right... 69 | CDCWRITEFLUSH(); 70 | CDCWRITESTRING("Achieved: "); 71 | cdc_write_dec_u32( (uint32_t)boardconfig_autoclocking_achieved(0)); 72 | CDCWRITESTRING(" Hz\r\n"); 73 | */ 74 | 75 | } 76 | void cmd_set_autoclock_manual(SUIInteractionFunctions *funcs) { 77 | 78 | CDCWRITESTRING("Auto clock was: "); 79 | 80 | BoardConfigPtrConst bc = boardconfig_get(); 81 | 82 | if (bc->clocking[0].enabled) { 83 | CDCWRITESTRING("ENABLED @ "); 84 | cdc_write_dec_u32(bc->clocking[0].freq_hz); 85 | CDCWRITESTRING(" Hz. Disabling.\r\n"); 86 | boardconfig_autoclock_disable(); 87 | 88 | } else { 89 | CDCWRITESTRING("already disabled.\r\n"); 90 | } 91 | 92 | CDCWRITESTRING("Now clocking manually.\r\n"); 93 | MainDriverState.clocking_manually = true; 94 | } 95 | -------------------------------------------------------------------------------- /src/sui/commands/clocking.h: -------------------------------------------------------------------------------- 1 | /* 2 | * clocking.h, part of the riffpga project 3 | * 4 | * Created on: Jan 23, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #ifndef SUI_COMMANDS_CLOCKING_H_ 23 | #define SUI_COMMANDS_CLOCKING_H_ 24 | 25 | #include "sui/sui_util.h" 26 | 27 | void cmd_set_sys_clock_hz(SUIInteractionFunctions * funcs); 28 | void cmd_set_autoclock_hz(SUIInteractionFunctions * funcs); 29 | void cmd_set_autoclock_manual(SUIInteractionFunctions * funcs); 30 | 31 | #endif /* SUI_COMMANDS_CLOCKING_H_ */ 32 | 33 | -------------------------------------------------------------------------------- /src/sui/commands/config.c: -------------------------------------------------------------------------------- 1 | /* 2 | * config.c, part of the riffpga project 3 | * 4 | * Created on: Jan 23, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #include "sui/commands/config.h" 23 | #include "cdc_interface.h" 24 | #include "bitstream.h" 25 | #include "sui/commands/dump.h" 26 | #include "sui/commands/fpga.h" 27 | #include "../../fpga.h" 28 | 29 | void cmd_factory_reset_config(SUIInteractionFunctions *funcs) { 30 | CDCWRITESTRING("\r\nConfiguration Factory Reset!\r\n"); 31 | boardconfig_factoryreset(true); 32 | cmd_dump_state(funcs); 33 | } 34 | void cmd_save_config(SUIInteractionFunctions *funcs) { 35 | boardconfig_write(); 36 | CDCWRITESTRING("\r\nConfiguration saved.\r\n"); 37 | cmd_dump_state(funcs); 38 | } 39 | 40 | void cmd_select_active_slot(SUIInteractionFunctions *funcs) { 41 | 42 | Bitstream_Slot_Content slot_contents[POSITION_SLOTS_ALLOWED]; 43 | uint8_t slot_programmed[POSITION_SLOTS_ALLOWED] = { 0 }; 44 | uint8_t num_found = bs_slot_contents(slot_contents); 45 | 46 | CDCWRITESTRING("\r\n"); 47 | for (uint8_t i = 0; i < POSITION_SLOTS_ALLOWED; i++) { 48 | CDCWRITESTRING(" * "); 49 | cdc_write_dec_u8((i + 1)); 50 | CDCWRITESTRING(": "); 51 | if (slot_contents[i].found == true) { 52 | if (slot_contents[i].info.namelen) { 53 | cdc_write(slot_contents[i].info.name, slot_contents[i].info.namelen); 54 | CDCWRITESTRING("\r\n"); 55 | slot_programmed[i] = 1; 56 | } else { 57 | CDCWRITESTRING("-unnammed-\r\n"); 58 | } 59 | } else { 60 | CDCWRITESTRING("-empty-\r\n"); 61 | 62 | } 63 | funcs->wait(); 64 | } 65 | CDCWRITEFLUSH(); 66 | if (!num_found) { 67 | CDCWRITESTRING("\r\nNo programmed slots, aborting.\r\n"); 68 | return; 69 | } 70 | 71 | const char *prompt = "\r\nEnter slot to use [1-3]: "; 72 | //BoardConfigPtrConst bc = boardconfig_get(); 73 | const Bitstream_Settings * bssetsorig = bs_settings_get(); 74 | 75 | CDCWRITESTRING("\r\nCurrent active slot is "); 76 | cdc_write_dec_u8(boardconfig_selected_bitstream_slot() + 1); 77 | 78 | if (bssetsorig->size) { 79 | CDCWRITESTRING(", have config of size "); 80 | cdc_write_dec_u32(bssetsorig->size); 81 | CDCWRITESTRING(" @ 0x"); 82 | cdc_write_u32_ln(bssetsorig->start_address); 83 | } else { 84 | 85 | CDCWRITESTRING(", but no valid stream present.\r\n"); 86 | } 87 | 88 | uint32_t slot_sel = sui_prompt_for_integer(prompt, strlen(prompt), 89 | funcs->write, funcs->read, funcs->avail, funcs->wait); 90 | 91 | if (slot_sel < 1 || slot_sel > POSITION_SLOTS_ALLOWED) { 92 | CDCWRITESTRING("\r\nInvalid slot, skipping\r\n"); 93 | return; 94 | } 95 | 96 | uint8_t slotidx = (uint8_t) slot_sel - 1; 97 | 98 | if (!slot_programmed[slotidx]) { 99 | CDCWRITESTRING("\r\nHave selected an empty slot.\r\n"); 100 | } 101 | 102 | boardconfig_set_bitstream_slot(slotidx); 103 | CDCWRITESTRING("Slot "); 104 | cdc_write_dec_u8(slotidx + 1); 105 | CDCWRITESTRING(" activated (marker @ 0x"); 106 | cdc_write_u32(boardconfig_bs_marker_address_for(slotidx)); 107 | CDCWRITESTRING(")\r\n\r\n"); 108 | boardconfig_write(); 109 | bs_clear_size_check_flag(); 110 | 111 | uint32_t bs_size = bs_check_for_marker(); 112 | if (bs_size) { 113 | 114 | const Bitstream_Settings *bsset = bs_settings_get(); 115 | 116 | CDCWRITESTRING("Found bitstream of size "); 117 | cdc_write_dec_u32(bs_size); 118 | CDCWRITESTRING(" in slot, located at 0x"); 119 | cdc_write_u32_ln(bsset->start_address); 120 | cmd_fpga_prog(funcs); 121 | } else { 122 | CDCWRITESTRING("No bitstream present in slot.\r\n"); 123 | fpga_reset(true); 124 | } 125 | 126 | } 127 | 128 | -------------------------------------------------------------------------------- /src/sui/commands/config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * config.h, part of the riffpga project 3 | * 4 | * Created on: Jan 23, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #ifndef SUI_COMMANDS_CONFIG_H_ 23 | #define SUI_COMMANDS_CONFIG_H_ 24 | 25 | #include "sui/sui_util.h" 26 | 27 | 28 | 29 | void cmd_factory_reset_config(SUIInteractionFunctions * funcs); 30 | void cmd_save_config(SUIInteractionFunctions * funcs); 31 | 32 | void cmd_select_active_slot(SUIInteractionFunctions * funcs); 33 | 34 | 35 | 36 | #endif /* SUI_COMMANDS_CONFIG_H_ */ 37 | -------------------------------------------------------------------------------- /src/sui/commands/dump.c: -------------------------------------------------------------------------------- 1 | /* 2 | * dump.c, part of the riffpga project 3 | * 4 | * Created on: Jan 23, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #include "sui/commands/dump.h" 23 | #include "sui/commands/io.h" 24 | #include "board_config_defaults.h" 25 | #include "cdc_interface.h" 26 | #include "bitstream.h" 27 | 28 | static void dump_clocks(BoardConfigPtrConst bc, SUIInteractionFunctions *funcs) { 29 | 30 | CDCWRITESTRING(" Auto-clocking: "); 31 | if (bc->clocking[0].enabled) { 32 | CDCWRITESTRING("ENABLED\r\n"); 33 | } else { 34 | CDCWRITESTRING("DISABLED\r\n"); 35 | } 36 | 37 | CDCWRITEFLUSH(); 38 | CDCWRITESTRING("\tpin: "); 39 | cdc_write_dec_u32(bc->clocking[0].pin); 40 | CDCWRITEFLUSH(); 41 | CDCWRITESTRING(", freq: "); 42 | cdc_write_dec_u32_ln(bc->clocking[0].freq_hz); 43 | 44 | uint32_t sysclkhz = clock_get_hz(clk_sys); 45 | if (sysclkhz != bc->system.clock_freq_hz) { 46 | CDCWRITESTRING(" Sys Clock requested "); 47 | cdc_write_dec_u32(bc->system.clock_freq_hz); 48 | CDCWRITESTRING(", actual: "); 49 | } else { 50 | CDCWRITESTRING(" Sys Clock: "); 51 | } 52 | 53 | cdc_write_dec_u32_ln(sysclkhz); 54 | CDCWRITEFLUSH(); 55 | 56 | } 57 | static void dump_cram_conf(BoardConfigPtrConst bc, 58 | SUIInteractionFunctions *funcs) { 59 | CDCWRITESTRING(" FPGA/CRAM pins: \r\n\tRESET: "); 60 | cdc_write_dec_u8(bc->fpga_cram.pin_reset); 61 | CDCWRITESTRING(", CDONE: "); 62 | #ifdef FPGA_PROG_DONE_LEVEL 63 | cdc_write_dec_u8_ln(bc->fpga_cram.pin_done); 64 | #else 65 | CDCWRITESTRING("n/a\r\n"); 66 | #endif 67 | CDCWRITESTRING("\tSPI CS: "); 68 | cdc_write_dec_u8(bc->fpga_cram.spi.pin_cs); 69 | CDCWRITEFLUSH(); 70 | CDCWRITESTRING(" SCK: "); 71 | cdc_write_dec_u8(bc->fpga_cram.spi.pin_sck); 72 | CDCWRITESTRING(" MISO: "); 73 | cdc_write_dec_u8(bc->fpga_cram.spi.pin_miso); 74 | CDCWRITESTRING(" MOSI: "); 75 | cdc_write_dec_u8(bc->fpga_cram.spi.pin_mosi); 76 | CDCWRITESTRING(" RATE: "); 77 | cdc_write_dec_u32_ln(bc->fpga_cram.spi.rate); 78 | CDCWRITEFLUSH(); 79 | } 80 | static void dump_uart_conf(BoardConfigPtrConst bc, 81 | SUIInteractionFunctions *funcs) { 82 | 83 | CDCWRITESTRING(" UART bridge: "); 84 | if (bc->uart_bridge.enabled) { 85 | CDCWRITESTRING("ENABLED\r\n"); 86 | } else { 87 | CDCWRITESTRING("DISABLED\r\n"); 88 | } 89 | CDCWRITEFLUSH(); 90 | CDCWRITESTRING("\tbaud: "); 91 | cdc_write_dec_u32(bc->uart_bridge.baud); 92 | CDCWRITESTRING(", rx: "); 93 | cdc_write_dec_u8(bc->uart_bridge.pin_rx); 94 | CDCWRITESTRING(", tx: "); 95 | cdc_write_dec_u8(bc->uart_bridge.pin_tx); 96 | CDCWRITESTRING(", breakout: 0x"); 97 | cdc_write_u8(bc->uart_bridge.breakout_sequence[0]); 98 | CDCWRITESTRING(" 0x"); 99 | cdc_write_u8(bc->uart_bridge.breakout_sequence[1]); 100 | CDCWRITESTRING(" 0x"); 101 | cdc_write_u8_ln(bc->uart_bridge.breakout_sequence[2]); 102 | 103 | } 104 | static void dump_switches_conf(BoardConfigPtrConst bc, 105 | SUIInteractionFunctions *funcs) { 106 | 107 | for (uint8_t i = 0; i < BOARD_MAX_NUM_SWITCHES; i++) { 108 | if (bc->switches[i].function != (uint8_t) SwitchFunctionNOTSET) { 109 | CDCWRITESTRING(" Switch "); 110 | cdc_write_dec_u8(i); 111 | CDCWRITESTRING(": "); 112 | CDCWRITEFLUSH(); 113 | switch (bc->switches[i].function) { 114 | case (uint8_t) SwitchFunctionReset: 115 | CDCWRITESTRING("Reset"); 116 | break; 117 | case (uint8_t) SwitchFunctionClocking: 118 | CDCWRITESTRING("Clocking"); 119 | break; 120 | case (uint8_t) SwitchFunctionUser: 121 | CDCWRITESTRING("User"); 122 | break; 123 | default: 124 | break; 125 | } 126 | CDCWRITESTRING("\r\n\tpin: "); 127 | cdc_write_dec_u8(bc->switches[i].pin); 128 | CDCWRITESTRING(", pull: "); 129 | cdc_write_dec_u8(bc->switches[i].pull); 130 | CDCWRITESTRING(", invert: "); 131 | cdc_write_dec_u8(bc->switches[i].inverted); 132 | CDCWRITEFLUSH(); 133 | 134 | CDCWRITESTRING(", state: "); 135 | if (gpio_get(bc->switches[i].pin)) { 136 | CDCWRITESTRING("HIGH\r\n"); 137 | } else { 138 | 139 | CDCWRITESTRING("LOW\r\n"); 140 | } 141 | CDCWRITEFLUSH(); 142 | } 143 | } 144 | } 145 | static void dump_fpga_resetprog_state(BoardConfigPtrConst bc, 146 | SUIInteractionFunctions *funcs) { 147 | 148 | if (gpio_get(bc->fpga_cram.pin_reset)) { 149 | if (bc->fpga_cram.reset_inverted) { 150 | CDCWRITESTRING(" FPGA enabled (not in reset), "); 151 | } else { 152 | 153 | CDCWRITESTRING(" FPGA in RESET, "); 154 | } 155 | } else { 156 | if (bc->fpga_cram.reset_inverted) { 157 | CDCWRITESTRING(" FPGA in RESET, "); 158 | } else { 159 | CDCWRITESTRING(" FPGA enabled (not in reset), "); 160 | } 161 | } 162 | funcs->wait(); 163 | 164 | CDCWRITESTRING("cdone is: "); 165 | #ifdef FPGA_PROG_DONE_LEVEL 166 | if (gpio_get(bc->fpga_cram.pin_done)) { 167 | CDCWRITESTRING("HIGH, "); 168 | } else { 169 | CDCWRITESTRING("LOW, "); 170 | } 171 | #else 172 | CDCWRITESTRING("n/a, "); 173 | #endif 174 | CDCWRITEFLUSH(); 175 | 176 | if (bc->clocking[0].enabled) { 177 | CDCWRITESTRING("AUTOCLOCK ON.\r\n"); 178 | } else { 179 | CDCWRITESTRING("autoclock OFF.\r\n"); 180 | } 181 | } 182 | static void dump_gp_inputs(BoardConfigPtrConst bc, 183 | SUIInteractionFunctions *funcs) { 184 | 185 | CDCWRITESTRING(" GP Inputs: "); 186 | if (!bc->system.num_inputs) { 187 | CDCWRITESTRING("None\r\n"); 188 | } else { 189 | for (uint8_t i = 0; i < bc->system.num_inputs; i++) { 190 | cdc_write_dec_u8(bc->system.input_io[i]); 191 | CDCWRITECHAR(' '); 192 | } 193 | cmd_read_io_inputs(funcs); 194 | CDCWRITEFLUSH(); 195 | 196 | } 197 | } 198 | 199 | 200 | static void uf2_magic_info(BoardConfigPtrConst bc, 201 | SUIInteractionFunctions *funcs) { 202 | 203 | CDCWRITESTRING(" UF2 Magic\r\n Bounds: 0x"); 204 | cdc_write_u32(bc->bin_download.magic_start); 205 | CDCWRITESTRING("/0x"); 206 | cdc_write_u32_ln(bc->bin_download.magic_end); 207 | CDCWRITESTRING(" Family: 0x"); 208 | cdc_write_u32_ln(bc->bin_download.family_id); 209 | funcs->wait(); 210 | CDCWRITEFLUSH(); 211 | } 212 | 213 | 214 | static void dump_bitstream_info(BoardConfigPtrConst bc, 215 | SUIInteractionFunctions *funcs) { 216 | 217 | Bitstream_Slot_Content slot_contents[POSITION_SLOTS_ALLOWED]; 218 | const Bitstream_Settings *bssets = bs_settings_get(); 219 | 220 | uint8_t num_found = bs_slot_contents(slot_contents); 221 | CDCWRITESTRING(" Slot Contents\r\n"); 222 | for (uint8_t i = 0; i < POSITION_SLOTS_ALLOWED; i++) { 223 | CDCWRITESTRING(" "); 224 | cdc_write_dec_u8((i + 1)); 225 | CDCWRITECHAR(':'); 226 | if (slot_contents[i].found == true) { 227 | if (slot_contents[i].info.namelen) { 228 | cdc_write(slot_contents[i].info.name, slot_contents[i].info.namelen); 229 | } else { 230 | CDCWRITESTRING("unnamed"); 231 | } 232 | } else { 233 | CDCWRITESTRING("empty"); 234 | } 235 | funcs->wait(); 236 | CDCWRITEFLUSH(); 237 | } 238 | 239 | CDCWRITESTRING("\r\n Using bitstream slot "); 240 | cdc_write_dec_u8(1 + boardconfig_selected_bitstream_slot()); 241 | if (bssets->size) { 242 | CDCWRITESTRING(", have config of size "); 243 | cdc_write_dec_u32(bssets->size); 244 | CDCWRITESTRING(" @ 0x"); 245 | cdc_write_u32_ln(bssets->start_address); 246 | } else { 247 | 248 | CDCWRITESTRING(", but no valid stream present.\r\n"); 249 | } 250 | } 251 | void cmd_dump_state(SUIInteractionFunctions *funcs) { 252 | 253 | BoardConfigPtrConst bc = boardconfig_get(); 254 | const char *header = 255 | "\r\n\r\n*********************** State/Config **************************\r\n"; 256 | const char *footer = 257 | "***************************************************************\r\n"; 258 | CDCWRITESTRING(header); 259 | CDCWRITESTRING(" Board: "); 260 | CDCWRITEFLUSH(); 261 | funcs->wait(); 262 | 263 | CDCWRITESTRING(bc->board_name); 264 | CDCWRITESTRING("\r\n RifFPGA Version: "); 265 | cdc_write_dec_u8(bc->version.major); 266 | CDCWRITECHAR('.'); 267 | cdc_write_dec_u8(bc->version.minor); 268 | CDCWRITECHAR('.'); 269 | cdc_write_dec_u8_ln(bc->version.patchlevel); 270 | CDCWRITESTRING("\r\n"); 271 | CDCWRITEFLUSH(); 272 | 273 | 274 | funcs->wait(); 275 | dump_clocks(bc, funcs); 276 | dump_cram_conf(bc, funcs); 277 | dump_uart_conf(bc, funcs); 278 | 279 | CDCWRITEFLUSH(); 280 | funcs->wait(); 281 | 282 | dump_switches_conf(bc, funcs); 283 | uf2_magic_info(bc, funcs); 284 | const Bitstream_Marker_State *bsmark = bs_marker_get(); 285 | if (bsmark->have_checked && bsmark->settings.size) { 286 | 287 | CDCWRITESTRING(" Project bitstream: "); 288 | cdc_write_dec_u32(bsmark->settings.size); 289 | CDCWRITESTRING(" bytes @ 0x"); 290 | cdc_write_u32_ln(bsmark->settings.start_address); 291 | } 292 | funcs->wait(); 293 | 294 | dump_gp_inputs(bc, funcs); 295 | 296 | dump_bitstream_info(bc, funcs); 297 | 298 | CDCWRITESTRING("\r\n"); 299 | dump_fpga_resetprog_state(bc, funcs); 300 | 301 | CDCWRITESTRING(footer); 302 | 303 | } 304 | 305 | void cmd_dump_raw_config(SUIInteractionFunctions *funcs) { 306 | 307 | BoardConfigPtrConst bc = boardconfig_get(); 308 | 309 | uint8_t *v = (uint8_t*) bc; 310 | 311 | CDCWRITESTRING("\r\nBoardConfig Raw Bytes:\r\n"); 312 | for (uint32_t i = 0; i < sizeof(BoardConfig); i++) { 313 | if (i % 32 == 0) { 314 | CDCWRITESTRING("\r\n"); 315 | CDCWRITEFLUSH(); 316 | } 317 | cdc_write_u8_leadingzeros(v[i]); 318 | CDCWRITECHAR(' '); 319 | } 320 | CDCWRITESTRING("\r\n\r\n"); 321 | CDCWRITEFLUSH(); 322 | } 323 | 324 | void cmd_dump_raw_slot(SUIInteractionFunctions *funcs) { 325 | UF2_Block* raw_bs_block = bs_info(); 326 | 327 | uint8_t *v = (uint8_t*) raw_bs_block; 328 | 329 | CDCWRITESTRING("\r\nCurrent slot raw bytes:\r\n"); 330 | for (uint32_t i = 0; i < sizeof(UF2_Block); i++) { 331 | if (i % 32 == 0) { 332 | CDCWRITESTRING("\r\n"); 333 | CDCWRITEFLUSH(); 334 | } 335 | cdc_write_u8_leadingzeros(v[i]); 336 | CDCWRITECHAR(' '); 337 | } 338 | CDCWRITESTRING("\r\n\r\n"); 339 | CDCWRITEFLUSH(); 340 | } 341 | 342 | 343 | -------------------------------------------------------------------------------- /src/sui/commands/dump.h: -------------------------------------------------------------------------------- 1 | /* 2 | * dump.h, part of the riffpga project 3 | * 4 | * Created on: Jan 23, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #ifndef SUI_COMMANDS_DUMP_H_ 23 | #define SUI_COMMANDS_DUMP_H_ 24 | 25 | #include "sui/sui_util.h" 26 | void cmd_dump_state(SUIInteractionFunctions * funcs); 27 | void cmd_dump_raw_config(SUIInteractionFunctions * funcs); 28 | void cmd_dump_raw_slot(SUIInteractionFunctions *funcs); 29 | 30 | #endif /* SUI_COMMANDS_DUMP_H_ */ 31 | -------------------------------------------------------------------------------- /src/sui/commands/fpga.c: -------------------------------------------------------------------------------- 1 | /* 2 | * fpga.c, part of the riffpga project 3 | * 4 | * Created on: Jan 23, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #include "sui/commands/fpga.h" 23 | #include "cdc_interface.h" 24 | #include "board_config.h" 25 | #include "../../fpga.h" 26 | #include "../../board.h" 27 | #include "bitstream.h" 28 | 29 | void cmd_fpga_erase(SUIInteractionFunctions *funcs) { 30 | 31 | CDCWRITESTRING("\r\n Erasing FPGA bitstreams\r\n"); 32 | bs_erase_all(); 33 | 34 | board_flash_pages_erased_clear(); 35 | bs_init(); 36 | fpga_reset(true); 37 | 38 | } 39 | 40 | void cmd_fpga_reset(SUIInteractionFunctions *funcs) { 41 | CDCWRITESTRING("\r\n Toggle FPGA reset, now: "); 42 | if (fpga_is_in_reset() == false) { 43 | fpga_reset(true); 44 | CDCWRITESTRING("in RESET."); 45 | } else { 46 | fpga_reset(false); 47 | CDCWRITESTRING("enabled (not reset)."); 48 | } 49 | 50 | } 51 | void cmd_fpga_prog(SUIInteractionFunctions *funcs) { 52 | 53 | BoardConfigPtrConst bc = boardconfig_get(); 54 | 55 | CDCWRITESTRING("\r\nProgramming FPGA..."); 56 | if (bs_program_fpga(funcs->wait) == false) { 57 | CDCWRITESTRING("Failed to prog?\r\n"); 58 | } else { 59 | CDCWRITESTRING("done!\r\n"); 60 | } 61 | 62 | CDCWRITESTRING("cdone is: "); 63 | #ifdef FPGA_PROG_DONE_LEVEL 64 | if (gpio_get(bc->fpga_cram.pin_done)) { 65 | CDCWRITESTRING("HIGH\r\n"); 66 | } else { 67 | CDCWRITESTRING("LOW\r\n"); 68 | } 69 | #else 70 | CDCWRITESTRING("n/a\r\n"); 71 | #endif 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/sui/commands/fpga.h: -------------------------------------------------------------------------------- 1 | /* 2 | * fpga.h, part of the riffpga project 3 | * 4 | * Created on: Jan 23, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #ifndef SUI_COMMANDS_FPGA_H_ 23 | #define SUI_COMMANDS_FPGA_H_ 24 | 25 | #include "sui/sui_util.h" 26 | 27 | void cmd_fpga_erase(SUIInteractionFunctions * funcs); 28 | void cmd_fpga_reset(SUIInteractionFunctions * funcs); 29 | void cmd_fpga_prog(SUIInteractionFunctions * funcs) ; 30 | 31 | #endif /* SUI_COMMANDS_FPGA_H_ */ 32 | -------------------------------------------------------------------------------- /src/sui/commands/io.c: -------------------------------------------------------------------------------- 1 | /* 2 | * io.c, part of the riffpga project 3 | * 4 | * Created on: Jan 23, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #include "sui/commands/io.h" 23 | #include "cdc_interface.h" 24 | #include "board_config.h" 25 | #include "io_inputs.h" 26 | 27 | void cmd_read_io_inputs(SUIInteractionFunctions *funcs) { 28 | uint16_t readVal = io_inputs_value(); 29 | CDCWRITESTRING("\r\n Inputs currently: "); 30 | cdc_write_dec_u16(readVal); 31 | CDCWRITESTRING(", 0x"); 32 | cdc_write_u16_ln(readVal); 33 | 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/sui/commands/io.h: -------------------------------------------------------------------------------- 1 | /* 2 | * io.h, part of the riffpga project 3 | * 4 | * Created on: Jan 23, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #ifndef SUI_COMMANDS_IO_H_ 23 | #define SUI_COMMANDS_IO_H_ 24 | 25 | #include "sui/sui_util.h" 26 | 27 | void cmd_read_io_inputs(SUIInteractionFunctions * funcs); 28 | 29 | 30 | #endif /* SUI_COMMANDS_IO_H_ */ 31 | -------------------------------------------------------------------------------- /src/sui/commands/rp_system.c: -------------------------------------------------------------------------------- 1 | /* 2 | * rp_system.c, part of the ASICSim2 project 3 | * 4 | * Created on: Jan 30, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #include "sui/commands/rp_system.h" 23 | #include "cdc_interface.h" 24 | #include "board.h" 25 | #include "debug.h" 26 | 27 | void cmd_rp2_reboot(SUIInteractionFunctions *funcs) { 28 | CDCWRITESTRING("\r\nRebooting! Virtual drive will dismount\r\n"); 29 | funcs->wait(); 30 | sleep_ms(250); 31 | CDCWRITEFLUSH(); 32 | funcs->wait(); 33 | board_reboot(); 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/sui/commands/rp_system.h: -------------------------------------------------------------------------------- 1 | /* 2 | * rp_system.h, part of the ASICSim2 project 3 | * 4 | * Created on: Jan 30, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #ifndef SUI_COMMANDS_RP_SYSTEM_H_ 23 | #define SUI_COMMANDS_RP_SYSTEM_H_ 24 | 25 | #include "sui/sui_util.h" 26 | 27 | void cmd_rp2_reboot(SUIInteractionFunctions * funcs); 28 | 29 | 30 | 31 | #endif /* SUI_COMMANDS_RP_SYSTEM_H_ */ 32 | -------------------------------------------------------------------------------- /src/sui/commands/uart.c: -------------------------------------------------------------------------------- 1 | /* 2 | * uart.c, part of the riffpga project 3 | * 4 | * Created on: Jan 23, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #include "sui/commands/uart.h" 23 | 24 | #include "board_config.h" 25 | #include "cdc_interface.h" 26 | #include "bitstream.h" 27 | #include "uart_bridge.h" 28 | 29 | void cmd_uartbridge_enable(SUIInteractionFunctions *funcs) { 30 | CDCWRITESTRING("\r\nEnabling UART bridge\r\n"); 31 | uart_bridge_enable(); 32 | boardconfig_uartbridge_enable(); 33 | } 34 | 35 | void cmd_uartbridge_baudrate(SUIInteractionFunctions *funcs) { 36 | const char *prompt = "\r\nEnter value [baud]: "; 37 | 38 | BoardConfigPtrConst bc = boardconfig_get(); 39 | 40 | CDCWRITESTRING("\r\nUART bridge baudrate now: "); 41 | cdc_write_dec_u32_ln(bc->uart_bridge.baud); 42 | uint32_t setting = sui_prompt_for_integer(prompt, strlen(prompt), 43 | funcs->write, funcs->read, funcs->avail, funcs->wait); 44 | if (setting < 9600) { 45 | CDCWRITESTRING("\r\ncancelled."); 46 | } else { 47 | boardconfig_set_uartbridge_baudrate(setting); 48 | CDCWRITESTRING("\r\nBaud rate set to "); 49 | cdc_write_dec_u32_ln(setting); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/sui/commands/uart.h: -------------------------------------------------------------------------------- 1 | /* 2 | * uart.h, part of the riffpga project 3 | * 4 | * Created on: Jan 23, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #ifndef SUI_COMMANDS_UART_H_ 23 | #define SUI_COMMANDS_UART_H_ 24 | #include "sui/sui_util.h" 25 | void cmd_uartbridge_enable(SUIInteractionFunctions * funcs); 26 | void cmd_uartbridge_baudrate(SUIInteractionFunctions * funcs); 27 | 28 | #endif /* SUI_COMMANDS_UART_H_ */ 29 | -------------------------------------------------------------------------------- /src/sui/sui_command.c: -------------------------------------------------------------------------------- 1 | /* 2 | * sui_command.c, part of the riffpga project 3 | * 4 | * Created on: Jan 6, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #include "cdc_interface.h" 23 | #include "sui_command.h" 24 | #include "debug.h" 25 | #include "sui/commands/clocking.h" 26 | #include "sui/commands/config.h" 27 | #include "sui/commands/dump.h" 28 | #include "sui/commands/fpga.h" 29 | #include "sui/commands/io.h" 30 | #include "sui/commands/uart.h" 31 | #include "sui/commands/rp_system.h" 32 | 33 | 34 | 35 | /* 36 | * Commands available in minishell 37 | * 38 | * Each command has 39 | * - a name, to trigger it 40 | * - description (help), displayed in help 41 | * - hotkey, a single KEY to trigger 42 | * - a callback (cb) 43 | * 44 | */ 45 | 46 | 47 | /* 48 | * List of available commands. To add a command: 49 | * 50 | * - add a CommandInfo entry in this list, somewhere 51 | * before the last entry 52 | * 53 | * - the .cb member will be a callback, the actual command 54 | * that is run. 55 | * 56 | * - implement that callback and do whatever you want to do 57 | * 58 | * Note: try to have a command who's first letters don't conflict 59 | * so you can use them easily with shortened strings, e.g. 60 | * 'uar' rather than having to type 'uartbridge' 61 | * Also, don't conflict with the hotkey 62 | */ 63 | static CommandInfo commands[] = { 64 | { 65 | .command = "slot", 66 | .help = "Select FPGA bitstream slot", 67 | .hotkey = 'S', 68 | .needs_confirmation = false, 69 | .cb = cmd_select_active_slot 70 | }, 71 | { 72 | .command = "projclock", 73 | .help = "Project Auto-Clocking", 74 | .hotkey = 'A', 75 | .needs_confirmation = false, 76 | .cb = cmd_set_autoclock_hz 77 | }, 78 | { 79 | .command = "manualclock", 80 | .help = "Stop Auto-Clocking", 81 | .hotkey = 'M', 82 | .needs_confirmation = true, 83 | .cb = cmd_set_autoclock_manual 84 | }, 85 | { 86 | .command = "fpgareset", 87 | .help = "Toggle FPGA reset", 88 | .hotkey = 'R', 89 | .needs_confirmation = true, 90 | .cb = cmd_fpga_reset 91 | }, 92 | { 93 | .command = "fpgaprogram", 94 | .help = "Program FPGA", 95 | .hotkey = 'P', 96 | .needs_confirmation = false, 97 | .cb = cmd_fpga_prog 98 | }, 99 | 100 | { 101 | .command = "fpgaerase", 102 | .help = "Erase all FPGA slots and reset", 103 | .hotkey = 'E', 104 | .needs_confirmation = true, 105 | .cb = cmd_fpga_erase 106 | }, 107 | 108 | { 109 | .command = "sysclock", 110 | .help = "RP2 System Clock", 111 | .hotkey = 'C', 112 | .needs_confirmation = false, 113 | .cb = cmd_set_sys_clock_hz 114 | }, 115 | { 116 | .command = "uartbridge", 117 | .help = "Enable UART bridge", 118 | .hotkey = 'U', 119 | .needs_confirmation = true, 120 | .cb = cmd_uartbridge_enable 121 | }, 122 | { 123 | .command = "baudrate", 124 | .help = "UART bridge baudrate", 125 | .hotkey = 'B', 126 | .needs_confirmation = false, 127 | .cb = cmd_uartbridge_baudrate 128 | }, 129 | #if SYSTEM_INPUTS_NUM > 0 130 | { 131 | .command = "readinputs", 132 | .help = "Read Inputs", 133 | .hotkey = 'I', 134 | .needs_confirmation = false, 135 | .cb = cmd_read_io_inputs 136 | }, 137 | #endif 138 | 139 | { 140 | .command = "dumpstate", 141 | .help = "Dump current state", 142 | .hotkey = 'D', 143 | .needs_confirmation = false, 144 | .cb = cmd_dump_state 145 | }, 146 | 147 | { 148 | .command = "save", 149 | .help = "Save current configuration", 150 | .hotkey = 'V', 151 | .needs_confirmation = true, 152 | .cb = cmd_save_config 153 | }, 154 | { 155 | .command = "factoryreset", 156 | .help = "Factory reset configuration", 157 | .hotkey = 'F', 158 | .needs_confirmation = true, 159 | .cb = cmd_factory_reset_config 160 | }, 161 | #ifdef DEBUG_OUTPUT_ENABLED 162 | { 163 | .command = "rawconf", 164 | .help = "Dump config, raw bytes", 165 | .hotkey = 'W', 166 | .needs_confirmation = false, 167 | .cb = cmd_dump_raw_config 168 | }, 169 | { 170 | .command = "rawslot", 171 | .help = "Dump slot conf, raw bytes", 172 | .hotkey = 'Y', 173 | .needs_confirmation = false, 174 | .cb = cmd_dump_raw_slot 175 | }, 176 | 177 | #endif 178 | { 179 | .command = "reboot", 180 | .help = "Reboot the RP2 chip", 181 | .hotkey = 'T', 182 | .needs_confirmation = true, 183 | .cb = cmd_rp2_reboot 184 | }, 185 | 186 | { 187 | .command = "?", 188 | .help = "Show this help", 189 | .hotkey = 'H', 190 | .needs_confirmation = false, 191 | .cb = sui_command_show_help 192 | }, 193 | // list terminator 194 | { 195 | .command = NULL, 196 | .help = NULL, 197 | .cb = NULL 198 | } 199 | 200 | }; 201 | 202 | CommandInfo* sui_command_by_name(const char *cmd) { 203 | uint8_t i = 0; 204 | static size_t command_max_len = 0; 205 | 206 | if (command_max_len == 0) { 207 | // max len unknown, figure it out 208 | while (commands[i].command != NULL) { 209 | size_t cmdlen = strlen(commands[i].command); 210 | if (cmdlen > command_max_len) { 211 | command_max_len = cmdlen; 212 | } 213 | i++; 214 | } 215 | 216 | i = 0; // reset 217 | } 218 | 219 | size_t inlen = strlen(cmd); 220 | size_t complen = inlen; 221 | if (complen > command_max_len) { 222 | complen = command_max_len; 223 | } 224 | 225 | while (commands[i].command != NULL) { 226 | if ( 227 | // only check cmd name substring for entered strings that have more than one 228 | // char--except in the case of commands that are 1 char long. 229 | (inlen > 1 || strlen(commands[i].command) == 1) 230 | && strncmp(commands[i].command, cmd, complen) == 0) { 231 | return &(commands[i]); 232 | } 233 | if (inlen == 1 && (commands[i].hotkey == cmd[0])) { 234 | return &(commands[i]); 235 | } 236 | i++; 237 | } 238 | return NULL; 239 | 240 | } 241 | 242 | void sui_command_show_help(SUIInteractionFunctions *funcs) { 243 | uint8_t i = 0; 244 | const char *starsep = 245 | " *************************************************************\r\n"; 246 | const char *starspacer = 247 | " * *\r\n"; 248 | BoardConfigPtrConst bc = boardconfig_get(); 249 | // char buf[2]; 250 | // buf[1] = '\0'; 251 | 252 | size_t namelen = strlen(bc->board_name); 253 | uint8_t namespaces_prefix = (59 - namelen) / 2; 254 | 255 | CDCWRITESTRING("\r\n\r\n"); 256 | CDCWRITESTRING(starsep); 257 | CDCWRITESTRING(" *"); 258 | for (uint8_t i = 0; i < namespaces_prefix; i++) { 259 | CDCWRITECHAR(' '); 260 | funcs->wait(); 261 | } 262 | CDCWRITESTRING(bc->board_name); 263 | for (uint8_t i = (namespaces_prefix + namelen); i < 59; i++) { 264 | 265 | CDCWRITECHAR(' '); 266 | funcs->wait(); 267 | } 268 | CDCWRITESTRING("*\r\n"); 269 | 270 | CDCWRITESTRING( 271 | " * === Available Commands === *\r\n"); 272 | CDCWRITESTRING(starspacer); 273 | CDCWRITEFLUSH(); 274 | funcs->wait(); 275 | CDCWRITESTRING( 276 | " * command key *\r\n"); 277 | while (commands[i].command != NULL) { 278 | // buf[0] = commands[i].hotkey; 279 | 280 | CDCWRITESTRING(" * "); 281 | CDCWRITESTRING(commands[i].command); 282 | 283 | size_t cmdlen = strlen(commands[i].command); 284 | size_t helplen = strlen(commands[i].help); 285 | 286 | if (cmdlen < 19) { 287 | for (uint8_t i = 0; i < (19 - cmdlen); i++) { 288 | CDCWRITECHAR(' '); 289 | } 290 | } 291 | 292 | CDCWRITECHAR('['); 293 | CDCWRITECHAR(commands[i].hotkey); 294 | CDCWRITESTRING("] "); 295 | CDCWRITESTRING(commands[i].help); 296 | 297 | CDCWRITEFLUSH(); 298 | if (helplen + 20 + 3 < 55) { 299 | 300 | for (uint8_t i = 0; i < (55 - (helplen + 20 + 3)); i++) { 301 | CDCWRITECHAR(' '); 302 | } 303 | } 304 | 305 | CDCWRITESTRING("*\r\n"); 306 | funcs->wait(); 307 | CDCWRITEFLUSH(); 308 | i++; 309 | 310 | } 311 | 312 | CDCWRITESTRING(starspacer); 313 | CDCWRITESTRING(" * RifFPGA v"); 314 | cdc_write_dec_u8(bc->version.major); 315 | CDCWRITECHAR('.'); 316 | cdc_write_dec_u8(bc->version.minor); 317 | CDCWRITECHAR('.'); 318 | cdc_write_dec_u8(bc->version.patchlevel); 319 | CDCWRITESTRING(" *\r\n"); 320 | 321 | funcs->wait(); 322 | CDCWRITEFLUSH(); 323 | 324 | CDCWRITESTRING( 325 | " * (C) 2025 Pat Deegan *\r\n"); 326 | CDCWRITESTRING(starspacer); 327 | funcs->wait(); 328 | CDCWRITEFLUSH(); 329 | funcs->wait(); 330 | 331 | CDCWRITESTRING( 332 | " * Enter command (or key) to trigger function. *\r\n"); 333 | funcs->wait(); 334 | CDCWRITESTRING(starspacer); 335 | funcs->wait(); 336 | CDCWRITESTRING(starsep); 337 | funcs->wait(); 338 | 339 | } 340 | 341 | 342 | 343 | -------------------------------------------------------------------------------- /src/sui/sui_command.h: -------------------------------------------------------------------------------- 1 | /* 2 | * sui_command.h, part of the riffpga project 3 | * 4 | * Created on: Jan 6, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #ifndef SUI_SUI_COMMAND_H_ 23 | #define SUI_SUI_COMMAND_H_ 24 | 25 | #include "sui_util.h" 26 | 27 | typedef void(*commandcallback)(SUIInteractionFunctions * funcs); 28 | 29 | typedef struct commandinfostruct { 30 | const char * command; 31 | const char * help; 32 | const char hotkey; 33 | bool needs_confirmation; 34 | commandcallback cb; 35 | } CommandInfo; 36 | 37 | void sui_command_show_help(SUIInteractionFunctions * funcs); 38 | 39 | CommandInfo * sui_command_by_name(const char * cmd); 40 | 41 | 42 | 43 | #endif /* SUI_SUI_COMMAND_H_ */ 44 | -------------------------------------------------------------------------------- /src/sui/sui_handler.c: -------------------------------------------------------------------------------- 1 | /* 2 | * sui_handler.c, part of the riffpga project 3 | * 4 | * Created on: Jan 6, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #include "sui_handler.h" 23 | #include "sui_command.h" 24 | #include "cdc_interface.h" 25 | #include "debug.h" 26 | 27 | void sui_handle_request(writestring_func wr, readchar_func rd, 28 | charavail_func avail, bgwaittask waittask) { 29 | char input_buffer[SUI_COMMAND_MAXLEN + 1]; 30 | 31 | SUIInteractionFunctions funcs = { .wait = waittask, .read = rd, .write = wr, 32 | .avail = avail 33 | 34 | }; 35 | 36 | if (!avail()) { 37 | return; 38 | } 39 | 40 | uint8_t numchars = sui_read_string(rd, avail, waittask, input_buffer, 41 | SUI_COMMAND_MAXLEN); 42 | if (!numchars) { 43 | CDCWRITESTRING("\r\n> "); 44 | return; 45 | } 46 | 47 | CommandInfo *cmd = sui_command_by_name(input_buffer); 48 | if (cmd != NULL) { 49 | if (cmd->needs_confirmation == false) { 50 | // just run it 51 | cmd->cb(&funcs); 52 | } else { 53 | CDCWRITESTRING("\r\n Run '"); 54 | CDCWRITESTRING(cmd->command); 55 | CDCWRITESTRING("' [y/N]? "); 56 | 57 | CDCWRITEFLUSH(); 58 | uint8_t numconfchars = sui_read_string(rd, avail, waittask, 59 | input_buffer, SUI_COMMAND_MAXLEN); 60 | if (input_buffer[0] == 'y' || input_buffer[0] == 'Y') { 61 | CDCWRITESTRING("\r\n Acknowledged. Running.\r\n"); 62 | 63 | cmd->cb(&funcs); 64 | } else { 65 | CDCWRITESTRING("\r\n Aborted. Skipping.\r\n"); 66 | } 67 | } 68 | 69 | CDCWRITESTRING("\r\n> "); 70 | CDCWRITEFLUSH(); 71 | } else { 72 | CDCWRITESTRING("\r\n> "); 73 | } 74 | } 75 | 76 | -------------------------------------------------------------------------------- /src/sui/sui_handler.h: -------------------------------------------------------------------------------- 1 | /* 2 | * sui_handler.h, part of the riffpga project 3 | * 4 | * Created on: Jan 6, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #ifndef SUI_SUI_HANDLER_H_ 23 | #define SUI_SUI_HANDLER_H_ 24 | #include "sui_util.h" 25 | #include "sui_command.h" 26 | 27 | #define SUI_COMMAND_MAXLEN 30 28 | void sui_handle_request(writestring_func wr, readchar_func rd, charavail_func avail, bgwaittask waittask); 29 | 30 | 31 | 32 | #endif /* SUI_SUI_HANDLER_H_ */ 33 | -------------------------------------------------------------------------------- /src/sui/sui_util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * sui_util.c, part of the riffpga project 3 | * 4 | * Created on: Jan 6, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #include "board_includes.h" 23 | #include "sui_util.h" 24 | #include "debug.h" 25 | #include "cdc_interface.h" 26 | 27 | uint32_t sui_prompt_for_integer(const char *prompt, uint32_t len, 28 | writestring_func wr, readchar_func rd, charavail_func avail, 29 | bgwaittask waittask) { 30 | wr(prompt, len); 31 | return sui_read_integer(rd, avail, waittask); 32 | 33 | } 34 | 35 | uint8_t sui_read_string(readchar_func rd, charavail_func avail, 36 | bgwaittask waittask, char *buffer, uint8_t maxlen) { 37 | 38 | bool have_val = false; 39 | uint8_t charcount = 0; 40 | while ((have_val == false) && (charcount < maxlen)) { 41 | if (!avail()) { 42 | waittask(); 43 | } else { 44 | char c = rd(); 45 | if (c == 0x08 /* backspace */) { 46 | if (charcount) { 47 | cdc_write_char(c); 48 | charcount -= 1; 49 | } 50 | } else if (c == ' ' || c == '\t' || c == '\r' || c == '\n' 51 | || c < ' ' || c > 'z') { 52 | have_val = true; 53 | cdc_write("\r\n", 2); 54 | CDCWRITEFLUSH(); 55 | 56 | } else { 57 | buffer[charcount] = c; 58 | cdc_write(&c, 1); 59 | CDCWRITEFLUSH(); 60 | charcount++; 61 | } 62 | } 63 | } 64 | buffer[charcount] = '\0'; 65 | 66 | return charcount; 67 | 68 | } 69 | 70 | uint32_t sui_read_integer(readchar_func rd, charavail_func avail, 71 | bgwaittask waittask) { 72 | 73 | bool have_val = false; 74 | uint32_t v = 0; 75 | uint8_t digits[11] = { 0 }; 76 | uint8_t dig_count = 0; 77 | while (have_val == false) { 78 | while (!avail()) { 79 | waittask(); 80 | } 81 | char c = rd(); 82 | if (c == 0x08 /* backspace */) { 83 | if (dig_count) { 84 | dig_count -= 1; 85 | } 86 | } else if (c < '0' || c > '9') { 87 | have_val = true; 88 | } else { 89 | digits[dig_count] = c - '0'; 90 | dig_count++; 91 | } 92 | cdc_write(&c, 1); 93 | CDCWRITEFLUSH(); 94 | // tud_cdc_write_flush(); 95 | } 96 | for (uint8_t i = 0; i < dig_count; i++) { 97 | v = v * 10; 98 | v += digits[i]; 99 | } 100 | // DEBUG("\r\n\r\nGOT VAL: "); 101 | // DEBUG_U32_LN(v); 102 | 103 | return v; 104 | } 105 | -------------------------------------------------------------------------------- /src/sui/sui_util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * sui_util.h, part of the riffpga project 3 | * 4 | * Created on: Jan 6, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #ifndef SUI_SUI_UTIL_H_ 23 | #define SUI_SUI_UTIL_H_ 24 | 25 | #include "board_config.h" 26 | #include "board_includes.h" 27 | 28 | typedef void(*bgwaittask)(void); 29 | typedef int32_t(*readchar_func) (void); 30 | typedef uint32_t(*charavail_func) (void); 31 | typedef uint32_t(*writestring_func)(const char*s, uint32_t len); 32 | 33 | typedef struct suiinteractionstruct { 34 | 35 | bgwaittask wait; 36 | readchar_func read; 37 | charavail_func avail; 38 | writestring_func write; 39 | } SUIInteractionFunctions; 40 | 41 | 42 | uint8_t sui_read_string(readchar_func rd, charavail_func avail, bgwaittask waittask, char * buffer, uint8_t maxlen); 43 | 44 | uint32_t sui_read_integer(readchar_func rd, charavail_func avail, bgwaittask waittask); 45 | 46 | uint32_t sui_prompt_for_integer(const char * prompt, uint32_t len, 47 | writestring_func wr, readchar_func rd, charavail_func avail, bgwaittask waittask); 48 | 49 | 50 | #endif /* SUI_SUI_UTIL_H_ */ 51 | -------------------------------------------------------------------------------- /src/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 | // Board Specific Configuration 35 | //--------------------------------------------------------------------+ 36 | 37 | // RHPort number used for device can be defined by board.mk, default to port 0 38 | #ifndef BOARD_TUD_RHPORT 39 | #define BOARD_TUD_RHPORT 0 40 | #endif 41 | 42 | // RHPort max operational speed can defined by board.mk 43 | #ifndef BOARD_TUD_MAX_SPEED 44 | #define BOARD_TUD_MAX_SPEED OPT_MODE_DEFAULT_SPEED 45 | #endif 46 | 47 | //-------------------------------------------------------------------- 48 | // Common Configuration 49 | //-------------------------------------------------------------------- 50 | 51 | // defined by compiler flags for flexibility 52 | #ifndef CFG_TUSB_MCU 53 | #error CFG_TUSB_MCU must be defined 54 | #endif 55 | 56 | #ifndef CFG_TUSB_OS 57 | #define CFG_TUSB_OS OPT_OS_NONE 58 | #endif 59 | 60 | #ifndef CFG_TUSB_DEBUG 61 | #define CFG_TUSB_DEBUG 0 62 | #endif 63 | 64 | // Enable Device stack 65 | #define CFG_TUD_ENABLED 1 66 | 67 | // Default is max speed that hardware controller could support with on-chip PHY 68 | #define CFG_TUD_MAX_SPEED BOARD_TUD_MAX_SPEED 69 | 70 | /* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. 71 | * Tinyusb use follows macros to declare transferring memory so that they can be put 72 | * into those specific section. 73 | * e.g 74 | * - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") )) 75 | * - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4))) 76 | */ 77 | #ifndef CFG_TUSB_MEM_SECTION 78 | #define CFG_TUSB_MEM_SECTION 79 | #endif 80 | 81 | #ifndef CFG_TUSB_MEM_ALIGN 82 | #define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) 83 | #endif 84 | 85 | //-------------------------------------------------------------------- 86 | // DEVICE CONFIGURATION 87 | //-------------------------------------------------------------------- 88 | 89 | #ifndef CFG_TUD_ENDPOINT0_SIZE 90 | #define CFG_TUD_ENDPOINT0_SIZE 64 91 | #endif 92 | 93 | //------------- CLASS -------------// 94 | #define CFG_TUD_CDC 1 95 | #define CFG_TUD_MSC 1 96 | #define CFG_TUD_HID 0 97 | #define CFG_TUD_MIDI 0 98 | #define CFG_TUD_VENDOR 0 99 | 100 | 101 | // CDC FIFO size of TX and RX 102 | #define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) 103 | #define CFG_TUD_CDC_TX_BUFSIZE 800 /* (TUD_OPT_HIGH_SPEED ? 512 : 64) */ 104 | 105 | // CDC Endpoint transfer buffer size, more is faster 106 | #define CFG_TUD_CDC_EP_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) 107 | 108 | // MSC Buffer size of Device Mass storage 109 | #define CFG_TUD_MSC_EP_BUFSIZE 512 110 | 111 | #ifdef __cplusplus 112 | } 113 | #endif 114 | 115 | #endif /* _TUSB_CONFIG_H_ */ 116 | -------------------------------------------------------------------------------- /src/uart_bridge.c: -------------------------------------------------------------------------------- 1 | /* 2 | * uart_bridge.c, part of the riffpga project 3 | * 4 | * Created on: Jan 6, 2025 5 | * Author: Pat Deegan 6 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with this program. If not, see . 20 | */ 21 | 22 | #include "board_config.h" 23 | #include "uart_bridge.h" 24 | 25 | typedef struct uartbridgestatestruct { 26 | bool is_init; 27 | uart_inst_t *uart; 28 | 29 | } UartBridgeState; 30 | 31 | static UartBridgeState ubridgestate = { 0 }; 32 | 33 | void uart_bridge_enable() { 34 | // bconf->uart_bridge. 35 | 36 | if (ubridgestate.is_init) { 37 | return; 38 | } 39 | 40 | BoardConfigPtrConst bc = boardconfig_get(); 41 | if (bc->uart_bridge.uartnum == 0) { 42 | ubridgestate.uart = uart0; 43 | } else { 44 | ubridgestate.uart = uart1; 45 | } 46 | 47 | gpio_set_function(bc->uart_bridge.pin_tx, 48 | UART_FUNCSEL_NUM(ubridgestate.uart, bc->uart_bridge.pin_tx)); 49 | 50 | gpio_set_function(bc->uart_bridge.pin_rx, 51 | UART_FUNCSEL_NUM(ubridgestate.uart, bc->uart_bridge.pin_rx)); 52 | uart_init(ubridgestate.uart, bc->uart_bridge.baud); 53 | 54 | ubridgestate.is_init = true; 55 | } 56 | void uart_bridge_disable() { 57 | 58 | if (!ubridgestate.is_init) { 59 | return; 60 | } 61 | 62 | uart_deinit(ubridgestate.uart); 63 | ubridgestate.is_init = false; 64 | 65 | } 66 | 67 | bool uart_bridge_is_writable() { 68 | 69 | if (!ubridgestate.is_init) { 70 | return false; 71 | } 72 | 73 | return uart_is_writable(ubridgestate.uart); 74 | } 75 | void uart_bridge_tx_wait_blocking() { 76 | if (!ubridgestate.is_init) { 77 | return; 78 | } 79 | 80 | uart_tx_wait_blocking(ubridgestate.uart); 81 | 82 | } 83 | bool uart_bridge_is_readable() { 84 | if (!ubridgestate.is_init) { 85 | return false; 86 | } 87 | return uart_is_readable(ubridgestate.uart); 88 | 89 | } 90 | void uart_bridge_write_blocking(const uint8_t *src, size_t len) { 91 | if (!ubridgestate.is_init) { 92 | return; 93 | } 94 | 95 | uart_write_blocking(ubridgestate.uart, src, len); 96 | 97 | } 98 | void uart_bridge_read_blocking(uint8_t *dst, size_t len) { 99 | if (!ubridgestate.is_init) { 100 | return; 101 | } 102 | uart_read_blocking(ubridgestate.uart, dst, len); 103 | } 104 | void uart_bridge_putc_raw(char c) { 105 | if (!ubridgestate.is_init) { 106 | return; 107 | } 108 | uart_putc_raw(ubridgestate.uart, c); 109 | 110 | } 111 | void uart_bridge_putc(char c) { 112 | if (!ubridgestate.is_init) { 113 | return; 114 | } 115 | uart_putc(ubridgestate.uart, c); 116 | 117 | } 118 | void uart_bridge_puts(const char *s) { 119 | if (!ubridgestate.is_init) { 120 | return; 121 | } 122 | uart_puts(ubridgestate.uart, s); 123 | 124 | } 125 | char uart_bridge_getc() { 126 | if (!ubridgestate.is_init) { 127 | return 0; 128 | } 129 | return uart_getc(ubridgestate.uart); 130 | 131 | } 132 | 133 | void uart_bridge_tx_flush() { 134 | if (!ubridgestate.is_init) { 135 | return; 136 | } 137 | uart_tx_wait_blocking(ubridgestate.uart); 138 | 139 | } 140 | -------------------------------------------------------------------------------- /src/uart_bridge.h: -------------------------------------------------------------------------------- 1 | /* 2 | * uart_bridge.h, part of the riffpga project 3 | * 4 | * The "uart bridge" allows us to leverage the serial connection to 5 | * bypass the RP2040 and talk directly to the FPGA, assuming the design 6 | * has UART support built in, on the right pins and at the right baudrate. 7 | * 8 | * Baudrate here is configurable through the serial UI. 9 | * 10 | * 11 | * Created on: Jan 6, 2025 12 | * Author: Pat Deegan 13 | * Copyright (C) 2025 Pat Deegan, https://psychogenic.com 14 | * 15 | * This program is free software: you can redistribute it and/or modify 16 | * it under the terms of the GNU General Public License as published by 17 | * the Free Software Foundation, either version 3 of the License, or 18 | * (at your option) any later version. 19 | * 20 | * This program is distributed in the hope that it will be useful, 21 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | * GNU General Public License for more details. 24 | * 25 | * You should have received a copy of the GNU General Public License 26 | * along with this program. If not, see . 27 | */ 28 | 29 | #ifndef UART_BRIDGE_H_ 30 | #define UART_BRIDGE_H_ 31 | 32 | 33 | 34 | void uart_bridge_enable(); 35 | void uart_bridge_disable(); 36 | 37 | void uart_bridge_tx_wait_blocking(); 38 | bool uart_bridge_is_readable(); 39 | void uart_bridge_write_blocking(const uint8_t *src, size_t len); 40 | void uart_bridge_read_blocking (uint8_t *dst, size_t len); 41 | void uart_bridge_putc_raw(char c); 42 | void uart_bridge_putc(char c); 43 | void uart_bridge_puts(const char *s); 44 | char uart_bridge_getc(); 45 | bool uart_bridge_is_writable(); 46 | void uart_bridge_tx_flush(); 47 | 48 | 49 | 50 | 51 | 52 | #endif /* UART_BRIDGE_H_ */ 53 | -------------------------------------------------------------------------------- /src/uf2.h: -------------------------------------------------------------------------------- 1 | /** 2 | 3 | Microsoft UF2 4 | 5 | The MIT License (MIT) 6 | 7 | Copyright (c) Microsoft Corporation 8 | 9 | All rights reserved. 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. 28 | 29 | */ 30 | #ifndef UF2FORMAT_H 31 | #define UF2FORMAT_H 1 32 | 33 | #include "board_includes.h" 34 | #include "bsp/board_api.h" 35 | 36 | //--------------------------------------------------------------------+ 37 | // UF2 Configuration 38 | //--------------------------------------------------------------------+ 39 | 40 | // Version is passed by makefile 41 | // #define UF2_VERSION "0.0.0" 42 | 43 | // The largest flash size that is supported by the board, in bytes, default is 4MB 44 | // Flash size is constrained by RAM, a 4MB size requires 2kB RAM, see MAX_BLOCKS 45 | // Largest tested is 256MB, with 0x300000 blocks (1.5GB), 64 sectors per cluster 46 | #ifndef CFG_UF2_FLASH_SIZE 47 | #define CFG_UF2_FLASH_SIZE (4*1024*1024) 48 | #endif 49 | 50 | // Number of 512-byte blocks in the exposed filesystem, default is just under 32MB 51 | // The filesystem needs space for the current file, text files, uploaded file, and FAT 52 | #ifndef CFG_UF2_NUM_BLOCKS 53 | #define CFG_UF2_NUM_BLOCKS (0x10109) 54 | #endif 55 | 56 | // Sectors per FAT cluster, must be increased proportionally for larger filesystems 57 | #ifndef CFG_UF2_SECTORS_PER_CLUSTER 58 | #define CFG_UF2_SECTORS_PER_CLUSTER (1) 59 | #endif 60 | 61 | //--------------------------------------------------------------------+ 62 | // 63 | //--------------------------------------------------------------------+ 64 | 65 | 66 | // If set, the block is "comment" and should not be flashed to the device 67 | #define UF2_FLAG_NOFLASH 0x00000001 68 | #define UF2_FLAG_FAMILYID 0x00002000 69 | 70 | #define MAX_BLOCKS (CFG_UF2_FLASH_SIZE / 256 + 100) 71 | 72 | #define BITSTREAM_NAME_MAXLEN 23 73 | typedef struct { 74 | uint32_t numBlocks; 75 | uint32_t numWritten; 76 | 77 | bool aborted; // aborting update and reset 78 | 79 | uint8_t writtenMask[MAX_BLOCKS / 8 + 1]; 80 | 81 | uint32_t payloadTotal; 82 | 83 | } WriteState; 84 | 85 | typedef struct { 86 | // 32 byte header 87 | uint32_t magicStart0; 88 | uint32_t magicStart1; 89 | uint32_t flags; 90 | uint32_t targetAddr; 91 | uint32_t payloadSize; 92 | uint32_t blockNo; 93 | uint32_t numBlocks; 94 | uint32_t familyID; 95 | 96 | // raw data; 97 | uint8_t data[476]; 98 | 99 | // store magic also at the end to limit damage from partial block reads 100 | uint32_t magicEnd; 101 | } UF2_Block; 102 | 103 | 104 | void uf2_init(void); 105 | void uf2_read_block(uint32_t block_no, uint8_t *data); 106 | int uf2_write_block(uint32_t block_no, uint8_t *data, WriteState *state); 107 | 108 | #endif 109 | -------------------------------------------------------------------------------- /src/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 "bsp/board_api.h" 27 | #include "tusb.h" 28 | 29 | /* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug. 30 | * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC. 31 | * 32 | * Auto ProductID layout's Bitmap: 33 | * [MSB] HID | MSC | CDC [LSB] 34 | */ 35 | #define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) ) 36 | #define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ 37 | _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) ) 38 | 39 | #define USB_VID 0xCafe 40 | #define USB_BCD 0x0200 41 | 42 | //--------------------------------------------------------------------+ 43 | // Device Descriptors 44 | //--------------------------------------------------------------------+ 45 | tusb_desc_device_t const desc_device = { 46 | .bLength = sizeof(tusb_desc_device_t), 47 | .bDescriptorType = TUSB_DESC_DEVICE, 48 | .bcdUSB = USB_BCD, 49 | 50 | // Use Interface Association Descriptor (IAD) for CDC 51 | // As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1) 52 | .bDeviceClass = TUSB_CLASS_MISC, 53 | .bDeviceSubClass = MISC_SUBCLASS_COMMON, 54 | .bDeviceProtocol = MISC_PROTOCOL_IAD, 55 | 56 | .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, 57 | 58 | .idVendor = USB_VID, 59 | .idProduct = USB_PID, 60 | .bcdDevice = 0x0100, 61 | 62 | .iManufacturer = 0x01, 63 | .iProduct = 0x02, 64 | .iSerialNumber = 0x03, 65 | 66 | .bNumConfigurations = 0x01 67 | }; 68 | 69 | // Invoked when received GET DEVICE DESCRIPTOR 70 | // Application return pointer to descriptor 71 | uint8_t const *tud_descriptor_device_cb(void) { 72 | return (uint8_t const *) &desc_device; 73 | } 74 | 75 | //--------------------------------------------------------------------+ 76 | // Configuration Descriptor 77 | //--------------------------------------------------------------------+ 78 | 79 | enum { 80 | ITF_NUM_CDC = 0, 81 | ITF_NUM_CDC_DATA, 82 | ITF_NUM_MSC, 83 | ITF_NUM_TOTAL 84 | }; 85 | 86 | #if CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC177X_8X || CFG_TUSB_MCU == OPT_MCU_LPC40XX 87 | // LPC 17xx and 40xx endpoint type (bulk/interrupt/iso) are fixed by its number 88 | // 0 control, 1 In, 2 Bulk, 3 Iso, 4 In, 5 Bulk etc ... 89 | #define EPNUM_CDC_NOTIF 0x81 90 | #define EPNUM_CDC_OUT 0x02 91 | #define EPNUM_CDC_IN 0x82 92 | 93 | #define EPNUM_MSC_OUT 0x05 94 | #define EPNUM_MSC_IN 0x85 95 | 96 | #elif CFG_TUSB_MCU == OPT_MCU_CXD56 97 | // CXD56 USB driver has fixed endpoint type (bulk/interrupt/iso) and direction (IN/OUT) by its number 98 | // 0 control (IN/OUT), 1 Bulk (IN), 2 Bulk (OUT), 3 In (IN), 4 Bulk (IN), 5 Bulk (OUT), 6 In (IN) 99 | #define EPNUM_CDC_NOTIF 0x83 100 | #define EPNUM_CDC_OUT 0x02 101 | #define EPNUM_CDC_IN 0x81 102 | 103 | #define EPNUM_MSC_OUT 0x05 104 | #define EPNUM_MSC_IN 0x84 105 | 106 | #elif defined(TUD_ENDPOINT_ONE_DIRECTION_ONLY) 107 | // MCUs that don't support a same endpoint number with different direction IN and OUT defined in tusb_mcu.h 108 | // e.g EP1 OUT & EP1 IN cannot exist together 109 | #define EPNUM_CDC_NOTIF 0x81 110 | #define EPNUM_CDC_OUT 0x02 111 | #define EPNUM_CDC_IN 0x83 112 | 113 | #define EPNUM_MSC_OUT 0x04 114 | #define EPNUM_MSC_IN 0x85 115 | 116 | #else 117 | #define EPNUM_CDC_NOTIF 0x81 118 | #define EPNUM_CDC_OUT 0x02 119 | #define EPNUM_CDC_IN 0x82 120 | 121 | #define EPNUM_MSC_OUT 0x03 122 | #define EPNUM_MSC_IN 0x83 123 | 124 | #endif 125 | 126 | #define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_MSC_DESC_LEN) 127 | 128 | // full speed configuration 129 | uint8_t const desc_fs_configuration[] = { 130 | // Config number, interface count, string index, total length, attribute, power in mA 131 | TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100), 132 | 133 | // Interface number, string index, EP notification address and size, EP data address (out, in) and size. 134 | TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64), 135 | 136 | // Interface number, string index, EP Out & EP In address, EP size 137 | TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 5, EPNUM_MSC_OUT, EPNUM_MSC_IN, 64), 138 | }; 139 | 140 | #if TUD_OPT_HIGH_SPEED 141 | // Per USB specs: high speed capable device must report device_qualifier and other_speed_configuration 142 | 143 | // high speed configuration 144 | uint8_t const desc_hs_configuration[] = { 145 | // Config number, interface count, string index, total length, attribute, power in mA 146 | TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100), 147 | 148 | // Interface number, string index, EP notification address and size, EP data address (out, in) and size. 149 | TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 512), 150 | 151 | // Interface number, string index, EP Out & EP In address, EP size 152 | TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 5, EPNUM_MSC_OUT, EPNUM_MSC_IN, 512), 153 | }; 154 | 155 | // other speed configuration 156 | uint8_t desc_other_speed_config[CONFIG_TOTAL_LEN]; 157 | 158 | // device qualifier is mostly similar to device descriptor since we don't change configuration based on speed 159 | tusb_desc_device_qualifier_t const desc_device_qualifier = { 160 | .bLength = sizeof(tusb_desc_device_qualifier_t), 161 | .bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER, 162 | .bcdUSB = USB_BCD, 163 | 164 | .bDeviceClass = TUSB_CLASS_MISC, 165 | .bDeviceSubClass = MISC_SUBCLASS_COMMON, 166 | .bDeviceProtocol = MISC_PROTOCOL_IAD, 167 | 168 | .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, 169 | .bNumConfigurations = 0x01, 170 | .bReserved = 0x00 171 | }; 172 | 173 | // Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request 174 | // Application return pointer to descriptor, whose contents must exist long enough for transfer to complete. 175 | // device_qualifier descriptor describes information about a high-speed capable device that would 176 | // change if the device were operating at the other speed. If not highspeed capable stall this request. 177 | uint8_t const *tud_descriptor_device_qualifier_cb(void) { 178 | return (uint8_t const *) &desc_device_qualifier; 179 | } 180 | 181 | // Invoked when received GET OTHER SEED CONFIGURATION DESCRIPTOR request 182 | // Application return pointer to descriptor, whose contents must exist long enough for transfer to complete 183 | // Configuration descriptor in the other speed e.g if high speed then this is for full speed and vice versa 184 | uint8_t const *tud_descriptor_other_speed_configuration_cb(uint8_t index) { 185 | (void) index; // for multiple configurations 186 | 187 | // if link speed is high return fullspeed config, and vice versa 188 | // Note: the descriptor type is OHER_SPEED_CONFIG instead of CONFIG 189 | memcpy(desc_other_speed_config, 190 | (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_fs_configuration : desc_hs_configuration, 191 | CONFIG_TOTAL_LEN); 192 | 193 | desc_other_speed_config[1] = TUSB_DESC_OTHER_SPEED_CONFIG; 194 | 195 | return desc_other_speed_config; 196 | } 197 | 198 | #endif // highspeed 199 | 200 | 201 | // Invoked when received GET CONFIGURATION DESCRIPTOR 202 | // Application return pointer to descriptor 203 | // Descriptor contents must exist long enough for transfer to complete 204 | uint8_t const *tud_descriptor_configuration_cb(uint8_t index) { 205 | (void) index; // for multiple configurations 206 | 207 | #if TUD_OPT_HIGH_SPEED 208 | // Although we are highspeed, host may be fullspeed. 209 | return (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_hs_configuration : desc_fs_configuration; 210 | #else 211 | return desc_fs_configuration; 212 | #endif 213 | } 214 | 215 | //--------------------------------------------------------------------+ 216 | // String Descriptors 217 | //--------------------------------------------------------------------+ 218 | 219 | // String Descriptor Index 220 | enum { 221 | STRID_LANGID = 0, 222 | STRID_MANUFACTURER, 223 | STRID_PRODUCT, 224 | STRID_SERIAL, 225 | }; 226 | 227 | // array of pointer to string descriptors 228 | char const *string_desc_arr[] = { 229 | (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) 230 | "Psychogenic Tech.", // 1: Manufacturer 231 | "Efabless FPGA Dev", // 2: Product 232 | NULL, // 3: Serials will use unique ID if possible 233 | "Control CDC", // 4: CDC Interface 234 | "FPGA Update", // 5: MSC Interface 235 | }; 236 | 237 | static uint16_t _desc_str[32 + 1]; 238 | 239 | // Invoked when received GET STRING DESCRIPTOR request 240 | // Application return pointer to descriptor, whose contents must exist long enough for transfer to complete 241 | uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) { 242 | (void) langid; 243 | size_t chr_count; 244 | 245 | switch ( index ) { 246 | case STRID_LANGID: 247 | memcpy(&_desc_str[1], string_desc_arr[0], 2); 248 | chr_count = 1; 249 | break; 250 | 251 | case STRID_SERIAL: 252 | chr_count = board_usb_get_serial(_desc_str + 1, 32); 253 | break; 254 | 255 | default: 256 | // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. 257 | // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors 258 | 259 | if ( !(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0])) ) return NULL; 260 | 261 | const char *str = string_desc_arr[index]; 262 | 263 | // Cap at max char 264 | chr_count = strlen(str); 265 | size_t const max_count = sizeof(_desc_str) / sizeof(_desc_str[0]) - 1; // -1 for string type 266 | if ( chr_count > max_count ) chr_count = max_count; 267 | 268 | // Convert ASCII string into UTF-16 269 | for ( size_t i = 0; i < chr_count; i++ ) { 270 | _desc_str[1 + i] = str[i]; 271 | } 272 | break; 273 | } 274 | 275 | // first byte is length (including header), second byte is string type 276 | _desc_str[0] = (uint16_t) ((TUSB_DESC_STRING << 8) | (2 * chr_count + 2)); 277 | 278 | return _desc_str; 279 | } 280 | --------------------------------------------------------------------------------