Несмотря на мой оптимизм по поводу встраиваемых систем и микроконтроллеров, во многих областях техники до сих пор используются старые контроллеры, в которых нужно экономить буквально каждый бит. А если в нём нет ещё и продвинутого таймера с аппаратным ШИМ, то реализация модуляции превращается в сущий ужас — времени на обработку не хватает, тайминги плывут, волосы на голове рвутся — чёткую обработку параллельных задач сделать тяжело, а памяти и мощности на нормальную RTOS нет. Конечно, я преувеличиваю, но в мощных системах тоже иногда может пригодиться описанный метод. Дальше будем говорить только о программном ШИМ, потому что при аппаратной его реализации всё решается проще. Однако я не исключаю что метод пригодится и для совершенно других задач.

Также примем обозначение «скважностный сигнал» для сигнала с переменным суммарным коэффициентом заполнения. Конечно, обычно он называется ШИМ–сигналом, но я хочу показать разницу между PWM методом генерирования такого сигнала и BAM–методом.

Чем плох ШИМ? Тем, что ему нужно два таймера — на состояние «1» и состояние «0», или на состояние «1» и полный цикл. К тому же эти таймеры приходится настраивать на точные временные промежутки. А при изменении периода ШИМ приходится перенастраивать таймеры. Но это ещё ничего, гораздо хуже спектр сигнала после такой модуляции, поскольку изменения сигнала происходят сравнительно редко. А ещё обычная проблема со значениями 0% и 100% — нужно специальным образом обрабатывать их чтобы не происходило нежелательных выбросов на границе временных промежутков.

Существует другой метод модуляции, который проще всего понять, рассмотрев перевод числа из двоичной формы в десятичную. Допустим, чтобы получить двоичный вид числа 10100110, мы вычисляем значение выражения 128*1+64*0+32*1+16*0+8*0+4*1+2*1+1*0. Думаю, вы уже видите соль — нужно настроить таймер на 128 единиц времени, вывести «1», после окончания этого таймера настроить его (или другой таймер) на 64 единицы, вывести «0» и так далее. Да, мы не ушли от нескольких таймеров или необходимости их перенастройки — однако мы значительно подняли полосу частот, и теперь нужен не такой сильный фильтр, как после стандартного ШИМ. Довольно удобно что все длительности таймеров оказываются пропорциональны степеням двойки, назовём их «степенными временными отрезками».

Стоит сказать, что этот метод может быть плох для управления полевыми транзисторами, потому что им периодически придётся включаться на очень короткое время, равное одной выбранной единице времени. Если после транзистора стоит индуктивная нагрузка — появится сильный экстраток. Однако для других применений, например для вывода звука, метод очень удобен.

Самое интересное начинается если вам нужно одновременно генерировать несколько скважностных сигналов. В случае ШИМ вам понадобится заводить пару отдельных таймеров на каждый сигнал (либо по одному таймеру на вывод «1» в каждом сигнале, и один таймер на обнуление всех сигналов). Либо, как в методе DI HALT, строить массив по возрастанию длительностей ШИМ, заводить таймер на минимальную длительность и каждый раз перенастраивать его на новую разницу длительностей. У ди хальта всё сделано правильно, сервомоторы принимают только ШИМ сигнал и никакой другой. Я говорю что этот метод не стоит применять для управления, допустим, восемью нагревателями.

В случае BAM достаточно на каждом степенном временном отрезке просто считывать параметры сигналов из массива и устанавливать соответствующие значения на всех GPIO–выходах — независимо от количества нужных РАЗНЫХ скважностных сигналов вам нужен только один таймер и один кусок простого кода. Никакой сложной возни с сортировкой массива, и прочего.

Я покажу в javascript–like псевдокоде, что я хочу вам донести.

Демонстрация простого кода для BAM-модуляции

var step = 0, quantum = 10, signals = [gpio_out, value]; //step — текущий шаг, quantum — временная единица, signals — массив значений сигналов и ног, на которые их нужно вывести

var function = timerEvent(timer) { //Таймер сработал

    step = (step + 1) % 8; //обновляем значение шага, он постоянно крутится от 1 до 8

    power_of_2 = 2^step; //текущая степень двойки

    for (i in signals) //Бежим по всему массиву signals

        gpio[signals[i].gpio_out] = signals[i].value & power_of_2 > 0; //в соответствующий GPIO выводим очередной бит сигнала. Бит получаем так — делаем побитовое И значения сигнала и конструкции из нулей, в которой единица на месте текущего шага, если это число больше нуля — значит, в позиции текущего шага в значении сигнала находится единица, и нужно включить GPIO.

    timer.period = quantum * power_of_2; //Обновляем значение периода. Можно просто умножать прошлый период на два.

    timer.start; //Снова запускаем таймер

}

Всё. Этот код достаточен для одновременного получения любого количества РАЗНЫХ BAM–модулированных сигналов. Используйте!

Куча дополнительной информации: сайт классного человека bsvi.ru, замечательный сайт easyelectronics.ru, сравнение ШИМ и BAM, хороший пример кода, обязательная ссылка на хабр про собаку-сияку, ещё один пример на клоне хабра.