blob: 05e9ef16126ce8fde19a194a7e6252ddecf55287 [file] [log] [blame]
// Copyright 2023 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#include "pw_display_driver_st7735/display_driver.h"
#include <array>
#include <cstddef>
#include "pw_chrono/system_clock.h"
#include "pw_digital_io/digital_io.h"
#include "pw_framebuffer/framebuffer.h"
#include "pw_thread/sleep.h"
using pw::digital_io::State;
using pw::framebuffer::Framebuffer;
using pw::framebuffer::PixelFormat;
using pw::spi::ChipSelectBehavior;
using pw::spi::Device;
using std::array;
using std::byte;
namespace pw::display_driver {
namespace {
constexpr array<byte, 0> kEmptyArray;
// ST7735 Display Registers
// clang-format off
#define ST7735_SWRESET 0x01
#define ST7735_RDDID 0x04
#define ST7735_RDDST 0x09
#define ST7735_SLPIN 0x10
#define ST7735_SLPOUT 0x11
#define ST7735_PTLON 0x12
#define ST7735_NORON 0x13
#define ST7735_INVOFF 0x20
#define ST7735_INVON 0x21
#define ST7735_GAMSET 0x26
#define ST7735_DISPOFF 0x28
#define ST7735_DISPON 0x29
#define ST7735_RAMWR 0x2C
#define ST7735_CASET 0x2A
#define ST7735_RASET 0x2B
#define ST7735_RAMWR 0x2C
#define ST7735_RAMRD 0x2E
#define ST7735_PTLAR 0x30
#define ST7735_TEOFF 0x34
#define ST7735_TEON 0x35
#define ST7735_MADCTL 0x36
#define ST7735_COLMOD 0x3A
#define ST7735_FRMCTR1 0xB1 // Frame Rate Control (normal mode/ Full colors)
#define ST7735_FRMCTR2 0xB2 // Frame Rate Control (Idle mode/ 8-colors)
#define ST7735_FRMCTR3 0xB3 // Frame Rate Control (Partial mode/ full colors)
#define ST7735_INVCTR 0xB4
#define ST7735_DISSET5 0xB6
#define ST7735_GCTRL 0xB7
#define ST7735_VCOMS 0xBB
#define ST7735_PWCTR1 0xC0
#define ST7735_PWCTR2 0xC1
#define ST7735_PWCTR3 0xC2
#define ST7735_VRHS 0xC3
#define ST7735_VDVS 0xC4
#define ST7735_VMCTR1 0xC5
#define ST7735_FRCTRL2 0xC6
#define ST7735_PWCTRL1 0xD0
#define ST7735_RDID1 0xDA
#define ST7735_RDID2 0xDB
#define ST7735_RDID3 0xDC
#define ST7735_RDID4 0xDD
#define ST7735_PORCTRL 0xB2
#define ST7735_GMCTRP1 0xE0
#define ST7735_GMCTRN1 0xE1
#define ST7735_PWCTR6 0xFC
// MADCTL Bits (See page 215: MADCTL (36h): Memory Data Access Control)
#define ST7735_MADCTL_ROW_ORDER 0b10000000
#define ST7735_MADCTL_COL_ORDER 0b01000000
#define ST7735_MADCTL_SWAP_XY 0b00100000
#define ST7735_MADCTL_SCAN_ORDER 0b00010000
#define ST7735_MADCTL_RGB_BGR 0b00001000
#define ST7735_MADCTL_HORIZ_ORDER 0b00000100
#define ST7735_INVCTR_NLA 0b00000100 // Inversion setting in full Colors normal mode
#define ST7735_INVCTR_NLB 0b00000010 // Inversion setting in Idle mode
#define ST7735_INVCTR_NLC 0b00000001 // Inversion setting in full Colors partial mode
// clang-format on
constexpr uint8_t HighByte(uint16_t val) { return val >> 8; }
constexpr uint8_t LowByte(uint16_t val) { return val & 0xff; }
} // namespace
// The ST7735 supports a max display size of 162x132. This was developed with
// a ST7735 development board with a 160x128 pixel screen - hence the row/col
// start values. These should be parameterized.
DisplayDriverST7735::DisplayDriverST7735(const Config& config)
: config_(config), row_start_(2), col_start_(1) {}
void DisplayDriverST7735::SetMode(Mode mode) {
// Set the D/CX pin to indicate data or command values.
if (mode == Mode::kData) {
config_.data_cmd_gpio.SetState(State::kActive);
} else {
config_.data_cmd_gpio.SetState(State::kInactive);
}
}
Status DisplayDriverST7735::WriteCommand(Device::Transaction& transaction,
const Command& command) {
SetMode(Mode::kCommand);
byte buff[1]{static_cast<byte>(command.command)};
auto s = transaction.Write(buff);
if (!s.ok())
return s;
SetMode(Mode::kData);
if (command.command_data.empty()) {
return OkStatus();
}
return transaction.Write(command.command_data);
}
Status DisplayDriverST7735::Init() {
auto transaction = config_.spi_device_8_bit.StartTransaction(
ChipSelectBehavior::kPerWriteRead);
WriteCommand(transaction, {ST7735_SWRESET, kEmptyArray}); // Software reset
pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(150ms));
WriteCommand(transaction, {ST7735_SLPOUT, kEmptyArray});
pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(500ms));
WriteCommand(transaction,
{ST7735_FRMCTR1,
array<byte, 3>{
byte{0x00},
byte{0x06},
byte{0x03},
}});
pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(10ms));
WriteCommand(transaction,
{ST7735_DISSET5,
array<byte, 2>{
byte{0x15},
byte{0x02},
}});
constexpr uint8_t inversion_val =
ST7735_INVCTR_NLA | ST7735_INVCTR_NLB | ST7735_INVCTR_NLC;
WriteCommand(transaction,
{ST7735_INVCTR,
array<byte, 1>{
byte{inversion_val},
}});
WriteCommand(transaction, {ST7735_TEON, kEmptyArray});
WriteCommand(transaction, {ST7735_COLMOD, array<byte, 1>{byte{0x05}}});
pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(10ms));
WriteCommand(transaction,
{ST7735_PORCTRL,
array<byte, 5>{
byte{0x0c},
byte{0x0c},
byte{0x00},
byte{0x33},
byte{0x33},
}});
WriteCommand(transaction,
{ST7735_PWCTR1,
array<byte, 2>{
byte{0x02}, // GVDD = 4.7V.
byte{0x70}, // 1.0uA.
}});
pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(10ms));
WriteCommand(transaction,
{ST7735_PWCTR2,
array<byte, 1>{
byte{0x05},
}});
WriteCommand(transaction,
{ST7735_PWCTR3,
array<byte, 2>{
byte{0x01},
byte{0x02},
}});
WriteCommand(transaction,
{ST7735_VMCTR1,
array<byte, 2>{
byte{0x3c},
byte{0x38},
}});
pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(10ms));
WriteCommand(transaction,
{ST7735_PWCTR6,
array<byte, 2>{
byte{0x11},
byte{0x15},
}});
WriteCommand(transaction, {ST7735_VRHS, array<byte, 1>{byte{0x12}}});
WriteCommand(transaction, {ST7735_VDVS, array<byte, 1>{byte{0x20}}});
WriteCommand(transaction,
{ST7735_PWCTRL1,
array<byte, 2>{
byte{0xa4},
byte{0xa1},
}});
WriteCommand(transaction, {ST7735_FRCTRL2, array<byte, 1>{byte{0x0f}}});
WriteCommand(transaction, {ST7735_INVOFF, kEmptyArray});
WriteCommand(transaction,
{ST7735_GMCTRP1,
array<byte, 16>{
byte{0x09},
byte{0x16},
byte{0x09},
byte{0x20},
byte{0x21},
byte{0x1B},
byte{0x13},
byte{0x19},
byte{0x17},
byte{0x15},
byte{0x1E},
byte{0x2B},
byte{0x04},
byte{0x05},
byte{0x02},
byte{0x0E},
}});
WriteCommand(transaction,
{ST7735_GMCTRN1,
array<byte, 16>{
byte{0x0B},
byte{0x14},
byte{0x08},
byte{0x1E},
byte{0x22},
byte{0x1D},
byte{0x18},
byte{0x1E},
byte{0x1B},
byte{0x1A},
byte{0x24},
byte{0x2B},
byte{0x06},
byte{0x06},
byte{0x02},
byte{0x0F},
}});
pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(10ms));
// Landscape drawing Column Address Set
const uint16_t max_column = config_.screen_width + col_start_ - 1;
WriteCommand(transaction,
{ST7735_CASET,
array<byte, 4>{
byte{HighByte(col_start_)},
byte{LowByte(col_start_)},
byte{HighByte(max_column)},
byte{LowByte(max_column)},
}});
// Page Address Set
const uint16_t max_row = config_.screen_height + row_start_ - 1;
WriteCommand(transaction,
{ST7735_RASET,
array<byte, 4>{
byte{HighByte(row_start_)},
byte{LowByte(row_start_)},
byte{HighByte(max_row)},
byte{LowByte(max_row)},
}});
constexpr bool rotate_180 = false;
uint8_t madctl = ST7735_MADCTL_COL_ORDER;
if (rotate_180)
madctl = ST7735_MADCTL_ROW_ORDER;
madctl |= ST7735_MADCTL_SWAP_XY | ST7735_MADCTL_SCAN_ORDER;
WriteCommand(transaction, {ST7735_MADCTL, array<byte, 1>{byte{madctl}}});
WriteCommand(transaction, {ST7735_NORON, kEmptyArray});
pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(10ms));
WriteCommand(transaction, {ST7735_DISPON, kEmptyArray});
pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(500ms));
return OkStatus();
}
void DisplayDriverST7735::WriteFramebuffer(Framebuffer framebuffer,
WriteCallback write_callback) {
PW_ASSERT(framebuffer.is_valid());
PW_ASSERT(framebuffer.pixel_format() == PixelFormat::RGB565);
Status s;
// Let controller know a write is coming.
{
auto transaction = config_.spi_device_8_bit.StartTransaction(
ChipSelectBehavior::kPerWriteRead);
s = WriteCommand(transaction, {ST7735_RAMWR, kEmptyArray});
if (!s.ok()) {
write_callback(std::move(framebuffer), s);
return;
}
}
// Write the pixel data.
auto transaction = config_.spi_device_16_bit.StartTransaction(
ChipSelectBehavior::kPerWriteRead);
const uint16_t* fb_data = static_cast<const uint16_t*>(framebuffer.data());
const size_t num_pixels = config_.screen_width * config_.screen_height;
s = transaction.Write(
ConstByteSpan(reinterpret_cast<const byte*>(fb_data), num_pixels));
write_callback(std::move(framebuffer), s);
}
Status DisplayDriverST7735::WriteRow(span<uint16_t> row_pixels,
uint16_t row_idx,
uint16_t col_idx) {
PW_ASSERT(false);
return Status::Unimplemented();
}
Status DisplayDriverST7735::Reset() {
if (!config_.reset_gpio)
return Status::Unavailable();
PW_TRY(config_.reset_gpio->SetStateInactive());
pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(100ms));
PW_TRY(config_.reset_gpio->SetStateActive());
pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(100ms));
PW_TRY(config_.reset_gpio->SetStateInactive());
pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(100ms));
return OkStatus();
}
} // namespace pw::display_driver