Since my last code didn't actually have a PLL (or need a PLL), and because I thought it was interesting, I implemented one to even out the intervals between pulses. It also has the added benefit of handling the opposite case of speeding up a pulse train, such as 4500 pulses out per 4000 pulses in.
// This simulates a pulse train on simPin,
// detects the pulses on pinIn,
// runs a PLL at pll_div * the speed of the pulses,
// Runs a bresenham algorithm on the ratio of dy/dx for outpin pulses of out_interval us
// if dy is faster than dx, you should use a bigger pll_div than 1 to provide
// distinct intervals between double-steps and reduced jitter.
// if dy < dx, a pll_div of 1 is OK
//
// https://wokwi.com/projects/355885955385104385
// for https://forum.arduino.cc/t/how-to-slow-down-the-signal-from-a-hall-effect-sensor/1086525/3
//
// https://www.romanblack.com/one_sec.htm is good reading.
// Look at the sections on the sinewave inverter
int32_t bresenham = 0;
int32_t bres_pll = 0;
int dy = 4000;
int dx = 4500;
const int pll_div = 4; // how many sub-pulses per input pulse
const int debug = 2; // level of serial output
unsigned long out_interval = 1000;
uint32_t last_pulse = 0;
const int pinIn = 3;
const int simPin = pinIn;
const int outpin = LED_BUILTIN;
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
pinMode(outpin, OUTPUT);
pinMode(pinIn, INPUT_PULLUP);
pinMode(simPin, OUTPUT); // same pin used to simulate input
}
uint32_t now = 0;
void simpulses(void) { // read the slide potentiometer and simulate a variable pulse train
int simState = digitalRead(simPin);
static uint32_t lastSim = 0;
static unsigned long last = 0;
// 4000/mi / (X mi/hr 1hr/3600s * 1s/1000us)
// 1/(4000 pulse/mile * 60 mi/hr /(3600s/hr*1000ms/s))) gives
static uint32_t interval = 3600UL * 1000 * 1000 / 4000 / 10 * 0 + 1000000UL ; // mph to ms/pulsein
static int32_t lastRead = -2500000UL;
static int lastA0 = 0;
if ( now - lastRead > 250000UL && abs((int)analogRead(A0) - lastA0) > 0 ) {
lastA0 = analogRead(A0);
//interval = 6000UL + 1000UL * lastA0;
interval = 3600UL * 1e6 * 1024 / 4000 / (0 + lastA0) / 15;
if (debug > 0) {
Serial.print("mph:");
Serial.print(interval ? 3600.0 * 1e6 / 4000 / interval : 0.0);
}
lastRead = now;
}
if (now - last >= interval ) { // simulate pulse
last = now;
digitalWrite(simPin, HIGH);
lastSim = now;
//Serial.print('^');
}
if (simState == HIGH && now - lastSim >= out_interval) {
// turn off simulated pulse
digitalWrite(simPin, LOW);
}
}
void loop() {
int inState = digitalRead(pinIn);
uint32_t lastInterval = 0;
static int lastin = HIGH;
static uint32_t lastFall = 0;
static uint32_t dt = 1;
static uint32_t lastpll = 0;
int simState = digitalRead(simPin);
static uint32_t lastSim = 0;
static uint32_t pllSteps = 0;
now = micros();
simpulses();
if (inState != lastin) { // check input pulse
if ( inState == LOW) { //falling edge
lastInterval = now - lastFall;
bres_pll = lastInterval; // += sensitive to wide swings near zero
dt = max(1, lastInterval / pll_div); //
lastFall = now;
if (debug > 0)Serial.print('\\'); // falling edge
//Serial.print(bres_pll);
//Serial.print(dt);
//Serial.print(pllSteps);
} else {
if (debug > 2) Serial.print('/'); // rising edge
}
lastin = inState;
}
// Run PLL at input freq * pll_div
if (now - lastpll >= dt && bres_pll +dt > 0) {
lastpll += dt;
if (bres_pll > 0) { //
bres_pll -= dt;
bresenham += dy;
if (debug > 1)Serial.print('.');
if (debug > 3) Serial.print(bresenham);
}
}
if (now - last_pulse > 6000 && bresenham > (int32_t)dx * pll_div) { //
bresenham -= (int32_t)dx * pll_div;
digitalWrite(LED_BUILTIN, HIGH);
if (debug > 0)Serial.print('*');
if (debug > 3)Serial.print(bresenham);
last_pulse = now;
}
if (digitalRead(LED_BUILTIN) && now - last_pulse > out_interval)
{ // turn off outpulse
digitalWrite(LED_BUILTIN, LOW);
if (debug > 2) Serial.print('_');
}
}
4000 pulses/mile at 150mph is 1/(4000p/mi*150mi/hr/(3600s/hr)=0.006s so there's lots of time to work with.
The cool thing about using the Bresenham algorithm is that by adding and subtracting integers, rather than dividing, you keep track of the fractional values and the phase.