123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282 |
- /**
- * Marlin 3D Printer Firmware
- * Copyright (c) 2020 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
- *
- * Based on Sprinter and grbl.
- * Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
- */
- #ifdef __AVR__
-
- #include "../../inc/MarlinConfigPre.h"
-
- #if NEEDS_HARDWARE_PWM // Specific meta-flag for features that mandate PWM
-
- #include "HAL.h"
-
- struct Timer {
- volatile uint8_t* TCCRnQ[3]; // max 3 TCCR registers per timer
- volatile uint16_t* OCRnQ[3]; // max 3 OCR registers per timer
- volatile uint16_t* ICRn; // max 1 ICR register per timer
- uint8_t n; // the timer number [0->5]
- uint8_t q; // the timer output [0->2] (A->C)
- };
-
- /**
- * get_pwm_timer
- * Get the timer information and register of the provided pin.
- * Return a Timer struct containing this information.
- * Used by set_pwm_frequency, set_pwm_duty
- */
- Timer get_pwm_timer(const pin_t pin) {
- uint8_t q = 0;
- switch (digitalPinToTimer(pin)) {
- // Protect reserved timers (TIMER0 & TIMER1)
- #ifdef TCCR0A
- #if !AVR_AT90USB1286_FAMILY
- case TIMER0A:
- #endif
- case TIMER0B:
- #endif
- #ifdef TCCR1A
- case TIMER1A: case TIMER1B:
- #endif
- break;
- #if defined(TCCR2) || defined(TCCR2A)
- #ifdef TCCR2
- case TIMER2: {
- Timer timer = {
- /*TCCRnQ*/ { &TCCR2, nullptr, nullptr },
- /*OCRnQ*/ { (uint16_t*)&OCR2, nullptr, nullptr },
- /*ICRn*/ nullptr,
- /*n, q*/ 2, 0
- };
- }
- #elif defined(TCCR2A)
- #if ENABLED(USE_OCR2A_AS_TOP)
- case TIMER2A: break; // protect TIMER2A
- case TIMER2B: {
- Timer timer = {
- /*TCCRnQ*/ { &TCCR2A, &TCCR2B, nullptr },
- /*OCRnQ*/ { (uint16_t*)&OCR2A, (uint16_t*)&OCR2B, nullptr },
- /*ICRn*/ nullptr,
- /*n, q*/ 2, 1
- };
- return timer;
- }
- #else
- case TIMER2B: ++q;
- case TIMER2A: {
- Timer timer = {
- /*TCCRnQ*/ { &TCCR2A, &TCCR2B, nullptr },
- /*OCRnQ*/ { (uint16_t*)&OCR2A, (uint16_t*)&OCR2B, nullptr },
- /*ICRn*/ nullptr,
- 2, q
- };
- return timer;
- }
- #endif
- #endif
- #endif
- #ifdef OCR3C
- case TIMER3C: ++q;
- case TIMER3B: ++q;
- case TIMER3A: {
- Timer timer = {
- /*TCCRnQ*/ { &TCCR3A, &TCCR3B, &TCCR3C },
- /*OCRnQ*/ { &OCR3A, &OCR3B, &OCR3C },
- /*ICRn*/ &ICR3,
- /*n, q*/ 3, q
- };
- return timer;
- }
- #elif defined(OCR3B)
- case TIMER3B: ++q;
- case TIMER3A: {
- Timer timer = {
- /*TCCRnQ*/ { &TCCR3A, &TCCR3B, nullptr },
- /*OCRnQ*/ { &OCR3A, &OCR3B, nullptr },
- /*ICRn*/ &ICR3,
- /*n, q*/ 3, q
- };
- return timer;
- }
- #endif
- #ifdef TCCR4A
- case TIMER4C: ++q;
- case TIMER4B: ++q;
- case TIMER4A: {
- Timer timer = {
- /*TCCRnQ*/ { &TCCR4A, &TCCR4B, &TCCR4C },
- /*OCRnQ*/ { &OCR4A, &OCR4B, &OCR4C },
- /*ICRn*/ &ICR4,
- /*n, q*/ 4, q
- };
- return timer;
- }
- #endif
- #ifdef TCCR5A
- case TIMER5C: ++q;
- case TIMER5B: ++q;
- case TIMER5A: {
- Timer timer = {
- /*TCCRnQ*/ { &TCCR5A, &TCCR5B, &TCCR5C },
- /*OCRnQ*/ { &OCR5A, &OCR5B, &OCR5C },
- /*ICRn*/ &ICR5,
- /*n, q*/ 5, q
- };
- return timer;
- }
- #endif
- }
- Timer timer = {
- /*TCCRnQ*/ { nullptr, nullptr, nullptr },
- /*OCRnQ*/ { nullptr, nullptr, nullptr },
- /*ICRn*/ nullptr,
- 0, 0
- };
- return timer;
- }
-
- void set_pwm_frequency(const pin_t pin, int f_desired) {
- Timer timer = get_pwm_timer(pin);
- if (timer.n == 0) return; // Don't proceed if protected timer or not recognised
- uint16_t size;
- if (timer.n == 2) size = 255; else size = 65535;
-
- uint16_t res = 255; // resolution (TOP value)
- uint8_t j = 0; // prescaler index
- uint8_t wgm = 1; // waveform generation mode
-
- // Calculating the prescaler and resolution to use to achieve closest frequency
- if (f_desired != 0) {
- int f = (F_CPU) / (2 * 1024 * size) + 1; // Initialize frequency as lowest (non-zero) achievable
- uint16_t prescaler[] = { 0, 1, 8, /*TIMER2 ONLY*/32, 64, /*TIMER2 ONLY*/128, 256, 1024 };
-
- // loop over prescaler values
- LOOP_S_L_N(i, 1, 8) {
- uint16_t res_temp_fast = 255, res_temp_phase_correct = 255;
- if (timer.n == 2) {
- // No resolution calculation for TIMER2 unless enabled USE_OCR2A_AS_TOP
- #if ENABLED(USE_OCR2A_AS_TOP)
- const uint16_t rtf = (F_CPU) / (prescaler[i] * f_desired);
- res_temp_fast = rtf - 1;
- res_temp_phase_correct = rtf / 2;
- #endif
- }
- else {
- // Skip TIMER2 specific prescalers when not TIMER2
- if (i == 3 || i == 5) continue;
- const uint16_t rtf = (F_CPU) / (prescaler[i] * f_desired);
- res_temp_fast = rtf - 1;
- res_temp_phase_correct = rtf / 2;
- }
-
- LIMIT(res_temp_fast, 1u, size);
- LIMIT(res_temp_phase_correct, 1u, size);
- // Calculate frequencies of test prescaler and resolution values
- const int f_temp_fast = (F_CPU) / (prescaler[i] * (1 + res_temp_fast)),
- f_temp_phase_correct = (F_CPU) / (2 * prescaler[i] * res_temp_phase_correct),
- f_diff = ABS(f - f_desired),
- f_fast_diff = ABS(f_temp_fast - f_desired),
- f_phase_diff = ABS(f_temp_phase_correct - f_desired);
-
- // If FAST values are closest to desired f
- if (f_fast_diff < f_diff && f_fast_diff <= f_phase_diff) {
- // Remember this combination
- f = f_temp_fast;
- res = res_temp_fast;
- j = i;
- // Set the Wave Generation Mode to FAST PWM
- if (timer.n == 2) {
- wgm = (
- #if ENABLED(USE_OCR2A_AS_TOP)
- WGM2_FAST_PWM_OCR2A
- #else
- WGM2_FAST_PWM
- #endif
- );
- }
- else wgm = WGM_FAST_PWM_ICRn;
- }
- // If PHASE CORRECT values are closes to desired f
- else if (f_phase_diff < f_diff) {
- f = f_temp_phase_correct;
- res = res_temp_phase_correct;
- j = i;
- // Set the Wave Generation Mode to PWM PHASE CORRECT
- if (timer.n == 2) {
- wgm = (
- #if ENABLED(USE_OCR2A_AS_TOP)
- WGM2_PWM_PC_OCR2A
- #else
- WGM2_PWM_PC
- #endif
- );
- }
- else wgm = WGM_PWM_PC_ICRn;
- }
- }
- }
- _SET_WGMnQ(timer.TCCRnQ, wgm);
- _SET_CSn(timer.TCCRnQ, j);
-
- if (timer.n == 2) {
- #if ENABLED(USE_OCR2A_AS_TOP)
- _SET_OCRnQ(timer.OCRnQ, 0, res); // Set OCR2A value (TOP) = res
- #endif
- }
- else
- _SET_ICRn(timer.ICRn, res); // Set ICRn value (TOP) = res
- }
-
- void set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t v_size/*=255*/, const bool invert/*=false*/) {
- // If v is 0 or v_size (max), digitalWrite to LOW or HIGH.
- // Note that digitalWrite also disables pwm output for us (sets COM bit to 0)
- if (v == 0)
- digitalWrite(pin, invert);
- else if (v == v_size)
- digitalWrite(pin, !invert);
- else {
- Timer timer = get_pwm_timer(pin);
- if (timer.n == 0) return; // Don't proceed if protected timer or not recognised
- // Set compare output mode to CLEAR -> SET or SET -> CLEAR (if inverted)
- _SET_COMnQ(timer.TCCRnQ, (timer.q
- #ifdef TCCR2
- + (timer.q == 2) // COM20 is on bit 4 of TCCR2, thus requires q + 1 in the macro
- #endif
- ), COM_CLEAR_SET + invert
- );
-
- uint16_t top;
- if (timer.n == 2) { // if TIMER2
- top = (
- #if ENABLED(USE_OCR2A_AS_TOP)
- *timer.OCRnQ[0] // top = OCR2A
- #else
- 255 // top = 0xFF (max)
- #endif
- );
- }
- else
- top = *timer.ICRn; // top = ICRn
-
- _SET_OCRnQ(timer.OCRnQ, timer.q, v * float(top) / float(v_size)); // Scale 8/16-bit v to top value
- }
- }
-
- #endif // NEEDS_HARDWARE_PWM
- #endif // __AVR__
|