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.
Строку 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:
Для SSD1306:
Раз пошла такая пьянка))), то перевёл комментарии. Надеюсь что ничего не сломал. Сделано в Японии!!!
Было-бы на дисплее 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 градусов. Чтобы восстановить исходную ориентацию нужно просто закомментировать в блоке инициализации эти команды.