1、SPI相比I2C最大的优势有两点:一个是速度快,最高可以大几十M,甚至上百MHz,第二个就是SPI是个全双工。
2、SPI接口和I2C一样,一个SPI接口可以连接多个SPI外设,SPI通过CS引脚/数据线,片选引脚来选择和哪个SPI外设通信。SPI通信前先将指定的SPI外设对应的CS引脚拉低来选中此设备。
3、ALPHA开发板上通过ECSPI3接口连接了一个6轴传感器,引脚如下:
ECSPI3_SCLK : UART2_RX
ECSPI3_MOSI:UART2_CTS
ECSPI3_SS0:UART2_TXD
ECSPI3_MISO: UART2_RTS
6ULL一个SPI主接口有4个硬件片选,分别为SS0~SS3。
4、根据CPOL和CPHA可以设置四种工作模式,一般使用CPOL=0、CPHA=0。
1、6ULL的SPI接口叫做ECSPI,支持全双工、主丛可配置。
2、4个硬件片选信号,可以使用软件片选,这样一个SPI接口所能连接的外设就无限制了。
1、RXDATA寄存器为接收到的数据。
2、TXDATA寄存器为发送数据寄存器。
3、CONREG寄存器为配置寄存器,bit0置1,使能SPI。Bit3置1,表示当向TXFIFO写入数据以后马上开启SPI突发访问,也就是发送数据。Bit7:4设置SPI通道主从模式,bit7为通道3,bit4为通道0,我们使用到了SS0,也就是通道0,因此需要设置bit4为1。Bit19:18设置为00,我们使用到SS0,也就是通道0。Bit31:30设置突发访问长度,我们设置为7,也就是8bit突发长度,一个字节。
4、CONFIGREG寄存器的bit0为PHA,设置为0,表示 串行时钟的第一个跳变沿开始采集数据。设置bit4为PO,设置为0,表示SCLK空闲的时候为低电平。Bit8设置0。Bit12设置 为0。Bit16设置为0,表示空闲的时候数据线为高。Bit20设置为0,表示SCLK空闲的时候为低。
5、STATREG寄存器,bit0表示TXFIFO为空,我们在发送数据之前要等待TXFIFO为空,也就是等待bit0为1。Bit3表示RXFIFO是否有数据,为1的时候示RXFIFO至少有1个字的数据,我们在接收数据的时候要等到bit3为1。
6、PERIODREG寄存器,bit14:0设置wait states时间,我们设置为0X2000。Bit15设置wait states的时钟源为SPI CLK,将此位设置0。Bit21:16表示片选信号的延时,可设置0-63,这里设置为0.
7、SPI时钟设置!
SPI时钟源最终来源于pll3_sw_clk=480MHz/8=60MHz,设置CSCDR2寄存器的bit18为0,也就是ECSPI时钟源为60MHz。bit24:19设置为0,表示1分频,因此最终进入到SPI外设的时钟源为60MHz
ECSPI模块还需要对时钟进行两级分频,由ECSPI_CONREG寄存器设置。Bit15:12设置前级分频,可以设置00xf,表示116分频。Bit11:8设置2级分频,设置2^n分频,n=0~15.
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。
1、我们在使用浮点计算的时候程序卡死了,因为我们没有开始6UL的硬件浮点运算。我们在编译的时候没有使用浮点。解决此问题需要两点:
①、开启6UL的硬件浮点单元
②、编译的是时候指定硬件浮点。
//bsp_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
//bsp_spi.c
#include "bsp_spi.h"
#include "bsp_gpio.h"
#include "stdio.h"
//初始化SPI
//base : 要初始化的SPI
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 */
}
//SPI通道0发送/接收一个字节的数据
//base : 要使用的SPI
//txdata : 要发送的数据
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;
}
//bsp_icm20608.h
#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
//bsp_icm20608.c
#include "bsp_icm20608.h"
#include "bsp_delay.h"
#include "bsp_spi.h"
#include "stdio.h"
struct icm20608_dev_struc icm20608_dev; /* icm20608设备 */
//初始化ICM20608
//返回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指定寄存器
//reg : 要读取的寄存器地址
//value: 要写入的值
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寄存器值
//reg : 要读取的寄存器地址
//返回读取到的寄存器值
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连续多个寄存器
//reg : 要读取的寄存器地址
//返回读取到的寄存器值
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传输 */
}
//获取陀螺仪的分辨率
//返回获取到的分辨率
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;
}
//获取加速度计的分辨率
//返回获取到的分辨率
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的加速度、陀螺仪和温度原始值
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;
}