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或者其他随机数
的原因!
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
此文章首次发表于 编码书生博客 版权归属于编码书生博客,禁止转载。
欢迎点赞评论转发打赏,您的支持就是我持续更新的动力
版权属于:编码书生
本文链接:https://codess.cc/archives/42.html
所有原创文章采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。
除特别注明,您可以自由的转载和修改,但请务必注明文章来源且不可用于商业目的。