SPI Master Control of BME280 on PSoC6

Tip / Sign in to post questions, reply, level up, and achieve exciting badges. Know more

cross mob
TeMa_1467596
Level 5
Level 5
5 sign-ins 5 likes given First like received

I have the CY8CKIT-062-BLE connected to a custom PCB that has a BME280 and another SPI chip on 2 different CS pins.

The SCB block that this is running from is called SPI_1 and it's set up as a SPI Master with 2 CSs and the pin allocation is all green as follows:

SPI_1a.PNG

I downloaded the example drivers from GIThub here GitHub - BoschSensortec/BME280_driver: Bosch Sensortec BME280 sensor driver

and I have added the following files to my project:

bme280.c

bme280.h

bme280_defs.h

I'm trying to follow the instructions to dovetail the necessary links to the bme280 code so that it triggers the SPI commands (see the first page of the GIThub link above) as follows

Example for SPI 4-Wire

struct bme280_dev dev; 
int8_t rslt = BME280_OK; 

/*
Sensor_0 interface over SPI with native chip select line */

dev.dev_id = 0;
dev.intf = BME280_SPI_INTF;
dev.read = user_spi_read;
dev.write = user_spi_write;
dev.delay_ms = user_delay_ms; 

rslt = bme280_init(&dev);

but I'm unclear as to what to put in place of the user_spi_read, user_spi_write, and user_delay_ms.  I think the delay is just CyDelay(); but it won't just take that function.

I can't find in the SPI documentation where the calls are for read and write and I'm unclear whether the CSs are just under my control and I take them low and high as needed.  Also if I include the ISR, is the ISR called when a byte is received or what?

Any help on this is much appreciated.

Ted

0 Likes
1 Solution

Ted,

Did you make sure the SPI interrupt is mapped to M0+ or M4 (the core that uses the driver) and the interrupt is enabled? The SPI driver should not get stuck if interrupt is properly enabled/configured and SPI is started. SPI does not have any ACK mechanism like I2C for getting stuck. So I would not expect it to be stuck there.

You can add a simple timeout like below -

uint32 timeout = 10000; //in us

if (status == CY_SCB_SPI_SUCCESS) 

    {                

        while ((CY_SCB_SPI_TRANSFER_ACTIVE & SPI_0_GetTransferStatus()) && (timeout != 0))

{

timeout--;

Cy_SysLib_DelayUs(1);


    }

if(timeout == 0)

{

//return HW timed out

}

Regards,

Meenakshi Sundaram R

View solution in original post

0 Likes
11 Replies
TeMa_1467596
Level 5
Level 5
5 sign-ins 5 likes given First like received

OK, I have found templates at the bottom of the first linked github page so I'm working through that now.....

0 Likes

Hi Ted,

Yes, you are correct, the templates for the function pointers are available in the github page.

In general, you can refer to Code example CE221120 (http://www.cypress.com/documentation/code-examples/ce221120-psoc-6-mcu-spi-master) and PDL documentation (Right click on component-> Open PDL documentation)

I see that you are using Port 8 for SPI communication in the CY8CKIT-062 BLE kit. Port 8 is used for CapSense sliders and buttons in the kit. So, please use other SCB ports (like Port 10 or 12).

You can control the CS line in firmware and not the SCB block. You can use two output pins (strong drive mode) and drive those pins low (CS enable), before a transaction occurs.

Drive the pins high (CS disable), after the transaction is completed.

For ISR based SPI master, you can refer to Low_Level_User_isr_SPI_Master code example.

In case, you want an interrupt to get triggered when 1 byte is received, you can set the trigger level to 1, by using Cy_SCB_SetRxFifoLevel() API.

I hope this helps.

Thanks,
Shanmathi

Shanmathi,

Thanks for the help, I have a lot of things going on on this board and I don't need Capsense so I am planning on adding and removing resistors to change the functionality of P8 to GPIO as per the PSoC6 BLE Pioneer Kit Guide (table 1-2) - e.g. P8_0 - Replace R31 with zero Ohm and populate R34 to connect to J20_1 header and remove proximity functionality.

I'm going to try to get the SPI working today, a few questions:

  1. If I don't use the SPI ISR, what's the best read/write commands to use?
  2. I can see that using the ISR means that I don't have to have my program wait for data to come back on the SPI but how do I deal with a request for multiple (5) bytes if the ISR fires for each byte received?
  3. Even though I define the CS (sso,ss1)  pins in the component, do I still control them manually or is there something automatic going on?

Thanks,

Ted

0 Likes

So I tried to wrap my mind around this but I've failed.  I've done I2C and SPI before but this is weird.

the function that's called user_spi_read ias passed a parameter called reg_addr which needs to be sent to the SPI, the subsequnt frames in SPI are going to return the required red_data up to the len of requested - this is what I have so far but it doesn't send the reg address out, how do I specify that?

int8_t user_spi_read(uint8_t dev_id, uint8_t reg_addr, uint8_t *reg_data, uint16_t len)

{

    int8_t rslt = 0; /* Return 0 for Success, non-zero for failure */

    /*

     * The parameter dev_id can be used as a variable to select which Chip Select pin has

     * to be set low to activate the relevant device on the SPI bus

     */

    if (dev_id) { // if Dev ID > 0

        Cy_GPIO_Write(SPI0_nCS_Sag_PORT, SPI0_nCS_Sag_NUM, 0); // Select Sag CS

    }

    else {        // else it's zero

        Cy_GPIO_Write(SPI0_nCS_Baro_PORT, SPI0_nCS_Baro_NUM, 0); // Select Baro CS

    }

    //Cy_SCB_SPI_SetActiveSlaveSelect(SPI_0_HW, 0); // for now

   

    // TM do I need to change the parmateres to the correct type before passing?

    rslt = SPI_0_ReadArray(reg_data, len); // TM <<<<< THIS IS NOT GOING TO WORK, STILL UNDER DEVELOPEMENT

    /*

     * Data on the bus should be like

     * |----------------+---------------------+-------------|

     * | MOSI           | MISO                | Chip Select |

     * |----------------+---------------------|-------------|

     * | (don't care)   | (don't care)        | HIGH        |

     * | (reg_addr)     | (don't care)        | LOW         |

     * | (don't care)   | (reg_data[0])       | LOW         |

     * | (....)         | (....)              | LOW         |

     * | (don't care)   | (reg_data[len - 1]) | LOW         |

     * | (don't care)   | (don't care)        | HIGH        |

     * |----------------+---------------------|-------------|

     */

    Cy_GPIO_Write(SPI0_nCS_Sag_PORT, SPI0_nCS_Sag_NUM, 1); // Deselect Sag CS

    Cy_GPIO_Write(SPI0_nCS_Baro_PORT, SPI0_nCS_Baro_NUM, 1); // Deselect Baro CS

    return rslt;

}

0 Likes

Just looked back on my old projects to see how I did this before, I was using a PSoC4 and had simple low level commands like SPI_spiUartxxxx (see below).  These don't seem to be available to me for the component in PSoC6

u8_t LIS3DH_ReadReg(u8_t Reg, u8_t* Data) {

  uint8 junkByte = 0xFF;

  //To be completed with either I2c or SPI reading function

  //i.e. *Data = SPI_Mems_Read_Reg( Reg );

    // combine the R/W register address and the command into one uint8

  ACC_CS_Write(0);  // clear ACC_CS for access TM ???? do we want to clear other CS or use a SPI busy flag ???

    SPI_SpiUartClearTxBuffer();

    SPI_SpiUartClearRxBuffer();

    dataToSend = (Reg | READ);

    SPI_SpiUartWriteTxData(dataToSend);     // TM the real byte to send

    SPI_SpiUartWriteTxData(junkByte);     // TM a second byte that's just there to send another 8 bits out to clock the response back in

    // the above Write TX data commands have just put 2 bytes in the output buffer   

    // now we have to wait for data to come back

    // while (SPI_SpiUartGetRxBufferSize() >= 2); // loop until 2 bytes are received

    CyDelayUs(6); // 6 uS delay

    junkByte = SPI_SpiUartReadRxData();        // TM read junk data from the first received buffer

    *Data = SPI_SpiUartReadRxData();        // TM read data from the next received buffer

Can anyone put me out of my misery?

0 Likes

So I found the Cy_SCB_SPI_Transfer() command that looks like it might work, what I have come up with is as follows...

int8_t user_spi_read(uint8_t dev_id, uint8_t reg_addr, uint8_t *reg_data, uint16_t len)

{

    /*

     * Data on the bus should be like

     * |----------------+---------------------+-------------|

     * | MOSI           | MISO                | Chip Select |

     * |----------------+---------------------|-------------|

     * | (don't care)   | (don't care)        | HIGH        |

     * | (reg_addr)     | (don't care)        | LOW         |

     * | (don't care)   | (reg_data[0])       | LOW         |

     * | (....)         | (....)              | LOW         |

     * | (don't care)   | (reg_data[len - 1]) | LOW         |

     * | (don't care)   | (don't care)        | HIGH        |

     * |----------------+---------------------|-------------|

     */

   

    int8_t rslt = 0; /* Return 0 for Success, non-zero for failure */

    uint8_t tmTXbuffer[10];

    uint8_t i;

   

    tmTXbuffer[0] = reg_addr;

    for (i = 1; i < 9; i++) {

        tmTXbuffer = 0xFF;

    }

    // The parameter dev_id is used as a variable to select which Chip Select pin has to be set low to activate the relevant device on the SPI bus

    if (dev_id) { // if Dev ID > 0

        Cy_GPIO_Write(SPI0_nCS_Sag_PORT, SPI0_nCS_Sag_NUM, 0);      // Select Sag CS if dev_id > 0

    }

    else {        // else it's zero

        Cy_GPIO_Write(SPI0_nCS_Baro_PORT, SPI0_nCS_Baro_NUM, 0);    // Select Baro CS if dev_id == 0

    }

    Cy_SCB_SPI_Transfer(SPI_0_HW, tmTXbuffer, reg_data, len, &SPI_0_context);

    Cy_GPIO_Write(SPI0_nCS_Sag_PORT, SPI0_nCS_Sag_NUM, 1); // Deselect Sag CS

    Cy_GPIO_Write(SPI0_nCS_Baro_PORT, SPI0_nCS_Baro_NUM, 1); // Deselect Baro CS

    return rslt;

}

But I'm not doing anything with rslt and I'm not sure whether I need to use this with an ISR???

0 Likes

Ted,

This will not work as the Transfer API works in the background using interrupts. So if you deselect the CS, then the transfer is fail.

You can try the below implementation - I had successfully tried this implementation with an invensense motion sensor. In the below example, the name of the SPI master component is SPIm_MPU. And I was using firmware controlled GPIO (not SPI CS line) for CS selection/deselection.

static uint8_t txBuf[60];

    static uint8_t rxBuf[60];

    unsigned long User_SPI_Write(unsigned char dev_id, unsigned char RegisterAddr, unsigned char RegisterLen, unsigned char *RegisterValue)

    {

       

        int32 i;

        if (dev_id == MY_DEVICE_1)

        {

            //SELECT MY_DEVICE_1;

            txBuf[0] = RegisterAddr;

           

            for(i = 1; i<= RegisterLen; i++)

            {

                txBuf = RegisterValue[i-1];

            }

           

            status = SPIm_MPU_Transfer(txBuf, rxBuf, RegisterLen+1);

           

            if (status == CY_SCB_SPI_SUCCESS)

            {

                while (CY_SCB_SPI_TRANSFER_ACTIVE & SPIm_MPU_GetTransferStatus());               

            }

           

            for(i = 0; i<= RegisterLen; i++)

            {

                txBuf = 0;

            }

           

            //DESELECT MY_DEVICE_1;

        }

       

        return status;

    }

    unsigned long User_SPI_Read(unsigned char dev_id, unsigned char RegisterAddr, unsigned char RegisterLen, unsigned char *RegisterValue)

    {

       

        int32 i;

       

        if (dev_id == MY_DEVICE_1)

        {

            /* Set MSB of register addr byte to 1 to indicate read request */

            txBuf[0] = RegisterAddr | SPIm_READFLAG;

       

            //SELECT MY_DEVICE_1;

           

            /* Write register address */

            SPI_FP_ClearTxFifo();

            SPI_FP_ClearRxFifo();

            status = SPIm_MPU_Transfer(txBuf, rxBuf, RegisterLen+1);

                  

            if (status == CY_SCB_SPI_SUCCESS)

            {               

                while (CY_SCB_SPI_TRANSFER_ACTIVE & SPIm_MPU_GetTransferStatus());

            }

           

            for(i = 1; i<= RegisterLen; i++)

            {

                RegisterValue[i-1] = rxBuf;

            }

           

            //DESELECT MY_DEVICE_1;

        }

       

        return status;

    }

Regards,

Meenakshi Sundaram R

Meenakshi,

Thanks, I'll try that code out.  I already changed the CSs to be simple GPIO so that should be straightforward.

Am I right in thinking that high priority functions that call other ISRs will leave this code if it's running and then come back when they are done?  If yes, am I right in thinking that this should not cause the SPI too many issues?  The 2 devices I want to read are going to run at 300 kHz SClk (because one of them can only go at 400 kHz), and I only need to read them once per minute,

Ted

0 Likes

Yes, interrupts execute irrespective of the while(). Though there is an option to hook callback to the SPI interrupt, that lets you know when the transfer is complete. But that will be useful in an RTOS context, where these APIs can cause the calling task to suspend and wait for a signal from the interrupt callback.

And yeah at that rate, it should not be hogging the CPU that much.

0 Likes

Meenakshi,

I ported your drivers into my project as follows

int8_t user_spi_read(uint8_t dev_id, uint8_t RegisterAddr, uint8_t *RegisterValue, uint16_t RegisterLen)

{

    int32 i;

    cy_en_scb_spi_status_t status;

   

   // The parameter dev_id is used as a variable to select which Chip Select pin has to be set low to activate the relevant device on the SPI bus

    if (dev_id) { // if Dev ID > 0

        Cy_GPIO_Write(SPI0_nCS_Sag_PORT, SPI0_nCS_Sag_NUM, 0);      // Select Sag CS if dev_id > 0

    }

    else {        // else it's zero

        Cy_GPIO_Write(SPI0_nCS_Baro_PORT, SPI0_nCS_Baro_NUM, 0);    // Select Baro CS if dev_id == 0

    }

    /* Set MSB of register addr byte to 1 to indicate read request */

    txBuf[0] = RegisterAddr | 0x80; // may be unecessary as the BME280 driver code may already set this bit

   

    /* Write register address */

    SPI_0_ClearTxFifo();

    SPI_0_ClearRxFifo();

    status = SPI_0_Transfer(txBuf, rxBuf, RegisterLen+1);

           

    if (status == CY_SCB_SPI_SUCCESS)

    {              

        while (CY_SCB_SPI_TRANSFER_ACTIVE & SPI_0_GetTransferStatus());

    }

    for(i = 1; i<= RegisterLen; i++)

    {

        RegisterValue[i-1] = rxBuf;

    }

    // deselect all CSs

    Cy_GPIO_Write(SPI0_nCS_Sag_PORT, SPI0_nCS_Sag_NUM, 1); // Deselect Sag CS

    Cy_GPIO_Write(SPI0_nCS_Baro_PORT, SPI0_nCS_Baro_NUM, 1); // Deselect Baro CS

   

    return status;

}

I have a problem that debug seems to indicate that it gets stuck forever waiting for the status to be CY_SCB_SPI_SUCCESS (lines 23-26 above). That could be beacue of hardware issues but can you suggest a way to make it time out and report what the status result was?

Ted

0 Likes

Ted,

Did you make sure the SPI interrupt is mapped to M0+ or M4 (the core that uses the driver) and the interrupt is enabled? The SPI driver should not get stuck if interrupt is properly enabled/configured and SPI is started. SPI does not have any ACK mechanism like I2C for getting stuck. So I would not expect it to be stuck there.

You can add a simple timeout like below -

uint32 timeout = 10000; //in us

if (status == CY_SCB_SPI_SUCCESS) 

    {                

        while ((CY_SCB_SPI_TRANSFER_ACTIVE & SPI_0_GetTransferStatus()) && (timeout != 0))

{

timeout--;

Cy_SysLib_DelayUs(1);


    }

if(timeout == 0)

{

//return HW timed out

}

Regards,

Meenakshi Sundaram R

0 Likes