ucl 2.0 # Copyright (c) 2006-2012 by Wayne C. Gramlich # All rights reserved. # This code implements the firmware for the MidiMotor1E. # The MidiMotor1E differs from the MidiMotor1 in that the '1E has # two extra connectors N2 and N4. N2 is a 5 pin connector that can # be used to read in two channels of quardarature position (plus an # an index pulse.) Alternatively, N2 can be used to read in an # analog signal between 0 and 5V for an absolute position. N4 is # used as an active low limit switch that can be used to term off # the motor. # # The architecture of this firmware is actually pretty complicated. # The pulse width modulation is controlled by the Timer0 and Timer1 # modules. The quadrature encoding and limit switch is delt with # via some pins on Port A. The UART is responsible for dealing # with RoboBricks2 bus I/O. The A/D converter is responsible # for processing Analog to digital conversions. The code in the # main loop is responsible for running a PID (Proportional, # Integral, Derivative) motor control loop. The PID algorithm is # done using 32-bit floating point arithmetic. Heavy use of interrupts # is used to ensure that everything works in a timely fashion. # # There are three main routines -- interrupt(), main() and uart_manage(). # The interrupt() routine is responsible for processing all interrupt # events. UART processing is sufficiently complicated, that UART input # is quickly stuffed into a temporary buffer, to minimize intterupt # latency. Further UART process is handled by the uart_manage() routine. # Calls to the uart_manage() routine are sprinkled throught the main # loop in main() to ensure that any buffered uart commands are processed # fairly quickly. # We use the PIC16F688 for this module: library $pic16f688 # The module runs at 20MHz: library clock20mhz # The {$eusart} module needs the following 2 constants defined # before inclusion: constant $eusart_clock = clock_rate constant $eusart_factor = 4 library $eusart constant microsecond = 5 library rb2_constants # Constants used by firmware and clients # The module uses a 20MHz resonator; thus, the oscillator mode is HS: configure fosc=hs # Besides the bus RX/TX lines, only 4 other lines are used to # control the motor -- RC<0:3>: package pdip pin 1 = power_supply pin 2 = osc1 pin 3 = osc2 pin 4 = ra3_nc pin 5 = rx, name=rx, bit=rx_bit pin 6 = tx, name=tx, bit=tx_bit pin 7 = rc3_nc pin 8 = rc2_nc pin 9 = rc1_out, name=m1b, mask=m1b_mask, bit=m1b_bit pin 10 = rc0_out, name=m1a, mask=m1a_mask, bit=m1a_bit pin 11 = an2 pin 12 = ra1_in, name=quad_a, mask=quad_a_mask, bit=quad_a_bit pin 13 = ra0_in, name=quad_b, mask=quad_b_mask, bit=quad_b_bit pin 14 = ground # All of the 24-bit signed file24 in the 24-bit register file are stored # in the {highs}, {middles} and {lows} arrays: constant position_index = 0 # Current position constant target_index = 1 # Target position constant divisor_index = 2 # Denominator for {kp}, {ki}, and {kd} constant kp_index = 3 # Numerator for {kp} constant ki_index = 4 # Numerator for {ki} constant kd_index = 5 # Numerator for {kd} constant ad0_value_index = 6 # AD0 constant ad1_value_index = 7 # AD1 constant ad0_limit_index = 8 # AD0 constant ad1_limit_index = 9 # AD1 constant file24_size = 10 # 24-bit register file has 6 registers # We are short of bytes in data bank 0, so we stuff the 24-bit register # files off into data bank 2: data_bank 2 global highs[file24_size] array[byte] # All the highs are grouped together global middles[file24_size] array[byte] # All the middles are grouped together global lows[file24_size] array[byte] # All the lows are grouped together # The quardarture is actually the same as the position: bind quad_high = highs[position_index] bind quad_middle = middles[position_index] bind quad_low = lows[position_index] global quad_state byte # The quadrature state bind ad0_value_high = highs[ad0_value_index] bind ad0_value_middle = middles[ad0_value_index] bind ad0_value_low = lows[ad0_value_index] bind ad1_value_high = highs[ad1_value_index] bind ad1_value_middle = middles[ad1_value_index] bind ad1_value_low = lows[ad1_value_index] bind ad0_limit_high = highs[ad0_limit_index] bind ad0_limit_middle = middles[ad0_limit_index] bind ad0_limit_low = lows[ad0_limit_index] bind ad1_limit_high = highs[ad1_limit_index] bind ad1_limit_middle = middles[ad1_limit_index] bind ad1_limit_low = lows[ad1_limit_index] # Temporarily removed to free up some space: #global xstates[32] array[byte] # Every else with the 24-bit register file24 is in data bank 0: data_bank 0 global file24_changed byte # 1 bit for each value to mark change global file24_zero byte # 1 bit for each value set to zero # A number of places need a temporary for high, middle and low. Rather # than chew up scarse memory, we share them all with globals. As of now, # these bytes are used in {file24_float_convert}() and {uart_manage}(): global high byte # Temporary high byte global middle byte # Temporary middle byte global low byte # Temporary low byte # The 8-bit register file is stored in {file8} and indexed using the # indices below: constant configure_index = 0 # Configuration byte index constant status_index = 1 # Status byte index constant speed_index = 2 # Current motor speed index constant errors_index = 3 # Error count constant deadband_index = 4 # Deadband for error constant minimum_index = 5 # Motor (non-zero) speed minimum constant maximum_index = 6 # Motor speed maximum constant file24_index_index = 7 # Index into 24-bit register file constant file8_size = 8 # We have 8 registers in {file8] # These are the configure bits numbers: constant configure_motor_reverse_bit = 0 # Motor reverse bit constant configure_position_reverse_bit = 1 # Position sensor reverse bit constant configure_quadrature_bit = 2 # Quadrature disable bit constant configure_lock_bit = 3 # Lock configuration registers constant configure_analog1_reverse_bit = 4 # Reverse analog1 sense direction constant configure_analog1_enable_bit = 5 # Reverse analog1 enable constant configure_analog0_reverse_bit = 6 # Reverse analog0 sense direction constant configure_analog0_enable_bit = 7 # Reverse analog0 enable # Associated analog masks: constant configure_motor_reverse_mask = 1 << configure_motor_reverse_bit constant configure_position_reverse_mask = 1 << configure_position_reverse_bit constant configure_quadrature_mask = 1 << configure_quadrature_bit constant configure_analog1_reverse_mask = 1 << configure_analog1_reverse_bit constant configure_analog1_enable_mask = 1 << configure_analog1_enable_bit constant configure_analog0_reverse_mask = 1 << configure_analog0_reverse_bit constant configure_analog0_enable_mask = 1 << configure_analog0_enable_bit constant configure_analogs_enabled_mask = configure_analog0_enable_mask | configure_analog1_enable_mask # Here is the actual array of {file8}: global file8[file8_size] array[byte] # It is a hassle to always index into {file8}, we use binds to make # the code more readable: bind configure = file8[configure_index] bind motor_reverse = configure@configure_motor_reverse_bit bind position_reverse = configure@configure_position_reverse_bit bind use_potentiometer = configure@configure_quadrature_bit bind configure_lock = configure@configure_lock_bit bind ad0_reverse = configure@configure_analog0_reverse_bit bind ad0_enable = configure@configure_analog0_enable_bit bind ad1_reverse = configure@configure_analog0_reverse_bit bind ad1_enable = configure@configure_analog1_enable_bit bind status = file8[status_index] bind speed = file8[speed_index] bind errors = file8[errors_index] bind deadband = file8[deadband_index] bind minimum = file8[minimum_index] bind maximum = file8[maximum_index] bind file24_index = file8[file24_index_index] # These are the variables used to control motor1: global motor1_speed byte global motor1_on_mask byte global motor1_pulse_width byte # UART Globals and constants: global state byte # State machine for command parsing constant state_select_wait = 0 # Waiting for a select address constant state_echo_then_command = 1 # Wait for echo then a command constant state_command = 2 # Wait for a command constant state_file24_full_get1 = 3 constant state_file24_full_get2 = 4 constant state_file24_full_set1 = 5 constant state_file24_full_set2 = 6 constant state_file24_full_set3 = 7 constant state_value_low_set1 = 8 constant state_value_middle_set1 = 9 constant state_value_high_set1 = 10 constant state_file8_set1 = 11 constant state_file8_set2 = 12 constant state_file8_set3 = 13 constant state_address_set1 = 14 constant state_address_set2 = 15 constant state_address_set3 = 16 constant state_address_set4 = 17 constant state_probe_get1 = 18 constant state_probe_get2 = 19 global id_index byte # Index into id string # UART data input buffer: constant uart_input_size = 4 # Buffer size (=power of 2) constant uart_input_mask = uart_input_size - 1 # Mask for index global uart_input[uart_input_size] array[byte] # Buffer for input global uart_input_in_index byte # Next index to insert into global uart_input_out_index byte # Next index to remove from global uart_input_count byte # Bytes in buffer global uart_input_pending bit # 1=>data is pending; 0=>no data global address byte # Address of this module global new_address byte # New address of this module global channel byte # A/D channel # Load up floating point library in code bank 1 using datab bank 0: library_bank 1 data_bank 1 library $float32 data_bank 0 origin 0 procedure start arguments_none returns_nothing return_suppress assemble databank start,main codebank start,main goto main origin 4 procedure interrupt arguments_none returns_nothing # This is the interrupt processing routine. While it is quite long # it actually does not take very long to execute. This is important # since long interrupt routines can cause problems with over module # latency. # Step1: Save our system registers. There are 4 registers that # need to be saved -- W, STATUS, FSR, and PCLATH. # # W & STATUS: # The compiler takes care of the weird code needed to safely # save W adn STATUS registers. (Read the generated assembly code # comments to see some strange usage of the SWAPF instruction.) # Since the microcontroller can be using any of the 4 register # banks when the interrupt occurs, the W and STATUS register # are stored into a couple of shared memory bank registers. # There are 16 of these registers available, and the interupt # takes 2 of the 16. # # PCLATH: # When the interrupt occurs, the previous progam counter is # pushed onto the stack and then the program counter is set # to 4 to force execution of the interrupt routine. The PCLATH # register is left unchanged. Since the PCLATH register can # pointing to any of the 4 code banks (only 2 implemented for # the PIC16F688), it is vital that we update PCLATH to point # to code bank 0 (where the interrupt routine is) before the # first GOTO, CALL, or computed GOTO. Otherwise, the first # GOTO, CALL, or computed GOTO will jump off into code bank # that was being used prior to the interrupt. # # FSR: # The FSR register needs to be saved since we using code that # indexes into a small buffer to store UART input. # # Both PCLATH and FSR can be saved into regular registers in # register bank 0. No shared registers are needed. Once we have # STATUS stashed away, the register bank can be set to 0. The # compiler takes care of this automagically before the first save # assignment. local fsr_save byte local pclath_save byte fsr_save := $fsr pclath_save := $pclath # Make sure we are on correct page: $pclath := 0 # Now we have to deal with any of the pending interrupts. Timer0 # is set to go off at the overall duty cycle of the PWM (Pulse Width # Modulation) rate. We turn the motor "on" each time Timer0 triggers # an interrupt. We also start Timer1 to specifiy the over width of # the pulse before we turn it off. When Timer1 triggers, it is time # to turn the pulse off. Timer0 is an 8-bit register and it it is # being driven by a the system clock divided by 256. Thus, the Timer0 # triggers every 65536 (=2^16) clock ticks. Timer 1 is a 16-bit # counter that runs at the system clock. We always set the low # order byte to 0, and the high order byte to W, where W=255-PW and # PW is the pulse width as a number between 0 and 255. Thus, we can # have the pulse width vary in increments of 1/256 of the total duty # cycle. # Deal with Timer 0 interrupt: if $t0if # Timer 0 just triggered. It is time to start a new pulse width # duty cycle: # Clear the interrupt flag: $t0if := $false # Make sure Timer1 is off while we muck around with Timer1: $tmr1on := $false # Set the pulse width to W::0, where W=255-PW, where PW={0,...,255}. $tmr1l := 0 $tmr1h := motor1_pulse_width # Make sure that we clear off the Timer1 interrupt flag, just in case: $tmr1if := $false # Turn on Timer1: $tmr1on := $true # Finally, turn on the appropriate leads of the motor to make it move. $portc := motor1_on_mask if use_potentiometer switch channel case 0 ad1_value_low := $adresl ad1_value_middle := $adresh ad1_value_high := 0 case 1 ad0_value_low := $adresl ad0_value_middle := $adresh ad0_value_high := 0 case 2 quad_low := $adresl quad_middle := $adresh quad_high := 0 if position_reverse # Perform a 1's complement of the result: quad_low := quad_low ^ 0xff quad_middle := quad_middle ^ 3 quad_low := quad_low + 1 if $z quad_middle := (quad_middle + 1) & 3 case_maximum 2 if configure & configure_analogs_enabled_mask = 0 channel := 2 else channel := channel + 1 if channel >= 3 channel := 0 # The other channels are enabled: $adcon0 := $adcon0 & 0xc3 | (channel << 2) delay 20 * microsecond do_nothing # Trigger an A/D conversion to pick up next time: $go := $true # Deal with Timer 1 interrupt: if $tmr1if # Timer 1 just triggered. It is time to turn off the pulse width. # Turn off the motor leads: $portc := 0 # Turn off Timer 1: $tmr1on := $false # Make sure we clear timer 1 interrupt flag: $tmr1if := $false # Deal with UART receive interrupt: if $rcif # We have a byte in the UART receive buffer. We want to be as # quick as possible here. Do the minimum to process the incoming # byte and move on.a # First, look at the ninth bit. If the ninth bit is set, we # process it immediately. if $rx9d # The ninth bit is set, we have an module address select # command: if $rcreg = address # The address that came in matches ours in {address}. # We need to prepare for further incoming data bytes: # Force the UART to start accepting data bytes with the # ninth bit clear: $adden := $false # Clear out the input data buffer: uart_input_in_index := 0 uart_input_out_index := 0 uart_input_count := 0 uart_input_pending := $false # Send an acknowledge byte: call uart_byte_put(rb2_ok) # Set the state machine for {uart_manage} to eat the echo # and start processing commands: state := state_echo_then_command else # The address that came in did not match our address: # Force the UART to only listen to bytes that have the # ninth bit set (i.e. address commands). All data bytes # to the other selected module will be invisible to us: $adden := $true # Set the state machine for {uart_manage} to ignore any # incoming data bytes. This should never happen, be it # it never hurts to be careful. state := state_select_wait else # We have a data byte for us to process. For now we # stuff it into an input buffer: # Stuff the byte into the {uart_input} buffer being careful # not to go out of bounds. The buffer is a power of 2 in # size, so masking {uart_input_in_index} with {uart_input_mask} # will keep us in bounds: uart_input[uart_input_in_index & uart_input_mask] := $rcreg # Bump {uart_input_in_index} so that we next byte the comes # in will follow this current byte: uart_input_in_index := uart_input_in_index + 1 # Keep track of how many bytes are in the input buffer: uart_input_count := uart_input_count + 1 # Let the {uart_manage} routine know that it has work to do: uart_input_pending := $true # The PIC UART can get wedged by errors. We fix that problem # by toggling {$cren} whenever we have an overrun error. The # code below is really tight (5 instructions): if $oerr # We have an over run error, clear {$cren}: $cren := $false if $ferr # We have a framing error, clear {$cren}: $cren := $false # If either of the two previous statements triggered, {$cren} # got turned off. This next statement will turn it on again. # Otherwise, {$cren} is already on, and this statement will do # nothing: $cren := $true # Clear the recieve interrupt condition: $rcif := $false # Deal with quadrature input changes: if $raif # The file24 a the A and B channels of Port A have changed # since the last time we read Port A. Deal with the change # by cycling the {quad_state} state machine. Please carefully # read the documentation in the states.ucl library to understand # the format of the {states} vector. # Cycle the {quad_state} state machine. Use only the lower 3 bits # currently in {quad_state} concatenated with the low order 2 bits # of {$porta} (the A and B quadrature channels). That gives a total # of 5 bits to work with: #FIXME: should be {xstates}!!! #quad_state := xstates[quad_state & 7 | ($porta << 3) & 0x18] quad_state := states[quad_state & 7 | ($porta << 3) & 0x18] # Disable interrupt condition until the next time: $raif := $false # The rest of this code just increments and decrements the quadrature # counter. The quad counter is signed 24 bit integer that is stored # in {quad_high}, {quad_middle}, and {quad_low}. This code is kind # big and long, but in reality, 99% of the time we will perform either # a single increment or decrement of {quad_low}. So, most of the # time this code very fast: if quad_state@7 # The state machine wants us to increment the quadrature counter # by 1. We work our way from {quad_low} through {quad_high}: quad_low := quad_low + 1 if $z # The zero status flag is set, so {quad_low} just wrapped from # 0xff to 0. Thus, we need to increment {quad_middle}" quad_middle := quad_middle + 1 if $z # The zero status flat is set, so {quad_middle} just # wrapped from 0xff to 0. Thus we need to increment # {quad_high}: quad_high := quad_high + 1 if quad_state@6 # This bit will be set in the state machine if we have an # "illegal" state machine transistion. # Bump the {errors} counter to let somebody know # that we are having problems: errors := errors + 1 # This code is the same as the first increment, so the # comments are not repeated: quad_low := quad_low + 1 if $z quad_middle := quad_middle + 1 if $z quad_high := quad_high + 1 if quad_state@5 # The state machine wants us to decrement the quadrature counter # by 1. We work our way from {quad_low} through {quad_high}: if quad_low = 0 # {quad_low} is 0, so we are going to wrap down to 0xff. # We need to decrement {quad_middle}: if quad_middle = 0 # {quad_middle} is 0, we are going to wrap down to 0xff. # We need to decrement {quad_high}: quad_high := quad_high - 1 # Do the {quad_middle} decrement: quad_middle := quad_middle - 1 # Do the {quad_low} decrement: quad_low := quad_low - 1 if quad_state@4 # This bit will be set in the state machine if we have an # "illegal" state machine transition: # Bump the {errors} counter to let somebody know that # we are having problems: errors := errors + 1 # This code is the same as the first decrement, so the # comments are not repeated: if quad_low = 0 if quad_middle = 0 quad_high := quad_high - 1 quad_middle := quad_middle - 1 quad_low := quad_low - 1 # All done processing interrupt conditions. Let's get out of here by # restoring {$fsr} and {$pclath}: $fsr := fsr_save $pclath := pclath_save # The compiler takes care of restoring {$status} and {$w}. # Split the floats between data banks 1 and 2: data_bank 2 global error float32 global position float32 global target float32 global kp float32 global ki float32 data_bank 1 global kd float32 global pid float32 global divisor float32 global numerator float32 global previous_error float32 global error_sum float32 data_bank 0 global target_speed byte # Target motor speed global target_seek bit global analog0_greater_than bit # Analog0 value > analog0 limit global analog1_greater_than bit # Analog1 value > analog1 limit global value byte # 8-bit analog value global limit byte # 8-bit limit value procedure main arguments_none returns_nothing local pos_high byte local pos_middle byte local pos_low byte local negative bit local small_error byte call reset() loop_forever # Make sure we manage the UART: call uart_manage() # Update target if necesary if file24_changed != 0 # Something has changed: if file24_changed@target_index target_seek := $true target := file24_float_convert(target_index) file24_changed@target_index := $false call uart_manage() if file24_changed@divisor_index divisor := file24_float_convert(divisor_index) if file24_zero@divisor_index # Yikes, the user set the divisor to zero; set to 1 instead: lows[divisor_index] := 1 divisor := 1.0 file24_changed@divisor_index := $false file24_changed@kp_index := $true file24_changed@ki_index := $true file24_changed@kd_index := $true call uart_manage() if file24_changed@kp_index numerator := file24_float_convert(kp_index) call uart_manage() kp := numerator / divisor file24_changed@kp_index := $false call uart_manage() if file24_changed@ki_index numerator := file24_float_convert(ki_index) call uart_manage() ki := numerator / divisor file24_changed@ki_index := $false call uart_manage() if file24_changed@kd_index numerator := file24_float_convert(kd_index) call uart_manage() kd := numerator / divisor file24_changed@kd_index := $false call uart_manage() if target_seek # Fetch the position atomically: $gie := $false pos_high := quad_high pos_middle := quad_middle pos_low := quad_low $gie := $true call uart_manage() # Convert the position bytes into a float: position := xyz(pos_high, pos_middle, pos_low) call uart_manage() # Figure out the proportional error: previous_error := error error := position - target error_sum := error_sum + error call uart_manage() # Compute the PID equation: pid := kp * error call uart_manage() pid := pid + kd * (error - previous_error) call uart_manage() pid := pid + ki * error_sum # Set the new motor speed: negative := $false if pid < 0.0 # Work in positive arithmetic: negative := $true pid := -pid if pid > 127.0 target_speed := 127 error_sum := 0.0 else target_speed := $float32_to_byte(pid) call uart_manage() # Extract the error as a small byte: if error >= 0.0 if error >= 255.0 small_error := 255 else small_error := $float32_to_byte(error) else if error < -255.0 small_error := 255 else small_error := $float32_to_byte(-error) call uart_manage() # We we are in -{deadband} <= {error} <= {deadband}, # we turn everything off: if small_error <= deadband target_speed := 0 error_sum := 0.0 # Deal with {minimum}: if target_speed != 0 target_speed := target_speed + minimum if target_speed > maximum target_speed := maximum if negative target_speed := 0 - target_speed # Deal with analog0 motor disable: if ad0_enable value := (ad0_value_middle << 6) | (ad0_value_low >> 2) limit := (ad0_limit_middle << 6) | (ad0_limit_low >> 2) analog0_greater_than := $false if value > limit analog0_greater_than := $true if ad0_reverse if target < position target_speed := 0 else if target > position target_speed := 0 # Deal with analog1 motor disable: if ad1_enable value := (ad1_value_middle << 6) | (ad1_value_low >> 2) limit := (ad1_limit_middle << 6) | (ad1_limit_low >> 2) analog1_greater_than := $false if value > limit analog1_greater_than := $true if ad1_reverse if target < position target_speed := 0 else if target > position target_speed := 0 call speed_set(target_speed) procedure reset arguments_none returns_nothing # This procedure will return the restore all of the state to start-up state. local index byte # Initialize UART: address := rb2bus_eedata_read() if address = 0 address := 21 call uart_initialize() # Initialize prescaler for use by Timer 0: # $rapu := $true # Disable bit port A pull-ups (bit 7) # $integ := $false # Interrupt edge select (bit 6) # $t0cs := $false # Use clock for tmr0 (bit 5) # $t0se := $false # Source edge select (bit 4) # $psa := $false # Prescaler is assigned to timer0 (bit 3) # $ps2 := $true # $ps1 := $true # $ps0 := $true # Prescaler = 256 # 1000 0111 = 0x87 $option_reg := 0x87 # Initialize Timer 1: # $t1ginv := $false # Timer1 gate (bit 7) # $tmr1ge := $false # Timer1 gate enable (bit 6) # $t1ckps1 := $false # (bit 5) # $t1ckps0 := $false # Timer1 prescale = 1:1 (bit 4) # $t10scen := $false # LP oscillator is off (bit 3) # $t1sync := $false # Timer 1 synchorinize (bit 2) # $tmr1cs := $false # Use internal clock (bit 1) # $tmr1on := $false # Leave timer off for now (bit 0) # 0000 0000 = 0 $t1con := 0 # Initialize A/D module: # ANSEL is already set by the compiler: # # ADCON0 needs to be configured: # $adfm := $true # Right justified result # $vcfg := $false # Use 5V as Vref # $chs2 := $false # $chs1 := $true # $chs0 := $false # Channel = 010 = AN2 # $go := $false # $adon := $true # Turn on A/D converter # 10x0 1001 = 0x89 $adcon0 := 0x89 channel := 2 # ADCON1 needs to be configured: # ADCS<2:0> := 101 = Tad=1.6uS at Fosc=20MHz # $adcs2 := $true # $adcs1 := $false # $adcs0 := $true # x110 xxxx = 0x50 $adcon1 := 0x50 # Initialize motor: call speed_set(0) # Initialize {file8}: index := 0 while index < file8_size file8[index] := 0 index := index + 1 call configure_update() deadband := 5 minimum := 0 maximum := 60 error_sum := 0.0 # Initialize {file24}: index := 0 while index < file24_size highs[index] := 0 middles[index] := 0 lows[index] := 0 index := index + 1 file24_index := 0 file24_changed := 0 file24_zero := 0 # For now force divisor to -1: highs[divisor_index] := 0xff middles[divisor_index] := 0xff lows[divisor_index] := 0xff # Force the an update of {divisor}: file24_changed@divisor_index := $true # For now kp = 20: lows[kp_index] := 1 # No need to manually force a {kp} update, since updating {divisor} does # that already: # We only turn on target seeking when the {target} is changed. # It gets turned off when find the target (or a limit switch is hit): target_seek := $false # It does not really matter, but setting {target} to 0.0 does not hurt: target := 0.0 # Always initialize {quad_state} even if we do not use quadrature: quad_state := 0 # The weak pull-ups can be turned off: $wpua := 0 # We are only interested in changes in the A and B channels: $ioca := quad_a_mask | quad_b_mask # We only want to turn on the interrupt on change if we are using # the quadrature (i.e. !{use_potentiometer}: $raie := !use_potentiometer # Enable the appropriate interrupts: $t0if := $false $t0ie := $true $tmr1if := $false $tmr1ie := $true $peie := $true $gie := $true procedure uart_manage arguments_none returns_nothing local command byte local index byte local allow bit if uart_input_count != 0 # We have a data/command byte to process: command := uart_input[uart_input_out_index & uart_input_mask] uart_input_out_index := uart_input_out_index + 1 # Manage {uart_input_count} and {uart_input_pending} atomically: $gie := $false uart_input_count := uart_input_count - 1 if uart_input_count = 0 uart_input_pending := $false $gie := $true # We received a "data" byte" switch state case state_select_wait # Technically we should not get here because {$adden} should # be {$true}, but just in case we do, we just ignore the byte: do_nothing case state_echo_then_command # Ignore the echo before processing a command byte: state := state_command case state_command # Process a command byte: switch command >> 6 case 0 # 00xx xxxx: switch (command >> 3) & 7 case 0 # 0000 0xxx: switch command & 7 case 0 # 0000 0000 (Value All Get): call uart_byte_put(highs[file24_index]) state := state_file24_full_get1 case 1 # 0000 0001 (Value Low Get): call uart_byte_put(lows[file24_index]) state := state_echo_then_command case 2 # 0000 0010 (Value Middle Get): call uart_byte_put(middles[file24_index]) state := state_echo_then_command case 3 # 0000 0011 (Value High Get): call uart_byte_put(highs[file24_index]) state := state_echo_then_command case 4 # 0000 0100 (Value All Set): state := state_file24_full_set1 case 5 # 0000 0101 (Value Low Set): state := state_value_low_set1 case 6 # 0000 0110 (Value Middle Set): state := state_value_middle_set1 case 7 # 0000 0111 (Value High Set): state := state_value_high_set1 case 1 # 0000 1sss (Value Index Set): file24_index := command & 7 # Make sure we stay in bounds: if file24_index >= file24_size file24_index := 0 state := state_command case 2 # 0001 0xxx (Byte Get): index := command & 7 if index = 2 # 0001 0010: (Status_Get): call status_update() call uart_byte_put(file8[index]) state := state_echo_then_command case 3 # 0001 1xxx (Byte Set): index := command & 7 state := state_file8_set1 case 4 # 0010 0xxx (Probe Get): switch command & 7 case 0 call zyx(position) case 1 call zyx(target) case 2 call zyx(error) case 3 call zyx(pid) case 4 call zyx(divisor) case 5 call zyx(kp) case_maximum 7 call uart_byte_put(high) state := state_probe_get1 case_maximum 7 case 3 # 11xx xxxx: switch (command >> 3) & 7 case 7 # 1111 1xxx: switch command & 7 case 4 # 1111 1100 (Address Set): call uart_byte_put(address) state := state_address_set1 case 5 # 1111 1101 (Id_Next): call uart_byte_put(id[id_index]) id_index := id_index + 1 if id_index >= id.size id_index := id_index - 1 state := state_echo_then_command case 6 # 1111 1110 (Id_Start): id_index := 0 case 7 # 1111 1111 (Deselect): $adden := $true state := state_select_wait case state_file24_full_get1 # Value All Get 2nd response byte: call uart_byte_put(middles[file24_index]) state := state_file24_full_get2 case state_file24_full_get2 # Value All Get 3rd response byte: call uart_byte_put(lows[file24_index]) state := state_echo_then_command case state_file24_full_set1 # Value All Set 2nd command byte: high := command state := state_file24_full_set2 case state_file24_full_set2 # Value All Set 3rd command byte: middle := command state := state_file24_full_set3 case state_file24_full_set3 # Value All Set 4th command byte: highs[file24_index] := high middles[file24_index] := middle lows[file24_index] := command call file24_updated() state := state_command case state_value_low_set1 # Value Low Set 2nd command byte: lows[file24_index] := command call file24_updated() state := state_command case state_value_middle_set1 # Value Middle Set 2nd command byte: middles[file24_index] := command call file24_updated() state := state_command case state_value_high_set1 # Value High Set 2nd command byte: highs[file24_index] := command call file24_updated() state := state_command case state_file8_set1 # File8 Set 2nd command byte: allow := $false switch index case configure_index allow := $true case status_index do_nothing default if !configure_lock allow := $true case_maximum file8_size - 1 if allow file8[index] := command switch index case configure_index call configure_update() case speed_index call speed_set(speed) case file24_index_index if command > file24_size command := 0 case_maximum 7 state:= state_command case state_address_set1 # Ignore the echo and wait for the new address: state := state_address_set2 case state_address_set2 # First, copy of new address just arrived: new_address := command # Return it call uart_byte_put(new_address) state := state_address_set3 case state_address_set3 # Ignore the echo state := state_address_set4 case state_address_set4 # We have the 2nd copy of new addres. if command = new_address # They match; write it in: call rb2bus_eedata_write(new_address) $gie := $true address := new_address call uart_byte_put(rb2_ok) else # They do not match, return an error: call uart_byte_put(0) state := state_echo_then_command case state_probe_get1 call uart_byte_put(middle) state := state_probe_get2 case state_probe_get2 call uart_byte_put(low) state := state_echo_then_command procedure file24_updated arguments_none returns_nothing # This routine will cause the {file24_change} mask to be updated # with the value that changed. switch file24_index case position_index do_nothing case target_index file24_changed@target_index := $true case divisor_index file24_changed@divisor_index := $true # Changing the divisor will cause kp, ki, and kd to be updated: case kp_index file24_changed@kp_index := $true case ki_index file24_changed@ki_index := $true case kd_index file24_changed@kd_index := $true procedure file24_float_convert argument index byte returns float32 # This routine will return {file24}[{index}] as a {float32}. local mask byte high := highs[index] middle := middles[index] low := lows[index] mask := index2mask[index] if high = 0 && middle = 0 && low = 0 file24_zero := file24_zero | mask else file24_zero := file24_zero & (mask ^ 0xff) return xyz(high, middle, low) procedure status_update arguments_none returns_nothing # This routine will cause {status} to be updated. status := 0 if quad_a status@0 := $true if quad_b status@1 := $true if analog0_greater_than status@2 := $true if analog1_greater_than status@3 := $true procedure configure_update arguments_none returns_nothing local index byte local state byte # This routine will read {configure} and process it: # 0x1c = ad0_enable:ad1_enable: if configure & 0x18 != 0 # We have to configure quad A and B as A/D inputs: $ansel@0 := $true $ansel@1 := $true else # We configure quad A and B as digital inputs: $ansel@0 := $false $ansel@1 := $false index := 0 while index < 32 state := states[index] if position_reverse # Swap the increment and decrement bits in {xstates}: state := state & 0xf | (state << 2) & 0xc0 | (state >> 2) & 0x30 #FIXME: Uncomment to enable {xstates}: #xstates[index] := state index := index + 1 procedure uart_byte_put argument value byte returns_nothing # This routine will send {value} to the bus. # Wait until {$txreg} is ready for a value: while !$txif do_nothing # Ship {value} out to the bus with 9th bit turned off: $adden := $false $tx9d := $false $txreg := value procedure uart_initialize arguments_none returns_nothing # This procedure is responsibile for initializing the UART # connected to the bus. This code is currently specific to # the PIC16F688. #FIXME: Use {rx_bit} and {tx_bit}!!! # Warm up the UART: $trisc@5 := $true $trisc@4 := $true # Initialize the {$txsta} register: $txsta := 0 $tx9 := $true $txen := $true $brgh := $true # Initialize the {$rcsta} register: $rcsta := 0 $spen := $true $rx9 := $true $cren := $true $adden := $true # Set up the baud rate generator: $baudctl := 0 $brg16 := $true $spbrg := $eusart_500000_low $spbrgh := $eusart_500000_high state := state_select_wait id_index := 0 uart_input_in_index := 0 uart_input_out_index := 0 uart_input_count := 0 uart_input_pending := $false # Turn on the interrupts: $txie := $false $rcie := $true procedure speed_set argument speed byte returns_nothing # This procedure will set {motor1_speed}, {motor1_on_mask}, and # {motor1_pulse_width} from {speed}. motor1_speed := speed if motor1_speed@7 # Reverse direction: if motor_reverse motor1_on_mask := 1 else motor1_on_mask := 2 motor1_pulse_width := speed << 1 else if speed = 0 # Stopped motor1_on_mask := 0 else # Forward direction: if motor_reverse motor1_on_mask := 2 else motor1_on_mask := 1 motor1_pulse_width := 0xff - (speed << 1) # FIXME: These two routines are directly copied from rb2_pic16f688.ucl # and should be included via a library command. Alas, the rb2_pic16f688.ucl # library also include rb2bus.ucl, which we do not want!!! constant rb2bus_eedata_address = 0xfe procedure rb2bus_eedata_read arguments_none returns byte # This procedure will return the address stored in EEData. If # there is no data, 0 is returned. local temp1 byte local temp2 byte # Read the first byte (the address): $eecon1 := 0 $eeadr := rb2bus_eedata_address $rd := $true temp1 := $eedat # Read the second byte (the 1'z complement) $eeadr := $eeadr + 1 $rd := $true temp2 := $eedat # If they are 1's complement of one another, they are valid: if temp1 = (0xff ^ temp2) # Return the valid address: return temp1 # They are not 1's complement, so return 0 as an error: return 0 procedure rb2bus_eedata_write argument address byte returns_nothing # This procedure will write {address} into the EEData. The # microcontroller pauses while the EEData is written. # Clear out the {$eecon1} register $eecon1 := 0 $eeadr := rb2bus_eedata_address $eedat := address # Write 2 bytes ({address} followed by {address}^0xff): loop_exactly 2 # Set the data to write: # Set up the for the write: $wren := $true $gie := $false $eecon2 := 0x55 $eecon2 := 0xaa # Start the write: $wr := $true # Wait for write to complete: while $wr do_nothing # Disable writing: $wren := $false # Prepare the second byte of data: $eeadr := $eeadr + 1 $eedat := address ^ 0xff # 24-bit convert to from float code: procedure xyz argument high byte argument middle byte argument low byte returns float32 return_suppress assemble # Grab the high byte: movf xyz__high,w databank xyz,$float32_aargb0 movwf $float32_aargb0 # Grab the middle byte: databank $float32_aargb0,xyz movf xyz__middle,w databank xyz,$float32_aargb1 movwf $float32_aargb1 # Grab the low byte: databank $float32_aargb1,xyz movf xyz__low,w databank xyz,$float32_aargb2 movwf $float32_aargb2 # Do the conversion to float32: databank $float32_aargb2,$float32_from_integer24 codebank xyz,$float32_from_integer24 call $float32_from_integer24 # Result is in the float32 A "register"l not move to 0return: movlw xyz__0return >> 1 databank $float32_from_integer24,$float32_pointer_store codebank $float32_from_integer24,$float32_pointer_store call $float32_pointer_store # Get out code and data banks back home: databank $float32_pointer_store,xyz codebank $float32_pointer_store,xyz return # 24-bit convert to from float code: procedure zyx argument value float32 returns byte return_suppress assemble movlw zyx__value>>1 databank zyx,$float32_pointer_load codebank zyx,$float32_pointer_load call $float32_pointer_load databank $float32_pointer_load,$float32_integer24_convert codebank $float32_pointer_load,$float32_integer24_convert call $float32_integer24_convert codebank $float32_integer24_convert,zyx databank $float32_integer24_convert,$float32_aargb0 movf $float32_aargb0,w databank $float32_aargb0,high movwf high databank high,$float32_aargb1 movf $float32_aargb1,w databank $float32_aargb0,middle movwf middle databank middle,$float32_aargb2 movf $float32_aargb2,w databank $float32_aargb0,low movwf low databank low,zyx return string index2mask = "\1,2,4,8,16,32,64,128\" string id = "\16,0,21,2,3,14\MidiMotor1E-B4\7\Gramson" # {state} is defined by another file: library states