Proceed to Safety

Floyd-Steinberg Motor Control    

This page describes a way to get better control of motor speed than the built-in 7 speeds. If you need to position something really accurately you might consider a stepper motor instead.

In most (or perhaps even all) applications that require changing the speed of the motors, you will find that the 7 available motor speeds really don't give you much control of the motor speed. All the speeds seem to make the motor turn at the same rate, except perhaps the very lowest speed.

To get a more accurate type of control over motor speed you have to pulse the motor on and off in some controlled manner (actually the bultin speeds do this, but apparently the pulse rate is too fast). The simplest way to do that is to use a method like this (the examples on this page use NQC)

/* variable "a_speed" is modified by another task to set the motor speed */ /* enhancement to handle negative values for reverse motion is left as an exercise to the reader */ while(true) { Fwd(OUT_A, 7); Sleep(a_speed); OFF(OUT_A); Sleep(16 - a_speed); }

This turns the motor on and off once every 16/100 of a second, each time for a period of time proportional to the desired speed.

Although this method works well, it results in a lot of unnecessary "stuttering". The motor is starting and stopping only 6 times a second, which is very noticable because it is so slow. It will cause most designs to vibrate noticably and may even degrade performance by causing resonance oscillations that interfere with proper operation or that make part of the model fall apart.

Using a higher frequency would seem to require reducing the number of available "steps", or perhaps the use of multiplication or division or other complex math to compute delays. In fact that is not the case.

Floyd Steinberg error diffusion is a commonly-used technique for converting grayscale or continuous-tone color images into something with 1, 3, or some other small number of bits per pixel. The fundamental principle is extremely simple: accumulate fractional amounts at each step, but output only the integer portion. The code will show how this works:

/* Floyd-Steinberg version. 100 steps from stop to full, and handles negative (reverse) too! */ int a_accum; int a_speed;    task motor_a { while(true) { a_accum = a_accum + a_speed;    if (a_accum > 50) { Fwd(OUT_A, 7); a_accum = a_accum - 100; } else if (a_accum < -50) { Rev(OUT_A, 7); a_accum = a_accum + 100; } else { OFF(OUT_A); }    /* See note below */ /* Sleep(1); */ } }

Note: The Sleep(1) has been commented out because the rest of the loop actually takes about 1/100 of a second to run. If I put the Sleep(1) back in the whole thing runs about half as fast (apparent from the pulse rate at low speeds, see discussion below.)

Imagine that aspeed has been set to 50 (half speed). aaccum starts at 0. The first time through the loop it will add 50, so a_accum will be 50, and it will go down to the "else" section and turn the motor off (if it wasn't already). Then at the next step (which is about 1/100 of a second later; I timed the loop) it adds another 50 and a_accum is 100. Now it turns the motor on and subtracts 100 from aaccum, putting aaccum back to 0. Another 1/100 of a second has gone by and the process repeats. 50, 100->0, 50, 100->0, on, off, on, off... the motor goes on and off with a frequency of 50 times per second.

Now set a_speed to 25. Follow through the loop, you'll see the motor goes on for 1, off for 3, then on for 1 off for 3, etc. for a total of 25 on/offs per second. If you set a_speed to 75 it does on for 3, off for 1.

In each case it generates the fastest possible on/off frequency rate that is available for the specified speed.

It even works well for speeds that do not divide well into 100. For example, a speed of 40 creates this pattern:

off for 2, on for 1, off for 1, on for 1 (and repeat)

that's a total of 2 on/off transitions each 5/100 of a second, or 40 transitions per second, and 2/5 of the time (40 percent) is spent on. Speeds like 37, 61, etc (numbers that have few or no common factors with 100) produce patterns like this but it takes longer for the patterns to repeat.

An important thing to notice is that all speeds from 0 to 50 produce pulses that are 1/100 of a second long. Speeds over 50 have "gaps" (off periods) that are 1/100 of a second long.


If you find that the 100% speed isn't going three times as fast as the 33% speed, the motor is probably too heavily loaded for the pulse rate and it probably needs more time to start up on each pulse. To solve this, you should put a Sleep() command in the loop. Start with Sleep(1) which will make it twice as slow as it is now, and then try higher values. The actual loop time (and therefore, the pulse length at speeds under 50%) will be 1/100 of a second longer than the Sleep() value you use.

You could also try making each pulse longer but keep the overall loop delay the same. This involves making it delay an extra 1/100 second only at the beginning of pulses:

int old_accum;    task motor_a { while(true) { old_accum = a_accum; a_accum = a_accum + a_speed;    if (a_accum > 50) { Fwd(OUT_A, 7); a_accum = a_accum - 100; if (old_accum <= 50) { /* this pulse just started */ Sleep(1); } } else if (a_accum < -50) { Rev(OUT_A, 7); a_accum = a_accum + 100; } else { OFF(OUT_A); } } }

This can reduce the linearity of response but it's worth trying if your motor drives a constant load and a much slower pulse rate is unacceptable.

LEGO® creations index

The graph paper in my newer photos is ruled at a specing of 1 LSS, which is about 7.99 mm.

This site is not affiliated with the LEGO® group of companies.

LEGO®, Duplo®, QUATRO®, DACTA®, MINDSTORMS®, Constructopedia®, Robotics Invention System® and Lego Technic®, etc. are trademarks or registered trademarks of LEGO Group. LEGO Group does not sponsor, authorize or endorse this site.

All other trademarks, service marks, and copyrights are property of their respective owners.

If you want to visit the official LEGO® site, click here

Parts images are from LUGNET. On this page they explicitly give permission to link to the images:

Note: you may link (as in, Yes, it's OK) directly to these parts images from an off-site web page.

Robert Munafo's home pages on AWS    © 1996-2023 Robert P. Munafo.    about    contact
This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License. Details here.

This page was written in the "embarrassingly readable" markup language RHTF, and was last updated on 2008 Jan 29. s.27