嵌入式Linux开发11——SPI实验

背景知识

  同 I2C 一样, SPI 是很常用的通信接口,也可以通过 SPI 来连接众多的传感器。相比 I2C 接口, SPI 接口的通信速度很快, I2C 最多 400KHz,但是 SPI 可以到达几十 MHz。

SPI简介

  上一章我们讲解了 I2C, I2C 是串行通信的一种,只需要两根线就可以完成主机和从机之间的通信,但是 I2C 的速度最高只能到 400KHz,如果对于访问速度要求比较高的话 I2C 就不适合了。本章我们就来学习一下另外一个和 I2C 一样广泛使用的串行通信: SPI, SPI 全称是 Serial Perripheral Interface,也就是串行外围设备接口。 SPI 是 Motorola 公司推出的一种同步串行接口技术,是一种高速、全双工的同步通信总线, SPI 时钟频率相比 I2C 要高很多,最高可以工作在上百 MHz。 SPI 以主从方式工作,通常是有一个主设备和一个或多个从设备,一般 SPI 需要4 根线,但是也可以使用三根线(单向传输),本章我们讲解标准的 4 线 SPI,这四根线如下:
①、 CS/SS, Slave Select/Chip Select,这个是片选信号线,用于选择需要进行通信的从设备。I2C 主机是通过发送从机设备地址来选择需要进行通信的从机设备的, SPI 主机不需要发送从机设备,直接将相应的从机设备片选信号拉低即可。
②、 SCK, Serial Clock,串行时钟,和 I2C 的 SCL 一样,为 SPI 通信提供时钟。
③、 MOSI/SDO, Master Out Slave In/Serial Data Output,简称主出从入信号线,这根数据线只能用于主机向从机发送数据,也就是主机输出,从机输入。
④、 MISO/SDI, Master In Slave Out/Serial Data Input,简称主入从出信号线,这根数据线只能用户从机向主机发送数据,也就是主机输入,从机输出。
  SPI 通信都是由主机发起的,主机需要提供通信的时钟信号。主机通过 SPI 线连接多个从设备的结构如图:
嵌入式Linux开发11——SPI实验
  SPI 有四种工作模式,通过串行时钟极性(CPOL)和相位(CPHA)的搭配来得到四种工作模式:
①、 CPOL=0,串行时钟空闲状态为低电平。
②、 CPOL=1,串行时钟空闲状态为高电平,此时可以通过配置时钟相位(CPHA)来选择具体的传输协议。
③、 CPHA=0,串行时钟的第一个跳变沿(上升沿或下降沿)采集数据。
④、 CPHA=1,串行时钟的第二个跳变沿(上升沿或下降沿)采集数据。
  这四种工作模式如图所示:
嵌入式Linux开发11——SPI实验  跟I2C 一样, SPI 也是有时序图的,以 CPOL=0, CPHA=0 这个工作模式为例, SPI 进行全双工通信的时序如图:
嵌入式Linux开发11——SPI实验

  从图中可以看出, SPI 的时序图很简单,不像 I2C 那样还要分为读时序和写时序,因为 SPI 是全双工的,所以读写时序可以一起完成。图中, CS 片选信号先拉低,选中要通信的从设备,然后通过 MOSI 和 MISO 这两根数据线进行收发数据, MOSI 数据线发出了0XD2 这个数据给从设备,同时从设备也通过 MISO 线给主设备返回了 0X66 这个数据。这个就是 SPI 时序图。

I.MX6U ECSPI 简介

  I.MX6U 自带的 SPI 外设叫做 ECSPI,全称是 Enhanced Configurable Serial Peripheral Interface,别看前面加了个“EC”就以为和标准 SPI 有啥不同的, 其实就是 SPI。 ECSPI 有 6432 个接收FIFO(RXFIFO)和 6432 个发送 FIFO(TXFIFO), ECSPI 特性如下:
①、全双工同步串行接口。
②、可配置的主/从模式。
③、四个片选信号,支持多从机。
④、发送和接收都有一个 32x64 的 FIFO。
⑤、片选信号 SS/CS,时钟信号 SCLK 极性可配置。
⑥、支持 DMA。
  I.MX6U 的 ECSPI 可以工作在主模式或从模式,I.MX6U 有 4 个ECSPI,每个 ECSPI 支持四个片选信号,也就说,如果你要使用 ECSPI 的硬件片选信号的话,一个 ECSPI 可以支持 4 个外设。如果不使用硬件的片选信号就可以支持不限个外设。

程序编写

SPI.c

/*
 * @description		: 初始化SPI
 * @param - base	: 要初始化的SPI
 * @return 			: 无
 */
void spi_init(ECSPI_Type *base)
{
	/* 配置CONREG寄存器
	 * bit0 : 		1 	使能ECSPI
	 * bit3 : 		1	当向TXFIFO写入数据以后立即开启SPI突发。
	 * bit[7:4] : 	0001 SPI通道0主模式,根据实际情况选择,
	 *            	   	开发板上的ICM-20608接在SS0上,所以设置通道0为主模式
	 * bit[19:18]:	00 	选中通道0(其实不需要,因为片选信号我们我们自己控制)
	 * bit[31:20]:	0x7	突发长度为8个bit。 
	 */
	base->CONREG = 0; /* 先清除控制寄存器 */
	base->CONREG |= (1 << 0) | (1 << 3) | (1 << 4) | (7 << 20); /* 配置CONREG寄存器 */

	/*
     * ECSPI通道0设置,即设置CONFIGREG寄存器
     * bit0:	0 通道0 PHA为0
     * bit4:	0 通道0 SCLK高电平有效
     * bit8: 	0 通道0片选信号 当SMC为1的时候此位无效
     * bit12:	0 通道0 POL为0
     * bit16:	0 通道0 数据线空闲时高电平
     * bit20:	0 通道0 时钟线空闲时低电平
	 */
	base->CONFIGREG = 0; 		/* 设置通道寄存器 */
	
	/*  
     * ECSPI通道0设置,设置采样周期
     * bit[14:0] :	0X2000  采样等待周期,比如当SPI时钟为10MHz的时候
     *  		    0X2000就等于1/10000 * 0X2000 = 0.8192ms,也就是连续
     *          	读取数据的时候每次之间间隔0.8ms
     * bit15	 :  0  采样时钟源为SPI CLK
     * bit[21:16]:  0  片选延时,可设置为0~63
	 */
	base->PERIODREG = 0X2000;		/* 设置采样周期寄存器 */

	/*
     * ECSPI的SPI时钟配置,SPI的时钟源来源于pll3_sw_clk/8=480/8=60MHz
     * 通过设置CONREG寄存器的PER_DIVIDER(bit[11:8])和POST_DIVEDER(bit[15:12])来
     * 对SPI时钟源分频,获取到我们想要的SPI时钟:
     * SPI CLK = (SourceCLK / PER_DIVIDER) / (2^POST_DIVEDER)
     * 比如我们现在要设置SPI时钟为6MHz,那么PER_DIVEIDER和POST_DEIVIDER设置如下:
     * PER_DIVIDER = 0X9。
     * POST_DIVIDER = 0X0。
     * SPI CLK = 60000000/(0X9 + 1) = 60000000=6MHz
	 */
	base->CONREG &= ~((0XF << 12) | (0XF << 8));	/* 清除PER_DIVDER和POST_DIVEDER以前的设置 */
	base->CONREG |= (0X9 << 12);					/* 设置SPI CLK = 6MHz */
}

/*
 * @description		: SPI通道0发送/接收一个字节的数据
 * @param - base	: 要使用的SPI
 * @param - txdata	: 要发送的数据
 * @return 			: 无
 */
unsigned char spich0_readwrite_byte(ECSPI_Type *base, unsigned char txdata)
{ 
	uint32_t  spirxdata = 0;
	uint32_t  spitxdata = txdata;

    /* 选择通道0 */
	base->CONREG &= ~(3 << 18);
	base->CONREG |= (0 << 18);

  	while((base->STATREG & (1 << 0)) == 0){} /* 等待发送FIFO为空 */
		base->TXDATA = spitxdata;
	
	while((base->STATREG & (1 << 3)) == 0){} /* 等待接收FIFO有数据 */
		spirxdata = base->RXDATA;
	return spirxdata;
}


  SPI.c中有两个函数:spi_init 和 spich0_readwrite_byte,函数 spi_init 是 SPI 初始化函数,此函数会初始化 SPI 的时钟,通道等。函数 spich0_readwrite_byte 是 SPI 通道0收发函数,通过此函数即可完成 SPI 的全双工数据收发。

上一篇:Android studio的make Project、clean Project、Rebuild Project


下一篇:stm32之SPI学习