В моей прошлой статье про брелок со светодиодной матрицей основной проблемой была яркость. Он очень блекло светил. Мне написали в комментариях много предположений и мне захотелось проверить как можно решить эту проблему. Сначала, я хотел только дополнить первую статью сноской и перечислить, что работает, а что нет, но потом подумал, что лучше оформить это в отдельный и полноценный ответ. Возможно, его будет проще увидеть тем, кому была интересна первая статья.
В этой статье сделаем два новых брелка, проверим оригинальную схему с MAX7219, заменим батарейку и посмотрим на отличия разных светодиодных матриц.
❯ Введение
Если коротко, то в прошлой статье я сделал светодиодный брелок на микросхеме CH32V003F4P6. Как мне кажется, самое интересное в этой затее было то, что он самодельный на односторонней плате и весь процесс я постарался описать. А еще там были заклепки. (Можете ознакомиться с ней, чтобы лучше понимать, о чем я здесь пишу, но все должно быть понятно, если вы понимаете, как работает светодиодная матрица и что в один момент времени могут гореть 8 светодиодов.) Основная проблема, что получившийся брелок светил блекло. Идея этого устройства появилась из роликов с YouTube и там он светит хорошо, даже видно через черный пластик. Поэтому и появилась мысль, что можно сделать лучше.
В комментариях к статье VT100, как мне кажется, точно подсказал симптомы, что разным светодиодам нужно разное напряжение и красные матрицы должны светиться ярче. Там же было подмечено, что CR2032 очень хилые.
В других комментариях и я в конце статьи предположил, что возможно микроконтроллер может быть ограничен по току. И действительно в даташите написано, что максимальный потребляемый ток CH32V003 это 100 мА.
Поэтому план статьи следующий:
Посмотрим как светят разные светодиодные матрицы.
Заменим CR2032 на LIR2032.
Соберем два новых брелка: один на CH32V003 (с другими токоограничивающими резисторами) и один на ATtiny2313A.
Сравним с оригинальной схемой на MAX7219 и ATtiny85.
На научную статью не претендую, просто хочу посмотреть на грубые отличия от разных идей и предположений. И было бы хорошо решить эту проблему, а то получившийся в первой части брелок тяжело кому-то даже подарить или повторить, как раз из-за того, что светит слабо.
Когда начал планировать статью, думал, чтобы сделать большую таблицу с разными вариантами, но на самом деле это не нужно. Мы просто пройдем по каждому пункту, выберем лучший вариант и будем сравнивать его с последующим, так сократим очень много вариантов и найдем лучший. Чтобы было чуть меньше погрешностей все фото для одного пункта буду делать в одном освещении.
❯ Светодиодные матрицы
Сначала определимся какая матрица светит ярче. Для тестов я возьму брелок из первой статьи (для упрощения назову его V1) и сделаю фото 3х матриц с ним: синей, зеленой, красной. Напомню, V1 — это брелок со схемой, в которой микроконтроллер CH32V003F4P6 управляет светодиодной матрицей напрямую через токоограничивающие резисторы в 200 Ом.
Но прежде посмотрим на данные из википедии, как должно быть на самом деле. Падение напряжения на светодиодах:
Красный — 1,63 < ΔU < 2,03
Зеленый — 1,9 < ΔU < 4,0
Синий — 2,48 < ΔU < 3,7
Другими словами, если у нас батарейка 3.3В и падение напряжения 1.63В (красный) с токоограничивающим резистором 200 Ом, то ток будет (3.3 - 1.3) / 200 = 9.9 мА. А для падения напряжения 2.48В (синий) получаем ток 4 мА. Этими простыми вычислениями хочу показать, что светодиодные матрицы должны светиться по-разному, красные ярче, затем зеленые, затем синие.

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

Здесь уже видно невооруженным глазом, с LIR2032 заметно ярче, чем когда сменили синюю матрицу на красную.
В последующих тестах будем использовать батарейку LIR2032.
❯ Новые брелки
Теперь проверим новые устройства.
Когда я писал первую статью, то уже сделал плату под следующий брелок и решил на нем проверить, что будет если поставить другие токоограничивающие резисторы. На первой плате они были 200 Ом, а на второй стали 100 Ом.
Но больше всего меня заинтересовал вопрос, возможно можно выбрать другой микроконтроллер у которого лучше характеристики потребления тока. Стал изучать этот вопрос и нашел, что у микроконтроллеров AVR (200 мА) и PIC (300 мА) обычно везде в даташитах потребляемые токи больше, чем у CH32V003 (100 мА), а у STM32 (100 мА) примерно такие же. Другие микроконтроллеры не смотрел, но, забегая вперед, у микросхемы MAX7219 самый большие отдаваемые токи (Sink Current 500мА).
Сначала я хотел попробовать PIC микроконтроллер, нашел что-то вроде PIC16F628A и у него в даташите максимальный потребляемы ток 300 мА, но у меня программатор K150 и он его не поддерживает, находил другие микросхемы и они тоже не поддерживались, поэтому я почти быстро отказался от PIC, т.к. пришлось бы покупать новый программатор и оно того вряд ли бы стоило. (Напишите комментарий, пожалуйста, если вам было это интересно, потому что я подумал, что они сейчас никому не интересны) Потом я искал среди AVR микроконтроллеров и почти у всех было написано максимально потребляемый ток 200 мА и случайно на Озон наткнулся на ATtiny2313A, которые точно подходили под брелок и стоили около 60 рублей за штуку, поэтому я их сразу же заказал и тоже ждал около месяца.
Хочется добавить, что в даташитах AVR или PIC микроконтроллеров везде были одинаковые цифры потребляемого тока: у AVR это 200 мА, у PIC это 300 мА. Т.к. я остановился на AVR, то у некоторых была (не у всех) интересная сноска в даташите, что максимально отдаваемый ток не должен превышать 60 мА и кажется это правдиво для ATtiny2313A, но как проверить не знаю и наверно не нужно, потому что дальше по статье мы поймем что лучше ATtiny2313A или CH32V003F4P6 при потреблении тока.
Приведу получившиеся фото, схему и код проекта на ATtiny2313A, возможно кому-то будет интересно посмотреть.




Скрытый текст
#include <Arduino.h>
// 0 - PD0
// 1 - PD1
// 2 - PA1
// 3 - PA0
// 4 - PD2
// 5 - PD3
// 6 - PD4
// 7 - PD5
// 8 - PD6
// 9 - PB0
// 10 - PB1
// 11 - PB2
// 12 - PB3
// 13 - PB4
// 14 - PB5
// 15 - PB6
// 16 - PB7
// 17 - PA2
#define COL1 0 // PD0
#define COL2 5 // PD3
#define COL3 15 // PB6
#define COL4 3 // PA0
#define COL5 8 // PD6
#define COL6 14 // PB5
#define COL7 9 // PB0
#define COL8 12 // PB3
#define ROW1 4 // PD2
#define ROW2 10 // PB1
#define ROW3 11 // PB2
#define ROW4 1 // PD1
#define ROW5 13 // PB4
#define ROW6 2 // PA1
#define ROW7 6 // PD4
#define ROW8 7 // PD5
const int row[8] = {
ROW1, ROW2, ROW3, ROW4, ROW5, ROW6, ROW7, ROW8
};
const int col[8] = {
COL1, COL2, COL3, COL4, COL5, COL6, COL7, COL8
};
const uint8_t ghost_Frame_1[8] PROGMEM = { 0x1c, 0x3e, 0x6b, 0x49, 0x7f, 0x63, 0x7f, 0x55 };
const uint8_t ghost_Frame_2[8] PROGMEM = { 0x1c, 0x3e, 0x5b, 0x49, 0x7f, 0x63, 0x7f, 0x55 };
const uint8_t ghost_Frame_3[8] PROGMEM = { 0x1c, 0x3e, 0x49, 0x5b, 0x7f, 0x63, 0x7f, 0x55 };
const uint8_t ghost_Frame_4[8] PROGMEM = { 0x1c, 0x3e, 0x49, 0x6d, 0x7f, 0x63, 0x7f, 0x55 };
const uint8_t ghost_Frame_5[8] PROGMEM = { 0x1c, 0x3e, 0x6d, 0x49, 0x7f, 0x63, 0x7f, 0x55 };
const uint8_t ghost_Frame_6[8] PROGMEM = { 0x1c, 0x3e, 0x6b, 0x49, 0x7f, 0x63, 0x7f, 0x55 };
const uint8_t ghost_Frame_7[8] PROGMEM = { 0x1c, 0x3e, 0x6b, 0x49, 0x7f, 0x63, 0x7f, 0x55 };
const uint8_t ghost_Frame_8[8] PROGMEM = { 0x1c, 0x3e, 0x6b, 0x49, 0x7f, 0x63, 0x7f, 0x55 };
const uint8_t *ghost_frames[8] = {
ghost_Frame_1,
ghost_Frame_2,
ghost_Frame_3,
ghost_Frame_4,
ghost_Frame_5,
ghost_Frame_6,
ghost_Frame_7,
ghost_Frame_8
};
uint8_t _pixels[8];
uint16_t _pwm;
const uint8_t _intensity = 15;
void displayFrame(const uint8_t* frame) {
for (int y = 0; y < 8; y++) {
for (int x = 0; x < 8; x++) {
uint8_t b = pgm_read_byte(frame + y);
if (b & (1 << x)) {
_pixels[x] = _pixels[x] | (1 << y);
} else {
_pixels[x] = _pixels[x] & ~(1 << y);
}
}
}
}
void refreshScreen() {
_pwm++;
if (_pwm > 16) {
_pwm = 0;
}
if (_pwm < _intensity) {
for (int thisRow = 0; thisRow < 8; thisRow++) {
digitalWrite(row[thisRow], LOW);
for (int thisCol = 0; thisCol < 8; thisCol++) {
int thisPixel;
if (_pixels[thisCol] & (1 << thisRow)) {
thisPixel = HIGH;
} else {
thisPixel = LOW;
}
digitalWrite(col[thisCol], thisPixel);
if (thisPixel == HIGH) {
digitalWrite(col[thisCol], LOW);
}
}
digitalWrite(row[thisRow], HIGH);
}
}
}
void delayWithRefresh(unsigned long d) {
unsigned long prevTime = millis();
unsigned long duration = 0;
while (true) {
unsigned long time = millis();
unsigned long tmp = (time - prevTime);
prevTime = time;
duration += tmp;
if (duration > d) {
break;
}
refreshScreen();
}
}
void setup() {
for (int thisPin = 0; thisPin < 8; thisPin++) {
pinMode(col[thisPin], OUTPUT);
pinMode(row[thisPin], OUTPUT);
digitalWrite(col[thisPin], LOW);
digitalWrite(row[thisPin], HIGH);
}
for (int x = 0; x < 8; x++) {
for (int y = 0; y < 8; y++) {
_pixels[x] = _pixels[x] | (1 << y);
}
}
}
void loop() {
for (uint8_t i = 0; i < 8; i++) {
displayFrame(ghost_frames[i]);
delayWithRefresh(150);
}
}Код значительно упростил, он теперь показывает только привидение из Pacman и нажатие кнопки не обрабатывается. На самом деле пришлось небольшую оптимизацию сделать, т.к. ATtiny2313A не обладает большой памятью, поэтому все массивы констант вынесены в ПЗУ. А другие анимации уже не влезают, возможно если написать на ассемблере, то и влезут, но я решил, что пока это не стоит того.
Давайте посмотрим на результаты:

V1 — оригинальный брелок из прошлой статьи на CH32V003F4P6, у него 200 Ом токоограничивающие резисторы. V2 — тоже самое, но с 100 Ом токоограничивающими резисторами. Разница небольшая, но она есть. V3 — новый брелок с ATtiny2313A и у него тоже 100 Ом токоограничивающие резисторы.
По фото видно, что действительно ATtiny2313A лучше себя показал. В живую, это конечно не так ярко, а вот на камере появились даже блики. Если посветить им на поверхность, то видно как она освещается. Даже просвечивает через черный пластик! (Эти фото приведу дальше.)
Можно частично сказать, что победа, но давайте посмотрим последний тест.
❯ MAX7219
В роликах на YouTube, которые мне подкинули идею для этого брелка, уже есть готовый код и рассчитан он на ATtiny85. Поэтому я решил собрать их оригинальную схему и посмотреть, что получится.

Скрытый текст
#include <Arduino.h>
#include <LedControl.h>
#define DIN 0 // PB0
#define CS 1 // PB1
#define CLK 3 // PB3
const uint8_t ghost_Frame_1[8] PROGMEM = {0x1c, 0x3e, 0x6b, 0x49, 0x7f, 0x63, 0x7f, 0x55};
const uint8_t ghost_Frame_2[8] PROGMEM = {0x1c, 0x3e, 0x5b, 0x49, 0x7f, 0x63, 0x7f, 0x55};
const uint8_t ghost_Frame_3[8] PROGMEM = {0x1c, 0x3e, 0x49, 0x5b, 0x7f, 0x63, 0x7f, 0x55};
const uint8_t ghost_Frame_4[8] PROGMEM = {0x1c, 0x3e, 0x49, 0x6d, 0x7f, 0x63, 0x7f, 0x55};
const uint8_t ghost_Frame_5[8] PROGMEM = {0x1c, 0x3e, 0x6d, 0x49, 0x7f, 0x63, 0x7f, 0x55};
const uint8_t ghost_Frame_6[8] PROGMEM = {0x1c, 0x3e, 0x6b, 0x49, 0x7f, 0x63, 0x7f, 0x55};
const uint8_t ghost_Frame_7[8] PROGMEM = {0x1c, 0x3e, 0x6b, 0x49, 0x7f, 0x63, 0x7f, 0x55};
const uint8_t ghost_Frame_8[8] PROGMEM = {0x1c, 0x3e, 0x6b, 0x49, 0x7f, 0x63, 0x7f, 0x55};
const uint8_t *ghost_frames[8] = {
ghost_Frame_1,
ghost_Frame_2,
ghost_Frame_3,
ghost_Frame_4,
ghost_Frame_5,
ghost_Frame_6,
ghost_Frame_7,
ghost_Frame_8
};
LedControl lc = LedControl(DIN, CLK, CS, 1);
void displayFrame(const uint8_t *frame)
{
for (int i = 0; i < 8; i++)
{
lc.setRow(0, i, pgm_read_byte(frame + i));
}
}
void setup()
{
lc.shutdown(0, false);
lc.setIntensity(0, 15);
lc.clearDisplay(0);
}
void loop()
{
for (uint8_t i = 0; i < 8; i++)
{
displayFrame(ghost_frames[i]);
delay(150);
}
}Код очень простой, здесь нет никаких сложностей, т.к. они скрыты в библиотеке LedControl.

В схеме на макетке можете заметить батарейку, та же самая, LIR2032.

Фото с макеткой получилось мягче, чем следующее фото, потому что я включил лампу на столе. На фото с сравнением ATtiny2313A с MAX7219 лампа была выключена, поэтому такое отличие. (Думаю надо повториться, но на левой и на правой части фото лампа выключена, т.к. освещение на сравнениях одинаковое, но в рамках одного сравнения.)
В жизни MAX7219 светит действительно ярко, заметно ярче, чем ATtiny2313A, но в то же время не сказать, что ATtiny2313A плохо светит, можно сказать достаточно, чтобы брелком можно пользоваться или хотя бы показывать.
В итоге, столько телодвижений, а можно было взять сразу готовый проект, который показали в YouTube роликах, но правда, как по мне, это было бы менее интересно.
❯ Финальное сравнение
Давайте теперь возьмем самый лучший получившийся вариант (ATtiny2313A + LIR2032 + красная матрица) и сделаем фото как из первой статьи и сравним.

Я специально подставил руку, что было понятно, что теперь брелок заметно освещает поверхность, что никак не получалось у самого первого брелка.

И даже в черном корпусе что-то видно, плохо, но видно.

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



И теперь можно увидеть, что не обязательно красная матрица будет ярче всех. Видно, что и синяя неплохо светит.
❯ Заключение
Итого больше всего помогло заменить микроконтроллер, затем батарейку, но кажется, что светодиодную матрицу можно было и не менять (но см. комментарий ниже).
Кто давно занимается светодиодами, наверно, мог до всех выводов из статьи догадаться уже заранее, здесь нет ничего нового. Хотя мне некоторые моменты были тоже понятны, но как это на практике может выглядеть, все же до написания стать не представлял.
Практически каждый существенный комментарий к прошлой статье по своему помогал и улучшал общую яркость, но однозначно сказать, что же помогло сложно, да на самом деле проблема осталась, т.к. обычные микроконтроллеры для таких задач конечно подходят, но не лучшим образом. Возможно и PIC бы показал себя лучше, но это на самом деле не важно, потому что очевидно, что ему не превзойти MAX7219.
Еще не стоит забывать, что в получившемся брелке использовался ATtiny2313A, а он в 3 и больше раз дороже, чем CH32V003F4P6, поэтому сложно сказать победа это или нет. Наверно не победа, т.к. изначальная идея была сделать это все на самом дешевом микроконтроллере, а тут суть потеряна. Но все же и небольшая победа, потому что проблема частично решилась и брелок светит приемлемым качеством. А для меня это оказался интересный и полезный проект, возможно и вам было интересно за ним следить.
Все файлы из статьи находятся в репозитории LedKeychain. В том же, что и к первой статье, но в папке V2.
❯ Важный комментарий
Хотя в конце статьи я сделал вывод, что не так важен оказался выбор светодиодной матрицы, но случайно я обнаружил, что в одних случаях он может быть существенным, если не главным.
После того, как написал статью, я взял первый свой брелок с CH32V003 и проверил его на красной и синей матрице, при этом была обычная батарейка CR2032. В результате синяя матрица еле светилась, а красная нормально. Я вставил батарейку LIR2032 и они обе стали светиться нормально, не сказать, что какая-то ярче. Поэтому можно сделать вывод, что если напряжения для каких-то задач может не хватать и тогда в этом случае матрица светит плохо. Я проделал такой же тест с ATtiny2313A и обе матрицы светились нормально при той же CR2032. Получается это может быть банально какая-то особенность CH32V003.
Не понимаю, почему этот случай не произошел в самом первом тесте, возможно батарейка разрядилась. Получается решение взять красную матрицу было правильным.
❯ Это еще не конец
После того как я написал статью, в комментариях ahabreader мне указал на серьезную ошибку с моей стороны — опустить из рассмотрения алгоритм, точнее код. Да, я решил искать проблему в других местах, потому что подумал, что оптимизация на первый взгляд не нужна, но её надо было сделать, а потом делать вывод. В итоге я переписал код и яркость увеличилась вдвое, почитайте его комментарий, чтобы лучше понять насколько много он подметил неточностей. На самом деле там была одна главная, что из-за того, что хотелось сделать яркость равномерной в каждый момент загорался один светодиод, а не восемь! Достаточно этот момент исправить и все становится гораздо лучше. Теперь в один момент могут гореть несколько светодиодов.
Улучшенная версия
#if TOPTIMIZED
#include <Arduino.h>
#include <avr/io.h>
//#define ROW1 0 // PD0
//#define ROW2 5 // PD3
//#define ROW3 15 // PB6
//#define ROW4 3 // PA0
//#define ROW5 8 // PD6
//#define ROW6 14 // PB5
//#define ROW7 9 // PB0
//#define ROW8 12 // PB3
//#define COL1 4 // PD2
//#define COL2 10 // PB1
//#define COL3 11 // PB2
//#define COL4 1 // PD1
//#define COL5 13 // PB4
//#define COL6 2 // PA1
//#define COL7 6 // PD4
//#define COL8 7 // PD5
const uint8_t ghost_Frame_1[8] PROGMEM = { 0x1c, 0x3e, 0x6b, 0x49, 0x7f, 0x63, 0x7f, 0x55 };
const uint8_t ghost_Frame_2[8] PROGMEM = { 0x1c, 0x3e, 0x5b, 0x49, 0x7f, 0x63, 0x7f, 0x55 };
const uint8_t ghost_Frame_3[8] PROGMEM = { 0x1c, 0x3e, 0x49, 0x5b, 0x7f, 0x63, 0x7f, 0x55 };
const uint8_t ghost_Frame_4[8] PROGMEM = { 0x1c, 0x3e, 0x49, 0x6d, 0x7f, 0x63, 0x7f, 0x55 };
const uint8_t ghost_Frame_5[8] PROGMEM = { 0x1c, 0x3e, 0x6d, 0x49, 0x7f, 0x63, 0x7f, 0x55 };
const uint8_t ghost_Frame_6[8] PROGMEM = { 0x1c, 0x3e, 0x6b, 0x49, 0x7f, 0x63, 0x7f, 0x55 };
const uint8_t ghost_Frame_7[8] PROGMEM = { 0x1c, 0x3e, 0x6b, 0x49, 0x7f, 0x63, 0x7f, 0x55 };
const uint8_t ghost_Frame_8[8] PROGMEM = { 0x1c, 0x3e, 0x6b, 0x49, 0x7f, 0x63, 0x7f, 0x55 };
const uint8_t *ghost_frames[8] = {
ghost_Frame_1,
ghost_Frame_2,
ghost_Frame_3,
ghost_Frame_4,
ghost_Frame_5,
ghost_Frame_6,
ghost_Frame_7,
ghost_Frame_8
};
uint8_t _pixels[8];
const uint8_t* _currentFrame;
void setRow(uint8_t index, uint8_t value) {
switch (index) {
case 0:
if (value) {
PORTD |= (1 << PD0);
} else {
PORTD &= ~(1 << PD0);
}
break;
case 1:
if (value) {
PORTD |= (1 << PD3);
} else {
PORTD &= ~(1 << PD3);
}
break;
case 2:
if (value) {
PORTB |= (1 << PB6);
} else {
PORTB &= ~(1 << PB6);
}
break;
case 3:
if (value) {
PORTA |= (1 << PA0);
} else {
PORTA &= ~(1 << PA0);
}
break;
case 4:
if (value) {
PORTD |= (1 << PD6);
} else {
PORTD &= ~(1 << PD6);
}
break;
case 5:
if (value) {
PORTB |= (1 << PB5);
} else {
PORTB &= ~(1 << PB5);
}
break;
case 6:
if (value) {
PORTB |= (1 << PB0);
} else {
PORTB &= ~(1 << PB0);
}
break;
case 7:
if (value) {
PORTB |= (1 << PB3);
} else {
PORTB &= ~(1 << PB3);
}
break;
}
}
void setColumns(uint8_t data) {
uint8_t portb = PORTB;
uint8_t porta = PORTA;
uint8_t portd = PORTD;
if (data & (1 << 0)) {
portd |= (1 << PD2);
} else {
portd &= ~(1 << PD2);
}
if (data & (1 << 1)) {
portb |= (1 << PB1);
} else {
portb &= ~(1 << PB1);
}
if (data & (1 << 2)) {
portb |= (1 << PB2);
} else {
portb &= ~(1 << PB2);
}
if (data & (1 << 3)) {
portd |= (1 << PD1);
} else {
portd &= ~(1 << PD1);
}
if (data & (1 << 4)) {
portb |= (1 << PB4);
} else {
portb &= ~(1 << PB4);
}
if (data & (1 << 5)) {
porta |= (1 << PA1);
} else {
porta &= ~(1 << PA1);
}
if (data & (1 << 6)) {
portd |= (1 << PD4);
} else {
portd &= ~(1 << PD4);
}
if (data & (1 << 7)) {
portd |= (1 << PD5);
} else {
portd &= ~(1 << PD5);
}
PORTA = porta;
PORTB = portb;
PORTD = portd;
}
void displayFrame(const uint8_t* frame) {
_currentFrame = frame;
}
void refreshScreen() {
for (int thisRow = 0; thisRow < 8; thisRow++) {
setRow(thisRow, 0);
setColumns(pgm_read_byte(_currentFrame + thisRow));
delayMicroseconds(1000);
setColumns(0);
setRow(thisRow, 1);
}
}
void delayWithRefresh(unsigned long d) {
unsigned long startTime = millis();
while (millis() - startTime < d) {
refreshScreen();
}
}
void setup() {
DDRA |= (1 << PA0) | (1 << PA1);
DDRB |= (1 << PB0) | (1 << PB1) | (1 << PB2) | (1 << PB3) | (1 << PB4) | (1 << PB5) | (1 << PB6);
DDRD |= (1 << PD0) | (1 << PD1) | (1 << PD2) | (1 << PD3) | (1 << PD4) | (1 << PD5) | (1 << PD6);
}
void loop() {
for (uint8_t i = 0; i < 8; i++) {
displayFrame(ghost_frames[i]);
delayWithRefresh(150);
}
}
#endif

Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале ↩
Перед оплатой в разделе «Бонусы и промокоды» в панели управления активируйте промокод и получите кэшбэк на баланс.
Комментарии (25)

REPISOT
10.09.2025 08:35Но прежде посмотрим на данные из википедии, как должно быть на самом деле. Падение напряжения на светодиодах:
Смотреть надо данные из даташита производителя матрицы.
если у нас батарейка 3.3В и падение напряжения 1.63В (красный) с токоограничивающим резистором 200 Ом, то ток будет (3.3 - 1.3) / 200 = 9.9 мА. А для падения напряжения 2.48В (синий) получаем ток 40 мА
Как это у вас вышло? (3,3 - 2,48) / 200 = 4 mA
хочу показать, что светодиодные матрицы должны светится по-разному, красные ярче, затем зеленые, затем синие.
Поэтому обычно на разные цвета ставят разные номиналы резисторов.

m039 Автор
10.09.2025 08:35Спасибо большое за то, что нашли ошибки. 40 мА это опечатка, почему не догадался, что что-то не то, не понятно. А про светодиодные матрицы согласен, но почему-то был уверен, что можно смело брать данные из википедии, т.к. это уже стандартизированно. Надо было и то и другое привести. В целом для статьи это не критично, с учетом последнего комментария и разных фотографий, но так было бы грамотнее.

kkuznetzov
10.09.2025 08:35А если подобрать для каждого типа светодиода свой резистор, так что бы ток был одинаков, то какой ярче будет?

m039 Автор
10.09.2025 08:35По логике должны одинаково светить, но после теста резистров по 100 и 200 Ом думаю, что разница будет небольшая или не понятная для глаза. Но даже при этом, если делаешь законченное устройство их стоит поставить, чтобы пользователь был уверен, что цветопередача корректная, например, в каком-нибудь дисплее. Если кто-то лучше разбирается в этом вопросе, поправьте, пожалуйста, т.к. пишу по своим рассуждениям.

CitizenOfDreams
10.09.2025 08:35По логике должны одинаково светить
При одинаковом токе и одинаковом КПД светодиодов зеленый будет светить визуально ярче. Это особенность человеческого зрения - максимальная чувствительность у глаза находится в районе 550нм, что соответствует зеленому цвету.

alcotel
10.09.2025 08:35При одинаковых кпд и мощности, скорее. Напряжение на них разное - не меньше энергии кванта в электрон-вольтах.
Яркость в тех.описании (в канделах которая измеряется) как раз спектраоьную чувствительность глаза тоже учитывает.

PMA
10.09.2025 08:35Для некоторых изделий токоограничивающие резисторы вообще не ставят используя хак с высоким внутренним сопротивлением (не способностью отдать большой ток) элемента 2032 (всевозможные фонарики на 1 светодиод, эмуляторы сосулек на ёлку, горящего пламени). Можете попробовать закоротить (поставить на 10 Ом) резисторы на брелке V1.
Пример на микроконтроллере с резистором последовательно с батарейкой (эмуляция батарейки 2032)
Ролик на простом английском со схемой и сборкой

m039 Автор
10.09.2025 08:35Интересно, надо будет попробовать при случае, правда думал, что уже закончил с этим проектом. Просто я боялся ставить резисторы с малым номиналом, чтобы не спалить светодиоды. Еще хотел добавить, что этот тест с токоограничивающими резисторами был один из нескольких и на конечный вывод статьи скорее всего не повлияет, т.е. эти резисторы можно поставить и ATtiny2313A и получить еще ярче картину, а не только CH32V003.

Svyat2374
10.09.2025 08:35Интересно было бы вставить туда к примеру esp, с возможностью смены изо и анимации. Сделать например кнопку по которой модуль будет просыпаться и коннектится к телефону, а после загрузки кода нового изображения брелок ребутился бы espшка спала.
Интересно на сколько хватает данного аккумулятора

m039 Автор
10.09.2025 08:35Батарейки при такой схеме очевидно хватит не надолго. Надо будет больше держать устройство во сне, использовать лучше BLE, чем WiFi и коннектиться лучше после нажатия по кнопке. Я думал делать брелок с Bluetooth, устройство сразу бы получилось интереснее, чем без него, но есть нюанс, что надо это все уложить в размеры светодиодный матрицы и может не получится. По-крайней мере скорее всего не получится на односторонней плате, как делал я, но на двухсторонней можно попробовать. Правда на момент поисков я не рассматривал ESP, а смотрел в сторону HC-05.
У вас может быть совершенно другие критерии и все может быть по-другому. Например, можно взять ESP + MAX7219 + литиевую батарейку с модулем заряда и устройство будет скажем в 1.5 раза больше, но зато с радикоаналом и возможностью подзарядки через USB. Но, как по мне, этот брелок ближе к игрушке и зачем все так усложнять - не понятно. Более, чем достаточно запрограммировать через программатор, включить, поморгать и выключить.

Zeus42
10.09.2025 08:35Я тоже думал за ESP после прочтения первой и второй статьи. Но увы, сама по себе ESP потребляет много, и анимация (а принципе любая) тоже. Есть к примеру ESP32C3 которая упакована в один чип с небольшим набором компонентов, но все же.
Можно попробовать использовать WS2812B, например от Lolin 8x8 RGB. Это матрица хоть и в разы меньше, с возможностью контролировать яркость и цвета, но не уверен что она обыграет по потреблению обычные светодиоды.
Кстати можно немного усложнить сборку и использовать светодиоды формата 0802. По идее они будут потреблять меньше и светить не плохо. Однако их даташит я не изучал, поэтому не утверждаю.

m039 Автор
10.09.2025 08:35Интересно, можно попробовать заменить матрицу (взять самодельную), добавить Bluetooth через ESP и уже готов материал для следующей статьи, правда устройство заметно сложнее и по-хорошему надо придумать ему интересное применение, тогда можно браться. Пока правда я не планирую это делать, но посмотрим, может быть какая-та идея появится и захочется.

alcotel
10.09.2025 08:35Для синих светодиодов и для подсевшей батарейки можно соорудить простой синхронный повышающий преобразователь напряжения, управляемый от мк. И им застабилизировать ток и яркость
Типа такого

ADC1 измеряет напряжение батарейки, ADC2 - ток через светодиод. Мк вычисляет, на сколько нужно поднять напряжение, и выдаёт расчитанный ШИМ-сигнал на преобразователь. В качестве преобразователя - обычный или высокотоковый логический инвертор.

m039 Автор
10.09.2025 08:35Интересно, но можете пояснить подробнее про преобразователь? Про ADC1, ADC2, ШИМ понятно, а почему если подать ШИМ на 74AC04, то получится регулировать напряжение или стабилизировать ток? Логический инвертор же просто инвертирует сигнал ШИМ, т.е. это же можно сделать даже программно. В сети сходу про эту схему ничего не нашел.
Или подается ШИМ на питание 74AC04? Как в таком случае будет получаться повышение напряжения?

alcotel
10.09.2025 08:35Если вскрыть КМОП-инвертор, из него можно достать пару транзисторов. На них собираем синхронный повышающий DCDC. Запараллелив их просто повышаем допустимый ток.
Вот такой DCDC

Можно и отдельные транзисторы взять, или сборку из двух. Но их искать надо, а 74я логика везде продаëтся.
При ШИМ 100% (постоянной логической 1) VCC будет равно напряжению батарейки. При 90%, например, VCC = Vbat / 0,9 то есть, на 11% выше Vbat.
Только аккуратно прошивку писать надо. Сначала отладить, а уже потом впаять катушку. Иначе при ошибке в софте мк сам себя сожжëт.

m039 Автор
10.09.2025 08:35Вот как, интересно! Спасибо. Чтобы получше понять, сначало соберу эту схему в симуляторе, а потом поробую в каком-нибудь своем следующем устройстве.

ahabreader
10.09.2025 08:35Кажется, оно должно плохо работать из-за ошибок в алгоритме.
Динамическая индикация не даёт зажечь все светодиоды одновременно, из-за неё мы можем одновременно зажечь только 1 строку (или столбец). То есть одновременно может гореть только 1/8 часть матрицы.
Но из-за этого кода во внутреннем цикле получается сокращение до 1/64:
digitalWrite(col[thisCol], thisPixel); if (thisPixel == HIGH) { // <-- неявная задержка из-за медленности digitalWrite, // благодаря которой матрица вообще светится. digitalWrite(col[thisCol], LOW); }А из-за отсутствия задержек много времени будет потрачено на исполнение кода (обслуживающего индикацию), в это время матрица не горит.
Из delayWithRefresh() можно почти всё убрать.
void delayWithRefresh(unsigned long d) { unsigned long prevTime = millis(); unsigned long duration = 0; while (true) { unsigned long time = millis(); unsigned long tmp = (time - prevTime); prevTime = time; duration += tmp; if (duration > d) { break; } refreshScreen(); } }void delayWithRefresh(uint32_t durationInMs) { uint32_t startTime = millis(); while(millis() - startTime < durationInMs) { refreshScreen(); } }Регулировка яркости если работает, то из-за аналогичной неявной задержки (на вызов milis() и т.д.) - у условия
if (_pwm < _intensity) {<проводим здесь xxx времени>}нет ветки else с аналогичной задержкой.В displayFrame() (который всё-таки не показывает, а загружает в SRAM) заранее упакованные (1 строка - 1 байт) значения из флеш-памяти распаковываются (8 проходов по каждому байту) и снова упаковываются, хотя их достаточно только прочитать из флеша. А читать их можно побайтово там, где они используются в коде - эта функция и массив _pixels оказываются не нужны[*].
Можно двоичные литералы применить здесь:
const uint8_t ghost_Frame_1[8] PROGMEM = { 0x1c, 0x3e ...C++14 их даёт в таком виде: 0b0010'1111, но если компилятор старее, то библиотеки Arduino дают их в виде констант препроцессора типа B00101111.
const uint8_t ghost_Frame_1[8] PROGMEM = { 0b00011100, 0b00111110, 0b01101011, 0b01001001, 0b01111111, 0b01100011, 0b01111111, 0b01010101 };Как видится полноценный алгоритм для матрицы:
Для каждой строки:
Подготовить столбцы - подать напряжение (логическую единицу) на нужные аноды диодов[**]
Включить строку - подать лог.0 на строку (т.е. на катоды диодов, чтобы ток втекал в мк)
Задержка - такая, чтобы получить частоту обновления в 100..1000 Гц
Выключить строку - подать лог.1 на строку
<-- сюда можно перекинуть часть задержки для реализации регулировки яркости
[*] вообще, особое отношение к константам во флеше требуется из-за того, что AVR с его гарвардской архитектурой слишком старый с точки зрения C++ - расширение Named Address Spaces есть только для C.
[**] без Arduino-фреймворка это можно сократить до
PORTB = _pixels(i);(вместо того внутреннего цикла), если развести плату так, чтобы выводы столбцов приходились на один порт. Но в любом случае, фреймворк здесь добавляет проблем - заставляет думать о тяжести восьми вызовов digitalWrite().___
Какая-то статья про динамическую индикацию с хорошим описанием алгоритма: https://microsin.net/programming/avr/led-matrix-dynamic-indication.html
___
Исправление алгоритма повысит ток (как и уменьшение резисторов), надо подумать:
насколько просядет напряжение питания (=> стабильность микроконтроллера)
не станет ли яркость строки зависеть от количества включённых светодиодов из-за просадки напряжения (сейчас она не может зависеть, потому что одновременно горит только 1 светодиод)
насколько безопасно для выходов микроконтроллера

m039 Автор
10.09.2025 08:35Спасибо за развёрнутый комментарий, это действительно странно, что я сам не догадался избавиться от фреймворка Ардуино и оптимизировать код, да и никто в комментариях не предложил это. По ощущениям должно помочь, но могу сразу сказать, что алгоритм для ATtiny2313A и CH32V003 похожи, надо тогда оптимизировать оба. Но думаю лучше результат будет для ATtiny2313A. Пока вы совершенно правы, что я писал что горят 8 светодиодов одновременно, а по факту меньше из-за отсутствия оптимизации.
Вспомнил почему решил не оптимизировать, не избавляться от digitalWrite, потому что так легко от этой абстракции не избавиться, потому что используются все ножки микроконтроллера подряд (а по-другому плату не развести), т.е. там они не относятся к одному порту. Но это я так подумал, по-хорошему надо было попробовать оптимизировать и уже потом делать выводы, если не получается.
Сейчас правда понимаю, что и оптимизация не всесильна, там от многих задержек не избавиться, но возможно яркость в два раза удасться подтянуть. Я потом попробую это сделать.

ahabreader
10.09.2025 08:35Ещё, получается, displayFrame() занимается
переворачиваниемтранспонированием кадра, от которого особенно не захочется избавляться из-за возможности "рисовать кадр" двоичными литералами.И от которого нельзя удачно избавиться изменением направления сканирования матрицы (не построчно, а по столбцам), потому что зажигая строку мы имеем резистор параллельно каждому светодиоду, а при зажигании столбца такой "гарантии равномерного свечения" на этой плате не будет - будет один резистор на все светодиоды.
Я бы в такую сторону думал (выделил русским временные комменты):
for (uint8_t rowIdx = 0; rowIdx < 8; rowIdx++) { // 1. prepare columns (stored in transposed state) for (uint8_t i = 0; i < 8; i++) { // надо сделать указатель frame видимым отсюда uint8_t val = pgm_read_byte(frame + i); // viewed from software, val is bit-packed row of one frame // viewed from hardware, val is one column of LED matrix // and we need rowIdx'th bit from each column here digitalWrite(col[i], val & (1 << rowIdx)); } // 2. activate row digitalWrite(row[rowIdx], 0); // 3. delay in active state delayMicroseconds(1000); // итого частота обновления всей матрицы: до 1/(8*1000 мкс) = 125 Гц // ещё добавить к знаменателю время на (8+2)*8 = 64 вызовов digitalWrite(): // до 1/(8*(1000 мкс) + 64*(5 мкс)) = 120 Гц // 4. deactivate row digitalWrite(row[rowIdx], 1); // 5. delay in inactive state for brightness adjustment // ... }
m039 Автор
10.09.2025 08:35Да, изначальный код написан так, чтобы постараться обеспечить равномерное свечение. Попробую сделать, как вы описали, но еще перепишу digitalWrite под конкретно эту задачу. Посмотрим.
Еще, я хотел поднять вам карму, но мне написало, что это сделать не получается, потому что у вас нет публикаций. Если вам интересно, можете попробовать это исправить. Но возможно вы и так это знаете, просто решил написать на всякий случай.

m039 Автор
10.09.2025 08:35Спасибо за ваши комментарии, я переписал код и теперь брелок светит в разы ярче, почти как MAX7219. Я правда еще не оптимизировал версию с CH32V003, но думаю и там должно стать лучше. Изменил обложку, добавил код и фотографию к статье, можете там посмотреть. Попозже тогда займусь CH32V003.
CitizenOfDreams
Просто сейчас китайцы делают очень много хреновых батареек 2032 с высоким внутренним сопротивлением. Напряжение без нагрузки на них нормальное, в часах и на материнских платах работают отлично - но большие токи выдавать не способны даже кратковременно.
m039 Автор
Согласен, но хочу добавить, что они работают нормально, когда только-только их вставишь, а через время все как вы описали.
KiV66
Батарейка CR2032 фирмы Panasonic: Continuous drain 0.2mA.
Это из документации - https://industrial.panasonic.com/cdbs/www-data/pdf2/AAA4000/AAA4000C321.pdf
m039 Автор
ИИ гугла подсказывает, что при 0.2 мА достигается указанная емкость из даташита, но сама батарейка может испытывать пики по 100 мА и выше. И это верно, т.к. иначе бы брелок вообще не светился, потому что 0.2 мА это совсем как-то мало. Странно, что в даташите от Panasonic про пики и большие токи ничего не написано.