Continue to Site

Welcome to EDAboard.com

Welcome to our site! EDAboard.com is an international Electronics Discussion Forum focused on EDA software, circuits, schematics, books, theory, papers, asic, pld, 8051, DSP, Network, RF, Analog Design, PCB, Service Manuals... and a whole lot more! To participate you need to register. Registration is free. Click here to register now.

[PIC] SPI synchronization problem

Status
Not open for further replies.

flukeco

Junior Member level 1
Junior Member level 1
Joined
Apr 25, 2014
Messages
16
Helped
0
Reputation
0
Reaction score
0
Trophy points
1
Activity points
180
Hi everyone,

I'm trying to implement simple CAN bus monitoring system where slave read messages from CAN bus and send those bytes to master via SPI protocol.

I have CAN working correctly, however, I'm having problem with SPI.

Currently, I have the master continuously reading data from slave, so master get 0x00 if slave doesn't write anything.

When slave receives a CAN message, it first write a signature byte (character 'S') follows by the message (8-bytes in length) to master.

When master receives character 'S' from slave, it assume that the next 8 bytes should be the message. So the master perform SPI read in the for loop and keep each byte in array.

The problem is that master cannot receive character 'S' sent by slave most of the time.

The reason I use the signature byte to indicate the beginning of message transmission is because I don't know how to implement a blocking SPI read function where the function will not return until slave perform SPI write.

Also since this is a monitoring system, master cannot expect time range in which slave will send the data to it. So I think there might be a synchronization problem between master and slave.

Do you think the blocking SPI read is needed or do you have any other suggestions? Any help would be highly appreciated.

The following is the code:

I simplify the master and slave code to make it easy to read. I've already test the simplified code and it have the same problem as explained above.

Master super loop:

Code:
    while (1) {
        rx = SPI_read();
        if (rx == 'S') {
            for (i = 0; i < 8; i++) {
                rx = SPI_read();
                printf("%02x ", rx);
                delay_us(1);
            }
            printf("\r\n");
        }
    }

Slave super loop:

Note: To make the code easy to read. I write 0x00 - 0x07 to master instead of the CAN message. The 1 sec delay is to imitate the time range between each CAN message.

Code:
    while (1) {
        // while(!CAN_read(&message));
        SPI_write('S');
        for (tx = 0x00; tx < 0x08; tx++) {
            SPI_write(tx);
        }
        delay_ms(1000);
    }

SPI function:

Code:
void SPI_write(unsigned char data) {
    unsigned char TempVar;
    TempVar = SSPBUF;           /* Clears BF */
    PIR1bits.SSPIF = 0;         /* Clear interrupt flag */
    SSPCON1bits.WCOL = 0;	/* Clear any previous write collision */
    SSPBUF = data;              /* clock out data */
    while(!PIR1bits.SSPIF);     /* wait until transmission completed */
    PIR1bits.SSPIF = 0;         /* clear interrupt flag bit */
}

unsigned char SPI_read(void) {
    unsigned char TempVar;
    TempVar = SSPBUF;           /* Clear BF */
    PIR1bits.SSPIF = 0;         /* Clear interrupt flag */
    SSPBUF = 0x00;              /* initiate bus cycle */
    while(!PIR1bits.SSPIF);     /* wait until cycle complete */
    return (SSPBUF);            /* return with byte read */
}
 

I don't know how to implement a blocking SPI read function where the function will not return until slave perform SPI write.
That's simply not possible. The master will receive the data that the slave has put into SSPBUF before the spi_read transaction starts. There's no other way to detect an actual answer than looking at the received data.

Usually a SPI link uses a slave select signal for frame synchronization. I suppose it would be helpful in your aoolication.
 
Hi FvM,

Thank you for your answer. Now I enabled SS control on both master and slave side, but still have same problem. Master doesn't detect the start of frame sent by slave (in this case, the character 'S') most of the time.

FYI, I'm using PIC18F2580 for both master and slave. The SS pin (RA5) on master is connected to SS pin (RA5) on slave.
Other SPI pins are connected correctly (master SDO -> slave SDI, slave SDO->master SDI, master SCK -> slave SCK, ...).

Here is the current code. If you spot anything wrong or have any suggestions, please tell me. Your help will be highly appreciated.

Master:
Code:
while(1) {
 
    LATAbits.LA5 = 0; /* set CS low to start SPI transmission/reception */

    rx = SPI_read();
    if (rx == 'S') {
        for (i = 0; i < 8; i++) {
            rx = SPI_read();
            printf("%02x ", rx);
            delay_us(1);
        }
        printf("\r\n");
    }
    delay_us(1);

    LATAbits.LA5 = 1; /* set CS high to stop SPI transmission/reception */
 
}

Slave:
Code:
    while (1) {
        SPI_write('S');
        for (tx = 0x00; tx < 0x08; tx++) {
            SPI_write(tx);
        }
        delay_ms(1000);
    }

SPI functions:
Init
Code:
/* SMP: Sample bit
 * SPI Master mode:
 * 1 = Input data sampled at end of data output time
 * 0 = Input data sampled at middle of data output time
 * SPI Slave mode:
 * SMP must be cleared in slave mode
 */
#define _SMP 0

/* CKE: SPI Clock Select bit
 * 1 = Transmit occurs on transition from active to idle clock state
 * 0 = Transmit occurs on transition from idle to active clock state
 */
#define _CKE 0

/* CKP: Clock Polarity Select bit
 * 1 = Idle state for clock is a high level
 * 0 = Idle state for clock is a low level
 */
#define _CKP 1

/* Synchronous Serial Port Mode Select bits
 * 0101 = Slave mode, clock = SCK pin, SS pin disabled (can be used as I/O pin)
 * 0100 = Slave mode, clock = SCK pin, SS pin enabled
 * 0011 = Master mode, clock = TMR2 output/2
 * 0010 = Master mode, clock = Fosc/64
 * 0001 = Master mode, clock = Fosc/16
 * 0000 = Master mode, clock = Fosc/4
 */
#define _SSPM_MASTER 0b0001
#define _SSPM_SLAVE 0b0101

void SPI_init(unsigned char mode) {
    ADCON0 = 0x00; /* disable AD converter module */
    ADCON1 = 0x0F; /* set all port A pins, including SS (RA5), as digital I/O */

    TRISA = 0x00; /* set all port A pins as output pins */
    LATA = 0xFF; /* set all port A bits to disable SPI */

    TRISC = 0b00010000; /* Set SCK pin (RC3) as output
                                 * SDI pin (RC4) as input
                                 * SDO pin (RC5) as output */
    if (mode == 1) {
        TRISCbits.RC3 = 1; /* In slave mode,
                                 * Set SCK pin (RC3) as input */
        TRISAbits.RA5 = 1; /* Set SS pin (RA5) as input */
    }

    SSPSTAT = 0b00000000; /* Initialize SSPSTAT register to POR state */
    SSPSTATbits.SMP = _SMP; /* Configure sample bit */
    SSPSTATbits.CKE = _CKE; /* Configure clock select bit */

    SSPCON1 = 0b00000000; /* Initialize SSPCON1 register to POR state */
    SSPCON1bits.CKP = _CKP; /* Configure clock polarity select bit */
    SSPCON1bits.SSPEN = 1; /* Enable SPI (set SCK, SDO, SDI, and SS as serial port pins)*/

    if (mode == _MASTER_MODE)
        SSPCON1bits.SSPM = _SSPM_MASTER; /* Configure sync mode bits for master */
    else
        SSPCON1bits.SSPM = _SSPM_SLAVE; /* Configure sync mode bits for slave */
}

Read and write:
Code:
void SPI_write(unsigned char data) {
    unsigned char TempVar;
    TempVar = SSPBUF;           /* Clears BF */
    PIR1bits.SSPIF = 0;         /* Clear interrupt flag */
    SSPCON1bits.WCOL = 0;	/* Clear any previous write collision */
    SSPBUF = data;              /* clock out data */
    while(!PIR1bits.SSPIF);     /* wait until transmission completed */
    PIR1bits.SSPIF = 0;         /* clear interrupt flag bit */
}

unsigned char SPI_read(void) {
    unsigned char TempVar;
    TempVar = SSPBUF;           /* Clear BF */
    PIR1bits.SSPIF = 0;         /* Clear interrupt flag */
    SSPBUF = 0x00;              /* initiate bus cycle */
    while(!PIR1bits.SSPIF);     /* wait until cycle complete */
    return (SSPBUF);            /* return with byte read */
}

Best Regards,
Pat
 

The slave control is still completely wrong.

The slave should enable SS pin control and additionally monitor the SS pin in software. It must not perform arbitrary delays like delay_ms(1000), consider that the transmission timing can be only controlled from the master side.

On the slave side you can only act based on SS and SSPBUF state.
 

    V

    Points: 2
    Helpful Answer Positive Rating
Hi FvM,

Thank you so much again for your suggestion.

You are right. I forgot to change SSMP bits setting for slave to enable SS control. It should be set to 0b100. Thank you for pointing that out.

When you say monitor SS pin in software, does simply polling SS pin at slave side before performing the writes would work? Or I need to do more than that?

Code:
    while (1) {
        while (SS == 1);
        SPI_write('S');
        for (tx = 0x00; tx < 0x08; tx++) {
            SPI_write(tx);
        }
        //delay_ms(1000);
    }

Best Regards,
Pat
 

From what I can see your master is not doing things correct. To read something from the slave the master has to transmit a byte and then read what the slave sent. Masters transmit and slave transmit use the same clock cycles to transfer the data, but use different flanks of the clock pulse to read the result.
The idea with the SS-signal is to start the slave at the same clock cycle as the master. Depending on the speed of the slave and how fast the master start transmitting, the first byte may be scrap data, due to the fact that the slave need some time to write the first byte to the controller. If there is a posibillity to preload the first data byte, and sync with the SS in hardware, this would reduce the reaction time.

I always use interrupt for these things, so I can't evaluate how the functions displayed work when polled.

Working with MCU slaves can be a pain until you manage to understand what is going on. A master and a slave is two completely different beasts in this case.
 
To read something from the slave the master has to transmit a byte and then read what the slave sent.
That's what spi_read() does. It's sending 0x00 as dummy data. There are detail problems like the usage of the start character, but I don't see a basic problem with the master code, provided the slave can keep the byte rate.
 
Hi all,

Could you please guide me the proper way to implement slave code in this case. I've tried polling SS before every write at slave side, still master cannot get every bytes correctly from slave.

Best Regards,
Pat
 

That's what spi_read() does. It's sending 0x00 as dummy data. There are detail problems like the usage of the start character, but I don't see a basic problem with the master code, provided the slave can keep the byte rate.

Yes, I see that now. This way to do it is a bit alien to me, since I always use to exchange outgoing and ingoing data at the same time. This could be implemented in the write routine, so you get a received byte when you send one.
 

Status
Not open for further replies.

Similar threads

Part and Inventory Search

Welcome to EDABoard.com

Sponsor

Back
Top