正点原子 发表于 2020-6-8 16:03:47

【正点原子Linux连载】第二十七章SPI实验--摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南

本帖最后由 正点原子 于 2020-10-24 16:08 编辑

1)实验平台:正点原子阿尔法Linux开发板
2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html
4)本章实例源码下载:
5)对正点原子Linux感兴趣的同学可以加群讨论:876919289
6)关注正点原子公众号,获取最新资料





第二十七章SPI实验
      
      同I2C一样,SPI是很常用的通信接口,也可以通过SPI来连接众多的传感器。相比I2C接口,SPI接口的通信速度很快,I2C最多400KHz,但是SPI可以到达几十MHz。I.MX6U也有4个SPI接口,可以通过这4个SPI接口来连接一些I2C外设。I.MX6U-ALPHA使用SPI3接口连接了一个六轴传感器ICM-20608,本章我们就来学习如何使用I.MX6U的SPI接口来驱动ICM-20608,读取ICM-20608的六轴数据。

27.1 SPI & ICM-20608简介
27.1.1 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线连接多个从设备的结构如图27.1.1.1所示:

图27.1.1.1 SPI设备连接图
SPI有四种工作模式,通过串行时钟极性(CPOL)和相位(CPHA)的搭配来得到四种工作模式:
①、CPOL=0,串行时钟空闲状态为低电平。
②、CPOL=1,串行时钟空闲状态为高电平,此时可以通过配置时钟相位(CPHA)来选择具体的传输协议。
③、CPHA=0,串行时钟的第一个跳变沿(上升沿或下降沿)采集数据。
④、CPHA=1,串行时钟的第二个跳变沿(上升沿或下降沿)采集数据。
这四种工作模式如图27.1.1.2所示:

图27.1.1.2 SPI四种工作模式
      根I2C一样,SPI也是有时序图的,以CPOL=0,CPHA=0这个工作模式为例,SPI进行全双工通信的时序如图27.1.1.3所示:

图27.1.1.3 SPI时序图
      从图27.1.1.3可以看出,SPI的时序图很简单,不像I2C那样还要分为读时序和写时序,因为SPI是全双工的,所以读写时序可以一起完成。图27.1.1.3中,CS片选信号先拉低,选中要通信的从设备,然后通过MOSI和MISO这两根数据线进行收发数据,MOSI数据线发出了0XD2这个数据给从设备,同时从设备也通过MISO线给主设备返回了0X66这个数据。这个就是SPI时序图。
      关于SPI就讲解到这里,接下来我们看一下I.MX6U自带的SPI外设:ECSPI。
27.1.2 I.MX6U ECSPI简介
      I.MX6U自带的SPI外设叫做ECSPI,全称是Enhanced Configurable Serial Peripheral Interface,别看前面加了个“EC”就以为和标准SPI有啥不同的,起始就是SPI。ECSPI有64x32个接收FIFO(RXFIFO)和64x32个发送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所示:

图27.1.2.1寄存器ECSPIx_CONREG结构
      寄存器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通道主/从模式设置,CHANNEL_MODE分别对应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所示:

图27.1.2.2寄存器ECSPIx_CONFIGREG结构
      寄存器ECSPIx_CONREG用到的重要位如下:
      HT_LENGTH(bit28:24):HT模式下的消息长度设置,I.MX6ULL不支持。
      SCLK_CTL(bit23:20):设置SCLK信号线空闲状态电平,SCLK_CTL分别对应通道3~0,为0的话SCLK空闲状态为低电平,为1的话SCLK空闲状态为高电平。
      DATA_CTL(bit19:16):设置DATA信号线空闲状态电平,DATA_CTL分别对应通道3~0,为0的话DATA空闲状态为高电平,为1的话DATA空闲状态为低电平。
      SS_POL(bit15:12):设置SPI片选信号极性设置,SS_POL分别对应通道3~0,为0的话片选信号低电平有效,为1的话片选信号高电平有效。
      SCLK_POL(bit7:4):SPI时钟信号极性设置,也就是CPOL,SCLK_POL分别对应通道3~0,为0的话SCLK高电平有效(空闲的时候为低电平),为1的话SCLK低电平有效(空闲的时候为高电平)。
SCLK_PHA(bit3:0):SPI时钟相位设置,也就是CPHA,SCLK_PHA分别对应通道3~0,为0的话串行时钟的第一个跳变沿(上升沿或下降沿)采集数据,为1的话串行时钟的第二个跳变沿(上升沿或下降沿)采集数据。
通过SCLK_POL和SCLK_PHA可以设置SPI的工作模式。
      接下来看一下寄存器ECSPIx_PERIODREG,这个是ECSPI的采样周期寄存器,此寄存器结构如图27.1.2.3所示:

图27.1.2.3寄存器ECSPIx_PERIODREG结构
      寄存器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 SPI CLK时钟源
图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所示:

图27.1.2.5寄存器ECSPIx_STATREG寄存器
      寄存器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小节。
27.1.3 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,±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所示:

图27.1.3.1 ICM-20608检测轴方向和极性
      ICM-20608的结构框图如图27.1.3.2所示:

图27.1.3.2 ICM-20608框图
      如果使用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的寄存器手册:


表27.1.3.1 ICM-20608寄存器表
ICM-20608的介绍就到这里,关于ICM-20608的详细介绍请参考ICM-20608的数据手册和寄存器手册。
27.2硬件原理分析
本试验用到的资源如下:
、指示灯LED0。
、 RGB LCD屏幕。
③、ICM20608
④、串口
      ICM-20608是在I.MX6U-ALPHA开发板底板上,原理图如图27.2.1所示:

图27.2.1 ICM-20608原理图
27.3实验程序编写
      本实验对应的例程路径为:开发板光盘-> 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中输入如下内容:
示例代码27.3.1 bsp_spi.h文件代码
1#ifndef _BSP_SPI_H
2#define _BSP_SPI_H
3/***************************************************************
4Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
5文件名    : bsp_spi.h
6作者      : 左忠凯
7版本      : V1.0
8描述      : SPI驱动头文件。
9其他      : 无
10 论坛      : www.openedv.com
11 日志      : 初版V1.0 2019/1/17 左忠凯创建
12 ***************************************************************/
13 #include "imx6ul.h"
14
15/* 函数声明 */
16void spi_init(ECSPI_Type *base);
17unsignedchar spich0_readwrite_byte(ECSPI_Type *base,
unsignedchar txdata);
18 #endif
      文件bsp_spi.h内容很简单,就是函数声明。在文件bsp_spi.c中输入如下内容:
示例代码27.3.2 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   */
10void spi_init(ECSPI_Type *base)
11{
12      /* 配置CONREG寄存器
13         * bit0 :             1   使能ECSPI
14          * bit3 :             1   当向TXFIFO写入数据以后立即开启SPI突发。
15         * bit:      0001 SPI通道0主模式,根据实际情况选择,开发板上的
16         *                     ICM-20608接在SS0上,所以设置通道0为主模式
17         * bit:      00         选中通道0(其实不需要,因为片选信号我们我们自己控制)
18         * bit:      0x7 突发长度为8个bit。
19         */
20      ase->CONREG =0;                                                      /* 先清除控制寄存器 */
21      ase->CONREG |=(1<<0)|(1<<3)|(1<<4)|(7<<20);
22
23      /*
24      * ECSPI通道0设置,即设置CONFIGREG寄存器
25      * bit0:0 通道0 PHA为0
26      * bit4:0 通道0 SCLK高电平有效
27      * bit8:0 通道0片选信号当SMC为1的时候此位无效
28      * bit12: 0 通道0 POL为0
29      * bit16: 0 通道0 数据线空闲时高电平
30      * bit20: 0 通道0 时钟线空闲时低电平
31      */
32      base->CONFIGREG =0;                        /* 设置通道寄存器 */
33
34      /*
35      * ECSPI通道0设置,设置采样周期
36      * bit : 0X2000采样等待周期,比如当SPI时钟为10MHz的时候
37      *                         0X2000就等于1/10000 * 0X2000 = 0.8192ms,也就是
38      *                               连续读取数据的时候每次之间间隔0.8ms
39      * bit15:   0采样时钟源为SPI CLK
40      * bit: 0片选延时,可设置为0~63
41      */
42      base->PERIODREG =0X2000;/* 设置采样周期寄存器 */
43
44      /*
45      * ECSPI的SPI时钟配置,SPI的时钟源来源于pll3_sw_clk/8=480/8=60MHz
46      * SPI CLK = (SourceCLK / PER_DIVIDER) / (2^POST_DIVEDER)
47      * 比如我们现在要设置SPI时钟为6MHz,那么设置如下:
48      * PER_DIVIDER = 0X9。
49      * POST_DIVIDER = 0X0。
50      * SPI CLK = 60000000/(0X9 + 1) = 60000000=6MHz
51      */
52      base->CONREG &=~((0XF<<12)|(0XF<<8));/* 清除以前的设置 */
53      base->CONREG |=(0X9<<12);/* 设置SPI CLK = 6MHz */
54}
55
56/*
57* @description         : SPI通道0发送/接收一个字节的数据
58* @param - base         : 要使用的SPI
59* @param – txdata      : 要发送的数据
60* @return               : 无
61*/
62unsignedchar spich0_readwrite_byte(ECSPI_Type *base,
unsignedchar txdata)
63{
64      uint32_tspirxdata =0;
65      uint32_tspitxdata = txdata;
66
67      /* 选择通道0 */
68      base->CONREG &=~(3<<18);
69                base->CONREG |=(0<<18);
70
71while((base->STATREG &(1<<0))==0){}/* 等待发送FIFO为空 */
72      base->TXDATA = spitxdata;
73
74      while((base->STATREG &(1<<3))==0){}/* 等待接收FIFO有数据 */
75      spirxdata = base->RXDATA;
76      return spirxdata;
77}
      文件bsp_spi.c中有两个函数:spi_init和spich0_readwrite_byte,函数spi_init是SPI初始化函数,此函数会初始化SPI的时钟,通道等。函数spich0_readwrite_byte是SPI收发函数,通过此函数即可完成SPI的全双工数据收发。
      接下来在文件bsp_icm20608.h中输入如下内容:
示例代码27.3.3 bsp_icm20608.h文件代码
1#ifndef _BSP_ICM20608_H
2#define _BSP_ICM20608_H
3/***************************************************************
4Copyright © 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值 */
20 #define ICM20608D_ID            0XAE/* ID值 */
21
22/* ICM20608寄存器
23*复位后所有寄存器地址都为0,除了
24*Register 107(0X6B) Power Management 1         = 0x40
25*Register 117(0X75) WHO_AM_I                           = 0xAF或者0xAE
26*/
27/* 陀螺仪和加速度自测(出产时设置,用于与用户的自检输出值比较) */
28 #defineICM20_SELF_TEST_X_GYRO            0x00
29 #defineICM20_SELF_TEST_Y_GYRO         0x01
30 #defineICM20_SELF_TEST_Z_GYRO             0x02
31 #defineICM20_SELF_TEST_X_ACCEL         0x0D
32 #defineICM20_SELF_TEST_Y_ACCEL            0x0E
33 #defineICM20_SELF_TEST_Z_ACCEL         0x0F
34/***********省略掉其他宏定义*************/
35 #defineICM20_ZA_OFFSET_H                  0x7D
36 #defineICM20_ZA_OFFSET_L               0x7E
37
38/*
39* ICM20608结构体
40*/
41struct icm20608_dev_struc
42{
43      signedint gyro_x_adc;      /* 陀螺仪X轴原始值                */
44      signedint gyro_y_adc;      /* 陀螺仪Y轴原始值      */
45                signedint gyro_z_adc;      /* 陀螺仪Z轴原始值      */
46      signedint accel_x_adc;      /* 加速度计X轴原始值      */
47      signedint accel_y_adc;      /* 加速度计Y轴原始值      */
48      signedint accel_z_adc;      /* 加速度计Z轴原始值      */
49      signedint temp_adc;      /* 温度原始值      */
50
51      /* 下面是计算得到的实际值,扩大100倍 */
52      signedint gyro_x_act;      /* 陀螺仪X轴实际值      */
53                signedint gyro_y_act;      /* 陀螺仪Y轴实际值      */
54      signedint gyro_z_act;      /* 陀螺仪Z轴实际值      */
55      signedint accel_x_act;      /* 加速度计X轴实际值      */
56      signedint accel_y_act;      /* 加速度计Y轴实际值      */
57      signedint accel_z_act;      /* 加速度计Z轴实际值      */
58      signedint temp_act;      /* 温度实际值      */
59};
60
61struct icm20608_dev_struc icm20608_dev;/* icm20608设备 */
62
63/* 函数声明 */
64unsignedchar icm20608_init(void);
65void icm20608_write_reg(unsignedchar reg,unsignedchar value);
66unsignedchar icm20608_read_reg(unsignedchar reg);
67void icm20608_read_len(unsignedchar reg,unsignedchar*buf,
unsignedchar len);
68void icm20608_getdata(void);
69 #endif
      文件bsp_icm20608.h里面先定义了一个宏ICM20608_CSN,这个是ICM20608的SPI片选引脚。接下来定义了一些ICM20608的ID和寄存器地址。第41行定义了一个结构体icm20608_dev_struc,这个结构体是ICM20608的设备结构体,里面的成员变量用来保存ICM20608的原始数据值和经过转换得到的实际值。实际值是有小数的,本章例程取两位小数,为了方便计算,实际值扩大了100倍,这样实际值就是整数了,但是在使用的时候要除100重新得到小数部分。最后就是一些函数声明,接下来在文件bsp_icm20608.c中输入如下所示内容:
示例代码27.3.4 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
6struct icm20608_dev_struc icm20608_dev;/* icm20608设备 */
7
8/*
9    * @description         : 初始化ICM20608
10   * @param               : 无
11   * @return            : 0 初始化成功,其他值初始化失败
12   */
13unsignedchar icm20608_init(void)
14{
15unsignedchar regvalue;
16      gpio_pin_config_t cs_config;
17
18/* 1、ESPI3 IO初始化
19       * ECSPI3_SCLK-> UART2_RXD
20       * ECSPI3_MISO-> UART2_RTS
21       * ECSPI3_MOSI-> UART2_CTS
22       */
23      IOMUXC_SetPinMux(IOMUXC_UART2_RX_DATA_ECSPI3_SCLK,0);
24      IOMUXC_SetPinMux(IOMUXC_UART2_CTS_B_ECSPI3_MOSI,0);
25      IOMUXC_SetPinMux(IOMUXC_UART2_RTS_B_ECSPI3_MISO,0);
26      IOMUXC_SetPinConfig(IOMUXC_UART2_RX_DATA_ECSPI3_SCLK,0x10B1);
27      IOMUXC_SetPinConfig(IOMUXC_UART2_CTS_B_ECSPI3_MOSI,0x10B1);
28      IOMUXC_SetPinConfig(IOMUXC_UART2_RTS_B_ECSPI3_MISO,0x10B1);
29
30/*初始化片选引脚*/
31      IOMUXC_SetPinMux(IOMUXC_UART2_TX_DATA_GPIO1_IO20,0);
32      IOMUXC_SetPinConfig(IOMUXC_UART2_TX_DATA_GPIO1_IO20,0X10B0);
33      cs_config.direction = kGPIO_DigitalOutput;
34      cs_config.outputLogic =0;
35      gpio_init(GPIO1,20,&cs_config);
36
37/* 2、初始化SPI */
38      spi_init(ECSPI3);
39
40      icm20608_write_reg(ICM20_PWR_MGMT_1,0x80);/* 复位                */
41      delayms(50);
42      icm20608_write_reg(ICM20_PWR_MGMT_1,0x01);/* 关闭睡眠      */
43      delayms(50);
44
45      regvalue = icm20608_read_reg(ICM20_WHO_AM_I);
46      printf("icm20608 id = %#X\r\n", regvalue);
47if(regvalue != ICM20608G_ID && regvalue != ICM20608D_ID)
48return1;
49
50      icm20608_write_reg(ICM20_SMPLRT_DIV,0x00);      /* 输出速率设置    */
51      icm20608_write_reg(ICM20_GYRO_CONFIG,0x18);/* 陀螺仪±2000dps */
52      icm20608_write_reg(ICM20_ACCEL_CONFIG,0x18);/* 加速度计±16G   */
53      icm20608_write_reg(ICM20_CONFIG,0x04);/* 陀螺BW=20Hz   */
54      icm20608_write_reg(ICM20_ACCEL_CONFIG2,0x04);
55      icm20608_write_reg(ICM20_PWR_MGMT_2,0x00);/* 打开所有轴       */
56      icm20608_write_reg(ICM20_LP_MODE_CFG,0x00);/* 关闭低功耗       */
57      icm20608_write_reg(ICM20_FIFO_EN,0x00);      /* 关闭FIFO       */
58return0;
59}
60
61/*
62   * @description          : 写ICM20608指定寄存器
63   * @param - reg          : 要读取的寄存器地址
64   * @param – value      : 要写入的值
65   * @return               : 无
66   */
67void icm20608_write_reg(unsignedchar reg,unsignedchar value)
68{
69/* ICM20608在使用SPI接口的时候寄存器地址只有低7位有效,
70       * 寄存器地址最高位是读/写标志位,读的时候要为1,写的时候要为0。
71       */
72      reg &=~0X80;
73
74      ICM20608_CSN(0);      /* 使能SPI传输      */
75      spich0_readwrite_byte(ECSPI3, reg);      /* 发送寄存器地址      */
76      spich0_readwrite_byte(ECSPI3, value);      /* 发送要写入的值      */
77      ICM20608_CSN(1);      /* 禁止SPI传输      */
78}
79
80/*
81   * @description         : 读取ICM20608寄存器值
82   * @param - reg         : 要读取的寄存器地址
83   * @return            : 读取到的寄存器值
84   */
85unsignedchar icm20608_read_reg(unsignedchar reg)
86{
87unsignedchar reg_val;
88
89/* ICM20608在使用SPI接口的时候寄存器地址只有低7位有效,
90       * 寄存器地址最高位是读/写标志位,读的时候要为1,写的时候要为0。
91       */
92      reg |=0x80;
93
94      ICM20608_CSN(0);      /* 使能SPI传输         */
95      spich0_readwrite_byte(ECSPI3, reg);      /* 发送寄存器地址         */
96      reg_val = spich0_readwrite_byte(ECSPI3,0XFF);/* 读取寄存器的值*/
97      ICM20608_CSN(1);      /* 禁止SPI传输         */
98return(reg_val);      /* 返回读取到的寄存器值         */
99}
100
101/*
102* @description         : 读取ICM20608连续多个寄存器
103* @param - reg         : 要读取的寄存器地址
104* @return            : 读取到的寄存器值
105*/
106void icm20608_read_len(unsignedchar reg,unsignedchar*buf,
unsignedchar len)
107{
108unsignedchar i;
109
110/* ICM20608在使用SPI接口的时候寄存器地址,只有低7位有效,
111      * 寄存器地址最高位是读/写标志位读的时候要为1,写的时候要为0。
112      */
113   reg |=0x80;
114
115   ICM20608_CSN(0);/* 使能SPI传输                */
116   spich0_readwrite_byte(ECSPI3, reg);/* 发送寄存器地址      */
117for(i =0; i < len; i++)/* 顺序读取寄存器的值      */
118{
119         buf= spich0_readwrite_byte(ECSPI3,0XFF);
120}
121   ICM20608_CSN(1);/* 禁止SPI传输      */
122}
123
124/*
125* @description         : 获取陀螺仪的分辨率
126* @param               : 无
127* @return            : 获取到的分辨率
128*/
129float icm20608_gyro_scaleget(void)
130{
131unsignedchar data;
132float gyroscale;
133
134   data =(icm20608_read_reg(ICM20_GYRO_CONFIG)>>3)&0X3;
135switch(data){
136case0:
137             gyroscale =131;
138break;
139case1:
140             gyroscale =65.5;
141break;
142case2:
143             gyroscale =32.8;
144break;
145case3:
146             gyroscale =16.4;
147break;
148}
149return gyroscale;
150}
151
152/*
153* @description         : 获取加速度计的分辨率
154* @param               : 无
155* @return            : 获取到的分辨率
156*/
157unsignedshort icm20608_accel_scaleget(void)
158{
159unsignedchar data;
160unsignedshort accelscale;
161
162   data =(icm20608_read_reg(ICM20_ACCEL_CONFIG)>>3)&0X3;
163switch(data){
164case0:
165             accelscale =16384;
166break;
167case1:
168             accelscale =8192;
169break;
170case2:
171             accelscale =4096;
172break;
173case3:
174             accelscale =2048;
175break;
176}
177return accelscale;
178}
179
180/*
181* @description         : 读取ICM20608的加速度、陀螺仪和温度原始值
182* @param               : 无
183* @return            : 无
184*/
185void icm20608_getdata(void)
186{
187float gyroscale;
188unsignedshort accescale;
189unsignedchar data;
190
191   icm20608_read_len(ICM20_ACCEL_XOUT_H, data,14);
192
193   gyroscale = icm20608_gyro_scaleget();
194   accescale = icm20608_accel_scaleget();
195
196   icm20608_dev.accel_x_adc =(signedshort)((data<<8)|
data);
197   icm20608_dev.accel_y_adc =(signedshort)((data<<8)|
data);
198   icm20608_dev.accel_z_adc =(signedshort)((data<<8)|
data);
199   icm20608_dev.temp_adc    =(signedshort)((data<<8)|
data);
200   icm20608_dev.gyro_x_adc=(signedshort)((data<<8)|
data);
201   icm20608_dev.gyro_y_adc=(signedshort)((data<<8)|
data);
202   icm20608_dev.gyro_z_adc=(signedshort)((data<<8)|
data);
203
204/* 计算实际值 */
205   icm20608_dev.gyro_x_act =((float)(icm20608_dev.gyro_x_adc)/
gyroscale)*100;
206   icm20608_dev.gyro_y_act =((float)(icm20608_dev.gyro_y_adc)/
gyroscale)*100;
207   icm20608_dev.gyro_z_act =((float)(icm20608_dev.gyro_z_adc)/
gyroscale)*100;
208   icm20608_dev.accel_x_act =((float)(icm20608_dev.accel_x_adc)/
accescale)*100;
209   icm20608_dev.accel_y_act =((float)(icm20608_dev.accel_y_adc)/
accescale)*100;
210   icm20608_dev.accel_z_act =((float)(icm20608_dev.accel_z_adc)/
accescale)*100;
211   icm20608_dev.temp_act =(((float)(icm20608_dev.temp_adc)-25)/
326.8+25)*100;
212}
      文件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中输入如下内容:
示例代码27.3.5 main.c文件代码
/**************************************************************
Copyright © zuozhongkai Co., Ltd. 1998-2019. All rights reserved.
文件名   : mian.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"
3   #include "bsp_led.h"
4   #include "bsp_beep.h"
5   #include "bsp_key.h"
6   #include "bsp_int.h"
7   #include "bsp_uart.h"
8   #include "bsp_lcd.h"
9   #include "bsp_rtc.h"
10#include "bsp_icm20608.h"
11#include "bsp_spi.h"
12#include "stdio.h"
13
14/*
15   * @description         : 指定的位置显示整数数据
16   * @param - x         : X轴位置
17   * @param - y         : Y轴位置
18   * @param – size      : 字体大小
19   * @param - num         : 要显示的数据
20   * @return            : 无
21   */
22void integer_display(unsignedshort x,unsignedshort y,
unsignedchar size,signedint num)
23{
24char buf;
25
26      lcd_fill(x, y, x +50, y + size, tftlcd_dev.backcolor);
27
28      memset(buf,0,sizeof(buf));
29if(num <0)
30          sprintf(buf,"-%d",-num);
31else
32          sprintf(buf,"%d", num);
33      lcd_show_string(x, y,50, size, size, buf);
34}
35
36/*
37   * @description         : 指定的位置显示小数数据,比如5123,显示为51.23
38   * @param - x         : X轴位置
39   * @param - y         : Y轴位置
40   * @param – size      : 字体大小
41   * @param - num         : 要显示的数据,实际小数扩大100倍,
42   * @return            : 无
43   */
44void decimals_display(unsignedshort x,unsignedshort y,
unsignedchar size,signedint num)
45{
46signedint integ;/* 整数部分 */
47signedint fract;/* 小数部分 */
48signedint uncomptemp = num;
49char buf;
50
51if(num <0)
52          uncomptemp =-uncomptemp;
53      integ = uncomptemp /100;
54      fract = uncomptemp %100;
55
56      memset(buf,0,sizeof(buf));
57if(num <0)
58          sprintf(buf,"-%d.%d", integ, fract);
59else
60          sprintf(buf,"%d.%d", integ, fract);
61      lcd_fill(x, y, x +60, y + size, tftlcd_dev.backcolor);
62      lcd_show_string(x, y,60, size, size, buf);
63}
64
65/*
66   * @description         : 使能I.MX6U的硬件NEON和FPU
67   * @param               : 无
68   * @return            : 无
69   */
70void imx6ul_hardfpu_enable(void)
71{
72uint32_t cpacr;
73uint32_t fpexc;
74
75/* 使能NEON和FPU */
76      cpacr = __get_CPACR();
77      cpacr =(cpacr &~(CPACR_ASEDIS_Msk | CPACR_D32DIS_Msk))
78|(3UL<< CPACR_cp10_Pos)|(3UL<< CPACR_cp11_Pos);
79      __set_CPACR(cpacr);
80      fpexc = __get_FPEXC();
81      fpexc |=0x40000000UL;
82      __set_FPEXC(fpexc);
83}
84
85/*
86   * @description : main函数
87   * @param       : 无
88   * @return      : 无
89   */
90int main(void)
91{
92unsignedchar state = OFF;
93
94      imx6ul_hardfpu_enable();      /* 使能I.MX6U的硬件浮点      */
95      int_init();      /* 初始化中断(一定要最先调用!)      */
96      imx6u_clkinit();      /* 初始化系统时钟      */
97      delay_init();      /* 初始化延时      */
98      clk_enable();      /* 使能所有的时钟      */
99      led_init();      /* 初始化led                           */
100   beep_init();      /* 初始化beep                        */
101   uart_init();      /* 初始化串口,波特率115200         */
102   lcd_init();      /* 初始化LCD                           */
103
104   tftlcd_dev.forecolor = LCD_RED;
105   lcd_show_string(50,10,400,24,24,
(char*)"IMX6U-ALPHA SPI TEST");
106   lcd_show_string(50,40,200,16,16,(char*)"ICM20608 TEST");
107   lcd_show_string(50,60,200,16,16,(char*)"ATOM@ALIENTEK");
108   lcd_show_string(50,80,200,16,16,(char*)"2019/3/27");
109
110while(icm20608_init())      /* 初始化ICM20608 */
111{
112         lcd_show_string(50,100,200,16,16,
(char*)"ICM20608 Check Failed!");
113         delayms(500);
114         lcd_show_string(50,100,200,16,16,
(char*)"Please Check!      ");
115         delayms(500);
116}
117   lcd_show_string(50,100,200,16,16,(char*)"ICM20608 Ready");
118   lcd_show_string(50,130,200,16,16,(char*)"accel x:");
119   lcd_show_string(50,150,200,16,16,(char*)"accel y:");
120   lcd_show_string(50,170,200,16,16,(char*)"accel z:");
121   lcd_show_string(50,190,200,16,16,(char*)"gyrox:");
122   lcd_show_string(50,210,200,16,16,(char*)"gyroy:");
123   lcd_show_string(50,230,200,16,16,(char*)"gyroz:");
124   lcd_show_string(50,250,200,16,16,(char*)"temp   :");
125   lcd_show_string(50+181,130,200,16,16,(char*)"g");
126   lcd_show_string(50+181,150,200,16,16,(char*)"g");
127   lcd_show_string(50+181,170,200,16,16,(char*)"g");
128   lcd_show_string(50+181,190,200,16,16,(char*)"o/s");
129   lcd_show_string(50+181,210,200,16,16,(char*)"o/s");
130   lcd_show_string(50+181,230,200,16,16,(char*)"o/s");
131   lcd_show_string(50+181,250,200,16,16,(char*)"C");
132
133   tftlcd_dev.forecolor = LCD_BLUE;
134
135while(1)
136{
137         icm20608_getdata();/* 获取数据值 */
138/* 在LCD上显示原始值 */
139         integer_display(50+70,130,16, icm20608_dev.accel_x_adc);
140         integer_display(50+70,150,16, icm20608_dev.accel_y_adc);
141         integer_display(50+70,170,16, icm20608_dev.accel_z_adc);
142         integer_display(50+70,190,16, icm20608_dev.gyro_x_adc);
143         integer_display(50+70,210,16, icm20608_dev.gyro_y_adc);
144         integer_display(50+70,230,16, icm20608_dev.gyro_z_adc);
145         integer_display(50+70,250,16, icm20608_dev.temp_adc);
146
147/* 在LCD上显示计算得到的原始值 */
148         decimals_display(50+70+50,130,16,
icm20608_dev.accel_x_act);
149         decimals_display(50+70+50,150,16,
icm20608_dev.accel_y_act);
150         decimals_display(50+70+50,170,16,
icm20608_dev.accel_z_act);
151         decimals_display(50+70+50,190,16,
icm20608_dev.gyro_x_act);
152         decimals_display(50+70+50,210,16,
icm20608_dev.gyro_y_act);
153         decimals_display(50+70+50,230,16,
icm20608_dev.gyro_z_act);
154         decimals_display(50+70+50,250,16,
icm20608_dev.temp_act);
155         delayms(120);
156         state =!state;
157         led_switch(LED0,state);
158}
159return0;
160}
      文件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上。实验程序编写就到这里结束了,接下来就是编译、下载和验证了。
27.4编译下载验证
27.4.1 编写Makefile和链接脚本
修改Makefile中的TARGET为icm20608,然后在在INCDIRS和SRCDIRS中加入“bsp/spi”和“bsp/icm20608”,修改后的Makefile如下:
示例代码27.4.1.1Makefile文件代码
1CROSS_COMPILE      ?= arm-linux-gnueabihf-
2TARGET               ?= icm20608
3
4/* 省略掉其它代码...... */
5
6INCDIRS      :=      imx6ul \
7                stdio/include \
8                bsp/clk \
9                bsp/led \
10                bsp/delay\
11                bsp/beep \
12                bsp/gpio \
13                bsp/key \
14               bsp/exit \
15                bsp/int \
16                bsp/epittimer \
17                bsp/keyfilter \
18                bsp/uart \
19                bsp/lcd \
20                bsp/rtc \
21                bsp/i2c \
22                bsp/ap3216c \
23                bsp/spi \
24                bsp/icm20608
25
26 SRCDIRS      :=      project \
27                stdio/lib \
28                bsp/clk \
29                bsp/led \
30                bsp/delay \
31                bsp/beep \
32                bsp/gpio \
33                bsp/key \
34                bsp/exit \
35                bsp/int \
36                bsp/epittimer \
37                bsp/keyfilter \
38                bsp/uart \
39                bsp/lcd \
40                bsp/rtc \
41                bsp/i2c \
42                bsp/ap3216c \
43                bsp/spi \
44                bsp/icm20608
45
46/* 省略掉其它代码...... */
47
48$(COBJS): obj/%.o : %.c
49 $(CC) -Wall -march=armv7-a -mfpu=neon-vfpv4 -mfloat-abi=hard -Wa,
-mimplicit-it=thumb -nostdlib -fno-builtin
-c -O2$(INCLUDE) -o $@ [      DISCUZ_CODE_54      ]lt;
50
51 clean:
52rm -rf $(TARGET).elf $(TARGET).dis $(TARGET).bin $(COBJS)$(SOBJS)
第2行修改变量TARGET为“icm20608”,也就是目标名称为“ap3216c”。
      第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来编译浮点运算。
      链接脚本保持不变。
27.4.2编译下载
      使用Make命令编译代码,编译成功以后使用软件imxdownload将编译完成的icm20608.bin文件下载到SD卡中,命令如下:
chmod 777 imxdownload                              //给予imxdownload可执行权限,一次即可
./imxdownload icm20608.bin /dev/sdd                //烧写到SD卡中
      烧写成功以后将SD卡插到开发板的SD卡槽中,然后复位开发板。如果ICM20608工作正常的话就会在LCD上显示获取到的传感器数据,如图27.4.2.1所示:

图27.4.2.1LCD界面
      在图27.4.2.1中可以看到加速度计Z轴在静止状态下是0.98g,这不正是重力加速度。温度传感器测量到的温度是31.39°C,这个是芯片内部的温度,并不是室温!芯片内部温度一般要比室温高。如果动一下开发板的话加速度计和陀螺仪的数据就会变化。
页: [1]
查看完整版本: 【正点原子Linux连载】第二十七章SPI实验--摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南