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