blob: 27fcce7ce9f695d638c3fe0b51e3c7a4d9790cc0 [file] [log] [blame]
Austin Schuh812d0d12021-11-04 20:16:48 -07001// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
4
5#include <atomic>
6#include <thread>
7
8#include <hal/DMA.h>
9#include <hal/HAL.h>
10#include <wpi/SmallVector.h>
11#include <wpi/condition_variable.h>
12#include <wpi/priority_mutex.h>
13
14#include "CrossConnects.h"
15#include "LifetimeWrappers.h"
16#include "gtest/gtest.h"
17
18using namespace hlt;
19
20class PWMTest : public ::testing::TestWithParam<std::pair<int, int>> {};
21
22void TestTimingDMA(int squelch, std::pair<int, int> param) {
23 // Initialize DMA
24 int32_t status = 0;
25 DMAHandle dmaHandle(&status);
26 ASSERT_NE(dmaHandle, HAL_kInvalidHandle);
27 ASSERT_EQ(0, status);
28
29 status = 0;
30 PWMHandle pwmHandle(param.first, &status);
31 ASSERT_NE(pwmHandle, HAL_kInvalidHandle);
32 ASSERT_EQ(0, status);
33
34 // Ensure our PWM is disabled, and set up properly
35 HAL_SetPWMRaw(pwmHandle, 0, &status);
36 HAL_SetPWMConfig(pwmHandle, 2.0, 1.0, 1.0, 0, 0, &status);
37 HAL_SetPWMPeriodScale(pwmHandle, squelch, &status);
38
39 unsigned int checkPeriod = 0;
40 switch (squelch) {
41 case (0):
42 checkPeriod = 5050;
43 break;
44 case (1):
45 checkPeriod = 10100;
46 break;
47 case (3):
48 checkPeriod = 20200;
49 break;
50 }
51
52 status = 0;
53 DIOHandle dioHandle(param.second, true, &status);
54 ASSERT_NE(dioHandle, HAL_kInvalidHandle);
55
56 HAL_AddDMADigitalSource(dmaHandle, dioHandle, &status);
57 ASSERT_EQ(0, status);
58
59 HAL_SetDMAExternalTrigger(dmaHandle, dioHandle,
60 HAL_AnalogTriggerType::HAL_Trigger_kInWindow, true,
61 true, &status);
62 ASSERT_EQ(0, status);
63
64 // Loop to test 5 speeds
65 for (unsigned int testWidth = 1000; testWidth < 2100; testWidth += 250) {
66 HAL_StartDMA(dmaHandle, 1024, &status);
67 ASSERT_EQ(0, status);
68
69 while (true) {
70 int32_t remaining = 0;
71 HAL_DMASample testSample;
72 HAL_ReadDMA(dmaHandle, &testSample, 0.01, &remaining, &status);
73 if (remaining == 0) {
74 break;
75 }
76 }
77
78 HAL_SetPWMSpeed(pwmHandle, (testWidth - 1000) / 1000.0, &status);
79
80 constexpr const int kSampleCount = 15;
81 HAL_DMASample dmaSamples[kSampleCount];
82 int readCount = 0;
83 while (readCount < kSampleCount) {
84 status = 0;
85 int32_t remaining = 0;
86 HAL_DMAReadStatus readStatus = HAL_ReadDMA(
87 dmaHandle, &dmaSamples[readCount], 1.0, &remaining, &status);
88 ASSERT_EQ(0, status);
89 ASSERT_EQ(HAL_DMAReadStatus::HAL_DMA_OK, readStatus);
90 readCount++;
91 }
92
93 HAL_SetPWMSpeed(pwmHandle, 0, &status);
94 HAL_StopDMA(dmaHandle, &status);
95
96 // Find first rising edge
97 int startIndex = 4;
98 while (startIndex < 6) {
99 status = 0;
100 auto value = HAL_GetDMASampleDigitalSource(&dmaSamples[startIndex],
101 dioHandle, &status);
102 ASSERT_EQ(0, status);
103 if (value)
104 break;
105 startIndex++;
106 }
107 ASSERT_LT(startIndex, 6);
108
109 // Check that samples alternate
110 bool previous = false;
111 int iterationCount = 0;
112 for (int i = startIndex; i < startIndex + 8; i++) {
113 auto value =
114 HAL_GetDMASampleDigitalSource(&dmaSamples[i], dioHandle, &status);
115 ASSERT_EQ(0, status);
116 ASSERT_NE(previous, value);
117 previous = !previous;
118 iterationCount++;
119 }
120 ASSERT_EQ(iterationCount, 8);
121 iterationCount = 0;
122
123 // Check width between samples
124 for (int i = startIndex; i < startIndex + 8; i += 2) {
125 auto width = HAL_GetDMASampleTime(&dmaSamples[i + 1], &status) -
126 HAL_GetDMASampleTime(&dmaSamples[i], &status);
127 ASSERT_NEAR(testWidth, width, 10);
128 iterationCount++;
129 }
130 ASSERT_EQ(iterationCount, 4);
131 iterationCount = 0;
132
133 // Check period between samples
134 for (int i = startIndex; i < startIndex + 6; i += 2) {
135 auto period = HAL_GetDMASampleTime(&dmaSamples[i + 2], &status) -
136 HAL_GetDMASampleTime(&dmaSamples[i], &status);
137 ASSERT_NEAR(checkPeriod, period, 10);
138 iterationCount++;
139 }
140 ASSERT_EQ(iterationCount, 3);
141 }
142}
143
144struct InterruptCheckData {
145 wpi::SmallVector<uint64_t, 8> risingStamps;
146 wpi::SmallVector<uint64_t, 8> fallingStamps;
147 wpi::priority_mutex mutex;
148 wpi::condition_variable cond;
149 HAL_InterruptHandle handle;
150};
151
152// TODO switch this to DMA
153void TestTiming(int squelch, std::pair<int, int> param) {
154 // Initialize interrupt
155 int32_t status = 0;
156 InterruptHandle interruptHandle(&status);
157 // Ensure we have a valid interrupt handle
158 ASSERT_NE(interruptHandle, HAL_kInvalidHandle);
159
160 status = 0;
161 PWMHandle pwmHandle(param.first, &status);
162 ASSERT_NE(pwmHandle, HAL_kInvalidHandle);
163
164 // Ensure our PWM is disabled, and set up properly
165 HAL_SetPWMRaw(pwmHandle, 0, &status);
166 HAL_SetPWMConfig(pwmHandle, 2.0, 1.0, 1.0, 0, 0, &status);
167 HAL_SetPWMPeriodScale(pwmHandle, squelch, &status);
168
169 unsigned int checkPeriod = 0;
170 switch (squelch) {
171 case (0):
172 checkPeriod = 5050;
173 break;
174 case (1):
175 checkPeriod = 10100;
176 break;
177 case (3):
178 checkPeriod = 20200;
179 break;
180 }
181
182 status = 0;
183 DIOHandle dioHandle(param.second, true, &status);
184 ASSERT_NE(dioHandle, HAL_kInvalidHandle);
185
186 InterruptCheckData interruptData;
187 interruptData.handle = interruptHandle;
188
189 // Can use any type for the interrupt handle
190 HAL_RequestInterrupts(interruptHandle, dioHandle,
191 HAL_AnalogTriggerType::HAL_Trigger_kInWindow, &status);
192
193 HAL_SetInterruptUpSourceEdge(interruptHandle, true, true, &status);
194
195 // Loop to test 5 speeds
196 for (unsigned int i = 1000; i < 2100; i += 250) {
197 interruptData.risingStamps.clear();
198 interruptData.fallingStamps.clear();
199
200 std::atomic_bool runThread{true};
201
202 status = 0;
203 std::thread interruptThread([&]() {
204 while (runThread) {
205 int32_t threadStatus = 0;
206 auto mask =
207 HAL_WaitForInterrupt(interruptHandle, 5, true, &threadStatus);
208
209 if ((mask & 0x100) == 0x100 && interruptData.risingStamps.size() == 0 &&
210 interruptData.fallingStamps.size() == 0) {
211 // Falling edge at start of tracking. Skip
212 continue;
213 }
214
215 int32_t status = 0;
216 if ((mask & 0x1) == 0x1) {
217 auto ts = HAL_ReadInterruptRisingTimestamp(interruptHandle, &status);
218 // Rising Edge
219 interruptData.risingStamps.push_back(ts);
220 } else if ((mask & 0x100) == 0x100) {
221 auto ts = HAL_ReadInterruptFallingTimestamp(interruptHandle, &status);
222 // Falling Edge
223 interruptData.fallingStamps.push_back(ts);
224 }
225
226 if (interruptData.risingStamps.size() >= 4 &&
227 interruptData.fallingStamps.size() >= 4) {
228 interruptData.cond.notify_all();
229 runThread = false;
230 break;
231 }
232 }
233 });
234
235 // Ensure our interrupt actually got created correctly.
236 ASSERT_EQ(status, 0);
237 HAL_SetPWMSpeed(pwmHandle, (i - 1000) / 1000.0, &status);
238 ASSERT_EQ(status, 0);
239 {
240 std::unique_lock<wpi::priority_mutex> lock(interruptData.mutex);
241 // Wait for lock
242 // TODO: Add Timeout
243 auto timeout = interruptData.cond.wait_for(lock, std::chrono::seconds(2));
244 if (timeout == std::cv_status::timeout) {
245 runThread = false;
246 if (interruptThread.joinable()) {
247 interruptThread.join();
248 }
249 ASSERT_TRUE(false); // Exit test as failure on timeout
250 }
251 }
252
253 HAL_SetPWMRaw(pwmHandle, 0, &status);
254
255 // Ensure our interrupts have the proper counts
256 ASSERT_EQ(interruptData.risingStamps.size(),
257 interruptData.fallingStamps.size());
258
259 ASSERT_GT(interruptData.risingStamps.size(), 0u);
260
261 ASSERT_EQ(interruptData.risingStamps.size() % 2, 0u);
262 ASSERT_EQ(interruptData.fallingStamps.size() % 2, 0u);
263
264 for (size_t j = 0; j < interruptData.risingStamps.size(); j++) {
265 uint64_t width =
266 interruptData.fallingStamps[j] - interruptData.risingStamps[j];
267 ASSERT_NEAR(width, i, 10);
268 }
269
270 for (unsigned int j = 0; j < interruptData.risingStamps.size() - 1; j++) {
271 uint64_t period =
272 interruptData.risingStamps[j + 1] - interruptData.risingStamps[j];
273 ASSERT_NEAR(period, checkPeriod, 10);
274 }
275 runThread = false;
276 if (interruptThread.joinable()) {
277 interruptThread.join();
278 }
279 }
280}
281
282TEST_P(PWMTest, Timing4x) {
283 auto param = GetParam();
284 TestTiming(3, param);
285}
286
287TEST_P(PWMTest, Timing2x) {
288 auto param = GetParam();
289 TestTiming(1, param);
290}
291
292TEST_P(PWMTest, Timing1x) {
293 auto param = GetParam();
294 TestTiming(0, param);
295}
296
297TEST_P(PWMTest, TimingDMA4x) {
298 auto param = GetParam();
299 TestTimingDMA(3, param);
300}
301
302TEST_P(PWMTest, TimingDMA2x) {
303 auto param = GetParam();
304 TestTimingDMA(1, param);
305}
306
307TEST_P(PWMTest, TimingDMA1x) {
308 auto param = GetParam();
309 TestTimingDMA(0, param);
310}
311
312TEST(PWMTest, AllocateAll) {
313 wpi::SmallVector<PWMHandle, 21> pwmHandles;
314 for (int i = 0; i < HAL_GetNumPWMChannels(); i++) {
315 int32_t status = 0;
316 pwmHandles.emplace_back(PWMHandle(i, &status));
317 ASSERT_EQ(status, 0);
318 }
319}
320
321TEST(PWMTest, MultipleAllocateFails) {
322 int32_t status = 0;
323 PWMHandle handle(0, &status);
324 ASSERT_NE(handle, HAL_kInvalidHandle);
325 ASSERT_EQ(status, 0);
326
327 PWMHandle handle2(0, &status);
328 ASSERT_EQ(handle2, HAL_kInvalidHandle);
329 ASSERT_LAST_ERROR_STATUS(status, RESOURCE_IS_ALLOCATED);
330}
331
332TEST(PWMTest, OverAllocateFails) {
333 int32_t status = 0;
334 PWMHandle handle(HAL_GetNumPWMChannels(), &status);
335 ASSERT_EQ(handle, HAL_kInvalidHandle);
336 ASSERT_LAST_ERROR_STATUS(status, RESOURCE_OUT_OF_RANGE);
337}
338
339TEST(PWMTest, UnderAllocateFails) {
340 int32_t status = 0;
341 PWMHandle handle(-1, &status);
342 ASSERT_EQ(handle, HAL_kInvalidHandle);
343 ASSERT_LAST_ERROR_STATUS(status, RESOURCE_OUT_OF_RANGE);
344}
345
346TEST(PWMTest, CrossAllocationFails) {
347 int32_t status = 0;
348 DIOHandle dioHandle(10, true, &status);
349 ASSERT_NE(dioHandle, HAL_kInvalidHandle);
350 ASSERT_EQ(status, 0);
351 PWMHandle handle(10, &status);
352 ASSERT_EQ(handle, HAL_kInvalidHandle);
353 ASSERT_LAST_ERROR_STATUS(status, RESOURCE_IS_ALLOCATED);
354}
355
356INSTANTIATE_TEST_SUITE_P(PWMCrossConnectTests, PWMTest,
357 ::testing::ValuesIn(PWMCrossConnects));