I have been getting a lot of emails asking why UTFT doesn't work when using a shield made for an Arduino 2009 or Uno on an Arduino Mega. I will try to explain why here and also show you how to make it work.
To understand this how-to you should have a basic understanding of Bitwise logic and Bitwise operations in C.
You should also have a basic understanding of how direct access to the ATmega ports work.
This How-To will only explain how to use 8bit display modules and shields on an Arduino Mega, but if you follow through you should be able to figure out the remaining bit for 16bit modules yourself.
First things first...
The reason for the pre-selected pins is really easy. SPEED. Making these modifications will have an impact on the speed as it will add a few clock cycles to every byte sent to the display. I have not tested this myself so I don't know how much it will impact the speed. It is up to you to determine if the speed is OK or if you should switch to a Mega shield.
With the current requirements I was able to get the highest possible speed when sending commands and data to the display module. It did also help that the shields I bought from ITead Studio had the same setup.
If you take a look at the pinmap for an Arduino Mega you can see that all the required pins for 8bit displays (Digital 22-29) are mapped to Port A.
Since Port A is an 8 bit port I was able to set all 8 pins at once with one simple statement:
PORTA = byte_to_send_to_the_display;
Since our goal here is to output the same byte of data to Digital 0-7 we will have to figure out which ports we have to use. If we look at the pinmap again we will see that the pins are mapped as follows:
Digital Pin 0 = PE0 (Port E - bit 0) Digital Pin 1 = PE1 (Port E - bit 1) Digital Pin 2 = PE4 (Port E - bit 4) Digital Pin 3 = PE5 (Port E - bit 5) Digital Pin 4 = PG5 (Port G - bit 5) Digital Pin 5 = PE3 (Port E - bit 3) Digital Pin 6 = PH3 (Port H - bit 3) Digital Pin 7 = PH4 (Port H - bit 4)
Whoever thought this was a good arrangement must have been drunk... But at least we know where all our bits should go.
Preparing to dive in...
Before we go into how we will spread our data all over the place there are one important thing we need to do. The pins all need to be set as outputs. Since UTFT was made to use Port A this is done, again with one simple statement:
DDRA = 0xFF;
This means that we set Data Direction Register for port A to all outputs.
Since our new pins are spread over three different ports it is easier to simply set them as outputs using pinMode():
pinMode(0, OUTPUT); pinMode(1, OUTPUT); . . . . . pinMode(7, OUTPUT);
You are now ready for the first actual change in the code. If you open HW_ATmega1280.h in your favourite text editor you will find the Data Direction statement you will need to replace with the pinMode() lines on line 105.
Make this change now before proceeding...
Diving in the easy way...
If we first take a look at the code we will change we can see that it is actually sending two bytes. The code can be found on lines 69-72 in HW_ATmega1280.h.
PORTA = VH; pulse_low(P_WR, B_WR); PORTA = VL; pulse_low(P_WR, B_WR);
First Port A is set to the value of VH, then the WR pin is sent a low pulse to signal the display to accept the data. Both steps are then repeated but this time Port A is set to the value of VL.
To set our new pins correctly we will just have to go though every bit of both bytes and set or clear the right bits in out output ports. To set or clear bits we can use these statements:
Set a bit: PORTx |= (1<<bit); Clear a bit: PORTx &= ~(1<<bit);
If we start with the Least Sigificant Bit (bit0) we already know that it should be output to Digital 0 which is Port E, bit 0. To set the pin correctly we can use the following code:
if (VH & 0x01) PORTE |= (1<<0); else PORTE &= ~(1<<0);
If bit 0 in VH is set (VH & 0x01) will return true, and therefore, through PORTE |= (1<<0); set bit 0 of Port E high. If bit 0 in VH is not set the PORTE &= ~(1<<0); will clear bit 0 in Port E (set it low).
The code for the next bit will be:
if (VH & 0x02) PORTE |= (1<<1); else PORTE &= ~(1<<1);
The same here... If bit 1 in VH is set (VH & 0x02) will return true, and therefore, through PORTE |= (1<<1); set bit 1 of Port E high. If bit 1 in VH is not set the PORTE &= ~(1<<1); will clear bit 1 in Port E.
The code for the rest of VH is:
if (VH & 0x04) PORTE |= (1<<4); else PORTE &= ~(1<<4);
if (VH & 0x08) PORTE |= (1<<5); else PORTE &= ~(1<<5);
if (VH & 0x10) PORTG |= (1<<5); else PORTG &= ~(1<<5);
if (VH & 0x20) PORTE |= (1<<3); else PORTE &= ~(1<<3);
if (VH & 0x40) PORTH |= (1<<3); else PORTH &= ~(1<<3);
if (VH & 0x80) PORTH |= (1<<4); else PORTH &= ~(1<<4);
After all the bits have be processed we will have to send a pulse on the WR pin to let the display know that the first byte is ready for transfer. This is also done with a predefined macro:
pulse_low(P_WR, B_WR);
When we have signalled the display to read the first byte we will have to do it all over again, but this time we will use the value fra VL instead of VH. When all bits are set corretly we will have to send another pulse on the WR pin:
if (VL & 0x01) PORTE |= (1<<0); else PORTE &= ~(1<<0);
if (VL & 0x02) PORTE |= (1<<1); else PORTE &= ~(1<<1);
if (VL & 0x04) PORTE |= (1<<4); else PORTE &= ~(1<<4);
if (VL & 0x08) PORTE |= (1<<5); else PORTE &= ~(1<<5);
if (VL & 0x10) PORTG |= (1<<5); else PORTG &= ~(1<<5);
if (VL & 0x20) PORTE |= (1<<3); else PORTE &= ~(1<<3);
if (VL & 0x40) PORTH |= (1<<3); else PORTH &= ~(1<<3);
if (VL & 0x80) PORTH |= (1<<4); else PORTH &= ~(1<<4);
pulse_low(P_WR, B_WR);
As you can see, the new code is a litte bit longer than the original 4 lines, but if you have done this right you should now be able to use an Arduino 2009/Uno display shield on an Arduino Mega.
Diving in the hard way...
If we look at the pinmap again we can see that all our outputs goes to only three different ports. This means that we can optimize the bit shuffeling a bit, but the logic behind this get a little more complex. Make sure you have read up on bitwise logic :)
If we start with the simplest first we can see that only one of our bit will go to Port G. That would be our data bit 4.
First of all we have to make sure that the bit in Port G is cleared. This can be done this way:
PORTG &= ~0x20;
Now that we know the state of out bit in the output port we can isolate the bit in our data with:
(VH & 0x10)
We the have to shift the bit over one spot to the left to get it into the correct position:
(VH & 0x10)<<1
Then we assign the whole shebang to Port G while making sure the remaining Port G bits are untouched:
PORTG |= (VH & 0x10)<<1;
Port H is almost as easy as both the bits we need will have to be shifted that same number of bits to the right. First we must clear the bits in Port H:
PORTH &= ~0x18;
Then we isolate the bits we need, shift them to the correct position and move them into Port H:
PORTH |= (VH & 0xC0)>>3;
Port E gets a little more complex, but is basically the same as the above only done in three shift operations. But first we must make sure Port E is ready for our bits:
PORTE &= ~0x3B;
Then we move the bits into place:
PORTE |= (VH & 0x03) + ((VH & 0x0C)<<2) + ((VH & 0x20)>>2);
Then we need to let the display know that the byte is ready by sending a pulse on the WR pin:
pulse_low(P_WR, B_WR);
Then we do the exact same thing once more with the data in VL:
PORTG &= ~0x20;
PORTG |= (VL & 0x10)<<1;
PORTH &= ~0x18;
PORTH |= (VL & 0xC0)>>3;
PORTE &= ~0x3B;
PORTE |= (VL & 0x03) + ((VL & 0x0C)<<2) + ((VL & 0x20)>>2);
pulse_low(P_WR, B_WR);
And that should be it...
End Of Line...
Remember to change the pin numbers for the control pins as well :)
I have tested this code myself, but there may still be some bugs that were able to sneak in. Please let me know if you think there is a bug in here somewhere.
Game Over.
|