| .. _seed-0104: |
| |
| ===================== |
| 0104: Display Support |
| ===================== |
| .. seed:: |
| :number: 104 |
| :name: Display Support |
| :status: Accepted |
| :proposal_date: 2023-06-12 |
| :cl: 150793 |
| :authors: Chris Mumford |
| :facilitator: Anthony DiGirolamo |
| |
| ------- |
| Summary |
| ------- |
| Add support for graphics displays. This includes display drivers for a few |
| popular display controllers, framebuffer management, and a framework to simplify |
| adding a graphics display to a Pigweed application. |
| |
| ---------- |
| Motivation |
| ---------- |
| Pigweed currently has no specific support for a display device. Projects that |
| require a display currently must do the full implementation, including the |
| display driver in most instances, to add display support. |
| |
| This proposes the addition of a basic framework for display devices, as well |
| as implementations for a few common Pigweed test devices - specifically the |
| STM32F429I. This enables developers to quickly and easily add display support |
| for supported devices and an implementation to model when adding new device |
| support. |
| |
| --------------- |
| Proposal |
| --------------- |
| This proposes no changes to existing modules, but suggests several new modules |
| that together define a framework for rendering to displays. |
| |
| |
| New Modules |
| ----------- |
| |
| .. list-table:: |
| :widths: 5 45 |
| :header-rows: 1 |
| |
| * - Module |
| - Function |
| |
| * - pw_display |
| - Manage draw thread, framebuffers, and driver |
| |
| * - pw_display_driver |
| - Display driver interface definition |
| |
| * - pw_display_driver_ili9341 |
| - Display driver for the ILI9341 display controller |
| |
| * - pw_display_driver_imgui |
| - Host display driver using `Dear ImGui <https://www.dearimgui.com/>`_ |
| |
| * - pw_display_driver_mipi |
| - Display driver for `MIPI DSI <https://www.mipi.org/specifications/dsi>`_ controllers |
| |
| * - pw_display_driver_null |
| - Null display driver for headless devices |
| |
| * - pw_display_driver_st7735 |
| - Display driver for the `ST7735 <https://www.displayfuture.com/Display/datasheet/controller/ST7735.pdf>`_ display controller |
| |
| * - pw_display_driver_st7789 |
| - Display driver for the ST7789 display controller |
| |
| * - pw_simple_draw |
| - Very basic drawing library for test purposes |
| |
| * - pw_framebuffer |
| - Manage access to pixel buffer. |
| |
| * - pw_framebuffer_mcuxpresso |
| - Specialization of the framebuffer for the MCUxpresso devices |
| |
| * - pw_geometry |
| - Basic shared math types such as 2D vectors, etc. |
| |
| * - pw_pixel_pusher |
| - Transport of pixel data to display controller |
| |
| |
| Math |
| ---- |
| ``pw_geometry`` contains two helper structures for common values usually used as |
| a pair. |
| |
| .. code-block:: cpp |
| |
| namespace pw::math { |
| |
| template <typename T> |
| struct Size { |
| T width; |
| T height; |
| }; |
| |
| template <typename T> |
| struct Vector2 { |
| T x; |
| T y; |
| }; |
| |
| } // namespace pw::math |
| |
| |
| Framebuffer |
| ----------- |
| A framebuffer is a small class that provides access to a pixel buffer. It |
| keeps a copy of the pixel buffer metadata and provides accessor methods for |
| those values. |
| |
| .. code-block:: cpp |
| |
| namespace pw::framebuffer { |
| |
| enum class PixelFormat { |
| None, |
| RGB565, |
| }; |
| |
| class Framebuffer { |
| public: |
| // Construct a default invalid framebuffer. |
| Framebuffer(); |
| |
| Framebuffer(void* data, |
| PixelFormat pixel_format, |
| pw::math::Size<uint16_t> size, |
| uint16_t row_bytes); |
| |
| Framebuffer(const Framebuffer&) = delete; |
| Framebuffer(Framebuffer&& other); |
| |
| Framebuffer& operator=(const Framebuffer&) = delete; |
| Framebuffer& operator=(Framebuffer&&); |
| |
| bool is_valid() const; |
| |
| pw::ConstByteSpan data() const; |
| pw::ByteSpan data(); |
| |
| PixelFormat pixel_format() const; |
| |
| pw::math::Size<uint16_t> size(); |
| |
| uint16_t row_bytes() const; |
| }; |
| |
| } // namespace pw::framebuffer |
| |
| FrameBuffer is a moveable class that is intended to signify read/write |
| privileges to the underlying pixel data. This makes it easier to track when the |
| pixel data may be read from, or written to, without conflict. |
| |
| The framebuffer does not own the underlying pixel buffer. In other words |
| the deletion of a framebuffer will not free the underlying pixel data. |
| |
| Framebuffers do not have methods for reading or writing to the underlying pixel |
| buffer. This is the responsibility of the the selected graphics library which |
| can be given the pixel buffer pointer retrieved by calling ``data()``. |
| |
| .. code-block:: cpp |
| |
| constexpr size_t kWidth = 64; |
| constexpr size_t kHeight = 32; |
| uint16_t pixel_data[kWidth * kHeight]; |
| |
| void DrawScreen(Framebuffer* fb) { |
| // Clear framebuffer to black. |
| std::memset(fb->data(), 0, fb->height() * fb->row_bytes()); |
| |
| // Set first pixel to white. |
| uint16_t* pixel_data = static_cast<uint16_t*>(fb->data()); |
| pixel_data[0] = 0xffff; |
| } |
| |
| Framebuffer fb(pixel_data, {kWidth, kHeight}, |
| PixelFormat::RGB565, |
| kWidth * sizeof(uint16_t)); |
| DrawScreen(&fb); |
| |
| FramebufferPool |
| --------------- |
| |
| The FramebufferPool is intended to simplify the use of multiple framebuffers |
| when multi-buffered rendering is being used. It is a collection of framebuffers |
| which can be retrieved, used, and then returned to the pool for reuse. All |
| framebuffers in the pool share identical attributes. A framebuffer that is |
| returned to a caller of ``GetFramebuffer()`` can be thought of as "on loan" to |
| that caller and will not be given to any other caller of ``GetFramebuffer()`` |
| until it has been returned by calling ``ReleaseFramebuffer()``. |
| |
| .. code-block:: cpp |
| |
| namespace pw::framebuffer_pool { |
| |
| class FramebufferPool { |
| public: |
| using BufferArray = std::array<void*, FRAMEBUFFER_COUNT>; |
| |
| // Constructor parameters. |
| struct Config { |
| BufferArray fb_addr; // Address of each buffer in this pool. |
| pw::math::Size<uint16_t> dimensions; // width/height of each buffer. |
| uint16_t row_bytes; // row bytes of each buffer. |
| pw::framebuffer::PixelFormat pixel_format; |
| }; |
| |
| FramebufferPool(const Config& config); |
| virtual ~FramebufferPool(); |
| |
| uint16_t row_bytes() const; |
| |
| pw::math::Size<uint16_t> dimensions() const; |
| |
| pw::framebuffer::PixelFormat pixel_format() const; |
| |
| // Return a framebuffer to the caller for use. This call WILL BLOCK until a |
| // framebuffer is returned for use. Framebuffers *must* be returned to this |
| // pool by a corresponding call to ReleaseFramebuffer. This function will only |
| // return a valid framebuffer. |
| // |
| // This call is thread-safe, but not interrupt safe. |
| virtual pw::framebuffer::Framebuffer GetFramebuffer(); |
| |
| // Return the framebuffer to the pool available for use by the next call to |
| // GetFramebuffer. |
| // |
| // This may be called on another thread or during an interrupt. |
| virtual Status ReleaseFramebuffer(pw::framebuffer::Framebuffer framebuffer); |
| }; |
| |
| } // namespace pw::framebuffer |
| |
| An example use of the framebuffer pool is: |
| |
| .. code-block:: cpp |
| |
| // Retrieve a framebuffer for drawing. May block if pool has no framebuffers |
| // to issue. |
| FrameBuffer fb = framebuffer_pool.GetFramebuffer(); |
| |
| // Draw to the framebuffer. |
| UpdateDisplay(&fb); |
| |
| // Return the framebuffer to the pool for reuse. |
| framebuffer_pool.ReleaseFramebuffer(std::move(fb)); |
| |
| DisplayDriver |
| ------------- |
| |
| A DisplayDriver is usually the sole class responsible for communicating with the |
| display controller. Its primary responsibilities are the display controller |
| initialization, and the writing of pixel data when a display update is needed. |
| |
| This proposal supports multiple heterogenous display controllers. This could be: |
| |
| 1. A single display of any given type (e.g. ILI9341). |
| 2. Two ILI9341 displays. |
| 3. Two ILI9341 displays and a second one of a different type. |
| |
| Because of this approach the DisplayDriver is defined as an interface: |
| |
| .. code-block:: cpp |
| |
| namespace pw::display_driver { |
| |
| class DisplayDriver { |
| public: |
| // Called on the completion of a write operation. |
| using WriteCallback = Callback<void(framebuffer::Framebuffer, Status)>; |
| |
| virtual ~DisplayDriver() = default; |
| |
| virtual Status Init() = 0; |
| |
| virtual void WriteFramebuffer(pw::framebuffer::Framebuffer framebuffer, |
| WriteCallback write_callback) = 0; |
| |
| virtual pw::math::Size<uint16_t> size() const = 0; |
| }; |
| |
| } // namespace pw::display_driver |
| |
| Each driver then provides a concrete implementation of the driver. Below is the |
| definition of the display driver for the ILI9341: |
| |
| .. code-block:: cpp |
| |
| namespace pw::display_driver { |
| |
| class DisplayDriverILI9341 : public DisplayDriver { |
| public: |
| struct Config { |
| // Device specific initialization parameters. |
| }; |
| |
| DisplayDriverILI9341(const Config& config); |
| |
| // DisplayDriver implementation: |
| Status Init() override; |
| void WriteFramebuffer(pw::framebuffer::Framebuffer framebuffer, |
| WriteCallback write_callback) override; |
| Status WriteRow(span<uint16_t> row_pixels, |
| uint16_t row_idx, |
| uint16_t col_idx) override; |
| pw::math::Size<uint16_t> size() const override; |
| |
| private: |
| enum class Mode { |
| kData, |
| kCommand, |
| }; |
| |
| // A command and optional data to write to the ILI9341. |
| struct Command { |
| uint8_t command; |
| ConstByteSpan command_data; |
| }; |
| |
| // Toggle the reset GPIO line to reset the display controller. |
| Status Reset(); |
| |
| // Set the command/data mode of the display controller. |
| void SetMode(Mode mode); |
| // Write the command to the display controller. |
| Status WriteCommand(pw::spi::Device::Transaction& transaction, |
| const Command& command); |
| }; |
| |
| } // namespace pw::display_driver |
| |
| Here is an example retrieving a framebuffer from the framebuffer pool, drawing |
| into the framebuffer, using the display driver to write the pixel data, and then |
| returning the framebuffer back to the pool for use. |
| |
| .. code-block:: cpp |
| |
| FrameBuffer fb = framebuffer_pool.GetFramebuffer(); |
| |
| // DrawScreen is a function that will draw to the framebuffer's underlying |
| // pixel buffer using a drawing library. See example above. |
| DrawScreen(&fb); |
| |
| display_driver_.WriteFramebuffer( |
| std::move(framebuffer), |
| [&framebuffer_pool](pw::framebuffer::Framebuffer fb, Status status) { |
| // Return the framebuffer back to the pool for reuse once the display |
| // write is complete. |
| framebuffer_pool.ReleaseFramebuffer(std::move(fb)); |
| }); |
| |
| In the example above that the framebuffer (``fb``) is moved when calling |
| ``WriteFramebuffer()`` passing ownership to the display driver. From this point |
| forward the application code may not access the framebuffer in any way. When the |
| framebuffer write is complete the framebuffer is then moved to the callback |
| which in turn moves it when calling ``ReleaseFramebuffer()``. |
| |
| ``WriteFramebuffer()`` always does a write of the full framebuffer - sending all |
| pixel data. |
| |
| ``WriteFramebuffer()`` may be a blocking call, but on some platforms the driver |
| may use a background write and the write callback is called when the write |
| is complete. The write callback **may be called during an interrupt**. |
| |
| PixelPusher |
| ----------- |
| Pixel data for Simple SPI based display controllers can be written to the |
| display controller using ``pw_spi``. There are some controllers which use |
| other interfaces (RGB, MIPI, etc.). Also, some vendors provide an API for |
| interacting with these display controllers for writing pixel data. |
| |
| To allow the drivers to be hardware/vendor independent the ``PixelPusher`` |
| may be used. This defines an interface whose sole responsibility is to write |
| a framebuffer to the display controller. Specializations of this will use |
| ``pw_spi`` or vendor proprietary calls to write pixel data. |
| |
| .. code-block:: cpp |
| |
| namespace pw::pixel_pusher { |
| |
| class PixelPusher { |
| public: |
| using WriteCallback = Callback<void(framebuffer::Framebuffer, Status)>; |
| |
| virtual ~PixelPusher() = default; |
| |
| virtual Status Init( |
| const pw::framebuffer_pool::FramebufferPool& framebuffer_pool) = 0; |
| |
| virtual void WriteFramebuffer(framebuffer::Framebuffer framebuffer, |
| WriteCallback complete_callback) = 0; |
| }; |
| |
| } // namespace pw::pixel_pusher |
| |
| Display |
| ------- |
| |
| Each display has: |
| |
| 1. One and only one display driver. |
| 2. A reference to a single framebuffer pool. This framebuffer pool may be shared |
| with other displays. |
| 3. A drawing thread, if so configured, for asynchronous display updates. |
| |
| .. code-block:: cpp |
| |
| namespace pw::display { |
| |
| class Display { |
| public: |
| // Called on the completion of an update. |
| using WriteCallback = Callback<void(Status)>; |
| |
| Display(pw::display_driver::DisplayDriver& display_driver, |
| pw::math::Size<uint16_t> size, |
| pw::framebuffer_pool::FramebufferPool& framebuffer_pool); |
| virtual ~Display(); |
| |
| pw::framebuffer::Framebuffer GetFramebuffer(); |
| |
| void ReleaseFramebuffer(pw::framebuffer::Framebuffer framebuffer, |
| WriteCallback callback); |
| |
| pw::math::Size<uint16_t> size() const; |
| }; |
| |
| } // namespace pw::display |
| |
| Once applications are initialized they typically will not directly interact with |
| display drivers or framebuffer pools. These will be utilized by the display |
| which will provide a simpler interface. |
| |
| ``Display::GetFramebuffer()`` must always be called on the same thread and is not |
| interrupt safe. It will block if there is no available framebuffer in the |
| framebuffer pool waiting for a framebuffer to be returned. |
| |
| ``Display::ReleaseFramebuffer()`` must be called for each framebuffer returned by |
| ``Display::GetFramebuffer()``. This will initiate the display update using the |
| displays associated driver. The ``callback`` will be called when this update is |
| complete. |
| |
| A simplified application rendering loop would resemble: |
| |
| .. code-block:: cpp |
| |
| // Get a framebuffer for drawing. |
| FrameBuffer fb = display.GetFramebuffer(); |
| |
| // DrawScreen is a function that will draw to |fb|'s pixel buffer using a |
| // drawing library. See example above. |
| DrawScreen(&fb); |
| |
| // Return the framebuffer to the display which will be written to the display |
| // controller by the display's display driver. |
| display.ReleaseFramebuffer(std::move(fb), [](Status){}); |
| |
| Simple Drawing Module |
| --------------------- |
| |
| ``pw_simple_draw`` was created for testing and verification purposes only. It is |
| not intended to be feature rich or performant in any way. This is small |
| collection of basic drawing primitives not intended to be used by shipping |
| applications. |
| |
| .. code-block:: cpp |
| |
| namespace pw::draw { |
| |
| void DrawLine(pw::framebuffer::Framebuffer& fb, |
| int x1, |
| int y1, |
| int x2, |
| int y2, |
| pw::color::color_rgb565_t pen_color); |
| |
| // Draw a circle at center_x, center_y with given radius and color. Only a |
| // one-pixel outline is drawn if filled is false. |
| void DrawCircle(pw::framebuffer::Framebuffer& fb, |
| int center_x, |
| int center_y, |
| int radius, |
| pw::color::color_rgb565_t pen_color, |
| bool filled); |
| |
| void DrawHLine(pw::framebuffer::Framebuffer& fb, |
| int x1, |
| int x2, |
| int y, |
| pw::color::color_rgb565_t pen_color); |
| |
| void DrawRect(pw::framebuffer::Framebuffer& fb, |
| int x1, |
| int y1, |
| int x2, |
| int y2, |
| pw::color::color_rgb565_t pen_color, |
| bool filled); |
| |
| void DrawRectWH(pw::framebuffer::Framebuffer& fb, |
| int x, |
| int y, |
| int w, |
| int h, |
| pw::color::color_rgb565_t pen_color, |
| bool filled); |
| |
| void Fill(pw::framebuffer::Framebuffer& fb, |
| pw::color::color_rgb565_t pen_color); |
| |
| void DrawSprite(pw::framebuffer::Framebuffer& fb, |
| int x, |
| int y, |
| pw::draw::SpriteSheet* sprite_sheet, |
| int integer_scale); |
| |
| void DrawTestPattern(); |
| |
| pw::math::Size<int> DrawCharacter(int ch, |
| pw::math::Vector2<int> pos, |
| pw::color::color_rgb565_t fg_color, |
| pw::color::color_rgb565_t bg_color, |
| const FontSet& font, |
| pw::framebuffer::Framebuffer& framebuffer); |
| |
| pw::math::Size<int> DrawString(std::wstring_view str, |
| pw::math::Vector2<int> pos, |
| pw::color::color_rgb565_t fg_color, |
| pw::color::color_rgb565_t bg_color, |
| const FontSet& font, |
| pw::framebuffer::Framebuffer& framebuffer); |
| |
| } // namespace pw::draw |
| |
| Class Interaction Diagram |
| ------------------------- |
| |
| .. mermaid:: |
| :alt: Framebuffer Classes |
| :align: center |
| |
| classDiagram |
| class FramebufferPool { |
| uint16_t row_bytes() |
| PixelFormat pixel_format() |
| dimensions() : Size~uint16_t~ |
| row_bytes() : uint16_t |
| GetFramebuffer() : Framebuffer |
| |
| BufferArray buffer_addresses_ |
| Size~uint16_t~ buffer_dimensions_ |
| uint16_t row_bytes_ |
| PixelFormat pixel_format_ |
| } |
| |
| class Framebuffer { |
| is_valid() : bool const |
| data() : void* const |
| pixel_format() : PixelFormat const |
| size() : Size~uint16_t~ const |
| row_bytes() uint16_t const |
| |
| void* pixel_data_ |
| Size~uint16_t~ size_ |
| PixelFormat pixel_format_ |
| uint16_t row_bytes_ |
| } |
| |
| class DisplayDriver { |
| <<DisplayDriver>> |
| Init() : Status |
| WriteFramebuffer(Framebuffer fb, WriteCallback cb): void |
| dimensions() : Size~uint16_t~ |
| |
| PixelPusher& pixel_pusher_ |
| } |
| |
| class Display { |
| DisplayDriver& display_driver_ |
| const Size~uint16_t~ size_ |
| FramebufferPool& framebuffer_pool_ |
| |
| GetFramebuffer() : Framebuffer |
| } |
| |
| class PixelPusher { |
| Init() : Status |
| WriteFramebuffer(Framebuffer fb, WriteCallback cb) : void |
| } |
| |
| <<interface>> DisplayDriver |
| FramebufferPool --> "FRAMEBUFFER_COUNT" Framebuffer : buffer_addresses_ |
| |
| Display --> "1" DisplayDriver : display_driver_ |
| Display --> "1" FramebufferPool : framebuffer_pool_ |
| DisplayDriver --> "1" PixelPusher : pixel_pusher_ |
| |
| --------------------- |
| Problem investigation |
| --------------------- |
| With no direct display support in Pigweed and no example programs implementing |
| a solution Pigweed developers are essentially on their own. Depending on their |
| hardware this means starting with a GitHub project with a sample application |
| from MCUXpresso or STMCube. Each of these use a specific HAL and may be |
| coupled to other frameworks, such as FreeRTOS. This places the burden of |
| substituting the HAL calls with the Pigweed API, making the sample program |
| with the application screen choice, etc. |
| |
| This chore is time consuming and often requires that the application developer |
| acquire some level of driver expertise. Having direct display support in |
| Pigweed will allow the developer to more quickly add display support. |
| |
| The primary use-case being targeted is an application with a single display |
| using multiple framebuffers with display update notifications delivered during |
| an interrupt. The initial implementation is designed to support multiple |
| heterogenous displays, but that will not be the focus of development or testing |
| for the first release. |
| |
| Touch sensors, or other input devices, are not part of this effort. Display |
| and touch input often accompany each other, but to simplify this already large |
| display effort, touch will be added in a separate activity. |
| |
| There are many other embedded libraries for embedded drawing. Popular libraries |
| are LVGL, emWin, GUIslice, HAGL, ยตGFX, and VGLite (to just name a few). These |
| existing solutions generally offer one or more of: display drivers, drawing |
| library, widget library. The display drivers usually rely on an abstraction |
| layer, which they often refer to as a HAL, to interface with the underlying |
| hardware API. This HAL may rely on macros, or sometimes a structure with |
| function pointers for specific operations. |
| |
| The approach in this SEED was selected because it offers a low level API focused |
| on display update performance. It offers no drawing or GUI library, but should |
| be easily interfaced with those libraries. |
| |
| --------------- |
| Detailed design |
| --------------- |
| |
| This proposal suggests no changes to existing APIs. All changes introduce new |
| modules that leverage the existing API. It supports static allocation of the |
| pixel buffers and all display framework objects. Additionally pixel buffers |
| may be hard-coded addresses or dynamically allocated from SRAM. |
| |
| The ``Framebuffer`` class is intended to simplify code that interacts with the |
| pixel buffer. It includes the pixel buffer format, dimensions, and the buffer |
| address. The framebuffer is 16 bytes in size (14 when packed). Framebuffer |
| objects are created when requested and moved as a means of signifying ownership. |
| In other words, whenever code has an actual framebuffer object it is allowed |
| to both write to and read from the pixel buffer. |
| |
| The ``FramebufferPool`` is an object intended to simplify the management of a |
| collection of framebuffers. It tracks those that are available for use and |
| loans out framebuffers when requested. For single display devices this is |
| generally not a difficult task as the application would maintain an array of |
| framebuffers and a next available index. In this case framebuffers are always |
| used in order and the buffer collection is implemented as a queue. |
| |
| Because RAM is often limited, the framebuffer pool is designed to be shared |
| between multiple displays. Because display rendering and update may be at |
| different speeds framebuffers do not need to be retrieved |
| (via ``GetFramebuffer()``) and returned (via ``ReleaseFramebuffer()``) in the same |
| order. |
| |
| Whenever possible asynchronous display updates will be used. Depending on the |
| implementation this usually offloads the CPU from the pixel writing to the |
| display controller. In this case the CPU will initiate the update and using |
| some type of notification, usually an interrupt raised by a GPIO pin connected |
| to the display, will be notified of the completion of the display update. |
| Because of this the framebuffer pool ``ReleaseFramebuffer()`` call is interrupt |
| safe. |
| |
| ``FramebufferPool::GetFramebuffer()`` will block indefinitely if no framebuffer |
| is available. This unburdens the application drawing loop from the task of |
| managing framebuffers or tracking screen update completion. |
| |
| Testing |
| ------- |
| All classes will be accompanied by a robust set of unit tests. These can be |
| run on the host or the device. Test applications will be able to run on a |
| workstation (i.e. not an MCU) in order to enable tests that depend on |
| hardware available in most CPUs - like an MMU. This will enable the use of |
| `AddressSanitizer <https://github.com/google/sanitizers/wiki/AddressSanitizer>`_ |
| based tests. Desktop tests will use |
| `Xvfb <https://www.x.org/releases/X11R7.6/doc/man/man1/Xvfb.1.xhtml>`_ to allow |
| them to be run in a headless continuous integration environment. |
| |
| Performance |
| ----------- |
| Display support will include performance tests. Although this proposal does not |
| include a rendering library, it will include support for specific platforms |
| that will utilize means of transferring pixel data to the display controller |
| in the background. |
| |
| ------------ |
| Alternatives |
| ------------ |
| |
| One alternative is to create the necessary port/HAL, the terminology varies by |
| library, for the popular embedded graphics libraries. This would make it easier |
| for Pigweed applications to add display support - bot only for those supported |
| libraries. This effort is intended to be more focused on performance, which is |
| not always the focus of other libraries. |
| |
| Another alternative is to do nothing - leaving the job of adding display |
| support to the developers. As a significant percentage of embedded projects |
| contain a display, it will beneficial to have built-in display support in |
| Pigweed. This will allow all user to benefit by the shared display expertise, |
| continuous integration, testing, and performance testing. |
| |
| -------------- |
| Open questions |
| -------------- |
| |
| Parameter Configuration |
| ----------------------- |
| One open question is what parameters to specify in initialization parameters |
| to a driver ``Init()`` function, which to set in build flags via ``config(...)`` |
| in GN, and which to hard-code into the driver. The most ideal, from the |
| perspective of reducing binary size, is to hard-code all values in a single |
| block of contiguous data. The decision to support multiple displays requires |
| that the display initialization parameters, at least some of them, be defined |
| at runtime and cannot be hard-coded into the driver code - that is, if the |
| goal is to allow two of the same display to be in use with different settings. |
| |
| Additionally many drivers support dozens of configuration values. The ILI9341 |
| has 82 different commands, some with complex values like gamma tables or |
| multiple values packed into a single register. |
| |
| The current approach is to strike a balance where the most commonly set |
| values, for example display width/height and pixel format, are configurable |
| via build flags, and the remainder is hard-coded in the driver. If a developer |
| wants to set a parameter that is currently hard-coded in the driver, for |
| example display refresh rate or gamma table, they would need to copy the display |
| driver from Pigweed, or create a Pigweed branch. |
| |
| ``Display::WriteFramebuffer()`` always writes the full framebuffer. It is expected |
| that partial updates will be supported. This will likely come as a separate |
| function. This is being pushed off until needed to provide as much experience |
| with the various display controller APIs as possible to increase the likelihood |
| of a well crafted API. |
| |
| Module Hierarchy |
| ---------------- |
| At present Pigweed's module structure is flat and at the project root level. |
| There are currently 134 top level ``pw_*`` directories. This proposal could |
| significantly increase this count as each new display driver will be a new |
| module. This might be a good time to consider putting modules into a hierarchy. |
| |
| Pixel Pusher |
| ------------ |
| ``PixelPusher`` was created to remove the details of writing pixels from the |
| display driver. Many displays support multiple ways to send pixel data. For |
| example the ILI9341 supports SPI and a parallel bus for pixel transport. |
| The `STM32F429I-DISC1 <https://www.st.com/en/evaluation-tools/32f429idiscovery.html>`_ |
| also has a display controller (`LTDC <https://www.st.com/resource/en/application_note/an4861-lcdtft-display-controller-ltdc-on-stm32-mcus-stmicroelectronics.pdf>`_) |
| which uses an STM proprietary API. The ``PixelPusher`` was essentially created |
| to allow that driver to use the LTDC API without the need to be coupled in any |
| way to a vendor API. |
| |
| At present some display drivers use ``pw_spi`` to send commands to the display |
| controller, and the ``PixelPusher`` for writing pixel data. It will probably |
| be cleaner to move the command writes into the ``PixelPusher`` and remove any |
| ``pw_spi`` interaction from the display drivers. At this time ``PixelPusher`` |
| should be renamed. |
| |
| Copyrighted SDKs |
| ---------------- |
| Some vendors have copyrighted SDKs which cannot be included in the Pigweed |
| source code unless the project is willing to have the source covered by more |
| than one license. Additionally some SDKs have no simple download link and the |
| vendor requires that a developer use a web application to build and download |
| an SDK with the desired components. NXP's |
| `MCUXpresso SDK Builder <https://mcuxpresso.nxp.com/en/welcome>`_ is an example |
| of this. This download process makes it difficult to provide simple instructions |
| to the developer and for creating reliable builds as it may be difficult to |
| select an older SDK for download. |