Project:Arduino Rotary Encoder Menu System: Difference between revisions

From Nottinghack Wiki
Jump to navigation Jump to search
No edit summary
Fowkc (talk | contribs)
 
(3 intermediate revisions by one other user not shown)
Line 115: Line 115:
OK, working well --[[User:Msemtd|Michael Erskine]] 18:10, 29 July 2012 (EST)
OK, working well --[[User:Msemtd|Michael Erskine]] 18:10, 29 July 2012 (EST)


== Raspberry Pi menu for rotary encoder ==
When using the Raspberry Pi as the menu display system we need to do a few things to a stock raspbian install...
When using the Raspberry Pi as the menu display system we need to do a few things to a stock raspbian install...
* auto login and start X: use option in  raspi-config
* auto login and start X: use option in  raspi-config
Line 120: Line 121:
* auto start application
* auto start application


moving the cursor with Perl Tk
Here I'm building a Perl Tk menu application prototype using Tk::Canvas. I haven't yet wired up the serial as I'm just trying out some graphic styles on the small PAL LCD screen.
 
<div style ="height:300px;overflow-x:hidden;overflow-y:auto;border: 4px solid green;">
'''Raspberry Pi menu for rotary encoder'''
<syntaxhighlight lang="perl" line="GESHI_FANCY_LINE_NUMBERS" enclose="div">
#!/usr/bin/perl -w
use strict;
use IO::Handle;
autoflush STDOUT 1;
autoflush STDERR 1;
use tmstub;
use Tk;
#use Tk::FontDialog;
my $title = 'piduino menu';
my $version = "v0.2 2012-07-29";
 
my $mw = new MainWindow();
# PAL resolution 656x512@16
$mw->overrideredirect(1);
my ($scr_w, $scr_h) = $mw->maxsize();
t "maxsize says: $scr_w x $scr_h";
#~ $mw->geometry("656x512+0+0");
$mw->geometry("680x540+0+0");
#~ $mw->FullScreen;
#~ $mw->geometry(($mw->maxsize())[0] .'x'.($mw->maxsize())[1] . "+0+0");
my $c = $mw->Canvas(-bg => "yellow", -width => 680, -height => 540)->pack;
# in theory we should be able to scroll infinitely but let's stick to something reasonable
#$c->configure(-scrollregion => [0,0, 600, 400]);
my $font = $mw->fontCreate('menufont', -family => 'Village', -size => 38, -weight=>'bold');
 
my $menu_w = 400;
my $menu_h = 100;
 
# it don't work!
#~ invisible_cursor();
 
# warp pointer tests...
$mw->after(100, sub {warp(20,20)});
 
sub warp {
    my($x, $y) = @_;
    t "warp pointer to $x $y";
    $c->focus;
    $mw->eventGenerate("<Motion>",
      -when => 'head',
      -x => $x, -y => $y, -warp => 1
    );
}
 
# keybinding tests...
$mw->bind('<KeyPress>' => \&print_keysym);
 
sub print_keysym {
    my($widget) = @_;
    my $e = $widget->XEvent;    # get event object
    my($keysym_text, $keysym_decimal) = ($e->K, $e->N);
    print "keysym=$keysym_text, numeric=$keysym_decimal\n";
}
 
# hide cursor
sub invisible_cursor {
    my $bitmapfile = Tk->findINC('trans_cur.xbm');
    my $maskfile  = Tk->findINC('trans_cur.mask');
    $mw->configure(-cursor => [ '@' . $bitmapfile, $maskfile, 'black', 'white' ] );
}
 
# handy font chooser
#~ $mw->Button(
    #~ -font => $font,
    #~ -text => "Font",
    #~ -command => sub{choose_font()},
#~ )->pack();
 
# debug grid...
$c->createGrid(0, 0, 10, 10);
$c->createGrid(0, 0, 50, 50, -lines => 1, -dash => '-.');
$c->createGrid(0, 0, 100, 100, -width => 3, -lines => 1);
 
# TODO - load settings from file
menubox("Music", 0, 0);
menubox("Controls", 0, 1);
menubox("Vehicle", 0, 2);
menubox("System", 0, 3);
menubox("Settings", 0, 3);
# menubox("More...", 1, 0);
 
#~ music selection
#~ playlists
#~ artists by name
#~ albums
#~ directories
 
#~ controls
#~ volume
#~ bass
#~ treble
#~ balance
#~ system reboot
#~ system shutdown
#~ app quit
#~ app restart
 
 
 
sub menubox {
    my($t, $mx, $my) = @_;
    my $x1 = $mx * $menu_w;
    my $y1 = $my * $menu_h;
    my $x2 = $x1 + $menu_w;
    my $y2 = $y1 + $menu_h;
 
    $c->createRectangle($x1, $y1, $x2, $y2,
        -fill => "blue",
        -activefill => "green",
        -outline => "green",
        -activeoutline => "orange",
    );
    $c->createText($x1 + ($menu_w/2), $y1+ ($menu_h/2),
        -text => "$t",
        -font => $font,
        -fill => "black",
    );
}
 
 
=for docs
 
options:
 
* canvas width and height to cover the whole menu system
* sub-menus can't all be visible simultaneously so we'd have to hide them
* group items together
 
do top level menu first
 
menu level zero at 0,0
menu level one appears at 1*menu_w, selected item * menu_h
 
 
 
=cut
 
 
 
 
sub choose_font
{
#    t $mw->GetDescriptiveFontName($font);
   
#  my $f = $mw->FontDialog->Show(
        #-initfont => $font,
        #-nicefont => 1
#  );
#  return unless defined $f;
#  $mw->RefontTree(-font => $f, -canvas => 1);
#  my $d = $mw->GetDescriptiveFontName($f);
#  t $d;
#  $font = $f;
 
}
 
 
MainLoop();
 
</syntaxhighlight>
</div>
 
 
OK, using adaencoder on the Pi-arduino bridge! The Arduino is just saying what it sees over serial.
 
<div style ="height:300px;overflow-x:hidden;overflow-y:auto;border: 4px solid green;">
'''Raspberry Pi menu for rotary encoder'''
<syntaxhighlight lang="cpp" line="GESHI_FANCY_LINE_NUMBERS" enclose="div">
#include <PinChangeInt.h> // necessary otherwise we get undefined reference errors.
#include <AdaEncoder.h>
 
#define a_PINA 10
#define a_PINB 11
#define BTN 9
#define LED 13
#define b_PINA A3
#define b_PINB A4
 
int8_t clicks=0;
char id=0;
 
void setup()
{
  Serial.begin(9600);
  Serial.println("---------------------------------------");
  pinMode(BTN, INPUT);
  pinMode(LED, OUTPUT);
  digitalWrite(BTN, HIGH);
  AdaEncoder::addEncoder('a', a_PINA, a_PINB);
  // AdaEncoder::addEncoder('b', b_PINA, b_PINB); 
}
 
void loop()
{
  static unsigned long btnHeld = 0;
  encoder *thisEncoder;
  thisEncoder=AdaEncoder::genie(&clicks, &id);
  if (thisEncoder != NULL) {
    thisEncoder=AdaEncoder::getFirstEncoder();
 
    Serial.print(id);
    Serial.print(':');
    Serial.print(clicks, DEC);
    if (clicks > 0) {
      Serial.println(" UP");
    }
    if (clicks < 0) {
      Serial.println(" DOWN");
    }
  }
  // Upon button press...
  if((digitalRead(BTN) == LOW) && !btnHeld){
    btnHeld = millis();
    digitalWrite(LED, HIGH);
    Serial.println("pressed");
 
  }
  // Upon button release...
  if((digitalRead(BTN) == HIGH) && btnHeld){
    long t = millis();
    digitalWrite(LED, LOW);
    Serial.print("released: (after ");
    t -= btnHeld;
    Serial.print(t, DEC);
    Serial.println(" ms)");
 
    btnHeld = 0;
  }
}
 
</syntaxhighlight>
</div>

Latest revision as of 00:51, 12 June 2014

I have recovered a number of ALPS rotary encoders from discarded industrial CRT monitors. I'm working on building a user interface menu system that makes use of the rotary encoders as the sole input technique.

Rotary Encoder Test Sketch With Interrupts (non-PWM)

#define PIN_ENC1 2
#define PIN_ENC2 3
#define PIN_EBTN 4
#define PIN_LED1 13
static boolean moving=false;
volatile unsigned int encValue = 0;
unsigned int encValueTracking = 1;
boolean enc1 = false;              
boolean enc2 = false;

// Here I'm messing around with button press durations - we could go to town here!
enum pressDuration { reallyQuickPress, shortPress, normalPress, longPress, veryLongPress };
long presses[] = { 40, 150, 300, 800, 1400 };
char* pressText[]={"really quick press!", "short press", "normal press", "long press", "very looooooooong press"};

void setup() 
{
  pinMode(PIN_ENC1, INPUT); 
  pinMode(PIN_ENC2, INPUT); 
  pinMode(PIN_EBTN, INPUT);
  pinMode(PIN_LED1, OUTPUT);
  digitalWrite(PIN_ENC1, HIGH);
  digitalWrite(PIN_ENC2, HIGH);
  digitalWrite(PIN_EBTN, HIGH);
  Serial.begin(9600);
  menuIntro();
  attachInterrupt(0, intrEncChange1, CHANGE);
  attachInterrupt(1, intrEncChange2, CHANGE);
}

void intrEncChange1()
{
  if(moving) 
    delay(1);
  if(digitalRead(PIN_ENC1) == enc1)
    return;
  enc1 = !enc1;
  if(enc1 && !enc2) 
    encValue += 1;
  moving = false;
}

void intrEncChange2()
{
  if(moving) 
    delay(1);
  if(digitalRead(PIN_ENC2) == enc2)
    return; 
  enc2 = !enc2;
  if(enc2 && !enc1) 
    encValue -= 1;
  moving = false;
}

void loop() 
{ 
  static unsigned long btnHeld = 0;
  moving = true;
  if(encValueTracking != encValue){
    Serial.print("encValue: ");
    Serial.println(encValue, DEC);
    encValueTracking = encValue;
  }
  // Upon button press...
  if((digitalRead(PIN_EBTN) == LOW) && !btnHeld){
    btnHeld = millis();
    digitalWrite(PIN_LED1, HIGH);
    Serial.print("pressed selecting: ");
    Serial.println(encValue, DEC);
  }
  // Upon button release...
  if((digitalRead(PIN_EBTN) == HIGH) && btnHeld){
    long t = millis();
    t -= btnHeld;
    digitalWrite(PIN_LED1, LOW);
    int dur = veryLongPress;
    for(int i = 0; i<= veryLongPress; i++){
      if(t > presses[i])
         continue;
      dur = i;
      break;
    }
    
    Serial.print("released selecting: ");
    Serial.print(encValue, DEC);    
    Serial.print(" (after ");
    Serial.print(t, DEC);
    Serial.print(" ms = ");
    Serial.print(pressText[dur]);
    Serial.println(")");
    btnHeld = 0;
  }
}

void menuIntro()
{
  Serial.println("Encoder Menu - blah, blah, blah");
}

multiple encoders

Try out http://code.google.com/p/adaencoder/ and PinChangeInt library (http://code.google.com/p/arduino-pinchangeint/) see...

http://arduino.cc/playground/Main/RotaryEncoders#Example14

See if it can cope with two encoders at rapid rate usage.

OK, working well --Michael Erskine 18:10, 29 July 2012 (EST)

Raspberry Pi menu for rotary encoder

When using the Raspberry Pi as the menu display system we need to do a few things to a stock raspbian install...

  • auto login and start X: use option in raspi-config
  • switch off screen blanking: /etc/lightdm/lightdm.conf in SeatDefaults section "xserver-command=X -s 0 -dpms"
  • auto start application

Here I'm building a Perl Tk menu application prototype using Tk::Canvas. I haven't yet wired up the serial as I'm just trying out some graphic styles on the small PAL LCD screen.

Raspberry Pi menu for rotary encoder

#!/usr/bin/perl -w
use strict;
use IO::Handle;
autoflush STDOUT 1;
autoflush STDERR 1;
use tmstub;
use Tk;
#use Tk::FontDialog;
my $title = 'piduino menu';
my $version = "v0.2 2012-07-29";

my $mw = new MainWindow();
# PAL resolution 656x512@16
$mw->overrideredirect(1);
my ($scr_w, $scr_h) = $mw->maxsize();
t "maxsize says: $scr_w x $scr_h";
#~ $mw->geometry("656x512+0+0");
$mw->geometry("680x540+0+0");
#~ $mw->FullScreen;
#~ $mw->geometry(($mw->maxsize())[0] .'x'.($mw->maxsize())[1] . "+0+0");
my $c = $mw->Canvas(-bg => "yellow", -width => 680, -height => 540)->pack;
# in theory we should be able to scroll infinitely but let's stick to something reasonable
#$c->configure(-scrollregion => [0,0, 600, 400]);
my $font = $mw->fontCreate('menufont', -family => 'Village', -size => 38, -weight=>'bold');

my $menu_w = 400;
my $menu_h = 100;

# it don't work!
#~ invisible_cursor();

# warp pointer tests...
$mw->after(100, sub {warp(20,20)});

sub warp {
    my($x, $y) = @_;
    t "warp pointer to $x $y";
    $c->focus;
    $mw->eventGenerate("<Motion>",
      -when => 'head',
      -x => $x, -y => $y, -warp => 1
    );
}

# keybinding tests...
$mw->bind('<KeyPress>' => \&print_keysym);

sub print_keysym {
    my($widget) = @_;
    my $e = $widget->XEvent;    # get event object
    my($keysym_text, $keysym_decimal) = ($e->K, $e->N);
    print "keysym=$keysym_text, numeric=$keysym_decimal\n";
}

# hide cursor
sub invisible_cursor {
    my $bitmapfile = Tk->findINC('trans_cur.xbm');
    my $maskfile   = Tk->findINC('trans_cur.mask');
    $mw->configure(-cursor => [ '@' . $bitmapfile, $maskfile, 'black', 'white' ] );
}

# handy font chooser
#~ $mw->Button(
    #~ -font => $font, 
    #~ -text => "Font", 
    #~ -command => sub{choose_font()},
#~ )->pack();

# debug grid...
$c->createGrid(0, 0, 10, 10);
$c->createGrid(0, 0, 50, 50, -lines => 1, -dash => '-.');
$c->createGrid(0, 0, 100, 100, -width => 3, -lines => 1);

# TODO - load settings from file
menubox("Music", 0, 0);
menubox("Controls", 0, 1);
menubox("Vehicle", 0, 2);
menubox("System", 0, 3);
menubox("Settings", 0, 3);
# menubox("More...", 1, 0);

#~ music selection
#~ playlists
#~ artists by name
#~ albums
#~ directories

#~ controls
#~ volume
#~ bass
#~ treble
#~ balance
#~ system reboot
#~ system shutdown
#~ app quit
#~ app restart



sub menubox {
    my($t, $mx, $my) = @_;
    my $x1 = $mx * $menu_w;
    my $y1 = $my * $menu_h;
    my $x2 = $x1 + $menu_w;
    my $y2 = $y1 + $menu_h;

    $c->createRectangle($x1, $y1, $x2, $y2, 
        -fill => "blue", 
        -activefill => "green", 
        -outline => "green", 
        -activeoutline => "orange",
    );
    $c->createText($x1 + ($menu_w/2), $y1+ ($menu_h/2), 
        -text => "$t", 
        -font => $font,
        -fill => "black",
    );
}


=for docs

options: 

* canvas width and height to cover the whole menu system
* sub-menus can't all be visible simultaneously so we'd have to hide them
* group items together

do top level menu first

menu level zero at 0,0
menu level one appears at 1*menu_w, selected item * menu_h



=cut




sub choose_font
{
#    t $mw->GetDescriptiveFontName($font);
    
 #   my $f = $mw->FontDialog->Show( 
        #-initfont => $font,
        #-nicefont => 1
 #   );
 #   return unless defined $f;
 #   $mw->RefontTree(-font => $f, -canvas => 1);
 #   my $d = $mw->GetDescriptiveFontName($f);
 #   t $d;
 #   $font = $f;

}


MainLoop();


OK, using adaencoder on the Pi-arduino bridge! The Arduino is just saying what it sees over serial.

Raspberry Pi menu for rotary encoder

#include <PinChangeInt.h> // necessary otherwise we get undefined reference errors.
#include <AdaEncoder.h>

#define a_PINA 10
#define a_PINB 11
#define BTN 9
#define LED 13
#define b_PINA A3
#define b_PINB A4

int8_t clicks=0;
char id=0;

void setup()
{
  Serial.begin(9600); 
  Serial.println("---------------------------------------");
  pinMode(BTN, INPUT);
  pinMode(LED, OUTPUT);
  digitalWrite(BTN, HIGH);
  AdaEncoder::addEncoder('a', a_PINA, a_PINB);
  // AdaEncoder::addEncoder('b', b_PINA, b_PINB);  
}

void loop()
{
  static unsigned long btnHeld = 0;
  encoder *thisEncoder;
  thisEncoder=AdaEncoder::genie(&clicks, &id);
  if (thisEncoder != NULL) {
    thisEncoder=AdaEncoder::getFirstEncoder();

    Serial.print(id); 
    Serial.print(':');
    Serial.print(clicks, DEC);
    if (clicks > 0) {
      Serial.println(" UP");
    }
    if (clicks < 0) {
      Serial.println(" DOWN");
    }
  }
  // Upon button press...
  if((digitalRead(BTN) == LOW) && !btnHeld){
    btnHeld = millis();
    digitalWrite(LED, HIGH);
    Serial.println("pressed");

  }
  // Upon button release...
  if((digitalRead(BTN) == HIGH) && btnHeld){
    long t = millis();
    digitalWrite(LED, LOW);
    Serial.print("released: (after ");
    t -= btnHeld;
    Serial.print(t, DEC);
    Serial.println(" ms)");

    btnHeld = 0;
  }
}