I2C Communication with the Philips Semiconductor SAA1064

   


The Project Trailblazer engineers plan to use the Philips Semiconductor SAA1064 to interface four seven-segment LED displays to a target board that uses I2C bus signaling. An I2C bus requires only two bidirectional signal lines: serial data (SDA) and serial clock (SCL). This bus supports multiple slave devices through software addressing in which each bus device has a unique address. I2C communication is 8-bit communication, with the data receiver acknowledging the reception of each transferred byte. Bus devices can connect or disconnect at any time, without restarting the master. Philips offers complete and thorough I2C documentation.3

Up to four SAA1064 can exist on a single I2C bus. Therefore, the engineers can use them to display the lift top and lift bottom temperatures in both Fahrenheit and Celsius. Each SAA1064 can drive up to four seven-segment LED displays, for a total of 16 LEDs on a single serial bus. The SAA1064 design lowers overall package pin count with two multiplexing LED drive circuits that control two common-anode segments. (See SAA1064 Datasheet4 for more information.)

TIP

The SAA1064 provides direct connection for common-anode seven-segment LED displays without additional circuit components. The SAA1064 can drive common-cathode LED displays with additional circuitry. If you are purchasing LED displays for use with the SAA1064, you should choose common-anode type. This will simplify your interfacing design.


Communication commands can control all LED segments individually on each of the four seven-segment LEDS. The SAA1064 drives the LED segments with a programmable current source that allows for software-controlled brightness without requiring external current limiting resistors. Setting the SAA1064's I2C bus address occurs by applying an analog voltage to the address pin. The SAA1064's industrial temperature range of 40°C to +85°C ( 40°F to +185°F) permits unheated operation, which is a definite plus for Silverjack outdoor winter operation.

Connecting the SAA1064 to the x86 Parallel Printer Port

Connecting the SAA1064 to the parallel port requires two signals: SDA and SCL. The display LEDs require more drive current than the parallel port can supply. The power and ground lines must be connected to an external power supply. The PC's power supply can be used. The x86 parallel port doesn't contain a dedicated I2C communications controller. A device driver can implement the I2C protocol in software and then use the parallel port for the SDA and SCL signaling.

An I2C parallel port driver called i2c-pport exists within the lm_sensors project (see www.lm-sensors.nu). This open source project implements the I2C protocol for Linux. The Project Trailblazer engineers decided to use the i2c-pport device driver instead of writing their own. Connecting and communicating with an SAA1064 by using i2c-pport is a five-step process:

  1. Download and compile the I2C source code by using these commands:

     root@tbdev1[501]: cd /root root@tbdev1[502]: wget www.lm-sensors.nu/archive/ i2c-2.6.1.tar.gz root@tbdev1[503]: tar zxf i2c-2.6.1.tar.gz root@tbdev1[504]: cd i2c-2.6.1 root@tbdev1[505]: make    root@tbdev1[506]: ls kernel/*o kernel/i2c-algo-bit.o  kernel/i2c-elektor.o   kernel/i2c-proc.o kernel/i2c-algo-pcf.o  kernel/i2c-elv.o       kernel/i2c-velleman.o kernel/i2c-core.o      kernel/i2c-philips-par.o kernel/i2c-dev.o       kernel/i2c-pport.o 

    This ls output shows that the modules i2c-core.o, i2c-algo-bit.o, i2c-dev.o, and i2c-pport.o compiled successfully.

  2. Create the character device file with a major number 89 and minor number 0 in the /dev directory, by using this command:

     root@tbdev1[581]: mknod /dev/i2c0 c 89 0 
  3. Load the i2c-core, i2c-algo-bit, i2c-pport and the i2c-dev modules:

     root@tbdev1[515]: cd /root/i2c-2.6.1/kernel root@tbdev1[516]: insmod i2c-core.o root@tbdev1[517]: insmod i2c-algo-bit.o root@tbdev1[518]: insmod i2c-pport.o root@tbdev1[519]: insmod i2c-dev.o 
  4. Connect the SAA1064 SDA and SCL lines to the x86 printer port and connect the power and ground lines to a floppy disk connector. The author of i2c-pport has written instructions for connecting I2C devices to the parallel printer port (see /root/i2c-2.6.1/doc/i2c-pport). The parallel port's control lines allow for bidirectional communication. The i2c-pport device driver uses the control line auto linefeed signal for SDA and the control line initialize printer signal for SCL. Figure 10.5 shows the complete circuit schematic for connection of four SAA1064s that drive the four seven-segments LEDs.

    Figure 10.5. The SAA1064 seven-segment LED driver connection to the x86 parallel printer port.

    graphics/10fig05.gif

    TIP

    You can power an interface circuit from the PC's power supply. The floppy disk connector provides +5VDC, +12VDC, and ground connections. The connector's 0.1-inch pin spacing allows for use of standard headers, thus simplifying the wiring to your interface board. A 250-Watt power supply can typically source more than 20A from the +5V supply and 5A from the +12V supply. See your PC's power supply for actual specifications.

  5. Write, compile, and run a program that communicates with the device driver.

    The device driver SAA1064_x86.c communicates with the SAA1064 via the i2c-pport device driver. SAA1064_x86.c opens the device file, uses ioctl to set the I2C slave address, uses the write system call to send a 6-byte stream to the SAA1064, and then closes the device file and exits. The 6-byte stream consists of the following:

    • Byte 1 The internal starting SAA1064 register address

    • Byte 2 The configuration register setting

    • Byte 3 The LED segment 1 value

    • Byte 4 The LED segment 2 value

    • Byte 5 The LED segment 3 value

    • Byte 6 The LED segment 4 value

Listing 10.3 shows the source code for the SAA1064_x86.c device driver. Note that this device driver code only communicates with the SAA1064-#0 at address 0x00.

Listing 10.3 The SAA1064_x86.c Device Driver
 /*  * SAA1064_x86 v1.0 11/05/01  * www.embeddedlinuxinterfacing.com  *  * The original location of this code is  * http://www.embeddedlinuxinterfacing.com/chapters/10/  *  * Copyright (C) 2001 by Craig Hollabaugh  *  * This program is free software; you can redistribute it and/or modify  * it under the terms of the GNU Library General Public License as  * published by the Free Software Foundation; either version 2 of the  * License, or (at your option) any later version.  *  * This program is distributed in the hope that it will be useful, but  * WITHOUT ANY WARRANTY; without even the implied warranty of  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU  * Library General Public License for more details.  *  * You should have received a copy of the GNU Library General Public  * License along with this program; if not, write to the  * Free Software Foundation, Inc.,  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA  */ /* SAA1064_x86  * This program demonstrates communication with a SAA1064 I2C LED  * display driver using the i2c-pport x86 parallel port device driver.  *  * For more Linux I2C information, visit the lm_sensors site at  * http://www.lm-sensors.nu/  *  * This program opens the I2C device driver file, sets the address for  * SAA1064-#0 then sends it 6 bytes.  * byte 1 - internal starting SAA1064 register address  * byte 2 - configuration register setting  * byte 3 - LED segment 1 value  * byte 4 - LED segment 2 value  * byte 5 - LED segment 3 value  * byte 6 - LED segment 4 value  *  * For more SAA1064 or I2C protocol, visit the Philips Semiconductor Web site  * at http://www.semiconductors.philips.com  */ /*  * gcc -I/usr/src/linux/include -o SAA1064_x86 SAA1064_x86.c  */ #include <fcntl.h> #include <linux/i2c.h> #include <linux/i2c-dev.h> int main(void) {   int file;   int address;   unsigned char buffer[10], i; /* seg is a segment mapping table. Element 0, 0xFC, tells the  * SAA1064 to turn on the segments to display a 0. Likewise,  * seg's other entries map to 1 through 9 and a through f.  */   unsigned char seg[] = { 0xFC, 0x60, 0xDA, 0xF2, 0x66,                           0xB6, 0xBE, 0xE0, 0xFE, 0xF6,                           0xEE, 0x3E, 0x9C, 0x7A, 0x9E, 0x8E } ;   if (( file = open("/dev/i2c0",O_RDWR)) < 0) {     printf("Can't open /dev/i2c0\n");     exit(1);   }   address = 0x38; /*  * Why 0x38? Philip's SAA1064 group address is 0111, the SAA1064-#0 is  * an address zero, making it's full address 0111.0000 which is 0x70.  * The I2C driver wants addresses divided by 2, so 0x70 / 2 = 0x38.  */   if (ioctl(file,I2C_SLAVE,address) < 0) {     printf("Can't set address\n");     exit(1);   }   buffer[0] = 0x00;   /* internal starting SAA1064 register address */   buffer[1] = 0x46;   /* 12mA current, all digits on, dynamic mode */   buffer[2] = seg[1]; /* this puts a '1' on the display segment 1 */   buffer[3] = seg[2]; /* this puts a '2' on the display segment 2 */   buffer[4] = seg[3]; /* this puts a '3' on the display segment 3 */   buffer[5] = seg[4]; /* this puts a '4' on the display segment 4 */   if ( write(file,buffer,6) != 6) {     printf("Write failed\n");     close(file);     exit(1);   }   close(file); } 

Executing SAA1064_x86 results in 1234 being displayed on SAA1064-#0's LEDs. Figure 10.6 shows the first two characters of an I2C communication with the SAA1064.

Figure 10.6. An oscilloscope capture of SCL (top trace) and SDA (bottom trace) signals during an I2C communication with an SAA1064.

graphics/10fig06.gif

The SAA1064_x86.c program performs as expected. The four numbers sent to the SAA1064 appear on the seven-segment LEDs. Using code from the lm_sensors project meant that the Project Trailblazer engineers did not have to develop a bit-banged I2C driver for the x86 parallel port. However, the i2c-pport module offers access to the SAA1064 through a /dev directory device file that is not a /proc entry. All the Project Trailblazer interfaces to hardware occur as named /proc entries, and named /proc entries make bash control scripting simple.

It's possible to write an SAA1064 device driver by modifying the SAA1086_x86.c code shown in Listing 10.3 so that it provides named /proc entries. This device driver would access the /dev/i2c0 device file. Using this approach would require loading the i2c-core, i2c-algo-bit, i2c-dev, and i2c-pport modules, as well as the new device driver. It might be easier to write an I2C bit-banging x86 parallel printer port device driver and not use any lm_sensors code. Writing your own I2C bit-banging code is simple. Many I2C source code examples exist to help you understand the communication protocol. In fact, in the next section, you'll see an example because the Project Trailblazer engineers wrote an I2C bit-banging routine for the RPX-CLLF.

Connecting the SAA1064 to the RPX-CLLF

The RPX-CLLF's MPC860 contains a dedicated I2C controller. Use of the controller is more CPU efficient than bit-banging software. The current8xx lm_sensors code utilizes the MPC860's I2C controller. The lm_sensors device drivers provide access to I2C devices through /dev files. Ideally, use of these drivers should reduce Project Trailblazer development time because the engineers don't need a full understanding of MPC860 internals.

At first, the Project Trailblazer engineers were excited about using the lm_sensors code. However, when it came time to cross-compile code for the PowerPC, they changed their minds. Although 8xx files are contained within the lm_sensors project, they aren't kept current and aren't part of its configuration process. Cross-compiling isn't a simple make process. The engineers spent too much time patching and editing make files. They did successfully compile the device drivers. However, inserting modules resulted in errors because some assembly code was missing. Confused, the engineers read more about I2C on the PowerPC mailing list.5 They decided to abandon the lm_sensors code when they read about the I2C developer discussion concerning the I2C project restructuring: Accounts of unsuccessful compiling, missing assembly, and restructuring led the team to write their own SAA1064 device driver.

Right after successfully using the SA-1110's SSP controller for SPI communication, the engineers decided to investigate using the MPC860's I2C controller. Unfortunately, it wasn't simple. The MPC860's I2C controller uses interrupts, DMA, and memory buffers. The engineers simply didn't have the time or the expertise to figure all this out. Nor did they want to risk making Linux unstable by incorrectly configuring MPC860 registers.

The engineers chose to implement a MPC860 I2C bit-banging device driver for the SAA1064s. This device driver will provide a named /proc directory entry interface, allowing for easy bash scripting. The I2C bus communications will be slowed, to allow for longer bus wire lengths. Their driver will be simple and reliable, and it should provide exactly what the engineers need.

The hardware interface uses the MPC860's Port B because Port B offers open- collector/drain functionality. Remember that both I2C communication lines, SDA and SCL, are bidirectional. The unused lines PB30 and PB31 can serve as the SDA and SCL lines. Figure 10.7 shows the SAA1064 seven-segment LED driver connection to the RPX-CLLF.

Figure 10.7. The SAA1064 seven-segment LED driver connection to the RPX-CLLF.

graphics/10fig07.gif

The SAA1064_rpxcllf.c device driver code shown in Listing 10.4 configures the MPC860 registers and provides access to the SAA1064s through a /proc directory entry. For demonstration proposes, this device driver code only provides access to the SAA1064#0, at address 0x00.

Listing 10.4 The SAA1064_rpxcllf.c Device Driver
 /*  * SAA1064_rpxcllf v1.0 11/10/01  * www.embeddedlinuxinterfacing.com  *  * The original location of this code is  * http://www.embeddedlinuxinterfacing.com/chapters/10/  *  * Copyright (C) 2001 by Craig Hollabaugh  *  * This program is free software; you can redistribute it and/or modify  * it under the terms of the GNU Library General Public License as  * published by the Free Software Foundation; either version 2 of the  * License, or (at your option) any later version.  *  * This program is distributed in the hope that it will be useful, but  * WITHOUT ANY WARRANTY; without even the implied warranty of  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU  * Library General Public License for more details.  *  * You should have received a copy of the GNU Library General Public  * License along with this program; if not, write to the  * Free Software Foundation, Inc.,  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA  */ /*  * SAA1064_rpxcllf.c is based on procfs_example.c by Erik Mouw.  * For more information, please see The Linux Kernel Procfs Guide, Erik Mouw  * http://kernelnewbies.org/documents/kdoc/procfs-guide/lkprocfsguide.html  * */ /* SAA1064_rpxcllf  * This device driver demonstrates I2C communication with a SAA1064 LED  * display driver. The RPX-CLLF's MPC860 port B is used for I2C data (SDA)  * and clock (SCL) signals. This routine doesn't use the MPC860's I2C  * controller but implements a bit-banging algorithm.  *  * The driver creates a /proc directory entry called  * /proc/trailblazer/temperaturedisplay0. Scripts can write values to  * temperaturedisplay0 which are then displayed on the LED displays.  *  * This driver only communicates with a single SAA1064 at I2C bus address 0.  */ /* powerpc-linux-gcc -O2 -D__KERNEL__ -DMODULE -I/usr/src/powerpc-linux/include \  -c SAA1064_rpxcllf.c -o /tftpboot/powerpc-rootfs/tmp/SAA1064_rpxcllf.o */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/proc_fs.h> #include <asm/uaccess.h> #include <asm/io.h> #include <linux/delay.h> #include <asm/8xx_immap.h> #define MODULE_VERSION "1.0" #define MODULE_NAME "SAA1064_rpxcllf" #define SDA   0x00000001 #define SCL   0x00000002 #define DELAY 5 #define SAA1064ADDRESS 0x70 volatile immap_t *immap; /* references  * see section 34.3 Port B MPC860 PowerQUICC User's Manual  *  * For more SAA1064 or I2C protocol, visit the Philips Semiconductor Web site  * at http://www.semiconductors.philips.com  */ static struct proc_dir_entry  *tb_dir,                               *temperaturedisplay0_file; /* here are the I2C signaling macros */ #define SCLLow()  immap->im_cpm.cp_pbdat &= ~SCL #define SCLHigh() immap->im_cpm.cp_pbdat |=  SCL #define SDALow()  immap->im_cpm.cp_pbdat &= ~SDA #define SDAHigh() immap->im_cpm.cp_pbdat |=  SDA #define readSDA() (SDA == (immap->im_cpm.cp_pbdat & SDA)) #define readSCL() (SCL == (immap->im_cpm.cp_pbdat & SCL)) /*  * function startCommunication  * This function sets SDA and SCL in the idle state then  * initiates the 'start' condition  */ void startCommunication(void) {   SDAHigh();      /* put SDA in idle state    */   SCLHigh();      /* put SCL in idle state    */   udelay(DELAY);  /* let lines settle         */   SDALow();       /* initiate start condition */   udelay(DELAY);  /* let lines settle         */   SCLLow();       /* initiate start condition */ } /*  * function stopCommunication  * This function sets SDA and SCL in an known state then  * initiates the 'stop' condition  */ void stopCommunication(void) {   SCLLow();       /* put SCL in known state  */   SDALow();       /* put SDA in known state  */   udelay(DELAY);  /* let lines settle        */   SCLHigh();      /* initiate stop condition */   SDAHigh();      /* initiate stop condition */ } /*  * function receiveByte  * This function toggles the clock line while reading  * the transmitted data bits. I2C communications sends  * MSB first.  */ unsigned char receiveByte(void) {   unsigned char i, b;   SDAHigh();                 /* this tri-states SDA                      */   b = 0;   for (i = 0; i < 8; i++)   {     udelay(DELAY);            /* let lines settle                        */     SCLHigh();                /* send the clock                          */     udelay(DELAY);            /* let lines settle                        */     b = (b << 1) | readSDA(); /* shift the bits then OR the incoming bit */     SCLLow();                 /* send the clock                          */   }   /* this sets up for the next incoming byte */   udelay(DELAY);   SCLHigh();   udelay(DELAY*2);   SCLLow();   return b; } /*  * function sendByte  * This function toggles the clock line while transmitting  * data bits. I2C communications sends MSB first. This function  * allows monitors the acknowledge bit (bit 9) asserted by the  * receiver.  */ unsigned char sendByte(unsigned char b) {   unsigned char i;   for (i = 0; i < 8; i++)   {     if (0x80 == (b & 0x80)) /* is the MSB 0 or 1? */       SDAHigh();     else       SDALow();     udelay(DELAY);          /* let lines settle   */     SCLHigh();              /* send the clock     */     udelay(DELAY);          /* let lines settle   */     SCLLow();               /* send the clock     */     b = (b << 1);           /* shift to the left  */   }   /* this sets up for the next outgoing byte      */   udelay(DELAY);   SDAHigh();   SCLHigh();   udelay(DELAY*2); /* 2 delays help you see acks on a scope */   /* read the ack here, a sent ack is a 0. */   i = readSDA();   SCLLow();   return i; } /*  * function writei2c  * This function accepts an address, a character buffer and buffer length.  * It starts communication, sends the address, then the buffer.  * If an ack error occurs, it tells you (well it prints to the console).  */ unsigned char writei2c(unsigned address, unsigned char *buffer,                        unsigned length) {   unsigned char i, error;   startCommunication();   error = sendByte(address & 0xFE);     /* 0xFE? The last bit of the I2C address is a read/nWrite bit */   if (error) /* didn't get an ack at the address */   {     stopCommunication();     printk("no ack at address 0x%2X\n",address);     return error;   } /* sending the buffer here */   for (i = 0; i < length; i++)   {     error = sendByte(buffer[i]);          if (error) /* didn't get an ack for that byte sent */     {       stopCommunication();       printk("no ack at buffer byte %d\n",i);       return error;     }   } /* we're done */   stopCommunication();   return 0; } /*  * function readi2c  * This function accepts an address, a character buffer and buffer length.  * It starts communication, sends the address, then reads the reply into  * buffer. If an ack error occurs, it tells you (well it prints to the  * console).  */ unsigned char readi2c(unsigned address, unsigned char *buffer,                       unsigned length) {   unsigned char i, error;   startCommunication();   error = sendByte(address | 0x01);   if (error) /* didn't get an ack at the address */   {     stopCommunication();     printk("no ack at address 0x%2X\n",address);     return error;   }   /* receiving bytes for the buffer here */   for (i = 0; i < length; i++)   {     buffer[i] = receiveByte();   } /* we're done */   stopCommunication();   return 0; } /*  * function proc_write_display  * This function gets called when the user writes something to  * /proc/trailblazer/temperaturedisplay0. It contains a mapping array  * from numbers (0-9,a-f) to what segments to turn on  */ static int proc_write_temperaturedisplay0(struct file *file,                                           const char *buffer,                                           unsigned long count, void *data) {   unsigned char e, displaybuffer[5]; /* seg is a segment mapping table. Element 0, 0xFC, tells the  * SAA1064 to turn on the segments to display a 0. Likewise,  * seg's other entries map to 1 through 9 and a through f.  */   unsigned char seg[] = { 0xFC, 0x60, 0xDA, 0xF2, 0x66,                           0xB6, 0xBE, 0xE0, 0xFE, 0xF6,                           0xEE, 0x3E, 0x9C, 0x7A, 0x9E, 0x8E } ;   if (count >= 4)   {     displaybuffer[0] = 0x01;     displaybuffer[1] = seg[buffer[0]-'0']; /* subtracting '0' shifts the   */     displaybuffer[2] = seg[buffer[1]-'0']; /* ascii numbers the user wrote */     displaybuffer[3] = seg[buffer[2]-'0']; /* to the device file to their  */     displaybuffer[4] = seg[buffer[3]-'0']; /* numeric equivalent           */     e = writei2c(SAA1064ADDRESS, displaybuffer, 5); /* for debugging    printk("proc_write_display = %d\n",e); */   }   return 1; } /*  * function init_SAA1064_rpxcllf  * This function creates the /proc directory entries: trailblazer and  * trailblazer/temperaturedisplay0. It find the IMMR then configures  * PB30 and PB31 as general I/O, open drain and outputs.  * It initializes the SAA1064 and has it displays the middle segment '-'  * as a sign that the driver loaded successfully.  */ static int __init init_SAA1064_rpxcllf(void) {   unsigned char e,buffer[5];   int rv = 0; /* Create the trailblazer /proc entry */   tb_dir = proc_mkdir("trailblazer", NULL);   if(tb_dir == NULL) {           rv = -ENOMEM;           goto out;   }   tb_dir->owner = THIS_MODULE; /* Create temperaturedisplay0 and make it readable by all - 0444 */   temperaturedisplay0_file = create_proc_entry("temperaturedisplay0", 0444,                                                tb_dir);   if(temperaturedisplay0_file == NULL) {           rv = -ENOMEM;           goto no_temperaturedisplay0;   }   temperaturedisplay0_file->data = NULL;   temperaturedisplay0_file->read_proc = NULL;   temperaturedisplay0_file->write_proc = &proc_write_temperaturedisplay0;   temperaturedisplay0_file->owner = THIS_MODULE;   /* get the IMMR */   immap = (immap_t *)(mfspr(IMMR) & 0xFFFF0000);   /* make PB30 and PB31 general I/O */   immap->im_cpm.cp_pbpar &= ~SDA;   immap->im_cpm.cp_pbpar &= ~SCL;   /* make PB30 and PB31 open drain */   immap->im_cpm.cp_pbodr |= SDA;   immap->im_cpm.cp_pbodr |= SCL;   /* make PB30 and PB31 outputs    */   immap->im_cpm.cp_pbdir |= SDA;   immap->im_cpm.cp_pbdir |= SCL;   /* display a little info for the happy module loader */   printk("immr    = 0x%08X\n",immap);   printk("PBPAR   = 0x%04X\n",immap->im_cpm.cp_pbpar);   printk("PBDIR   = 0x%04X\n",immap->im_cpm.cp_pbdir);   printk("PBODR   = 0x%04X\n",immap->im_cpm.cp_pbodr);   printk("PBDAT   = 0x%04X\n",immap->im_cpm.cp_pbdat);   buffer[0] = 0x00; /* internal starting SAA1064 register address */   buffer[1] = 0x46; /* 12mA current, all digits on, dynamic mode  */   buffer[2] = 0x02; /* turn on '-' segment on each display        */   buffer[3] = 0x02;   buffer[4] = 0x02;   buffer[5] = 0x02;   if (writei2c(SAA1064ADDRESS, buffer, 6))   {     printk("Display initialization failed\n");     rv = -EREMOTEIO;     goto no_temperaturedisplay0;   }   else     printk("Display initialization passed\n"); /* everything initialized */   printk(KERN_INFO "%s %s initialized\n",MODULE_NAME, MODULE_VERSION);   return 0; no_temperaturedisplay0:   remove_proc_entry("temperaturedisplay0", tb_dir); out:   return rv; } /*  * function cleanup_SAA1064_rpxcllf  * This function turns off the SAA1064 display to show that the  * driver unloaded. It then removes the /proc directory entries  */ static void __exit cleanup_SAA1064_rpxcllf(void) {   unsigned char buffer[5];   buffer[0] = 0x00;   buffer[1] = 0x00; /* configuration reg, turn displays off */   writei2c(SAA1064ADDRESS, buffer, 2);   remove_proc_entry("temperaturedisplay0", tb_dir);   remove_proc_entry("trailblazer", NULL);   printk(KERN_INFO "%s %s removed\n", MODULE_NAME, MODULE_VERSION); } module_init(init_SAA1064_rpxcllf); module_exit(cleanup_SAA1064_rpxcllf); MODULE_AUTHOR("Craig Hollabaugh"); MODULE_DESCRIPTION("SAA1064 driver for RPX-CLLF"); EXPORT_NO_SYMBOLS; 

The SAA1064_rpxcllf.c device driver contains three main functions:

  • init_SAA1064_RPX-CLLF This is the module initialization function, and it creates the /proc directory entries trailblazer and trailblazer/temperaturedisplay0.

  • proc_write_temperaturedisplay0 This function is called whenever the user performs a write operation on /proc/trailblazer/temperaturedisplay0. proc_write_temperaturedisplay0 calls writei2c, which performs the actual I2C communication with the SAA1064.

  • cleanup_SAA1064_RPX-CLLF This is the module cleanup function, and it removes the /proc directory entries.

You can compile and test the SAA1064_rpxcllf device driver by using these steps:

  1. Compile the SAA1064_rpxcllf device driver by using this command:

     root@tbdev1[520]: powerpc-linux-gcc -O2 -D__KERNEL__ -DMODULE -I/usr/src/powerpc-linux/include \ -c SAA1064_rpxcllf.c \ -o /tftpboot/powerpc-rootfs/tmp/SAA1064_rpxcllf.o 
  2. Load the device driver on the RPX-CLLF by using this command:

     bash-2.04# insmod SAA1064_rpxcllf.o immr    = 0xFA200000 PBPAR   = 0x1CF0 PBDIR   = 0x1033 PBODR   = 0x0033 PBDAT   = 0x04B3 Display initialization passed SAA1064_rpxcllf 1.0 initialized 

    This console output shows the SAA1064 device driver initialized correctly. The SAA1064#0 LEDs have a dash character displayed.

  3. Use this command to write a number to the display:

     bash-2.04# echo  n 1234 > /proc/trailblazer/temperaturedisplay0 

    The LEDs now display 1234. The first two characters of an I2C communication with the SAA1064 is shown in Figure 10.8. This shows the first byte: address of 0x70 with a receiver acknowledge in bit 9 (the wider clock bit).

    Figure 10.8. An oscilloscope capture of SCL (top trace) and SDA (bottom trace) signals during an RPX-CLLF I2C communication with an SAA1064.

    graphics/10fig08.gif

TIP

When debugging I2C hardware and software, you can use an oscilloscope to look for the acknowledge bit of the address byte sent by the receiver. If the bit is there, you have successfully wired the circuit and sent the I2C device the correct start condition and its address. If the bit is not present, you need to check the circuit's wiring, signal and power supply voltage levels, and bus pull-up resistor values, and you need to closely examine the start condition and the data and clock signal timing. Also, while you're debugging the circuit, you should operate your I2C communication at the slowest possible speed. This gives your devices time to properly drive the data and clock signals. When your circuit functions correctly, you can remove delays and increase the communication speed.



       
    Top


    Embedded LinuxR. Hardware, Software, and Interfacing
    Embedded LinuxR. Hardware, Software, and Interfacing
    ISBN: N/A
    EAN: N/A
    Year: 2001
    Pages: 103

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net