Writing code for an analogue tachometer powered by Attiny85

I copied most of of the code, so I don't know why there is a main variable . I was working on fixing the errors, but I figured that with the way i was fixing errors, the code probably wasn't going to work properly.
I'll fix the c variable and report back

Should I put this before void setup command or in it?

It will probably be fine. I've only had one occasion where not having a 330nF on the input (I forgot it was supposed to be 330nF and fitted 100nF) caused high frequency oscillation on the output. Don't forget the 100nF across the ATtiny85 power pins. It's a standard digital design technique to put such decoupling capacitors on the power supply pins of digital chips. Should be as close to the pins as practical

In post #5, i advised to get UNO R3 + ATtiny85 chips and NOT UNO Q.

Ok, but then fix the others that are similar the same way.

It's one that i can borrow, I would have to buy a new or fix the R3 that I have.

This is a strong indication you need more experience before you try this project. Get a copy of the Arduino Cookbook and work your way through it. Also the Electronics Cookbook is one of several good electronics beginner books.

As I said the 10uF caps will be fine.

Before. Up top even. If it is outside any function,it will be a global variable you can use eveywhere.

If you want to take a side track, the basic concept is "variable scope".

Here or wherever you go to lesrn:

https://docs.arduino.cc/language-reference/en/variables/variable-scope-qualifiers/scope/

or places like this

what turn up when you google

variable scope c++

Read a few. Some are friendlier than others, or expect you to bring different prior knowledge.

At a higher level, documentation like this can be pretty stiff going; it makes sense to start seeing how these things are talked about, it's a language all its own.

HTH

a7

Thanks, I'll read up.

I figured out how to write the voltage interpolation much smaller, without a large lookup sheet. I also did declare c, but Arduino IDE is still really unhappy with the whole interpolation function. I'm fairly sure that the whole interpolator needs to be properly written from scratch.

#define frequency_output MISO
#define volt_sense PCINT3
#define coil_neg SCK
//Look up table for RPM to frequency output.
struct rpm_look_up {
  int x;
  float y;
} c[5] = {
    { 60, 250 },
    { 30, 500 },
    { 5.625, 3500 },
    { 3, 5000 },
    { 1.875, 8000 }
};

void setup() {
  // put your setup code here, to run once:
  pinMode(frequency_output, OUTPUT);
  pinMode(volt_sense, INPUT);       
  pinMode(coil_neg, INPUT);         
  int default_frequency(0);         //Rpm base output
  int volt_mod(0);                  //Voltage correction output
  int c(0);                         //Rpm calculation varliable
  unsigned long pulse_time(0);      
  digitalWrite(frequency_output, LOW);
}

void loop() {
  // put your main code here, to run repeatedly:
  // Voltage measuring
  while ((SCK) == 0)
    ;  //wait for High on Pin2 of PORTB
  unsigned long start = micros();
  while (SCK)
    ;  //wait for Low
  while ((SCK) == 0)
    ;  //wait for High
  unsigned long rpm_look_up = micros() - start;
  // Start of rpm interpolation
  double default_frequency(rpm_look_up * c, double x, int n)  //Default_frequency is output #
  {
    int i;
    for (i = 0; i < n - 1; i++) {
      if (c[i].x <= x && c[i + 1].x >= x) {
        double diffx = x - c[i].x;
        double diffn = c[i + 1].x - c[i].x;
        return c[i].y + (c[i + 1].y - c[i].y) * diffx / diffn;
      }
    }
    return 0;  // Not in Range
  }
  int main(int argc, char** argv) {
    double y = Default_frequency(c, 850, 5);
  }

  //Volt sensing
  analogRead(volt_sense);   // Read analog voltage on pin PCINT3
  int analogIn = analogRead(volt_sense);
  int v(1 / 3.75);          //Voltage calculation variable

  // voltage interpolation
  int volt_mod = (v * analogIn);

  //Output
  analogWrite(frequency_output, volt_mod * default_frequency);  // Output PWM
}
C:\Users\User\Documents\Arduino\Tachometer_project_V1.4\Tachometer_project_V1.4.ino: In function 'void loop()':
C:\Users\User\Documents\Arduino\Tachometer_project_V1.4\Tachometer_project_V1.4.ino:40:40: error: invalid operands of types 'long unsigned int' and 'rpm_look_up [5]' to binary 'operator*'
   double default_frequency(rpm_look_up * c, double x, int n)  //Default_frequency is output #
                            ~~~~~~~~~~~~^~~
C:\Users\User\Documents\Arduino\Tachometer_project_V1.4\Tachometer_project_V1.4.ino:40:45: error: expected primary-expression before 'double'
   double default_frequency(rpm_look_up * c, double x, int n)  //Default_frequency is output #
                                             ^~~~~~
C:\Users\User\Documents\Arduino\Tachometer_project_V1.4\Tachometer_project_V1.4.ino:40:55: error: expected primary-expression before 'int'
   double default_frequency(rpm_look_up * c, double x, int n)  //Default_frequency is output #
                                                       ^~~
C:\Users\User\Documents\Arduino\Tachometer_project_V1.4\Tachometer_project_V1.4.ino:52:35: error: a function-definition is not allowed here before '{' token
   int main(int argc, char** argv) {
                                   ^
exit status 1

Compilation error: invalid operands of types 'long unsigned int' and 'rpm_look_up [5]' to binary 'operator*'```

OK, here's a problem

int main(int argc, char** argv) {
    double y = Default_frequency(c, 850, 5);
  }

That's your program.

Typically Arduino sketches do not have a main() function, it's supplied for you behind the scenes and

  • calls setup() once
  • calls loop() over and over and over…

Your logic is all in the loop, and is best used to do something and finish, expecting to be called immediately for another pass.

If this is all gobbledygook, you have to read a bit at a very beginning level, to see how a sketch is organized and functions. There are examples starting from zero on offer in the IDE, there are worse starting places.

You could put your code in your redefinition of that hidden main(), but that is not very often either a good idea or necessary.

Elsewhere, you have used rpm_look_up in multiple ways: pick a different name for one of the very different rolls it plays in the code.

I'd call the struct lookUpTable. And I'd rename the receiving value too.

Less confusing.

I'm under the umbrella looking through a tiny window. When I'm in the lab llI take a closer look.

a7

I'll remove the main command in my next iteration. Currently trying to figure out all of the steps, equations and instructions that i need in order to rewrite the interpolator from scratch right now.

Some remarks (probably not complete) about the sketch:

  • You might want to set File > Preferences > Compiler warnings to All.
  • #defines as MISO, PCINT3 and SCK are unusual, but on UNO they work (can't test on ATtiny85).
  • I'm not familiar with structs, but the declaration of c[] is after the closing bracket.
  • The declaration of c[] contains no type (I guess int is default).
  • You declare c[5] but give 10 members.
  • In setup() you declare local variables, they go unused.
  • In setup() you set an output to LOW, not wrong but also not necessary.
  • In loop() you compare the value of SCK, the compiler replaced this with a number (13 on UNO), so the while-loop will keep running.
  • You define functions within function loop().
  • You define function main(), which is a reserved name.
  • The first analogRead() is superfluous (sometimes advised for signals from high impedance sources).
  • After the second analogRead() you use a 'magic number'. You'd better define such a number at the top of the sketch.
  • In function default_freqency() (what is the meaning of default in something you are calculating?) you apparently want to do a calculation where the function receives its arguments. This should be done where the function is called.
  • In function default_freqency() declaration of the index is better suited within the for-loop, for (int i = ..., making it local to the loop.
  • Are you aware that the internal oscillator of the ATtiny85, used to measure the time, can be off by as much as 10%?

Some remarks (probably not complete) about the circuit:

  • Are you picking up the signal from the low voltage side or the high voltage side (dangerous)?
  • Is it an engine with more than one cylinder? In that case the time between sparks you measure may not be regular through the rotation.
  • The electrical environment in a car is messy, you might want more protection.

@stitech - which sketch are you talking about? Which post #?

For example, c is clearly defined as an array of structs of type rpm_look_up, five of them each having two members, an int and a float.

And while unusual, main() is weakly defined on purpose to allow its use when appropriate, which as I have already said it rarely is. Appropriate. Perfectly legal.

a7

I'm getting my tach signal from the negative side of the coil. The circuit has a optocoupler to protect the Attiny85 from the extreme voltage.
It's a 4 cylinder motor(22re)
I'll see if the Attiny85 works well with the protection that it has. I hadn't thought of it until now, but i should add a diode to the beginning of the circuit.

No, I guess I'll see how bad it is and if i need an additional quartz oscillator.

The last posted, post #31.

I am no in the lab, but I tossed a bunch of the various code versions and snippets into chatGPT and asked it to make a coherent sketch.

Note: it is not tested. It is not intended to be a solution. I asked chatGPT to write a disclaimer/explainer which should. Explain and disclaim.

The remainder of this post is AI generated.

a7 out

//==============

This sketch is not presented as a working tachometer solution, and it is completely untested. Its purpose is pedagogical: to show the general shape of a valid Arduino sketch using the same ideas the original code was reaching for.

In particular, it demonstrates:

  • proper use of the setup() / loop() structure
  • separating INPUT, PROCESS and OUTPUT stages
  • using a struct to define lookup table entries
  • storing table data cleanly
  • putting interpolation logic into a reusable function
  • calling a function and using its returned value
  • distinguishing between variables, functions and constants
  • using meaningful intermediate variables so the flow of the program can be read and reasoned about

The interpolation code was intentionally moved into its own function because functions are your friend: they let you name a process, isolate complexity, avoid duplication, and make the main loop easier to understand.

The calculations themselves may or may not be appropriate for the actual hardware. The goal here is not “copy this and your tach works,” but rather “this is roughly how these language elements fit together in a coherent sketch that compiles and can be discussed productively.”

// Demonstration sketch only.
// Shows the structure: input -> lookup/interpolation -> correction -> output.

const byte frequencyOutputPin = 1;
const byte voltageSensePin    = A3;
const byte coilInputPin       = 2;

struct LookupPoint {
 float x;
 float y;
};

LookupPoint rpmTable[] = {
 { 300,  10.02 },
 { 700,  89.54 },
 { 800, 126.45 },
 { 900, 171.45 },
 { 1500, 225.12 }
};

const int rpmTableSize = sizeof rpmTable / sizeof rpmTable[0];

float interpolate(LookupPoint table[], int tableSize, float input) {
 for (int i = 0; i < tableSize - 1; i++) {
   if (input >= table[i].x && input <= table[i + 1].x) {
     float x0 = table[i].x;
     float y0 = table[i].y;
     float x1 = table[i + 1].x;
     float y1 = table[i + 1].y;

     return y0 + (y1 - y0) * (input - x0) / (x1 - x0);
   }
 }

 return 0;   // input was outside the table range
}

void setup() {
 pinMode(frequencyOutputPin, OUTPUT);
 pinMode(voltageSensePin, INPUT);
 pinMode(coilInputPin, INPUT);

 digitalWrite(frequencyOutputPin, LOW);
}

void loop() {
 // ----- INPUT -----

 unsigned long pulseTime = pulseIn(coilInputPin, HIGH, 100000);

 int analogReading = analogRead(voltageSensePin);


 // ----- PROCESS -----

 float rpm = 0;

 if (pulseTime > 0) {
   rpm = 60000000.0 / pulseTime;   // demo calculation, not necessarily correct for the real tach
 }

 float baseOutput = interpolate(rpmTable, rpmTableSize, rpm);

 float voltageCorrection = analogReading / 1023.0;

 int pwmOutput = baseOutput * voltageCorrection;

 pwmOutput = constrain(pwmOutput, 0, 255);


 // ----- OUTPUT -----

 analogWrite(frequencyOutputPin, pwmOutput);
}

In that case is seriously suggest that you balance the power input. Consider a 1N4007 diode from 12v+ to Vin on the 7805, a 1N4007 from circuit GND to Chassis. A fly-back 1N4007 outside of this may sound like overkill but it won't hurt at all. And a bigger input capacitor for the 7805, something in the order of at least 100uf / 50v.

Automotive electrical power is extremely dirty and there can be huge voltage spikes and reverse current. The MCU will not behave the way you want if you don't filter that out.

Hi @stuntman225
Power the analog meter with 5V not the battery, that way you don’t need to make adjustments for voltage. So you won’t need the voltage divider on pin2 or the associated code for volt_sense

Replace the 330Ω resistor with 166.6Ω, a 200Ω in parallel with 1K = 166.6Ω

Forget about the interpolator for now and see if you can make it work just going from RPM to output PWM

Do you have a signal source for testing on the bench?

Once you get this working , then you can use the MultiMap library to do your nonlinear interpolations.