文章目录



https://www.arduino.cn/thread-12569-1-1.html


这部分内容原先是回答某位网友的, 重新整理方便大家查看!


根据官网说明, analogRead( ) 大约要 100us:
http://arduino.cc/en/Reference/analogRead 也就是说, 一秒最多只能读取大约一万次(10K), 更正确的说, 理论上 sampling rate 是 9600 Hz, 接近 10KHz, 但这是因为 Arduino 的 ADC 之 Prescaler 被设为 128;

所以, 假设 Arduino 的频率(Clock)是 16MHz, 则 ADC clock = 16MHz / 128 = 125KHz;
一次 ADC 转换要踢它13下, 就是要 13 clock (tick), 于是变成 125KHz/13 = 9600Hz

请注意, 这是因为在 analogRead( ) 内必须等 ADC 取样完成才会返回,
关于 analogRead( ) 的源代码与解说可以参考这:
http://garretlab.web.fc2.com/en/arduino/inside/arduino/wiring_analog.c/analogRead.html

不过 9600Hz 的sampling rate 只是理论值, 因为你的程序本身也要花时间, 调用函数进出需要时间, for Loop 本身也要时间, 把 sample(采样) 到的值加总到 total 也要时间 !

所以, 如果你用一个 for Loop 全力一直做 analogRead(A0),
那一秒钟也只能读取大约 8925 次, 达不到理论值 9600次 !!
不相信, 用你的 Arduino 测试一下:

//
// test A0 sampling rate -- by  tsaiwn@cs.nctu.edu.tw
const int pin = A0;
const int n = 1000;  // sample 采样 1000 次
void setup() {
  Serial.begin(9600);
  for(int i=0; i< 543; i++) analogRead(pin); // 热身 :-)
  Serial.println(String("Sample ") + n + " times, pin=" + pin);
  Serial.flush( );
  delay(568);
}
void loop( ) { // 
  unsigned long begt, runt, total;
  total = 0;  // clear before sampling
  begt = micros();
  for(int i=0; i< n; i++) {
     total += analogRead(pin);
  }
  runt = micros() - begt;  // elapsed time
  Serial.println(String("Average=") + total/n);
  Serial.print(String("Time per sample: ")+runt/1.0/n +"us");
  Serial.println(String(", Frequency: ")+1000000.0/runt*n +" Hz");
  delay(5566);
}// loop(

你看, 以上程序已经简短到无法再简短了,
但是经过实际测试, 结果 sampling rate 只有不到 9KHz,(我测试结果大约 8925 次)
这是因为除了 analogRead( )等待 ADC 转换的延迟外, 程序中 for Loop 与 tatal += 也都要一些时间 !
有兴趣的可以看看 analogRead( ) 这函数的源代码:

int analogRead(uint8_t pin) {
  extern unsigned char analog_reference;
  uint8_t low, high;
  const unsigned char bitADSC = (1 << ADSC);
  if (pin >= 14) pin -= 14; // allow for channel or pin numbers
  // set the analog reference (high two bits of ADMUX) and select the
  // channel (low 4 bits).  this also sets ADLAR (left-adjust result)
  // to 0 (the default).
  ADMUX = (analog_reference << 6) | (pin & 0x07);
  // start the conversion
  ADCSRA |= (1 << ADSC);  //    sbi(ADCSRA, ADSC);
  // ADSC is cleared when the conversion finishes
  while (bit_is_set(ADCSRA, ADSC));  // while(ADCSRA & bitADSC) ;
  // we have to read ADCL first; doing so locks both ADCL
  // and ADCH until ADCH is read.  reading ADCL second would
  // cause the results of each conversion to be discarded,
  // as ADCL and ADCH would be locked when it completed.
  low  = ADCL;
  high = ADCH;
  // combine the two bytes
  return (high << 8) | low;
}

看到了吧, analogRead( ) 里面有个如下的 while Loop:
while (bit_is_set(ADCSRA, ADSC));
这句意思是要一直等到 ADC 转换完成把 ADSC 这 bit 清除为 0;
然后才可以读取 ADCL 与 ADCH, 合成总共 10 bit, 代表 0 ~ 1023 的取样值 !

刚刚说过, 理论值 9.6KHz 是因为 ADC 的 Prescaler设为128;
不过, Prescaler是可以改的!
只要把 ADC 的 Prescaler 改小, 就可以加快 ADC 转换速度与 analogRead( )速度!

例如, 把 ADC 的 Prescaler 改为 16,
则理论的 Sample Rate 可达 16MHz / 16 / 13 = 76.8KHz
不过, 经过实测只能达到大约 58KHz
若 ADC 的 Prescaler 改为 8,
则理论的 Sample Rate 可达 153KHz, 但实测只有大约93.5KHz
可是一般不建议把 Prescaler 设在 16以下, 否则ADC转换不太准 !!

以下是测试用 Prescaler 16, 采样频率 大约 58KHz;
但在以下程序中,
我已经帮忙写了数个可以把 ADC 的 Prescaler 设为各种值的函数:

//
// speed up sampling rate -- by tsaiwn@cs.nctu.edu.tw
const int pin = A0;
const int n = 1000;  // sample 采样 1000 次
void setup() {
  Serial.begin(9600);
  //setP64( ); // Prescaler = 64
  //setP32( ); // Prescaler = 32
  //setP8( );   // Prescaler = 8
  setP16( );   // Prescaler = 16
  //setP128( ); // Prescaler = 128 = default
  for(int i=0; i< 543; i++) analogRead(A0); // 热身 :-)
  Serial.println(String("Sample ") + n + " times, pin=" + pin);
  Serial.flush( );
  delay(568);
}
void loop( ) { // 
  long begt, runt, total;
  total = 0;  // clear before sampling
  begt = micros();
  for(int i=0; i< n; i++) {
     total += analogRead(pin);
  }
  runt = micros() - begt;  // elapsed time
  Serial.println(String("Average=") + total/n);
  Serial.print(String("Time per sample: ")+runt/1.0/n +"us");
  Serial.println(String(", Frequency: ")+1000000.0/runt*n +" Hz");
  delay(5566);
}// loop(
void setP16( ) {
  Serial.println("ADC Prescaler = 16");  // 100
  ADCSRA |=  (1 << ADPS2);   // 1
  ADCSRA &=  ~(1 << ADPS1);  // 0
  ADCSRA &=  ~(1 << ADPS0);  // 0
}
void setP8( ) {  // prescaler = 8 ; 不建议设为 16 以下!
  Serial.println("ADC Prescaler = 8");  // 011
  ADCSRA &=  ~(1 << ADPS2);  // 0
  ADCSRA |=  (1 << ADPS1);  // 1
  ADCSRA |=  (1 << ADPS0);  // 1
}
void setP4( ) {  // prescaler = 4 ; 不建议设为 16 以下!
  Serial.println("ADC Prescaler = 4");  // 010
  ADCSRA &=  ~(1 << ADPS2);  // 0
  ADCSRA |=  (1 << ADPS1);  // 1
  ADCSRA &=  ~(1 << ADPS0);  // 0
}
void setP32( ) {
  Serial.println("ADC Prescaler = 32");  // 101
  ADCSRA |=  (1 << ADPS2);   // 1
  ADCSRA &=  ~(1 << ADPS1);  // 0
  ADCSRA |=  (1 << ADPS0);   // 1
}
void setP64( ) {  //  prescaler = 64 ;  // 110
  const unsigned char PS_128 = (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
  const unsigned char PS_64 = (1 << ADPS2) | (1 << ADPS1);
  Serial.println("ADC Prescaler = 64");  // 110
  ADCSRA &= ~PS_128;  // clear that 3 bits
  ADCSRA |=  PS_64;  //  set as 110
}
// Reference  http://www.microsmart.co.za/tech ... vanced-arduino-adc/
void setP128( ) { // 默认就是这样
  Serial.println("ADC Prescaler = 128");  // 111
  ADCSRA |=  (1 << ADPS2);  // 1
  ADCSRA |=  (1 << ADPS1);  // 1
  ADCSRA |=  (1 << ADPS0);  // 1
} // setP128

请注意, 虽然可以把 ADC 的 Prescaler 设为很小,
但是当 Prescaler 小于 16, 用 analogRead( )读到的值将很不准确!
这有人做过实验, 如果 Prescaler 在 32 或以上大致还没问题, 参考:
http://www.gammon.com.au/forum/?id=12779

补充:
如果你还要更高的采样率Sampling Rate, 那必须使用 ADC 转换完成的中断处理,
也就是要自己写 ISR(ADC_vect) 中断程序, 不要使用 analogRead( ),
自己直接从 ADC 转换完成后的 ADCL 与 ADCH 读取答案!

以下是个简单范例, 只有读取采样 10 bits 的左边 8 bit, 所以答案是 0 到 255:
kittenblock中小学创客名师推荐的图形化编程软件

// 以下范例是只读取 10 bits 中的左边 8 bits
// 所以答案是 0…255, 如有必要, 你可以把该答案 map 到(0, 1023)

volatile unsigned long cnt;
volatile unsigned long total;
const unsigned long every = 50000;
void setup( ) {
  Serial.begin(9600);
  delay(123);
  initADC( );
}
unsigned long n, tot;
void loop( ) {
   if(cnt % every == 0) {
      cli( );
      n = cnt;
      tot = total;
      sei( );
      Serial.print("n="); Serial.print(n);
      Serial.print(", average="); 
      Serial.println(tot*1.0/n);
   }
} // loop(
 
void initADC( ) {
  cli( );
  ADMUX |= (1 << REFS0); //set reference voltage
  ADMUX |= (1 << ADLAR); //left align the ADC value- so we can read highest 8 bits from ADCH register only
  /// 以上是设定 10 bit 的左边 8 bit 改放在 ADCH
  ADCSRA &= ~(1 << ADPS1);  //  bitClear(ADPS1, ADPS1);   //  101
  ADCSRA |= (1 << ADPS2) | (1 << ADPS0); //set ADC clock with 32 prescaler- 16mHz/32=500kHz
  ADCSRA |= (1 << ADATE); //enabble auto trigger
  ADCSRA |= (1 << ADIE); //enable interrupts when measurement complete
  ADCSRA |= (1 << ADEN); //enable ADC  ;; bitSet(ADCSRA, ADEN);
  cnt = 0;
  ADCSRA |= (1 << ADSC); //start ADC measurements
  sei( );
} // initADC(
volatile int adcHigh;
ISR(ADC_vect) {//when new ADC value ready
  adcHigh = ADCH;  //update the new value from A0 (between 0 and 255)
  ++cnt;
}

其他参考文件:http://www.atmel.com/dyn/resources/prod_documents/DOC2559.PDFhttp://forum.arduino.cc/index.php/topic,6549.0.htmlhttp://www.microsmart.co.za/technical/2014/03/01/advanced-arduino-adc/http://meettechniek.info/embedded/arduino-analog.htmlhttp://www.instructables.com/id/Arduino-Audio-Input/step6/Sampling-rate-of-40kHz/https://bennthomsen.wordpress.com/arduino/peripherals/continuous-adc-capture/ 再补充一下, 透过 ADC 转换器,
我们还可以读取 Arduino 内部的温度传感器,
然后把温度传送到 PC 计算机上!
不过, 因为每个 Arduino 板子实际上会略有差异,
所以以下的程序你必须实际测量后调整里面 tempBias 的值:

//从 Serial monitor 印出当前温度
const int tempBias = 320;  // 要实际测量后调整这
void setup ( ) {
  Serial.begin (9600);
  ADCSRA =  bit (ADEN);   // turn ADC on
  ADCSRA |= bit (ADPS0) |  bit (ADPS1) | bit (ADPS2);  // Prescaler of 128
  ADMUX = bit (REFS0) | bit (REFS1)  | 0x08;    // temperature sensor
  delay (20);  // let it stabilize
  bitSet (ADCSRA, ADSC);  // start a conversion
  while (bit_is_set(ADCSRA, ADSC)) ;; // 等待转换完成
  int value = ADC;
  Serial.print ("Temperature = ");
  Serial.println (value - tempBias);
}  // setup(
void loop () {
  ;
}