IMX6ULL SPI应用-6轴陀螺仪加速度传感器ICM-20608-G

发布时间 2023-10-23 22:02:26作者: fuzidage

1 6轴陀螺仪加速度传感器ICM-20608-G

1.1 概述

The ICM-20608-G is a 6-axis MotionTracking device that combines a 3-axis gyroscope, and a 3-axis accelerometer in a small 3x3x0.75mm (16-pin LGA) package.
The gyroscope has a programmable full-scale range of ±250, ±500, ±1000, and ±2000 degrees/sec. The accelerometer has a user programmable accelerometer full-scale range of ±2g, ±4g, ±8g, and ±16g.
Other industry-leading features include on-chip 16-bit ADCs, programmable digital filters, an embedded temperature sensor, and programmable interrupts. The device features I2 C and SPI serial interfaces, a VDD operating range of 1.71 to 3.45V, and a separate digital IO supply, VDDIO from 1.71V to 3.45V. Communication with all registers of the device is performed using either I2 C at 400kHz or SPI at 8MHz.
1.包含3轴陀螺仪数据和3轴加速度数据。
2.陀螺仪和加速度量程可设定,陀螺仪量程可设定位+-250,+-500,+-1000, +-2000角度每秒。加速度同理也可设定量程。
3.精度为16bit ADC转换。
4.使用I2C/SPI接口通信,I2C速率高达400KHz, SPI高达8MHz。

1.2 应用场景

image

1.3 陀螺仪和加速度特性

image

1.4 电器特性

image
image
可以看到FS_SEL,AFS_SEL用来选择陀螺仪和加速度计的量程。举个例子,当角速度量程为+-250时,那么ADC的数据为多少表示为1度呢?已知ADC精度16bit, 数据范围[0,65535], 假如ADC的数据为x, 那么x/65636 = 1/500,算出x= 131.272x,对应表格数据中的131。加速度的换算公式也是同理, 当AFS_SEL=0时,x/65536 = 1/4, x=16384。

1.5 交流电器特性

image
当用i2c通信,AD0引脚决定i2c从地址是0x68还是0x69。可以看到power-on reset上电时序,需要Valid power-on RESET时间最少0.01ms, 从启动到寄存器读写等11ms。

1.6 工作模式

image

1.7 SPI方式寄存器访问

image
数据上升沿锁存,下降沿数据发生改变。最大高达8MHz时钟,一次读写需要16个或者更多时钟周期,第一个字节传输寄存器地址,第二个字节传输数据。首字节的首位表示是读还是写。

#define ICM20608_CSN(n)    (n ? gpio_pinwrite(GPIO1, 20, 1) : gpio_pinwrite(GPIO1, 20, 0))   /* SPI片选信号	 */
/*
 * @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传输			*/
}	
/*
 * @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);               	 				/* 返回读取到的寄存器值 */
}

2 ICM-20608-G寄存器描述

image
ICM-20608-G寄存器的地址和数据都是单字节。

2.1 控制寄存器

控制配置寄存器0x1a,0x1b,0x1c,0x1d,设置量程等配置。
image
0x19设置分频,不分频,配成0
image
0x1a设置陀螺仪低通滤波带宽BW=20Hz,配成0x4.
image
0x1b设置gyro量程,配成最大0x18.
image
0x1c设置加速度计的量程,也配成最大0x18.
image
0x1d设置加速度计低通滤波BW=21.2Hz
image
0x1e设置low power,配成0,关闭低功耗.
image
0x23设置fifo功能,这里配置0x0,禁用fifo.

设定量程,配置相关参数:

#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_FIFO_EN				0x23
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						*/

2.2 数据寄存器

数据寄存器0x3b~0x48表示加速度和陀螺仪数据,可以看到该传感器的寄存器地址都是单字节,ADC精度16bit,因此需要2个寄存器来表示一个轴的坐标数据。
image
0x3b-0x40表示加速度计3轴数据。
image
0x42 温度数据
image
image
0x43~0x48陀螺仪3轴数据。

2.3 WHO_AM_I

image
寄存器表示设备ID,默认0xAF.

2.4 PWR_MGMT_1/PWR_MGMT_2

电源管理模式寄存器
image
可以看到bit6默认是一个sleep mode, bit7是复位信号,复位后,默认bit6会变成1,进入睡眠模式。Bit4 陀螺仪待机,bit3关闭温度传感器等等都不要开启,设置成0,bit[2:0]时钟选择自动。
image
可以看到设置成0,6轴数据全使能

复位初始化:

#define	ICM20_PWR_MGMT_1			0x6B
#define	ICM20_WHO_AM_I 				0x75
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);

3 代码解析

icm20608.h
/* 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设备 */

icm20608.h定义了该模块的6轴数据寄存器地址和值。
连续顺序读写模块:前一个字节得写入寄存器地址,然后每次突发读取1字节数据,注意:这里不用每次都发送寄存器地址,顺序访问时,地址自动增长,即可顺序依次访问寄存器。如:向0x00~0x05地址依次发送6 byte数据,icm20608_read_len(0x00, buf, 6);

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()和icm20608_accel_scaleget()是获取陀螺仪和加速度计的最小单位:

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;
}

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


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

由于前面设置的陀螺仪和加速度计量程都是拉满的设置的0x18,因此gyroscale读出来就是对应16.4(最小单位),accescale读出来就是对应2048(最小单位)
然后读出14 byte数据,组装成short类型数据,16位ADC, 一轴数据刚好16位数据。最后转成人眼直观的实际的陀螺仪和加速度计数据,放大了100倍,放大一百倍目的是为了能够将小数的部分也能记录下来。
以陀螺仪为例:量程位+-2000时,换算出16.4为1°。同理以加速度计为例:量程为+-16是,换算出2048为1g。

可以看到用到了浮点运算,那么IMX6ULL属于armv7,支持硬件浮点运算:执行浮点运算前调用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);
}

打开Cortex-A7 MPCore Technical Reference Manual的4.3.34 Non-Secure Access Control Register介绍:开启硬件NEON和FPU
image
image
打开ARM®Architecture Reference Manual ARMv7-A and ARMv7-R edition介绍FPEXC寄存器, bit30置1,使能浮点运算
image

打开IM6ULL 参考手册:可见IMX6U支持浮点单元:
image
编译选项开启硬件浮点编译:
$(COBJS) : obj/%.o : %.c
$(CC) -Wall -march=armv7-a -mfpu=neon-vfpv4 -mfloat-abi=hard -Wa,-mimplicit-it=thumb -nostdlib -fno-builtin -c -O2 $(INCLUDE) -o $@ $<

效果:

/*
 * @description	: 指定的位置显示小数数据,比如5123,显示为51.23
 * @param - x	: X轴位置
 * @param - y 	: Y轴位置
 * @param - size: 字体大小
 * @param - num : 要显示的数据,实际小数扩大100倍,
 * @return 		: 无
 */
void decimals_display(unsigned short x, unsigned short y, unsigned char size, signed int num)
{
	signed int integ; 	/* 整数部分 */
	signed int fract;	/* 小数部分 */
	signed int uncomptemp = num; 
	char buf[200];

	if(num < 0)
		uncomptemp = -uncomptemp;
	integ = uncomptemp / 100;
	fract = uncomptemp % 100;

	memset(buf, 0, sizeof(buf));
	if(num < 0)
		sprintf(buf, "-%d.%d", integ, fract);
	else 
		sprintf(buf, "%d.%d", integ, fract);
	lcd_fill(x, y, x + 60, y + size, tftlcd_dev.backcolor);
	lcd_show_string(x, y, 60, size, size, buf); 
}

image
静止时,有一个z方向的加速度2048,也就是1g,刚好时重力加速度。静止时,陀螺仪几乎没有角速度,因此3轴数据都几乎为0°。

image
左右晃动时,陀螺仪数据明显增加。