Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix AVR set_pwm_duty unset default frequency #23463

Merged
merged 33 commits into from
Jan 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
15b243c
Fix AVR set_pwm_duty unset default frequency
descipher Jan 6, 2022
e363120
Set MOTOR_CURRENT_PWM_FREQUENCY default as 31400
descipher Jan 6, 2022
0ab6dcb
Merge 'bugfix-2.0.x' into bf2_weird_pwm_PR_23463
thinkyhead Jan 7, 2022
df4f122
defer to pinsformat.js
thinkyhead Jan 7, 2022
359415d
delete red highlighted trailing spaces
thinkyhead Jan 7, 2022
fe1f75d
clarify
thinkyhead Jan 7, 2022
eaef837
trust pins to retain freq after init ?
thinkyhead Jan 7, 2022
8cd296f
track init as a debug option
thinkyhead Jan 7, 2022
ffd9750
arg tweak, keep wrapper ?
thinkyhead Jan 7, 2022
dbd37f2
set_pwm_frequency with all HAL_CAN_SET_PWM_FREQ
thinkyhead Jan 7, 2022
8e10265
Add HAS_LCD_BRIGHTNESS Needs PWM condition check
descipher Jan 7, 2022
1953124
Corrected include and frequency init tracking
descipher Jan 7, 2022
ea74cdc
Cleanup condition check state
descipher Jan 8, 2022
8b334fd
Update fast_pwm.cpp
thinkyhead Jan 9, 2022
741078e
would other platforms ever use it?
thinkyhead Jan 9, 2022
e18008f
tweak wgm
thinkyhead Jan 9, 2022
aaa15d6
Init AVR timers 2,3,4,5 in HAL
descipher Jan 10, 2022
5b5b23b
Merge branch 'ultimate.pwm.fix' of https://github.com/descipher/Marli…
descipher Jan 10, 2022
4b01add
Add AT90 pins
descipher Jan 10, 2022
c1e1b75
kHz => KHz
thinkyhead Jan 10, 2022
e177429
unsigned PWM frequency
thinkyhead Jan 10, 2022
7ffc23a
32 bits may be better
thinkyhead Jan 10, 2022
092666e
fix formatting
thinkyhead Jan 10, 2022
1755e78
suppress warning
thinkyhead Jan 10, 2022
14795aa
Add more pins
descipher Jan 10, 2022
f95f606
unsigned enums
thinkyhead Jan 11, 2022
54501eb
move macros, tweak get_pwm_timer
thinkyhead Jan 11, 2022
78cb32f
tweak var names and maths
thinkyhead Jan 11, 2022
5aeac05
Merge branch 'bugfix-2.0.x' into pr/23463
thinkyhead Jan 11, 2022
90ba936
Timer check default return as non-PWM and not protected.
descipher Jan 11, 2022
0871ea3
return timer structs more directly
thinkyhead Jan 11, 2022
0f5fbe7
fallback to WGM2_PWM_PC
thinkyhead Jan 11, 2022
6a5eaec
style tweaks
thinkyhead Jan 11, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Marlin/src/HAL/AVR/HAL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ void HAL_init() {
#if HAS_SERVO_3
INIT_SERVO(3);
#endif

init_pwm_timers(); // Init user timers to default frequency - 1000HZ
}

void HAL_reboot() {
Expand Down
7 changes: 7 additions & 0 deletions Marlin/src/HAL/AVR/HAL.h
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ inline void HAL_adc_init() {
#define strtof strtod

#define HAL_CAN_SET_PWM_FREQ // This HAL supports PWM Frequency adjustment
#define PWM_FREQUENCY 1000 // Default PWM frequency when set_pwm_duty() is called without set_pwm_frequency()

/**
* set_pwm_frequency
Expand All @@ -226,3 +227,9 @@ void set_pwm_frequency(const pin_t pin, const uint16_t f_desired);
* Optionally allows changing the maximum size of the provided value to enable finer PWM duty control [default = 255]
*/
void set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t v_size=255, const bool invert=false);

/*
* init_pwm_timers
* sets the default frequency for timers 2-5 to 1000HZ
*/
void init_pwm_timers();
261 changes: 96 additions & 165 deletions Marlin/src/HAL/AVR/fast_pwm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,16 @@
*/
#ifdef __AVR__

#include "../../inc/MarlinConfigPre.h"
#include "HAL.h"

#if NEEDS_HARDWARE_PWM // Specific meta-flag for features that mandate PWM
#include "../../inc/MarlinConfig.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)
bool isPWM; // True if pin is a "hardware timer"
bool isProtected; // True if timer is protected
};

// Macros for the Timer structure
Expand All @@ -53,16 +52,13 @@ struct Timer {
#define _SET_ICRn(ICRn, V) (*(ICRn) = int(V) & 0xFFFF)

/**
* 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
* Return a Timer struct describing a pin's timer.
*/
Timer get_pwm_timer(const pin_t pin) {

uint8_t q = 0;

switch (digitalPinToTimer(pin)) {
// Protect reserved timers (TIMER0 & TIMER1)
#ifdef TCCR0A
IF_DISABLED(AVR_AT90USB1286_FAMILY, case TIMER0A:)
case TIMER0B:
Expand All @@ -71,212 +67,147 @@ Timer get_pwm_timer(const pin_t pin) {
case TIMER1A: case TIMER1B:
#endif

break;
break; // Protect reserved timers (TIMER0 & TIMER1)

#if HAS_TCCR2
case TIMER2: {
Timer timer = {
{ &TCCR2, nullptr, nullptr },
{ (uint16_t*)&OCR2, nullptr, nullptr },
nullptr,
2, 0
};
return timer;
}
case TIMER2:
return Timer({ { &TCCR2, nullptr, nullptr }, { (uint16_t*)&OCR2, nullptr, nullptr }, nullptr, 2, 0, true, false });
#elif ENABLED(USE_OCR2A_AS_TOP)
case TIMER2A: break; // protect TIMER2A since its OCR is used by TIMER2B
case TIMER2B:
return Timer({ { &TCCR2A, &TCCR2B, nullptr }, { (uint16_t*)&OCR2A, (uint16_t*)&OCR2B, nullptr }, nullptr, 2, 1, true, false });
#elif defined(TCCR2A)
#if ENABLED(USE_OCR2A_AS_TOP)
case TIMER2A: break; // protect TIMER2A
case TIMER2B: {
Timer timer = {
{ &TCCR2A, &TCCR2B, nullptr },
{ (uint16_t*)&OCR2A, (uint16_t*)&OCR2B, nullptr },
nullptr,
2, 1
};
return timer;
}
#else
case TIMER2B: ++q;
case TIMER2A: {
Timer timer = {
{ &TCCR2A, &TCCR2B, nullptr },
{ (uint16_t*)&OCR2A, (uint16_t*)&OCR2B, nullptr },
nullptr,
2, q
};
return timer;
}
#endif
case TIMER2B: ++q; case TIMER2A:
return Timer({ { &TCCR2A, &TCCR2B, nullptr }, { (uint16_t*)&OCR2A, (uint16_t*)&OCR2B, nullptr }, nullptr, 2, q, true, false });
#endif

#ifdef OCR3C
case TIMER3C: ++q;
case TIMER3B: ++q;
case TIMER3A: {
Timer timer = {
{ &TCCR3A, &TCCR3B, &TCCR3C },
{ &OCR3A, &OCR3B, &OCR3C },
&ICR3,
3, q
};
return timer;
}
case TIMER3C: ++q; case TIMER3B: ++q; case TIMER3A:
return Timer({ { &TCCR3A, &TCCR3B, &TCCR3C }, { &OCR3A, &OCR3B, &OCR3C }, &ICR3, 3, q, true, false });
#elif defined(OCR3B)
case TIMER3B: ++q;
case TIMER3A: {
Timer timer = {
{ &TCCR3A, &TCCR3B, nullptr },
{ &OCR3A, &OCR3B, nullptr },
&ICR3,
3, q
};
return timer;
}
case TIMER3B: ++q; case TIMER3A:
return Timer({ { &TCCR3A, &TCCR3B, nullptr }, { &OCR3A, &OCR3B, nullptr }, &ICR3, 3, q, true, false });
#endif

#ifdef TCCR4A
case TIMER4C: ++q;
case TIMER4B: ++q;
case TIMER4A: {
Timer timer = {
{ &TCCR4A, &TCCR4B, &TCCR4C },
{ &OCR4A, &OCR4B, &OCR4C },
&ICR4,
4, q
};
return timer;
}
case TIMER4C: ++q; case TIMER4B: ++q; case TIMER4A:
return Timer({ { &TCCR4A, &TCCR4B, &TCCR4C }, { &OCR4A, &OCR4B, &OCR4C }, &ICR4, 4, q, true, false });
#endif

#ifdef TCCR5A
case TIMER5C: ++q;
case TIMER5B: ++q;
case TIMER5A: {
Timer timer = {
{ &TCCR5A, &TCCR5B, &TCCR5C },
{ &OCR5A, &OCR5B, &OCR5C },
&ICR5,
5, q
};
return timer;
}
case TIMER5C: ++q; case TIMER5B: ++q; case TIMER5A:
return Timer({ { &TCCR5A, &TCCR5B, &TCCR5C }, { &OCR5A, &OCR5B, &OCR5C }, &ICR5, 5, q, true, false });
#endif
}

Timer timer = {
{ nullptr, nullptr, nullptr },
{ nullptr, nullptr, nullptr },
nullptr,
0, 0
};
return timer;
return Timer();
}

void set_pwm_frequency(const pin_t pin, const uint16_t f_desired) {
Timer timer = get_pwm_timer(pin);
if (timer.n == 0) return; // Don't proceed if protected timer or not recognized
uint16_t size;
if (timer.n == 2) size = 255; else size = 65535;
if (timer.isProtected || !timer.isPWM) return; // Don't proceed if protected timer or not recognized

const bool is_timer2 = timer.n == 2;
const uint16_t maxtop = is_timer2 ? 0xFF : 0xFFFF;

uint16_t res = 255; // resolution (TOP value)
uint8_t j = 0; // prescaler index
uint8_t wgm = 1; // waveform generation mode
uint16_t res = 0xFF; // resolution (TOP value)
uint8_t j = CS_NONE; // prescaler index
uint8_t wgm = WGM_PWM_PC_8; // 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;
constexpr uint16_t prescaler[] = { 1, 8, (32), 64, (128), 256, 1024 }; // (*) are Timer 2 only
uint16_t f = (F_CPU) / (2 * 1024 * maxtop) + 1; // Start with the lowest non-zero frequency achievable (1 or 31)

LOOP_L_N(i, COUNT(prescaler)) { // Loop through all prescaler values
const uint16_t p = prescaler[i];
uint16_t res_fast_temp, res_pc_temp;
if (is_timer2) {
#if ENABLED(USE_OCR2A_AS_TOP) // No resolution calculation for TIMER2 unless enabled USE_OCR2A_AS_TOP
const uint16_t rft = (F_CPU) / (p * f_desired);
res_fast_temp = rft - 1;
res_pc_temp = rft / 2;
#else
res_fast_temp = res_pc_temp = maxtop;
#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;
if (p == 32 || p == 128) continue; // Skip TIMER2 specific prescalers when not TIMER2
const uint16_t rft = (F_CPU) / (p * f_desired);
res_fast_temp = rft - 1;
res_pc_temp = rft / 2;
}

LIMIT(res_temp_fast, 1U, size);
LIMIT(res_temp_phase_correct, 1U, size);
LIMIT(res_fast_temp, 1U, maxtop);
LIMIT(res_pc_temp, 1U, maxtop);

// 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);
const uint32_t f_diff = _MAX(f, f_desired) - _MIN(f, f_desired),
f_fast_temp = (F_CPU) / (p * (1 + res_fast_temp)),
f_fast_diff = _MAX(f_fast_temp, f_desired) - _MIN(f_fast_temp, f_desired),
f_pc_temp = (F_CPU) / (2 * p * res_pc_temp),
f_pc_diff = _MAX(f_pc_temp, f_desired) - _MIN(f_pc_temp, 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;
if (f_fast_diff < f_diff && f_fast_diff <= f_pc_diff) { // FAST values are closest to desired f
// Set the Wave Generation Mode to FAST PWM
if (timer.n == 2)
wgm = TERN(USE_OCR2A_AS_TOP, WGM2_FAST_PWM_OCR2A, WGM2_FAST_PWM);
else
wgm = WGM_FAST_PWM_ICRn;
wgm = is_timer2 ? uint8_t(TERN(USE_OCR2A_AS_TOP, WGM2_FAST_PWM_OCR2A, WGM2_FAST_PWM)) : uint8_t(WGM_FAST_PWM_ICRn);
// Remember this combination
f = f_fast_temp; res = res_fast_temp; j = i + 1;
}
// 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;
else if (f_pc_diff < f_diff) { // PHASE CORRECT values are closes to desired f
// Set the Wave Generation Mode to PWM PHASE CORRECT
if (timer.n == 2)
wgm = TERN(USE_OCR2A_AS_TOP, WGM2_PWM_PC_OCR2A, WGM2_FAST_PWM);
else
wgm = WGM_PWM_PC_ICRn;
wgm = is_timer2 ? uint8_t(TERN(USE_OCR2A_AS_TOP, WGM2_PWM_PC_OCR2A, WGM2_PWM_PC)) : uint8_t(WGM_PWM_PC_ICRn);
f = f_pc_temp; res = res_pc_temp; j = i + 1;
}
}
}

_SET_WGMnQ(timer.TCCRnQ, wgm);
_SET_CSn(timer.TCCRnQ, j);

if (timer.n == 2) {
TERN_(USE_OCR2A_AS_TOP, _SET_OCRnQ(timer.OCRnQ, 0, res)); // Set OCR2A value (TOP) = res
if (is_timer2) {
TERN_(USE_OCR2A_AS_TOP, _SET_OCRnQ(timer.OCRnQ, 0, res)); // Set OCR2A value (TOP) = res
}
else
_SET_ICRn(timer.ICRn, res); // Set ICRn value (TOP) = res
_SET_ICRn(timer.ICRn, res); // Set ICRn value (TOP) = res
}

#endif // NEEDS_HARDWARE_PWM

void set_pwm_duty(const pin_t pin, const uint16_t v, const uint16_t v_size/*=255*/, const bool invert/*=false*/) {
#if NEEDS_HARDWARE_PWM

// 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 recognized
// Set compare output mode to CLEAR -> SET or SET -> CLEAR (if inverted)
_SET_COMnQ(timer.TCCRnQ, timer.q TERN_(HAS_TCCR2, + (timer.q == 2)), COM_CLEAR_SET + invert); // COM20 is on bit 4 of TCCR2, so +1 for q==2
// 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.isProtected) return; // Leave protected timer unchanged
if (timer.isPWM) {
_SET_COMnQ(timer.TCCRnQ, SUM_TERN(HAS_TCCR2, timer.q, timer.q == 2), COM_CLEAR_SET + invert); // COM20 is on bit 4 of TCCR2, so +1 for q==2
const uint16_t top = timer.n == 2 ? TERN(USE_OCR2A_AS_TOP, *timer.OCRnQ[0], 255) : *timer.ICRn;
_SET_OCRnQ(timer.OCRnQ, timer.q, uint16_t(uint32_t(v) * top / v_size)); // Scale 8/16-bit v to top value
}
else
digitalWrite(pin, v < 128 ? LOW : HIGH);
}
}

#else

analogWrite(pin, v);
UNUSED(v_size);
UNUSED(invert);
void init_pwm_timers() {
// Init some timer frequencies to a default 1KHz
const pin_t pwm_pin[] = {
#ifdef __AVR_ATmega2560__
10, 5, 6, 46
#elif defined(__AVR_ATmega1280__)
12, 31
#elif defined(__AVR_ATmega644__) || defined(__AVR_ATmega1284__)
15, 6
#elif defined(__AVR_AT90USB1286__) || defined(__AVR_mega64) || defined(__AVR_mega128)
16, 24
#endif
};

#endif
LOOP_L_N(i, COUNT(pwm_pin))
set_pwm_frequency(pwm_pin[i], 1000);
}

#endif // __AVR__
2 changes: 1 addition & 1 deletion Marlin/src/HAL/AVR/fastio.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ uint16_t set_pwm_frequency_hz(const_float_t hz, const float dca, const float dcb
const float pwm_top = round(count); // Get the rounded count

ICR5 = (uint16_t)pwm_top - 1; // Subtract 1 for TOP
OCR5A = pwm_top * ABS(dca); // Update and scale DCs
OCR5A = pwm_top * ABS(dca); // Update and scale DCs
OCR5B = pwm_top * ABS(dcb);
OCR5C = pwm_top * ABS(dcc);
_SET_COM(5, A, dca ? (dca < 0 ? COM_SET_CLEAR : COM_CLEAR_SET) : COM_NORMAL); // Set compare modes
Expand Down
5 changes: 0 additions & 5 deletions Marlin/src/inc/Conditionals_adv.h
Original file line number Diff line number Diff line change
Expand Up @@ -677,11 +677,6 @@
#define CUTTER_UNIT_IS(V) (_CUTTER_POWER(CUTTER_POWER_UNIT) == _CUTTER_POWER(V))
#endif

// Add features that need hardware PWM here
#if ANY(FAST_PWM_FAN, SPINDLE_LASER_USE_PWM)
#define NEEDS_HARDWARE_PWM 1
#endif

#if !defined(__AVR__) || !defined(USBCON)
// Define constants and variables for buffering serial data.
// Use only 0 or powers of 2 greater than 1
Expand Down
Loading