EncoderTool

Inspired by this https://forum.pjrc.com/threads/70233-Using-Teensy-for-big-MIDI-control-surface and the apparent lack of 23S17 IO Expanders I was thinking of yet another encoder multiplexing scheme which might handle the readout of the required 150+ encoders.

It uses the 74HC4040 (https://www.ti.com/product/CD74HC4040). This is a 12 stage counter with attached multiplexer. I.e. on each count pulse it sets one (the next) of its 12 outputs to high:
Screenshot 2022-05-16 174639.png

The idea is to use the outputs of the 4040 to supply the common pins of 12 encoders. The A/B/SW pins of the encoders can then be 'ored' with simple diodes. The controller would generate a count pulse which activates one of the encoders, read out the A/B/SW pins and supply the levels to the library as usual. Potentially this method should be much faster than reading out an IO Expander or using the slow analog multiplexers. The readout time should only be limited by the speed of the counter (>80MHz) and the digitalReadFast speed (~10ns).

Count and reset pulse can be shared by many counters. Each counter requires 2 (3 with switch) pins on the Teensy. So, with a T4.1 one could control some 20 counters which would be 240 encoders. CLK and Reset would probably need a buffer for that amount of counters.

Here a first draft of a schematic supporting 24 encoders (pdf attached)

Screenshot 2022-05-16 175246.png

Any feedback on the schematic? Did I overlook something? Better ideas?

Ups, looks like I misinterpreted the datasheet of the 4040. It seems not to set one pin high at a time like a decade counter. Back to the drawing board...
 

Attachments

  • mplex74HC4040_01.pdf
    100.9 KB · Views: 72
Last edited:
Either way is a counter, decade counter rolls over at different count.

Suggesting Ring counter if one exists. Component availability is a key design factor no matter the method.
 
Either way is a counter, decade counter rolls over at different count.
Suggesting Ring counter if one exists. Component availability is a key design factor no matter the method.

I was thinking of a simple shift register, but then I thought why bother with external parts, when a Teensy can directly read out the matrix even faster.

Screenshot 2022-05-18 191616.jpg

A quick calculation gives the following formulas for the number of encoders and the optimal matrix arrangement for a given number of available pins

Code:
n_col = n_pin/2
n_row = n_pin/4
n_enc = n_col X n_row = n_pin^2 / 8;
where n_col is the number of columns, n_row the number of rows, n_enc the number of encoders and n_pin the number of available pins.

Assuming communication with the main controller via UART (2 pins) and using 1 pin to signal when the encoder values changed we have 21 pins left on a T-LC. Thus, a LC can read out n_enc = 21*21/8 = 55 encoders as shown in the schematic above. A T4.1 has 39 pins available for the matrix which gives 190 encoders.

Here a very quick feasibility calculation regarding readout speed:
Let's assume some 100ns to read and process one pin (which is really long for a T4). Then, a complete scan of 190 encoders would be done in 0.1µs/pin X 2*190pins = 38µs. If we poll the matrix with >5kHz (which is fast for mechanical encoders) this would generate 5000 1/s * 38µs = 0.2 = 20% processor load only. All in all this does not look bad :)

Thinking of doing a PCB, what would be a reasonable spacing between the encoders? Would it make sense to pre-cut or pre-mill the board such that one could break off an arbitrary number of encoder positions from the board to meet the needs of a project?
 

Attachments

  • direct_read.pdf
    52.6 KB · Views: 63
Yeah, mechanicals. Currectly using 19mm grid to keep the whole thing compact and portable, no knobs.

End use case is cutting loose on a Synth so with that spacing is easy to nudge a neighbor and mess with the flow. Migrating from dev to use-case will be looking at maybe 35mm grid with knobs.

I like the snap up board idea. A user can get up and running quickly. Couple of thoughts:-

Reasonably large pads, hole size = desolder with minimal damage. Metalwork floating in case user wants to play with cap sense.

Serial ports can get gobbled up quick with Midi so usb comms with host of main MCU is likely a good tool in the box.

Down side is would be easy to get totally lost on it as a user interface.

A 16 encoder modular approach with a means to set a module's address is worthy of thought.
Must do 100Km shopping trek, good time to think.

My idea for another setter is for a means to change encoder resolution on-the-fly. Have it working in external code with SetDeadzone(x) where x = number of counts to skip before value change. Does a great emulation of a rotary switch with 96CPR. Bear in mind that a preset change may see the same physical encoder at full resolution. Have not managed to kludge it into the library yet.
 
I did a draft of a Encoder Matrix Proto board. The idea is that one can start developing without the tedious wiring of a lot of encoders. After development the board can be easily cut or broken or extended to fit the mechanic design of the device. The board accepts any ARM Teensy board. With a T4.1 it should be able to support some 190 Encoders.


Here some pictures:
Screenshot 2022-05-22 174538.png

Screenshot 2022-05-22 174207.png

Screenshot 2022-05-22 174229.png

Screenshot 2022-05-22 174503.png

Maybe this is of use for someone.
 
Ears have been burning again, been re-writing project codebase. Noted a thread about encoders and Midi.
Here is the essence of what my project does with EncPlex. Variations tested on T3.2 - T4.1, Usb Type = MIDI. Messages are sent concurrently via usbMIDI and MIDI.


Code:
#include <MIDI.h>
MIDI_CREATE_INSTANCE(HardwareSerial, Serial3, MIDI);
//*****************************************************
#include "EncoderTool.h"
#define NUM_ENCS 8
using namespace EncoderTool;
constexpr unsigned        QH_A   = 17; //output pin QH of shift register B - Encoder B
constexpr unsigned        QH_B   = 16; //output pin QH of shift register A - Encoder A
constexpr unsigned        pinLD  = 4; //load pin for all shift registers)
constexpr unsigned        pinCLK = 5; //clock pin for all shift registers
#define NUM_ENCS 8

EncPlex74165 encoders(NUM_ENCS, pinLD, pinCLK, QH_A, QH_B );
//****************************************************
struct EncData {
  uint16_t  CurVal;       // Could be a Sysex byte, CC  or NRPN value
  uint16_t  OldVal;       // Could be a Sysex byte, CC  or NRPN value
  uint16_t  MinVal;
  uint16_t  MaxVal;
  uint16_t  ControlNum;   // For CC#, NRPN#. Relevant bits are masked in the MidiSend() function
  byte      MidiChannel;
  int       CommandType;  // 0 = disabled, 1 = CC, 2= CC14bit, 3 = NRPN, 4 = Roland Single and 5 = Roland Two byte SYSEX messages
  byte      Address4;     // Sysex address
  byte      Address3;     //
  byte      Address2;     //
  byte      Address1;     //
};

EncData EncData[8] = {
  {0, 0, 1, 127,    7, 1, 1, 0, 0, 0, 0},
  {0, 0, 1, 127,   11, 2, 1, 0, 0, 0, 0},
  {0, 0, 1, 255,   25, 3, 2, 0, 0, 0, 0},
  {0, 0, 1, 511,  270, 4, 3, 0, 0, 0, 0},
  {0, 0, 0,   1,    0, 1, 4, 0x00, 0x00, 0x00, 0x00},   // Panel Mode
  {0, 0, 0,   4,    0, 1, 4, 0x30, 0x00, 0x10, 0x50},   // Tone 1 Filter Type
  {0, 0, 0, 127,    0, 1, 4, 0x03, 0x00, 0x10, 0x51},   // Tone 1 Filter freq
  {0, 0, 1, 255,    0, 1, 5, 0x00, 0x00, 0x00, 0x04},   // Patch Number 
};
//************************************************************************************
// Roland JV series specific. These two are kind of templates. The first 5 bytes are sent verbatim
// and address the Synth.
// The next four bytes are the Parameter address, MSB - LSB and the following byte contains the 7-bit parameter value

byte JV_7BitSysex[12]  = {0xF0, 0x41, 0x10, 0x6A, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF7};

// This one has two parameter value bytes, each with a range 0x0 - 0xF allowing param value range 0-255.

byte JV_8BitSysex[13]  = {0xF0, 0x41, 0x10, 0x6A, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF7};

// The second last byte of both the above contain the Checksum value.
// Address, Value and Checksum are inserted in the MidiSend() function.

uint16_t sum;
uint16_t idx;
uint16_t checkSum;
//************************************************************************************
int Cable = 0;
elapsedMillis stopwatch = 0;

void setup() {
  delay(1000);
  MIDI.begin(MIDI_CHANNEL_OMNI);
  MIDI.turnThruOff();
  encoders.begin(CountMode::half);
  for (unsigned i = 0; i < NUM_ENCS; i++) {
    encoders[i].setLimits(EncData[i].MinVal, EncData[i].MaxVal);
    encoders[i].setValue(EncData[i].CurVal);
    EncData[i].OldVal = EncData[i].CurVal;
  }
}
void loop() {
  ReadEncoders();
  SendMidi();
}
void sendMidi2() {
  for (unsigned i = 0; i < NUM_ENCS; i++) {
    if (EncData[i].CurVal != EncData[i].OldVal) {
      EncData[i].OldVal = EncData[i].CurVal;
      usbMIDI.sendControlChange(EncData[i].ControlNum, EncData[i].CurVal, EncData[i].MidiChannel);
      MIDI.sendControlChange(EncData[i].ControlNum, EncData[i].CurVal, EncData[i].MidiChannel);

    }
  }
}
//********************************************************************
void SendMidi() {
  for (int i = 0; i <  NUM_ENCS; i++) {
    if (EncData[i].CommandType != 0) {
      if    (EncData[i].CurVal != EncData[i].OldVal) {
        EncData[i].OldVal = EncData[i].CurVal;
        uint16_t CurrentValue    = EncData[i].CurVal;
        uint16_t _Control   = EncData[i].ControlNum;
        uint16_t value14    = (CurrentValue        & 0b00000000000000000011111111111111);
        uint16_t value21    = (CurrentValue        & 0b00000000000111111111111111111111);
        byte valueLSB       = (CurrentValue        & 0b00000000000000000000000001111111);
        byte valueMSB       = (CurrentValue        & 0b00000000000000000011111110000000) >> 7;
        uint16_t control14  = (_Control            & 0b00000000000000000011111111111111);
        byte controlLSB     = (_Control            & 0b00000000000000000000000001111111);
        byte controlMSB     = (_Control            & 0b00000000000000000011111110000000) >> 7;
        byte Channel        = EncData[i].MidiChannel;

        switch (EncData[i].CommandType)  {
          case 0:
            // Do nothing.
            break;
          case 1:
            usbMIDI.sendControlChange   (controlLSB, valueLSB, Channel, Cable);
            MIDI.sendControlChange   (controlLSB, valueLSB, Channel);
            break;

          case 2:
            usbMIDI.sendControlChange    (controlLSB, valueMSB, Channel, Cable);
            usbMIDI.sendControlChange    (controlLSB + 32, valueLSB, Channel, Cable);
            MIDI.sendControlChange    (controlLSB, valueMSB, Channel);
            MIDI.sendControlChange    (controlLSB + 32, valueLSB, Channel);
            break;
          case 3:
            usbMIDI.beginNrpn(control14, Channel, Cable);
            usbMIDI.sendNrpnValue(valueLSB, Channel, Cable);
            usbMIDI.endRpn(Channel, Cable);
            MIDI.beginNrpn(control14, Channel);
            MIDI.sendNrpnValue(valueLSB, Channel);
            MIDI.endNrpn(Channel);
            break;
          case 4:
            JV_7BitSysex[9] = valueLSB & 0b01111111;
            JV_7BitSysex[8] = EncData[i].Address1 & 0b01111111;
            JV_7BitSysex[7] = EncData[i].Address2 & 0b01111111;
            JV_7BitSysex[6] = EncData[i].Address3 & 0b01111111;
            JV_7BitSysex[5] = EncData[i].Address4 & 0b01111111;
            sum = 0;
            idx = 5;
            checkSum = 0;
            for (sum = 0; idx < 10; ++idx) {
            sum += (byte)JV_7BitSysex[idx];
            }
            checkSum = 128 - ((sum % 128) & 0b01111111);
            checkSum =   (checkSum  & 0b01111111);
            JV_7BitSysex[10] =    checkSum;
            usbMIDI.sendSysEx(12, JV_7BitSysex, true, Cable);
            MIDI.sendSysEx(12, JV_7BitSysex, true);
            break;
          case 5:
              JV_8BitSysex[10] =  EncData[i].CurVal   & 0b00000000000000000000000000001111;
              JV_8BitSysex[9]  = (EncData[i].CurVal   & 0b00000000000000000000000011110000) >> 4;
              JV_8BitSysex[8]  =  EncData[i].Address1 & 0b01111111;
              JV_8BitSysex[7]  =  EncData[i].Address2 & 0b01111111;
              JV_8BitSysex[6]  =  EncData[i].Address3 & 0b01111111;
              JV_8BitSysex[5]  =  EncData[i].Address4 & 0b01111111;
              sum = 0;
              idx = 5;
              checkSum = 0;
              for (sum = 0; idx < 11; ++idx) {
            sum += (byte)JV_8BitSysex[idx];
            }
            checkSum = 128 - ((sum % 128) & 0b01111111);
            checkSum =   (checkSum  & 0b01111111);
            JV_8BitSysex[11] = (byte)checkSum;
            usbMIDI.sendSysEx(13, JV_8BitSysex, true, Cable);
            MIDI.sendSysEx(13, JV_8BitSysex, true);
            break;
        }
        break;
      }
    }
  }
}
//**************************************************************************************
void ReadEncoders() {
  encoders.tick();
  if (stopwatch > 10)  // Look at sending Midi values every 10 ms
  {
    for (unsigned i = 0; i < NUM_ENCS; i++)
    {
      EncData[i].CurVal = encoders[i].getValue();
    }
    stopwatch = 0;
  }
}
 
I was thinking of a simple shift register, but then I thought why bother with external parts, when a Teensy can directly read out the matrix even faster.

View attachment 28413

A quick calculation gives the following formulas for the number of encoders and the optimal matrix arrangement for a given number of available pins

Code:
n_col = n_pin/2
n_row = n_pin/4
n_enc = n_col X n_row = n_pin^2 / 8;
where n_col is the number of columns, n_row the number of rows, n_enc the number of encoders and n_pin the number of available pins.

Assuming communication with the main controller via UART (2 pins) and using 1 pin to signal when the encoder values changed we have 21 pins left on a T-LC. Thus, a LC can read out n_enc = 21*21/8 = 55 encoders as shown in the schematic above. A T4.1 has 39 pins available for the matrix which gives 190 encoders.

Here a very quick feasibility calculation regarding readout speed:
Let's assume some 100ns to read and process one pin (which is really long for a T4). Then, a complete scan of 190 encoders would be done in 0.1µs/pin X 2*190pins = 38µs. If we poll the matrix with >5kHz (which is fast for mechanical encoders) this would generate 5000 1/s * 38µs = 0.2 = 20% processor load only. All in all this does not look bad :)

Thinking of doing a PCB, what would be a reasonable spacing between the encoders? Would it make sense to pre-cut or pre-mill the board such that one could break off an arbitrary number of encoder positions from the board to meet the needs of a project?

Very nice principle.
Has someone tested it?
 
Yes, I tested it. Works nicely.
However, the board from the GitHub repo has a missing GND connection (need to fix that some time...).
If you are using a T4.x make sure to wait some time after switching rows, the board is faster than it takes the row-voltage to settle.
 
Just tested it, my setup needs some 200ns delay to get a stable readout.

Here the test code I used to read out the 3x5 encoder-array shown in #6.
Code:
#include "Arduino.h"
#include "EncoderTool.h"
#include "DirectMux.h"

using namespace EncoderTool;

DirectMux<3, 5> matrix;

// define used pins
constexpr unsigned AR0 = 2, BR0 = 3, SR0 = 4;                   // row 0
constexpr unsigned AR1 = 5, BR1 = 6, SR1 = 7;                   // row 1
constexpr unsigned AR2 = 8, BR2 = 9, SR2 = 10;                  // row 2
constexpr unsigned C0 = 23, C1 = 22, C2 = 21, C3 = 20, C4 = 19; // columns

void onEncoderChanged(uint8_t channel, int value, int delta)
{
    Serial.printf("CH%d: %d\n", channel, value);
}

void setup()
{
    pinMode(LED_BUILTIN, OUTPUT);

    matrix.begin({AR0, AR1, AR2}, {BR0, BR1, BR2}, {SR0, SR1, SR2}, {C0, C1, C2, C3, C4});
    matrix.attachCallback(onEncoderChanged);
}

void loop()
{
    matrix.tick();
}

The DirectMux class is implemented as a template class which takes the number of rows and the number of columns as template parameters. The ARn, BRn, SRn and Cm pins correspond to the board shown in #6. Since querying all the encoders for changes would be a bit tedious, I use the callback onEncoderChanged which will be invoked whenever one of the encoders changed its value.

Here the definition of the DirectMux class. The readout happens in its 'tick()' member function which needs to be called as often as possible. As with the other multiplexing examples, all the decoding and housekeeping stuff is provided by the EncoderTool.

DirectMux.h
Code:
#pragma once

#include "Multiplexed/EncPlexBase.h"
#include <array>

namespace EncoderTool
{
    template <size_t rows, size_t cols>
    class DirectMux : public EncPlexBase
    {
        // some typedefs for less verbose code
        using cArr_t = std::array<uint_fast8_t, cols>;
        using rArr_t = std::array<uint_fast8_t, rows>;

     public:
        DirectMux() : EncPlexBase(rows * cols) {}

        void begin(rArr_t arPins, rArr_t brPins, rArr_t srPins, cArr_t cPins, CountMode mode = CountMode::quarter)
        {
            EncPlexBase::begin(mode); // setup the base class
            this->arPins = arPins;    // copy passed in pin numbers
            this->brPins = brPins;
            this->srPins = srPins;
            this->cPins  = cPins;

            for (auto pin : arPins) pinMode(pin, INPUT_PULLUP);
            for (auto pin : brPins) pinMode(pin, INPUT_PULLUP);
            for (auto pin : srPins) pinMode(pin, INPUT_PULLUP);
            for (auto pin : cPins)
            {
                pinMode(pin, OUTPUT);
                digitalWriteFast(pin, HIGH);  // board is implemented as active LOW 
            }
        }

        inline void tick() // call as often as possible
        {
            unsigned curEnc = 0;
            for (auto cPin : cPins)
            {
                digitalWriteFast(cPin, LOW);
                delayNanoseconds(500);

                for (unsigned row = 0; row < rows; row++)
                {
                    uint_fast8_t A = digitalReadFast(arPins[row]);
                    uint_fast8_t B = digitalReadFast(brPins[row]);
                    uint_fast8_t S = digitalReadFast(srPins[row]);

                    int delta = encoders[curEnc].update(A, B, S);
                    if (delta != 0 && callback != nullptr)
                    {
                        callback(curEnc, encoders[curEnc].getValue(), delta); // if something changed, invoke the callback
                    }
                    curEnc++;
                }
                digitalWriteFast(cPin, HIGH);
            }
        }

     protected:
        rArr_t arPins, brPins, srPins;
        cArr_t cPins;
    };

Edit: I added this code as an example to the repo
 
Last edited:
Thanks

"Since querying all the encoders for changes would be a bit tedious, I use the callback onEncoderChanged which will be invoked whenever one of the encoders changed its value. "

You could pass "this" as a parameter to the callback. Could be used on all callbacks, BTW.
 
@luni is there any chance to extend the output of EncoderTool to return an int64? I am using EncoderTool for my electronic lead screw project, and I am a bit worried about what would happen at an int32 rollover. That could happen while using the lathe all day and threading. Using an int64 would make that problem go away (at least for normal daily machining). I have a 4096 PPR rotary encoder and the lathe can go up to 3000RPM. Of course I wouldn't thread at that speed, but I probably would use the power feed at elevated RPMs for a nice finish.

I am also using your EncoderTool to read my DRO's. In that application, an int32 is fine. For a spindle, I do think an int64 would be better. Your thoughts on this?
 
I like the idea, but the encoder tool is pretty much platform independent. So setting the counter to int64 would be quite some overhead for smaller boards. Best would be to make the counter type a template parameter which defaults to 'int'. One could then change the counter type if needed. Shouldn't be too difficult, but currently my time is super limited (currently renovating a house), so it might take some time...
 
I like the idea, but the encoder tool is pretty much platform independent. So setting the counter to int64 would be quite some overhead for smaller boards. Best would be to make the counter type a template parameter which defaults to 'int'. One could then change the counter type if needed. Shouldn't be too difficult, but currently my time is super limited (currently renovating a house), so it might take some time...

I am not sure I am capable of doing this, as my C++ skills are limited, but could you point me towards where things need to change? At the very least, I will take a look. If I get it to work, I can send it to you.
 
Here the line which defines the type of the counter to 'int'
https://github.com/luni64/EncoderTo...e773756f9778d803762c22f/src/EncoderBase.h#L63

You can change this to int64 but you need to make sure to change e.g.getValue, setValue etc to reflect the change.

It would also seem that config.h needs to be modified? I think I am using a PLAIN_ENC_CALLBACK, so line #17 needs to be modified as well?
From:
Code:
   using encCallback_t = void (*)(int value, int delta);
To:
Code:
   using encCallback_t = void (*)(int64_t value, int64_t delta);
?

I saw the problem with delta = 2147483647, leading me to believe that delta should be an int64_t? Everything worked fine, until I reversed the direction of the spindle, then I saw the delta error, which I trap for!
In any case, shouldn't encCallback_t be consistent with what is in EncoderBase.h?

I made my changes. And everything works, however, now everything has to be int64_t. But no worries of overflow for now!
 
Last edited:
clinker8 said:
I guess that means, I have to maintain this for a while - at least until you update the library.

I pushed an experimental version with user selectable counter type to the GitHub repository https://github.com/luni64/EncoderTool/tree/counterType (use the branch "counterType").

The standard API did not change. I.e., the default counter is of type "int", regardless of how large that is for the controller you are using. E.g. for the Teensies "int" translates to "int32_t". Additionally you can use any integral type for the counter. This works for the interrupt based, the polled and the multiplexed encoders. Here a basic usage example showing how to use the standard 32bit encoder, a 64bit encoder and a tiny, unsigned 8bit encoder:

Code:
#include "EncoderTool.h"
using namespace EncoderTool;

//using Encoder   = Encoder_tpl<int>; // this is already defined by the library
using Encoder64 = Encoder_tpl<int64_t>; // e.g. use a 64bit counter
using smallEnc  = Encoder_tpl<uint8_t>; // e.g. use an unsigned 8bit counter (no negative values possible)

Encoder e1;
Encoder64 e2;
smallEnc e3;

void setup()
{
    e1.begin(1, 2);
    e2.begin(4, 5);
    e3.begin(7, 8);
}

void loop()
{
    if (e1.valueChanged()) Serial.printf("e1 (int32_t):  %d\n", e1.getValue());
    if (e2.valueChanged()) Serial.printf("e2 (int64_t):  %lld\n", e2.getValue());
    if (e3.valueChanged()) Serial.printf("e3 (uint8_t):  %d\n", e3.getValue());
}

@clinker8: I would very much appreciate if you could test it thoroughly before I pull it into the main branch
 
I pushed an experimental version with user selectable counter type to the GitHub repository https://github.com/luni64/EncoderTool/tree/counterType (use the branch "counterType").

The standard API did not change. I.e., the default counter is of type "int", regardless of how large that is for the controller you are using. E.g. for the Teensies "int" translates to "int32_t". Additionally you can use any integral type for the counter. This works for the interrupt based, the polled and the multiplexed encoders. Here a basic usage example showing how to use the standard 32bit encoder, a 64bit encoder and a tiny, unsigned 8bit encoder:

Code:
#include "EncoderTool.h"
using namespace EncoderTool;

//using Encoder   = Encoder_tpl<int>; // this is already defined by the library
using Encoder64 = Encoder_tpl<int64_t>; // e.g. use a 64bit counter
using smallEnc  = Encoder_tpl<uint8_t>; // e.g. use an unsigned 8bit counter (no negative values possible)

Encoder e1;
Encoder64 e2;
smallEnc e3;

void setup()
{
    e1.begin(1, 2);
    e2.begin(4, 5);
    e3.begin(7, 8);
}

void loop()
{
    if (e1.valueChanged()) Serial.printf("e1 (int32_t):  %d\n", e1.getValue());
    if (e2.valueChanged()) Serial.printf("e2 (int64_t):  %lld\n", e2.getValue());
    if (e3.valueChanged()) Serial.printf("e3 (uint8_t):  %d\n", e3.getValue());
}

@clinker8: I would very much appreciate if you could test it thoroughly before I pull it into the main branch

I would be honored to help you. I will test it out in the next few days. Been refusing library updates for a little bit. Would be good to keep everything up to date again. Is everything else the same? Disable the callback using nullptr? What type is "delta"? I had to edit the type of my version of the library to be int64_t as well, when I was surprised with a very large positive number when the spindle reversed direction.
 
Been refusing library updates for a little bit. Would be good to keep everything up to date again.
It usually is a good idea to git clone libraries. You can then switch between branches / versions without hassle.

Is everything else the same? Disable the callback using nullptr? What type is "delta"? I had to edit the type of my version of the library to be int64_t as well, when I was surprised with a very large positive number when the spindle reversed direction.

Good point, I need to change the callback types also. I'll update the repo as soon as this is working as well.
 
Good point, I need to change the callback types also. I'll update the repo as soon as this is working as well.

I updated the callback signatures (value and delta) to reflect the underlying counter type. Here an example for a 64 bit counter:
Code:
Encoder_tpl<int64_t> spindleEnc;

void onEncoderChanged(int64_t value, int64_t delta)
{
    Serial.println(value);
}

void setup()
{
    spindleEnc.begin(1, 2);
    spindleEnc.attachCallback(onEncoderChanged);
}

void loop()
{
}

I also cleaned up the API a bit and disabled unsigned types for the underlying counter since they might generate unexpected behavior.
 
I updated the callback signatures (value and delta) to reflect the underlying counter type. Here an example for a 64 bit counter:
Code:
Encoder_tpl<int64_t> spindleEnc;

void onEncoderChanged(int64_t value, int64_t delta)
{
    Serial.println(value);
}

void setup()
{
    spindleEnc.begin(1, 2);
    spindleEnc.attachCallback(onEncoderChanged);
}

void loop()
{
}

I also cleaned up the API a bit and disabled unsigned types for the underlying counter since they might generate unexpected behavior.

This is terrific! I will try this out tomorrow.

Been busy making the boxes for my electronic lead screw, and making cables. Everything seems to be straight-forward. My only difficult area is making the display box look nice. The ELS is working well with my preliminary cables. I am working on adding feeding to a stop. Once that is done, I think I will have enough experience at this to try threading to a stop. I think it is possible, since the information is there, but I need to work out all the math.
 
Renamed my edited EncoderTool directory.
Did a git clone -b counterType https://github.com/luni64/EncoderTool.git
Made my own branch test

Seem to be getting the following error:
Code:
In file included from /home/pi/Arduino/ELS/ELS.ino:1:0:
ELS.h:12: error: 'Encoder_tpl' does not name a type
 Encoder_tpl<int64_t> SpindleEnc;

Beginning of ELS.h
Code:
#include "Arduino.h"
//#include "QuadEncoder.h"
#include <EncoderTool.h>
#include "TeensyTimerTool.h"
#include "touchdisplay.h"
#include "threadchart.h"

using namespace TeensyTimerTool;
using namespace EncoderTool;
//using Encoder64 = Encoder_tpl<int64_t>; // e.g. use a 64bit counter

Encoder_tpl<int64_t> SpindleEnc;
Encoder_tpl<int64_t> DROZ_Enc;
Encoder_tpl<int64_t> DROX_Enc;

#define PUL   5
#define DIR   6
#define ENA   7

// Rotary Encoder Pins
There's a few more errors after that, but, they are all related to not getting the Encoders defined correctly. Please advise.
I used < .h> instead of " .h", because Arduino was picking up my renamed EncoderTool_mystuff instead of the new EncoderTool directory that I just cloned.

Edit: whoops. Due to a mistake of mine, I put EncoderTool in /Arduino not /Arduino/libraries ! Fixed that, and my code at least compiles using just the changes above. I will burn it to the Teensy 4.1 and try it out.
 
Last edited:
@luni, it passes the preliminary test. Ran my DRO encoders up and down and they behaved as expected, so that is a good sign! Yes that was a pun, using the word sign, as the DRO's counted properly, both up and down. I will give it a better test tomorrow, and try it on the lathe, rather than on my desktop Teensy PCB.
 
Back
Top