Внешний АЦП

hnick
Offline
Зарегистрирован: 10.11.2013

Привет!

Появилась необходимость более точнее считывать напряжение на аналоговом входе. Купил 2 внешних АЦП:

1) AD7705 (16 bit / http://www.analog.com/static/imported-files/data_sheets/AD7705_7706.pdf)

Пытался подключить по SPI:

Более наглядно:

т.к. максимальный уровень 65535 соответствует 2.5В, подключая на аналог #1 1.2В я должен получить что-то похожее на 65535/2, однако на монитор выводятся только нули.

Скетчи я испробовал разные, например:

http://playground.arduino.cc/Code/MCP3208 | http://forum.arduino.cc/index.php/topic,14450.0.html | http://www.edaboard.com/thread251037-2.html | http://forum.arduino.cc/index.php/topic,18893.0.html

Пробовал использовать готовую библиотеку для данного ацп — http://www.kerrywong.com/2012/04/18/ad7705ad7706-library-revisited/ .

Ничего не помогло.

 

2) AD7714 (24 bit / http://www.analog.com/static/imported-files/data_sheets/AD7714.pdf)

Более наглядно:

Аналогичено предыдущему примеру, на мониторе одни нули.

Скетчи пробывал изменять как и для 16 бит, так и пытался использовать:

http://forum.arduino.cc/index.php?topic=193723.0 | http://forum.arduino.cc/index.php?topic=57873.0;wap2 | http://forum.arduino.cc/index.php?topic=57873.0 | http://interface.khm.de/index.php/lab/experiments/connect-a-ltc2400-high... | http://dangerousprototypes.com/forum/viewtopic.php?t=4247&p=42053 | http://forums.netduino.com/index.php?/topic/1831-netduino-with-24bit-adc...

Подключения стандартные:

//#define SELPIN 10    // chip-select
//#define DATAOUT 11   // MOSI 
//#define DATAIN 12    // MISO 
//#define SPICLOCK 13  // Clock 
 
Пытался в обеих случаях играться с
SPI.setDataMode(SPI_MODE0);
ставил в соответствии с документацией и просто пытался менять от 0 до 4, толку 0.
Кварцовые генераторы в наличии на 1, 2, 4 MHz, подключать пробовал все, толку 0.
 
Пишу в надежде, что кто-то уже сталкивался с подобными АЦП и сможет указать либо на мои ошибки, либо на решение.
Спасибо.
trembo
trembo аватар
Offline
Зарегистрирован: 08.04.2011

DATAOUT 11   // MOSI

DATAIN 12    // MISO

Если DATAOUT это выход АЦП  (и вроде-бы АЦП у нас слейвом работает, а Ардуина мастером)

то MOSI это MacterOUT - SlaveIN для Ардуины.

Может я ошибаюсь, но мне кажется что  MOSI MISO   перепутаны...

hnick
Offline
Зарегистрирован: 10.11.2013

trembo пишет:

Может я ошибаюсь, но мне кажется что  MOSI MISO   перепутаны...

Да, Вы правы.

Подсоеденил по следующей схеме:

Для конкретного разбора взял ad7705.

Скетч:

#include <AD770X.h>
 #define DRDY (7)
//set reference voltage to 2.5 V
AD770X ad7706(4.8);
double v;
 
void setup()
{
 //initializes channel 1
  ad7706.reset();
  ad7706.init(AD770X::CHN_AIN1);
   
  Serial.begin(9600);
}
 
void loop()
{
  //read the converted results (in volts)
  while( !digitalRead(DRDY) );
  v = ad7706.readADResult(AD770X::CHN_AIN1);
  Serial.println(v);
  delay(100);
}

Используемая библиотека — http://www.kerrywong.com/2012/04/18/ad7705ad7706-library-revisited/ .

Иногда работает правильно, но потом появляются не понятные мне цифры, например, опорное 4.8V, на аналог (1) идет 2.5, кварц - 2MHz

Жму кнопку reset на плате, затем в пор идут первые 2-е адекватные цифры, потом пауза в секунды 2 и снова 2048:

30494.00
31965.00
2048.00
2048.00
2048.00
2048.00
2048.00
 
Бывают моменты, когда первые 70 значений адекватные, затем опять скатывается все в 2048.
 
Ставлю кварц в 1MHz, правильные значения идут стабильно.
НО, если я из кода убираю последний delay, то получается следующая картина:
 
А если я хочу получить 1000 значений и усреднить их, как тогда поступить?
Возможно проблема связанна с output update rate, как подбирать это значение?
trembo
trembo аватар
Offline
Зарегистрирован: 08.04.2011

Поставьте кондёрчики на входы для проверки.

Хотя-бы 0.1 мкф,  если сигнал не слишком быстро меняется.

Или подайте на входы 3.3 Вольта прямо с платы. И тогда смотрите что будет.

И ещё возможно вы читаете когда этого нельзя делать, там есть интересный выход DRDY (Data Ready).

Он выставляется в "0" при готовности данных.

Вешайте на прерывание (я бы поставил на FALLING) и читайте правильные данные.

Или в цикле через digitalRead...

 

Andrey_Y_Ostanovsky
Offline
Зарегистрирован: 03.12.2012

Наверное железка не успевает отдавать данные. Кроме того, в даташите что-то говорится про Master clock frequency = 400 KHz... Вобщем, я бы, для начала, сделал паузу между считыванием в секунду-две, а потом уменьшал бы ее.

Вот эти ссылки, надеюсь, уже изучили?

http://www.gaw.ru/html.cgi/txt/ic/Analog_Devices/adc/delta/ad7705-06.htm

http://forum.qrz.ru/inzhenernyy-forum-radiolyubitelskie-tehnologii/18813...

HWman
HWman аватар
Offline
Зарегистрирован: 26.02.2013

Автор, разобраться таки получилось?

ЮриБас
Offline
Зарегистрирован: 13.01.2012

Заглохло?.. 

roman2712@mail.ru
Offline
Зарегистрирован: 16.01.2014

Не так давно пытался запустить эту микросхему... Нужна была высокая скорость обработки АЦП. Не взлетело на 2 микросхемах... т.е. иногда данные шли, но непонятно какие. Грешу на кварц (ставил 4 МГц), так как частота не соответсвует даташиту.

В итоге заказал китайский аналаг распаенный на платке. Если кому интересно, когда придет отпишусь как заработало. Тока напомните...

 

 

roman2712@mail.ru
Offline
Зарегистрирован: 16.01.2014

Если кому интересно, заработало со следующим кодом. ВАЖНО используется пин dataReadyPin, который DRDY

#include <SPI.h>

const int dataReadyPin = 9;
const int chipSelectPin = 10;

#define pinCS chipSelectPin
#define pinDR dataReadyPin

//register selection
//RS2 RS1 RS0
static const byte REG_CMM = 0x0; //communication register 8 bit
static const byte REG_SETUP = 0x1; //setup register 8 bit
static const byte REG_CLOCK = 0x2; //clock register 8 bit
static const byte REG_DATA = 0x3; //data register 16 bit, contains conversion result
static const byte REG_TEST = 0x4; //test register 8 bit, POR 0x0
static const byte REG_NOP = 0x5; //no operation
static const byte REG_OFFSET = 0x6; //offset register 24 bit
static const byte REG_GAIN = 0x7; // gain register 24 bit

//channel selection for AD7706 (for AD7705 use the first two channel definitions)
//CH1 CH0
static const byte CHN_AIN1 = 0x0; //AIN1; calibration register pair 0
static const byte CHN_AIN2 = 0x1; //AIN2; calibration register pair 1
static const byte CHN_COMM = 0x2; //common; calibration register pair 0
static const byte CHN_AIN3 = 0x3; //AIN3; calibration register pair 2

//output update rate
//CLK FS1 FS0
static const byte UPDATE_RATE_20 = 0x0; // 20 Hz
static const byte UPDATE_RATE_25 = 0x1; // 25 Hz
static const byte UPDATE_RATE_100 = 0x2; // 100 Hz
static const byte UPDATE_RATE_200 = 0x3; // 200 Hz
static const byte UPDATE_RATE_50 = 0x4; // 50 Hz
static const byte UPDATE_RATE_60 = 0x5; // 60 Hz
static const byte UPDATE_RATE_250 = 0x6; // 250 Hz
static const byte UPDATE_RATE_500 = 0x7; // 500 Hz

//operating mode options
//MD1 MD0
static const byte MODE_NORMAL = 0x0; //normal mode
static const byte MODE_SELF_CAL = 0x1; //self-calibration
static const byte MODE_ZERO_SCALE_CAL = 0x2; //zero-scale system calibration, POR 0x1F4000, set FSYNC high before calibration, FSYNC low after calibration
static const byte MODE_FULL_SCALE_CAL = 0x3; //full-scale system calibration, POR 0x5761AB, set FSYNC high before calibration, FSYNC low after calibration

//gain setting
static const byte GAIN_1 = 0x0;
static const byte GAIN_2 = 0x1;
static const byte GAIN_4 = 0x2;
static const byte GAIN_8 = 0x3;
static const byte GAIN_16 = 0x4;
static const byte GAIN_32 = 0x5;
static const byte GAIN_64 = 0x6;
static const byte GAIN_128 = 0x7;

void setNextOperation(byte reg, byte channel, byte readWrite)
{
  byte r = 0;
  r = reg << 4 | readWrite << 3 | channel;

  digitalWrite(pinCS, LOW);
  SPI.transfer(r);
  digitalWrite(pinCS, HIGH);
}

//Clock Register
//   7      6       5        4        3        2      1      9
//ZERO(0) ZERO(0) ZERO(0) CLKDIS(0) CLKDIV(0)
void writeClockRegister(byte CLKDIS, byte CLKDIV, byte outputUpdateRate)
{
  byte r = CLKDIS << 4 | CLKDIV << 3 | outputUpdateRate;

  digitalWrite(pinCS, LOW);
  SPI.transfer(r);
  digitalWrite(pinCS, HIGH);
}

//Setup Register
//  7     6     5     4     3      2      1      0
//MD10) MD0(0) G2(0) G1(0) G0(0) B/U(0) BUF(0) FSYNC(1)
void writeSetupRegister(byte operationMode, byte gain, byte unipolar, byte buffered, byte fsync)
{
  byte r = operationMode << 6 | gain << 3 | unipolar << 2 | buffered << 1 | fsync;

  digitalWrite(pinCS, LOW);
  SPI.transfer(r);
  digitalWrite(pinCS, HIGH);
}

unsigned int readDataRegister()
{
  digitalWrite(pinCS, LOW);
  byte b1 = SPI.transfer(0xff);
  byte b2 = SPI.transfer(0xff);
  digitalWrite(pinCS, HIGH);

  unsigned int r = b1 << 8 | b2;

  return r;
}

void getReady()
{
  while (digitalRead(pinDR));
}

void init(byte channel, byte gain, byte updRate)
{
  setNextOperation(REG_CLOCK, channel, 0);
  writeClockRegister(0, 1, updRate);
  setNextOperation(REG_SETUP, channel, 0);
  writeSetupRegister(MODE_SELF_CAL, gain, 0, 0, 1);
  setNextOperation(REG_SETUP, channel, 0);
  writeSetupRegister(MODE_SELF_CAL, gain, 0, 0, 0);
  getReady();
}

unsigned int readADResult(byte channel)
{
  setNextOperation(REG_DATA, channel, 1);
  getReady();
  return readDataRegister();
}

double readResult(byte channel)
{
  double VRef = 2.5;
  return (readADResult(channel) - 32768) * 1.0 / 32768.0 * VRef;
}


void setup()
{
  SPI.begin();
  SPI.setDataMode(SPI_MODE3);
  SPI.setClockDivider(SPI_CLOCK_DIV2);
  Serial.begin(115200);
  Serial.println("start");

  pinMode(chipSelectPin, OUTPUT);
  pinMode(dataReadyPin, INPUT);

  init(CHN_AIN1, GAIN_1, UPDATE_RATE_500);
  init(CHN_AIN2, GAIN_1, UPDATE_RATE_500);
}

void loop()
{
  unsigned long currentMillis = millis();
  Serial.print(readResult(CHN_AIN1));
  Serial.print("\t");
  Serial.print(readResult(CHN_AIN2));
  Serial.print("\t");
  Serial.println(millis() - currentMillis);
}

 

ЮриБас
Offline
Зарегистрирован: 13.01.2012

roman2712@mail.ru пишет:
 Если кому интересно, заработало со следующим кодом. ВАЖНО используется пин dataReadyPin, который DRDY 

Сколько реально получилось выборок в сек. ?  Там по даташиту вроде не менее 19,2кГц (38кГц. на усилении =1)

roman2712@mail.ru
Offline
Зарегистрирован: 16.01.2014

Для одного канала, сколько укажете в UPDATE_RATE столько и будет. Чем выше частота, тем больше плавает значение

на 500 кГц у меня сложилось впечатление что 3 - 4 разряда плавают

Если переключать каналы, то частота опроса падает в 3 раза на 1 канал (т.е. при переключении канала он автоматически берет первые 3 значения на фильтр и только потом выдает результат). Т.е. если при частоте 50 кГц один канал опрашивается за 20 мС, то 2 канала опрашиваютя за (20 * 3) * 2 = 120 мС

 

hnick
Offline
Зарегистрирован: 10.11.2013

HWman пишет:

Автор, разобраться таки получилось?

ЮриБас пишет:

Заглохло?.. 

 

5 лет прошло :).

Да, мне удалось запустить его (AD7705), уже и не помню как и что делал. Если надо, могу покапаться на диске и поискать скетчи.

Mikhail86
Offline
Зарегистрирован: 01.08.2016

Здравствуйте, товарищи!

Я тоже сейчас заморочился с внешним ацп AD7707. Сразу повылезали проблемы: 1. Длительная смена канала. 2. При смене канала - почему-то данные были устаревшие. Решил я эту проблему вот таким скетчем:

#include <OneWire.h>
#include <SPI.h>
OneWire ds(48); // Создаем объект OneWire для шины 1-Wire, с помощью которого будет осуществляться работа с датчиком


const int dataReadyPin = 49;
const int chipSelectPin = 53;

double pH;
double bR;
double rX;
unsigned int redox_mv;
double pHcal1;
double pHcal2;
double pHcaltemp1;
double pHcaltemp2;
#define pinCS chipSelectPin
#define pinDR dataReadyPin

//register selection
//RS2 RS1 RS0
static const byte REG_CMM = 0x0; //communication register 8 bit
static const byte REG_SETUP = 0x1; //setup register 8 bit
static const byte REG_CLOCK = 0x2; //clock register 8 bit
static const byte REG_DATA = 0x3; //data register 16 bit, contains conversion result
static const byte REG_TEST = 0x4; //test register 8 bit, POR 0x0
static const byte REG_NOP = 0x5; //no operation
static const byte REG_OFFSET = 0x6; //offset register 24 bit
static const byte REG_GAIN = 0x7; // gain register 24 bit

//channel selection for AD7706 (for AD7705 use the first two channel definitions)
//CH1 CH0
static const byte CHN_AIN1 = 0x0; //AIN1; calibration register pair 0
static const byte CHN_AIN2 = 0x1; //AIN2; calibration register pair 1
static const byte CHN_COMM = 0x2; //common; calibration register pair 0
static const byte CHN_AIN3 = 0x3; //AIN3; calibration register pair 2

//output update rate
//CLK FS1 FS0
static const byte UPDATE_RATE_20 = 0x0; // 20 Hz
static const byte UPDATE_RATE_25 = 0x1; // 25 Hz
static const byte UPDATE_RATE_100 = 0x2; // 100 Hz
static const byte UPDATE_RATE_200 = 0x3; // 200 Hz
static const byte UPDATE_RATE_50 = 0x4; // 50 Hz
static const byte UPDATE_RATE_60 = 0x5; // 60 Hz
static const byte UPDATE_RATE_250 = 0x6; // 250 Hz
static const byte UPDATE_RATE_500 = 0x7; // 500 Hz

//operating mode options
//MD1 MD0
static const byte MODE_NORMAL = 0x0; //normal mode
static const byte MODE_SELF_CAL = 0x1; //self-calibration
static const byte MODE_ZERO_SCALE_CAL = 0x2; //zero-scale system calibration, POR 0x1F4000, set FSYNC high before calibration, FSYNC low after calibration
static const byte MODE_FULL_SCALE_CAL = 0x3; //full-scale system calibration, POR 0x5761AB, set FSYNC high before calibration, FSYNC low after calibration

//gain setting
static const byte GAIN_1 = 0x0;
static const byte GAIN_2 = 0x1;
static const byte GAIN_4 = 0x2;
static const byte GAIN_8 = 0x3;
static const byte GAIN_16 = 0x4;
static const byte GAIN_32 = 0x5;
static const byte GAIN_64 = 0x6;
static const byte GAIN_128 = 0x7;

void setNextOperation(byte reg, byte channel, byte readWrite)
{
  byte r = 0;
  r = reg << 4 | readWrite << 3 | channel;

  digitalWrite(pinCS, LOW);
  SPI.transfer(r);
  digitalWrite(pinCS, HIGH);
}

//Clock Register
//   7      6       5        4        3        2      1      9
//ZERO(0) ZERO(0) ZERO(0) CLKDIS(0) CLKDIV(0)
void writeClockRegister(byte CLKDIS, byte CLKDIV, byte outputUpdateRate)
{
  byte r = CLKDIS << 4 | CLKDIV << 3 | outputUpdateRate;
  digitalWrite(pinCS, LOW);
  SPI.transfer(r);
  digitalWrite(pinCS, HIGH);
}

//Setup Register
//  7     6     5     4     3      2      1      0
//MD10) MD0(0) G2(0) G1(0) G0(0) B/U(0) BUF(0) FSYNC(1)
void writeSetupRegister(byte operationMode, byte gain, byte unipolar, byte buffered, byte fsync)
{
  byte r = operationMode << 6 | gain << 3 | unipolar << 2 | buffered << 1 | fsync;

  digitalWrite(pinCS, LOW);
  SPI.transfer(r);
  digitalWrite(pinCS, HIGH);
}

unsigned int readDataRegister()
{
  digitalWrite(pinCS, LOW);
  byte b1 = SPI.transfer(0xff);
  byte b2 = SPI.transfer(0xff);
  digitalWrite(pinCS, HIGH);

  unsigned int r = b1 << 8 | b2;

  return r;
}

void getReady()
{
  while (digitalRead(pinDR)== 0);
}
 
boolean DRDY_REDY()
{   boolean testresult = digitalRead(pinDR);
   while(digitalRead(pinDR) != 0){
  testresult = digitalRead(pinDR);
    } 
  return testresult;
  }
void init(byte channel, byte gain, byte updRate)
{
  setNextOperation(REG_CLOCK, channel, 0);
  writeClockRegister(0, 1, updRate);
  setNextOperation(REG_SETUP, channel, 0);
  writeSetupRegister(MODE_SELF_CAL, gain, 0, 0, 1);
  setNextOperation(REG_SETUP, channel, 0);
  writeSetupRegister(MODE_SELF_CAL, gain, 0, 0, 0);
  getReady();
}

unsigned int readADResult(byte channel)
{
  setNextOperation(REG_DATA, channel, 1);
  getReady();
  return readDataRegister();
}

double readResult(byte channel)
{
  double VRef = 2.50000;
  return (readADResult(channel) - 32768)/ 32768.00000 * VRef;
}
 byte spiTransfer(volatile byte data) {
        SPDR = data;

        while (!(SPSR & _BV(SPIF)));

        return SPDR;
    }
void AD770Xreset() {
    digitalWrite(pinCS, LOW);
    for (int i = 0; i < 100; i++)
        spiTransfer(0xff);
    digitalWrite(pinCS, HIGH);
}
void setup()
{
  SPI.begin();
  SPI.setDataMode(SPI_MODE3);
  SPI.setClockDivider(SPI_CLOCK_DIV8);
  AD770Xreset();
   Serial.begin(9600);
  Serial.println("start");
  pinMode(chipSelectPin, OUTPUT);
  pinMode(dataReadyPin, INPUT);
  digitalWrite(dataReadyPin,HIGH);
}

float temperature = 20;
void tempread(){
  byte data[2];     ds.reset(); 
  ds.write(0xCC);    ds.write(0x44);
  ds.reset();  ds.write(0xCC); 
  ds.write(0xBE);   data[0] = ds.read(); 
  data[1] = ds.read();    temperature =  ((data[1] << 8) | data[0]) * 0.0625;
     }
  
  void spi_ch1_read(){
     init(CHN_AIN1, GAIN_1, UPDATE_RATE_20);
       DRDY_REDY();
          if(DRDY_REDY() == 0){
         bR = 1000*readResult(CHN_AIN1)-201.46;
          }
          }
        void spi_ch2_read(){
     init(CHN_AIN2, GAIN_1, UPDATE_RATE_20);
      DRDY_REDY();
       if(DRDY_REDY() == 0){
      pH = ((readResult(CHN_AIN2)*1000) -(temperature-10.2)*0.8165 + 18.456)*-0.02 + 7.1052;
      }}
       void spi_ch3_read(){
     init(CHN_AIN2, GAIN_1, UPDATE_RATE_20);
       DRDY_REDY();
       if(DRDY_REDY() == 0){
       redox_mv = round(readResult(CHN_AIN3)*1000 + 224) ;
          }
       }

void loop()
{
  delay(10000);
 spi_ch1_read();
   Serial.println("bR");
    Serial.println(bR,4);

 spi_ch2_read();
   Serial.println("pH");
   Serial.println(pH,3);

 spi_ch3_read();
  
  Serial.println("Redox:");
  Serial.println(redox_mv);
  
tempread();
  Serial.println("temp");
  Serial.println(temperature,1);
  
}

Добавил функцию  

void getReady()
{
  while (digitalRead(pinDR)== 0);
}
 
boolean DRDY_REDY()
{   boolean testresult = digitalRead(pinDR);
   while(digitalRead(pinDR) != 0){
  testresult = digitalRead(pinDR);
    } 

И ура - все заработало без всяких millis и delay. Правда на одно измерение мне надо реально 300-450мс. Измерения очень стабильные. 

Вопрос - можно ли как нибудь оптимизировать время чтения по SPI при выборе канала? Я сделал так, что в момент когда DRDY == LOW происходит считывание. В остальное время система просто ждет в цикле while. Правда если у меня в программе прилетит прерывание вероятно может это все испортить. Это я проверю чуть позже.  Может есть какой нибудь более оптимальный вариант кода ?

sadman41
Offline
Зарегистрирован: 19.10.2016

По-моему тут не время чтения по SPI нужно оптимизировать, а процедуру ожидания готовности. 

Вопрос только в том - есть ли куда тратить сэкономленные такты?

Mikhail86
Offline
Зарегистрирован: 01.08.2016

Конечно есть. У меня же не только эта программа исполняется на МК. Мне кажется очень глупо тратить по 500мс на одно измерение. Схема то используется в промышленности - я не думаю, что там по пол секунды тратят

sadman41
Offline
Зарегистрирован: 19.10.2016

Кто ж знает, где она у вас используется. В том случае, если хотите получить свои 500мс в рабочий цикл - запускайте конверсию, выставляйте флаг "работют датчики" и проверяйте каждый луп (или по таймеру) состояние INT-входа. Как только он упал - инициируйте процедуру считывания результата.

В целом так. Применимо к приведенному коду - я бы его переделал на какой-нить конечный автомат.

Mikhail86
Offline
Зарегистрирован: 01.08.2016

спасибо конечно, с таймерам я само собой сделал. опрос датчиков мне пока нужен раз в 8 секунд, и как под конечный автомат сделать я знаю. я интересуюсь как можно оптимизировать время выбора канала. Вот это интересно - неужели она такая медленная ?

sadman41
Offline
Зарегистрирован: 19.10.2016

Как я вижу - после команды инита канала еще определенное время мотается while(). Т.е. опять же - инит пустили, мигаете светодиодом, щелкаете релюшками, проверяете не выставился ли INT. Выставился - начинаете читать инициализированный канал. Вот и вся оптимизация.

На даташит глянул - там написано, что DRDY появляется, если есть новые данные. Скорее всего сразу после вместе с инитом запускается долгая конверсия/калибровка. Решение то же - пуск -> свои дела, проверка INT -> работа с каналом по обнаружению INT.

CotDaVinchi
Offline
Зарегистрирован: 12.05.2016

Призадумаешься, использовалить ли этот аналог девайс. 

susplus
Offline
Зарегистрирован: 22.09.2019

Интересный опыт https://digibird1.wordpress.com/arduino-as-a-5m-sample-osciloscope/

На базе внешнего  AЦП на базе микрочипа CA3306.

5.000.000 выборок в секунду (5Mhz)!