【硬件通信协议】5. 实例解析非标准SPI(三线SPI)
1. 前言鉴于之前的博客有详细的讲解到标准SPI发展史、时序图、参考代码。但是在实际应用中,标准spi很多都已经被封装成库,比如树莓派、fpga底层封装、各种第三方库。而真正用到我们使用c代码去模拟spi的时序的,一般是单片机,没有第三方库支持,只能使用gpio去模拟,而模拟的spi,速率则根据单片机主频,gpio口的切换速度(多几个函数调用时间差别就很大)相关。就我所知的,大...
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;
}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)