Previous

Next

Bottom

Contents

Glossary

Index

 

Persistor CF1 User's Manual

Max146 A-D Examples

May 1999

Revision 1.02

 Persistor Instruments Inc.
© 1998 All rights reserved.
Description

This application note shows various ways of using the Max146 8 channel, 12 bit A-D converter in your applications. All of the examples make use of common driver code (Max146.c and Max146.h) that lives in the "MotoCross Support\CF1\Drivers" directory. The Max146 driver makes extensive use of the QPB drivers in PicoDOS. QPB stands or Queued PicoBUS and is the standard mechanism for accessing SPI devices through the QSPI in the CF1. You do not need to understand how QPB or the Max146 drivers do their work to use the A-D, and that's the point of these examples.

In this application note are descriptions and annotations for the five example programs. The first example is called Max146LPStop and simply shows how to get the Max146 out of the way when it's not in use and additionally shows what's involved in getting the CF1 into the lowest non-suspend power mode where the Max146 idle current of about 1 mA can make a real difference. The second example called Max146SimpleAD and shows how to read A-D channels one at a time or as a block of channels. The third example is called Max146FastAD and demonstrates the techniques required to sample into memory at the highest data rates of 80,000 samples per second aggregate (10kHz for 8 channels). The fourth example is called Max146LowPowerAD and shows how to use the Max146 routines with a periodic interrupt timer to acquire samples into a block of memory while keeping power consumption to a minimum. The next example is called Max146LPFiledAD and adds CompactFlash file storage to the Max146LowPowerAD example. Finally, bringing in one last option for storage is the Max146LPFlashAD which stores data in the on-board flash of the CF1. You should read the descriptions in the order presented since most of the explanations provided for the earlier examples will not be repeated for the latter.

 

Important Notes!

The Max146 bipolar mode does not allow you to connect negative voltage signals to the analog inputs. Some Maxim A-D converters that feature bipolar inputs provide a separate negative supply voltage to accommodate negative going signals. The Max146 does not. Connecting signals below -0.3 volts to the analog inputs will most likely result in device failure.

The Max146Examples.mcp CodeWarrior project is a multiple target project. This is described in Metrowerk's IDE guide, but basically, you select the current build target from the large popup menu near the upper left of the project window. To keep from cluttering up the directory containing the project and source files, we generate the object and map files into the "Max146Examples Data" directory, and this is where you must direct MotoCross when loading files to the CF1.

Other Max146 Reference Documents

Max146.htm

This document describes the Max146 A-D driver built upon the PicoDOS QPB driver. This driver should form the basis for a variety of Maxim A-D devices. 

Max146.pdf

This is Maxim's acrobat document that fully describes the Max146 device. You can get to this from the link in the Max146Examples.mcp project in CodeWarrior or navigate to it in the "MotoCross Support\CF1\Docs\pdf" directory.

Persistor CF1SK Getting Started Guide

This document provides the schematic for the PRC-PLG RecipeCard and shows one way of connecting a Max146 to the CF1. It also contain the connection diagram for the 14 pin header that brings out the Max146 connection points.

<Max146.h>

This header file contains the function declarations used by your programs, and command constants used by the Max146 driver. You will need to include this header in every source module that references the Max146 functions.

PicoDAQ Example

The PicoDAQ example source demonstrates most of the capabilities of the Max146 driver code, but it's required complex functionality makes it less than ideal for learning how to use these functions. It does however demonstrate how to stream A-D data to CompactFlash files a fairly high data rates.

 

Max146LPStop Example

This tiny program shows you how to put the Max146 into shutdown mode so that it will not contribute its normal 1 milliamp of current drain while idling. To see the effect of the A-D shutdown, it's helpful to have the CF1 drawing as little power as possible, and that's what most of the code exists to do. For all of the examples we include the standard CF1 headers (<cf1bios.h> and <cf1pico.h>) plus the <Max146.h> header from the "MotoCross Support\CF1\Drivers" directory. Each example includes other headers from the standard C library as necessary.

The first code after the header defines the constant PRCPLG which means we're working with the PicoLog RecipeCard and can assume the Max146 we'll be working with connects directly to /PCS3 of the QSPI and we assign that to the constant ADSLOT. If you're using a different SPI configuration, you'll need to comment out the PRCPLG definition and provide your own appropriate definition for ADSLOT.

Following that are three more macro definitions that control the depth of power reduction. The first is PDMODE for the Max146 power down mode. Define this to true for full power down where the A-D should contribute just over a microamp, or false where the A-D will draw about 30 microamps. You will of course need a fairly precise meter to read this difference. The second constant is LPMODE which selects one of three predefined LPSTOP modes (defined in <cf1bios.h>) which are listed in the comment line following the definition. We use FullStop for this demonstration as it offers the most dramatic power savings by shutting down everything include the frequency multiplying VCO that generates the system clock. This combined with other steps ahead will bring the current down to about 320uA (270uA with no CompactFlash card installed), but requires about 8.5 milliseconds to resume operation. The FastStop alternate stops the system clock from reaching the CPU and submodules but leaves the VCO running. This brings the power down to between 450uA and 1.4mA for clock frequencies between 160kHz and 16MHz, but incurs no delays when resuming. The CPUStop is similar to the last mode but just stops the clock to the CPU and lets the other 68338 submodules keep running. However, even with the submodules individually turned off, the current ranges from about 550uA to 11mA in this mode, and increases with the work load performed by the submodules. The last macro definition controls the system clock speed which affects the FastStop and CPUStop modes. The faster the clock, the more power the CF1 will draw.

The only variable declaration in the program sets up an array of pin numbers that will be mirrored in a subsequent step. This list should include all of the general purpose I/O pins from the CF1's C connector that are not actively connected to driving signals, and must end with a zero entry. After the print statement that identifies the program, the function call "PIOMirrorList(mirrorpins)" tells the BIOS to setup each pin individually as input, sample its current state, then convert the pin back to an output at the previously read state. From a reset, all of these pins are inputs, and when left floating will drift between Vdd and Vss causing significant (in our low power world) and unpredictable current drains. Pins actively driven or pulled high or low will not have this problem, and setting the pins to outputs also eliminates floating. However, it's very bad practice to just generically set or clear I/O pins when you don't know ahead of time that something else may be driving them in the opposite direction. The mirror technique works for everything except driving signals that change - then it fails quite dramatically. After pin initialization, the system clock gets set using the SYSCLK definition described above.

Following that are the only two things that directly relate to the Max146. First we must register our Max146 device and slot number with QPB using the Max146Init() call, then we ask the Max146 driver to put the A-D into shutdown a call to Max146PowerDown(), passing the shutdown mode we defined at the top as PDMODE. If you are using the PRC-PLG for applications that do not need the A-D, these are the only two calls you need to make to eliminate its current drain from your system.

All of the remaining code exists to set up for LPSTOP operation at the very lowest power drain, and though it does not have much to do with the Max146, we'll walk through the steps so you can understand what's happening. This is not required reading.

PITSet100usPeriod(PITOff) turns off the periodic interrupt. The PIT is actually off at normal program startup, but this snippet of code may find its way into other applications where that is not guaranteed. Shutting down the PIT saves about 30uA of current drain.

CTMRun(false) shuts down the CTM6 sub-module which will affect current drain in the CPUStop mode. The CTM6 drain contribution will vary with the tasks assigned to channels, the system clock speed, and the speed of the various time base busses.

SCITxWaitCompletion() and EIAForceOff(true) work together to ensure that the last transmitted characters complete successfully before turning off the UARTs RS-232 output drivers. The Max3222 RS-232 driver includes charge pump circuitry to provide EIA levels that contribute about 3mA of static current drain. It's receivers continue to work even when the output drivers are shut down.

QSMStop() shuts down both the QSPI and the SCI (UART) which make up the QSM sub-module. This only affects current drain in the CPUStop mode and will vary with data rates, the system clock speed, and the speed of the various time base busses.

CFEnable(false) turns off the CompactFlash card and saves about 400uA. It has no effect if there is no CF card installed.

Finally LPStopCSE(LPMODE) puts the CF1 into LPSTOP. In this particular example, it never comes out of LPSTOP (until reset) and so is not very useful. In real applications, a periodic or external interrupt would wake the CPU from LPSTOP and we would perform the opposite of the shut down functions in preparation for resuming the program. Some of these techniques are demonstrated in the Max146LowPowerAD example ahead.

Max146SimpleAD Example

This example shows the simplest way to add infrequent or low speed analog readings into your programs. It's focus is simplicity and it provide neither fast conversions or low power operations which are covered in the examples ahead. Use the code from this example when you need a quick thermistor or battery voltage reading.

The first difference from the last example is the introduction of the DEBUG and DBG macros which allow us to embed debugging or diagnostic statements into our source code and turn all of them on and off with a single // comment. For this example we leave DEBUG defined so we can monitor the Max146 A-D function timing with an oscilloscope connected to pin 25 of the CF1. Though you can use any I/O pin for debugging, pin 25 (along with pin 1) is a good candidate since it's right in the middle and has a block of white paint to help find it with a probe.

The interesting variables declared in main are the uni and sgl booleans that will later control how the Max146 converts analog signals. They both start out as true which selects unipolar and single-ended conversion.

The first important piece of code is the Max146Init(ADSLOT) call which is the same as described in the first example. Nothing will work if this is missing. The remainder of the code is a forever loop (while(1)) that prompts for a keystroke and dispatches to a case selector to perform the selected operation. Keystroke 'P' puts the Max146 into shutdown pretty much the same way we did in the first example, only now, the full running current drain makes the difference much less noticeable. Keystrokes 'U', 'B', 'S', and 'D' select the conversion mode and modify the prompt accordingly. Keys '0' to '7' initiate a single conversion using the Max146Sample() driver function, while key 'R' repeats a previous conversion using the Max146Repeat() driver call, and both display the results in hexadecimal, decimal, and as floating point voltage. Keystroke '8' initiates a block conversion of all eight channels using the Max146SampleBlock() driver call.

Max146FastAD Example

This example appears ahead of the low power examples because it shares many of the same concerns and techniques, yet it can skip a few complex steps that are required of the low power counterpart. You may find it interesting to use CodeWarrior's Compare Files... feature from the Search menu to look at the small differences between Max146FastAD.c and Max146LowPowerAD.c. Fast acquisition loops need to make the most out of the fewest number of CPU instructions in order to keep up with high speed incoming data stream. Low power acquisition loops need to minimize the number of required CPU instructions to spend more time in LPSTOP.

This example introduces a few new defines at the start. It uses the Max146SampleBlock() call instead of the single channel version and defines FCHAN for the first channel to sample, and NCHAN for the number of channels to sample. Acquisition loop timing is controlled by the periodic interrupt timer, so we have a macro named PITRATE for the PIT sample rate. You can specify and value between 1 and 255 where each step represents 100 microseconds, giving us a range of 10kHz to about 40Hz. There are definitions for common rates in the PIT section of <cf1bios.h> and we use these in the examples for clarity.

The SYSCLK definition defines the clock rate in kHz that we'll run the acquisition at, and the WTMODE defines how aggressive we'll be in accessing the flash and RAM memory. There are two standard wait mode adjustment constants defined in <cf1bios.h> though you can plug in any value. When your program starts, it's running with with the nsMotoSpecAdj selected which is the most conservative application of wait states and guarantees you'll be running within the processor and memory manufacturers specifications over the full temperature range and under full bus loading conditions. With nsMotoSpecAdj you'll have one wait state for both RAM and flash accesses at 16MHz, and zero wait states at 8MHz. Because the CF1 is so tiny and has so few bus attachments, it has a great deal less bus capacitance than full-blown systems and you can usually take advantage of that to reduce the number of wait states at given clock rates. Using the nsStdSmallBusAdj constant you get zero waits at 16MHz and -1 waits at 8MHz. Each reduction in waits increases CPU throughput by about 30% but increases the likelihood that a memory access will catastrophically fail. You can use the HurryUpAndWait example to characterize your CF1 in its fully loaded state to make an intelligent decision about selecting the most appropriate wait state setting. For our examples, we default to the more aggressive nsStdSmallBusAdj setting.

The final macro defines how much memory (bytes) to use for the acquisition loop and that's followed by the global variables used for the acquisition loop. We create a typedef struct for the data array to take advantage of C's willingness to perform multi-byte structure copies in assignment statements and our knowledge that the Metrowerks back-end code generator does a particularly efficient job of performing that copy. We then declare a pointer to the buffer that will later get allocated from the heap, and define the head and tail pointers so we can fill the buffer and know when to stop.

In the main program, we allocate the buffer, set the system clock and waits as described above, then define a set of pins as outputs so we can monitor progress and utilization with an oscilloscope. Their associated DEBUG and DBG macros are explained in the previous example. We then initialize the Max146 driver as in the earlier examples, but immediately follow it with a call to Max146Lock(ADSLOT) to claim exclusive ownership of QPB and the QSPI. We do this because we need maximum performance from the QSPI and if it's not locked, it would internally have to perform many repeated re-setups of multiple registers to guarantee proper operation from a shared QSPI. Locking will always be successful in a program this simple, but it's good practice to check the return result and deal with rejection rather ending up with garbage data and no explanation as to why.

We follow the lock with a single call to Max146SampleBlock() which will perform a set of conversions that we don't really care about, but will also leave QPB and the QSPI setup to perform additional collections with very little overhead. We pass a pointer to our ADSamplingRuptHandler() which gets hold of the CPU when at the end of the last channels conversion and copies the data into the RAM buffer. This is why we preface the first call to Max146SampleBlock() by initializing the head and tail indices to zero. Next, we turn off the periodic interrupt timer (which in this simple example would already be off), then we remove and PIT chores (again, none for this simple example), and finally, we install our own periodic interrupt handling function. We do this instead of installing a PIT chore, because we really, really need the speed, and at the highest data rates, even a couple of tens of microseconds can make the difference between success and failure. At rates below 5kHz, you would most likely be safe (depending on other loading conditions) to simply install a PIT chore.

Now we're ready to start sampling. We print a prompting message then wait for a keystroke. We exit if we get a period character, but otherwise, we prime the head and tail indices, then start the periodic interrupt timer. The acquisition will happen in the background until the buffer fills and we wait in a while loop looking for the head index to catch up to the tail. If DBG is enabled, we can watch pin 26 toggle while it's waiting to finish. In a real application, you could be performing real work while the acquisition was under way. When we notice the buffer has filled, we stop acquiring by turning off the PIT, then offer a display of the collected data. Then we go back to the loop and wait for the next keystroke.

Max146LowPowerAD Example

This example makes a few minor modifications to the Max146FastAD example to run the CF1 and Max146 at greatly reduced power. If you were monitoring current drain in the last example, you probably saw the system draw about 50mA regardless of sampling rate at the 16MHz clock rate. Dropping both the system and sample rate clock by an order of magnitude (1.6MHz/1kHz/8ch) drops that current to about 14mA and that pretty much is the limit of what you can do with just a clock speed change. However, we can perform that same 8,000 samples per second (1kHz/8ch) at just about 4mA by borrowing some of the techniques from the Max146LPStop example, and we can get down to about 1mA for sample rates of 50Hz and below.

The first change from the Max146FastAD example is the lower PITRATE (1kHz vs 10kHz) and lower system clock (8MHz vs 16MHz). The graph below shows the effect of these two parameters (along with NCHAN) on the overall current drain.

The next change is the addition of the LPMODE constant which is described in the Max146LPStop example. Also from that example, we've added in the pin mirroring code to eliminate floating CMOS input currents, and we turn off the CTM6 submodule and shut down the CompactFlash card. The main change is what we do in the acquisition loop while waiting for the buffer to fill up. Instead of just burning up CPU cycles, we put the 68338 into one of two LPSTOP modes depending on the current state of the QSPI. When the QSPI is busy running the Max146, we use CPUStop mode to just turn off the CPU, but when the QSPI is idle, we use FastStop and shut down the QSM submodule to bring the current way down. For this to work, we need to modify the ADTimingRuptHandler() function to reactivate the QSM each time we need to take another block of samples.

Though this example limits the lowest sampling rate to about 50Hz, it would be fairly simple to add a modulus counter to ADTimingRuptHandler() to derive even lower sampling rates. We'll leave that exercise to you.

 

Max146LPFiledAD Example

This example is a minor rework of the Max146LowPowerAD example that streams acquired data onto a file on the CompactFlash card. Once again, use the CodeWarrior Compare Files... capability to quickly observe the differences. The first thing you'll notice is a new AD_RAW_DATA_BUF_MASK field. We need this to manipulate the ring buffer that holds A-D untl it can be written to the CF card. In the main() function, we add a FILE variable, and a sample set counter to report our results. Before starting acquisition, we attemp to open the target file and bail out if there's a problem. The main loop needed a bit of re-structuring to support the ring buffer, but it basically performs the same operations with the added task of sending out any acquired data before going into LPSTOP. Finally, the ADSamplingRuptHandler() function needed minor modifications to change to ring buffer operation.

Measuring current drain for this example when running slow acquisition loops is made difficult by the long periods between CompactFlash card writes which contribute a fair bit of current but may not be captured by a digital current meter. In 8 channel mode, sampling at 2kHz (16 kSPS aggragate) the average system draw is a little over 50mA, at 1KHz (8 kSPS) its about 25mA, and seems to be about 15mA at 500 Hz (4 kSPS).

Max146LPFiledAD Example

This final example is a minor rework of the Max146LPFiledAD example that streams acquired data into the CF1's onboard flash memory. Once again, use the CodeWarrior Compare Files... capability to quickly observe the differences. Several functions have been added to manages the flash. First is PrepFlash which checks whether or not the flash has been erased and erases if necessary. Also there is a function called FlashFwrite which replaces calls to stdio's fwrite to allow minimal changes between Max146LPFiledAD and this example. Keep in mind that the on-borad flash is not an acceptable mass storage method. This example exists simply to illustrate how one would write to flash, and we do not mean to suggest that you should use the flash as data storage, merely that it is possible and this is how you could go about it. It provides limited space, and even less if you have any appliciations in flash. In this example we have defined F_DF_BASE and F_DF_RANGE to specify the base address and size of the storage area. Furthermore, while this example displays the contents of the flash area written to at the end, it should be obvious that the advantage of flash data storage is its non-volatile nature.

   

Previous

Next

Top

Contents

Glossary

Index

Tel: 508-759-6434

Fax: 508-759-6436

Copyright (C) 1998 Persistor Instruments Inc. - All Rights Reserved