Skip to content

Add LCDDRVCE library #593

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/doxyfile
Original file line number Diff line number Diff line change
Expand Up @@ -937,7 +937,8 @@ INPUT = ../src/graphx/graphx.h \
../src/usbdrvce/usbdrvce.h \
../src/srldrvce/srldrvce.h \
../src/fatdrvce/fatdrvce.h \
../src/msddrvce/msddrvce.h
../src/msddrvce/msddrvce.h \
../src/lcddrvce/lcddrvce.h

# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses
Expand Down
1 change: 1 addition & 0 deletions docs/libraries/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Common libraries:
fontlibc
fileioc
keypadc
lcddrvce
libload

USB Libraries:
Expand Down
39 changes: 39 additions & 0 deletions docs/libraries/lcddrvce.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
.. _lcddrvce_h:

lcddrvce.h
==========

.. code-block:: c
#include <lcddrvce.h>
The :code:`lcddrvce` library is used for interacting with the Sitronix ST7789 LCD controller.

.. contents:: :local:
:depth: 3

Overview
--------

This library exposes interfaces to send any supported command to the LCD (this excludes read commands, which don't work reliably on CE hardware).

Communication with the LCD controller is done over an SPI connection; however, the SPI hardware is also used to communicate with the ARM coprocessor on Python models.
As such, the SPI hardware is not always set up properly to communicate with the LCD controller, and this library exists to provide a reliable and performant interface to the LCD across calculator models.

For additional information about the LCD controller and its commands, check out the documentation on `WikiTI <https://wikiti.brandonw.net/index.php?title=84PCE:LCD_Controller>`__.

Library Initialization
----------------------

The :code:`lcd_Init` and :code:`lcd_Cleanup` functions provide reference-counted initialization and cleanup of the SPI configuration.
That means multiple calls to :code:`lcd_Init()` are allowed, and the SPI hardware is restored to its original settings only after the same number of calls to :code:`lcd_Cleanup()`.
Since the configuration is set differently than the OS's default settings for performance reasons, it's not allowed to power off the calculator without cleaning up the library first.
This means if calling certain functions like :code:`os_GetKey` which can auto-power-down the calculator, either the LCD library should be cleaned up or auto-power-down should be disabled with :code:`os_DisableAPD()`.
When using this library as part of another library's implementation and performance is not critical, it's safest to call both :code:`lcd_Init()` and :code:`lcd_Cleanup()` each time commands need to be sent.
That way, users of the library will be able to cleanup the SPI configuration whenever they need to.

API Documentation
-----------------

.. doxygenfile:: lcddrvce.h
:project: CE C/C++ Toolchain
1 change: 1 addition & 0 deletions examples/library_examples/fontlibc/font_pack/autotest.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
},
"sequence":
[
"delay|200",
"action|launch",
"delay|200",
"hashWait|1",
Expand Down
7 changes: 7 additions & 0 deletions examples/library_examples/lcddrvce/gradient/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
obj/
bin/
src/gfx/*.c
src/gfx/*.h
src/gfx/*.8xv
.DS_Store
convimg.yaml.lst
15 changes: 15 additions & 0 deletions examples/library_examples/lcddrvce/gradient/makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# ----------------------------
# Makefile Options
# ----------------------------

NAME = DEMO
ICON = icon.png
DESCRIPTION = "CE C Toolchain Demo"
COMPRESSED = NO

CFLAGS = -Wall -Wextra -Oz
CXXFLAGS = -Wall -Wextra -Oz

# ----------------------------

include $(shell cedev-config --makefile)
66 changes: 66 additions & 0 deletions examples/library_examples/lcddrvce/gradient/src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#include <lcddrvce.h>
#include <sys/lcd.h>
#include <ti/getcsc.h>
#include <stdint.h>
#include <string.h>

/* Function Prototypes */
void DrawGradient(uint24_t gradientStep);

int main(void)
{
/* Initialize LCD driver */
lcd_Init();

/* Set uniform gamma profile */
lcd_SetUniformGamma();
/* Switch RAM access to SPI and display mode to MCU */
lcd_SetRamInterface(LCD_RAM_SPI | LCD_DM_MCU);
/* Set 18bpp pixel format */
lcd_SetPixelFormat(LCD_SPI_18BPP | LCD_RGB_DEFAULT);

/* Start RAM write command */
lcd_StartPixelWrite();

/* Generate and display gradient patterns */
DrawGradient(0x040404);
DrawGradient(0x000004);
DrawGradient(0x000400);
DrawGradient(0x040000);

/* Wait for a keypress */
while (!os_GetCSC());

/* Restore default LCD settings */
lcd_SetDefaultGamma();
lcd_SetRamInterface(LCD_RAMCTRL1_DEFAULT);
lcd_SetPixelFormat(LCD_COLMOD_DEFAULT);

/* Cleanup LCD driver */
lcd_Cleanup();

return 0;
}

void DrawGradient(uint24_t gradientStep)
{
/* Generate test pattern in line buffer */
static uint24_t lineBuffer[LCD_WIDTH];
uint24_t pixel = 0;
size_t index = 0;
while (index < LCD_WIDTH)
{
lineBuffer[index++] = pixel;
lineBuffer[index++] = pixel;
lineBuffer[index++] = pixel;
lineBuffer[index++] = pixel;
lineBuffer[index++] = pixel;
pixel += gradientStep;
}

/* Send the line buffer to 1/4 of the display */
for (uint8_t row = 0; row < LCD_HEIGHT / 4; row++)
{
lcd_SendParamsRaw(sizeof(lineBuffer), lineBuffer);
}
}
7 changes: 7 additions & 0 deletions examples/library_examples/lcddrvce/half_res/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
obj/
bin/
src/gfx/*.c
src/gfx/*.h
src/gfx/*.8xv
.DS_Store
convimg.yaml.lst
15 changes: 15 additions & 0 deletions examples/library_examples/lcddrvce/half_res/makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# ----------------------------
# Makefile Options
# ----------------------------

NAME = DEMO
ICON = icon.png
DESCRIPTION = "CE C Toolchain Demo"
COMPRESSED = NO

CFLAGS = -Wall -Wextra -Oz
CXXFLAGS = -Wall -Wextra -Oz

# ----------------------------

include $(shell cedev-config --makefile)
211 changes: 211 additions & 0 deletions examples/library_examples/lcddrvce/half_res/src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
#include <lcddrvce.h>
#include <graphx.h>
#include <sys/lcd.h>
#include <ti/getcsc.h>
#include <stdint.h>
#include <string.h>

#define HALF_LCD_WIDTH (LCD_WIDTH / 2)
#define HALF_LCD_HEIGHT (LCD_HEIGHT / 2)

/* Function Prototypes */
void PrintCentered(const char *str, unsigned int border);
void QuarterResFillScreen(uint8_t color);
void QuarterResSwapDraw(void);
void SetHalfResMode(bool enable);

int main(void)
{
/* Initialize GraphX first, because it sets LCD timing */
gfx_Begin();

/* Set half-resolution mode */
SetHalfResMode(true);

/* Set clipping to an 160x120 area that draws to every second line of the half-res buffer */
gfx_SetClipRegion(0, 0, HALF_LCD_WIDTH - 1, HALF_LCD_HEIGHT - 1);
gfx_SetDrawBuffer();

unsigned int border = 2;
int dir = 1;

/* Wait for a keypress */
while (!os_GetCSC())
{
/* Clear the screen, gfx_FillScreen covers too much area so use this instead */
QuarterResFillScreen(255);

/* Print the message on the screen */
PrintCentered("Hello, World!", border);

/* Swap buffers and fill in every second line */
QuarterResSwapDraw();

/* Change the border size for the next frame */
border += dir;
if (border <= 2 || border >= 25)
{
dir = -dir;
}
}

/* Ensure one empty frame is fully displayed to make the transition to full-res clean */
gfx_FillScreen(255);
gfx_SwapDraw();
gfx_FillScreen(255);
gfx_SwapDraw();

/* Restore normal resolution mode */
SetHalfResMode(false);

/* Wait for the second empty frame to display */
gfx_Wait();

/* Deinitialize GraphX */
gfx_End();

return 0;
}

/* Prints a quarter-res screen centered string */
void PrintCentered(const char *str, unsigned int border)
{
unsigned int width = gfx_GetStringWidth(str);
gfx_PrintStringXY(str,
(HALF_LCD_WIDTH - width) / 2,
(HALF_LCD_HEIGHT - 8) / 2);
gfx_Circle(HALF_LCD_WIDTH / 2, HALF_LCD_HEIGHT / 2, width / 2 + border);
}

/* Fills the drawable area of a quarter-res screen */
void QuarterResFillScreen(uint8_t color)
{
uint8_t oldColor = gfx_SetColor(color);
gfx_FillRectangle_NoClip(0, 0, HALF_LCD_WIDTH, HALF_LCD_HEIGHT);
gfx_SetColor(oldColor);
}

/* Copies the rendered lines to the unrendered lines before swapping the buffer */
void QuarterResSwapDraw(void)
{
gfx_CopyRectangle(gfx_buffer, gfx_buffer, 0, 0, HALF_LCD_WIDTH, 0, HALF_LCD_WIDTH, HALF_LCD_HEIGHT);
gfx_SwapDraw();
}

void SetHalfResMode(bool enable)
{
typedef struct lcd_timing
{
uint32_t : 2;
uint32_t PPL : 6;
uint32_t HSW : 8;
uint32_t HFP : 8;
uint32_t HBP : 8;

uint32_t LPP : 10;
uint32_t VSW : 6;
uint32_t VFP : 8;
uint32_t VBP : 8;

uint32_t PCD_LO : 5;
uint32_t CLKSEL : 1;
uint32_t ACB : 5;
uint32_t IVS : 1;
uint32_t IHS : 1;
uint32_t IPC : 1;
uint32_t IOE : 1;
uint32_t : 1;
uint32_t CPL : 10;
uint32_t BCD : 1;
uint32_t PCD_HI : 5;
} lcd_timing_t;

typedef struct res_settings
{
uint8_t frctrl;
uint8_t bp;
uint16_t xe;
uint8_t tfa;
uint8_t ramctrl1;
lcd_timing_t timing;
} res_settings_t;

static res_settings_t settings[2] =
{
{
/* Default ST7789 settings */
.frctrl = LCD_FRCTRL_DEFAULT,
.bp = LCD_BP_DEFAULT,
.xe = LCD_WIDTH - 1,
.tfa = 0,
.ramctrl1 = LCD_RAMCTRL1_DEFAULT
},
{
/* With the following ST7789 timing:
* Refreshes LCD in at most 16.51 ms after VSYNC, assuming worst case 9.5 MHz clock
* Waits at least 3.42 ms after VSYNC to read LCD memory, assuming worst case 10.5 MHz clock
*/
.frctrl = LCD_RTN_378 | LCD_NL_DEFAULT, /* 378 clocks per line */
.bp = 95, /* 95 lines of back porch */
.xe = HALF_LCD_WIDTH - 1,
.tfa = HALF_LCD_WIDTH,
.ramctrl1 = LCD_DM_VSYNC | LCD_RAM_DEFAULT,
/* With the following PL111 timing:
* Refreshes LCD at 60 Hz = 24 MHz / (800*250*2), a VSYNC period of 16.67 ms
* Outputs 38400 pixels to LCD memory within the first 3.40 ms after VSYNC
*/
.timing =
{
.PPL = 768 / 16 - 1, /* 768 pixels per line */
.HSW = 1 - 1,
.HFP = (800 - 768 - 1 - 1) - 1, /* 800 total clocks per line */
.HBP = 1 - 1,
.LPP = HALF_LCD_WIDTH * LCD_HEIGHT / 768, /* 50 lines */
.VSW = 1 - 1,
.VFP = 250 - (HALF_LCD_WIDTH * LCD_HEIGHT / 768) - 1, /* 250 total lines */
.VBP = 0,
.PCD_LO = (2 - 2) & 0x1F, /* clock divisor of 2 */
.CLKSEL = 0,
.ACB = 0,
.IVS = 1,
.IHS = 1,
.IPC = 1,
.IOE = 1,
.CPL = 768 - 1,
.BCD = 0,
.PCD_HI = (2 - 2) >> 5
}
}
};

const res_settings_t *p = &settings[enable];

/* Initialize LCD driver */
lcd_Init();

/* Set clocks per line */
lcd_SetNormalFrameRateControl(p->frctrl);
/* Set back porch */
lcd_SetNormalBackPorchControl(p->bp);
/* Set horizontal output window */
lcd_SetColumnAddress(0, p->xe);
/* Set fixed left scroll area */
lcd_SetScrollArea(p->tfa, LCD_WIDTH - p->tfa, 0);
/* Set starting vertical scroll address to 0 */
lcd_SetScrollAddress(0);
/* Set display mode */
lcd_SetRamInterface(p->ramctrl1);
/* Set interlace mode */
lcd_SetInterlacedMode(enable);

/* Save old display timing when enabling */
if (enable)
{
memcpy(&settings[0].timing, (const void *)&lcd_Timing0, sizeof(settings[0].timing));
}
/* Set display timing */
memcpy((void *)&lcd_Timing0, &p->timing, sizeof(p->timing));

/* Cleanup LCD driver */
lcd_Cleanup();
}
Loading
Loading