STM32通过SPI读取ICM20602传感器(一)

一、起因

        之前一直使用的IMU是正点原子的ATK-IMU901模块,集成度很高(一块板子上集成了气压计、磁力计、加速度计/陀螺仪,而且还带了一块GD32进行数据解算与融合),性能非常优秀,零飘几乎没有,唯一的问题是它是使用串口通讯的,这就带来了两个问题:

1.串口的通讯速率有限,一般串口通讯波特率为115200,在这个波特率下,若位格式为8位数据位、一位起始位、一位停止位、无校验位的组合,则最大数据传输速率为11520Byte/s,即11.25KB/s,这个速度对于一般的姿态解算任务而言是完全够用了,但是对于无人机这类需要同时以高帧率融合多传感器数据的任务来说则多少显得有点捉襟见肘;

2.串口无法同时接入多设备,对我来说,这个问题比起前面一个问题更要命一点,因为我的单片机不仅需要留出一个控制台串口,而且还需要和上位机进行通讯,此外还要兼顾未来可能出现的拓展需求,因此现阶段串口的占用自然是越少越好。

        因此,我试图将ATK-IMU901进行拆分,将相关传感器的功能通过IIC和SPI总线实现,以此节省下一个串口,作为ATK-IMU901最核心的传感器,ICM20602自然就成为了我的第一个目标。

二、传感器概述

        ICM20602为应美盛(现已被TDK收购)推出的一款六轴IMU模块,相较于MPU6050,其拥有更好的精度与更小的漂移,支持IIC与SPI两种通讯模式,本次使用SPI总线对其进行读取。通读ICM20602数据手册,通讯协议要点如下:

1.SPI通讯速率需小于10Mbit/s,IIC通讯速率需小于400Kbit/s;

Datasheet  Page 7 of 57

2.当SPI通讯速率达到10Mbit/s时,时钟高电平需介于45%至55%之间;

3.最小 SPI/I2C 时钟速率取决于 ODR。如果ODR低于4 kHz,则最小时钟速率为100 kHz。如果 ODR 大于 4 kHz,则最小时钟速率为 200 kHz;

Datasheet  Page 13 of 57

4.为防止在使用SPI时切换到I2C模式,应通过将I2C_IF_DIS寄存器配置位设置为I2C_IF来禁用I2C接口;

Datasheet  Page 24 of 57

5.数据在时钟的上升沿锁定,在时钟的下降沿进行传输;

6.一个完整的通讯周期至少为两个字节周期,第一个周期主机发送寄存器地址及读/写指令(可以不接收),第二个字节主机发送/接收数据;

Datasheet  Page 26 of 57

7.SPI数据一次传输一个字节为MSB优先。

Datasheet  Page 26 & 27 of 57

SPI通讯时序与相关参数如下图所示:

 Datasheet  Page 15 of 57
Datasheet  Page 26 & 27 of 57

三、代码实现

所需硬件为ICM20602模块、STM32单片机(我的型号为STM32L431RCT6)、杜邦线、下载器等

开发环境为cubemx 6.7.0 + MDK 5.18

首先在cubemx中进行配置:

 我是用的是SPI1,速度为5Mbit/s,配置Data Size为8位、First Bit为MSB First,此外,还需根据前文的时序图,配置CPOL为High,配置CPHA为2 Edge(我看到的几乎所有文章都将CPOL配置为Low,CPHA为1 Edge,不知道为什么),不使能硬件NSS(ST的硬件NSS貌似有bug,不过我没试过),然后选中一个引脚配置为GPIO_Output,以此作为NSS引脚使用(我使用的是PA4),详细配置如下:

 默认输出状态为高电平,添加上拉,输出速率倒不是非常重要(哪怕设为2Mhz也能支撑16Mbit/s的传输速度)。

ICM20602的SPI驱动代码如下,这部分代码我是在逐飞库的基础上改的:

SEEKFREE_ICM20602.h

/*********************************************************************************************************************
 * COPYRIGHT NOTICE
 * Copyright (c) 2018,逐飞科技
 * All rights reserved.
 * 技术讨论QQ群:179029047
 *
 * 以下所有内容版权均属逐飞科技所有,未经允许不得用于商业用途,
 * 欢迎各位使用并传播本程序,修改内容时必须保留逐飞科技的版权声明。
 *
 * @file       		ICM20602
 * @company	   		成都逐飞科技有限公司
 * @author     		逐飞科技(QQ3184284598)
 * @version    		查看common.h内VERSION宏定义
 * @Software 		IAR 7.8 or MDK 5.24a
 * @Target core		LPC54606J512BD100
 * @Taobao   		https://seekfree.taobao.com/
 * @date       		2018-05-24
 * @note		
					接线定义:
					------------------------------------ 

					------------------------------------ 
 ********************************************************************************************************************/
#ifndef _SEEKFREE_ICM20602_h
#define _SEEKFREE_ICM20602_h

#include "stm32l4xx_hal.h"





#define     ICM20602_DEV_ADDR           0x69 //SA0接地:0x68   SA0上拉:0x69  模块默认上拉


#define     ICM20602_SPI_W              0x00
#define     ICM20602_SPI_R              0x80


#define     ICM20602_XG_OFFS_TC_H       0x04
#define     ICM20602_XG_OFFS_TC_L       0x05
#define     ICM20602_YG_OFFS_TC_H       0x07
#define     ICM20602_YG_OFFS_TC_L       0x08
#define     ICM20602_ZG_OFFS_TC_H       0x0A
#define     ICM20602_ZG_OFFS_TC_L       0x0B
#define     ICM20602_SELF_TEST_X_ACCEL  0x0D
#define     ICM20602_SELF_TEST_Y_ACCEL  0x0E
#define     ICM20602_SELF_TEST_Z_ACCEL  0x0F
#define     ICM20602_XG_OFFS_USRH       0x13
#define     ICM20602_XG_OFFS_USRL       0x14
#define     ICM20602_YG_OFFS_USRH       0x15
#define     ICM20602_YG_OFFS_USRL       0x16
#define     ICM20602_ZG_OFFS_USRH       0x17
#define     ICM20602_ZG_OFFS_USRL       0x18
#define     ICM20602_SMPLRT_DIV         0x19
#define     ICM20602_CONFIG             0x1A
#define     ICM20602_GYRO_CONFIG        0x1B
#define     ICM20602_ACCEL_CONFIG       0x1C
#define     ICM20602_ACCEL_CONFIG_2     0x1D
#define     ICM20602_LP_MODE_CFG        0x1E
#define     ICM20602_ACCEL_WOM_X_THR    0x20
#define     ICM20602_ACCEL_WOM_Y_THR    0x21
#define     ICM20602_ACCEL_WOM_Z_THR    0x22
#define     ICM20602_FIFO_EN            0x23
#define     ICM20602_FSYNC_INT          0x36
#define     ICM20602_INT_PIN_CFG        0x37
#define     ICM20602_INT_ENABLE         0x38
#define     ICM20602_FIFO_WM_INT_STATUS 0x39 
#define     ICM20602_INT_STATUS         0x3A
#define     ICM20602_ACCEL_XOUT_H       0x3B
#define     ICM20602_ACCEL_XOUT_L       0x3C
#define     ICM20602_ACCEL_YOUT_H       0x3D
#define     ICM20602_ACCEL_YOUT_L       0x3E
#define     ICM20602_ACCEL_ZOUT_H       0x3F
#define     ICM20602_ACCEL_ZOUT_L       0x40
#define     ICM20602_TEMP_OUT_H         0x41
#define     ICM20602_TEMP_OUT_L         0x42
#define     ICM20602_GYRO_XOUT_H        0x43
#define     ICM20602_GYRO_XOUT_L        0x44
#define     ICM20602_GYRO_YOUT_H        0x45
#define     ICM20602_GYRO_YOUT_L        0x46
#define     ICM20602_GYRO_ZOUT_H        0x47
#define     ICM20602_GYRO_ZOUT_L        0x48
#define     ICM20602_SELF_TEST_X_GYRO   0x50
#define     ICM20602_SELF_TEST_Y_GYRO   0x51
#define     ICM20602_SELF_TEST_Z_GYRO   0x52
#define     ICM20602_FIFO_WM_TH1        0x60
#define     ICM20602_FIFO_WM_TH2        0x61
#define     ICM20602_SIGNAL_PATH_RESET  0x68
#define     ICM20602_ACCEL_INTEL_CTRL   0x69
#define     ICM20602_USER_CTRL          0x6A
#define     ICM20602_PWR_MGMT_1         0x6B
#define     ICM20602_PWR_MGMT_2         0x6C
#define     ICM20602_I2C_IF             0x70
#define     ICM20602_FIFO_COUNTH        0x72
#define     ICM20602_FIFO_COUNTL        0x73
#define     ICM20602_FIFO_R_W           0x74
#define     ICM20602_WHO_AM_I           0x75
#define     ICM20602_XA_OFFSET_H        0x77
#define     ICM20602_XA_OFFSET_L        0x78
#define     ICM20602_YA_OFFSET_H        0x7A
#define     ICM20602_YA_OFFSET_L        0x7B
#define     ICM20602_ZA_OFFSET_H        0x7D
#define     ICM20602_ZA_OFFSET_L        0x7E


extern int16_t icm_gyro_x,icm_gyro_y,icm_gyro_z;
extern int16_t icm_acc_x,icm_acc_y,icm_acc_z;



//--------硬件SPI--------------
void icm20602_init_spi(void);
void get_icm20602_accdata_spi(void);
void get_icm20602_gyro_spi(void);

#define SPI1_CS_PIN_GPIO_Port GPIOA
#define SPI1_CS_PIN_Pin GPIO_PIN_4

#endif

SEEKFREE_ICM20602.c

/*********************************************************************************************************************
 * COPYRIGHT NOTICE
 * Copyright (c) 2018,逐飞科技
 * All rights reserved.
 * 技术讨论QQ群:一群:179029047(已满)  二群:244861897
 *
 * 以下所有内容版权均属逐飞科技所有,未经允许不得用于商业用途,
 * 欢迎各位使用并传播本程序,修改内容时必须保留逐飞科技的版权声明。
 *
 * @file       		ICM20602
 * @company	   		成都逐飞科技有限公司
 * @author     		逐飞科技(QQ3184284598)
 * @version    		查看doc内version文件 版本说明
 * @Software 		IAR 8.3 or MDK 5.24
 * @Taobao   		https://seekfree.taobao.com/
 * @date       		2019-04-30
 * @note		
					接线定义:
					------------------------------------ 
					SCL                 查看STM32F103ZETx HAL ICM20602 Hardware SPI.ioc文件
					SDA                 查看STM32F103ZETx HAL ICM20602 Hardware SPI.ioc文件
					------------------------------------ 
 ********************************************************************************************************************/

#include "stm32l4xx_hal.h"
#include "SEEKFREE_ICM20602.h"
extern SPI_HandleTypeDef hspi1;

int16_t icm_gyro_x,icm_gyro_y,icm_gyro_z;
int16_t icm_acc_x,icm_acc_y,icm_acc_z;



//-------------------------------------------------------------------------------------------------------------------
//  以下函数是使用硬件SPI通信,相比较IIC,速度比IIC快非常多。
//-------------------------------------------------------------------------------------------------------------------



//-------------------------------------------------------------------------------------------------------------------
//  @brief      ICM20602 SPI写寄存器
//  @param      cmd     寄存器地址
//  @param      val     需要写入的数据
//  @return     void					
//  @since      v1.0
//  Sample usage:				
//-------------------------------------------------------------------------------------------------------------------
void icm_spi_w_reg_byte(uint8_t cmd, uint8_t val)
{
    uint8_t dat[2];
	HAL_GPIO_WritePin(SPI1_CS_PIN_GPIO_Port, SPI1_CS_PIN_Pin,GPIO_PIN_RESET);
	
    dat[0] = cmd | ICM20602_SPI_W;
    dat[1] = val;
    HAL_SPI_Transmit(&hspi1,dat,2,500);

	HAL_GPIO_WritePin(SPI1_CS_PIN_GPIO_Port, SPI1_CS_PIN_Pin,GPIO_PIN_SET);
}

//-------------------------------------------------------------------------------------------------------------------
//  @brief      ICM20602 SPI读寄存器
//  @param      cmd     寄存器地址
//  @param      *val    接收数据的地址
//  @return     void					
//  @since      v1.0
//  Sample usage:				
//-------------------------------------------------------------------------------------------------------------------

void icm_spi_r_reg_byte(uint8_t cmd, uint8_t *val)
{
    uint8_t dat[2];

	HAL_GPIO_WritePin(SPI1_CS_PIN_GPIO_Port, SPI1_CS_PIN_Pin,GPIO_PIN_RESET);
    dat[0] = cmd | ICM20602_SPI_R;
    dat[1] = *val;
	HAL_SPI_TransmitReceive(&hspi1,dat,dat,2,500);
	
	HAL_GPIO_WritePin(SPI1_CS_PIN_GPIO_Port, SPI1_CS_PIN_Pin,GPIO_PIN_SET);

    *val = dat[1];
}
  
//-------------------------------------------------------------------------------------------------------------------
//  @brief      ICM20602 SPI多字节读寄存器
//  @param      *val    接收数据的地址
//  @param      num     读取数量
//  @return     void					
//  @since      v1.0
//  Sample usage:				
//-------------------------------------------------------------------------------------------------------------------
void icm_spi_r_reg_bytes(uint8_t * val, uint8_t num)
{
	HAL_GPIO_WritePin(SPI1_CS_PIN_GPIO_Port, SPI1_CS_PIN_Pin,GPIO_PIN_RESET);
	HAL_SPI_TransmitReceive(&hspi1,val,val,num,500);
	HAL_GPIO_WritePin(SPI1_CS_PIN_GPIO_Port, SPI1_CS_PIN_Pin,GPIO_PIN_SET);
}


//-------------------------------------------------------------------------------------------------------------------
//  @brief      ICM20602自检函数
//  @param      NULL
//  @return     void					
//  @since      v1.0
//  Sample usage:				
//-------------------------------------------------------------------------------------------------------------------
void icm20602_self3_check(void)
{
    uint8_t dat=0;
    while(0x12 != dat)   //读取ICM20602 ID
    {
        icm_spi_r_reg_byte(ICM20602_WHO_AM_I,&dat);
        HAL_Delay(10);
        //卡在这里原因有以下几点
        //1 MPU6050坏了,如果是新的这样的概率极低
        //2 接线错误或者没有接好
        //3 可能你需要外接上拉电阻,上拉到3.3V
    }

}
     
//-------------------------------------------------------------------------------------------------------------------
//  @brief      初始化ICM20602
//  @param      NULL
//  @return     void					
//  @since      v1.0
//  Sample usage:				
//-------------------------------------------------------------------------------------------------------------------
void icm20602_init_spi(void)
{
    uint8_t val = 0x0;

    HAL_Delay(10);  //上电延时
    

    icm20602_self3_check();//检测
    
    icm_spi_w_reg_byte(ICM20602_PWR_MGMT_1,0x80);//复位设备
    HAL_Delay(2);
    do
    {//等待复位成功
        icm_spi_r_reg_byte(ICM20602_PWR_MGMT_1,&val);
    }while(0x41 != val);
    
    icm_spi_w_reg_byte(ICM20602_PWR_MGMT_1,     0x01);            //时钟设置
    icm_spi_w_reg_byte(ICM20602_PWR_MGMT_2,     0x00);            //开启陀螺仪和加速度计
    icm_spi_w_reg_byte(ICM20602_CONFIG,         0x01);            //176HZ 1KHZ
    icm_spi_w_reg_byte(ICM20602_SMPLRT_DIV,     0x07);            //采样速率 SAMPLE_RATE = INTERNAL_SAMPLE_RATE / (1 + SMPLRT_DIV)
    icm_spi_w_reg_byte(ICM20602_GYRO_CONFIG,    0x18);            //±2000 dps
    icm_spi_w_reg_byte(ICM20602_ACCEL_CONFIG,   0x10);            //±8g
    icm_spi_w_reg_byte(ICM20602_ACCEL_CONFIG_2, 0x03);            //Average 4 samples   44.8HZ   //0x23 Average 16 samples
	//ICM20602_GYRO_CONFIG寄存器
    //设置为:0x00 陀螺仪量程为:±250 dps     获取到的陀螺仪数据除以131           可以转化为带物理单位的数据, 单位为:°/s
    //设置为:0x08 陀螺仪量程为:±500 dps     获取到的陀螺仪数据除以65.5          可以转化为带物理单位的数据,单位为:°/s
    //设置为:0x10 陀螺仪量程为:±1000dps     获取到的陀螺仪数据除以32.8          可以转化为带物理单位的数据,单位为:°/s
    //设置为:0x18 陀螺仪量程为:±2000dps     获取到的陀螺仪数据除以16.4          可以转化为带物理单位的数据,单位为:°/s

    //ICM20602_ACCEL_CONFIG寄存器
    //设置为:0x00 加速度计量程为:±2g          获取到的加速度计数据 除以16384      可以转化为带物理单位的数据,单位:g(m/s^2)
    //设置为:0x08 加速度计量程为:±4g          获取到的加速度计数据 除以8192       可以转化为带物理单位的数据,单位:g(m/s^2)
    //设置为:0x10 加速度计量程为:±8g          获取到的加速度计数据 除以4096       可以转化为带物理单位的数据,单位:g(m/s^2)
    //设置为:0x18 加速度计量程为:±16g         获取到的加速度计数据 除以2048       可以转化为带物理单位的数据,单位:g(m/s^2)
}

//-------------------------------------------------------------------------------------------------------------------
//  @brief      获取ICM20602加速度计数据
//  @param      NULL
//  @return     void
//  @since      v1.0
//  Sample usage:				执行该函数后,直接查看对应的变量即可
//-------------------------------------------------------------------------------------------------------------------
void get_icm20602_accdata_spi(void)
{
    struct
    {
        uint8_t reg;
        uint8_t dat[6];
    }buf;

    buf.reg = ICM20602_ACCEL_XOUT_H | ICM20602_SPI_R;
    
    icm_spi_r_reg_bytes(&buf.reg, 7);
    icm_acc_x = (int16_t)(((uint16_t)buf.dat[0]<<8 | buf.dat[1]));
    icm_acc_y = (int16_t)(((uint16_t)buf.dat[2]<<8 | buf.dat[3]));
    icm_acc_z = (int16_t)(((uint16_t)buf.dat[4]<<8 | buf.dat[5]));
}

//-------------------------------------------------------------------------------------------------------------------
//  @brief      获取ICM20602陀螺仪数据
//  @param      NULL
//  @return     void
//  @since      v1.0
//  Sample usage:				执行该函数后,直接查看对应的变量即可
//-------------------------------------------------------------------------------------------------------------------
void get_icm20602_gyro_spi(void)
{
    struct
    {
        uint8_t reg;
        uint8_t dat[6];
    }buf;

    buf.reg = ICM20602_GYRO_XOUT_H | ICM20602_SPI_R;
    
    icm_spi_r_reg_bytes(&buf.reg, 7);
    icm_gyro_x = (int16_t)(((uint16_t)buf.dat[0]<<8 | buf.dat[1]));
    icm_gyro_y = (int16_t)(((uint16_t)buf.dat[2]<<8 | buf.dat[3]));
    icm_gyro_z = (int16_t)(((uint16_t)buf.dat[4]<<8 | buf.dat[5]));
}







//-------------------------------------------------------------------------------------------------------------------
//  @brief      将ICM20602数据转化为带有物理单位的数据
//  @param      NULL
//  @return     void
//  @since      v1.0
//  Sample usage:               执行该函数后,直接查看对应的变量即可
//-------------------------------------------------------------------------------------------------------------------
float unit_icm_gyro_x;
float unit_icm_gyro_y;
float unit_icm_gyro_z;

float unit_icm_acc_x;
float unit_icm_acc_y;
float unit_icm_acc_z;
void icm20602_data_change(void)
{
    //ICM20602_GYRO_CONFIG寄存器
    //设置为:0x00 陀螺仪量程为:±250 dps     获取到的陀螺仪数据除以131           可以转化为带物理单位的数据, 单位为:°/s
    //设置为:0x08 陀螺仪量程为:±500 dps     获取到的陀螺仪数据除以65.5          可以转化为带物理单位的数据,单位为:°/s
    //设置为:0x10 陀螺仪量程为:±1000dps     获取到的陀螺仪数据除以32.8          可以转化为带物理单位的数据,单位为:°/s
    //设置为:0x18 陀螺仪量程为:±2000dps     获取到的陀螺仪数据除以16.4          可以转化为带物理单位的数据,单位为:°/s

    //ICM20602_ACCEL_CONFIG寄存器
    //设置为:0x00 加速度计量程为:±2g          获取到的加速度计数据 除以16384      可以转化为带物理单位的数据,单位:g(m/s^2)
    //设置为:0x08 加速度计量程为:±4g          获取到的加速度计数据 除以8192       可以转化为带物理单位的数据,单位:g(m/s^2)
    //设置为:0x10 加速度计量程为:±8g          获取到的加速度计数据 除以4096       可以转化为带物理单位的数据,单位:g(m/s^2)
    //设置为:0x18 加速度计量程为:±16g         获取到的加速度计数据 除以2048       可以转化为带物理单位的数据,单位:g(m/s^2)
    unit_icm_gyro_x = (float)icm_gyro_x/131;
    unit_icm_gyro_y = (float)icm_gyro_y/131;
    unit_icm_gyro_z = (float)icm_gyro_z/131;

    unit_icm_acc_x = (float)icm_acc_x/4096;
    unit_icm_acc_y = (float)icm_acc_y/4096;
    unit_icm_acc_z = (float)icm_acc_z/4096;

}

至此,STM32通过SPI读取ICM20602的步骤已全部完成,接下来我会试着将这个库移植到梁山派上,当然,前提是我没有懒癌发作(笑

物联沃分享整理
物联沃-IOTWORD物联网 » STM32通过SPI读取ICM20602传感器(一)

发表评论