Анимация LED матрицы - Постепенное включение всех светодиодов матрицы в случайном порядке

the_matrix
the_matrix аватар
Offline
Зарегистрирован: 06.03.2018

Добрый день всем.

Такой вопрос возник: есть у меня матрица 16х32. И вот встал вопрос: как так сделать анимацию на ней, чтобы в случайном порядке загорались светодиоды, но так, чтобы в окончании выполнения горели они все?

Просто моргать светодиодами через random не проблема. Не могу понять, кк сделать так, чтобы выполнялось сразу две функции: включение случайного светодиода и проверка матрицы на то, сколько уже горит и сколько нет, чтобы random дважды не обращался к одному и тому же светодиоду, а плавно зажёг все.

Может, кто сталкивался с чем-то подобным? 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Ну, есть 100500 способов, например, очень простой, но довольно дорогой по ресурсам. Всего у Вас 512 светодиодов. Пронумеруйте их от 0 до 511,  заведите массив leds[512], заполните его числами от 0 до 511 (в смысле, что leds[i] = i), заведите некую переменную N изначально равную 511.

Далее в цикле ровно 512 раз делаете следующее:

1. Генерируете псевдослучайное число R от 0 до N включительно
2. Зажигаете светодиод номер leds[R]
3. Присваиваете leds[R] = leds[N]
4. Уменьшаете N на 1

После, после 512 проходов - все горят. Зажигались в случайном порядке.

Если нет свободного килобайта - можно по-другому, но ... ограничения на память заданы не были, так что чего пальцы-то бить?

qwone
qwone аватар
Offline
Зарегистрирован: 03.07.2016

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

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

qwone пишет:

зажигай. А если уже зажжена, так следующую соседнюю по ходу не зажженую.

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

sadman41
Offline
Зарегистрирован: 19.10.2016

Так можно же перемешать сначала по random() массив на 512 элементов, а потом пробежаться по нему... Ну, понятное дело, класс написать с методами для перемешивания и для поджигания очередного светодиода. И чтобы метод, стало быть, возвращал количество незадействованных элементов...

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Всё просто: 64 байта - на флаги - горит светодиод или нет. Плюс - random. Примерно вот так:

uint8_t flags[64] = {0};

bool isDone()
{
	const size_t cnt = sizeof(flags)/sizeof(flags[0]);
	for(size_t i=0;i<cnt;i++)
		if(flags[i] != 0xFF)
			return false;
		
	return true;
}

void ledOn(uint16_t ledNumber)
{
	uint8_t byteNum = ledNumber/8;
	uint8_t bitNum = ledNumber%8;
	flags[byteNum] |=  (1 << bitNum);
	
	digitalWrite(...);
}

void loop()
{
	while(!isDone())
	{
		uint16_t ledNumber = random(...);
		ledOn(ledNumber);
		delay(...);
	}
}

Дописать функцию проверки на то - горит светодиод или нет - элементарно ;)

 

the_matrix
the_matrix аватар
Offline
Зарегистрирован: 06.03.2018

ЕвгенийП пишет:

Ну, есть 100500 способов, например, очень простой, но довольно дорогой по ресурсам. Всего у Вас 512 светодиодов. Пронумеруйте их от 0 до 511,  заведите массив leds[512], заполните его числами от 0 до 511 (в смысле, что leds[i] = i), заведите некую переменную N изначально равную 511.

Далее в цикле ровно 512 раз делаете следующее:

1. Генерируете псевдослучайное число R от 0 до N включительно
2. Зажигаете светодиод номер leds[R]
3. Присваиваете leds[R] = leds[N]
4. Уменьшаете N на 1

После, после 512 проходов - все горят. Зажигались в случайном порядке.

Если нет свободного килобайта - можно по-другому, но ... ограничения на память заданы не были, так что чего пальцы-то бить?

 

 

 

 

 

 

 

Матрица вот так выглядит:

Задачка на Меге выполняется, потому памяти вроде не совсем уж мало, но хотелось бы оптимального решения, а не в лоб. Да и в случае необходимости, оптимальное решение может быть масштабируемо.

Пользуюсь библиотекой RGBmatrixPanel, у неё есть функция drawPixel(x,y). То есть каждый пиксель(светодиод) имеет свою координату. И вот как создать таблицу координат, чтобы после каждого прохода через random значение это (из random'а) вычиталось из таблицы и так пока не останется ни единого элемента в массиве.

Или как-то можно проще это исполнить?

 

sadman41
Offline
Зарегистрирован: 19.10.2016

Подсказываю: нужны две операции - деление и взятие остатка.

the_matrix
the_matrix аватар
Offline
Зарегистрирован: 06.03.2018

sadman41 пишет:

Подсказываю: нужны две операции - деление и взятие остатка.

Будьте добры, подскажите, пожалуйста, чуть подробнее.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

the_matrix пишет:

sadman41 пишет:

Подсказываю: нужны две операции - деление и взятие остатка.

Будьте добры, подскажите, пожалуйста, чуть подробнее.

Если у Вас есть номера от 0 до 511, то поделите номер на длину строки - получите номер строки, а остаток от деления номера на длину строки - номер столбца. Вы в каком классе учитесь? В в шестом это уже, вроде, знают.

Но если хотите по уму, то забудьте про то, что я писал, а внимательнее отнеситесь к посту DIYMan.

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

the_matrix пишет:

Матрица вот так выглядит:

Если надо не просто ВСЕ 512 светодиодов зажигать, а какие-нибудь фигуры, при этом знать, зажжены ли все - то надо будет ещё один массив - эталонный, итого - два массива по 64 байта (см. мой пример выше). Перед пуском заполняем эталонный массив, где биты установлены в тех позициях, какие светодиоды надо зажечь. При проверке, завершено ли зажигание - сравниваем не с 0xFF, а с байтом из эталонного массива.

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

the_matrix пишет:

sadman41 пишет:

Подсказываю: нужны две операции - деление и взятие остатка.

Будьте добры, подскажите, пожалуйста, чуть подробнее.

В моём примере выше - это операторы "/" и "%", соответственно. Что именно в примере непонятно?

the_matrix
the_matrix аватар
Offline
Зарегистрирован: 06.03.2018

Всё в кучу ))

Про деление и взятие остатка эт я понимаю.

Не очень понятно, как включать светодиоды, если они включаются не digitalWrite'ом, а командой matrix.drawPixel(x,y,Color) и надо указывать 2 координаты, которые могут повторяться сами по себе, но не должны повторяться вместе.

Пример: светодиоды по адресу 44х1 и 44х5.

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

the_matrix пишет:

Пользуюсь библиотекой RGBmatrixPanel, у неё есть функция drawPixel(x,y). То есть каждый пиксель(светодиод) имеет свою координату. 

Допустим, у нас матрица размером 16 строк х 32 диода в строке. И нам надо найти, в какой строке и каком столбце находится светодиод номер 200. Тогда:

200/32 = 6-я строка
200%32 = 8-й столбец

Проверка: поскольку у нас 32 диода в строке, то, чтобы не превысить число 200, целых строк до 200 поместится 6, т.е. 32*6 = 192. Остаётся 8, т.е. - в восьмом столбце нужный светодиод.

Простая арифметика. Нумерация строк/столбцов - ессно, с нуля.

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

the_matrix пишет:

Всё в кучу ))

Про деление и взятие остатка эт я понимаю.

Не очень понятно, как включать светодиоды, если они включаются не digitalWrite'ом, а командой matrix.drawPixel(x,y,Color) и надо указывать 2 координаты, которые могут повторяться сами по себе, но не должны повторяться вместе.

Пример: светодиоды по адресу 44х1 и 44х5.

А в чём проблема-то? Рассматривайте светодиоды в матрице при помощи СКВОЗНОЙ нумерации, слева-направо и сверху вниз. Первый светодиод в первой строке - первый (кэп!), последний светодиод в последней строке - последний (снова кэп!). Нумеруются по порядку. Как высчитать строки и столбцы из тупого номера светодиода - написал выше.

Это вы понимаете?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

the_matrix пишет:

Про деление и взятие остатка эт я понимаю.

Судя по следующей фразе

the_matrix пишет:

Не очень понятно, как включать светодиоды, если они включаются не digitalWrite'ом, а командой matrix.drawPixel(x,y,Color) и надо указывать 2 координаты, которые могут повторяться сами по себе, но не должны повторяться вместе.

Ни хрена на самом деле не понимаете.

Если Вы пронумерованли все светодиоды от 0 до 511, и номер уникален - как ему может соответствовать неуникальная пара строка/столбец? ну, как? 

DIYMan, готовтесь писать всё, до последней запятой. А то развели тут халтуру и отлынивание - "Дописать функцию ... элементарно" :)))

 

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

ЕвгенийП пишет:

DIYMan, готовтесь писать всё, до последней запятой. А то развели тут халтуру и отлынивание - "Дописать функцию ... элементарно" :)))

Яволь! :)

the_matrix
the_matrix аватар
Offline
Зарегистрирован: 06.03.2018

DIYMan пишет:

ЕвгенийП пишет:

DIYMan, готовтесь писать всё, до последней запятой. А то развели тут халтуру и отлынивание - "Дописать функцию ... элементарно" :)))

Яволь! :)

Всё же, прощу Вас внести некоторые вспомогательные комментарии к Вашему коду выше.

#include <RGBmatrixPanel.h>

#define CLK 11
#define OE   9
#define LAT 10
#define A   A0
#define B   A1
#define C   A2

RGBmatrixPanel  matrix(A, B, C, CLK, LAT, OE, false);

const uint16_t  x = 32;
const uint16_t  y = 16;
const uint16_t  max_pixels = x * y;
const uint16_t  Color = matrix.Color333(7, 0, 0);
uint16_t        flags[(max_pixels / 8)] = {0};

void setup() {
  matrix.begin();
}

void loop() {
  matrix.fillScreen(matrix.Color333(0, 0, 0));
  while (!isDone()) {
    uint16_t ledNumber = random(max_pixels);
    ledOn(ledNumber);
    delay(5);
  }
}

void ledOn(uint16_t ledNumber) {
  uint16_t byteNum = ledNumber / x;
  uint16_t bitNum = ledNumber % y;
  flags[byteNum] |=  (1 << bitNum);
  matrix.drawPixel(byteNum, bitNum, Color);
}

bool isDone() {
  const uint16_t cnt = sizeof(flags) / sizeof(flags[0]);
  for (uint16_t i = 0; i < cnt; i++) {
    if (flags[i] != 0xFF) {return false;}
    else                  {return true;}
  }
}

Вот Ваш код в моём. Или наоборот, кто как скажет.

Так и не понял, как мне вычёркивать уже включенные светодиоды.

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

the_matrix пишет:

Вот Ваш код в моём.

Нет, это не мой код. Вы его испортили, т.к. в моём коде return true было ВНЕ цикла for. Копируйте правильно.

the_matrix пишет:

Так и не понял, как мне вычёркивать уже включенные светодиоды.

Флаг включения светодиода устанавливается в функции ledOn. Проверить светодиод на включение - просто, достаточно проанализировать нужный бит в нужном байте массива. Смотрите код, там всё прозрачно.

Я приведу ещё один пример, в котором код отрабатывает только после того, если светодиод включился (т.е. не был включен ранее):

uint8_t flags[64] = {0};

bool isDone()
{
	const size_t cnt = sizeof(flags)/sizeof(flags[0]);
	for(size_t i=0;i<cnt;i++)
		if(flags[i] != 0xFF)
			return false;
		
	return true;
}

bool ledOn(uint16_t ledNumber)
{
	uint8_t byteNum = ledNumber/8;
	uint8_t bitNum = ledNumber%8;
    bool result = !(flags[byteNum] & (1 << bitNum));
	flags[byteNum] |=  (1 << bitNum);
	
	if(result) { digitalWrite(...); }
    return result;
}

void loop()
{
	while(!isDone())
	{
		uint16_t ledNumber = random(...);
		if(ledOn(ledNumber))
        {
		  delay(...);
        }
	}
}

delay будет срабатывать только после того, как светодиод включится. Повторные включения ранее включенного светодиода - производиться не будут (т.е. не отработает digitalWrite в примере).

the_matrix
the_matrix аватар
Offline
Зарегистрирован: 06.03.2018

Не очень ясно, что должно присвоиться в 17 строке.

result у меня всё время равен false, хотя сами переменные byte и bit постоянно меняются.

#include <RGBmatrixPanel.h>

#define CLK 11
#define OE   9
#define LAT 10
#define A   A0
#define B   A1
#define C   A2

RGBmatrixPanel  matrix(A, B, C, CLK, LAT, OE, false);

const uint16_t  x = 32;
const uint16_t  y = 16;
const uint16_t  max_pixels = x * y;
const uint16_t  Color = matrix.Color333(7, 0, 0);
uint16_t        flags[(max_pixels / 8)] = {0};

void setup() {
  Serial.begin(9600);
  matrix.begin();
}

void loop() {
  //matrix.fillScreen(matrix.Color333(0, 0, 0));
  while (!isDone()) {
    uint16_t ledNumber = random(max_pixels);
    if(ledOn(ledNumber)) delay(5);
  }
}

bool ledOn(uint16_t ledNumber) {
  uint16_t byteNum = ledNumber / x;
  uint16_t bitNum = ledNumber % x;
  Serial.print("X = ");     Serial.println(byteNum);
  Serial.print("Y = ");     Serial.println(bitNum);
  flags[byteNum] |=  (1 << bitNum);
  bool result = !(flags[byteNum] & (1 <<bitNum));
  Serial.print("Result = ");  Serial.println(result);
  if(result) {matrix.drawPixel(byteNum, bitNum, Color); Serial.println("RESULT OK");}
  return result;
}

bool isDone() {
  const uint16_t cnt = sizeof(flags) / sizeof(flags[0]);
  for (uint16_t i = 0; i < cnt; i++) if (flags[i] != 0xFF) {return false;}
  return true;
}

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

Потому что у вас код неправильный. Почему вы делите на X, а остаток от деления делаете на Y? Арифметику расчёта - я приводил, неужели так сложно понять, что там фигурирует ОДНО число НОМЕРА светодиода, и КОНСТАНТА 8? Зачем вы туда впихиваете ваши x и y ????????

Похоже, вы не понимаете простейших вещей, к сожалению.

the_matrix
the_matrix аватар
Offline
Зарегистрирован: 06.03.2018

Про х и у увидел, да, уже изменил, спасибо. Работать матрица начала, но есть один нюанс:

рандом выдаёт числа и одни и те же и новые. Повторные чаще. Из-за этого время заполнения матрицы светодиодами весьма велико.

Подскажите, пожалуйста, как при каждом проходе можно уменьшать список светодиодов, чтобы он рандомно выдавал каждый раз новое значение, без повторов?

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

the_matrix пишет:

Про х и у увидел, да, уже изменил, спасибо. Работать матрица начала, но есть один нюанс:

рандом выдаёт числа и одни и те же и новые. Повторные чаще. Из-за этого время заполнения матрицы светодиодами весьма велико.

Подскажите, пожалуйста, как при каждом проходе можно уменьшать список светодиодов, чтобы он рандомно выдавал каждый раз новое значение, без повторов?

В примере, что я приводил - какие-то действия (например, задержка между зажиганиями) происходит только после того, как ledOn вернёт true, что, в свою очередь, значит, что на вход ledOn поступило новое число, сгенерированное random (т.е. число, которого до этого не было).

Всё остальное: проблема равномерного распределения псевдослучайных чисел. Мой подход - просто демонстрация того, как можно обойтись меньшим кол-вом оперативки, НЕ генеря все числа при старте. Если генерить все числа при старте, то вам понадобится

512*2 = 1024 байта

массив данных. Тогда код будет выглядеть примерно так:

const uint8_t cols = 32;
const  uint8_t rows = 16;
uint16_t numbers[cols*rows] = {0};

void init()
{
	for(uint16_t i=0;i<cols*rows;)
	{
		uint16_t num = random(0,cols*rows);
		bool exists = false;
		for(uint16_t j=0;j<cols*rows;j++)
		{
			if(numbers[j] == num)
			{
				exists = true;
				break;
			}
		}
		if(!exists)
		{
			numbers[i] = num;
			i++;
		}
	}
}

void setup()
{
	init();
}

void loop()
{
	for(uint16_t i=0;i<cols*rows;i++)
	{
		uint8_t y = i/cols;
		uint8_t y = i%cols;
		
		matrix.setPixel(x,y); // или что у вас там для загорания диода в матрице...
		delay(200);
	}
}

Недостаток - время инициализации, плюс - огромный расход оперативки. Можно вместо оперативки писать куда-нибудь, например, в EEPROM. И после инициализации массива случайных чисел - вычитывать уже оттуда.

Короче, вариантов генерирования - много, предложенный в куске кода выше - алгоритмически самый позорный, к слову. Лучшим вариантом было бы применить тот же set или unordered_set, а после - random_shuffle - эта связка алгоритмически будет работать на порядок быстрее, пмсм.

З.Ы. set и unordered_set - контейнеры STL, порт которого есть и под дуню. random_shuffle - алгоритм из STL.

the_matrix
the_matrix аватар
Offline
Зарегистрирован: 06.03.2018

DIYMan пишет:

the_matrix пишет:

Про х и у увидел, да, уже изменил, спасибо. Работать матрица начала, но есть один нюанс:

рандом выдаёт числа и одни и те же и новые. Повторные чаще. Из-за этого время заполнения матрицы светодиодами весьма велико.

Подскажите, пожалуйста, как при каждом проходе можно уменьшать список светодиодов, чтобы он рандомно выдавал каждый раз новое значение, без повторов?

В примере, что я приводил - какие-то действия (например, задержка между зажиганиями) происходит только после того, как ledOn вернёт true, что, в свою очередь, значит, что на вход ledOn поступило новое число, сгенерированное random (т.е. число, которого до этого не было).

Всё остальное: проблема равномерного распределения псевдослучайных чисел. Мой подход - просто демонстрация того, как можно обойтись меньшим кол-вом оперативки, НЕ генеря все числа при старте. Если генерить все числа при старте, то вам понадобится

512*2 = 1024 байта

массив данных. Тогда код будет выглядеть примерно так:

const uint8_t cols = 32;
const  uint8_t rows = 16;
uint16_t numbers[cols*rows] = {0};

void init()
{
	for(uint16_t i=0;i<cols*rows;)
	{
		uint16_t num = random(0,cols*rows);
		bool exists = false;
		for(uint16_t j=0;j<cols*rows;j++)
		{
			if(numbers[j] == num)
			{
				exists = true;
				break;
			}
		}
		if(!exists)
		{
			numbers[i] = num;
			i++;
		}
	}
}

void setup()
{
	init();
}

void loop()
{
	for(uint16_t i=0;i<cols*rows;i++)
	{
		uint8_t y = i/cols;
		uint8_t y = i%cols;
		
		matrix.setPixel(x,y); // или что у вас там для загорания диода в матрице...
		delay(200);
	}
}

Недостаток - время инициализации, плюс - огромный расход оперативки. Можно вместо оперативки писать куда-нибудь, например, в EEPROM. И после инициализации массива случайных чисел - вычитывать уже оттуда.

Короче, вариантов генерирования - много, предложенный в куске кода выше - алгоритмически самый позорный, к слову. Лучшим вариантом было бы применить тот же set или unordered_set, а после - random_shuffle - эта связка алгоритмически будет работать на порядок быстрее, пмсм.

З.Ы. set и unordered_set - контейнеры STL, порт которого есть и под дуню. random_shuffle - алгоритм из STL.

Огромное спасибо!

DIYMan
DIYMan аватар
Offline
Зарегистрирован: 23.11.2015

the_matrix пишет:

Огромное спасибо!

Да незчт. Только вы это - всё-таки не копируйте бездумно, а попытайтесь понять, что делается и, главное - как это делается. Только так можно чуть продвинуться в программировании, пмсм ;)

mykaida
mykaida аватар
Offline
Зарегистрирован: 12.07.2018

Возможен и следующий подход - заполнить массив последовательными числами от 1 до cols*rows, массив хорошо перемешать, меняя между собой случайно выбранные элементы вот этим алгоритмом.

the_matrix
the_matrix аватар
Offline
Зарегистрирован: 06.03.2018

DIYMan пишет:

the_matrix пишет:

Огромное спасибо!

Да незчт. Только вы это - всё-таки не копируйте бездумно, а попытайтесь понять, что делается и, главное - как это делается. Только так можно чуть продвинуться в программировании, пмсм ;)

Это я понимаю, спасибо. Этим, собственно, сегодня и займусь.

the_matrix
the_matrix аватар
Offline
Зарегистрирован: 06.03.2018

mykaida пишет:

Возможен и следующий подход - заполнить массив последовательными числами от 1 до cols*rows, массив хорошо перемешать, меняя между собой случайно выбранные элементы вот этим алгоритмом.

Только вчера вечером эту статью читал. Для начала разберусь с вариантом DIYMan'а, а дальше посмотрим.