AVR Урок 28. SPI. Драйвер LED MAX7219



     

    Урок 28

    SPI. Драйвер LED MAX7219

 

Продолжаем работать с шиной SPI.

Сегодня мне в руки попался драйвер 8-разрядных светодиодных индикаторов. Данный драйвер оснащем декодером символов, умеет управлять динамической индикацией и много чего ещё. Ну, и, самое главное, управляется данная микросхема по шине SPI, что не дало мне права обойти его стороной.

Сегодня мы его и попробуем подключить и поуправлять им. Работать мы будем с ней также только на передачу, работа на приём ждёт нас впереди в более поздних занятиях.

Ну и как всегда, обратимся к технической документации, чтобы, так сказать немного с ним ознакомиться для начала.

Вот типовая схема подключения микросхемы MAX7219

 

Image00

 

Мы видим, что у данного драйвера есть 8 параллельных выходов для сегментов и также восем параллельных выходов для разрядов. Также есть контакт для последовательного входа данных, выхода на схеме нет, хотя вообще он у драйвера есть, то есть разработчики сами прекрасно понимают, что мониторить здесь нечего. Также есть вход для выбора микросхемы Chip Select и вход для синхроимупульсов. На 18 ножку Iset мы подтягиваем резистор с шины питания, который рекомендуется впаивать с номиналом 9,53 килоома, тем самым мы устанавливаем пиковый ток для сегментов. Ну и, соответственно, мы подключаем на 19 ножку питание, а на 9 и 4 — общий провод.

Так как данная микросхема не совсем простая, у неё также существует немалая структурная схема

 

Image01

 

Здесь мы видим внизу 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.

Посмотрим схему в проетусе. За основу также взята была схема с прошлого знанятия и, соответственно, переработана под новую микросхему. Дело в том. что у меня отдельной микросхемы нет, а есть модуль, в котором уже установлен восьмиразрядный индикатор, поэтому такой же индикатор мы подключим и в протеусе (нажмите на картинку для увеличения изображения)

 

Image02_0500

 

Мы видим, что на вход DIN идёт выход MISO контроллера, а также мы подвели к нему синхронизацию и выбор. Ну а индикатор соответствующими разрядами и сегментами мы подключили к соответствующим параллельным выходам драйвера. Вообщем, схема получилась проще паренной репы.

А вот так вот у нас всё выглядит в модуле

 

Image03

Image06

А вот вид сзади

 

Image04

 

Мы видим здесь нашу микросхему MAX3219 и все соединения. Выполнено очень даже чинно. Также мы подвели к микросхеме 4 провода, воспользовавшить обычными контактными разъёмами. Здесь у нас шина питания и общий провод, а также 3 провода SPI. Вот и всё

Ну а вот так выглядит всё это вместе с отладочной платой

 

Image05

 

Также здесь мы видим, что вместо восьмиразрядного индикатора в модуле установлены два четырёхразрядных индикатора, что, собственно, не очень тяготит, так как раздряды все находятся друг от друга на одинаковом расстоянии, так сказать, без разрывов.

Вернёмся в наш проект.

 

 

Так как мы никуда не торопимся, давайте всё-таки включим предделитель на тактирование шины 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); //низкий уровень

 

Задержку оставим.

Давайте сначала посмотрим в даташите таблицу адресов регистров

 

Image07

Сначала дадим следующую команду

 

SPI_init();

Send_7219(0x09, 0xFF); //включим режим декодирования

 

По комментарию ясно, что мы включили режим декодирования.

С девяткой ясно. Разбираемся с 0xFF.

Для этого есть другая таблица с примерами настройки декодирования

 

Image08

 

То есть мы видим, что каждому биту в аргументе команды соответствует один разряд, то есть значением 0xFF мы включили декодирование на все разряды нашего индикатора. А, например, если бы передали только 0x0F, то мы бы включили только 4 младших разряда.

Вообщем, с командой ясно, можно также поглядеть саму таблицу кодов для декодирования

 

Image09

 

Здесь мы видим, какие символы мы можем передать с помощью декодера. Вообщем-то основные все есть. Обидно только, что нет буквы «C». Ну да ничего. Также мы видим, что код символа передаётся только в четырёх младших разрядах байта данных, следующие три бита вообще не используются, а последний старший бит отвечает за точку.

Следующим шагом нашей инициализации будет настройка количества задействованных разрядов. Для этого у нас есть регистр с адресом 0x0B.

Вот его таблица

 

Image10

 

Здесь схема простая. Значение данному регистру мы передаём, равное количествузадейстовванных разрядов, уменьшенное на 1.

 

 

Поэтому давайте добавим глобальную переменную для количества разрядов и сразу её и инициализируем

 

#include «main.h»

char dg = 8;

 

То есть мы будем использовать все 8 разрядов

 

Теперь передадим значение в регистр

 

Send_7219(0x09, 0xFF); //включим режим декодирования

Send_7219(0x0B, dg 1); //сколько разрядов используем

 

Теперь займёмся яркостью свечения. Для этого у нас существует отдельный регистр с адресом 0x0A

 

Image11

 

Так как у нас 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);

 

Соберём код и посмотрим сначала работу кода в протеусе

 

Image12

 

В протеусе всё хорошо. Прошьём контроллер и проверим работу нашего индикатора на живом индикаторе

 

Image13

 

Всё у нас работает.

И мы видим, где именно и какой разряд.

Казалось бы, на этом можно и закончить. Но что толку в посылании в разряд символа? Не интересно. Давайте что-то повеселее напишем.

Сначала попробуем поиграть с яркостью. Попробуем её убавить

 

Send_7219(0x0A, 0x02); //яркость

 

Посмотрим результат

 

Image14

 

Да, отличается, стало темнее.

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

Создадим такую функцию опять же над функцией 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);

 

Соберём код, прошьём контроллер и посмотрим результат, так как в протеусе почему-то не заработало

 

Image15

 

Отлично!

Ну теперь, чтобы выглядело всё поживее, напишем какой-нибудь счётчик, тем более у нас переменная уже есть. Добавим код в беспонечный цикл, перед этим также вставим задержку

 

 

 

Number_7219(-2016);

_delay_ms(1000);

while(1)

{

  NumberFull_7219(i);

  i++;

  _delay_ms(200);

}

 

В принципе можно не ограничивать счёт, у нас разрядов для максимального значения типа переменной хватит.

Соберём код, прошьём контроллер и проверим

 

Image16

 

Ну вот мы с вами и познакомились с новым устройством, тем самым потренировались лишний раз с шиной SPI.

Только мы опять с шиной SPI не заканчиваем. На следующем занятии мы попробуем подключить по данной шине внешний АЦП.

 

 

    Предыдущий урок Программирование МК AVR Следующий урок

     

    Исходный код

 

    Техническая документация на микросхемы MAX7219-MAX7221

 

Программатор и индикатор с драйвером можно приобрести здесь:

Программатор (продавец надёжный) USBASP USBISP 2.0

Индикатор светодиодный семиразрядный с драйвером MAX7219

 

 

    Смотреть ВИДЕОУРОК

 

9 комментариев на “AVR Урок 28. SPI. Драйвер LED MAX7219
  1. Алексей A:

    Доброго времени суток!
    Изучая данный урок, заказал я себе индикатор на 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, если не трудно опубликуйте.

  2. Дмитрий:

    Не понятно что за do{}while(), как это работает, растолкуйте пожалуйста!

  3. Frut:

    У Вас всё правильно, это я ошибся 🙂

  4. User7:

    Если в строке
    Send_7219(0x0A, 0x02); //яркость
    поставить значение 0х03, то Proteus заработает

  5. Parnick:

    Для работы в Протеусе добавляем яркость- было Send_7219(0x0A, 0x02); //яркость
    Пишем Send_7219(0x0A, 0x0E); //яркость

  6. Абдулкадир:

    Что означает while(n) объясните пожалуйста.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*