Skip to content

Conversation

@speendo
Copy link
Contributor

@speendo speendo commented Jan 9, 2026

Summary

Adds a new EncoderDetents configuration option to support different rotary encoder hardware types, fixing compatibility issues with half-step and quarter-step encoders.

Problem

MiniDexed uses the circle library to implement rotary encoders. This library only worked correctly with full-step encoders. Users with half-step encoders (increasingly common) experienced missed steps. This issue was discussed at

Solution

  1. Added support for half step and quarter step encoders in Circle and circle-stddlib
  1. Added EncoderDetents configuration parameter in 'minidexed.ini' that allows users to specify their encoder type (4: full step encoder (default), 2: half step encoder, 1: quarter step encoder - other values also possible but likely not sensible)

Changes

Configuration

  • minidexed.ini: Added EncoderDetents=4 with documentation
  • config.h: Added m_nEncoderDetents member and getter
  • config.cpp: Added parsing for EncoderDetents

Integration

  • userinterface.cpp: Pass encoder detents value to CKY040 constructor
  • circle-stdlib: Updated submodule for KY040 driver improvements

Dependencies

This PR depends on:

Testing

Build verified on Raspberry Pi 3
Tested on a Raspberry Pi 3 with a half step encoder (HW-040)
EncoderDetents=2: works correctly
EncoderDetents=4: maintains existing behaviour (only every second step is recognised)
EncoderDetents=1: two steps per detent

Backward Compatibility

The default value (EncoderDetents=4) maintains existing behavior, so users with full-step encoders don't need to change anything.

Summary by Sourcery

Add configurable support for different rotary encoder detent resolutions to improve compatibility with various encoder hardware.

New Features:

  • Introduce an EncoderDetents configuration option to control the number of encoder detents per rotation.
  • Pass the configured encoder detent value to the rotary encoder driver to support different encoder hardware types.

Enhancements:

  • Extend the configuration system to store and expose encoder detent settings.
  • Update the circle-stdlib submodule reference to pick up improved KY040 encoder handling.

Documentation:

  • Document the new EncoderDetents option and its recommended values in minidexed.ini.

Allows configuring rotary encoder type via minidexed.ini.
Supports quarter-step (1), half-step (2), and full-step (4) encoders.

- Add EncoderDetents parameter to config (default=4)
- Pass encoder detents to KY040 driver constructor
- Document EncoderDetents option in minidexed.ini
@sourcery-ai
Copy link

sourcery-ai bot commented Jan 9, 2026

Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Adds a configurable encoder detent count and wires it through configuration to the KY040 rotary encoder driver, updating the circle-stdlib submodule so MiniDexed can correctly support full-, half-, and quarter-step encoders while preserving current default behaviour.

Sequence diagram for encoder initialization with detent configuration

sequenceDiagram
    participant UI as CUserInterface
    participant CFG as CConfig
    participant ENC as CKY040

    UI->>CFG: GetEncoderEnabled()
    CFG-->>UI: enabledFlag
    alt encoder enabled
        UI->>CFG: GetEncoderPinClock()
        CFG-->>UI: pinClock
        UI->>CFG: GetEncoderPinData()
        CFG-->>UI: pinData
        UI->>CFG: GetEncoderDetents()
        CFG-->>UI: encoderDetents
        UI->>ENC: new CKY040(pinClock, pinData, buttonPinShortcut, gpioManager, encoderDetents)
        UI->>ENC: Initialize()
        ENC-->>UI: initResult
    end
Loading

Class diagram for configurable encoder detents support

classDiagram
    class CConfig {
        - bool m_bEncoderEnabled
        - unsigned m_nEncoderPinClock
        - unsigned m_nEncoderPinData
        - unsigned m_nEncoderDetents
        + void Load()
        + bool GetEncoderEnabled()
        + unsigned GetEncoderPinClock()
        + unsigned GetEncoderPinData()
        + unsigned GetEncoderDetents()
    }

    class CUserInterface {
        - CConfig* m_pConfig
        - CKY040* m_pRotaryEncoder
        + bool Initialize()
    }

    class CKY040 {
        + CKY040(unsigned pinClock, unsigned pinData, unsigned buttonPin, CGPIOManager* gpioManager, unsigned encoderDetents)
        + bool Initialize()
    }

    CUserInterface --> CConfig : uses
    CUserInterface --> CKY040 : creates
    CConfig ..> CKY040 : provides encoderDetents
Loading

File-Level Changes

Change Details Files
Introduce encoder detent count as a configurable parameter and expose it via CConfig
  • Read EncoderDetents from the properties with a default of 4 when loading configuration
  • Add a GetEncoderDetents accessor to the configuration interface
  • Store the encoder detent count in a new member field on the configuration object
src/config.cpp
src/config.h
Document and default the new EncoderDetents setting in the sample configuration
  • Extend minidexed.ini with comments explaining allowed EncoderDetents values for quarter, half, and full step encoders
  • Set EncoderDetents=4 in the example configuration to maintain existing behaviour
src/minidexed.ini
Plumb encoder detent configuration into the user interface rotary encoder initialization
  • Update CKY040 construction to pass the configured encoder detent count alongside existing GPIO parameters
src/userinterface.cpp
Update circle-stdlib submodule to pull in enhanced KY040 driver with detent support
  • Advance circle-stdlib submodule pointer to a version that supports configurable encoder detents
circle-stdlib

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • Consider validating EncoderDetents in CConfig::Load (e.g. enforcing a minimum of 1 and optionally clamping to a small set of sensible values like 1/2/4) and logging a warning when an out-of-range value is provided, to avoid undefined behavior from misconfigured INI files.
  • The INI comment says "Detents per rotation to be recognized" while the parameter is named EncoderDetents; if the driver actually expects detents per step or a similar notion, it may be worth tightening the wording to make the expected unit/semantics crystal clear to users.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider validating `EncoderDetents` in `CConfig::Load` (e.g. enforcing a minimum of 1 and optionally clamping to a small set of sensible values like 1/2/4) and logging a warning when an out-of-range value is provided, to avoid undefined behavior from misconfigured INI files.
- The INI comment says "Detents per rotation to be recognized" while the parameter is named `EncoderDetents`; if the driver actually expects detents per step or a similar notion, it may be worth tightening the wording to make the expected unit/semantics crystal clear to users.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@probonopd
Copy link
Owner

hi @speendo, thanks for this pull request.

The build fails due to a constructor mismatch for CKY040 at this line in userinterface.cpp:

m_pRotaryEncoder = new CKY040(
    m_pConfig->GetEncoderPinClock(),
    m_pConfig->GetEncoderPinData(),
    m_pConfig->GetButtonPinShortcut(),
    m_pGPIOManager,
    m_pConfig->GetEncoderDetents()
);

The error:

error: no matching function for call to 'CKY040::CKY040(unsigned int, unsigned int, unsigned int, CGPIOManager&, unsigned int)'

and from ky040.h:

candidate: 'constexpr CKY040::CKY040(const CKY040&)'
candidate expects 1 argument, 5 provided

Solution:

  • The CKY040 class does not have a constructor matching the 5 arguments you are providing.
  • Review the CKY040 constructor in circle-stdlib/libs/circle/addon/sensor/ky040.h and update the instantiation in userinterface.cpp to match the actual constructor signature.

For example, if CKY040 only takes 3 arguments:

m_pRotaryEncoder = new CKY040(
    m_pConfig->GetEncoderPinClock(),
    m_pConfig->GetEncoderPinData(),
    m_pGPIOManager
);

Adjust the arguments in userinterface.cpp's Initialize() method at lines 181–185 accordingly.

Check CKY040's class declaration and documentation for correct usage. This change should resolve the constructor mismatch and allow your build to proceed.

@soyersoyer
Copy link
Contributor

I love this AI world already! ;)

Update the circle-stdlib to v17.2 and the circle to the latest develop commit, in submod.sh:

cd circle-stdlib/
git checkout -f --recurse-submodules v17.2
cd -

# Optional update submodules explicitly
cd circle-stdlib/libs/circle
git checkout -f --recurse-submodules b42d060
cd -

If this PR gets merged, I'll also rebase the DreamDexed on it.

@probonopd probonopd assigned Copilot and unassigned Copilot Jan 10, 2026
- Replace numeric parameter with user-friendly strings: full, half, quarter
- Add validation with silent fallback to default (full)
- Clarify INI documentation

Addresses feedback from sourcery-ai bot review.
@speendo
Copy link
Contributor Author

speendo commented Jan 11, 2026

Thanks everyone for the reviews and feedback!

@sourcery-ai - I've addressed the validation feedback by changing from numeric EncoderDetents to a string-based EncoderResolution parameter with values "full" (default and fallback for invalid values), "half", or "quarter". I didn't implement the idea of logging invalid values as this wouldn't match the current style how config.cpp handles the configuration.

@probonopd - The build failure is expected at this point. The PR depends on the new CKY040 constructor that was just merged to Circle's develop branch (rsta2/circle#683). The current MiniDexed submodules point to older Circle versions that still use the 4-parameter constructor.

@soyersoyer - Thank you for testing and for the solution! I'm excited to hear DreamDexed is using this!

The updated changes have just been pushed. Once the submodules are updated as soyersoyer suggested, the build should succeed.

@speendo
Copy link
Contributor Author

speendo commented Jan 11, 2026

It's also worth mentioning that this change potentially renders capacitors on the encoder's CLK and DT pins obsolete (see the discussion here), so the building process could be slightly changed and simplified.

Of course, keeping the capacitors does no harm.

- Changed EncoderResolution parsing to use std::string with == operator
- Removes dependency on strings.h header
- Consistent with other string configuration options in the file
@soyersoyer
Copy link
Contributor

soyersoyer commented Jan 16, 2026

The build still fails.
Did you forget to commit the submod.sh file?
The build process calls it.

- Update circle-stdlib to v17.2
- Update circle to commit b42d060 which includes the new CKY040 constructor with encoder detents parameter
- This fixes the build failure by providing the correct API for the encoder resolution feature
@github-actions
Copy link

Build for testing:
MiniDexed_1323_2026-01-16-2632838_32bit
MiniDexed_1323_2026-01-16-2632838_64bit
Use at your own risk.

@speendo
Copy link
Contributor Author

speendo commented Jan 16, 2026

Thanks for checking, @soyersoyer!

I thought it would be just a waiting game until the changes get merged to the main branches of circle-stdlib and circle.

Changing submod.sh of course makes waiting obsolete :)

Thanks!

@probonopd
Copy link
Owner

Thank you very much @speendo and @soyersoyer. Please let me know whether the build for testing (above) works for you and whether you think this is ready for being merged. Once merged, let's not forget to update the wiki.

@speendo
Copy link
Contributor Author

speendo commented Jan 19, 2026

So I downloaded the automatic 64bit build to use on my Raspberry Pi 3.

I can confirm that

  • MiniDexed is starting
  • MiniDexed still plays sounds when used with a Keyboard
  • With EncoderResolution=half My HW040 encoder (half-step) works as expected (one detent equals one step in the menu)

I didn't test

  • Other encoders (but @soyersoyer did here)
  • An encoder without a capacitor (as I already soldered 1 uF capacitors to my encoder, note: 1 uF anyway is smaller than the recommended 10 uF, however, @soyersoyer did here)
  • all features (but I did use my own build quite extensively already and had no issues so far)

From this biased perspective, I am rather optimistic that the build would be ready to be merged.

One thing might still be worth discussing:

Do you like the style of the new variable in minidexed.ini: EncoderResolution=full?

I bring this up because this requires the user to know that there are different styles of encoders (full-step, half-step and quarter-step) and also which version they own.

Another (rather naive) approach would be the user installing MiniDexed (which is defaulted to a full-step encoder) then noticing that the encoder needs 2 (or even 4) detents to recognise a step in the menu and then searching and editing a variable like DetentsPerStep (name needs improvement) to a numeric value like 2 or 4.

What do you think is better?

@probonopd
Copy link
Owner

probonopd commented Jan 19, 2026

@speendo I like the variables as they are implemented now. We just need to document them in the Wiki once merged... which is... now :) Thank you very much and I'd appreciate if you could update the Wiki. Thanks!

@probonopd probonopd merged commit 8e3fa30 into probonopd:main Jan 19, 2026
4 checks passed
@soyersoyer
Copy link
Contributor

DreamDexed has been rebased on top of this. Thanks.

@speendo
Copy link
Contributor Author

speendo commented Jan 24, 2026

Great! I updated several pages in the Wiki to reflect the changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants