ucl 1.0 # Copyright (c) 2000-2005 by Wayne C. Gramlich. # All rights reserved. # This is a boot loader that resides in high memory for # loading programs into low memory. library $pic16f876 library clock20mhz configure wrt=on # Port and pin definitions: constant a_reg = 0 constant b_reg = 1 constant c_reg = 2 constant n1 = 0 constant n1_reg = b_reg constant n2 = 1 constant n2_reg = a_reg constant n3 = 2 constant n3_reg = b_reg constant n4 = 3 constant n4_reg = a_reg constant n5 = 4 constant n5_reg = b_reg constant n6 = 5 constant n6_reg = a_reg constant n7 = 6 constant n7_reg = b_reg constant n8 = 7 constant n8_reg = c_reg constant n9 = 8 constant n9_reg = c_reg constant n10 = 9 constant n10_reg = c_reg constant n11 = 10 constant n11_reg = c_reg constant n_none = 11 # Port definitions: package pdip pin 1 = mclr pin 2 = ra0_in, name = n2in, bit = n2in_bit, mask = n2in_mask pin 3 = ra1_out, name = n2out, bit = n2out_bit, mask = n2out_mask pin 4 = ra2_in, name = n4in, bit = n4in_bit, mask = n4in_mask pin 5 = ra3_out, name = n4out, bit = n4out_bit, mask = n4out_mask pin 6 = ra4_in, name = n6in, bit = n6in_bit, mask = n6in_mask pin 7 = ra5_out, name = n6out, bit = n6_out_bit, mask = n6out_mask pin 8 = ground pin 9 = osc1 pin 10 = osc2 pin 11 = rc0_in, name = n9in, bit = n9in_bit, mask = n9in_mask pin 12 = rc1_out, name = n9out, bit = n9out_bit, mask = n9out_mask pin 13 = rc2_in, name = n8in, bit = n8in_bit, mask = n8in_mask pin 14 = rc3_in, name = n10in, bit = n10in_bit, mask = n10in_mask pin 15 = rc4_out, name = n10out, bit = n10out_bit, mask = n10out_mask pin 16 = rc5_out, name = n8out, bit = n8out_bit, mask = n8out_mask pin 17 = rc6_out, name = n11out, bit = n11out_bit, mask = n11out_mask pin 18 = rc7_in, name = n11in, bit = n11in_bit, mask = n11in_mask pin 19 = ground2 pin 20 = power_supply pin 21 = rb0_in, name = n7in, bit = n7in_bit, mask = n7in_mask pin 22 = rb1_out, name = n7out, bit = n7_out_bit, mask = n7out_mask pin 23 = rb2_in, name = n5in, bit = n5in_bit, mask = n5in_mask pin 24 = rb3_out, name = n5out, bit = n5out_bit, mask = n5out_mask pin 25 = rb4_in, name = n3in, bit = n3in_bit, mask = n3in_mask pin 26 = rb5_out, name = n3out, bit = n3out_bit, mask = n3out_mask pin 27 = rb6_in, name = n1in, bit = n1in_bit, mask = n1in_mask pin 28 = rb7_out, name = n1out, bit = n1out_bit, mask = n1out_mask # The tether is expected on connector N1: #constant tether_socket = n1 constant tether_socket = n1 # Some general constants: constant ascii_mask = 0x7f constant tab = 8 constant line_feed = 10 constant carriage_return = 13 constant space = 32 constant register_mask = 0x7f # Stome timing constants: constant baud_rate = 2400 constant instructions_per_bit = instruction_rate / baud_rate constant delays_per_bit = 3 constant instructions_per_delay = instructions_per_bit / delays_per_bit constant extra_delay_instructions = 8 constant extra_bit_instructions = extra_delay_instructions * delays_per_bit constant delay_instructions = instructions_per_delay - extra_delay_instructions # The null pulse that comes back from a clock pulse command is supposed to be # exactly 9 bits long. 9 bits at 2400 baud is 9/2400 = 3.75mS. The number # iterations through the loop is 3.75mS / (number of instructions per iteration.) constant nine_bits_instructions = (clock_rate * 9) / (4 * baud_rate) constant instructions_per_iteration = 13 constant iterations_for_nine_bits = nine_bits_instructions / instructions_per_iteration constant iterations_high = iterations_for_nine_bits / 256 constant iterations_low = iterations_for_nine_bits - (iterations_high * 256) origin 0 data_bank 1 procedure start arguments_none returns_nothing # This procedure will transfer control to the boot loader {main}. call main() constant program_origin = 8 origin program_origin procedure program arguments_none returns_nothing # Dummy program that does nothing: local index byte index := 1 loop_exactly message.size call put_character(message[index]) index := index + 1 call main() string message = "You need to download a program!\cr,lf\" data_bank 2 constant buffer_size = 8 global buffer_highs[buffer_size] array[byte] global buffer_lows[buffer_size] array[byte] constant boot_loader_origin = 0x1b00 constant boot_loader_origin_high = boot_loader_origin >> 8 origin boot_loader_origin data_bank 0 register_array bank01[256] = 0 register_array bank23[256] = 0x100 register_array ports[3] = 5 constant id_reset = 0xfd constant id_next = 0xfc # Global variables: # In general, I don't like gobal error registers, but for this # application, I think a global one works best: global ok bit global have_carriage_return bit # I/O registers: global in_mask byte global in_out_register byte global out_mask byte global receiving bit global sleep_count byte procedure main arguments_none returns_nothing local address_high byte local address_low byte local block_index byte local buffer_index byte local buffer_size byte local byte byte local check_sum byte local chr byte local command byte local count byte local errors byte local error_high byte local error_low byte local index byte local length byte local more bit local return1 byte local return2 byte local socket byte local type byte # Turn of general interrupts: $gie := 0 # Initilize serial port: # Do Baud Rate selection and asynch. serial port enable: # Prescaler = low: $brgh := 1 # Baud rate = 19200 Baud: $spbrg := 64 # Asynchronous mode: $sync := 0 # Serial port enable: $spen := 1 $txif := 0 # Enable the transmitter: # 8-bit mode: $tx9 := 0 # Enable transmitter: $txen := 1 # Enable the receiver: # 8-bit mode: $rx9 := 0 # Disable address: $adden := 0 # Continuous receive enable: $cren := 1 # Serial receive enable: $sren := 1 # Initialize all of the serial outs: n1out := 1 n2out := 1 n3out := 1 n4out := 1 n5out := 1 n6out := 1 n7out := 1 n8out := 1 n9out := 1 n10out := 1 #call put_character('H') #call put_character('i') #call put_character('!') #call put_character('\r\') #call put_character('\n\') # Print out a hello message: index := 0 while index < hello.size call put_character(hello[index]) index := index + 1 if seconds_sleep(60) call put_crlf() call execute() #FIXME: What is this? #call put_byte(0x11, n10) #call put_byte(0xa0, n10) socket := n1 errors := 0 # Command loop: #call put_crlf() loop_forever # Output a prompt: call put_character('>') # Go fetch a command character: have_carriage_return := 0 ok := 1 command := get_character() # Dispatch on command character: if command = 'C' byte := get_hex_byte() if get_carriage_return() call clock_adjust(byte, socket) else_if command = 'E' # Examine data memory command: address_high := get_hex_byte() address_low := get_hex_byte() count := get_hex_byte() if get_carriage_return() loop_exactly count if address_high = 0 byte := bank01[address_low] else byte := bank23[address_low] call put_hex_byte(byte) call put_space() address_low := address_low + 1 if $z address_high := address_high + 1 call put_crlf() else_if command = 'G' # Goto command: call goto_command() else_if command = 'I' if get_carriage_return() call put_byte(id_reset, socket) call id8(socket) call id8(socket) call id8(socket) call id_string(socket) call id_string(socket) else_if command = 'K' byte := get_hex_nibble() if byte = 0 || byte > 10 ok := 0 if get_carriage_return() socket := byte - 1 else_if command = 'P' # Print a page of memory: $eeadrh := get_hex_byte() if get_carriage_return() index := 0 more := 1 while more if index & 7 = 0 # Display the address: call put_hex_byte($eeadrh) call put_hex_byte(index) call put_character(':') # Read the word: $eeadr := index $eepgd := 1 $rd := 1 # The next two instructions are *ignored*: assemble nop nop # Display the word: call put_space() call put_hex_byte($eedath) call put_hex_byte($eedata) # Bump to next address in page: index := index + 1 if $z more := 0 if index & 7 = 0 call put_crlf() else_if command = 'R' # Send a command to a RoboBrick: byte := get_hex_byte() if get_carriage_return() call put_byte(byte, socket) return1 := get_byte(socket) return2 := get_byte(socket) call put_hex_byte(return1) call put_space() call put_hex_byte(return2) call put_crlf() else_if command = 'S' # Store data memory command: address_high := get_hex_byte() address_low := get_hex_byte() byte := get_hex_byte() if get_carriage_return() if address_high = 0 bank01[address_low] := byte else bank23[address_low] := byte else_if command = 'T' loop_forever call put_byte('U', n2) else_if command = 'V' if get_carriage_return() call put_character('1') call put_character('.') call put_character('0') call put_crlf() else_if command = 'X' if get_carriage_return() call execute() else_if command = ':' # Hex command: check_sum := 0 length := get_hex_byte() check_sum := check_sum + length address_high := get_hex_byte() check_sum := check_sum + address_high address_low := get_hex_byte() check_sum := check_sum + address_low type := get_hex_byte() check_sum := check_sum + type index := 0 buffer_size := length >> 1 while index < buffer_size byte := get_hex_byte() check_sum := check_sum + byte buffer_lows[index] := byte byte := get_hex_byte() check_sum := check_sum + byte buffer_highs[index] := byte index := index + 1 check_sum := check_sum + get_hex_byte() if get_carriage_return() # Command completed without error: # For debugging only: #call put_character('L') #call put_hex_byte(length) #call put_space() #call put_character('A') #call put_hex_byte(address_high) #call put_hex_byte(address_low) #call put_space() #call put_character('T') #call put_hex_byte(type) #call put_space() #index := 0 #buffer_size := length >> 1 #while index < buffer_size # call put_character('[') # call put_hex_nibble(index) # call put_character(']') # call put_hex_byte(buffer_highs[index]) # call put_hex_byte(buffer_lows[index]) # call put_space() # index := index + 1 #call put_character('C') #call put_hex_byte(check_sum) #call put_crlf() if check_sum = 0 # Check sum is cool: # Address is off by a factor of 2: address_low := address_low >> 1 if address_high@0 address_low@7 := 1 address_high := address_high >> 1 #call put_character('X') #call put_hex_byte(address_high) #call put_hex_byte(address_low) #call put_space() # Only write data into "safe" pages: if type = 1 if errors != 0 call put_character('E') call put_character('r') call put_character('r') call put_character('@') call put_hex_byte(error_high) call put_hex_byte(error_low) call put_crlf() errors := 0 else_if type != 0 #call put_character('$') do_nothing else_if address_high = 0 && address_low & 0xfc = 0 # Trying to modify first 4 words of flash; # This is not allowed: #call put_character('^') do_nothing else_if address_high < boot_loader_origin_high # Write data until the buffer is empty: #call put_character('@') ok := 1 index := 0 loop_exactly buffer_size # Write the instruction into flash: #call put_character('&') $eeadrh := address_high $eeadr := address_low $eedath := buffer_highs[index] $eedata := buffer_lows[index] $eepgd := 1 $wren := 1 $eecon2 := 0x55 $eecon2 := 0xaa $wr := 1 # The next two instructions must be nop's: assemble nop nop $wren := 0 # Verify that the data was written successfully: $eepgd := 1 $rd := 1 # The next two instructions must be nop's: assemble nop nop if buffer_highs[index] != $eedath #call put_character('=') error_high := $eeadrh error_low := $eeadr errors := errors + 1 if buffer_lows[index] != $eedata #call put_character('+') error_high := $eeadrh error_low := $eeadr errors := errors + 1 # Increment the address: address_low := address_low + 1 if $z address_high := address_high + 1 index := index + 1 else errors := errors + 1 call put_character('c') call put_character('s') call put_character('=') call put_hex_byte(check_sum) call put_crlf() else_if command = carriage_return # Empty command:ain11 do_nothing else # Unrecognized command: call get_carriage_return() call put_character('?') call put_crlf() procedure clock_adjust argument adjust byte argument socket byte returns_nothing # This procedure will adjust the clock to the slave. local command byte local count byte local error byte local error_minimum byte local high byte local in_mask byte local in_out_register byte local low byte local target byte target := iterations_low - adjust count := 2 error := 0xff error_minimum := 0xf0 # Print out the target: call put_character('T') call put_hex_byte(iterations_high) call put_hex_byte(target) call put_crlf() loop_forever # Print out the clock value: call put_character('C') call put_byte(0xfa, socket) call put_hex_byte(get_byte(socket)) call put_space() in_mask := in_masks(socket) in_out_register := in_out_registers(socket) # Ask for a timing byte: call put_byte(0xfb, socket) # Nasty: low := 0 high := 0 while ports[in_out_register] & in_mask != 0 do_nothing while ports[in_out_register] & in_mask = 0 low := low + 1 if $z high := high + 1 # Print out high and low: call put_character('H') call put_hex_byte(high) call put_space() call put_character('L') call put_hex_byte(low) call put_space() # Now think about adjusting clock. if high > iterations_high # Clock pulse is too long; slave clock is too slow: command := 0xf9 error := high - target else_if high < iterations_high # Clock pulse is too short; slave clock is too fast: command := 0xf8 error := target - high else # The high 8-bits are equal: if low > target # Clock pulse is too long; slave clock is too slow: command := 0xf9 error := low - target else_if low < target # Clock pulse is too short; slave clock is too fast: command := 0xf8 error := target - low else # Exact match; we are done: command := 0 error := 0 error_minimum := 0 # Print out the error and error minimum: call put_character('E') call put_hex_byte(error) call put_space() call put_character('M') call put_hex_byte(error_minimum) call put_space() if error = error_minimum call put_crlf() return else_if error < error_minimum error_minimum := error error := error + 1 else count := count - 1 if $z error_minimum := error_minimum + 1 count := 2 # Now adjust the clock: call put_byte(command, socket) call put_crlf() procedure execute arguments_none returns_nothing # This procedure will transfer control to location program_origin. $pclath := 0 $pcl := program_origin procedure goto_command arguments_none returns_nothing local address_low byte local address_high byte address_high := get_hex_byte() address_low := get_hex_byte() if get_carriage_return() $pclath := address_high $pcl := address_low # The `id' procedures are below: procedure id8 argument socket byte returns_nothing # This procedure will print out 8 bytes of id: local count byte loop_exactly 8 call put_hex_byte(put_get_byte(id_next, socket)) call put_space() call put_crlf() procedure id_string argument socket byte returns_nothing # This procedure will print out a string from the id: local size byte size := put_get_byte(id_next, socket) while size != 0 call put_character(put_get_byte(id_next, socket)) size := size - 1 call put_crlf() # The `get' procedures are below: procedure get_byte argument socket byte returns byte # This procedure will get a byte from the currently selected # input. If no byte is received in a reasonable amount of # time, 0xfc is returned. local counter byte local character byte local in_mask byte local in_out_register byte in_out_register := in_out_registers(socket) in_mask := in_masks(socket) receiving := 1 loop_exactly 255 if ports[in_out_register] & in_mask = 0 # We got a start bit: call delay() call delay() call delay() # 2 Instructions for loop_exactly initialization: delay extra_bit_instructions - 2 # Sample 1/3 of bit into each character: character := 0 loop_exactly 8 call delay() # 3 Instructions for loop overhead: delay extra_bit_instructions - 3 character := character >> 1 if ports[in_out_register] & in_mask != 0 character@7 := 1 call delay() call delay() # Now sleep through 2/3's of the stop bit: call delay() call delay() return character call delay() return 0xfc procedure get_carriage_return arguments_none returns bit # This procedure will verify that the next character is # a carriage return. If the next character is a carriage # return a 1 is returned. Otherwise, characters are read # until a carriage return is encountered and 0 is returned. # If any errors occurred (i.e. ok = 0), a "?" followed by # carriage-return line-feed is returned. while get_character() != carriage_return ok := 0 have_carriage_return := 0 if ok return 1 call put_character('?') call put_crlf() return 0 procedure get_character arguments_none returns byte # This procedure will get the next character from the UART. local character byte local pir1_copy byte if have_carriage_return return carriage_return # FIXME: Compiler can't bit test outside of the current data bank!!! # So read the entire register into a temporary and test second. # Read characters until something other than a space is typed in: character := space while character = space pir1_copy@5 := 0 while !(pir1_copy@5) # Wait for character: pir1_copy := $pir1 character := $rcreg # Convert to upper case: if 'a' <= character && character <= 'z' character := character - 'a' + 'A' # Carriage returns "stick" until get_carriage_return() is called: if character = carriage_return call put_character(line_feed) have_carriage_return := 1 return character procedure get_hex_byte arguments_none returns byte # This procedure will read one hexadecimal byte: return (get_hex_nibble() << 4) | get_hex_nibble() procedure get_hex_nibble arguments_none returns byte # This procedure will get and return one hexadecimal digit worth # of number. local character byte character := get_character() if '0' <= character && character <= '9' return character - '0' else_if 'A' <= character && character <= 'F' return character - 'A' + 10 ok := 0 return 0 # The `put' procedures: procedure put_get_byte argument command byte argument socket byte returns byte # This procedure will output {command} to {socket} and wait # for a response which is subseuently returned. call put_byte(command, socket) return get_byte(socket) procedure put_byte argument character byte argument socket byte returns_nothing # This procedure will send {character} out to {socket}. local counter byte local one byte local port byte local zero byte local in_out_register byte local out_mask byte in_out_register := in_out_registers(socket) out_mask := out_masks(socket) port := ports[in_out_register] one := port | out_mask zero := port & (out_mask ^ 0xff) # If we were last receiving some characters, let's delay by 2/3 bit # to make sure there is adequate turn-around: if receiving receiving := 0 call delay() call delay() # Do start bit: delay extra_bit_instructions - 2 ports[in_out_register] := zero call delay() call delay() call delay() # "- 2" is for two bytes of loop set up: # Do next 8 bits: loop_exactly 8 # 3 cycles for goto at end of loop delay extra_bit_instructions - 3 port := ports[in_out_register] if character@0 # Send a mark (1): port := one else # Send a space (0): port := zero ports[in_out_register] := port character := character >> 1 call delay() call delay() call delay() # Now do stop bit: delay extra_bit_instructions ports[in_out_register] := one call delay() call delay() call delay() procedure put_character argument character byte returns_nothing # This procedure will send {character} out to TX-pin of port C. local pir1_copy byte #FIXME: The compiler chokes on a bit test in another page!!! # For now may a local copy and test locally: pir1_copy@4 := 0 while !(pir1_copy@4) # Wait for transmit buffer to empty. pir1_copy := $pir1 # Send the character: $txreg := character procedure put_crlf arguments_none returns_nothing # This procedure will output a carriage-return line-feed. call put_character(carriage_return) call put_character(line_feed) procedure put_hex_byte argument byte byte returns_nothing # This procedure will output {byte} as two hexadecimal digits. call put_hex_nibble(byte >> 4) call put_hex_nibble(byte & 15) procedure put_hex_nibble argument nibble byte returns_nothing # This procedure will output {nibble} as a hexadecimal digit. if nibble < 10 nibble := nibble + '0' else nibble := nibble + 'A' - 10 call put_character(nibble) procedure put_space arguments_none returns_nothing call put_character(space) # Sleep routines: procedure seconds_sleep argument seconds byte returns bit # This procedure will sleep for the specified number of seconds. local count byte count := 0 loop_exactly seconds call put_hex_byte(count) call put_space() if second_sleep() if sleep_count < 10 return 1 return 0 count := count + 1 return 1 procedure second_sleep arguments_none returns bit # This procedure will sleep for one second. local count1 byte local count2 byte $rcif := 0 $rcreg := 0 loop_exactly 100 loop_exactly 100 delay 500 if $rcif return 1 if sleep_count != 255 sleep_count := sleep_count + 1 return 0 procedure delay arguments_none returns_nothing exact_delay delay_instructions # Delay 1/3 of a bit: # Kick the dog: watch_dog_reset string hello = "PICBrain11D\cr,lf\" procedure in_out_registers argument socket byte returns byte # This procedure will return the register number to access for {socket}. switch socket case n1 return n1_reg case n2 return n2_reg case n3 return n3_reg case n4 return n4_reg case n5 return n5_reg case n6 return n6_reg case n7 return n7_reg case n8 return n8_reg case n9 return n9_reg case n10 return n10_reg case n11 return n11_reg return n_none procedure in_masks argument socket byte returns byte # This procedure will return the input mask for {socket}. switch socket case n1 return n1in_mask case n2 return n2in_mask case n3 return n3in_mask case n4 return n4in_mask case n5 return n5in_mask case n6 return n6in_mask case n7 return n7in_mask case n8 return n8in_mask case n9 return n9in_mask case n10 return n10in_mask case n11 return n11in_mask return 0 procedure out_masks argument socket byte returns byte # This procedure will return the output mask for {socket}. switch socket case n1 return n1out_mask case n2 return n2out_mask case n3 return n3out_mask case n4 return n4out_mask case n5 return n5out_mask case n6 return n6out_mask case n7 return n7out_mask case n8 return n8out_mask case n9 return n9out_mask case n10 return n10out_mask case n11 return n11out_mask return 0