+ ]+ V( r' A' G% C# b! E, C2 L# d' \9 F( z$ q0 `
( f1 o0 t- G- ^! q) V5 w* {; s0 k8 R2 F* K
! [! f" j3 D2 g( ]3 F0 k背景
/ P$ P% H$ g, O 本文用于记录平衡自行车的制作过程,及制作中遇到的问题;总体方案如下:采用STM32F103C8T6作为主控单元、MPU6050作为位姿采集单元、无刷电机带动动量轮调节小车平衡、1S锂电池配合5V和12V升压模块作为电源、蓝牙模块用于和微信小程序进行无线遥控及PID调试、舵机用于控制行驶方向和支撑小车站立。
8 F$ o5 z# t, x+ b1 l" g( O3 j2 e! y+ l- q
MPU-6050简介' e/ L" N. W2 x: g, v) X$ I7 E
MPU-6050集成了 3 轴陀螺仪、3 轴加速度计及温度传感器,并且预留一个IIC 接口,可用于连接外部磁力传感器,并利用自带的数字运动处理器(DMP: Digital Motion Processor)硬件加速引擎,通过主 IIC 接口,向应用端输出完整的 9 轴融合演算数据。& s1 |! }& { A/ @7 @* g% d2 Z
3 Q$ |" Q9 A: a2 y MPU-6050 对陀螺仪和加速度计分别用了三个16 位的ADC(0~65535),将其测量的模拟量转化为可输出的数字量。且传感器的测量范围都是可以根据需求设定的,陀螺仪可测范围为±250,±500,±1000,±2000°/秒(dps),加速度计可测范围为±2,±4,±8,±16g。+ h4 @+ g2 d1 x; J3 _. R$ r$ d7 H0 g
# r1 n- M, c# _
' A4 ^8 ^% O8 u- ^2 g1 e
$ ?( D3 h2 d ?/ {
4 N$ ?! R& }/ p. L
MPU-6050姿态获取与处理(惭愧,我也一知半解)% Q9 _1 n* L) l& S/ \8 d+ @0 k$ G5 K
理论上只需要对3轴陀螺仪的角度进行积分,就可以得到MPU-6050的姿态数据。实际上由于陀螺仪受噪声的影响,只对陀螺仪积分并不能得到完全准确的姿态,所以需要用加速度计进行辅助矫正,常用的方法有三种:互补滤波、卡尔曼滤波、硬件DMP解算四元数。
$ c3 r+ } D. Q4 W6 z
9 d1 q: J8 p: ^7 |
0 }$ G4 C8 _: I: B4 b0 }
互补滤波、卡尔曼滤波计算位姿示例
+ s% a9 `; H# L8 x9 J
( \) } ?) D( L+ C
. H( [/ P2 V" C9 u" G9 v8 F0 }
5 ~7 k' M; U' p, o互补滤波、卡尔曼滤波计算位姿示例0 q& }+ ^: U$ G8 `( H7 g! \! h2 i
9 _# x X3 a! ?3 S7 L 除了使用加速度计计算的噪声较大,卡尔曼滤波,一阶互补滤波,二阶互补滤波看着差不多,O(∩_∩)O哈哈~(四元数计算的示例就不演示了,有兴趣的可以自己研究一下。)
7 u8 [0 n) s* O8 c! E1 P4 }% A2 D 1)一阶互补滤波:因为加速度计有高频噪声,陀螺仪有低频噪声,需要互补滤波融合得到较可靠的角度值。* P. c$ e2 E p2 e& p
2)卡尔曼滤波:利用线性系统状态方程,通过系统输入输出观测数据,对系统状态进行最优估计的算法。由于观测数据中包括系统中的噪声和干扰的影响,所以最优估计也可看作是滤波过程(来源于百度词条,我也看不懂这说的啥)。! d2 b6 N, j. W6 U/ p
3)硬件DMP解算四元数:DMP将原始数据直接转换成四元数输出,运用欧拉角转换算法,从而得到Yaw、Roll和Pitch(使用四元数计算位姿,好像会将初始化的位姿设定为零位)。
2 b+ h1 r$ C7 S9 q. S) Y) g! a% s2 ~' A" X) J# t
$ D8 c! U- s5 `+ Q% w, w. N) `/ [" f# H) K9 q7 Q
+ \+ N0 i3 c0 h- m
一、一阶互补滤波算法
6 b2 e2 w# J5 Y MPU-6050 的加速度计和陀螺仪各有优缺点,三轴的加速度值没有累积误差,通过简单的计算即可得到倾角,但是它包含的噪声太多(因为待测物运动时会产生加速度,电机运行时振动会产生加速度等),不能直接使用;陀螺仪对外界振动影响小,精度高,通过对角速度积分可以得到倾角,但是会产生累积误差。所以不能单独使用MPU-6050的加速度计或陀螺仪来得到倾角,需要二者进行互补。一阶互补算法的思想就是给加速度和陀螺仪不同的权值,把它们结合到一起进行修正,通过加速度和角速度就可以计算 Pitch 和 Roll 角(单靠 MPU6050 无法准确得到 Yaw 角,需要和地磁传感器结合使用)。
) \, k' ^% @8 X& ?6 N: M3 l( A0 |7 Y; j
! t- k) b- l4 A/ c一阶互补算法如下:
0 t! L L3 R+ a1 W) k: |- //一阶互补滤波
" f# q! o! c) ^ - float K1 =0.1; // 对加速度计取值的权重+ S) {7 N* F2 _8 f0 R
- float FirstOrder_Pitch;
! Y0 h6 i* P( n0 @# @
% m& }. c) n# j7 j8 }* a, w- float FirstOrder(float newAngle, float newRate, float dt)//采集后计算的角度和角加速度
" O* [% J5 p. O7 M( o! j5 c- W8 E, n - {' K! X9 {# k k- N% q }0 {! |$ ?
- FirstOrder_Pitch = K1 * newAngle + (1-K1) * (angle + newRate * dt);( l6 R, Z9 I+ C( G4 b: e
- return FirstOrder_Pitch;
! d; M' v* J1 @7 j/ G" Y+ } - }
复制代码
' I. {' U# ~+ I ~, [8 F0 Z( ]二阶互补算法如下:
5 h& B9 y' x# S3 q- //二阶互补滤波
2 ~! Z, ]/ O6 y/ {2 ] - float K2 =0.2; // 对加速度计取值的权重3 B5 ~. L9 c% `' f% W7 R
- float x1,x2,y1;" k1 r9 G R" m* y j p2 X
- float SecondOrder_Pitch;3 D3 U* X5 y$ p9 \
& j9 `7 D$ S6 M& H. ]- float SecondOrder(float newAngle, float newRate, float dt)//采集后计算的角度和角加速度
, R# e+ C* W' Y% W; c - {
* r9 y+ B# N" E - x1=(newAngle-SecondOrder_Pitch)*(1-K2)*(1-K2);% ^( o& h) @" R6 m# A/ o; Z
- y1=y1+x1*dt;
( h4 W4 X3 |* e4 k: h. ~ - x2=y1+2*(1-K2)*(newAngle-SecondOrder_Pitch)+newRate;1 G- l0 E% R: A4 W' x. G
- SecondOrder_Pitch=SecondOrder_Pitch+ x2*dt;( U8 n* C& X* A
- return SecondOrder_Pitch;
- M! @5 ?3 q7 a" h: F2 m2 c - }
复制代码 ) o! S/ @" L) n$ |8 q4 j* [$ ^
二、卡尔曼滤波 [5 U4 ` l- J2 u& n
- /* Kalman filter variables */
( {+ d& ~* A X i z; G - float Q_angle = 0.001; // Process noise variance for the accelerometer {2 E8 I- U2 N$ t; E0 s& s
- float Q_bias = 0.003; // Process noise variance for the gyro bias
: ^. f' {; h3 d6 k0 ?/ ~ - float R_measure = 0.03; // Measurement noise variance - this is actually the variance of the measurement noise
/ N5 [6 p1 ^6 b' @0 L% ?0 j - " a+ y: G! o2 W+ H; P5 G6 Q3 [
- float angle = 0.0; // The angle calculated by the Kalman filter - part of the 2x1 state vector
, [# v+ E; D' M/ B - float bias = 0.0; // The gyro bias calculated by the Kalman filter - part of the 2x1 state vector& y+ s+ l k/ F4 I( @# ]
- float rate; // Unbiased rate calculated from the rate and the calculated bias - you have to call getAngle to update the rate! d- p2 S# I) W5 t# W: z
- H, B8 Z$ A! a' Z7 O, T- float P[2][2]={0.0,0.0,0.0,0.0}; // Error covariance matrix - This is a 2x2 matrix
$ n& w1 r, Q9 c/ u3 u1 F
?2 n- m6 q, \% w, ~# n4 }- & y; V# [" l8 O, r6 q8 Z
- // The angle should be in degrees and the rate should be in degrees per second and the delta time in seconds# |( |; n0 M! Z5 ^6 b# G9 Y5 k/ M
- float Kalman_filter(float newAngle, float newRate, float dt) {4 h; }4 s: { h: _% j
- // KasBot V2 - Kalman filter module - http://www.x-firm.com/?page_id=145
* Z" t( @ A3 a: \ - // Modified by Kristian Lauszus
0 W. ^3 l6 Q, x4 Y - // See my blog post for more information: http://blog.tkjelectronics.dk/2012/09/a-practical-approach-to-kalman-filter-and-how-to-implement-it
5 e3 P, i9 n) h8 a - ! T2 e2 v/ l4 d& H: [
- // Discrete Kalman filter time update equations - Time Update ("Predict")
" r$ e2 l1 g4 a8 s3 V - // Update xhat - Project the state ahead
( H. f3 Z9 b4 V3 E - /* Step 1 */' R9 A% @) l1 `* F& L. l
- rate = newRate - bias;1 A! i8 G* W+ I( h
- angle += dt * rate;
; i0 E8 \ P1 z
W3 }/ \7 ]: \! w/ g- // Update estimation error covariance - Project the error covariance ahead5 ~( k7 B" O+ A+ m( ~8 S
- /* Step 2 */# x6 n! S2 @8 g4 G' k
- P[0][0] += dt * (dt*P[1][1] - P[0][1] - P[1][0] + Q_angle);4 [, b2 d/ L8 a( n
- P[0][1] -= dt * P[1][1];7 M' Z+ a( _9 b
- P[1][0] -= dt * P[1][1];' G! E" G* Y7 ^( U; j: R6 L
- P[1][1] += Q_bias * dt;
( W0 @# J; j6 O6 l - 7 S* B, D" o+ n4 ], m$ ?
- // Discrete Kalman filter measurement update equations - Measurement Update ("Correct")
* L" q- L. i% k/ G - // Calculate Kalman gain - Compute the Kalman gain
) S) m4 _# J3 E - /* Step 4 */) `$ ?" Q3 [7 H6 l
- float S = P[0][0] + R_measure; // Estimate error
$ K! B6 p; W3 t/ {: w - /* Step 5 */
" ?7 d, y( x, f# O1 H0 G - float K[2]; // Kalman gain - This is a 2x1 vector
$ O* u9 {3 u: K' n5 @ - K[0] = P[0][0] / S;
2 t% K& B6 C! k @! i - K[1] = P[1][0] / S;1 Y Y7 y. r0 ]1 J8 }- d. O! b
! N' n: w2 m, s" p5 i. n- // Calculate angle and bias - Update estimate with measurement zk (newAngle)
7 y& F/ `* X) z/ b* V - /* Step 3 */
. i8 E" D) S C2 E. g - float y = newAngle - angle; // Angle difference# T- i8 B0 J& G) ~5 w* T O
- /* Step 6 */
n9 l6 h8 A7 D9 e' I - angle += K[0] * y;" z+ O! y. S; ], y
- bias += K[1] * y;
9 }0 l- P5 g' u% n. i
P9 |0 S3 c# \( t y- // Calculate estimation error covariance - Update the error covariance) ^1 F4 Z/ w6 d
- /* Step 7 */; P8 Q# y' V* w% C4 `, `8 \
- float P00_temp = P[0][0];! |$ i( w3 B) O! V3 J9 H/ L$ }# u8 N
- float P01_temp = P[0][1];
" K: x$ E1 i4 U8 a8 \
& @7 A1 _) y( I$ H9 [) y% L+ l$ L- P[0][0] -= K[0] * P00_temp;$ j9 c* \ R+ V3 R( R
- P[0][1] -= K[0] * P01_temp;4 k! B& h0 ?% A
- P[1][0] -= K[1] * P00_temp;
% y; Y0 g& \: {. f - P[1][1] -= K[1] * P01_temp;
C' _8 n/ s# K% B1 A# R - : {/ I+ K x1 A* S q9 i
- return angle;' k8 J$ b1 c1 L9 c
- };
复制代码 : G k1 W8 Z8 P; C U
三、四元数法2 R9 ~! p# k: [
4 c0 k* N/ O" D+ T
MPU-6050 自带了数字运动处理器,即 DMP,并且InvenSense公司 提供了一个 MPU-6050 的嵌入式运动驱动库,结合 MPU-6050 的 DMP,可以将我们的原始数据直接转换成四元数输出,而得到四元数之后,可以很方便的计算出欧拉角,从而得到 Yaw、Roll 和Pitch。
9 z: m: L7 T1 K8 _# c+ j% q2 u, Y
使用内置的 DMP,大大简化了代码设计,且 MCU 不用进行姿态解算过程,大大降低了 MCU 的负担,从而有更多的时间去处理其他事件,提高系统实时性。
|6 `7 k. m9 m1 x, u) X3 [- void Read_DMP(float *Pitch,float *Roll,float *Yaw)
+ {$ j# e4 ~& }3 i \ - {% \; }" ]7 J5 c/ O1 d5 h2 g1 c2 l
- unsigned long sensor_timestamp;" X0 S- y) Y0 B" Q
- unsigned char more;( I9 }' R' b* y: o
- long quat[4];
: n; H9 n" H4 L8 B o$ V - ' X- k+ b2 P$ j
- dmp_read_fifo(gyro, accel, quat, &sensor_timestamp, &sensors, &more);
1 N* W6 |: ~% j* ]) l( e - if (sensors & INV_WXYZ_QUAT )* S# D% e; [, @ c" H% I# f
- {
: j1 Z# P5 [1 |0 V+ z - q0=quat[0] / q30;
+ X3 E& K7 c ~' L) {* \ - q1=quat[1] / q30;( i1 ^4 G4 Q( g) ]6 a8 ]
- q2=quat[2] / q30;+ r9 C7 w( F& W9 E( k! [3 Z
- q3=quat[3] / q30;4 T+ W% ~, ^, |' P
- *Pitch = asin(-2 * q1 * q3 + 2 * q0* q2)* 57.3;% u+ N2 `% B( u6 R3 k+ S1 @9 ?
- *Roll = atan2(2 * q2 * q3 + 2 * q0 * q1, -2 * q1 * q1 - 2 * q2* q2 + 1)* 57.3; // roll
" e" F+ S9 f: L& b1 R - *Yaw = atan2(2*(q1*q2 + q0*q3),q0*q0+q1*q1-q2*q2-q3*q3) * 57.3; //yaw
4 ~, \/ M+ }# W5 l4 A" N - }, w- R9 h4 r/ `
- / J; Q' Q) f+ {9 b \( F
- }
复制代码
+ g4 o! E/ r/ R$ h8 l( H9 }) o 以下是我测试一阶互补滤波和卡尔曼滤波项目的搭建过程,老鸟请忽略。
6 k" v5 A% Q$ @# E+ |3 b e7 a7 B4 i J, v; h( A% h$ O
固件开发采用STM32CubeIDE开发,对于我这种菜鸡来说还是很友好的。
4 G+ e5 s, A. ]* J. n2 O# c& k2 N7 V2 X0 j& l I
我使用的是STM32CubeIDE1.9.0,打开软件后,依次点击 File-->New-->Stm32 Project,弹出如下界面,输入MCU型号-->选择封装形式-->Next
8 L: F3 n$ d& `3 D3 D1 H8 Q
, o% v( X9 l+ T/ }) P& t$ X
. Y* W5 r! x& y* o4 E Z& v: Q
7 X- b/ [# S7 f: G输入工程名字,点击Next. S D2 r# C/ m8 k' |, t8 Z
% g7 L9 k- S& S# T3 _5 h/ }
?; r( a: p. {9 M ?& j
1 @ b, [6 R& w4 M* |7 ~设置完成后,点击Finish$ }8 c' k" Z1 c. w: E% j
5 d3 A- \! S" @1 [, g
0 u- c- |5 y# F' ~4 q+ ~7 h0 u% i
% c# d7 A5 I# E$ G" c' u
进入MCU配置界面& t3 A) S* ]& c8 H
* G. z0 ~+ e, _ u- A
: O' ]& g" v* x& I' y7 k0 _( Y" q s+ N, z6 D
设置I2C与MPU-6050通讯。! z+ O \: G' l( } O
# J' G6 d# }( G: ?9 T, `) O3 B
4 |$ Q* I$ @( x- C' Y) m, Q
7 A4 a& ]. C- m& a设置串口与上位机通讯,实时显示MPU-6050位姿。8 ^9 k8 k0 U7 f. k8 \0 W
) v) u7 `: F1 l Q0 W' @: H
; q# y: @& c6 I$ `! N% I5 _& B* |- ~$ J& E0 `; X' q P
设置系统时钟及调试模式。
) e+ E9 `) }% O b6 b* v& w4 l2 V; m6 Y3 H" J# O: ?6 ~
/ ^; w3 \. q. s6 e: I% b0 f/ w- V" x f( p" h5 |+ _+ W
设置时钟源。# S6 |, P" N2 b& o
# k4 P3 f$ g# R0 S4 e: i; J3 G! N
3 r: Y3 i2 c: U* w3 D$ i$ U8 e @- u* M3 F* ?. ~% G
全部设置好后,芯片引脚的分配情况。
4 i; F9 Y; M- Y, s8 n- M0 c7 ]- _7 L* q
9 b" j f) U5 l/ r7 @. I ~/ Q6 }: x
9 n# [ {8 v, l$ f$ S在时钟配置里设置时钟最大频率。/ s. j0 n' U. B$ m5 _
- F; T/ d4 g. Q, X/ I: ?4 m- R
4 y+ e4 d( Z, U ^! k( o6 ?- j# n- P+ K" V6 w
在工程管理——>代码生成器中进行设置
4 ~( c0 E$ T: [! q6 Z/ Q
% H1 S, N6 e& U' i
R) p) ^8 N5 M* s% a# }/ a# B
) p$ ]: s) n0 T! D$ }2 Y- S( U3 Q点击此处,生成代码
! L5 `& a7 K$ \) J6 [* |& v# }' [
* d6 K6 z% S4 |* {2 O! P: z4 v
. J1 O& u; V( J8 o4 _
~1 W" {6 `0 E, E! b
在工程文件夹中创建iCode文件。
0 c D# [' q+ i9 }: @- c; ?2 u0 s. ?3 [ K0 {4 Z' U
3 Q/ ?, P" D0 i' C8 P' b6 N
& c+ L" m" v; Z9 u将MPU-6050、卡尔曼滤波库文件复制到iCode文件中。
& J# X! j" p |8 q. w/ y. C ]" P' X0 u" X. ~8 i) O. \
8 h' A$ p9 l2 g7 Q' S% S7 h
8 d2 h K I9 [# o- A* ^点击Debug,刚刚添加的驱动文件全部在工程文件中显示出来。
+ r; @( D# o' F2 g, e1 P; k: [; {: i0 p, ?# F: i! d9 D
9 V' C1 [% f; l, w
2 o) ~% @/ w& I$ Y6 n# W( Z2 ]1 Y8 Q) c
按照下列方式设置MPU-6050、卡尔曼滤波驱动文件的编译路径。! Z, i [) @+ x, j( v/ r+ u& [
% i) A2 b$ j; o1 ]' H5 F; p3 Y' A E
# w6 o3 U; `* t. O
! _4 C+ t8 f; [: k: k4 o
1 Y+ u" V* A0 i- L* g( }# u* y
5 v" [9 d* G) O. A5 L/ g1 Y6 v
然后再main.c文件中添加如下代码,用于重定义printf函数,用于串口输出;获取系统时间,用于滤波。
* ^1 Q# ]0 E8 T- [5 |- /* USER CODE END Header */
) K3 o* z2 ]% h- h6 E* B - /* Includes ------------------------------------------------------------------*/" f& V0 [8 y3 K* Z: ?/ H! ?) L
- #include "main.h", @5 c1 G( A7 t+ F2 r7 X
- #include "i2c.h"3 B/ [3 d0 M$ J( d* A
- #include "usart.h"
* Q) ^. R4 Y+ _/ q- O3 T - #include "gpio.h"
; ^2 |+ s: a/ `; H# m% v8 r - . X, E7 b: Q2 {) A
- /* Private includes ----------------------------------------------------------*/3 ~2 w# k- P! A/ e! v1 E5 N
- /* USER CODE BEGIN Includes */
8 N3 M! i( d0 r- T - #include "mpu6050.h"
5 t" f" r7 S) \* x) B' _) i - #include <stdio.h>, Y5 z# I- Y+ b2 t- r& ~% q
- /* USER CODE END Includes */% z5 q# v; F$ Q0 }8 s) D( R
0 V" m3 l! {7 Z/ T x" ]& f E- /* Private typedef -----------------------------------------------------------*/ O5 d( {) a: D- t, i& U( Z1 Y
- /* USER CODE BEGIN PTD */
" P- h7 s5 H4 y# ^4 g+ O
2 b$ f# S3 m( D; l- /* USER CODE END PTD */
4 f; I; r+ c* G - . S6 ]" c- ?2 t
- /* Private define ------------------------------------------------------------*/' K0 Q. Y2 y; i
- /* USER CODE BEGIN PD */3 O% Q2 H- {1 ~2 \: E' ]
- /* USER CODE END PD */
6 W* J' e0 H4 O+ ?3 C$ t1 P
{3 Z* v, O: N) e3 L6 d$ [- /* Private macro -------------------------------------------------------------*/
P' l; V4 V+ Y% X5 k: K - /* USER CODE BEGIN PM */
- D' O+ T) B& j* ^ - 7 y* y# Z( f+ s7 h# k4 x
- /* USER CODE END PM */8 S4 s) d5 w: E3 u% E
- 1 m4 G3 \1 C4 g' [9 u: I0 r+ Q5 \4 `
- /* Private variables ---------------------------------------------------------*/
. m$ |& o# D0 J2 t r; \4 h - ) z0 I! a+ c2 R; q, W7 f( W Q' s3 C
- /* USER CODE BEGIN PV */. E8 V$ |) R! h6 q. o
- 2 Q( p. N& l( ~+ A X4 O, R
- //重定义printf函数,用于串口输出
) T. I* F* V) \. [$ m) y& A - #ifdef __GNUC__, Q4 P! H9 E( B5 f% H" w8 M- u
- #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
+ n8 \) O) j. \' S2 A - #else7 c2 E& S4 J2 S' l3 D: ]; G
- #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
" o; }4 u: k: u* l; f m - #endif5 ]" ^ ^, a% [/ u; c V4 [3 @
- . T9 H# {$ B, @
- PUTCHAR_PROTOTYPE
! X9 Q' f* U- F# D5 `0 F - {
7 I; V" R. f: [6 T3 b( t - HAL_UART_Transmit(&huart1, (uint8_t*)&ch,1,HAL_MAX_DELAY);
; ^: U: F. d* S! K: H* s3 b - return ch;/ ^, q' B, K0 U1 g2 N
- }
/ S+ ^; C+ B, E& X0 E - /* ---------------------------------------------------------------------------*/% a# s0 _" W Z, e; _! L
: S9 O" J; t. M$ T& u- //获取系统时间,用于卡尔曼滤波
# P" @" ^ s- q7 C$ k( y - __STATIC_INLINE uint32_t GXT_SYSTICK_IsActiveCounterFlag(void)
) E( e/ ]) i, s. v7 @2 [: |. X7 v7 B - {
8 O% x! ^3 y9 d3 k/ ? - return ((SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk) == (SysTick_CTRL_COUNTFLAG_Msk));- u; ? q, U0 P
- }, F9 G$ m5 x- F3 F. T
- static uint32_t getCurrentMicros(void)
; j; Y/ R$ q* f. u - {
0 S- \1 |# t; o9 @ - /* Ensure COUNTFLAG is reset by reading SysTick control and status register */* m( ?; d% A5 |3 q' J: m' I
- GXT_SYSTICK_IsActiveCounterFlag();
( ]+ Y2 V1 M/ H5 z& v+ c1 a9 D - uint32_t m = HAL_GetTick();5 |" v2 g* ^3 P% Q
- const uint32_t tms = SysTick->LOAD + 1;1 }# I, D1 _% q3 H: C; g
- __IO uint32_t u = tms - SysTick->VAL;
$ v7 M$ S( U4 |$ q* b1 o - if (GXT_SYSTICK_IsActiveCounterFlag()) {: r# Q2 n3 `( L# }$ t) d- E
- m = HAL_GetTick();' A0 L/ E: q" {4 V" Z2 x4 X
- u = tms - SysTick->VAL;
2 d' f) n( ^3 `% H3 {7 E7 j) h - }
# k/ m3 x* h+ b - return (m * 1000 + (u * 1000) / tms);% h5 h1 n" i( K' [; J
- }/ Q& w3 _+ p) ^" K( B
- //获取系统时间,单位us
; U# C3 W# E5 e/ q O+ c+ ~' e - uint32_t micros(void)
/ b, w' k0 `! e4 G0 n8 U+ i - {
: V" b% o0 [* W; Q; Z1 e - return getCurrentMicros();* J2 H5 ]0 y( E4 r' i
- }! D. q0 f" ^; R4 w# r
- * g' b9 q% B- K5 E
- ! {% T7 D& B" P* Z$ \! g
- /* USER CODE END PV */
复制代码 1 `( `6 C! T9 ]( s) X4 S; `
使用printf()函数,还需进行如下设置:点击菜单栏 Project-->properties,弹出如下界面
6 o0 {% ^: ]9 q# r9 z+ _! u! ^# x
0 b/ v9 y, h2 _
* K* H! y1 {4 i3 n& S
在main()函数中添加MPU6050初始化代码和发送位姿数据代码
( I* |/ P% W) |* Z+ ?- int main(void)3 _, r4 o1 B/ M6 y6 @
- {( P# X! U) ]8 z P( Q& K' x
- /* USER CODE BEGIN 1 */3 J: f9 r) t# ~% G. G7 x& [7 `
- , f2 N2 I- A1 L+ U7 M6 |) A
- /* USER CODE END 1 */
: A+ L/ @8 S; u; a2 o' q3 f i - ) ^0 u' P/ }* R% i( X& o1 X$ u
- /* MCU Configuration--------------------------------------------------------*/
: D$ n6 d" T/ n2 B1 r - 4 Z- I4 F/ G+ T3 B( N1 ]
- /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
% Z6 ~! t) N1 O- h# D" T - HAL_Init();( |9 p7 d9 m( b& X
- - B2 t/ N0 a/ S6 D" p
- /* USER CODE BEGIN Init */
6 L& `# V0 ]* V0 n/ `
/ r8 C- l3 x; O5 S- /* USER CODE END Init */
4 Z6 K2 g- Q9 R; K - ) x+ O& T( S+ c0 W; }3 h
- /* Configure the system clock */, p0 z( f \' X& [, L1 M9 K' [
- SystemClock_Config();
# M1 A1 t" V1 x, S - 9 i5 M. \! x/ h4 j
- /* USER CODE BEGIN SysInit */9 K6 Z/ w! y+ P- R$ f
( X% O# ?# X) `! {- /* USER CODE END SysInit */$ C R# |9 L- ~4 p6 I" Z' M& b
- - y; F$ ~; r; Q) D0 i, R
- /* Initialize all configured peripherals */
7 d! \& n7 S7 {9 p+ ?) v1 L/ ~* L5 R - MX_GPIO_Init();
8 b- _8 p$ \+ `/ a% D - MX_I2C1_Init();8 D/ M1 r5 s4 G9 f! o; Y2 R' {, k
- MX_USART1_UART_Init();
. m& E+ L/ F# O7 i* U - /* USER CODE BEGIN 2 */' L0 R& N2 _& E/ M% C1 }
- / q& r9 R0 }4 `( G
- while (MPU_Init()){ //如果初始化失败,继续初始化- U! H* K/ R9 c7 t8 f
- printf("MPU_Init_Fail...");9 p6 a8 x+ M1 s
- }
- C$ y$ P7 I+ B2 Q, c - ! [& E: c' f7 c x
- /* USER CODE END 2 */
" w9 ~9 n: R! v4 F a4 F - ) e, W+ {/ `5 N- ~
- /* Infinite loop */; @" z# g$ O* i& C9 J, u, P
- /* USER CODE BEGIN WHILE */
0 \+ r7 x7 T! ]" V - while (1)
4 w, Y0 i: I% f* p* w, o - {' v. W* O* [1 Y( e
- GetAngle();2 h( Y2 d& O% F. P
- GetPitch();
/ z+ |( P9 r9 ]3 b% v1 h4 ^ [" ~" @ - HAL_Delay(10);- D' e" w, N% V4 |! R4 |. g0 Y. i
- /* USER CODE END WHILE */
- s8 U; }& x( d) x" a) B! `* F - 8 _/ i3 L e6 @
- /* USER CODE BEGIN 3 */" ]- t( z" i& j. }
- }' Z: t( j* j. t" r6 x0 A, d
- /* USER CODE END 3 */
' M5 P0 f# b! ]8 x - }
复制代码
& I' L+ C- k3 |9 ?+ M1 k4 f点击,下载程序
9 N. C; y- y+ u- w
3 `- w0 j6 ]0 F$ C# t3 Z2 f' Y
; n: [" H8 j* G" F8 @
9 g7 Q+ I* w# x程序烧录好后就会通过串口通讯向上位机发送位姿数据(加速度计、卡尔曼、一阶二阶互补滤波计算的位姿),上位机我用的是Arduino编译器来接受并显示数据。设置好波特率后即可显示数据。
" l% W" }' _) L* f+ {5 z( c5 _6 j9 ]9 ?* l% `4 o4 b# B+ f
) o3 a5 K) C6 I' d7 y& x0 D
# ^- d7 N( R Q2 C+ y4 r 作者:DIY攻城狮
4 u% }# H7 I- {* }/ K2 u) y5 h) D" r" E8 `; o, U
9 k1 ]$ r& [8 z
|
MPU-6050、卡尔曼滤波库在哪里呀