# ############################################################################# # # Copyright (c) 2000-2001 by Wayne C. Gramlich. # All rights reserved. # # Permission to use, copy, modify, distribute, and sell this software # for any purpose is hereby granted without fee provided that the above # copyright notice and this permission are retained. The author makes # no representations about the suitability of this software for any purpose. # It is provided "as is" without express or implied warranty. # # ############################################################################# # This is a test boot loader that resides in high memory for # loading programs into low memory. processor pic16f876 cp=off debug=off cpd=off pwrte=off wdte=on boden=off lvp=off wrt=on fosc=xt # Port and pin definitions: port porta a bits_only read_write_static pin pri0 porta 0 read_only pin pri1 porta 1 read_only pin pri2 porta 2 read_only pin unused1a porta 3 write_only pin unused2a porta 4 write_only pin unused3a porta 5 write_only port portb b bits_only read_write_static pin int portb 0 read_only pin sel0 portb 1 write_only pin sel1 portb 2 write_only pin sel2 portb 3 write_only pin sel3 portb 4 write_only pin sel4 portb 5 write_only pin sel5 portb 6 write_only pin en portb 7 write_only port portc c bits_only read_write_static pin tx portc 6 write_only pin rx portc 7 read_only pin unused1c portb 0 write_only pin unused2c portb 1 write_only pin unused3c portb 2 write_only pin unused4c portb 3 write_only pin unused5c portb 4 write_only pin unused6c portb 5 write_only # Bank 0 registers: # IND register register ind 0 # PCL register register pcl 2 # Status register register status 3 bind c status @ 0 bind dc status @ 1 bind z status @ 2 bind pd status @ 3 bind to status @ 4 bind rp0 status @ 5 bind rp1 status @ 6 bind irp status @ 7 # FSR register register fsr 4 # PCLATH register register pclath 0xa # Interrupt Control register: register intcon 0xb bind gie intcon@7 bind peie intcon@6 bind toie intcon@5 bind inte intcon@4 bind rbie intcon@3 bind toif intcon@2 bind intf intcon@1 bind rbif intcon@0 # PIR1 registers (whatever PIR means): register pir1 0xc bind pspif pir1@7 bind adif pir1@6 bind rcif pir1@5 bind txif pir1@4 bind sspif pir1@3 bind ccp1if pir1@2 bind tmr2if pir1@1 bind tmr1if pir1@0 # Receive status register: register rcsta 0x18 bind spen rcsta@7 bind rx9 rcsta@6 bind sren rcsta@5 bind cren rcsta@4 bind adden rcsta@3 bind ferr rcsta@2 bind oerr rcsta@1 bind rx9d rcsta@0 # Transmit register: register txreg 0x19 # Receive register: register rcreg 0x1a # A/D Control register 0: register adcon0 0x1f bind adon adcon0 @ 0 bind go_done adcon0 @ 2 bind chs0 adcon0 @ 3 bind chs1 adcon0 @ 4 bind adcs0 adcon0 @ 6 bind adcs1 adcon0 @ 7 # Bank 1 registers: register pie1 0x8c bind pspie pie1@7 bind adie pie1@6 bind rcie pie1@5 bind txie pie1@4 bind sspie pie1@3 bind ccp1ie pie1@2 bind tmr2ie pie1@1 bind tmr1ie pie1@0 register txsta 0x98 bind csr txsta@7 bind tx9 txsta@6 bind txen txsta@5 bind sync txsta@4 bind brgh txsta@2 bind trmt txsta@1 bind tx9d txsta@0 # Baud rate generator: register spbrg 0x99 # A/D Control register 1: register adcon1 0x9f bind pcfg0 adcon1 @ 0 bind pcfg1 adcon1 @ 1 bind pcfg2 adcon1 @ 2 # Bank 2 registers: # EEPROM access registers: register eedata 0x10c register eeadr 0x10d register eedath 0x10e register eeadrh 0x10f # Bank 3 registers: register eecon1 0x18c bind eepgd eecon1@7 bind eezlich eecon1@6 bind wrerr eecon1@3 bind wren eecon1@2 bind wr eecon1@1 bind rd eecon1@0 register eecon2 0x18d # 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 constant clock_rate 20000000 constant instruction_rate clock_rate / 4 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 delay_count instructions_per_delay / 6 origin 0x100 procedure program { arguments_none returns_nothing variable count byte count_down count 15 { call put_char(0c'H') call put_char(0c'i') call put_char(0c'!') call put_char(carriage_return) call put_char(line_feed) } } procedure put_char { argument character byte returns_nothing # This procedure will send {character} out to TX-pin of port C. while (!txif) { # Wait for transmit buffer to empty. } # Send the character: txreg := character watch_dog_reset # Now wait for it to be fully sent: while (!txif) { # Wait for transmit buffer to empty. } } origin 0x1800 bank 3 # String constants: string_constants { hello = 0s'PIC16F876-A RoboBrick Rev. 11', carriage_return, line_feed } # Global variables: # In general, I don't like gobal error registers, but for this # application, I think a global one works best: global error byte constant ok 0 constant none 1 constant bad 0xff # One-character look ahead: global look_ahead byte global have_look_ahead bit global zilch_bit bit procedure main { arguments_none returns_nothing variable byte byte variable chr byte variable command byte variable count byte variable crlf_last bit variable end byte variable first bit variable from_address byte variable from_address_high byte variable from_address_low byte variable hex_command byte variable high byte variable high_byte byte variable index byte variable length byte variable low byte variable low_byte byte variable start byte variable to_address byte variable to_address_high byte variable to_address_low byte # Turn of general interrupts: gie := 0 # Initilize serial port: # Do Baud Rate selection and asynch. serial port enable: # Prescaler = low: brgh := 0 # Baud rate = 2400 baud: spbrg := 129 # 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 # Enable continuous recepition: cren := 1 # Enable single reception: sren := 1 # Configure A/D to make all pins of port A digital: pcfg0 := 1 pcfg1 := 1 pcfg2 := 1 # Turn off A/D module: adon := 0 # Select 1: #portb := 2 sel0 := 1 sel1 := 0 sel2 := 0 sel3 := 0 sel4 := 0 sel5 := 0 # Squirt out first characters: index := 0 while (index < hello.size) { call put_character(hello[index]) index := index + 1 } # Command loop: have_look_ahead := 0 loop_forever { # Get command character: call put_character(0c'>') command := get_character() #call put_character(command) # Convert any upper case command into a lower case one: if (0c'a' <= command && command <= 0c'z') { command := command - 0c'a' + 0c'A' } if (command = 0c'E') { # Examine data memory command: # Get {from_address}: from_address := get_hex_byte() & register_mask if (get_white_space()) { # Get to_address: to_address := get_hex_byte() & register_mask } else { to_address := from_address } # For debugging: #call put_character(0c'F') #call put_character(0c'm') #call put_hex_byte(from_address) #call put_character(0c'T') #call put_character(0c'o') #call put_hex_byte(to_address) # Get confirming carriage return: if (get_carriage_return() && error = ok) { # Print out each one: while (from_address <= to_address) { fsr := from_address byte := ind call put_hex_byte(byte) call put_space() from_address := from_address + 1 } call put_new_line() } } else_if (command = 0c'V') { # View program memory command: # Get from address: from_address_high, from_address_low := get_hex_address() if (get_white_space()) { to_address_high, to_address_low := get_hex_address() } else { to_address_high := from_address_high to_address_low := from_address_low } # For debugging: #call put_character(0c'F') #call put_character(0c'm') #call put_hex_address(from_address_high, from_address_low) #call put_character(0c'T') #call put_character(0c'o') #call put_hex_address(to_address_high, to_address_low) if (get_carriage_return() && error = ok) { first := 1 crlf_last := 0 high := from_address_high while (high <= to_address_high) { if (high = from_address_high) { start := from_address_low } else { start := 0 } if (high = to_address_high) { end := to_address_low } else { end := 0xff } low := start count_down count (end - start + 1) { if (first || (low & 7) = 0) { call put_hex_address(high, low) call put_character(0c':') } first := 0 # Read the byte: eeadrh := high eeadr := low eepgd := 1 rd := 1 # The next two instructions are *ignored*!!! eezlich := 0 eezlich := 1 high_byte := eedath low_byte := eedata call put_character(space) call put_hex_address(high_byte, low_byte) # Move to next instruction: low := low + 1 if ((low & 7) = 0) { call put_new_line() crlf_last := 1 } else { crlf_last := 0 } } high := high + 1 } if (!crlf_last) { call put_new_line() } } } else_if (command = 0c'S') { # Store data memory command: zilch_bit := get_carriage_return() } else_if (command = 0c'R') { # Run command zilch_bit := get_carriage_return() } else_if (command = 0c'W') { # Program a hex file: to_address_high, to_address_low := get_hex_address() while (get_white_space()) { # Read the data bytes: high, low := get_hex_address() # Restrict ourselves between # Write the data out: chr := 0c'-' if (to_address_high != 0 && to_address_high < 0x20) { # Read before write: eeadrh := to_address_high eeadr := to_address_low eepgd := 1 rd := 1 # The next two instructions are not executed: dc := 0 dc := 1 # Only write if it does not match: if (eedath = high && eedata = low) { chr := 0c'+' } else { # Do the write: eeadrh := to_address_high eeadr := to_address_low eedath := high eedata := low eepgd := 1 wren := 1 gie := 0 eecon2 := 0x55 eecon2 := 0xaa wr := 1 # The next two instructions are not executed: dc := 0 dc := 1 wren := 0 # Now verify the write: eeadrh := to_address_high eeadr := to_address_low eepgd := 1 rd := 1 # The next two instructions are not executed: dc := 0 dc := 1 # Provide some feedback: if (eedath = high && eedata = low) { chr := 0c'+' } } } call put_character(chr) # increment to next address: to_address_low := to_address_low + 1 if (to_address_low = 0) { to_address_high := to_address_high + 1 } } zilch_bit := get_carriage_return() } else_if (command = 0c'G') { call goto_command() } else { # Unrecognized command: zilch_bit := get_carriage_return() } } } procedure goto_command { arguments_none returns_nothing variable address_low byte variable address_high byte address_high, address_low := get_hex_address() zilch_bit := get_carriage_return() pclath := address_high pcl := address_low } # The `get' procedures are below: procedure get_byte { arguments_none returns byte # Get an 8-bit byte the RX pin of port C. # Compiler kludge: Compiler can't bit test outside of the current data # bank yet. So read the entire register into a temporary and test second. variable pir1_copy byte pir1_copy := pir1 while (!(pir1_copy@5)) { # Wait for character: watch_dog_reset pir1_copy := pir1 } return rcreg } 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. variable character byte character := get_character() if (character = carriage_return) { call put_character(line_feed) return 1 } while (character != carriage_return) { character := get_character() } call put_character(line_feed) return 0 } procedure get_character { arguments_none returns byte # This procedure will get and return a 7-bit ASCII character. # One character of look ahead is supported. #call put_character(0c'G') #call put_character(0c'C') #call put_character(0c'<') #call show_look_ahead(0c'c') look_ahead := peek_character() have_look_ahead := 0 #call show_look_ahead(0c'd') #call put_character(0c'>') #call put_character(0c'=') #call put_character(look_ahead) #call put_character(space) return look_ahead } procedure get_hex_address { arguments_none returns byte, byte # This procedure will read 4-digit hexadecimal address: variable high byte variable low byte error := ok high := get_hex_byte() if (error = ok) { low := get_hex_byte() } return high, low } procedure get_hex_byte { arguments_none returns byte # This procedure will read one hexadecimal byte: variable result byte #variable nibble1 byte #variable nibble2 byte #call put_character(0c'G') #call put_character(0c'H') #call put_character(0c'B') #call put_character(0c'<') error := ok result := get_hex_nibble() if (error = ok) { #nibble2 := get_hex_nibble() result := result << 4 | get_hex_nibble() } #call put_character(0c'>') #call put_character(0c'=') #call put_hex_nibble(nibble1) #call put_hex_nibble(nibble2) #call put_character(space) return result } procedure get_hex_nibble { arguments_none returns byte # This procedure will get and return one hexadecimal digit worth # of number. variable character byte #call put_character(0c'G') #call put_character(0c'H') #call put_character(0c'N') #call put_character(0c'<') character := peek_character() error := ok if (0c'0' <= character && character <= 0c'9') { character := character - 0c'0' } else_if (0c'a' <= character && character <= 0c'f') { character := character - 0c'a' + 10 } else_if (0c'A' <= character && character <= 0c'F') { character := character - 0c'A' + 10 } else { error := none character := 0xff } if (error = ok) { look_ahead := get_character() } #call put_character(0c'>') #call put_character(0c'=') #call put_character(character) #call put_character(space) return character } procedure get_white_space { arguments_none returns bit # This procedure will read exactly one space. If one space is # read, 1 is returned; otherwise, no characters are read and 0 # is returned. variable character byte character := peek_character() if (character = space) { character := get_character() return 1 } return 0 } procedure peek_character { arguments_none returns byte # This procedure will look ahead to the next character input # without actually `reading' it. #call put_character(0c'P') #call put_character(0c'C') #call put_character(0c'<') #call show_look_ahead(0c'a') if (!have_look_ahead) { have_look_ahead := 1 look_ahead := get_byte() & ascii_mask # Do echo: call put_character(look_ahead) } #call show_look_ahead(0c'b') #call put_character(0c'>') #call put_character(0c'=') #call put_character(look_ahead) #call put_character(space) return look_ahead } # The `put' procedures: procedure put_character { argument character byte returns_nothing # This procedure will send {character} out to TX-pin of port C. variable count byte variable pir1_copy byte pir1_copy := pir1 while (!(pir1_copy @ 4)) { # Wait for transmit buffer to empty. pir1_copy := pir1 } # Send the character: txreg := character watch_dog_reset # Now wait for it to be fully sent: pir1_copy := pir1 while (!(pir1_copy @4)) { # Wait for transmit buffer to empty. pir1_copy := pir1 } } procedure put_hex_address { argument high_byte byte argument low_byte byte returns_nothing # This procedure will output {high_byte} followed by {low_byte} # as four hexadecimal digits. call put_hex_byte(high_byte) call put_hex_byte(low_byte) } 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 + 0c'0' } else { nibble := nibble + 0c'A' - 10 } call put_character(nibble) } procedure put_new_line { arguments_none returns_nothing # This procedure will output a carriage-return line-feed pair. call put_character(carriage_return) call put_character(line_feed) } procedure put_space { arguments_none returns_nothing call put_character(space) } procedure delay { arguments_none returns_nothing uniform_delay instructions_per_delay # Delay 1 third of a bit: watch_dog_reset }