From 390d44777b140c71b45a0915ac8bc67f856b3490 Mon Sep 17 00:00:00 2001 From: Frederik Walk Date: Mon, 22 May 2023 00:08:23 +0200 Subject: [PATCH] Add ssd1306 display driver --- CMakeLists.txt | 1 + assets/idle_screen.bmp | Bin 0 -> 1154 bytes assets/idle_screen.xcf | Bin 0 -> 12377 bytes include/GlobalConfiguration.h | 36 ++- include/peripherals/Controller.h | 5 +- include/peripherals/Display.h | 45 ++++ libs/CMakeLists.txt | 3 +- libs/pico_ssd1306/CMakeLists.txt | 10 + libs/pico_ssd1306/LICENSE | 21 ++ libs/pico_ssd1306/include/ssd1306/font.h | 110 ++++++++ libs/pico_ssd1306/include/ssd1306/ssd1306.h | 266 ++++++++++++++++++ libs/pico_ssd1306/src/ssd1306.c | 282 ++++++++++++++++++++ src/main.cpp | 9 + src/peripherals/Controller.cpp | 7 - src/peripherals/Display.cpp | 115 ++++++++ 15 files changed, 890 insertions(+), 20 deletions(-) create mode 100644 assets/idle_screen.bmp create mode 100644 assets/idle_screen.xcf create mode 100644 include/peripherals/Display.h create mode 100644 libs/pico_ssd1306/CMakeLists.txt create mode 100644 libs/pico_ssd1306/LICENSE create mode 100644 libs/pico_ssd1306/include/ssd1306/font.h create mode 100644 libs/pico_ssd1306/include/ssd1306/ssd1306.h create mode 100644 libs/pico_ssd1306/src/ssd1306.c create mode 100644 src/peripherals/Display.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d4297fa..4a61585 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,6 +29,7 @@ target_link_libraries( hardware_adc pico_multicore pio_ws2812 + pico_ssd1306 mcp23017) pico_enable_stdio_usb(${PROJECT_NAME} 1) diff --git a/assets/idle_screen.bmp b/assets/idle_screen.bmp new file mode 100644 index 0000000000000000000000000000000000000000..50537ce6c89128d5127d42fe15512b283244a99d GIT binary patch literal 1154 zcmd6mv2MaJ5QYP)#K3LBS%+flEL%YtT2aa&hik~UHX6Et=7VP zl=gP4pq2}&C~@f~B$GG=$xz8e(aqdn z;nKAW%Z;y%<;MS@3wJKv`WChQzCPV^dmhP%f>v-E&OPV!IsH1_eNK00)@tjw;@ius z@f-6C3koR(Bmky&0Q&(!fX@WrA;8rTpLu?UnVpb202sOibpjIga)48Shb>LzgMjY< zBWq~3vRq$ZuWmHJE7QQG&FWI4x)Q%xYrG!6^zxOfl#CnI+WOL3wX(KZTXEX&Pj5Be zx>=34>NjgEGo|U(wdMNF`erTve7%QwAvXUgzP)v8X}Pws7B9?$ zd#jDJOl5nc)`)8x%dc0rSfakVx>aq&M&F}_Y*%KVy%c;ROpazlqKTQ}O~F&&2(El2 z*nQ_NISM!hBmi12f4RC|2UZ0D?XCfnq!&|sF*g9_GYtbAeZ-?5_2`mE#~xkw=zShN z;?ZNE*`7;95Nwb2AGI`)dXlt80)E1y-P++=@lyDq$E2VWfE`Ep6=bhG%e(mD?~qe2}4&RD)#{Nlb!FQJ|1&|VJsGz<`eK5hB4$ZHlqL;L@>7EF== zVgOSLNC0MWt!!?ugH|CwNc|QW3+w)IL1}-MktB=3*Vy z(*}>V@G--WYvP>Rucr(zIHx8I@8hFJKB0+o>cBt3`5yJNeb-(d{c-(r{p{?&8SaB>^*ZH_ci8pA@$5Ifr}UrW z=6H+X1&8_+_#t?W1>oJi3QsDF@H?5m5&tuXyfRA9!iNt5SKz~oz$aA6;N$S&1@KS7 zhqEU$@ZlWcj2bg|x`oFLe+)j82Lod2O~J>K?}aHp3Lj4S-^UZCoufOpW~SPB(662WJ;&C$XEv!YnqPgFVl{*3YVDG_d)==3Mvx_jSI2{XpMx3LcEU>kP%l3h1tPQX(C}Vt%Pj^px*YzvD@yjQ(<5{pnHl zhlcte%2-tJfZSmOKGET1;&w4=}8Kd{N2>kJEF0^{yMw0(H^1w{;~i4#z>nv z9e?KBNy4`ybqBNOpHBv}$MX?Sr-QvmUy*s?2XXJo=-fRb!rL9s@!~<`obP1xgp66_ zX^9-@_IpKUDSFX7PSU)q%9+1C78T$@`4)J$vfb+3tv@nzS^s;FueUAkwFkAg$5?KT zDz!Xs+~XJPxN)z?uU2n2;#cdpsM7B*jMUjHH*b!`OOtPVObR*y*qis{yP%P#z7%Y} zDZYuJ)a2)ak9;6lspjY_*sHvCPhxc)0z77J;k=C(!58hVJdb>lPg?!`d$jC}c;(U9 zh1$Hdm#`bhu&dU0HSSiR0Oc}gR`rliN0}Rw}tSB_EV%)jRlI_H17@4`dL%PQV z5CfP}KmzEBH`lhWBm04hH~71mpN@U&aVh8oVB^j7C}^b0dxFh3#WyjOnw}Lr^SI#u zXS|Vh6~No`{>wGTwMIH@#Uh^iTnvhk9~e2~64xWo)666Ag@iA^tb-5&+)Hq`o((-z zbDPXHEjX)g$W$fKr@JD*LFwEu_Y%jD^-n4@+m#|aV*cek+hvTtv_CkDa2 za(N0lpe)UPn$fMGZUf%z*CLxc+d~Igvv;igu#-2#R4wY}NTTkZLLCVp1~8?71Tc23 zdb7IRsMR;(D@$89kmF))Yg(Gfn`HhXrP)uj|LU~FC}ZQdV*Dn}vf}`2iYbu%b+(_} vud1o)rFrrxAOTp}Jb$H)G=D{~`KI_LhEnq{Sp2eJ91HMrMgX4xVu1P+qWGD0 literal 0 HcmV?d00001 diff --git a/include/GlobalConfiguration.h b/include/GlobalConfiguration.h index cf4de62..a1e55fd 100644 --- a/include/GlobalConfiguration.h +++ b/include/GlobalConfiguration.h @@ -2,12 +2,29 @@ #define _GLOBALCONFIGURATION_H_ #include "peripherals/Controller.h" +#include "peripherals/Display.h" #include "peripherals/Drum.h" #include "peripherals/StatusLed.h" #include "hardware/i2c.h" -namespace Doncon::Config::Default { +namespace Doncon::Config { + +struct I2c { + uint8_t sda_pin; + uint8_t scl_pin; + i2c_inst_t *block; + uint speed_hz; +}; + +namespace Default { + +const I2c i2c_config = { + 6, // SDA Pin + 7, // SCL Pin + i2c1, // Block + 1000000, // Speed +}; const Peripherals::Drum::Config drum_config = { // Pin config @@ -23,13 +40,10 @@ const Peripherals::Drum::Config drum_config = { }; const Peripherals::Buttons::Config button_config = { - // I2C config + // I2c config { - 6, // SDA Pin - 7, // SCL Pin - i2c1, // Block - 1000000, // Speed - 0x20, // Address + i2c_config.block, // Block + 0x20, // Address }, // Pins @@ -69,6 +83,12 @@ const Peripherals::StatusLed::Config led_config = { 255, // Brightness }; -} // namespace Doncon::Config::Default +const Peripherals::Display::Config display_config = { + i2c_config.block, // Block + 0x3C, // Address +}; + +} // namespace Default +} // namespace Doncon::Config #endif // _GLOBALCONFIGURATION_H_ \ No newline at end of file diff --git a/include/peripherals/Controller.h b/include/peripherals/Controller.h index 7f40bfb..02378e8 100644 --- a/include/peripherals/Controller.h +++ b/include/peripherals/Controller.h @@ -3,6 +3,7 @@ #include "utils/InputState.h" +#include "hardware/i2c.h" #include #include @@ -14,12 +15,8 @@ namespace Doncon::Peripherals { class Buttons { public: struct Config { - struct { - uint8_t sda_pin; - uint8_t scl_pin; i2c_inst_t *block; - uint speed_hz; uint8_t address; } i2c; diff --git a/include/peripherals/Display.h b/include/peripherals/Display.h new file mode 100644 index 0000000..85a2149 --- /dev/null +++ b/include/peripherals/Display.h @@ -0,0 +1,45 @@ +#ifndef _PERIPHERALS_DISPLAY_H_ +#define _PERIPHERALS_DISPLAY_H_ + +#include + +#include "hardware/i2c.h" + +#include +#include + +namespace Doncon::Peripherals { + +class Display { + public: + struct Config { + i2c_inst_t *i2c_block; + uint8_t i2c_address; + }; + + private: + enum class State { + Idle, + Menu, + }; + + Config m_config; + State m_state; + + ssd1306_t m_display; + + void drawIdleScreen(); + void drawMenuScreen(); + + public: + Display(const Config &config); + + void showIdle(); + void showMenu(); + + void update(); +}; + +} // namespace Doncon::Peripherals + +#endif // _PERIPHERALS_DISPLAY_H_ \ No newline at end of file diff --git a/libs/CMakeLists.txt b/libs/CMakeLists.txt index 2276ed8..039ab75 100644 --- a/libs/CMakeLists.txt +++ b/libs/CMakeLists.txt @@ -1,2 +1,3 @@ add_subdirectory(pio_ws2812) -add_subdirectory(mcp23017) \ No newline at end of file +add_subdirectory(pico_ssd1306) +add_subdirectory(mcp23017) diff --git a/libs/pico_ssd1306/CMakeLists.txt b/libs/pico_ssd1306/CMakeLists.txt new file mode 100644 index 0000000..d0f735a --- /dev/null +++ b/libs/pico_ssd1306/CMakeLists.txt @@ -0,0 +1,10 @@ +file(GLOB pico_ssd1306_SOURCES src/*.c) + +add_library(pico_ssd1306 STATIC ${pico_ssd1306_SOURCES}) + +target_include_directories( + pico_ssd1306 + PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/include/ssd1306) + +target_link_libraries(pico_ssd1306 PUBLIC pico_stdlib hardware_i2c hardware_dma) diff --git a/libs/pico_ssd1306/LICENSE b/libs/pico_ssd1306/LICENSE new file mode 100644 index 0000000..7306dc0 --- /dev/null +++ b/libs/pico_ssd1306/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 David Schramm + +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. \ No newline at end of file diff --git a/libs/pico_ssd1306/include/ssd1306/font.h b/libs/pico_ssd1306/include/ssd1306/font.h new file mode 100644 index 0000000..5996025 --- /dev/null +++ b/libs/pico_ssd1306/include/ssd1306/font.h @@ -0,0 +1,110 @@ +#ifndef _inc_font +#define _inc_font + +/* + * Format + * , , , + * , , + * + */ +const uint8_t font_8x5[] = +{ + 8, 5, 1, 32, 126, + 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x5F, 0x00, 0x00, + 0x00, 0x07, 0x00, 0x07, 0x00, + 0x14, 0x7F, 0x14, 0x7F, 0x14, + 0x24, 0x2A, 0x7F, 0x2A, 0x12, + 0x23, 0x13, 0x08, 0x64, 0x62, + 0x36, 0x49, 0x56, 0x20, 0x50, + 0x00, 0x08, 0x07, 0x03, 0x00, + 0x00, 0x1C, 0x22, 0x41, 0x00, + 0x00, 0x41, 0x22, 0x1C, 0x00, + 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, + 0x08, 0x08, 0x3E, 0x08, 0x08, + 0x00, 0x80, 0x70, 0x30, 0x00, + 0x08, 0x08, 0x08, 0x08, 0x08, + 0x00, 0x00, 0x60, 0x60, 0x00, + 0x20, 0x10, 0x08, 0x04, 0x02, + 0x3E, 0x51, 0x49, 0x45, 0x3E, + 0x00, 0x42, 0x7F, 0x40, 0x00, + 0x72, 0x49, 0x49, 0x49, 0x46, + 0x21, 0x41, 0x49, 0x4D, 0x33, + 0x18, 0x14, 0x12, 0x7F, 0x10, + 0x27, 0x45, 0x45, 0x45, 0x39, + 0x3C, 0x4A, 0x49, 0x49, 0x31, + 0x41, 0x21, 0x11, 0x09, 0x07, + 0x36, 0x49, 0x49, 0x49, 0x36, + 0x46, 0x49, 0x49, 0x29, 0x1E, + 0x00, 0x00, 0x14, 0x00, 0x00, + 0x00, 0x40, 0x34, 0x00, 0x00, + 0x00, 0x08, 0x14, 0x22, 0x41, + 0x14, 0x14, 0x14, 0x14, 0x14, + 0x00, 0x41, 0x22, 0x14, 0x08, + 0x02, 0x01, 0x59, 0x09, 0x06, + 0x3E, 0x41, 0x5D, 0x59, 0x4E, + 0x7C, 0x12, 0x11, 0x12, 0x7C, + 0x7F, 0x49, 0x49, 0x49, 0x36, + 0x3E, 0x41, 0x41, 0x41, 0x22, + 0x7F, 0x41, 0x41, 0x41, 0x3E, + 0x7F, 0x49, 0x49, 0x49, 0x41, + 0x7F, 0x09, 0x09, 0x09, 0x01, + 0x3E, 0x41, 0x41, 0x51, 0x73, + 0x7F, 0x08, 0x08, 0x08, 0x7F, + 0x00, 0x41, 0x7F, 0x41, 0x00, + 0x20, 0x40, 0x41, 0x3F, 0x01, + 0x7F, 0x08, 0x14, 0x22, 0x41, + 0x7F, 0x40, 0x40, 0x40, 0x40, + 0x7F, 0x02, 0x1C, 0x02, 0x7F, + 0x7F, 0x04, 0x08, 0x10, 0x7F, + 0x3E, 0x41, 0x41, 0x41, 0x3E, + 0x7F, 0x09, 0x09, 0x09, 0x06, + 0x3E, 0x41, 0x51, 0x21, 0x5E, + 0x7F, 0x09, 0x19, 0x29, 0x46, + 0x26, 0x49, 0x49, 0x49, 0x32, + 0x03, 0x01, 0x7F, 0x01, 0x03, + 0x3F, 0x40, 0x40, 0x40, 0x3F, + 0x1F, 0x20, 0x40, 0x20, 0x1F, + 0x3F, 0x40, 0x38, 0x40, 0x3F, + 0x63, 0x14, 0x08, 0x14, 0x63, + 0x03, 0x04, 0x78, 0x04, 0x03, + 0x61, 0x59, 0x49, 0x4D, 0x43, + 0x00, 0x7F, 0x41, 0x41, 0x41, + 0x02, 0x04, 0x08, 0x10, 0x20, + 0x00, 0x41, 0x41, 0x41, 0x7F, + 0x04, 0x02, 0x01, 0x02, 0x04, + 0x40, 0x40, 0x40, 0x40, 0x40, + 0x00, 0x03, 0x07, 0x08, 0x00, + 0x20, 0x54, 0x54, 0x78, 0x40, + 0x7F, 0x28, 0x44, 0x44, 0x38, + 0x38, 0x44, 0x44, 0x44, 0x28, + 0x38, 0x44, 0x44, 0x28, 0x7F, + 0x38, 0x54, 0x54, 0x54, 0x18, + 0x00, 0x08, 0x7E, 0x09, 0x02, + 0x18, 0xA4, 0xA4, 0x9C, 0x78, + 0x7F, 0x08, 0x04, 0x04, 0x78, + 0x00, 0x44, 0x7D, 0x40, 0x00, + 0x20, 0x40, 0x40, 0x3D, 0x00, + 0x7F, 0x10, 0x28, 0x44, 0x00, + 0x00, 0x41, 0x7F, 0x40, 0x00, + 0x7C, 0x04, 0x78, 0x04, 0x78, + 0x7C, 0x08, 0x04, 0x04, 0x78, + 0x38, 0x44, 0x44, 0x44, 0x38, + 0xFC, 0x18, 0x24, 0x24, 0x18, + 0x18, 0x24, 0x24, 0x18, 0xFC, + 0x7C, 0x08, 0x04, 0x04, 0x08, + 0x48, 0x54, 0x54, 0x54, 0x24, + 0x04, 0x04, 0x3F, 0x44, 0x24, + 0x3C, 0x40, 0x40, 0x20, 0x7C, + 0x1C, 0x20, 0x40, 0x20, 0x1C, + 0x3C, 0x40, 0x30, 0x40, 0x3C, + 0x44, 0x28, 0x10, 0x28, 0x44, + 0x4C, 0x90, 0x90, 0x90, 0x7C, + 0x44, 0x64, 0x54, 0x4C, 0x44, + 0x00, 0x08, 0x36, 0x41, 0x00, + 0x00, 0x00, 0x77, 0x00, 0x00, + 0x00, 0x41, 0x36, 0x08, 0x00, + 0x02, 0x01, 0x02, 0x04, 0x02, +}; + +#endif \ No newline at end of file diff --git a/libs/pico_ssd1306/include/ssd1306/ssd1306.h b/libs/pico_ssd1306/include/ssd1306/ssd1306.h new file mode 100644 index 0000000..6f1d5da --- /dev/null +++ b/libs/pico_ssd1306/include/ssd1306/ssd1306.h @@ -0,0 +1,266 @@ +/* +MIT License + +Copyright (c) 2021 David Schramm + +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. +*/ + +/** + * @file ssd1306.h + * + * simple driver for ssd1306 displays + */ + +#ifndef _inc_ssd1306 +#define _inc_ssd1306 +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief defines commands used in ssd1306 + */ +typedef enum { + SET_CONTRAST = 0x81, + SET_ENTIRE_ON = 0xA4, + SET_NORM_INV = 0xA6, + SET_DISP = 0xAE, + SET_MEM_ADDR = 0x20, + SET_COL_ADDR = 0x21, + SET_PAGE_ADDR = 0x22, + SET_DISP_START_LINE = 0x40, + SET_SEG_REMAP = 0xA0, + SET_MUX_RATIO = 0xA8, + SET_COM_OUT_DIR = 0xC0, + SET_DISP_OFFSET = 0xD3, + SET_COM_PIN_CFG = 0xDA, + SET_DISP_CLK_DIV = 0xD5, + SET_PRECHARGE = 0xD9, + SET_VCOM_DESEL = 0xDB, + SET_CHARGE_PUMP = 0x8D +} ssd1306_command_t; + +/** + * @brief holds the configuration + */ +typedef struct { + uint8_t width; /**< width of display */ + uint8_t height; /**< height of display */ + uint8_t pages; /**< stores pages of display (calculated on initialization*/ + uint8_t address; /**< i2c address of display*/ + i2c_inst_t *i2c_i; /**< i2c connection instance */ + bool external_vcc; /**< whether display uses external vcc */ + uint8_t *buffer; /**< display buffer */ + size_t bufsize; /**< buffer size */ + int dma_channel; /**< dma channel id for writing */ + uint16_t *dma_buffer; /**< buffer for dma transfer */ +} ssd1306_t; + +/** + * @brief initialize display + * + * @param[in] p : pointer to instance of ssd1306_t + * @param[in] width : width of display + * @param[in] height : heigth of display + * @param[in] address : i2c address of display + * @param[in] i2c_instance : instance of i2c connection + * + * @return bool. + * @retval true for Success + * @retval false if initialization failed + */ +bool ssd1306_init(ssd1306_t *p, uint16_t width, uint16_t height, uint8_t address, i2c_inst_t *i2c_instance); + +/** + * @brief deinitialize display + * + * @param[in] p : instance of display + * + */ +void ssd1306_deinit(ssd1306_t *p); + +/** + * @brief turn off display + * + * @param[in] p : instance of display + * + */ +void ssd1306_poweroff(ssd1306_t *p); + +/** + @brief turn on display + + @param[in] p : instance of display + +*/ +void ssd1306_poweron(ssd1306_t *p); + +/** + @brief set contrast of display + + @param[in] p : instance of display + @param[in] val : contrast + +*/ +void ssd1306_contrast(ssd1306_t *p, uint8_t val); + +/** + @brief set invert display + + @param[in] p : instance of display + @param[in] inv : inv==0: disable inverting, inv!=0: invert + +*/ +void ssd1306_invert(ssd1306_t *p, uint8_t inv); + +/** + @brief display buffer, should be called on change + + @param[in] p : instance of display + +*/ +void ssd1306_show(ssd1306_t *p); + +/** + @brief clear display buffer + + @param[in] p : instance of display + +*/ +void ssd1306_clear(ssd1306_t *p); + +/** + @brief draw pixel on buffer + + @param[in] p : instance of display + @param[in] x : x position + @param[in] y : y position +*/ +void ssd1306_draw_pixel(ssd1306_t *p, uint32_t x, uint32_t y); + +/** + @brief draw pixel on buffer + + @param[in] p : instance of display + @param[in] x1 : x position of starting point + @param[in] y1 : y position of starting point + @param[in] x2 : x position of end point + @param[in] y2 : y position of end point +*/ +void ssd1306_draw_line(ssd1306_t *p, int32_t x1, int32_t y1, int32_t x2, int32_t y2); + +/** + @brief draw filled square at given position with given size + + @param[in] p : instance of display + @param[in] x : x position of starting point + @param[in] y : y position of starting point + @param[in] width : width of square + @param[in] height : height of square +*/ +void ssd1306_draw_square(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t width, uint32_t height); + +/** + @brief draw empty square at given position with given size + + @param[in] p : instance of display + @param[in] x : x position of starting point + @param[in] y : y position of starting point + @param[in] width : width of square + @param[in] height : height of square +*/ +void ssd13606_draw_empty_square(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t width, uint32_t height); + +/** + @brief draw monochrome bitmap with offset + + @param[in] p : instance of display + @param[in] data : image data (whole file) + @param[in] size : size of image data in bytes + @param[in] x_offset : offset of horizontal coordinate + @param[in] y_offset : offset of vertical coordinate +*/ +void ssd1306_bmp_show_image_with_offset(ssd1306_t *p, const uint8_t *data, const long size, uint32_t x_offset, + uint32_t y_offset); + +/** + @brief draw monochrome bitmap + + @param[in] p : instance of display + @param[in] data : image data (whole file) + @param[in] size : size of image data in bytes +*/ +void ssd1306_bmp_show_image(ssd1306_t *p, const uint8_t *data, const long size); + +/** + @brief draw char with given font + + @param[in] p : instance of display + @param[in] x : x starting position of char + @param[in] y : y starting position of char + @param[in] scale : scale font to n times of original size (default should be 1) + @param[in] font : pointer to font + @param[in] c : character to draw +*/ +void ssd1306_draw_char_with_font(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, const uint8_t *font, char c); + +/** + @brief draw char with builtin font + + @param[in] p : instance of display + @param[in] x : x starting position of char + @param[in] y : y starting position of char + @param[in] scale : scale font to n times of original size (default should be 1) + @param[in] c : character to draw +*/ +void ssd1306_draw_char(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, char c); + +/** + @brief draw string with given font + + @param[in] p : instance of display + @param[in] x : x starting position of text + @param[in] y : y starting position of text + @param[in] scale : scale font to n times of original size (default should be 1) + @param[in] font : pointer to font + @param[in] s : text to draw +*/ +void ssd1306_draw_string_with_font(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, const uint8_t *font, + const char *s); + +/** + @brief draw string with builtin font + + @param[in] p : instance of display + @param[in] x : x starting position of text + @param[in] y : y starting position of text + @param[in] scale : scale font to n times of original size (default should be 1) + @param[in] s : text to draw +*/ +void ssd1306_draw_string(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, const char *s); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/libs/pico_ssd1306/src/ssd1306.c b/libs/pico_ssd1306/src/ssd1306.c new file mode 100644 index 0000000..fc6fc79 --- /dev/null +++ b/libs/pico_ssd1306/src/ssd1306.c @@ -0,0 +1,282 @@ +/* + +MIT License + +Copyright (c) 2021 David Schramm + +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. +*/ + +#include +#include +#include +#include +#include +#include + +#include "font.h" +#include "ssd1306.h" + +inline static void swap(int32_t *a, int32_t *b) { + int32_t *t = a; + *a = *b; + *b = *t; +} + +inline static void fancy_write(i2c_inst_t *i2c, uint8_t addr, const uint8_t *src, size_t len, char *name) { + switch (i2c_write_blocking(i2c, addr, src, len, false)) { + case PICO_ERROR_GENERIC: + printf("[%s] addr not acknowledged!\n", name); + break; + case PICO_ERROR_TIMEOUT: + printf("[%s] timeout!\n", name); + break; + default: + // printf("[%s] wrote successfully %lu bytes!\n", name, len); + break; + } +} + +inline static void ssd1306_write(ssd1306_t *p, uint8_t val) { + uint8_t d[2] = {0x00, val}; + fancy_write(p->i2c_i, p->address, d, 2, "ssd1306_write"); +} + +bool ssd1306_init(ssd1306_t *p, uint16_t width, uint16_t height, uint8_t address, i2c_inst_t *i2c_instance) { + p->width = width; + p->height = height; + p->pages = height / 8; + p->address = address; + + p->i2c_i = i2c_instance; + + p->bufsize = (p->pages) * (p->width); + if ((p->buffer = malloc(p->bufsize + 1)) == NULL) { + p->bufsize = 0; + return false; + } + + ++(p->buffer); + + // from https://github.com/makerportal/rpi-pico-ssd1306 + uint8_t cmds[] = { + SET_DISP, + // timing and driving scheme + SET_DISP_CLK_DIV, 0x80, SET_MUX_RATIO, height - 1, SET_DISP_OFFSET, 0x00, + // resolution and layout + SET_DISP_START_LINE, + // charge pump + SET_CHARGE_PUMP, p->external_vcc ? 0x10 : 0x14, + SET_SEG_REMAP | 0x01, // column addr 127 mapped to SEG0 + SET_COM_OUT_DIR | 0x08, // scan from COM[N] to COM0 + SET_COM_PIN_CFG, width > 2 * height ? 0x02 : 0x12, + // display + SET_CONTRAST, 0xff, SET_PRECHARGE, p->external_vcc ? 0x22 : 0xF1, SET_VCOM_DESEL, + 0x30, // or 0x40? + SET_ENTIRE_ON, // output follows RAM contents + SET_NORM_INV, // not inverted + SET_DISP | 0x01, + // address setting + SET_MEM_ADDR, + 0x00, // horizontal + }; + + for (size_t i = 0; i < sizeof(cmds); ++i) + ssd1306_write(p, cmds[i]); + + return true; +} + +inline void ssd1306_deinit(ssd1306_t *p) { free(p->buffer - 1); } + +inline void ssd1306_poweroff(ssd1306_t *p) { ssd1306_write(p, SET_DISP | 0x00); } + +inline void ssd1306_poweron(ssd1306_t *p) { ssd1306_write(p, SET_DISP | 0x01); } + +inline void ssd1306_contrast(ssd1306_t *p, uint8_t val) { + ssd1306_write(p, SET_CONTRAST); + ssd1306_write(p, val); +} + +inline void ssd1306_invert(ssd1306_t *p, uint8_t inv) { ssd1306_write(p, SET_NORM_INV | (inv & 1)); } + +inline void ssd1306_clear(ssd1306_t *p) { memset(p->buffer, 0, p->bufsize); } + +void ssd1306_clear_pixel(ssd1306_t *p, uint32_t x, uint32_t y) { + if (x >= p->width || y >= p->height) + return; + + p->buffer[x + p->width * (y >> 3)] &= ~(0x1 << (y & 0x07)); +} + +void ssd1306_draw_pixel(ssd1306_t *p, uint32_t x, uint32_t y) { + if (x >= p->width || y >= p->height) + return; + + p->buffer[x + p->width * (y >> 3)] |= 0x1 << (y & 0x07); // y>>3==y/8 && y&0x7==y%8 +} + +void ssd1306_draw_line(ssd1306_t *p, int32_t x1, int32_t y1, int32_t x2, int32_t y2) { + if (x1 > x2) { + swap(&x1, &x2); + swap(&y1, &y2); + } + + if (x1 == x2) { + if (y1 > y2) + swap(&y1, &y2); + for (int32_t i = y1; i <= y2; ++i) + ssd1306_draw_pixel(p, x1, i); + return; + } + + float m = (float)(y2 - y1) / (float)(x2 - x1); + + for (int32_t i = x1; i <= x2; ++i) { + float y = m * (float)(i - x1) + (float)y1; + ssd1306_draw_pixel(p, i, (uint32_t)y); + } +} + +void ssd1306_draw_square(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { + for (uint32_t i = 0; i < width; ++i) + for (uint32_t j = 0; j < height; ++j) + ssd1306_draw_pixel(p, x + i, y + j); +} + +void ssd1306_draw_empty_square(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t width, uint32_t height) { + ssd1306_draw_line(p, x, y, x + width, y); + ssd1306_draw_line(p, x, y + height, x + width, y + height); + ssd1306_draw_line(p, x, y, x, y + height); + ssd1306_draw_line(p, x + width, y, x + width, y + height); +} + +void ssd1306_draw_char_with_font(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, const uint8_t *font, char c) { + if (c < font[3] || c > font[4]) + return; + + uint32_t parts_per_line = (font[0] >> 3) + ((font[0] & 7) > 0); + for (uint8_t w = 0; w < font[1]; ++w) { // width + uint32_t pp = (c - font[3]) * font[1] * parts_per_line + w * parts_per_line + 5; + for (uint32_t lp = 0; lp < parts_per_line; ++lp) { + uint8_t line = font[pp]; + + for (int8_t j = 0; j < 8; ++j, line >>= 1) { + if (line & 1) + ssd1306_draw_square(p, x + w * scale, y + ((lp << 3) + j) * scale, scale, scale); + } + + ++pp; + } + } +} + +void ssd1306_draw_string_with_font(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, const uint8_t *font, + const char *s) { + for (int32_t x_n = x; *s; x_n += (font[1] + font[2]) * scale) { + ssd1306_draw_char_with_font(p, x_n, y, scale, font, *(s++)); + } +} + +void ssd1306_draw_char(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, char c) { + ssd1306_draw_char_with_font(p, x, y, scale, font_8x5, c); +} + +void ssd1306_draw_string(ssd1306_t *p, uint32_t x, uint32_t y, uint32_t scale, const char *s) { + ssd1306_draw_string_with_font(p, x, y, scale, font_8x5, s); +} + +static inline uint32_t ssd1306_bmp_get_val(const uint8_t *data, const size_t offset, uint8_t size) { + switch (size) { + case 1: + return data[offset]; + case 2: + return data[offset] | (data[offset + 1] << 8); + case 4: + return data[offset] | (data[offset + 1] << 8) | (data[offset + 2] << 16) | (data[offset + 3] << 24); + default: + __builtin_unreachable(); + } + __builtin_unreachable(); +} + +void ssd1306_bmp_show_image_with_offset(ssd1306_t *p, const uint8_t *data, const long size, uint32_t x_offset, + uint32_t y_offset) { + if (size < 54) // data smaller than header + return; + + const uint32_t bfOffBits = ssd1306_bmp_get_val(data, 10, 4); + const uint32_t biSize = ssd1306_bmp_get_val(data, 14, 4); + const int32_t biWidth = (int32_t)ssd1306_bmp_get_val(data, 18, 4); + const int32_t biHeight = (int32_t)ssd1306_bmp_get_val(data, 22, 4); + const uint16_t biBitCount = (uint16_t)ssd1306_bmp_get_val(data, 28, 2); + const uint32_t biCompression = ssd1306_bmp_get_val(data, 30, 4); + + if (biBitCount != 1) // image not monochrome + return; + + if (biCompression != 0) // image compressed + return; + + const int table_start = 14 + biSize; + uint8_t color_val = 0; + + for (uint8_t i = 0; i < 2; ++i) { + if (!((data[table_start + i * 4] << 16) | (data[table_start + i * 4 + 1] << 8) | + data[table_start + i * 4 + 2])) { + color_val = i; + break; + } + } + + uint32_t bytes_per_line = (biWidth / 8) + (biWidth & 7 ? 1 : 0); + if (bytes_per_line & 3) + bytes_per_line = (bytes_per_line ^ (bytes_per_line & 3)) + 4; + + const uint8_t *img_data = data + bfOffBits; + + int step = biHeight > 0 ? -1 : 1; + int border = biHeight > 0 ? -1 : biHeight; + for (int32_t y = biHeight > 0 ? biHeight - 1 : 0; y != border; y += step) { + for (int32_t x = 0; x < biWidth; ++x) { + if (((img_data[x >> 3] >> (7 - (x & 7))) & 1) == color_val) + ssd1306_draw_pixel(p, x_offset + x, y_offset + y); + } + img_data += bytes_per_line; + } +} + +inline void ssd1306_bmp_show_image(ssd1306_t *p, const uint8_t *data, const long size) { + ssd1306_bmp_show_image_with_offset(p, data, size, 0, 0); +} + +void ssd1306_show(ssd1306_t *p) { + uint8_t payload[] = {SET_COL_ADDR, 0, p->width - 1, SET_PAGE_ADDR, 0, p->pages - 1}; + if (p->width == 64) { + payload[1] += 32; + payload[2] += 32; + } + + for (size_t i = 0; i < sizeof(payload); ++i) + ssd1306_write(p, payload[i]); + + *(p->buffer - 1) = 0x40; + + fancy_write(p->i2c_i, p->address, p->buffer - 1, p->bufsize + 1, "ssd1306_show"); +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 701264c..00f7036 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,4 +1,5 @@ #include "peripherals/Controller.h" +#include "peripherals/Display.h" #include "peripherals/Drum.h" #include "peripherals/StatusLed.h" #include "usb/usb_driver.h" @@ -19,9 +20,16 @@ queue_t controller_input_queue; void core1_task() { multicore_lockout_victim_init(); + gpio_set_function(Config::Default::i2c_config.sda_pin, GPIO_FUNC_I2C); + gpio_set_function(Config::Default::i2c_config.scl_pin, GPIO_FUNC_I2C); + gpio_pull_up(Config::Default::i2c_config.sda_pin); + gpio_pull_up(Config::Default::i2c_config.scl_pin); + i2c_init(Config::Default::i2c_config.block, Config::Default::i2c_config.speed_hz); + Utils::InputState input_state; Peripherals::Buttons buttons(Config::Default::button_config); Peripherals::StatusLed led(Config::Default::led_config); + Peripherals::Display display(Config::Default::display_config); while (true) { buttons.updateInputState(input_state); @@ -33,6 +41,7 @@ void core1_task() { } led.update(); + display.update(); // sleep_ms(1); } diff --git a/src/peripherals/Controller.cpp b/src/peripherals/Controller.cpp index 4e15e65..ab56d75 100644 --- a/src/peripherals/Controller.cpp +++ b/src/peripherals/Controller.cpp @@ -1,6 +1,5 @@ #include "peripherals/Controller.h" -#include "hardware/gpio.h" #include "pico/time.h" namespace Doncon::Peripherals { @@ -49,12 +48,6 @@ void Buttons::socdClean(Utils::InputState &input_state) { } Buttons::Buttons(const Config &config) : m_config(config), m_socd_state{Id::DOWN, Id::RIGHT} { - i2c_init(m_config.i2c.block, m_config.i2c.speed_hz); - gpio_set_function(m_config.i2c.sda_pin, GPIO_FUNC_I2C); - gpio_set_function(m_config.i2c.scl_pin, GPIO_FUNC_I2C); - gpio_pull_up(m_config.i2c.sda_pin); - gpio_pull_up(m_config.i2c.scl_pin); - m_mcp23017 = std::make_unique(m_config.i2c.address, m_config.i2c.block); m_mcp23017->setDirection(0xFFFF); // All inputs m_mcp23017->setPullup(0xFFFF); // All on diff --git a/src/peripherals/Display.cpp b/src/peripherals/Display.cpp new file mode 100644 index 0000000..ad73696 --- /dev/null +++ b/src/peripherals/Display.cpp @@ -0,0 +1,115 @@ +#include "peripherals/Display.h" + +#include "hardware/gpio.h" +#include "pico/time.h" + +#include +#include +#include +#include + +namespace Doncon::Peripherals { + +static const std::array idle_screen = { + 0x42, 0x4d, 0x82, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x80, + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, + 0x23, 0x2e, 0x00, 0x00, 0x23, 0x2e, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, + 0x00, 0x00, 0xff, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x42, 0x47, 0x52, 0x73, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x00, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0x1f, 0xf8, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, + 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xe7, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfe, 0xff, 0xf0, 0x0f, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfd, 0xff, 0x07, 0xe0, 0xff, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xfc, 0x7f, + 0xfe, 0x3f, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xf9, 0xff, 0xff, 0x9f, 0xef, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xe3, 0xff, 0xff, 0xc7, 0xf7, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xcf, 0xff, 0xff, 0xf3, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xdf, 0x9f, 0xff, 0xff, 0xf9, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xbf, 0x3f, 0xff, 0xff, 0xfc, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0x7f, + 0xff, 0xff, 0xfe, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7e, 0x7f, 0xff, 0xff, 0xfe, + 0x7e, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7c, 0xff, 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xbf, 0x7f, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xfe, 0xf9, 0xff, 0xff, 0xff, 0xff, 0x9f, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xfe, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xdf, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfb, + 0xff, 0xff, 0xff, 0xff, 0xdf, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xfb, 0xff, 0xff, 0xff, + 0xff, 0xdf, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xbf, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xef, 0xbf, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xef, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xfd, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xef, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, + 0xf7, 0xff, 0xff, 0xff, 0xff, 0xef, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xf7, 0xff, 0xff, + 0xff, 0xff, 0xef, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xf7, 0xff, 0xff, 0xff, 0xff, 0xef, + 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xbf, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xdf, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfe, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xdf, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xdf, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xf9, 0xff, + 0xff, 0xff, 0xff, 0x9f, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfd, 0xff, 0xff, 0xff, 0xff, + 0xbf, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7c, 0xff, 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7e, 0x7f, 0xff, 0xff, 0xfe, 0x7e, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xbf, 0x7f, 0xff, 0xff, 0xfe, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xbf, 0x3f, 0xff, 0xff, 0xfc, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdf, + 0x9f, 0xff, 0xff, 0xf9, 0xfb, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xcf, 0xcf, 0xff, 0xff, + 0xf3, 0xf3, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xef, 0xe3, 0xff, 0xff, 0xc7, 0xf7, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf7, 0xf9, 0xff, 0xff, 0x9f, 0xef, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfb, 0xfc, 0x7f, 0xfe, 0x3f, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xfd, 0xff, 0x07, 0xe0, 0xff, 0xbf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0xff, 0xf0, 0x0f, 0xff, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, + 0xff, 0xfc, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xf9, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xe7, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0x9f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe, 0x1f, 0xf8, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xe0, 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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, 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, 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, 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}; + +Display::Display(const Config &config) : m_config(config), m_state(State::Idle) { + m_display.external_vcc = false; + ssd1306_init(&m_display, 128, 64, m_config.i2c_address, m_config.i2c_block); + ssd1306_clear(&m_display); +} + +void Display::showIdle() { m_state = State::Idle; } +void Display::showMenu() { m_state = State::Menu; } + +void Display::drawIdleScreen() { + // Background + ssd1306_bmp_show_image(&m_display, idle_screen.data(), idle_screen.size()); +} + +void Display::drawMenuScreen() {} + +void Display::update() { + static const uint32_t interval_ms = 17; // Limit to ~60fps + static uint32_t start_ms = 0; + + if (to_ms_since_boot(get_absolute_time()) - start_ms < interval_ms) { + return; + } + start_ms += interval_ms; + + ssd1306_clear(&m_display); + + switch (m_state) { + case State::Idle: + drawIdleScreen(); + break; + case State::Menu: + drawMenuScreen(); + break; + } + + ssd1306_show(&m_display); +}; + +} // namespace Doncon::Peripherals \ No newline at end of file