6-канальный осциллограф на Arduino

Добавлено 25 сентября 2018 в 15:40
6-канальный осциллограф на Arduino. Вывод осциллограмм
6-канальный осциллограф на Arduino. Вывод осциллограмм

О проекте

Способ работы: (0) установить подстраиваемые переменные, (1) запустить скетч, (2) активировать плоттер последовательного порта.

Описание: Данный скетч имитирует 6-лучевой осциллограф. Ожидается, что входные сигналы, представленные на аналоговых выводах, будут находиться в диапазоне от 0 до 5 вольт с максимальной частотой 1 кГц. Подстраиваемые переменные объявляются в отдельном разделе. Эти переменные можно найти после операторов define. Можно выбрать количество входов 1-6, которые отображают напряжения на выводах A0-A5 соответственно. Незадействованные открытые аналоговые выводы выдают ложные напряжения.

Осциллограф работает в двух режимах: «непрерывный» (свободная работа) и «принудительный запуск» («ждущий», запуск развертки при выполнении критерия). Критерий (триггер) срабатывания выполняется, когда входной сигнал, считанный с A0, пересекает предопределенное напряжение запуска, в зависимости от того, будет ли это «фронт» или «спад», когда он пересекает это предопределенное напряжение. В «ждущем» режиме общее время развертки может быть установлено в миллисекундах. Начало запуска развертки указывается, когда синхронизирующая отметка доскакивает до уровня 5 В.

При развертке выборка на аналоговых выводах будет производиться каждые 'SampleInterval' миллисекунд. В нижней части графика синхронизирующие отметки (прямоугольный сигнал) будут переключаться каждый 10-ые 'SampleInterval' миллисекунд.

Встроенный светодиод (вывод 13) является индикатором состояния осциллографа: (1) включен, непрерывный режим или развертка в «ждущем» режиме, (2) мигает, запущен «ждущий» режим, ожидает срабатывания триггера, (3) выключен, все операции приостановленны (с помощью кнопки).

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

Опционально между цифровым выводом 12 и землей может быть подключена кнопка. При ее нажатии цифровая выборка и развертка останавливаются. Повторное нажатие кнопки возобновляет развертку (но с разрывом в графиках сигналов).

Порядок и цвета выводимых графиков следующие: метки времени (синий), уровень запуска (красный, если включен ждущий режим), аналоговые сигналы A0-A5, соответственно (многоцветные).

Код Oscilloscope.ino

#define ul unsigned long
#define armed true                                  // состояние запуска и кнопки
#define continuous true                             // режим развертки (непрерывный)
#define falling false                               // запуск по спаду
#define rising true                                 // запуск по фронту
#define triggered false                             // режим развертки (развертка стартует, когда происходит срабатывание)
#define sweeping false                              // развертка для TriggeredSweepInterval миллисекунд
#define superimposed true                           // сигналы накладываются на экране
#define channeled false                             // сигналы разделяются на каналы на экране

// настраиваемые переменные
int   Beams = 6;                                    // количество каналов чтения
bool  DisplayMode = channeled;                      // 'superimposed' или 'channeled'
ul    SampleInterval = 200;                         // единицы: миллисекунды * 10; 0.1 <= SampleInterval
bool  SweepMode = triggered;                        // 'continuous' или 'triggered'
ul    TriggeredSweepInterval = 40000;               // общее время развертки, единицы: секунды * 10,000
float TriggerDirection = rising;                    // 'rising' или 'falling'
float TriggerVolts = 3.5;                           // постоянное напряжение запуска; 0 <= TriggerVolts <= 5

// переменные, контролируемые прерыванием
ul    LastSample = -1;                              // первоначально последнего отсчета нет
bool  LED = HIGH;
int   BlinkCount = 0;
ul    SweepCount = 0;                               // отсчитывает интервал развертки Sweep Interval
bool  Tick = false;                                 // устанавливает в true, когда TickCount = SampleRate
ul    TickCount = 0;                                // отсчитывает SampleRate
bool  TimingMark;                                   // переключается каждый 10-ый 'Sample Interval'
ul    TimingMarkCount = 0;                          // отсчитывает SampleInterval * 10
bool  TriggerState;                                 // 'armed' или 'sweeping'
bool  TriggerOnset = false;                         // отмечает первую метку после срабатывания триггера

// переменные функции цикла
float ChannelFloor;
float ChannelHeight;
bool  freeze = false;                               // true: стоп; false: запуск
int   PBPin = 12;                                   // кнопка между землей и выводом 12 (опционально)
int   PBLastState = HIGH;                           // LOW (нажата) или HIGH (отпущена)
int   PBVal;                                        // обратная логика, кнопка подтянута к земле
float ChannelScale;                                 // пропорция отображения сигнала
float TriggerDisplay;                               // вертикальное положение напряжения запуска (триггера)
int   TriggerLevel;                                 // рассчитывается из 'TriggerVolts'
float Value;

void interruptSetup() 
{
  noInterrupts();                                   // генерировать прерывание каждые 0,1 миллисекунды
  TCCR2A = 2;                                       // очистить таймер при сравнении, OCR2
  TCCR2B = 2;                                       // 16,000,000 Гц/8=2,000,000 Гц; T2 выдает импульсы каждые 0.0005 мс
  OCR2A = 199;                                      // прерывание = 0.0005*200 = 0.1 мс
  TIMSK2 = 2;                                       // установить обработчик на вектор COMPA
  interrupts();
}

ISR(TIMER2_COMPA_vect)                              // прерывание каждые 0,1 миллисекунды
{
  if (TickCount < SampleInterval) 
  {
    TickCount++;
    BlinkCount++;
    if (BlinkCount >= 500) 
    {
      BlinkCount = 0.0;
      LED = !LED;                                   // переключать светодиод каждые 50 мс
    }
  }
  else                                              // 'SampleInterval' истек
  {
    Tick = true;
    TickCount = 0;
    TimingMarkCount++;                              // обновить временную метку
    if (TimingMarkCount >= 10)                      // произошел 10-ый 'SampleInterval'
    {
      TimingMark = !TimingMark;
      TimingMarkCount = 0;
    }
    if (SweepMode == triggered) 
    {
      if (TriggerState == sweeping)                // развертка, обновить время развертки
      {
        SweepCount +=  SampleInterval;
        if (SweepCount >= TriggeredSweepInterval)  // развертка завершена
        {
          TriggerState = armed;
          LastSample = -1;
        }
      }
      else                                         // включен, ждать триггера
      {
        Value = analogRead(A0);
        if (LastSample > 0 and
            ((TriggerDirection == rising and Value >= TriggerLevel and LastSample < TriggerLevel) or
             (TriggerDirection == falling and Value <= TriggerLevel and LastSample > TriggerLevel))) 
        {
          TriggerState = sweeping;                                     // триггер сработал
          SweepCount = 0;
          TriggerOnset = true;
          TimingMarkCount = 0;
          TimingMark = true;
        }
        LastSample = Value;
      }
    }
  }
}

void setup() 
{
  pinMode (LED_BUILTIN, OUTPUT);
  pinMode(PBPin, INPUT);                            // подлкючен к кнопке, замыкающей на землю 
  digitalWrite(PBPin, HIGH);                        // включить подтягивающий резистор на выводе кнопки
  Serial.begin(115200);
  if (SweepMode == continuous) 
  {
    TriggerState = sweeping;
  }
  else 
  {
    TriggerState = armed;
  }
  TriggerLevel = (TriggerVolts / 5.0 ) * 1023;
  ChannelHeight = 5.0 / Beams;
  ChannelScale = 5.0 / 1024.0 / Beams;
  TriggerDisplay = TriggerVolts * ChannelScale + 5.0 - ChannelHeight;
  interruptSetup();
}

void loop() 
{
  if (freeze) 
  {
    digitalWrite(LED_BUILTIN, LOW);
  }
  else if (TriggerState == armed) 
  {
    digitalWrite(LED_BUILTIN, LED);
  }
  else  
  {
    digitalWrite(LED_BUILTIN, HIGH);
  }
  PBVal = digitalRead(PBPin);
  if (PBVal == LOW and PBLastState == HIGH)        // спад
  {
    freeze = !freeze;
    delay (2);                                     // игнорировать дребезг контактов
  }
  PBLastState = PBVal;
  if  (!freeze and TriggerState  == sweeping) 
  {
    if (Tick)                                      // выполнить выборку, если Sample Interval миллисекунд истекли
    {
      if (TimingMark)                              // отобразить временные метки и напряжение запуска, если оно есть
      {
        if (TriggerOnset) 
        {
          Serial.print(5.0);
          TriggerOnset = false;
        }
        else 
		{
          Serial.print(0.1);
        }
      }
      else 
	  {
        Serial.print(0.0);
      }
      Serial.print(" ");
      
      // отобразить уровень запуска, если он применяется
      ChannelFloor = 5.0 - ChannelHeight;
      if (SweepMode == triggered) 
      {
        Serial.print(TriggerLevel * ChannelScale + ChannelFloor);
        Serial.print (" ");
      }
      
      // выполнить выборку 1-6 аналоговых сигналов и отобразить их
      for (int AnalogPin = 0; AnalogPin <= Beams - 1; AnalogPin++)      
      {
        Value = analogRead(AnalogPin);
        Value = Value * ChannelScale + ChannelFloor;
        Serial.print(Value);
        Serial.print(" ");
        ChannelFloor -= ChannelHeight;
      }
      Tick = false;
      Serial.println("");
    }
  }
}

Схема

6-канальный осциллограф на Arduino. Схема
6-канальный осциллограф на Arduino. Схема

Теги

ArduinoArduino IDEОсциллограммаОсциллограф

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.