i2c og One-Wire

Når man ligesom jeg kun leger med embedded systemer og hardware i Januar/Februar er det en god ide at standardisere sig på nogle standarder der ikke udvikler sig. F.ex så er det meget frustrerende når den nye avr-gcc ikke kan compilere de programmer jeg lavede året før. Det er både fordi compilieren bliver mere strikt men ikke mindst fordi AVR Atmega er Harward architecture, hvorimod gcc er designed for Van Neuman verdenen, og de dygtige compiler folk vil gerne flytte sig højere og højere op i abstraktions niveau.  Det har jeg absolut ingen interesse i (læs: mangler evnerne).

lcd_avrusb

Mht til CPU så bruger jeg hvad som helst jeg nu har. For tiden er det AVR atmega, TI msp430, TI CC1110Fx/CC1111Fx (8051). Jeg foretrækker Van Neuman CPU-er, men specielt i den embeddede verden synes nogen der kan være et godt argument for Harward CPU-er, FLASH/ROM/EEPROM til code og RAM til data.  Vil man have data i FLASH, eller code i RAM er man ??cked. Hvad er det nu lige der er galt med selvmodificerende kode?

Mht til programmerings-sprog, så er det gammeldags C, “gcc” eller “sdcc”. Til test og debugning tager jeg inspiration fra FORTH, forstået på den måde at jeg ingen ønske har om at lave et FORTH system, men hvis jeg laver mine små-programmer således at de opfører sig som FORTH-words, så er det nemt at teste interaktivt, og systemet kan se ens ud, selv om man flytter til ny CPU, og det er nemt at lime forskellige FORTH-words sammen uden at skulle recompilere det hele, og på Harward CPU-er kan man så have code i RAM/EEPROM også.

https://forum.openwrt.org/viewtopic.php?pid=250729#p250729

pogo v4 i2c https://forum.openwrt.org/viewtopic.php?pid=250729#p250729

Mht til I/O grænseflade så vil jeg helst gå via i2c eller Dallas One-Wire, så jeg uden alt for meget besvær kan skifte Processor-platformen ud. og f.ex lave udvikling direkte på en laptop, og senere flytte det ned på en Embedded-SoC (System on a Chip) eller arduino.

Linux har god support for både i2c og one-wire, så man kan faktisk nå langt bare ved hjælp af kommando-linien og Shell-programering. Det kan jeg li’

På lang de flest Linux SoC systemer kan der altid lige findes et par GPIO pins så man kan forbinde en i2c-bus, Her er lidt inspiration:

usbasp_circuit

USBASP, atmega8 baseret

Jeg foretrækker at lege med i2c via en usb-to-i2c-adapter, og sådan en kan man lave selv for små penge, dvs man kan køber en USBASP formedelst $2.28 leveret fra ebay.com, og så får den en ny firmware, det er det der skal ske i denne POST:

USBASP med I2C-TINY_USB firmware

USBASP-programmer bruges til at putte kode ind i AVR atmega cpu-er. USBASP er udviklet af Thomas Fischl, og selv baseret på en atmega8, og både hardware, firmware og source-code er tilgængelig under GPL-2 licens. http://www.fischl.de/usbasp
Till Harbaum har æren for “i2c-tiny-usb” http://www.harbaum.org/till/i2c_tiny_usb, som er en lille AVR tiny45/atmega8 som gør i2c tilgængelig via USB. Det bedste ved denne løsning er at den er supporteret under Linux, så en i2c-enhed forbundet via en i2c-tiny-usb håndteres præcis som de andre i2c-enheder der måtte være i din Linux-box, og der er drivere til det meste.

Så går vi igang – Vi skal have krydset hardware fra USBASP med firmwaren fra i2c-tiny-usb

Først skal vi lige hente lidt remedier fra
http://www.atmel.com/tools/ATMELAVRTOOLCHAINFORLINUX.aspx, på min ubuntu skal jeg bruge 64bit versionen.

$ tar xvzf DIST/avr8-gnu-toolchain-3.4.5.1522-linux.any.x86_64.tar.gz
$ ls -l `pwd`/avr8-gnu-toolchain-linux_x86_64/bin
$ export PATH=`pwd`/avr8-gnu-toolchain-linux_x86_64/bin:$PATH
$ hash -r
$ type -a avr-gcc

Hent http://www.harbaum.org/till/i2c_tiny_usb/i2c_tiny_usb-2009-02-10.zip den skal pakkes den ud, og tilpasses USBASP hardwaren

$ unzip DIST/i2c_tiny_usb-2009-02-10.zip
$ patch -p0 << EOF
--- i2c_tiny_usb-2009-02-10/firmware/main.c	2007-06-07 09:53:47.000000000 -0400
+++ i2c_tiny_usb/firmware/main.c	2015-02-01 13:11:07.676250008 -0500
@@ -166,9 +166,15 @@
 static unsigned char saved_cmd;

 #if! defined (__AVR_ATtiny45__)
+#ifdef USBASP // MISO as SDA, SCLK as SCL
+#define I2C_PORT   PORTB
+#define I2C_PIN    PINB
+#define I2C_DDR    DDRB
+#else
 #define I2C_PORT   PORTC
 #define I2C_PIN    PINC
 #define I2C_DDR    DDRC
+#endif
 #define I2C_SDA    _BV(4)
 #define I2C_SCL    _BV(5)
 #else
--- i2c_tiny_usb-2009-02-10/firmware/Makefile-avrusb.mega8	2006-12-03 16:28:59.000000000 -0500
+++ i2c_tiny_usb/firmware/Makefile-avrusb.mega8	2015-02-01 14:24:07.262767528 -0500
@@ -14,8 +14,8 @@
 # to a Keyspan USB to serial converter to a Mac running Mac OS X.
 # Choose your favorite programmer and interface.

-DEFINES += -DDEBUG
-DEFINES += -DDEBUG_LEVEL=1
+#DEFINES += -DDEBUG -DDEBUG_LEVEL=1
+DEFINES += -DUSBASP -DF_CPU=12000000
 COMPILE = avr-gcc -Wall -O2 -Iusbdrv -I. -mmcu=atmega8 $(DEFINES)

 OBJECTS = usbdrv/usbdrv.o usbdrv/usbdrvasm.o usbdrv/oddebug.o main.o
--- i2c_tiny_usb-2009-02-10/firmware/usbconfig.h	2007-05-19 08:30:11.000000000 -0400
+++ i2c_tiny_usb/firmware/usbconfig.h	2015-02-01 14:25:35.646071082 -0500
@@ -19,6 +19,12 @@

 /* ---------------------------- Hardware Config ---------------------------- */

+#ifdef USBASP
+#define USB_CFG_IOPORTNAME      B
+#define USB_CFG_DMINUS_BIT      0
+#define USB_CFG_DPLUS_BIT       1
+#define USB_CFG_CLOCK_KHZ       (F_CPU/1000)
+#else
 #if! defined (__AVR_ATtiny45__)
 #define	USB_CFG_IOPORTNAME		C
 /* This is the port where the USB bus is connected. When you configure it to
@@ -39,6 +45,7 @@
 #define	USB_CFG_DMINUS_BIT		0
 #define	USB_CFG_DPLUS_BIT		2
 #endif
+#endif

 /* --------------------------- Functional Range ---------------------------- */

EOF
$ cd i2c_tiny_usb/firmware
$ make -f Makefile-avrusb.mega8

Hvis din avr-gcc er af nyere dato gik det IKKE, og du kan enten tilføje en masse “const” der hvor den brokker sig, eller hente en nyere version af V-USB firmware.

$ tar xvzf ../../DIST/vusb-20121206.tar.gz
$ rm -rf usbdrv
$ ln -s vusb-20121206/usbdrv .
$ make -f Makefile-avrusb.mega8

Denne gang var det sikkert OK, medmindre der er kommet en ny avr-gcc siden jeg skrev dette.

Så skal de to USBASP-er forbindes sammen, og den der skal blive til i2c-tiny-usb, skal have en jumper i jp2 (RESET), og så der kan brændes ny firmware

$ make program

Det var det hele, nu kan vi forbinde en i2c enhed til vores Linux box, via vores nyprogrammerede USBASP, lad os se om det virker, vi forbinder en $0.99 i2c-lcd-adapter fra ebay den er baseret på en pcf8574.

$ lsusb
Bus 001 Device 012: ID 0403:c631 Future Technology Devices International, Ltd i2c-tiny-usb interface
Bus 001 Device 006: ID 16c0:05dc Van Ooijen Technische Informatica shared ID for use with libusb
Bus 001 Device 002: ID 1a40:0101 Terminus Technology Inc. 4-Port HUB
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

$ i2cdetect -l
i2c-0	i2c       	i915 gmbus ssc                  	I2C adapter
i2c-1	i2c       	i915 gmbus vga                  	I2C adapter
i2c-2	i2c       	i915 gmbus panel                	I2C adapter
i2c-3	i2c       	i915 gmbus dpc                  	I2C adapter
i2c-4	i2c       	i915 gmbus dpb                  	I2C adapter
i2c-5	i2c       	i915 gmbus dpd                  	I2C adapter
i2c-6	i2c       	i2c-tiny-usb at bus 001 device 012	I2C adapter
$ i2cdetect -y 6
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- 27 -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

Vores i2c-tiny-usb blev fundet, og “forbundet” som i2c bus nr. 6 på den her PC, og pcf8574 blev fundet på addresse 0x27 på den.

Nu skal vi blot have skrevet “Hello World” på LCD displayet, det er der andre der har gjort før os, så det benytter vi os af:

Vi henter source-koden ovenfor, og retter den til så den ikke er Rapsberry Pi specifik.

$ mkdir lcdi2c
$ cd lcdi2c
$ tar -xvzf DIST/lcdi2c-tar.gz
$ patch -p0 << EOF
--- lcdi2c.orig/lcd_i2c.c	2014-10-09 19:08:29.000000000 -0400
+++ lcdi2c/lcd_i2c.c	2015-02-01 10:46:05.155659267 -0500
@@ -4,8 +4,14 @@
 // ----------------------------------------------------------------
 #include "lcd_i2c.h"

-#include <wiringPi.h>
-#include <wiringPiI2C.h>
+#define delayMicroseconds usleep
+#define delay(ms) usleep(1000*ms)
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <linux/i2c-dev.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -19,7 +25,7 @@

 // convenience macros
 // write current value of output variable to device
-#define LCD_I2C_WRITE(lcd_p) wiringPiI2CWrite(lcd_p->fd, lcd_p->output);
+#define LCD_I2C_WRITE(lcd_p) i2c_smbus_write_byte(lcd_p->fd, lcd_p->output)
 // enable
 #define LCD_I2C_E_HI(lcd_p)  lcd_p->output |= (1<<LCD_I2C_E)
 #define LCD_I2C_E_LO(lcd_p)  lcd_p->output &=~ (1<<LCD_I2C_E)
@@ -87,8 +93,14 @@

 int lcd_i2c_setup( lcd_i2c_t *lcd,int address)
 {
+    char device[100] ;
+    extern int8_t opt_bus;     // global variable
+    sprintf(device, "/dev/i2c-%d", opt_bus);
+    if ((lcd->fd = open (device, O_RDWR)) < 0)
+        return -1 ;
+    if (ioctl (lcd->fd, I2C_SLAVE, address) < 0)
+        return -1 ;
     lcd->output=0;
-    lcd->fd=wiringPiI2CSetup(address);
     // XXX for now, alway use a 1602 display
     lcd->rows=2;
     lcd->cols=16;
--- lcdi2c.orig/lcdi2c.c	2014-10-09 19:09:51.000000000 -0400
+++ lcdi2c/lcdi2c.c	2015-02-01 10:49:24.211071181 -0500
@@ -15,6 +15,7 @@
 // option flags
 int8_t opt_cursor=0; // 0 off, 1 on , 2 blink
 int8_t opt_address = LCD_I2C_PCF8574_ADDRESS_DEFAULT;
+int8_t opt_bus = 6;	// global variable
 int8_t opt_backlight=-1; // -1 do nothing, 0 turn off, 1 turn on
 int8_t opt_clear;
 int8_t opt_cols = 16;
@@ -109,6 +110,7 @@
     char msg[]="Display string on a HD44780 LCD which is connected by the i2c bus via a PCF8574 port expander\n\
 Options:\n\
 \t-a address\t- use this i2c (hexidecimal) address (default 0x27)\n\
+\t-d bus\t- use this i2c bus (default 6)\n\
 \t-i\t\t- initialise (reset) the lcd\n\
 \t-r rows\t\t- set the number of rows (default 2)\n\
 \t-c cols\t\t- set the number of columns (default 16)\n\
@@ -148,8 +150,11 @@
     int option; 

     opterr=0; // no error message printing by getopt()
-    while( (option = getopt(argc, argv,"ilha:r:c:x:y:b:s:")) != -1) {
+    while( (option = getopt(argc, argv,"ilha:d:r:c:x:y:b:s:")) != -1) {
         switch (option) {
+	    case 'd': opt_bus=str2int8('s',optarg,10,0,127);
+		if(opt_bus<0) return  -1;
+		break;
             case 'h' : opt_help = 1;
                  break;
 	    case 'l': opt_clear=1;
EOF
$ make

Så er den oversat, lad os se om det virker?

$ ./lcdi2c --help
Unknown option `--'.
Usage:  lcdi2c [options] "string to display"
Display string on a HD44780 LCD which is connected by the i2c bus via a PCF8574 port expander
Options:
	-a address	- use this i2c (hexidecimal) address (default 0x27)
	-d bus	- use this i2c bus (default 6)
	-i		- initialise (reset) the lcd
	-r rows		- set the number of rows (default 2)
	-c cols		- set the number of columns (default 16)
	-x col		- move cursor to this column (default 0)
	-y row		- move cursor to this row (default 0)
	-b [01]		- turn backlight on (1) or off (0) (default off)
	-l		- clear the screen
	-s [012]	- turn cursor off (0), on (1), or blink (2) (default off)
	-h		- display this help message

$ ./lcdi2c -i -d 6 -a 27 "Hello World"

Det virkede – og det var måske heldigt da de her i2c-lcd enheder kommer i to versioner (b0..b7) = (RS,RW,E,BL,d4..d7) eller (d4..d7,RS,RW,E,BL). Her bruger vi den første version men f.ex. LCDd er kodet til den anden version, det kan nu nemt fixes, enten i hardware eller i software.

$2.28 + $0.99 + $1.74

Godt og vel $5 og du kan kan have en 16×2 LCD display på din Linux – Det er da til at overskue.

Print Friendly, PDF & Email
This entry was posted in Hardware, HomeAutomation, Linux. Bookmark the permalink.