Project:Arduino Rotary Encoder Menu System

From Nottinghack Wiki
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

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)

 1 #define PIN_ENC1 2
 2 #define PIN_ENC2 3
 3 #define PIN_EBTN 4
 4 #define PIN_LED1 13
 5 static boolean moving=false;
 6 volatile unsigned int encValue = 0;
 7 unsigned int encValueTracking = 1;
 8 boolean enc1 = false;              
 9 boolean enc2 = false;
10 
11 // Here I'm messing around with button press durations - we could go to town here!
12 enum pressDuration { reallyQuickPress, shortPress, normalPress, longPress, veryLongPress };
13 long presses[] = { 40, 150, 300, 800, 1400 };
14 char* pressText[]={"really quick press!", "short press", "normal press", "long press", "very looooooooong press"};
15 
16 void setup() 
17 {
18   pinMode(PIN_ENC1, INPUT); 
19   pinMode(PIN_ENC2, INPUT); 
20   pinMode(PIN_EBTN, INPUT);
21   pinMode(PIN_LED1, OUTPUT);
22   digitalWrite(PIN_ENC1, HIGH);
23   digitalWrite(PIN_ENC2, HIGH);
24   digitalWrite(PIN_EBTN, HIGH);
25   Serial.begin(9600);
26   menuIntro();
27   attachInterrupt(0, intrEncChange1, CHANGE);
28   attachInterrupt(1, intrEncChange2, CHANGE);
29 }
30 
31 void intrEncChange1()
32 {
33   if(moving) 
34     delay(1);
35   if(digitalRead(PIN_ENC1) == enc1)
36     return;
37   enc1 = !enc1;
38   if(enc1 && !enc2) 
39     encValue += 1;
40   moving = false;
41 }
42 
43 void intrEncChange2()
44 {
45   if(moving) 
46     delay(1);
47   if(digitalRead(PIN_ENC2) == enc2)
48     return; 
49   enc2 = !enc2;
50   if(enc2 && !enc1) 
51     encValue -= 1;
52   moving = false;
53 }
54 
55 void loop() 
56 { 
57   static unsigned long btnHeld = 0;
58   moving = true;
59   if(encValueTracking != encValue){
60     Serial.print("encValue: ");
61     Serial.println(encValue, DEC);
62     encValueTracking = encValue;
63   }
64   // Upon button press...
65   if((digitalRead(PIN_EBTN) == LOW) && !btnHeld){
66     btnHeld = millis();
67     digitalWrite(PIN_LED1, HIGH);
68     Serial.print("pressed selecting: ");
69     Serial.println(encValue, DEC);
70   }
71   // Upon button release...
72   if((digitalRead(PIN_EBTN) == HIGH) && btnHeld){
73     long t = millis();
74     t -= btnHeld;
75     digitalWrite(PIN_LED1, LOW);
76     int dur = veryLongPress;
77     for(int i = 0; i<= veryLongPress; i++){
78       if(t > presses[i])
79          continue;
80       dur = i;
81       break;
82     }
83     
84     Serial.print("released selecting: ");
85     Serial.print(encValue, DEC);    
86     Serial.print(" (after ");
87     Serial.print(t, DEC);
88     Serial.print(" ms = ");
89     Serial.print(pressText[dur]);
90     Serial.println(")");
91     btnHeld = 0;
92   }
93 }
94 
95 void menuIntro()
96 {
97   Serial.println("Encoder Menu - blah, blah, blah");
98 }

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

  1 #!/usr/bin/perl -w
  2 use strict;
  3 use IO::Handle;
  4 autoflush STDOUT 1;
  5 autoflush STDERR 1;
  6 use tmstub;
  7 use Tk;
  8 #use Tk::FontDialog;
  9 my $title = 'piduino menu';
 10 my $version = "v0.2 2012-07-29";
 11 
 12 my $mw = new MainWindow();
 13 # PAL resolution 656x512@16
 14 $mw->overrideredirect(1);
 15 my ($scr_w, $scr_h) = $mw->maxsize();
 16 t "maxsize says: $scr_w x $scr_h";
 17 #~ $mw->geometry("656x512+0+0");
 18 $mw->geometry("680x540+0+0");
 19 #~ $mw->FullScreen;
 20 #~ $mw->geometry(($mw->maxsize())[0] .'x'.($mw->maxsize())[1] . "+0+0");
 21 my $c = $mw->Canvas(-bg => "yellow", -width => 680, -height => 540)->pack;
 22 # in theory we should be able to scroll infinitely but let's stick to something reasonable
 23 #$c->configure(-scrollregion => [0,0, 600, 400]);
 24 my $font = $mw->fontCreate('menufont', -family => 'Village', -size => 38, -weight=>'bold');
 25 
 26 my $menu_w = 400;
 27 my $menu_h = 100;
 28 
 29 # it don't work!
 30 #~ invisible_cursor();
 31 
 32 # warp pointer tests...
 33 $mw->after(100, sub {warp(20,20)});
 34 
 35 sub warp {
 36     my($x, $y) = @_;
 37     t "warp pointer to $x $y";
 38     $c->focus;
 39     $mw->eventGenerate("<Motion>",
 40       -when => 'head',
 41       -x => $x, -y => $y, -warp => 1
 42     );
 43 }
 44 
 45 # keybinding tests...
 46 $mw->bind('<KeyPress>' => \&print_keysym);
 47 
 48 sub print_keysym {
 49     my($widget) = @_;
 50     my $e = $widget->XEvent;    # get event object
 51     my($keysym_text, $keysym_decimal) = ($e->K, $e->N);
 52     print "keysym=$keysym_text, numeric=$keysym_decimal\n";
 53 }
 54 
 55 # hide cursor
 56 sub invisible_cursor {
 57     my $bitmapfile = Tk->findINC('trans_cur.xbm');
 58     my $maskfile   = Tk->findINC('trans_cur.mask');
 59     $mw->configure(-cursor => [ '@' . $bitmapfile, $maskfile, 'black', 'white' ] );
 60 }
 61 
 62 # handy font chooser
 63 #~ $mw->Button(
 64     #~ -font => $font, 
 65     #~ -text => "Font", 
 66     #~ -command => sub{choose_font()},
 67 #~ )->pack();
 68 
 69 # debug grid...
 70 $c->createGrid(0, 0, 10, 10);
 71 $c->createGrid(0, 0, 50, 50, -lines => 1, -dash => '-.');
 72 $c->createGrid(0, 0, 100, 100, -width => 3, -lines => 1);
 73 
 74 # TODO - load settings from file
 75 menubox("Music", 0, 0);
 76 menubox("Controls", 0, 1);
 77 menubox("Vehicle", 0, 2);
 78 menubox("System", 0, 3);
 79 menubox("Settings", 0, 3);
 80 # menubox("More...", 1, 0);
 81 
 82 #~ music selection
 83 #~ playlists
 84 #~ artists by name
 85 #~ albums
 86 #~ directories
 87 
 88 #~ controls
 89 #~ volume
 90 #~ bass
 91 #~ treble
 92 #~ balance
 93 #~ system reboot
 94 #~ system shutdown
 95 #~ app quit
 96 #~ app restart
 97 
 98 
 99 
100 sub menubox {
101     my($t, $mx, $my) = @_;
102     my $x1 = $mx * $menu_w;
103     my $y1 = $my * $menu_h;
104     my $x2 = $x1 + $menu_w;
105     my $y2 = $y1 + $menu_h;
106 
107     $c->createRectangle($x1, $y1, $x2, $y2, 
108         -fill => "blue", 
109         -activefill => "green", 
110         -outline => "green", 
111         -activeoutline => "orange",
112     );
113     $c->createText($x1 + ($menu_w/2), $y1+ ($menu_h/2), 
114         -text => "$t", 
115         -font => $font,
116         -fill => "black",
117     );
118 }
119 
120 
121 =for docs
122 
123 options: 
124 
125 * canvas width and height to cover the whole menu system
126 * sub-menus can't all be visible simultaneously so we'd have to hide them
127 * group items together
128 
129 do top level menu first
130 
131 menu level zero at 0,0
132 menu level one appears at 1*menu_w, selected item * menu_h
133 
134 
135 
136 =cut
137 
138 
139 
140 
141 sub choose_font
142 {
143 #    t $mw->GetDescriptiveFontName($font);
144     
145  #   my $f = $mw->FontDialog->Show( 
146         #-initfont => $font,
147         #-nicefont => 1
148  #   );
149  #   return unless defined $f;
150  #   $mw->RefontTree(-font => $f, -canvas => 1);
151  #   my $d = $mw->GetDescriptiveFontName($f);
152  #   t $d;
153  #   $font = $f;
154 
155 }
156 
157 
158 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

 1 #include <PinChangeInt.h> // necessary otherwise we get undefined reference errors.
 2 #include <AdaEncoder.h>
 3 
 4 #define a_PINA 10
 5 #define a_PINB 11
 6 #define BTN 9
 7 #define LED 13
 8 #define b_PINA A3
 9 #define b_PINB A4
10 
11 int8_t clicks=0;
12 char id=0;
13 
14 void setup()
15 {
16   Serial.begin(9600); 
17   Serial.println("---------------------------------------");
18   pinMode(BTN, INPUT);
19   pinMode(LED, OUTPUT);
20   digitalWrite(BTN, HIGH);
21   AdaEncoder::addEncoder('a', a_PINA, a_PINB);
22   // AdaEncoder::addEncoder('b', b_PINA, b_PINB);  
23 }
24 
25 void loop()
26 {
27   static unsigned long btnHeld = 0;
28   encoder *thisEncoder;
29   thisEncoder=AdaEncoder::genie(&clicks, &id);
30   if (thisEncoder != NULL) {
31     thisEncoder=AdaEncoder::getFirstEncoder();
32 
33     Serial.print(id); 
34     Serial.print(':');
35     Serial.print(clicks, DEC);
36     if (clicks > 0) {
37       Serial.println(" UP");
38     }
39     if (clicks < 0) {
40       Serial.println(" DOWN");
41     }
42   }
43   // Upon button press...
44   if((digitalRead(BTN) == LOW) && !btnHeld){
45     btnHeld = millis();
46     digitalWrite(LED, HIGH);
47     Serial.println("pressed");
48 
49   }
50   // Upon button release...
51   if((digitalRead(BTN) == HIGH) && btnHeld){
52     long t = millis();
53     digitalWrite(LED, LOW);
54     Serial.print("released: (after ");
55     t -= btnHeld;
56     Serial.print(t, DEC);
57     Serial.println(" ms)");
58 
59     btnHeld = 0;
60   }
61 }