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