Control of a 28BYJ motor with the ULN2003 circuit
published: 28 October 2020 / updated 28 October 2020
Preamble
In the previous article, Controlling a NEMA14 motor with the A4988 driver I had designed the code based on the specifications of the A4988 driver, but without having the hardware at hand.
Here, for this article, I have a step motor and a ULN2003 control circuit available.
In this video by 0033mer, he uses a similar component, a similar ULN2803A integrated circuit at ULN2003:
The FORTH code shown in this video is adapted to the eForth version for ARDUINO.
For us, we will remain faithful to FlashForth by integrating amazing solutions:
- using headless definitions created by
:noname
- vectorized execution with a word created by
defer
Materials needed
List of equipment used:
- an ARDUINO NANO card, but the FORTH code is also compatible with other ARDUINO cards, as long as FlashForth is installed on your card;
- a ULN2003 or compatible circuit. It is a circuit which integrates a network of seven transistors Darlington NPN capable of 500mA, 50V output. Learn more about the ULN2003 circuit: ULN2003 Stepper Motor Driver Module
- a unipolar stepper motor, reference 28BYJ-48 5 volts
- an external power supply for the stepper motor via the ULN2003 circuit
The 28BYJ-48 5 volt stepper motor
In this photo, five wires come out of the motor: blue, pink, red, orange and yellow.
The red wire brings our 5 Volts (or more for another type of motor).
The other four wires that allow the motor to rotate are: blue, pink, yellow and orange.
Here, the table of the activation sequences of the different wires for a clockwise rotation:
ULN2003 circuit assembly
Here is the connection diagram that inspired me:
In reality, I supply the ARDUINO NANO card from its USB socket. So there is no power supply via the VIN pin.
The stepper motor control program
To use the FORTH code which follows, it is first necessary to compile the contents of the file ARDUINO pins definitions
Physically, the ULN2003 circuit is connected to the ARDUINO board with four wires:
- pin IN1 from ULN2003 to pin PD2 of the ARDUINO NANO board
- pin IN2 from ULN2003 to pin PD3 of the ARDUINO NANO board
- pin IN3 from ULN2003 to pin PD4 of the ARDUINO NANO board
- pin IN4 from ULN2003 to pin PD5 of the ARDUINO NANO board
As a reminder, PDx is the short form of P ort D bit x ... which already allows us to declare port D and the connectors that interest us:
decimal 43 constant PORTD flash PORTD %00000100 defPIN: IN1 \ blue wire motor PORTD %00001000 defPIN: IN2 \ pink wire motor PORTD %00010000 defPIN: IN3 \ yellow wire motor PORTD %00100000 defPIN: IN4 \ orange wire motor ram : init.stepper ( ---) IN1 output IN2 output IN3 output IN4 output ;
Note that the definition of the words IN1
to IN4
is framed
by the words flash
and ram
. If we did not use the
word flash
, the data would be saved in RAM memory and lost when FlashForth restarts.
In the process, we also define the word init.stepper
which declares the
four pins PD2 to PD5 as digital outputs.
Using Headerless Definitions
This part of the code deserves detailed explanation.
The word :noname
allows you to compile a definition without a visible header
in FORTH Dictionary. Once the definition is compiled, it stays on the data stack
the cfa address (Code Field Address) of the compiled code.
A FORTH word can be executed as interpreted by simply typing its name. But once compiled, it remains in the definition of a compiled word only the cfa of the compiled words. Example:
: cube ( n --- n^3) dup dup * * ;
Pendant la compilation de cube
, le compilateur stocke successivement
les cfa des mots dup dup * *
Si on dispose de l'adresse cfa d'un mot FORTH, on peut exécuter ce mot par l'intermédiaire
du mot execute
Exemple:
variable action ' cube action ! \ push cfa of cube and store it in action 3 action @ execute \ retrive cfa of the word stored in action and execute it
Si on stocke le cfa d'un autre mot dans action
, la séquence
action @ execute
executera cet autre mot.
We define a pseudo constant 'PULSES
which will store
the cfa of the following eight headless words:
:noname \ step 7 IN4 high IN3 low IN2 low IN1 high ; :noname \ step 6 IN4 low IN3 low IN2 low IN1 high ; :noname \ step 5 IN4 low IN3 low IN2 high IN1 high ; :noname \ step 4 IN4 low IN3 low IN2 high IN1 low ; :noname \ step 3 IN4 low IN3 high IN2 high IN1 low ; :noname \ step 2 IN4 low IN3 high IN2 low IN1 low ; :noname \ step 1 IN4 high IN3 high IN2 low IN1 low ; :noname \ step 0 IN4 high IN3 low IN2 low IN1 low ; flash create 'PULSES , , , , , , , , ram
The word 'PULSES
is followed by eight words
. Word
,
store the value at the top of the stack in the dictionary
and increments the dictionary pointer.
We could have defined words step0 to step7. Example:
: step0 \ step 7 IN4 high IN3 low IN2 low IN1 low ;
then define 'PULSES
like this:
create 'PULSES ' step0 , \ ...etc... ' step7 ,
The word 'PULSES
is an array containing eight values, i.e. the cfa
of eight definitions without headers. Running 'PULSES
drops on the stack
the address of the first cfa stored in this array. To access a cfa of rank n in this table,
we will therefore execute a sequence of this type n 2 * 'PULSES + @
Here is how we will access the cfa of these definitions without header by creating the
word execPulse
:
\ pulse delay 2 value pulseDelay \ index pointing in 'PULSES 0 value currentPulse \ send one pulse to A4988 : execPulse ( ---) currentPulse 2* \ convert pointer in addr offset 'PULSES + @ \ extract cfa of noname definition execute \ execute nonamed definition pulseDelay ms ;
The word execPulse
retrieves the content of the value currentPulse
which only takes values in the interval 0..7.
The motor will be sent a cycle of 8 steps. This is the task of the following words:
\ send 8 pulses to A4988 in CW direction : cycleCW ( ---) 0 to currentPulse 8 for execPulse currentPulse 1+ to currentPulse next ; \ send 8 pulses to A4988 in CCW direction : cycleCCW ( ---) 7 to currentPulse 8 for execPulse currentPulse 1- to currentPulse next ;
There are few differences between the words cycleCW
and cycleCCW
.
In the definition of the word cycleCW
we initialize the index currentPulse
to zero, then we enter a for..next loop in which we send a step to the motor,
then we increment currentPulse
.
In the definition of the word cycleCCW
we initialize the index currentPulse
at seven, we send a step to the motor, then we decrease currentPulse
.
It remains to be seen how we determine and choose the direction of rotation of the stepper motor:
defer cycles : setDirection ( n ---) 0< if ['] cycleCCW is cycles else ['] cycleCW is cycles then ;
In the code above, we create the word cycles
from defer
The word cycles
is a word that has no action until it is indicated
not the action to perform.
This is what is done in the word setDirection
. If the value n is negative,
we run the code ['] cycleCCW is cycles
.
From now on, executing cycles
will execute cycleCCW
Conversely, if the value is positive, ['] cycleCW is cycles
will be executed.
Executing cycles
will execute cycleCW
.
The advantage of using the vectorized execution word cycles
allows
avoid executing the direction test while sending the steps to the motor.
There remains one final concern to be resolved. Pure formality, but oh so impotent!
If we analyze the timing diagram for triggering steps, we see that there are still not active. Leaving the motor winding under tension will cause the motor to overheat.
To avoid this overheating, all the steps must be deactivated, but by memorizing the last state:
0 value statePORTD : PORTDdown ( ---) PORTD c@ to statePORTD 0 PORTD c! ; : PORTDrestore ( ---) statePORTD PORTD c! ;
The statePORTD
value is used to memorize the last state of port D.
The word PORTDdown
stores the state of PORT D and resets all
exits from this port.
The word PORTDrestore
retrieves the last state of port D and reactivates this state.
If we refer to the table titled SWITCHING SEQUENCE, we see that step 1 is a continuation of step 8.
By restoring the last step taken, we start again on a cycle of 8 steps without risking causing movements random engine.
We get to the end by creating the word steps
:
: steps ( n ---) PORTDrestore dup setDirection abs for cycles next PORTDdown ;
The execution of the word steps
must be preceded by the number of step cycles to be performed:
\ test stepper motor init.stepper 200 steps
The full listing is available here: Controlling a 28BYJ motor with the ULN2003 chip