rp2040: st7789 PixelPusher using PIO
- Add a SupportsResize function to pw_display_driver and
pw_pixel_pusher.
- Added a PixelPusher for the rp2040 that uses PIO to write the
framebuffer with 2x scaling (pixel doubling).
- common_pico.cc switched to use double buffering.
- Fix a missing pixel on the '8' character in font6x8.cc
- Add a kudzu board definition in targets/rp2040/board_configs.gni
- Add an OVERCLOCK_250 define for common_pico.cc rp2040
Change-Id: Ia8d98688d3d9c7191bfa465f414357868d4fc0c6
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/experimental/+/169450
Reviewed-by: Chris Mumford <cmumford@google.com>
Commit-Queue: Anthony DiGirolamo <tonymd@google.com>
Presubmit-Verified: CQ Bot Account <pigweed-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Anthony DiGirolamo <tonymd@google.com>
diff --git a/applications/app_common_impl/BUILD.gn b/applications/app_common_impl/BUILD.gn
index 92e3f0a..d84a014 100644
--- a/applications/app_common_impl/BUILD.gn
+++ b/applications/app_common_impl/BUILD.gn
@@ -58,6 +58,14 @@
]
}
+config("rp2040_pio_flags") {
+ cflags = [
+ "-DUSE_PIO=1",
+ # Overclock the rp2040 in common_pico.cc
+ # "-DOVERCLOCK_250=1",
+ ]
+}
+
pw_source_set("stm32cube") {
public_configs = [
":common_flags",
@@ -123,6 +131,7 @@
"$PICO_ROOT/src/common/pico_stdlib",
"$PICO_ROOT/src/rp2_common/hardware_pwm",
"$PICO_ROOT/src/rp2_common/hardware_spi",
+ "$PICO_ROOT/src/rp2_common/hardware_vreg",
"$dir_pigweed_experimental/applications/app_common:app_common.facade",
"$dir_pw_digital_io_pico",
"$dir_pw_display",
@@ -157,6 +166,22 @@
remove_configs = [ "$dir_pw_build:strict_warnings" ]
}
+pw_source_set("pico_st7789_pio") {
+ public_configs = [
+ ":common_flags",
+ ":spi_flags",
+ ":rp2040_pio_flags",
+ ]
+ cflags = [ "-DDISPLAY_TYPE_ST7789_PIO" ]
+ deps = _pico_common_deps
+ deps += [
+ "$dir_pw_display_driver_st7789",
+ "$dir_pw_pixel_pusher_rp2040_pio",
+ ]
+ sources = [ "common_pico.cc" ]
+ remove_configs = [ "$dir_pw_build:strict_warnings" ]
+}
+
pw_source_set("pico_st7735") {
public_configs = [
":common_flags",
diff --git a/applications/app_common_impl/common_pico.cc b/applications/app_common_impl/common_pico.cc
index 8f1973a..c2c2cc5 100644
--- a/applications/app_common_impl/common_pico.cc
+++ b/applications/app_common_impl/common_pico.cc
@@ -20,9 +20,11 @@
#include "hardware/gpio.h"
#include "hardware/pwm.h"
+#include "hardware/vreg.h"
#include "pico/stdlib.h"
#include "pw_digital_io_pico/digital_io.h"
#include "pw_log/log.h"
+#include "pw_pixel_pusher_rp2040_pio/pixel_pusher.h"
#include "pw_spi_pico/chip_selector.h"
#include "pw_spi_pico/initiator.h"
#include "pw_sync/borrow.h"
@@ -37,6 +39,9 @@
#elif defined(DISPLAY_TYPE_ST7789)
#include "pw_display_driver_st7789/display_driver.h"
using DisplayDriver = pw::display_driver::DisplayDriverST7789;
+#elif defined(DISPLAY_TYPE_ST7789_PIO)
+#include "pw_display_driver_st7789/display_driver.h"
+using DisplayDriver = pw::display_driver::DisplayDriverST7789;
#else
#error "Undefined display type"
#endif
@@ -48,6 +53,7 @@
using pw::framebuffer::Framebuffer;
using pw::framebuffer::PixelFormat;
using pw::framebuffer_pool::FramebufferPool;
+using pw::pixel_pusher::PixelPusherRp2040Pio;
using pw::spi::Device;
using pw::spi::Initiator;
using pw::spi::PicoChipSelector;
@@ -73,7 +79,7 @@
static_assert(DISPLAY_WIDTH > 0);
static_assert(DISPLAY_HEIGHT > 0);
-constexpr uint16_t kDisplayScaleFactor = 1;
+constexpr uint16_t kDisplayScaleFactor = 2;
constexpr uint16_t kFramebufferWidth =
FRAMEBUFFER_WIDTH >= 0 ? FRAMEBUFFER_WIDTH / kDisplayScaleFactor
: DISPLAY_WIDTH / kDisplayScaleFactor;
@@ -116,8 +122,18 @@
SpiValues s_spi_16_bit(kSpiConfig16Bit,
s_spi_chip_selector,
s_spi_initiator_mutex);
-uint16_t s_pixel_data[kNumPixels];
-const pw::Vector<void*, 1> s_pixel_buffers{s_pixel_data};
+
+#if USE_PIO
+PixelPusherRp2040Pio s_pixel_pusher(DISPLAY_DC_GPIO,
+ DISPLAY_CS_GPIO,
+ SPI_MOSI_GPIO,
+ SPI_CLOCK_GPIO,
+ DISPLAY_TE_GPIO,
+ pio0);
+#endif
+uint16_t s_pixel_data1[kNumPixels];
+uint16_t s_pixel_data2[kNumPixels];
+const pw::Vector<void*, 2> s_pixel_buffers{s_pixel_data1, s_pixel_data2};
pw::framebuffer_pool::FramebufferPool s_fb_pool({
.fb_addr = s_pixel_buffers,
.dimensions = {kFramebufferWidth, kFramebufferHeight},
@@ -125,7 +141,7 @@
.pixel_format = PixelFormat::RGB565,
});
DisplayDriver s_display_driver({
- .data_cmd_gpio = s_display_dc_pin,
+ .data_cmd_gpio = s_display_dc_pin, .spi_cs_gpio = s_display_cs_pin,
#if DISPLAY_RESET_GPIO != -1
.reset_gpio = &s_display_reset_pin,
#else
@@ -138,6 +154,9 @@
#endif
.spi_device_8_bit = s_spi_8_bit.device,
.spi_device_16_bit = s_spi_16_bit.device,
+#if USE_PIO
+ .pixel_pusher = &s_pixel_pusher,
+#endif
});
Display s_display(s_display_driver, kDisplaySize, s_fb_pool);
@@ -163,6 +182,13 @@
// static
Status Common::Init() {
+#if OVERCLOCK_250
+ // Overvolt for a stable 250MHz on some RP2040s
+ vreg_set_voltage(VREG_VOLTAGE_1_20);
+ sleep_ms(10);
+ set_sys_clock_khz(250000, false);
+#endif
+
// Initialize all of the present standard stdio types that are linked into the
// binary.
stdio_init_all();
@@ -193,7 +219,15 @@
gpio_set_function(SPI_CLOCK_GPIO, GPIO_FUNC_SPI);
gpio_set_function(SPI_MOSI_GPIO, GPIO_FUNC_SPI);
+#if USE_PIO
+ // Init the display before the pixel pusher.
+ s_display_driver.Init();
+ auto result = s_pixel_pusher.Init(s_fb_pool);
+ s_pixel_pusher.SetPixelDouble(true);
+ return result;
+#else
return s_display_driver.Init();
+#endif
}
// static
diff --git a/build_overrides/pigweed.gni b/build_overrides/pigweed.gni
index 9dae5cc..cce6d5c 100644
--- a/build_overrides/pigweed.gni
+++ b/build_overrides/pigweed.gni
@@ -147,4 +147,7 @@
"abspath")
dir_pw_pixel_pusher =
get_path_info("$dir_pigweed_experimental/pw_pixel_pusher", "abspath")
+ dir_pw_pixel_pusher_rp2040_pio =
+ get_path_info("$dir_pigweed_experimental/pw_pixel_pusher_rp2040_pio",
+ "abspath")
}
diff --git a/pw_display_driver/public/pw_display_driver/display_driver.h b/pw_display_driver/public/pw_display_driver/display_driver.h
index bc9cc4f..95d6283 100644
--- a/pw_display_driver/public/pw_display_driver/display_driver.h
+++ b/pw_display_driver/public/pw_display_driver/display_driver.h
@@ -51,6 +51,9 @@
virtual uint16_t GetWidth() const = 0;
virtual uint16_t GetHeight() const = 0;
+
+ // Display driver supports resizing during write.
+ virtual bool SupportsResize() const { return false; }
};
} // namespace pw::display_driver
diff --git a/pw_display_driver_st7789/BUILD.gn b/pw_display_driver_st7789/BUILD.gn
index 93af99b..5800337 100644
--- a/pw_display_driver_st7789/BUILD.gn
+++ b/pw_display_driver_st7789/BUILD.gn
@@ -30,6 +30,7 @@
public_deps = [
"$dir_pw_digital_io",
"$dir_pw_display_driver:display_driver",
+ "$dir_pw_pixel_pusher:pixel_pusher",
"$dir_pw_spi:device",
]
sources = [ "display_driver.cc" ]
diff --git a/pw_display_driver_st7789/display_driver.cc b/pw_display_driver_st7789/display_driver.cc
index c45990a..a635289 100644
--- a/pw_display_driver_st7789/display_driver.cc
+++ b/pw_display_driver_st7789/display_driver.cc
@@ -186,6 +186,14 @@
void DisplayDriverST7789::WriteFramebuffer(Framebuffer frame_buffer,
WriteCallback write_callback) {
PW_ASSERT(frame_buffer.pixel_format() == PixelFormat::RGB565);
+ if (config_.pixel_pusher) {
+ // Write the pixel data.
+ config_.pixel_pusher->WriteFramebuffer(std::move(frame_buffer),
+ std::move(write_callback));
+ return;
+ }
+
+ // Write the frame_buffer using pw_spi.
// Let controller know a write is coming.
Status s;
{
@@ -260,4 +268,9 @@
return s;
}
+bool DisplayDriverST7789::SupportsResize() const {
+ return config_.pixel_pusher != nullptr &&
+ config_.pixel_pusher->SupportsResize();
+}
+
} // namespace pw::display_driver
diff --git a/pw_display_driver_st7789/public/pw_display_driver_st7789/display_driver.h b/pw_display_driver_st7789/public/pw_display_driver_st7789/display_driver.h
index 7000a8a..5f13d65 100644
--- a/pw_display_driver_st7789/public/pw_display_driver_st7789/display_driver.h
+++ b/pw_display_driver_st7789/public/pw_display_driver_st7789/display_driver.h
@@ -17,6 +17,7 @@
#include "pw_digital_io/digital_io.h"
#include "pw_display_driver/display_driver.h"
+#include "pw_pixel_pusher/pixel_pusher.h"
#include "pw_spi/device.h"
namespace pw::display_driver {
@@ -28,6 +29,7 @@
// The GPIO line to use when specifying data/command mode for the display
// controller.
pw::digital_io::DigitalOut& data_cmd_gpio;
+ pw::digital_io::DigitalOut& spi_cs_gpio;
// GPIO line to reset the display controller.
pw::digital_io::DigitalOut* reset_gpio;
pw::digital_io::DigitalIn* tear_effect_gpio;
@@ -39,6 +41,8 @@
pw::spi::Device& spi_device_16_bit;
uint16_t screen_width = 320;
uint16_t screen_height = 240;
+ // The pixel pusher.
+ pw::pixel_pusher::PixelPusher* pixel_pusher = nullptr;
};
DisplayDriverST7789(const Config& config);
@@ -52,6 +56,7 @@
uint16_t col_idx) override;
uint16_t GetWidth() const override { return config_.screen_width; }
uint16_t GetHeight() const override { return config_.screen_height; }
+ bool SupportsResize() const override;
private:
enum class Mode {
diff --git a/pw_graphics/pw_display/display.cc b/pw_graphics/pw_display/display.cc
index 6264c17..317bc93 100644
--- a/pw_graphics/pw_display/display.cc
+++ b/pw_graphics/pw_display/display.cc
@@ -92,6 +92,7 @@
return framebuffer_pool_.GetFramebuffer();
}
+// This may be async
Status Display::ReleaseFramebuffer(Framebuffer framebuffer) {
pw::framebuffer_pool::FramebufferPool& fb_pool = framebuffer_pool_;
auto write_cb = [&fb_pool](pw::framebuffer::Framebuffer fb, Status status) {
@@ -102,9 +103,14 @@
return Status::InvalidArgument();
if (framebuffer.size() != size_) {
#if DISPLAY_RESIZE
- Status result = UpdateNearestNeighbor(framebuffer);
- write_cb(std::move(framebuffer), result);
- return result;
+ if (display_driver_.SupportsResize()) {
+ display_driver_.WriteFramebuffer(std::move(framebuffer), write_cb);
+ return OkStatus();
+ } else {
+ Status result = UpdateNearestNeighbor(framebuffer);
+ write_cb(std::move(framebuffer), result);
+ return result;
+ }
#endif
// Rely on display driver's ability to support size mismatch. It is
// expected to return an error if it cannot.
diff --git a/pw_graphics/pw_draw/public/pw_draw/font6x8.cc b/pw_graphics/pw_draw/public/pw_draw/font6x8.cc
index 3ee9e43..5a78595 100644
--- a/pw_graphics/pw_draw/public/pw_draw/font6x8.cc
+++ b/pw_graphics/pw_draw/public/pw_draw/font6x8.cc
@@ -260,7 +260,7 @@
0b100010,
0b100010,
0b011100,
- 0b000010,
+ 0b100010,
0b100010,
0b011100,
0b000000,
diff --git a/pw_pixel_pusher/public/pw_pixel_pusher/pixel_pusher.h b/pw_pixel_pusher/public/pw_pixel_pusher/pixel_pusher.h
index d8edce9..6a6df0a 100644
--- a/pw_pixel_pusher/public/pw_pixel_pusher/pixel_pusher.h
+++ b/pw_pixel_pusher/public/pw_pixel_pusher/pixel_pusher.h
@@ -26,11 +26,14 @@
virtual ~PixelPusher() = default;
+ // PixelPusher implementation:
virtual Status Init(
const pw::framebuffer_pool::FramebufferPool& framebuffer_pool) = 0;
virtual void WriteFramebuffer(framebuffer::Framebuffer framebuffer,
WriteCallback complete_callback) = 0;
+
+ virtual bool SupportsResize() const { return false; }
};
} // namespace pw::pixel_pusher
diff --git a/pw_pixel_pusher_rp2040_pio/BUILD.gn b/pw_pixel_pusher_rp2040_pio/BUILD.gn
new file mode 100644
index 0000000..6b358d2
--- /dev/null
+++ b/pw_pixel_pusher_rp2040_pio/BUILD.gn
@@ -0,0 +1,41 @@
+# 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.
+
+import("//build_overrides/pi_pico.gni")
+import("//build_overrides/pigweed.gni")
+
+import("$dir_pw_build/target_types.gni")
+
+config("default_config") {
+ include_dirs = [ "public" ]
+}
+
+pw_source_set("pw_pixel_pusher_rp2040_pio") {
+ public_configs = [ ":default_config" ]
+ deps = [
+ "$dir_pw_function",
+ "$dir_pw_log",
+ ]
+ public_deps = [
+ "$PICO_ROOT/src/rp2_common/hardware_dma",
+ "$PICO_ROOT/src/rp2_common/hardware_pio",
+ "$PICO_ROOT/src/rp2_common/hardware_spi",
+ "$dir_pw_digital_io",
+ "$dir_pw_framebuffer_pool",
+ "$dir_pw_pixel_pusher:pixel_pusher",
+ "$dir_pw_sync:binary_semaphore",
+ ]
+ sources = [ "pixel_pusher.cc" ]
+ remove_configs = [ "$dir_pw_build:strict_warnings" ]
+}
diff --git a/pw_pixel_pusher_rp2040_pio/pixel_pusher.cc b/pw_pixel_pusher_rp2040_pio/pixel_pusher.cc
new file mode 100644
index 0000000..b4ce090
--- /dev/null
+++ b/pw_pixel_pusher_rp2040_pio/pixel_pusher.cc
@@ -0,0 +1,304 @@
+// 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_pixel_pusher_rp2040_pio/pixel_pusher.h"
+
+#include <array>
+#include <cstddef>
+
+#include "hardware/dma.h"
+#include "hardware/pio.h"
+#include "hardware/spi.h"
+#include "pw_digital_io/digital_io.h"
+#include "pw_pixel_pusher_rp2040_pio/st7789.pio.h"
+
+using pw::Status;
+using pw::framebuffer::Framebuffer;
+using pw::framebuffer_pool::FramebufferPool;
+
+namespace pw::pixel_pusher {
+
+namespace {
+
+Framebuffer s_framebuffer;
+Callback<void(Framebuffer, Status)> s_draw_callback;
+static uint dma_channel;
+static volatile int current_scanline = 240;
+static volatile int irq_fire_count;
+
+static void __isr irq_handler() {
+ irq_fire_count++;
+ // Write was active, just finished
+ if (dma_channel_get_irq0_status(dma_channel)) {
+ dma_channel_acknowledge_irq0(dma_channel);
+
+ // * 2 for pixel doubling
+ uint16_t fb_width = s_framebuffer.size().width * 2;
+ uint16_t fb_height = s_framebuffer.size().height * 2;
+
+ if (++current_scanline > fb_height / 2) {
+ // All scanlines written. This frame is done.
+ if (s_draw_callback != nullptr) {
+ s_draw_callback(std::move(s_framebuffer), pw::OkStatus());
+ s_draw_callback = nullptr;
+ }
+ return;
+ }
+
+ auto count =
+ current_scanline == (fb_height + 1) / 2 ? fb_width / 4 : fb_width / 2;
+
+ dma_channel_set_trans_count(dma_channel,
+ /* trans_count= */ count,
+ /* trigger= */ false);
+ dma_channel_set_read_addr(
+ dma_channel,
+ /* read_addr= */ static_cast<const uint16_t*>(s_framebuffer.data()) +
+ (current_scanline - 1) * (fb_width / 2),
+ /* trigger= */ true);
+ }
+}
+
+// PIO helpers
+static void pio_put_byte(PIO pio, uint sm, uint8_t b) {
+ while (pio_sm_is_tx_fifo_full(pio, sm))
+ ;
+ *(volatile uint8_t*)&pio->txf[sm] = b;
+}
+
+static void pio_wait(PIO pio, uint sm) {
+ uint32_t stall_mask = 1u << (PIO_FDEBUG_TXSTALL_LSB + sm);
+ pio->fdebug |= stall_mask;
+ while (!(pio->fdebug & stall_mask))
+ ;
+}
+
+} // namespace
+
+PixelPusherRp2040Pio::PixelPusherRp2040Pio(
+ int dc_pin, int cs_pin, int dout_pin, int sck_pin, int te_pin, PIO pio)
+ : dc_pin_(dc_pin),
+ cs_pin_(cs_pin),
+ dout_pin_(dout_pin),
+ sck_pin_(sck_pin),
+ te_pin_(te_pin),
+ pio_(pio) {}
+PixelPusherRp2040Pio::~PixelPusherRp2040Pio() = default;
+
+Status PixelPusherRp2040Pio::Init(const FramebufferPool& framebuffer_pool) {
+ const FramebufferPool::BufferArray& buffers =
+ framebuffer_pool.GetBuffersForInit();
+ if (buffers.empty()) {
+ return Status::Internal();
+ }
+
+ // PIO Setup
+ pio_offset_ = pio_add_program(pio_, /* program= */ &st7789_raw_program);
+ pio_double_offset_ =
+ pio_add_program(pio_, /* program= */ &st7789_pixel_double_program);
+
+ pio_sm_ = pio_claim_unused_sm(pio_, true);
+
+ pio_sm_config pio_config = st7789_raw_program_get_default_config(pio_offset_);
+
+#if OVERCLOCK_250
+ sm_config_set_clkdiv(&pio_config, 2); // Clock divide by 2 for 62.5MHz
+#endif
+
+ sm_config_set_out_shift(&pio_config,
+ /* shift_right= */ false,
+ /* autopull= */ true,
+ /* pull_threshold= */ 8);
+ sm_config_set_out_pins(
+ &pio_config, /* out_base= */ dout_pin_, /* out_count= */ 1);
+ sm_config_set_fifo_join(&pio_config, /* pio_fifo_join= */ PIO_FIFO_JOIN_TX);
+ sm_config_set_sideset_pins(&pio_config, /* sideset_base= */ sck_pin_);
+
+ pio_gpio_init(pio_, /* pin= */ dout_pin_);
+ pio_gpio_init(pio_, /* pin= */ sck_pin_);
+ pio_sm_set_consecutive_pindirs(pio_,
+ pio_sm_,
+ /* pin_base= */ dout_pin_,
+ /* pin_count= */ 1,
+ /* is_out= */ true);
+ pio_sm_set_consecutive_pindirs(pio_,
+ pio_sm_,
+ /* pin_base= */ sck_pin_,
+ /* pin_count= */ 1,
+ /* is_out= */ true);
+
+ pio_sm_init(pio_, pio_sm_, /* initial_pc= */ pio_offset_, &pio_config);
+ pio_sm_set_enabled(pio_, pio_sm_, /* enabled= */ true);
+
+ // DMA Setup
+ dma_channel_ = dma_claim_unused_channel(true);
+ dma_channel_config config = dma_channel_get_default_config(dma_channel_);
+ channel_config_set_transfer_data_size(
+ &config, /* dma_channel_transfer_size= */ DMA_SIZE_16);
+ // DMA byte swapping: off
+ channel_config_set_bswap(&config, /* bswap= */ false);
+ // Set tranfer request signal to a dreq (data request).
+ channel_config_set_dreq(
+ &config,
+ /* dreq= */ pio_get_dreq(pio_, pio_sm_, /* is_tx= */ true));
+ dma_channel_configure(dma_channel_,
+ &config,
+ /* write_addr= */ &pio_->txf[pio_sm_],
+ /* read_addr= */ NULL, // framebuffer
+ /* transfer_count= */ 0, // width * height
+ /* trigger= */ false);
+
+ irq_add_shared_handler(
+ /* num= */ DMA_IRQ_0,
+ /* handler= */ irq_handler,
+ /* order_priority= */ PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY);
+ irq_set_enabled(DMA_IRQ_0, /* enabled= */ true);
+
+ return OkStatus();
+}
+
+void PixelPusherRp2040Pio::WriteFramebuffer(
+ framebuffer::Framebuffer framebuffer,
+ Callback<void(framebuffer::Framebuffer, Status)> complete_callback) {
+ // If this should be non-blocking
+ // if (dma_channel_is_busy(dma_channel)) {
+ // return;
+ // }
+
+ // If not vsync
+ while (DmaIsBusy()) {
+ }
+
+ dma_channel_wait_for_finish_blocking(dma_channel);
+
+ if (!write_mode_) {
+ SetupWriteFramebuffer();
+ }
+
+ PW_ASSERT(s_draw_callback == nullptr);
+ s_draw_callback = std::move(complete_callback);
+ s_framebuffer = std::move(framebuffer);
+ dma_channel = dma_channel_;
+
+ const uint16_t* fb_data = static_cast<const uint16_t*>(s_framebuffer.data());
+ int fb_width = s_framebuffer.size().width;
+ int fb_height = s_framebuffer.size().height;
+
+ if (pixel_double_enabled_) {
+ fb_width *= 2;
+ fb_height *= 2;
+ current_scanline = 0;
+ irq_fire_count = 0;
+ dma_channel_set_trans_count(dma_channel_, fb_width / 4, false);
+ } else {
+ dma_channel_set_trans_count(dma_channel_, fb_width * fb_height, false);
+ }
+
+ dma_channel_set_read_addr(dma_channel_, fb_data, true);
+}
+
+void PixelPusherRp2040Pio::SetPixelDouble(bool enabled) {
+ pixel_double_enabled_ = enabled;
+
+ if (pixel_double_enabled_) {
+ dma_channel_acknowledge_irq0(dma_channel);
+ dma_channel_set_irq0_enabled(dma_channel, true);
+ } else {
+ dma_channel_set_irq0_enabled(dma_channel, false);
+ }
+}
+
+bool PixelPusherRp2040Pio::DmaIsBusy() {
+ if (pixel_double_enabled_ &&
+ current_scanline <= (s_framebuffer.size().height * 2) / 2) {
+ return true;
+ }
+ return dma_channel_is_busy(dma_channel);
+}
+
+void PixelPusherRp2040Pio::Clear() {
+ if (!write_mode_)
+ SetupWriteFramebuffer();
+
+ int fb_width = s_framebuffer.size().width;
+ int fb_height = s_framebuffer.size().height;
+ if (pixel_double_enabled_) {
+ fb_width *= 2;
+ fb_height *= 2;
+ }
+
+ for (int i = 0; i < fb_width * fb_height; i++)
+ pio_sm_put_blocking(pio_, pio_sm_, 0);
+}
+
+bool PixelPusherRp2040Pio::VsyncCallback(gpio_irq_callback_t callback) {
+#if DISPLAY_TE_GPIO != -1
+ gpio_set_irq_enabled_with_callback(
+ te_pin_, GPIO_IRQ_EDGE_RISE, true, callback);
+ return true;
+#else
+ return false;
+#endif
+}
+
+void PixelPusherRp2040Pio::SetupWriteFramebuffer() {
+ pio_wait(pio_, pio_sm_);
+
+ gpio_put(cs_pin_, 0);
+
+ // Enter command mode.
+ gpio_put(dc_pin_, 0);
+ // Tell the display a framebuffer is coming next.
+ pio_put_byte(pio_, pio_sm_, 0x2C); // ST7789_RAMWR
+ pio_wait(pio_, pio_sm_);
+
+ // Enter data mode.
+ gpio_put(dc_pin_, 1);
+
+ pio_sm_set_enabled(pio_, pio_sm_, false);
+ pio_sm_restart(pio_, pio_sm_);
+
+ if (pixel_double_enabled_) {
+ // Switch PIO to the pixel double program.
+ pio_sm_set_wrap(pio_,
+ pio_sm_,
+ pio_double_offset_ + st7789_pixel_double_wrap_target,
+ pio_double_offset_ + st7789_pixel_double_wrap);
+
+ pio_->sm[pio_sm_].shiftctrl &=
+ ~(PIO_SM0_SHIFTCTRL_PULL_THRESH_BITS | PIO_SM0_SHIFTCTRL_AUTOPULL_BITS);
+
+ pio_sm_exec(pio_, pio_sm_, pio_encode_jmp(pio_double_offset_));
+
+ dma_channel_hw_addr(dma_channel_)->al1_ctrl &=
+ ~DMA_CH0_CTRL_TRIG_DATA_SIZE_BITS;
+ dma_channel_hw_addr(dma_channel_)->al1_ctrl |=
+ DMA_SIZE_32 << DMA_CH0_CTRL_TRIG_DATA_SIZE_LSB;
+ } else {
+ pio_->sm[pio_sm_].shiftctrl &= ~PIO_SM0_SHIFTCTRL_PULL_THRESH_BITS;
+ pio_->sm[pio_sm_].shiftctrl |= (16 << PIO_SM0_SHIFTCTRL_PULL_THRESH_LSB) |
+ PIO_SM0_SHIFTCTRL_AUTOPULL_BITS;
+
+ dma_channel_hw_addr(dma_channel_)->al1_ctrl &=
+ ~DMA_CH0_CTRL_TRIG_DATA_SIZE_BITS;
+ dma_channel_hw_addr(dma_channel_)->al1_ctrl |=
+ DMA_SIZE_16 << DMA_CH0_CTRL_TRIG_DATA_SIZE_LSB;
+ }
+
+ pio_sm_set_enabled(pio_, pio_sm_, true);
+
+ write_mode_ = true;
+}
+
+} // namespace pw::pixel_pusher
diff --git a/pw_pixel_pusher_rp2040_pio/public/pw_pixel_pusher_rp2040_pio/pixel_pusher.h b/pw_pixel_pusher_rp2040_pio/public/pw_pixel_pusher_rp2040_pio/pixel_pusher.h
new file mode 100644
index 0000000..51e9f55
--- /dev/null
+++ b/pw_pixel_pusher_rp2040_pio/public/pw_pixel_pusher_rp2040_pio/pixel_pusher.h
@@ -0,0 +1,64 @@
+// 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.
+#pragma once
+
+#include <array>
+#include <cstddef>
+
+#include "hardware/dma.h"
+#include "hardware/irq.h"
+#include "hardware/pio.h"
+#include "hardware/spi.h"
+#include "pw_digital_io/digital_io.h"
+#include "pw_pixel_pusher/pixel_pusher.h"
+#include "pw_sync/binary_semaphore.h"
+
+namespace pw::pixel_pusher {
+
+class PixelPusherRp2040Pio : public PixelPusher {
+ public:
+ PixelPusherRp2040Pio(
+ int dc_pin, int cs_pin, int dout_pin, int sck_pin_, int te_pin, PIO pio);
+ ~PixelPusherRp2040Pio();
+
+ // PixelPusher implementation:
+ Status Init(
+ const pw::framebuffer_pool::FramebufferPool& framebuffer_pool) override;
+ void WriteFramebuffer(framebuffer::Framebuffer framebuffer,
+ WriteCallback complete_callback) override;
+ bool SupportsResize() const override { return true; }
+
+ // RP2040 PIO Functions
+ void SetPixelDouble(bool enabled);
+ bool DmaIsBusy();
+ void SetupWriteFramebuffer();
+ void Clear();
+ bool VsyncCallback(gpio_irq_callback_t callback);
+
+ private:
+ bool pixel_double_enabled_ = false;
+ bool write_mode_ = false;
+ PIO pio_;
+ uint dma_channel_;
+ uint dc_pin_;
+ uint cs_pin_;
+ uint te_pin_;
+ uint dout_pin_;
+ uint sck_pin_;
+ uint pio_sm_;
+ uint pio_offset_;
+ uint pio_double_offset_;
+};
+
+} // namespace pw::pixel_pusher
diff --git a/pw_pixel_pusher_rp2040_pio/public/pw_pixel_pusher_rp2040_pio/st7789.pio.h b/pw_pixel_pusher_rp2040_pio/public/pw_pixel_pusher_rp2040_pio/st7789.pio.h
new file mode 100644
index 0000000..f724347
--- /dev/null
+++ b/pw_pixel_pusher_rp2040_pio/public/pw_pixel_pusher_rp2040_pio/st7789.pio.h
@@ -0,0 +1,86 @@
+// -------------------------------------------------- //
+// This file is autogenerated by pioasm; do not edit! //
+// -------------------------------------------------- //
+
+#pragma once
+
+#if !PICO_NO_HARDWARE
+#include "hardware/pio.h"
+#endif
+
+// ---------- //
+// st7789_raw //
+// ---------- //
+
+#define st7789_raw_wrap_target 0
+#define st7789_raw_wrap 1
+
+static const uint16_t st7789_raw_program_instructions[] = {
+ // .wrap_target
+ 0x7001, // 0: out pins, 1 side 0
+ 0xb842, // 1: nop side 1
+ // .wrap
+};
+
+#if !PICO_NO_HARDWARE
+static const struct pio_program st7789_raw_program = {
+ .instructions = st7789_raw_program_instructions,
+ .length = 2,
+ .origin = -1,
+};
+
+static inline pio_sm_config st7789_raw_program_get_default_config(uint offset) {
+ pio_sm_config c = pio_get_default_sm_config();
+ sm_config_set_wrap(
+ &c, offset + st7789_raw_wrap_target, offset + st7789_raw_wrap);
+ sm_config_set_sideset(&c, 2, true, false);
+ return c;
+}
+#endif
+
+// ------------------- //
+// st7789_pixel_double //
+// ------------------- //
+
+#define st7789_pixel_double_wrap_target 0
+#define st7789_pixel_double_wrap 16
+
+static const uint16_t st7789_pixel_double_program_instructions[] = {
+ // .wrap_target
+ 0x80a0, // 0: pull block
+ 0xa027, // 1: mov x, osr
+ 0x6050, // 2: out y, 16
+ 0x7001, // 3: out pins, 1 side 0
+ 0x18e3, // 4: jmp !osre, 3 side 1
+ 0xa0e1, // 5: mov osr, x
+ 0x6070, // 6: out null, 16
+ 0x7001, // 7: out pins, 1 side 0
+ 0x18e7, // 8: jmp !osre, 7 side 1
+ 0xa0e2, // 9: mov osr, y
+ 0x6070, // 10: out null, 16
+ 0x7001, // 11: out pins, 1 side 0
+ 0x18eb, // 12: jmp !osre, 11 side 1
+ 0xa0e2, // 13: mov osr, y
+ 0x6070, // 14: out null, 16
+ 0x7001, // 15: out pins, 1 side 0
+ 0x18ef, // 16: jmp !osre, 15 side 1
+ // .wrap
+};
+
+#if !PICO_NO_HARDWARE
+static const struct pio_program st7789_pixel_double_program = {
+ .instructions = st7789_pixel_double_program_instructions,
+ .length = 17,
+ .origin = -1,
+};
+
+static inline pio_sm_config st7789_pixel_double_program_get_default_config(
+ uint offset) {
+ pio_sm_config c = pio_get_default_sm_config();
+ sm_config_set_wrap(&c,
+ offset + st7789_pixel_double_wrap_target,
+ offset + st7789_pixel_double_wrap);
+ sm_config_set_sideset(&c, 2, true, false);
+ return c;
+}
+#endif
diff --git a/targets/rp2040/BUILD.gn b/targets/rp2040/BUILD.gn
index dfb037c..10f7072 100644
--- a/targets/rp2040/BUILD.gn
+++ b/targets/rp2040/BUILD.gn
@@ -60,7 +60,7 @@
# Merge in the app_common_BACKEND and various application settings
# which are defined in board_configs.gni.
- forward_variables_from(board_config_st7789, "*")
+ forward_variables_from(board_config_kudzu, "*")
# forward_variables_from(board_config_st7735, "*")
# forward_variables_from(board_config_ili9341, "*")
@@ -78,6 +78,7 @@
pw_interrupt_CONTEXT_BACKEND = "$dir_pw_interrupt_cortex_m:context_armv7m"
pw_sync_INTERRUPT_SPIN_LOCK_BACKEND =
"$dir_pw_sync_freertos:interrupt_spin_lock"
+ pw_sync_BINARY_SEMAPHORE_BACKEND = "$dir_pw_sync_freertos:binary_semaphore"
pw_sync_MUTEX_BACKEND = "$dir_pw_sync_baremetal:mutex"
pw_sync_COUNTING_SEMAPHORE_BACKEND =
"$dir_pw_sync_freertos:counting_semaphore"
diff --git a/targets/rp2040/board_configs.gni b/targets/rp2040/board_configs.gni
index e114ecc..9cb5d12 100644
--- a/targets/rp2040/board_configs.gni
+++ b/targets/rp2040/board_configs.gni
@@ -63,3 +63,18 @@
pw_app_common_SPI_MOSI_GPIO = "19"
pw_app_common_SPI_CLOCK_GPIO = "18"
}
+
+board_config_kudzu = {
+ app_common_BACKEND =
+ "$dir_pigweed_experimental/applications/app_common_impl:pico_st7789_pio"
+ pw_app_common_DISPLAY_WIDTH = "320"
+ pw_app_common_DISPLAY_HEIGHT = "240"
+ pw_app_common_BACKLIGHT_GPIO = "20"
+ pw_app_common_DISPLAY_TE_GPIO = "21"
+ pw_app_common_DISPLAY_CS_GPIO = "17"
+ pw_app_common_DISPLAY_DC_GPIO = "16"
+ pw_app_common_DISPLAY_RESET_GPIO = "-1"
+ pw_app_common_SPI_MISO_GPIO = "-1"
+ pw_app_common_SPI_MOSI_GPIO = "19"
+ pw_app_common_SPI_CLOCK_GPIO = "18"
+}