imx6ull裸机-SPI

1 SPI介绍#

s3c2440裸机编程-SPI | Hexo (fuzidage.github.io)有详细介绍SPI协议。

1.1 imx6ull SPI控制器介绍#

NXP的6ull参考手册第Chapter 20介绍了SPI控制器,Enhanced Configurable SPI (ECSPI)

1.1.1 特点#

①、全双工同步串行接口。
②、可配置的主/从模式。
③、四个硬件片选信号,支持多从机。
④、发送和接收都有一个 32x64 的 FIFO。
⑤、片选信号 SS/CS,时钟信号 SCLK 的极性相位(CPOL,CPHA)可配置。
⑥、支持 DMA
⑦、SCK最高可以到输入参考时钟高达60Mhz

1.1.2 框图#

image
最右边是引脚,SCLK,MISO,MOSI等,上面是外围总线,通过APB总线进行寄存器读写,INTREG,CONREG等等。TXDATA和TXDATA寄存器存放了要发送的数据和接收的收据。
时钟源来自Reference Clock or Low Frequency Clock。可选时钟源如下:这里选用ecspi_clk_root
image
image
① CSCDR2的ECSPI_CLK_SEL位设置为0,选择出PLL3_SW_CLK 进行8分频作为 ECSPI 根时钟源。PLL3_SW_CLK=480MHz,8分频就是60MHz。
② CSCDR2 的 ECSPI_CLK_PODF位再次进行分频,ECSPI_CLK_PODF位设置成0,表示2^0分频,也就是1分频。
③ 最后ECSPI_CLK_ROOT就为60MHz

1.1.3 时序#

CPOL时钟极性 和CPHA时钟相位组合成了4种模式:

CPOL:表示SPI CLK的初始电平(空闲状态时电平),0为低电平,1为高电平
CPHA:表示相位,即第一个还是第二个时钟沿采样数据,0为第一个时钟沿,1为第二个时钟沿

image

1.2 SPI控制器寄存器#

1.2.1 控制器初始化流程#

CONREG[EN]:复位,0表示复位
CCM开启ECSPI时钟
CONREG[EN]:复位,1表示反选复位
image

1.2.2 寄存器介绍#

1.2.2.1 RXDATA#

RXDATA寄存器:接收数据寄存器,RR位的状态决定接受数据是否就绪
image

1.2.2.2 TXDATA#

TXDATA寄存器:发送数据寄存器,实际传输的位数由相应SPI控制寄存器的BURST_LENGTH位来决定。
image

1.2.2.3 CONREG#

CONREG寄存器:控制寄存器
image
EN:使能位,1为使能
SMC:为1表示当数据写入TXFIFO时,立即启动SPI突发;这里使用该模式
CHANNEL_MODE:硬件片选模式选择,bit[7:4]分别表示通道3到通道0,这里采用通道0设定为Master mode.因此bit[7:4]配置成1
POST_DIVIDER:后分频,0到15表示2^n次方分频,比如0就是1分频,15就是2^15分频
PRE_DIVIDER:前分频,0到15表示1到16分频
前面spi clk的时钟源为ECSPI_CLK_ROOT 60MHz,这里我们用6MHz,因此可以设置POST_DIVIDER=0,PRE_DIVIDER=9,表示10分频。
CHANNEL_SELECT:通道选择,也就是硬件片选SS选择,这里选择SS0,通道0
BURST_LENGTH:突发访问长度,这里我们用一次突发8bit, 配置成0x7

1.2.2.4 CONFIGREG#

CONFIGREG寄存器:配置寄存器
image
SCLK_PHA:时钟相位,SCLK_PHA[3:0]分别对应通道3~0,设置为0表示第一个时钟沿采集数据,设置成1表示第二个时钟沿采集数据。(同POL组成4种模式)
SCLK_POL:时钟极性,表示时钟初始空闲时的电平,0为低电平,1为高电平。(同PHA组成4种模式)
SS_CTL:硬件片选的wave form select,这个用不上设置成0
SS_POL:硬件片选的极性选择,用不上设置成0
DATA_CTL:数据线空闲时电平状态,我们设置成0表示高电平
SCLK_CTL:时钟线空闲时电平状态,我们设置成0表示低电平(POL设置了时钟初始空闲时的电平为低电平)
HT_LENGTH: HT Mode不用,无需配置

1.2.2.5 STATREG#

STATREG寄存器:状态寄存器
image
TE:TXFIFO empty, 为1表示TXFIFO为空,0表示TXFIFO还没空,因此往TXDATA发送数据时,需要先等待TXFIFO为空。
RR: RXFIFO Ready,1表示有数据,0表示数据还没ready.读取RXDATA需要等RXFIFO先ready。

1.2.2.6 PERIODREG#

PERIODREG寄存器:采样周期寄存器
image
SAMPLE_ PERIOD:突发访问时的等待周期,表示等待多少个时钟周期后进行一下次突发访问。我们设置为0x2000。
image
CSRC: 等待周期的单位,0表示以SPI clk为单位, 1表示以low-frequency reference clk 32.768KHz为单位。
CSD_CTL:硬件片选延时,表示片选后多少个时钟周期才可以进行数据传输。(这里不用,我们用软件片选)

1.3 SPI控制器代码编写#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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 */
}

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

2 SPI 应用#

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

2.1.1 ICM-20608-G概述#

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。

2.1.2 应用场景#

image

2.1.3 陀螺仪和加速度特性#

image

2.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。

2.1.5 交流电器特性#

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

2.1.6 工作模式#

image

2.1.7 SPI方式寄存器访问#

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#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.2 ICM-20608-G寄存器描述#

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

2.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.

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#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.2 数据寄存器#

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

2.2.3 WHO_AM_I寄存器#

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

2.2.4 PWR_MGMT_1/PWR_MGMT_2寄存器#

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

复位初始化:

1
2
3
4
5
6
7
8
#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);

2.3 代码解析#

icm20608.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/* 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);

1
2
3
4
5
6
7
8
9
10
11
12
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()是获取陀螺仪和加速度计的最小单位:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/*
* @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为。同理以加速度计为例:量程为+-16是,换算出2048为1g。

可以看到用到了浮点运算,那么IMX6ULL属于armv7,支持硬件浮点运算:执行浮点运算前调用imx6ul_hardfpu_enable()函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
* @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 Manual4.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
编译选项开启硬件浮点编译:

1
2
$(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 $@ $<`

2.3.1 测试效果#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*
* @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
左右晃动时,陀螺仪数据明显增加。