Remember that strange phenomenon on the dual voltage I2C bus where I was getting 3.4V on the 2.8V bus and 4V on the 5V bus? Well, I finally found the culprit. Turns out that I had, with breathtaking unparalleled stupidity, plugged the two TO-92 2N7000 MOSFETs the wrong way around! The drain and source pins got reversed! So all the while when the bus was idle, current was flowing from the 5V rail to the 2.8V I2C bus (due to the inherent diode in MOSFETs). Almost certainly protection diodes on the MMA7660FC accelerometer chip were conducting, thus giving that extra 0.6 to 0.7V excess voltage reading. Because the chip is still ticking the current flowing through it must've been within limits. Had I decreased the pull-up resistors to below 1kohm, it (and therefore I) may not have been so lucky.
Interestingly, as we've seen, I2C communication works even with this reversed set up--with the lower voltage on the MOSFET drain side and the higher voltage bus on the source side. Let's analyze the circuit to see how and why it works. (We'll consider only one line since the explanation applies to both SDA and SCL.)
A. When neither the 7660 nor the MCU is pulling the line low--i.e., Q1 and Q2 are off--then MOSFET Q0 is off--because its source pin is at a higher voltage than the gate. Points V1 and V2 ought to be pulled up to VDD1 and VDD2, respectively. But due to Q0's internal diode--with the anode at VDD2 (5-volt) side--current flows from VDD2 toward VDD1 side. Since VDD2 > (VDD1 + diode voltage drop), the accelerometer's protection diode D1 conducts and so at point V1 we get a voltage less than VDD2 but greater than VDD1. We expect V1 = VDD1 + VFD1, where VFD1 = D1's forward voltage.
Because of Q0's diode we expect the voltage at point V2 = V1 + VFD0, where VFD0 = forward voltage of Q0's diode. End result is that both V1 and V2 are at logic high. Note that V2 may not reach a logic high if the voltage difference between VDD1 and VDD2 is large. For instance if VDD1 = 2.8V and VDD2 = 15V then, assuming the accelerometer hasn't been fried, the voltage at V2 would still be around 4V which may not be sufficient to meet the minimum voltage required for a logic high.
B. When Q2 turns on V2 is pulled to ground. Q0 source is likewise pulled to ground. Because the voltage difference between Q0's gate and source exceeds the gate threshold voltage VGS(th), Q0 switches on causing V1 to be pulled to nearly to ground as well. So both V1 and V2 are at logic low.
C. When Q1 turns on, V1 is pulled to ground. Because of Q0's internal diode V2 gets pulled down close to ground as well. (V2 will be approximately 0.7V, with the exact value depending on the voltage across Q1's drain and source). V1 and V2 are at logic low. [March 13 edit: V2 will actually be very close to ground because as in B above the voltage difference between Q0's gate and source exceeds the gate threshold voltage VGS(th) and thus Q0 turns on. With less than a couple of milliamps of current Q0 drain-source voltage is in the order of millivolts.]
I want to post the screenshots of the oscilloscope readings of the dual voltage I2C with the error corrected, but I still don't have my USB flash drive so I'll put the images up when I finally get it back.
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.
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.
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.
Sunday, March 11, 2012
MMA7660FC Accelerometer on dual voltage I2C bus
I recently got a Freescale MMA7660FC accelerometer already soldered on a breakout board. It's made by made by Tautic and comes complete with decoupling capacitors. I soldered a header to the board and plugged in a 5-pin connector. The connector wires plug into the breadboard. This way I can freely rotate the accelerometer.
To make sure the communications between the microcontroller and the accelerometer was going well and to view the values of X, Y, Z axes, I initially relied on the Saleae Logic. After the bugs had been ferreted out I wired in an LCD so I can get a real time feedback as I rotated the board.
Note that Tautic has opted to tie the analog (AVDD) and digital (DVDD) supply together. This means the chip cannot be set to OFF MODE wherein only the digital part of the circuit is powered. According to the datasheet in this mode the "MMA7660FC will not respond, but I2C bus can be used for other devices (MMA7660FC does not load I2C bus)." This mode has the least power consumption and would be useful in handheld devices which need to conserve as much power as possible.
While I'm quoting the datasheet, a caveat is in order. To have absolute faith in the 7660 datasheet is to court misery--as I learned. Even as it's already in its 7th revision it still has at least two egregious errors which caused me confusion, not to mention wasted time in getting this chip up and running.
The first brick wall I crashed into: "During the Active Mode, continuous measurement on all three axes is enabled. In addition, the user can choose to enable: Shake Detection, Tap Detection, Orientation Detection, and/or Auto-Wake/Sleep Feature..." Well I tried to change a few registers in Active Mode and it wouldn't let me. So I did it in Standby Mode. I'm not sure if none of the registers--save for MODE register 0x07--can be edited in Active Mode.
Second Freescale booboo: "In order to enable Tap detection in the device the user must enable the Tap Interrupt in the INTSU (0x06) register...." No! I disabled all interrupts and tap detection still works. Of course there won't be any interrupts sent via INT. But the tap detection bit in TILT register 0x03 still gets set when the chip is tapped.
Can Freescale engineers please get their documentation right? This isn't a 300-page doc. It shouldn't be that hard to finally--after so many revisions--get rid of all the errors.
The MMA7660 has a maximum VDD of 3.6V and a recommended VDD of 2.8V. Maximum voltage on any other pin should not exceed VDD. Since it communicates with the microcontroller via I2C the MCU must either run at the same voltage or if the MCU runs at a higher voltage then voltage level shifting hardware has to be present on the I2C bus.
The microcontroller I chose is the 20-pin PIC16F1828. Because I'm using a 5V Displaytech 162F LCD I wanted the MCU to run at 5V. This meant I had to find a way to have a dual voltage I2C bus. Fortunately, the engineers at Philips devised a very simple way to realize this years ago. Check out the following application notes:
AN97055 Bi-directional level shifter for I²C-bus and other systems
AN10441 Level shifting techniques in I2C-bus design
We need only connect two MOSFETS to the bus and we automatically have a bidirectional voltage translator, with electrical isolation between the two voltage levels. In the breadboarded circuit I had an LM317 voltage regulator supply 2.8V to the 7660 while the MCU and LCD were run at 5.2V. The MOSFETs were 2N7000.
The comparator isolates the INT pin from the MCU. It also provides either 0V or 5V signal to the MCU. Note that the accelerometer's INT pin is configurable either as open drain or push-pull. In this set up it should be set as push-pull. In the test circuit I didn't use a comparator. Instead I employed a simple diode to isolate and protect the accelerometer. The pull down resistor prevents the MCU input pin from floating when INT is low. The voltage output at the INT pin is actually above the minimum of 2.0V required by the TTL input of the MCU. Note that given an INT max output of 2.8V this diode method will not work with Schmitt trigger inputs on the microcontroller since they require a logic high to be at least 80% of VDD.
For this test circuit I've added a 3.3V Zener diode and a 27-ohm current limiting resistor to protect the accelerometer from any accidental overvoltages.
Here are screenshots of samples taken by the Saleae Logic analyzer on both the 5V and 2.8V I2C bus. You'll notice that the sampling rate is a mere 500kHz. This is a limitation of my computer. I don't have a USB 2.0 connection and therefor--according Logic--condemns me to this max rate. Else I'd definitely be sampling at >1MHz.
The following vid shows how the accelerator's measured values change as it's tapped, turned on its sides, and flipped over. The XYZ values shown on the LCD are in the range of -31 to +31. They are not percentages of g.
I've already tested the tap interrupt function but have yet to try the other interrupts as well as shake detection and auto-sleep / auto-wake functions.
In the firmware I rely on mikroC's built-in functions for accessing the I2C bus and the LCD. The splash screen routine wasn't installed in the firmware I used in the vid.
To make sure the communications between the microcontroller and the accelerometer was going well and to view the values of X, Y, Z axes, I initially relied on the Saleae Logic. After the bugs had been ferreted out I wired in an LCD so I can get a real time feedback as I rotated the board.
Note that Tautic has opted to tie the analog (AVDD) and digital (DVDD) supply together. This means the chip cannot be set to OFF MODE wherein only the digital part of the circuit is powered. According to the datasheet in this mode the "MMA7660FC will not respond, but I2C bus can be used for other devices (MMA7660FC does not load I2C bus)." This mode has the least power consumption and would be useful in handheld devices which need to conserve as much power as possible.
While I'm quoting the datasheet, a caveat is in order. To have absolute faith in the 7660 datasheet is to court misery--as I learned. Even as it's already in its 7th revision it still has at least two egregious errors which caused me confusion, not to mention wasted time in getting this chip up and running.
The first brick wall I crashed into: "During the Active Mode, continuous measurement on all three axes is enabled. In addition, the user can choose to enable: Shake Detection, Tap Detection, Orientation Detection, and/or Auto-Wake/Sleep Feature..." Well I tried to change a few registers in Active Mode and it wouldn't let me. So I did it in Standby Mode. I'm not sure if none of the registers--save for MODE register 0x07--can be edited in Active Mode.
Second Freescale booboo: "In order to enable Tap detection in the device the user must enable the Tap Interrupt in the INTSU (0x06) register...." No! I disabled all interrupts and tap detection still works. Of course there won't be any interrupts sent via INT. But the tap detection bit in TILT register 0x03 still gets set when the chip is tapped.
Can Freescale engineers please get their documentation right? This isn't a 300-page doc. It shouldn't be that hard to finally--after so many revisions--get rid of all the errors.
The MMA7660 has a maximum VDD of 3.6V and a recommended VDD of 2.8V. Maximum voltage on any other pin should not exceed VDD. Since it communicates with the microcontroller via I2C the MCU must either run at the same voltage or if the MCU runs at a higher voltage then voltage level shifting hardware has to be present on the I2C bus.
The microcontroller I chose is the 20-pin PIC16F1828. Because I'm using a 5V Displaytech 162F LCD I wanted the MCU to run at 5V. This meant I had to find a way to have a dual voltage I2C bus. Fortunately, the engineers at Philips devised a very simple way to realize this years ago. Check out the following application notes:
AN97055 Bi-directional level shifter for I²C-bus and other systems
AN10441 Level shifting techniques in I2C-bus design
We need only connect two MOSFETS to the bus and we automatically have a bidirectional voltage translator, with electrical isolation between the two voltage levels. In the breadboarded circuit I had an LM317 voltage regulator supply 2.8V to the 7660 while the MCU and LCD were run at 5.2V. The MOSFETs were 2N7000.
The comparator isolates the INT pin from the MCU. It also provides either 0V or 5V signal to the MCU. Note that the accelerometer's INT pin is configurable either as open drain or push-pull. In this set up it should be set as push-pull. In the test circuit I didn't use a comparator. Instead I employed a simple diode to isolate and protect the accelerometer. The pull down resistor prevents the MCU input pin from floating when INT is low. The voltage output at the INT pin is actually above the minimum of 2.0V required by the TTL input of the MCU. Note that given an INT max output of 2.8V this diode method will not work with Schmitt trigger inputs on the microcontroller since they require a logic high to be at least 80% of VDD.
For this test circuit I've added a 3.3V Zener diode and a 27-ohm current limiting resistor to protect the accelerometer from any accidental overvoltages.
Here are screenshots of samples taken by the Saleae Logic analyzer on both the 5V and 2.8V I2C bus. You'll notice that the sampling rate is a mere 500kHz. This is a limitation of my computer. I don't have a USB 2.0 connection and therefor--according Logic--condemns me to this max rate. Else I'd definitely be sampling at >1MHz.
The following vid shows how the accelerator's measured values change as it's tapped, turned on its sides, and flipped over. The XYZ values shown on the LCD are in the range of -31 to +31. They are not percentages of g.
I've already tested the tap interrupt function but have yet to try the other interrupts as well as shake detection and auto-sleep / auto-wake functions.
In the firmware I rely on mikroC's built-in functions for accessing the I2C bus and the LCD. The splash screen routine wasn't installed in the firmware I used in the vid.
/* Displaying the measurements of the Freescale MMA7660FC Accelerometer on an LCD Created: March 2012 Processor: PIC 16F1828 Compiler mikroC Pro 5.0.0 Configuration: power up timer, brownout reset (set to 2.5V), stack over/underflow reset -- all enabled, all others disabled internal oscillator with clk pins as I/O */ #define t1h_ini 256 - 122 // value loaded into TMR1H every time it rolls over to zero for timer1 to count 0.25sec // at a prescale = 1:8 and clock of 4MHz #define I2Cclock 100000 // mikroC needs to know the desired I2C clock frequency (in Hz) in order to compute baud rate generator values for PIC registers // MMA7660FC 7-bit address = 0x4C, left shift by one bit to become 0x98 in 8-bit format. // LSB = 0 to read slave, 1 to write to slave #define ACCwr 0x98 + 0 #define ACCrd 0x98 + 1 int8 XOUT, YOUT, ZOUT; // X,Y,Z axis values from accelerometer - register addresses 0x00, 0x01, 0x02 respectively // values range from -31 to +32, with negative values in 2's complement // if bit 5 = 1 then number is negative. To retrieve the value, get the complement and then add 1 // if bit 6 = 1 then value is not accurate because accelerometer was writing to register when it was read int8 TILT; // contains the value of register address 0x03 of accelerometer bit tap; // flag for tap detect // following are needed by mikroC for built-in LCD functions // Lcd pinout settings sbit LCD_RS at RC1_bit; sbit LCD_EN at RA2_bit; sbit LCD_D7 at RC4_bit; sbit LCD_D6 at RC3_bit; sbit LCD_D5 at RC6_bit; sbit LCD_D4 at RC7_bit; // Pin direction sbit LCD_RS_Direction at TRISC1_bit; sbit LCD_EN_Direction at TRISA2_bit; sbit LCD_D7_Direction at TRISC4_bit; sbit LCD_D6_Direction at TRISC3_bit; sbit LCD_D5_Direction at TRISC6_bit; sbit LCD_D4_Direction at TRISC7_bit; // ************************************************************************** /* for LCD with Hitachi HD44780 (or compatible) controller chip c1 = is the address of line 1 column 1 of the LCD l2c1 = line 2 column 1 */ // ************************************************************************** #define c1 0x00 + 0x80 #define c2 0x01 + 0x80 #define c3 0x02 + 0x80 #define c4 0x03 + 0x80 #define c5 0x04 + 0x80 #define c6 0x05 + 0x80 #define c7 0x06 + 0x80 #define c8 0x07 + 0x80 #define c9 0x08 + 0x80 #define c10 0x09 + 0x80 #define c11 0x0a + 0x80 #define c12 0x0b + 0x80 #define c13 0x0c + 0x80 #define c14 0x0d + 0x80 #define c15 0x0e + 0x80 #define c16 0x0f + 0x80 #define c17 0x10 + 0x80 #define c18 0x11 + 0x80 #define c19 0x12 + 0x80 #define c20 0x13 + 0x80 #define c21 0x14 + 0x80 #define c22 0x15 + 0x80 #define c23 0x16 + 0x80 #define c24 0x17 + 0x80 #define c25 0x18 + 0x80 #define c26 0x19 + 0x80 #define c27 0x1a + 0x80 #define c28 0x1b + 0x80 #define c29 0x1c + 0x80 #define c30 0x1d + 0x80 #define c31 0x1e + 0x80 #define c32 0x1f + 0x80 #define c33 0x20 + 0x80 #define c34 0x21 + 0x80 #define c35 0x22 + 0x80 #define c36 0x23 + 0x80 #define c37 0x24 + 0x80 #define c38 0x25 + 0x80 #define c39 0x26 + 0x80 #define c40 0x27 + 0x80 #define l2c1 0x40 + 0x80 #define l2c2 0x41 + 0x80 #define l2c3 0x42 + 0x80 #define l2c4 0x43 + 0x80 #define l2c5 0x44 + 0x80 #define l2c6 0x45 + 0x80 #define l2c7 0x46 + 0x80 #define l2c8 0x47 + 0x80 #define l2c9 0x48 + 0x80 #define l2c10 0x49 + 0x80 #define l2c11 0x4a + 0x80 #define l2c12 0x4b + 0x80 #define l2c13 0x4c + 0x80 #define l2c14 0x4d + 0x80 #define l2c15 0x4e + 0x80 #define l2c16 0x4f + 0x80 #define l2c17 0x50 + 0x80 #define l2c18 0x51 + 0x80 #define l2c19 0x52 + 0x80 #define l2c20 0x53 + 0x80 #define l2c21 0x54 + 0x80 #define l2c22 0x55 + 0x80 #define l2c23 0x56 + 0x80 #define l2c24 0x57 + 0x80 #define l2c25 0x58 + 0x80 #define l2c26 0x59 + 0x80 #define l2c27 0x5a + 0x80 #define l2c28 0x5b + 0x80 #define l2c29 0x5c + 0x80 #define l2c30 0x5d + 0x80 #define l2c31 0x5e + 0x80 #define l2c32 0x5f + 0x80 #define l2c33 0x60 + 0x80 #define l2c34 0x61 + 0x80 #define l2c35 0x62 + 0x80 #define l2c36 0x63 + 0x80 #define l2c37 0x64 + 0x80 #define l2c38 0x65 + 0x80 #define l2c39 0x66 + 0x80 #define l2c40 0x67 + 0x80 #define int1 bit #define int8 unsigned char #define int16 unsigned int #define int32 unsigned long #define on 1 #define off 0 #define input 1 // for TRISx #define output 0 // for TRISx #define analog 1 // for ANSELx #define digital 0 // for ANSELx // ************************************************************************************************ // I2C // ************************************************************************************************ // initialize the accelerometer void ACCini() { // upon power up the MMA7660 defaults to Standby Mode // apparently the MMA7660 registers can only be changed when it's in Standby Mode I2C1_Init(I2Cclock); // mikroC needs to set up PIC I2C registers given the user desired I2C frequency I2C1_Start(); I2C1_Wr(ACCwr); // transmit MMA7660 address and write command I2C1_Wr(0x6); // point to register address 0x06 I2C1_Wr(0b0); // [reg addr 0x06] all interrupts disabled I2C1_Wr(0b0); // [reg addr 0x07] I2C1_Wr(0b0); // [reg addr 0x08] set to 120 samples/sec -- required for tap detect I2C1_Wr(0b01101111); // [reg addr 0x09] set I2C1_Wr(0x0F); // [reg addr 0x0A] I2C1_Stop(); // after registers have been initialized it can be put in Active Mode where it can start measuring values I2C1_Start(); I2C1_Wr(ACCwr); // issue MMA7660 address and write command I2C1_Wr(0x7); // point to register address 0x06 I2C1_Wr(0b11000001); // [reg addr 0x07] put MMA7660 in Active Mode. Upon power up chip enters Standby Mode // interrupt is push-pull, interrupt is active high I2C1_Stop(); } // void ACCini() // VAL contains the value of accelerometer value of the axes void DispAxis(int8 row, int8 col, int8 VAL) { #define pos 1 #define neg 0 bit sign; // 1 = number is positive; 0 = number is negative sign = pos; if (VAL.f5) // if bit5 is high then axis value is negative -- in two's complement { VAL.f6 = 1; // this bit is not part of the axis value so make it high so that when it's compelemented it'll be zero VAL.f7 = 1; // this bit is not part of the axis value so make it high so that when it's compelemented it'll be zero VAL = ~VAL + 1; // get the absolute value from the twos complement sign = neg; } if (sign == pos) Lcd_Chr(row,col, '+'); else Lcd_Chr(row,col, '-'); // display the tens and ones digit of the axis value Lcd_Chr_CP(VAL/10 + 0x30); // the ASCII code of a number is the number + 0x30 Lcd_Chr_CP(VAL%10 + 0x30); // the ASCII code of a number is the number + 0x30 } // read and display measurements from the accelerometer void ACCread() { int8 VAL; do { I2C1_Start(); I2C1_Wr(ACCwr); // issue MMA7660 adddress and write command I2C1_Wr(0x0); // point to desired register adddress I2C1_Repeated_Start(); I2C1_Wr(ACCrd); // issue MMA7660 adddress and read command XOUT = I2C1_Rd(1); YOUT = I2C1_Rd(1); ZOUT = I2C1_Rd(1); TILT = I2C1_Rd(0); I2C1_Stop(); } while (XOUT.f6 || YOUT.f6 || ZOUT.f6 || TILT.f6); // if bit6 of these registers is high then the registers were read while the chip was updating them // read the registers again since the values will be unreliable // X Y Z axes output DispAxis(1, 3, XOUT); DispAxis(1, 9, YOUT); DispAxis(1, 14, ZOUT); // Back / Front orientation VAL = TILT & 0b11; Lcd_Cmd(l2c1); if (!VAL) Lcd_Out_CP(" ? "); else if (VAL == 1) Lcd_Out_CP("FRONT"); else if (VAL == 2) Lcd_Out_CP("BACK "); else Lcd_Out_CP("error"); // Portrait / Landscape orientation Lcd_Cmd(l2c7); VAL = (TILT & 0b11100) >> 2; if (!VAL) Lcd_Out_CP(" ? "); else if (VAL == 1) Lcd_Out_CP("LEFT "); else if (VAL == 2) Lcd_Out_CP("RIGHT"); else if (VAL == 5) Lcd_Out_CP("DOWN "); else if (VAL == 6) Lcd_Out_CP("UP "); else Lcd_Out_CP("error"); // check for tap detect // single tap = (a tap is detected in the current I2C read) && (no tap was detected during the last I2C read || a double tap was detected during the previous read ) // double tap = a tap is detected in the current read && a single tap was detected in the last I2C read Lcd_Cmd(l2c13); if (TILT.f5) { if (!tap) { tap = 1; Lcd_Chr_CP('1'); } else { tap = 0; Lcd_Chr_CP('2'); } } else { Lcd_Chr_CP('-'); tap = 0; } } // void ACCread() // Splash screen and permanent text on LCD void DispIniText() { Lcd_Cmd(_LCD_CLEAR); // Clear display Lcd_Cmd(_LCD_CURSOR_OFF); // Cursor off // splash screen upon power up Lcd_Out(1, 1, "FreescaleMMA7660"); Lcd_Out(2, 2, "Accelerometer"); Delay_ms(3000); Lcd_Cmd(_LCD_CLEAR); // Clear display // Text that will always remain on screen Lcd_Out(1, 1, "X="); Lcd_Out(1, 7, "Y="); Lcd_Chr(1, 13, 'Z'); Lcd_Out(2, 14, "TAP"); } // ************************************************************************************************ // other functions // ************************************************************************************************ 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; TRISB.f7 = input; IOCBP.IOCBP7 = 1; IOCBF = 0; // Timer 1 TMR1L = 0; TMR1H = t1h_ini; PIR1.TMR1IF = 0; T1CON = 0b110001; // prescale = 1:8, timer1 clock = Fosc/4, timer1 oscillator off, timer1 on // Timer2 is used for state machine and switch debouncing timing tick // with clock = 2MHz, PR2 = 125, prescale = 1:16, postscale = 1:1 // TMR2 will count from zero to PR2 and timer2 interrupt occurs every 125*16 / (2MHz / 4) = 4ms = timer2 tick // T2CON = 0b110; // postscaler = 1:1, prescaler = 1:16, timer2 on // TMR2 = 0; // PR2 = 125; WDTCON = 0b1000; // prescale = 1:512 (16ms typical) // WDTE in configuration word is configured so that WDT enabled when MCU awake and disabled when MCU asleep tap = 0; } // void IniReg() // ************************************************************************************************ // main // ************************************************************************************************ void main() { IniReg(); Lcd_Init(); DispIniText(); ACCini(); while(1) { if (PIR1.TMR1IF) { TMR1H = t1h_ini; ACCread(); PIR1.TMR1IF = 0; } } } // void main()
Thursday, March 1, 2012
TNG traffic light - epilogue
Each of the two LED boards on top of the signal tower traffic light are joined to the "platform board" via 9 solder joints. I certainly would not rely on them to hold the boards together nor am I deluded enough to think the corners of the boards won't chip off when the kids start playing with the toy and quite predictably knock it over ... and over. Those boards simply aren't going to survive this school of--literally--hard knocks. So as was planned I've potted the copper side of the boards with clear epoxy. Not only will the plastic make the whole three-board structure rigid and strong, the epoxy will also seal the copper tracks, solder connections and component leads and protect them from corrosion as well as minimize if not prevent possible injury to the kids if they go about running their fingers on the LED pins.
The two sort of cream colored rings are more than conspicuous. For aesthetic reasons I actually would rather not have them except that they're absolutely necessary to soften the impact of any falls. Without them the canister and its cap and maybe even the colored acrylic(?) lenses will surely crack/break/shatter. The rings are made of soft rubber tubing (silicone I think) with an outer diameter of 10mm. To build and install the rings I first cut one piece equal to the circumference of the canister--50mm. I then glued the ends with cyanoacrylate superglue. After letting the glue set for a couple of minutes I coated the sides of the canister where the ring will be positioned with superglue. I slipped the ring on the canister and pressed the ring inwards to make sure there's good contact between the ring and canister. After it was securely bonded, I coated the top portion of that ring with superglue and placed the second ring on top of it. Hence, the upper ring is bonded only to the lower ring, not to the canister. This way I can push that top ring down when the canister cap has to be screwed off. The rings have to be as close to the cap as possible to make sure the face of the 7-segment LED does not hit the ground when the tower tips over.
I've tested the light with power on and knocked it down a couple of times and for now at least it can take a lickin' but keeps on tickin.
Just a couple of facts I failed to mention in the previous blog entry.
The green/ed on-time can be changed any time--in any mode. However, after doing so the light will immediately enter normal traffic light mode.
You'll notice the 5-pin header around the center of the main board. That's for the ICSP connection to a PICkit 2 or 3. I foresee the kids wanting some changes to how the light works--a max green/red on-time of 90 seconds perhaps, or different non numeric characters when the amber/red lights are flashing. The ICSP connection will make firmware updates a breeze. I've actually used it already. The firmware I initially uploaded to the MCU (whilst on the breadboard) had a tiny bug--upon power-up the bulbs aren't all off and so--since the circuit defaults to a flashing amber upon turn-on--either or both the red and green lights are also on. Fail! After inserting a call to the function turning all bulbs off, I used the PICkit 2's Programmer-To-Go feature by uploading the firmware to the PICkit 2 and then plugging it into the board's ICSP headers and pressing the PICkit's red button to upload to the MCU. Note the board has to be powered up when uploading.
While breadboarding the circuit I discovered that the resistors pulling the bases of the PNPs to the 12VDC supply were actually unnecessary. The LED display works fine without them. But I retained them anyway just to make sure the transistor bases aren't floating.
The two sort of cream colored rings are more than conspicuous. For aesthetic reasons I actually would rather not have them except that they're absolutely necessary to soften the impact of any falls. Without them the canister and its cap and maybe even the colored acrylic(?) lenses will surely crack/break/shatter. The rings are made of soft rubber tubing (silicone I think) with an outer diameter of 10mm. To build and install the rings I first cut one piece equal to the circumference of the canister--50mm. I then glued the ends with cyanoacrylate superglue. After letting the glue set for a couple of minutes I coated the sides of the canister where the ring will be positioned with superglue. I slipped the ring on the canister and pressed the ring inwards to make sure there's good contact between the ring and canister. After it was securely bonded, I coated the top portion of that ring with superglue and placed the second ring on top of it. Hence, the upper ring is bonded only to the lower ring, not to the canister. This way I can push that top ring down when the canister cap has to be screwed off. The rings have to be as close to the cap as possible to make sure the face of the 7-segment LED does not hit the ground when the tower tips over.
I've tested the light with power on and knocked it down a couple of times and for now at least it can take a lickin' but keeps on tickin.
Just a couple of facts I failed to mention in the previous blog entry.
The green/ed on-time can be changed any time--in any mode. However, after doing so the light will immediately enter normal traffic light mode.
You'll notice the 5-pin header around the center of the main board. That's for the ICSP connection to a PICkit 2 or 3. I foresee the kids wanting some changes to how the light works--a max green/red on-time of 90 seconds perhaps, or different non numeric characters when the amber/red lights are flashing. The ICSP connection will make firmware updates a breeze. I've actually used it already. The firmware I initially uploaded to the MCU (whilst on the breadboard) had a tiny bug--upon power-up the bulbs aren't all off and so--since the circuit defaults to a flashing amber upon turn-on--either or both the red and green lights are also on. Fail! After inserting a call to the function turning all bulbs off, I used the PICkit 2's Programmer-To-Go feature by uploading the firmware to the PICkit 2 and then plugging it into the board's ICSP headers and pressing the PICkit's red button to upload to the MCU. Note the board has to be powered up when uploading.
While breadboarding the circuit I discovered that the resistors pulling the bases of the PNPs to the 12VDC supply were actually unnecessary. The LED display works fine without them. But I retained them anyway just to make sure the transistor bases aren't floating.
Subscribe to:
Posts (Atom)