Mastering the SiLabs C8051 Microcontroller
|
I2C/SMBUS – HMC5883L Digital Compass
Going through the datasheet of C805F330D, we will not find any I2C hardware present in it. The same applies to the MikroC compiler. However, there is an SMBus hardware present. SMBus and I2C are compatible with each other, the only differences being the maximum bus speed and the timeout feature. Both I2C and SMBus are two-wire interfaces. Thus, whenever we need to hook up I2C devices with our C8051 microcontrollers, we can use SMBus without any issues.
There are tons of articles dedicated to I2C and SMBus protocols and so to know more about them the following links are very informative:
- https://learn.mikroe.com/i2c-everything-need-know
- https://learn.sparkfun.com/tutorials/i2c
- http://www.ti.com/lsds/ti/interface/i2c-overview.page
- http://www.robot-electronics.co.uk/i2c-tutorial
- https://www.i2c-bus.org/i2c-bus
- http://i2c.info
- https://www.totalphase.com/support/articles/200349186-Differences-between-I2C-and-SMBus
- https://www.nxp.com/docs/en/application-note/AN4471.pdf
- https://www.i2c-bus.org/smbus/
Code
HMC5883L.h
#define HMC5883L_READ_ADDR 0x3D
#define HMC5883L_WRITE_ADDR 0x3C
#define Config_Reg_A 0x00
#define Config_Reg_B 0x01
#define Mode_Reg 0x02
#define X_MSB_Reg 0x03
#define X_LSB_Reg 0x04
#define Z_MSB_Reg 0x05
#define Z_LSB_Reg 0x06
#define Y_MSB_Reg 0x07
#define Y_LSB_Reg 0x08
#define Status_Reg 0x09
#define ID_Reg_A 0x0A
#define ID_Reg_B 0x0B
#define ID_Reg_C 0x0C
#define PI 3.142
#define declination_angle -0.5167 // For Uttara, Dhaka, Bangladesh
signed int X_axis = 0;
signed int Y_axis = 0;
signed int Z_axis = 0;
float m_scale = 1.0;
unsigned int make_word(unsigned char HB, unsigned char LB);
void HMC5883L_init(void);
unsigned char HMC5883L_read(unsigned char reg);
void HMC5883L_write(unsigned char reg_address, unsigned char value);
void HMC5883L_read_data(void);
void HMC5883L_scale_axes(void);
void HMC5883L_set_scale(float gauss);
float HMC5883L_heading(void);
HMC5883L.c
#include "HMC5883L.h"
unsigned int make_word(unsigned char HB, unsigned char LB)
{
unsigned int val = 0;
val = HB;
val <<= 8;
val |= LB;
return val;
}
void HMC5883L_init(void)
{
SMBus1_Init(20000);
HMC5883L_write(Config_Reg_A, 0x70);
HMC5883L_write(Config_Reg_B, 0xA0);
HMC5883L_write(Mode_Reg, 0x00);
HMC5883L_set_scale(1.3);
}
unsigned char HMC5883L_read(unsigned char reg)
{
unsigned char val = 0;
SMBus1_Start();
SMBus1_Write(HMC5883L_WRITE_ADDR);
SMBus1_Write(reg);
SMBus1_Repeated_Start();
SMBus1_Write(HMC5883L_READ_ADDR);
val = SMBus1_Read(0);
SMBus1_Stop();
return(val);
}
void HMC5883L_write(unsigned char reg_address, unsigned char value)
{
SMBus1_Start();
SMBus1_Write(HMC5883L_WRITE_ADDR);
SMBus1_Write(reg_address);
SMBus1_Write(value);
SMBus1_Stop();
}
void HMC5883L_read_data(void)
{
unsigned char lsb = 0;
unsigned char msb = 0;
SMBus1_Start();
SMBus1_Write(HMC5883L_WRITE_ADDR);
SMBus1_Write(X_MSB_Reg);
SMBus1_Repeated_Start();
SMBus1_Write(HMC5883L_READ_ADDR);
msb = SMBus1_Read(1);
lsb = SMBus1_Read(1);
X_axis = make_word(msb, lsb);
msb = SMBus1_Read(1);
lsb = SMBus1_Read(1);
Z_axis = make_word(msb, lsb);
msb = SMBus1_Read(1);
lsb = SMBus1_Read(0);
Y_axis = make_word(msb, lsb);
SMBus1_Stop();
}
void HMC5883L_scale_axes(void)
{
X_axis *= m_scale;
Z_axis *= m_scale;
Y_Axis *= m_scale;
}
void HMC5883L_set_scale(float gauss)
{
unsigned char value = 0;
if(gauss == 0.88)
{
value = 0x00;
m_scale = 0.73;
}
else if(gauss == 1.3)
{
value = 0x01;
m_scale = 0.92;
}
else if(gauss == 1.9)
{
value = 0x02;
m_scale = 1.22;
}
else if(gauss == 2.5)
{
value = 0x03;
m_scale = 1.52;
}
else if(gauss == 4.0)
{
value = 0x04;
m_scale = 2.27;
}
else if(gauss == 4.7)
{
value = 0x05;
m_scale = 2.56;
}
else if(gauss == 5.6)
{
value = 0x06;
m_scale = 3.03;
}
else if(gauss == 8.1)
{
value = 0x07;
m_scale = 4.35;
}
value <<= 5;
HMC5883L_write(Config_Reg_B, value);
}
float HMC5883L_heading(void)
{
float heading = 0.0;
HMC5883L_read_data();
HMC5883L_scale_axes();
heading = atan2(Y_axis, X_axis);
heading += declination_angle;
if(heading < 0.0)
{
heading += (2.0 * PI);
}
if(heading > (2.0 * PI))
{
heading -= (2.0 * PI);
}
heading *= (180.0 / PI);
return heading;
}
main.c
#include "HMC5883L.c"
#define LED_DOUT P1_6_bit
#define LED_CLK P1_5_bit
#define LED_LATCH P1_7_bit
unsigned char i = 0;
signed int h = 0;
unsigned char val = 0;
const unsigned char code segment_code[13] =
{
0xC0, // 0
0xF9, // 1
0xA4, // 2
0xB0, // 3
0x99, // 4
0x92, // 5
0x82, // 6
0xF8, // 7
0x80, // 8
0x90, // 9
0x7F, // .
0xBF, // -
0x9C // deg
};
const unsigned char code display_pos[4] =
{
0xF7, //1st Display
0xFB, //2nd Display
0xFD, //3rd Display
0xFE //4th Display
};
void PCA_Init(void);
void SMBus_Init(void);
void Timer_Init(void);
void Port_IO_Init(void);
void Oscillator_Init(void);
void Interrupts_Init(void);
void Init_Device(void);
void write_74HC595(unsigned char send_data);
void segment_write(unsigned char disp, unsigned char pos);
void Timer_ISR()
iv IVT_ADDR_ET3
ilevel 0
ics ICS_AUTO
{
switch(i)
{
case 0:
{
val = (h / 100);
break;
}
case 1:
{
val = ((h % 100) / 10);
break;
}
case 2:
{
val = (h % 10);
break;
}
case 3:
{
val = 12;
break;
}
}
segment_write(val, i);
i++;
if(i > 3)
{
i = 0;
}
TMR3CN &= 0x7F;
}
void main(void)
{
Init_Device();
while(1)
{
h = HMC5883L_heading();
delay_ms(200);
}
}
void PCA_Init(void)
{
PCA0MD &= ~0x40;
PCA0MD = 0x00;
}
void SMBus_Init(void)
{
SMB0CF = 0x80;
}
void Timer_Init(void)
{
TMR3CN = 0x04;
TMR3RLL = 0x02;
TMR3RLH = 0xFC;
}
void Port_IO_Init(void)
{
// P0.0 - SDA (SMBus), Open-Drain, Digital
// P0.1 - SCL (SMBus), Open-Drain, Digital
// P0.2 - Unassigned, Open-Drain, Digital
// P0.3 - Unassigned, Open-Drain, Digital
// P0.4 - Unassigned, Open-Drain, Digital
// P0.5 - Unassigned, Open-Drain, Digital
// P0.6 - Unassigned, Open-Drain, Digital
// P0.7 - Unassigned, Open-Drain, Digital
// P1.0 - Unassigned, Open-Drain, Digital
// P1.1 - Unassigned, Open-Drain, Digital
// P1.2 - Unassigned, Open-Drain, Digital
// P1.3 - Unassigned, Open-Drain, Digital
// P1.4 - Unassigned, Open-Drain, Digital
// P1.5 - Skipped, Push-Pull, Digital
// P1.6 - Skipped, Push-Pull, Digital
// P1.7 - Skipped, Push-Pull, Digital
P1MDOUT = 0xE0;
P1SKIP = 0xE0;
XBR0 = 0x04;
XBR1 = 0x40;
}
void Oscillator_Init(void)
{
OSCICN = 0x82;
}
void Interrupts_Init(void)
{
IE = 0x80;
EIE1 = 0x80;
}
void Init_Device(void)
{
PCA_Init();
SMBus_Init();
Timer_Init();
Port_IO_Init();
Oscillator_Init();
Interrupts_Init();
HMC5883L_init();
}
void write_74HC595(unsigned char send_data)
{
signed char clks = 8;
while(clks > 0)
{
if((send_data & 0x80) == 0x00)
{
LED_DOUT = 0;
}
else
{
LED_DOUT = 1;
}
LED_CLK = 0;
send_data <<= 1;
clks--;
LED_CLK = 1;
}
}
void segment_write(unsigned char disp, unsigned char pos)
{
LED_LATCH = 0;
write_74HC595(segment_code[disp]);
write_74HC595(display_pos[pos]);
LED_LATCH = 1;
}
Schematic
Explanation
HMC5883L is a popular digital geomagnetic sensor or simply compass sensor that uses the I2C bus interface for communication. Since I2C and SMBUS are similar in terms of working principle, we can use the SMBUS interface to hook up this sensor with C8051F330. Shown below is the basic interfacing diagram:
To be able to successfully use SMBUS/I2C interface, we have to code according to the instructions given in the device datasheet. We have to be specifically attentive to I2C bus read and write operations because these are the operations that are used for data transactions. Generally, these operations do not vary from device to device.
I2C/SMBUS read process can be simply defined by the following functions and flow diagram. In our case with the HMC5883L digital compass, we have to begin communication by initiating a “Start” condition in the bus. This is followed by providing the 7-bit I2C slave address of the compass along with the write bit. If things are sent without any issue, the slave would issue an acknowledgement. It is not mandatory to check acknowledgement at this time. Acknowledgement is only needed to be checked when data is read but we have to specify which register is required to be read. Before actually reading the register, we want to read the I2C bus is restarted along with the 7-bit I2C slave address of the compass with the read bit attached to it.
unsigned char HMC5883L_read(unsigned char reg)
{
unsigned char val = 0;
SMBus1_Start();
SMBus1_Write(HMC5883L_WRITE_ADDR);
SMBus1_Write(reg);
SMBus1_Repeated_Start();
SMBus1_Write(HMC5883L_READ_ADDR);
val = SMBus1_Read(0);
SMBus1_Stop();
return(val);
}
The writing process is yet simpler all we have to do is to specify the register we wish to write followed by the value we want to write.
void HMC5883L_write(unsigned char reg_address, unsigned char value)
{
SMBus1_Start();
SMBus1_Write(HMC5883L_WRITE_ADDR);
SMBus1_Write(reg_address);
SMBus1_Write(value);
SMBus1_Stop();
}
Once we establish these two read-and-write processes, we are left with coding the compass as per the datasheet. This enables us to use the compass for real-world applications.
Demo
|
A valid alternative to Silab´s development board: http://www.while1.eu/arduone/arduone.html
Overall, I thoroughly enjoyed your article and found it highly informative, thanks for sharing.
Thanks for the feedback….