Please note as of Wednesday, August 15th, 2018 this wiki has been set to read only. If you are a TI Employee and require Edit ability please contact x0211426 from the company directory.

Omapl137 linux eqep driver

From Texas Instruments Wiki
Jump to: navigation, search

Agenda

  • Rotary encoder
  • eQEP Peripheral overview
  • eQEP Driver and application
  • References


Rotary encoder

  • Introduction
  • Output signals
  • Working principle


Introduction

  • The enhanced quadrature encoder pulse (eQEP) module is used for direct interface with a linear or rotary incremental encoder to get position, direction, and speed information from a rotating machine for use in a high-performance motion and position-control system.

RotaryEncoder.jpg

Rotary encoder present on OMAPL137 UI card

Ti omapl137 evm.jpg

Rotary encoder

RotaryEncoderWaveforms.jpg

Working Principle

  • As the disk rotates, the two photo-elements generate signals that are shifted 90 degrees out of phase from each other. These are commonly called the quadrature QEPA and QEPB signals
  • A second track is added to generate a signal that occurs once per revolution (QEPI: Signal to indicate the index or end of one revolution), which can be used to indicate an absolute position
  • The clockwise direction for most encoders is defined as the QEPA channel going positive before the QEPB channel and vise versa as shown in figure

Foward-reversemovement.jpg

eQEP Peripheral Overview

  • eQEP inputs
  • Functional block diagram
  • Quadrature decoder state machine
  • eQEP watchdog timer


eQEP inputs

The OMAPL137/DA830 has 2 eQEP instances on chip. The inputs for each of these eQEP peripheral include two pins for quadrature-clock mode or direction-count mode, an index (or 0 marker), and a strobe input.

  • QEPA/XCLK and QEPB/XDIR: These two pins can be used in quadrature-clock mode or direction-count mode
    • Quadrature-clock Mode: The eQEP encoders provide two square wave signals (A and B) 90 electrical degrees out of phase
    • Direction-count Mode: In direction-count mode, direction and clock signals are provided directly from the external source. Some position encoders have this type of output instead of quadrature output. The QEPA pin provides the clock input and the QEPB pin provides the direction input
  • QEPI: Index or Zero Marker
  • QEPS: Strobe Input: This general-purpose strobe signal can initialize or latch the position counter on the occurrence of a desired event on the strobe pin. This signal is typically connected to a sensor or limit switch to notify that the motor has reached a defined position


Functional block diagram

EqepFunctionalBlockDiagram.jpg

The eQEP peripheral contains the following major functional units (as shown in Figure):

  • Programmable input qualification for each pin (part of the GPIO MUX)
  • Quadrature decoder unit (QDU)
  • Position counter and control unit for position measurement (PCCU)
  • Quadrature edge-capture unit for low-speed measurement (QCAP)
  • Unit time base for speed/frequency measurement (UTIME)
  • Watchdog timer for detecting stalls (QWDOG)

Quadrature decoder state machine

  • The direction decoding logic of the eQEP circuit determines which one of the sequences (QEPA, QEPB) is the leading sequence and accordingly updates the direction information in the QDF bit in the eQEP status register (QEPSTS). Both edges of the QEPA and QEPB signals are sensed to generate count pulses for the position counter.

QuadratureDecoderStateMachine.jpg

eQEP watchdog timer

  • Contains a 16-bit watchdog timer to monitor the quadrature-clock to indicate proper operation of the motion-control system.
  • Clocked from SYSCLKOUT/64 and the quadrate clock event (pulse) resets the watchdog timer.
  • If no quadrature-clock event is detected until a period match (QWDPRD = QWDTMR), then the watchdog timer will time out and the watchdog interrupt flag will be set (QFLG[WTO]).
  • The time-out value is programmable through the watchdog period register (QWDPRD)

EQEPWatchdog.jpg

Registers

  • Here are the registers as part of eQEP peripheral. For more information on the registers, refer to eQEP datasheet.

EQEPRegisters.jpg

eQEP Driver on DA830/OMAPL137

  • Configuration
  • Overview
  • Application flow diagram
  • Example application


Driver Configuration

System Type --->
   [*] DA830/OMAP-L137 UI (User Interface) board support Device Drivers --->
SPI support --->
   [ ] SPI support Input device support --->
[*] Miscellaneous devices --->
   <*> TI enhanced Quadrature Encoder Pulse (eQEP) support

Note: The Rotary Encoders are present on the UI card, so the UI Card needs to be enabled


Driver Overview

  • Driver provides very low level interface of the peripheral to the user
  • On each interrupt, the driver sends up all of the latched register values along with the values of the interrupt flag and status registers.
  • User needs to have good understanding of the external encoder used and also about the functioning of the eQEP peripheral and its registers.
  • Application needs to configure the driver with required register settings through sysfs entries to operate with the device.


Application flow diagram

  • Here is the flow diagram of one of the application shared in the next slide

EQEPflowchart.JPG

Example application

  • The example application interfaces with the rotary encoder (RotaryEncoder MCV 290) present on the UI card.
  • Here is the test application
  • C code -
/*
 * Listen for events from TI eQEP attached to a volume control knob.
 *
 * Author: Mark A. Greer <mgreer@mvista.com>
 *
 * 2008 (c) MontaVista Software, Inc. This file is licensed under
 * the terms of the GNU General Public License version 2. This program
 * is licensed "as is" without any warranty of any kind, whether express
 * or implied.
 */
 
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <sys/fcntl.h>
#include <linux/input.h>
 
#define EQEP_DEFAULT_DEVICE	"/dev/input/event0"
#define EQEP_SYSFS_DEVICE	"/sys/devices/platform/eqep"
 
#define ARRAY_SIZE(x)	(sizeof(x) / sizeof((x)[0]))
 
typedef unsigned long	u32;
 
static int fd;
 
struct eqep_event_info {
	struct input_event qflg_ev;
	struct input_event qepsts_ev;
	struct input_event qposlat_ev;
	struct input_event qposilat_ev;
	struct input_event qposslat_ev;
	struct input_event qctmrlat_ev;
	struct input_event qcprdlat_ev;
	struct input_event sync_ev;
} __attribute__((packed));
 
 
static void print_usage(char *argv[])
{
	fprintf(stderr, "Usage: %s [<input device>] - default dev: %s\n",
			basename(argv[0]), EQEP_DEFAULT_DEVICE); }
 
enum {
	QPOSCNT,
	QPOSINIT,
	QPOSMAX,
	QPOSCMP,
	QUTMR,
	QUPRD,
	QWDTMR,
	QWDPRD,
	QDECCTL,
	QEPCTL,
	QCAPCTL,
	QPOSCTL,
	QCTMR,
	QCPRD,
	REVID,
	QEINT,
};
 
struct sysdev_info {
	char	*path;
	int	init_required;
	u32	init_val;
};
 
static struct sysdev_info sysdev_tab[] = {
	[QPOSCNT] = {
		.path		= "qposcnt",
		.init_required	= 1,
		.init_val	= 0xdeadbeef,
	},
	[QPOSINIT] = {
		.path		= "qposinit",
		.init_required	= 1,
		.init_val	= 0xdeadbeef,
	},
	[QPOSMAX] = {
		.path		= "qposmax",
		.init_required	= 1,
		.init_val	= 0xdeadbeef,
	},
	[QPOSCMP] = {
		.path		= "qposcmp",
		.init_required	= 1,
		.init_val	= 0xdeadbeef,
	},
	[QUTMR] = {
		.path		= "qutmr",
		.init_required	= 1,
		.init_val	= 0xdeadbeef,
	},
	[QUPRD] = {
		.path		= "quprd",
		.init_required	= 1,
		.init_val	= 0xdeadbeef,
	},
	[QWDTMR] = {
		.path		= "qwdtmr",
		.init_required	= 1,
		.init_val	= 0xdeadbeef,
	},
	[QWDPRD] = {
		.path		= "qwdprd",
		.init_required	= 1,
		.init_val	= 0xdeadbeef,
	},
	[QDECCTL] = {
		.path		= "qdecctl",
		.init_required	= 1,
		.init_val	= 0xdeadbeef,
	},
	[QEPCTL] = {
		.path		= "qepctl",
		.init_required	= 1,
		.init_val	= 0xdeadbeef,
	},
	[QCAPCTL] = {
		.path		= "qcapctl",
		.init_required	= 1,
		.init_val	= 0xdeadbeef,
	},
	[QPOSCTL] = {
		.path		= "qposctl",
		.init_required	= 1,
		.init_val	= 0xdeadbeef,
	},
	[QCTMR] = {
		.path		= "qctmr",
		.init_required	= 1,
		.init_val	= 0xdeadbeef,
	},
	[QCPRD] = {
		.path		= "qcprd",
		.init_required	= 1,
		.init_val	= 0xdeadbeef,
	},
	[REVID] = {
		.path		= "revid",
		.init_required	= 0,
	},
	[QEINT] = {
		.path		= "qeint",
		.init_required	= 0,
	},
};
 
static int read_eqep_event(int fd)
{
	ssize_t cnt;
	struct eqep_event_info eei;
 
	/* read() should be in loop to collect all data--or use fread() */
	cnt = read(fd, &eei, sizeof(eei));
	if (cnt != sizeof(eei))
		return -1;
 
	printf("%d:%d:\n", eei.qflg_ev.time.tv_sec,
eei.qflg_ev.time.tv_usec);
	printf("  %d %d qflg:\t0x%08x\n", eei.qflg_ev.type,
			eei.qflg_ev.code, eei.qflg_ev.value);
	printf("  %d %d qepsts:\t0x%08x\n", eei.qepsts_ev.type,
			eei.qepsts_ev.code, eei.qepsts_ev.value);
	printf("  %d %d qposlat:\t0x%08x\n", eei.qposlat_ev.type,
			eei.qposlat_ev.code, eei.qposlat_ev.value);
	printf("  %d %d qposilat:\t0x%08x\n", eei.qposilat_ev.type,
			eei.qposilat_ev.code, eei.qposilat_ev.value);
	printf("  %d %d qposslat:\t0x%08x\n", eei.qposslat_ev.type,
			eei.qposslat_ev.code, eei.qposslat_ev.value);
	printf("  %d %d qctmrlat:\t0x%08x\n", eei.qctmrlat_ev.type,
			eei.qctmrlat_ev.code, eei.qctmrlat_ev.value);
	printf("  %d %d qcprdlat:\t0x%08x\n", eei.qcprdlat_ev.type,
			eei.qcprdlat_ev.code, eei.qcprdlat_ev.value);
	printf("  %d %d sync:\t0x%08x\n", eei.sync_ev.type,
			eei.sync_ev.code, eei.sync_ev.value);
	return 0;
}
 
static int read_all_events(int fd)
{
	fd_set rfds;
	struct timeval tv;
	int rc;
 
	FD_ZERO(&rfds);
	FD_SET(fd, &rfds);
 
	for (;;) {
		tv.tv_sec = 0;
		tv.tv_usec = 0;
 
		rc = select(fd + 1, &rfds, NULL, NULL, &tv);
		if (rc < 0) {
			fprintf(stderr, "select() failed: %s (%d)\n",
					strerror(errno), errno);
			return -1;
		}
 
		if (!FD_ISSET(fd, &rfds))
			break;
 
		rc = read_eqep_event(fd);
		if (rc < 0) {
			fprintf(stderr, "read() failed: %s (%d)n",
					strerror(errno), errno);
			return -1;
		}
	}
 
	return 0;
}
 
void sigio_handler(int sig)
{
	int rc;
 
	if (sig == SIGIO) {
		rc = read_all_events(fd);
		if (rc != 0)
			exit(100);
	} else
		fprintf(stderr, "Bogus signal: %d\n", sig); }
 
int write_reg(struct sysdev_info *sip, int unit, u32 val) {
	int lfd;
	unsigned long v;
	char fn[64], s[16];
 
	sprintf(fn, "%s.%d/%s", EQEP_SYSFS_DEVICE, 0, sip->path);
 
	lfd = open(fn, O_WRONLY);
	if (lfd < 0) {
		fprintf(stderr, "Can't open '%s', %s (%d)\n", fn,
				strerror(errno), errno);
		return -1;
	}
 
	sprintf(s, "0x%08x", val);
 
	write(lfd, s, strlen(s));
 
	close(lfd);
	return 0;
}
 
int config_hw(int fd)
{
	int i, lfd;
	ssize_t cnt;
	char fn[64], s[64];
 
	write_reg(&sysdev_tab[QPOSINIT], 0, 0x0);
	write_reg(&sysdev_tab[QPOSMAX], 0, 0xffffffff);
	write_reg(&sysdev_tab[QPOSMAX], 0, 0x1);
 
	/* XXX Interrupt evey few seconds (50MHz) */
	write_reg(&sysdev_tab[QUPRD], 0, 5*50*1024*1024);
 
	write_reg(&sysdev_tab[QDECCTL], 0, 0x0);
	write_reg(&sysdev_tab[QEPCTL], 0, 0xd0be);
	write_reg(&sysdev_tab[QEPCTL], 0, 0x50ba);
	write_reg(&sysdev_tab[QEINT], 0, 0x0868);
	write_reg(&sysdev_tab[QEINT], 0, 0x0068);
 
	return 0;
}
 
int main(int argc, char *argv[])
{
	int rc;
	long buf;
	char *dev;
	struct sigaction sa;
 
	switch (argc) {
	case 1:
		dev = EQEP_DEFAULT_DEVICE;
		break;
	case 2:
		dev = argv[1];
		break;
	default:
		print_usage(argv);
		exit(1);
	}
 
	sa.sa_handler = sigio_handler;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
 
	rc = sigaction(SIGIO, &sa, NULL);
	if (rc == -1) {
		fprintf(stderr, "sigaction failed: %s (%d)\n",
				strerror(errno), errno);
		exit(2);
	}
 
	rc = config_hw(fd);
	if (rc < 0) {
		fprintf(stderr, "config_hw() failed.  I quit.\n");
		exit(7);
	}
 
	fd = open(dev, O_RDONLY);
	if (fd < 0) {
		fprintf(stderr, "Can't open '%s', %s (%d)\n", dev,
				strerror(errno), errno);
		exit(3);
	}
 
	/* Adding O_ASYNC & O_NONBLOCK in open() doesn't work */
	rc = fcntl(fd, F_SETFL, O_ASYNC | O_NONBLOCK);
	if (rc < 0) {
		fprintf(stderr, "fcntl() failed: %s (%d)\n",
				strerror(errno), errno);
		exit(4);
	}
 
	rc = fcntl(fd, F_SETOWN, getpid());
	if (rc < 0) {
		fprintf(stderr, "fcntl() failed: %s (%d)\n",
				strerror(errno), errno);
		exit(5);
	}
 
	rc = ioctl(fd, EVIOCGBIT(EV_MSC, MSC_MAX), &buf);
	if (!rc) {
		fprintf(stderr, "ioctl failed, %s (%d)\n",
				strerror(errno), errno);
		exit(6);
	} else
		printf("  0x%08x\n", buf);
 
	for (;;)
		sleep(24 * 3600);
 
	/* NOTREACHED */
	close(fd);
}
  • The application flow for the application is already shown in the previous slide
  • The application will display the register values of the eQEP0 (instance 0) peripheral, when user rotates the rotary encoder present on the OMAPL137/DA830 UI card interfaced to eQEP0 instance. The registers that are displayed are:
    • QFLG (Interrupt Flag Register)
    • qepsts (Status Register)
    • qposlat (Position counter latch Register)
    • qposilat (Index position latch Register)
    • qposslat (Strobe position latch Register)
    • qctmrlat (Capture position latch Register)
    • qcprdlat (Capture period latch Register)

References