# ############################################################################# # # 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 LED4 RoboBrick. Basically # it just waits for commands that come in at 2400 baud and responds # to them. See: # # http://web.gramlich.net/projects/robobricks/led4/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 # The 509 has 4 bits of OSCCAL and the 509A has 6 bits. #constant osccal_lsb 0x10 constant osccal_lsb 0x4 # Define pin assignments and directions: port porta a bits_and_byte read_write_static pin led0 porta 0 write_only pin led1 porta 1 write_only pin led2 porta 2 write_only pin serial_in porta 3 read_only pin led3 porta 4 write_only pin serial_out porta 5 write_only string_constants { id = 1, 0, 8, 0, 0, 0, 0, 0, 0r'16', 5, 0s'LED4B', 15, 0s'Gramlich&Benson' rate_to_mask = 0xff, 0x80, 0x40, 0x20, 0x10, 8, 4, 2, 1 bit_to_mask = 1, 2, 4, 8 } global leds_mask byte global blink_masks[4] 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 bite, 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: while (serial_in) { call delay() } # Skip over start bit: call delay() call delay() call delay() # Sample in the middle third of each data bit: char := 0 nop extra_instructions_per_bit - 6 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 for test and jump 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 # Send the start bit: # 1 cycle: serial_out := 0 call delay() call delay() call delay() # 2 cycles for loop setup: # 2+1 = 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 for test and jump at end of loop: # 4+2+3 = 9 = no NOP's needed: } # Send the stop bit: nop 1 serial_out := 1 call delay() call delay() call delay() nop extra_instructions_per_bit - 5 } procedure delay { arguments_none returns_nothing uniform_delay delay_instructions # This procedure delays 1/3 of a bit. variable blink byte variable high byte variable low byte # This procedure is called 7200 times a second. We want to # slow the fastest blink rate down to something more manageable, # like 4 times a second. # Kick the dog: watch_dog_reset # Slow the blink rate down: low := low + 1 if (z) { high := high + 1 # 7200/256 ~= 28; 28/4 = 7 => 4 blinks a second for fastest blink rate: if (high > 2) { high := 0 blink := blink + 1 # We never let the blink mask go to all zeros because the way # we indicate that an LED is to stay on alwayas is that we set # its blink mask to all one's. If the blink variable ever goes # to all zeros, there would be a small glitch for LED's that # are supposed to be always on. Hence we skip over a value of 0. if (z) { blink := blink + 1 } } } if (leds_mask@0) { if (blink & blink_masks[0] != 0) { led0 := 0 } else { led0 := 1 } } else { led0 := 1 } if (leds_mask@1) { if (blink & blink_masks[1] != 0) { led1 := 0 } else { led1 := 1 } } else { led1 := 1 } if (leds_mask@2) { if (blink & blink_masks[2] != 0) { led2 := 0 } else { led2 := 1 } } else { led2 := 1 } if (leds_mask@3) { if (blink & blink_masks[3] != 0) { led3 := 0 } else { led3 := 1 } } else { led3 := 1 } } origin 0x200 # 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 glitch byte variable index byte variable mask byte variable rate byte variable result byte # Initialize blink_masks: count_down index 4 { blink_masks[index - 1] := 0xff } # Initialize remaining registers: serial_out := 1 glitch := 0 index := 0 leds_mask := 0 # 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, 1 { # Write All (Command = 0000 abcd): leds_mask := command } case 2 { # Command = 0001 0xxx: mask := bit_to_mask[command & 3] if (command@2) { # Bit set (Command = 0001 01bb): leds_mask := leds_mask | mask } else { # Bit Clear (Command = 0001 00bb): leds_mask := leds_mask & (0xff ^ mask) } } case 3 { # Command = 0001 1xxx: mask := bit_to_mask[command & 3] if (command@2) { # Bit Read (Command = 0001 11bb): # Compute bit rate in {result}: result := 0 mask := blink_masks[command & 3] while (rate_to_mask[result] != mask) { result := result + 1 } # Form the final answer: result := result << 1 if (leds_mask & bit_to_mask[command & 3] != 0) { result := result + 1 } # Send it: call send_byte(result) } else { # Bit Toggle (Command = 0001 10bb): leds_mask := leds_mask ^ mask } } case 4, 5, 6, 7 { # Blink Rate Set (Command = 001r rrbb): blink_masks[command & 3] := rate_to_mask[(command >> 2) & 7] } } } case 1 { # (Command = 01xx xxxx): switch ((command >> 3) & 7) { case 0 { switch (command & 7) { case 0 { # Read all (Command = 0100 0000): call send_byte(leds_mask & 0xf) } case 1, 2, 3 { # Do nothing: } case 4, 5, 6, 7 { # Increment LED's (Command = 0100 01bb): leds_mask := (leds_mask + bit_to_mask[command & 3]) & 0xf } } } case 1 { if (command@2) { # Command = 0100 1100: } else { # Decrement LED's (Command = 0100 10bb): leds_mask := (leds_mask - bit_to_mask[command & 3]) & 0xf } } default 7 { # Do nothing: } } } case 2 { # Do nothing (Command = 10xx xxx): } case 3 { # (Command = 11xx xxxx): if ((command >> 3) & 7 = 7) { # Command = 1111 1xxx: 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 } } } } } } } }