High Resolution Encoders attached to Teensy 4.0 missing pulses

Hello I am new to this forum and hope someone can help me with my project.

I am building an inverted pendulum, for this i am using a Raspberry pi 4 a teensy 4.0 and an odrive.

The mechanical part is working as expected. However I have a serious issue I have not been able to solve in two weeks and is starting to frustrate me:

My overclocked teensy (ca. 1 Ghz) can apparently not handle the pulses coming from two cui amt 102 v encoders which have 2024 ppr each. I would like to utilize Pauls Encoder library since it supports the 4x counting mode and has optimized headers.

https://www.pjrc.com/teensy/td_libs_Encoder.html#optimize

Additionally i would like to "listen" to two end-line switches utilizing the bounce library.

The data-flow is as follows:

The teensy counts the pulses from cui amt encoders and sends the values to the raspberry pi with usb serial. The raspberry pi decodes the serial packages from the teensy and published them on a redis server that i can access through a python script and visualize the pendulum.

The only remaining problem is that when both encoders are moved quick enough at least one of them is loosing pulses. I think it is because altough the teensy is very fast it is not enough for handling the combined pulses of two encoders.

I suspect that the serial communication is slowing down the pulse reading.
Another plausible idea is that the Encoders that are connected to the teensy draw too much power from the board when turned really fast. (both encoders (5v)and both switches(3v) are fed by the teensy.

I would be really thankfull if anyone can give me some advice on how to solve my issue. I am open to add one or two teensies to my setup. however i would like to keep the usb serial communication with the raspberry pi for gathering all the sensor data, since it is very convenient.

My arduino Sketch is as follows:

#define ENCODER_OPTIMIZE_INTERRUPTS

#include <Bounce.h>
#include <Encoder.h>
#include <iostream>

Encoder angleEncoder(5, 6);
Encoder posEncoder(7, 8);



const int touchSensor_Pin_left = 15;
Bounce touchSensor_left = Bounce(touchSensor_Pin_left, 5); 

const int touchSensor_Pin_right = 14;
Bounce touchSensor_right = Bounce(touchSensor_Pin_right, 5); 

long anglePulses = 0;
long posPulses=0;
long newanglePulses = 0;
long newposPulses = 0;
String sdata="";  

void setup() {
  
  Serial.begin(115200);
  anglePulses  = 0;
  posPulses = 0;

  pinMode(touchSensor_Pin_left, INPUT);
  pinMode(touchSensor_Pin_right, INPUT);
}

void resetEncoders()
{
  anglePulses  = 0;
  posPulses = 0;
  newanglePulses = 0;
  newposPulses = 0;
  angleEncoder.write(0);
  posEncoder.write(0);
}

void resetAngle()
{
  //Serial.print("resetting angle");
  anglePulses  = 0;
  newanglePulses = 0;
  angleEncoder.write(0);
}

void resetPosition()
{
  //Serial.print("resetting position");
  posPulses = 0;
  newposPulses = 0;
  posEncoder.write(0);
}


void writePosition(int decision)
{
  int desired_left = -25585;
  int desired_right = +25585;
  switch(decision)
  { case 1:{
     posPulses = desired_left;
     newposPulses = desired_left;
     posEncoder.write(desired_left);
     break;
     }
     case 2:{
     posPulses = desired_right;
     newposPulses = desired_right;
     posEncoder.write(desired_right);
     break;
     }
  }
}

  bool old_left_flag = false;
  bool left_flag = false;
  bool left_flank = false;
  
  bool old_right_flag = false;
  bool right_flag = false;
  bool right_flank = false;

    char newangle ;
    char newpos ;
    char left_flag_c ;
    char right_flag_c ;
  
void loop() {

  
  if (touchSensor_left.update()) 
    {
      if ((touchSensor_left.risingEdge())&&(!left_flank)) {
        left_flag = true;
        left_flank =true;
      }
      
      if((left_flank)&&(touchSensor_left.fallingEdge())){
        left_flank = false;
        left_flag = false;
      }
    }
    

  if (touchSensor_right.update()) 
    {
      if ((touchSensor_right.risingEdge())&&(!right_flank)) {
        right_flag = true;
        right_flank =true;
      }
      if((right_flank)&&(touchSensor_right.fallingEdge())){
        right_flank = false;
        right_flag = false;
      }
    }

  
  newanglePulses = angleEncoder.read();
  newposPulses = posEncoder.read();
  
  if ((newanglePulses != anglePulses) || (newposPulses != posPulses) || (right_flag != old_right_flag||left_flag != old_left_flag)) {
    Serial.print(newanglePulses);
    Serial.print(",");
    Serial.print(newposPulses);
    Serial.print(",");
    Serial.print(left_flag);
    Serial.print(","); 
    Serial.print(right_flag);
    Serial.print("\n");

    anglePulses = newanglePulses;
    posPulses = newposPulses;
    old_left_flag = left_flag;
    old_right_flag = right_flag;
  } 


  byte ch;
  String valStr;

   if (Serial.available()) {
      ch = Serial.read();

      sdata += (char)ch;

      if (ch=='\n') {  // Command received and ready.
         sdata.trim();

         switch( sdata.charAt(0) ) {
         case 'c':
            resetEncoders();
            break;
         case 'a':
            resetAngle();
            break;
         case 'p':
            resetPosition();
            break;
         case 'r':
            writePosition(1);
            break;
         case 'l':
            writePosition(2);
            break;
         }
         sdata = "";
      }
   }
  }

Have you tried other libraries?

Interrupt frequency can be reduced by using 2x counting mode. If this doesn't help then I suspect a big problem somewhere else.

1 Like

So how fast is the pendulum moving? Can you estimate the PPS from each encoder?

A quick look a the encoder datasheet suggests bounce may not be an issue.

1 Like

Have you considered using some of the timers as counters? When a processor is overclocked it reaches a point where parts inside start to fail at the clock speed for whatever reason, such as internal rise time. This normally does not damage the part but since it is not working properly the results are not correct either. There are a lot of other things that happen as well but this I believe may be your problem. Also be sure it is not operating to hot, as you speed it up the temperature range it will operate in diminishes.

1 Like

And require 5V to work, the Teensy 4 being strictly 3.3V only...

1 Like

When you say it is losing pulses, what exactly is happening that tells you a pulse has been lost?

Would it be possible to accumulate the encoder changes in a circular buffer, then print from that buffer? That might give you an indication of where the problem is.

Also, I have no experience with this encoder. Is there no bounce at all?

1 Like

First of all, thank you all for the help and suggestions. I am surprised for how quick the responses are!
First thing: I will use a voltage translator for the encoders thanks to MarkT remark, I wasn't aware of that.

I have tried using the QuadEncoder library withouth success, it did not work out of the box and I stopped trying. Are there any suggestions to use an other library ? I am open to try!

For the velocity estimation it is difficult to say, since it looses steps only when the cart of the pendulum accelerate quickly and the pendulum is rotating very fast. However I need to increase the tolerance in speed and acceleration, because I am planning on training RL agents on this thing.... and i would like to have to implement or calculate velocity limits.

As for why I know that the teensy is missing steps:
The encoder library accumulates pulses. I display the pulses on a simulation that reads those pulses and at the initialization I set the stable position at the bottom to 0.
When I start to move the pendulum slowly, my simulation is accurate and mirrors the physical system very well. however, when I am very aggressive and move both the cart and pendulum very fast, the angle of the pendulum in simulation is not in congruence with the real pendulum.
That means that the serial messages encoding the pulses of the pendulum are wrong. I have observed that this happens also more often when the touch sensors at the sides are activated.

As for the plan of action I will for sure implement the 2x counting method for at least the position sensor. But I would really like to achieve I high resolution on the pendulum encoder.

I will try to estimate a Max velocity for rotation and position.

If nothing changes I will try to utilize two teensyies one for the rotation of the pendulum and another for the position and the touch sensors. And here I have a question for u guys. Is it possible that In connecting two serial devices with this kind of output frequency I overload the serial chip on the raspberry pi ?
I already have some latency issues what is concerning the control loop which is running at 10 hz.. my actuation loop is running at 20 hz.... at the moment my SensorCollector loop which is getting the information from serial is "listening" to the serial input. I guess that with two teensyies i could easily run into some problems.. right ??

I am happy if I get some feedback and some solutions ! I appreciate your time ! and thanks in advance

P.S
I need the bounce library for the touch sensors, they are super bouncy, in fact... is someone has a suggestion for another kind of sensor that can help me with the callibration of the cart position and safety of the cart during operation... that would be great...

Do you mean you move the cart by main or by code? IMO high pendulum speed does not occur with high cart speed, as long as the system remains stable.

Some questions:

  • How do you control the cart?
  • What are your touch sensors? Left/right track limit or touch pad for input? Light barriers instead don't need debouncing.
  • Why do you care so much about pulses in code, when angle and position (value and/or speed) are sufficient for control?
  • Are you sure that serial communication does not block your code? High pulse rate will force high communication rate, at the wrong time.

The answers to your questions:

  • During "normal" operation i control the cart using a bldc motor, controlled by an odrive, that operates with a triple PID cascade at 8khz
    However during testing i move the cart by hand, since I wanted to isolate the problem source.
    I want the sensors to be accurate also when there is some unexpected behavior from the actuation side.

  • the touch sensors are Left and Right "track limit" sensors and basically are just some pulled-down switches, and are therefor really bouncy.

  • The accurate count of the pulses is so important because they are the basis of the angle and position(also velocity for both dimensions) calculations made on the raspberry pi. So for instance if the angle pulses are lost, the simulation and therefore the controller might think that the upright position of the pendulum is achieved before having reached the upright position on the physical system. Please remember I initialize the pulse counting at the bottom stable position of the pendulum. If initially (for example) 10 pulses are counted to achieve the upper stable position on the physical system, after a loss of pulses during operation the angle of the physical system is not discretized by the pulses correctly, meaning that for instance the teensy only counted 9 pulses during the same travel to the upright position, leading to a false representation in simulation and the controller receiving garbage values. This is particularly bad if I want to train RL agents on this system, since collected data on such a system would not be necessarily markovian, and moreover would not mirror the physical world.

  • No I am not sure if serial communication does not block my code. In fact that is one of my former questions, since I am not that familiar with the Arduino libraries.

In fact it might be a good idea to send serial data on a fixed schedule. Any ideas on how to implement that nicely on the arduino, are there any good libraries for that?

As mentioned already my information flow is as follows:

physical System -> Arduino sends serial packages -> raspberry pi listens to packages -> publishes data at 10Hz on redis inmemory server-> calculates control -> sends actuation at 20hz

At the moment I am sending "action data" to the odrive through PWM which has a max frequency of 20 hz. that is the only bottleneck at the moment. I guess also the serial chip on the raspberry pi could represent a bottleneck in the future, because i will send "actuation" via another communication method.

Any suggestions ?

thanks in advance

See the BlinkWithoutDelay example.

I'm not seeing the full picture (probably my limitation). However it struck me you could focus on different things at different time. i.e. not all in one loop.
For instance,

  • hold off the serial com when measuring the pendulum movement.
  • Then don't measure the pendulum when performing serial communications.

In addition, I would write a small program that only reads the encoders, nothing else. Once you get that functioning properly you can likely see how to add the remaining functions.

I just watched Paul's Youtube video showing a test of his encoder library, and at the rotation speed he uses performance appears to be perfect. Are you turning faster than that? But of course he's just servicing one encoder, not two.

In theory, the processor's serial peripheral should be able to handle serial coms independently from what's going on elsewhere, at least with respect to transmitting a single byte. Once started, it should continue to transmit right through any interrupt that may occur. So, again in theory, serial should not interfere with the encoder interrupts, and the interrupts should not interfere with serial. But that doesn't take into account what the serial library may be doing. Could you test this by having the Teensy keep track of the encoder positions, but only transmit it every 10 seconds or so? That would at least test whether the Teensy and Paul's library can handle two encoders.

https://www.youtube.com/watch?v=2puhIong-cs