Прерывание таймера срабатывает слишком часто

Следующий вопрос касается Arduino Pro Mini (8 МГц, 3,3 В)

Я разрабатываю (еще один) шилд драйвера светодиодной ленты. Для этого я написал простой контур управления, который регулирует яркость канала (рабочий цикл ШИМ) до заданного целевого значения с плавным переходом.

Для тестирования я настроил Timer1 с интервалом 0,5 Гц для случайного назначения новых целевых значений. Это работает почти так, как и предполагалось. При просмотре значений вы можете видеть постоянные периоды значений и совершенно случайные, быстро меняющиеся периоды между ними (изображение создано с помощью встроенного графика Arduino). Изогнутые, довольно плавные линии — это выходы ШИМ, следующие за целевым значением (эта часть, как подтверждено, работает):

Соответствующий код, отвечающий за изменение целевого значения:

ISR(TIMER1_COMPA_vect) //прерывание таймера 1 0,5 Гц
{ 
  for(auto it = 0; it < 4; ++it)
        channel_target[it] = random(0, 255);  // присваиваем случайное значение в диапазоне 0..255 (максимум для ШИМ)
}

Я предполагаю, что ISR вызывается очень быстро в нечетких интервалах, но поскольку я еще не много работал с Arduino, я не знаю, какие фоновые функции могут этому помешать.

Для полноты картины полный код:

double          channel[4];
uint8_t         channel_target[4];
const double    channel_k_p[4]      = { 0.001, 0.001, 0.001, 0.001 };
const double    channel_k_i[4]      = { 0.01, 0.01, 0.01, 0.01 };
double          err;
const uint8_t   led_pins[4]         = { 3, 5, 6, 9 };
char            serial_buffer[100];

void setup() 
{
    Serial.begin(9600);

    for(auto it = 0; it < 4; ++it)
        channel[it] = 0;

    for(auto it = 0; it < 4; ++it)
    {
        pinMode(led_pins[it], OUTPUT);   // устанавливает вывод как выход
        analogWrite(led_pins[it], 0);
    }

    cli();//остановить прерывания


    TCCR1A = 0;// установить весь регистр TCCR1A в 0
    TCCR1B = 0;// то же самое для TCCR1B
    TCNT1  = 0;//инициализируем значение счетчика до 0
    // установить регистр сравнения для приращения 1 Гц
    OCR1A = 15624;// = (8*10^6) / (0.5*1024) - 1 (должно быть < 65536)
    // включить режим CTC
    TCCR1B |= (1 << WGM12);
    // Установить биты CS10 и CS12 для предварительного делителя 1024
    TCCR1B |= (1 << CS12) | (1 << CS10);  
    // включить прерывание сравнения таймера
    TIMSK1 |= (1 << OCIE1A);

    //установить прерывание таймера 2 на 500 Гц
    TCCR2A = 0;// установить весь регистр TCCR2A в 0
    TCCR2B = 0;// то же самое для TCCR2B
    TCNT2  = 0;//инициализируем значение счетчика до 0
    // установить регистр сравнения для приращения 8 кГц
    OCR2A = 248;// = (8*10^6) / (500*64) - 1 (должно быть < 256)
    // включить режим CTC
    TCCR2A |= (1 << WGM21);
    // Установить бит CS21 для 64-кратного предварительного делителя
    TCCR2B |= (1 << CS21) | (1 << CS20);   
    // включить прерывание сравнения таймера
    TIMSK2 |= (1 << OCIE2A);

    sei();  // повторно активировать прерывания

    randomSeed(analogRead(0));
}

void loop() 
{
    for(auto it = 0; it < 4; ++it)
    {
        sprintf(serial_buffer, "%d ",  channel_target[it]); 
        Serial.write(serial_buffer);
    }

    for(auto it = 0; it < 4; ++it)
    {
        sprintf(serial_buffer, "%d ", round(channel[it])); 
        Serial.write(serial_buffer);
  }
    Serial.write("\n");

    delay(100);
}

ISR(TIMER2_COMPA_vect)  // прерывание таймера 2 на частоте 1 кГц
{  
    // *******************************
    // Контур управления яркостью канала
    for(auto it = 0; it < 4; ++it)
    {
        err = channel_target[it] - channel[it];

        channel[it] += err * channel_k_p[it];           // пропорциональный
        channel[it] -= err * 0.002 * channel_k_p[it];   // интеграл

        if(channel[it] > 255)
            channel[it] = 255;
        analogWrite(led_pins[it], floor(channel[it]));
    }
}

ISR(TIMER1_COMPA_vect) //прерывание таймера 1 0,5 Гц
{ 
  for(auto it = 0; it < 4; ++it)
        channel_target[it] = random(0, 255);  // присваиваем случайное значение в диапазоне 0..255 (максимум для ШИМ)
}

, 👍3


1 ответ


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

1

Функция analogWrite() генерирует выходной сигнал ШИМ с использованием аппаратный таймер. Выход на выводе 3 управляется таймером 2, тогда как выход на контакте 9 управляется таймером 1. Здесь вы пытаетесь используйте эти таймеры для генерации ШИМ и для собственной синхронизации кода в то же время. Это не может работать. Либо вы позволяете библиотеке ядра Arduino используйте таймеры для analogWrite(), или вы принимаете их для себя, но не и то, и другое.

Решение вашей проблемы очень простое. В вашем коде на самом деле нет ничего требует таймера для начала. Вы можете управлять как яркостью контур управления и значение изменяется с использованием техники Blink Без задержки Учебник Arduino:

const uint32_t TARGET_CHANGE_PERIOD = 500000;
const uint32_t BRIGHTNESS_UPDATE_PERIOD = 1000;

void loop()
{
    uint32_t now = micros();
    static uint32_t last_target_change;
    if (now - last_target_change >= TARGET_CHANGE_PERIOD) {
        last_target_change += TARGET_CHANGE_PERIOD;
        // Измените целевые значения яркости.
    }
    static uint32_t last_brightness_update;
    if (now - last_brightness_update >= BRIGHTNESS_UPDATE_PERIOD) {
        last_brightness_update += BRIGHTNESS_UPDATE_PERIOD;
        // Обновляем яркость каналов.
    }
}
,