Hi all,
I'm just creating a project for a yarn winder based on this one:
I have added a rotary encoder E38S6G5 with a 80mm aluminium V pulley to be able to measure the yarn length more detailed. For that purpose I completely redesigned the 3D print to include a housing for the E38S6G5 (I call it "measurement encoder" for easier reading..
)
To get input from the KY-040 encoder I created a RotaryEncoder class with header:
#ifndef RotaryEncoder_h
#define RotaryEncoder_h
#define R_START 0x0
#define R_CW_FINAL 0x1
#define R_CW_BEGIN 0x2
#define R_CW_NEXT 0x3
#define R_CCW_BEGIN 0x4
#define R_CCW_FINAL 0x5
#define R_CCW_NEXT 0x6
#define DIR_NONE 0x0
#define DIR_CW 0x10
#define DIR_CCW 0x20
#define Mode_Step 0
#define Mode_Length 1
#define Mode_Run 2
#define Mode_Ready 3
#include "Arduino.h"
class RotaryEncoder
{
public:
RotaryEncoder(int pinDT
,int pinCLK
,int pinSW
,int minCounter
,int maxCounter
,unsigned long waitTimeLongPressed
);
void ChangeCounter(int &Counter, int &inMode); // change the variables of the calling procedure;
bool SwitchPressed;
bool SwitchDown;
bool SwitchUp;
bool HasCounterChanged;
bool WasLongPressed;
int Direction; // -1 CounterClockwise, 1 Clockwise
unsigned long WaitTimeLongPressed;
unsigned long TimeSwitchDown;
private:
//static const unsigned char ttable[7][4];
int prv_pinDT;
int prv_pinCLK;
int prv_pinSW;
int prv_minCounter;
int prv_maxCounter;
int prv_pinState;
byte prv_lastSwitchState;
unsigned long prv_lastPressTime;
bool prv_singlePressDetected;
};
#endif
and code:
#include "Arduino.h"
#include "RotaryEncoder.h"
// Initialize the static constant table
static const unsigned char ttable[7][4] =
{
// R_START
{R_START, R_CW_BEGIN, R_CCW_BEGIN, R_START},
// R_CW_FINAL
{R_CW_NEXT, R_START, R_CW_FINAL, R_START | DIR_CW},
// R_CW_BEGIN
{R_CW_NEXT, R_CW_BEGIN, R_START, R_START},
// R_CW_NEXT
{R_CW_NEXT, R_CW_BEGIN, R_CW_FINAL, R_START},
// R_CCW_BEGIN
{R_CCW_NEXT, R_START, R_CCW_BEGIN, R_START},
// R_CCW_FINAL
{R_CCW_NEXT, R_CCW_FINAL, R_START, R_START | DIR_CCW},
// R_CCW_NEXT
{R_CCW_NEXT, R_CCW_FINAL, R_CCW_BEGIN, R_START},
};
RotaryEncoder::RotaryEncoder(int pinDT
,int pinCLK
,int pinSW
,int minCounter
,int maxCounter
,unsigned long waitTimeLongPressed
)
: SwitchPressed(false) // all after ":" initializes the class members with the parameter values
,SwitchDown(false)
,SwitchUp(false)
,HasCounterChanged(false)
,WasLongPressed(false)
,WaitTimeLongPressed(waitTimeLongPressed)
,prv_pinDT(pinDT)
,prv_pinCLK(pinCLK)
,prv_pinSW(pinSW)
,prv_minCounter(minCounter)
,prv_maxCounter(maxCounter)
,prv_pinState(R_START)
,prv_lastSwitchState(HIGH)
,prv_lastPressTime(0)
,prv_singlePressDetected(false)
{
pinMode(prv_pinDT, INPUT_PULLUP);
pinMode(prv_pinCLK, INPUT_PULLUP);
pinMode(prv_pinSW, INPUT_PULLUP);
this->TimeSwitchDown = 0;
//Serial.println("Initialized");
}
void RotaryEncoder::ChangeCounter(int &Counter, int &inMode) // change the variables of the calling procedure;
{
// Grab state of input pins.
unsigned char pinState = (digitalRead(prv_pinCLK) << 1) | digitalRead(prv_pinDT);
// Determine new state from the pins and state table.
prv_pinState = ttable[prv_pinState & 0xf][pinState];
// Clockwise rotation.
if(prv_pinState == DIR_CW)
{
this->Direction = 1;
if(Counter < prv_maxCounter)
{
Counter++;
if(Counter > prv_maxCounter) Counter = prv_maxCounter;
this->HasCounterChanged = true;
}
}
// Counter clockwise rotation.
else if(prv_pinState == DIR_CCW)
{
this->Direction = -1;
if(Counter > prv_minCounter)
{
Counter--;
if(Counter < prv_minCounter) Counter = prv_minCounter;
this->HasCounterChanged = true;
}
}
// Read the button state
byte currentSwitchState = digitalRead(prv_pinSW);
if (currentSwitchState == prv_lastSwitchState)
{
if (prv_lastPressTime > 0 && currentSwitchState == LOW)
{
unsigned long currentTime = millis();
this->TimeSwitchDown = currentTime - prv_lastPressTime;
//Serial.print(currentTime); Serial.print("\t"); Serial.println(this->TimeSwitchDown);
if (this->TimeSwitchDown > this->WaitTimeLongPressed)
{
this->WasLongPressed = true;
}
}
}
else
{
if(currentSwitchState == LOW)
{
// Button is pressed
//Serial.println("Button pressed!");
this->SwitchPressed = false; // get true when released
this->SwitchDown = true;
this->SwitchUp = false;
if (prv_lastPressTime == 0)
{
prv_lastPressTime = millis();
}
}
else
{
// Button is released
//Serial.print("Button released! \t");
this->SwitchPressed = true;
this->SwitchDown = false;
this->SwitchUp = true;
this->TimeSwitchDown = 0;
this->WasLongPressed = false;
prv_lastPressTime = 0;
switch (inMode)
{
case Mode_Step:
{
inMode = Mode_Length;
break;
}
case Mode_Length:
{
inMode = Mode_Step;
break;
}
}
}
prv_lastSwitchState = currentSwitchState;
}
}
(parts of the code are from here:
KY-040 rotary encoder skipping steps - Arduino Stack Exchange)
When I instantiate the class in the main code with:
RotaryEncoder encoderStep (pinKY40_DT, pinKY40_CLK, pinKY40_SW, 0, MAXSTEPVALUES - 1, WaitTime);
RotaryEncoder encoderLength(pinKY40_DT, pinKY40_CLK, pinKY40_SW, 1, 10 , WaitTime);
RotaryEncoder encoderSpeed (pinKY40_DT, pinKY40_CLK, pinKY40_SW, 0, 127 , WaitTime);
it works perfectly in the Loop procedure. I can use the "Step" part to enter a step value, switch with the button to "Length" mode and enter the yarn length. Next click switches back to Step and so on.
The class also gives information back when I press the switch for a longer time, which displays a progress bar in the display. If the desired time is reached, the loop switches to Run mode, the button is then only used to set the motor speed to 0, the KY-040 is used to change the speed of the motor.
Everything great until here.
Now the measurement encoder comes. I've added two interrupts in the setup:
pinMode(pinENCODER_PIN_A, INPUT_PULLUP);
pinMode(pinENCODER_PIN_B, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(pinENCODER_PIN_A), readEncoder1, RISING);
attachInterrupt(digitalPinToInterrupt(pinENCODER_PIN_B), readEncoder2, RISING);
with the interrupt procedures:
void readEncoder1()
{
if(inMode == Mode_Run)
{
// readEncoder1 is activated if DigitalPin nr 2 is going from LOW to HIGH
// Check pin 3 to determine the direction
if(digitalRead(pinENCODER_PIN_B)==LOW)
{
yarnLengthMM += LENGTH_PER_PULSE_MM;
}
else
{
yarnLengthMM -= LENGTH_PER_PULSE_MM;
}
}
}
void readEncoder2()
{
if(inMode == Mode_Run)
{
// readEncoder2 is activated if DigitalPin nr 3 is going from LOW to HIGH
// Check pin 2 to determine the direction
if(digitalRead(pinENCODER_PIN_A)==LOW)
{
yarnLengthMM -= LENGTH_PER_PULSE_MM;
}
else
{
yarnLengthMM += LENGTH_PER_PULSE_MM;
}
}
}
In the loop procedure I can now turn the disc of the measurement encoder and it does what it should do: The measured length is displayed. Everything is good. But: When I want to change the motor speed with the KY-040 it turns out that the interrupts (600 pulses per 360°, so a lot more calls if the wheel turns faster) being called so often that the loop doesn't handle the KY-040 anymore so that I cannot (or as good as not) change the motor speed with the KY-040. When I stop the wheel of the measurement encoder to zero, I can handle the KY-040. But that would be no option as the motor is pulling the yarn over the wheel and would still do if I hold the wheel so a lot of measurement would be lost.
I've tried a lot of different possibilities like trying to read the KY-040 in the ISR or deactivating the interrupt and reading the measurement encoder in the loop (which is far too slow) or creating a state machine to switch between reading the measurement encoder and reading the KY-040 and also displaying something on the display.
What I found out so far: When the wheel is turned manually (interrupts happens) it display the progress as value and as progress bar without any delay. That means, the interrupt doesn't disturb the calculation of the values or displaying the values which is done in the loop in run mode. Means to me, the loop is fast enough to do all this. But it just don't handle the KY-040 with the "ChangeCounter" anymore until I stop the wheel. If I try to add the ChangeCounter call into the ISR (which is of course too slow for an ISR) the counter doesn't change at all (would of course only while I turning the wheel).
So I can only guess that reading the interrupts stops the Arduino (a Nano) from reading the pins for the KY-040 - if the loop were simply too slow it should also not handle the display anymore. Of course that cannot be seen directly as the display holds what it displays also if nothing is changed. But the values of the measurement encoder are displayed without delay so what could be the reason that the values of the KY-040 doesn't reach the code anymore?
It has also nothing to do with the BLDC motor, when I disable the code lines that change the motor speed the behaviour is the same.
I have also tried adding a TimerOne library with a Timer interrupt to handle the KY-040 which also didn't work.
I read somewhere that someone mentioned a user interrupt assignable to other pins than 2/3 which are reserved for the measurement encoder. It would be great to have the possibility to have an additional interrupt for the KY-040 so that both rotary encoders can be handled. It would mean that when the KY-040 is turned, maybe some interrupts of the measurement encoder would be lost, but I could live with that.
I cannot use another Arduino as I do not have any other and it is no room in the device to add a bigger one or one with another form factor. I can also rather not add any additional hardware into it.
I'm a bit lost now. Would be great if anyone has any idea.
Cheers,
Christian