From 3fd457c2b8a06860bf3b6e900e1bb843a353bcbb Mon Sep 17 00:00:00 2001 From: Simon Hosie Date: Mon, 7 Nov 2016 10:53:04 -0800 Subject: [PATCH] Quadrature decoder hardware driver. --- inc/drivers/MicroBitQuadratureDecoder.h | 174 ++++++++++++++ source/CMakeLists.txt | 1 + source/drivers/MicroBitQuadratureDecoder.cpp | 233 +++++++++++++++++++ 3 files changed, 408 insertions(+) create mode 100644 inc/drivers/MicroBitQuadratureDecoder.h create mode 100644 source/drivers/MicroBitQuadratureDecoder.cpp diff --git a/inc/drivers/MicroBitQuadratureDecoder.h b/inc/drivers/MicroBitQuadratureDecoder.h new file mode 100644 index 0000000..05fdc59 --- /dev/null +++ b/inc/drivers/MicroBitQuadratureDecoder.h @@ -0,0 +1,174 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016-2017 Simon Hosie + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#ifndef MICROBIT_QUADRATUREDECODER_H +#define MICROBIT_QUADRATUREDECODER_H + +#include "mbed.h" +#include "MicroBitConfig.h" +#include "MicroBitComponent.h" +#include "MicroBitPin.h" + + // Configuration flags... +#define QDEC_USING_SYSTEM_TICK 0x01 // Use systemTick() to keep position up to date. +#define QDEC_USING_DEBOUNCE 0x02 // Use input debounce feature. +#define QDEC_LED_ACTIVE_LOW 0x04 // Drive LED pin low to activate. + +/** + * Class definition for MicroBit Quadrature decoder. + * + */ +class MicroBitQuadratureDecoder : public MicroBitComponent +{ + protected: + static MicroBitPin pinNC; // A no-connect pin -- the default for unused LED arguments. + int64_t position = 0; // Absolute position + MicroBitPin& phaseA, // Phase A input for decoding + phaseB, // Phase B input for decoding + LED; // LED output to assert while decoding + uint32_t samplePeriod = 128; // Minimum sampling period allowed + uint16_t faults = 0; // Double-transition counter + uint8_t LEDDelay = 0; // power-up time for LED, in microseconds + uint8_t flags; + + public: + + /** + * Constructor. + * Create a software abstraction of the quadrature decoder. + * + * @param phaseA Pin connected to quadrature encoder output A + * @param phaseB Pin connected to quadrature encoder output B + * @param LED The pin for the LED to enable during each quadrature reading + * @param LEDDelay Number of microseconds after LED activation before sampling + * + * @code + * MicroBitQuadratureDecoder qdec(QDEC_ID, QDEC_PHA, QDEC_PHB, QDEC_LED); + * @endcode + */ + MicroBitQuadratureDecoder(MicroBitPin& phaseA, MicroBitPin& phaseB, MicroBitPin& LED, uint8_t LEDDelay = 0, uint8_t flags = 0); + MicroBitQuadratureDecoder(MicroBitPin& phaseA, MicroBitPin& phaseB, uint8_t flags = 0); + + /** + * Automatically call poll() from the systemTick() event. + * + * This has the effect of keeping the position up to date to within + * SYSTEM_TICK_PERIOD_MS milliseconds. The event is enabled after a call + * to start() or immediately, if start() has already been called. + * + * This should not be used if poll() is being called in response to + * another regular event. + */ + void enableSystemTick(); + + /** + * Do not automatically call poll() from the systemTick() event (this is the default). + */ + void disableSystemTick(); + + /** + * Set the rate at which input pins are sampled. + * + * @param The maximum interval between samples in microseconds. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER if the configuration is invalid. + */ + int setSamplePeriodUs(uint32_t period); + + /** + * Returns the current sampling period. + * + * @return The sampling period in microseconds. + */ + uint32_t getSamplePeriod(); + + /** + * Configure the hardware to keep this instance up to date. + * + * Several instances can exist so long as no more than one of them is + * attached to the hardware. This can be a practical way to control + * several motors with their own encoders if they run only at different + * times. + * + * While the hardware is active, `poll()` must be called + * + * @return MICROBIT_OK on success, MICROBIT_BUSY if the hardware is already attached to another instance, or MICROBIT_INVALID_PARAMETER if the configuration is invalid. + */ + virtual int start(); + + /** + * Stop the hardware and make it available for use by other instances. + */ + virtual void stop(); + + /** Poll hardware for latest decoder movement and reset the hardware counter to zero. + * + * This must be called regularly to prevent the hardware from overflowing. + * About ten times per second, or less if the attached hardware is + * guaranteed to count more slowly than 10000 encoder counts per second. + * + * This call may be made from systemTick(), or a dedicated motor control ticker interrupt. + */ + virtual void poll(); + + /** + * Read the absolute position of the encoder at last call to `poll()`. + * + * @return current decoder position. + */ + int64_t getPosition() { return position; } + + /** + * Reset the position to a known value. + * + * This can be used to zero the counter on detection of an index or end-stop signal. + * + * @param The value that getPosition() should return at this encoder position. + */ + virtual void resetPosition(int64_t position = 0); + + /** + * Read the number of polling errors since start(). + * + * This value shows the number of times a sample has encountered a + * double-transition condition, where the direction cannot be decoded + * because the relative order of edge transitions was not witnessed. + * + * Such faults imply that the sampling period is too long. + * + * @return total number of faults. + */ + int64_t getCountFaults() { return faults; } + + /** + * Destructor for MicroBitQuadratureDecoder. + * + * Ensures that stop() gets called if necessary. + */ + virtual ~MicroBitQuadratureDecoder() override; + + virtual void systemTick() override; +}; + +#endif diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index ad428ba..da5e93e 100755 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -34,6 +34,7 @@ set(YOTTA_AUTO_MICROBIT-DAL_CPP_FILES "drivers/MicroBitMessageBus.cpp" "drivers/MicroBitMultiButton.cpp" "drivers/MicroBitPin.cpp" + "drivers/MicroBitQuadratureDecoder.cpp" "drivers/MicroBitRadio.cpp" "drivers/MicroBitRadioDatagram.cpp" "drivers/MicroBitRadioEvent.cpp" diff --git a/source/drivers/MicroBitQuadratureDecoder.cpp b/source/drivers/MicroBitQuadratureDecoder.cpp new file mode 100644 index 0000000..0f3d04c --- /dev/null +++ b/source/drivers/MicroBitQuadratureDecoder.cpp @@ -0,0 +1,233 @@ +/* +The MIT License (MIT) + +Copyright (c) 2016-2017 Simon Hosie + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +#include "mbed.h" +#include "MicroBitConfig.h" +#include "MicroBitCompat.h" +#include "MicroBitEvent.h" +#include "MicroBitSystemTimer.h" +#include "ErrorNo.h" +#include "MicroBitQuadratureDecoder.h" + +MicroBitPin MicroBitQuadratureDecoder::pinNC(0, NC, PIN_CAPABILITY_DIGITAL); + +/** + * Constructor. + * Create a software abstraction of the quadrature decoder. + * + * @param phaseA Pin connected to quadrature encoder output A + * @param phaseB Pin connected to quadrature encoder output B + * @param LED The pin for the LED to enable during each quadrature reading + * @param LEDDelay Number of microseconds after LED activation before sampling + * + * @code + * MicroBitQuadratureDecoder qdec(QDEC_ID, QDEC_PHA, QDEC_PHB, QDEC_LED); + * @endcode + */ +MicroBitQuadratureDecoder::MicroBitQuadratureDecoder(MicroBitPin& phaseA_, MicroBitPin& phaseB_, MicroBitPin& LED_, uint8_t LEDDelay_, uint8_t flags_) + : phaseA(phaseA_), phaseB(phaseB_), LED(LED_), LEDDelay(LEDDelay_), flags(flags_) +{ +} + +MicroBitQuadratureDecoder::MicroBitQuadratureDecoder(MicroBitPin& phaseA_, MicroBitPin& phaseB_, uint8_t flags_) + : phaseA(phaseA_), phaseB(phaseB_), LED(pinNC), flags(flags_) +{ +} + +/** + * Automatically call poll() from the systemTick() event. + * + * This has the effect of keeping position up to date to within + * SYSTEM_TICK_PERIOD_MS milliseconds. The event is enabled after a call + * to start() or immediately, if start() has already been called. + * + * This should not be used if poll() is being called in response to + * another regular event. + */ +void MicroBitQuadratureDecoder::enableSystemTick() +{ + if (!(flags & QDEC_USING_SYSTEM_TICK)) + { + flags |= QDEC_USING_SYSTEM_TICK; + if ((status & MICROBIT_COMPONENT_RUNNING) != 0) + system_timer_add_component(this); + } +} + +/** + * Do not automatically call poll() from the systemTick() event. + */ +void MicroBitQuadratureDecoder::disableSystemTick() +{ + flags &= ~QDEC_USING_SYSTEM_TICK; + if ((status & MICROBIT_COMPONENT_RUNNING) != 0) + system_timer_remove_component(this); +} + +/** + * Set the rate at which input pins are sampled. + * + * @param The maximum interval between samples in microseconds. + * + * @return MICROBIT_OK on success, or MICROBIT_INVALID_PARAMETER if the configuration is invalid. + */ +int MicroBitQuadratureDecoder::setSamplePeriodUs(uint32_t period) +{ + if (period < 128) + return MICROBIT_INVALID_PARAMETER; + samplePeriod = period; + return MICROBIT_OK; +} + +/** + * Returns the current sampling period. + * + * @return The sampling period in microseconds. + */ +uint32_t MicroBitQuadratureDecoder::getSamplePeriod() +{ + return samplePeriod; +} + +/** + * Configure the hardware to keep this instance up to date. + * + * Several instances can exist so long as no more than one of them is + * attached to the hardware. This can be a practical way to control + * several motors with their own encoders if they only run at different + * times. + * + * @return MICROBIT_OK on success, MICROBIT_BUSY if the hardware is already attached to another instance, or MICROBIT_INVALID_PARAMETER if the configuration is invalid. + */ +int MicroBitQuadratureDecoder::start() +{ + int sampleper; + + faults = 0; + + for (sampleper = 7; sampleper >= 0; --sampleper) + { + // Find the highest (most power-efficient) sample period available + // which is not greater than the configuration. A longer period could + // miss input transitions. + if ((128u << sampleper) <= samplePeriod) + break; + } + + if ((status & MICROBIT_COMPONENT_RUNNING) != 0) + return MICROBIT_BUSY; + + NRF_QDEC->SHORTS = 0; // No shorts + NRF_QDEC->INTENCLR = ~0; // No interrupts + NRF_QDEC->LEDPOL = (flags & QDEC_LED_ACTIVE_LOW) != 0 ? 0 : 1; + NRF_QDEC->SAMPLEPER = sampleper; + NRF_QDEC->REPORTPER = 7; // Slowest possible reporting (not used) + NRF_QDEC->PSELLED = LED.name; + NRF_QDEC->PSELA = phaseA.name; + NRF_QDEC->PSELB = phaseB.name; + NRF_QDEC->DBFEN = (flags & QDEC_USING_DEBOUNCE) != 0 ? 1 : 0; + NRF_QDEC->LEDPRE = LEDDelay; + + // If these pins were previously triggering events (eg., when emulating + // quadrature decoder using transition events) then put a stop to that. + if (LED.name != NC) + LED.eventOn(MICROBIT_PIN_EVENT_NONE); + phaseA.eventOn(MICROBIT_PIN_EVENT_NONE); + phaseB.eventOn(MICROBIT_PIN_EVENT_NONE); + + // This is what all the cool kids are doing, so I'll do it too. + __NOP(); + __NOP(); + __NOP(); + + NRF_QDEC->TASKS_READCLRACC = 1; // Clear accumulators + NRF_QDEC->ENABLE = 1; + + NRF_QDEC->TASKS_START = 1; + status |= MICROBIT_COMPONENT_RUNNING; + + if ((flags & QDEC_USING_SYSTEM_TICK) != 0) + system_timer_remove_component(this); + + return MICROBIT_OK; +} + +/** + * Stop the hardware and make it available for use by other instances. + */ +void MicroBitQuadratureDecoder::stop() +{ + if ((flags & QDEC_USING_SYSTEM_TICK) != 0) + system_timer_remove_component(this); + + if ((status & MICROBIT_COMPONENT_RUNNING) != 0) + { + NRF_QDEC->TASKS_STOP = 1; + NRF_QDEC->ENABLE = 0; + status &= ~MICROBIT_COMPONENT_RUNNING; + } +} + +/** Poll hardware for latest decoder movement and reset the hardware counter to zero. + * + * This must be called regularly to prevent the hardware from overflowing. + * About ten times per second, or less if the attached hardware is + * guaranteed to count more slowly than 10000 encoder counts per second. + * + * This call may be made from systemTick(), or a dedicated motor control ticker interrupt. + */ +void MicroBitQuadratureDecoder::poll() +{ + NRF_QDEC->TASKS_READCLRACC = 1; + position += (int32_t)NRF_QDEC->ACCREAD; + faults = min(UINT16_MAX, faults + NRF_QDEC->ACCDBLREAD); +} + +/** + * Reset the position to a known value. + * + * This can be used to zero the counter on detection of an index or end-stop signal. + * + * @param The value that getPosition() should return at this encoder position. + */ +void MicroBitQuadratureDecoder::resetPosition(int64_t position) +{ + this->position = position; +} + +/** + * Destructor for MicroBitQuadratureDecoder. + * + * Ensures that stop() gets called if necessary. + */ +MicroBitQuadratureDecoder::~MicroBitQuadratureDecoder() +{ + stop(); +} + +void MicroBitQuadratureDecoder::systemTick() +{ + poll(); +} +