Gauge jumps once when I start driving it with PWM

I'm using an Arduino Pro Mini to drive a tachometer gauge element using PWM. When it first starts to move the needle there's a quick jump of the needle to about 1/3 scale that then settles and carries on following the PWM increase as it should.

Does anyone have any ideas what might cause this behavior? The gauge takes a very low current, about 1 mA or so for full scale, so it's really sensitive.

I can put together a video if that helps. Thanks

What would actually help is to post a link to the product page or data sheet for the gauge, a wiring diagram (hand drawn, not Fritzing) and your program, posted using code tags.

All this is described in the “How to use this forum” post.

The gauge element is from a 50 year old tachometer and not a commercial product by itself, so there isn’t any documentation on it.

But here’s the code. I had to trim some minor stuff out to meet the 9000 char limit:

#include <FreqMeasure.h>
#include <EEPROM.h>

const int light1Pin = 11; // shift light 1 PWM - Assumed RED
const int light2Pin = 5; // shift light 2 PWM - Assumed GREEN
const int light3Pin = 3; // shift light 3 PWM - Assumed BLUE
const int outputPin = 6; // PWM output to tach
const byte dimPin = A2; // Analog pin used to sense dash light level
const int inputPin = 8; //FreqMeasure input pin
// pin 9 used by FreqMeasure
// pin 10 used by FreqMeasure

const uint16_t rpmTbl[] = {0,500,1000,2000,3000,4000,5000,6000,7000,8000,65535};
int adjTbl[11];
// {0, 50, 100, 75, 25, -50, -100, -100, -150, 0, 0}; 
int lightTbl[13];
// {3500,5500, 10,3, 255,255,0, 0,0,255, 0,255,0}; // thirteen items

float frequency, dutyCycle, rpm, rpmout;
int redMin,greenMin,blueMin,dashLight;
int parseArray[10];

const byte numChars = 65;
char receivedChars[numChars];
char firstChar;
boolean newData = false;
boolean shiftLightOn = false;
int freq,bright;
unsigned long prevmillis, timedelta;
int hyster = 100;
double sum = 0;
int count = 0;
int freqstop;

extern const uint8_t gamma8[];  // This references the gamma table at the bottom of the code

// =======INTERPOLATION=======
uint16_t tween(uint16_t rpm) {
  uint16_t low,high;
  int16_t adjLow,adjHigh;
  int32_t adjust;
  uint8_t p = 0;
  while (!(rpm < rpmTbl[p + 1])) 
  p++; //p now points to the lower of the two rpm values it's between.
  low = rpmTbl[p];
  high = rpmTbl[p + 1];
  adjLow = adjTbl[p];
  adjHigh = adjTbl[p + 1];
  adjust = rpm - low;
  adjust = adjust * (adjHigh - adjLow);
  adjust /= (high - low);
  return (rpm + adjust + adjLow);
}
//
//=====PARSE CSV================
void parseCSV() {
  parseArray[0] = 0;
  parseArray[1] = 0;
  char *token;
  char *tstring = receivedChars;
  token = strtok(tstring, ",");
  int j = 0;
  do  {
    //    Serial.println(token);
    parseArray[j] = atoi(token);
    j = j + 1;
  }
  while (token = strtok(NULL, ","));
}
//
//
//====RECEIVE WITH MARKERS========
void recvWithMarkers() {
  static boolean recvInProgress = false;
  static byte ndx = 0;
  char startMarker = '<';
  char endMarker = '>';
  char rc;
  while (Serial.available() > 0 && newData == false) {
    rc = Serial.read();
    if (recvInProgress == true) {
      if (rc != endMarker) {
        receivedChars[ndx] = rc;
        ndx++;
        if (ndx > + numChars) {
          ndx = numChars - 1;
        }
      }
      else {
        receivedChars[ndx] = '\0'; //terminate the string
        recvInProgress = false;
        ndx = 0;
        newData = true;
      }
    }
    else if (rc == startMarker) {
      recvInProgress = true;
    }
  }
}
//
//=====SMOOTH SWEEP============
void smoothSweep() {
  freq = 20;
  freqstop = (lightTbl[1] + 1000) / 15;
  if (freqstop > 533) { freqstop = 533; }
  Serial.println(freqstop);
  while (freq < freqstop ) {
    rpm = freq * 15; //actual RPM
    rpmout = tween (rpm); 
    dutyCycle = 255 * ( rpmout / 8000) - 3;
    analogWrite(outputPin, dutyCycle);
    delay(20);
    shiftlights(); 
    freq ++;
  }
  while (freq > 20) {
    rpm = freq * 15; //actual RPM
    rpmout = tween (rpm); 
    dutyCycle = 255 * ( rpmout / 8000) - 3;
    analogWrite(outputPin, dutyCycle);
    delay(20);
    shiftlights(); // Check to turn on/off shift lights
    freq --;
  }
  analogWrite(outputPin, 0);
}
//
//========STEP SWEEP============
void stepSweep() {
  for (int i = 1; i < 10; i++) {
    rpm = rpmTbl[i];
    rpmout = tween (rpm); 
    dutyCycle = 255 * ( rpmout / 8000) - 3;
    analogWrite(outputPin, dutyCycle);
    delay(2000);
  }
  for (int i = 9; i > 0; i--) {
    rpm = rpmTbl[i];
    rpmout = tween (rpm); 
    dutyCycle = 255 * ( rpmout / 8000) - 3;
    analogWrite(outputPin, dutyCycle);
    delay(2000);
  }
  analogWrite(outputPin, 0);
  clearbuffer();
}
//
//
//=========RGB STRIP==============
void rgbStrip(int red, int green, int blue, int bfact)
{
  // Modify the color values by the brightness factor and 
  // convert them to gamma corrected values
  int redGamma = pgm_read_byte(&gamma8[red]) * bfact / 30; 
  int greenGamma = pgm_read_byte(&gamma8[green]) * bfact / 30;
  int blueGamma = pgm_read_byte(&gamma8[blue]) * bfact / 30;  
  analogWrite(light1Pin, redGamma);
  analogWrite(light2Pin, greenGamma);
  analogWrite(light3Pin, blueGamma);  
}
//
//=====GAUGE SWEEP CALIBRATION======
void sweepCal() {
  clearbuffer();
  while (firstChar != 'X') {
    newData = false;
    while (newData == false) {
      recvWithMarkers();
    }
    firstChar = receivedChars[0];
    if (firstChar == 'D') 
    {
      frequency = 533;
      rpmout = frequency * 15; //actual RPM
      dutyCycle = 255 * ( rpmout / 8000) - 3;
      analogWrite(outputPin, dutyCycle);
      delay(1000);
    }
  }
  blinktwice();
  analogWrite(outputPin, 0);
  clearbuffer();
}
//
//======SHIFTLIGHT SETPOINTS======
void lightCal() {
  clearbuffer();
  Serial.print("<");
  for (int x = 0; x < 12; x++) {
    Serial.print(lightTbl[x]);
    Serial.print(",");
  }
  Serial.print(lightTbl[12]);
  Serial.println(">");
  delay(500);
  do
  { // wait for AI2 app to send string
    newData = false;
    while (newData == false) {
      recvWithMarkers();
    }
    firstChar = receivedChars[0];
    // parse the AI2 data string and save it
    parseCSV();
    for (int i = 1; i < 14; i++) {
      lightTbl[(i - 1)] = parseArray[i];
    }
    // now check which button was selected
    if (firstChar == 'A') { // 
      blinktwice();
      smoothSweep();
    }
    if (firstChar == 'B') { // 
      blinktwice();
      testBrightness();
    }
  } while (firstChar != 'Z');
  clearbuffer();
  blinktwice();
  // store values in EEPROM
  for (int i = 11; i < 24; i++) {
    EEPROM.put((i*2), lightTbl[(i-11)]);
  }
}
//
//
// ========SHIFTLIGHTS==========
void shiftlights()
{
  if (analogRead(dimPin)>634) {bright = lightTbl[3];} //
  else {bright = lightTbl[2];} // 
  
  if (rpm > lightTbl[0] && rpm < lightTbl[1]) {
    rgbStrip(lightTbl[4],lightTbl[5],lightTbl[6],bright);
    shiftLightOn = true;
  }
  if (rpm > lightTbl[1] || rpm == lightTbl[1]) {
    rgbStrip(lightTbl[7],lightTbl[8],lightTbl[9],bright);
    shiftLightOn = true;
  }
  // Check to turn off shift lights
    //dashLight = round (30 * analogRead(dimPin) / 1023); 
    if (rpm < (lightTbl[0]-hyster)) {
      shiftLightOn = false;
      rgbStrip(0,0,0,0);
    }
}
//
//=======VOID SETUP==============
void setup() {
  FreqMeasure.begin();
  Serial.begin(9600);
  pinMode(inputPin, INPUT);
  pinMode(outputPin, OUTPUT);
  pinMode(light1Pin, OUTPUT);
  pinMode(light2Pin, OUTPUT);
  pinMode(light3Pin, OUTPUT);
  bright = lightTbl[2];
  redMin = 0;
  greenMin = 0;
  blueMin = 0;
// load in array values from eeprom
  for (int x = 0; x < 11; x++) {
      EEPROM.get((x*2),adjTbl[x]);
  }
  for (int y = 11; y < 24; y++) {
      EEPROM.get((y*2),lightTbl[(y-11)]);
  }
}
//
//=========VOID LOOP=============
void loop() {
// check dimPin and turn dash lights on or off
  constrain (dashLight, 0, 30);
  dashLight = map (analogRead(dimPin), 630, 920, 0, 30);
  
  if (dashLight > 0 && shiftLightOn == false) {rgbStrip(lightTbl[10],lightTbl[11],lightTbl[12],dashLight);}
  if (dashLight < 1 && shiftLightOn == false) {rgbStrip(0,0,0,0);}
   
  // measure rpm
  if (FreqMeasure.available()) {
    prevmillis = millis();
    // average several readings together
    sum = sum + FreqMeasure.read();
    count = count + 1;
    if (count > 10) {
      frequency = FreqMeasure.countToFrequency(sum / count);
      sum = 0;
      count = 0;
    }
  }
  
  timedelta = millis() - prevmillis;
  if (timedelta > 250) // quarter second with no data means engine is off
    {
      // rpm is zero
      //delay(100);// delay to allow AI2 app time
      frequency = 0;
      analogWrite(outputPin, 1);
      newData = false;
      recvWithMarkers();
      if (newData == true) {
        firstChar = receivedChars[0];
        if (firstChar == 'D') {
          Serial.println("Letter D received");
          blinktwice();
          clearbuffer();
          calibration();
         }
      }
    }
  else {
    rpm = frequency * 15; //actual RPM
    rpmout = tween (rpm); //corrected RPM to display on gauge
    Serial.println(rpmout);
    dutyCycle = 255 * ( rpmout / 8000) - 3; // calculate duty cycle to apply to the tach movement
    if (dutyCycle < 1) { dutyCycle = 1;}
    analogWrite(outputPin, dutyCycle);
    shiftlights(); // Check to turn on/off shift lights
  }
}
;

The wiring diagram is still missing, as is required info about the gauge. Why do you expect PWM to work? Is the gauge a voltmeter of some sort? If so, what is its internal resistance (VERY IMPORTANT)?

The recommended approach is to write a simple program that positions the gauge needle as desired. When that is working satisfactorily, add the fluff.

jremington:
The wiring diagram is still missing, as is required info about the gauge. Why do you expect PWM to work? Is the gauge a voltmeter of some sort? If so, what is its internal resistance (VERY IMPORTANT)?

The recommended approach is to write a simple program that positions the gauge needle as desired. When that is working satisfactorily, add the fluff.

What makes you think it doesn’t work satisfactorily? Or that I didn’t take a logical approach to my project?

Does it help if you do an immediate analogWrite in setup before reading any frequency measurement?

What makes you think it doesn't work satisfactorily?

You posted a request for advice on how to fix a problem with it.