Compare commits

..
10 Commits
Author SHA1 Message Date
patriandClaude Opus 5.5 be69bda4f1 12-OBD-Speed: battery voltage arc and idle/screen-off mode
- ATRV every 2 s; voltage arc in the bottom gap (11-15 V) + number,
  colored by state (low / weak / resting / charging / overcharge)
- idle after Off/2/5/10 min without speed data (settings page, NVS):
  screen off, GIF paused, OBD polling stopped, ATRV every 30 s;
  wakes on touch (swallowed by a top-layer overlay) or > 13.2 V
- status pill moved up to make room for the voltage

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 18:32:56 +02:00
patriandClaude Opus 5.5 0777db3498 CLAUDE.md: screen rotation verified at all angles
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 18:22:10 +02:00
patriandClaude Opus 5.5 4fc70f13d0 12-OBD-Speed: settings page with screen rotation (0/90/180/270)
- third tile (swipe left from the menu): rotation buttons, saved in NVS,
  applied at boot
- shared lib: setRotation() rotates only the panel and redraws; touch
  points are converted in touchpad_read() for the current rotation;
  getRotation() added

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 18:20:35 +02:00
patriandClaude Opus 5.5 733306a9c0 Cleanup: remove BLE scanner, rename 10-OBD-Display-2024 to 10-GIF-Tests
- 11-BLE-Scanner removed; its car-test log moved to 12-OBD-Speed/docs
- 10-OBD-Display-2024 -> 10-GIF-Tests (GIF playback experiments)
- the broken LVGL 9 archive was already deleted (unintentionally) in d6982ab
- READMEs and CLAUDE.md references updated; visual check of 80 MHz QSPI /
  RGB565_SWAPPED marked as confirmed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 18:14:23 +02:00
patriandClaude Opus 5.5 66f70c0cc1 Add 12-OBD-Speed: ELM327 speed over BLE on a GIF background
- speed page: 120 px antialiased number (lv_font_conv) + 0-200 km/h arc
  gauge over ezgif-5-cb56986bc7.gif (embedded via board_build.embed_files)
- swipe-left menu: scan, OBD-like adapters first, tap to select; adapter
  remembered in NVS, auto-connect at boot and reconnect every 3 s
- ELM327 init ATZ/ATE0/ATL0/ATS0/ATH0/ATSP0/0100, then 010D polling
- shared ini: -DLV_LVGL_H_INCLUDE_SIMPLE for generated fonts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 16:49:37 +02:00
patriandClaude Opus 5.5 9e9c0fac68 Remove 2024 BT-Classic bridge, ELMulator and WiFi speed sketch
The ELM327 is read directly over BLE now. Docs updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 16:38:15 +02:00
patriandClaude Opus 5.5 0bd6aef6b5 ELM327 BLE verified in the car: service FFF0, FFF1 notify/write, ELM327 v2.1
Scanner log saved in 11-BLE-Scanner/results; CLAUDE.md updated with the
adapter's GATT layout, replies and supported PIDs 01-20.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 14:21:44 +02:00
patriandClaude Opus 5.5 8a84b42a3b Add 11-BLE-Scanner: on-screen BLE scanner, inspector and ELM327 test
- NimBLE-Arduino 2.5.1, BLE work in a background task on core 0
- LVGL 9 UI for the round screen: device list (OBD-like devices in green),
  tap to list services/characteristics, ELM test (ATZ/ATE0/ATI/ATRV/0100)
- results saved to LittleFS /ble_log.txt; serial d/c/s commands
- shared lv_conf: Montserrat 16 and 20 enabled

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 01:54:36 +02:00
patriandClaude Opus 5.5 71d4bf7b58 display_gif_sd: load GIF from SD into PSRAM once, play from memory (9.8 -> 15.4 fps)
Also enable a 16 KB LVGL stdio read cache (no measurable effect for the GIF).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 01:43:31 +02:00
patriandClaude Opus 5.5 55e27b6572 Port display_gif_sd and display_speed_wifi to LVGL 9.6 + shared library
- both use shared/jc3636w518.ini, board library and perf monitor
- GIF decoded in the display color format, loop delay 1 ms,
  per-loop memory prints removed
- lv_conf: LVGL drive A: = SD card via LV_USE_FS_STDIO (/sdcard/)
- shared/README.md, CLAUDE.md and project README updated

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 01:37:29 +02:00
69 changed files with 5013 additions and 5849 deletions

No files matched your search

+22
View File
@@ -0,0 +1,22 @@
# GIF tests
GIF playback experiments on the JC3636W518 (originally part of the 2024 OBD display attempt; renamed 2026-10-23).
Used to find the fastest GIF setup — results are in `../shared/README.md` and `../CLAUDE.md`.
## Projects (PlatformIO, LVGL 9.6.0, shared library in `../shared`)
| Folder | What it does |
|---|---|
| `display_gif_embedded/` | `loligod.gif` compiled into firmware (`src/loligod.c`, 2.2 MB GIF → 3.5 MB firmware, `default_16MB.csv`). Plays at full GIF speed (~23.8 fps, 53 % CPU). Starts without SD card. |
| `display_gif_sd/` | `wallpaper.gif` from the SD card, loaded into PSRAM once and played from memory (~15 fps for the current file). |
| `assets/gifs/` | GIF files (`dump/` = unused collection) |
## Build / upload
```
pio run -d display_gif_embedded -t upload # flash (COM21)
pio device monitor -d display_gif_embedded # serial: FPS / refresh / other / CPU once per second
```
(`pio` = `%USERPROFILE%\.platformio\penv\Scripts\pio.exe` if not on PATH.)
## Notes
- `display_gif_embedded/src/montserrat_100.c` (100 px digits font, 1 bpp) is present but not used.
- `display_gif_sd` returns early in `setup()` if the SD card fails to mount (black screen).
@@ -0,0 +1,10 @@
; GIF from the SD card (A:wallpaper.gif) — JC3636W518, common settings in shared/jc3636w518.ini
[platformio]
extra_configs = ../../shared/jc3636w518.ini
[shared]
dir = ../../shared
[env:jc3636w518]
extends = jc3636w518
@@ -1,5 +1,6 @@
#include <Arduino.h>
#include "scr_st77916.h"
#include "jc3636w518.h"
#include "perf_monitor.h"
#include "SD_MMC.h"
void my_print(const char* buf) {
@@ -9,7 +10,10 @@ void my_print(const char* buf) {
void display_animation() {
lv_obj_t* img;
img = lv_gif_create(lv_scr_act());
img = lv_gif_create(lv_screen_active());
// shown on a plain black screen: no transparency needed, so decode straight into the display's
// color format (no alpha blending, no conversion) to save CPU
lv_gif_set_color_format(img, lv_display_get_color_format(lv_display_get_default()));
Serial.print("Available Heap before: ");
Serial.println(esp_get_free_heap_size());
@@ -18,14 +22,26 @@ void display_animation() {
lv_mem_monitor(&mem_mon);
printf("Free memory: %d bytes\n", mem_mon.free_size);
lv_fs_file_t file;
if (lv_fs_open(&file, "A:wallpaper.gif", LV_FS_MODE_RD) == LV_FS_RES_OK) {
lv_fs_close(&file); // File exists, proceed with loading
lv_gif_set_src(img, "A:wallpaper.gif");
Serial.println("GIF Loaded Successfully");
// Load the whole GIF from the SD card into PSRAM once and play it from memory:
// no SD access while playing (faster, and kinder to flaky cards)
static lv_image_dsc_t gif_dsc;
File f = SD_MMC.open("/wallpaper.gif");
size_t size = f ? f.size() : 0;
uint8_t* data = size ? (uint8_t*)ps_malloc(size) : NULL;
if (data && f.read(data, size) == size) {
gif_dsc.header.magic = LV_IMAGE_HEADER_MAGIC;
gif_dsc.header.cf = LV_COLOR_FORMAT_RAW;
gif_dsc.header.w = data[6] | (data[7] << 8); // GIF logical screen size
gif_dsc.header.h = data[8] | (data[9] << 8);
gif_dsc.data_size = size;
gif_dsc.data = data;
lv_gif_set_src(img, &gif_dsc);
Serial.printf("GIF loaded into PSRAM: %u bytes, %u x %u\n", size, gif_dsc.header.w, gif_dsc.header.h);
} else {
Serial.println("Failed to Load GIF: File not found or inaccessible");
Serial.println("Failed to load /wallpaper.gif from the SD card");
free(data);
}
if (f) f.close();
Serial.print("Available Heap after: ");
Serial.println(esp_get_free_heap_size());
@@ -54,7 +70,7 @@ void display_text() {
// lv_style_set_shadow_ofs_x(&style_text, 0);
// lv_style_set_shadow_ofs_y(&style_text, 0);
lv_obj_t* label1 = lv_label_create(lv_scr_act());
lv_obj_t* label1 = lv_label_create(lv_screen_active());
// Apply the style to the label
lv_obj_add_style(label1, &style_text, LV_PART_MAIN);
@@ -70,14 +86,13 @@ void display_text() {
void setup() {
delay(200);
Serial.begin(115200);
scr_lvgl_init();
lv_init();
lv_display_t* disp = scr_lvgl_init();
perf_monitor_begin(disp);
//lv_log_register_print_cb(my_print); // Register print function for LVGL logs
// Get the active screen (root object for the display)
lv_obj_t* screen = lv_scr_act();
lv_obj_t* screen = lv_screen_active();
// Set the background color of the entire screen to black
lv_obj_set_style_bg_color(screen, lv_color_black(), 0); // 0 is the part ID for the main part
@@ -105,13 +120,7 @@ void setup() {
}
void loop() {
lv_timer_handler();
vTaskDelay(5);
lv_mem_monitor_t mem_mon;
lv_mem_monitor(&mem_mon);
printf("Free memory: %d bytes\n", mem_mon.free_size);
Serial.print("Available Heap: ");
Serial.println(esp_get_free_heap_size());
perf_timer_handler();
vTaskDelay(1);
perf_monitor_print();
}
-44
View File
@@ -1,44 +0,0 @@
# OBD Display (2024 experiments)
Earlier attempt to show car speed from an ELM327 OBD-II adapter on the JC3636W518.
Because the ESP32-S3 has no Bluetooth Classic, the design used a second (classic) ESP32 as a bridge:
```
ELM327 --(BT Classic SPP)--> classic ESP32 (elm327_wifi_bridge) --(WiFi AP, HTTP /speed)--> JC3636W518 (display_speed_wifi)
```
Split out of `2-Program Example/Demo_Arduino/ST77916_LVGL_DEMO/` on 2026-10-22 and converted to PlatformIO. History is in git.
## Projects (all PlatformIO)
| Folder | Runs on | What it does | Was |
|---|---|---|---|
| `display_speed_wifi/` | JC3636W518 | GIF background from SD (`A:wallpaper.gif`) + joins AP `ESP32-Access-Point`, polls `http://192.168.4.1/speed`, shows it in a label | `ST77916_LVGL_DEMO/8.4/` |
| `display_gif_sd/` | JC3636W518 | GIF from SD (`A:wallpaper.gif`) + static text label | `ST77916_LVGL_DEMO/working version/` |
| `display_gif_embedded/` | JC3636W518 | GIF compiled into firmware (`src/loligod.c`, 2.2 MB GIF → 3.4 MB firmware, uses `default_16MB.csv` with 6.25 MB app slots). **LVGL 9.6.0**, plays at full GIF speed (~23.4 fps), prints FPS once per second. GIF starts without SD card. WiFi code commented out. | `ST77916_LVGL_DEMO/` (top level) |
| `elm327_wifi_bridge/` | **classic ESP32** (not S3) | BT Classic → ELM327 named `OBDII` (PIN 1234), WiFi AP `ESP32-Access-Point` / `123456789`, serves `/speed` (kph) | `ST77916_LVGL_DEMO/esp23_elm327_to_wifi/` |
| `elmulator/` | classic ESP32 | Fake ELM327 for testing without a car. Library in `lib/ELMulator`, envs `simple`, `custom`, `wifi` (BT name `OBDII`) | `ST77916_LVGL_DEMO/ELMulator-master/` |
| `assets/gifs/` | — | GIF files (`dump/` = unused collection) | `ST77916_LVGL_DEMO/gifs/` |
| `_archive/lvgl9_broken/` | — | Unfinished LVGL 9 port, does not compile, not converted | `ST77916_LVGL_DEMO/9.1/` |
## Build / upload
Open a project folder in VS Code with the PlatformIO extension, or from a terminal:
```
pio run -d display_speed_wifi # build
pio run -d display_speed_wifi -t upload # flash (COM21)
pio device monitor -d display_speed_wifi # serial monitor
pio run -d elmulator -e wifi -t upload --upload-port COMxx
```
(`pio` = `%USERPROFILE%\.platformio\penv\Scripts\pio.exe` if not on PATH.)
## Configuration
- **JC3636W518 projects:** pioarduino platform `51.03.05` (= Arduino core 3.0.5), board `esp32-s3-devkitc-1` with 16 MB QIO flash, OPI PSRAM, `huge_app.csv` (3 MB app), USB-CDC on boot, COM21.
Libraries come from the vendor folder via `lib_extra_dirs = ../../2-Program Example/Demo_Arduino/library`.
Each project uses its **own `include/lv_conf.h`** (`-DLV_CONF_INCLUDE_SIMPLE -Iinclude`):
- `display_speed_wifi`: LVGL heap 4 MB in PSRAM (`ps_malloc`)
- `display_gif_sd`: LVGL heap 7 MB in PSRAM
- **Classic ESP32 projects:** official `espressif32@6.10.0` (Arduino core 2.0.17), board `esp32dev`, `huge_app.csv`. Set `upload_port` to the classic ESP32's COM port.
Bridge libraries: `powerbroker2/ELMduino@3.4.1`, `esphome/ESPAsyncWebServer-esphome@3.4.0`.
## Notes
- `src/montserrat_100.c` (100 px digits font) is present but not referenced by any sketch yet.
- SD-based GIFs use LVGL drive `A:` = `LV_USE_FS_FATFS` (reads through ESP-IDF FatFS on the SD_MMC mount). Not yet verified on hardware.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

@@ -1,713 +0,0 @@
/**
* @file lv_conf.h
* Configuration file for v8.3.0-dev
*/
/*
* Copy this file as `lv_conf.h`
* 1. simply next to the `lvgl` folder
* 2. or any other places and
* - define `LV_CONF_INCLUDE_SIMPLE`
* - add the path as include path
*/
/* clang-format off */
#if 1 /*Set it to "1" to enable content*/
#ifndef LV_CONF_H
#define LV_CONF_H
#include <stdint.h>
/*====================
COLOR SETTINGS
*====================*/
/*Color depth: 1 (1 byte per pixel), 8 (RGB332), 16 (RGB565), 32 (ARGB8888)*/
#define LV_COLOR_DEPTH 16
/*Swap the 2 bytes of RGB565 color. Useful if the display has an 8-bit interface (e.g. SPI)*/
#define LV_COLOR_16_SWAP 1
/*Enable more complex drawing routines to manage screens transparency.
*Can be used if the UI is above another layer, e.g. an OSD menu or video player.
*Requires `LV_COLOR_DEPTH = 32` colors and the screen's `bg_opa` should be set to non LV_OPA_COVER value*/
#define LV_COLOR_SCREEN_TRANSP 0
/* Adjust color mix functions rounding. GPUs might calculate color mix (blending) differently.
* 0: round down, 64: round up from x.75, 128: round up from half, 192: round up from x.25, 254: round up */
#define LV_COLOR_MIX_ROUND_OFS (LV_COLOR_DEPTH == 32 ? 0: 128)
/*Images pixels with this color will not be drawn if they are chroma keyed)*/
#define LV_COLOR_CHROMA_KEY lv_color_hex(0x00ff00) /*pure green*/
/*=========================
MEMORY SETTINGS
*=========================*/
/*1: use custom malloc/free, 0: use the built-in `lv_mem_alloc()` and `lv_mem_free()`*/
#define LV_MEM_CUSTOM 0
#if LV_MEM_CUSTOM == 0
/*Size of the memory available for `lv_mem_alloc()` in bytes (>= 2kB)*/
// #define LV_MEM_SIZE (48U * 1024U) /*[bytes]*/
#define LV_MEM_SIZE (1024U * 1024U * 7u) // 4 * 1 MB
/*Set an address for the memory pool instead of allocating it as a normal array. Can be in external SRAM too.*/
#define LV_MEM_ADR 0 /*0: unused*/
/*Instead of an address give a memory allocator that will be called to get a memory pool for LVGL. E.g. my_malloc*/
#if LV_MEM_ADR == 0
//#define LV_MEM_POOL_INCLUDE your_alloc_library /* Uncomment if using an external allocator*/
//#define LV_MEM_POOL_ALLOC your_alloc /* Uncomment if using an external allocator*/
#define LV_MEM_POOL_INCLUDE <esp32-hal-psram.h>
#define LV_MEM_POOL_ALLOC ps_malloc
#endif
#else /*LV_MEM_CUSTOM*/
// #define LV_MEM_CUSTOM_INCLUDE <stdlib.h> /*Header for the dynamic memory function*/
// #define LV_MEM_CUSTOM_ALLOC malloc
// #define LV_MEM_CUSTOM_FREE free
// #define LV_MEM_CUSTOM_REALLOC realloc
#define LV_MEM_CUSTOM_INCLUDE <esp32-hal-psram.h>
#define LV_MEM_CUSTOM_ALLOC ps_malloc
#define LV_MEM_CUSTOM_FREE free
#define LV_MEM_CUSTOM_REALLOC ps_realloc
#endif /*LV_MEM_CUSTOM*/
/*Number of the intermediate memory buffer used during rendering and other internal processing mechanisms.
*You will see an error log message if there wasn't enough buffers. */
// #define LV_MEM_BUF_MAX_NUM 16
#define LV_MEM_BUF_MAX_NUM 100
/*Use the standard `memcpy` and `memset` instead of LVGL's own functions. (Might or might not be faster).*/
#define LV_MEMCPY_MEMSET_STD 0
/*====================
HAL SETTINGS
*====================*/
/*Default display refresh period. LVG will redraw changed areas with this period time*/
#define LV_DISP_DEF_REFR_PERIOD 15 /*[ms]*/ //15
/*Input device read period in milliseconds*/
#define LV_INDEV_DEF_READ_PERIOD 30 /*[ms]*///30
/*Use a custom tick source that tells the elapsed time in milliseconds.
*It removes the need to manually update the tick with `lv_tick_inc()`)*/
#define LV_TICK_CUSTOM 1
#if LV_TICK_CUSTOM
#define LV_TICK_CUSTOM_INCLUDE "Arduino.h" /*Header for the system time function*/
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (millis()) /*Expression evaluating to current system time in ms*/
#endif /*LV_TICK_CUSTOM*/
/*Default Dot Per Inch. Used to initialize default sizes such as widgets sized, style paddings.
*(Not so important, you can adjust it to modify default sizes and spaces)*/
#define LV_DPI_DEF 130 /*[px/inch]*/
/*=======================
* FEATURE CONFIGURATION
*=======================*/
/*-------------
* Drawing
*-----------*/
/*Enable complex draw engine.
*Required to draw shadow, gradient, rounded corners, circles, arc, skew lines, image transformations or any masks*/
#define LV_DRAW_COMPLEX 1
#if LV_DRAW_COMPLEX != 0
/*Allow buffering some shadow calculation.
*LV_SHADOW_CACHE_SIZE is the max. shadow size to buffer, where shadow size is `shadow_width + radius`
*Caching has LV_SHADOW_CACHE_SIZE^2 RAM cost*/
#define LV_SHADOW_CACHE_SIZE 0
/* Set number of maximally cached circle data.
* The circumference of 1/4 circle are saved for anti-aliasing
* radius * 4 bytes are used per circle (the most often used radiuses are saved)
* 0: to disable caching */
#define LV_CIRCLE_CACHE_SIZE 4
#endif /*LV_DRAW_COMPLEX*/
/*Default image cache size. Image caching keeps the images opened.
*If only the built-in image formats are used there is no real advantage of caching. (I.e. if no new image decoder is added)
*With complex image decoders (e.g. PNG or JPG) caching can save the continuous open/decode of images.
*However the opened images might consume additional RAM.
*0: to disable caching*/
#define LV_IMG_CACHE_DEF_SIZE 50
/*Number of stops allowed per gradient. Increase this to allow more stops.
*This adds (sizeof(lv_color_t) + 1) bytes per additional stop*/
#define LV_GRADIENT_MAX_STOPS 2
/*Default gradient buffer size.
*When LVGL calculates the gradient "maps" it can save them into a cache to avoid calculating them again.
*LV_GRAD_CACHE_DEF_SIZE sets the size of this cache in bytes.
*If the cache is too small the map will be allocated only while it's required for the drawing.
*0 mean no caching.*/
#define LV_GRAD_CACHE_DEF_SIZE 0
/*Allow dithering the gradients (to achieve visual smooth color gradients on limited color depth display)
*LV_DITHER_GRADIENT implies allocating one or two more lines of the object's rendering surface
*The increase in memory consumption is (32 bits * object width) plus 24 bits * object width if using error diffusion */
#define LV_DITHER_GRADIENT 0
#if LV_DITHER_GRADIENT
/*Add support for error diffusion dithering.
*Error diffusion dithering gets a much better visual result, but implies more CPU consumption and memory when drawing.
*The increase in memory consumption is (24 bits * object's width)*/
#define LV_DITHER_ERROR_DIFFUSION 0
#endif
/*Maximum buffer size to allocate for rotation.
*Only used if software rotation is enabled in the display driver.*/
#define LV_DISP_ROT_MAX_BUF (10*1024)
/*-------------
* GPU
*-----------*/
/*Use STM32's DMA2D (aka Chrom Art) GPU*/
#define LV_USE_GPU_STM32_DMA2D 0
#if LV_USE_GPU_STM32_DMA2D
/*Must be defined to include path of CMSIS header of target processor
e.g. "stm32f769xx.h" or "stm32f429xx.h"*/
#define LV_GPU_DMA2D_CMSIS_INCLUDE
#endif
/*Use NXP's PXP GPU iMX RTxxx platforms*/
#define LV_USE_GPU_NXP_PXP 0
#if LV_USE_GPU_NXP_PXP
/*1: Add default bare metal and FreeRTOS interrupt handling routines for PXP (lv_gpu_nxp_pxp_osa.c)
* and call lv_gpu_nxp_pxp_init() automatically during lv_init(). Note that symbol SDK_OS_FREE_RTOS
* has to be defined in order to use FreeRTOS OSA, otherwise bare-metal implementation is selected.
*0: lv_gpu_nxp_pxp_init() has to be called manually before lv_init()
*/
#define LV_USE_GPU_NXP_PXP_AUTO_INIT 0
#endif
/*Use NXP's VG-Lite GPU iMX RTxxx platforms*/
#define LV_USE_GPU_NXP_VG_LITE 0
/*Use SDL renderer API*/
#define LV_USE_GPU_SDL 0
#if LV_USE_GPU_SDL
#define LV_GPU_SDL_INCLUDE_PATH <SDL2/SDL.h>
/*Texture cache size, 8MB by default*/
#define LV_GPU_SDL_LRU_SIZE (1024 * 1024 * 8)
/*Custom blend mode for mask drawing, disable if you need to link with older SDL2 lib*/
#define LV_GPU_SDL_CUSTOM_BLEND_MODE (SDL_VERSION_ATLEAST(2, 0, 6))
#endif
/*-------------
* Logging
*-----------*/
/*Enable the log module*/
#define LV_USE_LOG 1
#if LV_USE_LOG
/*How important log should be added:
*LV_LOG_LEVEL_TRACE A lot of logs to give detailed information
*LV_LOG_LEVEL_INFO Log important events
*LV_LOG_LEVEL_WARN Log if something unwanted happened but didn't cause a problem
*LV_LOG_LEVEL_ERROR Only critical issue, when the system may fail
*LV_LOG_LEVEL_USER Only logs added by the user
*LV_LOG_LEVEL_NONE Do not log anything*/
#define LV_LOG_LEVEL LV_LOG_LEVEL_INFO
/*1: Print the log with 'printf';
*0: User need to register a callback with `lv_log_register_print_cb()`*/
#define LV_LOG_PRINTF 1
/*Enable/disable LV_LOG_TRACE in modules that produces a huge number of logs*/
#define LV_LOG_TRACE_MEM 1
#define LV_LOG_TRACE_TIMER 1
#define LV_LOG_TRACE_INDEV 1
#define LV_LOG_TRACE_DISP_REFR 1
#define LV_LOG_TRACE_EVENT 1
#define LV_LOG_TRACE_OBJ_CREATE 1
#define LV_LOG_TRACE_LAYOUT 1
#define LV_LOG_TRACE_ANIM 1
#endif /*LV_USE_LOG*/
/*-------------
* Asserts
*-----------*/
/*Enable asserts if an operation is failed or an invalid data is found.
*If LV_USE_LOG is enabled an error message will be printed on failure*/
#define LV_USE_ASSERT_NULL 1 /*Check if the parameter is NULL. (Very fast, recommended)*/
#define LV_USE_ASSERT_MALLOC 1 /*Checks is the memory is successfully allocated or no. (Very fast, recommended)*/
#define LV_USE_ASSERT_STYLE 0 /*Check if the styles are properly initialized. (Very fast, recommended)*/
#define LV_USE_ASSERT_MEM_INTEGRITY 0 /*Check the integrity of `lv_mem` after critical operations. (Slow)*/
#define LV_USE_ASSERT_OBJ 0 /*Check the object's type and existence (e.g. not deleted). (Slow)*/
/*Add a custom handler when assert happens e.g. to restart the MCU*/
#define LV_ASSERT_HANDLER_INCLUDE <stdint.h>
#define LV_ASSERT_HANDLER while(1); /*Halt by default*/
/*-------------
* Others
*-----------*/
/*1: Show CPU usage and FPS count*/
#define LV_USE_PERF_MONITOR 0
#if LV_USE_PERF_MONITOR
#define LV_USE_PERF_MONITOR_POS LV_ALIGN_BOTTOM_RIGHT
#endif
/*1: Show the used memory and the memory fragmentation
* Requires LV_MEM_CUSTOM = 0*/
#define LV_USE_MEM_MONITOR 0
#if LV_USE_MEM_MONITOR
#define LV_USE_MEM_MONITOR_POS LV_ALIGN_BOTTOM_LEFT
#endif
/*1: Draw random colored rectangles over the redrawn areas*/
#define LV_USE_REFR_DEBUG 0
/*Change the built in (v)snprintf functions*/
#define LV_SPRINTF_CUSTOM 0
#if LV_SPRINTF_CUSTOM
#define LV_SPRINTF_INCLUDE <stdio.h>
#define lv_snprintf snprintf
#define lv_vsnprintf vsnprintf
#else /*LV_SPRINTF_CUSTOM*/
#define LV_SPRINTF_USE_FLOAT 0
#endif /*LV_SPRINTF_CUSTOM*/
#define LV_USE_USER_DATA 1
/*Garbage Collector settings
*Used if lvgl is bound to higher level language and the memory is managed by that language*/
#define LV_ENABLE_GC 0
#if LV_ENABLE_GC != 0
#define LV_GC_INCLUDE "gc.h" /*Include Garbage Collector related things*/
#endif /*LV_ENABLE_GC*/
/*=====================
* COMPILER SETTINGS
*====================*/
/*For big endian systems set to 1*/
#define LV_BIG_ENDIAN_SYSTEM 0
/*Define a custom attribute to `lv_tick_inc` function*/
#define LV_ATTRIBUTE_TICK_INC
/*Define a custom attribute to `lv_timer_handler` function*/
#define LV_ATTRIBUTE_TIMER_HANDLER
/*Define a custom attribute to `lv_disp_flush_ready` function*/
#define LV_ATTRIBUTE_FLUSH_READY
/*Required alignment size for buffers*/
#define LV_ATTRIBUTE_MEM_ALIGN_SIZE 1
/*Will be added where memories needs to be aligned (with -Os data might not be aligned to boundary by default).
* E.g. __attribute__((aligned(4)))*/
#define LV_ATTRIBUTE_MEM_ALIGN
/*Attribute to mark large constant arrays for example font's bitmaps*/
#define LV_ATTRIBUTE_LARGE_CONST
/*Compiler prefix for a big array declaration in RAM*/
#define LV_ATTRIBUTE_LARGE_RAM_ARRAY
/*Place performance critical functions into a faster memory (e.g RAM)*/
#define LV_ATTRIBUTE_FAST_MEM
/*Prefix variables that are used in GPU accelerated operations, often these need to be placed in RAM sections that are DMA accessible*/
#define LV_ATTRIBUTE_DMA
/*Export integer constant to binding. This macro is used with constants in the form of LV_<CONST> that
*should also appear on LVGL binding API such as Micropython.*/
#define LV_EXPORT_CONST_INT(int_value) struct _silence_gcc_warning /*The default value just prevents GCC warning*/
/*Extend the default -32k..32k coordinate range to -4M..4M by using int32_t for coordinates instead of int16_t*/
#define LV_USE_LARGE_COORD 0
/*==================
* FONT USAGE
*===================*/
/*Montserrat fonts with ASCII range and some symbols using bpp = 4
*https://fonts.google.com/specimen/Montserrat*/
#define LV_FONT_MONTSERRAT_8 0
#define LV_FONT_MONTSERRAT_10 0
#define LV_FONT_MONTSERRAT_12 0
#define LV_FONT_MONTSERRAT_14 0
#define LV_FONT_MONTSERRAT_16 0
#define LV_FONT_MONTSERRAT_18 0
#define LV_FONT_MONTSERRAT_20 0
#define LV_FONT_MONTSERRAT_22 0
#define LV_FONT_MONTSERRAT_24 0
#define LV_FONT_MONTSERRAT_26 0
#define LV_FONT_MONTSERRAT_28 0
#define LV_FONT_MONTSERRAT_30 0
#define LV_FONT_MONTSERRAT_32 0
#define LV_FONT_MONTSERRAT_34 0
#define LV_FONT_MONTSERRAT_36 0
#define LV_FONT_MONTSERRAT_38 0
#define LV_FONT_MONTSERRAT_40 0
#define LV_FONT_MONTSERRAT_42 0
#define LV_FONT_MONTSERRAT_44 0
#define LV_FONT_MONTSERRAT_46 0
#define LV_FONT_MONTSERRAT_48 1
/*Demonstrate special features*/
#define LV_FONT_MONTSERRAT_12_SUBPX 0
#define LV_FONT_MONTSERRAT_28_COMPRESSED 0 /*bpp = 3*/
#define LV_FONT_DEJAVU_16_PERSIAN_HEBREW 0 /*Hebrew, Arabic, Persian letters and all their forms*/
#define LV_FONT_SIMSUN_16_CJK 0 /*1000 most common CJK radicals*/
/*Pixel perfect monospace fonts*/
#define LV_FONT_UNSCII_8 0
#define LV_FONT_UNSCII_16 0
/*Optionally declare custom fonts here.
*You can use these fonts as default font too and they will be available globally.
*E.g. #define LV_FONT_CUSTOM_DECLARE LV_FONT_DECLARE(my_font_1) LV_FONT_DECLARE(my_font_2)*/
#define LV_FONT_CUSTOM_DECLARE
/*Always set a default font*/
#define LV_FONT_DEFAULT &lv_font_montserrat_48
/*Enable handling large font and/or fonts with a lot of characters.
*The limit depends on the font size, font face and bpp.
*Compiler error will be triggered if a font needs it.*/
#define LV_FONT_FMT_TXT_LARGE 0
/*Enables/disables support for compressed fonts.*/
#define LV_USE_FONT_COMPRESSED 0
/*Enable subpixel rendering*/
#define LV_USE_FONT_SUBPX 0
#if LV_USE_FONT_SUBPX
/*Set the pixel order of the display. Physical order of RGB channels. Doesn't matter with "normal" fonts.*/
#define LV_FONT_SUBPX_BGR 0 /*0: RGB; 1:BGR order*/
#endif
/*=================
* TEXT SETTINGS
*=================*/
/**
* Select a character encoding for strings.
* Your IDE or editor should have the same character encoding
* - LV_TXT_ENC_UTF8
* - LV_TXT_ENC_ASCII
*/
#define LV_TXT_ENC LV_TXT_ENC_UTF8
/*Can break (wrap) texts on these chars*/
#define LV_TXT_BREAK_CHARS " ,.;:-_"
/*If a word is at least this long, will break wherever "prettiest"
*To disable, set to a value <= 0*/
#define LV_TXT_LINE_BREAK_LONG_LEN 0
/*Minimum number of characters in a long word to put on a line before a break.
*Depends on LV_TXT_LINE_BREAK_LONG_LEN.*/
#define LV_TXT_LINE_BREAK_LONG_PRE_MIN_LEN 3
/*Minimum number of characters in a long word to put on a line after a break.
*Depends on LV_TXT_LINE_BREAK_LONG_LEN.*/
#define LV_TXT_LINE_BREAK_LONG_POST_MIN_LEN 3
/*The control character to use for signalling text recoloring.*/
#define LV_TXT_COLOR_CMD "#"
/*Support bidirectional texts. Allows mixing Left-to-Right and Right-to-Left texts.
*The direction will be processed according to the Unicode Bidirectional Algorithm:
*https://www.w3.org/International/articles/inline-bidi-markup/uba-basics*/
#define LV_USE_BIDI 0
#if LV_USE_BIDI
/*Set the default direction. Supported values:
*`LV_BASE_DIR_LTR` Left-to-Right
*`LV_BASE_DIR_RTL` Right-to-Left
*`LV_BASE_DIR_AUTO` detect texts base direction*/
#define LV_BIDI_BASE_DIR_DEF LV_BASE_DIR_AUTO
#endif
/*Enable Arabic/Persian processing
*In these languages characters should be replaced with an other form based on their position in the text*/
#define LV_USE_ARABIC_PERSIAN_CHARS 0
/*==================
* WIDGET USAGE
*================*/
/*Documentation of the widgets: https://docs.lvgl.io/latest/en/html/widgets/index.html*/
#define LV_USE_ARC 1
#define LV_USE_ANIMIMG 1
#define LV_USE_BAR 1
#define LV_USE_BTN 1
#define LV_USE_BTNMATRIX 1
#define LV_USE_CANVAS 1
#define LV_USE_CHECKBOX 1
#define LV_USE_DROPDOWN 1 /*Requires: lv_label*/
#define LV_USE_IMG 1 /*Requires: lv_label*/
#define LV_USE_LABEL 1
#if LV_USE_LABEL
#define LV_LABEL_TEXT_SELECTION 1 /*Enable selecting text of the label*/
#define LV_LABEL_LONG_TXT_HINT 1 /*Store some extra info in labels to speed up drawing of very long texts*/
#endif
#define LV_USE_LINE 1
#define LV_USE_ROLLER 1 /*Requires: lv_label*/
#if LV_USE_ROLLER
#define LV_ROLLER_INF_PAGES 7 /*Number of extra "pages" when the roller is infinite*/
#endif
#define LV_USE_SLIDER 1 /*Requires: lv_bar*/
#define LV_USE_SWITCH 1
#define LV_USE_TEXTAREA 1 /*Requires: lv_label*/
#if LV_USE_TEXTAREA != 0
#define LV_TEXTAREA_DEF_PWD_SHOW_TIME 1500 /*ms*/
#endif
#define LV_USE_TABLE 1
/*==================
* EXTRA COMPONENTS
*==================*/
/*-----------
* Widgets
*----------*/
#define LV_USE_CALENDAR 1
#if LV_USE_CALENDAR
#define LV_CALENDAR_WEEK_STARTS_MONDAY 0
#if LV_CALENDAR_WEEK_STARTS_MONDAY
#define LV_CALENDAR_DEFAULT_DAY_NAMES {"Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"}
#else
#define LV_CALENDAR_DEFAULT_DAY_NAMES {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"}
#endif
#define LV_CALENDAR_DEFAULT_MONTH_NAMES {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}
#define LV_USE_CALENDAR_HEADER_ARROW 1
#define LV_USE_CALENDAR_HEADER_DROPDOWN 1
#endif /*LV_USE_CALENDAR*/
#define LV_USE_CHART 1
#define LV_USE_COLORWHEEL 1
#define LV_USE_IMGBTN 1
#define LV_USE_KEYBOARD 1
#define LV_USE_LED 1
#define LV_USE_LIST 1
#define LV_USE_MENU 1
#define LV_USE_METER 1
#define LV_USE_MSGBOX 1
#define LV_USE_SPINBOX 1
#define LV_USE_SPINNER 1
#define LV_USE_TABVIEW 1
#define LV_USE_TILEVIEW 1
#define LV_USE_WIN 1
#define LV_USE_SPAN 1
#if LV_USE_SPAN
/*A line text can contain maximum num of span descriptor */
#define LV_SPAN_SNIPPET_STACK_SIZE 64
#endif
/*-----------
* Themes
*----------*/
/*A simple, impressive and very complete theme*/
#define LV_USE_THEME_DEFAULT 1
#if LV_USE_THEME_DEFAULT
/*0: Light mode; 1: Dark mode*/
#define LV_THEME_DEFAULT_DARK 0
/*1: Enable grow on press*/
#define LV_THEME_DEFAULT_GROW 1
/*Default transition time in [ms]*/
#define LV_THEME_DEFAULT_TRANSITION_TIME 80
#endif /*LV_USE_THEME_DEFAULT*/
/*A very simple theme that is a good starting point for a custom theme*/
#define LV_USE_THEME_BASIC 1
/*A theme designed for monochrome displays*/
#define LV_USE_THEME_MONO 1
/*-----------
* Layouts
*----------*/
/*A layout similar to Flexbox in CSS.*/
#define LV_USE_FLEX 1
/*A layout similar to Grid in CSS.*/
#define LV_USE_GRID 1
/*---------------------
* 3rd party libraries
*--------------------*/
/*File system interfaces for common APIs */
/*API for fopen, fread, etc*/
#define LV_USE_FS_STDIO 0
#if LV_USE_FS_STDIO
#define LV_FS_STDIO_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#define LV_FS_STDIO_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/
#define LV_FS_STDIO_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
#endif
/*API for open, read, etc*/
#define LV_USE_FS_POSIX 0
#if LV_USE_FS_POSIX
#define LV_FS_POSIX_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#define LV_FS_POSIX_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/
#define LV_FS_POSIX_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
#endif
/*API for CreateFile, ReadFile, etc*/
#define LV_USE_FS_WIN32 0
#if LV_USE_FS_WIN32
#define LV_FS_WIN32_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#define LV_FS_WIN32_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/
#define LV_FS_WIN32_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
#endif
/*API for FATFS (needs to be added separately). Uses f_open, f_read, etc*/
#include <ff.h> // You can put this at the top of the header file
typedef FF_DIR DIR; // Add this line
#define LV_USE_FS_FATFS 1
#if LV_USE_FS_FATFS
#define LV_FS_FATFS_LETTER 'A' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#define LV_FS_FATFS_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
#endif
/*PNG decoder library*/
#define LV_USE_PNG 1
/*BMP decoder library*/
#define LV_USE_BMP 1
/* JPG + split JPG decoder library.
* Split JPG is a custom format optimized for embedded systems. */
#define LV_USE_SJPG 1
/*GIF decoder library*/
#define LV_USE_GIF 1
/*QR code library*/
#define LV_USE_QRCODE 0
/*FreeType library*/
#define LV_USE_FREETYPE 0
#if LV_USE_FREETYPE
/*Memory used by FreeType to cache characters [bytes] (-1: no caching)*/
#define LV_FREETYPE_CACHE_SIZE (16 * 1024)
#if LV_FREETYPE_CACHE_SIZE >= 0
/* 1: bitmap cache use the sbit cache, 0:bitmap cache use the image cache. */
/* sbit cache:it is much more memory efficient for small bitmaps(font size < 256) */
/* if font size >= 256, must be configured as image cache */
#define LV_FREETYPE_SBIT_CACHE 0
/* Maximum number of opened FT_Face/FT_Size objects managed by this cache instance. */
/* (0:use system defaults) */
#define LV_FREETYPE_CACHE_FT_FACES 0
#define LV_FREETYPE_CACHE_FT_SIZES 0
#endif
#endif
/*Rlottie library*/
#define LV_USE_RLOTTIE 0
/*FFmpeg library for image decoding and playing videos
*Supports all major image formats so do not enable other image decoder with it*/
#define LV_USE_FFMPEG 0
#if LV_USE_FFMPEG
/*Dump input information to stderr*/
#define LV_FFMPEG_AV_DUMP_FORMAT 0
#endif
/*-----------
* Others
*----------*/
/*1: Enable API to take snapshot for object*/
#define LV_USE_SNAPSHOT 0
/*1: Enable Monkey test*/
#define LV_USE_MONKEY 0
/*1: Enable grid navigation*/
#define LV_USE_GRIDNAV 0
/*1: Enable lv_obj fragment*/
#define LV_USE_FRAGMENT 0
/*==================
* EXAMPLES
*==================*/
/*Enable the examples to be built with the library*/
#define LV_BUILD_EXAMPLES 0
/*===================
* DEMO USAGE
====================*/
/*Show some widget. It might be required to increase `LV_MEM_SIZE` */
#define LV_USE_DEMO_WIDGETS 0
#if LV_USE_DEMO_WIDGETS
#define LV_DEMO_WIDGETS_SLIDESHOW 0
#endif
/*Demonstrate the usage of encoder and keyboard*/
#define LV_USE_DEMO_KEYPAD_AND_ENCODER 0
/*Benchmark your system*/
#define LV_USE_DEMO_BENCHMARK 0
/*Stress test for LVGL*/
#define LV_USE_DEMO_STRESS 0
/*Music player demo*/
#define LV_USE_DEMO_MUSIC 0
#if LV_USE_DEMO_MUSIC
# define LV_DEMO_MUSIC_SQUARE 1
# define LV_DEMO_MUSIC_LANDSCAPE 1
# define LV_DEMO_MUSIC_ROUND 1
# define LV_DEMO_MUSIC_LARGE 1
# define LV_DEMO_MUSIC_AUTO_PLAY 1
#endif
/*--END OF LV_CONF_H--*/
#endif /*LV_CONF_H*/
#endif /*End of "Content enable"*/
@@ -1,38 +0,0 @@
#ifndef _PINCFG_H_
#define _PINCFG_H_
#define TFT_BLK 15
#define TFT_RST 47
#define TFT_CS 10
#define TFT_SCK 9
#define TFT_SDA0 11
#define TFT_SDA1 12
#define TFT_SDA2 13
#define TFT_SDA3 14
#define BTN_PIN 0
#define TOUCH_PIN_NUM_I2C_SCL 8
#define TOUCH_PIN_NUM_I2C_SDA 7
#define TOUCH_PIN_NUM_INT 41
#define TOUCH_PIN_NUM_RST 40
#define SD_MMC_D0_PIN 2
#define SD_MMC_D1_PIN 1
#define SD_MMC_D2_PIN 6
#define SD_MMC_D3_PIN 5
#define SD_MMC_CLK_PIN 3
#define SD_MMC_CMD_PIN 4
#define AUDIO_I2S_MCK_IO -1 // MCK
#define AUDIO_I2S_BCK_IO 18 // BCK
#define AUDIO_I2S_WS_IO 16 // LCK
#define AUDIO_I2S_DO_IO 17 // DIN
#define AUDIO_MUTE_PIN 48 // 低电平静音
#define MIC_I2S_WS 45
#define MIC_I2S_SD 46
#define MIC_I2S_SCK 42
#endif
@@ -1,439 +0,0 @@
#ifndef _SCR_ST77916_H_
#define _SCR_ST77916_H_
#include "pincfg.h"
#include <lvgl.h>
#include <ESP_IOExpander.h>
#include <ESP_Panel_Library.h>
#define SCREEN_RES_HOR 360
#define SCREEN_RES_VER 360
static lv_color_t *disp_draw_buf;
static lv_disp_draw_buf_t draw_buf;
static lv_disp_drv_t disp_drv;
static lv_indev_t *indev_touchpad;
static ESP_PanelBacklight *backlight = NULL;
static ESP_PanelLcd *lcd = NULL;
static ESP_PanelTouch *touch = NULL;
#define USE_CUSTOM_INIT_CMD 0 // 是否用自定义的初始化代码
#if TOUCH_PIN_NUM_INT >= 0
IRAM_ATTR bool onTouchInterruptCallback(void *user_data)
{
return false;
}
#endif
const esp_lcd_panel_vendor_init_cmd_t lcd_init_cmd[] = {
{0xF0, (uint8_t[]){0x08}, 1, 0},
{0xF2, (uint8_t[]){0x08}, 1, 0},
{0x9B, (uint8_t[]){0x51}, 1, 0},
{0x86, (uint8_t[]){0x53}, 1, 0},
{0xF2, (uint8_t[]){0x80}, 1, 0},
{0xF0, (uint8_t[]){0x00}, 1, 0},
{0xF0, (uint8_t[]){0x01}, 1, 0},
{0xF1, (uint8_t[]){0x01}, 1, 0},
{0xB0, (uint8_t[]){0x54}, 1, 0},
{0xB1, (uint8_t[]){0x3F}, 1, 0},
{0xB2, (uint8_t[]){0x2A}, 1, 0},
{0xB4, (uint8_t[]){0x46}, 1, 0},
{0xB5, (uint8_t[]){0x34}, 1, 0},
{0xB6, (uint8_t[]){0xD5}, 1, 0},
{0xB7, (uint8_t[]){0x30}, 1, 0},
{0xBA, (uint8_t[]){0x00}, 1, 0},
{0xBB, (uint8_t[]){0x08}, 1, 0},
{0xBC, (uint8_t[]){0x08}, 1, 0},
{0xBD, (uint8_t[]){0x00}, 1, 0},
{0xC0, (uint8_t[]){0x80}, 1, 0},
{0xC1, (uint8_t[]){0x10}, 1, 0},
{0xC2, (uint8_t[]){0x37}, 1, 0},
{0xC3, (uint8_t[]){0x80}, 1, 0},
{0xC4, (uint8_t[]){0x10}, 1, 0},
{0xC5, (uint8_t[]){0x37}, 1, 0},
{0xC6, (uint8_t[]){0xA9}, 1, 0},
{0xC7, (uint8_t[]){0x41}, 1, 0},
{0xC8, (uint8_t[]){0x51}, 1, 0},
{0xC9, (uint8_t[]){0xA9}, 1, 0},
{0xCA, (uint8_t[]){0x41}, 1, 0},
{0xCB, (uint8_t[]){0x51}, 1, 0},
{0xD0, (uint8_t[]){0x91}, 1, 0},
{0xD1, (uint8_t[]){0x68}, 1, 0},
{0xD2, (uint8_t[]){0x69}, 1, 0},
{0xF5, (uint8_t[]){0x00, 0xA5}, 2, 0},
{0xDD, (uint8_t[]){0x3F}, 1, 0},
{0xDE, (uint8_t[]){0x3F}, 1, 0},
{0xF1, (uint8_t[]){0x10}, 1, 0},
{0xF0, (uint8_t[]){0x00}, 1, 0},
{0xF0, (uint8_t[]){0x02}, 1, 0},
{0xE0, (uint8_t[]){0x70, 0x09, 0x12, 0x0C, 0x0B, 0x27, 0x38, 0x54, 0x4E, 0x19, 0x15, 0x15, 0x2C, 0x2F}, 14, 0},
{0xE1, (uint8_t[]){0x70, 0x08, 0x11, 0x0C, 0x0B, 0x27, 0x38, 0x43, 0x4C, 0x18, 0x14, 0x14, 0x2B, 0x2D}, 14, 0},
{0xF0, (uint8_t[]){0x10}, 1, 0},
{0xF3, (uint8_t[]){0x10}, 1, 0},
{0xE0, (uint8_t[]){0x08}, 1, 0},
{0xE1, (uint8_t[]){0x00}, 1, 0},
{0xE2, (uint8_t[]){0x00}, 1, 0},
{0xE3, (uint8_t[]){0x00}, 1, 0},
{0xE4, (uint8_t[]){0xE0}, 1, 0},
{0xE5, (uint8_t[]){0x06}, 1, 0},
{0xE6, (uint8_t[]){0x21}, 1, 0},
{0xE7, (uint8_t[]){0x00}, 1, 0},
{0xE8, (uint8_t[]){0x05}, 1, 0},
{0xE9, (uint8_t[]){0x82}, 1, 0},
{0xEA, (uint8_t[]){0xDF}, 1, 0},
{0xEB, (uint8_t[]){0x89}, 1, 0},
{0xEC, (uint8_t[]){0x20}, 1, 0},
{0xED, (uint8_t[]){0x14}, 1, 0},
{0xEE, (uint8_t[]){0xFF}, 1, 0},
{0xEF, (uint8_t[]){0x00}, 1, 0},
{0xF8, (uint8_t[]){0xFF}, 1, 0},
{0xF9, (uint8_t[]){0x00}, 1, 0},
{0xFA, (uint8_t[]){0x00}, 1, 0},
{0xFB, (uint8_t[]){0x30}, 1, 0},
{0xFC, (uint8_t[]){0x00}, 1, 0},
{0xFD, (uint8_t[]){0x00}, 1, 0},
{0xFE, (uint8_t[]){0x00}, 1, 0},
{0xFF, (uint8_t[]){0x00}, 1, 0},
{0x60, (uint8_t[]){0x42}, 1, 0},
{0x61, (uint8_t[]){0xE0}, 1, 0},
{0x62, (uint8_t[]){0x40}, 1, 0},
{0x63, (uint8_t[]){0x40}, 1, 0},
{0x64, (uint8_t[]){0x02}, 1, 0},
{0x65, (uint8_t[]){0x00}, 1, 0},
{0x66, (uint8_t[]){0x40}, 1, 0},
{0x67, (uint8_t[]){0x03}, 1, 0},
{0x68, (uint8_t[]){0x00}, 1, 0},
{0x69, (uint8_t[]){0x00}, 1, 0},
{0x6A, (uint8_t[]){0x00}, 1, 0},
{0x6B, (uint8_t[]){0x00}, 1, 0},
{0x70, (uint8_t[]){0x42}, 1, 0},
{0x71, (uint8_t[]){0xE0}, 1, 0},
{0x72, (uint8_t[]){0x40}, 1, 0},
{0x73, (uint8_t[]){0x40}, 1, 0},
{0x74, (uint8_t[]){0x02}, 1, 0},
{0x75, (uint8_t[]){0x00}, 1, 0},
{0x76, (uint8_t[]){0x40}, 1, 0},
{0x77, (uint8_t[]){0x03}, 1, 0},
{0x78, (uint8_t[]){0x00}, 1, 0},
{0x79, (uint8_t[]){0x00}, 1, 0},
{0x7A, (uint8_t[]){0x00}, 1, 0},
{0x7B, (uint8_t[]){0x00}, 1, 0},
{0x80, (uint8_t[]){0x48}, 1, 0},
{0x81, (uint8_t[]){0x00}, 1, 0},
{0x82, (uint8_t[]){0x05}, 1, 0},
{0x83, (uint8_t[]){0x02}, 1, 0},
{0x84, (uint8_t[]){0xDD}, 1, 0},
{0x85, (uint8_t[]){0x00}, 1, 0},
{0x86, (uint8_t[]){0x00}, 1, 0},
{0x87, (uint8_t[]){0x00}, 1, 0},
{0x88, (uint8_t[]){0x48}, 1, 0},
{0x89, (uint8_t[]){0x00}, 1, 0},
{0x8A, (uint8_t[]){0x07}, 1, 0},
{0x8B, (uint8_t[]){0x02}, 1, 0},
{0x8C, (uint8_t[]){0xDF}, 1, 0},
{0x8D, (uint8_t[]){0x00}, 1, 0},
{0x8E, (uint8_t[]){0x00}, 1, 0},
{0x8F, (uint8_t[]){0x00}, 1, 0},
{0x90, (uint8_t[]){0x48}, 1, 0},
{0x91, (uint8_t[]){0x00}, 1, 0},
{0x92, (uint8_t[]){0x09}, 1, 0},
{0x93, (uint8_t[]){0x02}, 1, 0},
{0x94, (uint8_t[]){0xE1}, 1, 0},
{0x95, (uint8_t[]){0x00}, 1, 0},
{0x96, (uint8_t[]){0x00}, 1, 0},
{0x97, (uint8_t[]){0x00}, 1, 0},
{0x98, (uint8_t[]){0x48}, 1, 0},
{0x99, (uint8_t[]){0x00}, 1, 0},
{0x9A, (uint8_t[]){0x0B}, 1, 0},
{0x9B, (uint8_t[]){0x02}, 1, 0},
{0x9C, (uint8_t[]){0xE3}, 1, 0},
{0x9D, (uint8_t[]){0x00}, 1, 0},
{0x9E, (uint8_t[]){0x00}, 1, 0},
{0x9F, (uint8_t[]){0x00}, 1, 0},
{0xA0, (uint8_t[]){0x48}, 1, 0},
{0xA1, (uint8_t[]){0x00}, 1, 0},
{0xA2, (uint8_t[]){0x04}, 1, 0},
{0xA3, (uint8_t[]){0x02}, 1, 0},
{0xA4, (uint8_t[]){0xDC}, 1, 0},
{0xA5, (uint8_t[]){0x00}, 1, 0},
{0xA6, (uint8_t[]){0x00}, 1, 0},
{0xA7, (uint8_t[]){0x00}, 1, 0},
{0xA8, (uint8_t[]){0x48}, 1, 0},
{0xA9, (uint8_t[]){0x00}, 1, 0},
{0xAA, (uint8_t[]){0x06}, 1, 0},
{0xAB, (uint8_t[]){0x02}, 1, 0},
{0xAC, (uint8_t[]){0xDE}, 1, 0},
{0xAD, (uint8_t[]){0x00}, 1, 0},
{0xAE, (uint8_t[]){0x00}, 1, 0},
{0xAF, (uint8_t[]){0x00}, 1, 0},
{0xB0, (uint8_t[]){0x48}, 1, 0},
{0xB1, (uint8_t[]){0x00}, 1, 0},
{0xB2, (uint8_t[]){0x08}, 1, 0},
{0xB3, (uint8_t[]){0x02}, 1, 0},
{0xB4, (uint8_t[]){0xE0}, 1, 0},
{0xB5, (uint8_t[]){0x00}, 1, 0},
{0xB6, (uint8_t[]){0x00}, 1, 0},
{0xB7, (uint8_t[]){0x00}, 1, 0},
{0xB8, (uint8_t[]){0x48}, 1, 0},
{0xB9, (uint8_t[]){0x00}, 1, 0},
{0xBA, (uint8_t[]){0x0A}, 1, 0},
{0xBB, (uint8_t[]){0x02}, 1, 0},
{0xBC, (uint8_t[]){0xE2}, 1, 0},
{0xBD, (uint8_t[]){0x00}, 1, 0},
{0xBE, (uint8_t[]){0x00}, 1, 0},
{0xBF, (uint8_t[]){0x00}, 1, 0},
{0xC0, (uint8_t[]){0x12}, 1, 0},
{0xC1, (uint8_t[]){0xAA}, 1, 0},
{0xC2, (uint8_t[]){0x65}, 1, 0},
{0xC3, (uint8_t[]){0x74}, 1, 0},
{0xC4, (uint8_t[]){0x47}, 1, 0},
{0xC5, (uint8_t[]){0x56}, 1, 0},
{0xC6, (uint8_t[]){0x00}, 1, 0},
{0xC7, (uint8_t[]){0x88}, 1, 0},
{0xC8, (uint8_t[]){0x99}, 1, 0},
{0xC9, (uint8_t[]){0x33}, 1, 0},
{0xD0, (uint8_t[]){0x21}, 1, 0},
{0xD1, (uint8_t[]){0xAA}, 1, 0},
{0xD2, (uint8_t[]){0x65}, 1, 0},
{0xD3, (uint8_t[]){0x74}, 1, 0},
{0xD4, (uint8_t[]){0x47}, 1, 0},
{0xD5, (uint8_t[]){0x56}, 1, 0},
{0xD6, (uint8_t[]){0x00}, 1, 0},
{0xD7, (uint8_t[]){0x88}, 1, 0},
{0xD8, (uint8_t[]){0x99}, 1, 0},
{0xD9, (uint8_t[]){0x33}, 1, 0},
{0xF3, (uint8_t[]){0x01}, 1, 0},
{0xF0, (uint8_t[]){0x00}, 1, 0},
{0xF0, (uint8_t[]){0x01}, 1, 0},
{0xF1, (uint8_t[]){0x01}, 1, 0},
{0xA0, (uint8_t[]){0x0B}, 1, 0},
{0xA3, (uint8_t[]){0x2A}, 1, 0},
{0xA5, (uint8_t[]){0xC3}, 1, 1},
{0xA3, (uint8_t[]){0x2B}, 1, 0},
{0xA5, (uint8_t[]){0xC3}, 1, 1},
{0xA3, (uint8_t[]){0x2C}, 1, 0},
{0xA5, (uint8_t[]){0xC3}, 1, 1},
{0xA3, (uint8_t[]){0x2D}, 1, 0},
{0xA5, (uint8_t[]){0xC3}, 1, 1},
{0xA3, (uint8_t[]){0x2E}, 1, 0},
{0xA5, (uint8_t[]){0xC3}, 1, 1},
{0xA3, (uint8_t[]){0x2F}, 1, 0},
{0xA5, (uint8_t[]){0xC3}, 1, 1},
{0xA3, (uint8_t[]){0x30}, 1, 0},
{0xA5, (uint8_t[]){0xC3}, 1, 1},
{0xA3, (uint8_t[]){0x31}, 1, 0},
{0xA5, (uint8_t[]){0xC3}, 1, 1},
{0xA3, (uint8_t[]){0x32}, 1, 0},
{0xA5, (uint8_t[]){0xC3}, 1, 1},
{0xA3, (uint8_t[]){0x33}, 1, 0},
{0xA5, (uint8_t[]){0xC3}, 1, 1},
{0xA0, (uint8_t[]){0x09}, 1, 0},
{0xF1, (uint8_t[]){0x10}, 1, 0},
{0xF0, (uint8_t[]){0x00}, 1, 0},
{0x2A, (uint8_t[]){0x00, 0x00, 0x01, 0x67}, 4, 0},
{0x2B, (uint8_t[]){0x01, 0x68, 0x01, 0x68}, 4, 0},
{0x4D, (uint8_t[]){0x00}, 1, 0},
{0x4E, (uint8_t[]){0x00}, 1, 0},
{0x4F, (uint8_t[]){0x00}, 1, 0},
{0x4C, (uint8_t[]){0x01}, 1, 10},
{0x4C, (uint8_t[]){0x00}, 1, 0},
{0x2A, (uint8_t[]){0x00, 0x00, 0x01, 0x67}, 4, 0},
{0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0x67}, 4, 0},
{0x21, (uint8_t[]){0x00}, 1, 0},
//{0x3A, (uint8_t[]){0x55}, 1, 0}, // color=16
{0x11, (uint8_t[]){0x00}, 1, 120},
{0x29, (uint8_t[]){0x00}, 1, 0},
};
#define TFT_SPI_FREQ_HZ (50 * 1000 * 1000)
static void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{
ESP_PanelLcd *lcd = (ESP_PanelLcd *)disp->user_data;
const int offsetx1 = area->x1;
const int offsetx2 = area->x2;
const int offsety1 = area->y1;
const int offsety2 = area->y2;
lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)color_p);
}
IRAM_ATTR bool onRefreshFinishCallback(void *user_data)
{
lv_disp_drv_t *drv = (lv_disp_drv_t *)user_data;
lv_disp_flush_ready(drv);
return false;
}
void setRotation(uint8_t rot)
{
if (rot > 3)
return;
if (lcd == NULL || touch == NULL)
return;
switch (rot)
{
case 1: // 顺时针90度
lcd->swapXY(true);
lcd->mirrorX(true);
lcd->mirrorY(false);
touch->swapXY(true);
touch->mirrorX(true);
touch->mirrorY(false);
break;
case 2:
lcd->swapXY(false);
lcd->mirrorX(true);
lcd->mirrorY(true);
touch->swapXY(false);
touch->mirrorX(true);
touch->mirrorY(true);
break;
case 3:
lcd->swapXY(true);
lcd->mirrorX(false);
lcd->mirrorY(true);
touch->swapXY(true);
touch->mirrorX(false);
touch->mirrorY(true);
break;
default:
lcd->swapXY(false);
lcd->mirrorX(false);
lcd->mirrorY(false);
touch->swapXY(false);
touch->mirrorX(false);
touch->mirrorY(false);
break;
}
}
void screen_switch(bool on)
{
if (NULL == backlight)
return;
if (on)
backlight->on();
else
backlight->off();
}
// 输入值为0-100
void set_brightness(uint8_t bri)
{
if (NULL == backlight)
return;
backlight->setBrightness(bri);
}
static void touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
{
ESP_PanelTouch *tp = (ESP_PanelTouch *)indev_drv->user_data;
ESP_PanelTouchPoint point;
int read_touch_result = tp->readPoints(&point, 1);
if (read_touch_result > 0)
{
data->point.x = point.x;
data->point.y = point.y;
data->state = LV_INDEV_STATE_PRESSED;
}
else
{
data->state = LV_INDEV_STATE_RELEASED;
}
}
static lv_indev_t *indev_init(ESP_PanelTouch *tp)
{
ESP_PANEL_CHECK_FALSE_RET(tp != nullptr, nullptr, "Invalid touch device");
ESP_PANEL_CHECK_FALSE_RET(tp->getHandle() != nullptr, nullptr, "Touch device is not initialized");
static lv_indev_drv_t indev_drv_tp;
lv_indev_drv_init(&indev_drv_tp);
indev_drv_tp.type = LV_INDEV_TYPE_POINTER;
indev_drv_tp.read_cb = touchpad_read;
indev_drv_tp.user_data = (void *)tp;
return lv_indev_drv_register(&indev_drv_tp);
}
void scr_lvgl_init()
{
ledc_timer_config_t ledc_timer = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.duty_resolution = LEDC_TIMER_13_BIT,
.timer_num = LEDC_TIMER_0,
.freq_hz = 5000,
.clk_cfg = LEDC_AUTO_CLK};
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
ledc_channel_config_t ledc_channel = {
.gpio_num = (TFT_BLK),
.speed_mode = LEDC_LOW_SPEED_MODE,
.channel = LEDC_CHANNEL_0,
.intr_type = LEDC_INTR_DISABLE,
.timer_sel = LEDC_TIMER_0,
.duty = 0,
.hpoint = 0};
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
backlight = new ESP_PanelBacklight(ledc_timer, ledc_channel);
backlight->begin();
backlight->off();
ESP_PanelBus_I2C *touch_bus = new ESP_PanelBus_I2C(TOUCH_PIN_NUM_I2C_SCL, TOUCH_PIN_NUM_I2C_SDA, ESP_LCD_TOUCH_IO_I2C_CST816S_CONFIG());
touch_bus->configI2cFreqHz(400000);
touch_bus->begin();
touch = new ESP_PanelTouch_CST816S(touch_bus, SCREEN_RES_HOR, SCREEN_RES_VER, TOUCH_PIN_NUM_RST, TOUCH_PIN_NUM_INT);
touch->init();
touch->begin();
#if TOUCH_PIN_NUM_INT >= 0
touch->attachInterruptCallback(onTouchInterruptCallback, NULL);
#endif
ESP_PanelBus_QSPI *panel_bus = new ESP_PanelBus_QSPI(TFT_CS, TFT_SCK, TFT_SDA0, TFT_SDA1, TFT_SDA2, TFT_SDA3);
panel_bus->configQspiFreqHz(TFT_SPI_FREQ_HZ);
panel_bus->begin();
lcd = new ESP_PanelLcd_ST77916(panel_bus, 16, TFT_RST);
// 注意,初始化代码的设置必须在INIT之前
// lcd->configVendorCommands(lcd_init_cmd, sizeof(lcd_init_cmd) / sizeof(lcd_init_cmd[0]));
lcd->init();
lcd->reset();
lcd->begin();
lcd->invertColor(true);
// setRotation(0); //设置屏幕方向
lcd->displayOn();
screen_switch(true);
backlight->setBrightness(100); // 设置亮度
size_t lv_cache_rows = 72;
disp_draw_buf = (lv_color_t *)heap_caps_malloc(lv_cache_rows * SCREEN_RES_HOR * 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
lv_init();
lv_disp_draw_buf_init(&draw_buf, disp_draw_buf, NULL, SCREEN_RES_HOR * lv_cache_rows);
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = SCREEN_RES_HOR;
disp_drv.ver_res = SCREEN_RES_VER;
disp_drv.flush_cb = my_disp_flush;
disp_drv.draw_buf = &draw_buf;
disp_drv.user_data = (void *)lcd;
lv_disp_t *disp = lv_disp_drv_register(&disp_drv);
if (lcd->getBus()->getType() != ESP_PANEL_BUS_TYPE_RGB)
{
lcd->attachRefreshFinishCallback(onRefreshFinishCallback, (void *)disp->driver);
}
indev_touchpad = indev_init(touch);
}
#endif
@@ -1,28 +0,0 @@
; JC3636W518 — ESP32-S3R8, 16 MB QIO flash, 8 MB OPI PSRAM, 360x360 ST77916 QSPI + CST816S
; Settings mirror the vendor Arduino IDE setup (2-Program Example/Demo_Arduino/docs/Arduino set up.png)
[env:jc3636w518]
; pioarduino = Arduino core 3.0.5 (ESP32_Display_Panel 0.1.4 needs core 3.x)
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
board = esp32-s3-devkitc-1
framework = arduino
board_build.arduino.memory_type = qio_opi
board_build.flash_mode = qio
board_upload.flash_size = 16MB
board_build.partitions = huge_app.csv
build_flags =
-DBOARD_HAS_PSRAM
-DARDUINO_USB_CDC_ON_BOOT=1
; LVGL (also when compiled as a library) picks up include/lv_conf.h of this project
-DLV_CONF_INCLUDE_SIMPLE
-Iinclude
; vendor-bundled libraries: ESP32_Display_Panel 0.1.4, ESP32_IO_Expander 0.0.2, lvgl 8.3.11
lib_extra_dirs = ../../2-Program Example/Demo_Arduino/library
upload_port = COM21
monitor_port = COM21
monitor_speed = 115200
monitor_filters = esp32_exception_decoder
@@ -1,716 +0,0 @@
/**
* @file lv_conf.h
* Configuration file for v8.3.0-dev
*/
/*
* Copy this file as `lv_conf.h`
* 1. simply next to the `lvgl` folder
* 2. or any other places and
* - define `LV_CONF_INCLUDE_SIMPLE`
* - add the path as include path
*/
/* clang-format off */
#if 1 /*Set it to "1" to enable content*/
#ifndef LV_CONF_H
#define LV_CONF_H
#include <stdint.h>
/*====================
COLOR SETTINGS
*====================*/
/*Color depth: 1 (1 byte per pixel), 8 (RGB332), 16 (RGB565), 32 (ARGB8888)*/
#define LV_COLOR_DEPTH 16
/*Swap the 2 bytes of RGB565 color. Useful if the display has an 8-bit interface (e.g. SPI)*/
#define LV_COLOR_16_SWAP 1
/*Enable more complex drawing routines to manage screens transparency.
*Can be used if the UI is above another layer, e.g. an OSD menu or video player.
*Requires `LV_COLOR_DEPTH = 32` colors and the screen's `bg_opa` should be set to non LV_OPA_COVER value*/
#define LV_COLOR_SCREEN_TRANSP 0
/* Adjust color mix functions rounding. GPUs might calculate color mix (blending) differently.
* 0: round down, 64: round up from x.75, 128: round up from half, 192: round up from x.25, 254: round up */
#define LV_COLOR_MIX_ROUND_OFS (LV_COLOR_DEPTH == 32 ? 0: 128)
/*Images pixels with this color will not be drawn if they are chroma keyed)*/
#define LV_COLOR_CHROMA_KEY lv_color_hex(0x00ff00) /*pure green*/
/*=========================
MEMORY SETTINGS
*=========================*/
/*1: use custom malloc/free, 0: use the built-in `lv_mem_alloc()` and `lv_mem_free()`*/
#define LV_MEM_CUSTOM 1
#if LV_MEM_CUSTOM == 0
/*Size of the memory available for `lv_mem_alloc()` in bytes (>= 2kB)*/
// #define LV_MEM_SIZE (48U * 1024U) /*[bytes]*/
#define LV_MEM_SIZE (1024U * 1024U * 4u) // 4 * 1 MB
/*Set an address for the memory pool instead of allocating it as a normal array. Can be in external SRAM too.*/
#define LV_MEM_ADR 0 /*0: unused*/
/*Instead of an address give a memory allocator that will be called to get a memory pool for LVGL. E.g. my_malloc*/
#if LV_MEM_ADR == 0
//#define LV_MEM_POOL_INCLUDE your_alloc_library /* Uncomment if using an external allocator*/
//#define LV_MEM_POOL_ALLOC your_alloc /* Uncomment if using an external allocator*/
#define LV_MEM_POOL_INCLUDE <esp32-hal-psram.h>
#define LV_MEM_POOL_ALLOC ps_malloc
#endif
#else /*LV_MEM_CUSTOM*/
// #define LV_MEM_CUSTOM_INCLUDE <stdlib.h> /*Header for the dynamic memory function*/
// #define LV_MEM_CUSTOM_ALLOC malloc
// #define LV_MEM_CUSTOM_FREE free
// #define LV_MEM_CUSTOM_REALLOC realloc
#define LV_MEM_CUSTOM_INCLUDE <esp32-hal-psram.h>
#define LV_MEM_CUSTOM_ALLOC ps_malloc
#define LV_MEM_CUSTOM_FREE free
#define LV_MEM_CUSTOM_REALLOC ps_realloc
#endif /*LV_MEM_CUSTOM*/
/*Number of the intermediate memory buffer used during rendering and other internal processing mechanisms.
*You will see an error log message if there wasn't enough buffers. */
// #define LV_MEM_BUF_MAX_NUM 16
#define LV_MEM_BUF_MAX_NUM 32
/*Use the standard `memcpy` and `memset` instead of LVGL's own functions. (Might or might not be faster).*/
#define LV_MEMCPY_MEMSET_STD 0
/*====================
HAL SETTINGS
*====================*/
/*Default display refresh period. LVG will redraw changed areas with this period time*/
#define LV_DISP_DEF_REFR_PERIOD 15 /*[ms]*/ //15
/*Input device read period in milliseconds*/
#define LV_INDEV_DEF_READ_PERIOD 30 /*[ms]*///30
/*Use a custom tick source that tells the elapsed time in milliseconds.
*It removes the need to manually update the tick with `lv_tick_inc()`)*/
#define LV_TICK_CUSTOM 1
#if LV_TICK_CUSTOM
#define LV_TICK_CUSTOM_INCLUDE "Arduino.h" /*Header for the system time function*/
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (millis()) /*Expression evaluating to current system time in ms*/
#endif /*LV_TICK_CUSTOM*/
/*Default Dot Per Inch. Used to initialize default sizes such as widgets sized, style paddings.
*(Not so important, you can adjust it to modify default sizes and spaces)*/
#define LV_DPI_DEF 130 /*[px/inch]*/
/*=======================
* FEATURE CONFIGURATION
*=======================*/
/*-------------
* Drawing
*-----------*/
/*Enable complex draw engine.
*Required to draw shadow, gradient, rounded corners, circles, arc, skew lines, image transformations or any masks*/
#define LV_DRAW_COMPLEX 1
#if LV_DRAW_COMPLEX != 0
/*Allow buffering some shadow calculation.
*LV_SHADOW_CACHE_SIZE is the max. shadow size to buffer, where shadow size is `shadow_width + radius`
*Caching has LV_SHADOW_CACHE_SIZE^2 RAM cost*/
#define LV_SHADOW_CACHE_SIZE 0
/* Set number of maximally cached circle data.
* The circumference of 1/4 circle are saved for anti-aliasing
* radius * 4 bytes are used per circle (the most often used radiuses are saved)
* 0: to disable caching */
#define LV_CIRCLE_CACHE_SIZE 4
#endif /*LV_DRAW_COMPLEX*/
/*Default image cache size. Image caching keeps the images opened.
*If only the built-in image formats are used there is no real advantage of caching. (I.e. if no new image decoder is added)
*With complex image decoders (e.g. PNG or JPG) caching can save the continuous open/decode of images.
*However the opened images might consume additional RAM.
*0: to disable caching*/
#define LV_IMG_CACHE_DEF_SIZE 50
/*Number of stops allowed per gradient. Increase this to allow more stops.
*This adds (sizeof(lv_color_t) + 1) bytes per additional stop*/
#define LV_GRADIENT_MAX_STOPS 2
/*Default gradient buffer size.
*When LVGL calculates the gradient "maps" it can save them into a cache to avoid calculating them again.
*LV_GRAD_CACHE_DEF_SIZE sets the size of this cache in bytes.
*If the cache is too small the map will be allocated only while it's required for the drawing.
*0 mean no caching.*/
#define LV_GRAD_CACHE_DEF_SIZE 0
/*Allow dithering the gradients (to achieve visual smooth color gradients on limited color depth display)
*LV_DITHER_GRADIENT implies allocating one or two more lines of the object's rendering surface
*The increase in memory consumption is (32 bits * object width) plus 24 bits * object width if using error diffusion */
#define LV_DITHER_GRADIENT 0
#if LV_DITHER_GRADIENT
/*Add support for error diffusion dithering.
*Error diffusion dithering gets a much better visual result, but implies more CPU consumption and memory when drawing.
*The increase in memory consumption is (24 bits * object's width)*/
#define LV_DITHER_ERROR_DIFFUSION 0
#endif
/*Maximum buffer size to allocate for rotation.
*Only used if software rotation is enabled in the display driver.*/
#define LV_DISP_ROT_MAX_BUF (10*1024)
/*-------------
* GPU
*-----------*/
/*Use STM32's DMA2D (aka Chrom Art) GPU*/
#define LV_USE_GPU_STM32_DMA2D 0
#if LV_USE_GPU_STM32_DMA2D
/*Must be defined to include path of CMSIS header of target processor
e.g. "stm32f769xx.h" or "stm32f429xx.h"*/
#define LV_GPU_DMA2D_CMSIS_INCLUDE
#endif
/*Use NXP's PXP GPU iMX RTxxx platforms*/
#define LV_USE_GPU_NXP_PXP 0
#if LV_USE_GPU_NXP_PXP
/*1: Add default bare metal and FreeRTOS interrupt handling routines for PXP (lv_gpu_nxp_pxp_osa.c)
* and call lv_gpu_nxp_pxp_init() automatically during lv_init(). Note that symbol SDK_OS_FREE_RTOS
* has to be defined in order to use FreeRTOS OSA, otherwise bare-metal implementation is selected.
*0: lv_gpu_nxp_pxp_init() has to be called manually before lv_init()
*/
#define LV_USE_GPU_NXP_PXP_AUTO_INIT 0
#endif
/*Use NXP's VG-Lite GPU iMX RTxxx platforms*/
#define LV_USE_GPU_NXP_VG_LITE 0
/*Use SDL renderer API*/
#define LV_USE_GPU_SDL 0
#if LV_USE_GPU_SDL
#define LV_GPU_SDL_INCLUDE_PATH <SDL2/SDL.h>
/*Texture cache size, 8MB by default*/
#define LV_GPU_SDL_LRU_SIZE (1024 * 1024 * 8)
/*Custom blend mode for mask drawing, disable if you need to link with older SDL2 lib*/
#define LV_GPU_SDL_CUSTOM_BLEND_MODE (SDL_VERSION_ATLEAST(2, 0, 6))
#endif
/*-------------
* Logging
*-----------*/
/*Enable the log module*/
#define LV_USE_LOG 1
#if LV_USE_LOG
/*How important log should be added:
*LV_LOG_LEVEL_TRACE A lot of logs to give detailed information
*LV_LOG_LEVEL_INFO Log important events
*LV_LOG_LEVEL_WARN Log if something unwanted happened but didn't cause a problem
*LV_LOG_LEVEL_ERROR Only critical issue, when the system may fail
*LV_LOG_LEVEL_USER Only logs added by the user
*LV_LOG_LEVEL_NONE Do not log anything*/
#define LV_LOG_LEVEL LV_LOG_LEVEL_INFO
/*1: Print the log with 'printf';
*0: User need to register a callback with `lv_log_register_print_cb()`*/
#define LV_LOG_PRINTF 1
/*Enable/disable LV_LOG_TRACE in modules that produces a huge number of logs*/
#define LV_LOG_TRACE_MEM 1
#define LV_LOG_TRACE_TIMER 1
#define LV_LOG_TRACE_INDEV 1
#define LV_LOG_TRACE_DISP_REFR 1
#define LV_LOG_TRACE_EVENT 1
#define LV_LOG_TRACE_OBJ_CREATE 1
#define LV_LOG_TRACE_LAYOUT 1
#define LV_LOG_TRACE_ANIM 1
#endif /*LV_USE_LOG*/
/*-------------
* Asserts
*-----------*/
/*Enable asserts if an operation is failed or an invalid data is found.
*If LV_USE_LOG is enabled an error message will be printed on failure*/
#define LV_USE_ASSERT_NULL 1 /*Check if the parameter is NULL. (Very fast, recommended)*/
#define LV_USE_ASSERT_MALLOC 1 /*Checks is the memory is successfully allocated or no. (Very fast, recommended)*/
#define LV_USE_ASSERT_STYLE 0 /*Check if the styles are properly initialized. (Very fast, recommended)*/
#define LV_USE_ASSERT_MEM_INTEGRITY 0 /*Check the integrity of `lv_mem` after critical operations. (Slow)*/
#define LV_USE_ASSERT_OBJ 0 /*Check the object's type and existence (e.g. not deleted). (Slow)*/
/*Add a custom handler when assert happens e.g. to restart the MCU*/
#define LV_ASSERT_HANDLER_INCLUDE <stdint.h>
#define LV_ASSERT_HANDLER while(1); /*Halt by default*/
/*-------------
* Others
*-----------*/
/*1: Show CPU usage and FPS count*/
#define LV_USE_PERF_MONITOR 0
#if LV_USE_PERF_MONITOR
#define LV_USE_PERF_MONITOR_POS LV_ALIGN_BOTTOM_RIGHT
#endif
/*1: Show the used memory and the memory fragmentation
* Requires LV_MEM_CUSTOM = 0*/
#define LV_USE_MEM_MONITOR 0
#if LV_USE_MEM_MONITOR
#define LV_USE_MEM_MONITOR_POS LV_ALIGN_BOTTOM_LEFT
#endif
/*1: Draw random colored rectangles over the redrawn areas*/
#define LV_USE_REFR_DEBUG 0
/*Change the built in (v)snprintf functions*/
#define LV_SPRINTF_CUSTOM 0
#if LV_SPRINTF_CUSTOM
#define LV_SPRINTF_INCLUDE <stdio.h>
#define lv_snprintf snprintf
#define lv_vsnprintf vsnprintf
#else /*LV_SPRINTF_CUSTOM*/
#define LV_SPRINTF_USE_FLOAT 0
#endif /*LV_SPRINTF_CUSTOM*/
#define LV_USE_USER_DATA 1
/*Garbage Collector settings
*Used if lvgl is bound to higher level language and the memory is managed by that language*/
#define LV_ENABLE_GC 0
#if LV_ENABLE_GC != 0
#define LV_GC_INCLUDE "gc.h" /*Include Garbage Collector related things*/
#endif /*LV_ENABLE_GC*/
/*=====================
* COMPILER SETTINGS
*====================*/
/*For big endian systems set to 1*/
#define LV_BIG_ENDIAN_SYSTEM 0
/*Define a custom attribute to `lv_tick_inc` function*/
#define LV_ATTRIBUTE_TICK_INC
/*Define a custom attribute to `lv_timer_handler` function*/
#define LV_ATTRIBUTE_TIMER_HANDLER
/*Define a custom attribute to `lv_disp_flush_ready` function*/
#define LV_ATTRIBUTE_FLUSH_READY
/*Required alignment size for buffers*/
#define LV_ATTRIBUTE_MEM_ALIGN_SIZE 1
/*Will be added where memories needs to be aligned (with -Os data might not be aligned to boundary by default).
* E.g. __attribute__((aligned(4)))*/
#define LV_ATTRIBUTE_MEM_ALIGN
/*Attribute to mark large constant arrays for example font's bitmaps*/
#define LV_ATTRIBUTE_LARGE_CONST
/*Compiler prefix for a big array declaration in RAM*/
#define LV_ATTRIBUTE_LARGE_RAM_ARRAY
/*Place performance critical functions into a faster memory (e.g RAM)*/
#define LV_ATTRIBUTE_FAST_MEM
/*Prefix variables that are used in GPU accelerated operations, often these need to be placed in RAM sections that are DMA accessible*/
#define LV_ATTRIBUTE_DMA
/*Export integer constant to binding. This macro is used with constants in the form of LV_<CONST> that
*should also appear on LVGL binding API such as Micropython.*/
#define LV_EXPORT_CONST_INT(int_value) struct _silence_gcc_warning /*The default value just prevents GCC warning*/
/*Extend the default -32k..32k coordinate range to -4M..4M by using int32_t for coordinates instead of int16_t*/
#define LV_USE_LARGE_COORD 0
/*==================
* FONT USAGE
*===================*/
/*Montserrat fonts with ASCII range and some symbols using bpp = 4
*https://fonts.google.com/specimen/Montserrat*/
#define LV_FONT_MONTSERRAT_8 0
#define LV_FONT_MONTSERRAT_10 0
#define LV_FONT_MONTSERRAT_12 0
#define LV_FONT_MONTSERRAT_14 0
#define LV_FONT_MONTSERRAT_16 0
#define LV_FONT_MONTSERRAT_18 0
#define LV_FONT_MONTSERRAT_20 0
#define LV_FONT_MONTSERRAT_22 0
#define LV_FONT_MONTSERRAT_24 0
#define LV_FONT_MONTSERRAT_26 0
#define LV_FONT_MONTSERRAT_28 0
#define LV_FONT_MONTSERRAT_30 0
#define LV_FONT_MONTSERRAT_32 0
#define LV_FONT_MONTSERRAT_34 0
#define LV_FONT_MONTSERRAT_36 0
#define LV_FONT_MONTSERRAT_38 0
#define LV_FONT_MONTSERRAT_40 0
#define LV_FONT_MONTSERRAT_42 0
#define LV_FONT_MONTSERRAT_44 0
#define LV_FONT_MONTSERRAT_46 0
#define LV_FONT_MONTSERRAT_48 1
/*Demonstrate special features*/
#define LV_FONT_MONTSERRAT_12_SUBPX 0
#define LV_FONT_MONTSERRAT_28_COMPRESSED 0 /*bpp = 3*/
#define LV_FONT_DEJAVU_16_PERSIAN_HEBREW 0 /*Hebrew, Arabic, Persian letters and all their forms*/
#define LV_FONT_SIMSUN_16_CJK 0 /*1000 most common CJK radicals*/
/*Pixel perfect monospace fonts*/
#define LV_FONT_UNSCII_8 0
#define LV_FONT_UNSCII_16 0
// #include "montserrat_100.c"
/*Optionally declare custom fonts here.
*You can use these fonts as default font too and they will be available globally.
*E.g. #define LV_FONT_CUSTOM_DECLARE LV_FONT_DECLARE(my_font_1) LV_FONT_DECLARE(my_font_2)*/
#define LV_FONT_DECLARE//(Montserrat);
/*Always set a default font*/
#define LV_FONT_DEFAULT &lv_font_montserrat_48
/*Enable handling large font and/or fonts with a lot of characters.
*The limit depends on the font size, font face and bpp.
*Compiler error will be triggered if a font needs it.*/
#define LV_FONT_FMT_TXT_LARGE 0
/*Enables/disables support for compressed fonts.*/
#define LV_USE_FONT_COMPRESSED 0
/*Enable subpixel rendering*/
#define LV_USE_FONT_SUBPX 0
#if LV_USE_FONT_SUBPX
/*Set the pixel order of the display. Physical order of RGB channels. Doesn't matter with "normal" fonts.*/
#define LV_FONT_SUBPX_BGR 0 /*0: RGB; 1:BGR order*/
#endif
/*=================
* TEXT SETTINGS
*=================*/
/**
* Select a character encoding for strings.
* Your IDE or editor should have the same character encoding
* - LV_TXT_ENC_UTF8
* - LV_TXT_ENC_ASCII
*/
#define LV_TXT_ENC LV_TXT_ENC_UTF8
/*Can break (wrap) texts on these chars*/
#define LV_TXT_BREAK_CHARS " ,.;:-_"
/*If a word is at least this long, will break wherever "prettiest"
*To disable, set to a value <= 0*/
#define LV_TXT_LINE_BREAK_LONG_LEN 0
/*Minimum number of characters in a long word to put on a line before a break.
*Depends on LV_TXT_LINE_BREAK_LONG_LEN.*/
#define LV_TXT_LINE_BREAK_LONG_PRE_MIN_LEN 3
/*Minimum number of characters in a long word to put on a line after a break.
*Depends on LV_TXT_LINE_BREAK_LONG_LEN.*/
#define LV_TXT_LINE_BREAK_LONG_POST_MIN_LEN 3
/*The control character to use for signalling text recoloring.*/
#define LV_TXT_COLOR_CMD "#"
/*Support bidirectional texts. Allows mixing Left-to-Right and Right-to-Left texts.
*The direction will be processed according to the Unicode Bidirectional Algorithm:
*https://www.w3.org/International/articles/inline-bidi-markup/uba-basics*/
#define LV_USE_BIDI 0
#if LV_USE_BIDI
/*Set the default direction. Supported values:
*`LV_BASE_DIR_LTR` Left-to-Right
*`LV_BASE_DIR_RTL` Right-to-Left
*`LV_BASE_DIR_AUTO` detect texts base direction*/
#define LV_BIDI_BASE_DIR_DEF LV_BASE_DIR_AUTO
#endif
/*Enable Arabic/Persian processing
*In these languages characters should be replaced with an other form based on their position in the text*/
#define LV_USE_ARABIC_PERSIAN_CHARS 0
/*==================
* WIDGET USAGE
*================*/
/*Documentation of the widgets: https://docs.lvgl.io/latest/en/html/widgets/index.html*/
#define LV_USE_ARC 1
#define LV_USE_ANIMIMG 1
#define LV_USE_BAR 1
#define LV_USE_BTN 1
#define LV_USE_BTNMATRIX 1
#define LV_USE_CANVAS 1
#define LV_USE_CHECKBOX 1
#define LV_USE_DROPDOWN 1 /*Requires: lv_label*/
#define LV_USE_IMG 1 /*Requires: lv_label*/
#define LV_USE_LABEL 1
#if LV_USE_LABEL
#define LV_LABEL_TEXT_SELECTION 1 /*Enable selecting text of the label*/
#define LV_LABEL_LONG_TXT_HINT 1 /*Store some extra info in labels to speed up drawing of very long texts*/
#endif
#define LV_USE_LINE 1
#define LV_USE_ROLLER 1 /*Requires: lv_label*/
#if LV_USE_ROLLER
#define LV_ROLLER_INF_PAGES 7 /*Number of extra "pages" when the roller is infinite*/
#endif
#define LV_USE_SLIDER 1 /*Requires: lv_bar*/
#define LV_USE_SWITCH 1
#define LV_USE_TEXTAREA 1 /*Requires: lv_label*/
#if LV_USE_TEXTAREA != 0
#define LV_TEXTAREA_DEF_PWD_SHOW_TIME 1500 /*ms*/
#endif
#define LV_USE_TABLE 1
/*==================
* EXTRA COMPONENTS
*==================*/
/*-----------
* Widgets
*----------*/
#define LV_USE_CALENDAR 1
#if LV_USE_CALENDAR
#define LV_CALENDAR_WEEK_STARTS_MONDAY 0
#if LV_CALENDAR_WEEK_STARTS_MONDAY
#define LV_CALENDAR_DEFAULT_DAY_NAMES {"Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"}
#else
#define LV_CALENDAR_DEFAULT_DAY_NAMES {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"}
#endif
#define LV_CALENDAR_DEFAULT_MONTH_NAMES {"January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"}
#define LV_USE_CALENDAR_HEADER_ARROW 1
#define LV_USE_CALENDAR_HEADER_DROPDOWN 1
#endif /*LV_USE_CALENDAR*/
#define LV_USE_CHART 1
#define LV_USE_COLORWHEEL 1
#define LV_USE_IMGBTN 1
#define LV_USE_KEYBOARD 1
#define LV_USE_LED 1
#define LV_USE_LIST 1
#define LV_USE_MENU 1
#define LV_USE_METER 1
#define LV_USE_MSGBOX 1
#define LV_USE_SPINBOX 1
#define LV_USE_SPINNER 1
#define LV_USE_TABVIEW 1
#define LV_USE_TILEVIEW 1
#define LV_USE_WIN 1
#define LV_USE_SPAN 1
#if LV_USE_SPAN
/*A line text can contain maximum num of span descriptor */
#define LV_SPAN_SNIPPET_STACK_SIZE 64
#endif
/*-----------
* Themes
*----------*/
/*A simple, impressive and very complete theme*/
#define LV_USE_THEME_DEFAULT 1
#if LV_USE_THEME_DEFAULT
/*0: Light mode; 1: Dark mode*/
#define LV_THEME_DEFAULT_DARK 0
/*1: Enable grow on press*/
#define LV_THEME_DEFAULT_GROW 1
/*Default transition time in [ms]*/
#define LV_THEME_DEFAULT_TRANSITION_TIME 80
#endif /*LV_USE_THEME_DEFAULT*/
/*A very simple theme that is a good starting point for a custom theme*/
#define LV_USE_THEME_BASIC 1
/*A theme designed for monochrome displays*/
#define LV_USE_THEME_MONO 1
/*-----------
* Layouts
*----------*/
/*A layout similar to Flexbox in CSS.*/
#define LV_USE_FLEX 1
/*A layout similar to Grid in CSS.*/
#define LV_USE_GRID 1
/*---------------------
* 3rd party libraries
*--------------------*/
/*File system interfaces for common APIs */
/*API for fopen, fread, etc*/
#define LV_USE_FS_STDIO 0
#if LV_USE_FS_STDIO
#define LV_FS_STDIO_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#define LV_FS_STDIO_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/
#define LV_FS_STDIO_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
#endif
/*API for open, read, etc*/
#define LV_USE_FS_POSIX 0
#if LV_USE_FS_POSIX
#define LV_FS_POSIX_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#define LV_FS_POSIX_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/
#define LV_FS_POSIX_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
#endif
/*API for CreateFile, ReadFile, etc*/
#define LV_USE_FS_WIN32 0
#if LV_USE_FS_WIN32
#define LV_FS_WIN32_LETTER '\0' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#define LV_FS_WIN32_PATH "" /*Set the working directory. File/directory paths will be appended to it.*/
#define LV_FS_WIN32_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
#endif
/*API for FATFS (needs to be added separately). Uses f_open, f_read, etc*/
#include <ff.h> // You can put this at the top of the header file
typedef FF_DIR DIR; // Add this line
#define LV_USE_FS_FATFS 1
#if LV_USE_FS_FATFS
#define LV_FS_FATFS_LETTER 'A' /*Set an upper cased letter on which the drive will accessible (e.g. 'A')*/
#define LV_FS_FATFS_CACHE_SIZE 0 /*>0 to cache this number of bytes in lv_fs_read()*/
#endif
/*PNG decoder library*/
#define LV_USE_PNG 1
/*BMP decoder library*/
#define LV_USE_BMP 1
/* JPG + split JPG decoder library.
* Split JPG is a custom format optimized for embedded systems. */
#define LV_USE_SJPG 1
/*GIF decoder library*/
#define LV_USE_GIF 1
/*QR code library*/
#define LV_USE_QRCODE 0
/*FreeType library*/
#define LV_USE_FREETYPE 0
#if LV_USE_FREETYPE
/*Memory used by FreeType to cache characters [bytes] (-1: no caching)*/
#define LV_FREETYPE_CACHE_SIZE (16 * 1024)
#if LV_FREETYPE_CACHE_SIZE >= 0
/* 1: bitmap cache use the sbit cache, 0:bitmap cache use the image cache. */
/* sbit cache:it is much more memory efficient for small bitmaps(font size < 256) */
/* if font size >= 256, must be configured as image cache */
#define LV_FREETYPE_SBIT_CACHE 0
/* Maximum number of opened FT_Face/FT_Size objects managed by this cache instance. */
/* (0:use system defaults) */
#define LV_FREETYPE_CACHE_FT_FACES 0
#define LV_FREETYPE_CACHE_FT_SIZES 0
#endif
#endif
/*Rlottie library*/
#define LV_USE_RLOTTIE 0
/*FFmpeg library for image decoding and playing videos
*Supports all major image formats so do not enable other image decoder with it*/
#define LV_USE_FFMPEG 0
#if LV_USE_FFMPEG
/*Dump input information to stderr*/
#define LV_FFMPEG_AV_DUMP_FORMAT 0
#endif
/*-----------
* Others
*----------*/
/*1: Enable API to take snapshot for object*/
#define LV_USE_SNAPSHOT 0
/*1: Enable Monkey test*/
#define LV_USE_MONKEY 0
/*1: Enable grid navigation*/
#define LV_USE_GRIDNAV 0
/*1: Enable lv_obj fragment*/
#define LV_USE_FRAGMENT 0
/*==================
* EXAMPLES
*==================*/
/*Enable the examples to be built with the library*/
#define LV_BUILD_EXAMPLES 0
/*===================
* DEMO USAGE
====================*/
/*Show some widget. It might be required to increase `LV_MEM_SIZE` */
#define LV_USE_DEMO_WIDGETS 0
#if LV_USE_DEMO_WIDGETS
#define LV_DEMO_WIDGETS_SLIDESHOW 0
#endif
/*Demonstrate the usage of encoder and keyboard*/
#define LV_USE_DEMO_KEYPAD_AND_ENCODER 0
/*Benchmark your system*/
#define LV_USE_DEMO_BENCHMARK 0
/*Stress test for LVGL*/
#define LV_USE_DEMO_STRESS 0
/*Music player demo*/
#define LV_USE_DEMO_MUSIC 0
#if LV_USE_DEMO_MUSIC
# define LV_DEMO_MUSIC_SQUARE 1
# define LV_DEMO_MUSIC_LANDSCAPE 1
# define LV_DEMO_MUSIC_ROUND 1
# define LV_DEMO_MUSIC_LARGE 1
# define LV_DEMO_MUSIC_AUTO_PLAY 1
#endif
/*--END OF LV_CONF_H--*/
#endif /*LV_CONF_H*/
#endif /*End of "Content enable"*/
@@ -1,38 +0,0 @@
#ifndef _PINCFG_H_
#define _PINCFG_H_
#define TFT_BLK 15
#define TFT_RST 47
#define TFT_CS 10
#define TFT_SCK 9
#define TFT_SDA0 11
#define TFT_SDA1 12
#define TFT_SDA2 13
#define TFT_SDA3 14
#define BTN_PIN 0
#define TOUCH_PIN_NUM_I2C_SCL 8
#define TOUCH_PIN_NUM_I2C_SDA 7
#define TOUCH_PIN_NUM_INT 41
#define TOUCH_PIN_NUM_RST 40
#define SD_MMC_D0_PIN 2
#define SD_MMC_D1_PIN 1
#define SD_MMC_D2_PIN 6
#define SD_MMC_D3_PIN 5
#define SD_MMC_CLK_PIN 3
#define SD_MMC_CMD_PIN 4
#define AUDIO_I2S_MCK_IO -1 // MCK
#define AUDIO_I2S_BCK_IO 18 // BCK
#define AUDIO_I2S_WS_IO 16 // LCK
#define AUDIO_I2S_DO_IO 17 // DIN
#define AUDIO_MUTE_PIN 48 // 低电平静音
#define MIC_I2S_WS 45
#define MIC_I2S_SD 46
#define MIC_I2S_SCK 42
#endif
@@ -1,439 +0,0 @@
#ifndef _SCR_ST77916_H_
#define _SCR_ST77916_H_
#include "pincfg.h"
#include <lvgl.h>
#include <ESP_IOExpander.h>
#include <ESP_Panel_Library.h>
#define SCREEN_RES_HOR 360
#define SCREEN_RES_VER 360
static lv_color_t *disp_draw_buf;
static lv_disp_draw_buf_t draw_buf;
static lv_disp_drv_t disp_drv;
static lv_indev_t *indev_touchpad;
static ESP_PanelBacklight *backlight = NULL;
static ESP_PanelLcd *lcd = NULL;
static ESP_PanelTouch *touch = NULL;
#define USE_CUSTOM_INIT_CMD 0 // 是否用自定义的初始化代码
#if TOUCH_PIN_NUM_INT >= 0
IRAM_ATTR bool onTouchInterruptCallback(void *user_data)
{
return false;
}
#endif
const esp_lcd_panel_vendor_init_cmd_t lcd_init_cmd[] = {
{0xF0, (uint8_t[]){0x08}, 1, 0},
{0xF2, (uint8_t[]){0x08}, 1, 0},
{0x9B, (uint8_t[]){0x51}, 1, 0},
{0x86, (uint8_t[]){0x53}, 1, 0},
{0xF2, (uint8_t[]){0x80}, 1, 0},
{0xF0, (uint8_t[]){0x00}, 1, 0},
{0xF0, (uint8_t[]){0x01}, 1, 0},
{0xF1, (uint8_t[]){0x01}, 1, 0},
{0xB0, (uint8_t[]){0x54}, 1, 0},
{0xB1, (uint8_t[]){0x3F}, 1, 0},
{0xB2, (uint8_t[]){0x2A}, 1, 0},
{0xB4, (uint8_t[]){0x46}, 1, 0},
{0xB5, (uint8_t[]){0x34}, 1, 0},
{0xB6, (uint8_t[]){0xD5}, 1, 0},
{0xB7, (uint8_t[]){0x30}, 1, 0},
{0xBA, (uint8_t[]){0x00}, 1, 0},
{0xBB, (uint8_t[]){0x08}, 1, 0},
{0xBC, (uint8_t[]){0x08}, 1, 0},
{0xBD, (uint8_t[]){0x00}, 1, 0},
{0xC0, (uint8_t[]){0x80}, 1, 0},
{0xC1, (uint8_t[]){0x10}, 1, 0},
{0xC2, (uint8_t[]){0x37}, 1, 0},
{0xC3, (uint8_t[]){0x80}, 1, 0},
{0xC4, (uint8_t[]){0x10}, 1, 0},
{0xC5, (uint8_t[]){0x37}, 1, 0},
{0xC6, (uint8_t[]){0xA9}, 1, 0},
{0xC7, (uint8_t[]){0x41}, 1, 0},
{0xC8, (uint8_t[]){0x51}, 1, 0},
{0xC9, (uint8_t[]){0xA9}, 1, 0},
{0xCA, (uint8_t[]){0x41}, 1, 0},
{0xCB, (uint8_t[]){0x51}, 1, 0},
{0xD0, (uint8_t[]){0x91}, 1, 0},
{0xD1, (uint8_t[]){0x68}, 1, 0},
{0xD2, (uint8_t[]){0x69}, 1, 0},
{0xF5, (uint8_t[]){0x00, 0xA5}, 2, 0},
{0xDD, (uint8_t[]){0x3F}, 1, 0},
{0xDE, (uint8_t[]){0x3F}, 1, 0},
{0xF1, (uint8_t[]){0x10}, 1, 0},
{0xF0, (uint8_t[]){0x00}, 1, 0},
{0xF0, (uint8_t[]){0x02}, 1, 0},
{0xE0, (uint8_t[]){0x70, 0x09, 0x12, 0x0C, 0x0B, 0x27, 0x38, 0x54, 0x4E, 0x19, 0x15, 0x15, 0x2C, 0x2F}, 14, 0},
{0xE1, (uint8_t[]){0x70, 0x08, 0x11, 0x0C, 0x0B, 0x27, 0x38, 0x43, 0x4C, 0x18, 0x14, 0x14, 0x2B, 0x2D}, 14, 0},
{0xF0, (uint8_t[]){0x10}, 1, 0},
{0xF3, (uint8_t[]){0x10}, 1, 0},
{0xE0, (uint8_t[]){0x08}, 1, 0},
{0xE1, (uint8_t[]){0x00}, 1, 0},
{0xE2, (uint8_t[]){0x00}, 1, 0},
{0xE3, (uint8_t[]){0x00}, 1, 0},
{0xE4, (uint8_t[]){0xE0}, 1, 0},
{0xE5, (uint8_t[]){0x06}, 1, 0},
{0xE6, (uint8_t[]){0x21}, 1, 0},
{0xE7, (uint8_t[]){0x00}, 1, 0},
{0xE8, (uint8_t[]){0x05}, 1, 0},
{0xE9, (uint8_t[]){0x82}, 1, 0},
{0xEA, (uint8_t[]){0xDF}, 1, 0},
{0xEB, (uint8_t[]){0x89}, 1, 0},
{0xEC, (uint8_t[]){0x20}, 1, 0},
{0xED, (uint8_t[]){0x14}, 1, 0},
{0xEE, (uint8_t[]){0xFF}, 1, 0},
{0xEF, (uint8_t[]){0x00}, 1, 0},
{0xF8, (uint8_t[]){0xFF}, 1, 0},
{0xF9, (uint8_t[]){0x00}, 1, 0},
{0xFA, (uint8_t[]){0x00}, 1, 0},
{0xFB, (uint8_t[]){0x30}, 1, 0},
{0xFC, (uint8_t[]){0x00}, 1, 0},
{0xFD, (uint8_t[]){0x00}, 1, 0},
{0xFE, (uint8_t[]){0x00}, 1, 0},
{0xFF, (uint8_t[]){0x00}, 1, 0},
{0x60, (uint8_t[]){0x42}, 1, 0},
{0x61, (uint8_t[]){0xE0}, 1, 0},
{0x62, (uint8_t[]){0x40}, 1, 0},
{0x63, (uint8_t[]){0x40}, 1, 0},
{0x64, (uint8_t[]){0x02}, 1, 0},
{0x65, (uint8_t[]){0x00}, 1, 0},
{0x66, (uint8_t[]){0x40}, 1, 0},
{0x67, (uint8_t[]){0x03}, 1, 0},
{0x68, (uint8_t[]){0x00}, 1, 0},
{0x69, (uint8_t[]){0x00}, 1, 0},
{0x6A, (uint8_t[]){0x00}, 1, 0},
{0x6B, (uint8_t[]){0x00}, 1, 0},
{0x70, (uint8_t[]){0x42}, 1, 0},
{0x71, (uint8_t[]){0xE0}, 1, 0},
{0x72, (uint8_t[]){0x40}, 1, 0},
{0x73, (uint8_t[]){0x40}, 1, 0},
{0x74, (uint8_t[]){0x02}, 1, 0},
{0x75, (uint8_t[]){0x00}, 1, 0},
{0x76, (uint8_t[]){0x40}, 1, 0},
{0x77, (uint8_t[]){0x03}, 1, 0},
{0x78, (uint8_t[]){0x00}, 1, 0},
{0x79, (uint8_t[]){0x00}, 1, 0},
{0x7A, (uint8_t[]){0x00}, 1, 0},
{0x7B, (uint8_t[]){0x00}, 1, 0},
{0x80, (uint8_t[]){0x48}, 1, 0},
{0x81, (uint8_t[]){0x00}, 1, 0},
{0x82, (uint8_t[]){0x05}, 1, 0},
{0x83, (uint8_t[]){0x02}, 1, 0},
{0x84, (uint8_t[]){0xDD}, 1, 0},
{0x85, (uint8_t[]){0x00}, 1, 0},
{0x86, (uint8_t[]){0x00}, 1, 0},
{0x87, (uint8_t[]){0x00}, 1, 0},
{0x88, (uint8_t[]){0x48}, 1, 0},
{0x89, (uint8_t[]){0x00}, 1, 0},
{0x8A, (uint8_t[]){0x07}, 1, 0},
{0x8B, (uint8_t[]){0x02}, 1, 0},
{0x8C, (uint8_t[]){0xDF}, 1, 0},
{0x8D, (uint8_t[]){0x00}, 1, 0},
{0x8E, (uint8_t[]){0x00}, 1, 0},
{0x8F, (uint8_t[]){0x00}, 1, 0},
{0x90, (uint8_t[]){0x48}, 1, 0},
{0x91, (uint8_t[]){0x00}, 1, 0},
{0x92, (uint8_t[]){0x09}, 1, 0},
{0x93, (uint8_t[]){0x02}, 1, 0},
{0x94, (uint8_t[]){0xE1}, 1, 0},
{0x95, (uint8_t[]){0x00}, 1, 0},
{0x96, (uint8_t[]){0x00}, 1, 0},
{0x97, (uint8_t[]){0x00}, 1, 0},
{0x98, (uint8_t[]){0x48}, 1, 0},
{0x99, (uint8_t[]){0x00}, 1, 0},
{0x9A, (uint8_t[]){0x0B}, 1, 0},
{0x9B, (uint8_t[]){0x02}, 1, 0},
{0x9C, (uint8_t[]){0xE3}, 1, 0},
{0x9D, (uint8_t[]){0x00}, 1, 0},
{0x9E, (uint8_t[]){0x00}, 1, 0},
{0x9F, (uint8_t[]){0x00}, 1, 0},
{0xA0, (uint8_t[]){0x48}, 1, 0},
{0xA1, (uint8_t[]){0x00}, 1, 0},
{0xA2, (uint8_t[]){0x04}, 1, 0},
{0xA3, (uint8_t[]){0x02}, 1, 0},
{0xA4, (uint8_t[]){0xDC}, 1, 0},
{0xA5, (uint8_t[]){0x00}, 1, 0},
{0xA6, (uint8_t[]){0x00}, 1, 0},
{0xA7, (uint8_t[]){0x00}, 1, 0},
{0xA8, (uint8_t[]){0x48}, 1, 0},
{0xA9, (uint8_t[]){0x00}, 1, 0},
{0xAA, (uint8_t[]){0x06}, 1, 0},
{0xAB, (uint8_t[]){0x02}, 1, 0},
{0xAC, (uint8_t[]){0xDE}, 1, 0},
{0xAD, (uint8_t[]){0x00}, 1, 0},
{0xAE, (uint8_t[]){0x00}, 1, 0},
{0xAF, (uint8_t[]){0x00}, 1, 0},
{0xB0, (uint8_t[]){0x48}, 1, 0},
{0xB1, (uint8_t[]){0x00}, 1, 0},
{0xB2, (uint8_t[]){0x08}, 1, 0},
{0xB3, (uint8_t[]){0x02}, 1, 0},
{0xB4, (uint8_t[]){0xE0}, 1, 0},
{0xB5, (uint8_t[]){0x00}, 1, 0},
{0xB6, (uint8_t[]){0x00}, 1, 0},
{0xB7, (uint8_t[]){0x00}, 1, 0},
{0xB8, (uint8_t[]){0x48}, 1, 0},
{0xB9, (uint8_t[]){0x00}, 1, 0},
{0xBA, (uint8_t[]){0x0A}, 1, 0},
{0xBB, (uint8_t[]){0x02}, 1, 0},
{0xBC, (uint8_t[]){0xE2}, 1, 0},
{0xBD, (uint8_t[]){0x00}, 1, 0},
{0xBE, (uint8_t[]){0x00}, 1, 0},
{0xBF, (uint8_t[]){0x00}, 1, 0},
{0xC0, (uint8_t[]){0x12}, 1, 0},
{0xC1, (uint8_t[]){0xAA}, 1, 0},
{0xC2, (uint8_t[]){0x65}, 1, 0},
{0xC3, (uint8_t[]){0x74}, 1, 0},
{0xC4, (uint8_t[]){0x47}, 1, 0},
{0xC5, (uint8_t[]){0x56}, 1, 0},
{0xC6, (uint8_t[]){0x00}, 1, 0},
{0xC7, (uint8_t[]){0x88}, 1, 0},
{0xC8, (uint8_t[]){0x99}, 1, 0},
{0xC9, (uint8_t[]){0x33}, 1, 0},
{0xD0, (uint8_t[]){0x21}, 1, 0},
{0xD1, (uint8_t[]){0xAA}, 1, 0},
{0xD2, (uint8_t[]){0x65}, 1, 0},
{0xD3, (uint8_t[]){0x74}, 1, 0},
{0xD4, (uint8_t[]){0x47}, 1, 0},
{0xD5, (uint8_t[]){0x56}, 1, 0},
{0xD6, (uint8_t[]){0x00}, 1, 0},
{0xD7, (uint8_t[]){0x88}, 1, 0},
{0xD8, (uint8_t[]){0x99}, 1, 0},
{0xD9, (uint8_t[]){0x33}, 1, 0},
{0xF3, (uint8_t[]){0x01}, 1, 0},
{0xF0, (uint8_t[]){0x00}, 1, 0},
{0xF0, (uint8_t[]){0x01}, 1, 0},
{0xF1, (uint8_t[]){0x01}, 1, 0},
{0xA0, (uint8_t[]){0x0B}, 1, 0},
{0xA3, (uint8_t[]){0x2A}, 1, 0},
{0xA5, (uint8_t[]){0xC3}, 1, 1},
{0xA3, (uint8_t[]){0x2B}, 1, 0},
{0xA5, (uint8_t[]){0xC3}, 1, 1},
{0xA3, (uint8_t[]){0x2C}, 1, 0},
{0xA5, (uint8_t[]){0xC3}, 1, 1},
{0xA3, (uint8_t[]){0x2D}, 1, 0},
{0xA5, (uint8_t[]){0xC3}, 1, 1},
{0xA3, (uint8_t[]){0x2E}, 1, 0},
{0xA5, (uint8_t[]){0xC3}, 1, 1},
{0xA3, (uint8_t[]){0x2F}, 1, 0},
{0xA5, (uint8_t[]){0xC3}, 1, 1},
{0xA3, (uint8_t[]){0x30}, 1, 0},
{0xA5, (uint8_t[]){0xC3}, 1, 1},
{0xA3, (uint8_t[]){0x31}, 1, 0},
{0xA5, (uint8_t[]){0xC3}, 1, 1},
{0xA3, (uint8_t[]){0x32}, 1, 0},
{0xA5, (uint8_t[]){0xC3}, 1, 1},
{0xA3, (uint8_t[]){0x33}, 1, 0},
{0xA5, (uint8_t[]){0xC3}, 1, 1},
{0xA0, (uint8_t[]){0x09}, 1, 0},
{0xF1, (uint8_t[]){0x10}, 1, 0},
{0xF0, (uint8_t[]){0x00}, 1, 0},
{0x2A, (uint8_t[]){0x00, 0x00, 0x01, 0x67}, 4, 0},
{0x2B, (uint8_t[]){0x01, 0x68, 0x01, 0x68}, 4, 0},
{0x4D, (uint8_t[]){0x00}, 1, 0},
{0x4E, (uint8_t[]){0x00}, 1, 0},
{0x4F, (uint8_t[]){0x00}, 1, 0},
{0x4C, (uint8_t[]){0x01}, 1, 10},
{0x4C, (uint8_t[]){0x00}, 1, 0},
{0x2A, (uint8_t[]){0x00, 0x00, 0x01, 0x67}, 4, 0},
{0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0x67}, 4, 0},
{0x21, (uint8_t[]){0x00}, 1, 0},
//{0x3A, (uint8_t[]){0x55}, 1, 0}, // color=16
{0x11, (uint8_t[]){0x00}, 1, 120},
{0x29, (uint8_t[]){0x00}, 1, 0},
};
#define TFT_SPI_FREQ_HZ (50 * 1000 * 1000)
static void my_disp_flush(lv_disp_drv_t *disp, const lv_area_t *area, lv_color_t *color_p)
{
ESP_PanelLcd *lcd = (ESP_PanelLcd *)disp->user_data;
const int offsetx1 = area->x1;
const int offsetx2 = area->x2;
const int offsety1 = area->y1;
const int offsety2 = area->y2;
lcd->drawBitmap(offsetx1, offsety1, offsetx2 - offsetx1 + 1, offsety2 - offsety1 + 1, (const uint8_t *)color_p);
}
IRAM_ATTR bool onRefreshFinishCallback(void *user_data)
{
lv_disp_drv_t *drv = (lv_disp_drv_t *)user_data;
lv_disp_flush_ready(drv);
return false;
}
void setRotation(uint8_t rot)
{
if (rot > 3)
return;
if (lcd == NULL || touch == NULL)
return;
switch (rot)
{
case 1: // 顺时针90度
lcd->swapXY(true);
lcd->mirrorX(true);
lcd->mirrorY(false);
touch->swapXY(true);
touch->mirrorX(true);
touch->mirrorY(false);
break;
case 2:
lcd->swapXY(false);
lcd->mirrorX(true);
lcd->mirrorY(true);
touch->swapXY(false);
touch->mirrorX(true);
touch->mirrorY(true);
break;
case 3:
lcd->swapXY(true);
lcd->mirrorX(false);
lcd->mirrorY(true);
touch->swapXY(true);
touch->mirrorX(false);
touch->mirrorY(true);
break;
default:
lcd->swapXY(false);
lcd->mirrorX(false);
lcd->mirrorY(false);
touch->swapXY(false);
touch->mirrorX(false);
touch->mirrorY(false);
break;
}
}
void screen_switch(bool on)
{
if (NULL == backlight)
return;
if (on)
backlight->on();
else
backlight->off();
}
// 输入值为0-100
void set_brightness(uint8_t bri)
{
if (NULL == backlight)
return;
backlight->setBrightness(bri);
}
static void touchpad_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)
{
ESP_PanelTouch *tp = (ESP_PanelTouch *)indev_drv->user_data;
ESP_PanelTouchPoint point;
int read_touch_result = tp->readPoints(&point, 1);
if (read_touch_result > 0)
{
data->point.x = point.x;
data->point.y = point.y;
data->state = LV_INDEV_STATE_PRESSED;
}
else
{
data->state = LV_INDEV_STATE_RELEASED;
}
}
static lv_indev_t *indev_init(ESP_PanelTouch *tp)
{
ESP_PANEL_CHECK_FALSE_RET(tp != nullptr, nullptr, "Invalid touch device");
ESP_PANEL_CHECK_FALSE_RET(tp->getHandle() != nullptr, nullptr, "Touch device is not initialized");
static lv_indev_drv_t indev_drv_tp;
lv_indev_drv_init(&indev_drv_tp);
indev_drv_tp.type = LV_INDEV_TYPE_POINTER;
indev_drv_tp.read_cb = touchpad_read;
indev_drv_tp.user_data = (void *)tp;
return lv_indev_drv_register(&indev_drv_tp);
}
void scr_lvgl_init()
{
ledc_timer_config_t ledc_timer = {
.speed_mode = LEDC_LOW_SPEED_MODE,
.duty_resolution = LEDC_TIMER_13_BIT,
.timer_num = LEDC_TIMER_0,
.freq_hz = 5000,
.clk_cfg = LEDC_AUTO_CLK};
ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
ledc_channel_config_t ledc_channel = {
.gpio_num = (TFT_BLK),
.speed_mode = LEDC_LOW_SPEED_MODE,
.channel = LEDC_CHANNEL_0,
.intr_type = LEDC_INTR_DISABLE,
.timer_sel = LEDC_TIMER_0,
.duty = 0,
.hpoint = 0};
ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
backlight = new ESP_PanelBacklight(ledc_timer, ledc_channel);
backlight->begin();
backlight->off();
ESP_PanelBus_I2C *touch_bus = new ESP_PanelBus_I2C(TOUCH_PIN_NUM_I2C_SCL, TOUCH_PIN_NUM_I2C_SDA, ESP_LCD_TOUCH_IO_I2C_CST816S_CONFIG());
touch_bus->configI2cFreqHz(400000);
touch_bus->begin();
touch = new ESP_PanelTouch_CST816S(touch_bus, SCREEN_RES_HOR, SCREEN_RES_VER, TOUCH_PIN_NUM_RST, TOUCH_PIN_NUM_INT);
touch->init();
touch->begin();
#if TOUCH_PIN_NUM_INT >= 0
touch->attachInterruptCallback(onTouchInterruptCallback, NULL);
#endif
ESP_PanelBus_QSPI *panel_bus = new ESP_PanelBus_QSPI(TFT_CS, TFT_SCK, TFT_SDA0, TFT_SDA1, TFT_SDA2, TFT_SDA3);
panel_bus->configQspiFreqHz(TFT_SPI_FREQ_HZ);
panel_bus->begin();
lcd = new ESP_PanelLcd_ST77916(panel_bus, 16, TFT_RST);
// 注意,初始化代码的设置必须在INIT之前
// lcd->configVendorCommands(lcd_init_cmd, sizeof(lcd_init_cmd) / sizeof(lcd_init_cmd[0]));
lcd->init();
lcd->reset();
lcd->begin();
lcd->invertColor(true);
// setRotation(0); //设置屏幕方向
lcd->displayOn();
screen_switch(true);
backlight->setBrightness(100); // 设置亮度
size_t lv_cache_rows = 72;
disp_draw_buf = (lv_color_t *)heap_caps_malloc(lv_cache_rows * SCREEN_RES_HOR * 2, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
lv_init();
lv_disp_draw_buf_init(&draw_buf, disp_draw_buf, NULL, SCREEN_RES_HOR * lv_cache_rows);
lv_disp_drv_init(&disp_drv);
disp_drv.hor_res = SCREEN_RES_HOR;
disp_drv.ver_res = SCREEN_RES_VER;
disp_drv.flush_cb = my_disp_flush;
disp_drv.draw_buf = &draw_buf;
disp_drv.user_data = (void *)lcd;
lv_disp_t *disp = lv_disp_drv_register(&disp_drv);
if (lcd->getBus()->getType() != ESP_PANEL_BUS_TYPE_RGB)
{
lcd->attachRefreshFinishCallback(onRefreshFinishCallback, (void *)disp->driver);
}
indev_touchpad = indev_init(touch);
}
#endif
@@ -1,28 +0,0 @@
; JC3636W518 — ESP32-S3R8, 16 MB QIO flash, 8 MB OPI PSRAM, 360x360 ST77916 QSPI + CST816S
; Settings mirror the vendor Arduino IDE setup (2-Program Example/Demo_Arduino/docs/Arduino set up.png)
[env:jc3636w518]
; pioarduino = Arduino core 3.0.5 (ESP32_Display_Panel 0.1.4 needs core 3.x)
platform = https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip
board = esp32-s3-devkitc-1
framework = arduino
board_build.arduino.memory_type = qio_opi
board_build.flash_mode = qio
board_upload.flash_size = 16MB
board_build.partitions = huge_app.csv
build_flags =
-DBOARD_HAS_PSRAM
-DARDUINO_USB_CDC_ON_BOOT=1
; LVGL (also when compiled as a library) picks up include/lv_conf.h of this project
-DLV_CONF_INCLUDE_SIMPLE
-Iinclude
; vendor-bundled libraries: ESP32_Display_Panel 0.1.4, ESP32_IO_Expander 0.0.2, lvgl 8.3.11
lib_extra_dirs = ../../2-Program Example/Demo_Arduino/library
upload_port = COM21
monitor_port = COM21
monitor_speed = 115200
monitor_filters = esp32_exception_decoder
@@ -1,184 +0,0 @@
#include <Arduino.h>
#include "scr_st77916.h"
#include "SD_MMC.h"
// https://randomnerdtutorials.com/esp32-client-server-wi-fi/
#include <WiFi.h>
#include <HTTPClient.h>
const char* ssid = "ESP32-Access-Point";
const char* password = "123456789";
String httpGETRequest(const char* serverName);
void my_print(const char* buf) {
Serial.println(buf); // Print logs to the serial monitor
}
void display_animation() {
lv_obj_t* img;
img = lv_gif_create(lv_scr_act());
Serial.print("Available Heap before: ");
Serial.println(esp_get_free_heap_size());
lv_mem_monitor_t mem_mon;
lv_mem_monitor(&mem_mon);
printf("Free memory: %d bytes\n", mem_mon.free_size);
lv_fs_file_t file;
if (lv_fs_open(&file, "A:wallpaper.gif", LV_FS_MODE_RD) == LV_FS_RES_OK) {
lv_fs_close(&file); // File exists, proceed with loading
lv_gif_set_src(img, "A:wallpaper.gif");
Serial.println("GIF Loaded Successfully");
} else {
Serial.println("Failed to Load GIF: File not found or inaccessible");
}
Serial.print("Available Heap after: ");
Serial.println(esp_get_free_heap_size());
lv_mem_monitor(&mem_mon);
printf("Free memory: %d bytes\n", mem_mon.free_size);
lv_obj_align(img, LV_ALIGN_CENTER, 0, 0);
}
lv_obj_t* label1;
void display_text() {
// Create a style for the text
static lv_style_t style_text;
lv_style_init(&style_text);
// Set the text color to white
lv_style_set_text_color(&style_text, lv_color_white());
// // Set the shadow color to black (this will simulate the outline)
// lv_style_set_shadow_color(&style_text, lv_color_black());
// // Set the shadow width to create the outline effect
// lv_style_set_shadow_width(&style_text, 2); // Adjust for desired outline thickness
// // Set the shadow offset to 0 so it surrounds the text evenly
// lv_style_set_shadow_ofs_x(&style_text, 0);
// lv_style_set_shadow_ofs_y(&style_text, 0);
label1 = lv_label_create(lv_scr_act());
// Apply the style to the label
lv_obj_add_style(label1, &style_text, LV_PART_MAIN);
// lv_style_set_text_font(&label1, &Montserrat); /*Set a larger font*/
lv_label_set_long_mode(label1, LV_LABEL_LONG_WRAP); /*Break the long lines*/
lv_label_set_text(label1, "0");
lv_obj_set_width(label1, 150); /*Set smaller width to make the lines wrap*/
lv_obj_set_style_text_align(label1, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(label1, LV_ALIGN_CENTER, 0, 0);
// lv_obj_set_style_transform_zoom(label1, 256 * 2, 0); // Zoom: 256 = 1x scale (default)
}
void connectToWifi() {
WiFi.begin(ssid, password);
Serial.println("Connecting");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.print("Connected to WiFi network with IP Address: ");
Serial.println(WiFi.localIP());
}
void setup() {
delay(200);
Serial.begin(115200);
scr_lvgl_init();
lv_init();
//lv_log_register_print_cb(my_print); // Register print function for LVGL logs
// Get the active screen (root object for the display)
lv_obj_t* screen = lv_scr_act();
// Set the background color of the entire screen to black
lv_obj_set_style_bg_color(screen, lv_color_black(), 0); // 0 is the part ID for the main part
lv_obj_set_style_bg_opa(screen, LV_OPA_COVER, 0); // Set opacity to fully opaque
// Configure the SDMMC host
SD_MMC.setPins(SD_MMC_CLK_PIN, SD_MMC_CMD_PIN, SD_MMC_D0_PIN, SD_MMC_D1_PIN, SD_MMC_D2_PIN, SD_MMC_D3_PIN);
if (!SD_MMC.begin()) {
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD_MMC.cardType();
if (cardType == CARD_NONE) {
Serial.println("No SD card attached");
return;
}
Serial.println("SD Card initialized successfully.");
connectToWifi();
display_animation();
display_text();
}
void loop() {
lv_timer_handler();
vTaskDelay(5);
lv_mem_monitor_t mem_mon;
lv_mem_monitor(&mem_mon);
printf("Free memory: %d bytes\n", mem_mon.free_size);
Serial.print("Available Heap: ");
Serial.println(esp_get_free_heap_size());
if (WiFi.status() == WL_CONNECTED) {
String speed = httpGETRequest("http://192.168.4.1/speed");
Serial.print("Speed: ");
Serial.println(speed);
lv_label_set_text(label1, speed.c_str());
} else {
Serial.println("WiFi Disconnected");
connectToWifi();
}
}
String httpGETRequest(const char* serverName) {
WiFiClient client;
HTTPClient http;
// Your Domain name with URL path or IP address with path
http.begin(client, serverName);
// Send HTTP POST request
int httpResponseCode = http.GET();
String payload = "--";
if (httpResponseCode > 0) {
Serial.print("HTTP Response code: ");
Serial.println(httpResponseCode);
payload = http.getString();
} else {
Serial.print("Error code: ");
Serial.println(httpResponseCode);
}
// Free resources
http.end();
return payload;
}
@@ -1,703 +0,0 @@
/*******************************************************************************
* Size: 100 px
* Bpp: 1
* Opts: --bpp 1 --size 100 --no-compress --font Montserrat-Regular.ttf --symbols 1234567890 --format lvgl -o montserrat_100.c
******************************************************************************/
#include "lvgl.h"
#ifndef MONTSERRAT_100
#define MONTSERRAT_100 1
#endif
#if MONTSERRAT_100
/*-----------------
* BITMAPS
*----------------*/
/*Store the image of the glyphs*/
static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
/* U+0030 "0" */
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x7, 0xff, 0xe0, 0x0, 0x0, 0x0, 0x0,
0x3f, 0xff, 0xfc, 0x0, 0x0, 0x0, 0x0, 0xff,
0xff, 0xff, 0x0, 0x0, 0x0, 0x3, 0xff, 0xff,
0xff, 0xc0, 0x0, 0x0, 0xf, 0xff, 0xff, 0xff,
0xe0, 0x0, 0x0, 0x1f, 0xff, 0xff, 0xff, 0xf8,
0x0, 0x0, 0x3f, 0xfc, 0x0, 0x3f, 0xfc, 0x0,
0x0, 0x7f, 0xe0, 0x0, 0x7, 0xfe, 0x0, 0x0,
0xff, 0xc0, 0x0, 0x3, 0xff, 0x0, 0x1, 0xff,
0x0, 0x0, 0x0, 0xff, 0x0, 0x1, 0xfe, 0x0,
0x0, 0x0, 0x7f, 0x80, 0x3, 0xfc, 0x0, 0x0,
0x0, 0x3f, 0xc0, 0x7, 0xf8, 0x0, 0x0, 0x0,
0x1f, 0xe0, 0x7, 0xf8, 0x0, 0x0, 0x0, 0x1f,
0xe0, 0xf, 0xf0, 0x0, 0x0, 0x0, 0xf, 0xf0,
0xf, 0xe0, 0x0, 0x0, 0x0, 0x7, 0xf0, 0x1f,
0xe0, 0x0, 0x0, 0x0, 0x7, 0xf8, 0x1f, 0xc0,
0x0, 0x0, 0x0, 0x3, 0xf8, 0x1f, 0xc0, 0x0,
0x0, 0x0, 0x3, 0xf8, 0x3f, 0x80, 0x0, 0x0,
0x0, 0x1, 0xfc, 0x3f, 0x80, 0x0, 0x0, 0x0,
0x1, 0xfc, 0x3f, 0x80, 0x0, 0x0, 0x0, 0x1,
0xfc, 0x7f, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfe,
0x7f, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfe, 0x7f,
0x0, 0x0, 0x0, 0x0, 0x0, 0xfe, 0x7f, 0x0,
0x0, 0x0, 0x0, 0x0, 0xfe, 0x7f, 0x0, 0x0,
0x0, 0x0, 0x0, 0xfe, 0xfe, 0x0, 0x0, 0x0,
0x0, 0x0, 0x7f, 0xfe, 0x0, 0x0, 0x0, 0x0,
0x0, 0x7f, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0,
0x7f, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f,
0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfe,
0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfe, 0x0,
0x0, 0x0, 0x0, 0x0, 0x7f, 0xfe, 0x0, 0x0,
0x0, 0x0, 0x0, 0x7f, 0xfe, 0x0, 0x0, 0x0,
0x0, 0x0, 0x7f, 0xfe, 0x0, 0x0, 0x0, 0x0,
0x0, 0x7f, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0,
0x7f, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f,
0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfe,
0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfe, 0x0,
0x0, 0x0, 0x0, 0x0, 0x7f, 0xfe, 0x0, 0x0,
0x0, 0x0, 0x0, 0xff, 0x7f, 0x0, 0x0, 0x0,
0x0, 0x0, 0xfe, 0x7f, 0x0, 0x0, 0x0, 0x0,
0x0, 0xfe, 0x7f, 0x0, 0x0, 0x0, 0x0, 0x0,
0xfe, 0x7f, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfe,
0x7f, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfe, 0x3f,
0x80, 0x0, 0x0, 0x0, 0x1, 0xfc, 0x3f, 0x80,
0x0, 0x0, 0x0, 0x1, 0xfc, 0x3f, 0x80, 0x0,
0x0, 0x0, 0x1, 0xfc, 0x1f, 0xc0, 0x0, 0x0,
0x0, 0x3, 0xf8, 0x1f, 0xc0, 0x0, 0x0, 0x0,
0x3, 0xf8, 0x1f, 0xe0, 0x0, 0x0, 0x0, 0x7,
0xf8, 0xf, 0xe0, 0x0, 0x0, 0x0, 0x7, 0xf0,
0xf, 0xf0, 0x0, 0x0, 0x0, 0xf, 0xf0, 0x7,
0xf8, 0x0, 0x0, 0x0, 0x1f, 0xe0, 0x7, 0xf8,
0x0, 0x0, 0x0, 0x1f, 0xe0, 0x3, 0xfc, 0x0,
0x0, 0x0, 0x3f, 0xc0, 0x1, 0xfe, 0x0, 0x0,
0x0, 0x7f, 0x80, 0x1, 0xff, 0x0, 0x0, 0x0,
0xff, 0x0, 0x0, 0xff, 0xc0, 0x0, 0x3, 0xff,
0x0, 0x0, 0x7f, 0xe0, 0x0, 0x7, 0xfe, 0x0,
0x0, 0x3f, 0xfc, 0x0, 0x3f, 0xfc, 0x0, 0x0,
0x1f, 0xff, 0xff, 0xff, 0xf8, 0x0, 0x0, 0xf,
0xff, 0xff, 0xff, 0xe0, 0x0, 0x0, 0x3, 0xff,
0xff, 0xff, 0xc0, 0x0, 0x0, 0x0, 0xff, 0xff,
0xff, 0x0, 0x0, 0x0, 0x0, 0x3f, 0xff, 0xfc,
0x0, 0x0, 0x0, 0x0, 0xf, 0xff, 0xe0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x18, 0x0, 0x0, 0x0,
/* U+0031 "1" */
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f,
0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0,
0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0,
0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f,
0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0,
0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0,
0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f,
0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0,
0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0,
0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f,
0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0,
0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0,
0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f,
0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0,
0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0,
0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f,
0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0,
0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0,
0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f,
0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0,
0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0,
0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f,
0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0,
0x7f, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x7f, 0x0,
0x0, 0x7f,
/* U+0032 "2" */
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x3f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x3f, 0xff,
0xff, 0xc0, 0x0, 0x0, 0x3f, 0xff, 0xff, 0xff,
0x0, 0x0, 0x1f, 0xff, 0xff, 0xff, 0xf0, 0x0,
0xf, 0xff, 0xff, 0xff, 0xff, 0x80, 0x3, 0xff,
0xff, 0xff, 0xff, 0xf8, 0x1, 0xff, 0xf8, 0x0,
0x7, 0xff, 0x80, 0x7f, 0xf8, 0x0, 0x0, 0x3f,
0xf8, 0x1f, 0xfc, 0x0, 0x0, 0x1, 0xff, 0x1,
0xfe, 0x0, 0x0, 0x0, 0x1f, 0xf0, 0x1f, 0x80,
0x0, 0x0, 0x1, 0xfe, 0x1, 0xe0, 0x0, 0x0,
0x0, 0x1f, 0xe0, 0x18, 0x0, 0x0, 0x0, 0x1,
0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f, 0x80,
0x0, 0x0, 0x0, 0x0, 0x7, 0xf8, 0x0, 0x0,
0x0, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x0, 0x0,
0x0, 0xf, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x1,
0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f, 0x80,
0x0, 0x0, 0x0, 0x0, 0x7, 0xf0, 0x0, 0x0,
0x0, 0x0, 0x0, 0xfe, 0x0, 0x0, 0x0, 0x0,
0x0, 0x1f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x3,
0xf8, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfe, 0x0,
0x0, 0x0, 0x0, 0x0, 0x1f, 0xc0, 0x0, 0x0,
0x0, 0x0, 0x3, 0xf8, 0x0, 0x0, 0x0, 0x0,
0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f,
0xc0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xf8, 0x0,
0x0, 0x0, 0x0, 0x1, 0xfe, 0x0, 0x0, 0x0,
0x0, 0x0, 0x3f, 0xc0, 0x0, 0x0, 0x0, 0x0,
0xf, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xfc,
0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x80, 0x0,
0x0, 0x0, 0x0, 0x3f, 0xe0, 0x0, 0x0, 0x0,
0x0, 0xf, 0xf8, 0x0, 0x0, 0x0, 0x0, 0x3,
0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x80,
0x0, 0x0, 0x0, 0x0, 0x3f, 0xe0, 0x0, 0x0,
0x0, 0x0, 0xf, 0xf8, 0x0, 0x0, 0x0, 0x0,
0x3, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff,
0x80, 0x0, 0x0, 0x0, 0x0, 0x3f, 0xe0, 0x0,
0x0, 0x0, 0x0, 0xf, 0xf8, 0x0, 0x0, 0x0,
0x0, 0x3, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0,
0xff, 0x80, 0x0, 0x0, 0x0, 0x0, 0x3f, 0xe0,
0x0, 0x0, 0x0, 0x0, 0xf, 0xf8, 0x0, 0x0,
0x0, 0x0, 0x3, 0xfe, 0x0, 0x0, 0x0, 0x0,
0x0, 0xff, 0x80, 0x0, 0x0, 0x0, 0x0, 0x3f,
0xe0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xf8, 0x0,
0x0, 0x0, 0x0, 0x3, 0xfe, 0x0, 0x0, 0x0,
0x0, 0x0, 0xff, 0x80, 0x0, 0x0, 0x0, 0x0,
0x3f, 0xe0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xf8,
0x0, 0x0, 0x0, 0x0, 0x3, 0xfe, 0x0, 0x0,
0x0, 0x0, 0x0, 0xff, 0x80, 0x0, 0x0, 0x0,
0x0, 0x3f, 0xe0, 0x0, 0x0, 0x0, 0x0, 0xf,
0xf8, 0x0, 0x0, 0x0, 0x0, 0x3, 0xfe, 0x0,
0x0, 0x0, 0x0, 0x0, 0xff, 0x80, 0x0, 0x0,
0x0, 0x0, 0x3f, 0xe0, 0x0, 0x0, 0x0, 0x0,
0xf, 0xf8, 0x0, 0x0, 0x0, 0x0, 0x3, 0xff,
0xff, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff,
0xff, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe,
0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xff,
0xff, 0xff, 0xff, 0xff, 0xf8,
/* U+0033 "3" */
0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3, 0xff,
0xff, 0xff, 0xff, 0xff, 0xe0, 0x7f, 0xff, 0xff,
0xff, 0xff, 0xfc, 0xf, 0xff, 0xff, 0xff, 0xff,
0xff, 0x81, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf0,
0x3f, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0, 0x0,
0x0, 0x0, 0x0, 0x7f, 0x80, 0x0, 0x0, 0x0,
0x0, 0x1f, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x3,
0xf8, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfe, 0x0,
0x0, 0x0, 0x0, 0x0, 0x3f, 0xc0, 0x0, 0x0,
0x0, 0x0, 0xf, 0xf0, 0x0, 0x0, 0x0, 0x0,
0x3, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f,
0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0xe0, 0x0,
0x0, 0x0, 0x0, 0x7, 0xf8, 0x0, 0x0, 0x0,
0x0, 0x1, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0,
0x7f, 0x80, 0x0, 0x0, 0x0, 0x0, 0xf, 0xe0,
0x0, 0x0, 0x0, 0x0, 0x3, 0xfc, 0x0, 0x0,
0x0, 0x0, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0,
0x0, 0x3f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0xf,
0xf0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xfe, 0x0,
0x0, 0x0, 0x0, 0x0, 0x7f, 0x80, 0x0, 0x0,
0x0, 0x0, 0x1f, 0xe0, 0x0, 0x0, 0x0, 0x0,
0x7, 0xf8, 0x0, 0x0, 0x0, 0x0, 0x1, 0xff,
0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xc0, 0x0,
0x0, 0x0, 0x0, 0xf, 0xf0, 0x0, 0x0, 0x0,
0x0, 0x3, 0xff, 0xe0, 0x0, 0x0, 0x0, 0x0,
0x7f, 0xff, 0xc0, 0x0, 0x0, 0x0, 0xf, 0xff,
0xff, 0x0, 0x0, 0x0, 0x1, 0xff, 0xff, 0xf8,
0x0, 0x0, 0x0, 0x3f, 0xff, 0xff, 0x80, 0x0,
0x0, 0x7, 0xff, 0xff, 0xfc, 0x0, 0x0, 0x0,
0x0, 0x1f, 0xff, 0xc0, 0x0, 0x0, 0x0, 0x0,
0x3f, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x1, 0xff,
0x80, 0x0, 0x0, 0x0, 0x0, 0xf, 0xf8, 0x0,
0x0, 0x0, 0x0, 0x0, 0xff, 0x80, 0x0, 0x0,
0x0, 0x0, 0xf, 0xf0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f,
0xe0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xfc, 0x0,
0x0, 0x0, 0x0, 0x0, 0x3f, 0xc0, 0x0, 0x0,
0x0, 0x0, 0x3, 0xf8, 0x0, 0x0, 0x0, 0x0,
0x0, 0x7f, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf,
0xe0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xfc, 0x0,
0x0, 0x0, 0x0, 0x0, 0x3f, 0x80, 0x0, 0x0,
0x0, 0x0, 0x7, 0xf0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f,
0xc0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xf8, 0x0,
0x0, 0x0, 0x0, 0x0, 0xfe, 0x10, 0x0, 0x0,
0x0, 0x0, 0x1f, 0xc3, 0x0, 0x0, 0x0, 0x0,
0x7, 0xf8, 0xf0, 0x0, 0x0, 0x0, 0x1, 0xfe,
0x1f, 0x80, 0x0, 0x0, 0x0, 0x7f, 0xc7, 0xf8,
0x0, 0x0, 0x0, 0x1f, 0xf1, 0xff, 0xc0, 0x0,
0x0, 0x7, 0xfe, 0x1f, 0xff, 0x0, 0x0, 0x3,
0xff, 0x81, 0xff, 0xfc, 0x0, 0x1, 0xff, 0xe0,
0x1f, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x0, 0xff,
0xff, 0xff, 0xff, 0xfe, 0x0, 0x7, 0xff, 0xff,
0xff, 0xff, 0x0, 0x0, 0x3f, 0xff, 0xff, 0xff,
0x80, 0x0, 0x0, 0xff, 0xff, 0xff, 0xc0, 0x0,
0x0, 0x1, 0xff, 0xff, 0xc0, 0x0, 0x0, 0x0,
0x0, 0x38, 0x0, 0x0, 0x0,
/* U+0034 "4" */
0x0, 0x0, 0x0, 0x0, 0x7, 0xf8, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x7f, 0x80, 0x0, 0x0,
0x0, 0x0, 0x0, 0x7, 0xf8, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x3f, 0xc0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x3, 0xfc, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x3f, 0xc0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x3, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x3f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x1, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x1f, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1,
0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f,
0xe0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xff,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xf0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xf0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0xf, 0xf8, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x7f, 0x80, 0x0, 0x0,
0x0, 0x0, 0x0, 0x7, 0xf8, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x7f, 0x80, 0x0, 0x0, 0x0,
0x0, 0x0, 0x7, 0xf8, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x7f, 0xc0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x3, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x3f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x3, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x3f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3,
0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f,
0xe0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xfe,
0x0, 0x0, 0x7f, 0x0, 0x0, 0x0, 0x1f, 0xe0,
0x0, 0x3, 0xf8, 0x0, 0x0, 0x1, 0xfe, 0x0,
0x0, 0x1f, 0xc0, 0x0, 0x0, 0x1f, 0xf0, 0x0,
0x0, 0xfe, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0,
0x7, 0xf0, 0x0, 0x0, 0xf, 0xf0, 0x0, 0x0,
0x3f, 0x80, 0x0, 0x0, 0xff, 0x0, 0x0, 0x1,
0xfc, 0x0, 0x0, 0xf, 0xf0, 0x0, 0x0, 0xf,
0xe0, 0x0, 0x0, 0xff, 0x80, 0x0, 0x0, 0x7f,
0x0, 0x0, 0x7, 0xf8, 0x0, 0x0, 0x3, 0xf8,
0x0, 0x0, 0x7f, 0x80, 0x0, 0x0, 0x1f, 0xc0,
0x0, 0x7, 0xf8, 0x0, 0x0, 0x0, 0xfe, 0x0,
0x0, 0x7f, 0x80, 0x0, 0x0, 0x7, 0xf0, 0x0,
0x7, 0xfc, 0x0, 0x0, 0x0, 0x3f, 0x80, 0x0,
0x3f, 0xc0, 0x0, 0x0, 0x1, 0xfc, 0x0, 0x3,
0xfc, 0x0, 0x0, 0x0, 0xf, 0xe0, 0x0, 0x3f,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xfe, 0x0, 0x0, 0x0,
0x0, 0x1, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xf, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x7f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x3, 0xf8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x1f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7,
0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f,
0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xfc,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xe0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xf8, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0xc0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0xfe, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x7, 0xf0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x3f, 0x80, 0x0, 0x0,
0x0, 0x0, 0x0, 0x1, 0xfc, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0xf, 0xe0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x7f, 0x0, 0x0,
/* U+0035 "5" */
0x0, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0, 0x3f,
0xff, 0xff, 0xff, 0xff, 0x0, 0xf, 0xff, 0xff,
0xff, 0xff, 0xc0, 0x3, 0xff, 0xff, 0xff, 0xff,
0xf0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xfc, 0x0,
0x3f, 0xff, 0xff, 0xff, 0xff, 0x0, 0xf, 0xe0,
0x0, 0x0, 0x0, 0x0, 0x3, 0xf0, 0x0, 0x0,
0x0, 0x0, 0x1, 0xfc, 0x0, 0x0, 0x0, 0x0,
0x0, 0x7f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f,
0xc0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xf0, 0x0,
0x0, 0x0, 0x0, 0x1, 0xfc, 0x0, 0x0, 0x0,
0x0, 0x0, 0x7f, 0x0, 0x0, 0x0, 0x0, 0x0,
0x1f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xf0,
0x0, 0x0, 0x0, 0x0, 0x1, 0xf8, 0x0, 0x0,
0x0, 0x0, 0x0, 0xfe, 0x0, 0x0, 0x0, 0x0,
0x0, 0x3f, 0x80, 0x0, 0x0, 0x0, 0x0, 0xf,
0xe0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xf8, 0x0,
0x0, 0x0, 0x0, 0x0, 0xfe, 0x0, 0x0, 0x0,
0x0, 0x0, 0x3f, 0x80, 0x0, 0x0, 0x0, 0x0,
0xf, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xf8,
0x0, 0x0, 0x0, 0x0, 0x0, 0xfe, 0x0, 0x0,
0x0, 0x0, 0x0, 0x3f, 0x0, 0x0, 0x0, 0x0,
0x0, 0x1f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x7,
0xf0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xff, 0xff,
0xfe, 0x0, 0x0, 0x0, 0x7f, 0xff, 0xff, 0xfe,
0x0, 0x0, 0x1f, 0xff, 0xff, 0xff, 0xf0, 0x0,
0x7, 0xff, 0xff, 0xff, 0xff, 0x0, 0x1, 0xff,
0xff, 0xff, 0xff, 0xf0, 0x0, 0x7f, 0xff, 0xff,
0xff, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x3f, 0xff,
0xc0, 0x0, 0x0, 0x0, 0x0, 0xff, 0xf8, 0x0,
0x0, 0x0, 0x0, 0x7, 0xff, 0x0, 0x0, 0x0,
0x0, 0x0, 0x7f, 0xe0, 0x0, 0x0, 0x0, 0x0,
0x7, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff,
0x0, 0x0, 0x0, 0x0, 0x0, 0x3f, 0xe0, 0x0,
0x0, 0x0, 0x0, 0x7, 0xf8, 0x0, 0x0, 0x0,
0x0, 0x0, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0,
0x3f, 0x80, 0x0, 0x0, 0x0, 0x0, 0xf, 0xf0,
0x0, 0x0, 0x0, 0x0, 0x1, 0xfc, 0x0, 0x0,
0x0, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x0, 0x0,
0x0, 0x1f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x7,
0xf0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xfc, 0x0,
0x0, 0x0, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x0,
0x0, 0x0, 0x1f, 0xc0, 0x0, 0x0, 0x0, 0x0,
0x7, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xf8,
0x0, 0x0, 0x0, 0x0, 0x0, 0xfe, 0x10, 0x0,
0x0, 0x0, 0x0, 0x7f, 0x86, 0x0, 0x0, 0x0,
0x0, 0x1f, 0xc3, 0xc0, 0x0, 0x0, 0x0, 0xf,
0xf1, 0xf8, 0x0, 0x0, 0x0, 0x7, 0xfc, 0x7f,
0x80, 0x0, 0x0, 0x3, 0xfe, 0x3f, 0xf8, 0x0,
0x0, 0x1, 0xff, 0xf, 0xff, 0x80, 0x0, 0x1,
0xff, 0xc1, 0xff, 0xfe, 0x0, 0x1, 0xff, 0xe0,
0x1f, 0xff, 0xff, 0xff, 0xff, 0xf0, 0x3, 0xff,
0xff, 0xff, 0xff, 0xf0, 0x0, 0x3f, 0xff, 0xff,
0xff, 0xf8, 0x0, 0x1, 0xff, 0xff, 0xff, 0xf8,
0x0, 0x0, 0x1f, 0xff, 0xff, 0xf8, 0x0, 0x0,
0x0, 0x7f, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x0,
0x1c, 0x0, 0x0, 0x0,
/* U+0036 "6" */
0x0, 0x0, 0x0, 0x0, 0x80, 0x0, 0x0, 0x0,
0x0, 0xf, 0xff, 0xfe, 0x0, 0x0, 0x0, 0x3,
0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x7f, 0xff,
0xff, 0xfe, 0x0, 0x0, 0xf, 0xff, 0xff, 0xff,
0xf8, 0x0, 0x1, 0xff, 0xff, 0xff, 0xff, 0x80,
0x0, 0x3f, 0xff, 0xff, 0xff, 0xfc, 0x0, 0x3,
0xff, 0xe0, 0x0, 0xf, 0xc0, 0x0, 0x3f, 0xfc,
0x0, 0x0, 0xe, 0x0, 0x3, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x3f, 0xf0, 0x0, 0x0, 0x0,
0x0, 0x3, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0,
0x1f, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xfe,
0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0xe0, 0x0,
0x0, 0x0, 0x0, 0x0, 0xfe, 0x0, 0x0, 0x0,
0x0, 0x0, 0xf, 0xf0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x7f, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7,
0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f, 0x80,
0x0, 0x0, 0x0, 0x0, 0x3, 0xfc, 0x0, 0x0,
0x0, 0x0, 0x0, 0x1f, 0xc0, 0x0, 0x0, 0x0,
0x0, 0x0, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0,
0x7, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f,
0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xf8, 0x0,
0x0, 0x0, 0x0, 0x0, 0x1f, 0xc0, 0x0, 0x0,
0x0, 0x0, 0x0, 0xfe, 0x0, 0x0, 0x0, 0x0,
0x0, 0x7, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x7f, 0x0, 0x0, 0xff, 0xf0, 0x0, 0x3, 0xf8,
0x0, 0x3f, 0xff, 0xf8, 0x0, 0x1f, 0xc0, 0xf,
0xff, 0xff, 0xf0, 0x0, 0xfe, 0x1, 0xff, 0xff,
0xff, 0xe0, 0x7, 0xf0, 0x1f, 0xff, 0xff, 0xff,
0xc0, 0x3f, 0x83, 0xff, 0xff, 0xff, 0xff, 0x1,
0xfc, 0x3f, 0xfc, 0x0, 0xff, 0xfc, 0xf, 0xe3,
0xff, 0x0, 0x0, 0x7f, 0xf0, 0x7f, 0x3f, 0xc0,
0x0, 0x0, 0xff, 0xc3, 0xf9, 0xfc, 0x0, 0x0,
0x3, 0xff, 0x1f, 0xdf, 0xc0, 0x0, 0x0, 0x7,
0xf8, 0xfe, 0xfc, 0x0, 0x0, 0x0, 0x1f, 0xe7,
0xff, 0xc0, 0x0, 0x0, 0x0, 0xff, 0x3f, 0xfc,
0x0, 0x0, 0x0, 0x3, 0xfd, 0xff, 0xc0, 0x0,
0x0, 0x0, 0xf, 0xef, 0xfe, 0x0, 0x0, 0x0,
0x0, 0x7f, 0x3f, 0xe0, 0x0, 0x0, 0x0, 0x3,
0xfd, 0xff, 0x0, 0x0, 0x0, 0x0, 0xf, 0xef,
0xf8, 0x0, 0x0, 0x0, 0x0, 0x7f, 0x7f, 0xc0,
0x0, 0x0, 0x0, 0x3, 0xfb, 0xfe, 0x0, 0x0,
0x0, 0x0, 0x1f, 0xcf, 0xf0, 0x0, 0x0, 0x0,
0x0, 0xfe, 0x7f, 0x80, 0x0, 0x0, 0x0, 0x7,
0xf3, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x3f, 0x8f,
0xe0, 0x0, 0x0, 0x0, 0x1, 0xfc, 0x7f, 0x0,
0x0, 0x0, 0x0, 0x1f, 0xe3, 0xfc, 0x0, 0x0,
0x0, 0x0, 0xfe, 0xf, 0xe0, 0x0, 0x0, 0x0,
0x7, 0xf0, 0x7f, 0x80, 0x0, 0x0, 0x0, 0x7f,
0x81, 0xfc, 0x0, 0x0, 0x0, 0x3, 0xf8, 0xf,
0xf0, 0x0, 0x0, 0x0, 0x3f, 0xc0, 0x3f, 0xc0,
0x0, 0x0, 0x3, 0xfc, 0x0, 0xff, 0x0, 0x0,
0x0, 0x3f, 0xe0, 0x3, 0xfe, 0x0, 0x0, 0x3,
0xfe, 0x0, 0x1f, 0xf8, 0x0, 0x0, 0x7f, 0xe0,
0x0, 0x7f, 0xf8, 0x0, 0x1f, 0xfe, 0x0, 0x0,
0xff, 0xff, 0xff, 0xff, 0xe0, 0x0, 0x3, 0xff,
0xff, 0xff, 0xfe, 0x0, 0x0, 0xf, 0xff, 0xff,
0xff, 0xe0, 0x0, 0x0, 0x1f, 0xff, 0xff, 0xfc,
0x0, 0x0, 0x0, 0x3f, 0xff, 0xff, 0x80, 0x0,
0x0, 0x0, 0x3f, 0xff, 0xe0, 0x0, 0x0, 0x0,
0x0, 0x1, 0x80, 0x0, 0x0,
/* U+0037 "7" */
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe,
0x0, 0x0, 0x0, 0x0, 0x1f, 0xef, 0xe0, 0x0,
0x0, 0x0, 0x1, 0xfc, 0xfe, 0x0, 0x0, 0x0,
0x0, 0x3f, 0xcf, 0xe0, 0x0, 0x0, 0x0, 0x3,
0xf8, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x7f, 0x8f,
0xe0, 0x0, 0x0, 0x0, 0x7, 0xf0, 0xfe, 0x0,
0x0, 0x0, 0x0, 0xff, 0xf, 0xe0, 0x0, 0x0,
0x0, 0xf, 0xf0, 0xfe, 0x0, 0x0, 0x0, 0x1,
0xfe, 0xf, 0xe0, 0x0, 0x0, 0x0, 0x1f, 0xe0,
0xfe, 0x0, 0x0, 0x0, 0x3, 0xfc, 0xf, 0xe0,
0x0, 0x0, 0x0, 0x3f, 0xc0, 0xfe, 0x0, 0x0,
0x0, 0x3, 0xf8, 0x0, 0x0, 0x0, 0x0, 0x0,
0x7f, 0x80, 0x0, 0x0, 0x0, 0x0, 0x7, 0xf0,
0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0xf, 0xf0, 0x0, 0x0, 0x0,
0x0, 0x1, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0,
0x1f, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xfc,
0x0, 0x0, 0x0, 0x0, 0x0, 0x3f, 0xc0, 0x0,
0x0, 0x0, 0x0, 0x7, 0xf8, 0x0, 0x0, 0x0,
0x0, 0x0, 0x7f, 0x80, 0x0, 0x0, 0x0, 0x0,
0x7, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff,
0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xe0, 0x0,
0x0, 0x0, 0x0, 0x1, 0xfe, 0x0, 0x0, 0x0,
0x0, 0x0, 0x1f, 0xe0, 0x0, 0x0, 0x0, 0x0,
0x3, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f,
0xc0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xf8, 0x0,
0x0, 0x0, 0x0, 0x0, 0x7f, 0x80, 0x0, 0x0,
0x0, 0x0, 0xf, 0xf0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0xf,
0xe0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xfe, 0x0,
0x0, 0x0, 0x0, 0x0, 0x1f, 0xc0, 0x0, 0x0,
0x0, 0x0, 0x3, 0xfc, 0x0, 0x0, 0x0, 0x0,
0x0, 0x3f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x7,
0xf8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f, 0x80,
0x0, 0x0, 0x0, 0x0, 0xf, 0xf0, 0x0, 0x0,
0x0, 0x0, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0,
0x0, 0x1f, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x1,
0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0xc0,
0x0, 0x0, 0x0, 0x0, 0x3, 0xfc, 0x0, 0x0,
0x0, 0x0, 0x0, 0x3f, 0xc0, 0x0, 0x0, 0x0,
0x0, 0x7, 0xf8, 0x0, 0x0, 0x0, 0x0, 0x0,
0x7f, 0x80, 0x0, 0x0, 0x0, 0x0, 0xf, 0xf0,
0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0,
0x0, 0x0, 0x0, 0x1f, 0xe0, 0x0, 0x0, 0x0,
0x0, 0x1, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0,
0x1f, 0xc0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xfc,
0x0, 0x0, 0x0, 0x0, 0x0, 0x3f, 0x80, 0x0,
0x0, 0x0, 0x0, 0x7, 0xf8, 0x0, 0x0, 0x0,
0x0, 0x0, 0x7f, 0x80, 0x0, 0x0, 0x0, 0x0,
0xf, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff,
0x0, 0x0, 0x0, 0x0, 0x0, 0x1f, 0xe0, 0x0,
0x0, 0x0, 0x0, 0x1, 0xfe, 0x0, 0x0, 0x0,
0x0, 0x0, 0x3f, 0xc0, 0x0, 0x0, 0x0,
/* U+0038 "8" */
0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0, 0x0,
0x0, 0xff, 0xff, 0xe0, 0x0, 0x0, 0x0, 0x1f,
0xff, 0xff, 0xf0, 0x0, 0x0, 0x1, 0xff, 0xff,
0xff, 0xf0, 0x0, 0x0, 0x1f, 0xff, 0xff, 0xff,
0xf0, 0x0, 0x1, 0xff, 0xff, 0xff, 0xff, 0xe0,
0x0, 0xf, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x0,
0x7f, 0xfc, 0x0, 0x7, 0xff, 0xc0, 0x3, 0xff,
0x80, 0x0, 0x3, 0xff, 0x80, 0xf, 0xf8, 0x0,
0x0, 0x3, 0xfe, 0x0, 0x7f, 0xc0, 0x0, 0x0,
0x7, 0xfc, 0x3, 0xfe, 0x0, 0x0, 0x0, 0xf,
0xf0, 0xf, 0xf0, 0x0, 0x0, 0x0, 0x1f, 0xe0,
0x3f, 0x80, 0x0, 0x0, 0x0, 0x3f, 0x81, 0xfe,
0x0, 0x0, 0x0, 0x0, 0xff, 0x7, 0xf0, 0x0,
0x0, 0x0, 0x3, 0xfc, 0x1f, 0xc0, 0x0, 0x0,
0x0, 0x7, 0xf0, 0x7f, 0x0, 0x0, 0x0, 0x0,
0x1f, 0xc1, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x7f,
0x7, 0xf0, 0x0, 0x0, 0x0, 0x1, 0xfc, 0x1f,
0xc0, 0x0, 0x0, 0x0, 0x7, 0xf0, 0x7f, 0x0,
0x0, 0x0, 0x0, 0x1f, 0xc1, 0xfe, 0x0, 0x0,
0x0, 0x0, 0xff, 0x3, 0xf8, 0x0, 0x0, 0x0,
0x3, 0xf8, 0xf, 0xe0, 0x0, 0x0, 0x0, 0xf,
0xe0, 0x3f, 0xc0, 0x0, 0x0, 0x0, 0x7f, 0x80,
0x7f, 0x80, 0x0, 0x0, 0x3, 0xfc, 0x1, 0xff,
0x0, 0x0, 0x0, 0x1f, 0xe0, 0x3, 0xfe, 0x0,
0x0, 0x0, 0xff, 0x80, 0x7, 0xfe, 0x0, 0x0,
0xf, 0xfc, 0x0, 0xf, 0xff, 0x0, 0x1, 0xff,
0xe0, 0x0, 0x1f, 0xff, 0xff, 0xff, 0xff, 0x0,
0x0, 0x1f, 0xff, 0xff, 0xff, 0xf0, 0x0, 0x0,
0x1f, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0xff,
0xff, 0xff, 0xfc, 0x0, 0x0, 0xf, 0xff, 0xff,
0xff, 0xfc, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff,
0xfc, 0x0, 0xf, 0xff, 0xe0, 0x1, 0xff, 0xf8,
0x0, 0x7f, 0xf8, 0x0, 0x0, 0x7f, 0xf8, 0x3,
0xff, 0x0, 0x0, 0x0, 0x7f, 0xf0, 0x1f, 0xf8,
0x0, 0x0, 0x0, 0x7f, 0xc0, 0x7f, 0x80, 0x0,
0x0, 0x0, 0x7f, 0x83, 0xfc, 0x0, 0x0, 0x0,
0x0, 0xff, 0xf, 0xe0, 0x0, 0x0, 0x0, 0x3,
0xfc, 0x7f, 0x80, 0x0, 0x0, 0x0, 0x7, 0xf9,
0xfc, 0x0, 0x0, 0x0, 0x0, 0xf, 0xe7, 0xf0,
0x0, 0x0, 0x0, 0x0, 0x3f, 0xbf, 0x80, 0x0,
0x0, 0x0, 0x0, 0xff, 0xfe, 0x0, 0x0, 0x0,
0x0, 0x1, 0xff, 0xf8, 0x0, 0x0, 0x0, 0x0,
0x7, 0xff, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x1f,
0xff, 0x80, 0x0, 0x0, 0x0, 0x0, 0x7f, 0xfe,
0x0, 0x0, 0x0, 0x0, 0x1, 0xff, 0xf8, 0x0,
0x0, 0x0, 0x0, 0x7, 0xff, 0xe0, 0x0, 0x0,
0x0, 0x0, 0x1f, 0xff, 0xc0, 0x0, 0x0, 0x0,
0x0, 0xff, 0x7f, 0x0, 0x0, 0x0, 0x0, 0x3,
0xf9, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x1f, 0xe7,
0xf8, 0x0, 0x0, 0x0, 0x0, 0x7f, 0x8f, 0xf0,
0x0, 0x0, 0x0, 0x3, 0xfc, 0x3f, 0xe0, 0x0,
0x0, 0x0, 0x1f, 0xf0, 0x7f, 0xc0, 0x0, 0x0,
0x0, 0xff, 0x80, 0xff, 0xc0, 0x0, 0x0, 0xf,
0xfc, 0x3, 0xff, 0xc0, 0x0, 0x0, 0xff, 0xf0,
0x7, 0xff, 0xc0, 0x0, 0x1f, 0xff, 0x80, 0xf,
0xff, 0xff, 0xff, 0xff, 0xfc, 0x0, 0xf, 0xff,
0xff, 0xff, 0xff, 0xc0, 0x0, 0x1f, 0xff, 0xff,
0xff, 0xfe, 0x0, 0x0, 0x1f, 0xff, 0xff, 0xff,
0xe0, 0x0, 0x0, 0x1f, 0xff, 0xff, 0xfc, 0x0,
0x0, 0x0, 0x7, 0xff, 0xff, 0x80, 0x0, 0x0,
0x0, 0x0, 0x1c, 0x0, 0x0, 0x0,
/* U+0039 "9" */
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0xf, 0xff, 0xf0, 0x0, 0x0, 0x0, 0x7, 0xff,
0xff, 0xe0, 0x0, 0x0, 0x1, 0xff, 0xff, 0xff,
0x80, 0x0, 0x0, 0x7f, 0xff, 0xff, 0xfe, 0x0,
0x0, 0xf, 0xff, 0xff, 0xff, 0xf0, 0x0, 0x1,
0xff, 0xff, 0xff, 0xff, 0xc0, 0x0, 0x7f, 0xf8,
0x0, 0x1f, 0xfe, 0x0, 0x7, 0xfe, 0x0, 0x0,
0x3f, 0xf0, 0x0, 0xff, 0x80, 0x0, 0x0, 0xff,
0x80, 0x1f, 0xf0, 0x0, 0x0, 0x7, 0xf8, 0x1,
0xfe, 0x0, 0x0, 0x0, 0x3f, 0xc0, 0x3f, 0xc0,
0x0, 0x0, 0x1, 0xfe, 0x3, 0xf8, 0x0, 0x0,
0x0, 0xf, 0xe0, 0x7f, 0x80, 0x0, 0x0, 0x0,
0xff, 0x7, 0xf0, 0x0, 0x0, 0x0, 0x7, 0xf0,
0x7f, 0x0, 0x0, 0x0, 0x0, 0x7f, 0x8f, 0xf0,
0x0, 0x0, 0x0, 0x3, 0xf8, 0xfe, 0x0, 0x0,
0x0, 0x0, 0x3f, 0x8f, 0xe0, 0x0, 0x0, 0x0,
0x3, 0xfc, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x3f,
0xcf, 0xe0, 0x0, 0x0, 0x0, 0x3, 0xfc, 0xfe,
0x0, 0x0, 0x0, 0x0, 0x3f, 0xef, 0xe0, 0x0,
0x0, 0x0, 0x3, 0xfe, 0xfe, 0x0, 0x0, 0x0,
0x0, 0x3f, 0xef, 0xe0, 0x0, 0x0, 0x0, 0x3,
0xfe, 0xff, 0x0, 0x0, 0x0, 0x0, 0x3f, 0xe7,
0xf0, 0x0, 0x0, 0x0, 0x7, 0xff, 0x7f, 0x0,
0x0, 0x0, 0x0, 0x7f, 0xf7, 0xf8, 0x0, 0x0,
0x0, 0xf, 0xff, 0x3f, 0xc0, 0x0, 0x0, 0x1,
0xff, 0xf3, 0xfc, 0x0, 0x0, 0x0, 0x1f, 0xff,
0x1f, 0xe0, 0x0, 0x0, 0x3, 0xf7, 0xf1, 0xff,
0x80, 0x0, 0x0, 0xfe, 0x7f, 0xf, 0xfc, 0x0,
0x0, 0x1f, 0xe7, 0xf0, 0x7f, 0xf0, 0x0, 0x7,
0xfc, 0x7f, 0x3, 0xff, 0xe0, 0x3, 0xff, 0x87,
0xf0, 0x1f, 0xff, 0xff, 0xff, 0xf0, 0x7f, 0x0,
0xff, 0xff, 0xff, 0xfe, 0x7, 0xf0, 0x3, 0xff,
0xff, 0xff, 0x80, 0x7f, 0x0, 0x1f, 0xff, 0xff,
0xe0, 0x7, 0xf0, 0x0, 0x3f, 0xff, 0xf8, 0x0,
0x7f, 0x0, 0x0, 0x3f, 0xf8, 0x0, 0x7, 0xf0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x7e, 0x0, 0x0,
0x0, 0x0, 0x0, 0xf, 0xe0, 0x0, 0x0, 0x0,
0x0, 0x0, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0,
0xf, 0xe0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xfe,
0x0, 0x0, 0x0, 0x0, 0x0, 0xf, 0xc0, 0x0,
0x0, 0x0, 0x0, 0x1, 0xfc, 0x0, 0x0, 0x0,
0x0, 0x0, 0x1f, 0xc0, 0x0, 0x0, 0x0, 0x0,
0x1, 0xfc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3f,
0x80, 0x0, 0x0, 0x0, 0x0, 0x3, 0xf8, 0x0,
0x0, 0x0, 0x0, 0x0, 0x7f, 0x0, 0x0, 0x0,
0x0, 0x0, 0xf, 0xf0, 0x0, 0x0, 0x0, 0x0,
0x0, 0xfe, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1f,
0xe0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xfc, 0x0,
0x0, 0x0, 0x0, 0x0, 0x7f, 0xc0, 0x0, 0x0,
0x0, 0x0, 0xf, 0xf8, 0x0, 0x0, 0x0, 0x0,
0x1, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7f,
0xe0, 0x0, 0x38, 0x0, 0x0, 0x1f, 0xfc, 0x0,
0x3, 0xf0, 0x0, 0xf, 0xff, 0x80, 0x0, 0x7f,
0xff, 0xff, 0xff, 0xf0, 0x0, 0x7, 0xff, 0xff,
0xff, 0xfc, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff,
0x80, 0x0, 0x7, 0xff, 0xff, 0xff, 0xe0, 0x0,
0x0, 0x1f, 0xff, 0xff, 0xf0, 0x0, 0x0, 0x0,
0x1f, 0xff, 0xf8, 0x0, 0x0, 0x0, 0x0, 0x1,
0x80, 0x0, 0x0, 0x0
};
/*---------------------
* GLYPH DESCRIPTION
*--------------------*/
static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {
{.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */,
{.bitmap_index = 0, .adv_w = 1059, .box_w = 56, .box_h = 72, .ofs_x = 5, .ofs_y = -1},
{.bitmap_index = 504, .adv_w = 578, .box_w = 24, .box_h = 70, .ofs_x = 0, .ofs_y = 0},
{.bitmap_index = 714, .adv_w = 909, .box_w = 51, .box_h = 71, .ofs_x = 2, .ofs_y = 0},
{.bitmap_index = 1167, .adv_w = 902, .box_w = 51, .box_h = 71, .ofs_x = 1, .ofs_y = -1},
{.bitmap_index = 1620, .adv_w = 1058, .box_w = 61, .box_h = 70, .ofs_x = 3, .ofs_y = 0},
{.bitmap_index = 2154, .adv_w = 906, .box_w = 50, .box_h = 71, .ofs_x = 2, .ofs_y = -1},
{.bitmap_index = 2598, .adv_w = 974, .box_w = 53, .box_h = 72, .ofs_x = 5, .ofs_y = -1},
{.bitmap_index = 3075, .adv_w = 942, .box_w = 52, .box_h = 70, .ofs_x = 3, .ofs_y = 0},
{.bitmap_index = 3530, .adv_w = 1021, .box_w = 54, .box_h = 72, .ofs_x = 5, .ofs_y = -1},
{.bitmap_index = 4016, .adv_w = 974, .box_w = 52, .box_h = 72, .ofs_x = 3, .ofs_y = -1}
};
/*---------------------
* CHARACTER MAPPING
*--------------------*/
/*Collect the unicode lists and glyph_id offsets*/
static const lv_font_fmt_txt_cmap_t cmaps[] =
{
{
.range_start = 48, .range_length = 10, .glyph_id_start = 1,
.unicode_list = NULL, .glyph_id_ofs_list = NULL, .list_length = 0, .type = LV_FONT_FMT_TXT_CMAP_FORMAT0_TINY
}
};
/*--------------------
* ALL CUSTOM DATA
*--------------------*/
#if LVGL_VERSION_MAJOR == 8
/*Store all the custom data of the font*/
static lv_font_fmt_txt_glyph_cache_t cache;
#endif
#if LVGL_VERSION_MAJOR >= 8
static const lv_font_fmt_txt_dsc_t font_dsc = {
#else
static lv_font_fmt_txt_dsc_t font_dsc = {
#endif
.glyph_bitmap = glyph_bitmap,
.glyph_dsc = glyph_dsc,
.cmaps = cmaps,
.kern_dsc = NULL,
.kern_scale = 0,
.cmap_num = 1,
.bpp = 1,
.kern_classes = 0,
.bitmap_format = 0,
#if LVGL_VERSION_MAJOR == 8
.cache = &cache
#endif
};
/*-----------------
* PUBLIC FONT
*----------------*/
/*Initialize a public general font descriptor*/
#if LVGL_VERSION_MAJOR >= 8
const lv_font_t montserrat_100 = {
#else
lv_font_t montserrat_100 = {
#endif
.get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt, /*Function pointer to get glyph's data*/
.get_glyph_bitmap = lv_font_get_bitmap_fmt_txt, /*Function pointer to get glyph's bitmap*/
.line_height = 72, /*The maximum line height required by the font*/
.base_line = 1, /*Baseline measured from the bottom of the line*/
// #if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0)
// .subpx = LV_FONT_SUBPX_NONE,
// #endif
// #if LV_VERSION_CHECK(7, 4, 0) || LVGL_VERSION_MAJOR >= 8
// .underline_position = -10,
// .underline_thickness = 5,
// #endif
.dsc = &font_dsc, /*The custom font data. Will be accessed by `get_glyph_bitmap/dsc` */
// #if LV_VERSION_CHECK(8, 2, 0) || LVGL_VERSION_MAJOR >= 9
// .fallback = NULL,
// #endif
.user_data = NULL,
};
#endif /*#if MONTSERRAT_100*/
@@ -1,18 +0,0 @@
; ELM327 -> WiFi bridge — runs on a CLASSIC ESP32 (needs Bluetooth Classic, which the ESP32-S3 lacks)
; Original note: "needs esp32 2.0.* in boards manager" -> official platform 6.10.0 = Arduino core 2.0.17
[env:esp32dev]
platform = espressif32@6.10.0
board = esp32dev
framework = arduino
; BluetoothSerial + WiFi + web server do not fit the default 1.2 MB app partition
board_build.partitions = huge_app.csv
lib_deps =
powerbroker2/ELMduino@3.4.1
esphome/ESPAsyncWebServer-esphome@3.4.0
; upload_port = COMxx ; set to the classic ESP32's port (COM21 is the JC3636W518)
monitor_speed = 115200
monitor_filters = esp32_exception_decoder
@@ -1,96 +0,0 @@
#include <Arduino.h>
// Import required libraries
#include "WiFi.h"
#include <ESPAsyncWebServer.h>
// needs esp32 2.0.* in boards manaager // https://forum.arduino.cc/t/libary-problem-with-esp32-web-server/1282583/6
// Set your access point network credentials
const char* ssid = "ESP32-Access-Point";
const char* password = "123456789";
#include "BluetoothSerial.h"
#include "ELMduino.h"
// Create AsyncWebServer object on port 80
AsyncWebServer server(80);
BluetoothSerial SerialBT;
#define ELM_PORT SerialBT
#define DEBUG_PORT Serial
ELM327 myELM327;
uint32_t kph = 0;
String readSpeed() {
if (ELM_PORT.isClosed()) {
return "0";
}
float tempkph = myELM327.kph();
if (myELM327.nb_rx_state == ELM_SUCCESS) {
kph = (uint32_t)tempkph;
Serial.print("kph: ");
Serial.println(kph);
} else if (myELM327.nb_rx_state != ELM_GETTING_MSG)
myELM327.printError();
return String(kph);
// return "123";
}
void connectElm() {
if (!ELM_PORT.connect("OBDII")) {
DEBUG_PORT.println("Couldn't connect to OBD scanner - Phase 1");
// while (1)
// ;
return;
}
if (!myELM327.begin(ELM_PORT, true, 2000)) {
Serial.println("Couldn't connect to OBD scanner - Phase 2");
// while (1)
// ;
return;
}
Serial.println("Connected to ELM327");
}
void setup() {
DEBUG_PORT.begin(115200);
SerialBT.setPin("1234");
ELM_PORT.begin("ArduHUD", true);
Serial.println("Starting ELM327 to wifi bridge");
//connectElm();
// Setting the ESP as an access point
Serial.print("Setting AP (Access Point)…");
// Remove the password parameter, if you want the AP (Access Point) to be open
WiFi.softAP(ssid, password);
IPAddress IP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(IP);
server.on("/speed", HTTP_GET, [](AsyncWebServerRequest* request) {
request->send_P(200, "text/plain", readSpeed().c_str());
});
// Start server
server.begin();
}
void loop() {
if (ELM_PORT.isClosed()) {
Serial.println("Trying to reconnect to ELM327");
connectElm();
}
}
-7
View File
@@ -1,7 +0,0 @@
.pio
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/ipch
.vscode/extensions.json
-260
View File
@@ -1,260 +0,0 @@
# ELMulator
## ODBII & ELM327 Emulation library
This is a fork of the [GTTurboEcu library by TheBigBadWolfClub](https://github.com/TheBigBadWolfClub/GTTurboEcu) which is no longer being maintained.
I have updated the library with the following changes:
- included fixes submitted to the upstream fork but never merged
- updated the library to work with Bluetooth serial devices (like esp32)
- added support for connecting via WiFi (default port 30000)
- added additional functions to the emulator
- optimized the use of String class for lower memory requirements
- fixed a few bugs from earlier version
- refactored many methods to simplify use of the library
- made it easier to add your own new functions with sample/test data
- implemented a simple emulation mode that can be easily configured with just a few lines of code
- added new example programs to demonstrate use of the library
This library allows you to program a microcontroller device to emulate an ELM327 OBDII adapter connected to an ECU. The emulator can work with any of the huge number of ODBII apps that are compatible with the ELM327-standard adapters, like Torque or CarScanner.
There are two main use cases for this library:
1. It is ideal for use in developing OBDII applications using the [ELMduino](https://github.com/PowerBroker2/ELMduino) library. You can build the example program, upload it to a $10 ESP32 device with builtin Bluetooth and use it for your testing and development, avoiding the need to be communicating with an actual ELM327 device plugged into a vehicle. (Doing app development sitting on a folding chair in the garage gets old pretty quickly...).
2. This library can be used to as the building block for creating your own ELM327 compatible device that can read values from various custom sensors and allow you to monitor
those sensors with your own software or one of the many OBDII client applcations that are compatible with the ELM327 specification. No connection to an actual OBDII port is needed.
## Platforms
ELMulator is built and tested using the Arduino framework on a variety of ESP32 dev boards. The ESP32 platform is ideal as it has Bluetooth, Wifi and serial capability included and is very inexpensive. The library should work with any Arduino compatible device with the use of additional hardware like the [HC05 Bluetooth Module](https://components101.com/wireless/hc-05-bluetooth-module) to provide Bluetooth capability.
## Using ELMulator
In order to take best advantage of this library, you should be familiar with two specifications:
- The [OBDII protocol](https://en.wikipedia.org/wiki/OBD-II_PIDs)
- The [ELM327 datasheet](https://www.elmelectronics.com/DSheets/ELM327DSH.pdf)
You need to understand what a PID is and how it is defined (mode, pid, number of bytes, conversion formulas ...) and how to send/receive PID info from the ELM327 device.
### Easily create an ELM327 emulator with just a few lines of code:
```C
#include "ESP32_Simple_ELMulator.h"
/**
* This example demonstrates a very simple implementation of ELMulator.
* It handles all AT Commands, SUPPORTED_PIDS queries, etc. and rejectss unsupported requests.
* All Mode 01 PID requests are provided a response of the correct data size with mock data values.
* Typical use case is for testing custom applications to work with ELM327 devices.
*/
const String deviceName = "ELMULATOR"; // Bluetooth device name to use (no pin)
ELMulator ELMulator;
void setup()
{
Serial.begin(115200);
Serial.println("Starting ELMulator...");
ELMulator.init(deviceName, true); // Initialize with support for all PIDs 0x00 to 0x65, providing mock data
ELMulator.begin(); // ELMulator will run continuously, listening for and respondng to requests
}
void loop() {}
```
That's it. Complile and install the program on an inexpensive ESP32 dev board and you can be running in a couple of minutes. You can connect your app or device to the Bluetooth device "ELMULATOR" and query it as though it were an ELM327 adapter connected to a vehicle. Very handy for testing, without the need to install and configure a physical adapter attached to an actual vehicle.
### Custom ELM327-compatible programs/devices
You can also use ELMulator to provide custom responses to specific queries or as the basis for your own device that reads physical sensors and provides sensor data via the OBDII / ELM327 protocols. This example code shows responding to PID requests with hardcoded values, custom sensor data and mock sensor values to demonstrate some of the possibilities for creating your ELM327 compatible application or device.
```C
#include "ESP32_Custom_ELMulator.h"
const String deviceName = "ELMULATOR"; // Bluetooth device name to use (no pin)
// Set up hardcoded responses to non-standard PID requests for MIL, DTC, ODO data
const String milResponse = "4101830000"; // MIL response code indicating 3 current DTC
const String dtcResponse = "43010341\r\n43010123\r\n43010420"; // DTC response returning 3 DTC codes (multiline response)
const uint32_t odoResponse = 1234567; // Hardcode an odometer reading of 1234567
ELMulator ELMulator;
void setup()
{
Serial.begin(115200);
Serial.println("Starting ELMulator...");
ELMulator.init(deviceName);
// Register the specific PIDs we are going to handle.
// ELMulator will respond to SUPPORTED_PIDS request ("0101") with the appropriate value
// indicating which PIDs it can respond to.
ELMulator.registerMode01Pid(ODOMETER);
ELMulator.registerMode01Pid(ENGINE_COOLANT_TEMP);
ELMulator.registerMode01Pid(ENGINE_RPM);
ELMulator.registerMode01Pid(MONITOR_STATUS_SINCE_DTC_CLEARED);
ELMulator.registerMode01Pid(INTAKE_MANIFOLD_ABS_PRESSURE);
ELMulator.registerMode01Pid(ENGINE_LOAD);
ELMulator.registerMode01Pid(VEHICLE_SPEED);
ELMulator.registerMode01Pid(INTAKE_AIR_TEMP);
ELMulator.registerMode01Pid(THROTTLE_POSITION);
}
void loop()
{
/**
* Waits for a new request from an ELM client and does some pre-processing to validate the PID and request.
* Non-PID requests like AT commands and SUPPORTED_PIDS queries will be handled by ELMulator automatically.
* PID requests will be verified as supported (registered); unsupported PIDs responded to with NO DATA response.
* Returns true if the request is for one of the PIDS we have registered above so we can respond to it.
*/
if (ELMulator.readELMRequest())
{
handlePIDRequest(ELMulator.elmRequest);
}
}
/**
* After receiving a supported PID(sensor) request, we prepare the response here.
* Response can come from a hardcoded value, a real hardware sensor, or a mock value.
* Default response is a mock value if no other handling is provided.
*/
void handlePIDRequest(const String &request)
{
// Handle special case requests like MIL and DTC checks
if (ELMulator.isMode03(request)) // Mode 03 request == Returns (hardcoded) list current DTC codes
{
if (dtcResponse.length())
{
ELMulator.writeResponse(dtcResponse);
return;
}
else
{
DEBUG("DTC response is empty.");
ELMulator.writePidNotSupported();
return;
}
}
else if (ELMulator.isMode01MIL(request)) // Mode 0101 MIL request == Returns (hardcoded) number of current DTC codes
{
if (milResponse.length())
{
ELMulator.writeResponse(milResponse);
return;
}
else
{
DEBUG("MIL response is empty.");
ELMulator.writePidNotSupported();
return;
}
}
else // Handle any other supported PID request
{
uint8_t pidCode = ELMulator.getPidCode(request); // Extract the specific PID code from the request
// Example response for 0x05 (Engine Coolant Temp) - returning a mock data value
if (pidCode == ENGINE_COOLANT_TEMP)
{
uint32_t sensorValue = ELMulator.getMockSensorValue();
/**
* Response parameters:
* request - specifies the original PID request being responded to.
* numberOfBytes - the number of bytes this PID value returns (per OBDII spec), using lookup table responseBytes.
* sensorValue - the value to return in our response; a mock value in this example.
*/
ELMulator.writePidResponse(request, responseBytes[pidCode], sensorValue);
return;
}
// Example response for PID 0x0C (Engine RPM) - returns a modified mock sensor value
else if (pidCode == ENGINE_RPM)
{
uint16_t rpmValue = ELMulator.getMockSensorValue() * 100; // Here we multiply the mock value provided, for a more realistic RPM number
ELMulator.writePidResponse(request, responseBytes[pidCode], rpmValue);
return;
}
// Example response for PID 0xA6 (Odometer) - returns our hardcoded odometer response string
else if (pidCode == ODOMETER)
{
ELMulator.writePidResponse(request, responseBytes[pidCode], odoResponse);
return;
}
// // Example response for PID 0xOD (Vehicle speed) - returns a value from external GPS
// else if (pidCode == VEHICLE_SPEED)
// {
// uint32_t gpsSpeed = myGPS.speed();
// ELMulator.writePidResponse(request, responseBytes[pidCode], gpsSpeed);
// return;
// }
// Default response for any other supported PID request - returns an unmodified mock sensor value
else
{
ELMulator.writePidResponse(request, responseBytes[pidCode], ELMulator.getMockSensorValue());
return;
}
}
}
```
## Using ELMulator with an addon Bluetooth module (GPIO)
By default, ELMulator will work on an EPS32 with builtin Bluetooth, but you can configure another device type for use with a bluetooth module like the [HC05 Bluetooth Module](https://components101.com/wireless/hc-05-bluetooth-module) connected via GPIO and using SoftwareSerial.h library. To do so, the following two changes need to be made:
1. Update definitions.h file and undefine or set to false "BLUETOOTH_BUILTIN":
```
// true == We are using hardware that has builtin Bluetooth
// false == We are using hardware with a Bluetooth module attached via GPIO pins
#define BLUETOOTH_BUILTIN true
```
2. Provide the serial baud rate, Rx and Tx pin numbers when creating your ELMulator instance:
```
/**
* When creating a new instance of ELMulator with an attached
* BT module, we need to set the Bluetooth serial parameters
*
* @param baudRate - rate for bluetooth serial port (normally 9600)
* @param rxPin - the Arduino RX pin for the Bluetooth module (serial port)
* @param txPin - the Arduino TX pin for the Bluetooth module (serial port)
* @return
*/
ELMulator(uint32_t baudRate, uint8_t rxPin, uint8_t txPin);
```
Other than that, use of the ELMulator library is the same as when using builtin Bluetooth.
## License
The MIT License (MIT)
Copyright (c) 2023 Jim Whitelaw
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation 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
furnished 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 FOR 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.
Whitespace-only changes.
@@ -1,9 +0,0 @@
name=ELMulator
version=1.0.0
author=jimwhitelaw
maintainer=jimwhitelaw@gmail.com
sentence=Arduino ODBII ELM327 Emulator - Allows Arduino to act as a ECU and a OBD2 ELM327 device and response to PID requests, from any given ODBII app (ex: android::torque).
paragraph=Using Arduino and Bluetooth shield to act as an ELM237. Connect sensors to arduino define their PID code and it's possible to see them in an app like Torque.
category=Communication
url=https://github.com/jimwhitelaw/ELMulator
architectures=*
@@ -1,162 +0,0 @@
#include "ATCommands.h"
#include "definitions.h"
#if USE_WIFI
ATCommands::ATCommands(OBDWiFiComm *connection) {
this->connection = connection;
}
#else
ATCommands::ATCommands(OBDSerialComm *connection) {
this->connection = connection;
}
#endif
ATCommands::~ATCommands() {
operator delete(this->connection);
}
bool ATCommands::process(const String& command) {
bool processed = false;
if (isATCommand(command)) {
processed = true;
processCommand(command);
DEBUG("RX AT: " + command);
}
return processed;
}
void ATCommands::processCommand(const String& command) {
// if space is enabled (ex: AT H[charPosition])
uint8_t offset = 2;
if (command.charAt(2) == 0x20) {
offset = 3;
}
// refer to ELM327 specs
String specificCommand = command.substring(offset, command.length());
if (specificCommand.startsWith("D",offset)) {
ATCommands::ATD();
} else if (specificCommand.startsWith("Z")) {
ATCommands::ATZ();
} else if (specificCommand.startsWith("I")) {
ATCommands::ATI();
} else if (specificCommand.startsWith("E")) {
ATCommands::ATEx(specificCommand);
} else if (specificCommand.startsWith("L")) {
ATCommands::ATLx(specificCommand);
} else if (specificCommand.startsWith("M")) {
ATCommands::ATMx(specificCommand);
} else if (specificCommand.startsWith("S")) {
if (specificCommand.startsWith("SP")) {
ATCommands::ATSPx(specificCommand);
} else {
ATCommands::ATSx(specificCommand);
}
} else if (specificCommand.startsWith("H")) {
ATCommands::ATHx(specificCommand);
} else if (specificCommand.startsWith("AT")) {
ATCommands::ATATx(specificCommand);
} else if (specificCommand.startsWith("DPN")) {
ATCommands::ATDPN();
} else if (specificCommand.startsWith("DESC") || specificCommand.startsWith("@1")) {
ATCommands::ATDESC();
} else if (specificCommand.startsWith("PC")) {
ATCommands::ATPC();
} else if (specificCommand.startsWith("RV")){
ATCommands::ATRV();
} else {
// lets assume we process any at command
connection->writeEndOK();
}
}
// set all to defaults
void ATCommands::ATD() {
connection->setToDefaults();
connection->writeEndOK();
}
// reset all
void ATCommands::ATZ() {
connection->setToDefaults();
connection->writeTo(ID);
connection->writeEndOK();
}
// Print the version ID
void ATCommands::ATI() {
connection->writeTo(ID);
connection->writeEndOK();
}
// send description
void ATCommands::ATDESC() {
connection->writeTo(DESC);
connection->writeEndOK();
}
// set echoEnable 0=off 1=on
void ATCommands::ATEx(String& cmd) {
connection->setEcho(cmd.equals("E0") ? false : true);
connection->writeEndOK();
}
// set memory off=0 on=1
void ATCommands::ATMx(String& cmd) {
connection->setMemory(cmd.equals("M0") ? false : true);
connection->writeEndOK();
}
// line feeds off=0 on=1
void ATCommands::ATLx(String& cmd) {
connection->setLineFeeds(cmd.equals("L0") ? false : true);
connection->writeEndOK();
}
// ATSx printing spaces off=0 on=1
void ATCommands::ATSx(String& cmd) {
connection->setWhiteSpaces(cmd.equals("S0") ? false : true);
connection->writeEndOK();
}
// Headers off=0 on=1
void ATCommands::ATHx(String& cmd) {
connection->setHeaders(cmd.equals("H0") ? false : true);
connection->writeEndOK();
}
// ATSPx Define protocol 0=auto
void ATCommands::ATSPx(String& cmd) {
connection->writeEndOK();
}
// set protocol
void ATCommands::ATDPN() {
connection->writeTo(PROTOCOL);
connection->writeEndOK();
}
// AT AT2 adaptative time control
void ATCommands::ATATx(String& cmd) {
connection->writeEndOK();
}
// Terminates current diagnostic session. Protocol close
void ATCommands::ATPC() {
connection->writeEndOK();
}
void ATCommands::ATRV() {
connection->writeTo("AT RV\r13.5V");
connection->writeEnd();
}
// return true ir connectionand is AT
bool ATCommands::isATCommand(const String& command) {
toUpperCase(command.charAt(0));
return command.startsWith("AT") ? true : false;
}
@@ -1,65 +0,0 @@
#ifndef ELMulator_ATCommands_h
#define ELMulator_ATCommands_h
#include <Arduino.h>
#include "definitions.h"
#if USE_WIFI
#include "OBDWiFiComm.h"
#else
#include "OBDSerialComm.h"
#endif
class ATCommands
{
public:
#if USE_WIFI
ATCommands(OBDWiFiComm *connection);
#else
ATCommands(OBDSerialComm *connection);
#endif
~ATCommands();
bool process(const String &string);
private:
// Variables
#if USE_WIFI
OBDWiFiComm *connection;
#else
OBDSerialComm *connection;
#endif
void ATD();
void ATZ();
void ATI();
void ATEx(String &x);
void ATMx(String &x);
void ATLx(String &x);
void ATSx(String &x);
void ATHx(String &x);
void ATSPx(String &x);
void ATATx(String &cmd);
void ATPC();
void ATDPN();
void ATDESC();
void ATRV();
void processCommand(const String &command);
bool isATCommand(const String &command);
};
#endif
@@ -1,218 +0,0 @@
#include "ELMulator.h"
#if USE_WIFI
ELMulator::ELMulator()
{
_connection = new OBDWiFiComm();
_atProcessor = new ATCommands(_connection);
_pidProcessor = new PidProcessor(_connection);
_lastCommand = "";
elmRequest.reserve(MAX_REQUEST_SIZE);
elmRequest = "";
}
#else
ELMulator::ELMulator()
{
_connection = new OBDSerialComm();
_atProcessor = new ATCommands(_connection);
_pidProcessor = new PidProcessor(_connection);
_lastCommand = "";
elmRequest.reserve(MAX_REQUEST_SIZE);
elmRequest = "";
}
#endif
ELMulator::~ELMulator() {}
void ELMulator::init(const String &deviceName, bool registerPids)
{
_connection->init(deviceName);
if (registerPids)
{
registerAllMode01Pids();
}
}
void ELMulator::registerAllMode01Pids()
{
// Set up PIDs 0x00 to 0x65
// Will be provided a response with mock data value if no special handling is performed.
// Requests for PID > 0x65 are not currently supported - will return "NO DATA"
for (int i = 0; i < 0x66; i++)
{
_pidProcessor->registerMode01Pid(i);
}
}
bool ELMulator::readELMRequest()
{
do
{
elmRequest.clear(); // clear buffer from previous requests
_connection->readData(elmRequest);
elmRequest.toUpperCase();
// TODO ignore spaces, and all control chars (tab, etc)
// TODO accept single carriage return as repeat last command at or pid
// ignore null i.e 00
} while (processRequest(elmRequest)); // processRequest handles all non PID requests (AT commands, errors etc)
return true; // We have received a valid PID request we need to respond to
}
void ELMulator::sendELMResponse()
{
// Respond to any mode 01 request with a mock sensor value
if (isMode01(elmRequest))
{
uint8_t pidCode = getPidCode(elmRequest);
writePidResponse(elmRequest, responseBytes[pidCode], getMockSensorValue());
return;
}
// Not a mode 01 PID request. Report it as not supported (ie, "NO DATA");
else
{
writePidNotSupported();
}
}
void ELMulator::begin()
{
while (readELMRequest())
{
sendELMResponse();
}
}
uint8_t ELMulator::getPidCode(const String &request)
{
return _pidProcessor->getPidCodeFromRequest(request);
}
bool ELMulator::registerMode01Pid(uint32_t pid)
{
return _pidProcessor->registerMode01Pid(pid);
}
bool ELMulator::registerMode01MILResponse(const String &response)
{
return _pidProcessor->registerMode01MILResponse(response);
}
bool ELMulator::registerMode03Response(const String &response)
{
return _pidProcessor->registerMode03Response(response);
}
void ELMulator::writePidNotSupported()
{
_connection->writeEndNoData();
}
void ELMulator::writePidResponse(const String &requestPid, uint8_t numberOfBytes, uint32_t value)
{
_pidProcessor->writePidResponse(requestPid, numberOfBytes, value);
}
void ELMulator::writeResponse(const String &response)
{
_connection->writeTo(response.c_str());
_connection->writeEnd();
}
bool ELMulator::processRequest(String &command)
{
// TODO: check for no 0X0D char in command, return ? and >
// TODO: receive only <cr>, repeat last command
// carriage return, means repeat last command
if (command == "\n")
{
command = _lastCommand;
}
// empty command, do not send to user
if (command.length() == 0)
{
return true;
}
// Check for AT command
if (_atProcessor->process(command))
{
_lastCommand = command;
return true;
}
// Check for a valid hex string, return error if false
if (!isValidHex(command.c_str()))
{
_connection->writeEndUnknown();
DEBUG("Invalid HEX command: " + command);
return true;
}
// Check for a valid PID request
if (_pidProcessor->process(command))
{
_lastCommand = command;
return true;
}
return false;
}
bool ELMulator::isValidHex(const char *pid)
{
return (pid[strspn(pid, "0123456789abcdefABCDEF")] == 0);
}
bool ELMulator::isMode01(const String &command)
{
return _pidProcessor->isMode01(command);
}
bool ELMulator::isMode22(const String &command)
{
return _pidProcessor->isMode22(command);
}
bool ELMulator::isMode03(const String &command)
{
return _pidProcessor->isMode03(command);
}
bool ELMulator::isMode01MIL(const String &command)
{
return _pidProcessor->isMode01MIL(command);
}
/**
* Fake some data
* start counter = 0
* every call to getFakeSensorData, will increment the counter to a max of 255 (0xff)
* When 0xff is reached the cycle is reverted
* and the counter will decrement by one until 0 is reached
*/
uint32_t ELMulator::getMockSensorValue()
{
if (isCycleUp)
{
cycle++;
if (cycle == 0xff)
{
isCycleUp = false;
}
}
else
{
cycle--;
if (cycle == 0)
{
isCycleUp = true;
}
}
return cycle;
}
@@ -1,138 +0,0 @@
#ifndef ELMulator_ELMulator_h
#define ELMulator_ELMulator_h
#include <Arduino.h>
#include "definitions.h"
#if USE_WIFI
#include "OBDWiFiComm.h"
#else
#include "OBDSerialComm.h"
#endif
#include "ATCommands.h"
#include "PidProcessor.h"
#include "definitions.h"
class ELMulator
{
public:
/**
* When creating a new instance of ELMulator with an attached
* BT module, we need to set the Bluetooth serial parameters
*
* @param baudRate - rate for bluetooth serial port (normally 9600)
* @param rxPin - the Arduino RX pin for the Bluetooth module (serial port)
* @param txPin - the Arduino TX pin for the Bluetooth module (serial port)
* @return
*/
ELMulator(uint32_t baudRate, uint8_t rxPin, uint8_t txPin);
ELMulator();
~ELMulator();
void init(const String &deviceName, const bool registerPids = false);
/**
* Waits and Reads a request from OBD client software (example: torque)
*
* It will only return if the OBD client software
* request's one of the previous registred PID (see: registerMode01Pid())
*
* Any other request will be automaticly handle or it will be rejected.
*
* ELMulator lib will automaticly handle:
* - any AT command
* - PID's support request like 0100, 0120, 0140, 0160, ...
*
* @return the request PID (Vehicle speed ex: 010D )
*/
bool readELMRequest();
void sendELMResponse();
void begin();
/**
* Registry the PID's (sensors) your arduino will support.
* Currently ELMulator lib only supports MODE 01 PID's
*
* In OBDII MODE 01 PID's, are request
* to Show current data for a given sensor identified by a PID
*
* List of MODE 01 PID's: https://en.wikipedia.org/wiki/OBD-II_PIDs#Mode_01
*
* Example, for Vehicle speed, where OBDII PID code is 0D:
*
* registerMode01Pid(0x0D)
*
* @param pidHexId
* @return returns true if the entered ID is in the valide range [0x00, 0xFF]
*/
bool registerMode01Pid(uint32_t pidHexId);
bool registerMode01MILResponse(const String &response);
bool registerMode03Response(const String &response);
uint8_t getPidCodeOnly(uint16_t hexCommand);
/**
* Used to informe the OBD client that the PID,
* it has requested is not supported
*
* In normal conditions, you will only receive,
* request for the PID you have registred
* All other request is automaticly handle by GTTuboEcu lib
*/
void writePidNotSupported();
/**
* Respond to a given PID request from OBDII client (ex: torque)
*
* @param requestPid - the received pid
* @param numberOfBytes - number of bytes this PID response VALUE is supposed to have
* @param value - sensor value for this PID,
* lib will convert it to HEX
* with the appropriated number of HEX chars (numberOfChars)
*/
void writePidResponse(const String &requestPid, uint8_t numberOfBytes, uint32_t value);
// Write the response back to the requestor without PID formatting, etc
// Just pass the response string on. Useful for testing with a specific response
// that has been pre-configured.
void writeResponse(const String &response);
bool isMode01(const String &command);
bool isMode03(const String &command);
bool isMode01MIL(const String &command);
bool isMode22(const String &command);
uint8_t getPidCode(const String &request);
void registerAllMode01Pids();
uint32_t getMockSensorValue();
String elmRequest;
private:
#if USE_WIFI
OBDWiFiComm *_connection;
#else
OBDSerialComm *_connection;
#endif
ATCommands *_atProcessor;
PidProcessor *_pidProcessor;
String _lastCommand;
bool isCycleUp = true;
uint32_t cycle = 0;
bool processRequest(String &command);
bool isValidHex(const char *pid);
};
#endif
@@ -1,145 +0,0 @@
#include "OBDSerialComm.h"
#include "definitions.h"
OBDSerialComm::OBDSerialComm() {
}
OBDSerialComm::~OBDSerialComm() {
operator delete(serial);
}
void OBDSerialComm::init(const String& deviceName) {
Serial.println("Starting BT . . .");
serial = new BluetoothSerial();
//delay(2000);
serial->begin(deviceName, false);
setToDefaults();
}
void OBDSerialComm::writeEnd() {
// 1 - write carriage return
writeTo("\r");
// 2- (optional ) write linefeed
if (lineFeedEnable) {
writeTo("\n");
}
// 3 - Write prompt
writeTo(">");
serial->flush();
};
void OBDSerialComm::writeEndOK() {
writeTo("OK");
writeEnd();
}
void OBDSerialComm::writeEndERROR() {
writeTo("ERROR");
writeEnd();
}
void OBDSerialComm::writeEndNoData() {
writeTo("NO DATA");
writeEnd();
}
void OBDSerialComm::writeEndUnknown() {
writeTo("?");
writeEnd();
}
void OBDSerialComm::setToDefaults() {
setEcho(true);
setStatus(READY);
setWhiteSpaces(true);
setHeaders(false);
setLineFeeds(true);
setMemory(false);
}
void OBDSerialComm::writeTo(char const *response) {
serial->print(response);
}
void OBDSerialComm::writeTo(uint8_t cChar) {
serial->print(cChar);
}
void OBDSerialComm::writeEndPidTo(char const *response) {
if (whiteSpacesEnabled) {
uint8_t len = strlen(response);
char spacedResponse[len + len / 2 + 1];
addSpacesToResponse(response, spacedResponse);
writeTo(spacedResponse);
} else {
writeTo(response);
}
writeEnd();
}
void OBDSerialComm::readData(String& rxData) {
serial->flush(); // temp remove this
rxData = serial->readStringUntil(SERIAL_END_CHAR);
if (isEchoEnable()) {
writeTo(rxData.c_str());
}
//return rxData;
}
void OBDSerialComm::setBaudRate(uint32_t rate) {
this->baudRate = rate;
}
long OBDSerialComm::getBaudRate() {
return this->baudRate;
}
bool OBDSerialComm::isEchoEnable() {
return this->echoEnable;
}
void OBDSerialComm::setEcho(bool echo) {
this->echoEnable = echo;
}
void OBDSerialComm::setStatus(STATUS status) {
this->status = status;
}
void OBDSerialComm::setLineFeeds(bool status) {
this->lineFeedEnable = status;
}
void OBDSerialComm::setMemory(bool status) {
this->memoryEnabled = status;
}
void OBDSerialComm::setWhiteSpaces(bool status) {
this->whiteSpacesEnabled = status;
}
void OBDSerialComm::setHeaders(bool status) {
this->headersEnabled = status;
}
void OBDSerialComm::addSpacesToResponse(const char *response, char spacedRes[]) {
uint8_t len = strlen(response);
int j = 0;
for (int i = 0; i < len;) {
*(spacedRes + j++) = *(response + i++);
*(spacedRes + j++) = *(response + i++);
if (i < len) {
*(spacedRes + j++) = 0x20;
}
}
*(spacedRes + j) = '\0';
}
@@ -1,90 +0,0 @@
#ifndef ELMulator_OBDSerialComm_h
#define ELMulator_OBDSerialComm_h
#include <Arduino.h>
#include "definitions.h"
#include <BluetoothSerial.h>
class OBDSerialComm
{
public:
enum STATUS
{
IDLE = 0,
READY = 1
};
// // Bluetooth is not built in, we are using a BT module via GPIO
// OBDSerialComm(uint32_t baudRate, uint8_t rxPin, uint8_t txPin);
OBDSerialComm();
~OBDSerialComm();
void init(const String &deviceName);
void writeEndOK();
void writeEndERROR();
/**
* Response for unsupported PID command or missing sensor, respond "NO DATA".
*/
void writeEndNoData();
/**
* Response for invalid AT command, or invalid PID or other bad input, respond "?"
*/
void writeEndUnknown();
void setToDefaults();
void readData(String &rxData);
void writeTo(uint8_t cChar);
void writeTo(char const *string);
void setEcho(bool echo);
void writeEnd();
bool isEchoEnable();
void setLineFeeds(bool status);
void setMemory(bool status);
void setWhiteSpaces(bool status);
void setHeaders(bool status);
void setStatus(STATUS status);
void writeEndPidTo(char const *string);
private:
uint32_t baudRate; // Serial Baud Rate
STATUS status; // Operation status
bool echoEnable; // echoEnable command after received
bool lineFeedEnable;
bool memoryEnabled;
bool whiteSpacesEnabled;
bool headersEnabled;
void setBaudRate(uint32_t rate);
long getBaudRate();
void addSpacesToResponse(const char *response, char string[]);
#ifndef BLUETOOTH_BUILTIN
HardwareSerial *serial; // lib to communicate with bluetooth
#else
BluetoothSerial *serial;
#endif
};
#endif
@@ -1,151 +0,0 @@
#include "OBDWiFiComm.h"
#include "definitions.h"
WiFiServer server(35000);
IPAddress localIP = IPAddress(192, 168, 0, 10);
IPAddress gateway = IPAddress(192, 168, 0, 10);
IPAddress subnet = IPAddress(255,255,255,0);
OBDWiFiComm::OBDWiFiComm() {
}
OBDWiFiComm::~OBDWiFiComm() {
}
void OBDWiFiComm::init(const String& deviceName) {
Serial.println("Starting AP . . .");
WiFi.softAPConfig(localIP, gateway, subnet);
WiFi.softAP(deviceName);
IPAddress myIP = WiFi.softAPIP();
Serial.print("AP IP address: ");
Serial.println(myIP);
Serial.print("AP SSID: ");
Serial.println(deviceName);
server.begin();
setToDefaults();
}
void OBDWiFiComm::writeEnd() {
// 1 - write carriage return
writeTo("\r");
// 2- (optional ) write linefeed
if (lineFeedEnable) {
writeTo("\n");
}
// 3 - Write prompt
writeTo(">");
};
void OBDWiFiComm::writeEndOK() {
writeTo("OK");
writeEnd();
}
void OBDWiFiComm::writeEndERROR() {
writeTo("ERROR");
writeEnd();
}
void OBDWiFiComm::writeEndNoData() {
writeTo("NO DATA");
writeEnd();
}
void OBDWiFiComm::writeEndUnknown() {
writeTo("?");
writeEnd();
}
void OBDWiFiComm::setToDefaults() {
setEcho(true);
setStatus(READY);
setWhiteSpaces(true);
setHeaders(false);
setLineFeeds(true);
setMemory(false);
}
void OBDWiFiComm::writeTo(char const *response) {
client.print(response);
}
void OBDWiFiComm::writeTo(uint8_t cChar) {
client.print(cChar);
}
void OBDWiFiComm::writeEndPidTo(char const *response) {
if (this->whiteSpacesEnabled) {
uint8_t len = strlen(response);
char spacedResponse[len + len / 2 + 1];
addSpacesToResponse(response, spacedResponse);
writeTo(spacedResponse);
} else {
writeTo(response);
}
writeEnd();
}
void OBDWiFiComm::readData(String& rxData) {
if (!client.connected()) {
client = server.available();
}
if(client)
{
rxData
= client.readStringUntil(SERIAL_END_CHAR);
if (isEchoEnable())
{
writeTo(rxData.c_str());
}
}
}
bool OBDWiFiComm::isEchoEnable() {
return this->echoEnable;
}
void OBDWiFiComm::setEcho(bool echo) {
this->echoEnable = echo;
}
void OBDWiFiComm::setStatus(STATUS status) {
this->status = status;
}
void OBDWiFiComm::setLineFeeds(bool status) {
this->lineFeedEnable = status;
}
void OBDWiFiComm::setMemory(bool status) {
this->memoryEnabled = status;
}
void OBDWiFiComm::setWhiteSpaces(bool status) {
this->whiteSpacesEnabled = status;
}
void OBDWiFiComm::setHeaders(bool status) {
this->headersEnabled = status;
}
void OBDWiFiComm::addSpacesToResponse(const char *response, char spacedRes[]) {
uint8_t len = strlen(response);
int j = 0;
for (int i = 0; i < len;) {
*(spacedRes + j++) = *(response + i++);
*(spacedRes + j++) = *(response + i++);
if (i < len) {
*(spacedRes + j++) = 0x20;
}
}
*(spacedRes + j) = '\0';
}
@@ -1,78 +0,0 @@
#ifndef ELMulator_OBDWiFiComm_h
#define ELMulator_OBDWiFiComm_h
#include <Arduino.h>
#include <WiFi.h>
#include <WiFiServer.h>
#include "definitions.h"
class OBDWiFiComm
{
public:
enum STATUS
{
IDLE = 0,
READY = 1
};
OBDWiFiComm();
~OBDWiFiComm();
void init(const String &deviceName);
void writeEndOK();
void writeEndERROR();
/**
* Response for unsupported PID command or missing sensor, respond "NO DATA".
*/
void writeEndNoData();
/**
* Response for invalid AT command, or invalid PID or other bad input, respond "?"
*/
void writeEndUnknown();
void setToDefaults();
void readData(String &rxData);
void writeTo(uint8_t cChar);
void writeTo(char const *string);
void setEcho(bool echo);
void writeEnd();
bool isEchoEnable();
void setLineFeeds(bool status);
void setMemory(bool status);
void setWhiteSpaces(bool status);
void setHeaders(bool status);
void setStatus(STATUS status);
void writeEndPidTo(char const *string);
private:
STATUS status; // Operation status
bool echoEnable; // echoEnable command after received
bool lineFeedEnable;
bool memoryEnabled;
bool whiteSpacesEnabled;
bool headersEnabled;
void addSpacesToResponse(const char *response, char string[]);
WiFiClient client;
};
#endif
@@ -1,195 +0,0 @@
#include "PidProcessor.h"
#if USE_WIFI
PidProcessor::PidProcessor(OBDWiFiComm *connection) {
_connection = connection;
resetPidMode01Array();
};
#else
PidProcessor::PidProcessor(OBDSerialComm *connection) {
_connection = connection;
resetPidMode01Array();
};
#endif
bool PidProcessor::process(String& command) {
bool processed = false;
if (command.length() > 4)
{
command = command.substring(0, 4); // remove num_responses value if present; lib only handles single responses
}
if (!isMode01(command) & !isMode22(command))
{
_connection->writeEndNoData();
return false;
}
uint16_t hexCommand = strtoul(command.c_str(), NULL, HEX);
uint8_t pid = getPidCodeFromHex(hexCommand);
if (isSupportedPidRequest(pid)) { //reqeust to return a list of valid PIDs we can respond to
processed = true;
uint32_t supportedPids = getSupportedPids(pid);
writePidResponse(command, 4, supportedPids);
}
return processed;
}
void PidProcessor::writePidResponse(const String& requestPid, uint8_t numberOfBytes, uint32_t value) {
uint8_t nHexChars = PID_N_BYTES * N_CHARS_IN_BYTE + numberOfBytes * N_CHARS_IN_BYTE ;
char responseArray[nHexChars + 1]; // one more for termination char
getFormattedResponse(responseArray, nHexChars, requestPid, value);
_connection->writeEndPidTo(responseArray);
}
/**
* adds a supported pid, so it can answer to pid support request, ex 0100, 0120, ...
*/
bool PidProcessor::registerMode01Pid(uint32_t pid) {
// pid must be off type 01
if (pid > 0x00 && pid < 0x0200) {
// remove PidMode, only use pid code
pid = getPidCodeFromHex(pid);
setPidBit(pid);
char buffer[4];
sprintf(buffer, "%02X", pid);
DEBUG("Registered PID: " + String(buffer ));
return true;
}
return false;
}
bool PidProcessor::isMode01(const String& command)
{
return command.startsWith("01") ? true : false;
}
bool PidProcessor::isMode01MIL(const String& command)
{
return command.startsWith("0101") ? true : false;
}
bool PidProcessor::isMode03(const String& command)
{
return command.startsWith("03") ? true : false;
}
bool PidProcessor::isMode22(const String& command)
{
return command.startsWith("22") ? true : false;
}
/**
* return true if:
* - 1000
* - 1020
* - 1040
* - 1060
* ....
*/
bool PidProcessor::isSupportedPidRequest(uint8_t pid) {
return pid == getPidIntervalId(pid);
}
/**
* returns the interval RANGEID for the given pid (ex: 00, 20, 40, ...)
*/
uint8_t PidProcessor::getPidIntervalId(uint8_t pidcode) {
return getPidIntervalIndex(pidcode) * PID_INTERVAL_OFFSET;
}
/**
* returns the interval INDEX for the given pid (0,1,2,3,4,...)
*/
uint8_t PidProcessor::getPidIntervalIndex(uint8_t pidcode) {
return pidcode / PID_INTERVAL_OFFSET;
}
/**
* Receives a mode pid ex 010C ,
* returns only the pid ex 0C
*/
uint8_t PidProcessor::getPidCodeFromHex(uint16_t hexCommand) {
if (hexCommand >= 0x0100) {
hexCommand -= 0x100;
}
return (uint8_t) hexCommand; // return only the lower 8 bits (trim off num_messages or other extra bytes if present)
}
/**
* Receives a complete PID request,
* returns only the pid ex 0C
*/
uint8_t PidProcessor::getPidCodeFromRequest(const String& request) {
uint16_t hexCommand = strtoul(request.c_str(), NULL, HEX);
return getPidCodeFromHex(hexCommand);
}
void PidProcessor::setPidBit(uint8_t pid) {
uint8_t arrayIndex = getPidIntervalIndex(pid);
if(isSupportedPidRequest(pid)) {
arrayIndex -= 1;
}
uint8_t bitPosition = getPidBitPosition(pid);
bitWrite(pidMode01Supported[arrayIndex], bitPosition, 1);
// mark available intervals
for (int i = 0; i < arrayIndex; i++) {
bitWrite(pidMode01Supported[i], 0, 1);
}
}
uint8_t PidProcessor::getPidBitPosition(uint8_t pid) {
uint8_t intervalId = getPidIntervalId(pid) ;
if(isSupportedPidRequest(pid)) {
intervalId -= PID_INTERVAL_OFFSET;
}
uint16_t pos = pid - intervalId;
pos = PID_INTERVAL_OFFSET - pos;
return pos;
}
uint32_t PidProcessor:: getSupportedPids(uint8_t pid) {
uint8_t index = getPidIntervalIndex(pid);
return pidMode01Supported[index];
}
void PidProcessor::getFormattedResponse(char *response, uint8_t totalNumberOfChars, const String& pid, uint32_t value) {
uint8_t nValueChars = totalNumberOfChars - PID_N_BYTES * N_CHARS_IN_BYTE;
char cValue[3] = {0};
itoa(nValueChars, cValue, DEC);
String mask = "%s%0";
mask.concat(cValue);
mask.concat("lX\0");
String pidResponse = convertToPidResponse(pid);
sprintf(response, mask.c_str(), pidResponse.c_str(), value);
DEBUG("RX PID: " + pid + " - TX: " + String(response));
}
String PidProcessor::convertToPidResponse(String pid) {
String x = "4";
x.concat(pid.substring(1, pid.length()));
return x;
}
void PidProcessor::resetPidMode01Array() {
for(uint8_t i = 0; i < N_MODE01_INTERVALS; i++ ) {
pidMode01Supported[i] = 0x0;
}
}
@@ -1,62 +0,0 @@
#ifndef ELMulator_PIDPROCESSOR_H
#define ELMulator_PIDPROCESSOR_H
#include "definitions.h"
#include <Arduino.h>
#include <WString.h>
#include <Print.h>
#include "OBDWiFiComm.h"
#include "OBDSerialComm.h"
class PidProcessor
{
public:
PidProcessor(OBDWiFiComm *connection);
PidProcessor(OBDSerialComm *connection);
bool process(String &string);
bool registerMode01Pid(uint32_t pid);
bool registerMode01MILResponse(const String &response);
bool registerMode03Response(const String &response);
void writePidResponse(const String &requestPid, uint8_t numberOfBytes, uint32_t value);
uint8_t getPidCodeFromHex(uint16_t hexCommand);
uint8_t getPidCodeFromRequest(const String &command);
bool isMode01(const String &command);
bool isMode01MIL(const String &command);
bool isMode03(const String &command);
bool isMode22(const String &command);
private:
#if USE_WIFI
OBDWiFiComm *_connection;
#else
OBDSerialComm *_connection;
#endif
uint32_t pidMode01Supported[N_MODE01_INTERVALS];
bool isSupportedPidRequest(uint8_t pid);
uint32_t getSupportedPids(uint8_t pidcode);
uint8_t getPidIntervalId(uint8_t pidcode);
uint8_t getPidIntervalIndex(uint8_t pidcode);
void setPidBit(uint8_t pid);
uint8_t getPidBitPosition(uint8_t pidcode);
String convertToPidResponse(String pid);
void getFormattedResponse(char *response, uint8_t totalNumberOfChars, const String &pid, uint32_t value);
void resetPidMode01Array();
};
#endif // ELMulator_PIDPROCESSOR_H
@@ -1,294 +0,0 @@
#ifndef ELMulator_DEFINITIONS_H
#define ELMulator_DEFINITIONS_H
#include <stdint.h>
// true == We are running on hardware that has builtin Bluetooth
// false == We are using hardware that has BT module attached via GPIO pins
#define BLUETOOTH_BUILTIN true
#define USE_WIFI false
#define DO_DEBUG true
#define DEBUG(x) do {if (DO_DEBUG) { Serial.println(x); } } while (0)
#define xtoc(x) ((x < 10) ? ('0' + x) : ('A' - 10 + x))
#define getNumOfHexChars(nBytes) (nBytes * 2)
#define PID_N_BYTES 2
#define N_CHARS_IN_BYTE 2
// Device ID
#define ID "ELM327 / ELMulator V1.3.0"
#define DESC "ELMulator OBD2 Arduino library, based on ELM327 protocol"
#define PROTOCOL "6" // canbus 500k 11 bit protocol id for elm.
// Char representing end of serial string
#define SERIAL_END_CHAR 0x0D
#define SERIAL_READ_TIMEOUT 20000L
#define WIFI_END_CHAR 0x0A
const uint8_t maxPid = 0xFF;
const uint8_t N_MODE01_INTERVALS = 7;
const uint8_t PID_INTERVAL_OFFSET = 0x20;
const uint8_t MAX_REQUEST_SIZE = 40;
//-------------------------------------------------------------------------------------//
// Protocol IDs
//-------------------------------------------------------------------------------------//
const char AUTOMATIC = '0';
const char SAE_J1850_PWM_41_KBAUD = '1';
const char SAE_J1850_PWM_10_KBAUD = '2';
const char ISO_9141_5_BAUD_INIT = '3';
const char ISO_14230_5_BAUD_INIT = '4';
const char ISO_14230_FAST_INIT = '5';
const char ISO_15765_11_BIT_500_KBAUD = '6';
const char ISO_15765_29_BIT_500_KBAUD = '7';
const char ISO_15765_11_BIT_250_KBAUD = '8';
const char ISO_15765_29_BIT_250_KBAUD = '9';
const char SAE_J1939_29_BIT_250_KBAUD = 'A';
const char USER_1_CAN = 'B';
const char USER_2_CAN = 'C';
//-------------------------------------------------------------------------------------//
// PIDs (https://en.wikipedia.org/wiki/OBD-II_PIDs)
//-------------------------------------------------------------------------------------//
const uint8_t SUPPORTED_PIDS_1_20 = 0; // 0x00 - bit encoded
const uint8_t MONITOR_STATUS_SINCE_DTC_CLEARED = 1; // 0x01 - bit encoded
const uint8_t FREEZE_DTC = 2; // 0x02 -
const uint8_t FUEL_SYSTEM_STATUS = 3; // 0x03 - bit encoded
const uint8_t ENGINE_LOAD = 4; // 0x04 - %
const uint8_t ENGINE_COOLANT_TEMP = 5; // 0x05 - °C
const uint8_t SHORT_TERM_FUEL_TRIM_BANK_1 = 6; // 0x06 - %
const uint8_t LONG_TERM_FUEL_TRIM_BANK_1 = 7; // 0x07 - %
const uint8_t SHORT_TERM_FUEL_TRIM_BANK_2 = 8; // 0x08 - %
const uint8_t LONG_TERM_FUEL_TRIM_BANK_2 = 9; // 0x09 - %
const uint8_t FUEL_PRESSURE = 10; // 0x0A - kPa
const uint8_t INTAKE_MANIFOLD_ABS_PRESSURE = 11; // 0x0B - kPa
const uint8_t ENGINE_RPM = 12; // 0x0C - rpm
const uint8_t VEHICLE_SPEED = 13; // 0x0D - km/h
const uint8_t TIMING_ADVANCE = 14; // 0x0E - ° before TDC
const uint8_t INTAKE_AIR_TEMP = 15; // 0x0F - °C
const uint8_t MAF_FLOW_RATE = 16; // 0x10 - g/s
const uint8_t THROTTLE_POSITION = 17; // 0x11 - %
const uint8_t COMMANDED_SECONDARY_AIR_STATUS = 18; // 0x12 - bit encoded
const uint8_t OXYGEN_SENSORS_PRESENT_2_BANKS = 19; // 0x13 - bit encoded
const uint8_t OXYGEN_SENSOR_1_A = 20; // 0x14 - V %
const uint8_t OXYGEN_SENSOR_2_A = 21; // 0x15 - V %
const uint8_t OXYGEN_SENSOR_3_A = 22; // 0x16 - V %
const uint8_t OXYGEN_SENSOR_4_A = 23; // 0x17 - V %
const uint8_t OXYGEN_SENSOR_5_A = 24; // 0x18 - V %
const uint8_t OXYGEN_SENSOR_6_A = 25; // 0x19 - V %
const uint8_t OXYGEN_SENSOR_7_A = 26; // 0x1A - V %
const uint8_t OXYGEN_SENSOR_8_A = 27; // 0x1B - V %
const uint8_t OBD_STANDARDS = 28; // 0x1C - bit encoded
const uint8_t OXYGEN_SENSORS_PRESENT_4_BANKS = 29; // 0x1D - bit encoded
const uint8_t AUX_INPUT_STATUS = 30; // 0x1E - bit encoded
const uint8_t RUN_TIME_SINCE_ENGINE_START = 31; // 0x1F - sec
const uint8_t SUPPORTED_PIDS_21_40 = 32; // 0x20 - bit encoded
const uint8_t DISTANCE_TRAVELED_WITH_MIL_ON = 33; // 0x21 - km
const uint8_t FUEL_RAIL_PRESSURE = 34; // 0x22 - kPa
const uint8_t FUEL_RAIL_GUAGE_PRESSURE = 35; // 0x23 - kPa
const uint8_t OXYGEN_SENSOR_1_B = 36; // 0x24 - ratio V
const uint8_t OXYGEN_SENSOR_2_B = 37; // 0x25 - ratio V
const uint8_t OXYGEN_SENSOR_3_B = 38; // 0x26 - ratio V
const uint8_t OXYGEN_SENSOR_4_B = 39; // 0x27 - ratio V
const uint8_t OXYGEN_SENSOR_5_B = 40; // 0x28 - ratio V
const uint8_t OXYGEN_SENSOR_6_B = 41; // 0x29 - ratio V
const uint8_t OXYGEN_SENSOR_7_B = 42; // 0x2A - ratio V
const uint8_t OXYGEN_SENSOR_8_B = 43; // 0x2B - ratio V
const uint8_t COMMANDED_EGR = 44; // 0x2C - %
const uint8_t EGR_ERROR = 45; // 0x2D - %
const uint8_t COMMANDED_EVAPORATIVE_PURGE = 46; // 0x2E - %
const uint8_t FUEL_TANK_LEVEL_INPUT = 47; // 0x2F - %
const uint8_t WARM_UPS_SINCE_CODES_CLEARED = 48; // 0x30 - count
const uint8_t DIST_TRAV_SINCE_CODES_CLEARED = 49; // 0x31 - km
const uint8_t EVAP_SYSTEM_VAPOR_PRESSURE = 50; // 0x32 - Pa
const uint8_t ABS_BAROMETRIC_PRESSURE = 51; // 0x33 - kPa
const uint8_t OXYGEN_SENSOR_1_C = 52; // 0x34 - ratio mA
const uint8_t OXYGEN_SENSOR_2_C = 53; // 0x35 - ratio mA
const uint8_t OXYGEN_SENSOR_3_C = 54; // 0x36 - ratio mA
const uint8_t OXYGEN_SENSOR_4_C = 55; // 0x37 - ratio mA
const uint8_t OXYGEN_SENSOR_5_C = 56; // 0x38 - ratio mA
const uint8_t OXYGEN_SENSOR_6_C = 57; // 0x39 - ratio mA
const uint8_t OXYGEN_SENSOR_7_C = 58; // 0x3A - ratio mA
const uint8_t OXYGEN_SENSOR_8_C = 59; // 0x3B - ratio mA
const uint8_t CATALYST_TEMP_BANK_1_SENSOR_1 = 60; // 0x3C - °C
const uint8_t CATALYST_TEMP_BANK_2_SENSOR_1 = 61; // 0x3D - °C
const uint8_t CATALYST_TEMP_BANK_1_SENSOR_2 = 62; // 0x3E - °C
const uint8_t CATALYST_TEMP_BANK_2_SENSOR_2 = 63; // 0x3F - °C
const uint8_t SUPPORTED_PIDS_41_60 = 64; // 0x40 - bit encoded
const uint8_t MONITOR_STATUS_THIS_DRIVE_CYCLE = 65; // 0x41 - bit encoded
const uint8_t CONTROL_MODULE_VOLTAGE = 66; // 0x42 - V
const uint8_t ABS_LOAD_VALUE = 67; // 0x43 - %
const uint8_t FUEL_AIR_COMMANDED_EQUIV_RATIO = 68; // 0x44 - ratio
const uint8_t RELATIVE_THROTTLE_POSITION = 69; // 0x45 - %
const uint8_t AMBIENT_AIR_TEMP = 70; // 0x46 - °C
const uint8_t ABS_THROTTLE_POSITION_B = 71; // 0x47 - %
const uint8_t ABS_THROTTLE_POSITION_C = 72; // 0x48 - %
const uint8_t ABS_THROTTLE_POSITION_D = 73; // 0x49 - %
const uint8_t ABS_THROTTLE_POSITION_E = 74; // 0x4A - %
const uint8_t ABS_THROTTLE_POSITION_F = 75; // 0x4B - %
const uint8_t COMMANDED_THROTTLE_ACTUATOR = 76; // 0x4C - %
const uint8_t TIME_RUN_WITH_MIL_ON = 77; // 0x4D - min
const uint8_t TIME_SINCE_CODES_CLEARED = 78; // 0x4E - min
const uint8_t MAX_VALUES_EQUIV_V_I_PRESSURE = 79; // 0x4F - ratio V mA kPa
const uint8_t MAX_MAF_RATE = 80; // 0x50 - g/s
const uint8_t FUEL_TYPE = 81; // 0x51 - ref table
const uint8_t ETHANOL_FUEL_PERCENT = 82; // 0x52 - %
const uint8_t ABS_EVAP_SYS_VAPOR_PRESSURE = 83; // 0x53 - kPa
const uint8_t EVAP_SYS_VAPOR_PRESSURE = 84; // 0x54 - Pa
const uint8_t SHORT_TERM_SEC_OXY_SENS_TRIM_1_3 = 85; // 0x55 - %
const uint8_t LONG_TERM_SEC_OXY_SENS_TRIM_1_3 = 86; // 0x56 - %
const uint8_t SHORT_TERM_SEC_OXY_SENS_TRIM_2_4 = 87; // 0x57 - %
const uint8_t LONG_TERM_SEC_OXY_SENS_TRIM_2_4 = 88; // 0x58 - %
const uint8_t FUEL_RAIL_ABS_PRESSURE = 89; // 0x59 - kPa
const uint8_t RELATIVE_ACCELERATOR_PEDAL_POS = 90; // 0x5A - %
const uint8_t HYBRID_BATTERY_REMAINING_LIFE = 91; // 0x5B - %
const uint8_t ENGINE_OIL_TEMP = 92; // 0x5C - °C
const uint8_t FUEL_INJECTION_TIMING = 93; // 0x5D - °
const uint8_t ENGINE_FUEL_RATE = 94; // 0x5E - L/h
const uint8_t EMISSION_REQUIREMENTS = 95; // 0x5F - bit encoded
const uint8_t SUPPORTED_PIDS_61_80 = 96; // 0x60 - bit encoded
const uint8_t DEMANDED_ENGINE_PERCENT_TORQUE = 97; // 0x61 - %
const uint8_t ACTUAL_ENGINE_TORQUE = 98; // 0x62 - %
const uint8_t ENGINE_REFERENCE_TORQUE = 99; // 0x63 - Nm
const uint8_t ENGINE_PERCENT_TORQUE_DATA = 100; // 0x64 - %
const uint8_t AUX_INPUT_OUTPUT_SUPPORTED = 101; // 0x65 - bit encoded
const uint8_t ODOMETER = 166; // 0xA6 - km * 10
const uint8_t SERVICE_01 = 1;
const uint8_t SERVICE_02 = 2;
const uint8_t SERVICE_03 = 3;
//-------------------------------------------------------------------------------------//
// AT commands (https://www.sparkfun.com/datasheets/Widgets/ELM327_AT_Commands.pdf)
//-------------------------------------------------------------------------------------//
const char * const DISP_DEVICE_DESCRIPT = "AT @1"; // General
const char * const DISP_DEVICE_ID = "AT @2"; // General
const char * const STORE_DEVICE_ID = "AT @3 %s"; // General
const char * const REPEAT_LAST_COMMAND = "AT \r"; // General
const char * const ALLOW_LONG_MESSAGES = "AT AL"; // General
const char * const AUTOMATIC_RECEIVE = "AT AR"; // OBD
const char * const ADAPTIVE_TIMING_OFF = "AT AT0"; // OBD
const char * const ADAPTIVE_TIMING_AUTO_1 = "AT AT1"; // OBD
const char * const ADAPTIVE_TIMING_AUTO_2 = "AT AT2"; // OBD
const char * const DUMP_BUFFER = "AT BD"; // OBD
const char * const BYPASS_INIT_SEQUENCE = "AT BI"; // OBD
const char * const TRY_BAUD_DIVISOR = "AT BRD %02d"; // General
const char * const SET_HANDSHAKE_TIMEOUT = "AT BRT %02d"; // General
const char * const CAN_AUTO_FORMAT_OFF = "AT CAF0"; // CAN
const char * const CAN_AUTO_FORMAT_ON = "AT CAF1"; // CAN
const char * const CAN_EXTENDED_ADDRESS_OFF = "AT CEA"; // CAN
const char * const USE_CAN_EXTENDED_ADDRESS = "AT CEA %02d"; // CAN
const char * const SET_ID_FILTER = "AT CF %s"; // CAN
const char * const CAN_FLOW_CONTROL_OFF = "AT CFC0"; // CAN
const char * const CAN_FLOW_CONTROL_ON = "AT CFC1"; // CAN
const char * const SET_ID_MASK = "AT CM %s"; // CAN
const char * const SET_CAN_PRIORITY = "AT CP %02d"; // CAN
const char * const SHOW_CAN_STATUS = "AT CS"; // CAN
const char * const CAN_SILENT_MODE_OFF = "AT CSM0"; // CAN
const char * const CAN_SILENT_MODE_ON = "AT CSM1"; // CAN
const char * const CALIBRATE_VOLTAGE_CUSTOM = "AT CV %04d"; // Volts
const char * const RESTORE_CV_TO_FACTORY = "AT CV 0000"; // Volts
const char * const SET_ALL_TO_DEFAULTS = "AT D"; // General
const char * const DISP_DLC_OFF = "AT D0"; // CAN
const char * const DISP_DLC_ON = "AT D1"; // CAN
const char * const MONITOR_FOR_DM1_MESSAGES = "AT DM1"; // J1939
const char * const DISP_CURRENT_PROTOCOL = "AT DP"; // OBD
const char * const DISP_CURRENT_PROTOCOL_NUM = "AT DPN"; // OBD
const char * const ECHO_OFF = "AT E0"; // General
const char * const ECHO_ON = "AT E1"; // General
const char * const FLOW_CONTROL_SET_DATA_TO = "AT FC SD %s"; // CAN
const char * const FLOW_CONTROL_SET_HEAD_TO = "AT FC SH %s"; // CAN
const char * const FLOW_CONTROL_SET_MODE_TO = "AT FC SM %c"; // CAN
const char * const FORGE_EVENTS = "AT FE"; // General
const char * const PERFORM_FAST_INIT = "AT FI"; // ISO
const char * const HEADERS_OFF = "AT H0"; // OBD
const char * const HEADERS_ON = "AT H1"; // OBD
const char * const DISP_ID = "AT I"; // General
const char * const SET_ISO_BAUD_10400 = "AT IB 10"; // ISO
const char * const SET_ISO_BAUD_4800 = "AT IB 48"; // ISO
const char * const SET_ISO_BAUD_9600 = "AT IB 96"; // ISO
const char * const IFR_VAL_FROM_HEADER = "AT IFR H"; // J1850
const char * const IFR_VAL_FROM_SOURCE = "AT IFR S"; // J1850
const char * const IFRS_OFF = "AT IFR0"; // J1850
const char * const IFRS_AUTO = "AT IFR1"; // J1850
const char * const IFRS_ON = "AT IFR2"; // J1850
const char * const IREAD_IGNMON_INPUT_LEVEL = "AT IGN"; // Other
const char * const SET_ISO_SLOW_INIT_ADDRESS = "AT IIA %02d"; // ISO
const char * const USE_J1939_ELM_DATA_FORMAT = "AT JE"; // J1850
const char * const J1939_HEAD_FORMAT_OFF = "AT JHF0"; // J1850
const char * const J1939_HEAD_FORMAT_ON = "AT JHF1"; // J1850
const char * const USE_J1939_SAE_DATA_FORMAT = "AT JS"; // J1850
const char * const SET_J1939_TIMER_X_TO_1X = "AT JTM1"; // J1850
const char * const SET_J1939_TIMER_X_TO_5X = "AT JTM5"; // J1850
const char * const DISP_KEY_WORDS = "AT KW"; // ISO
const char * const KEY_WORD_CHECKING_OFF = "AT KW0"; // ISO
const char * const KEY_WORD_CHECKING_ON = "AT KW1"; // ISO
const char * const LINEFEEDS_OFF = "AT L0"; // General
const char * const LINEFEEDS_ON = "AT L1"; // General
const char * const LOW_POWER_MODE = "AT LP"; // General
const char * const MEMORY_OFF = "AT M0"; // General
const char * const MEMORY_ON = "AT M1"; // General
const char * const MONITOR_ALL = "AT MA"; // OBD
const char * const MONITOR_FOR_PGN = "AT MP %s"; // J1939
const char * const MONITOR_FOR_RECEIVER = "AT MR %02d"; // OBD
const char * const MONITOR_FOR_TRANSMITTER = "AT MT %02d"; // OBD
const char * const NORMAL_LENGTH_MESSAGES = "AT NL"; // OBD
const char * const SET_PROTO_OPTIONS_AND_BAUD = "AT PB %s"; // OBD
const char * const PROTOCOL_CLOSE = "AT PC"; // OBD
const char * const ALL_PROG_PARAMS_OFF = "AT PP FF OFF";// PPs
const char * const ALL_PROG_PARAMS_ON = "AT PP FF ON"; // PPs
const char * const SET_PROG_PARAM_OFF = "AT PP %s OFF";// PPs
const char * const SET_PROG_PARAM_ON = "AT PP %s ON"; // PPs
const char * const SET_PROG_PARAM_VAL = "AT PP %s SV %s"; // PPs
const char * const DISP_PP_SUMMARY = "AT PPS"; // PPs
const char * const RESPONSES_OFF = "AT R0"; // OBD
const char * const RESPONSES_ON = "AT R1"; // OBD
const char * const SET_RECEIVE_ADDRESS_TO = "AT RA %02d"; // OBD
const char * const READ_STORED_DATA = "AT RD"; // General
const char * const SEND_RTR_MESSAGE = "AT RTR"; // CAN
const char * const READ_VOLTAGE = "AT RV"; // Volts
const char * const PRINTING_SPACES_OFF = "AT S0"; // OBD
const char * const PRINTING_SPACES_ON = "AT S1"; // OBD
const char * const STORE_DATA_BYTE = "AT SD "; // General
const char * const SET_HEADER = "AT SH %s"; // OBD
const char * const PERFORM_SLOW_INIT = "AT SI"; // ISO
const char * const SET_PROTOCOL_TO_AUTO_H_SAVE = "AT SP A%c"; // OBD
const char * const SET_PROTOCOL_TO_H_SAVE = "AT SP %c"; // OBD
const char * const SET_PROTOCOL_TO_AUTO_SAVE = "AT SP 00"; // OBD
const char * const SET_REC_ADDRESS = "AT SR %02d"; // OBD
const char * const SET_STANDARD_SEARCH_ORDER = "AT SS"; // OBD
const char * const SET_TIMEOUT_TO_H_X_4MS = "AT ST %02d"; // OBD
const char * const SET_WAKEUP_TO_H_X_20MS = "AT SW %02d"; // ISO
const char * const SET_TESTER_ADDRESS_TO = "AT TA %02d"; // OBD
const char * const TRY_PROT_H_AUTO_SEARCH = "AT TP A%c"; // OBD
const char * const TRY_PROT_H = "AT TP %c"; // OBD
const char * const VARIABLE_DLC_OFF = "AT V0"; // CAN
const char * const VARIABLE_DLC_ON = "AT V1"; // CAN
const char * const SET_WAKEUP_MESSAGE = "AT WM"; // ISO
const char * const WARM_START = "AT WS"; // General
const char * const RESET_ALL = "AT Z"; // General
const uint8_t responseBytes[0xA9] =
{
4, 4, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2,
4, 2, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 1, 1, 1, 1, 1, 2, 2, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2,
4, 4, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 4, 4, 1, 1, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 2, 2, 1,
4, 1, 1, 2, 5, 2, 5, 3, 3, 7, 5, 5, 5, 11, 9, 3, 10, 6, 5, 5, 5, 7, 7, 5, 9, 9, 7, 7, 9, 1, 1, 13,
4, 41, 41, 9, 1, 10, 5, 5, 13, 41, 41, 7, 17, 1, 1, 7, 3, 5, 2, 3, 12, 9, 9, 6, 4, 17, 4, 2, 9
};
#endif //ELMulator_DEFINITIONS_H
-21
View File
@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2023 Jim Whitelaw
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation 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
furnished 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 FOR 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.
@@ -1,27 +0,0 @@
; ELMulator — fake ELM327 OBD-II adapter for testing without a car.
; Runs on a CLASSIC ESP32 (Bluetooth Classic SPP). Library lives in lib/ELMulator.
; One env per example: pio run -e simple | custom | wifi
[platformio]
default_envs = simple
[env]
platform = espressif32@6.10.0
board = esp32dev
framework = arduino
board_build.partitions = huge_app.csv
; upload_port = COMxx ; the classic ESP32's port (COM21 is the JC3636W518)
monitor_speed = 115200
monitor_filters = esp32_exception_decoder
; all Mode 01 PIDs answered with mock data, BT name "ELMULATOR"
[env:simple]
build_src_filter = +<simple/>
; custom PID handler + DTC response, BT name "ELMULATOR"
[env:custom]
build_src_filter = +<custom/>
; custom handler, BT name "OBDII" (matches what elm327_wifi_bridge connects to)
[env:wifi]
build_src_filter = +<wifi/>
@@ -1,6 +0,0 @@
#pragma once
#include <Arduino.h>
#include <ELMulator.h>
#include <definitions.h>
void handlePIDRequest(const String& pidRequest);
@@ -1,172 +0,0 @@
#include "ESP32_Custom_ELMulator.h"
const String deviceName = "ELMULATOR"; // Bluetooth device name to use (no pin)
// Set up hardcoded responses to non-standard PID requests for MIL, DTC, ODO data
const String milResponse = "4101830000"; // MIL response code indicating 3 current DTC
const String dtcResponse = "43010341\r\n43010123\r\n43010420"; // DTC response returning 3 DTC codes (multiline response)
const uint32_t odoResponse = 1234567; // Hardcode an odometer reading of 1234567
const String ethPercentResponse = "620052C6";
const String dpfCloggingResponse = "6218E4C6";
ELMulator ELMulator;
void setup()
{
Serial.begin(115200);
Serial.println("Starting ELMulator...");
ELMulator.init(deviceName);
// Register the specific PIDs we are going to handle.
// ELMulator will respond to SUPPORTED_PIDS request ("0101") with the appropriate value
// indicating which PIDs it can respond to.
ELMulator.registerMode01Pid(ODOMETER);
ELMulator.registerMode01Pid(ENGINE_COOLANT_TEMP);
ELMulator.registerMode01Pid(ENGINE_RPM);
ELMulator.registerMode01Pid(MONITOR_STATUS_SINCE_DTC_CLEARED);
ELMulator.registerMode01Pid(INTAKE_MANIFOLD_ABS_PRESSURE);
ELMulator.registerMode01Pid(ENGINE_LOAD);
ELMulator.registerMode01Pid(VEHICLE_SPEED);
ELMulator.registerMode01Pid(INTAKE_AIR_TEMP);
ELMulator.registerMode01Pid(THROTTLE_POSITION);
}
void loop()
{
/**
* Waits for a new request from an ELM client and does some pre-processing to validate the PID and request.
* Non-PID requests like AT commands and SUPPORTED_PIDS queries will be handled by ELMulator automatically.
* PID requests will be verified as supported (registered); unsupported PIDs responded to with NO DATA response.
* Returns true if the request is for one of the PIDS we have registered above so we can respond to it.
*/
if (ELMulator.readELMRequest())
{
handlePIDRequest(ELMulator.elmRequest);
}
}
/**
* After receiving a supported PID(sensor) request, we prepare the response here.
* Response can come from a hardcoded value, a real hardware sensor, or a mock value.
* Default response is a mock value if no other handling is provided.
*/
void handlePIDRequest(const String &request)
{
// Handle special case requests like MIL and DTC checks
if (ELMulator.isMode03(request)) // Mode 03 request == Returns (hardcoded) list current DTC codes
{
if (dtcResponse.length())
{
ELMulator.writeResponse(dtcResponse);
return;
}
else
{
DEBUG("DTC response is empty.");
ELMulator.writePidNotSupported();
return;
}
}
else if (ELMulator.isMode01MIL(request)) // Mode 0101 MIL request == Returns (hardcoded) number of current DTC codes
{
if (milResponse.length())
{
ELMulator.writeResponse(milResponse);
return;
}
else
{
DEBUG("MIL response is empty.");
ELMulator.writePidNotSupported();
return;
}
}
else if (ELMulator.isMode22(request)) // Mode 0x22 (extended info) service - Mfg-dependent and not defined in OBDII spec
{
Serial.print("Mode 22 PID: "); Serial.println(request.substring(2));
if (request.substring(2).compareTo("52") == 0) // Ethanol percent
{
if (ethPercentResponse.length())
{
ELMulator.writeResponse(ethPercentResponse);
return;
}
else
{
DEBUG("ETH % response is empty.");
ELMulator.writePidNotSupported();
return;
}
}
if (request.substring(2).compareTo("18E4") == 0) // DPF Clogging A-R Giulia
{
if (dpfCloggingResponse.length())
{
ELMulator.writeResponse(dpfCloggingResponse);
return;
}
else
{
DEBUG("DPF Clogging response is empty.");
ELMulator.writePidNotSupported();
return;
}
}
ELMulator.writePidNotSupported();
return;
}
else // Handle any other supported PID request
{
uint8_t pidCode = ELMulator.getPidCode(request); // Extract the specific PID code from the request
// Example response for 0x05 (Engine Coolant Temp) - returning a mock data value
if (pidCode == ENGINE_COOLANT_TEMP)
{
uint32_t sensorValue = ELMulator.getMockSensorValue();
/**
* Response parameters:
* request - specifies the original PID request being responded to.
* numberOfBytes - the number of bytes this PID value returns (per OBDII spec), using lookup table responseBytes.
* sensorValue - the value to return in our response; a mock value in this example.
*/
ELMulator.writePidResponse(request, responseBytes[pidCode], sensorValue);
return;
}
// Example response for PID 0x0C (Engine RPM) - returns a modified mock sensor value
else if (pidCode == ENGINE_RPM)
{
uint16_t rpmValue = ELMulator.getMockSensorValue() * 100; // Here we multiply the mock value provided, for a more realistic RPM number
ELMulator.writePidResponse(request, responseBytes[pidCode], rpmValue);
return;
}
// Example response for PID 0xA6 (Odometer) - returns our hardcoded odometer response string
else if (pidCode == ODOMETER)
{
ELMulator.writePidResponse(request, responseBytes[pidCode], odoResponse);
return;
}
// // Example response for PID 0xOD (Vehicle speed) - returns a value from external GPS
// else if (pidCode == VEHICLE_SPEED)
// {
// uint32_t gpsSpeed = myGPS.speed();
// ELMulator.writePidResponse(request, responseBytes[pidCode], gpsSpeed);
// return;
// }
// Default response for any other supported PID request - returns an unmodified mock sensor value
else
{
ELMulator.writePidResponse(request, responseBytes[pidCode], ELMulator.getMockSensorValue());
return;
}
}
}
@@ -1,6 +0,0 @@
#pragma once
#include <Arduino.h>
#include <ELMulator.h>
#include <definitions.h>
void handlePIDRequest(const String& pidRequest);
@@ -1,21 +0,0 @@
#include "ESP32_Simple_ELMulator.h"
/**
* This example demonstrates a very simple implementation of ELMulator.
* It handles all AT Commands, SUPPORTED_PIDS queries, etc. and rejectss unsupported requests.
* All Mode 01 PID requests are provided a response of the correct data size with mock data values.
* Typical use case is for testing custom applications to work with ELM327 devices.
*/
const String deviceName = "ELMULATOR"; // Bluetooth device name to use (no pin)
ELMulator ELMulator;
void setup()
{
Serial.begin(115200);
Serial.println("Starting ELMulator...");
ELMulator.init(deviceName, true); // Initialize with support for all PIDs 0x00 to 0x65, providing mock data
ELMulator.begin(); // ELMulator will run continuously, listening for and respondng to requests
}
void loop() {}
@@ -1,6 +0,0 @@
#pragma once
#include <Arduino.h>
#include <ELMulator.h>
#include <definitions.h>
void handlePIDRequest(const String& pidRequest);
@@ -1,172 +0,0 @@
#include "ESP32_WiFi_Custom_ELMulator.h"
const String deviceName = "OBDII";
// Set up hardcoded responses to non-standard PID requests for MIL, DTC, ODO data
const String milResponse = "4101830000"; // MIL response code indicating 3 current DTC
const String dtcResponse = "43010341\r\n43010123\r\n43010420"; // DTC response returning 3 DTC codes (multiline response)
const uint32_t odoResponse = 1234567; // Hardcode an odometer reading of 1234567
const String ethPercentResponse = "620052C6";
const String dpfCloggingResponse = "6218E4C6";
ELMulator ELMulator;
void setup()
{
Serial.begin(115200);
Serial.println("Starting ELMulator...");
ELMulator.init(deviceName);
// Register the specific PIDs we are going to handle.
// ELMulator will respond to SUPPORTED_PIDS request ("0101") with the appropriate value
// indicating which PIDs it can respond to.
ELMulator.registerMode01Pid(ODOMETER);
ELMulator.registerMode01Pid(ENGINE_COOLANT_TEMP);
ELMulator.registerMode01Pid(ENGINE_RPM);
ELMulator.registerMode01Pid(MONITOR_STATUS_SINCE_DTC_CLEARED);
ELMulator.registerMode01Pid(INTAKE_MANIFOLD_ABS_PRESSURE);
ELMulator.registerMode01Pid(ENGINE_LOAD);
ELMulator.registerMode01Pid(VEHICLE_SPEED);
ELMulator.registerMode01Pid(INTAKE_AIR_TEMP);
ELMulator.registerMode01Pid(THROTTLE_POSITION);
}
void loop()
{
/**
* Waits for a new request from an ELM client and does some pre-processing to validate the PID and request.
* Non-PID requests like AT commands and SUPPORTED_PIDS queries will be handled by ELMulator automatically.
* PID requests will be verified as supported (registered); unsupported PIDs responded to with NO DATA response.
* Returns true if the request is for one of the PIDS we have registered above so we can respond to it.
*/
if (ELMulator.readELMRequest())
{
handlePIDRequest(ELMulator.elmRequest);
}
}
/**
* After receiving a supported PID(sensor) request, we prepare the response here.
* Response can come from a hardcoded value, a real hardware sensor, or a mock value.
* Default response is a mock value if no other handling is provided.
*/
void handlePIDRequest(const String &request)
{
// Handle special case requests like MIL and DTC checks
if (ELMulator.isMode03(request)) // Mode 03 request == Returns (hardcoded) list current DTC codes
{
if (dtcResponse.length())
{
ELMulator.writeResponse(dtcResponse);
return;
}
else
{
DEBUG("DTC response is empty.");
ELMulator.writePidNotSupported();
return;
}
}
else if (ELMulator.isMode01MIL(request)) // Mode 0101 MIL request == Returns (hardcoded) number of current DTC codes
{
if (milResponse.length())
{
ELMulator.writeResponse(milResponse);
return;
}
else
{
DEBUG("MIL response is empty.");
ELMulator.writePidNotSupported();
return;
}
}
else if (ELMulator.isMode22(request)) // Mode 0x22 (extended info) service - Mfg-dependent and not defined in OBDII spec
{
Serial.print("Mode 22 PID: "); Serial.println(request.substring(2));
if (request.substring(2).compareTo("52") == 0) // Ethanol percent
{
if (ethPercentResponse.length())
{
ELMulator.writeResponse(ethPercentResponse);
return;
}
else
{
DEBUG("ETH % response is empty.");
ELMulator.writePidNotSupported();
return;
}
}
if (request.substring(2).compareTo("18E4") == 0) // DPF Clogging A-R Giulia
{
if (dpfCloggingResponse.length())
{
ELMulator.writeResponse(dpfCloggingResponse);
return;
}
else
{
DEBUG("DPF Clogging response is empty.");
ELMulator.writePidNotSupported();
return;
}
}
ELMulator.writePidNotSupported();
return;
}
else // Handle any other supported PID request
{
uint8_t pidCode = ELMulator.getPidCode(request); // Extract the specific PID code from the request
// Example response for 0x05 (Engine Coolant Temp) - returning a mock data value
if (pidCode == ENGINE_COOLANT_TEMP)
{
uint32_t sensorValue = ELMulator.getMockSensorValue();
/**
* Response parameters:
* request - specifies the original PID request being responded to.
* numberOfBytes - the number of bytes this PID value returns (per OBDII spec), using lookup table responseBytes.
* sensorValue - the value to return in our response; a mock value in this example.
*/
ELMulator.writePidResponse(request, responseBytes[pidCode], sensorValue);
return;
}
// Example response for PID 0x0C (Engine RPM) - returns a modified mock sensor value
else if (pidCode == ENGINE_RPM)
{
uint16_t rpmValue = ELMulator.getMockSensorValue() * 100; // Here we multiply the mock value provided, for a more realistic RPM number
ELMulator.writePidResponse(request, responseBytes[pidCode], rpmValue);
return;
}
// Example response for PID 0xA6 (Odometer) - returns our hardcoded odometer response string
else if (pidCode == ODOMETER)
{
ELMulator.writePidResponse(request, responseBytes[pidCode], odoResponse);
return;
}
// // Example response for PID 0xOD (Vehicle speed) - returns a value from external GPS
// else if (pidCode == VEHICLE_SPEED)
// {
// uint32_t gpsSpeed = myGPS.speed();
// ELMulator.writePidResponse(request, responseBytes[pidCode], gpsSpeed);
// return;
// }
// Default response for any other supported PID request - returns an unmodified mock sensor value
else
{
ELMulator.writePidResponse(request, responseBytes[pidCode], ELMulator.getMockSensorValue());
return;
}
}
}
+36
View File
@@ -0,0 +1,36 @@
# OBD Speed (JC3636W518)
Vehicle speed from an ELM327 over BLE, shown as a big number + arc gauge on top of an animated GIF.
Uses the shared board library and LVGL 9.6 (`../shared`), NimBLE-Arduino 2.5.1.
## Use
- **Speed page** — speed in km/h (`--` = no data: not connected, or ignition off), arc gauge 0–200 km/h,
status pill below the number (adapter name when running, otherwise what it is doing).
**Battery voltage** (`ATRV`, every 2 s) as a small arc in the bottom gap (11–15 V) + number; color:
red < 11.8 V, orange < 12.2 V, green < 13.0 V (resting OK), blue ≤ 14.8 V (charging), red above (overcharge).
- **Swipe left → adapter menu** — scans while open. OBD-like adapters (name OBD/ELM/…, or service FFF0/FFE0/18F0)
are listed first in green. **Tap one** to select it: it is remembered (NVS) and connected immediately.
**Forget** clears the remembered adapter, **Done** (or swipe right) returns to the speed page.
- **Swipe left again → settings** — screen rotation 0 / 90 / 180 / 270 degrees, and **screen off without data** Off / 2 / 5 / 10 min (default 2). Both remembered in NVS.
- **Idle** — after that time without speed data (ignition off, adapter gone) the screen goes dark, the GIF pauses and
OBD polling stops; only `ATRV` every 30 s (answered by the adapter itself, doesn't wake the car's bus).
Wakes on a touch (the touch is swallowed) or by itself when the voltage rises above 13.2 V (engine started).
Not used while no adapter is chosen.
- On power-up the remembered adapter is connected automatically; if the link drops or the adapter is not
there (car off), it retries every 3 s. With no adapter remembered, the device starts on the menu page.
## How it talks to the ELM327
Service `FFF0`, notifications + commands on `FFF1` (verified with the user's "OBDII" adapter, see
`docs/2026-10-23_ble_scanner_car_test.txt`, made with the BLE scanner tool that was removed afterwards — in git history); other adapters: first notify+write pair.
Init: `ATZ`, `ATE0`, `ATL0`, `ATS0`, `ATH0`, `ATSP0`, `0100`; then `010D` (speed) is polled continuously.
Serial (115200) prints status changes as `[obd] ...`.
## Files
| File | What |
|---|---|
| `src/main.cpp` | LVGL UI (tileview: speed page + menu page) |
| `src/obd_link.cpp/.h` | BLE scan, connect/reconnect, ELM327 init and speed polling (background task on core 0) |
| `src/font_speed_120.c` | Montserrat Medium 120 px, digits and `-`, 4 bpp (generated with `lv_font_conv`, see header) |
| `data/bg.gif` | Background (`ezgif-5-cb56986bc7.gif`, 360×360), linked into the firmware via `board_build.embed_files` |
Replace `data/bg.gif` with any 360×360 GIF to change the background (keep it under ~3 MB).
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

@@ -0,0 +1,121 @@
===== Inspect 61:69:aa:93:64:c9 (t=154s since boot) =====
(no name)
connect failed, error 13
===== Scan list (t=9s since boot) =====
d8:a7:77:62:cf:2b -87 dBm (no name) [not connectable]
6a:0b:59:2f:28:34 -78 dBm (no name)
02:04:bd:fc:cf:71 -88 dBm (no name) [not connectable]
1e:f4:be:9f:3b:ec -81 dBm (no name) [not connectable]
48:db:b1:48:1c:ba -92 dBm (no name)
71:e4:11:0c:69:31 -92 dBm (no name)
d7:03:e0:21:ae:2c -82 dBm (no name) [not connectable]
c8:49:09:05:72:65 -92 dBm (no name) [not connectable]
50:ed:64:42:4e:1f -93 dBm (no name) [not connectable]
===== Inspect 71:e4:11:0c:69:31 (t=9s since boot) =====
(no name) -92 dBm
SVC 0x1800
0x2a00 [R]
0x2a01 [R]
SVC 0x1801
0x2a05 [I]
SVC 0x180a
0x2a29 [R]
0x2a24 [R]
SVC d0611e78-bbb4-4591-a5f8-487910ae4366
8667556c-9a37-4c91-84ed-54ee27d90049 [W N]
SVC 9fa480e0-4967-4542-9390-d343dc5d04ae
af0badb1-5b99-43cd-917a-a77bc549e3cc [W N]
SVC 0x180f
0x2a19 [R N]
SVC 0x1805
0x2a2b [R N]
0x2a0f [R]
SVC 7905f431-b5ce-4e99-a40f-4b1e122d00d0
69d1d8f3-45e1-49a8-9821-9bbdfdaad9d9 [W]
9fbf120d-6301-42d9-8c58-25e699a21dbd [N]
22eac6e9-24d6-4bb5-be44-b36ace7c7bfb [N]
SVC 89d3502b-0f36-433a-8ef4-c502ad55f8dc
9b3c81d8-57b1-4a8a-b8df-0e56f7ca51c2 [W N]
2f7cabce-808d-411f-9a0c-bb92ba96c102 [W N]
c6b2f38c-23ab-46d8-a6ab-a3a870bbd5d7 [R W]
ELM candidate:
write 8667556c-9a37-4c91-84ed-54ee27d90049
notify 8667556c-9a37-4c91-84ed-54ee27d90049
===== Scan list (t=43s since boot) =====
be:67:00:63:4a:bb -69 dBm ELK-BLEDOM0B
57:d8:bc:03:00:d2 -86 dBm (no name)
11:23:d0:d0:94:76 -87 dBm (no name) [not connectable]
b8:ab:62:f5:df:63 -74 dBm BlackVue970X-EA0D41
58:4b:32:d8:a0:00 -59 dBm (no name) svc: 0xfd5a
5f:20:5f:9e:52:29 -97 dBm (no name) [not connectable]
5c:a0:49:f2:3d:63 -93 dBm (no name) [not connectable]
e3:8d:69:a0:80:9d -90 dBm (no name)
5c:13:59:55:ef:8e -61 dBm (no name) svc: 0xfd5a
6f:9a:f3:43:9e:69 -93 dBm (no name)
65:03:4a:ef:35:58 -59 dBm (no name) svc: 0xfd5a
c6:f4:05:9e:ef:46 -95 dBm (no name) [not connectable]
1c:a1:35:69:8d:c5 -77 dBm OBDII [OBD?] svc: 0xfff0
74:64:f9:df:4c:8e -69 dBm (no name) svc: 0xfd69
53:61:36:06:da:f6 -66 dBm (no name) svc: 0xfd69
===== Inspect 1c:a1:35:69:8d:c5 (t=43s since boot) =====
OBDII -77 dBm
SVC 0x1800
0x2a00 [R]
SVC 0x1801
0x2a05 [I]
SVC 0xfff0
0xfff1 [W WnR N]
0xfff2 [W WnR]
ELM candidate:
write 0xfff1
notify 0xfff1
===== ELM test 1c:a1:35:69:8d:c5 (t=50s since boot) =====
> ATZ (reset)
ATZ
ELM327 v2.1
> ATE0 (echo off)
ATE0
OK
> ATI (version)
ELM327 v2.1
> ATRV (battery voltage)
12.9V
> 0100 (supported PIDs (needs ignition on))
41 00 BE 3E A8 13
===== Scan list (saved by button) (t=54s since boot) =====
be:67:00:63:4a:bb -69 dBm ELK-BLEDOM0B
57:d8:bc:03:00:d2 -87 dBm (no name)
11:23:d0:d0:94:76 -87 dBm (no name) [not connectable]
b8:ab:62:f5:df:63 -71 dBm BlackVue970X-EA0D41
58:4b:32:d8:a0:00 -59 dBm (no name) svc: 0xfd5a
5f:20:5f:9e:52:29 -95 dBm (no name) [not connectable]
5c:a0:49:f2:3d:63 -93 dBm (no name) [not connectable]
e3:8d:69:a0:80:9d -90 dBm (no name)
5c:13:59:55:ef:8e -61 dBm (no name) svc: 0xfd5a
6f:9a:f3:43:9e:69 -93 dBm (no name)
65:03:4a:ef:35:58 -59 dBm (no name) svc: 0xfd5a
c6:f4:05:9e:ef:46 -95 dBm (no name) [not connectable]
1c:a1:35:69:8d:c5 -81 dBm OBDII [OBD?] svc: 0xfff0
74:64:f9:df:4c:8e -69 dBm (no name) svc: 0xfd69
53:61:36:06:da:f6 -66 dBm (no name) svc: 0xfd69
+18
View File
@@ -0,0 +1,18 @@
; OBD speed display: ELM327 over BLE, GIF background, swipe-left menu to pick/remember the adapter.
; JC3636W518, common settings in shared/jc3636w518.ini
[platformio]
extra_configs = ../shared/jc3636w518.ini
[shared]
dir = ../shared
[env:jc3636w518]
extends = jc3636w518
; firmware + 1.6 MB background GIF does not fit huge_app (3 MB): 6.25 MB app slots
board_build.partitions = default_16MB.csv
; background GIF linked into the firmware as-is (symbols _binary_data_bg_gif_start/_end)
board_build.embed_files = data/bg.gif
lib_deps =
${jc3636w518.lib_deps}
h2zero/NimBLE-Arduino@2.5.1
File diff suppressed because it is too large. Load diff
+522
View File
@@ -0,0 +1,522 @@
// OBD speed display for the JC3636W518: vehicle speed from an ELM327 over BLE on top of an animated GIF.
// Swipe left for the adapter menu (scan, select, forget); the selected adapter is remembered and reconnected.
// Swipe left again for settings (screen rotation, idle timeout; remembered).
// Battery voltage is shown as a small arc in the bottom gap; after a while without speed data the
// screen goes dark (idle) and wakes on touch or when the engine starts.
#include <Arduino.h>
#include <Preferences.h>
#include "jc3636w518.h"
#include "obd_link.h"
#define SPEED_MAX 200
#define VOLT_MIN_X10 110 // voltage arc range 11.0 .. 15.0 V
#define VOLT_MAX_X10 150
#define MAX_MENU_ITEMS 30
LV_FONT_DECLARE(font_speed_120);
// background GIF linked into the firmware (board_build.embed_files = data/bg.gif)
extern const uint8_t bg_gif_start[] asm("_binary_data_bg_gif_start");
extern const uint8_t bg_gif_end[] asm("_binary_data_bg_gif_end");
static const lv_color_t COLOR_TEXT = lv_color_hex(0xFFFFFF);
static const lv_color_t COLOR_DIM = lv_color_hex(0x9AA3AD);
static const lv_color_t COLOR_ARC = lv_color_hex(0x00C8FF);
static const lv_color_t COLOR_OBD = lv_color_hex(0x4CD964);
static const lv_color_t COLOR_PANEL = lv_color_hex(0x161C22);
static lv_obj_t *tileview, *tile_speed, *tile_menu, *tile_settings;
static lv_obj_t *rot_buttons[4], *idle_buttons[4];
static const uint8_t IDLE_MINUTES[4] = {0, 2, 5, 10}; // 0 = never
static lv_obj_t *speed_arc, *speed_label, *speed_shadow, *status_label;
static lv_obj_t *volt_arc, *volt_label, *bg_gif, *idle_overlay;
static lv_obj_t *menu_saved, *menu_status, *menu_list;
static lv_obj_t *menu_items[MAX_MENU_ITEMS];
static int menu_item_count = 0;
static std::vector<ObdDevInfo> scan_devs; // as received from obd_link
static std::vector<ObdDevInfo> menu_devs; // display order: OBD-like first
static uint32_t scan_version = 0;
static String menu_order; // addresses in display order (detects reordering)
// ---------- helpers ----------
static lv_obj_t *make_button(lv_obj_t *parent, const char *text, int x, int y, int w, lv_event_cb_t cb)
{
lv_obj_t *b = lv_button_create(parent);
lv_obj_set_size(b, w, 34);
lv_obj_align(b, LV_ALIGN_TOP_MID, x, y);
lv_obj_add_event_cb(b, cb, LV_EVENT_CLICKED, NULL);
lv_obj_t *l = lv_label_create(b);
lv_label_set_text(l, text);
lv_obj_set_style_text_font(l, &lv_font_montserrat_16, 0);
lv_obj_center(l);
return b;
}
static void show_tile(lv_obj_t *tile)
{
lv_tileview_set_tile(tileview, tile, LV_ANIM_ON);
}
static bool menu_visible()
{
return lv_tileview_get_tile_active(tileview) == tile_menu;
}
// ---------- events ----------
static void on_tile_changed(lv_event_t *e)
{
obd_scan_enable(menu_visible()); // scan only while the menu is open
}
static void on_device_clicked(lv_event_t *e)
{
int idx = (int)(intptr_t)lv_event_get_user_data(e);
if (idx < 0 || idx >= (int)menu_devs.size())
return;
obd_select_device(menu_devs[idx]);
show_tile(tile_speed);
}
static void on_forget_clicked(lv_event_t *e)
{
obd_forget_device();
}
static void on_done_clicked(lv_event_t *e)
{
show_tile(tile_speed);
}
// ---------- settings: screen rotation ----------
static uint8_t load_rotation()
{
Preferences p;
p.begin("ui", true);
uint8_t r = p.getUChar("rot", 0);
p.end();
return r > 3 ? 0 : r;
}
static void highlight_rotation(uint8_t rot)
{
for (int i = 0; i < 4; i++)
lv_obj_set_style_bg_color(rot_buttons[i], i == rot ? COLOR_ARC : lv_color_hex(0x222A33), 0);
}
static void on_rotation_clicked(lv_event_t *e)
{
uint8_t rot = (uint8_t)(intptr_t)lv_event_get_user_data(e);
setRotation(rot);
highlight_rotation(rot);
Preferences p;
p.begin("ui", false);
p.putUChar("rot", rot);
p.end();
}
// ---------- settings: idle timeout ----------
static uint8_t load_idle_minutes()
{
Preferences p;
p.begin("ui", true);
uint8_t m = p.getUChar("idle", 2);
p.end();
return m;
}
static void highlight_idle(uint8_t minutes)
{
for (int i = 0; i < 4; i++)
lv_obj_set_style_bg_color(idle_buttons[i], IDLE_MINUTES[i] == minutes ? COLOR_ARC : lv_color_hex(0x222A33), 0);
}
static void on_idle_clicked(lv_event_t *e)
{
uint8_t minutes = IDLE_MINUTES[(int)(intptr_t)lv_event_get_user_data(e)];
obd_set_idle_timeout(minutes);
highlight_idle(minutes);
Preferences p;
p.begin("ui", false);
p.putUChar("idle", minutes);
p.end();
}
static void on_idle_overlay_pressed(lv_event_t *e)
{
obd_wake(); // touch while the screen is dark: wake up (the touch itself is swallowed)
}
// ---------- speed page ----------
static void build_speed_tile()
{
// background GIF, decoded straight into the display format (no transparency needed on black)
static lv_image_dsc_t gif_dsc;
gif_dsc.header.magic = LV_IMAGE_HEADER_MAGIC;
gif_dsc.header.cf = LV_COLOR_FORMAT_RAW;
gif_dsc.header.w = bg_gif_start[6] | (bg_gif_start[7] << 8);
gif_dsc.header.h = bg_gif_start[8] | (bg_gif_start[9] << 8);
gif_dsc.data_size = bg_gif_end - bg_gif_start;
gif_dsc.data = bg_gif_start;
bg_gif = lv_gif_create(tile_speed);
lv_gif_set_color_format(bg_gif, lv_display_get_color_format(lv_display_get_default()));
lv_gif_set_src(bg_gif, &gif_dsc);
lv_obj_center(bg_gif);
// arc gauge along the round edge: 270° sweep, gap at the bottom
speed_arc = lv_arc_create(tile_speed);
lv_obj_set_size(speed_arc, 344, 344);
lv_obj_center(speed_arc);
lv_arc_set_rotation(speed_arc, 135);
lv_arc_set_bg_angles(speed_arc, 0, 270);
lv_arc_set_range(speed_arc, 0, SPEED_MAX);
lv_arc_set_value(speed_arc, 0);
lv_obj_remove_style(speed_arc, NULL, LV_PART_KNOB);
lv_obj_remove_flag(speed_arc, LV_OBJ_FLAG_CLICKABLE); // let swipes reach the tileview
lv_obj_set_style_arc_width(speed_arc, 12, LV_PART_MAIN);
lv_obj_set_style_arc_width(speed_arc, 12, LV_PART_INDICATOR);
lv_obj_set_style_arc_color(speed_arc, lv_color_hex(0xFFFFFF), LV_PART_MAIN);
lv_obj_set_style_arc_opa(speed_arc, LV_OPA_30, LV_PART_MAIN);
lv_obj_set_style_arc_color(speed_arc, COLOR_ARC, LV_PART_INDICATOR);
// battery voltage arc in the bottom gap of the speed arc, filling left to right
volt_arc = lv_arc_create(tile_speed);
lv_obj_set_size(volt_arc, 344, 344);
lv_obj_center(volt_arc);
lv_arc_set_rotation(volt_arc, 45);
lv_arc_set_bg_angles(volt_arc, 8, 82); // small margin to the speed arc ends
lv_arc_set_mode(volt_arc, LV_ARC_MODE_REVERSE); // starts at the left end (bottom-left)
lv_arc_set_range(volt_arc, VOLT_MIN_X10, VOLT_MAX_X10);
lv_arc_set_value(volt_arc, VOLT_MIN_X10);
lv_obj_remove_style(volt_arc, NULL, LV_PART_KNOB);
lv_obj_remove_flag(volt_arc, LV_OBJ_FLAG_CLICKABLE);
lv_obj_set_style_arc_width(volt_arc, 12, LV_PART_MAIN);
lv_obj_set_style_arc_width(volt_arc, 12, LV_PART_INDICATOR);
lv_obj_set_style_arc_color(volt_arc, lv_color_hex(0xFFFFFF), LV_PART_MAIN);
lv_obj_set_style_arc_opa(volt_arc, LV_OPA_30, LV_PART_MAIN);
volt_label = lv_label_create(tile_speed);
lv_obj_set_style_text_font(volt_label, &lv_font_montserrat_16, 0);
lv_obj_set_style_text_color(volt_label, COLOR_DIM, 0);
lv_label_set_text(volt_label, "-- V");
lv_obj_align(volt_label, LV_ALIGN_BOTTOM_MID, 0, -30);
// speed number with a soft drop shadow so it stays readable on the GIF
speed_shadow = lv_label_create(tile_speed);
lv_obj_set_style_text_font(speed_shadow, &font_speed_120, 0);
lv_obj_set_style_text_color(speed_shadow, lv_color_hex(0x000000), 0);
lv_obj_set_style_text_opa(speed_shadow, LV_OPA_70, 0);
lv_label_set_text(speed_shadow, "--");
lv_obj_align(speed_shadow, LV_ALIGN_CENTER, 4, -6);
speed_label = lv_label_create(tile_speed);
lv_obj_set_style_text_font(speed_label, &font_speed_120, 0);
lv_obj_set_style_text_color(speed_label, COLOR_TEXT, 0);
lv_label_set_text(speed_label, "--");
lv_obj_align(speed_label, LV_ALIGN_CENTER, 0, -10);
lv_obj_t *unit = lv_label_create(tile_speed);
lv_obj_set_style_text_font(unit, &lv_font_montserrat_20, 0);
lv_obj_set_style_text_color(unit, COLOR_TEXT, 0);
lv_label_set_text(unit, "km/h");
lv_obj_align(unit, LV_ALIGN_CENTER, 0, 62);
// status pill at the bottom
status_label = lv_label_create(tile_speed);
lv_obj_set_style_text_font(status_label, &lv_font_montserrat_14, 0);
lv_obj_set_style_text_color(status_label, COLOR_TEXT, 0);
lv_obj_set_style_bg_color(status_label, lv_color_hex(0x000000), 0);
lv_obj_set_style_bg_opa(status_label, LV_OPA_50, 0);
lv_obj_set_style_radius(status_label, 12, 0);
lv_obj_set_style_pad_hor(status_label, 10, 0);
lv_obj_set_style_pad_ver(status_label, 3, 0);
lv_label_set_text(status_label, "");
lv_obj_align(status_label, LV_ALIGN_CENTER, 0, 100);
}
// ---------- menu page ----------
static void build_menu_tile()
{
lv_obj_t *title = lv_label_create(tile_menu);
lv_obj_set_style_text_font(title, &lv_font_montserrat_20, 0);
lv_obj_set_style_text_color(title, COLOR_TEXT, 0);
lv_label_set_text(title, "OBD adapter");
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 20);
menu_saved = lv_label_create(tile_menu);
lv_obj_set_style_text_color(menu_saved, COLOR_TEXT, 0);
lv_obj_set_width(menu_saved, 240);
lv_label_set_long_mode(menu_saved, LV_LABEL_LONG_MODE_DOTS);
lv_obj_set_style_text_align(menu_saved, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(menu_saved, LV_ALIGN_TOP_MID, 0, 48);
menu_status = lv_label_create(tile_menu);
lv_obj_set_style_text_color(menu_status, COLOR_DIM, 0);
lv_obj_set_width(menu_status, 260);
lv_label_set_long_mode(menu_status, LV_LABEL_LONG_MODE_DOTS);
lv_obj_set_style_text_align(menu_status, LV_TEXT_ALIGN_CENTER, 0);
lv_obj_align(menu_status, LV_ALIGN_TOP_MID, 0, 68);
menu_list = lv_list_create(tile_menu);
lv_obj_set_size(menu_list, 276, 196);
lv_obj_align(menu_list, LV_ALIGN_TOP_MID, 0, 92);
lv_obj_set_style_bg_color(menu_list, COLOR_PANEL, 0);
lv_obj_set_style_border_width(menu_list, 0, 0);
lv_obj_set_style_pad_all(menu_list, 4, 0);
make_button(tile_menu, "Forget", -50, 296, 92, on_forget_clicked);
make_button(tile_menu, "Done", 50, 296, 92, on_done_clicked);
}
static void build_settings_tile()
{
lv_obj_t *title = lv_label_create(tile_settings);
lv_obj_set_style_text_font(title, &lv_font_montserrat_20, 0);
lv_obj_set_style_text_color(title, COLOR_TEXT, 0);
lv_label_set_text(title, "Settings");
lv_obj_align(title, LV_ALIGN_TOP_MID, 0, 20);
lv_obj_t *label = lv_label_create(tile_settings);
lv_obj_set_style_text_font(label, &lv_font_montserrat_16, 0);
lv_obj_set_style_text_color(label, COLOR_DIM, 0);
lv_label_set_text(label, "Screen rotation (degrees)");
lv_obj_align(label, LV_ALIGN_TOP_MID, 0, 54);
static const char *names[4] = {"0", "90", "180", "270"};
for (int i = 0; i < 4; i++)
{
lv_obj_t *b = lv_button_create(tile_settings);
lv_obj_set_size(b, 96, 40);
lv_obj_align(b, LV_ALIGN_TOP_MID, (i % 2) ? 52 : -52, 80 + (i / 2) * 46);
lv_obj_add_event_cb(b, on_rotation_clicked, LV_EVENT_CLICKED, (void *)(intptr_t)i);
lv_obj_t *l = lv_label_create(b);
lv_label_set_text(l, names[i]);
lv_obj_set_style_text_font(l, &lv_font_montserrat_20, 0);
lv_obj_center(l);
rot_buttons[i] = b;
}
highlight_rotation(getRotation());
lv_obj_t *idle_label = lv_label_create(tile_settings);
lv_obj_set_style_text_font(idle_label, &lv_font_montserrat_16, 0);
lv_obj_set_style_text_color(idle_label, COLOR_DIM, 0);
lv_label_set_text(idle_label, "Screen off without data (min)");
lv_obj_align(idle_label, LV_ALIGN_TOP_MID, 0, 180);
static const char *idle_names[4] = {"Off", "2", "5", "10"};
for (int i = 0; i < 4; i++)
{
lv_obj_t *b = lv_button_create(tile_settings);
lv_obj_set_size(b, 58, 40);
lv_obj_align(b, LV_ALIGN_TOP_MID, -99 + i * 66, 206);
lv_obj_add_event_cb(b, on_idle_clicked, LV_EVENT_CLICKED, (void *)(intptr_t)i);
lv_obj_t *l = lv_label_create(b);
lv_label_set_text(l, idle_names[i]);
lv_obj_set_style_text_font(l, &lv_font_montserrat_16, 0);
lv_obj_center(l);
idle_buttons[i] = b;
}
highlight_idle(load_idle_minutes());
make_button(tile_settings, "Done", 0, 296, 110, on_done_clicked);
}
static void build_ui()
{
lv_obj_set_style_bg_color(lv_screen_active(), lv_color_hex(0x000000), 0);
tileview = lv_tileview_create(lv_screen_active());
lv_obj_set_style_bg_color(tileview, lv_color_hex(0x000000), 0);
lv_obj_set_scrollbar_mode(tileview, LV_SCROLLBAR_MODE_OFF);
tile_speed = lv_tileview_add_tile(tileview, 0, 0, LV_DIR_RIGHT);
tile_menu = lv_tileview_add_tile(tileview, 1, 0, (lv_dir_t)(LV_DIR_LEFT | LV_DIR_RIGHT));
tile_settings = lv_tileview_add_tile(tileview, 2, 0, LV_DIR_LEFT);
lv_obj_add_event_cb(tileview, on_tile_changed, LV_EVENT_VALUE_CHANGED, NULL);
build_speed_tile();
build_menu_tile();
build_settings_tile();
idle_overlay = lv_obj_create(lv_layer_top());
lv_obj_remove_style_all(idle_overlay);
lv_obj_set_size(idle_overlay, SCREEN_RES_HOR, SCREEN_RES_VER);
lv_obj_add_flag(idle_overlay, LV_OBJ_FLAG_CLICKABLE);
lv_obj_add_event_cb(idle_overlay, on_idle_overlay_pressed, LV_EVENT_PRESSED, NULL);
lv_obj_add_flag(idle_overlay, LV_OBJ_FLAG_HIDDEN);
}
// ---------- periodic updates ----------
static lv_color_t voltage_color(float v)
{
if (v < 0)
return COLOR_DIM;
if (v < 11.8f)
return lv_color_hex(0xFF3B30); // red: battery low
if (v < 12.2f)
return lv_color_hex(0xFF9500); // orange: getting weak
if (v < 13.0f)
return lv_color_hex(0x4CD964); // green: resting, OK
if (v <= 14.8f)
return lv_color_hex(0x0A84FF); // blue: charging (engine running)
return lv_color_hex(0xFF3B30); // red: overcharging
}
static void update_voltage()
{
static int shown_x10 = -2;
float v = obd_voltage();
int x10 = v < 0 ? -1 : (int)(v * 10 + 0.5f);
if (x10 == shown_x10)
return;
shown_x10 = x10;
lv_color_t c = voltage_color(v);
lv_arc_set_value(volt_arc, x10 < 0 ? VOLT_MIN_X10 : constrain(x10, VOLT_MIN_X10, VOLT_MAX_X10));
lv_obj_set_style_arc_color(volt_arc, c, LV_PART_INDICATOR);
char buf[12];
if (x10 < 0)
strcpy(buf, "-- V");
else
snprintf(buf, sizeof(buf), "%d.%d V", x10 / 10, x10 % 10);
lv_label_set_text(volt_label, buf);
lv_obj_set_style_text_color(volt_label, c, 0);
}
static void update_idle()
{
static bool shown_idle = false;
bool idle = obd_is_idle();
if (idle == shown_idle)
return;
shown_idle = idle;
if (idle)
{
// screen off, animation paused, touches only wake up
lv_tileview_set_tile(tileview, tile_speed, LV_ANIM_OFF);
lv_gif_pause(bg_gif);
lv_obj_remove_flag(idle_overlay, LV_OBJ_FLAG_HIDDEN);
screen_switch(false);
}
else
{
lv_gif_resume(bg_gif);
lv_obj_add_flag(idle_overlay, LV_OBJ_FLAG_HIDDEN);
screen_switch(true);
}
}
static void update_speed()
{
static int shown = -2;
static String shown_status;
int v = obd_speed();
if (v != shown)
{
shown = v;
char buf[8];
if (v < 0)
strcpy(buf, "--");
else
snprintf(buf, sizeof(buf), "%d", v);
lv_label_set_text(speed_label, buf);
lv_label_set_text(speed_shadow, buf);
lv_arc_set_value(speed_arc, v < 0 ? 0 : min(v, SPEED_MAX));
}
String st = obd_status_text();
if (st != shown_status)
{
shown_status = st;
lv_label_set_text(status_label, st.c_str());
}
}
static void update_menu()
{
String saved = obd_saved_name();
String addr = obd_saved_address();
String line = addr.length() ? "Saved: " + (saved.length() ? saved : String("(no name)")) + " " + addr
: String("No adapter saved - tap one below");
if (line != lv_label_get_text(menu_saved))
lv_label_set_text(menu_saved, line.c_str());
String st = obd_status_text();
if (st != lv_label_get_text(menu_status))
lv_label_set_text(menu_status, st.c_str());
if (!obd_get_devices(scan_devs, scan_version))
return;
// display order: OBD-like adapters first, then the rest (each in first-seen order)
menu_devs.clear();
for (auto &d : scan_devs)
if (d.obd_hint)
menu_devs.push_back(d);
for (auto &d : scan_devs)
if (!d.obd_hint)
menu_devs.push_back(d);
if ((int)menu_devs.size() > MAX_MENU_ITEMS)
menu_devs.resize(MAX_MENU_ITEMS);
String order;
for (auto &d : menu_devs)
order += d.address;
if (order != menu_order)
{
// order changed (new device or new OBD hint): rebuild the list
menu_order = order;
lv_obj_clean(menu_list);
menu_item_count = 0;
for (int i = 0; i < (int)menu_devs.size(); i++)
{
lv_obj_t *btn = lv_list_add_button(menu_list, NULL, "");
lv_obj_add_event_cb(btn, on_device_clicked, LV_EVENT_CLICKED, (void *)(intptr_t)i);
lv_obj_set_style_bg_color(btn, lv_color_hex(0x222A33), 0);
lv_obj_set_style_pad_ver(btn, 6, 0);
menu_items[i] = btn;
menu_item_count = i + 1;
}
}
for (int i = 0; i < menu_item_count; i++)
{
const ObdDevInfo &d = menu_devs[i];
lv_obj_t *label = lv_obj_get_child_by_type(menu_items[i], 0, &lv_label_class);
String text = (d.name.length() ? d.name : String("(no name)")) + "\n" + d.address + " " + d.rssi + " dBm";
if (text != lv_label_get_text(label))
{
lv_label_set_text(label, text.c_str());
lv_obj_set_style_text_color(label, d.obd_hint ? COLOR_OBD : COLOR_TEXT, 0);
}
}
}
void setup()
{
delay(200);
Serial.begin(115200);
scr_lvgl_init();
setRotation(load_rotation());
build_ui();
obd_set_idle_timeout(load_idle_minutes());
obd_begin();
if (obd_saved_address().length() == 0)
{
// first start: go straight to the adapter menu
lv_tileview_set_tile(tileview, tile_menu, LV_ANIM_OFF);
obd_scan_enable(true);
}
}
void loop()
{
static uint32_t last_speed = 0, last_menu = 0;
lv_timer_handler();
uint32_t now = millis();
if (now - last_speed >= 100)
{
last_speed = now;
update_speed();
update_voltage();
update_idle();
}
if (now - last_menu >= 500 && menu_visible())
{
last_menu = now;
update_menu();
}
vTaskDelay(1);
}
+490
View File
@@ -0,0 +1,490 @@
#include "obd_link.h"
#include <NimBLEDevice.h>
#include <Preferences.h>
#define RETRY_DELAY_MS 3000
#define IDLE_RETRY_DELAY_MS 30000
#define POLL_FAIL_LIMIT 3
#define VOLTAGE_INTERVAL_MS 2000 // ATRV while driving
#define IDLE_VOLTAGE_INTERVAL_MS 30000 // ATRV while idle (adapter-internal, doesn't wake the car's bus)
#define WAKE_VOLTAGE 13.2f // above this the alternator is charging: engine running
// ---------- shared state (protected by `mtx`) ----------
static SemaphoreHandle_t mtx;
static volatile ObdState state = ObdState::NoDevice;
static volatile int speed_kmh = -1;
static volatile float voltage = -1;
static volatile bool idle = false;
static volatile uint32_t idle_timeout_ms = 2 * 60 * 1000;
static volatile uint32_t last_data_ms = 0; // last valid speed (or wake-up)
static String status_text = "starting";
static String saved_addr, saved_name;
static uint8_t saved_type = 0;
static std::vector<ObdDevInfo> devices;
static uint32_t devices_version = 1;
static String elm_rx; // bytes received from the ELM (notifications)
// ---------- requests from the UI ----------
static volatile bool req_select = false, req_forget = false, req_wake = false;
static volatile bool scan_wanted = false;
static ObdDevInfo req_dev;
// ---------- worker-owned ----------
static NimBLEClient *client = nullptr;
static NimBLERemoteCharacteristic *chr_write = nullptr, *chr_notify = nullptr;
static Preferences prefs;
static void lock() { xSemaphoreTake(mtx, portMAX_DELAY); }
static void unlock() { xSemaphoreGive(mtx); }
static void set_status(const String &s)
{
lock();
bool changed = status_text != s;
status_text = s;
unlock();
if (changed)
Serial.println("[obd] " + s);
}
// Services that cheap BLE OBD adapters use for their serial (UART-like) link
static const uint16_t ELM_SERVICE_HINTS[] = {0xFFF0, 0xFFE0, 0x18F0};
static bool name_looks_like_obd(String n)
{
n.toUpperCase();
return n.indexOf("OBD") >= 0 || n.indexOf("ELM") >= 0 || n.indexOf("VLINK") >= 0 ||
n.indexOf("V-LINK") >= 0 || n.indexOf("VEEPEAK") >= 0 || n.indexOf("VGATE") >= 0;
}
// ---------- scanning (menu) ----------
class ScanCallbacks : public NimBLEScanCallbacks
{
void onResult(const NimBLEAdvertisedDevice *adv) override
{
if (!adv->isConnectable())
return;
String addr = adv->getAddress().toString().c_str();
String name = adv->haveName() ? String(adv->getName().c_str()) : String();
bool hint = name_looks_like_obd(name);
for (uint8_t i = 0; i < adv->getServiceUUIDCount(); i++)
for (uint16_t h : ELM_SERVICE_HINTS)
if (adv->getServiceUUID(i).equals(NimBLEUUID(h)))
hint = true;
lock();
ObdDevInfo *d = nullptr;
for (auto &e : devices)
if (e.address == addr)
d = &e;
if (!d)
{
devices.push_back(ObdDevInfo());
d = &devices.back();
d->address = addr;
d->obd_hint = false;
}
d->addr_type = adv->getAddress().getType();
if (name.length())
d->name = name;
d->rssi = adv->getRSSI();
d->obd_hint = d->obd_hint || hint;
devices_version++;
unlock();
}
};
static ScanCallbacks scan_callbacks;
static void scan_set(bool on)
{
NimBLEScan *scan = NimBLEDevice::getScan();
if (on && !scan->isScanning())
{
scan->setScanCallbacks(&scan_callbacks, true);
scan->setActiveScan(true);
scan->setInterval(100);
scan->setWindow(90);
scan->start(0, false, true);
}
else if (!on && scan->isScanning())
{
scan->stop();
}
}
// ---------- ELM327 communication ----------
static void on_notify(NimBLERemoteCharacteristic *c, uint8_t *data, size_t len, bool is_notify)
{
lock();
for (size_t i = 0; i < len; i++)
elm_rx += (char)data[i];
unlock();
}
// Sends a command, waits for the '>' prompt. Returns the reply without prompt/CR, or "" on timeout.
static String elm_command(const char *cmd, uint32_t timeout_ms)
{
lock();
elm_rx = "";
unlock();
String line = String(cmd) + "\r";
if (!chr_write->writeValue((const uint8_t *)line.c_str(), line.length(), !chr_write->canWriteNoResponse()))
return "";
uint32_t t0 = millis();
String rx;
while (millis() - t0 < timeout_ms)
{
lock();
rx = elm_rx;
unlock();
if (rx.indexOf('>') >= 0)
{
rx.replace(">", "");
rx.replace("\r", "\n");
rx.trim();
return rx;
}
if (!client->isConnected())
return "";
vTaskDelay(pdMS_TO_TICKS(5));
}
return "";
}
// "410D3C" (spaces/headers removed by ATS0/ATH0; multiple ECUs possible) -> 60
static int parse_speed(String reply)
{
reply.replace(" ", "");
reply.toUpperCase();
int i = reply.indexOf("410D");
if (i < 0 || reply.length() < (unsigned)i + 6)
return -1;
return (int)strtol(reply.substring(i + 4, i + 6).c_str(), nullptr, 16);
}
// "12.6V" -> 12.6, -1 if not a voltage
static float parse_voltage(const String &reply)
{
int v = reply.indexOf('V');
if (v <= 0)
return -1;
float f = reply.substring(0, v).toFloat();
return f > 0 ? f : -1;
}
static void read_voltage()
{
float v = parse_voltage(elm_command("ATRV", 1500));
if (v > 0)
voltage = v;
}
// ---------- connection ----------
static void disconnect()
{
chr_write = chr_notify = nullptr;
if (client && client->isConnected())
client->disconnect();
speed_kmh = -1;
}
// Finds the ELM UART characteristics: service FFF0 (verified: FFF1 notify + write), else any notify+write pair
static bool find_characteristics()
{
chr_write = chr_notify = nullptr;
NimBLERemoteService *svc = client->getService(NimBLEUUID((uint16_t)0xFFF0));
if (svc)
{
chr_notify = svc->getCharacteristic(NimBLEUUID((uint16_t)0xFFF1));
if (chr_notify && (chr_notify->canWrite() || chr_notify->canWriteNoResponse()))
chr_write = chr_notify;
else
chr_write = svc->getCharacteristic(NimBLEUUID((uint16_t)0xFFF2));
}
if (!chr_write || !chr_notify)
{
chr_write = chr_notify = nullptr;
for (NimBLERemoteService *s : client->getServices(true))
{
NimBLERemoteCharacteristic *w = nullptr, *n = nullptr;
for (NimBLERemoteCharacteristic *c : s->getCharacteristics(true))
{
if (!n && (c->canNotify() || c->canIndicate()))
n = c;
if (!w && (c->canWrite() || c->canWriteNoResponse()))
w = c;
}
if (w && n)
{
chr_write = w;
chr_notify = n;
break;
}
}
}
return chr_write && chr_notify;
}
static bool connect_and_init()
{
bool was_scanning = NimBLEDevice::getScan()->isScanning();
scan_set(false); // NimBLE can't connect while scanning
state = ObdState::Connecting;
set_status("Connecting " + (saved_name.length() ? saved_name : saved_addr));
if (!client)
{
client = NimBLEDevice::createClient();
client->setConnectTimeout(8000);
}
bool ok = client->connect(NimBLEAddress(std::string(saved_addr.c_str()), saved_type));
if (was_scanning && scan_wanted)
scan_set(true);
if (!ok)
{
set_status("Adapter not found");
return false;
}
if (!find_characteristics() || !chr_notify->subscribe(chr_notify->canNotify(), on_notify))
{
set_status("Not an ELM adapter?");
disconnect();
return false;
}
state = ObdState::Initializing;
set_status("Initializing ELM327");
if (elm_command("ATZ", 4000).length() == 0)
{
set_status("ELM327 not responding");
disconnect();
return false;
}
// echo off, no linefeeds, no spaces, no headers, automatic protocol
const char *init[] = {"ATE0", "ATL0", "ATS0", "ATH0", "ATSP0"};
for (const char *c : init)
elm_command(c, 2000);
set_status("Searching protocol");
elm_command("0100", 10000); // the first OBD request triggers the protocol search
state = ObdState::Running;
return true;
}
// ---------- worker task ----------
static void load_saved()
{
prefs.begin("obd", true);
saved_addr = prefs.getString("addr", "");
saved_name = prefs.getString("name", "");
saved_type = prefs.getUChar("type", 0);
prefs.end();
}
static void worker_task(void *)
{
uint32_t next_attempt = 0;
uint32_t next_voltage = 0;
int fails = 0;
last_data_ms = millis();
for (;;)
{
if (req_wake)
{
req_wake = false;
if (idle)
Serial.println("[obd] wake (touch)");
idle = false;
last_data_ms = millis();
next_attempt = 0;
}
// no speed data for a while (ignition off / adapter gone): go idle
if (!idle && idle_timeout_ms && millis() - last_data_ms > idle_timeout_ms)
{
idle = true;
speed_kmh = -1;
Serial.println("[obd] idle");
}
// requests from the UI
if (req_forget)
{
req_forget = false;
disconnect();
prefs.begin("obd", false);
prefs.clear();
prefs.end();
lock();
saved_addr = saved_name = "";
unlock();
}
if (req_select)
{
req_select = false;
disconnect();
prefs.begin("obd", false);
prefs.putString("addr", req_dev.address);
prefs.putString("name", req_dev.name);
prefs.putUChar("type", req_dev.addr_type);
prefs.end();
lock();
saved_addr = req_dev.address;
saved_name = req_dev.name;
saved_type = req_dev.addr_type;
unlock();
next_attempt = 0;
}
if (state != ObdState::Connecting) // never scan during a connect attempt
scan_set(scan_wanted);
if (saved_addr.length() == 0)
{
state = ObdState::NoDevice;
idle = false;
last_data_ms = millis(); // no idle while no adapter is chosen (user is setting up)
set_status("No adapter - swipe left");
vTaskDelay(pdMS_TO_TICKS(100));
continue;
}
if (!client || !client->isConnected() || !chr_write)
{
if (state == ObdState::Running)
set_status("Connection lost");
speed_kmh = -1;
if ((int32_t)(millis() - next_attempt) < 0)
{
state = ObdState::Waiting;
vTaskDelay(pdMS_TO_TICKS(50));
continue;
}
fails = 0;
if (!connect_and_init())
{
disconnect();
state = ObdState::Waiting;
next_attempt = millis() + (idle ? IDLE_RETRY_DELAY_MS : RETRY_DELAY_MS);
}
next_voltage = 0;
continue;
}
if (idle)
{
// only the battery voltage now and then; an engine start (charging voltage) wakes us up
if ((int32_t)(millis() - next_voltage) >= 0)
{
next_voltage = millis() + IDLE_VOLTAGE_INTERVAL_MS;
read_voltage();
if (voltage > WAKE_VOLTAGE)
{
idle = false;
last_data_ms = millis();
Serial.printf("[obd] wake (%.1f V)\n", voltage);
}
}
vTaskDelay(pdMS_TO_TICKS(100));
continue;
}
if ((int32_t)(millis() - next_voltage) >= 0)
{
next_voltage = millis() + VOLTAGE_INTERVAL_MS;
read_voltage();
}
// connected: poll the vehicle speed
String reply = elm_command("010D", 1500);
int v = parse_speed(reply);
speed_kmh = v;
if (v >= 0)
{
fails = 0;
last_data_ms = millis();
set_status(saved_name.length() ? saved_name : saved_addr);
}
else if (reply.length() == 0)
{
// no prompt at all: adapter hung or link dead
if (++fails >= POLL_FAIL_LIMIT)
{
set_status("Adapter not answering");
disconnect();
next_attempt = millis() + RETRY_DELAY_MS;
}
}
else
{
// "NO DATA", "UNABLE TO CONNECT", "STOPPED"...: ignition off or ECU busy
fails = 0;
String r = reply;
r.replace("\n", " ");
set_status(r.substring(0, 24));
vTaskDelay(pdMS_TO_TICKS(500));
}
}
}
// ---------- public API ----------
void obd_begin()
{
mtx = xSemaphoreCreateMutex();
load_saved();
NimBLEDevice::init("JC3636W518-OBD");
NimBLEDevice::setPower(9);
xTaskCreatePinnedToCore(worker_task, "obd_link", 8192, nullptr, 2, nullptr, 0);
}
ObdState obd_state() { return state; }
int obd_speed() { return speed_kmh; }
float obd_voltage() { return voltage; }
bool obd_is_idle() { return idle; }
void obd_wake() { req_wake = true; }
void obd_set_idle_timeout(uint32_t minutes) { idle_timeout_ms = minutes * 60 * 1000; }
String obd_status_text()
{
lock();
String s = status_text;
unlock();
return s;
}
String obd_saved_name()
{
lock();
String s = saved_name;
unlock();
return s;
}
String obd_saved_address()
{
lock();
String s = saved_addr;
unlock();
return s;
}
void obd_scan_enable(bool on) { scan_wanted = on; }
bool obd_get_devices(std::vector<ObdDevInfo> &out, uint32_t &version)
{
lock();
bool changed = version != devices_version;
if (changed)
{
out = devices;
version = devices_version;
}
unlock();
return changed;
}
void obd_select_device(const ObdDevInfo &dev)
{
req_dev = dev;
req_select = true;
}
void obd_forget_device() { req_forget = true; }
+47
View File
@@ -0,0 +1,47 @@
// ELM327-over-BLE link: remembers the selected adapter, auto-connects/reconnects and polls the vehicle speed.
// Runs in a background task; all functions are safe to call from the UI (loop) task.
#pragma once
#include <Arduino.h>
#include <vector>
enum class ObdState
{
NoDevice, // no adapter selected yet
Connecting, // BLE connect in progress
Initializing, // ELM327 init (ATZ, protocol search)
Running, // polling speed
Waiting, // connect/init failed or link lost; retrying shortly
};
struct ObdDevInfo
{
String address;
uint8_t addr_type;
String name;
int rssi;
bool obd_hint; // looks like an OBD adapter (name or service FFF0/FFE0/18F0)
};
void obd_begin();
ObdState obd_state();
int obd_speed(); // km/h, -1 = no data (ignition off / not connected)
String obd_status_text(); // short human-readable status line
float obd_voltage(); // battery voltage from ATRV (V), -1 = unknown
// Idle: after `minutes` without speed data (0 = never) polling stops; only ATRV every 30 s.
// Leaves idle by itself when the voltage shows the engine running (> 13.2 V), or via obd_wake().
void obd_set_idle_timeout(uint32_t minutes);
bool obd_is_idle();
void obd_wake();
String obd_saved_name(); // "" if none
String obd_saved_address();
// Menu: scan for adapters while the menu is open
void obd_scan_enable(bool on);
bool obd_get_devices(std::vector<ObdDevInfo> &out, uint32_t &version);
// Select (and remember) an adapter, or forget the remembered one
void obd_select_device(const ObdDevInfo &dev);
void obd_forget_device();
+35 -14
View File
@@ -45,10 +45,12 @@ USB : D-=19 D+=20
- PlatformIO IDE extension is installed in VS Code. CLI: `%USERPROFILE%\.platformio\penv\Scripts\pio.exe` (Core 6.2.0, not on PATH).
- **JC3636W518 (ESP32-S3)**: platform `https://github.com/pioarduino/platform-espressif32/releases/download/51.03.05/platform-espressif32.zip` (pioarduino = Arduino core 3.0.5; the official PIO espressif32 only has core 2.x, which ESP32_Display_Panel 0.1.4 can't use).
Board `esp32-s3-devkitc-1` + `board_build.arduino.memory_type = qio_opi`, `flash_mode = qio`, `board_upload.flash_size = 16MB`, `partitions = huge_app.csv`, `-DBOARD_HAS_PSRAM -DARDUINO_USB_CDC_ON_BOOT=1`, upload/monitor COM21 @115200.
Libraries: `lib_extra_dirs` → vendor `2-Program Example/Demo_Arduino/library`.
**LVGL config:** `-DLV_CONF_INCLUDE_SIMPLE -Iinclude` — the `-Iinclude` is required, otherwise the LVGL library compiles without the project's `lv_conf.h` (symptom: `undefined reference to lv_gif_create`).
- **Classic ESP32** projects (bridge, ELMulator): `espressif32@6.10.0` (core 2.0.17), board `esp32dev`, `huge_app.csv`.
- Template for a new JC3636W518 project: copy `10-OBD-Display-2024/display_speed_wifi/platformio.ini` (+ `include/pincfg.h`, `include/scr_st77916.h`, `include/lv_conf.h`).
**All of this lives in `shared/jc3636w518.ini`**; a project's `platformio.ini` only does `extra_configs` + `[shared] dir` + `extends = jc3636w518` (see `shared/README.md`).
Libraries: `lib_deps = lvgl/lvgl@9.6.0`, `lib_extra_dirs` → `shared/lib` (board library) + vendor `2-Program Example/Demo_Arduino/library` (ESP32_Display_Panel, ESP32_IO_Expander; its lvgl 8 is not used).
**LVGL config:** `-DLV_CONF_INCLUDE_SIMPLE -I${shared.dir}/include` — the `-I` is required, otherwise the LVGL library compiles without `lv_conf.h` (symptom: `undefined reference to lv_gif_create`).
- (Classic ESP32 projects — the 2024 BT-Classic→WiFi bridge and ELMulator — were removed 2026-10-23; they used `espressif32@6.10.0`, board `esp32dev`. In git history.)
- **New JC3636W518 project:** follow `shared/README.md` (3-line platformio.ini + `#include "jc3636w518.h"`).
- Editing files with scripts: keep each file's line endings (most are LF, some `main.cpp` are CRLF) — Python `open(..., newline="")`.
- When converting `.ino` → `main.cpp`: add `#include <Arduino.h>` and prototypes for functions used before their definition; don't `#include` `.c` files that are also in `src/`.
## Software stack used by the vendor demo
@@ -66,17 +68,19 @@ Board **ESP32S3 Dev Module**, USB CDC On Boot **Enabled**, CPU 240 MHz, Flash **
Partition **Huge APP (3MB No OTA/1MB SPIFFS)**, PSRAM **OPI PSRAM**, Upload Mode UART0/Hardware CDC, Upload speed 921600.
If upload fails: hold BOOT (GPIO0) while plugging in / resetting.
## Display driver pattern (`scr_st77916.h`)
`scr_lvgl_init()` does everything: LEDC backlight → CST816S on I2C @400 kHz → QSPI bus @50 MHz → `ESP_PanelLcd_ST77916(bus, 16bpp, RST)` → `invertColor(true)` → LVGL draw buffer of **72 rows** (360×72×2 = ~52 KB, internal RAM) → flush callback with DMA-done callback → touch indev.
Helpers: `setRotation(0..3)`, `set_brightness(0..100)`, `screen_switch(bool)`.
Note: `scr_lvgl_init()` already calls `lv_init()`; the demo `.ino` calls it again (redundant).
## Display driver pattern (shared board library `shared/lib/JC3636W518`, was the vendor `scr_st77916.h`)
`scr_lvgl_init()` does everything: LEDC backlight → CST816S on I2C @400 kHz → QSPI bus @**80 MHz** (vendor: 50) → `ESP_PanelLcd_ST77916(bus, 16bpp, RST)` → `invertColor(true)` → `lv_init()` + tick → LVGL display with **two 72-row draw buffers** (2 × ~52 KB internal RAM) → flush callback with DMA-done callback → touch indev. Returns the `lv_display_t*`.
Helpers: `setRotation(0..3)` / `getRotation()` (panel rotates via MADCTL swap/mirror like the vendor table; touch converted in software in `touchpad_read`; **verified by the user at all 4 angles**, 2026-10-23), `set_brightness(0..100)`, `screen_switch(bool)`; `perf_monitor.h` prints FPS/refresh/other/CPU once per second.
Display color format is `RGB565_SWAPPED` (ST77916 byte order, set in `lv_conf.h`).
## Folder contents
| Folder | What it is |
|---|---|
| `1-Instructions` | Spec PDF (duplicate), AIDA64 remote-sensor layout `aida_remote_1.85.rslcd`, MJPEG conversion tool (Windows .NET, converts video to 360×360 MJPEG 25 fps) |
| `2-Program Example/Demo_Arduino` | **Vendor only:** `library/` (3 libs), `lv_conf.h` (vendor original), `sd/` (SD listing example; its lv_conf.h has FATFS edits), `docs/` (setup screenshots + compile instructions). The vendor's original LVGL demo sketch no longer exists unmodified. |
| `10-OBD-Display-2024` | **Earlier OBD speed-display project**, split into separate sketches — see its `README.md`: `display_speed_wifi`, `display_gif_sd`, `display_gif_embedded`, `elm327_wifi_bridge` (classic ESP32), `elmulator`, `assets/gifs`, `_archive/lvgl9_broken` |
| `shared` | **Shared code for all JC3636W518 projects:** `jc3636w518.ini` (PlatformIO base config), `include/lv_conf.h` (LVGL 9.6), `lib/JC3636W518` (board library). See its `README.md` |
| `12-OBD-Speed` | **The new program — working in the car (2026-10-23):** speed from the ELM327 over BLE as big number + arc gauge on a GIF background, battery-voltage arc in the bottom gap; swipe left for the adapter menu (scan, select → remembered in NVS, auto-reconnect every 3 s), again for settings (rotation, idle/screen-off timeout). See its `README.md` |
| `10-GIF-Tests` | **GIF playback experiments** (was `10-OBD-Display-2024`): `display_gif_embedded`, `display_gif_sd`, `assets/gifs`. The 2024 bridge/ELMulator/WiFi speed sketch, the broken LVGL 9 archive and the BLE scanner tool (`11-BLE-Scanner`: on-screen scan, GATT listing, ELM test, LittleFS log) were removed 2026-10-23 — all in git history |
| `3-Specification` | Spec sheet (factory firmware features, interface photo) |
| `4-Chip manual` | ESP32-S3R8 datasheet, PCM5100A datasheet |
| `5-Dimension` | Mechanical drawings (JPG + DWG) |
@@ -89,12 +93,20 @@ Note: `scr_lvgl_init()` already calls `lv_init()`; the demo `.ino` calls it agai
AIDA64 secondary screen (18 styles), mic spectrum, MP3 player (`/music` on SD), photo frame (`/pic`, 360×360 JPEG), MJPEG player (`/mjpeg`), weather, theme clocks, WiFi provisioning AP "My App"/12345678 + web config at device IP.
Re-flash it with Flash Download Tool: ESP32-S3, `1.85_demo.bin` @ 0x0.
## Car battery / idle
Cheap ELM327 clones draw ~20–50 mA permanently (OBD pin 16 = always 12 V); the display ~180 mA if its USB stays powered. `12-OBD-Speed` idle mode stops OBD polling after N min without data so the car's ECUs can sleep; `ATRV` (adapter-internal) every 30 s detects an engine start (> 13.2 V).
## OBD adapter (user's)
AliExpress "ELM327 Mini V2.1 Bluetooth OBD **Dual Mode** 5.1" — dual mode = Bluetooth Classic SPP **and BLE**, so the ESP32-S3 should be able to connect directly over BLE (no bridge ESP32).
Cheap dual-mode clones typically expose BLE service `FFF0` (notify `FFF1`, write `FFF2`) — **not yet verified** on this adapter; scan it first. Adapter is powered from the car's OBD port (pin 16, 12 V).
**Verified in the car (2026-10-23, with the since-removed BLE scanner tool; log in `12-OBD-Speed/docs/2026-10-23_ble_scanner_car_test.txt`):**
- BLE name **`OBDII`**, MAC `1c:a1:35:69:8d:c5`, advertises service **`0xFFF0`**. Characteristics: **`FFF1` [W, WnR, Notify]**, **`FFF2` [W, WnR]**.
Working setup: subscribe to notifications on `FFF1`, write commands (`"...␍"`) to `FFF1` (write without response) — `FFF2` is the usual write characteristic on such clones and should work too.
- Replies: `ATZ` → `ELM327 v2.1` (echo on by default → send `ATE0`), `ATRV` → `12.9V`, `0100` → `41 00 BE 3E A8 13`.
- **Supported PIDs 01–20** (from `BE 3E A8 13`): 01 monitor status, 03 fuel system, 04 engine load, 05 coolant temp, 06/07 fuel trim B1, **0B intake manifold pressure (MAP)**, **0C RPM**, **0D speed**, 0E timing advance, 0F intake air temp, 11 throttle, 13 O2 sensors, 15 O2 sensor 2, 1C OBD standard, 1F run time, 20 → more PIDs in 21–40. **No 10 (MAF)** → speed-density engine.
- Adapter is powered from the car's OBD port (pin 16, 12 V).
## Gotchas / observations
- **LVGL GIF bug (patched locally):** LVGL 8.3.11 `gifdec.c` (`gd_open_gif`) filled the canvas *opaque* with the GIF's background color. GIFs with a transparent background (e.g. `loligod.gif`, bg = RGB 28,29,49) showed that dark gray except where "restore to background" frames had cleared to transparent (→ screen black). **Patched** in the vendor copy `2-Program Example/Demo_Arduino/library/lvgl/src/extra/libs/gif/gifdec.c`: initial canvas alpha 0x00 (marked `LOCAL PATCH`). Re-apply if LVGL is ever replaced/upgraded.
- **LVGL 8 GIF bug (patched locally, only relevant if LVGL 8 is used again):** LVGL 8.3.11 `gifdec.c` (`gd_open_gif`) filled the canvas *opaque* with the GIF's background color. GIFs with a transparent background (e.g. `loligod.gif`, bg = RGB 28,29,49) showed that dark gray except where "restore to background" frames had cleared to transparent (→ screen black). **Patched** in the vendor copy `2-Program Example/Demo_Arduino/library/lvgl/src/extra/libs/gif/gifdec.c`: initial canvas alpha 0x00 (marked `LOCAL PATCH`). Re-apply if LVGL is ever replaced/upgraded.
- Most GIFs in `assets/gifs` are optimized (transparent unchanged pixels, disposal 1/2); `loligod.gif` has a fully transparent background (~84 % of each frame).
- **GIF performance** (360×360 loligod, embedded, measured with a `monitor_cb` FPS counter in `display_gif_embedded`):
baseline 15.4 fps (decode 27 ms + refresh 31 ms) → no LVGL log / no per-loop prints: no change → `vTaskDelay(1)`: 16.5 → **double draw buffer** (2×72 rows internal RAM): 19.5 (refresh 21 ms) → **`-O2`** instead of `-Os`: **20.6 fps** (refresh 19 ms, decode ~24 ms). GIF's own rate is 23.7 fps. From SD card it was only ~1.4 fps.
@@ -102,9 +114,18 @@ Cheap dual-mode clones typically expose BLE service `FFF0` (notify `FFF1`, write
- **SD card (user's 487 MB SDSC)** intermittently fails to init (`sdmmc_init_ocr ... 0x107`) after resets; once stuck only a power cycle helps (card VDD is hard-wired to 3V3). Recommend a newer SDHC card.
- Reading boot logs: USB-CDC — reset via DTR=0/RTS pulse, then reopen the port (it re-enumerates).
- **LVGL 9.6.0 port** (`display_gif_embedded`, 2026-10-23): **23.4 fps** (= the GIF's own 23.7 fps; decode ~12 ms, refresh ~24 ms) vs 20.6 fps tuned LVGL 8.3. LVGL 9.6 replaced gifdec with bitbank2 AnimatedGIF (2× faster decode, correct transparency — no local patch needed).
Port notes: `lib_deps = lvgl/lvgl@9.6.0` (takes priority over the vendor lvgl 8 in `lib_extra_dirs`); `lv_conf.h` generated from the 9.6 template (CLIB malloc, refresh 15 ms, GIF on, examples off); in 9.6 public headers live in `include/lvgl/` and `LV_COLOR_DEPTH` is deprecated → `LV_COLOR_FORMAT_DEFAULT` (RGB565); no LV_COLOR_16_SWAP → `lv_draw_rgb565_swap()` in the flush cb; no LV_TICK_CUSTOM → `lv_tick_set_cb(millis)`; no `monitor_cb` → display events `LV_EVENT_REFR_START/RENDER_START/REFR_READY`; images are `lv_image_dsc_t` with `LV_IMAGE_HEADER_MAGIC`. `lv_gif_set_color_format()` can switch the GIF buffer from ARGB8888 to RGB565 (untested).
- LVGL version: the other JC3636W518 projects use the vendor-bundled **LVGL 8.3.11**. LVGL 9 would require porting `scr_st77916.h` (display/touch driver glue).
Port notes: `lib_deps = lvgl/lvgl@9.6.0` (takes priority over the vendor lvgl 8 in `lib_extra_dirs`); `lv_conf.h` generated from the 9.6 template (CLIB malloc, refresh 15 ms, GIF on, examples off); in 9.6 public headers live in `include/lvgl/` and `LV_COLOR_DEPTH` is deprecated → `LV_COLOR_FORMAT_DEFAULT` (RGB565); no LV_COLOR_16_SWAP → `lv_draw_rgb565_swap()` in the flush cb; no LV_TICK_CUSTOM → `lv_tick_set_cb(millis)`; no `monitor_cb` → display events `LV_EVENT_REFR_START/RENDER_START/REFR_READY`; images are `lv_image_dsc_t` with `LV_IMAGE_HEADER_MAGIC`. `lv_gif_set_color_format()` switches the GIF buffer format (used, see below).
- **LVGL 9 tuning** (display_gif_embedded, CPU at full 23.8 fps): start 86 % → GIF in display color format 66 % → render `RGB565_SWAPPED` (no swap pass) 60 % → QSPI 80 MHz 55 % → IRAM for hot LVGL code 53 %. No effect: `LV_GIF_CACHE_DECODE_DATA` (not used anywhere in 9.6), 120-row buffers. AnimatedGIF "turbo" mode can't be used: it only outputs in COOKED draw mode, LVGL uses RAW.
- **All JC3636W518 LVGL projects now use LVGL 9.6.0 + the shared library** (10-GIF-Tests/*, 12-OBD-Speed). SD drive `A:` = `LV_USE_FS_STDIO` on `/sdcard/`.
- 80 MHz QSPI and RGB565_SWAPPED rendering: **visually confirmed OK by the user** (colors correct, no glitches).
- Display is round: corners of the 360×360 framebuffer are not visible — design UI within a 360 px circle.
- Compiling a 13.9 MB GIF into the firmware will not fit "Huge APP" (3 MB); large media should live on SD (or in a custom partition).
- The top-level demo `setup()` returns early if no SD card is present, before showing anything.
- `display_gif_sd` `setup()` returns early if the SD card fails to mount (black screen); `display_gif_embedded` starts its GIF first.
- Draw buffer is in internal RAM; PSRAM is available for larger buffers / images.
- **LittleFS** works on the `spiffs` partition of `huge_app.csv` (~900 KB); the first mount after flashing formats it (prints `Corrupted dir pair` / `mount failed (-84)` and some `task_wdt ... task not found` once — harmless).
- BLE (NimBLE) in the user's home: ~12 devices visible within 10 s.
- Big fonts: generate with `npx lv_font_conv` (Node 22 installed) from `lvgl/scripts/generators/built_in_font/Montserrat-Medium.ttf`; `-DLV_LVGL_H_INCLUDE_SIMPLE` (in shared ini) makes the generated `#include` work.
- Binary assets (GIFs): `board_build.embed_files = data/x.gif` → `extern const uint8_t x_start[] asm("_binary_data_x_gif_start")` (no huge C arrays).
- Python edit scripts: never pass C strings containing `\n` through a bash heredoc into Python — it turned into a real line break twice; write the script with the Write tool instead.
- Serial output from the background task (core 0) is often lost, not only right after reset; don't rely on it as proof. Serial output right after a reset can be lost/garbled (USB-CDC re-enumeration, prints from two cores interleave) — don't conclude a task isn't running from a missing early line.
- `Preferences` prints `nvs_open failed: NOT_FOUND` the first time a namespace is read before it was written — harmless.
+60
View File
@@ -0,0 +1,60 @@
# Shared JC3636W518 code
Everything every JC3636W518 project needs, in one place:
| Path | What |
|---|---|
| `jc3636w518.ini` | Common PlatformIO settings: pioarduino 51.03.05 (Arduino core 3.0.5), ESP32-S3 16 MB QIO / OPI PSRAM, `huge_app.csv`, `-O2`, LVGL 9.6.0, vendor display libraries, COM21 |
| `include/lv_conf.h` | LVGL 9.6 config (generated from the 9.6 template; every change is marked `JC3636W518`) |
| `include/lv_attr_jc3636w518.h` | LVGL attribute overrides: hot functions and `lv_display_flush_ready()` in IRAM |
| `lib/JC3636W518/` | Board library: `jc3636w518.h` (display/touch/backlight init + LVGL glue), `pincfg.h` (pin map), `perf_monitor.h` (FPS/CPU monitor) |
## New project
```ini
; platformio.ini
[platformio]
extra_configs = ../shared/jc3636w518.ini ; path to this folder
[shared]
dir = ../shared ; same path
[env:jc3636w518]
extends = jc3636w518
; project-specific overrides, e.g. board_build.partitions = default_16MB.csv
```
```cpp
#include <Arduino.h>
#include "jc3636w518.h"
#include "perf_monitor.h" // optional
void setup() {
Serial.begin(115200);
lv_display_t *disp = scr_lvgl_init(); // also does lv_init()
perf_monitor_begin(disp); // optional
// ... create LVGL UI ...
}
void loop() {
perf_timer_handler(); // or lv_timer_handler()
vTaskDelay(1);
perf_monitor_print(); // optional: FPS / refresh / other / CPU once per second
}
```
Add `-DJC_DRAW_BUF_ROWS=<n>` to `build_flags` (as `${jc3636w518.build_flags}` + extra line) to change the draw buffer height (default 72).
## Performance settings (measured with `display_gif_embedded`, 360×360 GIF at 23.7 fps)
| Setting | Effect |
|---|---|
| LVGL 9.6 (AnimatedGIF decoder) | GIF decode 24 → 12 ms per frame vs LVGL 8.3 |
| Two 72-row draw buffers in internal RAM | render while the previous chunk is sent (120 rows: no gain) |
| `LV_COLOR_FORMAT_DEFAULT = RGB565_SWAPPED` | renders in the ST77916 byte order → no swap pass in flush |
| QSPI 80 MHz (was 50) | faster transfer to the panel |
| `-O2`, IRAM for hot LVGL functions | faster rendering/decoding |
| GIF in display color format (`lv_gif_set_color_format`) | per sketch, only when the GIF needs no transparency (plain background) |
Result: CPU for the GIF 86 % → 53 % at full frame rate.
## Display / LVGL facts
- Display format is `RGB565_SWAPPED`. For images/canvases you create yourself, use `lv_display_get_color_format(disp)` or ARGB8888 when transparency is needed.
- LVGL drive `A:` = SD card (`LV_USE_FS_STDIO`, path `/sdcard/`); mount the card with `SD_MMC` first.
- `LV_USE_STDLIB_MALLOC = CLIB`: LVGL uses `malloc`, so large allocations land in PSRAM. `lv_mem_monitor()` reports 0 in this mode.
+6 -6
View File
@@ -1016,13 +1016,13 @@
#define LV_FONT_MONTSERRAT_14 1
/** Montserrat 16 */
#define LV_FONT_MONTSERRAT_16 0
#define LV_FONT_MONTSERRAT_16 1 /* JC3636W518 */
/** Montserrat 18 */
#define LV_FONT_MONTSERRAT_18 0
/** Montserrat 20 */
#define LV_FONT_MONTSERRAT_20 0
#define LV_FONT_MONTSERRAT_20 1 /* JC3636W518 */
/** Montserrat 22 */
#define LV_FONT_MONTSERRAT_22 0
@@ -1957,17 +1957,17 @@
#define LV_FS_DEFAULT_DRIVER_LETTER 0
/** stdio */
#define LV_USE_FS_STDIO 0
#define LV_USE_FS_STDIO 1 /* JC3636W518: 'A:' = SD card (SD_MMC mount /sdcard) */
#if LV_USE_FS_STDIO
/** Drive letter for stdio (65 = 'A', 0 = disabled) */
#define LV_FS_STDIO_LETTER 0
#define LV_FS_STDIO_LETTER 'A'
/** Working directory for stdio */
#define LV_FS_STDIO_PATH ""
#define LV_FS_STDIO_PATH "/sdcard/"
/** Read cache size in bytes for stdio (0 = disabled) */
#define LV_FS_STDIO_CACHE_SIZE 0
#define LV_FS_STDIO_CACHE_SIZE 16384 /* JC3636W518: fewer small SD reads (GIF decoding) */
#endif /*LV_USE_FS_STDIO*/
+2
View File
@@ -28,6 +28,8 @@ build_flags =
-DARDUINO_USB_CDC_ON_BOOT=1
; LVGL (also when compiled as a library) uses shared/include/lv_conf.h
-DLV_CONF_INCLUDE_SIMPLE
; generated LVGL fonts/images include "lvgl.h" directly
-DLV_LVGL_H_INCLUDE_SIMPLE
-I${shared.dir}/include
; shared/lib: JC3636W518 board library
+36 -17
View File
@@ -272,11 +272,15 @@ IRAM_ATTR bool onRefreshFinishCallback(void *user_data)
return false;
}
static uint8_t rotation = 0; // 0..3, clockwise in 90° steps
// The panel rotates the image itself (MADCTL swap/mirror, same combinations as the vendor code);
// touch points are converted in touchpad_read() so they always match what is shown.
void setRotation(uint8_t rot)
{
if (rot > 3)
return;
if (lcd == NULL || touch == NULL)
if (lcd == NULL)
return;
switch (rot)
@@ -285,35 +289,31 @@ void setRotation(uint8_t rot)
lcd->swapXY(true);
lcd->mirrorX(true);
lcd->mirrorY(false);
touch->swapXY(true);
touch->mirrorX(true);
touch->mirrorY(false);
break;
case 2:
case 2: // 180°
lcd->swapXY(false);
lcd->mirrorX(true);
lcd->mirrorY(true);
touch->swapXY(false);
touch->mirrorX(true);
touch->mirrorY(true);
break;
case 3:
case 3: // 270° clockwise
lcd->swapXY(true);
lcd->mirrorX(false);
lcd->mirrorY(true);
touch->swapXY(true);
touch->mirrorX(false);
touch->mirrorY(true);
break;
default:
lcd->swapXY(false);
lcd->mirrorX(false);
lcd->mirrorY(false);
touch->swapXY(false);
touch->mirrorX(false);
touch->mirrorY(false);
break;
}
rotation = rot;
if (disp)
lv_obj_invalidate(lv_display_get_screen_active(disp)); // redraw everything in the new orientation
}
uint8_t getRotation()
{
return rotation;
}
void screen_switch(bool on)
@@ -341,8 +341,27 @@ static void touchpad_read(lv_indev_t *indev, lv_indev_data_t *data)
int read_touch_result = tp->readPoints(&point, 1);
if (read_touch_result > 0)
{
data->point.x = point.x;
data->point.y = point.y;
// physical panel coordinates -> coordinates of the rotated image
const int max = SCREEN_RES_HOR - 1; // square display
switch (rotation)
{
case 1:
data->point.x = point.y;
data->point.y = max - point.x;
break;
case 2:
data->point.x = max - point.x;
data->point.y = max - point.y;
break;
case 3:
data->point.x = max - point.y;
data->point.y = point.x;
break;
default:
data->point.x = point.x;
data->point.y = point.y;
break;
}
data->state = LV_INDEV_STATE_PRESSED;
}
else
+2 -1
View File
@@ -17,8 +17,9 @@
// Initializes backlight, touch, LCD and LVGL (lv_init, tick, display, touch input). Returns the display.
lv_display_t *scr_lvgl_init();
// 0 = normal, 1 = 90° clockwise, 2 = 180°, 3 = 270° (LCD and touch together)
// 0 = normal, 1 = 90° clockwise, 2 = 180°, 3 = 270° (LCD and touch together; call after scr_lvgl_init)
void setRotation(uint8_t rot);
uint8_t getRotation();
void screen_switch(bool on);