#include <TimerOne.h> // https://github.com/PaulStoffregen/TimerOne
// This example creates a PWM signal with a variable duty cycle.
// This scheme enforces a minimum pulse width at low duty cycles
// by varying the frequency and increasing the off times between
// pulses.
//
// this code was adapted from the TimerOne library example
// for a 25kHz fan
// https://github.com/PaulStoffregen/TimerOne/blob/master/examples/FanSpeed/FanSpeed.ino
//
// Minimum pulse width could be important for good torque from motors
// With a tau=L/R, a 5*tau pulse width for a R=1.65ohm, L=3.2mH stepper
// would be tau=0.0032/1.65=1.9ms and 5*L/R = 5*.0032/1.65=9.7ms
//
constexpr int fanPin = 9; // Uno Timer1 OC1A pin
// Set the high frequency period and the minimum pulse
// These settings work for the Wokwi chips-scope from
// https://github.com/Dlloydev/Wokwi-Chip-Scope
// scope demo config:
// https://wokwi.com/projects/359331973918199809
// 2000us & 400us looks good with 200us sampling rate >22Hz
// 80 &4 at 1us sampling rate >1Hz
// 20000us and 2000us at 1ms sample rate for 50Hz>3Hz
// 20 & 1 and a real scope for 50K operation
constexpr unsigned long basePeriod = 1000000UL;
constexpr unsigned long minUs = 20;
// calculate the changeover from adjusting pulse width at const freq
// to adjusting the frequency at const pulse width:
constexpr float minDuty = 100.0 * minUs / basePeriod;
float dutyCycle = 0;
int lastA0 = 0;
void setup(void)
{
Timer1.initialize(basePeriod); // us
Serial.begin(9600);
}
void loop(void)
{
updateSettings();
delay(0);
}
void updateSettings(void) {
const int interval = 250;
static unsigned long last = -interval;
if (millis() - last < interval ) return;
last += interval;
int val = analogRead(A0);
if (val != lastA0) {
lastA0 = val;
dutyCycle = 100 * (1 - cos(PI / 2 * val / 1023)); // 0-100% with high res at low end
unsigned long myPeriod = 0;
Serial.print("PWM Fan, Duty Cycle = ");
Serial.println(dutyCycle,4);
if (dutyCycle > minDuty || dutyCycle == 0.0) {
myPeriod = basePeriod;
Timer1.setPeriod(myPeriod);
Timer1.pwm(fanPin, (dutyCycle * 1023UL / 100));
Serial.print((unsigned long)(dutyCycle * myPeriod / 100));
Serial.print("us pulse at Hz:");
Serial.println(1000000UL / myPeriod);
} else { // Set by minimum width:
myPeriod = minUs * 100UL / dutyCycle;
if(myPeriod > 1885292UL){
myPeriod = 1885292UL;
}
Timer1.setPeriod(myPeriod);
Timer1.pwm(fanPin, (uint32_t)(dutyCycle * 1023UL / 100));
Serial.print(minUs);
Serial.print("us pulse / ");
Serial.print(myPeriod);
Serial.print("us period / Hz:");
Serial.println(1.0e6 / myPeriod);
Serial.print("Timer1.OCR1A:"); Serial.print(OCR1A);
Serial.print(" / ICR1:");Serial.print(ICR1);
Serial.print(" TCCR1B:0b");Serial.println(TCCR1B,BIN);
}
}
}
DutyCycle
100%
0%