文章目录

前言
在【ARM 裸机开发 (IMX6ULL-mini)】I2C协议详解这篇文章中详细讲解了IMX6ULL上的I2C协议,这篇文章将讲解另一个常用的通信协议SPI。两者在许多外设中,常常都能够使用,但还是有很多差异的,在这篇文章最后,会总结两者的区别。除此以外,本篇文章还会介绍ADXL345加速度传感器与IMX6ULL的SPI通信。
一、SPI基础概念
SPI :Serial Peripheral interface(串行外设接口)
SPI 接口主要应用在 EEPROM,FLASH,实时时钟,AD 转换器,还有数字信号处理器和数字信号解码器之间。SPI 是一种高速 的,全双工 ,同步 的串行 通信总线。SPI数据发送与I2C一样是高位先行。
标准SPI采用四线 组网方式,如下图:

➢SCLK :时钟信号线。与I2C类似的,所有设备都都需要时钟信号线进行同步,该信号由主机负责提供;
➢ MOSI :主出从入,相当于主机的数据发送线;
➢ MISO :主入从出,相当于主机的数据接收线;
➢ CS:片选信号,与I2C不同,SPI设备没有所谓的从机地址,连接在总线上的设备通过CS片选信号选择。在同一时刻,总线上只有一个外设被选择,CS信号线通常是低电平有效的。
二、SPI的时序
SPI的通信时序与I2C不同,I2C的时序是固定的,时钟线(SCL)上的信号为高电平期间,数据线(SDA)上的数据必须保持稳定。只有在时钟线(SCL)上的信号为低电平期间,数据线(SCL)上的高电平或低电平状态才允许变化。但SPI的时序是取决于**时钟极性(Clock Polarity 简称CPOL)和时钟相位(Clock Phase简称CPHA)**的。
➢ CPOL :规定了时钟信号线在空闲时的状态,CPOL=0表示时钟信号线在空闲时为低电平;CPOL=1表示时钟信号线在空闲是为高电平;
➢ CPHA:与I2C不同,SPI的数据接收方并不是在时钟高或者低电平时采样的,而是在时钟信号处于沿时,至于是在上升沿还是下降沿取决于CPHA。CPHA=0表示是在时钟信号变化的第一个沿时采样,CPHA=1表示是在时钟信号变化的第二个沿时采样。
因此,我们可以得到这样的时序图:

无论哪种工作方式,SPI这种串行通信方式都是MSB先行的。
和I2C类似,大多数情况下与SPI外设的通信其实就是读写外设寄存器,这些寄存器同样也有着各自的地址。由于四线制SPI是全双工通信方式,在发送一个字节数据的同时,其实也就接收到了一个字节,因此,对于SPI来说,发送其实也就是接收 。IMX6ULL有一个发送缓冲寄存器 ,同时也有一个接收缓冲寄存器 ,发送完一个字节之后,接收缓冲寄存器也会收到一个字节。

三、IMX6ULL上的SPI
3.1 概念及原理框图
IMX6ULL 自带的 SPI 外设叫做 ECSPI,全称是 Enhanced Configurable Serial Peripheral Interface,增强的SPI。ECSPI 有 6432 个接收**FIFO(RXFIFO)**和 64 32 个发送 FIFO(TXFIFO)。
IMX6ULL中一共有3个SPI。其中SPI3是引出了引脚。
IMX6ULL参考手册中的原理框图:

| 名称 | 全称 | 作用 |
|---|---|---|
| TXDATA | 发送FIFO | TX FIFO,64×32bit,CPU 往这里写待发送数据,写进去就进发送缓冲区 |
| RXDATA | 接收FIFO | RX FIFO,64×32bit,移位寄存器收到的数据存入这里;CPU 读这个寄存器拿收到的数据 |
| STATREG | 状态寄存器 | 标记 FIFO 满 / 空、传输完成、错误标志位 |
| INTREG | 中断寄存器 | 中断使能、中断标志控制,产生Interrupt Request |
| DMAREG | DMA 寄存器 | DMA 相关配置,产生DMA Requests |
| PERIODREG | 周期寄存器 | 硬件片选延时 |
| TESTREG | 测试寄存器 | 模块自测,调试用 |
| CONREG | 控制寄存器 | 核心配置:主 / 从模式、CPOL/CPHA、MSB/LSB、数据位宽、硬件 SS 使能 |
3.2 相关寄存器

在一般的SPI通信中我们主要用到RXDATA、TXDATA、CONREG、CONFIGREG、STATREG这五个寄存器:
RXDATA

TXDATA

CONREG


CONFIGREG


STATREG


四、ADXL345加速度传感器
ADXL345是一款三轴加速度传感器,主要特点如下:
➢ 测量范围:±2g、±4g、±8g、±16g设置;
➢ 分辨率:最高13位(3.9mg/LSB);
➢ 工作电压:2.0V至3.6V;
➢ 低功耗:测量模式仅0.23mA,待机模式40μA;
➢ 核心功能:1、三轴加速度测量(X、Y、Z);2、静态重力加速度测量; 3、动态加速度(震动、运动)测量; 4、自由落体检测; 5、单击/双击检测; 6、活动/非活动监测;
➢ 接口支持:SPI(3线和4线模式)、I2C数字接口
➢ 特色功能:1、内置32级FIFO缓冲; 2、可编程中断功能; 3、自动休眠和唤醒功能;
➢ 应用场景:1、移动设备方向检测; 2、游戏和虚拟现实控制器;3、运动检测和计步器;4、防盗系统的运动检测;5、工业振动监测;6、医疗设备姿态检测
时序图:

通过时序图可见ADXL345采用的是SPI通信模式3,即CPOL=1.CPHA=1 。
这里需要注意一个细节,ADXL345在向寄存器写入和读取时,寄存器的最高位是不一样的,读的时候最高位为1 ,写的时候最高位为0。例如写数据到0x20寄存器时,先发0x20再发要写入的数据;读取时则需要先发0xA0,之后进入读取时序。
具体操作也是根据ADXL345参考手册操作相关寄存器。

五、SPI初始化
SPI3的四条线都是GPIO复用引脚,因此,我们需要将其配置为SPI模式。然后对SPI相关寄存器配置成我们想要的模式。
c
void spi3_channel0_init(void)
{
//片选
IOMUXC_SetPinMux(IOMUXC_UART2_TX_DATA_GPIO1_IO20, 0);
IOMUXC_SetPinConfig(IOMUXC_UART2_TX_DATA_GPIO1_IO20, 0x10B1);
//时钟
IOMUXC_SetPinMux(IOMUXC_UART2_RX_DATA_ECSPI3_SCLK, 0);
IOMUXC_SetPinConfig(IOMUXC_UART2_RX_DATA_ECSPI3_SCLK, 0x10B1);
//MISO
IOMUXC_SetPinMux(IOMUXC_UART2_RTS_B_ECSPI3_MISO, 0);
IOMUXC_SetPinConfig(IOMUXC_UART2_RTS_B_ECSPI3_MISO, 0x10B1);
//MOSI
IOMUXC_SetPinMux(IOMUXC_UART2_CTS_B_ECSPI3_MOSI, 0);
IOMUXC_SetPinConfig(IOMUXC_UART2_CTS_B_ECSPI3_MOSI, 0x10B1);
//通信前,CS拉高
gpio_init(GPIO1, 20, &(GPIO_Type_t){1, gpio_output});
//SPI寄存器设置
ECSPI3->CONREG = 0;
ECSPI3->CONREG |= (7 << 20) | (0B1110 << 12) | (2 << 8) | (1 << 4) | (1 << 3) | (1 << 0);
ECSPI3->CONFIGREG = 0;
ECSPI3->CONFIGREG |= (1 << 20) | (1 << 4) | (1 << 0);
}
六、SPI读写函数
在完成初始化后,根据SPI读即是写,写即是读的特点,我们可以写出这样一个读写函数:
c
unsigned char spi3_read_write(unsigned char data)
{
while((ECSPI3->STATREG & (1 << 0)) == 0);
ECSPI3->TXDATA = data;
while((ECSPI3->STATREG & (1 << 3)) == 0);
return ECSPI3->RXDATA;
}
七、ADXL345与IMX6ULL通信
根据ADXL345参考手册寄存器配置:0x31=0x0B 开启满分辨率 ,±16g,0.0039g/LSB 换算公式。原始数据拼接:低字节0x32在前,高字节0x33左移 8 位,CS 软件控制片选等,可以写出ADXL345的读函数、写函数、初始化函数、数据获取函数等。
c
struct ADXL345_Data {
short x;
short y;
short z;
float x_g;
float y_g;
float z_g;
};
extern void adxl345_write(unsigned char reg_addr, unsigned char data);
extern unsigned char adxl345_read(unsigned char reg_addr);
extern void adxl345_init(void);
extern struct ADXL345_Data adxl345_read_data(void);
c
void adxl345_write(unsigned char reg_addr, unsigned char data)
{
// CS low
gpio_set_value(GPIO1, 20, 0);
//主机发送寄存器地址
spi3_read_write(reg_addr);
//主机发送数据
spi3_read_write(data);
// CS high
gpio_set_value(GPIO1, 20, 1);
}
unsigned char adxl345_read(unsigned char reg_addr)
{
unsigned char data;
// CS low
gpio_set_value(GPIO1, 20, 0);
//主机发送读取的寄存器地址
spi3_read_write(reg_addr | 0x80);
//主机接收数据
data = spi3_read_write(0x00);
// CS high
gpio_set_value(GPIO1, 20, 1);
//返回数据
return data;
}
void adxl345_init(void)
{
//关闭中断
adxl345_write(0x2E, 0x08);
//设置满分辨率和满量程
adxl345_write(0x31, 0x0B);
//设置低功耗模式+配置采样速率
adxl345_write(0x2C, 0x08);
//配置测量和唤醒模式
adxl345_write(0x2D, 0x0B);
}
struct ADXL345_Data adxl345_read_data(void)
{
struct ADXL345_Data data;
//x轴数据
data.x = adxl345_read(0x32) | adxl345_read(0x33) << 8;
//y轴数据
data.y = adxl345_read(0x34) | adxl345_read(0x35) << 8;
//z轴数据
data.z = adxl345_read(0x36) | adxl345_read(0x37) << 8;
//获得三轴加速度
data.x_g = data.x * 0.0039;
data.y_g = data.y * 0.0039;
data.z_g = data.z * 0.0039;
return data;
}
八、UART、I2C与SPI对比
