//----------------------------------------------------------------------------- // F34x_SMBus_EEPROM.c //----------------------------------------------------------------------------- // Copyright 2006 Silicon Laboratories, Inc. // http://www.silabs.com // // Program Description: // // This example demonstrates how the C8051F34x SMBus interface can communicate // with a 256 byte I2C Serial EEPROM (Microchip 24LC02B). // - Interrupt-driven SMBus implementation // - Only master states defined (no slave or arbitration) // - Timer1 used as SMBus clock source // - Timer2 used by SMBus for SCL low timeout detection // - SCL frequency defined by <SMB_FREQUENCY> constant // - Pinout: // P0.0 -> SDA (SMBus) // P0.1 -> SCL (SMBus) // // P2.2 -> LED // // all other port pins unused // // How To Test: // // 1) Download code to a 'F34x device that is connected to a 24LC02B serial // EEPROM (see the EEPROM datasheet for the pinout information). // 2) Run the code: // a) the test will indicate proper communication with the EEPROM by // turning on the LED at the end the end of the test // b) the test can also be verified by running to the if statements // in main and checking the sent and received values by adding // the variables to the Watch Window // // FID: 34X000078 // Target: C8051F34x // Tool chain: Keil C51 7.50 / Keil EVAL C51 // Command Line: None // // Release 1.0 // -Initial Revision (TP) // -30 MAR 2006 //
//----------------------------------------------------------------------------- // Includes and Device-Specific Parameters //-----------------------------------------------------------------------------
#include <C8051F340.h>
//----------------------------------------------------------------------------- // Global CONSTANTS //-----------------------------------------------------------------------------
// System clock frequency in Hz #define SYSCLK 12000000
#define SMB_FREQUENCY 10000 // Target SCL clock rate // This example supports between 10kHz // and 100kHz
#define WRITE 0x00 // SMBus WRITE command #define READ 0x01 // SMBus READ command
// Device addresses (7 bits, lsb is a don't care) #define EEPROM_ADDR 0x40 // Device address for slave target // Note: This address is specified // in the Microchip 24LC02B // datasheet. // SMBus Buffer Size #define SMB_BUFF_SIZE 0x08 // Defines the maximum number of bytes // that can be sent or received in a // single transfer
// Status vector - top 4 bits only #define SMB_MTSTA 0xE0 // (MT) start transmitted #define SMB_MTDB 0xC0 // (MT) data byte transmitted #define SMB_MRDB 0x80 // (MR) data byte received // End status vector definition
//----------------------------------------------------------------------------- // Global VARIABLES //----------------------------------------------------------------------------- unsigned char* pSMB_DATA_IN; // Global pointer for SMBus data // All receive data is written here
unsigned char SMB_SINGLEBYTE_OUT; // Global holder for single byte writes.
unsigned char* pSMB_DATA_OUT; // Global pointer for SMBus data. // All transmit data is read from here
unsigned char SMB_DATA_LEN; // Global holder for number of bytes // to send or receive in the current // SMBus transfer.
unsigned char WORD_ADDR; // Global holder for the EEPROM word // address that will be accessed in // the next transfer
unsigned char TARGET; // Target SMBus slave address
bit SMB_BUSY = 0; // Software flag to indicate when the // EEPROM_ByteRead() or // EEPROM_ByteWrite() // functions have claimed the SMBus
bit SMB_RW; // Software flag to indicate the // direction of the current transfer
bit SMB_SENDWORDADDR; // When set, this flag causes the ISR // to send the 8-bit <WORD_ADDR> // after sending the slave address.
bit SMB_RANDOMREAD; // When set, this flag causes the ISR // to send a START signal after sending // the word address. // For the 24LC02B EEPROM, a random read // (a read from a particular address in // memory) starts as a write then // changes to a read after the repeated // start is sent. The ISR handles this // switchover if the <SMB_RANDOMREAD> // bit is set.
bit SMB_ACKPOLL; // When set, this flag causes the ISR // to send a repeated START until the // slave has acknowledged its address
// 16-bit SFR declarations sfr16 TMR3RL = 0x92; // Timer3 reload registers sfr16 TMR3 = 0x94; // Timer3 counter registers
//sbit LED = P2^2; // LED on port P2.2
sbit SDA = P0^0; // SMBus on P0.0 sbit SCL = P0^1; // and P0.1
//----------------------------------------------------------------------------- // Function PROTOTYPES //-----------------------------------------------------------------------------
void SMBus_Init(void); void Timer1_Init(void); void Timer3_Init(void); void Port_Init(void);
void SMBus_ISR(void); void Timer3_ISR(void);
void EEPROM_ByteWrite(unsigned char addr, unsigned char dat); void EEPROM_WriteArray(unsigned char dest_addr, unsigned char* src_addr, unsigned char len); unsigned char EEPROM_ByteRead(unsigned char addr); void EEPROM_ReadArray(unsigned char* dest_addr, unsigned char src_addr, unsigned char len); void mDelayS( unsigned char s ); void mDelaymS( unsigned char ms );
//----------------------------------------------------------------------------- // MAIN Routine //----------------------------------------------------------------------------- // // Main routine performs all configuration tasks, then loops forever sending // and receiving SMBus data to the slave EEPROM.
void main (void) { // char in_buff[8] = {0}; // Incoming data buffer // char out_buff[8] = "ABCDEFG"; // Outgoing data buffer
// unsigned char temp_char; // Temporary variable // bit error_flag = 0; // Flag for checking EEPROM contents unsigned char i; // Temporary counter variable
PCA0MD &= ~0x40; // WDTE = 0 (disable watchdog timer)
// Set internal oscillator to highest // setting of 24500000 (or 12000000 for 'F320) OSCICN |= 0x03;
// If slave is holding SDA low because of an improper SMBus reset or error while(!SDA) { // Provide clock pulses to allow the slave to advance out // of its current state. This will allow it to release SDA. XBR1 = 0x40; // Enable Crossbar SCL = 0; // Drive the clock low for(i = 0; i < 255; i++); // Hold the clock low SCL = 1; // Release the clock while(!SCL); // Wait for open-drain // clock output to rise for(i = 0; i < 10; i++); // Hold the clock high XBR1 = 0x00; // Disable Crossbar }
Port_Init (); // Initialize Crossbar and GPIO
// LED = 0; // Turn off the LED before the test // starts
Timer1_Init (); // Configure Timer1 for use as SMBus // clock source
Timer3_Init (); // Configure Timer3 for use with SMBus // low timeout detect
SMBus_Init (); // Configure and enable SMBus
EIE1 |= 0x01; // Enable the SMBus interrupt
EA = 1; // Global interrupt enable EEPROM_ByteWrite(0x48, 0x01); mDelayS(1); while(1){ EEPROM_ByteWrite(0x64, 0x3f); mDelayS(1); EEPROM_ByteWrite(0x68, 0x3f); mDelayS(1); EEPROM_ByteWrite(0x6A, 0x3f); mDelayS(1); }
}
//----------------------------------------------------------------------------- // Initialization Routines //-----------------------------------------------------------------------------
//----------------------------------------------------------------------------- // SMBus_Init() //----------------------------------------------------------------------------- // // Return Value : None // Parameters : None // // The SMBus peripheral is configured as follows: // - SMBus enabled // - Slave mode disabled // - Timer1 used as clock source. The maximum SCL frequency will be // approximately 1/3 the Timer1 overflow rate // - Setup and hold time extensions enabled // - Free and SCL low timeout detection enabled // void SMBus_Init (void) { SMB0CF = 0x5D; // Use Timer1 overflows as SMBus clock // source; // Disable slave mode; // Enable setup & hold time extensions; // Enable SMBus Free timeout detect; // Enable SCL low timeout detect;
SMB0CF |= 0x80; // Enable SMBus; }
//----------------------------------------------------------------------------- // Timer1_Init() //----------------------------------------------------------------------------- // // Return Value : None // Parameters : None // // Timer1 is configured as the SMBus clock source as follows: // - Timer1 in 8-bit auto-reload mode // - SYSCLK / 12 as Timer1 clock source // - Timer1 overflow rate => 3 * SMB_FREQUENCY // - The maximum SCL clock rate will be ~1/3 the Timer1 overflow rate // - Timer1 enabled // void Timer1_Init (void) { // Make sure the Timer can produce the appropriate frequency in 8-bit mode // Supported SMBus Frequencies range from 10kHz to 100kHz. The CKCON register // settings may need to change for frequencies outside this range. #if ((SYSCLK/SMB_FREQUENCY/3) < 255) #define SCALE 1 CKCON |= 0x08; // Timer1 clock source = SYSCLK #elif ((SYSCLK/SMB_FREQUENCY/4/3) < 255) #define SCALE 4 CKCON |= 0x01; CKCON &= ~0x0A; // Timer1 clock source = SYSCLK / 4 #endif
TMOD = 0x20; // Timer1 in 8-bit auto-reload mode
TH1 = -(SYSCLK/SMB_FREQUENCY/12/3); // Timer1 configured to overflow at 1/3 // the rate defined by SMB_FREQUENCY
TL1 = TH1; // Init Timer1
TR1 = 1; // Timer1 enabled }
//----------------------------------------------------------------------------- // Timer3_Init() //----------------------------------------------------------------------------- // // Return Value : None // Parameters : None // // Timer3 configured for use by the SMBus low timeout detect feature as // follows: // - Timer3 in 16-bit auto-reload mode // - SYSCLK/12 as Timer3 clock source // - Timer3 reload registers loaded for a 25ms overflow period // - Timer3 pre-loaded to overflow after 25ms // - Timer3 enabled // void Timer3_Init (void) { TMR3CN = 0x00; // Timer3 configured for 16-bit auto- // reload, low-byte interrupt disabled
CKCON &= ~0x40; // Timer3 uses SYSCLK/12
TMR3RL = -(SYSCLK/12/40); // Timer3 configured to overflow after TMR3 = TMR3RL; // ~25ms (for SMBus low timeout detect)
EIE1 |= 0x80; // Timer3 interrupt enable TMR3CN |= 0x04; // Start Timer3 }
//----------------------------------------------------------------------------- // PORT_Init //----------------------------------------------------------------------------- // // Return Value : None // Parameters : None // // Configure the Crossbar and GPIO ports. // // P0.0 digital open-drain SMBus SDA // P0.1 digital open-drain SMBus SCL // // P2.2 digital push-pull LED // // all other port pins unused // // Note: If the SMBus is moved, the SCL and SDA sbit declarations must also // be adjusted. // void PORT_Init (void) { P0MDOUT = 0x00; // All P0 pins open-drain output
// P2MDOUT |= 0x04; // Make the LED (P2.2) a push-pull // output
XBR0 = 0x04; // Enable SMBus pins XBR1 = 0x40; // Enable crossbar and weak pull-ups
// P0 = 0xFF; }
//----------------------------------------------------------------------------- // SMBus Interrupt Service Routine (ISR) //----------------------------------------------------------------------------- // // SMBus ISR state machine // - Master only implementation - no slave or arbitration states defined // - All incoming data is written starting at the global pointer <pSMB_DATA_IN> // - All outgoing data is read from the global pointer <pSMB_DATA_OUT> // void SMBus_ISR (void) interrupt 7 { bit FAIL = 0; // Used by the ISR to flag failed // transfers
static char i; // Used by the ISR to count the // number of data bytes sent or // received
static bit SEND_START = 0; // Send a start
switch (SMB0CN & 0xF0) // Status vector { // Master Transmitter/Receiver: START condition transmitted. case SMB_MTSTA: SMB0DAT = TARGET; // Load address of the target slave SMB0DAT &= 0xFE; // Clear the LSB of the address for the // R/W bit SMB0DAT |= SMB_RW; // Load R/W bit STA = 0; // Manually clear START bit i = 0; // Reset data byte counter break;
// Master Transmitter: Data byte (or Slave Address) transmitted case SMB_MTDB: if (ACK) // Slave Address or Data Byte { // Acknowledged? if (SEND_START) { STA = 1; SEND_START = 0; break; } if(SMB_SENDWORDADDR) // Are we sending the word address? { SMB_SENDWORDADDR = 0; // Clear flag SMB0DAT = WORD_ADDR; // Send word address
if (SMB_RANDOMREAD) { SEND_START = 1; // Send a START after the next ACK cycle SMB_RW = READ; }
break; }
if (SMB_RW==WRITE) // Is this transfer a WRITE? {
if (i < SMB_DATA_LEN) // Is there data to send? { // send data byte SMB0DAT = *pSMB_DATA_OUT;
// increment data out pointer pSMB_DATA_OUT++;
// increment number of bytes sent i++; } else { STO = 1; // Set STO to terminte transfer SMB_BUSY = 0; // Clear software busy flag } } else {} // If this transfer is a READ, // then take no action. Slave // address was transmitted. A // separate 'case' is defined // for data byte recieved. } else // If slave NACK, { if(SMB_ACKPOLL) { STA = 1; // Restart transfer } else { FAIL = 1; // Indicate failed transfer } // and handle at end of ISR } break;
// Master Receiver: byte received case SMB_MRDB: if ( i < SMB_DATA_LEN ) // Is there any data remaining? { *pSMB_DATA_IN = SMB0DAT; // Store received byte pSMB_DATA_IN++; // Increment data in pointer i++; // Increment number of bytes received ACK = 1; // Set ACK bit (may be cleared later // in the code)
}
if (i == SMB_DATA_LEN) // This is the last byte { SMB_BUSY = 0; // Free SMBus interface ACK = 0; // Send NACK to indicate last byte // of this transfer STO = 1; // Send STOP to terminate transfer }
break;
default: FAIL = 1; // Indicate failed transfer // and handle at end of ISR break; }
if (FAIL) // If the transfer failed, { SMB0CF &= ~0x80; // Reset communication SMB0CF |= 0x80; STA = 0; STO = 0; ACK = 0;
SMB_BUSY = 0; // Free SMBus
FAIL = 0; }
SI = 0; // Clear interrupt flag }
//----------------------------------------------------------------------------- // Timer3 Interrupt Service Routine (ISR) //----------------------------------------------------------------------------- // // A Timer3 interrupt indicates an SMBus SCL low timeout. // The SMBus is disabled and re-enabled if a timeout occurs. // void Timer3_ISR (void) interrupt 14 { SMB0CF &= ~0x80; // Disable SMBus SMB0CF |= 0x80; // Re-enable SMBus TMR3CN &= ~0x80; // Clear Timer3 interrupt-pending flag SMB_BUSY = 0; // Free bus }
//----------------------------------------------------------------------------- // Support Functions //-----------------------------------------------------------------------------
//----------------------------------------------------------------------------- // EEPROM_ByteWrite () //----------------------------------------------------------------------------- // // Return Value : None // Parameters : // 1) unsigned char addr - address to write in the EEPROM // range is full range of character: 0 to 255 // // 2) unsigned char dat - data to write to the address <addr> in the EEPROM // range is full range of character: 0 to 255 // // This function writes the value in <dat> to location <addr> in the EEPROM // then polls the EEPROM until the write is complete. // void EEPROM_ByteWrite(unsigned char addr, unsigned char dat) { while (SMB_BUSY); // Wait for SMBus to be free. SMB_BUSY = 1; // Claim SMBus (set to busy)
// Set SMBus ISR parameters TARGET = EEPROM_ADDR; // Set target slave address SMB_RW = WRITE; // Mark next transfer as a write SMB_SENDWORDADDR = 1; // Send Word Address after Slave Address SMB_RANDOMREAD = 0; // Do not send a START signal after // the word address SMB_ACKPOLL = 1; // Enable Acknowledge Polling (The ISR // will automatically restart the // transfer if the slave does not // acknoledge its address.
// Specify the Outgoing Data WORD_ADDR = addr; // Set the target address in the // EEPROM's internal memory space
SMB_SINGLEBYTE_OUT = dat; // Store <dat> (local variable) in a // global variable so the ISR can read // it after this function exits
// The outgoing data pointer points to the <dat> variable pSMB_DATA_OUT = &SMB_SINGLEBYTE_OUT;
SMB_DATA_LEN = 1; // Specify to ISR that the next transfer // will contain one data byte
// Initiate SMBus Transfer STA =  |