blob: ba25a0407024fca244643a968350eb6184a0cfff [file] [log] [blame]
Brian Silverman26e4e522015-12-17 01:56:40 -05001/*----------------------------------------------------------------------------*/
2/* Copyright (c) FIRST 2008. All Rights Reserved.
3 */
4/* Open Source Software - may be modified and shared by FRC teams. The code */
5/* must be accompanied by the FIRST BSD license file in $(WIND_BASE)/WPILib. */
6/*----------------------------------------------------------------------------*/
7
8#include "Ultrasonic.h"
9
10#include "Counter.h"
11#include "DigitalInput.h"
12#include "DigitalOutput.h"
13#include "Timer.h"
14#include "Utility.h"
15#include "WPIErrors.h"
16#include "LiveWindow/LiveWindow.h"
17
18constexpr double
19 Ultrasonic::kPingTime; ///< Time (sec) for the ping trigger pulse.
20const uint32_t Ultrasonic::kPriority; ///< Priority that the ultrasonic round
21 ///robin task runs.
22constexpr double
23 Ultrasonic::kMaxUltrasonicTime; ///< Max time (ms) between readings.
24constexpr double Ultrasonic::kSpeedOfSoundInchesPerSec;
25Ultrasonic *Ultrasonic::m_firstSensor =
26 nullptr; // head of the ultrasonic sensor list
27Task Ultrasonic::m_task;
28std::atomic<bool> Ultrasonic::m_automaticEnabled{false}; // automatic round robin mode
29priority_mutex Ultrasonic::m_mutex;
30
31/**
32 * Background task that goes through the list of ultrasonic sensors and pings
33 * each one in turn. The counter
34 * is configured to read the timing of the returned echo pulse.
35 *
36 * DANGER WILL ROBINSON, DANGER WILL ROBINSON:
37 * This code runs as a task and assumes that none of the ultrasonic sensors will
38 * change while it's
39 * running. If one does, then this will certainly break. Make sure to disable
40 * automatic mode before changing
41 * anything with the sensors!!
42 */
43void Ultrasonic::UltrasonicChecker() {
44 Ultrasonic *u = nullptr;
45 while (m_automaticEnabled) {
46 if (u == nullptr) u = m_firstSensor;
47 if (u == nullptr) return;
48 if (u->IsEnabled()) u->m_pingChannel->Pulse(kPingTime); // do the ping
49 u = u->m_nextSensor;
50 Wait(0.1); // wait for ping to return
51 }
52}
53
54/**
55 * Initialize the Ultrasonic Sensor.
56 * This is the common code that initializes the ultrasonic sensor given that
57 * there
58 * are two digital I/O channels allocated. If the system was running in
59 * automatic mode (round robin)
60 * when the new sensor is added, it is stopped, the sensor is added, then
61 * automatic mode is
62 * restored.
63 */
64void Ultrasonic::Initialize() {
65 bool originalMode = m_automaticEnabled;
66 SetAutomaticMode(false); // kill task when adding a new sensor
67 // link this instance on the list
68 {
69 std::lock_guard<priority_mutex> lock(m_mutex);
70 m_nextSensor = m_firstSensor;
71 m_firstSensor = this;
72 }
73
74 m_counter.SetMaxPeriod(1.0);
75 m_counter.SetSemiPeriodMode(true);
76 m_counter.Reset();
77 m_enabled = true; // make it available for round robin scheduling
78 SetAutomaticMode(originalMode);
79
80 static int instances = 0;
81 instances++;
82 HALReport(HALUsageReporting::kResourceType_Ultrasonic, instances);
83 LiveWindow::GetInstance()->AddSensor("Ultrasonic",
84 m_echoChannel->GetChannel(), this);
85}
86
87/**
88 * Create an instance of the Ultrasonic Sensor
89 * This is designed to supchannel the Daventech SRF04 and Vex ultrasonic
90 * sensors.
91 * @param pingChannel The digital output channel that sends the pulse to
92 * initiate the sensor sending
93 * the ping.
94 * @param echoChannel The digital input channel that receives the echo. The
95 * length of time that the
96 * echo is high represents the round trip time of the ping, and the distance.
97 * @param units The units returned in either kInches or kMilliMeters
98 */
99Ultrasonic::Ultrasonic(uint32_t pingChannel, uint32_t echoChannel,
100 DistanceUnit units)
101 : m_pingChannel(std::make_shared<DigitalOutput>(pingChannel)),
102 m_echoChannel(std::make_shared<DigitalInput>(echoChannel)),
103 m_counter(m_echoChannel) {
104 m_units = units;
105 Initialize();
106}
107
108/**
109 * Create an instance of an Ultrasonic Sensor from a DigitalInput for the echo
110 * channel and a DigitalOutput
111 * for the ping channel.
112 * @param pingChannel The digital output object that starts the sensor doing a
113 * ping. Requires a 10uS pulse to start.
114 * @param echoChannel The digital input object that times the return pulse to
115 * determine the range.
116 * @param units The units returned in either kInches or kMilliMeters
117 */
118Ultrasonic::Ultrasonic(DigitalOutput *pingChannel, DigitalInput *echoChannel,
119 DistanceUnit units)
120 : m_pingChannel(pingChannel, NullDeleter<DigitalOutput>()),
121 m_echoChannel(echoChannel, NullDeleter<DigitalInput>()),
122 m_counter(m_echoChannel) {
123 if (pingChannel == nullptr || echoChannel == nullptr) {
124 wpi_setWPIError(NullParameter);
125 m_nextSensor = nullptr;
126 m_units = units;
127 return;
128 }
129 m_units = units;
130 Initialize();
131}
132
133/**
134 * Create an instance of an Ultrasonic Sensor from a DigitalInput for the echo
135 * channel and a DigitalOutput
136 * for the ping channel.
137 * @param pingChannel The digital output object that starts the sensor doing a
138 * ping. Requires a 10uS pulse to start.
139 * @param echoChannel The digital input object that times the return pulse to
140 * determine the range.
141 * @param units The units returned in either kInches or kMilliMeters
142 */
143Ultrasonic::Ultrasonic(DigitalOutput &pingChannel, DigitalInput &echoChannel,
144 DistanceUnit units)
145 : m_pingChannel(&pingChannel, NullDeleter<DigitalOutput>()),
146 m_echoChannel(&echoChannel, NullDeleter<DigitalInput>()),
147 m_counter(m_echoChannel) {
148 m_units = units;
149 Initialize();
150}
151
152/**
153 * Create an instance of an Ultrasonic Sensor from a DigitalInput for the echo
154 * channel and a DigitalOutput
155 * for the ping channel.
156 * @param pingChannel The digital output object that starts the sensor doing a
157 * ping. Requires a 10uS pulse to start.
158 * @param echoChannel The digital input object that times the return pulse to
159 * determine the range.
160 * @param units The units returned in either kInches or kMilliMeters
161 */
162Ultrasonic::Ultrasonic(std::shared_ptr<DigitalOutput> pingChannel,
163 std::shared_ptr<DigitalInput> echoChannel,
164 DistanceUnit units)
165 : m_pingChannel(pingChannel),
166 m_echoChannel(echoChannel),
167 m_counter(m_echoChannel) {
168 m_units = units;
169 Initialize();
170}
171
172/**
173 * Destructor for the ultrasonic sensor.
174 * Delete the instance of the ultrasonic sensor by freeing the allocated digital
175 * channels.
176 * If the system was in automatic mode (round robin), then it is stopped, then
177 * started again
178 * after this sensor is removed (provided this wasn't the last sensor).
179 */
180Ultrasonic::~Ultrasonic() {
181 bool wasAutomaticMode = m_automaticEnabled;
182 SetAutomaticMode(false);
183 wpi_assert(m_firstSensor != nullptr);
184
185 {
186 std::lock_guard<priority_mutex> lock(m_mutex);
187 if (this == m_firstSensor) {
188 m_firstSensor = m_nextSensor;
189 if (m_firstSensor == nullptr) {
190 SetAutomaticMode(false);
191 }
192 } else {
193 wpi_assert(m_firstSensor->m_nextSensor != nullptr);
194 for (Ultrasonic *s = m_firstSensor; s != nullptr; s = s->m_nextSensor) {
195 if (this == s->m_nextSensor) {
196 s->m_nextSensor = s->m_nextSensor->m_nextSensor;
197 break;
198 }
199 }
200 }
201 }
202 if (m_firstSensor != nullptr && wasAutomaticMode) SetAutomaticMode(true);
203}
204
205/**
206 * Turn Automatic mode on/off.
207 * When in Automatic mode, all sensors will fire in round robin, waiting a set
208 * time between each sensor.
209 * @param enabling Set to true if round robin scheduling should start for all
210 * the ultrasonic sensors. This
211 * scheduling method assures that the sensors are non-interfering because no two
212 * sensors fire at the same time.
213 * If another scheduling algorithm is prefered, it can be implemented by
214 * pinging the sensors manually and waiting
215 * for the results to come back.
216 */
217void Ultrasonic::SetAutomaticMode(bool enabling) {
218 if (enabling == m_automaticEnabled) return; // ignore the case of no change
219
220 m_automaticEnabled = enabling;
221 if (enabling) {
222 // enabling automatic mode.
223 // Clear all the counters so no data is valid
224 for (Ultrasonic *u = m_firstSensor; u != nullptr; u = u->m_nextSensor) {
225 u->m_counter.Reset();
226 }
227 // Start round robin task
228 wpi_assert(m_task.Verify() ==
229 false); // should be false since was previously disabled
230 m_task = Task("UltrasonicChecker", &Ultrasonic::UltrasonicChecker);
231
232 // TODO: Currently, lvuser does not have permissions to set task priorities.
233 // Until that is the case, uncommenting this will break user code that calls
234 // Ultrasonic::SetAutomicMode().
235 //m_task.SetPriority(kPriority);
236 } else {
237 // disabling automatic mode. Wait for background task to stop running.
238 while (m_task.Verify())
239 Wait(0.15); // just a little longer than the ping time for round-robin to
240 // stop
241
242 // clear all the counters (data now invalid) since automatic mode is stopped
243 for (Ultrasonic *u = m_firstSensor; u != nullptr; u = u->m_nextSensor) {
244 u->m_counter.Reset();
245 }
246 m_automaticEnabled = false;
247 m_task.join();
248 }
249}
250
251/**
252 * Single ping to ultrasonic sensor.
253 * Send out a single ping to the ultrasonic sensor. This only works if automatic
254 * (round robin)
255 * mode is disabled. A single ping is sent out, and the counter should count the
256 * semi-period
257 * when it comes in. The counter is reset to make the current value invalid.
258 */
259void Ultrasonic::Ping() {
260 wpi_assert(!m_automaticEnabled);
261 m_counter.Reset(); // reset the counter to zero (invalid data now)
262 m_pingChannel->Pulse(
263 kPingTime); // do the ping to start getting a single range
264}
265
266/**
267 * Check if there is a valid range measurement.
268 * The ranges are accumulated in a counter that will increment on each edge of
269 * the echo (return)
270 * signal. If the count is not at least 2, then the range has not yet been
271 * measured, and is invalid.
272 */
273bool Ultrasonic::IsRangeValid() const { return m_counter.Get() > 1; }
274
275/**
276 * Get the range in inches from the ultrasonic sensor.
277 * @return double Range in inches of the target returned from the ultrasonic
278 * sensor. If there is
279 * no valid value yet, i.e. at least one measurement hasn't completed, then
280 * return 0.
281 */
282double Ultrasonic::GetRangeInches() const {
283 if (IsRangeValid())
284 return m_counter.GetPeriod() * kSpeedOfSoundInchesPerSec / 2.0;
285 else
286 return 0;
287}
288
289/**
290 * Get the range in millimeters from the ultrasonic sensor.
291 * @return double Range in millimeters of the target returned by the ultrasonic
292 * sensor.
293 * If there is no valid value yet, i.e. at least one measurement hasn't
294 * complted, then return 0.
295 */
296double Ultrasonic::GetRangeMM() const { return GetRangeInches() * 25.4; }
297
298/**
299 * Get the range in the current DistanceUnit for the PIDSource base object.
300 *
301 * @return The range in DistanceUnit
302 */
303double Ultrasonic::PIDGet() {
304 switch (m_units) {
305 case Ultrasonic::kInches:
306 return GetRangeInches();
307 case Ultrasonic::kMilliMeters:
308 return GetRangeMM();
309 default:
310 return 0.0;
311 }
312}
313
314void Ultrasonic::SetPIDSourceType(PIDSourceType pidSource) {
315 if (wpi_assert(pidSource == PIDSourceType::kDisplacement)) {
316 m_pidSource = pidSource;
317 }
318}
319
320/**
321 * Set the current DistanceUnit that should be used for the PIDSource base
322 * object.
323 *
324 * @param units The DistanceUnit that should be used.
325 */
326void Ultrasonic::SetDistanceUnits(DistanceUnit units) { m_units = units; }
327
328/**
329 * Get the current DistanceUnit that is used for the PIDSource base object.
330 *
331 * @return The type of DistanceUnit that is being used.
332 */
333Ultrasonic::DistanceUnit Ultrasonic::GetDistanceUnits() const {
334 return m_units;
335}
336
337void Ultrasonic::UpdateTable() {
338 if (m_table != nullptr) {
339 m_table->PutNumber("Value", GetRangeInches());
340 }
341}
342
343void Ultrasonic::StartLiveWindowMode() {}
344
345void Ultrasonic::StopLiveWindowMode() {}
346
347std::string Ultrasonic::GetSmartDashboardType() const { return "Ultrasonic"; }
348
349void Ultrasonic::InitTable(std::shared_ptr<ITable> subTable) {
350 m_table = subTable;
351 UpdateTable();
352}
353
354std::shared_ptr<ITable> Ultrasonic::GetTable() const { return m_table; }