mirror of
https://github.com/jam422470459/EPD-nRF52-hema213.git
synced 2025-12-06 08:32:54 +08:00
add GUI emulator
This commit is contained in:
19
.github/workflows/build.yml
vendored
19
.github/workflows/build.yml
vendored
@@ -49,3 +49,22 @@ jobs:
|
||||
path: |
|
||||
_build/nrf52811_xxaa.hex
|
||||
SDK/17.1.0_ddde560/components/softdevice/s112/hex/s112_nrf52_7.2.0_softdevice.hex
|
||||
|
||||
win32:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Install MSYS2
|
||||
uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
update: true
|
||||
install: >-
|
||||
make
|
||||
mingw-w64-x86_64-gcc
|
||||
- name: Build
|
||||
run: make -f Makefile.win32
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: emulator
|
||||
path: emulator.exe
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -33,6 +33,7 @@
|
||||
*.obj
|
||||
*.o
|
||||
*.sbr
|
||||
*.exe
|
||||
|
||||
# Build files
|
||||
# define exception below if needed
|
||||
|
||||
@@ -43,7 +43,16 @@ static void epd_gui_update(void * p_event_data, uint16_t event_size)
|
||||
|
||||
EPD_GPIO_Init();
|
||||
epd_model_t *epd = epd_init((epd_model_id_t)p_epd->config.model_id);
|
||||
DrawGUI(epd, event->timestamp, p_epd->display_mode);
|
||||
gui_data_t data = {
|
||||
.bwr = epd->bwr,
|
||||
.width = epd->width,
|
||||
.height = epd->height,
|
||||
.timestamp = event->timestamp,
|
||||
.temperature = epd->drv->read_temp(),
|
||||
.voltage = EPD_ReadVoltage(),
|
||||
};
|
||||
DrawGUI(&data, epd->drv->write_image, p_epd->display_mode);
|
||||
epd->drv->refresh();
|
||||
EPD_GPIO_Uninit();
|
||||
}
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@ static void UC8176_PowerOff(void)
|
||||
int8_t UC8176_Read_Temp(void)
|
||||
{
|
||||
EPD_WriteCommand(CMD_TSC);
|
||||
UC8176_WaitBusy(100);
|
||||
return (int8_t) EPD_ReadByte();
|
||||
}
|
||||
|
||||
|
||||
36
GUI/GUI.c
36
GUI/GUI.c
@@ -1,12 +1,8 @@
|
||||
#include "Adafruit_GFX.h"
|
||||
#include "fonts.h"
|
||||
#include "Lunar.h"
|
||||
#include "GUI.h"
|
||||
#include "nrf_log.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#define PAGE_HEIGHT ((__HEAP_SIZE / 50) - 4)
|
||||
|
||||
#define GFX_printf_styled(gfx, fg, bg, font, ...) \
|
||||
GFX_setTextColor(gfx, fg, bg); \
|
||||
GFX_setFont(gfx, font); \
|
||||
@@ -142,13 +138,12 @@ static void DrawTime(Adafruit_GFX *gfx, tm_t *tm, int16_t x, int16_t y, uint16_t
|
||||
Draw7Number(gfx, tm->tm_min, x, y, cS, GFX_BLACK, GFX_WHITE, nD);
|
||||
}
|
||||
|
||||
static void DrawBattery(Adafruit_GFX *gfx, int16_t x, int16_t y)
|
||||
static void DrawBattery(Adafruit_GFX *gfx, int16_t x, int16_t y, float voltage)
|
||||
{
|
||||
float vol = EPD_ReadVoltage();
|
||||
uint8_t level = (uint8_t)(vol * 100 / 4.2);
|
||||
uint8_t level = (uint8_t)(voltage * 100 / 4.2);
|
||||
GFX_setCursor(gfx, x - 26, y + 9);
|
||||
GFX_setFont(gfx, u8g2_font_wqy9_t_lunar);
|
||||
GFX_printf(gfx, "%.1fV", vol);
|
||||
GFX_printf(gfx, "%.1fV", voltage);
|
||||
GFX_fillRect(gfx, x, y, 20, 10, GFX_WHITE);
|
||||
GFX_drawRect(gfx, x, y, 20, 10, GFX_BLACK);
|
||||
GFX_fillRect(gfx, x + 20, y + 4, 2, 2, GFX_BLACK);
|
||||
@@ -162,7 +157,7 @@ static void DrawTemperature(Adafruit_GFX *gfx, int16_t x, int16_t y, int8_t temp
|
||||
GFX_printf(gfx, "%d℃", temp);
|
||||
}
|
||||
|
||||
static void DrawClock(Adafruit_GFX *gfx, tm_t *tm, struct Lunar_Date *Lunar, int8_t temp)
|
||||
static void DrawClock(Adafruit_GFX *gfx, tm_t *tm, struct Lunar_Date *Lunar, gui_data_t *data)
|
||||
{
|
||||
DrawDate(gfx, 40, 36, tm);
|
||||
GFX_setCursor(gfx, 40, 58);
|
||||
@@ -172,8 +167,8 @@ static void DrawClock(Adafruit_GFX *gfx, tm_t *tm, struct Lunar_Date *Lunar, int
|
||||
GFX_printf(gfx, "%s%s%s", Lunar_MonthLeapString[Lunar->IsLeap], Lunar_MonthString[Lunar->Month],
|
||||
Lunar_DateString[Lunar->Date]);
|
||||
|
||||
DrawBattery(gfx, 330, 25);
|
||||
DrawTemperature(gfx, 330, 58, temp);
|
||||
DrawBattery(gfx, 330, 25, data->voltage);
|
||||
DrawTemperature(gfx, 330, 58, data->temperature);
|
||||
|
||||
GFX_drawFastHLine(gfx, 30, 68, 330, GFX_BLACK);
|
||||
DrawTime(gfx, tm, 70, 98, 5, 2);
|
||||
@@ -197,24 +192,23 @@ static void DrawClock(Adafruit_GFX *gfx, tm_t *tm, struct Lunar_Date *Lunar, int
|
||||
}
|
||||
}
|
||||
|
||||
void DrawGUI(epd_model_t *epd, uint32_t timestamp, display_mode_t mode)
|
||||
void DrawGUI(gui_data_t *data, buffer_callback draw, display_mode_t mode)
|
||||
{
|
||||
tm_t tm = {0};
|
||||
struct Lunar_Date Lunar;
|
||||
|
||||
transformTime(timestamp, &tm);
|
||||
transformTime(data->timestamp, &tm);
|
||||
LUNAR_SolarToLunar(&Lunar, tm.tm_year + YEAR0, tm.tm_mon + 1, tm.tm_mday);
|
||||
|
||||
Adafruit_GFX gfx;
|
||||
|
||||
if (epd->bwr)
|
||||
GFX_begin_3c(&gfx, epd->width, epd->height, PAGE_HEIGHT);
|
||||
if (data->bwr)
|
||||
GFX_begin_3c(&gfx, data->width, data->height, PAGE_HEIGHT);
|
||||
else
|
||||
GFX_begin(&gfx, epd->width, epd->height, PAGE_HEIGHT);
|
||||
GFX_begin(&gfx, data->width, data->height, PAGE_HEIGHT);
|
||||
|
||||
GFX_firstPage(&gfx);
|
||||
do {
|
||||
NRF_LOG_DEBUG("page %d\n", gfx.current_page);
|
||||
GFX_fillScreen(&gfx, GFX_WHITE);
|
||||
|
||||
switch (mode) {
|
||||
@@ -222,16 +216,12 @@ void DrawGUI(epd_model_t *epd, uint32_t timestamp, display_mode_t mode)
|
||||
DrawCalendar(&gfx, &tm, &Lunar);
|
||||
break;
|
||||
case MODE_CLOCK:
|
||||
DrawClock(&gfx, &tm, &Lunar, epd->drv->read_temp());
|
||||
DrawClock(&gfx, &tm, &Lunar, data);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} while(GFX_nextPage(&gfx, epd->drv->write_image));
|
||||
} while(GFX_nextPage(&gfx, draw));
|
||||
|
||||
GFX_end(&gfx);
|
||||
|
||||
NRF_LOG_DEBUG("display start\n");
|
||||
epd->drv->refresh();
|
||||
NRF_LOG_DEBUG("display end\n");
|
||||
}
|
||||
|
||||
18
GUI/GUI.h
18
GUI/GUI.h
@@ -1,8 +1,11 @@
|
||||
#ifndef __GUI_H
|
||||
#define __GUI_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "EPD_driver.h"
|
||||
#include "Adafruit_GFX.h"
|
||||
|
||||
#ifndef PAGE_HEIGHT
|
||||
#define PAGE_HEIGHT ((__HEAP_SIZE / 50) - 4)
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
MODE_NONE = 0,
|
||||
@@ -10,6 +13,15 @@ typedef enum {
|
||||
MODE_CLOCK = 2,
|
||||
} display_mode_t;
|
||||
|
||||
void DrawGUI(epd_model_t *epd, uint32_t timestamp, display_mode_t mode);
|
||||
typedef struct {
|
||||
bool bwr;
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
uint32_t timestamp;
|
||||
int8_t temperature;
|
||||
float voltage;
|
||||
} gui_data_t;
|
||||
|
||||
void DrawGUI(gui_data_t *data, buffer_callback draw, display_mode_t mode);
|
||||
|
||||
#endif
|
||||
|
||||
18
Makefile.win32
Normal file
18
Makefile.win32
Normal file
@@ -0,0 +1,18 @@
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -O2 -IGUI -DPAGE_HEIGHT=600
|
||||
LDFLAGS = -lgdi32 -mwindows
|
||||
|
||||
SRCS = GUI/Adafruit_GFX.c GUI/u8g2_font.c GUI/fonts.c GUI/GUI.c GUI/Lunar.c emulator.c
|
||||
OBJS = $(SRCS:.c=.o)
|
||||
TARGET = emulator.exe
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS)
|
||||
$(CC) -o $@ $^ $(LDFLAGS)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS) $(TARGET)
|
||||
21
README.md
21
README.md
@@ -72,6 +72,27 @@
|
||||
2. 切换到 `flash_softdevice`,下载蓝牙协议栈,**不要编译直接下载**(只需刷一次)
|
||||
3. 切换到 `nRF51822_xxAA`,先编译再下载
|
||||
|
||||
### 模拟器
|
||||
|
||||
本项目提供了一个可在 Windows 下运行界面代码的模拟器,修改了界面代码后无需下载到单片机即可查看效果。
|
||||
|
||||
仿真效果图:
|
||||
|
||||

|
||||
|
||||
> **提示:** 按 `空格` 可切换日历时钟界面,按 `R` 可切换黑白、三色
|
||||
|
||||
**编译方法:**
|
||||
|
||||
下载并安装 [MSYS2](https://www.msys2.org) 后,打开 `MSYS2 MINGW64` 命令窗口执行:
|
||||
|
||||
```bash
|
||||
pacman -Syu
|
||||
pacman -S make mingw-w64-x86_64-gcc
|
||||
cd <本项目目录>
|
||||
make -f Makefile.win32
|
||||
```
|
||||
|
||||
## 附录
|
||||
|
||||
上位机支持的指令列表(指令和参数全部要使用十六进制):
|
||||
|
||||
BIN
docs/images/4.jpg
Normal file
BIN
docs/images/4.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
262
emulator.c
Normal file
262
emulator.c
Normal file
@@ -0,0 +1,262 @@
|
||||
// GUI emulator for Windows
|
||||
// This code is a simple Windows GUI application that emulates the display of an e-paper device.
|
||||
#include <windows.h>
|
||||
#include <stdint.h>
|
||||
#include <time.h>
|
||||
#include "GUI.h"
|
||||
|
||||
#define BITMAP_WIDTH 400
|
||||
#define BITMAP_HEIGHT 300
|
||||
#define WINDOW_WIDTH 400
|
||||
#define WINDOW_HEIGHT 340
|
||||
#define WINDOW_TITLE TEXT("Emurator")
|
||||
|
||||
// Global variables
|
||||
HINSTANCE g_hInstance;
|
||||
HWND g_hwnd;
|
||||
display_mode_t g_display_mode = MODE_CALENDAR; // Default to calendar mode
|
||||
BOOL g_bwr_mode = TRUE; // Default to BWR mode
|
||||
|
||||
// Convert bitmap data from e-paper format to Windows DIB format
|
||||
static uint8_t *convertBitmap(uint8_t *bitmap, uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
|
||||
int bytesPerRow = ((w + 31) / 32) * 4; // Round up to nearest 4 bytes
|
||||
int totalSize = bytesPerRow * h;
|
||||
|
||||
// Allocate memory for converted bitmap
|
||||
uint8_t *convertedBitmap = (uint8_t*)malloc(totalSize);
|
||||
if (convertedBitmap == NULL) return NULL;
|
||||
|
||||
memset(convertedBitmap, 0, totalSize);
|
||||
|
||||
int ePaperBytesPerRow = (w + 7) / 8; // E-paper buffer stride
|
||||
|
||||
for (int row = 0; row < h; row++) {
|
||||
for (int col = 0; col < w; col++) {
|
||||
// Calculate byte and bit position in e-paper buffer
|
||||
int bytePos = row * ePaperBytesPerRow + col / 8;
|
||||
int bitPos = 7 - (col % 8); // MSB first (typical e-paper format)
|
||||
|
||||
// Check if the bit is set in the e-paper buffer
|
||||
int isSet = (bitmap[bytePos] >> bitPos) & 0x01;
|
||||
|
||||
// Calculate byte and bit position in Windows DIB
|
||||
int dibBytePos = row * bytesPerRow + col / 8;
|
||||
int dibBitPos = 7 - (col % 8); // MSB first for DIB too
|
||||
|
||||
// Set the bit in the Windows DIB if it's set in the e-paper buffer
|
||||
if (isSet) {
|
||||
convertedBitmap[dibBytePos] |= (1 << dibBitPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return convertedBitmap;
|
||||
}
|
||||
|
||||
// Implementation of the buffer_callback function
|
||||
void DrawBitmap(uint8_t *black, uint8_t *color, uint16_t x, uint16_t y, uint16_t w, uint16_t h) {
|
||||
HDC hdc;
|
||||
RECT clientRect;
|
||||
int scale = 1;
|
||||
|
||||
// Get the device context for immediate drawing
|
||||
hdc = GetDC(g_hwnd);
|
||||
if (!hdc) return;
|
||||
|
||||
// Get client area for positioning
|
||||
GetClientRect(g_hwnd, &clientRect);
|
||||
|
||||
// Calculate position to center the entire bitmap in the window
|
||||
int drawX = (clientRect.right - BITMAP_WIDTH * scale) / 2;
|
||||
int drawY = (clientRect.bottom - BITMAP_HEIGHT * scale) / 2;
|
||||
|
||||
// Create DIB for visible pixels
|
||||
BITMAPINFO bmi;
|
||||
ZeroMemory(&bmi, sizeof(BITMAPINFO));
|
||||
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||
bmi.bmiHeader.biWidth = w;
|
||||
bmi.bmiHeader.biHeight = -h; // Negative for top-down bitmap
|
||||
bmi.bmiHeader.biPlanes = 1;
|
||||
bmi.bmiHeader.biBitCount = 1;
|
||||
bmi.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
uint8_t *convertedBitmap = convertBitmap(black, x, y, w, h);
|
||||
if (convertedBitmap == NULL) {
|
||||
ReleaseDC(g_hwnd, hdc);
|
||||
return;
|
||||
}
|
||||
|
||||
// Set colors for black and white display
|
||||
bmi.bmiColors[0].rgbBlue = 0;
|
||||
bmi.bmiColors[0].rgbGreen = 0;
|
||||
bmi.bmiColors[0].rgbRed = 0;
|
||||
bmi.bmiColors[0].rgbReserved = 0;
|
||||
|
||||
bmi.bmiColors[1].rgbBlue = 255;
|
||||
bmi.bmiColors[1].rgbGreen = 255;
|
||||
bmi.bmiColors[1].rgbRed = 255;
|
||||
bmi.bmiColors[1].rgbReserved = 0;
|
||||
|
||||
// Draw the black layer
|
||||
StretchDIBits(hdc,
|
||||
drawX + x * scale, drawY + y * scale, // Destination position
|
||||
w * scale, h * scale, // Destination size
|
||||
0, 0, // Source position
|
||||
w, h, // Source size
|
||||
convertedBitmap, // Converted bitmap bits
|
||||
&bmi, // Bitmap info
|
||||
DIB_RGB_COLORS, // Usage
|
||||
SRCCOPY); // Raster operation code
|
||||
free(convertedBitmap);
|
||||
|
||||
// Handle color layer if present (red in BWR displays)
|
||||
if (color) {
|
||||
// Allocate memory for converted color bitmap
|
||||
uint8_t *convertedColor = convertBitmap(color, x, y, w, h);
|
||||
if (convertedColor) {
|
||||
// Set colors for red overlay
|
||||
bmi.bmiColors[0].rgbBlue = 255;
|
||||
bmi.bmiColors[0].rgbGreen = 255;
|
||||
bmi.bmiColors[0].rgbRed = 0;
|
||||
bmi.bmiColors[0].rgbReserved = 0;
|
||||
|
||||
bmi.bmiColors[1].rgbBlue = 0;
|
||||
bmi.bmiColors[1].rgbGreen = 0;
|
||||
bmi.bmiColors[1].rgbRed = 0;
|
||||
bmi.bmiColors[1].rgbReserved = 0;
|
||||
|
||||
// Draw red overlay
|
||||
StretchDIBits(hdc,
|
||||
drawX + x * scale, drawY + y * scale, // Destination position
|
||||
w * scale, h * scale, // Destination size
|
||||
0, 0, // Source position
|
||||
w, h, // Source size
|
||||
convertedColor, // Converted bitmap bits
|
||||
&bmi, // Bitmap info
|
||||
DIB_RGB_COLORS, // Usage
|
||||
SRCINVERT); // Use XOR operation to blend
|
||||
|
||||
free(convertedColor);
|
||||
}
|
||||
}
|
||||
|
||||
// Release the device context
|
||||
ReleaseDC(g_hwnd, hdc);
|
||||
}
|
||||
|
||||
// Window procedure
|
||||
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
|
||||
switch (message) {
|
||||
case WM_CREATE:
|
||||
// Set a timer to update the display periodically (every second)
|
||||
SetTimer(hwnd, 1, 1000, NULL);
|
||||
return 0;
|
||||
|
||||
case WM_TIMER:
|
||||
// Force a redraw of the window without erasing the background
|
||||
InvalidateRect(hwnd, NULL, FALSE);
|
||||
return 0;
|
||||
|
||||
case WM_PAINT: {
|
||||
PAINTSTRUCT ps;
|
||||
HDC hdc = BeginPaint(hwnd, &ps);
|
||||
|
||||
// Get client rect for calculations
|
||||
RECT clientRect;
|
||||
GetClientRect(hwnd, &clientRect);
|
||||
|
||||
// Clear the entire client area with a solid color
|
||||
HBRUSH bgBrush = CreateSolidBrush(RGB(240, 240, 240));
|
||||
FillRect(hdc, &clientRect, bgBrush);
|
||||
DeleteObject(bgBrush);
|
||||
|
||||
// Get current timestamp
|
||||
gui_data_t data = {
|
||||
.bwr = g_bwr_mode,
|
||||
.width = BITMAP_WIDTH,
|
||||
.height = BITMAP_HEIGHT,
|
||||
.timestamp = time(NULL) + 8*3600,
|
||||
.temperature = 25,
|
||||
.voltage = 3.2f,
|
||||
};
|
||||
|
||||
// Call DrawGUI to render the interface, passing the BWR mode
|
||||
DrawGUI(&data, DrawBitmap, g_display_mode);
|
||||
|
||||
EndPaint(hwnd, &ps);
|
||||
return 0;
|
||||
}
|
||||
|
||||
case WM_KEYDOWN:
|
||||
// Toggle display mode with spacebar
|
||||
if (wParam == VK_SPACE) {
|
||||
if (g_display_mode == MODE_CLOCK)
|
||||
g_display_mode = MODE_CALENDAR;
|
||||
else
|
||||
g_display_mode = MODE_CLOCK;
|
||||
|
||||
InvalidateRect(hwnd, NULL, TRUE);
|
||||
}
|
||||
// Toggle BWR mode with R key
|
||||
else if (wParam == 'R') {
|
||||
g_bwr_mode = !g_bwr_mode;
|
||||
InvalidateRect(hwnd, NULL, TRUE);
|
||||
}
|
||||
return 0;
|
||||
|
||||
case WM_DESTROY:
|
||||
KillTimer(hwnd, 1);
|
||||
PostQuitMessage(0);
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return DefWindowProc(hwnd, message, wParam, lParam);
|
||||
}
|
||||
}
|
||||
|
||||
// Main entry point
|
||||
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
|
||||
g_hInstance = hInstance;
|
||||
|
||||
// Register window class
|
||||
WNDCLASSA wc = {0}; // Using WNDCLASSA for ANSI version
|
||||
wc.style = CS_HREDRAW | CS_VREDRAW;
|
||||
wc.lpfnWndProc = WndProc;
|
||||
wc.hInstance = hInstance;
|
||||
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||||
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
|
||||
wc.lpszClassName = "BitmapDemo"; // No L prefix - using ANSI strings
|
||||
|
||||
if (!RegisterClassA(&wc)) {
|
||||
MessageBoxA(NULL, "Window Registration Failed!", "Error", MB_ICONEXCLAMATION | MB_OK);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Create the window - explicit use of CreateWindowA for ANSI version
|
||||
g_hwnd = CreateWindowA(
|
||||
"BitmapDemo",
|
||||
"Emurator (Press Space/R Key)", // Using simple title
|
||||
WS_OVERLAPPEDWINDOW,
|
||||
CW_USEDEFAULT, CW_USEDEFAULT,
|
||||
WINDOW_WIDTH, WINDOW_HEIGHT,
|
||||
NULL, NULL, hInstance, NULL
|
||||
);
|
||||
|
||||
if (!g_hwnd) {
|
||||
MessageBoxA(NULL, "Window Creation Failed!", "Error", MB_ICONEXCLAMATION | MB_OK);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Show window
|
||||
ShowWindow(g_hwnd, nCmdShow);
|
||||
UpdateWindow(g_hwnd);
|
||||
|
||||
// Main message loop
|
||||
MSG msg;
|
||||
while (GetMessage(&msg, NULL, 0, 0)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
|
||||
return (int)msg.wParam;
|
||||
}
|
||||
Reference in New Issue
Block a user