На диске ёмкость нигде не указана, в названии число "42" и в магазинах его продавали как 42-меговый.
"For use also with" относится только к джамперам, у L40 другое число цилиндров. В рекламных каталогах его не найти, а в справочных каталогах он 41-меговый.
И в то время я точно не помню чтобы кто-то аппелировал к принятому стандарту разделяющему десятичные и двоичные ЕИ
...и на что отвечаете - тоже, уж извините. Я написал "это прослеживается до их появления в 50-х годах", то есть лет за 40 до стандарта IEC.
Переход на маркетинговые начался, если память не изменяет, где-то в районе 10гиговых дисков.
Изменяет. 1980-й год, ST-506, ёмкость 5.0 мегабайтов по мануалу и это десятичные мегабайты: 612 * 32 * 256 bytes = 5.01 MB = 4.78 MiB.
Он просто позволяет задать интерфейс для статического (compile-time) полиморфизма, не прибегая к концептам как к слишком новой штуке.
То есть мы в такой шаблон функции
template<typename T>
void foo(T bar) {
bar.f();
}
можем подавать* объекты любых классов, полагаясь на то, что у них есть метод f() (утиная типизация). А если нам это кажется небезопасным и хочется ограничить классы наследниками одного класса-интерфейса IBar, то это достигается с помощью CRTP.
* или как там правильно будет, "неявно специализировать"?
___
Не раскрыта тема девиртуализации. Компилятор достаточно умён, чтобы иногда избегать виртуальных вызовов. Используем CRTP = отказались от динамического полиморфизма = у компилятора есть возможность для девиртуализации, закатывание солнца вручную через CRTP может оказаться избыточным.
С таким байтом и в бите нельзя быть уверенным, ведь там, где байт определяют как наименьшую (?*) адресуемую единицу хранения данных, там бит определяют как
bit. unit of data storage in the execution environment large enough to hold an object that can have one of two values. It may not be possible to express the address of each individual bit of an object.
byte. addressable unit of data storage large enough to hold any member of the basic character set of the execution environment
* но там даже не сказано "наименьшую"?.. В обоих определениях размер ограничен снизу.
В Android с измерением свободного места было что-то ещё более странное
А именно, по крайней мере у Samsung, разницу между приставками на 2023 год прибавляют к размеру операционной системы.
Т.е. в телефоне имеется флеш-память на 512 ГиБ, без учёта служебной ёмкости (overprovisioning) доступно 512 ГБ, но в Android их хочется показать в двоичных гигабайтах (477 ГиБ), но красивее (477 двоичных контекстно-зависимых ГБ), но нет, ещё красивее (512 поддельных рекламных ГБ) - для этого разницу между 512 ГиБ и 512 ГБ прибавляют к размеру ОС, чтобы сошлось - и поэтому чем больше памяти у телефона, тем больше "весит" ОС.
когда они на hdd стали шлёпать ёмкость в десятичной системе
Они использовали десятичные гигабайты до и продолжили использовать после (и правы с точки зрения СИ, IEC или госта), но по мировому соглашению они дальше обязались добавлять приписки типа "*1TB = 1 trillion bytes" из-за винды.
То есть 80.06 GB = 74.56 GiB. Это норма. А надо найти найти хоть один обратный случай (и с учётом вероятного округления заявленной ёмкости вниз, а не до ближайшего).
Постарайтесь вспомнить. На этикетках 40-гиговых Maxtor'ов в гугле везде есть либо количество LBA, указывающее на десятичные гигабайты*, либо сразу приписка "1 GB=1,000,000,000 Bytes".
* например, продаётся как 40 ГБ и 80 293 248 LBA * 512 bytes = 41.1 GB = 38.3 GiB.
Вы распространяете заблуждения и написали статью в стиле "двухминутки ненависти" ради привлечения внимания.
Можно, конечно, поискать конспирологию в том, что это производители накопителей пролоббировали IEC
В гигабайтах тут считать выгоднее, вот маркетологи и насаждают это потребителям.
Жёсткие диски всегда использовали десятичные приставки, это прослеживается до их появления в 50-х годах.
Покупаешь жёсткий диск — на коробке написано 500 ГБ, а на самом деле там 465 ГиБ
Но на плашках RAM — «честные» гибибайты: сколько указано, столько и получаешь, только пишут всё равно 16 GB, а не 16 GiB
То есть правила СИ&IEC соблюдаются в HDD и нарушаются в RAM, но вы пытаетесь это вывернуть так, чтобы получить больше заявленного.
Ранние ОС действительно использовали систему подсчёта данных, основанную на степенях двойки
Ранняя macOS. Windows и дальше продолжает использовать двоичные килобайты, в Linux перешли на кибибайты. Если же смотреть из современной macOS, то вы увидите жёсткий диск на 500 ГБ как жёсткий диск на 500 ГБ и всё равно недовольны.
Или всё-таки на заре компьютеров бумеры-основатели зря решили пойти против существующих стандартов
Вы не предлагаете решения для тех времён и почему-то считаете систему СИ нерушимым абсолютом, хотя у Международного бюро мер и весов нет своей армии. Правила СИ&IEC до сих пор нарушаются и у Apple (в оперативной памяти), и у производителей различных микросхем. В Android с измерением свободного места было что-то ещё более странное. Они обычно смотрят на вопрос с рекламной стороны (как "1.44 МБ" раньше), а вы не замечаете.
так скорость выглядит в 8 раз больше
А размеры аудио-/видеофайлов в 8 раз больше? Битрейт (не байтрейт) - обычная практика.
ввела терминологию — киби-, меби-, гиби-, теби- и так далее — для того, чтобы отличать одно от другого и устранить растущую путаницу среди пользователей.
Но не дала механизма, который бы позволил отличить старые контекстно-зависимые килобайты от новых точно-десятичных килобайтов. Даже если все бы одновременно перешли на новые правила, старые тексты бы не исчезли. По ссылке выше такую ошибку допустили.
И вообще вроде бы есть специальные названия для ... «ровных»
сетевыми инженерами, которые считают 1 Гбит/с = 1 048 576 бит/с
В существование этих точек зрения после перечисленного не верится.
Да, этот прирост не нужен. Такие оптимизации пригодились бы, если бы у микроконтроллера были другие задачи, тогда ещё были бы нужны неблокирующие задержки, пришлось бы думать про (не)атомарность изменения PORTx.
использования регистров PINx (переключение пина без read-modify-write).
Я тут не подумал, что строка PORTB |= (1 << PB3); должна компилироваться в одну инструкцию SBI (Set Bit in I/O Register).
Так как почти весь прирост должен быть от зажигания 8 светодиодов вместо 1 и задержки во включённом состоянии, не стал писать про отказ от digitalWrite().
Но вообще тогда думал, что можно упороться вплоть до использования регистров PINx (переключение пина без read-modify-write). И чересстрочной развёртки, чтобы достичь незаметности мерцания на более низкой частоте:
Идеи
for (uint8_t rowIdx_ = 0; rowIdx_ < 8; rowIdx_++) {
uint8_t rowIdx = rowIdx_;
#if defined(USE_INTERLACED_SCAN)
rowIdx = rowIdx_/4 + 2*(rowIdx_%4); // 0,2,4,6,1,3,5,7
#endif
// 1. prepare columns
#define H(X) readAndUnpackPixel(frame, X, rowIdx) // helper macro
setMatrixColumns(H(0), H(1), H(2), H(3), H(4), H(5), H(6), H(7));
#undef H
// 2. activate row
ToggleRow(rowIdx);
// 3. delay in active state
delayMicroseconds(1000);
// 4. deactivate row
ToggleRow(rowIdx);
}
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
// Для инициализации; в т.ч. когда обновление прерывается,
// оставляя матрицу в неизвестном состоянии.
void SetRowsToHigh() {
PORTD |= ((1 << 2) | (1 << 1) | (1 << 4) | (1 << 5));
PORTB |= ((1 << 1) | (1 << 2) | (1 << 4));
PORTA |= ((1 << 1));
}
void ToggleRow(uint8_t rowIdx) {
switch(rowIdx) {
case 0: PIND = (1 << 2); break;
case 1: PINB = (1 << 1); break;
case 2: PINB = (1 << 2); break;
case 3: PIND = (1 << 1); break;
case 4: PINB = (1 << 4); break;
case 5: PINA = (1 << 1); break;
case 6: PIND = (1 << 4); break;
case 7: PIND = (1 << 5); break;
}
}
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
inline bool readAndUnpackPixel(frame, uint8_t colIdx, uint8_t rowIdx) {
return (bool)(pgm_read_byte(frame + colIdx) & (1 << rowIdx));
// ^^^^^^~~~~ изначально написал !! вместо этого, но не знаю,
// что лучше выразит намерение нормализовать bool
}
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
inline void setMatrixColumns(bool b0, bool b1, bool b2, bool b3,
bool b4, bool b5, bool b6, bool b7) {
PORTD &= ~((1 << 0) | (1 << 3) | (1 << 6));
PORTB &= ~((1 << 6) | (1 << 5) | (1 << 0) | (1 << 3));
PORTA &= ~((1 << 0));
// ^~~~~ можно вызывать реже,
// если в PINx передавать изменения относительно предыдущей строки
PIND = (b0 << 0) | (b1 << 3) | (b4 << 6);
PINB = (b2 << 6) | (b5 << 5) | (b6 << 0) | (b7 << 3);
PINA = (b3 << 0);
}
С последними стандартами плюсов, наверное, можно без особых ужасов проинициализировать массив consteval-функцией, которая бы делала необходимые оптимизации (повернуть массив в compile-time при необходимости и класть дельту начиная со второй строки матрицы, чтобы не считать её в рантайме).
Это что-то вроде простого баланса белого, но такого подхода недостаточно, чтобы определить RGB-пространство, в котором находимся, а из неизвестного пространства нельзя перейти в CIE XYZ, чтобы дальше перейти в OKLab.
Clear вроде приспосабливали для программной фильтрации инфракрасного света.
Этот датчик оказывается шатким фундаментом для OKLab, особенно если без калибровки по ColorChecker'у.
____
Заодно поправка к статье: адекватный перевод для gamut - это "цветовой охват", точно не "спектр".
Ещё, получается, 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
// ...
}
Кажется, оно должно плохо работать из-за ошибок в алгоритме.
Динамическая индикация не даёт зажечь все светодиоды одновременно, из-за неё мы можем одновременно зажечь только 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();
}
}
Регулировка яркости если работает, то из-за аналогичной неявной задержки (на вызов milis() и т.д.) - у условия if (_pwm < _intensity) {<проводим здесь xxx времени>} нет ветки else с аналогичной задержкой.
В displayFrame() (который всё-таки не показывает, а загружает в SRAM) заранее упакованные (1 строка - 1 байт) значения из флеш-памяти распаковываются (8 проходов по каждому байту) и снова упаковываются, хотя их достаточно только прочитать из флеша. А читать их можно побайтово там, где они используются в коде - эта функция и массив _pixels оказываются не нужны[*].
Включить строку - подать лог.0 на строку (т.е. на катоды диодов, чтобы ток втекал в мк)
Задержка - такая, чтобы получить частоту обновления в 100..1000 Гц
Выключить строку - подать лог.1 на строку
<-- сюда можно перекинуть часть задержки для реализации регулировки яркости
[*] вообще, особое отношение к константам во флеше требуется из-за того, что AVR с его гарвардской архитектурой слишком старый с точки зрения C++ - расширение Named Address Spaces есть только для C.
[**] без Arduino-фреймворка это можно сократить до PORTB = _pixels(i); (вместо того внутреннего цикла), если развести плату так, чтобы выводы столбцов приходились на один порт. Но в любом случае, фреймворк здесь добавляет проблем - заставляет думать о тяжести восьми вызовов digitalWrite().
Исправление алгоритма повысит ток (как и уменьшение резисторов), надо подумать:
насколько просядет напряжение питания (=> стабильность микроконтроллера)
не станет ли яркость строки зависеть от количества включённых светодиодов из-за просадки напряжения (сейчас она не может зависеть, потому что одновременно горит только 1 светодиод)
Расстояние между цветами в пространстве OKLCH как считается?
Как евклидово расстояние в цилиндре - т.е. не надо OKLab переводить в его версию с полярными цилиндрическими координатами (OKLCH), надо остаться в OKLab
"Because Oklab is more perceptually uniform than CIE Lab, the color difference is a straightforward distance in 3D space (root sum of squares)" https://www.w3.org/TR/css-color-4/
Ну dE в CIELab тоже нифига не евклидово.
Этот вариант и в CIELab имеет право на жизнь, только CIELab хуже (неравномернее) и сильно стандартизирован, что, видимо, подкрепляло идею улучшать "perceptually uniformness" у вариантов вычисления расстояния, а не у пространства (не менять пространство). И с уклоном в науку, а не красивые градиенты - "Международная комиссия" может больше углубляться в измерение расстояния.
Вы что-то недопоняли и продолжаете DDOS-атаку на меня.
Там вверху написано "LBA 145226112".
https://www.google.com/search?q=145226112+*+512+bytes+to+GB ~74 ГБ
оно же с двоичной приставкой будет
https://www.google.com/search?q=145226112+*+512+bytes+to+GiB ~69 ГиБ
То есть гигабайты на вашем диске десятичные, а я изначально заявил, что они на всех жёстких дисках десятичные.
Так вы смотрите, на что ссылаетесь.
На диске ёмкость нигде не указана, в названии число "42" и в магазинах его продавали как 42-меговый.
"For use also with" относится только к джамперам, у L40 другое число цилиндров. В рекламных каталогах его не найти, а в справочных каталогах он 41-меговый.
...и на что отвечаете - тоже, уж извините. Я написал "это прослеживается до их появления в 50-х годах", то есть лет за 40 до стандарта IEC.
Изменяет. 1980-й год, ST-506, ёмкость 5.0 мегабайтов по мануалу и это десятичные мегабайты: 612 * 32 * 256 bytes = 5.01 MB = 4.78 MiB.
Он просто позволяет задать интерфейс для статического (compile-time) полиморфизма, не прибегая к концептам как к слишком новой штуке.
То есть мы в такой шаблон функции
можем подавать* объекты любых классов, полагаясь на то, что у них есть метод f() (утиная типизация). А если нам это кажется небезопасным и хочется ограничить классы наследниками одного класса-интерфейса IBar, то это достигается с помощью CRTP.
* или как там правильно будет, "неявно специализировать"?
___
Не раскрыта тема девиртуализации. Компилятор достаточно умён, чтобы иногда избегать виртуальных вызовов. Используем CRTP = отказались от динамического полиморфизма = у компилятора есть возможность для девиртуализации, закатывание солнца вручную через CRTP может оказаться избыточным.
*Полушутя*
С таким байтом и в бите нельзя быть уверенным, ведь там, где байт определяют как наименьшую (?*) адресуемую единицу хранения данных, там бит определяют как
bit. unit of data storage in the execution environment large enough to hold an object that can have one of two values. It may not be possible to express the address of each individual bit of an object.
byte. addressable unit of data storage large enough to hold any member of the basic character set of the execution environment
* но там даже не сказано "наименьшую"?.. В обоих определениях размер ограничен снизу.
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3220.pdf#page=17
А именно, по крайней мере у Samsung, разницу между приставками на 2023 год прибавляют к размеру операционной системы.
Т.е. в телефоне имеется флеш-память на 512 ГиБ, без учёта служебной ёмкости (overprovisioning) доступно 512 ГБ, но в Android их хочется показать в двоичных гигабайтах (477 ГиБ), но красивее (477 двоичных контекстно-зависимых ГБ), но нет, ещё красивее (512 поддельных рекламных ГБ) - для этого разницу между 512 ГиБ и 512 ГБ прибавляют к размеру ОС, чтобы сошлось - и поэтому чем больше памяти у телефона, тем больше "весит" ОС.
Суд был, а этого не было:
Они использовали десятичные гигабайты до и продолжили использовать после (и правы с точки зрения СИ, IEC или госта), но по мировому соглашению они дальше обязались добавлять приписки типа "*1TB = 1 trillion bytes" из-за винды.
Там не прижились. Прижились в некотором софте - Linux в целом, ffmpeg...
Или из вежливости. Если читатель на самом деле не догадывается о точном значении приставки, его ждут ошибки в таких случаях:
"10.4 KB per track" - даташит на диск от пре-WD (Tandon)
"Head setting time: 15 Msec" - на диск из того диска
"768.0 Kbit/s" - пишет организация со 100-летней историей, National Association of Broadcasters.
"128 Kbps" - говорит американская ГКРЧ (FCC)
"9.6 Kbaud"
То есть 80.06 GB = 74.56 GiB. Это норма. А надо найти найти хоть один обратный случай (и с учётом вероятного округления заявленной ёмкости вниз, а не до ближайшего).
Постарайтесь вспомнить. На этикетках 40-гиговых Maxtor'ов в гугле везде есть либо количество LBA, указывающее на десятичные гигабайты*, либо сразу приписка "1 GB=1,000,000,000 Bytes".
* например, продаётся как 40 ГБ и 80 293 248 LBA * 512 bytes = 41.1 GB = 38.3 GiB.
Если ещё кто-нибудь вспомнит, то мы-таки получим настоящий эффект Манделы.
Вы распространяете заблуждения и написали статью в стиле "двухминутки ненависти" ради привлечения внимания.
Жёсткие диски всегда использовали десятичные приставки, это прослеживается до их появления в 50-х годах.
То есть правила СИ&IEC соблюдаются в HDD и нарушаются в RAM, но вы пытаетесь это вывернуть так, чтобы получить больше заявленного.
Ранняя macOS. Windows и дальше продолжает использовать двоичные килобайты, в Linux перешли на кибибайты. Если же смотреть из современной macOS, то вы увидите жёсткий диск на 500 ГБ как жёсткий диск на 500 ГБ и всё равно недовольны.
Вы не предлагаете решения для тех времён и почему-то считаете систему СИ нерушимым абсолютом, хотя у Международного бюро мер и весов нет своей армии. Правила СИ&IEC до сих пор нарушаются и у Apple (в оперативной памяти), и у производителей различных микросхем. В Android с измерением свободного места было что-то ещё более странное. Они обычно смотрят на вопрос с рекламной стороны (как "1.44 МБ" раньше), а вы не замечаете.
А размеры аудио-/видеофайлов в 8 раз больше? Битрейт (не байтрейт) - обычная практика.
Но не дала механизма, который бы позволил отличить старые контекстно-зависимые килобайты от новых точно-десятичных килобайтов. Даже если все бы одновременно перешли на новые правила, старые тексты бы не исчезли. По ссылке выше такую ошибку допустили.
В существование этих точек зрения после перечисленного не верится.
Зачем там больше гигабита?
Наверное, никак - в новые 8K-телевизоры 100 мбит/с ставят, прогоняя пользователей на Wi-Fi.
В даташите найти можно, хотя лучше знать, что искать.
https://ww1.microchip.com/downloads/en/DeviceDoc/doc8246.pdf#page=57
Да, этот прирост не нужен. Такие оптимизации пригодились бы, если бы у микроконтроллера были другие задачи, тогда ещё были бы нужны неблокирующие задержки, пришлось бы думать про (не)атомарность изменения PORTx.
Я тут не подумал, что строка
PORTB |= (1 << PB3);должна компилироваться в одну инструкцию SBI (Set Bit in I/O Register).Так как почти весь прирост должен быть от зажигания 8 светодиодов вместо 1 и задержки во включённом состоянии, не стал писать про отказ от digitalWrite().
Но вообще тогда думал, что можно упороться вплоть до использования регистров PINx (переключение пина без read-modify-write). И чересстрочной развёртки, чтобы достичь незаметности мерцания на более низкой частоте:
Идеи
С последними стандартами плюсов, наверное, можно без особых ужасов проинициализировать массив consteval-функцией, которая бы делала необходимые оптимизации (повернуть массив в compile-time при необходимости и класть дельту начиная со второй строки матрицы, чтобы не считать её в рантайме).
О, классно.
____
Заодно поправлю себя в прошлом комменте:
резистор
параллельнокаждому светодиоду(8+2)*8 =6480*Некропост*
Это что-то вроде простого баланса белого, но такого подхода недостаточно, чтобы определить RGB-пространство, в котором находимся, а из неизвестного пространства нельзя перейти в CIE XYZ, чтобы дальше перейти в OKLab.
Clear вроде приспосабливали для программной фильтрации инфракрасного света.
Этот датчик оказывается шатким фундаментом для OKLab, особенно если без калибровки по ColorChecker'у.
____
Заодно поправка к статье: адекватный перевод для gamut - это "цветовой охват", точно не "спектр".
Ещё, получается, displayFrame() занимается
переворачиваниемтранспонированием кадра, от которого особенно не захочется избавляться из-за возможности "рисовать кадр" двоичными литералами.И от которого нельзя удачно избавиться изменением направления сканирования матрицы (не построчно, а по столбцам), потому что зажигая строку мы имеем резистор параллельно каждому светодиоду, а при зажигании столбца такой "гарантии равномерного свечения" на этой плате не будет - будет один резистор на все светодиоды.
Я бы в такую сторону думал (выделил русским временные комменты):
Кажется, оно должно плохо работать из-за ошибок в алгоритме.
Динамическая индикация не даёт зажечь все светодиоды одновременно, из-за неё мы можем одновременно зажечь только 1 строку (или столбец). То есть одновременно может гореть только 1/8 часть матрицы.
Но из-за этого кода во внутреннем цикле получается сокращение до 1/64:
А из-за отсутствия задержек много времени будет потрачено на исполнение кода (обслуживающего индикацию), в это время матрица не горит.
Из delayWithRefresh() можно почти всё убрать.
Регулировка яркости если работает, то из-за аналогичной неявной задержки (на вызов milis() и т.д.) - у условия
if (_pwm < _intensity) {<проводим здесь xxx времени>}нет ветки else с аналогичной задержкой.В displayFrame() (который всё-таки не показывает, а загружает в SRAM) заранее упакованные (1 строка - 1 байт) значения из флеш-памяти распаковываются (8 проходов по каждому байту) и снова упаковываются, хотя их достаточно только прочитать из флеша. А читать их можно побайтово там, где они используются в коде - эта функция и массив _pixels оказываются не нужны[*].
Можно двоичные литералы применить здесь:
C++14 их даёт в таком виде: 0b0010'1111, но если компилятор старее, то библиотеки Arduino дают их в виде констант препроцессора типа B00101111.
Как видится полноценный алгоритм для матрицы:
Для каждой строки:
Подготовить столбцы - подать напряжение (логическую единицу) на нужные аноды диодов[**]
Включить строку - подать лог.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 светодиод)
насколько безопасно для выходов микроконтроллера
Как евклидово расстояние в цилиндре - т.е. не надо OKLab переводить в его версию с
полярнымицилиндрическими координатами (OKLCH), надо остаться в OKLab"Because Oklab is more perceptually uniform than CIE Lab, the color difference is a straightforward distance in 3D space (root sum of squares)" https://www.w3.org/TR/css-color-4/
Этот вариант и в CIELab имеет право на жизнь, только CIELab хуже (неравномернее) и сильно стандартизирован, что, видимо, подкрепляло идею улучшать "perceptually uniformness" у вариантов вычисления расстояния, а не у пространства (не менять пространство). И с уклоном в науку, а не красивые градиенты - "Международная комиссия" может больше углубляться в измерение расстояния.