/******************************************************************************
* File : st7920_backend.c
* Target : JieLi AC6925A / AC692x SDK V2.6.3
* Display : Sitronix ST7920, 128x64, native 3-wire serial interface
*
* Protocol note:
* ST7920 serial framing packs each logical command/data byte into three
* byte-aligned groups:
* byte0 = 1 1 1 1 1 RW RS 0 (sync + control)
* byte1 = D7 D6 D5 D4 0 0 0 0 (high nibble)
* byte2 = D3 D2 D1 D0 0 0 0 0 (low nibble)
* which is exactly three standard 8-bit SPI transfers, MSB first. This
* lets a normal hardware SPI peripheral drive the panel instead of
* bit-banging SCLK/SID by hand, the way lcd_disp.c bit-bangs I2C.
*
* GDRAM addressing note:
* 128x64 graphic mode on ST7920 is NOT a flat linear GDRAM. Vertical
* address only spans 0..31; the bottom half of the screen (rows 32..63)
* is reached by adding 8 to the horizontal WORD index instead. That is:
*
* vertical = row & 0x1F
* horizontal = word_index + ((row >= 32) ? 8 : 0)
*
* where word_index is 0..7 and each word covers 16 pixel columns. The
* two halves require separate Y/X address pairs; GDRAM horizontal auto
* increment wraps from 0x0F to 0x00 and does not move to the other half.
******************************************************************************/
#include "st7920_backend.h"
#include "common/common.h"
#include "hw_cpu.h"
#include "cpu/irq_api.h"
#include "ui/ui_api.h"
#include "uicon/ui_con.h"
#include "uicon/menu.h"
#include "uicon_api.h"
#include "font_api.h"
#include <string.h>
#if LCD_128X64_EN
/******************************************************************************
* COMMAND CONSTANTS
******************************************************************************/
/* Basic instruction set (RE=0) */
#define ST7920_CMD_DISPLAY_CLEAR 0x01u
#define ST7920_CMD_ENTRY_MODE 0x06u /* I/D=1, S=0 */
#define ST7920_CMD_DISPLAY_ON 0x0Cu /* D=1 C=0 B=0 */
#define ST7920_CMD_DISPLAY_OFF 0x08u
#define ST7920_CMD_FUNCTION_SET_BASIC 0x30u /* DL=1 RE=0 */
/* Extended instruction set (RE=1) */
#define ST7920_CMD_FUNCTION_SET_EXTENDED 0x34u /* DL=1 RE=1 G=0 */
#define ST7920_CMD_FUNCTION_SET_GRAPHIC_ON 0x36u /* DL=1 RE=1 G=1 */
#define ST7920_CMD_SET_GDRAM_ADDR 0x80u /* | vertical or horiz. */
#define ST7920_SERIAL_SYNC_WRITE 0xF8u /* sync + RW=0 + RS=0 */
#define ST7920_RS_BIT BIT(1)
#define ST7920_WORDS_PER_ROW 8u /* 128px / 16px-per-word */
#define ST7920_PAGE_BYTES ST7920_DISP_WIDTH
#define ST7920_ALL_PAGE_MASK ((u8)((1u << ST7920_DISP_PAGES) - 1u))
/******************************************************************************
* GPIO PORT SELECTION
******************************************************************************/
#if ST7920_CS_PORT_SELECT == ST7920_GPIO_PORT_A
#define ST7920_CS_PORT JL_PORTA
#elif ST7920_CS_PORT_SELECT == ST7920_GPIO_PORT_B
#define ST7920_CS_PORT JL_PORTB
#elif ST7920_CS_PORT_SELECT == ST7920_GPIO_PORT_C
#define ST7920_CS_PORT JL_PORTC
#elif ST7920_CS_PORT_SELECT == ST7920_GPIO_PORT_D
#define ST7920_CS_PORT JL_PORTD
#else
#error "Invalid ST7920_CS_PORT_SELECT value."
#endif
#if ST7920_RESET_PORT_SELECT == ST7920_GPIO_PORT_A
#define ST7920_RESET_PORT JL_PORTA
#elif ST7920_RESET_PORT_SELECT == ST7920_GPIO_PORT_B
#define ST7920_RESET_PORT JL_PORTB
#elif ST7920_RESET_PORT_SELECT == ST7920_GPIO_PORT_C
#define ST7920_RESET_PORT JL_PORTC
#elif ST7920_RESET_PORT_SELECT == ST7920_GPIO_PORT_D
#define ST7920_RESET_PORT JL_PORTD
#else
#error "Invalid ST7920_RESET_PORT_SELECT value."
#endif
#define ST7920_CS_MASK BIT(ST7920_CS_PIN_INDEX)
#define ST7920_RESET_MASK BIT(ST7920_RESET_PIN_INDEX)
/* SPI1 register bits/values. Although the ST7920 bit-bang example starts
* with SCLK low, this AC6925 SPI1B route has been electrically proven on the
* connected 5V module only with CKID set. Keep that known-good clock phase
* for the first hardware test; GDRAM addressing is independent from it. */
#define ST7920_SPI_CON_TRANSFER_DONE BIT(15)
#define ST7920_SPI_CON_CLEAR_DONE BIT(14)
#define ST7920_SPI_CON_DIRECTION_READ BIT(12)
#define ST7920_SPI_CON_THREE_WIRE BIT(3)
#define ST7920_SPI_CON_BASE 0x4021u
#define ST7920_SPI_CON_ENABLED (ST7920_SPI_CON_BASE | BIT(6))
#define ST7920_IOMAP_SPI1_GROUP_SELECT BIT(4)
#define ST7920_IOMAP_SPI1_EXTRA_MASK (BIT(18) | BIT(19))
#define ST7920_SPI_BLOCKING_TIMEOUT_LOOPS 200000UL
/******************************************************************************
* STATIC STORAGE
******************************************************************************/
u8 disp_buf[ST7920_DISP_PAGES * ST7920_DISP_WIDTH];
static volatile u8 st7920_spi_initialized;
static volatile u8 st7920_driver_available;
/******************************************************************************
* JIELI UI DESCRIPTORS
******************************************************************************/
const UI_LCD_IO ui_lcd_io = {
.lcd_clear_area_callback = lcd_clear_area,
.lcd_clear_area_with_draw_callback = lcd_clear_area_with_draw,
.lcd_clear_area_rect_callback = lcd_clear_area_rect,
.lcd_TurnPixelReverse_Page_callback = lcd_TurnPixelReverse_Page,
.lcd_TurnPixelReverse_Rect_callback = lcd_TurnPixelReverse_Rect,
};
const LCD_INFO lcd_info = {
.ui_lcd_callback = (UI_LCD_IO *)&ui_lcd_io,
.lcd_width = ST7920_DISP_WIDTH,
.lcd_height = ST7920_DISP_HEIGHT,
.lcd_buff = disp_buf,
};
/******************************************************************************
* GPIO / DELAY HELPERS
******************************************************************************/
#define ST7920_CONFIGURE_OUTPUT(port, mask) \
do { \
(port)->DIR &= ~(mask); \
(port)->DIE &= ~(mask); \
(port)->PU &= ~(mask); \
(port)->PD &= ~(mask); \
} while (0)
static void st7920_gpio_cs(u8 enabled)
{
/* Serial-mode RS pin doubles as CS: 1 = enabled, 0 = disabled/reset. */
if (enabled) {
ST7920_CS_PORT->OUT |= ST7920_CS_MASK;
} else {
ST7920_CS_PORT->OUT &= ~ST7920_CS_MASK;
}
}
static void st7920_gpio_reset(u8 high)
{
if (high) {
ST7920_RESET_PORT->OUT |= ST7920_RESET_MASK;
} else {
ST7920_RESET_PORT->OUT &= ~ST7920_RESET_MASK;
}
}
static void st7920_gpio_initialize(void)
{
ST7920_CONFIGURE_OUTPUT(ST7920_CS_PORT, ST7920_CS_MASK);
ST7920_CONFIGURE_OUTPUT(ST7920_RESET_PORT, ST7920_RESET_MASK);
#if ST7920_USE_SPI1B
ST7920_CONFIGURE_OUTPUT(JL_PORTC, BIT(4));
ST7920_CONFIGURE_OUTPUT(JL_PORTC, BIT(5));
/* CKID mode: clock idles high. SID remains fixed while CS is low. */
JL_PORTC->OUT |= BIT(4);
JL_PORTC->OUT &= ~BIT(5);
#else
ST7920_CONFIGURE_OUTPUT(JL_PORTB, BIT(4));
ST7920_CONFIGURE_OUTPUT(JL_PORTB, BIT(5));
JL_PORTB->OUT |= BIT(4);
JL_PORTB->OUT &= ~BIT(5);
#endif
st7920_gpio_cs(0u);
st7920_gpio_reset(1u);
}
extern void clear_wdt(void);
/* Microsecond-granularity busy-wait, calibrated the same way as the
* millisecond delay in st7789_backend.c. Re-check against actual CPU
* clock configuration before trusting the timing at high SPI baud rates. */
static void st7920_delay_us(u16 microseconds)
{
volatile u32 loops;
loops = (u32)microseconds * ST7920_DELAY_LOOPS_PER_US;
while (loops--) {
/* busy wait */
}
}
static void st7920_delay_ms(u16 milliseconds)
{
while (milliseconds--) {
st7920_delay_us(1000u);
clear_wdt();
}
}
/******************************************************************************
* SPI1 TRANSPORT
******************************************************************************/
static void st7920_spi_initialize_once(void)
{
if (st7920_spi_initialized) {
return;
}
JL_SPI1->CON = 0;
#if ST7920_USE_SPI1B
#if ST7920_DISABLE_IIC_FOR_SPI1B
JL_IIC->CON0 &= ~BIT(0);
#endif
JL_IOMAP->CON1 |= ST7920_IOMAP_SPI1_GROUP_SELECT;
JL_IOMAP->CON1 &= ~ST7920_IOMAP_SPI1_EXTRA_MASK;
#else
JL_IOMAP->CON1 &= ~ST7920_IOMAP_SPI1_GROUP_SELECT;
#endif
st7920_gpio_initialize();
JL_SPI1->BAUD = ST7920_SPI_BAUD_DIV;
JL_SPI1->CON = ST7920_SPI_CON_ENABLED;
JL_SPI1->CON &= ~ST7920_SPI_CON_DIRECTION_READ;
JL_SPI1->CON &= ~ST7920_SPI_CON_THREE_WIRE;
JL_SPI1->CON |= ST7920_SPI_CON_CLEAR_DONE;
st7920_spi_initialized = 1u;
}
static u8 st7920_spi_transfer_done(void)
{
return (JL_SPI1->CON & ST7920_SPI_CON_TRANSFER_DONE) ? 1u : 0u;
}
static void st7920_spi_acknowledge_done(void)
{
JL_SPI1->CON |= ST7920_SPI_CON_CLEAR_DONE;
}
static u8 st7920_spi_write_byte(u8 value)
{
u32 timeout;
JL_SPI1->CON &= ~ST7920_SPI_CON_DIRECTION_READ;
JL_SPI1->BUF = value;
timeout = ST7920_SPI_BLOCKING_TIMEOUT_LOOPS;
while (!st7920_spi_transfer_done()) {
if (--timeout == 0u) {
return 0u;
}
}
st7920_spi_acknowledge_done();
return 1u;
}
/******************************************************************************
* ST7920 PROTOCOL LAYER
******************************************************************************/
/*
* Send one logical byte (command or data) as the three-byte serial frame.
* Brackets the transaction with CS the same way the datasheet's own 8051
* bit-bang example does: assert, clock out the frame, deassert.
*/
static u8 st7920_send(u8 rs, u8 value)
{
u8 sync_byte;
u8 ok;
sync_byte = ST7920_SERIAL_SYNC_WRITE | (rs ? ST7920_RS_BIT : 0u);
st7920_spi_initialize_once();
st7920_gpio_cs(1u);
ok = st7920_spi_write_byte(sync_byte);
ok = ok && st7920_spi_write_byte((u8)(value & 0xF0u));
ok = ok && st7920_spi_write_byte((u8)(value << 4));
st7920_gpio_cs(0u);
return ok;
}
static u8 st7920_write_cmd(u8 cmd)
{
u8 ok = st7920_send(0u, cmd);
st7920_delay_us(ST7920_INSTR_DELAY_US);
return ok;
}
/*
* GDRAM data writes are the vast majority of transactions during a redraw
* (32 per vertical address vs. 2 address-set commands), so they're the
* highest-leverage place to shrink delay. ST7920_INSTR_DELAY_US (72us) is
* the datasheet's WORST-CASE figure for all instructions including RAM
* writes, chosen conservatively because serial mode can't poll the busy
* flag. ST7920_DATA_DELAY_US is a separate, shorter knob you can dial down
* experimentally -- start around 5-10us and back off (increase) only if
* you see corrupted/shifted pixels, which indicates the controller hadn't
* finished the previous write yet.
*/
static u8 st7920_write_data(u8 data)
{
u8 ok = st7920_send(1u, data);
st7920_delay_us(ST7920_DATA_DELAY_US);
return ok;
}
static u8 st7920_set_gdram_address(u8 row, u8 word_index)
{
u8 vertical;
u8 horizontal;
vertical = row & 0x1Fu;
horizontal = word_index + ((row >= 32u) ? 8u : 0u);
if (!st7920_write_cmd((u8)(ST7920_CMD_SET_GDRAM_ADDR | vertical))) {
return 0u;
}
return st7920_write_cmd((u8)(ST7920_CMD_SET_GDRAM_ADDR | horizontal));
}
static void st7920_hardware_reset(void)
{
st7920_gpio_reset(1u);
st7920_delay_ms(20u);
st7920_gpio_reset(0u);
st7920_delay_ms(20u);
st7920_gpio_reset(1u);
st7920_delay_ms(50u); /* datasheet: wait >40ms after power/reset */
}
static u8 st7920_controller_initialize(void)
{
st7920_spi_initialize_once();
st7920_hardware_reset();
if (!st7920_write_cmd(ST7920_CMD_FUNCTION_SET_BASIC)) {
return 0u;
}
st7920_delay_us(100u);
if (!st7920_write_cmd(ST7920_CMD_FUNCTION_SET_BASIC)) {
return 0u;
}
if (!st7920_write_cmd(ST7920_CMD_DISPLAY_ON)) {
return 0u;
}
if (!st7920_write_cmd(ST7920_CMD_DISPLAY_CLEAR)) {
return 0u;
}
st7920_delay_ms(ST7920_CLEAR_DELAY_MS);
if (!st7920_write_cmd(ST7920_CMD_ENTRY_MODE)) {
return 0u;
}
/* Switch to extended instruction set, then enable graphic mode in a
* second instruction -- DL/RE/G cannot all change in one transaction. */
if (!st7920_write_cmd(ST7920_CMD_FUNCTION_SET_EXTENDED)) {
return 0u;
}
if (!st7920_write_cmd(ST7920_CMD_FUNCTION_SET_GRAPHIC_ON)) {
return 0u;
}
return 1u;
}
/******************************************************************************
* FRAMEBUFFER -> GDRAM CONVERSION
******************************************************************************/
/*
* Push one 16-pixel-wide GDRAM word built from the current disp_buf page
* contents for physical row `row` and word index `word_index` (columns
* word_index*16 .. word_index*16+15).
*
* disp_buf is page/column/bit oriented exactly like lcd_disp.c's
* disp_buff[page][col]: bit b of disp_buf[page][col] is pixel
* row = page*8+b, column = col.
*/
static u16 st7920_build_word(u8 row, u8 word_index)
{
u8 page;
u8 bit;
u16 word;
u8 col;
u8 i;
page = row >> 3;
bit = (u8)BIT(row & 0x07u);
word = 0u;
for (i = 0; i < 16u; i++) {
col = (u8)(word_index * 16u + i);
word <<= 1;
if (disp_buf[(u16)page * ST7920_PAGE_BYTES + col] & bit) {
word |= 1u;
}
}
return word;
}
/******************************************************************************
* ASYNC STREAM SENDER (TIMER3 UI-DRIVEN)
*
* The old code blocked synchronously for the whole frame: build one logical
* byte, blast it over SPI, busy-wait ~72us for the controller to digest it,
* repeat ~1000 times in an unbroken run. That's a single multi-millisecond
* stretch of the CPU doing nothing useful, which is exactly what starves a
* timing-sensitive audio ISR (Timer0) of prompt service.
*
* The fix mirrors i2c_oled_hw.c's approach: do all the CPU-only work (the
* page-format -> GDRAM-word bit transpose) once, up front, into a flat
* buffer of already-framed SPI bytes. The existing Timer3 UI loop drains
* bounded groups from that buffer, leaving the audio/BT tasks serviceable.
******************************************************************************/
/* Per Y: 2 address commands + 16 top-half data bytes + 2 address commands
* + 16 bottom-half data bytes. The old formula omitted the lower half and
* overflowed st7920_stream while preparing a full frame. */
#define ST7920_STREAM_LOGICAL_BYTES (32u * (4u + 4u * ST7920_WORDS_PER_ROW))
#define ST7920_STREAM_RAW_BYTES (ST7920_STREAM_LOGICAL_BYTES * 3u)
static u8 st7920_stream[ST7920_STREAM_RAW_BYTES];
static volatile u16 st7920_stream_len;
static volatile u8 st7920_stream_active;
static volatile u8 st7920_redraw_pending;
static u16 st7920_stream_put(u16 pos, u8 rs, u8 value)
{
st7920_stream[pos++] = ST7920_SERIAL_SYNC_WRITE | (rs ? ST7920_RS_BIT : 0u);
st7920_stream[pos++] = (u8)(value & 0xF0u);
st7920_stream[pos++] = (u8)(value << 4);
return pos;
}
/*
* Build the entire frame's SPI byte stream from the current disp_buf
* contents. Pure CPU math, no I/O and no waiting -- safe to run in normal
* (non-ISR) context from draw_lcd_buf(). Uses the same top/bottom-half
* pairing as before: one vertical address's 16-word auto-increment span
* covers a top-half row followed immediately by its bottom-half
* counterpart, so only 2 address commands are needed per 32 data words.
*/
static void st7920_prepare_stream(void)
{
u16 pos;
u8 vertical;
u8 word_index;
u16 word;
pos = 0u;
for (vertical = 0; vertical < 32u; vertical++) {
/* Top physical half: Y=vertical, X=0..7. */
pos = st7920_stream_put(pos, 0u, (u8)(ST7920_CMD_SET_GDRAM_ADDR | vertical));
pos = st7920_stream_put(pos, 0u, (u8)(ST7920_CMD_SET_GDRAM_ADDR | 0u));
for (word_index = 0; word_index < ST7920_WORDS_PER_ROW; word_index++) {
word = st7920_build_word(vertical, word_index);
pos = st7920_stream_put(pos, 1u, (u8)(word >> 8));
pos = st7920_stream_put(pos, 1u, (u8)word);
}
/* Bottom physical half: Y is the same but X begins at 8. It must
* be explicitly addressed; automatic increment only wraps X. */
pos = st7920_stream_put(pos, 0u, (u8)(ST7920_CMD_SET_GDRAM_ADDR | vertical));
pos = st7920_stream_put(pos, 0u, (u8)(ST7920_CMD_SET_GDRAM_ADDR | 8u));
for (word_index = 0; word_index < ST7920_WORDS_PER_ROW; word_index++) {
word = st7920_build_word((u8)(vertical + 32u), word_index);
pos = st7920_stream_put(pos, 1u, (u8)(word >> 8));
pos = st7920_stream_put(pos, 1u, (u8)word);
}
}
st7920_stream_len = pos;
}
static void st7920_start_stream_send(void)
{
st7920_prepare_stream();
st7920_redraw_pending = 0u;
/* CS may remain high for consecutive serial packets. With PSB low this
* is the datasheet's recommended minimal-system arrangement and lets one
* non-blocking SPI DMA operation carry the whole prepared frame. */
JL_SPI1->CON &= ~ST7920_SPI_CON_DIRECTION_READ;
JL_SPI1->CON |= ST7920_SPI_CON_CLEAR_DONE;
st7920_gpio_cs(1u);
JL_SPI1->ADR = (u32)st7920_stream;
JL_SPI1->CNT = st7920_stream_len;
st7920_stream_active = 1u;
}
/*
* Runs from the SDK's existing 2ms Timer3 UI loop. It never writes SPI data
* and never waits: SPI1 DMA owns the physical transfer. Timer1 stays free
* for key_drv_ir.c and audio is no longer held behind per-packet delays.
*/
static void st7920_stream_pump(void)
{
if (!st7920_stream_active) {
return;
}
if (!(JL_SPI1->CON & ST7920_SPI_CON_TRANSFER_DONE)) {
return;
}
JL_SPI1->CON |= ST7920_SPI_CON_CLEAR_DONE;
st7920_gpio_cs(0u);
st7920_stream_active = 0u;
if (st7920_redraw_pending) {
st7920_start_stream_send();
}
}
LOOP_UI_REGISTER(st7920_stream_pump_loop) = {
.time = 1,
.fun = st7920_stream_pump,
};
/******************************************************************************
* PUBLIC HARDWARE API
******************************************************************************/
void lcd_hardware_init(void)
{
st7920_spi_initialize_once();
if (!st7920_controller_initialize()) {
st7920_driver_available = 0u;
return;
}
st7920_driver_available = 1u;
}
void lcd_reset(void)
{
st7920_driver_available = 0u;
lcd_hardware_init();
if (st7920_driver_available) {
draw_lcd_buf();
}
}
void lcd_display_on(void)
{
if (!st7920_driver_available) {
return;
}
/* Display ON/OFF is a basic-set instruction; drop to RE=0, issue it,
* then return to the extended/graphic-on state used for GDRAM access. */
st7920_write_cmd(ST7920_CMD_FUNCTION_SET_BASIC);
st7920_write_cmd(ST7920_CMD_DISPLAY_ON);
st7920_write_cmd(ST7920_CMD_FUNCTION_SET_EXTENDED);
st7920_write_cmd(ST7920_CMD_FUNCTION_SET_GRAPHIC_ON);
}
void lcd_display_off(void)
{
if (!st7920_driver_available) {
return;
}
st7920_write_cmd(ST7920_CMD_FUNCTION_SET_BASIC);
st7920_write_cmd(ST7920_CMD_DISPLAY_OFF);
st7920_write_cmd(ST7920_CMD_FUNCTION_SET_EXTENDED);
st7920_write_cmd(ST7920_CMD_FUNCTION_SET_GRAPHIC_ON);
}
/******************************************************************************
* PUBLIC FRAMEBUFFER API
******************************************************************************/
void lcd_clear(void)
{
memset(disp_buf, 0, sizeof(disp_buf));
}
void draw_lcd_buf(void)
{
if (!st7920_driver_available) {
return;
}
if (st7920_stream_active) {
/* A send is already in flight -- don't rebuild the stream out
* from under the ISR mid-transfer. Flag it so the ISR restarts
* a fresh send (picking up whatever disp_buf looks like then)
* as soon as the current one finishes. */
st7920_redraw_pending = 1u;
return;
}
st7920_start_stream_send();
}
void lcd_clear_area(u8 start_page, u8 end_page)
{
u8 page;
if (start_page >= ST7920_DISP_PAGES) {
return;
}
if (end_page > ST7920_DISP_PAGES) {
end_page = ST7920_DISP_PAGES;
}
if (start_page >= end_page) {
return;
}
for (page = start_page; page < end_page; page++) {
memset(&disp_buf[(u16)page * ST7920_PAGE_BYTES], 0, ST7920_PAGE_BYTES);
}
}
void lcd_clear_area_with_draw(u8 start_page, u8 end_page)
{
lcd_clear_area(start_page, end_page);
draw_lcd_buf();
}
void lcd_clear_area_rect(u8 start_page, u8 end_page, u8 x0, u8 x1)
{
u8 page;
u16 length;
if (start_page >= ST7920_DISP_PAGES || x0 >= ST7920_DISP_WIDTH) {
return;
}
if (end_page > ST7920_DISP_PAGES) {
end_page = ST7920_DISP_PAGES;
}
if (x1 > ST7920_DISP_WIDTH) {
x1 = ST7920_DISP_WIDTH;
}
if (start_page >= end_page || x0 >= x1) {
return;
}
length = (u16)x1 - x0;
for (page = start_page; page < end_page; page++) {
memset(&disp_buf[(u16)page * ST7920_PAGE_BYTES + x0], 0, length);
}
}
u32 lcd_TurnPixelReverse_Page(u8 startpage, u8 pagelen)
{
u8 page;
u16 column;
u8 end_page;
if (startpage >= ST7920_DISP_PAGES || pagelen == 0u) {
return FALSE;
}
if ((u16)startpage + pagelen > ST7920_DISP_PAGES) {
end_page = ST7920_DISP_PAGES;
} else {
end_page = startpage + pagelen;
}
for (page = startpage; page < end_page; page++) {
for (column = 0; column < ST7920_PAGE_BYTES; column++) {
disp_buf[(u16)page * ST7920_PAGE_BYTES + column] ^= 0xFFu;
}
}
return TRUE;
}
u32 lcd_TurnPixelReverse_Rect(u8 left, u8 top, u8 right, u8 bottom)
{
u8 page;
u16 column;
if (left >= ST7920_DISP_WIDTH || top >= ST7920_DISP_PAGES) {
return FALSE;
}
if (right > ST7920_DISP_WIDTH) {
right = ST7920_DISP_WIDTH;
}
if (bottom >= ST7920_DISP_PAGES) {
bottom = ST7920_DISP_PAGES - 1u;
}
if (left >= right || top > bottom) {
return FALSE;
}
for (page = top; page <= bottom; page++) {
for (column = left; column < right; column++) {
disp_buf[(u16)page * ST7920_PAGE_BYTES + column] ^= 0xFFu;
}
}
return TRUE;
}
/******************************************************************************
* INIT ENTRY POINT
******************************************************************************/
void lcd_init(void)
{
memset(disp_buf, 0, sizeof(disp_buf));
if (!uicon_init_api()) {
return;
}
if (!font_init_api(Chinese_Simplified)) {
return;
}
lcd_hardware_init();
lcd_clear();
draw_lcd_buf();
}
#endif /* LCD_128X64_EN */