Teensy 3.0 and Adafruit TFT Library Compile Errors

Status
Not open for further replies.

dttworld

Member
First off, I love using the Teensy 2.0 for my projects because of the small size and USB debug port. I'm in the process of switching to the Teensy 3.0 to take advantage of the higher speed for driving SPI based TFT displays.

Here's my problem:

I have an Adafruit 1.8" TFT (ST7735) and can compile and run their GFX library demo on a Teensy 2.0 with no problems. However, when I try to compile the same code on a Teensy 3.0 I get these errors:

...Adafruit_ST7735.cpp: In constructor 'Adafruit_ST7735::Adafruit_ST7735(uint8_t, uint8_t, uint8_t, uint8_t, uint8_t)':
...Adafruit_ST7735.cpp:34:3: error: '_cs' was not declared in this scope
...Adafruit_ST7735.cpp:35:3: error: '_rs' was not declared in this scope
...Adafruit_ST7735.cpp:36:3: error: '_sid' was not declared in this scope
...Adafruit_ST7735.cpp:37:3: error: '_sclk' was not declared in this scope
...Adafruit_ST7735.cpp:38:3: error: '_rst' was not declared in this scope

...Adafruit_ST7735.cpp: In constructor 'Adafruit_ST7735::Adafruit_ST7735(uint8_t, uint8_t, uint8_t)':
...Adafruit_ST7735.cpp:47:3: error: '_cs' was not declared in this scope
...Adafruit_ST7735.cpp:48:3: error: '_rs' was not declared in this scope
...Adafruit_ST7735.cpp:49:3: error: '_rst' was not declared in this scope
...Adafruit_ST7735.cpp:51:3: error: '_sid' was not declared in this scope
...Adafruit_ST7735.cpp:51:11: error: '_sclk' was not declared in this scope

Again, I don't get these errors when compiling using the Teensy 2.0 as a target.

I would be grateful if someone could point me in the right direction to fix these Teensy 3.0 compile errors. My gut tells me someone here with deep c++ knowledge could easily fix this issue and save me a bunch of time tearing my hair out. I also believe that the speed of the Teensy 3.0 would provide a superior solution to driving these SPI based displays for things like animation.

I am running Arduino 1.0.4 with the Adafruit GFX library and associated display library for the ST7735. To recreate the compile problem run the "graphicstest.ino" program in the ST7735 sample directory.

Zip file attached.

Much thanks.
 

Attachments

  • Adafruit-ST7735-Library-master.zip
    30.3 KB · Views: 188
  • Adafruit-GFX-Library-master.zip
    8.6 KB · Views: 176
Near the bottom of the file "Adafruit_ST7735.h" are those variable definitions, but they're wrapped up with a conditional test for "__AVR__":

Code:
#ifdef __AVR__
volatile uint8_t *dataport, *clkport, *csport, *rsport;
  uint8_t  _cs, _rs, _rst, _sid, _sclk,
           datapinmask, clkpinmask, cspinmask, rspinmask,
           colstart, rowstart; // some displays need this changed
#endif //  #ifdef __AVR__

#if defined(__SAM3X8E__)
  Pio *dataport, *clkport, *csport, *rsport;
  uint32_t  _cs, _rs, _rst, _sid, _sclk,
            datapinmask, clkpinmask, cspinmask, rspinmask,
            colstart, rowstart; // some displays need this changed
#endif //  #if defined(__SAM3X8E__)

My guess is the code's working for the Teensy 2 because it's an AVR chip (so __AVR__ is defined) but not on the Teensy 3 because it's an ARM chip.

My lunch hour is almost over so I unfortunately don't have time to slog through all the code to see what's going on but it looks like you pass the constructor the SPI pins and it's saving them off here. You could try commenting out the conditionals to force it to define those variables and see if it works...

Good luck!
 
First off, I love using the Teensy 2.0 for my projects because of the small size and USB debug port. I'm in the process of switching to the Teensy 3.0 to take advantage of the higher speed for driving SPI based TFT displays.

Here's my problem:

I have an Adafruit 1.8" TFT (ST7735)

For some reason, all the Adafruit display libraries drive SPI displays through software (bit-banged) SPI. This has the advantage that you have a free choice of pins, but the disadvantage that it is being emulated in software (often using AVR-specific port operations) while the fast, native hardware sits there unused.

Having said which, the first thing is to get it working, then after that to get it working fast.

It was helpful of you to attach the code, and potatotron identified a problem with it. So I suggest adding, after the Arduino Due (SAM3X8E) section, a copy of the Due code which is most likely to be a good fit for the Teensy 3.0. This is because Due uses an ARM Cortex M3, Teensy 3.0 uses an ARM Cortex M4. Also, its good to make a test that will work with any future ARM-based Teensies as well. So:

Code:
#if defined(__arm__) && defined(CORE_TEENSY) 
  Pio *dataport, *clkport, *csport, *rsport;
  uint32_t  _cs, _rs, _rst, _sid, _sclk,
            datapinmask, clkpinmask, cspinmask, rspinmask,
            colstart, rowstart; // some displays need this changed
#endif //  #if defined(__arm__) && defined(CORE_TEENSY)

You can then modify that section as needed without breaking the Due part. You will need to look out for other #if defined(__SAM3X8E__) sections and duplicate those, as well.

Once you have it working, and if you find that it is noticeably slower than you want, it may be worth making a copy of the entire library and then editing that copy to use the hardware SPI on Teensy 3.0.
 
Thanks Potatotron, Paul and Nantonos for the help.

Here's what I did to make the Teensy 3 work with the latest Adafruit GFX library and ST7735 TFT driver:

1) I replaced 99% of the "#if defined(__AVR__)" lines with "#if defined(__AVR__) || ( defined(__arm__) && defined(CORE_TEENSY) )"

2) The only "if defined(__AVR__)" I kept had to do with the SPI clock speed in the Adafruit_ST7735.cpp file. I lifted some code below from the forum to run the Teensy 3 SPI @ 24mHz:

Code:
//***********************************************************************************************
#if defined(__AVR__) 
    SPI.setClockDivider(SPI_CLOCK_DIV4); // 4 MHz (half speed)
    //Due defaults to 4mHz (clock divider setting of 21)
#endif
#if defined(__SAM3X8E__)
    SPI.setClockDivider(21); // 4 MHz
    //Due defaults to 4mHz (clock divider setting of 21), but we'll set it anyway 
#endif    
	SPI.setBitOrder(MSBFIRST);
    SPI.setDataMode(SPI_MODE0);
#if defined(__arm__) && defined(CORE_TEENSY)
	#define BAUD_DIV 0 /* 24MHz SPI */	
	//#define BAUD_DIV 1 /* 12MHz SPI */
	//#define BAUD_DIV 2 /* 8MHz SPI */
	//#define BAUD_DIV 3 /* 6MHz SPI */
	//#define BAUD_DIV 4 /* 3MHz SPI */

    SPI0_CTAR0 = SPI_CTAR_FMSZ(7) | SPI_CTAR_PBR(0) | SPI_CTAR_BR(BAUD_DIV) | SPI_CTAR_CSSCK(BAUD_DIV) | SPI_CTAR_DBR;
#endif  
//********************************************************************************************************

I verified the code works properly with software and hardware SPI using a Teensy 2.0 AND 3.0
I also verified the SPI clock using a scope.

Next will be optimizing the code and runing experiments on how fast I can refresh the entire display.

Thanks Paul for the code at the Adafruit forum for the ILI9340C display. I am working on getting the Teensy 3.0 to work with even more Adafruit displays.


Danh
 
Last edited:
Great.

I hope you'll submit a pull request to Adafruit's github? They are usually pretty cautious about accepting them, so sometimes it can take time. But usually they do merge the code after it's been verified.
 
Great.

I hope you'll submit a pull request to Adafruit's github? They are usually pretty cautious about accepting them, so sometimes it can take time. But usually they do merge the code after it's been verified.

I've never done a pull request before but I'll use this as my first. Still working on testing and optimizing. I found by accessing the SPI register directly on the Teensy 3.0 during a write I can go from 15 fps to 60fps!
 
Great work, 4X improvement. :)

Is the code posted anywhere? Maybe posting a zip file here could be a good start?

On pull requests, I must confess I have no idea how to do it from the command line. I just fork the project on github, then do a fresh "git clone" of my new repository and copy the modified files into it. The a "git commit -a" and "git push" gets the changes up to github. The website has a button to submit the changes as a pull request.
 
Great work, 4X improvement. :)

Is the code posted anywhere? Maybe posting a zip file here could be a good start?

On pull requests, I must confess I have no idea how to do it from the command line. I just fork the project on github, then do a fresh "git clone" of my new repository and copy the modified files into it. The a "git commit -a" and "git push" gets the changes up to github. The website has a button to submit the changes as a pull request.

Will do. I speed up the SPI writes in all sections of the code and things stopped working. Tracked it down to a chip select timing issue. With the previous slower SPI writes the chip select timing was not violated after an enable. Problem goes away when I hardwire CS at the fast speeds. Things get messy now because I need to insert some extra delay times.
 
Will do. I speed up the SPI writes in all sections of the code and things stopped working. Tracked it down to a chip select timing issue. With the previous slower SPI writes the chip select timing was not violated after an enable. Problem goes away when I hardwire CS at the fast speeds. Things get messy now because I need to insert some extra delay times.

Attached is the modified ST7735 TFT driver for Teensy 3 compatibility. I looped through the "tft.fillscreen()" function and toggled an led to measure the fps.

Originally I called the spiwrite() function which loaded the SPDR register and waited for a bit to get set using "while(!(SPSR & _BV(SPIF)));"

Replacing that call with this explicit code below resulted in a 60 fps refresh of the tft.fillscreen() loop. The code was lifted from the forum and appears to work.
note: c is the byte to transmit

SPI0_PUSHR = ((c & 0xff) | SPI0_PUSHR_CTAS(0) | SPI0_PUSHR_CONT);
while ((SPI0_SR & 0x0000f000) >= 0x00004000);

There are timing issues with the chip select (CS) if you use this fast SPI write everywhere. I left the slower spiwrite() function in place and only modified it in the tft.fillscreen() function as part of my test.
 

Attachments

  • Adafruit_ST7735_teensy3_fast_spi.zip
    30.3 KB · Views: 215
  • Adafruit-GFX-Library-master.zip
    8.6 KB · Views: 221
I was getting inconsistent operation when trying to optimize the library for speed with the SPI set to 24mHz. I went back to the datasheet of the display driver and saw that the max clock frequency for the SPI was 66ns or ~15mHz :-(
 
Unfortunately I have never done a pull request before. I will probably just tell Adafriut about the changes in their forum and let them decide what to do after the holiday.
 
Paul, it would be good to have an ST7735 entry on the libraries page pointing to this thread.

I'm going to add this library and several other newer ones to the libraries page soon (and probably 1.18-rc2).

Here's my recent efforts with this library. This version has a highly optimized SPI implementation, similar to Peter Loveday's earlier work on this library. It also automatically falls back to bit bashing if you choose different pins, and it preserves Adafruit's code for non-Teensy boards.

https://github.com/PaulStoffregen/Adafruit_ST7735

I have a total of 5 new displays working on my desk right now, and a 6th one is supposed to be delivered today.
 
If the ILI9340 is the driver for the TM022HDH26 display, how would the ILI9340 library work on the ST7735 controller for a different display?
 
Last edited:
Questions regarding the Adafruit_ILI9340 library

In the following code in the Adafruit_ILI9340.cpp, why does_mosi = _sclk = 0?

Code:
// Constructor when using hardware SPI.  Faster, but must use SPI pins
// specific to each board type (e.g. 11,13 for Uno, 51,52 for Mega, etc.)
Adafruit_ILI9340::Adafruit_ILI9340(uint8_t cs, uint8_t dc, uint8_t rst) : Adafruit_GFX(ILI9340_TFTWIDTH, ILI9340_TFTHEIGHT) {
  _cs   = cs;
  _dc   = dc;
  _rst  = rst;
  hwSPI = true;
  _mosi  = _sclk = 0;
}

why aren't we passing in the value for pin location to _mosi and _sclk?

so when we have a data transfer using this method:
Code:
void Adafruit_ILI9340::writedata(uint8_t c) {
  SET_BIT(dcport,  dcpinmask);
  //digitalWrite(_dc, HIGH);
  CLEAR_BIT(clkport, clkpinmask);
  //digitalWrite(_sclk, LOW);
  CLEAR_BIT(csport, cspinmask);
  //digitalWrite(_cs, LOW);
  
  spiwrite(c);

  //digitalWrite(_cs, HIGH);
  SET_BIT(csport, cspinmask);
}

how do we stop the clk and know which pin to send data out of if they both = 0? Can someone explain what _dc/dcport is doing?
 
Last edited:
Adafruit's library is designed to use either hardware SPI (through the SPI library) or bit bashing, where the waveforms are created by manipulating the pins directly. The "hwSPI" variable tells it which technique to use. Notice that variable is set to true right before those 2 pin numbers are assigned zeros. The SPI library sends the bits, so those 2 pins don't need to be accessed directly.
 
If I were to connect the MOSI pin on the controller to say pin 11 on the teensy, how would the library know which pin on the teensy to send the data out? Could you point me to the section of code that takes care of that?
I understand the CS is active low and the CLK needs to pause during the data transfer, but why is DC being set high? And I still don't understand how the SPI library knows which pin on the slave to set low for CLK
 
Last edited:
I have a feeling setting DC high is asserting that pin as the SPI bus and then something in the SPI library "follows" that pin and that is the line transfer is sending its bytes. Is this correct?
 
I still don't understand how the SPI library knows which pin on the slave to set low for CLK

Inside the chip, the SPI port connects to pin 11, 12 and 13. So when you use SPI.transfer(), the data is always transmitted on pin 11, received on pin 12, with the clock on pin 13.

Now, there are some other minor details. The hardware has a way to route those signals to alternate pins. But by default, the SPI automatically uses those 3 pins, because that's the way things are wired inside the chip. Likewise, other features like the 3 serial ports and the I2C port (Wire library) only work on certain pins, because that's the way Freescale made the chip.

Every Teensy ships with a pinout reference card, which you can also download from the website, to document which pins have which features.

Many libraries that use SPI or I2C don't give you any choice at all for the pins to use. Or some SPI-ones only let you choose which pin for the CS signal (which lets you connect several different SPI devices to the 3 main signals). But Adafruit wanted to give people more flexibility, so they let you choose which pins you want to use. One way uses any 5 pins, but slow software-based pin manipulation is used. The other way that uses the fast SPI hardware doesn't let you choose 2 of the pins, because their locations are fixed by the hardware design.

All the official Arduino boards work the same way. The SPI and I2C and other communication signals are at specific pins.
 
Status
Not open for further replies.
Back
Top