# ############################################################################# # # Copyright (c) 2000-2001 by Wayne C. Gramlich & William T. 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 BIROD2 RoboBrick. Basically # it just waits for commands that come in at 2400 baud and responds # to them. See # # http://web.gramlich.net/projects/robobricks/birod2/index.html # # for more details. # # ############################################################################# processor pic12c509 cp=off wdte=on mclre=off fosc=intrc # 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_unit 4 #constant osccal_unit 0x10 # Define I/O port bit assignments: port porta a bits_and_byte read_write_static pin out0 porta 0 write_only pin in0 porta 1 read_only pin out1 porta 2 write_only pin in1 porta 3 read_only pin serial_in porta 4 read_only pin serial_out porta 5 write_only string_constants { id = 1, 0, 28, 0, 0, 0, 0, 0, 0r'16', 7, 0s'BIROD5A', 15, 0s'Gramlich&Benson' } # Interrupt masks: global interrupt_enable bit global interrupt_pending bit global alternate bit global receiving bit # Various masks (low order two bits only): global inputs byte global enable byte global complement byte global falling byte global high byte global low byte global raising byte # For now put all the smaller routines first so that they can live # within the first 256 bytes of main memory. The PIC12C5xx chips # can only call routines that are within the first 256 bytes (i.e. # the first half) of the code page. bank 1 procedure get_byte { arguments_none returns byte # Wait for a character and return it. # The get_byte() procedure only waits for 9-2/3 bits. That # way the next call to get_byte() will sychronize on the start # bit instead of possibly starting a little later. variable count byte variable char byte # Wait for 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: 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: serial_out := 0 call delay() call delay() call delay() # 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: # 1 cycle to close out previous loop: nop 1 # 1 cylce: 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 0 procedure delay { arguments_none returns_nothing uniform_delay delay_instructions # Delay 1/3 of a bit: variable changed byte variable current byte variable previous byte variable not_current byte variable counter1 byte variable counter2 byte variable counter3 byte # Kick the dog: watch_dog_reset # We need to pulse OUTn low for 56ms; read INn; and restore OUTn high # for 3ms. 1/3 of a bit time is 1/(3*2400) = 138us. 56ms/138ms = 406 # ticks. 3ms/138us = 7.2ticks which is rounded up to 8 ticks. Round # 406 up to 408 so it is divisible by 8 (408/8=51). counter1 := counter1 - 1 if (z) { counter1 := 8 counter2 := counter2 + 1 if (counter2 = 1) { # Trigger the appropriate enabled IROD's: counter3 := counter3 + 1 if (enable@0 && (!alternate || alternate && counter3@0)) { out0 := 0 } if (enable@1 && (!alternate || alternate && !(counter3@0))) { out1 := 0 } } else_if (counter2 = 51) { # Approximately 56ms have elpased; # Time to read data and reset any triggers: # Note: the compiler is generating pretty awful code # for th next couple of statements. It works, but it # is not pretty. if (!out0) { inputs@0 := !in0 out0 := 1 } if (!out1) { inputs@1 := !in1 out1 := 1 } # Now wait an additional 3ms before triggering again: } else_if (counter2 = 52) { # The 3ms have elpased, next time we trigger again: inputs := inputs & 3 if (!(enable@0)) { inputs@0 := 0 } if (!(enable@1)) { inputs@1 := 0 } counter2 := 0 } } else { # Compute the interrupts: previous := current current := (inputs ^ complement) & 3 not_current := current ^ 3 changed := current ^ previous if ((low & not_current) | (high & current) | (changed & current & raising) | (changed & previous & falling) != 0) { interrupt_pending := 1 } # Send an interrupt if interrupts are enabled: if (interrupt_pending && interrupt_enable) { # Shove serial out to low: serial_out := 0 interrupt_enable := 0 } } } # The main routine can span the 256 byte boundary at 0x300: origin 0x200 bank 1 procedure main { arguments_none returns_nothing variable command byte variable glitch byte variable id_index byte variable result byte # Let ROOT be http://web.gramlich.com/projects/robobricks # For BIROD2 specific commands see: # ROOT/birod2/rev_a/index.html # For shared commands see: # ROOT/specifications.html#Software_Protocol # For shared interrupt commands see: # ROOT/specifications.html#Interrupts # Initialize output pins: serial_out := 1 out0 := 1 out1 := 1 # Initialize everything else: interrupt_enable := 0 interrupt_pending := 0 alternate := 0 enable := 3 complement := 0 falling := 0 high := 0 low := 0 raising := 0 glitch := 0 id_index := 0 # Infinite command loop: loop_forever { # Wait for command: command := get_byte() # Dispatch on command: switch (command >> 6) { case 0 { # Command = 00xx xxxx: switch (command >> 3) { case 0 { # Command = 0000 0xxx: switch (command & 7) { case 0 { # Read Inputs (Command = 0000 0000): call send_byte((inputs ^ complement) & 0xf) } case 1 { # Read Raw (Command = 0000 0001): call send_byte(inputs) } case 2 { # Read Alternate (Command = 0000 0010): if (alternate) { call send_byte(1) } else { call send_byte(0) } } case 3 { # Read Enable Complement Masks (Command = 0000 0011): call send_byte((enable << 2) | complement) } case 4 { # Read High Low Masks (Command = 0000 0100): call send_byte((high << 2) | low) } case 5 { # Read Raising Falling Masks (Command = 0000 0101): call send_byte((raising << 2) | falling) } case 6, 7 { # Set alternate bit (Command = 0000 011a): alternate := command@0 } } } case 1 { # Undefinded command; do nothing: } case 2, 3 { # Set Enable Complement Masks (Command = 0001 eecc): enable := (command >> 2) & 3 complement := command & 3 } case 4, 5 { # Set High Low Masks (Command = 0010 hhll): high := (command >> 2) & 3 low := command & 3 } case 6, 7 { # Set Raising Falling Mask (Command = 0011 rrff): raising := (command >> 2) & 3 falling := command & 3 } } } case 1, 2 { # Do nothing (Command = 01xx xxxx or 10xx xxxx): } case 3 { # Command = 11xx xxxx: switch ((command >> 3) & 7) { case 5 { # Command = 1110 1xxx: if ((command & 7) = 7) { # Return Interrupt Bits (Command = 1110 1111): result := 0 if (interrupt_enable) { result := result | 2 } if (interrupt_pending) { result := result | 1 } call send_byte(result) } } case 6 { # Shared Interrupt commands (Command = 1111 0xxx): switch (command & 7) { case 0, 1, 2, 3 { # Set interrupt enable and pending (Command = 1111 00ep): interrupt_enable := command@1 interrupt_pending := command@0 } case 4, 5 { # Set Interrupt pending bit only (Command = 1111 010p): interrupt_pending := command@0 } case 6, 7 { # Set Interrupt pending bit only (Command = 1111 011e): interrupt_enable := command@0 } } } case 7 { # Shared commands. switch (command & 7) { case 0 { # Clock Decrement (Command = 1111 1000): osccal := osccal - osccal_unit } case 1 { # Clock Increment (Command = 1111 1001): osccal := osccal + osccal_unit } 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): if (id_index >= id.size) { id_index := 0 } call send_byte(id[id_index]) id_index := id_index + 1 if (id_index >= id.size) { id_index := 0 } } case 5 { # ID Reset (Command = 1111 1101): id_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 } } } } } } } } }