; 3 AXIS NETWORKED MOTOR CONTROLLER ; ; SIC PROJECT 076 FAB IN A BOX ; ; Ilan Moyer ; ; (c) Massachusetts Institute of Technology 2010 ; Permission granted for experimental and personal use; ; license for commercial sale available from MIT. ; ;--REVISION HISTORY----------------------------------------------------------------------- ; ; --------------------------------------------------------------------------------- ; | DATE | MODIFICATIONS | NAME |FILENAME | ; |--------|---------------------------|-------------------|-----------------------| ; |03/09/08| CREATED |ILAN E MOYER |i0.smc.03.asm | ; |--------|---------------------------|-------------------|-----------------------| ; |12/31/08| SPIFFIFIED |ILAN E MOYER |056-054.asm | ; |--------|---------------------------|-------------------|-----------------------| ; |3/20/09 | ADDED HALF-STEPPING |ILAN E MOYER |056-054b.asm | I changed the format of motorpositions to allow 8 half-positions ; |--------|---------------------------|-------------------|-----------------------| ; |3/20/09 | FIXED SINGLE-STEP ISSUE |ILAN E MOYER |056-054c.asm | ; |--------|---------------------------|-------------------|-----------------------| ; |11/29/09| MODIFIED FOR NETWORKING |ILAN E MOYER |056-054d.asm | ; |--------|---------------------------|-------------------|-----------------------| ; |12/08/09| ADDED VM URL RESPONSE |ILAN E MOYER |056-054e.asm | ; |--------|---------------------------|-------------------|-----------------------| ; |12/13/09| ADDED 3-AXIS MOTION |ILAN E MOYER |056-054f.asm | ; |--------|---------------------------|-------------------|-----------------------| ; |01/06/10| REFACTORED FOR FABINABOX |ILAN E MOYER |076-001a.asm | ; |--------|---------------------------|-------------------|-----------------------| ; |01/07/10| ADDED RX PULLUP, LEDS OFF |ILAN E MOYER |076-001a.asm | RX pullup is necessary to avoid tripping rcv watchdog. Also turned off ind leds after move. ; ---------------------------------------------------------------------------------- ; ; ; ;--HARDWARE CONFIGURATION (076-001 REV1)--------------------------------------------- ; ; ; ; ; ; ; ; ; X ; T ; X A ; X ; E _ I ; N R S ; A E ; B T R S P P P L ; L X X E C C C E ; E D D T 5 4 3 D ; --------------- ; LED| | Y AXIS LED ; BUTTON| | Z AXIS LED ; GND| | ADC7 ; VCC| ATMEGA88 | GND ; GND| | AREF ; VCC| | ADC6 ; XTAL1| | AVCC ; XTAL2| | SCK ; --------------- ; M X X Y Y Z M M ; O O I ; T S D S D S S S ; O T I T I T I O ; R E R E R E / ; S P P P Z ; ; E D ; N I ; A R ; B ; L ; E ; ; ;--PORT B ASSIGNMENTS-------------------------------------------------------------- ; ; PB0 = Y AXIS STEP ; PB1 = Y AXIS DIR ; PB2 = Z AXIS STEP ; PB3 = MOSI / Z AXIS DIR ; PB4 = MISO ; PB5 = SCK ; PB6 = XTAL1 ; PB7 = XTAL2 ; ;--PORT C ASSIGNMENTS-------------------------------------------------------------- ; ; PC0 = Z AXIS INDICATOR LED ; PC1 = Y AXIS INDICATOR LED ; PC2 = X AXIS INDICATOR LED ; PC3 = N/C ; PC4 = N/C ; PC5 = N/C ; PC6 = /RESET ;--PORT D ASSIGNMENTS-------------------------------------------------------------- ; PD0 = RXD ; PD1 = TXD ; PD2 = RS485 TX ENABLE ; PD3 = LED ; PD4 = BUTTON ; PD5 = MOTORS ENABLE ; PD6 = X AXIS STEP ; PD7 = X AXIS DIRECTION ;--INCLUDE FILES------------------------------------------------------------------- ; .DEVICE ATMega88 .include "m88def.inc" ;--DEFINITIONS--------------------------------------------------------------------- ;PORT DIRECTION MASKS ;PACKET LOCATIONS .equ rx_packet_location = 0x0100 ;SRAM start location for rx packet .equ tx_packet_location = 0x0200 ;SRAM start location for tx packet ;PAcKET OFFSETS .equ packet_startbyte = 0 .equ packet_srcaddress0 = 1 .equ packet_srcaddress1 = 2 .equ packet_srcport = 3 .equ packet_destaddress0 = 4 .equ packet_destaddress1 = 5 .equ packet_destport = 6 .equ packet_length = 7 .equ packet_payload = 8 ;PACKET STANDARD VALUES .equ packet_unicast_startbyte = 74 .equ packet_broadcast_startbyte = 138 ;NODE OFFSETS .equ node_address_location = 0x0300 ;SRAM start location for node address ;MOTION CONTROL SRAM .equ motion_pointer_location = 0x0302 ;SRAM location for motion control queue pointer .equ motion_queue_location = 0x0303; SRAM start location for motion control queue ;MOTION CONTROL OFFSETS .equ motion_key = 0 .equ motion_directions = 1 .equ motion_maxsteps_0 = 2 .equ motion_maxsteps_1 = 3 .equ motion_xsteps_0 = 4 .equ motion_xsteps_1 = 5 .equ motion_ysteps_0 = 6 .equ motion_ysteps_1 = 7 .equ motion_zsteps_0 = 8 .equ motion_zsteps_1 = 9 .equ motion_topvalue_0 = 10 .equ motion_topvalue_1 = 11 ;MOTION CONTROL BIT DEFINITIONS: .equ x_axis_direction_bit = 0 .equ y_axis_direction_bit = 1 .equ z_axis_direction_bit = 2 ;NODE VALUES .equ node_address0 = 165 .equ node_address1 = 134 ;RECEIVER WATCHDOG CONSTANTS .equ watchdog_trip = 253 ;PORTS: UI .equ button_input = PIND .equ led_output = PORTD .equ led_toggle = PIND ;PORTS: COMM .equ comm_port = PORTD .equ comm_dir = DDRD ;PORTS: MOTION CONTROL .equ motor_enable_ddr = DDRD .equ motor_enable_port = PORTD .equ x_axis_dir_ddr = DDRD .equ x_axis_dir_port = PORTD .equ x_axis_step_ddr = DDRD .equ x_axis_step_port = PORTD .equ x_axis_step_toggle = PIND .equ x_axis_ind_ddr = DDRC ;INDICATOR LED .equ x_axis_ind_port = PORTC .equ x_axis_ind_toggle = PINC .equ y_axis_dir_ddr = DDRB .equ y_axis_dir_port = PORTB .equ y_axis_step_ddr = DDRB .equ y_axis_step_port = PORTB .equ y_axis_step_toggle = PINB .equ y_axis_ind_ddr = DDRC .equ y_axis_ind_port = PORTC .equ y_axis_ind_toggle = PINC .equ z_axis_dir_ddr = DDRB .equ z_axis_dir_port = PORTB .equ z_axis_step_ddr = DDRB .equ z_axis_step_port = PORTB .equ z_axis_step_toggle = PINB .equ z_axis_ind_ddr = DDRC .equ z_axis_ind_port = PORTC .equ z_axis_ind_toggle = PINC ;PINS: UI .equ button = PD4 .equ led = PD3 ;PINS: COMM .equ rx_pin = PD0 .equ tx_pin = PD1 .equ tx_enable = PD2 ;PINS: MOTION CONTROL .equ motor_enable = PD5 .equ x_axis_dir = PD7 .equ x_axis_step = PD6 .equ x_axis_ind = PC2 .equ y_axis_dir = PB1 .equ y_axis_step = PB0 .equ y_axis_ind = PC1 .equ z_axis_dir = PB3 .equ z_axis_step = PB2 .equ z_axis_ind = PC0 ;DIRECTION MASKS .equ portb_directions = (1 << y_axis_step)|(1 << y_axis_dir)|(1 << z_axis_step)|(1 << z_axis_dir) .equ portc_directions = (1 << x_axis_ind)|(1 << y_axis_ind)|(1 << z_axis_ind) .equ portd_directions = (1 << tx_pin)|(1 << tx_enable)|(1 << led)|(1 << motor_enable)|(1 << x_axis_step)|(1 << x_axis_dir) ;PULLUP MASKS .equ portb_pullups = 0 .equ portc_pullups = 0 .equ portd_pullups = (1 << button)|(1 << rx_pin) ;REGISTERS .def numzero = r0 .def temp4 = r1 .def packetlength = r2 .def packetchecksum = r3 .def rcv_watchdog = r4 .def delta_a_0 = r5 ;error register a .def delta_a_1 = r6 .def delta_b_0 = r7 ;error register b .def delta_b_1 = r8 .def delta_c_0 = r9 ;error register c .def delta_c_1 = r10 .def delta_x_0 = r11 ;delta_x * 2 .def delta_x_1 = r12 .def delta_y_0 = r13 ;delta_y * 2 .def delta_y_1 = r14 .def current_key = r15 ;current buffer key position ; working registers .def temp1 = r16 .def temp2 = r17 .def packetposition = r18 .def temp3 = r19 .def delta_z_0 = r20 ;delta_z * 2 .def delta_z_1 = r21 .def delta_max_0 = r22 ;delta_max .def delta_max_1 = r23 .def current_step_0 = r24 ;delta_max * 2 .def current_step_1 = r25 ; FLAG REGISTERS .equ comm_status = GPIOR0 ;using general purpose IO register to conserve standard registers. .equ packet_received = 0 ;first bit of comm_status. .equ packet_inbound = 1 .equ packet_outbound = 2 .equ now_moving = 3 ;indicates that a move is in progress .equ run_thru = 4 ;if set, motion packets should be run continuously until queue is exhausted. .equ sync_move = 5 ;if set, motion command is only executed upon sync, and run-thru is not set if packet is queued ;SRAM .equ Memory_Start = 0x0060 ;--PROGRAM INITIALIZATION---------------------------------------------------------- .cseg .org 0 ;--INTERRUPT VECTOR TABLE---------------------------------------------------------- rjmp reset ; External Pin, Power-on Reset, Brown-out Reset and Watchdog System .dw 0 ; External Interrupt Request 0 .dw 0 ; External Interrupt Request 1 .dw 0 ; Pin Change Interrupt Request 0 .dw 0 ; Pin Change Interrupt Request 1 .dw 0 ; Pin Change Interrupt Request 2 .dw 0 ; Watchdog Time-out Interrupt .dw 0 ; Timer/Counter2 Compare Match A .dw 0 ; Timer/Counter2 Compare Match B rjmp isr_packet_timeout_100 ; Timer/Counter2 Overflow .dw 0 ; Timer/Counter1 Capture Event rjmp isr_move_gen_100 ; Timer/Counter1 Compare Match A .dw 0 ; Timer/Coutner1 Compare Match B .dw 0 ; Timer/Counter1 Overflow .dw 0 ; Timer/Counter0 Compare Match A .dw 0 ; Timer/Counter0 Compare Match B .dw 0 ; Timer/Counter0 Overflow .dw 0 ; SPI Serial Transfer Complete rjmp isr_packet_receiver_100 ; USART Rx Complete rjmp isr_packet_transmitter_100 ; USART, Data Register Empty rjmp isr_packet_transmitter_100 ; USART, Tx Complete .dw 0 ; ADC Conversion Complete .dw 0 ; EEPROM Ready .dw 0 ; Analog Comparator .dw 0 ; 2-wire Serial Interface .dw 0 ; Store Program Memory Ready ;--INTERRUPT ROUTINES-------------------------------------------------------------- ;-- PACKET RECEIVER -- ; ;This interrupt routine stores data from an incoming packet into SRAM ; isr_packet_receiver_100: cli push ZL push ZH push temp1 push temp2 in temp2, SREG push temp2 sts TCNT2, numzero ;clears timer0 mov rcv_watchdog, numzero ;resets watchdog counter ldi temp1, (0 << OCIE0B)|(0 << OCIE0A)|(1 << TOIE0) ;enables timer2 as watchdog for receiver sts TIMSK2, TEMP1 isr_packet_receiver_200: ;load byte into temp1, and store in SRAM lds temp1, UDR0 ldi ZH, high(rx_packet_location) ldi ZL, low(rx_packet_location) add ZL, packetposition adc ZH, numzero st Z, temp1 sbi comm_status, packet_inbound ;sets packet inbound flag isr_packet_receiver_300: ;check for packet length cpi packetposition, packet_length ;see if this byte contains the packet length brne isr_packet_receiver_400 mov packetlength, temp1 ;if YES, store the packet length in packetlength register isr_packet_receiver_400: ;checks for end-of-packet condition cpi packetposition, packet_length brlo isr_packet_receiver_500 ;checks if the packet length byte has been read yet. If YES, checks for end-of-packet cp packetposition, packetlength breq isr_packet_receiver_600 ;if this is end_of_packet, skips the checksum isr_packet_receiver_500: ;calculates the CRC of the packet mov temp3, temp1 eor temp1, packetchecksum ; IN PYTHON: crc = input^crc ldi ZH, high(crc_table*2) ;loads CRC table location into Z pointer ldi ZL, low(crc_table*2) add ZL, temp1 ;offsets Z pointer by value of temp1 adc ZH, numzero lpm packetchecksum, Z ;stores new checksum in packetchecksum inc packetposition rjmp isr_packet_receiver_exit isr_packet_receiver_600: ;check if packet is for us mov temp3, temp1 ;stores a copy of the received byte in temp3 ldi ZH, high(rx_packet_location+packet_startbyte) ;loads packet start byte into temp1 ldi ZL, low(rx_packet_location+packet_startbyte) ld temp1, Z cpi temp1, packet_broadcast_startbyte breq isr_packet_receiver_700 ;broadcast packet, skips address check. cpi temp1, packet_unicast_startbyte brne isr_packet_receiver_900 ;packet bad! ldi ZH, high(rx_packet_location+packet_destaddress0) ;load destination address byte 0 into temp1 ldi ZL, low(rx_packet_location+packet_destaddress0) ld temp1, Z ldi ZH, high(node_address_location) ;load node address byte 0 into temp2 ldi ZL, low(node_address_location) ld temp2, Z cp temp1, temp2 brne isr_packet_receiver_900 ;packet not destined for us! ldi ZH, high(rx_packet_location+packet_destaddress1) ;load destination address byte 0 into temp1 ldi ZL, low(rx_packet_location+packet_destaddress1) ld temp1, Z ldi ZH, high(node_address_location+1) ;load node address byte 0 into temp2 ldi ZL, low(node_address_location+1) ld temp2, Z cp temp1, temp2 brne isr_packet_receiver_900 ;packet not destined for us! isr_packet_receiver_700: ;check packet checksum cp temp3, packetchecksum ;see if checksum matches last byte brne isr_packet_receiver_900 ;checksum bad. For now, ditch packet. Later, this could dispatch a resend-request. isr_packet_receiver_800: ;packet is good, and destined for US! ldi temp1, (0 << OCIE0B)|(0 << OCIE0A)|(0 << TOIE0) ;disables timer0 interrupts sts TIMSK2, TEMP1 sbi comm_status, packet_received ;sets packet waiting flag clr packetposition clr packetchecksum cbi comm_status, packet_inbound ;clears packet inbound flag cbi comm_status, packet_outbound rjmp isr_packet_receiver_exit isr_packet_receiver_900: ;packet either not for us, or checksum is bad. Packet is tossed. ; sts UDR0, numzero ;REMOVE clr packetposition clr packetchecksum cbi comm_status, packet_inbound ;sets packet inbound flag ldi temp1, (0 << OCIE0B)|(0 << OCIE0A)|(0 << TOIE0) ;disables timer2 interrupts sts TIMSK2, TEMP1 isr_packet_receiver_exit: pop temp2 out SREG, temp2 pop temp2 pop temp1 pop ZH pop ZL sei reti ;-- PACKET TRANSMITTER -- ; ; This interrupt routine transmits bytes of a packet whenever they become avaliable. ; isr_packet_transmitter_100: cli push ZL push ZH push temp1 push temp2 in temp2, SREG push temp2 ldi temp1, (1< delta_max, skip to the sauce. Otherwise, check lsb cp delta_a_0, delta_max_0 brlo isr_move_gen_300Y ;delta_a < delta_max, branch to check y axis isr_move_gen_350X: sub delta_a_0, temp1 ;delta_a = delta_a - 2*delta_max sbc delta_a_1, temp2 sbi x_axis_step_port, x_axis_step ;step in X axis sbi x_axis_ind_toggle, x_axis_ind ;toggle x axis indicator led isr_move_gen_300Y: ;check for a step in the X axis cp delta_b_1, delta_max_1 brlo isr_move_gen_300Z ;delta_b < delta_max, branch to check z axis brne isr_move_gen_350Y ;delta_b > delta_max, skip to the sauce. Otherwise, check lsb cp delta_b_0, delta_max_0 brlo isr_move_gen_300Z ;delta_b < delta_max, branch to check z axis isr_move_gen_350Y: sub delta_b_0, temp1 ;delta_b = delta_b - 2*delta_max sbc delta_b_1, temp2 sbi y_axis_step_port, y_axis_step ;step in y axis sbi y_axis_ind_toggle, y_axis_ind ;toggle y axis indicator led isr_move_gen_300Z: ;check for a step in the X axis cp delta_c_1, delta_max_1 brlo isr_move_gen_400 ;delta_c < delta_max, branch to move_gen_400 brne isr_move_gen_350Z ;delta_c > delta_max, skip to the sauce. Otherwise, check lsb cp delta_c_0, delta_max_0 brlo isr_move_gen_400 ;delta_c < delta_max, branch to move_gen_400 isr_move_gen_350Z: sub delta_c_0, temp1 ;delta_c = delta_c - 2*delta_max sbc delta_c_1, temp2 sbi z_axis_step_port, z_axis_step ;step in z axis sbi z_axis_ind_toggle, z_axis_ind ;toggle z axis indicator led isr_move_gen_400: ldi temp1, 7 ;delay of 7 loops, which is 22 clock cycles (3*7 + 1 (ldi)). isr_move_gen_420: dec temp1 cpi temp1, 0 brne isr_move_gen_420 cbi x_axis_step_port, x_axis_step ;reset all step pins cbi y_axis_step_port, y_axis_step cbi z_axis_step_port, z_axis_step isr_move_gen_500: ;check if move is over ldi temp1, 1 add current_step_0, temp1 ;increment current_step by 1 adc current_step_1, numzero cp current_step_1, delta_max_1 brne isr_move_gen_exit ;current_step != delta_max, move not over cp current_step_0, delta_max_0 brne isr_move_gen_exit ;current_step != delta_max, move not over sbis comm_status, run_thru rjmp isr_move_gen_700 ;run-thru disabled, go to end move routine isr_move_gen_600: ;run_thru enabled, load move from memory into registers push ZL push ZH cbi comm_status, run_thru ;clears run-thru flag to indicate that queue was just emptied. ldi ZH, high(motion_queue_location) ;points Z to first byte of incoming payload ldi ZL, low(motion_queue_location) ld current_key, Z+ ld temp1, Z+ ;loads directions into temp1 ld delta_max_0, Z+ ld delta_max_1, Z+ ld delta_x_0, Z+ ;loads delta_x ld delta_x_1, Z+ lsl delta_x_0 ;multiplies delta_x by two. rol delta_x_1 ld delta_y_0, Z+ ;loads delta_y ld delta_y_1, Z+ lsl delta_y_0 ;multiplies delta_y by two. rol delta_y_1 ld delta_z_0, Z+ ;loads delta_z ld delta_z_1, Z+ lsl delta_z_0 ;multiplies delta_z by two. rol delta_z_1 ld temp2, Z+ ;loads Counter Top 0 into temp2 ld temp3, Z+ ;loads Counter Top 1 into temp3 sts OCR1AH, temp3 ;loads temp2, temp3 into OCR1A sts OCR1AL, temp2 clr delta_a_0 ;initialized registers clr delta_a_1 clr delta_b_0 clr delta_b_1 clr delta_c_0 clr delta_c_1 clr current_step_0 clr current_step_1 isr_move_gen_620: ;set direction bits cbi x_axis_dir_port, x_axis_dir ;pre-clear all direction bits cbi y_axis_dir_port, y_axis_dir cbi z_axis_dir_port, z_axis_dir sbrc temp1, x_axis_direction_bit ;set x axis direction bit sbi x_axis_dir_port, x_axis_dir sbrc temp1, y_axis_direction_bit ;set y axis direction bit sbi y_axis_dir_port, y_axis_dir sbrc temp1, z_axis_direction_bit ;set z axis direction bit sbi z_axis_dir_port, z_axis_dir isr_move_gen_640: ;initialize counter1 ldi temp1, (1 << OCIE1A) ;prepare to enable counter interrupts ldi temp2, (1 << PSRSYNC) ;prepare to reset the timer0/timer1 prescalar out GTCCR, temp2 ;resets the timer0/timer1 prescalar sts TCNT1H, numzero ;clear timer1 sts TCNT1L, numzero pop ZH pop ZL rjmp isr_move_gen_exit isr_move_gen_exit: pop temp2 out SREG, temp2 pop temp2 pop temp1 sei reti isr_move_gen_700: cbi comm_status, now_moving ;clears now moving flag ldi temp1, (0 << OCIE1A) ;prepare to disable counter interrupts sts TIMSK1, temp1 ;disable counter interrupts cbi x_axis_ind_port, x_axis_ind ;turn off x axis indicator led cbi y_axis_ind_port, y_axis_ind ;turn off y axis indicator led cbi z_axis_ind_port, z_axis_ind ;turn off z axis indicator led rjmp isr_move_gen_exit ;--MAIN PROGRAM-------------------------------------------------------------------- reset: clr numzero ;CONFIGURE STACK ldi temp1, high(RAMEND) out SPH, temp1 ldi temp1, low(RAMEND) out SPL, temp1 ;CONFIGURE CLOCK ldi temp1, (1 << CLKPCE) sts clkpr, temp1 ;enables writing to the clock prescaler sts clkpr, numzero ;sets the clock prescaler to 1:1 ;INITIALIZE REGISTERS clr packetposition clr packetlength clr packetchecksum clr rcv_watchdog ;INITIALIZE PORTS ; set pull-ups. These get set first, when every pin is an input. ldi temp1, portb_pullups out PORTB, temp1 ldi temp1, portc_pullups out PORTC, temp1 ldi temp1, portd_pullups out PORTD, temp1 ; set directions. ldi temp1, portb_directions out DDRB, temp1 ldi temp1, portc_directions out DDRC, temp1 ldi temp1, portd_directions out DDRD, temp1 ; set initial values out comm_status, numzero sbi motor_enable_port, motor_enable ;disables motors at startup (low enable). ; initialize registers ;CONFIGURE USART0 ldi temp1, 0 ldi temp2, 12 ;19.6K @ 20MHz = 64, 115.2K @ 20MHz = 10, 115.2K @ 16MHz = 8, 76.8k @ 16MHz = 12 sts UBRR0H, temp1 ;19.6k baud at a clock of 20mhz sts UBRR0L, temp2 ldi temp1, (1<