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