Andry Smart, если собираетесь "плагиатить" у плагиатора, то вот ещё "его" программатор к "его" осциллоскопу, за 13$...
Покупаете на Али готовый мини переходник и припаиваете к нему, от туда же (с Али), пружинные контакты. Сэкономите около 10$, как минимум.
спасибо огромное. я так и не знал как это на али искать. как оно называется.
плагиатить у автора мне не интересно. я пытаюсь реализовать пробник автоэлектрика с функцией осциллографа. собственно медленный осциллограф для этиъ целей и нужен.
думаю такой трехкнопочный джой и кнопка . не охота на пробнике много кнопок лепить не удобно очень.
уже четыре варианта платы и кнопок и функций. но ни одну до ума еще не довел.
Думаю, лучше паять такой трипод с обратной стороны, от подводящих к нему проводников на плате: рассверливаешь отверстие 1,5-2мм (может и больше) и со стороны проводника заливаешь это отверстие припоем, пока он не прихватит контакты трипода.
Иначе - только столик с подогревом, как для пайки светодиодов в LED лампах. Фен на нём, скорее всего, всю пластмассу поплавит.
Всё-таки удалось запустить 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, там как-то лохмато, можно причесать?
А что вас удивляет? Наверное, по этому японская техника работает долго... потому что все нюансы учитываются а не только "авось"...
Процитирую то, что уже было сказано:
"...у любого МК есть определенный максимальный ток , который может проходить через вывод поэтому можно (а точнее нужно) защищаться от перегрузки и особенно от кз!
...это можно сделать диодами, тем самым исключить вероятность кз или резисторами - ограничив максимальный проходящий ток ниже допустимого (например 10-100к на каждую ногу).
...ну а если по правильному диод+резистор будет более верным, это и защита от переполюсовки и ограничение максимального тока."
Раз пошла такая жара, то наверное и код будет интересен.
Для 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
}
}
>ну и код....
Это его вызов молодым :) "После 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 Гц".
Много букв, надеюсь оно того стоит.
Хронологические заметки для лучшего понимания сути проекта. 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, который мы поддерживаем за кулисами".
Теперь вы об этом знаете :)
>что то не компилится.
Это вы о японской ручке? Для ssd1306 попробовал, компилятор даже не ругнулся нигде:
"Скетч использует 22210 байт (72%) памяти устройства. Всего доступно 30720 байт.
Глобальные переменные используют 802 байт (39%) динамической памяти, оставляя 1246 байт для локальных переменных. Максимум: 2048 байт".
С SH1106 - тоже никаких проблем, только память забита чуть поболее:
"Скетч использует 20376 байт (66%) памяти устройства. Всего доступно 30720 байт.
Глобальные переменные используют 1815 байт (88%) динамической памяти, оставляя 233 байт для локальных переменных. Максимум: 2048 байт.
Недостаточно памяти, программа может работать нестабильно".
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
Для SH1106 у меня ничем не отличается от той, что из ссылки выше.
PS
Я вам более скажу:
У меня вообще на диске нет файлов Adafruit_MonoOLED.cpp, Adafruit_MonoOLED.h да и самой библиотеки Adafruit-GFX-Library-master.
Да и в скетчах нет таких... Что вы компилите?
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мс - прибавка будет ну пару кадров, ты же не просто заливаешь, еще же уйма кода работает, кроме вывода...
я знаю еще несколько способов ускорить вывод, но считаю это нецелесообразным. намного больший эффект дает оптимизация кода и алгоритмов...
У меня какая то дурацкая есть склонность начинать все сначала. Я иногда играю в игры, но поиграв в игру и дойдя до какого то уровня и вспомнив про игру спустя пол года я начинаю играть в нее заново. Так же и с этим пультоскопом. Я уже раз десять переписал его заново. И никак не могу дойти до того финиша, который был сделан на этом "автомате". И еще - я этот проект как то подзабросил, уж очень много косяков в нем. Кто нибудь проверял на осциле, как работает генератор сигнала - да работает крайне криво. Ну кароче мне много что не нравится, и я не хочу опубликовывать косячный код. А тот код, который я сливал тут - это по сути код автора с моими доработками... Но я планирую скоро что нибудь выкинуть, тем более появилась некая обратная связь тут на этом форуме, где мне сказали, что графическое меню, над которым я долгое время потел - оказалось нахер ни кому не нужно. И я потом понял в чем дело. У 98% всех пользователей, которые заливали мою версию - китайский дисплейчик (я про 3310. Я пользую оригинал. После того, как с алика мне пришло несколько дисплеев - я понял в чем суть. Дисплеи просто гавно и я понимаю в чем дело. Про ssd1306 - молчу. У меня он тоже с китая. Плюс ко всему меня просили сделать то, сделать это. Я сделал. Обратки не ощутил. Зачем мне тогда что то делать для народа, если я могу взять, написать так, как мне надо и все. Прибором этим я не пользуюсь, т.к. у меня есть нормальный осцилл и что то там изобретать на коленке пропал интерес, но я хочу сильно ускорить вывод на дисплей и отказаться полностью от библ ардуино...... Будет, не будет - не знаю.....
Плюс ко всему меня просили сделать то, сделать это. Я сделал. Обратки не ощутил.
а номер кошеля пробовал указать для обратки?))) спасибо говорили?
странные вещи. я вот, ускорил вывод ослика раз в 20, сделал кучу приблуд, в том числе антидребезг, которого не было, и без которого эта фигня работала только за счет тормознутости, и еще всякие плюшки аля разная длина буфера для паузы и рабочего режима... и мне похер на "обратки". сказали спасибо - и хорошо. детишкам ничего не жалко))) а тебе корону не вручили...
Electronik83 пишет:
Только строчка внизу с орфографической ошибкой..
я бы сказал, с двумя)) OSCILLOSCOPE - нужно вместо S->C и две L - две ашыпки))
Плюс ко всему меня просили сделать то, сделать это. Я сделал. Обратки не ощутил.
а номер кошеля пробовал указать для обратки?))) спасибо говорили?
странные вещи. я вот, ускорил вывод ослика раз в 20, сделал кучу приблуд, в том числе антидребезг, которого не было, и без которого эта фигня работала только за счет тормознутости, и еще всякие плюшки аля разная длина буфера для паузы и рабочего режима... и мне похер на "обратки". сказали спасибо - и хорошо. детишкам ничего не жалко))) а тебе корону не вручили...
Electronik83 пишет:
Только строчка внизу с орфографической ошибкой..
я бы сказал, с двумя)) OSCILLOSCOPE - нужно вместо S->C и две L - две ашыпки))
Ну вот почему в интернете злые все? Похер до обратки - да. Мне тоже похер. Я писал код на интузиазме, для собственного саморазвития. Я не хочу с тобой ругаться, хочу наоборот - дружить. Но млин че то сделал - начинается эта критика в интернете: Нахера там те то цифры выводятся? Зачем ты сделал графику - тормозит же все и памяти мало! Сделай мне то, сделай другое!
А с тобой я не хочу ругаться, хочу дружить и корона (не вирус) тут не причем!
Ну раз уж все опираются на мой код - не иначе как обратка...
Вариантов может быть, как минимум, три:
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 отличаются от значений других тестеров, вы можете настроить их.
разобрался с кнопками. так как они работают на прерываниях то не стоит игнорировать фильтр входной цепи. оказалось с резистором на 100 ом все заработало.
Улыбнуло. Ваша ситуация похожа на китайскую "оптимизацию" с Али: "а уберём-ка мы эту ни на что не влияющую пиндюльку из схемы. Дешевле же будет..." А пом известные блогеры разбираются - "почему не работает или работает, но криво" :)
ошибка машинально получилось. не было под рукой резистора ну думаю на кнопки та фиг с ним. не учел, что это вход прерывания. кстати такую схему включения увидел впервые. Интересный вариант.
Подскажите, кто в курсе, как (если возможно) в скетче SSD1306_EL83_EDITION_avto повернуть изображение на 180 градусов?
Если бы использовалась библиотека, например U8glib.h, то можно было бы командой setRot180() повернуть на 180 градусов.
Но библиотек нет в скетче.
Нашел:
//процесс инициализации дисплея из даташита
oledCommand(0xA1);//отражение по горизонтали слева направо
oledCommand(0xA0);//отражение по горизонтали справа налево
oledCommand(0xC8);//отражение по вертикали
oledCommand(0xC0);//переворот изображения по вертикали
//Одновременное использование команд 0xC8 и 0xA1
или
0xA0 и 0xC0 позволяет повернуть изображение на 180 градусов
Практика показывает, что библиотеки для Ардуино сами поворачивают изображение для SSD1306 как раз на 180 градусов. Чтобы восстановить исходную ориентацию нужно просто закомментировать в блоке инициализации эти команды.
Andry Smart, если собираетесь "плагиатить" у плагиатора, то вот ещё "его" программатор к "его" осциллоскопу, за 13$...

Покупаете на Али готовый мини переходник и припаиваете к нему, от туда же (с Али), пружинные контакты. Сэкономите около 10$, как минимум.
Месяц уже жду эти подпружиненные контакты с Али )
спасибо огромное. я так и не знал как это на али искать. как оно называется.
плагиатить у автора мне не интересно. я пытаюсь реализовать пробник автоэлектрика с функцией осциллографа. собственно медленный осциллограф для этиъ целей и нужен.
думаю такой трехкнопочный джой и кнопка . не охота на пробнике много кнопок лепить не удобно очень.
уже четыре варианта платы и кнопок и функций. но ни одну до ума еще не довел.
удалил дубль.... достали эти глюки...
Думаю, лучше паять такой трипод с обратной стороны, от подводящих к нему проводников на плате: рассверливаешь отверстие 1,5-2мм (может и больше) и со стороны проводника заливаешь это отверстие припоем, пока он не прихватит контакты трипода.
Иначе - только столик с подогревом, как для пайки светодиодов в LED лампах. Фен на нём, скорее всего, всю пластмассу поплавит.
думаю это если выпаивать то проблема а запаивать можно и феном. секундное дело
ну поделитесь плиииз прошивкой в которой работает медленный осциллограф. можно на дисплее от нокия или олед.
как искать? как хоть файл называется? я же не могу их все перепробовать
Всё-таки удалось запустить 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, там как-то лохмато, можно причесать?
Нашел видео с созданием похожего устройства с исходниками

https://www.youtube.com/watch?v=c-ic0UaAq-A
Схема и код
http://easytechlearn.com/2020/06/14/how-to-make-mini-oscilloscope/
Вот ещё от японца, ССЫЛКА. Код и библа тут. Включайте переводчик. Что то стал популярен этот экран малыш))).
Надеюсь он меня простит за это :), вдруг кому схема понадобится.
Диодная развязка на кнопках?ХМ..
А что вас удивляет? Наверное, по этому японская техника работает долго... потому что все нюансы учитываются а не только "авось"...
Процитирую то, что уже было сказано:
"...у любого МК есть определенный максимальный ток , который может проходить через вывод поэтому можно (а точнее нужно) защищаться от перегрузки и особенно от кз!
...это можно сделать диодами, тем самым исключить вероятность кз или резисторами - ограничив максимальный проходящий ток ниже допустимого (например 10-100к на каждую ногу).
...ну а если по правильному диод+резистор будет более верным, это и защита от переполюсовки и ограничение максимального тока."
Там заведено прерывание от кнопок на INT2,опрос кнопок в обработчике прерывания pin2IRQ
Про прерывание понятно, мне больше интересно, какое назначение у ноги 12?
Думаю это аттенюатор 1/10, - 12 нога подтягивается к земе и коэффициент деления входного делителя увеличивается в 10 раз...(510К/120К или 12К)
Раз пошла такая жара, то наверное и код будет интересен.
Для 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 } }Раз пошла такая пьянка))), то перевёл комментарии. Надеюсь что ничего не сломал. Сделано в Японии!!!
/ * 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; // меняет флаг } }Было-бы на дисплее 5110 - уже бы протестировал, - интересная штучка. Похоже там есть элементы автоматических установок...
Было-бы на дисплее 5110 - уже бы протестировал,
Я пробовал переделать версию 1230 и 5110 под дисплей от Нокии 3510(монохром 96х65, только там контроллер PCF8814), но тоже результат отрицательный...
ну и код.... русскоговорящему тяжеловато понять. я так понял кнопки на прерывании висят. через диоды развязка.
интересно вольтметр сделан. смотря какая кнопка нажата такой и предел измерений. но блин можно же было автоматически сделать для удобства.
но все же много чего я вообще не понял. особенно с заморочками храниния текста в ЕЕПРОМ... (видно для экономии оперативки)
но в целом весьма интересная прошивка.
>ну и код....
Это его вызов молодым :) "После 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 Гц".
Много букв, надеюсь оно того стоит.
Хронологические заметки для лучшего понимания сути проекта.
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, который мы поддерживаем за кулисами".
Теперь вы об этом знаете :)
что то не компилится. похоже библиотеки адафрут с джитхаба не подходят.
пытаюсь попробовать с ssd1306
если у кого компилит то поделитесь библиотеками плиззз
>что то не компилится.
Это вы о японской ручке? Для ssd1306 попробовал, компилятор даже не ругнулся нигде:
"Скетч использует 22210 байт (72%) памяти устройства. Всего доступно 30720 байт.
Глобальные переменные используют 802 байт (39%) динамической памяти, оставляя 1246 байт для локальных переменных. Максимум: 2048 байт".
С SH1106 - тоже никаких проблем, только память забита чуть поболее:
"Скетч использует 20376 байт (66%) памяти устройства. Всего доступно 30720 байт.
Глобальные переменные используют 1815 байт (88%) динамической памяти, оставляя 233 байт для локальных переменных. Максимум: 2048 байт.
Недостаточно памяти, программа может работать нестабильно".
а можете заархивировать библиотеки от адафрут и поделиться?
У меня тоже 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.
Да и в скетчах нет таких... Что вы компилите?
а в целом, это явная суходрочка. так, для общего развития. вместо того, чтобы подчистить и настроить алгоритмы, ты взялся за невнятную хрень. софтварная заливка 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мс - прибавка будет ну пару кадров, ты же не просто заливаешь, еще же уйма кода работает, кроме вывода...
я знаю еще несколько способов ускорить вывод, но считаю это нецелесообразным. намного больший эффект дает оптимизация кода и алгоритмов...
У меня какая то дурацкая есть склонность начинать все сначала. Я иногда играю в игры, но поиграв в игру и дойдя до какого то уровня и вспомнив про игру спустя пол года я начинаю играть в нее заново. Так же и с этим пультоскопом. Я уже раз десять переписал его заново. И никак не могу дойти до того финиша, который был сделан на этом "автомате". И еще - я этот проект как то подзабросил, уж очень много косяков в нем. Кто нибудь проверял на осциле, как работает генератор сигнала - да работает крайне криво. Ну кароче мне много что не нравится, и я не хочу опубликовывать косячный код. А тот код, который я сливал тут - это по сути код автора с моими доработками... Но я планирую скоро что нибудь выкинуть, тем более появилась некая обратная связь тут на этом форуме, где мне сказали, что графическое меню, над которым я долгое время потел - оказалось нахер ни кому не нужно. И я потом понял в чем дело. У 98% всех пользователей, которые заливали мою версию - китайский дисплейчик (я про 3310. Я пользую оригинал. После того, как с алика мне пришло несколько дисплеев - я понял в чем суть. Дисплеи просто гавно и я понимаю в чем дело. Про ssd1306 - молчу. У меня он тоже с китая. Плюс ко всему меня просили сделать то, сделать это. Я сделал. Обратки не ощутил. Зачем мне тогда что то делать для народа, если я могу взять, написать так, как мне надо и все. Прибором этим я не пользуюсь, т.к. у меня есть нормальный осцилл и что то там изобретать на коленке пропал интерес, но я хочу сильно ускорить вывод на дисплей и отказаться полностью от библ ардуино...... Будет, не будет - не знаю.....
Этож я рисовал - за один вечер на даче все сделал. Было вдохновение:) Только строчка внизу с орфографической ошибкой.......
странные вещи. я вот, ускорил вывод ослика раз в 20, сделал кучу приблуд, в том числе антидребезг, которого не было, и без которого эта фигня работала только за счет тормознутости, и еще всякие плюшки аля разная длина буфера для паузы и рабочего режима... и мне похер на "обратки". сказали спасибо - и хорошо. детишкам ничего не жалко))) а тебе корону не вручили...
del
ну поделитесь плиииз прошивкой в которой работает медленный осциллограф. можно на дисплее от нокия или олед.
Зачем ныть то? - листай всю ветку....
странные вещи. я вот, ускорил вывод ослика раз в 20, сделал кучу приблуд, в том числе антидребезг, которого не было, и без которого эта фигня работала только за счет тормознутости, и еще всякие плюшки аля разная длина буфера для паузы и рабочего режима... и мне похер на "обратки". сказали спасибо - и хорошо. детишкам ничего не жалко))) а тебе корону не вручили...
Ну вот почему в интернете злые все? Похер до обратки - да. Мне тоже похер. Я писал код на интузиазме, для собственного саморазвития. Я не хочу с тобой ругаться, хочу наоборот - дружить. Но млин че то сделал - начинается эта критика в интернете: Нахера там те то цифры выводятся? Зачем ты сделал графику - тормозит же все и памяти мало! Сделай мне то, сделай другое!
А с тобой я не хочу ругаться, хочу дружить и корона (не вирус) тут не причем!
Ну раз уж все опираются на мой код - не иначе как обратка...
Спасибо скомпилил с этой библиотекой без проблем. но вот с кнопками какая то беда. почему то работает только одна кнопка UP.
[jnz кнопки опрашиваются. если нажимать при подаче питания то включаются режимы вольтметров и напряжение батареи
кто то еще пробовал эту схемку в работе? как у вас?
Вариантов может быть, как минимум, три:
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 отличаются от значений других тестеров, вы можете настроить их.
я в шоке. проверил порты, работают.
проверил диоды, работают. и включены правильно.
единственное у меня резистора нет на 100 ом . на схеме он R4 (заменил перемычкой)
ну и А0 в воздухе. нет делителя входного но я не думаю, что это повлияло.
яя уже не знаю, что думать. на выходных разберусь а то на работе и так крыша едет.
разобрался с кнопками. так как они работают на прерываниях то не стоит игнорировать фильтр входной цепи. оказалось с резистором на 100 ом все заработало.
Улыбнуло. Ваша ситуация похожа на китайскую "оптимизацию" с Али: "а уберём-ка мы эту ни на что не влияющую пиндюльку из схемы. Дешевле же будет..." А пом известные блогеры разбираются - "почему не работает или работает, но криво" :)
ошибка машинально получилось. не было под рукой резистора ну думаю на кнопки та фиг с ним. не учел, что это вход прерывания. кстати такую схему включения увидел впервые. Интересный вариант.
Погоняете - поделитесь впечатлениями. Особенно ценно будет сравнение, если есть такая возможность.
где 100 ом то, где?
http://prntscr.com/tg6vxq
Схема выше. R4 - 100 Ом.
Подскажите, кто в курсе, как (если возможно) в скетче SSD1306_EL83_EDITION_avto повернуть изображение на 180 градусов?
Если бы использовалась библиотека, например U8glib.h, то можно было бы командой setRot180() повернуть на 180 градусов.
Но библиотек нет в скетче.
Нашел:
//процесс инициализации дисплея из даташита
oledCommand(0xA1);//отражение по горизонтали слева направо
oledCommand(0xA0);//отражение по горизонтали справа налево
oledCommand(0xC8);//отражение по вертикали
oledCommand(0xC0);//переворот изображения по вертикали
//Одновременное использование команд 0xC8 и 0xA1
или
0xA0 и 0xC0 позволяет повернуть изображение на 180 градусов
Но как применить эти знания? Помогите.
[...было много буков...]
Практика показывает, что библиотеки для Ардуино сами поворачивают изображение для SSD1306 как раз на 180 градусов. Чтобы восстановить исходную ориентацию нужно просто закомментировать в блоке инициализации эти команды.