aboutsummaryrefslogtreecommitdiff
path: root/pw_digital_io/public/pw_digital_io/digital_io.h
blob: ba8516b01e361052023689d2b4fd2e9544283f15 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
// Copyright 2021 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#pragma once

#include "pw_assert/check.h"
#include "pw_digital_io/internal/conversions.h"
#include "pw_function/function.h"
#include "pw_result/result.h"
#include "pw_status/status.h"
#include "pw_status/try.h"

namespace pw::digital_io {

// The logical state of a digital line.
enum class State : bool {
  kActive = true,
  kInactive = false,
};

// The triggering configuration for an interrupt handler.
enum class InterruptTrigger : int {
  // Trigger on transition from kInactive to kActive.
  kActivatingEdge,
  // Trigger on transition from kActive to kInactive.
  kDeactivatingEdge,
  // Trigger on any state transition between kActive and kInactive.
  kBothEdges,
};

// Interrupt handling function. The argument contains the latest known state of
// the line. It is backend-specific if, when, and how this state is updated.
using InterruptHandler = ::pw::Function<void(State sampled_state)>;

// A digital I/O line that may support input, output, and interrupts, but makes
// no guarantees about whether any operations are supported. You must check the
// various provides_* flags before calling optional methods. Unsupported methods
// invoke PW_CRASH.
//
// All methods are potentially blocking. Unless otherwise specified, access from
// multiple threads to a single line must be externally synchronized - for
// example using pw::Borrowable. Unless otherwise specified, none of the methods
// are safe to call from an interrupt handler. Therefore, this abstraction may
// not be suitable for bitbanging and other low-level uses of GPIO.
//
// Note that the initial state of a line is not guaranteed to be consistent with
// either the "enabled" or "disabled" state. Users of the API who need to ensure
// the line is disabled (ex. output not driving the line) should call Disable.
//
// This class should almost never be used in APIs directly. Instead, use one of
// the derived classes that explicitly supports the functionality that your
// API needs.
//
// This class cannot be extended directly. Instead, extend one of the
// derived classes that explicitly support the functionality that you want to
// implement.
//
class DigitalIoOptional {
 public:
  virtual ~DigitalIoOptional() = default;

  // True if input (getting state) is supported.
  constexpr bool provides_input() const { return config_.input; }
  // True if output (setting state) is supported.
  constexpr bool provides_output() const { return config_.output; }
  // True if interrupt handlers can be registered.
  constexpr bool provides_interrupt() const { return config_.interrupt; }

  // Get the state of the line.
  //
  // This method is not thread-safe and cannot be used in interrupt handlers.
  //
  // Returns:
  //
  //   OK - an active or inactive state.
  //   FAILED_PRECONDITION - The line has not been enabled.
  //   Other status codes as defined by the backend.
  //
  Result<State> GetState() { return DoGetState(); }

  // Set the state of the line.
  //
  // Callers are responsible to wait for the voltage level to settle after this
  // call returns.
  //
  // This method is not thread-safe and cannot be used in interrupt handlers.
  //
  // Returns:
  //
  //   OK - the state has been set.
  //   FAILED_PRECONDITION - The line has not been enabled.
  //   Other status codes as defined by the backend.
  //
  Status SetState(State state) { return DoSetState(state); }

  // Check if the line is in the active state.
  //
  // The line is in the active state when GetState() returns State::kActive.
  //
  // This method is not thread-safe and cannot be used in interrupt handlers.
  //
  // Returns:
  //
  //   OK - true if the line is in the active state, otherwise false.
  //   FAILED_PRECONDITION - The line has not been enabled.
  //   Other status codes as defined by the backend.
  //
  Result<bool> IsStateActive() {
    PW_TRY_ASSIGN(const State state, GetState());
    return state == State::kActive;
  }

  // Sets the line to the active state. Equivalent to SetState(State::kActive).
  //
  // Callers are responsible to wait for the voltage level to settle after this
  // call returns.
  //
  // This method is not thread-safe and cannot be used in interrupt handlers.
  //
  // Returns:
  //
  //   OK - the state has been set.
  //   FAILED_PRECONDITION - The line has not been enabled.
  //   Other status codes as defined by the backend.
  //
  Status SetStateActive() { return SetState(State::kActive); }

  // Sets the line to the inactive state. Equivalent to
  // SetState(State::kInactive).
  //
  // Callers are responsible to wait for the voltage level to settle after this
  // call returns.
  //
  // This method is not thread-safe and cannot be used in interrupt handlers.
  //
  // Returns:
  //
  //   OK - the state has been set.
  //   FAILED_PRECONDITION - The line has not been enabled.
  //   Other status codes as defined by the backend.
  //
  Status SetStateInactive() { return SetState(State::kInactive); }

  // Set an interrupt handler to execute when an interrupt is triggered, and
  // Configure the condition for triggering the interrupt.
  //
  // The handler is executed in a backend-specific context - this may be a
  // system interrupt handler or a shared notification thread. Do not do any
  // blocking or expensive work in the handler. The only universally safe
  // operations are the IRQ-safe functions on pw_sync primitives.
  //
  // In particular, it is NOT safe to get the state of a DigitalIo line - either
  // from this line or any other DigitalIoOptional instance - inside the
  // handler.
  //
  // This method is not thread-safe and cannot be used in interrupt handlers.
  //
  // Precondition: no handler is currently set.
  //
  // Returns:
  //   OK - the interrupt handler was configured.
  //   INVALID_ARGUMENT - handler is empty.
  //   Other status codes as defined by the backend.
  //
  Status SetInterruptHandler(InterruptTrigger trigger,
                             InterruptHandler&& handler) {
    if (handler == nullptr) {
      return Status::InvalidArgument();
    }
    return DoSetInterruptHandler(trigger, std::move(handler));
  }

  // Clear the interrupt handler and disable interrupts if enabled.
  //
  // This method is not thread-safe and cannot be used in interrupt handlers.
  //
  // Returns:
  //   OK - the itnerrupt handler was cleared.
  //   Other status codes as defined by the backend.
  //
  Status ClearInterruptHandler() {
    return DoSetInterruptHandler(InterruptTrigger::kActivatingEdge, nullptr);
  }

  // Enable interrupts which will trigger the interrupt handler.
  //
  // This method is not thread-safe and cannot be used in interrupt handlers.
  //
  // Precondition: a handler has been set using SetInterruptHandler.
  //
  // Returns:
  //   OK - the interrupt handler was configured.
  //   FAILED_PRECONDITION - The line has not been enabled.
  //   Other status codes as defined by the backend.
  //
  Status EnableInterruptHandler() { return DoEnableInterruptHandler(true); }

  // Disable the interrupt handler. This is a no-op if interrupts are disabled.
  //
  // This method can be called inside the interrupt handler for this line
  // without any external synchronization. However, the exact behavior is
  // backend-specific. There may be queued events that will trigger the handler
  // again after this call returns.
  //
  // Returns:
  //   OK - the interrupt handler was configured.
  //   Other status codes as defined by the backend.
  //
  Status DisableInterruptHandler() { return DoEnableInterruptHandler(false); }

  // Enable the line to initialize it into the default state as determined by
  // the backend. This may enable pull-up/down resistors, drive the line high or
  // low, etc. The line must be enabled before getting/setting the state
  // or enabling interrupts.
  //
  // Callers are responsible to wait for the voltage level to settle after this
  // call returns.
  //
  // This method is not thread-safe and cannot be used in interrupt handlers.
  //
  // Returns:
  //   OK - the line is enabled and ready for use.
  //   Other status codes as defined by the backend.
  //
  Status Enable() { return DoEnable(true); }

  // Disable the line to power down any pull-up/down resistors and disconnect
  // from any voltage sources. This is usually done to save power. Interrupt
  // handlers are automatically disabled.
  //
  // This method is not thread-safe and cannot be used in interrupt handlers.
  //
  // Returns:
  //   OK - the line is disabled.
  //   Other status codes as defined by the backend.
  //
  Status Disable() { return DoEnable(false); }

 private:
  friend class DigitalInterrupt;
  friend class DigitalIn;
  friend class DigitalInInterrupt;
  friend class DigitalOut;
  friend class DigitalOutInterrupt;
  friend class DigitalInOut;
  friend class DigitalInOutInterrupt;

  // Private constructor so that only friends can extend us.
  constexpr DigitalIoOptional(internal::Provides config) : config_(config) {}

  // Implemented by derived classes to provide different functionality.
  // See the documentation of the public functions for requirements.
  virtual Status DoEnable(bool enable) = 0;
  virtual Result<State> DoGetState() = 0;
  virtual Status DoSetState(State level) = 0;
  virtual Status DoSetInterruptHandler(InterruptTrigger trigger,
                                       InterruptHandler&& handler) = 0;
  virtual Status DoEnableInterruptHandler(bool enable) = 0;

  // The configuration of this line.
  const internal::Provides config_;
};

// A digital I/O line that supports only interrupts.
//
// The input and output methods are hidden and must not be called.
//
// Use this class in APIs when only interrupt functionality is required.
// Extend this class to implement a line that only supports interrupts.
//
class DigitalInterrupt
    : public DigitalIoOptional,
      public internal::Conversions<DigitalInterrupt, DigitalIoOptional> {
 public:
  // Available functionality
  using DigitalIoOptional::ClearInterruptHandler;
  using DigitalIoOptional::DisableInterruptHandler;
  using DigitalIoOptional::EnableInterruptHandler;
  using DigitalIoOptional::SetInterruptHandler;

 protected:
  constexpr DigitalInterrupt()
      : DigitalIoOptional(internal::AlwaysProvidedBy<DigitalInterrupt>()) {}

 private:
  // Unavailable functionality
  using DigitalIoOptional::provides_input;
  using DigitalIoOptional::provides_interrupt;
  using DigitalIoOptional::provides_output;

  using DigitalIoOptional::GetState;
  using DigitalIoOptional::IsStateActive;
  using DigitalIoOptional::SetState;
  using DigitalIoOptional::SetStateActive;
  using DigitalIoOptional::SetStateInactive;

  // These overrides invoke PW_CRASH.
  Status DoSetState(State) final;
  Result<State> DoGetState() final;
};

// A digital I/O line that supports only input (getting state).
//
// The output and interrupt methods are hidden and must not be called.
//
// Use this class in APIs when only input functionality is required.
// Extend this class to implement a line that only supports getting state.
//
class DigitalIn : public DigitalIoOptional,
                  public internal::Conversions<DigitalIn, DigitalIoOptional> {
 public:
  // Available functionality
  using DigitalIoOptional::GetState;
  using DigitalIoOptional::IsStateActive;

 protected:
  constexpr DigitalIn()
      : DigitalIoOptional(internal::AlwaysProvidedBy<DigitalIn>()) {}

 private:
  // Unavailable functionality
  using DigitalIoOptional::provides_input;
  using DigitalIoOptional::provides_interrupt;
  using DigitalIoOptional::provides_output;

  using DigitalIoOptional::ClearInterruptHandler;
  using DigitalIoOptional::DisableInterruptHandler;
  using DigitalIoOptional::EnableInterruptHandler;
  using DigitalIoOptional::SetInterruptHandler;
  using DigitalIoOptional::SetState;
  using DigitalIoOptional::SetStateActive;
  using DigitalIoOptional::SetStateInactive;

  // These overrides invoke PW_CRASH.
  Status DoSetState(State) final;
  Status DoSetInterruptHandler(InterruptTrigger, InterruptHandler&&) final;
  Status DoEnableInterruptHandler(bool) final;
};

// An input line that supports interrupts.
//
// The output methods are hidden and must not be called.
//
// Use in APIs when input and interrupt functionality is required.
//
// Extend this class to implement a line that supports input (getting state) and
// listening for interrupts at the same time.
//
class DigitalInInterrupt
    : public DigitalIoOptional,
      public internal::Conversions<DigitalInInterrupt, DigitalIoOptional> {
 public:
  // Available functionality
  using DigitalIoOptional::ClearInterruptHandler;
  using DigitalIoOptional::DisableInterruptHandler;
  using DigitalIoOptional::EnableInterruptHandler;
  using DigitalIoOptional::GetState;
  using DigitalIoOptional::IsStateActive;
  using DigitalIoOptional::SetInterruptHandler;

 protected:
  constexpr DigitalInInterrupt()
      : DigitalIoOptional(internal::AlwaysProvidedBy<DigitalInInterrupt>()) {}

 private:
  // Unavailable functionality
  using DigitalIoOptional::provides_input;
  using DigitalIoOptional::provides_interrupt;
  using DigitalIoOptional::provides_output;

  using DigitalIoOptional::SetState;
  using DigitalIoOptional::SetStateActive;
  using DigitalIoOptional::SetStateInactive;

  // These overrides invoke PW_CRASH.
  Status DoSetState(State) final;
};

// A digital I/O line that supports only output (setting state).
//
// Input and interrupt functions are hidden and must not be called.
//
// Use in APIs when only output functionality is required.
// Extend this class to implement a line that supports output only.
//
class DigitalOut : public DigitalIoOptional,
                   public internal::Conversions<DigitalOut, DigitalIoOptional> {
 public:
  // Available functionality
  using DigitalIoOptional::SetState;
  using DigitalIoOptional::SetStateActive;
  using DigitalIoOptional::SetStateInactive;

 protected:
  constexpr DigitalOut()
      : DigitalIoOptional(internal::AlwaysProvidedBy<DigitalOut>()) {}

 private:
  // Unavailable functionality
  using DigitalIoOptional::provides_input;
  using DigitalIoOptional::provides_interrupt;
  using DigitalIoOptional::provides_output;

  using DigitalIoOptional::ClearInterruptHandler;
  using DigitalIoOptional::DisableInterruptHandler;
  using DigitalIoOptional::EnableInterruptHandler;
  using DigitalIoOptional::GetState;
  using DigitalIoOptional::IsStateActive;
  using DigitalIoOptional::SetInterruptHandler;

  // These overrides invoke PW_CRASH.
  Result<State> DoGetState() final;
  Status DoSetInterruptHandler(InterruptTrigger, InterruptHandler&&) final;
  Status DoEnableInterruptHandler(bool) final;
};

// A digital I/O line that supports output and interrupts.
//
// Input methods are hidden and must not be called.
//
// Use in APIs when output and interrupt functionality is required. For
// example, to represent a two-way signalling line.
//
// Extend this class to implement a line that supports both output and
// listening for interrupts at the same time.
//
class DigitalOutInterrupt
    : public DigitalIoOptional,
      public internal::Conversions<DigitalOutInterrupt, DigitalIoOptional> {
 public:
  // Available functionality
  using DigitalIoOptional::ClearInterruptHandler;
  using DigitalIoOptional::DisableInterruptHandler;
  using DigitalIoOptional::EnableInterruptHandler;
  using DigitalIoOptional::SetInterruptHandler;
  using DigitalIoOptional::SetState;
  using DigitalIoOptional::SetStateActive;
  using DigitalIoOptional::SetStateInactive;

 protected:
  constexpr DigitalOutInterrupt()
      : DigitalIoOptional(internal::AlwaysProvidedBy<DigitalOutInterrupt>()) {}

 private:
  // Unavailable functionality
  using DigitalIoOptional::provides_input;
  using DigitalIoOptional::provides_interrupt;
  using DigitalIoOptional::provides_output;

  using DigitalIoOptional::GetState;
  using DigitalIoOptional::IsStateActive;

  // These overrides invoke PW_CRASH.
  Result<State> DoGetState() final;
};

// A digital I/O line that supports both input and output.
//
// Use in APIs when both input and output functionality is required. For
// example, to represent a line which is shared by multiple controllers.
//
// Extend this class to implement a line that supports both input and output at
// the same time.
//
class DigitalInOut
    : public DigitalIoOptional,
      public internal::Conversions<DigitalInOut, DigitalIoOptional> {
 public:
  // Available functionality
  using DigitalIoOptional::GetState;
  using DigitalIoOptional::IsStateActive;
  using DigitalIoOptional::SetState;
  using DigitalIoOptional::SetStateActive;
  using DigitalIoOptional::SetStateInactive;

 protected:
  constexpr DigitalInOut()
      : DigitalIoOptional(internal::AlwaysProvidedBy<DigitalInOut>()) {}

 private:
  // Unavailable functionality
  using DigitalIoOptional::provides_input;
  using DigitalIoOptional::provides_interrupt;
  using DigitalIoOptional::provides_output;

  using DigitalIoOptional::ClearInterruptHandler;
  using DigitalIoOptional::DisableInterruptHandler;
  using DigitalIoOptional::EnableInterruptHandler;
  using DigitalIoOptional::SetInterruptHandler;

  // These overrides invoke PW_CRASH.
  Status DoSetInterruptHandler(InterruptTrigger, InterruptHandler&&) final;
  Status DoEnableInterruptHandler(bool) final;
};

// A line that supports input, output, and interrupts.
//
// Use in APIs when input, output, and interrupts are required. For example to
// represent a two-way shared line with state transition notifications.
//
// Extend this class to implement a line that supports all the functionality at
// the same time.
//
class DigitalInOutInterrupt
    : public DigitalIoOptional,
      public internal::Conversions<DigitalInOutInterrupt, DigitalIoOptional> {
 public:
  // Available functionality
  using DigitalIoOptional::ClearInterruptHandler;
  using DigitalIoOptional::DisableInterruptHandler;
  using DigitalIoOptional::EnableInterruptHandler;
  using DigitalIoOptional::GetState;
  using DigitalIoOptional::IsStateActive;
  using DigitalIoOptional::SetInterruptHandler;
  using DigitalIoOptional::SetState;
  using DigitalIoOptional::SetStateActive;
  using DigitalIoOptional::SetStateInactive;

 protected:
  constexpr DigitalInOutInterrupt()
      : DigitalIoOptional(internal::AlwaysProvidedBy<DigitalInOutInterrupt>()) {
  }

 private:
  // Unavailable functionality
  using DigitalIoOptional::provides_input;
  using DigitalIoOptional::provides_interrupt;
  using DigitalIoOptional::provides_output;
};

}  // namespace pw::digital_io