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