Smartcar Shield
Loading...
Searching...
No Matches
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
7namespace
8{
9const unsigned long kStopAttemptInterval = 100;
10const uint8_t kMaxStopAttempts = 3;
11} // namespace
12
13using namespace smartcarlib::constants::car;
15using namespace smartcarlib::utils;
16
17DistanceCar::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
42void DistanceCar::setSpeed(float speed)
43{
44 if (!areOdometersAttached())
45 {
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 {
81 }
82 mTargetSpeed = speed;
83}
84
85void 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;
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
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
162float DistanceCar::controlMotorSpeed(float previousSpeed, float targetSpeed, float currentSpeed)
163{
164 float error = targetSpeed - currentSpeed;
165 mIntegratedError += error;
166 float correction = (mProportional * error) + (mIntegral * mIntegratedError)
167 + (mDerivative * (error - mPreviousError));
168 mPreviousError = error;
169
170 return getConstrain(previousSpeed + correction,
171 static_cast<float>(kMinControlSpeed),
172 static_cast<float>(kMaxControlSpeed));
173}
174
176{
177 return areOdometersAttached() ? (mOdometerLeft.getSpeed() + mOdometerRight.getSpeed()) / 2
178 : static_cast<float>(kOdometersNotAttachedError);
179}
180
182{
183 mCruiseControlEnabled = false;
184}
185
186void DistanceCar::enableCruiseControl(float proportional,
187 float integral,
188 float derivative,
189 unsigned long frequency)
190{
191 mCruiseControlEnabled = true;
192 mProportional = proportional;
193 mIntegral = integral;
194 mDerivative = derivative;
195 mFrequency = frequency;
196}
197
198bool DistanceCar::areOdometersAttached() const
199{
200 return mOdometerLeft.isAttached() && mOdometerRight.isAttached();
201}
202
203bool DistanceCar::areOdometersDirectional() const
204{
205 return mOdometerLeft.providesDirection() && mOdometerRight.providesDirection();
206}
207
208void DistanceCar::overrideMotorSpeed(int firstMotorSpeed, int secondMotorSpeed)
209{
210 if (mCruiseControlEnabled)
211 {
212 return;
213 }
214
215 SimpleCar::overrideMotorSpeed(firstMotorSpeed, secondMotorSpeed);
216}
Interface to generalize all classes that can provide a way to control a Car.
Definition Control.hpp:24
long getDistance()
Gets the car's travelled distance.
void overrideMotorSpeed(int firstMotorSpeed, int secondMotorSpeed) override
Sets the motor speed individually as a percentage of the motors` total power unless cruise control is...
virtual void update()
Adjusts the cruise control speed.
void disableCruiseControl()
Disable cruise control.
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...
float getSpeed()
Gets the car's current speed in meters per second.
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.
DistanceCar(Runtime &runtime, Control &control, Odometer &odometer)
Constructs a car equipped with a distance sensor.
An interface to programmatically represent all odometers.
Definition Odometer.hpp:27
virtual bool providesDirection() const =0
Return whether the sensor is capable of inferring the direction of movement.
virtual long getDistance()=0
Returns the travelled distance in centimeters where sign can indicate direction if there is hardware ...
virtual float getSpeed()=0
Returns the current speed in meters/sec where sign can indicate direction if there is hardware suppor...
virtual bool isAttached() const =0
Returns whether the sensor has been properly attached.
An interface to serve as a wrapper for the native runtime environment, greatly inspired by the Arduin...
Definition Runtime.hpp:36
virtual void delayMillis(unsigned long milliseconds)=0
Block the execution for the specified number of milliseconds, equivalent of delay in Arduino.
virtual unsigned long currentTimeMillis()=0
Gets the amount of milliseconds since the microcontroller started running, equivalent of millis in Ar...
A class to programmatically represent a very basic vehicle with two motors.
Definition SimpleCar.hpp:11
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
void overrideMotorSpeed(int firstMotorSpeed, int secondMotorSpeed) override
Set the motor speed individually as a percentage of the motors` total power.
Definition SimpleCar.cpp:22
constexpr bool areAlmostEqual(float a, float b)
Compares two floating point numbers.
constexpr AnyNumber getConstrain(AnyNumber number, AnyNumber min, AnyNumber max)
Limit the number between a range.
Definition Utilities.hpp:46