IMU姿态传感器

前言:主包在网上查阅了很多资料,发现几乎没有系统关于姿态传感器(陀螺仪)的学习资料,那就自己做一期吧!本人是基于逐飞的开源库学习的,这种封装好的库很多东西可以直接拿来使用,芯片是AI8051u,姿态传感器型号是imu660ra。

姿态传感器浅析

IMU660RA是一款九轴惯性测量单元,里面集成了陀螺仪和加速度计,主要用于姿态估计、角速度检测等。

而我们主要使用Z 轴角速度,也就是绕垂直方向的旋转速度,来帮助判断小车当前转弯速度,从而调整左右轮的速度,实现更准确的转向控制。Z轴角速度表示小车绕垂直轴(如图放置,就是几何中的z轴)的转动快慢,正值表示逆时针,负值表示顺时针

零偏(零漂)

本人编写运行了以下的基础读取代码

#include "zf_common_headfile.h"
#define PIT                     (TIM0_PIT )           
void pit_hanlder (void);       
void main(){
    clock_init(SYSTEM_CLOCK_40M);
	debug_init();	
	if(imu660ra_init()){            //姿态传感器初始化
		while(1){                   //失败进入死循环
        }
	}
	tim0_irq_handler = pit_hanlder; //周期中断回调函数
	pit_ms_init(PIT, 100);          //初始化中断为100ms
	while(1)                        
    {
    }	
}
void pit_hanlder (void)
{
    imu660ra_get_acc();             //获取x、y、z方向加速度数值                                      
    imu660ra_get_gyro();            //获取x、y、z方向角速度数值                                           
	printf("
IMU660RA acc data: x=%5d, y=%5d, z=%5d
", imu660ra_acc_x, imu660ra_acc_y, imu660ra_acc_z);                      //逐飞库函数已自动更新这些值
    printf("
IMU660RA gyro data:  x=%5d, y=%5d, z=%5d
", imu660ra_gyro_x, imu660ra_gyro_y, imu660ra_gyro_z);
}

读取到的数值如下(主要看gyro data角速度里的z轴角速度)。奇怪了,我的车子就放在桌面上没有动,怎么读取到的数值还自己震动呢?难道它连地壳震动都能检测到?

[13:59:06.549]接收←
                    IMU660RA acc data: x=  102, y=   63, z= 4054
                    IMU660RA gyro data:  x=    0, y=    0, z=   -1                    
[13:59:06.644]接收←
                    IMU660RA acc data: x=  108, y=   58, z= 4058                    
                    IMU660RA gyro data:  x=   -1, y=   -2, z=    1                    
[13:59:06.756]接收←
                    IMU660RA acc data: x=  107, y=   63, z= 4065                    
                    IMU660RA gyro data:  x=   -1, y=    2, z=    0                    
[13:59:06.851]接收←
                    IMU660RA acc data: x=  103, y=   61, z= 4059                    
                    IMU660RA gyro data:  x=    1, y=    0, z=   -2                    
[13:59:06.946]接收←
                    IMU660RA acc data: x=  103, y=   61, z= 4061                    
                    IMU660RA gyro data:  x=    2, y=   -1, z=    0                   
[13:59:07.057]接收←
                    IMU660RA acc data: x=  105, y=   57, z= 4053                    
                    IMU660RA gyro data:  x=   -1, y=    2, z=   -2                    
[13:59:07.153]接收←
                    IMU660RA acc data: x=  102, y=   64, z= 4058                    
                    IMU660RA gyro data:  x=    1, y=   -1, z=   -2                    
[13:59:07.248]接收←
                    IMU660RA acc data: x=  100, y=   58, z= 4061                    
                    IMU660RA gyro data:  x=   -1, y=    1, z=    0

这是因为IMU 是一种精密传感器,它的芯片和电路会受以下因素影响:电源不稳定(比如电压波动),温度变化(芯片加热也会漂),制造误差(比如 ADC 偏移),板子轻微震动或噪声,PCB 上的电磁干扰

一般都是 ±1~3 的范围,属于非常小的零漂,非常正常!

单位转换

然后我们看逐飞的源代码

前面两个函数都只是从 IMU 的寄存器里直接取出值,并没有除以转换系数,所以单位是原始 ADC 值。 再看后面的注释,所以我们要去查看IMU660RA_GYRO_SAMPLE_DEFAULT宏的值,从而确定逐飞库寄存器初始化时到底设置了哪个量程,最后进行单位转化

去.h文件中查看,所以寄存器值为0x00,除以 16.4 得到单位 °/s

编写角度环

由上文得,姿态传感器存在零偏

我们可以做一次静态校准,初始化后采样200次,计算z轴的平均值,作为基准零点。

float gyro_z_offset = 0;        

void imu660ra_calibrate()
{
    int sum = 0;
    for (int i = 0; i < 200; i++)  
    {
        imu660ra_get_gyro();
        sum += imu660ra_gyro_z;
        system_delay_ms(5);                     //系统延时5ms
    }
    gyro_z_offset = sum / 200.0f;
}

然后每次角度值读取后减掉偏差

float imu660ra_get_gyro_z_dps()
{
    imu660ra_get_gyro();
    return (imu660ra_gyro_z - gyro_z_offset) / 16.4f;
}

(有没有人想问这个16.4f是什么呀?16.4后面的f是用来明确表示这是一个单精度浮点数(float类型)的后缀,不加f在C语言中,编译器会默认把它当作双精度浮点数来处理。ai8051是32位单片机,在32位MCU上,double(64位)需要两条指令存储,而float在32位系统上是原子操作,所以我们使用float性能更高且保证安全)

角速度积分成角度,我们可以设置定时器中断每隔 10ms 调用 update_yaw(),yaw代表从开始到现在累计转了多少度。

float yaw = 0;      // 当前角度
float dt = 0.01f;   // 采样间隔10ms

void update_yaw()
{
    float gz = imu660ra_get_gyro_z_dps();  // 角速度 °/s
    yaw += gz * dt;                        //角度增量=角速度*时间间隔
}

 简单角度环控制结构

float target_angle = 90;  // 目标转角
float kp = 1.0f;          // 比例系数,自己调
float turn_pwm = 0;

void update_turn_control()
{
    float error = target_angle - yaw;
    turn_pwm = kp * error;

    // 限幅
    if (turn_pwm > 100) turn_pwm = 100;
    if (turn_pwm < -100) turn_pwm = -100;

    set_motor_turn(turn_pwm);  
}

 编写角速度环

角度环会产生 PWM 控制量,但容易震荡或不平滑,特别是目标角度快达到时容易来回抖动。所以还要加上角速度环(速度控制)用来限制最大旋转速度,更平稳。

控制流程:角度环输出期望角速度;

角速度环再将目标角速度与实际角速度比较,输出最终 PWM。

float yaw_angle = 0;
float target_angle = 90.0f;
float target_angular_speed = 0;
float actual_angular_speed = imu_gyro_z; 
float pwm_output = 0;

// 参数(你需要调试)
float kp_angle = 2.0f;
float kp_speed = 1.0f;

void turn_control_loop() {
    // 位置环:角度差 -> 期望角速度
    float angle_error = target_angle - yaw_angle;
    target_angular_speed = kp_angle * angle_error;

    // 限制最大角速度
    if (target_angular_speed > 150) target_angular_speed = 150;
    if (target_angular_speed < -150) target_angular_speed = -150;

    // 速度环:角速度差 -> 最终 PWM
    float speed_error = target_angular_speed - actual_angular_speed;
    pwm_output = kp_speed * speed_error;

    // 限幅
    if (pwm_output > 100) pwm_output = 100;
    if (pwm_output < -100) pwm_output = -100;

    motor_set(pwm_output, -pwm_output); // 左右轮反转
}

这样就实现了一个简单的角度控制模块啦,好像也还行,就是调参是根据角度的,更抽象一点。

主包实操

如你们所知,主包是电机坏了,所以最近才出这些文章。那本人的实操也没法跑小车,主包写了一个简单的代码,每个100ms输出当前角度(从开始到现在旋转的角度)

#include "zf_common_headfile.h"

#define PIT                         (TIM0_PIT ) 

void pit_hanlder (void);

float gyro_z_offset = 0;
float yaw = 0; 
float dt = 0.1f;

void imu660ra_calibrate()
{
    int sum = 0,i;
    for (i = 0; i < 200; i++)  
    {
        imu660ra_get_gyro();
        sum += imu660ra_gyro_z;
        system_delay_ms(5);
    }
    gyro_z_offset = sum / 200.0f;
}
float imu660ra_get_gyro_z_dps()
{
    imu660ra_get_gyro();
    return (imu660ra_gyro_z - gyro_z_offset) / 16.4f;
}
void update_yaw()
{
    float gz = imu660ra_get_gyro_z_dps();  
    yaw += gz * dt;  
}

void main(){
    clock_init(SYSTEM_CLOCK_40M);
	debug_init();	
	
	if(imu660ra_init()){
    	while(1)
        {
		}
	}
	imu660ra_calibrate();
	tim0_irq_handler = pit_hanlder;
	pit_ms_init(PIT, 100);
		
	while(1)
    {
				
    }	
}
void pit_hanlder (void)
{
    update_yaw();
	printf("%.2f
",yaw);
}

然后将小车慢慢旋转了180°(人为感觉,可能有偏差哈),输出如下

[16:11:48.521]接收←imu660ra_read_register = 0xFF
                    imu660ra_read_register = 0x24                    
[16:11:49.689]接收←-0.18                    
[16:11:49.784]接收←-0.23                    
[16:11:49.879]接收←-0.25                    
[16:11:49.991]接收←-0.12                    
[16:11:50.087]接收←1.66                    
[16:11:50.183]接收←8.44                    
[16:11:50.278]接收←20.15                    
[16:11:50.390]接收←31.63
……                               //太多了,省略了一些  
[16:11:51.885]接收←176.66                    
[16:11:51.980]接收←176.24                    
[16:11:52.075]接收←176.57                    
[16:11:52.187]接收←175.74

结果还算是不错的吧——旋转半周,角度输出大概为175。好了,大功告小成~

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容