Динамическая индикация на arduino

Динамическая индикация в Arduino, на примере led-матрицы 8х8 + uln2803

от автора

в

Нашёл я недавно пример игральной кости, на матрице 8х8, но много чего там мне не понравилось. Захотелось поместить это в маленький корпус, но размер периферии arduino не позволяет это сделать. Поэтому делаем программу на arduino, а потом переходим на обычную плату с запаянным atmega328. Обычно используется индикатор 8х8 пикселей уже со сдвиговыми регистрами, мы будем использовать обычную матрицу, ибо потом этот сдвиговый регистр надо куда-то спрятать, мы будем ограничены в размерах, да и количество выводов atmega328 позволяет подключить всё напрямую. Посмотрим, что из себя представляет матрица

По сути это просто пересечение светодиодов, где аноды выведены в колонки, а катоды в ряды, или наоборот. Сразу понятно, что зажечь конкретный светодиод легко, а вот например одновременно на двух рядах разные уже не получится, они будут дублироваться на рядах. Для иллюстрации этого факта, если подать “плюс” на 1,2,4,6 ряд, и “минус” на 2 и 5 колонку.

динамическая индикация матрица 8 х 8

Но ведь если бы каждый светодиод имел отдельный выход, то их было бы 64! Но можно ряды зажигать по очереди, и если делать это очень быстро, то глаз (который обладает инерцией) не заметит подвоха. Это и есть динамическая индикация. Я взял матрицу, которая залежалась у меня, но она двухцветная (т.е. кадоты выведены, ещё и для зелёного цвета, и выводов суммарно 24), и я не знал её распиновки.

Распиновку узнать легко – просто берём плюс, через резистор 130 Ом, и минус, и рандомно тыкаем по контактам, пока не найдём зажжённый светодиод. Потом, также методом исключения, пытаемся понять, что анод, а что катод, что ряды, а что колонки. Выписываем всё на бумагу. У меня получилась вот такая картина, на моём модуле написано 1088DHG, может кому пригодится (вид сверху, со стороны диодов)

A – аноды, C – красные катоды, CG – зелёные катоды. Я выбрал для подключения красный цвет. Следующая проблема – выход МК обычно 5В, чем можно спалить диод, даже в режиме пикового короткого тока. Поэтому ставим сопротивление в разрыв подключения ног к анодам. Можно посчитать номинал любым онлайн калькулятором, если знаете параметры диодов, думаю 130 Ом подойдёт почти всем, их я и запаял на удобную контактную площадку, чтобы соединять соединителями DuPont. Не люблю я эти ужасные макетки огромные, мне проще быстро сделать такой переходник для своих нужд.

uln2803 к arduino

Вторая плата содержит в себе ULN2803 – это сборка составных транзисторов (Дарлингтон), на 8 входов и выходов. Очень удобная штука, чтобы не городить транзисторы отдельно, подойдёт конечно не только для светодиодов (тут даже избыточно) но и для реле и других силовых нагрузок.

Здесь изображён каждый каскад отдельно, если не вникать в подробности – выход с микросхемы – это открытый коллектор этой сборки, и он притягивается к земле. В первый раз, я туда подключил плюс нагрузки, и конечное же долго не понимал, почему не работает. К Выходу ULN2803 нужно подключать землю нагрузки!.

Можно заметить, что каждый выход держит 500ма нагрузки. Для чего мы вообще используем эту мискросхему? Мы уже разобрались, что в динамической индикации, изображение формируется очень быстро, как бы “по этажам” (катоды). На этажах мы зажигаем диоды отдельно, каждый от своего выхода микроконтроллера, поэтому ток на выходах небольшой. В вот сливаем весь этот ток мы на землю, и если был зажжён каждый светодиод, то 20ма*8 = уже 160ма, и ваш вход atmega328 быстро сгорит. Поэтому ток “этажей” (катодов) мы коммутируем на ULN2803, а анодами управляем через сопротивления напрямую с МК. Схема получится такая, в Proteus это не так важно, а вот в реальной жизни, выход COM лучше подтянуть к питанию.

динамическая индикация arduino

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

#define col1 2                                // Колонки
#define col2 3
#define col3 4
#define col4 5
#define col5 6
#define col6 7
#define col7 8
#define col8 9

#define row1 10                                // Ряды
#define row2 11
#define row3 12
#define row4 13
#define row5 14
#define row6 15
#define row7 16
#define row8 17

Затем, чтобы удобно оперировать ими, запишем их в одномерный массив

uint8_t rows[8] {row1, row2, row3, row4, row5, row6, row7, row8};    // Пины строк
uint8_t cols[8] {col1, col2, col3, col4, col5, col6, col7, col8};    // Пины колонок

Можно кстати, записывать сразу в массив номера выходов, но я для наглядности сделал так. Нужно сразу сказать методов организации динамической индикации много разных. Тут главное понять принцип, не обязательно выводить изображение из массива, но мы сделаем именно так, потому-что легко понять, что будет выведено на экран. Инициализируем двумерный массив по 8 значений в каждом измерении. Про массивы подробнее мы говорили в статье про матричную клавиатуру.

int indication[8][8] = {
  {'1', '1', '0', '0', '0', '0', '1', '1'},
  {'1', '1', '0', '0', '0', '0', '1', '1'},
  {'0', '0', '0', '0', '0', '0', '0', '0'},
  {'0', '0', '0', '1', '1', '0', '0', '0'},
  {'0', '0', '0', '1', '1', '0', '0', '0'},
  {'0', '0', '0', '0', '0', '0', '0', '0'},
  {'1', '1', '0', '0', '0', '0', '1', '1'},
  {'1', '1', '0', '0', '0', '0', '1', '1'}
};

Можно проследить, что “1” я нарисовал цифру 5, как на игральных костях. Её и будем выводить. Перейдём в раздел setup, и сделаем начальные установки. Нам нужно абсолютно все наши выходы, как рядом, так и колонок назначить на выход. Сделаем это в цикле, так быстрее и более читабельно.

void setup()
{
  for (int i = 0; i < 8; ++i) {
    pinMode(rows[i], OUTPUT);                 // Инициализируем пины строк на выход
    pinMode(cols[i], OUTPUT);                 // Инициализируем пины колонок на выход
  }
}

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

for (uint8_t c = 0; c < 8; c++)                        // Функция динамической индикации
  {

Итак делаем цикл for на 8 итераций, где будем управлять катодами. Теперь нужно зажечь диоды, подав напряжение на аноды, взяв информацию из массива. Мы знаем номер ряда, благодаря циклу и номера колонок. Значит нам опять нужен цикл for, снова на 8 значений, ведь матрица 8х8 пикселей.

for (uint8_t z = 0; z < 8; z++)
    {
      digitalWrite(cols[z], indication[c][z] - 48);      // Теперь назначаем колонкам значения из массива, для это строки и колонки
    }

Командой digitalWrite(cols[z], indication[z] – 48); мы назначаем выходу cols[z], который меняется в процессе цикла, значение
indication[z] – 48 из нашего массива с изображением. Отнимаем 48, чтобы привести значение int к char, из-за особенностей кодировки ascii. Можно сразу задать массив в виде const char, и не делать этой операции. Теперь мы выставили всем выходам колонок cols[] значения, и наш ряд может загореться, согласно значениям массива. Не забываем, чтобы выход ULN2803 притянуть к земле (чтобы светодиоды на ряду загорелись) нужно подать ему на вход логическую 1. Поэтому делаем такую вставку

 digitalWrite(rows[c], HIGH);                         // Устанавливаем строку в активное состояние   
 delay(1);                                            // Пауза
 digitalWrite(rows[c], LOW);                          // Переводим строку в неактивное состояние

После небольшой паузы, отключаем ряд (катоды), и цикл так должен пройти все 8 рядов. И каждый раз внутри этого цикла ещё 8 циклов для анодов (колонок). Весь код отображения выглядит так

void dynamic_indi()
{
  for (uint8_t c = 0; c < 8; c++)                        // Функция динамической индикации
  {
    for (uint8_t z = 0; z < 8; z++)
    {
      digitalWrite(cols[z], indication[c][z] - 48);      // Теперь назначаем колонкам значения из массива, для это строки и колонки
    }
    digitalWrite(rows[c], HIGH);                         // Устанавливаем строку в активное состояние   
    delay(1);                                            // Пауза
    digitalWrite(rows[c], LOW);                          // Переводим строку в неактивное состояние
  }
}

В итоге на экране мы видим цельную картинку.

игральные кости на arduino

Конечно у этого метода есть большой минус – яркость индикатора используется не на полную мощность, даже если мы подаём ток слегка завышенного значения (пиковый ток светодиода). Но есть и большие плюсы – вместо 64 выводов, мы используем всего 16, и при масштабировании экономия только растёт! Небольшое видео, где понятно, как работает алгоритм

Весь код статьи доступен на гитхаб. Дальше мы совместим датчик наклона, индикатор и соберём игральные кости.