PIC16F C code for the HMC6352 Digital Compass

Have you got the greatest 48 bit multiplier ever conceived? Prove it - post your code here.

Moderator: phalanx

Post Reply
waltr
Support Volunteer
Posts: 2823
Joined: Tue Sep 08, 2009 12:07 pm
Location: Philadelphia, USA

PIC16F C code for the HMC6352 Digital Compass

Post by waltr » Mon Jun 21, 2010 6:03 pm

Here is C code for the 16F Mid-range PICs using the MSSP module in I2C mode. Include the Mag_I2C.H in your file with the main function. Add both the Mag_I2C.h and the Mag_I2C.c file to the project.
This was written for the HiTech C compiler but can be easily ported to other compilers by changing any register names and register bit names to the ones defined in your compiler's header files. For some care it should also port to many of the PIC18F processors.

This code is free for any one to use and modify. Enjoy.

Code: Select all

/*******************************************************************
*
* Architecture	Midrange PIC
*	Compiler	Hi-Tech PICC v9.65
*
********************************************************************/
//#define FOSC         (19660800L)	// define external clock frequency
#define FOSC         (10000000L)
#define i2c_bus_rate (100000L)		//

void delay_c ( int a);
void delay_6ms ( int a);
//===============================================================
// I2C Functions Prototypes
//---------------------------------------------------------------
//
void I2C_init( void);
void IdleI2C( void);
void StartI2C( void);
void StartI2C( void);
void RestartI2C( void);
void WriteI2C( unsigned char DataByte );
unsigned char ReadI2C( void);
void StopI2C( void);
void AckI2C( void);
void NotAckI2C( void);
unsigned char DataRdyI2C( void);

unsigned char EEByteWriteI2C( unsigned char control, unsigned char address, unsigned char data);
unsigned char EEByteReadI2C( unsigned char ControlByte, unsigned char address);

unsigned char MagCommandWriteI2C( unsigned char command, unsigned char data );
unsigned char MagByteWriteI2C( unsigned char command, unsigned char data );
unsigned char MagByteReadI2C( unsigned char command, unsigned char address);
int MagGetHeadingI2C( void);

Code: Select all

/*******************************************************************
*
* Architecture	Midrange PIC
*	Compiler	Hi-Tech PICC v9.65
*
********************************************************************/

#include <htc.h>
#include "mag_i2c.h"

// 5.12 usec per count @ 20MHz Fosc
void delay_c ( int a) {		
	int i;
	for (i=0; i<a; i++) {
		NOP();
	}	
}
void delay_6ms ( int a) {		
	int i;
	for (i=0; i<a; i++) {
		NOP();
		delay_c(70);
	}	
}
//===============================================================
// I2C functions
//---------------------------------------------------------------
//

void I2C_init( void) {
	TRISC |= 0b00011000;	// ensure SDA & SLC pins are set to input
	SSPIF = 0;
	SSPSTAT = 0x00;			// clear all status bits
	SMP = 0;				// SMP 0 = Slew for 400kHz, 1 = 100k
	CKE = 1;				// CKE;  0 = 5V logic level; 1 = 3.3 V logic
	SSPCON = 0x28;			// Enable SSP port, IIC Master
	CKP = 1;				// Enable I2C clock in SSPCON
	SSPCON2 = 0x00;			// clear all state bits
	//SSPADD = 0x18;			// 100kz
	SSPADD = (( FOSC / (4 * i2c_bus_rate) )) - 1; // initialize i2c bus rate
}
void IdleI2C( void) {			// Idle check, wait for buss to go to Idle state
	while (RW == 1);					// SSPSTAT:2 wait for transmit to finish
	while ((SSPCON2 & 0x01F) != 0) ;	// wait for buss not busy
}
void StartI2C( void) {
	SEN = 1;				// SSPCON2:0 initiate I2C START condition
	while (SEN == 1);		// wait until START bit finishes
}
void RestartI2C( void) {
	RSEN = 1;				// SSPCON2:1 Repeated Start
	while (RSEN);
}
void WriteI2C( unsigned char DataByte ){
	SSPBUF = DataByte;
	while (RW == 1);
}
unsigned char ReadI2C( void) {
	RCEN = 1;					// SSPCON2:3 Receive enable
	while (RCEN == 1);
	return SSPBUF;
}
void StopI2C( void) {
	PEN = 1;				// SSPCON2:2 initiate I2C STOP condition
	while (PEN);
}
void AckI2C( void) {
	ACKDT = 0;				// SSPCON2:5 set to ACK
	ACKEN = 1;				// SSPCON2:4  initiate I2C ACK condition
	while ( ACKEN);    			// wait until ACK sequence is over 
}
void NotAckI2C( void) {
	ACKDT = 1;				// initiate I2C notACK condition
	ACKEN = 1;
	while ( ACKEN);    			// wait until ACK sequence is over 
}
unsigned char IsACKI2C( void) {
	if (ACKSTAT == 0) {
		return 1;			// ACK detected
	} else {
		return 0;			// ACK not detected
	}
}
unsigned char DataRdyI2C( void) {
	unsigned char a;
	
	return a;
}

//
void Test_24C01( void) {
	
}

//---------------------------------------------------------------
// I2C EEPROM functions
//
//************************************************************************
//     Function Name:    EEByteWriteI2C                                  
//     Parameters:       EE memory ControlByte, address and data         
//     Description:      Writes data one byte at a time to I2C EE        
//                       device. This routine can be used for any I2C    
//                       EE memory device, which only uses 1 byte of     
//                       address data as in the 24LC01B/02B/04B/08B/16B. 
//                                                                      
//************************************************************************
unsigned char EEByteWriteI2C(unsigned char control, unsigned char address, unsigned char data ) {
	IdleI2C();						// ensure module is idle
	StartI2C();						// initiate START condition
	WriteI2C( control);				// write 1 byte - R/W bit should be 0
	IdleI2C();						// ensure module is idle
	WriteI2C( address);				// write address byte to EEPROM
	IdleI2C();						// ensure module is idle
	WriteI2C ( data);				// Write data byte to EEPROM
	IdleI2C();						// ensure module is idle
	StopI2C();						// send STOP condition
	return ( 0 );					// return with no error
}
//************************************************************************
//     Function Name:    EEByteReadI2C                               
//     Parameters:       EE memory ControlByte, address.             
//     Description:      Reads data one byte at a time from I2C EE memory 
//                       device. This routine can be used for any I2C
//                       EE memory device, which only uses 1 byte of 
//                      address data as in the 24LC01B/02B/04B/08B. 
//                                                                   
//************************************************************************
unsigned char EEByteReadI2C( unsigned char ControlByte, unsigned char address)
{
	unsigned char data;
	IdleI2C();						// ensure module is idle
	StartI2C();                     // initiate START condition
	WriteI2C( ControlByte);			// write 1 byte od address
	IdleI2C();                      // ensure module is idle
	WriteI2C( address);				// WRITE word address to EEPROM
	IdleI2C();                      // ensure module is idle
	RestartI2C();                   // generate I2C bus restart condition
	WriteI2C( ControlByte | 0x01 ); // WRITE 1 byte - R/W bit should be 1 for read
	IdleI2C();                      // ensure module is idle
	data = ReadI2C();
	NotAckI2C();                    // send not ACK condition
	StopI2C();                      // send STOP condition
	return ( data);                 // return with no error
}
	
//---------------------------------------------------------------
// Honeywell HMC6352 Magnetometer
// 100kbps
// 7bit slave address = 0x42 for write operations
// 7bit slave address = 0x43 for read operations
// 10k Ohm pull-ups recommended for 3.0V supply
// 
//---------------------------------------------------------------
// Write command, no data
// Commands:
// 0x53, 'S' - Enter Sleep mode
// 0x57, 'W' - Exit Sleep mode
// 0x4F, 'O' - Update Bridge Offsets (S/R Now)
// 0x43, 'C' - Enter user calibration mode
// 0x45, 'E' - Exit user calibration mode
// 0x4C, 'L' - Save OP Mode to EEPROM

unsigned char MagCommandWriteI2C( unsigned char command, unsigned char data ) {
	IdleI2C();				// ensure module is idle
	StartI2C();				// initiate START condition
	WriteI2C( 0x42);		// write 1 byte - R/W bit should be 0
	IdleI2C();				// ensure module is idle
	WriteI2C( command);		// write address byte to EEPROM
	IdleI2C();				// ensure module is idle
	StopI2C();				// send STOP condition
	return ( 0 ); 
}
//---------------------------------------------------------------
// Write 1 byte
// Commands:
// 0x77, 'w' - write EEPROM
// 0x47, 'G' - write RAM

unsigned char MagByteWriteI2C( unsigned char command, unsigned char data ) {
	IdleI2C();				// ensure module is idle
	StartI2C();				// initiate START condition
	WriteI2C( 0x42);		// write 1 byte - R/W bit should be 0
	IdleI2C();				// ensure module is idle
	WriteI2C( command);		// write address byte to EEPROM
	IdleI2C();				// ensure module is idle
	WriteI2C ( data);		// Write data byte to EEPROM
	IdleI2C();				// ensure module is idle
	StopI2C();				// send STOP condition
	return ( 0 ); 
}
//---------------------------------------------------------------
// Read 1 byte
// commands:
// 0x72, 'r' - Read from EEPROM
// 0x67, 'g' - Read from RAM
// 

unsigned char MagByteReadI2C( unsigned char command, unsigned char address)
{
	unsigned char data;
	IdleI2C();				// ensure module is idle
	StartI2C();				// initiate START condition
	WriteI2C( 0x42);		// I2C address & WRITE
	IdleI2C();				// ensure module is idle
	WriteI2C( command);		// WRITE word address to EEPROM
	IdleI2C();				// ensure module is idle
	WriteI2C( address);		// register or EEPROM address
	StopI2C();
	delay_c(12);			// Response time delay (70us)
	StartI2C();
	WriteI2C( 0x43);		// I2C address & READ 
	IdleI2C();
	data = ReadI2C();		// 
	NotAckI2C();			// send not ACK condition
	StopI2C();				// send STOP condition
	return ( data);			// return 8 bits date
}
//---------------------------------------------------------------
// Get 16 bit Data, Heading is the default
//
int MagGetHeadingI2C( void)
{
	unsigned char data_l = 0, data_m = 0;
	
	IdleI2C();				// ensure module is idle
	StartI2C();				// initiate START condition
	WriteI2C( 0x42);		// I2C address & WRITE
	IdleI2C();				// ensure module is idle
	WriteI2C( 0x41);		// WRITE command 'A' to mag
	StopI2C();
	delay_6ms(40);			// Response time delay
	StartI2C();
	WriteI2C( 0x43);		// I2C address & READ 
	IdleI2C();
	data_l = ReadI2C();					// read LSB data byte
	AckI2C();  							// Ack Slave
	IdleI2C();                      	// ensure module is idle
	data_m = ReadI2C();					// read MSB data byte
	NotAckI2C();                    	// send not ACK condition
	StopI2C();                      	// send STOP condition
	return ( (data_m << 8) + data_l);   // return 16 bits data
}
Sample Usage:

Code: Select all

/*******************************************************************
*
* Architecture	Midrange PIC
*	Processor	16F873
*	Compiler	Hi-Tech PICC v9.65
*
********************************************************************
*	File: P_bot.c
*	Files required:	mag_i2c.h
*					mag_i2c.c
*
********************************************************************
*
*	Description:	I2C interface to MHC6352 Compass
*
********************************************************************
*
;	Port Pin Assignments
;					873/6	874/7
;	RA0/AN0			pin 2	pin 2	Ain		Analog input ch0, 
;	RA1/AN1			pin 3	pin 3	Ain		Analog input ch1	?nu
;	RA2/AN2/Vref-	pin 4	pin 4	Din		
;	RA3/AN3/Vref+	pin 5	pin 5	Ain		Analog input ch3	?nu
;	RA4/T0CLI		pin 6	pin 6	Din		Switch S2
;	RA5/AN4/SS		pin 7	pin 7	Din		SPI slave select (~SS) ?nu -
;
;	RB0/INT			pin 21	pin 33	out		
;	RB1				pin 22	pin 34	out		
;	RB2				pin 23	pin 35	out 	
;	RB3/PGM			pin 24	pin 36	out		
;	RB4				pin 25	pin 37	in		
;	RB5				pin 26	pin 38	in		
;	RB6/PGC			pin 27	pin 39	in		
;	RB7/PGD			pin 28	pin 40	in		
;
;	RC0/T1OSO/T1CLI	pin 11	pin 15	out 	
;	RC1/T1OSI/CCP2	pin 12	pin 16	out		
;	RC2/CCP1		pin 13	pin 17	out		
;	RC3/SCK/SCL		pin 14	pin 18	SSP		SPI SCK- I2C SCL
;	RC4/SDI/SDA		pin 15	pin 23	SSP		SPI SDI- I2C SDA
;	RC5/SDO			pin 16	pin 24	in		SPI SDO ?nu
;	RC6//TX/CK		pin 17	pin 25	UART	Serial TX
;	RC7/RX/DT		pin 18	pin 26	UART	Serial RX
;
;	~MCRL			pin 1	pin 1		Vpp
;	Vdd				pin 20	pin 11&32
;	Vss				pin 8&19    12&31
;	OSC1/CLKIN		pin 9	pin 13
;	OSC2/CLKOUT		pin 10	pin 14
*
*********************************************************************/
#include <htc.h>
#include "mag_i2c.h"

__CONFIG ( HS & WDTDIS & PWRTEN & UNPROTECT & BORDIS & LVPDIS); // defs are in pic1687x.h

// Set the XTAL Frequency in mag_i2c.h

//-----------------------------------------------------------------------------
// Port descriptions
//---------------------------------------------------------------
// PORT A
#define porta_dir_mask	0xff	// AD input, bit 4 = S2
								
//---------------------------------------------------------------
// PORT B Definitions
// 3-0 = output
// 7-4 = input, 
// enable weak pull-ups, OPTION_REG, bit 7 = 0
// interrupt on change enabled, INTCON reg bit 3 = 1
// interrupts on only change of input state, high or low going
#define portb_dir_mask	0xf0 	// 1111 0000b

//---------------------------------------------------------------
// PORT C
#define portc_dir_mask	0xf0	// bits 0, 1, 2, 3 output; 4, 5, 6, 7 input
#define portc_out_mask	0x01	//


//==============================================================================
// Register set-up values
//------------------------------------------------------------------------------
#define	option_init	0x06	// TMR0 prescaler = 1:128, RBUP on
#define	intcon_init	0xE8	//(GIE | T0IE | RBIE | PEIE)
#define cmcon_init	0x07	// comparadors off, PA = I/O
#define t1con_init	0x00	// T1CKPS1 | T1CKPS0		// _T1PRE_8

//---------------------------------------------------------------
// Timer init values
//------------------------------------------------------------------------------
// TMR0 timer and pre-scaler -
//
// timer counts down from set value, interrupts when count reaches zero
// time = (255 - tmr0_cnt) * prescaler * cycle time + interrupt latency
// pre-scale of 2 to 256, (2, 4, 8, 16, 32, 64, 128, 256)
// 
// prescaler = 1:2, set prescaler in option_reg to PS_2
//
// TMR0 prescale = 1:2, OPTION_REG, bits 2-0 = 000
// prescaler assignment = TMR0, OPTION_REG bit 3 = 0
// TMR0 clock source = Instr cycle, OPTION_REG, bit 5 = 0
//
#define	TMR0_cnt	96		// (256 - TMR0)*PRE = #inst cycles
							// = 320; time = 64 us
#define	Sys_tick	0x17	// TMR0 IRQ * Sys_tick = 99msec

//==============================================================================
// 	Flags
//
//==============================================================================

// Define the Time_out flags
// timeout_flags: process that requested the time-out counter services
#define 	unused0_to_f	0		// 
#define 	unused1_to_f	1		//
#define 	Sys_tick_f 		2		// 256 ticks of TMR0 = 51.2 msec
#define 	unused3_to_f	3		// 
#define 	Task_tick_f		4		// 
#define 	unused5_to_f	5		//
#define 	unused6_to_f	6		//
#define 	unused7_to_f	7		//

//======================================================================
// variables
static unsigned char	portB_image;		// port mirror, set/clear bits here then write value to port
static unsigned char	portC_image;		// to prevent any RMW issues

static unsigned char	timeout_flags;
static unsigned char	Sys_tick_counter;	// 2ms per tick per TMR0
static unsigned char	Sys_task_counter;

//===============================================================
// MACROS
#define bitset(var,bitno) ((var) |= 1UL << (bitno))
#define bitclr(var,bitno) ((var) &= ~(1UL << (bitno))
#define bittest(var,bitno) (((var) & 1UL << (bitno)) >> (bitno))	// result in bit 0

#define NOP1() asm( "nop");
//======================================================================
//	Function Prototypes
char ASCII( char);
void init( void);
void LongDelay ( int n);

//---------------------------------------------------------------
//	
char ASCII( char a) {
	char b;
	const char convASCII[16] = {
		0x30,	// 0
		0x31,	// 1
		0x32,	// 2
		0x33,	// 3
		0x34,	// 4
		0x35,	// 5
		0x36,	// 6
		0x37,	// 7
		0x38,	// 8
		0x39,	// 9
		0x41,	// A
		0x42,	// B
		0x43,	// C
		0x44,	// D
		0x45,	// E
		0x46	// F
	};
	a = a & 0x0F;
	b = convASCII[a];
	return b;
}

//================================================================
//	ISRs
void interrupt isr(void){
	unsigned char a;

//----------------------------------------------------------------
// Fosc = 20MHz
// Tcyc = 200ns
// prescaler = 1:128 => 4.096msec****
// prescaler = 1:64 => 2.048msec
// prescaler = 1:32 => 1.024msec

	if(T0IE && T0IF){ // every 4.096msec
		T0IF = 0;
		TMR0 = TMR0_cnt;
		
		if (Sys_tick_counter-- == 0) {
			bitset( timeout_flags, Sys_tick_f);
		}
	}
}	// End ISRs

//===============================================================

//===============================================================
//	MAIN
//---------------------------------------------------------------
void main(void){

	int mag_data;
	unsigned char a, addr;
	
	init();		// all ports and used periphials
	I2C_init();
	
	bitclr(timeout_flags, Sys_tick_f);
	Sys_tick_counter = Sys_tick;				// for 99msec

	LongDelay(2);
	
	INTCON = intcon_init;	// turn on interrupts

	addr = 0x08;
	
	while(1) {		// Main Loop

		if (bittest(timeout_flags, Sys_tick_f) == 1) {	// 99ms tasks
			Sys_tick_counter = Sys_tick;
			Sys_task_counter++;
			if (Sys_task_counter > 19) {				// 2 second tasks
				Sys_task_counter = 0;
				
				a = MagByteReadI2C( 'r', addr);
				// SendByte (a);				// output compass register value
				mag_data = MagGetHeadingI2C();
				// SendWord( mag_data);			// output compass heading value
				LongDelay( 2);

			}
		}
	}
}


//---------------------------------------------------------------
// Init Functions

void init(void){
	PORTA 	= 0;
	PORTB	= 0;
	PORTC	= 0;
	portB_image = 0;
	
	PORTC = portC_image;
	portC_image = 0;
	TRISA	= porta_dir_mask;		// Set PORTA as input
	TRISB	= portb_dir_mask;		// Set PORTB bits 0-3 in output mode
	TRISC	= portc_dir_mask;

	//OPTION	= PS_64;	// divide by 64 prescaler
	OPTION	= option_init;		
	TMR0	= TMR0_cnt; 		// Timer 0 count
	T1CON 	= t1con_init;
}
//---------------------------------------------------------------
// 1.9ms per n
void LongDelay ( int n) {
	int i, j;
	for( i=0; i<n;i++) {
		for( j=0; j<200; j++) {
			NOP();
		}
	}
}
// ------ eof ----------------------------


User avatar
SFE-Mike
SFE Guru
Posts: 75
Joined: Fri May 21, 2010 10:53 am

Re: PIC16F C code for the HMC6352 Digital Compass

Post by SFE-Mike » Wed Jun 23, 2010 5:06 pm

Great work! Thanks very much for posting this, I'm sure it will help a lot of people get this device working. I'm personally in the PIC camp so I know I'll use it myself at some point. Kudos!
-Mike, SparkFun Electronics
"This is only temporary. Unless it works!" -Red Green

Note: To get tech support from SparkFun, please email techsupport@sparkfun.com. SFE tech support doesn't constantly monitor the forums.

waltr
Support Volunteer
Posts: 2823
Joined: Tue Sep 08, 2009 12:07 pm
Location: Philadelphia, USA

Re: PIC16F C code for the HMC6352 Digital Compass

Post by waltr » Wed Jun 23, 2010 5:53 pm

Thanks Mike.
Maybe throw a link on the HMC6352 product page to this thread would be helpful.

jbird468
Posts: 1
Joined: Tue Feb 08, 2011 11:04 am

Re: PIC16F C code for the HMC6352 Digital Compass

Post by jbird468 » Tue Feb 08, 2011 11:12 am

@ Mike, I can't really tell which code block is which as far as filenames are concerned. I am trying to interface with a different I2C 3d compass & 3d Acceleromter combo (Polou part # 1250). Your code is the only reference material i have found so far. I am starting to grasp the I2C concept but the general c programming is new to me. I have used java and Microchips assembly.

Well back to soaking up your I2C code.

Thanks!

-James

User avatar
SFE-Mike
SFE Guru
Posts: 75
Joined: Fri May 21, 2010 10:53 am

Re: PIC16F C code for the HMC6352 Digital Compass

Post by SFE-Mike » Tue Feb 08, 2011 3:55 pm

The first block is "mag_i2c.h"
The second block is "mag_i2c.c"
The third block is the main code, called "P_bot.c" here, but it can really be named anything you like.

And to be clear, Waltr is the author of the code and may be able to help you out with further questions. Best of luck!
-Mike, SparkFun Electronics
"This is only temporary. Unless it works!" -Red Green

Note: To get tech support from SparkFun, please email techsupport@sparkfun.com. SFE tech support doesn't constantly monitor the forums.

waltr
Support Volunteer
Posts: 2823
Joined: Tue Sep 08, 2009 12:07 pm
Location: Philadelphia, USA

Re: PIC16F C code for the HMC6352 Digital Compass

Post by waltr » Tue Feb 08, 2011 6:18 pm

Hi James,
Yes, I posted that code since a lot of people were having difficulty getting a PIC to talk to that Compass chip.

I see I didn't give the file names directly so they are:

1st code block is mag_i2c.h the header file.
2nd is mag_i2c.c the C code for running I2C and includes the I2C primitives and the functions to interface with the HMC6352 and EEPROMs.
3rd is P_bot.c and is a stripped down version of the code for my Bot that gives the usage of the I2C code. If you look in the comments at the beginning of this file you'll see:

Code: Select all

/*******************************************************************
*
* Architecture   Midrange PIC
*   Processor   16F873
*   Compiler   Hi-Tech PICC v9.65
*
********************************************************************
*   File: P_bot.c
*   Files required:   mag_i2c.h
*               mag_i2c.c
*
********************************************************************
Which gives the files required in the 'project'.

For a different I2C device add functions to the code in the mag_i2c.c then add the function prototypes to mag_i2c.h.
And of course, you may re-name these if you wish and delete un-need sections. Just do it on a copy so you can go back to the original code if needed.

Good luck and please let me how these work out for you.
Walt

Post Reply