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 框图 最右边是引脚,SCLK,MISO,MOSI等,上面是外围总线,通过APB总线进行寄存器读写,INTREG,CONREG
等等。TXDATA和TXDATA寄存器存放了要发送的数据和接收的收据。 时钟源来自Reference Clock or Low Frequency Clock
。可选时钟源如下:这里选用ecspi_clk_root
。 ① 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为第二个时钟沿
1.2 SPI控制器寄存器 1.2.1 控制器初始化流程 CONREG [EN]:复位,0表示复位CCM 开启ECSPI时钟CONREG [EN]:复位,1表示反选复位
1.2.2 寄存器介绍 1.2.2.1 RXDATA RXDATA寄存器 :接收数据寄存器,RR位的状态决定接受数据是否就绪
1.2.2.2 TXDATA TXDATA寄存器 :发送数据寄存器,实际传输的位数由相应SPI控制寄存器的BURST_LENGTH位来决定。
1.2.2.3 CONREG CONREG寄存器 :控制寄存器EN :使能位,1为使能SMC :为1表示当数据写入TXFIFO时,立即启动SPI突发;这里使用该模式CHANNEL_MODE :硬件片选模式选择,bit[7:4]分别表示通道3到通道0,这里采用通道0设定为Master mode.因此bit[7:4]配置成1POST_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,通道0BURST_LENGTH :突发访问长度,这里我们用一次突发8bit, 配置成0x7
1.2.2.4 CONFIGREG CONFIGREG寄存器 :配置寄存器SCLK_PHA :时钟相位,SCLK_PHA[3:0]分别对应通道3~0,设置为0表示第一个时钟沿采集数据,设置成1表示第二个时钟沿采集数据。(同POL组成4种模式)SCLK_POL :时钟极性,表示时钟初始空闲时的电平,0为低电平,1为高电平。(同PHA组成4种模式)SS_CTL :硬件片选的wave form select,这个用不上设置成0SS_POL :硬件片选的极性选择,用不上设置成0DATA_CTL :数据线空闲时电平状态,我们设置成0表示高电平SCLK_CTL :时钟线空闲时电平状态,我们设置成0表示低电平(POL设置了时钟初始空闲时的电平为低电平)HT_LENGTH : HT Mode不用,无需配置
1.2.2.5 STATREG STATREG寄存器 :状态寄存器TE :TXFIFO empty, 为1表示TXFIFO为空,0表示TXFIFO还没空,因此往TXDATA发送数据时,需要先等待TXFIFO为空。RR : RXFIFO Ready,1表示有数据,0表示数据还没ready.读取RXDATA需要等RXFIFO先ready。
1.2.2.6 PERIODREG PERIODREG寄存器 :采样周期寄存器SAMPLE_ PERIOD :突发访问时的等待周期,表示等待多少个时钟周期后进行一下次突发访问。我们设置为0x2000。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) { base->CONREG = 0 ; base->CONREG |= (1 << 0 ) | (1 << 3 ) | (1 << 4 ) | (7 << 20 ); base->CONFIGREG = 0 ; base->PERIODREG = 0X2000 ; base->CONREG &= ~((0XF << 12 ) | (0XF << 8 )); base->CONREG |= (0X9 << 12 ); } unsigned char spich0_readwrite_byte (ECSPI_Type *base, unsigned char txdata) { uint32_t spirxdata = 0 ; uint32_t spitxdata = txdata; base->CONREG &= ~(3 << 18 ); base->CONREG |= (0 << 18 ); while ((base->STATREG & (1 << 0 )) == 0 ){} base->TXDATA = spitxdata; while ((base->STATREG & (1 << 3 )) == 0 ){} 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 应用场景
2.1.3 陀螺仪和加速度特性
2.1.4 电器特性 可以看到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 交流电器特性 当用i2c通信,AD0引脚决定i2c从地址是0x68还是0x69。可以看到power-on reset上电时序,需要Valid power-on RESET时间最少0.01ms, 从启动到寄存器读写等11ms。
2.1.6 工作模式
2.1.7 SPI方式寄存器访问 数据上升沿锁存,下降沿数据发生改变。最大高达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)) void icm20608_write_reg (unsigned char reg, unsigned char value) { reg &= ~0X80 ; ICM20608_CSN(0 ); spich0_readwrite_byte(ECSPI3, reg); spich0_readwrite_byte(ECSPI3, value); ICM20608_CSN(1 ); } unsigned char icm20608_read_reg (unsigned char reg) { unsigned char reg_val; reg |= 0x80 ; ICM20608_CSN(0 ); spich0_readwrite_byte(ECSPI3, reg); reg_val = spich0_readwrite_byte(ECSPI3, 0XFF ); ICM20608_CSN(1 ); return (reg_val); }
2.2 ICM-20608-G寄存器描述 ICM-20608-G寄存器的地址和数据都是单字节。
2.2.1 控制寄存器 控制配置寄存器0x1a,0x1b,0x1c,0x1d,设置量程等配置。 0x19设置分频,不分频,配成0 0x1a设置陀螺仪低通滤波带宽BW=20Hz,配成0x4. 0x1b设置gyro量程,配成最大0x18. 0x1c设置加速度计的量程,也配成最大0x18. 0x1d设置加速度计低通滤波BW=21.2Hz 0x1e设置low power,配成0,关闭低功耗. 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 ); icm20608_write_reg(ICM20_ACCEL_CONFIG, 0x18 ); icm20608_write_reg(ICM20_CONFIG, 0x04 ); icm20608_write_reg(ICM20_ACCEL_CONFIG2, 0x04 ); icm20608_write_reg(ICM20_PWR_MGMT_2, 0x00 ); icm20608_write_reg(ICM20_LP_MODE_CFG, 0x00 ); icm20608_write_reg(ICM20_FIFO_EN, 0x00 );
2.2.2 数据寄存器 数据寄存器0x3b0x48表示加速度和陀螺仪数据,可以看到该传感器的寄存器地址都是单字节,ADC精度16bit,因此需要2个寄存器来表示一个轴的坐标数据。 0x3b-0x40表示加速度计3轴数据。 0x42 温度数据 0x430x48陀螺仪3轴数据。
2.2.3 WHO_AM_I寄存器 寄存器表示设备ID,默认0xAF.
2.2.4 PWR_MGMT_1/PWR_MGMT_2寄存器 电源管理模式寄存器 可以看到bit6默认是一个sleep mode, bit7是复位信号,复位后,默认bit6会变成1,进入睡眠模式。Bit4 陀螺仪待机,bit3关闭温度传感器等等都不要开启,设置成0,bit[2:0]时钟选择自动。 可以看到设置成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 ); 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 #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 struct icm20608_dev_struc { signed int gyro_x_adc; signed int gyro_y_adc; signed int gyro_z_adc; signed int accel_x_adc; signed int accel_y_adc; signed int accel_z_adc; signed int temp_adc; signed int gyro_x_act; signed int gyro_y_act; signed int gyro_z_act; signed int accel_x_act; signed int accel_y_act; signed int accel_z_act; signed int temp_act; }; struct icm20608_dev_struc icm20608_dev ;
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; reg |= 0x80 ; ICM20608_CSN(0 ); spich0_readwrite_byte(ECSPI3, reg); for (i = 0 ; i < len; i++) buf[i] = spich0_readwrite_byte(ECSPI3, 0XFF ); ICM20608_CSN(1 ); }
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; } 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 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()
函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void imx6ul_hardfpu_enable (void ) { uint32_t cpacr; uint32_t fpexc; 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 |= 0x40000000 UL; __set_FPEXC(fpexc); }
打开Cortex-A7 MPCore Technical Reference Manual
的4.3.34 Non-Secure Access Control Register
介绍:开启硬件NEON和FPU 打开ARM®Architecture Reference Manual ARMv7-A and ARMv7-R edition
介绍FPEXC寄存器, bit30置1,使能浮点运算
打开IM6ULL 参考手册:可见IMX6U支持浮点单元: 编译选项开启硬件浮点编译:
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 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); }
静止时,有一个z方向的加速度2048,也就是1g,刚好时重力加速度。静止时,陀螺仪几乎没有角速度,因此3轴数据都几乎为0°。
左右晃动时,陀螺仪数据明显增加。