Управление большим количеством светодиодов через Binary Angle Modulation

Вот приспичило вам сделать себе могучую светодиодную хреновину, чтобы моргала и переливалась. Да еще в RGB и плавненько так. Собрали вы это дело, поглядели на количество каналов которыми нужно рулить и призадумались…
 

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


 

Каждый отдельный фронт потребует своего прерывания на смену уровня. А представьте, что у нас этих каналов будет не 4, а 40? Или 400? Да контроллер из прерываний вылезать не будет. Прерывания будут налезать друг на друга, порождая джиттер. Не говоря уже о том, что все эти каналы надо будет при любом изменении скважности заново сортировать по длительности. В общем, тупилово будет еще то.
 

▌Нас спасет BAM
Но решение есть. Зовется этот метод BAM. Суть его в том, что мы включаем нагрузку импульсами, поразрядно, с длительностью равной весу разряда.

 

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

 

Интегрируется все аналогично обычному ШИМу. Но есть ряд нюансов:

  1. Частота плавает и на малых разрядах она повышается. Для светодиода или грелки это наплевать. А вот двигатель или еще какую нагрузку с реактивными элементами вроде обмоток или емкостей я бы таким сигналом питать не стал.
  2. При переходе с малых весов к одному большому наблюдается мерцание. Но с этим можно бороться, подробности ниже.
  3. Выдавать вес лучше с большего к меньшему, так меньше заметно влияние второго пункта.

 

▌Код
Концепция ясна, попробуем замутить прогу которая это реализует.
 

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

Поэтому нам для всего этого хватит таймера в режиме СТС и одного прерывания OCR. Поглядим даташит на ATMega16A, раздел 8-bit Timer/Counter0 with PWM.
 

Там есть такая вот картинка:

 

То что нужно!
 

Дальше работать будет до смешного просто. Мы загружаем в OCR0 число 0b10000000 и запускаем таймер. А в прерывании по OCR сдвигаем это число по кругу направо, получая автоматом 0b01000000 -> 0b00100000 -> 0b00010000 -> 0b00001000 и так далее. Попутно у нас получается маска для выбора бита из массива. И все это само. Красота же :)
 

Вот код автомата времени:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avrlibtypes.h>
#include <avrlibdefs.h>
 
 
// Макросы циклических сдвигов
#define ROL(x) ((x << 1) | (x >> 7))
#define ROR(x) ((x >> 1) | (x << 7))
 
// Прерывание по сравнению
ISR(TIMER0_COMP_vect)
{
// Сдвигаем содержимое регистра сравнения. Это автоматом
// Уменьшает выдержку до следующего прерывания
// А также формирует маску выборки бита. 
OCR0 = ROR(OCR0);
 
// Тут будет самая движуха
 
}
 
void main(void)
{
volatile u08 a;
 
sei();
 
// Инициализируем таймер
TCNT0 = 0;
OCR0 = 0b10000000;
 
// Настраиваем в режим СТС и запускаем.
TIMSK |= 1<<OCF0;
TCCR0 |= 1<<WGM01 | 4<<CS00;
 
// Пустой цикл. Должна же где то висеть основная программа :)
while(1)
	{
	a++;
	}
}

 

Можно поглядеть в отладчике как работает все это :) Заодно посмотреть временные интервалы, хватает ли нам скорости и так далее.
 

 

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

Забахаем массивчик и сразу накидаем туда циферок:

1
u08 BRG[8]= {1,33,65,97,129,161,193,225};

 

Это яркости каждого канала.
 

Выборка битов из него будет следующая:
 

Сначала обнуляем нужный бит порта, чтобы ничего лишнего там не было.
 

1
PORTA &=~(1<<3);

 

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

1
PORTA |= ((BRG[3] & OCR0)?1:0)<<3;

 

Увы в битовых операциях Си (да и остальные высокие языки) ведет себя подобно карьерному бульдозеру во дворе. Остается лишь надеяться, чтобы компилятор сам применил всю мощь процессорных команд для работы с битами.
 

Посмотрим что получилось:

1
2
3
4
5
6
7
8
9
10
11
12
13
24:       PORTA &=~(1<<3);
+00000049:   98DB	CBI	0x1B,3		Clear bit in I/O register
 
25:       PORTA |= ((BRG[3] & OCR0)?1:0)<<3;
+0000004A:   B39B	IN	R25,0x1B	In from I/O location
+0000004B:   B78C	IN	R24,0x3C	In from I/O location
+0000004C:   91200063	LDS	R18,0x0063	Load direct from data space
+0000004E:   2382	AND	R24,R18		Logical AND
+0000004F:   F011	BREQ	PC+0x03		Branch if equal
+00000050:   E088	LDI	R24,0x08	Load immediate
+00000051:   C001	RJMP	PC+0x0002	Relative jump
+00000052:   E080	LDI	R24,0x00	Load immediate
25:       PORTA |= ((BRG[3] & OCR0)?1:0)<<3;

 

9 команд на операцию. Не сказать, что сильно много, но делая это вручную, на ассемблере, можно было бы вообще обойтись без сравнений. А просто копировать бит командами BST/BLD получилось бы быстрей и за меньшее число операций. Ну и в других архитектурах может быть более оптимально, особенно если есть возможность просто копировать биты.
 

Обернем эту процедурку в макрос, для пущей наглядности и удобства.

1
2
3
4
5
#define COPYBIT(dst,bit,src) 				\
	do {						\
	(dst) &=~(1<<(bit));				\
	(dst) |=(((src) & OCR0)?1:0)<<(bit);	\
	} while(0)

 

Весь код получился крошечный и простой:
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//Clock Config
#define F_CPU 8000000L
 
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avrlibtypes.h>
#include <avrlibdefs.h>
 
#define ROL(x) ((x << 1) | (x >> 7))
#define ROR(x) ((x >> 1) | (x << 7))
 
#define COPYBIT(dst,bit,src) 				\
	do {						\
	(dst) &=~(1<<(bit));				\
	(dst) |=((   (src) & OCR0  )?1:0)<<(bit);	\
	} while(0)									
 
 
//Загрузим яркости диодов
u08 BRG[8]= {1,33,65,97,129,161,193,225};
 
ISR(TIMER0_COMP_vect)
{
OCR0 = ROR(OCR0);
 
COPYBIT(PORTA,0,BRG[0]);
COPYBIT(PORTA,1,BRG[1]);
COPYBIT(PORTA,2,BRG[2]);
COPYBIT(PORTA,3,BRG[3]);
COPYBIT(PORTA,4,BRG[4]);
COPYBIT(PORTA,5,BRG[5]);
COPYBIT(PORTA,6,BRG[6]);
COPYBIT(PORTA,7,BRG[7]);
}
 
void main(void)
{
volatile u08 a;
 
sei();
 
TCNT0 = 0;
OCR0 = 0b10000000;
 
TIMSK |= 1<<OCF0;
TCCR0 |= 1<<WGM01 | 4<<CS00;
 
PORTA = 0x00;
DDRA = 0xFF;
 
while(1)
	{
	a++;
	}
}

 

Опа и получили градиентик:

 

Причем, как вы понимаете, совершенно не обязательно пихать данные сразу же в порт. А если у вас нет столько портов? Ничего страшного, пихать их можно куда угодно. Например затолкать их в буфер, а на последнем байте весь этот паровоз через какой-нибудь SPI протокол загнать в сдвиговые регистры и отправить на светодиодную линейку феерической длины.
 

А вот так он выглядит на логическом анализаторе:

 


 

▌Косяки
Дальше начинаются приколы. Самый безобидный заключается в том, что напряжением яркость светодиода регулируется весьма нелинейно. Т.е. если плавно менять яркость от 0 до 255, то вначале она резко наростает, а начиная со второй половины и не видно толком. Радости добавляет еще и нелинейность восприятия света глазом. В общем, если захотите прям ровненький такой переход яркости, то кривые зависимостей придется очень тщательно подбирать.
 

Еще один прикол обнаружился в динамике. В статике то оно все замечательно. Горит себе на 100 герцах и не жалуется. Но стоило мне начать гонять яркости по кругу в цикле, как начались странные моргания. Долго не мог понять что это за фигня, искал ошибку в алгоритме. Нерегулярная природа их намекала на некий случайный процесс. Как то, например, период изменения яркости относительно скорости автомата на прерываниях.
 

Решил делать смену яркости только на начало автомата. Реализовал это следующим образом:

1
2
3
4
5
6
7
while(1)
	{
	if(OCR0 == 128)
	{
	BRG[0]+=1;
	_delay_ms(10);
	}

 

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

И стал ловить переход подбирая числа. Выловил, оказалось, что при смене с 127 на 128 хорошо так мерцает. А вот в статике разница в яркости между 127 и 128 на глаз не различается почти. Что за мистика? Ткнулся осциилографом и увидел следующую картину:
 


 

В месте перехода со 128 на 127 возникает стыковка двух байтов. Она дает двойной интервал. Казалось бы, ну двойной интервал. Единичный, на 120ГЦ. Но блин, на глаз его видно.
 

Поставил смену на конец цикла:
 

1
2
3
4
5
6
7
while(1)
	{
	if(OCR0 == 1)
	{
	BRG[0]+=1;
	_delay_ms(10);
	}

 

Картинка изменилась. Теперь у нас вспышка. Да епт…

 

Взял и воткнул смену на середину цикла.
 

1
2
3
4
5
6
7
while(1)
	{
	if(OCR0 == 64)
	{
	BRG[0]+=1;
	_delay_ms(10);
	}

 

Стало намного лучше. Переход есть, но это надо оочень сильно всматриваться.

 

Вообще развлекаясь с этой штукой заметил интересную вещь — глаз отлично видит изменения, а разницу в статике практически не замечает. Т.е. когда яркость меняется на 1/256 — это заметно. А когда два рядом горят, отличаясь на тот же 1/256 разницы практически не видно.
 

▌Производительность
В отличии от ШИМ, который молотит равномерно, BAM имеет периоды загрузки и периоды практически полного бездействия. Анализируя состояние BAM автомата можно планировать тяжелые вычисления. Например между 128м весом и 64 времени ну просто завались, можно посчитать что-то очень тяжелое. А если у нас молотилка вышла на малые веса 8 и меньше, то лучше тормознуть все процессы и дать индикации нормально добить эти периоды, благо это недолго.
 

▌Outro
Ну и вот вам немного лулзов на тему синих светодидов выжигающих глаза, что так любят китайцы втыкать в лицевые панели разным девайсам. Была у меня на колонке эта пакость, на индикаторе питания. Жарила шо гиперболоид, даром что обычный дешманский светодиод на 3мм. Ну заклеил я ее изолентой. А через год полез зачем-то к колонке. Оторвал изоленту эту. А там…
 


 

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

52 thoughts on “Управление большим количеством светодиодов через Binary Angle Modulation”

  1. Это можно использовать, если делается малозначительное изделие «для себя».
    Когда нужна высокая частота обновления (исключить мерцание на камерах при ТВ-съемке), плавность (более 256 уровней яркости), то естественно надо смотреть в сторону железных LED-драйверов.
    Быть одновременно художником по свету и разработчиком световых приборов — знать косяки разработчиков с обеих сторон =)

    PS: В частности и из-за перехода со 127 на 128 не использовал BAM в DodgeRam.

    1. Ну 127-128 легко обходится банальной фильтрацией и правильной выборкой. Там реально можно было обойтись этим методом.

      Когда речь идет о ТВ съемке тут естественно совсем другие требования к частоте.

      1. Доброго времени суток,

        Искал в интернете реализацию BCM, и ваш пример оказался самым понятным для новичка коим я являюсь :) Хочу использовать его в своем проекте (управление матрицей 8×8 rgb светодиодов, которая в принципе является не совсем матирцей, а просто общьностью квадратных сегментов)

        Итак, пытаясь запустить все это дело динамично, столкнулся с описанным морганием при переходе с 127 на 128, и использование

        while(1)
        {
        if(OCR0 == 64)
        {
        BRG[0]+=1;
        _delay_ms(10);
        }

        помогло не сильно. Как его можно «банальной фильтровать» я не совсем понял, и что подразумивается под «правильной выборкой.» тоже не очень. Выборка уровней яркости?

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

        п.с. я новичок который хочет вырасти из ардуино в нормальные контроллеры, но пока что чувствует себя сброшенным со скалы :DD

        1. Ну во-первых, разложи яркости не тупо по порядку от 0 до MAX, а с умом, учтя логарифмическую зависимость яркости для глаза. Во-вторых, создай таблицу яркостей с учетом этого всего. И перебирай уже ее, а не тупо значения загоняй по порядку в OCR. При этом у тебя не будет броска яркости, т.к. ты сам по таблице каждую яркость проверишь и рассчитаешь.

          Динамическая индикация это отдельная тема. Там много зависит от того чем ты хочешь управлять и как оно подключено.

          1. хм, но сейчас для обеспечения плавного наростания яркости я прохожу значения от 0 до 255 в цикле, тупо +=1. значит ли это что таблицу яркости с учетом всего мне нужно составить из 255 значений? это очень много значений.

            В OCR? Я же менял яркости в массиве BRG, исходя из этого

            Яркость вся прописана тут:
            u08 BRG[8]= {1,33,65,97,129,161,193,225};

            Если надо изменить, то меняешь в этом массиве. А автомат сам хватает ее по фронтам исходя из наличия битов.

            динамическая индикация, будучи отдельной темой, как раз интригует меня больше всего :D Я хочу управлять управлять матрицей 8 на 8, где каждый пиксель это кусок RGB ленты. Хотел вначале использовать ShiftMatrixPWM под ардуино, но там, при матричном включении, зажигая отдельный пиксель, скважность была много меньше, чем если зажигать все сразу. Да и вообще хочется не использовать ардуино, я к этому готов.

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

    1. У меня была такая зажигалка, только там не синий светодиод был, а фиолетовый, точнее 405нм, это на границе видимого и УФ спектра, под ним много чего светится. А светильник из 48 таких светодиодов мне фоторезист за 20 секунд засвечивают.

  2. BAM штука известная, но эта реализация действительно очень красивая.
    Когда делал светодиодные пои тоже столкнулся с неравномерным изменением яркости при равномерном изменении ШИМ, сначала думал про нелинейную характеристику диодов, но ведь что при ШИМ, что при BAM светодиод включается всегда на один и тот же ток, только на разное время, а мощность излучения интегрирует уже глаз. Начал гуглить в этом направлении и выяснил что у глаза очень сложные характеристики и по амплитуде и по времени и по пространству, но если упростить то субъективная яркость от мощности зависит по логарифмическому закону.

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

    А почеу бы и нет, собственно? На таком расстоянии сверхяркий СИД вполне выдаст поток мощности, сравнимый с солнечным, а то и больше (скажем, если взять излучаемую мощность за 10мВт — вполне реально при КПД 15% и токе 20мА, то получается порядка 1кВт/м^2, у солнца примерно вдвое меньше), причем все это — в высокочастотной части спектра, а у солнца большая часть все же приходится на ИК.

  4. Самый безобидный заключается в том, что напряжением яркость светодиода регулируется весьма нелинейно. Т.е. если плавно менять яркость от 0 до 255, то вначале она резко наростает, а начиная со второй половины и не видно толком. Радости добавляет еще и нелинейность восприятия света глазом.
    Не «добавляет», а именно поэтому.
    Глаз воспринимает яркость так же, как ухо — звук: логарифмически.
    То есть чтобы перейти к следующей ступени яркости, нужно не добавлять, а умножать.
    Я воспользовался идеей децибела, только для простоты вычислений «бинаризовал» его: вместо десятикратного я взял за основу двукратное снижение коэффициента заполнения, и разбил его не на 10, а на 16 промежуточных ступеней. Загнал в контроллер табличку, полученную по формуле 2 в степени (n/−16), n=1..16, преобразованную в обыкновенные дроби (два варианта: один для аппаратного ШИМа, один для программного на прерываниях). Таким образом мы получаем ступени снижения яркости от 0 до 15 (0: duty cycle = 95,76%, 15: duty cycle = 50%), а все остальные ступени получаем битовым сдвигом. Таким образом у нас выходит 160 ступеней яркости (не считая 0% и 100%), самая тусклая — на пределе возможностей аппаратного ШИМа — 1/1024.

  5. Например вот такая конструкция (кусочек автомата развёртки на светодиодной матрице)

    result = __builtin_avr_insert_bits(0xffff7ff0, statesGLo, result); // 1, 8
    result = __builtin_avr_insert_bits(0xfffff70f, statesGHi, result); // 9, 16
    DDRC = result;
    PORTC = ~result & 0x0F;

    1. Разворачивается вот в это:

      result = __builtin_avr_insert_bits(0xffff7ff0, statesGLo, result); // 1, 8
      3c78: 80 e0 ldi r24, 0x00 ; 0
      3c7a: 50 fb bst r21, 0
      3c7c: 80 f9 bld r24, 0
      3c7e: 57 fb bst r21, 7
      3c80: 83 f9 bld r24, 3
      3c82: 58 2f mov r21, r24
      result = __builtin_avr_insert_bits(0xfffff70f, statesGHi, result); // 9, 16
      3c84: 40 fb bst r20, 0
      3c86: 51 f9 bld r21, 1
      3c88: 47 fb bst r20, 7
      3c8a: 52 f9 bld r21, 2
      DDRC = result;
      3c8c: 54 bb out 0x14, r21 ; 20
      PORTC = ~result & 0x0F;
      3c8e: 50 95 com r21
      3c90: 5f 70 andi r21, 0x0F ; 15
      3c92: 55 bb out 0x15, r21 ; 21

  6. Нас спасет DM633.

    К стати, глаз яркость воспринимает не линейно. Што бы мыргало приятно, нужно значение яркости прогонять через параболический массив, либо считать на ходу PWM = PWM^2/255 (для 8 битной градации), деление естественно шифтом.
    Массив резвее, но тяжелее.

  7. Di, не могу удержаться и задам пару вопросов из раздела *для самых маленьких.
    1) На фотке, же пинбоард? т.е. я смогу собрать дома такую-же конструкцию?
    2) Я там на форуме вопрос поднял, почитай плиз, насколько ВАМ применима для моей задачи? спасибо. (все можно не читать, задачу я постарался описать в первом посте. Заранее спасибо)
    http://forum.easyelectronics.ru/viewtopic.php?f=17&t=20580

    1. Он самый. Конечно сможете.

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

      1. DI, помогите, пожалуйста, малышам.
        Не могу понять вот эту операцию PORTA |= ((BRG[3] & OCR0)?1:0)<<3;
        То есть берем 3 значение из массива BRG — 97, затем побитово умножаем 97 на допустим 0b10000000 и что получаем в итоге? как меняется яркость?

        1. Мы не умножаем число. Внимательно еще раз прочитай начало. Нам надо яркость числа (97 у тебя) превратить в длительностный ШИМ подобный сигнал. Для этого мы его включаем побитово. Старшие биты дольше, младшие меньше. В итоге, общий вес сигнала после интегрирования получается 97 из 255, т.е. как и задумано.

          А эта операция просто берет маску бита (она же длительность текущего шага, она же OCR0 т.к. задает потолок) и накладываем на значение яркости. Если бит есть — ставим бит порта, если нет не ставим. А <<3 это выставление конкретного бита порта, в данном случае PB3 вот и все. Потом это все оборачивается макросом и юзается на все остальные биты, меняя только параметры.

          1. Спасибо за ответ, да, я понял, что 97 мы превращаем в длительность, но не понял именно как . Конкретно вот это: «Если бит есть — ставим бит порта, если нет не ставим».

              1. Бит в числе яркости. Еще раз прочитай начало статьи, как из числа яркости формируется BAM сигнал. Длительности у нас фиксированы, это, 1t, 2t, 4t, 8t, 16t … 256t,а вот включать или нет бит на каждом этапе длительности зависит от яркостного числа и его битов.

                1. Наконец то дошло, спасибо)
                  Еще, если можно, поясните эту конструкцию ROR(x) ((x >> 1) | (x << 7))
                  Не встречал такого, знаю, что смещение единицы это OCR0<>1 , то это что ?
                  И еще, где взять библиотеки #include
                  #include без них же дефайны не будут работать?

                  1. Это циклический сдвиг. Т.е. мы гоняем единичку по кругу. Если просто делать сдвиг << то единичка уйдет за край байта и все. А тут ее вернет если что. Сам на листочке в клетчочку протрассируй эту инструкцию в двоичном коде с учетом приоритетов операций и все поймешь.

                    1. «Инклюды стандартные для WinAVR или AVRToolchain. Они в составе компилятора идут»
                      Извиняюсь, а если у меня AVR Studio , то что делать? Компилятор ругается на avrlibtypes.h, avrlibdefs.h.

                    2. С библиотеками разобрался.
                      DI, можете подсказать, как можно сделать, чтобы диоды не все сразу включались , а по одному , начиная с самого яркого,который на выводе PA0, пробую через цикл:
                      while(1)
                      {
                      PORTA=0x00;
                      for (int i=0;i<=7;i++)
                      {
                      PORTA=PORTA|(1<<0);
                      COPYBIT(PORTA,i,BRG[i]);
                      _delay_ms(500);
                      }}
                      Не получается

  8. Господа, добрый день. Про BAM понял, где-то про него читал ещё лет 20 назад (на спектруме ещё делал подобный алгоритм), а вот другую идею пока не смог проработать.
    Идея в том, чтобы побороть одну из самых не хороших черт BAM, как в принципе и ШИМ: разная частота импульсов при разных значениях уставки. Т.е. частота ШИМ — это частота меаднра при уставке равной половине диапазона (127, например). Но ведь в то же время мы могли бы включать выходной порт с частотой в 256 раз большей, т.е. длительностью, равной 1 биту. Блин, не могу это объяснить. Я имею ввиду, что при значении уставки равной 127 можно было бы при каждом такте счетчика ШИМ менять состояние выходного порта, и мы получили бы те же самые 50% скважности но при очень высокой частоте.
    Если кто-то понял, что я хочу, может подскажете, в каком направлении копать?

  9. Привет.Спасибо за статью. Разобрался с этим алгоритмом пока только в протеусе. Но у меня частота довольна стабильна судя по внутреннему осциллографу. При частоте 16 МГц и коэффициенте предделителя 64, на меге16, частота получается 1866 Гц.
    Вот, кстати мой кусок кода в обработчике прерывания по совпадению
    tim0_ocr:
    in temp,OCR0
    mov temp1,yark
    and temp,temp1 ;поставим маску на яркость
    cpi temp,0 ; если результат 0, то погасим порт, иначе зажгем
    breq lbl01
    sbi PORTB,0
    rjmp lbl02
    lbl01: cbi PORTB,0

    lbl02: in temp,OCR0 ;
    ROR temp ;сдвинем регистр сравнения на один «вес» вправо
    cpi temp,0 ; если сдвинули до нуля, то
    brne lbl03
    ldi temp,0b10000000 ; заново загрузим в регистр сравнения 1 в старший разряд
    lbl03: out OCR0,temp

    RETI

    И самый главный вопрос/мысль.
    ////———-начало цитаты
    // Прерывание по сравнению
    ISR(TIMER0_COMP_vect)
    {
    // Сдвигаем содержимое регистра сравнения. Это автоматом
    // Уменьшает выдержку до следующего прерывания
    // А также формирует маску выборки бита.
    OCR0 = ROR(OCR0);

    // Тут будет самая движуха

    }
    /////———конец цитаты————-

    Движуха происходит в каждом прерывании по сравнению, но каждое прерывание все меньше и меньше по времени. Хватит ли времени для управления 400 светодиодами в самом малом «весе» разряда? И как можно организовать вывод этого ШИМа через те же сдвиговые регистры

  10. Добрый день. Немного оффтоп наверно, да и некропост. Но все таки. Возможно ли написать такую прошивку на ассемблере, чтобы можно изменить код самой программы? Например через бутлоадер?

    1. Возможно, код выполняемый из секции бутлоадера может изменять основной код. Но делать это он может только поблочно, достаточно крупными кусками. Так что надо будет всосать в ОЗУ кусок кода из флеша, изменить в нем нужный байт и перезаписать его обратно. Нетривиальная задачка, не факт что осуществимая, но попытаться можно.

  11. Здравствуйте! Попытался реализовать этот алгоритм, сделал в точности как здесь-в протеусе работает, на макетной плате нет. С обычным программным ШИМ та же самая история. Светодиод либо светится на максимум, либо не горит, в зависимости от изначальных настроек регистра PORT. Схема вот такая (https://www.dropbox.com/s/06psliixqfgw2u8/%D0%A1%D0%BD%D0%B8%D0%BC%D0%BE%D0%BA.PNG?dl=0). Включал и через транзисторы и напрямую-разницы нет. Фьюзы установлены на внутренний генератор 8МГц. Помогите советом, пожалуйста!

  12. Зарегался, чтобы сказать спасибо.
    Использовал код из статьи, чтобы запустить BAM на ардуине и использую сдвиговый регистр 74HC595, чтобы управлять светодиодами. Вот что получилось (заодно потренировался в ЛУТ — это моя первая успешная плата):

    гифка

  13. Здравствуйте.
    Концепцию BAM понял, но вот возникла такая детская проблема:
    Никак не доходит, как на ATTiny2313 настроить таймер TC0 в режиме СТС и прерывание по OCR.
    Читаю даташит, книги разных умных людей.
    Выписал себе на листок нужные (как мне кажется) регистры, биты, флаги, но в голове никак не хочет складываться общая картина.
    Кому не в западло, плиз, черканите эти заветные несколько строчек инициализации TC0 желательно (но не обязательно) на ассемблере.

    1. А, похоже стало доходить:
      1) Записать требуемое значение в регистр сравнения OCR0A
      2) TCCR0A <— 0b00000010 — режим СТС
      3) TCCR0B <— битами CS02, CS01 и CS00 выбираем предделитель. КАКОЙ ПРЕДДЕЛИТЕЛЬ ЛУЧШЕ ВЫБРАТЬ ДЛЯ ВАМ?
      4) TIMSK <— 0b00000001 — разрешение прерываний от TC0
      5) sei — глобально разрешаем прерывания.

      Как-то так?

  14. Привет, может уже не актуально вам, но возникла непроверенная идея. Насчёт «моргания» при переходе 127->128. А что если начинать не с 1000 0000 а с 0000 1000? В прерывании SWAP и в счетчик грузим 1000 0000. Потом ROR, SWAP и тд. Т.е. вес бита следует так: 8, 128, 4, 64… Может, тогда не будет такого значительного скачка яркости? Повторю, идея чисто теоретическая.

Добавить комментарий