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