Skip to content

Instantly share code, notes, and snippets.

@philipmorg
Last active July 10, 2025 15:33
Show Gist options
  • Select an option

  • Save philipmorg/93907949fc039166434b6abfe5be7b7e to your computer and use it in GitHub Desktop.

Select an option

Save philipmorg/93907949fc039166434b6abfe5be7b7e to your computer and use it in GitHub Desktop.
Arduino sewing machine motor controller
#include <TimerOne.h>
// Pin Definitions
const int PWM_PIN = 9; // PWM output
const int PRESSURE_OUT_PIN = 2; // HX710 DOUT pin
const int PRESSURE_SCK_PIN = 5; // HX710 SCK pin
// PWM Configuration
const int PWM_PERIOD = 50; // 50 microseconds = 20kHz
const int PWM_MIN = 2; // Minimum effective PWM value
const int PWM_MAX = 40; // Maximum PWM value
// Pressure Sensor Parameters
const int READINGS_COUNT = 5; // Number of readings to average.
const long RAW_MIN = -2690000;
const long RAW_MAX = 8388607;
const float PRESSURE_SMOOTHING = 0.1; // Exponential smoothing factor.
// Variables for averaging readings
long pressureReadings[READINGS_COUNT];
int readIndex = 0;
long pressureTotal = 0;
long pressureAverage = 0;
float smoothedPressure = 0;
// --- CONTROL PARAMETERS (using RAW HX710 values) ---
const long DEAD_ZONE_MAX = -2610000;
const long LOW_SPEED_MAX = 600000;
const long MED_SPEED_MAX = 4000000;
// PWM values for constant speed ranges
const int LOW_SPEED_PWM = 8;
const int MED_SPEED_PWM = 12;
const int MAX_PROPORTIONAL_PWM = 24;
// HX710 reading function
long readHX710() {
while (digitalRead(PRESSURE_OUT_PIN) == HIGH);
unsigned long result = 0;
for (int i = 0; i < 24; ++i) {
digitalWrite(PRESSURE_SCK_PIN, HIGH);
digitalWrite(PRESSURE_SCK_PIN, LOW);
result = (result << 1) | digitalRead(PRESSURE_OUT_PIN);
}
digitalWrite(PRESSURE_SCK_PIN, HIGH);
digitalWrite(PRESSURE_SCK_PIN, LOW);
digitalWrite(PRESSURE_SCK_PIN, HIGH);
digitalWrite(PRESSURE_SCK_PIN, LOW);
digitalWrite(PRESSURE_SCK_PIN, HIGH);
digitalWrite(PRESSURE_SCK_PIN, LOW);
if (result & 0x800000) {
result |= 0xFF000000;
}
return result;
}
// --- PWM CALCULATION (Corrected Proportional Transition) ---
int calculatePWM(long pressure) {
if (pressure <= DEAD_ZONE_MAX) {
return 0;
} else if (pressure <= LOW_SPEED_MAX) {
return LOW_SPEED_PWM;
} else if (pressure <= MED_SPEED_MAX) {
return MED_SPEED_PWM;
} else {
// Map from MED_SPEED_MAX to RAW_MAX, starting from MED_SPEED_PWM
float mappedPWM = map(float(pressure), float(MED_SPEED_MAX), float(RAW_MAX), float(MED_SPEED_PWM), float(MAX_PROPORTIONAL_PWM));
return constrain(int(mappedPWM), 0, MAX_PROPORTIONAL_PWM); // Still constrain for safety
}
}
void setup() {
Serial.begin(115200);
Serial.println("Pressure-Controlled PWM - Enhanced Diagnostics");
pinMode(PRESSURE_OUT_PIN, INPUT);
pinMode(PRESSURE_SCK_PIN, OUTPUT);
pinMode(PWM_PIN, OUTPUT);
digitalWrite(PRESSURE_SCK_PIN, LOW);
Timer1.initialize(PWM_PERIOD);
Timer1.pwm(PWM_PIN, 0);
long initialReading = readHX710();
for (int i = 0; i < READINGS_COUNT; i++) {
pressureReadings[i] = initialReading;
}
pressureTotal = initialReading * READINGS_COUNT;
pressureAverage = initialReading;
smoothedPressure = (float)initialReading;
}
void loop() {
long rawReading = readHX710();
pressureTotal = pressureTotal - pressureReadings[readIndex];
pressureReadings[readIndex] = rawReading;
pressureTotal = pressureTotal + rawReading;
readIndex = (readIndex + 1) % READINGS_COUNT;
pressureAverage = pressureTotal / READINGS_COUNT;
smoothedPressure = smoothedPressure + (rawReading - smoothedPressure) * PRESSURE_SMOOTHING;
int pwmValue = calculatePWM((long)smoothedPressure);
Timer1.pwm(PWM_PIN, pwmValue);
// --- ENHANCED DIAGNOSTIC OUTPUT ---
Serial.print("Raw:");
Serial.print(rawReading);
Serial.print(",Avg:");
Serial.print(pressureAverage);
Serial.print(",Smooth:");
Serial.print(smoothedPressure);
Serial.print(",PWM:");
Serial.print(pwmValue);
Serial.print(",PWM%: ");
Serial.print(map(pwmValue, 0, PWM_MAX, 0, 100));
Serial.print(",Range:");
if (smoothedPressure <= DEAD_ZONE_MAX) {
Serial.print("Dead Zone");
} else if (smoothedPressure <= LOW_SPEED_MAX) {
Serial.print("Low Constant");
} else if (smoothedPressure <= MED_SPEED_MAX) {
Serial.print("Med Constant");
} else {
Serial.print("Proportional");
}
Serial.print(",Thresh: ");
Serial.print(DEAD_ZONE_MAX);
Serial.print(",");
Serial.print(LOW_SPEED_MAX);
Serial.print(",");
Serial.print(MED_SPEED_MAX);
Serial.print(",");
Serial.println(RAW_MAX);
delay(10);
}

Okay, let's integrate the "kick start" feature and ensure swift stopping.

Here's the modified code with explanations:

  1. Kick Start Feature Added:

    • New constants KICK_START_PWM and KICK_START_DURATION are added at the top for easy configuration.
    • State variables isKicking, kickStartTime, and previousPwmValue are introduced to manage the kick start logic.
    • The loop() now checks if the motor was previously stopped (previousPwmValue == 0) and is now commanded to start (targetPwmValue > 0). If so, it initiates the kick.
    • During the kick duration, the KICK_START_PWM value overrides the normally calculated PWM.
    • After the duration expires, the control reverts to the calculatePWM output.
  2. Swift Stopping Enhancement:

    • The primary factor slowing down the stop was the delay(10); at the end of the loop(). Removing this allows the code to detect the pressure drop into the dead zone and set the PWM to 0 much more quickly.
    • The averaging and smoothing (READINGS_COUNT, PRESSURE_SMOOTHING) inherently introduce a small lag. Reducing READINGS_COUNT or increasing PRESSURE_SMOOTHING (e.g., to 0.15 or 0.2) would make it react faster, but potentially make the control more jittery or sensitive to noise. The current values are often a reasonable starting compromise, but you can tune them if stopping is still not fast enough. Removing the explicit delay() is the most significant improvement for stopping speed without sacrificing stability as much.
#include <TimerOne.h>
#include <Arduino.h> // Include standard Arduino functions like millis()

// Pin Definitions
const int PWM_PIN = 9;          // PWM output pin for Timer1
const int PRESSURE_OUT_PIN = 2;  // HX710 DOUT pin (data out)
const int PRESSURE_SCK_PIN = 5;  // HX710 SCK pin (clock)

// PWM Configuration
const int PWM_PERIOD = 50;      // PWM period in microseconds (50us = 20kHz frequency)
                                // Timer1 uses 0-1023 for duty cycle resolution

// Pressure Sensor Parameters
const int READINGS_COUNT = 2;   // Number of readings for moving average filter.
const long RAW_MIN = -2690000;  // Approximate minimum raw value observed from HX710
const long RAW_MAX = 8388607;   // Maximum possible 24-bit signed value from HX710
const float PRESSURE_SMOOTHING = 0.7; // Exponential smoothing factor (0-1). Higher = less smoothing, faster reaction.

// Variables for sensor reading filters
long pressureReadings[READINGS_COUNT]; // Array for moving average
int readIndex = 0;                     // Current index in the moving average array
long pressureTotal = 0;                // Sum for moving average calculation
long pressureAverage = 0;              // Result of moving average
float smoothedPressure = 0;            // Result of exponential smoothing

// --- CONTROL PARAMETERS (using RAW HX710 pressure values) ---
const long DEAD_ZONE_MAX = -2610000; // Pressure below this threshold = motor off
const long LOW_SPEED_MAX = 600000;   // Pressure threshold for low speed band
const long MED_SPEED_MAX = 4000000;  // Pressure threshold for medium speed band
                                     // Pressure above MED_SPEED_MAX enters proportional speed range

// --- DIRECT PWM DUTY CYCLE VALUES (0-1023 range for Timer1) ---
// These values are now intended to be sent DIRECTLY to Timer1.pwm()
const int LOW_SPEED_PWM = 8;   // Direct Timer1 duty cycle for low speed
const int MED_SPEED_PWM = 12;  // Direct Timer1 duty cycle for medium speed
const int MAX_PROPORTIONAL_PWM = 24; // Max direct Timer1 duty cycle reached at RAW_MAX pressure

// --- KICK START CONFIGURATION ---
// Set the kick PWM slightly higher than LOW_SPEED_PWM, using the same direct scale
const int KICK_START_PWM = 18; // Direct Timer1 duty cycle during kick (e.g., 10 out of 1023)
// Duration can be tuned. Start short.
const unsigned long KICK_START_DURATION = 60;  // Duration of the kick start pulse in milliseconds (try slightly longer?)

// --- State Variables for Kick Start Logic ---
bool isKicking = false;         // Flag to indicate if the kick start is currently active
unsigned long kickStartTime = 0; // Timestamp (from millis()) when the kick started
int previousPwmValue = 0;      // Stores the *target* PWM value from the previous loop iteration

// --- HX710 Reading Function ---
// Reads a 24-bit value from the HX710 load cell amplifier
long readHX710() {
    // Wait for the DOUT pin to go low, indicating data is ready
    unsigned long timeout_start = millis();
    while (digitalRead(PRESSURE_OUT_PIN) == HIGH) {
        if (millis() - timeout_start > 100) { // Example timeout: 100ms
           Serial.println("HX710 Timeout!");
           return (long)smoothedPressure; // Return last known good value or a default
        }
    }

    unsigned long value = 0;
    // Clock out the 24 data bits (MSB first)
    for (int i = 0; i < 24; i++) {
        digitalWrite(PRESSURE_SCK_PIN, HIGH); // Clock pulse up
        value = value << 1;                  // Shift current value left
        digitalWrite(PRESSURE_SCK_PIN, LOW);  // Clock pulse down
        if (digitalRead(PRESSURE_OUT_PIN) == HIGH) {
            value++; // Set the least significant bit if DOUT is high
        }
    }

    // Set gain for next reading (1 pulse = Gain 128)
    digitalWrite(PRESSURE_SCK_PIN, HIGH);
    digitalWrite(PRESSURE_SCK_PIN, LOW);

    // Convert to 32-bit signed long (2's complement)
    if (value & 0x800000) {
        value |= 0xFF000000;
    }
    return static_cast<long>(value);
}


// --- PWM CALCULATION ---
// Calculates the *target* DIRECT Timer1 PWM duty cycle based on pressure, BEFORE applying kick start.
int calculateTargetPWM(long pressure) {
    if (pressure <= DEAD_ZONE_MAX) {
        return 0; // Motor off (0 duty cycle)
    } else if (pressure <= LOW_SPEED_MAX) {
        return LOW_SPEED_PWM; // Low constant speed duty cycle (e.g., 8)
    } else if (pressure <= MED_SPEED_MAX) {
        return MED_SPEED_PWM; // Medium constant speed duty cycle (e.g., 12)
    } else {
        // Proportional speed range: Map pressure linearly from MED_SPEED_MAX->RAW_MAX
        // to the DIRECT duty cycle range MED_SPEED_PWM->MAX_PROPORTIONAL_PWM.
        // Use float casting for map calculation.
        // IMPORTANT: The output range is now the DIRECT low duty cycle values (12 to 24)
        float mappedPWM = map(float(pressure), float(MED_SPEED_MAX), float(RAW_MAX), float(MED_SPEED_PWM), float(MAX_PROPORTIONAL_PWM));

        // Constrain the result to be within the allowed proportional range (0 to MAX_PROPORTIONAL_PWM)
        return constrain(int(mappedPWM), 0, MAX_PROPORTIONAL_PWM);
    }
}

// --- SETUP ---
void setup() {
    Serial.begin(115200);
    while (!Serial);
    Serial.println("Pressure-Controlled PWM w/ Kick Start (Direct Duty Cycle)");

    pinMode(PRESSURE_OUT_PIN, INPUT);
    pinMode(PRESSURE_SCK_PIN, OUTPUT);
    pinMode(PWM_PIN, OUTPUT);
    digitalWrite(PRESSURE_SCK_PIN, LOW);

    Timer1.initialize(PWM_PERIOD); // Set PWM frequency
    Timer1.pwm(PWM_PIN, 0);        // Start motor off

    Serial.println("Waiting for HX710 stabilization...");
    delay(500);
    Serial.println("Reading initial pressure...");
    long initialReading = readHX710();
    Serial.print("Initial Raw Reading: "); Serial.println(initialReading);

    for (int i = 0; i < READINGS_COUNT; i++) {
        pressureReadings[i] = initialReading;
    }
    pressureTotal = initialReading * READINGS_COUNT;
    pressureAverage = initialReading;
    smoothedPressure = (float)initialReading;

    previousPwmValue = 0;
    isKicking = false;

    Serial.println("Setup complete. Starting control loop.");
}

// --- MAIN LOOP ---
void loop() {
    // 1. Read Sensor and Update Filtered Value
    long rawReading = readHX710();

    // Update filters
    pressureTotal = pressureTotal - pressureReadings[readIndex];
    pressureReadings[readIndex] = rawReading;
    pressureTotal = pressureTotal + rawReading;
    readIndex = (readIndex + 1) % READINGS_COUNT;
    pressureAverage = pressureTotal / READINGS_COUNT;
    smoothedPressure = smoothedPressure + (rawReading - smoothedPressure) * PRESSURE_SMOOTHING;

    // 2. Calculate Target DIRECT PWM Duty Cycle
    int targetPwmValue = calculateTargetPWM((long)smoothedPressure);

    // 3. Implement Kick Start Logic
    int finalPwmValue; // This will hold the DIRECT duty cycle value (0-1023) to be used

    if (isKicking) {
        // Kick active. Check duration.
        if (millis() - kickStartTime >= KICK_START_DURATION) {
            // Kick duration over.
            isKicking = false;
            finalPwmValue = targetPwmValue; // Revert to normal target duty cycle
        } else {
            // Kick still active.
            finalPwmValue = KICK_START_PWM; // Use the direct kick duty cycle (e.g., 10)
        }
    } else {
        // Kick not active. Check if need to start.
        if (previousPwmValue == 0 && targetPwmValue > 0) {
            // Start the kick!
            isKicking = true;
            kickStartTime = millis();
            finalPwmValue = KICK_START_PWM; // Use the direct kick duty cycle (e.g., 10)
        } else {
            // Normal operation.
            finalPwmValue = targetPwmValue; // Use the normal target duty cycle
        }
    }

    // 4. Set Motor PWM Duty Cycle
    // *** NO MAPPING NEEDED ***
    // finalPwmValue now directly represents the desired Timer1 duty cycle (0-1023)
    int timer1PwmDuty = finalPwmValue;

    // Ensure the value is within the valid 0-1023 range for Timer1, just in case.
    timer1PwmDuty = constrain(timer1PwmDuty, 0, 1023);

    // Apply the calculated duty cycle to the PWM pin via Timer1
    Timer1.pwm(PWM_PIN, timer1PwmDuty);

    // 5. Update State for Next Loop Iteration
    previousPwmValue = targetPwmValue; // Store the *target* value

    // --- ENHANCED DIAGNOSTIC OUTPUT ---
    Serial.print("Raw:");    Serial.print(rawReading);
    Serial.print(", Avg:");  Serial.print(pressureAverage);
    Serial.print(", Smooth:");Serial.print( (long)smoothedPressure);
    Serial.print(", TargetPWM:"); Serial.print(targetPwmValue); // Target duty cycle
    Serial.print(", FinalPWM(Duty):"); Serial.print(finalPwmValue); // Final duty cycle sent (includes kick)
    // Serial.print(", Duty(0-1023):"); Serial.print(timer1PwmDuty); // Removed, same as FinalPWM now

    Serial.print(", Range:");
    if (smoothedPressure <= DEAD_ZONE_MAX) {
        Serial.print("Dead");
    } else if (smoothedPressure <= LOW_SPEED_MAX) {
        Serial.print("Low");
         if(isKicking) Serial.print("+Kick");
    } else if (smoothedPressure <= MED_SPEED_MAX) {
        Serial.print("Med");
    } else {
        Serial.print("Prop");
    }
    Serial.println(); // End the line

} // end loop

Key Changes Summary:

  1. Kick Start Constants: KICK_START_PWM, KICK_START_DURATION.
  2. Kick Start State Variables: isKicking, kickStartTime, previousPwmValue.
  3. Kick Start Logic: Added within the loop() before setting the final PWM value. It checks for the 0 -> positive PWM transition and applies the kick PWM for the specified duration.
  4. calculateTargetPWM function: Renamed from calculatePWM to clarify it calculates the desired speed before kick start override.
  5. Removed delay(10): This allows the loop to iterate faster, significantly improving the reaction time when the pedal is released and pressure enters the dead zone, leading to a quicker stop.
  6. PWM Mapping Clarification: Added comments and logic (ABSOLUTE_MAX_PWM_FOR_MAPPING, map, constrain) to handle the conversion from your desired PWM intensity values (like 8, 12, 24, KICK_START_PWM) to the 0-1023 range expected by Timer1.pwm(). Adjust the map range (e.g., the 512 value) based on the actual maximum duty cycle you want the motor to reach. If your original values were already scaled for 0-1023, remove this mapping step.
  7. Diagnostic Output: Updated to show both TargetPWM and FinalPWM (which includes the kick) and the final Duty value sent to Timer1.

Remember to adjust the KICK_START_PWM and KICK_START_DURATION values based on your motor's characteristics and how much "kick" you need. You might also need to tune the PRESSURE_SMOOTHING value for the best balance between responsiveness and stability.

#include <TimerOne.h>
#include <Arduino.h> // Include standard Arduino functions like millis()
// Pin Definitions
const int PWM_PIN = 9; // PWM output pin for Timer1
const int PRESSURE_OUT_PIN = 2; // HX710 DOUT pin (data out)
const int PRESSURE_SCK_PIN = 5; // HX710 SCK pin (clock)
// PWM Configuration
const int PWM_PERIOD = 50; // PWM period in microseconds (50us = 20kHz frequency)
// Timer1 uses 0-1023 for duty cycle resolution
// Pressure Sensor Parameters
const int READINGS_COUNT = 2; // Number of readings for moving average filter.
const long RAW_MIN = -2690000; // Approximate minimum raw value observed from HX710
const long RAW_MAX = 8388607; // Maximum possible 24-bit signed value from HX710
const float PRESSURE_SMOOTHING = 0.7; // Exponential smoothing factor (0-1). Higher = less smoothing, faster reaction.
// Variables for sensor reading filters
long pressureReadings[READINGS_COUNT]; // Array for moving average
int readIndex = 0; // Current index in the moving average array
long pressureTotal = 0; // Sum for moving average calculation
long pressureAverage = 0; // Result of moving average
float smoothedPressure = 0; // Result of exponential smoothing
// --- DYNAMIC BASELINE AND CALIBRATION ---
long dynamicBaseline = 0; // Current zero-pressure baseline (updates with temperature drift)
bool isCalibrating = false; // Flag indicating calibration is in progress
const unsigned long CALIBRATION_DURATION = 30000; // 30 second calibration period
const int CALIBRATION_READINGS = 300; // Number of readings during calibration (100ms each = 30s)
long calibrationSum = 0; // Sum of readings during calibration
int calibrationCount = 0; // Count of readings taken during calibration
unsigned long calibrationStartTime = 0; // When calibration started
const unsigned long IDLE_BASELINE_UPDATE_INTERVAL = 5000; // Update baseline every 5 seconds when idle
unsigned long lastBaselineUpdate = 0; // Last time baseline was updated
const float BASELINE_SMOOTHING = 0.05; // Very slow smoothing for baseline updates (temperature drift)
// --- CONTROL PARAMETERS (relative to dynamic baseline) ---
const long DEAD_ZONE_OFFSET = 80000; // Pressure above baseline to exit dead zone
const long LOW_SPEED_OFFSET = 3290000; // Pressure above baseline for low speed band
const long MED_SPEED_OFFSET = 6690000; // Pressure above baseline for medium speed band
// Pressure above MED_SPEED_OFFSET enters proportional speed range
// --- DIRECT PWM DUTY CYCLE VALUES (0-1023 range for Timer1) ---
// These values are now intended to be sent DIRECTLY to Timer1.pwm()
const int LOW_SPEED_PWM = 8; // Direct Timer1 duty cycle for low speed
const int MED_SPEED_PWM = 12; // Direct Timer1 duty cycle for medium speed
const int MAX_PROPORTIONAL_PWM = 24; // Max direct Timer1 duty cycle reached at RAW_MAX pressure
// --- KICK START CONFIGURATION ---
// Set the kick PWM slightly higher than LOW_SPEED_PWM, using the same direct scale
const int KICK_START_PWM = 18; // Direct Timer1 duty cycle during kick (e.g., 10 out of 1023)
// Duration can be tuned. Start short.
const unsigned long KICK_START_DURATION = 60; // Duration of the kick start pulse in milliseconds (try slightly longer?)
// --- State Variables for Kick Start Logic ---
bool isKicking = false; // Flag to indicate if the kick start is currently active
unsigned long kickStartTime = 0; // Timestamp (from millis()) when the kick started
int previousPwmValue = 0; // Stores the *target* PWM value from the previous loop iteration
// --- HX710 Reading Function ---
// Reads a 24-bit value from the HX710 load cell amplifier
long readHX710() {
// Wait for the DOUT pin to go low, indicating data is ready
unsigned long timeout_start = millis();
while (digitalRead(PRESSURE_OUT_PIN) == HIGH) {
if (millis() - timeout_start > 100) { // Example timeout: 100ms
Serial.println("HX710 Timeout!");
return (long)smoothedPressure; // Return last known good value or a default
}
}
unsigned long value = 0;
// Clock out the 24 data bits (MSB first)
for (int i = 0; i < 24; i++) {
digitalWrite(PRESSURE_SCK_PIN, HIGH); // Clock pulse up
value = value << 1; // Shift current value left
digitalWrite(PRESSURE_SCK_PIN, LOW); // Clock pulse down
if (digitalRead(PRESSURE_OUT_PIN) == HIGH) {
value++; // Set the least significant bit if DOUT is high
}
}
// Set gain for next reading (1 pulse = Gain 128)
digitalWrite(PRESSURE_SCK_PIN, HIGH);
digitalWrite(PRESSURE_SCK_PIN, LOW);
// Convert to 32-bit signed long (2's complement)
if (value & 0x800000) {
value |= 0xFF000000;
}
return static_cast<long>(value);
}
// --- PWM CALCULATION ---
// Calculates the *target* DIRECT Timer1 PWM duty cycle based on pressure RELATIVE TO BASELINE
int calculateTargetPWM(long pressure) {
// Calculate pressure relative to current baseline (compensates for temperature drift)
long relativePressure = pressure - dynamicBaseline;
if (relativePressure <= DEAD_ZONE_OFFSET) {
return 0; // Motor off (0 duty cycle)
} else if (relativePressure <= LOW_SPEED_OFFSET) {
return LOW_SPEED_PWM; // Low constant speed duty cycle (e.g., 8)
} else if (relativePressure <= MED_SPEED_OFFSET) {
return MED_SPEED_PWM; // Medium constant speed duty cycle (e.g., 12)
} else {
// Proportional speed range: Map relative pressure from MED_SPEED_OFFSET to effective max
// Use a reasonable max range above the medium threshold
long effectiveMax = dynamicBaseline + MED_SPEED_OFFSET + 2000000; // 2M above medium threshold
float mappedPWM = map(float(relativePressure), float(MED_SPEED_OFFSET), float(effectiveMax - dynamicBaseline),
float(MED_SPEED_PWM), float(MAX_PROPORTIONAL_PWM));
// Constrain the result to be within the allowed proportional range
return constrain(int(mappedPWM), MED_SPEED_PWM, MAX_PROPORTIONAL_PWM);
}
}
// --- SETUP ---
void setup() {
Serial.begin(115200);
while (!Serial);
Serial.println("Pressure-Controlled PWM w/ Temperature Compensation & Kick Start");
pinMode(PRESSURE_OUT_PIN, INPUT);
pinMode(PRESSURE_SCK_PIN, OUTPUT);
pinMode(PWM_PIN, OUTPUT);
digitalWrite(PRESSURE_SCK_PIN, LOW);
Timer1.initialize(PWM_PERIOD); // Set PWM frequency
Timer1.pwm(PWM_PIN, 0); // Start motor off
Serial.println("Waiting for HX710 stabilization...");
delay(500);
// Start 30-second calibration process
Serial.println("*** STARTING 30-SECOND CALIBRATION PROCESS ***");
Serial.println("Please ensure NO pressure is applied during calibration!");
isCalibrating = true;
calibrationStartTime = millis();
calibrationSum = 0;
calibrationCount = 0;
// Calibration loop - collect readings for 30 seconds
while (millis() - calibrationStartTime < CALIBRATION_DURATION) {
long reading = readHX710();
calibrationSum += reading;
calibrationCount++;
// Show progress every 3 seconds
unsigned long elapsed = millis() - calibrationStartTime;
if (calibrationCount % 30 == 0) {
Serial.print("Calibration progress: ");
Serial.print(elapsed / 1000);
Serial.print("/30 seconds, Current reading: ");
Serial.println(reading);
}
delay(100); // 100ms between readings
}
// Calculate final baseline
dynamicBaseline = calibrationSum / calibrationCount;
isCalibrating = false;
Serial.println("*** CALIBRATION COMPLETE ***");
Serial.print("Baseline established: "); Serial.println(dynamicBaseline);
Serial.print("Based on "); Serial.print(calibrationCount); Serial.println(" readings");
// Initialize filters with a reading after calibration
long initialReading = readHX710();
for (int i = 0; i < READINGS_COUNT; i++) {
pressureReadings[i] = initialReading;
}
pressureTotal = initialReading * READINGS_COUNT;
pressureAverage = initialReading;
smoothedPressure = (float)initialReading;
previousPwmValue = 0;
isKicking = false;
lastBaselineUpdate = millis();
Serial.println("Setup complete. Starting control loop.");
Serial.println("System ready - temperature drift will be automatically compensated.");
}
// --- MAIN LOOP ---
void loop() {
// 1. Read Sensor and Update Filtered Value
long rawReading = readHX710();
// Update filters
pressureTotal = pressureTotal - pressureReadings[readIndex];
pressureReadings[readIndex] = rawReading;
pressureTotal = pressureTotal + rawReading;
readIndex = (readIndex + 1) % READINGS_COUNT;
pressureAverage = pressureTotal / READINGS_COUNT;
smoothedPressure = smoothedPressure + (rawReading - smoothedPressure) * PRESSURE_SMOOTHING;
// 2. Update Dynamic Baseline (Temperature Drift Compensation)
// Only update baseline when system is idle (no motor activity) to avoid drift from vibration
if (millis() - lastBaselineUpdate > IDLE_BASELINE_UPDATE_INTERVAL && previousPwmValue == 0) {
// Very slow baseline update when motor is off (compensates for temperature drift)
dynamicBaseline = dynamicBaseline + ((long)smoothedPressure - dynamicBaseline) * BASELINE_SMOOTHING;
lastBaselineUpdate = millis();
}
// 3. Calculate Target DIRECT PWM Duty Cycle (using temperature-compensated pressure)
int targetPwmValue = calculateTargetPWM((long)smoothedPressure);
// 4. Implement Kick Start Logic
int finalPwmValue; // This will hold the DIRECT duty cycle value (0-1023) to be used
if (isKicking) {
// Kick active. Check duration.
if (millis() - kickStartTime >= KICK_START_DURATION) {
// Kick duration over.
isKicking = false;
finalPwmValue = targetPwmValue; // Revert to normal target duty cycle
} else {
// Kick still active.
finalPwmValue = KICK_START_PWM; // Use the direct kick duty cycle (e.g., 10)
}
} else {
// Kick not active. Check if need to start.
if (previousPwmValue == 0 && targetPwmValue > 0) {
// Start the kick!
isKicking = true;
kickStartTime = millis();
finalPwmValue = KICK_START_PWM; // Use the direct kick duty cycle (e.g., 10)
} else {
// Normal operation.
finalPwmValue = targetPwmValue; // Use the normal target duty cycle
}
}
// 5. Set Motor PWM Duty Cycle
// *** NO MAPPING NEEDED ***
// finalPwmValue now directly represents the desired Timer1 duty cycle (0-1023)
int timer1PwmDuty = finalPwmValue;
// Ensure the value is within the valid 0-1023 range for Timer1, just in case.
timer1PwmDuty = constrain(timer1PwmDuty, 0, 1023);
// Apply the calculated duty cycle to the PWM pin via Timer1
Timer1.pwm(PWM_PIN, timer1PwmDuty);
// 6. Update State for Next Loop Iteration
previousPwmValue = targetPwmValue; // Store the *target* value
// --- ENHANCED DIAGNOSTIC OUTPUT WITH TEMPERATURE COMPENSATION ---
long relativePressure = (long)smoothedPressure - dynamicBaseline;
Serial.print("Raw:"); Serial.print(rawReading);
Serial.print(", Baseline:"); Serial.print(dynamicBaseline);
Serial.print(", Relative:"); Serial.print(relativePressure);
Serial.print(", TargetPWM:"); Serial.print(targetPwmValue);
Serial.print(", FinalPWM:"); Serial.print(finalPwmValue);
Serial.print(", Range:");
if (relativePressure <= DEAD_ZONE_OFFSET) {
Serial.print("Dead");
} else if (relativePressure <= LOW_SPEED_OFFSET) {
Serial.print("Low");
if(isKicking) Serial.print("+Kick");
} else if (relativePressure <= MED_SPEED_OFFSET) {
Serial.print("Med");
} else {
Serial.print("Prop");
}
Serial.println();
} // end loop
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment