1. 前言

        鉴于之前的博客有详细的讲解到标准SPI发展史、时序图、参考代码。但是在实际应用中,标准spi很多都已经被封装成库,比如树莓派、fpga底层封装、各种第三方库。而真正用到我们使用c代码去模拟spi的时序的,一般是单片机,没有第三方库支持,只能使用gpio去模拟,而模拟的spi,速率则根据单片机主频,gpio口的切换速度(多几个函数调用时间差别就很大)相关。就我所知的,大概是200K到800K左右。而我们的spi号称是支持高速数据收发的一种协议,这样的时钟速率感觉是对不起他的学名咯。

        其实不然,每个项目都是不一样的,一切项目都不能只为追求快,而应该是追求稳定。选择最稳定的速率,匹配自身的项目,这个才是一个成功的项目。

        模拟spi最致命的问题,就是gpio口异常。你想想,如果SDI、SDO gpio有个时间点翻转错误,那么就会导致整个操作产生偏移,那数据无疑就是不是预期的数据,而这类问题的定位,往往是需要使用示波器去抓波形才能得知。所以,除非不得已,建议还是选用带有标准spi的mcu,或者需要支持第三方库,这样才能使得操作更为安全。

2. 实例(CMT2300A)

        每一款芯片都有他相应的时序要求,以及支持的最大时钟速率。该芯片最高支持5M时钟速率,并且他的时序包括读寄存器、写寄存器、读fifo和写fifo四个操作说明。

2.1 时序说明

        从时序要求上看,我们可以知道,三线spi和标准四线spi不同的地方(就这个芯片而已,不代表其他的spi),三线spi是共用了一个数据口(既SDIO),数据的写入和读取都在该io,并且是受两个控制信号依次控制,代表是操作寄存器还是fifo。

        另外,还需要注意在操作之前、切换过程和结束时的操作,控制线需要如何被控制,并且有时间要求,这个需要使用示波器抓取波形来调整。

2.2 三线spi实例代码(参考官方文档代码)

#define SPI_CLK_PIN       (1)
#define SPI_CS_PIN        (2)
#define SPI_MISO_PIN      (3) //SDI与SDO共用一个gpio 当然也可以只定义一个
#define SPI_MOSI_PIN      (3)
#define SPI_FCS_PIN       (4)

void GPIO_Init(int pin , int mode)
{
    if(mode == 0)//输入上拉
    {
        iot_gpio_close(pin);
        iot_gpio_open_as_input(pin);//GPIO_Functional_Config_Bit(port , pin , FUNCTION_INPUT );//输入
        iot_gpio_set_pull_mode(pin, GPIO_PULL_NONE);//GPIO_Pull_Up_Config_Bit(port , pin , ENABLE );//上拉			
    }
    else
    {
        iot_gpio_close(pin);
        iot_gpio_open_as_output(pin);//GPIO_Functional_Config_Bit(port , pin , FUNCTION_OUTPUT );//输出
        iot_gpio_set_opendrain_mode(pin, GPIO_DRAIN_NORMAL);
    }				
}

/* ************************************************************************
*  The following need to be modified by user
*  ************************************************************************ */
#define SET_GPIO_OUT(x)             GPIO_Init(x, GPIO_OUTPUT)
#define SET_GPIO_IN(x)              GPIO_Init(x, GPIO_INPUT)

#define SET_GPIO_H(x)               iot_gpio_value_set(x,1)
#define SET_GPIO_L(x)               iot_gpio_value_set(x,0)
#define READ_GPIO_PIN(x)			iot_gpio_value_get(x)
//

#define cmt_spi3_csb_out()      SET_GPIO_OUT(SPI_CS_PIN)
#define cmt_spi3_fcsb_out()     SET_GPIO_OUT(SPI_FCS_PIN)
#define cmt_spi3_sclk_out()     SET_GPIO_OUT(SPI_CLK_PIN)
#define cmt_spi3_sdio_out()		SET_GPIO_OUT(SPI_MOSI_PIN)
#define cmt_spi3_sdio_in()		SET_GPIO_IN(SPI_MOSI_PIN)

#define cmt_spi3_csb_1()        SET_GPIO_H(SPI_CS_PIN)
#define cmt_spi3_csb_0()        SET_GPIO_L(SPI_CS_PIN)

#define cmt_spi3_fcsb_1()       SET_GPIO_H(SPI_FCS_PIN)
#define cmt_spi3_fcsb_0()       SET_GPIO_L(SPI_FCS_PIN)
    
#define cmt_spi3_sclk_1()       SET_GPIO_H(SPI_CLK_PIN)
#define cmt_spi3_sclk_0()       SET_GPIO_L(SPI_CLK_PIN)

#define cmt_spi3_sdio_1()       SET_GPIO_H(SPI_MOSI_PIN)
#define cmt_spi3_sdio_0()       SET_GPIO_L(SPI_MOSI_PIN)
#define cmt_spi3_sdio_read()    READ_GPIO_PIN(SPI_MOSI_PIN)
/* ************************************************************************ */

void cmt_spi3_delay(void)
{
    u16 n = 2;
    while(n--);
}

void cmt_spi3_delay_us(void)
{
    u16 n = 1;
    while(n--);
}

uint32_t RF_Spi_Init(void)
{
	cmt_spi3_csb_out();
    cmt_spi3_csb_1();
    cmt_spi3_csb_out();
    cmt_spi3_csb_1();   
    
	cmt_spi3_sclk_out();
    cmt_spi3_sclk_0();
    cmt_spi3_sclk_out();
    cmt_spi3_sclk_0();  
    
	cmt_spi3_sdio_out();
    cmt_spi3_sdio_1();
    cmt_spi3_sdio_out();
    cmt_spi3_sdio_1();
    
	cmt_spi3_fcsb_out();
    cmt_spi3_fcsb_1();
    cmt_spi3_fcsb_out();
    cmt_spi3_fcsb_1(); 

    cmt_spi3_delay();
}

void cmt_spi3_send(u8 data8)
{
    u8 i;

    for(i=0; i<8; i++)
    {
        cmt_spi3_sclk_0();
        /* Send byte on the rising edge of SCLK */
        if(data8 & 0x80)
            cmt_spi3_sdio_1();
        else            
            cmt_spi3_sdio_0();
        //cmt_spi3_delay();
        data8 <<= 1;
        cmt_spi3_sclk_1();
        cmt_spi3_delay();
    }
}

u8 cmt_spi3_recv(void)
{
    u8 i;
	
    u8 data8 = 0xFF;
    for(i=0; i<8; i++)
    {
        cmt_spi3_sclk_0();
        cmt_spi3_delay();
        data8 <<= 1;
        cmt_spi3_sclk_1();
        /* Read byte on the rising edge of SCLK */
        if(cmt_spi3_sdio_read())
		//if (PADIN & 0x20)
            data8 |= 0x01;
        else
            data8 &= ~0x01;
    }
    return data8;
}

void cmt_spi3_write(u8 addr, u8 dat)
{
    cmt_spi3_sdio_1();
    cmt_spi3_csb_0();

    /* > 0.5 SCLK cycle */
    /* r/w = 0 */
    cmt_spi3_send(addr&0x7F);
    cmt_spi3_send(dat);
    cmt_spi3_sclk_0();
    /* > 0.5 SCLK cycle */
    cmt_spi3_csb_1();
    cmt_spi3_sdio_1();
}

void cmt_spi3_read(u8 addr, u8* p_dat)
{
    cmt_spi3_sdio_1();
    cmt_spi3_csb_0();
    /* > 0.5 SCLK cycle */
    /* r/w = 1 */
    cmt_spi3_send(addr|0x80);
    /* Must set SDIO to input before the falling edge of SCLK */
    cmt_spi3_sdio_in();
    *p_dat = cmt_spi3_recv();
    cmt_spi3_sclk_0();
    /* > 0.5 SCLK cycle */
    cmt_spi3_csb_1();
    cmt_spi3_sdio_1();
    
	cmt_spi3_sdio_out();
}

void cmt_spi3_write_fifo(u8* p_buf, u16 len)
{
    u16 i;

    for(i=0; i<len; )
    {
        cmt_spi3_fcsb_0();
        /* > 1 SCLK cycle */
        cmt_spi3_send(p_buf[i]);
        cmt_spi3_sclk_0();
        /* > 2 us */
        cmt_spi3_fcsb_1();
        /* > 4 us */
        i++;
    }
}

u8 cmt_spi3_read_fifo(u8* p_buf, u8 len)
{
    u8 i=0;
	
    cmt_spi3_sdio_in();
	for(i=0; i<len; i++)
	{
		cmt_spi3_fcsb_0();
		/* > 1 SCLK cycle */
		p_buf[i] = cmt_spi3_recv();
		cmt_spi3_sclk_0();
		/* > 2 us */
		cmt_spi3_delay_us();
		cmt_spi3_fcsb_1();
		/* > 4 us */
		cmt_spi3_delay_us();
	}
	cmt_spi3_sdio_out();
	return i;
}

3. 总结

        当使用一款新的芯片时,首先需要明确它的时序要求。像这款国产芯片,快速上手文档上说明很详细,可以快速的进行开发。如何才能知道spi与芯片是否通信上,最直观的就是使用示波器去抓取4根线的波形,分析每一位;然后再找到某个寄存器,尝试对其进行读、写、读操作,确定读写真的成功;另外,有些芯片会有一些测试模式,比如支持fifo的回读功能,这样就可以进行相应的测试了。

        最后,如果底层fpga支持将4线改成三线的话,那么就可以使用相应的接口函数进行进一步的封装,最底层的一个封装函数如下:

typedef struct spi_xfer_buf
{
    struct spi_xfer_buf *p_nt;
    /** bytes for recieving or for transmiting. */
    int size;
    /** bytes recieved or transmited  */
    int xfer_size;
    char *rxbuf;
    char *txbuf;
}xfer_buf;
uint32_t iot_spi_write_one_data(char *data, int len)
{
    xfer_buf spi_tx;

    if ((NULL == data) || (0 >= len))
    {
        return ERR_FAIL;
    }

    spi_tx.p_nt = NULL;
    spi_tx.txbuf = data;
    spi_tx.rxbuf = NULL;
    spi_tx.size = len;

    if(-1 == iot_spi_poll_transfer(DEV_RF_SPI, &spi_tx))
    {
        return ERR_FAIL;
    }

    return ERR_OK;
}

uint32_t iot_spi_read_one_data(xfer_buf *xbuf)
{
    if ((NULL == xbuf))
    {
        return 1;
    }

    if(-1 == iot_spi_poll_transfer(DEV_RF_SPI, xbuf))
    {
        return 1;
    }

    return 0;
}

//api
void spi3_read(uint8_t addr, uint8_t* p_dat)
{
	char p_addr[1];

    p_addr[0] = (char)(addr | 0x80);

    xfer_buf xbuf[2];
    xbuf[0].p_nt = &xbuf[1];
    xbuf[0].txbuf = p_addr;
    xbuf[0].rxbuf = NULL;
    xbuf[0].size = 1;

    xbuf[1].p_nt = NULL;
    xbuf[1].txbuf = NULL;
    xbuf[1].rxbuf = p_dat;
    xbuf[1].size = 1;

    SCS_ENABLE;
    iot_spi_read_one_data(xbuf);
    SCS_DISABLE;
}

 

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐