NXP

arm底层通讯协议之SPI通讯

2019-07-12 13:21发布

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通讯接口的实验内容,如果有问题,可以留言告诉我。