Can't get correct temperature information from temp sensor via ADC

I am running FreeRTOS in a emulated LM3S811 board via QEMU and I’m trying to get temperature info (I am using this project (the keil one)… you can find the vanilla source code here ).

Currently, this is the code:

// ...
// include and define stuff
// ...


int main(void)

{

prvSetupHardware();

// other stuff

xTaskCreate( vMyTask, "MyTask", configMINIMAL_STACK_SIZE+300, NULL, mainCHECK_TASK_PRIORITY , NULL );
vTaskStartScheduler();

}


static void prvSetupHardware( void )
{

/* Setup the PLL. */
SysCtlClockSet( SYSCTL_SYSDIV_10 | SYSCTL_USE_PLL | SYSCTL_OSC_MAIN | SYSCTL_XTAL_6MHZ );

// ...
// other gpu and uart stuff
// ...

// Enable ADC Peripheral
SysCtlPeripheralEnable(SYSCTL_PERIPH_ADC);

//Allow the ADC12 to run at its default rate of 1Msps.
ADCSequenceConfigure(ADC_BASE, 1, ADC_TRIGGER_PROCESSOR, 0);

//our code will average all four samples of temperature sensor data    ta on sequencer 1 to calculate the temperature, so all four sequencer steps will measure the temperature sensor
ADCSequenceStepConfigure(ADC_BASE, 1, 0, ADC_CTL_TS);
ADCSequenceStepConfigure(ADC_BASE, 1, 1, ADC_CTL_TS);
ADCSequenceStepConfigure(ADC_BASE, 1, 2, ADC_CTL_TS);
ADCSequenceStepConfigure(ADC_BASE,1,3,ADC_CTL_TS|ADC_CTL_IE|ADC_CTL_END);

//enable ADC sequencer 1.
ADCSequenceEnable(ADC_BASE, 1);


IntMasterEnable();

}


// ...
// other stuff for uart and gpio
// ...


volatile uint32_t ui32TempAvg;
volatile uint32_t ui32TempValueC;
volatile uint32_t ui32TempValueF;
volatile uint32_t ui32TempSet;

static void vMyTask( void *pvParameter )
{
    uint32_t ui32ADC0Value[4];
    ui32TempSet = 25;
    
    while (1)
    {
        //indication that the ADC conversion process is complete
        ADCIntClear(ADC_BASE, 1);
        //trigger the ADC conversion with software
        ADCProcessorTrigger(ADC_BASE, 1);
        //wait for the conversion to complete
        while(!ADCIntStatus(ADC_BASE, 1, false));
        //read the ADC value from the ADC Sample Sequencer 1 FIFO
        ADCSequenceDataGet(ADC_BASE, 1, ui32ADC0Value);
        ui32TempAvg = (ui32ADC0Value[0] + ui32ADC0Value[1] + ui32ADC0Value[2] + ui32ADC0Value[3] + 2)/4;
        ui32TempValueC = (1475 - ((2475 * ui32TempAvg)) / 4096)/10;

        char valchartemper[10];
        sprintf(valchartemper, "%d", ui32TempValueC);
        char *valchartemperPtr = valchartemper;
        while( *valchartemperPtr != NULL ){
            UARTCharPut(UART0_BASE,*valchartemperPtr); // this prints via uart the temperature value
            valchartemperPtr++ ;
        }
        SysCtlDelay(SysCtlClockGet() / 3); //delay ~1 sec
        
    }

    vTaskDelete( NULL );
        
}

Now, inside the “vMyTask” code there is while(!ADCIntStatus(ADC_BASE, 1, false)) which allows to exit from it just when the conversion is complete.

But since it NEVER exit from it, the code below it can’t be executed.

So just for curiosity I commented it so to execute the code below anyway, and what I get is always the same value. It’s also an impossible value: 429452990 celsius degrees. so impossibile.

Questions:

  1. What is that value? Is it just a random uninitialized one?

  2. Since it’s obvious that I must be miss something (it’s not normal that while(!ADCIntStatus(ADC_BASE, 1, false)) never exits), I want to know what I’m missing. Am I missing an ISR code for ADC that I have to MANUALLY write? Am I missing some interrupt handling or peripherals handling in qemu? What am I doing wrong?

  3. Does qemu simulate the behavior of temperature or does it ALWAYS give the same temperature regardless of the cpu usage for example?

Is the ADC emulated by QEMU? It sounds like the ADCIntStatus function is reading the memory location where the ADC is found and expecting to find a completion status flag. If there is no ADC model in the emulator, this flag will never set and the loop will not complete.

This is an opportunity to add code to that while loop that will check the tickcounts from FreeRTOS and abort the loop with and ADC error if the conversion takes too long. Generally that is not a failure for HW ADC’s (the conversion timers are usually very simple) but it is probably an issue in the emulation.

Here is a rough idea.

uin32_t ADCstartTime = xTaskGetTickCount();
enum error_t error = NOERROR;
while(!ADCIntStatus(ADC_BASE,1,false)){
   if( xTaskGetTickCount() - ADCstartTime > pdMS_TO_TICKS( ADC_CONVERSION_TIME_MS )){
      error = ADCTIMEOUT;
      break;
   }
}
if( error == NOERROR ){
   // USE THE ADC VALUE
}
else{
   // NO ADC PRESENT OR ADC IS BROKEN.
}

Thanks for the answer!

So we are not waiting for an interrupt signal that arrives the cpu, but instead ADCIntStatus only periodically reads a certain and specific memory address that represent the status flag of ADC peripheral, right? There is not a ISR here, right? Just a polling method. And hence we don’t need an interrupt handler because of this, right? Or am I saying wrong things?

Anyway, QEMU supports ADC emulation for this board, so that is not the problem… I really have no clue. What could it be? What can I do?

I do not know the details on how Luminary ADC works as that is not part of freeRTOS and is provided by Luminary.

Have you run a simple ADC demo provided by Luminary? (I assume there is such a demo). If that works correctly, the same code should work correctly in freeRTOS.

Thanks for answer.

Even if you don’t know the details… but what I’ve written above, can it be a realistic scenario? If I don’t manually register ANY interrupt handling function associated with ADC, this means that no ISR is executed for ADC peripheral, right? So I guess that the ADCIntStatus just reads that ADC peripheral address to check if there is a status flag… so it’s like a polling method(?)

Have you run a simple ADC demo provided by Luminary?

Either I can’t find it on web, or Luminary doesn’t provide an ADC demo. Maybe I am searching in wrong way?

Your code looks reasonable to me. I would expect the ADC code to work as follows:

  1. Configure I/O pin for ADC operation
  2. initialize ADC
  3. Select an ADC channel (that matches the pin)
  4. Start the ADC conversion
  5. Poll for ADC conversion complete
  6. Read the data.

The details will depend upon how that API/HW work.
Looking at your code I see what looks reasonable but without knowing that device I can’t be more helpful. Does the datasheet provide ADC operation examples?

I only found this: https://www.ti.com/lit/an/spma028/spma028.pdf and this: https://www.ti.com/lit/ds/symlink/lm3s811.pdf

One thought, does the interrupt status get set even if interrupts are disabled for this device?

It might be that you need to test a different register/bit.

thanks for answer.

One thought, does the interrupt status get set even if interrupts are disabled for this device?

What you mean? As I wrote above, there really isn’t actually an interrupt handler for ADC, in the code that I am using. So it’s more like a polling method that reads in that specific address and checks for the “interrupt status flag” which actually doesn’t really interrupt anything, it just sets 1 bit, if I have understood. So it’s not a ISR thing, it’s a polling method I think.

This only if I have understood correctly and I am not missing something.

Anyway, ADCIntStatus basically performs this operation:

if(bMasked)
{
    return(HWREG(ulBase + ADC_O_ISC) & (1 << ulSequenceNum));
}
else
{
    return(HWREG(ulBase + ADC_O_RIS) & (1 << ulSequenceNum));
}

so if it returns true it exists from the while. So basically this is never true.

In the description of ADCIntStatus it’s written:

//! This returns the interrupt status for the specified sample sequence.

//! Either the raw interrupt status or the status of interrupts that are

//! allowed to reflect to the processor can be returned.

richard-damon and I are speculating, but some hardware will set flags inside a peripheral register and those peripheral flags can also set interrupt flags but only if the interrupt flags are enabled. If the interrupt flags are NOT enabled, then polling on the interrupt flag will have no result.

I did a bit of googling and without HW there is not much more that I can do to help… BUT I did find this bit of work. https://users.ece.utexas.edu/~valvano/arm/ADCSWTrigger.c

The key interesting piece is found here:

// This function triggers an ADC conversion using sample
// sequencer 3, waits for the conversion to finish, and returns
// the result in the lower 10 bits of the return value.  It
// assumes that the hardware has already been initialized
// using ADC_InitSWTriggerSeq3().
unsigned long ADC_InSeq3(void){
  unsigned long result;
  ADC0_PSSI_R = ADC_PSSI_SS3;               // initiate SS3
  while((ADC0_RIS_R&ADC_RIS_INR3)==0){};      // wait for conversion done
  result = ADC0_SSFIFO3_R&ADC_SSFIFO3_DATA_M;
  ADC0_ISC_R = ADC_ISC_IN3;                 // acknowledge completion of current conversion
  return result;
}

Of course I cannot vouch for this code but it purports to perform a single polled ADC conversion so you might compare it to your code.

Good Luck

thanks a lot for your help :slight_smile:

some hardware will set flags inside a peripheral register and those peripheral flags can also set interrupt flags but only if the interrupt flags are enabled. If the interrupt flags are NOT enabled, then polling on the interrupt flag will have no result.

what you mean for peripheral register? and what you mean for interrupt flag?
This is my interpretation:
peripheral register: is the address in which the peripheral is “mapped”, so peripheral writes stuff there and we get data from these address.
interrupt flags: it’s a specific bit in a peripheral register that is set by the peripheral so that we can understand if something happened in peripheral.

are these interpretation right? or I am misunderstanding?

by the way, we are not executing any ISR code, these interrupt flags are checked via POLLING method, not interrupt method. Is this right?

I tried to enable the interrupt handler (ISR) then, via “ADCIntRegister” which executes “IntRegister” inside. But if I do this: ADCIntRegister(ADC_BASE, 3, testHandlerAdc); where testHandlerAdc
is just a sample and basic code just to test if interrupt handling works, I get this from qemu:

qemu: fatal: Lockup: can't escalate 3 to HardFault (current priority -1)

R00=40038000 R01=00000003 R02=00000000 R03=00000001
R04=40038000 R05=00000000 R06=00000000 R07=00000000
R08=00000000 R09=00000000 R10=00000000 R11=00000000
R12=00002824 R13=20000330 R14=fffffff1 R15=200001c0
XPSR=00000003 ---- A handler
FPSCR: 00000000
Aborted (core dumped)

so something is wrong in registering the interrupt handler routine.

edit:
I guess it has something to do with this description of IntRegister method:

//! \note The use of this function (directly or indirectly via a peripheral

//! driver interrupt register function) moves the interrupt vector table from

//! flash to SRAM. Therefore, care must be taken when linking the application

//! to ensure that the SRAM vector table is located at the beginning of SRAM;

//! otherwise NVIC will not look in the correct portion of memory for the

//! vector table (it requires the vector table be on a 1 kB memory alignment).

//! Normally, the SRAM vector table is so placed via the use of linker scripts;

//! some tool chains, such as the evaluation version of RV-MDK, do not support

//! linker scripts and therefore will not produce a valid executable. See the

//! discussion of compile-time versus run-time interrupt handler registration

//! in the introduction to this chapter.

but I have not a clear understanding of what I should do.

A peripheral tends not to be A register, but a structure of various registers used to communicate with the device. You may need to study the hardware manual for the chip/device to see what needs to be setup to use it properly. Note, not setting up an ISR means you shouldn’t enable interrupts, nothing stops them just because you haven’t setup the vectors. For many devices there are several status registers. One will have a bit to say the operation is complete. and then there will be an interrupt enable that allows that bit to set an interrupt request bit. Often to test the interrupt request status bit, the interrupt needs to be enabled, or it never gets set, but then you need an ISR configured, and THAT can check the interrupt status bit. If code want to check for conversion complete without an ISR, it leaves the interrupt disabled, and then checks the conversion complete bit in the (ordinary) status register (not the interrupt status register). It sounds like you need to study the support library more to understand how you are supposed to be using it, or contacting their support system for help. This really doesn’t seem to be a FreeRTOS issue, at least not until you can make it work without FreeRTOS.

thanks for answer but… it seems like the device I’m trying to emulate (the ADC peripheral) doesn’t give any sign of life. it never put anything in FIFO queue. can it be possibile that qemu has some issue in emulating it? qemu docs say that qemu emulates adc in lm3s811 board. but looking at the FIFO queue, it NEVER adds anything, even though I do a “process triggered” conversion…

You might need to go into the details of the documents on how you give it information about what values to return. I suspect that somewhere you need to give it information, as it won’t know what signals exist that the ADC is converting. Without it knowing what value to give, it might not simulate correctly.

when you talk about “it”, what are you referring to? and the “details of the documents” what are you referring to?

The simulation of the ADC. The QEMU Emulator must have documentation that tells you how to use it.

I swear that I tried to search, but without any success. Can you be so kind to spend ONLY one minute to search (maybe you know keywords that I don’t know so I can’t find it but you can) ?

Look at it this way: If you were to emulate a USART, all the board can do for you is pretend that it can communicate over the USART, but there must obvioulsy be some preprovided way to fake data into/read out of the emulated USART - there is no way for your board to know what data goes over it.

Likewise, all the board can do is emulate an ADC, but the data to be converted must be provided by you via some kind of interface. All of this is minimum functionality of an emulation board.

So it is your task to figure out what and how to emulate. This must be documented somewhere in the board docu, otherwise the board would be useless.

Edit: Have you checked here? Chapter 5: ADC12 (youtube.com)

Looking at the QEMU site, I see them mentioning that they support the ADC, but on the “Device Emulation” page (Device Emulation — QEMU documentation) I see no mention of it. This is really a issue you need to take up with QEMU support, not here, as that is where the people who know the tool will be.