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