Ёлка новогодняя из светодиодиков. (чарлиплексинг)
Добавлено: Пн ноя 23, 2020 18:44:44
Для обсуждения статьи https://radiokot.ru/artfiles/6598/
Добавлено after 5 hours 39 minutes 10 seconds:
По просьбам трудящихся, пример очень простой программы для сей ёлки. В стиле для начинающих постараюсь, поэтому профи будет скучновато, но я всё же прошу по возможности прочитать с целью критики-исправлений-дополнений, если есть время, за что заранее благодарю.
Данная программа очень проста - предполагает свечение лишь одного светодиода в единицу нормального времени. Под нормальным временем подразумевается время не менее 1/10 секунды. Никаких комбинаций с одновременным свечением нескольких светодиодов напрямую или же через быстрое попеременное вкл-выкл (то есть, динамическая индикация) здесь нету.
Итак, предположим, что у нас некий абстрактный микроконтроллер, но на котором почему-то написано PIC18, что мистическим образом накладет лапу на синтаксис и радует тех, кто решил использовать именно такой мк. И есть спаянная ёлка.
Будем делать эффекты. Первое, что обычно всегда приходит мне в голову, так это написать кучу состояний, неких "кадров", которые затем прокрутить. Разумеется, даже примерный подсчёт показывает, что это громадный и неэффективный объём данных, не зря же в свое время появилась gif-анимация и различные виды сжатия видео, где в каждом следующем кадре учитываются лишь изменения относительно предыдущего кадра, поэтому часовой видеофильм, показывающий неподвижную картину "Рождение Домомучительницы" будет по размеру несильно отличаться от подобного же, но идущего всего минуту: нет изменений - нет данных.
Вторым приходит в голову - математика. Да, кучу светоэффектов можно описать математикой. Это очень компактно, удобно, но... не знаю, как кто, а я с трудом помню, как вычисляется дискриминант квадратного уравнения, и есть вполне оправданные сомнения, что даже если верно его помню, то не помню что с ним делать дальше.
Поэтому всё максимально упростим, и поверьте, ваши родные и друзья придут в восторг от перемигивающихся зеленых огоньков и никто не догадается, как ужасно всё внутри.
Мы просто создадим массивы, где каждый элемент будет соответствовать состоянию светодиода, и будем считать это "кадром" нашей анимации.
В моей ёлочке 56 светодиодов, а значит будет 56 "кадров", и 10 "стволов", т.е. выводов МК.
Глядя на плату уже сделанной ёлки, вижу, что под это дело был отдан весь порт C (C0..C7) и две лапы порта А (А4, А5)
Ну, так и напишем:
Думаю, ни у кого не возникнет вопрос: - А что тут делает TRIS?
А вот у меня возник. Нам же нужно зажигать светодиоды, и всё. То есть, нужны лишь выходы. Инициализируем TRIS в режим выхода, и забываем о нём, зачем его-то в массив?
И я так и сделал. Все порты на выход, всем состояние "0". Прекрасно, ничего не светится. И пытаемся зажечь наш первый светодиод, подавая "1" на, например, "ствол" номер 0:

Как видите, в итоге получается, что у светодиодов D10 и D1 на анодах логическая "1", то есть +V, на катодах "0", то есть -V. Ничто не мешает светиться обоим сразу, что они радостно и делают.
(небольшое отвлечение для совсем юных радиолюбителей: анод и катод на символе диода легко запоминается по формуле "Аткуда Куда", сам же символ стрелкообразной формой хорошо показывает "аткуда" и "куда")
Что же делать? Не нужон нам второй светодиод, он паразитный.
Чтобы паразитный диод погасить, на его катоде не должен быть "0".
-Идея!, - воскликнет кто-нибудь. - Давайте туда подадим "1", ведь следующий диод включен наоборот, он не будет светиться.
Давайте. И обнаруживаем, что совершенно верно, ненужный светодиод больше не светится. Зато теперь светится другой, в другом ярусе...
Выходит, что в одном кадре должны быть только один "0" и одна "1". Что же тогда будет на других выходах? А ничего. И это состояние называется "Z-состояние", или "Высокоимпедансное"(высокоомное), на микроконтроллере его можно получить, переведя порт в режим входа с отключением подтягивающих резисторов (если они есть).
Отсюда и необходимость в массиве TRIS: каждый наш "кадр" мы будем чётко указывать пару выводов микроконтроллера, настроенных на выход.
Дальше самое скучное: смотрим на ёлку, определяем, какие лапы мк для какого светодиода определить на выход, и записываем в массив. У меня получилось так:
Можно заметить, что ярусность дублируется - строки одинаковые. Жаль, что порт 8-битный... тогда хватило бы одного массива. На самом деле, leds_trisa4 и leds_trisa5 можно объединить в один, но для простоты дальнейшего доступа к выводу порта я оставлю так, да и как пример того, что может произойти, если выводы берутся из разных портов.
Ну вот, 50% работы сделано - обеспечили возможность свечения только выбранного светодиода. Теперь это и сделаем:
Ура! Теперь пишем "проектор" нашего "кино":
Ну вот, присвоив i какое-то значение от 0 до 55 мы получим какой-то "кадр".
Пора делать эффекты!
Думаю, никакого труда не составляет сделать "бегущий огонёк" - просто пишем i++, и когда i станет больше 55 - снова его обнуляем. Ну и добавим возможность хаотичного зажигания и изменения скорости. Смешаем это в кучу, и позволим воле случая выбирать:
Вот и всё. Ничего сложного и масса возможностей для оптимизации (например, очень легко выкинуть массивы *a4 и *a5, просто добавив несколько условий проверки i определенным значениям, кол-во которых существенно меньше элементов массива)
Добавлено after 7 minutes 3 seconds:
P. S.
Не забудьте про токоограничивающие резисторы.
Добавлено after 5 hours 39 minutes 10 seconds:
По просьбам трудящихся, пример очень простой программы для сей ёлки. В стиле для начинающих постараюсь, поэтому профи будет скучновато, но я всё же прошу по возможности прочитать с целью критики-исправлений-дополнений, если есть время, за что заранее благодарю.
Данная программа очень проста - предполагает свечение лишь одного светодиода в единицу нормального времени. Под нормальным временем подразумевается время не менее 1/10 секунды. Никаких комбинаций с одновременным свечением нескольких светодиодов напрямую или же через быстрое попеременное вкл-выкл (то есть, динамическая индикация) здесь нету.
Итак, предположим, что у нас некий абстрактный микроконтроллер, но на котором почему-то написано PIC18, что мистическим образом накладет лапу на синтаксис и радует тех, кто решил использовать именно такой мк. И есть спаянная ёлка.
Будем делать эффекты. Первое, что обычно всегда приходит мне в голову, так это написать кучу состояний, неких "кадров", которые затем прокрутить. Разумеется, даже примерный подсчёт показывает, что это громадный и неэффективный объём данных, не зря же в свое время появилась gif-анимация и различные виды сжатия видео, где в каждом следующем кадре учитываются лишь изменения относительно предыдущего кадра, поэтому часовой видеофильм, показывающий неподвижную картину "Рождение Домомучительницы" будет по размеру несильно отличаться от подобного же, но идущего всего минуту: нет изменений - нет данных.
Вторым приходит в голову - математика. Да, кучу светоэффектов можно описать математикой. Это очень компактно, удобно, но... не знаю, как кто, а я с трудом помню, как вычисляется дискриминант квадратного уравнения, и есть вполне оправданные сомнения, что даже если верно его помню, то не помню что с ним делать дальше.
Поэтому всё максимально упростим, и поверьте, ваши родные и друзья придут в восторг от перемигивающихся зеленых огоньков и никто не догадается, как ужасно всё внутри.
Мы просто создадим массивы, где каждый элемент будет соответствовать состоянию светодиода, и будем считать это "кадром" нашей анимации.
В моей ёлочке 56 светодиодов, а значит будет 56 "кадров", и 10 "стволов", т.е. выводов МК.
Глядя на плату уже сделанной ёлки, вижу, что под это дело был отдан весь порт C (C0..C7) и две лапы порта А (А4, А5)
Ну, так и напишем:
Код: Выделить всё
#include <xc.h>
#include "main.h"
unsigned char leds_trisc[56] ={};
unsigned char leds_trisa4[56] ={};
unsigned char leds_trisa5[56] ={};
unsigned char leds_latc[56] ={};
unsigned char leds_lata4[56] ={};
unsigned char leds_lata5[56] ={};А вот у меня возник. Нам же нужно зажигать светодиоды, и всё. То есть, нужны лишь выходы. Инициализируем TRIS в режим выхода, и забываем о нём, зачем его-то в массив?
И я так и сделал. Все порты на выход, всем состояние "0". Прекрасно, ничего не светится. И пытаемся зажечь наш первый светодиод, подавая "1" на, например, "ствол" номер 0:

Как видите, в итоге получается, что у светодиодов D10 и D1 на анодах логическая "1", то есть +V, на катодах "0", то есть -V. Ничто не мешает светиться обоим сразу, что они радостно и делают.
(небольшое отвлечение для совсем юных радиолюбителей: анод и катод на символе диода легко запоминается по формуле "Аткуда Куда", сам же символ стрелкообразной формой хорошо показывает "аткуда" и "куда")
Что же делать? Не нужон нам второй светодиод, он паразитный.
Чтобы паразитный диод погасить, на его катоде не должен быть "0".
-Идея!, - воскликнет кто-нибудь. - Давайте туда подадим "1", ведь следующий диод включен наоборот, он не будет светиться.
Давайте. И обнаруживаем, что совершенно верно, ненужный светодиод больше не светится. Зато теперь светится другой, в другом ярусе...
Выходит, что в одном кадре должны быть только один "0" и одна "1". Что же тогда будет на других выходах? А ничего. И это состояние называется "Z-состояние", или "Высокоимпедансное"(высокоомное), на микроконтроллере его можно получить, переведя порт в режим входа с отключением подтягивающих резисторов (если они есть).
Отсюда и необходимость в массиве TRIS: каждый наш "кадр" мы будем чётко указывать пару выводов микроконтроллера, настроенных на выход.
Дальше самое скучное: смотрим на ёлку, определяем, какие лапы мк для какого светодиода определить на выход, и записываем в массив. У меня получилось так:
Код: Выделить всё
unsigned char leds_trisc[56] ={ 0xfc,0xf9,0xf3,0xe7,0xcf,0x9f,0x3f,0x7f,0xff,0xfe, //1 ярус снизу
0xfc,0xf9,0xf3,0xe7,0xcf,0x9f,0x3f,0x7f,0xff,0xfe, //2 ярус
0xf7,0xf6,0xfa,0xdb,0xd7,0xb7,0xaf,0xef, //3 ярус
0xf7,0xf6,0xfa,0xdb,0xd7,0xb7,0xaf,0xef, //4 ярус
0xfb,0xbb,0xbf,0xf7,0x77,0x7f, //5 ярус
0xfb,0xbb,0xbf,0xf7,0x77,0x7f, //6ярус
0xfe,0xdf,0x5f,0x7e, //7 ярус
0xfe,0xdf,0x5f,0x7e}; //8 ярус
unsigned char leds_trisa4[56] ={0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,
0xff,0xff,0x00,0x00,0xff,0xff,
0xff,0xff,0x00,0x00,0xff,0xff,
0x00,0x00,0xff,0xff,
0x00,0x00,0xff,0xff};
unsigned char leds_trisa5[56] ={0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,
0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0x00,0x00,
0x00,0xff,0xff,0xff,0xff,0xff,0xff,0x00,
0x00,0xff,0xff,0xff,0xff,0xff,0xff,0x00,
0x00,0xff,0xff,0xff,0xff,0x00,
0x00,0xff,0xff,0xff,0xff,0x00,
0xff,0xff,0xff,0xff,
0xff,0xff,0xff,0xff};Можно заметить, что ярусность дублируется - строки одинаковые. Жаль, что порт 8-битный... тогда хватило бы одного массива. На самом деле, leds_trisa4 и leds_trisa5 можно объединить в один, но для простоты дальнейшего доступа к выводу порта я оставлю так, да и как пример того, что может произойти, если выводы берутся из разных портов.
Ну вот, 50% работы сделано - обеспечили возможность свечения только выбранного светодиода. Теперь это и сделаем:
Код: Выделить всё
unsigned char leds_latc[56] ={ 0x01,0x04,0x04,0x10,0x10,0x40,0x40,0x00,0x00,0x01,
0x02,0x02,0x08,0x08,0x20,0x20,0x80,0x80,0x00,0x00,
0x08,0x08,0x04,0x04,0x08,0x08,0x10,0x10,
0x00,0x01,0x01,0x20,0x20,0x40,0x40,0x00,
0x04,0x04,0x00,0x00,0x80,0x80,
0x00,0x40,0x40,0x08,0x08,0x00,
0x01,0x20,0x20,0x01,
0x00,0x00,0x80,0x80};
unsigned char leds_lata4[56] ={ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x01,0x01,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x01,0x01,0x00,0x00};
unsigned char leds_lata5[56] ={ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0x00,0x00,0x00,0x00,0x01,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00};Ура! Теперь пишем "проектор" нашего "кино":
Код: Выделить всё
int i = 0;
void main(void) {
// инициализируем таймер, который будет генерировать прерывания, для смены "кадров"
// что делает каждая строчка предлагаю разобраться самостоятельно по даташиту и/или учебнику
// задача: заставить таймер бесконечно работать и генерировать событие (прерывание)
T0CONbits.T08BIT = 0;
T0CONbits.T0CS = 0;
T0CONbits.PSA = 1;
TMR0H = 0;
TMR0L = 0;
INTCONbits.T0IF = 0;
INTCONbits.T0IE = 1;
INTCONbits.PEIE = 1;
INTCONbits.GIE = 1;
T0CONbits.TMR0ON = 1;
// бесконечная "кинолента"
while(1)
{
LATC = 0x00; // выключим все, чтобы во время дальнейших переключений не появились "артефакты"
LATA = 0x00;
TRISC = leds_trisc[i];
TRISAbits.TRISA4 = leds_trisa4[i];
TRISAbits.TRISA5 = leds_trisa5[i];
LATC = leds_latc[i];
LATAbits.LATA4 = leds_lata4[i];
LATAbits.LATA5 = leds_lata5[i];
}
}
Пора делать эффекты!
Думаю, никакого труда не составляет сделать "бегущий огонёк" - просто пишем i++, и когда i станет больше 55 - снова его обнуляем. Ну и добавим возможность хаотичного зажигания и изменения скорости. Смешаем это в кучу, и позволим воле случая выбирать:
Код: Выделить всё
unsigned long ce = 0; //счётчик, который считает, через сколько кадров сменить эффект
unsigned char effect = 1; // номер эффекта
unsigned char speed = 1; // скорость "киноленты"
//оработка прерывания таймера
__interrupt() void ISR(void) {
if (INTCONbits.TMR0IE && INTCONbits.T0IF) {
INTCONbits.T0IF = 0;
TMR0H = 100 + (speed * 10); // здесь изменяется интервал таймера, то есть "скорость киноленты"
TMR0L = 0;
if (++ce > 170){ // если прошло более 170 кадров - меняем эффект
effect = rand()%3; // случайным образом выбираем один из трёх эффектов
speed = rand()%11;// случайным образом выбираем одну из одиннадцати скоростей
ce = 0; // сбрасываем счётчик кадров
}
switch (effect) // определяем, какой сейчас эффект
{
case 0: // первый эффект: случайный выбор светодиода
i = rand()%56;
break;
case 1: // второй эффект: бегущий огонёк в одну сторону
if (++i > 55) i = 0;
break;
case 2: // третий эффект: бегущий огонёк в другую сторону
if (--i < 0) i = 55;
break;
}
}
}
Добавлено after 7 minutes 3 seconds:
P. S.
Не забудьте про токоограничивающие резисторы.
