Наноамперметр на Attiny84

nanocurrentmeterВ этой статье описывается простой измеритель слабых токов, который можно использовать для замера потребления в спящем режиме различных цифровых схем, в том числе и на микроконтроллерах. Он позволяет измерять токи от 30нА до 10 мкA с достаточной точностью, и собран на микроконтроллере ATtiny84 и нескольких других недорогих деталях.

nanocurrentmeter

Наноамперметр на ATtiny84, измеряющий ток в 106нА, собранный на макетной плате.

Принцип измерения малых токов

Измерение очень малых токов обычными цифровыми мультиметрами, как известно, затруднено ; они либо не обеспечивают низкий диапазон измерения тока вообще, или, если такой диапазон все же присутствует, они создают падение напряжения, называемое "напряжение нагрузки", которое может привести к неточным показаниям. Один из способов обойти это-использовать прецизионные датчики тока, но такие схемы очень дороги.

В этой схеме не используется высокая точность, поэтому в данном случае альтернативным подходом будет вычисление тока, основанного на времени, которое требуется конденсатору для разряда. Напряжение на конденсаторе, разряжающемся на резистивную нагрузку, экспоненциально. Напряжение делится пополам для каждого последующего фиксированного интервала времени, поэтому, если напряжение начинается с 5V, через некоторое время t оно упадет до половины этого значения, 2.5 V. Через некоторое время t оно упадет до 1.25 V и так далее. Время t называется Постоянная времени.

Постоянная времени равна логарифму двух от временной постоянной RC, или 0.693 x RC. таким образои, для фиксированного конденсатора C, измеряя время разряда до 50% от начального значения, мы можем вычислить сопротивление R, эффективное сопротивление нагрузки, а отсюда и потребление тока.

Это легко сделать, используя микроконтрроллер с АЦП, несколько других элементов и измерительный конденсатора; недостатком является то, что измерение тока в наноамперах может занять несколько секунд, пока конденсатор разряжается.

Схема наноамперметра

Вот схема, примерно отражающая то, как она была собрана на макетной плате:

nanocurrentmeter

Схема наноамперметр на ATtiny84.

Схема собрана на ATtiny84 и трехразрядном семисегментном индикаторе с высотой символов 0.28 дюйма.

Выводы PA0 - PA6 порта A используются для управления сегментами индикатора, а PB0 - PB2  для управления разрядами. Так как больше свободных выводов не остается PB2 также используется для управления десятичной точкой, это срабатывает потому, что в третьем разряде точка не нужна. Вход PA7 настроен как аналоговый вход для измерения напряжения на конденсаторе.

Конденсатор используется пленочный с допуском ±5%. Можно использовать любой высококачественный конденсатор кроме электролитического.

Кнопка, подключенная ко входу сброса используется для запуска измерения.

Схема должна питаться от стабилизированного источника на 5 вольт, так как это влияет на точность измерений.

Использование наноамперметра

Для измерения потребляемого тока подсоедините тестируемую схему между контактами "Test" и "Gnd" и нажмите кнопку "Reset". Программа сначала кратковременно подаст 5 вольт на вход "Test"  чтобы зарядить конденсатор. Затем она отключает напрядение, оставляя испытуемую схему питаться от конденсатора.

Когда конденсатор разрядится до определенного напряжения программа посчитает ток разряда и отобразит его в наноамперах или микроамперах; микроамперы отображаются с суффиксом "u". Например, возможные варианты индикации:

Дисплей Ток
Lo Ток< 30nA
45 Ток= 45nA
750 Ток= 750nA
2.5u Ток= 2.5µA
10u Ток= 10µA
Hi Ток> 10µA

Обратите внимание, что измерение может занять до 100 секунд при измерении токов до 30нA.

Если вы тестируете потребление тока схемы в спящем режиме, питайте ее от тестового входа наноамперметра, а когда цепь перейдет в спящий режим, нажмите кнопку "Reset".

Программа наноамперметра

Динамическая индикация

Динамическая индикация реализуется с помощью прерывания, выводя содержимое массива Buffer[]. Например, для отображения "123" код такой:

Buffer[0]=1; Buffer[1]=2; Buffer[2]=3;

В программе используется Timer/Counter1 для генерации прерывания с частотой 200Гц, которая используется для динамической индикации.  Timer/Counter1 инициализируется в функции SetupDisplay():

void SetupDisplay () {
  TCCR1A = 0<<WGM10;
  TCCR1B = 1<<WGM12 | 2<<CS10;  // Divide by 8
  OCR1A = 4999;                 // Compare match at 200Hz
  TIMSK1 = 1<<OCIE1A;           // Enable compare match interrupt
}

Процедура прерываения вызывает функцию DisplayNextDigit():

ISR(TIM1_COMPA_vect) {
  DisplayNextDigit();
}

Эта функция выводит следующую цифру, включая соответствующие сегменты индикатора, а затем подает единицу на анод соответствующего разряда индикатора:

void DisplayNextDigit () {
  DDRB = 0;                                    // All low
  digit = (digit+1) % ndigits;
  char segs = charArray[Buffer[digit]];
  DDRA = DDRA & 0x80;                          // All inputs
  // Set display segments
  if (dp == digit && dp != 2) DDRB = 1<<DDB2;
  PORTA = (PORTA & 0x80) | (~segs & 0x7F);     // 1 = low
  DDRA = (DDRA & 0x80) | (segs & 0x7F);        // 1 = output
  DDRB = DDRB | 1<<digit;                      // Current digit output
  PORTB = 1<<digit;                            // Current digit high
}

Старший бит PORTA маскирован для предтвращения помех с измеряемым сигналом.

Измерение времени разряда конденсатора

Основная программа запускается после нажатия кнопки Reset. Сначала она подает логическую единицу на PA7, чтобы зарядить конденсатор. Затем она переводит этот пин в состояние входа и вызывает функцию analogRead() для отслеживания падения напряжения.

Когда напряжение упадет до значения, указанного в переменное Target, программа расчитывает сопротивление нагрузки, основанной на постоянной времени RC по формуле:

half life = log(2) RC

для минимизации влияния падения напряжения на измеряемую схему была выбрана только половина от времени полуразряда, то есть когда напряжение падает на 1/√2 от начального значения, или примерно до 3.5В. В программе используется значение 29/41, которое является хорошим приближением величины 1/√2. Мы может просто удвоить это значение чтобы получить время полуразряда.

Затем расчитывается ток разряда при 5 вольтах, по формуле:

ток разряда = (5 x log(2) x 10-6) / t

где ток разряда в амперах, 5  - напряжение питания в вольтах, 10-6 - емкость конденсатора, , t - время полуразряда в секундах. Это дает:

  nA = 1732868 / Time;

где nA - ток разряда в  нA, а Time - время в миллисекундах.

Главный цикл программы:

void loop() {
  pinMode(7, OUTPUT);
  digitalWrite(7, HIGH);
  delay(500);
  pinMode(7, INPUT);
  unsigned long Start = millis(), Time, nA;
  unsigned int Initial = analogRead(7);
  unsigned int Target = (Initial * 29) / 41;
  do {
    Time = millis() - Start;
  } while (analogRead(7) > Target && Time < 100000);
  nA = 1732868 / Time;
  dp = 2;
  if (Time >= 100000) { Buffer[0] = Lo; Buffer[1] = Lo+1; Buffer[2] = Space; }
  else if (nA < 1000) { dp = 2; Display(nA); }
  else if (nA < 10000) { dp = 0; Display(nA/10); Buffer[2] = uA; }
  else if (nA < 100000) { dp = 2; Display(nA/100); Buffer[2] = uA; }
  else { Buffer[0] = Hi; Buffer[1] = Hi+1; Buffer[2] = Space; }
  pinMode(7, OUTPUT);
  digitalWrite(7, HIGH);
  for (;;);
}

Последние несколько строк программы предназначены для различных форматов отображения, описанных выше.

Компиляция

Программа скомпилирована в  Spence Konde's ATTiny Core. Выбираем опцию ATtiny24/44/84 в разделе ATTinyCore меню Board. Далее устанавливаем следующие параметры:

Chip: "ATtiny84"
Clock: "8 MHz (internal)"
B.O.D: "B.O.D. Disabled"
Pin Mapping: "Clockwise (like damellis core)"

По умолчанию ATtiny84 работает на частоте 1МГц. Выбираем Burn Bootloader чтобы установить фьюзы на 8MГц. Затем прошейте контроллер программатором для внутрисхемного программирования ( ISP (in-system programming)) к примеру, Sparkfun's Tiny AVR Programmer Board.

Полный текст программы наноамперметра: 

/* Nano Current Meter
*/

// Seven-segment definitions
const int charArrayLen = 17;
char charArray[] = {
//  ABCDEFG  Segments
  0b1111110, // 0
  0b0110000, // 1
  0b1101101, // 2
  0b1111001, // 3
  0b0110011, // 4
  0b1011011, // 5
  0b1011111, // 6
  0b1110000, // 7
  0b1111111, // 8
  0b1111011, // 9
  0b0000000, // 10  Space
  0b0000001, // 11  '-'
  0b0001110, // 12  'L'
  0b0011101, // 13  'o'
  0b0110111, // 14  'H'
  0b0000100, // 15  'i'
  0b0011100  // 16  'u'
};

const int Space = 10;
const int Dash = 11;
const int Lo = 12;
const int Hi = 14;
const int uA = 16;
const int ndigits = 3;
volatile int Buffer[] = {Dash, Dash, Dash};
char dp = 2;  // Decimal point position 2 (off) or 0 to 1
int digit = 0;

// Display multiplexer **********************************************

void DisplayNextDigit () {
  DDRB = 0;                                    // All low
  digit = (digit+1) % ndigits;
  char segs = charArray[Buffer[digit]];
  DDRA = DDRA & 0x80;                          // All inputs
  // Set display segments
  if (dp == digit && dp != 2) DDRB = 1<<DDB2;
  PORTA = (PORTA & 0x80) | (~segs & 0x7F);     // 1 = low
  DDRA = (DDRA & 0x80) | (segs & 0x7F);        // 1 = output
  DDRB = DDRB | 1<<digit;                      // Current digit output
  PORTB = 1<<digit;                            // Current digit high
}

// Display a three digit decimal number
void Display (int i) {
  for (int d=2; d>=0 ; d--) {
    Buffer[d]=i % 10;
    i = i / 10;
  }
}

// Set up Timer/Counter1 to multiplex the display
void SetupDisplay () {
  TCCR1A = 0<<WGM10;
  TCCR1B = 1<<WGM12 | 2<<CS10;  // Divide by 8
  OCR1A = 4999;                 // Compare match at 200Hz
  TIMSK1 = 1<<OCIE1A;           // Enable compare match interrupt
}

// Timer interrupt - multiplexes display
ISR(TIM1_COMPA_vect) {
  DisplayNextDigit();
}

// Setup **********************************************
  
void setup() {
  SetupDisplay();
}

void loop() {
  pinMode(7, OUTPUT);
  digitalWrite(7, HIGH);
  delay(500);
  pinMode(7, INPUT);
  unsigned long Start = millis(), Time, nA;
  unsigned int Initial = analogRead(7);
  unsigned int Target = (Initial * 29) / 41;
  do {
    Time = millis() - Start;
  } while (analogRead(7) > Target && Time < 100000);
  nA = 1732868 / Time;
  dp = 2;
  if (Time >= 100000) { Buffer[0] = Lo; Buffer[1] = Lo+1; Buffer[2] = Space; }
  else if (nA < 1000) { dp = 2; Display(nA); }
  else if (nA < 10000) { dp = 0; Display(nA/10); Buffer[2] = uA; }
  else if (nA < 100000) { dp = 2; Display(nA/100); Buffer[2] = uA; }
  else { Buffer[0] = Hi; Buffer[1] = Hi+1; Buffer[2] = Space; }
  pinMode(7, OUTPUT);
  digitalWrite(7, HIGH);
  for (;;);
}


Загрузка...