Опубликовано

Управление светодиодной матрицей с использованием чарлиплексирования

Раздел из главы 7 “Управление светодиодными устройствами вывода данных” из книги “Arduino. Большая книга рецептов, 3-е изд.” (авторы Джепсон Брайан, Марголис Майкл, Уэлдин Николас Роберт)

ЗАДАЧА

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

РЕШЕНИЕ

Одно из возможных решений этой задачи — использование чарлиплексирования. Чарлиплексирование — это особый вид мультиплексирования, позволяющий увеличить количество устройств (светодиодов), которыми можно управлять одной группой контактов. В листинге 7.13 приводится скетч для управления посредством чарлиплексирования шестью светодиодами, задействовав всего лишь три контакта платы Arduino. Подключение светодиодов показано на рис. 7.11 (методика вычисления значений токоограничивающих резисторов для светодиодов приводится в разд. 7.1).

Использование метода чарлиплексирования для управления шестью светодиодами с помошью трех контактов платы

Рис. 7.11. Использование метода чарлиплексирования для управления шестью светодиодами с помошью трех контактов платы

Листинг 7.13. Управление несколькими светодиодами методом чарлиплексирования

/*

* Скетч Charlieplexing

* Последовательно включает и выключает три светодиода.

*/

int pins[] = {2,3,4}; // Номера контактов для управления светодиодами

// Следующие две строки кода вычисляют количество контактов

// и светодиодов на основе информации в массиве pins

const int NUMBER_OF_PINS = sizeof(pins)/ sizeof(pins[0]);

const int NUMBER_OF_LEDS = NUMBER_OF_PINS * (NUMBER_OF_PINS-1);

byte pairs[NUMBER_OF_LEDS/2][2] = { {2,1}, {1,0}, {2,0} }; // Сопоставляем

// контакты светодиодам

void setup()

{

// Ничего не делаем

}

void loop()

{

for(int i=0; i < NUMBER_OF_LEDS; i++)

{

lightLed(i); // Последовательно включаем каждый светодиод

delay(1000);

}

}

// Эта функция включает требуемый светодиод. Нумерация светодиодов

// начинается с 0

void lightLed(int led)

{

// Следующие четыре строки кода преобразовывают номер светодиода

// в номера контактов

int indexA = pairs[led/2][0];

int indexB = pairs[led/2][1];

int pinA = pins[indexA];

int pinB = pins[indexB];

// Выключаем все контакты, не подключенные к заданному светодиоду

for(int i=0; i < NUMBER_OF_PINS; i++)

{

if(pins[i] != pinA && pins[i] != pinB)

{

// Если этот контакт не является одним из требуемых

pinMode(pins[i], INPUT); // Задаем для него входной режим работы

digitalWrite(pins[i],LOW); // Отключаем повышающий резистор

}

}

// Теперь включаем контакты для требуемого светодиода

pinMode(pinA, OUTPUT);

pinMode(pinB, OUTPUT);

if( led % 2 == 0)

{

digitalWrite(pinA,LOW);

digitalWrite(pinB,HIGH);

}

else

{

digitalWrite(pinB,LOW);

digitalWrite(pinA,HIGH);

}

}

Обсуждение работы решения и возможных проблем

Этот метод мультиплексирования называется чарлиплексированием по имени Чарли Аллена (Charlie Allen) из компании Microchip Technology, Inc., который его разработал и опубликовал. Метод основан на том обстоятельстве, что светодиоды включаются только при правильном подключении: когда анод более положительный, чем катод. В табл. 7.3 приводится список комбинаций выходных сигналов на трех контактах управления и соответствующие состояния шести управляемых ими светодиодов (см. рис. 7.9). Обозначение L означает LOW (низкий уровень), H — HIGH (высокий уровень), а i — INPUT (входной режим работы). Контакт, находящийся во входном режиме работы, по сути, отключен от схемы.

Таблица 7.3. Комбинации уровней контактов и соответствующие состояния светодиодов

Контакты Светодиоды
4 3 2 1 2 3 4 5 6
L L L 0 0 0 0 0 0
L H i 1 0 0 0 0 0
H L i 0 1 0 0 0 0
i L H 0 0 1 0 0 0
i H L 0 0 0 1 0 0
L i H 0 0 0 0 1 0
H i L 0 0 0 0 0 1

Количество светодиодов можно увеличить до 12, добавив всего лишь еще один контакт управления. В таком случае первые шесть светодиодов подключаются так же, как и в предыдущем примере (см. рис. 7.11), а дополнительные шесть, как показано на рис. 7.12.

Для управления удвоенным количеством светодиодов предыдущий скетч (см. листинг 7.13) нужно модифицировать, добавив дополнительный контакт в массив pins:

byte pins[] = {2,3,4,5}; // Номера контактов для управления светодиодами

Использование метода чарлиплексирования для управления 12 светодиодами с помошью четырех контактов платыРис. 7.12. Использование метода чарлиплексирования для управления 12 светодиодами
с помошью четырех контактов платы

Также необходимо откорректировать строку кода, сопоставляющую контакты светодиодам, добавив в массив pairs дополнительные пары:

byte pairs[NUMBER_OF_LEDS/2][2] = { {0,1}, {1,2}, {0,2}, {2,3}, {1,3}, {0,3} };

Остальной код остается таким же, но в цикле loop() будут обрабатываться 12 светодиодов, т. к. их количество определяется по количеству элементов массива pins.

Поскольку чарлиплексирование состоит в управлении контактами Arduino таким образом, что в единицу времени включается только один светодиод, создание впечатления одновременного включения нескольких светодиодов является в этом случае более сложной задачей. Однако несколько светодиодов можно включать одновременно с помощью метода мультиплексирования, модифицированного для использования с чарлиплексированием.

В листинге 7.14 приводится код скетча, в котором этот подход используется для создания линейного индикатора, включая последовательность светодиодов, количество которых пропорционально значению сигнала датчика, подаваемого на аналоговый контакт A0.

Листинг 7.14. Создание линейного индикатора с помощью модифицированного чарлиплексирования

/* Скетч создания линейного индикатора с использованием чарлиплексирования

*/

byte pins[] = {2,3,4};

const int NUMBER_OF_PINS = sizeof(pins)/ sizeof(pins[0]);

const int NUMBER_OF_LEDS = NUMBER_OF_PINS * (NUMBER_OF_PINS-1);

byte pairs[NUMBER_OF_LEDS/2][2] = { {2,1}, {1,0}, {2,0} }; // Сопоставляем

// контакты светодиодам

int ledStates = 0; // Переменная для хранения состояний для 15 светодиодов

int refreshedLed; // Светодиод, состояние которого обновляется

void setup()

{

// Ничего не делаем

}

void loop()

{

const int analogInPin = 0; // Входной аналоговый контакт

// для подключения потенциометра

// Код из решения для создания линейного индикатора

int sensorValue = analogRead(analogInPin); // Считываем входной

// аналоговый сигнал

// Сопоставляем полученное значение количеству светодиодов

int ledLevel = map(sensorValue, 0, 1023, 0, NUMBER_OF_LEDS);

for (int led = 0; led < NUMBER_OF_LEDS; led++)

{

if (led < ledLevel )

{

setState(led, HIGH); // Включаем светодиоды ниже полученного уровня

}

else

{

setState(led, LOW); // Выключаем светодиоды выше уровня

}

}

ledRefresh();

}

void setState( int led, bool state)

{

bitWrite(ledStates,led, state);

}

void ledRefresh()

{

// При каждом вызове обновляем состояние другого светодиода

if( refreshedLed++ > NUMBER_OF_LEDS) // Переходим к следующему светодиоду

refreshedLed = 0; // Повторяем, начиная с первого светодиода,

// если все были обновлены

if( bitRead(ledStates, refreshedLed ) == HIGH)

lightLed( refreshedLed );

else

if(refreshedLed == 0) // Выключаем все светодиоды, если контакт 0 выключен

for(int i=0; i < NUMBER_OF_PINS; i++)

digitalWrite(pins[i],LOW);

}

// Эта функция идентична функции из скетча решения

// Она включает требуемый светодиод. Нумерация светодиодов

// начинается с 0

void lightLed(int led)

{

// Следующие четыре строки кода преобразовывают номер светодиода

// в номера контактов

int indexA = pairs[led/2][0];

int indexB = pairs[led/2][1];

int pinA = pins[indexA];

int pinB = pins[indexB];

// Выключаем все контакты, не подключенные к заданному светодиоду

for(int i=0; i < NUMBER_OF_PINS; i++)

{

if(pins[i] != pinA && pins[i] != pinB)

{

// Если этот контакт не является одним из требуемых

pinMode(pins[i], INPUT); // Задаем для него входной режим работы

digitalWrite(pins[i],LOW); // Отключаем повышающий резистор

}

}

// Теперь включаем контакты для требуемого светодиода

pinMode(pinA, OUTPUT);

pinMode(pinB, OUTPUT);

if( led % 2 == 0)

{

digitalWrite(pinA,LOW);

digitalWrite(pinB,HIGH);

}

else

{

digitalWrite(pinB,LOW);

digitalWrite(pinA,HIGH);

}

}

В скетче используются значения битов переменной ledStates для представления состояния светодиодов (0 — выключен, 1 — включен). Функция refresh() проверяет значение каждого бита и включает светодиоды для битов со значением 1. Вызов функции должен осуществляться быстро и постоянно, чтобы избежать мигания светодиодов.

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

Вместо явного вызова функции refresh() для обновления состояния светодиодов ее можно вызывать посредством прерывания. Прерывания по таймеру подробно рассматриваются в главе 18, но в листинге 7.15 приводится пример одного способа использования прерывания для обновления состояния светодиодов. Для создания прерываний в скетче задействована библиотека FrequencyTimer2, устанавливаемая с помощью Менеджера библиотек (установка библиотек сторонних разработчиков подробно рассматривается в разд. 16.2).

Листинг 7.15. Обновление состояния светодиодов с использованием прерывания

#include <FrequencyTimer2.h> // Библиотека для создания прерывания

byte pins[] = {2,3,4};

const int NUMBER_OF_PINS = sizeof(pins)/ sizeof(pins[0]);

const int NUMBER_OF_LEDS = NUMBER_OF_PINS * (NUMBER_OF_PINS-1);

byte pairs[NUMBER_OF_LEDS/2][2] = { {2,1}, {1,0}, {2,0} };

int ledStates = 0; //Переменная для хранения состояний для 15 светодиодов

int refreshedLed; // Светодиод, состояние которого обновляется

void setup()

{

FrequencyTimer2::setPeriod(20000/NUMBER_OF_LEDS); // Задаем период

// Следующая строка кода указывает объекту FrequencyTimer2,

// какую функцию нужно вызывать (ledRefresh)

FrequencyTimer2::setOnOverflow(ledRefresh);

FrequencyTimer2::enable();

}

void loop()

{

const int analogInPin = 0; // Входной аналоговый контакт

// для подключения потенциометра

// Код из решения для создания линейного индикатора

int sensorValue = analogRead(analogInPin); // Считываем входной аналоговый сигнал

// Сопоставляем полученное значение количеству светодиодов

int ledLevel = map(sensorValue, 0, 1023, 0, NUMBER_OF_LEDS);

for (int led = 0; led < NUMBER_OF_LEDS; led++)

{

if (led < ledLevel )

{

setState(led, HIGH); // Включаем светодиоды ниже полученного уровня

}

else

{

setState(led, LOW); // Выключаем светодиоды выше уровня

}

}

// Состояние светодиода обновляется теперь не в цикле loop(),

// а при обработке прерывания, создаваемого FrequencyTimer2

}

// Остальной код такой же, что и в предыдущем примере

Объект FrequencyTimer2 устанавливает период прерывания величиной 1666 микросекунд (20 мс, разделенное на количество светодиодов, равное 12). Затем методу FrequencyTimer2::setOnOverflow указывается функция (ledRefresh), которую нужно вызывать при каждом активировании таймера. Библиотека FrequencyTimer2 совместима с ограниченным количеством плат: Arduino Uno (и, скорее всего, с большинством плат с микроконтроллером ATmega328), Arduino Mega и с несколькими версиями платы Teensy. Более подробная информация по этой библиотеке предлагается на веб-сайте PJRC (https://oreil.ly/e-KTE).

Дополнительная информация

Прерывания по таймеру более подробно рассматриваются в главе 18.

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