blob: e79fa5c13a21f318f0813edc64ef93cdf4807e96 [file] [log] [blame]
// Copyright 2022 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_ili9341/display_driver.h"
#include <algorithm>
#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;
namespace pw::display_driver {
namespace {
constexpr std::array<std::byte, 0> kEmptyByteArray = {};
// clang-format off
// Level 1 commands:
constexpr uint8_t CMD_SWRESET = 0x01; // Software Reset.
constexpr uint8_t CMD_READ_DISPLAY_ID = 0x04; // Read display identification information.
constexpr uint8_t CMD_RDDST = 0x09; // Read Display Status.
constexpr uint8_t CMD_RDDPM = 0x0A; // Read Display Power Mode.
constexpr uint8_t CMD_RDDMADCTL = 0x0B; // Read Display MADCTL.
constexpr uint8_t CMD_RDDCOLMOD = 0x0C; // Read Display Pixel Format.
constexpr uint8_t CMD_RDDIM = 0x0D; // Read Display Image Format.
constexpr uint8_t CMD_RDDSM = 0x0E; // Read Display Signal Mode.
constexpr uint8_t CMD_RDDSDR = 0x0F; // Read Display Self-Diagnostic Result.
constexpr uint8_t CMD_SPLIN = 0x10; // Enter Sleep Mode.
constexpr uint8_t CMD_SLEEP_OUT = 0x11; // Sleep Out.
constexpr uint8_t CMD_PTLON = 0x12; // Partial Mode ON.
constexpr uint8_t CMD_NORMAL_MODE_ON = 0x13; // Normal Display Mode ON.
constexpr uint8_t CMD_DINVOFF = 0x20; // Display Inversion OFF.
constexpr uint8_t CMD_DINVON = 0x21; // Display Inversion ON.
constexpr uint8_t CMD_GAMMA = 0x26; // Gamma Set.
constexpr uint8_t CMD_DISPLAY_OFF = 0x28; // Display OFF.
constexpr uint8_t CMD_DISPLAY_ON = 0x29; // Display ON.
constexpr uint8_t CMD_COLUMN_ADDR = 0x2A; // Column Address Set.
constexpr uint8_t CMD_PAGE_ADDR = 0x2B; // Page Address Set.
constexpr uint8_t CMD_GRAM = 0x2C; // Memory Write.
constexpr uint8_t CMD_RGBSET = 0x2D; // Color Set.
constexpr uint8_t CMD_RAMRD = 0x2E; // Memory Read.
constexpr uint8_t CMD_PLTAR = 0x30; // Partial Area.
constexpr uint8_t CMD_VSCRDEF = 0x33; // Vertical Scrolling Definition.
constexpr uint8_t CMD_TEOFF = 0x34; // Tearing Effect Line OFF.
constexpr uint8_t CMD_TEON = 0x35; // Tearing Effect Line ON.
constexpr uint8_t CMD_MADCTL = 0x36; // Memory Access Control.
constexpr uint8_t CMD_VSCRSADD = 0x37; // Vertical Scrolling Start Address.
constexpr uint8_t CMD_IDMOFF = 0x38; // Idle Mode OFF.
constexpr uint8_t CMD_IDMON = 0x39; // Idle Mode ON.
constexpr uint8_t CMD_PIXEL_FORMAT = 0x3A; // COLMOD: Pixel Format Set.
constexpr uint8_t CMD_WRITE_MEM_CONTINUE = 0x3C; // Write_Memory_Continue.
constexpr uint8_t CMD_READ_MEM_CONTINUE = 0x3E; // Read_Memory_Continue.
constexpr uint8_t CMD_SET_TEAR_SCANLINE = 0x44; // Set_Tear_Scanline.
constexpr uint8_t CMD_GET_SCANLINE = 0x45; // Get_Scanline.
constexpr uint8_t CMD_WDB = 0x51; // Write Display Brightness.
constexpr uint8_t CMD_RDDISBV = 0x52; // Read Display Brightness.
constexpr uint8_t CMD_WCD = 0x53; // Write CTRL Display.
constexpr uint8_t CMD_RDCTRLD = 0x54; // Read CTRL Display.
constexpr uint8_t CMD_WRCABC = 0x55; // Write Content Adaptive Brightness Control.
constexpr uint8_t CMD_RDCABC = 0x56; // Read Content Adaptive Brightness Control.
constexpr uint8_t CMD_WRITE_CABC = 0x5E; // Write CABC Minimum Brightness.
constexpr uint8_t CMD_READ_CABC = 0x5F; // Read CABC Minimum Brightness.
constexpr uint8_t CMD_READ_ID1 = 0xDA; // Read ID1.
constexpr uint8_t CMD_READ_ID2 = 0xDB; // Read ID2.
constexpr uint8_t CMD_READ_ID3 = 0xDC; // Read ID3.
// Level 2 commands:
constexpr uint8_t CMD_RGB_INTERFACE = 0xB0; // RGB Interface Signal Control.
constexpr uint8_t CMD_FRMCTR1 = 0xB1; // Frame Rate Control (In Normal Mode/Full Colors.
constexpr uint8_t CMD_FRMCTR2 = 0xB2; // Frame Rate Control (In Idle Mode/8 colors).
constexpr uint8_t CMD_FRMCTR3 = 0xB3; // Frame Rate control (In Partial Mode/Full Colors).
constexpr uint8_t CMD_INVTR = 0xB4; // Display Inversion Control.
constexpr uint8_t CMD_BPC = 0xB5; // Blanking Porch Control.
constexpr uint8_t CMD_DFC = 0xB6; // Display Function Control.
constexpr uint8_t CMD_ETMOD = 0xB7; // Entry Mode Set.
constexpr uint8_t CMD_BACKLIGHT1 = 0xB8; // Backlight Control 1.
constexpr uint8_t CMD_BACKLIGHT2 = 0xB9; // Backlight Control 2.
constexpr uint8_t CMD_BACKLIGHT3 = 0xBA; // Backlight Control 3.
constexpr uint8_t CMD_BACKLIGHT4 = 0xBB; // Backlight Control 4.
constexpr uint8_t CMD_BACKLIGHT5 = 0xBC; // Backlight Control 5.
constexpr uint8_t CMD_BACKLIGHT7 = 0xBE; // Backlight Control 7.
constexpr uint8_t CMD_BACKLIGHT8 = 0xBF; // Backlight Control 8.
constexpr uint8_t CMD_POWER1 = 0xC0; // Power Control 1.
constexpr uint8_t CMD_POWER2 = 0xC1; // Power Control 2.
constexpr uint8_t CMD_VCOM1 = 0xC5; // VCOM Control 1.
constexpr uint8_t CMD_VCOM2 = 0xC7; // VCOM Control 2.
constexpr uint8_t CMD_NVMWR = 0xD0; // NV Memory Write.
constexpr uint8_t CMD_NVMPKEY = 0xD1; // NV Memory Protection Key.
constexpr uint8_t CMD_RDNVM = 0xD2; // NV Memory Status Read.
constexpr uint8_t CMD_READ_ID4 = 0xD3; // Read ID4.
constexpr uint8_t CMD_PGAMMA = 0xE0; // Positive Gamma Correction.
constexpr uint8_t CMD_NGAMMA = 0xE1; // Negative Gamma Correction.
constexpr uint8_t CMD_DGAMCTRL1 = 0xE2; // Digital Gamma Control 1.
constexpr uint8_t CMD_DGAMCTRL2 = 0xE3; // Digital Gamma Control 2.
constexpr uint8_t CMD_INTERFACE = 0xF6; // Interface Control.
// Extended register commands:
constexpr uint8_t CMD_POWERA = 0xCB; // Power control A.
constexpr uint8_t CMD_POWERB = 0xCF; // Power control B.
constexpr uint8_t CMD_DTCA = 0xE8; // Driver timing control A.
constexpr uint8_t CMD_DTCA_2 = 0xE9; // Driver timing control A.
constexpr uint8_t CMD_DTCB = 0xEA; // Driver timing control B.
constexpr uint8_t CMD_POWER_SEQ = 0xED; // Power on sequence control.
constexpr uint8_t CMD_3GAMMA_EN = 0xF2; // Enable 3G.
constexpr uint8_t CMD_PRC = 0xF7; // Pump ratio control .
// clang-format on
// The ILI9341 is hard-coded at 320x240;
constexpr int kDisplayWidth = 320;
constexpr int kDisplayHeight = 240;
constexpr int kDisplayNumPixels = kDisplayWidth * kDisplayHeight;
// clang-format off
constexpr std::byte MADCTL_MY = std::byte{0b10000000}; // Row address order.
constexpr std::byte MADCTL_MX = std::byte{0b01000000}; // Column address order.
constexpr std::byte MADCTL_MV = std::byte{0b00100000}; // Row/column exchange.
constexpr std::byte MADCTL_ML = std::byte{0b00010000}; // Vertical refresh order.
constexpr std::byte MADCTL_BGR = std::byte{0b00001000}; // BGR/RGB order.
constexpr std::byte MADCTL_MH = std::byte{0b00000100}; // Horizontal refresh order.
// Take value specified in target.
constexpr std::byte kMADMode = std::byte{ILI9341_MADCTL};
constexpr uint8_t kDTC_PTG_MASK = 0b00001100;
constexpr uint8_t kDTC_PTG_NORMAL_SCAN = 0b00000000;
constexpr uint8_t kDTC_PTG_PROHIBITED1 = 0b00000100;
constexpr uint8_t kDTC_PTG_INTERVAL_SCAN = 0b00001000;
constexpr uint8_t kDTC_PTG_PROHIBITED2 = 0b00001100;
// Mask values for CMD_RGB_INTERFACE:
constexpr uint8_t kIFMODE_MASK_EPL = 0b00000001;
constexpr uint8_t kIFMODE_MASK_DPL = 0b00000010;
constexpr uint8_t kIFMODE_MASK_HSPL = 0b00000100;
constexpr uint8_t kIFMODE_MASK_VSPL = 0b00001000;
constexpr uint8_t kIFMODE_MASK_UNUSED = 0b00010000;
constexpr uint8_t kIFMODE_MASK_RCM = 0b01100000;
constexpr uint8_t kIFMODE_MASK_BYPASS = 0b10000000;
// clang-format on
// Bypass=memory, RGB IF="VSYNC, HSYNC, DOTCLK, DE, D", DPL=falling.
constexpr uint8_t kRGBWithDE = 0xC2;
// Bypass=memory, RGB IF="VSYNC, HSYNC, DOTCLK, D", DPL=falling.
constexpr uint8_t kRGBWithoutDE = 0xE2;
// Frame Control (Normal Mode)
constexpr std::byte kFrameRate61 = std::byte{0x1F};
constexpr std::byte kFrameRate70 = std::byte{0x1B};
constexpr std::byte kFrameRate79 = std::byte{0x18};
constexpr std::byte kFrameRate119 = std::byte{0x10};
constexpr std::byte kPixelFormat16bits = std::byte(0x55);
constexpr std::byte kPixelFormat18bits = std::byte(0x36);
constexpr uint8_t HighByte(uint16_t val) { return val >> 8; }
constexpr uint8_t LowByte(uint16_t val) { return val & 0xff; }
} // namespace
DisplayDriverILI9341::DisplayDriverILI9341(const Config& config)
: config_(config) {}
void DisplayDriverILI9341::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 DisplayDriverILI9341::WriteCommand(Device::Transaction& transaction,
const Command& command) {
SetMode(Mode::kCommand);
std::byte buff[1]{static_cast<std::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 DisplayDriverILI9341::Init() {
Reset().IgnoreError();
// TODO(cmumford): Figure out why kPerTransaction is flakey for this.
// Seems to be OK on the Pico's display, but not the STM32F429I-DISC1.
auto transaction = config_.spi_device_8_bit.StartTransaction(
ChipSelectBehavior::kPerWriteRead);
// ?
WriteCommand(transaction,
{0xEF,
std::array<std::byte, 3>{
std::byte{0x03},
std::byte{0x80},
std::byte{0x02},
}});
WriteCommand(transaction,
{CMD_POWERB,
std::array<std::byte, 3>{
std::byte{0x00},
std::byte{0xC1},
std::byte{0x30},
}});
WriteCommand(transaction,
{CMD_POWER_SEQ,
std::array<std::byte, 4>{
std::byte{0x64},
std::byte{0x03},
std::byte{0x12},
std::byte{0x81},
}});
WriteCommand(transaction,
{CMD_DTCA,
std::array<std::byte, 3>{
std::byte{0x85},
std::byte{0x00},
std::byte{0x78},
}});
WriteCommand(transaction,
{CMD_POWERA,
std::array<std::byte, 5>{
std::byte{0x39},
std::byte{0x2C},
std::byte{0x00},
std::byte{0x34},
std::byte{0x02},
}});
WriteCommand(transaction,
{CMD_PRC, std::array<std::byte, 1>{std::byte{0x20}}});
WriteCommand(transaction,
{CMD_DTCB,
std::array<std::byte, 2>{
std::byte{0x00},
std::byte{0x00},
}});
WriteCommand(transaction,
{CMD_FRMCTR1,
std::array<std::byte, 2>{
std::byte{0x00}, // division ratio = fosc.
kFrameRate70,
}});
// Display Function Control
WriteCommand(transaction,
{CMD_DFC,
std::array<std::byte, 2>{
std::byte{0x0A},
std::byte{0xA2},
}});
// Power control. GVDD = 0x10 = 3.65V.
WriteCommand(transaction,
{CMD_POWER1, std::array<std::byte, 1>{std::byte{0x10}}});
// Power control
WriteCommand(transaction,
{CMD_POWER2, std::array<std::byte, 1>{std::byte{0x10}}});
// VCM control
WriteCommand(transaction,
{CMD_VCOM1,
std::array<std::byte, 2>{
std::byte{0x3e},
std::byte{0x28},
}});
// VCM control
WriteCommand(transaction,
{CMD_VCOM2, std::array<std::byte, 1>{std::byte{0x86}}});
// Memory Access Control.
WriteCommand(transaction, {CMD_MADCTL, std::array<std::byte, 1>{kMADMode}});
WriteCommand(
transaction,
{CMD_PIXEL_FORMAT, std::array<std::byte, 1>{kPixelFormat16bits}});
// Gamma Function Disable?
WriteCommand(transaction,
{CMD_3GAMMA_EN, std::array<std::byte, 1>{std::byte{0x00}}});
switch (config_.interface) {
case InterfaceType::SPI:
break;
case InterfaceType::WithDE:
WriteCommand(
transaction,
{CMD_RGB_INTERFACE, std::array<std::byte, 1>{std::byte{kRGBWithDE}}});
break;
case InterfaceType::WithoutDE:
WriteCommand(transaction,
{CMD_RGB_INTERFACE,
std::array<std::byte, 1>{std::byte{kRGBWithoutDE}}});
break;
}
// Display Function Control
WriteCommand(transaction,
{CMD_DFC,
std::array<std::byte, 4>{
std::byte{0x0A},
std::byte{0xA7},
std::byte{0x27},
std::byte{0x04},
}});
// Max pixel coordinates in portrait mode.
constexpr uint16_t kMinX = 0;
constexpr uint16_t kMaxX = 240 - 1;
constexpr uint16_t kMinY = 0;
constexpr uint16_t kMaxY = 320 - 1;
// Landscape drawing Column Address Set
const uint16_t kMinColumn = config_.swap_row_col ? kMinX : kMinY;
const uint16_t kMaxColumn = config_.swap_row_col ? kMaxX : kMaxY;
WriteCommand(transaction,
{CMD_COLUMN_ADDR,
std::array<std::byte, 4>{
std::byte{HighByte(kMinColumn)},
std::byte{LowByte(kMinColumn)},
std::byte{HighByte(kMaxColumn)},
std::byte{LowByte(kMaxColumn)},
}});
// Page Address Set
const uint16_t kMinRow = config_.swap_row_col ? kMinY : kMinX;
const uint16_t kMaxRow = config_.swap_row_col ? kMaxY : kMaxX;
WriteCommand(transaction,
{CMD_PAGE_ADDR,
std::array<std::byte, 4>{
std::byte{HighByte(kMinRow)},
std::byte{LowByte(kMinRow)},
std::byte{HighByte(kMaxRow)},
std::byte{LowByte(kMaxRow)},
}});
if (config_.interface != InterfaceType::SPI) {
WriteCommand(transaction,
{CMD_INTERFACE,
std::array<std::byte, 3>{
std::byte{0x00},
std::byte{0x01},
std::byte{0x06},
}});
}
WriteCommand(transaction, {CMD_GRAM, kEmptyByteArray});
pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(200ms));
// Gamma Set
WriteCommand(transaction,
{CMD_GAMMA, std::array<std::byte, 1>{std::byte{0x01}}});
// Positive Gamma Correction
WriteCommand(transaction,
{CMD_PGAMMA,
std::array<std::byte, 15>{
std::byte{0x0F},
std::byte{0x31},
std::byte{0x2B},
std::byte{0x0C},
std::byte{0x0E},
std::byte{0x08},
std::byte{0x4E},
std::byte{0xF1},
std::byte{0x37},
std::byte{0x07},
std::byte{0x10},
std::byte{0x03},
std::byte{0x0E},
std::byte{0x09},
std::byte{0x00},
}});
// Negative Gamma Correction
WriteCommand(transaction,
{CMD_NGAMMA,
std::array<std::byte, 15>{
std::byte{0x00},
std::byte{0x0E},
std::byte{0x14},
std::byte{0x03},
std::byte{0x11},
std::byte{0x07},
std::byte{0x31},
std::byte{0xC1},
std::byte{0x48},
std::byte{0x08},
std::byte{0x0F},
std::byte{0x0C},
std::byte{0x31},
std::byte{0x36},
std::byte{0x0F},
}});
WriteCommand(transaction, {CMD_SLEEP_OUT, kEmptyByteArray});
pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(200ms));
WriteCommand(transaction, {CMD_DISPLAY_ON, kEmptyByteArray});
WriteCommand(transaction, {CMD_NORMAL_MODE_ON, kEmptyByteArray});
WriteCommand(transaction, {CMD_GRAM, kEmptyByteArray});
return OkStatus();
}
void DisplayDriverILI9341::WriteFramebuffer(Framebuffer frame_buffer,
WriteCallback write_callback) {
PW_ASSERT(frame_buffer.is_valid());
PW_ASSERT(frame_buffer.pixel_format() == PixelFormat::RGB565);
if (config_.pixel_pusher) {
config_.pixel_pusher->WriteFramebuffer(std::move(frame_buffer),
std::move(write_callback));
return;
}
auto transaction = config_.spi_device_16_bit.StartTransaction(
ChipSelectBehavior::kPerTransaction);
const uint16_t* fb_data = static_cast<const uint16_t*>(frame_buffer.data());
Status s;
// TODO(cmumford): Figure out why the STM32F429I cannot send the entire
// framebuffer in a single write, but another display can.
#if 1
constexpr int kNumRowsPerSend = 10;
static_assert(!(kDisplayHeight % kNumRowsPerSend),
"Cannot send fractional number of rows");
constexpr int kNumSends = kDisplayHeight / kNumRowsPerSend;
constexpr size_t kNumPixelsInSend = kDisplayWidth * kNumRowsPerSend;
for (int i = 0; i < kNumSends && s.ok(); i++) {
const uint8_t* data = reinterpret_cast<const uint8_t*>(
&fb_data[kDisplayWidth * (kNumRowsPerSend * i)]);
// At this point the SPI bus is in 16-bit mode, so we send the number
// of 16-bit values (i.e. pixels).
s = transaction.Write(ConstByteSpan(
reinterpret_cast<const std::byte*>(data), kNumPixelsInSend));
}
#else
s = transaction.Write(ConstByteSpan(
reinterpret_cast<const std::byte*>(fb_data), kDisplayNumPixels));
#endif
write_callback(std::move(frame_buffer), s);
}
Status DisplayDriverILI9341::WriteRow(span<uint16_t> row_pixels,
uint16_t row_idx,
uint16_t col_idx) {
{
// Let controller know a write is coming.
auto transaction = config_.spi_device_8_bit.StartTransaction(
ChipSelectBehavior::kPerWriteRead);
// Landscape drawing Column Address Set
const uint16_t max_col_idx = std::max(
kDisplayWidth - 1, col_idx + static_cast<int>(row_pixels.size()));
WriteCommand(transaction,
{CMD_COLUMN_ADDR,
std::array<std::byte, 4>{
std::byte{HighByte(col_idx)},
std::byte{LowByte(col_idx)},
std::byte{HighByte(max_col_idx)},
std::byte{LowByte(max_col_idx)},
}});
// Page Address Set
uint16_t max_row_idx = row_idx;
WriteCommand(transaction,
{CMD_PAGE_ADDR,
std::array<std::byte, 4>{
std::byte{HighByte(row_idx)},
std::byte{LowByte(row_idx)},
std::byte{HighByte(max_row_idx)},
std::byte{LowByte(max_row_idx)},
}});
PW_TRY(WriteCommand(transaction, {CMD_GRAM, kEmptyByteArray}));
}
auto transaction = config_.spi_device_16_bit.StartTransaction(
ChipSelectBehavior::kPerTransaction);
return transaction.Write(
ConstByteSpan(reinterpret_cast<const std::byte*>(row_pixels.data()),
row_pixels.size()));
}
uint16_t DisplayDriverILI9341::GetWidth() const { return kDisplayWidth; }
uint16_t DisplayDriverILI9341::GetHeight() const { return kDisplayHeight; }
Status DisplayDriverILI9341::Reset() {
if (!config_.reset_gpio)
return Status::Unavailable();
auto s = config_.reset_gpio->SetStateActive();
if (!s.ok())
return s;
pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(100ms));
s = config_.reset_gpio->SetStateInactive();
pw::this_thread::sleep_for(pw::chrono::SystemClock::for_at_least(100ms));
return s;
}
} // namespace pw::display_driver