Autolaturi mökille varavoimaksi Arduinon avulla

@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?
 
Nyt varmistui muuten, miksi Arduino ei toimi ilman tietokoneeseen USB:llä liittämistä. VIN-pinnin tulee 9 voltin sijaan vain 0,96 volttia. Pahoin pelkään, että regulaattori on rikki. Mutta miksi? Kytkennässähän tuo on erotettu muusta, että ei sen 100 mA:n regulaattorin olisi pitänyt olla rajoilla.

No nyt pitää purkaa tuo johtohässäkkä, jotta vielä varmistellaan, että onko vika tosiaan siinä regulaattorissa vai missä. :(
 
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 ...
}
 
@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?

Ihan mahdollista että johtuu tuosta.

Ja tavallaan interrupt on blokkaava. Käytännössä voi t ajatella niin että loop() suoritetaan peräkkäin koko ajan.
Kun tulee pin change interrupt niin loopin suoritus laitetaan paussille, suoritetaan interrupt- funktio ja loop jatkaa siitä mihin jäi.
Tästä johtuen interrupt- funktiossa ei saa käyttää viiveitä yms vaan koodin pitää tehdä haluttu asia mahdollisimman nopeasti.


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 ...
}

PCINT funktio suoritetaan sen jälkeen kun tilan vaihto on tapahtunut, siis edellistä tilaa ei tarvitse tutkia ja säästää vaan vain tarkistaa että nyt on alhaalla (tilanvaihto -> edellinen oli pakosti ylhäällä)
Int- funktion sisällä on myös määritetty prevTime- muuttuja, se on aina 0 tuolla tavalla -> pitää olla globaali jos haluaa verrata suorituysten välillä. Tämä näyttää olevan ai- koodarin kantapää aika usein...
Ja kyllä, C++ std mukaan tuonkin pitäisi toimia mutta koska kyseessä on avr niin en välttämättä luottaisi siihen että kaikki toimisi varmasti. Parempi siis laittaa suosiolla itse globaaliksi kaikki mikä sinne kuuluu, varsinkin int- funktioissa.

Eli joku tällainen pitäisi riittää:

Koodi:
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;
  }
}

Alustuksen saat tarkastaa datalehdestä pinnin perusteella, teknisesti on oikein jos ai tulkkasi oikeaa datalehteä oikeasta arduinokortista.
 
@ississ, okei, eli siis olisiko pähkinänkuoressa:

  1. Tarpeeton aiemman tilan tarkistaminen pois, riittää, että tunnistetaan vain LOW-tila?
  2. prevTime globaaliksi koodin alkuun
  3. PIN5:n interrupt change alustaminen, PCINT21 näyttää olevan oikein muutaman tutoriaalin perusteella, mitä pikaisesti vilkaisin. Koko alustamisen syntaksi näyttäisi olevan myös oikein, jos oikein olen tämän ymmärtänyt.
  4. Ymmärränkö oikein, että tuossa tuo micros() -funtio blokkaa joka kierroksella puolen sekunnin ajaksi muun koodin suorittamisen? Jos blokkaa, niin se ei ole hyvä, koska aikaisemman koodin viive oli liikaa. Koodi toimi todella hitaasti ja ajastuksen menivät liian pitkäksi.

EDIT:
Tässä hieman erilainen lähestymistapa. Koodi lyheni vielä aika paljon, en ole vielä testannut tätä, mutta toivottavasti toimii.

Tässä siis koko koodi:
C++:
#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);
}
 
Viimeksi muokattu:
@ississ, okei, eli siis olisiko pähkinänkuoressa:

  1. Tarpeeton aiemman tilan tarkistaminen pois, riittää, että tunnistetaan vain LOW-tila?
  2. prevTime globaaliksi koodin alkuun
  3. PIN5:n interrupt change alustaminen, PCINT21 näyttää olevan oikein muutaman tutoriaalin perusteella, mitä pikaisesti vilkaisin. Koko alustamisen syntaksi näyttäisi olevan myös oikein, jos oikein olen tämän ymmärtänyt.
  4. Ymmärränkö oikein, että tuossa tuo micros() -funtio blokkaa joka kierroksella puolen sekunnin ajaksi muun koodin suorittamisen? Jos blokkaa, niin se ei ole hyvä, koska aikaisemman koodin viive oli liikaa. Koodi toimi todella hitaasti ja ajastuksen menivät liian pitkäksi.
1-3. kyllä
4. micros() ei blokkaa vaan laskee mikrosekunnit milleistä + ajastimelta.
Täältä löytyy koodikin: micros()
Mutta koska se tekee enemmän kuin millis() niin kannattaa pysytellä millisekunnin tarkkuudessa aina kun se vaan riittää.


EDIT:
Tässä hieman erilainen lähestymistapa. Koodi lyheni vielä aika paljon, en ole vielä testannut tätä, mutta toivottavasti toimii.

Tässä siis koko koodi:
C++:
#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);
}

Onko tämäkin ai- aikaansaannos ?

Koodi:
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
}

Edelleen tuolla on turha lastState jota ei oikeasti tarvita mihinkään.
Int- funktiota kutsutaan laskevalla ja nousevalla reunalla -> kutsun jälkeen tila on vaihtunut eli jos vaan lukee sen arvon ja vertaa onko alhaalla niin voi tehdä asioita jokaisella laskevalla reunalla. Ei sitä erikseen tarvitse tarkastaa vaan lukea manuaalista miten pcint toimii.

Tässä mallissa jossa lasketaan pulsseja tietun ajan niin int- funktiossa pitää vaan tarkastaa että pin tila on oikein (= low) ja jos on niin kasvattaa laskuria.
 
@ississ , tekoälykoodihan se on. Käskin vain tehdä mahdollisimman yhtenäisen (solid) ja lyhyen (short) koodin rpm:n laskentaan. Huomasin itsekin edelleen, että se tarpeettomasti tarkastaisi myös HIGH-tilaa.

Yritän vielä muuttaa itse tuota koodia jälleen kerran ansiokkaiden ohjeidesi avulla sen osalta, että HIGH-tilaa ei tarkkailtaisi lainkaan. Parempihan se olisi, ettei tehdä turhaa työtä.
 
Nyt muokkasin tuota niin, että poistin HIGH-tilan tarkastelun. Ainakin ymmärtääkseni... Meni se kyllä läpi kääntäjästä, että heilahti. 🤒

Tuota on hankala testata ilman todellista sovellusta, koska pitäisi olla pulssitulo simulaattorissa. Wokwi-simussa ei käsitykseni mukaan sellaista ole, eikä se osaa myöskään simuloida pulssia esim. pinnistä toiseen. Joten pitää testata paikallaan. Yrittää saada koodi niin lähelle, kuin testaamatta voi.

C++:
// Enhanced Pin Change Interrupt Service Routine for RPM pin (D5)
ISR(PCINT2_vect) {
  uint8_t currentState = (PIND >> PD5) & 1;  // Read state of D5 (PD5)

  // Detect falling edge (LOW state)
  if (currentState == LOW) {
    pulseCount++;
  }
}

Onko muuten tässä allaolevassa logiikkavirhe?

C++:
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
    }
  }
}

Tekeekö nyt niin, että vaikka virtakynnys ei täyty, mutta byPass == 0 ja motorStatus == SHUTTING , loppukoodi suoritetaan?

Kun pitäisi olla, että tuo suoritetaan vain kun kaksi ensimmäistä ehtoa täytyy, mutta niin, että se suoritetaan loppuun saakka VAIKKA virtakynnys normaalin vaihtelun vuoksi huojahtelisikin takaisin yli 5 ampeerin. Toisaalta, moottori ei voi olla sammutustilassa ennen kuin tuo 5 ampeerin kynnys alittuu ja jos tuota ehtoa, että moottorin pitää olla sammutustilassa (SHUTTING) ei olisi tuossa, voisi käydä juuri niin, että huojahtelun vuoksi koodia ei suoriteta loppuun.

---

Päätin mennä tässä järjestyksessä, että viritellään koodia vielä ennen kuin puretaan kortti pois sen virransyöttövian tarkemman selvittelyn vuoksi. Kun kuitenkin pystyy koodia nyt testaamaan, kun Arduino saa virtansa tietokoneesta.
 

Nyt muokkasin tuota niin, että poistin HIGH-tilan tarkastelun. Ainakin ymmärtääkseni... Meni se kyllä läpi kääntäjästä, että heilahti. 🤒

Tuota on hankala testata ilman todellista sovellusta, koska pitäisi olla pulssitulo simulaattorissa. Wokwi-simussa ei käsitykseni mukaan sellaista ole, eikä se osaa myöskään simuloida pulssia esim. pinnistä toiseen. Joten pitää testata paikallaan. Yrittää saada koodi niin lähelle, kuin testaamatta voi.

C++:
// Enhanced Pin Change Interrupt Service Routine for RPM pin (D5)
ISR(PCINT2_vect) {
  uint8_t currentState = (PIND >> PD5) & 1;  // Read state of D5 (PD5)

  // Detect falling edge (LOW state)
  if (currentState == LOW) {
    pulseCount++;
  }
}

Onko muuten tässä allaolevassa logiikkavirhe?

C++:
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
    }
  }
}

Tekeekö nyt niin, että vaikka virtakynnys ei täyty, mutta byPass == 0 ja motorStatus == SHUTTING , loppukoodi suoritetaan?

Kun pitäisi olla, että tuo suoritetaan vain kun kaksi ensimmäistä ehtoa täytyy, mutta niin, että se suoritetaan loppuun saakka VAIKKA virtakynnys normaalin vaihtelun vuoksi huojahtelisikin takaisin yli 5 ampeerin. Toisaalta, moottori ei voi olla sammutustilassa ennen kuin tuo 5 ampeerin kynnys alittuu ja jos tuota ehtoa, että moottorin pitää olla sammutustilassa (SHUTTING) ei olisi tuossa, voisi käydä juuri niin, että huojahtelun vuoksi koodia ei suoriteta loppuun.

---

Päätin mennä tässä järjestyksessä, että viritellään koodia vielä ennen kuin puretaan kortti pois sen virransyöttövian tarkemman selvittelyn vuoksi. Kun kuitenkin pystyy koodia nyt testaamaan, kun Arduino saa virtansa tietokoneesta.
Kyllä se siltä näyttää kuten sanoitkin. Jos byPass==0 ja joko (current < 5 || motorStatus == SHUTTING), niin ehto on true.
 
@Pekste , nyt kun miettii uudella järjellä ruoan jälkeen, oikeinhan tuon pitäisi olla. Nimittäin kuin tila ei voi olla SHUTTING, ilman, että siihen ensin siirrytään. Ja siirtymiseen vaaditaan tuo virtakynnys. :)
 
No niin! Kiitokset @ississ :lle tuosta pin change interruptista! Kierroslukumittaus toimi oikein hyvin. Ensin ihmettelin, kun näyttää yli 3 800 r/min. Mutta kaasuttimen jousi oli jäänyt kopan väliin, joten ei ole ihme, että vähän kierroksilla kävi. Jousi pois kopan välistä, niin saatiin haluttu 2 500 r/min ja suunnilleen samaa näyttää siis Arduinon saama mittaus ja erillinen laser-mittari.

Ei mitään hyvää, ettei jotain huonoakin. Ryyppy ei lähde pois päältä. Kovasti siinä harmaantunutta ohimoa santapaperilla hieroessani, päätin kaivella esiin taas "servontestaus-Arduinon". Sitä ennen tosin yleismittarilla tsekkasin, että servolle tulee virrat: ok.

Mutta servo ei inahtanutkaan. Eli servo on rikki. Olisi pitänyt uskoa muistaakseni @ississ :n vinkkiä hankkia kunnon, kallis servo. Kaipa se jo nyt sitten realisoitui, ennen kuin on edes muutamaa kertaa enempää startattu. Itse asiassa ei kertaakaan ole saatu tällä kokoonpanolla käynnistettyä automaagisesti!

Onneksi testikäytössä hörpyn voi napata käsin pois, mutta servo pitää ehdottomasti saada uusittua. Toinen ongelma toki säilyy edelleen, eli se, että Arduino ei saa virtaa muuten kuin USB:n kautta. En kyllä mitenkään keksi muuta syytä, kuin että on tullut jokin maanantaikappale nyt sitten regulaattoriksi (12 V --> 9 V). Eihän se ole muuhun kytketty kuin Arduinon VIN-pinniin! :(

Lopuksi vähän iloisemmin. Kone toimi muuten hyvin ja laturi latasi. Näyttää siltä, että kun taas muutaman kuukauden tässä värkkäilee hitaasti, niin saatetaan saada lopullinen testi tehtyä.

Kaipa se täytyy tuohonkin sitten äheltää tuommoinen iso ja kallis regulaattori, jos tuo pikkuinen on noin heikkolaatuinen tai mahdollisuus sellaiseen sattumaan. Tai en minä tiedä. Semmoinenkin kävi mielessä, että voisiko siellä olla ollut jokin "läpilyönti" tms. virtapiikki, joka olisi pimentänyt tuon regulaattorin. Mene ja tiedä... :)

P.S. Aina kun hässäkän luo menee, tuntuu, että on niin kiire ettei kuvia kerkeä ottaa. No kyllä niitä joskus tulee. ;)
 
No niin! Kiitokset @ississ :lle tuosta pin change interruptista! Kierroslukumittaus toimi oikein hyvin. Ensin ihmettelin, kun näyttää yli 3 800 r/min. Mutta kaasuttimen jousi oli jäänyt kopan väliin, joten ei ole ihme, että vähän kierroksilla kävi. Jousi pois kopan välistä, niin saatiin haluttu 2 500 r/min ja suunnilleen samaa näyttää siis Arduinon saama mittaus ja erillinen laser-mittari.

Ei mitään hyvää, ettei jotain huonoakin. Ryyppy ei lähde pois päältä. Kovasti siinä harmaantunutta ohimoa santapaperilla hieroessani, päätin kaivella esiin taas "servontestaus-Arduinon". Sitä ennen tosin yleismittarilla tsekkasin, että servolle tulee virrat: ok.

Mutta servo ei inahtanutkaan. Eli servo on rikki. Olisi pitänyt uskoa muistaakseni @ississ :n vinkkiä hankkia kunnon, kallis servo. Kaipa se jo nyt sitten realisoitui, ennen kuin on edes muutamaa kertaa enempää startattu. Itse asiassa ei kertaakaan ole saatu tällä kokoonpanolla käynnistettyä automaagisesti!

Onneksi testikäytössä hörpyn voi napata käsin pois, mutta servo pitää ehdottomasti saada uusittua. Toinen ongelma toki säilyy edelleen, eli se, että Arduino ei saa virtaa muuten kuin USB:n kautta. En kyllä mitenkään keksi muuta syytä, kuin että on tullut jokin maanantaikappale nyt sitten regulaattoriksi (12 V --> 9 V). Eihän se ole muuhun kytketty kuin Arduinon VIN-pinniin! :(

Lopuksi vähän iloisemmin. Kone toimi muuten hyvin ja laturi latasi. Näyttää siltä, että kun taas muutaman kuukauden tässä värkkäilee hitaasti, niin saatetaan saada lopullinen testi tehtyä.

Kaipa se täytyy tuohonkin sitten äheltää tuommoinen iso ja kallis regulaattori, jos tuo pikkuinen on noin heikkolaatuinen tai mahdollisuus sellaiseen sattumaan. Tai en minä tiedä. Semmoinenkin kävi mielessä, että voisiko siellä olla ollut jokin "läpilyönti" tms. virtapiikki, joka olisi pimentänyt tuon regulaattorin. Mene ja tiedä... :)

P.S. Aina kun hässäkän luo menee, tuntuu, että on niin kiire ettei kuvia kerkeä ottaa. No kyllä niitä joskus tulee. ;)
Mikä se 9v regu olikaan ?
Yksi mikä voi tappaa tuollaisessa ympäristössä on myös tulon ylijännite.

Jos se on 78L09 TO92- kotelossa niin laita tilalle TO220- kotelolla joka kestää 1A. Jos ei muuten niin levyn alapuolelle ?
Tai sitten joku isompi johtojen päähän.
 
@ississ , tuota just olin tulossa kirjoittelemaan, kun alkoi ihmetyttää tuo negatiivinen regu. En ihan ymmärrä, mitä se käytännössä edes tarkoittaa, mutta toisaalta ihmettelen, miksi olen tuommoisen edes tilannut. Kun maalaisjärkikin sanoo, että pitäisi joka tapauksessa olla "plussaregu". Toisaalta muistelen, että se toimi keväällä. Liekö sitten kestänyt "vähän aikaa"? :O

Tuo isompi malli nyt helpompi laittaa, kun ne pcb-reiät tehtiin isommalla rasterilla.

Onko muuten suosituksia servosta nyt, kun se täytyy kerran hankkia? Pitää pystyä ajamaan Arduinolla suoraan, toki virta tulee siitä kortin yhteisestä 2 A:n Draco Powerin virtalähteestä. Varmaan kannattaa varmuuden vuoksi olla vedenpitävä?
 
Nyt muuten pitää miettiä, olenko tuon piirikortinkin piirtänyt väärin. Kun vertaa tähän sen negatiivisen regun datasheetin kuvaan:

1754474624962.png


Kytkentää:
1754474691815.png
1754474736895.png


Näyttäisi kuvien perusteella olevan tuo piirikortti väärin. Mutta sinänsä ei ole niin kauheaa, kun toivottavasti mitään muuta ei ole hajonnut kuin se regu. Uudessa regussa kytkentä taas näyttäisi olevan noin, että keskellä on maa ja reunoilla tulo ja lähtö.

1754474876193.png
 
Nyt muuten pitää miettiä, olenko tuon piirikortinkin piirtänyt väärin. Kun vertaa tähän sen negatiivisen regun datasheetin kuvaan:

1754474624962.png


Kytkentää:
1754474691815.png
1754474736895.png


Näyttäisi kuvien perusteella olevan tuo piirikortti väärin. Mutta sinänsä ei ole niin kauheaa, kun toivottavasti mitään muuta ei ole hajonnut kuin se regu. Uudessa regussa kytkentä taas näyttäisi olevan noin, että keskellä on maa ja reunoilla tulo ja lähtö.

1754474876193.png

Sullahan on kytkentäkaaviossa oikea positiivinen 78- regu ja sen perusteella tehty levy.
Eli kortti on oikein, negaregussa on eri johdinjärjestys.
 
Negatiiviset regut on tarkoitettu sellaisiin joissa tarvitaan myös negatiivinen jännite maahan verrattuna.
Tämän tyyppistä siis:
1754482976863.png


Sovelluksina esimerkiksi monet vahvistimet, mittalaitteet ja ohjausjärjestelmät...
 
Onko muuten suosituksia servosta nyt, kun se täytyy kerran hankkia? Pitää pystyä ajamaan Arduinolla suoraan, toki virta tulee siitä kortin yhteisestä 2 A:n Draco Powerin virtalähteestä. Varmaan kannattaa varmuuden vuoksi olla vedenpitävä?

Mitä tahansa nykyservoa voi ohjata arduinolla. Useissa rc- vastaanottimissa signaali on 3V tai 3.3V vaikka servon käyttöjännite olisikin suurempi. Servot myös hyväksyvät ainakin 5V ohjauksen joten se ei ole ongelma.

Perinteisesti vesitiiviit ovat myös kalliita. Joku "vakiokokoinen" eli noin 40x20x20mm on todennäköisesti edullisin, mahdollisesti rc- autoon tarkoitettu.
Itse käytän vain lentävissä laitteissa (Hitec/Futaba/Savöx/Hyperion) eikä niissä yleensä tarvita edes roisketiivistä. Kunnollisten hinta on yleensä jossain 50€ alkaen.
Kokeilisin ehkä jotain tämän tyyppistä: Servo Traxxas 2056 5.7kg/0.22sek Sealed TRX2056 - Harrastekauppa Hobbylinna tai - Harrastekauppa Hobbylinna
Kummastakaan ei ole kerrottu max virtaa, on ihan mahdollista että 2A ei riitä. Jotkin kestävät suoraan 12V mutta ne taitaa olla turhan kalliita.
Mutta varmaan antaa suuntaa, tuohon käyttöön ei tarvita tarkkuutta eikä kuulalaakereita, riittävä voima ja mielellään hidas liike parempi, roiskekestävyyden voi ehkä hoitaa koteloinnilla.
 
@ississ, no ehkäpä se virtalähdekin joutaisi sitten vaihtaa, jos ei 2 A riitä. Mikäli tekoälyä on uskominen, pahimmillaan nappaisi luokka 1,0-1,5 ampeeria, mutta vain kovaa vastusta vastaan. Hyvältä siis näyttää.
 
@ississ, no ehkäpä se virtalähdekin joutaisi sitten vaihtaa, jos ei 2 A riitä. Mikäli tekoälyä on uskominen, pahimmillaan nappaisi luokka 1,0-1,5 ampeeria, mutta vain kovaa vastusta vastaan. Hyvältä siis näyttää.
Älä usko tekoälyä vaan ainoastaan servon teknisiä tietoja.
Jos niitä ei ole eikä kukaan ole oikeasti mitannut niin sitten ei voi kuin arvata.
Servo ottaa piikkivirtaa varsinkin liikkeelle lähdössä, siksi kannattaa olla hidas että on suurempi ratasvälitys ja liikkeelle lähtö on helpompi. Eikä tässä ole oikeasti väliä kestääkö kääntö 0.1 vai 1sek, periaatteessa moottorin tai oikeastaan kaasuttimen virtauksen kannalta hidas on parempi.
Powerin kannalta taas se alkupiikki pitää kestää eli tavallaan no-win tilanne
 
@ississ , noo se on nyt jo tilattu tuo servo. Jos ei toimi niin hankkii jostain sitten tehokkaamman powerin. Ei ollut Triopakilla niitä tehokkaampia Dracoja. Varmaan oisin tehokkaamman laittanut, mutta on sitten sen ajan murhe jos kyykkää servosta. :)
 

Statistiikka

Viestiketjuista
283 307
Viestejä
4 864 638
Jäsenet
78 640
Uusin jäsen
aimbertti

Hinta.fi

Back
Ylös Bottom