И снова здравствуйте, дамы и господа. Наш Отдел Перспективных Разработок продолжает свой цикл статей о древних технологиях и опять выходит на связь с очередным передовым творением нашего сумрачного разума приближающим трепетный момент всеобщей строггофикации.
Сегодня мы совершим небольшой экскурс в историю прошлого века и выдернем оттуда знания об артефакте, практическая пригодность которого на сегодняшний день такова, что перед ней спасует даже наша отдельная лаборатория, которая всем этим поделкам назначение придумывает. В смысле, нулевая практическая пригодность. Однако, вполне сгодится на лабораторную работу для школьника с ардуиной, или занятие на вечер для тебя, мой дорогой инженер с каждодневной работой, женой, дочкой, двумя котами и кризисом среднего возраста.
Итак, мы рассмотрим память на ферритовых кольцах, причем, даже не RAM, а ROM.
Теория
В ретроспективе компьютерной истории двадцатого века память на ферритовых кольцах стала одной из важных вех развития цифровых технологий. Не смотря на то, что классическое ОЗУ на ферритовых кольцах появилось аж в 1945 году, ПЗУ на похожей технологии в физическом виде реализовали несколько позже, когда понадобилось вместить некоторый объем данных в минимально возможное для технологий того времени пространство. ПЗУ на ферритовых кольцах, оно же «веревочная» память применялась как способ хранения прошивки в виде адресуемых единиц данных, например, в бортовых компьютерах СССР и НАСА того времени, летавшие зондами на Марс и на Луну (миссия «Аполлон» как широко известный пример).
Не смотря на сходный принцип работы ферритовых ОЗУ и ПЗУ, способ хранения информации в них несколько различается. Рассмотрение работы ОЗУ мы оставим для следующих статей, а ПЗУ же функционировало достаточно просто:
Ферритовое кольцо являлось, по сути, примитивнейшим трансформатором, который использовался как датчик наличия тока. Если пропустить через кольцо два проводника и подать через один проводник ток (пусть это будет измеряемый проводник), в кольце наведется магнитный поток, и во втором (измеряющем) проводнике так же возникнет ток, который возможно зафиксировать.
Однако, возникающие напряжения на измеряющем проводнике гораздо ниже, чем на измеряемом, потому, для наших целей мы его немного модифицируем - сделаем несколько витков вокруг кольца и добавим конденсатор и диод. Конденсатор позволит накапливать заряд, стекающий с «измеряющего» проводника, по наличию которого в дальнейшем мы будем принимать решение о наличии тока в «измеряемом» проводнике, а диод позволит не потерять накопленный в конденсаторе заряд.
Суть работы данной схемы проста:
Если подать на измеряемый проводник напряжение и считать накопленный конденсатором потенциал с датчика, то, при его наличии можно принять данный факт за установленный бит. Если напряжения нет – бит не установлен.
А теперь магия! Через кольцо возможно пропустить много проводников. Столько, сколько позволит в него запихнуть внутренний диаметр. И, если пометить каждый такой измеряемый проводник адресом, можно судить о наличие отдельного бита по данному адресу.
Экстраполируем данную схему на нашу суровую действительность и попробуем снимать с одного адреса уже не один бит, а целый байт, ибо от одного бита толку мало. Для этого возьмем восемь колец:
Пропустим через кольца проводник, помеченный адресом 0. Поскольку каждое кольцо будет опознавать свой бит, то, через кольца с номером бита, который должен быть установлен в 0, мы просто не будем наш проводник пропускать.
Теперь, если подать напряжение на проводник и читать потенциалы с датчиков колец последовательно, можно составить один байт считанной информации на каждый такой проводник.
Это здорово похоже на запись узелками на веревочках, которую применяли некоторые древние народы, собственно, потому и возник термин «веревочная» память.
Практика
Итак, возьмемся воплощать составленную модель в физическую. Создавать отдельный компьютер под эксплуатирование данного вида памяти мы сегодня не будем и воспользуемся тем, что есть. Я, например, выудил из недр ящика стола очередную ардуину, благо функционала ее для решения нашей задачи хватит с головой.
Во-первых, для считывания потенциала с восьми ферритовых колец нам возможно использовать восемь аналоговых входов (A0 – A7).
Во-вторых, для адресации проводников можно использовать цифровые выводы, коих для небольшого количества адресов вполне хватит.
Впрочем, занимать целый вывод ардуины на один адрес мне показалось расточительным, потому, я еще усложнил схему, применив для адресации выводов сдвиговый регистр из двух 74HC595.
Понимаю, что использование современных микросхем в данном случае несколько «неспортивно», но, построение вот таких вот «гибридов» древних технологий с современными – это особый шик.
В теории, применение сдвигового регистра позволит масштабировать количество адресов памяти не занимая все возможные выводы контроллера, который будет с ними работать, и позволит ему выполнять еще какие-либо задачи. Но, на практике здесь всплывает очередной подводный камень, о котором в конце.
Соберем схему для сдвигового регистра для 16 адресов, который позволит нам адресовать 16 байт:
И схему подключения восьми колец на аналоговые пины A0 – A7. Конденсаторы здесь можно взять любые керамические номиналом 100 нФ, диоды так же любые. Я, например, вообще применил что нашел - германиевые 80х годов. Для зрелищности можно попробовать использовать светодиоды. Измеряющая обмотка на кольце – 10 витков любого тонкого провода.
Так же нужно учесть, что адресные проводники в дальнейшем будут садиться одним концом на землю. Если их все соединить в одной точке, то, при подаче напряжения каждый проводник окажется связан со всеми остальными и сможет вносить помехи в чтение с колец, наводя потенциалы там, где это не нужно. Потому, проводники на землю стоит посадить через диоды. Для наших целей понадобится 16 диодов. Соединение пропускаемых через кольца проводников на землю в данном случае будет выглядеть так:
Я не заморачивался с травлением и свой вариант на макетной плате спаял проводом МГТФ за вечер. Получилось нечто, что можно лицезреть в первой же картинке данной статьи.
Программирование
Итак, мы можем что-то записать в нашу память. Для этого начнем соединять измеряемые проводники:
Один конец, как упоминалось выше, садится через диод на землю. Затем проводник пропускается последовательно через все ферритовые кольца формируя биты, по принципу - если проводник проходит через кольцо, бит установлен, если проводник не проходит через кольцо, бит не установлен.
После этого второй конец проводника сажается на ножку сдвигового регистра, ассоциированную с адресом данного проводника. Всё, ПЗУ «зашито», причем, в прямом смысле.
Теперь обратимся, наконец, к нашей ардуино и попробуем прочесть то, что мы наделали.
Прошивка ардуино
//Адреса пинов снимающих сигнал с ферритовых колец
uint8_t in_pins[8] = {A0, A1, A2, A3, A4, A5, A6, A7};
//Пины управляющие сдвиговым регистром на двух 74HC595
//Пин подключен к ST_CP входу 74HC595
int latchPin = 12;
//Пин подключен к SH_CP входу 74HC595
int clockPin = 13;
//Пин подключен к DS входу 74HC595
int dataPin = 11;
//Вывод на 16-битный сдвиговый регистр числа, кодирующего пины
void shift_out_value(unsigned int val) {
digitalWrite(latchPin, LOW);
//Выведем на сдвиговый регистр последний байт
shiftOut(dataPin, clockPin, MSBFIRST, highByte(val));
//Выведем на сдвиговый регистр первый байт
shiftOut(dataPin, clockPin, MSBFIRST, lowByte(val));
digitalWrite(latchPin, HIGH);
}
//Разрядка конденсаторов на датчиках колец
void discharge() {
for (int i = 0; i < 8; i++) {
//Переключить пин на выход
pinMode(in_pins[i], OUTPUT);
//занулить
digitalWrite(in_pins[i], LOW);
//небольшая задержка
delay(3);
//Переключить обратно на вход
pinMode(in_pins[i], INPUT);
}
}
//Считывание байта с колец
uint8_t read_ferrite_byte(unsigned int address) {
uint8_t values[] = {0, 0, 0, 0, 0, 0, 0, 0};
//Для точности можно увеличить количество проходов чтения, но,
//работает и так
//for (uint8_t phase = 0; phase < 2; phase++)
{
//Разрядим
discharge();
//Подергаем нужным проводком по нужному адресу через сдвиговый
//регистр
//Это нужно чтобы создать в ферритовых кольцах магнитный поток и
//зарядить конденсаторы
//на датчиках тех колец, через которые проходит проводник с нужным
//адресом
for (uint8_t j = 0; j < 8; j++) {
shift_out_value(address);
shift_out_value(0);
}
//Считаем показания с датчиков всех колец
for (uint8_t ix = 0; ix < 8; ix++)
{
values[ix] += analogRead(in_pins[ix]);
}
}
discharge();
//Упакуем все считанные значения в один байт, где 0 - не считано
//ничего, 1 - считано что-то
uint8_t result = 0;
for (uint8_t ix = 0; ix < 8; ix++)
{
result = (result << 1) | ((values[ix] > 1) ? 1 : 0);
}
return result;
}
void setup() {
pinMode(latchPin, OUTPUT);
pinMode(clockPin, OUTPUT);
pinMode(dataPin, OUTPUT);
for (int i = 0; i < 8; i++) {
pinMode(in_pins[i], INPUT_PULLUP);
digitalWrite(in_pins[i], LOW);
}
Serial.begin(115200);
//разрядим конденсаторы
discharge();
Serial.println("------------------------------");
//Убедимся, что с ферритовых колец ничего не читается
for (uint8_t ix = 0; ix < 8; ix++)
{
Serial.print(analogRead(in_pins[ix]), DEC);
Serial.print(" ");
}
Serial.println();
Serial.println();
//Считаем и выведем значения по адресам 1..16
for (int i = 0; i < 16; i++) {
Serial.print("address[");
if (i < 10) Serial.print("0");
Serial.print( i, DEC);
Serial.print("] = ");
uint8_t nibbles = read_ferrite_byte((unsigned int)1 << i);
Serial.print(nibbles, DEC);
Serial.print(" - ");
Serial.print(nibbles, BIN);
if (nibbles > 32) {
Serial.print(" - ");
Serial.print((char)nibbles);
}
Serial.println();
}
}
void loop() {
}
Алгоритм чтения следующий:
Разрядить конденсаторы на датчиках ферритовых колец, поскольку, на них мог сохраниться потенциал, оставшийся от предыдущих шагов чтения, и он может внести помехи в читаемые данные.
Задать на адресном регистре адрес ножки, к которой подключен проводник с нужным адресом. При этом, через проводник потечет ток, который вызовет накопление потенциала в конденсаторах датчиков тех колец, через которые пропущен данный проводник.
Снять показание потенциала накопленного заряда со всех колец последовательно по номерам бит в байте и составить считанное значение.
Результат зависит от того, что было зашито, разумеется.
Ну и подводный камень, о котором я упоминал ранее:
Для наведения магнитного потока в кольцах нужно подавать питание на измеряемые проводники не постоянно, а с определенной частотой. В схеме со сдвиговым регистром для того приходится совершать цикл из нескольких записей адреса и нуля:
Записать в регистр адрес пина нужного проводника, тем самым включив его.
Записать в регистр 0, выключив все проводники.
Повторить N раз.
Если регистр небольшой, скорость записи в него позволит сформировать некоторую частоту подаваемого на проводники тока, позволяющую завести ферритовые кольца и накопить в их датчиках некоторый потенциал.
Если начать увеличивать длину регистра, увеличивая количество адресуемых проводников, скорость записи в регистр так же вырастет, и в какой-то момент частота подаваемого тока уже будет недостаточна для формирования магнитного потока в кольцах.
В таком случае, интересным домашним заданием становится решение подобной задачи масштабирования памяти. Можно попробовать для увеличения количества адресов памяти использовать какую-нибудь каскадную адресацию сдвиговых регистров, где сдвиговый регистр будет ссылаться на следующий сдвиговый регистр. Или использовать логические дешифраторы, повешенные на тот же регистр.
Если у вас в голове родилась интересная схема – поделитесь, а то у меня целый мешок советской логики без дела лежит. ?
UPD: В комментариях предложили использовать ШИМ через ножку OE самого сдвигового регистра, что решает задачу.