在众多磁力计中,HMC5883L因为其廉价实用而受到大多数嵌入式开发小白的追捧。然而某宝上不仅仅有HMC5883L这一款,还有QMC5883和HMC5983,常有商家搞不清芯片就出售,导致提供的数据手册不对应。同时,不同单片机对IIC的操作也略有差异,对小白而言也是件头疼的事情。
可以看到,QMC5883L的各寄存器地址与HMC5883L区别还是不小。这样,移植时候程序整体结构不用大改,把寄存器改掉即可。
(1) 修改I2C address
0x3C -> 0x1A
0x3D -> 0x1B
(2) X/Y/Z Register
起始:0x03 -> 0x00
顺序:X-Z-Y -> X-Y-Z
(3) Mode Register
0x02 -> 0x09
0x00 -> 0x01
HMC5983是HMC5883L的“加强版”,寄存器与5883基本一致,但同时支持I2C和SPI两种通信模式,更加快捷。因此,使用方法与5883几乎没有差别。这里,本人将官方提供的测试程序修改为STC15系列单片机,主要移植I2C通信中时序函数。
本人增加了一个定任意方向为0度的功能,主要为了解决各类机器人竞赛中场地不一定何种角度朝向的问题。不过由于时间仓促没有来的及写滤波。思路就是将坐标系进行转换。贴出程序,仅供参考。
STC15W408AS x1
HMC5983 x1
1k电阻 x1
独立按键 x1
排针 若干
洞洞板 x1
(1)user_config.h
/*-----------------------------------------
用户配置文件
-------------------------------------------*/
#ifndef _CONFIG_H_
#define _CONFIG_H_
/* 宏定义 */
#define uchar unsigned char
#define uint unsigned int
typedef unsigned char BYTE;
typedef unsigned short WORD;
/* 包含头文件 */
#include
#include //Keil library
#include //Keil library
#include
//USER Library
#include "HMC5983_Driver.h"
#include "HMC5983_Software.h"
#include "uart.h"
#include "delay.h"
#include "interrupt.h"
//#include "LQ12864.h"
//使用的端口,请按照以下接线
sbit SCL=P3^2; //IIC时钟引脚定义
sbit SDA=P3^3; //IIC数据引脚定义
sbit KEY=P3^6; //按键
//sfr INT_CLKO = 0x8f; //外部中断与时钟输出控制寄存器
//全局变量引入
extern BYTE BUF[8];
extern uchar ge,shi,bai,qian,wan; //显示变量
extern int dis_data; //变量
extern double angle_ch;
#endif
(2)HMC5983_Driver.h
/*------------------------------------
HMC5983驱动文件
-------------------------------------*/
#ifndef _HMC5983_DRIVER_H_
#define _HMC5983_DRIVER_H_
#define SlaveAddress 0x3C //定义器件5883在IIC总线中的从地址
//************
//函数
//************
void Init_HMC5883(void); //初始化5883
void Single_Write_HMC5883(uchar REG_Address,uchar REG_data); //单个写入数据
//uchar Single_Read_HMC5883(uchar REG_Address); //单个读取内部寄存器数据
void Multiple_Read_HMC5883(void); //连续的读取内部寄存器数据
//以下是模拟iic使用函数-------------
void HMC5883_Start(void);
void HMC5883_Stop(void);
void HMC5883_SendACK(bit ack);
bit HMC5883_RecvACK(void);
void HMC5883_SendByte(BYTE dat);
BYTE HMC5883_RecvByte(void);
void HMC5883_ReadPage(void);
void HMC5883_WritePage(void);
#endif
(3)interrupt.h
/*--------------------------
中断服务头文件
---------------------------*/
#ifndef _IT_H_
#define _IT_H_
#define Init_INT2() INT_CLKO |= 0x10
#endif
(4)uart.c
/*-----------------------------------
串口配置
STC15F408AS
内置晶振
波特率 9600 bps
------------------------------------*/
#include "user_config.h"
BYTE BUF[8]; //接收数据缓存区
//*********************************************
//串口初始化
//9600 bps @ 11.059 MHz
void init_uart(void)
{
SCON = 0x50;
AUXR |= 0x04;
T2L = 0xa0;
T2H = 0xfc;
AUXR |= 0x10;
TI = 1;
EA = 1;
}
//*********串口数据发送******************
void SeriPushSend(uchar send_data)
{
SBUF = send_data;
while(!TI);TI=0;
}
(5)interrupt.c
/*-----------------------------
中断服务
-----------------------------*/
#include "user_config.h"
double angle_ch = 0;
void KEY_DOWN(void) interrupt 10
{
Delay5ms();
if(KEY == 0)
{
while(!KEY);
angle_ch = Angle_Update();
}
}
(6)HMC5983_Driver.c
//***************************************
// HMC5883 51串口测试程序
// 使用单片机STC89C51
// 晶振:11.0592M
// 显示:PC串口
// 编译环境 Keil uVision2
// 参考宏晶网站24c04通信程序
// 时间:2011年3月1日
//****************************************
#include "user_config.h"
#include "HMC5983_Driver.h"
int dis_data; //变量
//-----------------------------------
/**************************************
起始信号
**************************************/
void HMC5883_Start(void)
{
SDA = 1; //拉高数据线
SCL = 1; //拉高时钟线
Delay5us(); //延时
SDA = 0; //产生下降沿
Delay5us(); //延时
SCL = 0; //拉低时钟线
}
/**************************************
停止信号
**************************************/
void HMC5883_Stop(void)
{
SDA = 0; //拉低数据线
SCL = 1; //拉高时钟线
Delay5us(); //延时
SDA = 1; //产生上升沿
Delay5us(); //延时
}
/**************************************
发送应答信号
入口参数:ack (0:ACK 1:NAK)
**************************************/
void HMC5883_SendACK(bit ack)
{
SDA = ack; //写应答信号
SCL = 1; //拉高时钟线
Delay5us(); //延时
SCL = 0; //拉低时钟线
Delay5us(); //延时
}
/**************************************
接收应答信号
**************************************/
bit HMC5883_RecvACK(void)
{
SCL = 1; //拉高时钟线
Delay5us(); //延时
CY = SDA; //读应答信号
SCL = 0; //拉低时钟线
Delay5us(); //延时
return CY;
}
/**************************************
向IIC总线发送一个字节数据
**************************************/
void HMC5883_SendByte(BYTE dat)
{
BYTE i;
for (i=0; i<8; i++) //8位计数器
{
dat <<= 1; //移出数据的最高位
SDA = CY; //送数据口
SCL = 1; //拉高时钟线
Delay5us(); //延时
SCL = 0; //拉低时钟线
Delay5us(); //延时
}
HMC5883_RecvACK();
}
/**************************************
从IIC总线接收一个字节数据
**************************************/
BYTE HMC5883_RecvByte(void)
{
BYTE i;
BYTE dat = 0;
SDA = 1; //使能内部上拉,准备读取数据,
for (i=0; i<8; i++) //8位计数器
{
dat <<= 1;
SCL = 1; //拉高时钟线
Delay5us(); //延时
dat |= SDA; //读数据
SCL = 0; //拉低时钟线
Delay5us(); //延时
}
return dat;
}
//***************************************************
void Single_Write_HMC5883(uchar REG_Address,uchar REG_data)
{
HMC5883_Start(); //起始信号
HMC5883_SendByte(SlaveAddress); //发送设备地址+写信号
HMC5883_SendByte(REG_Address); //内部寄存器地址,请参考中文pdf
HMC5883_SendByte(REG_data); //内部寄存器数据,请参考中文pdf
HMC5883_Stop(); //发送停止信号
}
/*
//********单字节读取内部寄存器*************************
uchar Single_Read_HMC5883(uchar REG_Address)
{
uchar REG_data;
HMC5883_Start(); //起始信号
HMC5883_SendByte(SlaveAddress); //发送设备地址+写信号
HMC5883_SendByte(REG_Address); //发送存储单元地址,从0开始
HMC5883_Start(); //起始信号
HMC5883_SendByte(SlaveAddress+1); //发送设备地址+读信号
REG_data=HMC5883_RecvByte(); //读出寄存器数据
HMC5883_SendACK(1);
HMC5883_Stop(); //停止信号
return REG_data;
}
*/
//******************************************************
//
//连续读出HMC5883内部角度数据,地址范围0x3~0x5
//
//******************************************************
void Multiple_read_HMC5883(void)
{ uchar i;
HMC5883_Start(); //起始信号
HMC5883_SendByte(SlaveAddress); //发送设备地址+写信号
HMC5883_SendByte(0x03); //发送存储单元地址,从0x3开始
HMC5883_Start(); //起始信号
HMC5883_SendByte(SlaveAddress+1); //发送设备地址+读信号
for (i=0; i<7; i++) //连续读取6个地址数据,存储中BUF
{
BUF[i] = HMC5883_RecvByte(); //BUF[0]存储0x32地址中的数据
if (i == 6)
{
HMC5883_SendACK(1); //最后一个数据需要回NOACK
}
else
{
HMC5883_SendACK(0); //回应ACK
}
}
HMC5883_Stop(); //停止信号
Delay5ms();
}
//初始化HMC5883,根据需要请参考pdf进行修改****
void Init_HMC5883(void)
{
Single_Write_HMC5883(0x02,0x00); //
Single_Write_HMC5883(0x01,0xE0); //
}
(7)HMC5983_Software.c
/*---------------------------------
HMC5983 角度变换
将任意角度变为0度
-----------------------------------*/
#include "user_config.h"
uchar ge,shi,bai,qian,wan; //显示变量
double Angle_Update(void)
{
int x,y,z,t;
Multiple_Read_HMC5883(); //连续读出数据,存储在BUF中
x = BUF[0] << 8 | BUF[1];
z = BUF[2] << 8 | BUF[3];
y = BUF[4] << 8 | BUF[5];
t = atan2((double)y,(double)x) * (180 / 3.14159265) + 180; // angle in degrees
while(abs(t-45) <= 0.001)
{
Multiple_Read_HMC5883(); //连续读出数据,存储在BUF中
x = BUF[0] << 8 | BUF[1];
z = BUF[2] << 8 | BUF[3];
y = BUF[4] << 8 | BUF[5];
t = atan2((double)y,(double)x) * (180 / 3.14159265) + 180; // angle in degrees
}
return t;
}
void conversion(uint temp_data)
{
wan=temp_data/10000+0x30 ;
temp_data=temp_data%10000; //取余运算
qian=temp_data/1000+0x30 ;
temp_data=temp_data%1000; //取余运算
bai=temp_data/100+0x30 ;
temp_data=temp_data%100; //取余运算
shi=temp_data/10+0x30 ;
temp_data=temp_data%10; //取余运算
ge=temp_data+0x30;
}
double Change_Target(void)
{
double angle_now = Angle_Update();
if(angle_now >= angle_ch) return angle_now-angle_ch;
else return 360-angle_ch+angle_now;
}
(8)main.c
/*----------------------------------------------------------
基于HMC5983智能电子罗盘
2017/10/24 Listen C
@ 型号:STC15F408AS
@ 晶振:内置33.1776MHz
@ 通信:IIC
@ 显示:0.96 OLED
@ 串口:9600 bps
------------------------------------------------------------*/
#include
#include "user_config.h"
//*********************************************************
// 主程序
//*********************************************************
void main(void)
{ // bit sign_bit;
unsigned int i;
double angle;
delay(100);
Init_INT2();
init_uart();
Init_HMC5883();
while(1) //循环
{
angle = Change_Target();
conversion(angle); //计算数据和显示
Delay5ms();
SeriPushSend(0x0d);
SeriPushSend(0x0a);
SeriPushSend(bai);
SeriPushSend(shi);
SeriPushSend(ge);
}
}
在arduino中,I2C address用的是7位地址而不是8位,所以需要对应将address改掉。
比如QMC5883为0x1a,则二进制为0001_1010。而换算为7位,右移一位二进制为0000_1101。
则十六进制为0x0d。
源码如下:
#include
#define address 0x0d
void setup() {
//put your setup code here, to run once:
Serial.begin(9600);
delay(500);
Wire.begin();
Wire.beginTransmission(address);
Wire.write(0x09);
Wire.write(0x01);
Wire.endTransmission();
}
void loop() {
//put your main code here, to run repeatedly:
intx,y,z;
Wire.beginTransmission(address);
Wire.write(0x00);
Wire.endTransmission();
Wire.requestFrom(address,6);
if(Wire.available() >= 6){
x= Wire.read() << 8;
x |= Wire.read();
y = Wire.read() << 8;
y |= Wire.read();
z= Wire.read() << 8;
z |= Wire.read();
}
Serial.print("x: ");
Serial.print(x);
Serial.print(" y: ");
Serial.print(y);
Serial.print(" z: ");
Serial.println(z);
delay(250);
}
网盘链接:http://pan.baidu.com/s/1gfEje8r 密码:dbvr