目录
- SPI & ICM-20608 简介
- SPI 简介
- SPI四线
- SPI四种工作模式
- SPI时序图
- I.MX6U ECSPI 简介
- ICM-20608 简介
- 硬件原理分析
- 实验程序编写
- 编译下载验证
- 编写Makefile 和链接脚本
- 编译下载
同I2C 一样,SPI 是很常用的通信接口,也可以通过SPI 来连接众多的传感器。相比I2C 接口,SPI 接口的通信速度很快,I2C 最多400KHz,但是SPI 可以到达几十MHz。I.MX6U 也有4 个SPI 接口,可以通过这4 个SPI 接口来连接一些SPI 外设。I.MX6U-ALPHA 使用SPI3 接口连接了一个六轴传感器ICM-20608,本章我们就来学习如何使用I.MX6U 的SPI 接口来驱动
ICM-20608,读取ICM-20608 的六轴数据。
SPI & ICM-20608 简介
SPI 简介
上一章我们讲解了I2C,I2C 是串行通信的一种,只需要两根线就可以完成主机和从机之间的通信,但是I2C 的速度最高只能到400KHz,如果对于访问速度要求比价高的话I2C 就不适合了。本章我们就来学习一下另外一个和I2C 一样广泛使用的串行通信:SPI。
SPI 全称是 Serial Perripheral Interface,也就是串行外围设备接口。 SPI 是 Motorola 公司推出的一种同步串行接口技术,是一种高速、全双工的同步通信总线, SPI 时钟频率相比 I2C 要高很多,最高可以工作在上百 MHz。 SPI 以主从方式工作,通常是有一个主设备和一个或多个从设备,一般 SPI 需要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 线连接多个从设备的结构如下图所示:
SPI四种工作模式
SPI 有四种工作模式,通过串行时钟极性(CPOL)和相位(CPHA)的搭配来得到四种工作模式:
- ①、 CPOL=0,串行时钟空闲状态为低电平。
- ②、 CPOL=1,串行时钟空闲状态为高电平,此时可以通过配置时钟相位(CPHA)来选择具体的传输协议。
- ③、 CPHA=0,串行时钟的第一个跳变沿(上升沿或下降沿)采集数据。
- ④、 CPHA=1,串行时钟的第二个跳变沿(上升沿或下降沿)采集数据。
这四种工作模式如下图所示:
SPI时序图
以 CPOL=0, CPHA=0 这个工作模式为例, SPI 进行全双工通信的时序如下图所示:
从上图可以看出, SPI 的时序图很简单,不像 I2C 那样还要分为读时序和写时序,因为 SPI 是全双工的,所以读写时序可以一起完成。图中CS 片选信号先拉低,选中要通信的从设备,然后通过 MOSI 和 MISO 这两根数据线进行收发数据, MOSI 数据线发出了0XD2 这个数据给从设备,同时从设备也通过 MISO 线给主设备返回了 0X66 这个数据。这个就是 SPI 时序图。
关于SPI 就讲解到这里,接下来我们看一下I.MX6U 自带的SPI 外设:ECSPI。
I.MX6U ECSPI 简介
I.MX6U 自带的SPI 外设叫做ECSPI,全称是Enhanced Configurable Serial Peripheral Interface,别看前面加了个“EC”就以为和标准SPI 有啥不同的,其实就是SPI。ECSPI 有64 * 32 个接收FIFO(RXFIFO)和64 * 32 个发送FIFO(TXFIFO),ECSPI 特性如下:
①、全双工同步串行接口。
②、可配置的主/从模式。
③、四个片选信号,支持多从机。
④、发送和接收都有一个32x64 的FIFO。
⑤、片选信号SS/CS,时钟信号SCLK 极性可配置。
⑥、支持DMA。
I.MX6U 的ECSPI 可以工作在主模式或从模式,本章我们使用主模式,I.MX6U 有4 个ECSPI,每个ECSPI 支持四个片选信号,也就说,如果你要使用ECSPI 的硬件片选信号的话,一个ECSPI 可以支持4 个外设。如果不使用硬件的片选信号就可以支持无数个外设,本章实验我们不使用硬件片选信号,因为硬件片选信号只能使用指定的片选IO,软件片选的话可以使用
任意的IO。
我们接下来看一下ECSPI 的几个重要的寄存器,首先看一下ECSPIx_CONREG(x=1~4)寄存器,这是ECSPI 的控制寄存器,此寄存器结构如图27.1.2.1 所示:
寄存器ECSPIx_CONREG 各位含义如下:
BURST_LENGTH(bit31:24):突发长度,设置SPI 的突发传输数据长度,在一次SPI 发送中最大可以发送2^12bit 数据。可以设置0X000~ 0XFFF,分别对应1~2^12bit。我们一般设置突发长度为一个字节,也就是8bit,BURST_LENGTH=7。
CHANNEL_SELECT(bit19:18):SPI 通道选择,一个ECSPI 有四个硬件片选信号,每个片选信号是一个硬件通道,虽然我们本章实验使用的软件片选,但是SPI 通道还是要选择的。可设置为0~ 3,分别对应通道0~3。I.MX6U-ALPHA 开发板上的ICM-20608 的片选信号接的是ECSPI3_SS0,也就是ECSPI3 的通道0,所以本章实验设置为0。
DRCTL(bit17:16):SPI 的SPI_RDY 信号控制位,用于设置SPI_RDY 信号,为0 的话不关心SPI_RDY 信号;为1 的话SPI_RDY 信号为边沿触发;为2 的话SPI_DRY 是电平触发。
PRE_DIVIDER(bit15:12):SPI 预分频,ECSPI 时钟频率使用两步来完成分频,此位设置的是第一步,可设置0~ 15,分别对应1~16 分频。
POST_DIVIDER(bit11:8):SPI 分频值,ECSPI 时钟频率的第二步分频设置,分频值为2^POST_DIVIDER。
CHANNEL_MODE(bit7:4):SPI 通道主/从模式设置,HANNEL_MODE[3:0]分别对应SPI通道3~0,为0 的话就是设置为从模式,如果为1 的话就是主模式。比如设置为0X01 的话就是设置通道0 为主模式。
SMC(bit3):开始模式控制,此位只能在主模式下起作用,为0 的话通过XCH 位来开启SPI突发访问,为1 的话只要向TXFIFO 写入数据就开启SPI 突发访问。
XCH(bit2):此位只在主模式下起作用,当SMC 为0 的话此位用来控制SPI 突发访问的开启。
HT(bit1):HT 模式使能位,I.MX6ULL 不支持。
EN(bit0):SPI 使能位,为0 的话关闭SPI,为1 的话使能SPI。
接下来看一下寄存器ECSPIx_CONFIGREG,这个也是ECSPI 的配置寄存器,此寄存器结构如图27.1.2.2 所示:
寄存器ECSPIx_CONFIGREG 用到的重要位如下:
HT_LENGTH(bit28:24):HT 模式下的消息长度设置,I.MX6ULL 不支持。
SCLK_CTL(bit23:20):设置SCLK 信号线空闲状态电平,SCLK_CTL[3:0]分别对应通道3~0,为0 的话SCLK 空闲状态为低电平,为1 的话SCLK 空闲状态为高电平。
DATA_CTL(bit19:16):设置DATA 信号线空闲状态电平,DATA_CTL[3:0]分别对应通道3~0,为0 的话DATA 空闲状态为高电平,为1 的话DATA 空闲状态为低电平。
SS_POL(bit15:12):设置SPI 片选信号极性设置,SS_POL[3:0]分别对应通道3~0,为0 的话片选信号低电平有效,为1 的话片选信号高电平有效。
SCLK_POL(bit7:4):SPI 时钟信号极性设置,也就是CPOL,
SCLK_POL[3:0]分别对应通道3~0,为0 的话SCLK 高电平有效(空闲的时候为低电平),为1 的话SCLK 低电平有效(空闲的时候为高电平)。
SCLK_PHA(bit3:0):SPI时钟相位设置,也就是CPHA,SCLK_PHA[3:0]分别对应通道3~0,为0 的话串行时钟的第一个跳变沿(上升沿或下降沿)采集数据,为1 的话串行时钟的第二个跳变沿(上升沿或下降沿)采集数据。
通过SCLK_POL 和SCLK_PHA 可以设置SPI 的工作模式。
接下来看一下寄存器ECSPIx_PERIODREG,这个是ECSPI 的采样周期寄存器,此寄存器结构如图27.1.2.3 所示:
寄存器ECSPIx_PERIODREG 用到的重要位如下:
CSD_CTL(bit21:16):片选信号延时控制位,用于设置片选信号和第一个SPI 时钟信号之间的时间间隔,范围为0~63。
CSRC(bit15):SPI 时钟源选择,为0 的话选择SPI CLK 为SPI 的时钟源,为1 的话选择32.768KHz 的晶振为SPI 时钟源。我们一般选择SPI CLK 作为SPI 时钟源,SPI CLK 时钟来源如图27.1.2.4 所示:
图27.1.2.4 中各部分含义如下:
①、这是一个选择器,用于选择根时钟源,由寄存器CSCDR2 的位ECSPI_CLK_SEL 来控制,为0 的话选择pll3_60m 作为ECSPI 根时钟源。为1 的话选择osc_clk 作为ECSPI 时钟源。本章我们选择pll3_60m 作为ECSPI 根时钟源。
②、ECSPI 时钟分频值,由寄存器CSCDR2 的位ECSPI_CLK_PODF 来控制,分频值为2^ECSPI_CLK_PODF。本章我们设置为0,也就是1 分频。
③、最终进入ECSPI 的时钟,也就是SPI CLK=60MHz。
SAMPLE_PERIO:采样周期寄存器,可设置为0~0X7FFF 分别对应0~32767 个周期。
接下来看一下寄存器ECSPIx_STATREG,这个是ECSPI 的状态寄存器,此寄存器结构如图27.1.2.5 所示:
寄存器ECSPIx_STATREG 用到的重要位如下:
TC(bit7):传输完成标志位,为0 表示正在传输,为1 表示传输完成。
RO(bit6):RXFIFO 溢出标志位,为0 表示RXFIFO 无溢出,为1 表示RXFIFO 溢出。
RF(bit5):RXFIFO 空标志位,为0 表示RXFIFO 不为空,为1 表示RXFIFO 为空。
RDR(bit4):RXFIFO 数据请求标志位,此位为0 表示RXFIFO 里面的数据不大于RX_THRESHOLD,此位为1 的话表示RXFIFO 里面的数据大于RX_THRESHOLD。
RR(bit3):RXFIFO 就绪标志位,为0 的话RXFIFO 没有数据,为1 的话表示RXFIFO 中至少有一个字的数据。
TF(bit2):TXFIFO 满标志位,为0 的话表示TXFIFO 不为满,为1 的话表示TXFIFO 为满。
TDR(bit1):TXFIFO 数据请求标志位,为0 表示TXFIFO 中的数据大于TX_THRESHOLD,为1 表示TXFIFO 中的数据不大于TX_THRESHOLD。
TE(bit0):TXFIFO 空标志位,为0 表示TXFIFO 中至少有一个字的数据,为1 表示TXFIFO为空。
最后就是两个数据寄存器,ECSPIx_TXDATA 和ECSPIx_RXDATA,这两个寄存器都是32位的,如果要发送数据就向寄存器ECSPIx_TXDATA 写入数据,读取及存取ECSPIx_RXDATA里面的数据就可以得到刚刚接收到的数据。
关于ECSPI 的寄存器就介绍到这里,关于这些寄存器详细的描述,请参考《I.MX6ULL 参考手册》第805 页的20.7 小节。
ICM-20608 简介
ICM-20608 是InvenSense 出品的一款6 轴MEMS 传感器,包括3 轴加速度和3 轴陀螺仪。ICM-20608 尺寸非常小,只有3x3x0.75mm,采用16P 的LGA 封装。ICM-20608 内部有一个512字节的FIFO。陀螺仪的量程范围可以编程设置,可选择±250,±500,±1000 和±2000°/s,加速度的量程范围也可以编程设置,可选择±2g,±4g,±8g 和±16g。陀螺仪和加速度计都是16 位的ADC,并且支持I2C 和SPI 两种协议,使用I2C 接口的话通信速度最高可以达到400KHz,使用SPI 接口的话通信速度最高可达到8MHz。I.MX6U-ALPHA 开发板上的ICM-20608 通过SPI 接口和I.MX6U 连接在一起。ICM-20608 特性如下:
①、陀螺仪支持X,Y 和Z 三轴输出,内部集成16 位ADC,测量范围可设置:±250,±500,±1000 和±2000°/s。
②、加速度计支持X,Y 和Z 轴输出,内部集成16 位ADC,测量范围可设置:±2g,± 4g,±4g,±8g 和±16g。
③、用户可编程中断。
④、内部包含512 字节的FIFO。
⑤、内部包含一个数字温度传感器。
⑥、耐10000g 的冲击。
⑦、支持快速I2C,速度可达400KHz。
⑧、支持SPI,速度可达8MHz。
ICM-20608 的3 轴方向如图27.1.3.1 所示:
ICM-20608 的结构框图如图27.1.3.2 所示:
如果使用IIC 接口的话ICM-20608 的AD0 引脚决定I2C 设备从地址的最后一位,如果AD0为0 的话ICM-20608 从设备地址是0X68,如果AD0 为1 的话ICM-20608 从设备地址为0X69。本章我们使用SPI 接口,跟上一章使用AP3216C 一样,ICM-20608 也是通过读写寄存器来配置和读取传感器数据,使用SPI 接口读写寄存器需要16 个时钟或者更多(如果读写操作包括多个
字节的话),第一个字节包含要读写的寄存器地址,寄存器地址最高位是读写标志位,如果是读的话寄存器地址最高位要为1,如果是写的话寄存器地址最高位要为0,剩下的7 位才是实际的寄存器地址,寄存器地址后面跟着的就是读写的数据。表27.1.3.1 列出了本章实验用到的一些寄存器和位,关于ICM-20608 的详细寄存器和位的介绍请参考ICM-20608 的寄存器手册:
ICM-20608 的介绍就到这里,关于ICM-20608 的详细介绍请参考ICM-20608 的数据手册和寄存器手册。
硬件原理分析
本试验用到的资源如下:
①、指示灯LED0。
②、RGB LCD 屏幕。
③、ICM20608
④、串口
ICM-20608 是在I.MX6U-ALPHA 开发板底板上,原理图如图27.2.1 所示:
实验程序编写
本实验对应的例程路径为:开发板光盘-> 1、裸机例程-> 18_spi。
本章实验在上一章例程的基础上完成,更改工程名字为“icm20608”,然后在bsp 文件夹下创建名为“spi”和“icm20608”的文件。在bsp/spi 中新建bsp_spi.c 和bsp_spi.h 这两个文件,在bsp/icm20608 中新建bsp_icm20608.c 和bsp_icm20608.h 这两个文件。bsp_spi.c 和bsp_spi.h是I.MX6U 的SPI 文件,bsp_icm20608.c 和bsp_icm20608.h 是ICM20608 的驱动文件。在bsp_spi.h中输入如下内容:
1 #ifndef _BSP_SPI_H
2 #define _BSP_SPI_H
3 /***************************************************************
4 Copyright © zuozhongkai 5 文件名: bsp_spi.h
文件bsp_spi.h 内容很简单,就是函数声明。在文件bsp_spi.c 中输入如下内容:
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名: bsp_spi.c
作者: 左忠凯
版本: V1.0
描述: SPI驱动文件。
其他: 无
论坛: www.openedv.com
日志: 初版V1.0 2019/1/17 左忠凯创建
***************************************************************/
1 #include "bsp_spi.h"
2 #include "bsp_gpio.h"
3 #include "stdio.h"
4
5 /*
6 * @description : 初始化SPI
7 * @param - base : 要初始化的SPI
8 * @return : 无
9 */
10 void spi_init(ECSPI_Type *base)
11 {
12 /* 配置CONREG寄存器
13 * bit0 : 1 使能ECSPI
14 * bit3 : 1 当向TXFIFO写入数
文件bsp_spi.c 中有两个函数:spi_init 和spich0_readwrite_byte,函数spi_init 是SPI 初始化函数,此函数会初始化SPI 的时钟,通道等。函数spich0_readwrite_byte 是SPI 收发函数,通过此函数即可完成SPI 的全双工数据收发。
接下来在文件bsp_icm20608.h 中输入如下内容:
1 #ifndef _BSP_ICM20608_H
2 #define _BSP_ICM20608_H
3 /***************************************************************
4 Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
5 文件名: bsp_icm20608.h
6 作者: 左忠凯
7 版本: V1.0
8 描述: ICM20608驱动文件。
9 其他: 无
10 论坛: www.openedv.com
11 日志: 初版V1.0 2019/3/26 左忠凯创建
12 ***************************************************************/
13 #include "imx6ul.h"
14 #include "bsp_gpio.h"
15
16 /* SPI片选信号*/
17 #define ICM20608_CSN(n) (n ? gpio_pinwrite(GPIO1, 20, 1) :
gpio_pinwrite(GPIO1, 20, 0))
18
19 #define ICM20608G_ID 0XAF /* ID值*/
文件bsp_icm20608.h 里面先定义了一个宏ICM20608_CSN,这个是ICM20608 的SPI 片选引脚。接下来定义了一些ICM20608 的ID 和寄存器地址。第41 行定义了一个结构体icm20608_dev_struc,这个结构体是ICM20608 的设备结构体,里面的成员变量用来保存ICM20608 的原始数据值和经过转换得到的实际值。实际值是有小数的,本章例程取两位小数,
为了方便计算,实际值扩大了100 倍,这样实际值就是整数了,但是在使用的时候要除100 重新得到小数部分。最后就是一些函数声明,接下来在文件bsp_icm20608.c 中输入如下所示内容:
/***************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名: bsp_icm20608.c
作者: 左忠凯
版本: V1.0
描述: ICM20608驱动文件。
其他: 无
论坛: www.openedv.com
日志: 初版V1.0 2019/3/26 左忠凯创建
***************************************************************/
1 #include "bsp_icm20608.h"
2 #include "bsp_delay.h"
3 #include "bsp_spi.h"
4 #include "stdio.h"
5
6 struct icm20608_dev_struc icm20608_dev; /* icm20608设备*/
7
8 /*
9 * @description : 初始化ICM20608
10 * @param : 无
11 * @return : 0 初始化成功,其他值初始化失败
12 */
13 unsigned char icm20608_init(void)
14 {
15 unsigned char regvalue;
16 gpio_pin_config_t cs_config;
17
18 /* 1、ESPI3 IO初始化
文件bsp_imc20608.c 是ICM20608 的驱动文件,里面有7 个函数,我们依次来看一下。第1 个函数是icm20608_init,这个是ICM20608 的初始化函数,此函数先初始化ICM20608 所使用的SPI 引脚,将其复用为ECSPI3。因为我们本章的SPI 片选采用软件控制的方式,所以SPI片选引脚设置成了普通的输出模式。设置完SPI 所使用的引脚以后就是调用函数spi_init 来初始化SPI3,最后初始化ICM20608,就是配置ICM20608 的寄存器。第2 个和第3 个函数分别是icm20608_write_reg 和icm20608_read_reg,这两个函数分别用于写/读ICM20608 的指定寄存器。第4 个函数是icm20608_read_len,此函数也是读取ICM20608 的寄存器值,但是此函数可以读取连续多个寄存器的值,一般用于读取ICM20608 传感器数据。第5 和第6 个函数分别是
icm20608_gyro_scaleget 和icm20608_accel_scaleget,这两个函数分别用于获取陀螺仪和加速度计的分辨率,因为陀螺仪和加速度的测量范围设置的不同,其分辨率就不同,所以在计算实际值的时候要根据实际的量程范围来得到对应的分辨率。最后一个函数是icm20608_getdata,此函数就是用于获取ICM20608 的加速度计、陀螺仪和温度计的数据,并且会根据设置的测量范围计算出实际的值,比如加速度的g 值、陀螺仪的角速度值和温度计的温度值。
最后在main.c 中输入如下内容:
/**************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名: main.c
作者: 左忠凯
版本: V1.0
描述: I.MX6U开发板裸机实验19 SPI实验
其他: SPI也是最常用的接口,ALPHA开发板上有一个6轴传感器ICM20608,
这个六轴传感器就是SPI接口的,本实验就来学习如何驱动I.MX6U
的SPI接口,并且通过SPI接口读取ICM20608的数据值。
论坛: www.openedv.com
日志: 初版V1.0 2019/1/17 左忠凯创建
**************************************************************/
1 #include "bsp_clk.h"
2 #include "bsp_delay.h
文件main.c 一开始有两个函数integer_display 和decimals_display,这两个函数用于在LCD上显示获取到的ICM20608 数据值,函数integer_display 用于显示原始数据值,也就是整数值。函数decimals_display 用于显示实际值,实际值扩大了100 倍,此函数会提取出实际值的整数部分和小数部分并显示在LCD 上。另一个重要的函数是imx6ul_hardfpu_enable,这个函数用于开启I.MX6U 的NEON 和硬件FPU(浮点运算单元),因为本章使用到了浮点运算,而I.MX6U 的Cortex-A7 是支持NEON 和FPU(VFPV4_D32)的,但是在使用I.MX6U 的硬件FPU 之前是先要开启的。
第110 行调用了函数icm20608_init 来初始化ICM20608,如果初始化失败的话就会在LCD上闪烁提示语句。最后在main 函数的while 循环中不断的调用函数icm20608_getdata 获取ICM20608 的传感器数据,并且显示在LCD 上。实验程序编写就到这里结束了,接下来就是编译、下载和验证了。
编译下载验证
编写Makefile 和链接脚本
修改Makefile 中的TARGET 为icm20608,然后在在INCDIRS 和SRCDIRS 中加入“bsp/spi”和“bsp/icm20608”,修改后的Makefile 如下:
1 CROSS_COMPILE ?= arm-linux-gnueabihf-
2 TARGET ?= icm20608
3
4 /* 省略掉其它代码...... */
第2 行修改变量TARGET 为“icm20608”,也就是目标名称为“icm20608”。
第23 和24 行在变量INCDIRS 中添加SPI 和ICM20608 的驱动头文件(.h)路径。
第43 和44 行在变量SRCDIRS 中添加SPI 和ICM20608 驱动文件(.c)路径。
第49 行加入了“-march=armv7-a -mfpu=neon-vfpv4 -mfloat-abi=hard”指令,这些指令用于指定编译浮点运算的时候使用硬件FPU。因为本章使用到了浮点运算,而I.MX6U 是支持硬件FPU 的,虽然我们在main 函数中已经打开了NEON 和FPU,但是在编译相应C 文件的时候也要指定使用硬件FPU 来编译浮点运算。
链接脚本保持不变。
编译下载
使用Make 命令编译代码,编译成功以后使用软件imxdownload 将编译完成的icm20608.bin文件下载到SD 卡中,命令如下:
chmod 777 imxdownload //给予imxdownload 可执行权限,一次即可
./imxdownload icm20608.bin /dev/sdd //烧写到SD 卡中,不能烧写到/dev/sda 或sda1 里面!
烧写成功以后将SD 卡插到开发板的SD 卡槽中,然后复位开发板。如果ICM20608 工作正常的话就会在LCD 上显示获取到的传感器数据,如图27.4.2.1 所示:
在图27.4.2.1 中可以看到加速度计Z 轴在静止状态下是0.98g,这正是重力加速度。温度传感器测量到的温度是31.39°C,这个是芯片内部的温度,并不是室温!芯片内部温度一般要比室温高。如果动一下开发板的话加速度计和陀螺仪的数据就会变化。