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

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

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

ЗАДАЧА

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

РЕШЕНИЕ

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/*
 
* Скетч 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. Создание линейного индикатора с помощью модифицированного чарлиплексирования

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/* Скетч создания линейного индикатора с использованием чарлиплексирования
 
*/
 
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. Обновление состояния светодиодов с использованием прерывания

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#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.

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