Тема: Модуляція яскравості світлодіодів
Запитання було в групі DIY Електроніка у фейсбуці, але там незручно відповідати.
Чи може хтось підказати по алгоритмам. Задача в наступному. Скажімо є лінійка світлодіодів з динамічною індикацією, яскравість кожного з яких керується через ключі цифровими сигналами. Для регулювання яскравості можна застосувати pwm модуляцію, але хочеться отримати мінімальне мерехтіння при мінімальних затратах процесорного часу. Пробував bam (binary angle modulation) модуляцію, це щось схоже на pwm, але трохи не таке - результат значно кращий, але при 50% яскравості нема виграшу в порівнянні з pwm при тій же частоті. Є ще один тип модуляції, який я пробував, якщо я не помиляюсь, він найбільш близький до pdm (pulse density modulation). Він трохи схожий на bam. Суть його полягає в тому що ми ділимо якийсь проміжок часу, скажімо на 8 частин. Тоді можемо отримати 9 рівнів яскравості (включно з нулем). Якщо нам потрібна яскравість 50%, то беремо 4 одиниці і 4 нулі і рівномірно розподіляємо їх у вигляді 0b01010101. Зараз я реалізував такий алгоритм таблично. Тобто яскравість 1 - 0b00010000, 2 - 0b10001000, 3 - 0b10010010 і т.д. Тобто задача полягає в рівномірному розподілі нулів і одиниць. Чи є якийсь простий алгоритм, щоб зробити таку модуляцію алгоритмічно? По суті, це має бути функція, що приймає на вхід значення від 0 до 8, а на виході - 1 байт з рівномірно розподіленими нулями і одиницями (в двійковій системі).Якщо хто стикався, дайте будь ласка посилання, де був би нормально пояснений принцип роботи такого алгоритма. Заздалегідь вибачаюся, якщо погано пояснив питання.
Далі у обговоренні виявилося, що алгоритм має бути реалізованим у FPGA.
У мене є два улюблених алгоритми.
DDA і «SN7497».
DDA загалом — один з алгоритмів малювання лінії в діапазоні кутів [0,45°). Біжить координата по X, а координату по Y іноді збільшують. Щоб лінія була красивіша, треба точки збільшення Y розподілити якомога рівномірно по X.
Тобто саме те, що потрібно Тарасові
Нехай у нас є переривання від таймера (у мікроконтролері) чи якийсь тактовий сигнал для суматора (у FPGA).
Тоді для 5-бітового модулятора буде така функція мікроконтролера, тут n на вході — потрібна яскравість.
enum { width = 5 };
uint8_t dda_accum = 0;
bool dda(uint8_t n) {
enum { CARRY_BIT = 1 << 5 };
dda_accum += n;
bool result = dda_accum & CARRY_BIT;
dda_accum &= ~CARRY_BIT;
return result;
}
повертає ознаку того, що у цьому такті потрібно активувати вихід (результат нижче).
Для FPGA потрібен повноцінний суматор на відповідну кількість бітів, по суматору на канал.
SN7497 — дробовий дільник, який ділить частоту на дріб 64/N де N=1..64 (або множить на дріб N/64, який менший або дорівнює одиниці). Там лічильник і логічна схема, на вхід якої яскравість подають з реверсним порядком бітів. Тому якщо у регістрі величини модуляції буде піднято старший біт (дає половину величини), то на вихід пройдуть імпульси з молодшого біта лічильника, вихід буде активним теж половину часу.
Для мікроконтролера буде такий код (n — реверсований код яскравості!).
uint8_t ie8_cnt;
bool ie8(uint8_t n)
{
uint8_t temp = ie8_cnt++;
bool result = ie8_cnt & ~temp & n;
return result;
}
Тестовий код для комп'ютера
uint8_t bit_rev(uint8_t val, uint8_t w)
{
uint8_t mask_in = 1 << (w-1);
uint8_t mask_out = 1;
uint8_t temp = 0;
while (mask_in) {
if (val & mask_in) temp |= mask_out;
mask_out <<= 1;
mask_in >>= 1;
}
return temp;
}
void test(uint8_t value)
{
ie8_cnt = 0;
dda_accum = 0;
printf("value = 0x%02X, rev=0x%02X\n", value, bit_rev(value,width));
printf(" ");
for (int i = 0; i < (1 << width); ++i) putchar('0' + i % 10);
printf("\nDDA ");
for (int i = 0; i < (1 << width); ++i) putchar(dda(value) ? '1' : '_');
printf("\nIE8 ");
value = bit_rev(value, width);
for (int i = 0; i < (1 << width); ++i) putchar(ie8(value) ? '1' : '_');
putchar('\n');
}
int main()
{
test(0);
test(5);
test(7);
test(11);
test(19);
}
І буде такий результат
value = 0x05, rev=0x14
01234567890123456789012345678901
DDA ______1_____1______1_____1_____1______1_____1______1_____1_____1
IE8 ___1_______1___1___1_______1_______1_______1___1___1_______1____
value = 0x07, rev=0x1C
01234567890123456789012345678901
DDA ____1____1___1____1___1____1___1____1____1___1____1___1____1___1
IE8 ___1___1___1___1___1___1___1_______1___1___1___1___1___1___1____
value = 0x0B, rev=0x1A
01234567890123456789012345678901
DDA __1__1__1__1__1__1__1__1__1__1_1__1__1__1__1__1__1__1__1__1__1_1
IE8 _1___1_1_1___1_1_1___1_1_1___1___1___1_1_1___1_1_1___1_1_1___1__
value = 0x13, rev=0x19
01234567890123456789012345678901
DDA _1_1_11_1_11_1_11_1_11_1_11_1_11_1_1_11_1_11_1_11_1_11_1_11_1_11
IE8 1_1_1_111_1_1_111_1_1_111_1_1_1_1_1_1_111_1_1_111_1_1_111_1_1_1_
Як бачимо, DDA дає рівномірніший сигнал. Для мікроконтролера все одно, що лічильник, що суматор однак буде байт, а для режиму «SN7497» ще потрібно реверсувати біти (втім, для Cortex-M3 і вище це максимум дві команди, власне реверс і зсув на потрібну кількість бітів).
Для FPGA трохи інакше, і оптимальність залежить від архітектури. Реверс бітів ресурсів не вимагає взагалі, тому для багатьох каналів потрібен один лічильник на всі канали, по одному регістру яскравості на канал і логіка.
Але для LUT-based пристроїв регістр яскравості займе тригери логічних елементів, а логіка в них гулятиме. Її можна задіяти, тоді варіант DDA для N каналів по W бітів потребуватиме 2*N*W логічних елементів, а «SN7497» — лише (N+1)*W.
Для давно вже забутої на цей час Альтерівської серії FLEX8000 (EPF8452) я на AHDL задіяв ланцюги переносу і виписав код такого модулятора так, що вся логіка влізла в ті LUT, де були регістри яскравості. На жаль, навряд чи зараз знайду той код, дуже давно було.
p.s. IE8 — це від рядянського клона мікросхеми, називалася 155ИЕ8. І стояла вона, зокрема, у апаратному генераторі ліній векторної частини графічного дисплея ДГП-7 виробництва Сіверодонецького заводу років так 1980-затертих, блок логіки якого був розміром з газову плиту (власне, той столик, на якому стояв монітор).