Обзор светодиодной матрицы из GearBest.com

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

GearBest.com: светодиодная матрица

Давайте посмотрим на полноцветную (RGB) светодиодную матрицу, который продаётся в Интернет-магазине GearBest.com. Исследование образца проводилось по просьбе магазина.

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

1. Внешний вид

Выглядит матрица собственно как и должна выглядеть - никаких претензий. всё аккуратно и солидно.

5.jpg

8.jpg

2. Подключение

Матрица подключается через 32 пина, расположенные по 16 в ряд. Восемь пинов отвечают за строки матрицы, а остальные 24 - за столбцы. Столбцов-то вообще-то восемь, но поскольку это RGB светодиоды, каждый из них имеет три пина, отвечающие за красный, голубой и зелёный цвета соответсвенно. Таким образом можно сказать, что каждая строка состоит из 24 светодиодов.

Как же её подключать? Напрямую к ардуино-UNO (или Nano) она не подключится по банальной причине - у них просто нет столько ножек. Можно, конечно, взять Arduino-Mega, но это плохая идея. Одиночная такая матрица всё равно используется крайне редко, а если потребуется подключить две, то уже никакой Меги не хватит. Надо искать более систематический способ. С монохромными матрицами нас всегда выручала микросхема MAX7219, но здесь она нам не помошник. Во-первых, она не поддерживает индивидуальное управление яркостью каждого светодиода, что нам необходимо для смешивания цветов, а, во-вторых, она отлично работает с матрицами 8х8, но не с 8х24 как у нас. Городить три штуки? Больно дорого и громоздко (хотя и можно, конечно), да и управление яркостью всё равно ниоткуда не возьмётся. В принципе, есть много способов подключить эту матрицу. Я остановился на использовании двух микросхем TLC5940. Не то, чтобы это обусловлено какими-то высокими соображениями, просто они у меня были под рукой.

Микросхема TLC5940 - это 16-канальный драйвер светодиодов и, по совместительтву, ШИМ-контроллер. Две штуки дают нам 32 канала, на которых поддерживается постоянным ток и на которых можно организовать 12-разрядный ШИМ. Последнее явно избыточно, мы ограничимся 8-ю (что тоже избыточно, кстати).

Схема подключения проста и незамысловата:

Все пронумерованный цифрами термииналы должны быть соединены (по цифрам). Эти соединения не показаны, чтобы схема была хоть немного читабельной.

Синий разъём S1 справа - это разъём S1 следущей такой же платы, если их у Вас несколько.

Транзисторы подойдут любые PNP с током коллектора от 500 мА и коэффициентом усиления по току не менее 24. Я использовал 2N2907A просто потому, что они у меня были в нужном количестве.

На схеме отсутствует резистор 10к от пина Blank к питанию, который нужен, чтобы держать все выходы выключенными в момент инициализации устройства, чтобы избежать беспорядочного мигания при включении. Дело в том, что при каскадировании TLC5940, все пины Blank соединяются, а резистор нужен только один на всех. Поэтому он располагается не на этой плате (чтобы все платы были одинаковыми), а возле контроллера.

Именно эти пины были выбраны ислючительно из соображений удобства разводки печатной платы, чтобы поменьше ставить перемычек и вообще, чтобы она попроще была. А вот, кстати и печатная плата:

В результате получилась плата размером точно с матрицу. Матрица вставляется в неёё на два разъёма по 16 пин. Можно ставить радом несколько матриц, соединять их проводами с помощью разъемов, которые предусмотрены и получится единый (без щелей) экран из несколькх матриц. Вот как выглядит готовая плата.

 

3. Базовая библиотека

Основу нашей конструкции составляют две микросхемы TLC5940, поэтому, разумно было бы воспользоваться распространённой библиотекой для этой микросхемы. Но мы не будем этого делать. Дело в том, что эта библиотека заннимает два таймера, а это значит, что в нашем проекте с Ардуино на базе микроконтроллера ATMega328P нам пришлось бы остаться без таймеров вовсе, что неприятно. Поэтому, специально для данного исследования была написана мини-библиотека, которая использует только 8 бит управления яркостью и, за счёт этого, занимает только один таймер. Бибилотека написана именно для этого исследования и потому работает только с ATMega328P (Ардуино UNO, Nano и им подобные). С другими платами (Ардуино Мега, например), данная библиотека работать не будет.

На задней стенке матрицы нанесена маркировка «Hew2338RGB5» - это и будет названем библиотеки. Состоит она из двух файлов Hew2338RGB5.cpp и Hew2338RGB5.h.

Hew2338RGB5.h

#ifndef	HEW2338RGB5_H
#define	HEW2338RGB5_H

#include <arduino.h>

#define	TOTAL_LINES	8
#define	TOTAL_COLUMNS	8

#define	MAX_VALUE	255 // мы используем только 256 градация яркости цвета
#define	DATA_AMOUNT	48	// TLC5940 требует 24 байта. Мы используем 2 микросхемы, отсюда 48

class RGBColor {
public:
		RGBColor(const byte red = 0, const byte green = 0, const byte blue = 0) {
			m_red = red;
			m_green = green;
			m_blue = blue;
		}

	byte m_red;
	byte m_green;
	byte m_blue;
};

class RGBDiode : public RGBColor {
public:
	static byte gsData[DATA_AMOUNT * TOTAL_LINES];
	
	RGBDiode(const byte row, const byte column, const byte red = 0, const byte green = 0, const byte blue = 0) : RGBColor (red, green, blue) {
		m_dataBase = gsData + ((int)row) * DATA_AMOUNT;
		//
		//	КОНФИГУРАЦИЯ
		// 	bluePin - номер пина TLC5940 для голубого светодиода в колонке column
		// 	redPin - номер пина TLC5940 для красного светодиода в колонке column
		// 	greenPin - номер пина TLC5940 для зелёного светодиода в колонке column
		// при этом пины первой (от Ардуино) TLC5940 - 0-15, а пины второй - 16-31
		//
		const byte bluePin = column;
		const byte redPin = column + 8;
		const byte greenPin = 27 - column;
		//
		calculateBaseHalf(bluePin, m_blueBase, m_blueHalf);
		m_even.blue = (!(bluePin & 1));
		calculateBaseHalf(redPin, m_redBase, m_redHalf);
		m_even.red = (!(redPin & 1));
		calculateBaseHalf(greenPin, m_greenBase, m_greenHalf);
		m_even.green = (!(greenPin & 1));
		setRed(m_red);
		setGreen(m_green);
		setBlue(m_blue);
	}

	void setRed(const byte val) {
		setColor(val, m_red, m_even.red, m_redBase, m_redHalf);
	}

	void setGreen(const byte val) {
		setColor(val, m_green, m_even.green, m_greenBase, m_greenHalf);
	}

	void setBlue(const byte val) {
		setColor(val, m_blue, m_even.blue, m_blueBase, m_blueHalf);
	}

	void setRGB(const byte r, const byte g, const byte b) {
		setRed(r);
		setGreen(g);
		setBlue(b);
	}

	RGBDiode & operator = (const RGBColor & orig) {
		setRed(orig.m_red);
		setGreen(orig.m_green);
		setBlue(orig.m_blue);
		return *this;
	}

	void incRed(int d) {
		incColor(d, m_red, m_even.red, m_redBase, m_redHalf);
	}

	void incGreen(int d = 1) {
		incColor(d, m_green, m_even.green, m_greenBase, m_greenHalf);
	}

	void incBlue(int d = 1) {
		incColor(d, m_blue, m_even.blue, m_blueBase, m_blueHalf);
	}

	RGBDiode & Add(const RGBColor & colorToAdd, byte transparency = 127) {
		const unsigned antiTr = 256 - transparency;
		setRed(addColors(m_red, colorToAdd.m_red, transparency, antiTr));
		setGreen(addColors(m_green, colorToAdd.m_green, transparency, antiTr));
		setBlue(addColors(m_blue, colorToAdd.m_blue, transparency, antiTr));
		return *this;
	}

private:
	inline byte addColors(const unsigned  first, const unsigned  second, const unsigned  tr, const unsigned antitr) const {
		const unsigned newCol = first * antitr + second * tr;
		return newCol >> 8;
		//div_t dt = div(newCol, 255);
		//return (dt.rem > 127) ? dt.quot + 1 : dt.quot;
	}
	
	void incColor(int d, byte & col, const bool even, const byte base, const byte half) {
		d += col;
		if (d > 255) d = 255;
		if (d < 0) d = 0;
		setColor(d, col, even, base, half);
	}
	
	void setColor(const byte val, byte & col, const bool even, const byte base, const byte half) {
		if (col == val) return;
		col = val;
		if (even) writeEven(val, base, half);
		else writeOdd(val, base, half);
	}

	void calculateBaseHalf(const byte pin,  byte & base, byte & half) {
		if (!(pin & 1)) {	// чётный
			base = DATA_AMOUNT - 1 - (pin >> 1) * 3;
			half = base - 1;
		} else {	// нечётный
			base = DATA_AMOUNT - 3 - ((pin - 1) >> 1) * 3;
			half = base + 1;
		}
	}

	void writeOdd(const unsigned val, const byte base, const byte half) {
		const uint8_t mask = ((val & 0x000F) << 4);
		byte * pb = (byte *)m_dataBase + half;
		*pb = (*pb & 0x0F) | mask;
		m_dataBase[base] = ((val & 0x0FF0) >> 4);
	}
	
	void writeEven(const unsigned val, const byte base, const byte half) {
		const byte mask = ((val & 0x0F00) >> 8);
		byte * pb = (byte *)m_dataBase + half;
		*pb = (*pb & 0xF0) | mask;
		m_dataBase[base] = (val & 0x00FF);
	}
	
	byte m_redBase, m_redHalf, m_greenBase, m_greenHalf, m_blueBase, m_blueHalf;
	struct {
		unsigned red : 1;
		unsigned green : 1;
		unsigned blue : 1;
	} m_even;
	   byte *m_dataBase;
};

extern void activateMatrix(void);
extern void clearMatrix(const byte = 0);
extern RGBDiode screen[TOTAL_LINES][TOTAL_COLUMNS];

#endif	//	HEW2338RGB5_H

 

Hew2338RGB5.cpp

#include "Hew2338RGB5.h"

//
//	КОНФИГУРАЦИЯ (т.ж. см. слово "КОНФИГУРАЦИЯ" в конструкторе RGBDiode
//
// Строки сверху вниз от 0 до 7 - соотыетстыме пинам
//
#define	LINE_0	2
#define	LINE_1	4
#define	LINE_2	5
#define	LINE_3	6
#define	LINE_4	7
#define	LINE_5	8
#define	LINE_6	A0
#define	LINE_7	A1

// пин 12 (MISO) лучше не трогать от греха. В принципе его можно использовать
// как INPUT пин, но см. описание работы SPI в master mode в даташите

#define	_SIN_PIN		11	// MOSI pin Arduino - не менять, зарезервирован SPI
#define	_SCLK_PIN	13	// SCLK pin Arduino - не менять, зарезервирован SPI
#define	_BLANK_PIN	10	// SS pin Arduino - можно поменять, хотя это стандарт SPI
#define	_XLAT_PIN	9	// можно поменять
#define	_GSCLK_PIN	3	// не менять, если не хотите переписывать инициализацию таймера

/////////////////////////////////////////////////////////////////////
//
//	Минимальная реализация быстрых пиновых операций для ATmega328P
//
#if ! defined(__AVR_ATmega328P__)
	#ifndef IGNORE_CPU_CHECK
		#error This code is designed for ATmega328P MCU only. Define IGNORE_CPU_CHECK option to ignore this check.
	#endif // !IGNORE_CPU_CHECK
#endif // ! defined(__AVR_ATmega328P__)
#define ___pin2Port(pin, portD, portB, portC) (((pin) < 8) ? (portD) : (((pin) >= 8 && (pin) < 14) ? (portB) : (((pin) >= A0 && (pin) <= A7) ? (portC) : _SFR_IO8(255))))
#define __pin2Port(pin) ___pin2Port(pin, PORTD, PORTB, PORTC)
#define __pin2DirectionPort(pin) ___pin2Port(pin, DDRD, DDRB, DDRC)
#define __pin2InputPort(pin) ___pin2Port(pin,PIND,PINB,PINC)
#define	__pin2PortBit(pin) (((pin) < 8) ? (pin) : (((pin) >= 8 && (pin) < 14) ? ((pin)-8) : (((pin) >= A0 && (pin) <= A5) ? ((pin)-A0) : 255)))
#define __pin2Mask(pin)	(1 << __pin2PortBit(pin))
#define __pinHigh(port,mask) ((port) |= (mask))
#define __pinLow(port,mask) ((port) &= ~(mask))
#define	__digitalWrite(port,mask,val) (((val)!=LOW) ? (__pinHigh(port,mask)) : (__pinLow(port,mask)))
#define __pinModeOutput(dirport,mask) ((dirport) |= (mask))
#define __pinModeInput(dirport, mask)  ((dirport) &= ~(mask))
#define __pinMode(pin,state,mask) ((state) == INPUT) ? (__pinModeInput(__pin2DirectionPort(pin), mask)) : ((state) == OUTPUT) ? (__pinModeOutput(__pin2DirectionPort(pin), mask)) : (__pinModeOutput(__pin2DirectionPort(pin),0))
#define _pinMode(pin,state) __pinMode(pin,state,__pin2Mask(pin))
#define	_digitalWrite(pin,val) __digitalWrite(__pin2Port(pin),__pin2Mask(pin),val)

//
// Таймер 2 устанавливаем CTC режим на пин 3 
//
static inline void setupTimer(void) {
	_pinMode(_GSCLK_PIN, OUTPUT);
	TCCR2A = bit(COM2B0) | bit(WGM21); // инвертировать пин 3 по сравнению
	TCCR2B = bit(CS21);                // делитель частоты = 8
	OCR2A = 9;                         // для нормальной частоты обновления экрана
	TIMSK2 = bit(OCIE2B);              // установить перывание по сравнению
}

static inline void initSPI(void) {
	_pinMode(_SIN_PIN, OUTPUT);
	_pinMode(_SCLK_PIN, OUTPUT);
	_digitalWrite(_SCLK_PIN, LOW);
    SPCR = bit(SPE) | bit(MSTR); // SPI master mode
    SPSR = bit(SPI2X);				// Скорость SPI F_CPU/2
  	_pinMode(_BLANK_PIN, OUTPUT);	//	С точки зрения SPI это пин SS
	_digitalWrite(_BLANK_PIN, HIGH);

}

static inline void writeSPI(const byte b) {
	SPDR = b; 
	asm ("nop");
	while (!(SPSR & bit(SPIF)));
}

   byte RGBDiode::gsData[DATA_AMOUNT * TOTAL_LINES];

RGBDiode screen[TOTAL_LINES][TOTAL_COLUMNS] = {
	{ RGBDiode(0,0), RGBDiode(0,1), RGBDiode(0,2), RGBDiode(0,3), RGBDiode(0,4), RGBDiode(0,5), RGBDiode(0,6), RGBDiode(0,7) },
	{ RGBDiode(1,0), RGBDiode(1,1), RGBDiode(1,2), RGBDiode(1,3), RGBDiode(1,4), RGBDiode(1,5), RGBDiode(1,6), RGBDiode(1,7) },
	{ RGBDiode(2,0), RGBDiode(2,1), RGBDiode(2,2), RGBDiode(2,3), RGBDiode(2,4), RGBDiode(2,5), RGBDiode(2,6), RGBDiode(2,7) },
	{ RGBDiode(3,0), RGBDiode(3,1), RGBDiode(3,2), RGBDiode(3,3), RGBDiode(3,4), RGBDiode(3,5), RGBDiode(3,6), RGBDiode(3,7) },
	{ RGBDiode(4,0), RGBDiode(4,1), RGBDiode(4,2), RGBDiode(4,3), RGBDiode(4,4), RGBDiode(4,5), RGBDiode(4,6), RGBDiode(4,7) },
	{ RGBDiode(5,0), RGBDiode(5,1), RGBDiode(5,2), RGBDiode(5,3), RGBDiode(5,4), RGBDiode(5,5), RGBDiode(5,6), RGBDiode(5,7) },
	{ RGBDiode(6,0), RGBDiode(6,1), RGBDiode(6,2), RGBDiode(6,3), RGBDiode(6,4), RGBDiode(6,5), RGBDiode(6,6), RGBDiode(6,7) },
	{ RGBDiode(7,0), RGBDiode(7,1), RGBDiode(7,2), RGBDiode(7,3), RGBDiode(7,4), RGBDiode(7,5), RGBDiode(7,6), RGBDiode(7,7) }
};


static    int8_t currentActiveLine = 0;

static inline void updateTLC5940(void) {
	//sei();
	   const byte * data = RGBDiode::gsData + ((int)currentActiveLine) * DATA_AMOUNT;
	for (register int8_t i = 0; i < DATA_AMOUNT; writeSPI(data[i++]));
	_digitalWrite(_BLANK_PIN, HIGH);
	_digitalWrite(_XLAT_PIN, HIGH);
	_digitalWrite(_BLANK_PIN, LOW);
	_digitalWrite(_XLAT_PIN, LOW);
}

static const int8_t lines[TOTAL_LINES] = {LINE_0, LINE_1, LINE_2, LINE_3, LINE_4, LINE_5, LINE_6, LINE_7 }; 

//
// Задача этой функции, вызывать функцию reloadTLC
// каждое 256-ое прерывание.
//
ISR(TIMER2_COMPB_vect) {
	static byte counter = 0;
	if (! (++counter)) {
		currentActiveLine = (currentActiveLine + 1) % TOTAL_LINES;
		updateTLC5940();
	}
}

void clearMatrix(const byte val) {
	memset((void *)RGBDiode::gsData, val, sizeof(RGBDiode::gsData));
	RGBDiode::gsData[23] = 0xFF;
	RGBDiode::gsData[69] = 0x0F;
	RGBDiode::gsData[70] = 0xF0;
	RGBDiode::gsData[116] = 0xFF;
	RGBDiode::gsData[162] = 0x0F;
	RGBDiode::gsData[163] = 0xF0;
	RGBDiode::gsData[197] = 0xFF;
	RGBDiode::gsData[243] = 0x0F;
	RGBDiode::gsData[244] = 0xF0;
	RGBDiode::gsData[290] = 0xFF;
	RGBDiode::gsData[336] = 0x0F;
	RGBDiode::gsData[337] = 0xF0;
}

void activateMatrix(void) {
	clearMatrix();
	initSPI();
	_pinMode(_XLAT_PIN, OUTPUT);
	_digitalWrite(_XLAT_PIN, LOW);
	setupTimer();
}

В результате в нашем распоряжении оказался двумерный массив screen размерами 8х8, каждый элмент которого представляет собой экземпляр класса RGBDiode. В классе определены операции установки цвета и классическое аддитивное сложение цветов. Перед началом работы необходимы вызвать функцию activateMatrix(), которая выполнит необходимую инициализацию. Подробнее методы и типы параметров можно посмотреть в тексте.

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

/////////////////////////////////////////////////////////////////////////
//
// Базовый тест для проверки работспособности матрицы и правильности
// соединения. Если всё нормально, то Вы должны наблюдать как экран сначала
// закрашивается красным - диод за диодом, строка за строкой, затем также 
// перекрашивается в зелёный, а затем в голубой и, ..., наконец, в белый.
// Все диоды должны светиться с одинаковой яркостьюю.
// Если Вы видите именно это, поздравляю, всё у Вас работает отлично!
//
#include	"Hew2338RGB5.h"

void setup() {
	delay(1000);
	activateMatrix();
}

static void FillOutScreen(const byte r, const byte g, const byte b) {
	for (register byte i=0; i < 8; i++) {
		for (register byte j = 0; j < 8; j++) {
			screen[i][j].setRGB(r, g, b);
			delay(200);	
		}
	}
}

void loop() {
	FillOutScreen(MAX_VALUE, 0, 0);	//	Красный
	FillOutScreen(0, MAX_VALUE, 0);	// Зелёный
	FillOutScreen(0, 0, MAX_VALUE);	// Голубой
	FillOutScreen(MAX_VALUE, MAX_VALUE, 0); // Жёлтый
	FillOutScreen(MAX_VALUE, 0, MAX_VALUE); // Розовый	
	FillOutScreen(0, MAX_VALUE, MAX_VALUE); // Синий
	FillOutScreen(MAX_VALUE, MAX_VALUE, MAX_VALUE); // Белый
}

Скетч простой и в достаточной мере самодокументирован, поэтому, мы не будем останавливаться на его коде.

Заработало всё сразу, ниже представлены несколько фото.

all.jpg

Здесь я должен извиниться перед читателем. Толи потому, что я ни разу не фотограф, толи просто в силу особенностей имеющейся у меня камеры, но цвета сильно перевраны и вообще, вместо красного, например, Вы видите что-то бело-жёлтое с красной каёмкой. Я пытался как-то настроить фото-камеру (играл с балансом белого и т.п.), но не получилось. Поэтому, снимки столь ужасны. Прошу Вас просто поверить мне на слово - цвета реально красивые. сочные, полные. Работающая матрица и впрямь выглядит намного красивее, чем на моих убогих фото.

Итак, цвета оказались яркими и очень красивыми. Особенно приятно удивил чистый белый цвет и чистые цвета смешанные из двух. те, кто имел дело с цветными светодиодами, поймут о чём я. Добиться чистого белого или там чистого жёлтого обычно на них не так-то просто. Здесь всё отлично - цвета замечательные.

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

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

Вот скетч для окружности:

/////////////////////////////////////////////////////////////////////////
//
// Программа рисвования окружности. За счёт того, что некоторые 
// светодиоды подсвечиваются неполность, а "чем дальше от точного попадания
//	на окружность, тем тусклее, изображение действительно больше похоже на
//	оружность, чем если рисовать только на полной яркости.
//
#include	<Hew2338RGB5.h>

static void drawCircle(const byte xc, const byte yc, RGBColor & color) {
	const byte r = 3;
	// Ниже приведено 4 варианта массива яркостей. чем ниже, тем резче
	// падает яркость при отдалении от точного попадания
	static const unsigned transparensies[] = {
//	/*4*/	336, 303, 286, 255, 238, 164, 0, 162, 225, 269, 255, 263, 281, 303, 326, 303, 307, 317, 331, 346, 362, 336, 338, 344, 354, 365, 378, 390, 361, 362, 367, 374, 382, 392, 403, 413
	/*5*/		318, 293, 280, 255, 242, 179, 0, 177, 231, 266, 255, 261, 276, 293, 310, 293, 296, 303, 314, 326, 338, 318, 319, 324, 331, 340, 349, 358, 336, 338, 341, 346, 353, 360, 368, 375
//	/*6*/		306, 286, 275, 255, 244, 190, 0, 188, 235, 264, 255, 260, 272, 286, 300, 286, 289, 295, 303, 313, 322, 306, 308, 312, 317, 324, 331, 339, 321, 322, 325, 329, 334, 340, 346, 352
//	/*7*/	298, 282, 272, 255, 245, 198, 0, 197, 237, 263, 255, 259, 269, 282, 293, 282, 283, 289, 296, 304, 312, 298, 299, 303, 308, 313, 319, 325, 311, 312, 314, 317, 321, 326, 331, 336
	};
	for (byte k2 = 0; k2 <= r; k2 ++) {
		for (byte k1 = k2; k1 <= r; k1 ++) {
			const byte index = k2 + k1 * (k1 + 1) / 2;
			const unsigned transp = transparensies[index];
			if (transp >= 255) continue;
			const byte trans = 255 - transp;
			screen[xc + k2][yc + k1].Add(color, trans);
			screen[xc + k1][yc + k2].Add(color, trans);
			screen[xc + k2][yc - k1].Add(color, trans);
			screen[xc + k1][yc - k2].Add(color, trans);
			screen[xc - k2][yc + k1].Add(color, trans);
			screen[xc - k1][yc + k2].Add(color, trans);
			screen[xc - k2][yc - k1].Add(color, trans);
			screen[xc - k1][yc - k2].Add(color, trans);
		}
	}
}

void setup() {
	activateMatrix();
	RGBColor color(0, 0, MAX_VALUE);
	drawCircle(3, 3, color);
}

void loop() {}

А вот приблизительно, что получилось:

circle.jpg

Опять фото ужасное, но в реальности я доволен результатом.

Ну и последний тест - небольшая цветомузыкальная программа. В аналоговому входу Ардуино A7 был поключён звуковой сигнал и написана небольшая программа визуализации звука (без деления на частоты, просто по амплитуде, для простоты). Вот что в итоге полуилось (изображение плывёт по тем же причинам почему в кино плывут экраны телевизиров - в реальности ничего не плывёт и выглядит вполне прилично) - https://www.youtube.com/watch?v=wXN9dGQElUM

А вот и текст программы:

/////////////////////////////////////////////////////////////////////////
//
// Простая цветомузыкальная программа для мтарицы.
// звуковой вход подключается к аналоговому пину 7
//
#include	<Hew2338RGB5.h>

#define	TRANSPARENCY	1
#define	NOSOUND_LEVEL	24
#define	TWICE_LEVEL	200
#define	THRICE_LEVEL	240

#define	MUSIC_PIN	7

int signalLevel() {
	const int signal = analogRead(MUSIC_PIN);
	const int aSignal = abs(signal - 485) / 2;
	const int mappedSignal = map(aSignal, 0, 251, 0, 255);
	return constrain(mappedSignal, 0, 255);
}

void darkerAll(void) {
	static const RGBColor black;
	RGBDiode * cell = & (screen[0][0]);
	for (int8_t indx = 0; indx < TOTAL_LINES * TOTAL_COLUMNS; indx ++, cell ++ ) cell->Add(black, TRANSPARENCY);
}

void setupTimer1(void) {
	TCCR1A = bit(WGM11);
	TCCR1B = bit(WGM13) | bit(CS12); // СТС режим и делитель 256
	TIMSK1 = bit(TOIE1);
	ICR1 = 625;
	TCNT1 = 0;
}

ISR(TIMER1_OVF_vect) {
	sei();
	darkerAll();
}

void setup() {
	randomSeed(analogRead(0));
	activateMatrix();
	setupTimer1();
}

void setNeighbourColor(const int8_t x, const int8_t y, RGBColor & original) {
	if (x >= 0 && x < TOTAL_COLUMNS && y >= 0 && y < TOTAL_LINES) screen[x][y].Add(original, 200);
}

void loop() {
	const int upperBarrier = signalLevel();
	if (upperBarrier < NOSOUND_LEVEL) return;
	const int8_t x = random(8);
	const int8_t y = random(8);
	const uint8_t red = random(upperBarrier);
	const uint8_t green = random(upperBarrier);
	const uint8_t blue = random(upperBarrier);
	const RGBColor col(red, green, blue);
	screen[x][y].Add(col);
	if (upperBarrier > TWICE_LEVEL) {
		setNeighbourColor(x-1, y-1, screen[x][y]);
		setNeighbourColor(x-1, y, screen[x][y]);
		setNeighbourColor(x-1, y+1, screen[x][y]);
		setNeighbourColor(x, y-1, screen[x][y]);
		setNeighbourColor(x, y+1, screen[x][y]);
		setNeighbourColor(x+1, y-1, screen[x][y]);
		setNeighbourColor(x+1, y, screen[x][y]);
		setNeighbourColor(x+1, y+1, screen[x][y]);
	}
	if (upperBarrier > THRICE_LEVEL) {
		setNeighbourColor(x-2, y-2, screen[x][y]);
		setNeighbourColor(x-2, y-1, screen[x][y]);
		setNeighbourColor(x-2, y, screen[x][y]);
		setNeighbourColor(x-2, y+1, screen[x][y]);
		setNeighbourColor(x-2, y+2, screen[x][y]);
		setNeighbourColor(x+2, y-2, screen[x][y]);
		setNeighbourColor(x+2, y-1, screen[x][y]);
		setNeighbourColor(x+2, y, screen[x][y]);
		setNeighbourColor(x+2, y+1, screen[x][y]);
		setNeighbourColor(x+2, y+2, screen[x][y]);
		setNeighbourColor(x-1, y-2, screen[x][y]);
		setNeighbourColor(x, y-2, screen[x][y]);
		setNeighbourColor(x+1, y-2, screen[x][y]);
		setNeighbourColor(x-1, y+2, screen[x][y]);
		setNeighbourColor(x, y+2, screen[x][y]);
		setNeighbourColor(x+1, y+2, screen[x][y]);
	}
}

Вывод

Отличная вещь! Цвета красивые, насыщенные - очень хороший белый цвет - никаких смещений, действительно белый. Мне очень понравилось.

Рекомендация магазину

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

Gippopotam
Gippopotam аватар
Offline
Зарегистрирован: 12.09.2014

Круто!

Интересно, через какое время у китайцев появится шилд для подключения этой матрицы? Может им ссылку на эту тему кинуть? :)

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

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

strarbit
Offline
Зарегистрирован: 12.06.2016

КРУТО!

__Alexander
Offline
Зарегистрирован: 24.10.2012

Скорее появится такая-же матрица со встроенным контроллером.

Gippopotam
Gippopotam аватар
Offline
Зарегистрирован: 12.09.2014

__Alexander пишет:

Скорее появится такая-же матрица со встроенным контроллером.

Так я это и имел в виду.

trembo
trembo аватар
Offline
Зарегистрирован: 08.04.2011
Gippopotam
Gippopotam аватар
Offline
Зарегистрирован: 12.09.2014

Это похоже часть светодиодного экрана, из которых бегущие строки делают.

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

Gippopotam пишет:

Это похоже часть светодиодного экрана, из которых бегущие строки делают.

Оно и есть.

Но к обсуждаемой матрице это не имеет отношения.

kadir544
Offline
Зарегистрирован: 25.11.2015

у меня была такая похожа. по размерам и тем что она многоцветная интереснее той что бывает в комплекте

https://www.youtube.com/watch?v=0ZkZ4uOY3xs&feature=em-upload_owner

http://vfl.ru/fotos/45e68de814152911.html

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

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

kadir544
Offline
Зарегистрирован: 25.11.2015

наверно снимал в темноте поэтому и пиксели слишком яркие для камеры в темноте поэтому. 

Gippopotam
Gippopotam аватар
Offline
Зарегистрирован: 12.09.2014

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

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

под большим углом

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

Спасибо, попробую.

LastHopeMan
Offline
Зарегистрирован: 19.09.2016

Хороший обзор. Мне, как программисту и ни разу не инженеру, очень помогло в понимании матчасти. Не только по дисплею. 

И подход с массивом интересный. 

Denis_B
Offline
Зарегистрирован: 26.01.2016
Подскажите, в этом участке кода что происходит?
void clearMatrix(const byte val) {
	memset((void *)RGBDiode::gsData, val, sizeof(RGBDiode::gsData));
	RGBDiode::gsData[23] = 0xFF;
	RGBDiode::gsData[69] = 0x0F;
	RGBDiode::gsData[70] = 0xF0;
	RGBDiode::gsData[116] = 0xFF;
	RGBDiode::gsData[162] = 0x0F;
	RGBDiode::gsData[163] = 0xF0;
	RGBDiode::gsData[197] = 0xFF;
	RGBDiode::gsData[243] = 0x0F;
	RGBDiode::gsData[244] = 0xF0;
	RGBDiode::gsData[290] = 0xFF;
	RGBDiode::gsData[336] = 0x0F;
	RGBDiode::gsData[337] = 0xF0;
}

 

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

Очищается матрица. В строке 2 весь массив цветов заполняется значением val, а в строка 3-14 элемента массива данных, соответствующие общим пинам матрицы, заполняются единицами.

В результате все светодиоды получат цвет (R=val, G=val, B=val). Т.е. если val равно 0, то матрица полностью погаснет, если val равно 255, то станет белой, а при промежуточных значениях val - серой, чем меньше val, тем темнее.