DECLARE SUB senddata (p&, size%, index%) REM DOWNLOAD MINDSTORMS FIRMWARE WITH QBASIC REM ---------------------------------------- REM See start of code for revision number REM All trademarks acknowledged REM REM Without the following invaluable advice from Kekoa Proudfoot I would not have REM been able to write this program. I am most grateful for his help. I obtained more REM help from his web site http://graphics.stanford.edu/~kekoa/rcx/ REM Peter Hesketh 1999/01/01 REM http://www.phesk.demon.co.uk/lego/ REM --------------------------------------------------------------------------- REM Lego Mindstorms Firmware Download Protocol REM REM Step 1: Read in the s-record file REM REM See http://graphics.stanford.edu/~kekoa/rcx/s-record.txt for a REM description of the format that somebody else wrote up. REM REM After this, you should have an image with a starting address and an REM ending address. REM REM If you like, search from the ending address backward until you find the REM first non-zero byte, and make that the new ending address. This is not REM entirely legal, because maybe that data was supposed to be set to zero, REM but it speeds downloading, and it works with the supplied Firm0309.lgo. REM REM Step 2: Find the checksum of the image REM REM Add up the bytes between the starting and ending address. You checksum REM should be an unsigned short. A short has 16 bits. REM REM Step 3: Send a delete firmware message REM REM In hex, the data in the message should be: REM REM 65 01 03 05 07 0b REM REM You need to supply the header, complement, and checksum bytes. REM REM Step 4: Send a start firmware download message REM REM In hex, the data in the message should be: REM REM 75 00 80 dd cc 00 REM REM cc is the most significant byte of the image checksum REM dd is the least significant byte of the image checksum REM REM Step 5: Send the image data using transfer data messages REM REM Break up the image into 200 byte chunks. REM REM Loop over the chunks. Compute the checksum for the current chunk by REM adding up the bytes of the current chunk. This checksum should be a REM byte, not a short. REM REM For each chunk, send a message with the following data (in hex): REM REM xx bb aa dd cc databytes ee REM REM xx is the message opcode REM aa is the most significant byte of the index of this chunk REM bb is the least significant byte of the index of this chunk REM cc is the most significant byte of the size of this chunk REM dd is the least significant byte of the size of this chunk REM databytes are the data bytes in this chunk REM ee is the checksum byte of this chunk REM REM The opcode should alternate between 45 and 4d with every other chunk REM The index should start at 1 and increase by 1 with each successive chunk REM The last chunk should have an index of 0 REM REM Step 6: Send an unlock firmware message REM REM In hex, the data in the message should be: REM REM a5 4c 45 47 4f ae REM REM That's it. REM REM Sending messages is non-trivial. First you have to configure the serial REM port. Then, when you send a message by writing data to the serial port, REM you have to wait for a proper reply to make sure the rcx received it. If REM you do not receive a proper reply, send the exact same message again until REM you do hear a proper reply. REM REM For more information on messages and the serial protocol, see: REM REM http://graphics.stanford.edu/~kekoa/rcx/#Serial REM http://graphics.stanford.edu/~kekoa/rcx/opcodes.html REM REM With all of this in mind, the following download trace should make sense: REM REM http://www.crynwr.com/lego-robotics/trace1.txt REM REM A few things to note about the trace: REM REM - opcodes alternate between having the 0x08 bit set and clear REM - the apparent RCX echo is really the caused by the IR tower REM - the trace includes some extra commands (10,18) that aren't strictly needed REM REM ------------------------------------------------------- DECLARE SUB centre (t$) DECLARE FUNCTION reply$ (message$) DECLARE FUNCTION badmessage% (message$) DECLARE FUNCTION tidy$ (message$) DECLARE SUB send (message$) DECLARE FUNCTION string2hex$ (s$) DECLARE SUB try (outmess$, inmess$) DIM SHARED seqno AS INTEGER, true AS INTEGER, false AS INTEGER DIM SHARED maxtries DIM SHARED cr$, lf$, crlf$, FF00$ DIM SHARED messageheader$ REM Revision History REM rev$ = "1.00 1999/01/02": REM Original issue rev$ = "1.01 1999/01/03": REM Download actually works! rev$ = "1.02 1999/04/14": REM "Unlock firmware" added REM set up a few useful constants true = -1 false = 0 cr$ = CHR$(13) lf$ = CHR$(10) crlf$ = cr$ + lf$ FF00$ = CHR$(&HFF) + CHR$(0) maxtries = 5 CLS CLOSE LOCATE 10, 1 centre "F I R M W A R E D O W N L O A D" PRINT centre "Revision " + rev$ PRINT centre "Acknowledgements to Kekoa Proudfoot for his invaluable help" centre "in explaining the download protocol." PRINT PRINT REM Alternate commands have hex 08 set. REM Initialise this sequence seqno = 1 REM Choose your serial port (no colon) port$ = "COM1" REM Or add it to the command line (no colon) REM This gives an error in interpreted Qbasic ON ERROR GOTO handler IF COMMAND$ > "" THEN port$ = LEFT$(LTRIM$(UCASE$(COMMAND$)), 4) filename$ = LTRIM$(RTRIM$(MID$(LTRIM$(UCASE$(COMMAND$)), 5))) REM All that just to get the arg2 filename! END IF skip: ON ERROR GOTO 0 REM Check the port name p = INSTR(" COM1 COM2 COM3 COM4 ", UCASE$(" " + port$ + " ")) IF p = 0 THEN PRINT "Usage:" PRINT " firmwdl [com1 | com2 | com3 | com4 [FirmwareFileName]]" PRINT " (defaults to com1 and firm0309.lgo)" END END IF REM This OPEN command is taken from the Qbasic help file. OPEN port$ + ":2400,N,8,1,CD0,CS0,DS0,OP0,RS,TB2048,RB2048" FOR RANDOM AS #1 REM QBasic doesn't like 8 + odd, so zap the UART REM First find the UART parity-register address REM NB Some PC BIOSes don't follow these rules correctly REM so you may have to amend this lookup string. lookup$ = "COM1 &H3FB, COM2 &H2FB, COM3 &H3EB, COM4 &H2EB" register = VAL(MID$(lookup$, INSTR(lookup$, UCASE$(port$)) + 5, 5)) OUT register, &HB: REM odd/8/1 REM All messages start the same way. messageheader$ = CHR$(&H55) + FF00$ REM Can't skip the conversion as firstaddr is set up during conversion. REM Step 1: Read in the s-record file REM --------------------------------- REM and create a ram-image file IF filename$ = "" THEN filename$ = "firm0309.lgo" OPEN filename$ FOR INPUT AS #2 OPEN "~firmwdl.tmp" FOR OUTPUT AS #3 firstaddress& = 99999999: REM Huge lastaddress& = 0 DO UNTIL EOF(2) REM Assume file is correct so ignore S-record checksums LINE INPUT #2, a$ IF LEFT$(a$, 1) = "S" THEN bytecount% = VAL("&H" + MID$(a$, 3, 2)) SELECT CASE MID$(a$, 2, 1) CASE "0" REM Header b$ = "" FOR i = 0 TO bytecount% - 4 c = VAL("&H" + MID$(a$, 9 + i * 2, 2)) IF c < 32 OR c > 126 THEN b$ = b$ + "." ELSE b$ = b$ + CHR$(c) NEXT centre "File Header ID is " + b$ centre "Reading address xxxx" CASE "1" REM 16-bit Data b$ = "" bytecount% = VAL("&H" + MID$(a$, 3, 2)) addr& = VAL("&H" + MID$(a$, 5, 4) + "&") IF addr& < firstaddress& THEN firstaddress& = addr& IF addr& > lastaddress& THEN lastaddress& = addr& + bytecount% - 3 FOR i = 0 TO bytecount% - 4 b$ = b$ + CHR$(VAL("&H" + MID$(a$, 9 + i * 2, 2))) NEXT SEEK 3, addr& PRINT #3, b$ REM Show progress LOCATE 19, 46 PRINT HEX$(addr&) CASE "9" REM Terminator SEEK 2, LOF(2): REM eof END SELECT END IF LOOP CLOSE #2 CLOSE #3 REM Step 2: Find the checksum of the image REM -------------------------------------- LOCATE 19, 1 PRINT " "; LOCATE 19, 1 centre "Calculating Checksum" OPEN "~firmwdl.tmp" FOR BINARY AS #2 Csum& = 0 SEEK 2, firstaddress& FOR counter& = firstaddress& TO lastaddress& Csum& = Csum& + ASC(INPUT$(1, 2)) NEXT CLOSE #2 REM PRINT "Csum "; HEX$(csum&) REM Step 3: Send a delete firmware message REM -------------------------------------- try "10", "": REM Ping try "10", "": REM Ping LOCATE 19, 1 PRINT " "; LOCATE 19, 1 centre "Requesting ROM version" rawromver$ = reply("1d 01 03 05 07 0b") romver$ = "" FOR i = 1 TO 4 romver$ = romver$ + MID$(STR$(VAL("&H" + MID$(rawromver$, i * 6 - 1, 2) + MID$(rawromver$, i * 6 + 2, 2))), 2) + "." NEXT romver$ = LEFT$(romver$, LEN(romver$) - 1) centre "ROM version " + romver$ LOCATE 19, 1 PRINT " "; LOCATE 19, 1 centre "Deleting Firmware" try "6d 01 03 05 07 0b", "" REM Step 4: Send a start firmware download message REM ---------------------------------------------- LOCATE 19, 1 PRINT " "; LOCATE 19, 1 centre "Downloading" try "75 00 80 78 c2 00", "00" REM Step 5: Send the image data using transfer data messages REM -------------------------------------------------------- OPEN "~firmwdl.tmp" FOR BINARY AS #2 chunksize% = 200: REM bytes normalchunks% = (lastaddress& - firstaddress&) \ chunksize% lastchunksize% = (lastaddress& - firstaddress&) MOD chunksize% LOCATE 23, 5 centre STRING$(70, "|") FOR i% = 1 TO normalchunks% senddata firstaddress& + (i% - 1) * chunksize%, chunksize%, i% REM senddata firstaddress& + (51 - 1) * chunksize%, chunksize%, 51 LOCATE 23, 5 PRINT STRING$(69 * i% / normalchunks%, "*") NEXT REM last oddsize chunk senddata firstaddress& + normalchunks% * chunksize%, lastchunksize%, 0 LOCATE 23, 5 PRINT STRING$(70 * i% / normalchunks%, "*") REM Step 6: Send an unlock firmware message REM --------------------------------------- try "a5 4c 45 47 4f ae", MID$(string2hex$("Just a bit off the block!"), 2) centre "Firmware download complete" END handler: RESUME skip DeviceError: IF ERR <> 57 THEN PRINT "Error"; ERR; "while reading the echo in SendMessage" ELSE LOCATE 21, 1 centre "Device error trying to read the echo" END IF RESUME FUNCTION badmessage% (message$) REM the Tidy$ function returns an empty string if the message is bad. IF tidy$(message$) = "" THEN badmessage% = true ELSE badmessage% = false END IF END FUNCTION SUB centre (t$) REM Print a string at the centre of the screen tt$ = t$ DO UNTIL LEN(tt$) < 80 ttt$ = LEFT$(tt$, 78) LOCATE CSRLIN, 40 - LEN(ttt$) / 2 PRINT ttt$ tt$ = MID$(tt$, 79) LOOP LOCATE CSRLIN, 40 - LEN(tt$) / 2 PRINT tt$ END SUB FUNCTION reply$ (message$) REM Returns the RCX reply after message$ has been sent tidymessage$ = tidy$((message$)) send tidymessage$ r$ = "" t = TIMER + 1: REM Wait for 1 second after last byte DO REM Start to receive bytes into r$ IF LOC(1) > 0 THEN r$ = r$ + INPUT$(1, 1) t = TIMER + 1 END IF LOOP UNTIL TIMER > t IF r$ = "" THEN PRINT "Nothing received after sending " + string2hex$(tidymessage$) PRINT "Have you plugged in the IR Tower, and checked the 9v battery?" EXIT FUNCTION END IF IF r$ = tidymessage$ THEN REM All we got back was the transmitted message. PRINT "Received an echo, but no reply from RCX" PRINT "Have you switched on the RCX?" EXIT FUNCTION END IF IF LEFT$(r$, LEN(tidymessage$)) <> tidymessage$ THEN PRINT "Bad echo. Sent " + string2hex$(tidymessage$) PRINT "but received " + string2hex$(r$) EXIT FUNCTION END IF REM Remove the echo from the front of the reply rawreply$ = MID$(r$, LEN(tidymessage$) + 1) REM Now check integrity of reply REM Hex FF 00 must be within first 3 bytes p = INSTR(LEFT$(rawreply$, 3), FF00$) IF p = 0 THEN PRINT "Reply does not start with FF 00" PRINT "Sent " + string2hex$(tidymessage$) PRINT "but received " + string2hex$(r$) EXIT FUNCTION END IF REM remove preamble rawreply$ = MID$(rawreply$, p + 2) REM must be even number of bytes IF (LEN(rawreply$) AND 1) = 1 THEN PRINT "Reply should contain even number of bytes after FF 00" PRINT "Sent " + string2hex$(tidymessage$) PRINT "but received " + string2hex$(r$) EXIT FUNCTION END IF checksum = 0 tidyreply$ = "" FOR i = 1 TO LEN(rawreply$) STEP 2 REM Check for complements, assemble message, and calculate sumcheck c = ASC(MID$(rawreply$, i, 1)) IF c + ASC(MID$(rawreply$, i + 1, 1)) <> 255 THEN PRINT string2hex(rawreply$) PRINT "Reply contains bad complement on bytes"; i; "and"; i + 1 EXIT FUNCTION END IF IF i < LEN(rawreply$) - 2 THEN REM These are data bytes checksum = checksum + c IF LEN(tidyreply$) = 0 THEN REM Command byte tidyreply$ = tidyreply$ + CHR$(c AND &HF7) ELSE tidyreply$ = tidyreply$ + CHR$(c) END IF ELSE REM This is the checksum. Check it. IF (checksum AND 255) <> c THEN PRINT "Bad checksum on reply" PRINT "Sent " + string2hex$(tidymessage$) PRINT "but received " + string2hex$(r$) EXIT FUNCTION END IF END IF NEXT REM check that first received byte is the complement of command IF ((255 - ASC(LEFT$(tidyreply$, 1))) AND &HF7) <> (ASC(MID$(tidymessage$, 4, 1)) AND &HF7) THEN PRINT "RCX reply does not complement the transmitted command" PRINT "Sent " + string2hex$(tidymessage$) PRINT "but received " + string2hex$(r$) EXIT FUNCTION END IF reply$ = string2hex$(tidyreply$) END FUNCTION SUB send (message$) REM Send to Com port. PRINT #1, message$; END SUB SUB senddata (p&, size%, index%) REM Sends a bufferfull of data to the RCX (eg as part of a download) buff$ = STRING$(size%, " ") GET #2, p&, buff$ REM Calculate sumcheck of buff Csum% = 0 FOR i = 1 TO LEN(buff$) Csum% = Csum% + ASC(MID$(buff$, i, 1)) NEXT mess$ = "45" + string2hex$(CHR$(index% MOD 256) + CHR$(index% \ 256)) mess$ = mess$ + string2hex$(CHR$(size% MOD 256) + CHR$(size% \ 256)) mess$ = mess$ + string2hex$(buff$ + CHR$(Csum% AND 255)) tidymessage$ = tidy$(mess$) tries = 0 DO UNTIL tries > maxtries send tidymessage$ tries = tries + 1 REM expect echo followed by tidy$(" B2 00") REM Total length of reply is 26 + size * 2 if all preambles ok t = TIMER + 1: REM 1 second timout L = 0 DO IF L <> LOC(1) THEN L = LOC(1) t = TIMER + 1 END IF LOOP UNTIL TIMER > t OR L = (26 + size% * 2) IF L = (26 + size% * 2) THEN LOCATE 21, 1 centre " ": REM Remove retry message EXIT DO ELSE LOCATE 21, 1 centre " Retry" + STR$(tries) + " " END IF REM We have had a failure ON ERROR GOTO DeviceError IF LOC(1) > 0 THEN a$ = INPUT$(LOC(1), 1): REM empty the buffer ON ERROR GOTO 0 LOOP ON ERROR GOTO DeviceError a$ = INPUT$(LOC(1), 1): REM empty the buffer ON ERROR GOTO 0 REM PRINT index% EXIT SUB END SUB FUNCTION string2hex$ (s$) REM Turn a raw binary string into space-separated hex pairs answer$ = "" FOR i = 1 TO LEN(s$) answer$ = answer$ + " " + RIGHT$("0" + HEX$(ASC(MID$(s$, i, 1))), 2) NEXT string2hex$ = answer$ END FUNCTION FUNCTION tidy$ (message$) REM Returns a binary string by compressing message$ whose format is "xx xx xx" DIM p% message$ = LCASE$(message$) p = 1: REM pointer within message$ tidymessage$ = "": REM Resulting compressed message checksum% = 0 seqno% = seqno% XOR 1: REM sequence the Hex 08 DO WHILE LTRIM$(MID$(message$, p)) > "" REM Extract the next xx bytestring$ = RTRIM$(LEFT$(LTRIM$(MID$(message$, p)), 2)) IF LEN(bytestring$) <> 2 THEN PRINT "Hex symbols must be two characters each, eg 0D 0A" tidy$ = "" EXIT FUNCTION END IF IF INSTR("0123456789abcdef", LEFT$(bytestring$, 1)) = 0 OR INSTR("0123456789abcdef", RIGHT$(bytestring$, 1)) = 0 THEN PRINT "Message contains a non-hex character" tidy$ = "" EXIT FUNCTION END IF byteval = VAL("&H" + bytestring$) IF p = 1 THEN REM this is the command byte so sequence it IF seqno = 1 THEN byteval = byteval OR 8 ELSE byteval = byteval AND &HF7 END IF END IF checksum% = checksum% + byteval tidymessage$ = tidymessage$ + CHR$(byteval) + CHR$((NOT byteval) AND 255) REM P points to the next byte p = LEN(message$) - LEN(LTRIM$(MID$(message$, p))) + 3 LOOP tidy$ = messageheader$ + tidymessage$ + CHR$(checksum% AND 255) + CHR$((NOT checksum%) AND 255) END FUNCTION SUB try (outmess$, inmess$) REM Send outmess$ to RCX and check that the reply is inmess$ OK% = false trycount = 0 DO UNTIL OK% OR trycount = maxtries trycount = trycount + 1 tidymessage$ = tidy$((outmess$)) send tidymessage$ r$ = "" waittime = .33: REM secs waittime = waittime * 2 ^ (trycount - 1): REM double the wait each retry t = TIMER + waittime: REM Wait for waittime seconds after last byte DO REM Start to receive bytes into r$ IF LOC(1) > 0 THEN r$ = r$ + INPUT$(1, 1) t = TIMER + waittime END IF LOOP UNTIL TIMER > t IF r$ = "" THEN IF trycount > 1 THEN REM Only print an error after the first retry PRINT "Nothing received after sending " + string2hex$(tidymessage$) PRINT "Have you plugged in the IR Tower, and checked the 9v battery?" END IF GOTO NotOK END IF IF r$ = tidymessage$ THEN REM All we got back was the transmitted message. IF trycount > 1 THEN REM Only print an error after the first retry PRINT "Received an echo, but no reply from RCX" PRINT "Have you switched on the RCX?" END IF GOTO NotOK END IF IF LEFT$(r$, LEN(tidymessage$)) <> tidymessage$ THEN PRINT "Bad echo. Sent " + string2hex$(tidymessage$) PRINT "but received " + string2hex$(r$) GOTO NotOK END IF REM Remove the echo from the front of the reply rawreply$ = MID$(r$, LEN(tidymessage$) + 1) REM Now check integrity of reply REM Hex FF 00 must be within first 3 bytes p = INSTR(LEFT$(rawreply$, 3), FF00$) IF p = 0 THEN PRINT "Reply does not start with FF 00" PRINT "Sent " + string2hex$(tidymessage$) PRINT "but received " + string2hex$(r$) PRINT "Retrying" GOTO NotOK END IF REM remove preamble rawreply$ = MID$(rawreply$, p + 2) REM must be even number of bytes IF (LEN(rawreply$) AND 1) = 1 THEN PRINT "Reply should contain even number of bytes after FF 00" PRINT "Sent " + string2hex$(tidymessage$) PRINT "but received " + string2hex$(r$) GOTO NotOK END IF checksum = 0 tidyreply$ = "" FOR i = 1 TO LEN(rawreply$) STEP 2 REM Check for complements, assemble message, and calculate sumcheck c = ASC(MID$(rawreply$, i, 1)) IF c + ASC(MID$(rawreply$, i + 1, 1)) <> 255 THEN PRINT string2hex(rawreply$) PRINT "Reply contains bad complement on bytes"; i; "and"; i + 1 GOTO NotOK END IF IF i < LEN(rawreply$) - 2 THEN REM These are data bytes checksum = checksum + c IF LEN(tidyreply$) = 0 THEN REM Command byte tidyreply$ = tidyreply$ + CHR$(c AND &HF7) ELSE tidyreply$ = tidyreply$ + CHR$(c) END IF ELSE REM This is the checksum. Check it. IF (checksum AND 255) <> c THEN PRINT "Bad checksum on reply" PRINT "Sent " + string2hex$(tidymessage$) PRINT "but received " + string2hex$(r$) GOTO NotOK END IF END IF NEXT REM check that first received byte is the complement of command IF ((255 - ASC(LEFT$(tidyreply$, 1))) AND &HF7) <> (ASC(MID$(tidymessage$, 4, 1)) AND &HF7) THEN PRINT "RCX reply does not complement the transmitted command" PRINT "Sent " + string2hex$(tidymessage$) PRINT "but received " + string2hex$(r$) GOTO NotOK END IF OK% = true NotOK: LOOP IF NOT OK% OR inmess$ <> LTRIM$(RTRIM$(MID$(string2hex$(tidyreply$), 4))) THEN PRINT "Reply does not match" PRINT "Expected "; inmess$ PRINT "Received "; LTRIM$(RTRIM$(MID$(string2hex$(tidyreply$), 4))) PRINT tidyreply$ END END IF END SUB