Пробник-частотомер - это портативный инструмент, предназначенный для отладки электронных цепей, предоставляя визуальную индикацию частоты или напряжения. Для периодической формы сигнала прибор производит измерение частоты в диапазоне от 1 Гц до 5 МГц и с точностью не хуже 0,3%. В режиме вольтметра пробник показывает измеренное значение напряжения.

Прибор собран на микроконтроллере ATtiny84A и питается от небольшого литиевого аккумулятора.

Введение

Самый очевидный способ реализовать частотомер - это подсчитать количество импульсов в течение одной секунды, тогда напрямую результатом измерения будет частота. Я называю его частотным методом. Недостатком этого метода является то, что для точного измерения низких частот требуется длительное время выборки.

Другой способ - измерить интервал между двумя импульсами входного сигнала, обратная величина дает частоту. Я называю его интервальным методом. Например, если интервал между импульсами составляет одну секунду, частота равна 1 Гц. Недостатком этого метода является то, что для высоких частот нужно очень точно измерять интервал.

Идеальное решение - использовать частотный метод для высоких частот и интервальный метод для низких частот, что является подходом, который я применил в этом пробнике. Ниже я объясню, как рассчитать наилучшую точку для переключения между режимами измерения.

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

Процессор

Я выбрал ATtiny84 для этого проекта, потому что он имеет 8-битный Таймер/Счетчик 0, который можно синхронизировать с внешним сигналом на T0, обеспечивая точный способ измерения внешней частоты. Он также имеет 16-битный Таймер/Счетчик 1 с захватом входных данных, который копирует содержимое счетчика в регистр ICP1 на переднем или заднем фронте входного сигнала. Это позволяет очень точно синхронизировать период сигнала.

Модуль USI микроконтроллера используется для управления OLED-дисплеем с I2C, который отображает показания в символах двойного размера с использованием символьного шрифта 6x8 пикселей.

Диапазон частот

Верхний предел частоты, которую можно измерить в частотном режиме, определяется максимальной частотой, которую можно измерять на входе T0. В описании сказано, что это clk/2,5, где clk - частота процессора. При напряжении питания 3 В максимальная частота процессора составляет около 12 МГц, что дает максимальную измеряемую частоту 4,8 МГц.

В интервальном режиме нижний предел частоты определяется тем, как долго пользователь готов ждать ответа. Я выбрал 1 Гц, чтобы показания обновлялись на дисплее раз в секунду.

Точность

В частотном режиме точность измерения частоты увеличивается с увеличением частоты, так как в выборке больше циклов выборки. В программе, описанной ниже, я использую Таймер/Счетчик 0 для подсчета циклов в прерывании от Таймер/Счетчик 1. Он синхронизируется с системной тактовой частотой 12 МГц, деленной на 16384 x 8, что дает прерывание 91,6 раза в секунду. Таким образом, точность измерения частоты составляет:

100 x 12000000 / (8 x 16384 x f) %

Например, при 100 кГц точность измерения составляет 0,09%.

В интервальном режиме точность уменьшается с частотой, поскольку между двумя переходами меньше циклов таймера. В программе используется захват по входу Таймера/Счетчика 1 для измерения интервала между двумя переходами входного сигнала. Таким образом, точность измерения частоты составляет:

100 x f / 12000000 %

Например, на частоте 10 кГц точность измерения составляет 0,083%.

Вот график, показывающий эти точки и общую точность каждого метода:

Наилучшая точность определяется местом пересечения линий, что достигается с помощью интервального режима для частот ниже 30 кГц и частотного режима выше 30 кГц, это дает точность не хуже примерно 0,3% по всем частотам.

Измеритель напряжения

Если на щупе есть постоянное напряжение, индикатор пробника отображает напряжение от 0 до 5 В с точностью до одной части от 1024, или 0,1%.

Напряжение считывается на аналоговом входе ADC1. АЦП использует внутренний источник опорного напряжения 1,1 В, поэтому используется входной делитель напряжения для приведения диапазона 0 - 5 В к диапазону 0 - 1,1 В.

Тестирование схемы

Чтобы протестировать пробник на высоких частотах, я использовал свой Programmable Signal Generator для генерации частот от 1 кГц до 5 МГц. Для более низких частот я использовал свой Tiny Function Generator для частот до 1 Гц, также проверил частоты по всему диапазону с помощью цифрового мультиметра.

Схема

Вход IN соответствует кончику щупа. Отдельный вывод от GND подключается к тестируемой GND цепи.

Вход пробника защищен от напряжения выше Vcc или отрицательного напряжения ниже GND резистором 220 Ом и двумя диодами Шоттки. Затем сигнал подается на вход T0(PA3) для частотного режима, вход ICP(PA7) для интервального режима и ADC1(PA1) через делитель напряжения для режима вольтметра. С 5 В на входе делитель напряжения дает 6800/(6800 + 24000) х 5 = 1,10 В на PA1, чтобы соответствовать диапазону 1,1 В, используемый в режиме вольтметра. Делитель напряжения разработан для согласования со входом АЦП, который оптимизирован для входного сопротивления 10 кОм или меньше.

При использовании модуля USI для обеспечения интерфейса I2C невозможно использовать внутренние подтягивающие резисторы на SCL и SDA, поэтому в схеме используются внешние на 4,7 кОм.

Единственные оставшиеся компоненты - это кварц на 12 МГц и кнопка для сброса процессора и вывода его из спящего режима.

Сборка

Сначала я протестировал конструкцию на макетной плате с использованием выводных компонентов. Для окончательной версии плата была спроектирована в Eagle и изготовлена в OSH Park, используя их сервис After Dark, в котором используется прозрачная паяльная маска на черной подложке и медные дорожки становятся видимыми.

ATtiny84A используется в корпусе SOIC, а резисторы, конденсаторы и диоды имеют размер 0805, поэтому их относительно легко припаять вручную. Размер кварца 5x3,2 мм. Дисплей представляет собой модуль OLED 128x32 I2C с драйвером SSD1306. Я использовал недорогой модуль с AliExpress. В качестве щупа используется обычная иголка для шитья.

Первоначально я планировал запитать схему от дискового элемента CR1225 на 3 В в держателе для поверхностного монтажа, но он не смог обеспечить достаточный ток. К счастью, на печатной плате были предусмотрены выводы для аккумулятора LiPo, поэтому я использовал элемент емкостью 40 мАч, который помещается в том же пространстве и крепится к плате с помощью двустороннего скотча:

Аккумулятор можно зарядить на месте с помощью зарядного устройства Li-Po. Чтобы избежать использования переключателя вкл/выкл, устройство автоматически переходит в спящий режим через 30 секунд, чтобы разбудить его необходимо нажать на кнопку.

Я использовал термофен Youyue 858D+ при температуре 250°C, чтобы припаять SMD-компоненты к задней части платы. Дисплей закреплен с помощью двустороннего скотча на передней части платы, а затем припаян с помощью четырех выводов, которые были обрезаны заподлицо с платой. Если нет термофена, вы сможете паять SMD-компоненты с небольшой осторожностью, используя паяльник с тонким наконечником.

Программа

Программа состоит из интерфейса дисплея для управления OLED-дисплеем, процедур для реализации трех режимов измерения и основного цикла, который выбирает, какой режим использовать для данного входного сигнала.

Интерфейс дисплея использует те же процедуры, что и мой предыдущий Tiny Function Generator, в котором использовался аналогичный I2C OLED-дисплей. Текст отображается с использованием набора символов 6x8 пикселей, но с удвоенным масштабом, чтобы получить эффективный размер символа 12x16 пикселей, с использованием процедуры сглаживания, описанной в статье Smooth Big Text.

Текущий режим определяется переменной Mode, которая имеет постоянное значение INTERVAL в интервальном режиме и FREQUENCY в частотном режиме. Схема запускается в частотном режиме.

Программа сначала настраивает Таймер/Счетчик 0, Таймер/Счетчик 1, сторожевой таймер и АЦП:

  // Устанавливаем 8-битный Таймер/Счетчик 0
  TCCR0A = 0 << WGM00;                          // Нормальный режим, счетчик остановлен
  TCCR0B = 0 << WGM02 | 0 << CS00;              // Счетчик остановлен
  OCR0A = 255;                                  // В регистре сравнения верхнее значение
  TIMSK0 = 0;                                   // Прерывания изначально запрещены
  //
  // Устанавливаем 16-битный Таймер/Счетчик 1
  TCCR1A = 0 << WGM10;                          // Счетчик остановлен
  TCCR1B = 1 << WGM12 | 1 << ICES1 | 0 << CS10; // Режим CTC, нарастающий фронт, счетчик остановлен
  TIMSK1 = 0;                                   // Прерывания изначально запрещены
  //
  // Устанавливаем прерывание от сторожевого таймера 1 Гц для обновления дисплея
  WDTCSR = 0 << WDIE | 3 << WDP0;               // Прерывание раз в 1/8 секунды
  //
  // Настраиваем ADC на использование ADC1(PA1)
  ADMUX = 2 << REFS0 | 1 << MUX0;               // Вход ADC1, внутренний ИОН на 1,1 В
  ADCSRA = 1 << ADEN | 0 << ADIE | 7 << ADPS0;  // Включаем АЦП, частота АЦП 93,75 кГц
}

Первоначально Таймеры/Счетчики остановлены, сторожевой таймер отключен и прерывания отключены.

Для запуска частотного или интервального режима программа вызывает GetFrequency() или GetInterval(), которые настраивают периферийные устройства соответствующим образом для каждого режима.

Частотный режим

Вызов GetFrequency() выполняет измерение в частотном режиме:

unsigned long GetFrequency () {
  Capture = false;
  FreqHigh = 0;
  TCNT0 = 0;
  TCNT1 = 0;
  OCR1A = 16383;                          // Делитель на 16384
  TIFR1 = 1<<OCF1A;                       // Очистить флаг сравнения
  TIMSK0 = 1<<OCIE0A;                     // Прерывание по сравнению от таймера 0
  TIMSK1 = 1<<OCIE1A;                     // Прерывание по сравнению от таймера 1
  GTCCR = 1;                              // Очищаем предделитель
  TCCR1B = TCCR1B | 2<<CS10;              // Запуск таймера 1, делитель на 8
  TCCR0B = TCCR0B | 7<<CS00;              // Запуск таймера 0, захват по нарастающему фронту
  while (!Capture);                       // Ждем окончания захвата
  TCCR1B = TCCR1B & ~(2<<CS10);           // Остановка таймера
  TCCR0B = TCCR0B & ~(7<<CS00);           // Остановка таймера
  return Count;
}

Сначала очищаем счетчики, разрешаем прерывания от Таймеров/Счетчиков и настраивает 8-битный Таймер/Счетчик 0 для синхронизации через входной сигнал на T0(PA3).

Чтобы получить 16-битное значение счетчика, генерируется прерывание сравнения при переполнении регистра Таймера/Счетчика 0 и инкрементируется переменная FreqHigh (мы не можем использовать более логичный вектор прерывания TIM0_OVF_vect, потому что он уже используется ядром Arduino):

ISR (TIM0_COMPA_vect) {
  FreqHigh ++;
}

Частота 16-битного Таймера/Счетчика 1 в 8 раз меньше системной частоты. При значении OCR1A, равном 16383, это дает прерывание сравнения каждые 16384 x 8 циклов или частоту 91,6 Гц.

Подпрограмма обслуживания прерываний отключает прерывания Таймера/Счетчика и устанавливает для переменной Count значение, полученное путем объединения регистра счетчика Таймера/Счетчика 0 и FreqHigh:

ISR(TIM1_COMPA_vect) {
  uint8_t temp = TCNT0;
  TIMSK1 = 0;                             // Запрещаем прерывание
  TIMSK0 = 0;                             // Запрещаем прерывание
  Count = (unsigned long)FreqHigh<

Фактическая частота в Гц определяется следующим образом:

(count * 46875)/512;

где 46875/512 - наилучшее приближение к 91,6 Гц.

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

void PlotFrequency (unsigned long c, int line) {
  unsigned long f = (c * 46875)/512;
  if (f >= 1000000) {
    PlotValue(f/100, line, Col, 4);
    PlotText(PSTR(" MHz"), line, Col+6*6*Scale);
  } else if (f >= 100000) {
    PlotValue(f/10, line, Col, 2);
    PlotText(PSTR(" kHz"), line, Col+6*6*Scale);
  } else if (f >= 10000) {
    PlotValue(f, line, Col, 3);
    PlotText(PSTR(" kHz"), line, Col+6*6*Scale);
  } else PlotText(PSTR("----------"), line, Col);
}

Интервальный режим

Вызов GetInterval() выполняет измерение в интервальном режиме:

unsigned long GetInterval () {
  Captures = 0;
  Count = 0;
  IntHigh = 0;
  TCNT1 = 0;
  OCR1A = 65535;                          // Делитель на 65536
  TCCR1B = TCCR1B | 1<<CS10;              // Запуск таймера, без предделителя
  TIFR1 = 1<

В этом режиме используется только Таймер/Счетчик 1. Сначала очищается счетный регистр, включается прерывания по захвату и переполнению Таймера/Счетчика и активируется счетчик, который будет синхронизироваться с системной тактовой частотой.

Прерывание по переполнению увеличивает переменную IntHigh:

ISR (TIM1_OVF_vect) {
  IntHigh ++;
}

Прерывание по захвату подсчитывает два захвата входных данных, а затем устанавливает переменную Count равной разнице между ними:

ISR(TIM1_CAPT_vect) {
  unsigned int temp = ICR1;
  Count0 = Count1;
  Count1 = (unsigned long)IntHigh<

Если на входе нет сигнала, подпрограмма GetInterval() заблокируется, поскольку захват входных данных не происходит. Таким образом, прерывание от сторожевого таймера на частоте 8 Гц используется для обратного отсчета переменной Timer:

ISR(WDT_vect) {
  WDTCSR = WDTCSR | 1<<WDIE;
  Timer--;
}

Подпрограмма DelaySecond() используется для генерации задержки в одну секунду и возврата из GetInterval() по истечении этого времени:

void DelaySecond () {
  WDTCSR = WDTCSR | 1<<WDIE;
  Timer = 8;
  while (Timer > 0);
  WDTCSR = WDTCSR & ~(0<<WDIE);
}

Режим вольтметра

Наконец, режим вольтметра просто использует АЦП для измерения напряжения на аналоговом входе ADC1:

unsigned long GetVoltage () { 
  ADCSRA = ADCSRA | 1<<ADSC;              // Старт преобразования
  while(ADCSRA & 1<<ADSC);                // Ждем завершения преобразования
  int low, high;
  low = ADCL;
  high = ADCH;
  return high<

Основной цикл

Основной цикл многократно снимает показания в текущем выбранном режиме, при необходимости переключает режим, если показание выходит за пределы диапазона для этого режима:

    if (mode == FREQUENCY) {
      c = GetFrequency();
      if (c > 320) {
        PlotFrequency(c, 1);
        Sleep = Timeout;
        DelaySecond();
      } else if (c == 0) mode = VOLTAGE;
      else mode = INTERVAL;
    } else if (mode == INTERVAL) {
      c = GetInterval();
      if (c > 390) {
        PlotInterval(c, 1);
        Sleep = Timeout;
      } else if (c == 0) mode = VOLTAGE;
      else mode = FREQUENCY;
    } else if (mode == VOLTAGE) {
      c = GetVoltage();
      PlotVoltage(c, 1);
      if (c < 40) Sleep--;
      DelaySecond();
      mode = FREQUENCY;
    }
  } while (Sleep > 0);

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

Режим сна

Чтобы исключить из схемы переключатель вкл/выкл, программа прерывается примерно через 30 секунд, если на щупе нет входного сигнала, а напряжение ниже 0,20 В. Затем происходит выключение дисплея и перевод процессора в спящий режим. Потребляемый ток в режиме сна составляет около 7,1 мкА, что обеспечивает срок службы батареи емкостью 40 мАч около года. Нажатие на кнопку сбрасывает процессор и пробуждает устройство.

Компилирование программы

Я скомпилировал программу с помощью Spence Konde's ATTiny Core. Для этого в Arduino IDE выберите вариант ATtiny24/44/84 под заголовком ATTinyCore в меню плат. Затем убедитесь, что следующие параметры установлены правильно:

Chip: "ATtiny84"
Clock: "12 MHz (external)"
B.O.D: Level: "B.O.D. Disabled"
Pin Mapping: "Clockwise (like damellis core)"


Файлы к статье "Пробник-частотомер на ATtiny84A"
Описание:

Исходный код(Си), схема и макет печатной платы Eagle

Размер файла: 39.08 KB Количество загрузок: 281 Скачать