Запускаем Intel 87C51 — первый крупносерийный микроконтроллер (1980)

1600d7c0aeb88f78d9d01b9801953381.jpg

  Мы принимаем как должное удобства современных микроконтроллеров — все ключевые компоненты интегрированы в один корпус: флэш-память/EEPROM, SRAM, само процессорное ядро, PLL, ADC/DAC, PWM, последовательные порты и многое другое.

  Но так было не всегда. Первым монолитным микроконтроллером был Intel 8048 (MCS-48) выпущенный в 1976 по n-МОП технологии. Не планировалось что у него будет длинный жизненный цикл и уже через 4 года в 1980 на смену ему пришел Intel 8051 (MCS-51), завоевавший мир. Он имел на борту 4КиБ однократно-программируемой памяти, 128 байт SRAM, GPIO, последовательные порт и, собственно, 8-битное процессорное ядро. Intel 8751 был вариантом на базе УФ-стираемой EPROM памяти, и C-версии на КМОП процессе. Ядро 8051 не было особенно производительным — т. к. даже самые простые операции требовали 12 тактов, так что при тактовой частоте 20МГц он едва мог сделать миллион операций в секунду. Также отсутствовали команды деления и умножения, впрочем, для того времени это было повсеместно. Конечно, современные 8051-совместимые ядра зачастую на порядки быстрее (и по тактовой частоте, и по количеству тактов на команду).

Пару недель назад ко мне в руки случайно попал D87C51FC-20 — и я решил его запустить, чтобы прочувствовать проверенные временем технологии. Сразу отмечу — не стоит тут искать практической пользы, это скорее экскурс в историю на 44 года назад…

2568cb9598e26a7f877276eaa8162c03.jpeg

Мне всегда было интересно, что там внутри и какая толщина кварцевого окна, так что я пожертвовал AMD AM27C64. Окно оказалось толщиной примерно 1 мм и приклеено на удивление хорошо:

8c855f69d7d02e6c590a1d558c88ad7e.jpeg

  Мы привыкли думать, что плоские оптические окна не влияют на изображение и через них все можно рассмотреть без ограничений. Но это верно только для объектов на условной бесконечности, а вблизи, когда мы наблюдаем с большой числовой апертурой (что обычно делают для повышения разрешения в случае микроскопа) — плоское окно вносит сферическую аберрацию и для объективов с апертурой примерно более 0.3 — разрешение начинает ухудшаться, а не увеличиваться. Аналогичная проблема есть и при попытке увидеть данные на компакт-диске — со стандартным объективом с апертурой 0.3 видно лучше, чем с 0.7.

  Тут самое время достать козырь — объектив с коррекцией толщины стекла (изначально их делали для контроля ЖК мониторов на производстве). На фотографии ниже — слева 87C51 без коррекции толщины стекла (низкий контраст и разрешение из-за сферической аберрации), справа — с коррекцией на 1.05mm стекла:

3ddf5951345fb824bc319ae945e705e8.jpeg117e63c43266751b1f4b2ed4d5e2b4b5.jpeg

Теперь можно сфотографировать весь кристалл через кварцевое окно:

0871deb6ce89517c71790ebbab971396.jpeg

Стираем старые данные

  Чип стирается относительно высокой дозой УФ излучения. Обычно используются кварцевые лампы излучающие 254нм. Я попробовал стереть светодиодом на 245nm -, но у него была настолько маленькая оптическая мощность / крошечный КПД, что даже через час ни одного бита не стерлось. Видимо для длин волн короче 350нм ртутные лампы пока безусловно разрывают полупроводниковые светодиоды.

  Некоторое время назад для другого проекта я купил странные ртутные лампы требующие 10v/300mA. Их продают в версии «с озоном» (кварцевая колба, пропускает линию ртути 185нм) и «без озона» (излучение 254нм и далее). Для стирания данных, конечно, лучше использовать 254нм, но у меня была более злая версия. Интересно, что только верхняя часть спирали лампы покрыта оксидным слоем для увеличения эмиссии электронов.

bbde76b3b68977f697c1edd8d3621626.jpeg

Лампа работает очень необычным образом. Включать её нужно блоком питания с напряжением ~14–15В и ограничением тока 300mA. При включении спираль нагревается до красна, и испаряет ртуть из пластины с амальгамой.

b4800d7d31ed64b8344d4436ceb1c745.jpeg

  Когда давление паров достаточно повышается — зажигается разряд между верхними частями спирали, и резистивный нагрев спирали автоматически резко снижается. Эта фотография сделана в защитных очках, камера с УФ фильтром. Все тело должно быть закрыто, т. к. и 185 и 254нм вызывают рак кожи и катаракту. Озон ядовит. В общем, это лучше не повторять, и стирать лампой без озона.

  Когда кварц так светится голубым — нужно бежать!

21c37e51ec28a0308f8b2c3b668c9c4e.jpeg

Лампа установлена в банке с огурцами (без огурцов), с алюминиевой фольгой с внутренней стороны. Обычное стекло банки не пропускает излучение короче 365нм. Точка излучения лампы была примерно в 2 сантиметрах от окна микросхемы, и содержимое стиралось примерно за 10 минут.

208e127a12151f996dcc1a2ea40a33d8.jpeg

Прошивка

С народным программатором MiniPro TL866 — никаких сложностей (сейчас на него есть open-source софт).

14a4526f19ae87c2fcbc86e7491546e4.jpeg

Пишем демо-программу

  Что ж, завязываем с фотографиями — пора писать код! В дополнение к стандартному мигающему светодиоду я решил также найти простые числа и напечатать их через последовательный порт (чтобы задача была не слишком сложная, и не слишком тривиальная вычислительно). Изначально я попробовал пойти по пути Jay Carlson и попробовал использовать современную IDE 8051-совместимых чипов от Silicon Labs (Simplicity Studio). К сожалению, там бинарная совместимость похоже только для GPIO, но не для последовательного порта (что не удивительно, последовательный порт в оригинальном 8051 очень уж простой). Так что я перешел на open source SDCC с которым все пошло как по маслу.

  Я не хотел усложнять код кольцевыми буферами и прерываниями, и отправляю данные побайтно. Единственная оптимизация, которую я тут сделал — проверка завершения отправки до отправки следующего байта, а не после (как это сделано в большинстве примеров). Это позволяет частично распараллелить работу последовательного порта и вычислений. Для того, чтобы это работало и для первого байта — я устанавливаю флаг завершения передачи предыдущего байта TI = 1 в начале программы.

#include <8051.h>
#include 
#include 

int putchar(int c) {
    while (TI==0);      /* Wait until transmission is complete */
    TI = 0;
    SBUF = c;     
    return c;
}

void toggle_led(void)
{
    P1_0 = !P1_0;
}

int main (void)
{
    int i, j, loop_limit;
    bool is_prime;

    //Serial port speed. Perfect 9600 for 18.432MHz crystal
    TH1 = (unsigned char)(256-5);//No "overflow in implicit constant conversion"
    TMOD = 0x20;
    SCON = 0x50;
    TR1  = 1;
    TI = 1;


    printf("Hello world!\r\n");
    printf("Let's calculate some primes: 2");
    while (1) 
    {
      for (i = 3; i <= 32000; i+=2) {
          loop_limit = 180;
          //We should not calculate square root on 8051 :-)
          if(i-1

Т. к. в 8751 нет PLL (что не удивительно), получить требуемую скорость работы последовательного порта может оказаться сложнее, чем кажется. Скорость последовательного порта определяется частотой кварца и предельным значением счета таймера1 (TH1). Изначально я запускал 8751 от кварца с максимальной частотой 20Мгц и TH1=256–5 -, но данные последовательного порта не удавалось корректно прочитать на компьютере. А вот осциллограф декодировал корректно. Оказалось, что скорость последовательного порта получилась с ошибкой в 9% (10416 бод вместо 9600, по формуле X = F/(12×32*Y) где F это частота кварца и Y — предел счета таймера). Можно было бы сконфигурировать USB-UART адаптер CP2102 на эту частоту, или подобрать другой кварц, который дал бы меньшую ошибку.

b5334abc567295a5a73d89391e0a0417.png

Оказалось, что с кварцем на 18.432Mhz и таймером 256–5 — частота получается идеальная, как будто этот кварц и был создан для этой задачи. (Комментатор за кадром: Именно так)

Собираем прототип

P1.0 (pin 1): LED.
Reset (pin 9): Active high. Притягиваем к GND резистором, и к VCC через небольшой электролитический конденсатор. Это обеспечит сброс микроконтроллера при подаче питания.
TXD (pin 11): Выход последовательного порта.
XTAL1 и XTAL2 (pins 18 and 19): Кристалл на 18.432Mhz. Заработало без внешних конденсаторов, что вероятно дает некоторую незначительную ошибку частоты и меньшую стабильность. Паразитных емкостей макетной платы однозначно недостаточно (~2pF).
GND (pin 20) и VCC (pin 40): 5V питание и земля.
EA (pin 31): Для отключения внешней памяти (её у нас нет) — подключаем к VCC.

e4f40c4e33b4a4dc0da32b78308b28ec.jpeg

Запускаем

С корректными настройками последовательного порта — все работает!

6dfce23d389e935546c1171497379af6.png

Если посмотрим на TX осциллографом при передачи разных простых чисел, можно увидеть что-то неожиданное:

861c92dfe305d889e393fc6a36edbffd.pnga9f15162ff158e9a74fd3d6cda47ef04.png0e763741a365330d4ea50f384f4f751f.png

Когда печатаем трехзначное число — задержка между пробелом и первой цифрой ~1.25ms, 1.8ms для четырехзначного и 2.5ms для пятизначного. Почему так получается?

  После того как printf (» %d», i) печатает пробел, микроконтроллеру нужно конвертировать число в строку и этот перевод без аппаратного деления занимает достаточно большое время, так что даже на скорости 9600 бод заметно. Больше знаков — больше делений — больше задержка на конвертацию.

Резюме

  Заметную часть времени до рабочего кода съели 8 итераций стереть-записать, раньше для этого были полноценные отладочные платы с кодом во внешней памяти. Самые необходимые фичи современных микроконтроллеров уже существуют 87C51 — он очень сильно обогнал своё время. Тем не менее, современные контроллеры на этой задаче ожидаемо были бы намного проще в работе:

  1. Производительность. 1(один) 8-bit MOPS против ~50–150 32-bit MOPS в современных микроконтроллерах за 1–2$. Разрыв в математике еще больше, т.к. теперь мы избалованы относительно быстрым делением и умножением. Вместо написания уникального ручного кода деления и умножения на 10 сдвигами — можно просто делить и не переживать. Теперь и аппаратной арифметикой с плавающей точкой местами никого не удивить.

  2. PLL. Сейчас можно поставить самый популярный/дешевый кварц на 8Mhz и сконфигурировать почти любую удобную частоту, не собирая библиотеку кварцев на все случаи жизни. Уникальные кварцы теперь нужны в основном только в ситуации где нужен низкий фазовый шум (высокоскоростные интерфейсы, ЦАП/АЦП высокого разрешения и проч.).

  3. Аппаратный отладчик и программирование через JTAG — обеспечивает очень быстрые итерации.

  4. Последовательные порты (и другие интерфейсы) с буферами и гораздо более высокими скоростями.

  5. Внутренний сброс, brown-out detection, watchdog timer.

    Список, конечно, можно продолжать…

  Так что, несмотря на то что зачастую 8051-совместимые микроконтроллеры дешевле, разработка под них требует больше усилий и времени (=денег). Но для применений с очень большим объемом (миллионы штук), где задача относительно несложная и не требует много математики (условная микроволновка) или уже есть наработанная база готового кода — общая стоимость проекта может оказаться минимальной, потому наследники 8051 ставят в новые продукты и сегодня (и нет предпосылок к уходу 8051 на пенсию даже при наличии свободных/условно-бесплатных ядер RISC-V).

© Habrahabr.ru