К сожалению, пришлось столкнуться с большой партией бракованных микросхем AD7691 [1], которую нам поставила компания ООО "Аметист Электроникс". Из 50 штук оказалось годными только 4. Поэтому понадобилось собрать тестер, чтобы проверять исправность микросхем АЦП.
Тестер был собран на основе платы Arduino (микроконтроллер ATmega328p), см. схему на рис. ниже.
Принцип работы довольно простой. Микроконтроллер генерирует сигналы опроса АЦП (SCK, SDO, CNV, используются порты микроконтроллера PB5 для SCK, PB4 для SDO, PB1 для CNV), которое работает в 3-проводном режиме, без индикатора занятости [1]. В качестве входного сигнала для дифференциальных входов АЦП используется проинтегрированный конденсаторами (C1, C2) импульс, формируемый ключами GPIO микроконтроллера (PD4, PD5, подключенные к резисторам R5, R6). Результаты считывания АЦП в виде двоичных разрядов D17..D0 кода и соответствующего напряжения выводятся в окно консоли терминала, подключенной к виртуальному COM-порту платы Arduino.
Если нажать в консоли клавишу ?, то выведется подсказка. Если нажать 1, программа перейдет в непрерывное чтение АЦП с выводом результатов в консоль. Если нажать 2, то микроконтроллер сгенерирует для АЦП отрицательный импульс, при этом результат чтения АЦП будет плавно спадать от отрицательного максимума до 0, и можно наблюдать насколько измеряется напряжение и как меняются разряды данных АЦП. Если нажать 3, то микроконтроллер сгенерирует для АЦП положительный импульс, при этом результат чтения АЦП будет плавно спадать от положительного максимума до 0.
Работа бракованных АЦП отличалась от исправных следующим образом. У бракованных всегда разряды D3..D0 читались как 0, и никогда не менялись. Остальные разряды давали код, совершенно неадекватный входному дифференциальному напряжению. Попадались также АЦП, которые вообще не выдавали никакого кода на выходе. Ниже приведены диаграммы сигналов интерфейса исправных и неисправных АЦП.
Тестером можно проверять не только АЦП AD7691, но и другие 16-битные и 18-битные АЦП с таким же интерфейсом и цоколевкой.
Программа для микроконтроллера была написана с помощью 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();
}
}