# ############################################################################# # # Copyright (c) 1999-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 harness for testing the InOut10 RoboBrick. See: # # http://web.gramlich.net/projects/robobricks/inout10/index.html # # for more details. # # ############################################################################# processor pic16f628 cp=off cpd=off lvp=off bowden=off mclre=on pwrte=off wdte=off fosc=xt constant clock_rate 10000000 constant clock_ticks_per_instruction 4 constant instructions_per_second clock_rate / clock_ticks_per_instruction constant baud_rate 2400 constant instructions_per_bit clock_rate / (clock_ticks_per_instruction * baud_rate) constant delays_per_bit 3 constant instructions_per_delay instructions_per_bit / delays_per_bit constant extra_instructions_per_bit 12 constant extra_instructions_per_delay extra_instructions_per_bit / delays_per_bit constant delay_instructions instructions_per_delay - extra_instructions_per_delay # 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) / (clock_ticks_per_instruction * baud_rate) constant instructions_per_iteration 7 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) # Some character constants: constant sp 32 constant cr 13 constant lf 10 # Some bit definitions: constant rx_slave_bit 5 constant tx_slave_bit 4 constant rx_master_bit 1 constant tx_master_bit 2 constant rx_slave_mask 1 << rx_slave_bit constant tx_slave_mask 1 << tx_slave_bit constant rx_master_mask 1 << rx_master_bit constant tx_master_mask 1 << tx_master_bit # Some register definitions: register status 3 bind c status@0 bind z status@2 # Some port and pin definitions: #port porta a unused none port portb b bits_only read_write_static pin tx_master_pin portb tx_master_bit write_only pin rx_master_pin portb rx_master_bit read_only pin tx_slave_pin portb tx_slave_bit write_only pin rx_slave_pin portb rx_slave_bit read_only # Miscellaneous definitions: constant space 0xff constant buffer_size 5 constant mask 0x1f string_constants { common_string = 0s'Common' patterns = 0, 1, 2, 4, 8, 0x10, 0x1f, 0xf, 0x17, 0x1b, 0x1d, 0x1e, 0 done_string = 0s'Done' fail_string = 0s'Fail' interrupt_string = 0s'Int' io_string = 0s'I/O' hello_string = 0s'InOut10_Test' test_string = 0s'Test' register_string = 0s'Reg' } procedure main { arguments_none returns_nothing # Read a byte. variable buffer[buffer_size] byte variable char byte variable count byte variable index byte variable number byte # Print out a welcome message: call master_crlf() call master_string(hello_string) call master_crlf() # Main loop number := 0 loop_forever { # Get a character: tx_slave_pin := 1 char := master_get() # Delay 2/3's of bit make sure that get_byte is done. call delay() call delay() if (0c'0' <= char && char < (0c'9' + 1)) { # Do a multiply by 8 then add in digit: call master_send(char) number := (number << 3) + char - 0c'0' } else_if (char = 0c's') { # Send byte to brick, no wait: # Echo command and send CRLF: call master_send(char) call master_crlf() # Ship the byte down to the brick: call slave_send(number) number := 0 } else_if (char = 0c'w') { # Send byte to brick, wait for results: # Echo command and send CRLF: call master_send(char) call master_crlf() # Ship the byte down to the brick ... call slave_send(number) number := 0 #call delay() # ...and wait for a response: index := 0 while (index < buffer_size) { buffer[index] := slave_get() index := index + 1 } index := 0 while (index < buffer_size) { call master_octal(buffer[index]) index := index + 1 } # Terminate the output list call master_crlf() } else_if (char = 0c'i') { # Interrogate the slave RoboBrick: call master_send(char) call master_crlf() # Initialize the id index: call slave_send(0xfd) # Get the first 8 bytes: call common_test_id_bytes8() # Get the next 8 bytes of random numbers: call common_test_id_bytes8() # Get the next 8 bytes of random numbers: call common_test_id_bytes8() # Get the slave brick name: call common_test_id_string() # Get the vendor name: call common_test_id_string() } else_if (char = 0c't') { # Do testing: call master_send(char) call master_crlf() call test() } else_if (char = 0c'c') { call master_send(char) call master_crlf() call clock_adjust(number) number := 0 } else_if (char != 0xfc) { # Just echo back the current number: call master_send(0c'<') call master_octal(char) call master_send(0c'>') # Send a carriage-return line-feed: call master_crlf() # Output the character in octal: call master_octal(number) # Send a carriage-return line-feed: call master_crlf() # Reset number number := 0 } } } procedure clock_adjust { argument adjust byte returns_nothing # This procedure will adjust the clock to the slave. variable command byte variable count byte variable error byte variable error_minimum byte variable high byte variable low byte variable target byte target := iterations_low - adjust count := 2 error := 0xff error_minimum := 0xf0 # Print out the target: call master_send(0c'T') call master_octal(iterations_high) call master_octal(target) call master_crlf() loop_forever { # Print out the clock value: call master_send(0c'C') call slave_send(0xfa) call master_octal(slave_get()) # Ask for a timing byte: call slave_send(0xfb) low := 0 high := 0 while (rx_slave_pin) { # Do nothing: } while (!rx_slave_pin) { low := low + 1 if (z) { high := high + 1 } } # Print out high and low: call master_send(0c'H') call master_octal(high) call master_send(0c'L') call master_octal(low) # Now think about adjusting clock. if (high > iterations_high) { # Clock pulse is too long; slave clock is too slow: command := 0xf9 error := 0xff - target } else_if (high < iterations_high) { # Clock pulse is too short; slave clock is too fast: command := 0xf8 error := target } 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 master_send(0c'E') call master_octal(error) call master_send(0c'M') call master_octal(error_minimum) if (error = error_minimum) { call master_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 slave_send(command) call master_crlf() } } procedure test { arguments_none returns_nothing # This procedure will test the InOut10 RoboBrick. variable high byte variable index byte variable low byte variable low_high byte variable pattern byte variable pattern_index byte variable register_index byte variable read_command byte variable set_command byte variable temp byte # This code assumes that the bit-wise pairs of the module # have been tied together -- bit 0 to 1, bit 2 to 3, ..., # bit 8 to 9. # Verify that the common commands are working: call common_test() # Verify the common interrupt stuff: call interrupt_test() # Verify that the registers can all be read and written properly: call master_string(register_string) call master_string(test_string) call master_crlf() # Force the outputs to all zeros so that when we teak # the direction command below, the outputs are always # driven to the same value. # Verify that we can read and write the registers: register_index := 0 while (register_index < 8) { switch (register_index) { case 0 { # Complement: read_command := 2 set_command := 0x12 } case 1 { # Direction: read_command := 4 set_command := 0x14 } case 3 { # Low: read_command := 8 set_command := 0x18 } case 4 { # High: read_command := 0xa set_command := 0x1a } case 5 { # Raising: read_command := 0xc set_command := 0x1c } case 6 { # Falling: read_command := 0xe set_command := 0x1e } case 7 { # Outputs: read_command := 0x10 set_command := 0xff } } # Let's just do a reset to get the direction register set to all 1's # and the outputs to all zero's: call slave_send(0x17) low_high := 0 while (low_high < 2) { pattern_index := 0 while (pattern_index < patterns.size) { pattern := patterns[pattern_index] # Write the pattern out: if (set_command = 0xff) { # Outputs are special: if (low_high = 0) { call slave_send(0x20 | pattern) } else { call slave_send(0x40 | pattern) } } else { # Otherwise just use set_command as is: call slave_send(set_command | low_high) call slave_send(pattern) } # Read the pattern back in: call slave_send(read_command | low_high) temp := slave_get() if (temp != pattern) { call master_octal(register_index) call master_octal(low_high) call master_octal(pattern) call master_octal(temp) call master_fail(register_string, 0x30) } pattern_index := pattern_index + 1 } low_high := low_high + 1 } register_index := register_index + 1 } # Test out the inputs and outputs: call master_string(io_string) call master_string(test_string) call master_crlf() # This code is really tedious: low_high := 0 while (low_high < 2) { # Set the direction bits into alternate mode: if (low_high = 0) { # Pass 1 -- even pin outputs to odd pin inputs: call slave_send(0x14) call slave_send(0x15) call slave_send(0x15) call slave_send(0xa) } else { # Pass 2 -- odd pin outputs to even pin inputs: call slave_send(0x14) call slave_send(0xa) call slave_send(0x15) call slave_send(0x15) } # Send some patterns out: index := 0 while (index < 32) { # Spread bits abcde from {index} into aabbc and cddee # in {high} and {low}: low := 0 high := 0 if (index@0) { low@0 := 1 low@1 := 1 } if (index@1) { low@2 := 1 low@3 := 1 } if (index@2) { low@4 := 1 high@0 := 1 } if (index@3) { high@1 := 1 high@2 := 1 } if (index@4) { high@3 := 1 high@4 := 1 } # Send the outputs: call slave_send(0x20 | low) call slave_send(0x40 | high) # Now verify that the bits transfered across: # Read Raw Low call slave_send(6) temp := slave_get() if (temp != low) { call master_octal(index) call master_octal(low) call master_octal(temp) call master_fail(io_string, 0x31) } # Read Raw High call slave_send(7) temp := slave_get() if (temp != high) { call master_octal(index) call master_octal(high) call master_octal(temp) call master_fail(io_string, 0x32) } # Set complement mask to all ones: call slave_send(0x12) call slave_send(0x1f) call slave_send(0x13) call slave_send(0x1f) # Read Inputs Low call slave_send(0) temp := slave_get() if (temp ^ mask != low) { call master_octal(index) call master_octal(low) call master_octal(temp) call master_fail(io_string, 0x33) } # Read Inputs High call slave_send(1) temp := slave_get() if (temp ^ mask != high) { call master_octal(index) call master_octal(high) call master_octal(temp) call master_fail(io_string, 0x34) } # Set complement mask to all zeros: call slave_send(0x12) call slave_send(0) call slave_send(0x13) call slave_send(0) # Read Inputs Low call slave_send(0) temp := slave_get() if (temp != low) { call master_octal(index) call master_octal(low) call master_octal(temp) call master_fail(io_string, 0x35) } # Read Inputs High call slave_send(1) temp := slave_get() if (temp != high) { call master_octal(index) call master_octal(high) call master_octal(temp) call master_fail(io_string, 0x36) } index := index + 1 } low_high := low_high + 1 } # Now test the interrupt stuff: call master_string(interrupt_string) call master_string(test_string) call master_crlf() # Set even pin outputs to odd pin inputs: call slave_send(0x14) call slave_send(0x15) call slave_send(0x15) call slave_send(0xa) # Low test: call test_interrupt(0x18, 0x20, mask, 0x40) call test_interrupt(0x19, 0x40, mask, 0x42) # High test: call test_interrupt(0x1a, 0x20, 0, 0x44) call test_interrupt(0x1b, 0x40, 0, 0x46) # Raising test: call test_interrupt(0x1c, 0x20, 0, 0x48) call test_interrupt(0x1d, 0x40, 0, 0x4a) # Falling test: call test_interrupt(0x1e, 0x20, mask, 0x4c) call test_interrupt(0x1f, 0x40, mask, 0x4e) # Reset everything: call slave_send(0x17) # Announce that we are done: call master_string(done_string) call master_crlf() } procedure test_interrupt { argument mask_set byte argument output_set byte argument pattern byte argument error byte returns_nothing # This procedure will test interrupts. variable temp byte # Set outputs to pattern: call slave_send(output_set | pattern) # Set the mask to all one's: call slave_send(mask_set) call slave_send(mask) # Verify that pending is not set: call slave_send(0xef) temp := slave_get() if (temp != 0) { call master_octal(temp) call master_fail(interrupt_string, error) } # Now complement the outputs: call slave_send(output_set | (pattern ^ mask)) # Verify that pending is set: call slave_send(0xef) temp := slave_get() if (temp != 1) { call master_octal(temp) call master_fail(interrupt_string, error + 1) } # Now clear the interrupt mask: call slave_send(mask_set) call slave_send(0) # Now clear the pending bit: call slave_send(0xf0) } # The procedures below are used to test the common shared commands: procedure common_test { arguments_none returns_nothing # This procedure will verify that the common shared commands work. variable actual byte # Print the ID information: # ID reset: call slave_send(0xfd) # Read the fixed bytes: call common_test_id_match(1, 0xc0) call common_test_id_match(0, 0xc1) call common_test_id_match(13, 0xc2) call common_test_id_match(0, 0xc3) call common_test_id_match(0, 0xc4) call common_test_id_match(0, 0xc5) call common_test_id_match(0, 0xc6) call common_test_id_match(0, 0xc7) call master_crlf() call common_test_id_bytes8() call common_test_id_bytes8() # Read the brick name: call common_test_id_string() # Read the vendor name: call common_test_id_string() # Verfify that we don't read off the end of the id and crash: call common_test_id_next() # Reset id: call slave_send(0xfd) # Verify that we are still alive: call common_test_id_match(1, 0xc8) # Read glitch: # Clear glitch register: call slave_send(0xfe) actual := slave_get() # Send a couple of glitches: call slave_send(0xff) call slave_send(0xff) # Read the glitch register: call slave_send(0xfe) if (slave_get() != 2) { call master_fail(common_string, 0xc9) } # Do a clock pulse: call slave_send(0xfb) if (slave_get() != 0) { call master_fail(common_string, 0xca) } # Read clock: call slave_send(0xfa) actual := slave_get() # Increment: call slave_send(0xf9) # Decrement: call slave_send(0xf8) # Read clock again: call slave_send(0xfa) if (actual != slave_get()) { call master_fail(common_string, 0xcb) } } procedure common_test_id_match { argument desired byte argument test_number byte returns_nothing # This procedure will verify that the next byte in the id is {desired}. if (desired != common_test_id_next()) { call master_fail(common_string, test_number) } } procedure common_test_id_bytes8 { arguments_none returns_nothing # This procedure will print out the next 8 bytes of the id in octal. variable count byte count_down count 8 { call master_octal(common_test_id_next()) } call master_crlf() } procedure common_test_id_string { arguments_none returns_nothing # This procedure will print out the next id string. variable count byte count_down count (common_test_id_next()) { call master_send(common_test_id_next()) } call master_crlf() } procedure common_test_id_next { arguments_none returns byte # This procedure returns the next byte from the identification string. call slave_send(0xfc) return slave_get() } # The procedures below are used to test the shared interrupt commands: procedure interrupt_test { arguments_none returns_nothing # This procedure tests the common shared interrupt commands. variable counter byte # Clear interrupt bits: call slave_send(0xf0) call interrupt_read(0, 0xd0) # Set/clear interrupt pending bit only: call slave_send(0xf5) call interrupt_read(1, 0xd1) call slave_send(0xf4) call interrupt_read(0, 0xd2) call slave_send(0xf1) call interrupt_read(1, 0xd3) call slave_send(0xf0) call interrupt_read(0, 0xd4) # Set/clear interrupt enable bit only: call slave_send(0xf7) call interrupt_read(2, 0xd5) call slave_send(0xf6) call interrupt_read(0, 0xd6) call slave_send(0xf2) call interrupt_read(2, 0xd7) call slave_send(0xf0) call interrupt_read(0, 0xd8) # Now set both the interrupt enable and pending bit. # This must trigger an interrupt. call slave_send(0xf3) count_down counter 3 { call delay() } if (rx_slave_pin) { # No interrupt detected: call master_fail(interrupt_string, 0xd9) } # The interrupt enable bit should cleared as a side effect # of triggering the interrupt: call interrupt_read(1, 0xda) # Clear the interrupt pending bit: call slave_send(0xf0) call interrupt_read(0, 0xdb) } procedure interrupt_read { argument desired byte argument test_number byte returns_nothing # This procedure will verify that the interrupt bits match {desired}. variable temp byte call slave_send(0xef) temp := slave_get() if (temp != desired) { call master_octal(temp) call master_fail(interrupt_string, test_number) } } # The following procedures are used to communicate with the master: procedure master_crlf { arguments_none returns_nothing # This procedure will output a carriage-return line-feed # to the master. call master_send(cr) call master_send(lf) } procedure master_fail { argument test_name string argument test_number byte returns_nothing # This procedure will output `fail' followed by a carriage return # and line feed. call master_string(fail_string) call master_string(test_name) call master_octal(test_number) call master_crlf() } procedure master_get { arguments_none returns byte # This procedure will get the next byte or return 0xfc if # no byte is forthcoming. return get_byte(rx_master_mask) } procedure master_octal { argument number byte returns_nothing # This procedure will output {number} in octal to the tx port. # Output the character in octal: call master_send((number>>6) + 0c'0') call master_send(((number>>3) & 7) + 0c'0') call master_send((number & 7) + 0c'0') call master_send(sp) } procedure slave_get { arguments_none returns byte # This procedure will get a byte from the slave or return 0xfc # if no byte is forthcoming. return get_byte(rx_slave_mask) } procedure slave_send { argument data byte returns_nothing # This procedure will send one byte of {data} to the slave. call send_byte(data, tx_slave_mask) } # The last procedures do character sending and receiving: procedure get_byte { argument mask byte returns byte # Get an 8-bit byte from {mask} bit of {portb} and return it. # If no character shows up in a while 0xfc is returned. variable count byte variable char byte # Wait until a start bit arrives: count := 0 while (portb & mask != 0) { count := count - 1 if (count = 0) { return 0xfc } call delay() } # Skip over the start bit: call delay() call delay() call delay() # Sample in the middle third of each data bit: # 1 cycle: char := 0 # 2 cycles for loop setup: # 1+2 = 3 nop extra_instructions_per_bit - 3 count_down count 8 { call delay() # 2 cycles: char := char >> 1 # 3 cycles: if (portb & mask != 0) { char@7 := 1 } call delay() call delay() # 3 cycles at end of loop: # 2+3+3 = 8 nop extra_instructions_per_bit - 8 } # Skip over 2/3 of the stop bit: call delay() call delay() return char } procedure send_byte { argument char byte argument mask byte returns_nothing # Send {char} to {mask} bit of {portb}. variable count byte variable mark byte # Send the start bit: # 3 cycles: mark := mask ^ space # 1 cycles (mark is already in W): portb := mark call delay() call delay() call delay() # 2 cycles for loop setup: # 3+1+2 = 6 nop extra_instructions_per_bit - 6 # Send the data: count_down count 8 { # 5 cycles: if (char@0) { portb := space } else { portb := mark } # 2 cycles: char := char >> 1 call delay() call delay() call delay() # 3 cycles at end of loop: # 5+2+3 = 10 nop extra_instructions_per_bit - 10 } # Send the stop bit: # 1 cycle to finish off previous loop: nop 1 # 2 cycles: portb := space call delay() call delay() call delay() # 4 cycles for call/return: # 4 cycles for two arguments: # 2+4+4 = 10 nop extra_instructions_per_bit - 10 } procedure master_send { argument character byte returns_nothing # This procedure will output {character} to the master. call send_byte(character, tx_master_mask) } procedure master_string { argument message string returns_nothing # This procedure will output {message} to the master. variable size byte variable index byte index := 0 while (index < message.size) { call master_send(message[index]) index := index + 1 } call master_send(sp) } procedure delay { arguments_none returns_nothing uniform_delay delay_instructions # Delay 1 third of a bit: }