Linistepper in C

Results 1 to 10 of 10

Thread: Linistepper in C

  1. #1
    Registered
    Join Date
    Jan 2011
    Location
    -
    Posts
    5
    Downloads
    0
    Uploads
    0

    Default Linistepper in C

    Hi,

    I've been dreaming for a long time to change some linistepper code just to add a few "features".
    But, i'm nowhere an expert in PIC assembly.
    So, i decide to rewrite linistepper to C. Thanks to Roman Black for good code commenting, making me who's bad in assembly can easily rewrite the thing in C

    Whats changed:
    - Move high/low power table from code to EEPROM lookup
    - Ignore ENABLE pin input.
    - Use step signal timeout to switch between low and high power.
    - Store stepping table to EEPROM.
    - Use inline assembly for pwm loop (maintains 333kHz pwm)
    - Disable runtime stepping mode change.
    - Stepping mode is initialized on startup only to improve calculation time between step.
    - Use quadrature input instead of STEP/DIR

    Simply put, this version will drop motor current after ~1 second if there is no signal received in that timeframe. Motor current will go to full power when next stepping is received.

    This 1 second timeout is changeable in source code.

    This linistepper-c also support quadrature input (Phase-A and Phase-B)
    From EMC2 manual:
    type 0: step/dir
    Two pins, one for step and one for direction. make-pulses must run at least twice for each step (once to set the step pin true, once to clear it). This limits the maximum step rate to half (or less) of the
    rate that can be reached by types 2-14. The parameters steplen and stepspace can further lower the maximum step rate. Parameters dirsetup and dirhold also apply to this step type.
    type 2: quadrature
    Two pins, phase-A and phase-B. For forward motion, A leads B. Can advance by one step every time make-pulses runs.
    The good thing with quadrature is your step can be as fast as your parallel-port. With step/dir, each step need to toggle between '1' and '0', so speed is cut by two.

    Also, it's possible to detect a single miss steps with quadrature, as it is a form of Gray-code.

    Btw, the step/dir code is also included in zip, but it's never tested.

    The bad things:

    1) The source code looks "hack"ish. This is due to:
    - My incompetence
    - SDCC workaround.

    2) The code generated by SDCC is slow. It insert BANKSEL instruction every here and there. I've included hand edited assembly generated by SDCC to remove this unnecessary call, just as an example on how to improve further.

    3)It's slower than the original linistepper in asm.
    With linistepper-asm, there's only ~20us delay before it output a new motors' current value.
    With SDCC compiled code, there's around ~50us before it output a new motors' current value.
    By editing the sdcc code and remove unneeded BANKSEL, "thinking" time now drop from 50us to ~30us.

    4)It might have bugs.
    This software is still untested by many, so there's probably a few bugs creeping inside. If you find any, please update it here so we can share the fixes.

    Thanks in advance.

    Similar Threads:
    Attached Files Attached Files


  2. #2
    Registered
    Join Date
    Oct 2005
    Location
    Australia
    Posts
    2392
    Downloads
    0
    Uploads
    0

    Default

    Very interesting! I recently did a simple conversion of the Lini v2 code from ASM to C although I am not sure if James put it up on the PICList page yet. My conversion was done quite quickly and doesn't have the PWM range so no 18th steps.

    Why use EEPROM for the tables?

    Good call on dropping the ability to change step modes when running, nobody uses that feature anyway.

    I'm not sure about the quadrature input, it should be pretty fast using standard step signal anyway if you are using a 16MHz or 20MHz xtal.

    Do you have a link to a text file for your source code? Many people don't like downloading a ZIP because of potential viruses etc, and the source code should only be tiny anyway so it doesn't need ZIP compression.



  3. #3
    Registered
    Join Date
    Jan 2011
    Location
    -
    Posts
    5
    Downloads
    0
    Uploads
    0

    Default

    Reason for eeprom:
    - most pic have it and end up not used anyway.
    - speed wise, it's similar to reading from array in flash. (i don't see any datasheet about flash vs eeprom data access, so this is my assumption)
    - the only way to improve speed is to follow just like what you did in assembly, make switch case, then declare the value 1 by 1.
    - the other "unrelated" reason is to make it tunable. For example, use serial port to change the value, etc (eeprom is writable on runtime).

    For the quadrature, it's an advantage if :
    - Slow PC. To make 1200 steps per second, pc need to toggle the 1 single bit for 1200 time in 1 second. With quadrature, it only need to toggle 2 bit, each for 600time/seconds (2x600=1200 step).
    - Slow PIC (the case of this code that is). If the PC is too fast, miss steps can happen. With quadrature, it can detect 1 miss steps (though it do nothing right now. Should be easy to trigger fault, etc).
    And because quadrature signal doesn't return zero (NRZ), user doesn't need to mess with stepspace (min time for '1'), steplength (min time for '0'), etc.

    As for the zip file, it got others like Makefile, hex file, linkers, etc.

    Code:
    /**
    * LiniStepper.c  
    *
    * For PIC16F628A code
    * 
    * LiniStepper.c Changelog
    * 	- Literal conversion from assembly to C
    * 	- Move high/low power table from code to EEPROM lookup
    * 	- Ignore ENABLE pin input. 
    * 	- Use step signal timeout to switch between low and high power. 
    * 	- Store stepping table to EEPROM.
    * 	- Use inline assembly for pwm loop
    * 	- Disable runtime stepping mode change. 
    * 	- Stepping mode is initialized on startup only to improve calculation time between step. 
    * 	- Use quadrature input instead of STEP/DIR
    *
    * Based on LiniStepper.asm v1.0
    * Copyright Aug 2002 Roman Black   http://www.romanblack.com
    *
    * PIC assembler code for the LiniStepper stepper motor driver board.
    * 200/400/1200/3600 steps
    *
    * LiniStepper.asm Changelog
    * v1.0	Seems to be working ok for now, few minor things need improving;
    *		* touch up phase switching for direction -> 0
    *		* table system is messy, can reduce in size a LOT if needed
    *		* low-power mode doesn't microstep, only halfstep
    *		* no easy way to step motor from within PIC softweare
    */
    
    #include "pic16f628a.h"
    
    unsigned int __at 0x2007 __CONFIG = _CP_OFF & _WDT_OFF & _PWRTE_ON & _HS_OSC & _MCLRE_ON & _BODEN_OFF & _LVP_OFF;
    
    // Switch to low power after TIMEOUT x 128milisecond 
    #define TIMEOUT		10
    
    // Stepping mode
    #define MODE_3600	0b0011
    #define MODE_1200	0b0010
    #define MODE_400	0b0001
    #define MODE_200	0b0000
    
    /** Input pinout
    * ---x----	RA4	* mode bit1	( 10=1200 step	11=3600 step
    * ----x---	RA3	* mode bit0	 00=200 step	01=400 step )
    * -----x--	RA2	* power		(ignored, use timeout instead)
    * ------x-	RA1	* direction
    * -------x	RA0	* step 
    */
    #define MASK_MODE1	0b00010000
    #define MASK_MODE0	0b00001000
    #define MASK_POWER	0b00000100
    #define MASK_B	0b00000010
    #define MASK_A	0b00000001
    
    // Overflow bit for step calculation
    #define STEP_OVERFLOW	0b10000000
    
    // Relative Address of high power table from EEPROM
    #define ADDR_POWER_HI	0x00
    
    // Relative Address of low power table from EEPROM
    #define ADDR_POWER_LO	2 * 36 + ADDR_POWER_HI 
    
    // Relative Address of stepping table from EEPROM
    #define ADDR_STEPPING	36 + ADDR_POWER_LO 
    
    // Column of stepping table as formatted in EEPROM
    #define TBL_STEP_MIN	0
    #define TBL_STEP_MAX	1
    #define TBL_STEP_INC	2
    
    /**
     * Correct sequence for reading the EEPROM is:
     * @ Set address
     * @ Set RD bit
     * @ Read value from EEDATA
     *
     * This expression does exactly that, first setting EEADR and RD
     * before returning the value of EEDATA.
     * 
     * Source: http://burningsmell.org/pic16f628/src/0009-eeprom.c
     */
    #define EEPROM_READ(ADDR) (EEADR=ADDR,RD=1,EEDATA)
    
    /**
     * NOTE: This variable must be store in BANK 0 for pwm_loop to works.
     * SDCC 3.0 crash with internal compiler error when absolute addressing is used.
     * Workaround: use linker trick. 
     * See section "UDL_linistepper_0" in 16f628a.lkr to define RAM address
     */
    __data static unsigned char inputs;		// store new input pins
    __data static unsigned char inputs_last;	// store last states of input pins
    
    __data static unsigned char current1;		// for current tween pwm
    __data static unsigned char current2;		// for current tween pwm
    __data static unsigned char phase;		// stores the 4 motor phase pins
    
    volatile __data static unsigned char timer_count;	// count timer1 overflow for larger delay 
    							// NOTE: declared volatile due to sdcc optimizer 
    							// can't track value changed from inline assembly
    
    __data static unsigned char step;		// (0-71) ustep position
    __data static unsigned char steptemp;		// for step calcs
    
    __data static unsigned char mode;		// store stepping mode
    
    __data static unsigned char eeprom_addr;	// for eeprom table lookup (max addressable=0xff)
    
    __data static unsigned char step_max;		// max step for selected mode
    __data static unsigned char step_min;		// min step for selected mode
    __data static unsigned char step_inc;		// step increment for selected mode
    
    __data static unsigned char phase_a;			// for calcs
    __data static unsigned char phase_b;			// for calcs
      
    
    void handler(void) __interrupt 0 {
      // put additional interrupt code here...
    } 
    
    /**
    * pwm() the fast pwm loop
    *	
    * the 2 target currents were set in the move_motor code.
    *
    * what this function does is spend 2 time units at current2,
    * and 1 time unit at current1.
    * actual is 8 clocks at current2
    * and 4 clocks at current 1
    * total 12 cycles, so 333 kHz with 16MHz resonator.
    *
    * this gives an average pwm current of 2/3 the way between
    * current2 and current1.
    *
    * the routine is kept short to keep pwm frequency high, so it
    * is easy to smooth in hardware by the ramping caps.
    *
    * IMPORTANT! is timed by clock cycles, don't change this code!
    * it also checks for any change in input pins here
    *
    * the modified code here was originally supplied by Eric Bohlman (thanks!)
    *
    * Code below is the equivalent pwm_loop in C (without timeout count).
    * (SDCC 3.0) :	~24 cycles for current2, 
    * 		~12 cycles for current1 
    * 
    * do {
    *    PORTB = current2;    			// send current2 to motor
    *    inputs = PORTA;      			// read input
    *    if((inputs_last ^ inputs) & ~MASK_POWER) {	// break loop except power pin change      
    *      break;
    *    }  
    *    PORTB = current1;				// send current1 to motor
    *  } while (!TMR1IF);				// loop unless input timeout
    * 
    */
    static void pwm(void) {    
      timer_count = TIMEOUT;			// reload delay counter
      TMR1H = 0;					// reset timer value
      TMR1L = 0;  
      /* T1CON 
      ;  x-------		; bit7 	unimplemented
      ;  -x------		; bit6	unimplemented
      ;  --x-----		; bit5 	TMR1 prescale (10=1:4, 11=1:8)
      ;  ---x----		; bit4 	TMR1 prescale (00=1:1, 01=1:2)
      ;  ----x---		; bit3	timer 1 osc (0=osc on, 1=osc off)
      ;  -----x--		; bit2	tmr1 sync (ignored when tmr1 source=0)
      ;  ------x-		; bit1	tmr1 source (0=internal, 1=external)
      ;  -------x		; bit0	tmr1 on (1=on, 0=off) */
      T1CON=0b00110001;				// start timer 1 with prescaler 1:8  
      __asm						// NOTE:make sure we're already in BANK0 before start 
       pwm_loop:					// main entry!
    						// better to enter at current2 for motor power.
         movf _current2,w				// output current2 to motor     
         movwf PORTB				// send to motor! 
         nop					// (8 cycle for current2)
         btfsc PIR1,0				// check for timer1 overflow            
         goto pwm_timeout
    						// now test input pins
         movf PORTA,w				// get pin values from port       
         xorwf _inputs_last,w			// xor to compare new inputs with last values
         andlw b'00000011'				// only check for step change (MASK_A | MASK_B)
         skpz					
         goto pwm_final				// not zero, one or more input pins have changed!
    						// else, output current1 to motor
         movf _current1,w				// get currents and phase switching
         movwf PORTB				// send to motor! (4 cycles for current1)
         goto pwm_loop				// keep looping
       pwm_final:
         xorwf _inputs_last,w			
         movwf _inputs				// restore xored value back to the orig inputs value  
         goto pwm_exit
       pwm_timeout: 				// count how many time timer1 have overflow
         bcf PIR1,0					// clear timer1 overflow flag
         decfsz _timer_count,f			// loop until timer_count reach zero     
         goto pwm_loop				
       pwm_exit:
      __endasm;  
      
      if(!timer_count) {
        inputs &= ~MASK_POWER;			// drop to low power
      } else {        
        inputs |= MASK_POWER;			// turn on high power
      }  
      TMR1ON = 0;      				// stop the timer
    }
    
    /**
    *  new_inputs()   input change was detected
    *
    * when we enter here:
    * - inputs have just changed
    * - inputs_last	contains last PORTA inputs values
    * - inputs	contains new PORTA inputs values
    *
    * Quadrature is simply 2 bit gray code
    * Only 1 bit is changed for every step
    * Forward sequence = AB:00->01->11->10->00->01...
    */
    static void new_inputs(void) {     
      phase_a = (inputs & MASK_A);			// phase_a=1 if A=1, else 0
      phase_b = (inputs & MASK_B) >> 1;		// phase_b=1 if B=1, else 0
      if((inputs ^ inputs_last) & MASK_A) {		// Check if A changed from previous
        if((inputs ^ inputs_last) & MASK_B) {	// A changed from previous. Check for B 
          // invalid state or missing step 00->11, 11->00, 01->10, 10->01
          // B should not change in single step
          // TODO: trigger fault/emergency stop, etc
        } else if(phase_b == phase_a) { 		
          step += step_inc;				// forward AB:10->00 or 01->11
        } else {
          step -= step_inc;				// reverse AB:00->10 or 11->01         
        }
      } else {
        if((inputs ^ inputs_last) & MASK_A) {	// B changed from previous, Check for A 
          // invalid state or missing step 00->11, 11->00, 01->10, 10->01
          // A should not change in single step
          // TODO: trigger fault/emergency stop, etc
        } else if(phase_b == phase_a) { 		
          step -= step_inc;				// reverse AB:10->11 or 01->00      
        } else {					 
          step += step_inc;				// forward AB:11->10 or 00->01      
        } 
      }
      if(step & STEP_OVERFLOW) {		// test for roll under <0	    
        step = step_max;			// rolled under so force to max step
      } else if(step > step_max) {			// test for roll over >max step
        step = step_min;			// rolled over so force to step minimum
      } 
      inputs_last = inputs;				// save a copy of the inputs
    }
    
    /**
    * move_motor() 	sets 8 output pins to control motor
    *
    * this code controls the phase sequencing and current
    * settings for the motor.
    *
    * there are always 72 steps (0-71)
    *
    * we can split the main table into 2 halves, each have identical
    * current sequencing. That is only 12 entries for hardware current.
    *
    * Then can x3 the table to get 36 table entries which cover all 72 steps.
    * the 36 entries jump to 36 code pieces, which set the current values
    * for the 2 possible tween steps... We need 2 current values, one
    * for the x2 value and one for the x1 value.
    *
    * PHASE SEQUENCING (switch the 4 coils)
    *
    * there are 4 possible combinations for the phase switching:
    * each have 18 steps, total 72 steps:
    *
    *	A+ B+	range 0		step 0-17
    *	A- B+	range 1		18-35
    *	A- B-	range 2		36-53
    *	A+ B-	range 3		54-71
    *
    *
    */
    static void move_motor(void) {
      steptemp = step;				// find which of the 4 ranges we are in 
      if(steptemp - 35 > 0) {			// step is between 36-71   
        steptemp -= 36; 				
        if(steptemp - 17 > 0) {			// step is in range 3
          phase = 0b00000110;			// 0110 = A+ B-
        } else {      				// step is in range 2
          phase = 0b00001010;			// 1010 = A- B-
        }    
      } else {    					// step is between 0-35
        if(steptemp - 17 > 0) {			// step is in range 1
          phase = 0b00001001;			// 1001 = A- B+
        } else {					// step is in range 0
          phase = 0b00000101;			// 0101 = A+ B+
        }
      }
      /*-------------------------------------------------
      ; at this point we have the phasing done and stored as the last
      ; 4 bits in var phase; 0000xxxx
      
      ; now we have 36 possible current combinations, which we can do
      ; by reading from EEPROM.
    
      ; as we have 2 power modes; full and low power, we need 2 tables.
      -------------------------------------------------*/	
      if(inputs & MASK_POWER) {			// high power selected    
        eeprom_addr = (steptemp + steptemp) + ADDR_POWER_HI;
        current2 = phase | EEPROM_READ(eeprom_addr);
        current1 = phase | EEPROM_READ(eeprom_addr + 1);
        
      } else {					// low power is selected
        eeprom_addr = steptemp + ADDR_POWER_LO;
        current2 = phase | EEPROM_READ(eeprom_addr);
        current1 = current2;     
      }  
    }
    
    /**
    *  setup()   sets port directions and interrupt stuff etc,
    */
    static void setup(void) {  
      /* OPTION_REG
      ;  x-------		; 7, 0=enable, 1=disable, portb pullups
      ;  -x------		; 6, 1=/, int edge select bit
      ;  --x-----		; 5, timer0 source, 0=internal clock, 1=ext pin.
      ;  ---x----		; 4, timer0 ext edge, 1=\
      ;  ----x---		; 3, prescaler assign, 1=wdt, 0=timer0
      ;  -----x--		; 2,1,0, timer0 prescaler rate select
      ;  ------x-		;   000=2, 001=4, 010=8, 011=16, etc.
      ;  -------x		; */
      OPTION_REG = 0b10000010;
      TRISA = 0b00011111;				// all 5 port A pins are inputs (1=input, 0=output)  
      TRISB = 0;					// all 8 port B are outputs (1=input, 0=output)
      VRCON = 0;					// disable Vref
      /* INTCON 
      ;  x-------		; bit7 	GIE global int enable, 1=enabled
      ;  -x------		; bit6	EE write complete enable, 1=en
      ;  --x-----		; bit5 	TMR0 overflow int enable, 1=en
      ;  ---x----		; bit4 	RB0/INT enable, 1=en
      ;  ----x---		; bit3	RB port change int enable, 1=en
      ;  -----x--		; bit2	TMR0 int flag bit, 1=did overflow and get int
      ;  ------x-		; bit1	RB0/INT flag bit, 1=did get int
      ;  -------x		; bit0	RB port int flag bit, 1=did get int */
      INTCON=0;  
      PIE1 = 0;					// disable pi etc
      T1CON = 0;					// disable timer1
      T2CON = 0;					// disable timer2
      CCP1CON = 0;					// disable CCP module
      CMCON = 0b00000111;				// disable comparators
      PORTB = 0;					// clear PORTB   
      PORTA = 0;					// clear PORTA    
      step = 0;					// reset step value  
      inputs_last = 0;  
      inputs = PORTA;				// get initial value for inputs  
      mode = (inputs & (MASK_MODE1 | MASK_MODE0)) >> 3;	// find which of the 4 modes we are in
      eeprom_addr = (mode + mode + mode) + ADDR_STEPPING;	// each mode store 3 value: min,max,increment
      step_inc = EEPROM_READ(eeprom_addr + TBL_STEP_INC);
      step_min = EEPROM_READ(eeprom_addr + TBL_STEP_MIN);
      step_max = EEPROM_READ(eeprom_addr + TBL_STEP_MAX);      
    }
    
    /**
    *  Main 
    */
    void main(void) {
      setup();					// do initial setup for ports and ints and stuff
      while(1) {     						
        move_motor();				// will set the motor current based on step value
        pwm();					// move the motor and loop until input changed 
        new_inputs(); 				// read new inputs and set the step value    
      }
    
    }
    This is table in EEPROM. There's no way to declare eeprom data with SDCC, so i end up with assembly. sdcc also can only "compile" c code to asm. The rest is done by gpasm or mpasm, etc.

    Code:
    	list    p=16f628a
    	include "p16f628a.inc"
    	org 0x2100	; change this to eeprom address of your processor
    
    
    	;-------------------------------------------------
    	; HIGH POWER TABLE
    	;-------------------------------------------------
    	; here are the 36 value pairs for the high power table.
    	;
    	; CURRENT INFO.
    	; hardware requires that we send the entire 8 bits to the motor
    	; at one time, to keep pwm fast.
    
    	; ----xxxx,  where xxxx is the coils on/off phasing (done)
    	; xxxx----,  where xxxx is the current settings for the A and B phases;
    	; xx------,  where xx is current for A phase
    	; --xx----,  where xx is current for B phase
    
    	; hardware currents for 6th stepping have 4 possible values;
    	; 00  =  0% current
    	; 01  =  25% current
    	; 10  =  55% current
    	; 11  =  100% current
    	;
    	;-------------------------------------------------
    	; PWM INFO.
    	; hardware gives us 6th steps, or 1200 steps/rev.
    	; to get 3600 steps/rev we need TWO more
    	; "tween" steps between every proper hardware 6th step.
    
    	; to do this we set 2 currents, current1 and current2.
    	; then we do FAST pwm, with 2 time units at current2,
    	; and 1 time unit at current1.
    	; this gives a current which is between the two currents,
    	; proportionally closer to current2. (2/3 obviously)
    	; this gives the ability to get 2 evenly spaced "tween" currents
    	; between our hardware 6th step currents, and go from 1200 to 3600.
    
    	; the next 36 value pairs set the 2 currents desired, then
    	; loaded to a fast-pwm loop (same loop used for all currents)
    	; which modulates between the 2 currents and gives final
    	; output current.
    	;-------------------------------------------------
    	; High Power Table				current: current2 : current1
    	de b'11000000',b'11000000' ; 00: (6th step),	current: 100,000 : 100,000 
    	de b'11000000',b'11010000' ; 01: (tween step),	current: 100,000 : 100,025
    	de b'11010000',b'11000000' ; 02: (tween step),	current: 100,025 : 100,000
    	de b'11010000',b'11010000' ; 03: (6th step),	current; 100,025 : 100,025
    	de b'11010000',b'11100000' ; 04: (tween step),	current: 100,025 : 100,055
    	de b'11100000',b'11010000' ; 05: (tween step),	current: 100,055 : 100,025
    	de b'11100000',b'11100000' ; 06: (6th step),	current; 100,055 : 100,055
    	de b'11100000',b'11110000' ; 07: (tween step),	current: 100,055 : 100,100
    	de b'11110000',b'11100000' ; 08: (tween step),	current: 100,100 : 100,055
    	de b'11110000',b'11110000' ; 09: (6th step),	current; 100,100 : 100,100
    	de b'11110000',b'10110000' ; 10: (tween step),	current: 100,100 : 055,100
    	de b'10110000',b'11110000' ; 11: (tween step),	current: 055,100 : 100,100
    	de b'10110000',b'10110000' ; 12: (6th step),	current; 055,100 : 055,100
    	de b'10110000',b'01110000' ; 13: (tween step),	current; 055,100 : 025,100
    	de b'01110000',b'10110000' ; 14: (tween step),	current; 025,100 : 055,100
    	de b'01110000',b'01110000' ; 15: (6th step),	current; 025,100 : 025,100
    	de b'01110000',b'00110000' ; 16: (tween step),	current; 025,100 : 000,100
    	de b'00110000',b'01110000' ; 17: (tween step),	current; 000,100 : 025,100
    	de b'00110000',b'00110000' ; 18: (6th step),	current; 000,100 : 000,100
    	de b'00110000',b'01110000' ; 19: (tween step),	current; 000,100 : 025,100
    	de b'01110000',b'00110000' ; 20: (tween step),	current; 025,100 : 000,100
    	de b'01110000',b'01110000' ; 21: (6th step),	current; 025,100 : 025,100
    	de b'01110000',b'10110000' ; 22: (tween step),	current; 025,100 : 055,100
    	de b'10110000',b'01110000' ; 23: (tween step),	current; 055,100 : 025,100
    	de b'10110000',b'10110000' ; 24: (6th step),	current; 055,100 : 055,100
    	de b'10110000',b'11110000' ; 25: (tween step),	current; 055,100 : 100,100
    	de b'11110000',b'10110000' ; 26: (tween step),	current; 100,100 : 055,100
    	de b'11110000',b'11110000' ; 27: (6th step),	current; 100,100 : 100,100
    	de b'11110000',b'11100000' ; 28: (tween step),	current; 100,100 : 100,055
    	de b'11100000',b'11110000' ; 29: (tween step),	current; 100,055 : 100,100
    	de b'11100000',b'11100000' ; 30: (6th step),	current; 100,055 : 100,055
    	de b'11100000',b'11010000' ; 31: (tween step),	current; 100,055 : 100,025
    	de b'11010000',b'11100000' ; 32: (tween step),	current; 100,025 : 100,055
    	de b'11010000',b'11010000' ; 33: (6th step),	current; 100,025 : 100,025
    	de b'11010000',b'11000000' ; 34: (tween step),	current; 100,025 : 100,000
    	de b'11000000',b'11010000' ; 35: (tween step),	current; 100,000 : 100,025  
    
    
    
    	;-------------------------------------------------
    	; LOW POWER TABLE
    	;-------------------------------------------------
    	; as low power mode is for wait periods we don't need to
    	; maintain the full step precision and can wait on the
    	; half-step (400 steps/rev). This means much easier code tables.
    	; The nature of the board electronics is not really suited
    	; for LOW power microstepping, but it could be programmed here
    	; if needed.
    	;
    	; NOTE!! uses my hi-torque half stepping, not normal half step.
    	;
    	; doing half stepping with the 55,25 current values gives;
    	; 55+25 = 80
    	; max current 100+100 = 200
    	; typical (high) current 100+50 = 150
    	; so low power is about 1/2 the current of high power mode,
    	; giving about 1/4 the motor heating and half the driver heating.
    
    	; for now it uses only half-steps or 8 separate current modes.
    	; we only have to use 4 actual current modes as
    	; the table is doubled like the table_highpower is.
    	;
    	; NOTE!! This table is truncated to single value due to out of space in EEPROM
    	; Expand to value pairs if you want to use full size table.
    	;-------------------------------------------------      
    	; current 	: current1 = current2
    	de b'10010000'	; 0-8: current: 55.25
    	de b'10010000'
    	de b'10010000'
    	de b'10010000'
    	de b'10010000'
    	de b'10010000'
    	de b'10010000'
    	de b'10010000'
    	de b'10010000'
    	de b'01100000'	; 9-17: current 25.55
    	de b'01100000'
    	de b'01100000'
    	de b'01100000'
    	de b'01100000'
    	de b'01100000'
    	de b'01100000'
    	de b'01100000'
    	de b'01100000'
    	de b'01100000'	; 18-26: current 25.55
    	de b'01100000'
    	de b'01100000'
    	de b'01100000'
    	de b'01100000'
    	de b'01100000'
    	de b'01100000'
    	de b'01100000'
    	de b'01100000'
    	de b'10010000'	; 19-26: current 55.25
    	de b'10010000'
    	de b'10010000'
    	de b'10010000'
    	de b'10010000'
    	de b'10010000'
    	de b'10010000'
    	de b'10010000'
    	de b'10010000'
    
    	;-------------------------------------------------
    	; STEPPING TABLE
    	;-------------------------------------------------
    	; This table store stepping value for various mode
    	; Mode 00 : 200 steps (72/18),4 valid step: 9,27,45,63
    	; Mode 01 : 400 steps (72/9),8 valid step: 4,13,22,31,40,49,58,67
    	; Mode 10 : 1200 steps (72/3), valid step is mod 3 (0,3,6,9 etc)
    	; Mode 11 : 3600 steps (72/1), step increment by 1
    	;-------------------------------------------------
    	; step min, step max, step increment 
    	de d'9',d'63',d'18'
    	de d'4',d'67',d'9'
    	de d'0',d'69',d'3'
    	de d'0',d'71',d'1'
    	end




  4. #4
    Registered
    Join Date
    Jan 2011
    Location
    -
    Posts
    5
    Downloads
    0
    Uploads
    0

    Default

    I've found 1 bug, the power keep toggling between high and low when idle, and this might cause the stepper to rotate.

    The fix is simple, don't exit pwm_loop when timeout if we're already in low power mode.

    Code:
    pwm_loop:					// main entry!
    						// better to enter at current2 for motor power.
         movf _current2,w				// output current2 to motor     
         movwf PORTB				// send to motor! 
         nop					// (8 cycle for current2)
         btfsc PIR1,0				// check for timer1 overflow            
         goto pwm_timeout
    						// now test input pins
         movf PORTA,w				// get pin values from port       
         xorwf _inputs_last,w			// xor to compare new inputs with last values
         andlw b'00000011'				// only check for step change (MASK_A | MASK_B)
         skpz					
         goto pwm_final				// not zero, one or more input pins have changed!
    						// else, output current1 to motor
         movf _current1,w				// get currents and phase switching
         movwf PORTB				// send to motor! (4 cycles for current1)
         goto pwm_loop				// keep looping
       pwm_final:
         xorwf _inputs_last,w			
         movwf _inputs				// restore xored value back to the orig inputs value  
         goto pwm_exit
       pwm_timeout: 				// count how many time timer1 have overflow
         bcf PIR1,0					// clear timer1 overflow flag
         decfsz _timer_count,f			// loop until timer_count reach zero     
         goto pwm_loop
         btfss _inputs,2				// continue loop, already in low power
         goto pwm_loop
       pwm_exit:
      __endasm;
    Attached is the fixed version.

    Attached Files Attached Files


  5. #5
    Registered
    Join Date
    Oct 2005
    Location
    Australia
    Posts
    2392
    Downloads
    0
    Uploads
    0

    Default

    Reason for eeprom:
    - most pic have it and end up not used anyway.
    - speed wise, it's similar to reading from array in flash. (i don't see any datasheet about flash vs eeprom data access, so this is my assumption)
    - the only way to improve speed is to follow just like what you did in assembly, make switch case, then declare the value 1 by 1.
    - the other "unrelated" reason is to make it tunable. For example, use serial port to change the value, etc (eeprom is writable on runtime).
    Very clever. Being able to write different tables to the eeprom is a great idea. Might I suggest that it could be synergised with the ability to write the number of usteps as an eeprom value too, so a user could load in a 10 ustep value and a table of 10th usteps, instead of the standard 18 ustep and table of 18ths? Just an idea.

    If the SDCC compiler can't load eeprom data from a text file and you are using MPASM anyway for the programming of the PIC, could you include the eeprom table as a text file etc and just get MPASM to load it into the PIC? However that could cause the project to be more complex and people really like simplicity, one of the benefits of the .ASM version is they only need 1 file to have the whole project.

    Anyway I appreciate and respect the large amount of work you have done, would you like it added to the Linistepper open-source pages on the Massmind (PIClist) website? I'm sure if you email James there he would put your code up and/or include a link to your page (as you prefer).

    You should also add yor name into the source code as the author of that C version.

    How committed are you to the SDCC compiler? I prefer the MikroC PRO compiler for PIC 16F and 18F, it is free for small projects (under 2k ROM) which will do all your PC16F628A work. Actually there is a limit on the RAM as well as the ROM in the free version but it still does 16F628 pretty good if I remember. The MikroC one is quite powerful.

    And, is that your web page at Burningsmell.org? If so, here's a for making such an impressive site!



  6. #6
    Registered
    Join Date
    Jan 2011
    Location
    -
    Posts
    5
    Downloads
    0
    Uploads
    0

    Default

    Yes, it's possible to specify arbitrary table, though one might need to modify hard-coded definition of table address. Or better yet, moved it and store in eeprom (ie: start address for high power, end address of high power table, etc). Currently, linistepper-c only capable to use custom step size,max step, etc.

    But, for the time being i think the capability of PIC16F628A is already fully utilized so one might need to move on to higher end of microcontroller to add more features.

    One of the reason being is, the pwm loop already operate on tight clock cycle. There's simply no way for interrupt to happen without disturbing the "averaging" current circuit. So, one either need to :
    - remove the pwm loop and operate with limit of 1200 steps, or
    - offload pwm to hardware. The simplest circuit is probably using quad 2:1 multiplexer (something like 74hc157).
    This idea need to use 9 bit output from microcontroller. 4 bit store current1, 4bit store current2. The single 1 bit is pwm with 33% duty cycle at freq 333kHz, connected to multiplexer select bit. This multiplexer then will do the bit flipping (4 bit current1 and 4 bit current2) based on pwm cycle. As everything is done in hardware, there should be lot of clock cycle available to waste The downside is it need microcontroller with lot of i/o, more than what pic16f628a currently provides.

    The other reason is again, lack of i/o. There's no more i/o available for communication. It would be nice if linistepper could be use for wider range of hardware. Eg: able use it from usb,serial, etc. Or, maybe there's someone creative enough to make close loop stepper...

    As for sdcc, i use it because it's already installed on my pc. After struggling a while with sdcc, I do tried to use free cc5x, but the code quality is just as good as sdcc (if not worse). In the end, i just use sdcc and try to live with it
    The good thing with sdcc is it's processor neutral (or atleast that what i think it's supposed to be). So, it should be easy if ones decide to upgrade to PIC18Fxxx, or perhaps other kind of processor (ie: 8051, etc). Of course, the register and config is changed but the syntax should stay (i hope...). Either way, i think i just stay with sdcc for a time being. Besides, it's still on "experimental status" with pic16fxxx. The pic18fxxx series should generate better code quality.

    As for MikroC PRO, that's the first time i heard about it. Sure, by looking screenshot from website it's a powerful tools. But it is a bit pricy for hobby things. Right now, i only use kwrite (similar to notepad/wordpad in windows), command console to compile and simulator from oshonsoft (trial 40 times). Should be enough for now, as i just started with this pic thing.

    As for the burningsmell website, no it's not from me. Just a random website from google as i search for guide. And as i do copy his macro, i should to give him proper credits

    Oh, for the author name, i should put proper legal license too (MIT license). And as this is a derivative works, I'll also include your name in the copyright. This is MIT license template:
    Code:
     Permission is hereby granted, free of charge, to any person obtaining a 
     copy of this software and associated documentation files (the “Software”),
     to deal in the Software without restriction, including without limitation 
     the rights to use, copy, modify, merge, publish, distribute, sublicense,
     and/or sell copies of the Software, and to permit persons to whom the 
     Software is furnished to do so, subject to the following conditions:
    
     The above copyright notice and this permission notice shall be 
     included in all copies or substantial portions of the Software.
    I hope that's is ok with you.

    Finally, feel free for anyone to mirror the works. I would be happy if more people interested and use this code.

    Also, i've attached a "cleaned" version. With proper copyright and license. Please use this file for mirrors and distribution.

    Thanks.



    Oh, i also included linistepper-stepdir.hex in those zipfile. If there's no bug, it should work just like linistepper asm version, except the motor will be cooler. But that can only happen if the motors has lot of idle in between operation.

    Attached Files Attached Files
    Last edited by hazimin; 01-23-2011 at 01:46 PM.


  7. #7
    Registered mmcp42's Avatar
    Join Date
    Dec 2010
    Location
    UK
    Posts
    327
    Downloads
    0
    Uploads
    0

    Default

    nice looking piece of code
    I'm surprised how small it is!
    tried to download the zip in your last post
    seem to get a broken link?



  8. #8
    Registered
    Join Date
    Jan 2011
    Location
    -
    Posts
    5
    Downloads
    0
    Uploads
    0

    Default

    Oops, sorry.
    Attachment have been reuploaded.

    Please try again. Should be ok now.



  9. #9
    Registered mmcp42's Avatar
    Join Date
    Dec 2010
    Location
    UK
    Posts
    327
    Downloads
    0
    Uploads
    0

    Default

    crikey that was quick!
    burnt my fingers keeping up with you!

    thanks



  10. #10
    Registered James Newton's Avatar
    Join Date
    May 2005
    Location
    USA
    Posts
    1397
    Downloads
    0
    Uploads
    0

    Default

    Nice work!

    I've added both versions to the modifications page at:
    piclist.com/techref/io/stepper/linistep/mods.htm

    And I will post a quick note on the facebook group.

    Thanks for sharing!



Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  


About CNCzone.com

    We are the largest and most active discussion forum for manufacturing industry. The site is 100% free to join and use, so join today!

Follow us on


Our Brands

Linistepper in C

Linistepper in C