View Categories

Генерирование сигналов

ШИМ сигнал #

На практике может пригодиться программная реализация ШИМ сигнала, например если жалко тратить аппаратный таймер, каналы аппаратного ШИМ уже закончились или если нужен низкочастотный ШИМ.

По счётчику #

Данный подход близок к тому, как работает аппаратный ШИМ на таймере: есть счётчик, который бегает от 0 до максимального значения top. При нулевом значении включаем пин, при совпадении с величиной заполнения ШИМ - выключаем. Всю конструкцию нужно вызывать по таймеру (программному или аппаратному) с нужным периодом, либо просто в loop:

const uint8_t pin = 13;
uint8_t count, duty, top = 255; // 8 бит ШИМ

void setup() {
  pinMode(pin, OUTPUT);
  duty = 128;   // яркость 0-255
}

void tickPWM() {
  if (!count && duty) digitalWrite(pin, 1);
  if (count >= duty && duty != top) digitalWrite(pin, 0);
  count = (count == top) ? 0 : count + 1;
}

void loop() {
    // по таймеру или прямо так, но частота будет нестабильная
    tickPWM();
}

Либо по программному таймеру, например так (период 100 мкс):

void loop() {
    static uint32_t tmr;
    if (micros() - tmr >= 100) {
        tmr += 100;
        tickPWM();
    }
}

Особенности реализации:

  • Происходит однократное переключение состояния пина, что хорошо для производительности
  • Для большей скорости можно использовать библиотеку GyverIO (AVR, ESP8266, ESP32)
  • Легко переносится на любой другой МК или фреймворк - достаточно заменить digitalWrite на свой аналог
  • Частота вызова тикера должна быть в top+1 раз больше желаемой частоты ШИМ. Например для 8-бит ШИМ с частотой 1 кГц тикер нужно вызывать с частотой 256 кГц
  • Можно вызывать в прерывании детектора нуля при управлении сетевым напряжением через симистор или SSR, получится ШИМ с частотой 2.5 секунды для управления инерционными процессами без использования таймера и без выбросов в сеть как при фазовом управлении симистором
  • При значениях 0 и top (максимум) не происходит "дребезга", т.е. пин остаётся полностью выключенным или полностью включенным
  • Для разрешения больше 8 бит нужно использовать соответствующий тип данных, т.е. uint16_t
  • Для разрешения 8 бит можно слегка упростить код:
const uint8_t pin = 13;
uint8_t count, duty;

void setup() {
  pinMode(pin, OUTPUT);
  duty = 128;   // яркость 0-255
}

void tickPWM() {
  if (!count && duty) digitalWrite(pin, 1);
  if (count >= duty && duty != 255) digitalWrite(pin, 0);
  ++count;  // автоматически перейдёт с 255 в 0
}

void loop() {
    tickPWM();
}

Также данная реализация легко расширяется на несколько каналов, которые могут работать на одном таймере:

#define SOFT_PWM_N 3

uint8_t count, top = 255;
uint8_t duty[SOFT_PWM_N];
uint8_t pins[SOFT_PWM_N] = {11, 12, 13};

void setup() {
  for (uint8_t pin : pins) pinMode(pin, OUTPUT);
  duty[0] = 10;
  duty[1] = 128;
  duty[2] = 200;
}

void tickPWM() {
  if (!count) {
    for (uint8_t i = 0; i < SOFT_PWM_N; i++) {
      if (duty[i]) digitalWrite(pins[i], 1);
    }
  }
  for (uint8_t i = 0; i < SOFT_PWM_N; i++) {
    if (count >= duty[i] && duty[i] != top) digitalWrite(pins[i], 0);
  }
  count = (count == top) ? 0 : count + 1;
}

void loop() {
    tickPWM();
}

По двум периодам #

Второй вариант - переключать пин по двум периодам. Пример реализации:

const uint8_t pin = 13;
bool state;
uint16_t tmr, high, low;
const uint16_t depth = 8;   // разрешение, бит
const uint16_t freq = 50;   // частота ШИМ, Гц

// установить заполнение
void setPWM(uint16_t duty) {
    if (duty == 0 || duty >= (1 << depth) - 1) {
        // граничные значения - отключаем таймер и подаём сигнал
        high = 0;
        digitalWrite(pin, duty);
    } else {
        uint32_t prd = 1000000ul / freq;
        high = (duty * prd) >> depth;
        low = prd - high;
    }
}

// тикер, вызывать в loop
void tickPWM() {
    if (high && uint16_t(uint16_t(micros()) - tmr) >= (state ? high : low)) {
        tmr = micros();
        state ^= 1;
        digitalWrite(pin, state);
    }
}

void setup() {
    pinMode(pin, OUTPUT);
    setPWM(200);
}

void loop() {
    tickPWM();
}

Сигма-дельта, PDM #

PDM - импульсно-плотностная модуляция (Pulse-Density Modulation). Как и ШИМ, позволяет представить аналоговый сигнал в виде цифровых импульсов. В отличие от ШИМ, имеет фиксированную ширину импульсов, но разный период между ними - сигнал состоит из одиночных импульсов, а среднее значение соответствует плотности этих импульсов на единицу времени. PDM даёт более высокочастотный шум, который проще сгладить фильтром для получения аналогового сигнала, поэтому часто используется для модуляции аналоговых сигналов (ЦАП), в том числе для воспроизведения звука.

Для управления моторами лучше использовать PWM (ШИМ), он эффективнее сглаживается индуктивностью мотора, меньше шумит и гораздо стабильнее работает

Для генерирования PDM можно использовать сигма-дельта алгоритм, который работает как алгоритм Брезенхема для рисования растровых линий - накапливает ошибку и переключает состояние при превышении порога, таким образом ошибка распределяется по времени:

const uint8_t pin = 13;
const int16_t top = 255;  // "разрешение" сигнала
uint16_t value = 100;     // значение, от 0 до top
int16_t accum = 0;

void setup() {
    pinMode(pin, OUTPUT);
}

void tickSD() {
    accum += value;
    if (accum >= top) {
        accum -= top;
        digitalWrite(pin, 1);
    } else {
        digitalWrite(pin, 0);
    }
}

void loop() {
    tickSD();
}

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

Для сглаживания PDM и получения аналогового сигнала можно использовать RC фильтр:

Номиналы подбираются под задачу и частоту сигнала. Например для первичной фильтрации звукового сигнала можно взять 10 кОм и 0.001 мкФ, а частота PDM - порядка нескольких МГц.

Полезные страницы #

Подписаться
Уведомить о
guest

0 комментариев
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии
Прокрутить вверх