SPI通信原理

目录

总述

信号线

通信模式和时序

通信模式

通信时序

软件SPI

 初始化代码

四种模拟SPI

CPOL = 0, CPHA = 0, first 模式下读写多字节数据


总述

        SPI 是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola(摩托罗拉)首先在其MC68HCXX系列处理器上定义的。

        SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。

        SPI是全双工且SPI没有定义速度限制,一般的实现通常能达到甚至超过10 Mbps

信号线

SPI接口一般使用四条信号线通信:SDI(数据输入),SDO(数据输出),SCK(时钟),CS(片选)

        MISO: 主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。

        MOSI: 主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。

        SCLK:串行时钟信号,由主设备产生。

        CS/SS:从设备片选信号,由主设备控制。它的功能是用来作为“片选引脚”,也就是选择指定的从设备,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。

通信模式和时序

时钟极性(CPOL)定义了时钟空闲状态电平:

        CPOL=0,表示当SCLK=0时处于空闲态,所以有效状态就是SCLK处于高电平时

        CPOL=1,表示当SCLK=1时处于空闲态,所以有效状态就是SCLK处于低电平时

时钟相位(CPHA)定义数据的采集时间。

        CPHA=0,在时钟的第一个跳变沿(上升沿或下降沿)进行数据采样,在第2个边沿发送数据

        CPHA=1,在时钟的第二个跳变沿(上升沿或下降沿)进行数据采样,在第1个边沿发送数据

通信模式

Mode0:CPOL=0,CPHA=0

此时空闲态时,SCLK处于低电平,数据采样是在第1个边沿,也就是SCLK由低电平到高电平的跳变,所以数据采样是在上升沿(准备数据),(发送数据)数据发送是在下降沿。

Mode1:CPOL=0,CPHA=1

此时空闲态时,SCLK处于低电平,数据发送是在第1个边沿,也就是SCK由低电平到高电平的跳变,所以数据采样是在下降沿,数据发送是在上升沿。

Mode2:CPOL=1,CPHA=0

此时空闲态时,SCLK处于高电平,数据采集是在第1个边沿,也就是SCLK由高电平到低电平的跳变,所以数据采集是在下降沿,数据发送是在上升沿。

Mode3:CPOL=1,CPHA=1

此时空闲态时,SCLK处于高电平,数据发送是在第1个边沿,也就是SCLK由高电平到低电平的跳变,所以数据采集是在上升沿,数据发送是在下降沿。

通信时序

软件SPI

 初始化代码

/**SPI1 GPIO Configuration    
    PA5     ------> SPI1_SCK
    PA6     ------> SPI1_MISO
    PA7     ------> SPI1_MOSI 
    */
#define SPI_SCK_PIN                     GPIO_PIN_5
#define SPI_SCK_GPIO_PORT               GPIOA
#define SPI_MOSI_PIN                    GPIO_PIN_7
#define SPI_MOSI_GPIO_PORT              GPIOA
#define SPI_MISO_PIN                    GPIO_PIN_6
#define SPI_MISO_GPIO_PORT              GPIOA
#define SPI_NSS_PIN                     GPIO_PIN_14
#define SPI_NSS_GPIO_PORT               GPIOD
 
 
#define SPI_SCK_GPIO_CLK_ENABLE()       __HAL_RCC_GPIOA_CLK_ENABLE()
#define SPI_MISO_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOA_CLK_ENABLE()
#define SPI_MOSI_GPIO_CLK_ENABLE()      __HAL_RCC_GPIOA_CLK_ENABLE()
#define SPI_NSS_GPIO_CLK_ENABLE()       __HAL_RCC_GPIOD_CLK_ENABLE()
 
#define MOSI_H  HAL_GPIO_WritePin(SPI_MOSI_GPIO_PORT, SPI_MOSI_PIN, GPIO_PIN_SET)  
#define MOSI_L  HAL_GPIO_WritePin(SPI_MOSI_GPIO_PORT, SPI_MOSI_PIN, GPIO_PIN_RESET)  
#define SCK_H   HAL_GPIO_WritePin(SPI_SCK_GPIO_PORT, SPI_SCK_PIN, GPIO_PIN_SET)  
#define SCK_L   HAL_GPIO_WritePin(SPI_SCK_GPIO_PORT, SPI_SCK_PIN, GPIO_PIN_RESET)  
#define MISO    HAL_GPIO_ReadPin(SPI_MISO_GPIO_PORT, SPI_MISO_PIN) 
#define NSS_H   HAL_GPIO_WritePin(SPI_NSS_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_SET)  
#define NSS_L   HAL_GPIO_WritePin(SPI_NSS_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_RESET) 
	·
 
void SPI_Init(void)
{  
  /*##-1- Enable peripherals and GPIO Clocks #########################*/
  /* Enable GPIO TX/RX clock */
  SPI_SCK_GPIO_CLK_ENABLE();
  SPI_MISO_GPIO_CLK_ENABLE();
  SPI_MOSI_GPIO_CLK_ENABLE();
  SPI_NSS_GPIO_CLK_ENABLE();
 
 
  /*##-2- Configure peripheral GPIO #######################*/
  /* SPI SCK GPIO pin configuration  */
  GPIO_InitTypeDef GPIO_InitStruct;
  
  GPIO_InitStruct.Pin       = SPI_SCK_PIN;
  GPIO_InitStruct.Mode      = GPIO_MODE_OUTPUT_PP;
  //GPIO_InitStruct.Pull      = GPIO_PULLDOWN;
  GPIO_InitStruct.Speed     = GPIO_SPEED_FREQ_VERY_HIGH;
  HAL_GPIO_Init(SPI_SCK_GPIO_PORT, &GPIO_InitStruct);
  HAL_GPIO_WritePin(SPI_SCK_GPIO_PORT, SPI_SCK_PIN, GPIO_PIN_SET);
 
  /* SPI MISO GPIO pin configuration  */
  GPIO_InitStruct.Pin = SPI_MISO_PIN;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  HAL_GPIO_Init(SPI_MISO_GPIO_PORT, &GPIO_InitStruct);
 
  /* SPI MOSI GPIO pin configuration  */
  GPIO_InitStruct.Pin = SPI_MOSI_PIN;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  HAL_GPIO_Init(SPI_MOSI_GPIO_PORT, &GPIO_InitStruct);
  HAL_GPIO_WritePin(SPI_MOSI_GPIO_PORT, SPI_MOSI_PIN, GPIO_PIN_SET);
  
  GPIO_InitStruct.Pin = SPI_NSS_PIN;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  HAL_GPIO_Init(SPI_NSS_GPIO_PORT, &GPIO_InitStruct);
  HAL_GPIO_WritePin(SPI_NSS_GPIO_PORT, SPI_NSS_PIN, GPIO_PIN_SET);
  
}

四种模拟SPI

/* CPOL = 0, CPHA = 0, MSB first */
uint8_t SOFT_SPI_RW_MODE0(uint8_t write_dat)
{
    uint8_t i, read_dat = 0; // 初始化读取数据的变量
    // 遍历处理每一个比特位(8位对于 uint8_t 类型)
    for (i = 0; i < 8; i++)
    {
        // 将最高位(MSB)数据输出到 MOSI 引脚
        if (write_dat & 0x80)
            MOSI_H;  // 如果最高位是 1,则设置 MOSI 为高电平
        else
            MOSI_L;  // 如果最高位是 0,则设置 MOSI 为低电平
        // 将数据字节向左移动一位,为下一个数据位做准备
        write_dat <<= 1;
        // 延时,可能是为了满足时序要求
        delay_us(1);
        // 在 SCK(时钟)上升沿之前,从设备准备采样 MOSI 的数据
        // 这里省略了从设备采样的具体代码,通常在硬件 SPI 中,从设备会在 SCK 上升沿时采样 MOSI 的数据
        // 将 SCK(时钟)置高,主设备在此时刻采样 MISO 上的数据
        SCK_H; 
        // 从 MISO 输入的数据位,被主设备读取到 read_dat 中
        read_dat <<= 1;  // 主设备左移一位,为下一个数据位做准备
        if (MISO) 
            read_dat++;  // 如果 MISO 为高电平,则在 read_dat 中设置相应的位
        // 延时,可能是为了满足时序要求
        delay_us(1);
        // 将 SCK(时钟)置低,结束本次数据传输周期
        SCK_L; 
        // 空操作,可能是为了处理时序
        __nop();
    }
    // 返回读取到的数据
    return read_dat;
}
 
 
/* CPOL=0,CPHA=1, MSB first */
uint8_t SOFT_SPI_RW_MODE1(uint8_t byte) 
{
    uint8_t i,Temp=0;
 
	for(i=0;i<8;i++)     // 循环8次
	{
		SCK_H;     //拉高时钟
		if(byte&0x80)
        {
			MOSI_H;  //若最到位为高,则输出高
        }
		else      
		{
			MOSI_L;   //若最到位为低,则输出低
		}
		byte <<= 1;     // 低一位移位到最高位
		delay_us(1);
		SCK_L;     //拉低时钟
		Temp <<= 1;     //数据左移
 
		if(MISO)
			Temp++;     //若从从机接收到高电平,数据自加一
		delay_us(1);
 
	}
	return (Temp);     //返回数据
}
 
/* CPOL=1,CPHA=0, MSB first */
uint8_t SOFT_SPI_RW_MODE2(uint8_t byte) 
{
    uint8_t i,Temp=0;
 
	for(i=0;i<8;i++)     // 循环8次
	{
		if(byte&0x80)
        {
			MOSI_H;  //若最到位为高,则输出高
        }
		else      
		{
			MOSI_L;   //若最到位为低,则输出低
		}
		byte <<= 1;     // 低一位移位到最高位
		delay_us(1);
		SCK_L;     //拉低时钟
		Temp <<= 1;     //数据左移
 
		if(MISO)
			Temp++;     //若从从机接收到高电平,数据自加一
		delay_us(1);
		SCK_H;     //拉高时钟
		
	}
	return (Temp);     //返回数据
}
 
 
/* CPOL = 1, CPHA = 1, MSB first */
uint8_t SOFT_SPI_RW_MODE3( uint8_t write_dat )
{
    uint8_t i, read_dat;
    for( i = 0; i < 8; i++ )
    {
		SCK_L; 
        if( write_dat & 0x80 )
            MOSI_H;  
        else                    
            MOSI_L;  
        write_dat <<= 1;
        delay_us(1);	
        SCK_H; 
        read_dat <<= 1;  
        if( MISO ) 
            read_dat++; 
		delay_us(1);
        __nop();
    }
    return read_dat;
}
 

CPOL = 0, CPHA = 0, first 模式下读写多字节数据

/*
u16Len: 读写的数据长度
pu8Dat  写入的数据
pu8RxBuf 读取的数据
*/
int soft_spi_trx_bytes ( uint16_t u16Len, uint8_t* pu8Dat, uint8_t* pu8RxBuf)
{
    uint16_t i = 0; 
	
    if (( NULL == pu8Dat ) || ( 0 == u16Len ))
    {
        return 1;
    }
    
    for ( i = 0; i < u16Len; i++ )
    {
       pu8RxBuf[i]=SOFT_SPI_RW_MODE0 ( pu8Dat[i]);
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值