之前一直使用的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;
2.当SPI通讯速率达到10Mbit/s时,时钟高电平需介于45%至55%之间;
3.最小 SPI/I2C 时钟速率取决于 ODR。如果ODR低于4 kHz,则最小时钟速率为100 kHz。如果 ODR 大于 4 kHz,则最小时钟速率为 200 kHz;
4.为防止在使用SPI时切换到I2C模式,应通过将I2C_IF_DIS寄存器配置位设置为I2C_IF来禁用I2C接口;
5.数据在时钟的上升沿锁定,在时钟的下降沿进行传输;
6.一个完整的通讯周期至少为两个字节周期,第一个周期主机发送寄存器地址及读/写指令(可以不接收),第二个字节主机发送/接收数据;
7.SPI数据一次传输一个字节为MSB优先。
SPI通讯时序与相关参数如下图所示:
所需硬件为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的步骤已全部完成,接下来我会试着将这个库移植到梁山派上,当然,前提是我没有懒癌发作(笑