常用的地磁传感器主要有FreeScale(飞思卡尔)的MAG系列和Honeywell(霍尼韦尔)的HMC系列,下面就以市场上常见的Honeywell的HMC5883的地磁传感器来进行讨论。
HMC5883是一种表面贴装的高集成度、带有IIC数字接口的弱磁传感器芯片。它内含有最先进的高分辨率HMC118X系列磁阻传感器,并附带霍尼韦尔专利的集成电路(包括有放大器、自动消磁驱动器和偏差校准等),具有12位模数转换器能使罗盘精度控制在1°~2°之间。霍尼韦尔的磁传感器在低磁场传感器行业中是灵敏度最高和可靠性最好的传感器。其测量范围能从毫高斯到8高斯(gauss)。下面是它的封装外形和引脚图。
HMC5883的工作电压在2.16V~3.6V之间,典型为3.3V。虽然工作电压为低电压方式,但数据端口的电压可通过VDDIO口来指定,因此它与单片机的接口有两种方式,一种是单片机为5V方式,一种是单片机为3.3V方式,具体如如下图所示。
#include <math.h>
//=========================定义从器件地址和读写方式=============================
#define rd_device_add 0x3d //即00111101,0011110是HMC5883的固定地址,最后的1表示对从器件进行读操作
#define wr_device_add 0x3c //即00111100,0011110是HMC5883的固定地址,最后的0表示对从器件时行写操作
//===============================TWI状态定义==================================
#define START 0x08
#define RE_START 0x10
#define MT_SLA_ACK 0x18
#define MT_SLA_NOACK 0x20
#define MT_DATA_ACK 0x28
#define MT_DATA_NOACK 0x30
#define MR_SLA_ACK 0x40
#define MR_SLA_NOACK 0x48
#define MR_DATA_ACK 0x50
#define MR_DATA_NOACK 0x58
//=============================常用TWI操作定义================================
#define Start() (TWCR=(1<<TWINT)|(1<<TWSTA)|(1<<TWEN))
#define Stop() (TWCR=(1<<TWINT)|(1<<TWSTO)|(1<<TWEN))
#define Wait() {while(!(TWCR&(1<<TWINT)));}
#define TestAck() (TWSR&0xf8)
#define SetAck() (TWCR|=(1<<TWEA))
#define SetNoAck() (TWCR&=~(1<<TWEA))
#define Twi() (TWCR=(1<<TWINT)|(1<<TWEN))
#define Write8Bit(x) {TWDR=(x);TWCR=(1<<TWINT)|(1<<TWEN);}
//============引脚电平的宏定义===============
#define LCM_RS_1 PORTB_Bit0=1 //RS脚输出高电平
#define LCM_RS_0 PORTB_Bit0=0 //RS脚输出低电平
#define LCM_RW_1 PORTB_Bit1=1 //RW脚输出高电平
#define LCM_RW_0 PORTB_Bit1=0 //RW脚输出低电平
#define LCM_EN_1 PORTB_Bit2=1 //EN脚输出高电平
#define LCM_EN_0 PORTB_Bit2=0 //EN脚输出低电平
#define DataPort PORTA //PORTA为数据端口
#define Busy 0x80 //忙信号
//==============定义变量================
unsigned char ge,shi,bai,qian,wan; //显示变量
unsigned char BUF[8]; //接收数据缓存区
//==============定义显示字符串================
const unsigned char str0[]={"Angle: . "}; //显示角度
//===============1mS延时===================
void delay_1ms(void)
{ unsigned int i;
for(i=1;i<(unsigned int)(8*143-2);i++)
;
}
//=============n*1mS延时===============
void delay_nms(unsigned int n)
{
unsigned int i=0;
while(i<n)
{delay_1ms();
i++;
}
}
//===============IIC总线写n个字节(成功返回0,失败返回1)=====================
unsigned char I2C_Write(unsigned char RomAddress,unsigned char *buf,unsigned char len)
{
unsigned char i;
Start(); //启动I2C总线
Wait(); //等待回应
if(TestAck()!=START)
return 1; //若回应的不是启动信号,则失败返回1
Write8Bit(wr_device_add); //写I2C从器件地址、写方向
Wait(); //等待回应
if(TestAck()!=MT_SLA_ACK)
return 1; //若回应的不是ACK信号,则失败返回值1
Write8Bit(RomAddress); //写HMC5883的ROM地址
Wait(); //等待回应
if(TestAck()!=MT_DATA_ACK)
return 1; //若回应的不是ACK信号则失败返回值1
for(i=0;i<len;i++)
{
Write8Bit(buf[i]); //写数据到HMC5883的ROM中
Wait(); //等待回应
if(TestAck()!=MT_DATA_ACK)
{return 1;} //若回应的不是ACK信号则失败返回值1
delay_nms(10);
}
Stop(); //停止I2C总线
delay_nms(10); //延时等待HMC5883写完
return 0; //写入成功,返回值0
}
//====================IIC总线读n个字节(成功返回0,失败返回1)=========================
unsigned char I2C_Read(unsigned char RomAddress,unsigned char *buf,unsigned char len)
{
unsigned char i;
Start(); //启动I2C总线
Wait(); //等待回应
if(TestAck()!=START)
return 1; //若回应的不是启动信号,则失败返回1
Write8Bit(wr_device_add); //写I2C从器件地址、写方向
Wait(); //等待回应
if(TestAck()!=MT_SLA_ACK)
return 1; //若回应的不是ACK信号,则失败返回值1
Write8Bit(RomAddress); //写HMC5883的ROM地址
Wait(); //等待回应
if(TestAck()!=MT_DATA_ACK)
return 1; //若回应的不是ACK信号,则失败返回值1
Start(); //重新启动I2C总线
Wait(); //等待回应
if(TestAck()!=RE_START)
return 1; //若回应的不是重复启动信号,则失败返回1
Write8Bit(rd_device_add); //写I2C从器件地址、读方向
Wait(); //等待回应
if(TestAck()!=MR_SLA_ACK)
return 1; //若回应的不是ACK信号,则失败返回值1
for(i=0;i<len;i++)
{
Twi(); //启动I2C读方式
SetAck(); //设置接收自动应答
delay_nms(10);
Wait(); //等待回应
delay_nms(10);
*(buf+i)=TWDR; //把连续读取的len个字节数据依次存入对应的地址单元(数组)中
}
SetNoAck(); //读数据的最后一位后紧跟的是无应答
delay_nms(10);
Stop(); //停止I2C总线
return 0; //成功返回值0
}
//================检测LCD忙信号子函数================
void WaitForEnable(void)
{
unsigned char val;
DataPort=0xff; //数据线电平拉高
LCM_RS_0; //选择指令寄存器
LCM_RW_1; //选择写方式
__asm("NOP"); //调用汇编指令延时一个空指令周期,等待稳定
LCM_EN_1; //使能端拉高电平
__asm("NOP");
__asm("NOP"); //调用汇编指令延时两个空指令周期,等待稳定
DDRA=0x00; //改变数据线方向成输入
val=PINA; //读取数据
while(val&Busy)
val=PINA; //当DB7位为1时表示忙,循环检测
LCM_EN_0; //忙信号结束,拉低使能端电平
DDRA=0xff; //改变数据线方向成输出
}
//================写数据到LCD子函数=================
void LcdWriteData(unsigned char dataW) //写数据dataW到LCD中
{
WaitForEnable(); //检测忙信号
LCM_RS_1; //选择数据寄存器
LCM_RW_0; //选择读方式
__asm("NOP"); //调用汇编指令延时一个空指令周期,等待稳定
DataPort=dataW; //把显示数据送到数据线上
__asm("NOP"); //调用汇编指令延时一个空指令周期,等待稳定
LCM_EN_1; //使能端拉高电平
__asm("NOP");
__asm("NOP"); //调用汇编指令延时两个空指令周期,等待稳定
LCM_EN_0; //拉低使能端,执行写入动作
}
//================写命令到LCD子函数================
void LcdWriteCommand(unsigned char CMD,unsigned char Attribc) //写命令CMD到LCD中,Arribc为1时检测忙信号,否则不检测
{
if(Attribc)
WaitForEnable(); //检测忙信号
LCM_RS_0; //选择指令寄存器
LCM_RW_0; //选择写方式
__asm("NOP"); //调用汇编指令延时一个空指令周期,等待稳定
DataPort=CMD; //把命令数据送到数据线上
__asm("NOP"); //调用汇编指令延时一个空指令周期,等待稳定
LCM_EN_1; //使能端拉高电平
__asm("NOP");
__asm("NOP"); //调用汇编指令延时两个空指令周期,等待稳定
LCM_EN_0; //拉低使能端,执行写入动作
}
//================显示光标定位子函数================
void LocateXY(char posx,char posy) //定位位置到地址x列y行
{
unsigned char temp;
temp=posx&0x0f; //屏蔽高4位,限定x座标的范围为0~15
posy&=0x01; //屏蔽高7位,限定y座标的范围为0~1
if(posy)
temp|=0x40; //若要显示的是第二行,则地址码+0x40,因为第二行起始地址为0x40
temp|=0x80; //指令码为地址码+0x80,因为写DDRAM时DB7恒为1(即0x80)
LcdWriteCommand(temp,1); //把temp写入LCD中,检测忙信号
}
//===========显示指定座标的一个字符子函数============
void DisplayOneChar(unsigned char x,unsigned char y,unsigned char Wdata) //在x列y行处显示变量Wdata中的一个字符
{
LocateXY(x,y); //定位要显示的位置
LcdWriteData(Wdata); //将要显示的数据Wdata写入LCD
}
//==========显示指定座标的一串字符子函数===========
void ePutstr(unsigned char x,unsigned char y,unsigned char const *ptr) //在x列y行处显示ptr指向的字符串
{
unsigned char i,j=0;
while(ptr[j]>31)
j++; //ptr[j]>31时为ASCII码,j累加,计算出字符串长度
for(i=0;i<j;i++)
{
DisplayOneChar(x++,y,ptr[i]); //显示单个字符,同时x座标递增
if(x==16)
{
x=0;
y^=1; //当每行显示超过16个字符时换行继续显示
}
}
}
//==================LCD初始化子函数==================
void InitLcd(void)
{
LcdWriteCommand(0x38,0); //8位数据方式,双行显示,5X7字形,不检测忙信号
delay_nms(5); //延时5ms
LcdWriteCommand(0x38,0);
delay_nms(5);
LcdWriteCommand(0x38,0);
delay_nms(5); //重复三次
LcdWriteCommand(0x38,1); //8位数据方式,双行显示,5X7字形,检测忙信号
LcdWriteCommand(0x08,1); //关闭显示,检测忙信号
LcdWriteCommand(0x01,1); //清屏,检测忙信号
LcdWriteCommand(0x06,1); //显示光标右移设置,检测忙信号
LcdWriteCommand(0x0C,1); //打开显示,光标不显示,不闪烁,检测忙信号
}
//================HMC5883初始化===================
void InitHMC5883(void)
{
unsigned char t=0x00;
I2C_Write(0x02,&t,1); //写模式寄存器(地址0x02),设置为连续测量模式(0x00)
}
//==================转换子函数====================
void conversion(unsigned int 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;
}
//==================主函数=====================
void main(void)
{
int x,y;
double angle;
delay_nms(400); //延时400ms等待电源稳定
DDRA=0xff;PORTA=0x00;
DDRB=0xff;PORTB=0x00;
DDRC=0xff;PORTC=0xff;
DDRD=0xff;PORTD=0xff; //初始化I/O口
InitLcd(); //LCD初始化
InitHMC5883(); //HMC5883初始化
ePutstr(0,0,str0); //显示初始字符
while(1)
{
I2C_Read(0x03,BUF,6); //连续读出6组数据,存储在BUF中
x=BUF[0]<<8|BUF[1]; //把X方向合并为十六位数据
y=BUF[4]<<8|BUF[5]; //把Y方向合并为十六位数据
angle=atan2((double)y,(double)x)*(180/3.14159265)+180; // 换算成角度
angle*=10;
conversion((unsigned int)angle); //计算数据和显示
DisplayOneChar(6,0,qian); //显示百位数据
delay_nms(10);
DisplayOneChar(7,0,bai); //显示十位数据
delay_nms(10);
DisplayOneChar(8,0,shi); //显示个位数据
delay_nms(10);
DisplayOneChar(10,0,ge); //显示小数点后一位数据
delay_nms(50); //延时
}
}