Author Topic: Uni-polar stepper motor sequence and wiring  (Read 1281 times)

0 Members and 1 Guest are viewing this topic.

Offline newtekuserTopic starter

  • Frequent Contributor
  • **
  • Posts: 358
  • Country: us
Uni-polar stepper motor sequence and wiring
« on: October 20, 2023, 01:44:01 am »
Hi All,

I have a 5v uni-polar stepper motor connected to an ULN2803 and PIC MCU and wired it according to the stepper motor symbol pinout available in Kicad6, where:

01->motor pin 2
02->motor pin 3
03->motor pin 4
04->motor pin 5
motor pin 1->5V

From the ULN:

I1->RE2 (A1)
I2->RE1 (B1)
I3->RE0 (A2)
I4->RA5 (B2)


From an earlier post I gathered that for full steps rotation, the sequence should be A1+B1, A2+B1, A2+B2, A1+B2. Based off this I wrote the following code:

For CW rotation

Code: [Select]
PORTE = 0b00000110; //A1, B1
__delay_ms(5);     
PORTE = 0b00000011; //A2, B1
__delay_ms(5);
PORTE ^= 0b00000001; //A2 (flip only PORTE0)
PORTA ^= 0b00100000; //B2 (flip only PORTA5)
__delay_ms(5);
PORTE ^= 0b00000100; //A1 (flip only PORTE2)
PORTA ^= 0b00100000; //B2 (flip only PORTA5)
__delay_ms(5);

For CCW rotation (just reversed)

Code: [Select]
PORTE = 0b00000110;
__delay_ms(5);     
PORTE = 0b00000011;
__delay_ms(5);
PORTE ^= 0b00000001;
PORTA ^= 0b00100000;
__delay_ms(5);
PORTE ^= 0b00000100;
PORTA ^= 0b00100000;
__delay_ms(5);

The problem I have is the motor just buzzes and does not turn. Obviously I have either wrong wiring, wrong code or both but can't figure out which.

Thanks!
 

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6275
  • Country: fi
    • My home page and email address
Re: Uni-polar stepper motor sequence and wiring
« Reply #1 on: October 20, 2023, 11:09:03 am »
No, you still have the same coil in adjacent pins.  That is, you do have RE2=A1 and RA5=B2 right, but RE1 is A2, and RE0 is B1: you have A2 and B1 swapped.
(The logic is that A and B refer to the same coil, with 1 and 2 the two ends of that coil.)

To repeat, if we call pin 2 A1=RE2, pin 3 A2=RE1, pin 4 B1=RE0, and pin 5 B2=RA5, then the sequence is A1+B1, A2+B1, A2+B2, A1+B2, and repeat.

Try
Code: [Select]
    // A1+B1 = RE2 + RE0
    PORTA = 0b00000000 | (PORTA & 0b00100000);
    PORTE = 0b00000101 | (PORTE & 0b00000111);
    __delay_ms(20);

    // A2+B1 = RE1 + RE0
    PORTA = 0b00000000 | (PORTA & 0b00100000);
    PORTE = 0b00000011 | (PORTE & 0b00000111);
    __delay_ms(20);

    // A2+B2 = RE1 + RA5
    PORTA = 0b00100000 | (PORTA & 0b00100000);
    PORTE = 0b00000010 | (PORTE & 0b00000111);
    __delay_ms(20);

    // A1+B2 = RE2 + RA5
    PORTA = 0b00100000 | (PORTA & 0b00100000);
    PORTE = 0b00000100 | (PORTE & 0b00000111);
    __delay_ms(20);
and for reverse direction,
Code: [Select]
    // A1+B2 = RE2 + RA5
    PORTA = 0b00100000 | (PORTA & 0b00100000);
    PORTE = 0b00000100 | (PORTE & 0b00000111);
    __delay_ms(20);

    // A2+B2 = RE1 + RA5
    PORTA = 0b00100000 | (PORTA & 0b00100000);
    PORTE = 0b00000010 | (PORTE & 0b00000111);
    __delay_ms(20);

    // A2+B1 = RE1 + RE0
    PORTA = 0b00000000 | (PORTA & 0b00100000);
    PORTE = 0b00000011 | (PORTE & 0b00000111);
    __delay_ms(20);

    // A1+B1 = RE2 + RE0
    PORTA = 0b00000000 | (PORTA & 0b00100000);
    PORTE = 0b00000101 | (PORTE & 0b00000111);
    __delay_ms(20);
These use a larger delay, to ensure the speed is low enough for the motor to turn reliably.
 

Online Ian.M

  • Super Contributor
  • ***
  • Posts: 12870
Re: Uni-polar stepper motor sequence and wiring
« Reply #2 on: October 20, 2023, 11:21:33 am »
*DON'T* perform ALU or bitwise output operations directly on PORT registers!
See http://www.massmind.org/techref/readmodwrite.htm
and RMW and solutions for it @Microchip Forum

TLDR: If your PIC is new enough to have LAT registers use them for all port output!
 

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6275
  • Country: fi
    • My home page and email address
Re: Uni-polar stepper motor sequence and wiring
« Reply #3 on: October 20, 2023, 12:57:47 pm »
*DON'T* perform ALU or bitwise output operations directly on PORT registers!
In my defense, I do not use PIC microcontrollers myself, and only adapted OP's code minimally so it should work.

I take it on PICs reading from PORTx does not return the output latch states, and instead returns the actual pin states?
If so, I assume the generic solution (especially if using C) suitable for PICs is to use a shadow register, i.e.
    unsigned char  PORTA_shadow, PORTE_shadow;
and
    PORTA = PORTA_shadow = 0b00000000 | (0b00100000 & PORTA_shadow);
    PORTE = PORTE_shadow = 0b00000000 | (0b00000111 & PORTE_shadow);
where the underlined zeroes vary between 0 and 1 in different steps?  That you're Ian.M at Microchip forums also, suggesting the idea yourself?

For OP: This shadow register approach uses a normal global variable that "shadows" the actual register state.  The right side assignment is done first using that variable, and then the resulting value copied to the register (left side).  All accesses to PORTA and PORTE have to use the shadow register, though, because otherwise the next assignment using the shadow value will revert the non-shadowed modifications.
Because the shadow variable itself is used via read-modify-write, this approach will not allow the shadowed ports to be used from an interrupt context (which is also the reason the shadow variables are not marked volatile).

(To allow access in interrupt context also, not only does the variable need to be declared volatile to stop the C compiler from trying to infer its value from surrounding code, all modifications like above need to temporarily disable interrupts for the duration of recalculating the shadow variable value.)
« Last Edit: October 20, 2023, 01:00:06 pm by Nominal Animal »
 

Online Ian.M

  • Super Contributor
  • ***
  • Posts: 12870
Re: Uni-polar stepper motor sequence and wiring
« Reply #4 on: October 20, 2023, 01:47:51 pm »
Yep. that's me, though I am no longer active there.  However the old Baseline (12 bit core) and 'classic' Midrange (old 14 bit core) PICs are dinosaurs - overpriced and under-specced, only useful for legacy products and education, and anyone sane treats all of them as NRND, as you can get Enhanced Midrange (new 14 bit core) devices offering LAT registers and more performance and memory at a lower cost.  Heck they even grafted a LAT register onto the PIC10F32x 6 pin part's 'classic' Midrange core.  All the higher performance PICs from PIC18 upwards have had LAT registers from the git-go.

If you are trying to put a bandaid on a sucking chest wound in a legacy design by using shadow registers, and any ISR updates them, then yes, in the main code you either need to disable interrupts while bit twiddling them, or only use single bit set/clear instructions, (which are atomic, and will be generated by XC8 if you assign a literal to a single bit bitfield), or use in-situ XOR masking: See http://www.microchip.com/forums/FindPost/715375
« Last Edit: October 20, 2023, 01:55:03 pm by Ian.M »
 

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6275
  • Country: fi
    • My home page and email address
Re: Uni-polar stepper motor sequence and wiring
« Reply #5 on: October 20, 2023, 03:07:43 pm »
use single bit set/clear instructions, (which are atomic, and will be generated by XC8 if you assign a literal to a single bit bitfield)
That would make a lot more sense than anything else.  Is the suitable structure already defined, or does one need to define it oneself?

Without knowing any of the PIC/XC8 intricacies (other than they are gcc or clang/LLVM derivatives), I assume something like
Code: [Select]
typedef struct {
    union {
        volatile unsigned char      all;
        struct {
            volatile unsigned char  bit0:1;
            volatile unsigned char  bit1:1;
            volatile unsigned char  bit2:1;
            volatile unsigned char  bit3:1;
            volatile unsigned char  bit4:1;
            volatile unsigned char  bit5:1;
            volatile unsigned char  bit6:1;
            volatile unsigned char  bit7:1;
        };
    };
} register_byte;

volatile register_byte *const PORT_A = &PORTA;  // Or device-specific ((volatile void *const)address) expression
volatile register_byte *const PORT_B = &PORTB;  // Ditto
volatile register_byte *const PORT_C = &PORTC;  // Ditto
volatile register_byte *const PORT_D = &PORTD;  // Ditto
volatile register_byte *const PORT_E = &PORTE;  // Ditto
volatile register_byte *const PORT_F = &PORTF;  // Ditto
should work, so that the suggested code for OP would become
Code: [Select]
    // A1 + B1
    PORT_E.bit2 = 1;  // A1 = RE2 = Motor pin 2
    PORT_E.bit1 = 0;  // A2 = RE1 = Motor pin 3
    PORT_E.bit0 = 1;  // B1 = RE0 = Motor pin 4
    PORT_A.bit5 = 0;  // B2 = RA5 = Motor pin 5
    __delay_ms(20);

    // A2 + B1
    PORT_E.bit2 = 0;  // A1 = RE2 = Motor pin 2
    PORT_E.bit1 = 1;  // A2 = RE1 = Motor pin 3
    PORT_E.bit0 = 1;  // B1 = RE0 = Motor pin 4
    PORT_A.bit5 = 0;  // B2 = RA5 = Motor pin 5
    __delay_ms(20);

    // A2 + B2
    PORT_E.bit2 = 0;  // A1 = RE2 = Motor pin 2
    PORT_E.bit1 = 1;  // A2 = RE1 = Motor pin 3
    PORT_E.bit0 = 0;  // B1 = RE0 = Motor pin 4
    PORT_A.bit5 = 1;  // B2 = RA5 = Motor pin 5
    __delay_ms(20);

    // A1 + B2
    PORT_E.bit2 = 1;  // A1 = RE2 = Motor pin 2
    PORT_E.bit1 = 0;  // A2 = RE1 = Motor pin 3
    PORT_E.bit0 = 0;  // B1 = RE0 = Motor pin 4
    PORT_A.bit5 = 1;  // B2 = RA5 = Motor pin 5
    __delay_ms(20);
and in reverse order for the other direction.

If so, then the following helper function would be quite useful:
Code: [Select]
static void motor_state(unsigned char phase) {
    switch (phase & 3) {
    case 0:
        PORT_E.bit2 = 1;  // A1 = RE2 = Motor pin 2
        PORT_E.bit1 = 0;  // A2 = RE1 = Motor pin 3
        PORT_E.bit0 = 1;  // B1 = RE0 = Motor pin 4
        PORT_A.bit5 = 0;  // B2 = RA5 = Motor pin 5
        return;
    case 1:
        PORT_E.bit2 = 0;  // A1 = RE2 = Motor pin 2
        PORT_E.bit1 = 1;  // A2 = RE1 = Motor pin 3
        PORT_E.bit0 = 1;  // B1 = RE0 = Motor pin 4
        PORT_A.bit5 = 0;  // B2 = RA5 = Motor pin 5
        return;
    case 2:
        PORT_E.bit2 = 0;  // A1 = RE2 = Motor pin 2
        PORT_E.bit1 = 1;  // A2 = RE1 = Motor pin 3
        PORT_E.bit0 = 0;  // B1 = RE0 = Motor pin 4
        PORT_A.bit5 = 1;  // B2 = RA5 = Motor pin 5
        return;
    case 3:
        PORT_E.bit2 = 1;  // A1 = RE2 = Motor pin 2
        PORT_E.bit1 = 0;  // A2 = RE1 = Motor pin 3
        PORT_E.bit0 = 0;  // B1 = RE0 = Motor pin 4
        PORT_A.bit5 = 1;  // B2 = RA5 = Motor pin 5
        return;
    }
}
or perhaps
Code: [Select]
static void motor_state(unsigned char phase) {
    const unsigned char  a = ((phase + 3) >> 1) & 1;
    const unsigned char  b = ((phase + 2) >> 1) & 1;

    PORT_E.bit2 = a;
    PORT_E.bit1 = !a;
    PORT_E.bit0 = b;
    PORT_A.bit5 = !b;
}
depending on which one generates better code for PICs.  OP, you'd simply increment or decrement an integer variable by one depending on the direction you want, and call motor_state(thatvar), and delay for a suitable amount of time.

Or, do you have a better suggestion, Ian.M?  You have experience on these, I don't; my suggestions are based on general principles only.
 
The following users thanked this post: newtekuser

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6275
  • Country: fi
    • My home page and email address
Re: Uni-polar stepper motor sequence and wiring
« Reply #6 on: October 20, 2023, 03:31:49 pm »
A quick search indicates the structures are defined in <xc.h> using PORTXbits.RXn naming scheme.  Therefore,
Code: [Select]
#include <xc.h>

static void motor_state(unsigned char phase)
{
    const unsigned char  a = ((phase + 3) >> 1) & 1;
    const unsigned char  b = ((phase + 2) >> 1) & 1;

    PORTEbits.RE2 = a;
    PORTEbits.RE1 = !a;
    PORTEbits.RE0 = b;
    PORTAbits.RA5 = !b;
}
and then say
Code: [Select]
    unsigned char  step = 0;

    for (int i = 0; i < 512; i++) {
        motor_state(step++);
        __delay_ms(20);
    }

    for (int i = 0; i < 512; i++) {
        motor_state(--step);
        __delay_ms(20);
    }
should turn the motor a bit over five seconds in one direction, a bit over five seconds in the other, at fifty steps per second.
Wrap the above in a while (1) { ... } to repeat it forever when power is applied.

If the gear ratio is such that 4096 pulses is full revolution, then each way the motor should turn one eighth of a turn.
If the gear ratio is such that 512 pulses is a full revolution, then each way the motor should turn one full turn.
The CW-to-CCW direction change point should always be in the same direction wrt the motor, as should the CCW-to-CW change point; there should be no drift.
 

Online Ian.M

  • Super Contributor
  • ***
  • Posts: 12870
Re: Uni-polar stepper motor sequence and wiring
« Reply #7 on: October 20, 2023, 04:16:23 pm »
No. Single bit operations on PORTx registers are *NOT* RMW safe.  On PICs without LATx registers, if you need independent bitbanged outputs on a port you still need to use a shadow register, and update the whole PORTx from it.  If you only ever output a limited set of states setting *ALL* the output pins you don't need to shadow it as you can simply write the whole port, but never use those pins to compute a new state.

See http://www.microchip.com/forums/m775818.aspx for how to use the port bit definitions in your PIC10/12/16's device specific header with a shadow variable without having to create your own bitfields + byte union for it.   
« Last Edit: October 20, 2023, 04:21:38 pm by Ian.M »
 

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6275
  • Country: fi
    • My home page and email address
Re: Uni-polar stepper motor sequence and wiring
« Reply #8 on: October 20, 2023, 05:30:02 pm »
No. Single bit operations on PORTx registers are *NOT* RMW safe.
Ah, so
Code: [Select]
#include <xc.h>

static void motor_state(unsigned char phase)
{
    if ((phase + 3) & 2) {
        PORTEbits.RE2 = 1;
        PORTEbits.RE0 = 0;
    } else {
        PORTEbits.RE2 = 0;
        PORTEbits.RE0 = 0;
    }
    if ((phase + 2) & 2) {
        PORTEbits.RE0 = 1;
        PORTAbits.RA5 = 0;
    } else {
        PORTEbits.RE0 = 0;
        PORTAbits.RA5 = 1;
    }
}
instead, I take it, should generate the atomic bit assignments using XC8?
 

Online Ian.M

  • Super Contributor
  • ***
  • Posts: 12870
Re: Uni-polar stepper motor sequence and wiring
« Reply #9 on: October 20, 2023, 05:38:32 pm »
That generates Atomic but non-RMW-safe bit operations on the PORTA and PORTE SFRs, as the PORTxbits structs simply map to the ports.   Its more elegant, probably fewer instructions than the original, but still fundamentally broken by RMW.

Your choices are:
  • Use LATx registers (if available)
  • Do whole port writes calculated or looked up from the state variable without readback of any output bit
  • Implement port shadowing properly  :horse:
  • Glitch at the drop of a hat due to external or self-induced EMI!  :o  |O

N.B. #2 is only viable if there are no unrelated bitbanged outputs on the same  port.
« Last Edit: October 20, 2023, 05:44:17 pm by Ian.M »
 
The following users thanked this post: newtekuser

Offline newtekuserTopic starter

  • Frequent Contributor
  • **
  • Posts: 358
  • Country: us
Re: Uni-polar stepper motor sequence and wiring
« Reply #10 on: October 20, 2023, 06:35:09 pm »
Thanks all! I'll move my connections around, in fact I have the entire PORTD at my disposal. In fact, when I had the motor connected to PORTA exclusively I had no issues doing an entire port write and the motor was moving in fine in both directions.
Using LATs is out of the question since this is an ancient PIC device, but for my next project will switch to something more modern.
 

Offline DavidAlfa

  • Super Contributor
  • ***
  • Posts: 5931
  • Country: es
Re: Uni-polar stepper motor sequence and wiring
« Reply #11 on: October 21, 2023, 10:17:10 am »
Newtekuser, I guess you're still with the 16F887?
Have you considered making a thread for your project?
I.E. "My journey with a PIC16F887", where you post all the oddities and troubles while developing the thing.

As long as the pins have limiting resistors high enough so their states can't be overriden by malfunctioning external circuitry (Shorted to vdd or gnd) and port writes aren't issued extremely close in time (Less than few us), RMW won't be an issue.
« Last Edit: October 21, 2023, 10:22:45 am by DavidAlfa »
Hantek DSO2x1x            Drive        FAQ          DON'T BUY HANTEK! (Aka HALF-MADE)
Stm32 Soldering FW      Forum      Github      Donate
 

Online Ian.M

  • Super Contributor
  • ***
  • Posts: 12870
Re: Uni-polar stepper motor sequence and wiring
« Reply #12 on: October 21, 2023, 12:06:02 pm »
99+% of the time, not overloading a port output pin, and adding delays will give the port long enough to settle and apparently resolve the RMW issue, but before sprinkling your code with NOPs to get the port writes further apart, read John Oldenkamp's comments at https://forum.microchip.com/s/topic/a5C3l000000M46eEAC/t245844?comment=P-1950469 and the rest of that topic.

*ANY* glitch that happens to hit the instant when the port is sampled in the Q1 phase of a RMW instruction on a port can result in output corruption.  You cant control external EMI so have to mitigate it in hardware - screening, filtering and minimising the length of susceptible traces exposed to it.  Otherwise, you get stuff happening like PORTB output corruption due to the trace lengths to the ICSP header happening to be near a quarterwave (or odd multiple of) at one of the cellular network frequencies + proximity of a mobile phone!   Add shadowing and watch the problem go away . . .

N.B. I've added delays myself to get LEDs used for debugging to work properly, I just don't trust that method of RMW mitigation for anything serious.  If its only for a status LED that will fix itself on the next pass through the main loop, fair enough, as it probably will never be corrupted long enough for a human to notice.
« Last Edit: October 21, 2023, 12:10:14 pm by Ian.M »
 

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6275
  • Country: fi
    • My home page and email address
Re: Uni-polar stepper motor sequence and wiring
« Reply #13 on: October 21, 2023, 01:44:28 pm »
On AVRs, each 8-bit/pin port consists of three registers: DDx, PORTx, and PINxDDx sets the direction, PORTx the output state (if output) or pull-up (if input), and PINx reflects the actual pin states.  A trick is that writing to PINx toggles the corresponding bits in PORTx.

This leads to a surprising way to control unipolar stepper motors, when all four pins are in the same port.  You need two byte variables: one with the bits corresponding to the four pins set (say pins), and the other (say next) initialized to the bits connected to the same coil, i.e. A1+A2.  At initialization time, the output pins are initialized to one end of each coil, i.e. A1+B1 for example.

The trick is that to advance the motor in the current direction, you do just
    PINx = next;
    next ^= pins;
To reverse the direction, you simply do an extra next ^= pins;.

Funky, eh?  The same trick is available on ARM cores with toggle registers.  But the AVR version certainly leads to obscure code hard to fathom what it does, unless one knows about this trick beforehand.

(If we start with pins=0b1111, next=0b1100, and PORTx=0b1010, the sequence the above generates in PORTx is 0b1010, 0b0110, 0b0101, 0b1001, repeating; this works for a pin order A1 A2 B1 B2 as OP uses.)
 

Offline newtekuserTopic starter

  • Frequent Contributor
  • **
  • Posts: 358
  • Country: us
Re: Uni-polar stepper motor sequence and wiring
« Reply #14 on: October 21, 2023, 05:17:08 pm »
Newtekuser, I guess you're still with the 16F887?
Have you considered making a thread for your project?
I.E. "My journey with a PIC16F887", where you post all the oddities and troubles while developing the thing.

As long as the pins have limiting resistors high enough so their states can't be overriden by malfunctioning external circuitry (Shorted to vdd or gnd) and port writes aren't issued extremely close in time (Less than few us), RMW won't be an issue.

I may start one after having some terms of comparison between it and a newer generation, but for the time being my troubles have been attributed mostly due to lack of knowledge, making and relying on assumptions, and not consulting datasheets for the obvious "a-ha!" moment.
That being said, I thank you all for the patience bearing with me and the oddities encountered while reading my posts  :)
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf