Establishing consistent encoder increments

Hello, I am trying to get my encoder to change with consistent 1 flash per minute increments, but currently, it is changing by changing values. Essentially it is a variable strobe light (stroboscope) with increasing flash rate as the encoder is turned. Currently, the led blinks and changes with the encoder, however the increment at what it increases with is not consistent. If someone could help me with establishing these increments, that would be very helpful. The code I am using is shown below:

#include <Wire.h>
#include <LCD.h>
#include <LiquidCrystal_I2C.h>
#include <Encoder.h>

Encoder myEnc(14,15);

LiquidCrystal_I2C lcd(0x27,2,1,0,4,5,6,7);

unsigned long startMillis; 
unsigned long currentMillis;
float fpm;
const byte ledPin = 13;
long ONTIME = 6;

void setup()
{
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);
  startMillis = millis();  //initial start time

  lcd.setBacklightPin(3,POSITIVE);
  lcd.setBacklight(HIGH);
  lcd.begin(16, 2);
  lcd.setCursor(0,0);
  lcd.print("RPM = ");
  lcd.setCursor(0,1);
  lcd.print("Temp (F) = ");
  lcd.setCursor(20,0);
  lcd.print("Bearing Life = ");
  myEnc.write(60000);
}

long oldPeriod = -999;
//long newPeriod = 0;

void loop()
{
  unsigned long period = myEnc.read();
  if (period != oldPeriod){
    oldPeriod = period;
    lcd.setCursor(6,0);
    fpm = 60.0*1000.0/period;
    lcd.print(fpm);
    lcd.print("     ");
  }
  currentMillis = millis();
  if (millis() - startMillis >= period && digitalRead(ledPin)== LOW)
  {
    digitalWrite(ledPin, HIGH); 
  }
  else if(millis() - startMillis >= period+ONTIME) //where ONTIME is your defined ON time
   {
     digitalWrite(ledPin, LOW); 
     startMillis = currentMillis;
   }
  Serial.println(fpm);
}

Your period comes from reading the encoder which is initialized at 60000 and will vary by +1 or -1 (in some edge cases by +2 or -2 with your library) so what do you think fpm variations are going to be if there is a +1 for example? fpm = 60.0*1000.0/period; Why don’t you have the encoder give you directly fpm and do the maths for timing from there?

How can you tell if it’s +1 -1, or +2 -2 increments from the library?

By reading the source code… whilst it’s programmed in assembly language the author put a c version to explain what the code does

//                           _______         _______       
//               Pin1 ______|       |_______|       |______ Pin1
// negative <---         _______         _______         __      --> positive
//               Pin2 __|       |_______|       |_______|   Pin2

		//	new	new	old	old
		//	pin2	pin1	pin2	pin1	Result
		//	----	----	----	----	------
		//	0	0	0	0	no movement
		//	0	0	0	1	+1
		//	0	0	1	0	-1
		//	0	0	1	1	+2  (assume pin1 edges only)
		//	0	1	0	0	-1
		//	0	1	0	1	no movement
		//	0	1	1	0	-2  (assume pin1 edges only)
		//	0	1	1	1	+1
		//	1	0	0	0	+1
		//	1	0	0	1	-2  (assume pin1 edges only)
		//	1	0	1	0	no movement
		//	1	0	1	1	-1
		//	1	1	0	0	+2  (assume pin1 edges only)
		//	1	1	0	1	-1
		//	1	1	1	0	+1
		//	1	1	1	1	no movement
/*
	// Simple, easy-to-read "documentation" version :-)
	//
	void update(void) {
		uint8_t s = state & 3;
		if (digitalRead(pin1)) s |= 4;
		if (digitalRead(pin2)) s |= 8;
		switch (s) {
			case 0: case 5: case 10: case 15:
				break;
			case 1: case 7: case 8: case 14:
				position++; break;
			case 2: case 4: case 11: case 13:
				position--; break;
			case 3: case 12:
				position += 2; break;
			default:
				position -= 2; break;
		}
		state = (s >> 2);
	}
*/

you're still struggling with this huh! :)

if you look at the source code for static void update(Encoder_internal_state_t *arg) - the C part - it tells you when it does that...

There is also a decent commenting about how that works in there as J-M-L mentioned i.e if you can get you head round it! :)

Recommendation

Look at the post at the top of the forum on how to use millis() and study also the built in example «blink without delay»

Let the encoder class do its work. If this is black magic to you, you can come back to this later. That's one of the benefit of encapsulation. You just need to know that you initialize it with a number that will evolve based on how the encoder is turned.

Initialize your instance with say 60 and not 60000. This number will be your fpm

Assume The number will evolve with +1 or -1 but if I turn the button super super fast and your loop is slow you might have missed a few ticks in the loop (but the interrupts will have got that for you) so in the loop the reading will be whatever it is, you can assume it's consistent with the user action. Keep the loop fast (ie no delay) so that behavior closely matches user action is a good principle

In the loop ask what the value is - if it had changed recalculate the half blinking period: if you know your fpm = flashes per minute and a flash is ON for x milliseconds and OFF for y milliseconds (x could be equal to y) then one full cycle is x+y. you have 60000 ms in a minute, so you have x+y = 60000 / fpm; if you choose to have x = y (same time ON and OFF) then you have 2*x = 60000 / fpm and so x = 60000 / (2*fpm)

Then use the blink without delay method to blink your light by using x as the duration between state changes.

if you want shorter flashes and longer dark period to have more of a stroboscopic effect - you can decide that y is way longer than x for example decide that y = 9*x

So in the formula above you get x + 9 * x = 60000 / fpm which leads to x = 60000 / (10 * fpm)

Then you need to be a bit smarter in the way you implement the blinking: the tests against millis() won't be for the same duration depending if your light is ON or OFF. one will be for x and the other for 9*x

I am trying to get my encoder to change with consistent 1 flash per minute increments

the problem is not exactly with your code but rather how the result is calculated/stored. you can try this to see what I mean in any online c compile (e.g https://www.jdoodle.com/c-online-compiler)

#include<stdio.h>

#define BASE_PERIOD 60000UL

int main() {
    for(int i=0; i<5000;++i){
        printf("step:%i, 1 pulse every %i t\n",i+1, BASE_PERIOD/(i+1));
    }
}

what you will see is that for some steps, especially as ‘t’ gets smaller, ‘t’ remains the same because we are outputing an INTEGER which is the value used by millis(). one way to improve the resolution is to instead use micros() [change BASE_PERIOD to 60000000UL for that].

Also as I said to you before, millis() does not have a 1ms resolution.

If you really want to get accurate flashes per minute with every encoder step, consider using the timers on your board.

sherzaad:
I am trying to get my encoder to change with consistent 1 flash per minute increments

the problem is not exactly with your code but rather how the result is calculated/stored. you can try this to see what I mean in any online c compile (e.g https://www.jdoodle.com/c-online-compiler)

#include<stdio.h>

#define BASE_PERIOD 60000UL

int main() {
    for(int i=0; i<5000;++i){
        printf(“step:%i, 1 pulse every %i t\n”,i+1, BASE_PERIOD/(i+1));
    }
}




what you will see is that for some steps, especially as 't' gets smaller, 't' remains the same because we are outputing an INTEGER which is the value used by millis(). one way to improve the resolution is to instead use micros() [change BASE_PERIOD to 60000000UL for that].

Also as I said to you before, millis() does not have a 1ms resolution.

If you really want to get accurate flashes per minute with every encoder step, consider using the timers on your board.

what you say is technically correct but it’s not OP’s problem.

OP said he/she wants the encoder to change with consistent 1 flash per minute (fpm) increments. So trying to drive a time from the encoder which is doing +1 or -1 is never going to result in +1 or -1 fpm changes.

The correct approach is just to have the encoder be the fpm and compute the timing based on this.

On this comment

Also as I said to you before, millis() does not have a 1ms resolution.
If you really want to get accurate flashes per minute with every encoder step, consider using the timers on your board.

again it’s technically true that depending on what you do you can skip a ms it was my understanding that OP was looking at a pretty low (a few tens) strobe per minutes… flashing at 60fpm is once a second (1000ms cycle)… and flashing at 300 fpm is 5 times a second which is still a 200ms cycle. Even with a 16MHz basic arduino It’s pretty safe to be accurate in the timing at 99,99%.

J-M-L: Recommendation

Look at the post at the top of the forum on how to use millis() and study also the built in example «blink without delay»

Let the encoder class do its work. If this is black magic to you, you can come back to this later. That's one of the benefit of encapsulation. You just need to know that you initialize it with a number that will evolve based on how the encoder is turned.

Initialize your instance with say 60 and not 60000. This number will be your fpm

I have followed the millis() and blink without delay examples already and have the led blinking to the number of flashes per minute somewhat accurately. The only issue is trying to get the increments to change in intervals of 1 or just consistent intervals. I tried to do some quick math on how some of the fpm values change but it's not consistent and I couldn't solve how.

For example, if I say myEnc.write(60), the fpm starts out at 1000fpm and 1 click over is 1071.43 fpm and 2 clicks over is 1153.85fpm. I tried calculating with "period+ONTIME" rather than period and I still was puzzled at how these numbers come about. I was able to verify that it does what I want when switching it to myEnc.write(60000) because it actually flashes 1 time for 6ms, but the intervals is what is giving me trouble.

I know this is a lot to understand, please let me know if I can explain anything clearer.

predzZzZzZ: For example, if I say myEnc.write(60), the fpm starts out at 1000fpm and ..

NO - if your encoder starts at 60, the encoder value IS the fpm. fpm = 60. that's it.

Then you derive the timing from the fpm

if you turn the knob, encoder will tell you fpm is 59 or 61 and you recalculate timing accordingly.

predzZzZzZ: For example, if I say myEnc.write(60), the fpm starts out at 1000fpm and 1 click over is 1071.43 fpm and 2 clicks over is 1153.85fpm.

That is actually an interesting result. With the initial position set to 60, 1 click it goes to 56 (60000/1071.43) and 2 clicks the position goes to 52 (60000/1153.85)!

did you check if your encoder is outputing a clean waveform with every click. if there is some debouncing occuring with every click, that may explain why you are getting varying intervals....

J-M-L: NO - if your encoder starts at 60, the encoder value IS the fpm. fpm = 60. that's it.

Then you derive the timing from the fpm

if you turn the knob, encoder will tell you fpm is 59 or 61 and you recalculate timing accordingly.

With everything being in millis() the time the led is off is what "myEnc.write()" is starting out at. So starting out at 60000 ms gives me a rate of 1 flash per minute and so on. It was decided that a fixed on time would be implemented and the encoder will adjust the time off. I agree that I would like it to be that way based on your words, but with my lack of knowledge of coding, it's a lot easier said than done.

sherzaad: That is actually an interesting result. With the initial position set to 60, 1 click it goes to 56 (60000/1071.43) and 2 clicks the position goes to 52 (60000/1153.85)!

did you check if your encoder is outputing a clean waveform with every click. if there is some debouncing occuring with every click, that may explain why you are getting varying intervals....

You know it's funny you mention that because I was running into that issue earlier, but I thought my adjusted code would fix that.

I agree that I would like it to be that way based on your words, but with my lack of knowledge of coding, it's a lot easier said than done.

@J-M-L has given you just about everything you need. Give a try at coding his suggested approach with the encoder determining the flashes per minute, and the timing math worked out from that setting. We will help you get it right.

What is the onTime? Is it always 6 milliseconds?

offTime = (60000/fpm) - onTime

So, if you wanted 60 flashes per minute at 6ms on time then your setup for blink without delay will have on time of 6ms and an off time of 994. You can certainly use micros() for more precision.

const byte ledPin = 13;
boolean ledState = LOW;
int fpm = 60;
int onTime = 6;
int offTime = (60000/fpm) - onTime;
unsigned long lastSwitchTime = 0;


void setup() {

 pinMode(13, OUTPUT);

}

void loop() {

  //fpm = encoder reading
  //if fpm is changed from old fpm recaluclate offTime

  unsigned long currentTime = millis();

  // if the led is ON and has been for more than onTime
  if((ledState == HIGH) && (currentTime - lastSwitchTime >= onTime)){
    //turn it off
    ledState = LOW;
    lastSwitchTime = currentTime;    
  }
  // if the led is off and has been for more than offTime
  if((ledState == LOW) && (currentTime - lastSwitchTime >= offTime)){
    ledState = HIGH;
    lastSwitchTime = currentTime;
  }
  // write the ledState to the led
  digitalWrite(ledPin, ledState);
}

cattledog: @J-M-L has given you just about everything you need. Give a try at coding his suggested approach with the encoder determining the flashes per minute, and the timing math worked out from that setting. We will help you get it right.

What is the onTime? Is it always 6 milliseconds?

offTime = (60000/fpm) - onTime

So, if you wanted 60 flashes per minute at 6ms on time then your setup for blink without delay will have on time of 6ms and an off time of 994. You can certainly use micros() for more precision.

onTime is always going to be at 6ms because that is the max time it is required to be on for 5000 fpm. I'm now following what you're saying now I will test my updated code when I get home. So because I'm changing everything in terms of minutes does that now set myEnc.write() in terms of minutes as well? I'm trying to get it to start at zero and increase blink speeds as you turn the knob so I'm worried on how this changes the code thus far. Thank you for all of your help @cattledog & @J-M-L.