From 52b746ac4b7368db8b8e7b6b689c82a062a1eba8 Mon Sep 17 00:00:00 2001 From: Shuanglei Tao Date: Sat, 8 Feb 2025 17:58:09 +0800 Subject: [PATCH] add calendar support --- .gitignore | 1 + EPD/DEV_Config.h | 57 -- EPD/EPD_4in2.c | 144 ++--- EPD/EPD_4in2.h | 50 -- EPD/EPD_4in2_V2.c | 170 +++--- EPD/EPD_4in2_V2.h | 53 -- EPD/EPD_4in2b_V2.c | 136 ++--- EPD/EPD_4in2b_V2.h | 49 -- EPD/EPD_Test.c | 90 +-- EPD/EPD_ble.c | 74 +-- EPD/EPD_ble.h | 34 +- EPD/{DEV_Config.c => EPD_driver.c} | 89 ++- EPD/EPD_driver.h | 95 +++ GUI/Adafruit_GFX.c | 943 +++++++++++++++++++++++++++++ GUI/Adafruit_GFX.h | 91 +++ GUI/Calendar.c | 106 ++++ GUI/Calendar.h | 8 + GUI/Lunar.c | 550 +++++++++++++++++ GUI/Lunar.h | 53 ++ GUI/fonts.c | 135 +++++ GUI/fonts.h | 37 ++ GUI/u8g2_font.c | 573 ++++++++++++++++++ GUI/u8g2_font.h | 157 +++++ Keil/EPD.uvprojx | 187 +++--- config/nrf_drv_config.h | 6 +- config/pstorage_platform.h | 78 --- html/index.html | 16 +- html/js/main.js | 49 +- main.c | 165 ++++- 29 files changed, 3417 insertions(+), 779 deletions(-) delete mode 100644 EPD/DEV_Config.h delete mode 100644 EPD/EPD_4in2.h delete mode 100644 EPD/EPD_4in2_V2.h delete mode 100644 EPD/EPD_4in2b_V2.h rename EPD/{DEV_Config.c => EPD_driver.c} (63%) create mode 100644 EPD/EPD_driver.h create mode 100644 GUI/Adafruit_GFX.c create mode 100644 GUI/Adafruit_GFX.h create mode 100644 GUI/Calendar.c create mode 100644 GUI/Calendar.h create mode 100644 GUI/Lunar.c create mode 100644 GUI/Lunar.h create mode 100644 GUI/fonts.c create mode 100644 GUI/fonts.h create mode 100644 GUI/u8g2_font.c create mode 100644 GUI/u8g2_font.h delete mode 100644 config/pstorage_platform.h diff --git a/.gitignore b/.gitignore index 83229e6..6e830f1 100644 --- a/.gitignore +++ b/.gitignore @@ -74,6 +74,7 @@ *.l1p *.l2p *.iex +*.Bak # To explicitly override the above, define any exceptions here; e.g.: # !my_customized_scatter_file.sct diff --git a/EPD/DEV_Config.h b/EPD/DEV_Config.h deleted file mode 100644 index 1c0ed50..0000000 --- a/EPD/DEV_Config.h +++ /dev/null @@ -1,57 +0,0 @@ -/***************************************************************************** -* | File : DEV_Config.h -* | Author : Waveshare team -* | Function : debug with prntf -* | Info : -* Image scanning -* Please use progressive scanning to generate images or fonts -*---------------- -* | This version: V1.0 -* | Date : 2018-01-11 -* | Info : Basic version -* -******************************************************************************/ -#ifndef _DEV_CONFIG_H_ -#define _DEV_CONFIG_H_ - -#include "nrf_delay.h" -#include "nrf_gpio.h" -#include -#include - -/** - * data -**/ -#define UBYTE uint8_t -#define UWORD uint16_t -#define UDOUBLE uint32_t - -extern uint32_t EPD_MOSI_PIN; -extern uint32_t EPD_SCLK_PIN; -extern uint32_t EPD_CS_PIN; -extern uint32_t EPD_DC_PIN; -extern uint32_t EPD_RST_PIN; -extern uint32_t EPD_BUSY_PIN; -extern uint32_t EPD_BS_PIN; - -/** - * GPIO read and write -**/ -#define DEV_Digital_Write(_pin, _value) nrf_gpio_pin_write(_pin, _value) -#define DEV_Digital_Read(_pin) nrf_gpio_pin_read(_pin) - - -/** - * delay x ms -**/ -#define DEV_Delay_ms(__xms) nrf_delay_ms(__xms); -#define DEV_Delay_us(__xus) nrf_delay_us(__xus); - -UBYTE DEV_Module_Init(void); -void DEV_Module_Exit(void); - -void DEV_SPI_WriteByte(UBYTE value); -void DEV_SPI_WriteBytes(UBYTE *value, UBYTE len); -UBYTE DEV_SPI_ReadByte(void); - -#endif diff --git a/EPD/EPD_4in2.c b/EPD/EPD_4in2.c index d470421..3d1c4c7 100644 --- a/EPD/EPD_4in2.c +++ b/EPD/EPD_4in2.c @@ -6,7 +6,6 @@ *---------------- * | This version: V3.0 * | Date : 2019-06-13 -* | Info : * ----------------------------------------------------------------------------- # # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -28,7 +27,11 @@ # THE SOFTWARE. # ******************************************************************************/ -#include "EPD_4in2.h" +#include "EPD_driver.h" + +// Display resolution +#define EPD_4IN2_WIDTH 400 +#define EPD_4IN2_HEIGHT 300 /****************************************************************************** function : Software reset @@ -47,40 +50,6 @@ static void EPD_4IN2_Reset(void) } } -/****************************************************************************** -function : send command -parameter: - Reg : Command register -******************************************************************************/ -void EPD_4IN2_SendCommand(UBYTE Reg) -{ - DEV_Digital_Write(EPD_DC_PIN, 0); - DEV_Digital_Write(EPD_CS_PIN, 0); - DEV_SPI_WriteByte(Reg); - DEV_Digital_Write(EPD_CS_PIN, 1); -} - -/****************************************************************************** -function : send data -parameter: - Data : Write data -******************************************************************************/ -void EPD_4IN2_SendData(UBYTE Data) -{ - DEV_Digital_Write(EPD_DC_PIN, 1); - DEV_Digital_Write(EPD_CS_PIN, 0); - DEV_SPI_WriteByte(Data); - DEV_Digital_Write(EPD_CS_PIN, 1); -} - -void EPD_4IN2_SendData2(UBYTE *Data, UBYTE Len) -{ - DEV_Digital_Write(EPD_DC_PIN, 1); - DEV_Digital_Write(EPD_CS_PIN, 0); - DEV_SPI_WriteBytes(Data, Len); - DEV_Digital_Write(EPD_CS_PIN, 1); -} - /****************************************************************************** function : Wait until the busy_pin goes LOW parameter: @@ -98,7 +67,7 @@ parameter: ******************************************************************************/ void EPD_4IN2_TurnOnDisplay(void) { - EPD_4IN2_SendCommand(0x12); + EPD_WriteCommand(0x12); DEV_Delay_ms(100); EPD_4IN2_ReadBusy(); } @@ -111,20 +80,20 @@ void EPD_4IN2_Init(void) { EPD_4IN2_Reset(); - EPD_4IN2_SendCommand(0x04); // POWER ON + EPD_WriteCommand(0x04); // POWER ON EPD_4IN2_ReadBusy(); - EPD_4IN2_SendCommand(0x00); // panel setting - EPD_4IN2_SendData(0x1f); // 400x300 B/W mode, LUT from OTP + EPD_WriteCommand(0x00); // panel setting + EPD_WriteByte(0x1f); // 400x300 B/W mode, LUT from OTP - EPD_4IN2_SendCommand(0x61); // resolution setting - EPD_4IN2_SendData (EPD_4IN2_WIDTH / 256); - EPD_4IN2_SendData (EPD_4IN2_WIDTH % 256); - EPD_4IN2_SendData (EPD_4IN2_HEIGHT / 256); - EPD_4IN2_SendData (EPD_4IN2_HEIGHT % 256); + EPD_WriteCommand(0x61); // resolution setting + EPD_WriteByte (EPD_4IN2_WIDTH / 256); + EPD_WriteByte (EPD_4IN2_WIDTH % 256); + EPD_WriteByte (EPD_4IN2_HEIGHT / 256); + EPD_WriteByte (EPD_4IN2_HEIGHT % 256); - EPD_4IN2_SendCommand(0x50); // VCOM AND DATA INTERVAL SETTING - EPD_4IN2_SendData(0x97); // LUTB=0 LUTW=1 interval=10 + EPD_WriteCommand(0x50); // VCOM AND DATA INTERVAL SETTING + EPD_WriteByte(0x97); // LUTB=0 LUTW=1 interval=10 } /****************************************************************************** @@ -137,48 +106,55 @@ void EPD_4IN2_Clear(void) Width = (EPD_4IN2_WIDTH % 8 == 0)? (EPD_4IN2_WIDTH / 8 ): (EPD_4IN2_WIDTH / 8 + 1); Height = EPD_4IN2_HEIGHT; - EPD_4IN2_SendCommand(0x10); + EPD_WriteCommand(0x10); for (UWORD j = 0; j < Height; j++) { for (UWORD i = 0; i < Width; i++) { - EPD_4IN2_SendData(0xFF); + EPD_WriteByte(0xFF); } } - EPD_4IN2_SendCommand(0x13); + EPD_WriteCommand(0x13); for (UWORD j = 0; j < Height; j++) { for (UWORD i = 0; i < Width; i++) { - EPD_4IN2_SendData(0xFF); + EPD_WriteByte(0xFF); } } EPD_4IN2_TurnOnDisplay(); } -/****************************************************************************** -function : Sends the image buffer in RAM to e-Paper and displays -parameter: -******************************************************************************/ -void EPD_4IN2_Display(UBYTE *Image) +static void _setPartialRamArea(UWORD x, UWORD y, UWORD w, UWORD h) { - UWORD Width, Height; - Width = (EPD_4IN2_WIDTH % 8 == 0)? (EPD_4IN2_WIDTH / 8 ): (EPD_4IN2_WIDTH / 8 + 1); - Height = EPD_4IN2_HEIGHT; + uint16_t xe = (x + w - 1) | 0x0007; // byte boundary inclusive (last byte) + uint16_t ye = y + h - 1; + x &= 0xFFF8; // byte boundary + EPD_WriteCommand(0x90); // partial window + EPD_WriteByte(x / 256); + EPD_WriteByte(x % 256); + EPD_WriteByte(xe / 256); + EPD_WriteByte(xe % 256); + EPD_WriteByte(y / 256); + EPD_WriteByte(y % 256); + EPD_WriteByte(ye / 256); + EPD_WriteByte(ye % 256); + EPD_WriteByte(0x01); +} - EPD_4IN2_SendCommand(0x10); - for (UWORD j = 0; j < Height; j++) { - for (UWORD i = 0; i < Width; i++) { - EPD_4IN2_SendData(0x00); +void EPD_4IN2_Write_Image(UBYTE *black, UBYTE *color, UWORD x, UWORD y, UWORD w, UWORD h) +{ + UWORD wb = (w + 7) / 8; // width bytes, bitmaps are padded + x -= x % 8; // byte boundary + w = wb * 8; // byte boundary + if (x + w > EPD_4IN2_WIDTH || y + h > EPD_4IN2_HEIGHT) return; + EPD_WriteCommand(0x91); // partial in + _setPartialRamArea(x, y, w, h); + EPD_WriteCommand(0x13); + for (UWORD i = 0; i < h; i++) { + for (UWORD j = 0; j < w / 8; j++) { + EPD_WriteByte(black[j + i * wb]); } } - - EPD_4IN2_SendCommand(0x13); - for (UWORD j = 0; j < Height; j++) { - for (UWORD i = 0; i < Width; i++) { - EPD_4IN2_SendData(Image[i + j * Width]); - } - } - - EPD_4IN2_TurnOnDisplay(); + EPD_WriteCommand(0x92); // partial out } /****************************************************************************** @@ -187,12 +163,26 @@ parameter: ******************************************************************************/ void EPD_4IN2_Sleep(void) { - EPD_4IN2_SendCommand(0x50); // DEEP_SLEEP - EPD_4IN2_SendData(0XF7); + EPD_WriteCommand(0x50); // DEEP_SLEEP + EPD_WriteByte(0XF7); - EPD_4IN2_SendCommand(0x02); // POWER_OFF + EPD_WriteCommand(0x02); // POWER_OFF EPD_4IN2_ReadBusy(); - EPD_4IN2_SendCommand(0x07); // DEEP_SLEEP - EPD_4IN2_SendData(0XA5); + EPD_WriteCommand(0x07); // DEEP_SLEEP + EPD_WriteByte(0XA5); } + +const epd_driver_t epd_driver_4in2 = { + .id = EPD_DRIVER_4IN2, + .width = EPD_4IN2_WIDTH, + .height = EPD_4IN2_HEIGHT, + .init = EPD_4IN2_Init, + .clear = EPD_4IN2_Clear, + .send_command = EPD_WriteCommand, + .send_byte = EPD_WriteByte, + .send_data = EPD_WriteData, + .write_image = EPD_4IN2_Write_Image, + .display = EPD_4IN2_TurnOnDisplay, + .sleep = EPD_4IN2_Sleep, +}; diff --git a/EPD/EPD_4in2.h b/EPD/EPD_4in2.h deleted file mode 100644 index 95c0ffb..0000000 --- a/EPD/EPD_4in2.h +++ /dev/null @@ -1,50 +0,0 @@ -/***************************************************************************** -* | File : EPD_4in2.h -* | Author : Waveshare team -* | Function : 4.2inch e-paper -* | Info : -*---------------- -* | This version: V3.0 -* | Date : 2019-06-13 -* | Info : -* ----------------------------------------------------------------------------- -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documnetation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -******************************************************************************/ -#ifndef _EPD_4IN2_H_ -#define _EPD_4IN2_H_ - -#include "DEV_Config.h" - -// Display resolution -#define EPD_4IN2_WIDTH 400 -#define EPD_4IN2_HEIGHT 300 - -void EPD_4IN2_Init(void); -void EPD_4IN2_Clear(void); -void EPD_4IN2_Display(UBYTE *Image); -void EPD_4IN2_Sleep(void); - -void EPD_4IN2_SendCommand(UBYTE Reg); -void EPD_4IN2_SendData(UBYTE Data); -void EPD_4IN2_SendData2(UBYTE *Data, UBYTE Len); -void EPD_4IN2_TurnOnDisplay(void); - -#endif diff --git a/EPD/EPD_4in2_V2.c b/EPD/EPD_4in2_V2.c index 3ec1138..600d530 100644 --- a/EPD/EPD_4in2_V2.c +++ b/EPD/EPD_4in2_V2.c @@ -6,7 +6,6 @@ *---------------- * | This version: V1.0 * | Date : 2023-09-12 -* | Info : * ----------------------------------------------------------------------------- # # Permission is hereby granted, free of charge, to any person obtaining a copy @@ -28,7 +27,11 @@ # THE SOFTWARE. # ******************************************************************************/ -#include "EPD_4in2_V2.h" +#include "EPD_driver.h" + +// Display resolution +#define EPD_4IN2_V2_WIDTH 400 +#define EPD_4IN2_V2_HEIGHT 300 /****************************************************************************** function : Software reset @@ -44,39 +47,6 @@ static void EPD_4IN2_V2_Reset(void) DEV_Delay_ms(100); } -/****************************************************************************** -function : send command -parameter: - Reg : Command register -******************************************************************************/ -void EPD_4IN2_V2_SendCommand(UBYTE Reg) -{ - DEV_Digital_Write(EPD_DC_PIN, 0); - DEV_Digital_Write(EPD_CS_PIN, 0); - DEV_SPI_WriteByte(Reg); - DEV_Digital_Write(EPD_CS_PIN, 1); -} - -/****************************************************************************** -function : send data -parameter: - Data : Write data -******************************************************************************/ -void EPD_4IN2_V2_SendData(UBYTE Data) -{ - DEV_Digital_Write(EPD_DC_PIN, 1); - DEV_Digital_Write(EPD_CS_PIN, 0); - DEV_SPI_WriteByte(Data); - DEV_Digital_Write(EPD_CS_PIN, 1); -} - -void EPD_4IN2_V2_SendData2(UBYTE *Data, UBYTE Len) -{ - DEV_Digital_Write(EPD_DC_PIN, 1); - DEV_Digital_Write(EPD_CS_PIN, 0); - DEV_SPI_WriteBytes(Data, Len); - DEV_Digital_Write(EPD_CS_PIN, 1); -} /****************************************************************************** function : Wait until the busy_pin goes LOW parameter: @@ -94,9 +64,9 @@ parameter: ******************************************************************************/ void EPD_4IN2_V2_TurnOnDisplay(void) { - EPD_4IN2_V2_SendCommand(0x22); - EPD_4IN2_V2_SendData(0xF7); - EPD_4IN2_V2_SendCommand(0x20); + EPD_WriteCommand(0x22); + EPD_WriteByte(0xF7); + EPD_WriteCommand(0x20); EPD_4IN2_V2_ReadBusy(); } @@ -106,15 +76,15 @@ parameter: ******************************************************************************/ static void EPD_4IN2_V2_SetWindows(UWORD Xstart, UWORD Ystart, UWORD Xend, UWORD Yend) { - EPD_4IN2_V2_SendCommand(0x44); // SET_RAM_X_ADDRESS_START_END_POSITION - EPD_4IN2_V2_SendData((Xstart>>3) & 0xFF); - EPD_4IN2_V2_SendData((Xend>>3) & 0xFF); + EPD_WriteCommand(0x44); // SET_RAM_X_ADDRESS_START_END_POSITION + EPD_WriteByte((Xstart>>3) & 0xFF); + EPD_WriteByte((Xend>>3) & 0xFF); - EPD_4IN2_V2_SendCommand(0x45); // SET_RAM_Y_ADDRESS_START_END_POSITION - EPD_4IN2_V2_SendData(Ystart & 0xFF); - EPD_4IN2_V2_SendData((Ystart >> 8) & 0xFF); - EPD_4IN2_V2_SendData(Yend & 0xFF); - EPD_4IN2_V2_SendData((Yend >> 8) & 0xFF); + EPD_WriteCommand(0x45); // SET_RAM_Y_ADDRESS_START_END_POSITION + EPD_WriteByte(Ystart & 0xFF); + EPD_WriteByte((Ystart >> 8) & 0xFF); + EPD_WriteByte(Yend & 0xFF); + EPD_WriteByte((Yend >> 8) & 0xFF); } /****************************************************************************** @@ -123,12 +93,12 @@ parameter: ******************************************************************************/ static void EPD_4IN2_V2_SetCursor(UWORD Xstart, UWORD Ystart) { - EPD_4IN2_V2_SendCommand(0x4E); // SET_RAM_X_ADDRESS_COUNTER - EPD_4IN2_V2_SendData(Xstart & 0xFF); + EPD_WriteCommand(0x4E); // SET_RAM_X_ADDRESS_COUNTER + EPD_WriteByte(Xstart & 0xFF); - EPD_4IN2_V2_SendCommand(0x4F); // SET_RAM_Y_ADDRESS_COUNTER - EPD_4IN2_V2_SendData(Ystart & 0xFF); - EPD_4IN2_V2_SendData((Ystart >> 8) & 0xFF); + EPD_WriteCommand(0x4F); // SET_RAM_Y_ADDRESS_COUNTER + EPD_WriteByte(Ystart & 0xFF); + EPD_WriteByte((Ystart >> 8) & 0xFF); } /****************************************************************************** @@ -140,23 +110,23 @@ void EPD_4IN2_V2_Init(void) EPD_4IN2_V2_Reset(); EPD_4IN2_V2_ReadBusy(); - EPD_4IN2_V2_SendCommand(0x12); // soft reset + EPD_WriteCommand(0x12); // soft reset EPD_4IN2_V2_ReadBusy(); - // EPD_4IN2_V2_SendCommand(0x01); //Driver output control - // EPD_4IN2_V2_SendData((EPD_4IN2_V2_HEIGHT-1)%256); - // EPD_4IN2_V2_SendData((EPD_4IN2_V2_HEIGHT-1)/256); - // EPD_4IN2_V2_SendData(0x00); + // EPD_WriteCommand(0x01); //Driver output control + // EPD_WriteByte((EPD_4IN2_V2_HEIGHT-1)%256); + // EPD_WriteByte((EPD_4IN2_V2_HEIGHT-1)/256); + // EPD_WriteByte(0x00); - EPD_4IN2_V2_SendCommand(0x21); // Display update control - EPD_4IN2_V2_SendData(0x40); - EPD_4IN2_V2_SendData(0x00); + EPD_WriteCommand(0x21); // Display update control + EPD_WriteByte(0x40); + EPD_WriteByte(0x00); - EPD_4IN2_V2_SendCommand(0x3C); //BorderWavefrom - EPD_4IN2_V2_SendData(0x05); + EPD_WriteCommand(0x3C); //BorderWavefrom + EPD_WriteByte(0x05); - EPD_4IN2_V2_SendCommand(0x11); // data entry mode - EPD_4IN2_V2_SendData(0x03); // X-mode + EPD_WriteCommand(0x11); // data entry mode + EPD_WriteByte(0x03); // X-mode EPD_4IN2_V2_SetWindows(0, 0, EPD_4IN2_V2_WIDTH-1, EPD_4IN2_V2_HEIGHT-1); @@ -175,46 +145,54 @@ void EPD_4IN2_V2_Clear(void) Width = (EPD_4IN2_V2_WIDTH % 8 == 0)? (EPD_4IN2_V2_WIDTH / 8 ): (EPD_4IN2_V2_WIDTH / 8 + 1); Height = EPD_4IN2_V2_HEIGHT; - EPD_4IN2_V2_SendCommand(0x24); + EPD_WriteCommand(0x24); for (UWORD j = 0; j < Height; j++) { for (UWORD i = 0; i < Width; i++) { - EPD_4IN2_V2_SendData(0xFF); + EPD_WriteByte(0xFF); } } - EPD_4IN2_V2_SendCommand(0x26); + EPD_WriteCommand(0x26); for (UWORD j = 0; j < Height; j++) { for (UWORD i = 0; i < Width; i++) { - EPD_4IN2_V2_SendData(0xFF); + EPD_WriteByte(0xFF); } } EPD_4IN2_V2_TurnOnDisplay(); } -/****************************************************************************** -function : Sends the image buffer in RAM to e-Paper and displays -parameter: -******************************************************************************/ -void EPD_4IN2_V2_Display(UBYTE *Image) +static void _setPartialRamArea(UWORD x, UWORD y, UWORD w, UWORD h) { - UWORD Width, Height; - Width = (EPD_4IN2_V2_WIDTH % 8 == 0)? (EPD_4IN2_V2_WIDTH / 8 ): (EPD_4IN2_V2_WIDTH / 8 + 1); - Height = EPD_4IN2_V2_HEIGHT; + EPD_WriteCommand(0x11); // set ram entry mode + EPD_WriteByte(0x03); // x increase, y increase : normal mode + EPD_WriteCommand(0x44); + EPD_WriteByte(x / 8); + EPD_WriteByte((x + w - 1) / 8); + EPD_WriteCommand(0x45); + EPD_WriteByte(y % 256); + EPD_WriteByte(y / 256); + EPD_WriteByte((y + h - 1) % 256); + EPD_WriteByte((y + h - 1) / 256); + EPD_WriteCommand(0x4e); + EPD_WriteByte(x / 8); + EPD_WriteCommand(0x4f); + EPD_WriteByte(y % 256); + EPD_WriteByte(y / 256); +} - EPD_4IN2_V2_SendCommand(0x24); - for (UWORD j = 0; j < Height; j++) { - for (UWORD i = 0; i < Width; i++) { - EPD_4IN2_V2_SendData(Image[i + j * Width]); +void EPD_4IN2_V2_Write_Image(UBYTE *black, UBYTE *color, UWORD x, UWORD y, UWORD w, UWORD h) +{ + int32_t wb = (w + 7) / 8; // width bytes, bitmaps are padded + x -= x % 8; // byte boundary + w = wb * 8; // byte boundary + if (x + w > EPD_4IN2_V2_WIDTH || y + h > EPD_4IN2_V2_HEIGHT) return; + _setPartialRamArea(x, y, w, h); + EPD_WriteCommand(0x24); + for (UWORD i = 0; i < h; i++) { + for (UWORD j = 0; j < w / 8; j++) { + EPD_WriteByte(black[j + i * wb]); } } - - EPD_4IN2_V2_SendCommand(0x26); - for (UWORD j = 0; j < Height; j++) { - for (UWORD i = 0; i < Width; i++) { - EPD_4IN2_V2_SendData(Image[i + j * Width]); - } - } - EPD_4IN2_V2_TurnOnDisplay(); } /****************************************************************************** @@ -223,7 +201,21 @@ parameter: ******************************************************************************/ void EPD_4IN2_V2_Sleep(void) { - EPD_4IN2_V2_SendCommand(0x10); // DEEP_SLEEP - EPD_4IN2_V2_SendData(0x01); + EPD_WriteCommand(0x10); // DEEP_SLEEP + EPD_WriteByte(0x01); DEV_Delay_ms(200); } + +const epd_driver_t epd_driver_4in2v2 = { + .id = EPD_DRIVER_4IN2_V2, + .width = EPD_4IN2_V2_WIDTH, + .height = EPD_4IN2_V2_HEIGHT, + .init = EPD_4IN2_V2_Init, + .clear = EPD_4IN2_V2_Clear, + .send_command = EPD_WriteCommand, + .send_byte = EPD_WriteByte, + .send_data = EPD_WriteData, + .write_image = EPD_4IN2_V2_Write_Image, + .display = EPD_4IN2_V2_TurnOnDisplay, + .sleep = EPD_4IN2_V2_Sleep, +}; diff --git a/EPD/EPD_4in2_V2.h b/EPD/EPD_4in2_V2.h deleted file mode 100644 index 2a1cbe7..0000000 --- a/EPD/EPD_4in2_V2.h +++ /dev/null @@ -1,53 +0,0 @@ -/***************************************************************************** -* | File : EPD_4in2_V2.h -* | Author : Waveshare team -* | Function : 4.2inch e-paper V2 -* | Info : -*---------------- -* | This version: V1.0 -* | Date : 2023-09-12 -* | Info : -* ----------------------------------------------------------------------------- -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documnetation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -******************************************************************************/ -#ifndef _EPD_4IN2_V2_H_ -#define _EPD_4IN2_V2_H_ - -#include "DEV_Config.h" - -// Display resolution -#define EPD_4IN2_V2_WIDTH 400 -#define EPD_4IN2_V2_HEIGHT 300 - -#define Seconds_1_5S 0 -#define Seconds_1S 1 - -void EPD_4IN2_V2_Init(void); -void EPD_4IN2_V2_Clear(void); -void EPD_4IN2_V2_Display(UBYTE *Image); -void EPD_4IN2_V2_Sleep(void); - -void EPD_4IN2_V2_SendCommand(UBYTE Reg); -void EPD_4IN2_V2_SendData(UBYTE Data); -void EPD_4IN2_V2_SendData2(UBYTE *Data, UBYTE Len); -void EPD_4IN2_V2_TurnOnDisplay(void); - -#endif diff --git a/EPD/EPD_4in2b_V2.c b/EPD/EPD_4in2b_V2.c index d6b089e..727e347 100644 --- a/EPD/EPD_4in2b_V2.c +++ b/EPD/EPD_4in2b_V2.c @@ -6,7 +6,7 @@ *---------------- * | This version: V1.0 * | Date : 2020-11-27 -* | Info : +* ----------------------------------------------------------------------------- # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documnetation files (the "Software"), to deal @@ -27,7 +27,11 @@ # THE SOFTWARE. # ******************************************************************************/ -#include "EPD_4in2b_V2.h" +#include "EPD_driver.h" + +// Display resolution +#define EPD_4IN2B_V2_WIDTH 400 +#define EPD_4IN2B_V2_HEIGHT 300 /****************************************************************************** function : Software reset @@ -43,40 +47,6 @@ static void EPD_4IN2B_V2_Reset(void) DEV_Delay_ms(200); } -/****************************************************************************** -function : send command -parameter: - Reg : Command register -******************************************************************************/ -void EPD_4IN2B_V2_SendCommand(UBYTE Reg) -{ - DEV_Digital_Write(EPD_DC_PIN, 0); - DEV_Digital_Write(EPD_CS_PIN, 0); - DEV_SPI_WriteByte(Reg); - DEV_Digital_Write(EPD_CS_PIN, 1); -} - -/****************************************************************************** -function : send data -parameter: - Data : Write data -******************************************************************************/ -void EPD_4IN2B_V2_SendData(UBYTE Data) -{ - DEV_Digital_Write(EPD_DC_PIN, 1); - DEV_Digital_Write(EPD_CS_PIN, 0); - DEV_SPI_WriteByte(Data); - DEV_Digital_Write(EPD_CS_PIN, 1); -} - -void EPD_4IN2B_V2_SendData2(UBYTE *Data, UBYTE Len) -{ - DEV_Digital_Write(EPD_DC_PIN, 1); - DEV_Digital_Write(EPD_CS_PIN, 0); - DEV_SPI_WriteBytes(Data, Len); - DEV_Digital_Write(EPD_CS_PIN, 1); -} - /****************************************************************************** function : Wait until the busy_pin goes LOW parameter: @@ -84,7 +54,7 @@ parameter: void EPD_4IN2B_V2_ReadBusy(void) { do{ - EPD_4IN2B_V2_SendCommand(0x71); + EPD_WriteCommand(0x71); DEV_Delay_ms(50); }while(!(DEV_Digital_Read(EPD_BUSY_PIN))); DEV_Delay_ms(50); @@ -96,7 +66,7 @@ parameter: ******************************************************************************/ void EPD_4IN2B_V2_TurnOnDisplay(void) { - EPD_4IN2B_V2_SendCommand(0x12); // DISPLAY_REFRESH + EPD_WriteCommand(0x12); // DISPLAY_REFRESH DEV_Delay_ms(100); EPD_4IN2B_V2_ReadBusy(); } @@ -109,11 +79,11 @@ void EPD_4IN2B_V2_Init(void) { EPD_4IN2B_V2_Reset(); - EPD_4IN2B_V2_SendCommand(0x04); + EPD_WriteCommand(0x04); EPD_4IN2B_V2_ReadBusy(); - EPD_4IN2B_V2_SendCommand(0x00); - EPD_4IN2B_V2_SendData(0x0f); + EPD_WriteCommand(0x00); + EPD_WriteByte(0x0f); } /****************************************************************************** @@ -126,48 +96,62 @@ void EPD_4IN2B_V2_Clear(void) Width = (EPD_4IN2B_V2_WIDTH % 8 == 0)? (EPD_4IN2B_V2_WIDTH / 8 ): (EPD_4IN2B_V2_WIDTH / 8 + 1); Height = EPD_4IN2B_V2_HEIGHT; - EPD_4IN2B_V2_SendCommand(0x10); + EPD_WriteCommand(0x10); for (UWORD j = 0; j < Height; j++) { for (UWORD i = 0; i < Width; i++) { - EPD_4IN2B_V2_SendData(0xFF); + EPD_WriteByte(0xFF); } } - EPD_4IN2B_V2_SendCommand(0x13); + EPD_WriteCommand(0x13); for (UWORD j = 0; j < Height; j++) { for (UWORD i = 0; i < Width; i++) { - EPD_4IN2B_V2_SendData(0xFF); + EPD_WriteByte(0xFF); } } EPD_4IN2B_V2_TurnOnDisplay(); } -/****************************************************************************** -function : Sends the image buffer in RAM to e-Paper and displays -parameter: -******************************************************************************/ -void EPD_4IN2B_V2_Display(const UBYTE *blackimage, const UBYTE *ryimage) +static void _setPartialRamArea(UWORD x, UWORD y, UWORD w, UWORD h) { - UWORD Width, Height; - Width = (EPD_4IN2B_V2_WIDTH % 8 == 0)? (EPD_4IN2B_V2_WIDTH / 8 ): (EPD_4IN2B_V2_WIDTH / 8 + 1); - Height = EPD_4IN2B_V2_HEIGHT; + UWORD xe = (x + w - 1) | 0x0007; // byte boundary inclusive (last byte) + UWORD ye = y + h - 1; + x &= 0xFFF8; // byte boundary + xe |= 0x0007; // byte boundary + EPD_WriteCommand(0x90); // partial window + EPD_WriteByte(x / 256); + EPD_WriteByte(x % 256); + EPD_WriteByte(xe / 256); + EPD_WriteByte(xe % 256); + EPD_WriteByte(y / 256); + EPD_WriteByte(y % 256); + EPD_WriteByte(ye / 256); + EPD_WriteByte(ye % 256); + EPD_WriteByte(0x00); // distortion on right half +} - EPD_4IN2B_V2_SendCommand(0x10); - for (UWORD j = 0; j < Height; j++) { - for (UWORD i = 0; i < Width; i++) { - EPD_4IN2B_V2_SendData(blackimage[i + j * Width]); +void EPD_4IN2B_V2_Write_Image(UBYTE *black, UBYTE *color, UWORD x, UWORD y, UWORD w, UWORD h) +{ + UWORD wb = (w + 7) / 8; // width bytes, bitmaps are padded + x -= x % 8; // byte boundary + w = wb * 8; // byte boundary + if (x + w > EPD_4IN2B_V2_WIDTH || y + h > EPD_4IN2B_V2_HEIGHT) return; + EPD_WriteCommand(0x91); // partial in + _setPartialRamArea(x, y, w, h); + EPD_WriteCommand(0x10); + for (UWORD i = 0; i < h; i++) { + for (UWORD j = 0; j < w / 8; j++) { + EPD_WriteByte(black ? black[j + i * wb] : 0xFF); } } - - EPD_4IN2B_V2_SendCommand(0x13); - for (UWORD j = 0; j < Height; j++) { - for (UWORD i = 0; i < Width; i++) { - EPD_4IN2B_V2_SendData(ryimage[i + j * Width]); + EPD_WriteCommand(0x13); + for (UWORD i = 0; i < h; i++) { + for (UWORD j = 0; j < w / 8; j++) { + EPD_WriteByte(color ? color[j + i * wb] : 0xFF); } } - - EPD_4IN2B_V2_TurnOnDisplay(); + EPD_WriteCommand(0x92); // partial out } /****************************************************************************** @@ -176,11 +160,25 @@ parameter: ******************************************************************************/ void EPD_4IN2B_V2_Sleep(void) { - EPD_4IN2B_V2_SendCommand(0X50); - EPD_4IN2B_V2_SendData(0xf7); //border floating + EPD_WriteCommand(0X50); + EPD_WriteByte(0xf7); //border floating - EPD_4IN2B_V2_SendCommand(0X02); //power off + EPD_WriteCommand(0X02); //power off EPD_4IN2B_V2_ReadBusy(); //waiting for the electronic paper IC to release the idle signal - EPD_4IN2B_V2_SendCommand(0X07); //deep sleep - EPD_4IN2B_V2_SendData(0xA5); + EPD_WriteCommand(0X07); //deep sleep + EPD_WriteByte(0xA5); } + +const epd_driver_t epd_driver_4in2bv2 = { + .id = EPD_DRIVER_4IN2B_V2, + .width = EPD_4IN2B_V2_WIDTH, + .height = EPD_4IN2B_V2_HEIGHT, + .init = EPD_4IN2B_V2_Init, + .clear = EPD_4IN2B_V2_Clear, + .send_command = EPD_WriteCommand, + .send_byte = EPD_WriteByte, + .send_data = EPD_WriteData, + .write_image = EPD_4IN2B_V2_Write_Image, + .display = EPD_4IN2B_V2_TurnOnDisplay, + .sleep = EPD_4IN2B_V2_Sleep, +}; diff --git a/EPD/EPD_4in2b_V2.h b/EPD/EPD_4in2b_V2.h deleted file mode 100644 index 7d82ae3..0000000 --- a/EPD/EPD_4in2b_V2.h +++ /dev/null @@ -1,49 +0,0 @@ -/***************************************************************************** -* | File : EPD_4in2b_V2.h -* | Author : Waveshare team -* | Function : 4.2inch e-paper b V2 -* | Info : -*---------------- -* | This version: V1.0 -* | Date : 2020-11-27 -* | Info : -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documnetation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS OR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -******************************************************************************/ -#ifndef __EPD_4IN2B_V2_H_ -#define __EPD_4IN2B_V2_H_ - -#include "DEV_Config.h" - -// Display resolution -#define EPD_4IN2B_V2_WIDTH 400 -#define EPD_4IN2B_V2_HEIGHT 300 - -void EPD_4IN2B_V2_Init(void); -void EPD_4IN2B_V2_Clear(void); -void EPD_4IN2B_V2_Display(const UBYTE *blackimage, const UBYTE *ryimage); -void EPD_4IN2B_V2_Sleep(void); - -void EPD_4IN2B_V2_SendCommand(UBYTE Reg); -void EPD_4IN2B_V2_SendData(UBYTE Data); -void EPD_4IN2B_V2_SendData2(UBYTE *Data, UBYTE Len); -void EPD_4IN2B_V2_TurnOnDisplay(void); - -#endif diff --git a/EPD/EPD_Test.c b/EPD/EPD_Test.c index 28c9052..17e2140 100644 --- a/EPD/EPD_Test.c +++ b/EPD/EPD_Test.c @@ -1,5 +1,5 @@ #include "EPD_Test.h" -#include "EPD_4in2.h" +#include "EPD_driver.h" const unsigned char EPD_4IN2_4Gray_lut_vcom[] = { @@ -52,113 +52,115 @@ const unsigned char EPD_4IN2_4Gray_lut_bb[] ={ 0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00, }; -static void EPD_4IN2_4Gray_lut(void) +static void write_4gray_lut(epd_driver_t *driver) { unsigned int count; - EPD_4IN2_SendCommand(0x20); //vcom + driver->send_command(0x20); //vcom for(count=0;count<42;count++) - {EPD_4IN2_SendData(EPD_4IN2_4Gray_lut_vcom[count]);} + {driver->send_byte(EPD_4IN2_4Gray_lut_vcom[count]);} - EPD_4IN2_SendCommand(0x21); //red not use + driver->send_command(0x21); //red not use for(count=0;count<42;count++) - {EPD_4IN2_SendData(EPD_4IN2_4Gray_lut_ww[count]);} + {driver->send_byte(EPD_4IN2_4Gray_lut_ww[count]);} - EPD_4IN2_SendCommand(0x22); //bw r + driver->send_command(0x22); //bw r for(count=0;count<42;count++) - {EPD_4IN2_SendData(EPD_4IN2_4Gray_lut_bw[count]);} + {driver->send_byte(EPD_4IN2_4Gray_lut_bw[count]);} - EPD_4IN2_SendCommand(0x23); //wb w + driver->send_command(0x23); //wb w for(count=0;count<42;count++) - {EPD_4IN2_SendData(EPD_4IN2_4Gray_lut_wb[count]);} + {driver->send_byte(EPD_4IN2_4Gray_lut_wb[count]);} - EPD_4IN2_SendCommand(0x24); //bb b + driver->send_command(0x24); //bb b for(count=0;count<42;count++) - {EPD_4IN2_SendData(EPD_4IN2_4Gray_lut_bb[count]);} + {driver->send_byte(EPD_4IN2_4Gray_lut_bb[count]);} - EPD_4IN2_SendCommand(0x25); //vcom + driver->send_command(0x25); //vcom for(count=0;count<42;count++) - {EPD_4IN2_SendData(EPD_4IN2_4Gray_lut_ww[count]);} + {driver->send_byte(EPD_4IN2_4Gray_lut_ww[count]);} } -static void drawNormal(void) +static void drawNormal(epd_driver_t *driver) { UWORD Width, Height; - Width = (EPD_4IN2_WIDTH % 8 == 0)? (EPD_4IN2_WIDTH / 8 ): (EPD_4IN2_WIDTH / 8 + 1); - Height = EPD_4IN2_HEIGHT; + Width = (driver->width % 8 == 0)? (driver->width / 8 ): (driver->width / 8 + 1); + Height = driver->height; - EPD_4IN2_SendCommand(0x10); + driver->send_command(0x10); for (UWORD j = 0; j < Height; j++) { for (UWORD i = 0; i < Width; i++) { - EPD_4IN2_SendData(0xFF); + driver->send_byte(0xFF); } } - EPD_4IN2_SendCommand(0x13); + driver->send_command(0x13); for (UWORD j = 0; j < Height; j++) { for (UWORD i = 0; i < Width; i++) { - EPD_4IN2_SendData(((j + 1)*2 > Height) ? 0x00 : 0xFF); + driver->send_byte(((j + 1)*2 > Height) ? 0x00 : 0xFF); } } - EPD_4IN2_TurnOnDisplay(); + driver->display(); } -static void draw4Gray(void) +static void draw4Gray(epd_driver_t *driver) { UWORD Width, Height; - Width = (EPD_4IN2_WIDTH % 8 == 0)? (EPD_4IN2_WIDTH / 8 ): (EPD_4IN2_WIDTH / 8 + 1); - Height = EPD_4IN2_HEIGHT; + Width = (driver->width % 8 == 0)? (driver->width / 8 ): (driver->width / 8 + 1); + Height = driver->height; - EPD_4IN2_SendCommand(0x10); + driver->send_command(0x10); for (UWORD i = 0; i < Width * Height; i++) { UWORD idx = (i % 50) / 12; if (idx > 3) idx = 3; if (idx == 0 || idx == 1) { - EPD_4IN2_SendData(0x00); + driver->send_byte(0x00); } else if (idx == 2 || idx == 3) { - EPD_4IN2_SendData(0xFF); + driver->send_byte(0xFF); } } - EPD_4IN2_SendCommand(0x13); + driver->send_command(0x13); for (UWORD i = 0; i < Width * Height; i++) { UWORD idx = (i % 50) / 12; if (idx > 3) idx = 3; if (idx == 0 || idx == 2) { - EPD_4IN2_SendData(0x00); + driver->send_byte(0x00); } else if (idx == 1 || idx == 3) { - EPD_4IN2_SendData(0xFF); + driver->send_byte(0xFF); } } // Load LUT from register - EPD_4IN2_SendCommand(0x00); - EPD_4IN2_SendData(0x3f); + driver->send_command(0x00); + driver->send_byte(0x3f); - EPD_4IN2_4Gray_lut(); - EPD_4IN2_TurnOnDisplay(); + write_4gray_lut(driver); + driver->display(); // Load LUT from OTP - EPD_4IN2_SendCommand(0x00); - EPD_4IN2_SendData(0x1f); + driver->send_command(0x00); + driver->send_byte(0x1f); } void EPD_4in2_test(void) { - DEV_Module_Init(); + epd_driver_t *driver = epd_driver_by_id(EPD_DRIVER_4IN2); - EPD_4IN2_Init(); - EPD_4IN2_Clear(); + DEV_Module_Init(); + + driver->init(); + driver->clear(); DEV_Delay_ms(500); - drawNormal(); + drawNormal(driver); DEV_Delay_ms(1000); - draw4Gray(); + draw4Gray(driver); DEV_Delay_ms(1000); - - EPD_4IN2_Sleep(); + + driver->sleep(); DEV_Delay_ms(500); DEV_Module_Exit(); } diff --git a/EPD/EPD_ble.c b/EPD/EPD_ble.c index 2284971..c7a3961 100644 --- a/EPD/EPD_ble.c +++ b/EPD/EPD_ble.c @@ -18,46 +18,22 @@ #include "nrf_soc.h" #include "nrf_log.h" #include "pstorage.h" -#include "EPD_4in2.h" -#include "EPD_4in2_V2.h" -#include "EPD_4in2b_V2.h" #include "EPD_ble.h" -#define BLE_EPD_CONFIG_ADDR (NRF_FICR->CODEPAGESIZE * (NRF_FICR->CODESIZE - 1)) // Last page of the flash +#define EPD_CFG_DEFAULT {0x05, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x01, 0x07} + +#ifndef EPD_CFG_DEFAULT +#define EPD_CFG_DEFAULT {0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x03, 0x09, 0x03} +#endif + #define BLE_EPD_BASE_UUID {{0XEC, 0X5A, 0X67, 0X1C, 0XC1, 0XB6, 0X46, 0XFB, \ 0X8D, 0X91, 0X28, 0XD8, 0X22, 0X36, 0X75, 0X62}} #define BLE_UUID_EPD_CHARACTERISTIC 0x0002 -#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) #define EPD_CONFIG_SIZE (sizeof(epd_config_t) / sizeof(uint8_t)) static pstorage_handle_t m_flash_handle; -/** EPD drivers */ -static epd_driver_t epd_drivers[] = { - {EPD_DRIVER_4IN2, EPD_4IN2_Init, EPD_4IN2_Clear, - EPD_4IN2_SendCommand, EPD_4IN2_SendData2, - EPD_4IN2_TurnOnDisplay, EPD_4IN2_Sleep}, - {EPD_DRIVER_4IN2_V2, EPD_4IN2_V2_Init, EPD_4IN2_V2_Clear, - EPD_4IN2_V2_SendCommand, EPD_4IN2_V2_SendData2, - EPD_4IN2_V2_TurnOnDisplay, EPD_4IN2_V2_Sleep}, - {EPD_DRIVER_4IN2B_V2, EPD_4IN2B_V2_Init, EPD_4IN2B_V2_Clear, - EPD_4IN2B_V2_SendCommand, EPD_4IN2B_V2_SendData2, - EPD_4IN2B_V2_TurnOnDisplay, EPD_4IN2B_V2_Sleep}, -}; - -static epd_driver_t *epd_driver_get(uint8_t id) -{ - for (uint8_t i = 0; i < ARRAY_SIZE(epd_drivers); i++) - { - if (epd_drivers[i].id == id) - { - return &epd_drivers[i]; - } - } - return NULL; -} - static uint32_t epd_config_load(epd_config_t *cfg) { return pstorage_load((uint8_t *)cfg, &m_flash_handle, sizeof(epd_config_t), 0); @@ -90,7 +66,6 @@ static void on_connect(ble_epd_t * p_epd, ble_evt_t * p_ble_evt) nrf_gpio_pin_toggle(p_epd->config.led_pin); } p_epd->conn_handle = p_ble_evt->evt.gap_evt.conn_handle; - DEV_Module_Init(); } @@ -107,15 +82,17 @@ static void on_disconnect(ble_epd_t * p_epd, ble_evt_t * p_ble_evt) nrf_gpio_pin_toggle(p_epd->config.led_pin); } p_epd->conn_handle = BLE_CONN_HANDLE_INVALID; - DEV_Module_Exit(); } static void epd_service_process(ble_epd_t * p_epd, uint8_t * p_data, uint16_t length) { if (p_data == NULL || length <= 0) return; NRF_LOG_PRINTF("[EPD]: CMD=0x%02x, LEN=%d\n", p_data[0], length); - - uint32_t err_code; + + if (p_epd->epd_cmd_cb != NULL) { + if (p_epd->epd_cmd_cb(p_data[0], length > 1 ? &p_data[1] : NULL, length - 1)) + return; + } switch (p_data[0]) { @@ -131,24 +108,19 @@ static void epd_service_process(ble_epd_t * p_epd, uint8_t * p_data, uint16_t le EPD_RST_PIN = p_epd->config.rst_pin = p_data[5]; EPD_BUSY_PIN = p_epd->config.busy_pin = p_data[6]; EPD_BS_PIN = p_epd->config.bs_pin = p_data[7]; - err_code = epd_config_save(&p_epd->config); - NRF_LOG_PRINTF("epd_config_save: %d\n", err_code); - - NRF_LOG_PRINTF("[EPD]: MOSI=0x%02x SCLK=0x%02x CS=0x%02x DC=0x%02x RST=0x%02x BUSY=0x%02x BS=0x%02x\n", - EPD_MOSI_PIN, EPD_SCLK_PIN, EPD_CS_PIN, EPD_DC_PIN, EPD_RST_PIN, EPD_BUSY_PIN, EPD_BS_PIN); + epd_config_save(&p_epd->config); + DEV_Module_Init(); break; case EPD_CMD_INIT: if (length > 1) { - epd_driver_t *driver = epd_driver_get(p_data[1]); - if (driver != NULL) + if (epd_driver_set(p_data[1])) { - p_epd->driver = driver; - p_epd->config.driver_id = driver->id; - err_code = epd_config_save(&p_epd->config); - NRF_LOG_PRINTF("epd_config_save: %d\n", err_code); + p_epd->driver = epd_driver_get(); + p_epd->config.driver_id = p_epd->driver->id; + epd_config_save(&p_epd->config); } } @@ -343,7 +315,7 @@ static void epd_config_init(ble_epd_t * p_epd) // write default config if (is_empty_config) { - uint8_t cfg[] = {0x05, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x01, 0x07}; + uint8_t cfg[] = EPD_CFG_DEFAULT; memcpy(&p_epd->config, cfg, ARRAY_SIZE(cfg)); epd_config_save(&p_epd->config); } @@ -357,11 +329,8 @@ static void epd_config_init(ble_epd_t * p_epd) EPD_BUSY_PIN = p_epd->config.busy_pin; EPD_BS_PIN = p_epd->config.bs_pin; - p_epd->driver = epd_driver_get(p_epd->config.driver_id); - if (p_epd->driver == NULL) - { - p_epd->driver = &epd_drivers[0]; - } + epd_driver_set(p_epd->config.driver_id); + p_epd->driver = epd_driver_get(); } void ble_epd_sleep_prepare(ble_epd_t * p_epd) @@ -387,12 +356,13 @@ static void pstorage_callback(pstorage_handle_t * p_handle, NRF_LOG_PRINTF("pstorage_callback: op_code=%d, result=%d\n", op_code, result); } -uint32_t ble_epd_init(ble_epd_t * p_epd) +uint32_t ble_epd_init(ble_epd_t * p_epd, epd_callback_t cmd_cb) { if (p_epd == NULL) { return NRF_ERROR_NULL; } + p_epd->epd_cmd_cb = cmd_cb; // Initialize the service structure. p_epd->conn_handle = BLE_CONN_HANDLE_INVALID; diff --git a/EPD/EPD_ble.h b/EPD/EPD_ble.h index 448c6d2..dc6f375 100644 --- a/EPD/EPD_ble.h +++ b/EPD/EPD_ble.h @@ -17,12 +17,14 @@ #include "ble_srv_common.h" #include #include -#include "DEV_Config.h" +#include "EPD_driver.h" #define BLE_UUID_EPD_SERVICE 0x0001 #define EPD_SERVICE_UUID_TYPE BLE_UUID_TYPE_VENDOR_BEGIN #define BLE_EPD_MAX_DATA_LEN (GATT_MTU_SIZE_DEFAULT - 3) /**< Maximum length of data (in bytes) that can be transmitted to the peer. */ +typedef bool (*epd_callback_t)(uint8_t cmd, uint8_t *data, uint16_t len); + /**< EPD Service Configs */ typedef struct { @@ -50,6 +52,8 @@ enum EPD_CMDS EPD_CMD_SEND_DATA, /**< send data to EPD */ EPD_CMD_DISPLAY, /**< diaplay EPD ram on screen */ EPD_CMD_SLEEP, /**< EPD enter sleep mode */ + + EPD_CMD_SET_TIME = 0x20, /** < set time with unix timestamp */ EPD_CMD_SET_CONFIG = 0x90, /**< set full EPD config */ EPD_CMD_SYS_RESET = 0x91, /**< MCU reset */ @@ -57,29 +61,6 @@ enum EPD_CMDS EPD_CMD_CFG_ERASE = 0x99, /**< Erase config and reset */ }; -/**< EPD driver IDs. */ -enum EPD_DRIVER_IDS -{ - EPD_DRIVER_4IN2 = 1, - EPD_DRIVER_4IN2_V2, - EPD_DRIVER_4IN2B_V2, -}; - -/**@brief EPD driver structure. - * - * @details This structure contains epd driver functions. - */ -typedef struct -{ - uint8_t id; /**< driver ID. */ - void (*init)(void); /**< Initialize the e-Paper register */ - void (*clear)(void); /**< Clear screen */ - void (*send_command)(UBYTE Reg); /**< send command */ - void (*send_data)(UBYTE *Data, UBYTE Len); /**< send data */ - void (*display)(void); /**< Sends the image buffer in RAM to e-Paper and displays */ - void (*sleep)(void); /**< Enter sleep mode */ -} epd_driver_t; - /**@brief EPD Service structure. * * @details This structure contains status information related to the service. @@ -93,6 +74,7 @@ typedef struct bool is_notification_enabled; /**< Variable to indicate if the peer has enabled notification of the RX characteristic.*/ epd_driver_t *driver; /**< current EPD driver */ epd_config_t config; /**< EPD config */ + epd_callback_t epd_cmd_cb; /**< EPD callback */ } ble_epd_t; /**@brief Function for preparing sleep mode. @@ -106,12 +88,12 @@ void ble_epd_sleep_prepare(ble_epd_t * p_epd); * @param[out] p_epd EPD Service structure. This structure must be supplied * by the application. It is initialized by this function and will * later be used to identify this particular service instance. - * @param[in] p_epd_init Information needed to initialize the service. + * @param[in] cmd_cb Time update callback * * @retval NRF_SUCCESS If the service was successfully initialized. Otherwise, an error code is returned. * @retval NRF_ERROR_NULL If either of the pointers p_epd or p_epd_init is NULL. */ -uint32_t ble_epd_init(ble_epd_t * p_epd); +uint32_t ble_epd_init(ble_epd_t * p_epd, epd_callback_t cmd_cb); /**@brief Function for handling the EPD Service's BLE events. * diff --git a/EPD/DEV_Config.c b/EPD/EPD_driver.c similarity index 63% rename from EPD/DEV_Config.c rename to EPD/EPD_driver.c index 81be0ef..36ff258 100644 --- a/EPD/DEV_Config.c +++ b/EPD/EPD_driver.c @@ -11,12 +11,9 @@ * | Info : Basic version * ******************************************************************************/ -#include "nrf_drv_spi.h" -#include "DEV_Config.h" -#include -#include -#include +#include "nrf_drv_spi.h" +#include "EPD_driver.h" uint32_t EPD_MOSI_PIN = 5; uint32_t EPD_SCLK_PIN = 8; @@ -28,6 +25,50 @@ uint32_t EPD_BS_PIN = 13; static const nrf_drv_spi_t spi = NRF_DRV_SPI_INSTANCE(0); +extern epd_driver_t epd_driver_4in2; +extern epd_driver_t epd_driver_4in2v2; +extern epd_driver_t epd_driver_4in2bv2; + +/** EPD drivers */ +static epd_driver_t *epd_drivers[] = { + &epd_driver_4in2, + &epd_driver_4in2v2, + &epd_driver_4in2bv2, +}; + +/**< current EPD driver */ +static epd_driver_t *m_driver = NULL; + +epd_driver_t *epd_driver_get(void) +{ + if (m_driver == NULL) + m_driver = epd_drivers[0]; + return m_driver; +} + +epd_driver_t *epd_driver_by_id(uint8_t id) +{ + for (uint8_t i = 0; i < ARRAY_SIZE(epd_drivers); i++) + { + if (epd_drivers[i]->id == id) + { + return epd_drivers[i]; + } + } + return NULL; +} + +bool epd_driver_set(uint8_t id) +{ + epd_driver_t *driver = epd_driver_by_id(id); + if (driver ) + { + m_driver = driver; + return true; + } + return false; +} + /****************************************************************************** function: Initialize Arduino, Initialize Pins, and SPI parameter: @@ -57,10 +98,21 @@ UBYTE DEV_Module_Init(void) DEV_Digital_Write(EPD_DC_PIN, 0); DEV_Digital_Write(EPD_CS_PIN, 0); DEV_Digital_Write(EPD_RST_PIN, 1); - + return 0; } +void DEV_Module_Exit(void) +{ + DEV_Digital_Write(EPD_DC_PIN, 0); + DEV_Digital_Write(EPD_CS_PIN, 0); + + //close 5V + DEV_Digital_Write(EPD_RST_PIN, 0); + + nrf_drv_spi_uninit(&spi); +} + /********************************************* function: Hardware interface note: @@ -84,13 +136,26 @@ UBYTE DEV_SPI_ReadByte(void) return value; } -void DEV_Module_Exit(void) +void EPD_WriteCommand(UBYTE Reg) { DEV_Digital_Write(EPD_DC_PIN, 0); DEV_Digital_Write(EPD_CS_PIN, 0); - - //close 5V - DEV_Digital_Write(EPD_RST_PIN, 0); - - nrf_drv_spi_uninit(&spi); + DEV_SPI_WriteByte(Reg); + DEV_Digital_Write(EPD_CS_PIN, 1); +} + +void EPD_WriteByte(UBYTE Data) +{ + DEV_Digital_Write(EPD_DC_PIN, 1); + DEV_Digital_Write(EPD_CS_PIN, 0); + DEV_SPI_WriteByte(Data); + DEV_Digital_Write(EPD_CS_PIN, 1); +} + +void EPD_WriteData(UBYTE *Data, UBYTE Len) +{ + DEV_Digital_Write(EPD_DC_PIN, 1); + DEV_Digital_Write(EPD_CS_PIN, 0); + DEV_SPI_WriteBytes(Data, Len); + DEV_Digital_Write(EPD_CS_PIN, 1); } diff --git a/EPD/EPD_driver.h b/EPD/EPD_driver.h new file mode 100644 index 0000000..7e26482 --- /dev/null +++ b/EPD/EPD_driver.h @@ -0,0 +1,95 @@ +/***************************************************************************** +* | File : DEV_Config.h +* | Author : Waveshare team +* | Function : debug with prntf +* | Info : +* Image scanning +* Please use progressive scanning to generate images or fonts +*---------------- +* | This version: V1.0 +* | Date : 2018-01-11 +* | Info : Basic version +* +******************************************************************************/ + +#ifndef __EPD_DRIVER_H +#define __EPD_DRIVER_H + +#include +#include +#include "nrf_delay.h" +#include "nrf_gpio.h" + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) +#endif + +/** + * data +**/ +#define UBYTE uint8_t +#define UWORD uint16_t +#define UDOUBLE uint32_t + +/**< EPD driver IDs. */ +enum EPD_DRIVER_IDS +{ + EPD_DRIVER_4IN2 = 1, + EPD_DRIVER_4IN2_V2, + EPD_DRIVER_4IN2B_V2, +}; + +/**@brief EPD driver structure. + * + * @details This structure contains epd driver functions. + */ +typedef struct +{ + uint8_t id; /**< driver ID. */ + uint16_t width; + uint16_t height; + void (*init)(void); /**< Initialize the e-Paper register */ + void (*clear)(void); /**< Clear screen */ + void (*send_command)(UBYTE Reg); /**< send command */ + void (*send_byte)(UBYTE Reg); /**< send byte */ + void (*send_data)(UBYTE *Data, UBYTE Len); /**< send data */ + void (*write_image)(UBYTE *black, UBYTE *color, UWORD x, UWORD y, UWORD w, UWORD h); /**< write image */ + void (*display)(void); /**< Sends the image buffer in RAM to e-Paper and displays */ + void (*sleep)(void); /**< Enter sleep mode */ +} epd_driver_t; + +extern uint32_t EPD_MOSI_PIN; +extern uint32_t EPD_SCLK_PIN; +extern uint32_t EPD_CS_PIN; +extern uint32_t EPD_DC_PIN; +extern uint32_t EPD_RST_PIN; +extern uint32_t EPD_BUSY_PIN; +extern uint32_t EPD_BS_PIN; + +/** + * GPIO read and write +**/ +#define DEV_Digital_Write(_pin, _value) nrf_gpio_pin_write(_pin, _value) +#define DEV_Digital_Read(_pin) nrf_gpio_pin_read(_pin) + +/** + * delay x ms +**/ +#define DEV_Delay_ms(__xms) nrf_delay_ms(__xms); +#define DEV_Delay_us(__xus) nrf_delay_us(__xus); + +UBYTE DEV_Module_Init(void); +void DEV_Module_Exit(void); + +void DEV_SPI_WriteByte(UBYTE value); +void DEV_SPI_WriteBytes(UBYTE *value, UBYTE len); + +void EPD_WriteCommand(UBYTE Reg); +void EPD_WriteByte(UBYTE Data); +void EPD_WriteData(UBYTE *Data, UBYTE Len); + +epd_driver_t *epd_driver_get(void); +epd_driver_t *epd_driver_by_id(uint8_t id); +bool epd_driver_set(uint8_t id); + +#endif diff --git a/GUI/Adafruit_GFX.c b/GUI/Adafruit_GFX.c new file mode 100644 index 0000000..9898129 --- /dev/null +++ b/GUI/Adafruit_GFX.c @@ -0,0 +1,943 @@ +/* +This is the core graphics library for all our displays, providing a common +set of graphics primitives (points, lines, circles, etc.). It needs to be +paired with a hardware-specific library for each display device we carry +(to handle the lower-level functions). + +Adafruit invests time and resources providing this open source code, please +support Adafruit & open-source hardware by purchasing products from Adafruit! + +Copyright (c) 2013 Adafruit Industries. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include "Adafruit_GFX.h" + +#ifndef abs +#define abs(x) ((x)>0?(x):-(x)) +#endif +#ifndef min +#define min(a, b) (((a) < (b)) ? (a) : (b)) +#endif + +#ifndef _swap_int16_t +#define _swap_int16_t(a, b) \ + { \ + int16_t t = a; \ + a = b; \ + b = t; \ + } +#endif + +static void GFX_u8g2_draw_hv_line(int16_t x, int16_t y, int16_t len, + uint8_t dir, uint16_t color, void *arg) +{ + Adafruit_GFX *gfx = (Adafruit_GFX *)arg; + switch(dir) { + case 0: + GFX_drawFastHLine(gfx, x, y, len, color); + break; + case 1: + GFX_drawFastVLine(gfx, x, y, len, color); + break; + case 2: + GFX_drawFastHLine(gfx, x - len + 1, y, len, color); + break; + case 3: + GFX_drawFastVLine(gfx, x, y - len + 1, len, color); + break; + } +} + +/**************************************************************************/ +/*! + @brief Instatiate a GFX context for graphics + @param w Display width, in pixels + @param h Display height, in pixels + @param buffer_height Page buffer height +*/ +/**************************************************************************/ +void GFX_begin(Adafruit_GFX *gfx, int16_t w, int16_t h, int16_t buffer_height) { + memset(gfx, 0, sizeof(Adafruit_GFX)); + memset(&gfx->u8g2, 0, sizeof(gfx->u8g2)); + gfx->WIDTH = gfx->_width = w; + gfx->HEIGHT = gfx->_height = h; + gfx->u8g2.draw_hv_line = GFX_u8g2_draw_hv_line; + gfx->u8g2.draw_hv_line_arg = gfx; + gfx->buffer = malloc(((gfx->WIDTH + 7) / 8) * buffer_height); + gfx->page_height = buffer_height; + gfx->total_pages = (gfx->HEIGHT / gfx->page_height) + (gfx->HEIGHT % gfx->page_height > 0); +} + +/**************************************************************************/ +/*! + @brief Instatiate a 3-color GFX context for graphics + @param w Display width, in pixels + @param h Display height, in pixels + @param buffer_height Page buffer height, should be multiple of 2 +*/ +/**************************************************************************/ +void GFX_begin_3c(Adafruit_GFX *gfx, int16_t w, int16_t h, int16_t buffer_height) { + GFX_begin(gfx, w, h, buffer_height); + gfx->page_height = buffer_height / 2; + gfx->color = gfx->buffer + ((gfx->WIDTH + 7) / 8) * gfx->page_height; + gfx->total_pages = (gfx->HEIGHT / gfx->page_height) + (gfx->HEIGHT % gfx->page_height > 0); +} + +void GFX_end(Adafruit_GFX *gfx) { + if (gfx->buffer) free(gfx->buffer); +} + +void GFX_firstPage(Adafruit_GFX *gfx) { + GFX_fillScreen(gfx, GFX_WHITE); + gfx->current_page = 0; +} + +bool GFX_nextPage(Adafruit_GFX *gfx, buffer_callback callback) { + int16_t page_y = gfx->current_page * gfx->page_height; + int16_t height = min(gfx->page_height, gfx->HEIGHT - page_y); + if (callback) + callback(gfx->buffer, gfx->color, 0, page_y, gfx->WIDTH, height); + + gfx->current_page++; + GFX_fillScreen(gfx, GFX_WHITE); + + return gfx->current_page < gfx->total_pages; +} + +/**************************************************************************/ +/*! + @brief Set rotation setting for display + @param r 0 thru 3 corresponding to 4 cardinal rotations +*/ +/**************************************************************************/ +void GFX_setRotation(Adafruit_GFX *gfx, GFX_Rotate r) { + gfx->rotation = r; + switch (gfx->rotation) { + case GFX_ROTATE_0: + case GFX_ROTATE_180: + gfx->_width = gfx->WIDTH; + gfx->_height = gfx->HEIGHT; + break; + case GFX_ROTATE_90: + case GFX_ROTATE_270: + gfx->_width = gfx->HEIGHT; + gfx->_height = gfx->WIDTH; + break; + } +} + + +/**************************************************************************/ +/*! + @brief Draw a pixel + @param x x coordinate + @param y y coordinate + @param color 16-bit 5-6-5 Color to fill with +*/ +/**************************************************************************/ +void GFX_drawPixel(Adafruit_GFX *gfx, int16_t x, int16_t y, uint16_t color) { + if (x < 0 || x >= gfx->_width || y < 0 || y >= gfx->_height) return; + + switch (gfx->rotation) { + case GFX_ROTATE_0: + break; + case GFX_ROTATE_90: + _swap_int16_t(x, y); + x = gfx->WIDTH - x - 1; + break; + case GFX_ROTATE_180: + x = gfx->WIDTH - x - 1; + y = gfx->HEIGHT - y - 1; + break; + case GFX_ROTATE_270: + _swap_int16_t(x, y); + y = gfx->HEIGHT - y - 1; + break; + } + + y -= gfx->current_page * gfx->page_height; + if (y < 0 || y >= gfx->page_height) return; + + uint16_t i = x / 8 + y * (gfx->WIDTH / 8); + if (gfx->color != NULL) { + gfx->buffer[i] |= 0x80 >> (x & 7); // white + gfx->color[i] |= 0x80 >> (x & 7); + if (color == GFX_BLACK) + gfx->buffer[i] &= ~(0x80 >> (x & 7)); + else if (color == GFX_RED) + gfx->color[i] &= ~(0x80 >> (x & 7)); + } else { + if (color == GFX_WHITE) + gfx->buffer[i] |= 0x80 >> (x & 7); + else + gfx->buffer[i] &= ~(0x80 >> (x & 7)); + } +} + +/**************************************************************************/ +/*! + @brief Draw a line. Bresenham's algorithm - thx wikpedia + @param x0 Start point x coordinate + @param y0 Start point y coordinate + @param x1 End point x coordinate + @param y1 End point y coordinate + @param color 16-bit 5-6-5 Color to draw with +*/ +/**************************************************************************/ +void GFX_drawLine(Adafruit_GFX *gfx, int16_t x0, int16_t y0, int16_t x1, int16_t y1, + uint16_t color) { + int16_t steep = abs(y1 - y0) > abs(x1 - x0); + if (steep) { + _swap_int16_t(x0, y0); + _swap_int16_t(x1, y1); + } + + if (x0 > x1) { + _swap_int16_t(x0, x1); + _swap_int16_t(y0, y1); + } + + int16_t dx, dy; + dx = x1 - x0; + dy = abs(y1 - y0); + + int16_t err = dx / 2; + int16_t ystep; + + if (y0 < y1) { + ystep = 1; + } else { + ystep = -1; + } + + for (; x0 <= x1; x0++) { + if (steep) { + GFX_drawPixel(gfx, y0, x0, color); + } else { + GFX_drawPixel(gfx, x0, y0, color); + } + err -= dy; + if (err < 0) { + y0 += ystep; + err += dx; + } + } +} + +/**************************************************************************/ +/*! + @brief Draw a perfectly vertical line + @param x Top-most x coordinate + @param y Top-most y coordinate + @param h Height in pixels + @param color 16-bit 5-6-5 Color to fill with +*/ +/**************************************************************************/ +void GFX_drawFastVLine(Adafruit_GFX *gfx, int16_t x, int16_t y, int16_t h, + uint16_t color) { + GFX_drawLine(gfx, x, y, x, y + h - 1, color); +} + +/**************************************************************************/ +/*! + @brief Draw a perfectly horizontal line + @param x Left-most x coordinate + @param y Left-most y coordinate + @param w Width in pixels + @param color 16-bit 5-6-5 Color to fill with +*/ +/**************************************************************************/ +void GFX_drawFastHLine(Adafruit_GFX *gfx, int16_t x, int16_t y, int16_t w, + uint16_t color) { + GFX_drawLine(gfx, x, y, x + w - 1, y, color); +} + +/**************************************************************************/ +/*! + @brief Fill a rectangle completely with one color. + @param x Top left corner x coordinate + @param y Top left corner y coordinate + @param w Width in pixels + @param h Height in pixels + @param color 16-bit 5-6-5 Color to fill with +*/ +/**************************************************************************/ +void GFX_fillRect(Adafruit_GFX *gfx, int16_t x, int16_t y, int16_t w, int16_t h, + uint16_t color) { + for (int16_t i = x; i < x + w; i++) { + GFX_drawFastVLine(gfx, i, y, h, color); + } +} + +/**************************************************************************/ +/*! + @brief Fill the screen completely with one color. + @param color 16-bit 5-6-5 Color to fill with +*/ +/**************************************************************************/ +void GFX_fillScreen(Adafruit_GFX *gfx, uint16_t color) { + uint32_t size = ((gfx->WIDTH + 7) / 8) * gfx->page_height; + memset(gfx->buffer, color == GFX_WHITE ? 0xFF : 0x00, size); + if (gfx->color != NULL) + memset(gfx->color, color == GFX_RED ? 0x00 : 0xFF, size); +} + +/**************************************************************************/ +/*! + @brief Draw a circle outline + @param x0 Center-point x coordinate + @param y0 Center-point y coordinate + @param r Radius of circle + @param color 16-bit 5-6-5 Color to draw with +*/ +/**************************************************************************/ +void GFX_drawCircle(Adafruit_GFX *gfx, int16_t x0, int16_t y0, int16_t r, + uint16_t color) { + int16_t f = 1 - r; + int16_t ddF_x = 1; + int16_t ddF_y = -2 * r; + int16_t x = 0; + int16_t y = r; + + GFX_drawPixel(gfx, x0, y0 + r, color); + GFX_drawPixel(gfx, x0, y0 - r, color); + GFX_drawPixel(gfx, x0 + r, y0, color); + GFX_drawPixel(gfx, x0 - r, y0, color); + + while (x < y) { + if (f >= 0) { + y--; + ddF_y += 2; + f += ddF_y; + } + x++; + ddF_x += 2; + f += ddF_x; + + GFX_drawPixel(gfx, x0 + x, y0 + y, color); + GFX_drawPixel(gfx, x0 - x, y0 + y, color); + GFX_drawPixel(gfx, x0 + x, y0 - y, color); + GFX_drawPixel(gfx, x0 - x, y0 - y, color); + GFX_drawPixel(gfx, x0 + y, y0 + x, color); + GFX_drawPixel(gfx, x0 - y, y0 + x, color); + GFX_drawPixel(gfx, x0 + y, y0 - x, color); + GFX_drawPixel(gfx, x0 - y, y0 - x, color); + } +} + +/**************************************************************************/ +/*! + @brief Quarter-circle drawer, used to do circles and roundrects + @param x0 Center-point x coordinate + @param y0 Center-point y coordinate + @param r Radius of circle + @param cornername Mask bit #1 or bit #2 to indicate which quarters of + the circle we're doing + @param color 16-bit 5-6-5 Color to draw with +*/ +/**************************************************************************/ +void GFX_drawCircleHelper(Adafruit_GFX *gfx, int16_t x0, int16_t y0, int16_t r, + uint8_t cornername, uint16_t color) { + int16_t f = 1 - r; + int16_t ddF_x = 1; + int16_t ddF_y = -2 * r; + int16_t x = 0; + int16_t y = r; + + while (x < y) { + if (f >= 0) { + y--; + ddF_y += 2; + f += ddF_y; + } + x++; + ddF_x += 2; + f += ddF_x; + if (cornername & 0x4) { + GFX_drawPixel(gfx, x0 + x, y0 + y, color); + GFX_drawPixel(gfx, x0 + y, y0 + x, color); + } + if (cornername & 0x2) { + GFX_drawPixel(gfx, x0 + x, y0 - y, color); + GFX_drawPixel(gfx, x0 + y, y0 - x, color); + } + if (cornername & 0x8) { + GFX_drawPixel(gfx, x0 - y, y0 + x, color); + GFX_drawPixel(gfx, x0 - x, y0 + y, color); + } + if (cornername & 0x1) { + GFX_drawPixel(gfx, x0 - y, y0 - x, color); + GFX_drawPixel(gfx, x0 - x, y0 - y, color); + } + } +} + +/**************************************************************************/ +/*! + @brief Draw a circle with filled color + @param x0 Center-point x coordinate + @param y0 Center-point y coordinate + @param r Radius of circle + @param color 16-bit 5-6-5 Color to fill with +*/ +/**************************************************************************/ +void GFX_fillCircle(Adafruit_GFX *gfx, int16_t x0, int16_t y0, int16_t r, + uint16_t color) { + GFX_drawFastVLine(gfx, x0, y0 - r, 2 * r + 1, color); + GFX_fillCircleHelper(gfx, x0, y0, r, 3, 0, color); +} + +/**************************************************************************/ +/*! + @brief Quarter-circle drawer with fill, used for circles and roundrects + @param x0 Center-point x coordinate + @param y0 Center-point y coordinate + @param r Radius of circle + @param corners Mask bits indicating which quarters we're doing + @param delta Offset from center-point, used for round-rects + @param color 16-bit 5-6-5 Color to fill with +*/ +/**************************************************************************/ +void GFX_fillCircleHelper(Adafruit_GFX *gfx, int16_t x0, int16_t y0, int16_t r, + uint8_t corners, int16_t delta, uint16_t color) { + + int16_t f = 1 - r; + int16_t ddF_x = 1; + int16_t ddF_y = -2 * r; + int16_t x = 0; + int16_t y = r; + int16_t px = x; + int16_t py = y; + + delta++; // Avoid some +1's in the loop + + while (x < y) { + if (f >= 0) { + y--; + ddF_y += 2; + f += ddF_y; + } + x++; + ddF_x += 2; + f += ddF_x; + // These checks avoid double-drawing certain lines, important + // for the SSD1306 library which has an INVERT drawing mode. + if (x < (y + 1)) { + if (corners & 1) + GFX_drawFastVLine(gfx, x0 + x, y0 - y, 2 * y + delta, color); + if (corners & 2) + GFX_drawFastVLine(gfx, x0 - x, y0 - y, 2 * y + delta, color); + } + if (y != py) { + if (corners & 1) + GFX_drawFastVLine(gfx, x0 + py, y0 - px, 2 * px + delta, color); + if (corners & 2) + GFX_drawFastVLine(gfx, x0 - py, y0 - px, 2 * px + delta, color); + py = y; + } + px = x; + } +} + +/**************************************************************************/ +/*! + @brief Draw a rectangle with no fill color + @param x Top left corner x coordinate + @param y Top left corner y coordinate + @param w Width in pixels + @param h Height in pixels + @param color 16-bit 5-6-5 Color to draw with +*/ +/**************************************************************************/ +void GFX_drawRect(Adafruit_GFX *gfx, int16_t x, int16_t y, int16_t w, int16_t h, + uint16_t color) { + GFX_drawFastHLine(gfx, x, y, w, color); + GFX_drawFastHLine(gfx, x, y + h - 1, w, color); + GFX_drawFastVLine(gfx, x, y, h, color); + GFX_drawFastVLine(gfx, x + w - 1, y, h, color); +} + +/**************************************************************************/ +/*! + @brief Draw a rounded rectangle with no fill color + @param x Top left corner x coordinate + @param y Top left corner y coordinate + @param w Width in pixels + @param h Height in pixels + @param r Radius of corner rounding + @param color 16-bit 5-6-5 Color to draw with +*/ +/**************************************************************************/ +void GFX_drawRoundRect(Adafruit_GFX *gfx, int16_t x, int16_t y, int16_t w, + int16_t h, int16_t r, uint16_t color) { + int16_t max_radius = ((w < h) ? w : h) / 2; // 1/2 minor axis + if (r > max_radius) + r = max_radius; + // smarter version + GFX_drawFastHLine(gfx, x + r, y, w - 2 * r, color); // Top + GFX_drawFastHLine(gfx, x + r, y + h - 1, w - 2 * r, color); // Bottom + GFX_drawFastVLine(gfx, x, y + r, h - 2 * r, color); // Left + GFX_drawFastVLine(gfx, x + w - 1, y + r, h - 2 * r, color); // Right + // draw four corners + GFX_drawCircleHelper(gfx, x + r, y + r, r, 1, color); + GFX_drawCircleHelper(gfx, x + w - r - 1, y + r, r, 2, color); + GFX_drawCircleHelper(gfx, x + w - r - 1, y + h - r - 1, r, 4, color); + GFX_drawCircleHelper(gfx, x + r, y + h - r - 1, r, 8, color); +} + +/**************************************************************************/ +/*! + @brief Draw a rounded rectangle with fill color + @param x Top left corner x coordinate + @param y Top left corner y coordinate + @param w Width in pixels + @param h Height in pixels + @param r Radius of corner rounding + @param color 16-bit 5-6-5 Color to draw/fill with +*/ +/**************************************************************************/ +void GFX_fillRoundRect(Adafruit_GFX *gfx, int16_t x, int16_t y, int16_t w, + int16_t h, int16_t r, uint16_t color) { + int16_t max_radius = ((w < h) ? w : h) / 2; // 1/2 minor axis + if (r > max_radius) + r = max_radius; + // smarter version + GFX_fillRect(gfx, x + r, y, w - 2 * r, h, color); + // draw four corners + GFX_fillCircleHelper(gfx, x + w - r - 1, y + r, r, 1, h - 2 * r - 1, color); + GFX_fillCircleHelper(gfx, x + r, y + r, r, 2, h - 2 * r - 1, color); +} + +/**************************************************************************/ +/*! + @brief Draw a triangle with no fill color + @param x0 Vertex #0 x coordinate + @param y0 Vertex #0 y coordinate + @param x1 Vertex #1 x coordinate + @param y1 Vertex #1 y coordinate + @param x2 Vertex #2 x coordinate + @param y2 Vertex #2 y coordinate + @param color 16-bit 5-6-5 Color to draw with +*/ +/**************************************************************************/ +void GFX_drawTriangle(Adafruit_GFX *gfx, int16_t x0, int16_t y0, int16_t x1, + int16_t y1, int16_t x2, int16_t y2, uint16_t color) { + GFX_drawLine(gfx, x0, y0, x1, y1, color); + GFX_drawLine(gfx, x1, y1, x2, y2, color); + GFX_drawLine(gfx, x2, y2, x0, y0, color); +} + +/**************************************************************************/ +/*! + @brief Draw a triangle with color-fill + @param x0 Vertex #0 x coordinate + @param y0 Vertex #0 y coordinate + @param x1 Vertex #1 x coordinate + @param y1 Vertex #1 y coordinate + @param x2 Vertex #2 x coordinate + @param y2 Vertex #2 y coordinate + @param color 16-bit 5-6-5 Color to fill/draw with +*/ +/**************************************************************************/ +void GFX_fillTriangle(Adafruit_GFX *gfx, int16_t x0, int16_t y0, int16_t x1, + int16_t y1, int16_t x2, int16_t y2, uint16_t color) { + + int16_t a, b, y, last; + + // Sort coordinates by Y order (y2 >= y1 >= y0) + if (y0 > y1) { + _swap_int16_t(y0, y1); + _swap_int16_t(x0, x1); + } + if (y1 > y2) { + _swap_int16_t(y2, y1); + _swap_int16_t(x2, x1); + } + if (y0 > y1) { + _swap_int16_t(y0, y1); + _swap_int16_t(x0, x1); + } + + if (y0 == y2) { // Handle awkward all-on-same-line case as its own thing + a = b = x0; + if (x1 < a) + a = x1; + else if (x1 > b) + b = x1; + if (x2 < a) + a = x2; + else if (x2 > b) + b = x2; + GFX_drawFastHLine(gfx, a, y0, b - a + 1, color); + return; + } + + int16_t dx01 = x1 - x0, dy01 = y1 - y0, dx02 = x2 - x0, dy02 = y2 - y0, + dx12 = x2 - x1, dy12 = y2 - y1; + int32_t sa = 0, sb = 0; + + // For upper part of triangle, find scanline crossings for segments + // 0-1 and 0-2. If y1=y2 (flat-bottomed triangle), the scanline y1 + // is included here (and second loop will be skipped, avoiding a /0 + // error there), otherwise scanline y1 is skipped here and handled + // in the second loop...which also avoids a /0 error here if y0=y1 + // (flat-topped triangle). + if (y1 == y2) + last = y1; // Include y1 scanline + else + last = y1 - 1; // Skip it + + for (y = y0; y <= last; y++) { + a = x0 + sa / dy01; + b = x0 + sb / dy02; + sa += dx01; + sb += dx02; + /* longhand: + a = x0 + (x1 - x0) * (y - y0) / (y1 - y0); + b = x0 + (x2 - x0) * (y - y0) / (y2 - y0); + */ + if (a > b) + _swap_int16_t(a, b); + GFX_drawFastHLine(gfx, a, y, b - a + 1, color); + } + + // For lower part of triangle, find scanline crossings for segments + // 0-2 and 1-2. This loop is skipped if y1=y2. + sa = (int32_t)dx12 * (y - y1); + sb = (int32_t)dx02 * (y - y0); + for (; y <= y2; y++) { + a = x1 + sa / dy12; + b = x0 + sb / dy02; + sa += dx12; + sb += dx02; + /* longhand: + a = x1 + (x2 - x1) * (y - y1) / (y2 - y1); + b = x0 + (x2 - x0) * (y - y0) / (y2 - y0); + */ + if (a > b) + _swap_int16_t(a, b); + GFX_drawFastHLine(gfx, a, y, b - a + 1, color); + } +} + +/**************************************************************************/ +/*! + @brief Draw a RAM-resident 1-bit image at the specified (x,y) position, + using the specified foreground color (unset bits are transparent). + @param x Top left corner x coordinate + @param y Top left corner y coordinate + @param bitmap byte array with monochrome bitmap + @param w Width of bitmap in pixels + @param h Height of bitmap in pixels + @param color 16-bit 5-6-5 Color to draw with + @param invert When true, will invert the bitmap +*/ +/**************************************************************************/ +void GFX_drawBitmap(Adafruit_GFX *gfx, int16_t x, int16_t y, const uint8_t bitmap[], + int16_t w, int16_t h, uint16_t color, bool invert) { + + int16_t byteWidth = (w + 7) / 8; // Bitmap scanline pad = whole byte + uint8_t byte = 0; + + for (int16_t j = 0; j < h; j++) { + for (int16_t i = 0; i < w; i++) { + if (i & 7) + byte <<= 1; + else + byte = bitmap[j * byteWidth + i / 8]; + if (((byte & 0x80) == 0x80) ^ invert) + GFX_drawPixel(gfx, x + i, y + j, color); + } + } +} + +/* + + U8g2_for_Adafruit_GFX.cpp + + Add unicode support and U8g2 fonts to Adafruit GFX libraries. + + U8g2 for Adafruit GFX Lib (https://github.com/olikraus/U8g2_for_Adafruit_GFX) + + Copyright (c) 2018, olikraus@gmail.com + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list + of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or other + materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +void GFX_setCursor(Adafruit_GFX *gfx, int16_t x, int16_t y) { + gfx->tx = x; + gfx->ty = y; + gfx->utf8_state = 0; +} + +void GFX_setFont(Adafruit_GFX *gfx, const uint8_t *font) { + u8g2_SetFont(&gfx->u8g2, font); +} + +void GFX_setFontMode(Adafruit_GFX *gfx, uint8_t is_transparent) { + u8g2_SetFontMode(&gfx->u8g2, is_transparent); +} + +void GFX_setFontDirection(Adafruit_GFX *gfx, GFX_Rotate d) { + u8g2_SetFontDirection(&gfx->u8g2, (uint8_t)d); +} + +void GFX_setTextColor(Adafruit_GFX *gfx, uint16_t fg, uint16_t bg) { + u8g2_SetForegroundColor(&gfx->u8g2, fg); + u8g2_SetBackgroundColor(&gfx->u8g2, bg); +} + +int8_t GFX_getFontAscent(Adafruit_GFX *gfx) { + return gfx->u8g2.font_info.ascent_A; +} + +int8_t GFX_getFontDescent(Adafruit_GFX *gfx) { + return gfx->u8g2.font_info.descent_g; +} + +int16_t GFX_drawGlyph(Adafruit_GFX *gfx, int16_t x, int16_t y, uint16_t e) { + return u8g2_DrawGlyph(&gfx->u8g2, x, y, e); +} + +int16_t GFX_drawStr(Adafruit_GFX *gfx, int16_t x, int16_t y, const char *s) { + return u8g2_DrawStr(&gfx->u8g2, x, y, s); +} + +static uint16_t utf8_next(Adafruit_GFX *gfx, uint8_t b) +{ + if ( b == 0 ) /* '\n' terminates the string to support the string list procedures */ + return 0x0ffff; /* end of string detected, pending UTF8 is discarded */ + if ( gfx->utf8_state == 0 ) + { + if ( b >= 0xfc ) /* 6 byte sequence */ + { + gfx->utf8_state = 5; + b &= 1; + } + else if ( b >= 0xf8 ) + { + gfx->utf8_state = 4; + b &= 3; + } + else if ( b >= 0xf0 ) + { + gfx->utf8_state = 3; + b &= 7; + } + else if ( b >= 0xe0 ) + { + gfx->utf8_state = 2; + b &= 15; + } + else if ( b >= 0xc0 ) + { + gfx->utf8_state = 1; + b &= 0x01f; + } + else + { + /* do nothing, just use the value as encoding */ + return b; + } + gfx->encoding = b; + return 0x0fffe; + } + else + { + gfx->utf8_state--; + /* The case b < 0x080 (an illegal UTF8 encoding) is not checked here. */ + gfx->encoding<<=6; + b &= 0x03f; + gfx->encoding |= b; + if ( gfx->utf8_state != 0 ) + return 0x0fffe; /* nothing to do yet */ + } + return gfx->encoding; +} + +int16_t GFX_drawUTF8(Adafruit_GFX *gfx, int16_t x, int16_t y, const char *str) +{ + uint16_t e; + int16_t delta, sum; + + gfx->utf8_state = 0; + sum = 0; + for(;;) + { + e = utf8_next(gfx, (uint8_t)*str); + if ( e == 0x0ffff ) + break; + str++; + if ( e != 0x0fffe ) + { + delta = u8g2_DrawGlyph(&gfx->u8g2, x, y, e); + + switch(gfx->u8g2.font_decode.dir) + { + case 0: + x += delta; + break; + case 1: + y += delta; + break; + case 2: + x -= delta; + break; + case 3: + y -= delta; + break; + } + + sum += delta; + } + } + return sum; +} + +int16_t GFX_getUTF8Width(Adafruit_GFX *gfx, const char *str) +{ + uint16_t e; + int16_t dx, w; + + gfx->u8g2.font_decode.glyph_width = 0; + gfx->utf8_state = 0; + w = 0; + dx = 0; + for(;;) + { + e = utf8_next(gfx, (uint8_t)*str); + if ( e == 0x0ffff ) + break; + str++; + if ( e != 0x0fffe ) + { + dx = u8g2_GetGlyphWidth(&gfx->u8g2, e); + w += dx; + } + } + /* adjust the last glyph, check for issue #16: do not adjust if width is 0 */ + if ( gfx->u8g2.font_decode.glyph_width != 0 ) + { + w -= dx; + w += gfx->u8g2.font_decode.glyph_width; /* the real pixel width of the glyph, sideeffect of GetGlyphWidth */ + /* issue #46: we have to add the x offset also */ + w += gfx->u8g2.glyph_x_offset; /* this value is set as a side effect of u8g2_GetGlyphWidth() */ + } + + return w; +} + +size_t GFX_print(Adafruit_GFX *gfx, const char c) { + int16_t delta; + uint16_t e = utf8_next(gfx, (uint8_t)c); + if ( e == '\n' ) + { + gfx->tx = 0; + gfx->ty += gfx->u8g2.font_info.ascent_para - gfx->u8g2.font_info.descent_para; + } + else if ( e == '\r' ) + { + gfx->tx = 0; + } + else if ( e < 0x0fffe ) + { + delta = u8g2_DrawGlyph(&gfx->u8g2, gfx->tx, gfx->ty, e); + switch(gfx->u8g2.font_decode.dir) + { + case 0: + gfx->tx += delta; + break; + case 1: + gfx->ty += delta; + break; + case 2: + gfx->tx -= delta; + break; + case 3: + gfx->ty -= delta; + break; + } + } + return 1; +} + +size_t GFX_write(Adafruit_GFX *gfx, const char *buffer, size_t size) { + size_t cnt = 0; + while( size > 0 ) { + cnt += GFX_print(gfx, *buffer++); + size--; + } + return cnt; +} + +size_t GFX_printf(Adafruit_GFX *gfx, const char* format, ...) { + va_list va; + char tmp[64] = {0}; + char *buf = tmp; + size_t len; + + va_start(va, format); + len = vsnprintf(tmp, sizeof(tmp), format, va); + va_end(va); + + if (len > sizeof(tmp) - 1) + { + buf = malloc(len + 1); + if (buf == NULL) + return 0; + va_start(va, format); + vsnprintf(buf, len + 1, format, va); + va_end(va); + } + + return GFX_write(gfx, buf, len); +} diff --git a/GUI/Adafruit_GFX.h b/GUI/Adafruit_GFX.h new file mode 100644 index 0000000..5986dad --- /dev/null +++ b/GUI/Adafruit_GFX.h @@ -0,0 +1,91 @@ +#ifndef _ADAFRUIT_GFX_H +#define _ADAFRUIT_GFX_H + +#include +#include +#include +#include "u8g2_font.h" + +#define GFX_BLACK 0x0000 +#define GFX_WHITE 0xFFFF +#define GFX_RED 0xF800 + +typedef void (*buffer_callback)(uint8_t *black, uint8_t *color, uint16_t x, uint16_t y, uint16_t w, uint16_t h); + +typedef enum { + GFX_ROTATE_0 = 0, + GFX_ROTATE_90 = 1, + GFX_ROTATE_180 = 2, + GFX_ROTATE_270 = 3, +} GFX_Rotate; + +// GRAPHICS CONTEXT +typedef struct { + int16_t WIDTH; ///< This is the 'raw' display width - never changes + int16_t HEIGHT; ///< This is the 'raw' display height - never changes + int16_t _width; ///< Display width as modified by current rotation + int16_t _height; ///< Display height as modified by current rotation + GFX_Rotate rotation; ///< Display rotation (0 thru 3) + + u8g2_font_t u8g2; + int16_t tx, ty; // current position for the print command + uint16_t encoding; // the unicode, detected by the utf-8 decoder + uint8_t utf8_state; // current state of the utf-8 decoder, contains the remaining bytes for a detected unicode glyph + + uint8_t *buffer; // black pixel buffer + uint8_t *color; // color pixel buffer + int16_t page_height; + int16_t current_page; + int16_t total_pages; +} Adafruit_GFX; + +// CONTROL API +void GFX_begin(Adafruit_GFX *gfx, int16_t w, int16_t h, int16_t buffer_height); +void GFX_begin_3c(Adafruit_GFX *gfx, int16_t w, int16_t h, int16_t buffer_height); +void GFX_setRotation(Adafruit_GFX *gfx, GFX_Rotate r); +void GFX_firstPage(Adafruit_GFX *gfx); +bool GFX_nextPage(Adafruit_GFX *gfx, buffer_callback callback); +void GFX_end(Adafruit_GFX *gfx); + +// DRAW API +void GFX_drawPixel(Adafruit_GFX *gfx, int16_t x, int16_t y, uint16_t color); +void GFX_drawLine(Adafruit_GFX *gfx, int16_t x0, int16_t y0, int16_t x1, int16_t y1, uint16_t color); +void GFX_drawFastVLine(Adafruit_GFX *gfx, int16_t x, int16_t y, int16_t h, uint16_t color); +void GFX_drawFastHLine(Adafruit_GFX *gfx, int16_t x, int16_t y, int16_t w, uint16_t color); +void GFX_fillRect(Adafruit_GFX *gfx, int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color); +void GFX_fillScreen(Adafruit_GFX *gfx, uint16_t color); +void GFX_drawRect(Adafruit_GFX *gfx, int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color); +void GFX_drawCircle(Adafruit_GFX *gfx, int16_t x0, int16_t y0, int16_t r, uint16_t color); +void GFX_drawCircleHelper(Adafruit_GFX *gfx, int16_t x0, int16_t y0, int16_t r, uint8_t cornername, + uint16_t color); +void GFX_fillCircle(Adafruit_GFX *gfx, int16_t x0, int16_t y0, int16_t r, uint16_t color); +void GFX_fillCircleHelper(Adafruit_GFX *gfx, int16_t x0, int16_t y0, int16_t r, uint8_t cornername, + int16_t delta, uint16_t color); +void GFX_drawTriangle(Adafruit_GFX *gfx, int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, + int16_t y2, uint16_t color); +void GFX_fillTriangle(Adafruit_GFX *gfx, int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, + int16_t y2, uint16_t color); +void GFX_drawRoundRect(Adafruit_GFX *gfx, int16_t x0, int16_t y0, int16_t w, int16_t h, + int16_t radius, uint16_t color); +void GFX_fillRoundRect(Adafruit_GFX *gfx, int16_t x0, int16_t y0, int16_t w, int16_t h, + int16_t radius, uint16_t color); +void GFX_drawBitmap(Adafruit_GFX *gfx, int16_t x, int16_t y, const uint8_t bitmap[], int16_t w, + int16_t h, uint16_t color, bool invert); + +// U8G2 FONT API +void GFX_setCursor(Adafruit_GFX *gfx, int16_t x, int16_t y); +void GFX_setFont(Adafruit_GFX *gfx, const uint8_t *font); +void GFX_setFontMode(Adafruit_GFX *gfx, uint8_t is_transparent); +void GFX_setFontDirection(Adafruit_GFX *gfx, GFX_Rotate d); +void GFX_setTextColor(Adafruit_GFX *gfx, uint16_t fg, uint16_t bg); +int8_t GFX_getFontAscent(Adafruit_GFX *gfx); +int8_t GFX_getFontDescent(Adafruit_GFX *gfx); +int16_t GFX_drawGlyph(Adafruit_GFX *gfx, int16_t x, int16_t y, uint16_t e); +int16_t GFX_drawStr(Adafruit_GFX *gfx, int16_t x, int16_t y, const char *s); +int16_t GFX_drawUTF8(Adafruit_GFX *gfx, int16_t x, int16_t y, const char *str); +int16_t GFX_getUTF8Width(Adafruit_GFX *gfx, const char *str); +size_t GFX_print(Adafruit_GFX *gfx, const char c); +size_t GFX_write(Adafruit_GFX *gfx, const char *buffer, size_t size); +size_t GFX_printf(Adafruit_GFX *gfx, const char* format, ...); + +#endif // _ADAFRUIT_GFX_H diff --git a/GUI/Calendar.c b/GUI/Calendar.c new file mode 100644 index 0000000..b9b8d6d --- /dev/null +++ b/GUI/Calendar.c @@ -0,0 +1,106 @@ +#include "Adafruit_GFX.h" +#include "fonts.h" +#include "EPD_driver.h" +#include "Lunar.h" +#include "Calendar.h" + +#define PAGE_HEIGHT 32 + +static void DrawDateHeader(Adafruit_GFX *gfx, int16_t x, int16_t y, tm_t *tm, struct Lunar_Date *Lunar) +{ + GFX_setCursor(gfx, x, y); + GFX_setFont(gfx, u8g2_font_wqy12b_t_lunar); + GFX_setTextColor(gfx, GFX_BLACK, GFX_WHITE); + GFX_printf(gfx, "%d年%02d月%02d日 星期%s", tm->tm_year + YEAR0, tm->tm_mon + 1, tm->tm_mday, Lunar_DayString[tm->tm_wday]); + + LUNAR_SolarToLunar(Lunar, tm->tm_year + YEAR0, tm->tm_mon + 1, tm->tm_mday); + GFX_setFont(gfx, u8g2_font_wqy9_t_lunar); + GFX_setCursor(gfx, x + 226, y); + GFX_printf(gfx, "农历: %s%s%s %s%s[%s]年", Lunar_MonthLeapString[Lunar->IsLeap], Lunar_MonthString[Lunar->Month], + Lunar_DateString[Lunar->Date], Lunar_StemStrig[LUNAR_GetStem(Lunar)], + Lunar_BranchStrig[LUNAR_GetBranch(Lunar)], Lunar_ZodiacString[LUNAR_GetZodiac(Lunar)]); +} + +static void DrawWeekHeader(Adafruit_GFX *gfx, int16_t x, int16_t y) +{ + GFX_setTextColor(gfx, GFX_WHITE, GFX_RED); + GFX_fillRect(gfx, x, y, 380, 18, GFX_RED); + for (int i = 0; i < 7; i++) + { + GFX_setCursor(gfx, x + 15 + i * 55, y + 14); + GFX_printf(gfx, "%s", Lunar_DayString[i]); + } +} + +static void DrawMonthDay(Adafruit_GFX *gfx, int16_t x, int16_t y, tm_t *tm, struct Lunar_Date *Lunar, uint8_t day) +{ + if (day == tm->tm_mday) + { + GFX_fillCircle(gfx, x + 10, y + 9, 22, GFX_RED); + GFX_setTextColor(gfx, GFX_WHITE, GFX_RED); + } + else + { + GFX_setTextColor(gfx, GFX_BLACK, GFX_WHITE); + } + + GFX_setFont(gfx, u8g2_font_wqy12b_t_lunar); + GFX_setCursor(gfx, x + 2, y + 4); + GFX_printf(gfx, "%d", day); + + GFX_setFont(gfx, u8g2_font_wqy9_t_lunar); + GFX_setCursor(gfx, x, y + 24); + uint8_t JQdate; + if (GetJieQi(tm->tm_year + YEAR0, tm->tm_mon + 1, day, &JQdate) && JQdate == day) + { + uint8_t JQ = (tm->tm_mon + 1 - 1) * 2; + if (day >= 15) + JQ++; + if (day != tm->tm_mday) GFX_setTextColor(gfx, GFX_RED, GFX_WHITE); + GFX_printf(gfx, "%s", JieQiStr[JQ]); + } + else + { + LUNAR_SolarToLunar(Lunar, tm->tm_year + YEAR0, tm->tm_mon + 1, day); + if (Lunar->Date == 1) + GFX_printf(gfx, "%s", Lunar_MonthString[Lunar->Month]); + else + GFX_printf(gfx, "%s", Lunar_DateString[Lunar->Date]); + } +} + +void DrawCalendar(uint32_t timestamp) +{ + tm_t tm = {0}; + struct Lunar_Date Lunar; + epd_driver_t *driver = epd_driver_get(); + + transformTime(timestamp, &tm); + + uint8_t firstDayWeek = get_first_day_week(tm.tm_year + YEAR0, tm.tm_mon + 1); + uint8_t monthMaxDays = thisMonthMaxDays(tm.tm_year + YEAR0, tm.tm_mon + 1); + + Adafruit_GFX gfx; + + if (driver->id == EPD_DRIVER_4IN2B_V2) + GFX_begin_3c(&gfx, driver->width, driver->height, PAGE_HEIGHT); + else + GFX_begin(&gfx, driver->width, driver->height, PAGE_HEIGHT); + + GFX_firstPage(&gfx); + do { + GFX_fillScreen(&gfx, GFX_WHITE); + + DrawDateHeader(&gfx, 10, 22, &tm, &Lunar); + DrawWeekHeader(&gfx, 10, 26); + + for (uint8_t i = 0; i < monthMaxDays; i++) + { + DrawMonthDay(&gfx, 22 + (firstDayWeek + i) % 7 * 55, 60 + (firstDayWeek + i) / 7 * 50, &tm, &Lunar, i + 1); + } + } while(GFX_nextPage(&gfx, driver->write_image)); + + GFX_end(&gfx); + + driver->display(); +} diff --git a/GUI/Calendar.h b/GUI/Calendar.h new file mode 100644 index 0000000..962b6c3 --- /dev/null +++ b/GUI/Calendar.h @@ -0,0 +1,8 @@ +#ifndef __CALENDAR_H +#define __CALENDAR_H + +#include + +void DrawCalendar(uint32_t timestamp); + +#endif diff --git a/GUI/Lunar.c b/GUI/Lunar.c new file mode 100644 index 0000000..f0cad67 --- /dev/null +++ b/GUI/Lunar.c @@ -0,0 +1,550 @@ +#include "Lunar.h" + +const char Lunar_MonthString[13][7] = { + "未知", + "正月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", + "冬月", "腊月"}; + +const char Lunar_MonthLeapString[2][4] = { + " ", + "闰"}; + +const char Lunar_DateString[31][7] = { + "未知", + "初一", "初二", "初三", "初四", "初五", "初六", "初七", "初八", "初九", "初十", + "十一", "十二", "十三", "十四", "十五", "十六", "十七", "十八", "十九", "二十", + "廿一", "廿二", "廿三", "廿四", "廿五", "廿六", "廿七", "廿八", "廿九", "三十"}; + +const char Lunar_DayString[7][4] = { + "日", + "一", "二", "三", "四", "五", "六"}; + +const char Lunar_ZodiacString[12][4] = { + "猴", "鸡", "狗", "猪", "鼠", "牛", "虎", "兔", "龙", "蛇", "马", "羊"}; + +const char Lunar_StemStrig[10][4] = { + "庚", "辛", "壬", "癸", "甲", "乙", "丙", "丁", "戊", "已"}; + +const char Lunar_BranchStrig[12][4] = { + "申", "酉", "戌", "亥", "子", "丑", "寅", "卯", "辰", "巳", "午", "未"}; + +/* 2000 ~ 2199 */ +const uint32_t lunar_month_days[] = { + 1997, + 0x0000B26D, 0x0000125C, 0x0000192C, 0x00009A95, 0x00001A94, 0x00001B4A, 0x00004B55, 0x00000AD4, 0x0000F55B, + 0x000004BA, 0x0000125A, 0x0000B92B, 0x0000152A, 0x00001694, 0x000096AA, 0x000015AA, 0x00012AB5, 0x00000974, + 0x000014B6, 0x0000CA57, 0x00000A56, 0x00001526, 0x00008E95, 0x00000D54, 0x000015AA, 0x000049B5, 0x0000096C, + 0x0000D4AE, 0x0000149C, 0x00001A4C, 0x0000BD26, 0x00001AA6, 0x00000B54, 0x00006D6A, 0x000012DA, 0x0001695D, + 0x0000095A, 0x0000149A, 0x0000DA4B, 0x00001A4A, 0x00001AA4, 0x0000BB54, 0x000016B4, 0x00000ADA, 0x0000495B, + 0x00000936, 0x0000F497, 0x00001496, 0x0000154A, 0x0000B6A5, 0x00000DA4, 0x000015B4, 0x00006AB6, 0x0000126E, + 0x0001092F, 0x0000092E, 0x00000C96, 0x0000CD4A, 0x00001D4A, 0x00000D64, 0x0000956C, 0x0000155C, 0x0000125C, + 0x0000792E, 0x0000192C, 0x0000FA95, 0x00001A94, 0x00001B4A, 0x0000AB55, 0x00000AD4, 0x000014DA, 0x00008A5D, + 0x00000A5A, 0x0001152B, 0x0000152A, 0x00001694, 0x0000D6AA, 0x000015AA, 0x00000AB4, 0x000094BA, 0x000014B6, + 0x00000A56, 0x00007527, 0x00000D26, 0x0000EE53, 0x00000D54, 0x000015AA, 0x0000A9B5, 0x0000096C, 0x000014AE, + 0x00008A4E, 0x00001A4C, 0x00011D26, 0x00001AA4, 0x00001B54, 0x0000CD6A, 0x00000ADA, 0x0000095C, 0x0000949D, + 0x0000149A, 0x00001A2A, 0x00005B25, 0x00001AA4, 0x0000FB52, 0x000016B4, 0x00000ABA, 0x0000A95B, 0x00000936, + 0x00001496, 0x00009A4B, 0x0000154A, 0x000136A5, 0x00000DA4, 0x000015AC, 0x0000CAB6, 0x0000126E, 0x0000092E, + 0x00008C97, 0x00000A96, 0x00000D4A, 0x00006DA5, 0x00000D54, 0x0000F56A, 0x0000155A, 0x00000A5C, 0x0000B92E, + 0x0000152C, 0x00001A94, 0x00009D4A, 0x00001B2A, 0x00016B55, 0x00000AD4, 0x000014DA, 0x0000CA5D, 0x00000A5A, + 0x0000151A, 0x0000BA95, 0x00001654, 0x000016AA, 0x00004AD5, 0x00000AB4, 0x0000F4BA, 0x000014B6, 0x00000A56, + 0x0000B517, 0x00000D16, 0x00000E52, 0x000096AA, 0x00000D6A, 0x000165B5, 0x0000096C, 0x000014AE, 0x0000CA2E, + 0x00001A2C, 0x00001D16, 0x0000AD52, 0x00001B52, 0x00000B6A, 0x0000656D, 0x0000055C, 0x0000F45D, 0x0000145A, + 0x00001A2A, 0x0000DA95, 0x000016A4, 0x00001AD2, 0x00008B5A, 0x00000AB6, 0x0001455B, 0x000008B6, 0x00001456, + 0x0000D52B, 0x0000152A, 0x00001694, 0x0000B6AA, 0x000015AA, 0x00000AB6, 0x000064B7, 0x000008AE, 0x0000EC57, + 0x00000A56, 0x00000D2A, 0x0000CD95, 0x00000B54, 0x0000156A, 0x00008A6D, 0x0000095C, 0x000014AE, 0x00004A56, + 0x00001A54, 0x0000DD2A, 0x00001AAA, 0x00000B54, 0x0000B56A, 0x000014DA, 0x0000095C, 0x000074AB, 0x0000149A, + 0x0000FA4B, 0x00001652, 0x000016AA, 0x0000CAD5, 0x000005B4}; + +/* 2000 ~ 2199 */ +const uint32_t solar_1_1[] = { + 1997, + 0x000F9C3C, 0x000F9E50, 0x000FA045, 0x000FA238, 0x000FA44C, 0x000FA641, 0x000FA836, 0x000FAA49, 0x000FAC3D, + 0x000FAE52, 0x000FB047, 0x000FB23A, 0x000FB44E, 0x000FB643, 0x000FB837, 0x000FBA4A, 0x000FBC3F, 0x000FBE53, + 0x000FC048, 0x000FC23C, 0x000FC450, 0x000FC645, 0x000FC839, 0x000FCA4C, 0x000FCC41, 0x000FCE36, 0x000FD04A, + 0x000FD23D, 0x000FD451, 0x000FD646, 0x000FD83A, 0x000FDA4D, 0x000FDC43, 0x000FDE37, 0x000FE04B, 0x000FE23F, + 0x000FE453, 0x000FE648, 0x000FE83C, 0x000FEA4F, 0x000FEC44, 0x000FEE38, 0x000FF04C, 0x000FF241, 0x000FF436, + 0x000FF64A, 0x000FF83E, 0x000FFA51, 0x000FFC46, 0x000FFE3A, 0x0010004E, 0x00100242, 0x00100437, 0x0010064B, + 0x00100841, 0x00100A53, 0x00100C48, 0x00100E3C, 0x0010104F, 0x00101244, 0x00101438, 0x0010164C, 0x00101842, + 0x00101A35, 0x00101C49, 0x00101E3D, 0x00102051, 0x00102245, 0x0010243A, 0x0010264E, 0x00102843, 0x00102A37, + 0x00102C4B, 0x00102E3F, 0x00103053, 0x00103247, 0x0010343B, 0x0010364F, 0x00103845, 0x00103A38, 0x00103C4C, + 0x00103E42, 0x00104036, 0x00104249, 0x0010443D, 0x00104651, 0x00104846, 0x00104A3A, 0x00104C4E, 0x00104E43, + 0x00105038, 0x0010524A, 0x0010543E, 0x00105652, 0x00105847, 0x00105A3B, 0x00105C4F, 0x00105E45, 0x00106039, + 0x0010624C, 0x00106441, 0x00106635, 0x00106849, 0x00106A3D, 0x00106C51, 0x00106E47, 0x0010703C, 0x0010724F, + 0x00107444, 0x00107638, 0x0010784C, 0x00107A3F, 0x00107C53, 0x00107E48, 0x0010803D, 0x00108250, 0x00108446, + 0x0010863A, 0x0010884E, 0x00108A42, 0x00108C36, 0x00108E4A, 0x0010903E, 0x00109251, 0x00109447, 0x0010963B, + 0x0010984F, 0x00109A43, 0x00109C37, 0x00109E4B, 0x0010A041, 0x0010A253, 0x0010A448, 0x0010A63D, 0x0010A851, + 0x0010AA45, 0x0010AC39, 0x0010AE4D, 0x0010B042, 0x0010B236, 0x0010B44A, 0x0010B63E, 0x0010B852, 0x0010BA47, + 0x0010BC3B, 0x0010BE4F, 0x0010C044, 0x0010C237, 0x0010C44B, 0x0010C641, 0x0010C854, 0x0010CA48, 0x0010CC3D, + 0x0010CE50, 0x0010D045, 0x0010D239, 0x0010D44C, 0x0010D642, 0x0010D837, 0x0010DA4A, 0x0010DC3E, 0x0010DE52, + 0x0010E047, 0x0010E23A, 0x0010E44E, 0x0010E643, 0x0010E838, 0x0010EA4B, 0x0010EC41, 0x0010EE54, 0x0010F049, + 0x0010F23C, 0x0010F450, 0x0010F645, 0x0010F839, 0x0010FA4C, 0x0010FC42, 0x0010FE37, 0x0011004B, 0x0011023E, + 0x00110452, 0x00110647, 0x0011083B, 0x00110A4E, 0x00110C43, 0x00110E38, 0x0011104C, 0x0011123F, 0x00111435, + 0x00111648, 0x0011183C, 0x00111A4F, 0x00111C45, 0x00111E39, 0x0011204D, 0x00112242, 0x00112436, 0x0011264A, + 0x0011283E, 0x00112A51, 0x00112C46, 0x00112E3B, 0x0011304F}; + +static uint32_t GetBitInt(uint32_t data, uint8_t length, uint8_t shift) +{ + return (data & (((1 << length) - 1) << shift)) >> shift; +} + +// WARNING: Dates before Oct. 1582 are inaccurate +static uint16_t SolarToInt(uint16_t y, uint8_t m, uint8_t d) +{ + m = (m + 9) % 12; + y = y - m / 10; + return 365 * y + y / 4 - y / 100 + y / 400 + (m * 306 + 5) / 10 + (d - 1); +} + +void LUNAR_SolarToLunar(struct Lunar_Date *lunar, uint16_t solar_year, uint8_t solar_month, uint8_t solar_date) +{ + uint8_t i, lunarM, m, d, leap, dm; + uint16_t year_index, lunarY, y, offset; + uint32_t solar_data, solar11, days; + + if (solar_month < 1 || solar_month > 12 || solar_date < 1 || solar_date > 31 || + (solar_year - solar_1_1[0] < 3) || ((solar_year - solar_1_1[0]) > (sizeof(solar_1_1) / sizeof(uint32_t) - 2))) + { + lunar->Year = 0; + lunar->Month = 0; + lunar->Date = 0; + lunar->IsLeap = 0; + return; + } + + year_index = solar_year - solar_1_1[0]; + solar_data = ((uint32_t)solar_year << 9) | ((uint32_t)solar_month << 5) | ((uint32_t)solar_date); + if (solar_1_1[year_index] > solar_data) + { + year_index -= 1; + } + solar11 = solar_1_1[year_index]; + y = GetBitInt(solar11, 12, 9); + m = GetBitInt(solar11, 4, 5); + d = GetBitInt(solar11, 5, 0); + offset = SolarToInt(solar_year, solar_month, solar_date) - SolarToInt(y, m, d); + + days = lunar_month_days[year_index]; + leap = GetBitInt(days, 4, 13); + + lunarY = year_index + solar_1_1[0]; + lunarM = 1; + offset += 1; + for (i = 0; i < 13; i++) + { + if (GetBitInt(days, 1, 12 - i) == 1) + { + dm = 30; + } + else + { + dm = 29; + } + if (offset > dm) + { + lunarM += 1; + offset -= dm; + } + else + { + break; + } + } + lunar->IsLeap = 0; + if (leap != 0 && lunarM > leap) + { + if (lunarM == leap + 1) + { + lunar->IsLeap = 1; + } + lunarM -= 1; + } + lunar->Month = lunarM; + lunar->Date = offset; + lunar->Year = lunarY; +} + +uint8_t LUNAR_GetZodiac(const struct Lunar_Date *lunar) +{ + return lunar->Year % 12; +} + +uint8_t LUNAR_GetStem(const struct Lunar_Date *lunar) +{ + return lunar->Year % 10; +} + +uint8_t LUNAR_GetBranch(const struct Lunar_Date *lunar) +{ + return lunar->Year % 12; +} + +/********************************************************************************************************* + ** 以下为24节气计算相关程序 + **------------------------------------------------------------------------------------------------------ + ********************************************************************************************************/ + +/* + 每年24节气标志表 + 有兴趣的朋友可按照上面给的原理添加其它年份的表格 + 不是很清楚的朋友可给我发EMAIL + */ +static const uint8_t YearMonthBit[160] = + { + 0x4E, 0xA6, 0x99, // 2000 + 0x9C, 0xA2, 0x98, // 2001 + 0x80, 0x00, 0x18, // 2002 + 0x00, 0x10, 0x24, // 2003 + 0x4E, 0xA6, 0x99, // 2004 + 0x9C, 0xA2, 0x98, // 2005 + 0x80, 0x82, 0x18, // 2006 + 0x00, 0x10, 0x24, // 2007 + 0x4E, 0xA6, 0xD9, // 2008 + 0x9E, 0xA2, 0x98, // 2009 + + 0x80, 0x82, 0x18, // 2010 + 0x00, 0x10, 0x04, // 2011 + 0x4E, 0xE6, 0xD9, // 2012 + 0x9E, 0xA6, 0xA8, // 2013 + 0x80, 0x82, 0x18, // 2014 + 0x00, 0x10, 0x00, // 2015 + 0x0F, 0xE6, 0xD9, // 2016 + 0xBE, 0xA6, 0x98, // 2017 + 0x88, 0x82, 0x18, // 2018 + 0x80, 0x00, 0x00, // 2019 + + 0x0F, 0xEF, 0xD9, // 2020 + 0xBE, 0xA6, 0x99, // 2021 + 0x8C, 0x82, 0x98, // 2022 + 0x80, 0x00, 0x00, // 2023 + 0x0F, 0xEF, 0xDB, // 2024 + 0xBE, 0xA6, 0x99, // 2025 + 0x9C, 0xA2, 0x98, // 2026 + 0x80, 0x00, 0x18, // 2027 + 0x0F, 0xEF, 0xDB, // 2028 + 0xBE, 0xA6, 0x99, // 2029 + + 0x9C, 0xA2, 0x98, // 2030 + 0x80, 0x00, 0x18, // 2031 + 0x0F, 0xEF, 0xDB, // 2032 + 0xBE, 0xA2, 0x99, // 2033 + 0x8C, 0xA0, 0x98, // 2034 + 0x80, 0x82, 0x18, // 2035 + 0x0B, 0xEF, 0xDB, // 2036 + 0xBE, 0xA6, 0x99, // 2037 + 0x8C, 0xA2, 0x98, // 2038 + 0x80, 0x82, 0x18, // 2039 + + 0x0F, 0xEF, 0xDB, // 2040 + 0xBE, 0xE6, 0xD9, // 2041 + 0x9E, 0xA2, 0x98, // 2042 + 0x80, 0x82, 0x18, // 2043 + 0x0F, 0xEF, 0xFB, // 2044 + 0xBF, 0xE6, 0xD9, // 2045 + 0x9E, 0xA6, 0x98, // 2046 + 0x80, 0x82, 0x18, // 2047 + 0x0F, 0xFF, 0xFF, // 2048 + 0xFC, 0xEF, 0xD9, // 2049 + 0xBE, 0xA6, 0x18, // 2050 +}; +static const uint8_t days[24] = + { + 6, 20, 4, 19, 6, 21, // 一月到三月 的节气基本日期 + 5, 20, 6, 21, 6, 21, // 四月到六月 的节气基本日期 + 7, 23, 8, 23, 8, 23, // 七月到九月 的节气基本日期 + 8, 24, 8, 22, 7, 22, // 十月到十二月的节气基本日期 +}; +/*立春、雨水、惊蛰、春分、清明、谷雨、立夏、小满、芒种、夏至、小暑、大暑、立秋、处暑、白露、秋分、寒露、霜降、立冬、小雪、大雪、冬至、小寒、大寒 + * + */ +const char JieQiStr[24][7] = { + "小寒", + "大寒", + "立春", + "雨水", + "惊蛰", + "春分", + "清明", + "谷雨", + "立夏", + "小满", + "芒种", + "夏至", + "小暑", + "大暑", + "立秋", + "处暑", + "白露", + "秋分", + "寒露", + "霜降", + "立冬", + "小雪", + "大雪", + "冬至", +}; +const uint8_t MonthDayMax[12] = { + 31, + 28, + 31, + 30, + 31, + 30, + 31, + 31, + 30, + 31, + 30, + 31, +}; + +/********************************************************************************************************* + ** 函数名称:GetJieQi + ** 功能描述:输入公历日期得到本月24节气日期 day<15返回上半月节气,反之返回下半月 + ** 如:GetJieQiStr(2007,02,08,str) 返回str[0]=4 + ** 输 入: year 公历年 + ** month 公历月 + ** day 公历日 + ** str 储存对应本月节气日期地址 1Byte + ** 输 出: 1 成功 + ** 0 失败 + ** 作 者: 赖皮 ★〓个人原创〓★ + ** 日 期: 2007年02月08日 + **------------------------------------------------------------------------------------------------------- + ** 修改人: + ** 日 期: + **------------------------------------------------------------------------------------------------------ + ********************************************************************************************************/ +uint8_t GetJieQi(uint16_t myear, uint8_t mmonth, uint8_t mday, uint8_t *JQdate) +{ + uint8_t bak1, value, JQ; + + if ((myear < 2000) || (myear > 2050)) + return 0; + if ((mmonth == 0) || (mmonth > 12)) + return 0; + JQ = (mmonth - 1) * 2; // 获得节气顺序标号(0~23 + if (mday >= 15) + JQ++; // 判断是否是上半月 + + bak1 = YearMonthBit[(myear - 2000) * 3 + JQ / 8]; // 获得节气日期相对值所在字节 + value = ((bak1 << (JQ % 8)) & 0x80); // 获得节气日期相对值状态 + + *JQdate = days[JQ]; + if (value != 0) + { + // 判断年份,以决定节气相对值1代表1,还是-1。 + if ((JQ == 1 || JQ == 11 || JQ == 18 || JQ == 21) && myear < 2044) + (*JQdate)++; + else + (*JQdate)--; + } + return 1; +} + +/********************************************************************************************************* + ** 函数名称:GetJieQiStr位置 + ** 如:GetJieQiStr(2007,02,08,day) 返回str="离雨水还有11天" day式距离几天 + ** 输 入: year 公历年 + ** month 公历月 + ** day 公历日 + ** str 储存24节气字符串地址 15Byte + ** 输 出: 1 成功 + ** 0xFF 失败 + ** 作 者: 赖皮 ★〓个人原创〓★ + ** 日 期: 2007年02月08日 + **------------------------------------------------------------------------------------------------------- + ** 修改人: + ** 日 期: + **------------------------------------------------------------------------------------------------------ + ********************************************************************************************************/ +uint8_t GetJieQiStr(uint16_t myear, uint8_t mmonth, uint8_t mday, uint8_t *day) +{ + uint8_t JQdate, JQ, MaxDay; + + if (GetJieQi(myear, mmonth, mday, &JQdate) == 0) + return 0xFF; + + JQ = (mmonth - 1) * 2; // 获得节气顺序标号(0~23 + + if (mday >= 15) + JQ++; // 判断是否是上半月 + + if (mday == JQdate) // 今天正是一个节气日 + { + *day = 0; + return JQ; + } + // 今天不是一个节气日 + // StrCopy(str, (uint8_t *)"离小寒还有??天", 15); + + if (mday < JQdate) // 如果今天日期小于本月的节气日期 + { + + mday = JQdate - mday; + } + else // 如果今天日期大于本月的节气日期 + { + JQ++; + + if (mday < 15) + { + GetJieQi(myear, mmonth, 15, &JQdate); + mday = JQdate - mday; + } + else // 翻月 + { + MaxDay = MonthDayMax[mmonth - 1]; + if (mmonth == 2) // 润月问题 + { + if ((myear % 4 == 0) && ((myear % 100 != 0) || (myear % 400 == 0))) + MaxDay++; + } + if (++mmonth == 13) + mmonth = 1; + GetJieQi(myear, mmonth, 1, &JQdate); + mday = MaxDay - mday + JQdate; + } + } + *day = mday; + return JQ; +} + +uint32_t SEC_PER_YR[2] = {31536000, 31622400}; // 闰年和非闰年的秒数 +uint32_t SEC_PER_MT[2][12] = { + {2678400, 2419200, 2678400, 2592000, 2678400, 2592000, + 2678400, 2678400, 2592000, 2678400, 2592000, 2678400}, + {2678400, 2505600, 2678400, 2592000, 2678400, 2592000, + 2678400, 2678400, 2592000, 2678400, 2592000, 2678400}, +}; +#define SECOND_OF_DAY 86400 // 一天多少秒 + +/** + * @Name : static int is_leap(int yr) + * @Description: 判断是否为闰年 + * "非整百年份:能被4整除的是闰年。" + * "整百年份:能被400整除的是闰年。" + * @In : 待机算的年份 + * @Out : 1:是闰年 0:非闰年 + * @Author : Denis + */ +int is_leap(int yr) +{ + if (0 == (yr % 100)) + return (yr % 400 == 0) ? 1 : 0; + else + return (yr % 4 == 0) ? 1 : 0; +} + +/** + * @Name : static unsigned char day_of_week_get(unsigned char month, unsigned char day, + unsigned short year) + * @Description: 根据输入的年月日计算当天为星期几 + * @In : 年、月、日 + * @Out : 星期几 + * @Author : Denis + */ +unsigned char day_of_week_get(unsigned char month, unsigned char day, + unsigned short year) +{ + /* Month should be a number 0 to 11, Day should be a number 1 to 31 */ + static int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4}; + year -= (uint8_t)(month < 3); + return (year + year / 4 - year / 100 + year / 400 + t[month - 1] + day) % 7; +} + +void transformTime(uint32_t unix_time, struct devtm *result) +{ + int leapyr = 0; + uint32_t ltime = unix_time; + + memset(result, 0, sizeof(struct devtm)); + result->tm_year = EPOCH_YR; + + while (1) + { + if (ltime < SEC_PER_YR[is_leap(result->tm_year)]) + { + break; + } + ltime -= SEC_PER_YR[is_leap(result->tm_year)]; + ++(result->tm_year); + } + + leapyr = is_leap(result->tm_year); + + while (1) + { + if (ltime < SEC_PER_MT[leapyr][result->tm_mon]) + break; + ltime -= SEC_PER_MT[leapyr][result->tm_mon]; + ++(result->tm_mon); + } + + result->tm_mday = ltime / SEC_PER_DY; + ++(result->tm_mday); + ltime = ltime % SEC_PER_DY; + + result->tm_hour = ltime / SEC_PER_HR; + ltime = ltime % SEC_PER_HR; + + result->tm_min = ltime / 60; + result->tm_sec = ltime % 60; + + result->tm_wday = + day_of_week_get(result->tm_mon + 1, result->tm_mday, + result->tm_year); + + /* + * The number of years since YEAR0" + */ + result->tm_year -= YEAR0; +} + +uint8_t map[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + +/* +获取一个月最后一天值 +*/ +uint8_t get_last_day(uint16_t year, uint8_t month) +{ + if (month % 12 == 1) + { + return map[month % 12] + is_leap(year); + } + return map[month % 12]; +} + +/* +获取一个月第一天星期值 +*/ +uint8_t get_first_day_week(uint16_t year, uint8_t month) +{ + return day_of_week_get(month, 1, year); +} + +// 时间结构体转时间戳 +uint32_t transformTimeStruct(struct devtm *result) +{ + uint32_t Cyear = 0; + for (uint16_t i = 1970; i < result->tm_year; i++) + { + if (is_leap(i) == 1) + Cyear++; + } + + uint32_t CountDay = Cyear * (uint32_t)366 + (uint32_t)(result->tm_year - 1970 - Cyear) * (uint32_t)365 + result->tm_mday - 1; + for (uint8_t i = 0; i < result->tm_mon - 1; i++) + { + CountDay += get_last_day(result->tm_year, i); + } + + return (CountDay * SECOND_OF_DAY + (uint32_t)result->tm_sec + (uint32_t)result->tm_min * 60 + (uint32_t)result->tm_hour * 3600); +} + +uint8_t thisMonthMaxDays(uint8_t year, uint8_t month) +{ + if (year % 4 == 0 && month == 2) + return MonthDayMax[month - 1] + 1; + else + return MonthDayMax[month - 1]; +} diff --git a/GUI/Lunar.h b/GUI/Lunar.h new file mode 100644 index 0000000..bf4f27f --- /dev/null +++ b/GUI/Lunar.h @@ -0,0 +1,53 @@ +#ifndef _LUNAR_H_ +#define _LUNAR_H_ +#include +#include + +#define YEAR0 (1900) /* The first year */ +#define EPOCH_YR (1970) /* EPOCH = Jan 1 1970 00:00:00 */ +#define SEC_PER_DY (86400) // 一天的秒数 +#define SEC_PER_HR (3600) // 一小时的秒数 + +typedef struct devtm +{ + uint16_t tm_year; + uint8_t tm_mon; + uint8_t tm_mday; + uint8_t tm_hour; + uint8_t tm_min; + uint8_t tm_sec; + uint8_t tm_wday; +} tm_t; + +struct Lunar_Date +{ + uint8_t IsLeap; + uint8_t Date; + uint8_t Month; + uint16_t Year; +}; + +extern const char Lunar_MonthString[13][7]; +extern const char Lunar_MonthLeapString[2][4]; +extern const char Lunar_DateString[31][7]; +extern const char Lunar_DayString[7][4]; +extern const char Lunar_ZodiacString[12][4]; +extern const char Lunar_StemStrig[10][4]; +extern const char Lunar_BranchStrig[12][4]; +extern const char JieQiStr[24][7]; + +void LUNAR_SolarToLunar(struct Lunar_Date *lunar, uint16_t solar_year, uint8_t solar_month, uint8_t solar_date); +uint8_t LUNAR_GetZodiac(const struct Lunar_Date *lunar); +uint8_t LUNAR_GetStem(const struct Lunar_Date *lunar); +uint8_t LUNAR_GetBranch(const struct Lunar_Date *lunar); +uint8_t GetJieQiStr(uint16_t myear, uint8_t mmonth, uint8_t mday, uint8_t *day); +uint8_t GetJieQi(uint16_t myear, uint8_t mmonth, uint8_t mday, uint8_t *JQdate); + +void transformTime(uint32_t unix_time, struct devtm *result); +uint32_t transformTimeStruct(struct devtm *result); +uint8_t get_first_day_week(uint16_t year, uint8_t month); +uint8_t get_last_day(uint16_t year, uint8_t month); +unsigned char day_of_week_get(unsigned char month, unsigned char day, unsigned short year); +uint8_t thisMonthMaxDays(uint8_t year, uint8_t month); + +#endif diff --git a/GUI/fonts.c b/GUI/fonts.c new file mode 100644 index 0000000..68fdb6f --- /dev/null +++ b/GUI/fonts.c @@ -0,0 +1,135 @@ +#include "fonts.h" + +/* + Fontname: -wenquanyi-wenquanyi bitmap song-medium-r-normal--12-120-75-75-P-119-ISO10646-1 + Copyright: (null) + Glyphs: 96/30503 + BBX Build Mode: 0 +*/ +const uint8_t u8g2_font_wqy9_t_lunar[3235] U8G2_FONT_SECTION("u8g2_font_wqy9_t_lunar") = + "\266\0\3\2\4\4\3\4\5\13\15\0\376\10\376\12\377\1a\2\317\4\30 \5\0L\13!\7\221F" + "\213S\0\42\7\64}\213\310\24#\16\226\304\233\250eX\242^\206%j\1$\17\245<\253l\251(" + "\231\250%Je\213\0%\20\226<\233(\351\242%a\232DI\27-\1&\16\205D\253,\211\222\254" + "\62%Q\244\4'\6\61\376\212\1(\13\263=\253$J\242nQ\26)\14\263=\213,\312\242.Q" + "\22\1*\14uD\253J\345\240,M\21\0+\13wD\274\270\66\14Y\134\3,\7\62>\13E\1" + "-\6\25d\213A.\6!\306\12\1/\14\304<\273\246\254\224\225\262\32\0\60\12\205D\233%\363-" + "Y\0\61\11\205D\213\261O\203\0\62\13\205D\233%\13km\203\0\63\14\205D\213!\15\223\65\14" + "\7\5\64\16\206\304;-\211\272d\311\60\246\11\0\65\15\205D\213!\11\303!\15\303A\1\66\15\205" + "D\233%\14\207$\263%\13\0\67\13\205D\213A\254\205YX\2\70\15\205D\233%\323\222%\263%" + "\13\0\71\15\205D\233%\263%C\250%\13\0:\6aD\11I;\11\202>\213!V\24\0<\10" + "\225D\313\254k\7=\10\65\134\213A\35\4>\11\225D\213\264[G\0\77\15\225D\233%\323\302H" + "\313\241\60\2@\24\247<\254\255\222HZ\42)JE\251H\211\222e\23\0A\16\207D\274\70M\302" + "$+\15J\252\6B\15\206\304\213A\11\305a\11\215\303\2C\14\206\304\233!\11\325\216\311\220\0D" + "\15\207D\214A\12\223\324c\62H\0E\12\205D\213c\70\214\305AF\13\205D\213c\70$a#" + "\0G\15\206\304\233!\11\325\332(&\203\0H\13\206\304\213\320\70\14\242c\0I\10\203D\212%\352" + "eJ\10\243\64\252\376\264\0K\15\205D\213LJJ\232\226D\225,L\11\205D\213\260\217\203\0M" + "\20\207D\14m\310\226\212R\221\42)R\325\0N\15\206\304\213pS\42)\321Fc\0O\14\207D" + "\254\255\222\272&Y\66\1P\14\205D\213!\311l\203\22\26\1Q\16\227<\254\255\222\272&Y\266\3" + "\11\0R\20\206\304\213!\312\222,\311\222!jK\302\0S\14\205D\233AL\327\60\34\24\0T\11" + "\207D\214C\26\367\15U\12\206\304\213\320\217\311\220\0V\17\207D\214TM\262(\253\204I\32g\0" + "W\21\211D\215,\323\62\255\322)iJ\332\212Y\4X\15\206\304\213PL\242L\213ZB\61Y\13" + "\207D\214\64\311*i\334\15Z\12\207D\214C\332\347a\10[\11\263>\213!\352O\3\134\14\245<" + "\213\60\15\323\60\15\323\60]\11\263=\213\251\77\15\1^\10\65t\253,\251\5_\6\25<\213A`" + "\7\62\375\212$\12a\14eD\233%K\6MK\206\0b\14\205D\213\60\34\222\314mP\0c\10" + "d\304\232!k\34d\13\205D\313\312\240\271%C\0e\13eD\233%\33\206\60\35\2f\11\203D" + "\232i\210:\1g\14\205\64\233AsK\206\60Y\0h\13\205D\213\60\34\222\314[\0i\7\201D" + "\211d\30j\10\242\264\231,\351ek\15\205D\213\260\224\224\264$\252d\1l\7\201D\211\203\0m" + "\16gD\214E\211\42)\222\42)\222\12n\11eD\213!\311\274\5o\12eD\233%sK\26\0" + "p\14\205\64\213!\311\334\6%\14\1q\13\205\64\233AsK\206\260\0r\10cD\212!\352\4s" + "\14eD\233%K\324$K\26\0t\12\203D\212(\32\242\66\1u\11eD\213\314[\62\4v\14" + "eD\213LKJI\26F\0w\16gD\214(\222\42\245\242t\213\262\4x\13eD\213,\251U" + "jZ\0y\15\205\64\213LKJI\26fa\6z\12eD\213A\314\332\6\1{\13\243<\252$" + "\252dQ[\0|\7\261\276\212\7\1}\13\243<\212,\252%QK\4~\7&\334\33\311\2\200\17" + "\225D+\251\222\15Q\66Da\24)\0\0\0\0\4\377\377N\0\12+d~(\31\16\2N\1\25" + "\252=\216\207\64\207r(\207r(\207r(\207r`\4N\3\26\272=\316\34\312\241\34\312\304!\32" + "t(\207r(l\35\4N\11\17\253<\236\341\316\313\60\344|\34\16\2N\21\27\253<\236a\220\263" + "\70\213\263l\30\324\60\15\323\60\15\243\341 N\31\32\273<\216\7\65\307\342\341\224\225\62\251K\224\244" + "Q\16D\71\20\245\13\0NY\30\272=\216w$Gr$Gr$Gr$GrDG\222\341\0" + "N]\27\272=\276\34\312\241x\30\342(\216\342(\315\322,J\302H\36N\214\14\213D\236\341\316\77" + "\16\7\1N\224\26\253<\236\341\234c\71\226\3\207\70\213\263\70\213\263h\70\10N\245\27\272=\316\34" + "\13\207C\226#Y\70Hi\24J\231Y\212V\1QT\32\273<\276\34\33\324,\35.\245,\33\6" + "\71\207\222r\322\26e\211\70\4Qk\30\253D\356\34\210r \312\201(\7\262\70K\213i\226\3I" + "\16\5Qm\24\273<\316\34\314I\303Ag\314\342\260\34\345\210\16\5Q\234\33\273<\336x\70$Y" + "\250ia\224\3QMJ\262$+%Q*jq\0Q\254\30\273<\276\34\33T-\214\222\34\312\21" + "\333\24\315\71\240\203\71\30\2R\6\30\273<\276r\226\26\323,\7\222d\30\222\60\213\263\64\254F\242" + "\6R\35\33\273<\236\34\214\206\203T+\25\243D\211\42E\213*Q[\251\30%\231\2SA\27\273" + "<\336\34\313\261\34K\207\203\232c\71\226c\71\226c)\0SH\27\272=\256\34\32\206(\13\353P" + "\70\34\322\34\312\241\34\312\241\20So\33\272=\276x\32\306LJ\62)\311\244$S\224l\351\226D" + "Y\24j!\0S\206\32\273<\256\341\224c\345,N\206!\312JY\251\230da\22\245R\250\0V" + "\333\30\251=\216\207(\211\244$\222\222HJ\42%\32f\35x\320\201\0X\354\30\273<~ \207\266" + "A\307r\254\62\34\324\34\313\261\34\213\262\341\2Y\4\32\273<\256\60\15\323!I\243D\253\364\324\22" + "\305Y\232Da&\306C\0Y\17\31\273<\216\7\65G\206\65L\247\64\31\322\60\34\206,\252\205\362" + "\64\4Y'\30\273<\336\34\313\261t\70\250\71\226CI\216\324\201,-\351\200\0[P\30\273<\256" + "a\310\241\34\312\241\34K\207\203\232c\71\226#u(\6[\305\31\273<\336tx\207\224aH\322\34" + "\70\204Qq\30\302\250\70\14\321:[\322\33\273<\336tx\213\42e\30\222\60\312\206\203\30\305I\24" + "m\212\250\203\32\0\134\17\30\273<\336\34\313\261\34\210\212Q\26e\245,\24\253\71R\207b\0]\362" + "\26\252=\216\203\16\345P\71\32\6)\207rDGt$\31\16]\363\30\272=\216\203\24Gq\24G" + "\303 \345P\16\345\210\216\350H\62\34^t\27\273<\256\34\33.a\65\36\6\255\234\205\303A\316\261" + "\34\13\1^\232\33\273<\356x\70eq\62l\245l\70eQ\226\14[\224\204Q\26%\252\0^\377" + "\30\273<\276\60\15\323\60\32\16Z\230\206i\230\206i\230\16k\30\1`\312\36\273<\256\60M\206A" + "\321\201$\31\226R\226\224\6-L\243\244\226D%\245T\13\1b\12\31\273<\356$G\242l\70e" + "q\26e\245,\24KI\230\264e\223\32b\14\33\273<\356$G\242l\70eq\26e\203\224U\302" + "j&%Y\224H\242\0e\345\15\247>\216\203j\35\256\326\341\32e\366\34\273<~$\32\302(\32" + "NaT\211\242!J\242\226\250\30\25\243!\314\221\11f\16\31\273<\356\341\224I\65)\32N\231T" + "\223\242\341\224\245a-\211\324\4f\37\33\273<\256a\10\323p\30\302\64\34\206\60\212\207K\230\3\207" + "\34H\207\203\0f%\32\273<\336x\70\244\71pP\343\341 \211\212\62%Y\62\244a:L\0f" + "\221\33\273<\256a\10\323p\30\302\64\33\316I\66\34\304,\34\206H\11\323a\2g\10\31\271=\256" + "a\210\322(\215\206!J\243\64\32\206(\215\322$L\344\4g\37 \273<\236(\32\16I\224D-" + "C\64$Q\313\20U\242h\70$Q\16DIT\22%\1g*\27\273<\336\34\213\207s:\34\324" + "\34\332\201\244\32\225\264LM\1kc\32\253<\216\7\65\307r \312\201h\320\242\34\210r \312\201" + "(\35\16\2l\64\30\273<\336\34\313\261\312\220(iRN\252Q\61\252\265DIX\6n\5\36\273" + "<\236\264\62\14\71\220E\311 %Y\232\14CTK\264%\312\222)+e\221\2n\341 \273<\236" + "\60\11\223a\310\242$\212\206A\11\223\60\31\206(i\231\222NJ\242D-Q(r[\27\272=\336" + "\70\212\243t\30\224\60\207\302\341\220\346P\16\345P\10r\327\35\273<\216\250\232Dq\64,\215\222\262" + "DR\322)I\244h\311\342\244\226di\4s* \273<\216,\252T\206$\12\223(I\206!\312" + "RiP*\232\244\14Z\224%\245A\312\12s\64 \273<\216\244\62D\305\244\62\14\321\22'\311\240" + "\230*\265\60\31\206(\311\242\244\224D\225,u\62\26\271=\216\207,\323\262\341\240eZ\66\34\264b" + "\216\344H\10u\63\26\271=\316\34\11\207C\226\15\7-\323\262\341\240\25s$\4vx\32\273<\236" + "a\211\243\226(\11[\206$\312B\61\36\16i\222\3Y\266\12v}\20\270>\276\70\35\316\362p\210" + "\315\303!\16w\345\33\273<\256\34\313\241A\31\244,\312J\303)+e\245JT\311\222A\307\0y" + "\313\35\273<>)\33\323\60I\206\245\26%\332RT\242,\311\222\60K\302(\253\244\1y\315\33\273" + "<>)\33\323hx\211Z\242H)%\312\60(a\32\246a\32f\0y\322\33\273<\276\332\230F" + "IeHJ-Q\262\24\225(R\342,\15\63\65Q\1z\313\30\273<\336\34L\207CN\10\323\60" + "\216r \312\201$\307\302\341 \177\212\27\273<\256\264\26\16\347\34\313\201C\16\344X:\34\324\34K" + "\1\201J#\273<\236)\311\222(\311\222d\220\246$K\206A\251#\7)\251EI\62(Q\222%" + "\211\62(\0\201\363\27\273<\236\341\232CYZ\33\316\71\226\3\207\34\310\261t\70\10\202\222\30\273<" + "\276\332p\320\352H\16\206\303A\312\261\34\313\261\34\33\6\5\206N\34\273<\336A\216\207C\22\205\321" + "\60hQ\232D\303\220\344X\64\204QI\21\7\206\307\32\273<\256\60-F\303\243\322\230\64%\203\62" + "fqR\213\226l\212\206\0\206\360\36\273<\256\60\33\222A\13\223d\210\224\254\42Ia\64\14Z\224" + "e\303 g\311p\11\214\67\32\273<\316(\16KY\224ia\24\207\341\60(J\230da:\254a" + "\4\217\233\27\273<\336x\270\245\251\224\3I\70\34\324x\70\347X\216\245\0\217\260\34\273<\256\341\224" + "c\311\260\345\330pJ\312I)K\42-\312JI\224db\0\221I\33\273<\216\7\61\311\221\352p" + "\252D-Q\264M\71\20\15\247\34\210\206\13\0\225\360\33\273<\256h\30\242X\207\244A\22C\61\224" + "\6I\14\305P\31\206D\7\6\226M\36\273<\216!\12\243hX\42)Q\245$\222\246\254\224\14\203" + "\322\226$\303 gq\6\226\350\30\273<\216\7\65\307\342\341\224\225\222N\221\247J\247\254\224E\12\0" + "\226\352\31\273<\236\341\234\16\217\241\242$J\216\16w,\33\6\35\213\206\13\0\227\34\35\273<\236\341" + "\234\16OI\251\66$\203\22e\331\220I%%\31\222NY\66$\0\227\62\36\273<\236\341\234\16O" + "Ii\220\206$J\244d\310\304HZ\16I-J\206hH\0\232l\30\273<\236a\320\261\64L\263" + "\64L\207;\226c\303)\307rD\1\236!\35\273<~ \33\222AL\262$J\232\222nQ\61\32" + "\226r\22\15\212\16\345\210\2\237 \36\272=\216!\31\222\34H\206dHr \31\356\240\244DIE" + "\262$J\313\240L\1\237\231\32\273<\316(\7\262\70\36\16b\222#\245\60J\322H\315*\231I\32" + "\2\0"; + +/* + Fontname: -wenquanyi-wenquanyi bitmap song-bold-r-normal--16-160-75-75-P-80-iso10646-1 + Copyright: (null) + Glyphs: 21/29889 + BBX Build Mode: 0 +*/ +const uint8_t u8g2_font_wqy12b_t_lunar[491] U8G2_FONT_SECTION("u8g2_font_wqy12b_t_lunar") = + "\25\0\4\3\5\5\3\4\6\20\20\0\376\13\375\14\374\0\0\0\0\0\253\60\16g\25S\231\221P\303" + "o$\324\214\0\61\13f\25S\221\31!\322O\14\62\15g\25\323\250\220\241\221R\244W\7\63\21h" + "\21\323(\221\221\23\223\222\241SF#R\2\64\25h\21\323\222\32\242\221\20Q!#!#q\20%" + "\246\2\0\65\21h\21S\270\20Sf\62\42'&F#R\2\66\24h\21S\241\21\221\20\23\63\31" + "\221\20b$!\42C\2\67\21h\21S\70\10\223\22\223\22\223\22S%&\4\70\23h\21S\241\21" + "\321\33\32\21\11!F\22\42\62$\0\71\24h\21S\241\21\221\20b$!\62b\246BD\206\4\0" + "\0\0\0\4\377\377N\0\10\60\320cx N\11\20\220\21\343\70\270\307\335A=\336\35\34\10N\214" + "\15P\61c\71\250\307\237\36\34\10N\224\36\320\361\342\70\270\23\327\364\200PHPHPHPHN" + "JNJNJ\346\340@\0Qm \360\361b\223\7\220\7\20\227\207;\70\220G'\42*#(%" + "'&%'$)\42*\1V\333\37\256\365bx \242\204D\11\211\22\22%$J(dD(\204" + "\16\4+I\17\16H\5^t\37\20\322\342\21\27\77\250\21\22\24\222\223\222<\240\222\221\224\221\224\21" + ";\70\20\24\327\31\0e\345\20\352\335b\70(\343\331\301\31\237\35\234\11f\37\36\316\365b\71\30\22" + "\23:\30\22\23:\30T\42x@\42#&$x\60(,vpg\10&\15\332\342\71\230\21\223\21" + "\223\21\223\21\223\71\230\21\223\21\223\21\223\71\230\21\223\21\23\221\23\221\223\20\63\224\0g\37.\17\322" + "bQ)\42aq\60\242\205\210\26\42\42\26&*D\264\20\21\261\20\321\302D\205\310\201\211\224\204\214" + "\210\10\215\204\220\210\221\12\0\0"; diff --git a/GUI/fonts.h b/GUI/fonts.h new file mode 100644 index 0000000..5172b92 --- /dev/null +++ b/GUI/fonts.h @@ -0,0 +1,37 @@ +#ifndef _FONTS_H +#define _FONTS_H + +#include "u8g2_font.h" + +/** + * 文字列表: +所有 ASCII 字符 (32-128) + +未知 +农历 +正月二月三月四月五月六月七月八月九月十月 +冬月腊月 +闰 +初一初二初三初四初五初六初七初八初九初十 +十一十二十三十四十五十六十七十八十九二十 +廿一廿二廿三廿四廿五廿六廿七廿八廿九三十 +日 +一二三四五六 +猴鸡狗猪鼠牛虎兔龙蛇马羊 +庚辛壬癸甲乙丙丁戊已 +申酉戌亥子丑寅卯辰巳午未 +小寒大寒立春雨水惊蛰春分清明谷雨立夏小满芒种夏至小暑大暑立秋处暑白露秋分寒露霜降立冬小雪大雪冬至 +年月日时分秒 +星期 + */ +extern const uint8_t u8g2_font_wqy9_t_lunar[] U8G2_FONT_SECTION("u8g2_font_wqy9_t_lunar"); +/** + * 文字列表: +0123456789 +年月日 +星期 +一二三四五六 + */ +extern const uint8_t u8g2_font_wqy12b_t_lunar[] U8G2_FONT_SECTION("u8g2_font_wqy12b_t_lunar"); + +#endif diff --git a/GUI/u8g2_font.c b/GUI/u8g2_font.c new file mode 100644 index 0000000..92ec0cd --- /dev/null +++ b/GUI/u8g2_font.c @@ -0,0 +1,573 @@ +/* + + U8g2_for_Adafruit_GFX.cpp + + Add unicode support and U8g2 fonts to Adafruit GFX libraries. + + U8g2 for Adafruit GFX Lib (https://github.com/olikraus/U8g2_for_Adafruit_GFX) + + Copyright (c) 2018, olikraus@gmail.com + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list + of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or other + materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +#include +#include "u8g2_font.h" + +static uint8_t u8g2_font_get_byte(const uint8_t *font, uint8_t offset) +{ + font += offset; + return u8x8_pgm_read( font ); +} + +static uint16_t u8g2_font_get_word(const uint8_t *font, uint8_t offset) U8X8_NOINLINE; +static uint16_t u8g2_font_get_word(const uint8_t *font, uint8_t offset) +{ + uint16_t pos; + font += offset; + pos = u8x8_pgm_read( font ); + font++; + pos <<= 8; + pos += u8x8_pgm_read( font); + return pos; +} + +/*========================================================================*/ +/* new font format */ +void u8g2_read_font_info(u8g2_font_info_t *font_info, const uint8_t *font) +{ + /* offset 0 */ + font_info->glyph_cnt = u8g2_font_get_byte(font, 0); + font_info->bbx_mode = u8g2_font_get_byte(font, 1); + font_info->bits_per_0 = u8g2_font_get_byte(font, 2); + font_info->bits_per_1 = u8g2_font_get_byte(font, 3); + + /* offset 4 */ + font_info->bits_per_char_width = u8g2_font_get_byte(font, 4); + font_info->bits_per_char_height = u8g2_font_get_byte(font, 5); + font_info->bits_per_char_x = u8g2_font_get_byte(font, 6); + font_info->bits_per_char_y = u8g2_font_get_byte(font, 7); + font_info->bits_per_delta_x = u8g2_font_get_byte(font, 8); + + /* offset 9 */ + font_info->max_char_width = u8g2_font_get_byte(font, 9); + font_info->max_char_height = u8g2_font_get_byte(font, 10); + font_info->x_offset = u8g2_font_get_byte(font, 11); + font_info->y_offset = u8g2_font_get_byte(font, 12); + + /* offset 13 */ + font_info->ascent_A = u8g2_font_get_byte(font, 13); + font_info->descent_g = u8g2_font_get_byte(font, 14); + font_info->ascent_para = u8g2_font_get_byte(font, 15); + font_info->descent_para = u8g2_font_get_byte(font, 16); + + /* offset 17 */ + font_info->start_pos_upper_A = u8g2_font_get_word(font, 17); + font_info->start_pos_lower_a = u8g2_font_get_word(font, 19); + + /* offset 21 */ + font_info->start_pos_unicode = u8g2_font_get_word(font, 21); +} + +uint8_t u8g2_GetFontBBXWidth(u8g2_font_t *u8g2) +{ + return u8g2->font_info.max_char_width; /* new font info structure */ +} + +uint8_t u8g2_GetFontBBXHeight(u8g2_font_t *u8g2) +{ + return u8g2->font_info.max_char_height; /* new font info structure */ +} + +int8_t u8g2_GetFontBBXOffX(u8g2_font_t *u8g2) +{ + return u8g2->font_info.x_offset; /* new font info structure */ +} + +int8_t u8g2_GetFontBBXOffY(u8g2_font_t *u8g2) +{ + return u8g2->font_info.y_offset; /* new font info structure */ +} + +uint8_t u8g2_GetFontCapitalAHeight(u8g2_font_t *u8g2) +{ + return u8g2->font_info.ascent_A; /* new font info structure */ +} + +static uint8_t u8g2_font_decode_get_unsigned_bits(u8g2_font_decode_t *f, uint8_t cnt) U8X8_NOINLINE; +static uint8_t u8g2_font_decode_get_unsigned_bits(u8g2_font_decode_t *f, uint8_t cnt) +{ + uint8_t val; + uint8_t bit_pos = f->decode_bit_pos; + uint8_t bit_pos_plus_cnt; + + //val = *(f->decode_ptr); + val = u8x8_pgm_read( f->decode_ptr ); + + val >>= bit_pos; + bit_pos_plus_cnt = bit_pos; + bit_pos_plus_cnt += cnt; + if ( bit_pos_plus_cnt >= 8 ) + { + uint8_t s = 8; + s -= bit_pos; + f->decode_ptr++; + //val |= *(f->decode_ptr) << (8-bit_pos); + val |= u8x8_pgm_read( f->decode_ptr ) << (s); + //bit_pos -= 8; + bit_pos_plus_cnt -= 8; + } + val &= (1U<decode_bit_pos = bit_pos_plus_cnt; + return val; +} + + +/* + 2 bit --> cnt = 2 + -2,-1,0. 1 + + 3 bit --> cnt = 3 + -2,-1,0. 1 + -4,-3,-2,-1,0,1,2,3 + + if ( x < 0 ) + r = bits(x-1)+1; + else + r = bits(x)+1; + +*/ +/* optimized */ +static int8_t u8g2_font_decode_get_signed_bits(u8g2_font_decode_t *f, uint8_t cnt) U8X8_NOINLINE; +static int8_t u8g2_font_decode_get_signed_bits(u8g2_font_decode_t *f, uint8_t cnt) +{ + int8_t v, d; + v = (int8_t)u8g2_font_decode_get_unsigned_bits(f, cnt); + d = 1; + cnt--; + d <<= cnt; + v -= d; + return v; + //return (int8_t)u8g2_font_decode_get_unsigned_bits(f, cnt) - ((1<>1); +} + + +static int16_t u8g2_add_vector_y(int16_t dy, int8_t x, int8_t y, uint8_t dir) U8X8_NOINLINE; +static int16_t u8g2_add_vector_y(int16_t dy, int8_t x, int8_t y, uint8_t dir) +{ + switch(dir) + { + case 0: + dy += y; + break; + case 1: + dy += x; + break; + case 2: + dy -= y; + break; + default: + dy -= x; + break; + } + return dy; +} + +static int16_t u8g2_add_vector_x(int16_t dx, int8_t x, int8_t y, uint8_t dir) U8X8_NOINLINE; +static int16_t u8g2_add_vector_x(int16_t dx, int8_t x, int8_t y, uint8_t dir) +{ + switch(dir) + { + case 0: + dx += x; + break; + case 1: + dx -= y; + break; + case 2: + dx -= x; + break; + default: + dx += y; + break; + } + return dx; +} + +/* + Description: + Draw a run-length area of the glyph. "len" can have any size and the line + length has to be wrapped at the glyph border. + Args: + len: Length of the line + is_foreground foreground/background? + u8g2->font_decode.target_x X position + u8g2->font_decode.target_y Y position + u8g2->font_decode.is_transparent Transparent mode + Return: + - + Calls: + u8g2_Draw90Line() + Called by: + u8g2_font_decode_glyph() +*/ +/* optimized */ +static void u8g2_font_decode_len(u8g2_font_t *u8g2, uint8_t len, uint8_t is_foreground) +{ + uint8_t cnt; /* total number of remaining pixels, which have to be drawn */ + uint8_t rem; /* remaining pixel to the right edge of the glyph */ + uint8_t current; /* number of pixels, which need to be drawn for the draw procedure */ + /* current is either equal to cnt or equal to rem */ + + /* local coordinates of the glyph */ + uint8_t lx,ly; + + /* target position on the screen */ + int16_t x, y; + + u8g2_font_decode_t *decode = &(u8g2->font_decode); + + cnt = len; + + /* get the local position */ + lx = decode->x; + ly = decode->y; + + for(;;) + { + /* calculate the number of pixel to the right edge of the glyph */ + rem = decode->glyph_width; + rem -= lx; + + /* calculate how many pixel to draw. This is either to the right edge */ + /* or lesser, if not enough pixel are left */ + current = rem; + if ( cnt < rem ) + current = cnt; + + + /* now draw the line, but apply the rotation around the glyph target position */ + //u8g2_font_decode_draw_pixel(u8g2, lx,ly,current, is_foreground); + + /* get target position */ + x = decode->target_x; + y = decode->target_y; + + /* apply rotation */ + x = u8g2_add_vector_x(x, lx, ly, decode->dir); + y = u8g2_add_vector_y(y, lx, ly, decode->dir); + + /* draw foreground and background (if required) */ + if ( current > 0 ) /* avoid drawing zero length lines, issue #4 */ + { + if ( is_foreground ) + { + u8g2->draw_hv_line(x, y, current, decode->dir, decode->fg_color, u8g2->draw_hv_line_arg); + } + else if ( decode->is_transparent == 0 ) + { + u8g2->draw_hv_line(x, y, current, decode->dir, decode->bg_color, u8g2->draw_hv_line_arg); + } + } + + /* check, whether the end of the run length code has been reached */ + if ( cnt < rem ) + break; + cnt -= rem; + lx = 0; + ly++; + } + lx += cnt; + + decode->x = lx; + decode->y = ly; + +} + +static void u8g2_font_setup_decode(u8g2_font_t *u8g2, const uint8_t *glyph_data) +{ + u8g2_font_decode_t *decode = &(u8g2->font_decode); + decode->decode_ptr = glyph_data; + decode->decode_bit_pos = 0; + + /* 8 Nov 2015, this is already done in the glyph data search procedure */ + /* + decode->decode_ptr += 1; + decode->decode_ptr += 1; + */ + + decode->glyph_width = u8g2_font_decode_get_unsigned_bits(decode, u8g2->font_info.bits_per_char_width); + decode->glyph_height = u8g2_font_decode_get_unsigned_bits(decode,u8g2->font_info.bits_per_char_height); + +} + + +/* + Description: + Decode and draw a glyph. + Args: + glyph_data: Pointer to the compressed glyph data of the font + u8g2->font_decode.target_x X position + u8g2->font_decode.target_y Y position + u8g2->font_decode.is_transparent Transparent mode + Return: + Width (delta x advance) of the glyph. + Calls: + u8g2_font_decode_len() +*/ +/* optimized */ +static int8_t u8g2_font_decode_glyph(u8g2_font_t *u8g2, const uint8_t *glyph_data) +{ + uint8_t a, b; + int8_t x, y; + int8_t d; + int8_t h; + u8g2_font_decode_t *decode = &(u8g2->font_decode); + + u8g2_font_setup_decode(u8g2, glyph_data); + h = u8g2->font_decode.glyph_height; + + x = u8g2_font_decode_get_signed_bits(decode, u8g2->font_info.bits_per_char_x); + y = u8g2_font_decode_get_signed_bits(decode, u8g2->font_info.bits_per_char_y); + d = u8g2_font_decode_get_signed_bits(decode, u8g2->font_info.bits_per_delta_x); + + + if ( decode->glyph_width > 0 ) + { + decode->target_x = u8g2_add_vector_x(decode->target_x, x, -(h+y), decode->dir); + decode->target_y = u8g2_add_vector_y(decode->target_y, x, -(h+y), decode->dir); + //u8g2_add_vector(&(decode->target_x), &(decode->target_y), x, -(h+y), decode->dir); + + + /* reset local x/y position */ + decode->x = 0; + decode->y = 0; + + /* decode glyph */ + for(;;) + { + a = u8g2_font_decode_get_unsigned_bits(decode, u8g2->font_info.bits_per_0); + b = u8g2_font_decode_get_unsigned_bits(decode, u8g2->font_info.bits_per_1); + do + { + u8g2_font_decode_len(u8g2, a, 0); + u8g2_font_decode_len(u8g2, b, 1); + } while( u8g2_font_decode_get_unsigned_bits(decode, 1) != 0 ); + + if ( decode->y >= h ) + break; + } + + } + return d; +} + +/* + Description: + Find the starting point of the glyph data. + Args: + encoding: Encoding (ASCII or Unicode) of the glyph + Return: + Address of the glyph data or NULL, if the encoding is not avialable in the font. +*/ +const uint8_t *u8g2_font_get_glyph_data(u8g2_font_t *u8g2, uint16_t encoding) +{ + const uint8_t *font = u8g2->font; + font += 23; + + + if ( encoding <= 255 ) + { + if ( encoding >= 'a' ) + { + font += u8g2->font_info.start_pos_lower_a; + } + else if ( encoding >= 'A' ) + { + font += u8g2->font_info.start_pos_upper_A; + } + + for(;;) + { + if ( u8x8_pgm_read( font + 1 ) == 0 ) + break; + if ( u8x8_pgm_read( font ) == encoding ) + { + return font+2; /* skip encoding and glyph size */ + } + font += u8x8_pgm_read( font + 1 ); + } + } + else + { + uint16_t e; + const uint8_t *unicode_lookup_table; + /* support for the new unicode lookup table */ + + font += u8g2->font_info.start_pos_unicode; + unicode_lookup_table = font; + + /* u8g2 issue 596: search for the glyph start in the unicode lookup table */ + do + { + font += u8g2_font_get_word(unicode_lookup_table, 0); + e = u8g2_font_get_word(unicode_lookup_table, 2); + unicode_lookup_table+=4; + } while( e < encoding ); + + /* variable "font" is now updated according to the lookup table */ + + for(;;) + { + e = u8x8_pgm_read( font ); + e <<= 8; + e |= u8x8_pgm_read( font + 1 ); + if ( e == 0 ) + break; + if ( e == encoding ) + { + return font+3; /* skip encoding and glyph size */ + } + font += u8x8_pgm_read( font + 2 ); + } + } + return NULL; +} + +static int16_t u8g2_font_draw_glyph(u8g2_font_t *u8g2, int16_t x, int16_t y, uint16_t encoding) +{ + int16_t dx = 0; + u8g2->font_decode.target_x = x; + u8g2->font_decode.target_y = y; + //u8g2->font_decode.is_transparent = is_transparent; this is already set + //u8g2->font_decode.dir = dir; + const uint8_t *glyph_data = u8g2_font_get_glyph_data(u8g2, encoding); + if ( glyph_data != NULL ) + { + dx = u8g2_font_decode_glyph(u8g2, glyph_data); + } + return dx; +} + + +//======================================================== + +uint8_t u8g2_IsGlyph(u8g2_font_t *u8g2, uint16_t requested_encoding) +{ + /* updated to new code */ + if ( u8g2_font_get_glyph_data(u8g2, requested_encoding) != NULL ) + return 1; + return 0; +} + +/* side effect: updates u8g2->font_decode and u8g2->glyph_x_offset */ +/* actually u8g2_GetGlyphWidth returns the glyph delta x and glyph width itself is set as side effect */ +int8_t u8g2_GetGlyphWidth(u8g2_font_t *u8g2, uint16_t requested_encoding) +{ + const uint8_t *glyph_data = u8g2_font_get_glyph_data(u8g2, requested_encoding); + if ( glyph_data == NULL ) + return 0; + + u8g2_font_setup_decode(u8g2, glyph_data); + u8g2->glyph_x_offset = u8g2_font_decode_get_signed_bits(&(u8g2->font_decode), u8g2->font_info.bits_per_char_x); + u8g2_font_decode_get_signed_bits(&(u8g2->font_decode), u8g2->font_info.bits_per_char_y); + + /* glyph width is here: u8g2->font_decode.glyph_width */ + return u8g2_font_decode_get_signed_bits(&(u8g2->font_decode), u8g2->font_info.bits_per_delta_x); +} + + +void u8g2_SetFontMode(u8g2_font_t *u8g2, uint8_t is_transparent) +{ + u8g2->font_decode.is_transparent = is_transparent; // new font procedures +} + +void u8g2_SetFontDirection(u8g2_font_t *u8g2, uint8_t dir) +{ + u8g2->font_decode.dir = dir; +} + + + +int16_t u8g2_DrawGlyph(u8g2_font_t *u8g2, int16_t x, int16_t y, uint16_t encoding) +{ + return u8g2_font_draw_glyph(u8g2, x, y, encoding); +} + +int16_t u8g2_DrawStr(u8g2_font_t *u8g2, int16_t x, int16_t y, const char *s) +{ + int16_t sum, delta; + sum = 0; + + while( *s != '\0' ) + { + delta = u8g2_DrawGlyph(u8g2, x, y, *s); + switch(u8g2->font_decode.dir) + { + case 0: + x += delta; + break; + case 1: + y += delta; + break; + case 2: + x -= delta; + break; + case 3: + y -= delta; + break; + } + sum += delta; + s++; + } + return sum; +} + + + +void u8g2_SetFont(u8g2_font_t *u8g2, const uint8_t *font) +{ + if ( u8g2->font != font ) + { + u8g2->font = font; + u8g2->font_decode.is_transparent = 0; + + u8g2_read_font_info(&(u8g2->font_info), font); + } +} + +void u8g2_SetForegroundColor(u8g2_font_t *u8g2, uint16_t fg) +{ + u8g2->font_decode.fg_color = fg; +} + +void u8g2_SetBackgroundColor(u8g2_font_t *u8g2, uint16_t bg) +{ + u8g2->font_decode.bg_color = bg; +} diff --git a/GUI/u8g2_font.h b/GUI/u8g2_font.h new file mode 100644 index 0000000..1e6e0d5 --- /dev/null +++ b/GUI/u8g2_font.h @@ -0,0 +1,157 @@ +/* + + U8g2_for_Adafruit_GFX.h + + Add unicode support and U8g2 fonts to Adafruit GFX libraries. + + U8g2 for Adafruit GFX Lib (https://github.com/olikraus/U8g2_for_Adafruit_GFX) + + Copyright (c) 2018, olikraus@gmail.com + All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list + of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, this + list of conditions and the following disclaimer in the documentation and/or other + materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ +#ifndef __U8G2_H +#define __U8G2_H + +#include + +#ifdef __GNUC__ +# define U8X8_NOINLINE __attribute__((noinline)) +# define U8X8_SECTION(name) __attribute__ ((section (name))) +# define U8X8_UNUSED __attribute__((unused)) +#else +# define U8X8_SECTION(name) +# define U8X8_NOINLINE +# define U8X8_UNUSED +#endif + +#if defined(__GNUC__) && defined(__AVR__) +# define U8X8_FONT_SECTION(name) U8X8_SECTION(".progmem." name) +# define u8x8_pgm_read(adr) pgm_read_byte_near(adr) +# define U8X8_PROGMEM PROGMEM +#endif + +#ifndef U8X8_FONT_SECTION +# define U8X8_FONT_SECTION(name) +#endif + +#ifndef u8x8_pgm_read +# define u8x8_pgm_read(adr) (*(const uint8_t *)(adr)) +#endif + +#ifndef U8X8_PROGMEM +# define U8X8_PROGMEM +#endif + +#define U8G2_FONT_SECTION(name) U8X8_FONT_SECTION(name) + +/* the macro U8G2_USE_LARGE_FONTS enables large fonts (>32K) */ +/* it can be enabled for those uC supporting larger arrays */ +#if defined(unix) || defined(__arm__) || defined(__arc__) || defined(ESP8266) || defined(ESP_PLATFORM) +#ifndef U8G2_USE_LARGE_FONTS +#define U8G2_USE_LARGE_FONTS +#endif +#endif + +typedef struct _u8g2_font_info_t +{ + /* offset 0 */ + uint8_t glyph_cnt; + uint8_t bbx_mode; + uint8_t bits_per_0; + uint8_t bits_per_1; + + /* offset 4 */ + uint8_t bits_per_char_width; + uint8_t bits_per_char_height; + uint8_t bits_per_char_x; + uint8_t bits_per_char_y; + uint8_t bits_per_delta_x; + + /* offset 9 */ + int8_t max_char_width; + int8_t max_char_height; /* overall height, NOT ascent. Instead ascent = max_char_height + y_offset */ + int8_t x_offset; + int8_t y_offset; + + /* offset 13 */ + int8_t ascent_A; + int8_t descent_g; /* usually a negative value */ + int8_t ascent_para; + int8_t descent_para; + + /* offset 17 */ + uint16_t start_pos_upper_A; + uint16_t start_pos_lower_a; + + /* offset 21 */ + uint16_t start_pos_unicode; +} u8g2_font_info_t; + +typedef struct _u8g2_font_decode_t +{ + const uint8_t *decode_ptr; /* pointer to the compressed data */ + + int16_t target_x; + int16_t target_y; + uint16_t fg_color; + uint16_t bg_color; + + int8_t x; /* local coordinates, (0,0) is upper left */ + int8_t y; + int8_t glyph_width; + int8_t glyph_height; + + uint8_t decode_bit_pos; /* bitpos inside a byte of the compressed data */ + uint8_t is_transparent; + uint8_t dir; /* direction */ +} u8g2_font_decode_t; + +typedef struct _u8g2_font_t +{ + const uint8_t *font; /* current font for all text procedures */ + + u8g2_font_decode_t font_decode; /* new font decode structure */ + u8g2_font_info_t font_info; /* new font info structure */ + + int8_t glyph_x_offset; /* set by u8g2_GetGlyphWidth as a side effect */ + + void *draw_hv_line_arg; + void (*draw_hv_line)(int16_t x, int16_t y, int16_t len, uint8_t dir, uint16_t color, void *arg); +} u8g2_font_t; + +uint8_t u8g2_IsGlyph(u8g2_font_t *u8g2, uint16_t requested_encoding); +int8_t u8g2_GetGlyphWidth(u8g2_font_t *u8g2, uint16_t requested_encoding); +void u8g2_SetFontMode(u8g2_font_t *u8g2, uint8_t is_transparent); +void u8g2_SetFontDirection(u8g2_font_t *u8g2, uint8_t dir); +int16_t u8g2_DrawGlyph(u8g2_font_t *u8g2, int16_t x, int16_t y, uint16_t encoding); +int16_t u8g2_DrawStr(u8g2_font_t *u8g2, int16_t x, int16_t y, const char *s); +void u8g2_SetFont(u8g2_font_t *u8g2, const uint8_t *font); +void u8g2_SetForegroundColor(u8g2_font_t *u8g2, uint16_t fg); +void u8g2_SetBackgroundColor(u8g2_font_t *u8g2, uint16_t bg); + +#endif diff --git a/Keil/EPD.uvprojx b/Keil/EPD.uvprojx index 3adab28..e3be889 100644 --- a/Keil/EPD.uvprojx +++ b/Keil/EPD.uvprojx @@ -340,7 +340,7 @@ BLE_STACK_SUPPORT_REQD S110 SWI_DISABLE0 SOFTDEVICE_PRESENT NRF51 - ..\config;..\EPD;..\components\toolchain;..\components\toolchain\cmsis\include;..\components\drivers_nrf\config;..\components\drivers_nrf\common;..\components\drivers_nrf\delay;..\components\drivers_nrf\gpiote;..\components\drivers_nrf\hal;..\components\drivers_nrf\spi_master;..\components\drivers_nrf\pstorage;..\components\drivers_nrf\pstorage\config;..\components\drivers_nrf\twi_master;..\components\libraries\trace;..\components\libraries\timer;..\components\libraries\util;..\components\ble\common;..\components\ble\ble_advertising;..\components\softdevice\common\softdevice_handler;..\components\softdevice\s110\headers + ..\config;..\EPD;..\GUI;..\components\toolchain;..\components\toolchain\cmsis\include;..\components\drivers_nrf\config;..\components\drivers_nrf\common;..\components\drivers_nrf\delay;..\components\drivers_nrf\gpiote;..\components\drivers_nrf\hal;..\components\drivers_nrf\spi_master;..\components\drivers_nrf\pstorage;..\components\drivers_nrf\pstorage\config;..\components\drivers_nrf\twi_master;..\components\libraries\trace;..\components\libraries\timer;..\components\libraries\util;..\components\ble\common;..\components\ble\ble_advertising;..\components\softdevice\common\softdevice_handler;..\components\softdevice\s110\headers @@ -358,7 +358,7 @@ BLE_STACK_SUPPORT_REQD S110 SWI_DISABLE0 SOFTDEVICE_PRESENT NRF51 - ..\config;..\EPD;..\components\toolchain;..\components\toolchain\cmsis\include;..\components\drivers_nrf\config;..\components\drivers_nrf\common;..\components\drivers_nrf\delay;..\components\drivers_nrf\gpiote;..\components\drivers_nrf\hal;..\components\drivers_nrf\spi_master;..\components\drivers_nrf\pstorage;..\components\drivers_nrf\pstorage\config;..\components\drivers_nrf\twi_master;..\components\libraries\trace;..\components\libraries\timer;..\components\libraries\util;..\components\ble\common;..\components\ble\ble_advertising;..\components\softdevice\common\softdevice_handler;..\components\softdevice\s110\headers + ..\config;..\EPD;..\GUI;..\components\toolchain;..\components\toolchain\cmsis\include;..\components\drivers_nrf\config;..\components\drivers_nrf\common;..\components\drivers_nrf\delay;..\components\drivers_nrf\gpiote;..\components\drivers_nrf\hal;..\components\drivers_nrf\spi_master;..\components\drivers_nrf\pstorage;..\components\drivers_nrf\pstorage\config;..\components\drivers_nrf\twi_master;..\components\libraries\trace;..\components\libraries\timer;..\components\libraries\util;..\components\ble\common;..\components\ble\ble_advertising;..\components\softdevice\common\softdevice_handler;..\components\softdevice\s110\headers @@ -399,76 +399,20 @@ 5 ..\config\nrf_drv_config.h - - pstorage_platform.h - 5 - ..\config\pstorage_platform.h - EPD - EPD_Test.c + EPD_ble.c 1 - ..\EPD\EPD_Test.c - - - 2 - 0 - 0 - 0 - 0 - 0 - 2 - 2 - 2 - 2 - 11 - - - 1 - - - - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 0 - 2 - 2 - 2 - 2 - 2 - 0 - 0 - 2 - 2 - 2 - 2 - 2 - - - - - - - - - + ..\EPD\EPD_ble.c - DEV_Config.c + EPD_driver.c 1 - ..\EPD\DEV_Config.c + ..\EPD\EPD_driver.c EPD_4in2.c @@ -485,10 +429,35 @@ 1 ..\EPD\EPD_4in2b_V2.c + + + + GUI + - EPD_ble.c + Calendar.c 1 - ..\EPD\EPD_ble.c + ..\GUI\Calendar.c + + + Lunar.c + 1 + ..\GUI\Lunar.c + + + fonts.c + 1 + ..\GUI\fonts.c + + + u8g2_font.c + 1 + ..\GUI\u8g2_font.c + + + Adafruit_GFX.c + 1 + ..\GUI\Adafruit_GFX.c @@ -890,7 +859,7 @@ 1 0x18000 - 0x8000 + 0x18000 1 @@ -953,7 +922,7 @@ BLE_STACK_SUPPORT_REQD S110 SWI_DISABLE0 SOFTDEVICE_PRESENT NRF51 DEBUG NRF_LOG_USES_RTT=1 - ..\config;..\EPD;..\components\toolchain;..\components\toolchain\cmsis\include;..\components\drivers_nrf\config;..\components\drivers_nrf\common;..\components\drivers_nrf\delay;..\components\drivers_nrf\gpiote;..\components\drivers_nrf\hal;..\components\drivers_nrf\spi_master;..\components\drivers_nrf\pstorage;..\components\drivers_nrf\pstorage\config;..\components\drivers_nrf\twi_master;..\components\drivers_ext\segger_rtt;..\components\libraries\trace;..\components\libraries\timer;..\components\libraries\util;..\components\ble\common;..\components\ble\ble_advertising;..\components\softdevice\common\softdevice_handler;..\components\softdevice\s110\headers + ..\config;..\EPD;..\GUI;..\components\toolchain;..\components\toolchain\cmsis\include;..\components\drivers_nrf\config;..\components\drivers_nrf\common;..\components\drivers_nrf\delay;..\components\drivers_nrf\gpiote;..\components\drivers_nrf\hal;..\components\drivers_nrf\spi_master;..\components\drivers_nrf\pstorage;..\components\drivers_nrf\pstorage\config;..\components\drivers_nrf\twi_master;..\components\drivers_ext\segger_rtt;..\components\libraries\trace;..\components\libraries\timer;..\components\libraries\util;..\components\ble\common;..\components\ble\ble_advertising;..\components\softdevice\common\softdevice_handler;..\components\softdevice\s110\headers @@ -971,7 +940,7 @@ BLE_STACK_SUPPORT_REQD S110 SWI_DISABLE0 SOFTDEVICE_PRESENT NRF51 - ..\config;..\EPD;..\components\toolchain;..\components\toolchain\cmsis\include;..\components\drivers_nrf\config;..\components\drivers_nrf\common;..\components\drivers_nrf\delay;..\components\drivers_nrf\gpiote;..\components\drivers_nrf\hal;..\components\drivers_nrf\spi_master;..\components\drivers_nrf\pstorage;..\components\drivers_nrf\pstorage\config;..\components\drivers_nrf\twi_master;..\components\drivers_ext\segger_rtt;..\components\libraries\trace;..\components\libraries\timer;..\components\libraries\util;..\components\ble\common;..\components\ble\ble_advertising;..\components\softdevice\common\softdevice_handler;..\components\softdevice\s110\headers + ..\config;..\EPD;..\GUI;..\components\toolchain;..\components\toolchain\cmsis\include;..\components\drivers_nrf\config;..\components\drivers_nrf\common;..\components\drivers_nrf\delay;..\components\drivers_nrf\gpiote;..\components\drivers_nrf\hal;..\components\drivers_nrf\spi_master;..\components\drivers_nrf\pstorage;..\components\drivers_nrf\pstorage\config;..\components\drivers_nrf\twi_master;..\components\drivers_ext\segger_rtt;..\components\libraries\trace;..\components\libraries\timer;..\components\libraries\util;..\components\ble\common;..\components\ble\ble_advertising;..\components\softdevice\common\softdevice_handler;..\components\softdevice\s110\headers @@ -1012,25 +981,20 @@ 5 ..\config\nrf_drv_config.h - - pstorage_platform.h - 5 - ..\config\pstorage_platform.h - EPD - EPD_Test.c + EPD_ble.c 1 - ..\EPD\EPD_Test.c + ..\EPD\EPD_ble.c - DEV_Config.c + EPD_driver.c 1 - ..\EPD\DEV_Config.c + ..\EPD\EPD_driver.c EPD_4in2.c @@ -1047,10 +1011,35 @@ 1 ..\EPD\EPD_4in2b_V2.c + + + + GUI + - EPD_ble.c + Calendar.c 1 - ..\EPD\EPD_ble.c + ..\GUI\Calendar.c + + + Lunar.c + 1 + ..\GUI\Lunar.c + + + fonts.c + 1 + ..\GUI\fonts.c + + + u8g2_font.c + 1 + ..\GUI\u8g2_font.c + + + Adafruit_GFX.c + 1 + ..\GUI\Adafruit_GFX.c @@ -1574,25 +1563,20 @@ 5 ..\config\nrf_drv_config.h - - pstorage_platform.h - 5 - ..\config\pstorage_platform.h - EPD - EPD_Test.c + EPD_ble.c 1 - ..\EPD\EPD_Test.c + ..\EPD\EPD_ble.c - DEV_Config.c + EPD_driver.c 1 - ..\EPD\DEV_Config.c + ..\EPD\EPD_driver.c EPD_4in2.c @@ -1609,10 +1593,35 @@ 1 ..\EPD\EPD_4in2b_V2.c + + + + GUI + - EPD_ble.c + Calendar.c 1 - ..\EPD\EPD_ble.c + ..\GUI\Calendar.c + + + Lunar.c + 1 + ..\GUI\Lunar.c + + + fonts.c + 1 + ..\GUI\fonts.c + + + u8g2_font.c + 1 + ..\GUI\u8g2_font.c + + + Adafruit_GFX.c + 1 + ..\GUI\Adafruit_GFX.c diff --git a/config/nrf_drv_config.h b/config/nrf_drv_config.h index 5ff228c..3c10ce0 100644 --- a/config/nrf_drv_config.h +++ b/config/nrf_drv_config.h @@ -138,7 +138,7 @@ #define SPI1_ENABLED 0 #if (SPI1_ENABLED == 1) -#define SPI1_USE_EASY_DMA 0 +#define SPI1_USE_EASY_DMA 1 #define SPI1_CONFIG_SCK_PIN 2 #define SPI1_CONFIG_MOSI_PIN 3 @@ -230,7 +230,7 @@ #define TWI0_INSTANCE_INDEX 0 #endif -#define TWI1_ENABLED 1 +#define TWI1_ENABLED 0 #if (TWI1_ENABLED == 1) #define TWI1_CONFIG_FREQUENCY NRF_TWI_FREQ_100K @@ -326,7 +326,7 @@ #if (WDT_ENABLED == 1) #define WDT_CONFIG_BEHAVIOUR NRF_WDT_BEHAVIOUR_RUN_SLEEP -#define WDT_CONFIG_RELOAD_VALUE 2000 +#define WDT_CONFIG_RELOAD_VALUE 20000 #define WDT_CONFIG_IRQ_PRIORITY APP_IRQ_PRIORITY_HIGH #endif diff --git a/config/pstorage_platform.h b/config/pstorage_platform.h deleted file mode 100644 index 910b99b..0000000 --- a/config/pstorage_platform.h +++ /dev/null @@ -1,78 +0,0 @@ -/* Copyright (c) 2013 Nordic Semiconductor. All Rights Reserved. - * - * The information contained herein is property of Nordic Semiconductor ASA. - * Terms and conditions of usage are described in detail in NORDIC - * SEMICONDUCTOR STANDARD SOFTWARE LICENSE AGREEMENT. - * - * Licensees are granted free, non-transferable use of the information. NO - * WARRANTY of ANY KIND is provided. This heading must NOT be removed from - * the file. - * - */ - - /** @cond To make doxygen skip this file */ - -/** @file - * This header contains defines with respect persistent storage that are specific to - * persistent storage implementation and application use case. - */ -#ifndef PSTORAGE_PL_H__ -#define PSTORAGE_PL_H__ - -#include -#include "nrf.h" - -static __INLINE uint16_t pstorage_flash_page_size() -{ - return (uint16_t)NRF_FICR->CODEPAGESIZE; -} - -#define PSTORAGE_FLASH_PAGE_SIZE pstorage_flash_page_size() /**< Size of one flash page. */ -#define PSTORAGE_FLASH_EMPTY_MASK 0xFFFFFFFF /**< Bit mask that defines an empty address in flash. */ - -#ifdef NRF51 -#define BOOTLOADER_ADDRESS (NRF_UICR->BOOTLOADERADDR) -#elif defined NRF52 -#define BOOTLOADER_ADDRESS (PSTORAGE_FLASH_EMPTY_MASK) -#endif - -static __INLINE uint32_t pstorage_flash_page_end() -{ - uint32_t bootloader_addr = BOOTLOADER_ADDRESS; - - return ((bootloader_addr != PSTORAGE_FLASH_EMPTY_MASK) ? - (bootloader_addr/ PSTORAGE_FLASH_PAGE_SIZE) : NRF_FICR->CODESIZE); -} - -#define PSTORAGE_FLASH_PAGE_END pstorage_flash_page_end() - -#define PSTORAGE_NUM_OF_PAGES 1 /**< Number of flash pages allocated for the pstorage module excluding the swap page, configurable based on system requirements. */ -#define PSTORAGE_MIN_BLOCK_SIZE 0x0010 /**< Minimum size of block that can be registered with the module. Should be configured based on system requirements, recommendation is not have this value to be at least size of word. */ - -#define PSTORAGE_DATA_START_ADDR ((PSTORAGE_FLASH_PAGE_END - PSTORAGE_NUM_OF_PAGES - 1) \ - * PSTORAGE_FLASH_PAGE_SIZE) /**< Start address for persistent data, configurable according to system requirements. */ -#define PSTORAGE_DATA_END_ADDR ((PSTORAGE_FLASH_PAGE_END - 1) * PSTORAGE_FLASH_PAGE_SIZE) /**< End address for persistent data, configurable according to system requirements. */ -#define PSTORAGE_SWAP_ADDR PSTORAGE_DATA_END_ADDR /**< Top-most page is used as swap area for clear and update. */ - -#define PSTORAGE_MAX_BLOCK_SIZE PSTORAGE_FLASH_PAGE_SIZE /**< Maximum size of block that can be registered with the module. Should be configured based on system requirements. And should be greater than or equal to the minimum size. */ -#define PSTORAGE_CMD_QUEUE_SIZE 10 /**< Maximum number of flash access commands that can be maintained by the module for all applications. Configurable. */ - - -/** Abstracts persistently memory block identifier. */ -typedef uint32_t pstorage_block_t; - -typedef struct -{ - uint32_t module_id; /**< Module ID.*/ - pstorage_block_t block_id; /**< Block ID.*/ -} pstorage_handle_t; - -typedef uint16_t pstorage_size_t; /** Size of length and offset fields. */ - -/**@brief Handles Flash Access Result Events. To be called in the system event dispatcher of the application. */ -void pstorage_sys_event_handler (uint32_t sys_evt); - -#endif // PSTORAGE_PL_H__ - -/** @} */ -/** @endcond */ diff --git a/html/index.html b/html/index.html index 497f91c..941d9cc 100644 --- a/html/index.html +++ b/html/index.html @@ -36,7 +36,7 @@

4.2 寸电子墨水屏蓝牙控制器(nRF51)

- 蓝牙 + 蓝牙连接
@@ -57,16 +57,19 @@
- 传图 + 屏幕控制
- +
- + +
+
+
@@ -110,6 +113,7 @@
  • 驱动选择:黑白屏可尝试 EPD_4in2 / EPD_4in2_V2, 三色屏选择 EPD_4in2b_V2 (选错驱动可能会导致任何未知的异常,重启即可恢复)
  • 引脚配置:格式为十六进制,顺序:MOSI/SCLK/CS/DC/ST/BUSY/BS,必须按此顺序包含完整的 7 个引脚配置(没有用到的引脚可配置为 FF
  • 确认间隔: 这个间隔指的是数据包数量间隔,即发送此数量的不确认响应的数据包后才发送一次需确认响应的数据包。加大此值可优化传图速度,但是丢包风险也更大(你可能会发现图片有部分位置显示不正常,此时需调小这个值)。 +
  • 日历模式: 点击“日历模式”按钮将自动从浏览器同步时间到墨水屏,并切换到日历显示。
  • 指令列表(指令和参数全部要使用十六进制):
      @@ -124,6 +128,10 @@
    • 06: 屏幕睡眠
  • +
  • 日历模式: +
      +
    • 20+UNIX 时间戳: 同步时间并开启日历模式
    • +
  • 系统相关:
    • 90+配置: 写入配置信息(重启生效,格式参考源码 epd_config_t
    • diff --git a/html/js/main.js b/html/js/main.js index 2a58e49..3d03dad 100644 --- a/html/js/main.js +++ b/html/js/main.js @@ -17,6 +17,8 @@ const EpdCmd = { DISPLAY: 0x05, SLEEP: 0x06, + SET_TIME: 0x20, + SET_CONFIG: 0x90, SYS_RESET: 0x91, SYS_SLEEP: 0x92, @@ -31,7 +33,7 @@ function resetVariables() { } async function handleError(error) { - console.log(error); + console.error(error); resetVariables(); if (bleDevice == null) return; @@ -48,22 +50,30 @@ async function handleError(error) { async function write(cmd, data, withResponse=true) { if (!epdCharacteristic) { addLog("服务不可用,请检查蓝牙连接"); - return; + return false; } let payload = [cmd]; if (data) { if (typeof data == 'string') data = hex2bytes(data); if (data instanceof Uint8Array) data = Array.from(data); payload.push(...data) - }; + } if (payload.length > MAX_PACKET_SIZE) { - throw new Error("BLE packet too large!"); + addLog("BLE packet too large!"); + return false; } addLog(` ${bytes2hex(payload)}`); - if (withResponse) - await epdCharacteristic.writeValueWithResponse(Uint8Array.from(payload)); - else - await epdCharacteristic.writeValueWithoutResponse(Uint8Array.from(payload)); + try { + if (withResponse) + await epdCharacteristic.writeValueWithResponse(Uint8Array.from(payload)); + else + await epdCharacteristic.writeValueWithoutResponse(Uint8Array.from(payload)); + } catch (e) { + console.error(e); + if (e.message) addLog(e.message); + return false; + } + return true; } async function epdWrite(cmd, data) { @@ -95,7 +105,21 @@ async function setDriver() { await write(EpdCmd.INIT, document.getElementById("epddriver").value); } -async function clearscreen() { +async function syncTime() { + const timestamp = new Date().getTime() / 1000; + const data = new Uint8Array([ + (timestamp >> 24) & 0xFF, + (timestamp >> 16) & 0xFF, + (timestamp >> 8) & 0xFF, + timestamp & 0xFF, + -(new Date().getTimezoneOffset() / 60) + ]); + if(await write(EpdCmd.SET_TIME, data)) { + addLog("日历模式:时间已同步!需要一定时间刷新,请耐心等待。"); + } +} + +async function clearScreen() { if(confirm('确认清除屏幕内容?')) { await write(EpdCmd.CLEAR); } @@ -163,6 +187,7 @@ function updateButtonStatus() { const status = connected ? null : 'disabled'; document.getElementById("reconnectbutton").disabled = (gattServer == null || gattServer.connected) ? 'disabled' : null; document.getElementById("sendcmdbutton").disabled = status; + document.getElementById("synctimebutton").disabled = status; document.getElementById("clearscreenbutton").disabled = status; document.getElementById("sendimgbutton").disabled = status; document.getElementById("setDriverbutton").disabled = status; @@ -178,18 +203,18 @@ function disconnect() { async function preConnect() { if (gattServer != null && gattServer.connected) { if (bleDevice != null && bleDevice.gatt.connected) { - await write(EpdCmd.SLEEP); bleDevice.gatt.disconnect(); } } else { - connectTrys = 0; + reconnectTrys = 0; try { bleDevice = await navigator.bluetooth.requestDevice({ optionalServices: ['62750001-d828-918d-fb46-b6c11c675aec'], acceptAllDevices: true }); } catch (e) { + console.error(e); if (e.message) addLog(e.message); return; } @@ -204,7 +229,7 @@ async function preConnect() { } async function reConnect() { - connectTrys = 0; + reconnectTrys = 0; if (bleDevice != null && bleDevice.gatt.connected) bleDevice.gatt.disconnect(); resetVariables(); diff --git a/main.c b/main.c index 56eabc9..5461926 100644 --- a/main.c +++ b/main.c @@ -26,16 +26,16 @@ #include "pstorage.h" #include "app_error.h" #include "app_timer.h" +#include "app_util_platform.h" +#include "nrf_drv_gpiote.h" #include "EPD_ble.h" -#ifdef DEBUG -#include "EPD_Test.h" -#endif +#include "Calendar.h" #define IS_SRVC_CHANGED_CHARACT_PRESENT 1 /**< Include or not the service_changed characteristic. if not enabled, the server's database cannot be changed for the lifetime of the device*/ #define DEVICE_NAME "NRF_EPD" /**< Name of device. Will be included in the advertising data. */ -#define APP_ADV_INTERVAL 300 /**< The advertising interval (in units of 0.625 ms. This value corresponds to 40 ms). */ -#define APP_ADV_TIMEOUT_IN_SECONDS 180 /**< The advertising timeout (in units of seconds). */ +#define APP_ADV_INTERVAL 320 /**< The advertising interval (in units of 0.625 ms. This value corresponds to 200 ms). */ +#define APP_ADV_TIMEOUT_IN_SECONDS 120 /**< The advertising timeout (in units of seconds). */ #define APP_TIMER_PRESCALER 0 /**< Value of the RTC1 PRESCALER register. */ #define APP_TIMER_OP_QUEUE_SIZE 4 /**< Size of timer operation queues. */ @@ -48,11 +48,37 @@ #define NEXT_CONN_PARAMS_UPDATE_DELAY APP_TIMER_TICKS(30000, APP_TIMER_PRESCALER) /**< Time between each call to sd_ble_gap_conn_param_update after the first call (30 seconds). */ #define MAX_CONN_PARAMS_UPDATE_COUNT 3 /**< Number of attempts before giving up the connection parameter negotiation. */ +#define CLOCK_TIMER_INTERVAL APP_TIMER_TICKS(1000, APP_TIMER_PRESCALER) /**< Clock timer interval (ticks). */ + #define DEAD_BEEF 0xDEADBEEF /**< Value used as error code on stack dump, can be used to identify stack location on stack unwind. */ +static uint16_t m_driver_refs = 0; static uint16_t m_conn_handle = BLE_CONN_HANDLE_INVALID; /**< Handle of the current connection. */ static ble_uuid_t m_adv_uuids[] = {{BLE_UUID_EPD_SERVICE, EPD_SERVICE_UUID_TYPE}}; /**< Universally unique service identifier. */ static ble_epd_t m_epd; /**< Structure to identify the EPD Service. */ +static uint32_t m_timestamp = 1735689600; /**< Current timestamp. */ +static bool m_update_calendar = false; /**< Update calendar if true */ +static bool m_calendar_mode = false; /**< Whether we are in calendar mode */ + +APP_TIMER_DEF(m_clock_timer_id); /**< Clock timer. */ + +static void epd_driver_init() +{ + if (m_driver_refs == 0) { + NRF_LOG_PRINTF("[EPD]: driver init\n"); + DEV_Module_Init(); + } + m_driver_refs++; +} + +static void epd_driver_exit() +{ + m_driver_refs--; + if (m_driver_refs == 0) { + NRF_LOG_PRINTF("[EPD]: driver exit\n"); + DEV_Module_Exit(); + } +} /**@brief Callback function for asserts in the SoftDevice. * @@ -70,6 +96,19 @@ void assert_nrf_callback(uint16_t line_num, const uint8_t * p_file_name) app_error_handler(DEAD_BEEF, line_num, p_file_name); } +static void clock_timer_timeout_handler(void * p_context) +{ + UNUSED_PARAMETER(p_context); + + m_timestamp++; + + // Update calendar on 00:00:00 + if (m_calendar_mode && m_timestamp % 86400 == 0) + { + m_update_calendar = true; + } +} + /**@brief Function for the Timer initialization. * * @details Initializes the timer module. This creates and starts application timers. @@ -78,6 +117,54 @@ static void timers_init(void) { // Initialize timer module. APP_TIMER_INIT(APP_TIMER_PRESCALER, APP_TIMER_OP_QUEUE_SIZE, false); + + // Create timers. + uint32_t err_code = app_timer_create(&m_clock_timer_id, + APP_TIMER_MODE_REPEATED, + clock_timer_timeout_handler); + APP_ERROR_CHECK(err_code); +} + +/**@brief Function for starting application timers. + */ +static void application_timers_start(void) +{ + // Start application timers. + uint32_t err_code = app_timer_start(m_clock_timer_id, CLOCK_TIMER_INTERVAL, NULL); + APP_ERROR_CHECK(err_code); +} + +bool epd_cmd_callback(uint8_t cmd, uint8_t *data, uint16_t len) +{ + switch (cmd) + { + case EPD_CMD_SET_TIME: + if (len < 4) { + NRF_LOG_PRINTF("invalid time data!\n"); + return false; + } + + NRF_LOG_PRINTF("time: %02x %02x %02x %02x\n", data[0], data[1], data[2], data[3]); + if (len > 4) { + NRF_LOG_PRINTF("timezone: %d\n", (int8_t)data[4]); + } + + app_timer_stop(m_clock_timer_id); + m_timestamp = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3]; + m_timestamp += (len > 4 ? (int8_t)data[4] : 8) * 60 * 60; // timezone + app_timer_start(m_clock_timer_id, CLOCK_TIMER_INTERVAL, NULL); + + m_calendar_mode = true; + m_update_calendar = true; + return true; + case EPD_CMD_CLEAR: + case EPD_CMD_DISPLAY: + m_calendar_mode = false; + break; + default: + break; + } + return false; } /**@brief Function for initializing services that will be used by the application. @@ -90,7 +177,7 @@ static void services_init(void) APP_ERROR_CHECK(err_code); memset(&m_epd, 0, sizeof(ble_epd_t)); - err_code = ble_epd_init(&m_epd); + err_code = ble_epd_init(&m_epd, epd_cmd_callback); APP_ERROR_CHECK(err_code); } @@ -187,6 +274,12 @@ static void conn_params_init(void) APP_ERROR_CHECK(err_code); } +static void advertising_start(void) +{ + NRF_LOG_PRINTF("advertising start\n"); + uint32_t err_code = ble_advertising_start(BLE_ADV_MODE_FAST); + APP_ERROR_CHECK(err_code); +} /**@brief Function for putting the chip into sleep mode. * @@ -194,6 +287,8 @@ static void conn_params_init(void) */ static void sleep_mode_enter(void) { + NRF_LOG_PRINTF("Entering deep sleep mode\n"); + // Prepare wakeup pin ble_epd_sleep_prepare(&m_epd); @@ -202,6 +297,28 @@ static void sleep_mode_enter(void) APP_ERROR_CHECK(err_code); } +void gpiote_evt_handler(nrf_drv_gpiote_pin_t pin, nrf_gpiote_polarity_t action) { + NRF_LOG_PRINTF("pin: %d, event: %d\n", pin, action); + + nrf_drv_gpiote_in_event_disable(pin); + nrf_drv_gpiote_in_uninit(pin); + nrf_drv_gpiote_uninit(); + + advertising_start(); +} + +static void setup_wakeup_pin(nrf_drv_gpiote_pin_t pin) { + NRF_LOG_PRINTF("Setting up wakeup pin\n"); + + ret_code_t err_code = nrf_drv_gpiote_init(); + APP_ERROR_CHECK(err_code); + nrf_drv_gpiote_in_config_t config = GPIOTE_CONFIG_IN_SENSE_LOTOHI(false); + + err_code = nrf_drv_gpiote_in_init(pin, &config, gpiote_evt_handler); + APP_ERROR_CHECK(err_code); + + nrf_drv_gpiote_in_event_enable(pin, true); +} /**@brief Function for handling advertising events. * @@ -216,7 +333,12 @@ static void on_adv_evt(ble_adv_evt_t ble_adv_evt) case BLE_ADV_EVT_FAST: break; case BLE_ADV_EVT_IDLE: - sleep_mode_enter(); + NRF_LOG_PRINTF("advertising timeout\n"); + if (m_calendar_mode) { + setup_wakeup_pin(m_epd.config.wakeup_pin); + } else { + sleep_mode_enter(); + } break; default: break; @@ -235,12 +357,13 @@ static void on_ble_evt(ble_evt_t * p_ble_evt) { case BLE_GAP_EVT_CONNECTED: m_conn_handle = p_ble_evt->evt.gap_evt.conn_handle; + epd_driver_init(); break; case BLE_GAP_EVT_DISCONNECTED: m_conn_handle = BLE_CONN_HANDLE_INVALID; - err_code = ble_advertising_start(BLE_ADV_MODE_FAST); - APP_ERROR_CHECK(err_code); + epd_driver_exit(); + advertising_start(); break; case BLE_GAP_EVT_SEC_PARAMS_REQUEST: @@ -364,6 +487,19 @@ static void power_manage(void) { uint32_t err_code = sd_app_evt_wait(); APP_ERROR_CHECK(err_code); + + NRF_LOG_PRINTF("timestamp: %d\n", m_timestamp); +} + +static void calendar_update(void) +{ + if (!m_update_calendar) return; + + m_update_calendar = false; + epd_driver_init(); + m_epd.driver->init(); + DrawCalendar(m_timestamp); + epd_driver_exit(); } /**@brief Function for application main entry. @@ -384,17 +520,16 @@ int main(void) advertising_init(); conn_params_init(); - err_code = ble_advertising_start(BLE_ADV_MODE_FAST); - APP_ERROR_CHECK(err_code); + // Start execution. + application_timers_start(); + + advertising_start(); NRF_LOG_PRINTF("done.\n"); -#ifdef DEBUG - EPD_4in2_test(); -#endif - for (;;) { + calendar_update(); power_manage(); } }