Авторизация
Регистрация

Напомнить пароль

«Магический глаз» тёплых ламповых времён — эмуляция на ардуино

В магнитофонах и приёмниках моей юности использовались исчезнувшие ныне ламповые индикаторы уровня на лампах 6E1П или 6Е5С. Сейчас пришла пора ностальгирования по «старым временам» и на алиэкспрессе или амазоне можно купить собранные индикаторы, они почти также популярны как часы на лампах «Никси».

Поскольку лампа требует высокого напряжения для работы, в современных устройствах это решается с помощью преобразователя напряжения на таймере 555 упроавлящим мощным полевым транзистором включенным в первичную обмотку повышающего трансформатора, и дальше вторичная обмотка подключается к умножителю напряжения из 4-5 ступеней. Этого достаточно чтобы преобразовать входные 5 вольт в 250 с током 1-2 ma.

Я хочу поделиться своим домашним проектом, суть которого в эмуляции, насколько возможно, «зелёного глаза» лампы 6E1П с помощью быстрого OLED дисплея, контролируемого платой Arduino:

Проект выполнен несколько лет назад.
Самым сложным оказалось подобрать подходящий дисплей. На рынке их огромное количество, и большинство из них легко управляется известной библиотекойAdafruit-GFX-Library. Это прекрасная универсальная библиотека, очень простая в использовании, однако при использовании обычных плат Arduino она слишком медленная и не позволяет выводить качественную анимацию. После большого количества неудач методом проб и ошибок был найден дисплей на чипе SH1106, размером 128х64 точек и с голубым свечением. Для него предлагается библиотека ардуино U8G2. Особенностью этой библиотеки является наличие страничного вывода, т.е. сначала изображение создаётся в памяти контроллера, и затем единственной командой выводится на дисплей. Это позволяет создавать простые анимации с хорошей частотой обновления. Библиотека также содержит команды для построения геометрических примитивов — прямоугольников, треугольников и окружностей. Также возможен вывод простых битмапов, однако он слишком медленный чтобы использовать для анимации. Однако битмапы могут работать как маски для вырезания части изображения, и я использовал такой битмап для придания характерной формы экрана «зеленого глаза» — вертикального прямоугольника с округлой верхней стороной.

Общий принцип построения геометрии из примитивов:
Размеры примитивов меняются динамически в зависимости от напряжения на входе A0 платы Arduino.

Как я уже говорил, эмуляция вполне приблизительная. Хотя геометрию изображения я считаю довольно близкой к оригиналу, монохромность дисплея не позволила создать правильный фон, так что у меня изображение на полностью чёрном фоне, тогда как в оригинальной лампе фон всегда бледно-зелёный.

Была еще одна техническая запинка, которую мне удалось преодолеть. Дисплей этот имеет синее свечение, а мне нужно ярко-зелёное. Проблема решилась с помощью высокотемпературного скотча дюймовой ширины, это интенсивно-жёлтая полиимидная липкая лента, материал также называют «каптон». По ширине она идеально соответствует дисплею и будучи аккуратно наклееной, прекрасно превращает голубое свечение экрана в зелёное, очень близкое по цвету к «ламповому».

Для эффектности я добавил к входу Arduino сигнал с простенькой платы микрофона с усилителем от Adafruit.

Дисплей и несколько дополнительных компонент для микрофона я разместил на вырезаной под размер Arduino простенькой монтажной плате, весь монтаж выполнен проводами:
Диаграмма соединения деталей:
Для любопытствующих приведу мой код для Ардуино. Я отнюдь не считаю себя продвинутым программистом микроконтроллеров и никоим образом не претендую на совершенство стиля и кода, меня вполне устраивает что он просто работает.

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

Я взял себе за правило добавлять простой информативный header в свой код а также пару строчек, которые при запуске напоминают из какого файла и когда этот код был залит.
/************************************************************
* WHEN: 07-SEP-2022
* WHAT: Magic eye emulator with OLED 1.3″ 128×64 I2C Blue Display I2C address 0x3C
* https://protosupplies.com/product/oled-1-3-128x64-i2c-blue-display/
* Uses Universal 8bit Graphics Library (https://github.com/olikraus/u8g2/)
* DETAILS:  
* without RunningAvg library as per
* https://www.powertronika.com/2020/04/reducing-noise-from-sensor-data.html 
* bitmap for central triangle, side with circle and rectangle
* On Mega2056 SDA A20, SCL A21
* On UNO SDA A4, SCL A5
* Use analogReference() for signal 1-2.5 v
* Analog input on A0
* Works on direct input from radio receiver
/************************************************************/

#include <Arduino.h>
#include <U8g2lib.h>

#define ARR_SIZE 5


// U8g2 Contructor List (Frame Buffer)
// The complete list is available here: https://github.com/olikraus/u8g2/wiki/u8g2setupcpp
//U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R1, /* reset=*/ U8X8_PIN_NONE);
U8G2_SSD1327_WS_128X128_F_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 10, /* dc=*/ 9, /* reset=*/ 8);

int sum, a, y_max, delta_side,delta_y; //delta_side,delta_y - for small side triangles when a>32; y_max - maximum height
float p,p_avg,myRA[ARR_SIZE];
byte index;

#define owl_width 64
#define owl_height 36
static const unsigned char owl_bits[] U8X8_PROGMEM  = {
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
   0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80,
   0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x01, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0x80, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0,
   0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x03, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0xc0, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0,
   0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x0f, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0xf0, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0,
   0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x1f, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0xf8, 0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc,
   0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x7f, 0x00, 0x00, 0x00,
   0x00, 0x00, 0x00, 0xfe, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
   0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0xff, 0x03, 0x00, 0x00,
   0x00, 0x00, 0xc0, 0xff, 0xff, 0x07, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xff,
   0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xff, 0xff, 0x1f, 0x00, 0x00,
   0x00, 0x00, 0xf8, 0xff, 0xff, 0x3f, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xff,
   0xff, 0x7f, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xff, 0xff, 0xff, 0x01, 0x00,
   0x00, 0x80, 0xff, 0xff, 0xff, 0xff, 0x07, 0x00, 0x00, 0xe0, 0xff, 0xff,
   0xff, 0xff, 0x1f, 0x00, 0x00, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00,
   0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xfc, 0xff, 0xff, 0xff };

void setup(void) {
  Serial.begin(115200);
  u8g2.begin();
  Serial.print("Sketch:   ");   Serial.println(__FILE__);
  Serial.print("Uploaded: ");   Serial.println(__DATE__);
// #if defined(__AVR_ATmega2560__)
//    analogReference(INTERNAL1V1); 
    //analogReference(INTERNAL2V56);
 //#else
 //   analogReference(INTERNAL);
// #endif
 delta_side=12;
 delta_y=40;
 y_max=100;
 sum=0;
 index=0;
 u8g2.setFont(u8g2_font_timR10_tr);
 u8g2.setFontDirection(2);
}

void loop(void) {
  p=analogRead(A0);
    //averaging
    sum=sum-myRA[index];
    myRA[index]=p;
    sum+=p;
    index=(index+1)%ARR_SIZE;
    p_avg=sum/ARR_SIZE;
  //myRA.addValue(p);
  //p_avg=myRA.getAverage();
  a=min(map(p_avg,1,60,1,32),80);
  //drawScreen(a,p_avg,1);  //print amplitude
  drawScreen(a,p_avg,0);  //don't print amplitude
}

void drawScreen(int a, int vu, boolean printVU) {
  int16_t delta, a_x,a_y,a1,r,r1;
  u8g2.clearBuffer();
  delta=min(32,a);
  a1=min(a,72);
  a_y=min(map(a,4,32,18,32),50);//70
  a_x=map(delta,4,32,16,10);
  r=map(delta,4,32,11,8);
  r1=map(delta,4,32,6,0);
  if (a<6) u8g2.drawBox(29,0,7,y_max);
  //central triangle
   else u8g2.drawTriangle(32,0,32-delta,y_max,32+delta,y_max);
  if (a1>32) {
    u8g2.drawTriangle(32-delta_side, delta_y,0,y_max,0,y_max-(a1-32));
    u8g2.drawTriangle(32+delta_side, delta_y,64,y_max,64,y_max-(a1-32));
  }
  //side triangles    
  u8g2.drawTriangle(28,10,a_x,a_y,a_x,10);
  u8g2.drawBox(0,10,a_x,a_y-10);
  u8g2.drawDisc(r1+1,a_y-r1-2,r,U8G2_DRAW_LOWER_RIGHT|U8G2_DRAW_LOWER_LEFT);
  u8g2.drawTriangle(36,10,64-a_x,a_y,64-a_x,10);
  u8g2.drawBox(64-a_x,10,a_x,a_y-10);
   u8g2.drawDisc(64-r1-1,a_y-r1-2,r,U8G2_DRAW_LOWER_RIGHT|U8G2_DRAW_LOWER_LEFT);
  u8g2.setDrawColor(0);
  u8g2.drawBox(0,0,64,14);
u8g2.setBitmapMode(1);
u8g2.setDrawColor(0);
u8g2.drawXBMP(0,64,owl_width, owl_height, owl_bits);
u8g2.setDrawColor(1);
  if (printVU) {
    u8g2.setCursor(40, 110);
    u8g2.print(vu);
  }  
    u8g2.sendBuffer();    
} 
Добавить в избранное
+18 +20
свернутьразвернуть
Комментарии (6)
RSS
+
avatar
+4
  • valerak2
  • 15 сентября 2025, 08:03
Эмуляция — фигня, только bare metal, только хардкор

PS: Лампы продаются, стоит у меня в Ригонде mysku.club/blog/diy/76018.html
+
avatar
+2
надо бы под кат немного раньше убрать наверно
+
avatar
+3
  • Boor
  • 15 сентября 2025, 08:11
Сейчас пришла пора ностальгирования по «старым временам»
Пора эта видимо только у тех, кто в детстве/юности не насмотрелся на эти лампы. Лично я, более чем достаточно и на магнитофоне ''Астра-4'' и на радиоле «Симфония-003»...)
+
avatar
+2
  • kvarkk
  • 15 сентября 2025, 08:43
Непонятно, как это выглядит в работе. Моим залить видео не только на ютуб, или вообще ютуб не использовать.
+
avatar
-1
Помню магнитофон Яуза. Часами мог сидеть зачарованно глядя на волшебный зеленый огонек.
+
avatar
0
Комментарий ожидает проверки администрацией сайта. Подробнее...
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.