Adafruit: Rotary Encoder
Overview: 2 Wiring: 1 Code: 1 git/Compiles with make: 2 Demo: 2 Total: 8/10 Comments: How do I do the pull ups?
Overview
Adafruit's Rotary Encoder is a device that detects rotation. As it rotates, it sends pulses on two pins. By comparing the signals, the direction of rotation can be determined. There are 24 pulses per rotation. The device also acts as a pushbutton switch.
Inputs and Outputs
The device has five pins - three on one side and two on the other. The side with three pins is for the encoder and is ordered (from left to right) signal A, ground, and signal B. The side with two pins is for the pushbutton switch. The signal pins require the use of pullup resistors, and the switch should use either a pullup or pulldown resistor. More information can be found on the product page or on the datasheet.
Bone Usage
The pins on the device can be difficult to use with a bread board, so it may be useful to solder the devide to a perfboard and add connectors.
How do you configure the pullups?
. The two signal pins and one of the pushbutton pins can be hooked up directly to any of the GPIO ports on the BeagleBone. The pins should then be configured as pullups for the signal pins and either pullup or pulldown for the switch pin. The remaining pins can be hooked up to GND and +3,3v. The example below uses GPIO1_6 for signal A, GPIO1_15 for signal B, and GPIO1_16 for the pushbutton switch.
The C code below demonstrates how to use the encoder with the BeagleBone.
/* Port Congiguration: -Encoder A: GPIO1_6 - Header P8, pin 3 - GPIO 38 -Encoder B: GPIO1_15 - Header P8, pin 15 - GPIO 47 -Encoder GND: Header P8, pin 1 -Pushbutton Switch: GPIO1_16 - Header P9, pin 15 - GPIO 48 -Pusshbuton Switch V+: Header P9, pin 3 This program keeps track of an encoder. CW rotation will increment the ticks, and CCW will decrement it. The current number of ticks is printed when the button is pressed. The files gpio.h and gpio.c consist of the GPIO methods copied directly from: https://www.ridgerun.com/developer/wiki/index.php/Gpio-int-test.c */ #include "gpio.h" #include <signal.h> #include <poll.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> #define OMAP_DIR "/sys/kernel/debug/omap_mux" int running = 1; // Quit when ^C is pressed void signal_handler(int sig) { printf("\n"); running = 0; } int main(int argc, char* argv[]) { // Polling variables int rc; struct pollfd fdset[3]; int nfds = 3; int len; char* buf[MAX_BUF]; // Keeps track of encoder ticks. int ticks = 0; // Variables used to store GPIO values unsigned int enc_a_val; unsigned int enc_b_val; unsigned int sw_val; // Handle Ctrl^C signal(SIGINT, signal_handler); // Configure the pins to use internal pull-down resistors FILE *mux_ptr; char pullup_str[10]; char pulldown_str[10]; strcpy(pullup_str, "0x0037"); strcpy(pulldown_str, "0x0027"); // Configure pull-up for the A signal if ((mux_ptr = fopen(OMAP_DIR "/gpmc_ad6", "rb+")) == NULL) { printf("Failed to open gpmc_ad6. Quitting.\n"); exit(1); } else { fwrite(&pullup_str, sizeof(char), 6, mux_ptr); fclose(mux_ptr); } // Configure pull-up for the B signal if ((mux_ptr = fopen(OMAP_DIR "/gpmc_ad15", "rb+")) == NULL) { printf("Failed to open gpmc_ad15. Quitting.\n"); exit(1); } else { fwrite(&pullup_str, sizeof(char), 6, mux_ptr); fclose(mux_ptr); } // Configure pull-down for the switch if ((mux_ptr = fopen(OMAP_DIR "/gpmc_a0", "rb+")) == NULL) { printf("Failed to open gpmc_a0. Quitting.\n"); exit(1); } else { fwrite(&pulldown_str, sizeof(char), 6, mux_ptr); fclose(mux_ptr); } // Set up GPIO pins unsigned int gpio_a = 38; unsigned int gpio_b = 47; unsigned int gpio_sw = 48; int enc_a_fd; int enc_b_fd; int enc_sw_fd; printf("Exporting a... %d\n", gpio_export(gpio_a)); printf("Exporting b... %d\n", gpio_export(gpio_b)); printf("Exporting sw... %d\n", gpio_export(gpio_sw)); printf("Setting a direction... %d\n", gpio_set_dir(gpio_a, 0)); printf("Setting b direction... %d\n", gpio_set_dir(gpio_b, 0)); printf("Setting sw direction... %d\n", gpio_set_dir(gpio_sw, 0)); // Interrupts printf("setting edge a... %d\n", gpio_set_edge(gpio_a, "rising")); printf("setting edge sw... %d\n", gpio_set_edge(gpio_sw, "rising")); // Open the file for the encoder A signal enc_a_fd = gpio_fd_open(gpio_a); enc_sw_fd = gpio_fd_open(gpio_sw); // Main loop while (running == 1) { memset((void*)fdset, 0, sizeof(fdset)); fdset[0].fd = STDIN_FILENO; fdset[0].events = POLLIN; fdset[1].fd = enc_a_fd; fdset[1].events = POLLPRI; fdset[2].fd = enc_sw_fd; fdset[2].events = POLLPRI; rc = poll(fdset, nfds, -1); if (rc < 0) { // printf("poll() failed.\n"); } if (rc == 0) { // printf("."); } if (fdset[0].revents & POLLIN) { (void) read(fdset[0].fd, buf, 1); } // Encoder click if (fdset[1].revents & POLLPRI) { lseek(fdset[1].fd, 0, SEEK_SET); len = read(fdset[1].fd, buf, MAX_BUF); enc_a_val = atoi((const char*) buf); gpio_get_value(gpio_b, &enc_b_val); if (enc_a_val == 1) // rising edge { if (enc_b_val == 0) ticks--; else ticks++; } } // Button press - print the current number of encoder ticks if (fdset[2].revents & POLLPRI) { lseek(fdset[2].fd, 0, SEEK_SET); len = read(fdset[2].fd, buf, MAX_BUF); // Very simple debouncing usleep(5000); gpio_get_value(gpio_sw, &sw_val); if (sw_val == 1) { printf("Ticks: %d\n", ticks); } } } gpio_fd_close(enc_a_fd); return 0; }
The full code can be found in John Lobdell's Git repository.
Interfacing with node.js
Interfacing it with node.js is relatively straight-forward. The code is shwon below, and can be found in John Lobdell's Git repository.
rotary_encoder.js:
// From Getting Started With node.js and socket.io // http://codehenge.net/blog/2011/12/getting-started-with-node-js-and-socket-io-v0-7-part-2/ "use strict"; var http = require('http'), url = require('url'), fs = require('fs'), exec = require('child_process').exec, server, connectCount = 0; // Number of connections to server server = http.createServer(function (req, res) { // server code var path = url.parse(req.url).pathname; console.log("path: " + path); switch (path) { case '/': res.writeHead(200, {'Content-Type': 'text/html'}); res.write('<h1>John Lobdell - Mini Project 4</h1>Try<ul><li><a href="/rotary_encoder.html">Rotary Encoder</a></li></ul>'); res.end(); break; default: // This is so all the files will be sent. fs.readFile(__dirname + path, function (err, data) { if (err) {return send404(res); } // console.log("path2: " + path); res.write(data, 'utf8'); res.end(); }); break; } }); var send404 = function (res) { res.writeHead(404); res.write('404'); res.end(); }; server.listen(8081); // socket.io, I choose you var io = require('socket.io').listen(server); io.set('log level', 2); // on a 'connection' event io.sockets.on('connection', function (socket) { var frameCount = 0; // Counts the frames from arecord var lastFrame = 0; // Last frame sent to browser console.log("Connection " + socket.id + " accepted."); // console.log("socket: " + socket); // now that we have our connected 'socket' object, we can // define its event handlers // Make sure gpio 38, 47, and 48 are available. exec("echo 38 > /sys/class/gpio/export"); exec("echo 47 > /sys/class/gpio/export"); exec("echo 48 > /sys/class/gpio/export"); // Configure pullup/pulldown resistors exec("echo 0x0037 > /sys/kernel/debug/omap_mux/gpmc_ad6"); // Pullup for encoder A exec("echo 0x0037 > /sys/kernel/debug/omap_mux/gpmc_ad15"); // Pullup for encoder B exec("echo 0x0027 > /sys/kernel/debug/omap_mux/gpmc_a0"); // Pulldown for the switch // Handle disconnects socket.on('disconnect', function () { console.log("Connection " + socket.id + " terminated."); connectCount--; if(connectCount === 0) { } console.log("connectCount = " + connectCount); }); // Periodically send data var push_interval = 20; function push_data() { // Send encoder A status var gpioPath = "/sys/class/gpio/gpio38/value"; fs.readFile(gpioPath, 'base64', function (err, data) { if (err) throw err; socket.emit('enc_a', data); }); // Send encoder B status gpioPath = "/sys/class/gpio/gpio47/value"; fs.readFile(gpioPath, 'base64', function (err, data) { if (err) throw err; socket.emit('enc_b', data); }); // Send encoder switch status gpioPath = "/sys/class/gpio/gpio48/value"; fs.readFile(gpioPath, 'base64', function (err, data) { if (err) throw err; socket.emit('enc_sw', data); }); setTimeout(push_data, push_interval); } push_data(); connectCount++; console.log("connectCount = " + connectCount); });
rotary_encoder.html:
<!doctype html> <html> <head> <title>Rotary Encoder Demo</title> <script src="/json.js"></script> <!-- for ie --> <script src="/socket.io/socket.io.js"></script> <link href="layout.css" rel="stylesheet" type="text/css"> <script src="jquery.js"></script> <script src="jquery.flot.js"></script> <script src="jquery.flot.navigate.js"></script> </head> <body> <h1>Rotary Encoder Demo <a href="http://Rose-Hulman.edu" target="_blank"> <img src="RoseLogo96.png" width=200 style="float:right"></a></h1> <button id="connect" onClick='connect()'/>Connect</button> <button id="disconnect" onClick='disconnect()'>Disconnect</button> <!-- <button id="send" onClick='send()'/>Send Message</button> --> <table> <tr> <td><div id="plotTop" style="width:550px;height:150px;"></div> <center>samples</center></td> </tr> <tr> <td><div id="plotBot" style="width:550px;height:150px;"></div> <center>samples<center></td> </tr> </table> <br /> <br /> <a href="http://beagleboard.org" target="_blank"> <img src="beagle-hd-logo.gif" width=200 align="right"></a> <div><p id="status">Waiting for input</p></div> <div><p id="ticks"></p></div> <a href="http://www.ti.com/sitara" target="_blank"> <img src="hdr_ti_logo.gif" width=200 align="right"></a> By <i>John Lobdell</i> <br /> <script> var socket; var firstconnect = true, fs = 8000, Ts = 1/fs*1000, samples = 100, plotTop, enc_a = [], ienc_a = 0, enc_b = [], ienc_b = 0, sw_data = [], isw_data = 0, gpio_enc_a = 38, gpio_enc_b = 47, gpio_sw = 48; enc_a[samples] = 0; enc_b[samples] = 0; sw_data[samples] = 0; function connect() { if(firstconnect) { socket = io.connect(null); socket.on('message', function(data) { status_update("Received: message");}); socket.on('connect', function() { status_update("Connected to Server"); }); socket.on('disconnect', function() { status_update("Disconnected from Server"); }); socket.on('reconnect', function() { status_update("Reconnected to Server"); }); socket.on('reconnecting', function( nextRetry ) { status_update("Reconnecting in " + nextRetry/1000 + " s"); }); socket.on('reconnect_failed', function() { message("Reconnect Failed"); }); socket.on('enc_a', enca); socket.on('enc_b', encb); socket.on('enc_sw', encsw); socket.on('set_ticks', setTicks); firstconnect = false; } else { socket.socket.reconnect(); } } function disconnect() { socket.disconnect(); } // When new data arrives, convert it and plot it. function enca(data) { // status_update("enc_a " + data); data = atob(data); enc_a[ienc_a] = [ienc_a, data]; ienc_a++; if (ienc_a >= samples) { ienc_a = 0; enc_a = []; } plotTop.setData([ enc_a, enc_b ]); plotTop.draw(); } function encb(data) { // status_update("enc_b " + data); data = atob(data); enc_b[ienc_b] = [ienc_b, data]; ienc_b++; if (ienc_b >= samples) { ienc_b = 0; enc_b = []; } plotTop.setData([ enc_a, enc_b ]); plotTop.draw(); } function encsw(data) { // status_update("encsw " + data); data = atob(data); sw_data[isw_data] = [isw_data, data]; isw_data++; if (isw_data >= samples) { isw_data = 0; sw_data = []; } plotBot.setData([ sw_data ]); plotBot.draw(); } function status_update(txt){ document.getElementById('status').innerHTML = txt; } function setTicks(numTicks){ document.getElementById('ticks').innerHTML = "Ticks: " + numTicks; } connect(); $(function () { function initPlotData() { // zip the generated y values with the x values var result = []; for (var i = 0; i <= samples; i++) result[i] = [i, 0]; return result; } // setup plot var optionsTop = { series: { shadowSize: 0, // drawing is faster without shadows points: { show: false}, lines: { show: true, lineWidth: 5}, }, yaxis: { min: 0, max: 2, zoomRange: [10, 256], panRange: [-128, 128] }, xaxis: { show: true, zoomRange: [10, 100], panRange: [0, 100] }, legend: { position: "sw" }, zoom: { interactive: true, amount: 1.1 }, pan: { interactive: true } }; plotTop = $.plot($("#plotTop"), [ { data: initPlotData(), label: "Encoder A" }, { data: initPlotData(), label: "Encoder B" } ], optionsTop); var optionsBot = { series: { shadowSize: 0, // drawing is faster without shadows points: { show: false}, lines: { show: true, lineWidth: 5}, color: 2 }, yaxis: { min: 0, max: 2, zoomRange: [10, 256], panRange: [-128, 128] }, xaxis: { show: true, zoomRange: [10, 100], panRange: [0, 100] }, legend: { position: "sw" }, zoom: { interactive: true, amount: 1.1 }, pan: { interactive: true } }; plotBot = $.plot($("#plotBot"), [ { data: initPlotData(), label: "Encoder Switch"} ], optionsBot); }); </script> </body> </html>