 | Level: Introductory Lewin Edwards (sysadm@zws.com), Design Engineer, Freelance
11 Oct 2005 Lewin Edwards reviews the I/O expansion possibilities for the slave microcontroller on the robot submarine. The Inter-IC Communication (I2C) bus provides a simple and compatible way to expand the submarine's I/O options and connect it to a number of stepper motors. The previous article in this series introduced the basic vehicle control module (VCM) hardware and demonstrated
how to get it talking to the Kuro Box over an asynchronous serial
connection. This article builds on last month's hardware
design by adding the capability to control two four-phase unipolar stepper
motors (more explanation of that terminology later). You'll also see how to enhance the rather skeletal serial protocol introduced last month, to
include integrity checking and more intelligence.
The ATmega32 offers a rich set of peripherals useful for this sort of
project, but inevitably once you start to use those peripherals, the
availability of general-purpose I/O pins shrinks.
To realize the maximum amenity from the ATmega32's peripheral
set, you therefore need to categorize your I/O requirements, first
according to whether a hardware feature of the microcontroller can carry out or accelerate the desired function, and secondly (in
the case of functions that you will be implementing entirely in software), according to
the specific timing limits for implementing the function. For example,
something like a radio receiver input, with tight timing requirements,
would be best connected directly to a micro input pin (preferably a pin
with interrupt-on-change capability).
Building I/O expansion
You then need to build enough I/O expansion to get around the shortage of
I/O pins on the microcontroller. You can achieve this goal in many ways, of which these are the top three:
- Use external flip-flops (for example, 74HC373) to latch expansion output
data and external tristatable buffers (for example, 74HC244) to scan in expansion
inputs.
- Use the ATmega32's on-chip SPI module to drive external I/O
expanders.
- Use the ATmega32's on-chip I2C (TWI, see sidebar1) module to drive external I/O
expanders.
Each of these methods has its own distinct advantages and disadvantages that are worth exploring.
Using external logic is a simple route. It also offers the lowest possible
latencies. However, the difficulty is that it directly consumes a
relatively large number of I/Os. For example, if you wanted to add 24 I/Os
to your device, you could add three 74HC373s and three 74HC244s. The inputs
of the 373s and the outputs of the 244s would form a common data bus,
routed to eight pins on the micro. But you would also need a read/write strobe
line and at least three addressing pins -- bringing the total required I/O
count up to 12.
The simple external logic method also can't give you an interrupt when
input states change; in some applications, that might be important. Of
course, you could structure external interfaces
with jellybean logic in many other ways, but it's a headache to design and debug and is often
difficult to route on the printed circuit board (PCB) as well.
Serial-connected I/O expanders utilize CPU pins much more effectively, and
these chips also provide interrupt-on-change capabilities on the inputs,
if you should require it -- they are also generally programmable, so you
can remap inputs and outputs as necessary with software changes rather
than needing to mess with physical wiring. All things considered, serial
I/O expansion is definitely the way to go.
On-board serial interfaces
This article considers two very popular on-board serial interfaces:
SPI and I2C (see sidebar1).
 |
I2C, SPI, TWI, 2-Wire, 3-Wire?
The two most popular low-bandwidth synchronous serial buses for
intra-board communications are I2C (Inter-IC Communication) and SPI
(Serial Peripheral Interface). However, some slightly confusing
nomenclature is in use: Atmel calls its I2C interface TWI (Two-Wire
Interface), and many other vendors refer to I2C as 2-Wire, and SPI as
3-Wire. The reason for this is intellectual property rights -- I2C is
trademarked by Philips, and SPI is trademarked by Motorola. Everybody who
implements a compatible interface without paying licensing fees has to
hide behind an alternate name.
Once in a while you will encounter a truly proprietary serial bus, but
it's far from common.
|
|
SPI is usually described in fashionable literature as a three-wire
interface, but in any system with more than one peripheral this is a bit
of a smoke and mirrors act -- it really requires four lines:
clock, data in, data out, and select. All SPI peripherals are connected to
common clock, data in, and data out lines, and each peripheral has its own
dedicated select line. (The two data lines are referred to as MOSI and
MISO, acronyms for Master Out, Slave In; and Master In, Slave Out,
respectively). To address an SPI device, you assert its _SS (Slave Select)
pin and start clocking data out of your MOSI pin. The same clock signal
pulls data out of the peripheral into your MISO pin, so after eight clocks
you've simultaneously sent a byte and received a byte. The transaction
ends when you deassert the _SS pin.
SPI is very easy to use (especially if you have to implement it in pure
software), but the requirement for individual _SS (Slave Select) lines
means that you need to add extra wiring -- and an extra I/O -- for every
peripheral, rather than having a true "bus" architecture. It is therefore
slightly harder to route complex SPI buses over a PCB.
The I2C serial bus
I2C gets over this limitation and allows you to add extra peripherals to
the bus simply by wiring them in parallel with existing devices. It
achieves this at the expense of some implementation complexity. I2C is a
two-wire interface; the signals are named SCL (Serial CLock) and SDA
(Serial DAta). I2C peripherals have a 7-bit address -- generally, some of
the address is fixed for a given part, and a few bits are configurable by
means of resistor strapping or EEPROM setup. The protocol is,
unfortunately, quite involved, with numerous meaningful states and state
transitions. Luckily for us, practically all of the requisite magic is
implemented in hardware inside the ATmega32's TWI module; this is a case
of hardware support really saving you a lot of design and debug time. All
you need to do is add two pull-up resistors selected according to the load
capacitance of your I2C bus and the desired maximum data rate, and wire up
all your devices in parallel.
The principal disadvantage of I2C is that it's difficult to propagate over
long distances through noisy environments. Because the data line (and, in
multimaster systems, also the clock line) is bidirectional, you can't
shape up the signals with a simple buffer. I2C was designed for, and works
best in, communications on a single board or within a subassembly. It's
commonly used in TV sets, PC motherboards, laptop batteries (and other
smart battery types), and other consumer equipment.
I have an ulterior motive for using I2C -- namely, the rich variety of
peripherals available with I2C buses. Practically any sensor you can think
of that might be of interest in an embedded application is available with
an I2C-flavored interface -- for example (and this list is far from
exhaustive), temperature sensors, pressure sensors, battery charge
controllers, control interfaces for image sensors, television on-screen
display chips, and even complex subassemblies like flux gate compasses,
gyroscopes, and GPS receivers. Although some of these sensors are available
with SPI-style interfaces, I2C is more widely supported.
The revised circuit design
With all that background information digested, examine the schematic for
the revised circuit:
Figure 1. The
VCM module
As you see, I've chosen to use the Microchip MCP23008 8-bit I/O expander.
This chip supports the three standard I2C data rates of 100kHz, 400kHz,
and 1.7MHz. Note that there are other expander ICs, such as the Philips
PCA95xx series parts, that offer more bits of I/O for roughly the same
price as the MCP23008. I'm using the Microchip device in these
developerWorks projects because it's available in a dual in-line package (DIP) and is
therefore easier to hand-prototype; the PCA95xx parts are only available
in SOIC and (T)SSOP. Moving to one of the higher pin-count devices is,
however, a trivial modification, both in terms of hardware and code.
The MCP23008's outputs drive the inputs of a ULN2803A octal Darlington
driver, which I use to drive two four-phase stepper motors. You'll
find a link in Resources to some excellent
background reading on stepper motors, as well as some handy hints on how
to identify steppers scavenged from computer peripherals. I have amassed a
huge collection of stepper motors simply by picking up my neighbors'
unwanted inkjet printers and flatbed scanners off the curb on trash day.
Because it is now cheaper to buy an entire new printer with fresh ink
cartridges than to buy a set of replacement cartridges, I usually see two
or three junked printers per week, each one a trove of motors and
mechanical parts suitable for miscellaneous experiments.
For more formal projects, you can buy new, documented steppers from
sources such as the suppliers mentioned in Resources. eBay is also a rich
source of brand new steppers, usually with documentation -- although you
often have to buy them a tray at a time.
Note that the ATmega32 allows you to perform I2C operations
asynchronously, using interrupts (though it can be quite complex to write
an interrupt service routine (ISR) that covers all the bases if you're using several different I2C
peripherals that have radically different protocols). The code I'm
presenting here uses a simpler polled method. Note that this I2C code
is not reentrant! If you plan to use the TWI module from an interrupt
routine as well as main process code, you must erect suitable barriers to
prevent an interrupt from attempting to use I2C in the middle of a main
process I2C transaction. The firmware in this article is carefully
designed to avoid this sort of contention.
Speaking of code, it's time to unpack the source code
archive for this episode and build the VCM code in the vcm_399
directory and the SCM code in the scmd directory.
 |
A hint on easy code editing
If you're following along with the code, you're now probably working on
two platforms simultaneously. If so, I've got a simple hint for you. Keep
all your source code -- including the AVR source code -- on the Kuro Box in
the /mnt/share directory. On your development PC, mount that directory
over SMB (it's \\KURO-BOX\SHARE by default if you didn't mess with any
samba settings).
That way, you can keep a telnet window open to the Kuro Box to build the
Kuro code, and build the AVR code directly off the Kuro's hard disk, but
with the compiler running on your "other" machine (be it Linux® or
Microsoft® Windows®). In fact, you can keep all the source code inside a single IDE.
If you're running on Windows, I suggest you map the Kuro Box to a network
drive letter, as some programs have difficulty working from universal naming convention (UNC) paths.
|
|
Observe first, and most importantly, that we've tightened up the serial
packet format. The previous article in this series used some very simple
demo code that you could talk to with a terminal emulator, just to prove
that the communications were working bidirectionally.
The new packet format is as follows:
Table 1. New packet format
|
Name
|
Type
|
Description
| | STRT | 1 byte | Start character, '!' | | CSUM | 1 byte | Unsigned checksum of all bytes in packet from CSUM+1 to end of BODY (or, all bytes except STRT and CSUM) | | TXSN | 1 byte | Serial number of transmission (see below) | | IRSN | 1 byte | 'In Response To' serial number (see below) | | CMND | 1 byte | Command or data ID | | BLEN | 1 byte | Number of bytes in BODY or zero if no BODY | | BODY | Variable | Additional data, if required |
Each end waits for a "!" character to begin buffering data. The packet is
deemed complete when the requisite number of BODY bytes has been received.
Bytes received beyond the capacity of the serial-receive buffer are
discarded. Since the start character might occur in the middle of real
data, the CSUM field is used to verify that the data block just received
was in fact a complete packet.
But what are these "serial numbers" I mention? They are a means of
identifying specific question-answer pairs in a transmission stream, as
well as for spotting dropped packets. At power up, both the VCM and the
Kuro Box initialize their internal serial number counter to 1 (0 is a
reserved value). When one end of the link wants to send a packet, it sets
the TXSN field to the current serial number value, then increments the
serial number counter (if the counter overflows to 0, it is reset to 1).
If the packet being sent requires a response, when the other end replies,
it sets the IRSN field to the TXSN from the packet to which it is
responding.
An "originated" packet, or, one that is not a response to a request from
the other side of the link, has the IRSN field set to 0.
This very simple system allows either end to keep a history of pending
data requests and track which responses belong to which outstanding
requests (as well as, if necessary, implementing time-outs and retries on
packets that simply don't get a response).
An example illustrates this most easily. Suppose you defined command
#99 to be "Get current vehicle attitude," and command #100 to be "Get
current vehicle position from GPS." Furthermore, suppose that the current
serial number in the VCM was 56 and the serial number in the Kuro Box was
80.
Then say the Kuro Box wants to know the vehicle's current attitude and
position as soon as possible. It might send the following packets in quick succession:
- A packet with CMND=100 (get GPS position), TXSN=80, IRSN=0, and no BODY
- A packet with CMND=99 (get attitude), TXSN=81, IRSN=0, and no BODY
The GPS position can take some time for the VCM to acquire, so the
attitude could easily be ready for transmission before the position data.
Assuming this scenario takes place, the VCM would respond with:
- A packet with CMD=99 (get attitude), TXSN=56, IRSN=81, and attitude
data in the BODY. Note that the nonzero IRSN implicitly indicates that this
is a response rather than a request.
- (Later, when data is available) A packet with CMD=100 (get position),
TXSN=57, IRSN=80, and the position data in the BODY.
If for some reason the VCM didn't respond to one of these packets, the
Kuro Box could keep a record of which serial numbers are outstanding, and
after some time-out period, reissue the request. (Note that this system
breaks down if the serial number wraps and comes back to the same number
as the earliest pending request. For this reason, your time-out code should
also trigger if TXSN wraps to the earliest pending serial number).
Some more rules wrap these kinds of queued requests, but
I'll discuss those protocol details once you've got some of the
appropriate hardware attached.
Before you run the code, you should understand what it does and how to wire up
your stepper motor(s). The links in Resources
describe in detail how a stepper motor (synchronous DC motor) is
constructed and driven, but here's a thumbnail: A stepper motor has fixed
coils and a permanent magnet rotor. The types of motors I use have
four coils (phases), each of which has one line run to a common wire and
the other wire run to the outside world. (The common wire is also run to
the outside world.) When a given phase is energized, it pulls the rotor
towards a certain position. The four phases are physically positioned so
that if you energize them in repeated sequence (1, 2, 3, 4, 1, 2, 3, 4, 1,
...) the motor will turn continuously in a given direction.
The stepper outputs on the VCM board are labeled SAP1 through 4 (Stepper A Phase 1 through 4) and SBP1 through 4 (Stepper B Phase 1 through 4). You
should connect the phase lines up to the SxPx lines as appropriate, and
the common line(s) from your motor(s) to the +24V line. On the VCM, I use
a 9-position terminal block to take these signals off-board.
Attention! Observe the series resistors on the stepper drive lines. The 0ohm resistors I've specified are placeholders; you should substitute
suitable current-limiting resistors based on your drive voltage and the
stepper motor's coil resistance and rated current.
This code and hardware are designed to provide very simple control of two
four-phase unipolar stepper motors, Stepper A and Stepper B. The step
functions are driven, by default, off a 100Hz software timer. This is a
moderate speed for junkbox steppers, and a fairly safe arbitrary choice on
my part. The hardware is capable of considerably higher step rates,
however. You should be aware of the following two issues as you increase
step rates:
- Without going into the detailed mechanics of it, a stepper can't
start from zero and instantly begin operating at its maximum rated step
frequency. You need to implement "acceleration profiles" if you intend to
get the most out of your motors. In brief, this means that if you need to
make a speed change on the motor, instead of just jumping to a new step
rate, you ramp the step rate towards the target speed. Acceleration
profiles need to be calibrated for the motor and the mechanical load being
driven.
- As you increase step rates, torque decreases. Cunning hardware
designs are necessary to squeeze the maximum possible performance out of a
given motor.
This particular design is nowhere near to pushing the envelope in either
of these respects -- you have plenty of headroom to increase step rates,
for instance.
Referring to stepper.c, you'll see that the software keeps track of the
absolute position of each stepper, checking it at the step speed interval.
If the position ordered by the Kuro Box is not the same as the current
position, the appropriate motor is stepped either forwards or backwards.
The serial interface code listens for "seek to..." packets and updates the
target values appropriately.
The VCM code, for the moment, assumes that the power-up position of each
stepper motor is its known absolute zero position. Finding the zero
reference is normally accomplished by having a hardware zero position
detector attached to the motor's drive shaft (or the mechanism it
operates). For example, in floppy disk drives, the track 0 sensor is an
opto-interrupter gate, or (in older drives) sometimes a microswitch that
the head engages when it is stepped to the track 0 position.
The two packet types supported by the VCM firmware in the vcm_399
directory are CMD_STEP_A and CMD_STEP_B (defined in vcmpacket.h). If you
build the Kuro Box side in the scmd directory, you'll see the Kuro Box
printing out status info from the VCM and, once every four status packets,
sending the VCM commands to seek both stepper motors to random locations.
Please note that the Kuro Box side of the code is quick and dirty, since
at the moment I'm concentrating on the VCM side. A little later on, after
we start integrating the two more closely, I'll tidy and tighten the Kuro
Box-side code.
However, while on the Kuro code, note that the terminal
setup is slightly different in this version than in the scmd provided with
the previous article. Specifically, I removed the IGNCR bit on the input
flags, otherwise every 0x0d byte would be stripped by the terminal layer.
Here's some sample output from the scmd program:
Listing 1. Stepper motor demo output
root@KURO-BOX:/mnt/share/article8/scmd# ./scmd
IBM developerWorks Kuro Box to VCM Demo Applet #2 - Stepper Demo
Waiting for VCM to start sending...
Rx packet (TXSN 0x01, BLEN 0x05) - MTIME 0x000005b8, FLAGS1 0x00
Rx packet (TXSN 0x02, BLEN 0x05) - MTIME 0x00000b70, FLAGS1 0x00
Rx packet (TXSN 0x03, BLEN 0x05) - MTIME 0x00001128, FLAGS1 0x00
Rx packet (TXSN 0x04, BLEN 0x05) - MTIME 0x000016e0, FLAGS1 0x00
Tx 2 packets: CMD_STEP_A->00000167, CMD_STEP_B->FFFFFF97
Rx packet (TXSN 0x05, BLEN 0x05) - MTIME 0x00001c98, FLAGS1 0x00
Rx packet (TXSN 0x06, BLEN 0x05) - MTIME 0x00002250, FLAGS1 0x00
Rx packet (TXSN 0x07, BLEN 0x05) - MTIME 0x00002808, FLAGS1 0x00
Rx packet (TXSN 0x08, BLEN 0x05) - MTIME 0x00002dc0, FLAGS1 0x00
Tx 2 packets: CMD_STEP_A->FFFFFFAF, CMD_STEP_B->0000004A
Rx packet (TXSN 0x09, BLEN 0x05) - MTIME 0x00003378, FLAGS1 0x01
Rx packet (TXSN 0x0a, BLEN 0x05) - MTIME 0x00003930, FLAGS1 0x00
Rx packet (TXSN 0x0b, BLEN 0x05) - MTIME 0x00003ee8, FLAGS1 0x00
Rx packet (TXSN 0x0c, BLEN 0x05) - MTIME 0x000044a0, FLAGS1 0x00
Tx 2 packets: CMD_STEP_A->FFFFFED7, CMD_STEP_B->FFFFFF46
Rx packet (TXSN 0x0d, BLEN 0x05) - MTIME 0x00004a58, FLAGS1 0x00
Rx packet (TXSN 0x0e, BLEN 0x05) - MTIME 0x00005010, FLAGS1 0x00
Rx packet (TXSN 0x0f, BLEN 0x05) - MTIME 0x000055c8, FLAGS1 0x00
Rx packet (TXSN 0x10, BLEN 0x05) - MTIME 0x00005b80, FLAGS1 0x00
Tx 2 packets: CMD_STEP_A->FFFFFE0E, CMD_STEP_B->000001E3
Rx packet (TXSN 0x11, BLEN 0x05) - MTIME 0x00006138, FLAGS1 0x02
Rx packet (TXSN 0x12, BLEN 0x05) - MTIME 0x000066f0, FLAGS1 0x00
Rx packet (TXSN 0x13, BLEN 0x05) - MTIME 0x00006ca8, FLAGS1 0x00
|
Observe that the VCM informs the Kuro Box if it's busy stepping every
time the VCM sends status; this information is contained in FLAGS1 in the
CMD_STATUS_REPORT packet. FLAGS1 bit 0 set means that Stepper A was
running when the status packet was generated; likewise, bit 1 means that
Stepper B was running.
So now you have most of the makings for a set of dive planes and a rudder,
and that's it for another exciting episode of the Kuro Box-based vehicle.
In the next episode, you'll see how to add some sensors to the VCM --
specifically, MEMS accelerometers and temperature sensors. Now would be a
good time to brush up on your vector arithmetic in preparation for next
month's installment!
Download | Description | Name | Size | Download method |
|---|
| Source code | pa-migrate8code.tar.gz | 19 KB | HTTP |
|---|
Resources Learn
Get products and technologies
- Download the source code referenced in this article from the Download table above. The usual
warning applies to Internet Explorer users -- make sure to save the file as
something.tar.gz.
- Most surplus components suppliers sell reasonably priced stepper motors.
I generally look at Alltronics, or
MPJA, or Jameco when I'm trying to acquire a small
number of identical steppers with documentation at a reasonable price. The
major distributors like Digi-Key and
Mouser also carry a small range of
steppers at premium prices. Failing that, the big industrial supplies like
McMaster-Carr can meet ALL of your
needs for (large) submarine parts including massive NEMA steppers, air
bottles, tubing, solenoid valves, and anything else you can think of.
Discuss
About the author  | |  |
Lewin A.R.W. Edwards works for a Fortune 50 company as a wireless security/fire safety device design engineer. Prior to that, he spent five years
developing x86, ARM and PA-RISC-based networked multimedia appliances at
Digi-Frame Inc. He has extensive experience in encryption and security
software and is the author of two books on embedded systems development.
He can be reached at sysadm@zws.com.
|
Rate this page
|  |