DEVICE SX28, OSCHS3, TURBO, STACKX, OPTIONX IRC_CAL IRC_SLOW FREQ 50000000 'afp version 0.3 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> Checked receive uart. '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 --- last real changes dec 2009 '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 'idle high 5v state stores 0's --- from the first falling edge until 13824 bytes later, 'the drive must send data ' ------------------------------------------------------------------------- ' 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) USBDTR var RC.7 'input from USB CHIP RXDATA var RC.6 'input from FTDI USB used to be rc7 TXDATA var RC.5 'transmit to the USB used to be rc6 '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 RTCCDIFF var byte realbits var byte 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 watch RTCCDIFF,8,UDEC WATCH LOBYTE,8,UDEC WATCH REALBITS,8,UDEC watch inloop,8,udec ' ------------------------------------------------------------------------- ' INTERRUPT NOCODE ' ------------------------------------------------------------------------- 'ASM 'NOP '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 'STEPPIN DIR SEL1 MTRXD INDEX WPROTECT SIDE READDATA 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 'RXDATA TXDATA debugoutput NA DENSEL TRACK0 WGATE WDATA 'changed 10-29-2007 tris c used to be 1001 1100 'usbdtr rxdata txdata na densel track0 wgate wdata 'this was 11001100 prior to me messing with densel because high density floppies arent working TRIS_C=%11001100 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 'NEED TO SET PRESCALER TO 1:2 OPTION = $C0 CHECKSUM = 0 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 (FRAM->PC) if gotdata = 70 then dofcmd 'F for step Forward if gotdata = 76 then dolcmd 'L for side Lower if gotdata = 77 then domcmd 'M for memtest if gotdata = 82 then dorcmd 'R for read if gotdata = 83 then doscmd 'S for store track if gotdata = 84 then dotcmd 'T for send test sequence if gotdata = 85 then doucmd 'U for side Upper goto repeat 'start command routines DOTCMD: PAUSE 100 for outloop = 0 to 255 for inloop = 0 to 255 sendusb inloop next inloop next outloop goto repeat 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 11 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 11 '14 sectors to 11 nov-3-2007 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 next bytecounter next outloop 'send end frame FF FF SENDUSB 255 'pauseus 500 'commented out 10-15-2007 why do w eneed this? SENDUSB 255 'pauseus 500 'commented out 10-15-2007 shouldnt need this 'send checksum SENDUSB checksum SENDUSB 254 'added 11-1-2007 as a special event character to tell usb2ser to xmit last packet pauseus 50 'was 500, changed to 50. slight pause at end of transmision before repeat 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 start ' 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. ASM waitforfirstedge: waitfirsthigh: JNB RB.0, @waitfirsthigh waitfirstlow: JB RB.0, @waitfirstlow CLR RTCC SETB RC.4 waitforanotheredge: waitsecondhigh: JNB RB.0, @waitsecondhigh waitsecondlow: JB RB.0, @waitsecondlow MOV RTCCDIFF, RTCC 'preserve rtcc CLR RTCC 'CLRB RC.4 CJB RTCCDIFF, #81, @handletoosmall CJA RTCCDIFF, #206, @handletoobig CJA RTCCDIFF, #106, @biggerthan106 'so we know that rtccdiff is more than 81 and less than 106. 'so this must be a 4us difference between pulses '81 = 3.24us, 106=4.24us 'mfm 10 'this stores a 1 CLRB SCK NOP SETB SI NOP SETB SCK NOP 'this stores a 0 CLRB SCK NOP CLRB SI NOP SETB SCK MOV realbits, #2 JMP @countbitsandreturn biggerthan106: CJA RTCCDIFF, #156, @biggerthan156 CJB RTCCDIFF, #131, @undefinedarea 'so we know that rtccdiff is bigger than 106 and 131, but less than 156 'so this must be a 6us diff between pulses '131=5.24us, 156=6.24us 'mfm 100 'this stores a 1 CLRB SCK NOP SETB SI NOP SETB SCK NOP 'this stores a 0 CLRB SCK NOP CLRB SI NOP SETB SCK 'this stores another 0 CLRB SCK NOP SETB SCK MOV realbits, #3 JMP @countbitsandreturn biggerthan156: CJB RTCCDIFF, #181, @undefinedarea 'so we know that rtccdiff is bigger than 181, but less than 206 '181 = 7.24us, 206 = 8.24us ' mfm 1000 'this stores a 1 CLRB SCK NOP SETB SI NOP SETB SCK NOP 'this stores a 0 CLRB SCK NOP CLRB SI NOP SETB SCK 'this stores another 0 CLRB SCK NOP SETB SCK 'this stores another 0 CLRB SCK NOP SETB SCK MOV realbits, #4 JMP @countbitsandreturn handletoosmall: 'time is less than 3.24us, aka rtccdiff is 81 or less 'this can happen due to pulses in the gap 'our token for too small diff is 110 'transmit a 1 to fram CLRB SCK NOP SETB SI NOP SETB SCK NOP 'another 1 CLRB SCK NOP SETB SCK NOP 'transmit a 0 CLRB SCK NOP CLRB SI NOP SETB SCK MOV realbits, #0 JMP @countbitsandreturn handletoobig: 'time is more than rtcc 206, so 8.24us 'should be a rare occurance 'our token for too big diff is 111 'transmit a 1 to fram CLRB SCK NOP SETB SI NOP SETB SCK NOP 'another 1 CLRB SCK NOP SETB SCK NOP 'and one more CLRB SCK NOP SETB SCK MOV realbits, #0 JMP @countbitsandreturn undefinedarea: 'this is the grayarea of times between pulses not matching up 'this could happen due to funky gap pulses 'but we don't have enough tokens to tell the pc. for now just 'reset and try again MOV realbits,#0 JMP @countbitsandreturn countbitsandreturn: ADD lobyte, realbits JNC @didntoverflow 'OH SO WE DID OVERFLOW IJNZ hibyte, @didntoverflow INC superhibyte didntoverflow: CJNE superhibyte, #1, @waitforanotheredge CJNE hibyte, #183, @waitforanotheredge CJB lobyte, #56, @waitforanotheredge endasm goto DODCMD 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 doscmd: 'S command, store track received from PC 'ready FRAM for writing, start by terminating current command CS = 1 pauseus 1 '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 checksum = 0 for bytecounter = 1 to 11968 RECVUSB checksum = checksum + gotdata SENDFRAM gotdata next bytecounter 'receive checksum as last byte transferred RECVUSB if gotdata = checksum then storeok 'otherwise we have a checksum mismatch, notify PC as such SENDUSB 67 'C SENDUSB 70 'F SENDUSB 65 'A SENDUBS 73 'I SENDUSB 76 'L goto repeat storeok: 'tell pc we got the track ok SENDUSB 67 'C SENDUSB 79 'O SENDUSB 75 'K goto repeat domcmd: 'do a memory test '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 pause 500 SENDUSB 77 'M SENDUSB 69 'E SENDUSB 77 'M SENDUSB 45 '- for outloop = 0 to 127 for inloop = 0 to 255 checksum = outloop+inloop checksum = checksum & 170 sendfram checksum next inloop next outloop '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 for outloop = 0 to 127 for inloop = 0 to 255 RECVFRAM checksum = outloop+inloop checksum = checksum & 170 if gotdata <> checksum then badmemory next inloop next outloop SENDUSB 79 'O SENDUSB 75 'K CS=1 'END READING goto repeat badmemory: SENDUSB 70 'F SENDUSB 65 'A SENDUSB 73 'I SENDUSB 76 'L 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 'current run time for this routine is approximately 2.20us per byte ASM MOV nsb, #8 CLR gotdata loopzz: SETB SCK 'outputs are latched on falling edge, this just raises the clock 'note that the other instructions that follow serve to allow 'at least 28ns for a minimum clock high time. 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 CLRB SCK 'drop clock (clock low min is 28ns, data not valid until Todv, or 24ns) 'data comes valid 24ns after this clock drop. JNZ loopzz 'delay here is long enough for Todv and Tcl(clock low time) 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 'reviewed this code march-1-2008. everything looks good 'receives exactly 2,000,000 bps. '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 nextrecvbit: JMP $+1 '300ns from here to MOV 'approx end of start bit, wait 12 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? JMP $+1 'eat stop bit w/ 500ns delay JMP $+1 'should end up ~ middle of stop bit JMP $+1 JMP $+1 JMP $+1 JMP $+1 JMP $+1 JMP $+1 NOP ENDASM 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 waitfordtr: JNB USBDTR, @waitfordtr 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 'removed june 28 2009 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 nsb>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 'chg juen282009 NOP 'chg june 282009 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