Странный звук при модуляции ширины импульса на Arduino

Я пытаюсь создать звук с широтно-импульсной модуляцией, используя Arduino Nano.

Но он издает странный звук, чего я и не ожидал.

Вот мой код:

class Phasor
{
public:
    Phasor(const float frequency)
    :frequency(frequency)
    ,phase(1.0f)
    ,lastTimeMicros(0){};
    virtual ~Phasor(){};
    void setFrequency(const float frequency)
    {
        this->frequency = frequency;
    }
    float process()
    {
        const unsigned long currentTimeMicros = micros();
        if (phase == 1.0f) lastTimeMicros = currentTimeMicros;
        const unsigned long elapsedTimeMicros = currentTimeMicros - lastTimeMicros;
        const unsigned long cycleDurationMicros = static_cast<unsigned long>(1000.0f / frequency) * 1000;
        if (elapsedTimeMicros < cycleDurationMicros)
            phase = static_cast<float>(elapsedTimeMicros) / static_cast<float>(cycleDurationMicros);
        else
            phase = 1.0f;
        return phase;
    }
private:
    float frequency;
    float phase;
    unsigned long lastTimeMicros;
};

class PulseOsc
{
public:
    PulseOsc(const float frequency, const float pulseWidth)
    :phasor(frequency)
    ,pulseWidth(pulseWidth){};
    virtual ~PulseOsc(){};
    void setFrequency(const float frequency)
    {
        this->phasor.setFrequency(frequency);
    }
    void setPulseWidth(const float pulseWidth)
    {
        this->pulseWidth = pulseWidth;
    }
    float process()
    {
        const float phase = phasor.process();
        if (phase <= pulseWidth)
            return 1.0f;
        else
            return -1.0f;
    }
private:
    Phasor phasor;
    float pulseWidth;
};

Phasor *phasorLFO;
PulseOsc *pulseOsc;

void setup()
{
    Serial.begin(9600);
    pinMode(9, OUTPUT);
    phasorLFO = new Phasor(0.1f);
    pulseOsc = new PulseOsc(440.0f, 0.5f);
}

void loop()
{
    pulseOsc->setPulseWidth(phasorLFO->process());
    int outVal = LOW;
    if (pulseOsc->process() > 0.0f) outVal = HIGH;
    digitalWrite(9, outVal);
}

Результирующий звук очень странный и глючный. Он отлично работает, когда я фиксирую значение ширины импульса. (без ГНЧ)

Что не так с моим кодом? Как это исправить? Буду признателен за любой совет.

, 👍0

Обсуждение

Вероятно, Nano недостаточно мощен, чтобы своевременно выполнять все эти вычисления с плавающей запятой., @Majenko


2 ответа


Лучший ответ:

1

Я вижу две проблемы с этой программой. Первый уже был указал Маженко в комментарии: вы делаете слишком много плавающего точечные расчеты. Второе связано с тем, как ваш Phasor управляет временем.

Если вы посмотрите учебник Мигать без задержки по Arduino, вы увидит что-то вроде этих строк:

if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    toggle_the_LED();
}

В этом коде interval — это минимальное время между переключениями. В любое время программа немного опаздывает, и currentMillis - previousMillis строго больше, чем interval, это дополнительное время теряется и программа будет опаздывать для всех последующих переключений светодиодов. Если вместо минимальное интересующее вас среднее время между переключениями, то вы должны написать:

if (currentMillis - previousMillis >= interval) {
    previousMillis += interval;  // <- вперед ровно на один период
    toggle_the_LED();
}

Теперь previousMillis больше не является временем, когда последний переключатель был выполнено, это время, когда последнее переключение было запланировано. С этой версии у вас все еще может быть некоторое дрожание, но, по крайней мере, среднее частота должна быть правильной. Если программа настолько загружена, что может получить опоздал на полный период, то было бы лучше заменить if на пока, чтобы наверстать все пропущенные периоды.

Ваш метод Phasor::process() немного сложнее, чем метод учебник выше, но линия

if (phase == 1.0f) lastTimeMicros = currentTimeMicros;

выбивает вас из графика точно по той же причине.

Тем не менее, вот мое мнение о вашей идее вектора, полностью не проверено:

class Phasor()
{
public:
    Phasor(float frequency_Hz)
    {
        period = round(1e6 / frequency_Hz);
        frequency = 0x10000 / period;
    }
    uint16_t process()
    {
        uint16_t now = micros();
        while (now - last_time >= period)
            last_time += period;
        return (now - last_time) * frequency;
    }
private:
    uint16_t period;     // unit = 1 us
    uint16_t frequency;  // unit = 1/(2^16 us) ~ 15.3 Hz
    uint16_t last_time = 0;
};

Я кое-что изменил в вашей реализации:

  • удален метод setFrequency(), поскольку YAGNI
  • без плавающей запятой, кроме как в конструкторе, по соображениям производительности
  • только 16-битная арифметика по той же причине
  • нет деления в process(), потому что деление обходится дорого
  • фаза не сохраняется, так как хранить ее бесполезно
  • фаза – это целое число, состоящее из 2−16 периодов
  • .

Обратите внимание, что единица измерения, выбранная для фазы, является наиболее естественной, когда работа с 16-битной арифметикой. Обратите также внимание на то, что хранение микросекунд в 16-битные переменные работают только для частот выше 15,3 Гц. А очень низкочастотный фазовращатель (например, ваш «LFO») потребует 32-битного арифметика.

,

2

Похоже, вы используете системные часы micros() для расчета elapsedTimeMicros, поэтому, вероятно, время, затраченное на процесс расчета и отправки сигнала на вывод, может повлиять это может привести к непостоянству рабочего цикла.

,