基于Linux微计算机BBB的飞控开发之IMU数据获取与预处理

基于Linux微计算机BBB的飞控开发之IMU数据获取与预处理

1. 介绍

最近在做Linux平台的飞控开发,过程中还是遇到了不少的问题,当然也学到很多东西。在我博客的四旋翼无人机开发专栏里我会分享我制作的每一个过程,希望能够帮到同样在制作飞控的小伙伴。硬件平台是Beaglebone Blue(BBB)。

2. Linux下调用I2C

BBB上的IMU是个已经停产的MPU9250,使用的是I2C-2。我将分别介绍一下与I2C有关的Shell命令和C语言调用方法。

2.1 Shell下的调用方法

i2cdetect -l可以列举I2C总线适配器。
图1

i2cdetect -y -r x可以探测i2c-x总线上的设备。图2的–表示i2c-x总线上这个地址没有设备。UU表示这个地址的设备已经有驱动器使用了。我使用的mpu9250的地址为0x68,显然mpu9250已经被某个驱动程序控制了。这里我没有研究过这个mpu9250驱动程序的使用方法。经过我的测试,只需要使用sudo rmmod inv_mpu6050_i2c就可以卸载掉驱动。卸掉之后再使用i2cdetect -y -r x,就是图3这样。可以看到0x68这里出现了68,这样就可以直接通过i2c访问mpu9250,否则会提示Device or resource busy。重启之后这个驱动程序会自动重装。
图2

图3
在这里插入图片描述
i2cdump -y x n可以读取i2c-x总线上地址为n的设备的所有寄存器。在后面我们使用C语言配置mpu9250寄存器的时候,可以使用这个命令检查是否配置成功。图4是获取mpu9250所有寄存器的值。
图4
在这里插入图片描述
i2cget -y x n a可以获取i2c-x总线上地址为n的设备的地址a的寄存器。可以看到mpu9250的0x00地址值为0xba,这跟图4中的一致。
图5
在这里插入图片描述
i2cset -y x n a v可以设置i2c-x总线上地址为n的设备的地址为a的寄存器的值为v。图6中就将mpu9250的0x6b处的寄存器修改成了0x00。读者自己尝试的时候一定要先查一下寄存器手册,看这个寄存器是只读还是可写的,只读是写入无效的。
图6在这里插入图片描述

2.2 使用C语言访问i2c设备

Linux的一切皆文件的思想还是带来非常大方便的,访问一个设备就跟写文件一样简单。
下面是以mpu9250为例,简单介绍访问方法。程序经过实测。如果运行失败,请使用i2cdetect -y -r 2看一下0x68的位置是不是UU,如果是UU,则使用sudo rmmod inv_mpu6050_i2c卸载mpu9250驱动程序,原因在2.1提到了。

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>

int main(void)
{
	/*
	/dev/i2c-2其实是i2c总线驱动程序的接口,我们打开这个文件的时候
	并不是打开了一个真正的文件,而是触发了系统调用,open()会触发软
	中断,cpu执行权移交给操作系统,操作系统最终会调用i2c驱动程序里
	面的open()函数,进行一些初始化操作。ioctl(),write(),read()也
	是这样的原理。
	*/
	int file = open("/dev/i2c-2", O_RDWR);
	if(-1 == file)
		return 1;
	//这里是设置我们要访问的设备的i2c地址,mpu9250是0x68。
	if(-1 == ioctl(file, I2C_SLAVE, 0x68))
	{
		close(file);
		return 1;
	}
	/*
	这里就是写入了,了解i2c协议的读者应该知道写入和读取之前都要先写
	入需要写或读的寄存器的地址。这里的0x6b就是我们测试时写的寄存器
	的地址。我们把它写入0x01,再把它读出来看看是不是我们写的值。
	*/
	char writebuf[] = {0x6b, 0x01};
	if(-1 == write(file, writebuf, 2))	//写入两个字节
	{
		close(file);
		return 1;
	}
	char addr = 0x6b;
	char readbuf;
	if(-1 == write(file, addr, 1))	//仅写入地址
	{
		close(file);
		return 1;
	}
	if(-1 == read(file, &readbuf, 1))
	{
		close(file);
		return 1;
	}
	printf("0x68 register = 0x%02x", readbuf);
	close(file);
	return 0;
}

3. 配置IMU

先提一下mpu9250是mpu6050与磁力计ak8963的杂交种,mpu9250我们可以直接通过i2c-2的0x68地址访问,而访问ak8963有两种方式,一种是使用mpu9250的i2c主机去访问ak8963,我们再从mpu9250获取数据,还有一种是使用i2c-2的0x0c地址直接访问。第一种我没有尝试过,所以我们来看第二种如何实现。首先我们看图2,其中0x0c的位置并不存在设备,那时因为mpu9250默认使用了第一种方式,因而我们要先配置mpu9250关闭i2c主机,使能旁通,即将mpu9250的0x6a寄存器的bit5配置为0,将0x37的bit1配置为1。读者可以使用i2cset -y 2 0x68 0x6a 0x00 && i2cset -y 2 0x68 0x37 0x02来实现,并且使用i2cdetect -y -r 2看是否0x0c的位置显示为0c,若显示0c则成功。下面就可以直接用0x0c的地址访问ak8963了。
使用C语言访问i2c前面已经简单说了。下面我们使用i2c配置IMU。直接上程序。
这里的read_data和write_data的第一个参数是设备的i2c地址,第二个参数是寄存器地址**(下面的寄存器地址是用的宏定义,限于篇幅无法给出,可以查手册,或者联系我)**,第三个参数是寄存器的值,第四个参数是写入或读取的字节数。函数定义这里暂不方便给出,其实也就是对write和read函数做了简单的封装,根据前面的介绍和i2c协议,读者应该可以写出这样的函数。下面就是主要的寄存器配置内容。注意初始化ak8963之前一定要先设置mpu9050。

#define MPU9250_DEV	0x68
#define AK8963_DEV	0x0c
static int mpu9250_init(void)
{
	unsigned char reg_val;

	/*读取设备ID*/
	if(-1 == read_data(MPU9250_DEV, WHO_AM_I, &reg_val, 1))
		return -1;
	else
		printf("The ID of MPU9250 is 0x%02x\n\r", reg_val);

	/*复位*/
	reg_val = (1 << 7);
	if(-1 == write_data(MPU9250_DEV, PWR_MGMT_1, &reg_val, 1))
		return -1;
	udelay(50000);	//等待复位完成

	/*调整时钟*/
	reg_val = 0;
	if(-1 == write_data(MPU9250_DEV, PWR_MGMT_1, &reg_val, 1))
		return -1;

	/*设置采样率为1000Hz*/
#define INTERNAL_SAMPLE_RATE 1000
	reg_val = INTERNAL_SAMPLE_RATE / 1000 - 1;
	if(-1 == write_data(MPU9250_DEV, SMPLRT_DIV, &reg_val, 1))
		return -1;

	/*设置加速度计量程为+\-8g*/
	reg_val = (2 << 3);
	if(-1 == write_data(MPU9250_DEV, ACCEL_CONFIG, &reg_val, 1))
		return -1;

	/*设置陀螺仪量程为+2000dps*/
	reg_val = (3 << 3);
	if(-1 == write_data(MPU9250_DEV, GYRO_CONFIG, &reg_val, 1))
		return -1;

	/*设置低通滤波为20Hz*/
	reg_val = (4 << 0);
	if(-1 == write_data(MPU9250_DEV, CONFIG, &reg_val, 1))
		return -1;

	/*关闭MPU9250的I2C主机*/
	reg_val = (0 << 5);
	if(-1 == write_data(MPU9250_DEV, USER_CTRL, &reg_val, 1))
		return -1;

	/*使用直通方式访问AK8963*/
	reg_val = (1 << 1);
	if(-1 == write_data(MPU9250_DEV, INT_PIN_CFG, &reg_val, 1))
		return -1;
	
	return 0;
}

static int ak8963_init(void)
{
	unsigned char reg_val;

	/*读取设备ID*/
	if(-1 == read_data(AK8963_DEV, WIA, &reg_val, 1))
		return -1;
	else
		printf("The ID of AK8963 is 0x%02x\n\r", reg_val);

	/*复位*/
	reg_val = (1 << 0);
	if(-1 == write_data(AK8963_DEV, CNTL2, &reg_val, 1))
		return -1;
	udelay(50000);	//等待复位完成

	/*设定为100Hz、16bit连续采样模式*/
	reg_val = (6 << 0) | (1 << 4);
	if(-1 == write_data(AK8963_DEV, CNTL1, &reg_val, 1))
		return -1;
}

4. 读取IMU

首先我们读出原始数据:
主要ak8963有读取保护,读取完数据之后,一定要读取ST2(0x09)寄存器,否则ak8963会陷入数据保护状态,读出来值就一直是原来的值。

unsigned char i2c_data[14];
if(-1 == read_data(MPU9250_DEV, ACCEL_XOUT_H, i2c_data, 14))
		return -1;

	mpu_raw_data.acc.x = (short)((i2c_data[0] << 8) | i2c_data[1]);
	mpu_raw_data.acc.y = (short)((i2c_data[2] << 8) | i2c_data[3]);
	mpu_raw_data.acc.z = (short)((i2c_data[4] << 8) | i2c_data[5]);
	mpu_raw_data.gyro.x = (short)((i2c_data[8] << 8) | i2c_data[9]);
	mpu_raw_data.gyro.y = (short)((i2c_data[10] << 8) | i2c_data[11]);
	mpu_raw_data.gyro.z = (short)((i2c_data[12] << 8) | i2c_data[13]);
	mpu_raw_data.temperature = (short) ((i2c_data[6] << 8) | i2c_data[7]);
	
if(-1 == read_data(AK8963_DEV, HXL, i2c_data, 6))
		return -1;
/*解除读取保护*/
if(-1 == read_data(AK8963_DEV, ST2, &ak8963_status_2, 1))
		return -1;
	
	mag_raw_data.x = (i2c_data[0] | (i2c_data[1] << 8));
	mag_raw_data.y = (i2c_data[2] | (i2c_data[3] << 8));
	mag_raw_data.z = (i2c_data[4] | (i2c_data[5] << 8));

然后对数据进行纠偏:
这里的acc_offset、gyro_offset与mag_offset是在IMU静止的条件下读取1000次数据,并取平均值计算出来的,也就是加速度计、陀螺仪、磁力计的零点漂移。

filter_buf[filter_cnt][ACC_FILTER].x = mpu_raw_data.acc.x - acc_offset.x;
filter_buf[filter_cnt][ACC_FILTER].y = mpu_raw_data.acc.y - acc_offset.y;
filter_buf[filter_cnt][ACC_FILTER].z = mpu_raw_data.acc.z - acc_offset.z;
filter_buf[filter_cnt][GYRO_FILTER].x = mpu_raw_data.gyro.x - gyro_offset.x;
filter_buf[filter_cnt][GYRO_FILTER].y = mpu_raw_data.gyro.y - gyro_offset.y;
filter_buf[filter_cnt][GYRO_FILTER].z = mpu_raw_data.gyro.z - gyro_offset.z;
mag_handled_data.x = (mag_raw_data.x - mag_offset.x);
mag_handled_data.y = (mag_raw_data.y - mag_offset.y);
mag_handled_data.z = (mag_raw_data.z - mag_offset.z);
//这里对磁力计的数据另外进行了坐标转换,磁力计原始数据的坐标轴与加速度计、陀螺仪不一致。

接着对数据进行滑动均值滤波:

/*滑动均值滤波*/
	for(i = 0; i < SLIDING_FILTER_CNT_MAX; i++)
	{
		filter_tmp[ACC_FILTER].x += filter_buf[i][ACC_FILTER].x;
		filter_tmp[ACC_FILTER].y += filter_buf[i][ACC_FILTER].y;
		filter_tmp[ACC_FILTER].z += filter_buf[i][ACC_FILTER].z;
		filter_tmp[GYRO_FILTER].x += filter_buf[i][GYRO_FILTER].x;
		filter_tmp[GYRO_FILTER].y += filter_buf[i][GYRO_FILTER].y;
		filter_tmp[GYRO_FILTER].z += filter_buf[i][GYRO_FILTER].z;
	}
	if(filter_buf_full)
	{
		mpu_handled_data.acc.x = filter_tmp[ACC_FILTER].x / SLIDING_FILTER_CNT_MAX;
		mpu_handled_data.acc.y = filter_tmp[ACC_FILTER].y / SLIDING_FILTER_CNT_MAX;
		mpu_handled_data.acc.z = filter_tmp[ACC_FILTER].z / SLIDING_FILTER_CNT_MAX;
		mpu_handled_data.gyro.x = filter_tmp[GYRO_FILTER].x / SLIDING_FILTER_CNT_MAX;
		mpu_handled_data.gyro.y = filter_tmp[GYRO_FILTER].y / SLIDING_FILTER_CNT_MAX;
		mpu_handled_data.gyro.z = filter_tmp[GYRO_FILTER].z / SLIDING_FILTER_CNT_MAX;
	}
	else
	{
		mpu_handled_data.acc.x = filter_tmp[ACC_FILTER].x / filter_cnt;
		mpu_handled_data.acc.y = filter_tmp[ACC_FILTER].y / filter_cnt;
		mpu_handled_data.acc.z = filter_tmp[ACC_FILTER].z / filter_cnt;
		mpu_handled_data.gyro.x = filter_tmp[GYRO_FILTER].x / filter_cnt;
		mpu_handled_data.gyro.y = filter_tmp[GYRO_FILTER].y / filter_cnt;
		mpu_handled_data.gyro.z = filter_tmp[GYRO_FILTER].z / filter_cnt;
	}

最后对数据进行转换:

#define TO_ANGLE 	0.061037	//陀螺仪数据转换为角度制角速度的转换系数
#define ANG_TO_RAD	0.01745329	//角度制转为弧度制
imu->acc.x = mpu_handled_data.acc.x;
imu->acc.y = mpu_handled_data.acc.y;
imu->acc.z = mpu_handled_data.acc.z;
imu->gyro.x = mpu_handled_data.gyro.x * TO_ANGLE * ANG_TO_RAD;
imu->gyro.y = mpu_handled_data.gyro.y * TO_ANGLE * ANG_TO_RAD;
imu->gyro.z = mpu_handled_data.gyro.z * TO_ANGLE * ANG_TO_RAD;
imu->temperature = mpu_handled_data.temperature;
imu->mag.x = mag_handled_data.y;
imu->mag.y = mag_handled_data.x;
imu->mag.z = -mag_handled_data.z;

这里对磁力计的数据进行了坐标转换,是因为磁力计的坐标轴与加速度计与陀螺仪的坐标轴不一致。
图7
在这里插入图片描述
在后面的文章中我会继续介绍IMU的姿态解算。

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Simulink是一款广泛用于模拟和设计各种系统的工具,包括控制系统、通信系统、数字信号处理系统等等。在航空航天领域中,Simulink也被广泛应用于飞行控制系统的开发和仿真。下面,我们以基于Simulink的飞控系统开发为例,来详细介绍Simulink仿真实例。 1. 飞控系统的建模 首先,我们需要对飞控系统进行建模,包括飞行器的动力学和控制系统的设计。这里我们以四旋翼飞行器为例,其动力学模型可以用以下公式表示: $$ \begin{cases} \ddot{x} = \frac{1}{m}(cos\phi sin\theta cos\psi + sin\phi sin\psi)\sum_{i=1}^4F_i \\ \ddot{y} = \frac{1}{m}(cos\phi sin\theta sin\psi - sin\phi cos\psi)\sum_{i=1}^4F_i \\ \ddot{z} = \frac{1}{m}(cos\phi cos\theta)\sum_{i=1}^4F_i - g \\ \end{cases} $$ 其中,$m$为飞行器的质量,$g$为重力加速度,$F_i$为四个电机产生的推力。控制系统的设计包括姿态控制和高度控制,这里我们采用PID控制器进行控制。具体的模型建立可以通过Simulink中的模块进行搭建。 2. 仿真参数设置 在模型建立好之后,我们需要对仿真进行参数设置。主要包括仿真时间、仿真步长、控制器参数等。在飞控系统中,仿真时间一般较长,因为需要验证系统的稳定性和鲁棒性。仿真步长需要根据系统的动态特性进行设置,一般较小的仿真步长可以提高仿真精度。控制器参数需要根据实际应用进行调整,以达到较好的控制效果。 3. 仿真结果分析 在仿真运行完成后,我们需要对仿真结果进行分析。主要包括飞行器的姿态和高度响应,控制器的响应曲线等。通过对仿真结果的分析,可以评估系统的性能和优化控制器参数。 以上就是基于Simulink的飞控系统开发的仿真实例,希望对您有所帮助。 ### 回答2: 基于Simulink的飞控系统开发是一种基于仿真的方法,用于设计和开发飞机控制系统。Simulink是一种用于建模、仿真和分析动态系统的工具,可以帮助工程师快速开发和测试飞行控制算法。 Simulink仿真实例中,首先需要建立一个飞机飞行动力学模型。这个模型包括飞机的结构、飞行姿态、控制系统以及环境因素等等。模型可以通过连接各个组件来描述飞机的行为。接下来,我们需要定义飞机的控制策略,比如俯仰、横滚和偏航等控制动作。这些控制策略可以通过PID控制器或其他控制算法来实现。 在进行仿真之前,我们需要设置模拟的时间范围和仿真步长。然后,通过在仿真回路中引入输入信号,如风速、龙卷风等,来模拟不同的飞行情况。在仿真过程中,Simulink会实时运行飞行控制系统,并输出飞机的响应结果。 通过仿真,我们可以评估飞机在不同飞行条件下的性能。我们可以分析飞机的稳定性、灵敏度和鲁棒性等指标。如果需要对飞机的控制策略进行优化,我们可以通过调整控制参数进行多次仿真,以找到最佳的控制策略。 此外,Simulink还提供了一些可视化工具,如飞机响应曲线、信号功率谱和频谱分析等。这些工具可以帮助工程师更好地理解飞机的响应特性,并提供定量的性能评估。 总之,基于Simulink的飞控系统开发是一种高效、灵活且可靠的方法。通过仿真实例,工程师可以更好地理解飞机的行为,并优化飞机的控制策略。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值