18 мая 2012 г.

Arduino: используем всю мощь двухцветной матрицы

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


Подключение матрицы 

Я уже писал о том, как подключать светодиодную матрицу в случае использования одного красного цвета. Для добавления зеленого нам нужно просто включить в схему еще один сдвиговый регистр 74HC595, и посадить его на шину SPI последовательно.

Вспомним расположение ног матрицы:

1-8 - это аноды, ra-rh - красные катоды, ga-gh - зеленые катоды. На каждую из этих групп нам понадобится по одному регистру (всего 3 штуки). В прошлый раз наша установка выглядела так:


видно, что ноги зеленых катодов не задействованы. Теперь пришел их черед! Добавляем еще один регистр и подключаем его к зеленым катодам вот так:


Для большей наглядности я таки-нарисовал схему на этот раз:



На графической схеме синими проводами подключен "анодный" регистр, фиолетовыми - "красный" регистр, а белыми - "зеленый" регистр. Если вглядеться, как они сидят на шине SPI (внимание на желтые провода), можно увидеть, что ближе всего к Arduino "красный" регистр, далее за ним "зеленый", а потом "анодный". Это нам нужно запомнить, чтобы в программе посылать байты в правильном порядке. Сначала на придется посылать "анодный" байт, затем "зеленый" байт, а в конце - "красный", поскольку байты проталкиваются сквозь регистры.

Первый опыт

Напишем простенькую программу, которая будет набегом менять цвет точек. Сначала лучше покажу, как она работает, а потом мы разберем код:



Код:

#include <SPI.h>             //подключаем библиотеку SPI
enum { reg = 9 };             //выбираем линию SS регистра на 9-м пине Arduino
void setup(){
  SPI.begin();                   //инициализируем SPI
  //переводим выбранный для передачи пин в режим вывода
  pinMode(reg, OUTPUT);
}
static uint8_t spiByte=0xFF;

void loop(){
  for (int i=0;i<8;i++){
    spiByte=spiByte ^ (1 << i);   //рассчитываем анодный байт
    digitalWrite(reg, LOW);       //начинаем передачу по SPI
    SPI.transfer(0xFF);           //передаем анодный байт
    SPI.transfer(spiByte);        //передаем зеленый байт
    SPI.transfer(0xFF ^ spiByte); //передаем красный байт
    digitalWrite(reg, HIGH);      //заканчиваем передачу по SPI
    delay(80);                    //пауза
  }
}
Попробуем разобраться, что же мы написали. В include и Setup все как всегда. Далее мы заводим переменную под называнием spiByte, которая символизирует количество зеленых точек в матрице. В цикле мы увеличиваем количество зеленых точек в каждой колонке следующим образом: представим себе один столбец - т.е. восемь светодиодов с общим анодом, если в соответствующем бите зеленого байта единица, то светодиод не горит, а если ноль, то горит. Красный байт - это инвертированный зеленый байт, это сделано для того, чтобы красный не горел, когда горит зеленый, и наоборот.Ниже на рисунке видно, как происходит смена цветов:

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

spiByte=spiByte ^ (1 << i);
включает следующий светодиод. Напомню, что << - это побитовый сдвиг, а ^ - исключающее ИЛИ. Когда мы объявили зеленый бит, мы заполнили его единицами. В ходе выполнения цикла будут производиться следующие действия:
11111111 ^ 1 = 11111110
11111110 ^ 10 = 11111100
11111100 ^ 100 = 11111000
и так далее.

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

Займемся более интересными вещами - напишем букву на матрице. Пусть это будет красная буква N на зеленом фоне.
Нам надо создать такую штуку, которая называется битовая маска. Нарисуем поле 8 на 8 и раскрасим его в 2 цвета, как мы представляем себе букву N:


Нам понадобится отдельно красная маска и зеленая. Начнем с красной. Берем первую колонку и начинаем снизу вверх записывать 0, если клетка красная, и 1, если зеленая. Для удобства сразу запишем код для IDE Arduino, в которой бинарные числа записываются с префиксом 0b:

  0b00000000,
  0b00000000,
  0b11111001,
  0b11110011,
  0b11100111,
  0b11001111,
  0b00000000,
  0b00000000
Если вглядеться, то можно увидеть букву N, составленную из нулей и лежащую на боку.

Теперь сделаем зеленую маску:

  0b11111111,
  0b11111111,
  0b00000110,
  0b00001100,
  0b00011000,
  0b00110000,
  0b11111111,
  0b11111111
Видно, что лежачая буква N теперь составлена из единиц, а это значит, что мы все правильно сделали.

Общая идея динамической индикации в матрице в том, что в определенный момент времени мы включаем светодиоды, подключенные к одному и тому же аноду (т.е. в одной колонке), и поочередно перебираем аноды. Если делать это медленно, то буква будет мерцать. Если мы увеличим частоту полного обновления матрицы до 50 герц, то глаз не увидит моргания, и картинка будет казаться статичной.

Собственно код:

 #include <SPI.h>   //подключаем библиотеку SPI
enum { reg = 9 };   //выбираем линию SS регистра на 9-м пине Arduino
void setup(){
  SPI.begin();      //инициализируем SPI
  pinMode(reg, OUTPUT);
}

static uint8_t red[8] = {                    //красная маска
  0b00000000,
  0b00000000,
  0b11111001,
  0b11110011,
  0b11100111,
  0b11001111,
  0b00000000,
  0b00000000
},
green[8] = {                                   //зеленая маска
  0b11111111,
  0b11111111,
  0b00000110,
  0b00001100,
  0b00011000,
  0b00110000,
  0b11111111,
  0b11111111
};

void loop(){
  for(int i=0; i<8; i++){
    digitalWrite(reg, LOW);      //начинаем передачу по SPI
    SPI.transfer(1 << i);        //выбираем анод
    SPI.transfer(green[i]);      //передаем зеленый байт
    SPI.transfer(red[i]);        //передаем красный байт
    digitalWrite(reg, HIGH);     //заканчиваем передачу по SPI
    delay(2);                    //пауза 
  }
}
И результат:



Почему пауза 2? Посчитаем частоту обновления матрицы как 1/(0,002*8)=62,5 Гц. Этого хватает, чтобы глазу свечение казалось непрерывным. Если поставить 3, то частота будет 1/(0,003*8)=41,(6) - уже меньше.

Анимация 

Нарисуем нашу Феррари:


и аккуратно запишем две битовые маски для нее, заполнив первые восемь байт единицами, чтобы тачка как бы выезжала с правого краю:

static uint8_t red[40] = {     //красная маска
  0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
  0b11001111,
  0b11000111,
  0b11100111,
  0b11110111,
  0b11110111,
  0b11100111,
  0b10000011,
  0b10000101,
  0b10000110,
  0b10010110,
  0b10000000,
  0b10000110,
  0b10000110,
  0b10010110,
  0b11100000,
  0b11110011,
  0b11110011,
  0b11100001,
  0b11000001,
  0b11111111,
  0b11011111,
  0b11111111,
  0b11110111,
  0b01001001,
  0b10101011,
  0b01011011,
  0b11100101,
  0b11011111,
  0b11110111,
  0b11111111,
  0b11110111,
  0b11101111
},
green[40]={        //зеленая маска
  0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
  0b11110111,
  0b11111111,
  0b10011111,
  0b00001111,
  0b00001111,
  0b10011111,
  0b11111111,
  0b11111011,
  0b11111001,
  0b11111001,
  0b11111111,
  0b11111001,
  0b11111001,
  0b11111001,
  0b10011111,
  0b00001111,
  0b00001111,
  0b10011111,
  0b11111111,
  0b11011111,
  0b10101111,
  0b10000111,
  0b00001011,
  0b10110111,
  0b01010100,
  0b10100100,
  0b10011011,
  0b11100111,
  0b11101111,
  0b11100111,
  0b11101111,
  0b11111111
};
Теперь введем всякие переменные:

int timer, timerPrev=0;     //для подсчета времени
int shift=0;                //текущая величина сдвига 
int len=40;                 //длина изображения
Суть (TM) переменных timer и timerPrev описана в старом посте в разделе "Итоговый проект: часы и хитрая змейка", где мы делали параллельные задачи для Arduiino. Переменная shift - это величина сдвига изображения относительно начала. Каждые 80 миллисекунд (это будет такая длительность кадра) shift увеличивается на 1, и на матрице показываются очередные 8 колонок изображения.

Действуем вот таким макаром:

void loop(){
  timer = millis();                 //засекам время
//если прошло 80 мс после прошлого фрейма, включаем следующий
  if (timer-timerPrev>80){
    shift++;                        //увеличиваем сдвиг на 1
    if (shift==len)shift=0;
    timerPrev=timer;
  }
  for(int i=0; i<8; i++){
    digitalWrite(reg, LOW);
    SPI.transfer(1 << i);
    SPI.transfer(green[i+shift > len-1 ? i+shift-len : i+shift]);
    SPI.transfer(red[i+shift > len-1 ? i+shift-len : i+shift]);
    digitalWrite(reg, HIGH);
    delay(2);
  }
}
Все просто, все понятно. Наслаждаемся:


Ваши вопросы оставляйте в комментариях или присылайте по почте. Успехов в ваших проектах!

Далее по теме Arduino и SPI