Smartcar Shield
DistanceCar.cpp
Go to the documentation of this file.
1 #include <math.h> // NOLINT(modernize-deprecated-headers)
2 #include <stdint.h> // NOLINT(modernize-deprecated-headers)
3 
4 #include "../../utilities/Utilities.hpp"
5 #include "DistanceCar.hpp"
6 
7 namespace
8 {
9 const unsigned long kStopAttemptInterval = 100;
10 const uint8_t kMaxStopAttempts = 3;
11 } // namespace
12 
13 using namespace smartcarlib::constants::car;
14 using namespace smartcarlib::constants::control;
15 using namespace smartcarlib::utils;
16 
17 DistanceCar::DistanceCar(Runtime& runtime, Control& control, Odometer& odometer)
18  : SimpleCar(control)
19  , mOdometerLeft(odometer)
20  , mOdometerRight(odometer)
21  , mRuntime(runtime)
22 {
23 }
24 
26  Control& control,
27  Odometer& odometerLeft,
28  Odometer& odometerRight)
29  : SimpleCar(control)
30  , mOdometerLeft(odometerLeft)
31  , mOdometerRight(odometerRight)
32  , mRuntime(runtime)
33 {
34 }
35 
37 {
38  return areOdometersAttached() ? (mOdometerLeft.getDistance() + mOdometerRight.getDistance()) / 2
39  : static_cast<long>(kOdometersNotAttachedError);
40 }
41 
42 void DistanceCar::setSpeed(float speed)
43 {
44  if (!areOdometersAttached())
45  {
46  SimpleCar::setSpeed(speed);
47  return;
48  }
49  speed = getConstrain(
50  speed, static_cast<float>(kMinControlSpeed), static_cast<float>(kMaxControlSpeed));
51  // If odometers are directional and cruise control is enabled then we don't
52  // need to do more since the speed controller will take care of the speed
53  if (areOdometersDirectional() && mCruiseControlEnabled)
54  {
55  mTargetSpeed = speed;
56  return;
57  }
58  // If we need to change direction of movement while the cruise control is
59  // enabled, then we need to stop the car before proceeding to a different
60  // direction since the car cannot determine which direction it is actually
61  // moving.
62  auto newSpeedAtOppositeDirection = mTargetSpeed * speed < 0;
63  auto changeOfDirectionWhileCruiseControl = mCruiseControlEnabled && newSpeedAtOppositeDirection;
64  // Alternatively, we also need to stop the car if the supplied speed is `0`
65  // and the car is not already stopped.
66  auto carIsAlreadyStopped = areAlmostEqual(mTargetSpeed, static_cast<float>(kIdleControlSpeed));
67  auto carWantsToStop = areAlmostEqual(speed, static_cast<float>(kIdleControlSpeed));
68  auto carShouldStop = carWantsToStop && !carIsAlreadyStopped;
69  if (changeOfDirectionWhileCruiseControl || carShouldStop)
70  {
71  brake();
72  mTargetSpeed = speed;
73  return;
74  }
75  // If the car does not need to stop then set the supplied speed to the motors
76  // unless cruise control is enabled, in which case the speed adjustments will
77  // be taken care by the speed controller.
78  if (!mCruiseControlEnabled)
79  {
80  SimpleCar::setSpeed(speed);
81  }
82  mTargetSpeed = speed;
83 }
84 
85 void DistanceCar::brake()
86 {
87  // Apply the opposite speed than the one currently applied to the motors
88  // until the car is moving slow enough and is practically stopped
89  auto currentSpeed = mCruiseControlEnabled ? mPreviousControlledSpeed : mTargetSpeed;
90  SimpleCar::setSpeed(-currentSpeed / kBreakSpeedScale);
91  // Determine when we are moving slow enough to stop applying speed towards
92  // the opposite direction. We do not use the interrupt-based `getSpeed` from
93  // the odometers since when there are no pulses there will be no speed
94  // calculation.
95  for (auto attempt = 0; attempt < kMaxStopAttempts; attempt++)
96  {
97  auto initialDistance = getDistance();
98  mRuntime.delayMillis(kStopAttemptInterval);
99  auto distanceTravelled = getDistance() - initialDistance;
100  if (distanceTravelled == 0)
101  {
102  break;
103  }
104  }
105  SimpleCar::setSpeed(static_cast<float>(kIdleControlSpeed));
106  // Reset the speed controller variables since we have manually set the speed
107  mPreviousControlledSpeed = 0;
108  mIntegratedError = 0;
109  mPreviousError = 0;
110 }
111 
113 {
114  if (!areOdometersAttached() || !mCruiseControlEnabled)
115  {
116  return;
117  }
118 
119  auto currentTime = mRuntime.currentTimeMillis();
120  if (currentTime < mPreviousUpdate + mFrequency)
121  {
122  return;
123  }
124  mPreviousUpdate = currentTime;
125 
126  // Don't try to immobilize the car if odometers are not directional since we
127  // have already done that when setting the speed
128  if (!areOdometersDirectional() && mTargetSpeed == kIdleControlSpeed)
129  {
130  return;
131  }
132  auto actualSpeed = getSpeed();
133  // If we do not have a way of determining direction of movement and the set
134  // speed is negative, then we assume that the car is also moving backwards.
135  // This assumption is based on the fact that if a speed towards the opposite
136  // direction is supplied we should make first sure we are stopped before
137  // changing direction.
138  if (!areOdometersDirectional() && mTargetSpeed < 0)
139  {
140  actualSpeed = -actualSpeed;
141  }
142  auto controlledSpeed = controlMotorSpeed(mPreviousControlledSpeed, mTargetSpeed, actualSpeed);
143  // In case direction readings are not supported by the odometers, we need to
144  // ensure that runaway will not occur (see #5) due to our assumption that we
145  // are moving at the same direction as our set speed.
146  if (!areOdometersDirectional() && (controlledSpeed * actualSpeed < 0))
147  {
148  // If we are unable to determine the direction of movement AND the PID controller output
149  // hints us to move towards the opposite direction, i.e. due to moving faster than we should
150  // or the odometers providing faulty readings, then don't try to spin the motors towards
151  // the opposite direction and just halt the motoros. This is because we do not have a
152  // reliable way to determine whether we are merely slowing down or accelerating towards
153  // the wrong (opposite) direction.
154  controlledSpeed = kIdleControlSpeed;
155  }
156  // Pass the rounded output of the pid as an input to the control
157  SimpleCar::setSpeed(roundf(controlledSpeed));
158  // Save the unrounded float output of the PID controller
159  mPreviousControlledSpeed = controlledSpeed;
160 }
161 
162 float DistanceCar::controlMotorSpeed(const float& previousSpeed,
163  const float& targetSpeed,
164  const float& currentSpeed)
165 {
166  float error = targetSpeed - currentSpeed;
167  mIntegratedError += error;
168  float correction = (mProportional * error) + (mIntegral * mIntegratedError)
169  + (mDerivative * (error - mPreviousError));
170  mPreviousError = error;
171 
172  return getConstrain(previousSpeed + correction,
173  static_cast<float>(kMinControlSpeed),
174  static_cast<float>(kMaxControlSpeed));
175 }
176 
178 {
179  return areOdometersAttached() ? (mOdometerLeft.getSpeed() + mOdometerRight.getSpeed()) / 2
180  : static_cast<float>(kOdometersNotAttachedError);
181 }
182 
184 {
185  mCruiseControlEnabled = false;
186 }
187 
188 void DistanceCar::enableCruiseControl(float proportional,
189  float integral,
190  float derivative,
191  unsigned long frequency)
192 {
193  mCruiseControlEnabled = true;
194  mProportional = proportional;
195  mIntegral = integral;
196  mDerivative = derivative;
197  mFrequency = frequency;
198 }
199 
200 bool DistanceCar::areOdometersAttached()
201 {
202  return mOdometerLeft.isAttached() && mOdometerRight.isAttached();
203 }
204 
205 bool DistanceCar::areOdometersDirectional()
206 {
207  return mOdometerLeft.providesDirection() && mOdometerRight.providesDirection();
208 }
209 
210 void DistanceCar::overrideMotorSpeed(int firstMotorSpeed, int secondMotorSpeed)
211 {
212  if (mCruiseControlEnabled)
213  {
214  return;
215  }
216 
217  SimpleCar::overrideMotorSpeed(firstMotorSpeed, secondMotorSpeed);
218 }
smartcarlib::constants::control::kMinControlSpeed
const int kMinControlSpeed
Definition: Control.hpp:16
smartcarlib::constants::car::kOdometersNotAttachedError
const int kOdometersNotAttachedError
Definition: DistanceCar.hpp:21
smartcarlib::constants::car
Definition: DistanceCar.hpp:15
DistanceCar::getDistance
long getDistance()
Gets the car's travelled distance.
Definition: DistanceCar.cpp:36
smartcarlib::constants::control::kIdleControlSpeed
const int kIdleControlSpeed
Definition: Control.hpp:17
Odometer
Definition: Odometer.hpp:26
Odometer::getSpeed
virtual float getSpeed()=0
Returns the current speed in meters/sec where sign can indicate direction if there is hardware suppor...
Odometer::isAttached
virtual bool isAttached() const =0
Returns whether the sensor has been properly attached.
Runtime::delayMillis
virtual void delayMillis(unsigned long milliseconds)=0
Block the execution for the specified number of milliseconds, equivalent of delay in Arduino.
Odometer::providesDirection
virtual bool providesDirection() const =0
Return whether the sensor is capable of inferring the direction of movement.
Runtime
Definition: Runtime.hpp:35
SimpleCar::setSpeed
void setSpeed(float speed) override
Sets the car's driving speed as a percentage of the motors total speed where the sign indicates direc...
Definition: SimpleCar.cpp:12
SimpleCar::overrideMotorSpeed
void overrideMotorSpeed(int firstMotorSpeed, int secondMotorSpeed) override
Set the motor speed individually as a percentage of the motors` total power.
Definition: SimpleCar.cpp:22
Control
Definition: Control.hpp:23
smartcarlib::constants::control::kMaxControlSpeed
const int kMaxControlSpeed
Definition: Control.hpp:18
DistanceCar::disableCruiseControl
void disableCruiseControl()
Disable cruise control.
Definition: DistanceCar.cpp:183
DistanceCar::DistanceCar
DistanceCar(Runtime &runtime, Control &control, Odometer &odometer)
Constructs a car equipped with a distance sensor.
Definition: DistanceCar.cpp:17
DistanceCar::enableCruiseControl
void enableCruiseControl(float proportional=smartcarlib::constants::car::kDefaultProportional, float integral=smartcarlib::constants::car::kDefaultIntegral, float derivative=smartcarlib::constants::car::kDefaultDerivative, unsigned long frequency=smartcarlib::constants::car::kDefaultPidFrequency)
Enables the car to move with a stable speed using the odometers.
Definition: DistanceCar.cpp:188
smartcarlib::utils
Definition: Utilities.hpp:9
Runtime::currentTimeMillis
virtual unsigned long currentTimeMillis()=0
Gets the amount of milliseconds since the microcontroller started running, equivalent of millis in Ar...
smartcarlib::constants::control
Definition: Control.hpp:11
smartcarlib::constants::car::kBreakSpeedScale
const auto kBreakSpeedScale
Definition: DistanceCar.hpp:22
smartcarlib::utils::areAlmostEqual
constexpr bool areAlmostEqual(float a, float b)
Compares two floating point numbers.
Definition: Utilities.hpp:124
SimpleCar
Definition: SimpleCar.hpp:10
DistanceCar::update
virtual void update()
Adjusts the cruise control speed.
Definition: DistanceCar.cpp:112
smartcarlib::utils::getConstrain
constexpr AnyNumber getConstrain(AnyNumber number, AnyNumber min, AnyNumber max)
Limit the number between a range.
Definition: Utilities.hpp:46
Odometer::getDistance
virtual long getDistance()=0
Returns the travelled distance in centimeters where sign can indicate direction if there is hardware ...
DistanceCar::overrideMotorSpeed
void overrideMotorSpeed(int firstMotorSpeed, int secondMotorSpeed) override
Sets the motor speed individually as a percentage of the motors` total power unless cruise control is...
Definition: DistanceCar.cpp:210
DistanceCar.hpp
DistanceCar::setSpeed
void setSpeed(float speed) override
Sets the car's speed in meters per second if cruise control is enabled otherwise as a percentage of t...
Definition: DistanceCar.cpp:42
DistanceCar::getSpeed
float getSpeed()
Gets the car's current speed in meters per second.
Definition: DistanceCar.cpp:177