Forum Rule: Always post complete source code & details to reproduce any issue!
Page 1 of 2 1 2 LastLast
Results 1 to 25 of 41

Thread: Using Teensy for big MIDI control surface

  1. #1

    Using Teensy for big MIDI control surface

    Hello
    I am interested in using teensy boards in my MIDI controller projects.

    I need loads of inputs for internal interrupts, and when I say loads I mean loads (300)
    I was wondering if it is possible to network several teensy boards together (so as to
    allow the connection of many components that need internal interupts)
    and then designate one of the teensy boards as master, the others as slaves
    and then connect to a computer via a USB connector?

    I am not a teensy expert, but was wondering how feasible it would be to
    achieve this type of scenario.
    Thanks

  2. #2
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,748
    What are you interfacing to that needs so many interrupt inputs?

  3. #3
    Quote Originally Posted by MarkT View Post
    What are you interfacing to that needs so many interrupt inputs?
    Hello
    Thanks for replying to my post.

    To answer your question, I want to use high definition endless rotary encoders and precision really is essential for this particular purpose.
    I have tried rotary encoders connected using digital pins and using external interrupts
    I found the precision is superior using the interrupts.

    If you take a look at a pro (large and small) mixing consoles / desks, or perhaps the Euphonix system 5 control surface
    that will give you some idea of what I would like to achieve

    although perhaps a cut down version, even 24 channel version would be something.

  4. #4
    Member
    Join Date
    Jun 2019
    Location
    Sydney, Australia
    Posts
    42
    You can daisy chain 8 MCP23017s off one i2c bus, giving 128 (interrupt capable) digital pins.

    That would support 64 encoders (if you're not using switches).

    And the Teensy 4 has 3 I2C busses, so you can support 192 encoders without having to do anything crazy.

    You will also have plenty of other digital pins still available for more (especially with a T4.1).

  5. #5
    Senior Member
    Join Date
    Aug 2019
    Location
    Melbourne Australia
    Posts
    302
    You might also check this out:-

    https://github.com/luni64/EncoderTool

    Currently using it with 32 x 96 PPR detentless encoders using the 4067 method.

  6. #6
    Quote Originally Posted by kallikak View Post
    You can daisy chain 8 MCP23017s off one i2c bus, giving 128 (interrupt capable) digital pins.

    That would support 64 encoders (if you're not using switches).

    And the Teensy 4 has 3 I2C busses, so you can support 192 encoders without having to do anything crazy.

    You will also have plenty of other digital pins still available for more (especially with a T4.1).

    Hi Kalikak
    thanks very much for your advice
    I will have to look into this, as it sounds like a solution
    great

  7. #7
    Quote Originally Posted by MatrixRat View Post
    You might also check this out:-

    https://github.com/luni64/EncoderTool

    Currently using it with 32 x 96 PPR detentless encoders using the 4067 method.
    hey MatrixRat
    thanks for your advice
    I shall have to take a look into this in more detail

    as from what you guys have said
    sounds like the teensy boards combined with the expansions could be exactly what i need
    to achieve what i need
    cheers
    jus

  8. #8
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,748
    Quote Originally Posted by ghostdzog1 View Post
    I want to use high definition endless rotary encoders
    Perhaps a link to the datasheet for the parts in question? - better than trying to guess what "high definition"
    might mean to you, 1000 PPR, 10000 PPR, or even more?

    How many encoders are likely to be changing simultaneously too - that could be a thing to consider.

  9. #9
    Quote Originally Posted by MarkT View Post
    Perhaps a link to the datasheet for the parts in question? - better than trying to guess what "high definition"
    might mean to you, 1000 PPR, 10000 PPR, or even more?

    How many encoders are likely to be changing simultaneously too - that could be a thing to consider.
    Hello Mark T
    Thank you for replying to my thread.

    I would ideally like to use interrupts instead of normal digital pins, as in practice, I have tried both, and the interrupt method gives
    a really smooth precise performance compared with the normal method, which gives a fairly poor performance.

    Presently using the encoders to control various features and functions in Cubase 12.
    The digital pin method basically unusable.

    So, if all it means is to add a couple of extra chips that gives an expansion
    then why not

    From a sound engineering / music producers perspective, people generally want to go for best performance in such areas.
    Also, from a pro engineering perspective. if the encoders were used on a large format mixing console. It isn't that uncommon
    in pro studios, for more than one engineer to be accessing several controls at the same time.

    take sliding potentiometers for example
    it isn't uncommon for one person to attenuate several sliding potentiometers at the same time, either literally or when included as a group
    some of the more fancy large format mixing consoles allow for the user to incorporate several into a group, which then can be operated using one fader

    when it comes to rotary encoders, there could be several all being twisted at the same time, even on a small format mixing console

    anyway, if the cost of implementing a couple of expansion chips is only $20
    then why not use interrupts if it gives a superior performance

    The euphonix system 5, when released, used to cost an arm and a leg (in the tens of thousands, probably closer to $100,000)
    so, $20 to include interrupts seems reasonable for a budget version of something similar but cut down

  10. #10
    come to think about it, when using so many encoders (150+)
    using interrupts is likely the most efficient with regards to memory usage, as the encoders only send data when their settings change
    compared to the teensy having to constantly check the settings, imagine how memory intensive it would be using the latter method
    if you have 150 plus encoders that need to be checked constantly

    yep. i am sold on the interrupt method. more efficient.

  11. #11
    Senior Member
    Join Date
    Aug 2019
    Location
    Melbourne Australia
    Posts
    302
    Two questions:- How many Counts per revolution are you looking for? and what type of encoders?

    For what I'm doing with Midi, most parameters are in the range 0-127 so 96 CPR covers the range with about 1.25 turns. 48 CPR - boring!

  12. #12
    Quote Originally Posted by MatrixRat View Post
    Two questions:- How many Counts per revolution are you looking for? and what type of encoders?

    For what I'm doing with Midi, most parameters are in the range 0-127 so 96 CPR covers the range with about 1.25 turns. 48 CPR - boring!
    I just know from practice, using Cubase 12. Both methods still output MIDI data
    the second using interrupts gives a smooth easy to control performance

    the first gives a really jumpy performance with the encoders that I currently am using
    especially if using multiplex's or shift registers to connect the amount I want.

    If the interrupt method is more data efficient than the digital pin method and I can implement the mcp23017s easy enough, then why not.

    Perhaps you should try the interrupt method and compare.
    Even if using Arduino Uno's, you have 2 interrupts which can be used to connect 1 rotary encoder, while connecting the same type of encoder using your normal method.

    Just try and see.

  13. #13
    Senior Member
    Join Date
    Aug 2019
    Location
    Melbourne Australia
    Posts
    302
    Am using interrupts. Did need to tweak EncoderTool library to generate additional address bits for 74HCT138 for up to eight pairs of 4067s however uses more pins than the MCP23017 method. Either way is still a multiplexer.

    As for the (mechanical) encoder itself, a batch of 50 had some duds, like 20% so is a good idea to run them in and test before comitting them to solder. Wise to design hardware so are easily replaceable.

  14. #14
    Member
    Join Date
    Jun 2019
    Location
    Sydney, Australia
    Posts
    42
    Some thoughts:

    - memory is a non-issue for this application
    - interrupts are more efficient and more elegant to implement than polling (what you are calling the digital pin method, but both methods are digital)
    - if you are seeing such a big difference though, check your code. I reckon a T4 can comfortably poll > 100 encoders in under a millisecond.
    - with MCP23017s you can read all 16 bits with one call
    - 4067s are analog switches, not an advantage for encoders, but necessary if you want to multiplex pots. However you have to deal with noise, settling time etc, so they can be more complex to deal with.

    In the past I made a control surface for a Blofeld that had maybe 80 controls - a mixture of pots, encoders and switches. I used a T4 and 3 MCP23017s and 2 CD74HC4067s. All worked well and easily.

  15. #15
    Quote Originally Posted by kallikak View Post
    Some thoughts:

    - memory is a non-issue for this application
    - interrupts are more efficient and more elegant to implement than polling (what you are calling the digital pin method, but both methods are digital)
    - if you are seeing such a big difference though, check your code. I reckon a T4 can comfortably poll > 100 encoders in under a millisecond.
    - with MCP23017s you can read all 16 bits with one call
    - 4067s are analog switches, not an advantage for encoders, but necessary if you want to multiplex pots. However you have to deal with noise, settling time etc, so they can be more complex to deal with.

    In the past I made a control surface for a Blofeld that had maybe 80 controls - a mixture of pots, encoders and switches. I used a T4 and 3 MCP23017s and 2 CD74HC4067s. All worked well and easily.

    Thanks for your advice and I can understand your perspective and opinion. it all sounds very interesting
    but to be honest, when i say 150 rotary encoders, i mean the min is 150, and ideally a great deal more.

    I don't understand why you have a problem with me using this latter form of methodology, as after all, what difference does it matter to any one else in the universe. I just see it as more efficient

    as if you take a moment to consider, 150 plus encoders constantly polling is far less effiecient than one or two encoders sending interrupt messages every now and then

    that's how i see it basically, interrupts logically sound miles more efficient long term than using constant polling
    elegant or not

  16. #16
    Member
    Join Date
    Jun 2019
    Location
    Sydney, Australia
    Posts
    42
    Quote Originally Posted by ghostdzog1 View Post
    I don't understand why you have a problem with me using this latter form of methodology
    I don't understand why you would think I do - I explicitly stated that interrupts are better and more elegant.

    But from your comments it seems like you are new to this, so I tried to address some aspects that are relevant and understanding them should help you make a better solution.

    But don't worry - I won't disturb you anymore.

  17. #17
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,833
    Discussions about encoder readout strategies seem to always end up like windows vs. linux discussions :-)

    I'm the author of the EncoderTool library which, to avoid those discussions, supports both strategies :-). As MatrixRat already mentioned, it also supports a few multiplexing methods out of the box (see https://github.com/luni64/EncoderToo.../master/extras) and can easily be adapted to support others like a MCP23017. I'd probably go for the SPI version which is faster.

    To read out 150+ encoders I'd spend a dedicated T4.0 to do the readout. The additional cost will be marginal compared to the cost of the encoders, boards and mechanics. I personally would start with implementing a polled algorithm since it is more robust, but if you prefer, you can of course implement the readout interrupt based.The library doesn't really care. In any case the processor will be quite busy with this. But, since neither NXP nor PJRC will refund any money for not used processor cycles I wouldn't care. The EncoderTool also implements a callback mechanism which triggers on value changes and might come in handy for this amount of encoders.

    @MatrixRat: Do you have anything online of your project? If you don't mind I'd like to add a link as usage example to it. Your extension to more than one 4067 might also be a nice example...
    Last edited by luni; 05-13-2022 at 06:29 AM.

  18. #18
    Senior Member mortonkopf's Avatar
    Join Date
    Apr 2013
    Location
    London, uk
    Posts
    962
    plus one for the SPI version: MCP23s17. also has two interrupts for each chip (two ports), and like I2C can be chained to eight in length. obviously nowhere near 150 encoders, I am running sixteen rotary encoders in polling mode on a single chain, and no glitches. the interrupt method was proving a little tricky to nail down.

  19. #19
    Quote Originally Posted by mortonkopf View Post
    plus one for the SPI version: MCP23s17. also has two interrupts for each chip (two ports), and like I2C can be chained to eight in length. obviously nowhere near 150 encoders, I am running sixteen rotary encoders in polling mode on a single chain, and no glitches. the interrupt method was proving a little tricky to nail down.
    thanks to everyone for their advice

  20. #20
    Quote Originally Posted by kallikak View Post
    I don't understand why you would think I do - I explicitly stated that interrupts are better and more elegant.

    But from your comments it seems like you are new to this, so I tried to address some aspects that are relevant and understanding them should help you make a better solution.

    But don't worry - I won't disturb you anymore.
    Hello Kalilikak
    thanks for your reply, please don't be offended by my previous reply. I suffer from Asperger Syndrome, which does affect the way I communicate
    I understand that at times I can come across as abrupt and offensive when I really do not mean to be.
    I am just trying to work out the best solution for the problem I have at hand and am very grateful for any advice from you and others who are experienced in
    this area, and in return, I would be happy to help people who help me in areas that I may have more experience in.
    Thanks again for your advice

  21. #21
    Senior Member
    Join Date
    Aug 2019
    Location
    Melbourne Australia
    Posts
    302
    I'm using the Sparkfun 4067 breakouts and the actual chip is marked HP4067 which i think translates to 74HC4067. I see nothing preventing one doing the job of my HC138 so theoretically at least that's 256 encoders.

    At the end of the day, it doesn't matter what method, just that you get the results you want and at my age, not really fussed about elegance.

    @luni. My 32 enc 4067 version has been tested on a T4.1, used series R from T4 outputs to HCT245. Think it a good idea to add support for 23017.

    My ears must have been burning earlier because I got out the board and camera. Will put something together.

  22. #22
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,833
    Think it a good idea to add support for 23017.
    Yes, I just looked through my stock of bought but never used parts and found a couple of 23S017. Starting to rain here. So, extending the EncoderTool might be a nice weekend project :-)

  23. #23
    Quote Originally Posted by luni View Post
    Yes, I just looked through my stock of bought but never used parts and found a couple of 23S017. Starting to rain here. So, extending the EncoderTool might be a nice weekend project :-)
    Cool. I think there would be a fan base for such endeavours, perhaps even something you could get people to donate money to you in order to do so.
    In a platform such as patreon.

    I will order a couple of teensys and a few mcp23017s, already have a few multiplexes.
    Will need to go through encoders to try and find the ones that i think are best for the purpose I need them for.
    and buy some for the prototyping stage, write / test code before moving on to creating pcb's and building the end result.

    If i get stumped, I may even see if i can pay someone to help complete this ambitious project.

  24. #24
    Senior Member
    Join Date
    Apr 2014
    Location
    Germany
    Posts
    1,833
    Here an example how to use a MCP23S17 to read out encoders with the EncoderTool (working example here: https://github.com/luni64/EncoderToo...lexed_MCP23S17). If you want to use any kind of multiplexer all you need to do is to subclass the EncPlex base class and call its update(A,B) function whenever you have got new A/B values for one of the encoders. In this example the updates happen in a tick() function which you need to call as often as possible (loop() or better yield() is a good place to do so)

    file: EncPlex23S17.h
    Code:
    #pragma once
    
    #include "Adafruit_MCP23X17.h"  //  //https://github.com/adafruit/Adafruit-MCP23017-Arduino-Library,  a bit slow, I'd look for a faster one
    #include "EncoderTool.h"
    
    namespace EncoderTool
    {
        class EncPlex23S17 : public EncPlexBase  // The base class will take care of the bookkeeping and decoding 
        {
         public:
            inline EncPlex23S17(unsigned EncoderCount);  
    
            inline void begin(CountMode mode);
            inline void tick(); // call as often as possible
    
         protected:
            Adafruit_MCP23X17 mcp21S17;
            bool isSetup = false;
        };
    
        //================================================================================
        // INLINE IMPLEMENTATION
    
        EncPlex23S17::EncPlex23S17(unsigned encoderCount) // nothing to do but telling the base class the number of encoders it shall generate
            : EncPlexBase(encoderCount)
        {}
    
        void EncPlex23S17::begin(CountMode mode = CountMode::quarter)
        {
            EncPlexBase::begin(mode);                   // setup the base class
            mcp21S17.begin_SPI(10);                     // setup the Adafruit MCP21S17 interface: SPI, CS on pin 10
            for (unsigned i = 0; i < encoderCount; i++) // configure all needed pins as input. We start with A_0/B_0 up to the required number of pin pairs
            {
                mcp21S17.pinMode(i, INPUT_PULLUP);
                mcp21S17.pinMode(i + 8, INPUT_PULLUP);
            }
            isSetup = true;
        }
    
        void EncPlex23S17::tick() // call this as often as possible
        {
            if (isSetup)  // tick might be called from a timer or yield before begin was called -> Prevent accessing the muliplexer before it is setup
            {
                uint16_t data = mcp21S17.readGPIOAB();       // read the data from the 23S17 multiplexer
                for (unsigned i = 0; i < encoderCount; i++)  // for all configured encoders
                {                                            // extract the A/B
                    unsigned A = (data & 1 << i) != 0;       //
                    unsigned B = (data & 1 << (i + 8)) != 0; //
                    int delta = encoders[i].update(A, B);    // the base class will take care of the decoding
    
                    if (delta != 0 && callback != nullptr) callback(i, encoders[i].getValue(), delta); // if something changed, invoke the callback
                }
            }
        }
    } // namespace EncoderTool
    And here how to use it. The example uses a callback to print changes of the encoder. You can also directly access the encoders using the [] operator of the EncPlex23S17 class. E.g. encoders[3].getValue() would return the current value of the encoder attached to A_3 B_3 inputs of the 23S17 (See here https://github.com/luni64/EncoderToo...lexed-encoders for more information on using mulitplexed encoders)


    Code:
    /************************************************************
     *
     * Use a MCP23S17 multiplexer to read out up to 8 attached encoders
     * The A/B pins of the encoders go to the A/B inputs of the MCP
     *
     ************************************************************/
    
    #include "EncPlex23S17.h"
    #include "EncoderTool.h"
    
    using namespace EncoderTool;
    
    EncPlex23S17 encoder(8); // use all 8 (A/B) inputs of the 23S17 to connect encoders
    
    // this will be called whenever one of the connected encoders changes
    void onChange(byte ch, int value, int delta)
    {
        Serial.printf("Encoder #: %d, value: %3d, delta: %2d\n", ch, value, delta);
    }
    
    void setup()
    {
        digitalWriteFast(LED_BUILTIN, OUTPUT);
    
        encoder.begin();
        encoder.attachCallback(onChange); // attach a common callback for all encoders
    }
    
    void loop()
    {
        encoder.tick();
    }
    To extend the code to more than one 23S17 you'd simply extend the tick function to loop over the used chips. The example uses the Adafruit_MCP23X17 library to read out the multiplexer. Looks like this library is a bit slow (takes ~30Ás to read out the 16 pins). If you want to read out 150+ encoders with this chip, using a faster library might be a good idea.

    I used this breakout:
    https://www.amazon.de/-/en/MCP23017-...ps%2C71&sr=8-1

    Hope that helps...
    Last edited by luni; 05-15-2022 at 01:49 PM.

  25. #25
    Quote Originally Posted by luni View Post
    Here an example how to use a MCP23S17 to read out encoders with the EncoderTool (working example here: https://github.com/luni64/EncoderToo...lexed_MCP23S17). If you want to use any kind of multiplexer all you need to do is to subclass the EncPlex base class and call its update(A,B) function whenever you have got new A/B values for one of the encoders. In this example the updates happen in a tick() function which you need to call as often as possible (loop() or better yield() is a good place to do so)

    file: EncPlex23S17.h
    Code:
    #pragma once
    
    #include "Adafruit_MCP23X17.h"  //  //https://github.com/adafruit/Adafruit-MCP23017-Arduino-Library,  a bit slow, I'd look for a faster one
    #include "EncoderTool.h"
    
    namespace EncoderTool
    {
        class EncPlex23S17 : public EncPlexBase  // The base class will take care of the bookkeeping and decoding 
        {
         public:
            inline EncPlex23S17(unsigned EncoderCount);  
    
            inline void begin(CountMode mode);
            inline void tick(); // call as often as possible
    
         protected:
            Adafruit_MCP23X17 mcp21S17;
            bool isSetup = false;
        };
    
        //================================================================================
        // INLINE IMPLEMENTATION
    
        EncPlex23S17::EncPlex23S17(unsigned encoderCount) // nothing to do but telling the base class the number of encoders it shall generate
            : EncPlexBase(encoderCount)
        {}
    
        void EncPlex23S17::begin(CountMode mode = CountMode::quarter)
        {
            EncPlexBase::begin(mode);                   // setup the base class
            mcp21S17.begin_SPI(10);                     // setup the Adafruit MCP21S17 interface: SPI, CS on pin 10
            for (unsigned i = 0; i < encoderCount; i++) // configure all needed pins as input. We start with A_0/B_0 up to the required number of pin pairs
            {
                mcp21S17.pinMode(i, INPUT_PULLUP);
                mcp21S17.pinMode(i + 8, INPUT_PULLUP);
            }
            isSetup = true;
        }
    
        void EncPlex23S17::tick() // call this as often as possible
        {
            if (isSetup)  // tick might be called from a timer or yield before begin was called -> Prevent accessing the muliplexer before it is setup
            {
                uint16_t data = mcp21S17.readGPIOAB();       // read the data from the 23S17 multiplexer
                for (unsigned i = 0; i < encoderCount; i++)  // for all configured encoders
                {                                            // extract the A/B
                    unsigned A = (data & 1 << i) != 0;       //
                    unsigned B = (data & 1 << (i + 8)) != 0; //
                    int delta = encoders[i].update(A, B);    // the base class will take care of the decoding
    
                    if (delta != 0 && callback != nullptr) callback(i, encoders[i].getValue(), delta); // if something changed, invoke the callback
                }
            }
        }
    } // namespace EncoderTool
    And here how to use it. The example uses a callback to print changes of the encoder. You can also directly access the encoders using the [] operator of the EncPlex23S17 class. E.g. encoders[3].getValue() would return the current value of the encoder attached to A_3 B_3 inputs of the 23S17 (See here https://github.com/luni64/EncoderToo...lexed-encoders for more information on using mulitplexed encoders)


    Code:
    /************************************************************
     *
     * Use a MCP23S17 multiplexer to read out up to 8 attached encoders
     * The A/B pins of the encoders go to the A/B inputs of the MCP
     *
     ************************************************************/
    
    #include "EncPlex23S17.h"
    #include "EncoderTool.h"
    
    using namespace EncoderTool;
    
    EncPlex23S17 encoder(8); // use all 8 (A/B) inputs of the 23S17 to connect encoders
    
    // this will be called whenever one of the connected encoders changes
    void onChange(byte ch, int value, int delta)
    {
        Serial.printf("Encoder #: %d, value: %3d, delta: %2d\n", ch, value, delta);
    }
    
    void setup()
    {
        digitalWriteFast(LED_BUILTIN, OUTPUT);
    
        encoder.begin();
        encoder.attachCallback(onChange); // attach a common callback for all encoders
    }
    
    void loop()
    {
        encoder.tick();
    }
    To extend the code to more than one 23S17 you'd simply extend the tick function to loop over the used chips. The example uses the Adafruit_MCP23X17 library to read out the multiplexer. Looks like this library is a bit slow (takes ~30Ás to read out the 16 pins). If you want to read out 150+ encoders with this chip, using a faster library might be a good idea.

    I used this breakout:
    https://www.amazon.de/-/en/MCP23017-...ps%2C71&sr=8-1

    Hope that helps...
    Hello Luni
    Thanks so much for posting such helpful / useful information.
    I am presently in the learning phase of the project, learning how the tech works and how to implement it. So, very good timing.
    I already own a handful of multiplex's, but do not own any 23017s, so may buy a few, just to try out and test. See what type of performance from them both.
    If I can get the mux's to work to a high enough level of performance, perhaps i will stay with it.
    Although think it would be worth trying out the mcp23017s, just to see.

    Yes, many thanks for your help on this project
    j

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •