2150.jpg

  

SSI 和 SPI

SSI: 是有 TI 公司定义的协议标准;
SPI:是有摩托罗拉公司定义的协议标准;
这两个协议在使用上有类似的地方,很多人会把 SSI 当做 SPI 去使用,但是实际上他们是有不同的。

  • 相同点:都是串行同步信号,并且协议管脚都相同(这可能也是很多人搞混的原因)
  • 不同点:1、SPI串行帧数据在SSIFss 低电平时有效,在整个数据帧传输过程中,SSIFss始终是低电平;
        2、而 SSI 串行帧同步SSIFss在发送每个数据帧之前,产生一个宽度为时钟周期的高脉冲,SSI 和片外设备都在SSIClk 的上升沿发送数据,在SSIClk 的下降沿终止数据输入;

也就是说:SSI 相比 SPI 在每帧数据的发送/接收前,SSIClk 都会产生一个宽度为系统时钟周期的脉冲,发送时为脉冲的上升沿驱动数据发送,接收时为脉冲的下降沿锁存数据输入;这么一理思路就简单了,但是问题并不是想象的那么简单!


SSI 浅述

TI 的手册上 SSI 有三种模式:

  • Bi-SSI mode (0x1)
  • Quad-SSI mode (0x2)
  • Advanced SSI mode (0x3)

  Bi-SSI使用两个数据引脚SSInXDAT0和SSInXDAT1,可配置为接收或发送数据。在Quad-SSI模式下,SSInXDAT0,SSInXDAT1,SSInXDAT2和SSInXDAT3允许一次接收或发送四位数据。注意,在Bi-SSI和Quad-SSI中,数据传输仅为半双工。

通过编程SSICR1寄存器中的MODE位,可以使能Advanced SSI mode,Bi-SSI mode或Quad-SSI mode。提供方向位DIR,用于在Bi-SSI mode或Quad-SSI mode转换期间操作方向。在Advanced SSI mode中,如果启用QSSI模块TX(写入)模式,则会自动阻止RX FIFO接收任何数据。当Advanced SSI mode处于RX(读取)模式时,它将作为全双工接口运行。
(翻译水平有限...)

简单理解就是:双通道(Bi-SSI)模式和四通道(Quad-SSI)模式下,传输数据时是半双工。前者一次允许接收/发送2个数据,后者允许接收/发送4个数据;

而SSICR1寄存器中有一个MODE 位用来定义SSI 的模式,还有一个DIR 为用来定义数据的传输方向;当处于高级(Advanced mode)模式时,发送数据的时候,会自动阻止 RX FIFO(接收缓存区,类似于Buff)接收数据,接收数据时,则是全双工(允许读写!)

划重点:当处于高级(Advanced mode)模式时,发送数据的时候,会自动阻止 RX FIFO(接收缓存区,类似于Buff)接收数据,接收数据时,则是全双工(允许读写!)

这句话很重要!因为在使用高级模式的时候,很容易忽略这点!在使用过程中,SSI 每发送一帧数据,RX FIFO 就会接收到一帧将数据,可是此时接收到的数据并不是合法的(此时接收到的是无效的数据!),这也是很多人在调试 SSI Advanced mode时遇到接收到的数据总是 0x00 或 0xff或者其他随机数的原因!

809125155.jpg

 SSI 初始化配置

如上图所示,是TI关于SSI 配置的数据表,通过对系统时钟进行分频的方式,给SSI 分配时钟。

在高级模式中**SSI 时钟务必小于或等于系统时钟的1/12** 这句话在官方的数据手册上没有提现,但是TI 英文论坛有人提及这个隐藏的BUG!这里坑了我好久好久。。。

初始化示例:

void
SPI_Init(void)
{
    uint32_t ui32SysClock;
    
    UARTprintf("SPI Initializing...\n");
    UARTprintf("  Mode: MODE3 \n");
    UARTprintf("  Data: 8-bit\n");
    //
    // The SSI2 peripheral must be enabled for use.
    //
    SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI2);

    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOD);
    
    SysCtlClockFreqSet(SYSCTL_OSC_INT | SYSCTL_USE_PLL | SYSCTL_CFG_VCO_320,        //设置系统时钟——40MHz
                    60000000);
    
    ui32SysClock = SysCtlClockFreqSet((SYSCTL_XTAL_25MHZ |
                                       SYSCTL_OSC_MAIN |
                                       SYSCTL_USE_OSC), 30000000);
    //
    // Configure the GPIO settings for the SSI pins.  This function also gives
    // control of these pins to the SSI hardware.  Consult the data sheet to
    // see which functions are allocated per pin.
    // The pins are assigned as follows:
    //      PD0 - SSI0Tx
    //      PD1 - SSI0Rx
    //      PD2 - SSI0Fss
    //      PD3 - SSI0CLK
    // TODO: change this to select the port/pin you are using.
    //
    GPIOPinConfigure(GPIO_PD3_SSI2CLK);
//  GPIOPinConfigure(GPIO_PD2_SSI2FSS);
    GPIOPinConfigure(GPIO_PD0_SSI2XDAT1);
    GPIOPinConfigure(GPIO_PD1_SSI2XDAT0);


    GPIOPinTypeSSI(GPIO_PORTD_BASE, GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_3 );    
    //
    // Configure and enable the SSI port for TI master mode.  Use SSI2, system
    // clock supply, master mode, 5MHz SSI frequency, and 8-bit data.
    // 
    // 

    SSIConfigSetExpClk(SSI2_BASE, ui32SysClock, SSI_FRF_MOTO_MODE_3,                //SSI 时钟、MODE-3、主机模式、 8位数据传输;
                                   SSI_MODE_MASTER, 500000, 8);                        // SSI Speed-5MHz
    //
    // Enable the SSI0 module.
    //
    SSIEnable(SSI2_BASE);
    
    GPIOPinTypeGPIOOutput(GPIO_PORTD_BASE, GPIO_PIN_2);
    
    SysCtlDelay(10);
 
}

在使用SSI Advanced mode 时,如果外设对FSS 片选的拉高/拉低有严格的时间要求的话,可以在上面代码中将GPIOPinConfigure(GPIO_PD2_SSI2FSS);注释掉,并使用GPIOPinWrite()函数单独对片选管脚进行拉高/拉低的操作。但是使用之前,务必使用GPIOPinTypeGPIOOutput()函数对改引脚进行初始化的操作。

因为TI自带SSI库函数对操作时,发送数据时,FSS片选拉低是在CLK 的上升沿激活数据帧发送的,并且这个延时的时间只有一个系统时钟周期,时间非常短,所以根本无法与外设的片选延时一致,这就是很多人用高级模式无法准确获取外设返回的准确数据的原因。

SSI 发送/接收数据函数

前面提到,当处于高级(Advanced mode)模式时,发送数据的时候,会自动阻止 RX FIFO(接收缓存区,类似于Buff)接收数据,接收数据时,则是全双工(允许读写!),所以在发送数据的时候,要舍弃从FIFO 里读出的数据,因为此时的数据都是无效的!

发送示例:

extern void SSI_Adv_SndData(SndBuf_s *pSndBuf)
{
    uint16_t i=0;
    
    while(SSIBusy(SSI2_BASE)) {}
        
    GPIOPinWrite(GPIO_PORTD_BASE, GPIO_PIN_2, 0);  //片选拉低,开始数据传输
    SysCtlDelay(30000);        //延时一定时间,用于SPI稳定传输;
    
        
    for(i=0;i<(pSndBuf->Len);i++)
    {
        SSIDataPut(SSI2_BASE,pSndBuf->DataBuf[i]);  
        //这里是C++ 的写法,pSndBuf是一个类,从pSndBuf类中取DataBuf对应的结构体中的数据
        //UARTprintf("\nput data is:%x",pSndBuf->DataBuf[i]);  just for test
        while(SSIBusy(SSI2_BASE)) {}
    }
    
    SysCtlDelay(10);
    GPIOPinWrite(GPIO_PORTD_BASE, GPIO_PIN_2, GPIO_PIN_2);  //片选拉高,停止数据传输
    SysCtlDelay(30000);        //延时一定时间,用于SPI稳定传输;

}


接收示例:

do
    {
        SysCtlDelay(10);    
        while(SSIBusy(SSI2_BASE)) { }
        SSIDataPut(SSI2_BASE,0);// 发送0以保证接口设备在接收数据时,MOSI引脚始终保持低电平
        SSIDataGet(SSI2_BASE, &bytes);
        rcvByte = bytes;
        //UARTprintf("\nthe rcvByte is:%2x",rcvByte);        //just for test
        timeOutCnt--;
    }while(rcvByte!=Flash_BUSY_SW &&  timeOutCnt);

如上,每次接收一个 Byte 数据时,都需要使用SSIDataPut(SSI2_BASE,0); 发送一个0,以保证主机设备在接收数据时,MOSI引脚始终保持低电平;然后再SSIDataGet(SSI2_BASE, &bytes);去获取RX FIFO中的数据,此时的数据才是合法、有效的。

总结

TI 的 SSI 在使用上与 SPI 有类似的地方,但区别还是挺大,坑也有点多。国内用这个的相对少,国内论坛上也找不到很多有实质性帮助的文件、资料,都是依赖官方文档,但是有时候官方文档并没有详述某些功能的详细配置及用法。这就会造成初次使用者的困扰,需配合强大谷歌,看看 TI 英文论坛,或许能帮你找到解决办法,不过 TI 在国外用得好像挺多的,国内打不过 ST 的深入人心23333

此文章首次发表于 编码书生博客 版权归属于编码书生博客,禁止转载。

欢迎点赞评论转发打赏,您的支持就是我持续更新的动力

最后修改:2019 年 09 月 20 日
您的支持就是我持续更新的动力!