#include <TimerOne.h> // 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.
// for a 25kHz fan
// 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 
//github.com/Dlloydev/Wokwi-Chip-Scope
//  scope demo config: 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 = 20000;
constexpr unsigned long  minUs = 2000;
// 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() { Serial.begin(9600); Timer1.initialize(basePeriod); }  // us
void loop() { updateSettings();  }

void updateSettings() {
  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);
    if (dutyCycle > minDuty || dutyCycle == 0.0) {
      myPeriod = basePeriod;
      Timer1.setPeriod(myPeriod);
      Timer1.pwm(fanPin, (dutyCycle * 1023UL / 100));
      Serial.print((int)(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); // OCR1A - регистр
      Serial.print(" / ICR1:");      Serial.print(ICR1);  // ICR1 - регистр
      Serial.print(" TCCR1B:0b");    Serial.println(TCCR1B,BIN); //TCCR1B - регистр
    }
  }
}
Loading chip...chip-scope
DutyCycle 100% 0%