当前位置: 首页 > news >正文

imx6ull-裸机学习实验17——SPI 实验

目录

前言

SPI协议

基本特性

通信流程​​

工作模式

工作时序图

I.MX6U ECSPI

特性

寄存器

控制寄存器ECSPIx_CONREG(x=1~4)

配置寄存器ECSPIx_CONFIGREG

采样周期寄存器ECSPIx_PERIODREG

状态寄存器ECSPIx_STATREG

数据寄存器 ECSPIx_TX/RX DATA

ICM-20608

简介

寄存器列表

硬件原理图

实验程序编写

spi.h

spi.c

icm20608.h

icm20608.c

main.c

Makefile文件


前言

SPI(串行外设接口)是一种​高速、全双工、同步​​的串行通信协议,由摩托罗拉(现NXP)设计,广泛用于​​芯片间短距离通信​​(如传感器、存储器、显示屏等)。

正点原子开发板I.MX6U-ALPHA 使用 SPI3 接口连接了一个六轴传感器 ICM-20608,本讲实验我们跟着官方例程,学习如何使用 I.MX6U 的 SPI 接口来驱动ICM-20608,读取 ICM-20608 的六轴数据。

SPI协议

SPI, SPI 全称是 Serial Perripheral Interface,也就是串行外围设备接口,是一种高速、全双工的同步通信总线。

SPI 时钟频率相比 I2C 要高很多,最高可以工作在上百 MHz。SPI 以主从方式工作,通常是有一个主设备和一个或多个从设备,一般 SPI 需要4 根线,但是也可以使用三根线(单向传输)。

基本特性

  • ​四线制​​(标准SPI):
    • ​SCLK​​(Serial Clock):时钟信号,由主机控制。
    • ​MOSI​​(Master Out Slave In):主机→从机数据线。
    • ​MISO​​(Master In Slave Out):从机→主机数据线。
    • ​SS/CS​​(Slave Select/Chip Select):从机片选(低电平有效)。
  • ​全双工通信​​:主机和从机可​​同时收发数据​​。
  • ​高速传输​​:通常可达 ​​10 MHz~100 MHz​​(远高于I²C)。
  • ​无地址机制​​:依赖硬件片选(SS)选择从机。
  • ​无标准协议​​:数据格式(如字节长度、时钟极性等)需主从设备一致。

SPI 通信都是由主机发起的,主机需要提供通信的时钟信号。主机通过 SPI 线连接多个从设备的结构如图:

通信流程​

  1. ​主机拉低SS信号​​(选中从机)。
  2. ​主机生成SCLK时钟​​,同时通过MOSI发送数据。
  3. ​从机通过MISO返回数据​​(全双工模式下)。
  4. ​通信结束,主机拉高SS信号​​。

工作模式

SPI 有四种工作模式,通过串行时钟极性(CPOL)和相位(CPHA)的搭配来得到四种工作模式:

  • CPOL=0,串行时钟空闲状态为低电平。
  • CPOL=1,串行时钟空闲状态为高电平,此时可以通过配置时钟相位(CPHA)来选择具体的传输协议。
  • CPHA=0,串行时钟的第一个跳变沿(上升沿或下降沿)采集数据。
  • CPHA=1,串行时钟的第二个跳变沿(上升沿或下降沿)采集数据。
​模式​​CPOL​​CPHA​​数据采样时机​
000SCLK上升沿采样
101SCLK下降沿采样
210SCLK下降沿采样
311SCLK上升沿采样

工作时序图

以 CPOL=0, CPHA=0 这个工作模式为例, SPI 进行全双工通信的时序如图:

CS 片选信号先拉低,选中要通信的从设备,通过 MOSI 和 MISO 这两根数据线进行收发数据。

MOSI 数据线发出了0XD2 这个数据给从设备,同时从设备也通过 MISO 线给主设备返回了 0X66 这个数据。

I.MX6U ECSPI

特性

I.MX6U 自带的 SPI 外设叫做 ECSPI,全称是 Enhanced Configurable Serial Peripheral Interface。ECSPI 有 64*32 个接收FIFO(RXFIFO)和 64*32 个发送 FIFO(TXFIFO)。

ECSPI 特性如下:

  • 全双工同步串行接口。②、可配置的主/从模式。
  • 四个片选信号,支持多从机。
  • 发送和接收都有一个 32x64 的 FIFO。
  • 片选信号 SS/CS,时钟信号 SCLK 极性可配置。
  • 支持 DMA。

I.MX6U 的 ECSPI 可以工作在主模式或从模式,本讲实验我们使用主模式。

寄存器

我们接下来看一下 ECSPI 的几个重要的寄存器:

  • 控制寄存器 ECSPIx_CONREG(x=1~4)
  • 配置寄存器 ECSPIx_CONFIGREG
  • 采样周期寄存器 ECSPIx_PERIODREG
  • 状态寄存器 ECSPIx_STATREG
  • 数据寄存器 ECSPIx_TXDATA 和 ECSPIx_RXDATA

控制寄存器ECSPIx_CONREG(x=1~4)

控制寄存器(ECSPI_CONREG)允许软件启用ECSPI,配置其操作模式,指定除法器值和SPI_RDY控制信号,并定义传输长度。

BURST_LENGTH(bit31:24): 突发长度,设置 SPI 的突发传输数据长度,在一次 SPI 发送中最大可以发送 2^12bit 数据。可以设置 0X000~0XFFF,分别对应 1~2^12bit。我们一般设置突发长度为一个字节,也就是 8bit, BURST_LENGTH=7

CHANNEL_SELECT(bit19:18): SPI 通道选择,分别对应通道 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[3:0]分别对应 SPI通道 3~0. 比如设置为 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_CONFIGREG)允许软件配置每个SPI通道,配置其操作模式,指定时钟的相位和极性,配置芯片选择(SS),并定义HT传输长度。

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_PERIODREG)为软件提供了一种在连续SPI传输之间插入延迟(等待状态)的方法。该寄存器中的控制位选择采样周期计数器的时钟源和延迟计数,延迟计数指示在数据传输之间插入的等待状态的数量。

CSD_CTL(bit21:16): 片选信号延时控制位,用于设置片选信号和第一个 SPI 时钟信号之间的时间间隔,范围为 0~63。

CSRC(bit15): SPI 时钟源选择,为 0 的话选择 SPI CLK 为 SPI 的时钟源,为 1 的话选择32.768KHz 的晶振为 SPI 时钟源。我们一般选择 SPI CLK 作为 SPI 时钟源.

SAMPLE_PERIO: 采样周期寄存器,可设置为 0~0X7FFF 分别对应 0~32767 个周期。

状态寄存器ECSPIx_STATREG

ECSPI状态寄存器(ECSPI_STATREG)反映了ECSPI的运行状态。如果ECSPI被禁用,则该寄存器的读数为0x0000_0003。

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_TX/RX DATA

数据寄存器, ECSPIx_TXDATA 和 ECSPIx_RXDATA,这两个寄存器都是 32位的,如果要发送数据就向寄存器 ECSPIx_TXDATA 写入数据,读取及存取 ECSPIx_RXDATA里面的数据就可以得到刚刚接收到的数据。

传输数据(ECSPI_TXDATA)寄存器是一个只写数据寄存器,它形成了64 x 32 TXFIFO的底部字。只要TXFIFO未满,即使ECSPI_CONREG中的SPI交换位(XCH)已设置,也可以写入TXFIFO。这允许软件在SPI数据交换过程中写入TXFIFO。当ECSPI被禁用ECSPI_CONREG[EN]位被清除),对该寄存器的写入将被忽略。

接收数据寄存器(ECSPI_RXDATA)是一个只读寄存器,形成64 x 32接收FIFO的顶字。该寄存器保存在数据事务期间从外部SPI设备接收到的数据。只允许字大小的读取操作。

ICM-20608

简介

I.MX6U-ALPHA 开发板通过 SPI 接口和 ICM- 20608 连接在一起, ICM- 20608 是 InvenSense 出品的一款 6 轴 MEMS 传感器,包括 3 轴加速度和 3 轴陀螺仪。

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 轴方向如图:

ICM-20608 也是通过读写寄存器来配置和读取传感器数据,使用 SPI 接口读写寄存器需要 16 个时钟或者更多。

数据帧结构​​:

  • ​第1字节​​:寄存器地址(8位)
    • ​最高位(Bit7)​​:1=读,0=写
    • ​低7位(Bit6~0)​​:实际寄存器地址
  • ​后续字节​​:写入/读取的数据(可连续多字节)。

寄存器列表

硬件原理图

ICM-20608 是在 I.MX6U-ALPHA 开发板底板上,原理图如图:

实验程序编写

在 bsp 文件夹下创建名为“spi”和“icm20608”的文件。

在 bsp/spi 中新建 bsp_spi.c 和 bsp_spi.h, 这两个文件是 I.MX6U 的 SPI 文件。

在 bsp/icm20608 中新建 bsp_icm20608.c 和 bsp_icm20608.h ,这两个文件是ICM20608 的驱动文件。

spi.h

spi.h 内容很简单,就是函数声明。

#ifndef _BSP_SPI_H
#define _BSP_SPI_H#include "imx6ul.h"/* 函数声明 */
void spi_init(ECSPI_Type *base);
unsigned char spich0_readwrite_byte(ECSPI_Type *base, unsigned char txdata);#endif

spi.c

spi.c 中有两个函数:

  • spi_init
  • spich0_readwrite_byte

函数 spi_init 是 SPI 初始化函数,此函数会初始化 SPI 的时钟,通道等。

/** @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 */
}

函数 spich0_readwrite_byte 是 SPI 收发函数,通过此函数即可完成 SPI 的全双工数据收发。

/** @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;
}

icm20608.h

icm20608.h 里面:

  • 定义了一个宏 ICM20608_CSN,这个是 ICM20608 的 SPI 片选引脚。
  • 定义了一些 ICM20608 的 ID 和寄存器地址。
  • 定义了一个结构体icm20608_dev_struc,这个结构体是 ICM20608 的设备结构体,里面的成员变量用来保存ICM20608 的原始数据值和经过转换得到的实际值。
#ifndef _BSP_ICM20608_H
#define _BSP_ICM20608_H#include "imx6ul.h"
#include "bsp_gpio.h"/* 宏定义 */
#define ICM20608_CSN(n)    (n ? gpio_pinwrite(GPIO1, 20, 1) : gpio_pinwrite(GPIO1, 20, 0))   /* SPI片选信号	 */#define ICM20608G_ID			0XAF	/* ID值 */
#define ICM20608D_ID			0XAE	/* ID值 *//* ICM20608寄存器 *复位后所有寄存器地址都为0,除了*Register 107(0X6B) Power Management 1 	= 0x40*Register 117(0X75) WHO_AM_I 				= 0xAF或0xAE*/
/* 陀螺仪和加速度自测(出产时设置,用于与用户的自检输出值比较) */
#define	ICM20_SELF_TEST_X_GYRO		0x00
#define	ICM20_SELF_TEST_Y_GYRO		0x01
#define	ICM20_SELF_TEST_Z_GYRO		0x02
#define	ICM20_SELF_TEST_X_ACCEL		0x0D
#define	ICM20_SELF_TEST_Y_ACCEL		0x0E
#define	ICM20_SELF_TEST_Z_ACCEL		0x0F/* 陀螺仪静态偏移 */
#define	ICM20_XG_OFFS_USRH			0x13
#define	ICM20_XG_OFFS_USRL			0x14
#define	ICM20_YG_OFFS_USRH			0x15
#define	ICM20_YG_OFFS_USRL			0x16
#define	ICM20_ZG_OFFS_USRH			0x17
#define	ICM20_ZG_OFFS_USRL			0x18#define	ICM20_SMPLRT_DIV			0x19
#define	ICM20_CONFIG				0x1A
#define	ICM20_GYRO_CONFIG			0x1B
#define	ICM20_ACCEL_CONFIG			0x1C
#define	ICM20_ACCEL_CONFIG2			0x1D
#define	ICM20_LP_MODE_CFG			0x1E
#define	ICM20_ACCEL_WOM_THR			0x1F
#define	ICM20_FIFO_EN				0x23
#define	ICM20_FSYNC_INT				0x36
#define	ICM20_INT_PIN_CFG			0x37
#define	ICM20_INT_ENABLE			0x38
#define	ICM20_INT_STATUS			0x3A/* 加速度输出 */
#define	ICM20_ACCEL_XOUT_H			0x3B
#define	ICM20_ACCEL_XOUT_L			0x3C
#define	ICM20_ACCEL_YOUT_H			0x3D
#define	ICM20_ACCEL_YOUT_L			0x3E
#define	ICM20_ACCEL_ZOUT_H			0x3F
#define	ICM20_ACCEL_ZOUT_L			0x40/* 温度输出 */
#define	ICM20_TEMP_OUT_H			0x41
#define	ICM20_TEMP_OUT_L			0x42/* 陀螺仪输出 */
#define	ICM20_GYRO_XOUT_H			0x43
#define	ICM20_GYRO_XOUT_L			0x44
#define	ICM20_GYRO_YOUT_H			0x45
#define	ICM20_GYRO_YOUT_L			0x46
#define	ICM20_GYRO_ZOUT_H			0x47
#define	ICM20_GYRO_ZOUT_L			0x48#define	ICM20_SIGNAL_PATH_RESET		0x68
#define	ICM20_ACCEL_INTEL_CTRL 		0x69
#define	ICM20_USER_CTRL				0x6A
#define	ICM20_PWR_MGMT_1			0x6B
#define	ICM20_PWR_MGMT_2			0x6C
#define	ICM20_FIFO_COUNTH			0x72
#define	ICM20_FIFO_COUNTL			0x73
#define	ICM20_FIFO_R_W				0x74
#define	ICM20_WHO_AM_I 				0x75/* 加速度静态偏移 */
#define	ICM20_XA_OFFSET_H			0x77
#define	ICM20_XA_OFFSET_L			0x78
#define	ICM20_YA_OFFSET_H			0x7A
#define	ICM20_YA_OFFSET_L			0x7B
#define	ICM20_ZA_OFFSET_H			0x7D
#define	ICM20_ZA_OFFSET_L 			0x7E/** ICM20608结构体*/
struct icm20608_dev_struc
{signed int gyro_x_adc;		/* 陀螺仪X轴原始值 			*/signed int gyro_y_adc;		/* 陀螺仪Y轴原始值 			*/signed int gyro_z_adc;		/* 陀螺仪Z轴原始值 			*/signed int accel_x_adc;		/* 加速度计X轴原始值 			*/signed int accel_y_adc;		/* 加速度计Y轴原始值 			*/signed int accel_z_adc;		/* 加速度计Z轴原始值 			*/signed int temp_adc;		/* 温度原始值 				*//* 下面是计算得到的实际值,扩大100倍 */signed int gyro_x_act;		/* 陀螺仪X轴实际值 			*/signed int gyro_y_act;		/* 陀螺仪Y轴实际值 			*/signed int gyro_z_act;		/* 陀螺仪Z轴实际值 			*/signed int accel_x_act;		/* 加速度计X轴实际值 			*/signed int accel_y_act;		/* 加速度计Y轴实际值 			*/signed int accel_z_act;		/* 加速度计Z轴实际值 			*/signed int temp_act;		/* 温度实际值 				*/
};struct icm20608_dev_struc icm20608_dev;	/* icm20608设备 *//* 函数声明 */
unsigned char icm20608_init(void);
void icm20608_write_reg(unsigned char reg, unsigned char value);
unsigned char icm20608_read_reg(unsigned char reg);
void icm20608_read_len(unsigned char reg, unsigned char *buf, unsigned char len);
void icm20608_getdata(void);#endif

icm20608.c

imc20608.c 是 ICM20608 的驱动文件,里面有 7 个函数:

  • 函数icm20608_init
  • 函数icm20608_write_reg
  • 函数icm20608_read_reg
  • 函数icm20608_read_len
  • 函数icm20608_gyro_scaleget
  • 函数icm20608_accel_scaleget
  • 函数icm20608_getdata

函数 icm20608_init,这个是 ICM20608 的初始化函数,此函数先初始化 ICM20608 所使用的 SPI 引脚,将其复用为 ECSPI3。然后调用函数 spi_init 来初始化 SPI3,最后初始化 ICM20608。

/** @description	: 初始化ICM20608* @param		: 无* @return 		: 0 初始化成功,其他值 初始化失败*/
unsigned char icm20608_init(void)
{	unsigned char regvalue;gpio_pin_config_t cs_config;/* 1、ESPI3 IO初始化 * ECSPI3_SCLK 	-> UART2_RXD* ECSPI3_MISO 	-> UART2_RTS* ECSPI3_MOSI	-> UART2_CTS*/IOMUXC_SetPinMux(IOMUXC_UART2_RX_DATA_ECSPI3_SCLK, 0);IOMUXC_SetPinMux(IOMUXC_UART2_CTS_B_ECSPI3_MOSI, 0);IOMUXC_SetPinMux(IOMUXC_UART2_RTS_B_ECSPI3_MISO, 0);/* 配置SPI   SCLK MISO MOSI IO属性	*bit 16: 0 HYS关闭*bit [15:14]: 00 默认100K下拉*bit [13]: 0 keeper功能*bit [12]: 1 pull/keeper使能 *bit [11]: 0 关闭开路输出*bit [7:6]: 10 速度100Mhz*bit [5:3]: 110 驱动能力为R0/6*bit [0]: 1 高转换率*/IOMUXC_SetPinConfig(IOMUXC_UART2_RX_DATA_ECSPI3_SCLK, 0x10B1);IOMUXC_SetPinConfig(IOMUXC_UART2_CTS_B_ECSPI3_MOSI, 0x10B1);IOMUXC_SetPinConfig(IOMUXC_UART2_RTS_B_ECSPI3_MISO, 0x10B1);IOMUXC_SetPinMux(IOMUXC_UART2_TX_DATA_GPIO1_IO20, 0);IOMUXC_SetPinConfig(IOMUXC_UART2_TX_DATA_GPIO1_IO20, 0X10B0);cs_config.direction = kGPIO_DigitalOutput;cs_config.outputLogic = 0;gpio_init(GPIO1, 20, &cs_config);/* 2、初始化SPI */spi_init(ECSPI3);	icm20608_write_reg(ICM20_PWR_MGMT_1, 0x80);		/* 复位,复位后为0x40,睡眠模式 			*/delayms(50);icm20608_write_reg(ICM20_PWR_MGMT_1, 0x01);		/* 关闭睡眠,自动选择时钟 					*/delayms(50);regvalue = icm20608_read_reg(ICM20_WHO_AM_I);printf("icm20608 id = %#X\r\n", regvalue);if(regvalue != ICM20608G_ID && regvalue != ICM20608D_ID)return 1;icm20608_write_reg(ICM20_SMPLRT_DIV, 0x00); 	/* 输出速率是内部采样率					*/icm20608_write_reg(ICM20_GYRO_CONFIG, 0x18); 	/* 陀螺仪±2000dps量程 				*/icm20608_write_reg(ICM20_ACCEL_CONFIG, 0x18); 	/* 加速度计±16G量程 					*/icm20608_write_reg(ICM20_CONFIG, 0x04); 		/* 陀螺仪低通滤波BW=20Hz 				*/icm20608_write_reg(ICM20_ACCEL_CONFIG2, 0x04); 	/* 加速度计低通滤波BW=21.2Hz 			*/icm20608_write_reg(ICM20_PWR_MGMT_2, 0x00); 	/* 打开加速度计和陀螺仪所有轴 				*/icm20608_write_reg(ICM20_LP_MODE_CFG, 0x00); 	/* 关闭低功耗 						*/icm20608_write_reg(ICM20_FIFO_EN, 0x00);		/* 关闭FIFO						*/return 0;
}

函数 icm20608_write_reg,写 ICM20608 的指定寄存器。

/** @description  : 写ICM20608指定寄存器* @param - reg  : 要读取的寄存器地址* @param - value: 要写入的值* @return		 : 无*/
void icm20608_write_reg(unsigned char reg, unsigned char value)
{/* ICM20608在使用SPI接口的时候寄存器地址* 只有低7位有效,寄存器地址最高位是读/写标志位* 读的时候要为1,写的时候要为0。*/reg &= ~0X80;	ICM20608_CSN(0);						/* 使能SPI传输			*/spich0_readwrite_byte(ECSPI3, reg); 	/* 发送寄存器地址		*/ spich0_readwrite_byte(ECSPI3, value);	/* 发送要写入的值			*/ICM20608_CSN(1);						/* 禁止SPI传输			*/
}	

函数 icm20608_read_reg,读 ICM20608 的指定寄存器。

/** @description	: 读取ICM20608寄存器值* @param - reg	: 要读取的寄存器地址* @return 		: 读取到的寄存器值*/
unsigned char icm20608_read_reg(unsigned char reg)
{unsigned char reg_val;	   	/* ICM20608在使用SPI接口的时候寄存器地址* 只有低7位有效,寄存器地址最高位是读/写标志位* 读的时候要为1,写的时候要为0。*/reg |= 0x80; 	ICM20608_CSN(0);               					/* 使能SPI传输	 		*/spich0_readwrite_byte(ECSPI3, reg);     		/* 发送寄存器地址  		*/ reg_val = spich0_readwrite_byte(ECSPI3, 0XFF);	/* 读取寄存器的值 			*/ICM20608_CSN(1);                				/* 禁止SPI传输 			*/return(reg_val);               	 				/* 返回读取到的寄存器值 */
}

函数 icm20608_read_len,读取 ICM20608 的寄存器值,但可以读取连续多个寄存器的值,一般用于读取 ICM20608 传感器数据。

/** @description	: 读取ICM20608连续多个寄存器* @param - reg	: 要读取的寄存器地址* @return 		: 读取到的寄存器值*/
void icm20608_read_len(unsigned char reg, unsigned char *buf, unsigned char len)
{  unsigned char i;/* ICM20608在使用SPI接口的时候寄存器地址,只有低7位有效,* 寄存器地址最高位是读/写标志位读的时候要为1,写的时候要为0。*/reg |= 0x80; ICM20608_CSN(0);               				/* 使能SPI传输	 		*/spich0_readwrite_byte(ECSPI3, reg);			/* 发送寄存器地址  		*/   	   for(i = 0; i < len; i++)					/* 顺序读取寄存器的值 			*/{buf[i] = spich0_readwrite_byte(ECSPI3, 0XFF);	}ICM20608_CSN(1);                			/* 禁止SPI传输 			*/
}

函数icm20608_gyro_scaleget ,获取陀螺仪的分辨率。

/** @description : 获取陀螺仪的分辨率* @param		: 无* @return		: 获取到的分辨率*/
float icm20608_gyro_scaleget(void)
{unsigned char data;float gyroscale;data = (icm20608_read_reg(ICM20_GYRO_CONFIG) >> 3) & 0X3;switch(data) {case 0: gyroscale = 131;break;case 1:gyroscale = 65.5;break;case 2:gyroscale = 32.8;break;case 3:gyroscale = 16.4;break;}return gyroscale;
}

函数 icm20608_accel_scaleget,获取加速度计的分辨率。

/** @description : 获取加速度计的分辨率* @param		: 无* @return		: 获取到的分辨率*/
unsigned short icm20608_accel_scaleget(void)
{unsigned char data;unsigned short accelscale;data = (icm20608_read_reg(ICM20_ACCEL_CONFIG) >> 3) & 0X3;switch(data) {case 0: accelscale = 16384;break;case 1:accelscale = 8192;break;case 2:accelscale = 4096;break;case 3:accelscale = 2048;break;}return accelscale;
}

函数 icm20608_getdata,获取 ICM20608 的加速度计、陀螺仪和温度计的数据,并且会根据设置的测量范围计算出实际的值,比如加速度的 g 值、陀螺仪的角速度值和温度计的温度值。

/** @description : 读取ICM20608的加速度、陀螺仪和温度原始值* @param 		: 无* @return		: 无*/
void icm20608_getdata(void)
{float gyroscale;unsigned short accescale;unsigned char data[14];icm20608_read_len(ICM20_ACCEL_XOUT_H, data, 14);gyroscale = icm20608_gyro_scaleget();accescale = icm20608_accel_scaleget();icm20608_dev.accel_x_adc = (signed short)((data[0] << 8) | data[1]); icm20608_dev.accel_y_adc = (signed short)((data[2] << 8) | data[3]); icm20608_dev.accel_z_adc = (signed short)((data[4] << 8) | data[5]); icm20608_dev.temp_adc    = (signed short)((data[6] << 8) | data[7]); icm20608_dev.gyro_x_adc  = (signed short)((data[8] << 8) | data[9]); icm20608_dev.gyro_y_adc  = (signed short)((data[10] << 8) | data[11]);icm20608_dev.gyro_z_adc  = (signed short)((data[12] << 8) | data[13]);/* 计算实际值 */icm20608_dev.gyro_x_act = ((float)(icm20608_dev.gyro_x_adc)  / gyroscale) * 100;icm20608_dev.gyro_y_act = ((float)(icm20608_dev.gyro_y_adc)  / gyroscale) * 100;icm20608_dev.gyro_z_act = ((float)(icm20608_dev.gyro_z_adc)  / gyroscale) * 100;icm20608_dev.accel_x_act = ((float)(icm20608_dev.accel_x_adc) / accescale) * 100;icm20608_dev.accel_y_act = ((float)(icm20608_dev.accel_y_adc) / accescale) * 100;icm20608_dev.accel_z_act = ((float)(icm20608_dev.accel_z_adc) / accescale) * 100;icm20608_dev.temp_act = (((float)(icm20608_dev.temp_adc) - 25 ) / 326.8 + 25) * 100;
}

main.c

文件 main.c里有一个重要的函数是 imx6ul_hardfpu_enable,这个函数用于开启 I.MX6U 的 NEON 和硬件 FPU(浮点运算单元),因为使用到了浮点运算,而 I.MX6U 的Cortex-A7 是支持 NEON 和 FPU(VFPV4_D32)的,但是在使用 I.MX6U 的硬件 FPU 之前是先要开启的。

函数 imx6ul_hardfpu_enable

/** @description	: 使能I.MX6U的硬件NEON和FPU* @param 		: 无* @return 		: 无*/void imx6ul_hardfpu_enable(void)
{uint32_t cpacr;uint32_t fpexc;/* 使能NEON和FPU */cpacr = __get_CPACR();cpacr = (cpacr & ~(CPACR_ASEDIS_Msk | CPACR_D32DIS_Msk))|  (3UL << CPACR_cp10_Pos) | (3UL << CPACR_cp11_Pos);__set_CPACR(cpacr);fpexc = __get_FPEXC();fpexc |= 0x40000000UL;	__set_FPEXC(fpexc);
}

main函数外设初始化部分调用了函数 icm20608_init 来初始化 ICM20608,在 while 循环中不断的调用函数 icm20608_getdata 获取ICM20608 的传感器数据,并且打印出来。

/** @description	: main函数* @param 		: 无* @return 		: 无*/
int main(void)
{unsigned char state = OFF;imx6ul_hardfpu_enable();	/* 使能I.MX6U的硬件浮点 			*/int_init(); 				/* 初始化中断(一定要最先调用!) */imx6u_clkinit();			/* 初始化系统时钟 					*/delay_init();				/* 初始化延时 					*/clk_enable();				/* 使能所有的时钟 					*/led_init();					/* 初始化led 					*/beep_init();				/* 初始化beep	 				*/uart_init();				/* 初始化串口,波特率115200 */	while(icm20608_init())		/* 初始化ICM20608	 			*/{printf("ICM20608 Check Failed!\r\n");delayms(500);printf("Please Check!        \r\n");delayms(500);}	while(1)					{		icm20608_getdata();printf("accel x = %d\r\n",icm20608_dev.accel_x_adc);printf("accel y = %d\r\n",icm20608_dev.accel_y_adc);printf("accel z = %d\r\n",icm20608_dev.accel_z_adc);printf("gyrp  x = %d\r\n",icm20608_dev.gyro_x_adc);printf("gyro  y = %d\r\n",icm20608_dev.gyro_y_adc);printf("gyro  z = %d\r\n",icm20608_dev.gyro_z_adc);printf("temp    = %d\r\n",icm20608_dev.temp_adc);delayms(120);state = !state;led_switch(LED0,state);	}return 0;
}


Makefile文件

首先添加 SPI 和 ICM20608 的驱动头文件(.h)和源文件(.c)路径。

编译的时候加入了“-march=armv7-a -mfpu=neon-vfpv4 -mfloat-abi=hard”指令,这些指令用于指定编译浮点运算的时候使用硬件 FPU。

程序编写完成后,烧录到开发板里,实验现象大概是:

能显示获取到的传感器数据,如果动一下开发板的话加速度计和陀螺仪的数据就会变化。

加速度计 Z 轴在静止状态下是 0.98g,这是重力加速度。

能看到芯片内部的温度。

http://www.dtcms.com/a/271879.html

相关文章:

  • 《数据库》第一次作业:MySQL数据库账户及授权
  • FeatherScan v4.0 – 适用于Linux的全自动内网信息收集工具
  • 2025.07.09华为机考真题解析-第二题200分
  • 华为L1-L6流程体系核心框架
  • 2025.07.09华为机考真题解析-第三题300分
  • java与sql的日期类型常用教程讲解
  • 常见射频电路板工艺流程
  • 《信号与系统》学习笔记——第八章
  • 大小端模式如何影响位域中各成员的位序;位域的其他细节问题
  • k8s:安装 Helm 私有仓库ChartMuseum、helm-push插件并上传、安装Zookeeper
  • 正点原子 文件权限
  • Spring核心原理的快速入门:快速了解IoC与DI
  • RHCE考试 ——笔记
  • 【Linux手册】从接口到管理:Linux文件系统的核心操作指南
  • Redis数据安全性分析
  • PyTorch Tensor 操作入门:转换、运算、维度变换
  • 【NLP入门系列六】Word2Vec模型简介,与以《人民的名义》小说原文实践
  • IPv4和IPv6双栈配置
  • 【K8S】Kubernetes 使用 Ingress-Nginx 基于 Cookie 实现会话保持的负载均衡
  • HCIA第一次实验报告:静态路由综合实验
  • day11-微服务面试篇
  • C++11 std::is_sorted 和 std::is_sorted_until 原理解析
  • CentOs 7 MySql8.0.23之前的版本主从复制
  • 无缝矩阵与普通矩阵的对比分析
  • 中老年人的陪伴,猫咪与机器人玩具有什么区别?
  • Java 与 MySQL 性能优化:MySQL连接池参数优化与性能提升
  • MySQL(127)如何解决主从同步失败问题?
  • adb 简介与常用命令
  • 分布式ID 与自增区别
  • 虚拟储能与分布式光伏协同优化:新型电力系统的灵活性解决方案