blob: d209658c9ce39e02dff49dac9a3a0475daad9ac9 [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
James Kuszmaulb13e13f2023-11-22 20:44:04 -08008#include <gtest/gtest.h>
Austin Schuh812d0d12021-11-04 20:16:48 -07009#include <hal/DMA.h>
10#include <hal/HAL.h>
11#include <wpi/SmallVector.h>
12#include <wpi/condition_variable.h>
13#include <wpi/priority_mutex.h>
14
15#include "CrossConnects.h"
16#include "LifetimeWrappers.h"
Austin Schuh812d0d12021-11-04 20:16:48 -070017
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
James Kuszmaulb13e13f2023-11-22 20:44:04 -080035 HAL_SetPWMPulseTimeMicroseconds(pwmHandle, 0, &status);
36 HAL_SetPWMConfigMicroseconds(pwmHandle, 2000, 1000, 1000, 0, 0, &status);
Austin Schuh812d0d12021-11-04 20:16:48 -070037 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);
James Kuszmaulcf324122023-01-14 14:07:17 -0800103 if (value) {
Austin Schuh812d0d12021-11-04 20:16:48 -0700104 break;
James Kuszmaulcf324122023-01-14 14:07:17 -0800105 }
Austin Schuh812d0d12021-11-04 20:16:48 -0700106 startIndex++;
107 }
108 ASSERT_LT(startIndex, 6);
109
110 // Check that samples alternate
111 bool previous = false;
112 int iterationCount = 0;
113 for (int i = startIndex; i < startIndex + 8; i++) {
114 auto value =
115 HAL_GetDMASampleDigitalSource(&dmaSamples[i], dioHandle, &status);
116 ASSERT_EQ(0, status);
117 ASSERT_NE(previous, value);
118 previous = !previous;
119 iterationCount++;
120 }
121 ASSERT_EQ(iterationCount, 8);
122 iterationCount = 0;
123
124 // Check width between samples
125 for (int i = startIndex; i < startIndex + 8; i += 2) {
126 auto width = HAL_GetDMASampleTime(&dmaSamples[i + 1], &status) -
127 HAL_GetDMASampleTime(&dmaSamples[i], &status);
128 ASSERT_NEAR(testWidth, width, 10);
129 iterationCount++;
130 }
131 ASSERT_EQ(iterationCount, 4);
132 iterationCount = 0;
133
134 // Check period between samples
135 for (int i = startIndex; i < startIndex + 6; i += 2) {
136 auto period = HAL_GetDMASampleTime(&dmaSamples[i + 2], &status) -
137 HAL_GetDMASampleTime(&dmaSamples[i], &status);
138 ASSERT_NEAR(checkPeriod, period, 10);
139 iterationCount++;
140 }
141 ASSERT_EQ(iterationCount, 3);
142 }
143}
144
145struct InterruptCheckData {
146 wpi::SmallVector<uint64_t, 8> risingStamps;
147 wpi::SmallVector<uint64_t, 8> fallingStamps;
148 wpi::priority_mutex mutex;
149 wpi::condition_variable cond;
150 HAL_InterruptHandle handle;
151};
152
153// TODO switch this to DMA
154void TestTiming(int squelch, std::pair<int, int> param) {
155 // Initialize interrupt
156 int32_t status = 0;
157 InterruptHandle interruptHandle(&status);
158 // Ensure we have a valid interrupt handle
159 ASSERT_NE(interruptHandle, HAL_kInvalidHandle);
160
161 status = 0;
162 PWMHandle pwmHandle(param.first, &status);
163 ASSERT_NE(pwmHandle, HAL_kInvalidHandle);
164
165 // Ensure our PWM is disabled, and set up properly
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800166 HAL_SetPWMPulseTimeMicroseconds(pwmHandle, 0, &status);
167 HAL_SetPWMConfigMicroseconds(pwmHandle, 2000, 1000, 1000, 0, 0, &status);
Austin Schuh812d0d12021-11-04 20:16:48 -0700168 HAL_SetPWMPeriodScale(pwmHandle, squelch, &status);
169
170 unsigned int checkPeriod = 0;
171 switch (squelch) {
172 case (0):
173 checkPeriod = 5050;
174 break;
175 case (1):
176 checkPeriod = 10100;
177 break;
178 case (3):
179 checkPeriod = 20200;
180 break;
181 }
182
183 status = 0;
184 DIOHandle dioHandle(param.second, true, &status);
185 ASSERT_NE(dioHandle, HAL_kInvalidHandle);
186
187 InterruptCheckData interruptData;
188 interruptData.handle = interruptHandle;
189
190 // Can use any type for the interrupt handle
191 HAL_RequestInterrupts(interruptHandle, dioHandle,
192 HAL_AnalogTriggerType::HAL_Trigger_kInWindow, &status);
193
194 HAL_SetInterruptUpSourceEdge(interruptHandle, true, true, &status);
195
196 // Loop to test 5 speeds
197 for (unsigned int i = 1000; i < 2100; i += 250) {
198 interruptData.risingStamps.clear();
199 interruptData.fallingStamps.clear();
200
201 std::atomic_bool runThread{true};
202
203 status = 0;
204 std::thread interruptThread([&]() {
205 while (runThread) {
206 int32_t threadStatus = 0;
207 auto mask =
208 HAL_WaitForInterrupt(interruptHandle, 5, true, &threadStatus);
209
210 if ((mask & 0x100) == 0x100 && interruptData.risingStamps.size() == 0 &&
211 interruptData.fallingStamps.size() == 0) {
212 // Falling edge at start of tracking. Skip
213 continue;
214 }
215
216 int32_t status = 0;
217 if ((mask & 0x1) == 0x1) {
218 auto ts = HAL_ReadInterruptRisingTimestamp(interruptHandle, &status);
219 // Rising Edge
220 interruptData.risingStamps.push_back(ts);
221 } else if ((mask & 0x100) == 0x100) {
222 auto ts = HAL_ReadInterruptFallingTimestamp(interruptHandle, &status);
223 // Falling Edge
224 interruptData.fallingStamps.push_back(ts);
225 }
226
227 if (interruptData.risingStamps.size() >= 4 &&
228 interruptData.fallingStamps.size() >= 4) {
229 interruptData.cond.notify_all();
230 runThread = false;
231 break;
232 }
233 }
234 });
235
236 // Ensure our interrupt actually got created correctly.
237 ASSERT_EQ(status, 0);
238 HAL_SetPWMSpeed(pwmHandle, (i - 1000) / 1000.0, &status);
239 ASSERT_EQ(status, 0);
240 {
241 std::unique_lock<wpi::priority_mutex> lock(interruptData.mutex);
242 // Wait for lock
243 // TODO: Add Timeout
244 auto timeout = interruptData.cond.wait_for(lock, std::chrono::seconds(2));
245 if (timeout == std::cv_status::timeout) {
246 runThread = false;
247 if (interruptThread.joinable()) {
248 interruptThread.join();
249 }
250 ASSERT_TRUE(false); // Exit test as failure on timeout
251 }
252 }
253
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800254 HAL_SetPWMPulseTimeMicroseconds(pwmHandle, 0, &status);
Austin Schuh812d0d12021-11-04 20:16:48 -0700255
256 // Ensure our interrupts have the proper counts
257 ASSERT_EQ(interruptData.risingStamps.size(),
258 interruptData.fallingStamps.size());
259
260 ASSERT_GT(interruptData.risingStamps.size(), 0u);
261
262 ASSERT_EQ(interruptData.risingStamps.size() % 2, 0u);
263 ASSERT_EQ(interruptData.fallingStamps.size() % 2, 0u);
264
265 for (size_t j = 0; j < interruptData.risingStamps.size(); j++) {
266 uint64_t width =
267 interruptData.fallingStamps[j] - interruptData.risingStamps[j];
268 ASSERT_NEAR(width, i, 10);
269 }
270
271 for (unsigned int j = 0; j < interruptData.risingStamps.size() - 1; j++) {
272 uint64_t period =
273 interruptData.risingStamps[j + 1] - interruptData.risingStamps[j];
274 ASSERT_NEAR(period, checkPeriod, 10);
275 }
276 runThread = false;
277 if (interruptThread.joinable()) {
278 interruptThread.join();
279 }
280 }
281}
282
283TEST_P(PWMTest, Timing4x) {
284 auto param = GetParam();
285 TestTiming(3, param);
286}
287
288TEST_P(PWMTest, Timing2x) {
289 auto param = GetParam();
290 TestTiming(1, param);
291}
292
293TEST_P(PWMTest, Timing1x) {
294 auto param = GetParam();
295 TestTiming(0, param);
296}
297
298TEST_P(PWMTest, TimingDMA4x) {
299 auto param = GetParam();
300 TestTimingDMA(3, param);
301}
302
303TEST_P(PWMTest, TimingDMA2x) {
304 auto param = GetParam();
305 TestTimingDMA(1, param);
306}
307
308TEST_P(PWMTest, TimingDMA1x) {
309 auto param = GetParam();
310 TestTimingDMA(0, param);
311}
312
313TEST(PWMTest, AllocateAll) {
314 wpi::SmallVector<PWMHandle, 21> pwmHandles;
315 for (int i = 0; i < HAL_GetNumPWMChannels(); i++) {
316 int32_t status = 0;
317 pwmHandles.emplace_back(PWMHandle(i, &status));
318 ASSERT_EQ(status, 0);
319 }
320}
321
322TEST(PWMTest, MultipleAllocateFails) {
323 int32_t status = 0;
324 PWMHandle handle(0, &status);
325 ASSERT_NE(handle, HAL_kInvalidHandle);
326 ASSERT_EQ(status, 0);
327
328 PWMHandle handle2(0, &status);
329 ASSERT_EQ(handle2, HAL_kInvalidHandle);
330 ASSERT_LAST_ERROR_STATUS(status, RESOURCE_IS_ALLOCATED);
331}
332
333TEST(PWMTest, OverAllocateFails) {
334 int32_t status = 0;
335 PWMHandle handle(HAL_GetNumPWMChannels(), &status);
336 ASSERT_EQ(handle, HAL_kInvalidHandle);
337 ASSERT_LAST_ERROR_STATUS(status, RESOURCE_OUT_OF_RANGE);
338}
339
340TEST(PWMTest, UnderAllocateFails) {
341 int32_t status = 0;
342 PWMHandle handle(-1, &status);
343 ASSERT_EQ(handle, HAL_kInvalidHandle);
344 ASSERT_LAST_ERROR_STATUS(status, RESOURCE_OUT_OF_RANGE);
345}
346
347TEST(PWMTest, CrossAllocationFails) {
348 int32_t status = 0;
349 DIOHandle dioHandle(10, true, &status);
350 ASSERT_NE(dioHandle, HAL_kInvalidHandle);
351 ASSERT_EQ(status, 0);
352 PWMHandle handle(10, &status);
353 ASSERT_EQ(handle, HAL_kInvalidHandle);
354 ASSERT_LAST_ERROR_STATUS(status, RESOURCE_IS_ALLOCATED);
355}
356
357INSTANTIATE_TEST_SUITE_P(PWMCrossConnectTests, PWMTest,
358 ::testing::ValuesIn(PWMCrossConnects));