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