tttapa / Control-Surface

Arduino library for creating MIDI controllers and other MIDI devices.
GNU General Public License v3.0
1.24k stars 139 forks source link

oled library #302

Open masydoblig opened 4 years ago

masydoblig commented 4 years ago

Hey Peter

How hard is it to use a different display?

i have an sh1106 SPI 128x64 which uses the U8g library?

Cheers Sam

tttapa commented 4 years ago

You can implement the DisplayInterface class: https://github.com/tttapa/Control-Surface/blob/master/src/Display/DisplayInterface.hpp

masydoblig commented 4 years ago

Hi Peter Trying to get this to work with the DisplayInterface class and im clearly not getting it right if you get a chance can you have a look.

using the library which works with the display (tested with examples provide in library) will be testing it on my Due board connected to the SPI headers

/**
 * An example demonstrating the use of DisplayElement%s to display information
 * from the DAW on a small OLED display.
 *
 * @boards  Mega
 * 
 * Connections
 * -----------
 * 
 * - 5:  Push button (to ground)
 * - 6:  Push button (to ground)
 * - 51  OLED Data/D1 (SPI MOSI)
 * - 52: OLED Clock/D0 (SPI SCK)
 * - 49: OLED Data/Command
 * - 53: OLED Cable Select
 * 
 * Add a capacitor between the reset pin of the display and ground, and a 
 * resistor from reset to 3.3V. The values are not critical, 0.1µF and 10kΩ 
 * work fine.  
 * You do need some way to reset the display, without it, it won't work.  
 * Alternatively, you could use an IO pin from the Arduino to reset the 
 * display, but this just "wastes" a pin.
 * 
 * Behavior
 * --------
 * 
 * - The time (bars, beats, fraction), play and record status are shown at the 
 *   top of the display.
 * - For each of the 8 first tracks, a VU level meter with peak indicator and
 *   a V-Pot ring showing the pan are displayed, as well as the the mute, solo 
 *   and record arm status.
 * - Two tracks are displayed at once. By pressing the push buttons connected
 *   to pins 5 and 6, you can cycle through four banks to display all 8 tracks.
 * 
 * Mapping
 * -------
 * 
 * Map "Control Surface" as a Mackie Control Universal unit in your DAW.
 * 
 * @note    There seem to be some differences in the way some applications 
 *          handle VU meters: some expect the hardware to decay automatically,
 *          some don't.  
 *          If you notice that the meters behave strangely, try both 
 *          MCU::VUDecay::Hold and MCU::VUDecay::Default, or try a different 
 *          decay time.
 * 
 * Written by PieterP, 2019-11-12  
 * https://github.com/tttapa/Control-Surface
 */

#include <Encoder.h> // Include the Encoder library.
// This must be done before the Control Surface library.
#include <Control_Surface.h> // Include the Control Surface library
// Include the display interface you'd like to use
#include <Display/DisplayInterface.hpp>
#include <Adafruit_SH1106.h>

// ----------------------------- MIDI Interface ----------------------------- //
// ========================================================================== //

/*
   Instantiate a MIDI interface to use for the Control Surface.
*/

USBMIDI_Interface midi;
// USBDebugMIDI_Interface midi(115200);

// ----------------------------- Display setup ------------------------------ //
// ========================================================================== //

/*
   Instantiate and initialize the SSD1306 OLED display
*/

constexpr uint8_t SCREEN_WIDTH = 128;
constexpr uint8_t SCREEN_HEIGHT = 64;

constexpr int8_t OLED_DC = 49;    // Data/Command pin of the display
constexpr int8_t OLED_RESET = -1; // Use the external RC circuit for reset
constexpr int8_t OLED_CS = 53;    // Chip Select pin of the display

constexpr uint32_t SPI_Frequency = SPI_MAX_SPEED;

//Instantiate the displays
/*Adafruit_SSD1306 ssd1306Display = {
  SCREEN_WIDTH, SCREEN_HEIGHT, &SPI,          OLED_DC,
  OLED_reset,   OLED_CS,       SPI_Frequency,
};
*/
/*
#define OLED_MOSI   9
#define OLED_CLK   10
#define OLED_DC    11
#define OLED_CS    12
#define OLED_RESET 13
*/
Adafruit_SH1106 display(OLED_DC, OLED_RESET, OLED_CS);
// --------------------------- Display interface ---------------------------- //
// ========================================================================== //

// Implement the display interface, specifically, the begin and drawBackground
// methods.
/*class MySSD1306_DisplayInterface : public SSD1306_DisplayInterface {
 public:
  MySSD1306_DisplayInterface(Adafruit_SSD1306 &display)
    : SSD1306_DisplayInterface(display) {}

  void begin() override {
    // Initialize the Adafruit_SSD1306 display
    if (!disp.begin())
      FATAL_ERROR(F("SSD1306 allocation failed."), 0x1306);

    // If you override the begin method, remember to call the super class method
    SSD1306_DisplayInterface::begin();
  }

  void drawBackground() override { disp.drawLine(1, 8, 126, 8, WHITE); }

} display = ssd1306Display;
*/
class DisplayInterface : public Print {
 public:
  DisplayInterface(Adafruit_SH1106 &display)
    : DisplayInterface(display) {}

  void begin() {
    // Initialize the Adafruit_SSD1306 display
//    if (!disp.begin())
//      FATAL_ERROR(F("SH1106 allocation failed."), 0x1306);

    // If you override the begin method, remember to call the super class method
    DisplayInterface::begin();
  }

  //void drawBackground(){ };
  //void display();

}; //display = SH1106Display;

// ------------------------------- Bank setup ------------------------------- //
// ========================================================================== //

/*
   Create a bank and a bank selector to change its setting.
*/

Bank<4> bank(2); // Create a new bank with two tracks per bank

// Create a new bank selector to control the bank using two push buttons
IncrementDecrementSelector<4> bankselector = {bank, {5, 6}, Wrap::Wrap};

// -------------------------- MIDI Input Elements --------------------------- //
// ========================================================================== //

/*
   Define all elements that listen for MIDI messages.
*/

// Time display keeps track of the bar counter
MCU::TimeDisplay timedisplay = {};

// Play / Record
NoteValue play = {MCU::PLAY};
NoteValue record = {MCU::RECORD};

// Mute
Bankable::NoteValue<4> mute[] = {
  {bank, MCU::MUTE_1},
  {bank, MCU::MUTE_2},
};

// Solo
Bankable::NoteValue<4> solo[] = {
  {bank, MCU::SOLO_1},
  {bank, MCU::SOLO_2},
};

NoteValue rudeSolo = {MCU::RUDE_SOLO};

// Record arm / ready
Bankable::NoteValue<4> recrdy[] = {
  {bank, MCU::REC_RDY_1},
  {bank, MCU::REC_RDY_2},
};

// VU meters
MCU::Bankable::VU<4> vu[] = {
  {bank, 1, MCU::VUDecay::Hold},
  {bank, 2, MCU::VUDecay::Hold},
};

// VPot rings
MCU::Bankable::VPotRing<4> vpot[] = {
  {bank, 1},
  {bank, 2},
};

// ---------------------------- Display Elements ---------------------------- //
// ========================================================================== //

/*
   Define all display elements that display the state of the input elements.
*/

// Time display
MCU::TimeDisplayDisplay timedisplaydisplay = {
  // position (0, 0), font size (1)
  display, timedisplay, {0, 0}, 1, WHITE,
};

// Play / Record
NoteBitmapDisplay playDisp = {
  display, play, XBM::play_7, {16 + 64, 0}, WHITE,
};
NoteBitmapDisplay recordDisp = {
  display, record, XBM::record_7, {26 + 64, 0}, WHITE,
};

// Mute
NoteBitmapDisplay muteDisp[] = {
  {display, mute[0], XBM::mute_10B, {14, 50}, WHITE},
  {display, mute[1], XBM::mute_10B, {14 + 64, 50}, WHITE},
};

// Solo
NoteBitmapDisplay soloDisp[] = {
  {display, solo[0], XBM::solo_10B, {14, 50}, WHITE},
  {display, solo[1], XBM::solo_10B, {14 + 64, 50}, WHITE},
};

NoteBitmapDisplay rudeSoloDisp = {
  display, rudeSolo, XBM::solo_7, {36 + 64, 0}, WHITE};

// Record arm / ready
NoteBitmapDisplay recrdyDisp[] = {
  {display, recrdy[0], XBM::rec_rdy_10B, {14 + 14, 50}, WHITE},
  {display, recrdy[1], XBM::rec_rdy_10B, {14 + 14 + 64, 50}, WHITE},
};

// VU meters
MCU::VUDisplay vuDisp[] = {
  // position (32+11, 60), width (16), bar height (3) px, bar spacing (1) px
  {display, vu[0], {32 + 11, 60}, 16, 3, 1, WHITE},
  {display, vu[1], {32 + 11 + 64, 60}, 16, 3, 1, WHITE},
};

// VPot rings
MCU::VPotDisplay vpotDisp[] = {
  // position (0, 10), outer radius (16) px, inner radius (13) px
  {display, vpot[0], {0, 10}, 16, 13, WHITE},
  {display, vpot[1], {64, 10}, 16, 13, WHITE},
};

// Bank seting
BankDisplay bankDisp[] = {
  // first track of the bank (1), position (0, 50), font size (2)
  {display, bank, 1, {0, 50}, 2, WHITE},
  {display, bank, 2, {64, 50}, 2, WHITE},
};

// --------------------------------- Setup ---------------------------------- //
// ========================================================================== //

void setup() {
  // Correct relative mode for MCU rotary encoders

  RelativeCCSender::setMode(MACKIE_CONTROL_RELATIVE);
  Control_Surface.begin(); // Initialize Control Surface

}

// ---------------------------------- Loop ---------------------------------- //
// ========================================================================== //

void loop() {
  Control_Surface.loop();  // Refresh all elements

}
tttapa commented 4 years ago

DisplayInterface is an abstract interface. It declares a set of abstract (pure virtual) drawing functions for displays. Control Surface uses these functions to draw to the displays, but Control Surface doesn't know how these functions are implemented, the implementation depends on the display library you're using, and it's up to the user to create an adapter between Control Surface and the display library by implementing the pure virtual functions of DisplayInterface.

As an example, you could have a look at the DisplayInterfaceSSD1306, it should be almost identical for the Adafruit_SH1106 library.

masydoblig commented 3 years ago

Hi Peter @tttapa

Can you have a look at the below i cannot get the display interface working at all It is for the Adafruit_ST7735 screen library i have been trying to make a new adapter to the display interface and its not working at all This is the sketch i was using and the adapter is posted below

#include <Encoder.h>
#include <Control_Surface.h>
#include <Display/DisplayInterfaces/DisplayInterfaceST7735.hpp>

 // ----------------------------- MIDI Interface ----------------------------- //
 // ========================================================================== //

 /*
    Instantiate a MIDI interface to use for the Control Surface.
 */

    USBMIDI_Interface midi;
// USBDebugMIDI_Interface midi(115200);

// ----------------------------- Display setup ------------------------------ //
// ========================================================================== //

//constexpr uint8_t SCREEN_WIDTH = 128;
//constexpr uint8_t SCREEN_HEIGHT = 64;

constexpr int8_t TFT_DC = 8;    // Data/Command pin of the display
constexpr int8_t TFT_RST = -1; // Use the external RC circuit for reset
constexpr int8_t TFT_CS = 9;    // Chip Select pin of the display

//constexpr uint32_t SPI_Frequency = SPI_MAX_SPEED;

// Instantiate the displays
Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);
float p = 3.1415926;

// --------------------------- Display interface ---------------------------- //
// ========================================================================== //

// Implement the display interface, specifically, the begin and drawBackground
// methods.
class MyST7735_DisplayInterface : public ST7735_DisplayInterface {
public:
    MyST7735_DisplayInterface(Adafruit_ST7735& display)
        : ST7735_DisplayInterface(display) {}

    void begin() override {
        // Initialize the Adafruit_SSD1306 display
        if (!tft.begin())
            FATAL_ERROR(F("ST7735 allocation failed."), 0x1306);

        // If you override the begin method, remember to call the super class method
        ST7735_DisplayInterface::begin();
    }

    void drawBackground() override { tft.drawLine(1, 8, 126, 8, ST7735_WHITE); }

} display = tft;

// ------------------------------- Bank setup ------------------------------- //
// ========================================================================== //

/*
   Create a bank and a bank selector to change its setting.
*/

Bank<4> bank(2); // Create a new bank with two tracks per bank

// Create a new bank selector to control the bank using two push buttons
IncrementDecrementSelector<4> bankselector = { bank, {5, 6}, Wrap::Wrap };

// -------------------------- MIDI Input Elements --------------------------- //
// ========================================================================== //

/*
   Define all elements that listen for MIDI messages.
*/

// Time display keeps track of the bar counter
MCU::TimeDisplay timedisplay = {};

// ---------------------------- Display Elements ---------------------------- //
// ========================================================================== //

/*
   Define all display elements that display the state of the input elements.
*/

// Time display
MCU::TimeDisplayDisplay timedisplaydisplay = {
     position (0, 0), font size (1)
    display, timedisplay, {0, 0}, 1, ST7735_WHITE,
};

// --------------------------------- Setup ---------------------------------- //
// ========================================================================== //

void setup() {
    // Correct relative mode for MCU rotary encoders

    tft.initR(INITR_144GREENTAB);

    RelativeCCSender::setMode(MACKIE_CONTROL_RELATIVE);
    Control_Surface.begin(); // Initialize Control Surface
}

// ---------------------------------- Loop ---------------------------------- //
// ========================================================================== //

void loop() {
    Control_Surface.loop(); // Refresh all elements
}
masydoblig commented 3 years ago

and this is the Adapter

#pragma once

#include <Adafruit_ST7735.h> 
#include <Display/DisplayInterface.hpp>

BEGIN_CS_NAMESPACE

/**
 * @brief   This class creates a mapping between the Adafruit_SSD1306 display 
 *          driver and the general display interface used by the Control Surface
 *          library.
 */
class ST7735_DisplayInterface : public DisplayInterface {
  protected:
    ST7735_DisplayInterface(Adafruit_ST7735 &display) : tft(display) {}

public:
    void clear() override { tft.fillScreen(ST77XX_BLACK)(); }
    void drawBackground() override = 0;
    void display() override { tft.initR(); }

    void drawPixel(int16_t x, int16_t y, uint16_t color) override {
        tft.drawPixel(x, y, color);
    }

    void setTextColor(uint16_t color) override { tft.setTextColor(color); }
    void setTextSize(uint8_t size) override { tft.setTextSize(size); }
    void setCursor(int16_t x, int16_t y) override { tft.setCursor(x, y); }

    size_t write(uint8_t c) override { return tft.write(c); }

    void drawLine(int16_t x0, int16_t y0, int16_t x1, int16_t y1,
                  uint16_t color) override {
        tft.drawLine(x0, y0, x1, y1, color);
    }
    void drawFastVLine(int16_t x, int16_t y, int16_t h,
                       uint16_t color) override {
        tft.drawFastVLine(x, y, h, color);
    }
    void drawFastHLine(int16_t x, int16_t y, int16_t w,
                       uint16_t color) override {
        tft.drawFastHLine(x, y, w, color);
    }

    void drawXBitmap(int16_t x, int16_t y, const uint8_t bitmap[], int16_t w,
                     int16_t h, uint16_t color) override {
        tft.drawXBitmap(x, y, bitmap, w, h, color);
    }

  protected:
    Adafruit_ST7735 &tft;
};

END_CS_NAMESPACE
tttapa commented 3 years ago

I don't have the right hardware to test anything, but this seems strange to me:

    void begin() override {
        // Initialize the Adafruit_SSD1306 display
        if (!tft.begin())
            FATAL_ERROR(F("ST7735 allocation failed."), 0x1306);

        // If you override the begin method, remember to call the super class method
        ST7735_DisplayInterface::begin();
    }

Looking at the Adafruit examples (https://github.com/adafruit/Adafruit-ST7735-Library/blob/master/examples/graphicstest/graphicstest.ino), I think it should be:

    void begin() override {
        tft.initR(INITR_144GREENTAB);
        // If you override the begin method, remember to call the super class method
        ST7735_DisplayInterface::begin();
    }

And then don't call tft.initR(...) in your setup:

void setup() {
    RelativeCCSender::setMode(MACKIE_CONTROL_RELATIVE);
    Control_Surface.begin(); // Initialize Control Surface
}

The DisplayInterface::display() method writes the display buffer to the display. Since the Adafruit_ST7735 library writes to the display directly without a buffer, you have to leave the display() method empty:

class ST7735_DisplayInterface : public DisplayInterface {
    ...
    void display() override { /* nothing */ }
    ...
};