Using the LS7366R SPI Quadrature Counter

From Mech
Jump to navigationJump to search

Introduction

The LS7366R is a powerful decoder/counter chip which can be connected directly to a motor encoder to count encoder pulses. The 7366 stores a current count of the pulses, which can be read by the PIC via SPI at any time. This is in contrast with the LS7166, which uses a parallel bus to communicate with the PIC. By using the 7366, the PIC can keep track of a motor's current position without devoting any onboard resources to tracking pulses. The 7366 can be configured to store the count as a 1-4 byte number, depending on the application.

The code on this page is for the PIC32.

Overview of the LS7366R

The LS7366R is a 32-bit counter. In quadrature mode the counter can be directly connected to the A and B channels of a motor encoder and will count pulses as they arrive. The counter register can be configured to be 1, 2, 3, or 4 bytes wide, so that if the application needs less than 32 bits transmission times can be reduced. In "modulo-n" mode the counter will remain in a given range, which can be set to the number of pulses in a full rotation so that a read from the counter will result in the exact position of the motor without need for additional calculations.

The 7366 is connected to the PIC via a 4 wire SPI bus. Three of the wires, SCK, SDI, and SDO, can be shared with other SPI devices. The /SS (slave select) line must be dedicated to the 7366. A series of communication actions results in a particular internal register being read from or written to. The registers are for control, status, and output functions. Although mostly handled by the library code, here is a description of each register:

  • MDR0- "Mode Regiser 0", the first control register which controls the quadrature mode, counting mode, index mode, and more
  • MDR1- "Mode register 1", the second control register controlling the number of bytes the counter will use and the flags
  • DTR- The value of DTR can be transferred to the counter register (CNTR) under software or hardware control. This is were the high count value is stored for modulo-n mode.
  • CNTR- Counter register which stores the current count. This register cannot be read from directly. Instead, a read will trigger a copy from CNTR to OTR, then OTR will be read.
  • OTR- This register is used to store the value copied from CNTR for output. Note that reading from CNTR will automatically use OTR, and reading from OTR manually will read the value of CNTR at the time of the last CNTR read.
  • STR- This is a status register containing status bits
  • IR- This register is used internally to select the register being written to or read from on the next bus transaction.

Note that after setup the only registers the user should generally be referencing are CNTR (count), and STR (status) if needed.

Circuit

7366 circuit1.PNG

The 7366 connects to the PIC using the SPI bus, as mentioned above. As seen in the circuit to the right, the relevant wires are SCK, SDI, SDO, and SS. In the code below, the SPI1 module is used, but with slight changes the SPI2 module could be used instead. There are other optional pins that could be connected for interrupts, etc., for more information see the 7366 datasheet.

As seen in the diagram, the 7366 requires an external clock. Instead of using a crystal as shown, one could also supply an external clock pulse to the fCKI pin. For more information and frequency requirements, see the datasheet.

Code

The code consists of 4 files:

  • 7366.h: A header file containing various defines
  • 7366_lib.c: A C file containing functions that interact with the 7366
  • 7366_decoder.c: A C file containing main() and a simple test program

All three can be downloaded here.

7366_lib.c

This file contains library functions for using the LS7366R. The most relavent functions are enable_7366(), write_7366(), and read_7366(). The configuration flags are listed in the 7366.h file. Note that the read and write functions are passed pointers to arrays to read from or write to. This uses the SPI1 module.

/********************************************************************
 * 7366_lib.c : Library functions for using the 7366R SPI decoder
 *
 * Creation date: 16-APR-2010
 * Last modified: 27-APR-2010
 *
 * Thomas Peterson
 * LIMS
 ********************************************************************/
 
#include <plib.h>
#include <peripheral/generic.h>
#include <peripheral/spi.h>

#include "_spi_map_tbl.h"
#include "7366.h"


/* enable_7366(): enables counting on 7366, sends configuration words
     and optionally turns on flags */
void enable_7366(int config1, int flags) {
 unsigned char writebyte;
 writebyte = config1; //config word 1
 write_7366(MDR0,&writebyte);
 writebyte = flags | (4-COUNTER_BYTES); //config word 2
 write_7366(MDR1, &writebyte);
}

void disable_7366_counting() {
 //send disable to MDR1
 unsigned char writebyte;
 writebyte = COUNTER_BYTES;
 write_7366(MDR1, &writebyte);
}

/* write_7366(): Writes bytes in array "bytearray" to register "reg".
     The number of bytes written depends on the reister and mode.
     Config registers will write 1 byte. Other registers will
     write the number of bytes specified by COUNTER_BYTES */
void write_7366(int reg,unsigned  char *bytearray) {
 unsigned char ir = (0x2 << 6 ) | (reg << 3); //Instruction
 unsigned char ReadData;
 if ( (reg == MDR0) || (reg == MDR1) || (reg == STR) ) {
  //One byte to write
  volatile SpiRegMap* pReg=SpiMapTbl[SPICHN-1];
  SLAVE_SELECT = 0;
  delay();     //Setup time
  SpiChnPutC(SPICHN, ir);   //Write instruction after TX buffer empty
  while (pReg->stat.SPIRBF == 0); //Wait for RX buffer full
  ReadData = SpiChnGetC(SPICHN); //Read what was clocked in during last write (nothing)
  SpiChnPutC(SPICHN, bytearray[0]);   //Clock out write byte after TX buffer empty
  while (pReg->stat.SPIRBF == 0); //Wait for RX buffer full
  ReadData = SpiChnGetC(SPICHN); //Read what was clocked in during last write (garbage /dont care)
  SLAVE_SELECT = 1;  //End comm
  return;
 }
 if ( (reg == DTR) || (reg == CNTR) || (reg == OTR) ) {
  //1-4 bytes to read
  volatile SpiRegMap* pReg=SpiMapTbl[SPICHN-1];
  SLAVE_SELECT = 0;
  delay();      //Setup time
  SpiChnPutC(SPICHN, ir);   //Write instruction after TX buffer empty
  while (pReg->stat.SPIRBF == 0); //Wait for RX buffer full
  ReadData = SpiChnGetC(SPICHN); //Read what was clocked in during last write (nothing)
  //Do reads
  int i;
  for (i=0;i<COUNTER_BYTES;i++) {
   SpiChnPutC(SPICHN, bytearray[i]);   //Clock out byte after TX buffer empty
   while (pReg->stat.SPIRBF == 0); //Wait for RX buffer full
   ReadData = SpiChnGetC(SPICHN); //Read what was clocked in during last write (don't care)
  } 
  SLAVE_SELECT = 1;  //End comm
  return;
 }
}


/* read_7366(): Reads bytes to array "bytearray" from register "reg".
     The number of bytes read depends on the reister and mode.
     Config registers will read 1 byte. Other registers will
     read the number of bytes specified by COUNTER_BYTES */
void read_7366(int reg, unsigned char *bytearray) {
 unsigned char ir = (0x1 << 6 ) | (reg << 3); //Instruction
 unsigned char ReadData;
 if ( (reg == MDR0) || (reg == MDR1) || (reg == STR) ) {
  //One byte to read
  volatile SpiRegMap* pReg=SpiMapTbl[SPICHN-1];
  SLAVE_SELECT = 0;
  delay();     //Setup time
  SpiChnPutC(SPICHN, ir);   //Write instruction after TX buffer empty
  while (pReg->stat.SPIRBF == 0); //Wait for RX buffer full
  ReadData = SpiChnGetC(SPICHN); //Read what was clocked in during last write (nothing)
  SpiChnPutC(SPICHN, 0);   //Clock out dummy byte after TX buffer empty
  while (pReg->stat.SPIRBF == 0); //Wait for RX buffer full
  ReadData = SpiChnGetC(SPICHN); //Read what was clocked in during last write (register)
  *bytearray = ReadData;
  SLAVE_SELECT = 1;  //End comm
  return;
 }
 if ( (reg == DTR) || (reg == CNTR) || (reg == OTR) ) {
  //1-4 bytes to read
  volatile SpiRegMap* pReg=SpiMapTbl[SPICHN-1];
  SLAVE_SELECT = 0;
  delay();     //Setup time
  SpiChnPutC(SPICHN, ir);   //Write instruction after TX buffer empty
  while (pReg->stat.SPIRBF == 0); //Wait for RX buffer full
  ReadData = SpiChnGetC(SPICHN); //Read what was clocked in during last write (nothing)
  //Do reads
  int i;
  for (i=0;i<COUNTER_BYTES;i++) {
   SpiChnPutC(SPICHN, 0);   //Clock out dummy byte after TX buffer empty
   while (pReg->stat.SPIRBF == 0); //Wait for RX buffer full
   ReadData = SpiChnGetC(SPICHN); //Read what was clocked in during last write (register)
   bytearray[i] = ReadData;
  } 
  SLAVE_SELECT = 1;  //End comm
  return;
 }
}

/* clear_reg_7366(): Clears the given register */
void clear_reg_7366(int reg) {
 char ReadData;
 volatile SpiRegMap* pReg=SpiMapTbl[SPICHN-1];
 char ir = (reg << 3); //Instruction
 SLAVE_SELECT = 0;
 delay();     //Setup time
 SpiChnPutC(SPICHN, ir);   //Write instruction after TX buffer empty
 while (pReg->stat.SPIRBF == 0); //Wait for RX buffer full
 ReadData = SpiChnGetC(SPICHN); //Read what was clocked in during last write (nothing)
 SLAVE_SELECT = 1;
}

/* 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");
}

7366_decoder.c

This file contains a simple test function that sets up the counter and reads from it periodically. Note that it sets up the 7366 using the enable_7366 function and sets it up in the x4 and modulo-n modes, which are generally used for motors. In modulo-n mode, the encoder high count value (the number of tics in a full rotation) is loaded into DTR, as seen.

During the main loop, the program calls read_7366(CNTR, readbuf), which reads the current counter value by writing it into readbuf. Note that since the counter is in 3 byte mode (as defined in the 7366.h file) the function will read 3 bytes into the array pointed to by readbuf.

/********************************************************************
 * 7366_decoder.c : Interface between the 7366R decoder by LSI/CSI
 *     and the PIC32. The 7366R uses the SPI interface
 *     to communicate with the microcontroller.
 *
 * Creation date: 15-APR-2010
 * Last modified: 27-APR-2010
 *
 * Thomas Peterson
 * LIMS
 ********************************************************************/

#include "HardwareProfile.h"
#include "7366.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
 //RF13 has been set to slave select in 7366.h
 TRISFbits.TRISF13 = 0; //Output for slave select
 SLAVE_SELECT = 1; //Start unselected (slave select is active low)

 //UART setup
 setup_rs232(pbClk);

 //SPI setup
 int rData = SPI1BUF;    //Clears receive buffer
 IFS0CLR = 0x03800000;   //Clears any existing event (rx / tx/ fault interrupt)
 SPI1STATCLR = 0x40;      //Clears overflow
 //Enables the SPI channel (channel, master mode enable | use 8 bit mode | turn on, clock divider)
 SpiChnOpen(1, SPI_CON_MSTEN | SPI_CON_MODE8 | SPI_CON_ON|SPI_CKE_ON, 128);   // divide fpb by 128, configure the I/O ports.

 //Init 7366R
 enable_7366(QUADRATURE_X4 | MODULO_N | DISABLE_INDX,NO_FLAGS);
 
 unsigned char writebuf[COUNTER_BYTES];
 //Note that MSB should be in slot 0
 writebuf[1] = 0x58;
 writebuf[0] = 0x98;
 //Sets up DTR to contain 0x9858 = 39,000 (ticks/rev)
 write_7366(DTR, writebuf);
 clear_reg_7366(STR);
 
 //Read periodically and send encoder value
 unsigned char readbuf[COUNTER_BYTES];
 unsigned char strbuf;
 char string[40];
 while(1) {
  Delayms(250); //Wait 1/4 sec.
  read_7366(CNTR, readbuf);
  sprintf(string,"Read from CNTR: %x%x\r\n", readbuf[0], readbuf[1]);
  putsUART2(string);
 }