Управление на светодиодна матрица 8х8 с Ардуино, регистри и драйвер (74HC595 и ULN2803)

Причината за написването на това обяснение е Симеон от българския сайт за Ардуино фенове http://www.robotev.com. Според него щяло да представлява интерес. Може пък да е прав. Мнението ми е, че проекта е доволно прост и няма какво да му се обяснява но все пак ще се постарая.

Идеята беше да се направи модинг за компютърна кутия но колегата се отказа точно преди самата изработка. Понеже поне на теория проекта беше напълно готов все пак реших да го 'светна' и на практика. С използваните елементи се управляват максимум 64 светодиода но не е проблем да се ползват по-малко. Целта ми беше използваните компоненти да са от най-достъпните и евтини а самото изпълнение да е лесно.

Списък с чарколяци.

1. платка двустранно фолиран текстолит 15 х 20 сантиметра – 1 брой за 1.80лв

2. жълти светодиоди размер 10 милиметра – 64 броя кристални на обща цена 6.40лв

3. резистор 220Ohm – 8 броя общо за около 0.20лв

4. регистър 74HC595 – 2 броя по 0.35лв

5. драйвер ULN2803 – 1 брой за 0.65лв

0. опционално цокли за интегралите – 2 с по 16 крака и 1 с 18 крака на обща цена 0.30лв

Всичко струваше около 10лв и е купувано от магазин 'Кузмов електроникс'.

За контрол е използвано Arduino Serial с чип ATmega328 работещ на 16MHz но може да се ползва и друго.

Управлението е динамично на принципа на мултиплексирането. Последователно се изреждат 8 групи от по 8 светодиода. Понеже скоростта на редуване е висока и заради инертността на човешкото око визуалното впечатление е като да си светят всички светодиоди. Светодиодите са подредени в квадрат като по редове всички аноди са свързани заедно а по колони са със свързани катоди.

На картинката е илюстриран принципа. С бутоните се избира кои светодиоди от избраната колона да светят а със селектора последователно се изреждат колоните. Това позволява контролирането на 64 светодиода по 16 проводника и само 2 регистъра за адресирането по редове и колони.

Разликата в цените на кристалните и дифузните жълти светодиоди беше 5 пъти (0.10лв срещу 0.50лв за бройка) като за пробата предпочетох по-евтините. Все пак за да изглежда по-добре матирах полусферичната им част като за целта използвах акумулаторен винтоверт и шкурка номер 320.

Оказа се, че периферията на светодиодите пасва идеално в малка засечка на патронника и с внимателно притискане към шкурката при подходящи обороти се получи доста приличен резултат. Цялата процедура ми отне малко повече от час за всичките светодиоди и ми спести 28лв. Ако решите да ползвате този метод само имайте предвид, че по-голям номер на шкурката означава по-фин мат но и повече загравяне по време на матирането. Вместо номер 320 може би е по-добре да ползвате по-малък а не по-голям но това е въпрос на предпочитание.

Схемата 74HC595 представлява преместващ регистър с 1 сериен вход, 8 паралелни и 1 сериен изходи. Серийния вход позволява да се управлява само по 3 проводника и съответно само толкова пина са необходими от Ардуино-то. 8-те паралелни изхода са с товароносимост 20mA всеки и през резисторите захранват светодиодите. Ползваните от мен светодиоди са с пад около 1.9V и при 5V захранване през резистори 220Ohm светят при ток 14mA. Тока може да се ограничи до 10mA на светодиод а за по-силно светене да се ползват от ярките светодиоди, които обаче са по-скъпи. Серийния изход също влиза в употреба като към него се свързва серийния вход на другия регистър. Това каскадно свързване позволява управлението на няколко регистъра все още по само 3 проводника. Впрочем необходимите връзки общо са 5 ако броим и захранващите – Vcc и GND.

Другата схема ULN2803 съдържа 8 NPN Дарлингтон драйверни транзистора с отворен колектор и общи емитери и има вградени резистори на входа за TTL съвместимост, които позволяват директно свързване към управляващия го регистър. Всеки драйвер има обратно свързан диод на изхода и така може директно да се управляват индуктивни товари като соленоиди и моторчета и издържа максимален ток до 500mA. Тази товароносимост и каскадното свързване в случая позволява да се упавляват още 64 светодиода (или общо 128) само като се добавят 1 регистър с 8 резистора. Или бройката да остане същата но да се заменят с такива с по 3 извода по схема общ катод и светещи в зелено и червено (евентуално смесване на цветовете дава жълт цвят като резултат). Ако се ползват монохромни светодиоди при зададените данни се получава, че би могло да се направи 4 кратно мащабиране (8 светодиода х 14mA х 4 групи = 448mA, което пък е в опустимата граница от 500mA на драйвера)и да се управляват общо 256 светодиода само като се добавят още 3 регистъра и 24 съпротивления. С толкова диоди може да се изгради матрица 16х16 или 8х32 например. Естествено за контрола на някой от тези варианти управляващата програма ще трябва да се промени.

В началото щях да ползвам просто изреждане на отделни кадри всеки със зададено време за визуализация но бързо се отказах. Реших дефинираните кадри да могат да се редуват чрез плавен преход като по този начин със сравнително малко на брой кадри лесно се постига прост ефект на анимиране. Самите кадри се задават чрез 8 байта като за всеки светодиод се ползва по 1 бит и по този начин се ползва минимално памет. Така с малко по-сложно управление като програма печеля повечко място за кадри. За да се улесни описанието на кадрите щях да ползвам електронна таблица но впоследствие попаднах на по-добро решение.

На линка http://skss.learnfree.eu/archives/172 има описан подобен проект като за кодирането на кадрите автора е написал програмка с помощта на AutoIT. Понеже не разбирам от програмиране само съм редактирал предоставения сорс код за да може вместо за 5х8 да става за 8х8 светодиода. От линка може да си свалите окончателния компилиран вариант.

Като свалите програмката просто я стартирайте. В прозореца й има 8х8 квадратчета и в зависимост дали има или не чавка в някой от тях се задава дали съответстващия светодиод свети. Когато кадъра е готов само копирате всички определящи двоични числа от дясната част и ги добавяте в управляващата програма като елемент от масива matrix []. Дойде ред и на самата управляваща програмка.


// примерна програма с демонстрация управлението на
// светодиодна матрица 8х8 чрез регистри и драйвер
// масив с кадрите
// 8 байта за кадър
const byte matrix [] = 
{
  0, 0, 0, 0, 0, 0, 0, 0,
  B00000000, B00000000, B00000000, B00011000, B00100100, B00000000, B00000000, B00000000,
  B11111110, B00010001, B00010001, B00010001, B00010001, B11111110, B00000000, B00000000,
  B11111111, B00010001, B00010001, B00110001, B01010001, B10001110, B00000000, B00000000,
  B11111111, B10000001, B10000001, B10000001, B10000001, B01111110, B00000000, B00000000,
  B01111111, B10000000, B10000000, B10000000, B10000000, B01111111, B00000000, B00000000,
  B00000000, B10000001, B11111111, B11111111, B10000001, B00000000, B00000000, B00000000,
  B11111111, B00000010, B00000100, B00111000, B01000000, B11111111, B00000000, B00000000,
  B01111110, B10000001, B10000001, B10000001, B10000001, B01111110, B00000000, B00000000,
  B00000000, B00000000, B00000000, B00100100, B00011000, B00000000, B00000000, B00000000,
  0, 0, 0, 0, 0, 0, 0, 0
};
// масива описва < A R D U I N O > като кадър 0 е празен
// т.е. изпълнява ролята на SPACE
// масив за временно съхранение на текущия кадър

byte SframeS [ 8 ];
// размер на масива
const word DIM = sizeof ( matrix );
// пинове за контрол на регистрите 74HC595
const byte SH_CP = 5;
const byte ST_CP = 4;
const byte OE = 3;
// OE може да не се ползва а да се свърже директно към GND 
// и всички маркирани с * редове да се изтрият като излишни
const byte DS = 2;
// времеви променливи
unsigned long cycle;
unsigned long timer;
// технологично времезадържане в микросекунди - стойността
// е определена опитно
// при еквивалентното му задаване в милисекунди се получи 
// слабо но забележимо и дразнещо примигаване
// ако е зададена прекалено малка стойност започва да
// оказва влияние инертността на елементите
// като ефекта се проявява в по-слабото светене на 
// светодиодите
const word PAUSE = 500;
// помощни променливи и масив за определяне на посоката
word sec = 250;
const char drctn [] = "LURD";
byte change;
byte chng;
byte Hmirror;
byte Vmirror;
// инициализиране на управляващите пинове

void setup ()
{
  pinMode ( OE, OUTPUT ); // *
  digitalWrite ( OE, HIGH ); // *
  pinMode ( ST_CP, OUTPUT );
  digitalWrite ( ST_CP, HIGH );
  pinMode ( DS, OUTPUT );
  pinMode ( SH_CP, OUTPUT );
}

void loop ()
{
// параметри на процедура frames ( word b, word e, char 
// dir, word time, byte hm, byte vm, byte inv )
// b - начален кадър
// e - краен кадър
// dir - посока на прехода м/у началния и крайния кадри - 
// 'L' ляво,'U' горе, 'R' дясно или 'D' долу
// time - 10...65000 време в милисекунди като има 2 случая
//  1) при b=e означава времето за показване на кадъра
//  2) иначе е времетраенето на всеки от общо преходи 0...7 
// т.е. от началния кадър до -1 на крайния кадър
// hm, vm - 0 и 1 определя хоризонтално и вертикално огледало съответно
//  понеже се инвертира резултатния кадър може да изглежда, 
// че посоката на преход е обратна
// inv - 0 и 1 определя позитивно/негативно състояние на 
// кадъра за визуализиране
// по принцип се прави някаква проверка на входните данни и 
// ако не са допустими
// процедурата не се изпълнява изобщо или се изпълнява 
// частично като
// резултата вероятно ще е странен и по-добре да се избягва 
// некоректно задаване
  for ( byte n = 0; n < 4; n ++ )
  {
    Hmirror = n & 1;
    Vmirror = ( n & 2 ) >> 1;
    for ( byte rpt = 1; rpt < DIM / 8 - 1; rpt ++ )
    {
      frames ( 0, rpt, drctn [ n ], sec, Hmirror, Vmirror, n % 2 );
      frames ( rpt, 0, drctn [ n ], sec, Hmirror, Vmirror, n % 2 );
    }
    frames ( 0, 0, drctn [ n ], sec, Hmirror, Vmirror, n % 2 );
  }
}
// следва дефиниране на използваните процедури
// процедура за визуализиране на текущия кадър
void Sframe ( word duration )
{
  cycle = millis ();
  do
  {
    timer = millis () - cycle;
    for ( byte n = 0; n < 8; n++ )
    {
      digitalWrite ( ST_CP, LOW );
      shiftOut ( DS, SH_CP, LSBFIRST, 1 << n );
// изреждане на колоните
      shiftOut ( DS, SH_CP, LSBFIRST, SframeS [ n ] );
// задаване на редовете в избраната колона
      digitalWrite ( ST_CP, HIGH );
      digitalWrite ( OE, LOW ); // *
      delayMicroseconds ( PAUSE );
      digitalWrite ( OE, HIGH ); // *
    }
  }
  while ( duration > timer );
}
// процедура за генериране на текущия кадър
void frame ( word fb, word fe, char dir, byte DIRstep )
{
  switch ( dir )
  {
    case 'L':
    for ( byte MOVEstep = 0; MOVEstep < 8; MOVEstep ++ )
    {
      if ( MOVEstep + DIRstep < 8 )
      {
        SframeS [ MOVEstep ] = matrix [ fb * 8 + MOVEstep + DIRstep ];
      }
      else
      {
        SframeS [ MOVEstep ] = matrix [ fe * 8 + MOVEstep + DIRstep - 8 ];
      }
    }
    break;
    case 'U':
    for ( byte f = 0; f < 8; f ++ )
    {
      SframeS [ f ] = ( matrix [ fb * 8 + f ] >> DIRstep ) | ( matrix [ fe * 8 + f ] << ( 8 - DIRstep ) );
    }
    break;
    case 'R':
    for ( byte MOVEstep = 0; MOVEstep < 8; MOVEstep ++ )
    {
      if ( MOVEstep - DIRstep < 0 )
      {
        SframeS [ MOVEstep ] = matrix [ fe * 8 + MOVEstep - DIRstep + 8 ];
      }
      else
      {
        SframeS [ MOVEstep ] = matrix [ fb * 8 + MOVEstep - DIRstep ];
      }
    }
    break;
    case 'D':
    for ( byte f = 0; f < 8; f ++ )
    {
      SframeS [ f ] = ( matrix [ fb * 8 + f ] << DIRstep ) | ( matrix [ fe * 8 + f ] >> ( 8 - DIRstep ) );
    }
    break;
  }
}
// процедура за модифициране текущия кадър - 
// хоризонтално/вертикално огледало + инверсия
void frm ( byte HM, byte VM, byte INV )
{
  if ( HM | VM | INV < 2 )
  {
    if ( HM == 1 )
    {
      for ( byte n = 0; n < 4; n ++ )
      {
        change = SframeS [ n ];
        SframeS [ n ] = SframeS [ 7 - n ];
        SframeS [ 7 - n ] = change;
      }
    }
    if ( VM == 1 )
    {
      for ( byte n = 0; n < 8; n ++ )
      {
        change = SframeS [ n ];
        for ( byte b = 0; b < 8; b ++ )
        {
          bitWrite ( chng, b, bitRead ( change, 7 - b ) );
        }
        SframeS [ n ] = chng;
      }
    }
    if ( INV == 1 )
    {
      for ( byte n = 0; n < 8; n ++ )
      {
        SframeS [ n ] = ~ SframeS [ n ];
      }
    }
  }
}
// процедура за визуализиране плавен преход м/у 2 кадъра 
// или статичен кадър
void frames ( word fb, word fe, char dir, word drtn, byte HM, byte VM, byte INV )
{
  if ( fb < DIM / 8 && fe < DIM / 8 && ( dir == 'L' || dir == 'U' || dir == 'R' || dir == 'D' ) )
  {
    if ( fb == fe )
    {
      frame ( fb, fe, dir, 0 );
      frm ( HM, VM, INV );
      Sframe ( drtn );
    }
    else
    {
      for ( byte s = 0; s < 8; s ++ )
      {
        frame ( fb, fe, dir, s );
        frm ( HM, VM, INV );
        Sframe ( drtn );
      }
    }
  }
}


Ако в програмата се включи следене на входовете би могло се получи известна интерактивност – например 2 потенциометъра свързани към 2 аналогови входа и задаващи посока на текста и плавно изменящи скоростта му. В самата програма не съм пестил коментари и едва ли ще има нещо неясно. Ползва се процедурата frames ( word b, word e, char dir, word time, byte hm, byte vm, byte inv ) а параметрите й са описани. Демонстрацията изрежда всички дефинирани в масива кадри като се променят контролните данни. Самата процедура ползва няколко други процедури защото така ми беше по-лесно да напиша кода а и някои от възможностите й добавих по-късно от възникнали впоследствие идеи. Дори биха могли да се добавят други ефекти при преход м/у кадрите. Клипчето не е с добро качество защото все пак е снимано с телефон но дава някаква представа за работата на програмата.

http://www.youtube.com/watch?v=3-PQPM-Ti4M

Така изглежда крайния резултат. Начина на изработка на самата платка е въпрос на избор. В архива има няколко файла, които представляват самото PCB. Скосения ъгъл на контура на платката е за по-лесно ориентиране на страна елементи и страна спойки. Файловете могат да се ползват като шаблони за изработка на платката като само трябва да се принтират при мащаб 1:1. Има няколко отвора, на които не съответстват елементи защото на тях се запояват мостчета за преход м/у страна елементи и страна спойки.

Освен за отделни светодиоди принципно електрически схемата би трябвало да може да управлява до 8 индикатора от 7 сегментните с или без десетична точка и вътрешно свързване общ катод. При кодирането на символи за тях е достатъчен 1 байт. За по-добър визуален ефект може да са до 4 но 14 сегментни

или 16 сегментни

и ако се ползват за изписване на някакъв текст необходимата памет за символ става 2 байта. Друг вариант е да се ползва готова матрица 8х8

или при максимално мащабиране съответно 4 като нея. Предимството е, че са по-удобни за работа и не се налага да се запоява много.

Ако сте прочели цялото обяснение и ви е било поне малко интересно значи все пак не съм си губил времето напразно.

Пуснал съм тема ако евентуално има нещо за обсъждане.

Share