Follow along with the video below to see how to install our site as a web app on your home screen.
Huomio: This feature may not be available in some browsers.
// ... existing code ...
// RPM Calculation variables - volatile for interrupt safety
volatile unsigned long lastFallTime = 0; // Time of last falling edge
volatile unsigned long period = 0; // Pulse period in microseconds
// ... existing setup ...
void setup() {
// ... existing setup code ...
// RPM Pin with pull-up and PCI enabled
pinMode(rpmPin, INPUT_PULLUP);
PCICR |= (1 << PCIE2); // Enable PCINT2 group
PCMSK2 |= (1 << PCINT21); // Enable interrupt for PCINT21 (pin 5)
// ... rest of setup ...
}
// Pin Change Interrupt for RPM sensor (pin 5)
ISR(PCINT2_vect) {
static unsigned long prevTime = 0;
static int prevState = HIGH;
int currentState = digitalRead(rpmPin);
if (prevState == HIGH && currentState == LOW) { // Falling edge
unsigned long currentTime = micros();
lastFallTime = currentTime;
if (prevTime != 0) {
period = currentTime - prevTime;
}
prevTime = currentTime;
}
prevState = currentState;
}
void loop() {
// ... existing sensor readings ...
// New RPM Calculation (atomic read + timeout handling)
unsigned long now = micros();
unsigned long temp_lastFallTime;
unsigned long temp_period;
// Atomic read of volatile variables
noInterrupts();
temp_lastFallTime = lastFallTime;
temp_period = period;
interrupts();
if (temp_lastFallTime == 0) {
rpm = 0; // No pulse ever detected
} else if (now - temp_lastFallTime > 500000) { // 500ms timeout
rpm = 0;
} else {
rpm = (temp_period > 0) ? 60000000UL / temp_period : 0;
}
// ... rest of loop ...
}
@ississ , ahaa eli lienee mahdollisuus, että tuo kierroslukumittauksen virhe kierrosluvun kasvaessa johtuisi tuosta?
Korjaa, jos ymmärrän väärin, mutta eikös tuo interrupt sitten olisi blokkaava?
Kiinanpojan tekoäly ehdottaisi tällaisia muutoksia kierrosluvun laskentaan, onko aivan kaheleita vai voisiko jopa toimia äkkiä katsottuna?
C++:// ... existing code ... // RPM Calculation variables - volatile for interrupt safety volatile unsigned long lastFallTime = 0; // Time of last falling edge volatile unsigned long period = 0; // Pulse period in microseconds // ... existing setup ... void setup() { // ... existing setup code ... // RPM Pin with pull-up and PCI enabled pinMode(rpmPin, INPUT_PULLUP); PCICR |= (1 << PCIE2); // Enable PCINT2 group PCMSK2 |= (1 << PCINT21); // Enable interrupt for PCINT21 (pin 5) // ... rest of setup ... } // Pin Change Interrupt for RPM sensor (pin 5) ISR(PCINT2_vect) { static unsigned long prevTime = 0; static int prevState = HIGH; int currentState = digitalRead(rpmPin); if (prevState == HIGH && currentState == LOW) { // Falling edge unsigned long currentTime = micros(); lastFallTime = currentTime; if (prevTime != 0) { period = currentTime - prevTime; } prevTime = currentTime; } prevState = currentState; } void loop() { // ... existing sensor readings ... // New RPM Calculation (atomic read + timeout handling) unsigned long now = micros(); unsigned long temp_lastFallTime; unsigned long temp_period; // Atomic read of volatile variables noInterrupts(); temp_lastFallTime = lastFallTime; temp_period = period; interrupts(); if (temp_lastFallTime == 0) { rpm = 0; // No pulse ever detected } else if (now - temp_lastFallTime > 500000) { // 500ms timeout rpm = 0; } else { rpm = (temp_period > 0) ? 60000000UL / temp_period : 0; } // ... rest of loop ... }
volatile unsigned long prevTime = 0;
// Pin Change Interrupt for RPM sensor (pin 5)
ISR(PCINT2_vect) {
if (digitalRead(rpmPin) == LOW) { // Falling edge
unsigned long currentTime = micros();
lastFallTime = currentTime;
if (prevTime != 0) {
period = currentTime - prevTime;
}
prevTime = currentTime;
}
}
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <neotimer.h>
#include <ServoTimer2_v2.h> // the servo library
// System status variables
#define WAITING 0
#define STARTING 1
#define STARTED 2
#define RAMP_UP 3
#define RUNNING 4
#define FAIL 5
#define SHUTTING 6
// Fail state LCD display
#define FAILED_START 1
#define OVERVOLTAGE 2
#define FAILED_RPM 3
// Pin definitions
const int currentPin = A0; // Current sensor in
const int voltagePin = A3; // Voltage divider input
const int pwmPin = 9; // PWM Output
const int relayPin = 4; // Ignition relay
const int rpmPin = 5; // RPM Sensor in
const int overPin = 2; // Voltage bypass switch
const int starterPin = 3; // Starter motor pin
const int servoPin = 11; // Servo control pin (using Timer2 via ServoTimer2)
// Servo configuration (easily adjustable)
#define CHOKE_ON_US 1230 // Fully engaged position (microseconds) // Servo, connect to the 3rd outermost hole of the servo arm
#define CHOKE_OFF_US 2185 // Retracted position (microseconds)
#define SERVO_MOVE_TIME 1000 // Time for servo movement in ms
// Timers
Neotimer motorTime = Neotimer(6000); // 6 seconds for startup detection
Neotimer stepTime = Neotimer(1000); // 1 second for PWM step interval
Neotimer shutTime = Neotimer(10000); // 10 seconds for engine shutdown
Neotimer crankTime = Neotimer(4000); // 4 seconds for cranking
Neotimer crankWait = Neotimer(5000); // 5 seconds wait after failed crank
Neotimer voltageWait = Neotimer(10000); // 10 seconds wait for undervoltage
Neotimer buttonTime = Neotimer(2000); // 2 seconds charging button timer, bypass voltage waiting
Neotimer currentReadTimer = Neotimer(40); // 40ms for current readings
Neotimer voltageReadTimer = Neotimer(40); // 40ms for voltage readings
Neotimer servoMoveTimer = Neotimer(SERVO_MOVE_TIME); // Servo movement duration
Neotimer chokeEngageTimer = Neotimer(2000); // 2s choke hold after start
Neotimer rpmCalcTimer = Neotimer(500); // 500ms interval for RPM calculation
// RPM Calculation - Interrupt based
volatile unsigned long pulseCount = 0; // Counts pulses in ISR
unsigned long rpm = 0; // Stores calculated RPM
// LCD
LiquidCrystal_I2C lcd(0x27, 16, 2);
// Variables
int motorStatus = WAITING;
int startTry = 0;
int startInterval = 0; // Waiting time on after start attempt
int currentPwmStep = 0;
int pwmValues[] = { 15, 67, 119, 171 }; // PWM steps
int lcdFail = 0; // LCD fail state display
int byPass = 0; // 0 = normal function use shutdown function, 1 = bypass switch toggled, use bypasshutdown function with lower amperage shutdown
// Current and voltage calculation
float voltageFactor = 5.00 / 1023.00; // Factor to convert ADC reading to voltage
// Define the points for current calculation / the line equation
float x1 = 0.489; // volts
float y1 = 0; // amps
float x2 = 2.136; // volts
float y2 = 80; // amps
// Calculate the slope and intercept
float m = (y2 - y1) / (x2 - x1);
float b = y1 - m * x1;
float currentCal = 1; // Variable to shift the whole current level
float sensVfactor = 20.00 / 4.9828; // 4.9197 volts = 20 volts, factor to convert high voltage to 0-5 V
const int N = 25; // Number of current readings to average
const int Y = 25; // Number of sensed voltage readings to average
int readings[N]; // Array to store the current readings
int readingsV[Y]; // Array to store the sensed voltage readings
// Step counters for readings
int currentReadStep = 0;
int voltageReadStep = 0;
float sensVoltage = 12.00; // Example voltage
float current = 0.00; // Example current
// Servo control using ServoTimer2
ServoTimer2_v2 chokeServo; // Changed to ServoTimer2 instance
bool isChokeEngaged = false; // Track choke physical state
// Enhanced Pin Change Interrupt Service Routine for RPM pin (D5)
ISR(PCINT2_vect) {
static uint8_t lastState = (PIND >> PD5) & 1; // Initialize with current state
uint8_t currentState = (PIND >> PD5) & 1; // Read state of D5 (PD5)
// Detect falling edge (HIGH to LOW transition)
if (lastState == HIGH && currentState == LOW) {
pulseCount++;
}
lastState = currentState; // Update state for next interrupt
}
void setup() {
Serial.begin(250000);
// Change timers on pins to change PWM freq to 122 Hz
// Pins D9 and D10 - 122 Hz
TCCR1A = 0b00000001; // 8bit
TCCR1B = 0b00000100; // x256 phase correct
// Initialize I2C and check if LCD is connected
Wire.begin();
Wire.beginTransmission(0x27);
if (Wire.endTransmission() == 0) {
// LCD is connected, proceed with initialization
lcd.begin(16, 2);
lcd.backlight();
lcd.clear();
} else {
// LCD is not connected, continue without initializing the LCD
Serial.println("LCD not connected. Skipping.");
}
// Pin modes
pinMode(currentPin, INPUT);
pinMode(voltagePin, INPUT);
pinMode(pwmPin, OUTPUT);
pinMode(relayPin, OUTPUT);
pinMode(rpmPin, INPUT_PULLUP); // Enable pull-up resistor
pinMode(overPin, INPUT_PULLUP);
pinMode(starterPin, OUTPUT);
// Enhanced Pin Change Interrupt setup for RPM pin (D5)
PCICR |= (1 << PCIE2); // Enable PORTD interrupts
PCMSK2 |= (1 << PCINT21); // Enable interrupt specifically for D5 (PCINT21)
// Start RPM calculation timer
rpmCalcTimer.start();
analogWrite(pwmPin, 255); // Start with PWM off
}
void startCharge() { // Start charging without to wait lower voltage, bypass voltage time
if (digitalRead(overPin) == LOW) {
if (!buttonTime.started()) {
buttonTime.start();
}
if (buttonTime.done()) {
motorStatus = STARTING;
byPass = 1;
digitalWrite(relayPin, HIGH); // Relay on
buttonTime.stop();
buttonTime.reset();
}
} else {
buttonTime.stop();
buttonTime.reset();
}
}
void motorCrankState() { // Voltage lower than this executes starting function
if (sensVoltage < 11.0) {
if (!voltageWait.started()) {
voltageWait.start();
}
if (voltageWait.done()) {
motorStatus = STARTING;
digitalWrite(relayPin, HIGH); // Relay on
voltageWait.stop();
voltageWait.reset();
}
} else {
voltageWait.stop();
voltageWait.reset();
}
}
void crank() { // Starting function
if (startTry < 5 && startInterval == 0) {
if (rpm <= 300) { // If motor runs slower than this, switch starter relay on
digitalWrite(starterPin, HIGH);
if (!crankTime.started()) {
crankTime.start();
}
if (crankTime.done()) { // If max cranking time finished without succeeding start, switch starter relay off
digitalWrite(starterPin, LOW);
crankTime.stop();
crankTime.reset();
startTry++;
startInterval = 1;
}
} else if (rpm > 700) { // If motor runs faster than this, switch starter relay off
digitalWrite(starterPin, LOW);
startTry = 0;
motorStatus = STARTED;
crankTime.stop();
crankTime.reset();
}
}
}
void Waiter() { // Wait intervals between start attempts
if (startInterval == 1) {
if (!crankWait.started()) {
crankWait.start();
}
if (crankWait.done()) {
startInterval = 0;
crankWait.stop();
crankWait.reset();
}
}
}
void motorRunning() { // Assume that motor is running after the time interval
if (rpm > 1400) {
if (!motorTime.started()) {
motorTime.start();
}
if (motorTime.done()) {
motorStatus = RAMP_UP;
motorTime.stop();
motorTime.reset();
}
}
}
void rampUp() { // Ramping up PWM
if (stepTime.repeat()) {
analogWrite(pwmPin, pwmValues[currentPwmStep]);
currentPwmStep++;
if (currentPwmStep >= sizeof(pwmValues) / sizeof(pwmValues[0])) {
motorStatus = RUNNING;
stepTime.stop();
stepTime.reset();
}
}
}
void shutDown() { // Shut down function
// Execute if current is low OR shutdown is already in progress
if (byPass == 0 && (current < 5 || motorStatus == SHUTTING)) {
// Only execute initial steps when first entering SHUTTING state
if (motorStatus != SHUTTING) {
motorStatus = SHUTTING;
digitalWrite(relayPin, LOW); // Turn off the ignition relay
analogWrite(pwmPin, 15); // Set PWM to 5% (reduce alternator load)
if (!shutTime.started()) {
shutTime.start(); // Start the shutdown timer
}
}
// Continue monitoring timer until shutdown completes
if (shutTime.done()) {
analogWrite(pwmPin, 255); // Set PWM to 100% (alternator electromagnet off)
motorStatus = WAITING; // Revert to WAITING state
currentPwmStep = 0; // Reset PWM step counter for next RAMP_UP
shutTime.stop(); // Stop the timer
shutTime.reset(); // Reset the timer
}
}
}
void bypassShutdown() { // Shut down function with lower amperage
// Execute if current is low OR shutdown is already in progress
if (byPass == 1 && (current < 0.5 || motorStatus == SHUTTING)) {
// Only execute initial steps when first entering SHUTTING state
if (motorStatus != SHUTTING) {
motorStatus = SHUTTING;
digitalWrite(relayPin, LOW); // Turn off the ignition relay
analogWrite(pwmPin, 15); // Set PWM to 5% (reduce alternator load)
if (!shutTime.started()) {
shutTime.start(); // Start the shutdown timer
}
}
// Continue monitoring timer until shutdown completes
if (shutTime.done()) {
analogWrite(pwmPin, 255); // Set PWM to 100% (alternator electromagnet off)
motorStatus = WAITING; // Revert to WAITING state
currentPwmStep = 0; // Reset PWM step counter for next RAMP_UP
byPass = 0;
shutTime.stop(); // Stop the timer
shutTime.reset(); // Reset the timer
}
}
}
void failState() { // Handle failed start attempts, overvoltage etc.
if (startTry == 5) { // Failed start
motorStatus = FAIL;
digitalWrite(starterPin, LOW); // Starter relay off
digitalWrite(relayPin, LOW); // Ignition switch relay off
lcdFail = FAILED_START;
} else if (sensVoltage >= 15 || lcdFail == OVERVOLTAGE) { // Overvoltage
motorStatus = FAIL;
digitalWrite(relayPin, LOW); // Ignition relay off
lcdFail = OVERVOLTAGE;
if (!shutTime.started()) {
shutTime.start(); // Start the shutdown timer
}
if (shutTime.done()) {
analogWrite(pwmPin, 255); // Set PWM to 100% (alternator electromagnet off)
shutTime.stop(); // Stop the timer
shutTime.reset(); // Reset the timer
}
} else if ((rpm <= 900 || rpm >= 3700) && (motorStatus == RUNNING)) { // RPM sensor
motorStatus = FAIL;
digitalWrite(relayPin, LOW); // Ignition relay off
lcdFail = FAILED_RPM;
if (!shutTime.started()) {
shutTime.start(); // Start the shutdown timer
}
if (shutTime.done()) {
analogWrite(pwmPin, 255); // Set PWM to 100% (alternator electromagnet off)
shutTime.stop(); // Stop the timer
shutTime.reset(); // Reset the timer
}
}
}
void lcdDisplay() { // LCD display
if (motorStatus == RUNNING || motorStatus == RAMP_UP || motorStatus == SHUTTING) {
lcd.setCursor(0, 0);
lcd.print("Virta: ");
lcd.print(current);
lcd.print(" A ");
lcd.setCursor(0, 1);
lcd.print("J");
lcd.print((char)0xe1);
lcd.print("nnite: ");
lcd.print(sensVoltage);
lcd.print(" V ");
} else if (motorStatus == WAITING) {
lcd.setCursor(0, 0);
lcd.print("Odottaa... ");
lcd.setCursor(0, 1);
lcd.print("J");
lcd.print((char)0xe1);
lcd.print("nnite: ");
lcd.print(sensVoltage);
lcd.print(" V ");
} else if (motorStatus == STARTING || motorStatus == STARTED) {
lcd.setCursor(0, 0);
lcd.print("K");
lcd.print((char)0xe1);
lcd.print("ynnistet");
lcd.print((char)0xe1);
lcd.print((char)0xe1);
lcd.print("n");
lcd.setCursor(0, 1);
lcd.print("................");
} else if (lcdFail == FAILED_START) {
lcd.setCursor(0, 0); // On lcd print "Engine start failed"
lcd.print("K");
lcd.print((char)0xe1);
lcd.print("ynnistys ");
lcd.setCursor(0, 1);
lcd.print("ep");
lcd.print((char)0xe1);
lcd.print("onnistui ");
} else if (lcdFail == OVERVOLTAGE) {
lcd.setCursor(0, 0); // On lcd print "Overvoltage"
lcd.print("Ylij");
lcd.print((char)0xe1);
lcd.print("nnite ");
lcd.setCursor(0, 1);
lcd.print(" ");
} else if (lcdFail == FAILED_RPM) {
lcd.setCursor(0, 0); // On lcd print "Faulty rpm"
lcd.print("Kierrosluku ");
lcd.setCursor(0, 1);
lcd.print("virheellinen ");
}
}
void loop() {
// Calculate current from sensor reading
if (currentReadStep < N) {
if (currentReadTimer.repeat()) {
readings[currentReadStep] = analogRead(currentPin);
currentReadStep++;
}
} else {
// Calculate the average of the N readings
float sum = 0;
for (int i = 0; i < N; i++) {
sum += readings[i];
}
float average = sum / N;
float voltage = average * voltageFactor;
current = (m * voltage + b) * currentCal;
currentReadStep = 0;
}
// Calculate high voltage from voltage divider input
if (voltageReadStep < Y) {
if (voltageReadTimer.repeat()) {
readingsV[voltageReadStep] = analogRead(voltagePin);
voltageReadStep++;
}
} else {
// Calculate the average of the Y readings
float sumV = 0;
for (int j = 0; j < Y; j++) {
sumV += readingsV[j];
}
float averageV = sumV / Y;
float voltageV = averageV * voltageFactor;
sensVoltage = voltageV * sensVfactor;
voltageReadStep = 0;
}
/* Optimized RPM calculation
* - Uses atomic access to pulseCount to prevent corruption
* - Calculates RPM every 500ms (120 = 60s/0.5s)
* - Minimal blocking time (only 3 operations protected)
*/
if (rpmCalcTimer.done()) {
// Atomically capture and reset pulse count
unsigned long countCopy;
noInterrupts();
countCopy = pulseCount;
pulseCount = 0;
interrupts();
rpm = countCopy * 120; // Convert pulses per 500ms to RPM
rpmCalcTimer.start(); // Restart immediately for consistent interval
}
// Simulate sensor readings via Serial input
if (Serial.available() > 0) {
char input = Serial.read();
switch (input) {
case '1':
rpm = 0;
break;
case '2':
rpm = 500;
break;
case '3':
rpm = 2000;
break;
case 'a':
current = 2;
break;
case 'b':
current = 20;
break;
case 'c':
current = 80;
break;
case 'z':
sensVoltage = 11;
break;
case 'x':
sensVoltage = 14;
break;
case 'y':
sensVoltage = 16;
break;
default:
Serial.println("Invalid input.");
return;
}
}
// Handle motor status
switch (motorStatus) {
case WAITING:
motorCrankState();
startCharge();
break;
case STARTING:
// Engage choke fully before cranking using ServoTimer2
if (!isChokeEngaged) {
if (!servoMoveTimer.started()) {
chokeServo.attach(servoPin); // Attach servo to pin 11
chokeServo.write(CHOKE_ON_US); // ServoTimer2 uses write() for microseconds
servoMoveTimer.start();
}
if (servoMoveTimer.done()) {
chokeServo.detach();
isChokeEngaged = true;
servoMoveTimer.stop();
servoMoveTimer.reset();
}
} else {
crank();
Waiter();
}
break;
case STARTED:
motorRunning();
// Retract choke after defined delay using ServoTimer2
if (!chokeEngageTimer.started()) {
chokeEngageTimer.start();
}
if (chokeEngageTimer.done() && isChokeEngaged) {
if (!servoMoveTimer.started()) {
chokeServo.attach(servoPin);
chokeServo.write(CHOKE_OFF_US); // Retract using write() with microseconds
servoMoveTimer.start();
}
if (servoMoveTimer.done()) {
chokeServo.detach();
isChokeEngaged = false;
servoMoveTimer.stop();
servoMoveTimer.reset();
chokeEngageTimer.stop();
chokeEngageTimer.reset();
}
}
break;
case RAMP_UP:
rampUp();
break;
case RUNNING:
shutDown();
bypassShutdown();
break;
case SHUTTING:
shutDown();
bypassShutdown();
break;
}
lcdDisplay();
failState();
// Debug output
Serial.print("Voltage: ");
Serial.print(sensVoltage);
Serial.print(" V, Current: ");
Serial.print(current);
Serial.print(" A, RPM: ");
Serial.print(rpm);
Serial.print(", Status: ");
Serial.print(motorStatus);
Serial.print(", Start try: ");
Serial.print(startTry);
Serial.print(", Start wait: ");
Serial.println(startInterval);
}