Reading a mouse wheel via Ardunio

We’re currently working on a project which uses a mouse wheel like a joystick– it can be pushed to the left and right but also normally scrolled. The mouse wheel is connected to an Arduino and we are already able to read the left and right buttons via the code. Now we would like the wheel to put out a character for scrolling up– but only once and then go back to a neutral state and another character for scrolling down, also just once and then go back to neutral state. At the moment the encoder gives out a 1 for scrolling up, a 2 for scrolling down and the neutral state is 0. However when we scroll the values seem to appear arbitrary. How could we make it more precise?

#define button1 12
#define button2 10

#define encoder0PinA 2
#define encoder0PinB 3


boolean in_1, in_2; 
boolean en_A, en_B; 
volatile int wheel = 0;
uint32_t milRef;


void setup() {
  // put your setup code here, to run once:

  Serial.begin(9600);
  pinMode(button1, INPUT_PULLUP);
  pinMode(button2, INPUT_PULLUP);
  
  pinMode(encoder0PinA, INPUT_PULLUP);
  pinMode(encoder0PinB, INPUT_PULLUP);

  attachInterrupt(en_A, rotation, RISING);
  milRef = millis()+20;
}




void loop() {
  uint32_t m = millis();
  in_1 = digitalRead(button1);
  in_2 = digitalRead(button2);

  if(m>milRef){
    Serial.print(in_1);
    Serial.print(in_2);
    Serial.print(wheel);
    Serial.println();

  
    if(wheel!=0){ wheel=0; }
    milRef = m+20;
  }
}



void rotation(){
  en_B = digitalRead(encoder0PinB);
  // if(en_B){ wheel++; } else { wheel--; }
  if(en_B){ wheel=1; } else { wheel=2; }
}

Two things I notice here: a) in your rotation() function you are only reading encoder0PinB, not encoder0pinA. Maybe you don't need to, but I'm not familiar with mouse scroll encoders. You did define pinA after all. b) You may need some 'debounce' in rotation() to be sure that signal changes are genuine and not noise. Interrupts happen very fast so it's possible you're getting more than one transition for each click of the wheel. In rotation(), you could save a new value of millis() each time but only do a 'wheel=1' or 'wheel=2' if the pin is still the same as it was 10 or 20 milliseconds ago. Trial and error may get you the best timing.

This code is too slow. You should use an optimized library that uses hardware interrupts, like this one. By the way, if the mouse you are using supports the P/S2 protocol, it's much easier to use it that way. It handles everything for you, gives you (relative) positions, scroll data, button presses...

Hope this helps! Pieter

ThePrax: We’re currently working on a project which uses a mouse wheel like a joystick– it can be pushed to the left and right but also normally scrolled. The mouse wheel is connected to an Arduino and we are already able to read the left and right buttons via the code. Now we would like the wheel to put out a character for scrolling up– but only once and then go back to a neutral state and another character for scrolling down, also just once and then go back to neutral state. At the moment the encoder gives out a 1 for scrolling up, a 2 for scrolling down and the neutral state is 0. However when we scroll the values seem to appear arbitrary. How could we make it more precise?

Your code looks close and you are using Hardware interrupts and you know what encoder0PinA state is because of the interrupt so I'm not sure where they are coming from.. With that said I looked over your nice code and tweaked it a little I cleaned up unneeded variables and added my version of Blink Without Delay timer I also set the encoder to count up and down so you can tell how far it has moved between your 20 millisecond samples other than that I did't see too many problems. the only possible problem was with your initialization of the interrupt

boolean en_A, en_B;  
 attachInterrupt(en_A, rotation, RISING);

en_A is a boolean value and not interrupt zero. this could prevent the attachInterrupt from actually attaching to Pin2

 attachInterrupt(digitalPinToInterrupt(encoder0PinA), rotation, RISING);

Here's My version of your code:

#define button1 12
#define button2 10

#define encoder0PinA 2
#define encoder0PinB 3

boolean in_1, in_2;
volatile int wheel = 0;

void setup() {
  // put your setup code here, to run once:

  Serial.begin(9600);
  pinMode(button1, INPUT_PULLUP);
  pinMode(button2, INPUT_PULLUP);

  pinMode(encoder0PinA, INPUT_PULLUP);
  pinMode(encoder0PinB, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(encoder0PinA), rotation, RISING);
}

void loop() {
  in_1 = digitalRead(button1);
  in_2 = digitalRead(button2);
  static unsigned long SpamTimer;
  if ( (unsigned long)(millis() - SpamTimer) >= (20)) {
    SpamTimer = millis();
    Serial.print(in_1);
    Serial.print(" ");
    Serial.print(in_2);
    Serial.print(" ");
    Serial.print(wheel);
    Serial.println();
    wheel = 0;
  }
}

void rotation() {
  // because the attachInterrupt triggers this function when the input encoder0PinA goes HIGH we know that encoder0PinA is HIGH
  if (digitalRead(encoder0PinB) == HIGH) {
    wheel++; // encoder0PinB is High Step Forward
  } else {
    wheel--; // encoder0PinB is LOW Step Backward
  }
}