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