#!/bin/sh # the next line restarts using tclsh \ exec tclsh "$0" "$@" # eeburn - HC11 System EEPROM (Internal or External) # Burning Utility # for A1, A8 (and probably E2) family members. # written entirely in TCL. # # Copyright 1999, 2000, Loren Wyard-Scott (wyard@ee.ualberta.ca) # The author hereby grants permission to use this code in # whole or in part as long as credit is given. # # Note that due to the nature of the program that is # downloaded to the HC11 (boot_ext.s19) when programming # external EEPROM, this program can also be `fooled' into # downloading a program to external RAM. Neat! # # Usage: # ------ # eeburn file [-baud baudrate] [-port serialport] [-mode dispmode] # [-chip eetype] [-start startaddr] # # where # file is the name of the .s19 file (including the # extension) to burn into EEPROM. # baudrate is an integer value specifying the bootstrap # download baud rate (used by hcdl). # This is an optional argument that defaults to # the value specified within hcdl. # serialport is the name of the serial port that will be # used. eg) /dev/ttyS1 # This is an optional argument that defaults to # the value specified below. # dispmode is one of the following: # colour - uses ANSI escape sequences for a pretty # display. # line - displays information, but not using # a colorful display. # Again, this is an optional argument that defaults to # the value specified below. # eetype is the type of EEPROM being programmed. # The following are currently supported: # internal - programs the internal EEPROM # starting at B600. Note that for # the E2 device, the EEPROM should # appear at this location. # 28128 - external EEPROM (16K) # 28256 - external EEPROM (32K) # This is an optional argument that defaults # To the value specified below. # start is the starting address of the EEPROM. # This value is used to translate an arbitrary # .s19 file to the appropriate memory range # for burning. Perhaps useful if the HC11 # board is being used as an EEPROM burner. # # Requires TCL version 8.1 or greater. # available from ftp.scriptics.com # # Installation instructions: # -------------------------- # 1. Copy this file to /usr/local/bin or wherever you want # it to reside. # 2. Set permissions to allow execution by whomever you # feel comfortable giving access to serial ports. # 3. Copy boot_ext.s19 and boot_int.s19 to some location # where they can reside. I suggest /usr/local/hc11/. # These are the HC11 .s19 files which perform the actual # burning of either external or internal EEPROM. # 3. Whoever uses this program will also need to access # the serial port in both read and write capacities. # Set permissions accordingly. # 4. Adjust the path to tclsh by modifying the first line # of this file. # 5. Edit the default variable values to match your # system (HC11 board and PC). # # Note: eeburnx is a version of eeburn with an X interface. # # Lessons learned from writing this program: # ------------------------------------------ # # Future Directions: # ------------------ # 1. Add an event loop (and serial port in non-blocking mode) # so the user can be notified when returns from the HC11 are # not received (rather than hanging-up). # 2. Utlize (and check) serial port lockout. # # Revision History # ---------------- # Revision 0.2 -- Wed May 3 14:51:41 MDT 2000 # - Changed serial port to non-blocking operating mode. # - Added polling loop which aborts after so many unsuccessfull reads of # the serial port. Made somewhat CPU-independent by using the "after" # command. # - Added lockfile serial port access semaphore. In order to avoid # coercing execution permissions to allow file creation in /var/lock, # the lock file is placed in the user's home directory: ~/LCK..ttySx # (the same naming format that minicom uses). # ###################################################################### # DEFAULT VARIABLE VALUES # The following variables may need to be changed depending upon # your system configuration. # If no serial port is specified on the command-line, this # one is used. set serialport /dev/ttyS1 # If no bootstrap baudrate is specified on the command-line, # this one is used. This rate is used by hcdl. set baudrate 4800; # If no display mode is specified, this one is used. set dispmode colour # Default EEPROM type. set eetype 28128 # Default EEPROM address. set startaddress 0xC000 # Path to the .s19 files used to actually burn the EEPROM. # Trailing slash (/) is required. set s19path "/usr/local/hc11/" ###################################################################### set Revision 0.2 # Establish the maximum time that the non-blocking serial port # should be polled prior to considering the line dead (i.e. no response). # Variable MAXPOLLCOUNT is the number of times that the line should be # read (and nothing received), delay for 1 ms, and then re-read. set MAXPOLLCOUNT 100; if {$tcl_version < 8.1} { puts stdout "This is TCL version $tcl_version. You need at least 8.1." puts stderr "This is TCL version $tcl_version. You need at least 8.1." exit 1 } set programstart ""; # Uninitialized program start. # Parse the command-line arguments. if {$argc < 1} { puts stdout "HC11 EEPROM Burning Utility" puts stdout "Usage:" puts stdout " eeburn file \[-baud baudrate\] \[-port serialport\] " puts stdout " \[-mode dispmode\] \[-chip eetype\] \[-start startaddr\]" puts stdout " file is the name of the .s19 file (including the" puts stdout " extension) to burn into EEPROM." puts stdout " baudrate is an integer value specifying the bootstrap" puts stdout " download baud rate (used by hcdl). " puts stdout " This is an optional argument that defaults to" puts stdout " the value specified within hcdl." puts stdout " serialport is the name of the serial port that will be" puts stdout " used. Defaults to $serialport." puts stdout " the value specified below." puts stdout " dispmode is one of the following:" puts stdout " colour - uses ANSI escape sequences for a pretty" puts stdout " display." puts stdout " line - displays information, but not using" puts stdout " a colorful display." puts stdout " Default is $dispmode." puts stdout " eetype is the type of EEPROM being programmed." puts stdout " The following are currently supported:" puts stdout " internal - programs the internal EEPROM" puts stdout " starting at B600. Note that for" puts stdout " the E2 device, the EEPROM should" puts stdout " appear at this location." puts stdout " 28128 - external EEPROM (16K)" puts stdout " 28256 - external EEPROM (32K)" puts stdout " Defaults to $eetype." puts stdout " start is the starting address of the EEPROM." puts stdout " This value is used to translate an arbitrary" puts stdout " .s19 file to the appropriate memory range" puts stdout " for burning. Perhaps useful if the HC11" puts stdout " board is being used as an EEPROM burner." puts stdout " Defaults to $startaddress." exit 1 } # The name of the file should be the first argument on the command # line. set filename [lindex $argv 0] if [catch "open $filename RDONLY" fileID] { puts stdout $fileID exit 1 } # Now parse the optional arguments in pairs. set listlength [llength $argv] for {set argument 1} {[expr $argument+1] < $listlength} {incr argument 2} { set option [lindex $argv $argument] set value [lindex $argv [expr $argument+1]] if {[string match $option -baud]} { set baudrate $value } elseif {[string match $option -port]} { set serialport $value } elseif {[string match $option -mode]} { set dispmode $value } elseif {[string match $option -chip]} { set eetype $value } elseif {[string match $option -start]} { set startaddress $value } else { puts stdout "Unknown option/value: $option $value" # Print the help by recursively calling this script. catch "exec eeburn" result puts stdout $result exit 1 } } # Ensure that if we are programming the internal EEPROM, the # options are set correctly. # The following steps depend upon the selected device. if [string match internal $eetype] { # Fool the following code into knowing the size # of the internal EEPROM. set eetype "in4"; # 4kbits set startaddress B600; # Internal EEPROM address. } # Done parsing the command-line arguments. # Call the bootstrap downloader and send our more complex # version which looks after internals of EEPROM programming. # The version of the s19 file sent depends upon what we # are programming. puts stdout "This program makes use of the hcdl (HC11 download) utility." puts stdout "Place the HC11 in download mode and press return to execute hcdl." gets stdin if [string match in4 $eetype] { # Programming the internal EEPROM. Send the .s19 file # that does this. append s19path "boot_int.s19" set cmd [list hcdl $s19path -mode $dispmode -baud $baudrate >@ stdout] } else { # Must be programming external EEPROM. append s19path "boot_ext.s19" set cmd [list hcdl $s19path -mode $dispmode -baud $baudrate >@ stdout] } # Call hcdl. puts stdout "Calling hcdl to send $s19path." if [catch "exec $cmd" result] { puts stdout "Failed to download $s19path." puts stdout $result puts stderr "Failed to download $s19path." exit 1 } # If we got this far, then the bootstap download (burner) # program should now be resident on the HC11 from 0x0000 to # 0x00FF or so. # Reserve the serial port using a lockout file "LCK..ttySx" in the user's home directory. # Create the lock file name. set lockname "$env(HOME)/LCK..[lindex [split $serialport /] end]" if [catch "exec lockfile -r 1 $lockname" result] { puts stderr "Could not create lock file $lockname." puts stderr " $result" puts stderr "Is there another program currently using $serialport?" puts stderr "If not, please type: rm -f $lockname" exit 1 } # Open the serial port at 9600 baud (the speed at which the # burning program works. if [catch "open $serialport RDWR" serID] { puts stdout "Problems opening serial port: $serialport" puts stderr "Problems opening serial port: $serialport" ExitProgram 1 } # Set the baudrate and the asynchronous format. if [catch "fconfigure $serID -mode 9600,n,8,1 -translation binary -blocking 0"] { puts stdout "Problem attempting to set serial transfer rate of $baudrate." ExitProgram 1 } # Display the static screen (if colour) or miscellaneous information. if [string match $dispmode colour] { puts stdout "\x1b\[?50l\x1b\[2J\x1b\[2;25H\x1b\[41m---------EEPROM Burner---------" puts stdout "\x1b\[3;25H HC11 A1/A8/E2 Revision $Revision " puts stdout "\x1b\[4;25H Copyright 2000 L. Wyard-Scott " puts stdout "\x1b\[5;25H-------------------------------\x1b\[m" puts stdout "\x1b\[17;25H\x1b\[1mStatus: \x1b\[m" puts stdout "\x1b\[19;18H\x1b\[1mWrite Address: Byte: \x1b\[m" puts stdout "\x1b\[20;18H\x1b\[1mVerify Address: Byte: \x1b\[m" } elseif [string match $dispmode line] { puts stdout "---------EEPROM Burner---------" puts stdout " HC11 A1/A8/E2 Revision $Revision " puts stdout " Copyright 2000 L. Wyard-Scott " puts stdout "-------------------------------" } # Show the connection speed and file name. if [string match $dispmode colour] { puts -nonewline stdout "\x1b\[7;12H\x1b\[mSerial Mode: [fconfigure $serID -mode] " puts stdout "File: [file tail $filename] Device: $eetype" } elseif [string match $dispmode line] { puts stdout "Serial Mode: [fconfigure $serID -mode]" puts stdout "File: $filename" puts stdout "Device: $eetype" } # Create a global array that will contain the # data read in from the file. set sdata(0) "" ###################################################################### proc readfile {fileID} { # Read in an .s19 file into global array called sdata. # The program is shifted down to 0x0000 based upon the # address specified in the first record. This could # potentially be a problem if the S-records are out # of order in the file. global sdata global programstart # Grab the S record type. set rectype [read $fileID 2] if ![string match S9 $rectype] { #puts stdout "Processing record: $rectype" set numdata [expr 0x[read $fileID 2]] set numdata [expr $numdata -3] # puts stdout "Number of pieces of data: $numdata" set addr [expr 0x[read $fileID 4]] # puts stdout "Starting address: $addr" if {[string match "" $programstart]} { # This is the first record. Get the program # starting address from the record. set programstart $addr # puts stdout "Program starting address: $programstart" } for {set i 0} {$i<$numdata} {incr i} { # puts stdout "$i-" nonewline # Put the data in the array, adjusting for the starting # address of the program. set sdata([expr $addr + $i - $programstart]) [read $fileID 2] } gets $fileID readfile $fileID } } ###################################################################### proc filldata {length} { # Ensures that all array elements from 0 to length of global # array sdata are initialized to FF. global sdata for {set i 0} {$i<$length} {incr i} { set sdata($i) FF } } ###################################################################### proc printdata {length} { global sdata set numblock [expr $length/32] for {set block 0} {$block < $numblock} {incr block} { puts stdout "$block: " nonewline for {set i 0} {$i < 32} {incr i} { puts stdout $sdata([expr $i + 32 * $block]) nonewline } puts stdout ""; # Append new line. } } ###################################################################### proc SendDataExternal {address length serID} { # Routine to send info to the bootstrap program resident # in the HC11 0x0000-0x00FF (the EEPROM burning program). # Sends length bytes through serial port identified by serID # in 64 byte blocks (by call to sendblock). # Data is contained in global sdata. global dispmode global MAXPOLLCOUNT set numblocks [expr $length / 64] set count 0 # First send NULL to synchronize with the HC11. if [string match $dispmode colour] { puts stdout "\x1b\[17;34H Sending Synchronization Byte\x1b\[0K" } elseif [string match $dispmode line] { puts stdout "Sending Synchronization Byte" } sendbyte "00" $serID # Await the NULL which should be returned by the HC11. set response [receivebyte $serID $MAXPOLLCOUNT] if [string match NoChar $response] { if [string match $dispmode colour] { puts stdout "\x1b\[17;34H\x1b\[5m Synchronization byte not returned.\x1b\[1m" puts stdout "\x1b\[24;1H\x1b\[m\x1b\[?25h" } puts stderr "Synch not returned. Did hcdl run correctly?" ExitProgram 1 } # Compare the byte received with that sent out. comparebyte "00" $response "Starting Synchronization Error!" for {set i 0} {$i < $numblocks} {incr i} { if [string match $dispmode colour] { puts stdout "\x1b\[17;34HSending block $i of [expr $numblocks-1]\x1b\[0K" } elseif [string match $dispmode line] { puts stdout "Sending block $i of [expr $numblocks-1]" } # Format the starting address of the block so it is 4 hex digits. # Leading zeroes are required. set addresshex [format "%04X" $address] printblock $i $addresshex $count sendblock $addresshex $count $serID # Prepare to process the next block. incr count 64 incr address 64 } } ###################################################################### proc SendDataInternal {address length serID} { # Routine to send info to the bootstrap program resident # in the HC11 0x0000-0x00FF (the EEPROM burning program). # Sends length bytes through serial port identified by serID # Data is contained in global sdata. # This routine interfaces with the HC11-resident bootstrap # program responsible for programming internal EEPROM. global dispmode global sdata global MAXPOLLCOUNT # First send NULL to synchronize with the HC11. if [string match $dispmode colour] { puts stdout "\x1b\[17;34H Sending Synchronization Byte" } elseif [string match $dispmode line] { puts stdout "Sending Synchronization Byte" } sendbyte "00" $serID # Await the NULL which should be returned by the HC11. set response [receivebyte $serID $MAXPOLLCOUNT] if [string match NoChar $response] { if [string match $dispmode colour] { puts stdout "\x1b\[17;34H\x1b\[5m Synchronization byte not returned.\x1b\[1m" puts stdout "\x1b\[24;1H\x1b\[m\x1b\[?25h" } puts stderr "Synch not returned. Did hcdl run correctly?" ExitProgram 1 } # Compare the byte received with that sent out. comparebyte "00" $response "Starting Synchronization Error!" # Alright! Now to send all of the data out! if [string match $dispmode colour] { puts stdout "\x1b\[17;34HTransmitting Internal EEPROM Data\x1b\[K" } elseif [string match $dispmode line] { puts stdout "Transmitting Internal EEPROM Data." } for {set i 0} {$i<$length} {incr i} { set byte $sdata([expr $i ]) sendbyte $byte $serID if [string match $dispmode colour] { puts stdout "\x1b\[19;35H[format "%04X" [expr $i + 0x$address]]" puts stdout "\x1b\[19;50H$byte" } elseif [string match $dispmode line] { puts stdout "." nonewline flush stdout } # Grab the response from the HC11. set response [receivebyte $serID $MAXPOLLCOUNT] if [string match NoChar $response] { if [string match $dispmode colour] { puts stdout "\x1b\[17;34H\x1b\[5m Timed out waiting for response.\x1b\[1m" puts stdout "\x1b\[24;1H\x1b\[m\x1b\[?25h" } puts stderr "No response from board." ExitProgram 1 } if [string match $dispmode colour] { puts stdout "\x1b\[20;35H[format "%04X" [expr $i + 0x$address]]" puts stdout "\x1b\[20;50H$byte" } comparebyte $byte $response \ [list "Internal EEPROM failure: Wrote $byte, received $response" \ " Address: [format "%04X" [expr $i + 0x$address]]"] } } ###################################################################### proc printblock {blocknum address index} { # Procedure to display a block of data being sent. global sdata global dispmode if [string match $dispmode colour] { puts stdout "\x1b\[9;1H" } puts stdout "Block: $blocknum Address: $address" for {set i 0} {$i < 32} {incr i} { puts stdout $sdata([expr $i + $index]) nonewline } puts stdout "" for {set i 32} {$i < 64} {incr i} { puts stdout $sdata([expr $i + $index]) nonewline } puts stdout "" } ###################################################################### proc sendblock {address index serID} { # Routine to send one 64 byte block of data (plus its starting # address). Determines the checksum and ensures that the HC11 # wrote the data correctly by comparing this checksum with that # returned by the HC11. global sdata global dispmode global MAXPOLLCOUNT # Now that we are synchronized, send the address blindly. # Calculate the checksum as we go. set addrhigh [string range $address 0 1] set addrlow [string range $address 2 3] sendbyte $addrhigh $serID sendbyte $addrlow $serID # Convert the address bytes into their decimal values. scan $addrhigh "%x" addrhighval scan $addrlow "%x" addrlowval set checksum [expr $addrhighval + $addrlowval] # Alright! Now to send all 64 bytes of data data out! for {set i 0} {$i<64} {incr i} { set byte $sdata([expr $i + $index]) if [string match $dispmode colour] { puts stdout "\x1b\[19;35H[format "%04X" [expr $i + 0x$address]]" puts stdout "\x1b\[19;50H$byte" } elseif [string match $dispmode line] { puts stdout "." nonewline flush stdout } sendbyte $byte $serID # Increase the checksum. scan $byte "%x" byteval set checksum [expr $checksum + $byteval] } # Get the data back from the HC11 to verify. # This data has already been written. if [string match $dispmode line] { puts stdout "" puts stdout "Verifying Block Data " } for {set i 0} {$i<64} {incr i} { # What the byte was supposed to be. set byte $sdata([expr $i + $index]) # What the byte is supposed to be. set hc11byte [receivebyte $serID $MAXPOLLCOUNT] if [string match NoChar $hc11byte] { if [string match $dispmode colour] { puts stdout "\x1b\[17;34H\x1b\[5m Checksum byte not returned.\x1b\[1m" puts stdout "\x1b\[24;1H\x1b\[m\x1b\[?25h" } puts stderr "Checksum not returned." ExitProgram 1 } if [string match $dispmode colour] { puts stdout "\x1b\[20;35H[format "%04X" [expr $i + 0x$address]]" puts stdout "\x1b\[20;50H$byte" } elseif [string match $dispmode line] { puts stdout "." nonewline flush stdout } set addresshex [format "%04X" [expr $i + 0x$address]] comparebyte $byte $hc11byte \ [concat "\n\rByte mismatch ([expr $i + 1] of 64) " \ " Address $addresshex. (Written, Returned)."] } # Now receive the checksum byte. if [string match $dispmode colour] { puts stdout "\x1b\[17;34H Awaiting Checksum Byte\x1b\[K" } elseif [string match $dispmode line] { puts stdout "" puts stdout "Awaiting Checksum Byte" } # All 64 bytes of data have been sent. The HC11 is to # respond with a 1's complement of the single-byte checksum. set hc11checksum [receivebyte $serID $MAXPOLLCOUNT] if [string match NoChar $hc11checksum] { if [string match $dispmode colour] { puts stdout "\x1b\[17;34H\x1b\[5m Checksum byte not returned.\x1b\[1m" puts stdout "\x1b\[24;1H\x1b\[m\x1b\[?25h" } puts stderr "Checksum not returned." ExitProgram 1 } # Convert the HC11 checksum to a value. scan $hc11checksum "%x" hc11val # Take the 1's complement. set hc11val [expr 255 - $hc11val] # Now put it back into 2-digit hex. This will be used for comparison. set hc11hex [format "%02X" $hc11val] # Convert the checksum calculated here into hex and grab the 2 least- # significant digits. set checksumhex [format "%02X" $checksum] set length [string length $checksumhex] set checksumbytehex [string range $checksumhex [expr $length - 2] end] comparebyte $checksumbytehex $hc11hex [list "Block checksums unequal" \ " Calculated $checksumbytehex" \ " Returned $hc11hex."] if [string match $dispmode colour] { puts stdout "\x1b\[17;34HChecksum verified.\x1b\[K" } else { puts stdout "Checksums: $checksumbytehex (calculated), $hc11hex (returned)." } } ###################################################################### proc sendbyte {hexvalue serID} { # Sends a byte specfied in hex out to the serial port. set data [binary format H2 $hexvalue] # The NULL needs to be handled just a little differently. if [string match 00 $hexvalue] { puts $serID \x00 nonewline } else { puts $serID $data nonewline } flush $serID } ###################################################################### proc receivebyte {serID PollCount} { # Receives a byte from the specified serial port and returns # it in 2-digit hex form. The incoming information is polled. # If the serial port is read PollCount times, then the # routine returns the string "NoChar" rather than the byte received. set PollNumber 0 while {$PollNumber < $PollCount} { binary scan [read $serID 1] H2 value # Make the Hex letters capital for comparison. if [info exists value] { set value [string toupper $value] return $value } else { after 1; # Delay 1 millisecond. incr PollNumber } } return "NoChar" } ###################################################################### proc comparebyte {arg1 arg2 errormsg} { # Procedure to compare the 2-digit (hex) arguments. If # they are not equal, the error message is displayed and # the application aborted. global dispmode if ![string match $arg1 $arg2] { if [string match $dispmode colour] { puts stdout "\x1b\[17;34H\x1b\[5m $errormsg\x1b\[1m" puts stdout "\x1b\[18;34H Comparison: $arg1, $arg2" puts stdout "\x1b\[24;1H\x1b\[m\x1b\[?25h" } else { puts stdout $errormsg puts stdout "Comparison: $arg1, $arg2" } ExitProgram 1 } } ###################################################################### proc ExitProgram {exitvalue} { # Procedure to intercept termination of the program. Removes the # lock file and closes the files. global serID global fileID global lockname # Close the files. close $serID close $fileID # Remove the lockfile. exec rm -f $lockname exit $exitvalue } ###################################################################### # Determine the size of the EEPROM (and therfore the array, sdata). set bits [string range $eetype 2 end] set bytes [expr $bits * 1024 / 8] if [string match $dispmode colour] { puts stdout "\x1b\[8;12H\x1b\[mDevice bits: $bits K Bytes: $bytes" } else { puts stdout "Device bits: $bits K Bytes: $bytes" } # Fill the entire array with value 'FF'. filldata $bytes # Read the information from the .s19 file. # Address information is stripped off when read in. # This is OK because it is restated in startaddress. readfile $fileID # Now actually interface with the program that was downloaded # in bootstrap mode. if [string match in4 $eetype] { SendDataInternal $startaddress $bytes $serID } else { # Programming external interface. There is a slightly # different protocol here. SendDataExternal $startaddress $bytes $serID } if [string match $dispmode colour] { puts stdout "\x1b\[17;34H\x1b\[5mSuccessfully Completed.\x1b\[K" puts stdout "\x1b\[24;1H\x1b\[m\x1b\[?25h" } else { puts stdout "" puts stdout "Successfully completed." } ExitProgram 1