Monday, January 16, 2012

Pulse-Width Modulation with Arduino

One of the most used features on microcontrollers and microprocessors that you find in embedded systems are the General Purpose I/O (GPIO) pins. These are pins directly on the chip that can be set to low (ground) or high (typically 3.3 or 5 volts) values, or whose digital voltages can be read and turned in to bits, under the control of software running on the chip.

System-on-a-chip (SoC) solutions are so-called because they incorporate one or more processor cores with on-chip peripheral controllers that handle I/O and other functions commonly used in embedded systems. Although many of these controllers can handle relatively sophisticated functions like Ethernet physical layer protocols or specialized communication busses like I2C, SoCs inevitably have at least a handful of pins that can be manipulated by an on-chip GPIO controller.

This capability gives the embedded developer a way to implement non-standard hardware protocols (or, sometimes, implement a protocol that the existing SoC controller didn't quite get right), to talk to application specific devices like a Field Programmable Gate Array (FPGA), to sense a button or switch on the front panel, or even to do something as mundane as blink a Light Emitting Diode (LED). This last example is exactly what was going on in my Amigo project article Getting to Hello World with Arduino: some simple software used the digitalWrite function to blink an LED. If you spend five minutes poking around in the Arduino run-time software you will see that ultimately this is implemented by just setting and clearing a bit at a memory location that represents a memory-mapped register in a GPIO controller built into the little eight-bit ATmega328P chip. (This is also exactly what was going on in my article Memory Mapped Devices on the BeagleBoard with Android except with a Texas Instruments DM3730 SoC with a powerful thirty-two bit ARM Cortex-A8 processor running Linux and Android.)

So let's suppose that we hook up a Radio Shack digital multimeter and a Saleae Logic analyzer to an Arduino and look at a GPIO pin. What might we see? (Ignore for a moment the explosion of wiring on the breadboard. We'll get to that in a moment.)

Digital Voltmeter: Programmed 0% PWM Duty Cycle

The multimeter is reading negative millivolts, which I think is just noise below the logic level of the GPIO pin. (You can click on any of these photographs to get access to larger sizes.)

Saleae Logic: Programmed 0% PWM Duty Cycle

Channel zero on the logic analyzer is reading a logic zero. So far, not that interesting. So let's write a little Arduino code to set the GPIO pin to a logic one value, just as we did to blink the LED in the previous article.

Digital Voltmeter: Programmed 100% PWM Duty Cycle

The multimeter reads about 4.5 volts. (That should nominally be five volts; the reason it's not I believe is that I'm actually driving four GPIO pins all at one time; it simplified the wiring.)

Saleae Logic: Programmed 100% PWM Duty Cycle

Channel zero on the logic analyzer reads a logic one. That's what my Digital Signal Processor (DSP) buddies would call a DC (for direct current) signal, by which they mean it's a constant value. They would use the term unmodulated.

So lets say we write a little more Arduino code to modulate the output of the GPIO pin, that is, turn it on and off. We'll turn it on for the same amount of time we turn it off. That means the pin will be on half, or 50%, of the time, and off the other half of the time. When it's on, it will output its full voltage, and when it's off it will be zero volts or ground. We'll refer to this by saying that the output of the GPIO pin has a 50% duty cycle.

Saleae Logic: Programmed 50% PWM Duty Cycle

You can see that the output of the GPIO pin is a square wave with a period of a smidge over two milliseconds. In a bit you will see that I was shooting for a delay of two milliseconds in the Arduino software. The actual duty cycle, measured by the analyzer, and thanks to slop in timing, is a little over 50%. Any guess as to what the multimeter will read?

Digital Voltmeter: Programmed 25% PWM Duty Cycle

The multimeter reads this square wave as a voltage of a little over 50% of the maximum. That's because the multimeter is expecting a constant DC input and it is reporting the average voltage over some time window. It turns out a lot of electrical devices work this way. So if we can generalize this, we can fool other devices to think they're getting different voltages the same way we fooled the multimeter, and do it all under software control. Let's try an 80% duty cycle.

Saleae Logic: Programmed 80% PWM Duty Cycle

So far so good. What does the multimeter say?

Digital Voltmeter: Programmed 80% PWM Duty Cycle

Yep, that's about right. What we've just done is reinvent Pulse-Width Modulation (PWM). PWM is a method of modulation that encodes a digital signal by altering the duty cycle of a square wave, that is, by changing the width of the pulse. PWM can be used as a way of encoding bits onto a wire, although not in any modern communications system of which I am aware. But its ability to fool electrical devices into thinking they are seeing different voltages is used in lots of applications.

Digital power supplies, ranging from the desktop unit you might see on a lab bench to the unit inside the wall wart that is powering your laptop, use PWM to change 120V from your wall outlet to whatever output voltage is desired. Digitally controlled PWM circuitry replaced old heavy transformers which were nothing more than heavy iron cores with a lot of copper wire wrapped around them, enabling wall warts to become pocket sized and to be much more efficient. The variable speed cooling fans in your computer are probably controlled by firmware that is reading a temperature sensor and then using PWM to apparently alter the voltage going to the electric motors in the fans. The popular AR.drone, a WiFi-controlled quadcopter toy that I've obsessively written about, uses PWM to individually control the speed of each of the four electric motors that drive its fan blades.

Indeed, PWM is so widely used that many SoC chips include a dedicated PWM controller that modulates the output of a GPIO pin at a frequency synchronized to a hardware timer. The ATmega328P chip used in Arduino is such an SoC. PWM requires no more work on the software developer's part than using the Arduino analogWrite function to set a duty cycle encoded as a number from 0 to 255.

But what would the fun be in that?

PWM has long been on my list of things with which to experiment. Naturally, I insisted on doing it the hard way: by writing a PWM algorithm completely in software without taking advantage of the PWM controller built into the chip. I combined two of the circuits included in the SparkFun Inventor's Kit for Arduino, CIRC-03 ("Transistors and Motors") and CIRC-08 ("Potentiometers"), to create an electric motor whose speed could be digitally controlled by turning the dial on a potentiometer. The voltage drop across the potentiometer, a kind of variable resister that can be tuned by turning its dial, is read periodically by the software using an analog-to-digital (A/D) convertor built into the SoC that returns a value in the range 0 to 1023, corresponding to a range of zero to five volts from the reference source. That value is used to compute a duty cycle in the range 0 to 100 for the PWM square wave. The square wave is then fed into a transistor, which is used to switch power on and off to the electric motor. In the photographs above, you can see the electric motor sitting just above the SparkFun fixture, and the blue potentiometer in the lower center with an arrow on its dial.

Here's the complete Arduino program. (As usual, the Blogger editor did violence to the indentation.) I broke up the square wave generator (function square), the timing generator (function timing), and the potentiometer sensing (function control) to simplify trying different things, like hard-coding the duty cycle instead of reading it from the potentiometer. The specific mechanism of generating the square wave is a dirt simple one known as time-proportioning: there is a counter that cycles in the range 0 to 99; the on portion of the square wave begins when the counter is greater than or equal to 100 minus the duty cycle. I also included function toggle to toggle a GPIO pin as quickly as possible to see what my upper limit on frequency was (best case about 500 Hertz), and function pwm to use the built-in PWM controller (which is a lot simpler and is what you should do in an actual PWM application).

int sensorPin = 0;
int motorPin = 9;
int meterPin = 10;
int logicPin = 11;
int ledPin = 13;

volatile uint8_t * pointer = 0;
uint8_t mask = 0;

int tick = 0; /* 0..99 */
int dutyCycle = 0; /* 0..100 */
unsigned int period = 20; /* microseconds duration */
long sample = 0; /* 0.5 seconds */
long iteration = 0; /* 0..(sample - 1) */
boolean before = false; /* true or false */
unsigned long then = 0; /* microseconds elapsed */

void setup() {
pinMode(motorPin, OUTPUT);
digitalWrite(motorPin, LOW);
pinMode(meterPin, OUTPUT);
digitalWrite(meterPin, LOW);
pinMode(logicPin, OUTPUT);
digitalWrite(logicPin, LOW);
pointer = portOutputRegister(digitalPinToPort(motorPin));
mask = digitalPinToBitMask(motorPin) | digitalPinToBitMask(meterPin) | digitalPinToBitMask(logicPin) | digitalPinToBitMask(ledPin);
sample = 1000000L / period / 4;
then = micros();
}

void toggle() {
for (long ii = 0; ii < 25000000; ++ii) {
*pointer |= mask;
*pointer &= ~mask;
}
}

void square() {
boolean state = (tick >= (100 - dutyCycle));
if (state != before) {
uint8_t oldSREG = SREG;
cli();
if (state) {
*pointer |= mask;
} else {
*pointer &= ~mask;
}
SREG = oldSREG;
before = state;
}
tick = (tick + 1) % 100;
}

void timing() {
unsigned long now = micros();
unsigned int duration = now - then;
if (duration < period) {
delayMicroseconds(period - duration);
}
square();
then = now;
}

void control() {
timing();
if (iteration == 0) {
long value = analogRead(sensorPin) / (1024 / 16);
dutyCycle = (value * 100) / 15;
}
iteration = (iteration + 1) % sample;
}

void pwm() {
dutyCycle = analogRead(sensorPin) / 4; /* 0..255 */
analogWrite(motorPin, dutyCycle);
analogWrite(meterPin, dutyCycle);
analogWrite(logicPin, dutyCycle);
delay(250);
}

void loop() {
control();
}


I used the hard-coded duty cycle capability to do the test cases presented above so I could get as close to the desired values as possible. But lest you think the potentiometer solution doesn't work, here's a test run in which I lovingly hand dialed the potentiometer to get a duty cycle of about 66% and a voltage of about three volts.

Saleae Logic: Dialed 66% PWM Duty Cycle

Digital Voltmeter: Dialed 66% PWM Duty Cycle

Just for comparison, here's a test run using my pwm function that uses the built-in PWM generator, at about a 50% duty cycle dialed-in manually using the potentiometer, instead of my purely software-only version. The SoC PWM square wave generator has a higher frequency and its timing is more precise, hence it is able to generate more accurate voltages, and with a lot less effort on my part.

Saleae Logic: Hardware PWM 50% Duty Cycle

Digital Voltmeter: Hardware PWM 50% Duty Cycle

I don't recommend the do-it-yourself approach for an actual application using PWM. I had to do a lot of work to get the timing to be even as precise as you see here, and to deal with the odd side effect here and there like the A/D conversion causing bounce in the GPIO output pin. But along the way I did learn an awful lot about the Atmel hardware and the Arduino software, as I used the Saleae Logic analyzer to characterize the square wave I was generating. That, I do recommend.

6 comments:

F.T said...

Great idea of Arduino

what is arduino

Greg said...

Can you post a parts list and circuit diagram?

I'd really like to go over parts of this experiment myself.

Chip Overclock said...

You can find parts lists and circuit diagrams by following the CIRC-03 and CIRC-08 links in the article. I just wired up those two projects, the motor control circuit and the potentiometer circuit, then wrote the software to read the potentiometer via ADC and control the motor via PWM. (Coincidentally, I'm now writing firmware for PIC microcontrollers to do something similar for a paying client. Although in that case I'm using the built-in PIC PWM controller, as I would recommend for with the AVR as well in a real application.)

Luke said...

I am new to programming, one of my first frustrations starting out was learning the reason behind the apparent success of my smaller projects yet the total breakdown of anything of even slightly more complexity.

As it turned out my Achilles heel was in my limited programming knowledge and the use of "DELAY()". Having re-written my programs to never use delay allows the program and processor to operate freely and smoothly at all times.

Essentially, when you call for a delay you are putting the whole program you are running on timeout (hitting the pause button on the DVD player if you will) for the duration of the delay, and if you use enough of them for timing purposes you end up with a completely non responsive system that is in a constant state of freeze (unaware of its surroundings through lack of being able to process the information around it). Even the use of a single delay in some of my experiments would introduce flicker from the duty cycle the program was non-responsive for due to the delay period. Great fun was had trying to discover why a 5 minute delay was not allowing me to punch in my alarm code, or watching a software delay based switch debouncer routine fail horribly.

Much easier to pay attention to the time in ms and specify via "if" statement when something should happen with reference to a time window.

Luke said...

I am new to programming, one of my first frustrations starting out was learning the reason behind the apparent success of my smaller projects yet the total breakdown of anything of even slightly more complexity.

As it turned out my Achilles heel was in my limited programming knowledge and the use of "DELAY()". Having re-written my programs to never use delay allows the program and processor to operate freely and smoothly at all times.

Essentially, when you call for a delay you are putting the whole program you are running on timeout (hitting the pause button on the DVD player if you will) for the duration of the delay, and if you use enough of them for timing purposes you end up with a completely non responsive system that is in a constant state of freeze (unaware of its surroundings through lack of being able to process the information around it). Even the use of a single delay in some of my experiments would introduce flicker from the duty cycle the program was non-responsive for due to the delay period. Great fun was had trying to discover why a 5 minute delay was not allowing me to punch in my alarm code, or watching a software delay based switch debouncer routine fail horribly.

Much easier to pay attention to the time in ms and specify via "if" statement when something should happen with reference to a time window.

Chip Overclock said...

You've just been introduced to the problems in real-time systems. It's also why the Arduino software architecture, as usable as it is, and a perfect introduction to the field, isn't really useful for larger, more complex, projects. If you look at some of my other Arduino articles, you'll find I end up using a real-time OS, FreeRTOS, on the Arduino, which allows me to write interrupt driven device drivers and use multiple threads of control. However, the technique you describe, which is sometimes referred to as a "task loop" or an "interject scheduler", works quite well for smaller projects. Well done!