Using the LS7366R SPI Quadrature Counter
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
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); }