During development of my Sumo robot, I ran into some problems around controlling the H-bridges:
I decided to use a slave microcontroller to solve these problems. I used a PIC16F84A, mostly out of convenience. It has enough I/O pins to run all four inputs of two H-bridges, plus a few left over for configuration and control. It generates phase-coherent PWM drive signals that are suitable to directly control an H-bridge. It's controlled by an I2C bus; a simple serial bus that is commonly implemented on microcontrollers. The AVR has hardware support for it (although it calls it TWI), making programming simple.
Device pinout
The address bits let you have multiple motor controllers on the same bus. Each I2C device on a bus has a unique 7-bit address. This device has that split into a five-bit prefix ('10000') and a two-bit suffix, set by these pins. They should be tied high or low depending on the address that you want the chip to have.
The I2C pins are used to control the chip. They're covered in more detail below.
The Status LED is lit while an I2C transfer is taking place. It's mostly useful for debugging.
The H-bridge outputs are on PORTB. If you don't need braking, you can just connect the A and B outputs from the chip to A and B on your H-bridge. Most H-bridges will just melt if you try to brake them, so check before you connect!
You can control a motor directly from the A output, too (with a buffer or FET or power transistor!)
The device has a fairly limited vocabulary as far as I2C transactions goes: it only responds to writes, addressed directly to it (no global calls), using the normal (fast) START condition (no slow STARTs) and 7-bit addresses. The default address is b'10000xx', where 'xx' is the value of the pin straps on PORTA. The device will accept an eight-bit data transfer in the format '0cdbpppp', where:
The device has a 10MHz clock by default, which gives a maximum I2C frequency of 8kHz. It can go faster, but will become less reliable. The most common symptom of a too-fast I2C bus is that transfers won't be ACKed. This is a recoverable condition; the device won't ACK unless it received all data bits intact. It won't (shouldn't!) receive the wrong data and destroy your H-bridge. Of course, if you want to run the bus faster, you can give it a faster clock - the PIC16F84A is specced up to 20MHz.
The default PWM output frequency is about 300Hz with a 10MHz clock. This appears to be a good number for small DC motors. The firmware can be tweaked to give higher or lower rates - or again, you can twiddle the clock rate.
The I2C bus (also referred to as TWI, or Two Wire Interface) uses, unsurprisingly, two wires. It's pretty neat; there's a data (DATA) and clock (CLK) line that is shared amongst all of the devices on the bus. A few simple rules govern data transfer; the original spec from Philips is short and well-written. The clock rate (for standard-rate devices) is up to 400kHz, which is more than enough. Since the PIC uses a bit-banging I2C implementation, the highest clock rate is about 8kHz - still plenty for robotics applications.
The gist of how I2C works is:
There, wasn't that easy? :-)
The controller is based around a PIC16F84A. It has 13 I/O's, a timer, and not a lot else. To program it I'm using JDM programmer hardware, ICProg software, and the excellent Microchip MPLAB suite to compile and simulate the software.
The controller board, attached to the main control board: Note the I2C cable running between them, which provides the main control board with power.
There's not much to the hardware. The address select pins need to be tied high or low depending on the I2C address that you would like the chip to use. The LED can be left out if you like. The crystal is 10MHz by default, but it can be adjusted to pretty much whatever you like given the caveats described elsewhere.
I use a 4-pin header to provide +5V, I2C data, I2C clock and ground. The plan is that each module on the robot will have two of these headers. They connect to the same pins so that modules can be daisy-chained together on the same bus. Each module can take its power from the bus, which simplifies everything greatly. Only one board needs to provide power to the bus. I'm doing it on the motor control board because it already has a (relatively) high voltage supply to run the motors.
For those that just want the firmware download, we have:
There are two main tasks to be performed by the software: talking to the I2C bus, and setting the H-bridge outputs. The outputs need to be updated on a set schedule, so the PIC's timer is used to regulate it. The I2C bus is polled, and the PIC has no direct hardware support. This is why the maximum frequency is so low compared with I2C-native devices.
The I2C implementation is fairly uninspiring; read the Philips I2C spec if you want to understand what's going on. It requires open-drain outputs for the bus lines. The PIC only has one (and I'd already attached it to a LED on the prototype). To simulate open-drain outputs, the PIC I/O's are tristated for a '1', and set as outputs (with an output value of '0') for a '0'.
Once the I2C code has received an entire transfer, it does some legwork for the H-bridge code. When a channel's PWM signal is high or low, a certain value is written to the output port. This is the mapping between command and H-bridge lines. Each channel has a pair of registers that set these high or low values. Depending on the command (forward, reverse, brake, coast), the I2C code will set up these register in advance, so all that the PWM code has to do is stick them on the output pins.
The PWM uses a 'ratio' register for each channel to determine what fraction of the total time the PWM signal is 'high' for. On each iteration, a 'value' register is incremented. Its maximum value is the maximum PWM ratio. On each cycle, if 'value' is greater than 'ratio' for a given channel, the signal is low. Otherwise, it's high. Simple, in theory!
This algorithm has a few implications. First, with the H-bridge output algorithm, the four output PWM signals (per channel) are phase-coherent. This is necesary for H-bridges to prevent heating or possible H-bridge destruction.
The code runs once per possible output value. Hence the low PWM resolution (four bits). This isn't a problem for most motor control applications, although I'm sure someone will email me and tell me that they need more! More precision means more interrupts, which will limit the maximum I2C frequency.
Since the outputs are updated on a timer, the PWM frequency can be adjusted fairly trivially by adjusting the timer period. The only limitation on this is that the PWM interrupt takes cycles away from the I2C code. I assume that at worst, the I2C code will be interrupted once between each bus sample, which gives a maximum PWM frequency of about 2kHz. If you're running near this limit and in doubt, reduce the I2C speed, or stress-test the I2C bus, or add some error checking to your I2C transmitter. The I2C code won't ACK if it doesn't receive a transfer properly, so there's no (theoretical) risk of getting corrupt data.
If the PWM signal for a given channel is high, we stick the stored 'high' value for that channel on the outputs. If not, we put 'low' on the outputs. That's pretty much it! All of the real work has been done by the I2C code.
There is a slight possibility of H-bridge heating here if the PIC outputs don't switch quickly enough, switch at slightly different times, or if the H-bridge itself can't switch fast enough. This would only come up during a forward/reverse or braking transition. It's not an issue in my application, but a possible improvement would be to have some 'coast' time in the middle of the transition during which the H-bridge stops conducting. This will restrict the maximum output power slightly, but it's not likely to be noticeable given that we are controlling electromechanical devices which can't change direction that quickly anyway.
It's entirely dependent on your situation - the H-bridge you're using, the purpose of the motors you're driving, the drive voltage, and so on. The easiest thing to do, if it's an issue, is to insert the 'coast' period in your control software.
Stuff I'd like to do for Version 2:
Some of these things can be done on the same PIC device, but the a better solution would be to change technologies. The AVRs often have onboard I2C, so that would get me part of the way (and remove a pretty major testing burden, as well as possible licensing issues). Probably a better solution would be to use a CPLD or FPGA. Current devices have clock speeds vastly in excess of what's necessary in this application, so there's no performance problem. They tend to have a lot of I/O, so many channels (at least four) would fit on a single device. And the programming languages (VHDL or Verilog) tend to be much more suitable for this type of work, where you're mostly twiddling bits in registers and maintaining a state machine. (I later wrote the VHDL for everything except the I2C interface in about half an hour.)
The only problem I can see so far is that the current devices have relatively low maximum I/O voltages - typically 3.3V. (The internal cores run at significantly less than that; 2.5V is a typical maximum nowadays). So there'd need to be some support circuitry to provide output buffering. Remember that a crisis can also be an opportunity (crisitunity! or not); this would give the possibility of arbitrary output voltages for your difficult-to-drive H-bridges.
Most current FPGAs are SRAM-based, so they lose their program when power is lost. There are plenty of ways to get around that - use an OTP or Flash-based FPGA, or a slave Flash chip to program the FPGA on startup - so that's not a big deal.
FPGAs are awesome.