I’ve been trying to figure out a function to ramp a 12v dc motor up and then down once a specific target is reached. The target is a linear measurement (rack & pinion) correlating to a number of counts from a Hall Effect sensor.
The ramping up works well and is convention (blink without delay approach), using arduino’s timer0_millis function.
The problem with the function I’ve put together, is that it will not proceed beyond one execution of the ramp down if statement. I’ve tried re-arranging braces, else and if else, but cannot seem to get this working as is, and am stuck in my approach, right now.
The code is my attempt at simulating this, in hope to come to a solution.
#define DEBUG_RAMP
#define MAX_PWM 25
int PWM_val = 0;
long last_ramp = 0;
unsigned long ramp = 0;
unsigned long cycleA = 6000; // ramp timing cycle at 6 seconds
static int countA = 0;
static int Decel_target = 25;
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
delay(1000); //0 - 999 blocking count
last_ramp = millis(); // initiate timer0
}
void loop() {
// put your main code here, to run repeatedly:
delay(500); //0 - 499 blocking count
// top of loop statements
countA += 1; // simulate encoder count
Serial.print(F("Encoder Count: "));
Serial.println(countA);
static bool Ramp = true;
// check and call until ramping completed.
if (!Ramp || ramp_PWM())
{
// start the ramping function.
Ramp = false;
}
// continue with loop functions,
// Marker: ramp has completed.
if (PWM_val == MAX_PWM)
{
Serial.println(F("Ramp has completed"));
Serial.println();
}
// Marker: ramp down condition.
if (countA == Decel_target)
{
Serial.println(F("PWM_val is ramping down"));
Serial.println();
}
}
int ramp_PWM() {
ramp = millis();
if (ramp - last_ramp > 0)
{
int Accel = 0; // begin ramp up, interpolate PWM_val's
Accel = MAX_PWM * float(ramp - last_ramp) / float(cycleA);
PWM_val = Accel;
#ifdef DEBUG_RAMP
Serial.println(ramp);
Serial.println(last_ramp);
Serial.println(ramp - last_ramp);
#endif
if (ramp - last_ramp >= cycleA)
PWM_val = MAX_PWM; // PWM_val set to a limit
#ifdef DEBUG_RAMP
Serial.print(F("PWM value: "));
Serial.println(PWM_val);
Serial.println();
#endif
//only executes once, when the target condition is met - exactly at count = 25 ??
if (ramp - last_ramp >= cycleA && countA == Decel_target)
{
int Decel = 0; // PWM_val ramping down
Decel = MAX_PWM - MAX_PWM * float(ramp - last_ramp - 1 * cycleA) / float(cycleA);
PWM_val = PWM_val + Decel;
#ifdef DEBUG_RAMP
Serial.print(F("Decel value: "));
Serial.println(Decel);
Serial.print(F("PWM value: "));
Serial.println(PWM_val);
#endif
}
}
return 0;
}
Here's an example that uses a Finite State Machine to do the ramping. It ramps up over 6 seconds, holds at full speed until count 25, and then ramps down over 6 seconds. You kick off a sequence by setting RampStartTime to millis(). When RampStartTime is zero the sequence is done.
#define DEBUG_RAMP
#define MAX_PWM 255
int PWM_val = 0;
unsigned long RampStartTime = 0;
unsigned long currentTime = 0;
const unsigned long RampInterval = 6000; // Ramp timing cycle at 6 seconds
int EncoderCount = 0;
const int StartDecelerationCount = 25;
void setup()
{
// put your setup code here, to run once:
Serial.begin(115200);
delay(1000); //0 - 999 blocking count
RampStartTime = millis(); // initiate ramp-up
}
void loop()
{
// put your main code here, to run repeatedly:
delay(500); //0 - 499 blocking count
// top of loop statements
EncoderCount += 1; // simulate encoder count
Ramp_PWM();
}
enum RampStates {Idle, RampingUp, FullSpeed, RampingDown} RampState = Idle;
int Ramp_PWM()
{
unsigned long currentTime = millis();
unsigned long elapsedRampTime = currentTime - RampStartTime;
switch (RampState)
{
case Idle:
if (RampStartTime != 0)
{
EncoderCount = 0;
PWM_val = 0;
#ifdef DEBUG_RAMP
Serial.println(F("Starting Ramp Up"));
#endif
RampState = RampingUp;
}
break;
case RampingUp:
if (elapsedRampTime <= RampInterval)
{
PWM_val = MAX_PWM *
(float(elapsedRampTime) / float(RampInterval));
}
else
{
#ifdef DEBUG_RAMP
Serial.println(F("Reached Full Speed"));
#endif
RampState = FullSpeed;
}
break;
case FullSpeed:
PWM_val = MAX_PWM;
if (EncoderCount >= StartDecelerationCount)
{
RampStartTime = currentTime;
#ifdef DEBUG_RAMP
Serial.println(F("Start Ramping Down"));
#endif
RampState = RampingDown;
}
break;
case RampingDown:
if (elapsedRampTime <= RampInterval)
{
PWM_val = MAX_PWM - MAX_PWM *
(float(elapsedRampTime) / float(RampInterval));
}
else
{
PWM_val = 0;
RampStartTime = 0;
#ifdef DEBUG_RAMP
Serial.println(F("End Ramping Down"));
#endif
RampState = Idle;
}
}
#ifdef DEBUG_RAMP
if (RampStartTime != 0)
{
Serial.print(F("Elapsed time: "));
Serial.print(elapsedRampTime);
Serial.print(F(", Encoder Count: "));
Serial.print(EncoderCount);
Serial.print(F(", PWM value: "));
Serial.println(PWM_val);
}
#endif
return 0;
}
@johnwasser - I had been thinking the next options were a switch or enumerating a state machine, but was apprehensive to attempt those, because I just could not understand how to make what I had work.
Perhaps it is a lesson to move along and at least attempt a different approach having backed oneself into a corner.
@gcjr - I see, am to understand the += 1 does not increment beyond a single loop and that it should be a condition that cycleA is at the beginning of its cycle. Perhaps I'm still not getting where it messed it up!
i'm confused why the code ramps the motor speed based on the variable described as an "encoder count" when your description describes a position measured with a Hall effect sensor.
why isn't the position measured by the hall effect sensor not used to control the motor? is the purpose of ramping similar to what is done with PID to begin reducing voltage the motor based both on distance from and speed toward a target position?
Hi, Sorry about misleading, not intentional, the ‘encoder’ is made up of two scavenged commercial Hall Effect sensors, on separate circuit boards, arranged 90 degree out of phase or in this case 3.5 poles apart, which read an 8 pole magnet, 4 pulses per revolution (falling) on the motors end shaft. Indeed separate PID’s have controlled position and speed in some of the tests.
Nonetheless simplified testing uses the count of a single Hall Effect sensor to set the target. This is previously determined by manually stopping the motor at a specific point giving the measured distance of travel by count without calculation. This very simplistic and very much a work in progress!