Read pressure sensor with oversampling

Hi guys, I am new to this stuff but i am very interested and i have jumped into a project where i want to read a pressure sensor, which i will utilize a calibration chart to scale the analog values , 0-5v, to a force value between 0 and 2500 pounds (i know, that’s a lot of force).

I would like to first make this happen with just a simple POT setup on one analog pin, because i am just in the beginning stages of building the rig that will have a bottle jack and i have not sourced the exact sensor i will use yet to monitor the hydraulic fluid pressure in the bottle jack.

I have read and have seen some sample code… but i have a hard time understanding that code and how to make it work for me. I want to be able to read and display in the serial monitor values in one pound increments from 0-2500. I think i need to have either a 13 bit or more ADC or do the oversampling (i would prefer this) to get to the resolution i need. For this application, i don’t expect there to be a lot of fluctuations in the sensor readings except while i am operating and applying more pressure to the bottle jack. I know the signal will need a certain amount of noise to function properly, so i am going to assume that i will have enough noise in the system for this to work well. If i don’t, i can always add in the noise.

Here is the code i saw someone else do but i cant get it to work with my extremely limited programming knowledge. For now i just want to get this working with a voltage divider POT circuit that will mimic the pressure sensor. I know there is a whole other thing to learn to get all this to display to an LED display later but i just want to get the data part working for now. Any help you guys can give would be greatly appreciated!

/*

#define BUFFER_SIZE 16 // For 12 Bit ADC data

volatile uint32_t result[BUFFER_SIZE];
volatile int i = 0;
volatile uint32_t sum=0;

ADC and Timer1 setup function
  Argument 1: uint8_t channel must be between 0 and 7
  Argument 2: int frequency must be integer from 1 to 600
    WARNING! Any value above 600 is likely to result in a loss
    of data and could result in a reduced accuracy of ADC
    conversions
*/
void setupADC(uint8_t channel, int frequency)
{
  cli();
  ADMUX = channel | _BV(REFS0);
  ADCSRA |= _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0) | _BV(ADATE) | _BV(ADIE);
  ADCSRB |= _BV(ADTS2) | _BV(ADTS0);  //Compare Match B on counter 1
  TCCR1A = _BV(COM1B1);
  TCCR1B = _BV(CS11)| _BV(CS10) | _BV(WGM12);
  uint32_t clock = 250000;
  uint16_t counts = clock/(BUFFER_SIZE*frequency);
  OCR1B = counts;
  
  TIMSK1 = _BV(OCIE1B);
  ADCSRA |= _BV(ADEN) | _BV(ADSC);
  sei();
}

ISR(ADC_vect)
{
  result[i] = ADC;
  i=++i&(BUFFER_SIZE-1);
  for(int j=0;j<BUFFER_SIZE;j++)
  {
    sum+=result[j];
  }
  if(i==0)
  {
   /****DEAL WITH DATA HERE*****/
    sum = sum>>2;
    Serial.println(sum,DEC);
  }
  sum=0;
  TCNT1=0;
}
ISR(TIMER1_COMPB_vect)
{
}

"Can't get it to work" an not quite enough information to help here. What do you expect? What do you get? Errors? And why in the world would you add noise to the measurement?

Ok. Thanks for the reply. I am going to show my ignorance here:

#define BUFFER_SIZE 16 // For 12 Bit ADC data

volatile uint32_t result[BUFFER_SIZE];
volatile int i = 0;
volatile uint32_t sum=0;
/*
ADC and Timer1 setup function
  Argument 1: uint8_t channel must be between 0 and 7
  Argument 2: int frequency must be integer from 1 to 600
    WARNING! Any value above 600 is likely to result in a loss
    of data and could result in a reduced accuracy of ADC
    conversions
*/
void setupADC(uint8_t channel, int frequency)
{
  cli();
  ADMUX = channel | _BV(REFS0);
  ADCSRA |= _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0) | _BV(ADATE) | _BV(ADIE);
  ADCSRB |= _BV(ADTS2) | _BV(ADTS0);  //Compare Match B on counter 1
  TCCR1A = _BV(COM1B1);
  TCCR1B = _BV(CS11)| _BV(CS10) | _BV(WGM12);
  uint32_t clock = 250000;
  uint16_t counts = clock/(BUFFER_SIZE*frequency);
  OCR1B = counts;
  
  TIMSK1 = _BV(OCIE1B);
  ADCSRA |= _BV(ADEN) | _BV(ADSC);
  sei();
}

ISR(ADC_vect)
{
  result[i] = ADC;
  i=++i&(BUFFER_SIZE-1);
  for(int j=0;j<BUFFER_SIZE;j++)
  {
    sum+=result[j];
  }
  if(i==0)
  {
    sum = sum>>2;
    Serial.println(sum,DEC);
    delay(500);
  }
  sum=0;
  TCNT1=0;
}
ISR(TIMER1_COMPB_vect)
{
}
void setup()
{
  Serial.begin(9600);
}
void loop()
{
 uint8_t channel = 0;
 int frequency = 40;
 int k;
 k = setupADC(channel,frequency);
}

I am just trying to get a reading to display in the serial monitor, for now using the exact code that another user supplied… reading the value from input pin 0 on an arduino Uno, and displaying 12 bit range of values.

With this exact code that is the same as what one other user posted with the addition of a setup() and loop() functions, i get the following error: sketch_oct26c.ino: In function ‘void loop()’:
sketch_oct26c:63: error: void value not ignored as it ought to be

Forget that code it is way over the top for what you need. Look at the examples in the arduino IDE about analogue input. They are in the file -> examples menu.

i need to read 2500 increments in a 4v span... i need to have 12 bits of resolution i think.

i got it working, displaying values from 0-4095 but it outputs a zero every few lines. How do i get rid of the 0?

You saying it’s working, but didn’t provide what changes you made. Have you modified it like that:

void setup()
{
  Serial.begin(9600);
  uint8_t channel = 0;
  int frequency = 40;
  setupADC(channel,frequency);
}
void loop()
{
}

Your ADC interrupt subroutine too fat:

ISR(ADC_vect)
{
  result[i] = ADC;
  i=++i&(BUFFER_SIZE-1);
  for(int j=0;j<BUFFER_SIZE;j++)
  {
    sum+=result[j];
  }
  if(i==0)
  {
    sum = sum>>2;
    Serial.println(sum,DEC);
    delay(500);
  }
  sum=0;
  TCNT1=0;
}

Remove Serial.println out of the function, it takes too mach times to print, and probably you are skipping some samples.

Sorry about that… I did what you said and it seems to work perfectly now. I didn’t realize i could have nothing in the void loop!

Here is the code that works:

#define BUFFER_SIZE 16 // For 12 Bit ADC data

volatile uint32_t result[BUFFER_SIZE];
volatile int i = 0;
volatile uint32_t sum=0;
/*
ADC and Timer1 setup function
  Argument 1: uint8_t channel must be between 0 and 7
  Argument 2: int frequency must be integer from 1 to 600
    WARNING! Any value above 600 is likely to result in a loss
    of data and could result in a reduced accuracy of ADC
    conversions
*/
void setupADC(uint8_t channel, int frequency)
{
  cli();
  ADMUX = channel | _BV(REFS0);
  ADCSRA |= _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0) | _BV(ADATE) | _BV(ADIE);
  ADCSRB |= _BV(ADTS2) | _BV(ADTS0);  //Compare Match B on counter 1
  TCCR1A = _BV(COM1B1);
  TCCR1B = _BV(CS11)| _BV(CS10) | _BV(WGM12);
  uint32_t clock = 250000;
  uint16_t counts = clock/(BUFFER_SIZE*frequency);
  OCR1B = counts;
  
  TIMSK1 = _BV(OCIE1B);
  ADCSRA |= _BV(ADEN) | _BV(ADSC);
  sei();
}

ISR(ADC_vect)
{
  result[i] = ADC;
  i=++i&(BUFFER_SIZE-1);
  for(int j=0;j<BUFFER_SIZE;j++)
  {
    sum+=result[j];
  }
  if(i==0)
  {
    sum = sum>>2;
    Serial.println(sum,DEC);
    delay(500);
  }
  sum=0;
  TCNT1=0;
}
ISR(TIMER1_COMPB_vect)
{
}
void setup()
{
  Serial.begin(9600);
  uint8_t channel = 0;
  int frequency = 40;
  setupADC(channel,frequency);
}
void loop()
{
}

Magician, what did you mean by the ADC subroutine being too fat?

thanks!

moparx12: i need to read 2500 increments in a 4v span... i need to have 12 bits of resolution i think.

i got it working, displaying values from 0-4095 but it outputs a zero every few lines. How do i get rid of the 0?

Well you are not going to get "2500 increments" from an Arduino A/d converter. And do you really need to read them ? You really need a reading for every 1 pound-force of additional pressure ? I don't think you really "need" that. Your "noise" level is going to be more than that, anyway. And you would never be able to calibrate it properly to that level of actual accuracy.

You can get external A/D converter chips which have 12 or more bits of resolution.

I may not really need that accuracy, but I will be displaying 4 digits on an led display (0-2500) and i don't want the display to jump from 1800 to 1804 to 1808 for example. I want it to jump from 1800 to 1801 etc. I don't think that accuracy is asking too much. but maybe you are right in that this method is not really giving me the accuracy i am looking for. I don't know enough about what the code is doing related to the actual input voltage to determine that.

Atmel has some literature on this oversampling thing, i just dont know if i have the right amount of noise to make this work without adding "dithering". I guess the next step will be to observe the amount of noise to be sure i am toggling the LSB.

http://www.atmel.com/images/doc8003.pdf

"Normally a signal contains some noise, this noise very often has the characteristic of Gaussian noise, more commonly known as White noise or Thermal noise, recognized by the wide frequency spectrum and that the total energy is equally divided over the entire frequency range. In these cases the method of ‘Oversampling and decimation’ will work, if the amplitude of the noise is sufficient to toggle the LSB of the ADC conversion. In other cases it might be necessary to add artificial noise signal to the input signal, this method is referred to as Dithering. The waveform of this noise should be Gaussian noise, but a periodical waveform will also work. What frequency this noise signal should have depends on the sampling frequency. A rule of thumb is: ”When adding m samples, the noise signals period should not exceed the period of m samples”. The amplitude of the noise should be at least 1 LSB. When adding artificial noise to a signal, it is important to remember that noise has mean value of zero; insufficient oversampling therefore may cause an offset, as shown in Figure 3-3."

Could you post a link to the data sheet for the pressure sensor? Unless it is VERY expensive, you are probably asking way too much of the sensor. If the precision or accuracy of the measurement is, for example, 1% of full scale (rather typical) then there is no meaningful difference between pressure readings of 1800 and 1810.

For example this digital gauge, a great bargain at only US$ 761.20 plus shipping, has 0.5% full scale accuracy, which translates to meaningful step sizes in the displayed reading of about 20 PSI. http://www.aisat.com.au/d654-129-FFE415/digital-press-gauge-dc400-0-250-bar-ss/

Magician, what did you mean by the ADC subroutine being too fat?

I mean you should only include a code, that absolutely necessary inside the ISR(ADC_vect) and such function. Everything else, like summing up an array, or even worse, serial printing you better to do in the main loop. You may use a “flags” variable, to indicate that some process has completed, for example filling up an array, than using “if” statement and flag you take some other action inside main loop.
If reading of the sensor jumping, you may try to implement low pass filtering, which is basically same as averaging, and narowing bandwidth always work in the way of noise reduction and increasing a resolution.
Try this code: http://coolarduino.wordpress.com/2013/01/09/audio-vu-meter/
Even it’s written for audio, LPF should work , averaging close to 1 : 256

If you really want to resolve the value to 1lb in 2500, don't rely on oversampling. The ADC in the Arduino very likely won't be sufficiently linear to give you good results even if you add the correct amount of noise to the signal. Use an external 12-bit (or better) ADC instead.

i don't want the display to jump from 1800 to 1804 to 1808 for example. I want it to jump from 1800 to 1801 etc. I don't think that accuracy is asking too much.

Well you may think it isn't "asking too much", but I am going to suggest that it is. The differences between the concepts of accuracy, precision and resolution elude many people.

Thank you all for the advice and comments. I have decided to purchase an external 12 bit ADC for this project (on order) and i will most likely be using this sensor:

http://www.meas-spec.com/downloads/M7100.pdf

Which to michinyon's point does seem to have a better than common accuracy at 0.25%. Remember i am talking about a bottle jack, so i dont expect a lot of noise in the signal (assuming no hydraulic leaks). Also, i understand that i need to take the accuracy into consideration, but honestly i am more interested in having a number that steps in increments of 1 lb than having accuracy dead nuts.

In other words, the art outweighs the science, which is certainly a valid consideration! But it does remind me of the inexpensive digital thermometers that indicate room temperature to tenths of a degree, even though the actual room temperature could be +/- 2 degrees different.

if the 12 bit ADC does not meet the expectations, this is a nice 16 bit ADC - http://www.adafruit.com/products/1085 -

you can even loose a few bits for noise :)

Thank you all for your inputs, I am still trying to move forward with this project and i am kind of stuck once again with code.

I purchased a single channel SPI interface ADC, MCP3201, and i am trying to find code that i can use to implement it in reading a voltage divider.

There is an example for similar ADC chips here on this site but i need someone to hold my hand all the way through understanding how to slightly modify the code for a single channel ADC like the one i am using…

here is the code provided on this site:

#define SELPIN 10 //Selection Pin 
#define DATAOUT 11//MOSI 
#define DATAIN  12//MISO 
#define SPICLOCK  13//Clock 
int readvalue; 

void setup(){ 
 //set pin modes 
 pinMode(SELPIN, OUTPUT); 
 pinMode(DATAOUT, OUTPUT); 
 pinMode(DATAIN, INPUT); 
 pinMode(SPICLOCK, OUTPUT); 
 //disable device to start with 
 digitalWrite(SELPIN,HIGH); 
 digitalWrite(DATAOUT,LOW); 
 digitalWrite(SPICLOCK,LOW); 

 Serial.begin(9600); 
} 

int read_adc(int channel){
  int adcvalue = 0;
  byte commandbits = B11000000; //command bits - start, mode, chn (3), dont care (3)

  //allow channel selection
  commandbits|=((channel-1)<<3);

  digitalWrite(SELPIN,LOW); //Select adc
  // setup bits to be written
  for (int i=7; i>=3; i--){
    digitalWrite(DATAOUT,commandbits&1<<i);
    //cycle clock
    digitalWrite(SPICLOCK,HIGH);
    digitalWrite(SPICLOCK,LOW);    
  }

  digitalWrite(SPICLOCK,HIGH);    //ignores 2 null bits
  digitalWrite(SPICLOCK,LOW);
  digitalWrite(SPICLOCK,HIGH);  
  digitalWrite(SPICLOCK,LOW);

  //read bits from adc
  for (int i=11; i>=0; i--){
    adcvalue+=digitalRead(DATAIN)<<i;
    //cycle clock
    digitalWrite(SPICLOCK,HIGH);
    digitalWrite(SPICLOCK,LOW);
  }
  digitalWrite(SELPIN, HIGH); //turn off device
  return adcvalue;
}


void loop() { 
 readvalue = read_adc(1); 
 Serial.println(readvalue,DEC); 
 readvalue = read_adc(2); 
 Serial.println(readvalue,DEC); 
 Serial.println(" "); 
 delay(250); 
}

The part i dont understand what it is doing and therefore how i need to modify it is here:

int read_adc(int channel){
  int adcvalue = 0;
  byte commandbits = B11000000; //command bits - start, mode, chn (3), dont care (3)

  //allow channel selection
  commandbits|=((channel-1)<<3);

  digitalWrite(SELPIN,LOW); //Select adc
  // setup bits to be written
  for (int i=7; i>=3; i--){
    digitalWrite(DATAOUT,commandbits&1<<i);
    //cycle clock
    digitalWrite(SPICLOCK,HIGH);
    digitalWrite(SPICLOCK,LOW);    
  }

thanks again

If you are going to connect it to pins 10-13 then you may as well use the SPI library. Use SPI mode 0, MSB first. Transfer 2 bytes to/from the device - it doesn't matter what you send because the MCP3201 has no data input pin. Assemble the return bytes into a 2-byte integer, and mask it to 12 bits.