28 Сен., 2021, 01:28

Всегда лучше наблюдать за процессом, чем делать что-то самому.


Как засветить светодиоды от AVR через сдвиговый регистр?

Автор Shaman, 23 Сен., 2020, 19:16

« предыдущая - следующая »

Shaman

Захотелось мне сделать свой космический модуль... Задний фонарь для велосипеда с указанием поворотов и стоп-сигналом. Поскольку лепить такое на ардуино будет слишком громоздким и есть потребность поглубже разобраться в написании программ на Си решено было использовать голый микроконтроллер. Потому как отдельных светодиодов зажечь нужно много (27) решено было использовать сдвиговые регистры 74HC595 (4шт.). Микроконтроллер был взят Atmega328P как самый доступный.
Поскольку в AVR я новичок то решил разбить задачу на этапы и первый из них это написать процедуру, которая будет из некоего буфера экрана брать данные и вносить их последовательно в регистры.
Немного побившись головой об стол получил вот такой код:
Немного побившись головой об стол получил вот такой код:
#define F_CPU 8000000UL
#include <avr/io.h>
#define _data 3  // устанавливаем пин вывода данных в сдвиговые регистры
#define _clock 4 // устанавливаем пин clock
#define _latch 5 // устанавливаем пин защёлки
#define SIZE_SCREEN 4
unsigned char buffer [SIZE_SCREEN]; //создаем буфер буфер экрана (выяснить какой тип данных применить)


int record (); //прототип функции для записи в сдвиговые регистры

int main (void)
{

    DDRC = 0b00111000;
    PORTC |= (1<<_clock); // устанавливаем clock в 1 для предотвращения ложного срабатывания
    PORTC |= (1<<_latch); // поднимаем защелку
    PORTC |= (1<<_data); // устанавливаем на выходе дата 1       
   
}
int record ()
{
    PORTC &= ~(1<<_latch); // опускаем защелку
  for (int x = 0; x<SIZE_SCREEN; x++) //цикл для переборки байтов в массиве (уточнить как храниться число)
  {
       for (int y = 0; y<8; y++) // цикл для перебора битов в байте и подачи их на выход
        {
            if ((buffer[x] & (1<<y)) == 0) // накладываем побитовую маску для считывания нужного бита
            {
                PORTC &= ~(1<<_data); // если ответ 0, выставляем на входе регистра 0
            }
            else
            {
                PORTC |= (1<<_data); //иначе, выставляем на входе регистра 1
            }
            PORTC &= ~(1<<_clock); // устанавливаем clock в 0, переводим регистр в режим ожидания данных
            PORTC |= (1<<_clock);  // устанавливаем clock в 1, записываем данные в регистр
        }
  }
    PORTC |= (1<<_latch); // поднимаем защелку включаем светодиоды
}
[свернуть]
Он даже скомпилировался значит явных ошибок нет. Осталось выяснить как его можно проверить на железе или в эмуляторе.
А пока уважаемые гуру не могли бы вы проверить меня на ошибки?

Slabovik

23 Сен., 2020, 23:00 #1 Последнее редактирование: 24 Сен., 2020, 15:22 от Slabovik
Насколько я понимаю, схема подключения регистров выглядит примерно так
Схема-регистров-595.png

Для начала надо точно понимать, что собой представляет 74HC595 (она же ИР52 в отечественной системе обозначений). Думаю, с этим проблем нет, но я всё-таки нарисую, чтобы было куда пальцем тыкать (это важно т.к. устраняет неопределённости, когда один думает об одном, а другой о другом и в итоге друг друга не понимают).

ИР52 по внутреннему строению состоит как бы из двух микросхем. Вот как-то так
Схема-регистров-595-внутри.png
Т.е. внутри у неё фактически два регистра, первый регистр сдвига, куда данные можно задвигать при помощи сигналов D (Data) и C (Clock). При этом процесс записи и сдвига (вообще, сдвиг - это тоже запись) происходит строго по фронту (переходу из 0 в 1) сигнала Clock. Чисто логически это можно изобразить так
Схема-регистров-595-сдвиг.png

Такая связка двух микросхем в одном корпусе весьма удобна - поскольку выходной регистр "непрозрачный", то на его выходах всегда хранится информация, которая там запомнилась при переходе сигнала OC (по схеме Ready) из 0 в 1. Получается, что пока мы "двигаем" Data в регистре сдвига, на выходах Q0~Q7 информация "стоит как вкопанная" с прошлого раза. И только когда мы "дёрнем" OC, на выходах появится то, что мы только что записывали в регистр сдвига. Очень, очень удобно  :)

Диаграмма записи всех 8 бит в регистр типа 595 рекомендую делать такого вида

Схема-регистров-595-процедура.png

Что тут получается. Самое главное: опускаем сигнал Clock -> выставляем сигнал Data (очередной бит, который мы ходим задвинуть) -> поднимаем сигнал Clock. Делаем так столько раз, сколько бит нам нужно записать в регистр. В случае 4-х последовательно включенных регистров 595, сделать так надо 32 раза (есть исключения, но они связаны уже со схемотехникой. Вдруг, например, выводы Q4~Q7 самого правого (по схеме выше) регистра никуда не подключены - тогда их и заполнять не надо, а значит, сдвигов можно делать меньше).

Сигнал Ready рекомендую (именно рекомендую, т.к. фактически, опустить, т.е. сделать 0, его можно в любой момент) опускать ровно перед тем, как соберётесь записывать Data в регистр сдвига. А как всё запишете - поднимайте. Дело в том, что такое поведение сигнала Ready даёт возможность контролировать физически (осциллографом), когда у вас началась процедура записи, а когда закончилась, что облегчает ловлю багов (не хвалитесь - баги всегда есть, даже если вы их не видите).

Посмотрим код. Вот честно... каждый раз говорю, что Си, а тем более ++ я знаю весьма фигово. То ли дело - ассемблер :)
Цитата: undefined#define _data 3  // устанавливаем пин
Наверное, не устанавливаем пин, а назначаем разряд порта.
Да, тут надо аккуратно, потому что здесь есть одно место, порождающее глобальную путаницу при малейшей неосторожности (я тоже постоянно путаюсь).
Биты порта нумеруются с 1 по 8. А вот разряды с 0 по 7. При этом 1-й бит порта - это 0-й разряд, второй бит - первый разряд и т.д. Я тоже буду путать(ся) в показаниях - имейте это в виду и следите за руками  :)

про дефайны
При таком способе назначений (define) для работы с портами используется записи типа
Цитата: undefinedPORTC |= (1<<_clock)
Честно говоря, мне совсем не нравятся эти записи "1<<Clock". Считаю, что в этом месте надо чётко писать "Clock" без всяких 1<<

Для этого надо... всего лишь привести в порядок дефайн. Например, для Data это должно выглядеть так
#define _data 8  // назначаем разряд 3 порта как сигнал DataЭто потому что на порт посмотреть как на биты, слева старший, справа младший, то он выглядит как бинарное число '00001000', т.е. '8' в нашем обиходе.

Да, '8' надо записать просто потому, что говорят, в Си, нет возможности записывать бинарные числа. Или врут? Я в коде вижу бинарную запись. Удивительно...
Впрочем, можно (и может даже нагляднее) использовать такое определение
#define _data 1<<3  // назначаем разряд 3 порта как сигнал DataЗдесь записано, что 1 сдвинута логически влево три раза, т.е. на три разряда. Бинарно это выглядит так
00000001(bin) << 3 получаем 00001000(bin)Точно также определим "_clock"
#define _clock 1<<4 // назначаем разряд порта для сигнала clockЕсли сделать так, то процедура изменения значения порта станет выглядеть так
PORTC |= _clock; // устанавливаем clock в "1"
PORTC &= ~_clock; // устанавливаем clock в "0"
[свернуть]
Алгоримт вроде верен, но на 100% ручаться не могу. Единственное, проход битов производится от 0 до 7, что означает, что первым будет задвигаться в регистр младший бит, затем старший, а на микросхемах выходы маркированы наоборот. Это совершенно ничего не значит, просто надо иметь ввиду, что Q7 микросхемы в этом случае соответствует младшему биту буфера (в проектах даже можно переименовывать имена выводов микросхем, чтобы они соответствовали их реальной логической роли).
Да, разве что установку _clock в 0 я бы я бы делал не после установки _data, а перед. Но можно и так оставить, если между установкой _clock в "0" и установкой в "1" вставить какой-нибудь nop.

Для проверки работоспособности можно в CharBuffer записать какие-нибудь значения и поморгать (в цикле, естественно, не быстром)

зы: долго вспоминал, как выглядят логические опреаторы. В итоге Вики сдала все  :)

зы:зы: Просвещаюсь дальше: оказывается, в Си нет типа "byte". Похоже, что тип "char" - вполне нормально для данного места. Хотя если определить его как "unsigned char", то он видимо и будет полностью равноценен типу "byte", который я точно где-то использовал...

зы:зы:зы: С вашего позволения продолжу.
Могу показать свой ассемблерный код, которым я делаю то же самое на этом же (точнее, на 168, но это те же штаны) процессоре.
AsmCode.png
Это один файл, просто я показал нужные места. Если чуть помедитировать, то станет видна та же картина: имеется буфер под выводимые на индикатор символы и процедура вывода этих символов, чуть украшенная ещё парой входов на "гасим всё" и "зажигаем всё".

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

И вот как чисто физически, на осциллографе (весьма хреновеньком, надо сказать) выглядит бурст обновления "экрана".
AsmCode_Burst_8_bytes.png AsmCode_Burst.png
Синий луч - сигнал "Clock", жёлтый - сигнал "Rdy" (Ready, Latch, OC...). Data не показана - там не так явно.
Частота процессора здесь 14,3 МГц, в пределах одного байта частота следования битов 1 МГц, а между байтами видна небольшая задержка в передаче - всё согласно программы: между байтами ведь выполняется несколько больше действий, нежели между битами.
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

Shaman

Благодарю за столь подробный ответ!  :o  :)

С вашей помощью немного оптимизировал код.
Сейчас он выглядит так:
#define F_CPU 8000000UL
#include <avr/io.h>
#include <util/delay.h>
#define _data_1 PORTC |= (1<<3)      // Выбираем пин для вывода данных в сдвиговые регистры и устанавливаем на нём 1 путём установки порта и назначения разряда соответствующего пину
#define _data_0 PORTC &= ~(1<<3)     // -.- устанавливаем на нём 0 -.-
#define _clock_high PORTC |= (1<<4)  // Выбираем пин clock и устанавливаем на нём высокий уровень
#define _clock_low PORTC &= ~(1<<4)  // Выбираем пин clock и устанавливаем на нём низкий уровень
#define _latch_up PORTC |= (1<<5)    // Выбираем пин защёлки и поднимаем её
#define _latch_down PORTC &= ~(1<<5) // Выбираем пин защёлки и опускаем её
#define SIZE_SCREEN 4                //размер буфера экрана
unsigned char buffer [SIZE_SCREEN];  //создаем буфер буфер экрана


int record (); //прототип функции для записи в сдвиговые регистры

int main (void)
{

    DDRC = 0b00111000; //устанавливаем необходимые пины на вывод
    _clock_high;       // для предотвращения ложного срабатывания
    _latch_up;         // поднимаем защелку   
    while (1)
    {
        buffer[0] = 0b01010101;
        buffer[1] = 0b01010101;
        buffer[2] = 0b01010101;
        buffer[3] = 0b01010101;
         record ();
        _delay_ms(50);
        buffer[0] = 0b10101010;
        buffer[1] = 0b10101010;
        buffer[2] = 0b10101010;
        buffer[3] = 0b10101010;
        record ();
        _delay_ms(50);               
    }
   
   
}
int record ()
{
    _latch_down;                           // опускаем защелку
   
  for (int x = 0; x<SIZE_SCREEN; x++)      //цикл для переборки байтов в массиве (уточнить как храниться число)
  {
       for (int y = 7; y>=0; y--)          // цикл для перебора битов в байте и подачи их на выход
        {   
            _clock_low;                    // переводим регистры в режим ожидания данных   
           
            if ((buffer[x] & (1<<y)) == 0) // накладываем побитовую маску для считывания нужного бита
            {
                _data_0;                   // если ответ 0, выставляем на входе регистра 0
            }
            else
            {
                _data_1;                   //иначе, выставляем на входе регистра 1
            }
           
            _clock_high;                   // записываем данные в регистр
           
        }
  }
    _latch_up;                            // поднимаем защелку, включаем светодиоды
}
[свернуть]
И работает
[свернуть]

Команды дерганья пинов решил полностью вывести в дефайны и сделать их 2-х видов выставление 1 и 0.
В противном случае, даже при использовании ваших примеров, при использовании других выводов и портов либо я не смогу назначить им уникальные имена поскольку все эти варианты
#define _data 8  // назначаем разряд 3 порта как сигнал Data
#define _data 1<<3  // назначаем разряд 3 порта как сигнал Data
#define _clock 1<<4 // назначаем разряд порта для сигнала clock
PORTC |= _clock; // устанавливаем clock в "1"
PORTC &= ~_clock; // устанавливаем clock в "0"
[/quote]
означают что я всего лишь заменяю операцию сдвига бита или вообще номер бита уникальным именем, а эта операция используется по отношению ко всем портам.
А вот мой вариант
#define _data_1 PORTC |= (1<<3)      // Выбираем пин для вывода данных в сдвиговые регистры и устанавливаем на нём 1 путём установки порта и назначения разряда соответствующего пину
#define _data_0 PORTC &= ~(1<<3)     // -.- устанавливаем на нём 0 -.-
#define _clock_high PORTC |= (1<<4)  // Выбираем пин clock и устанавливаем на нём высокий уровень
#define _clock_low PORTC &= ~(1<<4)  // Выбираем пин clock и устанавливаем на нём низкий уровень
#define _latch_up PORTC |= (1<<5)    // Выбираем пин защёлки и поднимаем её
#define _latch_down PORTC &= ~(1<<5) // Выбираем пин защёлки и опускаем её
это уже использование конкретного бита на конкретном порту и буде возникнет необходимость поменять порт и бит нужно всего лишь исправить несколько строчек кода, а не шарахаться по всему листингу.

И да простят меня прожженные AVR-щики  :), но я буду комментировать что выбираю я именно пины просто так привык со среды Arduino. Там оперирование идет именно пинами, а не абстрактными регистрами и битами. Просто подкорректирую формулировку дабы было понятно, что оперирование идет именно через регистры памяти привязанные к пинам и биты в них.

Сейчас буду думать в каком виде хранить данные выводимые на экран и как их передавать в буфер. Если подскажут идею не обижусь.  ;)   ;D

Slabovik

Ну вот, а то всё "мама, мама"!  ;)
Вполне нормально, разве что потакание привычке называть разряд порта пином в будущем приведёт к непоняткам (видимо, ещё не сталкивались с другим клаcсом чипов, где вывод порта можно назначать на определённый пин).

Для раскладки экрана могу порекомендовать изобрести "шрифт". Подобно тому, как изобретают (назначают) "шрифт" всяким семисегментным индикаторам. Пусть, например, там будут "буквы": 1) габарит 2) стоп 3) влево 4) вправо, и ещё какие-нибудь (я слабо представляю, что там в реальности творится). По событиям контроллер будет выводить "буквы" в экранную область памяти и вызывать вот эту процедурку, которая заработала чуть выше. Я так думаю...
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

Slabovik

Интересно, что есть у КМОП-серии аналогично работающая микросхема типа CD4094.
Традиционная КМОП (не 74HC) серий 561, 1561 хотя и более медленная, но умеет работать в гораздо более широком диапазоне питающих напряжений, о трёх до 15 вольт. Если полистать старые журналы, то быстро найдётся устройства, где питание таких микросхем (всяких часов, автоматики и т.п.) производится батарейкой "Крона" или напрямую от бортсети автомобиля (12 вольт).

Кстати, фишка. Некоторые, вроде аналогично работающие микросхемы, отличаются типом защёлки на OC (Output Clock). У одних там именно защёлка (Latch), работающая по уровню сигнала (низкий - хранение, высокий - прозрачно), а у других по фронту (flip-flop).
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

Shaman

Доброго времени суток продолжаю клепать свой космический модуль. И сейчас уткнулся в проблему. Гуру помогите разобраться  :)
Для того чтобы сделать  оранжевый свет на RGB светодиоде, ток на зелёном был просто ограничен (этот пиксель нужен только для оранжевого), а красный регулируется через шим. Поскольку его нужно светить ещё и м полную яркость.
Шим подаётся на 13 ногу U2 и U4 каждая из которых отвечает за группу красных и синих светодиодов.

Fonar.jpg

Поясню, на рисунке 1 светододы сверху в низ синие, зелёные, красные.
Зелёные подключены к U3
Синие слева и красные справа по 4 шт. к U2
Красные слева и синие справа по 4 шт. к U4
И 3 светодиода в центре к U5

Мне нужно получить вот такой эффект
[свернуть]

И вод здесь у меня возникли трудности
Сейчас мигание после бегущих огней организовано просто перезаписью буфера экрана и задержкой через delay

кусок кода
int left_stop ()
{
    _scvagnost;
    _pwm_on
    _pwm_open_BrRl;
    _Null;
    _Green = ABC[4];
    _Blue_right_Red_left = ABC[4];
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(100);
    _Null;
    _Green = ABC[5];
    _Blue_right_Red_left = ABC[5];
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(100);
    _Null;
    _Green = ABC[6];
    _Blue_right_Red_left = ABC[6];
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(100);
    _Null;
    _Green = ABC[7];
    _Blue_right_Red_left = ABC[7];
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(100);
    _Null;
    _Green = ABC[7] | ABC [4];
    _Blue_right_Red_left = ABC[7] | ABC [4];
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(100);
    _Null;
    _Green = ABC[7] | ABC [5];
    _Blue_right_Red_left = ABC[7] | ABC [5];
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(100);
    _Null;
    _Green = ABC[7] | ABC [6];
    _Blue_right_Red_left = ABC[7] | ABC [6];
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(100);
    _Null;
    _Green = ABC[7] | ABC [6] | ABC [4];
    _Blue_right_Red_left = ABC[7] | ABC [6] | ABC [4];
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(100);
    _Null;
    _Green = ABC[7] | ABC [6] | ABC [5];
    _Blue_right_Red_left = ABC[7] | ABC [6] | ABC [5];
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(50);
    _Null;
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(50);
    _Null;
    _Green = ABC[7] | ABC [6] | ABC [5];
    _Blue_right_Red_left = ABC[7] | ABC [6] | ABC [5];
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(50);
    _Null;
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(50);
    _Null;
    _Green = ABC[7] | ABC [6] | ABC [5];
    _Blue_right_Red_left = ABC[7] | ABC [6] | ABC [5];
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(50);
    _Null;
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(50);
    _Null;
    _Green = ABC[7] | ABC [6] | ABC [5];
    _Blue_right_Red_left = ABC[7] | ABC [6] | ABC [5];
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(50);
    _Null;
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(50);
    _Null;
    _Green = ABC[7] | ABC [6] | ABC [5];
    _Blue_right_Red_left = ABC[7] | ABC [6] | ABC [5];
    _Red_right_Blue_left = ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _delay_ms(50);
    _Null;
    _Red_right_Blue_left =  ABC[3] | ABC[2] | ABC[1] | ABC[0];
    _RGB = ABC[0];
    record ();
    _pwm_close_BrRl;
    _pwm_off;
}     
[свернуть]

Но мне хотелось бы реализовать это через тот же 13 пин

И собственно вот. Не могу реализовать это с помощью простейших логических элементов.

Имеем.
1 вывод ШИМ
2 максимум 3 вывода микроконтроллера для управления коммутацией из.

Задача.
Коммутировать
Шим только на правую или левую часть.
Возможность вместо шим или при её отсутствии подавать на вывод 13 0 или 1.

Дополнение. Если выключить шим командой TCCR1B &= ~(1<<CS10), то на выходе останется тот уровень, который был последним и управлять им через выставления бита порта нельзя.

Постарался написать как можно внятнее если расписывать дальше получается поток мыслей. Если что-то не понятно спрашивайте, дополню.

Slabovik

Что-то мне подсказывает (могу быть не прав т.к. до железа сейчас не дотянуться), что состояние пина, на который выводится ШИМ сигнал, можно контролировать, записывая в регистр счётчика таймера так называемое начальное значение (т.е. просто значение), с которого счётчик начнёт счёт, если вновь запустить его. Потому что то, что выводится на пин, зависит от содержимого этого счётчика, которое внутри сравнивается с содержимым регистра сравнения (цифровым компаратором), по результату чего и формируется состояние пина. Если в регистр счётчика записать нужное значение, то можно предопределить состояние пина.
Общением на форуме подпитываю свою эгоистичную, склонную к самолюбованию сущность.

Shaman

Не помогает либо я не правильно пишу код
пробую записать во весь регистр целиком
TCCR1B &= ~(1<<CS10);    //выключаем шим
    _delay_ms(500);
    TCNT1 = 300;
    _delay_ms(500);
    TCNT1 = 100;
    _delay_ms(500);
    TCNT1 = 300;
    _delay_ms(500);
    TCNT1 = 100;
    _delay_ms(500);
    TCNT1 = 300;
    _delay_ms(500);
    TCNT1 = 100;
    _delay_ms(500);
    TCNT1 = 300;
    _delay_ms(500);
    TCNT1 = 100;
    _delay_ms(500);
    TCNT1 = 300;
    _delay_ms(500);
    TCNT1 = 100;
    _delay_ms(500);
    TCNT1 = 300;
    _delay_ms(500);
    TCNT1 = 100;
    _delay_ms(500);
    TCNT1 = 300;
    _delay_ms(500);
    TCNT1 = 100;
    _delay_ms(500);
    TCNT1 = 300;
    _delay_ms(500);
[свернуть]

и в каждый по отдельности
_TCCR1B &= ~(1<<CS10);    //выключаем шим
    TCNT1H = 0b00000000;
    TCNT1L = 0b00000011;
    _delay_ms(500);
    TCNT1H = 0b00000111;
    TCNT1L = 0b00000000;
    _delay_ms(500);
    TCNT1H = 0b00000000;
    TCNT1L = 0b00000011;
    _delay_ms(500);
    TCNT1H = 0b00000111;
    TCNT1L = 0b00000000;
    _delay_ms(500);
    TCNT1H = 0b00000000;
    TCNT1L = 0b00000011;
    _delay_ms(500);
    TCNT1H = 0b00000111;
    TCNT1L = 0b00000000;
    _delay_ms(500);
    TCNT1H = 0b00000000;
    TCNT1L = 0b00000011;
    _delay_ms(500);
    TCNT1H = 0b00000111;
    TCNT1L = 0b00000000;
    _delay_ms(500);
    TCNT1H = 0b00000000;
    TCNT1L = 0b00000011;
    _delay_ms(500);
    TCNT1H = 0b00000111;
    TCNT1L = 0b00000000;
    _delay_ms(500);
    TCNT1H = 0b00000000;
    TCNT1L = 0b00000011;
    _delay_ms(500);
    TCNT1H = 0b00000111;
    TCNT1L = 0b00000000;
    _delay_ms(500);
[свернуть]

 Регистр сравнения устанавливаю командой OCR1A = 256 На 10-ти битном счетчике это середина
На выходе после отключения рандомное состояние (читай последнее установленное до выключения)

Slabovik

Честно говоря, в последние дни был пригружен по работе, поэтому в код не вникал. Но вот тебе картинка, может поможет

Включить-ШИМ.png

Для управления "жёстким" включить-выключить нужно две управляющие ноги.
Нога "Б" включает 100% яркость.
Нога "А" выключает. Но преимущество за ногой "Б", нога "А" не может выключить, если включена нога "Б".

Микросхема КР1564ЛЕ1, она же 74HC02. Одной микросхемы достаточно для управления двумя каналами (потому что в ней четыре логических элемента).

Да, ШИМ получается инвертированный, 100% заполнение на выходе контроллера соответствует минимальной яркости.

Нет ЛЕ1 - возьми универсальную микросхему всех времён и народов ЛА3

Включить-ШИМ-2.png

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