The basic idea is that: X_steps * X_period = Y_steps * Y_period. So that both motors finish turning at the same time. I've used Timer0 and Timer1 with some asm code to make the pulses and PBP to work out the steps and periods.
To get a diagonal move one motor is set at one of the pre-define speeds(crawl,slow,fast..)
and the period of the other motor is calculated from the desired move.
To get the longest periods possible, the 'Prescales' of Timer0 and Timer1 are used if necessary. Timer0 is allocated to the Y axis because it's prescale goes to 'divide' by 256 and it's on the SHORTEST physical axis that you need the longest periods.
The code has tested well for a few days and, so far, is stable. It's far from gereral purpose code and is tuned to my home built XY plotter (scan area 16000 by 32000 steps so WORD variables can hold the data OK). I've also stripped out all the LCD, home/limit switch stuff and the input now is only by RS232 from a terminal at 9200 Baud.
I've posted it here because:
1. It may be a good starting point to writing your own XY plotter code - I can't find any code to make diagonal moves anywhere else?
2. Someone might spot better ways to doing this - advice and comments are welcome.
Finally - thank you very much Bruce; you got me started on interrupts and I'm now 'at ease' with them, simple asm and MPLAB.
The code is fairly well commented but I've worked with it so long that it's difficult to tell.
Regards Bill legge
' ***************************************************************************** ' * * ' * File...... 8722 Scanner_RS232 * ' * Purpose... Plotter/Scanner XY table with diagonal moves * ' * Date...... April 2010 by Bill legge * ' * * ' ***************************************************************************** ' 1. Outline: software for a XY plotter/scanner table, axis driven by stepper ' motors that can move at the same time to achieve diagonal plotting/scanning ' The main variables are: ' * Current location of the plot head: X_position, Y_position ' * Desired location: X_demand, Y_demand ' * Speed of move ' The X-demand, Y_demand and Speed words are input by RS232 @ 19200 Baud ' 2. MCU PIC18F8722. Board is Futurlec ET-BASE PIC8722 (ICD2) 10MHz Xtal ' 3. ETT relay board, opto isolated, active low, jumpers select PIC or Mach3 drive ' 4. ETT LED display, active low ' 5. ETT button input, active low ' 6. Chinese stepper dive, absolute max voltage is 24V, Use 20V. DB25 Pins: ' 1 X Drive To Port D0 ' 2 Y Enable To Port D5. High = enable, low = disable ' 3 Y Direction To Port D4. Low = move up, high = move down ' 4 Z Direction Unused ' 5 Z Step Unused ' 6 Z Enable Unused ' 7 X Direction To Port D1. Low = move right, high = move left ' 8 Y Drive To Port D3 ' 9 Relay Unused input ' 10 X Home To Port D6. Active low ' 11 Y Home To Port D7. Active low ' 12 Unused input ' 13 Unused input ' 14 X Enable To Port D2. High = enable, low = disable ' 15 Unused Input ' 16 ' 17 ' 18-25 Ground ' Mechanical ' Motor settings 200 steps/rev*16 pulses/step = 3,200 pulses/rev ' Belt pitch 2mm ' Pulley 36 teeth. 1rev = 36*2mm = 72mm ' X axis length 720mm = 10 revs = 32,000 pulses ' Y axis length 360mm = 5 revs = 16,000 pulses ' WORD [$ffff or 65,525] will hold maximum X or Y steps ' Motor Speeds ' MCU Osc is 4*10MHz. Period = 0.025uS. Tcy = 4*Period = 0.1uS ' Pulse periods: 800uS gives v.slow rotation and 100uS is maximum speed ' Use pulse rates that are 'powers of 2' so division/multiplication is fast ' Slug = 8192[Tcy] = 819.2uS = 819.2*3,200 = 2,621,440uS = 2.6S/rev (2^13) ' Slow = 4096[Tcy] = 409.6uS = 409.6*3,200 = 1,310,720uS = 1.3S/rev (2^12) ' Fast = 2048[Tcy] = 204.8uS = 204.8*3,200 = 655,360uS = 0.6S/rev (2^11) ' Race = 1024[Tcy] = 102.4uS = 102.4*3,200 = 327,680uS = 0.3S/rev (2^10) ' Timer1 in 16 Bit Mode, used for X axis ' Maximum count = $FFFF = 65,535 ' Maximum prescale = 8 ' Max count with max prescale = 65,535*8 = 524,280[Tcy] ' = 52,428uS = 52.4mS ' Timer0 in 16 Bit Mode, used for Y axis ' Maximum count = $FFFF = 65,535 ' Maximum prescale = 256 ' Max count with max prescale = 65,535*256 = 16,776,960[Tcy] ' = 1,677,696uS = 1677mS = 1.677 Seconds = 0.596Hz ' Straight Line Plots ' If the X or Y steps are zero, a special routine is used to avoid ' division by zero ' ***************************************************************************** ' * * ' * INCLUDE & DEFINES * ' * * ' ***************************************************************************** clear define OSC 40 ' Use HSPLL during compilation DEFINE INTHAND Timer_ints include "modedefs.bas" ' Include serout defines define HSER_RCSTA 90h ' RS232-1 TX is C7, RX is C6 define HSER_TXSTA 24h define HSER_BAUD 19200 define HSER_CLROERR 1 ' ***************************************************************************** ' * * ' * VARIABLES * ' * * ' ***************************************************************************** T0_delay var word BankA System ' Subtracted from 65,535 in asm T1_delay var word BankA System ' Subtracted from 65,535 in asm X_steps var word BankA System ' Steps to move = Demand - Current Y_steps var word BankA System ' Steps to move = Demand - Current lcount var byte BankA System ' Delay to make pulse out X_position var word ' Current X position Y_Position var word ' Current Y position X_demand var word ' Demanded X position Y_demand var word ' Demanded Y position Temp_delay var long ' Holds temporaty pulse period calculations Prescale var word ' Timer0/1 prescale to get long delays Slug con 8192 ' Delay = 8192[Tcy]. 2^13. 1rev = 2.62S Slow con 4096 ' Delay = 4096[Tcy]. 2^12. 1rev = 1.31S Fast con 2048 ' Delay = 2048[Tcy]. 2^11. 1rev = 0.65S Race con 1024 ' Delay = 1024[Tcy]. 2^10. 1rev = 0.33S Speed var word ' Selected before issuing D_move command X_drive var PORTD.0 X_dir var PORTD.1 ' Low = move right, high = move left X_ena var PORTD.2 ' High = enable, low = disable Y_drive var PORTD.3 Y_dir var PORTD.4 ' Low = move up, high = move down Y_ena var PORTD.5 ' High = enable, low = disable Heartbeat var PORTH.0 ' Green LED on MCU board ' ***************************************************************************** ' * * ' * INITIALISE * ' * * ' ***************************************************************************** ADCON1 = %00001101 ' A0, A1 analog, rest digital CMCON = %00000111 ' Comparators off, this frees up PORTF TRISC = %10000000 ' C0-C3 is output, C6 is HSER Tx, C7 is HSER Rx TRISD = %11000000 ' D0-D5 is stepper out, D6,D7 are HOME inputs Speed = Slow ' 409.6uS pulses goto Main ' Skip over ASM code ' ***************************************************************************** ' * * ' * ASSEMBLER CODE * ' * * ' ***************************************************************************** ASM Timer_ints bcf INTCON,7 ; Disable all interrupts btfss INTCON,TMR0IF ; Is Timer0 interrupt flag set? bra Check_T1 ; No so branch to Timer1 interrupt movff T0_delay+1,TMR0H ; Load Timer0 with delay_hb first movff T0_delay,TMR0L ; Load Timer0 with delay_lb last bcf INTCON,TMR0IF ; Clear Timer0 interrupt flag bsf PORTD,3 ; T0 is always Y axis call Short_pulse ; Make output pulse bcf PORTD,3 ; Low Y drive movf Y_steps,f ; Update STATUS register btfsc STATUS,Z ; Is low byte zero? decf Y_steps+1,f ; Yes so decrement high byte decf Y_steps,f ; Decrament low byte Check_T1 btfss PIR1,TMR1IF ; Is Timer1 interrupt flag set? bra Int_exit ; No so branch to end of ISR movff T1_delay+1,TMR1H ; Load Timer0 with delay_hb first movff T1_delay,TMR1L ; Load Timer0 with delay_lb last bcf PIR1,TMR1IF ; Clear Timer1 interrupt flag bsf PORTD,0 ; T1 is always X axis call Short_pulse ; Make output pulse bcf PORTD,0 ; low X drive movf X_steps,f ; Update STATUS register btfsc STATUS,Z ; Is low byte zero? decf X_steps+1,f ; Yes so decrement high byte decf X_steps,f ; Decrament low byte Int_exit bsf INTCON,7 ; Enable all interrupts retfie FAST ; Return automatic restore ; Pulse out subroutine Short_pulse ; Delay = 3.n+7 [0.1uS] movlw 0x62 ; $21 = 10uS, $62 = 30uS, $ff = 77.2uS movwf lcount Pulse_loop decfsz lcount ; Decrement the file Count goto Pulse_loop ; Loop if not zero return ;************************************************************************* ;* MAIN ASM * ;************************************************************************* ENDASM ASM _Main_ASM comf T0_delay+1 ; So that the computed delay comf T0_delay ; does not have to be subtracted comf T1_delay+1 ; from 65,535, the overflow comf T1_delay ; of Timer0 and Timer1 in 16 bit mode ; Set up interrupt conditions bcf RCON,7 ; Disable priority levels, IPEN=0 bsf INTCON,7 ; Enable all unmasked interrupts bsf INTCON,6 ; Enable all unmasked peripheral interrupts bcf INTCON,3 ; Disable PORTB interrupts bcf INTCON2,2 ; Timer0 overflow low priority bcf IPR1,0 ; Timer1 overflow low priority ; Load timers, T0CON and T1CON set in PBP movff T0_delay+1,TMR0H ; Load Timer0 with delay_hb first movff T0_delay,TMR0L ; Load Timer0 with delay_lb after hi byte movff T1_delay+1,TMR1H ; Load Timer1 with delay_hb first movff T1_delay,TMR1L ; Load Timer1 with delay_lb after hi byte bcf INTCON,TMR0IF ; Timer0 clear interrupt flag bsf INTCON,TMR0IE ; Timer0 enable interrupt bcf PIR1,TMR1IF ; Timer1 clear interrupt flag bsf PIE1,TMR1IE ; Timer1 enable interrupt Main_loop tstfsz X_steps+1 ; Is high byte zero? goto Test_Y ; Not zero so test Y_steps tstfsz X_steps ; Is low byte zero? goto Test_Y ; Not zero so test Y_steps bcf PIE1,TMR1IE ; Is zero so disable Timer1 interrupt bcf PIR1,TMR1IF ; Is zero so clear Timer1 flag movlw b'10000000' ; Stop timer1 running movwf T1CON ; Kill Timer1 now Test_Y tstfsz Y_steps+1 ; Is high byte zero? goto Main_loop ; Not zero so keep running tstfsz Y_steps ; Is low byte zero? goto Main_loop ; Not zero so do it all again bcf INTCON,TMR0IE ; Timer0 disable interrupt bcf INTCON,TMR0IF ; Timer0 clear interrupt flag movlw b'00000000' ; Stop Timer0 running movwf T0CON ; Kill timer0 now btfsc PIE1,TMR1IE ; Has Timer1 interrupt been killed? goto Main_loop ; Not zero so keep running All_done bcf INTCON,7 ; Disable all interrupts return ENDASM ' ***************************************************************************** ' * * ' * MAIN * ' * * ' ***************************************************************************** Main: Speed = Slow gosub RS232_input gosub D_move toggle Heartbeat pause 100 goto Main ' ***************************************************************************** ' * * ' * DIAGONAL MOVE. Move to X_demand, Y_demand and update X/Y_position * ' * * ' ***************************************************************************** D_move: if X_demand > 32000 then X_demand = 32000 ' X upper limit IF Y_demand > 16000 THEN Y_demand = 16000 ' Y upper limit if X_demand>=X_position then low X_dir ' Move right X_Steps = X_demand - X_position Else High X_dir ' Move left X_Steps = X_position - X_demand endif if Y_demand>=Y_position then low Y_dir ' Move up Y_Steps = Y_demand - Y_position ELSE HIGH Y_dir ' Move down Y_Steps = Y_position - Y_demand ENDIF if X_steps = 0 then ' Only Y_steps, only Timer0 needed T0_delay = Speed ' Timer0 delay is fixed T0CON = %10001000 ' Timer0 on, 16 bit, prescale=1 T1CON = %10000000 ' Timer1 off goto D_move_done ' No X steps to do so end endif IF Y_steps = 0 then ' Only X_steps, only Timer1 needed T1_delay = Speed ' Timer1 delay is fixed T1CON = %10000001 ' Timer1 on, 16 bit, prescale=1 T0CON = %00001000 ' Timer0 off goto D_move_done ' No Y steps to do so end endif if X_steps >= Y_steps then T1_delay = Speed ' Timer1 delay is fixed T1CON = %10000001 ' Timer1 on, 16 bit, prescale=1 Temp_delay = X_steps*Speed ' Calculate Timer0 delay Temp_delay = Temp_delay/Y_steps ' Delay needed without prescale in [Tcy] gosub Get_T0_prescale ' Now divide delay by prescale goto D_move_done else T0_delay = Speed ' Timer0 delay is fixed T0CON = %10001000 ' Timer0 on, 16 bit, prescale=1 Temp_delay = Y_steps*Speed ' Calculate Timer1 delay Temp_delay = Temp_delay/X_steps ' Delay needed without prescale in [Tcy] gosub Get_T1_prescale ' Now divide delay by prescale goto D_move_done endif D_move_done: CALL Main_ASM ' Make the pulses X_position=X_demand ' Move done so position = demand Y_position=Y_demand ' Move done so position = demand return ' ***************************************************************************** ' * * ' * SUB-ROUTINES * ' * * ' ***************************************************************************** Get_T0_Prescale: select case Temp_delay case is > 8388480 T0CON = %10000111 Prescale = 8 ; Divide by 2.2.2.2.2.2.2.2 case is > 4194240 T0CON = %10000110 Prescale = 7 ; Divide by 2.2.2.2.2.2.2 case is > 2097120 T0CON = %10000101 Prescale = 6 ; Divide by 2.2.2.2.2.2 case is > 1048560 T0CON = %10000100 Prescale = 5 ; Divide by 2.2.2.2.2 case is > 524280 T0CON = %10000011 Prescale = 4 ; Divide by 2.2.2.2 case is > 262140 T0CON = %10000010 Prescale = 3 ; Divide by 2.2.2 case is > 131070 T0CON = %10000001 Prescale = 2 ; Divide by 2.2 case is > 65535 T0CON = %10000000 Prescale = 1 ; Divide by 2 case else T0CON = %10001000 Prescale = 0 ; Divide by 1 end select Temp_delay = Temp_delay >> Prescale T0_delay = Temp_delay.word0 return Get_T1_Prescale: select case Temp_delay case is > 262140 T1CON = %10110001 Prescale = 3 ; Divide by 2.2.2 case is > 131070 T1CON = %10100001 Prescale = 2 ; Divide by 2.2 case is > 65535 T1CON = %10010001 Prescale = 1 ; Divide by 2 case else T1CON = %10000001 Prescale = 0 ; Divide by 1 end select Temp_delay = Temp_delay >> Prescale T1_delay = Temp_delay.word0 return ' ***************************************************************************** ' * * ' * RS232 INPUT. Get X_demand, Y_demand from terminal * ' * * ' ***************************************************************************** RS232_input: hserout ["X-demand 0 to 32000",13] hserout ["Y_demand 0 to 16000",13] hserout ["Speed 2000(fast) to 8000(slow)",13] hserout ["Enter X_demand, Y_demand, Speed",13] hserin [dec X_demand,dec Y_demand,dec Speed] hserout ["You entered ",dec X_demand," ",dec Y_demand," ",dec Speed,13] RETURN end
Re: I2CWRITE "Label" option - what does it do?
Exactly what is writen in manual :D
pedja089 Today, 01:48I2C Slave send 1 bit response after each byte received. If that bit is missing, then command will jump to label. If label is not present, it will just ignore it,...