1、基本概念区分 (1)SPI和SSP区别 可能很多人在其他的地方都多次到过SPI和SSP,比如爱NXP的LPC11XX系列的SOC手册中多次出现SSP,这里作统一区分:SSP(Synchronize Serial Port,同步串行口)和SPI(Serial Peripheral Interface,串行外设接口)。SSP是在SOC和一些串行外部设备通信的通信模块,他有两种工作模式:SPI和IIC。
(2)SPI协议和代码关系 我们在使用SPI接口进行数据通信的时候,之所以要了解SPI通信协议的原因是我们需要了解SPI通信协议原理。虽然实际硬件通讯硬件电路已经做好了,但是我们还是需要通过配置寄存器来实现SPI通讯时序,所以说还是要学会SPI通信原理的。
了解SPI通信原理只是第一步,第二步是阅读硬件用户手册,根据手册指示编写配置寄存器代码,实现基本配置(时钟频率、数据帧格式、引脚功能等),第三步就是实际收发接口程序编写。
2、SPI硬件接口 通过上图可以知道,首先SPI通信有4线:SCK(同步时钟信号)、MISO(主接收从发送)、MOSI(主发送从接收)、NSS(片选信号)。SPI通信最少3根线:SCK同步时钟、NSS(或者CS)片选信号、MISO或者MOSI。
SCK:作为同步通信的时钟,指示什么时候开始/结束通讯,以及中间数据传输(主设备什么时候发送,从设备什么时候接收)。时钟由主设备发出,控制。
MISO:SPI通信分为主设备(Master)和从设备(Slave),主设备intput,从设备output
MOSI:主设备发送(output输出),从设备接收(intput输入)
NSS:SPI通信分为一主多从,主设备要想从多个从设备中选中和哪个从设备通信,直接将和从设备连接的NSS引脚拉低即可。这个和IIC不一样,IIC直接发送从设备ID来选择从设备。
3、专业术语解释 (1)空闲态 指的是SPI总线上没有数据发送时候的状态。
(2)时钟极性(CPOL) SCK时钟线,在SPI总线空闲时候,所处的电平状态(帧与帧之间的状态):
CPOL = 0,SCK空闲时,主设备持续发送低电平
CPOL = 1,SCK空闲时,主设备持续发送高电平
(3)时钟相位(CPHA) 时钟相位,实际指的就是发送和接受双方如何协调收发:
CPHA = 0,无论收发方,在第一个边沿(上升沿还是下降沿看CPOL=0还是1)处采样(读取数据),在第二个边沿处发送数据
CPHA = 1,在第二个边沿处
(上升沿还是下降沿看CPOL=0还是1)处采样(读取数据),在第一个边沿处发送数据。4、SPI通信协议 SPI通信协议总结起来就是一句话,解决SPI通信问题: (1)数据发送高位在前还是低位在前:比如0x55(0101 0101),先发送第0位的1还是第7位的0? 高位在前,先发送0(MSB),最后发送1(LSB)
(2)怎么区分正在发送数据还是处在空闲,例如0xff(1111 1111):区分空闲和数据发送? 在开始发送之前SCK处在空闲态(根据CPOL时钟极性设置规定,持续输出高电平(CPOL=1)或者低电平(CPOL=0)),开始发送时候,主设备会控制SCK发生变化(低电平转高电平,高电平转换低电平),知道数据发送完,SCK会继续回到空闲状态(持续输出高电平或者低电平)。
(3)发送方什么时候发送? 根据CPHA时钟相位设置,向数据线上放数据:CPHA=1,在第一个边沿处,向数据线上放数据(发送),CPHA=0,在第二个边沿处,向数据线上放数据(发送)
(4)接收方什么时候接收? 根据CPHA设置采样(读取数据):CPHA=1,在第二个边沿处,从数据线上采样(读取数据),CPHA=0,在第一个边沿处,从数据线上采样(读取数据)。
(5)总结 从上图可以看出SPI通信双方内部的基本结构,在主设备在发送的时候,从设备也在发送,只不过主设备发送的数据是我们(程序员)放进去的有效数据,但是从设备发送过来的数据确是不确定的(当然我们也可以自己指定垃圾数据,比如0xff或者0x00)。
所以SPI通信的特点就是,发送方发送数据的时候,是将实现准备好的有效数据在CPHA规定的边沿发送出去的(比如MOSI上,主设备发送数据),同一时刻从设备也在CPHA规定的边沿发送数据(比如MISO上,从设备发送),然后在CPHA规定的采样边沿处,收发双方都会从MOSI或者MISO上去采样(读取数据)。
总结起来就是一句话:发送方发送数据的同时,接收方也在发送数据,只不过发送方发的是实际有意义的数据,接收方发送的是无效垃圾数据(这是SPI通信方式所限定的)。5、LPC11C14平台特点 在开始在LPC11C14平台上编写SPI通信代码接口之前,将这几个问题搞明白就可以了:
(1)既然SPI都是一位一位的传送的,那么我(程序员)发送或者接收的时候可是都以byte为单位的,怎么去界定已经发送完或者接收完一个byte? 在LPC11C14的芯片手册中,可以知道,整个SPI的传输都是以8bit一个字节为单位的(一帧),当帧传输完成时,如果想要传输多个字节,在每个字节传输后,释放总线(还原SCK到空闲态,拉高NSS片选线),这样会将SPI标记寄存器中关于接受和发送标记置位。
(2)发送和接受有没有FIFO?FIFO的大小是多少? LPC11C14上发送和接受的FIFO是公用一个的(环路收发),都是2字节(16bit)
(3)发送和接受方式是轮训?还是中断? SPI的接收和发送均是轮训方式的,当然也可以设置成中断(SSP0IMSC寄存器,半满中断(FIFO收够1个字节)),建议不这么做,因为单个byte就产生一次中断,中断次数太频繁,容易打断CPU主程序执行
(4)需要写几个接口?每个接口什么功能?
需要至少2个接口:
a、SPI初始化配置接口:void spi__0_config(void)
完成相关引脚功能、时钟选择和分频、SPI通信帧格式、主从角 {MOD}设置、时钟极性和时钟相位设置
/****************************************************
* 函数名称:spi_0_config
* 功能描述:配置SPI通讯接口:引脚功能、时钟频率、主从模式、数据帧格式
* 参数:无
* 返回值:无
*****************************************************/
void spi_0_config(void)
{
//1、spi引脚初始化:CLK-PIO0_6,MOSI0-PIO0_9,MISO-PIO0_8,C/S-PIO2_4
LPC_IOCON->PIO0_9 &= ~(0x7<<0);
LPC_IOCON->PIO0_9 |= (1<<0); //MOSI0
LPC_IOCON->PIO0_8 &= ~(0x7<<0);
LPC_IOCON->PIO0_8 |= (1<<0); //MISO0
LPC_IOCON->PIO0_6 &= ~(0x7<<0);
LPC_IOCON->PIO0_6 |= (2<<0); //SCK0
//2、设置SCK0管脚时钟功能
LPC_IOCON->SCK_LOC &= ~(0x3<<0);
LPC_IOCON->SCK_LOC |= (2<<0);
//3、设置SSP0总线时钟使能
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<11);
//4、SPI时钟分频设置
LPC_SYSCON->SSP0CLKDIV &= ~(0xff<<0);
LPC_SYSCON->SSP0CLKDIV |= 2<<0;
//5、关闭SSP0复位
LPC_SYSCON->PRESETCTRL |= (1<<0);
//6、使能GPIO时钟输出功能:SPI的SCK输出
LPC_SYSCON->SYSAHBCLKCTRL |= (1<<6);
//7、设置SPI的偏选引脚为输出功能:PIO2_4
LPC_IOCON->PIO2_4 &= ~(0x7<<0);
LPC_GPIO2->DIR |= (1<<4); //设置PIO2_4引脚为输出功能
LPC_GPIO2->DATA |= (1<<4); //设置PIO2_4引脚输出高电平(默认悬空不选中)
//8、SPI通讯时钟选择、总线类型、数据长度设置
LPC_SSP0->CR0 &= ~(0xf<<0);
LPC_SSP0->CR0 |= (7<<0); //设置帧长度为8bit
LPC_SSP0->CR0 &= ~(3<<4); //设置帧格式为SPI
LPC_SSP0->CR0 &= ~(3<<6); //SPI时钟极性为低电平,在第一个边沿采样,第二个边沿输出
LPC_SSP0->CR0 &= ~(0xf<<8);
LPC_SSP0->CR0 |= (7<<8); //设置时钟分频因子7+1
LPC_SSP0->CPSR = 2;//分频结果 = 48MHz/(2*(7+1)) = 3MHz
//9、设置SPI通讯模式为主设备模式,使能SPI
LPC_SSP0->CR1 &= ~(0xf<<0);
LPC_SSP0->CR1 |= (1<<1);
}
b、spi收发接口:unsigned short spi_put_get(unsigned short data)
由于SPI发送和接受的特点:发送1个字节,必然会有一个字节数据交换接收回来,所以,如果是发送数据,对于返回自己不需要考虑(交换得来的垃圾数据);如果是接收数据,发送数据(垃圾无效数据),将交换得来的字节读取即可。
/*****************************************************
* 函数名称:spi0_put_get
* 功能描述:收发一体接口,用来接收数据或者发送数据
* 参数:data,发送数据,
* 返回值: 接收的数据
***************************************************/
unsigned char spi0_put_get(unsigned char data)
{
//等待spi空闲,并且发送FIFO不为满
while((LPC_SSP0->SR & ((1<<1) | (1<<4))) != (1<<1))
{
;
}
//发送数据
LPC_SSP0->DR = data;
//等待接收FIFO不为空
while((LPC_SSP0->SR & (1<<2)) == 0)
{
;
}
//返回接收数据
return LPC_SSP0->DR;
}
c、spi接收接口:void spi_receive(unsigned char *buf, unsigned int size)
单独接收数据功能的接口
d、spi发送接口:void spi_send(unsigned char *data, unsigned int size)
单独发送数据功能的接口
/****************************************************
* 函数名称:ssp0_send
* 功能描述:spi发送数据接口
* 参数:data,发送的数据,size,发送字节数
* 返回值:无
*****************************************************/
void ssp0_send(unsigned char *data, unsigned int size)
{
unsigned int ret = 0;
unsigned int i = 0;
unsigned char byte = 0;
if(size <= 0)
return ;
for(i=size; i>0; i--) //循环发送
{
//判断SPI是否繁忙、发送FIFO中是否已满
while(((LPC_SSP0->SR & (1<<4)) != 0) && ((LPC_SSP0->SR & (1<<1)))) /*SPI处在繁忙状态 && 发送FIFO未满*/
{
;
}
//开始发送数据
LPC_SSP0->DR = *data;
data++;
//等待接收交换字节
while((LPC_SSP0->SR & (1<<2)) == 0)//接收FIFO为空
{
;
}
//读取接收FIFO
byte = LPC_SSP0->DR ;
}
}
(5)使用SPI通信方式,SCK时钟频率设置多少?时钟极性和时钟相位该怎么设? 首先在SPI通信过程中,LPC11C14控制器属于主设备角 {MOD},本次实验中的从设备通信对象是OLED显示屏,所以时钟频率具体怎么设置全看从设备OLED的SPI极限频率,只要主设备时钟频率设置的低于OLED时钟上线即可(这里OLED上限是3.3MHz),lpc11c14的外设总线频率是48MHz,根据此条件设置分频因子即可。
时钟极性和时钟相位,在本实验中,主要是由从设备(OLED显示屏)决定,从OLED显示屏驱动芯片SSD1306芯片手册得到,SPI采样必须在上升沿,因此可以根据这个信息任意设置时钟极性和时钟相位,只要让采样发生在上升就可以了,这里使用时钟极性和时钟相位都设置为0(直接在第一个时钟边沿采样,也是在上升沿采样)
以上内容就是针对LPC11C14平台上关于SPI通讯接口的实验内容,如果有问题,可以留言告诉我。