espressif / arduino-esp32

Arduino core for the ESP32
GNU Lesser General Public License v2.1
13.62k stars 7.41k forks source link

Setting digitalWrite(pin, LOW) does not work in an object class #3152

Closed Proton23 closed 4 years ago

Proton23 commented 5 years ago

Hardware:

Board: ESP32 DevKit V1 Core Installation/update date: Don't know where to look IDE name: Soebler (Eclipse) Flash Frequency: 80Mhz PSRAM enabled: Don't know Upload Speed: 115200 Computer OS: Ubuntu

Description:

I want to turn a 28BYJ-48 stepper motor. The first sketch works.

first Sketch:


#include "Arduino.h"

#include "Motor.h"

int LED = 2;
int step_number = 0;
const int STEPPER_PIN_1 = 23;
const int STEPPER_PIN_2 = 22;
const int STEPPER_PIN_3 = 33;
const int STEPPER_PIN_4 = 32;
boolean builtin = true;

//Motor motor(STEPPER_PIN_1, STEPPER_PIN_2, STEPPER_PIN_3, STEPPER_PIN_4);

void setup() {
    Serial.begin(115200);
    pinMode(LED, OUTPUT);
    pinMode(STEPPER_PIN_1, OUTPUT);
    pinMode(STEPPER_PIN_2, OUTPUT);
    pinMode(STEPPER_PIN_3, OUTPUT);
    pinMode(STEPPER_PIN_4, OUTPUT);
}

void oneStep(){
    digitalWrite(STEPPER_PIN_1, step_number == 0 ? HIGH : LOW);
    digitalWrite(STEPPER_PIN_2, step_number == 1 ? HIGH : LOW);
    digitalWrite(STEPPER_PIN_3, step_number == 2 ? HIGH : LOW);
    digitalWrite(STEPPER_PIN_4, step_number == 3 ? HIGH : LOW);
    Serial.println(digitalRead(STEPPER_PIN_1));
    Serial.println(digitalRead(STEPPER_PIN_2));
    Serial.println(digitalRead(STEPPER_PIN_3));
    Serial.println(digitalRead(STEPPER_PIN_4));
  step_number++;
  if(step_number > 3){
    step_number = 0;
  }
}

void loop() {
//  motor.close();
    if (builtin){
        digitalWrite(LED, HIGH);
    } else {
        digitalWrite(LED, LOW);
    }
    builtin = !builtin;
    delay(200);
}

The second one with an object oriented approach does NOT work

second Sketch:

#include "Arduino.h"

#include "Motor.h"

int LED = 2;
int step_number = 0;
const int STEPPER_PIN_1 = 23;
const int STEPPER_PIN_2 = 22;
const int STEPPER_PIN_3 = 33;
const int STEPPER_PIN_4 = 32;
boolean builtin = true;

Motor motor(STEPPER_PIN_1, STEPPER_PIN_2, STEPPER_PIN_3, STEPPER_PIN_4);

void setup() {
    Serial.begin(115200);
    pinMode(LED, OUTPUT);
}

void loop() {
    motor.close();
    if (builtin){
        digitalWrite(LED, HIGH);
    } else {
        digitalWrite(LED, LOW);
    }
    builtin = !builtin;
    delay(200);

    for(int i = 0; i < 500; i++){
        oneStep();
        delay(200);
    }
}

Motor.h:


#ifndef MOTOR_H_
#define MOTOR_H_

class Motor {
public:
    Motor(int STEPPER_PIN_1, int STEPPER_PIN_2, int STEPPER_PIN_3, int STEPPER_PIN_4);
    Motor(int STEPPER_PIN_1, int STEPPER_PIN_2, int STEPPER_PIN_3,
            int STEPPER_PIN_4, int delayTurnLeft, int delayTurnRight);
    virtual ~Motor();
    void close();
private:
    int delayTurnLeft;
    int delayTurnRight;
    int step_number_left;
    int step_number_right;
    int STEPPER_PIN_1;
    int STEPPER_PIN_2;
    int STEPPER_PIN_3;
    int STEPPER_PIN_4;
    void turnLeft(int numberOfSteps);
    void turnRight(int numberOfSteps);
    void oneStepForward();
    void oneStepBackward();
};

#endif /* MOTOR_H_ */

Motor.cpp:

#include "Arduino.h"
#include "Motor.h"

Motor::Motor(int STEPPER_PIN_1, int STEPPER_PIN_2, int STEPPER_PIN_3, int STEPPER_PIN_4) {
    Motor(STEPPER_PIN_1, STEPPER_PIN_2, STEPPER_PIN_3, STEPPER_PIN_4, 2, 2);
}

Motor::Motor(int STEPPER_PIN_1, int STEPPER_PIN_2, int STEPPER_PIN_3,
        int STEPPER_PIN_4, int delayTurnLeft, int delayTurnRight) {
    this->STEPPER_PIN_1 = STEPPER_PIN_1;
    this->STEPPER_PIN_2 = STEPPER_PIN_2;
    this->STEPPER_PIN_3 = STEPPER_PIN_3;
    this->STEPPER_PIN_4 = STEPPER_PIN_4;
    pinMode(STEPPER_PIN_1, OUTPUT);
    pinMode(STEPPER_PIN_2, OUTPUT);
    pinMode(STEPPER_PIN_3, OUTPUT);
    pinMode(STEPPER_PIN_4, OUTPUT);
    step_number_left = 0;
    step_number_right = 0;
    this->delayTurnLeft = delayTurnLeft;
    this->delayTurnRight = delayTurnRight;
}

Motor::~Motor() {
    // TODO Auto-generated destructor stub
}

void Motor::oneStepForward() {
    digitalWrite(STEPPER_PIN_1, step_number_right == 0 ? HIGH : LOW);
    digitalWrite(STEPPER_PIN_2, step_number_right == 1 ? HIGH : LOW);
    digitalWrite(STEPPER_PIN_3, step_number_right == 2 ? HIGH : LOW);
    digitalWrite(STEPPER_PIN_4, step_number_right == 3 ? HIGH : LOW);

    Serial.println(digitalRead(STEPPER_PIN_1));
    Serial.println(digitalRead(STEPPER_PIN_2));
    Serial.println(digitalRead(STEPPER_PIN_3));
    Serial.println(digitalRead(STEPPER_PIN_4));
    step_number_right++;
    if (step_number_right > 3) {
        step_number_right = 0;
    }
    Serial.println(5);
}

void Motor::oneStepBackward() {
    digitalWrite(STEPPER_PIN_1, step_number_left == 3 ? HIGH : LOW);
    digitalWrite(STEPPER_PIN_2, step_number_left == 2 ? HIGH : LOW);
    digitalWrite(STEPPER_PIN_3, step_number_left == 1 ? HIGH : LOW);
    digitalWrite(STEPPER_PIN_4, step_number_left == 0 ? HIGH : LOW);
    Serial.println(digitalRead(STEPPER_PIN_1));
    Serial.println(digitalRead(STEPPER_PIN_2));
    Serial.println(digitalRead(STEPPER_PIN_3));
    Serial.println(digitalRead(STEPPER_PIN_4));
    step_number_left++;
    if (step_number_left > 3) {
        step_number_left = 0;
    }
    Serial.println(6);
}

void Motor::turnLeft(int numberOfSteps){
    for (int i = 0; i < numberOfSteps; i++) {
        oneStepBackward();
        delay(2000);
    }
}
void Motor::turnRight(int numberOfSteps) {
    for (int i = 0; i < numberOfSteps; i++) {
        oneStepForward();
        delay(2000);
    }
}

void Motor::close(){
    turnRight(1024);
    turnLeft(1024);
}

the output of the first sketch is something like 1 0 0 0

0 1 0 0

and so on

output of the second sketch is 1 1 1 1

1 1 1 1

and so on But I don't understand what is wrong with this code. Any help is highly appreciated.

Thanks in advance.

Best regards Proton

stickbreaker commented 5 years ago

@Proton23 what circuit are you using to drive the stepper? That stepper 28BYJ-48 is specified by its data sheet to have 50ohm coils. You cannot directly drive it from an ESP32. When driving the 5v/50ohm coil you need at least 100mA of current.

Your digitalRead() confusion is reverse EMF. The motor is also a generator. When you step forward with one coil you induce a current in the other coils. To prove this to yourself. remove the stepper from the circuit. isolate/disconnect all wires from the stepper. Rotate the motor shaft, feel the opposing torque. Then connect all of motor wires together(short out the motor). Then rotate the motor shaft, you will have to use much more force to rotate the motor shaft.

You need to use some type of stepper motor driver circuit LM2003. On this Components101 webpage they describe how to use it. Halfway down the page in the section "How to use 28-BYJ48 Stepper Motor" they describe the pulse sequence needed to drive the motor. Your single coil at a time is not a valid sequence:

void Motor::oneStepForward() {
    digitalWrite(STEPPER_PIN_1, step_number_right == 0 ? HIGH : LOW);
    digitalWrite(STEPPER_PIN_2, step_number_right == 1 ? HIGH : LOW);
    digitalWrite(STEPPER_PIN_3, step_number_right == 2 ? HIGH : LOW);
    digitalWrite(STEPPER_PIN_4, step_number_right == 3 ? HIGH : LOW);

    Serial.println(digitalRead(STEPPER_PIN_1));
    Serial.println(digitalRead(STEPPER_PIN_2));
    Serial.println(digitalRead(STEPPER_PIN_3));
    Serial.println(digitalRead(STEPPER_PIN_4));
    step_number_right++;
    if (step_number_right > 3) {
        step_number_right = 0;
    }
    Serial.println(5);
}

That unipolar stepper needs an eight step sequence.

const uin8_t STEPSEQ[8] = {14,12,13,9,11,3,7,6}; // binary encode bit0 = stepper coil1. 0..7 for right, 7..0 for left.
uint8_t step_number = 0;
const uint8_t COILPINS[4] ={STEPPER_PIN_1,STEPPER_PIN_2,STEPPER_PIN_3,STEPPER_PIN_4];

void Motor::Step( uint8_t direction) { // 0==right, 1 == left
  if(direction == 0 ) {
   if(step_number > 6 ) step_number = 0;
   else step_number ++;
  }
  else {
    if (step_number ==0 ) step_number = 7;
    else step_number--;
  }

  for(uint8_t a = 0; a <4; a++){ // a = coil  pin
     digitalWrite(COILPINS[a], STEPSEQ[step_number]& (1<<a)); 
    /*
    '1<< a' does a binary left shift  of 1 'a' places, so:
     if a==0, 1<<0 = 1 or 0b0001
     if a==1, 1<<1 = 2 or 0b0010
     if a==2, 1<<2 = 4 or 0b0100
     if a==3, 1<<3 = 8 or 0b1000
    '&' anding with STEPSEQ[] produces a '1'(HIGH) if the matching bit is set for that coil/step combo.
  */
    }
}

Chuck.

Proton23 commented 5 years ago

Hello Chuck,

thanks for your quick reply. I forgot to mention that I use uln2003 as driver. My Setup contains also a photo sensor but not is not in use to determine the error. All tutorials about the 28BYJ-48 I found, used 4 steps and everything works fine when it's in the ino-file. I don't get why the sequence is invalid only when the code is in an object class.

Nevertheless I will give it a try this evening and let you know. Thank you very much.

Best regards Antonio

Proton23 commented 5 years ago

Hello Chuck, it work's but actually it rises more questions than it answers, at least for me. Is there a way to make as fast as with 4 steps?

{14,12,13,9,11,3,7,6}; // binary encode bit0 = stepper coil1. 0..7 for right, 7..0 for left.

I don't understand how you got those numbers

if(step_number > 6 ) step_number = 0;

Is the intentional or a typo? Is 7 not making more sense? If it's intentional, it's something I also don't get.

My main problem is

STEPSEQ[step_number]& (1<<a)

Let's say step_number is 2, then STEPSEQ[2] is 13 a == 0 -> STEPSEQ[step_number] & (1 << a) -> 1 a == 1 -> STEPSEQ[step_number] & (1 << a) -> 0 a == 2 -> STEPSEQ[step_number] & (1 << a) -> 4 a == 3 -> STEPSEQ[step_number] & (1 << a) -> 8

Let's say step_number is 1, then STEPSEQ[2] is 12 a == 0 -> STEPSEQ[step_number] & (1 << a) -> 0 a == 1 -> STEPSEQ[step_number] & (1 << a) -> 0 a == 2 -> STEPSEQ[step_number] & (1 << a) -> 4 a == 3 -> STEPSEQ[step_number] & (1 << a) -> 8

and I have no idea why.

Thanks in advance.

Best regards Antonio

stickbreaker commented 5 years ago

@Proton23

{14,12,13,9,11,3,7,6}; // binary encode bit0 = stepper coil1. 0..7 for right, 7..0 for left.

I don't understand how you got those numbers

from this webpage components101 28byj-48 about half way down the page is this table: stepperseq Red is the common line, the others are the poles. if you look at the column "Step 1" from top to bottom is the sequence 0 1 1 1. If convert that to a binary value, assign each row a value starting at 1, so 1,2,4,8. (0 1)+(1 2)+(1 4)+(1 8) = 14.

if(step_number > 6 ) step_number = 0;

Is the intentional or a typo? Is 7 not making more sense? If it's intentional, it's something I also don't get.

I want to limit step number to the range of 0..7. When I increment it I want 0 to follow 7, and I am kinda paranoid. If I use this code:

if (step_number ==7) step_number = 0;
else step_number++;

it will work fine, as long a step_number never gets set above 7. else bad things happen, I am using step_number as a index into an array of byte. The compiler assumes I know what I am doing. I defined the STEPSEQ[] as an array[0..7]. if I tell it to access byte 8, it will! The compiler does not do range checking, It will just return the byte at the requested location, whatever it is.

if( step_number > 6) step_number=0;
else step_number++'

will never let step_number get higher than 7.

main problem is

STEPSEQ[step_number]& (1<<a)

Let's say step_number is 2, then STEPSEQ[2] is 13 a == 0 -> STEPSEQ[step_number] & (1 << a) -> 1 a == 1 -> STEPSEQ[step_number] & (1 << a) -> 0 a == 2 -> STEPSEQ[step_number] & (1 << a) -> 4 a == 3 -> STEPSEQ[step_number] & (1 << a) -> 8

the calculation gives me either zero or non_zero, digitalWrite(pin,value) sets the output LOW if value is zero, else it sets output HIGH

The speed is controlled by the rate you call step(). If you call step() too often the motor will miss step.

I would design the motor subsystem to support move_to and move_rate.

I would use a timer() callback to step(). set the timer() to activate every n milliseconds. Inside the callback the logic is simple: timer example page

// untested / uncompiled.  use at your own risk :)

// volatile because `I am accessing/changing the value inside an interrupt.
volatile int currentPOS=0;
volatile int newPOS=0;
volatile int stepDirection=0;
volatile int step_number=0; 

hw_timer_t * timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;

void IRAM_ATTR onTimer() {
  portENTER_CRITICAL_ISR(&timerMux);
  int _newPos = newPos;
  int _currentPos = currentPos;
  int _stepDirection = stepDirection;
  portEXIT_CRITICAL_ISR(&timerMux);

  if( _newPos != _currentPos) {
     step(_stepDirection);
  portENTER_CRITICAL_ISR(&timerMux);
      if(currentPos == _currentPos) {  // hasn't been changed by another thread
        if(_stepDirection == 0) currentPos++;
        else currentPos--;
      }
  portEXIT_CRITICAL_ISR(&timerMux);
    }
  else { // at newPos, so shutoff timer.
    timerAlarmDisable(timer);
  }
}

void setup() {

  Serial.begin(115200);

  timer = timerBegin(0, 80, true);
  timerAttachInterrupt(timer, &onTimer, true);
  timerAlarmWrite(timer, 2000, true); // 500hz, every 2000 microSeconds
  timerAlarmEnable(timer);

}

void loop() {
  if(signal){ // whatever signal or button or .. you use to control when/where to step
    portENTER_CRITICAL(&timerMux);
    newPos = ?
    stepDirection =?
    if(newPos != currentPos){
       timerAlarmEnable(timer); // enable timer to do steps every 2000us until currentPos==newPos
    }
     portEXIT_CRITICAL(&timerMux);
 }

Chuck.

Proton23 commented 5 years ago

Hello Chuck,

after describing how you got the numbers everything started to make sense. Thank you so much for explaining that to me.

Because I don't like to use code I don't understand, so I will have to take a look into it before deciding if I want to use it. Will the 8-step way with timer be as fast as the 4-step way?

The only thing I still don't get is: Why is the 4-step way working in the ino file but not in the class?

Best regards Antonio

stickbreaker commented 5 years ago

@Proton23

Will the 8-step way with timer be as fast as the 4-step way?

Depends on how fast you call step(), the ESP32 functions at 240MHz, it can call the simple Step() function MILLIONS of times per second. The motor is a physical device, its datasheet lists its frequency as 100Hz (one hundred steps per second) 28BYJ-48 datasheet

The only thing I still don't get is: Why is the 4-step way working in the ino file but not in the class?

I don't know, but since I believe your activation sequence is invalid, I am not going to spend any time figuring out why it inconsistently works.

Chuck.

Proton23 commented 5 years ago

Hello Chuck, I took a look into the code, but it's seems like overkill to me and I don't know if I really got it figured out totally. So I'm going to stick to your first version, that should be enough for my purpose. I also set the delay from 2 to 1 and now it seems to be as fast as the 4-step way.

Thanks a lot for helping me out.

Best regards Antonio

stale[bot] commented 5 years ago

[STALE_SET] This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 14 days if no further activity occurs. Thank you for your contributions.

stale[bot] commented 4 years ago

[STALE_DEL] This stale issue has been automatically closed. Thank you for your contributions.