DEVICE SX28, OSCHS3, TURBO, STACKX, OPTIONX IRC_CAL IRC_SLOW FREQ 50000000 'afp version 0.2 WORK IN PROGRESS 'Keith Monahan 'keith@techtravels.org 'TO DO LIST: '1> Deal with motor being on all the time. do we want to have a turn-off-motor command? '2> Optimize forward and backward code and figure out why this code actally works AS IS '3> double check receive uart code & comments '4> Deal with a non-amiga/really bad disk locking up findsync() '5> All code sections, including subroutines, need pre-state information, variables used ' and post-state to identify what needs to be in place for them to operate correectly ' This will make them more modular and will allow MAC's better '6> More generally, handle error / no-data conditions more elegantly - display error and exit 'August 21, 2007 'The incoming data is at 500kbps+, as the floppy drive data rate varies. 'bit cells are approximately 2us each, and obviously get shorter as data rate increases 'worst case variation here seems to be roughly 10% (shortest is something like 7.5 on an 8us group) 'seems to NEVER exceed 4us, 6us, or 8us, floppy runs faster, but never slower 'possible receive data patterns are 10, 100, and 1000. 'idle condition is a ttl high 5v 'transition to 0v is a "1" data bit 'no transition in a bitcell following a 1 is considered a "0" data bit 'data is raw amiga MFM(does not follow standard MFM rules, beware) 'transition always happens first, followed by up to (3) zeros. 'never two transitions back to back EXCEPT possibly in space between start of track 'and end of track (aka track gap) 'Logic of this program is as follows 'setup ports of the SX, initialize variables and pins as necessary 'prepare memory for writes 'enable interrupts where all the action happens 'if you send more than 32k total bytes, the FRAM rolls and overwrites old data. 'main sits and waits for a command, "R" for read, "D" for dump, and so on. 'ISR logic 'Once started by the "R" command, the main program waits for a falling edge to start interrupts 'The ISR can fire due to a> ~2us timeout or b> falling edge 'It stores a "1" on falling edge, and a "0" on a subsequent timeout 'idle high 5v state stores 0's --- from the first falling edge until 13824 bytes later, 'the drive must send data 'note isr doesn't use SENDFRAM or RECVFRAM routines, but instead stores one bit at a time ' ------------------------------------------------------------------------- ' Variables ' ------------------------------------------------------------------------- 'PORTC names 'These names are all from the fram perspective CS var RA.3 'chip select (output towards FRAM) SO var RA.2 'serial output (input from FRAM) SI var RA.0 'serial input (output towards FRAM) SCK var RA.1 'serial clock (output towards FRAM) RXDATA var RC.7 'input from FTDI USB TXDATA var RC.6 'transmit to the USB 'PORTB names 'ignore these pin numbers for now. DATAIN var RB.0 'READDATA pin 30 'debugpin var RB.1 'debug lead DENSEL var RC.3 'Density select pin 2 MTRXD var RB.4 'motor control SIDE var RB.1 'side select PIN 32 STEPPIN var RB.7 'step the head PIN 20 DIR var RB.6 'head direction PIN 18 SEL1 var RB.5 'select drive 1 datatosend var byte gotdata var byte nsb var byte 'number of sent bits. decremented from 8 to indicate a byte received inloop var byte 'inloop and outloop are used to clear the entire fram prior to start outloop var byte lobyte var byte 'these three for storing number of bits received hibyte var byte 'I have to represent over 100,000 bits, so use 2^24 because superhibyte var byte '2^16 is only 65535 checksum var byte 'used for calculating checksum during a dump CYLNO var byte 'used to keep track of cylno, not much use right now myres var byte 'gets contents of w temporarily so we can extract edge bit BYTEONE var byte 'for findsync() BYTETWO var byte bytecounter var word 'used during dump WATCH gotdata,8,UDEC WATCH nsb,8,UDEC WATCH datatosend,8,UDEC WATCH lobyte,8,udec watch hibyte,8,udec WATCH superhibyte,8,udec WATCH RC.1,1,UBIN WATCH outloop,8,udec watch CHECKSUM,8,UDEC watch CYLNO,8,UDEC WATCH BYTEONE,8,uhex WATCH BYTETWO,8,uhex ' ------------------------------------------------------------------------- INTERRUPT NOCODE ' ------------------------------------------------------------------------- ASM 'SETB rb.1 'debug pin(inside interrupt) CLR RTCC CLR FSR ISR_Start: MODE $09 MOV !RB,#%00000000 mov myres, w 'W NOW CONTAINS A %00000001 IF FIRED BY EDGE, 0 IF RTCC CLRB SCK 'make sure clock is low to start with NOP MOVB SI, myres.0 'send bit to fram NOP 'satisfy 5ns data setup time SETB SCK 'raise clock notifying chip to read the data 'COUNT THE Bit stored IJNZ lobyte, goback IJNZ hibyte, goback inc superhibyte goback: 'CLRB rb.1 MOV W, #-100 retiw ENDASM SENDFRAM SUB 1 'sub definition for full byte writes RECVFRAM SUB 'sub definition for a full byte read SENDUSB SUB 1 'sub def for usb xmit 2-2-06 RECVUSB SUB 'sub def for receiving from usb 2-2-06 findsync SUB 'void(void) PROGRAM start_point ' Program Code ' ------------------------------------------------------------------------- start_point: 'setup port a, this is memory fram port 'NA NA NA NA CS SO SI SCK (names from memory perspective) TRIS_A=%11110100 'setup port b TRIS_B=%00001101 'NEW CIRCUITBOARD ST_B = %11111110 'schmitt trigger for our drive input WKPND_B = 0 'clear pending interrupts WKED_B = %11111111 'set falling edge trigger WKEN_B = %11111111 'disable drive input interrupts 'setup port c 'changed this from 1011 0100 to 1011 1100. rc.3 is dens select, and sx is heating up, i'm gonna try not using it 3-12-07 TRIS_C=%10111100 PLP_C = %00000100 RC.0 = 1 RC.1 = 1 SEL1 = 1 STEPPIN = 1 DIR = 1 SIDE = 1 MTRXD = 1 CS = 1 SCK = 0 TXDATA = 1 'NORMAL STATE FOR serial IS HIGH CYLNO = 0 CHECKSUM = 0 gotdata = 255 'this entire code segment until repeat: is designed to do a couple things '1. prepare the FRAM for writing by enabling writes '2. clear the entire contents of the FRAM since this is non-volatile 'this transmits the FRAM opcode dec 6, Set Write Enable Latch, aka WREN CS=0 SENDFRAM 6 CS = 1 pauseus 1 CS = 0 'This transmits FRAM opcode dec 2, Write Memory data, WRITE SENDFRAM 2 'send the two byte address, in this case, start at 0 SENDFRAM 0 SENDFRAM 0 for outloop = 0 to 127 for inloop = 0 to 255 SENDFRAM 0 'set fram to 0 next inloop next outloop ' turn off interrupts OPTION = $C0 'LET'S NOT TURN THIS ON UNTIL WE NEED IT TO RECEIVE DATA repeat: 'WAIT FOR A COMMAND RECVUSB SENDUSB GOTDATA if gotdata = 65 then doacmd 'A for Are you there? if gotdata = 66 then dobcmd 'B for step Backward if gotdata = 67 then doccmd 'C for cyl number if gotdata = 68 then dodcmd 'D for dump if gotdata = 70 then dofcmd 'F for step Forward if gotdata = 76 then dolcmd 'L for side Lower if gotdata = 82 then dorcmd 'R for read if gotdata = 85 then doucmd 'U for side Upper goto repeat 'start command routines DOACMD: 'A command = Are you there? Answer with Y for YES PAUSE 100 SENDUSB 89 goto repeat DODCMD: 'D command = Dump FRAM to usb 'ie dump 'send buffer to PC via USB 'Note this routine sends exactly 14 sectors worth of data 'By using findsync() it searches for the start of a new sector 'and then transmits it both BIT aligned and BYTE aligned to the 'SYNC word. This allows for super easy decoding on the PC side 'PC sees a normal full 1088 byte raw undecoded sector 'note the data sent to the PC is actually sent semi-framed 'so finding start of frame is easy 'the sx sends <14*1088=15232bytes> 'checksum is simply an 8-bit checksum. checksum starts at 0 'checksum = checksum + newbyte; if checksum > 255 ch=ch-256; 'ie mod 256. 'first ready FRAM for reading CS = 1 'end open command pauseus 1 CS = 0 'here comes a new opcode SENDFRAM 3 'xmit READ opcode SENDFRAM 0 'xmit zero hibyte SENDFRAM 0 'xmit zero lobyte checksum = 0 'set checksum to 0 'send start frame FF FF SENDUSB 255 SENDUSB 255 for outloop = 1 to 14 '14 sectors gosub findsync SENDUSB $AA 'WE ATE THIS PRIOR TO FINDING SYNC, SO PUT IT BACK SENDUSB $AA SENDUSB $AA SENDUSB $AA SENDUSB $44 SENDUSB $89 checksum = checksum + 117 '117 = checksum of what we added by aaaaaaaa4489 'transmit this sector properly aligned for bytecounter = 0 to 1081 RECVFRAM SENDUSB gotdata checksum = checksum + gotdata 'PAUSE 1 next bytecounter next outloop 'send end frame FF FF SENDUSB 255 pauseus 500 SENDUSB 255 pauseus 500 'send checksum SENDUSB checksum pauseus 500 goto repeat DORCMD: 'R command = read data from drive 'this prepares the sx to receive data from the drive CS = 1 pauseus 1 CS = 0 SENDFRAM 6 CS = 1 pauseus 1 CS = 0 'This transmits FRAM opcode dec 2, Write Memory data, WRITE SENDFRAM 2 'send the two byte address, in this case, start at 0 SENDFRAM 0 SENDFRAM 0 lobyte = 0 hibyte = 0 superhibyte = 0 CHECKSUM = 0 NSB = 8 '3-20-06 tell drive we want the data MTRXD = 0 'turn on motor pauseus 1.65 SEL1 = 0 'select the drive ' find falling edge of real data moving before we enable interrupts ' this is a better indicator and doesnt relay on any data ready pin 'note that once motor is turned on, it's kept on forever 'maybe add a command to stop the motor? hrrmmm. findhigh: if RB.0 = 0 then findhigh findlow: if rb.0 = 1 then findlow WKEN_B = %11111110 'enable drive input interrupts OPTION = $88 waitforisr: '2 @ 65536 = 131072 bits stored or 16384 bytes if superhibyte <> 2 then waitforisr if hibyte <> 0 then waitforisr 'to optimize speed, don't turn off the motor, keep it running OPTION = $C0 'turn off interrupts WKEN_B = %11111111 'disable drive input interrupts goto repeat dofcmd: 'F command = step forward MTRXD = 0 'turn on motor pauseus 1.65 pauseus 2 SEL1 = 0 'select the drive pauseus 2 DIR = 0 pauseus 2 STEPPIN = 0 pauseus 2 'create a low 2us pulse STEPPIN = 1 PAUSE 5 MTRXD = 1 'turn motor off pauseus 2 SEL1 = 1 'deselect drive CYLNO = CYLNO + 1 goto repeat dobcmd: 'B command = step backward MTRXD = 0 'turn on motor pauseus 1.65 'pauseus 2 SEL1 = 0 'select the drive pauseus 2 DIR = 1 pauseus 2 STEPPIN = 0 pauseus 2 'create a low 2us pulse STEPPIN = 1 PAUSE 5 'MTRXD = 1 'turn motor off 'pauseus 2 SEL1 = 1 'deselect drive IF rc.2 = 0 then cylz 'check track 0 for pulse pause 3 CYLNO = CYLNO - 1 goto repeat cylz: SENDUSB 84 CYLNO = 0 goto repeat dolcmd: 'L command = do lower side SIDE = 1 goto repeat doucmd: 'U command = do upper side SIDE = 0 goto repeat doccmd: 'C command, send current cylno sendusb cylno goto repeat 'end command routines SENDFRAM: 'this routine takes the passed byte parameter, and transmits it to the fram datatosend = __PARAM1 'save incoming sub argument ASM MOV nsb, #8 loopz: CLRB SCK 'make sure clock is low to start with CLRB C RL datatosend 'left shift data onto the port, because chip's MSB MOVB SI, C 'put the carry onto the port DEC nsb 'decrement counter SETB SCK 'raise clock notifying chip to read the data JNZ loopz 'if nsb > 0 then loopz CLRB SCK 'drop final bit clock ENDASM RETURN RECVFRAM: 'this receives exactly one byte from fram, and loads it into gotdata ASM MOV nsb, #8 CLR gotdata loopzz: MOVB C, SO 'read the bit into carry RL gotdata 'bring the bit via carry into gotdata, leftshifting for MSB DEC nsb 'decrement byte counter SETB SCK 'We are ready to get a bit, so give it us! NOP 'high for 40ns NOP CLRB SCK 'drop clock JNZ loopzz 'delay here is long enough for Todv ENDASM RETURN RECVUSB: 'custom UART code. This has NOT been extensively tested 'but seems to work fine for single byte commands. if we implement 'writing THIS MUST BE DOUBLECHECKED FOR RELIABILITY AT HIGH 'SPEEDS 'receives exactly 2,000,000 bps. 'these comments also need checked 'receives one byte from the PC and puts it into GOTDATA ASM MOV NSB, #8 CLR GOTDATA waitforstartbit: jb RXDATA, waitforstartbit JMP $+1 '360ns this group JMP $+1 JMP $+1 JMP $+1 JMP $+1 JMP $+1 NOP nextrecvbit: JMP $+1 '300ns from here to MOV 'end of start bit, wait 13 cycles, 1/2 bit, 240ns from here JMP $+1 JMP $+1 JMP $+1 JMP $+1 'we should be close to middle here MOVB C, RXDATA 'read pin and put it in C RR GOTDATA 'right rotate C into the byte DEC NSB 'decrement bit counter JNZ nextrecvbit 'more bits to receive? ENDASM PAUSEUS 1 'EAT STOP BIT w/ delay RETURN SENDUSB: 'my custom send UART code. Has been briefly peer reviewed 'for cycle count etc. Logic analyzer has this thing nailing 'the timing. This reliably works at 2mbps with ZERO errors. 'I passed something like 13 million bytes through it without 'a single error. I think this code is rock solid. 'takes passed single byte parameter and sends it to the PC 'I still hate the nops and JMP $+1, but what can you do datatosend = __PARAM1 'save incoming argument ASM MOV NSB, #8 CLRB TXDATA 'transmit start bit JMP $+1 '100ns delay NOP NOP nextbit: JMP $+1 '280ns delay JMP $+1 JMP $+1 JMP $+1 NOP NOP CLC RR datatosend 'right rotate and put lsb into C MOVB TXDATA, C 'put C on the port DEC NSB 'decrement bit counter JNZ nextbit 'if jnz>0 then nextbit JMP $+1 '380ns delay JMP $+1 JMP $+1 JMP $+1 JMP $+1 JMP $+1 NOP SETB TXDATA 'transmit stop bit JMP $+1 '500ns delay JMP $+1 JMP $+1 JMP $+1 JMP $+1 JMP $+1 JMP $+1 JMP $+1 NOP endasm RETURN 'this routine looks for a $4489 SYNC string. This combination 'does NOT occur in regular data and is safe to use 'doesn't look for the whole SYNCword but who cares 'this starts whereever the fram is happened to be positioned 'and returns the FRAM positioned with the first BIT on the SO 'pin ready to be read by readfram. 'note this could be a lock-up situation if it never finds at least 'one $4489. this is unlikely to happen, because you'd hope at least 'one sector SYNCword is intact. But this might happen if you insert 'a non-amiga disk. also, the fram wraps at 32k, so as long as there 'is one, this subroutine will return findsync: ASM MOVB C, SO RL BYTEONE RL BYTETWO CJNE BYTETWO,#$44,nextfindsyncbit CJNE BYTEONE,#$89,nextfindsyncbit 'NEED TO READ ONE MORE BIT SETB SCK NOP NOP CLRB SCK ENDASM RETURN nextfindsyncbit: ASM SETB SCK NOP NOP CLRB SCK JMP findsync ENDASM