Uni-polar stepper motor sequence and wiring - Page 1
Forum section: electronics / general

author: newtekuser
author_rank: Frequent Contributor
timestamp: October 20, 2023, 01:44:01 am
content: 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 202->motor pin 303->motor pin 404->motor pin 5motor pin 1->5VFrom 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 rotationCode: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: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!

author: Nominal Animal
author_rank: Super Contributor
timestamp: October 20, 2023, 11:09:03 am
content: 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,thenthe sequence is A1+B1, A2+B1, A2+B2, A1+B2, and repeat.TryCode:// A1+B1 = RE2 + RE0PORTA = 0b00000000 | (PORTA & 0b00100000);PORTE = 0b00000101 | (PORTE & 0b00000111);__delay_ms(20);// A2+B1 = RE1 + RE0PORTA = 0b00000000 | (PORTA & 0b00100000);PORTE = 0b00000011 | (PORTE & 0b00000111);__delay_ms(20);// A2+B2 = RE1 + RA5PORTA = 0b00100000 | (PORTA & 0b00100000);PORTE = 0b00000010 | (PORTE & 0b00000111);__delay_ms(20);// A1+B2 = RE2 + RA5PORTA = 0b00100000 | (PORTA & 0b00100000);PORTE = 0b00000100 | (PORTE & 0b00000111);__delay_ms(20);and for reverse direction,Code:// A1+B2 = RE2 + RA5PORTA = 0b00100000 | (PORTA & 0b00100000);PORTE = 0b00000100 | (PORTE & 0b00000111);__delay_ms(20);// A2+B2 = RE1 + RA5PORTA = 0b00100000 | (PORTA & 0b00100000);PORTE = 0b00000010 | (PORTE & 0b00000111);__delay_ms(20);// A2+B1 = RE1 + RE0PORTA = 0b00000000 | (PORTA & 0b00100000);PORTE = 0b00000011 | (PORTE & 0b00000111);__delay_ms(20);// A1+B1 = RE2 + RE0PORTA = 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.

author: Ian.M
author_rank: Super Contributor
timestamp: October 20, 2023, 11:21:33 am
content: *DON'T* perform ALU or bitwise output operations directly on PORT registers!Seehttp://www.massmind.org/techref/readmodwrite.htmandRMW and solutions for it @Microchip ForumTLDR: If your PIC is new enough to have LAT registers use them for all port output!

author: Nominal Animal
author_rank: Super Contributor
timestamp: October 20, 2023, 12:57:47 pm
content: Quote from: Ian.M on October 20, 2023, 11:21:33 amIn 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 fromPORTxdoes 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;andPORTA = 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).Allaccesses 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 markedvolatile).(To allow access in interrupt context also, not only does the variable need to be declaredvolatileto stop the C compiler from trying to infer its value from surrounding code,allmodifications like above need to temporarily disable interrupts for the duration of recalculating the shadow variable value.)

author: Ian.M
author_rank: Super Contributor
timestamp: October 20, 2023, 01:47:51 pm
content: 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: Seehttp://www.microchip.com/forums/FindPost/715375

author: Nominal Animal
author_rank: Super Contributor
timestamp: October 20, 2023, 03:07:43 pm
content: Quote from: Ian.M on October 20, 2023, 01:47:51 pmThat 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 likeCode: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) expressionvolatile register_byte *const PORT_B = &PORTB; // Dittovolatile register_byte *const PORT_C = &PORTC; // Dittovolatile register_byte *const PORT_D = &PORTD; // Dittovolatile register_byte *const PORT_E = &PORTE; // Dittovolatile register_byte *const PORT_F = &PORTF; // Dittoshould work, so that the suggested code for OP would becomeCode:// A1 + B1PORT_E.bit2 = 1; // A1 = RE2 = Motor pin 2PORT_E.bit1 = 0; // A2 = RE1 = Motor pin 3PORT_E.bit0 = 1; // B1 = RE0 = Motor pin 4PORT_A.bit5 = 0; // B2 = RA5 = Motor pin 5__delay_ms(20);// A2 + B1PORT_E.bit2 = 0; // A1 = RE2 = Motor pin 2PORT_E.bit1 = 1; // A2 = RE1 = Motor pin 3PORT_E.bit0 = 1; // B1 = RE0 = Motor pin 4PORT_A.bit5 = 0; // B2 = RA5 = Motor pin 5__delay_ms(20);// A2 + B2PORT_E.bit2 = 0; // A1 = RE2 = Motor pin 2PORT_E.bit1 = 1; // A2 = RE1 = Motor pin 3PORT_E.bit0 = 0; // B1 = RE0 = Motor pin 4PORT_A.bit5 = 1; // B2 = RA5 = Motor pin 5__delay_ms(20);// A1 + B2PORT_E.bit2 = 1; // A1 = RE2 = Motor pin 2PORT_E.bit1 = 0; // A2 = RE1 = Motor pin 3PORT_E.bit0 = 0; // B1 = RE0 = Motor pin 4PORT_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:static void motor_state(unsigned char phase) {switch (phase & 3) {case 0:PORT_E.bit2 = 1; // A1 = RE2 = Motor pin 2PORT_E.bit1 = 0; // A2 = RE1 = Motor pin 3PORT_E.bit0 = 1; // B1 = RE0 = Motor pin 4PORT_A.bit5 = 0; // B2 = RA5 = Motor pin 5return;case 1:PORT_E.bit2 = 0; // A1 = RE2 = Motor pin 2PORT_E.bit1 = 1; // A2 = RE1 = Motor pin 3PORT_E.bit0 = 1; // B1 = RE0 = Motor pin 4PORT_A.bit5 = 0; // B2 = RA5 = Motor pin 5return;case 2:PORT_E.bit2 = 0; // A1 = RE2 = Motor pin 2PORT_E.bit1 = 1; // A2 = RE1 = Motor pin 3PORT_E.bit0 = 0; // B1 = RE0 = Motor pin 4PORT_A.bit5 = 1; // B2 = RA5 = Motor pin 5return;case 3:PORT_E.bit2 = 1; // A1 = RE2 = Motor pin 2PORT_E.bit1 = 0; // A2 = RE1 = Motor pin 3PORT_E.bit0 = 0; // B1 = RE0 = Motor pin 4PORT_A.bit5 = 1; // B2 = RA5 = Motor pin 5return;}}or perhapsCode: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 callmotor_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.

author: Nominal Animal
author_rank: Super Contributor
timestamp: October 20, 2023, 03:31:49 pm
content: A quick search indicates the structures are defined in<xc.h>usingPORTXbits.RXnnaming scheme. Therefore,Code:#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 sayCode: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 awhile (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.

author: Ian.M
author_rank: Super Contributor
timestamp: October 20, 2023, 04:16:23 pm
content: 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.Seehttp://www.microchip.com/forums/m775818.aspxfor 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.

author: Nominal Animal
author_rank: Super Contributor
timestamp: October 20, 2023, 05:30:02 pm
content: Quote from: Ian.M on October 20, 2023, 04:16:23 pmAh, soCode:#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?

author: Ian.M
author_rank: Super Contributor
timestamp: October 20, 2023, 05:38:32 pm
content: 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 bitImplement port shadowingproperlyGlitch at the drop of a hat due to external or self-induced EMI!N.B. #2 is only viable if there are no unrelated bitbanged outputs on the same port.

author: newtekuser
author_rank: Frequent Contributor
timestamp: October 20, 2023, 06:35:09 pm
content: 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.

author: DavidAlfa
author_rank: Super Contributor
timestamp: October 21, 2023, 10:17:10 am
content: 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.

author: Ian.M
author_rank: Super Contributor
timestamp: October 21, 2023, 12:06:02 pm
content: 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 athttps://forum.microchip.com/s/topic/a5C3l000000M46eEAC/t245844?comment=P-1950469and 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.

author: Nominal Animal
author_rank: Super Contributor
timestamp: October 21, 2023, 01:44:28 pm
content: On AVRs, each 8-bit/pin port consists of three registers:DDx,PORTx, andPINx.DDxsets the direction,PORTxthe output state (if output) or pull-up (if input), andPINxreflects the actual pin states. A trick is that writing toPINxtoggles the corresponding bits inPORTx.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 (saypins), and the other (saynext) 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 justPINx= next;next ^= pins;To reverse the direction, you simply do an extranext ^= 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 withpins=0b1111,next=0b1100, andPORTx=0b1010, the sequence the above generates inPORTxis0b1010,0b0110,0b0101,0b1001, repeating; this works for a pin order A1 A2 B1 B2 as OP uses.)

author: newtekuser
author_rank: Frequent Contributor
timestamp: October 21, 2023, 05:17:08 pm
content: Quote from: DavidAlfa on October 21, 2023, 10:17:10 amI 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