Summary of the Problem
We produce an industrial high speed document scanner which is controlled by an embedded PC. Our embedded PC board is based on the Xilinx Zynq (ZC702) board, running VxWorks on an ARM processor. The board has an ADC128D818 and we communicate with it via IIC to monitor six voltage levels and temperature on the board.
We run the ADC in mode 0 with continuous conversion and internal VREF. We don't use interrupts with the ADC, and simply read the various channel data periodically for our own analysis. We emit an informational warning message if any of the voltages or the temperature is out of tolerance from what we expect (we permit up to 5% error for voltages, and 150 C for temperature). The voltage readings are typically less than 1% from expected and the temperature is typically around 28 C. We really expect never to see a warning message.
But occasionally, a warning message will indeed appear, typically triggered as a voltage out-of-tolerance warning. I seriously doubt that the voltages were really out of tolerance, and what really happened was that the data was wrong. Here are a couple of samples of what was read, from two different systems:
Channel: IN0 IN1 IN2 IN3 IN4 IN5 TEMP
Expected: 1.000 1.500 1.800 2.500 3.300 5.000 <150
Instance 1: 0.995 1.502 1.502 2.504 3.368 5.035 34.0
Instance 2: 0.994 0.994 1.794 2.516 3.355 5.053 30.0
For each read cycle, the channels are read one after the other, in order, IN0, IN1, ... , IN5, TEMP. Note the anomalous red underlined values. In each case, the value read for that channel was EXACTLY the value returned in the previous channel read (1.502 was read for IN1 but then also reported for IN2; and 0.994 was read for IN0 but then also reported for IN1). This isn't a data handling error in our software. These are the actual values reported by the read command issued via IIC (scaled to meaningful voltage and temperature levels).
When we see this, it is always just a single read that is in error, and the next read cycle shows readings that are as expected
How could this possibly happen?
Device Initialization
The task that interfaces with the ADC128D818 initializes it and then falls into a read loop. The loop repeats once every ten seconds (very slow). This is what we are doing to initialize the device. Numbers at left margin are recommended steps in device Quick Start documentation, added for reference. This code was all plunked into the application from a piece of sample code obtained from who-knows-where without knowing what it really does. I inherited this and am trying to understand it. This is my analysis of what the code is doing.
The "wr" and "rd" calls are all IIC write and IIC read calls (see next section for detail) and I have shown the register and data bytes written and read.
call init():
- initialize iic driver:
XIicPs_LookupConfig()
XIicPs_CfgInitialize()
XIicPs_SetSClk() 100000
2 - read busy status reg // wr 0x0c, rd 1 byte // ADC_REG_Busy_Status_Register
- stop() // wr 0x00, wr 0x00 // ADC_REG_Configuration_Register : clr START, clr INT_Enable
3 - program adv cfg reg // wr 0x0b, wr 0x00 // ADC_REG_Advanced_Configuration_Register : ADC_MODE_0, ADC_VREF_INT
4 - program conv rate reg // wr 0x07, wr 0x01 // ADC_REG_Conversion_Rate_Register : sel ADC_RATE_CONTINUOUS
5 - enable/disable channels // wr 0x08, wr 0x40 // ADC_REG_Channel_Disable_Register : 0x40 (disable IN6)
6 - mask interrupt status // wr 0x03, wr 0x7f // ADC_REG_Interrupt_Mask_Register : 0x7f (unmask only TEMP)
7 call init_limit() // wr 0x2a, wr 0x80 // IN0 High :
call init_limit() // wr 0x2c, wr 0xa6 // IN1 High :
call init_limit() // wr 0x2e, wr 0x80 // IN2 High :
call init_limit() // wr 0x30, wr 0x80 // IN3 High :
call init_limit() // wr 0x32, wr 0x80 // IN4 High :
call init_limit() // wr 0x34, wr 0x80 // IN5 High :
call init_limit() // wr 0x36, wr 0x80 // IN6 High :
call init_limit() // wr 0x38, wr 30 // TEMP High :
call init_limit() // wr 0x39, wr 29 // TEMP Hyst :
? call read_register() // wr 0x0b, rd 1 byte // ADC_REG_Advanced_Configuration_Register
? call read_channel( 0x0c ) // wr 0x2c, rd 2 bytes // bug? meant 1 byte from ADC_REG_Busy_Status_Register?
8,9 call start() // wr 0x00, wr 0x03 // ADC_REG_Configuration_Register : START=1, INT_Enable=1, INT_Clear=0
Data Acquisition
This is how we acquire the data after the device is initialized. The loop repeats indefinitely, but is only kicked once every 10 seconds:
loop:
wait for semaphore (given every 10 sec)
call read_channel( IN0 ) // wr 0x20, rd 2 bytes // IN0 Reading
call read_channel( IN1 ) // wr 0x21, rd 2 bytes // IN1 Reading
call read_channel( IN2 ) // wr 0x22, rd 2 bytes // IN2 Reading
call read_channel( IN3 ) // wr 0x23, rd 2 bytes // IN3 Reading
call read_channel( IN4 ) // wr 0x24, rd 2 bytes // IN4 Reading
call read_channel( IN5 ) // wr 0x25, rd 2 bytes // IN5 Reading
call read_channel( TEMP ) // wr 0x27, rd 2 bytes // TEMP Reading
call read_register() // wr 0x01, rd 1 byte // ADC_REG_Interrupt_Status_Register
Read/Write Routines
Here is the C++ code, stripped and simplified, that interfaces with the IIC calls in the BSP:
These routines are called to perform "wr" and "rd" operations above.
Note in particular the delay in the write routine.
int ADC128D818::I2CWrite( int addr, u8 *data, u16 ByteCount )
{
if( XIicPs_MasterSendPolled( &IicInstance, data, ByteCount, addr ) != XST_SUCCESS ) return XST_FAILURE;
while( XIicPs_BusIsBusy( &IicInstance ) );
taskDelay( gIicWriteDlyMs ); // gIicWriteDlyMs = 300 ms // was usleep( 250000 )
return XST_SUCCESS;
}
int ADC128D818::I2CRead( int addr, u8 *BufferPtr, u16 ByteCount )
{
if( XIicPs_MasterRecvPolled( &IicInstance, BufferPtr, ByteCount, addr ) != XST_SUCCESS ) return XST_FAILURE;
while( XIicPs_BusIsBusy( &IicInstance ) );
return XST_SUCCESS;
}
I can furnish the exact code for the initialization and data acquisition if desired (it's just a little prolix).