Pin2->EncoderA
Pin3->EncoderB
Pin4->Switch
GND->Obviously the two commons
I wanted in the monitor a "Radio Frequency", that I can tune in this way:
Turning the shaft -> tune the kHz (decimals, 0.025 step)
Turning the shaft KEEPING THE BUTTON PRESSED -> tune the MHz (1 MHz step)
So I came to this code.
int encoderPin1 = 2;
int encoderPin2 = 3;
int encoderSW = 4;
volatile int lastEncoded = 0;
volatile long encoderValue = 0;
long tmp=0;
long lastencoderValue = 0;
double encoderValue1=118;
void setup() {
Serial.begin (9600);
pinMode(encoderPin1, INPUT_PULLUP);
pinMode(encoderPin2, INPUT_PULLUP);
pinMode(encoderSW, INPUT_PULLUP);
attachInterrupt(0, updateEncoder, CHANGE);
Serial.println("Selezione Frequenza...");
}
void loop(){
if (tmp != encoderValue) {
if (encoderValue ==2){
if (encoderValue1<136.975){
encoderValue1 +=0.025;
Serial.println(encoderValue1);
}
encoderValue=0;
}
else if (encoderValue ==2 && !digitalRead(encoderSW) == HIGH){
if (encoderValue1<136.975){
encoderValue1 +=1;
Serial.println(encoderValue1);
}
encoderValue=0;
}
if (encoderValue ==-2 ){
if (encoderValue1>118){
encoderValue1-=0.025;
Serial.println(encoderValue1);
}
encoderValue=0;
}
else if (encoderValue ==-2 && !digitalRead(encoderSW) == HIGH){
if (encoderValue1>118){
encoderValue1-=1;
Serial.println(encoderValue1);
}
encoderValue=0;
}
tmp = encoderValue;
}
}
void updateEncoder(){
int MSB = digitalRead(encoderPin1); //MSB = most significant bit
int LSB = digitalRead(encoderPin2); //LSB = least significant bit
int encoded = (MSB << 1) |LSB; //converting the 2 pin value to single number
int sum = (lastEncoded << 2) | encoded; //adding it to the previous encoded value
if(sum == 0b1001 || sum == 0b0110) encoderValue ++;
if(sum == 0b0011 || sum == 0b1100) encoderValue --;
lastEncoded = encoded; //store this value for next time
}
But i can only tune the decimals.
WHERE AM I WRONG?
Any kind of help will be appreciated, especially from someone who explains me not only where, but why I was wrong.
You're checking the switch for ± 1 but not for ± .25. Fractions are *always * inc/decremented and the [color=blue]else if[/color] bypasses the integer adjust.
void updateEncoder()
{
int MODE = digitalRead(encoderSW );
int MSB = digitalRead(encoderPin1); //MSB = most significant bit
int LSB = digitalRead(encoderPin2); //LSB = least significant bit
if (MODE == HIGH)
{
int encoded = (MSB << 1) |LSB; //converting the 2 pin value to single number
int sum = (lastEncoded << 2) | encoded; //adding it to the previous encoded value
if(sum == 0b1001 || sum == 0b0110) encoderValue ++;
if(sum == 0b0011 || sum == 0b1100) encoderValue --;
lastEncoded = encoded; //store this value for next time
}
else
{
int encoded = (MSB << 1) |LSB; //converting the 2 pin value to single number
int sum = (lastEncodedTWO << 2) | encoded; //adding it to the previous encoded value
if(sum == 0b1001 || sum == 0b0110) encoderValueTWO ++;
if(sum == 0b0011 || sum == 0b1100) encoderValueTWO --;
lastEncodedTWO = encoded; //store this value for next time
}
}
I'm banging my head to the wall.
I tried modifying the "void updateEncoder" section, obviously declaring the new variables, but I still have no result.
I know this is annoying, and I'm so sorry for that, but I really need someone who takes my hand through this sketch, even reworking it from the beginning if this will make it more understandable to me.
robtillaart:
Should the rotary encoder trigger on CHANGE?
If you only trigger on FALLING the code in the ISR() can be much simpler.
It was my understanding that triggering CHANGE I can use only one interrupt pin, am I wrong?
The core is 2 counters, one for KHz steps and one for MHz steps since last readout.
These steps are updated in the interrupt routine, depending on the keypress or not.
LOOP
a copy of the counters is made and the counters are reset to zero.
So these copies hold the delta since last readout.
We add this delta to a global frequency variable which hold the frequency in KHz.
Note this is a long and therefor exact.
Because not all frequencies are allowed, the KHz is constrained between a minimum and maximum.
Then we calculate the value in MHz and the frequency is printed.
We wait a short while and exit (and LOOP will be called again)
Please go through the code and check if you recognize the above story in the code.
This should get you started to do something with the frequency (radio tuner?)
The code compiles, but is not tested as I did not have appropriate hardware nearby.
//
// FILE: encoderWithSwitch.ino
// AUTHOR: Rob Tillaart
// PURPOSE: demo
// DATE: 2018-02-04
//
//////////////////////////////////////////////////////////////////////////////
//
// CONSTANTS
//
#define VERSION "0.1.0"
const uint32_t baudrate = 115200;
const int encoderPin1 = 2; // keep pin 3 free as it is also a HW interrupt.
const int encoderPin2 = 4;
const int encoderPush = 5;
// INTERRUPT VARS
volatile long counter1 = 0;
volatile long counter2 = 0;
// FREQUENCY VARS
long KHz = 0; // can handle up to ~2 THz
long minKHz = 500; // TODO adjust
long maxKHz = 140000; // TODO ADJUST
void setup()
{
Serial.begin(baudrate);
Serial.println(__FILE__); // prints the sketch name
Serial.println(VERSION);
pinMode(encoderPin1, INPUT_PULLUP);
pinMode(encoderPin2, INPUT_PULLUP);
pinMode(encoderPush, INPUT_PULLUP);
attachInterrupt(0, updateCounters, FALLING);
Serial.println("Selezione Frequenza...");
}
void loop()
{
noInterrupts(); // DISABLE INTERRUPT to make local copy of the counters
long khzSteps = counter1; // !! can be negative
counter1 = 0;
long mhzSteps = counter2;
counter2 = 0;
interrupts(); // ENABLE INTERRUPT
// CALC FREQ in KHz
KHz = KHz + (khzSteps * 25) + (mhzSteps * 1000);
// do not exceed limits.
KHz = constrain(KHz, minKHz, maxKHz);
double MHz = KHz / 1000.0;
// DEBUG OUTPUT
// Serial.print("STEPS:\t");
// Serial.print(mhzSteps);
// Serial.print("\t");
// Serial.println(khzSteps);
// DISPLAY FREQ
Serial.print("FREQ KHz:\t");
Serial.println(KHz);
Serial.print("FREQ Mhz:\t");
Serial.println(MHz, 3);
delay(100); // TODO adjust or replace with other code
}
void updateCounters()
{
uint8_t pin1 = digitalRead(encoderPin1);
uint8_t pin2 = digitalRead(encoderPin2);
uint8_t push = digitalRead(encoderPush);
if (push == HIGH)
{
if (pin1 == pin2) counter1++;
else counter1--;
}
else
{
if (pin1 == pin2) counter2++;
else counter2--;
}
}
I really didn't expect you making this for me, I really appreciate it.
It's working flawlessly.
I see what you did there, and now I understand why my sketch did not work.
I can't thank you enough for your help and your explanation, it was difficult to me (there's no need to go full in detail but let's say that my mental capabilities are under the average ) but again thanks to your patience now I can implement what I learned from you today in my future projects.
Welcome, I had a quite similar sketch written recently in a project so I mostly stripped that one.
Note that the proposed sketch only works if you do not need the CHANGE perse. If you need to capture every change of the rotary the interrupt routine needs to keep last state. A bit like in your last sketch. Another solution is often to have a rotary encoder that just gives more pulses per rotation.
Writing software can be easier when you divide the problems in parts. E.g. write the loop by calling (not yet existing) functions that solve part of the problem. Then write those functions.
Furthermore by using variable names that define what they represent helps to keep the semantics clear.
Hello ROB ,
Ik ben ook met dergelijk project bezig , heb veel bijgeleerd met Uw scetch !!!
Ik zou de frequentie nu op een I2C display wille krijgen , kunt U mij daarmee helpen ?
Ik ben nog maar een beginnende Arduino leerling !!
Vele groetjes ,
Roland