LS7366 quadrature counter

The LS7366 is a 32 bit counter with a direct interface for quadrature signals from incremental encoders. There is also an index channel for marker functions. The interface to the microcontroller is SPI mode 0 making it relatively easy to drive with a variety of common controllers. Decimus has one of these on each motor channel connected to the encoders on the back of the Faulhaber 2224 coreless motors. There is very little information about these chips available except the data sheet. This is how they are used on Decimus.Interface to the encoders is very easy as the phase inputs can be connected directly to the chip. A clock signal must be provided for the counter and I used the same 8MHz oscillator that drives the processor on Decimus. the clock will need to be less than 40MHz in a 5V system and must be greater than 4 times the maximum count frequency. Even at a speed of 5m/s Decimus will only be generating encoder pulses at about 140kHz so there is plenty of margin.The counter is very flexible and can be configured for x1, x2, x4 and non-quadrature counting and the counter register may be set to 8, 16, 24 or 32 bits in size. Selecting x1 decoding will give 512 counts per revolution of the motor shaft and 2048 counts per revolution of the wheel. For this application, the counter width will be set to 8 bits. This is the fastest to read back from the counter and has sufficient range for my needs. With a single byte counter, the maximum difference in counts that can be reliably detected between two reads is 127. Since each count on Decimus is 24mm * pi / 512 * 4 or about 0.04mm. The control loop runs at 1KHz so the maximum recordable speed would be 127 * 0.04mm / 1ms which is around 5m/s. Practically speaking, the mouse would almost never be able to reach such a speed in the maze and the top speed will be limited to no more than 4m/s by the control loop.Configuring the chip is a question of sending a number of values to internal registers over the SPI. Here is a code fragment for the mouse initialisation:

CNTR_LEFT_SEL = 0;                              // start a command
SPI_putc(WR + MDR0);                            // write MDR0
SPI_putc(CLOCK_DIV_2 + FREE_RUN + CNT_MODE_X1); // free-running, x1 decoding
CNTR_LEFT_SEL = 1;                              // end this command
asm("nop");asm("nop");asm("nop");               // delay between commands
CNTR_LEFT_SEL = 0;                              // start next command
SPI_putc(WR + MDR1);                            // write MDR1
SPI_putc(COUNT_ENABLE + CNTR_WIDTH_1);          // enabled, single-byte
CNTR_LEFT_SEL = 1;                              // end command
asm("nop");asm("nop");asm("nop");               // delay between commands
CNTR_LEFT_SEL = 0;                              // start next command
SPI_putc(CLR + CNTR);                           // clear the counter
CNTR_LEFT_SEL = 1;

The only real ‘gotcha’ was not noticing that after each command the select line must be raised to complete the command then lowered again for the next command. There is a minimum time the line must be held high, hence the ‘NOP’ instructions. The setup and hold times for the select line are easily met by the overhead of the function call for the SPI_putc() function.Reading the counter is also quite straightforward. A command is sent to read the counter. This transfers the current counter value to the output register and the next write to the chip clocks out that register. You must perform as many reads as there are bytes. Remember that the data comes out MSB first so you can’t just read the low bytes and ignore the rest. Like all SPI systems, a byte has to be sent so that you can get a byte back as transfers occur simultaneously in both directions. So for example, if the counter were set up for a 16 bit width, the current value can be read with code like this:

CNTR_LEFT_SEL = 0;
SPI_putc(RD + CNTR);      // transfer CNTR to OTR and prepare to read
temp1 = SPI_putc(0x00);   // send a dummy byte to get the first byte back
temp1 = (temp1 << 8);     // we get the high byte first
temp1 += SPI_putc(0x00);  // another dummy byte gets the lower byte returned
CNTR_LEFT_SEL = 1;

This is fairly quick. Reading both encoders in 16 bit mode takes just under 30us on Decimus with the SPI running a 4MHz clock.

This entry was posted in Micromouse and tagged , , , . Bookmark the permalink.

20 Responses to LS7366 quadrature counter

  1. Have you interfaced both the quadrature counter chips to the same SPI port?

  2. peteh says:

    Yes, the only difference in the code is that CNTR_LEFT_SEL becomes CNTR_RIGHT_SEL for the other wheel.

    Each counter has its own select line.

  3. Siddharth Chinoy says:

    //I used the same 8MHz oscillator that drives the processor

    Did you share the same crystal between the two quadrature decoders and the uC or did you have different crystals with the same value?

  4. peteh says:

    There is an 8MHz Oscillator module that provides a single clocke used for the processor and both the counters.

  5. Olaf Schumann says:

    Hi,

    where did you buy the LS7366. Seems to be a very interesting chip, but I can’t find it. At least not in good old germany.

    sincerely
    Olaf

  6. dark knight says:

    hi…. i want to know how to interfae ls7366r with arm9 processor…
    Please help me …..

  7. peteh says:

    I have not used this device with any other processor. It is quite simple to use so there should not be any great problem I would think.

  8. Anonymous says:

    HI peter. I am having a really difficult time trying to set this up. I am using the dspic30f6015 as well. Any way you could help?

  9. peteh says:

    Can you post more details. Schematic, code, whatever…

  10. Anonymous says:

    I am following the schematics of decimus. as for the code, here it is :


    #include "p30f6015.h"
    #include "delay.c"/// WHEELS 12MM THICK 27-28 mm diater
    #include "utilities.c"
    #include "adc.c"
    #include "motor.c"
    #include "led.c"
    #include "sensor.c"
    #include "button.c"
    #include "uart.c"

    int temp1;

    char putc(char d) {
    //SPI2IF = 0;
    SPI2BUF = d; // send character
    while (!SPI2STATbits.SPITBF); // wait until sent
    //SPI2STATbits.SPIROV=0;
    delay_us(40);
    return SPI2BUF; // and return the received character
    }

    char readc(char d) {
    putc(d);
    putc(0x00);
    return SPI2BUF;
    }

    int main() {
    initled();
    // initmotors();
    initadc();
    initsens();
    inituart();

    TRISE = 0;
    LATEbits.LATE6 = 1;
    LATEbits.LATE5 = 1;
    SPI2STATbits.SPIEN = 0;
    SPI2STATbits.SPISIDL = 0;
    //SPI2CONbits.DISSCK = 0;
    SPI2CONbits.DISSDO = 0;
    SPI2CONbits.MSTEN = 1;
    SPI2CONbits.MODE16 = 0;
    SPI2CONbits.SMP = 1;
    SPI2CONbits.CKE = 1;
    SPI2CONbits.SSEN = 0;
    SPI2CONbits.CKP = 0;
    SPI2CONbits.SPRE = 0;
    SPI2CONbits.PPRE = 0;

    SPI2STATbits.SPIEN = 1;
    //SPI2CON = 0x0130; SHOULD I USE THIS?

    LATEbits.LATE6 = 0; // start a command
    delay_us(100);
    putc(0x088); // write MDR0
    putc(0x081); // free-running, x1 decoding
    LATEbits.LATE6 = 1; // end this command
    delay_us(100); // delay between commands
    LATEbits.LATE6 = 0;
    delay_us(100); // start next command
    putc(0x090); // write MDR1
    putc(0x003); // enabled, single-byte
    LATEbits.LATE6 = 1; // end command
    delay_us(100); // delay between commands
    LATEbits.LATE6 = 0;
    delay_us(100); // start next command
    putc(0x020); // clear the counter
    LATEbits.LATE6 = 1;

    blink2();

    while (1) {
    LATEbits.LATE6 = 0;
    delay_us(100);
    readc(0x060);
    temp1 = SPI2BUF;
    LATEbits.LATE6 = 1;
    delay_us(100);

    ledon(1);

    putcUART(temp1);
    }
    return 0;
    }

    The problem is that temp1 always returns a value of 0 when output on a terminal. I hope that you could help me as this is the last road block for me in terms of the peripherals. Thanks in advance

  11. peteh says:

    I believe you need to clear the SPIROV flag before doing anything else when using the SPI peripheral. Here is my code for the read/write funtion:


    int SPI2_rw(int c) {
    int temp;
    if (spi2Enabled == 0) return 0;
    temp = (int) SPI2BUF; // clears the SPIROV flag if set
    SPI2BUF = (unsigned) c;
    while (SPI2STATbits.SPIRBF == 0);
    return (int) SPI2BUF;
    }

    And this is the code fragment to read one encoder:


    int temp1;
    CNTR_LEFT_SEL = 0;
    (void)SPI2_rw( RD + CNTR ); // xfer CNTR to OTR and prepare to read
    temp1 = (int)SPI2_rw( 0x00 ) & 0xff;
    temp1 = ( temp1 < < 8) + ((int)SPI2_rw( 0x00 ) & 0xff);
    CNTR_LEFT_SEL = 1;

  12. Anonymous says:

    WIll try that out. Hope it works!

  13. Anonymous says:

    Hi peter. It seems to be working. However, i try something like , while(temp1 < 200) then turn on my motors then if its >200 i will turn the motors off. However, the readings from the UART output do not correspond. FUrthermore , they are constantly varying with large difference between two values. Any ideas why? by the way, to count the current encoder clicks, i use the code

    *after sending read command and sending dummy byte

    temp1 = SPI#BUF;

    is there anything wrong with this?

  14. peteh says:

    I have the counter chips set to do 16 bit counting. you are using 8 nbit counting. Make sure all the variables are of a suitable type and range.

    Turn the motors over slowly by hand so that the counts are very small and check that you get sensible results.

    Only when you are sure the counters are configured correctly, turn on the motors at various speeds and make sure the results from your counters are consistent.

    Other places to look:

    The time delays in your code may not be running corectly – possibly because of interrupts or because of optimisation.

    Unless you sample the counters at exact time intervals (I do it in a 1ms timer interrupt), the results will never be consistent.

  15. Anonymous says:

    I shall try out 16 bit spi though I’m not very sure how to do it. (int is 16 bits, am I right?)

    How do you set up the interrupts?

    Also do you use x4 pll for your normal processing (which equates to 32 MHz) ?

    Thank you

  16. Anonymous says:

    Hi peter. I seem to have gotten the encoders working ok. but 2 problems.

    1) I dont want an up down count cycle. I just want an up count for both directions. How?

    2) After a half revolution (0xff) it resets back to 0. Whats Wrong?

    Thanks in Advance

    Regards

  17. peteh says:

    Well, it seems you have it nearly done now.

    What did you do to make it work? Others following this thread may have the same issues.

    First, I would suggest that youdo want up/down counting. To get a distance travelled between queries, just keep a record of the previous count value and subtract that from the new one. You can then chgoose to leave this as it is for a positive or negative distance or take the absolute value if you don’t care about the direction.

    Second, the count will wrap around because you are using only an eight bit count. Try setting it up for 16 bits.

    Whatever you set the encoder up as, you will need to be reading it at regular, fixed intervals and keeping your own count of the amount of movement. Even if the counter were set up to use 32 bits, sooner or later it would wrap around. For my application, that would be after 86km but even so…

  18. Anonymous says:

    I tried is but to no avail.

    My problem with the updown count is that from rest, in one direction its counts to 1,2,3, etc. and in the other direction, its counts 0xff downwards. So its somewhat a problem.

    As for the 16 bits. I set it to 2byte mode. However, now, one revolution is only one or two counts. Very very frustrating.

  19. peteh says:

    That is just what it should do.

    I think your hardware may be exactly right. Can you send your complete code to me in an email?

Leave a Reply