Stepper motor driver DIY project

This time I needed to build a more powerful and more robust stepper motor driver. For me, it was also a good opportunity to test some theoretical and practical ideas I was considering for some time… Usually I use the L298 integrated circuit, but this time I decided to use discrete MOSFET transistors to make the driver robust enough so that all sort of experimentation can be carried out without much fear that the driver board will burn.


  • bipolar stepper motor driver, 6.5A per phase
  • working voltage 24 - 90 V
  • 32 microsteps
  • chopping frequency 15kHz
I decided to use ATmega8 microcontroller because I had several of them on stock and because I wanted to see how much I can squeeze out of this small processor. I also aimed toward a single-layer PCB because I wanted that the driver can be made by anyone. (On my site you will also find a page that describes a very similar, but simpler,1.5A stepper motor driver.)

Power supply

The driver needs external DC source in range from 24 to 90V. I didn’t place large capacitors on my PCB so I must use a capable power supply. Internally, the driver also needs +5VDC (up to 100mA) and +15VDC (up to 100mA). These voltages are derived from the input voltage in two stages: first, a switching-mode power supply generates 20...30VDC, and then the 7805 and 7815 regulators generate stable voltage levels.

For the switching-mode power supply I use an inductor (min 25mH, min 200mA) and it might be the hardest-to-find component. The local electronics store had a 27mH inductor so I bought it. Otherwise I would resort to scavenging.

The R2 resistor brings voltage to the Q1 transistor gate; the transistor opens charging current into the L1 inductor. If your supply voltage is 90V and the L1 inductor only has 25mH then the current peek will be high (up to 3A) so it might be good to reduce C2 (and also C3 and C4) capacity value.

Once the voltage at C2 reaches about 17-18V, the optocoupler VO1 will open reducing the Q1 transistor gate voltage and reducing the current. From then on, the transistor Q1 works in current-limiting mode, possibly heating a lot if a high voltage (above 60V) is supplied. It is therefore important that the ATmega8 starts PWM regulation as early as possible.

The PWM regulation is quite problematic because ATmega8 works half-blindly and must keep the voltage at the C2 capacitor within a narrow range (20…30VDC). The ATmega8 senses the input voltage (the U_SEN signal) and computes the PWM ratio to achieve the desired voltage at the C2 capacitor. It would be much easier if another voltage sensing signal from the C2 capacitor is also brought to the ATmega8. However I preferred to use ATmega8 pins sparingly.

If the voltage at the C2 is not within the right range, the PWM switching might not work properly. The right range on the C2 capacitor (20…30VDC) ensures that the optocoupler will turn on when the PWM_PWR signal is zero and that the optocoupler will turn off when the PWM_PWR signal reaches +4.5VDC.

Another thing is that if the input voltage is low (below 40VDC), the transistor Q1 switch-on time will be long because high resistance R2 resistor will charge the Q1 gate slowly. In this case the switching-mode is not effective and the transistor mostly works in current-limiting mode. You might improve the switching time by reducing the R2 resistance while increasing its power (for example: 10kohm, 0.5W).

For the Q1 transistor I used IRF630. This is bit overkill and maybe a smaller MOSFET with a smaller gate capacity would be a better choice. The diode D1 is only present to protect from wrong power supply polarity (it should be a large 6A diode). The diode D2 is a flyback diode and should be capable to carry 200mA (2A peek) and 100V reverse voltage. The intended PWM frequency is 15kHz. The diode D3 might not be needed, but is here just in case.


The cheapest power MOSFET transistors in the local store were IRF630 (9A, 200V) so decided to use them. Eight of them are needed for two H-bridges. IRF630 incorporates drain-to-source diode that I am going to use gladly. The driver, because it will be used for experimentation purposes, will have improved current measurement abilities. I want to be able to easily measure positive and negative currents during both power phase and current-recirculation phase.

The principal schematics of one half of one H-bridge is shown above. As you can see, the upper transistor is activated by an optocoupler powered from a bootstrap capacitor. The lower transistor is disactivated by a small transistor. The biggest problem with the above simple solution is the slow turn-off time of the upper transistor because the Roff resistor cannot be made too small. Even with 330 ohm resistor the optocoupler transistor must allow 50mA and have current transfer ratio of 200 (PC817C, for example)! Two possibly better solutions are displayed below (I was too much of a coward to deploy the left one; the other one seems too complex).

When switching from one transistor to the other one (within the same half-bridge branch) the care must be taken that the first transistor closes slightly before the other one opens. A timing circuit is therefore employed. The principal schematic shows how it works:

The timing circuit uses the fact that the transistor Q will turn on at lower voltage (0.6V) than the optocoupler (1V). When the voltage at the capacitor is between 0.6V and 1V neither MOSFET works (optocoupler is off so the upper MOSFET is off; the transistor Q is on, so the lower MOSFET is also off). By adjusting the capacitor capacity and resistor resistances, it is possible to adjust the timing to a degree. One can add a diode in series to the optocoupler diode to increase the triggering difference even more... (BTW, you can see another two diodes in series in the above circuit that are needed to ensure that the low PWM level (0.5V) will securely turn off the transistor Q.)

In any case I am not too happy with the switching time. The turn-off time for the upper MOSFET is about 6us – way longer than I hoped for. Fortunately, the problem was not catastrophic and I was capable to use the driver at 15kHz PWM switching frequency as planned.

Click to enlarge.
(Q1..Q4=IRF630; Q5,Q6=BC550; D3...D15=1N4148 or similar)

The full schematics of one H-bridge is depicted above (you need two of them to drive a stepper motor: I call them H-bridge A and H-Bridge B). Each H-bridge uses three control signals: PWM, DIR and DECAY. I intentionally decided to use these control signals because I wanted to use PWM generators inside the ATmega8. The PWM signal is used to generate voltage level. The DIR signal is used to define the voltage direction at the motor. The DECAY signal is used to put the H-bridge into the fast-current-decay mode (all transistors off)… Depending on the DIR signal one half of the bridge is always kept low while the PWM signal only acts on the other half of the bridge. When PWM signal is high, the H-bridge is in the power phase; when the PWM signal is low the H-bridge is in the current-recirculation phase…. When the DECAY signal is high, all transistors are forced off… Anyway, if you need a more detailed explanation on how the circuit works, please write an e-mail to me.

Current sensing

What you noticed is a very elaborate way to measure the current that is feed to the motor. The current is measured by measuring voltage over two 0.1 ohm resistors – the voltage is then amplified by an op-amp. The circuit works better than I expected. The output CSEN is at cca 2.5V when no current is flowing. At 6A motor current the voltage at CSEN is at cca 4.4V. At 6A in opposite direction the voltage at CSEN is at cca 0.6V. This works in both, the power phase and the recirculation phase.

During power phase: Suppose that MOSFET Q3 is closed – the resistors dividers R7/R11 and R8/R13 will bring high voltage (about 1V) to diodes D12 and D13 making them reversely polarized. Therefore, voltage measurement on the resistor R1 will be prevented…. In recirculation phase: One 0.1 ohm measurement resistor will have positive voltage, and the other one negative. Diodes that lead to the positively biased measurement resistor will be reversely polarized and the measurement will be only made on the negatively biased resistor.

For best results, diodes D12-D15 should be from the same batch and should be kept at the same temperature (close one to each other). You could simplify the circuit by placing measurement resistors above Q3 and Q4 transistors, but then high-voltage (100V) diodes D12-D15 must be used.

The op-amp is 741 and is supplied by 15 volts – this gives only about +5…+10 working range for its output signal. When no current is present, its output should be at about 7.5V. A 5.1V zenner diode is used to bring this down to about 2.5V… In any case, the ATmega8 must first measure and remember the zero offset (about 2.5V) when no current is flowing through measurement resistors. Later, this offset must be subtracted from any current measurement to get the current value.

Control circuits

I am not going to describe control circuits in detail because you will make your own design. I used following:

  • three external digital inputs
  • one external analog input
  • two LEDs to show the board state
  • 4 DIP switches for configuration
  • one SPI port for programming ATmega8
Here is a list of ATmega8 pins as I used them:

 D0 – not used at the moment (can be used for UART RX)
 D1 – LED1 (alternatively can be used for UART TX)
 D2 – external digital input (forward-step pulse)
 D3 – external digital input (reverse-step pulse)
 D4 – LED2
 D5 – DIR_A signal
 D6 – DIR_B signal
 D7 – external digital input (driver enable)
 B0 – DIP switch read
 B1 – PWM_B signal
 B2 – PWM_A signal
 B3 – PWM_PWR signal (also used for SPI interface)
 B4 – SPI interface
 B5 – SPI interface
 B6 – DECAY_A signal
 B7 – DECAY_B signal
 C0 – external analog input (speed or current reference)
 C1 – CSEN_B signal
 C2 – CSEN_A signal
 C3 – U_SEN signal
 C4 – not used!!!
 C5 – temperature monitoring
 C6 – reset (only used for SPI programming)

The B3 pin is shared between SPI interface and PWM_PWR. Therefore I placed a jumper on the board so that PWM_PWR signal can be disconnected during ATmega8 programming. When programming, the board must be powered from a lower voltage source (24…40VDC) because when the switching-mode power supply is disabled and the transistor Q1_PS might overheat.

The C4 signal is not used and it would be nice to use it for LED1 instead of the D1 pin if the UART communication is needed. I however had hard time routing the PCB so it was easier to connect the LED1 to the D1 pin. Alternatively, the C4 pin can be used to measure the voltage at the C2_PS capacitor.

The D2 and D3 pins can be programmed as external interrupts in ATmega8. Therefore I used these pins for “step forward” and “step backward” external digital inputs.

I used a trick to read DIP switches because I was short of ATmega8 pins. In normal work C0, D2, D3 and D7 are input pins, while B0 is the output pin and is always kept low. When DIP switches needs to be read, B0 is reprogrammed as an input pin with its pull-up resistor enabled, while C0, D2, D3 and D7 are reprogrammed as output pins and kept high. To read a particular DIP switch, just lower any one of the C0, D2, D3 and D7 pins (keep the rest of them high) and read the state of the B0 pin.

Making the PCB

I mill my prototype PCBs using a rough home-made desktop CNC mill. Milling is easier for me as I don’t like chemistry. The CNC mill also drills holes for me. Of course, the result does not look professional, but it is okay for a prototype PCB. Anyway, this article is not to teach you about PCB making (I don’t think I am a person that excels in crafting) so I will only comment on two experiments I made this time:

Experiment1: I will use a board with copper on both sides, but I will place all routes on one side only, as if I am making a single-layer board. I will use the other copper layer exclusively for GND. This way I only need to mill one side of the board while I don’t have to route GND tracks… Result: I was only partially satisfied – It can be used if you have no way to make double-layer boards. Otherwise, real double-layer boards win. The problem is that even after you remove copper around holes on the GND plane, you still need to carefully place components not to make an accidental contact with the GND. Resistors, for example must be placed high above the board (2 millimeters) to ensure that their leads enter the hole vertically.

Experiment2: I will mount TO220 components horizontally as surface mount components on the bottom side. This way I hope to have easier access to the heatsink…. Result: I was satisfied. There are disadvantages (uses lots of PCB surface area; small clearance between PCB and heatsink) but it is a good solution when a product of low height needs to be made.


Images above show: A) PCB bottom layer (CNC milled, solder applied manually to cover all tracks); B) top GND plane (the copper around holes is removed manually using a small drill with a grinding bit, a self-adhesive plastic foil is then pasted over the whole surface); C) the PCB with components soldered (you can see that TO220 components are soldered from bottom and fastened to a 4mm thick aluminum plate beneath the PCB – an additional heatsink should be mounted on the aluminum plate).

For reference, you can download DIPTrace shematics (single H-bridge and power supply only) and PCB layout from this zip file. The file also includes low-resolution trace layout bitmap as I used it when making the PCB. Note however that this was the first time I used DIPTrace, free version that has 300 pads limit, and this is why I had to split the PCB into two files. Also there are several awkwardnesses (traces are painted in the top layer insead in the bottom layer...) so I suggest you to rework the PCB according to your needs. If you make a good PCB and want to share it on this site, let me know.


You noticed that I placed PWM_A, PWM_B and PWM_PWR signals to B2, B1 and B3 pins. Obviously, this makes possible to use ATmega8 PWM generators. This is the only way to achieve the ‘non-audible’ 15kHz switching frequency. I generally use phase-correct 8-bit PWM with no prescaler. At 8MHz master clock this gives 15.6kHz PWM.

The ATmega8 has somewhat slow and multiplexed analog-to-digital converter. But I wanted to read current measurements as often as possible. Therefore the analog signals are read in the following 9-step sequence: currentA, currentB, external analog input, currentA, currentB, input voltage, currentA, currentB, temperature. In addition to that, I am also using 500kHz for the ADC clock. This is too high for 10-bit accuracy so I work with 8-bit ADC accuracy. In theory, it is possible to achieve over 10kHz current sampling rate, but in practice it is much lower because I am not using interrupts when ADC finishes. Instead I am testing the finish flag in a loop. (I am using interrupts for other purposes and I don’t want to interfere. But still, programming interrupt procedure for ADC reading could lead to some improvements.)

You can download the basic stepper driver source code (AVR studio C file, 13kB). If you used ATmega8 pins the same as I did (see above) the driver software should work without modifications. However it is expected that you finish it according to your own needs.

I also tested a more advanced stepper driver code capable to prevent vibrations in the middle-frequency range. The regulation algorithm is made according to this stepper motor theory document. Contact me if you need clarifications.

I am using a 200-step NEMA34 size stepper motor (round style), max current 5.5A. The motor has winding resistance of 1ohm and winding inductance of about 10mH. Using the improved driver software I can accelerate the unloaded motor from zero to 950rpm without vibrations. Using the basic driver software I can only make this with a loaded motor.

You can use the driver to drive two DC motors, but recall that 100% duty cycle cannot be obtained. You might even have problem with a stepper motor if you try 100% duty cycle at slow speed.

You can e-mail me if you have any question or request. Also, please e-mail me if you have any suggestion or idea how to make the driver simpler/better... Oh, when you work on your projects you can use the Math-o-mir, math notepad software.

Danijel Gorupec, 2014