Implementing a custom SPI Bus-System handling for communication with another MCU

Accept86
Posts: 12
Joined: Fri Jun 02, 2023 9:07 am

Implementing a custom SPI Bus-System handling for communication with another MCU

Postby Accept86 » Mon Jun 05, 2023 4:32 pm

Hi,
I'm trying to implement a custom SPI Bus-System.
I'm short on IO's, so I'm trying to have a virtual CS pin.

My idea is, clock is in high impedance normally.

Each sender has their own send Line / MISO / MOSI

One bususer, has the task to cyclically send a 1 byte token. This one Byte token is a address, either 1 or 2

The Bus with the address of the two, can afterwards send something.

I imagine this way, the message can have any length, while I don't know if thats the case for normal master-slave communication.

For now I'm just testing with fixed 6 Byte-transferlenghts.


I tried following:

Code: Select all

/* SPI for Com with other IOMCU
*/
#include <stdio.h>
#include <stdint.h>
#include <stddef.h>
#include <string.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#include "esp_log.h"

#include "SPI_com_slave.h"

#include "include_all.h"
#include "../APPL/usercode.h"

#include "esp_attr.h"
#include "hal/include/hal/gpio_types.h"
#include "driver/include/driver/spi_master.h"
#include "driver/include/esp_private/spi_common_internal.h"

#include <esp_types.h>
#include <stdatomic.h>

#include "soc/include/soc/lldesc.h"
#include "esp_system/include/esp_task_wdt.h"



/*
SPI receiver/sender
*/

//ESP32
#define GPIO_MOSI 4     //GPIO4 - Pin6
#define GPIO_MISO 32    //GPIO32 - Pin13
#define GPIO_SCLK 16    //GPIO16 - Pin11
#define GPIO_CS 5       //GPIO5 (NC)

#define RCV_HOST    SPI2_HOST
#define _SPI_EX_BUF_SIZE    (LLDESC_MAX_NUM_PER_DESC/8+1)

#define TRANSFER_SIZE 6


unsigned char SPI_RX_transaction_readdata_pending = 0;
unsigned char SPI_TX_transaction_complete = 0;

void spi_ex_setupCplt(spi_transaction_t *trans)
{
    volatile unsigned short breakpoint = 5;
    /*
    //https://github.com/espressif/esp-idf/issues/1373
    I also needed to communicate with a master (mode 3) without cs-line. 
    With the tips from @ginkgm I was able to get it working. For my application I first needed set he transaction length to 6 bytes ( t.length = 6*8; )
    to receive the master command. In the post_setup_cb callback I set my fake-CS line low and in the post_trans_cb callback I set the fake-CS line high. 
    After the transaction I set the length depending on the received command.
    To get In sync with the SPI-clk I also have a soft-timer that toggles the CS-line after a certain time of inactivity of the SPI-clk line.
    */

    ESP_ERROR_CHECK(gpio_set_level(GPIO_CS, 0));
    if(trans->rx_buffer!=NULL)
    {
            trans->length = TRANSFER_SIZE*8;
    }
    else
    {
            trans->length = TRANSFER_SIZE*8;
    }
   
}
 
void spi_ex_transactionCplt(spi_transaction_t *trans)
{
    volatile unsigned short breakpoint = 6;

    if( (trans->rxlength!=0)&&(trans->rx_buffer!=NULL) )
    {
        SPI_RX_transaction_readdata_pending = 1;
    }
    else if(trans->tx_buffer!=NULL)
    {
        SPI_TX_transaction_complete = 1;
    }
        
}


spi_device_interface_config_t devConfig_ESPCOM = {
        .command_bits = 0,           ///< Default amount of bits in command phase (0-16), used when ``SPI_TRANS_VARIABLE_CMD`` is not used, otherwise ignored.
        .address_bits = 0,           ///< Default amount of bits in address phase (0-64), used when ``SPI_TRANS_VARIABLE_ADDR`` is not used, otherwise ignored.
        .dummy_bits = 0,             ///< Amount of dummy bits to insert between address and data phase
        .mode = 1,                   /**< SPI mode, representing a pair of (CPOL, CPHA) configuration:
                                            - 0: (0, 0)
                                            - 1: (0, 1)
                                            - 2: (1, 0)
                                            - 3: (1, 1)
                                        */
        .clock_source = SPI_CLK_SRC_DEFAULT, ///< Select SPI clock source, `SPI_CLK_SRC_DEFAULT` by default.
        .duty_cycle_pos = 0,        ///< Duty cycle of positive clock, in 1/256th increments (128 = 50%/50% duty). Setting this to 0 (=not setting it) is equivalent to setting this to 128.
        .cs_ena_pretrans = 0,      ///< Amount of SPI bit-cycles the cs should be activated before the transmission (0-16). This only works on half-duplex transactions.
        .cs_ena_posttrans = 0,       ///< Amount of SPI bit-cycles the cs should stay active after the transmission (0-16)
        .clock_speed_hz = 100000,             ///< Clock speed, divisors of the SPI `clock_source`, in Hz
        .input_delay_ns = 0,             /**< Maximum data valid time of slave. The time required between SCLK and MISO
            valid, including the possible clock delay from slave to master. The driver uses this value to give an extra
            delay before the MISO is ready on the line. Leave at 0 unless you know you need a delay. For better timing
            performance at high frequency (over 8MHz), it's suggest to have the right value.
            */
        .spics_io_num = GPIO_CS,     ///< CS GPIO pin for this device, or -1 if not used
        .flags = /*SPICOMMON_BUSFLAG_IOMUX_PINS |*/ /*SPI_DEVICE_HALFDUPLEX |*/ SPICOMMON_BUSFLAG_SCLK | SPICOMMON_BUSFLAG_MISO | SPICOMMON_BUSFLAG_MOSI,
        .queue_size = 3, 
        .pre_cb = spi_ex_setupCplt, 
        .post_cb = spi_ex_transactionCplt
};


WORD_ALIGNED_ATTR uint8_t spi_ex_3txBuf[_SPI_EX_BUF_SIZE+1] = { 0 };
WORD_ALIGNED_ATTR uint8_t spi_ex_3rxBuf[_SPI_EX_BUF_SIZE+1] = { 0 };
 
spi_transaction_t spi_ex_trans_rx = {
        .flags =  SPI_TRANS_MULTILINE_CMD,            
        .cmd = 0,           
        .addr = 0,
        .length = _SPI_EX_BUF_SIZE *8,
        .rxlength = TRANSFER_SIZE *8,                 // TBDTBDESP TRANSFER_SIZE 100 Byte Paketgröße
        .tx_buffer = 0,
        .rx_buffer = spi_ex_3rxBuf
};

spi_transaction_t spi_ex_trans_tx = {
        .flags =  SPI_TRANS_MULTILINE_CMD,            
        .cmd = 0,           
        .addr = 0,
        .length = _SPI_EX_BUF_SIZE *8,
        .rxlength = 0,                 // TBDTBDESP TRANSFER_SIZE 100 Byte Paketgröße
        .tx_buffer = spi_ex_3txBuf,
        .rx_buffer = 0
};

spi_bus_config_t buscfg;
spi_device_handle_t _spi_handles[SPI_HOST_MAX]; 

bool spi_ex_isInitialized[SOC_SPI_PERIPH_NUM] = { false, false, false };
 
void my_spi_ex_init()
{
    spi_host_device_t spi_num = SPI2_HOST;

    if (spi_ex_isInitialized[spi_num])
    {
        return;
    }

    memset(spi_ex_trans_rx.rx_buffer, 0, _SPI_EX_BUF_SIZE+1);
    memset(spi_ex_trans_tx.tx_buffer, 0, _SPI_EX_BUF_SIZE+1);
 
    memset(&buscfg, 0, sizeof(buscfg));
    
    buscfg.intr_flags = ESP_INTR_FLAG_LEVEL1; // Lowest priority (see comments above)
    buscfg.flags = devConfig_ESPCOM.flags;
    buscfg.mosi_io_num = GPIO_MOSI;
    buscfg.miso_io_num = GPIO_MISO;
    buscfg.sclk_io_num = GPIO_SCLK;
    buscfg.quadwp_io_num = -1;
    buscfg.quadhd_io_num = -1;
    buscfg.max_transfer_sz = _SPI_EX_BUF_SIZE+1;

    ESP_ERROR_CHECK(gpio_set_direction(GPIO_CS, GPIO_MODE_INPUT));      // Fixed, must be Input
    
    ESP_ERROR_CHECK(spi_bus_initialize(spi_num, &buscfg, SPI_DMA_CH_AUTO));
    ESP_ERROR_CHECK(spi_bus_add_device(spi_num, &devConfig_ESPCOM, &_spi_handles[SPI2_HOST]));

    ESP_ERROR_CHECK(gpio_set_direction(GPIO_MOSI, GPIO_MODE_INPUT));
    ESP_ERROR_CHECK(gpio_set_direction(GPIO_SCLK, GPIO_MODE_INPUT));    // ToChange on Rec/Send switch
    ESP_ERROR_CHECK(gpio_set_direction(GPIO_CS, GPIO_MODE_INPUT));      // Fixed, must be Input
    ESP_ERROR_CHECK(gpio_set_direction(GPIO_MISO, GPIO_MODE_OUTPUT));

    ESP_ERROR_CHECK(gpio_set_pull_mode(GPIO_MOSI, GPIO_FLOATING));
    ESP_ERROR_CHECK(gpio_set_pull_mode(GPIO_SCLK, GPIO_PULLUP_ONLY));
    ESP_ERROR_CHECK(gpio_set_pull_mode(GPIO_CS, GPIO_PULLUP_ONLY));
    ESP_ERROR_CHECK(gpio_set_pull_mode(GPIO_MISO, GPIO_PULLUP_ONLY));
   
    spi_ex_isInitialized[spi_num] = true;
}


unsigned char SPIinitComplete = 0; 

spi_transaction_t* p_spi_trans_tx_temp;
spi_transaction_t* p_spi_trans_rx_temp;

char sRecBuf[_SPI_EX_BUF_SIZE*4 +1];
void SPI_com_slave_com_loop_RX(void)	// Is cyclically called from a task that gets created for this.
{
    int n=0;
    esp_err_t ret;

    memset(sRecBuf, 0, _SPI_EX_BUF_SIZE*4 +1);
    static unsigned char do_once = 1;
    
    if((spi_ex_isInitialized[SPI2_HOST] == true)&&(spi_ex_trans_rx.rx_buffer!=NULL))
    {
    
        if(do_once) // why do I need this?
        {
            do_once = 0;
            ESP_ERROR_CHECK(spi_device_transmit(_spi_handles[SPI2_HOST] , &spi_ex_trans_rx));     // no additional & different than with spi_bus_add_device !
        }
    
        SPIinitComplete =1; // kann senden

        if(SPI_RX_transaction_readdata_pending)
        {
            SPI_RX_transaction_readdata_pending = 0;
            
                short i;
                for(i=0;i<TRANSFER_SIZE;i++)
                {
                    snprintf(&sRecBuf[i*4], 5, "%03d ", (short)(*((unsigned char*)&(spi_ex_trans_rx.rx_buffer[i]))) );
                }

                OutputDebugStrF("SPI_Received: %s\r\n", sRecBuf);

                TaskSleep(1 / portTICK_PERIOD_MS);

                memset(sRecBuf, 0, _SPI_EX_BUF_SIZE*4 +1);
                memset(spi_ex_trans_rx.rx_buffer, 0, _SPI_EX_BUF_SIZE+1);

                //Workaround for Olimex HW
                ESP_ERROR_CHECK(gpio_set_level(GPIO_CS, 1));
                TaskSleep(2 / portTICK_PERIOD_MS);

                // Clock auf output umschalten
                ESP_ERROR_CHECK(gpio_set_direction(GPIO_SCLK, GPIO_MODE_OUTPUT));    // ToChange on Rec/Send switch
                //ESP_ERROR_CHECK(gpio_set_direction(GPIO_CS, GPIO_MODE_OUTPUT));    // Fixed, must be Input

                //Workaround for Olimex HW
                ESP_ERROR_CHECK(gpio_set_level(GPIO_CS, 0));

                SPI_TX_transaction_complete = 0;

                p_spi_trans_tx_temp = &spi_ex_trans_tx;
                if((p_spi_trans_tx_temp->tx_buffer != NULL) && (SPIinitComplete == 1))
                {
                    sprintf((char*)p_spi_trans_tx_temp->tx_buffer, "%04d.", n);
                    ESP_ERROR_CHECK(spi_device_queue_trans(_spi_handles[SPI2_HOST], p_spi_trans_tx_temp, portMAX_DELAY));     // TBD Write
                }

                while(SPI_TX_transaction_complete==0)
                {
                    TaskSleep(1 / portTICK_PERIOD_MS);
                }

                OutputDebugStrF("SPI_Sendt: %s\r\n", (char*)p_spi_trans_tx_temp->tx_buffer);

                //Workaround for Olimex HW
                TaskSleep(2 / portTICK_PERIOD_MS);
                ESP_ERROR_CHECK(gpio_set_level(GPIO_CS, 1));
                TaskSleep(3 / portTICK_PERIOD_MS);

                ESP_ERROR_CHECK(gpio_set_direction(GPIO_SCLK, GPIO_MODE_DEF_INPUT));    // ToChange on Rec/Send switch
                //ESP_ERROR_CHECK(gpio_set_direction(GPIO_CS, GPIO_MODE_DEF_INPUT));    // Fixed, must be Input

                //Workaround for Olimex HW
                ESP_ERROR_CHECK(gpio_set_level(GPIO_CS, 0));


                p_spi_trans_rx_temp = &spi_ex_trans_rx;
                if(spi_ex_trans_rx.rx_buffer!=NULL)
                {
                    ret = spi_device_get_trans_result(_spi_handles[SPI2_HOST], p_spi_trans_rx_temp, portMAX_DELAY);
                }
                //spi_device_release_bus(_spi_handles[SPI2_HOST]); // TBD: Clock (u.u.CS) auf input umschalten

                SPI_TX_transaction_complete=0;

                n++;
        }

    } // if((spi_ex_isInitialized[SPI2_HOST] == true)&&(spi_ex_trans_rx.rx_buffer!=NULL))

}



Sadly I'm neither receiving something, nor the initial runthrough of

Code: Select all

void SPI_com_slave_com_loop_RX(void)
{
    int n=0;
    esp_err_t ret;

    memset(sRecBuf, 0, _SPI_EX_BUF_SIZE*4 +1);
    static unsigned char do_once = 1;
    
    if((spi_ex_isInitialized[SPI2_HOST] == true)&&(spi_ex_trans_rx.rx_buffer!=NULL))
    {
    
        if(do_once) // why do I need this?
        {
            do_once = 0;
            ESP_ERROR_CHECK(spi_device_transmit(_spi_handles[SPI2_HOST] , &spi_ex_trans_rx));     // no additional & different than with spi_bus_add_device !
        }
does also not send a clock or data.


The data sent to the module are ok as shown in attachment:
SPI_ESP_Receive.png
SPI_ESP_Receive.png (60.49 KiB) Viewed 922 times

MicroController
Posts: 1136
Joined: Mon Oct 17, 2022 7:38 pm
Location: Europe, Germany

Re: Implementing a custom SPI Bus-System handling for communication with another MCU

Postby MicroController » Mon Jun 05, 2023 4:48 pm

How about just using I2C? Takes only 2 lines total, is standardized, directly supported by hardware, and has already taken care of both addressing and transaction delimitation.

Accept86
Posts: 12
Joined: Fri Jun 02, 2023 9:07 am

Re: Implementing a custom SPI Bus-System handling for communication with another MCU

Postby Accept86 » Mon Jun 05, 2023 10:49 pm

Oh!
Thank you.
Really glad I asked

Who is online

Users browsing this forum: No registered users and 111 guests