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. :)
 
Nyt tässä kun osia odotellaan, voisi joutessaan kysyä. Jos olisi joskus mahdollisuus tehdä tämä projekti "kunnolla", varmastikin haluaisin jopa varmuuden vuoksi erottaa ohjauksen (Arduinon) ja LCD:n kokonaan muusta systeemistä. Mitenhän sellainen onnistuisi? Digilinjat varmasti menisivät erittäin helposti perus optoerottimilla, mutta entä analogisignaalit?
 
@Nasty76 , joo, eli ilmeisesti analogi pitäisi muuntaa digitaaliksi? I2C -väyläänhän voi käsittääkseni liittää vaikka kuinka paljon kamaa. Toki jos sisääntulo on sitten 0-255, niin tarkkuus laskee ainakin jonkin verran portaattomaan verrattuna. Voi olla, että en nyt muista jotain. 🧐

EDIT: Eikun sehän onkin tietenkin 0-1023. Eli kyllä siinä tarpeeksi on portaita puhuttaessa kuitenkin volttimittauksista.
 
Analogit on vähän haasteellisempi erottaa.
Yksi vaihtoehto on laittaa erillinen mittari (esim sopiva avr tai pelkkä a/d muunnin) jolloin digiväylä on helppo erottaa kunhan valitsee väylän jossa samassa johdossa ei kulje signaalia kahteen suuntaan. Onnistuu sekin mutta ei niin helposti.
 
Jos haluaa erottaa ohjauspuolen niin jännitteitä mittaava "jokin" pitää valita sen perusteella että erotus on järkevästi toteutettavissa.
Esimerkkinä tuo ADS1115 on i2c- väylällä joka on 2- suuntainen. I2c- erotus perus optoilla ei onnistu ihan helposti vaan kannattaa käyttää i2c- erotuspiiriä, esimerkiksi ISO164x. Onneksi niitäkin on valmiina.
Mutta noin esimerkkinä ihan kaikki väylät ei onnistu helpolla vain optoilla.

Vaikka datalinjat erottaa niin pitää miettiä miten kummankin pään virransyöttö toteutetaan.
Jos molemmilla on sama syöttö niin on ihan turha erottaa signaaleja. Jos taas on erillinen syöttö niin mistä se toinen pää saa käyttöjännitteen ?
 
@ississ , erotettu DC-DC -muunnin? Eli "herkkä" elektroniikka sellaisen varaan. Mitenhän lie autoissa tehty tuo ECUN erottaminen muusta virtapiiristä, vai liekö siellä ollenkaan erotusta?
 
Mitä itse ecuja tutkinut siellä on 12v sisään ja itse ecu käyttää 5v joten siellä on regu ja jonkinmoinen konkka.
Epäilen kyllä että tollasissa laitteissa ihan turhaan itse ainakin luottaisin esim sen ad1115 piirin erotukseen ja vetäisin suoraan arduinolle
 
@ississ , erotettu DC-DC -muunnin? Eli "herkkä" elektroniikka sellaisen varaan. Mitenhän lie autoissa tehty tuo ECUN erottaminen muusta virtapiiristä, vai liekö siellä ollenkaan erotusta?

Autoissa on ihan omat jutut, hiukan kun hakee niin netistä löytyy juttua automaailman powereihin.
Viralliset "mitä powerin pitää kestää ja sietää" speksit on aika kovat.

Ne autolaitteet jotka on tähän mennessä tullut vastaan eivät ole olleet erotettuja vaan syöttöpuolella on ollut filtteri ja väärin päin kytkennän suojaus + tietysti virtalähdeosio tarpeen mukaan. Ja monet anturit syötetään powerin jälkeen pienemmällä kuin 12V jolloin ne ovat jo suodatuksen takana (paitsi kaapelihäiriöt) joka helpottaa esim AD- muunnosta.

Erotetulla hakkurilla pärjää kyllä pitkälle kunhan huolehtii tulon piikinsuodatuksesta. Ja siitä että tulojännitealue on riitävän suuri vaikka nimellisesti onkin vain max ~15V.

Ja jos laitat erikseen jännitteen mittauksen se pitää kuitenkin olla suoraan kiinni virallisessa jännitteessä jolloin mittauspisteessä pitäisi olla suojaus (nytkin oikeastaan pitäisi) ja maa tietysti yhdistettynä. Tässä tapauksessa täysin erotettu poweri ei oikeastaan eroa tavallisesta jos sen suodatus on kunnossa.
Logiikkaosan erotus on selkeämpi koska se voisi olla myös kokonaan kelluva. Yleensä kyllä maa on silti yhdistetty.


Mitä itse ecuja tutkinut siellä on 12v sisään ja itse ecu käyttää 5v joten siellä on regu ja jonkinmoinen konkka.
Epäilen kyllä että tollasissa laitteissa ihan turhaan itse ainakin luottaisin esim sen ad1115 piirin erotukseen ja vetäisin suoraan arduinolle
Datalehden mukaan ads1115 ei ole minkäänlaista erotusta siinä mielessä kun nyt on keskusteltu. Joka tapauksessa mittaus vaatii saman maan moottorin kanssa ja se on piirissä sama kuin digitaalipuolen maa.
Eli väliin pitää laittaa i2c erotinpiiri jos haluaa erotuksen, muuten ei ole erotusta.
 
Lähinnä meinasin että itse tyytyisin siihen että AD puoli ei suoraan tule mikrokonnttollerille vaan i2c väylällä joten tuskin sieltä 12v puolelta sellaista virtapiikkiä tulee joka menisi sen AD piiri. Läpi i2c väylään.ja totta i2c väylään saa lisää kompolla sen todellisen erotuksen.
 
Tuossa kun pitää kuitenkin olla aika iso vastusjako niin suojadiodit maahan ja käyttöjännitteeseen pitäisi kyllä riittää.
Itse en lähtisi tekemään tuohon erotuksia kun ei oikeastaan ole tarpeen.
Powerille laittaisin varmaan eteen diodit ja LC suotimen.
 
Kehtaanko edes rustata tästä... mutta siis eilen laitoin sen uuden regun paikalleen. Niin testivirtalähteessäni (perus verkkomuuntaja useilla eri jännitteillä) on plussakaapeli punainen ja miinus musta. Niin kuin tavallisesti. Kytkin sen korttiin ja panin virrat päälle. Mitään ei tapahtunut, virtoja ei tullut korttiin.

Virtalähteessä on myös napaisuuden kääntökatkaisin ja oletin varmana, etten aiemmin nyt ole sitä vääntänyt väärään asentoon suhteessa johtojen väriin. Haettuani yleismittarin, niin se vaan oli, että napaisuus oli väärin päin. Väänsin katkaisinta ja löin virtalähteen takaisin seinään. Virrat tulivat päälle ja näyttö alustui, odotustila (WAITING) tuli näkyviin näytölle ja jännitenäyttö näkyi.

Onni onnettomuudessa - mitään ei tainnut hajota. Toisaalta mietin, mikseivät esimerkiksi elkot räjähtäneet tai muuta vastaavaa?

Opimme tästä ainakin sen, että aina pitää napaisuus tarkistaa, vaikka johtojen väri tuollaisessa tilanteessa passaisikin.
 
Joo... nyt tuli tuo servokin Matkiksella, mutta eihän siinä ollut mitään vipuja mukana. Eikä edellisen servon vipu siihen sovi aavistuksen verran liian pienellä boorilla. Kyllä on aika rasittavaa, että joutuu useilla postitilauksilla yksittäisiä juttuja tilailemaan. Varmaan pelkästään postikuluihin on jo mennyt 50-100 euroa tämän prokiksen aikana. No mutta, eipä siinä.

Traxxas 2056. Luultavasti nyt sitten tuossa on tällainen 25T -boori. Servovipuja, alumiinisia lähinnä noista nettipuljuista jotain löytyy, että pitää katsella, jos sellaisen tilaa. Sinänsä ei tarvitsisi kuin muutaman kymmenen sentin muovinen vipu, mutta niinkö on ei-oota? Kevyt käyttö kun kyseessä. Itse tuohon on lähes mahdotonta ilman 3d-tulostamista alkaa jotain kunnollista kiinnitystä tekemään.

Ei muuten löytynyt tietoa, millaisella ruuvilla tuo vipu kiinnitetään servoon? Onko sekin itsepureutuva ruuvi kuten mikroservoissa? Jos on, mitä kokoa? Ei äkkiseltään osunut silmään servoja myyvissä liikkeissä. Ruuvi taas sitten pitäisi tilata eri kaupasta. :eek:

Tässä varmaan olisi sopiva vipu: SERVON VARSI 25T 5CM - PARTCO
 
Viimeksi muokattu:
Jos akseli on metallia niin sitten M3, muuten "se toinen".

Täältäkin voisi löytyä vipuja: Servovivut - Harrastekauppa Hobbylinna
Ja ehkä kysymällä voisi löytyä se ruuvikin...
Suosittelen kysymään postilla/puhelimella, todennäköisesti selviää helpoimmalla.

Tulostamalla ei saa toimivaa ja kestävää vipua oikein millään, kyllä se kannattaa ostaa valmiina.

Tällainen muovirimpulakin kestää yllättävän paljon, tuo on kuitenkin vain 5-6kg servo. Varmuuden vuoksi tekisin varmaan isosta pyöreästä koska se on paksumpaa ainetta ja tukevampi.
1755459939111.png



Sinänsä ei tarvitsisi kuin muutaman kymmenen sentin muovinen vipu, mutta niinkö on ei-oota?

Kuinkas paljon sen ryypyn pitää liikkua ?
Servovivun pituus kannattaa valita niin että servosta käyttää koko liikealueen, turvallisesti vaikka 100 astetta ja siitä laskee (tai vaikka piirtää) vivun pituuden tarvittavan liikematkan mukaan.

Eli muutama kymmenen milliä tai vaan muutama sentti on varmaan oikeampi ;)
 
@ississ , mp tällaisesta? Servo Saver - Harrastekauppa Hobbylinna

Melkein himottaa tuo tilata, mutta tuolla on nuo toimitusajat melko pitkiä. Kun on flow päällä, pitäisi saada sormet syyhyten duunata tuota eteenpäin. Mutta ei se nyt totta puhuen varmaan parin viikon päälle ole tuon kanssa.
 
@ississ , mp tällaisesta? Servo Saver - Harrastekauppa Hobbylinna

Melkein himottaa tuo tilata, mutta tuolla on nuo toimitusajat melko pitkiä. Kun on flow päällä, pitäisi saada sormet syyhyten duunata tuota eteenpäin. Mutta ei se nyt totta puhuen varmaan parin viikon päälle ole tuon kanssa.
Ei kai se ryyppy nyt tuollaista tarvitse. Normaali pyörä ja jos kaipaat joustoa tankoon niin vaikka 1...1.5 teräslanka ja siihen sopiva u- mutka matkalle niin ei haittaa vaikka liikealue ei olisikaan ihan kohdalla.

Ne tavarat joita on hyllyssä tulee nopeasti, muuten ruotsista päätoimittajalta
 
@ississ , no niinpäs näkyy, just sellaisia ei ole hyllyssä joita tarvisi.

Eihän tuossa tuollaista tarvisi, mutta on halvin.
 
Nyt saatiin tuo servon kampikin paikan päälle. Traxxas 2056 ja siinä on nyt kampi paikallansa.

Toimi "testi-Arduinolla" ihan Arduinon kautta virtaa syöden, toki pikkaisen näyttö himmenteli, koska ottaa sen verran virtaa, mutta ihan takuulla siis toimii tällä perusteella tuossa hässäkässämmekin.

Ainoa, että liikematka jää noin 90 asteeseen 180 asteen sijasta. Ilmeisesti tämänkin pitäisi kuitenkin puoliympyräservo olla, niin lienee ohjauksessa sitten jotain huomioitavaa. Tosiaan rajat on noin 1050-1950 µs tällä hetkellä. Mitään liikettä ei tapahdu noiden ulkopuolella, eikä servo siis myöskään "juristele" ollenkaan noiden ulkopuolella.

EDIT: Otetaas takassiin päin, eli siis ilmeisesti on 60 asteen servo. Eipä se haittaa, kyllä tuossa liikematka riittää tähän käyttöön ihan kivasti. Traxxas 2056 Servo Specs and Reviews

Tässä on myös tuo pulse cycle (2 ms) eri kuin SG90-mikroservossa (20 ms). Traxxaksessa olisi tuo ohjausalue tuon databasen mukaan 858-1670 µs kun taas SG90:ssä 500-2400 µs. Lieneekö tällä sitten käytännön toimintaan vaikutusta, koska kuitenkin toimi testissä ihan ok?
 
Viimeksi muokattu:
Nyt saatiin tuo servon kampikin paikan päälle. Traxxas 2056 ja siinä on nyt kampi paikallansa.

Toimi "testi-Arduinolla" ihan Arduinon kautta virtaa syöden, toki pikkaisen näyttö himmenteli, koska ottaa sen verran virtaa, mutta ihan takuulla siis toimii tällä perusteella tuossa hässäkässämmekin.

Ainoa, että liikematka jää noin 90 asteeseen 180 asteen sijasta. Ilmeisesti tämänkin pitäisi kuitenkin puoliympyräservo olla, niin lienee ohjauksessa sitten jotain huomioitavaa. Tosiaan rajat on noin 1050-1950 µs tällä hetkellä. Mitään liikettä ei tapahdu noiden ulkopuolella, eikä servo siis myöskään "juristele" ollenkaan noiden ulkopuolella.

EDIT: Otetaas takassiin päin, eli siis ilmeisesti on 60 asteen servo. Eipä se haittaa, kyllä tuossa liikematka riittää tähän käyttöön ihan kivasti. Traxxas 2056 Servo Specs and Reviews

Tässä on myös tuo pulse cycle (2 ms) eri kuin SG90-mikroservossa (20 ms). Traxxaksessa olisi tuo ohjausalue tuon databasen mukaan 858-1670 µs kun taas SG90:ssä 500-2400 µs. Lieneekö tällä sitten käytännön toimintaan vaikutusta, koska kuitenkin toimi testissä ihan ok?
Suurin osa rc- servoista on noin 90 astetta (1-2ms välillä). Suuremmat kulmat on harvinaisia ja pienempiäkin löytyy.

Ohjauspulssin pituus vaikuttaa yleensä siihen mihin asti liikkuu ja tietysti keskikohta on puilivälissä.

Säädä ohjaus niin että ei mene rajojen yli ja sille liikematkalle sopiva mekaniikka. Ohjelmassa sitten hienosäätö jos mekaniikka vaatii.
 
No niin, nyt saatiin taas kasattua koko hässäkkä. Ja eihän se ihan toiminut, ei päästy ihan testaamaan. Ryyppy ei toimi, eli siis servo ei liiku.

Servo oli kyllä kytketty väärään napaan ( :rofl: ), mutta vaikka se nyt varmasti on oikein ja johdotkin ovat kunnossa, silti ei inahdakaan. Eli vika löytyy 90 prosentin varmuudella koodista.

Se taas selvisi oskilloskoopin avulla. Alla olevassa kuvassa tuo aaltomuoto on testi-Arduinon testikytkennästä, millä siis Traxxaksen servo toimii amerikkalaisittain "flawless". Kun mittasin servon karvasta laturihässäkästä, niin samantapainen aaltomuoto vilahtaa näytöllä, mutta katoaa välittömästi, kun starttirele vetää. Aaltomuoto ei palaudu myöskään enää missään vaiheessa. Eli jokin tuossa koodissa on nyt ristiriidassa niin, ettei servomoottoria ohjatakaan oikein.

Aikanaan simulaattorissa testattuna tuo servokoodi kyllä toimi ok.

EDIT: Voisko olla, tekoäly ehdottaa, että pin 3 ja 11 käyttävät Timer2:ta. Ja kun pin 3:ssa oleva starttirele vetää, tuo PWM nollautuu ja pysyy toki siinä? Enpä osaa arvioida asiaa. Tekoäly kuitenkin muokkasi hieman noita servon aika-asetuksia, niin katotaan huomenna, toimiiko. Toinen ratkaisuehdotus oli, että vaihtaa servon pin 11:n sijaan pin 7:ään. Se menisi juotoshommiksi.

Alla kuvat ja tämänhetkinen koodi, jos kiinnostaa ihmetellä.

DSC_1361.jpg DSC_1360.jpgDSC_1359.jpg

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 1465      // Fully engaged position (microseconds) // Servo, connect to the 3rd outermost hole of the servo arm, Traxxas 2056: min 1058
#define CHOKE_OFF_US 1800     // Retracted position (microseconds), Traxxas: max 1950
#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, 57, 109, 161 };  // 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) {
  uint8_t currentState = (PIND >> PD5) & 1;  // Read state of D5 (PD5)

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

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 <= 790) {  // 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 > 790) {  // 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 >= 7000) && (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.print(startInterval);
  Serial.print(", Choke: ");
  Serial.println(isChokeEngaged);
}
 
Viimeksi muokattu:
No niin, nyt saatiin taas kasattua koko hässäkkä. Ja eihän se ihan toiminut, ei päästy ihan testaamaan. Ryyppy ei toimi, eli siis servo ei liiku.

Servo oli kyllä kytketty väärään napaan ( :rofl: ), mutta vaikka se nyt varmasti on oikein ja johdotkin ovat kunnossa, silti ei inahdakaan. Eli vika löytyy 90 prosentin varmuudella koodista.

Se taas selvisi oskilloskoopin avulla. Alla olevassa kuvassa tuo aaltomuoto on testi-Arduinon testikytkennästä, millä siis Traxxaksen servo toimii amerikkalaisittain "flawless". Kun mittasin servon karvasta laturihässäkästä, niin samantapainen aaltomuoto vilahtaa näytöllä, mutta katoaa välittömästi, kun starttirele vetää. Aaltomuoto ei palaudu myöskään enää missään vaiheessa. Eli jokin tuossa koodissa on nyt ristiriidassa niin, ettei servomoottoria ohjatakaan oikein.

Aikanaan simulaattorissa testattuna tuo servokoodi kyllä toimi ok.

EDIT: Voisko olla, tekoäly ehdottaa, että pin 3 ja 11 käyttävät Timer2:ta. Ja kun pin 3:ssa oleva starttirele vetää, tuo PWM nollautuu ja pysyy toki siinä? Enpä osaa arvioida asiaa. Tekoäly kuitenkin muokkasi hieman noita servon aika-asetuksia, niin katotaan huomenna, toimiiko. Toinen ratkaisuehdotus oli, että vaihtaa servon pin 11:n sijaan pin 7:ään. Se menisi juotoshommiksi.

Joo, pinnit 3 ja 11 on mahdollista kytkeä timer2 lähdöiksi.

Jos timerin asetuksissa tuota kytkentää ei ole tehty niin timer ei vaikuta pinneihin eikä niiden heiluttelu timeriin.

Jos timeriin kytketään vaikka pinni 11 ja laitetaan pwm päälle niin sen pitäisi toimia. Jos sitten käsin asettaa pinnin vaikka ylös niin sen pitäisi jatkaa oikeaa pwmää seuraavalla timerin asettamalla tilanvaihdolla.

Tietysti nyt kun käytetään arduinokirjastoa niin muistaakseni digitalOut() kääntää pinniltä pois kaikki muut toiminnot ja asettaa tilan.
Eli tarkasta koodista että missään ei aseteta servon pwm- pinniä muuten kuin output- tilaan. Ja sekin pitää tarkastaa tuosta servokirjastosta, jos kirjasto tekee sen niin sitten ei pidä koskea ollenkaan kyseiseen pinniin omassa koodissa muuten kuin kertomalla se servokirjastolle.

Toinen vaihtoehto on se että 2 eri kirjastoa käyttää timer2:sta ja siksi menee solmuun.
 
@ississ , mitäs tämä tarkoittaa (ServoTimer2:n koodista pastettu):
Note that analogWrite of PWM on pins 3 and 11 is disabled when the first servo is attached

Jos en ihan väärin ymmärrä, niin ongelma on juuri tuossa. Saattaisi helpoin konsti jopa olla releen kytkeminen pinniin 7.

EDIT: Eikun otetaan vähän takaisin. Siis analogWrite tarkoittaa juuri PWM:ää, kirjoitetaan PWM -arvo pinniin. Ei siis pitäisi vaikuttaa on-off (digitalWrite) -toimintaan, jollen ole ihan väärässä. :O
 
Viimeksi muokattu:
@ississ , mitäs tämä tarkoittaa (ServoTimer2:n koodista pastettu):


Jos en ihan väärin ymmärrä, niin ongelma on juuri tuossa. Saattaisi helpoin konsti jopa olla releen kytkeminen pinniin 7.

EDIT: Eikun otetaan vähän takaisin. Siis analogWrite tarkoittaa juuri PWM:ää, kirjoitetaan PWM -arvo pinniin. Ei siis pitäisi vaikuttaa on-off (digitalWrite) -toimintaan, jollen ole ihan väärässä. :O
Joo analogwrite = pwm eli digitalwrite ei pitäisi vaikuttaa mitenkään.

Pitäisi katsoa mitä siellä oikein on tehty että tietäisi mitä kommentti oikeasti tarkoittaa.

En enää muista missä timerien alustukset tehdään, todennäköisesti analogwrite pinnin perusteella.
 
@ississ , tuossa on tuota ServoTimer2:n koodia (v2 tulee omista muutoksista, laajennettiin se pulssialue 544-2400).

Pitänee tehdä se hardwaremuutos, jos koodillisesti homman korjaaminen osoittautuu liian haasteelliseksi. Kytkisi sen starttireleen tuonne pinnin 7, jolloin ei ainakaan pitäisi mitään ristiriitoja olla. Löysin ulkomaan foorumilta jotain tietoa, että jollakin oli joku rc-homma ollut, niin ServoTimer2-ohjaus servolle ei toiminut lainkaan, kun se rc-tms. kirjasto oli koodissa. Jotain etäisesti vastaavaahan saattaisi tässäkin ehkä olla. :)

C-like:
/*
  ServoTimer2_v2.h - Interrupt driven Servo library for Arduino using Timer2_v2- Version 0.1
  Copyright (c) 2008 Michael Margolis.  All right reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

// Updated for Arduino 1.x by Nick Bontrager 2013

/*
  This library uses Timer2 to drive up to 8 servos using interrupts so no refresh activity is required from within the sketch.
  The usage and method naming is similar to the Arduino software servo library http://www.arduino.cc/playground/ComponentLib/Servo
  except that pulse widths can be in microseconds or degrees.
  write() treats parameters of 180 or less as degrees, otherwise values are milliseconds.
 
 
  A servo is activated by creating an instance of the Servo class passing the desired pin to the attach() method.
  The servo is pulsed in the background to the value most recently written using the write() method

  Note that analogWrite of PWM on pins 3 and 11 is disabled when the first servo is attached

  The methods are:

   ServoTimer2_v2 - Class for manipulating servo motors connected to Arduino pins.



   attach(pin )  - Attaches a servo motor to an i/o pin.

   attach(pin, min, max  ) - Attaches to a pin setting min and max values in microseconds

    default min is 544, max is 2400

 

   write()     - Sets the servo pulse width in microseconds.



   read()      - Gets the last written servo pulse width in microseconds.



   attached()  - Returns true if there is a servo attached.



   detach()    - Stops an attached servos from pulsing its i/o pin.
 


The library takes about 824 bytes of program memory and 32+(1*servos) bytes of SRAM.

The pulse width timing is accurate to within 1%


 */

// ensure this library description is only included once
#ifndef ServoTimer2_v2_h
#define ServoTimer2_v2_h

#include <inttypes.h>
//typedef uint8_t boolean;
//typedef uint8_t byte;

#define MIN_PULSE_WIDTH       544        // the shortest pulse sent to a servo

#define MAX_PULSE_WIDTH      2400        // the longest pulse sent to a servo

#define DEFAULT_PULSE_WIDTH  1500        // default pulse width when servo is attached

#define FRAME_SYNC_PERIOD   20000        // total frame duration in microseconds

#define NBR_CHANNELS 8                   // the maximum number of channels, don't change this

typedef struct  {

      uint8_t nbr        :5 ;  // a pin number from 0 to 31

      uint8_t isActive   :1 ;  // false if this channel not enabled, pin only pulsed if true

   } ServoPin_t   ;



typedef struct {

  ServoPin_t Pin;

  byte counter;

  byte remainder;

}  servo_t;

class ServoTimer2_v2
{
  public:
    // constructor:
    ServoTimer2_v2();

    uint8_t attach(int);     // attach the given pin to the next free channel, sets pinMode, returns channel number or 0 if failure
                             // the attached servo is pulsed with the current pulse width value, (see the write method)
    uint8_t attach(int, int, int); // as above but also sets min and max values for writes.
    void detach();
    void write(int);         // store the pulse width in microseconds (between MIN_PULSE_WIDTH and MAX_PULSE_WIDTH)for this channel
    int read();              // returns current pulse width in microseconds for this servo
    boolean attached();      // return true if this servo is attached
 private:
     uint8_t chanIndex;      // index into the channel data for this servo

};


// the following ServoArrayT2 class is not implemented in the first version of this library
class ServoArrayT2
{
  public:
    // constructor:
    ServoArrayT2();

    uint8_t attach(int);     // attach the given pin to the next free channel, sets pinMode, returns channel number or 0 if failure
                             // channels are assigned consecutively starting from 1
                             // the attached servo is pulsed with the current pulse width value, (see the write method)
    void detach(int);        // detach the servo on the given channel
    void write(int,int);     // store the pulse width in microseconds (between MIN_PULSE_WIDTH and MAX_PULSE_WIDTH)for the given channel
    int read(int);              // returns current pulse width in microseconds for the given channel
    boolean attached(int);   // return true if the servo on the given channel is attached
 private:
     uint8_t chanIndex;      // index into the channel data for this servo

};

#endif

C-like:
extern "C" {
  // AVR LibC Includes
  #include <inttypes.h>
  #include <avr/interrupt.h>
  // #include "WConstants.h"
}
#include <Arduino.h>
#include "ServoTimer2_v2.h"
static void initISR();   
static void writeChan(uint8_t chan, int pulsewidth);

#define FRAME_SYNC_INDEX   0         // frame sync delay is the first entry in the channel array
#define FRAME_SYNC_PERIOD  20000       // total frame duration in microseconds
#define FRAME_SYNC_DELAY   ((FRAME_SYNC_PERIOD - ( NBR_CHANNELS * DEFAULT_PULSE_WIDTH))/ 128) // number of iterations of the ISR to get the desired frame rate
#define DELAY_ADJUST     8         // number of microseconds of calculation overhead to be subtracted from pulse timings   

static servo_t servos[NBR_CHANNELS+1];    // static array holding servo data for all channels

static volatile uint8_t Channel;   // counter holding the channel being pulsed
static volatile uint8_t ISRCount;  // iteration counter used in the interrupt routines;
uint8_t ChannelCount = 0;        // counter holding the number of attached channels
static boolean isStarted = false;  // flag to indicate if the ISR has been initialised

ISR (TIMER2_OVF_vect)
{
  ++ISRCount; // increment the overlflow counter
  if (ISRCount ==  servos[Channel].counter ) // are we on the final iteration for this channel
  {
    TCNT2 =  servos[Channel].remainder;   // yes, set count for overflow after remainder ticks
  } 
  else if(ISRCount >  servos[Channel].counter) 
  {
    // we have finished timing the channel so pulse it low and move on
    if(servos[Channel].Pin.isActive == true)         // check if activated
        digitalWrite( servos[Channel].Pin.nbr,LOW); // pulse this channel low if active   

      Channel++;    // increment to the next channel
    ISRCount = 0; // reset the isr iteration counter
    TCNT2 = 0;    // reset the clock counter register
    if( (Channel != FRAME_SYNC_INDEX) && (Channel <= NBR_CHANNELS) ){         // check if we need to pulse this channel   
        if(servos[Channel].Pin.isActive == true)       // check if activated
         digitalWrite( servos[Channel].Pin.nbr,HIGH); // its an active channel so pulse it high   
    }
    else if(Channel > NBR_CHANNELS){
       Channel = 0; // all done so start over           
    }
   } 
}

ServoTimer2_v2::ServoTimer2_v2()
{
   if( ChannelCount < NBR_CHANNELS) 
    this->chanIndex = ++ChannelCount;  // assign a channel number to this instance
   else
    this->chanIndex = 0;  // todo    // too many channels, assigning 0 inhibits this instance from functioning
}

uint8_t ServoTimer2_v2::attach(int pin)
{
    if( isStarted == false)
     initISR();   
    if(this->chanIndex > 0) 
    {
     //debug("attaching chan = ", chanIndex);
     pinMode( pin, OUTPUT) ;  // set servo pin to output
     servos[this->chanIndex].Pin.nbr = pin; 
     servos[this->chanIndex].Pin.isActive = true; 
    }
    return this->chanIndex ;
}

void ServoTimer2_v2::detach() 
{
    servos[this->chanIndex].Pin.isActive = false; 
}

void ServoTimer2_v2::write(int pulsewidth)
{   
   writeChan(this->chanIndex, pulsewidth); // call the static function to store the data for this servo       
}

int ServoTimer2_v2::read()
{
  unsigned int pulsewidth;
   if( this->chanIndex > 0)
    pulsewidth =  servos[this->chanIndex].counter * 128 + ((255 - servos[this->chanIndex].remainder) / 2) + DELAY_ADJUST ;
   else
     pulsewidth  = 0;
   return pulsewidth;   
}
 
boolean ServoTimer2_v2::attached()
{
    return servos[this->chanIndex].Pin.isActive ;
}

static void writeChan(uint8_t chan, int pulsewidth)
{
   // calculate and store the values for the given channel
   if( (chan > 0) && (chan <= NBR_CHANNELS) )   // ensure channel is valid
   {
    if( pulsewidth < MIN_PULSE_WIDTH )            // ensure pulse width is valid
        pulsewidth = MIN_PULSE_WIDTH;
    else if( pulsewidth > MAX_PULSE_WIDTH )
        pulsewidth = MAX_PULSE_WIDTH;     
    
      pulsewidth -=DELAY_ADJUST;             // subtract the time it takes to process the start and end pulses (mostly from digitalWrite)
    servos[chan].counter = pulsewidth / 128;   
    servos[chan].remainder = 255 - (2 * (pulsewidth - ( servos[chan].counter * 128)));  // the number of 0.5us ticks for timer overflow       
   }
}

static void initISR()
{   
    for(uint8_t i=1; i <= NBR_CHANNELS; i++) {  // channels start from 1   
       writeChan(i, DEFAULT_PULSE_WIDTH);  // store default values       
    }
    servos[FRAME_SYNC_INDEX].counter = FRAME_SYNC_DELAY;   // store the frame sync period     

    Channel = 0;  // clear the channel index 
    ISRCount = 0;  // clear the value of the ISR counter;
    
    /* setup for timer 2 */
    TIMSK2 = 0;  // disable interrupts
    TCCR2A = 0;  // normal counting mode
    TCCR2B = _BV(CS21); // set prescaler of 8
    TCNT2 = 0;     // clear the timer2 count
    TIFR2 = _BV(TOV2);  // clear pending interrupts;
    TIMSK2 =  _BV(TOIE2) ; // enable the overflow interrupt     
      
    isStarted = true;  // flag to indicate this initialisation code has been executed
}
 
Eipä tuossa pikaisesti vilkaisten mitenkään vaikuteta analogwrite toimintaan. Ehkä kommentti pitäisi olla että älä käytä noiden pinnien kanssa jos servokirjasto on käytössä...

Jos mikään muu koodissa ei käytä timer2 niin sitten jostain pitäisi löytyä joku kirjoitusvirhe/vastaava jolloin vahingossa kirjoitettaisiin samoihin pinneihin, sammutetaan timer2 tai irrotetaan pinni(t) timer2:sta.
 
@ississ joo, minun mielestä ei nyt minkään pitäisi käyttää Timer2:ta, koska tämän mahdollisen ristiriitatilanteen takia otettiin tuo ServoTimer2-kirjasto. Silloin, kun tuo oli perus servo-kirjastolla, ei toiminut PWM oikein. Pitää yrittää vielä katsella tuota koodia, jos sieltä jotain löytyisi. :)
 

Statistiikka

Viestiketjuista
286 353
Viestejä
4 920 691
Jäsenet
79 167
Uusin jäsen
igrvks

Hinta.fi

Back
Ylös Bottom