Как подключить MAX7219 к Arduino

Для проекта нам понадобятся:
- Ардуино UNO (или совместимая плата);
- модуль с драйвером MAX7219 и LED-матрицей 1088AS (8x8);
- макетная плата;
- соединительные провода;
- персональный компьютер или ноутбук.
1Описание драйвера MAX7219
Микросхема MAX7219 – это компактный драйвер дисплея, который позволяет управлять 7-сегментными индикаторами разрядностью до 8 цифр или 64 отдельными светодиодами. Сам драйвер управляется по последовательному интерфейсу (чаще всего SPI) с помощью микроконтроллера, например, Arduino. MAX7219 позволяет изменять состояние каждого сегмента или светодиода без обновления состояния всех остальных выводов. А также он позволяет создавать цепочку из аналогичных драйверов, чтобы управлять большим количеством подключённых светодиодов.
Микросхема MAX7219 выполнена в 24-выводном корпусе. Назначение и расположение выводов показаны ниже:

На рисунке справа представлена типичная схема подключения MAX7219. Как видно, драйвер управляется микроконтроллером по последовательному интерфейсу. К выходу сегментов и выходу разрядов подключён дисплей либо светодиодная матрица.
Название вывода | Назначение |
---|---|
DIN | Вход последовательных данных для управления драйвером. |
DOUT | Выход последовательных данных для соединения в цепочку со следующим драйвером. |
DIG 0...DIG 7 | Выходы управления разрядами 8-символьного сегментного дисплея. |
SEG A...SEG G, DP | Выходы управления отдельными сегментами и десятичной точкой. |
CLK | Вход тактовой частоты последовательного интерфейса, до 10 МГц. |
LOAD | Вход загрузки данных. Данные «защёлкиваются» по фронту отрицательного импульса. |
ISET | Регулировка максимального потребляемого тока на один сегмент (следовательно, яркости). |
V+ | Питание, 5±0.5 В. |
GND | Земля. |
Драйвер MAX7219 позволяет регулировать яркость подключённых светодиодов. Причём можно сделать это аппаратно, подключив к ножке ISET микросхемы резистор, или программно, записав нужное значение в соответствующий регистр. Последний вариант мне кажется намного удобнее, т.к. позволяет управлять яркостью в процессе работы устройства.
Из существенных плюсов данного драйвера то, что не нужно постоянно обновлять состояние подключённых светодиодов: он сохраняет последнее заданное состояние, пока оно не будет произвольно изменено. Это также хорошо тем, что светодиоды не мерцают из-за постоянного обновления.
2Принципы управления драйвером MAX7219
Драйвер MAX7219 управляется по последовательному интерфейсу SPI. Никаких неожиданностей здесь нет, всё стандартно, как мы уже не раз разбирали. Выбор ведомого (CS) низким уровнем, скорость обмена до 10 МГц.
Данные передаются посылками по 16 бит. В первых 8-ми битах содержится адрес регистра, в который необходимо передать данные (или команду). А вторые 8 бит, собственно, данные. Для каждого регистра будут свои данные, т.к. регистры отличаются по назначению.

Регистров всего 14, чуть позднее мы познакомимся с большинством из них подробнее. В таблице адреса регистров обозначены как, например, 0xX3. Что это значит? Сигнатура "0x" говорит о том, что дальнейшее число записано в шестнадцатеричном виде. Далее идёт "X", что означает, что первая половина байта не влияет и может быть любой. Я буду здесь всегда ставить ноль. И последнее число, собственно, адрес регистра. В рассматриваемом примере "0xX3" адрес регистра – "3", и будем записывать его как "0x03".

MSB означает most significant bit, т.е. наиболее значимый бит. Это старший бит в байте. Напротив, LSB означает least significant bit, т.е. наименее значимый бит. Это младший бит байта.
3Подключение и работа с драйвером MAX7219
У меня в наличии есть готовый модуль с драйвером MAX7219 и светодиодной матрицей 1088AS (8 на 8 точек). У модуля есть 2 ряда выводов: VCC, GND, DIN, CS, CLK – это вход модуля, а с противоположной стороны VCC, GND, DOUT, CS, CLK – это выходы модуля.

Понятно, что VCC – это вход питания (5 В), а GND – земля. DIN – вход последовательных данных от микроконтроллера или от предыдущего в цепочке модуля; DOUT – это выход последовательных данных на следующий модуль. CS (он же LOAD) – загрузка данных в драйвер. CLK – вход тактовой частоты.

Точнее, под рукой нашлись восемь таких модулей Поэтому предлагаю сразу усложнить задачу: соединить в цепочку несколько драйверов MAX7219. Можно для примера сделать часы в формате ЧЧ:ММ:СС, по одному модулю на цифру или символ ":". Соединение будет предельно простым, каскадным. Это значит, что управляющий сигнал мы подадим только на вход первого модуля в цепочке, а далее соединим по цепочке выходы со входами всех модулей друг за другом. Для надёжности закрепим всю конструкцию на жёсткой основе из обрезка пластикового кабель-канала.
Кстати, 8 полностью включённых панелей при максимальной яркости светодиодов потребляют более 1 А. Необходимо их запитывать от блока питания.

Кроме того, раз делаем часы, то нужно откуда-то брать время. Значит, ещё нужен модуль с часами реального времени (RTC). Например, DS1302. Он будет хранить время. Мы с помощью Arduino будем считывать его и выводить на LED матрицы.

4Изучение драйвера MAX7219с помощью FT2232H
Но прежде чем подключать модули с MAX7219 к Arduino, попробуем поизучать их с помощью отладочной платы с микросхемой FT2232H. Она позволяет обмениваться по SPI с устройствами и не требует программирования. Все настройки делаются в программе SPI via FTDI, и для быстрого знакомства с новым устройством это очень удобно.
Для начала переведём все драйверы MAX7219 в цепочке в режим теста. Для этого необходимо в каждое из устройств в нашей цепочке передать команду 0F 01. В драйвере MAX7219 имеется сдвиговый регистр, и нам нужно «протолкнуть» команду по всем регистрам в цепочке. Чтобы команда прошла по всей цепочке, необходимо повторить её выдачу 8 раз (по числу драйверов в цепочке).

Почему команда именно такая? Смторим в документацию и видим, что адрес регистра Display Test – 0x0F. А чтобы перейти в режим теста, нужно в регистр Display Test записать 1 (см. таблицу 10 из технического описания драйвера MAX7219). В режиме теста должны загореться все светодиоды, подключённые к драйверу. Выполним команду один раз: зажглась первая LED панель, после второго выполнения команды уже горят две LED панели. После восьмикратного выполнения команды все восемь светодиодных панелей зажглись.

Чтобы выйти из режима теста, следует записать 8 раз команду 0F 00. Уже догадались, почему такая команда? Выход из тестового режима осуществляется записью нуля в регистр 0x0F.
Кстати, если в поле количества раз поставить 8, то можно записать команду за одно нажатие на кнопку «Записать».
После включения драйвер MAX7219 переходит в режим выключения, за который отвечает регистр Shutdown (0xXC). В этом режиме единственные команды, которые мы можем послать – это переход в режим теста и выход из него. Чтобы перевести устройство в рабочий режим, нужно в регистр 0x0C записать "1" (см. таблицу 3 технического описания):

Теперь устройство готово к приёму всех команд.
Драйвер MAX7219 может работать в режиме декодирования или без него. Причём можно включать и отключать режим декодирования для каждого из разрядов дисплея (или столбцов LED матрицы) индивидуально. Давайте отключим режим декодирования. Для этого нужно послать команду 09 00 восемь раз (см. таблицу 4 технического описания). В режиме без декодирования биты D0..D7 отвечают за сегменты индикатора. Или, как в нашем случае, за один ряд светодиодов LED панели.

Режим с декодированием используется, когда драйвер MAX7219 подключён к 7-сегментному индикатору. Этот режим позволяет передавать драйверу число, и он сам зажигает нужные сегменты дисплея, чтобы отобразить это число на дисплее. Но так как в нашем случае к драйверу подключена LED панель, то режим с декодированием нам не подходит.
Давайте для примера зажгём в первой строчке всех панелей убывающее число светодиодов: на первой панели в первой строчке будет гореть 8 светодиодов, на второй – 7, и так далее. Для этого мы должны так же послать 8 команд, но теперь они будут разные. За запись в первую строчку отвечает регистр Digit 0 (0x01). Кроме того, мы имеем сдвиговый регистр, поэтому команда, которую мы пошлём первой, окажется в последнем регистре. То есть сначала мы управляем последним регистром, затем предпоследним, и так далее. Таким образом, вот последовательность команд:
01 01 (00000001) 01 03 (00000011) 01 07 (00000111) 01 0F (00001111) 01 1F (00011111) 01 3F (00111111) 01 7F (01111111) 01 FF (11111111)
Первое число – номер регистра (Digit 0), второе число – то, что мы записываем в регистр. В скобках указано двоичное представление того, что записываем в регистр. Единицы будут горящими светодиодами на LED панели, нули – потухшими.

Теперь давайте изменим яркость свечения LED панели. Для этого необходимо в регистр Intensity (0x0A) записать число от 0 до 15 (0x0F), где "0" соответствует минимальной яркости, а 0x0F – максимальной (см. таблицу 7 технического описания). Я поставлю что-то около нуля, например, 1. Для этого нужно 8 раз отправить команду 0A 01.

Теперь мы знаем, как управлять драйвером MAX7219 и уже почти готовы подключить его к Arduino.
5Подключение драйвера MAX7219к Arduino
Прежде чем делать часы, давайте сделаем бегущую строку. Подробное объяснение принципа бегущей строки описано здесь. Скетч для Arduino будет такой.
Бегущая строка на драйверах MAX7219 (разворачивается)
#include <SPI.h> // #include <EEPROM.h> #define SYM_WIDTH 6 // ширина символа #define NUM_LINES 8 // число строк LED панели #define NUM_DISP 8 // число LED матриц #define DEL 100 // регулирует скорость бегущей строки #define MAX_LEN 100 // максимальная длина сообщения enum decodeMode : byte { no_decode = 0, decode_lo = 1, decode_hi = 0x0f, decode_full = 0xff }; enum shutdownMode : byte { shutdown = 0, normal_operation = 1 }; enum intensity : byte { min = 0, x1 = 1, x2 = 2, x3 = 3, x4 = 4, x5 = 5, x6 = 6, x7 = 7, x8 = 8, x9 = 9, x10 = 0xA, x11 = 0xB, x12 = 0xC, x13 = 0xD, x14 = 0xE, max = 0xf }; enum scanLimit : byte { dig0 = 0, // показывать 1-ю линию dig01 = 1, dig02 = 2, dig03 = 3, dig04 = 4, dig05 = 5, dig06 = 6, dig07 = 7 // показывать все линии }; // Массив символов (шрифт) в кодировке windows-1251. byte characters[159][SYM_WIDTH] = { { 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00000000 }, // space, элемент 0, ASCII 32 => адрес 32-32 { 0b00000000, 0b00000000, 0b01001111, 0b00000000, 0b00000000, 0b00000000 }, // ! { 0b00000000, 0b00000111, 0b00000000, 0b00000111, 0b00000000, 0b00000000 }, // '' { 0b00010100, 0b01111111, 0b00010100, 0b01111111, 0b00010100, 0b00000000 }, // # { 0b00100100, 0b00101010, 0b01111111, 0b00101010, 0b00010010, 0b00000000 }, // $ { 0b00100011, 0b00010011, 0b00001000, 0b01100100, 0b01100010, 0b00000000 }, // % { 0b00110110, 0b01001001, 0b01010101, 0b00100010, 0b01010000, 0b00000000 }, // & { 0b00000000, 0b00000101, 0b00000011, 0b00000000, 0b00000000, 0b00000000 }, // ; { 0b00000000, 0b00011100, 0b00100010, 0b01000001, 0b00000000, 0b00000000 }, // ( { 0b00000000, 0b01000001, 0b00100010, 0b00011100, 0b00000000, 0b00000000 }, // ) { 0b00010100, 0b00001000, 0b00111110, 0b00001000, 0b00010100, 0b00000000 }, // * // 10 { 0b00001000, 0b00001000, 0b00111110, 0b00001000, 0b00001000, 0b00000000 }, // + { 0b00000000, 0b01010000, 0b00110000, 0b00000000, 0b00000000, 0b00000000 }, // , { 0b00001000, 0b00001000, 0b00001000, 0b00001000, 0b00001000, 0b00000000 }, // - { 0b00000000, 0b01100000, 0b01100000, 0b00000000, 0b00000000, 0b00000000 }, // . { 0b00100000, 0b00010000, 0b00001000, 0b00000100, 0b00000010, 0b00000000 }, // / { 0b00111110, 0b01010001, 0b01001001, 0b01000101, 0b00111110, 0b00000000 }, // 0 { 0b00000000, 0b01000010, 0b01111111, 0b01000000, 0b00000000, 0b00000000 }, // 1 { 0b01000010, 0b01100001, 0b01010001, 0b01001001, 0b01000110, 0b00000000 }, // 2 { 0b00100001, 0b01000001, 0b01000101, 0b01001011, 0b00110001, 0b00000000 }, // 3 { 0b00011000, 0b00010100, 0b00010010, 0b01111111, 0b00010000, 0b00000000 }, // 4 // 20 { 0b00100111, 0b01000101, 0b01000101, 0b01000101, 0b00111001, 0b00000000 }, // 5 { 0b00111100, 0b01001010, 0b01001001, 0b01001001, 0b00110000, 0b00000000 }, // 6 { 0b00000011, 0b01110001, 0b00001001, 0b00000101, 0b00000011, 0b00000000 }, // 7 { 0b00110110, 0b01001001, 0b01001001, 0b01001001, 0b00110110, 0b00000000 }, // 8 { 0b00000110, 0b01001001, 0b01001001, 0b00101001, 0b00011110, 0b00000000 }, // 9 { 0b00000000, 0b01101100, 0b01101100, 0b00000000, 0b00000000, 0b00000000 }, // : { 0b00000000, 0b01010110, 0b00110110, 0b00000000, 0b00000000, 0b00000000 }, // ; { 0b00001000, 0b00010100, 0b00100010, 0b01000001, 0b00000000, 0b00000000 }, // < { 0b00010100, 0b00010100, 0b00010100, 0b00010100, 0b00010100, 0b00000000 }, // = { 0b00000000, 0b01000001, 0b00100010, 0b00010100, 0b00001000, 0b00000000 }, // > // 30 { 0b00000010, 0b00000001, 0b01010001, 0b00001001, 0b00000110, 0b00000000 }, // ? { 0b00110010, 0b01001001, 0b01111001, 0b01000001, 0b00111110, 0b00000000 }, // @ { 0b01111110, 0b00010001, 0b00010001, 0b00010001, 0b01111110, 0b00000000 }, // A { 0b01111111, 0b01001001, 0b01001001, 0b01001001, 0b00111110, 0b00000000 }, // B { 0b00111110, 0b01000001, 0b01000001, 0b01000001, 0b00100010, 0b00000000 }, // C { 0b01111111, 0b01000001, 0b01000001, 0b01000001, 0b00111110, 0b00000000 }, // D { 0b01111111, 0b01001001, 0b01001001, 0b01001001, 0b01001001, 0b00000000 }, // E { 0b01111111, 0b00001001, 0b00001001, 0b00001001, 0b00000001, 0b00000000 }, // F { 0b00111110, 0b01000001, 0b01001001, 0b01001001, 0b00111010, 0b00000000 }, // G { 0b01111111, 0b00001000, 0b00001000, 0b00001000, 0b01111111, 0b00000000 }, // H // 40 { 0b01000001, 0b01000001, 0b01111111, 0b01000001, 0b01000001, 0b00000000 }, // I { 0b00110000, 0b01000001, 0b01000001, 0b00111111, 0b00000001, 0b00000000 }, // J { 0b01111111, 0b00001000, 0b00010100, 0b00100010, 0b01000001, 0b00000000 }, // K { 0b01111111, 0b01000000, 0b01000000, 0b01000000, 0b01000000, 0b00000000 }, // L { 0b01111111, 0b00000010, 0b00001100, 0b00000010, 0b01111111, 0b00000000 }, // M { 0b01111111, 0b00000100, 0b00001000, 0b00010000, 0b01111111, 0b00000000 }, // N { 0b00111110, 0b01000001, 0b01000001, 0b01000001, 0b00111110, 0b00000000 }, // O { 0b01111111, 0b00001001, 0b00001001, 0b00001001, 0b00000110, 0b00000000 }, // P { 0b00111110, 0b01000001, 0b01010001, 0b00100001, 0b01011110, 0b00000000 }, // Q { 0b01111111, 0b00001001, 0b00001001, 0b00011001, 0b01100110, 0b00000000 }, // R // 50 { 0b01000110, 0b01001001, 0b01001001, 0b01001001, 0b00110001, 0b00000000 }, // S { 0b00000001, 0b00000001, 0b01111111, 0b00000001, 0b00000001, 0b00000000 }, // T { 0b00111111, 0b01000000, 0b01000000, 0b01000000, 0b00111111, 0b00000000 }, // U { 0b00001111, 0b00110000, 0b01000000, 0b00110000, 0b00001111, 0b00000000 }, // V { 0b00111111, 0b01000000, 0b00111000, 0b01000000, 0b00111111, 0b00000000 }, // W { 0b01100011, 0b00010100, 0b00001000, 0b00010100, 0b01100011, 0b00000000 }, // X { 0b00000011, 0b00000100, 0b01111000, 0b00000100, 0b00000011, 0b00000000 }, // Y { 0b01100001, 0b01010001, 0b01001001, 0b01000101, 0b01000011, 0b00000000 }, // Z { 0b01111111, 0b01000001, 0b01000001, 0b00000000, 0b00000000, 0b00000000 }, // [ { 0b00000010, 0b00000100, 0b00001000, 0b00010000, 0b00100000, 0b00000000 }, // '\' // 60 { 0b00000000, 0b00000000, 0b01000001, 0b01000001, 0b01111111, 0b00000000 }, // ] { 0b00000100, 0b00000010, 0b00000001, 0b00000010, 0b00000100, 0b00000000 }, // ^ { 0b01000000, 0b01000000, 0b01000000, 0b01000000, 0b01000000, 0b00000000 }, // _ { 0b00000000, 0b00000001, 0b00000010, 0b00000100, 0b00000000, 0b00000000 }, // ` { 0b00100000, 0b01010100, 0b01010100, 0b01010100, 0b01111000, 0b00000000 }, // a { 0b01111111, 0b01001000, 0b01000100, 0b01000100, 0b00111000, 0b00000000 }, // b { 0b00111000, 0b01000100, 0b01000100, 0b01000100, 0b00100000, 0b00000000 }, // c { 0b00111000, 0b01000100, 0b01000100, 0b01001000, 0b01111111, 0b00000000 }, // d { 0b00111000, 0b01010100, 0b01010100, 0b01010100, 0b00011000, 0b00000000 }, // e { 0b00001000, 0b01111110, 0b00001001, 0b00000001, 0b00000010, 0b00000000 }, // f // 70 { 0b00001100, 0b01010010, 0b01010010, 0b01010010, 0b00111110, 0b00000000 }, // g { 0b01111111, 0b00001000, 0b00000100, 0b00000100, 0b01111000, 0b00000000 }, // h { 0b00000000, 0b01000100, 0b01111101, 0b01000000, 0b00000000, 0b00000000 }, // i { 0b00100000, 0b01000000, 0b01000100, 0b00111101, 0b00000000, 0b00000000 }, // j { 0b01111111, 0b00010000, 0b00101000, 0b01000100, 0b00000000, 0b00000000 }, // k { 0b00000000, 0b01000001, 0b01111111, 0b01000000, 0b00000000, 0b00000000 }, // l { 0b01111000, 0b00000100, 0b00001000, 0b00000100, 0b01111000, 0b00000000 }, // m { 0b01111100, 0b00001000, 0b00000100, 0b00000100, 0b01111000, 0b00000000 }, // n { 0b00111000, 0b01000100, 0b01000100, 0b01000100, 0b00111000, 0b00000000 }, // o { 0b01111100, 0b00010100, 0b00010100, 0b00010100, 0b00001000, 0b00000000 }, // p // 80 { 0b00001000, 0b00010100, 0b00010100, 0b01111100, 0b00000000, 0b00000000 }, // q { 0b01111100, 0b00001000, 0b00000100, 0b00000100, 0b00001000, 0b00000000 }, // r { 0b01001000, 0b01010100, 0b01010100, 0b01010100, 0b00100000, 0b00000000 }, // s { 0b00000100, 0b00111111, 0b01000100, 0b01000000, 0b00100000, 0b00000000 }, // t { 0b00111100, 0b01000000, 0b01000000, 0b00100000, 0b01111100, 0b00000000 }, // u { 0b00011100, 0b00100000, 0b01000000, 0b00100000, 0b00011100, 0b00000000 }, // v { 0b00111100, 0b01000000, 0b00110000, 0b01000000, 0b00111100, 0b00000000 }, // w { 0b01000100, 0b00101000, 0b00010000, 0b00101000, 0b01000100, 0b00000000 }, // x { 0b00001100, 0b01010000, 0b01010000, 0b01010000, 0b00111100, 0b00000000 }, // y { 0b01000100, 0b01100100, 0b01010100, 0b01001100, 0b01000100, 0b00000000 }, // z // 90 { 0b00000000, 0b00001000, 0b00110110, 0b01000001, 0b00000000, 0b00000000 }, // { { 0b00000000, 0b00000000, 0b01111111, 0b00000000, 0b00000000, 0b00000000 }, // | { 0b00000000, 0b01000001, 0b00110110, 0b00001000, 0b00000000, 0b00000000 }, // } { 0b00001000, 0b00000100, 0b00000100, 0b00001000, 0b00000100, 0b00000000 }, // ~ => элемент 94, ASCII 126 => адрес 126-32, далее пропущены 16*4 символов { 0b01111100, 0b00010010, 0b00010001, 0b00010001, 0b01111111, 0b00000000 }, // А => элемент 95, win-1251 192 => адрес 192-32-16*4-1 { 0b01111111, 0b01001001, 0b01001001, 0b01001001, 0b00110001, 0b00000000 }, // Б { 0b01111111, 0b01001001, 0b01001001, 0b01001001, 0b00110110, 0b00000000 }, // В { 0b01111111, 0b00000001, 0b00000001, 0b00000001, 0b00000001, 0b00000000 }, // Г { 0b11000000, 0b01111100, 0b01000010, 0b01000001, 0b01111111, 0b11000000 }, // Д { 0b01111111, 0b01001001, 0b01001001, 0b01001001, 0b01000001, 0b00000000 }, // Е { 0b01110111, 0b00001000, 0b01111111, 0b00001000, 0b01110111, 0b00000000 }, // Ж { 0b00100010, 0b01000001, 0b01000001, 0b01001001, 0b00110110, 0b00000000 }, // З { 0b01111111, 0b00010000, 0b00001000, 0b00000100, 0b01111111, 0b00000000 }, // И { 0b01111111, 0b00010000, 0b00001001, 0b00000100, 0b01111111, 0b00000000 }, // Й { 0b01111111, 0b00001000, 0b00010100, 0b00100010, 0b01000001, 0b00000000 }, // К { 0b01111100, 0b00000010, 0b00000001, 0b00000001, 0b01111111, 0b00000000 }, // Л { 0b01111111, 0b00000010, 0b00000100, 0b00000010, 0b01111111, 0b00000000 }, // М { 0b01111111, 0b00001000, 0b00001000, 0b00001000, 0b01111111, 0b00000000 }, // Н { 0b00111110, 0b01000001, 0b01000001, 0b01000001, 0b00111110, 0b00000000 }, // О { 0b01111111, 0b00000001, 0b00000001, 0b00000001, 0b01111111, 0b00000000 }, // П { 0b01111111, 0b00010001, 0b00010001, 0b00010001, 0b00001110, 0b00000000 }, // Р // 111 { 0b00111110, 0b01000001, 0b01000001, 0b01000001, 0b00100010, 0b00000000 }, // С // 112 { 0b00000001, 0b00000001, 0b01111111, 0b00000001, 0b00000001, 0b00000000 }, // Т { 0b01000111, 0b01001000, 0b01010000, 0b01010000, 0b00111111, 0b00000000 }, // У { 0b00011100, 0b00100010, 0b01111111, 0b00100010, 0b00011100, 0b00000000 }, // Ф { 0b01100011, 0b00010100, 0b00001000, 0b00010100, 0b01100011, 0b00000000 }, // Х { 0b00111111, 0b01000000, 0b01000000, 0b01000000, 0b01111111, 0b11000000 }, // Ц { 0b00001111, 0b00010000, 0b00010000, 0b00010000, 0b01111111, 0b00000000 }, // Ч { 0b01111111, 0b01000000, 0b01111100, 0b01000000, 0b01111111, 0b00000000 }, // Ш { 0b01111111, 0b01000000, 0b01111100, 0b01000000, 0b01111111, 0b11000000 }, // Щ { 0b00000001, 0b01111111, 0b01001000, 0b01001000, 0b00110000, 0b00000000 }, // Ъ { 0b01111111, 0b01001000, 0b01001000, 0b00110000, 0b01111111, 0b00000000 }, // Ы { 0b01111111, 0b01001000, 0b01001000, 0b01001000, 0b00110000, 0b00000000 }, // Ь { 0b00100010, 0b01001001, 0b01001001, 0b01001001, 0b00111110, 0b00000000 }, // Э { 0b01111111, 0b00001000, 0b00111110, 0b01000001, 0b00111110, 0b00000000 }, // Ю { 0b01110110, 0b00001001, 0b00001001, 0b00001001, 0b01111111, 0b00000000 }, // Я { 0b00000000, 0b00100100, 0b01010100, 0b01010100, 0b01111000, 0b01000000 }, // а { 0b00000000, 0b00111000, 0b01010100, 0b01010100, 0b00100100, 0b00000000 }, // б { 0b00000000, 0b01111100, 0b01010100, 0b01010100, 0b00101000, 0b00000000 }, // в { 0b00000000, 0b01111100, 0b00000100, 0b00000100, 0b00000000, 0b00000000 }, // г { 0b00000000, 0b10011000, 0b10100100, 0b10100100, 0b01111100, 0b00000000 }, // д { 0b00000000, 0b00111000, 0b01010100, 0b01010100, 0b01011000, 0b00000000 }, // е { 0b01101100, 0b00010000, 0b01111100, 0b00010000, 0b01101100, 0b00000000 }, // ж { 0b00000000, 0b00101000, 0b01000100, 0b01010100, 0b00101000, 0b00000000 }, // з { 0b00000000, 0b00111100, 0b01000000, 0b01000000, 0b01111100, 0b00000000 }, // и { 0b00000000, 0b00111100, 0b01000000, 0b01000010, 0b01111100, 0b00000000 }, // й { 0b00000000, 0b01111100, 0b00010000, 0b00101000, 0b01000100, 0b00000000 }, // к { 0b00000000, 0b01110000, 0b00001000, 0b00000100, 0b01111100, 0b00000000 }, // л { 0b01111100, 0b00001000, 0b00010000, 0b00001000, 0b01111100, 0b00000000 }, // м { 0b00000000, 0b01111100, 0b00010000, 0b00010000, 0b01111100, 0b00000000 }, // н { 0b00000000, 0b00111000, 0b01000100, 0b01000100, 0b00111000, 0b00000000 }, // о { 0b00000000, 0b01111100, 0b00000100, 0b00000100, 0b01111100, 0b00000000 }, // п { 0b00000000, 0b11111100, 0b01000100, 0b01000100, 0b00111000, 0b00000000 }, // р { 0b00000000, 0b00111000, 0b01000100, 0b01000100, 0b00101000, 0b00000000 }, // с { 0b00000000, 0b00000100, 0b01111100, 0b00000100, 0b00000000, 0b00000000 }, // т { 0b00000000, 0b10011100, 0b10100000, 0b10100000, 0b01111100, 0b00000000 }, // у { 0b00111000, 0b01000100, 0b11111110, 0b01000100, 0b00111000, 0b00000000 }, // ф { 0b00000000, 0b01101100, 0b00010000, 0b00010000, 0b01101100, 0b00000000 }, // х { 0b00000000, 0b00111100, 0b01000000, 0b01000000, 0b01111100, 0b11000000 }, // ц { 0b00000000, 0b00001100, 0b00010000, 0b00010000, 0b01111100, 0b00000000 }, // ч { 0b01111100, 0b01000000, 0b01111000, 0b01000000, 0b01111100, 0b00000000 }, // ш { 0b01111100, 0b01000000, 0b01111000, 0b01000000, 0b01111100, 0b11000000 }, // щ { 0b00000100, 0b01111100, 0b01010000, 0b01010000, 0b00100000, 0b00000000 }, // ъ { 0b01111100, 0b01010000, 0b01010000, 0b00100000, 0b01111100, 0b00000000 }, // ы { 0b00000000, 0b01111100, 0b01010000, 0b01010000, 0b00100000, 0b00000000 }, // ь { 0b00000000, 0b01000100, 0b01010100, 0b01010100, 0b00111000, 0b00000000 }, // э { 0b01111100, 0b00010000, 0b00111000, 0b01000100, 0b00111000, 0b00000000 }, // ю { 0b00000000, 0b01001000, 0b00110100, 0b00010100, 0b01111100, 0b00000000 } // я }; int inputIndeces[MAX_LEN]; // буфер для приёма индексов сообщения из последовательного порта int curInputIndex = 0; // индекс текущего принятого символа int messageIndeces[MAX_LEN] = { 110, 143, 135, 129, 132, 145, 12, 0, 51, 79, 76, 84, 65, 85, 14, 82, 85, 1, 0, 26, 9 }; // индексы отображаемого сообщения в массиве шрифта int messageLen = 21; // длина текущего сообщения byte buffer[NUM_LINES][NUM_DISP] = { { 0, 0, 0, 0, 0, 0, 0, 1 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 }, { 0, 0, 0, 0, 0, 0, 0, 0 } }; void setup() { InitLedPanel(); InitBuffer(); // storeFont(); // выполнить однократно, затем закомментировать for (int i = 0; i < 2; i++) { test(true); delay(50); test(false); delay(200); } delay(1000); for (int i = 0; i < 2; i++) { testLines(); } } // Инициализирует SPI и последовательный порт, задаёт режим работы и яркость панели. void InitLedPanel() { Serial.begin(115200); SPI.begin(); pinMode(SS, OUTPUT); digitalWrite(SS, HIGH); setMode(normal_operation); setBrightness(min); setDecodeMode(no_decode); setScanLimit(dig07); } // Инициализирует пустой буфер. void InitBuffer(void) { for (int l = 0; l < NUM_LINES; l++) { for (int d = 0; d < NUM_DISP; d++) { buffer[l][d] = 0x00; } } } void test(bool on) { for (int i = 0; i < NUM_DISP; i++) { digitalWrite(SS, LOW); SPI.transfer(0x0F); // регистр самотестирования дисплея SPI.transfer((byte)on); digitalWrite(SS, HIGH); } } void testLines() { for (int j = 0; j <= 8; j++) { for (byte i = 1; i <= 8; i++) { digitalWrite(SS, LOW); SPI.transfer(i); SPI.transfer(0xFF >> j); SPI.transfer(i); SPI.transfer(0xFE >> j); SPI.transfer(i); SPI.transfer(0xFC >> j); SPI.transfer(i); SPI.transfer(0xF8 >> j); SPI.transfer(i); SPI.transfer(0xF0 >> j); SPI.transfer(i); SPI.transfer(0xE0 >> j); SPI.transfer(i); SPI.transfer(0xC0 >> j); SPI.transfer(i); SPI.transfer(0x80 >> j); digitalWrite(SS, HIGH); } delay(DEL); } } // Нормальный режим / выключение. void setMode(shutdownMode value) { for (int i = 0; i < NUM_DISP; i++) { digitalWrite(SS, LOW); SPI.transfer(0x0C); SPI.transfer(value); digitalWrite(SS, HIGH); } Serial.println("Mode = " + (String)value); } // Задаёт яркость. void setBrightness(intensity value) { for (int i = 0; i < NUM_DISP; i++) { digitalWrite(SS, LOW); SPI.transfer(0x0A); SPI.transfer(value); digitalWrite(SS, HIGH); } Serial.println("Brightness = " + (String)value); } // Режим декодирования. void setDecodeMode(decodeMode value) { for (int i = 0; i < NUM_DISP; i++) { digitalWrite(SS, LOW); SPI.transfer(0x09); SPI.transfer(value); digitalWrite(SS, HIGH); } Serial.println("Decode mode = " + (String)value); } // Сколько цифр отображать. void setScanLimit(scanLimit value) { for (int i = 0; i < NUM_DISP; i++) { digitalWrite(SS, LOW); SPI.transfer(0x0B); SPI.transfer(value); digitalWrite(SS, HIGH); } Serial.println("Scan limit = " + (String)value); } void loop() { for (int n = 0; n < messageLen; n++) { // для каждого символа сообщения for (int col = 0; col < SYM_WIDTH; col++) { // для каждого столбца символа ShiftBuffer(); // сдвигаем буфер, освобождается крайний правый столбец byte symCol = characters[messageIndeces[n]][col]; // текущий столбец символа //byte symCol = getEeSymbol(messageIndeces[n], col); // текущий столбец символа из ППЗУ PushColumn(symCol); // размещаем столбец символа в крайнем правом столбце буфера DisplayBuffer(); // отображаем текущий буфер delay(DEL); } } } /// Сохраняет шрифт в ППЗУ. // void storeFont() { // int eeAddress = 0; // адрес в ПЗУ // for (int n = 0; n < 159; n++) { // for (int col = 0; col < SYM_WIDTH; col++) { // EEPROM.put(eeAddress, characters[n][col]); // eeAddress += 1; // } // } // Serial.println("Font table stored to EEPROM"); // } // // Возвращает символ из ППЗУ по адресу строки и столбца. // byte getEeSymbol(int row, int col) { // byte sym; // int addr = row * SYM_WIDTH + col; // EEPROM.get(addr, sym); // return sym; // } // Сдвигает содержимое буфера на 1 столбец влево. void ShiftBuffer() { for (int l = 0; l < NUM_LINES; l++) { byte msb = 0; // старший бит числа for (int d = 0; d < NUM_DISP; d++) { if (d < NUM_DISP - 1) { msb = buffer[l][d + 1] >> 7; // запомнили старший бит следующего числа } else { msb = 0; } buffer[l][d] = (buffer[l][d] << 1) | msb; // сдвигаем на 1 бит и младший заполняем значением предыдущего старшего } } // Serial.println("Shifted:"); // для отладки // for (int l = 0; l < NUM_LINES; l++) { // for (int d = 0; d < NUM_DISP; d++) { // char buf[3]; // sprintf(buf, "%02x", buffer[l][d]); // Serial.print(buf); // Serial.print(" "); // } // Serial.println(); // } } // Вдвигаем столбец символа в крайний правый столбец буфера. void PushColumn(byte col) { for (int i = 0; i < NUM_LINES; i++) { // каждый бит столбца размещается в своей строке byte curBit = (col >> i) & 1; // текущий бит столбца buffer[i][NUM_DISP - 1] = buffer[i][NUM_DISP - 1] | curBit; // размещаем в младшем разряде последнего байта буфера } } // Выводит текущий буфер на LED панель. void DisplayBuffer() { for (int i = 0; i < NUM_LINES; i++) { WriteLine((byte)i, buffer[i]); } } // Выводит на LED панель заданную строку. void WriteLine(byte line, byte *ar) { digitalWrite(SS, LOW); for (int i = 0; i < NUM_DISP; i++) { SPI.transfer(line + 1); // адрес линии 1..8. SPI.transfer(ar[NUM_DISP - i - 1]); // задвигаем байты "с конца" } digitalWrite(SS, HIGH); } // Определяет индекс символа в таблице шрифта. int IndexInFontTable(String sym) { String w1251Sym = Utf8_Win1251(sym); // для латиницы ничего не меняется, для кириллицы получаем значение больше 127 и меньшее 256. byte asciiVal = (byte)w1251Sym[0]; byte fontIndex = asciiVal - 32; // индекс символа в таблице шрифта if (asciiVal > 126) { fontIndex -= 65; // 65 = 16*4+1 - столько символов пропущено в таблице шрифта, и сдвиг индекса на 1 } Serial.println("orig=[" + sym + "]; win1251=" + w1251Sym + "; table_index=" + (String)fontIndex); return fontIndex; } // Перекодирует строку из UTF-8 в Windows-1251. String Utf8_Win1251(String source) { String target; char m[2] = { '0', '\0' }; int k = source.length(); int i = 0; while (i < k) { unsigned char n = source[i]; i++; m[0] = n; target = target + String(m); } return target; } void serialEvent() { while (Serial.available()) { char inSym = (char)Serial.read(); // принимаем символ из порта if ((inSym != '\n') && (inSym != '\r')) { // если пришёл НЕ символ переноса строки int indexInFontTable = IndexInFontTable((String)inSym); // ищем его индекс в таблице шрифтов inputIndeces[curInputIndex] = indexInFontTable; // размещаем индекс во временном буфере curInputIndex += 1; // запоминаем текущее положение в буфере } else { if (curInputIndex != 0) { // защита от двойного сброса InitBuffer(); Serial.println("New message indeces:"); for (int i = 0; i < curInputIndex; i++) { messageIndeces[i] = inputIndeces[i]; // обновляем индексы выводимого сообщения Serial.print(String(messageIndeces[i]) + " "); } Serial.println(); messageLen = curInputIndex; // запоминаем размер сообщения curInputIndex = 0; } } } }
Здесь закомментированы участки кода, которые позволят сохранить массив шрифта в ППЗУ Arduino, и таким образом сэкономить динамическую память. В следующем видео даны подробные разъяснения к коду, а также демонстрируется результат работы скетча.
Перед реализацией часов осталось освоить ещё несколько деталей проекта: вывод цифр на дисплей и часы реального времени DS1302. О работе с часами DS1302 подробно описано в этой статье.
Download attachments:
- Техническое описание драйвера MAX7219 (979 Downloads)
- Техническое описание LED матрицы 1088AS (791 Downloads)