Hey guys, I'm trying to drive a 5v voltage meter via a rotary encoder through an arduino Uno R3.
I have a pretty simple sketch, and the rotary encoder works great, counting between 0 and 100 in increments of 1, with little bounce unless I spin it SUPER fast. Any normal movements work as expected.
But when I enable the two output lines to the Volt Meter, it starts bouncing like crazy. It becomes difficult to even advance in one direction or the other.
I'm pretty new to this, so I don't care if my code is clean or perfect, but I can't figure out why it starts bouncing by running an analog write. . .
Lil help?
#define meterPin 5 //PWM on digital pin 5
// Rotary Encoder pins
#define encoder0PinA 2
#define encoder0PinB 4
#define encoder0PinC 3
volatile int encoder0Pos = 50;
int meterOutput = 127;
void setup() {
pinMode(encoder0PinA, INPUT); //Initiate pin as Input
pinMode(encoder0PinB, INPUT); //Initiate pin as Input
pinMode(encoder0PinC, INPUT); //Initiate pin as Input
digitalWrite(encoder0PinA, HIGH); // turn on pull-up resistor
digitalWrite(encoder0PinB, HIGH); // turn on pull-up resistor
digitalWrite(encoder0PinC, HIGH); // turn on pull-up resistor
attachInterrupt(digitalPinToInterrupt(encoder0PinA), doEncoder, RISING); // encoder pin on interrupt 0 - pin 2
attachInterrupt(digitalPinToInterrupt(encoder0PinC), resetEncoder, FALLING); // encoder pin on interrupt 1 - pin 3
Serial.begin (9600); // Start the serial monitor
Serial.println("start"); // setup is done
}
void loop(){
}
void resetEncoder() {
encoder0Pos = 50;
Serial.println("Reset:");
Serial.println(encoder0Pos);
}
void doEncoder() {
if (digitalRead(encoder0PinA) == HIGH && digitalRead(encoder0PinB) == LOW) { //If encoder is turning clockwise . . .
if (encoder0Pos < 100){ //Keep it in range
encoder0Pos++; //Add 1
}
} else {
if (encoder0Pos > 0){ //Keep it in range
encoder0Pos--; //Subtract 1
}
}
Serial.println (encoder0Pos);
// meterOutput = map(encoder0Pos, 0, 100, 0, 255); // Remap encoder number to 0-255 for Volt Meter
// analogWrite(meterPin, meterOutput); //Output voltage to the meter
}
Edit: Ok, I've read up on ISRs, and I take it that my doEncoder() is the ISR, yes? (I had only understood it as an Interrupt until now)
If Interrupts (like Serial print) are disabled in ISRs, why does it work fine with the code included above? The Serial Print is there in both the working version and the non-working version.
What I have changed in the past is the last two lines, which write out to the volt meter.
However, even with those two lines commented out, if I simply set the volt meter to something in the initial setup I get the same bouncing.
Edit2: I've moved the offending lines, including the print, out to their own function, which gets called from the ISR, which research tells me is ok. But I still see the same bounce behavior.
What is the Volt Meter? When you say "it is bouncing", do you mean the reading on the Volt Meter?
The analog output us not really analog (0 to 5V) or a steady analog voltage. The analog output is a digital pulse train with varying duty cycle (0 or 5V). This type of signal is called Pulse Width Modulation or PWM. To get a steady(ish) analog voltage, the PWM signal can be filtered with a capacitor and resistor (low pass filter).
Encoder0Pos starts at 50.
With all of the volt meter stuff disabled, when I move the rotary encoder one notch to the right, it prints 51 in the serial monitor. Another click, and I get 52. If I move it one click to the left, I'm back to 51.
This is what I expect.
But when I enable the volt meter, one click to the right prints 49, 50, 49.
Another click and it might be something like, 50, 51. Click again to the right and I'm back to 49.
If I spin it quickly, it is likely to move further, but sometimes in the wrong direction.
I can get actual lists of outputs from the serial monitor when I get home tonight.
For its part, the volt meter, when it's active, is steady, and moves accordingly with the changes in the encoder number.
see playground.arduino.cc/Main/RotaryEncoders
for some sample code using interrupts with tighter ISRs.
below I've recoded your's slightly to put more of the data manipulation into the 'loop'
#define meterPin 5 //PWM on digital pin 5
// Rotary Encoder pins
#define encoder0PinA 2
#define encoder0PinB 4
#define encoder0PinC 3
volatile int encoder0Pos = 50;
volatile int DoReset = 0;
volatile int movement = 0;
int meterOutput = 127;
void setup() {
pinMode(encoder0PinA, INPUT); //Initiate pin as Input
pinMode(encoder0PinB, INPUT); //Initiate pin as Input
pinMode(encoder0PinC, INPUT); //Initiate pin as Input
digitalWrite(encoder0PinA, HIGH); // turn on pull-up resistor
digitalWrite(encoder0PinB, HIGH); // turn on pull-up resistor
digitalWrite(encoder0PinC, HIGH); // turn on pull-up resistor
attachInterrupt(digitalPinToInterrupt(encoder0PinA), doEncoder, RISING); // encoder pin on interrupt 0 - pin 2
attachInterrupt(digitalPinToInterrupt(encoder0PinC), resetEncoder, FALLING); // encoder pin on interrupt 1 - pin 3
Serial.begin (9600); // Start the serial monitor
Serial.println("start"); // setup is done
}
void loop() {
if (DoReset == 1) {
DoReset = 0;
encoder0Pos = 50;
Serial.println("Reset:");
Serial.println(encoder0Pos);
}
if (movement == 1) {
movement = 0;
if (encoder0Pos > 100) encoder0Pos = 100; //Keep it in range
if (encoder0Pos < 0) encoder0Pos = 0; //Keep it in range
Serial.println (encoder0Pos);
// meterOutput = map(encoder0Pos, 0, 100, 0, 255); // Remap encoder number to 0-255 for Volt Meter
// analogWrite(meterPin, meterOutput); //Output voltage to the meter
}
}
void resetEncoder() {
DoReset = 1;
}
void doEncoder() {
movement = 1;
if (digitalRead(encoder0PinA) == HIGH && digitalRead(encoder0PinB) == LOW) { //If encoder is turning clockwise . . .
encoder0Pos++; //Add 1
} else {
encoder0Pos--; //Subtract 1
}
}
// this gets accessed on rising edge of encoder0PinA
movement = 1;
// does checking the current state of encoder0PinA here in span of few nS /uS really debounce the input?
if (digitalRead(encoder0PinA) == HIGH && digitalRead(encoder0PinB) == LOW) {
//If encoder is turning clockwise . . . does this still detects the rotation direction?
encoder0Pos++; //Add 1 - (minor) not really good descriptive comment - just duplicate of code
} else {
encoder0Pos--; //Subtract 1
}
}
Thank you for the code update, Marshaj847. That makes sense now that I see it.
However, I'm still getting the same result when I just initialize the meter in the setup.
Here's the current code, adopting your suggestions, though I typed them rather than copy pasta, just so I could understand what was going on better.
#define meterPin 5 //PWM on digital pin 5
// Rotary Encoder pins
#define encoder0PinA 2
#define encoder0PinB 4
#define encoder0PinC 3
volatile int encoder0Pos = 50;
volatile int encoder0Reset = 0;
volatile int encoder0Movement = 0;
int meterOutput = 127;
void setup() {
pinMode(encoder0PinA, INPUT); //Initiate pin as Input
pinMode(encoder0PinB, INPUT); //Initiate pin as Input
pinMode(encoder0PinC, INPUT); //Initiate pin as Input
digitalWrite(encoder0PinA, HIGH); // turn on pull-up resistor
digitalWrite(encoder0PinB, HIGH); // turn on pull-up resistor
digitalWrite(encoder0PinC, HIGH); // turn on pull-up resistor
attachInterrupt(digitalPinToInterrupt(encoder0PinA), doEncoder, RISING); // encoder pin on interrupt 0 - pin 2
attachInterrupt(digitalPinToInterrupt(encoder0PinC), resetEncoder, FALLING); // encoder pin on interrupt 1 - pin 3
// analogWrite(meterPin, meterOutput);
Serial.begin (9600); // Start the serial monitor
Serial.println("start"); // setup is done
}
void loop(){
if (encoder0Reset == 1) {
encoder0Reset = 0;
encoder0Pos = 50;
Serial.println("Reset:");
Serial.println(encoder0Pos);
}
if (encoder0Movement == 1) {
encoder0Movement = 0;
if (encoder0Pos > 100) {
encoder0Pos = 100; //Keep it in range
}
if (encoder0Pos < 0) {
encoder0Pos = 0; //Keep it in range
}
// updateMeter();
Serial.println (encoder0Pos);
}
}
void resetEncoder() {
encoder0Reset = 1;
}
void doEncoder() {
encoder0Movement = 1;
if (digitalRead(encoder0PinA) == HIGH && digitalRead(encoder0PinB) == LOW) { //If encoder is turning clockwise . . .
encoder0Pos++; //Add 1
} else {
encoder0Pos--; //Subtract 1
}
}
//void updateMeter() {
// meterOutput = map(encoder0Pos, 0, 100, 0, 255);
// analogWrite(meterPin, meterOutput);//Output voltage to the meter
//}
Running the above code, I start the arduino, I turn the rotary encoder 5 clicks to the right, and then 10 clicks to the left. This should count up to 55, then back down to 45.
Here's the result, using the code above:
And here's the result with the only change being the un-commenting of the analogwrite to the meter in the Setup block:
232, fair point. As I'm still learning, I find it useful sometimes to just comment with what the line is doing in plain english, even if it's not really useful for others. But I'll try to be more descriptive.
Here is a "warning" note in Resources -> analogWrite function.
Perhaps the real problem is still with using Serial.print which his uses both millis and delay.
Maybe it is time to use real DAC instead of PWM. Or first move the meter to other pins and test it again.
The PWM outputs generated on pins 5 and 6 will have higher-than-expected duty cycles. This is because of interactions with the millis() and delay() functions, which share the same internal timer used to generate those PWM outputs. This will be noticed mostly on low duty-cycle settings (e.g. 0 - 10) and may result in a value of 0 not fully turning off the output on pins 5 and 6.
I'm not sure I follow. I have tried moving things around to different pins, and have not found any combos that yield different results.
With the Serial Print, are you saying that the print in itself is what is screwing up? If that were the case, wouldn't it be screwed up in either case, since the print is there in both cases? How would the inclusion of an analogwrite() cause the print to malfunction?
There should be no interaction between the analogWrite() and the encoder interrupts. It would appear there is some sort of cross talk between the input and output wires.
How are they arranged?
You may try putting a capacitor or low pass filter on the encoder output to give some hardware debounce.
You could also try some software debounce with a lockout on the interrupt execution
void doEncoder()
{
static unsigned long last_interrupt_time = 0;
unsigned long interrupt_time = millis();
if (interrupt_time - last_interrupt_time > = 50)//adjust lock out time as necessary
{
encoder0Movement = 1;
if (digitalRead(encoder0PinA) == HIGH && digitalRead(encoder0PinB) == LOW) { //If encoder is turning clockwise . . .
encoder0Pos++; //Add 1
} else {
encoder0Pos--; //Subtract 1
}
}
last_interrupt_time = interrupt_time;
}
I've got, what I believe to be a hardware debounce circuit on the encoder. I put this together a while ago, so I can't remember where I got this, but here's what it is:
And here's a photo of the physical setup. The Voltage Meter's wires do not interact with the encoders at all until they meet at the Arduino's pins.
Tumerboy:
Hey guys, I'm trying to drive a 5v voltage meter via a rotary encoder through an arduino Uno R3.
I have a pretty simple sketch, and the rotary encoder works great, counting between 0 and 100 in increments of 1, with little bounce unless I spin it SUPER fast. Any normal movements work as expected.
But when I enable the two output lines to the Volt Meter, it starts bouncing like crazy. It becomes difficult to even advance in one direction or the other.
I'm pretty new to this, so I don't care if my code is clean or perfect, but I can't figure out why it starts bouncing by running an analog write. . .
Here's all you need (edit pins as needed):
#define ENC_A (1UL << 0) // encoder A (on PORTD) (mega pin 21)
#define ENC_B (1UL << 1) // encoder B (on PORTD) (mega pin 20)
#define ENC_OUT PORTD // encoder on port D
#define ENC_INP PIND
#define ENC_DDR DDRD
#define LOW_LEVEL(I)((0b00)<<(I*2))
#define ANY_EDGE(I) ((0b01)<<(I*2))
#define FALL_EDGE(I)((0b10)<<(I*2))
#define RISE_EDGE(I)((0b11)<<(I*2))
volatile uint8_t state;
volatile uint32_t position;
// int0 is PORTD, bit 0
ISR (INT0_vect)
{
updEncoder ();
}
// int1 is PORTD, bit 1
ISR (INT1_vect)
{
updEncoder ();
}
uint32_t readEncoder (void)
{
uint32_t newval;
cli();
newval = position;
sei();
return newval;
}
void writeEncoder (uint32_t newval)
{
cli();
position = newval;
sei();
}
void updEncoder (void)
{
cli();
state >>= 2;
state |= (ENC_INP & ENC_A) ? 0b0100 : 0;
state |= (ENC_INP & ENC_B) ? 0b1000 : 0;
sei();
switch (state) {
case 1:
case 8:
case 14:
case 7: {
position += 1;
break;
}
case 2:
case 4:
case 13:
case 11: {
position -= 1;
break;
}
default: {
break;
}
}
}
// port and pin init in main() or setup()
//
// ENC_OUT |= (ENC_A | ENC_B); // encoder input_pullup
// ENC_DDR &= ~(ENC_A | ENC_B);
//
// EICRA |= ANY_EDGE (0);
// EICRA |= ANY_EDGE (1);
//
// EIMSK |= ((1UL << INT1) | (1UL << INT0)); // enable int1 and int0
//
// state = 0;
// state |= (ENC_INP & ENC_A) ? 0b0100 : 0; // init state
// state |= (ENC_INP & ENC_B) ? 0b1000 : 0;
//
// writeEncoder (0); // init encoder value