# ############################################################################# # # Copyright (c) 2000-2001 by Wayne C. Gramlich and Bill Benson # 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 the code that implements the Out10 RoboBrick. Basically # it just waits for commands that come in at 2400 baud and responds # to them. See # # http://web.gramlich.net/projects/robobricks/out10/index.html # # for more details. # # ############################################################################# processor pic16c505 cp=off wdte=on mclre=off fosc=intrc_no_clock # Define processor constants: constant clock_rate 4000000 constant clocks_per_instruction 4 constant instruction_rate clock_rate / clocks_per_instruction # Define serial communication control 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_instructions_per_bit 9 constant extra_instructions_per_delay extra_instructions_per_bit / delays_per_bit constant delay_instructions instructions_per_delay - extra_instructions_per_delay # Register definitions: # Status register: register status 3 bind c status@0 bind z status@2 # OSCCAL register: register osccal 5 constant osccal_lsb 4 # Define bit offsets: constant io0_bit 0 constant io1_bit 1 constant io2_bit 2 constant io3_bit 3 constant io4_bit 4 constant io5_bit 5 constant io6_bit 4 constant io7_bit 5 constant io8_bit 0 constant io9_bit 1 constant serial_out_bit 2 constant serial_in_bit 3 # Define pin assignments and directions: # Techically, the pins are all read_write_manual, but it # is easier to do all of the tris register manipulation # by hand rather than using uCL "direction" commands. We # start with all the I/O pins set to read-only until such # time as the user explicitly sets them to outputs: port portb b bits_and_byte read_write_static port portc c bits_and_byte read_write_static pin io9 portb io9_bit read_only pin io8 portb io8_bit read_only pin io7 portb io7_bit read_only pin io6 portb io6_bit read_only pin io5 portc io5_bit read_only pin io4 portc io4_bit read_only pin io3 portc io3_bit read_only pin io2 portc io2_bit read_only pin io1 portc io1_bit read_only pin io0 portc io0_bit read_only pin serial_out portb serial_out_bit write_only pin serial_in portb serial_in_bit read_only constant mask 0x1f string_constants { id = 1, 0, 13, 0, 0, 0, 0, 0, 0r'16', 8, 0s'InOut10A', 15, 0s'Gramlich&Benson' } # Some globals: byte global raw_low byte global raw_high byte global inputs_low byte global inputs_high byte global outputs_low byte global outputs_high byte global complement_low byte global complement_high byte global low_low byte global low_high byte global high_low byte global high_high byte global raising_low byte global raising_high byte global falling_low byte global falling_high byte global temporary byte global interrupt_enable bit global interrupt_pending bit global receiving bit bank 1 global direction_low byte global direction_high byte global glitch byte global index byte # Note that the 12-bit PIC's only have a 2-level deep stack. # The code starts in the main procedure (located at the end of this code) # The next level of procedure call is either get_byte or send_byte. # Lastly, the lowest level of procedure call is delay. It all fits, # but just barely. procedure get_byte { arguments_none returns byte # This procedure will wait for a byte to be received from # serial_in_bit. It calls the delay procedure for all delays. variable count byte variable char byte # Why does the delay procedure wait for a third of bit? Well, it # has to do with the loop immediately below. If we catch the # start bit at the beginning of a 1/3 bit time, we will be # sampling data at approximately 1/3 of the way into each bit. # Conversely, if we catch the start near the end of a 1/3 bit # bit time, we will be sampling data at approximately 2/3 of the # way into each bit. So, what this means is that our bit sample # times will be somewhere between 1/3 and 2/3 of bit (i.e. in # the middle of the bit. # It would be nice to tweak the code to shorter delay times # (1/4 bit, 1/5 bit, etc.) but then it gets too hard to get # the bookeeping done in the delay routine. A 12-bit PIC # running at 4MHz (=1MIPS), only has 138 instructions available # for the delay routine when at 1/3 of bit. # Wait for a start bit: receiving := 1 while (serial_in) { call delay() } # Clear interrupt: # 1 cycle: serial_out := 1 # Skip over start bit: call delay() call delay() call delay() # Sample in the middle third of each data bit; # 1 cycle: char := 0 # 2 cycles to set up loop: # 1+1+2 = 4 nop extra_instructions_per_bit - 4 count_down count 8 { call delay() # 2 cycles: char := char >> 1 # 2 cycles: if (serial_in) { char@7 := 1 } call delay() call delay() # 3 cycles at end of loop: # 2+2+3 = 7 nop extra_instructions_per_bit - 7 } # Skip over 2/3's of stop bit: call delay() call delay() return char } procedure send_byte { argument char byte returns_nothing # Send {char} to {tx}: variable count byte # {receiving} will be 1 if the last get/put routine was a get. # Before we start transmitting a response back, we want to ensure # that there has been enough time to turn the line around. # We delay the first 1/3 of a bit to pad out the 9-2/3 bits from # for get_byte to 10 bits. We delay another 1/3 of a bit just # for good measure. Technically, the second call to delay() # is not really needed. if (receiving) { receiving := 0 call delay() call delay() } # Send the start bit: # 1 cycle: serial_out := 0 call delay() call delay() call delay() # 2 cycles to set up loop: # 1+2 = 3 nop extra_instructions_per_bit - 3 # Send the data: count_down count 8 { # 4 cycles: serial_out := char@0 # 2 cycles: char := char >> 1 call delay() call delay() call delay() # 3 cycles at end of loop: # 4+2+3 = 9 = no NOP's needed: } # Send the stop bit: nop 1 # 1 cycle serial_out := 1 call delay() call delay() call delay() # 2 cycles for call/return # 2 cycles for argument # 1+2+2 = 5 nop extra_instructions_per_bit - 5 } bank 1 procedure direction_set { arguments_none returns_nothing # This procedure will set the direction appropriately. variable temp byte # Deal with port C: temp := 0 if (direction_low@0) { temp@io0_bit := 1 } if (direction_low@1) { temp@io1_bit := 1 } if (direction_low@2) { temp@io2_bit := 1 } if (direction_low@3) { temp@io3_bit := 1 } if (direction_low@4) { temp@io4_bit := 1 } if (direction_high@0) { temp@io5_bit := 1 } assemble { movf direction_set__temp w tris 7 } # Deal with port B: temp := 0 if (direction_high@1) { temp@io6_bit := 1 } if (direction_high@2) { temp@io7_bit := 1 } if (direction_high@3) { temp@io8_bit := 1 } if (direction_high@4) { temp@io9_bit := 1 } temp@serial_in_bit := 1 assemble { movf direction_set__temp w tris 6 } } procedure reset { arguments_none returns_nothing # This procedure will initialize all global registers: # Initialize global registers: inputs_low := 0 inputs_high := 0 raw_low := 0 raw_high := 0 outputs_low := 0 outputs_high := 0 complement_low := 0 complement_high := 0 direction_low := mask direction_high := mask low_low := 0 low_high := 0 high_low := 0 high_high := 0 raising_low := 0 raising_high := 0 falling_low := 0 falling_high := 0 interrupt_enable := 0 interrupt_pending := 0 # Initialize remaining registers: glitch := 0 index := 0 } bank 0 procedure delay { arguments_none returns_nothing uniform_delay delay_instructions # This procedure delays 1/3 of a bit. variable previous_low byte variable previous_high byte variable temp byte # Kick the dog: watch_dog_reset # Set the port C outputs first: temp := outputs_low if (outputs_high@0) { temp@io5_bit := 1 } portc := temp # Set the port outputs next: temp := 0 if (outputs_high@1) { temp@io6_bit := 1 } if (outputs_high@2) { temp@io7_bit := 1 } if (outputs_high@3) { temp@io8_bit := 1 } if (outputs_high@4) { temp@io9_bit := 1 } temp@serial_out_bit := portb@serial_out_bit portb := temp # Now read inputs: temp := portc raw_low := temp & mask raw_high := 0 raw_high@0 := temp@5 temp := portb if (temp@io6_bit) { raw_high@1 := 1 } if (temp@io7_bit) { raw_high@2 := 1 } if (temp@io8_bit) { raw_high@3 := 1 } if (temp@io9_bit) { raw_high@4 := 1 } # Process the inputs through the complements mask: previous_low := inputs_low previous_high := inputs_high inputs_low := (raw_low ^ complement_low) & mask inputs_high := (raw_high ^ complement_high) & mask # Now generate any interrupts: temp := inputs_low & high_low | inputs_high & high_high temp := temp | (inputs_low ^ mask) & low_low | (inputs_high ^ mask) & low_high temp := temp | (inputs_low ^ previous_low) & raising_low & inputs_low temp := temp | (inputs_high ^ previous_high) & raising_high & inputs_high temp := temp | (inputs_low ^ previous_low) & falling_low & previous_low temp := temp | (inputs_high ^ previous_high) & falling_high & previous_high if (temp != 0) { interrupt_pending := 1 } if (receiving && interrupt_pending && interrupt_enable) { serial_out := 0 interrupt_enable := 0 } } origin 0x200 bank 1 # The main procedure is loaded with switch statements. On the 12-bit # PIC's, switch statements have to live in the first 256 bytes of # each code bank. For this reason, we shove main into code bank 1. # If we, try to put main in code bank 0, it pushes the first bytes # of several routines out of the first 256 bytes, which is also a # no-no of the 12-bit PIC's. procedure main { arguments_none returns_nothing variable command byte variable temp byte # Initalize all of the globals: call reset() # Process commands: loop_forever { # Wait for command: command := get_byte() # Dispatch on command: switch (command >> 6) { case 0 { # (Command = 00xx xxxx): switch ((command >> 3) & 7) { case 0 { # (Command = 0000 0xxx): switch (command & 7) { case 0 { # Read Inputs Low (Command = 0000 0000): temp := inputs_low } case 1 { # Read Inputs High (Command = 0000 0001): temp := inputs_high } case 2 { # Read Complement Mask Low(Command = 0000 0010): temp := complement_low } case 3 { # Read Complement Mask High (Command = 0000 0011): temp := complement_high } case 4 { # Read Direction Mask Low (Command = 0000 0100): temp := direction_low } case 5 { # Read Direction Mask High (Command = 0000 0101): temp := direction_high } case 6 { # Read Raw Low (Command = 0000 0110): temp := raw_low } case 7 { # Read Raw High (Command = 0000 0111): temp := raw_high } } call send_byte(temp & mask) } case 1 { # (Command = 0000 1xxx): switch (command & 7) { case 0 { # Read Low Mask Low (Command = 0000 1000): temp := low_low } case 1 { # Read Low Mask High (Command = 0000 1001): temp := low_high } case 2 { # Read High Mask Low(Command = 0000 1010): temp := high_low } case 3 { # Read High Mask High (Command = 0000 1011): temp := high_high } case 4 { # Read Raising Mask Low (Command = 0000 1100): temp := raising_low } case 5 { # Read Raising Mask High (Command = 0000 1101): temp := raising_high } case 6 { # Read Falling Mask Low (Command = 0000 1110): temp := falling_low } case 7 { # Read Falling Mask High (Command = 0000 1111): temp := falling_high } } call send_byte(temp & mask) } case 2 { # (Command = 0001 0xxx): switch (command & 7) { case 0 { # Read Outputs Low (Command = 0001 0000): call send_byte(outputs_low) } case 1 { # Read Outputs High (Command = 0001 0001): call send_byte(outputs_high) } case 2 { # Set Complement Mask Low (Command = 0001 0010): complement_low := get_byte() & mask } case 3 { # Set Complement Mask High (Command = 0001 0011): complement_high := get_byte() & mask } case 4 { # Set Direction Mask High (Command = 0001 0100): direction_low := get_byte() & mask call direction_set() } case 5 { # Set Direction Mask High (Command = 0001 0101): direction_high := get_byte() & mask call direction_set() } case 6 { # Reset Everything (Command = 0001 0110): outputs_low := 0 outputs_high := 0 } case 7 { # Reset Everything (Command = 0001 0110): call reset() } } } case 3 { # (Command = 0001 1xxx): temp := get_byte() & mask switch (command & 7) { case 0 { # Set Low Mask Low (Command = 0001 1000): low_low := temp } case 1 { # Set Low Mask High (Command = 0001 1001): low_high := temp } case 2 { # Set High Mask Low(Command = 0001 1010): high_low := temp } case 3 { # Set High Mask High (Command = 0001 1011): high_high := temp } case 4 { # Set Raising Mask Low (Command = 0001 1100): raising_low := temp } case 5 { # Set Raising Mask High (Command = 0001 1101): raising_high := temp } case 6 { # Set Falling Mask Low (Command = 0001 1110): falling_low := temp } case 7 { # Set Falling Mask High (Command = 0001 1111): falling_high := temp } } } case 4, 5, 6, 7 { # Set Outputs Low (Command = 001x xxxx): outputs_low := command & mask } } } case 1 { # (Command = 01xx xxxx): if (command@5) { # Set Output Bit (Command = 011v bbbb): switch (command & 15) { case 0 { outputs_low@0 := command@4 } case 1 { outputs_low@1 := command@4 } case 2 { outputs_low@2 := command@4 } case 3 { outputs_low@3 := command@4 } case 4 { outputs_low@4 := command@4 } case 5 { outputs_high@0 := command@4 } case 6 { outputs_high@1 := command@4 } case 7 { outputs_high@2 := command@4 } case 8 { outputs_high@3 := command@4 } case 9 { outputs_high@4 := command@4 } } } else { # Set Outputs High (Command = 010o oooo): outputs_high := command & mask } } case 2 { # (Command = 10xx xxxx): # Do nothing: } case 3 { # (Command = 11xx xxxx): switch ((command >> 3) & 7) { case 0, 1, 2, 3, 4 { # (Command = 110x xxxx or 1110 0xxx): # Do nothing: } case 5 { # (Command = 1110 1xxx): if (command = 0xef) { # Read Interrupt Bits (Command = 1110 1111): temporary := 0 if (interrupt_pending) { temporary@0 := 1 } if (interrupt_enable) { temporary@1 := 1 } call send_byte(temporary) } } case 6 { # (Command = 1111 0xxx): # Switching between register banks generates bulky code; # Keep code generation in bank 0 by assigning command to # temporary: temporary := command switch (temporary & 7) { case 0, 1, 2, 3 { # Set Interrupt Bits (Command = 1111 00ep): interrupt_enable := temporary@1 interrupt_pending := temporary@0 } case 4, 5 { # Set Interrupt Pending (Command = 1111 010p): interrupt_pending := temporary@0 } case 6, 7 { # Set Interrupt Enable (Command = 1111 011e): interrupt_enable := temporary@0 } } } case 7 { switch (command & 7) { case 0 { # Clock Decrement (Command = 1111 1000): osccal := osccal - osccal_lsb } case 1 { # Clock Increment (Command = 1111 1001): osccal := osccal + osccal_lsb } case 2 { # Clock Read (Command = 1111 1010): call send_byte(osccal) } case 3 { # Clock Pulse (Command = 1111 1011): call send_byte(0) } case 4 { # ID Next (Command = 1111 1100): call send_byte(id[index]) index := index + 1 if (index >= id.size) { index := 0 } } case 5 { # ID Reset (Command = 1111 1101): index := 0 } case 6 { # Glitch Read (Command = 1111 1110): call send_byte(glitch) glitch := 0 } case 7 { # Glitch (Command = 1111 1111): if (glitch != 0xff) { glitch := glitch + 1 } } } } } } } } }