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 на выходе.
Я её проверил. В коде:
#include <util/atomic.h> // defined => Mode 11: Phase correct, update of OCR1x at = TOP // undefined => Mode 9: Phase and frequency correct, update of OCR1x at = BOTTOM #define PHASE_CORRECT #define D10_Low PORTB &=B11111011 #define D10_High PORTB|=B00000100 #define D13_High PORTB |= B00100000 #define D13_Low PORTB &= B11011111 #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) uint16_t timerTop = 600, timerDuty = timerTop/4; inline void timerStart() { sbi(TCCR1A, COM1B1); ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { OCR1B = timerDuty; // Jump to OCR1B update point #if defined(PHASE_CORRECT) TCNT1 = OCR1A; #else TCNT1 = 0x00; #endif } D13_High; } inline void timerStop() { cbi(TCCR1A, COM1B1); D10_Low; ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { OCR1B = 0x00; // Jump to OCR1B update point #if defined(PHASE_CORRECT) TCNT1 = OCR1A; #else // Update works as unexpected - we have full HIGH period after jump on update point // TCNT1 = 0x00; #endif } D13_Low; } void setup() { Serial.begin(115200); pinMode(13, OUTPUT); pinMode(10, OUTPUT); TCCR1A = TCCR1B = TCNT1 = 0x00; OCR1A = timerTop; OCR1B = timerDuty; // Channel A, pin 9, off // Channel B, pin 10, clear OC1B on compare match, set OC1B at BOTTOM (non-inverting mode) TCCR1A |= (0 << COM1A1) | (0 << COM1A0) | (1 << COM1B1) | (0 << COM1B0); #if defined(PHASE_CORRECT) // Mode 11: Phase correct, update of OCR1B at on TOP TCCR1A |= (1 << WGM11) | (1 << WGM10); #else // Mode 9: Phase and frequency correct, update of OCR1B on BOTTOM TCCR1A |= (0 << WGM11) | (1 << WGM10); #endif TCCR1B |= (1 << WGM13) | (1 << CS11) | (1 << CS10); } void loop() { static uint32_t prevTimerToggle; static uint8_t stateTimerToggle; uint32_t nowTime = millis(); if (nowTime - prevTimerToggle > 30UL) { prevTimerToggle = nowTime; stateTimerToggle ? timerStart() : timerStop(); stateTimerToggle = !stateTimerToggle; } }На картинках с осциллографа можно видеть, что в режиме "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:
#define D13_High PORTB |= B00100000 #define D10_High PORTB |= B00000100 #define D13_Low PORTB &= B11011111 #define D10_Low PORTB &= B11111011 void setup() { pinMode(13, OUTPUT); pinMode(10, OUTPUT); TCCR1A = TCCR1B = 0x00; // Channel A, pin 9, off // Channel B, pin 10, clear OC1B on compare match, set OC1B at BOTTOM (non-inverting mode) TCCR1A |= (0 << COM1A1) | (0 << COM1A0) | (1 << COM1B1) | (0 << COM1B0); // Mode 11: Phase correct, update of OCR1B at on TOP TCCR1A |= (1 << WGM11) | (1 << WGM10); TCCR1B |= (1 << WGM13); OCR1A = 7810; OCR1B = OCR1A / 4; TCNT1 = OCR1A; TCCR1B |= (1 << CS12) | (1 << CS10); } void loop() { D10_High; D13_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? Компилируем, заливаем...
#include <util/atomic.h> const uint16_t timerTop = 127; const uint16_t stepDuration = 50; const uint16_t stopDuration = 5000; // defined => Mode 14: Fast PWM, update of ICR1 at on BOTTOM // undefined => Mode 8: Phase and frequency correct, update of OCR1x on BOTTOM #define FAST_PWM void setup() { Serial.begin(115200); pinMode(9, OUTPUT); pinMode(10, OUTPUT); TCCR1A = TCCR1B = 0x00; // Channel A, pin 9, clear OC1B on compare match, set OC1B at BOTTOM (non-inverting mode) // Channel B, pin 10, clear OC1B on compare match, set OC1B at BOTTOM (non-inverting mode) TCCR1A |= (1 << COM1A1) | (0 << COM1A0) | (1 << COM1B1) | (0 << COM1B0); #if defined(FAST_PWM) // Mode 14: Fast PWM, update of ICR1 at on BOTTOM TCCR1A |= (1 << WGM11); TCCR1B |= (1 << WGM13) | (1 << WGM12); #else // Mode 8: Phase and frequency correct, update of OCR1x on BOTTOM TCCR1B |= (1 << WGM13); #endif ICR1 = timerTop; OCR1A = timerTop / 4; OCR1B = 0x00; TCNT1 = 0x00; TCCR1B |= (1 << CS11) | (1 << CS10); } void loop() { for (int32_t duty = 1; timerTop >= duty ; duty++) { Serial.println(duty); ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { OCR1B = (uint16_t) duty; } delay(stepDuration); } delay(stopDuration); for (int32_t duty = timerTop; 0x00 <= duty; duty--) { Serial.println(duty); ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { OCR1B = (uint16_t) duty; } delay(stepDuration); } delay(stopDuration); }Смотрим на осциллографе картинки:
Вон оно чего - на "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-а код, рассмотрим эффект синхронизации под микроскопом:
#include <util/atomic.h> // Sync all timers //#define SYNC_TIMERS // Use big prescaler - to see no different with syncing //#define BIG_PRESCALER void setup() { #if defined(SYNC_TIMERS) GTCCR = (1 << TSM) | (1 << PSRASY) | (1 << PSRSYNC); #endif // Timer #1 const uint16_t timerOneTop = 255; pinMode(9, OUTPUT); pinMode(10, OUTPUT); TCCR1A = TCCR1B = 0x00; // Channel A, pin 9, clear on compare match, set at BOTTOM (non-inverting mode). // Channel B, pin 10, clear on compare match, set at BOTTOM (non-inverting mode). TCCR1A |= (1 << COM1A1) | (0 << COM1A0) | (1 << COM1B1) | (0 << COM1B0); // Mode 14: Fast PWM, TOP = ICR1 TCCR1A |= (1 << WGM11); TCCR1B |= (1 << WGM13) | (1 << WGM12); ICR1 = timerOneTop; // Channel A duty 50% OCR1B = timerOneTop / 2; // Channel A duty 25% OCR1A = timerOneTop / 4; TCNT1 = 0x00; #if defined(BIG_PRESCALER) // Prescaler /256 TCCR1B |= (1 << CS12); #else // Prescaler /8 TCCR1B |= (1 << CS11); #endif // Timer #2 pinMode(3, OUTPUT); pinMode(11, OUTPUT); TCCR2A = TCCR2B = 0x00; // Channel A, pin 3, clear on compare match, set at BOTTOM (non-inverting mode). // Channel B, pin 11, clear on compare match, set at BOTTOM (non-inverting mode). TCCR2A |= (1 << COM2A1) | (0 << COM2A0) | (1 << COM2B1) | (0 << COM2B0); // Mode 3: Fast PWM, TOP = 0xFF TCCR2A |= (1 << WGM21) | (1 << WGM20); // Channel A duty 40% OCR2A = 0xFF / 5 * 2; // Channel B duty 75% OCR2B = 0xFF / 4 * 3; TCNT2 = 0x00; #if defined(BIG_PRESCALER) // Prescaler /256 TCCR2B |= (1 << CS22) | (1 << CS21); #else // Prescaler /8 TCCR2B |= (1 << CS21); #endif #if defined(SYNC_TIMERS) GTCCR = 0; #endif } void loop() {}Разница между несинхронно стартовавшими таймерами видна на относительно больших частотах
При увеличении заметно, что эффект от синхронизации для любительских целей более чем приемлем - наносекудная точность.
На низких частотах, по-моему мнению, синхронизация - перфекционизм чистой воды.
Эксперимент #5: Hard&Soft PWM
Цель: "перенести" PWM на выходы, не связанные с таймерами.
Результат: успех.
На Arduino Pro Micro загрузчик плавно моргает набортным светодиодом. Получится ли сделать и на Mini/Nano/Uno сделать красиво? В принципе - да.
Просто будем паразитировать на каком-нибудь таймере и устроим дискотеку на D13 или любом другом пине, не поддерживающем PWM на аппаратном уровне.
#include <util/atomic.h> const uint16_t timerTop = 2000; // Freq = 1KHz on /8 prescaler const uint32_t flipFlopDelay = 2000; const uint32_t stepDelay = 1; #define D13_Low PORTB &= B11011111 #define D13_High PORTB |= B00100000 // LED pin toggles to inverted state of Timer #1 OC1B pin //#define D13_MODE_INVERTED // Just show LED effect on low frequency (prescaler /1024) //#define LOW_TIMER_SPEED ISR(TIMER1_OVF_vect) { // Use special stuff to avoid spikes on duty 0% #if defined(D13_MODE_INVERTED) (0x00 == OCR1B) ? D13_High : D13_Low; #else (0x00 == OCR1B) ? D13_Low : D13_High; #endif } ISR(TIMER1_COMPB_vect) { // Use special stuff to avoid spikes on duty 100% #if defined(D13_MODE_INVERTED) (timerTop == OCR1B) ? D13_Low : D13_High; #else (timerTop == OCR1B) ? D13_High : D13_Low; #endif } void setup() { uint16_t timerDuty = 0x00; int8_t dutyStep = 1; pinMode(13, OUTPUT); // Timer #1 pinMode(10, OUTPUT); TCCR1A = TCCR1B = 0x00; // Channel A, pin 9, off TCCR1A |= (0 << COM1A1) | (0 << COM1A0); // Channel B, pin 10 // Clear OC1A/OC1B on Compare Match, set OC1A/OC1B at BOTTOM (non-inverting mode) TCCR1A |= (1 << COM1B1) | (0 << COM1B0); // Mode #15: Fast PWM, TOP = OCR1A TCCR1A |= (1 << WGM11) | (1 << WGM10); TCCR1B |= (1 << WGM13) | (1 << WGM12); OCR1A = timerTop; OCR1B = 0x00; #if defined(LOW_TIMER_SPEED) TCCR1B |= (1 << CS12) | (1 << CS10); #else TCCR1B |= (1 << CS11) | (0 << CS10); #endif TIMSK1 = (1 << TOIE1) | (1 << OCIE1B); // Change duty bothways: 0% to 100% and 100% to 0% while (true) { timerDuty += dutyStep; ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { OCR1B = timerDuty; } delay(stepDelay); if (timerTop == timerDuty || 0x00 == timerDuty) { delay(flipFlopDelay); dutyStep = -dutyStep; } } } void loop() {}Сдвиг относительно основного сигнала, естественно, имеется. Стабильность такого ШИМ-а, разумеется, зависит от того, насколько долго будут блокироваться прерывания МК.
Эксперимент #5, продолжение: Hard&Soft PWM
Цель: найти применение возможностям, выявленными в ходе эксперимента #5
Результат: вроде успешно
Так, как мигание одним светодиодом на псевдо-PWM-е не сильно показательно и полезно, приделаем 6 синих сервомеханизмов к трем 16-битным таймерам ATMega328PB и будем их крутить ногами A0...A5. Напомню, что таймеры #3 и #4 штатно выводят на пины D0, D1, D2, что не всегда удобно.
Заливаем полуиндусский код, подключаем сервы, радуемся (возможно). У меня шести серв под рукой нет, но две штуки я поперетыкал в разные выходы.
#include <util/atomic.h> // Servo Set #1, Timer #1 on A0, A1 // Servo Set #2, Timer #3 on A2, A3 // Servo Set #3, Timer #4 on A4, A5 /** user area ***/ // Can be used with both 16-bit & 8-bit timers const uint32_t prescaler = 256; const uint16_t timerTop = F_CPU / prescaler * 2 / 100; // Fast PWM: timerTop = 1250 for freq = 50Hz on /256 prescaler const uint16_t workAngleMin = 0; const uint16_t workAngleMax = 180; const uint32_t flipFlopDelay = 2000; const uint32_t stepDelay = 10; /** system area ***/ const uint16_t workAngleMinPulse = 544; const uint16_t workAngleMaxPulse = 2400; const uint16_t dutyForAngleMin = (F_CPU / prescaler * workAngleMinPulse) / 1000000UL; const uint16_t dutyForAngleMax = (F_CPU / prescaler * workAngleMaxPulse) / 1000000UL; #define D13_Low PORTB &= B11011111 #define D13_High PORTB |= B00100000 #define A0_High PORTC |=B00000001 #define A1_High PORTC |=B00000010 #define A2_High PORTC |=B00000100 #define A3_High PORTC |=B00001000 #define A4_High PORTC |=B00010000 #define A5_High PORTC |=B00100000 #define A0_Low PORTC &= B11111110 #define A1_Low PORTC &= B11111101 #define A2_Low PORTC &= B11111011 #define A3_Low PORTC &= B11110111 #define A4_Low PORTC &= B11101111 #define A5_Low PORTC &= B11011111 // Timer #1 ISRs. ISR(TIMER1_OVF_vect) { (0x00 == OCR1A) ? A0_Low : A0_High ; (0x00 == OCR1B) ? A1_Low : A1_High; } ISR(TIMER1_COMPA_vect) { (timerTop == OCR1A) ? A0_High : A0_Low; } ISR(TIMER1_COMPB_vect) { (timerTop == OCR1B) ? A1_High : A1_Low; } // Timer #3 ISRs ISR(TIMER3_OVF_vect) { (0x00 == OCR3A) ? A2_Low : A2_High ; (0x00 == OCR3B) ? A3_Low : A3_High; } ISR(TIMER3_COMPA_vect) { (timerTop == OCR3A) ? A2_High : A2_Low; } ISR(TIMER3_COMPB_vect) { (timerTop == OCR3B) ? A3_High : A3_Low; } // Timer #4 ISRs ISR(TIMER4_OVF_vect) { (0x00 == OCR4A) ? A4_Low : A4_High ; (0x00 == OCR4B) ? A5_Low : A5_High; } ISR(TIMER4_COMPA_vect) { (timerTop == OCR4A) ? A4_High : A4_Low; } ISR(TIMER4_COMPB_vect) { (timerTop == OCR4B) ? A5_High : A5_Low; } void setup() { int16_t workAngle = 0x00; int8_t angleStep = 7; pinMode(13, OUTPUT); for (uint8_t i = A0; A5 >= i; i++) { pinMode(i, OUTPUT); } TCCR1A = TCCR1B = TCCR3A = TCCR3B = TCCR4A = TCCR4B = 0x00; ICR1 = ICR3 = ICR4 = timerTop; OCR1A = OCR3A = OCR4A = workAngleMax; OCR1B = OCR3B = OCR4B = workAngleMin; TIMSK1 = TIMSK3 = TIMSK4 = (1 << TOIE1) | (1 << OCIE1A) | (1 << OCIE1B); // TOIE1 eq TOIE3 eq TOIE4 and so on // Timer #1 // Channel A, B turned off // Mode #14: Fast PWM, TOP = ICR1 TCCR1A |= (1 << WGM11) | (0 << WGM10); TCCR1B |= (1 << WGM13) | (1 << WGM12); // /256 TCCR1B |= (1 << CS12); // Timer #3 // Channel A, B turned off // Mode #14: Fast PWM, TOP = ICR3 TCCR3A |= (1 << WGM31) | (0 << WGM30); TCCR3B |= (1 << WGM33) | (1 << WGM32); // /256 TCCR3B |= (1 << CS32); // Timer #4 // Channel A, B turned off // Mode #14: Fast PWM, TOP = ICR3 TCCR4A |= (1 << WGM41) | (0 << WGM40); TCCR3B |= (1 << WGM43) | (1 << WGM42); // /256 TCCR4B |= (1 << CS42); // Change servo's angle bothways: min..max degree and max..min degree while (true) { workAngle += angleStep; workAngle = constrain(workAngle, (int16_t)workAngleMin, (int16_t)workAngleMax); // Servo set #1 on Timer #1 uint8_t workAngleSetN1 = workAngle; // 0..180 uint16_t timerDutyServoOne = map(workAngleSetN1, workAngleMin, workAngleMax, dutyForAngleMin, dutyForAngleMax); // 180..0 uint16_t timerDutyServoTwo = map(workAngleMax - workAngleSetN1, workAngleMin, workAngleMax, dutyForAngleMin, dutyForAngleMax); ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { OCR1A = timerDutyServoOne; OCR1B = timerDutyServoTwo; } // Servo set #2 on Timer #3 uint8_t workAngleSetN2 = workAngle / 2; // 0..90 uint16_t timerDutyServoThree = map(workAngleSetN2, workAngleMin, workAngleMax, dutyForAngleMin, dutyForAngleMax); // 180..90 uint16_t timerDutyServoFour = map(workAngleMax - workAngleSetN2, workAngleMin, workAngleMax, dutyForAngleMin, dutyForAngleMax); ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { OCR3A = timerDutyServoThree; OCR3B = timerDutyServoFour; } // Servo set #3 on Timer #4 uint8_t workAngleSetN3 = workAngle / 4 * 3; // 0..135 uint16_t timerDutyServoFive = map(workAngleSetN3, workAngleMin, workAngleMax, dutyForAngleMin, dutyForAngleMax); // 45..180 uint16_t timerDutyServoSix = map(workAngleMax - workAngleSetN3, workAngleMin, workAngleMax, dutyForAngleMin, dutyForAngleMax); ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { OCR4A = timerDutyServoFive; OCR4B = timerDutyServoSix; } delay(stepDelay); if (workAngleMin == workAngle || workAngleMax == workAngle) { delay(flipFlopDelay); angleStep *= -1; } } } void loop() {}Пытаясь объяснить себе возникающие в эксперименте #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, последний продолжает работать и следует выдержать не менее одного периода генерации перед тем, как вновь "подключить генератор" к физическому выходу.
#include <util/atomic.h> #define D10_Low PORTB &=B11111011 #define D10_High PORTB|=B00000100 #define D13_High PORTB |= B00100000 #define D13_Low PORTB &= B11011111 #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit)) const uint16_t timerTop = 600, timerDuty = timerTop / 4; inline void timerStart() { sbi(TCCR1A, COM1B1); ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { OCR1B = timerDuty; TCNT1 = 0x00; } } inline void timerStop() { cbi(TCCR1A, COM1B1); D10_Low; ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { OCR1B = 0x00; } } void setup() { pinMode(13, OUTPUT); pinMode(10, OUTPUT); TCCR1A = TCCR1B = TCNT1 = 0x00; OCR1A = timerTop; OCR1B = timerDuty; // Channel B, pin 10, clear OC1B on compare match, set OC1B at BOTTOM (non-inverting mode) TCCR1A |= (1 << COM1B1); // Mode 9: Phase and frequency correct, update of OCR1B on BOTTOM TCCR1A |= (1 << WGM10); TCCR1B |= (1 << WGM13) | (1 << CS11) | (1 << CS10); } void loop() { delay(110); D13_Low; timerStop(); delay(1); D13_High; timerStart(); }МОЛОДЕЦ!!! Прекрасно изложил!!! Плюсики поставил!!!
неплохой ролик объясняющий это за 2 минуты
Тут тоже ролики есть:
https://hackaday.com/2020/02/06/tool-writes-your-pwm-code-for-you/