Smooth / precise scroll with mouse.h

Hi all,

I've been working on my harddisk scrollwheel for some time now and it works kind of ok. The code is in the next reply if you're interested. It could be improved though, with smoother, more precise scroll.

Using the mouse.h library "Mouse.move(x, y, wheel)" command the scrolling can be controlled. It is however to rough/jumpy. I have tried windows and program settings, tweaks in the code (using rising, falling or both edges, counting etc.). Bottom line is that one Mouse.move(0, 0, 1) makes the scroll jump to much.

However, I also have a wacom tablet. When I put this in touch mode I can scroll very precisely by dragging, lets say as smooth as a macbook trackpad.

Ok, so now for my question. Would it in any way be possible to achieve smooth scrolling with arduino / mouse.h ? I mean my tablet is sending some usb commands, can those be mimicked using mouse.h ?

I found Nico Hood's github: GitHub - NicoHood/HID: Bring enhanced HID functions to your Arduino! but I'm not sure what I'm looking for exactly and if it's even possible.

So... if any of you have any thoughts, I'd love to hear them :slight_smile:

Thanks.

1 Like

The code for my harddisk scrollwheel:

/*
 * This is the code to turn a harddisk motor into a functinal scrollwheel
 * 
 * Motor: 4 lead motor, 1 common and 3 signal leads
 * 
 * Principal:
 * HDmotors need a controller to run. It runs them by powering the leads in a timed sequence.
 * However, if you spin the motor by hand, you generate signals on the leads. These are millivolt signals
 * 
 * The common of the motor is conected to ground and the three leads are connected to the input+ inputs on an LM324 opamp IC:
 * Ground is conected to input- inputs for reference, the leads are connected to the input+ input on the LM324
 * If a lead generates a few millivolts on the input+ the LM324 sends out a logical 1 (+5v) from the output
 * 
 * These signal from the LM324 are monitored by the arduino using interrupts
 * 
 * 
 * 
 */

 
#include <Mouse.h>
#include <Streaming.h>                              // for serial output
#include <PinChangeInterrupt.h>                     // library to use NON-interrupt pins as interrupt pins


//HD Motor > LM324 > arduino pins
const byte L1 = 16;    
const byte L2 = 8;
const byte L3 = 9;

int intPreviousLead;                                // Stores the previously fired lead
int intCheck;                                       // Used to calculate an integer that indicates the direction
int intDirection;                                   // CW = 1, CCW = -1, no scroll = 0

#define intLengthArrPrevDir 3                       // the number of elements in arrPrevDir, this is the number of changes needed in the same direction, before scrolling is enabled
int arrPrevDir[intLengthArrPrevDir];                // array to hold previous directions
int intPrevDirIndex = 0;                            // index to place previous directions  
int intSumArrPrevDir;                               // holds the sum of all elements in arrPrevDir

const int intTresholdFast = 10;                        
const int intTresholdMedium = 20;
const int intTresholdSlow = 500;
int intScrollMultiplier = 1;
unsigned long uslInterval;
unsigned long uslPrevInterrupt;                     // time stamp in millis to determine interval between interrupts

bool bScroll;                                       // boolean used to disable/enable scrolling
int intScrollCount = 0;                             // when scrolling slowly you might want to skip scrolls to prevent jumpy/fast scrolling, to have more control 
int intMaxScrollCount = 2;                          // the number of scrolls on which a scroll command is sent

void setup() {

  pinMode(L1, INPUT);
  pinMode(L2, INPUT);
  pinMode(L3, INPUT);

  pinMode(LED_BUILTIN_TX,INPUT);                                                        // turn off onboard TX/RX leds on arduino micro
  pinMode(LED_BUILTIN_RX,INPUT);

  // EDGE CHANGES===============================================================
  attachPCINT(digitalPinToPCINT(L1), vChange1, CHANGE);
  attachPCINT(digitalPinToPCINT(L2), vChange2, CHANGE);
  attachPCINT(digitalPinToPCINT(L3), vChange3, CHANGE);

}

void loop(){
  // Nothing in the loop, all is done by interrupts
}

void vChange1(){                                    // Parameters can not be passed from interrupts that are defined in setup
  vScroll(1);                                       // If a lead fires an interrupt the number of the lead is passed
}                                                   // to the scroll function as an integer
void vChange2(){                                                                        
  vScroll(2);                                                                            
}
void vChange3(){
  vScroll(3);
}

void vScroll(int intLead){                                                              
  
  uslInterval = millis() - uslPrevInterrupt;        // determine interval between this and previous interrupt
  uslPrevInterrupt = millis();  
  
  intCheck = intLead - intPreviousLead;            // Clockwise: 132132132132
                                                   // 3-1=2  2-3=-1  1-2=-1  3-1=2  >>> result current-previous is -1 or  2 
  switch(intCheck){                                // Counter Clockwise 231231231231
    case -1:      case 2:                          // 3-1=1  1-3=-2  2-1=1   3-2=1  >>> result current-previous is  1 or -2 
      intDirection = -1;                                            
    break;                                           
    case 1:      case -2:                          // defining scroll direction depends on personal preferences
      intDirection = 1;                            // when turning clockwise or counter clockwise
    break;
    default:                                                                            
      intDirection = 0;
    break;
  }
   
  intPreviousLead = intLead;  

  // DISABLE / ENABLE SCROLLING =======================================================
  // To prevent unpredictable scolling when starting and stopping the disk
  // only after n (intLengthArrPrevDir) valid changes in same direction scrolling is enabled. 

  arrPrevDir[intPrevDirIndex] = intDirection;                  // place the registered direction into arrPrevDir
  
  intPrevDirIndex++;                                           // increment the index
  
  if(intPrevDirIndex == intLengthArrPrevDir){                  // set index back to zero if it's the length of the array
    intPrevDirIndex = 0;
  }
  
  intSumArrPrevDir = 0;                                        // reset the sum variable to zero                                                            

  for(int i=0; i<intLengthArrPrevDir; i++){                    // By calculating the sum of all elements in the array it 
    intSumArrPrevDir += arrPrevDir[i];                         // can be determined if all elements are the same (all -1 or 1)
  }

  if(abs(intSumArrPrevDir) == intLengthArrPrevDir){            // if the absolute value of the sum of the elements in arrPrevDir is equal to the number of elements, all element are either -1 or 1
    bScroll = true;                                                                      
  }else{
    bScroll = false;
  }

  // SCROLL ============================================================================
  // Scroll only if bScroll is true

  if(bScroll){

    intScrollCount++;
    
    if (intScrollCount > intMaxScrollCount){                                // keep track of how many scrolls have been registered
      intScrollCount = 0;
    }

    switch(uslInterval){
    
      case 0 ... intTresholdFast:                                                   // fast:  when intInterval is in the range [0 up until and including intTresholdFast]
        intScrollMultiplier = 2;                             
      break;
      
      case intTresholdFast + 1 ... intTresholdMedium:                               // medium:  when intInterval is in the range [intTresholdFast+1 up until and including intTresholdMedium]
        if(intScrollCount == intMaxScrollCount){                                    // only scroll on every intMaxScrollCount-th scroll
          intScrollMultiplier = 2;                                                  // if for example 3, scroll on every third 
        }else{
          intScrollMultiplier = 0;
        }
      break;
      
      case intTresholdMedium + 1 ... intTresholdSlow:                               
        if(intScrollCount == intMaxScrollCount){                                    // only scroll on every intMaxScrollCount-th scroll
          intScrollMultiplier = 1;                                                  // if for example 3, scroll on every third 
        }else{
          intScrollMultiplier = 0;
        }
      break;

      default:
        intScrollMultiplier = 0;
      break;        
      
    }
    
    Mouse.move(0, 0, intScrollMultiplier * intDirection);
   }
  
} // end void vScroll
1 Like

The HID descriptor defines the x and y coordinates range and precision. When you use mouse.move() you
are giving movement within the defined coordinate system. I would suggest changing the precision of the
X and Y coordinates in the HID descriptor.

1 Like