Monday, March 12, 2012

Quirks on dual voltage I2C bus / Using a 5V LCD with an MCU at less than 5V

[Update: I know what's causing the overvoltage condition on the I2C. Find out over here.]

Previously I talked about how a MMA7660FC accelerometer was supplied with 2.8V while the microcontroller was run at 5.2V. Because the two chips communicate via I2C a voltage level shifter using MOSFETs was employed. To make sure the voltage levels on the bus were within specs I took some oscilloscope readings .Unfortunately, unlike the scope readings in the AN97055 Bi-directional level shifter for I²C-bus and other systems, mine show that the 2.8V side of the I2C is at around 3.4V while the 5V bus is at 4.0V. This is strange and I have yet to find an explanation for this. Intriguingly the voltage difference between 2.8V and 3.4V is the forward voltage of a diode.

Here are screenshots of the readings. I've also taken shots of the rise/fall times.

A. 2.8-volt I²C






B. 5-volt I²C.






Pull resistors are 4.7K on both the 2.8V and 5V bus. I also tested 10K pull ups. As expected rise time increased.

As stated in the previous blog entry the reason I ran the MCU at 5V is that I'm using a 5V LCD. It turns out, however, that the LCD is fine with signals way below its minimum required VDD of 4.5V. According to the Displaytech 162F datasheet the minimum voltage for a logic high  (VIH) is 2.2V. So running the MCU at 2.8V and feeding the LCD a signal of 2.5-2.8V would be acceptable. Thus, if there are no other hardware on the circuit which requires the MCU to run at a higher voltage than 2.8-3.0V then the following simpler circuit (than the one with MOSFETs and additional pull ups) can be used.


Notice that the LCD's RW pin is connected to ground. RW is kept at logic low when writing to the LCD or issuing commands to it. It is only brought high when reading from the LCD. The most important use of the read operation is to check whether the LCD is busy--we cannot write or issue a command while it's still processing. To read the LCD, the MCU pins connected to DB4 to DB7 must first be tristated--the pins configured as inputs. We then poll DB7 until it goes low--signifying the LCD is ready. However, in lieu of reading the LCD, we can just wait a few milliseconds so the LCD can finish whatever it's doing. This is the method the mikroC compiler employs. It does not tristate the PIC pins. It just issues a delay of 5.5ms. Apparently this is the maximum amount of time generic LCDs need. So with RW tied to ground, this is the only method of ensuring we don't write to the LCD while it's still busy.

Besides freeing up one MCU pin, using this delay method ensures no overvoltage condition will exist on the MCU pins. Remember that in our circuit MCU VDD < LCD VDD. When the MCU pins are tristated and the LCD is read from the signals from the LCD are close to LCD VDD of 5V. As per datasheet PIC pins are not supposed to have voltages > MCU VDD.

So what happens if we have signal voltages on the PIC pins greater than its VDD? Well the protection diodes start conducting. The following schematic is from the PIC16F1828 datasheet and clearly shows the diodes on the I/O pins.


Actually the voltage on the MCU pins can exceed MCU VDD. But the current through the diodes has to be limited so as not to fry the chip. In fact even mains voltages of 110 or 220V can be applied to the MCU so long as a resistor of at least 1Mohm is placed in series to severely limit the current flowing into the chip.

To test how the MCU will stand up to the 5V signal voltages from the LCD--without any series current-limiting resistor--I had the MCU use the polling method for determining the busy status of the LCD. The RW pin is connected to the MCU of course. Firmware is as follows.

#define  en        PORTA.f2
#define  rs        PORTC.f1
#define  rw        PORTC.f0
#define  db7       PORTC.f4
#define  db6       PORTC.f3
#define  db5       PORTC.f6
#define  db4       PORTC.f7
#define  trisdb7   TRISC.f4
#define  trisdb6   TRISC.f3
#define  trisdb5   TRISC.f6
#define  trisdb4   TRISC.f7

#define  clearlcd            0x1

#define  int1                bit
#define  int8                unsigned char
#define  int16               unsigned int
#define  int32               unsigned long

#define  on                  1
#define  off                 0

#define  hi                  1
#define  lo                  0

#define  input               1         // for TRISx
#define  output              0         // for TRISx

#define  analog              1         // for ANSELx
#define  digital             0         // for ANSELx

 //************************************************************************************************
 //    LCD subroutines
 //************************************************************************************************

void DelayMillisec(int8 ms)
{
  int8 i;
  for (i=0; i<=ms; i++)
    Delay_ms(1);
}

void PulseEN()
{
  en = hi;
  asm {nop}            // one cycle delay
  en = lo;
}

void CheckLCDbusy()
{
  bit lcdbusy;

  en = lo;
  rs = lo;
  rw = hi;

  // change data bits to input
  trisdb7 = input;
  trisdb6 = input;
  trisdb5 = input;
  trisdb4 = input;

  lcdbusy = 1;
  while(lcdbusy)
  {
    en = hi;
    if (!db7)
      lcdbusy = 0;
    en = lo;
    PulseEN();                // dummy read lower nibble; this is necessary since both nibbles must be read
  }

  // change data bits back to output
  trisdb7 = output;
  trisdb6 = output;
  trisdb5 = output;
  trisdb4 = output;

  rw = lo;
}

void SendNibbles(int8 text)
{
  db7 = text.f7;
  db6 = text.f6;
  db5 = text.f5;
  db4 = text.f4;
  PulseEN();

  db7 = text.f3;
  db6 = text.f2;
  db5 = text.f1;
  db4 = text.f0;
  PulseEN();
}

void WriteTextLCD(int8 text)
{
  CheckLCDbusy();
  en = lo;
  rs = hi;
  SendNibbles(text);
}

void SendCommandLCD(int8 text)
{
  CheckLCDbusy();
  en = lo;
  rs = lo;
  SendNibbles(text);
}

void IniLCD()
{
  DelayMillisec(15);
  en = 0;
  rs = 0;
  rw = 0;
  db7 = 0;
  db6 = 0;
  db5 = 1;
  db4 = 1;
  PulseEN();

  Delay_ms(5);
  PulseEN();

  Delay_us(160);
  PulseEN();

  Delay_us(160);
  db4 = 0;
  PulseEN();

  // lcd now in 4-bit operation mode
  // the SendCommandLCD() function can now be used to send all other initialization instructions
  SendCommandLCD(0b101000);            // Function Set: 4-bit data bus, 2-line display, 5x7 dots
  SendCommandLCD(0b1000);              // Display: display off
  SendCommandLCD(clearlcd);            // clear lcd screen-- clears DDRAM
  SendCommandLCD(0b110);               // Entry Mode: increment, display shift off
  SendCommandLCD(0b1100);              // Display: display on, cursor off, cursor blink off
  asm {clrwdt}
}

//************************************************************************************************


void IniReg()
{
  // set internal clock frequency to 4MHz
  OSCCON = 0b1101000;

  TRISA = output;
  TRISB = output;
  TRISC = output;
  ANSELA = digital;
  ANSELB = digital;
  ANSELC = digital;
  PORTA = 0;
  PORTB = 0;
  PORTC = 0;
} // void IniReg()


void main()
{
  IniReg();
  IniLCD();
  while(1)
  {
    WriteTextLCD('T');
    WriteTextLCD('e');
    WriteTextLCD('s');
    WriteTextLCD('t');
    WriteTextLCD(' ');
    WriteTextLCD('P');
    WriteTextLCD('I');
    WriteTextLCD('C');
    WriteTextLCD(' ');
    WriteTextLCD('p');
    WriteTextLCD('i');
    WriteTextLCD('n');
    WriteTextLCD('s');
    DelayMillisec(200);
    SendCommandLCD(clearlcd);
  }
} // void main()


With the circuit powered up I probed DB4 to DB7 using a Fluke 87V with its PEAK MIN MAX voltage detect enabled. As expected the voltage was significantly above its VDD of 2.81V. I recorded a maximum of 3.53V. The difference between VDD and the peak voltage is the voltage drop across the protection diode which is conducting due to the excess voltage on the I/O pin. I didn't bother measuring the current. I just left the circuit running for several minutes and from the looks of it, the MCU can take it.

I've also used the oscilloscope to measure the voltages and the waveforms are rather intriguing. Unfortunately I don't have my USB flash drive right now. I'll post screenshots when I get it back.

No comments:

Post a Comment