/
bortsov
/
ch32v203-BatteryDisplay
Обзор
Документация
Войти
/
bortsov
/
ch32v203-BatteryDisplay
Код
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
source/main.cpp
355 строк
15 KB
Vitaliy Bortsov
cleanup: remove unused include, move sources to appropriate location
23 июн 2026, 11:55
23 июн 2026, 11:55
aada369
Код
Авторство
О чём код?
#include "UsbInterface.h" #include "microshell/microshell.h" #include "system_CH32V203F6P6.h" #include "u8g2_port.h" #include "u8g2.h" #include "Model.h" #include "HeatbeatLed.h" #include "bsp/CH32V203_systick.h" #include <cstring> static void drawPercentSymbol(u8g2_t* u8, u8g2_uint_t x, u8g2_uint_t y) { y = y - 3; /* from real world */ // Геометрия символа '%' (фиксированный размер 16×28) constexpr int8_t radius{3}; // радиус точек (диаметр = 8) // Координаты центров окружностей относительно (x, y) constexpr int8_t top_cx{6}; // верхняя точка: x+6 constexpr int8_t top_cy{6}; // верхняя точка: y+6 constexpr int8_t bot_cx{10}; // нижняя точка: x+10 (сдвиг вправо на 4) constexpr int8_t bot_cy{22}; // нижняя точка: y+22 (сдвиг вниз на 16) constexpr u8g2_uint_t TOTAL_WIDTH{16U}; // 1. Верхняя точка (круг) u8g2_DrawDisc(u8, static_cast<u8g2_uint_t>(x + top_cx), static_cast<u8g2_uint_t>(y + top_cy), static_cast<u8g2_uint_t>(radius), U8G2_DRAW_ALL); u8g2_DrawLine(u8, static_cast<u8g2_uint_t>(x + 2), static_cast<u8g2_uint_t>(y + 24), static_cast<u8g2_uint_t>(x + 14), static_cast<u8g2_uint_t>(y + 4)); // 3. Нижняя точка (круг) u8g2_DrawDisc(u8, static_cast<u8g2_uint_t>(x + bot_cx), static_cast<u8g2_uint_t>(y + bot_cy), static_cast<u8g2_uint_t>(radius), U8G2_DRAW_ALL); } // ============================================================================ // FUNCTION: drawSocWithIcon // PURPOSE: Отрисовка состояния заряда (SoC) с иконкой молнии на OLED дисплее // PARAMS: u8 - указатель на контекст u8g2_t // x,y - координаты левого верхнего угла текста (режим FontPosTop) // soc - значение заряда в десятых долях процента [0..1000] // 1000 = 100.0%, 999 = 99.9%, 0 = 0.0% // RETURNS: void // NOTES: Формат вывода: "100%" для 1000, "X.X%" для остальных значений // Знак '%' отрисовывается графически для экономии места // ============================================================================ static void drawSoc(u8g2_t* u8, u8g2_uint_t x, u8g2_uint_t y, int soc, const bool flagCharge) { char buf[5]; char* p = buf; // Ручное преобразование int → ASCII без snprintf (экономия Flash/RAM) if (soc >= 1000) { // Специальный случай: 100.0% → "100" (без десятичной точки) *p++ = static_cast<char>('0' + 1); *p++ = static_cast<char>('0' + 0); *p++ = static_cast<char>('0' + 0); } else { // Общий случай: формат "XX.X" или "X.X" const int whole = soc / 10; const int tenth = soc % 10; if (whole >= 10) { *p++ = static_cast<char>('0' + (whole / 10)); } *p++ = static_cast<char>('0' + (whole % 10)); *p++ = '.'; *p++ = static_cast<char>('0' + tenth); } *p = '\0'; // Отрисовка цифр u8g2_SetFont(u8, u8g2_font_logisoso28_tn); u8g2_DrawStr(u8, x, y, buf); // Вычисление позиции для знака '%' const u8g2_uint_t num_width = static_cast<u8g2_uint_t>(u8g2_GetStrWidth(u8, buf)); constexpr u8g2_uint_t GAP_AFTER_NUM{6U}; const u8g2_uint_t percent_x = static_cast<u8g2_uint_t>(x + num_width + GAP_AFTER_NUM); const u8g2_uint_t percent_y = static_cast<u8g2_uint_t>(y + 4U); drawPercentSymbol(u8, percent_x, percent_y); if (flagCharge) { // Отрисовка иконки молнии (фиксированная позиция) u8g2_SetFont(u8, u8g2_font_open_iconic_embedded_4x_t); u8g2_DrawStr(u8, static_cast<u8g2_uint_t>(128U - 32U), y, "\x43"); } } /** * Принципы работы со шрифтами в U8G2. * 1. имеется две высоты символа: первая высота это "Capital A", без учёта завитков сверху и снизу, вторая это максимальная высота с учётом всех завитков * 2. библиотеке нужно указать, что считать "высотой текста": * 2.1 u8g2_SetFontRefHeightText(): высота = высота строчных букв без завитков * Включает: a c e m n o r s u v w x z * 2.2 u8g2_SetFontRefHeightExtendedText(): высота = высота строчных + основные выносные элементы * Это "значение по-умолчанию" * "Основные выносные элементы" — это стандартные части букв, которые есть в любом читаемом тексте: * Верхние (ascenders): b d f h i k l t * Нижние (descenders): g j p q y * 2.3 u8g2_SetFontRefHeightAll(): высота = все декоративные элементы * Дополнительно к ExtendedText включает: * Заглавные буквы с завитками: Q J * Символы с выносами: '*' '+' (выше базовой линии) ',' '/' (ниже базовой линии) * Диакритика: ä ö ü ñ (точки, тильды) * 3. библиотеке нужно указать, что считать (0,0) для символа * 3.1 u8g2_SetFontPosTop(): символ будет рисоваться ниже указанной координаты; насколько ниже: определяется тем, какая u8g2_SetFontRefHeight была вызвана * * u8g2_SetFontRefHeightAll(): любой символ будет отрисован ниже указанной координаты * * u8g2_SetFontRefHeightExtendedText(): почти все строчные символы (кроме Q, J) будут отрисованы ниже указанной координаты; некоторые могут быть отрисованы выше * * u8g2_SetFontRefHeightText(): * - Ниже координаты: только строчные без ascenders (a c e o...) * - Выше координаты: буквы с ascenders (b d h l...), заглавные, декоративные * 3.2 u8g2_SetFontPosBaseline(): базовая линия любого символа начнёт рисоваться с указанной координаты * Это "значение по-умолчанию" * * если рисуемый символ имеет элементы ниже базовой линии, то часть отрисованного символа будет ниже указанной координаты Y * * остальная часть отрисованного символа будет располагаться выше указанной координаты */ /** * Список шрифтов для проверки (D/F: digit/full; Height(Capital A size); Width; Size) * Для всех шрифтов следует координату вывода по X уменьшать на 1, если не сказано особо * * Шрифты для 1 строки: * D-32(28)-16-429 u8g2_font_logisoso28_tn цифры и знаки; '%' нет * F-32(26)-36-3312 u8g2_font_calblk36_tr * F-32(25)-30-2965 u8g2_font_helvB24_tr * F-32(23)-34-3337 u8g2_font_timR24_tr * F-29(22)-19-3506 u8g2_font_inr21_mr * F-31(21)-20-2227 u8g2_font_logisoso20_tr * F-29(21)-19-3538 u8g2_font_inb21_mr * F-30(21)-32-3232 u8g2_font_osr21_tr * F-30(21)-31-3207 u8g2_font_osb21_tr * F-29(20)-28-2514 u8g2_font_fur20_tr * F-30(20)-22-2983 u8g2_font_courB24_tr * !F-30(20)-16-2032 u8g2_font_spleen16x32_mu размер 28 px * !!F-32(20)-16-3023 u8g2_font_spleen16x32_mr смещение Y=-3, размер 28 px * * Шрифты для 2 строк: * F-16(13)-17-1849 u8g2_font_lubR12_tr не нравится: внизу сливаются */ int main() { int16_t x_offset{}; int16_t y_offset{}; constexpr int16_t scroll_step {1}; bool flagStopScroll{true}; bool flagInverse{}; bool flagTestMode{true}; constexpr uint32_t timeOnHeatbeatLed_us {3'000'000UL}; systemInit(); systick::createWith8MHzOsc(); auto u{usb::create()}; auto ms{std::make_unique<MicroShell>(*u)}; ms->registerCommand( "stats", "Show stacks and heap info", [] (MicroShell& shell, [[maybe_unused]] MicroShell::CommandArgumentType* argv, [[maybe_unused]] int argc) { auto& cb {shell.getIo()}; const int totalStack{static_cast<int>(getStackSize())}; const int totalHeap{static_cast<int>(getHeapSize())}; const int freeStack{static_cast<int>(getFreeStack())}; const int freeHeap{static_cast<int>(getFreeHeap())}; cb << "Stack usage: " << totalStack - freeStack << '/' << totalStack << "\r\n"; cb << "Heap usage: " << totalHeap - freeHeap << '/' << totalHeap << "\r\n"; return true; } ); ms->registerCommand( "xy", "Run PMOLED XY mode", [&flagTestMode] ([[maybe_unused]] MicroShell& shell, [[maybe_unused]] MicroShell::CommandArgumentType* argv, [[maybe_unused]] int argc) { flagTestMode = true; shell.getIo().putString("Use CTRL+Q to exit from test PMOLED XY mode"); return true; } ); struct u8g2_struct u8; /* no init */ u8g2_Setup_ssd1306_i2c_128x32_univision_f(&u8, U8G2_R0, u8x8_byte_sw_i2c, u8x8_port); /* Display #1 */ // u8g2_Setup_ssd1306_i2c_128x64_noname_f(&u8, U8G2_R0, u8x8_byte_sw_i2c, u8x8_port); /* Display #2 */ struct u8x8_struct* x8 {u8g2_GetU8x8(&u8)}; u8g2_InitDisplay(&u8); u8g2_ClearBuffer(&u8); u8g2_SendBuffer(&u8); u8g2_SetPowerSave(&u8, 0); u8g2_SetDrawColor(&u8, 1); // u8g2_SetFont(&u8, u8g2_font_timR24_tr); // u8g2_SetFont(&u8, u8g2_font_logisoso28_tr); // u8g2_SetFont(&u8, u8g2_font_inb30_mn); u8g2_SetFont(&u8, u8g2_font_battery24_tr); u8g2_SetFontMode(&u8, 1); // 1 = transparent u8g2_SetFontRefHeightText(&u8); /* то же что и u8g2_SetFontRefHeightExtendedText() */ // u8g2_SetFontRefHeightExtendedText(&u8); // u8g2_SetFontRefHeightAll(&u8); u8g2_SetFontPosTop(&u8); // === Получение информации о шрифте через прямой доступ к u8.font === const uint8_t *font = u8.font; uint8_t start_char, char_count; if (font && font[0] == 0) { // Формат 0 (старый): [6]=start_char, [7]=end_char start_char = font[6]; uint8_t end_char = font[7]; char_count = (end_char >= start_char) ? (uint8_t)(end_char - start_char + 1) : 0; } else if (font && font[0] == 1) { // Формат 1 (новый): [4]=start_upper, [5]=start_lower, [6]=end_lower, [7]=end_upper uint8_t start_lower = font[5]; uint8_t end_lower = font[6]; start_char = start_lower; char_count = (end_lower >= start_lower) ? (uint8_t)(end_lower - start_lower + 1) : 0; } else { // Фоллбэк: стандартный printable ASCII start_char = 0x20; char_count = 95; // 0x20..0x7E } // Защита от некорректных значений if (char_count == 0 || char_count > 200) { start_char = 0x20; char_count = 95; } // 1. Выделение буфера в ОЗУ: число символов + '\0' // char *buf = (char*)malloc(char_count + 1); // if (!buf) { // while (1) { } // обработка ошибки выделения памяти // } // 2. Заполнение буфера последовательными кодами символов // for (uint8_t i = 0; i < char_count; ++i) { // buf[i] = (char)(start_char + i); // } // buf[char_count] = '\0'; const char buf[]="G0G1G2G3G4G5G6G7G8G9G:G;G<G=G>G?G@GAGBGCGDGEGF"; // 3. Подготовка параметров скроллинга int16_t total_width = u8g2_GetStrWidth(&u8, buf); /** * need for USB CDC ACM plug and play; without this, in Win10 COM port is not detected */ static auto* s_ms = ms.get(); auto wrapper = []() { s_ms->taskBody(); s_ms->getIo().process(); }; systick::waitmsWithUserFunction(1000, wrapper); int soc{}; bool flagCharge{}; auto hb{createHeartbeatLed(Gpio::GPIOA, 0)}; uint32_t tb_hb {systick::get32bit_us()}; while (true) { const uint32_t t_hb = systick::get32bit_us(); const uint32_t dt {t_hb - tb_hb}; if (dt >= timeOnHeatbeatLed_us) { hb->toggle(); tb_hb += timeOnHeatbeatLed_us; } if (flagTestMode) /* use some keys to control scrolling without microshell */ { ms->getIo().process(); if (ms->getIo().isNewChar()) { const auto ch {ms->getIo().getChar()}; constexpr char CTRL_Q {'\x11'}; switch (ch) { case '0' : y_offset = 0; break; case '-' : y_offset = y_offset - 1; break; case '+' : y_offset = y_offset + 1; break; case '1' : flagStopScroll = !flagStopScroll; break; case '2' : x_offset = 0; break; case '3' : flagInverse = !flagInverse; u8x8_cad_SendCmd(x8, flagInverse ? 0xA7 : 0xA6); break; case CTRL_Q : flagTestMode = false; break; default : break; } } } else { ms->taskBody(); ms->getIo().process(); } // Подготовка буфера отрисовки u8g2_ClearBuffer(&u8); // 3-4. Отрисовка строки + автоматическая обрезка по границам дисплея // u8g2_DrawStr(&u8, x_offset, y_offset, buf); drawSoc(&u8, x_offset, y_offset, soc, flagCharge); ++soc; if (soc > 1030) { soc = 0; flagCharge = !flagCharge; } // 5. Вывод на дисплей u8g2_SendBuffer(&u8); // Обновление позиции скроллинга if (flagStopScroll == false) { x_offset -= scroll_step; if (x_offset < -total_width) { x_offset = 0; // перезапуск скроллинга } } } // NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); // DMA_INIT(); return 0; }