# ############################################################################# # # 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 # 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 pin assignments and directions: port portb b bits_and_byte read_write_static port portc c bits_and_byte write_only pin led9 portb 0 write_only pin led8 portb 1 write_only pin led7 portc 0 write_only pin led6 portc 1 write_only pin led5 portc 2 write_only pin led4 portc 3 write_only pin led3 portc 4 write_only pin led2 portc 5 write_only pin led1 portb 4 write_only pin led0 portb 5 write_only pin serial_out portb 2 write_only pin serial_in portb 3 read_only string_constants { id = 1, 0, 13, 0, 0, 0, 0, 0, 0r'16', 6, 0s'Out10A', 15, 0s'Gramlich&Benson' bit_to_mask = 0xff, 0x40, 0x20, 0x10 ,8, 4, 2, 1 } constant leds_count 10 bank 0 global leds_mask_low byte global leds_mask_high byte global blink_masks[leds_count] 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. bank 2 procedure mask_to_bit { argument mask byte returns byte variable bit byte if (mask = 0xff) { return 7 } bit := 0 mask := mask | 0x80 while (!(mask@0)) { mask := mask >> 1 bit := bit + 1 } return bit } 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: 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 count_down count 8 { call delay() char := char >> 1 if (serial_in) { char := char | 0x80 } call delay() call delay() } # 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: serial_out := 0 call delay() call delay() call delay() # Send the data: count_down count 8 { serial_out := char@0 char := char >> 1 call delay() call delay() call delay() } # Send the stop bit: serial_out := 1 call delay() call delay() call delay() } bank 0 procedure delay { arguments_none returns_nothing uniform_delay instructions_per_delay # 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; for fastest blink rate: # 28/7 = 4 => 4 blinks a second, or # 28/4 = 7 => 7 blinks a second 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 always 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_low@0) { # led0 := 0 if (blink & blink_masks[0] != 0) { led0 := 0 } else { led0 := 1 } } else { led0 := 1 } if (leds_mask_low@1) { if (blink & blink_masks[1] != 0) { led1 := 0 } else { led1 := 1 } } else { led1 := 1 } if (leds_mask_low@2) { if (blink & blink_masks[2] != 0) { led2 := 0 } else { led2 := 1 } } else { led2 := 1 } if (leds_mask_low@3) { if (blink & blink_masks[3] != 0) { led3 := 0 } else { led3 := 1 } } else { led3 := 1 } if (leds_mask_low@4) { if (blink & blink_masks[4] != 0) { led4 := 0 } else { led4 := 1 } } else { led4 := 1 } if (leds_mask_high@0) { if (blink & blink_masks[5] != 0) { led5 := 0 } else { led5 := 1 } } else { led5 := 1 } if (leds_mask_high@1) { if (blink & blink_masks[6] != 0) { led6 := 0 } else { led6 := 1 } } else { led6 := 1 } if (leds_mask_high@2) { if (blink & blink_masks[7] != 0) { led7 := 0 } else { led7 := 1 } } else { led7 := 1 } if (leds_mask_high@3) { if (blink & blink_masks[8] != 0) { led8 := 0 } else { led8 := 1 } } else { led8 := 1 } if (leds_mask_high@4) { if (blink & blink_masks[9] != 0) { led9 := 0 } else { led9 := 1 } } else { led9 := 1 } } 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 ledx byte variable command byte variable data byte variable glitch byte variable index byte variable mask byte variable rate byte variable result byte # Initialize blink_masks: count_down index leds_count { blink_masks[index - 1] := 0xff } # Initialize remaining registers: glitch := 0 index := 0 leds_mask_low := 0 leds_mask_high := 0 # Process commands: loop_forever { # Wait for command: command := get_byte() # Dispatch on command: switch (command >> 6) { # xx = 00: write upper and write lower commands case 0 { # (Command = 00xx xxxx): if (command@5) { # Write Upper (Command = 001a bcde): leds_mask_high := command & 0x1f } else { # Write Lower (Command = 000a bcde): leds_mask_low := command } } # xx = 01: Bit operations - clear, set, toggle & read case 1 { # Bit commands:(Command = 01cc bbbb): ledx := command & 0xf data := leds_mask_low if (ledx >= 5) { ledx := ledx - 5 data := leds_mask_high } # Compute the mask: mask := 1 while (ledx != 0) { mask := mask << 1 ledx := ledx - 1 } ledx := command & 0xf switch ((command >> 4) & 3) { case 0 { # Bit Clear (Command = 0100 bbbb): data := data & (0xff ^ mask) } case 1 { # Bit Set (Command = 0100 bbbb): data := data | mask } case 2 { # Bit Toggle (Command = 0100 bbbb): data := data ^ mask } case 3 { # Bit Read (Command = 0100 bbbb): result := (mask_to_bit(blink_masks[ledx]) ^ 7) << 5 if (data & mask != 0) { result := result + 1 } call send_byte(result) } } # Stuff the data back: if (ledx < 5) { leds_mask_low := data } else { leds_mask_high := data } } case 2 { # Do nothing (Command = 10xx xxx): switch ((command >> 4) & 3) { case 0 { # Command = 1000 xxxx: switch ((command >> 2) & 3) { case 0 { # Command = 1000 00xx: switch (command & 3) { case 0 { # Read All (Command = 1000 0000): call send_byte(leds_mask_high) call send_byte(leds_mask_low) } case 1 { # Read Lower (Command = 1000 0001): call send_byte(leds_mask_low) } # xx = 10: read-all, lower, upper; blink rate; inc/dec leds;pwr mode case 2 { # Read Upper (Command = 1000 0010): call send_byte(leds_mask_high) } # xx = 11: shared commands case 3 { # Blink Rate Set (Command = 1000 0011): command := get_byte() ledx := command & 0xf if (ledx >= 10) { ledx := 0 } blink_masks[ledx] := bit_to_mask[command >> 5] } } } default 3 { # Do nothing: } } } case 1 { # Increment LED's (Command = 1001 bbbb): ledx := command & 0xf mask := 1 if (ledx < 5) { while (ledx != 0) { mask := mask << 1 ledx := ledx - 1 } leds_mask_low := leds_mask_low + mask if (leds_mask_low@5) { leds_mask_low := leds_mask_low & 0x1f leds_mask_high := leds_mask_high + 1 } } else { ledx := ledx - 5 while (ledx != 0) { mask := mask << 1 ledx := ledx - 1 } leds_mask_high := leds_mask_high + mask } leds_mask_high := leds_mask_high & 0x1f } case 2 { # Decrement LED's (Command = 1010 bbbb): ledx := command & 0xf mask := 1 if (ledx < 5) { while (ledx != 0) { mask := mask << 1 ledx := ledx -1 } leds_mask_low := leds_mask_low - mask if (leds_mask_low@5) { leds_mask_low := leds_mask_low & 0x1f leds_mask_high := leds_mask_high - 1 } } else { ledx := ledx - 5 while (ledx != 0) { mask := mask << 1 ledx := ledx -1 } leds_mask_high := leds_mask_high - mask } leds_mask_high := leds_mask_high & 0x1f } case 3 { # Command = 1011 llll: # Do nothing: } } } 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 } } } } } } } }