// This file is part of Necroware's GamePort adapter firmware. // Copyright (C) 2021 Necroware // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . #pragma once #include /// Digital signal edge types. enum class Edge { any, falling, rising }; /// Digital pin base constants. /// /// This implementation was born out of the need for faster digital I/O. The /// original digitalRead(...) function of Arduino suite needs about 2.7us per /// call on a 16MHz MCU. Which makes it impossible to poll 5us pulses, not /// talking about doing with the data something in between. This was a hard /// requirement for this project, so following solution is up to 50% faster /// and needs about 1.6us per call on the same hardware. template struct DigitalPin { using RegType = uint8_t; const RegType mask; const RegType port; DigitalPin() : mask(digitalPinToBitMask(Id)) , port(digitalPinToPort(Id)) { } }; /// Digital Output class. template class DigitalOutput { public: /// Constructor. DigitalOutput() : m_output(*portOutputRegister(m_pin.port)) { *portModeRegister(m_pin.port) |= m_pin.mask; } /// Sets output high. void setHigh() const { m_output |= m_pin.mask; } /// Sets output low. void setLow() const { m_output &= ~m_pin.mask; } /// Sets output to the given value. void set(bool value) const { value ? setHigh() : setLow(); } /// Toggles the output. void toggle() const { m_output ^= m_pin.mask; } /// Triggers a pulse of given duration. /// @param[in] duration is the duration in microseconds void pulse(uint16_t duration = 0) const { toggle(); if (duration) { delayMicroseconds(duration); } toggle(); } private: const DigitalPin m_pin; volatile typename DigitalPin::RegType &m_output; }; /// Digital input class. template class DigitalInput { public: /// Constructor. DigitalInput() : m_input(*portInputRegister(m_pin.port)) { *portModeRegister(m_pin.port) &= ~m_pin.mask; if (Pullup) { *portOutputRegister(m_pin.port) |= m_pin.mask; } } operator bool() const { return isHigh(); } /// Read raw bit data uint8_t read() const { return m_input & m_pin.mask; } /// Checks if the input is high. bool isHigh() const { return read(); } /// Checks if the input is low bool isLow() const { return !read(); } /// Waits for an edge with given timeout. /// @param[in] edge is the type of edge to wait for /// @param[in] timeount is the timeout in microseconds uint16_t wait(Edge edge, uint16_t timeout) const { if (edge == Edge::falling) { return waitImpl(timeout, [](uint8_t a, uint8_t) {return a;}); } if (edge == Edge::rising) { return waitImpl(timeout, [](uint8_t, uint8_t b) {return b;}); } // edge == Edge::rising return waitImpl(timeout, [](uint8_t a, uint8_t b) {return a|b;}); } /// Waits for a state with given timeout. /// @param[in] state is the state to wait for /// @param[in] timeount is the timeout in microseconds uint16_t wait(bool state, uint16_t timeout) const { for (; state != isHigh() && timeout; timeout--) ; return timeout; } private: DigitalPin m_pin; volatile typename DigitalPin::RegType &m_input; template uint16_t waitImpl(uint16_t timeout, T compare) const { auto last = read(); for (; timeout; timeout--) { const auto next = read(); if (last != next) { if (compare(last, next)) { return timeout; } last = next; } } return 0u; } };