diff --git a/tests/hsv_tests.cc b/tests/hsv_tests.cc new file mode 100644 index 0000000..9d5b73d --- /dev/null +++ b/tests/hsv_tests.cc @@ -0,0 +1,19 @@ +#include "doctest.h" +#include "util/hsv.hh" + +TEST_CASE("HSV to Color") { + // check primary colors + CHECK(Color{HSVColor{0, 255, 255}} == Colors::red); + CHECK(Color{HSVColor{86, 255, 255}} == Colors::green); + CHECK(Color{HSVColor{172, 255, 255}} == Colors::blue); + + // check no saturation + const auto ns = Color{HSVColor{172, 0, 255}}; + CHECK(ns.red() == ns.green()); + CHECK(ns.red() == ns.blue()); + CHECK(ns.red() == 255); + + // check value + const auto v = Color{HSVColor{0, 255, 0}}; + CHECK(v == Colors::black); +} diff --git a/tests/quadrature_encoder_tests.cc b/tests/quadrature_encoder_tests.cc new file mode 100644 index 0000000..097e1a6 --- /dev/null +++ b/tests/quadrature_encoder_tests.cc @@ -0,0 +1,55 @@ +#include "doctest.h" +#include "util/quadrature_encoder.hh" + +TEST_CASE("Quadrature Encoder") { + + using Enc = QuadratureEncoder<>; + + Enc encoder{}; + + // check clockwise + CHECK(encoder.get_motion(true, false) == Enc::None); + CHECK(encoder.get_motion(false, false) == Enc::None); + CHECK(encoder.get_motion(false, true) == Enc::None); + CHECK(encoder.get_motion(true, true) == Enc::CW); + + // check clockwise no state change + CHECK(encoder.get_motion(true, false) == Enc::None); + CHECK(encoder.get_motion(true, false) == Enc::None); + CHECK(encoder.get_motion(true, false) == Enc::None); + CHECK(encoder.get_motion(true, false) == Enc::None); + + CHECK(encoder.get_motion(false, false) == Enc::None); + CHECK(encoder.get_motion(false, false) == Enc::None); + CHECK(encoder.get_motion(false, false) == Enc::None); + CHECK(encoder.get_motion(false, false) == Enc::None); + + CHECK(encoder.get_motion(false, true) == Enc::None); + CHECK(encoder.get_motion(false, true) == Enc::None); + CHECK(encoder.get_motion(false, true) == Enc::None); + CHECK(encoder.get_motion(false, true) == Enc::None); + + CHECK(encoder.get_motion(true, true) == Enc::CW); + CHECK(encoder.get_motion(true, true) == Enc::None); + CHECK(encoder.get_motion(true, true) == Enc::None); + CHECK(encoder.get_motion(true, true) == Enc::None); + + // check counter-clockwise + CHECK(encoder.get_motion(false, true) == Enc::None); + CHECK(encoder.get_motion(false, false) == Enc::None); + CHECK(encoder.get_motion(true, false) == Enc::None); + CHECK(encoder.get_motion(true, true) == Enc::CCW); + + // check with no state change + CHECK(encoder.get_motion(false, true) == Enc::None); + CHECK(encoder.get_motion(false, true) == Enc::None); + + CHECK(encoder.get_motion(false, false) == Enc::None); + CHECK(encoder.get_motion(false, false) == Enc::None); + + CHECK(encoder.get_motion(true, false) == Enc::None); + CHECK(encoder.get_motion(true, false) == Enc::None); + + CHECK(encoder.get_motion(true, true) == Enc::CCW); + CHECK(encoder.get_motion(true, true) == Enc::None); +} diff --git a/util/hsv.hh b/util/hsv.hh new file mode 100644 index 0000000..ed49dd8 --- /dev/null +++ b/util/hsv.hh @@ -0,0 +1,39 @@ +#pragma once + +#include "util/colors.hh" +#include + +class HSVColor { +public: + uint8_t h; + uint8_t s; + uint8_t v; + + constexpr operator Color() const { + if (!s) { + return Color{v, v, v}; + } + + const auto region = h / 43u; + const auto remainder = (h - (region * 43u)) * 6u; + + const uint8_t p = (v * (255u - s)) >> 8u; + const uint8_t q = (v * (255u - ((s * remainder) >> 8u))) >> 8u; + const uint8_t t = (v * (255u - ((s * (255u - remainder)) >> 8u))) >> 8u; + + switch (region) { + case 0: + return Color{v, t, p}; + case 1: + return Color{q, v, p}; + case 2: + return Color{p, v, t}; + case 3: + return Color{p, q, v}; + case 4: + return Color{t, p, v}; + default: + return Color{v, p, q}; + } + } +}; diff --git a/util/quadrature_encoder.hh b/util/quadrature_encoder.hh new file mode 100644 index 0000000..fc28be0 --- /dev/null +++ b/util/quadrature_encoder.hh @@ -0,0 +1,33 @@ +#pragma once + +#include + +template +class QuadratureEncoder { + static constexpr uint8_t valid_ccw = 0b10000111 ^ (invert ? 0xff : 0x00); + static constexpr uint8_t valid_cw = 0b01001011 ^ (invert ? 0xff : 0x00); + + uint8_t prev_state{}; + +public: + enum Direction { None = 0, CW = 1, CCW = -1 }; + + Direction get_motion(bool state_a, bool state_b) { + const auto cur_state = state_a | (state_b << 1u); + + if ((prev_state & 0b11) == cur_state) + return None; + + prev_state <<= 2; + prev_state |= cur_state; + + switch (prev_state) { + case valid_cw: + return CW; + case valid_ccw: + return CCW; + default: + return None; + } + } +};