Experiments with i2c on the (OpenWrt Kamikaze and Debian) Linksys NSLU2 - the Slug
Working drivers for PCF8574 (port expander) and PCF9591 (A/D D/A)
for the LM75 thermometer all is OK on Debian Slug but for OpenWrt

slug I need a "work around" see below

For OpenWrt an ipackage lets you add

- i2cdetect
- i2cdump
- i2cget
- i2cset

use "apt-get install lm-sensors" for Debian

My i2c test board was connected via a Philips designed FET level shifter
(as for the Sweex menu item 6) and I ran "i2cdetect 0" to find my chip addresses
-

0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- 27 -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- 48 -- -- -- 4c -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- UU
70: -- -- -- -- -- -- -- --

So my chips are at
PCF8574 port expander - - - 0x27 = dec39
LM75 thermometer - - - - - - - 0x48 = dec72

PCF8591 A/D D/A - - - - - - - 0x4c= dec76

I suppose UU is the RTC - real time clock on the Slug board.

I tried to compile my c drivers that worked in the Sweex but they failed

I did some Googling many thanks to this link and cutting and pasting from i2cget and i2cset and the following work fine
This is pure Lesson 1 C-code (copy and pray) - please email me if you can up the quality!

/*
PCF8574_addr_byteout_slug.c
----------------------
Send address and byte to switch lines on 8 line port of i2c bus PCF8574
-----------------------------------------------------------------------

1) send the i2c address
2) send byte to set the 8 port lines on or off

usage :- type with spaces but without the < >
<PCF8574_addr_byteout_slug> <decimal address> <decimal byte out 0-255>

*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/i2c-dev.h>
#define I2C_SLAVE 0x0703 /* Change slave address */

int i2c;
char filename[20];
unsigned long address;

int rc;
unsigned char data[1];

int main(int argc, char *argv[])
{
if (argc != 3) { /* error if we are not getting just 2 inputs after the program name */
fprintf(stderr, "usage: %s <address> <databyte>\n",argv[0]);
exit(1);
}

/* address is the first number after the program name */
address = atoi(argv[1]);
/* the byte to send out to the PC8574 is the second number
place it into the first element of the buf array */
data[0] = atoi(argv[2]);

i2c = open("/dev/i2c-0",O_RDWR); /* open the device dev/i2c-0 */

rc=ioctl(i2c,I2C_SLAVE,address); /* set the i2c chip address */

write(i2c,data,1) ; /* send the byte */

i2c = close(i2c);
return(0);
}

/* Sunspot 080607 */

compiled for OpenWrt Slug . . . . compiled for Debian Slug


/*
PCF8574_addr_read_slug.c
-------------------
read the data on the 8 line port of an i2c bus PCF8574
------------------------------------------------------

1) set the i2c address of the PCF8574
2) send data 255 to turn off all 8 lines - make them inputs
3) read the data on the 8 port lines as a byte
usage :- type (without the < >)
<PCF8574_address_read_4> <space> <i2c_bus_address_of_PCF8574_as_decimal>
*/

#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/i2c-dev.h>
#define I2C_SLAVE 0x0703 /* Change slave address */

int i2c;
char filename[20];
unsigned long address;
int rc;
unsigned char data[1];
int byte; /* the 8 bit byte that represents the data present on the 8 wire port */

int main(int argc, char ** argv)
{
if (argc != 2) { /* error if we are not getting just 1 input after the program name */
fprintf(stderr, "usage: %s <address> <databyte>\n",argv[0]);
exit(1);
}

/* address is the first number after the program name */
address = atoi(argv[1]);

i2c = open("/dev/i2c-0",O_RDWR); /* open the device dev/i2c-0 */
rc=ioctl(i2c,I2C_SLAVE,address); /* set the i2c chip address */

data[0] = 255;
write(i2c,data,1); /* we send 255 to make all lines go off, ready for input data */

read(i2c,data,1); /* now data(0) contains the byte of data on the 8 port lines*/

byte = data[0];

printf("BYTE = %d \n", byte);
/* BYTE value is 0-256)*/

i2c = close(i2c);
return(0);
}

 

/* Sunspot 080607 */

compiled for OpnWrt Slug . . . . compiled for Debian Slug


/*
PCF8591-all-lines_addr_V-slug.c
---------------------
read the voltage on all A/D lines (0-3) and set the output voltage on the D/A pin
---------------------------------------------------------------------------------

1) send the i2c address
3) send byte for D/A out
3) read all 4 A/D lines
usage :- type with spaces but without the < >
<PCF8591_address_readvolts> <address as decimal> <D/A value as decimal 0-255>
(even if the D/A pin is unused type in any value 0-255)
*/

#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/i2c-dev.h>
#define I2C_SLAVE 0x0703 /* Change slave address */

int i2c;
int AD_line; /* line number 0-3 */
int V_out; /* value for the D/A out line */
unsigned char buf[1]; /* use to feed the i2c driver */
unsigned long address; /* i2c bus address */
int byte_in_0; /* the 8 bit byte that represents the voltage on the AD_line */
int AD_line_0; /* volts byte on line number 0 */
int AD_line_1; /* volts byte on line number 1 */
int AD_line_2; /* volts byte on line number 2 */
int AD_line_3; /* volts byte on line number 3 */
int rc;

int main(int argc, char** argv)
{
if (argc != 3) /* report error if we are not getting just 2 inputs after the program name */

{

printf("Error. usage: %s i2c_chip_address D/A_Vout(0-255)\n", argv[0]);

}

address = atoi(argv[1]); /* address is the first number after the program name */

V_out = atoi(argv[2]); /* A/D input line 0-3 */

i2c = open("/dev/i2c-0",O_RDWR); /* open the device dev/i2c-0 */
rc=ioctl(i2c,I2C_SLAVE,address); /* set the i2c chip address */

AD_line = 0 + 64; /* enable the D/A output pin 0*/
buf[0] = AD_line; /* A/D input line number is the first data byte sent */
buf[1] = V_out; /* D/A out value is the second data byte sent */
write(i2c,buf,2); /* we send 2 bytes <control register> <D/A value> */
read(i2c,buf,1); /* buf(0) contains the byte of data in the chip cache */
sleep (1); /* do it again */
read(i2c,buf,1); /* buf(0) now contains the byte of data from the most recent analogue input*/
AD_line_0 = buf[0];

AD_line = 1 + 64; /* enable the D/A output pin 0*/
buf[0] = AD_line; /* A/D input line number is the first data byte sent */
buf[1] = V_out; /* D/A out value is the second data byte sent */
write(i2c,buf,2); /* we send 2 bytes <control register> <D/A value> */
read(i2c,buf,1); /* buf(0) contains the byte of data in the chip cache */
//sleep (1); /* do it again */
// read(i2c,buf,1); /* buf(0) now contains the byte of data from the most recent analogue input*/
AD_line_1 = buf[0];

AD_line = 2 + 64; /* enable the D/A output pin 0*/
buf[0] = AD_line; /* A/D input line number is the first data byte sent */
buf[1] = V_out; /* D/A out value is the second data byte sent */
write(i2c,buf,2); /* we send 2 bytes <control register> <D/A value> */
read(i2c,buf,1); /* buf(0) contains the byte of data in the chip cache */
//sleep (1); /* do it again */
// read(i2c,buf,1); /* buf(0) now contains the byte of data from the most recent analogue input*/
AD_line_2 = buf[0];

AD_line = 3 + 64; /* enable the D/A output pin 0*/
buf[0] = AD_line; /* A/D input line number is the first data byte sent */
buf[1] = V_out; /* D/A out value is the second data byte sent */
write(i2c,buf,2); /* we send 2 bytes <control register> <D/A value> */
read(i2c,buf,1); /* buf(0) contains the byte of data in the chip cache */
//sleep (1); /* do it again */
// read(i2c,buf,1); /* buf(0) now contains the byte of data from the most recent analogue input*/
AD_line_3 = buf[0];

printf("A/D line 0 = %d \n", AD_line_0, " ");
printf("A/D line 1 = %d \n", AD_line_1, " ");
printf("A/D line 2 = %d \n", AD_line_2, " ");
printf("A/D line 3 = %d \n", AD_line_3, " ");

/*AD_line_x goes 0 to 255 as voltage input goes from 0 to supply voltage*/

i2c = close(i2c);
return(0);
}
/* Sunspot 080607 */

compiled for OpenWrt Slug . . . . compiled for Debian Slug


/*
PCF8591_address_channel_Vout.c
------------------------------
read the voltage on one A/D lines (0-3) and set the output voltage on the D/A pin
---------------------------------------------------------------------------------

1) send the i2c address
2) choose channel 0-3
3) send byte for D/A out
3) read channel volts as (0 to 255) / 255 x 5V
usage :- type with spaces but without the < >
<PCF8591_address_readvolts> <address as decimal> <channel 0-3> <D/A value as decimal 0-255>
(even if the D/A pin is unused type in any value 0-255)
*/

#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/i2c-dev.h>
#define I2C_SLAVE 0x0703 /* Change slave address */

int i2c;
int channel; /* line number 0-3 */
int V_out; /* value for the D/A out line */
unsigned char buf[1]; /* use to feed the i2c driver */
unsigned long address; /* i2c bus address */
int byte_in_0; /* the 8 bit byte that represents the voltage on the AD_line */
int AD_input; /* volts byte on chosen channel */

int rc;

int main(int argc, char** argv)
{
if (argc != 4) /* report error if we are not getting just 3 inputs after the program name */
{
printf("Error. usage: %s i2c_chip_address channel_to_read D/A_Vout(0-255)\n", argv[0]);
}

address = atoi(argv[1]); /* decimal address is the first number after the program name */
channel = atoi(argv[2]); /* choose A to D line to read 0-3 */
V_out = atoi(argv[3]); /* volts out as decimal 0-255 */

i2c = open("/dev/i2c-0",O_RDWR); /* open the device dev/i2c-0 */
rc=ioctl(i2c,I2C_SLAVE,address); /* set the i2c chip address */

channel = channel + 64; /* enable the D/A output */
buf[0] = channel; /* A/D input line number is the first data byte sent */
buf[1] = V_out; /* D/A out value is the second data byte sent */
write(i2c,buf,2); /* we send 2 bytes <control register> <D/A value> */
read(i2c,buf,1); /* buf(0) contains the byte of data in the chip cache */
usleep (1000); /* do it again */
read(i2c,buf,1); /* buf(0) now contains the byte of data from the most recent analogue input*/
AD_input = buf[0];

 

printf("%d", AD_input);

 

/*AD_line_x goes 0 to 255 as voltage input goes from 0 to supply voltage*/

i2c = close(i2c);
return(0);
}
/* Sunspot 080807 */

compiled for Debian Slug

 

/*
LM75slugx10.c compiles for OpenWrt but gives run error - see workaround below
-------------------

*/

#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/i2c-dev.h>
#define I2C_SLAVE 0x0703 /* Change slave address */

int i2c;
char filename[20];
unsigned long address;
int rc;
unsigned char data[2];
int byte1;
int byte2;
int temperature;
double realtemp;
int main(int argc, char ** argv)
{
if (argc != 2) { /* error if we are not getting just 1 input after the program name */
fprintf(stderr, "usage: %s <address> \n",argv[0]);
exit(1);
}

/* address is the first number after the program name */
address = atoi(argv[1]);

i2c = open("/dev/i2c-0",O_RDWR); /* open the device dev/i2c-0 */
rc=ioctl(i2c,I2C_SLAVE,address); /* set the i2c chip address */

read(i2c,data,2);

byte1 = data[0];
byte2 = data[1];

temperature = byte1*256+byte2;
temperature >>=7;
if (temperature>512)
temperature &= -1;

printf("%d \n", temperature*5);

//cast and divide by 2 (next 2 lines) compiles but gave run error:-
//realtemp = (double)temperature / 10;
//printf("temperature = %.1f \n", realtemp);
// so divide result by 10 in a script!

i2c = close(i2c);
return(0);
}

compiled for OpenWrt Slug

-----------------------------------------------------------------------

I found a way to work round this problem and divide by 10 in a script-

Inside my ash script that builds a web page reporting the temperature I use AWK like this -

# ash is integer only so use use awk to calculate decimals
AWKSCRIPT=' { printf( "%3.1f", $1/10 ) } '
#run the i2c driver program LM75slugx10 and send it the address 72
cd /home/C-tests
realtemp=`./LM75slugx10 72`
echo "The temperature is "
echo $realtemp | awk "$AWKSCRIPT"
echo " degrees"

result=`echo $realtemp | awk "$AWKSCRIPT"`

echo "<BR><HR> result of AWK print = "
echo $result "degrees as an ash string<BR><HR><BR>"

- this does the printing and 51, for example, becomes 25.5 as required

- or I can get a new ash string result = 25.5 (for example)
by using a pair of ` characters to capture the print output.


For Debian Slug the print statements compile and run OK - no need for the "work-around" above

This code just gives the temperature as a string of characters (eg 23.5) with no return character

for i2c decimal address 72 use

./LM75-Deb-slug 72

/*
LM75-Deb-slug.c
----------------
read 2 data bytes from LM75 thermometer chip and print temperature
------------------------------------------------------------------
Debian Slug
-----------

*/

#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <linux/i2c-dev.h>
#define I2C_SLAVE 0x0703 /* Change slave address */

int i2c;
char filename[20];
unsigned long address;
int rc;
unsigned char data[2];
int byte1;
int byte2;
int temperature;
double realtemp;
int main(int argc, char ** argv)
{
if (argc != 2) { /* error if we are not getting just 1 input after the program name */
fprintf(stderr, "usage: %s <address> \n",argv[0]);
exit(1);
}

/* address is the first number after the program name */
address = atoi(argv[1]);

i2c = open("/dev/i2c-0",O_RDWR); /* open the device dev/i2c-0 */
rc=ioctl(i2c,I2C_SLAVE,address); /* set the i2c chip address */

read(i2c,data,2);

byte1 = data[0];
byte2 = data[1];

temperature = byte1*256+byte2;
temperature >>=7;
if (temperature>512)
temperature &= -1;

/* the following 2 lines compile and run for Debian Slug
they compile for OpenWrt Slug but give an error when run*/
realtemp = (double)temperature / 2;
printf("%.1f", realtemp);

i2c = close(i2c);
return(0);
}

compiled for Debian Slug


Use a Blassic basic program with i2cset and i2cget
to read the 13 bit thermometer SE95

SE95_i2ctools_read_once.bas

#!/usr/sbin/blassic
' use i2cset and i2cget to read a SE95 13 bit i2c thermometer
' with all 3 address lines tied to 5 volts the address is 0x4f (decimal 79)
' available decimal addresses are 72 73 74 75 76 77 78 79
'/home/graham/i2c/solar/SE95_i2ctools_read_once.bas 79

address$ = PROGRAMARG$(1)

'write a 0 to the 0 register
SHELL "i2cset -y 0 "+address$+" 0x00 0x00 b > /dev/null 2>&1"

SHELL "i2cget -y 0 "+address$+" 0x00 w > /var/www/ramdisk/lm75_1.txt"
OPEN "/var/www/ramdisk/lm75_1.txt" FOR INPUT AS #1:INPUT #1,temp$:CLOSE #1

'get the two hex bytes from the stored text string
HI$ = RIGHT$(temp$,2) : LO$ = MID$(temp$,3,2)

'get the decimal value of the hex numbers
HI_dec = VAL("&h"+ HI$) : LO_dec = VAL("&h"+ LO$)

sign = 1
' if bit 7 of the HI byte is 1 then the temperature is negative
' convert twos complement to decimal (invert all bits and add 1 to the low byte)
IF HI_dec >= 128 THEN HI_dec = HI_dec XOR 255 :LO_dec = (LO_dec XOR 255) +1: sign = -1

temp_all = (HI_dec + LO_dec/256)
temp_1_dec = ROUND(temp_all,1)
IF temp_1_dec = 0 THEN sign = 1
temp_1_dec = sign * temp_1_dec
PRINT temp_1_dec

SYSTEM