Curtain project with encoder motor accuracy

Hi, i want to control my roller blinds with an encoder motor like those that rotate the chain.
I use esp32, 5v encoder motor, mx1508, txs0108e. 3 buttons for up, down and stop, long press on stop enter set limit mode. The issue is that it takes some time from when i brake the motor until it actually stop. I checked the count difference for both direction (up around 116 and down 122) but it can differ from time to time. I tested it on my curtain and its not so accurate. I wonder if there is a better approach to do this? I added my sample code here if someone want to take a look.

#include <OneButton.h>
#include "ESP32Encoder.h"
#include <ESP32MX1508.h>

#define CH1 0
#define CH2 1
#define IN1_PIN 4
#define IN2_PIN 17
#define ENCODER_SIGNAL_A 13
#define ENCODER_SIGNAL_B 14

#define UP_BUTTON_PIN 25
#define STOP_BUTTON_PIN 26
#define DOWN_BUTTON_PIN 27
#define SET_LIMIT_LED 33

OneButton upButton(UP_BUTTON_PIN);
OneButton stopButton(STOP_BUTTON_PIN);
OneButton downButton(DOWN_BUTTON_PIN);

bool isInSetLimitsMode = false;
bool isUpperLimitSet = true;
bool isLowerLimitSet = true;
int numOfStopClicks = 0;

MX1508 motor(IN1_PIN, IN2_PIN, CH1, CH2);
ESP32Encoder encoder;
bool isMotorRunning = false;
bool isGoingUp = false;
long currentEncoderPos = 0;
long upperLimitPos = 0;
long lowerLimitPos = 0;

void tickButtons()
{
  stopButton.tick();
  upButton.tick();
  downButton.tick();
}

void curtainStop()
{
  if (isMotorRunning)
  {
    motor.motorBrake();
    delay(1000);    
    motor.motorStop();  
    isMotorRunning = false;
  }
}

void curtainUp()
{
  Serial.println("currentEncoderPos: " + String(currentEncoderPos) + " upperLimitPos: " + String(upperLimitPos));
  if (currentEncoderPos < upperLimitPos || !isUpperLimitSet)
  {                    
    curtainStop();     
    motor.motorGo(255); 
    isMotorRunning = true;
    isGoingUp = true;
  }
  else
  {
    Serial.println("motor in upper limit");
    curtainStop(); 
  }
}

void curtainDown()
{
  Serial.println("currentEncoderPos: " + String(currentEncoderPos) + " lowerLimitPos: " + String(lowerLimitPos));
  if (currentEncoderPos > lowerLimitPos || !isLowerLimitSet)
  {                     
    curtainStop();       
    motor.motorRev(255); 
    isMotorRunning = true;
    isGoingUp = false; 
  }
  else
  {
    Serial.println("motor in lower limit");
    curtainStop(); 
  }
}

void checkLimits()
{
  if (isMotorRunning)
  {
    currentEncoderPos = encoder.getCount(); 

    if (isGoingUp && currentEncoderPos >= (upperLimitPos - 116) && isUpperLimitSet)
    {
      Serial.println("Stopping motor at upper limit");
      curtainStop();
    }

    if (!isGoingUp && currentEncoderPos <= (lowerLimitPos + 122) && isLowerLimitSet)
    {
      Serial.println("Stopping motor at lower limit");
      curtainStop();
    }
  }
}

void curtainGoToPos(int targetPos)
{
  targetPos = constrain(targetPos, 0, 100);
  long targetEncoderPos = map(targetPos, 0, 100, upperLimitPos, lowerLimitPos);

  Serial.println("currentEncoderPos: " + String(currentEncoderPos) + " targetEncoderPos: " + String(targetEncoderPos));

  if (currentEncoderPos > targetEncoderPos)
  {
    targetEncoderPos += 122;
    Serial.println("go to pos down: " + String(targetEncoderPos));
    curtainDown();
    while (currentEncoderPos > targetEncoderPos && isMotorRunning)
    {
      currentEncoderPos = encoder.getCount(); 
      if (currentEncoderPos <= targetEncoderPos)
      {
        curtainStop();
        break; 
      }
      tickButtons(); 
    }
  }
  else if (currentEncoderPos < targetEncoderPos)
  {
    targetEncoderPos -= 116;
    Serial.println("go to pos up: " + String(targetEncoderPos));

    curtainUp();
    while (currentEncoderPos < targetEncoderPos && isMotorRunning)
    {
      currentEncoderPos = encoder.getCount(); 
      if (currentEncoderPos >= targetEncoderPos)
      {
        curtainStop();
        break; 
      }
      tickButtons(); 
    }
  }

  delay(1000);
  Serial.println("adjsut = " + String(targetEncoderPos - encoder.getCount()));
}

void upButtonHandleClick()
{
  Serial.println("clicked up button");
  if (isInSetLimitsMode)
  {
    numOfStopClicks = 0;
  }

  curtainUp();
}

void downButtonHandleClick()
{
  Serial.println("clicked down button");
  if (isInSetLimitsMode)
  {
    numOfStopClicks = 0;
  }

  curtainDown();
}

void stopButtonHandleClick()
{
  Serial.println("clicked stop button");
  curtainStop();

  if (isInSetLimitsMode)
  {
    numOfStopClicks++;
    if (numOfStopClicks >= 2)
    {
      if (!isUpperLimitSet)
      {
        encoder.clearCount();
        upperLimitPos = encoder.getCount();
        Serial.println("upper limit has been set: " + String(upperLimitPos));
        isUpperLimitSet = true;
        numOfStopClicks = 0;

        digitalWrite(SET_LIMIT_LED, LOW);
        delay(1000);
        digitalWrite(SET_LIMIT_LED, HIGH);
        return;
      }
      else
      {
        lowerLimitPos = encoder.getCount();
        Serial.println("lower limit has been set: " + String(lowerLimitPos));
        isInSetLimitsMode = false;
        isLowerLimitSet = true;
        numOfStopClicks = 0;

        digitalWrite(SET_LIMIT_LED, LOW);
        return;
      }
    }
  }
}

void stopButtonHandleLongPress(void *oneButton)
{
  Serial.println("long press stop button");
  isInSetLimitsMode = !isInSetLimitsMode;
  if (isInSetLimitsMode)
  {
    digitalWrite(SET_LIMIT_LED, HIGH);
    isUpperLimitSet = false;
    isLowerLimitSet = false;
    numOfStopClicks = 0;
  }
  else
  {
    digitalWrite(SET_LIMIT_LED, LOW);
  }
}

void attachButtons()
{
  upButton.attachClick(upButtonHandleClick);
  downButton.attachClick(downButtonHandleClick);
  stopButton.attachClick(stopButtonHandleClick);
  stopButton.attachLongPressStart(stopButtonHandleLongPress, &stopButton);
  stopButton.setPressMs(1000);
}

void setup()
{
  Serial.begin(115200);

  pinMode(SET_LIMIT_LED, OUTPUT);
  attachButtons();
  ESP32Encoder::useInternalWeakPullResistors = puType::up; 
  encoder.attachFullQuad(ENCODER_SIGNAL_A, ENCODER_SIGNAL_B);
  encoder.clearCount();
}

void loop()
{
  tickButtons();

  long newEncoderPos = encoder.getCount(); 
  if (newEncoderPos != currentEncoderPos)
  {
    currentEncoderPos = newEncoderPos;
  }

  checkLimits();
}

What to you mean my "accurate"? You say you are off by 116 or 122 encoder counts. Does that represent 1/4" or 1 foot? If you want absolute precision, you will need to use a stepper motor.

2 Likes

The difference can be up to 3cm for now, but since its a zebra curtain it matters. The curtain motor I have now seems to be encoder motor with 8 wires and its working fine. I just wanted to know if my way is the most i can get from my motor/encoder or I'm doing something wrong.

What is this component used for in your circuit?

for the encoder signals, 0-5v and esp32 3.3v.

Isn't momentum the problem here?

If you were driving a car from one parking bay, 1 mile down a road, into another parking bay, you wouldn't floor the accelerator (gas pedal) and then try to perfectly time an emergency stop, hitting the brakes hard at full speed, in order to stop exactly in the parking bay (unless you are a professional stunt driver, of course!). As you approach the parking bay, you would slow down by taking your foot off the gas as you approach, so that as you manoeuvre into the bay, the car is only moving slowly. You would only press the brake just to stop that last little bit of momentum, when you are perfectly in the bay.

Your code is only using

motor.motorGo(255);
motor.motorRev(255);
motor.motorStop();
motor.motorBrake();
1 Like

Oh. Why not just use resistors? A couple per pin.

Why does your code have this line?

ESP32Encoder::useInternalWeakPullResistors = puType::up;

So you suggest to slow down from 255 to 0? by what decrement? But anyway dose it matter since i calculate the counts it takes to complete stop? the desired count for the upper limit is 0 and the count after i completely stop is lets say 2. I think i might need to test with the motor mounted to the wall to be sure it doesn't miss.

Oh. Why not just use resistors? A couple per pin.

I have heard you should use logic converter for actual application / long term, not sure if its correct. of course i would need 2 channel instead.

Why does your code have this line?

It was on the library example and since I had issues i decided to try this but I don't see any difference. should this be removed?

You will need to experiment to find out what works well. You may not need to gradually change the motor speed, maybe just use full speed (255) until the encoder gets to within some distance of the target count, then simply cut the speed to 1/4 or 1/8 of full for the last short distance.

Note: there will be a minimum PWM below which the motor will not run, or not run reliably. That might be different when going up versus going down. And also different to the minimum PWM at which the motor will start from stationary, that's normally higher.

2 Likes

Active level conversion can be important where high frequency signals and/or very long wires are involved, but I doubt that's the case here.

It won't do any harm, but won't make any difference when using the txs0108e, or when using resistors to reduce the 5V signal to 3.3V (one of the resistors in a voltage divider acts as a pull-up anyway).

Are you adding pull-up resistors to the inputs of the txs0108e? Does the encoder board inside the motor contain pull-up-resistors? I guess one or the other must be true, or your encoder would not be working at all.

But many encoders are simply a pair of switches with a common terminal ("A", "B", "COM"). The COM may be internally connected to ground. If that's the case, you might need neither the txs0108e or resistors between the encoder and the ESP32 and simply use those internal pull-ups. But if the encoder pins are pulled up to 5V internally, then you will need to reduce that down to 3.3V to avoid damaging the ESP.

my knowledge in electronics is not so good. I did not add pull ups to the logic converter inputs. The encoder has 2 10k resistors, one goes to positive and the other to negative of the encoder. I'm not sure what is the COM, how can i check that? as much as i know from the seller the signals output 0-5v.

How much time does it take to stop? In time and encoder position counts? How much time does it take to do the whole motion? Also in time and steps.

That makes no sense. There might be pull-up resistors or pull-down resistors, but not both.

Perhaps you could post a link to the seller's page with the specifications or internal schematic for the encoder?

The holding mechanism in your shade is the thing that is missing 3cm. There are sets of internal springs on both ends of the rod. These springs need back-and-forth play because they are also brakes to hold the shade at any position. Your chain will also elongate as it ages. I have a dozen heavy shades with chain pulls that I shorten a couple times a year. I prefer the nylon/plastic chain pulls over the metal chain pulls because the nylon/plastic is kinder on the pulley gear and does not stretch anywhere near the metal chain pull.

It's not consistent but this is when the curtain goes up.
the movement: 10075 counts, 3076 millis.
the breaking: 45 counts, 37 millis.

You are right, both goes to positive.

Perhaps you could post a link to the seller's page with the specifications or internal schematic for the encoder?

I bought from Aliexpress so not much info there. I asked the seller for that, will see.

1 Like

I don't think its the issue here because my current curtain motor working fine. I think the issue might be the encoder or maybe because the motor is not mounted to the wall.

Can you probe and graph the present motor current? Maybe seeing how it uses power will indicate if your motor project needs adjustment.

You have no schematic from the seller, but you know that? Is the PCB exposed so that you can trace the tracks by eye, or figure out where they go with a multimeter?

Then go full speed for 10075-45=10030 counts, and then taper off to slow/stopped over the next 45 counts.