I2C
An old two-wire communication beetwen devices invented in 1982 by Philips Semiconductor. Mostly it used to communicate with e.g. EEPROMs,sensores. Nowadays it is used in most of electronic devices (e.g. computers, mobiles,...)
I2C-bus specification and user manual, including new UFm specifications: http://www.nxp.com/documents/user_manual/UM10204.pdf
WARNING!
These programs can confuse your I2C bus, cause data loss and worse!
Do not use, if you are don't know exactly what you are doing!
i2c-dev
Under linuxes devices can be accessed at /dev. Usually you cannot access I2C devices, but if you load i2c-dev kernel module you can access them from userspace at /dev/i2c-x. There are three utilities ( i2cdetect, i2cdump and i2cset ) that can list/read/manipulate I2C devices.
CH341A USB to I2C adapter
I needed a way to access an I2C eprom and a sensor, so I bought this CH341A USB to I2C/SPI/UART/TTL/ISP/GPIO adapter.
If you have the same, you may need to install it through e.g. CH341A I2C kernel module.
The test
With these utils you can access your PC-s hardware. If you do something wrong you may cause problem!
Do everything at your own risk! If you don't understand what you are doing, please, do nothing!
My test hardware is built from a 24C02 eprom and an LM75A temperature sensor:
(I use it in my simple I2C thermostat, built with attiny45 and this module.)
-
Load kernel modules: i2c-dev and i2c-ch341-usb (Avoiding conflict with serial driver, first unload it with rmmod ch431.)
-
"i2cdetect -l" have to give similar result, where i2c-ch341a-usb could be bounded to an I2C line.
Search ch341a adapter's line number at your list. (/dev/i2c-x, in my example x=4)
-
"i2cdetect x" have to give similar result, where you can see the I2C devices at the adapter.
In my example an eprom connects at address 0x50 and a sensor at address 0x48.
-
"i2cdump x address" have to give similar result, where you can see the I2C eprom's data.
-
Use "chmod / chown" to give correct rights to user, which may access only this I2C line (in my example /dev/i2c-4) and do nothing else as root (system administrator)!
Programming
If you would like to read data from an I2C device from your program developed in C/C++ it should be something like this:
#include <i2c/smbus.h> // or <linux/i2c-smbus.h>
#include <linux/i2c-dev.h>
int main( void ) {
myfile = open( "/dev/i2c-x", O_RDWR ); // open i2c line
result = ioctl( myfile, I2C_SLAVE, device_address ); // access i2c slave device
data = i2c_smbus_read_byte_data( myfile, data_address ); // read data
close ( myfile ); // close communication at i2c line
}
It looks very easy, so I wrote a test application to my test device (see above). It manupulates an LM75A sensor and a 24c02 eprom device.
I've created two classes, one for each type of devices, which implements all the supported functions.
The example code for testing classes and I2C devices:
//
// Test program for accessing LM75A and 24c02 through i2c-dev + i2c-ch341a-usb in C++
//
// Copyright (c)2015-2019 Tóthpál István, istvan@tothpal.eu
//
#include <linux/i2c-dev.h>
#include <stdio.h>
// -------------------------------- My classes -----------------------------
#include "include/i2c/lm75a.h"
#include "include/i2c/24c02.h"
int main (void) {
__u8 result = 0;
lm75a *mylm75 = new lm75a( 4, 0x48 ); // init i2c line4 at 0x48 address in my hardware
printf("ProductID:%s rev:%xn", ( mylm75->is_lm75a() ? "LM75a" : "Unknown" ),
mylm75->get_productid() & 0x0f );
if ( mylm75->is_lm75a() ) {
mylm75->set_thyst( 24 );
mylm75->set_tos( 28 );
mylm75->go_comparator_mode();
printf("Conf:%xn", mylm75->get_configuration() );
printf("THYST: %f°Cn", mylm75->get_thyst() );
printf("TOS: %f°Cn", mylm75->get_tos() );
printf("nCurrent Temperature: %f°Cn", mylm75->get_temperature() );
};
delete mylm75;
e24c02 *my24c02 = new e24c02( 4, 0x50 ); // init i2c line4 at 0x50 address in my hardware
my24c02->write_byte( 0x72, 0x72 );
result = my24c02->read_byte( 0x72 );
printf(" - 24x02 on 0x50 at 0x72: %xn", result );
my24c02->write_byte( 0x72, 0x69 );
result = my24c02->read_byte( 0x72 );
printf(" - 24x02 on 0x50 at 0x72: %xn", result );
delete my24c02;
}
and the test result:
You can see that the current temperature is higher than the overtemp. shutdown, so the sensor's OS PIN is switched and if you connected e.g. a cooler, it started to cool down the system. Read more from LM75A.
include/i2c/lm75a.h:
//
// Class header file for accessing LM75A through i2c-dev + i2c-ch341a-usb in C++
//
// Copyright (c)2015-2019 Tóthpál István, istvan@tothpal.eu
//
#define LM75a_BASE_ADDRESS 0x48 // +address pins
// LM75a_POINTER_REGISTER // the address & 0x07
#define LM75a_TEMPERATURE_REGISTER 0x00 // LSB 0,5°C only bit 7
#define LM75a_CONFIGURATION_REGISTER 0x01
#define LM75a_THYST_REGISTER 0x02
#define LM75a_TOS_REGISTER 0x03
#define LM75a_PRODUCTID_REGISTER 0x07
extern float temp_to_float( __s32 temp );
extern __s32 float_to_temp( float temp );
class lm75a {
int i2c_line;
int i2c_address;
int myfile;
struct registers_struct {
__s16 temperature;
__s8 configuration;
__s16 thyst;
__s16 tos;
__s8 productid;
} registers;
public:
//constructor
lm75a( int line, int address );
// destructor
~lm75a();
// --------- Check if LM75a is answering --------
bool is_lm75a();
int get_i2c_line();
int get_i2c_address();
void read_registers();
__s16 read_register( int lmreg );
__s8 read_byte_register( int lmreg );
float get_temperature();
float get_thyst();
float get_tos();
__s8 get_configuration();
__s8 get_productid();
void set_thyst( float temperature );
void set_tos( float temperature );
void write_configuration( __u8 configuration );
void go_shutdown();
void go_online();
void go_comparator_mode();
void go_interrupt_mode();
void go_OS_polarity_low();
void go_OS_polarity_high();
void set_fault_number( int fault );
};
include/i2c/24c02.h:
//
// Class header file for accessing 24c02 through i2c-dev + i2c-ch341a-usb in C++
//
// Copyright (c)2015-2019 Tóthpál István, istvan@tothpal.eu
//
#define e24c02_BASE_ADDRESS 0x50;
class e24c02 {
int i2c_line;
int i2c_address;
int myfile;
public:
e24c02 ( int line, int address );
~e24c02 ();
__u8 read_byte( int address );
void write_byte( int address, __u8 data );
};
lm75a.c:
//
// Class file for accessing LM75A through i2c-dev + i2c-ch341a-usb in C++
//
// Copyright (c)2015-2019 Tóthpál István, istvan@tothpal.eu
//
#include <i2c/smbus.h>
#include <linux/i2c-dev.h>
#include "../include/i2c/lm75a.h"
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdlib.h> //for using the function sleep
float temp_to_float( __s32 temp ) {
signed int t;
float f;
// second byte's highest bit is the sign
// first byte's highest bit is 0.5°C
t= ((temp & 0x80) << 24) + (temp & 0x80 ? 0x7fffff80 : 0) + (temp & 0x7f);
f = t + ( (temp & 0x8000) ? 0.5 : 0);
return f;
}
// returned data:
// second byte's highest bit is the sign
// first byte's highest bit is 0.5°C
__s32 float_to_temp( float temp ){
int half = 2*( temp - int(temp) );
__s32 ret = ( abs(half) ? 0x8000 : 0x0000 ) + ( (temp < 0) ? 0x00ff-abs(temp) : abs(temp) );
return ret;
};
/*
********************************************************************
* LM75a CLASS *
********************************************************************
*/
// --------- Constructor ---------
lm75a::lm75a( int line, int address ) {
__s32 opResult = 0; // for error checking of operations
char line_str[20];
i2c_line = line;
i2c_address = address;
sprintf(line_str, "/dev/i2c-%d", line);
myfile = open( line_str, O_RDWR );
opResult = ioctl( myfile, I2C_SLAVE_FORCE, address );
if (opResult < 0) {
printf("%mn", opResult );
exit(EXIT_FAILURE);
}
else {
read_registers();
}
}
// ---------- Destructor ------------
lm75a::~lm75a() {
close( myfile );
}
// --------- Check if LM75a is answering --------
bool lm75a::is_lm75a() {
__s8 pid = read_byte_register( LM75a_PRODUCTID_REGISTER );
return ( pid & 0xf0) == 0xa0 ;
}
int lm75a::get_i2c_line() {
return i2c_line;
}
int lm75a::get_i2c_address() {
return i2c_address;
}
void lm75a::read_registers() {
registers.temperature = read_register( LM75a_TEMPERATURE_REGISTER );
registers.configuration = read_byte_register( LM75a_CONFIGURATION_REGISTER );
registers.thyst = read_register( LM75a_THYST_REGISTER );
registers.tos = read_register( LM75a_TOS_REGISTER );
registers.productid = read_byte_register( LM75a_PRODUCTID_REGISTER );
}
__s16 lm75a::read_register( int lmreg ) {
return i2c_smbus_read_word_data( myfile, lmreg );
}
__s8 lm75a::read_byte_register( int lmreg ) {
return i2c_smbus_read_byte_data( myfile, lmreg );
}
float lm75a::get_temperature(){
registers.temperature = read_register( LM75a_TEMPERATURE_REGISTER );
return temp_to_float( registers.temperature );
};
float lm75a::get_thyst(){
registers.thyst = read_register( LM75a_THYST_REGISTER );
return temp_to_float( registers.thyst );
};
float lm75a::get_tos(){
registers.tos = read_register( LM75a_TOS_REGISTER );
return temp_to_float( registers.tos );
};
__s8 lm75a::get_configuration(){
registers.configuration = read_register( LM75a_CONFIGURATION_REGISTER );
return registers.configuration;
};
__s8 lm75a::get_productid(){
return registers.productid;
};
void lm75a::set_thyst( float temperature ){
i2c_smbus_write_word_data( myfile, LM75a_THYST_REGISTER, float_to_temp( temperature ) );
};
void lm75a::set_tos( float temperature ){
i2c_smbus_write_word_data( myfile, LM75a_TOS_REGISTER, float_to_temp( temperature ) );
};
// ------ modifying Configuration ------
void lm75a::write_configuration( __u8 configuration ){
i2c_smbus_write_byte_data( myfile, LM75a_CONFIGURATION_REGISTER, configuration );
};
// --- go to sleep, low power mode
void lm75a::go_shutdown(){
write_configuration( get_configuration() | 0x01 );
};
// --- wake up to normal
void lm75a::go_online(){
write_configuration( get_configuration() & 0xfe );
};
void lm75a::go_comparator_mode(){
write_configuration( get_configuration() & 0xfd );
};
void lm75a::go_interrupt_mode(){
write_configuration( get_configuration() | 0x02 );
}
void lm75a::go_OS_polarity_low(){
write_configuration( get_configuration() & 0xfb );
};
void lm75a::go_OS_polarity_high(){
write_configuration( get_configuration() | 0x04 );
};
void lm75a::set_fault_number( int fault ){
int f = 0 ;
switch (fault) {
case 1: f=0; break;
case 2: f=1; break;
case 4: f=2; break;
case 6: f=3; break;
default: f=0;
};
write_configuration( (get_configuration() & 0xe7) | (f << 3) );
};
24c02.c:
//
// Class file for accessing 24c02 through i2c-dev + i2c-ch341a-usb in C++
//
// Copyright (c)2015-2019 Tóthpál István, istvan@tothpal.eu
//
#include <linux/i2c-dev.h>
#include <i2c/smbus.h>
#include "../include/i2c/24c02.h"
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
e24c02::e24c02 ( int line, int address ){
int i2c_line = line;
int i2c_address = address;
char i2c_line_str[20];
sprintf( i2c_line_str, "/dev/i2c-%d", i2c_line );
// open i2c line
myfile = open( i2c_line_str, O_RDWR);
// Select i2c Line for 24x02 on 0x50
__s32 opResult = ioctl(myfile, I2C_SLAVE_FORCE, i2c_address );
printf("nTrying to change to eeprom: %s %dn", i2c_line_str, opResult );
};
e24c02::~e24c02 (){
// close i2c line
close ( myfile );
};
__u8 e24c02::read_byte( int address ){
return i2c_smbus_read_byte_data( myfile, address );
};
void e24c02::write_byte( int address, __u8 data ){
i2c_smbus_write_byte_data( myfile, address, data );
usleep(10000);
};
This can work if you have different USB/I2C adapter, just load its kernel module.
You may not read 16bit eproms - like 24c256 - through this way, because i2c_smbus_read_byte_data and similar functions can only address 256 Bytes.