【硬件通信协议】5. 实例解析非标准SPI(三线SPI)

Hester ·
更新时间:2024-11-13
· 952 次阅读

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 < 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 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 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; }
作者:sishuihuahua



协议 通信协议 通信 spi

需要 登录 后方可回复, 如果你还没有账号请 注册新账号