Пультоскоп на Arduino 27МГц!!!

fly245
fly245 аватар
Offline
Зарегистрирован: 25.08.2013

Alex-Bee пишет:

Andry Smart, если собираетесь "плагиатить" у плагиатора, то вот ещё "его" программатор к "его" осциллоскопу, за 13$...
Покупаете на Али готовый мини переходник и припаиваете к нему, от туда же (с Али), пружинные контакты. Сэкономите около 10$, как минимум.

Месяц уже жду эти подпружиненные контакты с Али )

Andry Smart
Offline
Зарегистрирован: 06.09.2016

спасибо огромное. я так и не знал как это на али искать. как оно называется.

 

плагиатить у автора мне не интересно. я пытаюсь реализовать пробник автоэлектрика с функцией осциллографа. собственно медленный осциллограф для этиъ целей и нужен.

думаю такой трехкнопочный джой и кнопка . не охота на пробнике много кнопок лепить не удобно очень.

 

уже четыре варианта платы и кнопок и функций. но ни одну до ума еще не довел.

Andry Smart
Offline
Зарегистрирован: 06.09.2016

удалил дубль.... достали эти глюки...

Alex-Bee
Offline
Зарегистрирован: 13.03.2020

Думаю, лучше паять такой трипод с обратной стороны, от подводящих к нему проводников на плате: рассверливаешь отверстие 1,5-2мм (может и больше) и со стороны проводника заливаешь это отверстие припоем, пока он не прихватит контакты трипода.
Иначе - только столик с подогревом, как для пайки светодиодов в LED лампах. Фен на нём, скорее всего, всю пластмассу поплавит.

Andry Smart
Offline
Зарегистрирован: 06.09.2016

думаю это если выпаивать то проблема а запаивать можно и феном. секундное дело

smokok
smokok аватар
Offline
Зарегистрирован: 08.06.2018

Andry Smart пишет:

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

Смотри тут, КЛАЦ. Или ищи автора сообщение с его ссылкой и оригиналом.

Andry Smart
Offline
Зарегистрирован: 06.09.2016

как искать? как хоть файл называется? я же не могу их все перепробовать

seri0shka
seri0shka аватар
Offline
Зарегистрирован: 19.11.2018

Всё-таки удалось запустить LCD от Nokia1202 (1230) через аппаратный USART в режиме SPI. Как я уже писал раньше, этот режим позволяет отправку 16 бит подряд (2 байта). А неделю назад в поисках информации наткнулся на одном из англоязычных  сайтов на упоминание о том, что контроллер дисплея гипотетически может игнорировать лишние биты. Дисплею нужны 9 бит данных (первый бит обозначает тип- данные или команду). Мы можем отправить 16 бит вместо 9. Мои эксперименты показали, что дисплей может игнорировать только 3-4 лишних байта, точно не помню. В то же время данные принимаются дисплеем только при низком уровне на входе CS дисплея. Обычно низкий уровень устанавливают единожды при инициализации дисплея и больше не меняют. Но ничто не мешает менять его при передаче каждой команды или данных. Вот только смена уровня с низкого на высокий должна произойти не раньше 9-го и не позже 12-го бита. После того, как контроллер получил команду на отправку двух байт, он начинает передачу аппаратно, то есть до окончания передачи он параллельно может обрабатывать и выполнять другие команды. Вот здесь то и можно с помощью простой задержки вставить команду на смену уровня на управляющем выводе в нужный момент а именно через 18 тактов. Так как чтение и выполнение команды тоже занимает время, то задержка сокращается до 12-15 тактов, что чудным образом совпадает с выдержкой в 1 микросекунду.

Подключение дисплея железно привязано к конкретным выводам: Data - 1, CLK - 4, CS - 0 (последний можно поменять на любой другой). Сброс дисплея можно посадить на плюс питания, но у меня при проверке сидел на 12 выводе (первая строка кода).

Вот так выглядит тестовый сетч, smokok, попробуй на своём 1230.

#define RES 12

//================================================FONT
const char font[] = {
  0x3e, 0x51, 0x49, 0x45, 0x3e ,  // 33   0
  0x00, 0x42, 0x7f, 0x40, 0x00 ,  // 34   1
  0x42, 0x61, 0x51, 0x49, 0x46 ,  // 35   2
  0x21, 0x41, 0x45, 0x4b, 0x31 ,  // 36   3
  0x18, 0x14, 0x12, 0x7f, 0x10 ,  // 37   4
  0x27, 0x45, 0x45, 0x45, 0x39 ,  // 38   5
  0x3c, 0x4a, 0x49, 0x49, 0x30 ,  // 39   6
  0x01, 0x71, 0x09, 0x05, 0x03 ,  // 40   7
  0x36, 0x49, 0x49, 0x49, 0x36 ,  // 41   8
  0x06, 0x49, 0x49, 0x29, 0x1e ,  // 42   9
  0x7e, 0x11, 0x11, 0x11, 0x7e ,  // 43   A
  0x7f, 0x49, 0x49, 0x49, 0x36 ,  // 44   B
  0x3e, 0x41, 0x41, 0x41, 0x22 ,  // 45   C
  0x7f, 0x41, 0x41, 0x22, 0x1c ,  // 46   D
  0x7f, 0x49, 0x49, 0x49, 0x41 ,  // 47   E
  0x7f, 0x09, 0x09, 0x09, 0x01 ,  // 48   F
  0x3e, 0x41, 0x49, 0x49, 0x7a ,  // 49   G
  0x7f, 0x08, 0x08, 0x08, 0x7f ,  // 50   H
  0x00, 0x41, 0x7f, 0x41, 0x00 ,  // 51   I
  0x20, 0x40, 0x41, 0x3f, 0x01 ,  // 52   J
  0x7f, 0x08, 0x14, 0x22, 0x41 ,  // 53   K
  0x7f, 0x40, 0x40, 0x40, 0x40 ,  // 54   L
  0x7f, 0x02, 0x0c, 0x02, 0x7f ,  // 55   M
  0x7f, 0x04, 0x08, 0x10, 0x7f ,  // 56   N
  0x3e, 0x41, 0x41, 0x41, 0x3e ,  // 57   O
  0x7f, 0x09, 0x09, 0x09, 0x06 ,  // 58   P
  0x3e, 0x41, 0x51, 0x21, 0x5e ,  // 59   Q
  0x7f, 0x09, 0x19, 0x29, 0x46 ,  // 60   R
  0x46, 0x49, 0x49, 0x49, 0x31 ,  // 61   S
  0x01, 0x01, 0x7f, 0x01, 0x01 ,  // 62   T
  0x3f, 0x40, 0x40, 0x40, 0x3f ,  // 63   U
  0x1f, 0x20, 0x40, 0x20, 0x1f ,  // 64   V
  0x3f, 0x40, 0x38, 0x40, 0x3f ,  // 65   W
  0x63, 0x14, 0x08, 0x14, 0x63 ,  // 66   X
  0x07, 0x08, 0x70, 0x08, 0x07 ,  // 67   Y
  0x61, 0x51, 0x49, 0x45, 0x43 ,  // 68   Z
  0x00, 0x00, 0x00, 0x00, 0x00 ,  // 32   space
};

void setup() {
  bitWrite(PORTD, 0, 1);
  bitWrite(DDRD, 0, 1);
  UBRR0 = 0x0000;
  bitWrite(DDRD, 4, 1);//DDRD | = _BV (PD4); // Установка вывода порта XCK1 в качестве выхода, включает основной режим USART SPI (этот вывод для ATmega1284p)
  UCSR0C = _BV (UMSEL01) | _BV (UMSEL00) | _BV (UCSZ00) | _BV (UCPOL0);
  // Установить режим работы USART SPI и режим данных SPI 1,1. UCPHA1 = UCSZ10
  UCSR0B = _BV (TXEN0); // Включить передатчик. Включите Tx (также отключите Rx, а остальные биты разрешения прерываний тоже установите на 0).
  // Установить скорость передачи. ВАЖНО: Скорость передачи данных должна быть установлена ??после включения передатчика.
  UBRR0 = 0x0000; // где максимальная скорость FCPU / 2 = 0x0000

  pinMode(RES,   OUTPUT);
  digitalWrite(RES, 1);
  delay(150);
  SendByte(0, 0xE2);//reset
  Inicialize();
  delay(80);
}

void loop() {
  Clear_LCD__();
  drawChar (0, 0, 0);
  delay(300);
  Clear_LCD__();
  drawChar (5, 1, 1);
  delay(300);
    Clear_LCD__();
  drawChar (10, 2, 2);
  delay(300);
  Clear_LCD__();
  drawChar (15, 3, 3);
  delay(300);
    Clear_LCD__();
  drawChar (20, 4, 4);
  delay(300);
  Clear_LCD__();
  drawChar (25, 5, 5);
  delay(300);
    Clear_LCD__();
  drawChar (30, 6, 6);
  delay(300);
  Clear_LCD__();
  drawChar (35, 7, 7);
  delay(300);
}

//===========================Инициализация дисплея=======================
void Inicialize() {
  SendByte(0, 0x2F); // Power control set(charge pump on/off)
  SendByte(0, 0xA4); // Normal Display Mode
  SendByte(0, 0xAF); // AF экран вкл / AE выкл
  SendByte(0, 0x40);
}

//===========================Отправка 9 байт=============================
void SendByte(byte mode, byte c) {     
  UCSR0A = _BV (TXC0); // Сбросить флаг завершения передачи, все остальные биты должны быть записаны в 0.
  bitWrite(PORTD, 0, 0); // Потяните вниз CS, чтобы выбрать устройство SPI.
  UDR0 = (c + (mode << 8)) >> 1; // Начать передачу первого байта.
  UDR0 = c << 7; // Продолжаем передачу вторым байтом.    
  delayMicroseconds(1); // asm volatile ( "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n" "nop\n"  );
  bitWrite(PORTD, 0, 1); // Потяните CS вверх, чтобы отменить выбор устройства SPI.
  while (! (UCSR0A & _BV (TXC0))); // Проверьте, что мы закончили, ожидая флага завершения передачи.
}     

//===========================Очистка дисплея=============================
void Clear_LCD__() {
  for (byte y = 0; y < 9; y++) {
    SendByte(0, 0xB0 | y);
    SendByte(0, 0x00);
    SendByte(0, 0x10);
    for (byte col = 0; col < 96; col++) {
      SendByte(1, 0);
    }
    SendByte(0, 0x40);
  }           
}

//===========================Нарисовать букву============================
void drawChar (byte x, byte y, byte z) { // 0-95, 0-8, 0-255
  SendByte(0, 0xB0 | y);
  if (x > 91) return;
  byte q = x >> 4; // q = x/16;
  SendByte(0, q + 16); // +32
  SendByte(0, x - (q * 16)); // +2
  for (byte c = 0; c < 5; c++) {
    SendByte(1, font[c + z * 5]);
  }
  SendByte(1, 0);
}

//===========================Нарисовать колонку==========================
void drawColumn (byte x, byte y, byte z) {
  SendByte(0, 0xB0 | y);
  if (x > 91) return;
  byte q = x >> 4; // q = x/16;
  SendByte(0, q + 16); // +32
  SendByte(0, x - (q * 16)); // +2
  SendByte(1, z);
}

Строку 106 ((while (! (UCSR0A...) можно попробовать закомментировать, у меня при этом работает нормально, зато цикл полной очистки дисплея сокращается с 3,9 миллисекунд сокращается до 2,9 миллисекунд.

Вот так это дело выглядит на анализаторе, частота тактирования 8 МГц, сверху вниз Data, CS, CLK:

А вот так с закомментированной строкой, обратите внимание на две красные точки справа, всё на грани, довольно рискованно, но работает.

progrik, посмотри void SendByte, там как-то лохмато, можно причесать?

Dr_midon
Offline
Зарегистрирован: 05.07.2020

Нашел видео с созданием похожего устройства с исходниками

https://www.youtube.com/watch?v=c-ic0UaAq-A

Схема и код
http://easytechlearn.com/2020/06/14/how-to-make-mini-oscilloscope/

smokok
smokok аватар
Offline
Зарегистрирован: 08.06.2018

Вот ещё от японца, ССЫЛКА. Код и библа тут. Включайте переводчик. Что то стал популярен этот экран малыш))).

Alex-Bee
Offline
Зарегистрирован: 13.03.2020




Надеюсь он меня простит за это :), вдруг кому схема понадобится.
 

fly245
fly245 аватар
Offline
Зарегистрирован: 25.08.2013

Диодная развязка на кнопках?ХМ..

Alex-Bee
Offline
Зарегистрирован: 13.03.2020

А что вас удивляет? Наверное, по этому японская техника работает долго... потому что все нюансы учитываются а не только "авось"...
Процитирую то, что уже было сказано:
"...у любого МК есть определенный максимальный ток , который может проходить через вывод поэтому можно (а точнее нужно) защищаться от перегрузки и особенно от кз!
...это можно сделать диодами, тем самым исключить вероятность кз или резисторами - ограничив максимальный проходящий ток ниже допустимого (например 10-100к на каждую ногу).
...ну а если по правильному диод+резистор будет более верным, это и защита от переполюсовки и ограничение максимального тока."

Novice User
Offline
Зарегистрирован: 25.09.2017

Там заведено прерывание от кнопок на INT2,опрос кнопок в обработчике прерывания pin2IRQ

seri0shka
seri0shka аватар
Offline
Зарегистрирован: 19.11.2018

Про прерывание понятно, мне больше интересно, какое назначение у ноги 12?

serhiy58
Offline
Зарегистрирован: 19.06.2019

Думаю это аттенюатор 1/10, - 12 нога подтягивается к земе и коэффициент деления входного делителя увеличивается в 10 раз...(510К/120К или 12К)

Alex-Bee
Offline
Зарегистрирован: 13.03.2020

Раз пошла такая жара, то наверное и код будет интересен.

Для SH1106:

/*
    PMO-RP1 V2.0 for SH1106
    20200625_OLEDoscilloscopeSh1106_V200E.ino
    sketchЃF21206byteЃAlocal variable:217byte free
    Jun.25 2020 by radiopench http://radiopench.blog96.fc2.com/
*/

#include <Wire.h>
#include <Adafruit_GFX.h>
//#include <Adafruit_SSD1306.h>
#include <Adafruit_SH1106.h>            // https://github.com/wonho-maker/Adafruit_SH1106
#include <EEPROM.h>

#define SCREEN_WIDTH 128                // OLED display width
#define SCREEN_HEIGHT 64                // OLED display height
#define REC_LENG 200                    // size of wave data buffer
#define MIN_TRIG_SWING 5                // minimum trigger swing.(Display "Unsync" if swing smaller than this value

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     -1      // Reset pin # (or -1 if sharing Arduino reset pin)
//Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);   // device name is oled
Adafruit_SH1106 oled(OLED_RESET);        // use this when SH1106

// Range name table (those are stored in flash memory)
const char vRangeName[10][5] PROGMEM = {"A50V", "A 5V", " 50V", " 20V", " 10V", "  5V", "  2V", "  1V", "0.5V", "0.2V"}; // Vertical display character (number of characters including \ 0 is required)
const char * const vstring_table[] PROGMEM = {vRangeName[0], vRangeName[1], vRangeName[2], vRangeName[3], vRangeName[4], vRangeName[5], vRangeName[6], vRangeName[7], vRangeName[8], vRangeName[9]};
const char hRangeName[10][6] PROGMEM = {"200ms", "100ms", " 50ms", " 20ms", " 10ms", "  5ms", "  2ms", "  1ms", "500us", "200us"};  //  Hrizontal display characters
const char * const hstring_table[] PROGMEM = {hRangeName[0], hRangeName[1], hRangeName[2], hRangeName[3], hRangeName[4], hRangeName[5], hRangeName[6], hRangeName[7], hRangeName[8], hRangeName[9]};
const PROGMEM float hRangeValue[] = { 0.2, 0.1, 0.05, 0.02, 0.01, 0.005, 0.002, 0.001, 0.5e-3, 0.2e-3}; // horizontal range value in second. ( = 25pix on screen)

int waveBuff[REC_LENG];        // wave form buffer (RAM remaining capacity is barely)
char chrBuff[8];               // display string buffer
char hScale[] = "xxxAs";       // horizontal scale character
char vScale[] = "xxxx";        // vartical scale

float lsb5V = 0.00566826;      // sensivity coefficient of 5V range. std=0.00563965 1.1*630/(1024*120)
float lsb50V = 0.05243212;     // sensivity coefficient of 50V range. std=0.0512898 1.1*520.91/(1024*10.91)

volatile int vRange;           // V-range number 0:A50V,  1:A 5V,  2:50V,  3:20V,  4:10V,  5:5V,  6:2V,  7:1V,  8:0.5V,  9:0.2V
volatile int hRange;           // H-range nubmer 0:200ms, 1:100ms, 2:50ms, 3:20ms, 4:10ms, 5:5ms, 6;2ms, 7:1ms, 8:500us, 9;200us
volatile int trigD;            // trigger slope flag,     0:positive 1:negative
volatile int scopeP;           // operation scope position number. 0:Veratical, 1:Hrizontal, 2:Trigger slope
volatile boolean hold = false; // hold flag
volatile boolean switchPushed = false; // flag of switch pusshed !
volatile int saveTimer;        // remaining time for saving EEPROM
int timeExec;                  // approx. execution time of current range setting (ms)

int dataMin;                   // buffer minimum value (smallest=0)
int dataMax;                   //        maximum value (largest=1023)
int dataAve;                   // 10 x average value (use 10x value to keep accuracy. so, max=10230)
int rangeMax;                  // buffer value to graph full swing
int rangeMin;                  // buffer value of graph botto
int rangeMaxDisp;              // display value of max. (100x value)
int rangeMinDisp;              // display value if min.
int trigP;                     // trigger position pointer on data buffer
boolean trigSync;              // flag of trigger detected
int att10x;                    // 10x attenetor ON (effective when 1)

float waveFreq;                // frequency (Hz)
float waveDuty;                // duty ratio (%)

void setup() {
  pinMode(2, INPUT_PULLUP);             // button pussed interrupt (int.0 IRQ)
  pinMode(8, INPUT_PULLUP);             // Select button
  pinMode(9, INPUT_PULLUP);             // Up
  pinMode(10, INPUT_PULLUP);            // Down
  pinMode(11, INPUT_PULLUP);            // Hold
  pinMode(12, INPUT);                   // 1/10 attenuator(Off=High-Z, Enable=Output Low)
  pinMode(13, OUTPUT);                  // LED

// oled.begin(SSD1306_SWITCHCAPVCC, 0x3C) { // select 3C or 3D (set your OLED I2C address)
  oled.begin(SH1106_SWITCHCAPVCC, 0x3C);  // use this when SH1106 

  auxFunctions();                       // Voltage measure (never return)
  loadEEPROM();                         // read last settings from EEPROM
  analogReference(INTERNAL);            // ADC full scale = 1.1V
  attachInterrupt(0, pin2IRQ, FALLING); // activate IRQ at falling edge mode
  startScreen();                        // display start message
}

void loop() {
  setConditions();                      // set measurment conditions
  digitalWrite(13, HIGH);               // flash LED
  readWave();                           // read wave form and store into buffer memory
  digitalWrite(13, LOW);                // stop LED
  setConditions();                      // set measurment conditions again (reflect change during measure)
  dataAnalize();                        // analize data
  writeCommonImage();                   // write fixed screen image (2.6ms)
  plotData();                           // plot waveform (10-18ms)
  dispInf();                            // display information (6.5-8.5ms)
  oled.display();                       // send screen buffer to OLED (37ms)
  saveEEPROM();                         // save settings to EEPROM if necessary
  while (hold == true) {                // wait if Hold flag ON
    dispHold();
    delay(10);
  }                                     // loop cycle speed = 60-470ms (buffer size = 200)
}

void setConditions() {           // measuring condition setting
  // get range name from PROGMEM
  strcpy_P(hScale, (char*)pgm_read_word(&(hstring_table[hRange])));  // H range name
  strcpy_P(vScale, (char*)pgm_read_word(&(vstring_table[vRange])));  // V range name

  switch (vRange) {              // setting of Vrange
    case 0: {                    // Auto50V range
        att10x = 1;              // use input attenuator
        break;
      }
    case 1: {                    // Auto 5V range
        att10x = 0;              // no attenuator
        break;
      }
    case 2: {                    // 50V range
        rangeMax = 50 / lsb50V;  // set full scale pixcel count number
        rangeMaxDisp = 5000;     // vartical scale (set100x value)
        rangeMin = 0;
        rangeMinDisp = 0;
        att10x = 1;              // use input attenuator
        break;
      }
    case 3: {                    // 20V range
        rangeMax = 20 / lsb50V;  // set full scale pixcel count number
        rangeMaxDisp = 2000;
        rangeMin = 0;
        rangeMinDisp = 0;
        att10x = 1;              // use input attenuator
        break;
      }
    case 4: {                    // 10V range
        rangeMax = 10 / lsb50V;  // set full scale pixcel count number
        rangeMaxDisp = 1000;
        rangeMin = 0;
        rangeMinDisp = 0;
        att10x = 1;              // use input attenuator
        break;
      }
    case 5: {                    // 5V range
        rangeMax = 5 / lsb5V;    // set full scale pixcel count number
        rangeMaxDisp = 500;
        rangeMin = 0;
        rangeMinDisp = 0;
        att10x = 0;              // no input attenuator
        break;
      }
    case 6: {                    // 2V range
        rangeMax = 2 / lsb5V;    // set full scale pixcel count number
        rangeMaxDisp = 200;
        rangeMin = 0;
        rangeMinDisp = 0;
        att10x = 0;              // no input attenuator
        break;
      }
    case 7: {                    // 1V range
        rangeMax = 1 / lsb5V;    // set full scale pixcel count number
        rangeMaxDisp = 100;
        rangeMin = 0;
        rangeMinDisp = 0;
        att10x = 0;              // no input attenuator
        break;
      }
    case 8: {                    // 0.5V range
        rangeMax = 0.5 / lsb5V;  // set full scale pixcel count number
        rangeMaxDisp = 50;
        rangeMin = 0;
        rangeMinDisp = 0;
        att10x = 0;              // no input attenuator
        break;
      }
    case 9: {                    // 0.5V range
        rangeMax = 0.2 / lsb5V;  // set full scale pixcel count number
        rangeMaxDisp = 20;
        rangeMin = 0;
        rangeMinDisp = 0;
        att10x = 0;              // no input attenuator
        break;
      }
  }
}

void writeCommonImage() {                 // Common screen image drawing
  oled.clearDisplay();                    // erase all(0.4ms)
  oled.setTextColor(WHITE);               // write in white character
  oled.setCursor(86, 0);                  // Start at top-left corner
  oled.println(F("av    V"));             // 1-st line fixed characters
  oled.drawFastVLine(26, 9, 55, WHITE);   // left vartical line
  oled.drawFastVLine(127, 9, 3, WHITE);   // right vrtical line up
  oled.drawFastVLine(127, 61, 3, WHITE);  // right vrtical line bottom

  oled.drawFastHLine(24, 9, 7, WHITE);    // Max value auxiliary mark
  oled.drawFastHLine(24, 36, 2, WHITE);
  oled.drawFastHLine(24, 63, 7, WHITE);

  oled.drawFastHLine(51, 9, 3, WHITE);    // Max value auxiliary mark
  oled.drawFastHLine(51, 63, 3, WHITE);

  oled.drawFastHLine(76, 9, 3, WHITE);    // Max value auxiliary mark
  oled.drawFastHLine(76, 63, 3, WHITE);

  oled.drawFastHLine(101, 9, 3, WHITE);   // Max value auxiliary mark
  oled.drawFastHLine(101, 63, 3, WHITE);

  oled.drawFastHLine(123, 9, 5, WHITE);   // right side Max value auxiliary mark
  oled.drawFastHLine(123, 63, 5, WHITE);

  for (int x = 26; x <= 128; x += 5) {
    oled.drawFastHLine(x, 36, 2, WHITE);  // Draw the center line (horizontal line) with a dotted line
  }
  for (int x = (127 - 25); x > 30; x -= 25) {
    for (int y = 10; y < 63; y += 5) {
      oled.drawFastVLine(x, y, 2, WHITE); // Draw 3 vertical lines with dotted lines
    }
  }
}

void readWave() {                            // Record waveform to memory array
  if (att10x == 1) {                         // if 1/10 attenuator required
    pinMode(12, OUTPUT);                     // assign attenuator controle pin to OUTPUT,
    digitalWrite(12, LOW);                   // and output LOW (output 0V)
  } else {                                   // if not required
    pinMode(12, INPUT);                      // assign the pin input (Hi-z)
  }
  switchPushed = false;                      // Clear switch operation flag

  switch (hRange) {                          // set recording conditions in accordance with the range number
    case 0: {                                // 200ms range
        timeExec = 1600 + 60;                // Approximate execution time(ms) Used for countdown until saving to EEPROM
        ADCSRA = ADCSRA & 0xf8;              // clear bottom 3bit
        ADCSRA = ADCSRA | 0x07;              // dividing ratio = 128 (default of ArduinoЃj
        for (int i = 0; i < REC_LENG; i++) { // up to rec buffer size
          waveBuff[i] = analogRead(0);       // read and save approx 112us
          delayMicroseconds(7888);           // timing adjustment
          if (switchPushed == true) {        // if any switch touched
            switchPushed = false;
            break;                           // abandon record(this improve response)
          }
        }
        break;
      }
    case 1: {                                // 100ms range
        timeExec = 800 + 60;                 // Approximate execution time(ms) Used for countdown until saving to EEPROM
        ADCSRA = ADCSRA & 0xf8;              // clear bottom 3bit
        ADCSRA = ADCSRA | 0x07;              // dividing ratio = 128 (default of ArduinoЃj
        for (int i = 0; i < REC_LENG; i++) {  // up to rec buffer size
          waveBuff[i] = analogRead(0);       // read and save approx 112us
          // delayMicroseconds(3888);           // timing adjustmet
          delayMicroseconds(3860);           // timing adjustmet tuned
          if (switchPushed == true) {        // if any switch touched
            switchPushed = false;
            break;                           // abandon record(this improve response)
          }
        }
        break;
      }
    case 2: {                                // 50ms range
        timeExec = 400 + 60;                 // Approximate execution time(ms)
        ADCSRA = ADCSRA & 0xf8;              // clear bottom 3bit
        ADCSRA = ADCSRA | 0x07;              // dividing ratio = 128 (default of ArduinoЃj
        for (int i = 0; i < REC_LENG; i++) { // up to rec buffer size
          waveBuff[i] = analogRead(0);       // read and save approx 112us
          // delayMicroseconds(1888);           // timing adjustmet
          delayMicroseconds(1880);           // timing adjustmet tuned

          if (switchPushed == true) {        // if any switch touched
            break;                           // abandon record(this improve response)
          }
        }
        break;
      }
    case 3: {                                // 20ms range
        timeExec = 160 + 60;                 // Approximate execution time(ms)
        ADCSRA = ADCSRA & 0xf8;              // clear bottom 3bit
        ADCSRA = ADCSRA | 0x07;              // dividing ratio = 128 (default of ArduinoЃj
        for (int i = 0; i < REC_LENG; i++) { // up to rec buffer size
          waveBuff[i] = analogRead(0);       // read and save approx 112us
          // delayMicroseconds(688);            // timing adjustmet
          delayMicroseconds(686);            // timing adjustmet tuned
          if (switchPushed == true) {        // if any switch touched
            break;                           // abandon record(this improve response)
          }
        }
        break;
      }
    case 4: {                                // 10ms range
        timeExec = 80 + 60;                  // Approximate execution time(ms)
        ADCSRA = ADCSRA & 0xf8;              // clear bottom 3bit
        ADCSRA = ADCSRA | 0x07;              // dividing ratio = 128 (default of ArduinoЃj
        for (int i = 0; i < REC_LENG; i++) { // up to rec buffer size
          waveBuff[i] = analogRead(0);       // read and save approx 112us
          // delayMicroseconds(288);            // timing adjustmet
          delayMicroseconds(287);            // timing adjustmet tuned
          if (switchPushed == true) {        // if any switch touched
            break;                           // abandon record(this improve response)
          }
        }
        break;
      }
    case 5: {                                // 5ms range
        timeExec = 40 + 60;                  // Approximate execution time(ms)
        ADCSRA = ADCSRA & 0xf8;              // clear bottom 3bit
        ADCSRA = ADCSRA | 0x07;              // dividing ratio = 128 (default of ArduinoЃj
        for (int i = 0; i < REC_LENG; i++) { // up to rec buffer size
          waveBuff[i] = analogRead(0);       // read and save approx 112ѓКs
          // delayMicroseconds(88);             // timing adjustmet
          delayMicroseconds(87);             // timing adjustmet tuned
          if (switchPushed == true) {        // if any switch touched
            break;                           // abandon record(this improve response)
          }
        }
        break;
      }
    case 6: {                                // 2ms range
        timeExec = 16 + 60;                  // Approximate execution time(ms)
        ADCSRA = ADCSRA & 0xf8;              // clear bottom 3bit
        ADCSRA = ADCSRA | 0x06;              // dividing ratio = 64 (0x1=2, 0x2=4, 0x3=8, 0x4=16, 0x5=32, 0x6=64, 0x7=128)
        for (int i = 0; i < REC_LENG; i++) { // up to rec buffer size
          waveBuff[i] = analogRead(0);       // read and save approx 56us
          // delayMicroseconds(24);             // timing adjustmet
          delayMicroseconds(23);             // timing adjustmet tuned
        }
        break;
      }
    case 7: {                                // 1ms range
        timeExec = 8 + 60;                   // Approximate execution time(ms)
        ADCSRA = ADCSRA & 0xf8;              // clear bottom 3bit
        ADCSRA = ADCSRA | 0x05;              // dividing ratio = 16 (0x1=2, 0x2=4, 0x3=8, 0x4=16, 0x5=32, 0x6=64, 0x7=128)
        for (int i = 0; i < REC_LENG; i++) { // up to rec buffer size
          waveBuff[i] = analogRead(0);       // read and save approx 28us
          // delayMicroseconds(12);             // timing adjustmet
          delayMicroseconds(10);             // timing adjustmet tuned
        }
        break;
      }
    case 8: {                                // 500us range
        timeExec = 4 + 60;                   // Approximate execution time(ms)
        ADCSRA = ADCSRA & 0xf8;              // clear bottom 3bit
        ADCSRA = ADCSRA | 0x04;              // dividing ratio = 16(0x1=2, 0x2=4, 0x3=8, 0x4=16, 0x5=32, 0x6=64, 0x7=128)
        for (int i = 0; i < REC_LENG; i++) { // up to rec buffer size
          waveBuff[i] = analogRead(0);       // read and save approx 16us
          delayMicroseconds(4);              // timing adjustmet
          // time fine adjustment 0.0625 x 8 = 0.5usЃinop=0.0625us @16MHz)
          asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop");
        }
        break;
      }
    case 9: {                                // 200us range
        timeExec = 2 + 60;                   // Approximate execution time(ms)
        ADCSRA = ADCSRA & 0xf8;              // clear bottom 3bit
        ADCSRA = ADCSRA | 0x02;              // dividing ratio = 4(0x1=2, 0x2=4, 0x3=8, 0x4=16, 0x5=32, 0x6=64, 0x7=128)
        for (int i = 0; i < REC_LENG; i++) { // up to rec buffer size
          waveBuff[i] = analogRead(0);       // read and save approx 6us
          // time fine adjustment 0.0625 * 20 = 1.25us (nop=0.0625us @16MHz)
          asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop");
          asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop");
        }
        break;
      }
  }
}

void dataAnalize() {                       // get various information from wave form
  int d;
  long sum = 0;

  // search max and min value
  dataMin = 1023;                          // min value initialize to big number
  dataMax = 0;                             // max value initialize to small number
  for (int i = 0; i < REC_LENG; i++) {     // serach max min value
    d = waveBuff[i];
    sum = sum + d;
    if (d < dataMin) {                     // update min
      dataMin = d;
    }
    if (d > dataMax) {                     // updata max
      dataMax = d;
    }
  }

  // calculate average
  dataAve = (sum + 10) / 20;               // Average value calculation (calculated by 10 times to improve accuracy)

  // decide display's max min value
  if (vRange <= 1) {                       // if Autorabge(Range number <=1Ѓj
    rangeMin = dataMin - 20;               // maintain bottom margin 20
    rangeMin = (rangeMin / 10) * 10;       // round 10
    if (rangeMin < 0) {
      rangeMin = 0;                        // no smaller than 0
    }
    rangeMax = dataMax + 20;               // set display top at  data max +20
    rangeMax = ((rangeMax / 10) + 1) * 10; // round up 10
    if (rangeMax > 1020) {
      rangeMax = 1023;                     // if more than 1020, hold down at 1023
    }

    if (att10x == 1) {                            // if 10x attenuator used
      rangeMaxDisp = 100 * (rangeMax * lsb50V);   // display range is determined by the data.(the upper limit is up to the full scale of the ADC)
      rangeMinDisp = 100 * (rangeMin * lsb50V);   // lower depend on data, but zero or more
    } else {                                      // if no attenuator used
      rangeMaxDisp = 100 * (rangeMax * lsb5V);
      rangeMinDisp = 100 * (rangeMin * lsb5V);
    }
  } else {                                        // if fix range
    // Write necessary code here (none for now)
  }

  // Trigger position search
  for (trigP = ((REC_LENG / 2) - 51); trigP < ((REC_LENG / 2) + 50); trigP++) { // Find the points that straddle the median at the center Ѓ} 50 of the data range
    if (trigD == 0) {                             // if trigger direction is positive
      if ((waveBuff[trigP - 1] < (dataMax + dataMin) / 2) && (waveBuff[trigP] >= (dataMax + dataMin) / 2)) {
        break;                                    // positive trigger position found !
      }
    } else {                                      // trigger direction is negative
      if ((waveBuff[trigP - 1] > (dataMax + dataMin) / 2) && (waveBuff[trigP] <= (dataMax + dataMin) / 2)) {
        break;
      }                                           // negative trigger poshition found !
    }
  }
  trigSync = true;
  if (trigP >= ((REC_LENG / 2) + 50)) {           // If the trigger is not found in range
    trigP = (REC_LENG / 2);                       // Set it to the center for the time being
    trigSync = false;                             // set Unsync display flag
  }
  if ((dataMax - dataMin) <= MIN_TRIG_SWING) {    // amplitude of the waveform smaller than the specified value
    trigSync = false;                             // set Unsync display flag
  }
  freqDuty();
}

void freqDuty() {                               // detect frequency and duty cycle value from waveform data
  int swingCenter;                              // center of wave (half of p-p)
  float p0 = 0;                                 // 1-st posi edge
  float p1 = 0;                                 // total length of cycles
  float p2 = 0;                                 // total length of pulse high time
  float pFine = 0;                              // fine position (0-1.0)
  float lastPosiEdge;                           // last positive edge position

  float pPeriod;                                // pulse period
  float pWidth;                                 // pulse width

  int p1Count = 0;                              // wave cycle count
  int p2Count = 0;                              // High time count

  boolean a0Detected = false;
  //  boolean b0Detected = false;
  boolean posiSerch = true;                      // true when serching posi edge

  swingCenter = (3 * (dataMin + dataMax)) / 2;   // calculate wave center value

  for (int i = 1; i < REC_LENG - 2; i++) {       // scan all over the buffer
    if (posiSerch == true) {   // posi slope (frequency serch)
      if ((sum3(i) <= swingCenter) && (sum3(i + 1) > swingCenter)) {  // if across the center when rising (+-3data used to eliminate noize)
        pFine = (float)(swingCenter - sum3(i)) / ((swingCenter - sum3(i)) + (sum3(i + 1) - swingCenter) );  // fine cross point calc.
        if (a0Detected == false) {               // if 1-st cross
          a0Detected = true;                     // set find flag
          p0 = i + pFine;                        // save this position as startposition
        } else {
          p1 = i + pFine - p0;                   // record length (length of n*cycle time)
          p1Count++;
        }
        lastPosiEdge = i + pFine;                // record location for Pw calcration
        posiSerch = false;
      }
    } else {   // nega slope serch (duration serch)
      if ((sum3(i) >= swingCenter) && (sum3(i + 1) < swingCenter)) {  // if across the center when falling (+-3data used to eliminate noize)
        pFine = (float)(sum3(i) - swingCenter) / ((sum3(i) - swingCenter) + (swingCenter - sum3(i + 1)) );
        if (a0Detected == true) {
          p2 = p2 + (i + pFine - lastPosiEdge);  // calucurate pulse width and accumurate it
          p2Count++;
        }
        posiSerch = true;
      }
    }
  }

  pPeriod = p1 / p1Count;                 // pulse period
  pWidth = p2 / p2Count;                  // palse width

  waveFreq = 1.0 / ((pgm_read_float(hRangeValue + hRange) * pPeriod) / 25.0); // frequency
  waveDuty = 100.0 * pWidth / pPeriod;                                      // duty ratio
}

int sum3(int k) {       // Sum of before and after and own value
  int m = waveBuff[k - 1] + waveBuff[k] + waveBuff[k + 1];
  return m;
}

void startScreen() {                      // Staru up screen
  oled.clearDisplay();
  oled.setTextSize(2);                    // at double size character
  oled.setTextColor(WHITE);
//  oled.setCursor(10, 15);
//  oled.setCursor(10, 0);
  oled.println(F("PMO-RP1"));             // Title(Poor Man's Osilloscope, RadioPench 1)
  oled.println(F("SH1106"));              // this for SH1106
  oled.println(F("v2.0"));                // version No.
  oled.display();                         // actual display here
  delay(1500);
  oled.clearDisplay();
  oled.setTextSize(1);                    // After this, standard font size
}

void dispHold() {                         // display "Hold"
  oled.fillRect(42, 11, 24, 8, BLACK);    // black paint 4 characters
  oled.setCursor(42, 11);
  oled.print(F("Hold"));                  // Hold
  oled.display();                         //
}

void dispInf() {                          // Display of various information
  float voltage;
  // display vertical sensitivity
  oled.setCursor(2, 0);                   // around top left
  oled.print(vScale);                     // vertical sensitivity value
  if (scopeP == 0) {                      // if scoped
    oled.drawFastHLine(0, 7, 27, WHITE);  // display scoped mark at the bottom
    oled.drawFastVLine(0, 5, 2, WHITE);
    oled.drawFastVLine(26, 5, 2, WHITE);
  }

  // horizontal sweep speed
  oled.setCursor(34, 0);                  //
  oled.print(hScale);                     // display sweep speed (time/div)
  if (scopeP == 1) {                      // if scoped
    oled.drawFastHLine(32, 7, 33, WHITE); // display scoped mark at the bottom
    oled.drawFastVLine(32, 5, 2, WHITE);
    oled.drawFastVLine(64, 5, 2, WHITE);
  }

  // trigger polarity
  oled.setCursor(75, 0);                  // at top center
  if (trigD == 0) {                       // if positive
    oled.print(char(0x18));               // up mark
  } else {
    oled.print(char(0x19));               // down mark              Ѓ«
  }
  if (scopeP == 2) {                      // if scoped
    oled.drawFastHLine(71, 7, 13, WHITE); // display scoped mark at the bottom
    oled.drawFastVLine(71, 5, 2, WHITE);
    oled.drawFastVLine(83, 5, 2, WHITE);
  }

  // average voltage
  if (att10x == 1) {                         // if 10x attenuator is used
    voltage = dataAve * lsb50V / 10.0;       // 50V range value
  } else {                                   // no!
    voltage = dataAve * lsb5V / 10.0;        // 5V range value
  }
  if (voltage < 10.0) {                      // if less than 10V
    dtostrf(voltage, 4, 2, chrBuff);         // format x.xx
  } else {                                   // no!
    dtostrf(voltage, 4, 1, chrBuff);         // format xx.x
  }
  oled.setCursor(98, 0);                     // around the top right
  oled.print(chrBuff);                       // display average voltage€і‚М•Ѕ‹П’l‚р•\Ћ¦
  //  oled.print(saveTimer);                 // use here for debugging

  // vartical scale lines
  voltage = rangeMaxDisp / 100.0;            // convart Max voltage
  if (vRange == 1 || vRange > 4) {           // if range below 5V or Auto 5V
    dtostrf(voltage, 4, 2, chrBuff);         // format *.**
  } else {                                   // no!
    dtostrf(voltage, 4, 1, chrBuff);         // format **.*
  }
  oled.setCursor(0, 9);
  oled.print(chrBuff);                       // display Max value

  voltage = (rangeMaxDisp + rangeMinDisp) / 200.0; // center value calculation
  if (vRange == 1 || vRange > 4) {           // if range below 5V or Auto 5V
    dtostrf(voltage, 4, 2, chrBuff);         // format *.**
  } else {                                   // no!
    dtostrf(voltage, 4, 1, chrBuff);         // format **.*
  }
  oled.setCursor(0, 33);
  oled.print(chrBuff);                       // display the value

  voltage = rangeMinDisp / 100.0;            // convart Min vpltage
  if (vRange == 1 || vRange > 4) {           // if range below 5V or Auto 5V
    dtostrf(voltage, 4, 2, chrBuff);         // format *.**
  } else {                                   // no!
    dtostrf(voltage, 4, 1, chrBuff);         // format **.*
  }
  oled.setCursor(0, 57);
  oled.print(chrBuff);                       // display the value

  // display frequency, duty % or trigger missed
  if (trigSync == false) {                   // If trigger point can't found
    oled.fillRect(92, 14, 24, 8, BLACK);     // black paint 4 character
    oled.setCursor(92, 14);                  //
    oled.print(F("unSync"));                 // dosplay Unsync
  } else {
    oled.fillRect(91, 12, 25, 9, BLACK);    // erase Freq area
    oled.setCursor(92, 13);                  // set display locatio
    if (waveFreq < 100.0) {                  // if less than 100Hz
      oled.print(waveFreq, 1);               // display 99.9Hz
      oled.print(F("Hz"));
    } else if (waveFreq < 1000.0) {          // if less than 1000Hz
      oled.print(waveFreq, 0);               // display 999Hz
      oled.print(F("Hz"));
    } else if (waveFreq < 10000.0) {         // if less than 10kHz
      oled.print((waveFreq / 1000.0), 2);    // display 9.99kH
      oled.print(F("kH"));
    } else {                                 // if more
      oled.print((waveFreq / 1000.0), 1);    // display 99.9kH
      oled.print(F("kH"));
    }
    oled.fillRect(97, 21, 25, 10, BLACK);    // erase Freq area (as small as possible)
    oled.setCursor(98, 23);                  // set location
    oled.print(waveDuty, 1);                 // display duty (High level ratio) in %
    oled.print(F("%"));
  }
}

void plotData() {                    // plot wave form on OLED
  long y1, y2;
  for (int x = 0; x <= 98; x++) {
    y1 = map(waveBuff[x + trigP - 50], rangeMin, rangeMax, 63, 9); // convert to plot address
    y1 = constrain(y1, 9, 63);                                     // Crush(Saturate) the protruding part
    y2 = map(waveBuff[x + trigP - 49], rangeMin, rangeMax, 63, 9); // to address calucurate
    y2 = constrain(y2, 9, 63);                                     //
    oled.drawLine(x + 27, y1, x + 28, y2, WHITE);                  // connect between point
  }
}

void saveEEPROM() {                    // Save the setting value in EEPROM after waiting a while after the button operation.
  if (saveTimer > 0) {                 // If the timer value is positive,
    saveTimer = saveTimer - timeExec;  // Timer subtraction
    if (saveTimer < 0) {               // if time up
      EEPROM.write(0, vRange);         // save current status to EEPROM
      EEPROM.write(1, hRange);
      EEPROM.write(2, trigD);
      EEPROM.write(3, scopeP);
    }
  }
}

void loadEEPROM() {                    // Read setting values from EEPROM (abnormal values will be corrected to default)
  int x;
  x = EEPROM.read(0);                  // vRange
  if ((x < 0) || (9 < x)) {            // if out side 0-9
    x = 3;                             // default value
  }
  vRange = x;

  x = EEPROM.read(1);                  // hRange
  if ((x < 0) || (9 < x)) {            // if out of 0-9
    x = 3;                             // default value
  }
  hRange = x;
  x = EEPROM.read(2);                  // trigD
  if ((x < 0) || (1 < x)) {            // if out of 0-1
    x = 1;                             // default value
  }
  trigD = x;
  x = EEPROM.read(3);                  // scopeP
  if ((x < 0) || (2 < x)) {            // if out of 0-2
    x = 1;                             // default value
  }
  scopeP = x;
}

void auxFunctions() {                       // voltage meter function
  float voltage;
  long x;
  if (digitalRead(8) == LOW) {              // if SELECT button pushed, measure battery voltage
    analogReference(DEFAULT);               // ADC full scale set to Vcc
    while (1) {                             // do forever
      x = 0;
      for (int i = 0; i < 100; i++) {       // 100 times
        x = x + analogRead(1);              // read A1 pin voltage and accumulate
      }
      voltage = (x / 100.0) * 5.0 / 1023.0; // convert voltage value
      oled.clearDisplay();                  // all erase screen(0.4ms)
      oled.setTextColor(WHITE);             // write in white character
      oled.setCursor(20, 16);               //
      oled.setTextSize(1);                  // standerd size character
      oled.println(F("Battery voltage"));
      oled.setCursor(35, 30);               //
      oled.setTextSize(2);                  // double size character
      dtostrf(voltage, 4, 2, chrBuff);      // display batterry voltage x.xxV
      oled.print(chrBuff);
      oled.println(F("V"));
      oled.display();
      delay(150);
    }
  }
  if (digitalRead(9) == LOW) {              // if UP button pushed, 5V range
    analogReference(INTERNAL);
    pinMode(12, INPUT);                     // Set the attenuator control pin to Hi-z (use as input)
    while (1) {                             // do forever,
      digitalWrite(13, HIGH);               // flash LED
      voltage = analogRead(0) * lsb5V;      // measure voltage
      oled.clearDisplay();                  // erase screen (0.4ms)
      oled.setTextColor(WHITE);             // write in white character
      oled.setCursor(26, 16);               //
      oled.setTextSize(1);                  // by standerd size character
      oled.println(F("DVM 5V Range"));
      oled.setCursor(35, 30);               //
      oled.setTextSize(2);                  // double size character
      dtostrf(voltage, 4, 2, chrBuff);      // display batterry voltage x.xxV
      oled.print(chrBuff);
      oled.println(F("V"));
      oled.display();
      digitalWrite(13, LOW);                // stop LED flash
      delay(150);
    }
  }
  if (digitalRead(10) == LOW) {             // if DOWN botton pushed, 50V range
    analogReference(INTERNAL);
    pinMode(12, OUTPUT);                    // Set the attenuator control pin to OUTPUT
    digitalWrite(12, LOW);                  // output LOW
    while (1) {                             // do forever
      digitalWrite(13, HIGH);               // flush LED
      voltage = analogRead(0) * lsb50V;     // measure voltage
      oled.clearDisplay();                  // erase screen (0.4ms)
      oled.setTextColor(WHITE);             // write in white character
      oled.setCursor(26, 16);               //
      oled.setTextSize(1);                  // by standerd size character
      oled.println(F("DVM 50V Range"));
      oled.setCursor(35, 30);               //
      oled.setTextSize(2);                  // double size character
      dtostrf(voltage, 4, 1, chrBuff);      // display batterry voltage xx.xV
      oled.print(chrBuff);
      oled.println(F("V"));
      oled.display();
      digitalWrite(13, LOW);                // stop LED flash
      delay(150);
    }
  }
}

void uuPinOutputLow(unsigned int d, unsigned int a) { // Ћw’иѓsѓ“‚рЏo—НЃALOW‚ЙђЭ’и
  // PORTx =0, DDRx=1
  unsigned int x;
  x = d & 0x00FF; PORTD &= ~x; DDRD |= x;
  x = d >> 8;     PORTB &= ~x; DDRB |= x;
  x = a & 0x003F; PORTC &= ~x; DDRC |= x;
}

void pin2IRQ() {                   // Pin2(int.0) interrupr handler
  // Pin8,9,10,11 buttons are bundled with diodes and connected to Pin2.
  // So, if any button is pressed, this routine will start.
  int x;                           // Port information holding variable

  x = PINB;                        // read port B status
  if ( (x & 0x07) != 0x07) {       // if bottom 3bit is not all Hi(any wer pressed)
    saveTimer = 5000;              // set EEPROM save timer to 5 secnd
    switchPushed = true;           // switch pushed falag ON
  }
  if ((x & 0x01) == 0) {           // if select button(Pin8) pushed,
    scopeP++;                      // forward scope position
    if (scopeP > 2) {              // if upper limit
      scopeP = 0;                  // move to start position
    }
  }

  if ((x & 0x02) == 0) {           // if UP button(Pin9) pusshed, and
    if (scopeP == 0) {             // scoped vertical range
      vRange++;                    // V-range up !
      if (vRange > 9) {            // if upper limit
        vRange = 9;                // stay as is
      }
    }
    if (scopeP == 1) {             // if scoped hrizontal range
      hRange++;                    // H-range up !
      if (hRange > 9) {            // if upper limit
        hRange = 9;                // stay as is
      }
    }
    if (scopeP == 2) {             // if scoped trigger porality
      trigD = 0;                   // set trigger porality to +
    }
  }

  if ((x & 0x04) == 0) {           // if DOWN button(Pin10) pusshed, and
    if (scopeP == 0) {             // scoped vertical range
      vRange--;                    // V-range DOWN
      if (vRange < 0) {            // if bottom
        vRange = 0;                // stay as is
      }
    }
    if (scopeP == 1) {             // if scoped hrizontal range
      hRange--;                    // H-range DOWN
      if (hRange < 0) {            // if bottom
        hRange = 0;                // satay as is
      }
    }
    if (scopeP == 2) {             // if scoped trigger porality
      trigD = 1;                   // set trigger porality to -
    }
  }

  if ((x & 0x08) == 0) {           // if HOLD button(pin11) pushed
    hold = ! hold;                 // revers the flag
  }
}

Для SSD1306:

/*
    PMO-RP1 V2.0
    20200427_OLEDoscilloscope_V200E.ino)
    sketchЃF23020byteЃAlocal variable:1231byte free
    Apr.27 2020 by radiopench http://radiopench.blog96.fc2.com/
*/

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <EEPROM.h>

#define SCREEN_WIDTH 128                // OLED display width
#define SCREEN_HEIGHT 64                // OLED display height
#define REC_LENG 200                    // size of wave data buffer
#define MIN_TRIG_SWING 5                // minimum trigger swing.(Display "Unsync" if swing smaller than this value

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET     -1      // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);   // device name is oled

// Range name table (those are stored in flash memory)
const char vRangeName[10][5] PROGMEM = {"A50V", "A 5V", " 50V", " 20V", " 10V", "  5V", "  2V", "  1V", "0.5V", "0.2V"}; // Vertical display character (number of characters including \ 0 is required)
const char * const vstring_table[] PROGMEM = {vRangeName[0], vRangeName[1], vRangeName[2], vRangeName[3], vRangeName[4], vRangeName[5], vRangeName[6], vRangeName[7], vRangeName[8], vRangeName[9]};
const char hRangeName[10][6] PROGMEM = {"200ms", "100ms", " 50ms", " 20ms", " 10ms", "  5ms", "  2ms", "  1ms", "500us", "200us"};  //  Hrizontal display characters
const char * const hstring_table[] PROGMEM = {hRangeName[0], hRangeName[1], hRangeName[2], hRangeName[3], hRangeName[4], hRangeName[5], hRangeName[6], hRangeName[7], hRangeName[8], hRangeName[9]};
const PROGMEM float hRangeValue[] = { 0.2, 0.1, 0.05, 0.02, 0.01, 0.005, 0.002, 0.001, 0.5e-3, 0.2e-3}; // horizontal range value in second. ( = 25pix on screen)

int waveBuff[REC_LENG];        // wave form buffer (RAM remaining capacity is barely)
char chrBuff[8];               // display string buffer
char hScale[] = "xxxAs";       // horizontal scale character
char vScale[] = "xxxx";        // vartical scale

float lsb5V = 0.00566826;      // sensivity coefficient of 5V range. std=0.00563965 1.1*630/(1024*120)
float lsb50V = 0.05243212;     // sensivity coefficient of 50V range. std=0.0512898 1.1*520.91/(1024*10.91)

volatile int vRange;           // V-range number 0:A50V,  1:A 5V,  2:50V,  3:20V,  4:10V,  5:5V,  6:2V,  7:1V,  8:0.5V,  9:0.2V
volatile int hRange;           // H-range nubmer 0:200ms, 1:100ms, 2:50ms, 3:20ms, 4:10ms, 5:5ms, 6;2ms, 7:1ms, 8:500us, 9;200us
volatile int trigD;            // trigger slope flag,     0:positive 1:negative
volatile int scopeP;           // operation scope position number. 0:Veratical, 1:Hrizontal, 2:Trigger slope
volatile boolean hold = false; // hold flag
volatile boolean switchPushed = false; // flag of switch pusshed !
volatile int saveTimer;        // remaining time for saving EEPROM
int timeExec;                  // approx. execution time of current range setting (ms)

int dataMin;                   // buffer minimum value (smallest=0)
int dataMax;                   //        maximum value (largest=1023)
int dataAve;                   // 10 x average value (use 10x value to keep accuracy. so, max=10230)
int rangeMax;                  // buffer value to graph full swing
int rangeMin;                  // buffer value of graph botto
int rangeMaxDisp;              // display value of max. (100x value)
int rangeMinDisp;              // display value if min.
int trigP;                     // trigger position pointer on data buffer
boolean trigSync;              // flag of trigger detected
int att10x;                    // 10x attenetor ON (effective when 1)

float waveFreq;                // frequency (Hz)
float waveDuty;                // duty ratio (%)

void setup() {
  pinMode(2, INPUT_PULLUP);             // button pussed interrupt (int.0 IRQ)
  pinMode(8, INPUT_PULLUP);             // Select button
  pinMode(9, INPUT_PULLUP);             // Up
  pinMode(10, INPUT_PULLUP);            // Down
  pinMode(11, INPUT_PULLUP);            // Hold
  pinMode(12, INPUT);                   // 1/10 attenuator(Off=High-Z, Enable=Output Low)
  pinMode(13, OUTPUT);                  // LED

  //       Serial.begin(115200);        // meny RAM used when activate this. (may by crash!)
  if (!oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { // select 3C or 3D (set your OLED I2C address)
    for (;;);                           // loop forever
  }
  auxFunctions();                       // Voltage measure (never return)
  loadEEPROM();                         // read last settings from EEPROM
  analogReference(INTERNAL);            // ADC full scale = 1.1V
  attachInterrupt(0, pin2IRQ, FALLING); // activate IRQ at falling edge mode
  startScreen();                        // display start message
}

void loop() {
  setConditions();                      // set measurment conditions
  digitalWrite(13, HIGH);               // flash LED
  readWave();                           // read wave form and store into buffer memory
  digitalWrite(13, LOW);                // stop LED
  setConditions();                      // set measurment conditions again (reflect change during measure)
  dataAnalize();                        // analize data
  writeCommonImage();                   // write fixed screen image (2.6ms)
  plotData();                           // plot waveform (10-18ms)
  dispInf();                            // display information (6.5-8.5ms)
  oled.display();                       // send screen buffer to OLED (37ms)
  saveEEPROM();                         // save settings to EEPROM if necessary
  while (hold == true) {                // wait if Hold flag ON
    dispHold();
    delay(10);
  }                                     // loop cycle speed = 60-470ms (buffer size = 200)
}

void setConditions() {           // measuring condition setting
  // get range name from PROGMEM
  strcpy_P(hScale, (char*)pgm_read_word(&(hstring_table[hRange])));  // H range name
  strcpy_P(vScale, (char*)pgm_read_word(&(vstring_table[vRange])));  // V range name

  switch (vRange) {              // setting of Vrange
    case 0: {                    // Auto50V range
        att10x = 1;              // use input attenuator
        break;
      }
    case 1: {                    // Auto 5V range
        att10x = 0;              // no attenuator
        break;
      }
    case 2: {                    // 50V range
        rangeMax = 50 / lsb50V;  // set full scale pixcel count number
        rangeMaxDisp = 5000;     // vartical scale (set100x value)
        rangeMin = 0;
        rangeMinDisp = 0;
        att10x = 1;              // use input attenuator
        break;
      }
    case 3: {                    // 20V range
        rangeMax = 20 / lsb50V;  // set full scale pixcel count number
        rangeMaxDisp = 2000;
        rangeMin = 0;
        rangeMinDisp = 0;
        att10x = 1;              // use input attenuator
        break;
      }
    case 4: {                    // 10V range
        rangeMax = 10 / lsb50V;  // set full scale pixcel count number
        rangeMaxDisp = 1000;
        rangeMin = 0;
        rangeMinDisp = 0;
        att10x = 1;              // use input attenuator
        break;
      }
    case 5: {                    // 5V range
        rangeMax = 5 / lsb5V;    // set full scale pixcel count number
        rangeMaxDisp = 500;
        rangeMin = 0;
        rangeMinDisp = 0;
        att10x = 0;              // no input attenuator
        break;
      }
    case 6: {                    // 2V range
        rangeMax = 2 / lsb5V;    // set full scale pixcel count number
        rangeMaxDisp = 200;
        rangeMin = 0;
        rangeMinDisp = 0;
        att10x = 0;              // no input attenuator
        break;
      }
    case 7: {                    // 1V range
        rangeMax = 1 / lsb5V;    // set full scale pixcel count number
        rangeMaxDisp = 100;
        rangeMin = 0;
        rangeMinDisp = 0;
        att10x = 0;              // no input attenuator
        break;
      }
    case 8: {                    // 0.5V range
        rangeMax = 0.5 / lsb5V;  // set full scale pixcel count number
        rangeMaxDisp = 50;
        rangeMin = 0;
        rangeMinDisp = 0;
        att10x = 0;              // no input attenuator
        break;
      }
    case 9: {                    // 0.5V range
        rangeMax = 0.2 / lsb5V;  // set full scale pixcel count number
        rangeMaxDisp = 20;
        rangeMin = 0;
        rangeMinDisp = 0;
        att10x = 0;              // no input attenuator
        break;
      }
  }
}

void writeCommonImage() {                 // Common screen image drawing
  oled.clearDisplay();                    // erase all(0.4ms)
  oled.setTextColor(WHITE);               // write in white character
  oled.setCursor(86, 0);                  // Start at top-left corner
  oled.println(F("av    V"));             // 1-st line fixed characters
  oled.drawFastVLine(26, 9, 55, WHITE);   // left vartical line
  oled.drawFastVLine(127, 9, 3, WHITE);   // right vrtical line up
  oled.drawFastVLine(127, 61, 3, WHITE);  // right vrtical line bottom

  oled.drawFastHLine(24, 9, 7, WHITE);    // Max value auxiliary mark
  oled.drawFastHLine(24, 36, 2, WHITE);
  oled.drawFastHLine(24, 63, 7, WHITE);

  oled.drawFastHLine(51, 9, 3, WHITE);    // Max value auxiliary mark
  oled.drawFastHLine(51, 63, 3, WHITE);

  oled.drawFastHLine(76, 9, 3, WHITE);    // Max value auxiliary mark
  oled.drawFastHLine(76, 63, 3, WHITE);

  oled.drawFastHLine(101, 9, 3, WHITE);   // Max value auxiliary mark
  oled.drawFastHLine(101, 63, 3, WHITE);

  oled.drawFastHLine(123, 9, 5, WHITE);   // right side Max value auxiliary mark
  oled.drawFastHLine(123, 63, 5, WHITE);

  for (int x = 26; x <= 128; x += 5) {
    oled.drawFastHLine(x, 36, 2, WHITE);  // Draw the center line (horizontal line) with a dotted line
  }
  for (int x = (127 - 25); x > 30; x -= 25) {
    for (int y = 10; y < 63; y += 5) {
      oled.drawFastVLine(x, y, 2, WHITE); // Draw 3 vertical lines with dotted lines
    }
  }
}

void readWave() {                            // Record waveform to memory array
  if (att10x == 1) {                         // if 1/10 attenuator required
    pinMode(12, OUTPUT);                     // assign attenuator controle pin to OUTPUT,
    digitalWrite(12, LOW);                   // and output LOW (output 0V)
  } else {                                   // if not required
    pinMode(12, INPUT);                      // assign the pin input (Hi-z)
  }
  switchPushed = false;                      // Clear switch operation flag

  switch (hRange) {                          // set recording conditions in accordance with the range number
    case 0: {                                // 200ms range
        timeExec = 1600 + 60;                // Approximate execution time(ms) Used for countdown until saving to EEPROM
        ADCSRA = ADCSRA & 0xf8;              // clear bottom 3bit
        ADCSRA = ADCSRA | 0x07;              // dividing ratio = 128 (default of ArduinoЃj
        for (int i = 0; i < REC_LENG; i++) { // up to rec buffer size
          waveBuff[i] = analogRead(0);       // read and save approx 112us
          delayMicroseconds(7888);           // timing adjustment
          if (switchPushed == true) {        // if any switch touched
            switchPushed = false;
            break;                           // abandon record(this improve response)
          }
        }
        break;
      }
    case 1: {                                // 100ms range
        timeExec = 800 + 60;                 // Approximate execution time(ms) Used for countdown until saving to EEPROM
        ADCSRA = ADCSRA & 0xf8;              // clear bottom 3bit
        ADCSRA = ADCSRA | 0x07;              // dividing ratio = 128 (default of ArduinoЃj
        for (int i = 0; i < REC_LENG; i++) {  // up to rec buffer size
          waveBuff[i] = analogRead(0);       // read and save approx 112us
          // delayMicroseconds(3888);           // timing adjustmet
          delayMicroseconds(3860);           // timing adjustmet tuned
          if (switchPushed == true) {        // if any switch touched
            switchPushed = false;
            break;                           // abandon record(this improve response)
          }
        }
        break;
      }
    case 2: {                                // 50ms range
        timeExec = 400 + 60;                 // Approximate execution time(ms)
        ADCSRA = ADCSRA & 0xf8;              // clear bottom 3bit
        ADCSRA = ADCSRA | 0x07;              // dividing ratio = 128 (default of ArduinoЃj
        for (int i = 0; i < REC_LENG; i++) { // up to rec buffer size
          waveBuff[i] = analogRead(0);       // read and save approx 112us
          // delayMicroseconds(1888);           // timing adjustmet
          delayMicroseconds(1880);           // timing adjustmet tuned

          if (switchPushed == true) {        // if any switch touched
            break;                           // abandon record(this improve response)
          }
        }
        break;
      }
    case 3: {                                // 20ms range
        timeExec = 160 + 60;                 // Approximate execution time(ms)
        ADCSRA = ADCSRA & 0xf8;              // clear bottom 3bit
        ADCSRA = ADCSRA | 0x07;              // dividing ratio = 128 (default of ArduinoЃj
        for (int i = 0; i < REC_LENG; i++) { // up to rec buffer size
          waveBuff[i] = analogRead(0);       // read and save approx 112us
          // delayMicroseconds(688);            // timing adjustmet
          delayMicroseconds(686);            // timing adjustmet tuned
          if (switchPushed == true) {        // if any switch touched
            break;                           // abandon record(this improve response)
          }
        }
        break;
      }
    case 4: {                                // 10ms range
        timeExec = 80 + 60;                  // Approximate execution time(ms)
        ADCSRA = ADCSRA & 0xf8;              // clear bottom 3bit
        ADCSRA = ADCSRA | 0x07;              // dividing ratio = 128 (default of ArduinoЃj
        for (int i = 0; i < REC_LENG; i++) { // up to rec buffer size
          waveBuff[i] = analogRead(0);       // read and save approx 112us
          // delayMicroseconds(288);            // timing adjustmet
          delayMicroseconds(287);            // timing adjustmet tuned
          if (switchPushed == true) {        // if any switch touched
            break;                           // abandon record(this improve response)
          }
        }
        break;
      }
    case 5: {                                // 5ms range
        timeExec = 40 + 60;                  // Approximate execution time(ms)
        ADCSRA = ADCSRA & 0xf8;              // clear bottom 3bit
        ADCSRA = ADCSRA | 0x07;              // dividing ratio = 128 (default of ArduinoЃj
        for (int i = 0; i < REC_LENG; i++) { // up to rec buffer size
          waveBuff[i] = analogRead(0);       // read and save approx 112ѓКs
          // delayMicroseconds(88);             // timing adjustmet
          delayMicroseconds(87);             // timing adjustmet tuned
          if (switchPushed == true) {        // if any switch touched
            break;                           // abandon record(this improve response)
          }
        }
        break;
      }
    case 6: {                                // 2ms range
        timeExec = 16 + 60;                  // Approximate execution time(ms)
        ADCSRA = ADCSRA & 0xf8;              // clear bottom 3bit
        ADCSRA = ADCSRA | 0x06;              // dividing ratio = 64 (0x1=2, 0x2=4, 0x3=8, 0x4=16, 0x5=32, 0x6=64, 0x7=128)
        for (int i = 0; i < REC_LENG; i++) { // up to rec buffer size
          waveBuff[i] = analogRead(0);       // read and save approx 56us
          // delayMicroseconds(24);             // timing adjustmet
          delayMicroseconds(23);             // timing adjustmet tuned
        }
        break;
      }
    case 7: {                                // 1ms range
        timeExec = 8 + 60;                   // Approximate execution time(ms)
        ADCSRA = ADCSRA & 0xf8;              // clear bottom 3bit
        ADCSRA = ADCSRA | 0x05;              // dividing ratio = 16 (0x1=2, 0x2=4, 0x3=8, 0x4=16, 0x5=32, 0x6=64, 0x7=128)
        for (int i = 0; i < REC_LENG; i++) { // up to rec buffer size
          waveBuff[i] = analogRead(0);       // read and save approx 28us
          // delayMicroseconds(12);             // timing adjustmet
          delayMicroseconds(10);             // timing adjustmet tuned
        }
        break;
      }
    case 8: {                                // 500us range
        timeExec = 4 + 60;                   // Approximate execution time(ms)
        ADCSRA = ADCSRA & 0xf8;              // clear bottom 3bit
        ADCSRA = ADCSRA | 0x04;              // dividing ratio = 16(0x1=2, 0x2=4, 0x3=8, 0x4=16, 0x5=32, 0x6=64, 0x7=128)
        for (int i = 0; i < REC_LENG; i++) { // up to rec buffer size
          waveBuff[i] = analogRead(0);       // read and save approx 16us
          delayMicroseconds(4);              // timing adjustmet
          // time fine adjustment 0.0625 x 8 = 0.5usЃinop=0.0625us @16MHz)
          asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop");
        }
        break;
      }
    case 9: {                                // 200us range
        timeExec = 2 + 60;                   // Approximate execution time(ms)
        ADCSRA = ADCSRA & 0xf8;              // clear bottom 3bit
        ADCSRA = ADCSRA | 0x02;              // dividing ratio = 4(0x1=2, 0x2=4, 0x3=8, 0x4=16, 0x5=32, 0x6=64, 0x7=128)
        for (int i = 0; i < REC_LENG; i++) { // up to rec buffer size
          waveBuff[i] = analogRead(0);       // read and save approx 6us
          // time fine adjustment 0.0625 * 20 = 1.25us (nop=0.0625us @16MHz)
          asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop");
          asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop");
        }
        break;
      }
  }
}

void dataAnalize() {                       // get various information from wave form
  int d;
  long sum = 0;

  // search max and min value
  dataMin = 1023;                          // min value initialize to big number
  dataMax = 0;                             // max value initialize to small number
  for (int i = 0; i < REC_LENG; i++) {     // serach max min value
    d = waveBuff[i];
    sum = sum + d;
    if (d < dataMin) {                     // update min
      dataMin = d;
    }
    if (d > dataMax) {                     // updata max
      dataMax = d;
    }
  }

  // calculate average
  dataAve = (sum + 10) / 20;               // Average value calculation (calculated by 10 times to improve accuracy)

  // decide display's max min value
  if (vRange <= 1) {                       // if Autorabge(Range number <=1Ѓj
    rangeMin = dataMin - 20;               // maintain bottom margin 20
    rangeMin = (rangeMin / 10) * 10;       // round 10
    if (rangeMin < 0) {
      rangeMin = 0;                        // no smaller than 0
    }
    rangeMax = dataMax + 20;               // set display top at  data max +20
    rangeMax = ((rangeMax / 10) + 1) * 10; // round up 10
    if (rangeMax > 1020) {
      rangeMax = 1023;                     // if more than 1020, hold down at 1023
    }

    if (att10x == 1) {                            // if 10x attenuator used
      rangeMaxDisp = 100 * (rangeMax * lsb50V);   // display range is determined by the data.(the upper limit is up to the full scale of the ADC)
      rangeMinDisp = 100 * (rangeMin * lsb50V);   // lower depend on data, but zero or more
    } else {                                      // if no attenuator used
      rangeMaxDisp = 100 * (rangeMax * lsb5V);
      rangeMinDisp = 100 * (rangeMin * lsb5V);
    }
  } else {                                        // if fix range
    // Write necessary code here (none for now)
  }

  // Trigger position search
  for (trigP = ((REC_LENG / 2) - 51); trigP < ((REC_LENG / 2) + 50); trigP++) { // Find the points that straddle the median at the center Ѓ} 50 of the data range
    if (trigD == 0) {                             // if trigger direction is positive
      if ((waveBuff[trigP - 1] < (dataMax + dataMin) / 2) && (waveBuff[trigP] >= (dataMax + dataMin) / 2)) {
        break;                                    // positive trigger position found !
      }
    } else {                                      // trigger direction is negative
      if ((waveBuff[trigP - 1] > (dataMax + dataMin) / 2) && (waveBuff[trigP] <= (dataMax + dataMin) / 2)) {
        break;
      }                                           // negative trigger poshition found !
    }
  }
  trigSync = true;
  if (trigP >= ((REC_LENG / 2) + 50)) {           // If the trigger is not found in range
    trigP = (REC_LENG / 2);                       // Set it to the center for the time being
    trigSync = false;                             // set Unsync display flag
  }
  if ((dataMax - dataMin) <= MIN_TRIG_SWING) {    // amplitude of the waveform smaller than the specified value
    trigSync = false;                             // set Unsync display flag
  }
  freqDuty();
}

void freqDuty() {                               // detect frequency and duty cycle value from waveform data
  int swingCenter;                              // center of wave (half of p-p)
  float p0 = 0;                                 // 1-st posi edge
  float p1 = 0;                                 // total length of cycles
  float p2 = 0;                                 // total length of pulse high time
  float pFine = 0;                              // fine position (0-1.0)
  float lastPosiEdge;                           // last positive edge position

  float pPeriod;                                // pulse period
  float pWidth;                                 // pulse width

  int p1Count = 0;                              // wave cycle count
  int p2Count = 0;                              // High time count

  boolean a0Detected = false;
//  boolean b0Detected = false;
  boolean posiSerch = true;                      // true when serching posi edge

  swingCenter = (3 * (dataMin + dataMax)) / 2;   // calculate wave center value

  for (int i = 1; i < REC_LENG - 2; i++) {       // scan all over the buffer
    if (posiSerch == true) {   // posi slope (frequency serch)
      if ((sum3(i) <= swingCenter) && (sum3(i + 1) > swingCenter)) {  // if across the center when rising (+-3data used to eliminate noize)
        pFine = (float)(swingCenter - sum3(i)) / ((swingCenter - sum3(i)) + (sum3(i + 1) - swingCenter) );  // fine cross point calc.
        if (a0Detected == false) {               // if 1-st cross
          a0Detected = true;                     // set find flag
          p0 = i + pFine;                        // save this position as startposition
        } else {
          p1 = i + pFine - p0;                   // record length (length of n*cycle time)
          p1Count++;
        }
        lastPosiEdge = i + pFine;                // record location for Pw calcration
        posiSerch = false;
      }
    } else {   // nega slope serch (duration serch)
      if ((sum3(i) >= swingCenter) && (sum3(i + 1) < swingCenter)) {  // if across the center when falling (+-3data used to eliminate noize)
        pFine = (float)(sum3(i) - swingCenter) / ((sum3(i) - swingCenter) + (swingCenter - sum3(i + 1)) );
        if (a0Detected == true) {
          p2 = p2 + (i + pFine - lastPosiEdge);  // calucurate pulse width and accumurate it
          p2Count++;
        }
        posiSerch = true;
      }
    }
  }

  pPeriod = p1 / p1Count;                 // pulse period
  pWidth = p2 / p2Count;                  // palse width

  waveFreq = 1.0 / ((pgm_read_float(hRangeValue + hRange) * pPeriod) / 25.0); // frequency
  waveDuty = 100.0 * pWidth / pPeriod;                                      // duty ratio
}

int sum3(int k) {       // Sum of before and after and own value
  int m = waveBuff[k - 1] + waveBuff[k] + waveBuff[k + 1];
  return m;
}

void startScreen() {                      // Staru up screen
  oled.clearDisplay();
  oled.setTextSize(2);                    // at double size character
  oled.setTextColor(WHITE);
  oled.setCursor(10, 15);
  oled.println(F("PMO-RP1"));             // Title(Poor Man's Osilloscope, RadioPench 1)
  oled.setCursor(10, 35);
  oled.println(F("    v2.0"));            // version No.
  oled.display();                         // actual display here
  delay(1500);
  oled.clearDisplay();
  oled.setTextSize(1);                    // After this, standard font size
}

void dispHold() {                         // display "Hold"
  oled.fillRect(42, 11, 24, 8, BLACK);    // black paint 4 characters
  oled.setCursor(42, 11);
  oled.print(F("Hold"));                  // Hold
  oled.display();                         //
}

void dispInf() {                          // Display of various information
  float voltage;
  // display vertical sensitivity
  oled.setCursor(2, 0);                   // around top left
  oled.print(vScale);                     // vertical sensitivity value
  if (scopeP == 0) {                      // if scoped
    oled.drawFastHLine(0, 7, 27, WHITE);  // display scoped mark at the bottom
    oled.drawFastVLine(0, 5, 2, WHITE);
    oled.drawFastVLine(26, 5, 2, WHITE);
  }

  // horizontal sweep speed
  oled.setCursor(34, 0);                  //
  oled.print(hScale);                     // display sweep speed (time/div)
  if (scopeP == 1) {                      // if scoped
    oled.drawFastHLine(32, 7, 33, WHITE); // display scoped mark at the bottom
    oled.drawFastVLine(32, 5, 2, WHITE);
    oled.drawFastVLine(64, 5, 2, WHITE);
  }

  // trigger polarity
  oled.setCursor(75, 0);                  // at top center
  if (trigD == 0) {                       // if positive
    oled.print(char(0x18));               // up mark
  } else {
    oled.print(char(0x19));               // down mark              Ѓ«
  }
  if (scopeP == 2) {                      // if scoped
    oled.drawFastHLine(71, 7, 13, WHITE); // display scoped mark at the bottom
    oled.drawFastVLine(71, 5, 2, WHITE);
    oled.drawFastVLine(83, 5, 2, WHITE);
  }

  // average voltage
  if (att10x == 1) {                         // if 10x attenuator is used
    voltage = dataAve * lsb50V / 10.0;       // 50V range value
  } else {                                   // no!
    voltage = dataAve * lsb5V / 10.0;        // 5V range value
  }
  if (voltage < 10.0) {                      // if less than 10V
    dtostrf(voltage, 4, 2, chrBuff);         // format x.xx
  } else {                                   // no!
    dtostrf(voltage, 4, 1, chrBuff);         // format xx.x
  }
  oled.setCursor(98, 0);                     // around the top right
  oled.print(chrBuff);                       // display average voltage€і‚М•Ѕ‹П’l‚р•\Ћ¦
  //  oled.print(saveTimer);                 // use here for debugging

  // vartical scale lines
  voltage = rangeMaxDisp / 100.0;            // convart Max voltage
  if (vRange == 1 || vRange > 4) {           // if range below 5V or Auto 5V
    dtostrf(voltage, 4, 2, chrBuff);         // format *.**
  } else {                                   // no!
    dtostrf(voltage, 4, 1, chrBuff);         // format **.*
  }
  oled.setCursor(0, 9);
  oled.print(chrBuff);                       // display Max value

  voltage = (rangeMaxDisp + rangeMinDisp) / 200.0; // center value calculation
  if (vRange == 1 || vRange > 4) {           // if range below 5V or Auto 5V
    dtostrf(voltage, 4, 2, chrBuff);         // format *.**
  } else {                                   // no!
    dtostrf(voltage, 4, 1, chrBuff);         // format **.*
  }
  oled.setCursor(0, 33);
  oled.print(chrBuff);                       // display the value

  voltage = rangeMinDisp / 100.0;            // convart Min vpltage
  if (vRange == 1 || vRange > 4) {           // if range below 5V or Auto 5V
    dtostrf(voltage, 4, 2, chrBuff);         // format *.**
  } else {                                   // no!
    dtostrf(voltage, 4, 1, chrBuff);         // format **.*
  }
  oled.setCursor(0, 57);
  oled.print(chrBuff);                       // display the value

  // display frequency, duty % or trigger missed
  if (trigSync == false) {                   // If trigger point can't found
    oled.fillRect(92, 14, 24, 8, BLACK);     // black paint 4 character
    oled.setCursor(92, 14);                  //
    oled.print(F("unSync"));                 // dosplay Unsync
  } else {
    oled.fillRect(91, 12, 25, 9, BLACK);    // erase Freq area
    oled.setCursor(92, 13);                  // set display locatio
    if (waveFreq < 100.0) {                  // if less than 100Hz
      oled.print(waveFreq, 1);               // display 99.9Hz
      oled.print(F("Hz"));
    } else if (waveFreq < 1000.0) {          // if less than 1000Hz
      oled.print(waveFreq, 0);               // display 999Hz
      oled.print(F("Hz"));
    } else if (waveFreq < 10000.0) {         // if less than 10kHz
      oled.print((waveFreq / 1000.0), 2);    // display 9.99kH
      oled.print(F("kH"));
    } else {                                 // if more
      oled.print((waveFreq / 1000.0), 1);    // display 99.9kH
      oled.print(F("kH"));
    }
    oled.fillRect(97, 21, 25, 10, BLACK);    // erase Freq area (as small as possible)
    oled.setCursor(98, 23);                  // set location
    oled.print(waveDuty, 1);                 // display duty (High level ratio) in %
    oled.print(F("%"));
  }
}

void plotData() {                    // plot wave form on OLED
  long y1, y2;
  for (int x = 0; x <= 98; x++) {
    y1 = map(waveBuff[x + trigP - 50], rangeMin, rangeMax, 63, 9); // convert to plot address
    y1 = constrain(y1, 9, 63);                                     // Crush(Saturate) the protruding part
    y2 = map(waveBuff[x + trigP - 49], rangeMin, rangeMax, 63, 9); // to address calucurate
    y2 = constrain(y2, 9, 63);                                     //
    oled.drawLine(x + 27, y1, x + 28, y2, WHITE);                  // connect between point
  }
}

void saveEEPROM() {                    // Save the setting value in EEPROM after waiting a while after the button operation.
  if (saveTimer > 0) {                 // If the timer value is positive,
    saveTimer = saveTimer - timeExec;  // Timer subtraction
    if (saveTimer < 0) {               // if time up
      EEPROM.write(0, vRange);         // save current status to EEPROM
      EEPROM.write(1, hRange);
      EEPROM.write(2, trigD);
      EEPROM.write(3, scopeP);
    }
  }
}

void loadEEPROM() {                    // Read setting values from EEPROM (abnormal values will be corrected to default)
  int x;
  x = EEPROM.read(0);                  // vRange
  if ((x < 0) || (9 < x)) {            // if out side 0-9
    x = 3;                             // default value
  }
  vRange = x;

  x = EEPROM.read(1);                  // hRange
  if ((x < 0) || (9 < x)) {            // if out of 0-9
    x = 3;                             // default value
  }
  hRange = x;
  x = EEPROM.read(2);                  // trigD
  if ((x < 0) || (1 < x)) {            // if out of 0-1
    x = 1;                             // default value
  }
  trigD = x;
  x = EEPROM.read(3);                  // scopeP
  if ((x < 0) || (2 < x)) {            // if out of 0-2
    x = 1;                             // default value
  }
  scopeP = x;
}

void auxFunctions() {                       // voltage meter function
  float voltage;
  long x;
  if (digitalRead(8) == LOW) {              // if SELECT button pushed, measure battery voltage
    analogReference(DEFAULT);               // ADC full scale set to Vcc
    while (1) {                             // do forever
      x = 0;
      for (int i = 0; i < 100; i++) {       // 100 times
        x = x + analogRead(1);              // read A1 pin voltage and accumulate
      }
      voltage = (x / 100.0) * 5.0 / 1023.0; // convert voltage value
      oled.clearDisplay();                  // all erase screen(0.4ms)
      oled.setTextColor(WHITE);             // write in white character
      oled.setCursor(20, 16);               //
      oled.setTextSize(1);                  // standerd size character
      oled.println(F("Battery voltage"));
      oled.setCursor(35, 30);               //
      oled.setTextSize(2);                  // double size character
      dtostrf(voltage, 4, 2, chrBuff);      // display batterry voltage x.xxV
      oled.print(chrBuff);
      oled.println(F("V"));
      oled.display();
      delay(150);
    }
  }
  if (digitalRead(9) == LOW) {              // if UP button pushed, 5V range
    analogReference(INTERNAL);
    pinMode(12, INPUT);                     // Set the attenuator control pin to Hi-z (use as input)
    while (1) {                             // do forever,
      digitalWrite(13, HIGH);               // flash LED
      voltage = analogRead(0) * lsb5V;      // measure voltage
      oled.clearDisplay();                  // erase screen (0.4ms)
      oled.setTextColor(WHITE);             // write in white character
      oled.setCursor(26, 16);               //
      oled.setTextSize(1);                  // by standerd size character
      oled.println(F("DVM 5V Range"));
      oled.setCursor(35, 30);               //
      oled.setTextSize(2);                  // double size character
      dtostrf(voltage, 4, 2, chrBuff);      // display batterry voltage x.xxV
      oled.print(chrBuff);
      oled.println(F("V"));
      oled.display();
      digitalWrite(13, LOW);                // stop LED flash
      delay(150);
    }
  }
  if (digitalRead(10) == LOW) {             // if DOWN botton pushed, 50V range
    analogReference(INTERNAL);
    pinMode(12, OUTPUT);                    // Set the attenuator control pin to OUTPUT
    digitalWrite(12, LOW);                  // output LOW
    while (1) {                             // do forever
      digitalWrite(13, HIGH);               // flush LED
      voltage = analogRead(0) * lsb50V;     // measure voltage
      oled.clearDisplay();                  // erase screen (0.4ms)
      oled.setTextColor(WHITE);             // write in white character
      oled.setCursor(26, 16);               //
      oled.setTextSize(1);                  // by standerd size character
      oled.println(F("DVM 50V Range"));
      oled.setCursor(35, 30);               //
      oled.setTextSize(2);                  // double size character
      dtostrf(voltage, 4, 1, chrBuff);      // display batterry voltage xx.xV
      oled.print(chrBuff);
      oled.println(F("V"));
      oled.display();
      digitalWrite(13, LOW);                // stop LED flash
      delay(150);
    }
  }
}

void pin2IRQ() {                   // Pin2(int.0) interrupr handler
  // Pin8,9,10,11 buttons are bundled with diodes and connected to Pin2.
  // So, if any button is pressed, this routine will start.
  int x;                           // Port information holding variable

  x = PINB;                        // read port B status
  if ( (x & 0x07) != 0x07) {       // if bottom 3bit is not all Hi(any wer pressed)
    saveTimer = 5000;              // set EEPROM save timer to 5 secnd
    switchPushed = true;           // switch pushed falag ON
  }
  if ((x & 0x01) == 0) {           // if select button(Pin8) pushed,
    scopeP++;                      // forward scope position
    if (scopeP > 2) {              // if upper limit
      scopeP = 0;                  // move to start position
    }
  }

  if ((x & 0x02) == 0) {           // if UP button(Pin9) pusshed, and
    if (scopeP == 0) {             // scoped vertical range
      vRange++;                    // V-range up !
      if (vRange > 9) {            // if upper limit
        vRange = 9;                // stay as is
      }
    }
    if (scopeP == 1) {             // if scoped hrizontal range
      hRange++;                    // H-range up !
      if (hRange > 9) {            // if upper limit
        hRange = 9;                // stay as is
      }
    }
    if (scopeP == 2) {             // if scoped trigger porality
      trigD = 0;                   // set trigger porality to +
    }
  }

  if ((x & 0x04) == 0) {           // if DOWN button(Pin10) pusshed, and
    if (scopeP == 0) {             // scoped vertical range
      vRange--;                    // V-range DOWN
      if (vRange < 0) {            // if bottom
        vRange = 0;                // stay as is
      }
    }
    if (scopeP == 1) {             // if scoped hrizontal range
      hRange--;                    // H-range DOWN
      if (hRange < 0) {            // if bottom
        hRange = 0;                // satay as is
      }
    }
    if (scopeP == 2) {             // if scoped trigger porality
      trigD = 1;                   // set trigger porality to -
    }
  }

  if ((x & 0x08) == 0) {           // if HOLD button(pin11) pushed
    hold = ! hold;                 // revers the flag
  }
}

 

smokok
smokok аватар
Offline
Зарегистрирован: 08.06.2018

Раз пошла такая пьянка))), то перевёл комментарии. Надеюсь что ничего не сломал. Сделано в Японии!!!

/ *
    PMO-RP1 V2.0
    20200427_OLEDoscilloscope_V200E.ino)
    эскиз: 23020 байтов, локальная переменная: 1231 байтов бесплатно
    27 апреля 2020 г. автор: radiopench http://radiopench.blog96.fc2.com/
* /

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <EEPROM.h>

#define SCREEN_WIDTH 128 // Ширина дисплея OLED
#define SCREEN_HEIGHT 64 // Высота дисплея OLED
#define REC_LENG 200     // размер буфера волновых данных
#define MIN_TRIG_SWING 5 // минимальное колебание триггера. (Отображать «Unsync», если колебание меньше этого значения

// Объявление для дисплея SSD1306, подключенного к I2C (выводы SDA, SCL)
#define OLED_RESET -1 // Сбросить вывод № (или -1, если используется вывод сброса Arduino)
Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT, & Wire, OLED_RESET); // имя устройства выбрано

// Таблица имен диапазонов (они хранятся во флэш-памяти)
const char vRangeName[10] [5] PROGMEM = {"A50V", "A 5V", "50V", "20V", "10V", "5V", "2V", "1V", "0.5V", "0.2V"}; // Вертикальный символ отображения (требуется количество символов, включая \ 0)
const char * const vstring_table[] PROGMEM = {vRangeName[0], vRangeName[1], vRangeName[2], vRangeName[3], vRangeName[4], vRangeName[5], vRangeName[6], vRangeName[7], vRangeName[8], vRangeName[9]};
const char hRangeName[10][6] PROGMEM = {"200ms", "100ms", " 50ms", " 20ms", " 10ms", "  5ms", "  2ms", "  1ms", "500us", "200us"}; // Hrizontal отображать символы
const char * const hstring_table[] PROGMEM = {hRangeName[0], hRangeName[1], hRangeName[2], hRangeName[3], hRangeName[4], hRangeName[5], hRangeName[6], hRangeName[7], hRangeName[8], hRangeName[9]};
const PROGMEM float hRangeValue[] = { 0.2, 0.1, 0.05, 0.02, 0.01, 0.005, 0.002, 0.001, 0.5e-3, 0.2e-3}; // значение горизонтального диапазона в секунду. (= 25 пикселей на экране)

int waveBuff[REC_LENG];        // буфер формы волны (оставшийся объем ОЗУ практически отсутствует)
char chrBuff[8];               // отображаем строковый буфер
char hScale[] = "xxxAs";       // символ горизонтальной шкалы
char vScale[] = "xxxx";        // виртуальная шкала

float lsb5V = 0.00566826;      // коэффициент чувствительности диапазона 5 В. стандартный = 0.00563965 1.1 * 630 / (1024 * 120)
float lsb50V = 0.05243212;     // коэффициент чувствительности диапазона 50 В. стандартный = 0.0512898 1.1 * 520.91 / (1024 * 10.91)

volatile int vRange;           // V-диапазон 0:A50V,  1:A 5V,  2:50V,  3:20V,  4:10V,  5:5V,  6:2V,  7:1V,  8:0.5V,  9:0.2V
volatile int hRange;           // Н-диапазон Н: 0:200ms, 1:100ms, 2:50ms, 3:20ms, 4:10ms, 5:5ms, 6;2ms, 7:1ms, 8:500us, 9;200us
volatile int trigD;            // флаг наклона триггера, 0: положительный 1: отрицательный
volatile int scopeP;           // номер позиции области действия. 0: вертикаль, 1: хризонталь, 2: спусковой крючок
volatile boolean hold = false; // удерживать флаг
volatile boolean switchPushed = false; // флаг переключателя нажал!
volatile int saveTimer;        // оставшееся время для сохранения EEPROM
int timeExec;                  // прибл. время выполнения текущей настройки диапазона (мс)

int dataMin;                   // минимальное значение буфера (самое малое = 0)// (smallest=0)
int dataMax;                   // максимальное значение (самое большое = 1023) // (largest=1023)
int dataAve;                   // 10-кратное среднее значение (используйте 10-кратное значение, чтобы сохранить точность. Итак, max = 10230)
int rangeMax;                  // значение буфера для графа полным ходом
int rangeMin;                  // значение буфера графа ботто
int rangeMaxDisp;              // отображаем значение макс. (Значение 100x)
int rangeMinDisp;              // отображаем значение, если мин.
int trigP;                     // вызвать указатель положения на буфере данных
boolean trigSync;              // флаг триггера обнаружен
int att10x;                    // 10-кратный аттенюатор включен (действует, когда 1)

float waveFreq;                // частота (Гц)
float waveDuty;                // коэффициент заполнения (%)

void setup() {
  pinMode(2, INPUT_PULLUP);    // прерывание по нажатию кнопки (int.0 IRQ)
  pinMode(8, INPUT_PULLUP);    // кнопка выбора
  pinMode(9, INPUT_PULLUP);    // вверх
  pinMode(10, INPUT_PULLUP);   // Вниз
  pinMode(11, INPUT_PULLUP);   // Держать
  pinMode(12, INPUT);          // 1/10 аттенюатор (Off = High-Z, Enable = Output Low)
  pinMode(13, OUTPUT);         // СВЕТОДИОД

  // Serial.begin(115200);     // meny RAM используется при активации (может быть сбой!)
  if (! oled.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {// выберите 3C или 3D (установите свой адрес OLED I2C)
    for (;;);                  // цикл навсегда
  }
  auxFunctions();              // Измерение напряжения (никогда не возвращается)
  loadEEPROM();                // читаем последние настройки из EEPROM
  analogReference(INTERNAL);  // АЦП полная шкала = 1,1 В
  attachInterrupt(0, pin2IRQ, FALLING); // активировать IRQ в режиме падающего фронта
  startScreen();               // отображаем стартовое сообщение
}

void loop () {
  setConditions();            // установить условия измерения
  digitalWrite(13, HIGH);     // мигает светодиод
  readWave();                 // читаем форму волны и сохраняем в буферную память
  digitalWrite(13, LOW);      // стоп светодиод
  setConditions();            // снова устанавливаем условия измерения (отражаем изменение во время измерения)
  dataAnalize();              // анализ данных
  writeCommonImage();         // записать фиксированное изображение на экране (2,6 мс)
  plotData();                 // график формы волны (10-18мс)
  dispInf();                  // отображаем информацию (6,5-8,5 мс)
  oled.display();             // отправляем экранный буфер в OLED (37мс)
  saveEEPROM();               // сохранить настройки в EEPROM при необходимости
  while (hold == true) {       // ждать если флаг Hold включен
    dispHold();
    delay(10);
  }                            // скорость цикла цикла = 60-470 мс (размер буфера = 200)
}

void setConditions () {                    // настройка условия измерения
  // получаем имя диапазона из PROGMEM
  strcpy_P (hScale, (char *) pgm_read_word (& (hstring_table [hRange]))); // Имя диапазона H
  strcpy_P (vScale, (char *) pgm_read_word (& (vstring_table [vRange]))); // V имя диапазона

  switch (vRange) {                        // настройка Vrange
    case 0: {                              // диапазон Auto50V
        att10x = 1;                        // используем входной аттенюатор
        break;
      }
    case 1: {                              // Авто диапазон 5 В
        att10x = 0;                        // нет аттенюатора
        break;
      }
    case 2: {                               // диапазон 50 В
        rangeMax = 50 / lsb50V;             // устанавливаем полный счетчик числа пикселей
        rangeMaxDisp = 5000;                // виртуальная шкала (значение set100x)
        rangeMin = 0;
        rangeMinDisp = 0;
        att10x = 1;                         // используем входной аттенюатор
        break;
      }
    case 3: {                               // диапазон 20 В
        rangeMax = 20 / lsb50V;             // устанавливаем полный счетчик числа пикселей
        rangeMaxDisp = 2000;
        rangeMin = 0;
        rangeMinDisp = 0;
        att10x = 1;                         // используем входной аттенюатор
        break;
      }
    case 4: {                               // диапазон 10 В
        rangeMax = 10 / lsb50V;             // устанавливаем полный счетчик числа пикселей
        rangeMaxDisp = 1000;
        rangeMin = 0;
        rangeMinDisp = 0;
        att10x = 1;                         // используем входной аттенюатор
        break;
      }
    case 5: {                               // диапазон 5 В
        rangeMax = 5 / lsb5V;               // устанавливаем полный счетчик числа пикселей
        rangeMaxDisp = 500;
        rangeMin = 0;
        rangeMinDisp = 0;
        att10x = 0;                         // нет входного аттенюатора
        break;
      }
    case 6: {                               // диапазон 2 В
        rangeMax = 2 / lsb5V;               // устанавливаем полный счетчик числа пикселей
        rangeMaxDisp = 200;
        rangeMin = 0;
        rangeMinDisp = 0;
        att10x = 0;                         // нет входного аттенюатора
        break;
      }
    case 7: {                               // диапазон 1 В
        rangeMax = 1 / lsb5V;               // устанавливаем полный счетчик числа пикселей
        rangeMaxDisp = 100;
        rangeMin = 0;
        rangeMinDisp = 0;
        att10x = 0;                         // нет входного аттенюатора
        break;
      }
    case 8: {                               // диапазон 0,5 В
        rangeMax = 0.5 / lsb5V;             // устанавливаем полный счетчик числа пикселей
        rangeMaxDisp = 50;
        rangeMin = 0;
        rangeMinDisp = 0;
        att10x = 0;                         // нет входного аттенюатора
        break;
      }
    case 9: {                               // диапазон 0,5 В
        rangeMax = 0.2 / lsb5V;             // устанавливаем полный счетчик числа пикселей
        rangeMaxDisp = 20;
        rangeMin = 0;
        rangeMinDisp = 0;
        att10x = 0;                         // нет входного аттенюатора
        break;
      }
  }
}

void writeCommonImage () {                // Обычное рисование изображения на экране
  oled.clearDisplay ();                   // стереть все (0,4 мс)
  oled.setTextColor (WHITE);              // пишем белым символом
  oled.setCursor (86, 0);                 // Начинаем с левого верхнего угла
  oled.println (F ("av V"));              // фиксированные символы 1-й строки
  oled.drawFastVLine (26, 9, 55, WHITE);  // левая линия
  oled.drawFastVLine (127, 9, 3, WHITE);  // правая вертикальная линия
  oled.drawFastVLine (127, 61, 3, WHITE); // правая вертикальная линия внизу

  oled.drawFastHLine (24, 9, 7, WHITE);   // Максимальное значение вспомогательной отметки
  oled.drawFastHLine(24, 36, 2, WHITE);
  oled.drawFastHLine(24, 63, 7, WHITE);

  oled.drawFastHLine(51, 9, 3, WHITE);    // Max value auxiliary mark
  oled.drawFastHLine(51, 63, 3, WHITE);

  oled.drawFastHLine(76, 9, 3, WHITE);    // Max value auxiliary mark
  oled.drawFastHLine(76, 63, 3, WHITE);

  oled.drawFastHLine(101, 9, 3, WHITE);   // Max value auxiliary mark
  oled.drawFastHLine(101, 63, 3, WHITE);

  oled.drawFastHLine(123, 9, 5, WHITE);   // правая сторона Max value вспомогательная отметка
  oled.drawFastHLine(123, 63, 5, WHITE);

  for (int x = 26; x <= 128; x += 5) {
    oled.drawFastHLine(x, 36, 2, WHITE);   // Рисуем центральную линию (горизонтальную линию) пунктирной линией
  }
  for (int x = (127 - 25); x > 30; x -= 25) {
    for (int y = 10; y < 63; y += 5) {
      oled.drawFastVLine(x, y, 2, WHITE);   // Рисуем 3 вертикальные линии пунктирными линиями
    }
  }
}

void readWave() {                             // Запись сигнала в массив памяти
  if (att10x == 1) {                          // если требуется 1/10 аттенюатора
    pinMode(12, OUTPUT);                      // назначаем вывод управления аттенюатором на OUTPUT,
    digitalWrite(12, LOW);                    // и вывод НИЗКИЙ (выход 0В)
  } else {                                    // если не требуется
    pinMode(12, INPUT);                       // назначаем контактный ввод (Hi-z)
  }
  switchPushed = false;                       // Очистить флаг операции переключения

  switch (hRange) {                           // установить условия записи в соответствии с номером диапазона
    case 0: {                                 // диапазон 200 мс
        timeExec = 1600 + 60;                 // Приблизительное время выполнения (мс) Используется для обратного отсчета до сохранения в EEPROM
        ADCSRA = ADCSRA & 0xf8;               // очистить дно 3 бита
        ADCSRA = ADCSRA | 0x07;               // коэффициент деления = 128 (по умолчанию Arduino)
        for (int i = 0; i < REC_LENG; i++) {  // до размера буфера записи
          waveBuff[i] = analogRead(0);        // читаем и сохраняем около 112us
          delayMicroseconds(7888);            // настройка времени
          if (switchPushed == true) {         // если какой-либо переключатель коснулся
            switchPushed = false;
            break;                            // отказаться от записи (это улучшает ответ)
          }
        }
        break;
      }
    case 1: {                         // диапазон 100 мс
        timeExec = 800 + 60;          // Приблизительное время выполнения (мс) Используется для обратного отсчета до сохранения в EEPROM
        ADCSRA = ADCSRA & 0xf8;       // очистить дно 3 бита
        ADCSRA = ADCSRA | 0x07;       // коэффициент деления = 128 (по умолчанию Arduino)
        for (int i = 0; i < REC_LENG; i++) {// до размера буфера записи
          waveBuff[i] = analogRead(0); // читаем и сохраняем около 112us
          // delayMicroseconds(3888);  // настройка времени
          delayMicroseconds(3860);     // настройка времени настроена
          if (switchPushed == true) {  // если какой-либо переключатель коснулся
            switchPushed = false;
            break;                     // отказаться от записи (это улучшает ответ)
          }
        }
        break;
      }
    case 2: {                           // диапазон 50 мс
        timeExec = 400 + 60;            // Приблизительное время выполнения (мс)
        ADCSRA = ADCSRA & 0xf8;         // очистить дно 3 бита
        ADCSRA = ADCSRA | 0x07;         // коэффициент деления = 128 (по умолчанию Arduino)
        for (int i = 0; i < REC_LENG; i++) {// до размера буфера записи
          waveBuff[i] = analogRead(0); // читаем и сохраняем около 112us
          // delayMicroseconds(1888);   // настройка времени
          delayMicroseconds(1880);      // настройка времени настроена

          if (switchPushed == true) {    // если какой-либо переключатель коснулся
            break;                       // отказаться от записи (это улучшает ответ)
          }
        }
        break;
      }
    case 3: {                            // диапазон 20 мс
        timeExec = 160 + 60;              // Приблизительное время выполнения (мс)
        ADCSRA = ADCSRA & 0xf8;           // очистить дно 3 бита
        ADCSRA = ADCSRA | 0x07;           // коэффициент деления = 128 (по умолчанию Arduino)
        for (int i = 0; i < REC_LENG; i++) {// до размера буфера записи
          waveBuff[i] = analogRead(0);    // читаем и сохраняем около 112us
          // delayMicroseconds(688);      // настройка времени
          delayMicroseconds(686);         // настройка времени настроена
          if (switchPushed == true) {     // если какой-либо переключатель коснулся
            break;                        // отказаться от записи (это улучшает ответ)
          }
        }
        break;
      }
    case 4: {                              // диапазон 10 мс
        timeExec = 80 + 60;                // Приблизительное время выполнения (мс)
        ADCSRA = ADCSRA & 0xf8;            // очистить дно 3 бита
        ADCSRA = ADCSRA | 0x07;            // коэффициент деления = 128 (по умолчанию Arduino)
        for (int i = 0; i < REC_LENG; i++) {// до размера буфера записи
          waveBuff[i] = analogRead(0);     // читаем и сохраняем около 112us
          // delayMicroseconds(288);       // настройка времени
          delayMicroseconds(287);          // настройка времени настроена
          if (switchPushed == true) {      // если какой-либо переключатель коснулся
            break;                         // отказаться от записи (это улучшает ответ)
          }
        }
        break;
      }
    case 5: {                               // диапазон 5 мс
        timeExec = 40 + 60;                 // Приблизительное время выполнения (мс)
        ADCSRA = ADCSRA & 0xf8;             // очистить дно 3 бита
        ADCSRA = ADCSRA | 0x07;             // коэффициент деления = 128 (по умолчанию Arduino)
        for (int i = 0; i < REC_LENG; i++) {// до размера буфера записи
          waveBuff[i] = analogRead(0);      // читать и сохранять около 112 мкс
          // delayMicroseconds(88);         // настройка времени
          delayMicroseconds(87);            // настройка времени настроена
          if (switchPushed == true) {       // если какой-либо переключатель коснулся
            break;                          // отказаться от записи (это улучшает ответ)
          }
        }
        break;
      }
    case 6: {                               // диапазон 2 мс
        timeExec = 16 + 60;                 // Приблизительное время выполнения (мс)
        ADCSRA = ADCSRA & 0xf8;             // очистить дно 3 бита
        ADCSRA = ADCSRA | 0x06;             // коэффициент деления = 64 (0x1 = 2, 0x2 = 4, 0x3 = 8, 0x4 = 16, 0x5 = 32, 0x6 = 64, 0x7 = 128)
        for (int i = 0; i < REC_LENG; i++) {// до размера буфера записи
          waveBuff[i] = analogRead(0);      // прочитать и сохранить около 56us
          // delayMicroseconds(24);         // настройка времени
          delayMicroseconds(23);            // настройка времени настроена
        }
        break;
      }
    case 7: {                               // диапазон 1 мс
        timeExec = 8 + 60;                  // Приблизительное время выполнения (мс)
        ADCSRA = ADCSRA & 0xf8;             // очистить дно 3 бита
        ADCSRA = ADCSRA | 0x05;             // коэффициент деления = 16 (0x1 = 2, 0x2 = 4, 0x3 = 8, 0x4 = 16, 0x5 = 32, 0x6 = 64, 0x7 = 128)
        for (int i = 0; i < REC_LENG; i++) {// до размера буфера записи
          waveBuff[i] = analogRead(0);      // читаем и сохраняем около 28us
          // delayMicroseconds(12);         // настройка времени
          delayMicroseconds(10);            // настройка времени настроена
        }
        break;
      }
    case 8: {                               // диапазон 500us
        timeExec = 4 + 60;                  // Приблизительное время выполнения (мс)
        ADCSRA = ADCSRA & 0xf8;             // очистить дно 3 бита
        ADCSRA = ADCSRA | 0x04;             // коэффициент деления = 16 (0x1 = 2, 0x2 = 4, 0x3 = 8, 0x4 = 16, 0x5 = 32, 0x6 = 64, 0x7 = 128)
        for (int i = 0; i < REC_LENG; i++) {// до размера буфера записи
          waveBuff[i] = analogRead(0);      // читаем и сохраняем около 16us
          delayMicroseconds(4);             // настройка времени
          // точная настройка времени 0.0625 x 8 = 0.5us(nop=0.0625us  при 16 МГц)
          asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop");
        }
        break;
      }
    case 9: {// 200us range
        timeExec = 2 + 60;                  // Приблизительное время выполнения (мс)
        ADCSRA = ADCSRA & 0xf8;             // очистить дно 3 бита
        ADCSRA = ADCSRA | 0x02;             // коэффициент деления = 4 (0x1 = 2, 0x2 = 4, 0x3 = 8, 0x4 = 16, 0x5 = 32, 0x6 = 64, 0x7 = 128)
        for (int i = 0; i < REC_LENG; i++) {// до размера буфера записи
          waveBuff[i] = analogRead(0);      // читаем и сохраняем около 6us
          // точная настройка времени 0.0625 x 8 = 0.5us(nop=0.0625us при 16 МГц)
          asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop");
          asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop"); asm("nop");
        }
        break;
      }
  }
}

void dataAnalize() {                        // получить различную информацию из формы волны
  int d;
  long sum = 0;

  // поиск максимального и минимального значения
  dataMin = 1023;                           // минимальное значение инициализируется большим числом
  dataMax = 0;                              // максимальное значение инициализируется небольшим числом
  for (int i = 0; i < REC_LENG; i++) {      // поиск максимального минимального значения
    d = waveBuff [i];
    sum = sum + d;
    if (d < dataMin) {                      // обновить мин
      dataMin = d;
    }
    if (d > dataMax) {                      // обновить max
      dataMax = d;
    }
  }

  // рассчитать среднее
  dataAve = (sum + 10) / 20;                // Расчет среднего значения (рассчитан в 10 раз для повышения точности)

  // определяем максимальное минимальное значение дисплея
  if (vRange <= 1) {                        // if Autorabge (Номер диапазона <= 1)
    rangeMin = dataMin - 20;                // поддерживаем нижнее поле 20
    rangeMin = (rangeMin / 10) * 10;        // раунд 10
    if (rangeMin <0) {
      rangeMin = 0;                         // не меньше 0
    }
    rangeMax = dataMax + 20;                // устанавливаем верх дисплея на максимум данных +20
    rangeMax = ((rangeMax / 10) + 1) * 10;  // округляем до 10
    if (rangeMax> 1020) {
      rangeMax = 1023;                      // если больше 1020, удерживать на 1023
    }

    if (att10x == 1) {                          // если используется 10-кратный аттенюатор
      rangeMaxDisp = 100 * (rangeMax * lsb50V); // диапазон отображения определяется данными. (верхний предел до полной шкалы АЦП)
      rangeMinDisp = 100 * (rangeMin * lsb50V); // ниже зависит от данных, но ноль или больше
    } else {                                    // если аттенюатор не используется
      rangeMaxDisp = 100 * (rangeMax * lsb5V);
      rangeMinDisp = 100 * (rangeMin * lsb5V);
    }
  } else {// if fix range
    // Напишите здесь необходимый код (пока нет)
  }

  // Триггер поиска позиции
  for (trigP = ((REC_LENG / 2) - 51); trigP < ((REC_LENG / 2) + 50); trigP++) {// Найти точки, которые находятся между медианой в центре ± 50 диапазона данных
    if (trigD == 0) {                             // если направление запуска положительное
      if ((waveBuff[trigP - 1] < (dataMax + dataMin) / 2) && (waveBuff[trigP] >= (dataMax + dataMin) / 2)) {
        break;                                    // положительная позиция триггера найдена!
      }
    } else {                                      // направление триггера отрицательное
      if ((waveBuff[trigP - 1] > (dataMax + dataMin) / 2) && (waveBuff[trigP] <= (dataMax + dataMin) / 2)) {
        break;
      }                                           // найден отрицательный триггер!
    }
  }
  trigSync = true;
  if (trigP >= ((REC_LENG / 2) + 50)) {           // Если триггер не найден в диапазоне
    trigP = (REC_LENG / 2);                       // Установите его в центр на данный момент
    trigSync = false;                             // устанавливаем флаг отображения Unsync
  }
  if ((dataMax - dataMin) <= MIN_TRIG_SWING) {    // амплитуда сигнала меньше указанного значения
    trigSync = false;                             // устанавливаем флаг отображения Unsync
  }
  freqDuty();
}

void freqDuty() {                                     // определить частоту и значение коэффициента заполнения по данным сигнала
  int swingCenter;                                    // центр волны (половина пп)
  float p0 = 0;                                       // 1-й положительный край
  float p1 = 0;                                       // общая длина циклов
  float p2 = 0;                                       // общая длительность импульса высокого времени
  float pFine = 0;                                    // точная позиция (0-1.0)
  float lastPosiEdge;                                 // последняя положительная крайняя позиция

  float pPeriod;                                      // период импульса
  float pWidth;                                       // длительность импульса

  int p1Count = 0;                                    // количество циклов волны
  int p2Count = 0;                                    // Счетчик времени

  boolean a0Detected = false;
//  boolean b0Detected = false;
  boolean posiSerch = true;                           // true при поиске положительного края

  swingCenter = (3 * (dataMin + dataMax)) / 2;        // вычисляем значение волнового центра

  for (int i = 1; i <REC_LENG - 2; i++) {             // сканировать весь буфер
    if (posiSerch == true) {                          // posi slope (частота поиска)
      if ((sum3(i) <= swingCenter) && (sum3(i + 1)> swingCenter)) { // если по центру при подъеме (+ -3 данные используются для устранения шума)
        pFine = (float)(swingCenter - sum3(i)) / ((swingCenter - sum3(i)) + (sum3(i + 1) - swingCenter) ); // точный расчет точки пересечения
        if (a0Detected == false) {                    // if 1-й крестик
          a0Detected = true;                          // установить флаг поиска
          p0 = i + pFine;                             // сохранить эту позицию как стартовую позицию
        } else {
          p1 = i + pFine - p0;                        // длина записи (длина n * времени цикла)
          p1Count++;
        }
        lastPosiEdge = i + pFine;                     // местоположение записи для расчета Pw
        posiSerch = false;
      }
    } else {   // nega slope serch (duration serch)
      if ((sum3 (i)> = swingCenter) && (sum3 (i + 1) <swingCenter)) {  // если по центру при падении (+ -3 данные используются для устранения шума)
        pFine = (float)(sum3(i) - swingCenter) / ((sum3(i) - swingCenter) + (swingCenter - sum3(i + 1)) );
        if (a0Detected == true) {
          p2 = p2 + (i + pFine - lastPosiEdge);       // рассчитаем ширину импульса и накапливаем ее
          p2Count++;
        }
        posiSerch = true;
      }
    }
  }

  pPeriod = p1 / p1Count;                            // период импульса
  pWidth = p2 / p2Count;                             // ширина пальца

  waveFreq = 1.0 / ((pgm_read_float (hRangeValue + hRange) * pPeriod) / 25.0); // частота
  waveDuty = 100.0 * pWidth / pPeriod;               // коэффициент заполнения
}

int sum3(int k) {                                    // Сумма до, после и собственного значения
  int m = waveBuff[k - 1] + waveBuff[k] + waveBuff[k + 1];
  return m;
}

void startScreen() {                                 // Staru up screen
  oled.clearDisplay();
  oled.setTextSize(2);                               // с двойным размером символа
  oled.setTextColor(WHITE);
  oled.setCursor(10, 15);
  oled.println(F("PMO-RP1"));                        // Заголовок (Osilloscope для бедных, RadioPench 1)
  oled.setCursor(10, 35);
  oled.println(F("    v2.0"));                       // номер версии
  oled.display();                                    // фактическое отображение здесь
  delay(1500);
  oled.clearDisplay();
  oled.setTextSize(1);                               // После этого стандартный размер шрифта
}

void dispHold() {                                    // отображать "Hold"
  oled.fillRect(42, 11, 24, 8, BLACK);               // черная краска 4 символа
  oled.setCursor(42, 11);
  oled.print(F("Hold"));                             // Держать
  oled.display();                                    //
}

void dispInf() {                                     // Отображение различной информации
  float voltage;
  // отображать вертикальную чувствительность
  oled.setCursor(2, 0);                              // вокруг верхнего левого
  oled.print(vScale);                                // значение вертикальной чувствительности
  if (scopeP == 0) {                                 // если область видимости
    oled.drawFastHLine(0, 7, 27, WHITE);             // отображаем метку в нижней части
    oled.drawFastVLine(0, 5, 2, WHITE);
    oled.drawFastVLine(26, 5, 2, WHITE);
  }

  // скорость горизонтальной развертки
  oled.setCursor(34,0);                              //
  oled.print(hScale);                                // отображаем скорость развертки (время / дел)
  if (scopeP == 1) {                                 // если область видимости
    oled.drawFastHLine(32, 7, 33, WHITE);            // отображаем метку в нижней части
    oled.drawFastVLine(32, 5, 2, WHITE);
    oled.drawFastVLine(64, 5, 2, WHITE);
  }

  // запуск полярности
  oled.setCursor(75,0);                              // в верхнем центре
  if (trigD == 0) {                                  // если положительный
    oled.print(char(0x18));                          // вверх
  } else {
    oled.print(char(0x19));                          // вниз ↓
  }
  if (scopeP == 2) {                                 // если область видимости
    oled.drawFastHLine(71, 7, 13, WHITE);            // отображаем метку в нижней части
    oled.drawFastVLine(71, 5, 2, WHITE);
    oled.drawFastVLine(83, 5, 2, WHITE);
  }

  // среднее напряжение
  if (att10x == 1) {                                  // если используется 10-кратный аттенюатор
    voltage = dataAve * lsb50V / 10.0;                // значение диапазона 50В
  } else {// нет!
    voltage = dataAve * lsb5V / 10.0;                 // 5V значение диапазона
  }
  if (voltage <10.0) {                                // если меньше 10 В
    dtostrf(voltage, 4, 2, chrBuff);                  // формат x.xx
  } else {// нет!
    dtostrf(voltage, 4, 1, chrBuff);                  // формат xx.x
  }
  oled.setCursor(98, 0);                              // вокруг верхнего правого
  oled.print(chrBuff);                                // отображаем среднее напряжение 圧 の 平均 値 を 表示
  // oled.print(saveTimer);                           // использовать здесь для отладки

  // линии шкалы
  voltage = rangeMaxDisp / 100.0;                     // согласовать максимальное напряжение
  if (vRange == 1 || vRange> 4) {                     // если диапазон ниже 5 В или Авто 5 В
    dtostrf(voltage, 4, 2, chrBuff);                  // формат *. **
  } else {// нет!
    dtostrf(voltage, 4, 1, chrBuff);                  // форматировать **. *
  }
  oled.setCursor(0, 9);
  oled.print(chrBuff);                                // отображаем максимальное значение

  voltage = (rangeMaxDisp + rangeMinDisp) / 200,0;    // вычисление значения центра
  if (vRange == 1 || vRange> 4) {                     // если диапазон ниже 5 В или Авто 5 В
    dtostrf(voltage, 4, 2, chrBuff);                  // формат *. **
  } else {// нет!
    dtostrf(voltage, 4, 1, chrBuff);                  // форматировать **. *
  }
  oled.setCursor(0, 33);
  oled.print(chrBuff);                                // отображаем значение

  voltage = rangeMinDisp / 100.0;                     // convart Min vpltage
  if (vRange == 1 || vRange> 4) {                     // если диапазон ниже 5 В или Авто 5 В
    dtostrf(voltage, 4, 2, chrBuff);                  // формат *. **
  } else {// нет!
    dtostrf(voltage, 4, 1, chrBuff);                  // форматировать **. *
  }
  oled.setCursor(0, 57);
  oled.print(chrBuff);                                // отображаем значение

  // отображаем частоту, коэффициент заполнения или триггер пропущен
  if (trigSync == false) {                             // Если триггерная точка не найдена
    oled.fillRect(92, 14, 24, 8, BLACK);               // черная краска 4 символа
    oled.setCursor(92, 14);                            //
    oled.print(F("unSync"));                           // dosplay Unsync
  } else {
    oled.fillRect(91, 12, 25, 9, BLACK);               // стираем частоту
    oled.setCursor(92, 13);                            // установить расположение дисплея
    if (waveFreq <100.0) {                             // если меньше 100 Гц
      oled.print(waveFreq, 1);                         // отображаем 99,9 Гц
      oled.print(F("Hz"));
    } else if(waveFreq <1000.0) {                      // если меньше 1000 Гц
      oled.print(waveFreq, 0);                         // отображение 999 Гц
      oled.print(F("Hz"));
    } else if(waveFreq <10000.0) {                     // если меньше 10 кГц
      oled.print((waveFreq / 1000.0), 2);              // отображаем 9.99kH
      oled.print(F("kH"));
    } else {                                           // если больше
      oled.print((waveFreq / 1000.0), 1);              // отображаем 99.9kH
      oled.print(F("kH"));
    }
    oled.fillRect(97, 21, 25, 10, BLACK);              // стираем область Freq (как можно меньше)
    oled.setCursor(98, 23);                            // установить местоположение
    oled.print(waveDuty, 1);                           // отображать долг (высокий уровень отношения) в%
    oled.print(F("%"));
  }
}

void plotData() {                                                      // построение волновой формы на OLED
  long y1, y2;
  for (int x = 0; x <= 98; x++) {
    y1 = map(waveBuff[x + trigP - 50], rangeMin, rangeMax, 63, 9);     // преобразовать в адрес участка
    y1 = constrain(y1, 9, 63);                                         // Раздавить (Насытить) выступающую часть
    y2 = map(waveBuff[x + trigP - 49], rangeMin, rangeMax, 63, 9);     // обратиться к расчету
    y2 = constrain(y2, 9, 63);                                         //
    oled.drawLine(x + 27, y1, x + 28, y2, WHITE);                      // соединяем точку
  }
}

void saveEEPROM() {                       // Сохранить значение настройки в EEPROM через некоторое время после нажатия кнопки.
  if (saveTimer> 0) {                     // Если значение таймера положительное,
    saveTimer = saveTimer - timeExec;     // Таймер вычитания
    if (saveTimer <0) {                   // если время истекло
      EEPROM.write(0, vRange);            // сохранить текущий статус в EEPROM
      EEPROM.write(1, hRange);
      EEPROM.write(2, trigD);
      EEPROM.write(3, scopeP);
    }
  }
}

void loadEEPROM() {                         // Считать значения настроек из EEPROM (ненормальные значения будут исправлены по умолчанию)
  int x;
  x = EEPROM.read(0);                       // vRange
  if ((x <0) || (9 <x)) {                   // если вне 0-9
    x = 3;                                  // значение по умолчанию
  }
  vRange = x;

  x = EEPROM.read(1);                       // hRange
  if ((x <0) || (9 <x)) {                   // если из 0-9
    x = 3;                                  // значение по умолчанию
  }
  hRange = x;
  x = EEPROM.read(2);                       // trigD
  if ((x <0) || (1 <x)) {                   // если из 0-1
    x = 1;                                  // значение по умолчанию
  }
  trigD = x;
  x = EEPROM.read(3);                       // scopeP
  if ((x <0) || (2 <x)) {                   // если из 0-2
    x = 1;                                  // значение по умолчанию
  }
  scopeP = x;
}

void auxFunctions() {                       // функция измерителя напряжения
  float voltage;
  long x;
  if (digitalRead(8) == LOW) {              // если нажата кнопка SELECT, измерьте напряжение батареи
    analogReference(DEFAULT);               // полная шкала АЦП установлена ​​на Vcc
    while (1) {                             // делать вечно
      x = 0;
      for (int i = 0; i <100; i ++) {       // 100 раз
        x = x + analogRead(1);              // считываем напряжение на контакте A1 и накапливаем
      }
      voltage = (x / 100.0) * 5.0 / 1023.0; // преобразовать значение напряжения
      oled.clearDisplay();                  // весь экран стирания (0,4 мс)
      oled.setTextColor(WHITE);             // пишем белым символом
      oled.setCursor(20, 16);               //
      oled.setTextSize(1);                  // символ стандартного размера
      oled.println(F("Battery voltage"));
      oled.setCursor(35, 30);               //
      oled.setTextSize(2);                  // символ двойного размера
      dtostrf(voltage, 4, 2, chrBuff);      // отображаем напряжение батареи x.xxV
      oled.print(chrBuff);
      oled.println(F("V"));
      oled.display();
      delay(150);
    }
  }
  if (digitalRead(9) == LOW) {         // если нажата кнопка ВВЕРХ, диапазон 5 В
    analogReference(INTERNAL);
    pinMode(12, INPUT);                // Установить вывод управления аттенюатором в Hi-z (использовать в качестве входа)
    while (1) {                        // делай вечно,
      digitalWrite(13, HIGH);          // мигает светодиод
      voltage = analogRead(0) * lsb5V;; // измеряем напряжение
      oled.clearDisplay();             // стереть экран (0,4 мс)
      oled.setTextColor(WHITE);        // пишем белым символом
      oled.setCursor(26, 16);          //
      oled.setTextSize (1);            // по размеру символа
      oled.println(F("DVM 5V Range"));
      oled.setCursor(35, 30);          //
      oled.setTextSize(2);             // символ двойного размера
      dtostrf(voltage, 4, 2, chrBuff); // отображаем напряжение батареи x.xxV
      oled.print (chrBuff);
      oled.println(F("V"));
      oled.display();
      digitalWrite(13, LOW);            // остановить светодиодную вспышку
      delay(150);
    }
  }
  if (digitalRead(10) == LOW) {         // если нажата кнопка ВНИЗ, диапазон 50 В
    analogReference(INTERNAL);
    pinMode(12, OUTPUT);                // Установите вывод управления аттенюатора на OUTPUT
    digitalWrite(12, LOW);              // вывод НИЗКИЙ
    while (1) {                         // делать вечно
      digitalWrite(13, HIGH);           // светодиодный индикатор
      voltage = analogRead(0) * lsb50V; // измеряем напряжение
      oled.clearDisplay();              // стереть экран (0,4 мс)
      oled.setTextColor(WHITE);         // пишем белым символом
      oled.setCursor(26, 16);           //
      oled.setTextSize(1);              // по размеру символа
      oled.println(F("DVM 50V Range"));
      oled.setCursor(35, 30);           //
      oled.setTextSize(2);              // символ двойного размера
      dtostrf(voltage, 4, 1, chrBuff);  // отображаем напряжение батареи xx.xV
      oled.print(chrBuff);
      oled.println(F("V"));
      oled.display();
      digitalWrite(13, LOW);            // остановить светодиодную вспышку
      delay(150);
    }
  }
}

void pin2IRQ() {                    // Pin2 (int.0) обработчик прерываний
  // Кнопки Pin 8,9,10,11 связаны с диодами и подключены к Pin2.
  // Итак, если какая-либо кнопка нажата, эта процедура запустится.
  int x;                            // Информация о порте, содержащая переменную

  x = PINB;                         // читать статус порта B
  if ((x & 0x07)! = 0x07) {         // если младшие 3 бита не все Hi (любой нажат)
    saveTimer = 5000;               // установить таймер сохранения EEPROM на 5 секунд
    switchPushed = true;            // переключаем нажатием Falag ON
  }
  if ((x & 0x01) == 0) {            // если нажата кнопка выбора (Pin8),
    scopeP ++;                      // вперед позиция видимости
    if (scopeP> 2) {                // if верхний предел
      scopeP = 0;                   // переместимся в начальную позицию
    }
  }

  if ((x & 0x02) == 0) {            // если нажата кнопка UP (Pin9), и
    if (scopeP == 0) {              // диапазон по вертикали
      vRange ++;                    // V-диапазон вверх!
      if (vRange> 9) {              // if верхний предел
        vRange = 9;                 // оставайся как есть
      }
    }
    if (scopeP == 1) {               // если диапазон хризонтального диапазона
      hRange ++;                     // H-диапазон вверх!
      if (hRange> 9) {               // if верхний предел
        hRange = 9;                  // оставайся как есть
      }
    }
    if (scopeP == 2) {               // если ограничено триггером
      trigD = 0;                     // установить триггерное значение +
    }
  }

  if ((x & 0x04) == 0) {             // если нажата кнопка ВНИЗ (Pin10), и
    if (scopeP == 0) {               // диапазон по вертикали
      vRange--;                      // V-диапазон ВНИЗ
      if (vRange <0) {               // if bottom
        vRange = 0;                  // оставайся как есть
      }
    }
    if (scopeP == 1) {               // если диапазон хризонтального диапазона
      hRange--;                      // H-диапазон ВНИЗ
      if (hRange <0) {               // if bottom
        hRange = 0;                  // Сатай как есть
      }
    }
    if (scopeP == 2) {               // если ограничено триггером
      trigD = 1;                     // установить пусковое значение триггера на -
    }
  }

  if ((x & 0x08) == 0) {             // если нажата кнопка HOLD (pin11)
    hold = ! hold;                   // меняет флаг
  }
}

 

serhiy58
Offline
Зарегистрирован: 19.06.2019

Было-бы на дисплее 5110 - уже бы протестировал, - интересная штучка. Похоже там есть элементы автоматических установок...

smokok
smokok аватар
Offline
Зарегистрирован: 08.06.2018

serhiy58 пишет:

Было-бы на дисплее 5110 - уже бы протестировал,

Я пытался портировать, но что то не получилось(((. Так же как и на 1230.

serhiy58
Offline
Зарегистрирован: 19.06.2019

Я пробовал переделать версию 1230 и 5110 под  дисплей от Нокии 3510(монохром 96х65, только там контроллер PCF8814), но тоже результат отрицательный... 

Andry Smart
Offline
Зарегистрирован: 06.09.2016

ну и код.... русскоговорящему тяжеловато понять. я так понял кнопки на прерывании висят. через диоды развязка.

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

но все же много чего я вообще не понял. особенно с заморочками храниния текста в ЕЕПРОМ... (видно для экономии оперативки)

 

но в целом весьма интересная прошивка.

Alex-Bee
Offline
Зарегистрирован: 13.03.2020

>ну и код....
Это его вызов молодым :) "После 60 лет я бросаю вызов каждый день, чувствуя, что мое зрение и ловкость снижаются.."
Вот как он, примерно) описывает возможности своего изделия:
Основные характеристики
・ Вертикальная чувствительность (значение для полной шкалы)
- Фиксированный диапазон: 50 В, 20 В, 10 В, 5 В, 2 В, 1 В, 0,5 В, 0,2 В.
- Автоматический диапазон: 50 В, 5 В (форма сигнала отображается максимально возможной). Верхний и нижний пределы настраиваются автоматически.
・ Горизонтальный диапазон (значение на деление, 4 деления = полная шкала)
- 50 мс, 20 мс, 10 мс, 5 мс, 2 мс, 1 мс, 500 мкс, 200 мкс,
・ Автоматический при уровне входного сигнала 1/2 от установленного PP (PP - промежуточная точка). Триггерный уровень ВВЕРХ/ВНИЗ может быть указан.
- Форма сигнала отображается, даже если триггер не обнаружен, а ошибка обнаружения триггера отображается (отображается как несинхронизированный).
Function Функция вольтметра
Отображает среднее значение в зависимости от формы сигнала на экране. Должен использоваться как простой вольтметр.
Hold Удержание экрана
Нажмите переключатель удержания, чтобы удерживать экран, затем снова нажмите переключатель, чтобы освободить удержание. Кроме того, во время удержания он отображается на экране как удержание.
Возобновит свою функциональность через 5 секунд после нажатия кнопки, настройки сохраняются в EEPROM. Сохраненное содержимое будет отражено при следующем включении питания.

Говорит, что прибор может измерять частоту до 25 КГц, если заполнение 50%. А если с хорошо просматриваемой формой сигнала, то до 10 КГц.
PS :)
Название у прибора интересное - PMO-RP 2.0
"Начальный экран PMO - это Осциллограф Бедного Человека (Poor Man Oscilloscope), а RP - аббревиатура радиогубцев (форум их, типа РадиоКот).
...Частота и коэффициент заполнения отображаются в правом верхнем углу области экрана осциллографа ...минимальная измеряемая частота 1,2 Гц".

 

Alex-Bee
Offline
Зарегистрирован: 13.03.2020

Много букв, надеюсь оно того стоит.
Хронологические заметки для лучшего понимания сути проекта.
1.
Сначала была задумка "Осциллографа типа ручки", версия 20190226 (V1.1)
http://radiopench.blog96.fc2.com/blog-entry-998.html
Pen style oscilloscope made of Arduino
https://www.youtube.com/watch?v=7QI9CRNEi2w
2.
Потом код переписывался, добавлялись разные "плюшки" и проект просто был перенесен на макетку. Для удобства.
Схема в DIP-корпусе (по сути) - всё та же, что и с вариантом на Ардуино.
"Осциллограф, сделанный на макетке и осциллограф-ручка используют одну и ту же программу.
(Функция измерения напряжения аккумулятора работает только с осциллографом ручного типа).
"
В новой прошивке (которая SSD1306) частота и коэффициент заполнения стали отображаются правильно.
От Автора:
"Конечная цель состояла в том, чтобы обновить версию осциллографа ручного типа и чтобы работа была завершена.
Функция счетчика частоты, добавленная в этой обновленной версии, вычисляет частоту на основе информации о сигнале хранимой в памяти.
Может быть легко добавлен и метод вычисления путем программного обеспечения.
Кроме того, поскольку частота измеряется в зависимости от формы волны, то это метод, который вряд ли приведет к ошибкам измерения.
Однако по производительности он будет уступать программному частотомеру
".

"При измерении частоты с помощью Arduino вы можете использовать счетчик таймера и прерывания для измерения с более высокой точностью.
Было бы интересно добавить этот метод измерения к осциллографу типа "ручка", но входной контакт, во-первых, отличается.
Во-вторых, аттенюатор включен для измерения аналоговых сигналов, что усложняет ситуацию.
Возможно подключение через соответствующий защитный резистор, но я обеспокоен тем, что входная емкость увеличится.
По этой причине, кажется, лучше не добавлять правильную функцию частотомера... но мне интересно, можно ли это сделать легко
".
3.
Потом Автору показалось, что мониторчик по-крупнее будет лучше видно :)
Появилась версия для SH1106.
"Принципиальная схема такая же, только программа изменена.
(Будьте внимательны, так как контакты источника питания и заземления поменялись местами на SH1106)
"

"Загрузите библиотеку SH1106 отсюда:
https://github.com/wonho-maker/Adafruit_SH1106
(Эту библиотеку нельзя загрузить из Arduino IDE. Поэтому установка должна быть произведена вами самостоятельно. Актуальность - 26 июня 2020 г.)"

Ну и характеристики "Осциллографа типа ручки", версия 20190226 (V1.1):
Specifications
Функция осциллографа
-Скорость максимальных выборок: 8мкс
-Длина записи: 200 данных
-Диапазон дисплея: 100 данных (фиксированная)
-Horizontal:. 200мс 100мс, 50мс, 20мс, 10мс, 5ms, 2ms, 1мс, 500us, 200us
-Vertical: 0,2 В, 0,5 В, 1 В, 2 В, 5 В, 10 В, 20 В, 50 В, А 5 В. A50V
Измерение только положительных напряжений.
Минимальное напряжение зафиксировано на уровне 0 В.
Однако в диапазонах A5V и A50V он автоматически расширяется, следуя минимуму / максимуму сигнала.
-Trigger: установлена ​​только функция автоматического запуска промежуточной точки PP (без нормального или одиночного запуска).
Положительный / отрицательный наклон можно выбрать.
Отображение ошибки обнаружения триггера (дисплей Unsync).
Другие функции:
Функция отображения среднего значения сигнала.
Функция удержания формы волны.
Настройка функции сохранения состояния (функция возобновления).

Функция цифрового мультиметра
Режим цифрового напряжения активируется одновременным нажатием кнопок при включении питания.
+ Кнопка: диапазон 5 В
+ кнопка: диапазон 50 В
+ кнопка SEL: отображение напряжения батареи.

Питание
2 батарейки ААА (также доступен никель-водородный)
ток потребления батареи: 52 мА при 2,4 В
срок службы батареи: около 12 часов
PS
"...что публика не знает точно, каким образом этот блог является глобальным стандартом HP, который мы поддерживаем за кулисами".
Теперь вы об этом знаете :)

Andry Smart
Offline
Зарегистрирован: 06.09.2016

что то не компилится. похоже библиотеки адафрут с джитхаба не подходят. 

 

пытаюсь попробовать с ssd1306 

 

если у кого компилит то поделитесь библиотеками плиззз

Alex-Bee
Offline
Зарегистрирован: 13.03.2020

>что то не компилится.
Это вы о японской ручке? Для ssd1306 попробовал, компилятор даже не ругнулся нигде:
"Скетч использует 22210 байт (72%) памяти устройства. Всего доступно 30720 байт.
Глобальные переменные используют 802 байт (39%) динамической памяти, оставляя 1246 байт для локальных переменных. Максимум: 2048 байт
".

С SH1106 - тоже никаких проблем, только память забита чуть поболее:
"Скетч использует 20376 байт (66%) памяти устройства. Всего доступно 30720 байт.
Глобальные переменные используют 1815 байт (88%) динамической памяти, оставляя 233 байт для локальных переменных. Максимум: 2048 байт.
Недостаточно памяти, программа может работать нестабильно
".

 

Andry Smart
Offline
Зарегистрирован: 06.09.2016

а можете заархивировать библиотеки от адафрут и поделиться?

Andry Smart
Offline
Зарегистрирован: 06.09.2016
Arduino: 1.8.12 (Windows 7), Плата:"Arduino Nano, ATmega328P"
 
In file included from C:\Program Files (x86)\Arduino\libraries\Adafruit-GFX-Library-master\Adafruit_MonoOLED.cpp:20:0:
 
C:\Program Files (x86)\Arduino\libraries\Adafruit-GFX-Library-master\Adafruit_MonoOLED.h:30:10: fatal error: Adafruit_I2CDevice.h: No such file or directory
 
 #include <Adafruit_I2CDevice.h>
 
          ^~~~~~~~~~~~~~~~~~~~~~
 
compilation terminated.
 
exit status 1
Ошибка компиляции для платы Arduino Nano.
 
Этот отчёт будет иметь больше информации с
включенной опцией Файл -> Настройки ->
"Показать подробный вывод во время компиляции"
 
Alex-Bee
Offline
Зарегистрирован: 13.03.2020

У меня тоже Arduino: 1.8.12 (Windows 10х64), Плата:"Arduino ProMini, ATmega328P"
Вот моя библиотека, заархивированная прямо из рабочей папки IDE.
https://drive.google.com/file/d/1KB4V0p-fHhE98nXwoVMDzVhhsYJJtnB4/view?usp=sharing

Для SH1106 у меня ничем не отличается от той, что из ссылки выше.
PS
Я вам более скажу:
У меня вообще на диске нет файлов Adafruit_MonoOLED.cpp, Adafruit_MonoOLED.h да и самой библиотеки Adafruit-GFX-Library-master.
Да и в скетчах нет таких... Что вы компилите?

 

progrik
Offline
Зарегистрирован: 30.12.2018

seri0shka пишет:
progrik, посмотри void SendByte, там как-то лохмато, можно причесать?
сейчас не в состоянии. только с больницы. и это еще не все...

а в целом, это явная суходрочка. так, для общего развития. вместо того, чтобы подчистить и настроить алгоритмы, ты взялся за невнятную хрень. софтварная заливка 4,7 мс. ну вот зачем тебе 200+ ФПС?

сделай миллион к/с, но возьми старый код, и давай мериться)) там читалось 800 байт всегда. округлим до 1000. при минимальной скорости АЦП 10к ты сможешь наполнять буфер не более 10 раз/сек. и твои миллион к/с терпят фиаско, показывая только 10 к/с. я же сделал, что в рабочем режиме читается всего 256 байт - уже наполняем буф 40 раз/сек, ога? и мои 100+ фпс имеют твои миллион фпс в 3-4 раза. сила (мысли) алгоритмов...

ты вообще смотрел видосы разогнанного ослика? https://www.youtube.com/watch?v=ClhJJATps40 внимай на перемотку, чтобы понять скорость, установлен 1 пиксель на кадр. приблизительно за 1,5 сек проматывается экран(ослик справа, на левый не смотрел). то есть, около 60 к/с. чего тебе еще надобно, старче?)) дисплей и так с трудом поспевает. сделай хоть 2мс - прибавка будет ну пару кадров, ты же не просто заливаешь, еще же уйма кода работает, кроме вывода...

я знаю еще несколько способов ускорить вывод, но считаю это нецелесообразным. намного больший эффект дает оптимизация кода и алгоритмов...

Electronik83
Offline
Зарегистрирован: 06.12.2015

У меня какая то дурацкая есть склонность начинать все сначала. Я иногда играю в игры, но поиграв в игру и дойдя до какого то уровня и вспомнив про игру спустя пол года я начинаю играть в нее заново. Так же и с этим пультоскопом. Я уже раз десять переписал его заново. И никак не могу дойти до того финиша, который был сделан на этом "автомате". И еще - я этот проект как то подзабросил, уж очень много косяков в нем. Кто нибудь проверял на осциле, как работает генератор сигнала - да работает крайне криво. Ну кароче мне много что не нравится, и я не хочу опубликовывать косячный код. А тот код, который я сливал тут - это по сути код автора с моими доработками... Но я планирую скоро что нибудь выкинуть, тем более появилась некая обратная связь тут на этом форуме, где мне сказали, что графическое меню, над которым я долгое время потел - оказалось нахер ни кому не нужно. И я потом понял в чем дело. У 98% всех пользователей, которые заливали мою версию - китайский дисплейчик (я про 3310. Я пользую оригинал. После того, как с алика мне пришло несколько дисплеев - я понял в чем суть. Дисплеи просто гавно и я понимаю в чем дело. Про ssd1306 - молчу. У меня он тоже с китая. Плюс ко всему меня просили сделать то, сделать это. Я сделал. Обратки не ощутил. Зачем мне тогда что то делать для народа, если я могу взять, написать так, как мне надо и все. Прибором этим я не пользуюсь, т.к. у меня есть нормальный осцилл и что то там изобретать на коленке пропал интерес, но я хочу сильно ускорить вывод на дисплей и отказаться полностью от библ ардуино...... Будет, не будет - не знаю.....

Electronik83
Offline
Зарегистрирован: 06.12.2015

AIM2104 пишет:

 

Этож я рисовал - за один вечер на даче все сделал. Было вдохновение:) Только строчка внизу с орфографической ошибкой.......

progrik
Offline
Зарегистрирован: 30.12.2018

Electronik83 пишет:
Плюс ко всему меня просили сделать то, сделать это. Я сделал. Обратки не ощутил.
а номер кошеля пробовал указать для обратки?))) спасибо говорили?

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

Electronik83 пишет:
Только строчка внизу с орфографической ошибкой..
я бы сказал, с двумя)) OSCILLOSCOPE - нужно вместо S->C и две L - две ашыпки))

Electronik83
Offline
Зарегистрирован: 06.12.2015

del

Electronik83
Offline
Зарегистрирован: 06.12.2015

Andry Smart пишет:

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

Зачем ныть то? - листай всю ветку....

Electronik83
Offline
Зарегистрирован: 06.12.2015

progrik пишет:

Electronik83 пишет:
Плюс ко всему меня просили сделать то, сделать это. Я сделал. Обратки не ощутил.
а номер кошеля пробовал указать для обратки?))) спасибо говорили?

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

Electronik83 пишет:
Только строчка внизу с орфографической ошибкой..
я бы сказал, с двумя)) OSCILLOSCOPE - нужно вместо S->C и две L - две ашыпки))

Ну вот почему в интернете злые все? Похер до обратки - да. Мне тоже похер. Я писал код на интузиазме, для собственного саморазвития. Я не хочу с тобой ругаться, хочу наоборот - дружить. Но млин че то сделал - начинается эта критика в интернете: Нахера там те то цифры выводятся? Зачем ты сделал графику - тормозит же все и памяти мало! Сделай мне то, сделай другое!

А с тобой я не хочу ругаться, хочу дружить и корона (не вирус) тут не причем!

Ну раз уж все опираются на мой код - не иначе как обратка... 

progrik
Offline
Зарегистрирован: 30.12.2018

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

Andry Smart
Offline
Зарегистрирован: 06.09.2016

Спасибо скомпилил с этой библиотекой без проблем. но вот с кнопками какая то беда. почему то работает только одна кнопка UP.

[jnz кнопки опрашиваются. если нажимать при подаче питания то включаются режимы вольтметров и напряжение батареи

 

кто то еще пробовал эту схемку в работе? как у вас?

Alex-Bee
Offline
Зарегистрирован: 13.03.2020

Вариантов может быть, как минимум, три:
1. Человеческий фактор: невнимательность - попутана полярность диодов, провода идут не туда.
2. Железо: проблемы с D8, D10, D11 (брать пример blink, подключать светодиоды и пробовать ими моргать).
3. Программная... что вряд ли. На японском сайте есть отзывы... повторяли и все работает.

Вопрос-ответ с "РадиоГубцев"
Вопрос:
Есть строчка 666 в коде:
line x = x + analogRead (1);
но она никак не связана с реальной схемой, можно ли считать, что A1 → + 5V?

Строки 34 и 35 - мои собственные измерения?
float lsb5V = 0.00566826;      // sensivity coefficient of 5V range. std=0.00563965 1.1*630/(1024*120)
float lsb50V = 0.05243212;     // sensivity coefficient of 50V range. std=0.0512898 1.1*520.91/(1024*10.91)

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

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

Andry Smart
Offline
Зарегистрирован: 06.09.2016

я в шоке. проверил порты, работают.

проверил диоды, работают. и включены правильно.

единственное у меня резистора нет на 100 ом . на схеме он R4 (заменил перемычкой)

ну и А0 в воздухе. нет делителя входного но я не думаю, что это повлияло.

яя уже не знаю, что думать. на выходных разберусь а то на работе и так крыша едет.

Andry Smart
Offline
Зарегистрирован: 06.09.2016

разобрался с кнопками. так как они работают на прерываниях то не стоит игнорировать фильтр входной цепи. оказалось с резистором на 100 ом все заработало.

Alex-Bee
Offline
Зарегистрирован: 13.03.2020

Улыбнуло. Ваша ситуация похожа на китайскую "оптимизацию" с Али: "а уберём-ка мы эту ни на что не влияющую пиндюльку из схемы. Дешевле же будет..." А пом известные блогеры разбираются - "почему не работает или работает, но криво" :)

Andry Smart
Offline
Зарегистрирован: 06.09.2016

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

Alex-Bee
Offline
Зарегистрирован: 13.03.2020

Погоняете - поделитесь впечатлениями. Особенно ценно будет сравнение, если есть такая возможность.

uldin@mail.ru
Offline
Зарегистрирован: 15.02.2012

uldin@mail.ru
Offline
Зарегистрирован: 15.02.2012

uldin@mail.ru
Offline
Зарегистрирован: 15.02.2012

uldin@mail.ru
Offline
Зарегистрирован: 15.02.2012

uldin@mail.ru
Offline
Зарегистрирован: 15.02.2012

где 100 ом то, где?

uldin@mail.ru
Offline
Зарегистрирован: 15.02.2012

http://prntscr.com/tg6vxq

и как своё неправильное сообщение убить?