Ниже прилагаю полность програмную реализацию ШИМ, это перевод из книги Make AVR Programming Элиота Вильямса и моя интерпритация.
Код разделен на две независимые части. Функция pwmAllPins(), реализует програмный шим. Остальной код в главном цикле, увеличивает и уменьшает переменную отвечающую за яркость. Вызов функции pwmAllPins() и увеличение и уминьшение яркости приводит к пульсированию светодиодов.
В pwmAllPins(), мы итеритуемся по i, что дает ровно 256 шагов каждый раз. В нутри цикла мы сравниваем переменную яркости с i и включаем светодиоды если i меньше. Это дает эффект включенных светодиодов в начале каждого прохода до 256 и затем отключает их, так скоро как i достигает значени переменной яркости, так что для более высокого значение яркости светодиоды отсаются включены на больший процент из 256 шагов. ШИМ! Но следует отметить, это загружает процессор под завязку утилизируя его только на включение и отключение светодиодов.
Часть кода main() устанавливает переменную направления direction (как целое число со знаком) что позволяет нам добавлять или вычитать текущую яркость. Наконец, когда яркость достигает предела, направление меняет знак. В результате получается пульсирующее затухание и нарастание яркости.
Справка: Человеческий глаз гораздо лучше различает низкие уровни освещенности чем более высокие. То есть реакция человеческого глаза на свет нелинейна. Много светодиодов управляются в режиме PWM чтобы использовать этот факт, если вы заметите мерцание светодиодов светофора, когда вы внезапно поворачиваете голову мимо них, это ШИМ. Причина, по которой Светодиоды с ШИМ-управлением настолько распространены, в том что ваши глаза не могут заметить разницу между рабочим циклом 90% и 100%, поэтому город может использовать светофоры на 90% и сохранить 10% энергии.
Ссылку на gist с кодом прилагаю