Diving into the PWM
- Войдите на сайт для отправки комментариев
Втр, 17/12/2019 - 19:56
Коллеги, предлагаю собрать в кучку каверзные задачки по PWM на ATMega328P/PB.
Может оно никому и не будет нужно, но таки начну.
Коллеги, предлагаю собрать в кучку каверзные задачки по PWM на ATMega328P/PB.
Может оно никому и не будет нужно, но таки начну.
Эксперимент #1: включение и выключение таймера.
Цель: включить и выключить генерацию по событию.
Результат: частично удовлетворительный.
Решение задачи, казалось бы, очень простое - манипулируй Clock Select Bit и бед не знай. Надо остановить - обнуляй, запустить - устанавливай. Однако, тут нас поджидает подводный камень - на связанном с каналом таймера выходе однозначное состояние само по-себе не устанавливается при остановке таймера. В каком состоянии застало вход обнуление прескалера, в таком он и висит.
Помочь нам пытается Datasheet: "The extreme values for the OCR1x register represents special cases when generating a PWM waveform output in the phase correct PWM mode. If the OCR1x is set equal to BOTTOM the output will be continuously low and if set equal to TOP the output will be set to high for non-inverted PWM mode."
Т.е. хотите зафиксировать выход как LOW в неинвертированном режиме - делайте OCRnx = BOTTOM, нужен HIGH - применяйте OCRnx = TOP. Для инвертированного всё наоборот.
С таким приёмом всё становится более лучше - вход можно "заморозить" в требуемом состоянии. Однако, это не может произойти мгновенно в силу того, что регистры OCRnx реализованы с применением технологии "double buffered output compare registers", которая предписывает предварительно помещать новое значение регистра во временный буфер с целью корректировки "настоящего" в определённых точках. Для режима "Phase and frequency correct" (#8, #9) - при совпадении TCNTn с BOTTOM (нулём); для "Phase correct" (#10, #11) - при совпадении TCNTn с TOP (ICRn или OCRnx).
Подобное поведение механизма работы таймера приводит к тому, что отставание остановки генерации от события, по которому она должна быть осуществлена, имеет существенную величину - вплоть до одного периода.
В ходе экспериментов у меня родилась следующая идея: для скорейшей остановки генерации требуется задать OCRnx, затем вызвать его обновление установкой TCNTn = BOTTOM или TCNTn = TOP, а затем быстро скинуть TCNTn в положение BOTTOM для фактической установки LOW на выходе.
Я её проверил. В коде:
На картинках с осциллографа можно видеть, что в режиме "Phase & frequency correct" при попытке принудительной синхронизации при остановке генерации происходит выброс на целый период. Однако, начало генерации можно привязать к моменту событию.
Режим "Phase correct" более благосклонен к просьбам остановить генерацию. Впрочем, в некоторых случаях (при изменении частоты), я видел, что выход "замораживается" с небольшим отставанием. Максимально приблизить начало работы генератора к моменту события можно "прыгнув" в точку синхронизации сразу после установки OCRnx.
К сожалению никакие фокусы с последовательными прыжками сначала в точку синхронизации и, затем, в точку "заморозки" выхода не принесли стабильного результата. Хотя и в некоторых диапазонах частот это срабатывало, но, при более масштабной проверке, картинка портилась. Полагаю, что это связано с направлением входа (счёт сверху или снизу) TCNTx в точку переключения, а управлять им нет возможности.
Тем не менее, нашёлся небольшой грязный хак, позволяющий отрубать лишнее - принудительное отключение выхода МК от Waveform Generator и перевод в заданное состояние (см. применение cbi()/sbi() в функциях запуска и остановки генерации). Это наврядли можно назвать хорошим решением для высоких скоростей, но...
Впрочем, отрицательный результат тоже полезен.
Дополнения, объяснения спецэффектов и критика приветствуются.
UPD: На ATMega328PB с использованием OCM результат более достойный - сигналы прекращаются одновременно, без "перелётов". Запуск, правда, синхронизировать не удаётся - как наложатся частоты, так и будет.
Эксперимент #2
Цель: манипуляция выходом таймера, когда он подключен к Waveform Generator.
Результат: отрицательный
Подобно сибирским мужикам из анекдота про лом и бензопилу я давно собирался проверить одну глупую мысль - а что будет, если портить форму сигнала, который вылезает изнутри таймера, простой установкой состояния пина. В даташите, конечно, написано "The general I/O port function is overridden by the output compare (OC0x) from the waveform generator if either of the COM0x1:0 bits are set.", но... как оно выглядит на самом деле?
Запускаем генератор в режиме #11 на 1Hz и наваливаем в pin D10 уровень HIGH:
ATMega328 не задымилась, позиции не сдала, продолжала генерировать как ни в чем не бывало.
Эксперимент #3: "Fast PWM mode" vs "Phase and frequency correct mode"
Цель: увидеть разницу между двумя режимами.
Результат: успех.
В Datasheet читаем: "Fast PWM Mode. ... This high frequency makes the fast PWM mode well suited for power regulation, rectification, and DAC applications. High frequency allows physically small sized external components (coils, capacitors), hence reduces total system cost." и "Phase and Frequency Correct PWM Mode ... However, due to the symmetric feature of the dual-slope PWM modes, these modes are preferred for motor control applications."
Что же такого плохого в дешёвом Fast PWM? Компилируем, заливаем...
Смотрим на осциллографе картинки:
Вон оно чего - на "fast PWM" всплески при скважности 0%. Двигатель будет подёргивать на стоянке. Ибо "The extreme values for the OCR1x registers represents special cases when generating a PWM waveform output in the Fast PWM mode. If the OCR1x is set equal to BOTTOM (0x0000) the output will be a narrow spike for each TOP+1 timer clock cycle. Setting the OCR1x equal to TOP will result in a constant high or low output (depending on the polarity of the output which is controlled by COM1x[1:0])."
Дополнительное отличие - импульсы "толще" вдвое. Потому что это "The dual-slope operation" - смена состояния выхода происходит как на подъёме TCNTn к TOP, так и на спуске к BOTTOM.
К слову - в Wiring всё красиво. Таймеры инициализируются в "Phase correct pwm mode (8-bit)"
Эксперимент #4: Синхронизация таймеров
Цель: увидеть разницу между сигналами синхронизированных и несинхронизированных генераторов PWM.
Результат: успех.
Немного переработав честно украденный у dimax-а код, рассмотрим эффект синхронизации под микроскопом:
Разница между несинхронно стартовавшими таймерами видна на относительно больших частотах
При увеличении заметно, что эффект от синхронизации для любительских целей более чем приемлем - наносекудная точность.
На низких частотах, по-моему мнению, синхронизация - перфекционизм чистой воды.
Эксперимент #5: Hard&Soft PWM
Цель: "перенести" PWM на выходы, не связанные с таймерами.
Результат: успех.
На Arduino Pro Micro загрузчик плавно моргает набортным светодиодом. Получится ли сделать и на Mini/Nano/Uno сделать красиво? В принципе - да.
Просто будем паразитировать на каком-нибудь таймере и устроим дискотеку на D13 или любом другом пине, не поддерживающем PWM на аппаратном уровне.
Сдвиг относительно основного сигнала, естественно, имеется. Стабильность такого ШИМ-а, разумеется, зависит от того, насколько долго будут блокироваться прерывания МК.
Эксперимент #5, продолжение: Hard&Soft PWM
Цель: найти применение возможностям, выявленными в ходе эксперимента #5
Результат: вроде успешно
Так, как мигание одним светодиодом на псевдо-PWM-е не сильно показательно и полезно, приделаем 6 синих сервомеханизмов к трем 16-битным таймерам ATMega328PB и будем их крутить ногами A0...A5. Напомню, что таймеры #3 и #4 штатно выводят на пины D0, D1, D2, что не всегда удобно.
Заливаем полуиндусский код, подключаем сервы, радуемся (возможно). У меня шести серв под рукой нет, но две штуки я поперетыкал в разные выходы.
Пытаясь объяснить себе возникающие в эксперименте #1 спецэффекты, я экспериментально искал ответы на глупые и странные вопросы относительно режимов PWM #9 "Phase & Frequency Correct" и #11 "Phase Correct", которые, вроде бы и не должны были возникать у человека разумного.
Забегая вперёд, хочу констатировать, что на данный момент режим "Phase Correct" мне кажется более капризным и требующим большей аккуратности при использовании.
Q: До какого значения увеличивается TCNT1 при запуске таймера без предварительного сброса (TCNT1 = BOTTOM)?
A: До величины MAX
Q: Сбрасывает ли счёт установка TCNT1 = BOTTOM после запуска таймера без предварительного сброса?
A: Нет, счёт идёт вверх, до величины MAX.
Q: Каково направление счёта таймера после установки TCNT1 = BOTTOM?
A: Всегда вверх.
Q: Каково направление счёта таймера после установки TCNT1 = TOP?
A: Всегда вниз.
Q: Изменяется ли направление счёта после принудительной установки TCNT1 = N (N != BOTTOM, N != TOP) ?
A: Нет, не изменяется. Счёт идёт в прежнем направлении, начиная с N.
Q: Когда вызывается прерывание TOVF?
A: На следующем такте прескалера. Например, если TOVF = BOTTOM, то в ISR(TIMER1_OVF_vect) TCNT1 будет равен 1.
Q: Когда вызывается прерывание COMPB при движении вниз?
A: На следующем такте прескалера. Например, при OCR1B = 5, ISR(TIMER1_COMPB_vect) TCNT1 будет равен 4.
A: На следующем такте прескалера. Например, при OCR1B = 5, ISR(TIMER1_COMPB_vect) TCNT1 будет равен 6.
A: Да. Заданное состояние выхода восстанавливается при обратном подключении выхода к генератору после его (выхода) отключения и принудительного изменения состояния.
A: Предварительные данные эксперимента – ISR выполняется после переключения выхода генератором.
A: Да. В режиме #9, движение из точки BOTTOM при счёте вверх ведёт к переключению выхода в активное состояние. В режиме #11 движение из точки TOP при счёте вниз приводит к принудительной деактивации выхода.
Ситуация #8, повышение значения TCNT до значения TOP при работе генератора в режиме #11
зачистят, но всё же скажу, помнишь, я как-то говорил тебе об аналогичной задаче методом паразитирования и, что ты не можешь не написать )))
Итак, руководствуясь справочными материалами и результатами эксперимента, можно понять, что за "выброс" следовал после гиперпрыжка TCNT в точку BOTTOM в эксперименте #1
После последовательного помещения в регистр сравнения OCR1B (а фактически - во временный буфер, "виртуальный регистр") и в регистр счётчика TCNT1 значения BOTTOM, произошёл переход в точку "обновления", где из буферного (виртуального) регистра OCR1B содержимое было перенесено в физический. При этом, специальном значении (OCR1B = BOTTOM), выход OC1B должен "удерживаться" (на практике - постоянно устанавливаться при совпадении TCNT1 и OCR1B) микроконтроллером в состоянии LOW (режим #9, неинвертированный).
При изменении направления счёта TCNT1 вверх из точки BOTTOM, выход OC1B был принудительно переведён в состояние HIGH, а "прыжок" привёл к пропуску (блокировке) операции сравнения TCNT1 и OCR1B на один такт, как и описано в документации. Вследствие этого выход OC1B не был переключен и оставался в прежнем состоянии на протяжении целого периода.
При следующем совпадении (compare match) TCNT1 и OCR1B в точке BOTTOM, выход был штатно переведён и начал "удерживаться" в состоянии LOW.
Всё это кратко описано в разделе "Using the Output Compare Unit" даташита: "If the value written to TCNT1 equals the OCR1x value, the compare match will be missed, resulting in incorrect waveform generation. Do not write the TCNT1 equal to TOP in PWM modes with variable TOP values. The compare match for the TOP will be ignored and the counter will continue to 0xFFFF. Similarly, do not write the TCNT1 value equal to BOTTOM when the counter is downcounting."
Теперь же сухие строчки документации можно рассмотреть и на цветных картинках.
Подводя итог, приведу наиболее, на мой взгляд, беспроблемный и быстрый код старта/остановки генерации PMW на выходе OC1B в режине #9. Но, следует помнить, что, несмотря на отключение физического выхода от Waveform Generator, последний продолжает работать и следует выдержать не менее одного периода генерации перед тем, как вновь "подключить генератор" к физическому выходу.
МОЛОДЕЦ!!! Прекрасно изложил!!! Плюсики поставил!!!
неплохой ролик объясняющий это за 2 минуты
Тут тоже ролики есть:
https://hackaday.com/2020/02/06/tool-writes-your-pwm-code-for-you/