|
最近买了个数字罗盘模块,调通后发现很不错,非常灵敏,测试的时候精度在1°以内。连续测量模式下,最快测量、输出速率可达75hz,模块每次测量完毕并将数据更新至寄存器后,其DRDY引脚便产生一个低电平脉冲(可以配置一个外部中断捕获DRDY引脚的下降沿,并在中断服务程序中读取数据),在STM32中可以设置一个下降沿触发的外部中断,并在中断服务程序中调用角度数据读取函数。以下为操作该模块的主要步骤。 一、IIC协议相关操作(单片机作为主机控制时钟线)宏定义: - //这里用到了STM32的位带区操作,方便实现对一个位的操作- G' [4 e/ j' T7 h
- //PB13配置为OD输出,同时外部给上拉电阻,这样既可输出信号给从机,也能
% @; U5 O4 D8 b! u7 N' I, A5 V - //在PB13为漏极开路状态时接收从机的信号(STM32的IO配置为输出模式时,
$ C; k) ?& |! \& R9 O* x1 Z& @! [ - //IO口的电平也会不断地被捕获到输入寄存器中)' ^2 m9 E5 ^1 X5 K7 G: r
- //PB14配置为推挽输出,PB15配置为浮空输入
+ h- v4 _& z$ p* l - #define R_SDA IPB13 // PB13输入寄存器! }! C7 i: q* e; S5 I3 T7 a9 l
- #define W_SDA OPB13 // PB13输出寄存器* `4 i& \, f7 j' i3 |# j* y5 G4 q6 h
- #define W_SCL OPB14 // PB14输出寄存器, r4 x; N" O8 J D! N
- #define R_DRDY IPB15 // PB15输入寄存器
复制代码- #define Xmsb 0 //X轴数字量的高8位0 y4 M; u0 ]2 ~/ T4 \* Q
- #define Xlsb 1 //X轴数字量的低8位& k! e7 k- a" d" f/ t; z5 U
- #define Zmsb 2 //Z轴数字量的高8位
6 D5 f7 d. p$ O; v) `# g - #define Zlsb 3 //Z轴数字量的低8位: u/ H- h7 C% ?# |4 F1 B
- #define Ymsb 4 //Y轴数字量的高8位7 o4 H* c4 p: @8 v2 g& K4 u
- #define Ylsb 5 //Y轴数字量的低8位
复制代码
6 c: D! o9 Z4 ] 附位带宏定义: - #define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
0 r! \: l) @! T) d% R - #define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08
# K7 Q3 M% K! m( c8 m8 r - 1 N) }' T0 u4 C) `
- #define BITBAND_Addr(Addr,num) ((volatile unsigned long *)(0x42000000+32*(Addr-0x40000000)+4*num))1 s6 d2 D) l; S6 W
- 9 C5 a& B6 K" D; S+ i! v% T
- #define IPB13 *BITBAND_Addr(GPIOB_IDR_Addr,13): p+ n8 J# G+ o; ]' t
- #define OPB13 *BITBAND_Addr(GPIOB_ODR_Addr,13)
, H/ I3 B- Y* Q/ j+ [' x* h: K - #define OPB14 *BITBAND_Addr(GPIOB_ODR_Addr,14)
) r; ^- b: \% v- Z - #define IPB15 *BITBAND_Addr(GPIOB_IDR_Addr,15)
复制代码
; {! r( {- t2 }: G启动IIC传输: - void _iic_Start()7 H2 @8 ^6 [7 {* R
- { B& G2 [; q0 ?9 S7 x
- W_SCL=1;
( j- g# e$ e E" l% F3 n) K - W_SDA=1;
+ @) }7 I& Y: A7 o: ~) f3 q - _delay();; \2 v) q$ z7 e8 |
- W_SDA=0; //SCL高时,拉低SDA,表示开始IIC传输,占用总线
, v1 Q8 D+ ^5 m! E - _delay();
; U9 @! l: }8 _$ Y! ~' {/ _4 Q - W_SCL=0; //控制SCL
$ M1 J; o3 B% v9 d% x) b( v7 z5 b - _delay();8 m' e& Y% v+ l# J! D3 V/ a: |
- }
复制代码
; I* G {! d& a+ v停止IIC传输: - void _iic_Stop()
) V, _- N7 k! Z0 n V& {7 P$ n - {
$ O+ _: O5 H i2 A) D% f - W_SCL=1; //释放SCL(由于没有其他主器件,SCL无需开漏)
, d& C# z7 o* X1 n - W_SDA=0; L6 {4 a: T1 m% s( U
- _delay();: e/ X9 k4 @5 o% K0 Y, h
- W_SDA=1; //SCL为高时,拉高SDA表示结束ICC传输,释放总线
# I( C' v3 z& k - }
复制代码
& ^: w# d j8 b8 y9 [! X2 S8 h发送一个字节: - uint8_t _iic_SendByte(uint8_t dat) 5 U" M7 [2 _' C8 @
- {0 }( ~2 m' l1 y( B- n1 N5 _3 J1 _( m
- uint8_t i;" V- q! E% w2 L/ Z7 l$ {" x0 z
- for(i=0;i<8;i++)2 o! d2 `$ T$ _ I' A
- {
% v4 W" n& `, g5 U @6 j - _delay();, h3 {+ K$ e3 ?3 X5 r& d* Q7 K# G
- W_SDA=dat>>7; //SCL拉高之前写SDA7 r) [ o7 ~/ V! x! q7 A, m! ?, u
- dat=dat<<1;
f( ^* U: o' a+ ?* {, z4 ] - _delay();
/ j, I) Q7 x$ d! L2 W5 P - W_SCL=1; //拉高SCL,从器件开始读取SDA ( `7 \7 Q. S+ L3 w+ {2 O& F2 H/ \8 k
- _delay();
6 r2 ~. o9 m' ? - W_SCL=0; //重新拉低SCL& N% R% e0 k7 S: P
- }# c& R# \; U. l% F) S( w
- W_SDA=1; //释放SDA w! d9 `9 G6 n& n& E- a
- W_SCL=1; //拉高SCL,读取从器件应答信号
6 O7 z: ^/ T( v/ q* A5 ~ - // 等待应答
$ T; g1 I( w) {0 L. I# y - i=100;7 k- j% N0 a3 E4 S
- while(i&&R_SDA) {i--;_delay();}1 b, Q6 ^( Z. S" x7 ]7 ~, n3 R
- if(i==0) //无应答
, k: c% P+ m9 x: x; y" V - {3 h i+ F1 c+ b7 _ F" f0 k- x
- W_SCL=0; //重新拉低SCL. T$ m% ]5 K' \
- return 0;2 x/ g) Z; d+ C: \# O6 O
- }) V* c! Y2 {; q2 N2 \# B
- else { //有应答; V4 p" d' z. I/ p
- _delay();
) Z7 k* a' \ B- y- ]$ }$ O0 }3 L - W_SCL=0; //重新拉低SCL
9 s5 K+ k3 v+ y' ?4 h - return 1;1 f2 |6 H6 M# [4 D" f
- }" T3 w, j8 M: T
- }
复制代码 5 {& e# L C3 E8 v: w
接收一个字节: - uint8_t _iic_ReadByte(uint8_t Ack)
* P! Y( P+ t" q$ v6 w1 ^* j: S - {
3 j; ?5 A; V4 U7 Y2 Q - uint8_t temp,i;( D! R1 g$ G+ p% H% A3 \5 S
- W_SDA=1; //释放SDA7 |, d9 G9 i5 H" ?1 _- g# i6 F( `
- _delay();
i8 \" U: V' C1 h+ O; _, r H* T - for(i=0;i<8;i++) O) W2 N; E1 y. l$ m: U0 y
- {
: h+ e* ^' u' T! Q' G m5 _. D - _delay();
8 Y) s; s5 n* {7 T4 x9 z# y - W_SCL=1; //拉高SCL开始读取SDA 0 X$ R- Y! C7 p6 q0 n8 J7 L
- temp=temp<<1; 5 P: l8 { ^5 S% ]3 a
- temp|=R_SDA; //SCL拉高之后读取SDA* O; G1 G- j4 `" W! Z
- W_SCL=0; //拉低SCL,从器件开始放置数据$ C6 a3 a9 F2 {- r1 d7 V
- }5 _- @! @( r2 o# I8 i7 x; P
- //发送应答信号
) R( W2 z. W9 w4 a6 R1 V - if(Ack)W_SDA=0; //拉低SDA表示应答
& u3 u. ?& ~( p* J0 Q7 m5 i- Z - W_SCL=1; //拉高SCL,从器件接收应答信号' R9 o1 ~/ r8 w9 R
- _delay();
! N8 y+ R% p M" z - W_SCL=0; //重新拉低SCL
! W7 i; y5 |% S4 z# p( y+ I7 k - W_SDA=1; //释放SDA7 W9 h* v8 J) g1 m1 B
- return temp;
2 r8 m; W, S" y$ v- \ - }
复制代码 * T- o" A$ Y* P; i% [4 m+ s* T) t
6 f; n2 ]) e. V, {) W ^/ F
二、配置HMC5883L模块
) A; J5 N9 Z/ b8 d" r- L6 M- void HMC5883L_Init()! ?- }% p$ g' d* F% i
- { $ D7 `9 z! n9 z) q; h3 i# A& k2 x& N
- _iic_Start();
3 s! V& u: Q/ A5 J9 g - _iic_SendByte(0x3c); //写操作
) M% T2 r& p: F* G8 Z) H2 B6 m - _iic_SendByte(0x00); //指针指向00,配置寄存器A # f( k' l: P" F; T2 {1 B. ` o
- _iic_SendByte(0x78); //数据测量、输出速率75hz
! j6 ]6 D. X1 R% ^- Y5 s, a - _iic_Start(); //指针定位到02,模式寄存器; y0 P4 Y; q1 }. E
- _iic_SendByte(0x3c);
2 y5 t. P/ \# C/ v - _iic_SendByte(0x02);
. X4 p' d, x; k% ]# \, D - _iic_SendByte(0x00); //连续测量模式
6 k: k* ]+ X2 h9 X6 t B - _iic_Stop();4 V: F q' k$ z+ Q' K
- }
复制代码 & |, E. G8 y" c* S. U
三、读取角度数据接收三轴数据,处理X,Y轴的数据并计算角度: - int16_t HMC5883L_ReadAngle()
! J+ D" ^0 u+ e0 [ - {
% }9 q L. q( w% u - static uint8_t i;
5 b+ j6 k/ p4 h - static uint8_t XYZ_Data[6]; //用来存储三个轴输出的数字量
: d) o3 [3 f( ^% A
5 W6 j- e: R6 V* i# O7 A- _iic_Start();
6 ~8 R0 a2 q& v# ^) _2 X1 O - _iic_SendByte(0x3c); // 发送HMC5883L的器件地址0x3c,写操作
' y$ o$ g W1 \) U( g+ P6 E - _iic_SendByte(0x03); //指针指向03,X msb寄存器 : c0 Z: h* y& Q( p* a! o0 |
- _iic_Start();
$ |, n! x5 z% R& E5 {9 l6 H - _iic_SendByte(0x3d); //改为读操作
) h/ @; z2 W s
# p: y) d) r: y: @# o' |* g- //依次读取三个轴的数字量0 l% |1 @9 M9 }! T$ l
- for(i=0;i<5;i++) //前5次读取发送应答信号 x+ ~) A6 V2 L( X
- {8 f- e( J% b+ @* `* E$ L
- XYZ_Data[i]=_iic_ReadByte(1);
0 @# |; A5 d9 F. e% \) @ - }+ T2 h5 g) f3 [5 t2 Q, Z& n
- XYZ_Data[5] =_iic_ReadByte(0); //不应答
5 {) a6 B: N3 v! I% E# u - _iic_Stop();
& w. w$ t) d$ p: n6 P/ Y - 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' H7 `( ^- D - }
复制代码 ( b6 `1 [! _$ S, h' g
, x7 L9 P; J7 I5 u, D# E配置好IO口,调用HMC5883L_Init()后,便可调用HMC5883L_ReadAngle()读取角度值,0~360°。 以下为测试时的截图:
: E. l4 C4 K/ b7 m- G L" |- s
! }1 J$ k% W' }1 h测试时,模块比较灵敏且精确,稍微旋转模块便有精确的变化。由于该模块是基于对地磁场的测量,此模块容易受到其他磁场的干扰,比如将该模块靠近直流电机时, 便会因为电机内的磁场而降低精度甚至失灵(之前做智能小车时就遇到这个问题,要将电机内的磁场屏蔽起来才行)。 ! V! T: Q( _2 q. r6 ~; b6 e
|