├── COPYING ├── SIO2Arduino.ino ├── atari.h ├── config.h ├── disk_drive.cpp ├── disk_drive.h ├── disk_image.cpp ├── disk_image.h ├── drive_access.cpp ├── drive_access.h ├── drive_control.cpp ├── drive_control.h ├── readme.txt ├── sdrive.cpp ├── sdrive.h ├── sio_channel.cpp └── sio_channel.h /COPYING: -------------------------------------------------------------------------------- 1 | 2 | GNU GENERAL PUBLIC LICENSE 3 | Version 1, February 1989 4 | 5 | Copyright (C) 1989 Free Software Foundation, Inc. 6 | 675 Mass Ave, Cambridge, MA 02139, USA 7 | Everyone is permitted to copy and distribute verbatim copies 8 | of this license document, but changing it is not allowed. 9 | 10 | Preamble 11 | 12 | The license agreements of most software companies try to keep users 13 | at the mercy of those companies. By contrast, our General Public 14 | License is intended to guarantee your freedom to share and change free 15 | software--to make sure the software is free for all its users. The 16 | General Public License applies to the Free Software Foundation's 17 | software and to any other program whose authors commit to using it. 18 | You can use it for your programs, too. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Specifically, the General Public License is designed to make 22 | sure that you have the freedom to give away or sell copies of free 23 | software, that you receive source code or can get it if you want it, 24 | that you can change the software or use pieces of it in new free 25 | programs; and that you know you can do these things. 26 | 27 | To protect your rights, we need to make restrictions that forbid 28 | anyone to deny you these rights or to ask you to surrender the rights. 29 | These restrictions translate to certain responsibilities for you if you 30 | distribute copies of the software, or if you modify it. 31 | 32 | For example, if you distribute copies of a such a program, whether 33 | gratis or for a fee, you must give the recipients all the rights that 34 | you have. You must make sure that they, too, receive or can get the 35 | source code. And you must tell them their rights. 36 | 37 | We protect your rights with two steps: (1) copyright the software, and 38 | (2) offer you this license which gives you legal permission to copy, 39 | distribute and/or modify the software. 40 | 41 | Also, for each author's protection and ours, we want to make certain 42 | that everyone understands that there is no warranty for this free 43 | software. If the software is modified by someone else and passed on, we 44 | want its recipients to know that what they have is not the original, so 45 | that any problems introduced by others will not reflect on the original 46 | authors' reputations. 47 | 48 | The precise terms and conditions for copying, distribution and 49 | modification follow. 50 | 51 | GNU GENERAL PUBLIC LICENSE 52 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 53 | 54 | 0. This License Agreement applies to any program or other work which 55 | contains a notice placed by the copyright holder saying it may be 56 | distributed under the terms of this General Public License. The 57 | "Program", below, refers to any such program or work, and a "work based 58 | on the Program" means either the Program or any work containing the 59 | Program or a portion of it, either verbatim or with modifications. Each 60 | licensee is addressed as "you". 61 | 62 | 1. You may copy and distribute verbatim copies of the Program's source 63 | code as you receive it, in any medium, provided that you conspicuously and 64 | appropriately publish on each copy an appropriate copyright notice and 65 | disclaimer of warranty; keep intact all the notices that refer to this 66 | General Public License and to the absence of any warranty; and give any 67 | other recipients of the Program a copy of this General Public License 68 | along with the Program. You may charge a fee for the physical act of 69 | transferring a copy. 70 | 71 | 2. You may modify your copy or copies of the Program or any portion of 72 | it, and copy and distribute such modifications under the terms of Paragraph 73 | 1 above, provided that you also do the following: 74 | 75 | a) cause the modified files to carry prominent notices stating that 76 | you changed the files and the date of any change; and 77 | 78 | b) cause the whole of any work that you distribute or publish, that 79 | in whole or in part contains the Program or any part thereof, either 80 | with or without modifications, to be licensed at no charge to all 81 | third parties under the terms of this General Public License (except 82 | that you may choose to grant warranty protection to some or all 83 | third parties, at your option). 84 | 85 | c) If the modified program normally reads commands interactively when 86 | run, you must cause it, when started running for such interactive use 87 | in the simplest and most usual way, to print or display an 88 | announcement including an appropriate copyright notice and a notice 89 | that there is no warranty (or else, saying that you provide a 90 | warranty) and that users may redistribute the program under these 91 | conditions, and telling the user how to view a copy of this General 92 | Public License. 93 | 94 | d) You may charge a fee for the physical act of transferring a 95 | copy, and you may at your option offer warranty protection in 96 | exchange for a fee. 97 | 98 | Mere aggregation of another independent work with the Program (or its 99 | derivative) on a volume of a storage or distribution medium does not bring 100 | the other work under the scope of these terms. 101 | 102 | 3. You may copy and distribute the Program (or a portion or derivative of 103 | it, under Paragraph 2) in object code or executable form under the terms of 104 | Paragraphs 1 and 2 above provided that you also do one of the following: 105 | 106 | a) accompany it with the complete corresponding machine-readable 107 | source code, which must be distributed under the terms of 108 | Paragraphs 1 and 2 above; or, 109 | 110 | b) accompany it with a written offer, valid for at least three 111 | years, to give any third party free (except for a nominal charge 112 | for the cost of distribution) a complete machine-readable copy of the 113 | corresponding source code, to be distributed under the terms of 114 | Paragraphs 1 and 2 above; or, 115 | 116 | c) accompany it with the information you received as to where the 117 | corresponding source code may be obtained. (This alternative is 118 | allowed only for noncommercial distribution and only if you 119 | received the program in object code or executable form alone.) 120 | 121 | Source code for a work means the preferred form of the work for making 122 | modifications to it. For an executable file, complete source code means 123 | all the source code for all modules it contains; but, as a special 124 | exception, it need not include source code for modules which are standard 125 | libraries that accompany the operating system on which the executable 126 | file runs, or for standard header files or definitions files that 127 | accompany that operating system. 128 | 129 | 4. You may not copy, modify, sublicense, distribute or transfer the 130 | Program except as expressly provided under this General Public License. 131 | Any attempt otherwise to copy, modify, sublicense, distribute or transfer 132 | the Program is void, and will automatically terminate your rights to use 133 | the Program under this License. However, parties who have received 134 | copies, or rights to use copies, from you under this General Public 135 | License will not have their licenses terminated so long as such parties 136 | remain in full compliance. 137 | 138 | 5. By copying, distributing or modifying the Program (or any work based 139 | on the Program) you indicate your acceptance of this license to do so, 140 | and all its terms and conditions. 141 | 142 | 6. Each time you redistribute the Program (or any work based on the 143 | Program), the recipient automatically receives a license from the original 144 | licensor to copy, distribute or modify the Program subject to these 145 | terms and conditions. You may not impose any further restrictions on the 146 | recipients' exercise of the rights granted herein. 147 | 148 | 7. The Free Software Foundation may publish revised and/or new versions 149 | of the General Public License from time to time. Such new versions will 150 | be similar in spirit to the present version, but may differ in detail to 151 | address new problems or concerns. 152 | 153 | Each version is given a distinguishing version number. If the Program 154 | specifies a version number of the license which applies to it and "any 155 | later version", you have the option of following the terms and conditions 156 | either of that version or of any later version published by the Free 157 | Software Foundation. If the Program does not specify a version number of 158 | the license, you may choose any version ever published by the Free Software 159 | Foundation. 160 | 161 | 8. If you wish to incorporate parts of the Program into other free 162 | programs whose distribution conditions are different, write to the author 163 | to ask for permission. For software which is copyrighted by the Free 164 | Software Foundation, write to the Free Software Foundation; we sometimes 165 | make exceptions for this. Our decision will be guided by the two goals 166 | of preserving the free status of all derivatives of our free software and 167 | of promoting the sharing and reuse of software generally. 168 | 169 | NO WARRANTY 170 | 171 | 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 172 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 173 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 174 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 175 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 176 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 177 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 178 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 179 | REPAIR OR CORRECTION. 180 | 181 | 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 182 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 183 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 184 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 185 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 186 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 187 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 188 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 189 | POSSIBILITY OF SUCH DAMAGES. 190 | 191 | END OF TERMS AND CONDITIONS 192 | 193 | Appendix: How to Apply These Terms to Your New Programs 194 | 195 | If you develop a new program, and you want it to be of the greatest 196 | possible use to humanity, the best way to achieve this is to make it 197 | free software which everyone can redistribute and change under these 198 | terms. 199 | 200 | To do so, attach the following notices to the program. It is safest to 201 | attach them to the start of each source file to most effectively convey 202 | the exclusion of warranty; and each file should have at least the 203 | "copyright" line and a pointer to where the full notice is found. 204 | 205 | 206 | Copyright (C) 19yy 207 | 208 | This program is free software; you can redistribute it and/or modify 209 | it under the terms of the GNU General Public License as published by 210 | the Free Software Foundation; either version 1, or (at your option) 211 | any later version. 212 | 213 | This program is distributed in the hope that it will be useful, 214 | but WITHOUT ANY WARRANTY; without even the implied warranty of 215 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 216 | GNU General Public License for more details. 217 | 218 | You should have received a copy of the GNU General Public License 219 | along with this program; if not, write to the Free Software 220 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 221 | 222 | Also add information on how to contact you by electronic and paper mail. 223 | 224 | If the program is interactive, make it output a short notice like this 225 | when it starts in an interactive mode: 226 | 227 | Gnomovision version 69, Copyright (C) 19xx name of author 228 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 229 | This is free software, and you are welcome to redistribute it 230 | under certain conditions; type `show c' for details. 231 | 232 | The hypothetical commands `show w' and `show c' should show the 233 | appropriate parts of the General Public License. Of course, the 234 | commands you use may be called something other than `show w' and `show 235 | c'; they could even be mouse-clicks or menu items--whatever suits your 236 | program. 237 | 238 | You should also get your employer (if you work as a programmer) or your 239 | school, if any, to sign a "copyright disclaimer" for the program, if 240 | necessary. Here a sample; alter the names: 241 | 242 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 243 | program `Gnomovision' (a program to direct compilers to make passes 244 | at assemblers) written by James Hacker. 245 | 246 | , 1 April 1989 247 | Ty Coon, President of Vice 248 | 249 | That's all there is to it! 250 | -------------------------------------------------------------------------------- /SIO2Arduino.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * sio2arduino.ino - An Atari 8-bit device emulator for Arduino. 3 | * 4 | * Copyright (c) 2012 Whizzo Software LLC (Daniel Noguerol) 5 | * 6 | * This file is part of the SIO2Arduino project which emulates 7 | * Atari 8-bit SIO devices on Arduino hardware. 8 | * 9 | * SIO2Arduino is free software; you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation; either version 2 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * SIO2Arduino is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with SIO2Arduino; if not, write to the Free Software 21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 22 | */ 23 | #include "config.h" 24 | #include 25 | #include "atari.h" 26 | #include "sio_channel.h" 27 | #include "disk_drive.h" 28 | #ifdef LCD_DISPLAY 29 | #include 30 | #endif 31 | 32 | /** 33 | * Function declarations 34 | */ 35 | DriveStatus* getDeviceStatus(int deviceId); 36 | SectorDataInfo* readSector(int deviceId, unsigned long sector, byte *data); 37 | boolean writeSector(int deviceId, unsigned long sector, byte* data, unsigned long length); 38 | boolean format(int deviceId, int density); 39 | int getFileList(int startIndex, int count, FileEntry *entries); 40 | void mountFileIndex(int deviceId, int ix); 41 | void changeDirectory(int ix); 42 | 43 | /** 44 | * Global variables 45 | */ 46 | DriveAccess driveAccess(getDeviceStatus, readSector, writeSector, format); 47 | DriveControl driveControl(getFileList, mountFileIndex, changeDirectory); 48 | SIOChannel sioChannel(PIN_ATARI_CMD, &SIO_UART, &driveAccess, &driveControl); 49 | SdFat32 card; 50 | SdFile currDir; 51 | SdFile file; // TODO: make this unnecessary 52 | DiskDrive drive1; 53 | #ifdef SELECTOR_BUTTON 54 | boolean isSwitchPressed = false; 55 | unsigned long lastSelectionPress; 56 | boolean isFileOpened=false; 57 | #endif 58 | #ifdef RESET_BUTTON 59 | unsigned long lastResetPress; 60 | #endif 61 | #ifdef LCD_DISPLAY 62 | LiquidCrystal lcd(PIN_LCD_RD,PIN_LCD_ENABLE,PIN_LCD_DB4,PIN_LCD_DB5,PIN_LCD_DB6,PIN_LCD_DB7); 63 | #endif 64 | 65 | void setup() { 66 | #ifdef DEBUG 67 | // set up logging serial port 68 | LOGGING_UART.begin(115200); 69 | while (!LOGGING_UART) { 70 | yield(); 71 | } 72 | LOG_MSG_CR(F("Debug logging enabled")); 73 | #ifdef ARDUINO_UNO 74 | LOG_MSG_CR(F("WARNING: using debug logging with Uno, which only has 1 UART - SIO will conflict")); 75 | LOG_MSG_FLUSH(); // make sure this log displays before making the SIO connection 76 | #endif 77 | #endif 78 | 79 | // initialize serial port to Atari 80 | SIO_UART.begin(19200); 81 | 82 | // set pin modes 83 | #ifdef SELECTOR_BUTTON 84 | pinMode(PIN_SELECTOR, INPUT_PULLUP); 85 | #endif 86 | #ifdef RESET_BUTTON 87 | pinMode(PIN_RESET, INPUT_PULLUP); 88 | #endif 89 | 90 | #ifdef LCD_DISPLAY 91 | // set up LCD if appropriate 92 | lcd.begin(16, 2); 93 | lcd.print(F("SIO2Arduino")); 94 | lcd.setCursor(0,1); 95 | #endif 96 | 97 | // initialize SD card 98 | LOG_MSG(F("Initializing SD card...")); 99 | pinMode(PIN_SD_CS, OUTPUT); 100 | 101 | if (!card.begin(PIN_SD_CS, SD_SCK_MHZ(50))) { 102 | LOG_MSG_CR(F(" failed.")); 103 | #ifdef LCD_DISPLAY 104 | lcd.print(F("SD Init Error")); 105 | #endif 106 | return; 107 | } 108 | 109 | if (!currDir.open("/")) { 110 | LOG_MSG_CR(F(" failed.")); 111 | #ifdef LCD_DISPLAY 112 | lcd.print(F("SD Root Error")); 113 | #endif 114 | return; 115 | } 116 | 117 | LOG_MSG_CR(F(" done.")); 118 | #ifdef LCD_DISPLAY 119 | lcd.print(F("READY")); 120 | delay(3000); 121 | #endif 122 | mountFilename(0, "AUTORUN.ATR"); 123 | } 124 | 125 | void loop() { 126 | // let the SIO channel do its thing 127 | sioChannel.runCycle(); 128 | 129 | #ifdef SELECTOR_BUTTON 130 | // watch the selector button (accounting for debounce) 131 | if (digitalRead(PIN_SELECTOR) == LOW && millis() - lastSelectionPress > 250 && isSwitchPressed==false) { 132 | lastSelectionPress = millis(); 133 | changeDisk(0); 134 | } else isSwitchPressed=(digitalRead(PIN_SELECTOR) == LOW); 135 | #endif 136 | #ifdef RESET_BUTTON 137 | // watch the reset button 138 | if (digitalRead(PIN_RESET) == LOW && millis() - lastResetPress > 250) { 139 | lastResetPress = millis(); 140 | mountFilename(0, "AUTORUN.ATR"); 141 | } 142 | #endif 143 | 144 | #ifdef ARDUINO_TEENSY 145 | if (SIO_UART.available()) 146 | SIO_CALLBACK(); 147 | #endif 148 | } 149 | 150 | void SIO_CALLBACK() { 151 | // inform the SIO channel that an incoming byte is available 152 | sioChannel.processIncomingByte(); 153 | } 154 | 155 | DriveStatus* getDeviceStatus(int deviceId) { 156 | return drive1.getStatus(); 157 | } 158 | 159 | SectorDataInfo* readSector(int deviceId, unsigned long sector, byte *data) { 160 | if (drive1.hasImage()) { 161 | return drive1.getSectorData(sector, data); 162 | } else { 163 | return NULL; 164 | } 165 | } 166 | 167 | boolean writeSector(int deviceId, unsigned long sector, byte* data, unsigned long length) { 168 | return (drive1.writeSectorData(sector, data, length) == length); 169 | } 170 | 171 | boolean format(int deviceId, int density) { 172 | char name[13]; 173 | 174 | // get current filename 175 | file.getName(name, 13); 176 | 177 | // close and delete the current file 178 | file.close(); 179 | file.remove(); 180 | 181 | LOG_MSG(F("Remove old file: ")); 182 | LOG_MSG_CR(name); 183 | 184 | // open new file for writing 185 | file.open(&currDir, name, O_RDWR | O_SYNC | O_CREAT); 186 | 187 | LOG_MSG(F("Created new file: ")); 188 | LOG_MSG_CR(name); 189 | 190 | // allow the virtual drive to format the image (and possibly alter its size) 191 | if (drive1.formatImage(&file, density)) { 192 | // set the new image file for the drive 193 | drive1.setImageFile(&file); 194 | return true; 195 | } else { 196 | return false; 197 | } 198 | } 199 | 200 | void changeDisk(int deviceId) { 201 | DirFat_t dir; 202 | char name[13]; 203 | boolean imageChanged = false; 204 | 205 | while (!imageChanged) { 206 | // get next dir entry 207 | int8_t result = currDir.readDir((DirFat_t*)&dir); 208 | 209 | // if we got back a 0, rewind the directory and get the first dir entry 210 | if (!result) { 211 | currDir.rewind(); 212 | result = currDir.readDir((DirFat_t*)&dir); 213 | } 214 | 215 | // if we have a valid file response code, open it 216 | if (result > 0 && isValidFilename((char*)&dir.name)) { 217 | createFilename(name, (char*)dir.name); 218 | imageChanged = mountFilename(deviceId, name); 219 | } 220 | } 221 | } 222 | 223 | boolean isValidFilename(char *s) { 224 | return ( s[0] != '.' && // ignore hidden files 225 | s[0] != '_' && ( // ignore bogus files created by OS X 226 | (s[8] == 'A' && s[9] == 'T' && s[10] == 'R') 227 | || (s[8] == 'X' && s[9] == 'F' && s[10] == 'D') 228 | #ifdef PRO_IMAGES 229 | || (s[8] == 'P' && s[9] == 'R' && s[10] == 'O') 230 | #endif 231 | #ifdef ATX_IMAGES 232 | || (s[8] == 'A' && s[9] == 'T' && s[10] == 'X') 233 | #endif 234 | #ifdef XEX_IMAGES 235 | || (s[8] == 'X' && s[9] == 'E' && s[10] == 'X') 236 | #endif 237 | ) 238 | ); 239 | } 240 | 241 | void createFilename(char* filename, char* name) { 242 | for (int i=0; i < 8; i++) { 243 | if (name[i] != ' ') { 244 | *(filename++) = name[i]; 245 | } 246 | } 247 | if (name[8] != ' ') { 248 | *(filename++) = '.'; 249 | *(filename++) = name[8]; 250 | *(filename++) = name[9]; 251 | *(filename++) = name[10]; 252 | } 253 | *(filename++) = '\0'; 254 | } 255 | 256 | /** 257 | * Returns a list of files in the current directory. 258 | * 259 | * startIndex = the first valid file in the directory to start from 260 | * count = how many files to return 261 | * entries = a pointer to the a FileEntry array to hold the returned data 262 | */ 263 | int getFileList(int startIndex, int count, FileEntry *entries) { 264 | DirFat_t dir; 265 | int currentEntry = 0; 266 | 267 | currDir.rewind(); 268 | 269 | int ix = 0; 270 | while (ix < count) { 271 | if (currDir.readDir((DirFat_t*)&dir) < 1) { 272 | break; 273 | } 274 | if (isValidFilename((char*)&dir.name) || (isSubdir(&dir) && dir.name[0] != '.')) { 275 | if (currentEntry >= startIndex) { 276 | memcpy(entries[ix].name, dir.name, 11); 277 | if (isSubdir(&dir)) { 278 | entries[ix].isDirectory = true; 279 | } else { 280 | entries[ix].isDirectory = false; 281 | } 282 | ix++; 283 | } 284 | currentEntry++; 285 | } 286 | } 287 | 288 | return ix; 289 | } 290 | 291 | /** 292 | * Changes the SD card directory. 293 | * 294 | * ix = index number (or -1 to go to parent directory) 295 | */ 296 | void changeDirectory(int ix) { 297 | FileEntry entries[1]; 298 | char name[13]; 299 | SdFile subDir; 300 | 301 | if (ix > -1) { 302 | getFileList(ix, 1, entries); 303 | createFilename(name, entries[0].name); 304 | if (subDir.open(&currDir, name, O_READ)) { 305 | currDir = subDir; 306 | } 307 | } else { 308 | if (subDir.open("/")) { 309 | currDir = subDir; 310 | } 311 | } 312 | } 313 | 314 | /** 315 | * Mount a file with the given index number. 316 | * 317 | * deviceId = the drive ID 318 | * ix = the index of the file to mount 319 | */ 320 | void mountFileIndex(int deviceId, int ix) { 321 | FileEntry entries[1]; 322 | char name[13]; 323 | 324 | // figure out what filename is associated with the index 325 | getFileList(ix, 1, entries); 326 | 327 | // build a full 8.3 filename 328 | createFilename(name, entries[0].name); 329 | 330 | // mount the image 331 | mountFilename(deviceId, name); 332 | } 333 | 334 | /** 335 | * Mount a file with the given name. 336 | * 337 | * deviceId = the drive ID 338 | * name = the name of the file to mount 339 | */ 340 | boolean mountFilename(int deviceId, char *name) { 341 | // close previously open file 342 | if (file.isOpen()) { 343 | file.close(); 344 | } 345 | 346 | if (file.open(&currDir, name, O_RDWR | O_SYNC) && drive1.setImageFile(&file)) { 347 | LOG_MSG_CR(name); 348 | 349 | #ifdef LCD_DISPLAY 350 | lcd.clear(); 351 | lcd.print(name); 352 | lcd.setCursor(0,1); 353 | #endif 354 | 355 | return true; 356 | } 357 | 358 | return false; 359 | } 360 | -------------------------------------------------------------------------------- /atari.h: -------------------------------------------------------------------------------- 1 | #ifndef ATARI_H 2 | #define ATARI_H 3 | 4 | const byte ACK = 0x41; 5 | const byte NAK = 0x4E; 6 | const byte COMPLETE = 0x43; 7 | const byte ERR = 0x45; 8 | 9 | const byte DELAY_T2 = 1; 10 | const byte DELAY_T3 = 2; 11 | const byte DELAY_T4 = 1; 12 | const byte DELAY_T5 = 1; 13 | 14 | const byte DENSITY_SD = 1; 15 | const byte DENSITY_ED = 2; 16 | const byte DENSITY_DD = 3; 17 | 18 | const unsigned long SD_SECTOR_SIZE = 128; 19 | const unsigned long MAX_SECTOR_SIZE = 128; 20 | 21 | struct CommandFrame { 22 | byte deviceId; 23 | byte command; 24 | byte aux1; 25 | byte aux2; 26 | byte checksum; 27 | }; 28 | 29 | struct HardwareStatus { 30 | byte controllerBusy:1; 31 | byte dataRequestOrIndex:1; 32 | byte dataLostOrTrack0:1; 33 | byte crcError:1; 34 | byte recordNotFound:1; 35 | byte recordType:1; 36 | byte writeProtect:1; 37 | byte notReady:1; 38 | }; 39 | 40 | struct CommandStatus { 41 | byte invalidCommandFrame:1; 42 | byte invalidDataFrame:1; 43 | byte writeFailure:1; 44 | byte writeProtect:1; 45 | byte motorStatus:1; 46 | byte doubleDensity:1; 47 | byte unused:1; 48 | byte enhancedDensity:1; 49 | }; 50 | 51 | struct StatusFrame { 52 | CommandStatus commandStatus; 53 | HardwareStatus hardwareStatus; 54 | byte timeout_lsb; 55 | byte timeout_msb; 56 | }; 57 | 58 | struct DriveStatus { 59 | unsigned long sectorSize; 60 | StatusFrame statusFrame; 61 | }; 62 | 63 | struct SectorDataInfo { 64 | unsigned long length; 65 | StatusFrame statusFrame; 66 | boolean validStatusFrame; 67 | boolean error; 68 | }; 69 | 70 | #endif 71 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * config.h - Configuration for the SIO2Arduino build. 3 | * 4 | * Copyright (c) 2012 Whizzo Software LLC (Daniel Noguerol) 5 | * 6 | * This file is part of the SIO2Arduino project which emulates 7 | * Atari 8-bit SIO devices on Arduino hardware. 8 | * 9 | * SIO2Arduino is free software; you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation; either version 2 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * SIO2Arduino is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with SIO2Arduino; if not, write to the Free Software 21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 22 | */ 23 | #ifndef CONFIG_H 24 | #define CONFIG_H 25 | 26 | /** 27 | * These are SIO2Arduino feature definitions. 28 | */ 29 | 30 | // These are the Arduino devices that can be used. I'm sure others would work, 31 | // but these are the only ones I have to test with. Only one of these should 32 | // be uncommented. 33 | #define ARDUINO_UNO // Arduino Uno board 34 | //#define ARDUINO_MEGA // Arduino Mega 2560/ADK board 35 | //#define ARDUINO_TEENSY // PJRC Teensy 2.0 36 | 37 | // Uncomment this line if you are using an LCD display 38 | //#define LCD_DISPLAY 39 | 40 | // Uncomment this line if you are using a hardware button for image selection 41 | #define SELECTOR_BUTTON 42 | 43 | // Uncomment this line if you want a reset button (automatically mounts /AUTORUN.ATR) (deprecated, AUTORUN.ATR always mounted if it exists) 44 | //#define RESET_BUTTON 45 | 46 | // uncomment if using an Ethernet shield for SD capabilities 47 | //#define ETHERNET_SHIELD 48 | 49 | // uncomment for PRO image format support 50 | //#define PRO_IMAGES 51 | 52 | // uncomment for ATX image format support (Mega 2560 only) 53 | //#define ATX_IMAGES 54 | 55 | // uncomment for XEX "image" support 56 | #define XEX_IMAGES 57 | 58 | // uncomment this to enable debug logging -- make sure the HARDWARE_UART isn't the same as 59 | // the LOGGING_UART defined at the bottom of the file 60 | //#define DEBUG 61 | 62 | /* 63 | * These are the Arduino pin definitions. 64 | */ 65 | 66 | #ifdef ARDUINO_TEENSY 67 | #define PIN_ATARI_CMD 4 // the Atari SIO command line - usually the purple wire on the SIO cable 68 | #else 69 | #define PIN_ATARI_CMD 2 // the Atari SIO command line - usually the purple wire on the SIO cable 70 | #endif 71 | 72 | // for now, you can't change these pin definitions 73 | #ifdef ETHERNET_SHIELD 74 | #define PIN_SD_CS 4 // the SD CS line 75 | #else 76 | #ifdef ARDUINO_MEGA 77 | #define PIN_SD_CS 53 // the SD breakout board's CS (chip select) pin 78 | #define PIN_SD_DI 51 // the SD breakout board's DI pin 79 | #define PIN_SD_DO 50 // the SD breakout board's DO pin 80 | #define PIN_SD_CLK 52 // the SD breakout board's CLK pin 81 | #endif 82 | #ifdef ARDUINO_UNO 83 | #define PIN_SD_CS 10 // the SD breakout board's CS (chip select) pin 84 | #define PIN_SD_DI 11 // the SD breakout board's DI pin 85 | #define PIN_SD_DO 12 // the SD breakout board's DO pin 86 | #define PIN_SD_CLK 13 // the SD breakout board's CLK pin 87 | #endif 88 | #ifdef ARDUINO_TEENSY 89 | #define PIN_SD_CS 0 // the SD breakout board's CS (chip select) pin 90 | #define PIN_SD_DI 1 // the SD breakout board's DI pin 91 | #define PIN_SD_DO 2 // the SD breakout board's DO pin 92 | #define PIN_SD_CLK 3 // the SD breakout board's CLK pin 93 | #endif 94 | #endif 95 | 96 | #ifdef SELECTOR_BUTTON 97 | #define PIN_SELECTOR 3 // the selector button pin 98 | #endif 99 | 100 | #ifdef RESET_BUTTON 101 | #ifdef ARDUINO_TEENSY 102 | #define PIN_RESET 5 // the reset button pin 103 | #else 104 | #define PIN_RESET 3 // the reset button pin 105 | #endif 106 | #endif 107 | 108 | #ifdef LCD_DISPLAY 109 | #ifdef ARDUINO_MEGA 110 | #define PIN_LCD_RD 5 // * 111 | #define PIN_LCD_ENABLE 6 // * 112 | #define PIN_LCD_DB4 10 // * LCD display pins 113 | #define PIN_LCD_DB5 9 // * 114 | #define PIN_LCD_DB6 8 // * 115 | #define PIN_LCD_DB7 7 // * 116 | #else 117 | #define PIN_LCD_RD 4 // * 118 | #define PIN_LCD_ENABLE 5 // * 119 | #define PIN_LCD_DB4 9 // * LCD display pins 120 | #define PIN_LCD_DB5 8 // * 121 | #define PIN_LCD_DB6 7 // * 122 | #define PIN_LCD_DB7 6 // * 123 | #endif 124 | #endif 125 | 126 | // the hardware UART to use for SIO bus communication 127 | #if defined(ARDUINO_MEGA) || defined(ARDUINO_TEENSY) 128 | #define SIO_UART Serial1 129 | #define SIO_CALLBACK serialEvent1 130 | #else 131 | #define SIO_UART Serial 132 | #define SIO_CALLBACK serialEvent 133 | #endif 134 | 135 | //#define DEBUG 136 | /** 137 | * Logging/debug config 138 | */ 139 | #ifdef DEBUG 140 | #define LOGGING_UART Serial 141 | #define LOG_MSG(...) LOGGING_UART.print(__VA_ARGS__) 142 | #define LOG_MSG_CR(...) LOGGING_UART.println(__VA_ARGS__) 143 | #define LOG_MSG_FLUSH() LOGGING_UART.flush() 144 | #else 145 | #define LOG_MSG(...) 146 | #define LOG_MSG_CR(...) 147 | #define LOG_MSG_FLUSH() 148 | #endif 149 | 150 | #endif 151 | -------------------------------------------------------------------------------- /disk_drive.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * disk_drive.cpp - Virtual disk drive class. 3 | * 4 | * Copyright (c) 2012 Whizzo Software LLC (Daniel Noguerol) 5 | * 6 | * This file is part of the SIO2Arduino project which emulates 7 | * Atari 8-bit SIO devices on Arduino hardware. 8 | * 9 | * SIO2Arduino is free software; you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation; either version 2 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * SIO2Arduino is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with SIO2Arduino; if not, write to the Free Software 21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 22 | */ 23 | #include "disk_drive.h" 24 | 25 | DiskDrive::DiskDrive() { 26 | // reset device status 27 | memset(&m_driveStatus.statusFrame, 0, sizeof(m_driveStatus.statusFrame)); 28 | 29 | // set standard attributes 30 | m_driveStatus.statusFrame.timeout_lsb = 0xE0; 31 | } 32 | 33 | DriveStatus* DiskDrive::getStatus() { 34 | m_driveStatus.statusFrame.commandStatus.writeProtect = m_diskImage.isReadOnly() ? 0x01 : 0x00; 35 | return &m_driveStatus; 36 | } 37 | 38 | boolean DiskDrive::setImageFile(SdFile *file) { 39 | boolean result = m_diskImage.setFile(file); 40 | if (result) { 41 | // set device status 42 | memset(&m_driveStatus.statusFrame, 0, sizeof(m_driveStatus.statusFrame)); 43 | m_driveStatus.statusFrame.commandStatus.enhancedDensity = m_diskImage.isEnhancedDensity() ? 0x01 : 0x00; 44 | m_driveStatus.statusFrame.commandStatus.doubleDensity = m_diskImage.isDoubleDensity() ? 0x01 : 0x00; 45 | m_driveStatus.statusFrame.hardwareStatus.writeProtect = m_diskImage.isReadOnly() ? 0x00 : 0x01; 46 | m_driveStatus.sectorSize = m_diskImage.getSectorSize(); 47 | } 48 | return result; 49 | } 50 | 51 | SectorDataInfo* DiskDrive::getSectorData(unsigned long sector, byte *data) { 52 | if (m_diskImage.hasImage()) { 53 | unsigned long startTime = micros(); 54 | 55 | SectorDataInfo *info = m_diskImage.getSectorData(sector, data); 56 | // store the status frame if valid 57 | if (info->validStatusFrame) { 58 | memcpy(&m_driveStatus.statusFrame, &(info->statusFrame), sizeof(m_driveStatus.statusFrame)); 59 | } 60 | 61 | // for images with copy-protection, make read time consistent across all sector reads to 62 | // allow skew-based protection to work 63 | if (m_diskImage.hasCopyProtection()) { 64 | unsigned long time = micros() - startTime; 65 | unsigned long delta = (MIN_PRO_SECTOR_READ * ((time - 1) / MIN_PRO_SECTOR_READ + 1)) - time; 66 | delay(delta / 1000); // Arduino's delayMicroseconds() can't reliably 67 | delayMicroseconds(delta % 1000); // handle an argument larger than 16,383 68 | } 69 | 70 | return info; 71 | } else { 72 | return NULL; 73 | } 74 | } 75 | 76 | unsigned long DiskDrive::writeSectorData(unsigned long sector, byte *data, unsigned long len) { 77 | return m_diskImage.writeSectorData(sector, data, len); 78 | } 79 | 80 | boolean DiskDrive::formatImage(SdFile *file, int density) { 81 | return m_diskImage.format(file, density); 82 | } 83 | 84 | boolean DiskDrive::hasImage() { 85 | return m_diskImage.hasImage(); 86 | } 87 | -------------------------------------------------------------------------------- /disk_drive.h: -------------------------------------------------------------------------------- 1 | #ifndef DRIVE_H 2 | #define DRIVE_H 3 | 4 | #include 5 | #include "atari.h" 6 | #include "disk_image.h" 7 | 8 | const unsigned long MIN_PRO_SECTOR_READ = 25000 - DELAY_T5 * 1000; 9 | 10 | class DiskDrive { 11 | public: 12 | DiskDrive(); 13 | DriveStatus* getStatus(); 14 | boolean setImageFile(SdFile* file); 15 | unsigned long getImageSectorSize(); 16 | SectorDataInfo* getSectorData(unsigned long sector, byte *data); 17 | unsigned long writeSectorData(unsigned long sector, byte* data, unsigned long len); 18 | boolean formatImage(SdFile* file, int density); 19 | boolean hasImage(); 20 | private: 21 | DriveStatus m_driveStatus; 22 | DiskImage m_diskImage; 23 | }; 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /disk_image.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * disk_image.cpp - Handles disk images in various formats. 3 | * 4 | * Copyright (c) 2012 Whizzo Software LLC (Daniel Noguerol) 5 | * 6 | * This file is part of the SIO2Arduino project which emulates 7 | * Atari 8-bit SIO devices on Arduino hardware. 8 | * 9 | * SIO2Arduino is free software; you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation; either version 2 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * SIO2Arduino is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with SIO2Arduino; if not, write to the Free Software 21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 22 | */ 23 | #include "disk_image.h" 24 | #include "config.h" 25 | 26 | #ifdef XEX_IMAGES 27 | // The KBoot loader was written by Ken Siders (atari@columbus.rr.com) 28 | byte KBOOT_LOADER[] = { 29 | 0x00,0x03,0x00,0x07,0x14,0x07,0x4c,0x14,0x07,0xAA,0xBB,0x00,0x00,0xa9,0x46,0x8d,0xc6,0x02,0xd0,0xfe,0xa0,0x00,0xa9,0x6b, 30 | 0x91,0x58,0x20,0xd9,0x07,0xb0,0xee,0x20,0xc4,0x07,0xad,0x7a,0x08,0x0d,0x76,0x08,0xd0,0xe3,0xa5,0x80,0x8d,0xe0,0x02,0xa5, 31 | 0x81,0x8d,0xe1,0x02,0xa9,0x00,0x8d,0xe2,0x02,0x8d,0xe3,0x02,0x20,0xeb,0x07,0xb0,0xcc,0xa0,0x00,0x91,0x80,0xa5,0x80,0xc5, 32 | 0x82,0xd0,0x06,0xa5,0x81,0xc5,0x83,0xf0,0x08,0xe6,0x80,0xd0,0x02,0xe6,0x81,0xd0,0xe3,0xad,0x76,0x08,0xd0,0xaf,0xad,0xe2, 33 | 0x02,0x8d,0x70,0x07,0x0d,0xe3,0x02,0xf0,0x0e,0xad,0xe3,0x02,0x8d,0x71,0x07,0x20,0xff,0xff,0xad,0x7a,0x08,0xd0,0x13,0xa9, 34 | 0x00,0x8d,0xe2,0x02,0x8d,0xe3,0x02,0x20,0xae,0x07,0xad,0x7a,0x08,0xd0,0x03,0x4c,0x3c,0x07,0xa9,0x00,0x85,0x80,0x85,0x81, 35 | 0x85,0x82,0x85,0x83,0xad,0xe0,0x02,0x85,0x0a,0x85,0x0c,0xad,0xe1,0x02,0x85,0x0b,0x85,0x0d,0xa9,0x01,0x85,0x09,0xa9,0x00, 36 | 0x8d,0x44,0x02,0x6c,0xe0,0x02,0x20,0xeb,0x07,0x85,0x80,0x20,0xeb,0x07,0x85,0x81,0xa5,0x80,0xc9,0xff,0xd0,0x10,0xa5,0x81, 37 | 0xc9,0xff,0xd0,0x0a,0x20,0xeb,0x07,0x85,0x80,0x20,0xeb,0x07,0x85,0x81,0x20,0xeb,0x07,0x85,0x82,0x20,0xeb,0x07,0x85,0x83, 38 | 0x60,0x20,0xeb,0x07,0xc9,0xff,0xd0,0x09,0x20,0xeb,0x07,0xc9,0xff,0xd0,0x02,0x18,0x60,0x38,0x60,0xad,0x09,0x07,0x0d,0x0a, 39 | 0x07,0x0d,0x0b,0x07,0xf0,0x79,0xac,0x79,0x08,0x10,0x50,0xee,0x77,0x08,0xd0,0x03,0xee,0x78,0x08,0xa9,0x31,0x8d,0x00,0x03, 40 | 0xa9,0x01,0x8d,0x01,0x03,0xa9,0x52,0x8d,0x02,0x03,0xa9,0x40,0x8d,0x03,0x03,0xa9,0x80,0x8d,0x04,0x03,0xa9,0x08,0x8d,0x05, 41 | 0x03,0xa9,0x1f,0x8d,0x06,0x03,0xa9,0x80,0x8d,0x08,0x03,0xa9,0x00,0x8d,0x09,0x03,0xad,0x77,0x08,0x8d,0x0a,0x03,0xad,0x78, 42 | 0x08,0x8d,0x0b,0x03,0x20,0x59,0xe4,0xad,0x03,0x03,0xc9,0x02,0xb0,0x22,0xa0,0x00,0x8c,0x79,0x08,0xb9,0x80,0x08,0xaa,0xad, 43 | 0x09,0x07,0xd0,0x0b,0xad,0x0a,0x07,0xd0,0x03,0xce,0x0b,0x07,0xce,0x0a,0x07,0xce,0x09,0x07,0xee,0x79,0x08,0x8a,0x18,0x60, 44 | 0xa0,0x01,0x8c,0x76,0x08,0x38,0x60,0xa0,0x01,0x8c,0x7a,0x08,0x38,0x60,0x00,0x03,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00 45 | }; 46 | #endif 47 | 48 | DiskImage::DiskImage() { 49 | m_fileRef = NULL; 50 | } 51 | 52 | boolean DiskImage::setFile(SdFile* file) { 53 | m_fileRef = file; 54 | m_fileSize = file->fileSize(); 55 | 56 | // if image is valid... 57 | if (loadFile(file)) { 58 | return true; 59 | } else { 60 | m_fileRef = NULL; 61 | return false; 62 | } 63 | } 64 | 65 | byte DiskImage::getType() { 66 | return m_type; 67 | } 68 | 69 | unsigned long DiskImage::getSectorSize() { 70 | return m_sectorSize; 71 | } 72 | 73 | /** 74 | * Read data from drive image. 75 | */ 76 | SectorDataInfo* DiskImage::getSectorData(unsigned long sector, byte *data) { 77 | m_sectorInfo.length = m_sectorSize; 78 | m_sectorInfo.error = false; 79 | m_sectorInfo.validStatusFrame = false; 80 | 81 | // delay if necessary 82 | if (m_sectorReadDelay) { 83 | delay(m_sectorReadDelay); 84 | } 85 | 86 | // seek to proper offset in file 87 | switch (m_type) { 88 | #ifdef XEX_IMAGES 89 | case TYPE_XEX: { 90 | if (sector < 4) { 91 | unsigned long ix = (sector - 1) * m_sectorSize; 92 | for (int i=0; i < m_sectorSize; i++) { 93 | data[i] = KBOOT_LOADER[ix+i]; 94 | } 95 | return &m_sectorInfo; 96 | } else { 97 | m_fileRef->seekSet((sector - 4) * m_sectorSize); 98 | } 99 | } 100 | break; 101 | #endif 102 | #ifdef PRO_IMAGES 103 | case TYPE_PRO: { 104 | // if this is a PRO image, we seek based on the sector number + the sector header size (omitting the header) 105 | m_fileRef->seekSet(m_headerSize + ((sector - 1) * (m_sectorSize + sizeof(PROSectorHeader)))); 106 | 107 | // then we read the sector header 108 | for (int i=0; i < sizeof(PROSectorHeader); i++) { 109 | ((byte*)&m_proSectorHeader)[i] = (byte)m_fileRef->read(); 110 | } 111 | 112 | // return the status frame so the drive can return it on a subsequent status command 113 | memcpy(&m_sectorInfo.statusFrame, &m_proSectorHeader, sizeof(m_sectorInfo.statusFrame)); 114 | m_sectorInfo.validStatusFrame = true; 115 | 116 | // if the header shows there was an error in this sector, return an error 117 | if (!m_proSectorHeader.statusFrame.hardwareStatus.crcError || !m_proSectorHeader.statusFrame.hardwareStatus.dataLostOrTrack0 || !m_proSectorHeader.statusFrame.hardwareStatus.recordNotFound) { 118 | m_sectorInfo.error = true; 119 | } else { 120 | // if there are phantom sector(s) associated with this sector, decide what to return 121 | if (m_usePhantoms && m_proSectorHeader.totalPhantoms > 0 && m_phantomFlip) { 122 | m_fileRef->seekSet(m_headerSize + (((720 + m_proSectorHeader.phantom1) - 1) * (m_sectorSize + sizeof(PROSectorHeader))) + sizeof(PROSectorHeader)); 123 | } 124 | } 125 | m_phantomFlip = !m_phantomFlip; // TODO: do bad sectors cause this to flip? 126 | } 127 | break; 128 | #endif 129 | #ifdef ATX_IMAGES 130 | case TYPE_ATX: { 131 | int ix = -1; 132 | for (int i=0; i < 720; i++) { 133 | if (m_sectorHeaders[i].sectorNumber == (sector-1)) { 134 | ix = i; 135 | if (!m_phantomFlip) { 136 | break; 137 | } 138 | } 139 | } 140 | m_sectorInfo.validStatusFrame = true; 141 | if (ix > -1) { 142 | m_fileRef->seekSet(m_sectorHeaders[ix].fileIndex); 143 | if (m_sectorHeaders[ix].sstatus > 0) { 144 | m_sectorInfo.error = true; 145 | } 146 | // hardware status bits for floppy controller are active low, so bit flip 147 | *((byte*)&m_sectorInfo.statusFrame.hardwareStatus) = ~(m_sectorHeaders[ix].sstatus); 148 | *((byte*)&m_sectorInfo.statusFrame.commandStatus) = 0x10; 149 | *(&m_sectorInfo.statusFrame.timeout_lsb) = 0xE0; 150 | } else { 151 | // TODO: right now we just send back a random data frame -- is this correct? 152 | m_fileRef->seekSet(0); 153 | m_sectorInfo.error = true; 154 | // set the missing sector data bit (active low) 155 | *((byte*)&m_sectorInfo.statusFrame.hardwareStatus) = 0xF7; 156 | *((byte*)&m_sectorInfo.statusFrame.commandStatus) = 0x10; 157 | *(&m_sectorInfo.statusFrame.timeout_lsb) = 0xE0; 158 | } 159 | // for now, do the same global flip of duplicate sector data as PRO 160 | // (alternate between first/last duplicate sectors on successive reads) 161 | // TODO: this should be based on timing of sector angular position 162 | m_phantomFlip = !m_phantomFlip; 163 | } 164 | break; 165 | #endif 166 | default: 167 | m_fileRef->seekSet(m_headerSize + ((sector - 1) * m_sectorSize)); 168 | break; 169 | } 170 | 171 | // read sector data into buffer 172 | int b; 173 | for (int i=0; i < m_sectorSize; i++) { 174 | b = m_fileRef->read(); 175 | if (b != -1) { 176 | data[i] = (byte)b; 177 | } else { 178 | data[i] = 0; 179 | } 180 | } 181 | 182 | return &m_sectorInfo; 183 | } 184 | 185 | /** 186 | * Write data to drive image. 187 | */ 188 | unsigned long DiskImage::writeSectorData(unsigned long sector, byte* data, unsigned long len) { 189 | if (!m_readOnly) { 190 | // seek to proper offset in file 191 | unsigned long offset = m_headerSize + ((sector - 1) * m_sectorSize); 192 | m_fileRef->seekSet(offset); 193 | 194 | // write the data 195 | return m_fileRef->write(data, len); 196 | } 197 | 198 | return false; 199 | } 200 | 201 | /** 202 | * Format drive image. 203 | */ 204 | boolean DiskImage::format(SdFile *file, int density) { 205 | if (!m_readOnly) { 206 | // determine file length 207 | unsigned long length = FORMAT_SS_SD_40; 208 | 209 | // make sure we're at beginning of file 210 | file->seekSet(0); 211 | 212 | // if disk is an ATR, write the header 213 | if (m_type == TYPE_ATR) { 214 | ATRHeader header; 215 | memset(&header, 0, sizeof(header)); 216 | header.signature = ATR_SIGNATURE; 217 | header.pars = length / 0x10; 218 | header.secSize = SECTOR_SIZE_SD; 219 | file->write((byte*)&header, sizeof(header)); 220 | } 221 | 222 | // create empty byte buffer 223 | for (unsigned long i=0; i < length; i++) { 224 | file->write((byte)0); 225 | } 226 | 227 | return true; 228 | } 229 | 230 | return false; 231 | } 232 | 233 | boolean DiskImage::loadFile(SdFile *file) { 234 | char filename[13]; 235 | 236 | // make sure we're at the beginning of file 237 | file->seekSet(0); 238 | 239 | // read first 16 bytes of file & rewind again 240 | byte header[16]; 241 | for (int i=0; i < 16; i++) { 242 | header[i] = (byte)file->read(); 243 | } 244 | file->seekSet(0); 245 | 246 | // check if it's an ATR 247 | ATRHeader* atrHeader = (ATRHeader*)&header; 248 | if (atrHeader->signature == ATR_SIGNATURE) { 249 | m_type = TYPE_ATR; 250 | m_headerSize = 16; 251 | m_readOnly = false; 252 | m_sectorSize = atrHeader->secSize; 253 | m_sectorReadDelay = 0; 254 | 255 | LOG_MSG(F("Loaded ATR with sector size ")); 256 | LOG_MSG(atrHeader->secSize); 257 | LOG_MSG(F(": ")); 258 | 259 | return true; 260 | } 261 | 262 | #ifdef PRO_IMAGES 263 | // check if it's an APE PRO image 264 | PROFileHeader* proHeader = (PROFileHeader*)&header; 265 | if (proHeader->sectorCountHi * 256 + proHeader->sectorCountLo == ((m_fileSize-16)/(SECTOR_SIZE_SD+sizeof(PROSectorHeader))) && proHeader->magic == 'P') { 266 | m_type = TYPE_PRO; 267 | m_readOnly = true; 268 | m_headerSize = 16; 269 | m_sectorSize = SECTOR_SIZE_SD; 270 | m_sectorReadDelay = proHeader->sectorReadDelay * (1000/60); 271 | 272 | // set the phantom emulation mode 273 | switch (proHeader->phantomSectorMode) { 274 | case PSM_SIMPLE: 275 | case PSM_MINDSCAPE_SPECIAL: 276 | case PSM_STICKY: 277 | case PSM_SHIMMERING: 278 | case PSM_REVERSE_SHIMMER: 279 | m_usePhantoms = false; 280 | break; 281 | case PSM_GLOBAL_FLIP_FLOP: 282 | m_usePhantoms = true; 283 | m_phantomFlip = false; 284 | break; 285 | case PSM_GLOBAL_FLOP_FLIP: 286 | m_usePhantoms = true; 287 | m_phantomFlip = true; 288 | break; 289 | } 290 | 291 | LOG_MSG(F("Loaded PRO with sector size 128: ")); 292 | 293 | return true; 294 | } 295 | #endif 296 | 297 | #ifdef ATX_IMAGES 298 | // check if it's an ATX 299 | if (header[0] == 'A' && header[1] == 'T' && header[2] == '8' && header[3] == 'X') { 300 | m_type = TYPE_ATX; 301 | m_readOnly = true; 302 | m_sectorReadDelay = 0; 303 | m_sectorSize = 128; 304 | m_phantomFlip = false; 305 | 306 | unsigned long trackRecordSize; 307 | unsigned long l2; 308 | unsigned long fileIndex; 309 | 310 | // start with all sector numbers impossibly high (for a floppy disk) 311 | for (int i=0; i < 720; i++) { 312 | m_sectorHeaders[i].sectorNumber = 60000; 313 | } 314 | 315 | // read header size 316 | file->seekSet(28); 317 | fileIndex = file->read() + file->read() * 256 + file->read() * 512 + file->read() * 768; 318 | // skip to first track record 319 | file->seekSet(fileIndex); 320 | 321 | // NOTE: we're doing multiple file->read() statements to avoid creating any additional 322 | // heap variables (we have a lot more available program space than heap space) 323 | 324 | for (int i=0; i < 40; i++) { 325 | // read track header 326 | trackRecordSize = file->read(); 327 | trackRecordSize += file->read() * 256; 328 | trackRecordSize += file->read() * 512; 329 | trackRecordSize += file->read() * 768; 330 | file->read(); 331 | file->read(); 332 | file->read(); 333 | file->read(); 334 | byte trackNumber = file->read(); 335 | file->read(); 336 | int sectorCount = file->read(); 337 | sectorCount += file->read() * 256; 338 | file->read(); 339 | file->read(); 340 | file->read(); 341 | file->read(); 342 | file->read(); 343 | file->read(); 344 | file->read(); 345 | file->read(); 346 | l2 = file->read(); 347 | l2 += file->read() * 256; 348 | l2 += file->read() * 512; 349 | l2 += file->read() * 768; 350 | 351 | // seek to beginning of sector list 352 | file->seekSet(fileIndex + l2); 353 | 354 | // skip sector list header 355 | file->read(); 356 | file->read(); 357 | file->read(); 358 | file->read(); 359 | file->read(); 360 | file->read(); 361 | file->read(); 362 | file->read(); 363 | 364 | // read each sector 365 | for (int i2=0; i2< sectorCount; i2++) { 366 | byte sectorNum = file->read(); 367 | byte sectorStatus = file->read(); 368 | // skip sector position 369 | file->read(); 370 | file->read(); 371 | // read start data pos 372 | l2 = file->read(); 373 | l2 += file->read() * 256; 374 | l2 += file->read() * 512; 375 | l2 += file->read() * 768; 376 | m_sectorHeaders[trackNumber * 18 + i2].sectorNumber = (trackNumber * 18) + (sectorNum - 1); 377 | m_sectorHeaders[trackNumber * 18 + i2].sstatus = sectorStatus; 378 | m_sectorHeaders[trackNumber * 18 + i2].fileIndex = fileIndex + l2; 379 | } 380 | 381 | // move to next track record 382 | fileIndex += trackRecordSize; 383 | file->seekSet(fileIndex); 384 | } 385 | 386 | LOG_MSG(F("Loaded ATX with sector size 128: ")); 387 | return true; 388 | } 389 | #endif 390 | 391 | file->getName((char*)&filename, 13); 392 | int len = strlen(filename); 393 | char *extension = filename + len - 4; 394 | 395 | // check if it's an XFD 396 | // (since an XFD is just a raw data dump, we can only determine this by file name and size) 397 | if ((!strcmp(".XFD", extension) || !strcmp(".xfd", extension)) && (m_fileSize == FORMAT_SS_SD_40)) { 398 | m_type = TYPE_XFD; 399 | m_readOnly = false; 400 | m_headerSize = 0; 401 | m_sectorSize = SECTOR_SIZE_SD; 402 | m_sectorReadDelay = 0; 403 | 404 | LOG_MSG(F("Loaded XFD with sector size 128: ")); 405 | return true; 406 | #ifdef XEX_IMAGES 407 | } else if ((!strcmp(".XEX", extension) || !strcmp(".xex", extension))) { 408 | m_type = TYPE_XEX; 409 | m_readOnly = true; 410 | m_headerSize = 0; 411 | m_sectorSize = SECTOR_SIZE_SD; 412 | m_sectorReadDelay = 0; 413 | 414 | // set the size of the XEX in the correct bootloader location 415 | KBOOT_LOADER[9] = m_fileSize & 0xFF; 416 | KBOOT_LOADER[10] = m_fileSize >> 8; 417 | 418 | LOG_MSG(F("Loaded XEX with sector size 128: ")); 419 | return true; 420 | #endif 421 | } 422 | 423 | return false; 424 | } 425 | 426 | boolean DiskImage::hasImage() { 427 | return (m_fileRef != NULL); 428 | } 429 | 430 | boolean DiskImage::hasCopyProtection() { 431 | return (0 || 432 | #ifdef PRO_IMAGES 433 | m_type == TYPE_PRO || 434 | #endif 435 | #ifdef ATX_IMAGES 436 | m_type == TYPE_ATX || 437 | #endif 438 | 0); 439 | } 440 | 441 | boolean DiskImage::isEnhancedDensity() { 442 | return false; 443 | } 444 | 445 | boolean DiskImage::isDoubleDensity() { 446 | return false; 447 | } 448 | 449 | boolean DiskImage::isReadOnly() { 450 | return m_readOnly; 451 | } 452 | -------------------------------------------------------------------------------- /disk_image.h: -------------------------------------------------------------------------------- 1 | #ifndef DISK_IMAGE_h 2 | #define DISK_IMAGE_h 3 | 4 | #include 5 | #include 6 | #include "atari.h" 7 | #include "config.h" 8 | 9 | #define TYPE_ATR 1 10 | #define TYPE_XFD 2 11 | #ifdef PRO_IMAGES 12 | #define TYPE_PRO 3 13 | #endif 14 | #ifdef ATX_IMAGES 15 | #define TYPE_ATX 4 16 | #endif 17 | #ifdef XEX_IMAGES 18 | #define TYPE_XEX 5 19 | #endif 20 | 21 | #define SECTOR_SIZE_SD 128 22 | #define FORMAT_SS_SD_40 92160 23 | 24 | #ifdef ATX_IMAGES 25 | struct ATXSectorHeader { 26 | unsigned int sectorNumber; 27 | unsigned long fileIndex; 28 | byte sstatus; 29 | }; 30 | #endif 31 | 32 | // ATR format 33 | #define ATR_SIGNATURE 0x0296 34 | struct ATRHeader { 35 | unsigned int signature; 36 | unsigned int pars; 37 | unsigned int secSize; 38 | byte parsHigh; 39 | unsigned long crc; 40 | unsigned long unused; 41 | byte flags; 42 | }; 43 | 44 | #ifdef PRO_IMAGES 45 | // PRO format 46 | const byte PSM_SIMPLE = 0; 47 | const byte PSM_MINDSCAPE_SPECIAL = 1; 48 | const byte PSM_GLOBAL_FLIP_FLOP = 2; 49 | const byte PSM_GLOBAL_FLOP_FLIP = 3; 50 | const byte PSM_HEURISTIC = 4; 51 | const byte PSM_STICKY = 5; 52 | const byte PSM_REVERSE_STICKY = 6; 53 | const byte PSM_SHIMMERING = 7; 54 | const byte PSM_REVERSE_SHIMMER = 8; 55 | const byte PSM_ROLLING_THUNDER = 9; 56 | 57 | struct PROFileHeader { 58 | byte sectorCountHi; 59 | byte sectorCountLo; 60 | byte magic; 61 | byte imageType; 62 | byte phantomSectorMode; 63 | byte sectorReadDelay; 64 | byte g; 65 | byte h; 66 | byte i; 67 | byte j; 68 | byte k; 69 | byte l; 70 | byte m; 71 | byte n; 72 | byte o; 73 | byte p; 74 | }; 75 | 76 | struct PROSectorHeader { 77 | StatusFrame statusFrame; 78 | byte m; 79 | byte totalPhantoms; 80 | byte phantom4; 81 | byte phantom1; 82 | byte phantom2; 83 | byte phantom3; 84 | byte n; 85 | byte phantom5; 86 | }; 87 | #endif 88 | 89 | class DiskImage { 90 | public: 91 | DiskImage(); 92 | boolean setFile(SdFile* file); 93 | byte getType(); 94 | unsigned long getSectorSize(); 95 | SectorDataInfo* getSectorData(unsigned long sector, byte* data); 96 | unsigned long writeSectorData(unsigned long, byte* data, unsigned long size); 97 | boolean format(SdFile *file, int density); 98 | boolean isEnhancedDensity(); 99 | boolean isDoubleDensity(); 100 | boolean isReadOnly(); 101 | boolean hasImage(); 102 | boolean hasCopyProtection(); 103 | private: 104 | boolean loadFile(SdFile* file); 105 | SdFile* m_fileRef; 106 | byte m_type; 107 | unsigned long m_fileSize; 108 | boolean m_readOnly; 109 | unsigned long m_headerSize; 110 | unsigned long m_sectorSize; 111 | byte m_sectorReadDelay; 112 | SectorDataInfo m_sectorInfo; 113 | boolean m_usePhantoms; 114 | boolean m_phantomFlip; 115 | #ifdef PRO_IMAGES 116 | PROSectorHeader m_proSectorHeader; 117 | #endif 118 | #ifdef ATX_IMAGES 119 | ATXSectorHeader m_sectorHeaders[720]; 120 | #endif 121 | }; 122 | 123 | #endif 124 | -------------------------------------------------------------------------------- /drive_access.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * drive_access.cpp 3 | * 4 | * Copyright (c) 2012 Whizzo Software LLC (Daniel Noguerol) 5 | * 6 | * This file is part of the SIO2Arduino project which emulates 7 | * Atari 8-bit SIO devices on Arduino hardware. 8 | * 9 | * SIO2Arduino is free software; you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation; either version 2 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * SIO2Arduino is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with SIO2Arduino; if not, write to the Free Software 21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 22 | */ 23 | #include "drive_access.h" 24 | 25 | DriveAccess::DriveAccess(DriveStatus*(*a)(int), SectorDataInfo*(*b)(int,unsigned long,byte*), boolean(*c)(int,unsigned long,byte*,unsigned long), boolean(*d)(int,int)) { 26 | deviceStatusFunc = a; 27 | readSectorFunc = b; 28 | writeSectorFunc = c; 29 | formatFunc = d; 30 | } 31 | 32 | -------------------------------------------------------------------------------- /drive_access.h: -------------------------------------------------------------------------------- 1 | #ifndef DRIVE_ACCESS_h 2 | #define DRIVE_ACCESS_h 3 | 4 | #include 5 | #include "atari.h" 6 | 7 | class DriveAccess { 8 | public: 9 | DriveAccess(DriveStatus*(*deviceStatusFunc)(int), SectorDataInfo*(*readSectorFunc)(int,unsigned long,byte*), boolean(*writeSectorFunc)(int,unsigned long,byte*,unsigned long), boolean(*formatFunc)(int,int)); 10 | DriveStatus* (*deviceStatusFunc)(int); 11 | SectorDataInfo* (*readSectorFunc)(int,unsigned long,byte*); 12 | boolean (*writeSectorFunc)(int,unsigned long, byte*,unsigned long); 13 | boolean (*formatFunc)(int,int); 14 | }; 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /drive_control.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * disk_control.cpp 3 | * 4 | * Copyright (c) 2012 Whizzo Software LLC (Daniel Noguerol) 5 | * 6 | * This file is part of the SIO2Arduino project which emulates 7 | * Atari 8-bit SIO devices on Arduino hardware. 8 | * 9 | * SIO2Arduino is free software; you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation; either version 2 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * SIO2Arduino is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with SIO2Arduino; if not, write to the Free Software 21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 22 | */ 23 | #include "drive_control.h" 24 | 25 | DriveControl::DriveControl(int(*a)(int,int,FileEntry*), void(*b)(int,int), void(*c)(int)) { 26 | getFileList = a; 27 | mountFile = b; 28 | changeDir = c; 29 | } 30 | 31 | -------------------------------------------------------------------------------- /drive_control.h: -------------------------------------------------------------------------------- 1 | #ifndef DRIVE_CONTROL_h 2 | #define DRIVE_CONTROL_h 3 | 4 | struct FileEntry { 5 | char name[11]; 6 | bool isDirectory; 7 | }; 8 | 9 | class DriveControl { 10 | public: 11 | DriveControl(int(*getFileList)(int,int,FileEntry*), void(*mountFile)(int,int), void(*changeDir)(int)); 12 | 13 | int(*getFileList)(int,int,FileEntry*); 14 | void(*mountFile)(int,int); 15 | void(*changeDir)(int); 16 | }; 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | SIO2Arduino is an open-source implementation of the Atari SIO device protocol that runs on Arduino hardware. Arduino hardware is open-source and can be assembled by hand or purchased preassembled. 2 | 3 | Note: You will need the SdFat library (https://github.com/greiman/SdFat) in your Arduino libraries directory in order to compile. 4 | This has been tested using SdFat version 2.1.2. 5 | 6 | For more information on SIO2Arduino, see the website at: 7 | http://www.whizzosoftware.com/sio2arduino 8 | 9 | For more information on the Arduino platform, go to: 10 | http://www.arduino.cc 11 | -------------------------------------------------------------------------------- /sdrive.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * sdrive.cpp - SIO handler for processing SDrive commands. 3 | * 4 | * Copyright (c) 2012 Whizzo Software LLC (Daniel Noguerol) 5 | * 6 | * This file is part of the SIO2Arduino project which emulates 7 | * Atari 8-bit SIO devices on Arduino hardware. 8 | * 9 | * SIO2Arduino is free software; you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation; either version 2 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * SIO2Arduino is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with SIO2Arduino; if not, write to the Free Software 21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 22 | */ 23 | 24 | #import "sdrive.h" 25 | #import "config.h" 26 | 27 | SDriveHandler::SDriveHandler() { 28 | } 29 | 30 | void SDriveHandler::setDriveControl(DriveControl* driveControl) { 31 | m_driveControl = driveControl; 32 | } 33 | 34 | boolean SDriveHandler::isValidCommand(byte cmd) { 35 | return (cmd == CMD_SDRIVE_IDENT || 36 | cmd == CMD_SDRIVE_INIT || 37 | cmd == CMD_SDRIVE_CHROOT || 38 | cmd == CMD_SDRIVE_SWAP_VDN || 39 | cmd == CMD_SDRIVE_GETPARAMS || 40 | cmd == CMD_SDRIVE_GET_ENTRIES || 41 | cmd == CMD_SDRIVE_CHDIR_VDN || 42 | cmd == CMD_SDRIVE_CHDIR || 43 | cmd == CMD_SDRIVE_CHDIR_UP || 44 | cmd == CMD_SDRIVE_GET20 || 45 | cmd == CMD_SDRIVE_MOUNT_D0 || 46 | cmd == CMD_SDRIVE_MOUNT_D1 || 47 | cmd == CMD_SDRIVE_MOUNT_D2 || 48 | cmd == CMD_SDRIVE_MOUNT_D3 || 49 | cmd == CMD_SDRIVE_MOUNT_D4); 50 | } 51 | 52 | boolean SDriveHandler::isValidDevice(byte device) { 53 | return (device == DEVICE_SDRIVE); 54 | } 55 | 56 | void SDriveHandler::processCommand(CommandFrame* cmdFrame, Stream* stream) { 57 | switch (cmdFrame->command) { 58 | case CMD_SDRIVE_IDENT: 59 | cmdIdent(stream); 60 | break; 61 | case CMD_SDRIVE_INIT: 62 | cmdInit(stream); 63 | break; 64 | case CMD_SDRIVE_CHROOT: 65 | cmdChroot(stream); 66 | break; 67 | case CMD_SDRIVE_SWAP_VDN: 68 | cmdSwapVdn(stream); 69 | break; 70 | case CMD_SDRIVE_GETPARAMS: 71 | cmdGetParams(stream); 72 | break; 73 | case CMD_SDRIVE_GET_ENTRIES: 74 | cmdGetEntries(cmdFrame->aux1, stream); 75 | break; 76 | case CMD_SDRIVE_CHDIR_VDN: 77 | cmdChdirVDN(stream); 78 | break; 79 | case CMD_SDRIVE_CHDIR_UP: 80 | cmdChdirUp((cmdFrame->aux1 > 0), stream); 81 | break; 82 | case CMD_SDRIVE_CHDIR: 83 | cmdChdir(cmdFrame->aux2 * 256 + cmdFrame->aux1, stream); 84 | break; 85 | case CMD_SDRIVE_GET20: 86 | cmdGet20(cmdFrame->aux2 * 256 + cmdFrame->aux1, stream); 87 | break; 88 | case CMD_SDRIVE_MOUNT_D0: 89 | cmdMountDrive(0, cmdFrame->aux2 * 256 + cmdFrame->aux1, stream); 90 | break; 91 | case CMD_SDRIVE_MOUNT_D1: 92 | cmdMountDrive(1, cmdFrame->aux2 * 256 + cmdFrame->aux1, stream); 93 | break; 94 | case CMD_SDRIVE_MOUNT_D2: 95 | cmdMountDrive(2, cmdFrame->aux2 * 256 + cmdFrame->aux1, stream); 96 | break; 97 | case CMD_SDRIVE_MOUNT_D3: 98 | cmdMountDrive(3, cmdFrame->aux2 * 256 + cmdFrame->aux1, stream); 99 | break; 100 | case CMD_SDRIVE_MOUNT_D4: 101 | cmdMountDrive(4, cmdFrame->aux2 * 256 + cmdFrame->aux1, stream); 102 | break; 103 | } 104 | } 105 | 106 | void SDriveHandler::cmdIdent(Stream *stream) { 107 | delay(DELAY_T2); 108 | stream->write(ACK); 109 | delay(DELAY_T5); 110 | stream->write(COMPLETE); 111 | stream->write("SDrive01"); 112 | stream->write(0xB0); 113 | stream->flush(); 114 | } 115 | 116 | void SDriveHandler::cmdInit(Stream *stream) { 117 | // NO-OP 118 | delay(DELAY_T2); 119 | stream->write(ACK); 120 | delay(DELAY_T5); 121 | stream->write(COMPLETE); 122 | stream->flush(); 123 | } 124 | 125 | void SDriveHandler::cmdChroot(Stream *stream) { 126 | // NO-OP 127 | delay(DELAY_T2); 128 | stream->write(ACK); 129 | delay(DELAY_T5); 130 | stream->write(COMPLETE); 131 | stream->flush(); 132 | } 133 | 134 | void SDriveHandler::cmdSwapVdn(Stream *stream) { 135 | // NO-OP 136 | delay(DELAY_T2); 137 | stream->write(ACK); 138 | delay(DELAY_T5); 139 | stream->write(COMPLETE); 140 | stream->flush(); 141 | } 142 | 143 | void SDriveHandler::cmdGetParams(Stream *stream) { 144 | // NO-OP 145 | delay(DELAY_T2); 146 | stream->write(ACK); 147 | delay(DELAY_T5); 148 | stream->write(COMPLETE); 149 | stream->write(0x06); 150 | stream->write((byte)0x00); 151 | stream->write(0x06); 152 | stream->flush(); 153 | } 154 | 155 | void SDriveHandler::cmdGetEntries(byte n, Stream *stream) { 156 | // NO-OP 157 | delay(DELAY_T2); 158 | stream->write(ACK); 159 | delay(DELAY_T5); 160 | stream->write(COMPLETE); 161 | for (int i=0; i < n * 12; i++) { 162 | stream->write((byte)0x00); 163 | } 164 | stream->write((byte)0x00); 165 | stream->flush(); 166 | } 167 | 168 | void SDriveHandler::cmdChdirVDN(Stream* stream) { 169 | // NO-OP 170 | delay(DELAY_T2); 171 | stream->write(ACK); 172 | delay(DELAY_T5); 173 | stream->write(COMPLETE); 174 | for (int i=0; i < 14; i++) { 175 | stream->write((byte)0x00); 176 | } 177 | stream->write((byte)0x00); 178 | stream->flush(); 179 | } 180 | 181 | void SDriveHandler::cmdChdirUp(bool getDirName, Stream* stream) { 182 | delay(DELAY_T2); 183 | stream->write(ACK); 184 | delay(DELAY_T5); 185 | 186 | m_driveControl->changeDir(-1); 187 | 188 | stream->write(COMPLETE); 189 | 190 | if (getDirName) { 191 | stream->write('F'); 192 | for (int i=0; i < 10; i++) { 193 | stream->write(' '); 194 | } 195 | stream->write((byte)19); 196 | stream->write((byte)0); 197 | stream->write((byte)0); 198 | } 199 | 200 | stream->flush(); 201 | } 202 | 203 | void SDriveHandler::cmdChdir(int ix, Stream* stream) { 204 | delay(DELAY_T2); 205 | stream->write(ACK); 206 | delay(DELAY_T5); 207 | 208 | m_driveControl->changeDir(ix); 209 | 210 | stream->write(COMPLETE); 211 | stream->flush(); 212 | } 213 | 214 | void SDriveHandler::cmdGet20(int page, Stream *stream) { 215 | delay(DELAY_T2); 216 | stream->write(ACK); 217 | delay(DELAY_T5); 218 | stream->write(COMPLETE); 219 | byte chkSum = 0; 220 | 221 | FileEntry fileEntries[21]; 222 | for (int i=0; i < 21; i++) { 223 | memset(fileEntries[i].name, 0, 11); 224 | fileEntries[i].isDirectory = false; 225 | } 226 | 227 | int count = m_driveControl->getFileList(page, 21, fileEntries); 228 | 229 | char c; 230 | for (int i1=0; i1 < 20; i1++) { 231 | FileEntry entry = fileEntries[i1]; 232 | for (int i2=0; i2 < 11; i2++) { 233 | c = entry.name[i2]; 234 | stream->write(c); 235 | chkSum = ((chkSum+c)>>8) + ((chkSum+c)&0xff); 236 | } 237 | if (entry.isDirectory) { 238 | stream->write((byte)19); 239 | chkSum = ((chkSum+19)>>8) + ((chkSum+19)&0xff); 240 | } else { 241 | stream->write((byte)0x00); 242 | } 243 | } 244 | 245 | if (count > 20) { 246 | stream->write(fileEntries[20].name[0]); 247 | } else { 248 | stream->write((byte)0x00); 249 | } 250 | 251 | stream->write(chkSum); 252 | stream->flush(); 253 | } 254 | 255 | void SDriveHandler::cmdMountDrive(byte driveNum, byte index, Stream* stream) { 256 | delay(DELAY_T2); 257 | stream->write(ACK); 258 | 259 | m_driveControl->mountFile(1, index); 260 | 261 | delay(DELAY_T5); 262 | stream->write(COMPLETE); 263 | stream->flush(); 264 | } 265 | 266 | boolean SDriveHandler::printCmdName(byte cmd) { 267 | // we only compile this on DEBUG to save allocating string constants 268 | #ifdef DEBUG 269 | switch (cmd) { 270 | case CMD_SDRIVE_IDENT: 271 | LOG_MSG(F("SDRIVE IDENT")); 272 | break; 273 | case CMD_SDRIVE_INIT: 274 | LOG_MSG(F("SDRIVE INIT")); 275 | break; 276 | case CMD_SDRIVE_CHROOT: 277 | LOG_MSG(F("SDRIVE CHROOT")); 278 | break; 279 | case CMD_SDRIVE_SWAP_VDN: 280 | LOG_MSG(F("SDRIVE SWAP VDN")); 281 | break; 282 | case CMD_SDRIVE_GETPARAMS: 283 | LOG_MSG(F("SDRIVE GETPARAMS")); 284 | break; 285 | case CMD_SDRIVE_GET_ENTRIES: 286 | LOG_MSG(F("SDRIVE GET ENTRIES")); 287 | break; 288 | case CMD_SDRIVE_CHDIR_VDN: 289 | LOG_MSG(F("SDRIVE CHDIR VDN")); 290 | break; 291 | case CMD_SDRIVE_CHDIR: 292 | LOG_MSG(F("SDRIVE CHDIR")); 293 | break; 294 | case CMD_SDRIVE_CHDIR_UP: 295 | LOG_MSG(F("SDRIVE CHDIR UP")); 296 | break; 297 | case CMD_SDRIVE_GET20: 298 | LOG_MSG(F("SDRIVE GET20")); 299 | break; 300 | case CMD_SDRIVE_MOUNT_D0: 301 | LOG_MSG(F("SDRIVE MOUNTvD0")); 302 | break; 303 | case CMD_SDRIVE_MOUNT_D1: 304 | LOG_MSG(F("SDRIVE MOUNTvD1")); 305 | break; 306 | case CMD_SDRIVE_MOUNT_D2: 307 | LOG_MSG(F("SDRIVE MOUNTvD2")); 308 | break; 309 | case CMD_SDRIVE_MOUNT_D3: 310 | LOG_MSG(F("SDRIVE MOUNTvD3")); 311 | break; 312 | case CMD_SDRIVE_MOUNT_D4: 313 | LOG_MSG(F("SDRIVE MOUNTvD4")); 314 | break; 315 | default: 316 | return false; 317 | } 318 | #endif 319 | 320 | return true; 321 | } 322 | -------------------------------------------------------------------------------- /sdrive.h: -------------------------------------------------------------------------------- 1 | #ifndef SDRIVE_HANDLER_h 2 | #define SDRIVE_HANDLER_h 3 | 4 | #include 5 | #include "atari.h" 6 | #include "drive_control.h" 7 | 8 | const byte DEVICE_SDRIVE = 0x71; 9 | 10 | const byte CMD_SDRIVE_GET20 = 0xC0; 11 | const byte CMD_SDRIVE_IDENT = 0xE0; 12 | const byte CMD_SDRIVE_INIT = 0xE1; 13 | const byte CMD_SDRIVE_CHDIR_VDN = 0xE3; 14 | const byte CMD_SDRIVE_GET_ENTRIES = 0xEB; 15 | const byte CMD_SDRIVE_SWAP_VDN = 0xEE; 16 | const byte CMD_SDRIVE_GETPARAMS = 0xEF; 17 | const byte CMD_SDRIVE_MOUNT_D0 = 0xF0; 18 | const byte CMD_SDRIVE_MOUNT_D1 = 0xF1; 19 | const byte CMD_SDRIVE_MOUNT_D2 = 0xF2; 20 | const byte CMD_SDRIVE_MOUNT_D3 = 0xF3; 21 | const byte CMD_SDRIVE_MOUNT_D4 = 0xF4; 22 | const byte CMD_SDRIVE_CHDIR_UP = 0xFD; 23 | const byte CMD_SDRIVE_CHROOT = 0xFE; 24 | const byte CMD_SDRIVE_CHDIR = 0xFF; 25 | 26 | class SDriveHandler { 27 | public: 28 | SDriveHandler(); 29 | void setDriveControl(DriveControl* driveControl); 30 | boolean printCmdName(byte cmd); 31 | boolean isValidCommand(byte cmd); 32 | boolean isValidDevice(byte device); 33 | void processCommand(CommandFrame* cmdFrame, Stream* stream); 34 | void cmdIdent(Stream* stream); 35 | void cmdInit(Stream* stream); 36 | void cmdChroot(Stream* stream); 37 | void cmdSwapVdn(Stream* stream); 38 | void cmdGetParams(Stream* stream); 39 | void cmdGetEntries(byte n, Stream* stream); 40 | void cmdChdirVDN(Stream *stream); 41 | void cmdChdirUp(bool getDirName, Stream *stream); 42 | void cmdChdir(int index, Stream *stream); 43 | void cmdGet20(int startIndex, Stream *stream); 44 | void cmdMountDrive(byte driveNum, byte index, Stream* stream); 45 | 46 | DriveControl* m_driveControl; 47 | }; 48 | 49 | #endif 50 | -------------------------------------------------------------------------------- /sio_channel.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * sio_channel.cpp - Handles communication with the Atari SIO bus. 3 | * 4 | * Copyright (c) 2012 Whizzo Software LLC (Daniel Noguerol) 5 | * 6 | * This file is part of the SIO2Arduino project which emulates 7 | * Atari 8-bit SIO devices on Arduino hardware. 8 | * 9 | * SIO2Arduino is free software; you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation; either version 2 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * SIO2Arduino is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with SIO2Arduino; if not, write to the Free Software 21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 22 | */ 23 | #include "sio_channel.h" 24 | #include "config.h" 25 | 26 | SIOChannel::SIOChannel(int cmdPin, Stream* stream, DriveAccess *driveAccess, DriveControl *driveControl) { 27 | m_cmdPin = cmdPin; 28 | m_stream = stream; 29 | m_driveAccess = driveAccess; 30 | m_driveControl = driveControl; 31 | 32 | m_sdriveHandler.setDriveControl(m_driveControl); 33 | 34 | // set command pin to be read 35 | pinMode(m_cmdPin, INPUT); 36 | 37 | m_cmdPinState = STATE_INIT; 38 | } 39 | 40 | void SIOChannel::runCycle() { 41 | // watch the Atari command line 42 | switch (m_cmdPinState) { 43 | case STATE_INIT: 44 | if (digitalRead(m_cmdPin) == HIGH) { 45 | m_cmdPinState = STATE_WAIT_CMD_START; 46 | } 47 | break; 48 | case STATE_WAIT_CMD_START: 49 | if (digitalRead(m_cmdPin) == LOW) { 50 | m_cmdPinState = STATE_READ_CMD; 51 | resetCommandFrameBuffer(); 52 | } 53 | break; 54 | case STATE_READ_CMD: 55 | m_startTimeoutInterval = millis(); 56 | // if command frame is fully read... 57 | if (m_cmdFramePtr - (byte*)&m_cmdFrame == COMMAND_FRAME_SIZE) { 58 | dumpCommandFrame(); 59 | // process command frame 60 | if (isChecksumValid() && isCommandForThisDevice()) { 61 | if (isValidCommand() && isValidAuxData()) { 62 | m_cmdPinState = processCommand(); 63 | } else { 64 | m_stream->write(NAK); 65 | m_cmdPinState = STATE_WAIT_CMD_START; 66 | } 67 | } else { 68 | m_cmdPinState = STATE_WAIT_CMD_START; 69 | } 70 | // otherwise, check for command read timeout 71 | } else if (millis() - m_startTimeoutInterval > READ_CMD_TIMEOUT) { 72 | m_cmdPinState = STATE_WAIT_CMD_START; 73 | } 74 | break; 75 | case STATE_READ_DATAFRAME: 76 | // check for timeout 77 | if (millis() - m_startTimeoutInterval > READ_FRAME_TIMEOUT) { 78 | m_cmdPinState = STATE_WAIT_CMD_START; 79 | } 80 | break; 81 | case STATE_WAIT_CMD_END: 82 | if (digitalRead(m_cmdPin) == HIGH) { 83 | m_cmdPinState = STATE_WAIT_CMD_START; 84 | } 85 | break; 86 | } 87 | } 88 | 89 | void SIOChannel::processIncomingByte() { 90 | // read the next byte from the bus 91 | byte b = m_stream->read(); 92 | 93 | switch (m_cmdPinState) { 94 | // if we read a valid device byte and are in a "command wait" state, come out of it and 95 | // process the byte 96 | case STATE_INIT: 97 | case STATE_WAIT_CMD_START: 98 | case STATE_WAIT_CMD_END: 99 | if (digitalRead(m_cmdPin) == LOW && isValidDevice(b)) { 100 | m_cmdPinState = STATE_READ_CMD; 101 | resetCommandFrameBuffer(); 102 | } else { 103 | break; 104 | } 105 | // if we're reading a command frame... 106 | case STATE_READ_CMD: { 107 | // read the data into the command frame 108 | int idx = (int)m_cmdFramePtr - (int)&m_cmdFrame; 109 | // sometimes we see extra bytes between command frames on the bus while reading a command and things get lost -- 110 | // the isValidDevice() check prevents a command frame read from getting corrupted by them 111 | if (idx < COMMAND_FRAME_SIZE && (idx > 0 || (idx == 0 && isValidDevice(b)))) { 112 | *m_cmdFramePtr = b; 113 | m_cmdFramePtr++; 114 | return; 115 | } 116 | break; 117 | } 118 | // if we're reading a data frame... 119 | case STATE_READ_DATAFRAME: { 120 | // add byte to read sector buffer 121 | *m_putSectorBufferPtr = b; 122 | m_putSectorBufferPtr++; 123 | m_putBytesRemaining--; 124 | if (m_putBytesRemaining == 0) { 125 | doPutSector(); 126 | } 127 | break; 128 | } 129 | default: 130 | LOG_MSG(F("Ignoring byte ")); 131 | LOG_MSG(b, HEX); 132 | LOG_MSG(F(" in state ")); 133 | LOG_MSG_CR(m_cmdPinState); 134 | break; 135 | } 136 | } 137 | 138 | boolean SIOChannel::isChecksumValid() { 139 | byte chkSum = checksum((byte*)&m_cmdFrame, 4); 140 | if (chkSum != m_cmdFrame.checksum) { 141 | LOG_MSG(F("Checksum failed. Calculated: ")); 142 | LOG_MSG(chkSum); 143 | LOG_MSG(F("; received: ")); 144 | LOG_MSG_CR(m_cmdFrame.checksum); 145 | 146 | return false; 147 | } else { 148 | return true; 149 | } 150 | } 151 | 152 | boolean SIOChannel::isCommandForThisDevice() { 153 | // we only emulate drive 1 right now 154 | return (m_cmdFrame.deviceId == DEVICE_D1 || m_cmdFrame.deviceId == DEVICE_SDRIVE); 155 | } 156 | 157 | boolean SIOChannel::isValidCommand() { 158 | boolean result = (m_cmdFrame.command == CMD_READ || 159 | m_cmdFrame.command == CMD_WRITE || 160 | m_cmdFrame.command == CMD_STATUS || 161 | m_cmdFrame.command == CMD_PUT || 162 | m_cmdFrame.command == CMD_FORMAT || 163 | m_cmdFrame.command == CMD_FORMAT_MD); 164 | 165 | if (!result) { 166 | result = m_sdriveHandler.isValidCommand(m_cmdFrame.command); 167 | } 168 | 169 | return result; 170 | } 171 | 172 | boolean SIOChannel::isValidDevice(byte b) { 173 | boolean result = (b == DEVICE_D1 || 174 | b == DEVICE_D2 || 175 | b == DEVICE_D3 || 176 | b == DEVICE_D4 || 177 | b == DEVICE_D5 || 178 | b == DEVICE_D6 || 179 | b == DEVICE_D7 || 180 | b == DEVICE_D8 || 181 | b == DEVICE_R1); 182 | 183 | if (!result) { 184 | result = m_sdriveHandler.isValidDevice(b); 185 | } 186 | 187 | return result; 188 | } 189 | 190 | boolean SIOChannel::isValidAuxData() { 191 | return true; 192 | } 193 | 194 | byte SIOChannel::checksum(byte* chunk, int length) { 195 | int chkSum = 0; 196 | for(int i=0; i < length; i++) { 197 | chkSum = ((chkSum+chunk[i])>>8) + ((chkSum+chunk[i])&0xff); 198 | } 199 | return (byte)chkSum; 200 | } 201 | 202 | byte SIOChannel::processCommand() { 203 | int deviceId = 1; 204 | byte nextCmdPinState = STATE_WAIT_CMD_END; 205 | 206 | switch (m_cmdFrame.command) { 207 | case CMD_READ: 208 | cmdGetSector(deviceId); 209 | break; 210 | case CMD_WRITE: 211 | case CMD_PUT: 212 | cmdPutSector(deviceId); 213 | nextCmdPinState = STATE_READ_DATAFRAME; 214 | m_startTimeoutInterval = millis(); 215 | break; 216 | case CMD_STATUS: 217 | cmdGetStatus(deviceId); 218 | break; 219 | case CMD_FORMAT: 220 | cmdFormat(deviceId, DENSITY_SD); 221 | break; 222 | case CMD_FORMAT_MD: 223 | cmdFormat(deviceId, DENSITY_ED); 224 | break; 225 | default: 226 | m_sdriveHandler.processCommand(&m_cmdFrame, m_stream); 227 | break; 228 | } 229 | 230 | return nextCmdPinState; 231 | } 232 | 233 | void SIOChannel::cmdGetSector(int deviceId) { 234 | // send ACK 235 | delay(DELAY_T2); 236 | m_stream->write(ACK); 237 | 238 | // write data frame + checksum 239 | SectorDataInfo *p = m_driveAccess->readSectorFunc(deviceId, getCommandSector(), (byte*)&m_sectorBuffer); 240 | if (p != NULL && !p->error) { 241 | // send complete 242 | delay(DELAY_T5); 243 | m_stream->write(COMPLETE); 244 | } else { 245 | // send error 246 | delay(DELAY_T5); 247 | m_stream->write(ERR); 248 | } 249 | 250 | m_stream->flush(); 251 | 252 | delayMicroseconds(700); 253 | 254 | if (p != NULL) { 255 | byte *b = (byte*)&m_sectorBuffer; 256 | // write data 257 | for (int i=0; i < p->length; i++) { 258 | m_stream->write(*b); 259 | b++; 260 | } 261 | // write checksum 262 | m_stream->write(checksum((byte*)&m_sectorBuffer, p->length)); 263 | } else { 264 | // write empty data + checksum 265 | for (int i=0; i < SD_SECTOR_SIZE + 1; i++) { 266 | m_stream->write((byte)0x00); 267 | } 268 | } 269 | 270 | m_stream->flush(); 271 | } 272 | 273 | void SIOChannel::cmdPutSector(int deviceId) { 274 | // send ACK 275 | delay(DELAY_T2); 276 | m_stream->write(ACK); 277 | 278 | DriveStatus *status = m_driveAccess->deviceStatusFunc(deviceId); 279 | m_putBytesRemaining = status->sectorSize + 1; 280 | m_putSectorBufferPtr = m_sectorBuffer; 281 | } 282 | 283 | void SIOChannel::doPutSector() { 284 | int sectorSize = m_putSectorBufferPtr - m_sectorBuffer - 1; 285 | 286 | // calculate checksum 287 | byte chksum = checksum(m_sectorBuffer, sectorSize); 288 | 289 | // if checksum is good... 290 | if (m_sectorBuffer[sectorSize] == chksum) { 291 | // send ACK 292 | delay(DELAY_T4); 293 | m_stream->write(ACK); 294 | 295 | // write sector to disk image 296 | delay(DELAY_T5); 297 | if (m_driveAccess->writeSectorFunc(1, getCommandSector(), m_sectorBuffer, sectorSize)) { 298 | // send COMPLETE 299 | m_stream->write(COMPLETE); 300 | } else { 301 | LOG_MSG_CR(F("Write to device error")); 302 | m_stream->write(ERR); 303 | } 304 | // otherwise, NAK it 305 | } else { 306 | delay(DELAY_T4); 307 | m_stream->write(NAK); 308 | 309 | LOG_MSG(F("Data frame checksum error: ")); 310 | LOG_MSG(chksum, HEX); 311 | LOG_MSG(F(" vs. ")); 312 | LOG_MSG_CR(m_sectorBuffer[sectorSize], HEX); 313 | } 314 | 315 | // change state 316 | m_cmdPinState = STATE_WAIT_CMD_START; 317 | } 318 | 319 | void SIOChannel::cmdGetStatus(int deviceId) { 320 | // send ACK 321 | delay(DELAY_T2); 322 | m_stream->write(ACK); 323 | 324 | // send complete 325 | delay(DELAY_T5); 326 | m_stream->write(COMPLETE); 327 | 328 | // get device status 329 | DriveStatus* driveStatus = m_driveAccess->deviceStatusFunc(deviceId); 330 | 331 | // calculate checksum 332 | int frameLength = sizeof(driveStatus->statusFrame); 333 | byte chksum = checksum((byte*)&driveStatus->statusFrame, frameLength); 334 | 335 | // send status to bus 336 | byte* b = (byte*)&driveStatus->statusFrame; 337 | for (int i=0; i < frameLength; i++) { 338 | m_stream->write(*b); 339 | b++; 340 | } 341 | m_stream->write(chksum); 342 | } 343 | 344 | void SIOChannel::cmdFormat(int deviceId, int density) { 345 | // send ACK 346 | delay(DELAY_T2); 347 | m_stream->write(ACK); 348 | 349 | // perform image format 350 | if (m_driveAccess->formatFunc(deviceId, density)) { 351 | // send COMPLETE 352 | delay(DELAY_T5); 353 | m_stream->write(COMPLETE); 354 | 355 | LOG_MSG(F("Sending data frame of length ")); 356 | LOG_MSG_CR(SD_SECTOR_SIZE); 357 | 358 | m_stream->write(0xFF); 359 | m_stream->write(0xFF); 360 | for (int i=0; i < SD_SECTOR_SIZE - 3; i++) { 361 | m_stream->write((byte)0x00); 362 | } 363 | m_stream->write(0xFF); 364 | m_stream->write(0xFF); 365 | } else { 366 | delay(DELAY_T5); 367 | m_stream->write(ERR); 368 | } 369 | } 370 | 371 | void SIOChannel::dumpCommandFrame() { 372 | // we only compile this on DEBUG to save allocating string constants 373 | #ifdef DEBUG 374 | LOG_MSG(m_cmdFrame.deviceId, HEX); 375 | LOG_MSG(F(" ")); 376 | LOG_MSG(m_cmdFrame.command, HEX); 377 | LOG_MSG(F(" ")); 378 | LOG_MSG(m_cmdFrame.aux1, HEX); 379 | LOG_MSG(F(" ")); 380 | LOG_MSG(m_cmdFrame.aux2, HEX); 381 | LOG_MSG(F(" ")); 382 | LOG_MSG(m_cmdFrame.checksum, HEX); 383 | LOG_MSG(F(" : ")); 384 | 385 | switch (m_cmdFrame.command) { 386 | case CMD_STATUS: 387 | LOG_MSG(F("STATUS")); 388 | break; 389 | case CMD_POLL: 390 | LOG_MSG(F("POLL")); 391 | break; 392 | case CMD_READ: 393 | LOG_MSG(F("READ ")); 394 | LOG_MSG(getCommandSector()); 395 | break; 396 | case CMD_WRITE: 397 | LOG_MSG(F("WRITE ")); 398 | LOG_MSG(getCommandSector()); 399 | break; 400 | case CMD_PUT: 401 | LOG_MSG(F("PUT ")); 402 | LOG_MSG(getCommandSector()); 403 | break; 404 | case CMD_FORMAT: 405 | LOG_MSG(F("FORMAT")); 406 | break; 407 | case CMD_FORMAT_MD: 408 | LOG_MSG(F("FORMAT MD")); 409 | break; 410 | default: 411 | if (!m_sdriveHandler.printCmdName(m_cmdFrame.command)) { 412 | LOG_MSG(F("??")); 413 | } 414 | } 415 | 416 | LOG_MSG_CR(); 417 | #endif 418 | } 419 | 420 | unsigned long SIOChannel::getCommandSector() { 421 | return (unsigned long)(m_cmdFrame.aux2 << 8) + (m_cmdFrame.aux1 & 0xff); 422 | } 423 | 424 | void SIOChannel::resetCommandFrameBuffer() { 425 | // reset last command frame info 426 | memset(&m_cmdFrame, 0, sizeof(m_cmdFrame)); 427 | m_cmdFramePtr = (byte*)&m_cmdFrame; 428 | } 429 | -------------------------------------------------------------------------------- /sio_channel.h: -------------------------------------------------------------------------------- 1 | #ifndef SIO_CHANNEL_h 2 | #define SIO_CHANNEL_h 3 | 4 | #include 5 | #include "atari.h" 6 | #include "drive_access.h" 7 | #include "drive_control.h" 8 | #include "sdrive.h" 9 | 10 | const byte COMMAND_FRAME_SIZE = 5; 11 | 12 | const byte STATE_INIT = 1; 13 | const byte STATE_WAIT_CMD_START = 2; 14 | const byte STATE_READ_CMD = 3; 15 | const byte STATE_READ_DATAFRAME = 4; 16 | const byte STATE_WAIT_CMD_END = 5; 17 | 18 | const byte CMD_FORMAT = 0x21; 19 | const byte CMD_FORMAT_MD = 0x22; 20 | const byte CMD_POLL = 0x3F; 21 | const byte CMD_PUT = 0x50; 22 | const byte CMD_READ = 0x52; 23 | const byte CMD_STATUS = 0x53; 24 | const byte CMD_WRITE = 0x57; 25 | 26 | const unsigned long READ_CMD_TIMEOUT = 500; 27 | const unsigned long READ_FRAME_TIMEOUT = 2000; 28 | 29 | const byte DEVICE_D1 = 0x31; 30 | const byte DEVICE_D2 = 0x32; 31 | const byte DEVICE_D3 = 0x33; 32 | const byte DEVICE_D4 = 0x34; 33 | const byte DEVICE_D5 = 0x35; 34 | const byte DEVICE_D6 = 0x36; 35 | const byte DEVICE_D7 = 0x37; 36 | const byte DEVICE_D8 = 0x38; 37 | const byte DEVICE_R1 = 0x50; 38 | 39 | class SIOChannel { 40 | public: 41 | SIOChannel(int cmdPin, Stream* stream, DriveAccess *driveAccess, DriveControl *driveControl); 42 | void runCycle(); 43 | void processIncomingByte(); 44 | void sendDeviceStatus(DriveStatus *deviceStatus); 45 | byte* readSectorDataFrame(); 46 | private: 47 | boolean isChecksumValid(); 48 | boolean isCommandForThisDevice(); 49 | boolean isValidDevice(byte b); 50 | boolean isValidCommand(); 51 | boolean isValidAuxData(); 52 | byte checksum(byte* chunk, int size); 53 | byte processCommand(); 54 | void dumpCommandFrame(); 55 | void cmdGetSector(int deviceId); 56 | void cmdPutSector(int deviceId); 57 | void cmdPutSectorWithVerify(int deviceId); 58 | void cmdGetStatus(int deviceId); 59 | void cmdFormat(int deviceId, int density); 60 | unsigned long getCommandSector(); 61 | void doPutSector(); 62 | void resetCommandFrameBuffer(); 63 | 64 | int m_cmdPin; 65 | Stream* m_stream; 66 | byte m_cmdPinState; 67 | CommandFrame m_cmdFrame; 68 | byte* m_cmdFramePtr; 69 | byte m_sectorBuffer[MAX_SECTOR_SIZE + 1]; 70 | byte* m_putSectorBufferPtr; 71 | int m_putBytesRemaining; 72 | DriveAccess* m_driveAccess; 73 | DriveControl* m_driveControl; 74 | SDriveHandler m_sdriveHandler; 75 | unsigned long m_startTimeoutInterval; 76 | }; 77 | 78 | #endif 79 | --------------------------------------------------------------------------------