Working with the *old* Adafruit TFT libraries and changing pin assignments

I have a sketch that I worked out almost a year ago that worked on my Uno-and still does. I had bought a cheap 2.8" TFT display that I eventually got going with the Adafruit_GFX and Adafruit_TFTLCD libraries, before pin_magic.h and registers.h were part of the deal…

Now, I’ve finished my first “arduino compatable” board, using a Atmega2561 running Megacore. The pin assignments are quite different from the Uno, and I need to redefine them for the LCD-as the LCD is the only thing that will be plugged into the shield pinout on the board, eveything else is just brought out as seperate headers.

Now, in my old sketch, the control lines are defined-but the data lines aren’t defined, and I think in the TFTLCD library, they are not done using digitalWrite() methods but instead using direct AVR port calls. In effect…the libraries are hardwired for this LCD to be used on an Uno or Uno-compatable…which mine is not.

The below is my currently running Uno code:

#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_TFTLCD.h> // Hardware-specific library


//Define the LCD control lines
#define LCD_CS A3 // Chip Select goes to Analog 3
#define LCD_CD A2 // Command/Data goes to Analog 2
#define LCD_WR A1 // LCD Write goes to Analog 1
#define LCD_RD A0 // LCD Read goes to Analog 0

#define LCD_RESET A4 // Can alternately just connect to Arduino's reset pin

//Define some colors:
#define BLACK   0x0000
#define BLUE    0x001F
#define RED     0xF800
#define GREEN   0x07E0
#define CYAN    0x07FF
#define MAGENTA 0xF81F
#define YELLOW  0xFFE0
#define WHITE   0xFFFF

//configuring the data lines for tft library
Adafruit_TFTLCD tft(LCD_CS, LCD_CD, LCD_WR, LCD_RD, LCD_RESET);

void setup(void) {
  // setting up the serial console monitor
  Serial.begin(9600);
  Serial.println("serial console works"); 

  tft.reset(); //reset the display
  tft.begin(0x9341); //start up the display
  Serial.println("started the display, boss");
}
void loop(void) { 
  
 
  tft.setRotation(3); //landscape mode with data pins on the top and address lines on the bottom
  tft.fillScreen(BLACK); //this cleared the screen, but it did it every loop through-so display flickered for redraw

  //Filter Indicator
  tft.setCursor(6,6);
  tft.setTextColor(CYAN, BLACK);
  tft.setTextSize(2);
  tft.println("6Khz Filter");
  //tft.println("3Khz Filter");

  //Band Indicator, display indicated band
  tft.setCursor(6,25);
  tft.setTextColor(CYAN, BLACK);
  tft.setTextSize(2);
  tft.println("40M Band");
  //tft.println("20M band");
  //tft.println("10M Band");
  //tft.println("6M Band");

  //Memory Indicator
  tft.setCursor(220,6);
  tft.setTextColor(CYAN, BLACK);
  tft.setTextSize(2);
  tft.println("Memory A"); //replace A with A/B/C for whichever stored frequency is in position A/B/C

  //Setup Main TX Tuning Display
  tft.setCursor(6, 80); //Don't get closer than "6" from the edge, it's too close.
  tft.setTextColor(RED, BLACK);
  tft.setTextSize(2); //text size 2 is about 3/16" tall
  tft.println("TX");
  tft.setCursor(60, 80);
  tft.setTextColor(GREEN, BLACK);
  tft.setTextSize(3); //text size 4 is about 1/4" tall
  tft.println("Mhz"); //Make the "123" the Transmit Mhz tuning, "456" the Transmit khz tuning, and "789" the Transmit hz tuning later
  tft.setCursor(110, 80);
  tft.println("."); //seperator
  tft.setCursor(128, 80);
  tft.println("Khz"); //Khz tuning
  tft.setCursor(180, 80);
  tft.println("."); //seperator
  tft.setCursor(198, 80);
  tft.println("hz_"); //hz tuning
  tft.setCursor(280, 80);
  tft.setTextColor(YELLOW, BLACK);
  tft.setTextSize(2);
  tft.println("MHz");
  
   //Setup Main RX Tuning Display
  tft.setCursor(6, 136); //Don't get closer than "6" from the edge, it's too close.
  tft.setTextColor(RED, BLACK);
  tft.setTextSize(2); //text size 2 is about 3/16" tall
  tft.println("RX");
  tft.setCursor(60, 130);
  tft.setTextColor(GREEN, BLACK);
  tft.setTextSize(3); //text size 4 is about 1/4" tall
  tft.println("123"); //Make the "123" the Recive Mhz tuning, "456" the Recieve khz tuning, and "789" the Recieve hz tuning later
  tft.setCursor(110, 130);
  tft.println("."); //seperator
  tft.setCursor(128, 130);
  tft.println("456"); //Khz tuning
  tft.setCursor(180, 130);
  tft.println("."); //seperator
  tft.setCursor(198, 130);
  tft.println("789"); //hz tuning
  tft.setCursor(280, 136);
  tft.setTextColor(YELLOW, BLACK);
  tft.setTextSize(2);
  tft.println("MHz");
  

  //Setup to display Mode Indication, select only one at a time
  tft.setCursor(40, 180);
  tft.setTextColor(CYAN,BLACK);
  tft.setTextSize(3);
  tft.println("LSB");

  //tft.setCursor(40, 180);
  //tft.setTextColor(CYAN,BLACK);
  //tft.setTextSize(3);
  //tft.println("AM");

  //tft.setCursor(40, 180);
  //tft.setTextColor(CYAN,BLACK);
  //tft.setTextSize(3);
  //tft.println("USB");

  delay(5000); //slow the redraw flicker down for testing
}

and all it does is draw on the screen-no actual work yet. I’d like to at least get back to this point with the new board, but I’m at a loss as to how I’d have to do it.

Here’s how the display is connected to my current board; I hope it can be made to work! If not, well…I’ll trash the first 10 boards and start over.

Port-Uno shield-Pin defined in Megacore
PG1       A0          27
PC0       A1          28
PC1       A2          29
PC2       A3          30
PC3       A4          31
PC4       A5          32
PC5       D2          33
PC6       D3          34
PC7       D4          35
PG2       D5          36
PA7       D6          37
PA6       D7          38
PA5       D8          39
PA4       D9          40
PA3       D10        41
PA2       D11        42
PA1       D12        43
PA0       D13        44

Is there any hope? Have I just royally screwed up?

God provided the Mega2560 and it is cheap. Why would you want to use an ATmega2561 chip?
God invented shields. They plug into the Arduino.

No-one knows what pins you want to use for the data bus on your 2561. Why not use the same as for 2560?

No-one knows whether you have a hacked library or a genuine one.

David.

I used the ATMega2561 because I have them and I spent a few months designing an application specific board for it. I didn't use a Mega2560 because it won't fit in the case, the huge amount of jumper wires tends to make it unstable in an RF environment, and the TFT LCD display I have is setup as an Uno shield. I didn't buy 2560 chips because they were 10 to 15 times more expensive and the TQFP64 package was not supported at that time-it seems to be now. I did not fee like routing the TQFP100 package, nor did I need all the extra I/O for this project.

Seeing as I provided the 2561 port assignment, the Uno Shield pin number, and the Megacore pin assignment number, yes, you can look and see what pins I am using for the data bus on my 2561. All the pins are used as digital IO lines, so I fail to see why what I've done won't work once the code understands which pins to use.

As I stated, the library was obtained from Adafruit's Github some time ago. The newest library adds some extra dependencies for pin mapping and registers. Even after updating the library, the code as presented still compiles and runs. The issue is that in the new pin magic.h, which seems to be the way to change the pins, the code is commented on what it is doing, but not how it does it. I am not familiar enough with what I am doing here to be able to see if I can, or how I can, rework or add a new pin assignment to support my hardware.

Since god invented shields, maybe you can show me where to find a shield designed to live in an RF environment, that contains three TWI controlled flexible clock generators with low phase noise, high stability, and wide operating range; two high IIP3 mixers, a multi-mode demodulators and two multi-mode modulators, switchable bandpass filters for both IF and RF sections, and a 30W PA?

There is a reason I'm using what I'm using, and going through the extra work to get it to do what I need.

From the "TFTLCD-Library-master" directory on my PC:

// Shield pin usage:
// LCD Data Bit :    7    6    5    4    3    2    1    0
// Digital pin #:    7    6   13    4   11   10    9    8
// Uno port/pin :  PD7  PD6  PB5  PD4  PB3  PB2  PB1  PB0
// Mega port/pin:  PH4  PH3  PB7  PG5  PB5  PB4  PH6  PH5
// Leo port/pin :  PE6  PD7  PC7  PD4  PB7  PB6  PB5  PB4
// Due port/pin : PC23 PC24 PB27 PC26  PD7 PC29 PC21 PC22
// Breakout pin usage:
// LCD Data Bit :   7   6   5   4   3   2   1   0
// Uno dig. pin :   7   6   5   4   3   2   9   8
// Uno port/pin : PD7 PD6 PD5 PD4 PD3 PD2 PB1 PB0
// Mega dig. pin:  29  28  27  26  25  24  23  22
// Mega port/pin: PA7 PA6 PA5 PA4 PA3 PA2 PA1 PA0 (one contiguous PORT)
// Leo dig. pin :   7   6   5   4   3   2   9   8
// Leo port/pin : PE6 PD7 PC6 PD4 PD0 PD1 PB5 PB4
// Due dig. pin :  40  39  38  37  36  35  34  33
// Due port/pin : PC8 PC7 PC6 PC5 PC4 PC3 PC2 PC1 (one contiguous PORT. -ish…)

It looks as if it supports 2561 as well as 2560. If you select the "Breakout" the data bus is on PORTA. If you select the "Shield" it will use the "Adafruit shield" wiring.

We have no idea what "cheap 2.8" TFT display" you are using. The Blue 2.8" Mcufriend shields have the data bus in the "Breakout" pattern.

Seriously, life is much easier when you provide accurate links to the actual hardware and software you want to use. If you provide a schematic of your special hardware, you could get a tailored reply.

David.

This uses the "breakout" pin pattern. It's not an MCUfriend display, but it uses the same pinout.

I need to change the pin assignments inside that library, because none of the existing definitions are what I am using-the issue is that instead of defining the pins as individual pins, they're defined as macros that I am not entirely sure how to interpret. I have an idea of how they work, but not enough to know what to change.

For example, in the "mega w/breakout board" section of pin_magic.h:

#else // Mega w/Breakout board

  #define write8inline(d)   { PORTA = (d); WR_STROBE; }
  #define read8inline(result) { \
    RD_ACTIVE;                  \
    DELAY7;                     \
    result = PINA;              \
    RD_IDLE; }
  #define setWriteDirInline() DDRA  = 0xff
  #define setReadDirInline()  DDRA  = 0

 #endif

  // All of the functions are inlined on the Arduino Mega.  When using the
  // breakout board, the macro versions aren't appreciably larger than the
  // function equivalents, and they're super simple and fast.  When using
  // the shield, the macros become pretty complicated...but this board has
  // so much code space, the macros are used anyway.  If you need to free
  // up program space, some macros can be removed, at a minor cost in speed.
  #define write8            write8inline
  #define read8             read8inline
  #define setWriteDir       setWriteDirInline
  #define setReadDir        setReadDirInline
  #define writeRegister8    writeRegister8inline
  #define writeRegister16   writeRegister16inline
  #define writeRegisterPair writeRegisterPairInline

You can see that it's just using the 8 bits of PORTA such that PA0 = LCD bit 0, PA1 = LCD bit 1, etc. I need to understand how to change that such that:

PC5 = LCD bit 2, PC6 = LCD bit 3, PC7 = bit 4, PG2 = bit 5, PA7= bit 6, PA6 = bit 7

Looking at the other entries, it seems I would do this:

#else // Uno w/Breakout board

  #define write8inline(d) {                          \
    PORTC = (PORTC & B00000111) | ((d) & B11111000); \
    PORTG = (PORTG & B00100000) | ((d) & B11011111); \
    PORTA = (PORTA & B11111100) | ((d) & B00000011); \
    WR_STROBE; }
  #define read8inline(result) {                       \
    RD_ACTIVE;                                        \
    DELAY7;                                           \
    result = (PINC & B11111000) |(PING & B11011111) | (PINA & B00000011); \
    RD_IDLE; }
  #define setWriteDirInline() { DDRC |=  B11111000; DDRG |= B11011111; DDRA |=  B00000011; }
  #define setReadDirInline()  { DDRC &= ~B11111100; DDRG &= ~B11011111; DDRA &= ~B00000011; }

 #endif

  // As part of the inline control, macros reference other macros...if any
  // of these are left undefined, an equivalent function version (non-inline)
  // is declared later.  The Uno has a moderate amount of program space, so
  // only write8() is inlined -- that one provides the most performance
  // benefit, but unfortunately also generates the most bloat.  This is
  // why only certain cases are inlined for each board.

But, since I'm not really sure what that does, who knows if that's correct or not. It's also looking at the "Uno" section of the code, because that made more sense to try to edit than the Mega section, because the Uno code is already split across multiple ports.

Why don't you post the links as requested?
And say exactly which Shield pin you want to connect to which PORT pin. e.g. LCD_D6 to PA7

Life is considerably simpler if you use contiguous bits in PORTA. The library is already prepared for that.

Yes, you can use random pins. The macros become a bit horrible and slow. But hey-ho, if that is what you want. If you designed your own pcb, you would route the pins conveniently.

David.

@Xnke

I see you have tried to map the UNO Arduino designation for pins onto the Mega Core pins thinking this makes it compatible. Unfortunately the Arduino pin numbers are abstracted from the real bit register allocations in different processors and this means that bits get scattered inconveniently when trying to use pins as a parallel port (8 bits in this case).

So you have ended up with display data pin to Atmega2561 port bit allocation that is rather inconvenient:

TFT Mega Core port and bit
D0 PA5
D1 PA4
D2 PC5
D3 PC6
D4 PC7
D5 PG2
D6 PA7
D7 PA6

This means you will need to do 6 shift operations to get the bits from the data value into the right bit position in each port, plus all the assocaited ANDs and ORs. So the “Leonardo w/Breakout board” example is going to be a better indicator of how this has to be done.

Essentially what you need to do is use the AND (&), OR (|) and shift (<< or >>) operators to isolate the bits to shift and combine into the registers. Read this too. Dont forget you need to do the reverse shifts for reading the display too (if this is needed) and also you need to set the right bits in the direction control registers of each port.

As David says, the penalty for this rather random bit mapping is going to be slower updates of the screen, whether this matters depends on you needs.

I guess you now realize that mapping D0-7 to PA0-7 would have made life so much easier. If you decide to re-layout the PCB then I would get the rest of your project running using the serial port for debugging in case there are other “gotchas” in the design, and salvage a board by lifting pins and hand wiring up the PA0-7 pins to test the display actually works.

Yes, the port-to-pin assignment is messy. But, the PCB layout is much cleaner. All of this could have been avoided if Port G wasn't broken up all over the processor, but it is what it is. Could I have aligned the port with the display? Yes, if I hadn't been under the impression that the port-to-pin mapping was taken care of by the pins_arduino.h file for Megacore. I simply put the LCD pins in sequence with the "pin number" assignment.

Megacore pinout:

My PCB:

With this layout, the display is physically supported by the thicker uC PCB, and the uC PCB is mounted on standoffs to the front panel-keeping the display in place without resorting to double sided tape or the like.

The rest of the project is well underway-so far half of my digital IO is working, the panel buttons and rotary encoder are working, the analog inputs are working properly, and the serial console is responding correctly. The TWI interface and encoder code both work together, but not on this board yet (development of each section of code was done on an Uno over the last year) and the display code will run, the serial console responds correctly, the control lines are working, but the data bus is still scrambled, so no display yet.

This is the display I am using, Link To Banggood blue display

I generally call your display a Blue 2.8" Mcufriend Shield

Life would be easier (for me) if you test the shield with MCUFRIEND_kbv

Add this block to mcufriend_shield.h

Then the library should accept your “MEGACORE” as a standard target.

#elif defined(__AVR_ATmega2561__) || defined(__AVR_ATmega1281__)       //regular UNO shield on MEGACORE 2561
#define RD_PORT PORTG
#define RD_PIN  1
#define WR_PORT PORTC
#define WR_PIN  0
#define CD_PORT PORTC
#define CD_PIN  1
#define CS_PORT PORTC
#define CS_PIN  2
#define RESET_PORT PORTC
#define RESET_PIN  3

#define AMASK         0xF0
#define CMASK         0xE0
#define GMASK         0x04
#define write_8(x)   {  PORTA &= ~AMASK; PORTC &= ~CMASK; PORTG &= ~GMASK; \
                        PORTA |= (((x) & (1<<0)) << 5); \
                        PORTA |= (((x) & (1<<1)) << 3); \
                        PORTC |= (((x) & (7<<2)) << 3); \
                        PORTG |= (((x) & (1<<5)) >> 3); \
                        PORTA |= (((x) & (1<<6)) << 1); \
                        PORTA |= (((x) & (1<<7)) >> 1); \
 }

#define read_8()      ( ((PINA & (1<<5)) >> 5)\
                      | ((PINA & (1<<4)) >> 3)\
                      | ((PINC & (7<<5)) >> 3)\
                      | ((PING & (1<<2)) << 3)\
                      | ((PINA & (1<<7)) >> 1)\
                      | ((PINA & (1<<6)) << 1)\
                      )
#define setWriteDir() { DDRA |=  AMASK; DDRC |=  CMASK; DDRG |=  GMASK;  }
#define setReadDir()  { DDRA &= ~AMASK; DDRC &= ~CMASK; DDRG &= ~GMASK;  }
#define write8(x)     { write_8(x); WR_STROBE; }
#define write16(x)    { uint8_t h = (x)>>8, l = x; write8(h); write8(l); }
#define READ_8(dst)   { RD_STROBE; dst = read_8(); RD_IDLE; }
#define READ_16(dst)  { uint8_t hi; READ_8(hi); READ_8(dst); dst |= (hi << 8); }

#define PIN_LOW(p, b)        (p) &= ~(1<<(b))
#define PIN_HIGH(p, b)       (p) |= (1<<(b))
#define PIN_OUTPUT(p, b)     *(&p-1) |= (1<<(b))

I will do a version for your pin_macros.h when you have tested it.

As you can see. It is very fiddly when you have to shift random bits around. It is easier for you to test it than me.

David.

I'll give it a shot right now. I recall trying it in the past and didn't have any luck-but that was a year ago and things may have changed/updated.

Edit:

The LCD works fine on my Uno with this library, running your Diagnose_TFT_Support example.

This is the return on the serial console:

Diagnose whether this controller is supported
There are FAQs in extras/mcufriend_how_to.txt

tft.readID() finds: ID = 0x9341


PORTRAIT is 240 x 320

Run the examples/graphictest_kbv sketch
All colours, text, directions, rotations, scrolls
should work.  If there is a problem,  make notes on paper
Post accurate description of problem to Forum
Or post a link to a video (or photos)

I rely on good information from remote users

David, your customized library works perfectly now. Thank you for your help.

This should be the end of the custom library stuff, everything seems to be functional now-I just have to rewrite the display setup for the new library but that won’t take an afternoon.

david_prentice:
I generally call your display a Blue 2.8" Mcufriend Shield

Life would be easier (for me) if you test the shield with MCUFRIEND_kbv

Add this block to mcufriend_shield.h

Then the library should accept your “MEGACORE” as a standard target.

#elif defined(__AVR_ATmega2561__) || defined(__AVR_ATmega1281__)       //regular UNO shield on MEGACORE 2561

#define RD_PORT PORTG
#define RD_PIN  1
#define WR_PORT PORTC
#define WR_PIN  0
#define CD_PORT PORTC
#define CD_PIN  1
#define CS_PORT PORTC
#define CS_PIN  2
#define RESET_PORT PORTC
#define RESET_PIN  3

#define AMASK        0xF0
#define CMASK        0xE0
#define GMASK        0x04
#define write_8(x)  {  PORTA &= ~AMASK; PORTC &= ~CMASK; PORTG &= ~GMASK;
                        PORTA |= (((x) & (1<<0)) << 5);
                        PORTA |= (((x) & (1<<1)) << 3);
                        PORTC |= (((x) & (7<<2)) << 3);
                        PORTG |= (((x) & (1<<5)) >> 3);
                        PORTA |= (((x) & (1<<6)) << 1);
                        PORTA |= (((x) & (1<<7)) >> 1);
}

#define read_8()      ( ((PINA & (1<<5)) >> 5)
                      | ((PINA & (1<<4)) >> 3)
                      | ((PINC & (7<<5)) >> 3)
                      | ((PING & (1<<2)) << 3)
                      | ((PINA & (1<<7)) >> 1)
                      | ((PINA & (1<<6)) << 1)
                      )
#define setWriteDir() { DDRA |=  AMASK; DDRC |=  CMASK; DDRG |=  GMASK;  }
#define setReadDir()  { DDRA &= ~AMASK; DDRC &= ~CMASK; DDRG &= ~GMASK;  }
#define write8(x)    { write_8(x); WR_STROBE; }
#define write16(x)    { uint8_t h = (x)>>8, l = x; write8(h); write8(l); }
#define READ_8(dst)  { RD_STROBE; dst = read_8(); RD_IDLE; }
#define READ_16(dst)  { uint8_t hi; READ_8(hi); READ_8(dst); dst |= (hi << 8); }

#define PIN_LOW(p, b)        (p) &= ~(1<<(b))
#define PIN_HIGH(p, b)      (p) |= (1<<(b))
#define PIN_OUTPUT(p, b)    *(&p-1) |= (1<<(b))




I will do a version for your pin_macros.h when you have tested it.

As you can see. It is very fiddly when you have to shift random bits around. It is easier for you to test it than me.

David.

david ,i’m use my own schematic for mega128 i’m use portA for 8bit data and portB for another,i’m use HX8357 with uno & adafruit TFTLCD all working well…but with my mega128 i’m realy confuse where must be change,i need referency thanks a lot…

If you want advice on a specific screen with a specific library, please post a link to your screen e.g. Ebay sale. Quote library version from Library Manager.

A 16-bit data bus generally uses PORTA, PORTC on a mega64/128/1280/1281/2560/2561.

David.

Hardcoded Pins, yea that sounds like a great idea. smh.

There are so many wires to connect. The wiring will be known at compile-time.
There is an argument for putting control pins in the constructor. But putting data bus pins in a constructor makes it complex and cripples performance.

I am trying to use TFTLCD because MCUFRIEND_kbv is useless unless really using a sheild.

Both TFT_eSPI and MCUFRIEND_kbv expect the data bus to be on the “D1 R32” style ESP32 shield wiring.

If you have good reason for changing the wiring, test with LCD_ID_readreg.ino
When your wiring matches your defines, paste the defines to your message. i.e. the registers read correctly.

We can show you how to use this wiring with TFT_eSPI and MCUFRIEND_kbv

I am testing TFTLCD with some baloney ILI9486 (according to readReg)

What controller is reported?

Adafruit_TFTLCD only supports a small number of controllers.

David.

Managed to get it with TFT_eSPI, the pins were redefined in user_setup.h

Its at the bottom and needs the define for ESP32 PARALLEL uncommented along with the pin defines below that.

The only detail I was able to recognize, from the readreg() , was the 9486 value.

It came from this lootbox style kit HackerBox 0053: Chromalux : 8 Steps - Instructables (see step 3 for image of my board, only accurate one I've seen)

Was meant to just jam it into a UNO and call it done, didnt want to. Prefer smaller boards.

3.5inch Arduino Display-UNO - LCD wiki is the documentation they offered for it, got misdirected a lot trying to match up details and searching.

Mine works now, 8 data pins have to be below pin 31, 5 others for CS/WR/RD/RST/RS doesnt seem to matter where they went(on my heltec board). Just wanted to give the details for any wanderers later.

Screen seems fast and its shiny+bright, seems pretty decent so far.

That is the board I connected it to^

Go on. You must have bought it from somewhere. The TFT Display Shield 3.5 inch 480x320 in the HackerBox link shows a regular Mucufriend Uno Shield.

If you bought it online, you will have a link.
If it fell from a lorry, you probably do not know any information.

The Hackerbox photo shows a ILI9486 display. TFT_eSPI will work fine.

David.