Note: introduction and actual effect of simulated SPI

Posted by andrewb on Wed, 29 Dec 2021 07:05:40 +0100

0x00 Preface

Because it was mentioned earlier that for the relevant pin test of HC32F460, the author recently completed a simplified SPI bus according to the device, so put it on the MCU to test the effect.

0x10 introduction

As an SPI simulator, he needs at least:

  • A way of setting
  • An interface to view the current status
  • A write interface
  • A read interface

The characteristics of SPI have been introduced earlier, and will not be repeated here:

typedef union 
{
	unsigned char d8;
	struct
	{
	unsigned CPHA:1;		//0-even-catch|1-odd-catch
	unsigned CPOL:1;		//0-low|1-high
	unsigned DATASIZE:1;	//0-8bit|1-16bit
	unsigned FLASH_BIT:1;	//0-MSB|1-LSB
	unsigned MODE:1;		//0-master|1-slave
	unsigned DIR:2;		//0-Full duplex|1-onlyRX|2-onlyTX|3-Only_listen
	unsigned ENABLE :1; //0-off|1-on
	}b;
	
}S_SPI_CONFIG;
typedef struct
{
	unsigned CLK:1;
	unsigned CS:1;		
	unsigned MOSI:1;	
	unsigned MISO:1;	
	unsigned NU :28; 
}S_SPI_SOFT_PIN;

This is the current virtual device mode and pin modeling. Here, only Master and Mode 0 modes are implemented, and the remaining modes are not tested

Here, HC32F460 library does not have an interface for a single driver of the current GPIO, so the author has made one himself

void Port_Write_Pins(en_port_t enPort, uint16_t u16Pin,unsigned char state)
{
    uint16_t *PORx;
    if(state)
    {
        PORx = (uint16_t *)((uint32_t)(&M4_PORT->POSRA) + 0x10u * enPort);
    }
    else
    {
        PORx = (uint16_t *)((uint32_t)(&M4_PORT->PORRA) + 0x10u * enPort);
    }
    *PORx |= u16Pin;
}

A basic implementation is also required below, that is, to simulate the current SPI device using a timer model:

void TIM_IRQHandler()
    {
        {
            if(spi_soft_obj.config.b.ENABLE == 1)
            {
                //normal-no send
                if(spi_soft_obj.line_state == 0)
                {
                    if(spi_soft_obj.config.b.CPOL)
                    {
                        spi_pin.CLK = 1;
                    }
                    else
                    {
                        spi_pin.CLK = 0;
                    }
                    //spi_pin.MOSI = 0;
                    if(spi_pin.CS == 1)
                    {
                        //MSB
                        if(spi_soft_obj.config.b.FLASH_BIT == 0)
                        {
                            spi_soft_obj.data[SOFT_SPI_DATA_IN] |= MISO_GET();
                        }
                        //LSB
                        else
                        {   //no debug
                            spi_soft_obj.data[SOFT_SPI_DATA_IN] |= MISO_GET()<<((8*(spi_soft_obj.config.b.DATASIZE+1)));
                        }
                    }
                    spi_pin.CS = 0;
                    update_bit =0;
                    clock_count = 0;
                    spi_bit_count = 0;
                }
                //send-once byte
                else
                {
                        if((clock_count == 0))
                        {
                            spi_soft_obj.data[SOFT_SPI_DATA_IN] = 0;
                        }
                    //0-CPHA=0 
                    if((!spi_soft_obj.config.b.CPHA) && (clock_count == 0))
                    {
                        update_bit = 1;
                        spi_pin.CLK = 0;
                    }
                    else
                    {
                        spi_pin.CLK = !spi_pin.CLK;
                        if((clock_count % 2) == (spi_soft_obj.config.b.CPHA))
                        {
                            update_bit = 1;
                        }
                        else
                            update_bit = 0;
                    }
                    clock_count++;
                    //send_finish
                    if((8*(spi_soft_obj.config.b.DATASIZE+1)) <= spi_bit_count)
                    {
    
                        spi_soft_obj.line_state = 0;
                        //spi_pin.MOSI = 0;
                        spi_pin.CS = 1;
                        update_bit =0;
                        clock_count = 0;
                        spi_bit_count = 0;
                    }
                    if(update_bit)
                    {
                        spi_pin.MISO = MISO_GET();
                        //MSB
                        if(spi_soft_obj.config.b.FLASH_BIT == 0)
                        {
                            spi_pin.MOSI = spi_soft_obj.data[SOFT_SPI_DATA_OUT] >> ((8*(spi_soft_obj.config.b.DATASIZE+1)) - spi_bit_count - 1 ) & 0x01;
                            spi_soft_obj.data[SOFT_SPI_DATA_IN] |= MISO_GET()<<((8*(spi_soft_obj.config.b.DATASIZE+1))- spi_bit_count );
                        }
                        //LSB
                        else
                        {   //no debug
                            spi_pin.MOSI = spi_soft_obj.data[SOFT_SPI_DATA_OUT] >> (spi_bit_count) & 0x01;
                            spi_soft_obj.data[SOFT_SPI_DATA_IN] |= MISO_GET()<<(spi_bit_count);
                        }
                        spi_bit_count++;
                    }
                    
                    
                }
                CLK_SET(spi_pin.CLK);
                MOSI_SET(spi_pin.MOSI);
                
            }
            else
            {
                CLK_SET(0);
                MOSI_SET(0);
            }
        }
    }

Here we have got a basic device simulation code. As long as the interface of the timer is connected, SPI communication can be realized directly. Here, the author selects PB 6 7 8 9 as the IO port

void spi_soft_init()
    {
        stc_port_init_t port_init_struct;
        port_init_struct.enPinMode = Pin_Mode_Out;
        port_init_struct.enPullUp = Enable;
        port_init_struct.enLatch = Disable;
        port_init_struct.enExInt = Disable;
        port_init_struct.enInvert = Disable;
        port_init_struct.enPullUp = Enable;
        port_init_struct.enPinDrv = Pin_Drv_H;
        port_init_struct.enPinOType = Pin_OType_Cmos;
        port_init_struct.enPinSubFunc = Enable;
        
        
        PORT_Init(PortB, Pin06, &port_init_struct);
        PORT_Init(PortB, Pin07, &port_init_struct);
        PORT_Init(PortB, Pin08, &port_init_struct);
        port_init_struct.enPinMode = Pin_Mode_In;
        port_init_struct.enPinOType = Pin_OType_Od;
        port_init_struct.enPullUp = Disable;
        PORT_Init(PortB, Pin09, &port_init_struct);
        //PORT_SetFunc(PortA,Pin05, Func_Tima0, Enable);
    
        //TIM -- simulate the current frequency

    
        spi_soft_obj.config.b.ENABLE = 1;
    
        

    timer_id = timer_add(
                        0,
                        0,
                        0,
                        0,
                        0,
                        1,
                        0,
                        1,
                        0,
                        TIM_IRQHandler
                        );
        
    }

It is not known which port does what work. Therefore, the author uses the macro definition here for constraints, which is also convenient for later maintenance. This is only an example. You can also replace the macro definition of GPIO more accurately.

#define CS_HIGH do{PORT_SetBits(PortB, Pin06);}while(0)
#define CS_LOW do{PORT_ResetBits(PortB, Pin06);}while(0)

#define CLK_SET(state) do{Port_Write_Pins(PortB, Pin07, state);}while(0)


#define MOSI_SET(state) do{Port_Write_Pins(PortB, Pin08, state);}while(0)
#define MISO_GET() (PORT_GetBit(PortB,Pin09))

Finally, the external interfaces are required:

char SPI_GetBusBusy()
{
	while(spi_pin.CS || spi_soft_obj.line_state);
	return spi_pin.CS && spi_soft_obj.line_state;
}

void SPI_SendData(unsigned short data)
{
	if((spi_soft_obj.config.b.ENABLE) && (!spi_soft_obj.config.b.MODE))
		spi_soft_obj.data[SOFT_SPI_DATA_OUT] = data;
	spi_soft_obj.line_state = 1;
}

unsigned short SPI_ReceiveData()
{
	unsigned short data = 0x00;
	data = spi_soft_obj.data[SOFT_SPI_DATA_IN];
	spi_soft_obj.data[SOFT_SPI_DATA_IN] = 0;

	
	return data;
}

This completes the modeling of an SPI device. The replacement pin only needs to replace the pin position of macro definition and initialization.

The author can directly get the current test results by using the logic analyzer of Mengyuan. The following figure shows the specific situation of 25KHz

It can be seen that the current character accumulation result is correct and can be used.

0x20 summary

This article starts from Note: introduction and actual effect of simulated SPI , more articles can be found in my blog.

Topics: Single-Chip Microcomputer IoT SPI