So the speedo worked from an arduino feeding it pulses
That's the punto speedo, you can hear the racket it makes.
To feed the speed pulses to the speedo I need to use the same ABS sensor and VR conditioner setup I was using previously to get a 5V square wave. However the frequency of this signal is not what the electronic speedo control board is expecting to see so I needed to alter it. You can buy ‘speedo healer’ devices for motorbikes that do exactly this and by all accounts they work brilliantly for about £85. But they don’t work with VR sensors so I’d still have to condition the signal to feed into it and anyway, where’s the fun in buying?
So again the Arduino would do the job. The commercial ones probably use an AVR or PIC to do the job anyway. Idea is to read the 5v square wave coming in, measure the frequency, apply a correction multiplier, then output a suitably frequency adjusted 5V square wave. Do this multiple times per second. I tried to do this entirely in software on the Arduino but couldn’t get it to work robustly. Not sure why, I think maybe because the FrequencyMeasure library I’m using to derive the incoming signal frequency basically spams interrupts and that screws up the loop timing. Somebody cleverer/more conversant with Arduino could probably make it work.
Anyway I resorted to extra hardware in form of a £10 AD9850 DDS module all the way from China. Basically, you send it a frequency command over a serial connection and it outputs a wave form. So the Arduino measures the frequency coming in, applies a multiplier to calculate the output frequency and then sends that frequency to the AD9850 which it generates until it’s sent a new frequency (which I do every 100ms). It can change from frequency to frequency without jitter and it can do it (theoretically) millions of times per second. It’s meant for generating radio frequencies really and I’m using it right at the very bottom end of its capability. Pretty amazing what you can get for a tenner these days.
My finished board has an on-the-fly adjustable multiplier from 0.1% up to 999.9% in minimum of 0.1% steps. There are 3 buttons on my board – one to select the digit to change, one to increment and one to decrement. The multiplier is displayed on the OLED screen that was used as the odometer in my original stepper speedo. Yeah, way over the top but whatever. Multiplier is saved to EEPROM 10 seconds after a change and the screen turns off 10 seconds after any button press. It’s a bit on the big side but could easily be shrunk down. At the moment it just sits nicely in the centre console cassette holder. Total cost probably in the region of £45 (including the VR conditioner).
So anyway, this thing puts out a 5V square wave, then a basic signal transistor level shifts this to the 12V square wave that the punto speedo control board expects to see.
This is with it being fed a constant 100Hz signal, then adjusting the multiplier using the board
I know from calibrating my previous speedo that around 40000 ABS sensor pulses = 1 mile. Testing the Saxo speedo before pulling it apart it was 225Hz = 100mph. So initial multiplier was set to 14.8% and final adjustment on the road took it to 15.1% as spot on.
Code below in case anyone comes browsing along and wants to do something similar.
/*
Speedo Healer by Neal Wright 2018.
Uses FreqMeasure library from
FreqMeasure Library, for Measuring Frequencies in the 0.1 to 1000 Hz range, or RPM Tachometer Applications
and AD9850 library from
Arduino Projekte
*/
#include <Bounce2.h>
#include <EEPROMex.h>
#include <SPI.h>
#include <Wire.h>
//#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "FreqMeasure.h"
#define button_increase 12
#define button_decrease 11
#define button_select 10
#define OLED_RESET 4
#include <AH_AD9850.h>
//#define NUMFLAKES 10
//#define XPOS 0
//#define YPOS 1
//#define DELTAY 2
Bounce debouncer1 = Bounce();
Bounce debouncer2 = Bounce();
Bounce debouncer3 = Bounce();
AH_AD9850 AD9850(7, 6, 5, 4);
Adafruit_SSD1306 display(OLED_RESET);
const int UpdateInterval = 100;
unsigned long PreviousMillis = 0;
unsigned long buttonMillis;
unsigned long changemillis;
float outfrequency = 0;
float frequency = 0;
double sum=0;
int count=0;
int noInputCount = 0;
int increment = 1;
int multiplier = 1;
int multiplier2 = 1;
float percent;
int ones;
int tens;
int hundreds;
int thousands;
int ForceMulti = 0;
int memstatus = 1;
String MultiRead;
void setup(void)
{
if (ForceMulti == 0) {
MultiRead = String(EEPROM.read(0))+String(EEPROM.read(1))+String(EEPROM.read(2))+String(EEPROM.read(3));
multiplier = MultiRead.toInt();
multiplier2 = multiplier;
}
Serial.begin(9600);
display.begin(SSD1306_SWITCHCAPVCC);
FreqMeasure.begin();
AD9850.reset();
delay(1000);
AD9850.powerDown();
pinMode(8, INPUT_PULLUP);
pinMode(button_select,INPUT_PULLUP);
debouncer1.attach(button_select);
debouncer1.interval(5); // interval in ms
pinMode(button_increase,INPUT_PULLUP);
debouncer2.attach(button_increase);
debouncer2.interval(5); // interval in ms
pinMode(button_decrease,INPUT_PULLUP);
debouncer3.attach(button_decrease);
debouncer3.interval(5); // interval in ms
}
void loop() {
if (multiplier != multiplier2){
changemillis = millis();
memstatus = 0;
multiplier2 = multiplier;
}
if(memstatus == 0){
if(changemillis+10000 < millis()){
EEPROM.write(0,thousands);
EEPROM.write(1,hundreds);
EEPROM.write(2,tens);
EEPROM.write(3,ones);
memstatus = 1;
}
}
multiplier = constrain(multiplier,1,9999);
unsigned long currentMillis = millis();
if (currentMillis - PreviousMillis >= UpdateInterval) {
PreviousMillis = currentMillis;
count = 0;
sum = 0;
while (FreqMeasure.available()) {
sum += FreqMeasure.read();
count++;
}
if (count) {
frequency = FreqMeasure.countToFrequency(sum / count);
noInputCount = 0;
}
else if (++noInputCount == 2)
frequency = 0;
outfrequency = ((multiplier * frequency) / 1000);
Serial.println(outfrequency);
AD9850.set_frequency(outfrequency);
}
debouncer1.update();
if ( debouncer1.fell() ) {
buttonMillis = millis();
if(increment == 1)
increment = 1000;
else if(increment == 1000){
increment = 100;
}
else if (increment == 100){
increment = 10;
}
else if (increment == 10){
increment = 1;
}
}
debouncer2.update();
if ( debouncer2.fell() ) {
multiplier = multiplier + increment;
buttonMillis = millis();
}
debouncer3.update();
if ( debouncer3.fell() ) {
multiplier = multiplier - increment;
buttonMillis = millis();
}
if(buttonMillis+30000 > millis()){
thousands = ((multiplier/1000)%10);
hundreds = ((multiplier/100)%10);
tens = ((multiplier/10)%10);
ones = ((multiplier/1)%10);
display.setTextSize(3);
display.setTextColor(WHITE);
display.clearDisplay();
if(increment == 1000){
display.drawLine(4, 28, 20, 28, WHITE);
}
else if(increment == 100){
display.drawLine(24, 28, 40, 28, WHITE);
}
else if (increment == 10){
display.drawLine(44, 28, 60, 28, WHITE);
}
else if (increment == 1){
display.drawLine(72, 28, 88, 28, WHITE);
}
if(multiplier > 999){
display.setCursor(5,0);
display.println(thousands);
}
if(multiplier > 99){
display.setCursor(25,0);
display.println(hundreds);
}
if(multiplier > 9){
display.setCursor(45,0);
display.println(tens);
}
display.setCursor(73,0);
display.println(ones);
display.setCursor(58,0);
display.println(".");
display.setCursor(100,0);
display.println("%");
}
else if(buttonMillis+30000 <= millis()){
display.clearDisplay();
}
display.display();
}