Pulse width modulation, or PWM is a trick every embedded developer
should have in their toolbox. The idea is simple: Generate a pulse
train with a given duty cycle to control the average power sent to some
load device. Used with an LED, for example, PWM can dim or brighten the
light efficiently and reduce power consumption. Applied to motors, you
can control the speed of the motor while maintaining torque. With an RC
filter--and maybe an op amp--you can average the pulses into an anlog
voltage. Speaking of analog, it is often useful to read analog inputs.
In fact, there have been many times I've needed to control a PWM output
with an analog input. That's exactly what the
GP-9 does.
You can read more about the board (pictured above) on the GP-9 page.
But the short story is that the board has 5 0-5V analog inputs (8-bit)
and 8 PWM outputs. The board starts in one of two modes. In the first
mode, the board waits for computer input via an RS232 or USB port. The
computer can read the analog inputs, control the PWM outputs, or link
the inputs to the outputs.
The second mode doesn't even require a host computer (although you can
still use one if you like). In this mode, the board simply outputs 5
PWM outputs based on the 0-5V input on the corresponding analog
channel. If channel 1 reads 2.5V, for example, a 50% duty cycle PWM
signal will appear on output #1. You can still exercise computer
control in this mode, if you like. But for some applications, you don't
need a computer at all.
This application note will show you an example program (pictured to the
right) that allows you to control the GP9. The program is written using
the Qt library and works on Linux or Windows (you could probably port
it to the Mac with minimal difficulty). Details about Qt are beyond the
scope of this application note, but note that you can
download Qt and the tools for it free. The toolkit is licenesed under the LGPL.
The board uses the same low level serial port code that the GP-3 uses.
This differs between Linux and Windows (see gp3linux.cpp and gp3win.cpp
in the source archive). However, the basic calls do the same job.
Namely, open a serial port, read a byte from it, write a byte to it,
and close it. The board operates at 9600 baud and uses a similar
protocol to the GP3 to ensure the board does not get out of sync with
the host computer.
The real heart of things, though, is the GP9 object that represents the board to the rest of the program:
class gp9
{
public:
gp9(QString _port) { portopen=error=false; port=_port; } // create with given port (ttyUSB1, COM2, etc.)
bool open(); // open it up
bool isOpen(); // determine if open
bool close(); // close down
void setpwm(int chan, int pwm); // set PWM from 0 to 255 on channel 0-7. (Source must be set)
void setsource(int mask); // source bits for channel 0-7, 1=slider, 0=analog input
void setjam(int mask); // jam channels 0-7 high by setting the corresponding jam bit
void setoe(int mask); // output enable for channel 0-7 (0=enabled)
int readpwm(int chan); // read back the PWM
int readad(int chan); // read analog channel (0-4)
// ought to be protected but since we don't wrap everything they stay public
// so you can manually read/write to the board
void write(int byte); // single byte from 00-7F
void write(int b1,int b2); // two bytes sent in GP3/GP9 protocol
int read(void); // read back a byte
int getoe(void); // read back the OE bits
int getsource(void); // read back the source bits
bool error; // set if error detected
protected:
QString port; // port to use
bool portopen; // are we open
};
None of these are really Qt-specific except the port name does take a QString.
Reading a value from the board is easy:
int gp9::readad(int chan)
{
write(0x30+chan);
return read();
}
The only real trick is when you need to write more than one byte out. The code to set the PWM, for example, looks simple:
void gp9::setpwm(int chan, int pwm)
{
write(chan & 7, pwm);
}
The trick is that the two-argument write() call sends the output using
the special GP-9 protocol. In this protocol, the most signficant bit of
the first byte is always a zero. The same bit in all subsquent bytes
(well, in this case the second byte since no commands take more than
two bytes total) is a one. Of course, that means you lose a bit in each
byte. The protocol specifies that the least significant bit of the
second byte is actually the 6th bit of the first byte. That's easier to
see by example. Suppose you wanted to write the command "02 01" which
would set PWM channel #2 to a value of 1. The actual bytes you would
send would be 42 80 (in hex). This way the GP-9 can always synchronize
on the first byte. It notices the 6th bit is 1 (40) and strips it off,
leaving the actual first command of 02. Then it reads the 2nd byte,
strips off the top bit (which must be one) and adds the 6th bit read
previously so that the result is, indeed, 02 01.
The GP-9 manual has more details about the protocol and the different
commands, most of which are modeled by the gp9 class. Here's the actual
write code that implements the protocol:
void gp9::write(int byte)
{
if (!portopen)
if (!open()) return;
gp3write(byte);
}
void gp9::write(int b1, int b2)
{
if (b2&1) b1|=0x40; // if 2nd byte ends in 1, set b1.6
b2>>=1; // adjust b2
b2|=0x80;
write(b1 &0xFF);
write(b2 &0xFF);
}
The rest is just user interface things, mostly in the main window. The
voltmeters update on a timer and the sliders control the PWM outputs as
long as the bottom check box is checked. If the box is unchecked, the
analog inputs set the outputs. The top checkbox on each channel shuts
that channel off if unchecked.
You can
download the entire sourcecode (for Linux and Windows).