# ############################################################################# # # 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 AnalogIn4 RoboBrick. Basically # it just waits for commands that come in at 2400 baud and responds # to them. See # # http://web.gramlich.net/projects/robobricks/analogin4/index.html # # for more details. # # ############################################################################# processor pic12ce674 cp=off pwrte=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 # Oscillator Mask: register osccal 0x8f constant osccal_unit 0x10 # Analog to digital conversion result register: register addres 0x1e # Analog to digital conversion register 0: register addcon0 0x1f bind adon addcon0@0 bind go_done addcon0@2 bind chs0 addcon0@3 bind chs1 addcon0@4 bind adcs0 addcon0@6 bind adcs1 addcon0@7 # Interrupt Control Register: register intcon 0xb bind gpif intcon@0 bind intf intcon@1 bind toif intcon@2 bind gpie intcon@3 bind inte intcon@4 bind toie intcon@5 bind peie intcon@6 bind gie intcon@7 register pir1 0xc bind adif pir1@6 register pie1 0x8c bind adie pie1@6 # Analog to digital conversion register 1: register addcon1 0x9f bind pcfg0 addcon1@0 bind pcfg1 addcon1@1 bind pcfg2 addcon1@2 constant ain_bit0 0 constant ain_bit1 1 constant ain_bit2 2 constant serial_in_bit 3 constant ain_bit3 4 constant serial_out_bit 5 constant ain_mask0 (1 << ain_bit0) constant ain_mask1 (1 << ain_bit1) constant ain_mask2 (1 << ain_bit2) constant ain_mask3 (1 << ain_bit3) constant serial_in_mask (1 << serial_in_bit) constant serial_out_mask (1 << serial_out_bit) constant io_mask 0xf constant ain_mask (ain_mask0 | ain_mask1 | ain_mask2 | ain_mask3) constant serial_mask (serial_in_mask | serial_out_mask) # define port bit assignments port porta a bits_and_byte read_write_static pin ain0 porta ain_bit0 read_only pin ain1 porta ain_bit1 read_only pin ain2 porta ain_bit2 read_only pin ain3 porta ain_bit3 read_only pin serial_in porta serial_in_bit read_only pin serial_out porta serial_out_bit write_only constant analogs_size 4 string_constants { id = 1, 0, 12, 1, 0, 0, 0, 0, 0r'16', 10, 0s'AnalogIn4B', 15, 0s'Gramlich&Benson' } bank 0 global analogs[analogs_size] byte global thresholds_low[analogs_size] byte global thresholds_high[analogs_size] byte global inputs byte global complement byte global glitch byte global id_index byte global vref_mode bit # Interrupt masks: global interrupt_enable bit global interrupt_pending bit global receiving bit global falling byte global high byte global low byte global raising byte procedure main { arguments_none returns_nothing variable bit byte variable command byte variable temporary byte # Initialize the A/D module: # Select all 4 inputs as A/D: addcon1 := 0 # A/D Conversion clock is Fosc/8 (Tad=2uS) and AD is on: addcon0 := 0x41 adif := 0 adie := 0 gie := 0 call reset() # Set the direction: loop_forever { # Wait for a 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, 1, 2, 3 { # Read Pin (Command = 0000 00bb): call send_byte(analogs[command]) } case 4 { # Read Binary Values (Command = 0000 0100): call send_byte(inputs ^ complement) } case 5 { # Read Raw Binary (Command = 0000 0101): call send_byte(inputs) } case 6 { # Reset (Command = 0000 0110): call reset() } default 7 { # Undefined command: } } } case 1 { # Command = 0000 1xxx: switch (command & 7) { case 0 { # Read Complement Mask(Command = 0000 1000): call send_byte(complement) } case 1 { # Read High Mask (Command = 0000 1001): call send_byte(high) } case 2 { # Read Low Mask (Command = 0000 1010): call send_byte(low) } case 3 { # Read Raising Mask (Command = 0000 1011): call send_byte(raising) } case 4 { # Read Falling Mask (Command = 0000 1100): call send_byte(falling) } case 5 { # Read Vref Mode (Command = 0000 1101): temporary := 0 if (vref_mode) { temporary@0 := 1 } call send_byte(temporary) } case 6, 7 { # Set Vref Mode (Command = 0000 111v): temporary := 0 vref_mode := 0 if (command@0) { vref_mode := 1 temporary := 1 } addcon1 := temporary } } } case 2, 3 { # Command = 0001 xxxx: bit := command & 3 switch ((command >> 2) & 3) { case 0 { # Read High Threshold (Command = 0001 00bb): call send_byte(thresholds_high[bit]) } case 1 { # Read Low Threshold (Command = 0001 01bb): call send_byte(thresholds_low[bit]) } case 2 { # Set High Threshold (Command = 0001 10bb): thresholds_high[bit] := get_byte() } case 3 { # Set Low Threshold (Command = 0001 11bb): thresholds_low[bit] := get_byte() } } } case 4, 5 { # Set Complement Mask (Command = 0010 cccc): complement := command & io_mask } default 7 { # Do nothing: } } } case 1 { # Command = 01xx xxxx: temporary := command & io_mask # Kludge: to get switch so it does not span 256 byte boundary: nop 3 switch ((command >> 4) & 3) { case 0 { # Set High Mask (Command = 0100 hhhh): high := temporary } case 1 { # Set Low Mask (Command = 0101 llll): low := temporary } case 2 { # Set Raising Mask (Command = 0110 rrrr): raising := temporary } case 3 { # Set Falling Mask (Command = 0111 ffff): falling := temporary } } } case 2 { # Do nothing (Command = 10xx xxxx): } case 3 { # Command = 11xx xxxx: switch ((command >> 3) & 7) { case 0, 1, 2, 3, 4 { # Command = 1100 xxxx or 1110 0xxx: # Do nothing: } case 5 { # Read Interrupt Bits (Command = 1110 1111): if ((command & 7) = 7) { # Return Interrupt Bits: temporary := 0 if (interrupt_enable) { temporary@1 := 1 } if (interrupt_pending) { temporary@0 := 1 } call send_byte(temporary) } } case 6 { # Shared Interrupt commands (Command = 1111 0xxx): switch (command & 7) { case 0, 1, 2, 3 { # Set interrupt bits (Command = 1111 10ep): interrupt_enable := command@1 interrupt_pending := command@0 } case 4, 5 { # Set Interrupt Pending (Command = 1111 110p): interrupt_pending := command@0 } case 6, 7 { # Set Interrupt Enable (Command = 1110 111e): interrupt_enable := command@0 } } } case 7 { # Shared commands (Command = 1111 1xxx): 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): 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 } } } } } } } } } procedure delay { arguments_none returns_nothing uniform_delay delay_instructions # This procedure will delay for one third of a bit time. variable channel byte variable current byte variable changed byte variable previous byte variable not_current byte variable counter byte variable mask byte variable result byte # Kick the dog: watch_dog_reset channel := (counter >> 1) & 3 counter := counter + 1 if (counter@0) { # Set up and wait for acquistion: addcon0 := 0x41 | (channel << 3) & 0x18 # Setup for interrupts: previous := current # Read the I/O port once: current := inputs ^ complement not_current := current ^ 0xf changed := current ^ previous # See about triggering the interrupt_pending flag: if ((low & not_current) | (high & current) | (changed & current & raising) | (changed & not_current & falling) != 0) { interrupt_pending := 1 } # Send an interrupt if interrupts are enabled: if (interrupt_pending && interrupt_enable) { # Shove serial out to low: interrupt_enable := 0 serial_out := 0 } } else { # Start the conversion: go_done := 1 mask := 0 if (channel@1) { if (channel@0) { mask@3 := 1 } else { mask@2 := 1 } } else { if (channel@0) { mask@1 := 1 } else { mask@0 := 1 } } # Delaying 60uS is way longer than necessary for the conversion # to complete. nop 60 result := addres analogs[channel] := result if (result <= thresholds_low[channel]) { inputs := inputs & (mask ^ io_mask) } if (result >= thresholds_high[channel]) { inputs := inputs | mask } inputs := inputs & io_mask } } 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 interrupts and interrupt pending: # 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 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 for loop setup: # 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: # 1 cycle to close out previous loop: 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 } procedure reset { arguments_none returns_nothing # This procedure will initialize all of the registers: variable index byte inputs := 0 high := 0 low := 0 raising := 0 falling := 0 complement := 0 interrupt_enable := 0 interrupt_pending := 0 index := 0 while (index < 4) { thresholds_high[index] := 0xc0 thresholds_low[index] := 0x40 index := index + 1 } glitch := 0 id_index := 0 serial_out := 1 vref_mode := 0 addcon1 := 0 }