常用的地磁傳感器主要有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); //延時
}
}