Обсуждаем контроллеры компании Atmel.
Ответить

Управление адресной светодиодной лентой с помощью atmega8

Вт дек 19, 2023 21:55:10

Доброго времени дня! Помогите пожалуйста новичку. Пытаюсь управлять адресной светодиодной лентой (WS2811) с помощью atmega8 (далее - МК). Хочу просто зажечь самый первый светодиод в ленте зеленым цветом. Но у меня вся лента просто загорается белым.
Лента питается от отдельного источника 12В.
МК питается от 5В через USB. (тактирование от внутреннего кварца. Частоту в коде задал 8МГц)
Нули питания МК и ленты соединены между собой на макетной плате.
Управляющий сигнал идет от PORTB.1 через резистор 220 Ом.
В коде прописал 3 цикла (по одному на каждый цвет в светодиоде).
Насколько я знаю цвет загорается когда сперва передаем логическую единицу в течение 0.8мкс, а ноль в течение 0.45 мкс. А гаснет светодиод при передаче единицы в течение 0.4мкс, а нуля в течение 0.85мкс.
Зеленый цвет ставлю включаю (байты G7...G0), а красный(R7...R0) и синий(B7...B0) выключаю.

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

Сам код программы в Atmel Studio:
#define F_CPU 8000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>

int main(void)
{
DDRB=0b1111111;
PORTB=0b0000000;
while (1)
{
for(int i=0;i<8;++i) //передаем биты 1 в G7...G0
{
PORTB=0b0000010;
_delay_us(0.8);
PORTB=0b0000000;
_delay_us(0.4);
}

for(int i=0;i<8;++i) //передаем биты 0 в R7...R0
{
PORTB=0b0000010;
_delay_us(0.4);
PORTB=0b0000000;
_delay_us(0.85);
}

for(int i=0;i<8;++i) //передаем биты 0 в B7...B0
{
PORTB=0b0000010;
_delay_us(0.4);
PORTB=0b0000000;
_delay_us(0.85);
}
_delay_us(50);
}
}
Вложения
скрин2.jpg
(228.78 KiB) Скачиваний: 26

Re: Управление адресной светодиодной лентой с помощью atmega

Вт дек 19, 2023 23:13:43

посмотри осцилом - что ты передаешь. будет три байтных посылок с зазорами. это не прокатит.
растаскивай свои байты втрое - в битовую последовательность и передавай по SPI.

Re: Управление адресной светодиодной лентой с помощью atmega

Ср дек 20, 2023 07:52:31

сперва передаем логическую единицу в течение 0.8мкс, а ноль в течение 0.45 мкс. А гаснет светодиод при передаче единицы в течение 0.4мкс, а нуля в течение 0.85мкс.
for(int i=0;i<8;++i) //передаем биты 1 в G7...G0
{
PORTB=0b0000010;
_delay_us(0.8 );
PORTB=0b0000000;
_delay_us(0.4);
}

for(int i=0;i<8;++i) //передаем биты 0 в R7...R0
{
PORTB=0b0000010;
_delay_us(0.4);
PORTB=0b0000000;
_delay_us(0.85);
}

Это шуткатакой? :facepalm: :)))
Любезный, у вас частота ядра МК - 8 МГц. То есть всего ОДНА одноцикловая инструкция (а они далеко не все одноцикловые, особенно условное и безусловное ветвление кода) выполняется за 0,125 мкс.
Каким таким таинственным способом может быть реализована функция _delay_us(0.4), например, если она не кратна 0,125?
Каким таким образом вы полагаете исполнение цикла и управление портом за 0 мкс?
С чего вы вообще взяли, что функция _delay_us() в состоянии оперировать дробным аргументом?

Re: Управление адресной светодиодной лентой с помощью atmega

Ср дек 20, 2023 08:42:26

Я делал с ассемблерной вставкой
Спойлер
Код:
//void Send(uint8_t r, uint8_t g, uint8_t b) {                                   // Послать элемент на ленту
//void Send(flash uint8_t *addr) {                                   // Послать элемент на ленту по указателю из флеша
void Send(uint8_t *addr) {                                   // Послать элемент на ленту по указателю из ОЗУ
     #asm
        ;ldd  r26,y+2              ; Загрузить в r26 &b
        ;ldd  r27,y+1              ; Загрузить в r26 &r
        ;ld   r25,y                ; Загрузить в r26 g   
        MOVW R30,R26
        ;lpm  r26,z++      ;Загрузка по указателю из флеш
        ;lpm  r27,z++
        ;lpm  r25,z
        ld   r26,z++         ;Загрузка по указателю из ОЗУ
        ld   r27,z++
        ld   r25,z
        ldi  r30,24               ; Загрузить в r22 количество передаваемых бит
        dec  r30                  ; уменьшить количество передаваемых бит на 1
     label_1:                     ; Начало цикла
        brmi label_2              ; Если все биты передались выйти 
        in   r31,0x18             ; запомним состояние порта   !!!!0х18 - адрес порта, автоматизировать не смог!!!!   
     #endasm
     PIN_WS2812=1;          // поднимем пин
     #asm
        ;настройка задержек при изменении тактовой частоты производится изменением числа НОПов
        ;nop                       ; Подождать
        ;nop                       ; Подождать     
        nop                       ; Подождать
        sbrs r27,7                ; Пропустить следующую команду если бит в регистре r27 установлен
        out  0x18,r31             ; восстановим состояние порта (низкий уровень на пине) - с этой командой не пляшет общее время посылки
        ;      !!!!0х18 - адрес порта, автоматизировать не смог!!!!
        ;nop                       ; Подождать
        nop                       ; Подождать
        lsl  r25                   ; g<<1
        rol  r26                   ; b<<1 через перенос чтобы была непрерывность
        rol  r27                   ; r<<1 через перенос чтобы была непрерывность
        dec  r30                   ; уменьшить количество передаваемых бит на 1
     #endasm
     PIN_WS2812=0;          //  Пин к земле
     #asm
        ;nop                       ; Подождать
        ;nop                       ; Подождать
        rjmp label_1              ; Перейти к началу цикла
     label_2:                     ; Выйти из функции
     #endasm                     
     PIN_WS2812=0;          // Пин к земле
    } 
код не допилен до удобоваримого вида, но рабочий.
ПС написано и проверено в Кодевижен.
задефайнены:
#define uint8_t unsigned char
#define PIN_WS2812 PORTB.0

вызов:
Спойлер
Код:
for (i=0;i<sizeof(Chkala);i+=3)  {Send(&Chkala[i]);};
где Chkala[] - массив с выводимыми данными

Re: Управление адресной светодиодной лентой с помощью atmega

Ср дек 20, 2023 11:31:12

Каким таким таинственным способом может быть реализована функция _delay_us(0.4), например, если она не кратна 0,125?
Ето 1 цикл (ticks). Пересчитывайте из библиотечного кода. Получается задержка МК в 1 такт.
Спойлер
Код:
void _delay_us(double __us)
{
        double __tmp ;
#if __HAS_DELAY_CYCLES && defined(__OPTIMIZE__) && \
  !defined(__DELAY_BACKWARD_COMPATIBLE__) &&       \
  __STDC_HOSTED__
        uint32_t __ticks_dc;
        extern void __builtin_avr_delay_cycles(unsigned long);
        __tmp = ((F_CPU) / 1e6) * __us;

        #if defined(__DELAY_ROUND_DOWN__)
                __ticks_dc = (uint32_t)fabs(__tmp);

        #elif defined(__DELAY_ROUND_CLOSEST__)
                __ticks_dc = (uint32_t)(fabs(__tmp)+0.5);

        #else
                //round up by default
                __ticks_dc = (uint32_t)(ceil(fabs(__tmp)));
        #endif

        __builtin_avr_delay_cycles(__ticks_dc);

#else
        uint8_t __ticks;
        double __tmp2 ;
        __tmp = ((F_CPU) / 3e6) * __us;
        __tmp2 = ((F_CPU) / 4e6) * __us;
        if (__tmp < 1.0)
                __ticks = 1;
        else if (__tmp2 > 65535)
        {
                _delay_ms(__us / 1000.0);
        }
        else if (__tmp > 255)
        {
                uint16_t __ticks=(uint16_t)__tmp2;
                _delay_loop_2(__ticks);
                return;
        }
        else
                __ticks = (uint8_t)__tmp;
        _delay_loop_1(__ticks);
#endif
}

8000000 / 3000000 * 0.4 = 1.0666 -> _delay_loop_1(1); будет 1 такт (tick).
А и каждое меньшее число чем 0.4 сокращается до 1 такт.
(Автор кода решает, нужно ли это. В данном случае я бы пропустил написание этой строки).

Re: Управление адресной светодиодной лентой с помощью atmega

Ср дек 20, 2023 11:43:13

Пересчитывайте из библиотечного кода. Получается задержка МК в 1 такт.

Не получается. Просто вызов функции и возврат из нее - это ЧЕТЫРЕ машинных цикла. То есть 0,5 мкс при 8 МГц системной частоты.

Re: Управление адресной светодиодной лентой с помощью atmega

Ср дек 20, 2023 11:45:12

Возможно. Переход с C на ASM мне не близок.
Код:
PORTB=0b0000010;
PORTB=0b0000000;
в других быстрых устройствах работает (напр. AD98XX) напрямую без задержек даже на 16, 32 MHz. Но WS2811 не отличается особой скоростью. Бы добавил целое число задержки.

Re: Управление адресной светодиодной лентой с помощью atmega

Ср дек 20, 2023 12:17:18

WS2811 не отличается особой скоростью.
Времена диаграммы автор озвучил. Можно сделать на nop-ах в АСМе, точно посчитав циклы.
Но лучше делать аппаратно на таймере, либо SPI. Скорость там достаточно высокая с учетом выбранной частоты работы МК.
Возможно.

Тут есть два варианта.
Либо это макрос и его тело вставляется вместо функции, а содержимое считается не в рантайме, а на этапе компиляции. Тогда там можно откалибровать с точностью до 1 машинного цикла на основании дефайна системной частоты.
Либо это полноценная функция с передачей в нее аргумента и внутренним циклом. Тогда ни о каких единицах микросекунд речи быть не может.

Re: Управление адресной светодиодной лентой с помощью atmega

Ср дек 20, 2023 12:39:29

Два простейших решения -
1. сделать проект под ардуиноIDE, спользуя библиотеку Adafruit_NeoPixel;
(в составе IDE достаточно "платформ" под атмегу8)
2. использовать ассемблер при максимально возможной частоте МК.
( к примеру таким фрагментом:
Спойлер
Код:
;
;
;         trd2812_ma.txt
;
;         файл обработчика передачи массива
; из буфера вывода в линейку на основе WS2812B
; базовый МК из линейки АТМЕЛ при тактовой частоте
; от 16 Мегагерц ( 0,000000062 S)
;
; требуемые интервалы по даташиту WS2812B
;
;Data transfer time( TH+TL=1.25µs±600ns)
;  T0H  0 code ,high voltage time  0.4us   ±150ns
;  T1H  1 code ,high voltage time  0.8us   ±150ns
;  T0L  0 code ,low voltage time   0.85us  ±150ns
;  T1L  1 code ,low voltage time   0.45us  ±150ns
;  RES  low voltage time  Above 50µs
; исходный уровень линии связи = 0
; данные передаются пакетами из трех байт на точку
; старшими битами вперед в последовательности
; соответствующей G - R - B цветам точки
; количество блоков должно соответствовать
; количеству точек в ленте
;
; реальные данные согласно тест - отладки дебаггером (версия1!)
; авр-студио 4.19
;
; Data transfer time( TH+TL=1.38µs -10ns)
;  T0H  0 code ,high voltage time  0.44us  ±10ns
;  T1H  1 code ,high voltage time  0.88us  ±10ns
;  T0L  0 code ,low voltage time   0.94us  ±10ns
;  T1L  1 code ,low voltage time   0.50us  ±10ns
;  RES  low voltage time  192,88uS (Above 50µs)
;
; длина прерывания с пакетом загрузки (x60*3) = 2175uS (0.002175)
; интервал между прерываниями (irq t/c0) = 0.004S (4000uS)
;
;             define datas
; .equ port_out = PORTB ; порт вывода (по усмотрению)
; .equ out_line = 0 ; линия вывода данных
; .equ bufout = SRAM_START ; начальный адрес буфера вывода
; .equ pixel = 60 ; количество точек в линейке/ленте
; .equ bufout_size = (pixel * 3) ; не может быть более объема ОЗУ - стек!!!


;таблица обьявленных имен - переназначение регистров РОН
;
; .def name = r31 ; ZH регистр (полный)
; .def name = r30 ; ZL регистр (полный)
; .def name = r29 ; YH регистр (полный)
; .def name = r28 ; YL регистр (полный)
; .def name = r27 ; XH регистр (полный) указатель текущей ячейки массива bufout
; .def name = r26 ; XL регистр (полный) указатель текущей ячейки массива bufout
; .def name = r25 ; регистр (полный) BH
; .def name = r24 ; регистр (полный) BL
; .def name = r23 ; регистр (полный)
; .def name = r22 ; регистр (полный)
; .def name = r21 ; регистр (полный)
; .def name = r20 ; регистр (полный)
; .def name = r19 ; регистр (полный)
; .def name = r18 ; регистр (полный)
; .def tmp1 = r17 ; регистр (полный) счетчик байт вывода
; .def tmp0 = r16 ; регистр (полный) буфер выводимого байта
; .def regn = r15 ; регистр (урезан)
; .def regn = r14 ; регистр (урезан)
; .def regn = r11 ; регистр (урезан)
; .def regn = r10 ; регистр (урезан)
; .def regn = r9 ; регистр (урезан)
; .def regn = r8 ; регистр (урезан)
; .def regn = r7 ; регистр (урезан)
; .def regn = r6 ; регистр (урезан)
; .def regn = r5 ; регистр (урезан)
; .def regn = r4 ; регистр (урезан)
; .def regn = r3 ; регистр (урезан)
; .def regn = r2 ; регистр (урезан)
; .def matr = r1 ; регистр (урезан) r1 по возможности не использовать!!!
; .def madr = r0 ; регистр (урезан) r0 по возможности не использовать!!!
;
;----------
;  .macro   ;; ввод и предобработка данных
;     
;     
;    .endmacro
;
;----------
;
; определение буфера вывода в области данных
;  .dseg
;  .org bufout
;point0: .byte 3 ; g:r:b
;point1: .byte 3 ; g:r:b
;point2: .byte 3 ; g:r:b
;point3: .byte 3 ; g:r:b
;point4: .byte 3 ; g:r:b
;point5: .byte 3 ; g:r:b
;point6: .byte 3 ; g:r:b
;point7: .byte 3 ; g:r:b
;point8: .byte 3 ; g:r:b
;point9: .byte 3 ; g:r:b
;point10: .byte 3 ; g:r:b
;point11: .byte 3 ; g:r:b
;point12: .byte 3 ; g:r:b
;point13: .byte 3 ; g:r:b
;point14: .byte 3 ; g:r:b
;point15: .byte 3 ; g:r:b
;point16: .byte 3 ; g:r:b
;point17: .byte 3 ; g:r:b
;point18: .byte 3 ; g:r:b
;point19: .byte 3 ; g:r:b
;point20: .byte 3 ; g:r:b
;point21: .byte 3 ; g:r:b
;point22: .byte 3 ; g:r:b
;point23: .byte 3 ; g:r:b
;point24: .byte 3 ; g:r:b
;point25: .byte 3 ; g:r:b
;point26: .byte 3 ; g:r:b
;point27: .byte 3 ; g:r:b
;point28: .byte 3 ; g:r:b
;point29: .byte 3 ; g:r:b
;point30: .byte 3 ; g:r:b
;point31: .byte 3 ; g:r:b
;point32: .byte 3 ; g:r:b
;point33: .byte 3 ; g:r:b
;point34: .byte 3 ; g:r:b
;point35: .byte 3 ; g:r:b
;point36: .byte 3 ; g:r:b
;point37: .byte 3 ; g:r:b
;point38: .byte 3 ; g:r:b
;point39: .byte 3 ; g:r:b
;point40: .byte 3 ; g:r:b
;point41: .byte 3 ; g:r:b
;point42: .byte 3 ; g:r:b
;point43: .byte 3 ; g:r:b
;point44: .byte 3 ; g:r:b
;point45: .byte 3 ; g:r:b
;point46: .byte 3 ; g:r:b
;point47: .byte 3 ; g:r:b
;point48: .byte 3 ; g:r:b
;point49: .byte 3 ; g:r:b
;point50: .byte 3 ; g:r:b
;point51: .byte 3 ; g:r:b
;point52: .byte 3 ; g:r:b
;point53: .byte 3 ; g:r:b
;point54: .byte 3 ; g:r:b
;point55: .byte 3 ; g:r:b
;point56: .byte 3 ; g:r:b
;point57: .byte 3 ; g:r:b
;point58: .byte 3 ; g:r:b
;point59: .byte 3 ; g:r:b
;
;----------

  .cseg
bptr0:
  nop
   .org (bptr0 + (256 - (bptr0 & 0x00FF)))
slot0:
     ; 6/14 (6-4=2 посему роль остатка выполняет CBI)
    cbi port_out,out_line ; 2 цикла
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    ret ; 4 цикла
    .org (slot0+16)
slot1:
    nop ; 13/7 (13-4=9)
    nop
    nop
    nop
    nop
    nop
    nop
    cbi port_out,out_line ; 2 цикла
    nop
    nop
    ret ; 4 цикла
    ;.org 0x0060
    .org (slot1+16)
xslot0:
    ; 6/14 (6-5=1 посему роль остатка выполняет CBI с избытком в 1 nop)
    cbi port_out,out_line ; 2 цикла
    cbr zl,(1<<5) ; модификация указателя 1 цикл
    nop
    nop
    nop
    nop
    nop
    nop ; -2 цикла на ld tmp0,x+
    dec tmp1 ; 1 цикл
    brbs SREG_Z,ends_trd ; 1 цикл при неисполнении (в цикле)
    rjmp trasstt ; 2 цикла
    .org (xslot0+16)
xslot1:
    nop ; 13/7 (13-5=8)
    cbr zl,(1<<5) ; модификация указателя 1 цикл
    nop
    nop
    nop
    nop
    nop
    cbi port_out,out_line ; 2 цикла
           ; -2 цикла на ld tmp0,x+
      dec tmp1 ; 1 цикл
    brbs SREG_Z,ends_trd ; 1 цикл при неисполнении (в цикле)
    rjmp trasstt ; 2 цикла
;
ends_trd:
      pop tmp0
      pop tmp1
      pop xl
      pop xh
         pop zl
         pop zh ; восстановить рабочую область из стека
   ret
;----------
;
; предварительно:
; линия out_line настроена на вывод
; исходный уровень out_line =0
; указатель стека усатновлен на RAMEND
; массив данных (bufout:bufout_size) предварительно загружен
; флаг готовности массива данных установлен
;
mass_trm:
     push zh
     push zl
     push xh
     push xl
     push tmp1
     push tmp0 ; храним рабочую область в стеке
res_line:
     ldi tmp0,4
     ser tmp1
     cbi port_out,out_line
res_time:
     dec tmp1
     brne res_time
     dec tmp0
     brne res_time ; =>50uS time out
     ldi xh,high (bufout)
     ldi xl,low (bufout) ; загрузка начального адреса массива
        ; в указатель
     ldi tmp1,bufout_size
     ldiw z,slot0 ; адрес начала таблицы в указателе
;     ldiw z,(bptr0 + (256 - (bptr0 & 0x00FF)))
;----------
trasstt:
    ld tmp0,x+ ; 2 цикла
slot_0:
      sbi port_out,out_line ; 2 цикла реально до установки 3 цикла
         bst tmp0,7 ; 1 цикл
         bld zl,4 ; 1 цикл
         icall ; 3 цикла = 4 цикла от out_line=1
;----------
slot_1:
      sbi port_out,out_line
      bst tmp0,6 ; 1 цикл
         bld zl,4 ; 1 цикл
         icall ; 3 цикла
;----------
slot_2:
      sbi port_out,out_line
      bst tmp0,5 ; 1 цикл
         bld zl,4 ; 1 цикл
         icall ; 3 цикла
;----------
slot_3:
      sbi port_out,out_line
      bst tmp0,4 ; 1 цикл
         bld zl,4 ; 1 цикл
         icall ; 3 цикла
;----------
slot_4:
      sbi port_out,out_line
      bst tmp0,3 ; 1 цикл
         bld zl,4 ; 1 цикл
         icall ; 3 цикла
;----------
slot_5:
      sbi port_out,out_line
      bst tmp0,2 ; 1 цикл
         bld zl,4 ; 1 цикл
         icall ; 3 цикла
;----------
slot_6:
      sbi port_out,out_line
      bst tmp0,1 ; 1 цикл
         bld zl,4 ; 1 цикл
         icall ; 3 цикла
;----------
slot_7:
      sbi port_out,out_line
      ;sbr zl,(1<<6) ; модификация указателя под завершающий фрагмент
      sbr zl,(1<<5)
                  ; 1 цикл
      bst tmp0,0 ; 1 цикл
         bld zl,4 ; 1 цикл
         ijmp ; 3 цикла
;----------

)
:roll:

Re: Управление адресной светодиодной лентой с помощью atmega

Ср дек 20, 2023 12:55:46

КРАМ писал(а):Просто вызов функции и возврат из нее - это ЧЕТЫРЕ машинных цикла
3 цикла на вызов (rcall) и 4 на возврат (ret). итого 7 циклов.

Re: Управление адресной светодиодной лентой с помощью atmega

Ср дек 20, 2023 13:41:52

3 цикла на вызов (rcall) и 4 на возврат

Ну вооот, я то надеялся... :))) :))) :)))
У меня перед носом лежит альбом с инструкциями АВР. Но я поленился посмотреть... :oops:

Re: Управление адресной светодиодной лентой с помощью atmega

Ср дек 20, 2023 13:58:43

Использовал вот эту библиотеку на тини13 с внутренним генератором на 9,6 МГц. Можно либо её вотпрямсразу использовать, либо посмотреть как там всё внутри устроено. Зависит от целей. Уйма условной компиляции на разные случаи, всё программно. Так что это как минимум возможно.

Re: Управление адресной светодиодной лентой с помощью atmega

Ср дек 20, 2023 14:41:36

Так что это как минимум возможно.
Та ну! у меня тини 13 ленту таскает, (и код отправки данных в ленту я привел), или это ничего не доказывает? :dont_know: и на меге 8 на её 8 МГц тактовой тоже работает...

Добавлено after 53 seconds:
Re: Управление адресной светодиодной лентой с помощью atmega8
правда у меня тайминги приходится ручками подбирать... :oops: :)))

Добавлено after 2 minutes 5 seconds:
Re: Управление адресной светодиодной лентой с помощью atmega8
причем можно хоть указатель на ОЗУ или флеш, хоть непосредственно данные в функцию пихать

Re: Управление адресной светодиодной лентой с помощью atmega

Ср дек 20, 2023 20:05:07

Ув. АRV даже статью накатал. Респект.

viewtopic.php?f=57&t=159702

Re: Управление адресной светодиодной лентой с помощью atmega

Сб дек 23, 2023 10:21:25

Вообще для управления такими диодами удобно использовать SPI или UART.
Но бывают случаи, когда этот интерфейс недоступен. Тогда приходится дрыжками ногать.

В этом случае следует учесть, что 2811/2812 диоды требовательны к длительности именно высокого уровня. Низкий можно немного и растянуть. Главное, не дотягивать до 50 мкс - а то будет сброс ))

У меня на 8 МГц тиньке для управления одиночным диодом работает вот такой код (у тиньки ножек было мало и ножки с последовательными интерфейсами были заняты):

Код:
   #define NOP                           asm volatile ("nop" : : )

   static void decodeByte(uint8_t val, uint8_t* buff){
      uint8_t mask = 128;
      while (mask) {
         *buff = val & mask;
         buff++;
         mask >>= 1;
      }
   }
   
   void setLedColor(uint8_t R, uint8_t G, uint8_t B){
      uint8_t arr[24];
      decodeByte(G,&arr[ 0]);
      decodeByte(R,&arr[ 8]);
      decodeByte(B,&arr[16]);
      cli();
      GPIO_RESET_BIT(LED2812);
      _delay_us(50);
      register uint8_t* parr = arr;
      for ( uint8_t i = 0; i < 24; i++) {
         if (*parr++) {
            // bit == 1
            GPIO_SET_BIT(LED2812);
            NOP;NOP;NOP;NOP;
            GPIO_RESET_BIT(LED2812);
         } else {
            // bit == 0
            GPIO_SET_BIT(LED2812);
            NOP;
            GPIO_RESET_BIT(LED2812);
            NOP;
         }
      }
      GPIO_SET_BIT(LED2812);
      sei();
   }

GPIO_SET_BIT(LED2812); и GPIO_RESET_BIT(LED2812); - это просто макросы вида PORTx |= (1 << bit) и PORTx &= ~(1 << bit)
Длительность нулевой единички получилась около 375 нс, единичной единички - около 875 нс. Длительность ноликов достигает почти 1 мс, но повторюсь, это не настолько критично, как жесткие тайминги единички. Мерялось лог.анализатором с частотой выборки 24 МГц (каждые 41.6 нс выборка).

Для управления несколькими диодами можно разложить сразу все цвета в массив (если хватит памяти), либо аккуратно раскладывать биты прямо на лету, изменив условие на if (*parr & mask), а в конце либо уменьшать маску, либо инкрементировать parr и заново задавая маску. NOP в нулевом состоянии в ветке else тогда надо убрать, там и так будет перебор за счет доп. условий.

Ну и можно посмотреть еще вот тут - тоже управление диодами ногодрыгом.
Ответить