最近买了个数字罗盘模块,调通后发现很不错,非常灵敏,测试的时候精度在1°以内。连续测量模式下,最快测量、输出速率可达75hz,模块每次测量完毕并将数据更新至寄存器后,其DRDY引脚便产生一个低电平脉冲(可以配置一个外部中断捕获DRDY引脚的下降沿,并在中断服务程序中读取数据),在STM32中可以设置一个下降沿触发的外部中断,并在中断服务程序中调用角度数据读取函数。以下为操作该模块的主要步骤。 一、IIC协议相关操作(单片机作为主机控制时钟线)宏定义: - //这里用到了STM32的位带区操作,方便实现对一个位的操作: ^- Y* D' E* y( j
- //PB13配置为OD输出,同时外部给上拉电阻,这样既可输出信号给从机,也能% j+ B0 {% f' J. \% y
- //在PB13为漏极开路状态时接收从机的信号(STM32的IO配置为输出模式时,
+ F' W# ?* p! E5 V& R3 u - //IO口的电平也会不断地被捕获到输入寄存器中)
; A3 C5 X# T' u/ G' r - //PB14配置为推挽输出,PB15配置为浮空输入
$ v1 _, J- v! ^9 X8 D" \! A - #define R_SDA IPB13 // PB13输入寄存器
% v1 T0 U* \9 K; {' I - #define W_SDA OPB13 // PB13输出寄存器
! q7 k# _! O4 O+ S0 o- j7 i: V - #define W_SCL OPB14 // PB14输出寄存器4 r* s, |2 b: x
- #define R_DRDY IPB15 // PB15输入寄存器
复制代码- #define Xmsb 0 //X轴数字量的高8位; |. g4 Y: H3 c' c% }% G7 J
- #define Xlsb 1 //X轴数字量的低8位0 e6 T% f0 e' c: v$ G' \5 \
- #define Zmsb 2 //Z轴数字量的高8位
* e9 U* n7 D4 q, z - #define Zlsb 3 //Z轴数字量的低8位
: g0 c( |& p8 [ q: b - #define Ymsb 4 //Y轴数字量的高8位' U& d1 ?, f) g5 H1 [1 T q
- #define Ylsb 5 //Y轴数字量的低8位
复制代码 0 E6 x( e' K, c; B" l9 z
附位带宏定义: - #define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
7 |" g/ m' J: o( L! v - #define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08 ; \6 E r' `# G2 \/ A$ A1 ?- l
# S3 V. R9 U0 `% W5 |- k- #define BITBAND_Addr(Addr,num) ((volatile unsigned long *)(0x42000000+32*(Addr-0x40000000)+4*num))
4 @) l3 K# V2 G$ U% D - , t/ A- O* q" L0 w& M0 q
- #define IPB13 *BITBAND_Addr(GPIOB_IDR_Addr,13)9 \# u0 r+ {3 w" E7 d9 z& R
- #define OPB13 *BITBAND_Addr(GPIOB_ODR_Addr,13)
4 x, j0 R5 K: x4 H - #define OPB14 *BITBAND_Addr(GPIOB_ODR_Addr,14)+ _0 D2 e- }3 z
- #define IPB15 *BITBAND_Addr(GPIOB_IDR_Addr,15)
复制代码 " g& Y) S; C4 X) X
启动IIC传输: - void _iic_Start()
1 K6 C& x/ b3 |+ Q, e' u - {
# U2 ]. v3 ]& \ - W_SCL=1;
2 O$ Q6 Y( x6 s1 ?2 }( x - W_SDA=1;
/ @+ }9 h Y3 Y3 o8 _ - _delay();3 m+ I' c m+ h2 [4 V0 F1 t* V
- W_SDA=0; //SCL高时,拉低SDA,表示开始IIC传输,占用总线
6 ]+ b' K$ \) v! J- v - _delay();
7 [* e# e; p% c% q5 M" [ - W_SCL=0; //控制SCL- r- W, b4 s. M5 b4 ?3 w b
- _delay();3 O* H# X: E! [9 Z7 Y4 R3 h+ H- n
- }
复制代码
. S( P3 _0 x3 ~. Z l停止IIC传输: - void _iic_Stop()5 U; x& t1 ]$ _% |+ W: T' ^
- {9 ^' ^/ f# i4 z; x1 q. M6 S+ b
- W_SCL=1; //释放SCL(由于没有其他主器件,SCL无需开漏)# H: d! D0 K: m5 w" Y
- W_SDA=0;
& ]5 n1 F3 {: }) c& _ `9 Z' r4 U - _delay();& `% t$ l3 X& G1 o: r5 P# I
- W_SDA=1; //SCL为高时,拉高SDA表示结束ICC传输,释放总线
. Z" }, l; C1 o# l - }
复制代码
, y$ c3 E% E r9 r发送一个字节: - uint8_t _iic_SendByte(uint8_t dat) V) H; o5 x# v& l" U
- {2 a0 _7 P% E7 n7 `$ M% n: G, w* p
- uint8_t i;: m( i" R" }# Z0 E7 |1 i8 |
- for(i=0;i<8;i++)! u% N: i' U3 k) k% `
- {
7 k0 _1 o# u1 X/ i - _delay();# l& ~& h! |% w7 ?1 i7 }' v
- W_SDA=dat>>7; //SCL拉高之前写SDA
/ y @# S" n* f - dat=dat<<1;
! D2 n- x- |# l: E2 N! N& D - _delay();% P: k p0 j0 R4 o1 y3 y
- W_SCL=1; //拉高SCL,从器件开始读取SDA / o! K! R9 {6 K: q7 H2 p
- _delay();
- @3 F6 Q. f* S- P- s5 y% w - W_SCL=0; //重新拉低SCL! I1 S2 [6 p1 Q9 Z' p# B
- }
) ]8 w5 @" Y+ h) d5 L - W_SDA=1; //释放SDA
, H+ {* [. D: g - W_SCL=1; //拉高SCL,读取从器件应答信号 X! V6 y6 R1 n m; s
- // 等待应答 ) ^7 u. h5 H/ S9 |0 y2 p
- i=100;+ m1 E# n( K0 w/ J' T8 `, p
- while(i&&R_SDA) {i--;_delay();}4 J5 {: l# [/ ^* {5 \) ?: g! t) P) x# z
- if(i==0) //无应答2 t3 l: @/ `8 ~/ `' S
- {, u3 f$ f1 c$ K: d: B; \
- W_SCL=0; //重新拉低SCL8 x& s. r8 [) H# k) m) a
- return 0;6 p! j, Q: ]3 v! |
- }8 K( V% E$ Q' Y, [# T
- else { //有应答
) e: L2 l$ F- ]# a! O$ `$ z' B* U/ O - _delay();
" ~* b1 w+ C6 B! p - W_SCL=0; //重新拉低SCL
6 ~1 z$ z9 f" |- F2 Y5 Y" { - return 1;9 |5 t5 {* Q6 a" n7 E
- }
$ _5 _2 V0 D) M5 k/ F4 Y2 {2 b9 j - }
复制代码 - f5 N2 }: n/ y' L/ R! w# A% s) t( j
接收一个字节: - uint8_t _iic_ReadByte(uint8_t Ack)
7 n3 c/ [4 C6 ?$ e1 l+ ^" W |2 B - {
: `# l& ?( y' J( H# M; \ - uint8_t temp,i;
1 G5 R3 U# e$ k. Q6 V7 Q4 E - W_SDA=1; //释放SDA
- G, ^& i# M. Y/ h4 j" s - _delay();# J8 }9 \! v @ c
- for(i=0;i<8;i++)
! k- c# E$ R7 D( G - {0 c; A9 c1 L2 W) U
- _delay();3 d8 A8 E4 ?% e9 u
- W_SCL=1; //拉高SCL开始读取SDA 9 A2 E8 b9 P8 x9 w. q
- temp=temp<<1;
8 ?2 I" f$ q% W4 w. r4 b! T4 K - temp|=R_SDA; //SCL拉高之后读取SDA" N8 r, y7 n, O5 z; ]3 G0 C3 t+ Q, |
- W_SCL=0; //拉低SCL,从器件开始放置数据 `# N* F& u/ K5 G& Y& l! {1 ~8 A
- }
|: m* o# h7 k8 `& e - //发送应答信号
; z8 |/ f! ?# n' w0 ?0 V0 x - if(Ack)W_SDA=0; //拉低SDA表示应答. F1 e# i3 P, ]. T8 c1 x
- W_SCL=1; //拉高SCL,从器件接收应答信号5 h# D+ `- ~( i" _* i5 |& K7 L
- _delay();' P: K; \ i3 _3 V' Q# a6 T
- W_SCL=0; //重新拉低SCL
* m/ m+ g6 h) S# z - W_SDA=1; //释放SDA; Q; }( E6 O# T& a
- return temp;" A+ X: z* e! ~& C3 R; i
- }
复制代码
6 G5 N. ]9 A9 O: M; O, W7 M
0 h0 T5 \; r: `* G( q5 ~二、配置HMC5883L模块& t+ {, Q0 Y/ g- \- N: l9 D
- void HMC5883L_Init()7 R o' x$ w+ I6 o0 y4 b! J4 M
- {
# y5 }5 x E" h0 S - _iic_Start();
9 `- l/ g7 X2 r - _iic_SendByte(0x3c); //写操作 N/ c* V9 ?# q! W1 }
- _iic_SendByte(0x00); //指针指向00,配置寄存器A ' M6 G8 a8 j9 _2 I, j$ |) Y
- _iic_SendByte(0x78); //数据测量、输出速率75hz8 V7 _) U" a# c0 n" J2 B
- _iic_Start(); //指针定位到02,模式寄存器
# S! O, x5 U2 b* A! ?& j9 R' u - _iic_SendByte(0x3c);
$ k3 r4 X( M- b* r# U8 ] - _iic_SendByte(0x02);
" `6 a- T7 M# R$ U5 L6 I6 B. v - _iic_SendByte(0x00); //连续测量模式( m% }* J" _( z! D' a0 ? H
- _iic_Stop();
3 c) [( B3 z/ E% f [( Y - }
复制代码 # ~# ], m" F# f
三、读取角度数据接收三轴数据,处理X,Y轴的数据并计算角度: - int16_t HMC5883L_ReadAngle()( w/ B$ l) a3 L9 e5 E
- {
: j0 Z9 m- B8 {* ?& M0 }! K - static uint8_t i; . v0 W4 h( U, \, J( p0 \) X- c
- static uint8_t XYZ_Data[6]; //用来存储三个轴输出的数字量
' F! d0 Q7 |% ]6 q- g4 K7 S3 N l
/ [/ ^' d$ W6 u: c3 F" i" s% y- _iic_Start();# o5 L+ p: [- {% I% f
- _iic_SendByte(0x3c); // 发送HMC5883L的器件地址0x3c,写操作* S8 [+ q5 Y f* A+ D3 V
- _iic_SendByte(0x03); //指针指向03,X msb寄存器 0 u, H$ E5 i9 V! Q" ?
- _iic_Start(); ( D9 E0 I0 Y5 H$ T, w& w$ c: e
- _iic_SendByte(0x3d); //改为读操作! Q, ]* k% p G; E8 T; ?8 r/ F( D
- - V0 n% J6 u4 P3 E! Y, M
- //依次读取三个轴的数字量
; S W9 D1 X, h1 g% N* z - for(i=0;i<5;i++) //前5次读取发送应答信号
9 y. A1 u6 Z: Z - {
, g/ [# o" ?& D# `% c - XYZ_Data[i]=_iic_ReadByte(1);" J5 V5 O" H4 Z% P6 B
- }
) o- J/ e: B4 c$ H- p# Y) N6 o. g5 I - XYZ_Data[5] =_iic_ReadByte(0); //不应答
$ \8 |: a7 d7 ~4 x' U7 @+ P - _iic_Stop();9 H6 K) {) l9 \" N5 V* ~
- return atan2( (double)((int16_t)((XYZ_Data[Ymsb]<<8)+XYZ_Data[Ylsb]) ),(double)((int16_t)((XYZ_Data[Xmsb]<<8)+XYZ_Data[Xlsb])))*(180/3.14159265)+180; //计算角度,需要包含math.h头文件) R+ j- b5 p4 p
- }
复制代码
f1 q; a% ]8 T
) Y% J$ `* w- q) E" n0 V: a0 T# t配置好IO口,调用HMC5883L_Init()后,便可调用HMC5883L_ReadAngle()读取角度值,0~360°。 以下为测试时的截图:
( T6 e0 B" A( r7 {( K
7 c' J& z) H* D% P测试时,模块比较灵敏且精确,稍微旋转模块便有精确的变化。由于该模块是基于对地磁场的测量,此模块容易受到其他磁场的干扰,比如将该模块靠近直流电机时, 便会因为电机内的磁场而降低精度甚至失灵(之前做智能小车时就遇到这个问题,要将电机内的磁场屏蔽起来才行)。
# Z2 x/ D! E$ y( ~: a5 v! d |