ucl 2.0 # Copyright (c) 2005-2007 by Wayne C. Gramlich # All rights reserved. library $pic16f777 library clock20mhz library $uart package pdip pin 1 = mclr pin 2 = ra0_in, name = x_step pin 3 = ra1_in, name = y_dir pin 4 = ra2_in, name = z_dir pin 5 = ra3_in, name = x_dir pin 6 = ra4_in, name = a_step pin 7 = ra5_in, name = z_step pin 8 = re0_in, name = y_step pin 9 = re1_in, name = a_dir pin 10 = re2_unused pin 11 = vdd1 pin 12 = vss1 pin 13 = osc1 pin 14 = osc2 pin 15 = rc0_in, name = strobe pin 16 = rc1_unused pin 17 = rc2_out, name = load pin 18 = rc3_out, name = clock pin 19 = rd0_out, name = x4 pin 20 = rd1_out, name = x3 pin 21 = rd2_out, name = x2 pin 22 = rd3_out, name = x1 pin 23 = rc4_out, name = ldac pin 24 = rc5_out, name = data pin 25 = rc6_unused pin 26 = rx, name = rxd pin 27 = rd4_out, name = y4 pin 28 = rd5_out, name = y3 pin 29 = rd6_out, name = y2 pin 30 = rd7_out, name = y1 pin 31 = vss2 pin 32 = vdd2 pin 33 = rb0_out, name = z4 pin 34 = rb1_out, name = z3 pin 35 = rb2_out, name = z2 pin 36 = rb3_out, name = z1 pin 37 = rb4_out, name = a4 pin 38 = rb5_out, name = a3 pin 39 = rb6_out, name = a2 pin 40 = rb7_out, name = a1 configure fosc=hs constant microsecond = 5 constant tmr0_prescale_power = 0 constant tmr0_prescale = 1 << (tmr0_prescale_power + 1) constant refresh_rate = 25000 constant refresh_clocks = instruction_rate / refresh_rate constant tmr0_value = 255 - (refresh_clocks / tmr0_prescale) # sin((0/8) * 90) = 0.000000 x 255 = 0 # sin((1/8) * 90) = 0.195090 x 255 = 50 # sin((2/8) * 90) = 0.382683 x 255 = 98 # sin((3/8) * 90) = 0.555570 x 255 = 142 # sin((4/8) * 90) = 0.707107 x 255 = 180 # sin((5/8) * 90) = 0.831470 x 255 = 212 # sin((6/8) * 90) = 0.923880 x 255 = 236 # sin((7/8) * 90) = 0.980785 x 255 = 250 # sin((8/8) * 90) = 1.000000 x 255 = 255 constant sine_0_8 = 0 constant sine_1_8 = 50 constant sine_2_8 = 98 constant sine_3_8 = 142 constant sine_4_8 = 180 constant sine_5_8 = 212 constant sine_6_8 = 236 constant sine_7_8 = 250 constant sine_8_8 = 255 global dac byte global port_b byte global port_d byte global a_angle byte global x_angle byte global y_angle byte global z_angle byte global a_amount byte global x_amount byte global y_amount byte global z_amount byte global a_step_previous bit global x_step_previous bit global y_step_previous bit global z_step_previous bit global dac_update byte bind a_dac_update = dac_update@3 bind z_dac_update = dac_update@2 bind y_dac_update = dac_update@1 bind x_dac_update = dac_update@0 global a_mask byte global z_mask byte global y_mask byte global x_mask byte global a_strength byte global x_strength byte global y_strength byte global z_strength byte origin 0 procedure start arguments_none returns_nothing #: This procedure simply jumps around the interrupt procedure #, located at location 4. assemble goto main origin 4 procedure interrupt arguments_none returns_nothing # This procedure processes the TMR0 interrupt. # Restart TMR0: $tmr0 := tmr0_value $tmr0if := $false # Blank for 4 microseconds: strobe := $false delay 4 * microsecond # Set the H-Bridges: $portb := port_b $portd := port_d # Force the DAC values out to the pins: ldac := $false ldac := $true # Strobe the H-bridges # It may be necessary to add some delay here to let the # sense resistor voltages settle down. strobe := $true procedure sine argument angle byte returns byte # This procedure will return sin({angle})*255, where {angle} is # measured in multiples of 11.25 degrees (= (1/8) * 90). switch angle case 0x00 return 0 case 0x01 return 0 case 0x02 return 0 case 0x03 return 0 case 0x04 return 0 case 0x05 return 0 case 0x06 return 0 case 0x07 return 0 case 0x08 return 0 case 0x09 return 0 case 0x0a return 0 case 0x0b return 0 case 0x0c return 0 case 0x0d return 0 case 0x0e return 0 case 0x0f return 0 case 0x10 return sine_0_8 * 1 / 8 case 0x11 return sine_1_8 * 1 / 8 case 0x12 return sine_2_8 * 1 / 8 case 0x13 return sine_3_8 * 1 / 8 case 0x14 return sine_4_8 * 1 / 8 case 0x15 return sine_5_8 * 1 / 8 case 0x16 return sine_6_8 * 1 / 8 case 0x17 return sine_7_8 * 1 / 8 case 0x18 return sine_8_8 * 1 / 8 case 0x19 return sine_7_8 * 1 / 8 case 0x1a return sine_6_8 * 1 / 8 case 0x1b return sine_5_8 * 1 / 8 case 0x1c return sine_4_8 * 1 / 8 case 0x1d return sine_3_8 * 1 / 8 case 0x1e return sine_2_8 * 1 / 8 case 0x1f return sine_1_8 * 1 / 8 case 0x20 return sine_0_8 * 2 / 8 case 0x21 return sine_1_8 * 2 / 8 case 0x22 return sine_2_8 * 2 / 8 case 0x23 return sine_3_8 * 2 / 8 case 0x24 return sine_4_8 * 2 / 8 case 0x25 return sine_5_8 * 2 / 8 case 0x26 return sine_6_8 * 2 / 8 case 0x27 return sine_7_8 * 2 / 8 case 0x28 return sine_8_8 * 2 / 8 case 0x29 return sine_7_8 * 2 / 8 case 0x2a return sine_6_8 * 2 / 8 case 0x2b return sine_5_8 * 2 / 8 case 0x2c return sine_4_8 * 2 / 8 case 0x2d return sine_3_8 * 2 / 8 case 0x2e return sine_2_8 * 2 / 8 case 0x2f return sine_1_8 * 2 / 8 case 0x30 return sine_0_8 * 3 / 8 case 0x31 return sine_1_8 * 3 / 8 case 0x32 return sine_2_8 * 3 / 8 case 0x33 return sine_3_8 * 3 / 8 case 0x34 return sine_4_8 * 3 / 8 case 0x35 return sine_5_8 * 3 / 8 case 0x36 return sine_6_8 * 3 / 8 case 0x37 return sine_7_8 * 3 / 8 case 0x38 return sine_8_8 * 3 / 8 case 0x39 return sine_7_8 * 3 / 8 case 0x3a return sine_6_8 * 3 / 8 case 0x3b return sine_5_8 * 3 / 8 case 0x3c return sine_4_8 * 3 / 8 case 0x3d return sine_3_8 * 3 / 8 case 0x3e return sine_2_8 * 3 / 8 case 0x3f return sine_1_8 * 3 / 8 case 0x40 return sine_0_8 * 4 / 8 case 0x41 return sine_1_8 * 4 / 8 case 0x42 return sine_2_8 * 4 / 8 case 0x43 return sine_3_8 * 4 / 8 case 0x44 return sine_4_8 * 4 / 8 case 0x45 return sine_5_8 * 4 / 8 case 0x46 return sine_6_8 * 4 / 8 case 0x47 return sine_7_8 * 4 / 8 case 0x48 return sine_8_8 * 4 / 8 case 0x49 return sine_7_8 * 4 / 8 case 0x4a return sine_6_8 * 4 / 8 case 0x4b return sine_5_8 * 4 / 8 case 0x4c return sine_4_8 * 4 / 8 case 0x4d return sine_3_8 * 4 / 8 case 0x4e return sine_2_8 * 4 / 8 case 0x4f return sine_1_8 * 4 / 8 case 0x50 return sine_0_8 * 5 / 8 case 0x51 return sine_1_8 * 5 / 8 case 0x52 return sine_2_8 * 5 / 8 case 0x53 return sine_3_8 * 5 / 8 case 0x54 return sine_4_8 * 5 / 8 case 0x55 return sine_5_8 * 5 / 8 case 0x56 return sine_6_8 * 5 / 8 case 0x57 return sine_7_8 * 5 / 8 case 0x58 return sine_8_8 * 5 / 8 case 0x59 return sine_7_8 * 5 / 8 case 0x5a return sine_6_8 * 5 / 8 case 0x5b return sine_5_8 * 5 / 8 case 0x5c return sine_4_8 * 5 / 8 case 0x5d return sine_3_8 * 5 / 8 case 0x5e return sine_2_8 * 5 / 8 case 0x5f return sine_1_8 * 5 / 8 case 0x60 return sine_0_8 * 6 / 8 case 0x61 return sine_1_8 * 6 / 8 case 0x62 return sine_2_8 * 6 / 8 case 0x63 return sine_3_8 * 6 / 8 case 0x64 return sine_4_8 * 6 / 8 case 0x65 return sine_5_8 * 6 / 8 case 0x66 return sine_6_8 * 6 / 8 case 0x67 return sine_7_8 * 6 / 8 case 0x68 return sine_8_8 * 6 / 8 case 0x69 return sine_7_8 * 6 / 8 case 0x6a return sine_6_8 * 6 / 8 case 0x6b return sine_5_8 * 6 / 8 case 0x6c return sine_4_8 * 6 / 8 case 0x6d return sine_3_8 * 6 / 8 case 0x6e return sine_2_8 * 6 / 8 case 0x6f return sine_1_8 * 6 / 8 case 0x70 return sine_0_8 * 7 / 8 case 0x71 return sine_1_8 * 7 / 8 case 0x72 return sine_2_8 * 7 / 8 case 0x73 return sine_3_8 * 7 / 8 case 0x74 return sine_4_8 * 7 / 8 case 0x75 return sine_5_8 * 7 / 8 case 0x76 return sine_6_8 * 7 / 8 case 0x77 return sine_7_8 * 7 / 8 case 0x78 return sine_8_8 * 7 / 8 case 0x79 return sine_7_8 * 7 / 8 case 0x7a return sine_6_8 * 7 / 8 case 0x7b return sine_5_8 * 7 / 8 case 0x7c return sine_4_8 * 7 / 8 case 0x7d return sine_3_8 * 7 / 8 case 0x7e return sine_2_8 * 7 / 8 case 0x7f return sine_1_8 * 7 / 8 case 0x80 return sine_0_8 case 0x81 return sine_1_8 case 0x82 return sine_2_8 case 0x83 return sine_3_8 case 0x84 return sine_4_8 case 0x85 return sine_5_8 case 0x86 return sine_6_8 case 0x87 return sine_7_8 case 0x88 return sine_8_8 case 0x89 return sine_7_8 case 0x8a return sine_6_8 case 0x8b return sine_5_8 case 0x8c return sine_4_8 case 0x8d return sine_3_8 case 0x8e return sine_2_8 case 0x8f return sine_1_8 procedure test_mode arguments_none returns_nothing # This procedure is used for testing only. local command byte local high byte local low byte local voltage byte # Test 3: Verify that we can write to D/A chip: dac := 0 low := 0 high := 0 dac_update := 0 loop_forever #ldac := 0 ldac := $true load := $true call $uart_byte_put('>') command := $uart_byte_get() call $uart_byte_put(command) if '0' <= command && command <= '7' # Select a channel: dac := command - '0' if 'a' <= command && command <= 'p' low := command - 'a' if 'A' <= command && command <= 'P' # Set a voltage: high := command - 'A' if command = 's' # Set voltage: call $uart_crlf_put() voltage := (high << 4) | low call $uart_hex_put(dac) call $uart_hex_put(voltage) call $uart_space_put() # Select the DAC to output to: call spi_send(dac << 1) call spi_send(voltage) # Wait until the command is finished sending: while !$bf do_nothing # Load the command into the DAC chip: load := $false load := $true # Force the DAC values out: ldac := $false ldac := $true if command = 't' call $uart_byte_put('s') call $uart_hex_put($sspstat) call $uart_hex_put($sspcon) call $uart_byte_put('t') call $uart_hex_put($trisa) call $uart_hex_put($trisc) call $uart_byte_put('m') call $uart_hex_put($tmr0) call $uart_hex_put($option_reg) call $uart_hex_put($intcon) if command = 'T' call $uart_byte_put('d') call $uart_hex_put(port_d) call $uart_byte_put('$') call $uart_byte_put('d') call $uart_hex_put($portd) if command = 'u' $portd := (high << 4) | low if command = 'U' port_d := (high << 4) | low if command = 'v' $gie := $true $tmr0ie := $true if command = 'V' $gie := $false $tmr0ie := $false if command = 'w' a_angle := a_angle + a_amount a_dac_update := $true if command = 'W' a_angle := a_angle - a_amount a_dac_update := $true if command = 'x' x_angle := x_angle + x_amount x_dac_update := $true if command = 'X' x_angle := x_angle - x_amount x_dac_update := $true if command = 'y' y_angle := y_angle + y_amount y_dac_update := $true if command = 'Y' y_angle := y_angle - y_amount y_dac_update := $true if command = 'z' z_angle := z_angle + z_amount z_dac_update := $true if command = 'Z' z_angle := z_angle - z_amount z_dac_update := $true call $uart_crlf_put() # This is the code that forces the DAC's to be updated: if dac_update != 0 if a_dac_update a_dac_update := $false a_mask := dac_setup(a_angle, a_strength, 0) << 4 if z_dac_update z_dac_update := $false z_mask := dac_setup(z_angle, z_strength, 2) if y_dac_update y_dac_update := $false y_mask := dac_setup(y_angle, y_strength, 4) << 4 if x_dac_update x_dac_update := $false x_mask := dac_setup(x_angle, z_strength, 6) port_b := a_mask | z_mask port_d := y_mask | x_mask #call $uart_byte_put('b') #call $uart_hex_put(port_b) #call $uart_byte_put('d') #call $uart_hex_put(port_d) # Force the DAC values out: ldac := $false ldac := $true procedure main arguments_none returns_nothing # Turn off general interrupts: $gie := $false # The {strobe} line is actually an output. It *must* be # left high pretty much all the time. It only gets strobed # low for small number of cycles (currently 4uS) every # 1/{refresh_rate}'th of a second. If {strobe} is left # low for an extended period of time, it simply turns on # the H-bridges and overrides the current limit detection # circuitry. This can cause the H-bridges to burn out. # I know this from very painful experience. The solution is to # define {strobe} as a input and then switch it over to being # an output in the initialization code: # Step 1: Set {strobe} high first: strobe := $true # Step 2: Set {$portb} and {$portd} to 0 so that the H-bridges are # set not to conduct any current even if they are turned on. $portb := 0 $portd := 0 # Step 3: Enable {strobe} as an output by setting {$trisc}@0 to 0: $trisc@0 := $false # Initilize serial port: # Do Baud Rate selection and asynch. serial port enable: # Prescaler = low: $brgh := $true # Baud rate = 19200 Baud: $spbrg := 64 # Asynchronous mode: $sync := $false # Serial port enable: $spen := $true $txif := $false # Enable the transmitter: # 8-bit mode: $tx9 := $false # Enable transmitter: $txen := $true # Enable the receiver: # 8-bit mode: $rx9 := $false # Disable address: $adden := $false # Continuous receive enable: $cren := $true # Serial receive enable: $sren := $true # Test 1: Say "Hi!": #loop_forever # call $uart_byte_put('H') # call $uart_byte_put('i') # call $uart_byte_put('!') # call $uart_crlf_put() # Test 2: Double Echo: #loop_forever # dac := $uart_byte_get() # call $uart_byte_put(dac) # call $uart_byte_put(dac) # Initialize TMR0: # Set OPTION_REG<2:0> to {tmr0_prescale_power}: $option_reg := tmr0_prescale_power # Prescaler assigned to TMR0 module $psa := $false # TMR0 runs of system clock $t0cs := $false # The synchronous serial port can be run in master mode at three # different clock rates: # # Fosc/4 - SSPM<3:0> = 0000 = 5MHz @ Fosc=20MHz # Fosc/16 - SSPM<3:0> = 0001 = 1.25MHz @ Fosc=20MHz # Fosc/64 - SSPM<3:0> = 0010 = .3125MHz @ Fosc=20Mhz # # Technically, the specifications for the TLC5628 8-channel DAC # specify the following: # # Maximum clock rate = 1 MHz suggesting Fosc/64 # Minimum setup times = 50ns sugessing Fosc/16 # Mininum pulse widths = 250ns suggesting Fosc/16 # # In practice, everything seems to work just fine with Fosc/4. # Go figure. # Initialize {$sspcon}: $sspcon := 0 # Transmit on idle to active: $cke := $false # Initialize {$sspstat}: $sspstat := 0 # Idle SCK is low: $ckp := $false # Ensure that SDO is an output: $trisc5 := $false # Ensure that SCK is an output: $trisc3 := $false # {load} is active low, so it idles at high: load := $true # {ldac} is active low, so it idles at high: ldac := $true # Enable Synchronous Serial Port: $sspen := $true # The $bf (buffer full) bit is read-only and it initializes to 0. # We need to get it set. This is done by causing the synchronous # serial port to write (and read) 8 bits: $sspbuf := 0 # In about 8 cycles {$bf} should be 1: # Now initialize the axis angles: a_angle := 0 x_angle := 0 y_angle := 0 z_angle := 0 # Now intiialize the axis offsets: # 1 = 11.25 degrees # 2 = 22.5 degrees # 4 = 45 degrees (Half Step) # 8 = 90 degrees (Wave Step) a_amount := 1 x_amount := 1 y_amount := 1 z_amount := 1 # Clear any previous states: a_step_previous := $false x_step_previous := $false y_step_previous := $false z_step_previous := $false dac_update := 0 # Clear out the mask values: a_mask := 0 x_mask := 0 y_mask := 0 z_mask := 0 # Set the initial strength values: a_strength := 0x70 x_strength := 0x70 y_strength := 0x70 z_strength := 0x70 # This causes us to enter test mode: call test_mode() procedure step_check arguments_none returns_nothing # This procedure is reponsible for checking to see whether # a step pulse occurred. do_nothing procedure dac_setup argument angle byte argument strength byte argument dac_base byte returns byte # This procedure will send the appropriate DAC value for {angle} # out the two DAC channels numbered {dac_base} and {dac_base}+1. # The returned value is used to set the direction of both H-bridge # coils. local result byte #call $uart_byte_put('a') #call $uart_hex_put(angle) #call $uart_byte_put('d') #call $uart_hex_put(dac_base) result := dac_helper(angle, strength, dac_base) | dac_helper(angle + 8, strength, dac_base + 1) << 2 #call $uart_byte_put('r') #call $uart_hex_put(result) return result procedure dac_helper argument angle byte argument strength byte argument dac byte returns byte # This procedure will output the DAC value for {angle} to DAC # channel {dac}. local result byte # Select the DAC to output to: call spi_send(dac << 1) call spi_send(sine(strength | (angle & 0xf))) call step_check() # Wait for data to flush: while !$bf do_nothing # Load it in: load := $false load := $true if angle & 15 = 0 # Turn the coil entirely off: result := 0 else_if angle@4 # Coil goes in one direction ... result := 2 else # ... or the other direction. result := 1 #call $uart_byte_put('p') #call $uart_hex_put(result) return result procedure spi_send argument command byte returns_nothing # This procedure will stuff {command} into the SPI transmitter. local zilch byte # For debugging: #call $uart_byte_put('#') #call $uart_hex_put(command) # The code in the specification sheet is a little misleading # since it does not point out that {$bf} is in {$sspstat} which # is in data bank 1 and {$sspbuf} is in data bank 0. They do # not show any of the data bank switching. Luckily, the uCL # compiler keeps track of all of that. # Wait until the command is finished sending: while !$bf do_nothing # Read contents of receive buffer {$sspbuf} and throw it away. # This should cause {$bf} to clear: zilch := $sspbuf # Initiate command sending by writing {command} to {$sspbuf}: $sspbuf := command # # This is some bit bang code that gets the job done: # # loop_exactly 8 # if command@7 # data := 1 # else # data := 0 # clock := 1 # #delay 1 # # do_nothing # clock := 0 # #delay 1 # # do_nothing # command := command << 1