|
最近买了个数字罗盘模块,调通后发现很不错,非常灵敏,测试的时候精度在1°以内。连续测量模式下,最快测量、输出速率可达75hz,模块每次测量完毕并将数据更新至寄存器后,其DRDY引脚便产生一个低电平脉冲(可以配置一个外部中断捕获DRDY引脚的下降沿,并在中断服务程序中读取数据),在STM32中可以设置一个下降沿触发的外部中断,并在中断服务程序中调用角度数据读取函数。以下为操作该模块的主要步骤。 一、IIC协议相关操作(单片机作为主机控制时钟线)宏定义: - //这里用到了STM32的位带区操作,方便实现对一个位的操作
7 Y6 D& H/ }4 {8 C; E! N: a - //PB13配置为OD输出,同时外部给上拉电阻,这样既可输出信号给从机,也能$ V4 L$ P; ~; z8 N
- //在PB13为漏极开路状态时接收从机的信号(STM32的IO配置为输出模式时,
9 X& e1 b J6 i; N - //IO口的电平也会不断地被捕获到输入寄存器中)" e1 V& M9 Y+ w9 l2 u9 K9 V- s
- //PB14配置为推挽输出,PB15配置为浮空输入
' w8 W+ p: c m. X5 l3 j5 u - #define R_SDA IPB13 // PB13输入寄存器
1 A( u" I8 v$ b! D! i/ }" p - #define W_SDA OPB13 // PB13输出寄存器# K# w% d$ M* e
- #define W_SCL OPB14 // PB14输出寄存器
! b. i% F0 q, d% D- _; o3 E - #define R_DRDY IPB15 // PB15输入寄存器
复制代码- #define Xmsb 0 //X轴数字量的高8位- A3 b( Y2 R; Y m! {7 Z% a+ U
- #define Xlsb 1 //X轴数字量的低8位" ~1 p% Q9 U$ K3 o2 q; B" o: k
- #define Zmsb 2 //Z轴数字量的高8位3 }; ~5 q4 l# A" h4 P
- #define Zlsb 3 //Z轴数字量的低8位
! s8 i6 S5 F" B7 v7 Y - #define Ymsb 4 //Y轴数字量的高8位
: T$ l9 G( m+ _. U# s - #define Ylsb 5 //Y轴数字量的低8位
复制代码
6 i+ ]9 `) Y4 t 附位带宏定义: - #define GPIOB_ODR_Addr (GPIOB_BASE+12) //0x40010C0C
; z2 I9 w& s& k: r6 }6 u - #define GPIOB_IDR_Addr (GPIOB_BASE+8) //0x40010C08
! M7 l: G' P- @
9 B; Y3 D* K# r/ N- #define BITBAND_Addr(Addr,num) ((volatile unsigned long *)(0x42000000+32*(Addr-0x40000000)+4*num))' S# Z: C- t7 i
- + D, B+ `! E8 q' y
- #define IPB13 *BITBAND_Addr(GPIOB_IDR_Addr,13)$ x3 Z2 a. Z7 r5 Z" P
- #define OPB13 *BITBAND_Addr(GPIOB_ODR_Addr,13)
* i; L. i0 M( m5 A - #define OPB14 *BITBAND_Addr(GPIOB_ODR_Addr,14)
8 a% L, N* D. O2 j7 A+ k - #define IPB15 *BITBAND_Addr(GPIOB_IDR_Addr,15)
复制代码 1 d6 S# ^1 e8 n& `! y j
启动IIC传输: - void _iic_Start(). ~5 V% c$ e1 v: P, X
- {
: {3 N3 [6 b, ], B' q3 C - W_SCL=1; 9 x8 f: ~" C; @( @7 R# n: ~9 n6 ^6 j
- W_SDA=1;3 [; Z' ?% [7 E) s
- _delay();
6 r( O. p: M$ @) j) c. o% i - W_SDA=0; //SCL高时,拉低SDA,表示开始IIC传输,占用总线8 @* p/ G! `' j. W
- _delay();
5 l& z# j+ A( b# v - W_SCL=0; //控制SCL% i8 f S7 w& K; D1 ^
- _delay();, U: |& W; F A& z. E" Y
- }
复制代码
" R8 F# R# X+ k1 ^: M* h0 z* L$ L停止IIC传输: - void _iic_Stop()3 C) [( {+ n) K3 p& q
- {# Z, ^ |3 F* T! K
- W_SCL=1; //释放SCL(由于没有其他主器件,SCL无需开漏)- C0 z9 P+ I3 v6 z" w
- W_SDA=0;
: @/ h7 W- x7 n/ F4 Y L6 z8 A# h - _delay();9 u( P: g* U; D7 _) z; a
- W_SDA=1; //SCL为高时,拉高SDA表示结束ICC传输,释放总线
+ b* t+ i d" p `/ B - }
复制代码
" \/ p/ n' F) p( x4 g发送一个字节: - uint8_t _iic_SendByte(uint8_t dat)
" X3 r) m7 \2 x4 A; ?" Q8 J: V - {7 f9 f9 v' a. S* ^ C
- uint8_t i;6 S4 S1 |7 J$ E. b( B
- for(i=0;i<8;i++)' Q* I; u: Y, ~
- {" w( ]% T6 z1 B! ^& W8 u T
- _delay();
- E5 J2 f( ?+ Y! I& \ - W_SDA=dat>>7; //SCL拉高之前写SDA
p) |/ d5 e4 ?6 S3 r' F - dat=dat<<1;
* w- d1 [5 W1 ?5 H+ h& l" s - _delay();
- ^" O( T# q1 {' K! X - W_SCL=1; //拉高SCL,从器件开始读取SDA 5 f/ u( ]2 j; o* N. ?$ ~* h3 b4 y
- _delay();5 [+ K" S' X1 _ q" B
- W_SCL=0; //重新拉低SCL
- M4 {0 j/ N/ F h5 [- I - }4 G) b) N5 }- a2 A1 _$ _) M/ q
- W_SDA=1; //释放SDA
, k( c1 O( ] b: K9 ~ - W_SCL=1; //拉高SCL,读取从器件应答信号 E# V- r1 z4 l
- // 等待应答 $ m( P( w- Z' \ ^. v2 s
- i=100;
p: L! Y8 F" | - while(i&&R_SDA) {i--;_delay();}, _" i4 N* C; ~1 ^& y! F0 D, H" B
- if(i==0) //无应答
% G2 W& b) q* s. S. d- j4 e2 c - {0 K) `* S/ E! l2 X4 m" X
- W_SCL=0; //重新拉低SCL6 q8 h9 o4 `+ H+ O
- return 0;
, ]% o1 ], h- F# T$ U - }
/ V( i5 ?" O$ c' W - else { //有应答4 V2 @0 C# H' v; I# n0 `) N% F
- _delay();" k8 d& t% q9 M% I- Y
- W_SCL=0; //重新拉低SCL
% B2 p- W: D" O7 i/ r - return 1;
& R9 g0 w/ E$ m6 a/ _ - }
- f0 L+ z0 m, y- h/ h/ Z/ u - }
复制代码
7 e9 H) _, o" G9 r接收一个字节: - uint8_t _iic_ReadByte(uint8_t Ack)
0 j% l h$ i# u3 q+ S - {1 R( n( N! I9 D- s) f
- uint8_t temp,i;4 F ]6 I$ h- O+ c
- W_SDA=1; //释放SDA
& E6 O- b% P% ~& ] - _delay();
8 b* f1 I* n/ l2 g - for(i=0;i<8;i++)3 e0 K9 [$ I8 X; ]4 W: L1 ]
- {( [' v$ [( R' ?$ d
- _delay();, w) H+ \. t; {$ f' F$ D& C
- W_SCL=1; //拉高SCL开始读取SDA & f! Y6 Z1 Q! f3 a7 i/ l
- temp=temp<<1;
- E0 C. s* v6 W! v: b - temp|=R_SDA; //SCL拉高之后读取SDA, ^' c" A% p) j' C7 N; z5 c8 Q) i4 o3 N
- W_SCL=0; //拉低SCL,从器件开始放置数据
5 o% y+ p) b$ I4 a2 }& L: y - }
- Q# T6 D3 c+ q6 q - //发送应答信号3 Y* }% l% a: U
- if(Ack)W_SDA=0; //拉低SDA表示应答
' A# l9 E/ j' N- [ - W_SCL=1; //拉高SCL,从器件接收应答信号& u2 j% c$ l C' L0 I# \; V3 m
- _delay();
- l% B7 C7 P+ |& J% y+ o9 c: f - W_SCL=0; //重新拉低SCL/ } u; g ]- D) Z6 s
- W_SDA=1; //释放SDA
& i+ U; x O) V) j - return temp;- w& @3 O/ Q4 G, z+ l+ j* K- w) E
- }
复制代码 ; U* l4 r1 b2 o
0 Q7 B) |4 D% i% H$ F: {
二、配置HMC5883L模块) K9 b# P- G7 |$ q* ]0 h% ^
- void HMC5883L_Init()
3 E* O9 D6 Z+ y! C! Y# m/ t - { $ F9 S+ p: ?. x5 ]. G7 S
- _iic_Start();* k- j8 C- C1 [; V& ]7 q
- _iic_SendByte(0x3c); //写操作9 Q" R6 S) K5 G( p0 g
- _iic_SendByte(0x00); //指针指向00,配置寄存器A
- V, J' Z* I6 k* E - _iic_SendByte(0x78); //数据测量、输出速率75hz
8 ~- u* X* ~! `9 s6 H1 O2 M) m - _iic_Start(); //指针定位到02,模式寄存器; P) t( g+ w% B" e$ ]5 e) \3 Q o7 F! o) l
- _iic_SendByte(0x3c);
4 z; l6 y9 t6 j& ] - _iic_SendByte(0x02);
- N, l/ T( {3 N- L, R9 C$ W - _iic_SendByte(0x00); //连续测量模式5 T+ d$ R( i6 G# R+ l( Z1 O
- _iic_Stop();
( N" Q) v9 T. i - }
复制代码 / Z/ k0 N" v' b: x3 q s, `# }' Q
三、读取角度数据接收三轴数据,处理X,Y轴的数据并计算角度: - int16_t HMC5883L_ReadAngle()
' X6 s; b& H+ a. j8 O$ o - {
" B/ v) h @6 g; l - static uint8_t i; + v c' j0 F1 J, B: S
- static uint8_t XYZ_Data[6]; //用来存储三个轴输出的数字量; o" A3 }. _( W; t) }+ w! n( j
5 B, A# p* C/ C9 A- _iic_Start();
! A5 P j# ~; H# l z3 c( P - _iic_SendByte(0x3c); // 发送HMC5883L的器件地址0x3c,写操作
) i0 O2 p* y/ X% G+ z2 [ - _iic_SendByte(0x03); //指针指向03,X msb寄存器 2 R" b( }/ v( d l& D$ Q2 n
- _iic_Start(); - x% {$ |* v% }" f
- _iic_SendByte(0x3d); //改为读操作2 H3 @! |( x3 N+ S. i% b+ J/ A
. F/ K3 g2 I& c. @- q- //依次读取三个轴的数字量
" s" }! D! T7 A0 t/ A0 F - for(i=0;i<5;i++) //前5次读取发送应答信号! Q' y* M! u7 T V( i
- {: M- ?0 b$ Z" {2 \& Q/ s
- XYZ_Data[i]=_iic_ReadByte(1);" R$ L- O% \/ W& y, H
- }$ Q3 q9 [) i: w$ x- _0 _
- XYZ_Data[5] =_iic_ReadByte(0); //不应答
# \! j* }7 @/ C; `& c - _iic_Stop();
! B+ |2 v6 x, U3 w2 I - 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头文件
1 d1 S0 Z. G! Q/ E) R9 R& n - }
复制代码
& p# G' S( B+ Y
2 {- O8 G' K( w6 V. Z ^ _3 I1 H配置好IO口,调用HMC5883L_Init()后,便可调用HMC5883L_ReadAngle()读取角度值,0~360°。 以下为测试时的截图:
, j3 O5 t+ n7 u$ w3 `0 X6 z
" E3 u( f) W# _8 m( G" ^7 Y, Y9 u
测试时,模块比较灵敏且精确,稍微旋转模块便有精确的变化。由于该模块是基于对地磁场的测量,此模块容易受到其他磁场的干扰,比如将该模块靠近直流电机时, 便会因为电机内的磁场而降低精度甚至失灵(之前做智能小车时就遇到这个问题,要将电机内的磁场屏蔽起来才行)。
3 c9 p8 `5 K( p( \1 P |