В прошлой статье шла речь о схеме, теперь статья о программном обеспечении осциллографического пробника. Скетч основан на материале этой статьи. Эмблему http://rcl-radio.ru из уважения к ресурсу не уберал. Изменения коснулись оформления экрана, добавления отдельно кнопки HOLD, подключение пина переключения диапазона. Провел опыты с делителем частоты, при коэффициенте 4 резко падает точность, 8 актуально. Разрядность можно выбирать - 8,9 или 10 бит (комментируя или разкомментируя строки) Текст скетча в конце статьи. Несколько фотографий работы пробника: #include //Библиотека изменения частоты PWM https://github.com/atmelino/Arduino/tree/master/libraries/PWM #include #include Adafruit_TFTLCD tft(A3, A2, A1, A0, A4); /// LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET // A3,A2,A1,A0,A4 // D0 pin 8, D1 pin 9, D2 pin 2, D3 pin 3, D4 pin 4, D5 pin 5, D6 pin 6,D7 pin 7 void setup() { tft.reset(); tft.begin(0x9341); Serial.begin(115200); tft.fillScreen(0x0000); tft.setTextColor(0xFFFF); tft.setTextSize(2); InitTimersSafe(); SetPinFrequencySafe(46, 5000); pinMode(46, OUTPUT); analogWrite (46, 240);// PWM 9 ВЫХОД для тестирования осциллографа //loss pin.... Локализуеи горелый пин. Частный случай. pinMode (43, INPUT_PULLUP); digitalWrite(43, HIGH); pinMode (44, OUTPUT); digitalWrite(44, LOW); pinMode (45, OUTPUT); digitalWrite(45, LOW); // I min 140 ma pinMode(24, INPUT); /// menu pinMode(26, INPUT); /// + pinMode(28, INPUT); /// - pinMode(25, INPUT); /// V div // pinMode(27,INPUT);/// + резерв // pinMode(29,INPUT);/// - DIDR2 = 0b1000000; ADMUX = 0b11100111; // 0bxxxxx111 A7 nano // 0bxxxxx101 A5 uno // 0b01xxxxxx 5V // 0b11xxxxxx 1.1 V ADCSRA = 0b11100011; //0bxxxxx100 -16, 011 -8 приемлимо, 010 -4 большая ошибка, 001,000 -2 16Mg/("2" x13) = 615kG -частота ацп ADCSRB = 0b00001000; tft.setTextColor(0xFFFF); tft.setRotation(3); tft.setTextSize(1); tft.setCursor(140, 20); tft.print(" RCL-RADIO.RU "); tft.setCursor(0, 0); } int bits = 8; int xx = 100; int h0 = 0, sss = 127, zz = 10, x, y, i, i1, u, i2, i3, sinhro, sinhro1, kof, nn = 0, vd; int arr = 0, arr1 = 1024, menu = 0, hold, hold_reg, raz = 3; float u_max, u_min = 0.00, u_sig, per, f, ves = 0.00; int data[640], data1[640], w, w1; unsigned long times, time, time1, time3; byte lb, hb; void loop() { ///////////////////////////// кнопки управления ///////////////////// if (digitalRead(24) == HIGH) { menu++; tft.fillRect(0, 0, 110, 20, 0x0000); tft.fillRect(0, 40, 10, 200, 0x0000); hold = 0; hold_reg = 0; } if (menu > 1) { tft.fillScreen(0x0000); menu = 0; } if (digitalRead(29) == HIGH) { tft.fillRect(0, 0, 110, 20, 0x0000); hold = 1; } if (menu == 0 && hold == 0) { if (digitalRead(28) == HIGH) { raz++; tft.fillRect(20, 0, 110, 20, 0x0000); tft.fillRect(0, 40, 320, 220, 0x0000); } if (digitalRead(26) == HIGH) { raz--; tft.fillRect(20, 0, 110, 20, 0x0000); tft.fillRect(0, 40, 320, 220, 0x0000); } if (raz > 8) { raz = 8; } if (raz < 0) { raz = 0; } switch (raz) { case 0: zz = 0; kof = 2; per = 0.1; break; case 1: zz = 1; kof = 1; per = 0.2; break; case 2: zz = 7; kof = 1; per = 0.5; break; case 3: zz = 17; kof = 1; per = 1; break; case 4: zz = 36; kof = 1; per = 2; break; case 5: zz = 95; kof = 1; per = 5; break; case 6: zz = 185; kof = 1; per = 10; break; case 7: zz = 380; kof = 1; per = 20; break; } } if (menu == 1 && hold == 0) { // ручная синхронизация if (digitalRead(28) == HIGH) { sss += 5; } if (digitalRead(26) == HIGH) { sss -= 5; } if (sss > 240) { sss = 240; } if (sss < 20) { sss = 20; } } if (hold == 1) { // кнопки в режиме HOLD if (digitalRead(28) == HIGH) { hold_reg += 5; } if (digitalRead(26) == HIGH) { hold_reg -= 5; } if (hold_reg > 319) { hold_reg = 319; } if (hold_reg < 0) { hold_reg = 0; } } if (digitalRead(28) == HIGH && digitalRead(26) == HIGH) { w++; w1 = 1; tft.fillScreen(0x0000); if (w > 1) { w = 0; } } if (w == 1 && w1 == 1) { ADMUX = 0b01100111; //Vccvref w1 = 0; vd = 1; } if (w == 0 && w1 == 1) { ADMUX = 0b11100111; //2.56vref w1 = 0; } if (digitalRead(25) == HIGH && w == 0) { vd = 5; } else { if (vd != 1) { vd = 25; } } //////////////////////////////// измерение и синхронизация ////////////////////// if (hold == 0) { ADS(); while (ADCH != sinhro / 4) { ADS(); ADCH; h0++; if (h0 > 10000) { break; } } h0 = 0; // ждем минимальной амплитуды ADS(); while (ADCH < sss) { ADS(); ADCH; h0++; if (h0 > 10000) { break; } } h0 = 0; // СИНХРОНИЗАЦИЯ times = micros(); while (i < 639) { i++; delayMicroseconds(zz); while ((ADCSRA & 0x10) == 0); ADCSRA |= 0x10; // lb = ADCL | 0b01000000;//9bits // hb = ADCH;//9bits // data[i] = ADCH;//8bits data[i] = (ADCL >> 6) | (ADCH << 2); //10bits //data[i] = (lb>> 6) | (hb << 1); //9bits } i = 0; times = micros() - times; Serial.println(times); // Serial.print(" "); // монитор порта время измерения 640 точек } //////////////////////////////////////////////////////////////////////////////// /////////////////////////////ВЕРХНЯЯ ЧАСТЬ ЭКРАНА ////////////////////////////// //////////////////// максимальное и минимальное напряжение //////////// if (millis() - time > 1000 && hold == 0) { while (i < 319) { i++; arr = max(arr, data[i]); arr1 = min(arr1, data[i]); } i = 0; sinhro1 = arr; sinhro = arr1; tft.setCursor(xx, 0); tft.setTextSize(1, 2); tft.fillRect(xx + 25 + 153, 0, 32, 16, 0x0000); tft.fillRect(xx + 25, 0, 36, 16, 0x0000); tft.fillRect(xx + 25 + 77, 0, 36, 16, 0x0000); tft.fillRect(60, 15, 80, 20, 0x0000); tft.fillRect(0, 15, 5, 20, 0x0000); //9bit // arr=arr-255; // arr1=(254-arr1)*(-1); arr = arr - 512; arr1 = (512 - arr1) * (-1); if (vd == 5) { ves = 0.01; //10/1024.0; u_max = arr * ves; u_min = arr1 * ves; } else { ves = 0.05; //50/1024.0; u_max = (arr - 3) * ves; u_min = (arr1 - 1) * ves; } tft.setTextColor(0xFFFF); tft.print("U"); tft.setTextSize(1); tft.setCursor(xx + 7, 8); tft.print("max"); tft.setTextSize(1, 2); tft.setCursor(xx + 26, 0); tft.print(u_max); tft.setCursor(xx + 58, 0); tft.setTextSize(1); tft.print(" V "); tft.setCursor(xx + 58 + 18, 0); tft.setTextSize(1, 2); tft.setTextColor(0xFFFF); tft.print("U"); tft.setTextSize(1); tft.setCursor(xx + 7 + 76, 8); tft.print("min"); tft.setTextSize(1, 2); tft.setCursor(xx + 25 + 77, 0); tft.print(u_min); tft.setCursor(xx + 58 + 76, 0); tft.setTextSize(1); tft.print(" V "); tft.setCursor(xx + 58 + 76 + 18, 0); tft.setTextSize(1, 2); tft.setTextColor(0xFFFF); tft.print("U"); tft.setTextSize(1); tft.setCursor(xx + 7 + 152, 8); tft.print("sig"); tft.setTextSize(1, 2); tft.setCursor(xx + 25 + 153, 0); if (u_max >= 0 && u_min <= 0) { tft.print(u_sig = abs(u_max) + abs(u_min)); } else { tft.print(u_sig = abs(u_max) - abs(u_min)); } tft.setCursor(xx + 58 + 153 - 6, 0); tft.setTextSize(1); tft.print(" V "); if (menu != 1) { sss = (sinhro1 + sinhro) / 2; /// средний уровень напряжения для автосинхронизации sss = sss / 4; } arr = 0; arr1 = 1024; ///// частотомер от 315 Гц////// ADS(); while (ADCH > sss - 10) { ADS(); ADCH; h0++; if (h0 > 10000) { break; } } h0 = 0; ADS(); while (ADCH < sss + 10) { ADS(); ADCH; h0++; if (h0 > 10000) { break; } } h0 = 0; time3 = micros(); ADS(); while (ADCH > sss - 10) { ADS(); ADCH; h0++; if (h0 > 10000) { break; } } h0 = 0; ADS(); while (ADCH < sss + 10) { ADS(); ADCH; h0++; if (h0 > 10000) { break; } } h0 = 0; f = micros() - time3; f = (1 / f) * 1000; tft.setTextSize(1); tft.setCursor(0, 20); if (vd == 5) { tft.print("1.00V/DIV "); } else { if (vd == 25) { tft.print("5.00V/DIV "); } else { if (vd == 1) { tft.print("0.22V/DIV "); } } } tft.print(" F="); tft.print(f, 3); tft.print(" kHz "); time = millis(); } i = 0; /// вывод HOLD, длительность развертки, синхронизация и частота tft.setCursor(0, 0); if (hold == 1) { tft.setTextSize(2); tft.setTextColor(0xFFFF); tft.print("H"); } if (menu == 0 && hold == 0) { tft.setTextSize(2); tft.setTextColor(0xFFFF); tft.print("A"); tft.print(" "); tft.print(per, 1); tft.println("mS"); } if (menu == 1 && hold == 0) { tft.setTextSize(2); tft.setTextColor(0xFFFF); tft.print("M"); } ///////////////////////////////////////////////////////////////////////////////// setka(); ///////////////////////////// вывод сигнала //////////////////////////////////// while (i < 639) { i++; if (hold == 0) { data[i] = data[i] / (5.12); } } i = 0; // поправка на размер экрана int 255 > int 240 while (i < 319) { i++; /// основной цикл вывода изображения на экран tft.drawLine(i * kof, 239 - data1[i], i * kof, 239 - data1[i - 1], 0x0000); // стирание сигнала if (i < 2) {} else { tft.drawLine(i * kof, 239 - data[i + hold_reg], i * kof, 239 - data[i - 1 + hold_reg], 0x67E1); } } i = 0; // вывод сигнала while (i < 319) { i++; data1[i] = data[i + hold_reg]; } i = 0; // массив под стирание //0x67E1 - green //0xF800 - red //////////////////////////////////////////////////////////////////////////////// }// LOOP void setka() { ///// сетка if (menu == 1) { //////////// метка ручной синхронизации tft.drawFastHLine(0, 240 - (sss - 5) / 1.27, 10, 0x0000); tft.drawFastHLine(0, 240 - sss / 1.27, 10, 0x07ff); tft.drawFastHLine(0, 240 - (sss + 5) / 1.27, 10, 0x0000); } if (hold == 1) { //////////// метка прокрутки осциллограммы в режиме HOLD tft.drawFastHLine(hold_reg - 10, 30, 10, 0x0000); tft.drawFastHLine(hold_reg, 30, 10, 0x07ff); tft.drawFastHLine(hold_reg + 10, 30, 10, 0x0000); tft.drawFastHLine(hold_reg - 10, 31, 10, 0x0000); tft.drawFastHLine(hold_reg, 31, 10, 0x07ff); tft.drawFastHLine(hold_reg + 10, 31, 10, 0x0000); } for (y = 40; y < 240; y = y + 20) { for (x = 0; x < 320; x = x + 9) { if (y == 140) { tft.drawPixel(x, y, 0xFFFF); } else { tft.drawPixel(x, y, 0xCDCD); } } } for (x = 0; x < 320; x = x + 54) { for (y = 40; y < 240; y = y + 10) { tft.drawPixel(x, y, 0xCDCD); } } } void ADS() { while ((ADCSRA & 0x10) == 0); ADCSRA |= 0x10; } Ссылка на скачивание скетча: https://cloud.mail.ru/public/yBBS/of2ntdaAu Наладка осциллографического пробника. Методика измерения сигнала - относительная. Для наладки используется любой фиксированный источник тока с напряжением. не превышающим 5 вольт. Хорошо подойдет элемент питания АА или ААА. В первую очередь выставляем сопротивлением R12 (см. схему) выставляем "0". При выбранном 10 битном режиме АЦП последний старший бит будет "шуметь". Это связано и с упрощенным построением "земли" в ардуино (нет разделения на "цифровую" и "аналоговую" землю), и с качеством применяемых операционных усилителей, и монтаж тоже добавит свое. Вольтметром измеряем напряжение элемента питания. Далее измеряем пробником элемент питания, и сопротивлением R6 добиваемся показаний, соответствующих измеренным вольтметром. Переключаемся в диапазон 25 вольт и сопротивлением R8 добиваемся таких же показаний (не забываем, что в диапазоне 25В цена деления 0.05, а в диапазоне 5В 0.01). Если при переключении с диапазона на диапазон "0" значительно "плывет" - значит у вас экземпляр ОУ с большим смещением входа (делитель на входе тоже делит напряжение смещения). В таком случае коррекцию можно внести на программном уровне, вычитая или прибавляя arr и arr1. И помним, что это не точный измерительный прибор, задача смотреть примерную картину происходящего. Мой дзен-канал: https://zen.yandex.ru/id/60491e44c7b64a64cdb4aec7