0

I want to get meteo sensors values, and turn avr based 16mhz arduino (i have mega, but that seems not matter alot vs uno) into multichannel oscilloscope.

Task details. Desired data rate is 8(uno) to 16(mega) analog channels divide by 4 to 16 reads per second. Extra adc "speed capacity" must be spent on summing up on arduino. Prescale is almost not needed. Each reading is 32bit, lsb is always zeroed. Plus there is 0xffffffff between packets (easiest binary proto, i really hate those ascii low-quality slow shit, but this is personal and offtopic). So now you got the setup and purpose, lest get down to my simple sketch

// defines for setting and clearing register bits
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif


// packet starts after indicator and goes before next such indicator.
const long packetStartIndicator = 0xFFFFFFFFl;

// values are fixed point ints 0..65534 (with LSB always cleared)
// such descision is made because least significant bit almast always == noise;
// also its just easist pck splitting scheme
const long packetValueMask = 0xFFFFFFFEl;

const int numPins = 8;

long sensorValues[numPins+1];

// TODO: configure this from host via serial (inside setup)
int pinNumbers[] = {A0, A1, A2, A3, A4, A5, A6, A7, A8};
//int pinNumbers[] = {A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15};


// 1 reading is 10 bit. we can make 2^6==64 readings and sum them up (to top up to full 16 bit, to make kind of digital expoure)
// upd. 1 reading is counted as 16 bit, for host convenience (no matter or real adc)
// sum up like 256 of 16-bit readings to get 24bit value. send as 32bit long variable.
const int adcOrder = 10;
const int readingsOrder = 8;
const int targetOrder = 24;
const int numReadings = 1 << readingsOrder; //64;


void setup() {
  // TODO: reset ADCSRA, or it would not work. Also: get clue on free run mode
// sampling rate is [ADC clock] / [prescaler] / [conversion clock cycles]
// for Arduino Uno ADC clock is 16 MHz and a conversion takes 13 clock cycles
//ADCSRA |= (1 << ADPS2) | (1 << ADPS0);    // 32 prescaler for 38.5 KHz
//ADCSRA |= (1 << ADPS2);                     // 16 prescaler for 76.9 KHz
//ADCSRA |= (1 << ADPS1) | (1 << ADPS0);    // 8 prescaler for 153.8 KHz


//sbi(ADCSRA,ADPS2); cbi(ADCSRA,ADPS1); sbi(ADCSRA,ADPS0); // set prescale to 32 for 38.5 KHz
//sbi(ADCSRA,ADPS2); cbi(ADCSRA,ADPS1); cbi(ADCSRA,ADPS0); // set prescale to 16 for 76.9 KHz
//cbi(ADCSRA,ADPS2); sbi(ADCSRA,ADPS1); sbi(ADCSRA,ADPS0); // set prescale to 8 for 153.8 KHz


  sensorValues[numPins] = packetStartIndicator;
  //analogReadResolution(10);
  Serial.begin(115200, SERIAL_8N1);
  //Serial.begin(250000, SERIAL_8N1);
  //Serial.begin(500000, SERIAL_8N1); 
}


void zero_values() {
  int i;
  for(i=0; i<numPins; i++) {
    sensorValues[i] = 0;
  }
}

void clear_lsb() {
  int i;
  int shiftOrder = targetOrder - (adcOrder + readingsOrder);
  for(i=0; i<numPins; i++) {
    sensorValues[i] <<= shiftOrder;
    sensorValues[i] &= packetValueMask;
  }
}

void serial_write() {
  Serial.write( (uint8_t*)sensorValues, sizeof(long)*(numPins+1) );
}

void loop() {
  int i, j; 
  zero_values();
  for(i=0; i<numPins; i++) {
    for (j=0;j<numReadings; j++) {
      sensorValues[i] += analogRead(pinNumbers[i]);
    }
  }
  clear_lsb();
  serial_write();
}

I am weak with reading avr datasheets and lacking of knowledge on internals. My question is tied with my desire to switch it to freerun mode. I really dislike analogRead, seems it was written by real juniors, and is real slow. AFAIK free run mode is something when you have interrupt function running when conversion is done. I feel (dont know but really feel) that working with serial inside interrupt is a bad idea. How, in somple words, this can be reworked to event-based ISR with sending over serial when you would encounter inside your interrupt following: currentPin == lastPin && currentReading == numReading means you have summed up desired bumber of readings, and this is last pin, so its time to post it over serial.

  • Can you elucidate for us which parts of analogRead you feel are "junior". – Delta_G Apr 22 at 18:26
1

In the Arduino environment sending data over serial inside an interrupt is seldom a good idea. That's not to say it's impossible, you just need to keep certain caveats in mind.

The chief of which is that when the TX buffer fills up everything will lock up.

So with that in mind, if you can guarantee that all your data from one interrupt will be completely sent out before the next interrupt occurs then sending serial in an interrupt can be used.

And yes, analogRead(), like many things in the Arduino API, is not fast. It's not intended to be fast. It is, though, intended to be simple. And also like many things in the Arduino API it's easy to out-grow them.

I am not familiar with the Atmel ADC registers (preferring PIC32 to AVR any day...) but looking at the datasheet I can see a few points of interest.

  • First set the trigger source to "free running" (ADCSRB):
  • Bit 2:0 – ADTS2:0: ADC Auto Trigger Source

If ADATE in ADCSRA is written to one, the value of these bits selects which source will trigger an ADC conversion. If ADATE is cleared, the ADTS2:0 settings will have no effect. A conversion will be triggered by the rising edge of the selected Interrupt Flag. Note that switching from a trigger source that is cleared to a trigger source that is set, will generate a positive edge on the trigger signal. If ADEN in ADCSRA is set, this will start a conversion. Switching to Free Running mode (ADTS[2:0]=0) will not cause a trigger event, even if the ADC Interrupt Flag is set.

Then set it to use the trigger source for starting conversions (ADCSRA):

  • Bit 5 – ADATE: ADC Auto Trigger Enable

When this bit is written to one, Auto Triggering of the ADC is enabled. The ADC will start a conversion on a positive edge of the selected trigger signal. The trigger source is selected by setting the ADC Trigger Select bits, ADTS in ADCSRB.

And enable interrupts (ADCSRA):

  • Bit 3 – ADIE: ADC Interrupt Enable

When this bit is written to one and the I-bit in SREG is set, the ADC Conversion Complete Interrupt is activated

Alternatively you could set it to use a timer for the trigger source so you have finer control over when a conversion is performed. That will help you to ensure that all of the previous data set has been sent over serial before the next interrupt occurs.

| improve this answer | |
  • "if you can guarantee that all your data from one interrupt will be completely sent out before the next interrupt occurs" I can just gurantee that for 11500 baud/s == 359.375 longs/second. I can send each long when it's adc is fired, and i need to swotch to another send accumulated A0->send A1->..->send A7->send 0xffffffff->send A0 So ITS BEST and most lightweight, if i could send inside interrupt. – xakepp35 Apr 22 at 18:47
  • So i can fire 39.93 of those (8+1)xlong readings per seconds. I just need 4 to 16 times per second. So it's guranteed that serial bus is free 30% to 90% of time to send whatever I want. I cant gurantee implementation, and I dont know it, and.. (I dont want to know it, i want just to use it efficiently) So I asked how to fire something from inside ISR without breaking arduino to hell.. – xakepp35 Apr 22 at 19:06
  • The serial code is pretty efficient. There's a TX circular buffer and the sending from that buffer is all interrupt driven. write() blocks if there is no room in the buffer, and if that happens in an interrupt then you're screwed. As long as you make sure that can't happen (less than 64 bytes written between each ADC interrupt that writes data to serial) then you'll be fine. – Majenko Apr 22 at 19:13
  • So ARE you making me sure that writing send.serial.tx from some isr is just straightforward and fine enough? (if i gurantee that data send rate is low enoughh? (to wat degree should it be fine? 0.9 bandwith == fine? 0.3 == fine? or wat? how fast must i send to be fine and not dig into datashit? i dont want to, i just know that its right away and i go with it!)) – xakepp35 Apr 22 at 20:34
  • If it will work i'll accept. Here is just prattical impl question to theoretical knowers, so to what degree is it guranteed to send serial from isr, or should i make some timer, sync and another loop shitcode? (felt like not. i send serial from interrupt, no need for any other kind of loop, pretty straight tast!) Also if this is a loopless ADC solution, could you please post to arduino beginners, what should they post in their main? sleep? why? could you post some code plxz!? – xakepp35 Apr 22 at 20:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.