Skip to main content

Migrating from x86 to PowerPC, Part 8: Add stepper motors to the vehicle control module

Turn words into actions, literally

Lewin Edwards (sysadm@zws.com), Design Engineer, Freelance
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.

Summary:  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.

View more content in this series

Date:  11 Oct 2005
Level:  Introductory
Activity:  1954 views
Comments:  

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
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

NameTypeDescription
STRT1 byteStart character, '!'
CSUM1 byteUnsigned checksum of all bytes in packet from CSUM+1 to end of BODY (or, all bytes except STRT and CSUM)
TXSN1 byteSerial number of transmission (see below)
IRSN1 byte'In Response To' serial number (see below)
CMND1 byteCommand or data ID
BLEN1 byteNumber of bytes in BODY or zero if no BODY
BODYVariableAdditional 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

DescriptionNameSizeDownload method
Source codepa-migrate8code.tar.gz19 KB HTTP

Information about download methods


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.

Comments



Trademarks

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Multicore acceleration
ArticleID=95218
ArticleTitle=Migrating from x86 to PowerPC, Part 8: Add stepper motors to the vehicle control module
publish-date=10112005
author1-email=sysadm@zws.com
author1-email-cc=dWpower@ibm.com