I have build this RPM measure device for a tractorpulling sled. The measure device is mounted on the sled and connected to the tractorpuller with a 3 wire cable. The measure sensor on the tractorpuller is a inductive proximity switch. It know that all this works, cause i have another device tested to work.
The intension is that every time a tractor is connected to the sled, the maximum hold value is reset and it shows the actual RPM right now on a LCD. When the tractor is detached the LCD shows the maximum value from the previous pull.
The code is as follows:
/*
11-2-2012
Spark Fun Electronics
Nathan Seidle
This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).
Serial7Segment is an open source seven segment display.
This is example code that shows how to send data over I2C to the display.
Please Note: 0x71 is the 7-bit I2C address. If you are using a different language than Arduino you will probably
need to add the Read/Write bit to the end of the address. This means the default read address for the OpenSegment
is 0b.1110.0011 or 0xE3 and the write address is 0b.1110.0010 or 0xE2.
For more information see https://learn.sparkfun.com/tutorials/i2c
Note: This code expects the display to be listening at the default I2C address. If your display is not at 0x71, you can
do a software or hardware reset. See the Wiki for more info:
http://github.com/sparkfun/Serial7SegmentDisplay/wiki/Special-Commands
To get this code to work, attached an Serial7Segment to an Arduino Uno using the following pins:
A5 to SCL
A4 to SDA
VIN to PWR
GND to GND
For this example pull up resistors are not needed on SDA and SCL. If you have other devices on the
I2C bus then 4.7k pull up resistors are recommended.
OpenSegment will work at 400kHz Fast I2C. Use the .setClock() call shown below to set the data rate
faster if needed.
*/
#include <Wire.h>
#define DISPLAY_ADDRESS1 0x71 //This is the default address of the OpenSegment with both solder jumpers open
int cycles = 0;
int pin4 = 4;
int pin6 = 6;
int newData[1] = {};
int maximum[1] = {0};
int minimum[1] = {4000};
int newData1[1]={0}; //not sure if they have to be initialized as 0
int oldData1[1]={0};
int counter = 0;
boolean pulling;
boolean showing;
boolean resetswitch;
volatile byte half_revolutions;
unsigned int rpm;
unsigned long timeold;
void rpm_fun()
{
half_revolutions++;
//Each rotation, this interrupt function is run twice
}
void setup()
{
Wire.begin(); //Join the bus as master
pinMode(pin4,INPUT);
pinMode(pin6,INPUT);
//Send the reset command to the display - this forces the cursor to return to the beginning of the display
Wire.beginTransmission(DISPLAY_ADDRESS1);
Wire.write('v');
Wire.endTransmission();
attachInterrupt(0, rpm_fun, FALLING);
half_revolutions = 0;
rpm = 0;
timeold = 0;
resetswitch = false;
pulling = false;
showing = false;
}
void loop()
{
showing = false;
pulling = false;
resetswitch = false;
if (half_revolutions >= 60) {
//Update RPM every 20 counts, increase this for better RPM resolution,
//decrease for faster update
rpm = 30*1000/(millis() - timeold)*half_revolutions;
timeold = millis();
half_revolutions = 0;
// Serial.println(rpm,DEC);
}
newData[0] = rpm;
for(int i = 0; i < 1; i++)
{
if(newData[i] > maximum[i])
{
maximum[i] = newData[i];
}
if(newData[i] < minimum[i])
{
minimum[i] = newData[i];
}
}
newData1[0] = rpm;
if (newData1[0]==oldData1[0])
{
counter++;
}
if (newData1[0]!=oldData1[0])
{
counter = 0;
}
if (newData1[0]==oldData1[0] && counter >=300)
{
showing = true;
}
oldData1[0] = newData1[0];
if (rpm >= 200)
{
pulling = true;
}
if (showing==true)
{
resetswitch = true;
i2cSendValue(maximum[0]); //Send the four characters to the display
delay(50);
}
if (pulling==true)
{
i2cSendValue(rpm);
delay(50);
}
if (pulling== true && resetswitch == true)
{
maximum[0] = 0;
minimum[0] = 4000;
resetswitch = false;
}
//Serial.println(rpm);
//If we remove the slow debug statements, we need a very small delay to prevent flickering
}
//Given a number, i2cSendValue chops up an integer into four values and sends them out over I2C
void i2cSendValue(int tempCycles)
{
Wire.beginTransmission(DISPLAY_ADDRESS1); // transmit to device #1
Wire.write(0x79); // Send the Move Cursor Command
Wire.write(0x00); // Send the data byte, with value 1
int first = tempCycles / 1000;
Wire.write(first); //Send the left most digit
tempCycles %= 1000; //Now remove the left most digit from the number we want to display
int secon = tempCycles / 100;
Wire.write(secon);
tempCycles %= 100;
int third = tempCycles / 10;
Wire.write(third);
tempCycles %= 10;
int fourth = tempCycles;
Wire.write(fourth); //Send the right most digit
Wire.endTransmission(); //Stop I2C transmission
}
The showing and pulling values is tested individually and works. But somehow the != function to set showing = true don't work it resets and shows both actual and maximum.
newData1[0] = rpm;
if (newData1[0]==oldData1[0])
{
counter++;
}
if (newData1[0]!=oldData1[0])
{
counter = 0;
}
Checks if the cable is connected to the tractor or not. If not connected the value of rpm will stay on the last known value of rpm. The counter function is to avoid "not connected" if the values actually is the same two times in a row. eg measures 3180 rpm on first update, and also 3180 on second update.
Enteres eighter the state of showing the actual value(pulling-connected to tractor) or the state of not connected to tractor, then showing maximum (showing)
if (showing==true)
{
resetswitch = true;
i2cSendValue(maximum[0]); //Send the four characters to the display
delay(50);
}
if (pulling==true)
{
i2cSendValue(rpm);
delay(50);
}
if (pulling== true && resetswitch == true)
{
maximum[0] = 0;
minimum[0] = 4000;
resetswitch = false;
}
The two states mentioned above, and at last the function which resets maximum when another tractorpuller is connected to the sled.
The thought by this is that i checks if the rpm value is updated or equal the prevoius value. If it is equal the previous it is not connected to the tractor?
The sensor if an inductive proximity switch, which detects RPM on the tractorpuller. The puller idles around 700-1000 rpm. So if we disconnect the cable, the arduino will not detech any "counts" from the proximity switch, thus the rpm value will not update, because it only updates every 60 counts. In this case it should show maximum on the small display.
If the tractor is connected it updates the RPM value every 60 count. Thus it will not go into the state "showing" but instead into the state "pulling" Where it shows the actual RPM momentarily.
if (newData1[0]==oldData1[0]) // group these
{
counter++;
}
if (newData1[0]!=oldData1[0])
{
counter = 0;
}
As these are exclusive, you can just use an else :
if (newData1[0]==oldData1[0]) // group these
{
counter++;
}
else
{
counter = 0;
}
Also I'd try to avoid the delay() and use the non blocking method. Don't think you delay long enough to overflow your byte counter, as a tractor rpm cant be that high? but still.
Might suggest a finite state machine, looks like you have 2 very clear and exclusive states, pulling, and showing, and separating them would make life easy.
I have all ready made i functioning code that updates RPM every second(also tried with 500ms and 2000ms) that worked perfect, no problems at all. It was the same code as above, only difference was that it was controlled by the value of RPM. RPM<=200 = showing and RPM>=201 = pulling.
BUT BUT... The problem is that the flywheel only has to measuring points, thus with a 1 second update the resolution can only be 30 RPM, which is to less. The tractorpullers are only allowed to run with 3200RPM.
So the reason why i am doing it with updates based on "counts" is to increase the resolution by measuring the "time" between counts instead of the counts in a given time period.
BastianGaardahl:
The problem is that the flywheel only has to measuring points, thus with a 1 second update the resolution can only be 30 RPM, which is to less.
I'm not sure I understand. What is the lowest RPM the engine can run at?
What update resolution do you need ?
So the reason why i am doing it with updates based on "counts" is to increase the resolution by measuring the "time" between counts instead of the counts in a given time period.
I reckon you can use my system that way also. The ISR would need to compare halfRevolutions with prevHalfRevolutions and save the value of millis() after the appropriate number of counts.
Then, instead of your main code working with latestHalfRevs it would work with latestMillis
The engine idles at 700-1000 RPM. But when the cable from the sled to the tractorpuller is not connected the arduino doesn't read any counts. It need a resolution at least at 10 RPM.
I don't think you understand my problem. What you suggest i actually what i do, but i can't use the value of rpm to control if it should be in the state pulling or in the state showing because it only updates the value of rpm when 60 counts are measured. Thus i have to compare whetether the value of rpm are updated or not. If it is updated, the tractor are connected to the sled. If it is NOT updated it is not connected to the sled.
BastianGaardahl:
The engine idles at 700-1000 RPM. But when the cable from the sled to the tractorpuller is not connected the arduino doesn't read any counts. It need a resolution at least at 10 RPM.
I don't think you understand my problem. What you suggest i actually what i do, but i can't use the value of rpm to control if it should be in the state pulling or in the state showing because it only updates the value of rpm when 60 counts are measured. Thus i have to compare whetether the value of rpm are updated or not. If it is updated, the tractor are connected to the sled. If it is NOT updated it is not connected to the sled.
I agree. I am lost.
The code in your Original Post does not use the ISR to record the time after 60 pulses (as I suggest in Reply #10). (But maybe there is a later version that you have not shown us).
I still maintain that the question of connected or not-connected should not depend on RPM, but only on whether new data is arriving. For example if the ISR sets a flag after every 60 pulses (and records millis() at the same time) your main code can check the gap from the last time that the ISR recorded millis(). If it is is longer than X you can assume that the ISR is not getting pulses.
...R
PS, I agree that timing N counts is a more accurate way of measuring RPM.
I managed to make it work, when using the above regarding rpm value changed or not. I will post the working code later.
But i am not there yet.
The problem seems to be the resolution. We tested the equipment last weekend, and the the Measurements said: 3185, 3137 and 3088 which is a resolution of 49 rpm. This i don't understand.
The theoretical resolution should be 5 rpm. Lets take an example:
this is the code calculating rpm:
if (half_revolutions >= 60) {
//Update RPM every 20 counts, increase this for better RPM resolution,
//decrease for faster update
rpm = 30*1000/(millis() - timeold)*half_revolutions;
timeold = millis();
half_revolutions = 0;
}
lets say half rev = 60
millis() - timeold = 565 ms
rpm = 3185