Урок 28
SPI. Драйвер LED MAX7219
Продолжаем работать с шиной SPI.
Сегодня мне в руки попался драйвер 8-разрядных светодиодных индикаторов. Данный драйвер оснащем декодером символов, умеет управлять динамической индикацией и много чего ещё. Ну, и, самое главное, управляется данная микросхема по шине SPI, что не дало мне права обойти его стороной.
Сегодня мы его и попробуем подключить и поуправлять им. Работать мы будем с ней также только на передачу, работа на приём ждёт нас впереди в более поздних занятиях.
Ну и как всегда, обратимся к технической документации, чтобы, так сказать немного с ним ознакомиться для начала.
Вот типовая схема подключения микросхемы MAX7219
Мы видим, что у данного драйвера есть 8 параллельных выходов для сегментов и также восем параллельных выходов для разрядов. Также есть контакт для последовательного входа данных, выхода на схеме нет, хотя вообще он у драйвера есть, то есть разработчики сами прекрасно понимают, что мониторить здесь нечего. Также есть вход для выбора микросхемы Chip Select и вход для синхроимупульсов. На 18 ножку Iset мы подтягиваем резистор с шины питания, который рекомендуется впаивать с номиналом 9,53 килоома, тем самым мы устанавливаем пиковый ток для сегментов. Ну и, соответственно, мы подключаем на 19 ножку питание, а на 9 и 4 — общий провод.
Так как данная микросхема не совсем простая, у неё также существует немалая структурная схема
Здесь мы видим внизу 16-битный регистр, 8 младших битов которого являются данными, а следующие 4 бита — адресом для регистров. Также мы видим несколько регистров различного назначения, драйвер сегментов и драйвер разрядов. Видим мы ещё широтно-импульсный модулятор, управляющий интенсивностью свечения и управляемый особым регистром. Существует также интересный регистр — SCAN-LIMIT, который управляет количеством задействованных разрядов.
Интересным является также декодер сегментов, который может сам формировать свечение определённых сегментов в разряде в зависимости от поступившего значения. Раньше, я помню, это называлось дешифратором. Данный декодер можно также отключать, причём его можно для каких-то разрядов включить, а для каких-то — выключить. Возникает вопрос — для чего вообще выключать декодирование? Ответ здесь многогранный. Во-первых, для того, чтобы вывести какие-то особенные замысловатые символы, которых нет в списке кодов. А самое главное то, что при использовании декодера символов мы можем управлять индикатором только с общим катодом, индикатором с общим анодом мы уже управлять не можем, а если декодирование отключить, то уже можно что-то придумать для управления и общими анодами. Ну мы этим заниматься не будем, будем пользоваться декодером.
Дальше в технической документации рассматриваются все регистры подробно, сейчас мы не будем это рассматривать, а будем только по мере необходимости при написании кода.
Ну и тогда не будем медлить, а перейдём непосредственно к проекту. Проект создан был с именем LED7219, код нём находится весь с проекта предыдущего занятия. Единственная разница — мы не будем подключать библиотеку файлов led.c и led.h, так как у нас есть драйвер и он данными вещами, описанных в данных библиотеках, займётся на аппаратном уровне.
Уберём поэтому инициализацию таймера и прерываний из функции main()
timer_ini();
sei();
Также удалим всё полностью из бесконечного цикла.
Так как микросхема не простая, она нуждается уже в инициализации. Отдельную функцию инициализации мы писать на будем, так как кода будет не так много.
А создадим мы функцию, которая будет передавать команды в данный драйвер. Добавим мы её до функции main(). Параметром будет передаваемый байт, а в теле функции мы запишем данный байт в регистр SPDR и затем стандартным способом данный байт в шину SPI передадим
void SPI_SendByte (char byte)
{
SPDR = byte;
while (!(SPSR & (1 << SPIF)));
}
Затем будет ещё одна функция, которую мы напишем ниже. Данная функция будет передавать байт не просто в шину, а в определённый регистр микросхемы. Судя по рисунку со структурной схемой и глядя в нём на регистр данных мы видим, куда именно и что там расположить. Входными параметрами будут адрес регистра и байт данных
void Send_7219(char rg, char dt)
{
PORTB &= ~(1<<PORTB2);
SPI_SendByte(rg);
SPI_SendByte(dt);
PORTB |= (1<<PORTB2);
}
Вот такая вот нехитрая функция. Мы сначала опустим ножку CS, чтобы выбрать нашу микросхему, затем передадим байт с адресом регистра, а затем байт данных, ну и в конце поднимем CS.
Посмотрим схему в проетусе. За основу также взята была схема с прошлого знанятия и, соответственно, переработана под новую микросхему. Дело в том. что у меня отдельной микросхемы нет, а есть модуль, в котором уже установлен восьмиразрядный индикатор, поэтому такой же индикатор мы подключим и в протеусе (нажмите на картинку для увеличения изображения)
Мы видим, что на вход DIN идёт выход MISO контроллера, а также мы подвели к нему синхронизацию и выбор. Ну а индикатор соответствующими разрядами и сегментами мы подключили к соответствующим параллельным выходам драйвера. Вообщем, схема получилась проще паренной репы.
А вот так вот у нас всё выглядит в модуле
А вот вид сзади
Мы видим здесь нашу микросхему MAX3219 и все соединения. Выполнено очень даже чинно. Также мы подвели к микросхеме 4 провода, воспользовавшить обычными контактными разъёмами. Здесь у нас шина питания и общий провод, а также 3 провода SPI. Вот и всё
Ну а вот так выглядит всё это вместе с отладочной платой
Также здесь мы видим, что вместо восьмиразрядного индикатора в модуле установлены два четырёхразрядных индикатора, что, собственно, не очень тяготит, так как раздряды все находятся друг от друга на одинаковом расстоянии, так сказать, без разрывов.
Вернёмся в наш проект.
Так как мы никуда не торопимся, давайте всё-таки включим предделитель на тактирование шины SPI в функции SPI_init()
SPCR |= (1 << SPE) | (1 << MSTR)| (1<<SPR0);//включим шину, объ¤вим ведущим, делитель 16
Так как теперь мы можем подавать команды регистрам, то можем уже заняться инициализацией.
Прочистим себе дорогу в main(), убрав вот это всё
SPDR = 0b00000000;
while(!(SPSR & (1<<SPIF)));//подождем пока данные передадутся
SPDR = 0b00000000;
while(!(SPSR & (1<<SPIF)));//подождем пока данные передадутся
//сгенерируем отрицательный фронт для записи в STORAGE REGISTER
PORTB |= (1<<PORTB2); //высокий уровень
PORTB &= ~(1<<PORTB2); //низкий уровень
Задержку оставим.
Давайте сначала посмотрим в даташите таблицу адресов регистров
Сначала дадим следующую команду
SPI_init();
Send_7219(0x09, 0xFF); //включим режим декодирования
По комментарию ясно, что мы включили режим декодирования.
С девяткой ясно. Разбираемся с 0xFF.
Для этого есть другая таблица с примерами настройки декодирования
То есть мы видим, что каждому биту в аргументе команды соответствует один разряд, то есть значением 0xFF мы включили декодирование на все разряды нашего индикатора. А, например, если бы передали только 0x0F, то мы бы включили только 4 младших разряда.
Вообщем, с командой ясно, можно также поглядеть саму таблицу кодов для декодирования
Здесь мы видим, какие символы мы можем передать с помощью декодера. Вообщем-то основные все есть. Обидно только, что нет буквы «C». Ну да ничего. Также мы видим, что код символа передаётся только в четырёх младших разрядах байта данных, следующие три бита вообще не используются, а последний старший бит отвечает за точку.
Следующим шагом нашей инициализации будет настройка количества задействованных разрядов. Для этого у нас есть регистр с адресом 0x0B.
Вот его таблица
Здесь схема простая. Значение данному регистру мы передаём, равное количествузадейстовванных разрядов, уменьшенное на 1.
Поэтому давайте добавим глобальную переменную для количества разрядов и сразу её и инициализируем
#include «main.h»
char dg = 8;
То есть мы будем использовать все 8 разрядов
Теперь передадим значение в регистр
Send_7219(0x09, 0xFF); //включим режим декодирования
Send_7219(0x0B, dg — 1); //сколько разрядов используем
Теперь займёмся яркостью свечения. Для этого у нас существует отдельный регистр с адресом 0x0A
Так как у нас MAX7219, то для нас первая колонка. Здесь я думаю всё просто. Для начала зададим среднюю интенсивность свечения индикатора
Send_7219(0x0B, dg — 1); //сколько разрядов используем
Send_7219(0x0A, 0x07); //яркость
Ну и последняя настройка — это включение индикатора, также существует отдельный регистр, думаю, показывать его нет смысла, так как у него всего два значения: 0 — отключено, 1 — включено
Send_7219(0x0A, 0x07); //яркость
Send_7219(0x0C, 1); //включим индикатор
После всех данных настроек желательно весь мусор, который наберётся в буфере данных, очистить. Для этого напишем функцию очистки дисплея, опять же над функцией main()
void Clear_7219(void)
{
char i = dg;
do {
Send_7219(i, 0xF); //символ пустоты
} while (—i);
}
Ну здесь для нас, как всегда всё просто. Мы поочерёдно во все задействованные разряды установим пустые символы. В качестве адреса регистра у нес уже здесь выступает адрес информационного регистра, соответствующий заданному разряду. А байт 0xF — это у нас код символа пустоты (в таблице выше он выглядит как «blank»).
Вызовем вышенаписанную функцию в main()
Send_7219(0x0C, 1); //включим индикатор
Clear_7219();
Ну и наконец-то давайте теперь что-то попробуем вывести на дисплей, например, какой-нибудь символ, а лучше всего во все восемь разрядов символы номеров разрядов
Clear_7219();
Send_7219(0x01, 1);
Send_7219(0x02, 2);
Send_7219(0x03, 3);
Send_7219(0x04, 4);
Send_7219(0x05, 5);
Send_7219(0x06, 6);
Send_7219(0x07, 7);
Send_7219(0x08, 8);
Соберём код и посмотрим сначала работу кода в протеусе
В протеусе всё хорошо. Прошьём контроллер и проверим работу нашего индикатора на живом индикаторе
Всё у нас работает.
И мы видим, где именно и какой разряд.
Казалось бы, на этом можно и закончить. Но что толку в посылании в разряд символа? Не интересно. Давайте что-то повеселее напишем.
Сначала попробуем поиграть с яркостью. Попробуем её убавить
Send_7219(0x0A, 0x02); //яркость
Посмотрим результат
Да, отличается, стало темнее.
Теперь давайте напишем функцию для вывода какой-то числовой величины, чтобы посылать не поразрядно цифры, а как и в прежних проектах, целыми цифрами.
Создадим такую функцию опять же над функцией main()
void Number_7219(volatile long n)
{
}
Вот такая вот функция, на первый взгляд незатейливая, что не скажешь про её тело, которое мы ей напишем ниже. Во входе будет неоптимизируемая величина, а то мало ли, затеряется.
Начнём с переменной знака, вдруг, мы захотим вывести отрицательную величину
void Number_7219(volatile long n)
{
char ng = 0; //переменная для минуса
Дальше условие, при котором мы включим данный флаг и соответственно входное значение умножим на -1, чтобы сделать его положительным и в дальнейшем уже работать как с неотрицательной величиной
char ng = 0; //переменная для минуса
if (n < 0)
{
ng = 1;
n *= -1;
}
Очистим дисплей
n *= -1;
}
Clear_7219();
Дальше ещё условие равенства величины нулю, тогда мы ноль отправим только в младший разряд и выйдем из функции
Clear_7219();
if (n == 0) {
Send_7219(0x01, 0); //в первый разряд напишем 0
return;
}
Затем добавим ещё одну переменную для счётчика
return;
}
char i = 0;
Дальше цикл, в котором мы заполним разряды цифрами из нашей величины
char i = 0;
do {
Send_7219(++i, n%10);
n /= 10;
} while (n);
Здесь также у нас всё несложно, мы уменьшаем с каждым разрядом в 10 раз нашу величину и извлекаем из неё последний разряд с помощью деления по модулю на 10, и отправляем в текущий разряд.
Ну и по окончанию тела функции отобразим в следующий разряд символ минуса
} while (n);
if (ng) {
Send_7219(i+1, 0xA); //символ —
}
}
Ну, давайте проверим данную функцию. После отображения наших символов от 1 до 8 в функции main() добавим небольшую задержку, а затем отобразим какую-нибудь величину
Send_7219(0x08, 8);
_delay_ms(1000);
Number_7219(-2016);
Соберём код, прошьём контроллер и посмотрим результат, так как в протеусе почему-то не заработало
Отлично!
Ну теперь, чтобы выглядело всё поживее, напишем какой-нибудь счётчик, тем более у нас переменная уже есть. Добавим код в беспонечный цикл, перед этим также вставим задержку
Number_7219(-2016);
_delay_ms(1000);
while(1)
{
NumberFull_7219(i);
i++;
_delay_ms(200);
}
В принципе можно не ограничивать счёт, у нас разрядов для максимального значения типа переменной хватит.
Соберём код, прошьём контроллер и проверим
Ну вот мы с вами и познакомились с новым устройством, тем самым потренировались лишний раз с шиной SPI.
Только мы опять с шиной SPI не заканчиваем. На следующем занятии мы попробуем подключить по данной шине внешний АЦП.
Предыдущий урок Программирование МК AVR Следующий урок
Техническая документация на микросхемы MAX7219-MAX7221
Программатор и индикатор с драйвером можно приобрести здесь:
Программатор (продавец надёжный) USBASP USBISP 2.0
Индикатор светодиодный семиразрядный с драйвером MAX7219
Смотреть ВИДЕОУРОК
Доброго времени суток!
Изучая данный урок, заказал я себе индикатор на max7219. Но Пришла мне матрица (8×8)x4 на max7219.
Долго я с ней мучился, и в итоге у меня получилось, Зажигать выбранные точки, но только в первой секции. Поразбирав код, мне удалось раздельно совместить 4 секции, но с использованием массива переменных, тоесть каждая строка из секции сначала заноситься вертикально, т.е записывается первая секция, потом вторая и т.д. А выводить их приходилось построчно по порядку первая строка всех четырех секций, меняем адрес на вторую, и .т.д а удалось мне это при выборе строки, не закрывать ее а продолжать писать в выбранный адрес, и получалась строка, с массивом это вообще проблем не составило.
Что хочу сказать, я перевел часы на эту матрицу, закрыл белым листом и получилось очень симпатично. Сделал библиотеку с символами, мину лишь в том что вывести можно только сразу 4 символа, и позицию выбирать нельзя, то-есть отправлять можно: slovo(2,3,5,9)
Что хочу сказать, если интересно увидеть мою работу, которую я достиг благодоря вам, прошу отзовитесь.
P.S. в Proteus у меня данный урок не отображает индикацию. Пробовал даже частоту убавлять, Led индикаторы цеплял они моргают, а сегментники нет, ни один не запустился, редкие хаотичные моргания, Проверял сам сегментник питание масса, они рабочие. может что-то настроит в Proteus.
P.P.S Кстати написал библиотеку, Bounce Шарик летает, ударяется об стенки и меняет направление с углом 90 Градусов.
Здравствуйте Алексей,
хотелось бы увидеть ваш код для этой матрицы (8х8)х4, если не трудно опубликуйте.
Не понятно что за do{}while(), как это работает, растолкуйте пожалуйста!
Скоро выйдет урок по программированию на СИ по такой теме.
Это не тема программирования контроллеров. Это язык.
У Вас всё правильно, это я ошибся 🙂
Если в строке
Send_7219(0x0A, 0x02); //яркость
поставить значение 0х03, то Proteus заработает
Для работы в Протеусе добавляем яркость- было Send_7219(0x0A, 0x02); //яркость
Пишем Send_7219(0x0A, 0x0E); //яркость
Что означает while(n) объясните пожалуйста.
Смотрите цикл с 1 урока. Тогда будет понятно про бесконечный цикл.