2017年9月20日 星期三

Manipulating I2C Peripheral Directly in Linux User Space


      I2C (I Squared C) is a very common interface for controlling peripheral chip, lots sensor are based on this interface. Theoretically, it is necessary to write a driver for communicating with peripheral chip via I2C (it is typical bus driver). However, if the peripheral chip is specified for a application using, to understand the Linux driver model is a futile work but benefit is low. The driver model is a huge knowledge, it needs lots reading/searching/practicing to make the written driver stable.

     This post tries to resolve the pain : Someone could manipulate the peripheral chip but know nothing about Linux driver model.


   SHT20 :

      SHT10 is a very famous temperature and humidity sensor, it features high precision (with extravagant price 😮) for industrial usage. For the manufacturer has ploughed maker's market, the example code for SHT10 is very common. However, SHT10 is not a strict I2C peripheral device, I rather adopt SHT20 as example. SHT20 is easy to be obtained via Alibaba.

   零.  Install the necessary i2c  kernel modules and tool , and to configure it to be usable.
My development board is OpenWRT, to install those, the most easy way is to via opkg commands:


opkg update
opkg install i2c-tools
opkg install kmod-i2c-gpio-custom kmod-i2c-core

     After the installation has been done, it is time to load the i2c kernel module and to configure which pin would be serial data(SDA) and which would be serial clock (SCL). below is the cirecuit schematic of my OpenWRT,  I adopt GPIO 20 as SDA, and GPIO 19 as SCL, and I set this i2c device(SHT20) number is 0.


All the pins are able to be for I2C purpose (including SDA and SCL). Why I chose  pin 20/19 as SDA/SCL is only for it is convenient in wiring (with the sensor).

The command for configuration the setting is :
insmod i2c-dev
insmod i2c-gpio-custom bus0=0,20,19 #<ID>,<SDA>,<SCL>




And the i2c would reveal under folder /dev :


root@GL-AR150:~# ls /dev/i2c-0 
/dev/i2c-0

But once you reboot your development board, the i2c would disappear, it is necessary to re-configure again (from insmod mode command). The way to configurate i2c once and for all is to add a file under folder /etc/modules.d/. The file is essentially script but the name could be of your preference.  For me, the file name is i2c,  the content is

root@GL-AR150:~# cat /etc/modules.d/i2c 
i2c-dev
i2c-gpio-custom bus0=0,20,19

That is the commands I typed but leaves out "inmod".

The files under the /etc/modules.d/ would be run one by one to load the kernel modules (drivers).

If you want to configurate multi i2c device, the commands should be (take 2 as example)

insmod i2c-gpio-custom bus0=0,20,19 bus1=1,18,21
 
And the autoload module script content in /etc/modules.d/i2c is :
 
i2c-gpio-custom bus0=0,20,19 bus1=1,18,21

i2c-gpio-custom is a kernel module, you could not insert that into kernel twice.




 一. Check the SHT20 has connected to the linux device:
The tool has been prepared in most busybox especially the okpg has installed it. Type the following command to probe if there device under i2c-0 :


root@GL-AR150:~# i2cdetect 0
WARNING! This program can confuse your I2C bus, cause data loss and worse!
I will probe file /dev/i2c-0.
I will probe address range 0x03-0x77.
Continue? [Y/n] y
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: 40 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --   


There report means there is a i2c slave in the address 0x40, and refer to SHT20 datasheet, The default SHT20 i2c address is 0x40 indeedly  : in SHT20 datasheet page 7/4, session 5.3 : After  sending  the  Start condition,  the  subsequent  I2C header consists of the 7bit I2C device address ‘1000’000’ and an SDA direction bit (Read R: ‘1’, Write W: ‘0’). the value 0b0100 000 in binary is 0x40 in heximal.

 二. Prepare a header for i2c manipulations.



      Header i2c-dev.h is a header for application to manipulation the i2c peripheral  in the form of /dev/i2c-X, the application could access i2c peripheral without specified device-driver support. I do not write the SHT20 driver, so it is necessary for me.

     In OpenWRT (maybe for  Raspberry pi is the same), the i2c-dev.h is not complete: in the OpenWRT, the header is like this, compared with the complete version, the inline functions do not exist. Thus it is requisite  to replenish the header, I name the header as openwrt_i2c-dev.h.


#ifndef _OPENWRT_I2C_DEV_H_
#define _OPENWRT_I2C_DEV_H_

#include <linux/types.h>
#include <linux/i2c.h>

#include <sys/ioctl.h>


#ifdef __cplusplus
extern "C" {
#endif

static inline __s32 i2c_smbus_access(int file, char read_write, __u8 command, 
                                     int size, union i2c_smbus_data *data)
{
 struct i2c_smbus_ioctl_data args;

 args.read_write = read_write;
 args.command = command;
 args.size = size;
 args.data = data;
 return ioctl(file,I2C_SMBUS,&args);
}


static inline __s32 i2c_smbus_write_quick(int file, __u8 value)
{
 return i2c_smbus_access(file,value,0,I2C_SMBUS_QUICK,NULL);
}
 
static inline __s32 i2c_smbus_read_byte(int file)
{
 union i2c_smbus_data data;
 if (i2c_smbus_access(file,I2C_SMBUS_READ,0,I2C_SMBUS_BYTE,&data))
  return -1;
 else
  return 0x0FF & data.byte;
}

static inline __s32 i2c_smbus_write_byte(int file, __u8 value)
{
 return i2c_smbus_access(file,I2C_SMBUS_WRITE,value,
                         I2C_SMBUS_BYTE,NULL);
}

static inline __s32 i2c_smbus_read_byte_data(int file, __u8 command)
{
 union i2c_smbus_data data;
 if (i2c_smbus_access(file,I2C_SMBUS_READ,command,
                      I2C_SMBUS_BYTE_DATA,&data))
  return -1;
 else
  return 0x0FF & data.byte;
}

static inline __s32 i2c_smbus_write_byte_data(int file, __u8 command, 
                                              __u8 value)
{
 union i2c_smbus_data data;
 data.byte = value;
 return i2c_smbus_access(file,I2C_SMBUS_WRITE,command,
                         I2C_SMBUS_BYTE_DATA, &data);
}

static inline __s32 i2c_smbus_read_word_data(int file, __u8 command)
{
 union i2c_smbus_data data;
 if (i2c_smbus_access(file,I2C_SMBUS_READ,command,
                      I2C_SMBUS_WORD_DATA,&data))
  return -1;
 else
  return 0x0FFFF & data.word;
}

static inline __s32 i2c_smbus_write_word_data(int file, __u8 command, 
                                              __u16 value)
{
 union i2c_smbus_data data;
 data.word = value;
 return i2c_smbus_access(file,I2C_SMBUS_WRITE,command,
                         I2C_SMBUS_WORD_DATA, &data);
}

static inline __s32 i2c_smbus_process_call(int file, __u8 command, __u16 value)
{
 union i2c_smbus_data data;
 data.word = value;
 if (i2c_smbus_access(file,I2C_SMBUS_WRITE,command,
                      I2C_SMBUS_PROC_CALL,&data))
  return -1;
 else
  return 0x0FFFF & data.word;
}


/* Returns the number of read bytes */
static inline __s32 i2c_smbus_read_block_data(int file, __u8 command, 
                                              __u8 *values)
{
 union i2c_smbus_data data;
 int i;
 if (i2c_smbus_access(file,I2C_SMBUS_READ,command,
                      I2C_SMBUS_BLOCK_DATA,&data))
  return -1;
 else {
  for (i = 1; i <= data.block[0]; i++)
   values[i-1] = data.block[i];
   return data.block[0];
 }
}

static inline __s32 i2c_smbus_write_block_data(int file, __u8 command, 
                                               __u8 length, __u8 *values)
{
 union i2c_smbus_data data;
 int i;
 if (length > 32)
  length = 32;
 for (i = 1; i <= length; i++)
  data.block[i] = values[i-1];
 data.block[0] = length;
 return i2c_smbus_access(file,I2C_SMBUS_WRITE,command,
                         I2C_SMBUS_BLOCK_DATA, &data);
}

static inline __s32 i2c_smbus_write_i2c_block_data(int file, __u8 command,
                                               __u8 length, __u8 *values)
{
 union i2c_smbus_data data;
 int i;
 if (length > 32)
  length = 32;
 for (i = 1; i <= length; i++)
  data.block[i] = values[i-1];
 data.block[0] = length;
 return i2c_smbus_access(file,I2C_SMBUS_WRITE,command,
                         I2C_SMBUS_I2C_BLOCK_DATA, &data);
}

#ifdef __cplusplus
}
#endif

#endif/*_OPENWRT_I2C_DEV_H_*/



三. The application for fetch data from SHT20.

The code be :


#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>

#include <unistd.h>

#include <sys/time.h>

#include <fcntl.h>
#include <sys/ioctl.h>

#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <errno.h>


#include "openwrt_i2c-dev.h"

#define MAX_STR_LEN     (256)


int SeekI2CAddress(const char *p_device_name, int *p_i2c_device_address)
{
 int i;
 int fd;

 fd = open(p_device_name, O_RDWR);
 if(0 > fd)
 {
  printf("%s could not been open\r\n", p_device_name);
  return -1;
 }

 for(i = 3; i< 128; i++){
  int status;
  status = ioctl(fd, I2C_SLAVE, (void*)(intptr_t)i);

  if (0 > status)
  {
   if (errno == EBUSY) {
    printf("UU ");
    continue;
   }
   printf("can't set address to 0x%02x\r\n", i);
   return -2;
  }/*if */

  if ((i >= 0x30 && i <= 0x37) || (i >= 0x50 && i <= 0x5F))
  {
   status = i2c_smbus_read_byte(fd);
  }
  else
  {
   status = i2c_smbus_write_quick(fd, I2C_SMBUS_WRITE);
  }

  if (status >= 0)
  {
   *p_i2c_device_address = i;
   return 1;
  }
 }/*for i*/

 printf("no valid address has been found\r\n");
 *p_i2c_device_address = -1;

 return 0;
}/*SeekI2CAddress*/


void delay_micro_sec(unsigned int delay_time_in_us)
{
 struct timeval now;
 struct timeval period;
 struct timeval end;

 gettimeofday(&now, NULL);

 period.tv_sec = delay_time_in_us / 1000000;
 period.tv_usec = delay_time_in_us % 1000000;

 timeradd(&now, &period, &end);

 while(timercmp(&now, &end, < ))
  gettimeofday(&now, NULL);

}/*delay_micro_sec*/

#define SHT20_I2C_DEFAULT_ADDRESS     (0x40) /*1000 000*/

#define SHT20_WRITE_USER_REGISTER     (0xe6)
#define SHT20_READ_USER_REGISTER     (0xe7)
#define SHR21_SOFT_RESET       (0xfe)

#define FILTER_OUT_LOWEST_2_BIT(XX)     (~0x3 & (XX))

#if(0)
uint8_t ComputeCRC(uint8_t *p_data, uint32_t len)
{
 uint8_t crc;
 int32_t i;
 //calculates 8- Bit checksum with given polynomial

 crc = 0x00;
 for (i = 0; i < len; i++){
  int32_t j;
  crc ^= p_data[i];

  for(j = 8; j > 0; j--){
#define CRC_POLY        (0x131)

   if (crc & 0x80)
    crc = (crc << 1) ^ CRC_POLY;
   else
    crc <<= 1;
  }
 }

 return crc;
}/*ComputeCRC*/
#endif

int ReadSHT20(char *p_device_name, int32_t resolution_level,
 float *p_temperature, float *p_humidity)
{
 int fd;
 int i2c_address;
 uint8_t temp[2];
 float temperature;
 float humidity;

 if(0 >= SeekI2CAddress((const char*)p_device_name, &i2c_address))
 {
  printf("No i2c device has been found\r\n");
  return -2;
 }

 if(SHT20_I2C_DEFAULT_ADDRESS != i2c_address)
 {
  printf("i2c_address is not 0x%02x, it is = 0x%02x\r\n",
   SHT20_I2C_DEFAULT_ADDRESS, i2c_address);
  return -3;
 }/*SHT20_I2C_DEFAULT_ADDRESS != i2c_address*/


 fd = open(p_device_name, O_RDWR);

 if(0 > ioctl(fd, I2C_SLAVE, SHT20_I2C_DEFAULT_ADDRESS))
 {
  close(fd);
  return -4;
 }/*if */


#define SHT20_RESOLUTION_12_14BIT  (0x00) // RH=12bit, T=14bit
#define SHT20_RESOLUTION_8_12BIT  (0x01) // RH= 8bit, T=12bit
#define SHT20_RESOLUTION_10_13BIT  (0x80) // RH=10bit, T=13bit
#define SHT20_RESOLUTION_11_11BIT  (0x81) // RH=11bit, T=11bit

#define SHT20_RESOLUTION_MASK   (0x81)

 {
  unsigned char resolution;

  resolution = 0;
  switch(resolution_level)
  {
  case 1:
   resolution |= SHT20_RESOLUTION_8_12BIT;
   break;
  case 2:
   resolution |= SHT20_RESOLUTION_11_11BIT;
   break;
  case 3:
   resolution |= SHT20_RESOLUTION_10_13BIT;
   break;
  case 4:
  default:
   resolution |= SHT20_RESOLUTION_12_14BIT;
   break;
  }/*switch*/

  resolution &= SHT20_RESOLUTION_MASK;

  i2c_smbus_write_byte_data(fd, SHT20_WRITE_USER_REGISTER, resolution);
 }/*set resolution*/


#define SHT20_TRIGGER_TEMPERATURE_MEASUREMENT  (0xF3)
#define SHT20_TRIGGER_HUMIDITY_MEASUREMENT   (0xF5)

 i2c_smbus_write_byte(fd, SHT20_TRIGGER_TEMPERATURE_MEASUREMENT);
#define SHT20_TEMPERATURE_MEASURE_TIME_IN_MSEC   (85)
 delay_micro_sec(SHT20_TEMPERATURE_MEASURE_TIME_IN_MSEC*1000);

 temp[0] = i2c_smbus_read_byte(fd);
 temp[1] = i2c_smbus_read_byte(fd);
 //crc = i2c_smbus_read_byte(fd);
 //printf("crc = 0x%02x, computedCRC = 0x%02x\r\n",
 // crc, ComputeCRC((uint8_t*)&temp[0], 2));

 {
  uint16_t raw_temperature;
  raw_temperature = ((temp[0]<<8) + FILTER_OUT_LOWEST_2_BIT(temp[1]));
  temperature = -46.85 + (175.72 *raw_temperature) /(float)(1<<16);
 }

 i2c_smbus_write_byte(fd, SHT20_TRIGGER_HUMIDITY_MEASUREMENT);
#define SHT20_HUMIDITY_MEASURE_TIME_IN_MSEC   (29)

 i2c_smbus_write_byte(fd, SHT20_TRIGGER_HUMIDITY_MEASUREMENT);
 delay_micro_sec(SHT20_HUMIDITY_MEASURE_TIME_IN_MSEC*1000);

 temp[0] = i2c_smbus_read_byte(fd);
 temp[1] = i2c_smbus_read_byte(fd);
 //crc = i2c_smbus_read_byte(fd);
 //printf("crc = 0x%02x, computedCRC = 0x%02x\r\n", crc, ComputeCRC(&temp[0], 2));

 {
  uint16_t raw_humidity;
  raw_humidity = ((temp[0]<<8) + temp[1]) & (~0x03);
  humidity  = -6.0 + 125.0*raw_humidity/(float)(1<<16);
 }

 close(fd);

 *p_temperature = temperature;
 *p_humidity = humidity;

 return 0;
}/*ReadSHT20*/


void PrintCurrentTime(void)
{
 time_t t;
 struct tm calendar_time;
 t = time(NULL);
 localtime_r(&t, &calendar_time);

 printf("now time: %d-%d-%d %d:%d:%d, %s(+%d)\n",
  calendar_time.tm_year + 1900, calendar_time.tm_mon + 1,
  calendar_time.tm_mday,
  calendar_time.tm_hour, calendar_time.tm_min, calendar_time.tm_sec,
  calendar_time.tm_zone, (int)calendar_time.tm_gmtoff/3600);
}/*print_current_time*/


int main(int argc, char *argv[])
{
 char i2c_device_name[MAX_STR_LEN];
 int resolution_level;
 float temperature, humidity;

 resolution_level = 4;

 memset(&i2c_device_name[0], 0, MAX_STR_LEN);

 if(argc < 2)
 {
  printf("should be fellow the device location (/dev/i2c-XX)\r\n");
  return -1;
 }

 if(argc > 2)
 {
  char *p_temp;
  resolution_level = strtol(argv[2], &p_temp, 10);

  if(resolution_level > 4 || resolution_level < 1)
  {
   printf("%s should be >= 4 and <= 12 \r\n", argv[2]);
   return -3;
  }
 }

 strncpy(&i2c_device_name[0], argv[1], MAX_STR_LEN);
 while(1)
 {

  ReadSHT20(&i2c_device_name[0], resolution_level, &temperature, &humidity);
  printf("temperature = %8.6f, humidity = %8.6f\r\n", temperature, humidity);
  PrintCurrentTime();
  printf("\r\n");
  usleep(3*1000*1000);
 }

 return 0;
}/*main*/


For SHT20 protocol does not follow strict i2c, I could not get the Cyclic Redundancy Check (CRC) from the i2c operations. 

The makefile is simplem just adopt the cross-compiler(parasitic compiler) to build the binary:


OPENWRT_ROOT=/home/gaiger/openwrt-cc/staging_dir
OPENWRT_TOOLCHAIN_PATH = $(OPENWRT_ROOT)/toolchain-mips_34kc_gcc-4.8-linaro_uClibc-0.9.33.2


CC = $(OPENWRT_TOOLCHAIN_PATH)/bin/mips-openwrt-linux-gcc



CFLAGS := -O2 -Wall
all:
        $(CC)  $(CFLAGS)   main.c  -o sht20_measure
clean:
        rm sht20_measure -f

Run the binary result is :


root@GL-AR150:~# ./sht20_measure /dev/i2c-0 4
temperature = 28.450911, humidity = 57.491821
now time: 2017-9-21 3:4:33, CST(+8)

temperature = 28.450911, humidity = 57.491821
now time: 2017-9-21 3:4:36, CST(+8)

temperature = 28.450911, humidity = 57.491821
now time: 2017-9-21 3:4:39, CST(+8)

temperature = 28.450911, humidity = 57.491821
now time: 2017-9-21 3:4:42, CST(+8)

temperature = 28.450911, humidity = 57.491821
now time: 2017-9-21 3:4:46, CST(+8)

Taiwan is very humid even it is under air-conditioner.

If you interest how to retrive SHT20 CRC, you could refer to the SHT10  driver : it does not rely on i2c.h, the communication is based on self-implemented software i2c ( of the SHT10's specialty ).

GY271, QMC5883L:
  GY271 is  a three-axis magnet sensor in technical term, in common words, it is a electronic compass. It has widely used (but not only)  in flying-drone.
  Originally, GY271 is adopted the chip HMC8831L manufactured by Honeywell, now the company has licensed the intelligence patent for 睿矽 and is stopping to product HMC8831L in today(2017). 睿矽 has modified HMC8831L slightly and rename the chip as QMC8831L. The register addresses of QMC8831L is not the same as HMC8831L's, but the register definition is almost the same.
  My GY271 is QMC5883L version.
The datasheet of QMC5883L is here.

As SHT20 case, I set GPIO pin 20 as SDA, pin 19 as SCL.

一. Check if the wire has connected correctly.
After the QMC5883L has physically connected to the OpenWRT, exploit i2cdetect to check if the connection and soldering is authentic or not:


root@GL-AR150:~# i2cdetect 0
WARNING! This program can confuse your I2C bus, cause data loss and worse!
I will probe file /dev/i2c-0.
I will probe address range 0x03-0x77.
Continue? [Y/n] y
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- 0d -- -- 
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
70: -- -- -- -- -- -- -- --    

Refer to the datasheet page 10, session 5.4, the the phrase : The default I2C address is 0D: 0001101, proves the QMC5883L has connected to the OpenWRT.

the code is :


#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>

#include <unistd.h>

#include <math.h>

#include <sys/time.h>

#include <fcntl.h>
#include <sys/ioctl.h>


#include <signal.h>

#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <errno.h>


#include "openwrt_i2c-dev.h"

#define MAX_STR_LEN     (256)


int SeekI2CAddress(const char *p_device_name, int *p_i2c_device_address)
{
 int i;
 int fd;

 fd = open(p_device_name, O_RDWR);
 if(0 > fd)
 {
  printf("%s could not been open\r\n", p_device_name);
  return -1;
 }

 for(i = 3; i< 128; i++){
  int status;
  status = ioctl(fd, I2C_SLAVE, (void*)(intptr_t)i);

  if (0 > status)
  {
   if (errno == EBUSY) {
    printf("UU ");
    continue;
   }
   printf("can't set address to 0x%02x\r\n", i);
   return -2;
  }/*if */

  if ((i >= 0x30 && i <= 0x37) || (i >= 0x50 && i <= 0x5F))
  {
   status = i2c_smbus_read_byte(fd);
  }
  else
  {
   status = i2c_smbus_write_quick(fd, I2C_SMBUS_WRITE);
  }

  if (status >= 0)
  {
   *p_i2c_device_address = i;
   return 1;
  }
 }/*for i*/

 printf("no valid address has been found\r\n");
 *p_i2c_device_address = -1;

 return 0;
}/*SeekI2CAddress*/


void delay_micro_sec(unsigned int delay_time_in_us)
{
 struct timeval now;
 struct timeval period;
 struct timeval end;

 gettimeofday(&now, NULL);

 period.tv_sec = delay_time_in_us / 1000000;
 period.tv_usec = delay_time_in_us % 1000000;

 timeradd(&now, &period, &end);

 while(timercmp(&now, &end, < ))
  gettimeofday(&now, NULL);

}/*delay_micro_sec*/




enum {
 QMC5883LOutputDataRate10Hz   = 0,
 QMC5883LOutputDataRate50Hz   = 1,
 QMC5883LOutputDataRate100Hz  = 2,
 QMC5883LOutputDataRate300Hz  = 3,
};

enum {
 QMC5883LScaleRange2Gauss   = 0,
 QMC5883LScaleRange8Gauss   = 1,
};
enum {

 QMC5883LOverSampleRatio512   = 0,
 QMC5883LOverSampleRatio256   = 1,
 QMC5883LOverSampleRatio128   = 2,
 QMC5883LOverSampleRatio64   = 3,
};

int InitQMC5883L(char *p_device_name, 
 int output_data_rate, int scale_range, int over_sample_ratio, 
 int *p_fd)
{
 int fd;
 int i2c_address;

 if(0 >= SeekI2CAddress((const char*)p_device_name, &i2c_address))
 {
  printf("No i2c device has been found\r\n");
  return -2;
 }

#define QMC5883L_I2C_DEFAULT_ADDRESS     (0x0D)
 if(QMC5883L_I2C_DEFAULT_ADDRESS != i2c_address)
 {
  printf("i2c_address is not 0x%02x, it is = 0x%02x\r\n",
   QMC5883L_I2C_DEFAULT_ADDRESS, i2c_address);
  return -3;
 }/*QMC5883L_I2C_DEFAULT_ADDRESS != i2c_address*/


 fd = open(p_device_name, O_RDWR);

 if(0 > ioctl(fd, I2C_SLAVE, QMC5883L_I2C_DEFAULT_ADDRESS))
 {
  close(fd);
  return -4;
 }/*if */


#define QMC5883L_RESET_PERIOD_REGISTER    (0x0B)

#define QMC5883L_RESET         (0x01)

 i2c_smbus_write_byte_data(fd, QMC5883L_RESET_PERIOD_REGISTER, QMC5883L_RESET);



#define QMC5883L_CONTROL_REGISTER_1     (0x09)
#define QMC5883L_CONTROL_REGISTER_2     (0x0A)


#define QMC5883L_CONTINUOUS_MODE     ((0x01)<< 0)
#define QMC5883L_STANDBY_MODE      ((0x00)<< 0)
#define QMC5883L_OUTPUT_DATA_RATE_50HZ    ((0x01)<< 2)
#define QMC5883L_RANGE_2_GAUSS      ((0x00)<< 4)
#define QMC5883L_OVER_SAMPLE_RATIO_512    ((0x00)<< 6)

 unsigned char value_for_register1;

 //value_for_register1 = QMC5883L_CONTINUOUS_MODE | QMC5883L_OUTPUT_DATA_RATE_50HZ;
 //value_for_register1 |= QMC5883L_RANGE_2_GAUSS | QMC5883L_OVER_SAMPLE_RATIO_512;
 value_for_register1 = QMC5883L_CONTINUOUS_MODE | (output_data_rate << 2);
 value_for_register1 |= (scale_range << 4) | (over_sample_ratio << 6);

 i2c_smbus_write_byte_data(fd, QMC5883L_CONTROL_REGISTER_1, value_for_register1);


#define QMC5883L_TEMPERATURE_DATA_LSB_REGISTER  (0x07)
#define QMC5883L_TEMPERATURE_DATA_MSB_REGISTER  (0x08)

 {
  uint8_t temp[2];
  temp[0] = i2c_smbus_read_byte_data(fd, QMC5883L_TEMPERATURE_DATA_LSB_REGISTER);
  temp[1] = i2c_smbus_read_byte_data(fd, QMC5883L_TEMPERATURE_DATA_MSB_REGISTER);

  /*mips is bi-endian! *((uint16_t*)&temp[0]) is not the correct value */
  printf("relative temperature = %3.1fC\r\n", (temp[0] + (temp[1] << 8))/100.0 );
 }/*relative temperature block*/

 *p_fd = fd;

 return 0;
}/*InitQMC5883L*/

void CloseQMC5883L(int fd)
{
 i2c_smbus_write_byte_data(fd, QMC5883L_CONTROL_REGISTER_1, QMC5883L_STANDBY_MODE);
 close(fd);
}/*CloseQMC5883L*/


int GetQMC5883LData(int fd, int16_t *p_x, int16_t *p_y, int16_t *p_z)
{

#define QMC5883L_STATUS_REGISTER     (0x06)

 uint32_t count;
 count = 0;

 while(1)
 {
  uint8_t status;
  status = i2c_smbus_read_byte_data(fd, QMC5883L_STATUS_REGISTER);

#define QMC5883L_STATUS_DRDY_MASK     ((0x01) << 0)
#define QMC5883L_STATUS_OVL_MASK     ((0x01) << 1)
#define QMC5883L_STATUS_DOR_MASK     ((0x01) << 2)
#if(0)
  if(QMC5883L_STATUS_DRDY_MASK & status)
   printf("data is ready\r\n");
  
  if(QMC5883L_STATUS_OVL_MASK & status)
   printf("data is overflow\r\n");

  if(QMC5883L_STATUS_DOR_MASK & status)
   printf("preivious data is skipped\r\n");
#else
  if(QMC5883L_STATUS_DRDY_MASK & status)
   break;

  if(count > 50)
  {
   printf("ERROR, data is not ready for over %d times\r\n", count);
   return -1;
  }
  count++;
#endif
 }/*wait data ready*/



#define QMC5883L_DATA_OUTPUT_X_LSB_REGISTER   (0x00)
#define QMC5883L_DATA_OUTPUT_X_MSB_REGISTER   (0x01)
#define QMC5883L_DATA_OUTPUT_Y_LSB_REGISTER   (0x02)
#define QMC5883L_DATA_OUTPUT_Y_MSB_REGISTER   (0x03)
#define QMC5883L_DATA_OUTPUT_Z_LSB_REGISTER   (0x04)
#define QMC5883L_DATA_OUTPUT_Z_MSB_REGISTER   (0x05)

 {
  uint8_t temp[2];
  int16_t raw_x, raw_y, raw_z;

  temp[0] = i2c_smbus_read_byte_data(fd, QMC5883L_DATA_OUTPUT_X_LSB_REGISTER);
  temp[1] = i2c_smbus_read_byte_data(fd, QMC5883L_DATA_OUTPUT_X_MSB_REGISTER);
  raw_x = (temp[0] + (temp[1] << 8));

  temp[0] = i2c_smbus_read_byte_data(fd, QMC5883L_DATA_OUTPUT_Y_LSB_REGISTER);
  temp[1] = i2c_smbus_read_byte_data(fd, QMC5883L_DATA_OUTPUT_Y_MSB_REGISTER);
  raw_y = (temp[0] + (temp[1] << 8));

  temp[0] = i2c_smbus_read_byte_data(fd, QMC5883L_DATA_OUTPUT_Z_LSB_REGISTER);
  temp[1] = i2c_smbus_read_byte_data(fd, QMC5883L_DATA_OUTPUT_Z_MSB_REGISTER);
  raw_z = (temp[0] + (temp[1] << 8));

  *p_x = raw_x;
  *p_y = raw_y;
  *p_z = raw_z;
 }/*raw data block*/

 return 0;
}/*ReadSHT20*/


void PrintCurrentTime(void)
{
 time_t t;
 struct tm calendar_time;
 t = time(NULL);
 localtime_r(&t, &calendar_time);

 printf("now time: %d-%d-%d %d:%d:%d, %s(+%d)\n",
  calendar_time.tm_year + 1900, calendar_time.tm_mon + 1,
  calendar_time.tm_mday,
  calendar_time.tm_hour, calendar_time.tm_min, calendar_time.tm_sec,
  calendar_time.tm_zone, (int)calendar_time.tm_gmtoff/3600);
}/*print_current_time*/


int g_fd;

void InterruptSignalHandlingRoutine(int sig)
{
 CloseQMC5883L(g_fd); g_fd = 0;
 exit(0);
}/*InterruptSignalHandlingRoutine*/


int main(int argc, char *argv[])
{
 char i2c_device_name[MAX_STR_LEN];
 int print_frequency_per_sec;
 print_frequency_per_sec = 2;

 signal(SIGINT, InterruptSignalHandlingRoutine);

 memset(&i2c_device_name[0], 0, MAX_STR_LEN);

 if(argc < 2)
 {
  printf("should be fellow the device location (/dev/i2c-XX)\r\n");
  return -1;
 }

 if(argc > 2)
 {
  char *p_temp;
  print_frequency_per_sec = strtol(argv[2], &p_temp, 10);

  if(print_frequency_per_sec > 10 || print_frequency_per_sec < 0)
  {
   printf("%s should be >= 0 and <= 10 \r\n", argv[2]);
   return -3;
  }
 }

 strncpy(&i2c_device_name[0], argv[1], MAX_STR_LEN);

 InitQMC5883L(&i2c_device_name[0], 
  QMC5883LOutputDataRate10Hz, QMC5883LScaleRange2Gauss, QMC5883LOverSampleRatio512,
   &g_fd);


 while(1)
 {
  short x_value, y_value, z_value;
  int x_heading_degree;

  GetQMC5883LData(g_fd, &x_value, &y_value, &z_value);

  x_heading_degree = 180.0*atan2((float)y_value,(float)x_value)/M_PI;

  if(0 > x_heading_degree)
   x_heading_degree += 360;

  printf("x axis heading degree (biased from north) = %d\r\n", x_heading_degree);

  PrintCurrentTime();
  printf("\r\n");
  if(0 == print_frequency_per_sec)
   break;

  usleep(1000*1000/print_frequency_per_sec);
 }/*while 1*/

 CloseQMC5883L(g_fd); g_fd = 0;

 return 0;
}/*main*/

The Makefile for qmc5883L is pretty the same as SHT20's, but your should add -lm before -o, and the built binary name be "qmc5881_measure" replacing the name "sht20_measure".


The command be :


root@GL-AR150:~# ./qmc5883l_measure /dev/i2c-0 

Result :




 
relative temperature = 644.3C
:

x axis heading degree (biased from north) = 1
now time: 2017-9-23 20:21:17, CST(+8)

x axis heading degree (biased from north) = 0
now time: 2017-9-23 20:21:18, CST(+8)

x axis heading degree (biased from north) = 1
now time: 2017-9-23 20:21:18, CST(+8)

x axis heading degree (biased from north) = 359
now time: 2017-9-23 20:21:19, CST(+8)

x axis heading degree (biased from north) = 0
now time: 2017-9-23 20:21:19, CST(+8)

x axis heading degree (biased from north) = 0
now time: 2017-9-23 20:21:20, CST(+8)

x axis heading degree (biased from north) = 357
now time: 2017-9-23 20:21:20, CST(+8)

x axis heading degree (biased from north) = 357
now time: 2017-9-23 20:21:21, CST(+8)

x axis heading degree (biased from north) = 357
now time: 2017-9-23 20:21:21, CST(+8)

x axis heading degree (biased from north) = 356
now time: 2017-9-23 20:21:22, CST(+8)

x axis heading degree (biased from north) = 356
now time: 2017-9-23 20:21:22, CST(+8)

x axis heading degree (biased from north) = 358
now time: 2017-9-23 20:21:23, CST(+8)

x axis heading degree (biased from north) = 358
now time: 2017-9-23 20:21:23, CST(+8)

x axis heading degree (biased from north) = 0
now time: 2017-9-23 20:21:24, CST(+8)

x axis heading degree (biased from north) = 0
now time: 2017-9-23 20:21:24, CST(+8)

x axis heading degree (biased from north) = 0
now time: 2017-9-23 20:21:25, CST(+8)

x axis heading degree (biased from north) = 358
now time: 2017-9-23 20:21:25, CST(+8)

 
(It is all correct, the temperature DOES not relative to water icing and boiling point as 0 and 100)