Okay, let's integrate the "kick start" feature and ensure swift stopping.
Here's the modified code with explanations:
-
Kick Start Feature Added:
- New constants
KICK_START_PWMandKICK_START_DURATIONare added at the top for easy configuration. - State variables
isKicking,kickStartTime, andpreviousPwmValueare 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_PWMvalue overrides the normally calculated PWM. - After the duration expires, the control reverts to the
calculatePWMoutput.
- New constants
-
Swift Stopping Enhancement:
- The primary factor slowing down the stop was the
delay(10);at the end of theloop(). 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. ReducingREADINGS_COUNTor increasingPRESSURE_SMOOTHING(e.g., to0.15or0.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 explicitdelay()is the most significant improvement for stopping speed without sacrificing stability as much.
- The primary factor slowing down the stop was the
#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 loopKey Changes Summary:
- Kick Start Constants:
KICK_START_PWM,KICK_START_DURATION. - Kick Start State Variables:
isKicking,kickStartTime,previousPwmValue. - 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. calculateTargetPWMfunction: Renamed fromcalculatePWMto clarify it calculates the desired speed before kick start override.- 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. - 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 byTimer1.pwm(). Adjust themaprange (e.g., the512value) 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. - Diagnostic Output: Updated to show both
TargetPWMandFinalPWM(which includes the kick) and the finalDutyvalue 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.