Difference between revisions of "Adafruit: Rotary Encoder"

From eLinux.org
Jump to: navigation, search
m
(Bone Usage)
Line 24: Line 24:
 
==Bone Usage==
 
==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. [[File:Adafruit_Rotary_Encoder_With_Connectors.jpg|thumb|Rotary encoder with connectors added]]
+
The pins on the device can be difficult to use with a bread board, so it may be useful to solder the device to a perfboard and add connectors. [[File:Adafruit_Rotary_Encoder_With_Connectors.jpg|thumb|Rotary encoder with connectors added]]
  
 
<blockquote style="color:red">How do you configure the pullups?</blockquote>.
 
<blockquote style="color:red">How do you configure the pullups?</blockquote>.
  
 
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. [[File:Adafruit_Rotary_Encoder_in_BeagleBone.jpg|thumb|Rotary encoder hooked up to the BeagleBone]]
 
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. [[File:Adafruit_Rotary_Encoder_in_BeagleBone.jpg|thumb|Rotary encoder hooked up to the BeagleBone]]
 +
 +
To configure the pull-up resistor, write 0x0037 to the pin's OMAP mux file (in /sys/kernel/debug/omap_mux). For example, the code below writes 0x0037 into /sys/kernel/debug/omap_mux/gpmc_ad6 for the B signal pin. Writing a value of 0x0027 to the file will configure it to use a pull-down resistor.
  
 
The C code below demonstrates how to use the encoder with the BeagleBone.
 
The C code below demonstrates how to use the encoder with the BeagleBone.

Revision as of 22:54, 13 November 2012


Overview: 2
Wiring:   1
Code:     1
git/Compiles with make: 2
Demo:     2
Total:    8/10
Comments: How do I do the pull ups?
Adafruit's Rotary Encoder

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 device to a perfboard and add connectors.

Rotary encoder with connectors added

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.

Rotary encoder hooked up to the BeagleBone

To configure the pull-up resistor, write 0x0037 to the pin's OMAP mux file (in /sys/kernel/debug/omap_mux). For example, the code below writes 0x0037 into /sys/kernel/debug/omap_mux/gpmc_ad6 for the B signal pin. Writing a value of 0x0027 to the file will configure it to use a pull-down resistor.

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>