adding inverted color commands
This commit is contained in:
26
AGENTS.md
Normal file
26
AGENTS.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# Agent Guidelines for `eink_api`
|
||||||
|
|
||||||
|
These instructions apply to the entire repository.
|
||||||
|
|
||||||
|
## Code Style
|
||||||
|
- Follow existing Pico SDK patterns in `eink_api.c`; prefer straightforward, readable C without unnecessary abstractions.
|
||||||
|
- Match the project logging approach (`printf` for user feedback) and keep messages concise and informative.
|
||||||
|
- When modifying command handlers, mimic the `else if` chain style that ensures only one command executes per input.
|
||||||
|
- Avoid adding inline comments unless they clarify non-obvious logic.
|
||||||
|
|
||||||
|
## Command Handling
|
||||||
|
- Use fixed-length comparisons (`strncmp`) with explicit delimiter checks so longer commands do not trigger shorter prefixes (e.g., guard `draw_circle` vs. `draw_circle_fill`).
|
||||||
|
- Validate command arguments with `sscanf`, checking return counts before acting.
|
||||||
|
- Ensure screen-state guards (`screen_on`, `editor_mode`) remain intact around command logic.
|
||||||
|
|
||||||
|
## Display Operations
|
||||||
|
- Use the Waveshare `Paint_*` helpers already in use; do not introduce alternative drawing backends without a strong reason.
|
||||||
|
- Prefer existing constants (`BLACK`, `WHITE`, `DOT_PIXEL_1X1`, fill enums) for clarity and consistency.
|
||||||
|
|
||||||
|
## Build & Tooling
|
||||||
|
- Keep the CMake-based build flow unchanged (`cmake -S . -B build` / `cmake --build build`).
|
||||||
|
- Scripts under `scripts/` should remain POSIX shell compatible and should surface user-friendly errors tied to README guidance.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
- Update the README table of commands whenever new commands are added or behaviors change.
|
||||||
|
- Maintain alignment between serial command descriptions and their implementation details.
|
||||||
10
README.md
10
README.md
@@ -28,6 +28,10 @@ cmake --build build
|
|||||||
|
|
||||||
After building, flash the firmware to your Pico (e.g. by copying `build/eink_api.uf2` to the Pico's mass storage device or using `picotool load build/eink_api.uf2`).
|
After building, flash the firmware to your Pico (e.g. by copying `build/eink_api.uf2` to the Pico's mass storage device or using `picotool load build/eink_api.uf2`).
|
||||||
|
|
||||||
|
### Build/Flash Helper Script
|
||||||
|
|
||||||
|
Run `scripts/flash_and_connect.sh [/dev/ttyACM0]` to configure CMake, compile the firmware, optionally flash it with `picotool`, and open a serial session (defaults to `screen` at 115200 baud). Set `PICO_SERIAL` to override the port, `PICO_BAUD` for a different baud rate, and `SERIAL_CLIENT` if you prefer `picocom` or `minicom`. If `picotool` is missing, the script will remind you to copy the UF2 to the board manually.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
1. Open a serial terminal (115200 baud) over the Pico's USB CDC interface.
|
1. Open a serial terminal (115200 baud) over the Pico's USB CDC interface.
|
||||||
@@ -44,14 +48,18 @@ After building, flash the firmware to your Pico (e.g. by copying `build/eink_api
|
|||||||
| `display` | Pushes the framebuffer to the panel (requires display on). |
|
| `display` | Pushes the framebuffer to the panel (requires display on). |
|
||||||
| `draw_test` | Draws a hard-coded Waveshare demo pattern. |
|
| `draw_test` | Draws a hard-coded Waveshare demo pattern. |
|
||||||
| `draw_text x y text` | Writes `text` at `(x,y)` using the current font. |
|
| `draw_text x y text` | Writes `text` at `(x,y)` using the current font. |
|
||||||
|
| `draw_text_white x y text` | Writes inverted white text at `(x,y)` with a black background. |
|
||||||
| `draw_point x y` | Draws a black point at the given coordinates. |
|
| `draw_point x y` | Draws a black point at the given coordinates. |
|
||||||
| `draw_line x1 y1 x2 y2` | Draws a black line between two points. |
|
| `draw_line x1 y1 x2 y2` | Draws a black line between two points. |
|
||||||
|
| `draw_sine x y length amplitude frequency` | Draws a sine-wave curve of `length` pixels starting at `(x,y)`, oscillating by `amplitude`, with the given number of cycles. |
|
||||||
| `draw_rectangle x1 y1 x2 y2` | Draws an empty rectangle. |
|
| `draw_rectangle x1 y1 x2 y2` | Draws an empty rectangle. |
|
||||||
|
| `draw_rectangle_fill x1 y1 x2 y2` | Draws a filled rectangle. |
|
||||||
| `draw_circle x y r` | Draws an empty circle with radius `r`. |
|
| `draw_circle x y r` | Draws an empty circle with radius `r`. |
|
||||||
|
| `draw_circle_fill x y r` | Draws a filled circle with radius `r`. |
|
||||||
| `draw_num x y num` | Draws `num` using the default numeric font. |
|
| `draw_num x y num` | Draws `num` using the default numeric font. |
|
||||||
| `set_pixel x y color` | Sets a single pixel to `color` (0=white, 1=black). |
|
| `set_pixel x y color` | Sets a single pixel to `color` (0=white, 1=black). |
|
||||||
| `close` | Sends the panel to deep sleep and frees the framebuffer. |
|
| `close` | Sends the panel to deep sleep and frees the framebuffer. |
|
||||||
| `editor` | Enables editor mode; text typed is rendered live on-screen until inactivity. |
|
| `editor [0|1]` | Enables editor mode; default uses white background, non-zero switches to white text on black. |
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
|||||||
109
eink_api.c
109
eink_api.c
@@ -1,6 +1,12 @@
|
|||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#ifndef M_PI
|
||||||
|
#define M_PI 3.14159265358979323846
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "pico/stdlib.h"
|
#include "pico/stdlib.h"
|
||||||
#include "pico/multicore.h"
|
#include "pico/multicore.h"
|
||||||
#include "pico/util/queue.h"
|
#include "pico/util/queue.h"
|
||||||
@@ -47,11 +53,44 @@ int epaper_init()
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void draw_sine_wave_line(int x_start, int y_center, int length, int amplitude, float cycles)
|
||||||
|
{
|
||||||
|
if (length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int step_count = abs(length);
|
||||||
|
if (step_count < 40) {
|
||||||
|
step_count = 40;
|
||||||
|
} else if (step_count > 400) {
|
||||||
|
step_count = 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
double prev_x = x_start;
|
||||||
|
double prev_y = y_center;
|
||||||
|
|
||||||
|
for (int i = 1; i <= step_count; ++i) {
|
||||||
|
double t = (double)i / step_count;
|
||||||
|
double angle = 2.0 * M_PI * cycles * t;
|
||||||
|
double next_x = x_start + length * t;
|
||||||
|
double next_y = y_center + amplitude * sin(angle);
|
||||||
|
|
||||||
|
Paint_DrawLine((int)round(prev_x), (int)round(prev_y),
|
||||||
|
(int)round(next_x), (int)round(next_y),
|
||||||
|
BLACK, DOT_PIXEL_1X1, LINE_STYLE_SOLID);
|
||||||
|
|
||||||
|
prev_x = next_x;
|
||||||
|
prev_y = next_y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool editor_mode = false;
|
bool editor_mode = false;
|
||||||
uint32_t editor_last_ts = 0;
|
uint32_t editor_last_ts = 0;
|
||||||
char screen_log[2000];
|
char screen_log[2000];
|
||||||
int screen_log_i = 0;
|
int screen_log_i = 0;
|
||||||
sFONT *current_font = &Font16;
|
sFONT *current_font = &Font16;
|
||||||
|
UBYTE editor_bg_color = WHITE;
|
||||||
|
UBYTE editor_fg_color = BLACK;
|
||||||
|
|
||||||
int epaper_close()
|
int epaper_close()
|
||||||
{
|
{
|
||||||
@@ -89,6 +128,15 @@ void process_command(char *cmd)
|
|||||||
Paint_DrawString_CN(10, 110, "微雪电子", &Font24CN, BLACK, WHITE);
|
Paint_DrawString_CN(10, 110, "微雪电子", &Font24CN, BLACK, WHITE);
|
||||||
EPD_4IN2_V2_Display(BlackImage);
|
EPD_4IN2_V2_Display(BlackImage);
|
||||||
printf("Draw test complete\n");
|
printf("Draw test complete\n");
|
||||||
|
} else if (screen_on && strncmp(cmd, "draw_text_white", 15) == 0) {
|
||||||
|
int x, y;
|
||||||
|
char text[200];
|
||||||
|
if (sscanf(cmd, "draw_text_white %d %d %199[^\n]", &x, &y, text) == 3) {
|
||||||
|
Paint_DrawString_EN(x, y, text, current_font, BLACK, WHITE);
|
||||||
|
printf("Drew white text at %d, %d: %s\n", x, y, text);
|
||||||
|
} else {
|
||||||
|
printf("Invalid draw_text_white command format. Use: draw_text_white x y text\n");
|
||||||
|
}
|
||||||
} else if (screen_on && strncmp(cmd, "draw_text", 9) == 0) {
|
} else if (screen_on && strncmp(cmd, "draw_text", 9) == 0) {
|
||||||
int x, y;
|
int x, y;
|
||||||
char text[200];
|
char text[200];
|
||||||
@@ -114,7 +162,16 @@ void process_command(char *cmd)
|
|||||||
} else {
|
} else {
|
||||||
printf("Invalid draw_line command format. Use: draw_line x1 y1 x2 y2\n");
|
printf("Invalid draw_line command format. Use: draw_line x1 y1 x2 y2\n");
|
||||||
}
|
}
|
||||||
} else if (screen_on && strncmp(cmd, "draw_rectangle", 14) == 0) {
|
} else if (screen_on && strncmp(cmd, "draw_sine", 9) == 0) {
|
||||||
|
int x, y, length, amplitude;
|
||||||
|
float frequency;
|
||||||
|
if (sscanf(cmd, "draw_sine %d %d %d %d %f", &x, &y, &length, &litude, &frequency) == 5) {
|
||||||
|
draw_sine_wave_line(x, y, length, amplitude, frequency);
|
||||||
|
printf("Drew sine wave starting at %d,%d length %d amplitude %d frequency %.2f\n", x, y, length, amplitude, frequency);
|
||||||
|
} else {
|
||||||
|
printf("Invalid draw_sine command format. Use: draw_sine x y length amplitude frequency\n");
|
||||||
|
}
|
||||||
|
} else if (screen_on && strncmp(cmd, "draw_rectangle", 14) == 0 && (cmd[14] == ' ' || cmd[14] == '\0')) {
|
||||||
int x1, y1, x2, y2;
|
int x1, y1, x2, y2;
|
||||||
if (sscanf(cmd, "draw_rectangle %d %d %d %d", &x1, &y1, &x2, &y2) == 4) {
|
if (sscanf(cmd, "draw_rectangle %d %d %d %d", &x1, &y1, &x2, &y2) == 4) {
|
||||||
Paint_DrawRectangle(x1, y1, x2, y2, BLACK, DOT_PIXEL_1X1, DRAW_FILL_EMPTY);
|
Paint_DrawRectangle(x1, y1, x2, y2, BLACK, DOT_PIXEL_1X1, DRAW_FILL_EMPTY);
|
||||||
@@ -122,7 +179,15 @@ void process_command(char *cmd)
|
|||||||
} else {
|
} else {
|
||||||
printf("Invalid draw_rectangle command format. Use: draw_rectangle x1 y1 x2 y2\n");
|
printf("Invalid draw_rectangle command format. Use: draw_rectangle x1 y1 x2 y2\n");
|
||||||
}
|
}
|
||||||
} else if (screen_on && strncmp(cmd, "draw_circle", 11) == 0) {
|
} else if (screen_on && strncmp(cmd, "draw_rectangle_fill", 18) == 0) {
|
||||||
|
int x1, y1, x2, y2;
|
||||||
|
if (sscanf(cmd, "draw_rectangle_fill %d %d %d %d", &x1, &y1, &x2, &y2) == 4) {
|
||||||
|
Paint_DrawRectangle(x1, y1, x2, y2, BLACK, DOT_PIXEL_1X1, DRAW_FILL_FULL);
|
||||||
|
printf("Drew filled rectangle from %d,%d to %d,%d\n", x1, y1, x2, y2);
|
||||||
|
} else {
|
||||||
|
printf("Invalid draw_rectangle_fill command format. Use: draw_rectangle_fill x1 y1 x2 y2\n");
|
||||||
|
}
|
||||||
|
} else if (screen_on && strncmp(cmd, "draw_circle", 11) == 0 && (cmd[11] == ' ' || cmd[11] == '\0')) {
|
||||||
int x, y, r;
|
int x, y, r;
|
||||||
if (sscanf(cmd, "draw_circle %d %d %d", &x, &y, &r) == 3) {
|
if (sscanf(cmd, "draw_circle %d %d %d", &x, &y, &r) == 3) {
|
||||||
Paint_DrawCircle(x, y, r, BLACK, DOT_PIXEL_1X1, DRAW_FILL_EMPTY);
|
Paint_DrawCircle(x, y, r, BLACK, DOT_PIXEL_1X1, DRAW_FILL_EMPTY);
|
||||||
@@ -130,6 +195,14 @@ void process_command(char *cmd)
|
|||||||
} else {
|
} else {
|
||||||
printf("Invalid draw_circle command format. Use: draw_circle x y r\n");
|
printf("Invalid draw_circle command format. Use: draw_circle x y r\n");
|
||||||
}
|
}
|
||||||
|
} else if (screen_on && strncmp(cmd, "draw_circle_fill", 16) == 0) {
|
||||||
|
int x, y, r;
|
||||||
|
if (sscanf(cmd, "draw_circle_fill %d %d %d", &x, &y, &r) == 3) {
|
||||||
|
Paint_DrawCircle(x, y, r, BLACK, DOT_PIXEL_1X1, DRAW_FILL_FULL);
|
||||||
|
printf("Drew filled circle at %d,%d with radius %d\n", x, y, r);
|
||||||
|
} else {
|
||||||
|
printf("Invalid draw_circle_fill command format. Use: draw_circle_fill x y r\n");
|
||||||
|
}
|
||||||
} else if (screen_on && strncmp(cmd, "draw_num", 8) == 0) {
|
} else if (screen_on && strncmp(cmd, "draw_num", 8) == 0) {
|
||||||
int x, y, num;
|
int x, y, num;
|
||||||
if (sscanf(cmd, "draw_num %d %d %d", &x, &y, &num) == 3) {
|
if (sscanf(cmd, "draw_num %d %d %d", &x, &y, &num) == 3) {
|
||||||
@@ -149,11 +222,29 @@ void process_command(char *cmd)
|
|||||||
} else if (screen_on && strcmp(cmd, "close") == 0) {
|
} else if (screen_on && strcmp(cmd, "close") == 0) {
|
||||||
epaper_close();
|
epaper_close();
|
||||||
printf("e-Paper closed\n");
|
printf("e-Paper closed\n");
|
||||||
} else if (screen_on && strcmp(cmd, "editor") == 0) {
|
} else if (screen_on && strncmp(cmd, "editor", 6) == 0 && (cmd[6] == '\0' || cmd[6] == ' ')) {
|
||||||
|
int invert_flag = 0;
|
||||||
|
if (cmd[6] == ' ') {
|
||||||
|
if (sscanf(cmd, "editor %d", &invert_flag) != 1) {
|
||||||
|
printf("Invalid editor command format. Use: editor [0|1]\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (invert_flag != 0) {
|
||||||
|
editor_bg_color = BLACK;
|
||||||
|
editor_fg_color = WHITE;
|
||||||
|
} else {
|
||||||
|
editor_bg_color = WHITE;
|
||||||
|
editor_fg_color = BLACK;
|
||||||
|
}
|
||||||
|
if (!editor_mode) {
|
||||||
|
screen_log_i = 0;
|
||||||
|
screen_log[0] = '\0';
|
||||||
|
}
|
||||||
editor_mode = true;
|
editor_mode = true;
|
||||||
absolute_time_t time = get_absolute_time();
|
absolute_time_t time = get_absolute_time();
|
||||||
editor_last_ts = to_ms_since_boot(time);
|
editor_last_ts = to_ms_since_boot(time);
|
||||||
printf("Editor mode enabled\n");
|
printf("Editor mode enabled (%s background)\n", invert_flag != 0 ? "black" : "white");
|
||||||
} else {
|
} else {
|
||||||
printf("Unknown command: %s\n", cmd);
|
printf("Unknown command: %s\n", cmd);
|
||||||
}
|
}
|
||||||
@@ -216,9 +307,9 @@ void core1_entry() {
|
|||||||
screen_last_ts = to_ms_since_boot(time);
|
screen_last_ts = to_ms_since_boot(time);
|
||||||
if(editor_mode){
|
if(editor_mode){
|
||||||
bool overflow = false;
|
bool overflow = false;
|
||||||
Paint_Clear(WHITE);
|
Paint_Clear(editor_bg_color);
|
||||||
screen_log[screen_log_i] = '\0';
|
screen_log[screen_log_i] = '\0';
|
||||||
overflow = Paint_DrawString_EN(10, 10, screen_log, current_font, WHITE, BLACK);
|
overflow = Paint_DrawString_EN(10, 10, screen_log, current_font, editor_bg_color, editor_fg_color);
|
||||||
if(overflow){
|
if(overflow){
|
||||||
while(screen_log[screen_log_i] != '\n' || screen_log_i>1){
|
while(screen_log[screen_log_i] != '\n' || screen_log_i>1){
|
||||||
screen_log_i--;
|
screen_log_i--;
|
||||||
@@ -230,9 +321,9 @@ void core1_entry() {
|
|||||||
}
|
}
|
||||||
screen_log_i = new_i;
|
screen_log_i = new_i;
|
||||||
}
|
}
|
||||||
Paint_Clear(WHITE);
|
Paint_Clear(editor_bg_color);
|
||||||
// if overflow reset the screen_log buffer with only the last line
|
// if overflow reset the screen_log buffer with only the last line
|
||||||
Paint_DrawString_EN(10, 10, screen_log, current_font, WHITE, BLACK);
|
Paint_DrawString_EN(10, 10, screen_log, current_font, editor_bg_color, editor_fg_color);
|
||||||
}
|
}
|
||||||
EPD_4IN2_V2_PartialDisplay(BlackImage, 0, 0, 400, 300);
|
EPD_4IN2_V2_PartialDisplay(BlackImage, 0, 0, 400, 300);
|
||||||
} else {
|
} else {
|
||||||
@@ -285,6 +376,8 @@ int main()
|
|||||||
if(editor_last_ts != 0 && editor_mode && (editor_last_ts + 10000 < ms)){
|
if(editor_last_ts != 0 && editor_mode && (editor_last_ts + 10000 < ms)){
|
||||||
printf("Disabling editor after 10s of inactivity");
|
printf("Disabling editor after 10s of inactivity");
|
||||||
editor_mode = false;
|
editor_mode = false;
|
||||||
|
editor_bg_color = WHITE;
|
||||||
|
editor_fg_color = BLACK;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
59
scripts/flash_and_connect.sh
Executable file
59
scripts/flash_and_connect.sh
Executable file
@@ -0,0 +1,59 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
BUILD_DIR="${BUILD_DIR:-build}"
|
||||||
|
SERIAL_DEV="${1:-${PICO_SERIAL:-/dev/ttyACM0}}"
|
||||||
|
BAUD_RATE="${PICO_BAUD:-115200}"
|
||||||
|
|
||||||
|
cmake -S "$PROJECT_ROOT" -B "$PROJECT_ROOT/$BUILD_DIR"
|
||||||
|
cmake --build "$PROJECT_ROOT/$BUILD_DIR"
|
||||||
|
|
||||||
|
UF2_PATH="$PROJECT_ROOT/$BUILD_DIR/eink_api.uf2"
|
||||||
|
if [ ! -f "$UF2_PATH" ]; then
|
||||||
|
echo "UF2 image not found at $UF2_PATH" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v picotool >/dev/null 2>&1; then
|
||||||
|
echo "Put the Pico into BOOTSEL mode (hold BOOTSEL and plug in USB), then press Enter to flash..."
|
||||||
|
read -r
|
||||||
|
if ! picotool load "$UF2_PATH" --verify --reset; then
|
||||||
|
echo "picotool flashing failed." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "picotool not found; copy $UF2_PATH to the Pico's mass-storage drive manually to flash." >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
SERIAL_CLIENT="${SERIAL_CLIENT:-screen}"
|
||||||
|
case "$SERIAL_CLIENT" in
|
||||||
|
screen)
|
||||||
|
if ! command -v screen >/dev/null 2>&1; then
|
||||||
|
echo "screen not installed; set SERIAL_CLIENT to a different terminal (picocom, minicom)." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Connecting to $SERIAL_DEV at $BAUD_RATE with screen (Ctrl-A, then k to exit)."
|
||||||
|
exec screen "$SERIAL_DEV" "$BAUD_RATE"
|
||||||
|
;;
|
||||||
|
picocom)
|
||||||
|
if ! command -v picocom >/dev/null 2>&1; then
|
||||||
|
echo "picocom not installed; install it or set SERIAL_CLIENT=screen." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Connecting to $SERIAL_DEV at $BAUD_RATE with picocom (Ctrl-A, Ctrl-X to exit)."
|
||||||
|
exec picocom -b "$BAUD_RATE" "$SERIAL_DEV"
|
||||||
|
;;
|
||||||
|
minicom)
|
||||||
|
if ! command -v minicom >/dev/null 2>&1; then
|
||||||
|
echo "minicom not installed; install it or set SERIAL_CLIENT=screen." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Connecting to $SERIAL_DEV at $BAUD_RATE with minicom (Ctrl-A, Z then X to exit)."
|
||||||
|
exec minicom -D "$SERIAL_DEV" -b "$BAUD_RATE"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unsupported SERIAL_CLIENT '$SERIAL_CLIENT'." >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Reference in New Issue
Block a user