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