CPLD-ретрокомпьютинг. Часть 1 — Доска для студента

Ректрокомпьютинг бывает разный. Кто-то собирает килограммы древних процессоров, кто-то восстанавливает советские ЕС ЭВМ, кто-то до сих пор разгоняет Celeron в жидком азоте, а мы же насладимся платой Altera University Programm Board UP1 1997 года c древней CPLD MAX7128S и даже поморгаем светодиодом (и не только).477de8f8fc8f494ebe8d3a853cf661a3.jpg
Altera MAX (Multiple Array matriX) без цифр — это серия древних CPLD от фирмы Altera. Серия эта была начата в далеком 1993 году (более 20 лет назад!) и в настоящее время уже не производится, ее полностью заменили серии MAX II, MAX V и даже MAX 10. В те же времена первыми были выпущены MAX7000, потом «большие» MAX9000, потом «сложные» MAX5000 и «дешовые-низковольтные» MAX3000. Потом добавились разные буквы S, E, B для JTAG, малопотребляющих и низковольтных версий и т.д., вобщем, смотрите в документации.

Нас будет интересовать версия MAX7000S, которая отличается наличием JTAG-порта, то есть в ней реализована возможность «программирования в системе» через встроенный интерфейс IEEStd. 1149.1 Joint Test Action Group (JTAG).

Кстати, первые MAX7000 (без S) программировались специальным аппаратным программатором и для DIY примененеия практически не пригодны.


Для продвижения своего продукта фирма Altera выбрала простой и безошибочный метод: в 1997 году они анонсировали Altera University Program и стали распространять отладочную плату среди (американских) университетов задешево. Плата получила название Altera UP1 (от University Programm). Чуть позднее вышла вторая ревизия — Altera UP2, на которой была чуть изменена разводка и припаян более ёмкий чип FLEX (о нем чуть ниже).

Ссылка из WebArchive о данной плате. (На «материнском» сайте уже удалена.)

Надо сказать, что задумка фирмы Altera удалась на славу. Десятки университетов сделали на этой плате лабораторные работы по курсам «Скоростная Разработка Цифровой Электроники» и т.д. Гугление по «Altera UP1» и «UP2» до сих пор дает кучу ссылок, включая даже видеокурсы.

Один из наиболее полных ресурсов: users.ece.gatech.edu/~hamblen/ALTERA/altera.htm
Еще обзор: www.pyroelectro.com/tutorials/up2

Популярность платы в начале 2000-х была так высока, что даже вышло несколько книжек, упоминавших эту плату (гуглим Altera UP1 book), а книга «Rapid Prototyping of Digital Systems» за авторством James O. Hamblen и Michael D. Furman целиком построена вокруг этой платы, и может рассматриваться как расширение документации. (Желающие отыщут в два клика). Книга, кстати, выдержала несколько переизданий, но начиная с Edition 3 (SOPC Edition) она базируется вокруг плат Terasic DEx, тоже любимых у плисоводов, но которые не попадают под приставку ретро-

Наконец, в 2010-х плата окончательно устарела, их стали списывать из университетов и они стали появляться на e-bay за смешные деньги, где, собственно, пара таких плат и была приобретена. Есть и ложка дёгтя — увы, студенты (даже американские) не отличаются аккуратностью, поэтому битые I/O пины на таких second-hand платах не редкость, да и ресурс записи CPLD поизношен. Так что чип может быть слегка подгорелый, лучше его сразу заменить, благо он в панельке PLCC84.

Глубокое гугление дает кучу ссылок на университетские ресурсы разных чтран, от Японии до Польши, что показывает, насколько плата распространилось. Использовалась ли плата UP1 так широко в России, доподлинно неизвестно, но на сайте питерского ЛИАП (простите мне старое название) лежала методичка на русском с описанием платы для неких лабораторных работ. Если борду кто еще использовал в других ВУЗ-ах — дайте знать, интересно.

Теперь пара слов о самой плате. Кроме CPLD EPM7128S на ней припаян еще старинный чип FPGA FLEX 10K, есть DIP-8 панель под Configuration Memory, но сам чип EPC1 почему-то в комплект не входит (может потому что он одноразовый). Чип FLEX 10K — довольно приличный даже по современным меркам, особенно на плате Altera UP2 — EPF10K70. Как обычно, есть стабилизатор питания, две пары 7-сегментных индикаторов, кварц на 25.175 MHz, кнопки, переключатели и просто светодиодики (можно мигать!). К FLEX прпаяны гнезда VGA и PS/2 мыши, можно делать даже видеоигры, но речь сейчас не о FLEX.

К плате прилагается старинный роскошный LPT-шный JTAG Altera ByteBlasterMV (MV от слова MultiVolt). Кстати, если будете брать на e-bay, требуйте наличия ByteBlaster-а. Правда, зачем он в век USB и при отсутствии LPT — непонятно, но должен же быть полный комплект.


Поскольку слово «ретрокомпьютинг» было произнесено, то да, надо признаться, что внимание к MAX7000S было вызвано именно в аспекте применить её для интерграции в ретро-компьютеры, например приделать что-нибудь для БК-0010 или Микроши. И причина интереса тут очень простая: 5 вольт TTL. Понятно, этоха сменилась и низковольтный прогресс не остановить, но ретро- и прогресс вещи, мммм… несовместимые…

Так или иначе, в настоящее время 5-вольтовые CPLD и FPGA практически исчезли. Семейство MAX7000S уже не рекомендовано к применению и не выпускается, как и конкурирующая серия Xilinx XC9500 (без XL). Более новые CPLD уже »5-volt tolerant» то есть в лучшем случае не горят по входу от 5 вольт, но выход все равно 3.3 вольта, и на сайте 6502.org была информация, что некоторые старые процессоры не работают с 3.3 вольтовыми пинами.

MAX7000S еще можно купить на e-bay/aliexpress не задорого, но это складские остатки или откровенно б/у чипы. Мне один раз приехали изрядно поцарапанные корпуса PLCC84, но зато 6 штук, хотя заказано было 5. Китайцы тоже прекрасно понимают, чем они торгуют.


Курсы по Altera UP1 рекомендуют ее программировать с помощью античной программы MAX+PLUS II, брутально рисовать схемы из элементов 2И-НЕ и D-триггеров и прошивать программатором Altera ByteBlaster на порту LPT. Это, конечно, всё здорово, но не стоит путать ретро-компьютинг и мазохизм. У каждого плисовода развернут Quartus II и есть USB Blaster. Если нету, то цены на него упали настолько, что на aliexpress клон USB-Blaster-а стоит менее 5$ и несомненно рекомендуется к преобретению. Опять же пригодится для DE2 и т.д. (Бластеры обсудим чуть ниже).

Что касается Quartus II, то для поддержки MAX7000S последней версией является 13.0 и вполне годится Web Edition.Там немного урезан функционал, например компилятор не поддерживает многоядерность и т.д., но это нам не критично. У автора вообще развернут Quartus II 11.0 SP1 и все работает.

Итак, программировать мы будем на современном VHDL, в (почти) современном Quartus II и прошивать через (почти) современный USB-Blaster. И все это на ретро- MAX7000S. Как говорится, железо — ретро, а софт — с удобствами.

Статей по программированию CPLD/FPGA в сети предостаточно, например на we.easyelectronics.ru в хабе ПЛИС, а на гибнушем Хабре их был почти десяток. Идеологически близка, например, плата проекта «Марсоход», там только CPLD более новый. Так что туториалы «Марсохода» нам вполне пригодятся.

Проект не представляет из себя ничего особенного: сделаем 16-ричный таймер с отображением на 7-LED, раз он уже припаян на плате. Вдобавок, будем мигать десятичной точкой. Ресурсы у CPLD очень ограничены, в MAX7128S всего 128 макро-ячеек, поэтому будем постоянно проверять себя, заглядывая в получившийся RTL и обращая внимание на количество израсходованных ресурсов.

Первое — надо получить меандр с периодом около секунды, не обязательно точно, это же демо. Нужен счетчик с довольно большим коэффициентом пересчета, чтобы поделить 25.175MHz от кварца. Можно разделить, например, на 2^24= 16777216 и получим частоту примерно 1.5 герца. Классический синхронный счетчик +1 тут не подходит, потому что Quartus порождает гигантский сумматор на 24 разряда, на один вход которого подана константа »1». Жесть. Сделаем обыкновенный делитель из цепочки D-триггеров, потратив 24 ячейки, так называемый ripple-counter. Граммар-дизайн-наци скажут, это плохой стиль, тактовые импульсы должны ходить по глобал-клокам, счетчик должен быть синхронный и т.д. На что мы ответим просто — дайте нам PLL или не надо ставить такой высокочастотный кварц.

Для порождения длинной цепочки триггеров воспользуемся конструкцией VHDL GENERATE. Это такой замаскированный макро-оператор языка VHDL и с ним можно делать забавные вещи, но у нас все просто.

Счетчик
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;

entity D_FF is
-- Flipflop
port (D,CLK_S   : in std_logic;
                Q                       : buffer std_logic := '0';
                NQ                      : out std_logic := '1' );
end entity D_FF;

architecture A_RS_FF of D_FF is
begin
BIN_P_RS_FF: 
        process(CLK_S) begin
                if CLK_S = '1' and CLK_S'Event
                        then    Q  <= D;
                end if;
        end process;
NQ <= not Q;
end architecture A_RS_FF;

library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;

-- Couner
entity COUNTER_BIN_N is generic (N : integer := 24);

port (Q         : out std_logic_vector(0 to N-1);
                IN_1    : in std_logic );
end entity COUNTER_BIN_N;

architecture BEH of COUNTER_BIN_N is

component D_FF
        port(D, CLK_S : in std_logic; Q, NQ : out std_logic);
end component D_FF;

signal S : std_logic_vector(0 to N);

begin
        S(0) <= IN_1;
        G_1 : for I in 0 to N-1 generate
                D_Flip_Flop : D_FF port map 
                                                (D => S(I+1), CLK_S => S(I), Q => Q(I), NQ => S(I+1));
        end generate;

end architecture BEH;


Дизайн верхнего уровня примитивен чрезвычайно. Здесь сделаем классический счетчик на 8 разрядов, чисто поржать чтобы ужаснуться полученной RTL реализации с сумматором. Но в реале все не так плохо, счетчик кушает ровно 8 макроячеек, но сумматор впечатляет, хотя и влезает в стандартную логическую часть макроячеек в пределах логического блока. Компилятору зачёт.

Верхний уровень
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.std_logic_arith.all;

entity UP1TEST is
        port (
                CLOCKINPUT              : in std_logic;
                LEDpL                           : out   std_logic;
                LEDpR                           : out std_logic;
                LED7L                           : out   std_logic_vector(0 to 6);
                LED7R                           : out std_logic_vector(0 to 6)
                );
                
end entity UP1TEST;

architecture rtl of UP1TEST is
        signal COUNTER          : std_logic_vector(7 downto 0);
        signal SLOWCLOCK        : std_logic;
        signal divider                  : std_logic_vector(0 to 23);
begin

-- 27.175MHz ~~ 2^24 
div:
        work.COUNTER_BIN_N port map (Q => divider, IN_1 => CLOCKINPUT);

SLOWCLOCK <= divider(23);

LEDpL <= '1';
LEDpR <= SLOWCLOCK;
        
process (SLOWCLOCK)
        variable count : natural range 0 to 255 := 0;
begin
        if rising_edge(SLOWCLOCK) then
                        count := count + 1;
        end if;
        COUNTER <= conv_std_logic_vector(count,8);
end process;

disp_r:
        work.seg7 PORT MAP (DIG => COUNTER(3 downto 0), SEG7 => LED7R);
disp_l:
        work.seg7 PORT MAP (DIG => COUNTER(7 downto 4), SEG7 => LED7L); 
                                                                         
end rtl;


7-сегментный дешифратор вопросов не вызывает и синтезируется в обычные логические схемы с честными тёплыми 5-вольтовыми пинами для светодиодов, хотя и жрет макроячейку на каждый пин.

Дешифратор на 7-LED
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_unsigned.all;

entity seg7 is
        port (
                DIG     : in    std_logic_vector(3 downto 0);
                SEG7    : out   std_logic_vector(6 downto 0));
end entity seg7;

architecture rtl of seg7 is
begin
with DIG select
SEG7 <=      "1001111" WHEN "0001", -- 1 +--A--+
                "0010010" WHEN "0010", -- 2 |     |
                "0000110" WHEN "0011", -- 3 F     B
                "1001100" WHEN "0100", -- 4 |     |
                "0100100" WHEN "0101", -- 5 +--G--+
                "0100000" WHEN "0110", -- 6 |     |
                "0001111" WHEN "0111", -- 7 E     C
                "0000000" WHEN "1000", -- 8 |     |
                "0000100" WHEN "1001", -- 9 +--D--+
                "0001000" WHEN "1010", -- A
                "1100000" WHEN "1011", -- B
                "0110001" WHEN "1100", -- C
                "1000010" WHEN "1101", -- D
                "0110000" WHEN "1110", -- E
                "0111000" WHEN "1111", -- F
                "0000001" WHEN others; -- 0
end rtl;


Результат:
c339ed23efb0468e92a2ae36888940bb.jpg
Не забудем привязать выходные пины. Для назначения можно пользоваться редактором Pin Planner, но можно хардкорно редактировать текстовый .QSF файл. Здесь только одна, пожалуй, тонкость — порядок бит в массиве в зависимости от объявления его TO или DOWNTO. Тут можно заметить, что вход идет в DOWNTO сторону для совместимости с битами счетчика (у DOWNTO младший бит справа), а выход на 7-сегментный индикатор — TO для совместимости с документацией. Можно его сделать тоже DOWNTO, но тогда придется переопределить номера пинов.

QSF
# -------------------------------------------------------------------------- #
#
# Quartus II
# Version 11.0 Build 208 07/03/2011 Service Pack 1 SJ Web Edition
# Date created = 23:59:49  July 10, 2015
#
# -------------------------------------------------------------------------- #
#
# Notes:
#
# 1) The default values for assignments are stored in the file:
#               UP1-TEST_assignment_defaults.qdf
#    If this file doesn't exist, see file:
#               assignment_defaults.qdf
#
# 2) Altera recommends that you do not modify this file. This
#    file is updated automatically by the Quartus II software
#    and any changes you make may be lost or overwritten.
#
# -------------------------------------------------------------------------- #


set_global_assignment -name FAMILY MAX7000S
set_global_assignment -name DEVICE "EPM7128SLC84-7"
set_global_assignment -name TOP_LEVEL_ENTITY UP1TEST
set_global_assignment -name ORIGINAL_QUARTUS_VERSION "11.0 SP1"
set_global_assignment -name PROJECT_CREATION_TIME_DATE "23:59:49  JULY 10, 2015"
set_global_assignment -name LAST_QUARTUS_VERSION "11.0 SP1"
set_global_assignment -name ERROR_CHECK_FREQUENCY_DIVISOR "-1"
set_global_assignment -name MAX7000_DEVICE_IO_STANDARD TTL
set_global_assignment -name RESERVE_ALL_UNUSED_PINS_NO_OUTPUT_GND "AS INPUT TRI-STATED"

set_global_assignment -name OPTIMIZE_HOLD_TIMING OFF
set_global_assignment -name FITTER_EFFORT "STANDARD FIT"
set_location_assignment PIN_83 -to CLOCKINPUT
set_location_assignment PIN_58 -to LED7L[0]
set_location_assignment PIN_60 -to LED7L[1]
set_location_assignment PIN_61 -to LED7L[2]
set_location_assignment PIN_63 -to LED7L[3]
set_location_assignment PIN_64 -to LED7L[4]
set_location_assignment PIN_65 -to LED7L[5]
set_location_assignment PIN_67 -to LED7L[6]
set_location_assignment PIN_68 -to LEDpL
set_location_assignment PIN_69 -to LED7R[0]
set_location_assignment PIN_70 -to LED7R[1]
set_location_assignment PIN_73 -to LED7R[2]
set_location_assignment PIN_74 -to LED7R[3]
set_location_assignment PIN_76 -to LED7R[4]
set_location_assignment PIN_75 -to LED7R[5]
set_location_assignment PIN_77 -to LED7R[6]
set_location_assignment PIN_79 -to LEDpR
set_global_assignment -name VHDL_FILE counter.vhd
set_global_assignment -name VHDL_FILE seg7.vhd
set_global_assignment -name VHDL_FILE "UP1-TEST.vhd"


Результат: Logic cells; 47 / 128 (37%)

Репорт об утилизации МЯ
+---------------------------------------------------------------------------------------------------------------------------+
; Fitter Resource Utilization by Entity                                                                                     ;
+----------------------------------+------------+------+-----------------------------------------------------+--------------+
; Compilation Hierarchy Node       ; Macrocells ; Pins ; Full Hierarchy Name                                 ; Library Name ;
+----------------------------------+------------+------+-----------------------------------------------------+--------------+
; |UP1TEST                         ; 47         ; 21   ; |UP1TEST                                            ; work         ;
;    |COUNTER_BIN_N:div|           ; 24         ; 0    ; |UP1TEST|COUNTER_BIN_N:div                          ; work         ;
;       |D_FF:\G_1:0:D_Flip_Flop|  ; 1          ; 0    ; |UP1TEST|COUNTER_BIN_N:div|D_FF:\G_1:0:D_Flip_Flop  ; work         ;
;       |D_FF:\G_1:10:D_Flip_Flop| ; 1          ; 0    ; |UP1TEST|COUNTER_BIN_N:div|D_FF:\G_1:10:D_Flip_Flop ; work         ;
;       |D_FF:\G_1:11:D_Flip_Flop| ; 1          ; 0    ; |UP1TEST|COUNTER_BIN_N:div|D_FF:\G_1:11:D_Flip_Flop ; work         ;
;       |D_FF:\G_1:12:D_Flip_Flop| ; 1          ; 0    ; |UP1TEST|COUNTER_BIN_N:div|D_FF:\G_1:12:D_Flip_Flop ; work         ;
;       |D_FF:\G_1:13:D_Flip_Flop| ; 1          ; 0    ; |UP1TEST|COUNTER_BIN_N:div|D_FF:\G_1:13:D_Flip_Flop ; work         ;
;       |D_FF:\G_1:14:D_Flip_Flop| ; 1          ; 0    ; |UP1TEST|COUNTER_BIN_N:div|D_FF:\G_1:14:D_Flip_Flop ; work         ;
;       |D_FF:\G_1:15:D_Flip_Flop| ; 1          ; 0    ; |UP1TEST|COUNTER_BIN_N:div|D_FF:\G_1:15:D_Flip_Flop ; work         ;
;       |D_FF:\G_1:16:D_Flip_Flop| ; 1          ; 0    ; |UP1TEST|COUNTER_BIN_N:div|D_FF:\G_1:16:D_Flip_Flop ; work         ;
;       |D_FF:\G_1:17:D_Flip_Flop| ; 1          ; 0    ; |UP1TEST|COUNTER_BIN_N:div|D_FF:\G_1:17:D_Flip_Flop ; work         ;
;       |D_FF:\G_1:18:D_Flip_Flop| ; 1          ; 0    ; |UP1TEST|COUNTER_BIN_N:div|D_FF:\G_1:18:D_Flip_Flop ; work         ;
;       |D_FF:\G_1:19:D_Flip_Flop| ; 1          ; 0    ; |UP1TEST|COUNTER_BIN_N:div|D_FF:\G_1:19:D_Flip_Flop ; work         ;
;       |D_FF:\G_1:1:D_Flip_Flop|  ; 1          ; 0    ; |UP1TEST|COUNTER_BIN_N:div|D_FF:\G_1:1:D_Flip_Flop  ; work         ;
;       |D_FF:\G_1:20:D_Flip_Flop| ; 1          ; 0    ; |UP1TEST|COUNTER_BIN_N:div|D_FF:\G_1:20:D_Flip_Flop ; work         ;
;       |D_FF:\G_1:21:D_Flip_Flop| ; 1          ; 0    ; |UP1TEST|COUNTER_BIN_N:div|D_FF:\G_1:21:D_Flip_Flop ; work         ;
;       |D_FF:\G_1:22:D_Flip_Flop| ; 1          ; 0    ; |UP1TEST|COUNTER_BIN_N:div|D_FF:\G_1:22:D_Flip_Flop ; work         ;
;       |D_FF:\G_1:23:D_Flip_Flop| ; 1          ; 0    ; |UP1TEST|COUNTER_BIN_N:div|D_FF:\G_1:23:D_Flip_Flop ; work         ;
;       |D_FF:\G_1:2:D_Flip_Flop|  ; 1          ; 0    ; |UP1TEST|COUNTER_BIN_N:div|D_FF:\G_1:2:D_Flip_Flop  ; work         ;
;       |D_FF:\G_1:3:D_Flip_Flop|  ; 1          ; 0    ; |UP1TEST|COUNTER_BIN_N:div|D_FF:\G_1:3:D_Flip_Flop  ; work         ;
;       |D_FF:\G_1:4:D_Flip_Flop|  ; 1          ; 0    ; |UP1TEST|COUNTER_BIN_N:div|D_FF:\G_1:4:D_Flip_Flop  ; work         ;
;       |D_FF:\G_1:5:D_Flip_Flop|  ; 1          ; 0    ; |UP1TEST|COUNTER_BIN_N:div|D_FF:\G_1:5:D_Flip_Flop  ; work         ;
;       |D_FF:\G_1:6:D_Flip_Flop|  ; 1          ; 0    ; |UP1TEST|COUNTER_BIN_N:div|D_FF:\G_1:6:D_Flip_Flop  ; work         ;
;       |D_FF:\G_1:7:D_Flip_Flop|  ; 1          ; 0    ; |UP1TEST|COUNTER_BIN_N:div|D_FF:\G_1:7:D_Flip_Flop  ; work         ;
;       |D_FF:\G_1:8:D_Flip_Flop|  ; 1          ; 0    ; |UP1TEST|COUNTER_BIN_N:div|D_FF:\G_1:8:D_Flip_Flop  ; work         ;
;       |D_FF:\G_1:9:D_Flip_Flop|  ; 1          ; 0    ; |UP1TEST|COUNTER_BIN_N:div|D_FF:\G_1:9:D_Flip_Flop  ; work         ;
;    |lpm_counter:count_rtl_0|     ; 8          ; 0    ; |UP1TEST|lpm_counter:count_rtl_0                    ; work         ;
;    |seg7:disp_l|                 ; 7          ; 0    ; |UP1TEST|seg7:disp_l                                ; work         ;
;    |seg7:disp_r|                 ; 7          ; 0    ; |UP1TEST|seg7:disp_r                                ; work         ;
+----------------------------------+------------+------+-----------------------------------------------------+--------------+


Прошивка CPLD MAX7128S не вызывает никаких проблем, замена штатного Altera ByteBlasterMV на Altera USB Blaster проходит совершенно гладко, как с точки зрения Quartus II, так и с точки зрения платы Altera UP1. Разъем JTAG имеет точно такую же цоколевку, а пин VCC указывает напряжение работы JTAG порта. Собственно, поэтому ByteBlasterMV носил приставку Multi Volt. Возможность работы с разными напряжениями USB Blaster унаследовал от ByteBlasterMV.

О программторах Altera Byte Blaster и их клонах писали уже много и подробно.
2bc0441a523c4a2caf28c36c0feec758.jpg

Первые два — почти стандартные Altera USB Blaster, а Terasic (или его клон, кто ж разберет) вообще похоже лицензионный. Как известно «классический» USB Blaster собран по схеме «FT245BM + CPLD + буфер» как описано в мануале.

Последний же — это настоящее китайское чудо (точнее — японо-китайское). Это эмулятор FT245BM + CPLD на дешовеньком PIC18F14K50. Тем не менее Quartus II признает эту «подделку» за родной USB Blaster и функция прошивки через JTAG работает. Удивительно, что даже фирменная FTDI FT Prog находит как бы чип FT245BM и что-то с него считывает.

Вот здесь описание внутренностей этого китайского клона-эмулятора.

Внутренности
9f2d20b135614a6b845e55b36cbc0c1d.jpg


Итак, мы побывали в шкуре студента ~2007 года, при этом воспользовались VHDL и USB Blaster для прошивки CPLD. Плата довольно полезная для вхождения в курс CPLD, составляет достойную конкурецию платкам CPLD и FPGA от LC Tech, которыми забит aliexpress и ее вполне стоит купить, если увидите не дорого. Литературы и курсов по ней огромное количество. Плата работает со стандартными TTL 5 вольт и отлично сопрягается со старыми устройствами. Не забудьте только закупить запас MAX7128S — ресурс у них не вечный.

В следующей части мы попробуем заменить MAX7128S на Atmel ATF1508AS которая якобы совместимая и посмотрим, что получится.

© Geektimes