
По итогам 2023 года ролики о старых цифровых камерах набрали суммарно более 1 миллиарда просмотров в TikTok. В свою очередь на YouTube блогеры актино обозревают цифромыльницы с призывом приобщиться к тренду. На фоне становления Y2K эстетики и смещения интересов в области фотографии в прошлое я предлагаю заглянуть немного подальше и посмотреть, как всё начиналось.
Если вам интересно, как связаны между собой зарождение цифровой эпохи в фотографии, странная самоделка, и причём здесь вынесенная в название оперативная память – добро пожаловать под кат!
Введение
Как это часто бывает, тут не обойтись без личной истории. Для меня в фотографии сам процесс и его технические аспекты важны не меньше, чем результат. Рано или поздно такой подход приводит к мысли о создании самодельного устройства для захвата мгновений ради удовлетворения внутреннего ребёнка. Не я первый – не я последний, например, в интернете можно найти работы энтузиастов, воссоздавших старинные форматные камеры и снимающих на них же с использованием коллоидного процесса.
К сожалению, плотник из меня неважный и я совсем не силён в химии, но мало-мальски разбираюсь в цифровой технике.
Первыми в голову приходят следующие мысли:
Попробовать сделать что-нибудь на базе имеющихся в продаже датчиков, например, от OmniVision, но радости такая самоделка принесёт немного. По сути это маленькая готовая камера, порой даже с поддержкой автофокуса и оптикой в комплекте.
Однажды я узнал про сканирующие задники для больших форматных камер – можно попробовать раздобыть ПЗС-линейку или похожий контактный датчик и сделать что-то подобное. К сожалению, зачастую необходимые детали существуют только в каталоге производителя – найти и купить их в наших реалиях сложно. Также из принципа действия следует не столько недостаток, сколько особенность – снимать такой камерой можно только статичные сюжеты.
Изготовить фотодатчик самому, конечно, невозможно, однако даже с готовыми компонентами есть место творчеству.
Циклоп и радиолюбители

В далёком 1975 году американская компания Cromemco внезапно представила полностью твердотельную камеру под названием Cyclops c возможностью подключения к микро-ЭВМ, таким как Altair 8800. Применённое инженерами решение находилось где-то на грани наглости и накуренности – вместо электронной передающей трубки прибор смотрел на мир с помощью чипа динамической памяти. За 50 долларов можно было заказать необходимые детали для самостоятельной сборки камеры (версию для подключению к осциллографу), включая тот самый датчик; схема устройства в свою очередь публиковалась в журнале Popular Electronics.

Анализ принципа действия
В микросхемах памяти динамического типа функции запоминающей ячейки выполняет конденсатор, образованный внутри МОП-структуры. Информация представляется в виде наличия или отсутствия заряда. Согласно книге «Микросхемы памяти и их применение» из серии «Массовая радиобиблиотека» логическому нулю соответствует наличие заряда, единице – его отсутствие (об этом мы ещё обязательно поговорим). Из-за несовершенства нашего мира время сохранения заряда ячейкой ограничено, поэтому такой памяти требуется регулярная перезапись – регенерация.
Мы воспользуемся тем обстоятельством, что под действием света ячейка начинает разряжаться быстрее и в конечном итоге меняет своё состояние. Объяснения этому явлению ни в сети, ни в литературе я так и не нашёл, однако рискну предположить, что паразитный p-n переход между стоком и истоком n-МОП транзистора в составе ячейки ведёт себя подобно фотодиоду, позволяя конденсатору разряжаться на шину данных.
Подбор компонентов
Выбираем память
Ради справедливости стоит сказать, что та самая микросхема была заказной – ячейки памяти были расположены в виде массива из 32 строк и 32 колонок не только логически. Также я видел в сети упоминания похожих микросхем на 64К ячеек от компании Micron, шедших с прозрачным окошком в корпусе с завода и созданных для тех же целей. Оба варианта давно не найти, но можно попробовать перепрофилировать под эти цели серийную память. Опыт Германа Курца с зарубежной памятью типа 4164 очень вдохновляет.
Для получения картинки, очевидно, нужно подвергнуть кристалл воздействию света, а это значит, что нужно как-то вскрыть корпус микросхемы, не повредив сам кристалл и приваренную к нему золотую проволоку, ведущую к выводам корпуса. @BarsMonster травил пластиковые корпуса дымящей азотной кислотой, но у меня нет доступа ни к кислоте, ни к хорошей вытяжке, зато есть паяльник и доступ к гравёру с алмазным диском – самое время обратить внимание на память в металлокерамических корпусах.
Первое, что приходит в голову – это отечественная серия К565, и я полез на известный сайт объявлений. Шутки-шутками, но среди интересующих меня объявлений не было ни одного из Default City – всё в других городах. Было несколько предложений К565РУ3 в больших количествах, чудом не доставшихся варварам-аффинажникам, но выбор пал на более современные К565РУ5 (тот же продавец, у которого я заказал комплект из вожделенных микросхем, предлагал ещё и РУ7, но уже по какой-то совсем заоблачной цене). Дело тут не в большей ёмкости – РУ5 менее капризна в плане питания и её намного сложнее убить по невнимательности. Более старые РУ1 и РУ3 требуют сразу три напряжения питания: +12, +5 и -5 В, причём подавать и снимать их нужно в корректной последовательности во избежание теплового пробоя и выхода кристалла из строя. С современной элементной базой организовать всё это несложно, порой в даташитах к DC-DC преобразователям можно найти даже пример разводки платы, но от неправильного аварийного отключения это не спасает, особенно когда процесс разработки итеративный, ну или рядом попросту бегают котики.
Есть решение – мне поможет микроконтроллер!
Времена MS-DOS и параллельного порта давно прошли, а это значит, что от использования микроконтроллера с целью подружить память с современными машинами не отвертеться. Поначалу среди всего многообразия, предоставляемого рынком, мой взгляд достаточно быстро упал на старый-добрый ATmega328P в составе китайского клона Arduino Nano. Не спешите кидаться камнями и тухлыми яйцами – я прекрасно понимаю тех, кто презирает платформу, но считаю, что в моём случае выбор вполне оправдан. Главным критерием выбора были питание от 5В и как следствие – пятивольтовые уровни, память гарантированно можно подключить напрямую к микроконтроллеру без усложнений в виде сопряжения логических уровней. Как писал @aronsky, для моргания светодиодом это слишком мощно, а для обработки изображений – слишком слабо, но для того, чтобы устроить ногодрыг и отправить результаты большому брату по UART её более чем достаточно. Более того, на плате уже есть вся необходимая для экспериментов обвязка в лице стабилизатора и USB-UART преобразователя, а в сам камень уже зашит загрузчик – шей сколько влезет, не доставая программатор.

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

Процесс работы с памятью хорошо описан в вышеупомянутой книге. Временные диаграммы могут выглядеть немного пугающе, но на самом деле в обращении к памяти нет ничего сложного. Для чтения и записи нам нужно:
Подать сигнал чтения (1)/разрешения записи (0);
Подать на адресную шину код адреса строки и вместе с ним стробирующий сигнал ~RAS;
После небольшой задержки, которая зависит от типа микросхемы, подать адрес столбца и стробирующий сигнал ~CAS;
В случае записи – подать записываемое значение на вход DI, иначе – прочитать значение с выхода DO;
По окончании снять стробирующие сигналы в обратном порядке и выдержать нормированную задержку.

Но читать и писать по одному биту это слишком медленно – микросхема может работать в страничном режиме, когда мы пишем или читаем сразу несколько ячеек. Процесс очень похож на обычные чтение и запись, только адрес столбца и сигнал ~CAS подаются несколько раз. В книге не было временных диаграмм для этих режимов, но их можно подсмотреть в документации на зарубежную память типа 4164. Понятно, что задержки будут другими, но лишним не будет.
Ещё один необходимый режим – принудительная регенерация. В нашем случае мы по сути специально портим данные, чтобы получить изображение, и он нам не особо нужен, но перед началом работы память нужно инициализировать. К565РУ5 выходит в рабочий режим спустя 2 мс и 16 циклов регенерации после подачи питания. Для этого мы просто перебираем строки с сигналом ~CAS.
Поначалу при написании программы для микроконтроллера я допустил одну досадную ошибку, которую заметил не сразу. При скорости UART 57600 бод попытка считать память целиком и передать её содержимое на компьютер занимала около 25 секунд, при этом записанные данные практически не портились (несколько битых ячеек всё-таки было). Проблема крылась в функции формирования задержек – по ошибке я вбил в калькулятор несколько лишних нулей и вместо 62,5 нс на такт получил 0,625. Я как можно быстрее стёр это позорище; по сути мне требовались задержки в единицы тактов – издевательство над таймером в итоге превратилось во что-то такое:
// Задержка в N тактов
template <uint8_t N = 1>
void delay_cycles() {
__asm__ __volatile__ ("nop");
delay_cycles<N - 1>();
}
// База рекурсивного шаблона задержки
template <>
inline void delay_cycles<0>() {}
С другой стороны теперь я на практике убедился в том, что память может хранить данные на порядки дольше, чем написано в паспорте (в теории РУ5 требует принудительной регенерации каждые 2 мс, иначе сохранность данных не гарантирована). Эта возможность может пригодиться как для формирования более длинных выдержек, так и для передачи данных. Напомню, что у микроконтроллера всего 2 Кб оперативной памяти, в то время как ёмкость РУ5 – все 8, поэтому мы вынуждены прочитать порцию данных, отправить её и дальше читать следующую. В моём случае считывание происходит построчно – 256 посылок по 32 байта.
Пишем клиент
Одно дело смотреть на то, как байты бегут в окошке терминальной программы, другое – превратить этот поток в картинку и сохранить её в файл. Нужна клиентская программа которая будет на стороне «большого» компьютера принимать и обрабатывать данные.

Вообще моей «первой любовью» в мире разработки была Java, однако по превратности судьбы мне приходилось работать преимущественно с .NET и C#, поэтому с выбором инструментов долго думать не пришлось.
Microsoft любезно предоставляет класс System.IO.Ports.SerialPort для работы с UART, поэтому больших сложностей не возникло. Наверное, основное, на что стоит обратить внимание, это асинхронный метод получения данных – мы ждём завершения приёма в буфер в отдельном потоке, не блокируя UI.
var progressHandler = new Progress<int>(value => progressBar.Value = value);
var progress = progressHandler as IProgress<int>;
byte[] buffer = new byte[8192];
await Task.Run(() =>
{
// Пишем и читаем память по команде с компьютера
// Ждём 4 байта: мнемонику 0xBADBEE и число - как долго экспонируем кристалл
byte[] command = { 0xBA, 0xDB, 0xEE, (byte)shutterSpeed };
_serialPort.Write(command, 0, 4);
for (int i = 0; i < 8192; i++)
{
buffer[i] = (byte)_serialPort.ReadByte();
if (progress != null)
{
progress.Report(i);
}
}
});
Самый душный этап и беда, откуда не ждали
Вскрываем кристалл
Теоретические выкладки и написание ПО – это хорошо, но рано или поздно приходит пора проверить жизнеспособность всего этого на практике.
Пора. Вскрывать. Корпус.

Сначала я думал срезать крышку скальпелем или отпаять её. В процессе выяснилось, что запаян корпус на совесть, и просто так срезать крышку не выйдет. Отпаять крышку мне удалось только с добавлением сплава Розе (не делайте так, это очень плохая практика), причём в обоих случаях кристаллы были испорчены – первый я заляпал припоем, второй перегрел. Более здравой оказалась идея срезать крышку гравёром – тут всё получилось; для защиты от механических повреждений вместо металлической крышки было приклеено предметное стекло для оптического микроскопа (прошу прощения за качество фото, макрообъектива под рукой нет).
Геометрия и паттерны
Сначала я даже подумал, что повредил и этот кристалл – независимо от того, что я в него записывал, всё время считывался паттерн, который можно увидеть на скриншоте (чёрные ячейки соответствуют нулю). Однако стоило мне уйти в тёмную комнату или прикрыть окошко плотным картоном, как память начинала отдавать ровно то, что в неё записывали.

На этом же этапе в процессе экспериментов с режимом записи и частичным прикрытием окошка выяснилось несколько вещей:
Память необязательно забивать нулями – под действием света единички превращаются в нолики точно так же, как и нолики в единички. Как это соотносится с тем, что написано в книге по поводу соответствия наличия заряда нулю – хороший вопрос. Ну и похоже, что моё предположение оказалось неверным. Ждём знающих людей в комментариях.
В зависимости от того, чем была инициализирована память, на свет реагировали либо тёмные участки паттерна, либо светлые.
В считанном паттерне явно прослеживаются блоки 64 на 64 ячейки, это пригодится нам в дальнейшем.
Первые два пункта позволяют ввести компенсацию: инициализировать память инверсным паттерном и интерпретировать цвет ячейки в зависимости от её расположения на стороне клиента.
С точки зрения программиста память представляет собой массив 256 на 256 ячеек, но никто не говорил нам, что в реальности ячейки будут расположены так же.

Задача ясна с порога: найти соответствия между логическими адресами и положением ячеек на кристалле. Если посмотреть на кристалл внимательнее, то можно выделить 16 отдельных зон, на которых расположены ячейки.
We need to go deeper
То, что получилось на данном этапе – уже неплохо, но для дальнейших опытов было бы неплохо наблюдать за изменениями в реальном времени, а не получать изображение по запросу вручную.
Кроме платки с 328-й Мегой у меня также есть «Синяя таблетка» на базе STM32F103C8T6, у которого на борту не просто значительно больше ресурсов, но ещё есть аппаратный USB со скоростью до 12 Мб/с – то, что нужно! Даже не пришлось заниматься сопряжением уровней – у камня оказалось достаточно толерантных к высоким уровням выводов.
В прошивке нет ничего сверхъестественного: микроконтроллер в бесконечном цикле очищает память, вычитывает содержимое и отправляет по USB.
USB и GPIO сконфигурированы с помощью CubeIDE. Я, к сожалению, не прожжённый эмбеддер, поэтому сделать USB на чистом CMSIS для меня пока сложновато, хотя наверное это могло бы улушить производительность по сравнению с использованием HAL.
Я использую USB Communication Device Class, но не в режиме виртуального COM-порта, а в связке с libusb согласно этому руководству от ST.
Также я написал небольшой класс-обёртку, чтобы заменить громоздкие обращения к регистрам и вызовы функций HAL на более читаемые конструкции вида Pin.set(); Pin.reset();
#ifndef PIN_H_
#define PIN_H_
#include "stm32f1xx_hal.h"
class Pin {
GPIO_TypeDef *port_;
uint16_t pin_;
public:
Pin(GPIO_TypeDef *port_, uint16_t pin_) :
port_(port_), pin_(pin_) {
}
void mode(uint32_t mode) {
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.Pin = pin_;
GPIO_InitStruct.Mode = mode;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(port_, &GPIO_InitStruct);
}
inline bool read() const {
return ((port_->IDR >> pin_) & 1U) != 0;
}
inline void set() const {
port_->BSRR = (1UL << pin_);
}
inline void reset() const {
port_->BRR = (1UL << pin_);
}
inline void set(bool value) const {
if (value) {
this->set(); // Установить высокое напряжение
} else {
this->reset(); // Установить низкое напряжение
}
}
inline void toggle() const {
port_->ODR ^= (1UL << pin_);
}
};
#endif
В результате функции чтения/записи заметно упростились:
// Адресная шина
Pin A0(GPIOB, 12);
Pin A1(GPIOB, 13);
Pin A2(GPIOB, 14);
Pin A3(GPIOB, 15);
Pin A4(GPIOA, 8);
Pin A5(GPIOA, 9);
Pin A6(GPIOA, 10);
Pin A7(GPIOB, 7);
// RAS, CAS, WR
Pin RAS(GPIOB, 8);
Pin CAS(GPIOA, 15);
Pin WR(GPIOB, 3);
// DIN, DOUT
Pin DIN(GPIOB, 6);
Pin DOUT(GPIOB, 4);
//***
void write(uint8_t row, uint8_t column, uint8_t value) {
set_address(row);
RAS.reset();
WR.reset();
delay_cycles<4>();
set_address(column);
CAS.reset();
DIN.set(value);
// Задержка 80 нс
delay_cycles<6>();
WR.set();
RAS.set();
CAS.set();
// После окончания цикла нужно выдержать паузу в 150 нс
delay_cycles<11>();
}
uint8_t read(uint8_t row, uint8_t column) {
set_address(row);
RAS.reset();
WR.set();
delay_cycles<4>();
set_address(column);
CAS.reset();
// Задержка 175 нс; TA_CAS + TSU_(RAS-CAS)
delay_cycles<13>();
uint8_t ret = DOUT.read();
RAS.set();
CAS.set();
// После окончания цикла нужно выдержать паузу в 150 нс
delay_cycles<11>();
return ret;
}
Для отправки данных используется простой кольцевой буфер на 64 байта (по размеру пакета USB). К сожалению, нельзя сказать, что полуившееся решение достаточно эффективно. На каждый кадр имеем 256 вызовов очистки (нельзя очистить более одной строки за раз), аналогично 256 вызовов чтения и 128 посылок. При использовании inline-функций, прямом обращении к регистрам вместо HAL-вызовов там, где это возможно и включенной оптимизации -O3 полчается примерно 9-10 кадров в секунду.
Обработка данных и демонстрация работы
На стороне клиента из принятого массива формируется объект типа Bitmap.
var src = new Bitmap(128, 512, PixelFormat.Format1bppIndexed);
BitmapData data = src.LockBits(new Rectangle(0, 0, src.Width, src.Height),
ImageLockMode.WriteOnly, src.PixelFormat);
Marshal.Copy(buffer, 0, data.Scan0, buffer.Length);
src.UnlockBits(data);
Значения ширины и высоты подобраны экспериментально, исходя из геометрии кристалла и реакции отдельных участков изображения на засветку или затенение. Далее происходит разделение чётных и нечётных строк и столбцов (нечётные в одну половину региона, чётные – в другую):
private void SeparateRows(Bitmap bitmap, int startRow, int endRow)
{
List<int> evenRows = new List<int>();
List<int> oddRows = new List<int>();
for (int i = startRow; i < endRow; i++)
{
if ((i % 2) == 0)
evenRows.Add(i);
else
oddRows.Add(i);
}
List<int> reorderedRows = new List<int>(evenRows.Count + oddRows.Count);
reorderedRows.AddRange(evenRows);
reorderedRows.AddRange(oddRows);
Dictionary<int, int> newPositions = new Dictionary<int, int>();
for (int i = 0; i < reorderedRows.Count; i++)
newPositions[reorderedRows[i]] = i + startRow;
using (var src = bitmap.Clone(new RectangleF(0, 0, bitmap.Width, bitmap.Height), bitmap.PixelFormat))
using (Graphics g = Graphics.FromImage(bitmap))
{
for (int current = startRow; current < endRow; current++)
{
int target = newPositions[current];
g.DrawImage(src, new Rectangle(0, target, bitmap.Width, 1), new Rectangle(0, current, bitmap.Width, 1), GraphicsUnit.Pixel);
}
}
}
Здесь производительность временно принесена в жертву читаемости, я планирую переписать этот фрагмент с использованием прямого доступа к памяти вместо рисования с помощью объекта Graphics по аналогии с созданием исходного Bitmap.
Процесс повторяется несколько раз как для всего изображения, так и для разных регионов:
var workingCopy = src.Clone(new RectangleF(0, 0, src.Width, src.Height), PixelFormat.Format24bppRgb);
SeparateRows(workingCopy, 0, workingCopy.Height);
for (int i = 0; i < 8; i++)
{
SeparateRows(workingCopy, i * 64, ((i + 1) * 64 - 1));
}
Заключение
Причины появления паттерна при засветке кристалла выяснить так и не удалось. Его можно компенсировать, но ценой серьёзной потери производительности, т.к. заранее посчитанный буфер не влезает в память. Финальное изображение по-прежнему идёт блоками как из-за малого размера кристалла, так и несовершенства обработки и деления его на зоны.
Меня как будто преследует проклятие незавершённости – начинание доходит до стадии демонстрации концепции, но дальше начинает сказываться нехватка знаний и навыков. Так или иначе, это было интересно; надеюсь, что не только мне одному.
P.S. Вместо рекламы телеграм-канала: никому часом не нужен младший шарпист?
Комментарии (25)

dimao79
29.10.2025 17:19Если не стоит задача использовать именно память - то народ делает самодельные фотики на оптических сенсорах из мышей.

fhunter
29.10.2025 17:19Вроде не все подходят - часть отдают уже обработанные данные, без самой картинки.
Плюсом у всех таких датчиков есть "улучшение" картинки, типа усиления краёв и т.д.

MaFrance351
29.10.2025 17:19Нужно искать древние мыши, где стоит отдельно чип и отдельно контроллер интерфейса. В новых внутри один-единственный чип, который сразу и датчик, и обработчик сигнала, и контроллер USB или PS/2. Из него картинку уже не вытащить.

Lizdroz
29.10.2025 17:19Поди найди нынче правильную древнюю мышь, где сенсор отдает сырые пиксели, а не готовый вектор смещения) Современные мыши для этого уже не годятся

fhunter
29.10.2025 17:19Даташиты на сенсоры в помощь, в некоторых описано. Но по-моему я ещё не видел мышей, где в одном корпусе была и логика PS/2 или USB и сенсор.

AKudinov
29.10.2025 17:19Я, видимо, настолько стар, что при словах "оптический сенсор из мышей" представляю сдвоенный фототранзистор. Внутри механических мышей шарик вращал два перпендикулярных вала, на концах которых находились диски с прорезями. И вращение этих дисков считывалось с помощью оптопар, где с одной стороны диска находился светодиод, а с другой -- сдвоенный фототранзистор.
Но как из этого можно сделать фотоаппарат? Надо раскурочить очень много мышей...

Blackbird_shadow
29.10.2025 17:19ну это было в механических - потом уже стали делать банально камеру правда низкого разрешения ибо большое не требовалось . Народ даже писал проги чтобы вытянуть с неё картинку - более того можно было проехавшись по листу бумаги его сосканировать . А сейчас уже пишут что все мозги уже с камерой интегрированы и только перемещения сразу выдают - скучно :)

Lizdroz
29.10.2025 17:19Наглядное пособие о том, почему документация на чип важна

Coppermine Автор
29.10.2025 17:19Есть такое. Вменяемую информацию про 565-ю серию нашёл только в упомянутом в статье справочнике.

Fox_Alex
29.10.2025 17:19Надо над чипом (уже после объектива) поместить очень тонкую апертуру или точечный источник света. И его перемещением откалибровать истинное положение пикселей на кристалле.

Blackbird_shadow
29.10.2025 17:19слишком замороченно . Я когда хотел нечто такое запилить еще в начале 2000х когда все цеплялось еще к LPT порту . Вскрывал ру5 и обнаружил что там все блоками 32x32 идет - удобно отбраковки делать видимо (были половинные ру5 насколько я помню где часть памяти которая "не удалась" просто блокировалась ) . А делать можно просто поставив объектив и снимая прямую наклоную линию нарисрованную на бумаге например

Coppermine Автор
29.10.2025 17:19Мне показалось, что скорее блоками 64 на 64, причём эти области на кристалле не квадратные :)
Я пытался закрывать кристалл плотным картоном и играться с объективом от камеры видеонаблюдения (фокусное 8мм), но сфокусированное пятно света получается слишком большим и как раз закрывает один или несколько блоков.

Blackbird_shadow
29.10.2025 17:19А почему бы оригинальный циклоп не собрать ? номер журнала легко качается - она там совсем несложная схема то . Правда Mostek MK4008P-9 или AMI S4008-9 который со снятой крышкой используется как сенсор уже аналогофф не имеет - наша к565ру1 уже имела 4 килобита

Coppermine Автор
29.10.2025 17:19Я думал об этом, но как уже выяснилось, в той памяти, которую можно достать, физическое расположение ячейки и её логический адрес отличаются. Поэтому пришлось городить огород из перестановки строк и столбцов программно, чтобы получить мало-мальски вменяемые результаты.

volkahowl
29.10.2025 17:19Автор, а что у вас за планшет на последнем видео?

Coppermine Автор
29.10.2025 17:19Surface Go 3. i3-10100Y, 8/128. В своё время искал компактную машинку, которую можно постоянно носить с собой. По иронии судьбы сейчас это мой основной компьютер, у настольной машины похоже накрылась видеокарта и я временно не могу приобрести замену.

GidraVydra
29.10.2025 17:19Так а превращение единицы в ноль и ноля в единицу происходят при одних и тех же уровнях шин? Просто если одно из них можно объяснить фотодинамикой мосфета (фотогейтинг, фотопроводимость), то второе?

fhunter
29.10.2025 17:19https://www.righto.com/2020/11/reverse-engineering-classic-mk4116-16.html вот тут в статье описано (правда для другого чипа, но примерно того же периода), что одна половинка массива используется для чтения, а во второй половинке стоит опорная ячейка с меньшим конденсатором. (искать по dummy cell). И она определяет порог перехода.
(и при смене половинок - они меняются местами)

tormozedison
29.10.2025 17:19Помню похожий по назначению проект Kuckuck (кукушка, по аналогии с «сейчас вылетит птичка») с программой для DOS.

BarsMonster
Хехе, не раз думал сделать что-то подобное, и даже пытался запилить на EEPROM - но пока неудачно.
По паттерну - нужно калибровать по черному и белому кадру (т.е. для каждого пикселя - нужно помнить уровень черного и белого), без этого никак... Эти калибровочные данные будут зависить от напряжения питания и температуры.
Разброс транзисторов большой, так что без этого никуда. В МК памяти не хватит - а вот в настольнике вполне должно получится.
Lizdroz
EEPROM совсем другой уровень сложности, там ячейка заряд держит годами, заставить ее реагировать на свет за адекватное время наверное почти нереально. DRAM тут куда более подходящий кандидат из-за своей забывчивости
BarsMonster
Там свои хитрости, можно программировать ячейки "на половину", и потом в зависимости от напряжения питания измерять освещенность, что-то такое. Годами ждать конечно бесполезно, но EEPROM у всех много, можно попробовать и так )