ADC’s — High Temperature And FreeRTOS
I am a proponent of the PSOC5. If the PSOC6 had as many UDB’s in it, I would be on that bandwagon. If it worked at high temperature (not yet tested), at the office we would switch to it ASAP, even though it is NOT 5 volt tolerant.
The PSOC5 is 5 volt tolerant, works at 350F, and is low power. The PSOC Creator is excellent, if slow. If they would take the GUI designer and port it to a stand alone application, it would make even ModusToolbox something I would grimace and use. Especially if it supported PSOC5. Like Quigley, “doesn’t mean I don’t know how to use it.”
However, this post is about ADC’s and high temperature.
SAR ADC’s hunt to find the voltage at their input by comparing a voltage from a resistor divider to the input resistance. With a big enough set of resistors, enough analog switches, a voltage reference, and a comparator you can breadboard your own SAR ADC.
The two things of critical importance are the stability of the of the voltage reference and the amount of time the input voltage can stay stable at the input of the comparator. For this reason, many use capacitors to hold the voltage. Self discharge of those capacitors determines the accuracy, as the voltage can change before the Analog to Digital conversion is complete.
That is why a SAR takes a certain number of clock cycles to complete its conversion. You switch in comparison voltages and hunt your way to the answer. Once you have a high voltage that is just a little higher than the comparison ladder, you subtract that voltage from the input voltage and start the process over again with the lower voltage.
Delta Sigma ADC’s work about the same way, but they add a filter to their input. Their signal input frequency ranges are thus typically lower, but they can process to more accuracy, getting readings down to the billionths of a volt (nanovolt).
High Temperature Stand Alone ADC’s
In my primary business, it is known that products tested to 125C (250F) will generally work at 150C (300F). However, that is not always the case, and some products with have a success rate of 1 in 10 or less. Some products will have an entire production batch that works, and subsequent production batches fail.
The ads131m02 family of ADC’s from Texas Instruments will occasionally work at 300F to 350F. However, the failure rate above 125C is high enough to preclude the use of that device. In addition, its temperature stability, even to the temperature of 125C is low enough not to use for absolute measurements. Even so, it is a decent ADC with a good differential gain stage.
An ADC that will work at temperature is the Analog Devices AD7711. However, it is an extremely temperamental device. It often locks up, and the only solution is to power it down. It has an SPI interface which ignores the SPI rules. You need to receive the data from the device on a different edge than you send the data to the device, which can be done with UDB’s in the PSOC5. However, it is very stable if you cause it to perform self calibration often.
High Temperature Internal ADC’s
Unsurprisingly, the PSOC5’s ADCs all work at temperature. The voltage reference drifts with temperature, but you can use the Die Temperature and compensate for much of the drift. The aluminum wiring inside the IC changes resistance with temperature, but smart firmware can overcome these limitations. Not ideal, very annoying, but usable.
ADC’s and FreeRTOS
To use an ADC with an RTOS, you have to think differently. If you think properly, you can change which ADC you are using without changing much of your code. Or use multiple ADC’s at the same time using the same framework. I will give the outline of how to do this, and then show some general code. If you want code that works with the ADS131M0X family, search on Github for ads131m03. If you find https://github.com/aaronlevan/ADS131M03 you are there. You will have to adapt that to the M02 device, but it is relatively simple. That code has embedded some understanding of the 131 device in it. Pay close attention to its order of initialization and use. It differs from the TI data sheet because that document is misleading or wrong in places.
RTOS SYSTEMS: First, Separate ADC Data From Processing, If Possible
All uses of ADC’s fall into three parts: 1) Get the Raw Data, 2) Process it into Usable Form, 3) Make decisions or transmit data based on the Usable Form. The processing and the decisions can usually be made in what I call “slow time,” which is typically on the order of milliseconds. The acquisition of the Raw Data must be made in “real time,” which is typically on the order of microseconds.
Get the Raw Data
Have the ADC interrupt the processor when data is available, and transfer it in. If the data is available via SPI, if possible cause the data to be automatically transferred. Otherwise, initiate the transfer, and then get an interrupt at the end of the transfer. The following code initiates the data transfer from an SPI master using an interrupt from the ADC:
//------------------------------------------------------------------
// isr_adsDataIsReady() handle isr_ADC_DATA_READY interrup
//------------------------------------------------------------------
CY_ISR(isr_adsDataIsReady){
// very first sample after power up is total trash.
if (firstIRQ){
firstIRQ=0;
return;
}
StartSPITxRxDMA(); // start the normal ADC data gathering.
flag_nDRDY_INTERRUPT=true;// flag we had the interrupt
}
Once the data is received by the SPI master (which can take several hundred microseconds, use the SPI done interrupt to pull the data in:
//------------------------------------------------------------------
// isrSPIM_ADC_RX_Complete() -- post to the task, the isr finished
//------------------------------------------------------------------
// I like using a static to allow for easier debug
static BaseType_t xHigherPriorityTaskWoken;
CY_ISR(isrSPIM_ADC_RX_Complete){
// if the adc handler task is not initialized, throw away the data
if (!gADCHandlerTaskQ)
return;
//adsDataIsReady++; //<--debug
if (!adsADCCommand)
adsADCCommand=( void *)ADS131_ADC_TC_TQ_INDEX;// arbitrary
xQueueSendFromISR( gADCHandlerTaskQ, &adsADCCommand, &xHigherPriorityTaskWoken );
/* Now the task has been unblocked a context switch should be performed if
xHigherPriorityTaskWoken is equal to pdTRUE. */
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );//either this or portEND_SWITCHING_ISR() will work
}
Process The Data, Use The Data
In the ADC Handler task (which in my case handles multiple ADC’s, do the following:
void *ADCMsgPtr;
void task(){
gADCHandlerTaskQ = xQueueCreate( 8, sizeof( char* ) );
adc_init();
for(;;){
_TaskInfoPtr->runCounter++;// rough profiler
// layered xQReceive call, this is a pseudo code call
if ( QReceive( gADCHandlerTaskQ, &ADCMsgPtr, 1000) == pdPASS ){
switch((int16_t)ADCMsgPtr){
case ADS131_ADC_TC_TQ_INDEX:{
// declare local variables
MyDataType result;
// grab the data and process it.
data =SPIM_ADC_ReadDataFromDevice();// pseudo code
// process the data
result = lah_dee_dah(data);
UseTheData(result);
}break;
}
}
}
}
The priority of the FreeRTOS Task is relatively high, so it does not get behind processing the data. You can put the data in a ring buffer in the ISR and then post to the ADC Task, thus allowing its priority to be very low, as it would not have to keep up immediately, but on average could keep up with the data. Or, if no other ADC’s are in the system, post it to the gADCHandlerTaskQ and consume the Queue. The task could get up to Queue Length behind before losing samples.
Finally
I have used this pattern with many different devices and several different RTOS’s, including one I wrote myself for a 7 mhz processor. They are all the same, even though totally different.
Enjoy!