Тестер для АЦП AD76xx Печать
Добавил(а) microsin   

К сожалению, пришлось столкнуться с большой партией бракованных микросхем AD7691 [1], которую нам поставила компания ООО "Аметист Электроникс". Из 50 штук оказалось годными только 4. Поэтому понадобилось собрать тестер, чтобы проверять исправность микросхем АЦП.

[Общее описание тестера]

Тестер был собран на основе платы Arduino (микроконтроллер ATmega328p), см. схему на рис. ниже.

AD76xx testing sch

Принцип работы довольно простой. Микроконтроллер генерирует сигналы опроса АЦП (SCK, SDO, CNV, используются порты микроконтроллера PB5 для SCK, PB4 для SDO, PB1 для CNV), которое работает в 3-проводном режиме, без индикатора занятости [1]. В качестве входного сигнала для дифференциальных входов АЦП используется проинтегрированный конденсаторами (C1, C2) импульс, формируемый ключами GPIO микроконтроллера (PD4, PD5, подключенные к резисторам R5, R6). Результаты считывания АЦП в виде двоичных разрядов D17..D0 кода и соответствующего напряжения выводятся в окно консоли терминала, подключенной к виртуальному COM-порту платы Arduino.

11 11111111 11111011 -0.0002v

Для управления тестером имеется простейшее меню:

?  help
1  continuously read AD76xx
2  one-shoot -
3  one-shoot +

Если нажать в консоли клавишу ?, то выведется подсказка. Если нажать 1, программа перейдет в непрерывное чтение АЦП с выводом результатов в консоль. Если нажать 2, то микроконтроллер сгенерирует для АЦП отрицательный импульс, при этом результат чтения АЦП будет плавно спадать от отрицательного максимума до 0, и можно наблюдать насколько измеряется напряжение и как меняются разряды данных АЦП. Если нажать 3, то микроконтроллер сгенерирует для АЦП положительный импульс, при этом результат чтения АЦП будет плавно спадать от положительного максимума до 0.

Работа бракованных АЦП отличалась от исправных следующим образом. У бракованных всегда разряды D3..D0 читались как 0, и никогда не менялись. Остальные разряды давали код, совершенно неадекватный входному дифференциальному напряжению. Попадались также АЦП, которые вообще не выдавали никакого кода на выходе. Ниже приведены диаграммы сигналов интерфейса исправных и неисправных АЦП.

[Исправная микросхема AD7691]

Сигналы интерфейса исправного АЦП. Желтый луч такты SCK, зеленый луч данные SDO, синий луч сигнал запуска преобразования и выборки CNV:

AD7691 good shoot minus

AD7691 good shoot plus

Фото исправной микросхемы:

AD7691 n538 top AD7691 n538 bottom

[Неисправная микросхема AD7691]

Ниже показаны сигналы интерфейса бракованного АЦП. На верхней диаграмме измеряется отрицательное напряжение, на нижней положительное. Обратите внимание, что разряд знака D17 показывает противоположное значение, не соответствующее полярности приложенного входного сигнала. Кроме того, разряды D3..D0 никогда не меняют своего значения, всегда читаются как лог. 0.

AD7691 bad shoot minus

AD7691 bad shoot plus

Фото бракованой микросхемы:

AD7691 n416 bad top AD7691 n416 bad bottom

Тестером можно проверять не только АЦП AD7691, но и другие 16-битные и 18-битные АЦП с таким же интерфейсом и цоколевкой.

Параметр
AD7687 AD7688 AD7690 AD7691 AD7693
Разрешающая способность, бит 16 18 16
Скорость оцифровки, киловыб./сек, kSPS 250 500 400 250 500

[Программное обеспечение тестера]

Программа для микроконтроллера была написана с помощью AVR Studio 4.19. Для перепрошивки через USB использовался штатный загрузчик Arduino вместе с утилитой AVRDUDE. Ниже приведены подпрограммы опроса АЦП и вывода результатов. Полностью проект и прошивку микроконтроллера можно скачать по ссылке [2].

/////////////////////////////////////////////////////////////////////////
// Программа для ATmega328P (плата Arduino Nano v3.0), проверяющая
// работоспособность АЦП AD7691 (или других с аналогичным интерфейсом,
// например AD7687).
//
// Подключение АЦП:
//    SDI всегда лог. 1
//    SCK к SCK  порта SPI (PB5)
//    SDO к MISO порта SPI (PB4)
//    CNV к GPIO PB1
//
// Управление входным напряжением АЦП:
//    -   к GPIO PD4
//    +   к GPIO PD5
 
#include < inttypes.h >
#include < stdbool.h >
#include < avr/io.h >
#include < avr/interrupt.h >
#include < string.h >
#include < util/delay.h >
#include < stdio.h >
 
//Если раскомментировать строку с определением HARDWARE_SPI, то будет
// использоваться аппаратный SPI для чтения АЦП. Иначе будет использоваться
// программный SPI (управлением выводами GPIO).
//#define HARDWARE_SPI 1
 
#define CNV    PB1
#define _SS    PB2
#define MOSI   PB3
#define MISO   PB4
#define SCK    PB5
 
#define u8 unsigned char
#define s8 signed char
#define u16 unsigned int
#define u32 uint32_t
#define s32 int32_t
#define false 0
#define true  1
 
#define MODE_COLD_START             0
#define MODE_ADC_CONTINUOUS_READ    1
#define MODE_ADC_TEST_MINUS         2
#define MODE_ADC_TEST_PLUS          3
#define MODE_SHOW_HELP              4
#define MODE_WAIT                   5
 
u8 mode = MODE_COLD_START;
 
typedef union
{
   s32 sval;     //Для вывода
   u32 uval;     //Для отладки
   u8  bytes[4]; //Массив для приема оцифровки
}adcdata_t;
 
adcdata_t adcdata;
 
#ifdef HARDWARE_SPI
//Работа через аппаратный SPI.
void initSPImaster (void)
{
   volatile char IOReg;
 
   //Настройка ~SS как выхода, чтобы он не влиял
   // на работу master-а:
   DDRB |= (1 << _SS);
   //Настройка MOSI и SCK как выхода:
   DDRB |= (1 << MOSI)|(1 << SCK);
   //Настройка ножки конверсии:
   DDRB |= (1 << CNV);
   //Настройка регистра управления SPCR.
   //Тактовая частота SPI будет 16/4=4 МГц, MSB бит данных
   // идет первым. Биты CPHA и CPOL: работают все варианты,
   // кроме CPHA=1 и CPOL=1.
   SPCR = (1 << SPE)|(1 << MSTR)|(1 << CPOL);
   IOReg   = SPSR;               // очистить бит SPIF в регистре SPSR
   IOReg   = SPDR;
}
 
//Подпрограмма читает 18 бит из АЦП в массив bytes[4].
void readADC (void)
{
   s8 idx;
 
   PORTB &= ~(1 << CNV);
   for (idx=2; idx >= 0; idx--)
   {
      SPDR  = 0;
      while (!(SPSR & (1 << SPIF)));
      adcdata.bytes[idx] = SPDR;
   }
   PORTB |= (1 << CNV);
   adcdata.uval >>= 6;
   if (adcdata.bytes[2]&0x02)
   {
      adcdata.bytes[3]  = 0xFF;
      adcdata.bytes[2] |= 0xFC;
   }
   else
   {
      adcdata.bytes[3]  = 0x00;
      adcdata.bytes[2] &= ~0xFC;
   }
}
#else
//Работа через программный SPI.
void initSPImaster (void)
{
   //Настройка MOSI, SCK и CNV как выхода:
   DDRB |= (1 << MOSI)|(1 << SCK)|(1 << CNV);
}
 
#define READBITADC(tobyte) \
      adcdata.bytes[tobyte] << = 1;\
      if(PINB & (1 << MISO))\
         adcdata.bytes[tobyte] |= 0x01;\
      if(0==(PINB & (1 << MISO)))\
         adcdata.bytes[tobyte] &= ~0x01;\
      PORTB |= (1 << SCK);\
      PORTB &= ~(1 << SCK)
 
//Подпрограмма через программно организованный SPI
// читает 18 бит из АЦП в массив bytes[4].
void readADC (void)
{
   u8 bitcnt;
   adcdata.uval = 0;
 
   PORTB &= ~(1 << CNV);
   for (bitcnt=0; bitcnt < 2; bitcnt++)
   {
      READBITADC(2);
   }
   for (bitcnt=0; bitcnt < 8; bitcnt++)
   {
      READBITADC(1);
   }
   for (bitcnt=0; bitcnt < 8; bitcnt++)
   {
      READBITADC(0);
   }
   PORTB |= (1 << CNV);
   if (adcdata.bytes[2]&0x02)
   {
      adcdata.bytes[3]  = 0xFF;
      adcdata.bytes[2] |= 0xFC;
   }
   else
   {
      adcdata.bytes[3]  = 0x00;
      adcdata.bytes[2] &= ~0xFC;
   }
}
 
#endif
 
u8 rxbuf[RXBUF_SIZE];
u8 inrx, outrx;
u8 txbuf[TXBUF_SIZE];
u8 intx, outtx;
 
void uartInit(u32 baudrate, u8 parity, u8 stopbits, u8 databits)
{
   usbDWord_t   br;
   br.dword = F_CPU / (8L * baudrate) - 1;
   UCSR0A  |= (1 << U2X0);
   /* конфигурация USART */
   UCSR0B  = 0;
   UCSR0C  = URSEL_MASK | ((parity==1? 3:parity) << UPM00)
          | ((stopbits >> 1) << USBS0) | ((databits-5) << UCSZ00);
   UBRR0L  = br.bytes[0];
   UBRR0H  = br.bytes[1];
   inrx = outrx = 0;
   intx = outtx = 0;
   UCSR0A &= ~(1 << TXC0); //сбрасываем флаг окончания передачи
   //разрешаем прерывания, приемник и передатчик
   UCSR0B  = (1 << RXEN0) | (1 << TXEN0) | (1 << RXCIE0);
}
 
//Обработчик прерывания приема USART
ISR (USART_RX_vect)
{
    rxbuf[inrx++] = UDR0;
    inrx &= RXBUF_MASK;
    rxtimeout = 0;
}
 
u8 idxDiff (u8 idxIN, u8 idxOUT, u8 bufsize)
{
    if (idxIN >= idxOUT)
        return (idxIN - idxOUT);
    else
        return ((bufsize - idxOUT) + idxIN);
}
 
/////////////////////////////////////////////////////////////////////////////////
// Подпрограмма обслуживания буфера передачи. Перед проверкой проверяет
//  условие опустошения UDR0 (если установлен флаг UDRE0).
void handlerUsartTX (void)
{
   while (intx != outtx)
   {
      if (UCSR0A & (1 << UDRE0))
      {
         UDR0 = txbuf[outtx++];  //передаем байт
         outtx &= TXBUF_MASK;
      }
   }
}
 
/////////////////////////////////////////////////////////////////////////////////
// Подпрограмма отслеживает прием команды запуска #ABC.
// Если была команда #ABC, то вернет true, и обнулит буфер приема.
u8 handlerUsartRX (void)
{
   if (inrx!=outrx)
   {
      switch(rxbuf[outrx])
      {
      case '?':
         mode = MODE_SHOW_HELP;
         break;
      case '1':
         mode = MODE_ADC_CONTINUOUS_READ;
         break;
      case '2':
         mode = MODE_ADC_TEST_MINUS;
         break;
      case '3':
         mode = MODE_ADC_TEST_PLUS;
         break;
      }
      outrx++;
      outrx &= RXBUF_MASK;
      return true;
   }
   else
      return false;
}
 
static inline void outchar (char sym)
{
   UDR0 = sym;       //передаем байт через UART
   while(!(UCSR0A & (1 << UDRE0)));
}
 
#define REF198_VOLTAGE  4.096
#define ONE_BIT_VOLTAGE ((double)REF198_VOLTAGE/131071)
 
char tmpstr[32];
 
static void outputADCdata (void)
{
   u8 bitcnt, databyte;
   s8 idx;
 
   //Вывод двоичного значения:
   for (idx=2; idx >= 0; idx--)
   {
      databyte = adcdata.bytes[idx];
      if (2==idx)
      {
         for (bitcnt=0; bitcnt < 2; bitcnt++)
         {
            if (databyte & 0x02)
               outchar('1');
            else
               outchar('0');
            databyte << = 1;
         }
      }
      else
      {
         for (bitcnt=0; bitcnt < 8; bitcnt++)
         {
            if (databyte & 0x80)
               outchar('1');
            else
               outchar('0');
            databyte << = 1;
         }
      }
      outchar(' ');
   }
   //Вывод измеренного дифференциального напряжения в формате float:
   sprintf(tmpstr, "%2.4fv ", ONE_BIT_VOLTAGE * adcdata.sval);
   for (idx=0; idx < sizeof(tmpstr); idx++)
   {
      if (0==tmpstr[idx])
         break;
      outchar(tmpstr[idx]);
   }
   //outchar('\n');
   outchar('\r');
}
 
char usage [] = "\
?  help\n\r\
1  continuously read AD76xx\n\r\
2  one-shoot -\n\r\
3  one-shoot +\n\r";
 
void main (void)
{
   initSPImaster();
   uartInit(115200, 0/*no parity*/, 1/*stopbits*/, 8/*databits*/);
   sei();
   while(1)
   {
      switch(mode)
      {
      case MODE_COLD_START:
         for (u8 txtidx=0;usage[txtidx];txtidx++)
            outchar(usage[txtidx]);
         mode = MODE_ADC_CONTINUOUS_READ;
         break;
      case MODE_ADC_CONTINUOUS_READ:
         readADC();
         outputADCdata();
         break;
      case MODE_ADC_TEST_MINUS:
         PORTD &= ~((1 << PD4)|(1 << PD5));
         DDRD  |=   (1 << PD4)|(1 << PD5);
         PORTD |=   (1 << PD4);
         _delay_ms(10);
         DDRD  &= ~((1 << PD4)|(1 << PD5));
         PORTD &= ~((1 << PD4)|(1 << PD5));
         mode = MODE_ADC_CONTINUOUS_READ;
         break;
      case MODE_ADC_TEST_PLUS:
         PORTD &= ~((1 << PD4)|(1 << PD5));
         DDRD  |=   (1 << PD4)|(1 << PD5);
         PORTD |=   (1 << PD5);
         _delay_ms(10);
         DDRD  &= ~((1 << PD4)|(1 << PD5));
         PORTD &= ~((1 << PD4)|(1 << PD5));
         mode = MODE_ADC_CONTINUOUS_READ;
         break;
      case MODE_SHOW_HELP:
         for (u8 txtidx=0;usage[txtidx];txtidx++)
            outchar(usage[txtidx]);
         mode = MODE_WAIT;
         break;
      }
      handlerUsartRX();
      handlerUsartTX();
   }
}

Иногда бывает так, что в определенном диапазоне входного сигнала младшие разряды D4..D0 читаются как нули. Причина может быть в том, что на опорном напряжении REF присутствуют помехи, по фазе и частоте совпадающие с помехами, присутствующими во входном сигнале. Поэтому уделите особое внимание качеству разводки печатной платы возле АЦП, и качественной фильтрации (развязке) напряжения питания и опорного напряжения. Советы по разводке PCB и применению микросхемы АЦП см. в даташите.

[Ссылки]

1. АЦП AD7691.
2. 160218AD7691-tester.zip.
3Сокращенная кодировка компонентов Analog Devices.