Using the LS7166 Quadrature Counter

From Mech
Jump to navigationJump to search

Introduction

The LS7166 is a powerful quadrature decoder/counter chip. A motor's encoder can be connected directly to the LS7166, which will decode and count pulses as the encoder channels change. Using a parallel data bus, a microcontroller can simply read the current count value from the chip, allowing a project to interface with motor encoders without using any additional timers.

In this document, the LS7166 is interfaced with a PIC32, but the code could be ported to work with a PIC18.

A chip with similar capability is the LS7366R, which uses the SPI bus to communicate instead of in parallel, is discussed on separate wiki page.

Overview of the LS7166

The LS7166 is a 24 bit quadrature counter. The A and B inputs normally serve as quadrature inputs, like those which come from a quadrature encoder like those on most motors. The A and B inputs can also serve as up / down counter inputs, so that the LS7166 can be used for other applications as well. The internal counter can count in binary, BCD, or in time units (hours/minutes/seconds). In any motor-related application, binary counting is necessary. The chip can also operate in divide-by-n mode, which loads the count register with a fixed value on rollover. This is useful for motor applications, since the fixed value can be set to the number of pulses in a full rotation, and then whenever the count value is read, the exact position of the motor can be known no matter the amount of time that has passed since the last read. The chip has several other features that can be read about in the datasheet. The code in this document only covers divide-by-n binary quadrature mode, and for more advanced features the support code will need to be modified.

The LS7166 communicates with a microcontroller via a bi-directional 8-bit data bus and several control bits. Four of the contorl bits, /RD (read), /WR (write), C/D (control or data), and /CS (chip select), are required for communication. Two other control lines, LCTR/LLTC and ABGT/RCTR are for more complex features and can usually be tied to Vdd or GND as needed. There are also two pins which output flags when programmed events occur, such as an overflow. For more info on these features, see the datasheet. Note that most pins are active low, and thus must be kept at high at all times unless in use. If multiple chips are used (for interfacing to multiple motors), all the communication lines can be reused except /CS (chip select).

The following is a list of registers and the general use of each, even though they are already handled by the code written in a later section.

  • MCR- Master control register. This register controls functions like resetting the chip and register transfer operations. (Write only)
  • CNTR- Counter. This register contains the current count value. CNTR cannot be read to or written from directly. Instead PR or OR must be used.
  • PR- Preset register. This register contains a value that can be written to CNTR by software control or on underflow in the divide-by-n mode. As such, it generally will contain the maximum encoder value. (Write only)
  • ICR- Input control register. This register sets up input writing modes. (Write only)
  • OSR- Output status register. This register contains some status bits. (Read only)
  • OCCR- Output control register. This register sets up output modes. (Write only)
  • QR- Quadrature register. This register sets up the quadrature mode. (Write only)
  • OL- Output latch. This register is used to read the value of CNTR. A write of 0x03 to MCR will transfer CNTR to OL, and then reading OL will obtain the CNTR value. (Read only)

Circuit

7166ckt png.PNG

The LS7166, as mentioned above, connects to the PIC via an 8-bit data bus, 4 control bits, 2 optional control bits, and 2 optional flags. In the code below and example schematic, pins B0-B7 are used for the data bus. These pins can be changed, but the data bus read and write functions will need to be changed to interface with bit lines instead of bytes. Since this was put in the separate function, other code won't have to be changed, other than TRIS setups.

Other than PIC connections, the LS7166 also needs power (3.3v or 5v), ground, and connections to the A and B inputs of the encoder. Also note that in the case of this example the optional control bit /ABGT is tied to ground..

As mentioned above, multiple LS7166 can be used which are connected to the same data and control lines, except for chip select, which needs to be separate for each chip. The code will also have to be modified to toggle specific /CS lines during reads.

Code

The code consists of three files:

  • 7166.h - This header file contains function definitions, pin defines, etc.
  • 7166_lib.c - This c file contains functions that interface with the 7166
  • 7166_test.c - This c file contains the main function and is a simple example of how to use the library functions

All three can be download from this zip file.

7166_lib.c

This c file contains various functions used to interface with the LS7166. The init_7166() function should be called before using any other functions. The init function can be modified to utilize other special features of the chip not used here. Note that at the end of the init function, the counter limit value is loaded into the PR register. This number should be changed based on the full rotation count of the encoder in use. The value of the PR register is loaded whenever the counter borrows (downticks from 0). Note that the code does not reset to 0 when it reaches PR. To find the actual position, the software will have to take count MOD PR.

The other primary function that will be used is read_reg(OL), which returns the value from CNTR after transferring CNTR to OL. Most other functions will usually not be needed unless special features are needed.

/********************************************************************
 * 7166_lib.c : Library functions for using the 7166R SPI decoder
 *
 * Creation date: 20-APR-2010
 * Last modified: 21-APR-2010
 *
 * Thomas Peterson
 * LIMS
 ********************************************************************/

#include <plib.h>
#include <peripheral/generic.h>
#include <peripheral/spi.h>

#include "7166.h"

/* Comm_dir: Switches tris bits on comm bus to inputs or outputs
          If the bus is changed from port B to other pins, this fcn
          will need to be changed   */
void comm_dir(int dir) {
   if (dir==IN) {
      //Set as input
      TRISB |= 0x00FF;   
   }   
   else {
      //set as output
      TRISB &= 0xFF00;   
   }   
} 
    
/* bus_out : This function sends out a byte on the data bus
          If the bus pins are changed, this fcn will need
          to be changed too. 
          This does NOT handle other control lines. */
void bus_out(unsigned char byte) {
   comm_dir(OUT);
   LATB = byte;
}   

/* bus_in : This function reads a byte on the data bus
          If the bus pins are changed, this fcn will need
         to be changed too. 
         This does NOT handle other control lines. */
unsigned char bus_in() {
   comm_dir(IN);
   return (unsigned char)(PORTB & 0x00FF);
}   

void init_7166() {
   write_ctrl(MCR,0x20);    //Performs master reset
   write_ctrl(MCR,0x04);   //Sub-reset
   write_ctrl(ICR,0x18);    //Enables A/B, sets up pin 3-4
   write_ctrl(OCCR, 0x34); //Divide by n mode   0x04
   write_ctrl(QR,0x03);   //x4 quadrature mode   0x04
   //0x3840->Pr
   write_PR(0x3840);      //Upper limit
}   

/* write_ctrl: writes a byte to the given control register */
void write_ctrl(unsigned char reg, unsigned char byte){
   NCS = 0;   //Select chip
   NRD = 1;
   C_ND = 1;   //Setup to write to control register
   unsigned char op = (reg << 6) | byte;
   bus_out(op);
   latchWR();   //write value
   NCS = 1;   //Unselect chip   
}   

void latchWR() {
   delay();   //Setup time (???)
   NWR = 0;   //Latch out write
   delay();   //Hold time (???)
   NWR = 1;   //End write   
}   

void write_PR(unsigned int value) {
   unsigned int write_value;
   //Prepare for PR read
   write_ctrl(MCR,0x01);   //Reset ctr
   NCS = 0;   //select chip
   C_ND = 0;   //Writes will be to PR _only_
   write_value = value & 0xff; //Get lowest byte
   bus_out((unsigned char)write_value);
   latchWR();   //Latch out value
   write_value = (value >> 8) & 0xff; //Get next byte
   bus_out((unsigned char)write_value);
   latchWR(); //Latch out value
   write_value = (value >> 16) & 0xff; //Get MSB
   bus_out((unsigned char)write_value);
   latchWR(); //Latch out value
   NCS = 1;    //Unselect chip
   write_ctrl(MCR,0x08);    //PR->CNTR
}

unsigned int read_reg(unsigned char reg) {
   unsigned int read_value;
   if (reg == OSR) {
      NCS = 0;   //Select chip
      C_ND = 1;
      NWR = 1;
      read_value = latchRD();
      NCS = 1;   //Deselect chip
      return read_value;   
   }      
   if (reg == OL) {
      write_ctrl(MCR,0x03);   //Prepare for read
      C_ND = 0;
      NRD = 1;
      NWR = 1;
      NCS = 0;   //Select chip
      unsigned char read_byte;
      read_byte = latchRD();   //Read byte 0
      read_value = read_byte;
      delay();
      read_byte = latchRD();   //REad byte 1
      int tmp = read_byte;
      tmp = tmp << 8;
      read_value |= tmp;
      delay();
      read_byte = latchRD(); //Read byte 3
      tmp = read_byte;
      tmp = tmp << 16;
      read_value |= tmp;
      NCS = 1;   //Unselect chip
      return read_value;
   }
   //no other regs can be read   
}   

unsigned char latchRD() {
   NRD = 0;   //Read
   delay();   //???
   unsigned char read_byte = bus_in();
   delay();
   NRD = 1;   //End latch
   return read_byte;
}   

/* Delays for 10 cycles, plus branch time */
void delay() {
   asm("nop");
   asm("nop");
   asm("nop");
   asm("nop");
   asm("nop");
   asm("nop");
   asm("nop");
   asm("nop");
   asm("nop");
   asm("nop");
}

7166_test.c

This c file simply loops continuously and reads values from the LS7166. Note that to use the 7166, the only steps are to setup the pins, call the init function, then call the read function to get a value.

/***********************************************************
 * 7366_test.c : C-file which runs tests that use the
 *				 LS7166 parallel-interface encoder chip.
 *	
 * Creation date: 20-APR-2010
 * Last modified: 21-APR-2010
 *
 * Thomas Peterson
 * LIMS
 ********************************************************************/

#include "HardwareProfile.h"
#include "7166.h"
#include <plib.h>


#pragma config FPBDIV = DIV_1            //Sets PBCLK to SYSCLK
#define FPB 80000000

void setup_rs232(int pbClk) {
	#define config1 	UART_EN | UART_IDLE_CON | UART_RX_TX | UART_DIS_WAKE | UART_DIS_LOOPBACK | UART_DIS_ABAUD | UART_NO_PAR_8BIT | UART_1STOPBIT | UART_IRDA_DIS | UART_DIS_BCLK_CTS_RTS| UART_NORMAL_RX | UART_BRGH_SIXTEEN
	#define config2		UART_TX_PIN_LOW | UART_RX_ENABLE | UART_TX_ENABLE | UART_INT_TX | UART_INT_RX_CHAR | UART_ADR_DETECT_DIS | UART_RX_OVERRUN_CLEAR
	// Open UART2 with config1 and config2
	OpenUART2( config1, config2, pbClk/16/9600-1);	// calculate actual BAUD generate value.
	putsUART2("UART init done\r\n");
}

void Delayms( unsigned t)
// This uses Timer 1, can be changed to another timer. Assumes FPB = SYS_FREQ
{
    OpenTimer1(T1_ON | T1_PS_1_256, 0xFFFF);
    while (t--)
    {  // t x 1ms loop
        WriteTimer1(0);
        while (ReadTimer1() < FPB/256/1000);
	}
	CloseTimer1();
} // Delayms


/* Main Function */
int main(void)
{
	// Configure the proper PB frequency and the number of wait states
	int pbClk = SYSTEMConfigPerformance(SYS_FREQ);
	 
	// Set all analog pins to be digital I/O
	AD1PCFG = 0xFFFF;
	
	//Init I/O
	//Set comm bits as outputs
	TRISD &= 0xFFE1; 	//D4-D1 outputs (0)
	TRISC &= 0xFFE1; 	//C4-C1 outputs (0)
	//TRISB (data bus) handled by comm fcns
	//Set outputs high (inactive)
	NWR = 1;
	NCS = 1;
	C_ND = 1;
	NRD = 1;
 
	
	//UART setup
	setup_rs232(pbClk);
 
	//7166 setup
	init_7166();
	 
	//Read peroidically and send encoder value
	unsigned int readval;
	char string[40];
	while(1) {
		Delayms(250);
		readval = read_reg(OL);
		sprintf(string,"Read from OL: %x , OSR \r\n", readval );
		putsUART2(string);
	}	
}

Shortfalls / Next Steps

The biggest problem with the system above is that in divide-by-n mode the chip does not reset the counter to zero when CNTR = PR. Because of this, if running the motor backwards continuously (counting down), the value of CNTR will always be between 0 and PR at the current position. However, when running forward (counting up) the value of CNTR goes past PR. Because of this, the pic software must be written so that the read value of CNTR is modded by the maximum count number to find the position. For instance, if the maximum count is 4000, and CNTR is 4200, then the actual position is 200 (out of 4000). Eventually the CNTR will overflow if not reduced. In this case, the /CY flag can be set to interrupt the PIC so that the correct value of CNTR can be reloaded. Alternatively, at each read the PIC could send back the actual count value. Note that this would require writing to PR and then doing a PR->CNTR transfer using the control register.

If possible, the 7133R may be a better choice since it does bounded counting correctly.