Designing with the NIOS (Part 2)
Second-Order, Closed-Loop Servo Control
After introducing the NIOS embedded CPU to you, George laid the
groundwork for a two-axis motion control system. Now he discusses
velocity feedback and preps you on the software you'll need to drive
the system.
Last month I started describing a two-axis motion control system
featuring an Altera NIOS embedded CPU. In fact, I put the entire
system inside one of Altera’s FPGA devices. I left off with V00-00. I
had the CPU and one axis designed. Also, I did an initial placement and
achieved my design goal of 20-MHz operation. This month I’ll
explain how to enhance the axis component by adding velocity feedback
and building a two-axis system. I’ll also detail the software
you’ll need to run the design.
Well, I was extremely pleased with
the V00-00 version of this project. Typically, you would review
this with all the interested parties in your organization (a design
review). And out of that hypothetical meeting, suppose service
said that you needed to monitor the temperatures of motors and other
circuitry. And marketing was really close to having an answer
about the network requirements. What to do?
I found Analog
Device's AD747x serial A/D converters, which have 8, 10, or 12 bits
resolution, and convert at a 1-msps rate. So, I added a four-device SPI
serial interface to the NIOS processor. For the network requirements, I
added an interface to the LAN91C111 which I found detailed on
an Altera development board. So, now I have a schematic and software,
if marketing is ever able to firm up their requirements.
The new SPOC builder screen shot is shown in Photo 1. Note that the SPI
interface added seven pins, three of which are used for data in, data out, and
clock, and four of which are chip selects one for each device. The LAN91C111 added three
pins because there is already a buss for off chip memory. The pin count used in your design can be a
limiting factor in any FPGA design. Requiring too many pins pushes you
into a larger, more expensive FPGA package.
The place and route achieved
31.02 MHz in the selected device. The maximum clock frequency increased
even though I added more logic elements. Don't count on this to
be the norm. The timing analysis tells me that the longest path is in
the axis-error
calculation, which isn't surprising. I could speed up this path and
increase the maximum clock frequency by adding registers in the path.
But, as it stands, I've met the design goal.
I also added a velocity
feedback element to the design. The velocity feedback block
diagram is shown in Figure 1 and detailed in Figure 2. Its purpose is
to limit the systems maximum velocity. The velocity is calculated and
then subtracted from the error signal. When the error signal is at its
2047-count limit, the motion speeds up until the velocity reaches a
large enough value to limit additional speed increases.
If you recall, velocity is change in position divided by change in
time. The velocity feedback design has P0 to latch the present position
and P1 to latch the previous position. The latching is performed by
counting 4,096 clock ticks (4,096 × 50 ns = 0.203450 ms). A
little state machine transfers the position data into the appropriate
registers at the proper times using states S0, S1, and S3. Note that
S2 is not used, so don't look too long for that reference. I was
planning to add registers for settling time on the calculations, and S2
provides another 50 ns of delay for the calculations to complete.
So, I
have delta-P over delta-T, and that's available on the output pins.
This number needs to be scaled. I could scale it by changing the 4096
clock divider in the velocity feedback count block. I could increase or
decrease the time period for the measurement. If that's not sufficient,
I could change the type of counter to a count down and achieve any time
period. If that's still not enough, I could add a multiplier ot the VEL[31..0] signal to
scale it to the appropriate value.
Figure 1 shows an optional filter block just before the output
signal. I didn't include this as part of my design. This
would be a great place to add a digital filter to attenuate any
unwanted frequencies in the output signal. Most systems have
resonant frequencies. The mechanical designers try to eliminate
them, but you usually will be faced with working around them. If
you need a visual example, imagine a fly fishing rod. As you
slowly move your arm and the rod, the tip of the rod exhibits no lag
in its position. But, as you start accelerating your arm and the
rod, the rod's tip starts to lag. If you move your arm back and
forth at just the correct frequency, the tip of the rod will be moving
in the opposite direction of your arm.
You've just found the systems resonant frequency. The
controller's output can be thought of as a step containing all the
frequencies. If the resonant point in your mechanical system is
below the high-frequency cut off defined by the output drivers, then
the mechanics will oscillate whenever the offending frequency is
output. And because the step output contains all frequencies,
your system will oscillate.
After you know the resonant frequency, you could define a
digital filter to remove it from the output signal. The great
part about this solution is that each system probably has a slightly
different resonant point, and you can tune the digital filters for each
system. Production will love that.
SOFTWARE
I built a set of software routines to help you understand the NIOS
design. These routines create a test bed for code development and will
actually run the hardware. I used Turbo C++ V. 3.0 in a DOS window to
write, compile, and debug code. I know this is an old method, but it's
great for getting started, especially when the hardware doesn't exist
yet. Eventually, you will use the GNU tool chain to build the software
for the NIOS CPU.
UART
The interface to each of the peripherals selected for the design is
generated as a set of registers. Take the UART for example. The
register set for the UART is defined as a structure in the excalibur.h
file (see Listing 1). The structure np_uart (“np” stands for NIOS
peripheral) has six 32-bit registers that interface to the hardware.
These registers are memory-mapped I/O. The first register is
np_uartrxdata, which contains the data received in the UART. The actual
data is only 8 bits, but 32 bits are reserved for this and every
register. Others are defined too: na_uart_0 is a pointer to an np_uart
located at the 0x00040000 address; na_uart_0_base is the base address
value for the UART 0 register set; and na_uart_0_irq is the interrupt
request number.
The excalibur.h file also contains bit definitions for the UART and all
peripheral control purposes. So, the software can be completely written
using the excalibur.h file. To make hardware design changes, all you
need is the latest copy of excalibur.h. If you have ever bought a C
compiler dedicated to a microprocessor, it probably came with a header
file that described the CPU; those header files look a lot like
excalibur.h.
In the ARMCOM.c file posted on the Circuit Cellar ftp
site, you will find the routines that directly touch the NIOS hardware,
while other routines transfer data in and out of serial buffers in
memory (CCCOM.c and CCCOM1.c). The PutCom0(BYTE c) routine puts a BYTE
(8-bit entity) in the UART’s transmit buffer (see Listing 2). First, a
pointer to the UART register structure is declared (np_uart *uart), and
then the pointer is set to point to the uart_0 register set in
memory-mapped I/O. And finally, the data is placed in the Tx register
(see Listing 2).
I know I've got some explaining to do. First, let's take a look at the
constants __TURBOC__ and RUNNING_IN_NIOS. When the Turbo C compiler is
invoked, __TURBOC__ is defined and RUNNING_IN_NIOS is not defined. If
__TURBOC__ is not defined, then RUNNING_IN_NIOS is defined.
Look at the
beginning of CCCONST.h, which you may download from the Circuit Cellar
ftp site. This file enables code to run in Turbo C or any other compiler, without
writing to an address that will cause problems on the PC. Uart0 is
defined at some constant address in excalibur.h, so accessing it in
Turbo C will probably crash the PC’s operating system. I know this
because I've done it by accident.
Next, the BYTE keyword is defined in
CCCONST.h. When different compilers and CPUs are used, the value of
definitions such as int can change. So, I created keywords including
BYTE, INT16, INT32, and UINT8 to define what I intend to do.
If I use a
different CPU or compiler, I will redefine these values for each new
implementation. Take a look at the INT16 for the Turbo C (16-bit)
compiler versus the NIOS (32-bit) compiler. Lastly, note that
if you are compiling for Turbo C and running in a DOS window, the
output for Com0 goes to the stdout, which is the console on the PC.
The
serial communications routines are in the ARMCOM1.c, CCCOM.c, and
CCCOM1.c files. The incoming characters generate an interrupt in which
characters are read from the hardware and placed in a buffer. Input
line-feed characters are ignored. I've had trouble when systems send a
carriage return and then line feed while others send a line-feed
followed by a carriage return. So, I just started ignoring line feeds
and life became simpler.
Also, I processed complete lines of data as
they were received. I define an end-of-line character for each serial
port. When that character is received, a line counter is
incremented. All the main routine looks at is that line counter. When
the input line counter does not match the output line counter, I know a
complete line has been received and I start to process that line.
I selected a NIOS UART with a fixed data rate and other parameters. If
you look through these serial routines, you’ll see how to interface to
the NIOS UART hardware and then you can extrapolate to other variations
you might add to the CPU. Altera also provides documents and code
examples for each of the peripherals.
TIMER
The CCTIM1.c file contains the timer routines. You can set the timer
period and install the timer interrupt. The timer variables are
incremented for each interrupt. If you have a timer interrupt period of
1 ms, every 1000 interrupts are equal to 1 s in the real world.
I
created a timer test option in the main program loop. With this option,
you can install and remove the timer interrupt, and set the interrupt
period.
PIO INPUT AND OUTPUT
I used a parallel I/O scheme to interface to each axis. Basically, I
set a value for the NIOS PIO and that worked its way down through the
hardware. This is the safest approach I could think of. It's slower
than designing each axis as a device, like the UART, for which you
could just write to a memory-mapped register offset from some base
address. My approach takes a couple of extra instructions for each
operation, but it works without any hassle.
In the CCPOS.C file, I
defined the bit functions for the Read_Status[15..0] inputs and the
CTRL[15..0] outputs. On the input side, I have Loop Status (OK/fault),
Home, Limit, and Fault indicators for each axis. On the output side, I
have Latch Position, Clear Position, Set Position, Clear Target, Latch
Target, and Close Loop for each axis. I don't use every one of these
bits because several are redundant. I included all of them to show you
what's possible in this type of design.
The SetTarget, GetPosition, and
SetPosition routines are fairly self explanatory. These are used to clear control bits, output/input the data, and then
set any control bits (see Figure 3). The control bits are positive edge
active.
The InitAxis routine opens the control loop for the axis. Then,
it sets both the position and target registers to zero and closes the
loop.
I left out looking for errors in the control loop. Potential
errors include a high motor temperature reading, detection of limit
input, or a time out on a move command. Each system is so different
that showing one implementation is a waste of ink.
Another feature of the NIOS PIO input registers is that an interrupt
can be generated for an event on any of the input bits. If a safety
interlock is activated, you might want to open the loop immediately.
MOTION CONTROL
You have two options for motion testing in the main control loop. The
first is to zero both axes. The second is to move one of the axes a
predetermined distance.
Let's start with the second command. The format
is as follows: G n dddd<CR>. “G” is the unique command prefix,
“n” is the axis, “dddd” is the command amount, and “<CR>” is the
end-of-command character. This data is entered into the serial port and
processed using the call count = ReadLineCom1(buf). The return value is
the number of characters returned in “buf.” If there are any, the main loop attempts to process the
returned characters. The first character is the test option selected.
If that first character is “G” or “g,” then control is transferred to
RunMotion and pass that routine is passed a pointer to the rest of the command
line.
The RunMotion routine takes the pointer and extracts the axis
number and the size of the step. It does so by first ignoring (skipping
over) spaces in the command string. Then, a call goes to the C atoi()
routine, which returns the integer value of the characters starting at
the pointer value passed to it. That return value is the axis number.
Skipping over the axis number any any subsequent spaces, the pointer is now at the
start of the step size. The atol() C function to extracts the
value of the step size. Be careful here. You need to represent position and step sizes
in 32-bit entities. In Turbo C, atol is used to get a 32-bit return
value. In NIOS, I selected a 32-bit CPU, so I expect the compiler to
interpret atoi() to return an int, which is a 32-bit entity. You need
to test this assumption when you're debugging the hardware.
You now have an axis and a step size. The motion system should be
resting, and the position and target registers should be equal values.
However, note that the target register is constant, and the position
register varies slightly because outside disturbances cause the
mechanism to move.
I have defined target registers for each axis. You
need to add the step to the target register and set that new value in
the hardware. This causes the hardware to generate an error signal that
drives the axis motor in a direction to reduce the error. You can
monitor the position until the motor stops moving and it's close enough
to the target. If all is well, you're done.
A word of caution here:
Lots of things can go wrong. The mechanism could run into something and
never get to the target position. The step size could be inaccurate and
you might move too far past your limit. The move could be so slow that
you time out before reaching your goal. The motor could overheat and
shut down. Furthermore, you could reach your goal but be unstable,
oscillating back and forth across the target. I did not consider this when I wrote the code. I just looked for a way
to reach the target. This gets you started on the right foot. You also
might want to modify the design to address the potential
problems.
You'll notice that the RunMotion routine assumed the axis
was at some sort of acceptable, stable position. How did you get there?
Well, you went through a zeroing process, which moved the axis
to the home position, which was zeroed along with the target registers.
Actually, each axis has a home, zero, and limit position defined by its
construction.
Home is a position that you can move to rapidly. Some
overshooting is permitted. Zero is a position found under stable,
repeatable conditions. For example, moving to zero occurs at a slow rate
until an optical sensor is tripped. Limit is a fault-type condition
that indicates you've gone too far. Sometimes software reacts to the limit condition (soft limit), and sometimes the
hardware takes over (hard limit) and shorts the motor, causing the axis to stop
abruptly. You can move the axis by hand slowly away from the limit sensor. If you try to move
quickly, the motor will act as a generator and the short will act like
a brake.
A ZeroAxis() routine that takes care of this homing
operation. Its implementation requires information from outside
sensors. Keep in mind that I used hypothetical data for this article
rather than gathering data from sensors. Therefore, you’ll want to pay
more attention to the comments than the actual code. I suggest adding a
GoHome(INT16 Axis) routine that would close the loop and start stepping
relatively small amounts toward the home direction until the home
sensor is activated.
NEXT
Well, next month I would like to show you a complete system with...
Wait a minute. Why don't you take over? It doesn't have to be a NIOS;
any FPGA vendor's system would be good. Come on, show us where you're
going today.
To download the code, go to ftp.circuitcellar.com/pub/Circuit_Cellar/2004/168
NIOSUART-3.0, 2003. Nios Peripheral Library, www.altera.com/products/devices/nios/features/nio-peripherals.html
NIOS embedded processor, SOPC Builder, QuartusII design software
Altera Corp.
(408) 544-7000
www.altera.com
George Martin began his career in the aerospace industry in 1969. After
five years at a real job, he set out on his own and cofounded a design
and manufacturing firm (www.embedded-designer.com). George's designs
typically include servo-motion control, graphical input and output,
data acquisition, and remote control systems. George is a charter
member of the Ciarcia Design Works Team. He's currently working on a
mobile communications system for the military. In his spare time,
George has become a nationally ranked revolver shooter. You can reach
him at george.martin@att.net
RESOURCES
Information about development systems,
www.parallax.com/html_pages/products/altera/smartpacks.asp
www.altera.com/products/devices/NIOS/kits/nio-dev_kits.html
www.jopdesign.com/cyclone/index.jsp
www.cepdinc.com/altera/cas10.htm
and www.microtronix.com/products/cycldevkit.html
SOURCE
NIOS embedded processor, SOPC
Builder, QuartusII design software
Altera Corp.
(408) 544-7000
www.altera.com
If you found this article interesting you will find much more
like these at Circuit Cellar. You can obtain the complete article including all source
listings at Circuit Cellar.
Look in the Products and Services section for back issues in print and
CD
Part 1 of this article
back to Articles
back to ESC Home
Copyright © 2003-2004 by ESC Inc.
All rights Reserved
Last update: November 10, 2004