hexaguin / SSD1327

A full framebuffer Arduino driver for 128x128 SSD1327 OLED grayscale modules.
GNU Lesser General Public License v3.0
13 stars 5 forks source link

Speed issue #2

Open bk4nt opened 4 years ago

bk4nt commented 4 years ago

Hello,

Thanks for that driver. I had a speed issue using it, and on screen flickering also. Then I noticed my CPU was taking 130 to 140ms to send the 8K buffer to the display. So I had a look into the source code.

Inside SSD1327::writeFullBuffer(), I noticed the 8K are sent out on a slow byte per byte basis:

    for(int i = 0; i < 8192; i++){
        writeData(frameBuffer[i]);

I changed this, for "writeData()" and adding SSD1327::writeData(void) function, for SPI.transfer(frameBuffer, sizeof(frameBuffer));:

    //for(int i = 0; i < 8192; i++){
        writeData();
    //}

void SSD1327::writeData(void){//Writes the buffer to the display's memory
    SPISettings settings(24000000, MSBFIRST, SPI_MODE1); // 3102us
    digitalWrite(_dc, HIGH);
    digitalWrite(_cs, LOW);
    SPI.beginTransaction(settings);
    SPI.transfer(frameBuffer, sizeof(frameBuffer));
    digitalWrite(_cs, HIGH);
    SPI.endTransaction();
}

As I still had slow transfer speeds, over 100ms, I added also some lines to force SPI to run at a higer speed (in SSD1327::init() speed is set to SPI_CLOCK_DIV2). I didn't check what exactly the clock rate now is with 24M (shall be supported by my ARM). But now the 8K buffer is pushed out in some 3103us... and what is on my display is like expected. I'll keep it so.

I think clear buffer could also run much faster with 'void memset ( void ptr, int value, size_t num );'. Currently it is a long running for loop and tests:

void SSD1327::clearBuffer(){//
    for(int i = 0; i < 8192; i++){
        if (frameBuffer[i]) { // If there is a non-zero (non-black) byte...
            frameBuffer[i] = 0;

After this, changedPixels could also be fully memset to true. Followed by a writeFullBuffer().

Additionnaly, does SSD1327::writeUpdates(){ work? Whilst testing it, my display only got corrupted.

Best regards

hexaguin commented 4 years ago

You are right, there are certainly some speed improvements to be had by doing a single transaction instead of byte by byte. I mostly did it this way to hopefully make the code modular and readable (and out of laziness).

Also, I hadn't noticed that the SPI clock macros on ESP and ARM devices actually scale way down to match the SPI speeds of an AVR, which is no bueno. I'll probably just remove the SPI speed setting altogether and let the user set the SPI speed before calling init().

As for writeUpdates() not working, that's a matter for another issue. I've created #3 to track this problem. I've had no issues on the ESP32 or ESP8266, but ARM is not well tested yet. Any insights you might have into this issue would be appreciated over there.

I'll certainly take a look at implementing these improvements in the near future. It'll be interesting to see how much I can improve the FPS of the driver with more performant SPI.

Oh, and welcome to Github!

bk4nt commented 4 years ago

Sorry, since I've read that with SPI.transfer, "In case of buffer transfers the received data is stored in the buffer in-place". I haven't time now to retest immediatly, but this was sure the reason why I couldn't use writeupdate after my clearBuffer/writedata(frameBuffer) sequence.

For the speed (I do need the CPU + SPI port available for other tasks), you shall also notice timings improvements with a slow chip (much less togglings of CE pin needed, much less function calls). Instead of pushing the buffer directly, you will so have to copy parts to a small 32 byte buffer, for the SPI.transfer.

The Arduino standard suggests to wrap beginTransaction/endTransation around any SPI access. Or concurent access to SPI port would change the port settings. So you shall offer SPI speed/CE pin options in the constructor, for later usage. See: https://www.arduino.cc/en/Tutorial/SPITransaction

  digitalWrite (slaveAPin, LOW);
  // reading only, so data sent does not matter
  stat = SPI.transfer(0);
  val1 = SPI.transfer(0);
  val2 = SPI.transfer(0);
  digitalWrite (slaveAPin, HIGH);
  SPI.endTransaction();

I didn't test following codes or suggestions, which can still remain very readable;

  uint8_t buffer[32];
  memcpy(&buffer, frameBuffer[i * 32], 32);
  writeData(buffer, 32);
}

void SSD1327::setContrast(uint8_t contrast){
  uint8_t sequence[2] = { 0x81, //set contrast control
                          0x00 } //Contrast byte
  sequence[1] = contrast;
  writeCmd(sequence); // buffered
}

void SSD1327::setWriteZone(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2) {
  sequence[6] = { 0X15, //Set Column Address
                  0x00,
                  0x00,
                  0x75, //Set Row Address
                  0x00,
                  0x00 };
  ...
  writeCmd(sequence, 6); // buffered
}

void SSD1327::initRegs(){
  uint8_t sequence[tbd] = { 0xae; //--turn off oled panel
                            0x15;  //set column addresses
                            ...
                            0xAF };
  writeCmd(sequence, tbd); // buffered
}