├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README.md ├── atari_font_gen.py ├── atrs ├── dd_dos25.atr ├── dd_mydos.atr ├── dd_sdfs.atr ├── ed_dos25.atr ├── ed_mydos.atr ├── ed_sdfs.atr ├── qd_mydos.atr ├── qd_sdfs.atr ├── sd_dos25.atr ├── sd_mydos.atr └── sd_sdfs.atr ├── atx.cpp ├── atx.hpp ├── board ├── Gerber-PCB-pico-sio-board-ver-1-1.zip └── Schematic_pico-sio-board_2024-10-12.pdf ├── boot_h.py ├── boot_loader.h ├── config.h ├── disk_boot.asm ├── disk_counter.cpp ├── disk_counter.hpp ├── disk_counter.pio ├── disk_images_data.h ├── dist.sh ├── fatfs ├── 00history.txt ├── 00readme.txt ├── diskio.c ├── diskio.h ├── ff.c ├── ff.h ├── ffconf.h ├── ffsystem.c └── ffunicode.c ├── fatfs_disk.c ├── fatfs_disk.h ├── file_load.cpp ├── file_load.hpp ├── firmware ├── a8_pico1_sio_board.uf2 ├── a8_pico1_sio_standalone.uf2 ├── a8_pico2_sio_board.uf2 ├── a8_pico2_sio_standalone.uf2 ├── a8_pico2_sio_zaxon.uf2 ├── a8_pimoroni16mb_sio_board.uf2 └── a8_pimoroni16mb_sio_standalone.uf2 ├── flash_fs.c ├── flash_fs.h ├── font_atari_data.hpp ├── images ├── all_1.jpg ├── all_2.jpg ├── bare_connections.png ├── board_1.jpg ├── board_2.jpg ├── board_connections.png ├── joystick_plug_1.jpg ├── joystick_plug_2.jpg └── working.jpg ├── io.cpp ├── io.hpp ├── led_indicator.cpp ├── led_indicator.hpp ├── main.cpp ├── mounts.cpp ├── mounts.hpp ├── msc_disk.c ├── options.cpp ├── options.hpp ├── pin_io.pio ├── sd_driver ├── crc.c ├── crc.h ├── sd_card.c ├── sd_card.h ├── sd_spi.c ├── sd_spi.h ├── spi.c └── spi.h ├── sio.cpp ├── sio.hpp ├── tusb_config.h ├── usb_descriptors.c ├── wav_decode.cpp └── wav_decode.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | disk_boot.bin 3 | disk_boot.txt -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.13) 2 | 3 | if(NOT PICO_BOARD) 4 | set(PICO_BOARD pico) 5 | endif() 6 | 7 | include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake) 8 | include($ENV{PIMORONI_PICO_PATH}/pimoroni_pico_import.cmake) 9 | 10 | set(CMAKE_C_STANDARD 11) 11 | set(CMAKE_CXX_STANDARD 17) 12 | 13 | project(a8_pico_sio C CXX ASM) 14 | 15 | pico_sdk_init() 16 | 17 | include(libraries/pico_graphics/pico_graphics) 18 | include(libraries/pico_display_2/pico_display_2) 19 | include(drivers/rgbled/rgbled) 20 | include(drivers/button/button) 21 | include(drivers/st7789/st7789) 22 | include(common/pimoroni_bus) 23 | 24 | add_executable(a8_pico_sio) 25 | 26 | target_sources(a8_pico_sio PUBLIC 27 | ${CMAKE_CURRENT_LIST_DIR}/atx.cpp 28 | ${CMAKE_CURRENT_LIST_DIR}/disk_counter.cpp 29 | ${CMAKE_CURRENT_LIST_DIR}/led_indicator.cpp 30 | ${CMAKE_CURRENT_LIST_DIR}/mounts.cpp 31 | ${CMAKE_CURRENT_LIST_DIR}/file_load.cpp 32 | ${CMAKE_CURRENT_LIST_DIR}/io.cpp 33 | ${CMAKE_CURRENT_LIST_DIR}/options.cpp 34 | ${CMAKE_CURRENT_LIST_DIR}/wav_decode.cpp 35 | ${CMAKE_CURRENT_LIST_DIR}/sio.cpp 36 | ${CMAKE_CURRENT_LIST_DIR}/main.cpp 37 | ${CMAKE_CURRENT_LIST_DIR}/usb_descriptors.c 38 | ${CMAKE_CURRENT_LIST_DIR}/msc_disk.c 39 | ${CMAKE_CURRENT_LIST_DIR}/fatfs_disk.c 40 | ${CMAKE_CURRENT_LIST_DIR}/flash_fs.c 41 | ${CMAKE_CURRENT_LIST_DIR}/fatfs/diskio.c 42 | ${CMAKE_CURRENT_LIST_DIR}/fatfs/ff.c 43 | ${CMAKE_CURRENT_LIST_DIR}/fatfs/ffunicode.c 44 | ${CMAKE_CURRENT_LIST_DIR}/sd_driver/crc.c 45 | ${CMAKE_CURRENT_LIST_DIR}/sd_driver/sd_card.c 46 | ${CMAKE_CURRENT_LIST_DIR}/sd_driver/sd_spi.c 47 | ${CMAKE_CURRENT_LIST_DIR}/sd_driver/spi.c 48 | # ffsystem.c is not used 49 | ) 50 | 51 | pico_generate_pio_header(a8_pico_sio ${CMAKE_CURRENT_LIST_DIR}/pin_io.pio) 52 | pico_generate_pio_header(a8_pico_sio ${CMAKE_CURRENT_LIST_DIR}/disk_counter.pio) 53 | 54 | target_include_directories(a8_pico_sio PUBLIC 55 | ${CMAKE_CURRENT_LIST_DIR} 56 | ${CMAKE_CURRENT_LIST_DIR}/fatfs 57 | ${CMAKE_CURRENT_LIST_DIR}/sd_driver 58 | ) 59 | 60 | target_link_libraries(a8_pico_sio PUBLIC pico_stdlib pico_rand pico_multicore hardware_pio hardware_uart hardware_flash tinyusb_device hardware_dma hardware_irq hardware_spi hardware_pwm hardware_dma rgbled button pico_graphics pico_display_2 st7789) 61 | 62 | pico_add_extra_outputs(a8_pico_sio) 63 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # The Makefile to generate some of the source files, you can run this before 2 | # building the project with cmake (approriate tools are needed), but the 3 | # pre-generated files are included in the project. 4 | 5 | all: 6 | xa -M disk_boot.asm -o disk_boot.bin -l disk_boot.txt 7 | python3 boot_h.py 8 | python3 atari_font_gen.py 9 | -------------------------------------------------------------------------------- /atrs/dd_dos25.atr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/atrs/dd_dos25.atr -------------------------------------------------------------------------------- /atrs/dd_mydos.atr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/atrs/dd_mydos.atr -------------------------------------------------------------------------------- /atrs/dd_sdfs.atr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/atrs/dd_sdfs.atr -------------------------------------------------------------------------------- /atrs/ed_dos25.atr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/atrs/ed_dos25.atr -------------------------------------------------------------------------------- /atrs/ed_mydos.atr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/atrs/ed_mydos.atr -------------------------------------------------------------------------------- /atrs/ed_sdfs.atr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/atrs/ed_sdfs.atr -------------------------------------------------------------------------------- /atrs/qd_mydos.atr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/atrs/qd_mydos.atr -------------------------------------------------------------------------------- /atrs/qd_sdfs.atr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/atrs/qd_sdfs.atr -------------------------------------------------------------------------------- /atrs/sd_dos25.atr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/atrs/sd_dos25.atr -------------------------------------------------------------------------------- /atrs/sd_mydos.atr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/atrs/sd_mydos.atr -------------------------------------------------------------------------------- /atrs/sd_sdfs.atr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/atrs/sd_sdfs.atr -------------------------------------------------------------------------------- /atx.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the a8-pico-sio project -- 3 | * An Atari 8-bit SIO drive and (turbo) tape emulator for 4 | * Raspberry Pi Pico, see 5 | * 6 | * https://github.com/woj76/a8-pico-sio 7 | * 8 | * For information on what / whose work it is based on, check below and the 9 | * corresponding project repository. 10 | */ 11 | 12 | /* 13 | * (Rather) modified ATX implementation from the sdrive-max project. 14 | * Original author of the ATX code dated 21/01/2018 is Daniel Noguerol 15 | * Check the sdrive-max project for details: 16 | * 17 | * https://github.com/kbr-net/sdrive-max 18 | * 19 | * The original documentation is mostly removed due to major changes in the code 20 | * (so that this one is not confused with the source one). 21 | */ 22 | 23 | #include 24 | #include "pico/time.h" 25 | #include "pico/rand.h" 26 | 27 | #include "atx.hpp" 28 | 29 | #include "mounts.hpp" 30 | #include "disk_counter.hpp" 31 | 32 | const uint16_t atx_version = 0x01; 33 | const size_t max_track = 42; 34 | 35 | const uint au_full_rotation = 26042; // number of angular units in a full disk rotation 36 | const uint us_drive_request_delay = 3220; // number of microseconds drive takes to process a request 37 | //const uint us_crc_calculation = 2000; // According to SDrive-Max 38 | const uint us_cs_calculation_1050 = 270; // According to Altirra 39 | const uint us_cs_calculation_810 = 5136; // According to Altirra 40 | const uint us_track_step_810 = 5300; // number of microseconds drive takes to step 1 track 41 | //const uint us_track_step_1050 = 12410; // According to SDrive-Max 42 | const uint us_track_step_1050 = 20120; // According to Avery / Altirra 43 | const uint us_head_settle_1050 = 20000; 44 | const uint us_head_settle_810 = 10000; 45 | const uint ms_3fake_rot_810 = 1566; 46 | const uint ms_2fake_rot_1050 = 942; 47 | const int max_retries_810 = 4; 48 | const int max_retries_1050 = 2; 49 | 50 | const uint8_t mask_fdc_busy = 0x01; 51 | const uint8_t mask_fdc_drq = 0x02; 52 | const uint8_t mask_fdc_dlost = 0x04; // mask for checking FDC status "data lost" bit 53 | const uint8_t mask_fdc_crc = 0x08; 54 | const uint8_t mask_fdc_missing = 0x10; // RNF mask for checking FDC status "missing" bit 55 | const uint8_t mask_fdc_record_type = 0x20; 56 | const uint8_t mask_fdc_write_protect = 0x40; 57 | 58 | const uint8_t mask_extended_data = 0x40; // mask for checking FDC status extended data bit 59 | const uint8_t mask_reserved = 0x80; 60 | 61 | uint8_t atx_track_size[4]; // number of sectors in each track 62 | uint8_t atx_density[4]; 63 | uint32_t gTrackInfo[4][max_track]; // pre-calculated info for each track 64 | uint8_t gCurrentHeadTrack[4]; 65 | 66 | typedef struct { 67 | absolute_time_t stamp; 68 | uint16_t angle; 69 | } head_position_t; 70 | 71 | static void getCurrentHeadPosition(head_position_t *hp) { 72 | uint64_t s = get_absolute_time(); 73 | hp->stamp = s; 74 | #ifdef PIO_DISK_COUNTER 75 | hp->angle = (uint16_t)(au_full_rotation-disk_counter-1); 76 | #else 77 | hp->angle = (uint16_t)((to_us_since_boot(s) >> 3) % au_full_rotation); 78 | #endif 79 | } 80 | 81 | // Some older approach to the rotational disk counter... 82 | /* 83 | 84 | uint16_t incAngularDisplacement(uint16_t start, uint16_t delta) { 85 | uint16_t ret = start + delta; 86 | if (ret >= au_full_rotation) 87 | ret -= au_full_rotation; 88 | return ret; 89 | } 90 | 91 | uint16_t getCurrentHeadPosition() { 92 | return (uint16_t)(au_full_rotation-disk_counter-1); 93 | } 94 | 95 | void waitForAngularPosition(uint16_t pos) { 96 | // Alternative 1 97 | while(getCurrentHeadPosition() != pos) 98 | tight_loop_contents(); 99 | 100 | // Alternative 2 101 | //int32_t to_wait = pos - getCurrentHeadPosition(); 102 | //if(to_wait < 0) 103 | // to_wait += au_full_rotation; 104 | //sleep_us(8*to_wait); 105 | 106 | } 107 | */ 108 | 109 | enum atx_density { atx_single, atx_medium, atx_double }; 110 | 111 | typedef struct __attribute__((__packed__)) { 112 | uint8_t signature[4]; 113 | uint16_t version; 114 | uint16_t minVersion; 115 | uint16_t creator; 116 | uint16_t creatorVersion; 117 | uint32_t flags; 118 | uint16_t imageType; 119 | uint8_t density; 120 | uint8_t reserved0; 121 | uint32_t imageId; 122 | uint16_t imageVersion; 123 | uint16_t reserved1; 124 | uint32_t startData; 125 | uint32_t endData; 126 | uint8_t reserved2[12]; 127 | } atxFileHeader; 128 | 129 | typedef struct __attribute__((__packed__)) { 130 | uint32_t size; 131 | uint16_t type; 132 | uint16_t reserved0; 133 | uint8_t trackNumber; 134 | uint8_t reserved1; 135 | uint16_t sectorCount; 136 | uint16_t rate; 137 | uint16_t reserved3; 138 | uint32_t flags; 139 | uint32_t headerSize; 140 | uint8_t reserved4[8]; 141 | } atxTrackHeader; 142 | 143 | typedef struct __attribute__((__packed__)) { 144 | uint32_t next; 145 | uint16_t type; 146 | uint16_t pad0; 147 | } atxSectorListHeader; 148 | 149 | typedef struct __attribute__((__packed__)) { 150 | uint8_t number; 151 | uint8_t status; 152 | uint16_t timev; 153 | uint32_t data; 154 | } atxSectorHeader; 155 | 156 | typedef struct __attribute__((__packed__)) { 157 | uint32_t size; 158 | uint8_t type; 159 | uint8_t sectorIndex; 160 | uint16_t data; 161 | } atxTrackChunk; 162 | 163 | bool loadAtxFile(FIL *fil, int atx_drive_number) { 164 | atxFileHeader *fileHeader; 165 | atxTrackHeader *trackHeader; 166 | uint bytes_read; 167 | 168 | if(f_read(fil, sector_buffer, sizeof(atxFileHeader), &bytes_read) != FR_OK || bytes_read != sizeof(atxFileHeader)) 169 | return false; 170 | 171 | fileHeader = (atxFileHeader *) sector_buffer; 172 | // The AT8X header should have been checked by now 173 | if (fileHeader->version != atx_version || fileHeader->minVersion != atx_version) 174 | return false; 175 | 176 | atx_density[atx_drive_number] = fileHeader->density; 177 | atx_track_size[atx_drive_number] = (fileHeader->density == atx_medium) ? 26 : 18; 178 | disk_headers[atx_drive_number].atr_header.sec_size = (fileHeader->density == atx_double) ? 256 : 128; 179 | gCurrentHeadTrack[atx_drive_number] = 0; 180 | 181 | uint32_t startOffset = fileHeader->startData; 182 | for (uint8_t track = 0; track < max_track; track++) { 183 | f_lseek(fil, startOffset); 184 | if(f_read(fil, sector_buffer, sizeof(atxTrackHeader), &bytes_read) != FR_OK || bytes_read != sizeof(atxTrackHeader)) 185 | break; 186 | trackHeader = (atxTrackHeader *) sector_buffer; 187 | gTrackInfo[atx_drive_number][track] = startOffset; 188 | startOffset += trackHeader->size; 189 | } 190 | return true; 191 | } 192 | 193 | int8_t transferAtxSector(int atx_drive_number, uint16_t num, uint8_t *status, bool op_write, bool op_verify) { 194 | atxTrackHeader *trackHeader; 195 | atxSectorListHeader *slHeader; 196 | atxSectorHeader *sectorHeader; 197 | atxTrackChunk *extSectorData; 198 | 199 | uint16_t i; 200 | const size_t si = 256; 201 | int8_t r = 1; 202 | 203 | uint16_t atx_sector_size = disk_headers[atx_drive_number].atr_header.sec_size; 204 | bool is1050 = (disk_headers[atx_drive_number].atr_header.temp3 & 0x40) ? false : true; 205 | 206 | // calculate track and relative sector number from the absolute sector number 207 | uint8_t tgtTrackNumber = (num - 1) / atx_track_size[atx_drive_number]; 208 | uint8_t tgtSectorNumber = (num - 1) % atx_track_size[atx_drive_number] + 1; 209 | 210 | // set initial status (in case the target sector is not found) 211 | *status = mask_fdc_missing; 212 | 213 | // delay for track stepping if needed 214 | if (gCurrentHeadTrack[atx_drive_number] != tgtTrackNumber) { 215 | int diff = tgtTrackNumber - gCurrentHeadTrack[atx_drive_number]; 216 | if (diff > 0) 217 | diff += (is1050 ? 1 : 0); 218 | else 219 | diff = -diff; 220 | sleep_us(is1050 ? (diff*us_track_step_1050 + us_head_settle_1050) : (diff*us_track_step_810 + us_head_settle_810)); 221 | } 222 | 223 | gCurrentHeadTrack[atx_drive_number] = tgtTrackNumber; 224 | 225 | 226 | // sample current head position 227 | head_position_t headPosition; 228 | getCurrentHeadPosition(&headPosition); 229 | 230 | uint16_t sectorCount = 0; 231 | 232 | // get the track header 233 | uint32_t currentFileOffset = gTrackInfo[atx_drive_number][tgtTrackNumber]; 234 | // exit, if track not present 235 | if (currentFileOffset) { 236 | if(mounted_file_transfer(atx_drive_number+1, currentFileOffset, sizeof(atxTrackHeader), false, si) == FR_OK) { 237 | trackHeader = (atxTrackHeader *)§or_buffer[si]; 238 | sectorCount = trackHeader->sectorCount; 239 | }else 240 | r = -1; 241 | } 242 | 243 | // For "healthy" ATX files the first check should probably be always fail 244 | if (trackHeader->trackNumber != tgtTrackNumber || atx_density[atx_drive_number] != ((trackHeader->flags & 0x2) ? atx_medium : atx_single)) 245 | sectorCount = 0; 246 | 247 | uint32_t track_header_size = trackHeader->headerSize; 248 | 249 | if(sectorCount) { 250 | currentFileOffset += track_header_size; 251 | if (mounted_file_transfer(atx_drive_number+1, currentFileOffset, sizeof(atxSectorListHeader), false, si) == FR_OK) { 252 | slHeader = (atxSectorListHeader *)§or_buffer[si]; 253 | // sector list header is variable length, so skip any extra header bytes that may be present 254 | currentFileOffset += slHeader->next - sectorCount * sizeof(atxSectorHeader); 255 | } else { 256 | sectorCount = 0; 257 | r = -1; 258 | } 259 | } 260 | 261 | uint32_t tgtSectorOffset; // the offset of the target sector data 262 | uint32_t writeStatusOffset; // for the write operation, remember where to update the status bit 263 | int16_t weakOffset; 264 | uint retries = is1050 ? max_retries_1050 : max_retries_810; 265 | uint32_t retryOffset = currentFileOffset; 266 | uint8_t write_status; 267 | uint16_t ext_sector_size; 268 | while (retries > 0) { 269 | retries--; 270 | currentFileOffset = retryOffset; 271 | int pTT; 272 | uint16_t tgtSectorIndex = 0; // the index of the target sector within the sector list 273 | tgtSectorOffset = 0; 274 | writeStatusOffset = 0; 275 | weakOffset = -1; 276 | write_status = mask_fdc_missing; 277 | // iterate through all sector headers to find the target sector 278 | // but read all sector headers first not to bang the media (SD card) 279 | // repeatadely (it would mess the ATX timing) 280 | if (sectorCount) { 281 | if(mounted_file_transfer(atx_drive_number+1, currentFileOffset, sectorCount*sizeof(atxSectorHeader), false, si) == FR_OK) { 282 | for (i=0; i < sectorCount; i++) { 283 | sectorHeader = (atxSectorHeader *)§or_buffer[si+i*sizeof(atxSectorHeader)]; 284 | // if the sector is not flagged as missing and its number matches the one we're looking for... 285 | if (sectorHeader->number == tgtSectorNumber) { 286 | if(sectorHeader->status & mask_fdc_missing) { 287 | write_status |= sectorHeader->status; 288 | currentFileOffset += sizeof(atxSectorHeader); 289 | continue; 290 | } 291 | // check if it's the next sector that the head would encounter angularly... 292 | int tt = sectorHeader->timev - headPosition.angle; 293 | if (!tgtSectorOffset || (tt > 0 && pTT <= 0) || (tt > 0 && pTT > 0 && tt < pTT) || (tt <= 0 && pTT <= 0 && tt < pTT)) { 294 | pTT = tt; 295 | *status = sectorHeader->status; 296 | writeStatusOffset = currentFileOffset + 1; 297 | tgtSectorIndex = i; 298 | tgtSectorOffset = sectorHeader->data; 299 | } 300 | } 301 | currentFileOffset += sizeof(atxSectorHeader); 302 | } 303 | }else 304 | r = -1; 305 | } 306 | uint16_t act_sector_size = atx_sector_size; 307 | ext_sector_size = 0; 308 | if (*status & mask_extended_data) { 309 | // NOTE! the first part of the trackHeader data (stored in sector_buffer) is by now overwritten, 310 | // but the headerSize field is far enough into the struct to stay untouched! 311 | currentFileOffset = gTrackInfo[atx_drive_number][tgtTrackNumber] + track_header_size; 312 | do { 313 | if (mounted_file_transfer(atx_drive_number+1, currentFileOffset, sizeof(atxTrackChunk), false, si) != FR_OK) { 314 | r = -1; 315 | break; 316 | } 317 | extSectorData = (atxTrackChunk *) §or_buffer[si]; 318 | if (extSectorData->size) { 319 | // if the target sector has a weak data flag, grab the start weak offset within the sector data 320 | // otherwise check for the extended sector length and update ext_sector_size accordingly 321 | if (extSectorData->sectorIndex == tgtSectorIndex) { 322 | if(extSectorData->type == 0x10) // weak sector 323 | weakOffset = extSectorData->data; 324 | else if(extSectorData->type == 0x11) { // extended sector 325 | ext_sector_size = 128 << extSectorData->data; 326 | // 1050 waits for long sectors, 810 does not 327 | if(is1050 ? (ext_sector_size > act_sector_size) : (ext_sector_size < act_sector_size)) 328 | act_sector_size = ext_sector_size; 329 | } 330 | } 331 | currentFileOffset += extSectorData->size; 332 | } 333 | } while (extSectorData->size); 334 | } 335 | if(tgtSectorOffset){ 336 | if(mounted_file_transfer(atx_drive_number+1, gTrackInfo[atx_drive_number][tgtTrackNumber] + tgtSectorOffset, atx_sector_size, op_write, 0) != FR_OK) { 337 | r = -1; 338 | tgtSectorOffset = 0; 339 | } else if(op_verify) { 340 | if(mounted_file_transfer(atx_drive_number+1, gTrackInfo[atx_drive_number][tgtTrackNumber] + tgtSectorOffset, atx_sector_size, false, si) != FR_OK) { 341 | tgtSectorOffset = 0; 342 | r = -1; 343 | }else if(memcmp(sector_buffer, §or_buffer[si], atx_sector_size)) 344 | tgtSectorOffset = 0; 345 | } 346 | 347 | // This calculation is an educated guess based on all the different ATX implementations 348 | uint16_t au_one_sector_read = (23+act_sector_size)*(atx_density[atx_drive_number] == atx_single ? 8 : 4)+2; 349 | // We will need to circulate around the disk one more time if we are re-reading the just written sector 350 | if(op_verify) 351 | au_one_sector_read += au_full_rotation; 352 | sleep_until(delayed_by_us(headPosition.stamp, (au_one_sector_read + pTT + (pTT > 0 ? 0 : au_full_rotation))*8)); 353 | 354 | if(*status) 355 | // This is according to Altirra, but it breaks DjayBee's test J in 1050 mode?! 356 | //sleep_us(is1050 ? (us_track_step_1050+us_head_settle_1050) : (au_full_rotation*8)); 357 | // This is what seems to work: 358 | sleep_us(au_full_rotation*8); 359 | } else { 360 | // No matching sector found at all or the track does not match the disk density 361 | sleep_until(delayed_by_ms(headPosition.stamp, is1050 ? ms_2fake_rot_1050 : ms_3fake_rot_810)); 362 | if(is1050 || retries == 2) { 363 | // Repositioning the head for the target track 364 | if(!is1050) 365 | sleep_us((43+tgtTrackNumber)*us_track_step_810+us_head_settle_810); 366 | else if(tgtTrackNumber) 367 | sleep_us((2*tgtTrackNumber+1)*us_track_step_1050+us_head_settle_1050); 368 | } 369 | } 370 | 371 | getCurrentHeadPosition(&headPosition); 372 | 373 | if(!*status || (op_write && tgtSectorOffset) || r < 0) 374 | break; 375 | } 376 | 377 | *status &= ~(mask_reserved | mask_extended_data); 378 | 379 | if(op_write) { 380 | if(tgtSectorOffset) 381 | *status &= ~(mask_fdc_crc | mask_fdc_record_type); 382 | else 383 | *status = write_status & ~(mask_reserved | mask_extended_data); 384 | } else { 385 | if (*status & mask_fdc_dlost) { 386 | if(is1050) 387 | *status |= mask_fdc_drq; 388 | else { 389 | *status &= ~(mask_fdc_dlost | mask_fdc_crc); 390 | *status |= mask_fdc_busy; 391 | } 392 | } 393 | if(!is1050 && (*status & mask_fdc_record_type)) 394 | *status |= mask_fdc_write_protect; 395 | } 396 | 397 | if (tgtSectorOffset && !*status && r >= 0) 398 | r = 0; 399 | 400 | if(!op_write) { 401 | // if a weak offset is defined, randomize the appropriate data 402 | if (weakOffset > -1) 403 | for (i = (uint16_t) weakOffset; i < atx_sector_size; i++) 404 | sector_buffer[i] = (uint8_t) get_rand_32(); 405 | sleep_until(delayed_by_us(headPosition.stamp, is1050 ? us_cs_calculation_1050 : us_cs_calculation_810)); 406 | // This is probably equivalent in this case, some testing still needs to be done 407 | // to see which one works better for both the internal Flash and SD cards. 408 | //sleep_us(is1050 ? us_cs_calculation_1050 : us_cs_calculation_810); 409 | }else if(tgtSectorOffset) { 410 | if(writeStatusOffset) { 411 | sector_buffer[si] = *status; 412 | if(mounted_file_transfer(atx_drive_number+1, writeStatusOffset, 1, true, si) != FR_OK) { 413 | r = -1; 414 | ext_sector_size = 0; 415 | } 416 | } 417 | if(ext_sector_size > atx_sector_size) 418 | ext_sector_size = ext_sector_size - atx_sector_size; 419 | else 420 | ext_sector_size = 0; 421 | if((*status & mask_fdc_dlost) && ext_sector_size) { 422 | memset(§or_buffer[si], 0xFF, 128); 423 | currentFileOffset = gTrackInfo[atx_drive_number][tgtTrackNumber] + tgtSectorOffset + atx_sector_size; 424 | while(ext_sector_size) { 425 | if(mounted_file_transfer(atx_drive_number+1, currentFileOffset, 128, true, si) != FR_OK) { 426 | r = -1; 427 | break; 428 | } 429 | currentFileOffset += 128; 430 | ext_sector_size -= 128; 431 | } 432 | } 433 | } 434 | 435 | // the Atari expects an inverted FDC status byte 436 | *status = ~(*status); 437 | 438 | return r; 439 | } 440 | -------------------------------------------------------------------------------- /atx.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the a8-pico-sio project -- 3 | * An Atari 8-bit SIO drive and (turbo) tape emulator for 4 | * Raspberry Pi Pico, see 5 | * 6 | * https://github.com/woj76/a8-pico-sio 7 | * 8 | * For information on what / whose work it is based on, check below and the 9 | * corresponding project repository. 10 | */ 11 | 12 | /* See atx.cpp for credits and other info. */ 13 | 14 | #pragma once 15 | 16 | #include "config.h" 17 | 18 | #include "ff.h" 19 | 20 | extern const uint au_full_rotation; 21 | extern const uint us_drive_request_delay; 22 | extern uint8_t atx_track_size[]; 23 | 24 | bool loadAtxFile(FIL *fil, int atx_drive_number); 25 | int8_t transferAtxSector(int atx_drive_number, uint16_t num, uint8_t *status, bool op_write = false, bool op_verify = false); 26 | -------------------------------------------------------------------------------- /board/Gerber-PCB-pico-sio-board-ver-1-1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/board/Gerber-PCB-pico-sio-board-ver-1-1.zip -------------------------------------------------------------------------------- /board/Schematic_pico-sio-board_2024-10-12.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/board/Schematic_pico-sio-board_2024-10-12.pdf -------------------------------------------------------------------------------- /boot_h.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | f = open("disk_boot.bin", "rb") 4 | d = f.read() 5 | f.close() 6 | 7 | s = "/* THIS FILE IS AUTOMATICALLY GENERATED, DO NOT EDIT! */\n\n" 8 | 9 | s += f"#define xex_loader_len {len(d)}\n\nconst uint8_t boot_loader[xex_loader_len] = {{\n\t" 10 | 11 | i = 0 12 | 13 | for c in d: 14 | s += f"0x{c:02X}," 15 | i += 1 16 | if i % 16 == 0: 17 | s += "\n\t" 18 | s = s[:-3] 19 | s += "\n};\n\nconst uint16_t boot_reloc_locs[] = {" 20 | 21 | f = open("disk_boot.txt", "rt") 22 | d = f.read().split("\n") 23 | f.close() 24 | 25 | cnt = 0; 26 | for x in d: 27 | if x[:5] == "reloc": 28 | s += f"0x{int(x[12:15],16)-0x700:03X}," 29 | cnt += 1 30 | 31 | s = s[:-1]+f"}};\n\n#define boot_reloc_locs_size {cnt}\n\n" 32 | 33 | f = open("boot_loader.h","wt") 34 | f.write(s) 35 | f.close(); 36 | -------------------------------------------------------------------------------- /boot_loader.h: -------------------------------------------------------------------------------- 1 | /* THIS FILE IS AUTOMATICALLY GENERATED, DO NOT EDIT! */ 2 | 3 | #define xex_loader_len 384 4 | 5 | const uint8_t boot_loader[xex_loader_len] = { 6 | 0x00,0x03,0x00,0x07,0x06,0x07,0x20,0xDF,0x07,0xA9,0x00,0x8D,0x44,0x02,0x8D,0x7F, 7 | 0x08,0x8D,0x00,0x07,0xA0,0x7F,0x99,0x80,0x00,0x88,0x10,0xFA,0xA9,0x01,0x8D,0x7D, 8 | 0x08,0xA9,0x71,0x8D,0x7E,0x08,0x20,0xBF,0x07,0x30,0x5F,0x85,0x44,0x20,0xBF,0x07, 9 | 0x30,0x58,0x85,0x45,0x25,0x44,0xC9,0xFF,0xF0,0xEC,0x20,0xBF,0x07,0x30,0x4B,0x85, 10 | 0x46,0x20,0xBF,0x07,0x30,0x44,0x85,0x47,0xA9,0x00,0xD0,0x0D,0xEE,0x49,0x07,0xA5, 11 | 0x44,0x8D,0xE0,0x02,0xA5,0x45,0x8D,0xE1,0x02,0xA9,0xDE,0x8D,0xE2,0x02,0xA9,0x07, 12 | 0x8D,0xE3,0x02,0x20,0xBF,0x07,0x30,0x22,0xA0,0x00,0x91,0x44,0xA4,0x44,0xA5,0x45, 13 | 0xE6,0x44,0xD0,0x02,0xE6,0x45,0xC4,0x46,0xE5,0x47,0x90,0xE7,0xA9,0x07,0x48,0xA9, 14 | 0x25,0x48,0xA9,0x03,0x8D,0x0F,0xD2,0x6C,0xE2,0x02,0xA9,0x03,0x8D,0x0F,0xD2,0x6C, 15 | 0xE0,0x02,0xAD,0x7D,0x08,0xAC,0x7E,0x08,0xD0,0x04,0xC9,0x00,0xF0,0x1E,0x8C,0x0A, 16 | 0x03,0x8D,0x0B,0x03,0xA9,0x00,0x8D,0x7D,0x08,0x8D,0x7E,0x08,0x8E,0x02,0x03,0xA9, 17 | 0x08,0x8D,0x05,0x03,0xA9,0x00,0x8D,0x04,0x03,0x4C,0x53,0xE4,0xA0,0x88,0x60,0xAC, 18 | 0x00,0x07,0xCC,0x7F,0x08,0x90,0x0E,0xA2,0x52,0x20,0x92,0x07,0x30,0x10,0xAC,0x7F, 19 | 0x08,0xF0,0xE9,0xA0,0x00,0xB9,0x00,0x08,0xC8,0x8C,0x00,0x07,0xA0,0x01,0x60,0xA9, 20 | 0xC0,0xC5,0x6A,0xF0,0x21,0x85,0x6A,0x8D,0xE4,0x02,0xAD,0x01,0xD3,0x09,0x02,0x8D, 21 | 0x01,0xD3,0xA9,0x01,0x8D,0xF8,0x03,0xA2,0x02,0x20,0xFE,0x07,0xA2,0x00,0xBD,0x01, 22 | 0xE4,0x48,0xBD,0x00,0xE4,0x48,0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 23 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 24 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 25 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 26 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 27 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 28 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 29 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 30 | }; 31 | 32 | const uint16_t boot_reloc_locs[] = {0x003,0x005,0x008,0x010,0x013,0x020,0x025,0x028,0x02F,0x03C,0x043,0x04E,0x05F,0x065,0x07D,0x094,0x097,0x0A8,0x0AB,0x0B0,0x0C1,0x0C4,0x0CB,0x0D0,0x0D7,0x0DB,0x0FB}; 33 | 34 | #define boot_reloc_locs_size 27 35 | 36 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Relocate the Pimorioni Display A/B/X/Y button pins to GPIO 4 | // 0, 1, 2, 3. This is to free SPI1 pins for the SD card and the 5 | // carrier board is needed for that to work (or an equivalent Pico 6 | // to display pack rewiring) 7 | 8 | #define RELOCATE_BUTTON_PINS 9 | 10 | // State of the pin to detect motor on, should be 1, so do not change it. 11 | // (The 0 option is for an earlier design that incorporated a NOT gate 12 | // on the carries board, the option is kept in case that ever needs to 13 | // be revived.) 14 | 15 | #define MOTOR_ON_STATE 1u 16 | 17 | // Use this (default) to support loading of WAV files with 96000 sample rate. 18 | // This will overclock Pico1 boards (Pico2 seems to handle them fine with 19 | // the stock clock) and increase the file reading buffer size. 20 | 21 | #define WAV_96K 22 | 23 | // Use the PIO based emulated disk rotational counter for the ATX support 24 | // (This is more of a PIO programming exercise rather than anything else) 25 | //#define PIO_DISK_COUNTER 26 | 27 | // Give core1 more computing priority (not really needed) 28 | //#define CORE1_PRIORITY 29 | 30 | // This will make the PIO machine clock at the full board speed, this should (?) 31 | // provide more accurate (real) SIO timing for tape emulation, however, at 32 | // a slower speed it also works well, if not better according to many tests 33 | // I have done. 34 | 35 | //#define FULL_SPEED_PIO 36 | 37 | // Additional delay for emulating the motor stopping and starting lag (applies 38 | // to WAV files only). This was introduced in the attempt to make some "senstive" 39 | // WAV files to load better, but in the end it does not seem to have much effect, 40 | // left here for possible future use. Value 0 means no delay, otherwise both 41 | // are in 10ms units of time (defined by MOTOR_CHECK_INTERVAL_MS in io.hpp) to 42 | // define the delay. 43 | 44 | #define MOTOR_OFF_DELAY 0 // 500 ms 45 | #define MOTOR_ON_DELAY 0 // 500 ms 46 | 47 | // This changes colors for the device with at TFT screen that Zaxon of 48 | // atarionline.pl has built. 49 | 50 | //#define ZAXON_DEVICE 51 | 52 | // The card detection GPIO pin (9) state when the card is present, depending on 53 | // the sd card reader type and/or device circuitry might need to be 0. 54 | 55 | #define SD_DETECTED_GPIO_STATE 1 56 | -------------------------------------------------------------------------------- /disk_boot.asm: -------------------------------------------------------------------------------- 1 | ; This is a modified version of tiny xdos from the Numen demo 2 | ; found at https://github.com/pfusik/numen/blob/master/dos.asx 3 | ; See the original repository for credits. 4 | 5 | SECTOR_SIZE = 128 ; SD/ED 6 | 7 | #define BASIC_CHECK 8 | 9 | coldst = $244 10 | runad = $2e0 11 | initad = $2e2 12 | dcomnd = $302 13 | dbuflo = $304 14 | dbufhi = $305 15 | daux1 = $30a 16 | daux2 = $30b 17 | dskinv = $e453 18 | skctl = $d20f 19 | vcount = $d40b 20 | 21 | boot_flag = $09 22 | load_ptr = $44 23 | load_end = $46 24 | 25 | boot_start = $700 26 | 27 | buffer = $800 28 | 29 | *=boot_start 30 | 31 | buffer_ofs 32 | 33 | ; .byte 'F',2 34 | .byte 0,boot_blocks+1 ; Note! this does not work when the loader size 35 | ; divided evenly over 128 blocks 36 | .word boot_start : reloc01 = *-1 37 | ; .word $e477 ;?!?!? Relevant when using CASINI only 38 | .word boot_init : reloc24 = *-1 39 | 40 | boot_init 41 | #ifdef BASIC_CHECK 42 | jsr basic_check : reloc25 = *-1 43 | #endif 44 | lda #0 : sta coldst 45 | sta buffer+SECTOR_SIZE-1 : reloc02 = *-1 46 | sta buffer_ofs : reloc03 = *-1 47 | ldy #$7F 48 | clear_zp_loop: 49 | sta $80,y : dey : bpl clear_zp_loop 50 | lda #$01 : sta buffer+SECTOR_SIZE-3 : reloc04 = *-1 51 | ; sta boot_flag 52 | lda #$71 : sta buffer+SECTOR_SIZE-2 : reloc05 = *-1 53 | 54 | load_1 55 | jsr read : reloc06 = *-1 : bmi load_run : sta load_ptr 56 | jsr read : reloc07 = *-1 : bmi load_run : sta load_ptr+1 57 | and load_ptr : cmp #$ff : beq load_1 58 | jsr read : reloc08 = *-1 : bmi load_run : sta load_end 59 | jsr read : reloc09 = *-1 : bmi load_run : sta load_end+1 60 | lda #0 : runad_ready = *-1 : bne load_1_1 61 | inc runad_ready : reloc27 = *-1 62 | lda load_ptr : sta runad : lda load_ptr+1 : sta runad+1 63 | load_1_1 64 | lda #read_ret : reloc10 = *-1 : sta initad+1 66 | load_2 67 | jsr read : reloc11 = *-1 : bmi load_run 68 | ldy #$00 69 | sta (load_ptr),y 70 | ldy load_ptr 71 | lda load_ptr+1 72 | inc load_ptr 73 | bne *+4 74 | inc load_ptr+1 75 | cpy load_end 76 | sbc load_end+1 77 | bcc load_2 78 | lda #>(load_1-1) : reloc12 = *-1 : pha 79 | lda #<(load_1-1) : pha 80 | lda #$03 : sta skctl 81 | jmp (initad) 82 | load_run 83 | lda #$03 : sta skctl 84 | jmp (runad) 85 | 86 | sio_next 87 | lda buffer+SECTOR_SIZE-3 : reloc13 = *-1 : ldy buffer+SECTOR_SIZE-2 : reloc14 = *-1 88 | bne sio_sector 89 | cmp #0 90 | beq eof 91 | sio_sector 92 | sty daux1 : sta daux2 93 | lda #0 : sta buffer+SECTOR_SIZE-3 : reloc15 = *-1 : sta buffer+SECTOR_SIZE-2 : reloc16 = *-1 94 | sio_command 95 | stx dcomnd 96 | lda #>buffer : reloc17 = *-1 : sta dbufhi 97 | lda # 13 | */ 14 | 15 | #include "disk_counter.hpp" 16 | 17 | #ifdef PIO_DISK_COUNTER 18 | 19 | #include "hardware/clocks.h" 20 | #include "hardware/pio.h" 21 | #include "hardware/dma.h" 22 | #include "hardware/irq.h" 23 | 24 | #include "atx.hpp" 25 | 26 | #include "disk_counter.pio.h" 27 | 28 | #define disk_pio pio1 29 | 30 | volatile uint32_t disk_counter; 31 | 32 | static int disk_dma_channel; 33 | 34 | static void disk_dma_handler() { 35 | if(dma_hw->ints0 & (1u << disk_dma_channel)) { 36 | dma_hw->ints0 = 1u << disk_dma_channel; 37 | dma_channel_start(disk_dma_channel); 38 | } 39 | } 40 | 41 | void init_disk_counter() { 42 | uint disk_pio_offset = pio_add_program(disk_pio, &disk_counter_program); 43 | float disk_clk_divider = (float)clock_get_hz(clk_sys)/1000000; 44 | uint disk_sm = pio_claim_unused_sm(disk_pio, true); 45 | pio_sm_config disk_sm_config = disk_counter_program_get_default_config(disk_pio_offset); 46 | sm_config_set_clkdiv(&disk_sm_config, disk_clk_divider); 47 | sm_config_set_in_shift(&disk_sm_config, true, false, 32); 48 | sm_config_set_out_shift(&disk_sm_config, true, true, 32); 49 | pio_sm_init(disk_pio, disk_sm, disk_pio_offset, &disk_sm_config); 50 | 51 | disk_dma_channel = dma_claim_unused_channel(true); 52 | dma_channel_config disk_dma_c = dma_channel_get_default_config(disk_dma_channel); 53 | channel_config_set_transfer_data_size(&disk_dma_c, DMA_SIZE_32); 54 | channel_config_set_read_increment(&disk_dma_c, false); 55 | channel_config_set_write_increment(&disk_dma_c, false); 56 | channel_config_set_dreq(&disk_dma_c, pio_get_dreq(disk_pio, disk_sm, false)); 57 | pio_sm_set_enabled(disk_pio, disk_sm, true); 58 | dma_channel_set_irq0_enabled(disk_dma_channel, true); 59 | 60 | irq_add_shared_handler(DMA_IRQ_0, disk_dma_handler, PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY); 61 | irq_set_enabled(DMA_IRQ_0, true); 62 | 63 | // Pico 2 does not like the "full" 0x80000000 for transfer counter, the core freezes 64 | dma_channel_configure(disk_dma_channel, &disk_dma_c, &disk_counter, &disk_pio->rxf[disk_sm], 0x8000000, true); 65 | // dma_channel_configure(disk_dma_channel, &disk_dma_c, &disk_counter, &disk_pio->rxf[disk_sm], 1, true); 66 | 67 | disk_pio->txf[disk_sm] = (au_full_rotation-1); // Start the first iteration of the counter 68 | } 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /disk_counter.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the a8-pico-sio project -- 3 | * An Atari 8-bit SIO drive and (turbo) tape emulator for 4 | * Raspberry Pi Pico, see 5 | * 6 | * https://github.com/woj76/a8-pico-sio 7 | * 8 | * For information on what / whose work it is based on, check the corresponding 9 | * source files and the README file. This file is licensed under GNU General 10 | * Public License 3.0 or later. 11 | * 12 | * Copyright (C) 2025 Wojciech Mostowski 13 | */ 14 | 15 | #pragma once 16 | 17 | #include "config.h" 18 | 19 | #ifdef PIO_DISK_COUNTER 20 | 21 | extern volatile uint32_t disk_counter; 22 | 23 | void init_disk_counter(); 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /disk_counter.pio: -------------------------------------------------------------------------------- 1 | ; This file is part of the a8-pico-sio project -- 2 | ; An Atari 8-bit SIO drive and (turbo) tape emulator for 3 | ; Raspberry Pi Pico, see 4 | ; 5 | ; https://github.com/woj76/a8-pico-sio 6 | ; 7 | ; For information on what / whose work it is based on, check the corresponding 8 | ; source files and the README file. This file is licensed under GNU General 9 | ; Public License 3.0 or later. 10 | ; 11 | ; Copyright (C) 2025 Wojciech Mostowski 12 | 13 | .program disk_counter 14 | out y,32 ; provide 26041 here as a uint32_t 15 | restart: 16 | mov x,y 17 | jmp do_count 18 | loop: 19 | nop [2] 20 | do_count: 21 | mov isr,x 22 | push [2] 23 | jmp x--, loop 24 | jmp restart 25 | -------------------------------------------------------------------------------- /disk_images_data.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the a8-pico-sio project -- 3 | * An Atari 8-bit SIO drive and (turbo) tape emulator for 4 | * Raspberry Pi Pico, see 5 | * 6 | * https://github.com/woj76/a8-pico-sio 7 | * 8 | * For information on what / whose work it is based on, check the corresponding 9 | * source files and the README file. This file is licensed under GNU General 10 | * Public License 3.0 or later. 11 | * 12 | * Copyright (C) 2025 Wojciech Mostowski 13 | */ 14 | 15 | const uint8_t dummy_boot[] = { 16 | 0x00, 0x03, 0x00, 0x07, 0xB2, 0x07, 0x4C, 0x5E, 0x07, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 17 | 0x00, 0x7D, 0x00, 0x00, 0x43, 0x61, 0x6E, 0x6E, 0x6F, 0x74, 0x20, 0x62, 0x6F, 0x6F, 0x74, 0x20, 18 | 0x73, 0x69, 0x6E, 0x63, 0x65, 0x20, 0x74, 0x68, 0x65, 0x72, 0x65, 0x20, 0x69, 0x73, 0x20, 0x6E, 19 | 0x6F, 0x20, 0x44, 0x4F, 0x53, 0x9B, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6E, 0x74, 0x20, 0x6F, 0x6E, 20 | 0x20, 0x74, 0x68, 0x69, 0x73, 0x20, 0x64, 0x69, 0x73, 0x6B, 0x2E, 0x20, 0x2D, 0x70, 0x72, 0x65, 21 | 0x73, 0x73, 0x20, 0x61, 0x6E, 0x79, 0x20, 0x6B, 0x65, 0x79, 0x2D, 0x9C, 0x1C, 0x9C, 0xA2, 0x00, 22 | 0xA9, 0x14, 0x8D, 0x44, 0x03, 0xA9, 0x07, 0x8D, 0x45, 0x03, 0xA9, 0x47, 0x8D, 0x48, 0x03, 0xA9, 23 | 0x00, 0x8D, 0x49, 0x03, 0xA9, 0x0B, 0x20, 0xB4, 0x07, 0xA2, 0x10, 0xA9, 0xBA, 0x9D, 0x44, 0x03, 24 | 0xA9, 0x07, 0x9D, 0x45, 0x03, 0xA9, 0x04, 0x9D, 0x4A, 0x03, 0xA9, 0x03, 0x20, 0xB4, 0x07, 0xA9, 25 | 0x00, 0x9D, 0x48, 0x03, 0x9D, 0x49, 0x03, 0xA9, 0x07, 0x20, 0xB4, 0x07, 0xA9, 0x0C, 0x20, 0xB4, 26 | 0x07, 0xA2, 0x00, 0xA9, 0x5B, 0x9D, 0x44, 0x03, 0xA9, 0x03, 0x9D, 0x48, 0x03, 0xA9, 0x0B, 0x20, 27 | 0xB4, 0x07, 0x38, 0x60, 0x9D, 0x42, 0x03, 0x4C, 0x56, 0xE4, 0x4B, 0x9B 28 | }; 29 | const uint dummy_boot_len = 0xBC; 30 | 31 | const uint32_t image_sizes[] = { 92176, 133136, 183952, 368272 }; 32 | 33 | const uint8_t sd_empty_disk[] = { 0x05, 0x00, 0x00, 0x80, 0x96, 0x02, 0x80, 0x16, 0x80, 0x0B, 0x68, 0x01, 0x00, 0x00 }; 34 | const uint sd_empty_disk_len = 0x0E; 35 | 36 | const uint8_t ed_empty_disk[] = { 0x05, 0x00, 0x00, 0x80, 0x96, 0x02, 0x80, 0x20, 0x80, 0x0B, 0x08, 0x02, 0x00, 0x00 }; 37 | const uint ed_empty_disk_len = 0x0E; 38 | 39 | const uint8_t dd_empty_disk[] = { 0x06, 0x00, 0x00, 0x80, 0x96, 0x02, 0xE8, 0x2C, 0x00, 0x01, 0x8A, 0xCE, 0x02, 0x00, 0x00 }; 40 | const uint dd_empty_disk_len = 0x0F; 41 | 42 | const uint8_t qd_empty_disk[] = { 0x06, 0x00, 0x00, 0x80, 0x96, 0x02, 0xE8, 0x59, 0x00, 0x01, 0x8A, 0x9E, 0x05, 0x00, 0x00 }; 43 | const uint qd_empty_disk_len = 0x0F; 44 | 45 | const uint8_t qd_mydos_disk[] = { 46 | 0x06, 0x00, 0x00, 0x80, 0x96, 0x02, 0xE8, 0x59, 0x00, 0x01, 0x8A, 0x65, 0x01, 0x00, 0x00, 0x05, 47 | 0x00, 0x00, 0x80, 0x03, 0x94, 0x05, 0x94, 0x05, 0x05, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 48 | 0x00, 0x0F, 0x2C, 0x00, 0x00, 0x00, 0xFF, 0x02, 0x00, 0x00, 0x80, 0x00, 0x7F, 0x85, 0x00, 0x00, 49 | 0x00, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x80, 0x41, 0x38, 0x04, 0x00, 0x00 50 | }; 51 | const uint qd_mydos_disk_len = 60; 52 | 53 | const uint8_t qd_sparta_disk[] = { 54 | 0x06, 0x00, 0x00, 0x80, 0x96, 0x02, 0xE8, 0x59, 0x00, 0x01, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x26, 55 | 0x00, 0x00, 0x80, 0x03, 0x00, 0x30, 0x40, 0x07, 0x4C, 0x80, 0x30, 0x05, 0x00, 0xA0, 0x05, 0x9A, 56 | 0x05, 0x01, 0x04, 0x00, 0x45, 0x00, 0x45, 0x00, 0x4E, 0x45, 0x57, 0x44, 0x49, 0x53, 0x4B, 0x20, 57 | 0x28, 0x00, 0x21, 0x00, 0x01, 0x7E, 0x00, 0x01, 0xEC, 0x19, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 58 | 0x00, 0x00, 0x60, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x80, 0xA9, 0x60, 0x8D, 0x40, 59 | 0x07, 0x38, 0x60, 0xF9, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0xB3, 0x00, 0x00, 60 | 0x00, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x80, 0x4F, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 61 | 0x06, 0xFB, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x80, 0x28, 0x00, 0x00, 0x17, 0x00, 0x00, 62 | 0x4D, 0x41, 0x49, 0x4E, 0x07, 0x00, 0x00, 0x00, 0x20, 0xEF, 0x9A, 0x05, 0x00, 0x00 63 | }; 64 | const uint qd_sparta_disk_len = 142; 65 | 66 | const uint8_t sd_dos_disk[] = { 67 | 0x05, 0x00, 0x00, 0x80, 0x96, 0x02, 0x80, 0x16, 0x80, 0x8B, 0xB3, 0x00, 0x00, 0x00, 0x05, 0x00, 68 | 0x00, 0x80, 0x02, 0xC3, 0x02, 0xC3, 0x02, 0x05, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 69 | 0x0F, 0x2C, 0x00, 0x00, 0x00, 0xFF, 0x02, 0x00, 0x00, 0x80, 0x00, 0x7F, 0x2B, 0x00, 0x00, 0x00, 70 | 0xFF, 0x1C, 0xB4, 0x00, 0x00, 0x00 71 | }; 72 | const uint sd_dos_disk_len = 54; 73 | 74 | const uint8_t sd_mydos_disk[] = { 75 | 0x05, 0x00, 0x00, 0x80, 0x96, 0x02, 0x80, 0x16, 0x80, 0x8B, 0xB3, 0x00, 0x00, 0x00, 0x05, 0x00, 76 | 0x00, 0x80, 0x02, 0xC4, 0x02, 0xC4, 0x02, 0x05, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 77 | 0x0F, 0x2C, 0x00, 0x00, 0x00, 0xFF, 0x02, 0x00, 0x00, 0x80, 0x00, 0x7F, 0x2B, 0x00, 0x00, 0x00, 78 | 0xFF, 0x01, 0x00, 0x00, 0x00, 0x80, 0x1B, 0xB4, 0x00, 0x00, 0x00 79 | }; 80 | const uint sd_mydos_disk_len = 59; 81 | 82 | const uint8_t sd_sparta_disk[] = { 83 | 0x05, 0x00, 0x00, 0x80, 0x96, 0x02, 0x80, 0x16, 0x80, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x26, 0x00, 84 | 0x00, 0x80, 0x03, 0x00, 0x30, 0x40, 0x07, 0x4C, 0x80, 0x30, 0x05, 0x00, 0xD0, 0x02, 0xCA, 0x02, 85 | 0x01, 0x04, 0x00, 0x45, 0x00, 0x45, 0x00, 0x4E, 0x45, 0x57, 0x44, 0x49, 0x53, 0x4B, 0x20, 0x28, 86 | 0x80, 0x21, 0x80, 0x00, 0x3E, 0x00, 0x01, 0x0A, 0x19, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 87 | 0x00, 0x60, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x80, 0xA9, 0x60, 0x8D, 0x40, 0x07, 88 | 0x38, 0x60, 0xF9, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x59, 0x00, 0x00, 0x00, 89 | 0xFF, 0x01, 0x00, 0x00, 0x00, 0x80, 0x29, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 90 | 0x7B, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x80, 0x28, 0x00, 0x00, 0x17, 0x00, 0x00, 0x4D, 91 | 0x41, 0x49, 0x4E, 0x07, 0x00, 0x00, 0x00, 0x20, 0x6F, 0x65, 0x01, 0x00, 0x00 92 | }; 93 | const uint sd_sparta_disk_len = 141; 94 | 95 | const uint8_t ed_dos_disk[] = { 96 | 0x05, 0x00, 0x00, 0x80, 0x96, 0x02, 0x80, 0x20, 0x80, 0x8B, 0xB3, 0x00, 0x00, 0x00, 0x05, 0x00, 97 | 0x00, 0x80, 0x02, 0xC3, 0x02, 0xC3, 0x02, 0x05, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 98 | 0x0F, 0x2C, 0x00, 0x00, 0x00, 0xFF, 0x02, 0x00, 0x00, 0x80, 0x00, 0x7F, 0x2B, 0x00, 0x00, 0x00, 99 | 0xFF, 0x9C, 0x4B, 0x01, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0xFF, 0x02, 0x00, 0x00, 0x80, 0x00, 100 | 0x7F, 0x2B, 0x00, 0x00, 0x00, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x7F, 0x25, 0x00, 0x00, 0x00, 0xFF, 101 | 0x02, 0x00, 0x00, 0x80, 0x2F, 0x01, 0x04, 0x08, 0x00, 0x00, 0x00 102 | }; 103 | const uint ed_dos_disk_len = 91; 104 | 105 | const uint8_t ed_mydos_disk[] = { 106 | 0x05, 0x00, 0x00, 0x80, 0x96, 0x02, 0x80, 0x20, 0x80, 0x0B, 0xB3, 0x00, 0x00, 0x00, 0x0C, 0x00, 107 | 0x00, 0x00, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x80, 0x73, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 108 | 0x80, 0x03, 0x03, 0x04, 0x03, 0x04, 0x05, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0F, 109 | 0x2B, 0x00, 0x00, 0x00, 0xFF, 0x03, 0x00, 0x00, 0x80, 0xFE, 0x00, 0x7F, 0x47, 0x00, 0x00, 0x00, 110 | 0xFF, 0x00, 0x54, 0x01, 0x00, 0x00 111 | }; 112 | const uint ed_mydos_disk_len = 70; 113 | 114 | const uint8_t ed_sparta_disk[] = { 115 | 0x05, 0x00, 0x00, 0x80, 0x96, 0x02, 0x80, 0x20, 0x80, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x26, 0x00, 116 | 0x00, 0x80, 0x03, 0x00, 0x30, 0x40, 0x07, 0x4C, 0x80, 0x30, 0x06, 0x00, 0x10, 0x04, 0x09, 0x04, 117 | 0x02, 0x04, 0x00, 0x45, 0x00, 0x45, 0x00, 0x4E, 0x45, 0x57, 0x44, 0x49, 0x53, 0x4B, 0x20, 0x28, 118 | 0x80, 0x21, 0x80, 0x00, 0x3E, 0x00, 0x01, 0x1C, 0x19, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 119 | 0x00, 0x60, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x80, 0xA9, 0x60, 0x8D, 0x40, 0x07, 120 | 0x38, 0x60, 0xFA, 0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00, 0xFF, 0x01, 0x00, 0x00, 0x00, 121 | 0x80, 0x81, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x07, 0x7B, 0x00, 0x00, 0x00, 0x00, 122 | 0x0A, 0x00, 0x00, 0x80, 0x28, 0x00, 0x00, 0x17, 0x00, 0x00, 0x4D, 0x41, 0x49, 0x4E, 0x07, 0x00, 123 | 0x00, 0x00, 0x20, 0xEF, 0x04, 0x02, 0x00, 0x00 124 | }; 125 | const uint ed_sparta_disk_len = 136; 126 | 127 | const uint8_t dd_dos_disk[] = { 128 | 0x06, 0x00, 0x00, 0x80, 0x96, 0x02, 0xE8, 0x2C, 0x00, 0x01, 0x8A, 0x65, 0x01, 0x00, 0x00, 0x05, 129 | 0x00, 0x00, 0x80, 0x02, 0xC3, 0x02, 0xC3, 0x02, 0x05, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 130 | 0x00, 0x0F, 0x2C, 0x00, 0x00, 0x00, 0xFF, 0x02, 0x00, 0x00, 0x80, 0x00, 0x7F, 0x2B, 0x00, 0x00, 131 | 0x00, 0xFF, 0x9C, 0x68, 0x01, 0x00, 0x00 132 | }; 133 | const uint dd_dos_disk_len = 55; 134 | 135 | const uint8_t dd_mydos_disk[] = { 136 | 0x06, 0x00, 0x00, 0x80, 0x96, 0x02, 0xE8, 0x2C, 0x00, 0x01, 0x8A, 0x65, 0x01, 0x00, 0x00, 0x05, 137 | 0x00, 0x00, 0x80, 0x02, 0xC4, 0x02, 0xC4, 0x02, 0x05, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 138 | 0x00, 0x0F, 0x2C, 0x00, 0x00, 0x00, 0xFF, 0x02, 0x00, 0x00, 0x80, 0x00, 0x7F, 0x2B, 0x00, 0x00, 139 | 0x00, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x80, 0x9B, 0x68, 0x01, 0x00, 0x00 140 | }; 141 | const uint dd_mydos_disk_len = 60; 142 | 143 | const uint8_t dd_sparta_disk[] = { 144 | 0x06, 0x00, 0x00, 0x80, 0x96, 0x02, 0xE8, 0x2C, 0x00, 0x01, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x26, 145 | 0x00, 0x00, 0x80, 0x03, 0x00, 0x30, 0x40, 0x07, 0x4C, 0x80, 0x30, 0x05, 0x00, 0xD0, 0x02, 0xCA, 146 | 0x02, 0x01, 0x04, 0x00, 0x45, 0x00, 0x45, 0x00, 0x4E, 0x45, 0x57, 0x44, 0x49, 0x53, 0x4B, 0x20, 147 | 0x28, 0x00, 0x21, 0x00, 0x01, 0x7E, 0x00, 0x01, 0x6C, 0x19, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 148 | 0x00, 0x00, 0x60, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x80, 0xA9, 0x60, 0x8D, 0x40, 149 | 0x07, 0x38, 0x60, 0xF9, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x59, 0x00, 0x00, 150 | 0x00, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x80, 0xA9, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 151 | 0x06, 0xFB, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x80, 0x28, 0x00, 0x00, 0x17, 0x00, 0x00, 152 | 0x4D, 0x41, 0x49, 0x4E, 0x07, 0x00, 0x00, 0x00, 0x20, 0xEF, 0xCA, 0x02, 0x00, 0x00 153 | }; 154 | const uint dd_sparta_disk_len = 142; 155 | 156 | const uint8_t *disk_images_data[4][4] { 157 | {sd_empty_disk, sd_dos_disk, sd_mydos_disk, sd_sparta_disk}, 158 | {ed_empty_disk, ed_dos_disk, ed_mydos_disk, ed_sparta_disk}, 159 | {dd_empty_disk, dd_dos_disk, dd_mydos_disk, dd_sparta_disk}, 160 | {qd_empty_disk, nullptr, qd_mydos_disk, qd_sparta_disk} 161 | }; 162 | 163 | const uint disk_images_size[4][4] { 164 | {sd_empty_disk_len, sd_dos_disk_len, sd_mydos_disk_len, sd_sparta_disk_len}, 165 | {ed_empty_disk_len, ed_dos_disk_len, ed_mydos_disk_len, ed_sparta_disk_len}, 166 | {dd_empty_disk_len, dd_dos_disk_len, dd_mydos_disk_len, dd_sparta_disk_len}, 167 | {qd_empty_disk_len, 0, qd_mydos_disk_len, qd_sparta_disk_len} 168 | }; 169 | -------------------------------------------------------------------------------- /dist.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | mkdir -p firmware 4 | rm -rf firmware/*_$1.uf2 5 | 6 | rm -rf build 7 | mkdir build 8 | cd build 9 | 10 | cmake .. 11 | make 12 | mv a8_pico_sio.uf2 ../firmware/a8_pico1_sio_$1.uf2 13 | rm -rf * 14 | 15 | cmake -DPICO_BOARD=pico2 .. 16 | make 17 | mv a8_pico_sio.uf2 ../firmware/a8_pico2_sio_$1.uf2 18 | rm -rf * 19 | 20 | cmake -DPICO_BOARD=pimoroni_picolipo_16mb .. 21 | make 22 | mv a8_pico_sio.uf2 ../firmware/a8_pimoroni16mb_sio_$1.uf2 23 | rm -rf * 24 | -------------------------------------------------------------------------------- /fatfs/00history.txt: -------------------------------------------------------------------------------- 1 | ---------------------------------------------------------------------------- 2 | Revision history of FatFs module 3 | ---------------------------------------------------------------------------- 4 | 5 | R0.00 (February 26, 2006) 6 | 7 | Prototype. 8 | 9 | 10 | 11 | R0.01 (April 29, 2006) 12 | 13 | The first release. 14 | 15 | 16 | 17 | R0.02 (June 01, 2006) 18 | 19 | Added FAT12 support. 20 | Removed unbuffered mode. 21 | Fixed a problem on small (<32M) partition. 22 | 23 | 24 | 25 | R0.02a (June 10, 2006) 26 | 27 | Added a configuration option (_FS_MINIMUM). 28 | 29 | 30 | 31 | R0.03 (September 22, 2006) 32 | 33 | Added f_rename(). 34 | Changed option _FS_MINIMUM to _FS_MINIMIZE. 35 | 36 | 37 | 38 | R0.03a (December 11, 2006) 39 | 40 | Improved cluster scan algorithm to write files fast. 41 | Fixed f_mkdir() creates incorrect directory on FAT32. 42 | 43 | 44 | 45 | R0.04 (February 04, 2007) 46 | 47 | Added f_mkfs(). 48 | Supported multiple drive system. 49 | Changed some interfaces for multiple drive system. 50 | Changed f_mountdrv() to f_mount(). 51 | 52 | 53 | 54 | R0.04a (April 01, 2007) 55 | 56 | Supported multiple partitions on a physical drive. 57 | Added a capability of extending file size to f_lseek(). 58 | Added minimization level 3. 59 | Fixed an endian sensitive code in f_mkfs(). 60 | 61 | 62 | 63 | R0.04b (May 05, 2007) 64 | 65 | Added a configuration option _USE_NTFLAG. 66 | Added FSINFO support. 67 | Fixed DBCS name can result FR_INVALID_NAME. 68 | Fixed short seek (<= csize) collapses the file object. 69 | 70 | 71 | 72 | R0.05 (August 25, 2007) 73 | 74 | Changed arguments of f_read(), f_write() and f_mkfs(). 75 | Fixed f_mkfs() on FAT32 creates incorrect FSINFO. 76 | Fixed f_mkdir() on FAT32 creates incorrect directory. 77 | 78 | 79 | 80 | R0.05a (February 03, 2008) 81 | 82 | Added f_truncate() and f_utime(). 83 | Fixed off by one error at FAT sub-type determination. 84 | Fixed btr in f_read() can be mistruncated. 85 | Fixed cached sector is not flushed when create and close without write. 86 | 87 | 88 | 89 | R0.06 (April 01, 2008) 90 | 91 | Added fputc(), fputs(), fprintf() and fgets(). 92 | Improved performance of f_lseek() on moving to the same or following cluster. 93 | 94 | 95 | 96 | R0.07 (April 01, 2009) 97 | 98 | Merged Tiny-FatFs as a configuration option. (_FS_TINY) 99 | Added long file name feature. (_USE_LFN) 100 | Added multiple code page feature. (_CODE_PAGE) 101 | Added re-entrancy for multitask operation. (_FS_REENTRANT) 102 | Added auto cluster size selection to f_mkfs(). 103 | Added rewind option to f_readdir(). 104 | Changed result code of critical errors. 105 | Renamed string functions to avoid name collision. 106 | 107 | 108 | 109 | R0.07a (April 14, 2009) 110 | 111 | Septemberarated out OS dependent code on reentrant cfg. 112 | Added multiple sector size feature. 113 | 114 | 115 | 116 | R0.07c (June 21, 2009) 117 | 118 | Fixed f_unlink() can return FR_OK on error. 119 | Fixed wrong cache control in f_lseek(). 120 | Added relative path feature. 121 | Added f_chdir() and f_chdrive(). 122 | Added proper case conversion to extended character. 123 | 124 | 125 | 126 | R0.07e (November 03, 2009) 127 | 128 | Septemberarated out configuration options from ff.h to ffconf.h. 129 | Fixed f_unlink() fails to remove a sub-directory on _FS_RPATH. 130 | Fixed name matching error on the 13 character boundary. 131 | Added a configuration option, _LFN_UNICODE. 132 | Changed f_readdir() to return the SFN with always upper case on non-LFN cfg. 133 | 134 | 135 | 136 | R0.08 (May 15, 2010) 137 | 138 | Added a memory configuration option. (_USE_LFN = 3) 139 | Added file lock feature. (_FS_SHARE) 140 | Added fast seek feature. (_USE_FASTSEEK) 141 | Changed some types on the API, XCHAR->TCHAR. 142 | Changed .fname in the FILINFO structure on Unicode cfg. 143 | String functions support UTF-8 encoding files on Unicode cfg. 144 | 145 | 146 | 147 | R0.08a (August 16, 2010) 148 | 149 | Added f_getcwd(). (_FS_RPATH = 2) 150 | Added sector erase feature. (_USE_ERASE) 151 | Moved file lock semaphore table from fs object to the bss. 152 | Fixed f_mkfs() creates wrong FAT32 volume. 153 | 154 | 155 | 156 | R0.08b (January 15, 2011) 157 | 158 | Fast seek feature is also applied to f_read() and f_write(). 159 | f_lseek() reports required table size on creating CLMP. 160 | Extended format syntax of f_printf(). 161 | Ignores duplicated directory separators in given path name. 162 | 163 | 164 | 165 | R0.09 (September 06, 2011) 166 | 167 | f_mkfs() supports multiple partition to complete the multiple partition feature. 168 | Added f_fdisk(). 169 | 170 | 171 | 172 | R0.09a (August 27, 2012) 173 | 174 | Changed f_open() and f_opendir() reject null object pointer to avoid crash. 175 | Changed option name _FS_SHARE to _FS_LOCK. 176 | Fixed assertion failure due to OS/2 EA on FAT12/16 volume. 177 | 178 | 179 | 180 | R0.09b (January 24, 2013) 181 | 182 | Added f_setlabel() and f_getlabel(). 183 | 184 | 185 | 186 | R0.10 (October 02, 2013) 187 | 188 | Added selection of character encoding on the file. (_STRF_ENCODE) 189 | Added f_closedir(). 190 | Added forced full FAT scan for f_getfree(). (_FS_NOFSINFO) 191 | Added forced mount feature with changes of f_mount(). 192 | Improved behavior of volume auto detection. 193 | Improved write throughput of f_puts() and f_printf(). 194 | Changed argument of f_chdrive(), f_mkfs(), disk_read() and disk_write(). 195 | Fixed f_write() can be truncated when the file size is close to 4GB. 196 | Fixed f_open(), f_mkdir() and f_setlabel() can return incorrect value on error. 197 | 198 | 199 | 200 | R0.10a (January 15, 2014) 201 | 202 | Added arbitrary strings as drive number in the path name. (_STR_VOLUME_ID) 203 | Added a configuration option of minimum sector size. (_MIN_SS) 204 | 2nd argument of f_rename() can have a drive number and it will be ignored. 205 | Fixed f_mount() with forced mount fails when drive number is >= 1. (appeared at R0.10) 206 | Fixed f_close() invalidates the file object without volume lock. 207 | Fixed f_closedir() returns but the volume lock is left acquired. (appeared at R0.10) 208 | Fixed creation of an entry with LFN fails on too many SFN collisions. (appeared at R0.07) 209 | 210 | 211 | 212 | R0.10b (May 19, 2014) 213 | 214 | Fixed a hard error in the disk I/O layer can collapse the directory entry. 215 | Fixed LFN entry is not deleted when delete/rename an object with lossy converted SFN. (appeared at R0.07) 216 | 217 | 218 | 219 | R0.10c (November 09, 2014) 220 | 221 | Added a configuration option for the platforms without RTC. (_FS_NORTC) 222 | Changed option name _USE_ERASE to _USE_TRIM. 223 | Fixed volume label created by Mac OS X cannot be retrieved with f_getlabel(). (appeared at R0.09b) 224 | Fixed a potential problem of FAT access that can appear on disk error. 225 | Fixed null pointer dereference on attempting to delete the root direcotry. (appeared at R0.08) 226 | 227 | 228 | 229 | R0.11 (February 09, 2015) 230 | 231 | Added f_findfirst(), f_findnext() and f_findclose(). (_USE_FIND) 232 | Fixed f_unlink() does not remove cluster chain of the file. (appeared at R0.10c) 233 | Fixed _FS_NORTC option does not work properly. (appeared at R0.10c) 234 | 235 | 236 | 237 | R0.11a (September 05, 2015) 238 | 239 | Fixed wrong media change can lead a deadlock at thread-safe configuration. 240 | Added code page 771, 860, 861, 863, 864, 865 and 869. (_CODE_PAGE) 241 | Removed some code pages actually not exist on the standard systems. (_CODE_PAGE) 242 | Fixed errors in the case conversion teble of code page 437 and 850 (ff.c). 243 | Fixed errors in the case conversion teble of Unicode (cc*.c). 244 | 245 | 246 | 247 | R0.12 (April 12, 2016) 248 | 249 | Added support for exFAT file system. (_FS_EXFAT) 250 | Added f_expand(). (_USE_EXPAND) 251 | Changed some members in FINFO structure and behavior of f_readdir(). 252 | Added an option _USE_CHMOD. 253 | Removed an option _WORD_ACCESS. 254 | Fixed errors in the case conversion table of Unicode (cc*.c). 255 | 256 | 257 | 258 | R0.12a (July 10, 2016) 259 | 260 | Added support for creating exFAT volume with some changes of f_mkfs(). 261 | Added a file open method FA_OPEN_APPEND. An f_lseek() following f_open() is no longer needed. 262 | f_forward() is available regardless of _FS_TINY. 263 | Fixed f_mkfs() creates wrong volume. (appeared at R0.12) 264 | Fixed wrong memory read in create_name(). (appeared at R0.12) 265 | Fixed compilation fails at some configurations, _USE_FASTSEEK and _USE_FORWARD. 266 | 267 | 268 | 269 | R0.12b (September 04, 2016) 270 | 271 | Made f_rename() be able to rename objects with the same name but case. 272 | Fixed an error in the case conversion teble of code page 866. (ff.c) 273 | Fixed writing data is truncated at the file offset 4GiB on the exFAT volume. (appeared at R0.12) 274 | Fixed creating a file in the root directory of exFAT volume can fail. (appeared at R0.12) 275 | Fixed f_mkfs() creating exFAT volume with too small cluster size can collapse unallocated memory. (appeared at R0.12) 276 | Fixed wrong object name can be returned when read directory at Unicode cfg. (appeared at R0.12) 277 | Fixed large file allocation/removing on the exFAT volume collapses allocation bitmap. (appeared at R0.12) 278 | Fixed some internal errors in f_expand() and f_lseek(). (appeared at R0.12) 279 | 280 | 281 | 282 | R0.12c (March 04, 2017) 283 | 284 | Improved write throughput at the fragmented file on the exFAT volume. 285 | Made memory usage for exFAT be able to be reduced as decreasing _MAX_LFN. 286 | Fixed successive f_getfree() can return wrong count on the FAT12/16 volume. (appeared at R0.12) 287 | Fixed configuration option _VOLUMES cannot be set 10. (appeared at R0.10c) 288 | 289 | 290 | 291 | R0.13 (May 21, 2017) 292 | 293 | Changed heading character of configuration keywords "_" to "FF_". 294 | Removed ASCII-only configuration, FF_CODE_PAGE = 1. Use FF_CODE_PAGE = 437 instead. 295 | Added f_setcp(), run-time code page configuration. (FF_CODE_PAGE = 0) 296 | Improved cluster allocation time on stretch a deep buried cluster chain. 297 | Improved processing time of f_mkdir() with large cluster size by using FF_USE_LFN = 3. 298 | Improved NoFatChain flag of the fragmented file to be set after it is truncated and got contiguous. 299 | Fixed archive attribute is left not set when a file on the exFAT volume is renamed. (appeared at R0.12) 300 | Fixed exFAT FAT entry can be collapsed when write or lseek operation to the existing file is done. (appeared at R0.12c) 301 | Fixed creating a file can fail when a new cluster allocation to the exFAT directory occures. (appeared at R0.12c) 302 | 303 | 304 | 305 | R0.13a (October 14, 2017) 306 | 307 | Added support for UTF-8 encoding on the API. (FF_LFN_UNICODE = 2) 308 | Added options for file name output buffer. (FF_LFN_BUF, FF_SFN_BUF). 309 | Added dynamic memory allocation option for working buffer of f_mkfs() and f_fdisk(). 310 | Fixed f_fdisk() and f_mkfs() create the partition table with wrong CHS parameters. (appeared at R0.09) 311 | Fixed f_unlink() can cause lost clusters at fragmented file on the exFAT volume. (appeared at R0.12c) 312 | Fixed f_setlabel() rejects some valid characters for exFAT volume. (appeared at R0.12) 313 | 314 | 315 | 316 | R0.13b (April 07, 2018) 317 | 318 | Added support for UTF-32 encoding on the API. (FF_LFN_UNICODE = 3) 319 | Added support for Unix style volume ID. (FF_STR_VOLUME_ID = 2) 320 | Fixed accesing any object on the exFAT root directory beyond the cluster boundary can fail. (appeared at R0.12c) 321 | Fixed f_setlabel() does not reject some invalid characters. (appeared at R0.09b) 322 | 323 | 324 | 325 | R0.13c (October 14, 2018) 326 | Supported stdint.h for C99 and later. (integer.h was included in ff.h) 327 | Fixed reading a directory gets infinite loop when the last directory entry is not empty. (appeared at R0.12) 328 | Fixed creating a sub-directory in the fragmented sub-directory on the exFAT volume collapses FAT chain of the parent directory. (appeared at R0.12) 329 | Fixed f_getcwd() cause output buffer overrun when the buffer has a valid drive number. (appeared at R0.13b) 330 | 331 | 332 | 333 | R0.14 (October 14, 2019) 334 | Added support for 64-bit LBA and GUID partition table (FF_LBA64 = 1) 335 | Changed some API functions, f_mkfs() and f_fdisk(). 336 | Fixed f_open() function cannot find the file with file name in length of FF_MAX_LFN characters. 337 | Fixed f_readdir() function cannot retrieve long file names in length of FF_MAX_LFN - 1 characters. 338 | Fixed f_readdir() function returns file names with wrong case conversion. (appeared at R0.12) 339 | Fixed f_mkfs() function can fail to create exFAT volume in the second partition. (appeared at R0.12) 340 | 341 | 342 | R0.14a (December 5, 2020) 343 | Limited number of recursive calls in f_findnext(). 344 | Fixed old floppy disks formatted with MS-DOS 2.x and 3.x cannot be mounted. 345 | Fixed some compiler warnings. 346 | 347 | 348 | 349 | R0.14b (April 17, 2021) 350 | Made FatFs uses standard library for copy, compare and search instead of built-in string functions. 351 | Added support for long long integer and floating point to f_printf(). (FF_STRF_LLI and FF_STRF_FP) 352 | Made path name parser ignore the terminating separator to allow "dir/". 353 | Improved the compatibility in Unix style path name feature. 354 | Fixed the file gets dead-locked when f_open() failed with some conditions. (appeared at R0.12a) 355 | Fixed f_mkfs() can create wrong exFAT volume due to a timing dependent error. (appeared at R0.12) 356 | Fixed code page 855 cannot be set by f_setcp(). 357 | Fixed some compiler warnings. 358 | 359 | 360 | 361 | R0.15 (November 6, 2022) 362 | Changed user provided synchronization functions in order to completely eliminate the platform dependency from FatFs code. 363 | FF_SYNC_t is removed from the configuration options. 364 | Fixed a potential error in f_mount when FF_FS_REENTRANT. 365 | Fixed file lock control FF_FS_LOCK is not mutal excluded when FF_FS_REENTRANT && FF_VOLUMES > 1 is true. 366 | Fixed f_mkfs() creates broken exFAT volume when the size of volume is >= 2^32 sectors. 367 | Fixed string functions cannot write the unicode characters not in BMP when FF_LFN_UNICODE == 2 (UTF-8). 368 | Fixed a compatibility issue in identification of GPT header. 369 | 370 | -------------------------------------------------------------------------------- /fatfs/00readme.txt: -------------------------------------------------------------------------------- 1 | FatFs Module Source Files R0.15 2 | 3 | 4 | FILES 5 | 6 | 00readme.txt This file. 7 | 00history.txt Revision history. 8 | ff.c FatFs module. 9 | ffconf.h Configuration file of FatFs module. 10 | ff.h Common include file for FatFs and application module. 11 | diskio.h Common include file for FatFs and disk I/O module. 12 | diskio.c An example of glue function to attach existing disk I/O module to FatFs. 13 | ffunicode.c Optional Unicode utility functions. 14 | ffsystem.c An example of optional O/S related functions. 15 | 16 | 17 | Low level disk I/O module is not included in this archive because the FatFs 18 | module is only a generic file system layer and it does not depend on any specific 19 | storage device. You need to provide a low level disk I/O module written to 20 | control the storage device that attached to the target system. 21 | 22 | -------------------------------------------------------------------------------- /fatfs/diskio.c: -------------------------------------------------------------------------------- 1 | /*-----------------------------------------------------------------------*/ 2 | /* Low level disk I/O module SKELETON for FatFs (C)ChaN, 2019 */ 3 | /*-----------------------------------------------------------------------*/ 4 | /* If a working storage control module is available, it should be */ 5 | /* attached to the FatFs via a glue function rather than modifying it. */ 6 | /* This is an example of glue functions to attach various exsisting */ 7 | /* storage control modules to the FatFs module with a defined API. */ 8 | /*-----------------------------------------------------------------------*/ 9 | 10 | #include "ff.h" /* Obtains integer types */ 11 | #include "diskio.h" /* Declarations of disk functions */ 12 | 13 | #define DEV_FLASH 0 14 | #define DEV_SD 1 15 | 16 | #include "fatfs_disk.h" 17 | 18 | #include "sd_card.h" 19 | 20 | DSTATUS disk_status (BYTE pdrv) { 21 | switch(pdrv) { 22 | case DEV_FLASH: 23 | return 0; 24 | case DEV_SD: { 25 | sd_card_t *p_sd = sd_get_by_num(pdrv); 26 | if (!p_sd) 27 | return RES_PARERR; 28 | sd_card_detect(p_sd); 29 | return p_sd->m_Status; 30 | } 31 | default: 32 | return STA_NOINIT; 33 | } 34 | } 35 | 36 | DSTATUS disk_initialize (BYTE pdrv) { 37 | switch(pdrv) { 38 | case DEV_FLASH: 39 | return 0; 40 | case DEV_SD: { 41 | bool rc = sd_init_driver(); 42 | if (!rc) 43 | return RES_NOTRDY; 44 | sd_card_t *p_sd = sd_get_by_num(pdrv); 45 | if (!p_sd) 46 | return RES_PARERR; 47 | return p_sd->init(p_sd); 48 | } 49 | default: 50 | return STA_NOINIT; 51 | } 52 | } 53 | 54 | static int sdrc2dresult(int sd_rc) { 55 | switch (sd_rc) { 56 | case SD_BLOCK_DEVICE_ERROR_NONE: 57 | return RES_OK; 58 | case SD_BLOCK_DEVICE_ERROR_UNUSABLE: 59 | case SD_BLOCK_DEVICE_ERROR_NO_RESPONSE: 60 | case SD_BLOCK_DEVICE_ERROR_NO_INIT: 61 | case SD_BLOCK_DEVICE_ERROR_NO_DEVICE: 62 | return RES_NOTRDY; 63 | case SD_BLOCK_DEVICE_ERROR_PARAMETER: 64 | case SD_BLOCK_DEVICE_ERROR_UNSUPPORTED: 65 | return RES_PARERR; 66 | case SD_BLOCK_DEVICE_ERROR_WRITE_PROTECTED: 67 | return RES_WRPRT; 68 | case SD_BLOCK_DEVICE_ERROR_CRC: 69 | case SD_BLOCK_DEVICE_ERROR_WOULD_BLOCK: 70 | case SD_BLOCK_DEVICE_ERROR_ERASE: 71 | case SD_BLOCK_DEVICE_ERROR_WRITE: 72 | default: 73 | return RES_ERROR; 74 | } 75 | } 76 | 77 | DRESULT disk_read (BYTE pdrv, BYTE *buff, LBA_t sector, UINT count) { 78 | switch(pdrv) { 79 | case DEV_FLASH: 80 | return fatfs_disk_read((uint8_t*)buff, sector, count); 81 | case DEV_SD: { 82 | sd_card_t *p_sd = sd_get_by_num(pdrv); 83 | if (!p_sd) 84 | return RES_PARERR; 85 | int rc = p_sd->read_blocks(p_sd, buff, sector, count); 86 | return sdrc2dresult(rc); 87 | } 88 | default: 89 | return RES_PARERR; 90 | } 91 | } 92 | 93 | #if FF_FS_READONLY == 0 94 | 95 | DRESULT disk_write (BYTE pdrv, const BYTE *buff, LBA_t sector, UINT count) { 96 | switch(pdrv) { 97 | case DEV_FLASH: 98 | return fatfs_disk_write((const uint8_t*)buff, sector, count); 99 | case DEV_SD: { 100 | sd_card_t *p_sd = sd_get_by_num(pdrv); 101 | if (!p_sd) 102 | return RES_PARERR; 103 | int rc = p_sd->write_blocks(p_sd, buff, sector, count); 104 | return sdrc2dresult(rc); 105 | } 106 | default: 107 | return RES_PARERR; 108 | } 109 | } 110 | 111 | #endif 112 | 113 | DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void *buff) { 114 | 115 | switch(pdrv) { 116 | case DEV_FLASH: 117 | switch(cmd) { 118 | case CTRL_SYNC: 119 | fatfs_disk_sync(); 120 | return RES_OK; 121 | case GET_SECTOR_COUNT: 122 | *(LBA_t*) buff = SECTOR_NUM; 123 | return RES_OK; 124 | case GET_SECTOR_SIZE: 125 | *(WORD*) buff = SECTOR_SIZE; 126 | return RES_OK; 127 | case GET_BLOCK_SIZE: 128 | *(DWORD*) buff = 1; 129 | return RES_OK; 130 | case CTRL_TRIM: 131 | return RES_OK; 132 | default: 133 | return RES_PARERR; 134 | } 135 | case DEV_SD: { 136 | sd_card_t *p_sd = sd_get_by_num(pdrv); 137 | if (!p_sd) 138 | return RES_PARERR; 139 | switch (cmd) { 140 | case GET_SECTOR_COUNT: { 141 | static LBA_t n; 142 | n = sd_sectors(p_sd); 143 | *(LBA_t *)buff = n; 144 | if (!n) 145 | return RES_ERROR; 146 | return RES_OK; 147 | } 148 | case GET_SECTOR_SIZE: 149 | *(WORD*) buff = SECTOR_SIZE; 150 | return RES_OK; 151 | case GET_BLOCK_SIZE: { 152 | static DWORD bs = 1; 153 | *(DWORD *)buff = bs; 154 | return RES_OK; 155 | } 156 | case CTRL_SYNC: 157 | return RES_OK; 158 | default: 159 | return RES_PARERR; 160 | } 161 | } 162 | default: 163 | return RES_PARERR; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /fatfs/diskio.h: -------------------------------------------------------------------------------- 1 | /*-----------------------------------------------------------------------/ 2 | / Low level disk interface modlue include file (C)ChaN, 2019 / 3 | /-----------------------------------------------------------------------*/ 4 | 5 | #ifndef _DISKIO_DEFINED 6 | #define _DISKIO_DEFINED 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | /* Status of Disk Functions */ 13 | typedef BYTE DSTATUS; 14 | 15 | /* Results of Disk Functions */ 16 | typedef enum { 17 | RES_OK = 0, /* 0: Successful */ 18 | RES_ERROR, /* 1: R/W Error */ 19 | RES_WRPRT, /* 2: Write Protected */ 20 | RES_NOTRDY, /* 3: Not Ready */ 21 | RES_PARERR /* 4: Invalid Parameter */ 22 | } DRESULT; 23 | 24 | 25 | /*---------------------------------------*/ 26 | /* Prototypes for disk control functions */ 27 | 28 | 29 | DSTATUS disk_initialize (BYTE pdrv); 30 | DSTATUS disk_status (BYTE pdrv); 31 | DRESULT disk_read (BYTE pdrv, BYTE* buff, LBA_t sector, UINT count); 32 | DRESULT disk_write (BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count); 33 | DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff); 34 | 35 | 36 | /* Disk Status Bits (DSTATUS) */ 37 | 38 | #define STA_NOINIT 0x01 /* Drive not initialized */ 39 | #define STA_NODISK 0x02 /* No medium in the drive */ 40 | #define STA_PROTECT 0x04 /* Write protected */ 41 | 42 | 43 | /* Command code for disk_ioctrl fucntion */ 44 | 45 | /* Generic command (Used by FatFs) */ 46 | #define CTRL_SYNC 0 /* Complete pending write process (needed at FF_FS_READONLY == 0) */ 47 | #define GET_SECTOR_COUNT 1 /* Get media size (needed at FF_USE_MKFS == 1) */ 48 | #define GET_SECTOR_SIZE 2 /* Get sector size (needed at FF_MAX_SS != FF_MIN_SS) */ 49 | #define GET_BLOCK_SIZE 3 /* Get erase block size (needed at FF_USE_MKFS == 1) */ 50 | #define CTRL_TRIM 4 /* Inform device that the data on the block of sectors is no longer used (needed at FF_USE_TRIM == 1) */ 51 | 52 | /* Generic command (Not used by FatFs) */ 53 | #define CTRL_POWER 5 /* Get/Set power status */ 54 | #define CTRL_LOCK 6 /* Lock/Unlock media removal */ 55 | #define CTRL_EJECT 7 /* Eject media */ 56 | #define CTRL_FORMAT 8 /* Create physical format on the media */ 57 | 58 | /* MMC/SDC specific ioctl command */ 59 | #define MMC_GET_TYPE 10 /* Get card type */ 60 | #define MMC_GET_CSD 11 /* Get CSD */ 61 | #define MMC_GET_CID 12 /* Get CID */ 62 | #define MMC_GET_OCR 13 /* Get OCR */ 63 | #define MMC_GET_SDSTAT 14 /* Get SD status */ 64 | #define ISDIO_READ 55 /* Read data form SD iSDIO register */ 65 | #define ISDIO_WRITE 56 /* Write data to SD iSDIO register */ 66 | #define ISDIO_MRITE 57 /* Masked write data to SD iSDIO register */ 67 | 68 | /* ATA/CF specific ioctl command */ 69 | #define ATA_GET_REV 20 /* Get F/W revision */ 70 | #define ATA_GET_MODEL 21 /* Get model name */ 71 | #define ATA_GET_SN 22 /* Get serial number */ 72 | 73 | #ifdef __cplusplus 74 | } 75 | #endif 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /fatfs/ffconf.h: -------------------------------------------------------------------------------- 1 | /*---------------------------------------------------------------------------/ 2 | / Configurations of FatFs Module 3 | /---------------------------------------------------------------------------*/ 4 | 5 | #define FFCONF_DEF 80286 /* Revision ID */ 6 | 7 | /*---------------------------------------------------------------------------/ 8 | / Function Configurations 9 | /---------------------------------------------------------------------------*/ 10 | 11 | #define FF_FS_READONLY 0 12 | /* This option switches read-only configuration. (0:Read/Write or 1:Read-only) 13 | / Read-only configuration removes writing API functions, f_write(), f_sync(), 14 | / f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree() 15 | / and optional writing functions as well. */ 16 | 17 | 18 | #define FF_FS_MINIMIZE 0 19 | /* This option defines minimization level to remove some basic API functions. 20 | / 21 | / 0: Basic functions are fully enabled. 22 | / 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename() 23 | / are removed. 24 | / 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1. 25 | / 3: f_lseek() function is removed in addition to 2. */ 26 | 27 | 28 | #define FF_USE_FIND 0 29 | /* This option switches filtered directory read functions, f_findfirst() and 30 | / f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */ 31 | 32 | 33 | #define FF_USE_MKFS 1 34 | /* This option switches f_mkfs() function. (0:Disable or 1:Enable) */ 35 | 36 | #define FF_USE_FASTSEEK 1 37 | /* This option switches fast seek function. (0:Disable or 1:Enable) */ 38 | 39 | 40 | #define FF_USE_EXPAND 0 41 | /* This option switches f_expand function. (0:Disable or 1:Enable) */ 42 | 43 | 44 | #define FF_USE_CHMOD 0 45 | /* This option switches attribute manipulation functions, f_chmod() and f_utime(). 46 | / (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */ 47 | 48 | 49 | #define FF_USE_LABEL 1 50 | /* This option switches volume label functions, f_getlabel() and f_setlabel(). 51 | / (0:Disable or 1:Enable) */ 52 | 53 | 54 | #define FF_USE_FORWARD 0 55 | /* This option switches f_forward() function. (0:Disable or 1:Enable) */ 56 | 57 | 58 | #define FF_USE_STRFUNC 1 59 | #define FF_PRINT_LLI 1 60 | #define FF_PRINT_FLOAT 1 61 | #define FF_STRF_ENCODE 3 62 | /* FF_USE_STRFUNC switches string functions, f_gets(), f_putc(), f_puts() and 63 | / f_printf(). 64 | / 65 | / 0: Disable. FF_PRINT_LLI, FF_PRINT_FLOAT and FF_STRF_ENCODE have no effect. 66 | / 1: Enable without LF-CRLF conversion. 67 | / 2: Enable with LF-CRLF conversion. 68 | / 69 | / FF_PRINT_LLI = 1 makes f_printf() support long long argument and FF_PRINT_FLOAT = 1/2 70 | / makes f_printf() support floating point argument. These features want C99 or later. 71 | / When FF_LFN_UNICODE >= 1 with LFN enabled, string functions convert the character 72 | / encoding in it. FF_STRF_ENCODE selects assumption of character encoding ON THE FILE 73 | / to be read/written via those functions. 74 | / 75 | / 0: ANSI/OEM in current CP 76 | / 1: Unicode in UTF-16LE 77 | / 2: Unicode in UTF-16BE 78 | / 3: Unicode in UTF-8 79 | */ 80 | 81 | 82 | /*---------------------------------------------------------------------------/ 83 | / Locale and Namespace Configurations 84 | /---------------------------------------------------------------------------*/ 85 | 86 | #define FF_CODE_PAGE 437 87 | /* This option specifies the OEM code page to be used on the target system. 88 | / Incorrect code page setting can cause a file open failure. 89 | / 90 | / 437 - U.S. 91 | / 720 - Arabic 92 | / 737 - Greek 93 | / 771 - KBL 94 | / 775 - Baltic 95 | / 850 - Latin 1 96 | / 852 - Latin 2 97 | / 855 - Cyrillic 98 | / 857 - Turkish 99 | / 860 - Portuguese 100 | / 861 - Icelandic 101 | / 862 - Hebrew 102 | / 863 - Canadian French 103 | / 864 - Arabic 104 | / 865 - Nordic 105 | / 866 - Russian 106 | / 869 - Greek 2 107 | / 932 - Japanese (DBCS) 108 | / 936 - Simplified Chinese (DBCS) 109 | / 949 - Korean (DBCS) 110 | / 950 - Traditional Chinese (DBCS) 111 | / 0 - Include all code pages above and configured by f_setcp() 112 | */ 113 | 114 | 115 | #define FF_USE_LFN 1 116 | #define FF_MAX_LFN 255 117 | /* The FF_USE_LFN switches the support for LFN (long file name). 118 | / 119 | / 0: Disable LFN. FF_MAX_LFN has no effect. 120 | / 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe. 121 | / 2: Enable LFN with dynamic working buffer on the STACK. 122 | / 3: Enable LFN with dynamic working buffer on the HEAP. 123 | / 124 | / To enable the LFN, ffunicode.c needs to be added to the project. The LFN function 125 | / requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and 126 | / additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled. 127 | / The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can 128 | / be in range of 12 to 255. It is recommended to be set it 255 to fully support LFN 129 | / specification. 130 | / When use stack for the working buffer, take care on stack overflow. When use heap 131 | / memory for the working buffer, memory management functions, ff_memalloc() and 132 | / ff_memfree() exemplified in ffsystem.c, need to be added to the project. */ 133 | 134 | 135 | #define FF_LFN_UNICODE 0 136 | /* This option switches the character encoding on the API when LFN is enabled. 137 | / 138 | / 0: ANSI/OEM in current CP (TCHAR = char) 139 | / 1: Unicode in UTF-16 (TCHAR = WCHAR) 140 | / 2: Unicode in UTF-8 (TCHAR = char) 141 | / 3: Unicode in UTF-32 (TCHAR = DWORD) 142 | / 143 | / Also behavior of string I/O functions will be affected by this option. 144 | / When LFN is not enabled, this option has no effect. */ 145 | 146 | 147 | #define FF_LFN_BUF 255 148 | #define FF_SFN_BUF 12 149 | /* This set of options defines size of file name members in the FILINFO structure 150 | / which is used to read out directory items. These values should be suffcient for 151 | / the file names to read. The maximum possible length of the read file name depends 152 | / on character encoding. When LFN is not enabled, these options have no effect. */ 153 | 154 | 155 | #define FF_FS_RPATH 0 156 | /* This option configures support for relative path. 157 | / 158 | / 0: Disable relative path and remove related functions. 159 | / 1: Enable relative path. f_chdir() and f_chdrive() are available. 160 | / 2: f_getcwd() function is available in addition to 1. 161 | */ 162 | 163 | 164 | /*---------------------------------------------------------------------------/ 165 | / Drive/Volume Configurations 166 | /---------------------------------------------------------------------------*/ 167 | 168 | // TODO was 1 169 | #define FF_VOLUMES 2 170 | /* Number of volumes (logical drives) to be used. (1-10) */ 171 | 172 | 173 | #define FF_STR_VOLUME_ID 0 174 | #define FF_VOLUME_STRS "RAM","NAND","CF","SD","SD2","USB","USB2","USB3" 175 | /* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings. 176 | / When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive 177 | / number in the path name. FF_VOLUME_STRS defines the volume ID strings for each 178 | / logical drives. Number of items must not be less than FF_VOLUMES. Valid 179 | / characters for the volume ID strings are A-Z, a-z and 0-9, however, they are 180 | / compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is 181 | / not defined, a user defined volume string table is needed as: 182 | / 183 | / const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",... 184 | */ 185 | 186 | 187 | #define FF_MULTI_PARTITION 0 188 | /* This option switches support for multiple volumes on the physical drive. 189 | / By default (0), each logical drive number is bound to the same physical drive 190 | / number and only an FAT volume found on the physical drive will be mounted. 191 | / When this function is enabled (1), each logical drive number can be bound to 192 | / arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk() 193 | / function will be available. */ 194 | 195 | 196 | #define FF_MIN_SS 512 197 | #define FF_MAX_SS 512 198 | /* This set of options configures the range of sector size to be supported. (512, 199 | / 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and 200 | / harddisk, but a larger value may be required for on-board flash memory and some 201 | / type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured 202 | / for variable sector size mode and disk_ioctl() function needs to implement 203 | / GET_SECTOR_SIZE command. */ 204 | 205 | #define FF_LBA64 0 206 | /* This option switches support for 64-bit LBA. (0:Disable or 1:Enable) 207 | / To enable the 64-bit LBA, also exFAT needs to be enabled. (FF_FS_EXFAT == 1) */ 208 | 209 | 210 | #define FF_MIN_GPT 0x10000000 211 | /* Minimum number of sectors to switch GPT as partitioning format in f_mkfs and 212 | / f_fdisk function. 0x100000000 max. This option has no effect when FF_LBA64 == 0. */ 213 | 214 | 215 | #define FF_USE_TRIM 0 216 | /* This option switches support for ATA-TRIM. (0:Disable or 1:Enable) 217 | / To enable Trim function, also CTRL_TRIM command should be implemented to the 218 | / disk_ioctl() function. */ 219 | 220 | 221 | 222 | /*---------------------------------------------------------------------------/ 223 | / System Configurations 224 | /---------------------------------------------------------------------------*/ 225 | 226 | #define FF_FS_TINY 0 227 | /* This option switches tiny buffer configuration. (0:Normal or 1:Tiny) 228 | / At the tiny configuration, size of file object (FIL) is shrinked FF_MAX_SS bytes. 229 | / Instead of private sector buffer eliminated from the file object, common sector 230 | / buffer in the filesystem object (FATFS) is used for the file data transfer. */ 231 | 232 | 233 | #define FF_FS_EXFAT 0 234 | /* This option switches support for exFAT filesystem. (0:Disable or 1:Enable) 235 | / To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1) 236 | / Note that enabling exFAT discards ANSI C (C89) compatibility. */ 237 | 238 | 239 | #define FF_FS_NORTC 1 240 | #define FF_NORTC_MON 1 241 | #define FF_NORTC_MDAY 1 242 | #define FF_NORTC_YEAR 2024 243 | /* The option FF_FS_NORTC switches timestamp feature. If the system does not have 244 | / an RTC or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable the 245 | / timestamp feature. Every object modified by FatFs will have a fixed timestamp 246 | / defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time. 247 | / To enable timestamp function (FF_FS_NORTC = 0), get_fattime() function need to be 248 | / added to the project to read current time form real-time clock. FF_NORTC_MON, 249 | / FF_NORTC_MDAY and FF_NORTC_YEAR have no effect. 250 | / These options have no effect in read-only configuration (FF_FS_READONLY = 1). */ 251 | 252 | 253 | #define FF_FS_NOFSINFO 0 254 | /* If you need to know correct free space on the FAT32 volume, set bit 0 of this 255 | / option, and f_getfree() function at the first time after volume mount will force 256 | / a full FAT scan. Bit 1 controls the use of last allocated cluster number. 257 | / 258 | / bit0=0: Use free cluster count in the FSINFO if available. 259 | / bit0=1: Do not trust free cluster count in the FSINFO. 260 | / bit1=0: Use last allocated cluster number in the FSINFO if available. 261 | / bit1=1: Do not trust last allocated cluster number in the FSINFO. 262 | */ 263 | 264 | // This enabled double check of what we control in the application nevertheless, 265 | // this should not be necessary. The 6 for the maximum comes from: 4 disk 266 | // images, 1 cassette image, 1 directory xor 1 new file creation. 267 | #define FF_FS_LOCK 6 268 | /* The option FF_FS_LOCK switches file lock function to control duplicated file open 269 | / and illegal operation to open objects. This option must be 0 when FF_FS_READONLY 270 | / is 1. 271 | / 272 | / 0: Disable file lock function. To avoid volume corruption, application program 273 | / should avoid illegal open, remove and rename to the open objects. 274 | / >0: Enable file lock function. The value defines how many files/sub-directories 275 | / can be opened simultaneously under file lock control. Note that the file 276 | / lock control is independent of re-entrancy. */ 277 | 278 | 279 | #define FF_FS_REENTRANT 0 280 | #define FF_FS_TIMEOUT 1000 281 | /* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs 282 | / module itself. Note that regardless of this option, file access to different 283 | / volume is always re-entrant and volume control functions, f_mount(), f_mkfs() 284 | / and f_fdisk() function, are always not re-entrant. Only file/directory access 285 | / to the same volume is under control of this featuer. 286 | / 287 | / 0: Disable re-entrancy. FF_FS_TIMEOUT have no effect. 288 | / 1: Enable re-entrancy. Also user provided synchronization handlers, 289 | / ff_mutex_create(), ff_mutex_delete(), ff_mutex_take() and ff_mutex_give() 290 | / function, must be added to the project. Samples are available in ffsystem.c. 291 | / 292 | / The FF_FS_TIMEOUT defines timeout period in unit of O/S time tick. 293 | */ 294 | 295 | 296 | 297 | /*--- End of configuration options ---*/ 298 | -------------------------------------------------------------------------------- /fatfs/ffsystem.c: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------------------*/ 2 | /* A Sample Code of User Provided OS Dependent Functions for FatFs */ 3 | /*------------------------------------------------------------------------*/ 4 | 5 | #include "ff.h" 6 | 7 | 8 | #if FF_USE_LFN == 3 /* Use dynamic memory allocation */ 9 | 10 | /*------------------------------------------------------------------------*/ 11 | /* Allocate/Free a Memory Block */ 12 | /*------------------------------------------------------------------------*/ 13 | 14 | #include /* with POSIX API */ 15 | 16 | 17 | void* ff_memalloc ( /* Returns pointer to the allocated memory block (null if not enough core) */ 18 | UINT msize /* Number of bytes to allocate */ 19 | ) 20 | { 21 | return malloc((size_t)msize); /* Allocate a new memory block */ 22 | } 23 | 24 | 25 | void ff_memfree ( 26 | void* mblock /* Pointer to the memory block to free (no effect if null) */ 27 | ) 28 | { 29 | free(mblock); /* Free the memory block */ 30 | } 31 | 32 | #endif 33 | 34 | 35 | 36 | 37 | #if FF_FS_REENTRANT /* Mutal exclusion */ 38 | /*------------------------------------------------------------------------*/ 39 | /* Definitions of Mutex */ 40 | /*------------------------------------------------------------------------*/ 41 | 42 | #define OS_TYPE 0 /* 0:Win32, 1:uITRON4.0, 2:uC/OS-II, 3:FreeRTOS, 4:CMSIS-RTOS */ 43 | 44 | 45 | #if OS_TYPE == 0 /* Win32 */ 46 | #include 47 | static HANDLE Mutex[FF_VOLUMES + 1]; /* Table of mutex handle */ 48 | 49 | #elif OS_TYPE == 1 /* uITRON */ 50 | #include "itron.h" 51 | #include "kernel.h" 52 | static mtxid Mutex[FF_VOLUMES + 1]; /* Table of mutex ID */ 53 | 54 | #elif OS_TYPE == 2 /* uc/OS-II */ 55 | #include "includes.h" 56 | static OS_EVENT *Mutex[FF_VOLUMES + 1]; /* Table of mutex pinter */ 57 | 58 | #elif OS_TYPE == 3 /* FreeRTOS */ 59 | #include "FreeRTOS.h" 60 | #include "semphr.h" 61 | static SemaphoreHandle_t Mutex[FF_VOLUMES + 1]; /* Table of mutex handle */ 62 | 63 | #elif OS_TYPE == 4 /* CMSIS-RTOS */ 64 | #include "cmsis_os.h" 65 | static osMutexId Mutex[FF_VOLUMES + 1]; /* Table of mutex ID */ 66 | 67 | #endif 68 | 69 | 70 | 71 | /*------------------------------------------------------------------------*/ 72 | /* Create a Mutex */ 73 | /*------------------------------------------------------------------------*/ 74 | /* This function is called in f_mount function to create a new mutex 75 | / or semaphore for the volume. When a 0 is returned, the f_mount function 76 | / fails with FR_INT_ERR. 77 | */ 78 | 79 | int ff_mutex_create ( /* Returns 1:Function succeeded or 0:Could not create the mutex */ 80 | int vol /* Mutex ID: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) */ 81 | ) 82 | { 83 | #if OS_TYPE == 0 /* Win32 */ 84 | Mutex[vol] = CreateMutex(NULL, FALSE, NULL); 85 | return (int)(Mutex[vol] != INVALID_HANDLE_VALUE); 86 | 87 | #elif OS_TYPE == 1 /* uITRON */ 88 | T_CMTX cmtx = {TA_TPRI,1}; 89 | 90 | Mutex[vol] = acre_mtx(&cmtx); 91 | return (int)(Mutex[vol] > 0); 92 | 93 | #elif OS_TYPE == 2 /* uC/OS-II */ 94 | OS_ERR err; 95 | 96 | Mutex[vol] = OSMutexCreate(0, &err); 97 | return (int)(err == OS_NO_ERR); 98 | 99 | #elif OS_TYPE == 3 /* FreeRTOS */ 100 | Mutex[vol] = xSemaphoreCreateMutex(); 101 | return (int)(Mutex[vol] != NULL); 102 | 103 | #elif OS_TYPE == 4 /* CMSIS-RTOS */ 104 | osMutexDef(cmsis_os_mutex); 105 | 106 | Mutex[vol] = osMutexCreate(osMutex(cmsis_os_mutex)); 107 | return (int)(Mutex[vol] != NULL); 108 | 109 | #endif 110 | } 111 | 112 | 113 | /*------------------------------------------------------------------------*/ 114 | /* Delete a Mutex */ 115 | /*------------------------------------------------------------------------*/ 116 | /* This function is called in f_mount function to delete a mutex or 117 | / semaphore of the volume created with ff_mutex_create function. 118 | */ 119 | 120 | void ff_mutex_delete ( /* Returns 1:Function succeeded or 0:Could not delete due to an error */ 121 | int vol /* Mutex ID: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) */ 122 | ) 123 | { 124 | #if OS_TYPE == 0 /* Win32 */ 125 | CloseHandle(Mutex[vol]); 126 | 127 | #elif OS_TYPE == 1 /* uITRON */ 128 | del_mtx(Mutex[vol]); 129 | 130 | #elif OS_TYPE == 2 /* uC/OS-II */ 131 | OS_ERR err; 132 | 133 | OSMutexDel(Mutex[vol], OS_DEL_ALWAYS, &err); 134 | 135 | #elif OS_TYPE == 3 /* FreeRTOS */ 136 | vSemaphoreDelete(Mutex[vol]); 137 | 138 | #elif OS_TYPE == 4 /* CMSIS-RTOS */ 139 | osMutexDelete(Mutex[vol]); 140 | 141 | #endif 142 | } 143 | 144 | 145 | /*------------------------------------------------------------------------*/ 146 | /* Request a Grant to Access the Volume */ 147 | /*------------------------------------------------------------------------*/ 148 | /* This function is called on enter file functions to lock the volume. 149 | / When a 0 is returned, the file function fails with FR_TIMEOUT. 150 | */ 151 | 152 | int ff_mutex_take ( /* Returns 1:Succeeded or 0:Timeout */ 153 | int vol /* Mutex ID: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) */ 154 | ) 155 | { 156 | #if OS_TYPE == 0 /* Win32 */ 157 | return (int)(WaitForSingleObject(Mutex[vol], FF_FS_TIMEOUT) == WAIT_OBJECT_0); 158 | 159 | #elif OS_TYPE == 1 /* uITRON */ 160 | return (int)(tloc_mtx(Mutex[vol], FF_FS_TIMEOUT) == E_OK); 161 | 162 | #elif OS_TYPE == 2 /* uC/OS-II */ 163 | OS_ERR err; 164 | 165 | OSMutexPend(Mutex[vol], FF_FS_TIMEOUT, &err)); 166 | return (int)(err == OS_NO_ERR); 167 | 168 | #elif OS_TYPE == 3 /* FreeRTOS */ 169 | return (int)(xSemaphoreTake(Mutex[vol], FF_FS_TIMEOUT) == pdTRUE); 170 | 171 | #elif OS_TYPE == 4 /* CMSIS-RTOS */ 172 | return (int)(osMutexWait(Mutex[vol], FF_FS_TIMEOUT) == osOK); 173 | 174 | #endif 175 | } 176 | 177 | 178 | 179 | /*------------------------------------------------------------------------*/ 180 | /* Release a Grant to Access the Volume */ 181 | /*------------------------------------------------------------------------*/ 182 | /* This function is called on leave file functions to unlock the volume. 183 | */ 184 | 185 | void ff_mutex_give ( 186 | int vol /* Mutex ID: Volume mutex (0 to FF_VOLUMES - 1) or system mutex (FF_VOLUMES) */ 187 | ) 188 | { 189 | #if OS_TYPE == 0 /* Win32 */ 190 | ReleaseMutex(Mutex[vol]); 191 | 192 | #elif OS_TYPE == 1 /* uITRON */ 193 | unl_mtx(Mutex[vol]); 194 | 195 | #elif OS_TYPE == 2 /* uC/OS-II */ 196 | OSMutexPost(Mutex[vol]); 197 | 198 | #elif OS_TYPE == 3 /* FreeRTOS */ 199 | xSemaphoreGive(Mutex[vol]); 200 | 201 | #elif OS_TYPE == 4 /* CMSIS-RTOS */ 202 | osMutexRelease(Mutex[vol]); 203 | 204 | #endif 205 | } 206 | 207 | #endif /* FF_FS_REENTRANT */ 208 | 209 | -------------------------------------------------------------------------------- /fatfs_disk.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the a8-pico-sio project -- 3 | * An Atari 8-bit SIO drive and (turbo) tape emulator for 4 | * Raspberry Pi Pico, see 5 | * 6 | * https://github.com/woj76/a8-pico-sio 7 | * 8 | * For information on what / whose work it is based on, check below and the 9 | * corresponding project repository. 10 | */ 11 | 12 | /* This file or its parts come originally from the A8PicoCart 13 | * project, see https://github.com/robinhedwards/A8PicoCart for additional 14 | * information, license, and credits (see also below). The major modification is 15 | * the ability to handle various sizes of the Pico FLASH memory that stores the 16 | * FAT file system, not only the 15MB drive on a 16MB Pico clone. 17 | */ 18 | 19 | /** 20 | * _ ___ ___ _ ___ _ 21 | * /_\ ( _ ) _ (_)__ _ / __|__ _ _ _| |_ 22 | * / _ \/ _ \ _/ / _/_\ (__/ _` | '_| _| 23 | * /_/ \_\___/_| |_\__\_/\___\__,_|_| \__| 24 | * 25 | * 26 | * Atari 8-bit cartridge for Raspberry Pi Pico 27 | * 28 | * Robin Edwards 2023 29 | */ 30 | 31 | #include "ff.h" 32 | #include "diskio.h" 33 | #include "fatfs_disk.h" 34 | 35 | // #include 36 | 37 | // #include "pico/stdlib.h" 38 | #include "hardware/flash.h" 39 | 40 | bool flashfs_is_mounted = false; 41 | 42 | bool mount_fatfs_disk() { 43 | int err = flash_fs_mount(); 44 | if (err) 45 | return false; 46 | flashfs_is_mounted = true; 47 | return true; 48 | } 49 | 50 | bool fatfs_is_mounted() { 51 | return flashfs_is_mounted; 52 | } 53 | 54 | void create_fatfs_disk() { 55 | flash_fs_create(); 56 | flashfs_is_mounted = true; 57 | 58 | FATFS fs; 59 | FIL fil; 60 | FRESULT res; 61 | BYTE work[FF_MAX_SS]; 62 | 63 | res = f_mkfs("", 0, work, sizeof(work)); 64 | f_mount(&fs, "0:", 0); 65 | f_setlabel("A8-Pico-SIO"); 66 | res = f_open(&fil, "INFO.TXT", FA_CREATE_NEW | FA_WRITE); 67 | f_puts("Atari 8-bit Pico SIO USB device\r\n(c) 2025 woj@AtariAge, USB device inspired and based on A8PicoCart code by Electrotrains (c) 2023\r\nDrag your ATR, ATX, XEX, CAS, or WAV files in here!\r\n", &fil); 68 | f_close(&fil); 69 | f_mount(0, "0:", 0); 70 | } 71 | 72 | uint32_t fatfs_disk_read(uint8_t* buff, uint32_t sector, uint32_t count) { 73 | if (!flashfs_is_mounted) 74 | return RES_ERROR; 75 | if (sector < 0 || sector >= SECTOR_NUM) 76 | return RES_PARERR; 77 | 78 | for (int i=0; i= SECTOR_NUM) 87 | return RES_PARERR; 88 | 89 | for (int i=0; i 35 | 36 | #ifdef __cplusplus 37 | extern "C" { 38 | #endif 39 | 40 | #define SECTOR_NUM NUM_FAT_SECTORS 41 | #define SECTOR_SIZE 512 42 | 43 | void create_fatfs_disk(); 44 | bool mount_fatfs_disk(); 45 | bool fatfs_is_mounted(); 46 | uint32_t fatfs_disk_read(uint8_t* buff, uint32_t sector, uint32_t count); 47 | uint32_t fatfs_disk_write(const uint8_t* buff, uint32_t sector, uint32_t count); 48 | void fatfs_disk_sync(); 49 | 50 | #ifdef __cplusplus 51 | } 52 | #endif 53 | -------------------------------------------------------------------------------- /file_load.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the a8-pico-sio project -- 3 | * An Atari 8-bit SIO drive and (turbo) tape emulator for 4 | * Raspberry Pi Pico, see 5 | * 6 | * https://github.com/woj76/a8-pico-sio 7 | * 8 | * For information on what / whose work it is based on, check the corresponding 9 | * source files and the README file. This file is licensed under GNU General 10 | * Public License 3.0 or later. 11 | * 12 | * Copyright (C) 2025 Wojciech Mostowski 13 | */ 14 | 15 | #include 16 | 17 | #include "file_load.hpp" 18 | #include "ff.h" 19 | #include "mounts.hpp" 20 | 21 | char curr_path[MAX_PATH_LEN]; 22 | size_t num_files, num_files_page; 23 | DIR dir = {0}; 24 | 25 | char temp_array[MAX_PATH_LEN]; 26 | volatile int16_t create_new_file = 0; 27 | 28 | file_entry_type file_entries[max_files_per_page+2]; 29 | char last_file_name[15] = {0}; 30 | 31 | size_t get_filename_ext(char *filename) { 32 | size_t l = strlen(filename); 33 | size_t i = l; 34 | while(i > 0 && filename[--i] != '.'); 35 | return (i == 0 || l-i > 4) ? l : i+1; 36 | } 37 | 38 | static int file_entry_cmp(int i, int j) { 39 | file_entry_type e1 = file_entries[i]; 40 | file_entry_type e2 = file_entries[j]; 41 | if (e1.dir && !e2.dir) return -1; 42 | else if (!e1.dir && e2.dir) return 1; 43 | // Short names are a bit worse to use for comparison, but with the current 44 | // setup they are the only ones guaranteed not to have duplicates in the list 45 | else return strcasecmp(e1.short_name, e2.short_name); 46 | } 47 | 48 | static bool is_valid_file(char *filename) { 49 | size_t i = get_filename_ext(filename); 50 | switch(ft) { 51 | case file_type::casette: 52 | return !strcasecmp(&filename[i], "CAS") || !strcasecmp(&filename[i], "WAV"); 53 | case file_type::disk: 54 | return !strcasecmp(&filename[i], "ATR") || !strcasecmp(&filename[i], "ATX") || !strcasecmp(&filename[i], "XEX") || !strcasecmp(&filename[i], "COM") || !strcasecmp(&filename[i], "EXE"); 55 | default: 56 | return false; 57 | } 58 | } 59 | 60 | static void mark_directory(int i) { 61 | int l = strlen(file_entries[i].long_name); 62 | file_entries[i].long_name[l] = '/'; 63 | file_entries[i].long_name[l+1] = 0; 64 | l = strlen(file_entries[i].short_name); 65 | file_entries[i].short_name[l] = '/'; 66 | file_entries[i].short_name[l+1] = 0; 67 | } 68 | 69 | uint16_t next_page_references[5464]; // This is 65536 / 12 (max_files_per_page) with a tiny slack 70 | 71 | /** 72 | Read (the portion of) the directory, page_index is to control what is the current alphabetically smallest 73 | file name on the current page, page_size specifies how many files are supposed to appear on the current display page, 74 | use -1 to only pre-count files in the directory. Returns either the total number of files (when page_size is -1) 75 | or the total number of files for the next display (it should be assertable that result == page_size in this case). 76 | */ 77 | int32_t read_directory(int32_t page_index, int page_size) { 78 | FILINFO fno; 79 | int32_t ret = -1; 80 | 81 | if(!curr_path[0]) { 82 | int i; 83 | for(i=0; i < sd_card_present+1; i++) { 84 | strcpy(file_entries[i].short_name, volume_names[i]); 85 | strcpy(file_entries[i].long_name, volume_names[i]); 86 | file_entries[i].dir = true; 87 | file_entries[i].dir_index = i; 88 | file_entries[i].last_file = false; 89 | if(last_file_name[0] && !strcmp(file_entries[i].short_name, last_file_name)) 90 | file_entries[i].last_file = true; 91 | } 92 | return i; 93 | } 94 | 95 | mutex_enter_blocking(&fs_lock); 96 | //f (f_mount(&fatfs[0], curr_path, 1) == FR_OK) { 97 | if (f_rewinddir(&dir) == FR_OK) { 98 | //if (f_opendir(&dir, curr_path) == FR_OK) { 99 | ret = 0; 100 | uint16_t dir_index = -1; 101 | int32_t look_for_index = page_index >= 0 ? next_page_references[page_index] : -1; 102 | while (true) { 103 | dir_index++; 104 | if (f_readdir(&dir, &fno) != FR_OK || fno.fname[0] == 0) break; 105 | if (fno.fattrib & (AM_HID | AM_SYS)) continue; 106 | bool is_dir = (fno.fattrib & AM_DIR); 107 | if (!is_dir && !is_valid_file(fno.fname)) continue; 108 | // I did not document in time this alphabetical file sorting in display page increments 109 | // logic, and now I forgot how it was working... :/, but the general idea is to get the 110 | // next n files that are just after the last portion of files displayed previously 111 | // and remember the new smallest file for the next page. For previous pages, the data is 112 | // cached in the next_page_references array. 113 | if(page_index < 0) { 114 | file_entries[page_size].dir = is_dir; 115 | file_entries[page_size].dir_index = dir_index; 116 | strcpy(file_entries[page_size].long_name, fno.fname); 117 | strcpy(file_entries[page_size].short_name, fno.altname[0] ? fno.altname : fno.fname); 118 | if(is_dir) 119 | mark_directory(page_size); 120 | if(!ret || file_entry_cmp(page_size, page_size+1) < 0) 121 | file_entries[page_size+1] = file_entries[page_size]; 122 | ret++; 123 | } else { 124 | if(look_for_index >= 0 && dir_index != look_for_index) 125 | continue; 126 | file_entries[ret].dir = is_dir; 127 | file_entries[ret].dir_index = dir_index; 128 | strcpy(file_entries[ret].long_name, fno.fname); 129 | strcpy(file_entries[ret].short_name, fno.altname[0] ? fno.altname : fno.fname); 130 | file_entries[ret].last_file = false; 131 | if(is_dir) mark_directory(ret); 132 | if(last_file_name[0] && !strcmp(file_entries[ret].short_name, last_file_name)) 133 | file_entries[ret].last_file = true; 134 | if(!ret) { 135 | ret++; 136 | look_for_index = -1; 137 | dir_index = -1; 138 | f_rewinddir(&dir); 139 | continue; 140 | } 141 | if(file_entry_cmp(ret, 0) <= 0) 142 | continue; 143 | int32_t ri = ret; 144 | while(ri > 0 && file_entry_cmp(ri, ri-1) < 0) { 145 | file_entry_type fet = file_entries[ri]; 146 | file_entries[ri] = file_entries[ri-1]; 147 | file_entries[ri-1] = fet; 148 | ri--; 149 | } 150 | if(ret < page_size) { 151 | ret++; 152 | }else if(look_for_index == -1 || file_entry_cmp(page_size, page_size+1) <= 0) { 153 | look_for_index = -2; 154 | file_entries[page_size+1] = file_entries[page_size]; 155 | } 156 | } 157 | next_page_references[page_index+1] = file_entries[page_size+1].dir_index; 158 | } 159 | // f_closedir(&dir); 160 | } 161 | //f_mount(0, curr_path, 1); 162 | //} 163 | mutex_exit(&fs_lock); 164 | // sleep_ms(3000); // For testing to emulate long directory reading 165 | return ret; 166 | } 167 | 168 | #include "disk_images_data.h" 169 | 170 | int16_t create_new_disk_image() { 171 | FIL fil; 172 | FRESULT f_op_stat; 173 | 174 | uint8_t new_file_size_index = (create_new_file & 0xF)-1; // SD ED DD QD 175 | uint8_t new_file_format_index = ((create_new_file >> 4 ) & 0xF)-1; // None DOS MyDOS Sparta 176 | uint32_t image_size = image_sizes[new_file_size_index]; 177 | bool new_file_boot = ((create_new_file >> 8 ) & 0x1); 178 | f_op_stat = FR_OK; 179 | uint disk_image_size = disk_images_size[new_file_size_index][new_file_format_index]; 180 | memcpy(sector_buffer, (uint8_t *)(disk_images_data[new_file_size_index][new_file_format_index]), disk_image_size); 181 | if(new_file_boot) 182 | memcpy(§or_buffer[256], dummy_boot, dummy_boot_len); 183 | uint32_t bs; 184 | uint32_t ints; 185 | uint vol_num = temp_array[0]-'0'; 186 | if(!vol_num) { 187 | ints = save_and_disable_interrupts(); 188 | multicore_lockout_start_blocking(); 189 | } 190 | do { 191 | //if((f_op_stat = f_mount(&fatfs[0], temp_array, 1)) != FR_OK) 192 | // break; 193 | #if FF_MAX_SS != FF_MIN_SS 194 | bs = fatfs[vol_num].csize * fatfs[vol_num].ssize; 195 | #else 196 | bs = fatfs[vol_num].csize * FF_MAX_SS; 197 | #endif 198 | image_size = (image_size + bs - 1) / bs; 199 | FATFS *ff; 200 | if((f_op_stat = f_getfree(temp_array, &bs, &ff)) != FR_OK) 201 | break; 202 | if(bs < image_size) { 203 | f_op_stat = FR_INT_ERR; 204 | break; 205 | } 206 | if((f_op_stat = f_open(&fil, temp_array, FA_CREATE_NEW | FA_WRITE)) != FR_OK) 207 | break; 208 | uint ind=0; 209 | while(ind < disk_image_size) { 210 | memcpy(&bs, §or_buffer[ind], 4); 211 | ind += 4; 212 | bool nrb = (bs & 0x80000000) ? true : false; 213 | bs &= 0x7FFFFFFF; 214 | UINT wrt; 215 | while(bs > 0) { 216 | // if(f_putc(sector_buffer[ind], &fil) == -1) { 217 | if(f_write(&fil, §or_buffer[ind], 1, &wrt) != FR_OK || wrt != 1) { 218 | f_op_stat = FR_INT_ERR; 219 | break; 220 | } 221 | if(nrb) ind++; 222 | bs--; 223 | } 224 | if(f_op_stat != FR_OK) 225 | break; 226 | if(!nrb) ind++; 227 | } 228 | if(f_op_stat != FR_OK || !new_file_boot) 229 | break; 230 | if((f_op_stat = f_lseek(&fil, 16)) != FR_OK) 231 | break; 232 | f_op_stat = f_write(&fil, §or_buffer[256], dummy_boot_len, &ind); 233 | } while(false); 234 | f_close(&fil); 235 | //f_mount(0, temp_array, 1); 236 | if(!vol_num) { 237 | multicore_lockout_end_blocking(); 238 | restore_interrupts(ints); 239 | } 240 | return (f_op_stat != FR_OK) ? -1 : 0; 241 | /* 242 | if(f_op_stat != FR_OK) { 243 | create_new_file = -1; 244 | // Access error was not Atari drive specific 245 | last_access_error_drive = 5; 246 | }else 247 | create_new_file = 0; 248 | */ 249 | 250 | } 251 | -------------------------------------------------------------------------------- /file_load.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the a8-pico-sio project -- 3 | * An Atari 8-bit SIO drive and (turbo) tape emulator for 4 | * Raspberry Pi Pico, see 5 | * 6 | * https://github.com/woj76/a8-pico-sio 7 | * 8 | * For information on what / whose work it is based on, check the corresponding 9 | * source files and the README file. This file is licensed under GNU General 10 | * Public License 3.0 or later. 11 | * 12 | * Copyright (C) 2025 Wojciech Mostowski 13 | */ 14 | 15 | #pragma once 16 | 17 | #include 18 | #include 19 | #include "config.h" 20 | #include "ff.h" 21 | 22 | #define MAX_PATH_LEN 512 23 | 24 | extern char curr_path[]; 25 | extern size_t num_files; 26 | extern size_t num_files_page; 27 | extern DIR dir; 28 | 29 | extern char temp_array[]; 30 | extern volatile int16_t create_new_file; 31 | 32 | typedef struct __attribute__((__packed__)) { 33 | bool dir; 34 | char short_name[15]; 35 | char long_name[258]; 36 | uint16_t dir_index; 37 | bool last_file; 38 | } file_entry_type; 39 | 40 | #define max_files_per_page 12 41 | 42 | extern file_entry_type file_entries[]; 43 | 44 | extern char last_file_name[]; 45 | 46 | size_t get_filename_ext(char *filename); 47 | 48 | int32_t read_directory(int32_t page_index, int page_size); 49 | int16_t create_new_disk_image(); 50 | -------------------------------------------------------------------------------- /firmware/a8_pico1_sio_board.uf2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/firmware/a8_pico1_sio_board.uf2 -------------------------------------------------------------------------------- /firmware/a8_pico1_sio_standalone.uf2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/firmware/a8_pico1_sio_standalone.uf2 -------------------------------------------------------------------------------- /firmware/a8_pico2_sio_board.uf2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/firmware/a8_pico2_sio_board.uf2 -------------------------------------------------------------------------------- /firmware/a8_pico2_sio_standalone.uf2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/firmware/a8_pico2_sio_standalone.uf2 -------------------------------------------------------------------------------- /firmware/a8_pico2_sio_zaxon.uf2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/firmware/a8_pico2_sio_zaxon.uf2 -------------------------------------------------------------------------------- /firmware/a8_pimoroni16mb_sio_board.uf2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/firmware/a8_pimoroni16mb_sio_board.uf2 -------------------------------------------------------------------------------- /firmware/a8_pimoroni16mb_sio_standalone.uf2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/firmware/a8_pimoroni16mb_sio_standalone.uf2 -------------------------------------------------------------------------------- /flash_fs.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the a8-pico-sio project -- 3 | * An Atari 8-bit SIO drive and (turbo) tape emulator for 4 | * Raspberry Pi Pico, see 5 | * 6 | * https://github.com/woj76/a8-pico-sio 7 | * 8 | * For information on what / whose work it is based on, check below and the 9 | * corresponding project repository. 10 | */ 11 | 12 | /* This file or its parts come originally from the A8PicoCart 13 | * project, see https://github.com/robinhedwards/A8PicoCart for additional 14 | * information, license, and credits (see also below). The major modification is 15 | * the ability to handle various sizes of the Pico FLASH memory that stores the 16 | * FAT file system, not only the 15MB drive on a 16MB Pico clone. 17 | */ 18 | 19 | /** 20 | * _ ___ ___ _ ___ _ 21 | * /_\ ( _ ) _ (_)__ _ / __|__ _ _ _| |_ 22 | * / _ \/ _ \ _/ / _/_\ (__/ _` | '_| _| 23 | * /_/ \_\___/_| |_\__\_/\___\__,_|_| \__| 24 | * 25 | * 26 | * Atari 8-bit cartridge for Raspberry Pi Pico 27 | * 28 | * Robin Edwards 2023 29 | */ 30 | 31 | #include "hardware/flash.h" 32 | #include "hardware/sync.h" 33 | 34 | #include 35 | 36 | #include "flash_fs.h" 37 | 38 | #define MAGIC_8_BYTES "RHE!FS30" 39 | 40 | typedef struct { 41 | uint8_t header[8]; 42 | uint16_t sectors[NUM_FAT_SECTORS]; // map FAT sectors -> flash sectors 43 | } sector_map; 44 | 45 | sector_map fs_map; 46 | 47 | bool fs_map_needs_written[MAP_ENTRIES]; 48 | 49 | uint8_t used_bitmap[NUM_FLASH_SECTORS]; // we will use 256 flash sectors for 2048 fat sectors 50 | 51 | uint16_t write_sector = 0; // which flash sector we are writing to 52 | uint8_t write_sector_bitmap = 0; // 1 for each free 512 byte page on the sector 53 | 54 | uint16_t get_map_sector(uint16_t mapEntry) { 55 | return (mapEntry & 0xFFF8) >> 3; 56 | } 57 | 58 | uint8_t get_map_offset(uint16_t mapEntry) { 59 | return mapEntry & 0x7; 60 | } 61 | 62 | uint16_t make_map_entry(uint16_t sector, uint8_t offset) { 63 | return (sector << 3) | offset; 64 | } 65 | 66 | void flash_read_sector(uint16_t sector, uint8_t offset, void *buffer, uint16_t size); 67 | void flash_erase_sector(uint16_t sector); 68 | void flash_write_sector(uint16_t sector, uint8_t offset, const void *buffer, uint16_t size); 69 | void flash_erase_with_copy_sector(uint16_t sector, uint8_t preserve_bitmap); 70 | 71 | void write_fs_map() { 72 | for (int i=0; i 38 | 39 | #define HW_FLASH_STORAGE_BASE (1024*1024) 40 | #define BOARD_SIZE (PICO_FLASH_SIZE_BYTES/(1024*1024)) // in MB 41 | #define NUM_FLASH_SECTORS ((BOARD_SIZE - 1) * 256) 42 | #define NUM_FAT_SECTORS (NUM_FLASH_SECTORS * 8 - 4) 43 | #define MAP_ENTRIES (BOARD_SIZE-1) 44 | 45 | int flash_fs_mount(); 46 | void flash_fs_create(); 47 | void flash_fs_sync(); 48 | void flash_fs_read_FAT_sector(uint16_t fat_sector, void *buffer); 49 | void flash_fs_write_FAT_sector(uint16_t fat_sector, const void *buffer); 50 | bool flash_fs_verify_FAT_sector(uint16_t fat_sector, const void *buffer); 51 | 52 | #ifdef __cplusplus 53 | } 54 | #endif 55 | -------------------------------------------------------------------------------- /font_atari_data.hpp: -------------------------------------------------------------------------------- 1 | // THIS FILE IS GENERATED AUTOMATICALLY! 2 | 3 | #pragma once 4 | 5 | #include "bitmap_fonts.hpp" 6 | 7 | const bitmap::font_t atari_font { 8 | .height = 8, 9 | .max_width = 8, 10 | .widths = {8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8}, 11 | .data = { 12 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 13 | 0x00,0x00,0x00,0x5E,0x5E,0x00,0x00,0x00, 14 | 0x00,0x0E,0x0E,0x00,0x00,0x0E,0x0E,0x00, 15 | 0x24,0x7E,0x7E,0x24,0x24,0x7E,0x7E,0x24, 16 | 0x00,0x24,0x2E,0x6B,0x6B,0x3A,0x12,0x00, 17 | 0x00,0x66,0x36,0x18,0x0C,0x66,0x62,0x00, 18 | 0x00,0x30,0x7A,0x4F,0x5D,0x37,0x72,0x50, 19 | 0x00,0x00,0x00,0x0E,0x0E,0x00,0x00,0x00, 20 | 0x00,0x00,0x00,0x3C,0x7E,0x66,0x42,0x00, 21 | 0x00,0x42,0x66,0x7E,0x3C,0x00,0x00,0x00, 22 | 0x08,0x2A,0x3E,0x1C,0x1C,0x3E,0x2A,0x08, 23 | 0x00,0x08,0x08,0x3E,0x3E,0x08,0x08,0x00, 24 | 0x00,0x00,0x80,0xE0,0x60,0x00,0x00,0x00, 25 | 0x00,0x08,0x08,0x08,0x08,0x08,0x08,0x00, 26 | 0x00,0x00,0x00,0x60,0x60,0x00,0x00,0x00, 27 | 0x00,0x60,0x30,0x18,0x0C,0x06,0x02,0x00, 28 | 0x00,0x3C,0x7E,0x52,0x4A,0x7E,0x3C,0x00, 29 | 0x00,0x40,0x44,0x7E,0x7E,0x40,0x40,0x00, 30 | 0x00,0x44,0x66,0x72,0x5A,0x4E,0x44,0x00, 31 | 0x00,0x22,0x62,0x4A,0x5E,0x76,0x22,0x00, 32 | 0x00,0x30,0x38,0x2C,0x7E,0x7E,0x20,0x00, 33 | 0x00,0x2E,0x6E,0x4A,0x4A,0x7A,0x32,0x00, 34 | 0x00,0x3C,0x7E,0x4A,0x4A,0x7A,0x30,0x00, 35 | 0x00,0x02,0x62,0x72,0x1A,0x0E,0x06,0x00, 36 | 0x00,0x34,0x7E,0x4A,0x4A,0x7E,0x34,0x00, 37 | 0x00,0x04,0x4E,0x4A,0x6A,0x3E,0x1C,0x00, 38 | 0x00,0x00,0x00,0x6C,0x6C,0x00,0x00,0x00, 39 | 0x00,0x00,0x80,0xEC,0x6C,0x00,0x00,0x00, 40 | 0x00,0x00,0x08,0x1C,0x36,0x63,0x41,0x00, 41 | 0x00,0x24,0x24,0x24,0x24,0x24,0x24,0x00, 42 | 0x00,0x41,0x63,0x36,0x1C,0x08,0x00,0x00, 43 | 0x00,0x04,0x06,0x52,0x5A,0x0E,0x04,0x00, 44 | 0x00,0x3C,0x7E,0x42,0x5A,0x5E,0x5C,0x00, 45 | 0x00,0x78,0x7C,0x26,0x26,0x7C,0x78,0x00, 46 | 0x00,0x7E,0x7E,0x4A,0x4A,0x7E,0x34,0x00, 47 | 0x00,0x3C,0x7E,0x42,0x42,0x66,0x24,0x00, 48 | 0x00,0x7E,0x7E,0x42,0x66,0x3C,0x18,0x00, 49 | 0x00,0x7E,0x7E,0x4A,0x4A,0x4A,0x42,0x00, 50 | 0x00,0x7E,0x7E,0x0A,0x0A,0x0A,0x02,0x00, 51 | 0x00,0x3C,0x7E,0x42,0x52,0x72,0x72,0x00, 52 | 0x00,0x7E,0x7E,0x08,0x08,0x7E,0x7E,0x00, 53 | 0x00,0x42,0x42,0x7E,0x7E,0x42,0x42,0x00, 54 | 0x00,0x20,0x60,0x40,0x40,0x7E,0x3E,0x00, 55 | 0x00,0x7E,0x7E,0x18,0x3C,0x66,0x42,0x00, 56 | 0x00,0x7E,0x7E,0x40,0x40,0x40,0x40,0x00, 57 | 0x00,0x7E,0x7E,0x0C,0x18,0x0C,0x7E,0x7E, 58 | 0x00,0x7E,0x7E,0x1C,0x38,0x7E,0x7E,0x00, 59 | 0x00,0x3C,0x7E,0x42,0x42,0x7E,0x3C,0x00, 60 | 0x00,0x7E,0x7E,0x12,0x12,0x1E,0x0C,0x00, 61 | 0x00,0x3C,0x7E,0x42,0x22,0x7E,0x5C,0x00, 62 | 0x00,0x7E,0x7E,0x12,0x32,0x7E,0x4C,0x00, 63 | 0x00,0x04,0x4E,0x4A,0x4A,0x7A,0x30,0x00, 64 | 0x00,0x02,0x02,0x7E,0x7E,0x02,0x02,0x00, 65 | 0x00,0x7E,0x7E,0x40,0x40,0x7E,0x7E,0x00, 66 | 0x00,0x1E,0x3E,0x60,0x60,0x3E,0x1E,0x00, 67 | 0x00,0x7E,0x7E,0x30,0x18,0x30,0x7E,0x7E, 68 | 0x00,0x66,0x7E,0x18,0x18,0x7E,0x66,0x00, 69 | 0x00,0x06,0x0E,0x78,0x78,0x0E,0x06,0x00, 70 | 0x00,0x62,0x72,0x5A,0x4E,0x46,0x42,0x00, 71 | 0x00,0x00,0x00,0x7E,0x7E,0x42,0x42,0x00, 72 | 0x00,0x06,0x0C,0x18,0x30,0x60,0x40,0x00, 73 | 0x00,0x42,0x42,0x7E,0x7E,0x00,0x00,0x00, 74 | 0x00,0x10,0x18,0x0C,0x06,0x0C,0x18,0x10, 75 | 0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, 76 | 0x00,0x00,0x02,0x06,0x0C,0x08,0x00,0x00, 77 | 0x00,0x20,0x74,0x54,0x54,0x7C,0x78,0x00, 78 | 0x00,0x7E,0x7E,0x48,0x48,0x78,0x30,0x00, 79 | 0x00,0x38,0x7C,0x44,0x44,0x44,0x00,0x00, 80 | 0x00,0x30,0x78,0x48,0x48,0x7E,0x7E,0x00, 81 | 0x00,0x38,0x7C,0x54,0x54,0x5C,0x18,0x00, 82 | 0x00,0x00,0x08,0x7C,0x7E,0x0A,0x0A,0x00, 83 | 0x00,0x98,0xBC,0xA4,0xA4,0xFC,0x7C,0x00, 84 | 0x00,0x7E,0x7E,0x08,0x08,0x78,0x70,0x00, 85 | 0x00,0x00,0x48,0x7A,0x7A,0x40,0x00,0x00, 86 | 0x00,0x00,0x80,0x80,0x80,0xFA,0x7A,0x00, 87 | 0x00,0x7E,0x7E,0x10,0x38,0x68,0x40,0x00, 88 | 0x00,0x00,0x42,0x7E,0x7E,0x40,0x00,0x00, 89 | 0x00,0x7C,0x7C,0x18,0x38,0x1C,0x7C,0x78, 90 | 0x00,0x7C,0x7C,0x04,0x04,0x7C,0x78,0x00, 91 | 0x00,0x38,0x7C,0x44,0x44,0x7C,0x38,0x00, 92 | 0x00,0xFC,0xFC,0x24,0x24,0x3C,0x18,0x00, 93 | 0x00,0x18,0x3C,0x24,0x24,0xFC,0xFC,0x00, 94 | 0x00,0x7C,0x7C,0x04,0x04,0x0C,0x08,0x00, 95 | 0x00,0x48,0x5C,0x54,0x54,0x74,0x24,0x00, 96 | 0x00,0x04,0x04,0x3E,0x7E,0x44,0x44,0x00, 97 | 0x00,0x3C,0x7C,0x40,0x40,0x7C,0x7C,0x00, 98 | 0x00,0x1C,0x3C,0x60,0x60,0x3C,0x1C,0x00, 99 | 0x00,0x1C,0x7C,0x70,0x38,0x70,0x7C,0x1C, 100 | 0x00,0x44,0x6C,0x38,0x38,0x6C,0x44,0x00, 101 | 0x00,0x9C,0xBC,0xA0,0xE0,0x7C,0x3C,0x00, 102 | 0x00,0x44,0x64,0x74,0x5C,0x4C,0x44,0x00, 103 | 0x00,0x08,0x08,0x3E,0x77,0x41,0x41,0x00, 104 | 0x00,0x00,0x00,0x7F,0x7F,0x00,0x00,0x00, 105 | 0x00,0x41,0x41,0x77,0x3E,0x08,0x08,0x00, 106 | 0x00,0x08,0x04,0x08,0x08,0x10,0x08,0x00, 107 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 108 | 0x70,0x18,0x14,0x7E,0x7E,0x4A,0x42,0x00, 109 | 0x00,0x7E,0x7E,0x24,0x24,0x3C,0x18,0x00, 110 | 0x00,0xFC,0xFE,0x02,0x4A,0x7E,0x34,0x00, 111 | 0x20,0x74,0x5C,0x38,0x74,0x5C,0x48,0x00, 112 | 0x00,0x7E,0x7E,0x18,0x24,0x24,0x18,0x00, 113 | 0x00,0x48,0x7E,0x7F,0x49,0x41,0x40,0x00, 114 | 0x00,0x2A,0x2C,0x78,0x78,0x2C,0x2A,0x00, 115 | 0x1C,0x22,0x49,0x55,0x55,0x41,0x22,0x1C, 116 | 0x00,0x00,0x04,0x0A,0x0A,0x04,0x00,0x00, 117 | 7,6, 0x00,0x00,0x00,0x01,0x03,0x02,0x00,0x00, // Grave 118 | 7,6, 0x00,0x00,0x00,0x02,0x03,0x01,0x00,0x00, // Acute 119 | 7,6, 0x00,0x02,0x03,0x01,0x01,0x03,0x02,0x00, // Circumflex 120 | 7,6, 0x00,0x01,0x03,0x02,0x01,0x03,0x02,0x00, // Tilde 121 | 7,6, 0x00,0x03,0x03,0x00,0x00,0x03,0x03,0x00, // Diaresis 122 | 6,5, 0x00,0x00,0x02,0x05,0x05,0x02,0x00,0x00, // Ring Above 123 | 7,6, 0x00,0x00,0x80,0xC0,0x40,0x00,0x00,0x00, // Stroke 124 | 10,10, 0x00,0x00,0x80,0xa0,0xE0,0x40,0x00,0x00 // Cedilla 125 | } 126 | }; 127 | 128 | const bitmap::font_t symbol_font { 129 | .height = 8, 130 | .max_width = 8, 131 | .widths = {8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8}, 132 | .data = { 133 | 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 134 | 0x08,0x0C,0xFE,0xFF,0xFF,0xFE,0x0C,0x08, 135 | 0x10,0x30,0x7F,0xFF,0xFF,0x7F,0x30,0x10, 136 | 0xC3,0xE7,0x7E,0x3C,0x3C,0x7E,0xE7,0xC3, 137 | 0x04,0x8E,0xDF,0xFE,0xFC,0xF8,0xFC,0xFE, 138 | 0xC1,0xC3,0xC7,0xCF,0xCF,0xC7,0xC3,0xC1, 139 | 0xC8,0xCC,0xCE,0xCF,0xCF,0xCE,0xCC,0xC8, 140 | 0x00,0x7E,0x7E,0x3C,0x3C,0x18,0x18,0x00, 141 | 0x00,0x7E,0x7E,0x7E,0x7E,0x7E,0x7E,0x00, 142 | 0x00,0xC0,0xF0,0xF8,0xFC,0xFC,0xFE,0xFE, 143 | 0xFE,0xFE,0xFC,0xFC,0xF8,0xF0,0xC0,0x00, 144 | 0x00,0x03,0x0F,0x1F,0x3F,0x3F,0x7F,0x7F, 145 | 0x7F,0x7F,0x3F,0x3F,0x1F,0x0F,0x03,0x00, 146 | 0x00,0x04,0x0E,0x1C,0x38,0x70,0xE0,0xC0, 147 | 0xC0,0xE0,0x70,0x38,0x1C,0x0E,0x04,0x00, 148 | 0x00,0x20,0x70,0x38,0x1C,0x0E,0x07,0x03, 149 | 0x03,0x07,0x0E,0x1C,0x38,0x70,0x20,0x00 150 | } 151 | }; -------------------------------------------------------------------------------- /images/all_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/images/all_1.jpg -------------------------------------------------------------------------------- /images/all_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/images/all_2.jpg -------------------------------------------------------------------------------- /images/bare_connections.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/images/bare_connections.png -------------------------------------------------------------------------------- /images/board_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/images/board_1.jpg -------------------------------------------------------------------------------- /images/board_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/images/board_2.jpg -------------------------------------------------------------------------------- /images/board_connections.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/images/board_connections.png -------------------------------------------------------------------------------- /images/joystick_plug_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/images/joystick_plug_1.jpg -------------------------------------------------------------------------------- /images/joystick_plug_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/images/joystick_plug_2.jpg -------------------------------------------------------------------------------- /images/working.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woj76/a8-pico-sio/ee4628c07d9efb21d4a14f3ad02e77a60d145add/images/working.jpg -------------------------------------------------------------------------------- /io.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the a8-pico-sio project -- 3 | * An Atari 8-bit SIO drive and (turbo) tape emulator for 4 | * Raspberry Pi Pico, see 5 | * 6 | * https://github.com/woj76/a8-pico-sio 7 | * 8 | * For information on what / whose work it is based on, check the corresponding 9 | * source files and the README file. This file is licensed under GNU General 10 | * Public License 3.0 or later. 11 | * 12 | * Copyright (C) 2025 Wojciech Mostowski 13 | */ 14 | 15 | #include "config.h" 16 | 17 | #include "hardware/clocks.h" 18 | #include "hardware/pio.h" 19 | #include "hardware/dma.h" 20 | #include "hardware/irq.h" 21 | #include "hardware/uart.h" 22 | #include "pico/util/queue.h" 23 | 24 | #include "io.hpp" 25 | #include "wav_decode.hpp" 26 | 27 | #include "options.hpp" 28 | #include "led_indicator.hpp" 29 | #include "pin_io.pio.h" 30 | 31 | // const uint led_pin = 25; 32 | 33 | const uint sio_tx_pin = 4; 34 | const uint sio_rx_pin = 5; 35 | const uint normal_motor_pin = 10; 36 | const uint command_line_pin = 11; 37 | const uint proceed_pin = 21; 38 | const uint interrupt_pin = 22; 39 | const uint joy2_p1_pin = 26; 40 | const uint joy2_p2_pin = 27; 41 | const uint joy2_p4_pin = 28; 42 | 43 | // Conventional SIO, but also Turbo D & Turbo 6000 44 | const uint32_t normal_motor_pin_mask = (1u << normal_motor_pin); 45 | const uint32_t normal_motor_value_on = (MOTOR_ON_STATE << normal_motor_pin); 46 | 47 | // Turbo 2000 KSO - Joy 2 port 48 | const uint32_t kso_motor_pin_mask = (1u << joy2_p2_pin); 49 | const uint32_t kso_motor_value_on = (0u << joy2_p2_pin); 50 | 51 | // Turbo 2001 / 2000F and sorts over SIO data 52 | const uint32_t comm_motor_pin_mask = (1u << command_line_pin) | (1u << normal_motor_pin); 53 | const uint32_t comm_motor_value_on = (0u << command_line_pin) | (MOTOR_ON_STATE << normal_motor_pin); 54 | 55 | // Turbo Blizzard - SIO Data Out 56 | const uint32_t sio_motor_pin_mask = (1u << sio_rx_pin) | (1u << normal_motor_pin); 57 | const uint32_t sio_motor_value_on = (0u << sio_rx_pin) | (MOTOR_ON_STATE << normal_motor_pin); 58 | 59 | const uint opt_to_turbo_data_pin[] = {sio_tx_pin, joy2_p4_pin, proceed_pin, interrupt_pin, joy2_p1_pin}; 60 | const uint32_t opt_to_turbo_motor_pin_mask[] = {comm_motor_pin_mask, kso_motor_pin_mask, sio_motor_pin_mask, normal_motor_pin_mask}; 61 | const uint32_t opt_to_turbo_motor_pin_val[] = {comm_motor_value_on, kso_motor_value_on, sio_motor_value_on, normal_motor_value_on}; 62 | 63 | const uint hsio_opt_to_baud_ntsc[] = {19040, 38908, 68838, 74575, 81354, 89490, 99433, 111862, 127842}; 64 | const uint hsio_opt_to_baud_pal[] = {18866, 38553, 68210, 73894, 80611, 88672, 98525, 110840, 126675}; 65 | // PAL/NTSC average 66 | // const uint hsio_opt_to_baud[] = {18953, 38731, 68524, 74234, 80983, 89081, 98979, 111351, 127258}; 67 | 68 | 69 | uint32_t turbo_motor_pin_mask; 70 | uint32_t turbo_motor_value_on; 71 | uint turbo_data_pin; 72 | 73 | volatile bool cas_block_turbo; 74 | volatile bool dma_block_turbo; 75 | uint sm; 76 | uint sm_turbo; 77 | 78 | // Turbo 2000 KSO 79 | // const uint turbo_data_pin = joy2_p4_pin; // Joy 2 port, pin 4 80 | // Turbo D 81 | //const uint turbo_data_pin = joy2_p1_pin; // Joy 2 port, pin 1 82 | // Turbo 2001 / 2000F SIO / Turbo Blizzard 83 | //const uint turbo_data_pin = sio_tx_pin; 84 | // Turbo 6000, this needs pwm_bit invertion 85 | // const uint turbo_data_pin = proceed_pin; 86 | // Rambit, also needs bit inversion 87 | //const uint turbo_data_pin = interrupt_pin; 88 | 89 | uint32_t timing_base_clock; 90 | uint32_t max_clock_ms; 91 | 92 | pio_sm_config sm_config_turbo; 93 | uint pio_offset; 94 | 95 | int8_t turbo_conf[] = {-1, -1, -1}; 96 | 97 | queue_t pio_queue; 98 | // 10*8 is not enough for Turbo D 9000, but going wild here costs memory, each item is 4 bytes 99 | // 16*8 also fails sometimes with the 1MHz base clock 100 | // The WAV decoding now seems to work with 64, but increasing it might be a good idea if some WAV file is not working 101 | // WAV files with 96000 sample rate also prefer this to be more than 64 102 | const int pio_queue_size = 96*8; 103 | 104 | uint32_t pio_e; 105 | 106 | void reinit_pio() { 107 | if(turbo_conf[0] != current_options[turbo1_option_index]) { 108 | if(turbo_conf[0] >= 0) { 109 | pio_sm_set_enabled(cas_pio, sm_turbo, false); 110 | //gpio_set_function(turbo_data_pin, turbo_data_pin == sio_rx_pin ? GPIO_FUNC_UART : GPIO_FUNC_NULL); 111 | gpio_set_function(turbo_data_pin, GPIO_FUNC_NULL); 112 | } 113 | turbo_conf[0] = current_options[turbo1_option_index]; 114 | turbo_data_pin = opt_to_turbo_data_pin[turbo_conf[0]]; 115 | pio_gpio_init(cas_pio, turbo_data_pin); 116 | sm_config_set_out_pins(&sm_config_turbo, turbo_data_pin, 1); 117 | pio_sm_set_consecutive_pindirs(cas_pio, sm_turbo, turbo_data_pin, 1, true); 118 | pio_sm_init(cas_pio, sm_turbo, pio_offset, &sm_config_turbo); 119 | pio_sm_set_enabled(cas_pio, sm_turbo, true); 120 | } 121 | turbo_conf[1] = current_options[turbo2_option_index]; 122 | turbo_motor_pin_mask = opt_to_turbo_motor_pin_mask[turbo_conf[1]]; 123 | turbo_motor_value_on = opt_to_turbo_motor_pin_val[turbo_conf[1]]; 124 | turbo_conf[2] = current_options[turbo3_option_index]; 125 | //while(!queue_is_empty(&pio_queue)) 126 | // tight_loop_contents(); 127 | pio_sm_restart(cas_pio, sm); 128 | pio_sm_restart(cas_pio, sm_turbo); 129 | } 130 | 131 | volatile bool dma_going = false; 132 | int dma_channel, dma_channel_turbo; 133 | 134 | 135 | static void dma_handler() { 136 | int dc = dma_block_turbo ? dma_channel_turbo : dma_channel; 137 | if(dma_going) 138 | dma_hw->ints1 = 1u << dc; 139 | else 140 | dma_going = true; 141 | if(queue_try_remove(&pio_queue, &pio_e)) 142 | dma_channel_start(dc); 143 | else 144 | dma_going = false; 145 | } 146 | 147 | bool cas_motor_on() { 148 | return (cas_block_turbo ? turbo_motor_value_on : normal_motor_value_on) == 149 | (gpio_get_all() & (cas_block_turbo ? turbo_motor_pin_mask : normal_motor_pin_mask)); 150 | } 151 | 152 | void flush_pio() { 153 | wav_sample_size = 0; 154 | pio_sm_set_enabled(cas_pio, cas_block_turbo ? sm_turbo : sm, true); 155 | blue_blinks = 0; 156 | green_blinks = 0; 157 | update_rgb_led(false); 158 | //while(!queue_is_empty(&pio_queue)) 159 | // tight_loop_contents(); 160 | //pio_interrupt_clear(cas_pio, 7); 161 | } 162 | 163 | static bool repeating_timer_motor(struct repeating_timer *t) { 164 | #if MOTOR_OFF_DELAY > 0 165 | static uint motor_off_delay = MOTOR_OFF_DELAY; 166 | #endif 167 | #if MOTOR_ON_DELAY > 0 168 | static uint motor_on_delay = MOTOR_ON_DELAY; 169 | #endif 170 | if(wav_sample_size) { 171 | if(!cas_motor_on()) { 172 | #if MOTOR_ON_DELAY > 0 173 | motor_on_delay = MOTOR_ON_DELAY; 174 | #endif 175 | #if MOTOR_OFF_DELAY > 0 176 | if(motor_off_delay) 177 | motor_off_delay--; 178 | else 179 | #endif 180 | { 181 | //pio_sm_exec(cas_pio, cas_block_turbo ? sm_turbo : sm, pio_encode_irq_set(false, 7)); 182 | ////sleep_ms(1); 183 | //pio_sm_exec(cas_pio, cas_block_turbo ? sm_turbo : sm, pio_encode_wait_irq(0, false, 7)); 184 | pio_sm_set_enabled(cas_pio, cas_block_turbo ? sm_turbo : sm, false); 185 | blue_blinks = 0; 186 | green_blinks = 0; 187 | update_rgb_led(false); 188 | } 189 | } else { 190 | #if MOTOR_OFF_DELAY > 0 191 | motor_off_delay = MOTOR_OFF_DELAY; 192 | #endif 193 | #if MOTOR_ON_DELAY > 0 194 | if(motor_on_delay) 195 | motor_on_delay--; 196 | else 197 | #endif 198 | { 199 | //pio_interrupt_clear(cas_pio, 7); 200 | pio_sm_set_enabled(cas_pio, cas_block_turbo ? sm_turbo : sm, true); 201 | if(cas_block_turbo) 202 | blue_blinks = -1; 203 | green_blinks = -1; 204 | update_rgb_led(false); 205 | } 206 | 207 | } 208 | } 209 | return true; 210 | } 211 | 212 | void pio_enqueue(uint8_t b, uint32_t d) { 213 | uint32_t e = (b^(cas_block_turbo ? turbo_conf[2] : 0) | ((d - pio_prog_cycle_corr) << 1)); 214 | // queue_try_add(&pio_queue, &e); 215 | // absolute_time_t t = make_timeout_time_ms(1250); 216 | // while (!gpio_get(command_line_pin) && absolute_time_diff_us(get_absolute_time(), t) > 0) 217 | // tight_loop_contents(); 218 | queue_add_blocking(&pio_queue, &e); 219 | if(!dma_going) { 220 | dma_block_turbo = cas_block_turbo; 221 | dma_handler(); 222 | } 223 | } 224 | 225 | void init_io() { 226 | 227 | #ifdef FULL_SPEED_PIO 228 | timing_base_clock = clock_get_hz(clk_sys); 229 | #else 230 | timing_base_clock = 1000000; 231 | #endif 232 | // How much "silence" can the PIO produce in one step: 233 | max_clock_ms = 0x7FFFFFFF/(timing_base_clock/1000)/1000*1000; 234 | 235 | gpio_init(joy2_p2_pin); gpio_set_dir(joy2_p2_pin, GPIO_IN); gpio_pull_up(joy2_p2_pin); 236 | 237 | gpio_init(normal_motor_pin); gpio_set_dir(normal_motor_pin, GPIO_IN); 238 | 239 | // Pico2 bug! This pull down will lock the pin state high after the first read, instead 240 | // one has to use an external pull down resistor of not too high value, for example 4.7kohm 241 | // (reports sugguest that it has to be below 9kohm) 242 | #ifndef RASPBERRYPI_PICO2 243 | #if MOTOR_ON_STATE == 1 244 | gpio_pull_down(normal_motor_pin); 245 | #endif 246 | #endif 247 | gpio_init(command_line_pin); gpio_set_dir(command_line_pin, GPIO_IN); gpio_pull_up(command_line_pin); 248 | 249 | queue_init(&pio_queue, sizeof(uint32_t), pio_queue_size); 250 | 251 | pio_offset = pio_add_program(cas_pio, &pin_io_program); 252 | int clk_divider = clock_get_hz(clk_sys)/timing_base_clock; 253 | 254 | sm = pio_claim_unused_sm(cas_pio, true); 255 | pio_gpio_init(cas_pio, sio_tx_pin); 256 | pio_sm_set_consecutive_pindirs(cas_pio, sm, sio_tx_pin, 1, true); 257 | pio_sm_config c = pin_io_program_get_default_config(pio_offset); 258 | sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); 259 | sm_config_set_clkdiv_int_frac(&c, clk_divider, 0); 260 | 261 | sm_config_set_out_pins(&c, sio_tx_pin, 1); 262 | sm_config_set_out_shift(&c, true, true, 32); 263 | pio_sm_init(cas_pio, sm, pio_offset, &c); 264 | pio_sm_set_enabled(cas_pio, sm, true); 265 | 266 | sm_turbo = pio_claim_unused_sm(cas_pio, true); 267 | 268 | sm_config_turbo = pin_io_program_get_default_config(pio_offset); 269 | sm_config_set_fifo_join(&sm_config_turbo, PIO_FIFO_JOIN_TX); 270 | 271 | sm_config_set_clkdiv_int_frac(&sm_config_turbo, clk_divider, 0); 272 | 273 | sm_config_set_out_shift(&sm_config_turbo, true, true, 32); 274 | 275 | reinit_pio(); 276 | 277 | dma_channel = dma_claim_unused_channel(true); 278 | dma_channel_config dma_c = dma_channel_get_default_config(dma_channel); 279 | channel_config_set_transfer_data_size(&dma_c, DMA_SIZE_32); 280 | channel_config_set_read_increment(&dma_c, false); 281 | channel_config_set_dreq(&dma_c, pio_get_dreq(cas_pio, sm, true)); 282 | dma_channel_configure(dma_channel, &dma_c, &cas_pio->txf[sm], &pio_e, 1, false); 283 | dma_channel_set_irq1_enabled(dma_channel, true); 284 | 285 | dma_channel_turbo = dma_claim_unused_channel(true); 286 | dma_channel_config dma_c1 = dma_channel_get_default_config(dma_channel_turbo); 287 | channel_config_set_transfer_data_size(&dma_c1, DMA_SIZE_32); 288 | channel_config_set_read_increment(&dma_c1, false); 289 | channel_config_set_dreq(&dma_c1, pio_get_dreq(cas_pio, sm_turbo, true)); 290 | dma_channel_configure(dma_channel_turbo, &dma_c1, &cas_pio->txf[sm_turbo], &pio_e, 1, false); 291 | dma_channel_set_irq1_enabled(dma_channel_turbo, true); 292 | 293 | irq_set_exclusive_handler(DMA_IRQ_1, dma_handler); 294 | 295 | irq_set_enabled(DMA_IRQ_1, true); 296 | 297 | uart_init(uart1, current_options[clock_option_index] ? hsio_opt_to_baud_ntsc[0] : hsio_opt_to_baud_pal[0]); 298 | gpio_set_function(sio_tx_pin, GPIO_FUNC_UART); 299 | gpio_set_function(sio_rx_pin, GPIO_FUNC_UART); 300 | uart_set_hw_flow(uart1, false, false); 301 | uart_set_fifo_enabled(uart1, false); 302 | uart_set_format(uart1, 8, 1, UART_PARITY_NONE); 303 | 304 | static struct repeating_timer tmr; 305 | add_repeating_timer_ms(MOTOR_CHECK_INTERVAL_MS, repeating_timer_motor, NULL, &tmr); 306 | } 307 | -------------------------------------------------------------------------------- /io.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the a8-pico-sio project -- 3 | * An Atari 8-bit SIO drive and (turbo) tape emulator for 4 | * Raspberry Pi Pico, see 5 | * 6 | * https://github.com/woj76/a8-pico-sio 7 | * 8 | * For information on what / whose work it is based on, check the corresponding 9 | * source files and the README file. This file is licensed under GNU General 10 | * Public License 3.0 or later. 11 | * 12 | * Copyright (C) 2025 Wojciech Mostowski 13 | */ 14 | 15 | #pragma once 16 | 17 | #include 18 | #include 19 | 20 | #define MOTOR_CHECK_INTERVAL_MS 10 21 | 22 | #define cas_pio pio0 23 | #define GPIO_FUNC_PIOX GPIO_FUNC_PIO0 24 | 25 | // extern const uint led_pin; 26 | 27 | extern uint turbo_data_pin; 28 | 29 | extern const uint sio_tx_pin; 30 | extern const uint sio_rx_pin; 31 | extern const uint normal_motor_pin; 32 | extern const uint command_line_pin; 33 | extern const uint proceed_pin; 34 | extern const uint interrupt_pin; 35 | extern const uint joy2_p1_pin; 36 | extern const uint joy2_p2_pin; 37 | extern const uint joy2_p4_pin; 38 | 39 | extern const uint32_t normal_motor_pin_mask; 40 | extern const uint32_t normal_motor_value_on; 41 | 42 | extern const uint32_t kso_motor_pin_mask; 43 | extern const uint32_t kso_motor_value_on; 44 | 45 | extern const uint32_t comm_motor_pin_mask; 46 | extern const uint32_t comm_motor_value_on; 47 | 48 | extern const uint32_t sio_motor_pin_mask; 49 | extern const uint32_t sio_motor_value_on; 50 | 51 | extern uint32_t timing_base_clock; 52 | extern uint32_t max_clock_ms; 53 | 54 | extern uint32_t turbo_motor_pin_mask; 55 | extern uint32_t turbo_motor_value_on; 56 | 57 | extern int8_t turbo_conf[]; 58 | extern const uint hsio_opt_to_baud_ntsc[]; 59 | extern const uint hsio_opt_to_baud_pal[]; 60 | 61 | 62 | extern volatile bool cas_block_turbo; 63 | extern volatile bool dma_block_turbo; 64 | extern uint sm; 65 | extern uint sm_turbo; 66 | 67 | void init_io(); 68 | void reinit_pio(); 69 | void pio_enqueue(uint8_t b, uint32_t d); 70 | bool cas_motor_on(); 71 | void flush_pio(); 72 | -------------------------------------------------------------------------------- /led_indicator.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the a8-pico-sio project -- 3 | * An Atari 8-bit SIO drive and (turbo) tape emulator for 4 | * Raspberry Pi Pico, see 5 | * 6 | * https://github.com/woj76/a8-pico-sio 7 | * 8 | * For information on what / whose work it is based on, check the corresponding 9 | * source files and the README file. This file is licensed under GNU General 10 | * Public License 3.0 or later. 11 | * 12 | * Copyright (C) 2025 Wojciech Mostowski 13 | */ 14 | 15 | #include "pico/time.h" 16 | #include "hardware/sync.h" 17 | #include "libraries/pico_display_2/pico_display_2.hpp" 18 | #include "drivers/rgbled/rgbled.hpp" 19 | 20 | #include "led_indicator.hpp" 21 | 22 | volatile int8_t red_blinks = 0; 23 | volatile int8_t green_blinks = 0; 24 | volatile int8_t blue_blinks = 0; 25 | 26 | pimoroni::RGBLED led(pimoroni::PicoDisplay2::LED_R, pimoroni::PicoDisplay2::LED_G, pimoroni::PicoDisplay2::LED_B, pimoroni::Polarity::ACTIVE_LOW, 64); 27 | 28 | void update_rgb_led(bool from_interrupt) { 29 | uint32_t ints; 30 | if(!from_interrupt) 31 | ints = save_and_disable_interrupts(); 32 | uint8_t r = (red_blinks & 1) ? 128 : 0; 33 | uint8_t g = (green_blinks & 1) ? 128 : 0; 34 | uint8_t b = (blue_blinks & 1) ? 128 : 0; 35 | led.set_rgb(r, g, b); 36 | if(!from_interrupt) 37 | restore_interrupts(ints); 38 | } 39 | 40 | static bool repeating_timer_led(struct repeating_timer *t) { 41 | if(red_blinks > 0) red_blinks--; 42 | if(green_blinks > 0) green_blinks--; 43 | if(blue_blinks > 0) blue_blinks--; 44 | update_rgb_led(true); 45 | return true; 46 | } 47 | 48 | void init_rgb_led() { 49 | update_rgb_led(true); 50 | static struct repeating_timer tmr; 51 | add_repeating_timer_ms(250, repeating_timer_led, NULL, &tmr); 52 | } 53 | -------------------------------------------------------------------------------- /led_indicator.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the a8-pico-sio project -- 3 | * An Atari 8-bit SIO drive and (turbo) tape emulator for 4 | * Raspberry Pi Pico, see 5 | * 6 | * https://github.com/woj76/a8-pico-sio 7 | * 8 | * For information on what / whose work it is based on, check the corresponding 9 | * source files and the README file. This file is licensed under GNU General 10 | * Public License 3.0 or later. 11 | * 12 | * Copyright (C) 2025 Wojciech Mostowski 13 | */ 14 | 15 | #pragma once 16 | 17 | #include "config.h" 18 | 19 | extern volatile int8_t red_blinks; 20 | extern volatile int8_t green_blinks; 21 | extern volatile int8_t blue_blinks; 22 | 23 | void update_rgb_led(bool from_interrupt); 24 | void init_rgb_led(); 25 | -------------------------------------------------------------------------------- /mounts.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the a8-pico-sio project -- 3 | * An Atari 8-bit SIO drive and (turbo) tape emulator for 4 | * Raspberry Pi Pico, see 5 | * 6 | * https://github.com/woj76/a8-pico-sio 7 | * 8 | * For information on what / whose work it is based on, check the corresponding 9 | * source files and the README file. This file is licensed under GNU General 10 | * Public License 3.0 or later. 11 | * 12 | * Copyright (C) 2025 Wojciech Mostowski 13 | */ 14 | 15 | #include "config.h" 16 | 17 | #include 18 | #include 19 | 20 | #include "pico/multicore.h" 21 | #include "pico/time.h" 22 | #include "hardware/sync.h" 23 | 24 | #include "mounts.hpp" 25 | #include "led_indicator.hpp" 26 | #include "file_load.hpp" 27 | #include "io.hpp" 28 | 29 | char d1_mount[MAX_PATH_LEN] = {0}; 30 | char d2_mount[MAX_PATH_LEN] = {0}; 31 | char d3_mount[MAX_PATH_LEN] = {0}; 32 | char d4_mount[MAX_PATH_LEN] = {0}; 33 | char c_mount[MAX_PATH_LEN] = {0}; 34 | 35 | char str_d1[] = "D1: "; 36 | char str_d2[] = "D2: "; 37 | char str_d3[] = "D3: "; 38 | char str_d4[] = "D4: "; 39 | char str_cas[] = "C: "; 40 | 41 | mounts_type mounts[] = { 42 | {.str=str_cas, .mount_path=c_mount, .mounted=false, .status = 0}, 43 | {.str=str_d1, .mount_path=d1_mount, .mounted=false, .status = 0}, 44 | {.str=str_d2, .mount_path=d2_mount, .mounted=false, .status = 0}, 45 | {.str=str_d3, .mount_path=d3_mount, .mounted=false, .status = 0}, 46 | {.str=str_d4, .mount_path=d4_mount, .mounted=false, .status = 0} 47 | }; 48 | 49 | disk_header_type disk_headers[4]; 50 | 51 | uint8_t sector_buffer[sector_buffer_size]; 52 | 53 | file_type ft = file_type::none; 54 | cas_header_type cas_header; 55 | 56 | uint8_t pwm_bit_order; 57 | uint8_t pwm_bit; 58 | 59 | uint32_t pwm_sample_duration; // in cycles dep the base timing value 60 | uint32_t cas_sample_duration; // in cycles dep the base timing value 61 | uint16_t silence_duration; // in ms 62 | uint16_t cas_block_index; 63 | uint16_t cas_block_multiple; 64 | uint8_t cas_fsk_bit; 65 | 66 | volatile FSIZE_t cas_size; 67 | 68 | FATFS fatfs[2]; 69 | 70 | mutex_t fs_lock, mount_lock; 71 | 72 | void init_locks() { 73 | mutex_init(&fs_lock); 74 | mutex_init(&mount_lock); 75 | } 76 | 77 | void mount_file(char *f, int drive_number, char *lfn) { 78 | int j; 79 | bool read_only = false; 80 | 81 | if(!drive_number) 82 | flush_pio(); 83 | 84 | mutex_enter_blocking(&mount_lock); 85 | 86 | if(mounts[drive_number].mounted) 87 | f_close(&mounts[drive_number].fil); 88 | if(drive_number) { 89 | for(j=1; j<=4; j++) { 90 | if(j == drive_number) 91 | continue; 92 | if(!strcmp(mounts[j].mount_path, curr_path)) { 93 | if(!mounts[j].mounted) { 94 | mounts[j].mount_path[0] = 0; 95 | strcpy(&mounts[j].str[3], " "); 96 | } else if(!(disk_headers[j-1].atr_header.flags & 0x01)) 97 | read_only = true; 98 | } 99 | } 100 | } 101 | if(read_only) 102 | disk_headers[drive_number-1].atr_header.flags |= 0x01; 103 | mounts[drive_number].mounted = true; 104 | mounts[drive_number].status = 0; 105 | strcpy((char *)mounts[drive_number].mount_path, curr_path); 106 | j = 0; 107 | int si = (ft == file_type::disk) ? 3 : 2; 108 | size_t size_lfn = strlen(lfn)-4; 109 | strcpy(&mounts[drive_number].str[si+8], &lfn[size_lfn]); 110 | while(j<8) { 111 | mounts[drive_number].str[si+j] = (j < size_lfn ? lfn[j] : ' '); 112 | j++; 113 | } 114 | if(size_lfn > 8) 115 | mounts[drive_number].str[si+7] = '~'; 116 | 117 | mutex_exit(&mount_lock); 118 | } 119 | 120 | 121 | FRESULT mounted_file_transfer(int drive_number, FSIZE_t offset, FSIZE_t to_transfer, bool op_write, size_t t_offset, FSIZE_t brpt) { 122 | FIL* fil = &mounts[drive_number].fil; 123 | FRESULT f_op_stat; 124 | uint bytes_transferred; 125 | uint8_t *data = §or_buffer[t_offset]; 126 | 127 | mutex_enter_blocking(&fs_lock); 128 | do { 129 | //if((f_op_stat = f_mount(&fatfs[0], (const char *)mounts[drive_number].mount_path, 1)) != FR_OK) 130 | // break; 131 | // if((f_op_stat = f_open(fil, (const char *)mounts[drive_number].mount_path, op_write ? FA_WRITE : FA_READ)) != FR_OK) 132 | // break; 133 | if((f_op_stat = f_lseek(fil, offset)) != FR_OK) 134 | break; 135 | uint vol_num = mounts[drive_number].mount_path[0] - '0'; 136 | if(op_write) { 137 | uint32_t ints; 138 | if(!vol_num) { 139 | ints = save_and_disable_interrupts(); 140 | multicore_lockout_start_blocking(); 141 | } 142 | for(uint i=0; i> 2) & 0x1; 208 | cas_header.aux.aux_b[0] &= 0x3; 209 | if(cas_header.aux.aux_b[0] == 0b01) 210 | pwm_bit = 0; // 0 211 | else if(cas_header.aux.aux_b[0] == 0b10) 212 | pwm_bit = 1; // 1 213 | else { 214 | offset = 0; 215 | goto cas_read_forward_exit; 216 | } 217 | pwm_sample_duration = 0; 218 | if(f_read(fil, &pwm_sample_duration, sizeof(uint16_t), &bytes_read) != FR_OK || bytes_read != sizeof(uint16_t)) { 219 | offset = 0; 220 | goto cas_read_forward_exit; 221 | } 222 | offset += bytes_read; 223 | pwm_sample_duration = (timing_base_clock+pwm_sample_duration/2)/pwm_sample_duration; 224 | break; 225 | case cas_header_pwmc: 226 | cas_block_turbo = true; 227 | silence_duration = cas_header.aux.aux_w; 228 | cas_block_multiple = 3; 229 | goto cas_read_forward_exit; 230 | case cas_header_pwmd: 231 | cas_block_turbo = true; 232 | cas_block_multiple = 1; 233 | goto cas_read_forward_exit; 234 | case cas_header_pwml: 235 | cas_block_turbo = true; 236 | silence_duration = cas_header.aux.aux_w; 237 | cas_block_multiple = 2; 238 | cas_fsk_bit = pwm_bit; 239 | goto cas_read_forward_exit; 240 | default: 241 | break; 242 | } 243 | } 244 | cas_read_forward_exit: 245 | return offset; 246 | } 247 | 248 | volatile uint8_t sd_card_present = 0; 249 | const char * const volume_names[] = {"0:", "1:"}; 250 | const char * const str_int_flash = "Pico FLASH"; 251 | const char * const str_sd_card = "SD/MMC Card"; 252 | char volume_labels[2][17] = {"Int: ", "Ext: "}; 253 | 254 | void get_drive_label(int i) { 255 | DWORD sn; 256 | if(f_getlabel(volume_names[i], &volume_labels[i][5], &sn) == FR_OK) { 257 | if(!volume_labels[i][5]) 258 | sprintf(&volume_labels[i][5], "%04X-%04X", (sn >> 16) & 0xFFFF, sn & 0xFFFF); 259 | else 260 | for(int j=0; j= 0x80) volume_labels[i][j] = '?'; 262 | } else 263 | strcpy(&volume_labels[i][5], i ? str_sd_card : str_int_flash); 264 | } 265 | 266 | uint8_t try_mount_sd() { 267 | if(f_mount(&fatfs[1], volume_names[1], 1) == FR_OK) { 268 | get_drive_label(1); 269 | green_blinks = 4; 270 | sd_card_present = 1; 271 | }else 272 | sd_card_present = 0; 273 | return sd_card_present; 274 | } 275 | 276 | volatile int last_drive = -1; 277 | volatile uint32_t last_drive_access = 0; 278 | 279 | void update_last_drive(uint drive_number) { 280 | last_drive = drive_number; 281 | last_drive_access = to_ms_since_boot(get_absolute_time()); 282 | } 283 | 284 | int last_access_error_drive = -1; 285 | bool last_access_error[5] = {false}; 286 | 287 | void set_last_access_error(int drive_number) { 288 | last_access_error_drive = drive_number; 289 | last_access_error[last_access_error_drive] = true; 290 | } 291 | -------------------------------------------------------------------------------- /mounts.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the a8-pico-sio project -- 3 | * An Atari 8-bit SIO drive and (turbo) tape emulator for 4 | * Raspberry Pi Pico, see 5 | * 6 | * https://github.com/woj76/a8-pico-sio 7 | * 8 | * For information on what / whose work it is based on, check the corresponding 9 | * source files and the README file. This file is licensed under GNU General 10 | * Public License 3.0 or later. 11 | * 12 | * Copyright (C) 2025 Wojciech Mostowski 13 | */ 14 | 15 | #pragma once 16 | 17 | #include "config.h" 18 | 19 | #include "pico/multicore.h" 20 | 21 | #include "ff.h" 22 | 23 | typedef struct { 24 | char *str; 25 | char *mount_path; 26 | bool mounted; 27 | FSIZE_t status; 28 | FIL fil; 29 | } mounts_type; 30 | 31 | typedef struct __attribute__((__packed__)) { 32 | uint16_t magic; 33 | uint16_t pars; 34 | uint16_t sec_size; 35 | uint8_t pars_high; 36 | uint32_t crc; 37 | uint8_t temp1, temp2, temp3, temp4; 38 | uint8_t flags; 39 | } atr_header_type; 40 | 41 | typedef struct __attribute__((__packed__)) { 42 | uint32_t signature; 43 | uint16_t chunk_length; 44 | union { 45 | uint8_t aux_b[2]; 46 | uint16_t aux_w; 47 | } aux; 48 | } cas_header_type; 49 | 50 | typedef struct __attribute__((__packed__)) { 51 | uint32_t chunk_id; // "RIFF" 52 | uint32_t chunk_size; // == file_size - 8 53 | uint32_t format; // "WAVE" 54 | uint32_t subchunk1_id; // "fmt " 55 | uint32_t subchunk1_size; // 16 56 | uint16_t audio_format; // 1 ? 57 | uint16_t num_channels; // 2 58 | uint32_t sample_rate; // 44100 59 | uint32_t byte_rate; // sample_rate * bitrate in bytes * num_channels 60 | uint16_t block_align; // 4 == bit_rate_in_bytes * num_channels 61 | uint16_t bits_per_sample; // 16 62 | uint32_t subchunk2_id; // "data" 63 | uint32_t subchunk2_size; // file_size - 44 64 | } wav_header_type; 65 | 66 | typedef union { 67 | atr_header_type atr_header; 68 | uint8_t data[sizeof(atr_header_type)]; 69 | } disk_header_type; 70 | 71 | enum file_type {none, disk, casette}; 72 | extern file_type ft; 73 | 74 | extern char str_d1[]; 75 | extern char str_d2[]; 76 | extern char str_d3[]; 77 | extern char str_d4[]; 78 | extern char str_cas[]; 79 | 80 | extern mounts_type mounts[]; 81 | extern disk_header_type disk_headers[]; 82 | 83 | #define disk_type_atr 1 84 | #define disk_type_xex 2 85 | #define disk_type_atx 3 86 | 87 | #ifdef WAV_96K 88 | #define sector_buffer_size 2048 89 | #else 90 | #define sector_buffer_size 1024 91 | #endif 92 | 93 | extern uint8_t sector_buffer[]; 94 | 95 | extern cas_header_type cas_header; 96 | 97 | extern uint8_t pwm_bit_order; 98 | extern uint8_t pwm_bit; 99 | 100 | extern uint32_t pwm_sample_duration; 101 | extern uint32_t cas_sample_duration; 102 | extern uint16_t silence_duration; 103 | extern uint16_t cas_block_index; 104 | extern uint16_t cas_block_multiple; 105 | extern uint8_t cas_fsk_bit; 106 | 107 | extern volatile FSIZE_t cas_size; 108 | 109 | extern FATFS fatfs[]; 110 | 111 | extern mutex_t fs_lock; 112 | extern mutex_t mount_lock; 113 | 114 | void init_locks(); 115 | 116 | #define cas_header_FUJI 0x494A5546 117 | #define cas_header_baud 0x64756162 118 | #define cas_header_data 0x61746164 119 | #define cas_header_fsk 0x206B7366 120 | #define cas_header_pwms 0x736D7770 121 | #define cas_header_pwmc 0x636D7770 122 | #define cas_header_pwmd 0x646D7770 123 | #define cas_header_pwml 0x6C6D7770 124 | 125 | #define WAV_RIFF 0x46464952 126 | #define WAV_WAVE 0x45564157 127 | #define WAV_FMT 0x20746D66 128 | #define WAV_DATA 0x61746164 129 | #define WAV_LIST 0x5453494C 130 | 131 | void get_drive_label(int i); 132 | 133 | uint8_t try_mount_sd(); 134 | 135 | void mount_file(char *f, int drive_number, char *lfn); 136 | 137 | FRESULT mounted_file_transfer(int drive_number, FSIZE_t offset, FSIZE_t to_transfer, bool op_write, size_t t_offset=0, FSIZE_t brpt=1); 138 | 139 | FSIZE_t cas_read_forward(FSIZE_t offset); 140 | 141 | extern volatile uint8_t sd_card_present; 142 | extern const char * const volume_names[]; 143 | 144 | extern const char * const str_int_flash; 145 | extern const char * const str_sd_card; 146 | 147 | extern char volume_labels[][17]; 148 | 149 | extern volatile int last_drive; 150 | extern volatile uint32_t last_drive_access; 151 | 152 | void update_last_drive(uint drive_number); 153 | 154 | extern int last_access_error_drive; 155 | extern bool last_access_error[]; 156 | 157 | void set_last_access_error(int drive_number); 158 | -------------------------------------------------------------------------------- /msc_disk.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the a8-pico-sio project -- 3 | * An Atari 8-bit SIO drive and (turbo) tape emulator for 4 | * Raspberry Pi Pico, see 5 | * 6 | * https://github.com/woj76/a8-pico-sio 7 | * 8 | * For information on what / whose work it is based on, check below and the 9 | * corresponding project repository. 10 | */ 11 | 12 | /* This file or its parts come originally from the A8PicoCart 13 | * project, see https://github.com/robinhedwards/A8PicoCart for additional 14 | * information, license, and credits (see also below). The major modification is 15 | * the ability to handle various sizes of the Pico FLASH memory that stores the 16 | * FAT file system, not only the 15MB drive on a 16MB Pico clone. 17 | */ 18 | 19 | /** 20 | * _ ___ ___ _ ___ _ 21 | * /_\ ( _ ) _ (_)__ _ / __|__ _ _ _| |_ 22 | * / _ \/ _ \ _/ / _/_\ (__/ _` | '_| _| 23 | * /_/ \_\___/_| |_\__\_/\___\__,_|_| \__| 24 | * 25 | * 26 | * Atari 8-bit cartridge for Raspberry Pi Pico 27 | * 28 | * Robin Edwards 2023 29 | */ 30 | 31 | #include "tusb.h" 32 | #include "fatfs_disk.h" 33 | #include "ff.h" 34 | #include "diskio.h" 35 | 36 | #define MAX_LUN 2 37 | 38 | static bool ejected[MAX_LUN] = 39 | #if MAX_LUN == 1 40 | {true}; 41 | #else 42 | {true, true}; 43 | #endif 44 | 45 | static void try_disk_init(uint8_t lun) { 46 | if(!(disk_initialize(lun) & (lun ? (STA_NOINIT | STA_NODISK): 0xFF))) 47 | ejected[lun] = false; 48 | } 49 | 50 | void tud_mount_cb() { 51 | if (!mount_fatfs_disk()) 52 | create_fatfs_disk(); 53 | for(uint8_t lun = 0; lun < MAX_LUN; lun++) 54 | try_disk_init(lun); 55 | } 56 | 57 | void tud_umount_cb() {} 58 | 59 | uint8_t tud_msc_get_maxlun_cb(void) { 60 | return MAX_LUN; 61 | } 62 | 63 | void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4]) { 64 | 65 | const char vid[] = "PicoSIO"; 66 | const char pid[] = "Mass Storage I"; 67 | const char rev[] = "1.0"; 68 | 69 | int pid_len = strlen(pid); 70 | memcpy(vendor_id, vid, strlen(vid)); 71 | memcpy(product_id, pid, pid_len); 72 | if(lun) 73 | product_id[pid_len-1] = 'E'; 74 | memcpy(product_rev, rev, strlen(rev)); 75 | } 76 | 77 | bool tud_msc_test_unit_ready_cb(uint8_t lun) { 78 | 79 | if(disk_status(lun) & STA_NOINIT) 80 | return false; 81 | 82 | if (ejected[lun]) { 83 | tud_msc_set_sense(lun, SCSI_SENSE_NOT_READY, 0x3a, 0x00); 84 | return false; 85 | } 86 | 87 | return true; 88 | } 89 | 90 | void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size) { 91 | disk_ioctl(lun, GET_SECTOR_COUNT, block_count); 92 | disk_ioctl(lun, GET_SECTOR_SIZE, block_size); 93 | } 94 | 95 | bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject) { 96 | (void) power_condition; 97 | if (load_eject) { 98 | if (start) 99 | return !ejected[lun]; 100 | else { 101 | if (disk_ioctl(lun, CTRL_SYNC, NULL) != RES_OK) 102 | return false; 103 | else 104 | ejected[lun] = true; 105 | } 106 | } else if (!start && disk_ioctl(lun, CTRL_SYNC, NULL) != RES_OK) 107 | return false; 108 | return true; 109 | } 110 | 111 | int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize) { 112 | 113 | if(offset) 114 | return -1; 115 | 116 | uint16_t sec_size; 117 | disk_ioctl(lun, GET_SECTOR_SIZE, &sec_size); 118 | if(bufsize != sec_size) 119 | return -1; 120 | 121 | return disk_read(lun, buffer, lba, 1) == RES_OK ? (int32_t) bufsize : -1; 122 | } 123 | 124 | bool tud_msc_is_writable_cb (uint8_t lun) { 125 | return lun ? !(disk_status(lun) & STA_PROTECT) : true; 126 | } 127 | 128 | alarm_id_t alarm_id = -1; 129 | 130 | int64_t sync_callback(alarm_id_t id, void *user_data) { 131 | for(uint8_t lun = 0; lun < MAX_LUN; lun++) 132 | // For lun == 1 this seems to be a no-op 133 | disk_ioctl(lun, CTRL_SYNC, NULL); 134 | } 135 | 136 | int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize) { 137 | 138 | if(offset) 139 | return -1; 140 | 141 | uint16_t sec_size; 142 | disk_ioctl(lun, GET_SECTOR_SIZE, &sec_size); 143 | if(bufsize != sec_size) 144 | return -1; 145 | 146 | DSTATUS status = disk_write(lun, buffer, lba, 1); 147 | 148 | if(alarm_id >= 0) 149 | cancel_alarm(alarm_id); 150 | alarm_id = add_alarm_in_ms(250, sync_callback, NULL, false); 151 | 152 | return status == RES_OK ? (int32_t) bufsize : -1; 153 | } 154 | 155 | int32_t tud_msc_scsi_cb (uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, uint16_t bufsize) { 156 | /* 157 | void const* response = NULL; 158 | int32_t resplen = 0; 159 | 160 | bool in_xfer = true; 161 | 162 | switch (scsi_cmd[0]) { 163 | default: 164 | tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00); 165 | resplen = -1; 166 | break; 167 | } 168 | 169 | if ( resplen > bufsize ) 170 | resplen = bufsize; 171 | 172 | if ( response && (resplen > 0) ) { 173 | if(in_xfer) 174 | memcpy(buffer, response, (size_t) resplen); 175 | } 176 | 177 | return (int32_t) resplen; 178 | */ 179 | tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00); 180 | return -1; 181 | } 182 | -------------------------------------------------------------------------------- /options.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the a8-pico-sio project -- 3 | * An Atari 8-bit SIO drive and (turbo) tape emulator for 4 | * Raspberry Pi Pico, see 5 | * 6 | * https://github.com/woj76/a8-pico-sio 7 | * 8 | * For information on what / whose work it is based on, check the corresponding 9 | * source files and the README file. This file is licensed under GNU General 10 | * Public License 3.0 or later. 11 | * 12 | * Copyright (C) 2025 Wojciech Mostowski 13 | */ 14 | 15 | #include "pico/multicore.h" 16 | #include "hardware/sync.h" 17 | #include "hardware/flash.h" 18 | #include "string.h" 19 | 20 | #include "options.hpp" 21 | 22 | #include "file_load.hpp" 23 | #include "mounts.hpp" 24 | #include "flash_fs.h" 25 | 26 | uint8_t current_options[option_count] = { 27 | 0, // disk write off 28 | 0, // PAL 29 | 0, // HSIO off 30 | 0, // 1050 31 | 2, // loader at $700 32 | 0, // basic default 33 | 0, // SIO 34 | 0, // Command 35 | 0, // no invert 36 | 0 // FSK 37 | }; 38 | 39 | volatile bool save_config_flag = false; 40 | volatile bool save_path_flag = false; 41 | 42 | #define flash_save_offset (HW_FLASH_STORAGE_BASE-FLASH_SECTOR_SIZE) 43 | 44 | const uint8_t *flash_config_pointer = (uint8_t *)(XIP_BASE+flash_save_offset); 45 | #define flash_config_offset MAX_PATH_LEN 46 | #define flash_check_sig_offset (flash_config_offset+64) 47 | #define config_magic 0xDEADBEEF 48 | 49 | void check_and_load_config(bool reset_config) { 50 | if(!reset_config && *(uint32_t *)&flash_config_pointer[flash_check_sig_offset] == config_magic) { 51 | memcpy(curr_path, &flash_config_pointer[0], MAX_PATH_LEN); 52 | memcpy(current_options, &flash_config_pointer[flash_config_offset], option_count); 53 | } else { 54 | save_path_flag = true; 55 | save_config_flag = true; 56 | } 57 | } 58 | 59 | void check_and_save_config() { 60 | if(!save_path_flag && !save_config_flag) 61 | return; 62 | memset(sector_buffer, 0, sector_buffer_size); 63 | memcpy(sector_buffer, save_path_flag ? (uint8_t *)curr_path : &flash_config_pointer[0], MAX_PATH_LEN); 64 | memcpy(§or_buffer[flash_config_offset], save_config_flag ? current_options : (uint8_t *)&flash_config_pointer[flash_config_offset], option_count); 65 | *(uint32_t *)§or_buffer[flash_check_sig_offset] = config_magic; 66 | uint32_t ints = save_and_disable_interrupts(); 67 | multicore_lockout_start_blocking(); 68 | flash_range_erase(flash_save_offset, FLASH_SECTOR_SIZE); 69 | flash_range_program(flash_save_offset, (uint8_t *)sector_buffer, sector_buffer_size); 70 | multicore_lockout_end_blocking(); 71 | restore_interrupts(ints); 72 | save_path_flag = false; 73 | save_config_flag = false; 74 | } 75 | -------------------------------------------------------------------------------- /options.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the a8-pico-sio project -- 3 | * An Atari 8-bit SIO drive and (turbo) tape emulator for 4 | * Raspberry Pi Pico, see 5 | * 6 | * https://github.com/woj76/a8-pico-sio 7 | * 8 | * For information on what / whose work it is based on, check the corresponding 9 | * source files and the README file. This file is licensed under GNU General 10 | * Public License 3.0 or later. 11 | * 12 | * Copyright (C) 2025 Wojciech Mostowski 13 | */ 14 | 15 | #pragma once 16 | 17 | #include 18 | #include "config.h" 19 | 20 | #define option_count 10 21 | 22 | extern uint8_t current_options[]; 23 | 24 | #define mount_option_index 0 25 | #define clock_option_index 1 26 | #define hsio_option_index 2 27 | #define atx_option_index 3 28 | #define xex_option_index 4 29 | #define basic_option_index 5 30 | #define turbo1_option_index 6 31 | #define turbo2_option_index 7 32 | #define turbo3_option_index 8 33 | #define wav_option_index 9 34 | 35 | extern const uint8_t *flash_config_pointer; 36 | extern volatile bool save_config_flag; 37 | extern volatile bool save_path_flag; 38 | 39 | void check_and_load_config(bool reset_config); 40 | void check_and_save_config(); 41 | -------------------------------------------------------------------------------- /pin_io.pio: -------------------------------------------------------------------------------- 1 | ; This file is part of the a8-pico-sio project -- 2 | ; An Atari 8-bit SIO drive and (turbo) tape emulator for 3 | ; Raspberry Pi Pico, see 4 | ; 5 | ; https://github.com/woj76/a8-pico-sio 6 | ; 7 | ; For information on what / whose work it is based on, check the corresponding 8 | ; source files and the README file. This file is licensed under GNU General 9 | ; Public License 3.0 or later. 10 | ; 11 | ; Copyright (C) 2025 Wojciech Mostowski 12 | 13 | .program pin_io 14 | 15 | .wrap_target 16 | out pins,1 ; output the first bit 17 | out x,31 ; copy the next 31 bits = delay 18 | wait_loop: 19 | jmp x--, wait_loop ; delay for (x + 1) cycles 20 | .wrap 21 | 22 | %c-sdk { 23 | #define pio_prog_cycle_corr 3 24 | %} 25 | -------------------------------------------------------------------------------- /sd_driver/crc.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the a8-pico-sio project -- 3 | * An Atari 8-bit SIO drive and (turbo) tape emulator for 4 | * Raspberry Pi Pico, see 5 | * 6 | * https://github.com/woj76/a8-pico-sio 7 | * 8 | * For information on what / whose work it is based on, check below and the 9 | * corresponding project repository. 10 | */ 11 | 12 | /* This file or its parts come originally from the no-OS-FatFS-SD-SPI-RPi-Pico 13 | * project, see https://github.com/carlk3/no-OS-FatFS-SD-SPI-RPi-Pico */ 14 | 15 | /* crc.c 16 | Copyright 2021 Carl John Kugler III 17 | 18 | Licensed under the Apache License, Version 2.0 (the License); you may not use 19 | this file except in compliance with the License. You may obtain a copy of the 20 | License at 21 | 22 | http://www.apache.org/licenses/LICENSE-2.0 23 | Unless required by applicable law or agreed to in writing, software distributed 24 | under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR 25 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 26 | specific language governing permissions and limitations under the License. 27 | */ 28 | /* Derived from: 29 | * SD/MMC File System Library 30 | * Copyright (c) 2016 Neil Thiessen 31 | * 32 | * Licensed under the Apache License, Version 2.0 (the "License"); 33 | * you may not use this file except in compliance with the License. 34 | * You may obtain a copy of the License at 35 | * 36 | * http://www.apache.org/licenses/LICENSE-2.0 37 | * 38 | * Unless required by applicable law or agreed to in writing, software 39 | * distributed under the License is distributed on an "AS IS" BASIS, 40 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 41 | * See the License for the specific language governing permissions and 42 | * limitations under the License. 43 | */ 44 | 45 | #include "crc.h" 46 | 47 | static const char m_Crc7Table[] = {0x00, 0x09, 0x12, 0x1B, 0x24, 0x2D, 0x36, 48 | 0x3F, 0x48, 0x41, 0x5A, 0x53, 0x6C, 0x65, 0x7E, 0x77, 0x19, 0x10, 0x0B, 49 | 0x02, 0x3D, 0x34, 0x2F, 0x26, 0x51, 0x58, 0x43, 0x4A, 0x75, 0x7C, 0x67, 50 | 0x6E, 0x32, 0x3B, 0x20, 0x29, 0x16, 0x1F, 0x04, 0x0D, 0x7A, 0x73, 0x68, 51 | 0x61, 0x5E, 0x57, 0x4C, 0x45, 0x2B, 0x22, 0x39, 0x30, 0x0F, 0x06, 0x1D, 52 | 0x14, 0x63, 0x6A, 0x71, 0x78, 0x47, 0x4E, 0x55, 0x5C, 0x64, 0x6D, 0x76, 53 | 0x7F, 0x40, 0x49, 0x52, 0x5B, 0x2C, 0x25, 0x3E, 0x37, 0x08, 0x01, 0x1A, 54 | 0x13, 0x7D, 0x74, 0x6F, 0x66, 0x59, 0x50, 0x4B, 0x42, 0x35, 0x3C, 0x27, 55 | 0x2E, 0x11, 0x18, 0x03, 0x0A, 0x56, 0x5F, 0x44, 0x4D, 0x72, 0x7B, 0x60, 56 | 0x69, 0x1E, 0x17, 0x0C, 0x05, 0x3A, 0x33, 0x28, 0x21, 0x4F, 0x46, 0x5D, 57 | 0x54, 0x6B, 0x62, 0x79, 0x70, 0x07, 0x0E, 0x15, 0x1C, 0x23, 0x2A, 0x31, 58 | 0x38, 0x41, 0x48, 0x53, 0x5A, 0x65, 0x6C, 0x77, 0x7E, 0x09, 0x00, 0x1B, 59 | 0x12, 0x2D, 0x24, 0x3F, 0x36, 0x58, 0x51, 0x4A, 0x43, 0x7C, 0x75, 0x6E, 60 | 0x67, 0x10, 0x19, 0x02, 0x0B, 0x34, 0x3D, 0x26, 0x2F, 0x73, 0x7A, 0x61, 61 | 0x68, 0x57, 0x5E, 0x45, 0x4C, 0x3B, 0x32, 0x29, 0x20, 0x1F, 0x16, 0x0D, 62 | 0x04, 0x6A, 0x63, 0x78, 0x71, 0x4E, 0x47, 0x5C, 0x55, 0x22, 0x2B, 0x30, 63 | 0x39, 0x06, 0x0F, 0x14, 0x1D, 0x25, 0x2C, 0x37, 0x3E, 0x01, 0x08, 0x13, 64 | 0x1A, 0x6D, 0x64, 0x7F, 0x76, 0x49, 0x40, 0x5B, 0x52, 0x3C, 0x35, 0x2E, 65 | 0x27, 0x18, 0x11, 0x0A, 0x03, 0x74, 0x7D, 0x66, 0x6F, 0x50, 0x59, 0x42, 66 | 0x4B, 0x17, 0x1E, 0x05, 0x0C, 0x33, 0x3A, 0x21, 0x28, 0x5F, 0x56, 0x4D, 67 | 0x44, 0x7B, 0x72, 0x69, 0x60, 0x0E, 0x07, 0x1C, 0x15, 0x2A, 0x23, 0x38, 68 | 0x31, 0x46, 0x4F, 0x54, 0x5D, 0x62, 0x6B, 0x70, 0x79}; 69 | 70 | static const unsigned short m_Crc16Table[256] = {0x0000, 0x1021, 0x2042, 71 | 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, 0x8108, 0x9129, 0xA14A, 0xB16B, 72 | 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 73 | 0x4294, 0x72F7, 0x62D6, 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 74 | 0xF3FF, 0xE3DE, 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 75 | 0x5485, 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, 76 | 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, 0xB75B, 77 | 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, 0x48C4, 0x58E5, 78 | 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, 0xC9CC, 0xD9ED, 0xE98E, 79 | 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 80 | 0x1A71, 0x0A50, 0x3A33, 0x2A12, 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 81 | 0x8B58, 0xBB3B, 0xAB1A, 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 82 | 0x0C60, 0x1C41, 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 83 | 0x9D49, 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, 84 | 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, 0x9188, 85 | 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, 0x1080, 0x00A1, 86 | 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83B9, 0x9398, 0xA3FB, 87 | 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, 0x02B1, 0x1290, 0x22F3, 0x32D2, 88 | 0x4235, 0x5214, 0x6277, 0x7256, 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 89 | 0xE54F, 0xD52C, 0xC50D, 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 90 | 0x5424, 0x4405, 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 91 | 0xD73C, 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, 92 | 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, 0x5844, 93 | 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, 0xCB7D, 0xDB5C, 94 | 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, 0x4A75, 0x5A54, 0x6A37, 95 | 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 96 | 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 97 | 0x2C83, 0x1CE0, 0x0CC1, 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 98 | 0x8FD9, 0x9FF8, 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 99 | 0x1EF0}; 100 | 101 | char crc7(const char* data, int length) 102 | { 103 | //Calculate the CRC7 checksum for the specified data block 104 | char crc = 0; 105 | for (int i = 0; i < length; i++) { 106 | crc = m_Crc7Table[(crc << 1) ^ data[i]]; 107 | } 108 | 109 | //Return the calculated checksum 110 | return crc; 111 | } 112 | 113 | unsigned short crc16(const char* data, int length) 114 | { 115 | //Calculate the CRC16 checksum for the specified data block 116 | unsigned short crc = 0; 117 | for (int i = 0; i < length; i++) { 118 | crc = (crc << 8) ^ m_Crc16Table[((crc >> 8) ^ data[i]) & 0x00FF]; 119 | } 120 | 121 | //Return the calculated checksum 122 | return crc; 123 | } 124 | 125 | void update_crc16(unsigned short *pCrc16, const char data[], size_t length) { 126 | for (size_t i = 0; i < length; i++) { 127 | *pCrc16 = (*pCrc16 << 8) ^ m_Crc16Table[((*pCrc16 >> 8) ^ data[i]) & 0x00FF]; 128 | } 129 | } 130 | /* [] END OF FILE */ 131 | -------------------------------------------------------------------------------- /sd_driver/crc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the a8-pico-sio project -- 3 | * An Atari 8-bit SIO drive and (turbo) tape emulator for 4 | * Raspberry Pi Pico, see 5 | * 6 | * https://github.com/woj76/a8-pico-sio 7 | * 8 | * For information on what / whose work it is based on, check below and the 9 | * corresponding project repository. 10 | */ 11 | 12 | /* This file or its parts come originally from the no-OS-FatFS-SD-SPI-RPi-Pico 13 | * project, see https://github.com/carlk3/no-OS-FatFS-SD-SPI-RPi-Pico */ 14 | 15 | /* crc.h 16 | Copyright 2021 Carl John Kugler III 17 | 18 | Licensed under the Apache License, Version 2.0 (the License); you may not use 19 | this file except in compliance with the License. You may obtain a copy of the 20 | License at 21 | 22 | http://www.apache.org/licenses/LICENSE-2.0 23 | Unless required by applicable law or agreed to in writing, software distributed 24 | under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR 25 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 26 | specific language governing permissions and limitations under the License. 27 | */ 28 | /* Derived from: 29 | * SD/MMC File System Library 30 | * Copyright (c) 2016 Neil Thiessen 31 | * 32 | * Licensed under the Apache License, Version 2.0 (the "License"); 33 | * you may not use this file except in compliance with the License. 34 | * You may obtain a copy of the License at 35 | * 36 | * http://www.apache.org/licenses/LICENSE-2.0 37 | * 38 | * Unless required by applicable law or agreed to in writing, software 39 | * distributed under the License is distributed on an "AS IS" BASIS, 40 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 41 | * See the License for the specific language governing permissions and 42 | * limitations under the License. 43 | */ 44 | 45 | #pragma once 46 | 47 | #include 48 | 49 | char crc7(const char* data, int length); 50 | unsigned short crc16(const char* data, int length); 51 | void update_crc16(unsigned short *pCrc16, const char data[], size_t length); 52 | 53 | /* [] END OF FILE */ 54 | -------------------------------------------------------------------------------- /sd_driver/sd_card.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the a8-pico-sio project -- 3 | * An Atari 8-bit SIO drive and (turbo) tape emulator for 4 | * Raspberry Pi Pico, see 5 | * 6 | * https://github.com/woj76/a8-pico-sio 7 | * 8 | * For information on what / whose work it is based on, check below and the 9 | * corresponding project repository. 10 | */ 11 | 12 | /* This file or its parts come originally from the no-OS-FatFS-SD-SPI-RPi-Pico 13 | * project, see https://github.com/carlk3/no-OS-FatFS-SD-SPI-RPi-Pico */ 14 | 15 | /* sd_card.h 16 | Copyright 2021 Carl John Kugler III 17 | 18 | Licensed under the Apache License, Version 2.0 (the License); you may not use 19 | this file except in compliance with the License. You may obtain a copy of the 20 | License at 21 | 22 | http://www.apache.org/licenses/LICENSE-2.0 23 | Unless required by applicable law or agreed to in writing, software distributed 24 | under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR 25 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 26 | specific language governing permissions and limitations under the License. 27 | */ 28 | 29 | // Note: The model used here is one FatFS per SD card. 30 | // Multiple partitions on a card are not supported. 31 | 32 | #pragma once 33 | 34 | #include 35 | // 36 | #include "hardware/gpio.h" 37 | #include "pico/mutex.h" 38 | // 39 | #include "ff.h" 40 | // 41 | #include "spi.h" 42 | 43 | #ifdef __cplusplus 44 | extern "C" { 45 | #endif 46 | 47 | typedef struct sd_card_t sd_card_t; 48 | 49 | // "Class" representing SD Cards 50 | struct sd_card_t { 51 | spi_t *spi; 52 | // Slave select is here instead of in spi_t because multiple SDs can share an SPI. 53 | uint ss_gpio; // Slave select for this SD card 54 | bool use_card_detect; 55 | uint card_detect_gpio; // Card detect; ignored if !use_card_detect 56 | uint card_detected_true; // Varies with card socket; ignored if !use_card_detect 57 | // Drive strength levels for GPIO outputs. 58 | // enum gpio_drive_strength { GPIO_DRIVE_STRENGTH_2MA = 0, GPIO_DRIVE_STRENGTH_4MA = 1, GPIO_DRIVE_STRENGTH_8MA = 2, 59 | // GPIO_DRIVE_STRENGTH_12MA = 3 } 60 | //bool set_drive_strength; 61 | //enum gpio_drive_strength ss_gpio_drive_strength; 62 | 63 | // Following fields are used to keep track of the state of the card: 64 | int m_Status; // Card status 65 | uint64_t sectors; // Assigned dynamically 66 | int card_type; // Assigned dynamically 67 | mutex_t mutex; 68 | // FATFS fatfs; 69 | bool mounted; 70 | 71 | int (*init)(sd_card_t *sd_card_p); 72 | int (*write_blocks)(sd_card_t *sd_card_p, const uint8_t *buffer, 73 | uint64_t ulSectorNumber, uint32_t blockCnt); 74 | int (*read_blocks)(sd_card_t *sd_card_p, uint8_t *buffer, uint64_t ulSectorNumber, 75 | uint32_t ulSectorCount); 76 | 77 | // Useful when use_card_detect is false - call periodically to check for presence of SD card 78 | // Returns true if and only if SD card was sensed on the bus 79 | bool (*sd_test_com)(sd_card_t *sd_card_p); 80 | }; 81 | 82 | sd_card_t *sd_get_by_num(size_t num); 83 | 84 | #define SD_BLOCK_DEVICE_ERROR_NONE 0 85 | #define SD_BLOCK_DEVICE_ERROR_WOULD_BLOCK -5001 /*!< operation would block */ 86 | #define SD_BLOCK_DEVICE_ERROR_UNSUPPORTED -5002 /*!< unsupported operation */ 87 | #define SD_BLOCK_DEVICE_ERROR_PARAMETER -5003 /*!< invalid parameter */ 88 | #define SD_BLOCK_DEVICE_ERROR_NO_INIT -5004 /*!< uninitialized */ 89 | #define SD_BLOCK_DEVICE_ERROR_NO_DEVICE -5005 /*!< device is missing or not connected */ 90 | #define SD_BLOCK_DEVICE_ERROR_WRITE_PROTECTED -5006 /*!< write protected */ 91 | #define SD_BLOCK_DEVICE_ERROR_UNUSABLE -5007 /*!< unusable card */ 92 | #define SD_BLOCK_DEVICE_ERROR_NO_RESPONSE -5008 /*!< No response from device */ 93 | #define SD_BLOCK_DEVICE_ERROR_CRC -5009 /*!< CRC error */ 94 | #define SD_BLOCK_DEVICE_ERROR_ERASE -5010 /*!< Erase error: reset/sequence */ 95 | #define SD_BLOCK_DEVICE_ERROR_WRITE -5011 /*!< SPI Write error: !SPI_DATA_ACCEPTED */ 96 | 97 | ///* Disk Status Bits (DSTATUS) */ 98 | // See diskio.h. 99 | //enum { 100 | // STA_NOINIT = 0x01, /* Drive not initialized */ 101 | // STA_NODISK = 0x02, /* No medium in the drive */ 102 | // STA_PROTECT = 0x04 /* Write protected */ 103 | //}; 104 | 105 | bool sd_card_detect(sd_card_t *pSD); 106 | uint64_t sd_sectors(sd_card_t *pSD); 107 | 108 | bool sd_init_driver(); 109 | // bool sd_card_detect(sd_card_t *sd_card_p); 110 | 111 | #ifdef __cplusplus 112 | } 113 | #endif 114 | 115 | /* [] END OF FILE */ 116 | -------------------------------------------------------------------------------- /sd_driver/sd_spi.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the a8-pico-sio project -- 3 | * An Atari 8-bit SIO drive and (turbo) tape emulator for 4 | * Raspberry Pi Pico, see 5 | * 6 | * https://github.com/woj76/a8-pico-sio 7 | * 8 | * For information on what / whose work it is based on, check below and the 9 | * corresponding project repository. 10 | */ 11 | 12 | /* This file or its parts come originally from the no-OS-FatFS-SD-SPI-RPi-Pico 13 | * project, see https://github.com/carlk3/no-OS-FatFS-SD-SPI-RPi-Pico */ 14 | 15 | /* sd_spi.c 16 | Copyright 2021 Carl John Kugler III 17 | 18 | Licensed under the Apache License, Version 2.0 (the License); you may not use 19 | this file except in compliance with the License. You may obtain a copy of the 20 | License at 21 | 22 | http://www.apache.org/licenses/LICENSE-2.0 23 | Unless required by applicable law or agreed to in writing, software distributed 24 | under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR 25 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 26 | specific language governing permissions and limitations under the License. 27 | */ 28 | 29 | /* Standard includes. */ 30 | #include 31 | #include 32 | #include 33 | 34 | #include "hardware/gpio.h" 35 | 36 | #include "sd_card.h" 37 | #include "sd_spi.h" 38 | #include "spi.h" 39 | 40 | #pragma GCC diagnostic push 41 | #pragma GCC diagnostic ignored "-Wunused-variable" 42 | 43 | void sd_spi_go_high_frequency(sd_card_t *pSD) { 44 | spi_set_baudrate(pSD->spi->hw_inst, pSD->spi->baud_rate); 45 | } 46 | void sd_spi_go_low_frequency(sd_card_t *pSD) { 47 | spi_set_baudrate(pSD->spi->hw_inst, 400 * 1000); // Actual frequency: 398089 48 | } 49 | 50 | #pragma GCC diagnostic pop 51 | 52 | static void sd_spi_lock(sd_card_t *pSD) { 53 | spi_lock(pSD->spi); 54 | } 55 | 56 | static void sd_spi_unlock(sd_card_t *pSD) { 57 | spi_unlock(pSD->spi); 58 | } 59 | 60 | // Would do nothing if pSD->ss_gpio were set to GPIO_FUNC_SPI. 61 | static void sd_spi_select(sd_card_t *pSD) { 62 | //gpio_put(pSD->ss_gpio, 0); 63 | // A fill byte seems to be necessary, sometimes: 64 | uint8_t fill = SPI_FILL_CHAR; 65 | spi_write_blocking(pSD->spi->hw_inst, &fill, 1); 66 | LED_ON(); 67 | } 68 | 69 | static void sd_spi_deselect(sd_card_t *pSD) { 70 | //gpio_put(pSD->ss_gpio, 1); 71 | LED_OFF(); 72 | uint8_t fill = SPI_FILL_CHAR; 73 | spi_write_blocking(pSD->spi->hw_inst, &fill, 1); 74 | } 75 | 76 | /* Some SD cards want to be deselected between every bus transaction */ 77 | /* 78 | void sd_spi_deselect_pulse(sd_card_t *pSD) { 79 | sd_spi_deselect(pSD); 80 | sd_spi_select(pSD); 81 | } 82 | */ 83 | 84 | void sd_spi_acquire(sd_card_t *pSD) { 85 | sd_spi_lock(pSD); 86 | sd_spi_select(pSD); 87 | } 88 | 89 | void sd_spi_release(sd_card_t *pSD) { 90 | sd_spi_deselect(pSD); 91 | sd_spi_unlock(pSD); 92 | } 93 | 94 | bool sd_spi_transfer(sd_card_t *pSD, const uint8_t *tx, uint8_t *rx, size_t length) { 95 | return spi_transfer(pSD->spi, tx, rx, length); 96 | } 97 | 98 | uint8_t sd_spi_write(sd_card_t *pSD, const uint8_t value) { 99 | uint8_t received = SPI_FILL_CHAR; 100 | spi_transfer(pSD->spi, &value, &received, 1); 101 | return received; 102 | } 103 | 104 | void sd_spi_send_initializing_sequence(sd_card_t * pSD) { 105 | //bool old_ss = gpio_get(pSD->ss_gpio); 106 | //gpio_put(pSD->ss_gpio, 1); 107 | uint8_t ones[10]; 108 | memset(ones, 0xFF, sizeof ones); 109 | absolute_time_t timeout_time = make_timeout_time_ms(1); 110 | do { 111 | sd_spi_transfer(pSD, ones, NULL, sizeof ones); 112 | } while (0 < absolute_time_diff_us(get_absolute_time(), timeout_time)); 113 | //gpio_put(pSD->ss_gpio, old_ss); 114 | } 115 | 116 | /* [] END OF FILE */ 117 | -------------------------------------------------------------------------------- /sd_driver/sd_spi.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the a8-pico-sio project -- 3 | * An Atari 8-bit SIO drive and (turbo) tape emulator for 4 | * Raspberry Pi Pico, see 5 | * 6 | * https://github.com/woj76/a8-pico-sio 7 | * 8 | * For information on what / whose work it is based on, check below and the 9 | * corresponding project repository. 10 | */ 11 | 12 | /* This file or its parts come originally from the no-OS-FatFS-SD-SPI-RPi-Pico 13 | * project, see https://github.com/carlk3/no-OS-FatFS-SD-SPI-RPi-Pico */ 14 | 15 | /* sd_spi.h 16 | Copyright 2021 Carl John Kugler III 17 | 18 | Licensed under the Apache License, Version 2.0 (the License); you may not use 19 | this file except in compliance with the License. You may obtain a copy of the 20 | License at 21 | 22 | http://www.apache.org/licenses/LICENSE-2.0 23 | Unless required by applicable law or agreed to in writing, software distributed 24 | under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR 25 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 26 | specific language governing permissions and limitations under the License. 27 | */ 28 | 29 | //#ifndef _SD_SPI_H_ 30 | //#define _SD_SPI_H_ 31 | 32 | #pragma once 33 | 34 | #include 35 | #include "spi.h" 36 | #include "sd_card.h" 37 | 38 | /* Transfer tx to SPI while receiving SPI to rx. 39 | tx or rx can be NULL if not important. */ 40 | bool sd_spi_transfer(sd_card_t *pSD, const uint8_t *tx, uint8_t *rx, size_t length); 41 | uint8_t sd_spi_write(sd_card_t *pSD, const uint8_t value); 42 | // void sd_spi_deselect_pulse(sd_card_t *pSD); 43 | void sd_spi_acquire(sd_card_t *pSD); 44 | void sd_spi_release(sd_card_t *pSD); 45 | void sd_spi_go_low_frequency(sd_card_t *this); 46 | void sd_spi_go_high_frequency(sd_card_t *this); 47 | 48 | /* 49 | After power up, the host starts the clock and sends the initializing sequence on the CMD line. 50 | This sequence is a contiguous stream of logical ‘1’s. The sequence length is the maximum of 1msec, 51 | 74 clocks or the supply-ramp-uptime; the additional 10 clocks 52 | (over the 64 clocks after what the card should be ready for communication) is 53 | provided to eliminate power-up synchronization problems. 54 | */ 55 | void sd_spi_send_initializing_sequence(sd_card_t * pSD); 56 | // void sd_spi_init_pl022(sd_card_t *pSD); 57 | 58 | //#endif 59 | 60 | /* [] END OF FILE */ 61 | -------------------------------------------------------------------------------- /sd_driver/spi.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the a8-pico-sio project -- 3 | * An Atari 8-bit SIO drive and (turbo) tape emulator for 4 | * Raspberry Pi Pico, see 5 | * 6 | * https://github.com/woj76/a8-pico-sio 7 | * 8 | * For information on what / whose work it is based on, check below and the 9 | * corresponding project repository. 10 | */ 11 | 12 | /* This file or its parts come originally from the no-OS-FatFS-SD-SPI-RPi-Pico 13 | * project, see https://github.com/carlk3/no-OS-FatFS-SD-SPI-RPi-Pico */ 14 | 15 | /* spi.c 16 | Copyright 2021 Carl John Kugler III 17 | 18 | Licensed under the Apache License, Version 2.0 (the License); you may not use 19 | this file except in compliance with the License. You may obtain a copy of the 20 | License at 21 | 22 | http://www.apache.org/licenses/LICENSE-2.0 23 | Unless required by applicable law or agreed to in writing, software distributed 24 | under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR 25 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 26 | specific language governing permissions and limitations under the License. 27 | */ 28 | 29 | // #include 30 | #include 31 | // 32 | #include "pico/stdlib.h" 33 | #include "pico/mutex.h" 34 | #include "pico/sem.h" 35 | 36 | #include "spi.h" 37 | 38 | 39 | // SPI Transfer: Read & Write (simultaneously) on SPI bus 40 | // If the data that will be received is not important, pass NULL as rx. 41 | // If the data that will be transmitted is not important, 42 | // pass NULL as tx and then the SPI_FILL_CHAR is sent out as each data 43 | // element. 44 | bool spi_transfer(spi_t *spi_p, const uint8_t *tx, uint8_t *rx, size_t length) { 45 | 46 | // tx write increment is already false 47 | if (tx) { 48 | channel_config_set_read_increment(&spi_p->tx_dma_cfg, true); 49 | } else { 50 | static const uint8_t dummy = SPI_FILL_CHAR; 51 | tx = &dummy; 52 | channel_config_set_read_increment(&spi_p->tx_dma_cfg, false); 53 | } 54 | 55 | // rx read increment is already false 56 | if (rx) { 57 | channel_config_set_write_increment(&spi_p->rx_dma_cfg, true); 58 | } else { 59 | static uint8_t dummy = 0xA5; 60 | rx = &dummy; 61 | channel_config_set_write_increment(&spi_p->rx_dma_cfg, false); 62 | } 63 | 64 | dma_channel_configure(spi_p->tx_dma, &spi_p->tx_dma_cfg, 65 | &spi_get_hw(spi_p->hw_inst)->dr, // write address 66 | tx, // read address 67 | length, // element count (each element is of 68 | // size transfer_data_size) 69 | false); // start 70 | dma_channel_configure(spi_p->rx_dma, &spi_p->rx_dma_cfg, 71 | rx, // write address 72 | &spi_get_hw(spi_p->hw_inst)->dr, // read address 73 | length, // element count (each element is of 74 | // size transfer_data_size) 75 | false); // start 76 | 77 | // start them exactly simultaneously to avoid races (in extreme cases 78 | // the FIFO could overflow) 79 | dma_start_channel_mask((1u << spi_p->tx_dma) | (1u << spi_p->rx_dma)); 80 | 81 | absolute_time_t t = get_absolute_time(); 82 | while (dma_channel_is_busy(spi_p->rx_dma) && absolute_time_diff_us(t, get_absolute_time()) < 1000000) 83 | tight_loop_contents(); 84 | return !dma_channel_is_busy(spi_p->rx_dma); 85 | } 86 | 87 | void spi_lock(spi_t *spi_p) { 88 | mutex_enter_blocking(&spi_p->mutex); 89 | } 90 | 91 | void spi_unlock(spi_t *spi_p) { 92 | mutex_exit(&spi_p->mutex); 93 | } 94 | 95 | bool my_spi_init(spi_t *spi_p) { 96 | auto_init_mutex(my_spi_init_mutex); 97 | mutex_enter_blocking(&my_spi_init_mutex); 98 | if (!spi_p->initialized) { 99 | 100 | if (!mutex_is_initialized(&spi_p->mutex)) mutex_init(&spi_p->mutex); 101 | spi_lock(spi_p); 102 | 103 | // Default: 104 | if (!spi_p->baud_rate) 105 | spi_p->baud_rate = 10 * 1000 * 1000; 106 | 107 | /* Configure component */ 108 | // Enable SPI at 100 kHz and connect to GPIOs 109 | spi_init(spi_p->hw_inst, 100 * 1000); 110 | spi_set_format(spi_p->hw_inst, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST); 111 | 112 | gpio_set_function(spi_p->miso_gpio, GPIO_FUNC_SPI); 113 | gpio_set_function(spi_p->mosi_gpio, GPIO_FUNC_SPI); 114 | gpio_set_function(spi_p->sck_gpio, GPIO_FUNC_SPI); 115 | 116 | // ss_gpio is initialized in sd_init_driver() 117 | 118 | // Slew rate limiting levels for GPIO outputs. 119 | // enum gpio_slew_rate { GPIO_SLEW_RATE_SLOW = 0, GPIO_SLEW_RATE_FAST = 1 } 120 | // void gpio_set_slew_rate (uint gpio,enum gpio_slew_rate slew) 121 | // Default appears to be GPIO_SLEW_RATE_SLOW. 122 | 123 | // Drive strength levels for GPIO outputs. 124 | // enum gpio_drive_strength { GPIO_DRIVE_STRENGTH_2MA = 0, GPIO_DRIVE_STRENGTH_4MA = 1, GPIO_DRIVE_STRENGTH_8MA = 2, 125 | // GPIO_DRIVE_STRENGTH_12MA = 3 } 126 | // enum gpio_drive_strength gpio_get_drive_strength (uint gpio) 127 | /* 128 | if (spi_p->set_drive_strength) { 129 | gpio_set_drive_strength(spi_p->mosi_gpio, spi_p->mosi_gpio_drive_strength); 130 | gpio_set_drive_strength(spi_p->sck_gpio, spi_p->sck_gpio_drive_strength); 131 | } 132 | */ 133 | 134 | gpio_set_drive_strength(spi_p->mosi_gpio, sd_gpio_drive_strength); 135 | gpio_set_drive_strength(spi_p->sck_gpio, sd_gpio_drive_strength); 136 | 137 | // SD cards' DO MUST be pulled up. 138 | // This is done in hardware with a 4K7 resistor 139 | // gpio_pull_up(spi_p->miso_gpio); 140 | 141 | // Grab some unused dma channels 142 | spi_p->tx_dma = dma_claim_unused_channel(true); 143 | spi_p->rx_dma = dma_claim_unused_channel(true); 144 | 145 | spi_p->tx_dma_cfg = dma_channel_get_default_config(spi_p->tx_dma); 146 | spi_p->rx_dma_cfg = dma_channel_get_default_config(spi_p->rx_dma); 147 | channel_config_set_transfer_data_size(&spi_p->tx_dma_cfg, DMA_SIZE_8); 148 | channel_config_set_transfer_data_size(&spi_p->rx_dma_cfg, DMA_SIZE_8); 149 | 150 | // We set the outbound DMA to transfer from a memory buffer to the SPI 151 | // transmit FIFO paced by the SPI TX FIFO DREQ The default is for the 152 | // read address to increment every element (in this case 1 byte - 153 | // DMA_SIZE_8) and for the write address to remain unchanged. 154 | channel_config_set_dreq(&spi_p->tx_dma_cfg, spi_get_index(spi_p->hw_inst) 155 | ? DREQ_SPI1_TX 156 | : DREQ_SPI0_TX); 157 | channel_config_set_write_increment(&spi_p->tx_dma_cfg, false); 158 | 159 | // We set the inbound DMA to transfer from the SPI receive FIFO to a 160 | // memory buffer paced by the SPI RX FIFO DREQ We coinfigure the read 161 | // address to remain unchanged for each element, but the write address 162 | // to increment (so data is written throughout the buffer) 163 | channel_config_set_dreq(&spi_p->rx_dma_cfg, spi_get_index(spi_p->hw_inst) 164 | ? DREQ_SPI1_RX 165 | : DREQ_SPI0_RX); 166 | channel_config_set_read_increment(&spi_p->rx_dma_cfg, false); 167 | 168 | LED_INIT(); 169 | spi_p->initialized = true; 170 | spi_unlock(spi_p); 171 | } 172 | mutex_exit(&my_spi_init_mutex); 173 | return true; 174 | } 175 | -------------------------------------------------------------------------------- /sd_driver/spi.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the a8-pico-sio project -- 3 | * An Atari 8-bit SIO drive and (turbo) tape emulator for 4 | * Raspberry Pi Pico, see 5 | * 6 | * https://github.com/woj76/a8-pico-sio 7 | * 8 | * For information on what / whose work it is based on, check below and the 9 | * corresponding project repository. 10 | */ 11 | 12 | /* This file or its parts come originally from the no-OS-FatFS-SD-SPI-RPi-Pico 13 | * project, see https://github.com/carlk3/no-OS-FatFS-SD-SPI-RPi-Pico */ 14 | 15 | /* spi.h 16 | Copyright 2021 Carl John Kugler III 17 | 18 | Licensed under the Apache License, Version 2.0 (the License); you may not use 19 | this file except in compliance with the License. You may obtain a copy of the 20 | License at 21 | 22 | http://www.apache.org/licenses/LICENSE-2.0 23 | Unless required by applicable law or agreed to in writing, software distributed 24 | under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR 25 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 26 | specific language governing permissions and limitations under the License. 27 | */ 28 | 29 | #pragma once 30 | 31 | #include 32 | // 33 | // Pico includes 34 | #include "hardware/dma.h" 35 | #include "hardware/gpio.h" 36 | #include "hardware/irq.h" 37 | #include "hardware/spi.h" 38 | #include "pico/mutex.h" 39 | #include "pico/sem.h" 40 | #include "pico/types.h" 41 | 42 | #define SPI_FILL_CHAR (0xFF) 43 | 44 | // Pico 2 does not work with anything above 2mA 45 | #define sd_gpio_drive_strength GPIO_DRIVE_STRENGTH_2MA 46 | 47 | // "Class" representing SPIs 48 | typedef struct { 49 | // SPI HW 50 | spi_inst_t *hw_inst; 51 | uint miso_gpio; // SPI MISO GPIO number (not pin number) 52 | uint mosi_gpio; 53 | uint sck_gpio; 54 | uint baud_rate; 55 | // uint DMA_IRQ_num; // DMA_IRQ_0 or DMA_IRQ_1 56 | 57 | // Drive strength levels for GPIO outputs. 58 | // enum gpio_drive_strength { GPIO_DRIVE_STRENGTH_2MA = 0, GPIO_DRIVE_STRENGTH_4MA = 1, GPIO_DRIVE_STRENGTH_8MA = 2, 59 | // GPIO_DRIVE_STRENGTH_12MA = 3 } 60 | // bool set_drive_strength; 61 | // enum gpio_drive_strength mosi_gpio_drive_strength; 62 | // enum gpio_drive_strength sck_gpio_drive_strength; 63 | 64 | // State variables: 65 | uint tx_dma; 66 | uint rx_dma; 67 | dma_channel_config tx_dma_cfg; 68 | dma_channel_config rx_dma_cfg; 69 | //irq_handler_t dma_isr; // Ignored: no longer used 70 | bool initialized; 71 | //semaphore_t sem; 72 | mutex_t mutex; 73 | } spi_t; 74 | 75 | #ifdef __cplusplus 76 | extern "C" { 77 | #endif 78 | 79 | //bool __not_in_flash_func(spi_transfer)(spi_t *pSPI, const uint8_t *tx, uint8_t *rx, size_t length); 80 | bool spi_transfer(spi_t *pSPI, const uint8_t *tx, uint8_t *rx, size_t length); 81 | void spi_lock(spi_t *pSPI); 82 | void spi_unlock(spi_t *pSPI); 83 | bool my_spi_init(spi_t *pSPI); 84 | 85 | #ifdef __cplusplus 86 | } 87 | #endif 88 | 89 | /* 90 | This uses the Pico LED to show SD card activity. 91 | You can use it to get a rough idea of utilization. 92 | Warning: Pico W uses GPIO 25 for SPI communication to the CYW43439. 93 | 94 | You can enable this by putting something like 95 | add_compile_definitions(USE_LED=1) 96 | in CMakeLists.txt, for example. 97 | */ 98 | #if !defined(NO_PICO_LED) && defined(USE_LED) && USE_LED && defined(PICO_DEFAULT_LED_PIN) 99 | # define LED_INIT() \ 100 | { \ 101 | gpio_init(PICO_DEFAULT_LED_PIN); \ 102 | gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT); \ 103 | } 104 | # define LED_ON() gpio_put(PICO_DEFAULT_LED_PIN, 1) 105 | # define LED_OFF() gpio_put(PICO_DEFAULT_LED_PIN, 0) 106 | #else 107 | # define LED_ON() 108 | # define LED_OFF() 109 | # define LED_INIT() 110 | #endif 111 | 112 | /* [] END OF FILE */ 113 | -------------------------------------------------------------------------------- /sio.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the a8-pico-sio project -- 3 | * An Atari 8-bit SIO drive and (turbo) tape emulator for 4 | * Raspberry Pi Pico, see 5 | * 6 | * https://github.com/woj76/a8-pico-sio 7 | * 8 | * For information on what / whose work it is based on, check the corresponding 9 | * source files and the README file. This file is licensed under GNU General 10 | * Public License 3.0 or later. 11 | * 12 | * Copyright (C) 2025 Wojciech Mostowski 13 | */ 14 | 15 | #pragma once 16 | 17 | #include "config.h" 18 | 19 | #include 20 | 21 | extern volatile int8_t high_speed; 22 | extern volatile bool fs_error_or_change; 23 | 24 | void main_sio_loop(); 25 | -------------------------------------------------------------------------------- /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 | // CDC FIFO size of TX and RX 101 | #define CFG_TUD_CDC_RX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) 102 | #define CFG_TUD_CDC_TX_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) 103 | 104 | // CDC Endpoint transfer buffer size, more is faster 105 | #define CFG_TUD_CDC_EP_BUFSIZE (TUD_OPT_HIGH_SPEED ? 512 : 64) 106 | 107 | // MSC Buffer size of Device Mass storage 108 | #define CFG_TUD_MSC_EP_BUFSIZE 512 109 | 110 | #ifdef __cplusplus 111 | } 112 | #endif 113 | 114 | #endif /* _TUSB_CONFIG_H_ */ 115 | -------------------------------------------------------------------------------- /usb_descriptors.c: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2019 Ha Thach (tinyusb.org) 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | */ 25 | 26 | #include "tusb.h" 27 | 28 | /* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug. 29 | * Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC. 30 | * 31 | * Auto ProductID layout's Bitmap: 32 | * [MSB] HID | MSC | CDC [LSB] 33 | */ 34 | #define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) ) 35 | #define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ 36 | _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) ) 37 | 38 | #define USB_VID 0xCafe 39 | #define USB_BCD 0x0200 40 | 41 | //--------------------------------------------------------------------+ 42 | // Device Descriptors 43 | //--------------------------------------------------------------------+ 44 | tusb_desc_device_t const desc_device = 45 | { 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 | { 73 | return (uint8_t const *) &desc_device; 74 | } 75 | 76 | //--------------------------------------------------------------------+ 77 | // Configuration Descriptor 78 | //--------------------------------------------------------------------+ 79 | 80 | enum 81 | { 82 | ITF_NUM_CDC = 0, 83 | ITF_NUM_CDC_DATA, 84 | ITF_NUM_MSC, 85 | ITF_NUM_TOTAL 86 | }; 87 | 88 | #if CFG_TUSB_MCU == OPT_MCU_LPC175X_6X || CFG_TUSB_MCU == OPT_MCU_LPC177X_8X || CFG_TUSB_MCU == OPT_MCU_LPC40XX 89 | // LPC 17xx and 40xx endpoint type (bulk/interrupt/iso) are fixed by its number 90 | // 0 control, 1 In, 2 Bulk, 3 Iso, 4 In, 5 Bulk etc ... 91 | #define EPNUM_CDC_NOTIF 0x81 92 | #define EPNUM_CDC_OUT 0x02 93 | #define EPNUM_CDC_IN 0x82 94 | 95 | #define EPNUM_MSC_OUT 0x05 96 | #define EPNUM_MSC_IN 0x85 97 | 98 | #elif CFG_TUSB_MCU == OPT_MCU_SAMG || CFG_TUSB_MCU == OPT_MCU_SAMX7X 99 | // SAMG & SAME70 don't support a same endpoint number with different direction IN and OUT 100 | // e.g EP1 OUT & EP1 IN cannot exist together 101 | #define EPNUM_CDC_NOTIF 0x81 102 | #define EPNUM_CDC_OUT 0x02 103 | #define EPNUM_CDC_IN 0x83 104 | 105 | #define EPNUM_MSC_OUT 0x04 106 | #define EPNUM_MSC_IN 0x85 107 | 108 | #elif CFG_TUSB_MCU == OPT_MCU_CXD56 109 | // CXD56 doesn't support a same endpoint number with different direction IN and OUT 110 | // e.g EP1 OUT & EP1 IN cannot exist together 111 | // CXD56 USB driver has fixed endpoint type (bulk/interrupt/iso) and direction (IN/OUT) by its number 112 | // 0 control (IN/OUT), 1 Bulk (IN), 2 Bulk (OUT), 3 In (IN), 4 Bulk (IN), 5 Bulk (OUT), 6 In (IN) 113 | #define EPNUM_CDC_NOTIF 0x83 114 | #define EPNUM_CDC_OUT 0x02 115 | #define EPNUM_CDC_IN 0x81 116 | 117 | #define EPNUM_MSC_OUT 0x05 118 | #define EPNUM_MSC_IN 0x84 119 | 120 | #elif CFG_TUSB_MCU == OPT_MCU_FT90X || CFG_TUSB_MCU == OPT_MCU_FT93X 121 | // FT9XX doesn't support a same endpoint number with different direction IN and OUT 122 | // e.g EP1 OUT & EP1 IN cannot exist together 123 | #define EPNUM_CDC_NOTIF 0x81 124 | #define EPNUM_CDC_OUT 0x02 125 | #define EPNUM_CDC_IN 0x83 126 | 127 | #define EPNUM_MSC_OUT 0x04 128 | #define EPNUM_MSC_IN 0x85 129 | 130 | #else 131 | #define EPNUM_CDC_NOTIF 0x81 132 | #define EPNUM_CDC_OUT 0x02 133 | #define EPNUM_CDC_IN 0x82 134 | 135 | #define EPNUM_MSC_OUT 0x03 136 | #define EPNUM_MSC_IN 0x83 137 | 138 | #endif 139 | 140 | #define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_MSC_DESC_LEN) 141 | 142 | // full speed configuration 143 | uint8_t const desc_fs_configuration[] = 144 | { 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, 64), 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, 64), 153 | }; 154 | 155 | #if TUD_OPT_HIGH_SPEED 156 | // Per USB specs: high speed capable device must report device_qualifier and other_speed_configuration 157 | 158 | // high speed configuration 159 | uint8_t const desc_hs_configuration[] = 160 | { 161 | // Config number, interface count, string index, total length, attribute, power in mA 162 | TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100), 163 | 164 | // Interface number, string index, EP notification address and size, EP data address (out, in) and size. 165 | TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 512), 166 | 167 | // Interface number, string index, EP Out & EP In address, EP size 168 | TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 5, EPNUM_MSC_OUT, EPNUM_MSC_IN, 512), 169 | }; 170 | 171 | // other speed configuration 172 | uint8_t desc_other_speed_config[CONFIG_TOTAL_LEN]; 173 | 174 | // device qualifier is mostly similar to device descriptor since we don't change configuration based on speed 175 | tusb_desc_device_qualifier_t const desc_device_qualifier = 176 | { 177 | .bLength = sizeof(tusb_desc_device_qualifier_t), 178 | .bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER, 179 | .bcdUSB = USB_BCD, 180 | 181 | .bDeviceClass = TUSB_CLASS_MISC, 182 | .bDeviceSubClass = MISC_SUBCLASS_COMMON, 183 | .bDeviceProtocol = MISC_PROTOCOL_IAD, 184 | 185 | .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, 186 | .bNumConfigurations = 0x01, 187 | .bReserved = 0x00 188 | }; 189 | 190 | // Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request 191 | // Application return pointer to descriptor, whose contents must exist long enough for transfer to complete. 192 | // device_qualifier descriptor describes information about a high-speed capable device that would 193 | // change if the device were operating at the other speed. If not highspeed capable stall this request. 194 | uint8_t const* tud_descriptor_device_qualifier_cb(void) 195 | { 196 | return (uint8_t const*) &desc_device_qualifier; 197 | } 198 | 199 | // Invoked when received GET OTHER SEED CONFIGURATION DESCRIPTOR request 200 | // Application return pointer to descriptor, whose contents must exist long enough for transfer to complete 201 | // Configuration descriptor in the other speed e.g if high speed then this is for full speed and vice versa 202 | uint8_t const* tud_descriptor_other_speed_configuration_cb(uint8_t index) 203 | { 204 | (void) index; // for multiple configurations 205 | 206 | // if link speed is high return fullspeed config, and vice versa 207 | // Note: the descriptor type is OHER_SPEED_CONFIG instead of CONFIG 208 | memcpy(desc_other_speed_config, 209 | (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_fs_configuration : desc_hs_configuration, 210 | CONFIG_TOTAL_LEN); 211 | 212 | desc_other_speed_config[1] = TUSB_DESC_OTHER_SPEED_CONFIG; 213 | 214 | return desc_other_speed_config; 215 | } 216 | 217 | #endif // highspeed 218 | 219 | 220 | // Invoked when received GET CONFIGURATION DESCRIPTOR 221 | // Application return pointer to descriptor 222 | // Descriptor contents must exist long enough for transfer to complete 223 | uint8_t const * tud_descriptor_configuration_cb(uint8_t index) 224 | { 225 | (void) index; // for multiple configurations 226 | 227 | #if TUD_OPT_HIGH_SPEED 228 | // Although we are highspeed, host may be fullspeed. 229 | return (tud_speed_get() == TUSB_SPEED_HIGH) ? desc_hs_configuration : desc_fs_configuration; 230 | #else 231 | return desc_fs_configuration; 232 | #endif 233 | } 234 | 235 | //--------------------------------------------------------------------+ 236 | // String Descriptors 237 | //--------------------------------------------------------------------+ 238 | 239 | // array of pointer to string descriptors 240 | char const* string_desc_arr [] = 241 | { 242 | (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) 243 | "woj", // 1: Manufacturer 244 | "A8PicoSIO", // 2: Product 245 | "123456789012", // 3: Serials, should use chip ID 246 | "A8PicoSIO CDC", // 4: CDC Interface 247 | "A8PicoSIO MSC", // 5: MSC Interface 248 | }; 249 | 250 | static uint16_t _desc_str[32]; 251 | 252 | // Invoked when received GET STRING DESCRIPTOR request 253 | // Application return pointer to descriptor, whose contents must exist long enough for transfer to complete 254 | uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) 255 | { 256 | (void) langid; 257 | 258 | uint8_t chr_count; 259 | 260 | if ( index == 0) 261 | { 262 | memcpy(&_desc_str[1], string_desc_arr[0], 2); 263 | chr_count = 1; 264 | }else 265 | { 266 | // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. 267 | // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors 268 | 269 | if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) return NULL; 270 | 271 | const char* str = string_desc_arr[index]; 272 | 273 | // Cap at max char 274 | chr_count = (uint8_t) strlen(str); 275 | if ( chr_count > 31 ) chr_count = 31; 276 | 277 | // Convert ASCII string into UTF-16 278 | for(uint8_t i=0; i 13 | */ 14 | 15 | #include 16 | 17 | #include "wav_decode.hpp" 18 | #include "io.hpp" 19 | #include "options.hpp" 20 | 21 | uint32_t wav_avg_reads; 22 | uint wav_avg_offset; 23 | int32_t wav_avg_sum; 24 | uint32_t wav_silence_threshold; 25 | wav_header_type wav_header; 26 | volatile uint8_t wav_sample_size; 27 | uint8_t wav_sample_div; 28 | uint32_t wav_filter_window_size; 29 | uint32_t wav_scaled_sample_rate; 30 | uint32_t wav_last_silence; 31 | uint32_t wav_last_count; 32 | bool cas_last_block_marker; 33 | uint32_t wav_last_duration; 34 | uint8_t wav_last_duration_bit; 35 | int16_t wav_prev_sample; 36 | bool wav_filter1_started; 37 | bool wav_filter2_started; 38 | 39 | int16_t zcoeff1; 40 | int16_t zcoeff2; 41 | 42 | int32_t goertzel_int8(int8_t *x, int16_t zcoeff) { 43 | int32_t z; 44 | int32_t zprev = 0; 45 | int32_t zprev2 = 0; 46 | int8_t xn; 47 | for(int n=0; n>14) - zprev2; 49 | zprev2 = zprev; 50 | zprev = z; 51 | } 52 | return (zprev2*zprev2 + zprev*zprev - ((zcoeff*zprev)>>14)*zprev2) >> 5; 53 | } 54 | 55 | int32_t goertzel_int16(int16_t *x, int16_t zcoeff) { 56 | int32_t z; 57 | int32_t zprev = 0; 58 | int32_t zprev2 = 0; 59 | for(int n=0; n>6) + ((zcoeff*zprev)>>14) - zprev2; 61 | zprev2 = zprev; 62 | zprev = z; 63 | } 64 | return (zprev2*zprev2 + zprev*zprev - ((zcoeff*zprev)>>14)*zprev2) >> 5; 65 | } 66 | 67 | int16_t filter1(int16_t s) { 68 | int16_t rs; 69 | static int16_t prs; 70 | static int16_t ps; 71 | if(!wav_filter1_started) { 72 | rs = s; 73 | ps = s; 74 | prs = rs; 75 | } else { 76 | rs = prs*4/10 + (s-ps)*4/10; 77 | ps = s; 78 | prs = rs; 79 | } 80 | wav_filter1_started = true; 81 | return rs; 82 | } 83 | 84 | int16_t filter2(int16_t s) { 85 | int16_t rs; 86 | static int16_t prs; 87 | static int16_t ps; 88 | 89 | if(!wav_filter2_started) { 90 | rs = s; 91 | prs = rs; 92 | } else { 93 | rs = s*1/12 + prs*11/12; 94 | prs = rs; 95 | } 96 | wav_filter2_started = true; 97 | return rs; 98 | } 99 | 100 | // Stepping average over the last 16 values 101 | #define NUM_READS_SH 4 102 | #define NUM_READS (1u << NUM_READS_SH) 103 | 104 | int32_t filter_avg(int32_t s) { 105 | static int32_t values[NUM_READS]; 106 | wav_avg_reads++; 107 | if(wav_avg_reads > NUM_READS) 108 | wav_avg_sum -= values[wav_avg_offset]; 109 | wav_avg_sum += s; 110 | values[wav_avg_offset] = s; 111 | wav_avg_offset = (wav_avg_offset + 1) & (NUM_READS-1); 112 | if(wav_avg_reads <= NUM_READS) 113 | return wav_avg_sum / wav_avg_reads; 114 | return wav_avg_sum >> NUM_READS_SH; 115 | } 116 | 117 | void init_wav() { 118 | wav_avg_reads = 0; 119 | wav_avg_offset = 0; 120 | wav_avg_sum = 0; 121 | wav_prev_sample = 0; 122 | wav_filter1_started = false; 123 | wav_filter2_started = false; 124 | wav_last_silence = 0; 125 | wav_last_count = 0; 126 | wav_last_duration = 0; 127 | 128 | wav_sample_size = (wav_header.bits_per_sample == 16) ? 2 : 1; 129 | wav_sample_div = (wav_header.sample_rate > 48000) ? 2 : 1; 130 | wav_silence_threshold = wav_header.sample_rate / (wav_sample_div*20); // 32 131 | float coeff1 = 2.0*cosf(2.0*M_PI*(3995.0*wav_sample_div/(float)wav_header.sample_rate)); 132 | float coeff2 = 2.0*cosf(2.0*M_PI*(5327.0*wav_sample_div/(float)wav_header.sample_rate)); 133 | zcoeff1 = coeff1*(1<<14); 134 | zcoeff2 = coeff2*(1<<14); 135 | 136 | wav_filter_window_size = cas_block_turbo ? 0 : (wav_header.sample_rate < 44100 ? 12 : 20*wav_sample_div); 137 | if(cas_block_turbo) 138 | wav_scaled_sample_rate = wav_header.sample_rate / wav_sample_div; 139 | else 140 | // The ideal sampling frequency for PAL is 31668(.7), for NTSC 31960(.54) 141 | wav_scaled_sample_rate = wav_header.sample_rate < 44100 ? wav_header.sample_rate : (current_options[clock_option_index] ? 31960 : 31668); 142 | cas_sample_duration = (timing_base_clock+wav_scaled_sample_rate/2)/wav_scaled_sample_rate; 143 | pwm_bit = cas_block_turbo ? 0 : 1; 144 | cas_fsk_bit = pwm_bit; 145 | wav_last_duration_bit = cas_fsk_bit; 146 | } 147 | -------------------------------------------------------------------------------- /wav_decode.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the a8-pico-sio project -- 3 | * An Atari 8-bit SIO drive and (turbo) tape emulator for 4 | * Raspberry Pi Pico, see 5 | * 6 | * https://github.com/woj76/a8-pico-sio 7 | * 8 | * For information on what / whose work it is based on, check the corresponding 9 | * source files and the README file. This file is licensed under GNU General 10 | * Public License 3.0 or later. 11 | * 12 | * Copyright (C) 2025 Wojciech Mostowski 13 | */ 14 | 15 | #pragma once 16 | 17 | #include 18 | 19 | #include "config.h" 20 | #include "mounts.hpp" 21 | 22 | extern wav_header_type wav_header; 23 | extern volatile uint8_t wav_sample_size; 24 | extern uint8_t wav_last_duration_bit; 25 | extern uint32_t wav_scaled_sample_rate; 26 | extern uint8_t wav_sample_div; 27 | extern int16_t wav_prev_sample; 28 | extern uint32_t wav_last_silence; 29 | extern uint32_t wav_last_duration; 30 | extern bool cas_last_block_marker; 31 | extern uint32_t wav_filter_window_size; 32 | extern uint32_t wav_last_count; 33 | extern uint32_t wav_silence_threshold; 34 | 35 | extern int16_t zcoeff1; 36 | extern int16_t zcoeff2; 37 | 38 | int32_t goertzel_int8(int8_t *x, int16_t zcoeff); 39 | int32_t goertzel_int16(int16_t *x, int16_t zcoeff); 40 | int16_t filter1(int16_t s); 41 | int16_t filter2(int16_t s); 42 | int32_t filter_avg(int32_t s); 43 | void init_wav(); 44 | --------------------------------------------------------------------------------