libyggdrasil  v1.0.0
timer.hpp
Go to the documentation of this file.
1  /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2  * _____.___. .___ .__.__ *
3  * \__ | | ____ ____ __| _/___________ _____|__| | *
4  * / | |/ ___\ / ___\ / __ |\_ __ \__ \ / ___/ | | *
5  * \____ / /_/ > /_/ > /_/ | | | \// __ \_\___ \| | |__ *
6  * / ______\___ /\___ /\____ | |__| (____ /____ >__|____/ *
7  * \/ /_____//_____/ \/ \/ \/ *
8  * - Asgard - *
9  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
10  * This software can be used by students and other personal of the *
11  * Bern University of Applied Sciences under the terms of the MIT *
12  * license. *
13  * For other persons this software is under the terms of the GNU *
14  * General Public License version 2. *
15  * *
16  * Copyright © 2021, Bern University of Applied Sciences. *
17  * All rights reserved. *
18  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
26 #pragma once
27 
29 
30 #include <string>
31 #include <string_view>
32 #include <array>
33 
34 #include <cstdlib>
35 
36 #include <cmsis_gcc.h>
37 #include <limits>
38 #include <cstdio>
39 #include <cmath>
40 #include <stdio.h>
41 
42 namespace bsp::asg_coproc::drv {
43 
52  template<auto Context, u8 Channel, typename Size>
53  struct TimerChannel {
54  static_assert(Channel >= 1 && Channel <= 4, "Channel index out of range");
55 
62  ALWAYS_INLINE bool startPwm() const noexcept {
63  if(!hasPwmModule()) return false;
64  switch(Channel) {
65  case 1: Context->Instance->CCER |= TIM_CCER_CC1E; break; // Enable capture compare output for channel 1
66  case 2: Context->Instance->CCER |= TIM_CCER_CC2E; break; // Enable capture compare output for channel 2
67  case 3: Context->Instance->CCER |= TIM_CCER_CC3E; break; // Enable capture compare output for channel 3
68  case 4: Context->Instance->CCER |= TIM_CCER_CC4E; break; // Enable capture compare output for channel 4
69  default: bsp::unreachable();
70  }
71  Context->Instance->CR1 = TIM_CR1_CEN; // Enable the counter
72  return true;
73  }
74 
81  ALWAYS_INLINE bool stopPwm() const noexcept {
82  if(!hasPwmModule()) return false;
83  switch(Channel) {
84  case 1: Context->Instance->CCER &= ~TIM_CCER_CC1E; break; // Disable capture compare output for channel 1
85  case 2: Context->Instance->CCER &= ~TIM_CCER_CC2E; break; // Disable capture compare output for channel 2
86  case 3: Context->Instance->CCER &= ~TIM_CCER_CC3E; break; // Disable capture compare output for channel 3
87  case 4: Context->Instance->CCER &= ~TIM_CCER_CC4E; break; // Disable capture compare output for channel 4
88  default: bsp::unreachable();
89  }
90  if(Context->Instance->CCER == 0) Context->Instance->CR1 &= ~TIM_CR1_CEN; // Disable the counter when all pwm channels are disabled
91  return true;
92  }
93 
100  ALWAYS_INLINE bool setPolarityHigh(bool highActive = true) const noexcept {
101  if(!hasPwmModule()) return false;
102  if(!highActive){
103  switch(Channel) {
104  case 1: Context->Instance->CCER |= TIM_CCER_CC1P; break; // Capture/Compare 1 output Polarity to low
105  case 2: Context->Instance->CCER |= TIM_CCER_CC2P; break; // Capture/Compare 2 output Polarity to low
106  case 3: Context->Instance->CCER |= TIM_CCER_CC3P; break; // Capture/Compare 3 output Polarity to low
107  case 4: Context->Instance->CCER |= TIM_CCER_CC4P; break; // Capture/Compare 4 output Polarity to low
108  default: bsp::unreachable();
109  }
110  }
111  else {
112  switch(Channel) {
113  case 1: Context->Instance->CCER &= ~TIM_CCER_CC1P; break; // Capture/Compare 1 output Polarity to high
114  case 2: Context->Instance->CCER &= ~TIM_CCER_CC2P; break; // Capture/Compare 2 output Polarity to high
115  case 3: Context->Instance->CCER &= ~TIM_CCER_CC3P; break; // Capture/Compare 3 output Polarity to high
116  case 4: Context->Instance->CCER &= ~TIM_CCER_CC4P; break; // Capture/Compare 4 output Polarity to high
117  default: bsp::unreachable();
118  }
119  }
120 
121  return true;
122  }
123 
130  ALWAYS_INLINE bool setDutyCycle(float dutyCycle) const noexcept {
131  if(!hasPwmModule()) return false;
132  dutyCycle = std::abs(dutyCycle); // Make sure that the value is positive
133  if(dutyCycle > 100) dutyCycle = 100; // Limit the duty cycle to 100
134  Size arr = Context->Instance->ARR; // Get auto reload register
135  Size ccr = 0;
136  if(dutyCycle != 0) ccr = arr / 100 * dutyCycle; // Calculate capture compare register value
137  switch(Channel) {
138  case 1: Context->Instance->CCR1 = ccr; break; // Set the duty cycle for channel 1
139  case 2: Context->Instance->CCR2 = ccr; break; // Set the duty cycle for channel 2
140  case 3: Context->Instance->CCR3 = ccr; break; // Set the duty cycle for channel 3
141  case 4: Context->Instance->CCR4 = ccr; break; // Set the duty cycle for channel 4
142  default: bsp::unreachable();
143  }
144 
145  return true;
146  }
147 
148  private:
154  bool hasPwmModule() const noexcept{
155  if ((Context->Instance == TIM6) || // Checks if the timer does not have a pwm module (less to check)
156  (Context->Instance == TIM7)) {
157  return false;
158  }
159  return true;
160  }
161 
162  };
163 
164 
172  template<auto Context, typename Size>
173  struct TimerEncoder {
174 
178  enum class Direction : u8 {
179  Clockwise,
181  };
182 
186  enum class Mode : u8 {
189  };
190 
197  ALWAYS_INLINE bool enable() const noexcept {
198  if(!hasEncoderModule()) return false; // Check if the timer got a pwm modul
199  Context->Instance->CCER = TIM_CCER_CC1E | TIM_CCER_CC2E; // Enable capture compare channel 1 and 2
200  Context->Instance->CR1 = TIM_CR1_CEN; // Enable the timer
201  return true;
202  }
203 
210  ALWAYS_INLINE bool disable() const noexcept {
211  if(!hasEncoderModule()) return false; // Check if the timer got a pwm modul
212  Context->Instance->CR1 &= ~TIM_CR1_CEN; // Disable the timer
213  Context->Instance->CCER &= ~(TIM_CCER_CC1E | TIM_CCER_CC2E); // Disable capture compare channel 1 and 2
214  return true;
215  }
216 
223  ALWAYS_INLINE Size getCount() const noexcept {
224  return Context->Instance->CNT; // Read the counter value form the register
225  }
226 
232  ALWAYS_INLINE void setCount(Size cnt) const noexcept {
233  Context->Instance->CNT = cnt; // Write the new counter value to the register
234  }
235 
243  return (Context->Instance->CR1 & TIM_CR1_DIR) ? Direction::CounterClockwise : Direction::Clockwise;
244  }
245 
251  ALWAYS_INLINE void setMode(Mode mode) const noexcept {
252  Context->Instance->CR1 &= ~TIM_CR1_CEN; // Disable the timer
253  Context->Instance->SMCR &= ~(TIM_SMCR_SMS_0 | TIM_SMCR_SMS_1); // Reset the mode bits
254  switch(mode) { // Set the new mode according to
255  case Mode::_48StepsPerTurn: Context->Instance->SMCR |= TIM_SMCR_SMS_0; break; // Counter counts up/down on TI1FP1 edge depending on TI2FP2 level
256  case Mode::_96StepsPerTurn: Context->Instance->SMCR |= TIM_SMCR_SMS_0 | TIM_SMCR_SMS_1; break; // Counter counts up/down on both TI1FP1 and TI2FP2 edges depending on the level of the other input
257  }
258  Context->Instance->CR1 = TIM_CR1_CEN; // Enable the timer
259  }
260 
267  ALWAYS_INLINE bool init() const noexcept {
268  if(!hasEncoderModule()) return false; // Check if the timer got a pwm modul
269  Context->Instance->CCER = TIM_CCER_CC1E | TIM_CCER_CC2E; // Enable capture compare 1 and 2
270  Context->Instance->SMCR |= TIM_SMCR_SMS_0 | TIM_SMCR_SMS_1; // Counter counts up/down on both TI1FP1 and TI2FP2 edges depending on the level of the other input
271  Context->Instance->CR1 = TIM_CR1_CEN; // Enable the timer
272  return true;
273  }
274 
275  private:
281  bool hasEncoderModule() const noexcept{
282  if ((Context->Instance == TIM1) || // All timer with a Encoder Module
283  (Context->Instance == TIM2) ||
284  (Context->Instance == TIM3) ||
285  (Context->Instance == TIM4) ||
286  (Context->Instance == TIM5) ||
287  (Context->Instance == TIM8) ||
288  (Context->Instance == TIM12) ||
289  (Context->Instance == TIM15)) {
290  return true;
291  }
292  return false;
293  }
294  };
295 
303  template<auto Context, typename Size>
305 
306 
310  ALWAYS_INLINE void start() const noexcept {
311  Context->Instance->CR1 = TIM_CR1_CEN; // Enable the timer
312  }
313 
317  ALWAYS_INLINE void stop() const noexcept {
318  Context->Instance->CR1 &= ~TIM_CR1_CEN; // Disable the timer
319  }
320 
324  ALWAYS_INLINE void reset() const noexcept {
325  Context->Instance->CNT = 0;
326  }
327 
333  u64 getTimeToOverflow() const noexcept {
334  return cntToNanoSeconds(std::numeric_limits<Size>::max());
335  }
336 
342  std::string getFormattedTimeToOverflow() const noexcept {
343  return formatToString(cntToNanoSeconds(std::numeric_limits<Size>::max()));
344  }
345 
351  u64 getPassedTime() const noexcept {
352  return cntToNanoSeconds(Context->Instance->CNT);
353  }
354 
360  std::string getFormattedPassedTime() const noexcept {
361  return formatToString(cntToNanoSeconds(Context->Instance->CNT));
362  }
363 
370  std::string formatToString(u64 passedTime) const noexcept {
371  std::string buffer(0xFF, 0x00);
372  u32 s = passedTime / 1E9;
373  u16 ms = static_cast<u32>(passedTime / 1E6) % 1000;
374  u16 us = static_cast<u32>(passedTime / 1E3) % 1000;
375  u16 ns = passedTime % 1000;
376 
377  snprintf(buffer.data(), buffer.size(), "%lus %dms %dus %dns", s, ms, us, ns);
378 
379  return buffer;
380  }
381 
382 
383  private:
384 
391  u64 cntToNanoSeconds(Size cntValue) const noexcept {
392  float timerFrequency;
393 
394  if ((Context->Instance == TIM1) || // Get the timerFrequency before prescaler
395  (Context->Instance == TIM8) || // this is depending on the APBx bus different
396  (Context->Instance == TIM15) ||
397  (Context->Instance == TIM16) ||
398  (Context->Instance == TIM17)) {
399  float pclk2 = static_cast<float>(HAL_RCC_GetPCLK2Freq()); // Get the timer clock before prescaler for APB2
400  if ((RCC->TIMG2PRER & RCC_TIMG2PRER_TIMG2PRE) == 0) timerFrequency = pclk2; // Timer frequency when APB2 prescaler = 1
401  else timerFrequency = 2 * pclk2; // Timer frequency when APB2 prescaler = 2
402 
403  }
404  else {
405  float pclk1 = static_cast<float>(HAL_RCC_GetPCLK1Freq()); // Get the timer clock before prescaler for APB1
406  if ((RCC->TIMG1PRER & RCC_TIMG1PRER_TIMG1PRE) == 0) timerFrequency = pclk1; // Timer frequency when APB1 prescaler = 1
407  else timerFrequency = 2 * pclk1; // Timer frequency when APB1 prescaler = 1
408  }
409 
410  return static_cast<u64>(((cntValue) / timerFrequency) * 1E9); // Calculate the passed time in ns
411 
412 
413  }
414 
415 
416  };
417 
425  template<auto Context, typename Size>
426  struct Timer {
427 
433  template<u8 Number>
434  static constexpr auto Channel = TimerChannel<Context, Number, Size>();
435 
439  static constexpr auto Encoder = TimerEncoder<Context, Size>();
440 
445 
451  static bool init() {
452  return true;
453  }
454 
460  static bool deinit() {
461  return true;
462  }
463 
464 
468  static void enable() {
469  Context->Instance->CR1 = TIM_CR1_CEN; // Enable the timer
470  }
471 
475  static void disable() {
476  Context->Instance->CR1 &= ~TIM_CR1_CEN; // Disable the timer
477  }
478 
479 
485  static Size getCount() {
486  return Context->Instance->CNT;
487  }
488 
494  static void setCount(Size cnt) {
495  Context->Instance->CNT = cnt;
496  }
497 
504  static u32 getPwmFrequency() {
505  auto arr = Context->Instance->ARR;
506  auto psc = (Context->Instance->PSC + 1);
507 
508  if ((Context->Instance == TIM1) || // Get the timerFrequency before prescaler
509  (Context->Instance == TIM8) || // this is depending on the APBx bus different
510  (Context->Instance == TIM15) ||
511  (Context->Instance == TIM16) ||
512  (Context->Instance == TIM17)) {
513  float pclk2 = static_cast<float>(HAL_RCC_GetPCLK2Freq()); // Get the timer clock before prescaler for APB2
514  if ((RCC->TIMG2PRER & RCC_TIMG2PRER_TIMG2PRE) == 0) return (pclk2 / psc) / arr; // Return the pwm frequency when APB2 prescaler = 1
515  else return 2 * (pclk2 / psc) / arr; // Return the pwm frequency when APB2 prescaler = 2
516 
517  }
518  else {
519  float pclk1 = static_cast<float>(HAL_RCC_GetPCLK1Freq()); // Get the timer clock before prescaler for APB1
520  if ((RCC->TIMG1PRER & RCC_TIMG1PRER_TIMG1PRE) == 0) return (pclk1 / psc) / arr; // Return the pwm frequency when APB1 prescaler = 1
521  else return 2 * (pclk1 / psc) / arr; // Return the pwm frequency when APB1 prescaler = 2
522  }
523  }
524 
535  static bool setPwmFrequency(u32 f_hz, Size resolution = 0){
536  u32 timerFrequency; // Timerfrequency depending on the RCC configuration
537  u32 psc = 0;
538  Size arr = 0;
539 
540  std::array<float, 4> dutyCycle; // Save the actual duty cycle for each channel
541  dutyCycle[0] = intToDuty(Context->Instance->CCR1);
542  dutyCycle[1] = intToDuty(Context->Instance->CCR2);
543  dutyCycle[2] = intToDuty(Context->Instance->CCR3);
544  dutyCycle[3] = intToDuty(Context->Instance->CCR4);
545 
546 
547 
548 
549  if(resolution){
550  Context->Instance->ARR = resolution; // Set a new auto reload value
551  }
552  arr = Context->Instance->ARR; // Get the auto reload register
553 
554  if ((Context->Instance == TIM1) || // Get the timerFrequency before prescaler
555  (Context->Instance == TIM8) || // this is depending on the APBx bus different
556  (Context->Instance == TIM15) ||
557  (Context->Instance == TIM16) ||
558  (Context->Instance == TIM17)) {
559  float pclk2 = static_cast<float>(HAL_RCC_GetPCLK2Freq()); // Get the timer clock before prescaler for APB2
560  if ((RCC->TIMG2PRER & RCC_TIMG2PRER_TIMG2PRE) == 0) timerFrequency = pclk2; // Timer frequency when APB2 prescaler = 1
561  else timerFrequency = 2 * pclk2; // Timer frequency when APB2 prescaler = 2
562 
563  }
564  else {
565  float pclk1 = static_cast<float>(HAL_RCC_GetPCLK1Freq()); // Get the timer clock before prescaler for APB1
566  if ((RCC->TIMG1PRER & RCC_TIMG1PRER_TIMG1PRE) == 0) timerFrequency = pclk1; // Timer frequency when APB1 prescaler = 1
567  else timerFrequency = 2 * pclk1; // Timer frequency when APB1 prescaler = 1
568  }
569 
570  if((f_hz * arr) > timerFrequency) return false; // Check if the timer frequency is not to low
571 
572  psc = std::round((timerFrequency / (f_hz * arr)) - 1); // Calculate the prescaler
573 
574  if(psc > 0xFFFF) return false; // Check if prescaler is in the possible range from u16
575  else{
576  Context->Instance->CR1 &= ~TIM_CR1_CEN; // Stop counter to set the new prescaler and until the new duty cycle values are set
577  Context->Instance->PSC = psc; // Set the new prescaler
578  }
579 
580  // Reset the stored duty cycle for each channel according to the resolution
581  Context->Instance->CCR1 = dutyToInt(dutyCycle[0]);
582  Context->Instance->CCR2 = dutyToInt(dutyCycle[1]);
583  Context->Instance->CCR3 = dutyToInt(dutyCycle[2]);
584  Context->Instance->CCR4 = dutyToInt(dutyCycle[3]);
585 
586  Context->Instance->CR1 = TIM_CR1_CEN; // Enable the timer again
587 
588  return true;
589  }
590 
591 
592 
593  private:
594 
601  static constexpr Size dutyToInt(float dutyCycle){
602  if(dutyCycle > 100) dutyCycle = 100;
603  else if(dutyCycle <= 0) return 0;
604  Size arr = Context->Instance->ARR;
605  return static_cast<Size>(arr / 100 * dutyCycle);
606  }
607 
614  static constexpr float intToDuty(Size intDutyCycle){
615  Size arr = Context->Instance->ARR;
616  if(arr == 0) return 0;
617  return static_cast<float>(intDutyCycle) / static_cast<float>(arr) * 100.0F;
618  }
619  };
620 
621 }
bsp::asg_coproc::drv::Timer::getPwmFrequency
static u32 getPwmFrequency()
Get the pwm frequency.
Definition: timer.hpp:504
bsp::asg_coproc::drv::TimerChannel::startPwm
ALWAYS_INLINE bool startPwm() const noexcept
Start PWM generation for the channel.
Definition: timer.hpp:62
u16
uint16_t u16
Definition: types.h:37
bsp::asg_coproc::drv::TimerChannel
Timer channel implementation for Asgard.
Definition: timer.hpp:53
u8
uint8_t u8
Unsigned integer definitions.
Definition: types.h:36
bsp::asg_coproc::drv
Definition: adc.hpp:32
bsp::asg_coproc::drv::TimerProfileCounter::getFormattedTimeToOverflow
std::string getFormattedTimeToOverflow() const noexcept
Get the time to an overflow.
Definition: timer.hpp:342
bsp::asg_coproc::drv::Timer::init
static bool init()
Init function.
Definition: timer.hpp:451
bsp::asg_coproc::drv::TimerEncoder::Mode::_96StepsPerTurn
@ _96StepsPerTurn
bsp::asg_coproc::drv::TimerProfileCounter::getPassedTime
u64 getPassedTime() const noexcept
Get the time passed time since the start.
Definition: timer.hpp:351
bsp::asg_coproc::drv::TimerProfileCounter::stop
ALWAYS_INLINE void stop() const noexcept
Stop the counter.
Definition: timer.hpp:317
bsp::asg_coproc::drv::Timer::ProfileCounter
static constexpr auto ProfileCounter
Timer used as profile counter.
Definition: timer.hpp:444
timer.hpp
Frontend for the Timer abstraction.
bsp::asg_coproc::drv::TimerProfileCounter::reset
ALWAYS_INLINE void reset() const noexcept
Reset the counter to 0.
Definition: timer.hpp:324
bsp::asg_coproc::drv::Timer
Timer implementation for Midgard.
Definition: timer.hpp:426
u64
uint64_t u64
Definition: types.h:39
bsp::asg_coproc::drv::Timer::getCount
static Size getCount()
Get the counter value.
Definition: timer.hpp:485
ALWAYS_INLINE
#define ALWAYS_INLINE
Definition: attributes.h:34
bsp::asg_coproc::drv::TimerEncoder::init
ALWAYS_INLINE bool init() const noexcept
Initialization function for the encoder.
Definition: timer.hpp:267
bsp::asg_coproc::drv::TimerChannel::stopPwm
ALWAYS_INLINE bool stopPwm() const noexcept
Stop PWM generation for the channel.
Definition: timer.hpp:81
bsp::asg_coproc::drv::TimerEncoder::setCount
ALWAYS_INLINE void setCount(Size cnt) const noexcept
Set the encoder counter value.
Definition: timer.hpp:232
bsp::asg_coproc::drv::Timer::deinit
static bool deinit()
Deinit function.
Definition: timer.hpp:460
bsp::asg_coproc::drv::TimerEncoder::enable
ALWAYS_INLINE bool enable() const noexcept
Enable the encoder mode.
Definition: timer.hpp:197
bsp::asg_coproc::drv::TimerEncoder::getCount
ALWAYS_INLINE Size getCount() const noexcept
Get the counter value.
Definition: timer.hpp:223
bsp::asg_coproc::drv::TimerEncoder::setMode
ALWAYS_INLINE void setMode(Mode mode) const noexcept
Set the mode of the encoder (48 or 96 counts per turn)
Definition: timer.hpp:251
u32
uint32_t u32
Definition: types.h:38
bsp::asg_coproc::drv::TimerEncoder::Mode
Mode
Modes for the encoder, 48 odr 96 steps per turn are possible.
Definition: timer.hpp:186
bsp::asg_coproc::drv::Timer::setCount
static void setCount(Size cnt)
Set the counter value.
Definition: timer.hpp:494
bsp::asg_coproc::drv::TimerProfileCounter::getTimeToOverflow
u64 getTimeToOverflow() const noexcept
Get the time to an overflow.
Definition: timer.hpp:333
bsp::asg_coproc::drv::Timer::disable
static void disable()
Disable the counter.
Definition: timer.hpp:475
bsp::asg_coproc::drv::TimerProfileCounter::getFormattedPassedTime
std::string getFormattedPassedTime() const noexcept
Get the time passed time since the start.
Definition: timer.hpp:360
bsp::asg_coproc::drv::Timer::Channel
static constexpr auto Channel
Timer channel.
Definition: timer.hpp:434
bsp::asg_coproc::drv::TimerProfileCounter::formatToString
std::string formatToString(u64 passedTime) const noexcept
Formats the passed time in ns to a string.
Definition: timer.hpp:370
bsp::asg_coproc::drv::TimerEncoder::Mode::_48StepsPerTurn
@ _48StepsPerTurn
bsp::asg_coproc::drv::Timer::Encoder
static constexpr auto Encoder
Timer in encoder mode.
Definition: timer.hpp:439
bsp::asg_coproc::drv::TimerProfileCounter
Profile counter implementation for Midgard.
Definition: timer.hpp:304
bsp::asg_coproc::drv::TimerChannel::setDutyCycle
ALWAYS_INLINE bool setDutyCycle(float dutyCycle) const noexcept
Set the duty cycle as a float value.
Definition: timer.hpp:130
bsp::asg_coproc::drv::TimerEncoder
Timer encoder implementation for Midgard.
Definition: timer.hpp:173
bsp::asg_coproc::drv::Timer::setPwmFrequency
static bool setPwmFrequency(u32 f_hz, Size resolution=0)
Set the pwm frequency and (optional) the maximal ticks within on cycle for all channels.
Definition: timer.hpp:535
bsp::asg_coproc::drv::TimerEncoder::disable
ALWAYS_INLINE bool disable() const noexcept
Disable the encoder mode.
Definition: timer.hpp:210
bsp::asg_coproc::drv::TimerEncoder::Direction::Clockwise
@ Clockwise
bsp::asg_coproc::drv::Timer::enable
static void enable()
Enable the counter.
Definition: timer.hpp:468
bsp::asg_coproc::drv::TimerEncoder::Direction
Direction
Last known turning direction of the encoder.
Definition: timer.hpp:178
bsp::asg_coproc::drv::TimerEncoder::Direction::CounterClockwise
@ CounterClockwise
bsp::asg_coproc::drv::TimerChannel::setPolarityHigh
ALWAYS_INLINE bool setPolarityHigh(bool highActive=true) const noexcept
Start set pwm polarity.
Definition: timer.hpp:100
bsp::asg_coproc::drv::TimerProfileCounter::start
ALWAYS_INLINE void start() const noexcept
Start the counter.
Definition: timer.hpp:310
bsp::asg_coproc::drv::TimerEncoder::getDirection
ALWAYS_INLINE Direction getDirection() const noexcept
Get the direction of the last rotation.
Definition: timer.hpp:242