Skip to content

File oled_ssd130x.h

File List > dev > oled_ssd130x.h

Go to the documentation of this file

Source Code

#pragma once
#ifndef SA_OLED_SSD130X_H
#define SA_OLED_SSD130X_H 
#include "per/i2c.h"
#include "per/spi.h"
#include "per/gpio.h"
#include "sys/system.h"
#include "stm32h7xx_hal.h"

namespace daisy
{
class SSD130xI2CTransport
{
  public:
    struct Config
    {
        Config()
        {
            // Intialize using defaults
            Defaults();
        }
        I2CHandle::Config i2c_config;
        uint8_t           i2c_address;
        void              Defaults()
        {
            i2c_config.periph         = I2CHandle::Config::Peripheral::I2C_1;
            i2c_config.speed          = I2CHandle::Config::Speed::I2C_1MHZ;
            i2c_config.mode           = I2CHandle::Config::Mode::I2C_MASTER;
            i2c_config.pin_config.scl = Pin(PORTB, 8);
            i2c_config.pin_config.sda = Pin(PORTB, 9);
            i2c_address               = 0x3C;
        }
    };
    void Init(const Config& config)
    {
        i2c_address_ = config.i2c_address;
        i2c_.Init(config.i2c_config);
    };
    void SendCommand(uint8_t cmd)
    {
        uint8_t buf[2] = {0X00, cmd};
        i2c_.TransmitBlocking(i2c_address_, buf, 2, 1000);
    };

    void SendData(uint8_t* buff, size_t size)
    {
        for(size_t i = 0; i < size; i++)
        {
            uint8_t buf[2] = {0X40, buff[i]};
            i2c_.TransmitBlocking(i2c_address_, buf, 2, 1000);
        }
    };

  private:
    daisy::I2CHandle i2c_;
    uint8_t          i2c_address_;
};

class SSD130x4WireSpiTransport
{
  public:
    struct Config
    {
        Config()
        {
            // Initialize using defaults
            Defaults();
        }
        SpiHandle::Config spi_config;
        struct
        {
            Pin dc;    
            Pin reset; 
        } pin_config;
        bool useDma;
        void Defaults()
        {
            // SPI peripheral config
            spi_config.periph = SpiHandle::Config::Peripheral::SPI_1;
            spi_config.mode   = SpiHandle::Config::Mode::MASTER;
            spi_config.direction
                = SpiHandle::Config::Direction::TWO_LINES_TX_ONLY;
            spi_config.datasize       = 8;
            spi_config.clock_polarity = SpiHandle::Config::ClockPolarity::LOW;
            spi_config.clock_phase    = SpiHandle::Config::ClockPhase::ONE_EDGE;
            spi_config.nss            = SpiHandle::Config::NSS::HARD_OUTPUT;
            spi_config.baud_prescaler = SpiHandle::Config::BaudPrescaler::PS_8;
            // SPI pin config
            spi_config.pin_config.sclk = Pin(PORTG, 11);
            spi_config.pin_config.miso = Pin(PORTX, 0);
            spi_config.pin_config.mosi = Pin(PORTB, 5);
            spi_config.pin_config.nss  = Pin(PORTG, 10);
            // SSD130x control pin config
            pin_config.dc    = Pin(PORTB, 4);
            pin_config.reset = Pin(PORTB, 15);
            // Using DMA off by default
            useDma = false;
        }
    };
    void Init(const Config& config)
    {
        // Initialize both GPIO
        pin_dc_.Init(config.pin_config.dc, GPIO::Mode::OUTPUT);
        pin_reset_.Init(config.pin_config.reset, GPIO::Mode::OUTPUT);

        // Initialize SPI
        spi_.Init(config.spi_config);

        // Reset and Configure OLED.
        pin_reset_.Write(0);
        System::Delay(10);
        pin_reset_.Write(1);
        System::Delay(10);
    };

    void SendCommand(uint8_t cmd)
    {
        pin_dc_.Write(0);
        spi_.BlockingTransmit(&cmd, 1);
    };

    void SendCommands(uint8_t* buff, size_t size)
    {
        pin_dc_.Write(0);
        spi_.BlockingTransmit(buff, size);
    };

    void SendData(uint8_t* buff, size_t size)
    {
        pin_dc_.Write(1);
        spi_.BlockingTransmit(buff, size);
    };

    void SendDataDma(uint8_t*                          buff,
                     size_t                            size,
                     SpiHandle::EndCallbackFunctionPtr end_callback,
                     void*                             context)
    {
        SCB_CleanInvalidateDCache_by_Addr(buff, size);
        pin_dc_.Write(1);
        spi_.DmaTransmit(buff, size, NULL, end_callback, context);
    };

  private:
    SpiHandle spi_;
    GPIO      pin_reset_;
    GPIO      pin_dc_;
};

class SSD130x4WireSoftSpiTransport
{
  public:
    struct Config
    {
        Config()
        {
            // Initialize using defaults
            Defaults();
        }
        struct
        {
            uint32_t sclk_delay;
            Pin      sclk;
            Pin      mosi;
            Pin      dc;
            Pin      reset;
        } pin_config;
        void Defaults()
        {
            pin_config.sclk_delay = 0; // fast as possible?!
            // SPI peripheral config
            pin_config.sclk = Pin(PORTD, 3); 
            pin_config.mosi = Pin(PORTC, 3); 
            // SSD130x control pin config
            pin_config.dc    = Pin(PORTC, 11); //D2
            pin_config.reset = Pin(PORTC, 10); //D3
        }
    };
    void Init(const Config& config)
    {
        // Initialize both GPIO
        pin_sclk_.Init(config.pin_config.sclk, GPIO::Mode::OUTPUT);
        pin_sclk_.Write(1); //ClockPolarity::LOW
        clk_delay = config.pin_config.sclk_delay;
        pin_mosi_.Init(config.pin_config.mosi, GPIO::Mode::OUTPUT);
        pin_mosi_.Write(0);

        pin_dc_.Init(config.pin_config.dc, GPIO::Mode::OUTPUT);
        pin_reset_.Init(config.pin_config.reset, GPIO::Mode::OUTPUT);

        // Reset and Configure OLED.
        pin_reset_.Write(0);
        System::Delay(10);
        pin_reset_.Write(1);
        System::Delay(10);
    };
    void SendCommand(uint8_t cmd)
    {
        pin_dc_.Write(0);
        SoftSpiTransmit(cmd);
    };

    void SendData(uint8_t* buff, size_t size)
    {
        pin_dc_.Write(1);
        for(size_t i = 0; i < size; i++)
            SoftSpiTransmit(buff[i]);
    };

  private:
    void SoftSpiTransmit(uint8_t val)
    {
        // bit flip
        val = ((val & 0x01) << 7) | ((val & 0x02) << 5) | ((val & 0x04) << 3)
              | ((val & 0x08) << 1) | ((val & 0x10) >> 1) | ((val & 0x20) >> 3)
              | ((val & 0x40) >> 5) | ((val & 0x80) >> 7);

        for(uint8_t bit = 0u; bit < 8u; bit++)
        {
            pin_mosi_.Write((val & (1 << bit)) ? 1 : 0);

            System::DelayTicks(clk_delay);

            pin_sclk_.Toggle();

            System::DelayTicks(clk_delay);

            pin_sclk_.Toggle();
        }
    }

    uint32_t clk_delay;
    GPIO     pin_sclk_;
    GPIO     pin_mosi_;
    GPIO     pin_reset_;
    GPIO     pin_dc_;
};


template <size_t width, size_t height, typename Transport>
class SSD130xDriver
{
  public:
    struct Config
    {
        typename Transport::Config transport_config;
    };

    void Init(Config config)
    {
        transport_.Init(config.transport_config);

        // Init routine...

        // Display Off
        transport_.SendCommand(0xaE);
        // Dimension dependent commands...
        switch(height)
        {
            case 16:
                // Display Clock Divide Ratio
                transport_.SendCommand(0xD5);
                transport_.SendCommand(0x60);
                // Multiplex Ratio
                transport_.SendCommand(0xA8);
                transport_.SendCommand(0x0F);
                // COM Pins
                transport_.SendCommand(0xDA);
                transport_.SendCommand(0x02);
                break;
            case 32:
                // Display Clock Divide Ratio
                transport_.SendCommand(0xD5);
                transport_.SendCommand(0x80);
                // Multiplex Ratio
                transport_.SendCommand(0xA8);
                transport_.SendCommand(0x1F);
                // COM Pins
                transport_.SendCommand(0xDA);
                if(width == 64)
                {
                    transport_.SendCommand(0x12);
                }
                else
                {
                    transport_.SendCommand(0x02);
                }

                break;
            case 48:
                // Display Clock Divide Ratio
                transport_.SendCommand(0xD5);
                transport_.SendCommand(0x80);
                // Multiplex Ratio
                transport_.SendCommand(0xA8);
                transport_.SendCommand(0x2F);
                // COM Pins
                transport_.SendCommand(0xDA);
                transport_.SendCommand(0x12);
                break;
            default: // 128
                // Display Clock Divide Ratio
                transport_.SendCommand(0xD5);
                transport_.SendCommand(0x80);
                // Multiplex Ratio
                transport_.SendCommand(0xA8);
                transport_.SendCommand(0x3F);
                // COM Pins
                transport_.SendCommand(0xDA);
                transport_.SendCommand(0x12);
                break;
        }

        // Display Offset
        transport_.SendCommand(0xD3);
        transport_.SendCommand(0x00);
        // Start Line Address
        transport_.SendCommand(0x40);
        // Normal Display
        transport_.SendCommand(0xA6);
        // All On Resume
        transport_.SendCommand(0xA4);
        // Charge Pump
        transport_.SendCommand(0x8D);
        transport_.SendCommand(0x14);
        // Set Segment Remap
        transport_.SendCommand(0xA1);
        // COM Output Scan Direction
        transport_.SendCommand(0xC8);
        // Contrast Control
        transport_.SendCommand(0x81);
        transport_.SendCommand(0x8F);
        // Pre Charge
        transport_.SendCommand(0xD9);
        transport_.SendCommand(0x25);
        // VCOM Detect
        transport_.SendCommand(0xDB);
        transport_.SendCommand(0x34);


        // Display On
        transport_.SendCommand(0xAF); //--turn on oled panel
    };

    size_t Width() const { return width; };
    size_t Height() const { return height; };

    void DrawPixel(uint_fast8_t x, uint_fast8_t y, bool on)
    {
        if(x >= width || y >= height)
            return;
        if(on)
            buffer_[x + (y / 8) * width] |= (1 << (y % 8));
        else
            buffer_[x + (y / 8) * width] &= ~(1 << (y % 8));
    }

    void Fill(bool on)
    {
        for(size_t i = 0; i < sizeof(buffer_); i++)
        {
            buffer_[i] = on ? 0xff : 0x00;
        }
    };

    void Update()
    {
        uint8_t i;
        uint8_t high_column_addr;
        switch(height)
        {
            case 32: high_column_addr = 0x12; break;

            default: high_column_addr = 0x10; break;
        }
        for(i = 0; i < (height / 8); i++)
        {
            transport_.SendCommand(0xB0 + i);
            transport_.SendCommand(0x00);
            transport_.SendCommand(high_column_addr);
            transport_.SendData(&buffer_[width * i], width);
        }
    };

    bool UpdateFinished() { return true; }

  protected:
    Transport transport_;
    uint8_t   buffer_[width * height / 8];
};

using SSD130x4WireSpi128x64Driver
    = daisy::SSD130xDriver<128, 64, SSD130x4WireSpiTransport>;

using SSD130x4WireSpi128x32Driver
    = daisy::SSD130xDriver<128, 32, SSD130x4WireSpiTransport>;

using SSD130x4WireSpi98x16Driver
    = daisy::SSD130xDriver<98, 16, SSD130x4WireSpiTransport>;

using SSD130x4WireSpi64x48Driver
    = daisy::SSD130xDriver<64, 48, SSD130x4WireSpiTransport>;

using SSD130x4WireSpi64x32Driver
    = daisy::SSD130xDriver<64, 32, SSD130x4WireSpiTransport>;

using SSD130xI2c128x64Driver
    = daisy::SSD130xDriver<128, 64, SSD130xI2CTransport>;

using SSD130xI2c128x32Driver
    = daisy::SSD130xDriver<128, 32, SSD130xI2CTransport>;

using SSD130xI2c98x16Driver = daisy::SSD130xDriver<98, 16, SSD130xI2CTransport>;

using SSD130xI2c64x48Driver = daisy::SSD130xDriver<64, 48, SSD130xI2CTransport>;

using SSD130xI2c64x32Driver = daisy::SSD130xDriver<64, 32, SSD130xI2CTransport>;

using SSD130x4WireSoftSpi128x64Driver
    = daisy::SSD130xDriver<128, 64, SSD130x4WireSoftSpiTransport>;


template <size_t width, size_t height, typename Transport>
class SSD1307Driver
{
  public:
    struct Config
    {
        typename Transport::Config transport_config;
    };

    void Init(Config config)
    {
        transport_.Init(config.transport_config);

        useDma_ = config.transport_config.useDma;

        // Init routine...
        uint8_t uDispayOffset;
        uint8_t uMultiplex;
        switch(height)
        {
            case 64:
                uDispayOffset = 0x60;
                uMultiplex    = 0x7F;
                break;

            case 80:
                uDispayOffset = 0x68;
                uMultiplex    = 0x4F;
                break;

            case 128:
            default:
                uDispayOffset = 0x00;
                uMultiplex    = 0x7F;
                break;
        }

        // Display Off
        transport_.SendCommand(0xaE);

        // Memory Mode
        transport_.SendCommand(0x20);

        // Normal Display
        transport_.SendCommand(0xA6);

        // Multiplex Ratio
        transport_.SendCommand(0xA8);
        transport_.SendCommand(uMultiplex);

        // All On Resume
        transport_.SendCommand(0xA4);

        // Display Offset
        transport_.SendCommand(0xD3);
        transport_.SendCommand(uDispayOffset);

        // Display Clock Divide Ratio
        transport_.SendCommand(0xD5);
        transport_.SendCommand(0x80);

        // Pre Charge
        transport_.SendCommand(0xD9);
        transport_.SendCommand(0x22);

        // Com Pins
        transport_.SendCommand(0xDA);
        transport_.SendCommand(0x12);

        // VCOM Detect
        transport_.SendCommand(0xDB);
        transport_.SendCommand(0x35);

        // Contrast Control
        transport_.SendCommand(0x81);
        transport_.SendCommand(0x80);

        // Display On
        transport_.SendCommand(0xAF);
    };

    size_t Width() const { return width; };
    size_t Height() const { return height; };

    void DrawPixel(uint_fast8_t x, uint_fast8_t y, bool on)
    {
        if(x >= width || y >= height)
            return;
        if(on)
            buffer_[x + (y / 8) * width] |= (1 << (y % 8));
        else
            buffer_[x + (y / 8) * width] &= ~(1 << (y % 8));
    }

    void Fill(bool on)
    {
        for(size_t i = 0; i < sizeof(buffer_); i++)
        {
            buffer_[i] = on ? 0xff : 0x00;
        }
    };

    void Update()
    {
        if(useDma_)
        {
            transferPagesCount_ = (height / 8);
            if(transferPagesCount_)
            {
                updateing_ = true;
                TransferPageDma(0);
            }
        }
        else
        {
            uint8_t i;
            uint8_t high_column_addr;
            switch(height)
            {
                case 32: high_column_addr = 0x12; break;

                default: high_column_addr = 0x10; break;
            }
            for(i = 0; i < (height / 8); i++)
            {
                transport_.SendCommand(0xB0 + i);
                transport_.SendCommand(0x00);
                transport_.SendCommand(high_column_addr);
                transport_.SendData(&buffer_[width * i], width);
            }
            updateing_ = false;
        }
    };

    bool UpdateFinished() { return !updateing_; }

  private:
    Transport transport_;
    uint8_t   buffer_[width * height / 8];
    bool      updateing_;
    uint8_t   transferPagesCount_;
    uint8_t   transferingPage_;
    bool      useDma_;

    void TransferPageDma(uint8_t page)
    {
        transferingPage_ = page;

        uint8_t high_column_addr;
        switch(height)
        {
            case 32: high_column_addr = 0x12; break;

            default: high_column_addr = 0x10; break;
        }
        uint8_t commands[] = {static_cast<uint8_t>(0xB0 + transferingPage_),
                              0x00,
                              high_column_addr};
        transport_.SendCommands(commands, 3);
        // transport_.SendCommand(0xB0 + transferingPage_);
        // transport_.SendCommand(0x00);
        // transport_.SendCommand(high_column_addr);
        transport_.SendDataDma(&buffer_[width * transferingPage_],
                               width,
                               SpiPageCompleteCallback,
                               this);
        //        transport_.SendDataDma(&buffer_[width * 16], width, SpiPageCompleteCallback, this);
    }

    void PageTransfered(void)
    {
        if(transferingPage_ < transferPagesCount_ - 1)
        {
            TransferPageDma(transferingPage_ + 1);
        }
        else
            updateing_ = false;
    }

    static void SpiPageCompleteCallback(void*                    context,
                                        daisy::SpiHandle::Result result)
    {
        static_cast<SSD1307Driver*>(context)->PageTransfered();
    }
};

using SSD13074WireSpi128x64Driver
    = daisy::SSD1307Driver<128, 64, SSD130x4WireSpiTransport>;

using SSD13074WireSpi128x80Driver
    = daisy::SSD1307Driver<128, 80, SSD130x4WireSpiTransport>;

using SSD13074WireSpi128x128Driver
    = daisy::SSD1307Driver<128, 128, SSD130x4WireSpiTransport>;

using SSD1307I2c128x64Driver
    = daisy::SSD130xDriver<128, 64, SSD130xI2CTransport>;

using SSD1307I2c128x80Driver
    = daisy::SSD1307Driver<128, 80, SSD130xI2CTransport>;

using SSD1307I2c128x128Driver
    = daisy::SSD130xDriver<128, 128, SSD130xI2CTransport>;


}; // namespace daisy


#endif