Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[driver] Add best practice driver for soft graycode / quadrature decoding #580

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 11 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,65 +205,66 @@ can easily configure them for you specific needs.
<td align="center">DS1631</td>
<td align="center">DS18B20</td>
<td align="center">EA-DOG</td>
<td align="center">ENCODER-OUTPUT</td>
<td align="center">FT245</td>
<td align="center">ENCODER-INPUT-BITBANG</td>
<td align="center">ENCODER-OUTPUT-BITBANG</td>
</tr><tr>
<td align="center">FT245</td>
<td align="center">FT6X06</td>
<td align="center">GPIO-SAMPLER</td>
<td align="center">HCLAx</td>
<td align="center">HD44780</td>
<td align="center">HMC58x</td>
<td align="center">HMC6343</td>
</tr><tr>
<td align="center">HMC6343</td>
<td align="center">HX711</td>
<td align="center">I2C-EEPROM</td>
<td align="center">ILI9341</td>
<td align="center">IS31FL3733</td>
<td align="center">ITG3200</td>
<td align="center">L3GD20</td>
</tr><tr>
<td align="center">L3GD20</td>
<td align="center">LAN8720A</td>
<td align="center">LAWICEL</td>
<td align="center">LIS302DL</td>
<td align="center">LIS3DSH</td>
<td align="center">LIS3MDL</td>
<td align="center">LM75</td>
</tr><tr>
<td align="center">LM75</td>
<td align="center">LP503X</td>
<td align="center">LSM303A</td>
<td align="center">LSM6DS33</td>
<td align="center">LTC2984</td>
<td align="center">MAX6966</td>
<td align="center">MAX7219</td>
</tr><tr>
<td align="center">MAX7219</td>
<td align="center">MCP23X17</td>
<td align="center">MCP2515</td>
<td align="center">MMC5603</td>
<td align="center">NOKIA5110</td>
<td align="center">NRF24</td>
<td align="center">TFT-DISPLAY</td>
</tr><tr>
<td align="center">TFT-DISPLAY</td>
<td align="center">PAT9125EL</td>
<td align="center">PCA8574</td>
<td align="center">PCA9535</td>
<td align="center">PCA9548A</td>
<td align="center">PCA9685</td>
<td align="center">SIEMENS-S65</td>
</tr><tr>
<td align="center">SIEMENS-S65</td>
<td align="center">SIEMENS-S75</td>
<td align="center">SK6812</td>
<td align="center">SK9822</td>
<td align="center">SSD1306</td>
<td align="center">STUSB4500</td>
<td align="center">SX1276</td>
</tr><tr>
<td align="center">SX1276</td>
<td align="center">TCS3414</td>
<td align="center">TCS3472</td>
<td align="center">TLC594X</td>
<td align="center">TMP102</td>
<td align="center">TMP175</td>
<td align="center">VL53L0</td>
</tr><tr>
<td align="center">VL53L0</td>
<td align="center">VL6180</td>
<td align="center">WS2812</td>
</tr>
Expand Down
70 changes: 70 additions & 0 deletions examples/arduino_nano/encoder_input/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* Copyright (c) 2021, Thomas Sommer
*
* This file is part of the modm project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
// ----------------------------------------------------------------------------

#include <modm/board.hpp>
#include <modm/driver/encoder/bitbang_encoder_input.hpp>
#include <modm/math/algorithm/prescaler.hpp>
#include <modm/processing/timer.hpp>

using namespace modm::platform;

// Connect the encoders outputs to D7 and D8 Pins (usually the outer pins)
// The common third pin (usually in the middle) is connected to GND.
// Don't add any resistors or filters. It's all in the MCU and the driver.
modm::BitBangEncoderInput<Board::D11, Board::D12, 4> encoder;

MODM_ISR(TIMER2_COMPA)
{
encoder.update();
}

void
init_Timer2()
{
constexpr float f = 1_kHz;
constexpr auto prescaler =
modm::Prescaler::from_list(SystemClock::Timer, f * 2, {0, 1, 8, 32, 64, 128, 256, 1024});
constexpr auto ocr = (prescaler.frequency / f) - 1;
static_assert(ocr <= 255, "Can't configure Timer2 for desired f");

// Timer in CTC Mode
TCCR2A = (1 << WGM21);
OCR2A = ocr;
TIMSK2 |= (1 << OCIE2A);
TCCR2B |= prescaler.index;
}

int
main()
{
Board::initialize();
LedD13::setOutput();

encoder.connect();

init_Timer2();
enableInterrupts();

int value(0);

modm::ShortPeriodicTimer heartbeat(500ms);
modm::ShortPeriodicTimer outputValue(1000ms);

while (true)
{
if (heartbeat.execute()) Board::LedD13::toggle();
if (outputValue.execute())
{
value += encoder.getIncrement();
MODM_LOG_INFO << "value: " << value << modm::endl;
}
}
}
11 changes: 11 additions & 0 deletions examples/arduino_nano/encoder_input/project.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<library>
<extends>modm:arduino-nano</extends>
<options>
<option name="modm:build:build.path">../../../build/arduino_nano/encoder_input</option>
</options>
<modules>
<module>modm:build:scons</module>
<module>modm:processing:timer</module>
<module>modm:driver:encoder_input.bitbang</module>
</modules>
</library>
4 changes: 2 additions & 2 deletions examples/stm32f4_discovery/encoder_output/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
// ----------------------------------------------------------------------------

#include <modm/board.hpp>
#include <modm/driver/encoder/encoder_output.hpp>
#include <modm/driver/encoder/bitbang_encoder_output.hpp>

using namespace Board;

Expand All @@ -25,7 +25,7 @@ main()
LedGreen::set();

// period=100000 // 100000us => 10Hz (visible)
using TestEncoder = modm::EncoderOutput<LedBlue, LedGreen, int32_t, modm::PrecisePeriodicTimer, 100'000>;
using TestEncoder = modm::BitBangEncoderOutput<LedBlue, LedGreen, int32_t, modm::PrecisePeriodicTimer, 100'000>;
TestEncoder testEncoder{0};

testEncoder.setPosition(1 << 31);
Expand Down
2 changes: 1 addition & 1 deletion examples/stm32f4_discovery/encoder_output/project.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<option name="modm:build:build.path">../../../build/stm32f4_discovery/encoder_output</option>
</options>
<modules>
<module>modm:driver:encoder.output</module>
<module>modm:driver:encoder_output.bitbang</module>
<module>modm:build:scons</module>
</modules>
</library>
72 changes: 72 additions & 0 deletions src/modm/driver/encoder/bitbang_encoder_input.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright (c) 2021, Thomas Sommer
*
* This file is part of the modm project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
// ----------------------------------------------------------------------------

#ifndef MODM_BITBANG_ENCODER_INPUT_HPP
#define MODM_BITBANG_ENCODER_INPUT_HPP

#include <bit>
#include <modm/platform.hpp>
#include <type_traits>

namespace modm
{
/**
* @ingroup modm_driver_bitbang_encoder_input
* @author Thomas Sommer
*
* @brief This driver decodes a AB (incremental) encoder signal
*
* @tparam SignalA First modm::platform::Gpio pin to input the encoder signal.
* @tparam SignalB Second modm::platform::Gpio pin to input the encoder signal.
* @tparam POSTSCALER n_cycles to count as one in-/decrement.
* @tparam DeltaType Must be signed integer and fit at least POSTSCALER. The Bigger
* DeltaType, the more inc-/decrements can be stored temporarily.
*/
template<typename SignalA, typename SignalB, uint8_t POSTSCALER = 4,
std::signed_integral DeltaType = int8_t>
class BitBangEncoderInput
{
static_assert(std::popcount(POSTSCALER) == 1,
"POSTSCALER must be an integer to basis 2 and not 0: 1, 2, 4, 8, 16, ...");
static_assert(POSTSCALER <= std::numeric_limits<DeltaType>::max(),
"DeltaType is to small for POSTSCALER.");

using Signals = modm::platform::SoftwareGpioPort<SignalA, SignalB>;

static_assert(Signals::number_of_ports == 1,
"Signal A/B must be on the same GPIO port to prevent signal tearing!");

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
static_assert(Phases::number_of_ports() == 1, "Signal A/B must be on the same GPIO port to prevent signal tearing!");

Otherwise the atomic read properties may not be true.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice mechanism.. Damn i ♥ love ♥ the way you've implemented the low level stuff

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually think it's just Phases::number_of_ports without a function call… I don't even remember the code I wrote.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Damn i ♥ love ♥ the way you've implemented the low level stuff

Thanks, half of the credit goes to C++17 with the relaxed constexpr rules:
#19 (comment)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually think it's just Phases::number_of_ports without a function call… I don't even remember the code I wrote.

It is and has already been concidered in the last commits ;)

Copy link
Contributor Author

@TomSaw TomSaw Mar 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, half of the credit goes to C++17 with the relaxed constexpr rules:

Thanks for the interresting background!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the magic spell to get these shift maps?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uint8_t inline getRaw();

public:
using ValueType = DeltaType;
BitBangEncoderInput() : raw_last(0), delta(0){};

// Connect SingalA and SignalB and store power-up state
inline void
connect();

// Call @1kHz for manual movement
inline void
update();

ValueType
getIncrement();

private:
uint8_t raw_last;
DeltaType delta;
};
} // namespace modm

#include "bitbang_encoder_input_impl.hpp"

#endif // MODM_BITBANG_ENCODER_INPUT_HPP
32 changes: 32 additions & 0 deletions src/modm/driver/encoder/bitbang_encoder_input.lb
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2019, Thomas Sommer
#
# This file is part of the modm project.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
# -----------------------------------------------------------------------------

def init(module):
module.name = ":driver:encoder_input.bitbang"
module.description = """
# Quadrature Encoder Input

This driver decodes a AB (incremental) encoder signal.
Ported from code created by Peter Dannegger:
https://www.mikrocontroller.net/articles/Drehgeber.
"""


def prepare(module, options):
module.depends(":architecture:atomic")
return True


def build(env):
env.outbasepath = "modm/src/modm/driver/encoder"
env.copy("bitbang_encoder_input.hpp")
env.copy("bitbang_encoder_input_impl.hpp")
58 changes: 58 additions & 0 deletions src/modm/driver/encoder/bitbang_encoder_input_impl.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#ifndef MODM_BITBANG_ENCODER_INPUT_IMPL_HPP
#error "Don't include this file directly, use 'bitbang_encoder_input.hpp' instead!"
#endif

#include <cmath>

template<typename SignalA, typename SignalB, uint8_t POSTSCALER,
std::signed_integral DeltaType>
inline uint8_t
modm::BitBangEncoderInput<SignalA, SignalB, POSTSCALER, DeltaType>::getRaw()
{
const uint8_t read = Signals::read();
// convert graycode to binary
uint8_t raw = 0;
if (read & 0b10) raw = 3;
if (read & 0b01) raw ^= 1;
return raw;
}

template<typename SignalA, typename SignalB, uint8_t POSTSCALER,
std::signed_integral DeltaType>
inline void
modm::BitBangEncoderInput<SignalA, SignalB, POSTSCALER, DeltaType>::connect()
{
Signals::setInput(::Gpio::InputType::PullUp);

// Tare power-on state
modm::delay(10us);
raw_last = getRaw();
}

template<typename SignalA, typename SignalB, uint8_t POSTSCALER,
std::signed_integral DeltaType>
inline void
modm::BitBangEncoderInput<SignalA, SignalB, POSTSCALER, DeltaType>::update()
{
uint8_t raw = getRaw();
const uint8_t diff = raw_last - raw;
if (diff & 0b01)
{
raw_last = raw;
delta += (diff & 0b10) - 1; // bit 1 = direction (+/-)
}
}

template<typename SignalA, typename SignalB, uint8_t POSTSCALER,
std::signed_integral DeltaType>
DeltaType
modm::BitBangEncoderInput<SignalA, SignalB, POSTSCALER, DeltaType>::getIncrement()
{
::modm::atomic::Lock _;
DeltaType val = delta;

delta &= (POSTSCALER - 1); // mask out higher bits

constexpr uint8_t shift = std::log2(POSTSCALER); // Number of fraction bits
return val >> shift; // return whats left without fractions
}
Loading