Note: This C code is not an exact replacement for the current, well tested, production Linistepper asm code, which you will find on the main page (click on "Linistepper" above). The differences are:
Hazimin says:
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. 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.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).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.
Also:
See:
See also:
/** * LiniStepper-quadrature.c - stepper motor driver board for PIC16F628A * * Copyright (C) 2011 Hazimin Fauzi <hazimin@gmail.com> * Copyright (C) 2011 Roman Black <http://www.romanblack.com> * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * 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. * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * * 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 btfss _inputs,2 // continue loop, already in low power 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 } }
Because SDCC can't read EEPROM tables directly, the step table data is provided as a seperate asm file:
; ; eepromtbl.asm - A collection of data definition to be stored in eeprom ; ; Copyright (C) 2011 Hazimin Fauzi <hazimin@gmail.com> ; Copyright (C) 2011 Roman Black <http://www.romanblack.com> ; ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ; 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. ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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
file: /Techref/io/stepper/linistep/mod-hazimin-sdccc-quad.htm, 49KB, , updated: 2011/1/23 18:34, local time: 2024/11/24 15:20,
18.221.238.204:LOG IN
|
©2024 These pages are served without commercial sponsorship. (No popup ads, etc...).Bandwidth abuse increases hosting cost forcing sponsorship or shutdown. This server aggressively defends against automated copying for any reason including offline viewing, duplication, etc... Please respect this requirement and DO NOT RIP THIS SITE. Questions? <A HREF="http://ecomorder.com/techref/io/stepper/linistep/mod-hazimin-sdccc-quad.htm"> Linistepper, motor controller, stepper motor, quadrature encoder, c-language, pic microcontroller</A> |
Did you find what you needed? |
Welcome to ecomorder.com! |
Welcome to ecomorder.com! |
.