Перевод настроек PIC PWM в ATMega328P

Пытаюсь написать код на C, работающий на PIC18F, работающем на Arduino '328P. Он использует 8-битный таймер, ШИМ и таблицу преобразования для генерации синусоидальных сигналов. Надеюсь, получится запустить его с помощью Timer2 на Nano.

Код PIC взят из этой записи в блоге Романа Блэка. Я понимаю, что делает код PIC, но не могу перевести режимы и флаги в формат Nano-land.

PIC работает на частоте 8 МГц, Nano — на 16 МГц без предварительного масштабирования. PIC использует период ШИМ 128, Nano — 256. Я немного изменил значения PIC, но суть та же.

 const byte sin[64] = {...some values};
 uint32_t wave;
 uint32_t incr = 17179869;  // для 1000 Гц

 PR2 = (128-1);           // Период ШИМ = 128
  while(1) {
    while(!PIR1.TMR2IF);  // выравниваем по началу цикла ШИМ
    PIR1.TMR2IF = 0;
    wave += incr;
    CCPR2L = sin[(wave >> 24) & 0x3F]; // загрузить в модуль ШИМ
  }

Для полноты информации техническое описание PIC находится здесь

У меня есть следующая конфигурация Nano с целью быстрой ШИМ, без предварительного масштабирования, выход на выводе 11 (канал «B» таймера 2).

 TCCR2A = bit(COM2A0)
          | bit(COM2B1)
          | bit(WGM21)
          | bit(WGM20);

 TCCR2B = bit(WGM22) | bit(CS20);

Тогда генерация волны выглядит так:

while (true) {
  while ((TIMSK2 | bit(OCIE2B)) == 0) {}
  TIMSK2 |= bit(OCIE2B);
  wave += incr;
  OCR2A = sinLUT[(wave >> 24) & 0x3f];
}

Он не генерирует ничего похожего на синусоиду на частоте 1000 Гц, так что я явно что-то упускаю. Кстати, я фильтрую выходной сигнал резистором 2,2 кОм/100 нФ.

РЕДАКТИРОВАНО: ОБНОВЛЕНО

Как и просили, вот последняя настройка. Похоже, вывод немного глючит, хотя я не могу понять, где именно.

pinMode(4, OUTPUT);   // для стабильного триггера
pinMode(3, OUTPUT);   // выход таймера 2 B
TIMSK2 |= bit(TOIE2); // включить TOV2 = переполнение таймера 2
TCCR2A = bit(COM2B1)  // быстрый ШИМ, верх = 0xFF
       | bit(WGM20)   // Режим ШИМ 3
       | bit(WGM21);
TCCR2B = bit(CS20);   // без предварительного масштабирования

Затем:

ISR(TIMER2_OVF_vect) {  // Частота цикла ШИМ

  static uint8_t div;
  static uint32_t wave;
  const unit32_t incr = 1024 * 64 * 65536 / 62500 // 1024 Гц

  TIFR2 |= bit(TOV2); // очистить прерывание
  if (div++ == 0) PORTD ^= bit(4); // триггер области действия
  wave += incr;
  OCR2B = sinLUT[(wave >> 16) & 0x3f];
}

, 👍1

Обсуждение

@hcheung — Спасибо, но, как я уже говорил, я знаю, что делает PIC, и использую RC-фильтр на выходе. Но я не могу заставить '328 делать то же самое., @Jim Mack

Знаете ли вы, что выражение TIMSK2 | bit(OCIE2B) всегда отлично от нуля? -- Да, и чтобы мы могли воспроизвести это, пожалуйста, рассмотрите возможность расширить ваш фрагмент кода до минимального, полного и воспроизводимого наброска., @the busybee

@thebusybee — Верно подмечено. Да, это был удар по голове. Я перепутал прерывание *Enable* с прерыванием *Flags*. Что касается полного наброска, то я обычно так и делаю, но это было больше похоже на теорию. Как обычно, Эдгар Бонет разобрался с теорией на скорую руку., @Jim Mack


2 ответа


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

1

Здесь довольно много проблем.

Сначала вам нужно решить, какой канал вы будете использовать. Вы написали что вывод 11 — это канал B таймера 2, но это неверно:

  • контакт 11 = PB3 = OC2A = канал таймера 2
  • контакт 3 = PD3 = OC2B = канал B таймера 2

Далее необходимо установить пин как выход:

// либо:
DDRB  |= bit(PB3);  // установить контакт 11 = PB3 = OC2A как выход
// или:
DDRD  |= bit(PD3);  // установить вывод 3 = PD3 = OC2B как выход

Для включения неинвертирующего ШИМ необходимо установить либо бит COM2A1, либо COM2B1, в зависимости от нужного вам канала. Нет смысла трогать бит COM2A0, который предназначен для режима «включения при сравнении» (не то, что вам нужно).

Что касается режима таймера, вы выбрали режим 7 (быстрый ШИМ с TOP = OCR2A). Возможность установки значения TOP предназначена для включения частотной модуляции, ценой потери возможности ШИМ на Канал А. Опять же, это не то, что вам нужно. Вместо этого вы хотите, чтобы таймер... посчитайте все от 0x00 до 0xff, что вам дает режим 3.

Затем есть код, который ждет совпадения при сравнении, чтобы Обновляйте значение ШИМ в нужный момент. Вы используете не тот бит: OCIE2B — это бит разрешения прерывания, который используется для включения соответствующее прерывание. Если вы включили прерывание и не определили подходящую процедуру обслуживания прерываний, тогда ваша программа будет Сбой (перезапуск). Здесь прерывания не нужны. Вместо этого вы можете просто проверьте «флаг прерывания» OCF2A или OCF2B (в зависимости от канал) из регистра TIFR2.

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


Изменить: Вот некоторые комментарии по обновленной версии вашего кода.

Выражение 1024 * 64 * 65536 / 62500 переполняется в обоих случаях умножения. Я предлагаю вычислить это как число с плавающей точкой и присвоить ему к целому числу.

Линия

TIFR2 |= bit(TOV2); // очистить прерывание

полезно только при опросе флага прерывания с помощью прерываний Отключено. Поскольку вы сейчас используете прерывание, вам это не нужно: Флаг автоматически сбрасывается, когда начинается выполнение ISR.

Если вы хотите переключить выходной контакт, вы можете сделать

PIND |= bit(4);

которая представляет собой единую машинную инструкцию и более эффективна, чем PORTD ^= bit(4); (последовательность «чтение-изменение-запись»). Затем, для мониторинга выполнение ISR на прицеле, я бы предпочел установить и очистить прикреплять к каждому выполнению ISR.

Я протестировал вашу программу с ISR, изменённым следующим образом:

ISR(TIMER2_OVF_vect) {
    static uint32_t wave;
    const uint32_t incr = 1.0 * 64 * 256 * 65536 / F_CPU;

    PORTD |= bit(4);
    wave += incr;
    OCR2B = sinLUT[(wave >> 16) & 0x3f];
    PORTD &= ~bit(4);
}

Обратите внимание, что я снизил частоту до 1 Гц, чтобы лучше видеть Поведение в области видимости. Вывод выглядит очень чистым, на мой взгляд. Есть некоторые сбои на 4-м выводе импульсов, которые иногда запаздывают примерно на 6,2 мкс. Это происходит из-за прерывания по переполнению таймера 0, которое используется Ядро Arduino для хронометража. Если вам не нужен Arduino, функции хронометража, вы можете избежать этих сбоев, отключив их прерывание. Однако, поскольку ISR никогда не опаздывает более чем на один ШИМ, цикла, сбои не должны оказывать никакого влияния на генерацию сигнала ШИМ.

,

Большое спасибо. Я уже понял, что перепутал прерывание *Enable* (TIMSK2) с прерыванием *Flags* (TIFR2), и исправил это. Режимы 7 и 3 работали одинаково, потому что у меня OCR2A = 0xFF (TOP = MAX). Я переключился на режим 3. Затем включил TOV и перенёс обновления в обработчик прерываний (ISR). Всё работает как надо, но осциллограф показывает, что это немного глючит. Тем не менее, прогресс значительный., @Jim Mack

@Jim Mack, пожалуйста, добавьте свой последний код к вашему вопросу. Код PIC выглядит удивительно компактным, и его точность высоко оценивается. Стоит заняться разработкой аналога решения «Roman Black» для Arduino (без глюков)., @6v6gt

@6v6gt — подойдёт. Базовый метод высокой точности довольно хорошо известен, но я не видел его применения к синусоидам. Я перешёл от 24-битной к 16-битной дроби с фиксированной точкой, поскольку такой уровень точности не гарантируется на Nano., @Jim Mack

Спасибо за добавленные заметки. На самом деле я вычислил const float = 67.108864 и умножил на частоту, чтобы получить приращение. Цепочка умножений была нужна только для того, чтобы показать вывод. Согласен, что F_CPU должен быть множителем., @Jim Mack


0

Поступила просьба опубликовать полную схему, демонстрирующую этот метод в действии, и, хотя для этой цели это излишне, я думаю, что всем, кто интересуется генерацией синусоидальных волн, эта запись в блоге будет полезна. Ещё раз спасибо @edgarbonet.

,