AnalogOut resets to 0 when printed

Hi,

I am trying to use my mbed (NUCLEO F767I) to monitor some sensor data in response to air pressure controlled by an analog signal. I am doing this with two tickers - one firing every second to step up the air pressure by increasing the AnalogOut value and the other firing every 20 microseconds to poll the sensors and read the analog output and add them to a queue. My main then loops a printf to send the next set of data to the PC and pop them out of the queue.

The problem I am having is that with more than one variable in the printf line, the AnalogOut value reaches a ceiling and then resets back to 0. This ceiling decreases as I make the output lines longer, so I can reach 3.1V output with two values being sent, but no more than 0.25 with 4 variables being read out (time, pressure and data from two sensors). To make this even more confusing, the being sent to the DAC behaves correctly if I change its declaration from AnalogOut to a simple float.

My code (such as it is) is below:

#include <mbed.h>
#include <mbed_events.h>
#include <queue>

void readADC();
double voltage(AnalogIn &adc);
double acceleration(AnalogIn &adc);
void incrementPressure();

Ticker readVolt1;
Ticker setPressure;
Timer clocky;
AnalogIn z1(PA_3);
AnalogIn z2(PC_0);
AnalogIn z3(PC_3);
AnalogIn z4(PF_3);
AnalogIn z5(PF_5);
AnalogIn z6(PF_10);

AnalogOut pressure(PA_5);

std::queue<int> timestamps;
std::queue<double> z1data;
std::queue<double> z2data;
std::queue<double> z3data;
std::queue<double> z4data;
std::queue<double> pressures;

Serial pc(USBTX, USBRX);

DigitalOut myled(LED1);

int main() {
  //Set up serial connection
  pc.baud(921600);
  pc.printf("Accelorometer values follow:\n\n");

  //Set up tickers and the clock
  readVolt1.attach(&readADC, 0.0002);
  setPressure.attach(&incrementPressure, 1);
  clocky.start();

  //Start pressure at 0
  pressure = 0.0f;

  while(1) {
    
    //If there is data queued, send it to the PC and clear it out of the queue
    if(!timestamps.empty()){
      pc.printf("%i, %.3f, %.6f, %.6f, %.6f, %.6f\n", timestamps.front(), pressures.front(), z1data.front(), z2data.front(), z3data.front(), z4data.front());
      z1data.pop();
      z2data.pop();
      z3data.pop();
      z4data.pop();
      pressures.pop();
      timestamps.pop();
    }
    
    //Don't run forever
    if (clocky.read() > 120){
      readVolt1.detach();
      setPressure.detach();
      pc.printf("Program ended");
    }
  }
}

//Read data from sensors and timestamp it
void readADC(){
  int readStartTime = clocky.read_us();
  z1data.push(voltage(z1));
  z2data.push(voltage(z2));
  z3data.push(voltage(z3));
  z4data.push(voltage(z4));
  //pressures.push(pressure.read());
  pressures.push(pressure.read()*3.3*(-10));
  int readEndTime = clocky.read_us();
  int measurementTime = readStartTime + (( readEndTime - readStartTime ) / 2 );
  timestamps.push(measurementTime);
}

//Helper function to get voltage from ADC
double voltage(AnalogIn &adc){
  return (adc.read() * 3.0);
}

//Helper function to get acceleration from ADC (not finished)
double acceleration(AnalogIn &adc){
  return (voltage(adc) * (10/3));
}

//increase signal to pressure regulator
void incrementPressure(){
  pc.printf("pressure change\n");
  if (pressure.read() <= 0.95){
    pressure = (pressure + 0.02);
  }
  else{
    pressure = (pressure - 0.02);
  }
  return;
}

Is there any reason printf() would be interfering with AnalogOut in this way?

Hi Joshua,

I’ve duplicated your code (without the ADC), and it seems that there is an issue with C++ standard queue combined with Ticker class. The issue is that callbacks registered to Ticker are executed in interrupt context. The C++ standard queue does not work with interrupt context because of the dynamic memory allocation required to store the items. Depending on the Mbed OS version, you might get away with it until the heap corrupts, or you get an error immediately with newer versions of Mbed OS.

One solution could be to not use std::queue for data transport. For example, there is Mbed OS Queue that can be used within interrupt context. The challenge with Mbed OS Queue is that you can only store pointers to the queue with it and the memory needs to be allocated elsewhere.

The memory allocation can be solved with Mbed OS MemoryPool. An example how to use Mbed OS Queue and Mbed OS MemoryPool can be found from here. These two are already combined for the generic case in Mbed OS Mail class which I think can be used in your case.

I’ve modified your example a bit to show how this would work in your case. Also instead of using multiple different queues, I moved the sampled data to inside a struct. This reduces the memory footprint and also makes sure that queues don’t get out of sync.

#include <mbed.h>
#include <mbed_events.h>
#include <queue>

void readADC();
double voltage(AnalogIn &adc);
double acceleration(AnalogIn &adc);
void incrementPressure();

Ticker readVolt1;
Ticker setPressure;
Timer clocky;
AnalogIn z1(PA_3);
AnalogIn z2(PC_0);
AnalogIn z3(PC_3);
AnalogIn z4(PF_3);
AnalogIn z5(PF_5);
AnalogIn z6(PF_10);

AnalogOut pressure(PA_5);

typedef struct {
  double z1;
  double z2;
  double z3;
  double z4;
  double z5;
  double z6;
  double pressure;
  int timestamp;
} adc_sample_t;

rtos::Mail<adc_sample_t, 1> adc_mail_box;

Serial pc(USBTX, USBRX);

DigitalOut myled(LED1);

int main() {
  //Set up serial connection
  pc.baud(921600);
  pc.printf("Accelorometer values follow:\n\n");

  //Set up tickers and the clock
  readVolt1.attach(&readADC, 0.0002);
  setPressure.attach(&incrementPressure, 1);
  clocky.start();

  //Start pressure at 0
  pressure = 0.0f;

  while(1) {
    
    // Queue.get() will block until data is available.
    osEvent evt = adc_mail_box.get();
    if (evt.status == osEventMail) {
      adc_sample_t* adc_sample = (adc_sample_t*)evt.value.p;

      printf("%i, %.3f, %.6f, %.6f, %.6f, %.6f\n", \
        adc_sample->timestamp, \
        adc_sample->pressure, \
        adc_sample->z1, \
        adc_sample->z2, \
        adc_sample->z3, \
        adc_sample->z4
      );

      adc_mail_box.free(adc_sample);
    } else {
      // Something went wrong with queue.
    }
    
    //Don't run forever
    if (clocky.read() > 120){
      readVolt1.detach();
      setPressure.detach();
      pc.printf("Program ended");
    }
  }
}

//Read data from sensors and timestamp it
void readADC(){
  int readStartTime = clocky.read_us();

  // Allocate memory from static buffer
  // NOTE: remember to free at main()
  adc_sample_t* adc_sample = adc_mail_box.alloc();
  if (adc_sample == NULL) {
    // main thread couldn't process the data fast enough
    // maybe a larger queue or some other way of handling it?
    return;
  }

  // Populate measurement result
  adc_sample->z1 = voltage(z1);
  adc_sample->z2 = voltage(z2);
  adc_sample->z3 = voltage(z3);
  adc_sample->z4 = voltage(z4);
  adc_sample->pressure = pressure.read()*3.3*(-10);

  int readEndTime = clocky.read_us();
  int measurementTime = readStartTime + (( readEndTime - readStartTime ) / 2 );
  adc_sample->timestamp = measurementTime;

  // Put sample data to mail queue
  adc_mail_box.put(adc_sample);
}

//Helper function to get voltage from ADC
double voltage(AnalogIn &adc){
  return (adc.read() * 3.0);
}

//Helper function to get acceleration from ADC (not finished)
double acceleration(AnalogIn &adc){
  return (voltage(adc) * (10/3));
}

//increase signal to pressure regulator
void incrementPressure(){
  pc.printf("pressure change\n");
  if (pressure.read() <= 0.95){
    pressure = (pressure + 0.02);
  }
  else{
    pressure = (pressure - 0.02);
  }
  return;
}

NOTE: I also changed the ADC sampling time to 100ms. This is because the sampling duration must be slower than the processing duration. Currently the printing through serial port is most likely the slowest operation. If the processing duration is longer than the sampling duration, the queue cannot be emptied fast enough. This will then cause the Ticker callback to hang, as there is no space left in the MessageQueue.

Best regards,
Jaakko, team Mbed