blob: ff8c869e41c5e877ab2ef45389f850bce91d816a [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_digital_io/digital_io.h"
#include "pw_framebuffer/framebuffer.h"
#include "pw_spin_delay/delay.h"
using pw::color::color_rgb565_t;
using pw::digital_io::State;
using pw::framebuffer::Framebuffer;
using pw::spi::ChipSelectBehavior;
using pw::spi::Device;
namespace pw::display_driver {
namespace {
constexpr std::array<std::byte, 0> kEmptyByteArray = {};
constexpr uint16_t ILI9341_MADCTL = 0x36;
constexpr std::byte MADCTL_MY = std::byte{0x80};
constexpr std::byte MADCTL_MX = std::byte{0x40};
constexpr std::byte MADCTL_MV = std::byte{0x20};
constexpr std::byte MADCTL_ML = std::byte{0x10};
constexpr std::byte MADCTL_RGB = std::byte{0x00};
constexpr std::byte MADCTL_BGR = std::byte{0x08};
constexpr std::byte MADCTL_MH = std::byte{0x04};
constexpr uint8_t ILI9341_CASET = 0x2a; // Column address set.
constexpr uint8_t ILI9341_PASET = 0x2b; // Page address set.
constexpr uint8_t ILI9341_RAMWR = 0x2c; // Memory write.
constexpr uint16_t ILI9341_PIXEL_FORMAT_SET = 0x3A;
// The ILI9341 is hard-coded at 320x240;
constexpr int kDisplayWidth = 320;
constexpr int kDisplayHeight = 240;
constexpr int kDisplayNumPixels = kDisplayWidth * kDisplayHeight;
constexpr std::byte kMode0 = MADCTL_MX | MADCTL_BGR;
constexpr std::byte kMode1 = MADCTL_MV | MADCTL_BGR;
constexpr std::byte kMode2 = MADCTL_MY | MADCTL_BGR;
constexpr std::byte kMode3 = MADCTL_MX | MADCTL_MY | MADCTL_MV | MADCTL_BGR;
// 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,
{0xCF,
std::array<std::byte, 3>{
std::byte{0x00},
std::byte{0xC1},
std::byte{0x30},
}});
// ?
WriteCommand(transaction,
{0xED,
std::array<std::byte, 4>{
std::byte{0x64},
std::byte{0x03},
std::byte{0x12},
std::byte{0x81},
}});
// ?
WriteCommand(transaction,
{0xE8,
std::array<std::byte, 3>{
std::byte{0x85},
std::byte{0x00},
std::byte{0x78},
}});
// ?
WriteCommand(transaction,
{0xCB,
std::array<std::byte, 5>{
std::byte{0x39},
std::byte{0x2C},
std::byte{0x00},
std::byte{0x34},
std::byte{0x02},
}});
// ?
WriteCommand(transaction, {0xF7, std::array<std::byte, 1>{std::byte{0x20}}});
// ?
WriteCommand(transaction,
{0xEA,
std::array<std::byte, 2>{
std::byte{0x00},
std::byte{0x00},
}});
// Power control
WriteCommand(transaction, {0xC0, std::array<std::byte, 1>{std::byte{0x23}}});
// Power control
WriteCommand(transaction, {0xC1, std::array<std::byte, 1>{std::byte{0x10}}});
// VCM control
WriteCommand(transaction,
{0xC5,
std::array<std::byte, 2>{
std::byte{0x3e},
std::byte{0x28},
}});
// VCM control
WriteCommand(transaction, {0xC7, std::array<std::byte, 1>{std::byte{0x86}}});
WriteCommand(transaction, {ILI9341_MADCTL, std::array<std::byte, 1>{kMode3}});
WriteCommand(
transaction,
{ILI9341_PIXEL_FORMAT_SET, std::array<std::byte, 1>{kPixelFormat16bits}});
WriteCommand(transaction,
{0xB1,
std::array<std::byte, 2>{
std::byte{0x00}, // division ratio
kFrameRate61,
}});
// Display Function Control
WriteCommand(transaction,
{0xB6,
std::array<std::byte, 3>{
std::byte{0x08},
std::byte{0x82},
std::byte{0x27},
}});
// Gamma Function Disable?
WriteCommand(transaction, {0xF2, std::array<std::byte, 1>{std::byte{0x00}}});
// Gamma Set
WriteCommand(transaction, {0x26, std::array<std::byte, 1>{std::byte{0x01}}});
// Positive Gamma Correction
WriteCommand(transaction,
{0xE0,
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,
{0xE1,
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},
}});
// Exit Sleep
WriteCommand(transaction, {0x11, std::array<std::byte, 0>{}});
pw::spin_delay::WaitMillis(100);
// Display On
WriteCommand(transaction, {0x29, std::array<std::byte, 0>{}});
pw::spin_delay::WaitMillis(100);
// Normal display mode on
WriteCommand(transaction, {0x13, std::array<std::byte, 0>{}});
// Setup drawing full framebuffers
// Landscape drawing Column Address Set
constexpr uint16_t kMaxColumn = kDisplayWidth - 1;
WriteCommand(transaction,
{0x2A,
std::array<std::byte, 4>{
std::byte{0x0},
std::byte{0x0},
std::byte{kMaxColumn >> 8}, // high byte of short.
std::byte{kMaxColumn & 0xff}, // low byte of short.
}});
// Page Address Set
constexpr uint16_t kMaxRow = kDisplayHeight - 1;
WriteCommand(transaction,
{0x2B,
std::array<std::byte, 4>{
std::byte{0x0},
std::byte{0x0},
std::byte{kMaxRow >> 8}, // high byte of short.
std::byte{kMaxRow & 0xff}, // low byte of short.
}});
pw::spin_delay::WaitMillis(10);
WriteCommand(transaction, {0x2C, std::array<std::byte, 0>{}});
SetMode(Mode::kData);
pw::spin_delay::WaitMillis(100);
return OkStatus();
}
Framebuffer DisplayDriverILI9341::GetFramebuffer() {
return Framebuffer(config_.pool_data.fb_addr[0],
config_.pool_data.size.width,
config_.pool_data.size.height,
config_.pool_data.row_bytes);
}
Status DisplayDriverILI9341::ReleaseFramebuffer(Framebuffer frame_buffer) {
auto transaction = config_.spi_device_16_bit.StartTransaction(
ChipSelectBehavior::kPerTransaction);
const uint16_t* fb_data = frame_buffer.GetFramebufferData();
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
return s;
}
Status DisplayDriverILI9341::WriteRow(span<uint16_t> row_pixels,
int row_idx,
int 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,
{ILI9341_CASET,
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,
{ILI9341_PASET,
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, {ILI9341_RAMWR, 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()));
}
int DisplayDriverILI9341::GetWidth() const { return kDisplayWidth; }
int DisplayDriverILI9341::GetHeight() const { return kDisplayHeight; }
Status DisplayDriverILI9341::Reset() {
if (!config_.reset_gpio)
return Status::Unavailable();
auto s = config_.reset_gpio->SetStateInactive();
if (!s.ok())
return s;
pw::spin_delay::WaitMillis(100);
s = config_.reset_gpio->SetStateActive();
pw::spin_delay::WaitMillis(100);
return s;
}
} // namespace pw::display_driver