/**************************************************** * Fumot 25k Display Driver (c) 2025 * Code to turn disposable vapes into new projects. * Special thanks go to Dieter S. for the main work on the GPIO and SPI configuration and the display initialization. * https://www.mikrocontroller.net/topic/575561 * * Dieter S. * GPIO & SPI, init & most others. * * kilo_s * Text & drawing helpers. * * To do something useful with disposable vape hardware! * More or less Arduino-like basic functions. * * Minimum pin requirements on any microcontroller * include the original vape pinout. * * For use with the original PY32F030K28 MCU on a * QFN32 0.5 mm pitch breakout. * * Core: * Arduino-PY32 core * https://github.com/py32duino/Arduino-PY32 * * Copyright (c) Arduino-PY32 core authors * See repository for license and copyright details. * * LCD GND pins: 1, 2, 4, 12 * LCD VCC pins: 3 (see note!), 9, 10 * * PY32F030 pin 5 = PF3 / LCD * (switch, PNP between VCC and LCD pin 3!) * PY32F030 pin 18 = PA8 / LCD CS (LCD pin 11) * PY32F030 pin 22 = PA12 / LCD DCN (LCD pin 6) * PY32F030 pin 26 = PB3 / LCD CLK (LCD pin 8) * PY32F030 pin 27 = PB4 / LCD RESET (LCD pin 5) * PY32F030 pin 28 = PB5 / LCD MOSI (LCD pin 7) */ // Einbinden der Font- und Farbdefinitionen #include // Globale Variablen für die Display-Dimensionen // Diese werden je nach Rotation angepasst int LCD_WIDTH = 160; // Aktuelle Breite des Displays int LCD_HEIGHT = 128; // Aktuelle Höhe des Displays // ========== FUNKTIONSDEKLARATIONEN ========== // Nur für PY32F030K28 Mikrocontroller void LCD_GPIO_config(void); // GPIO-Pins konfigurieren void LCD_SPI_config(void); // SPI-Schnittstelle konfigurieren // Basis-Funktionen für Display-Steuerung void LCD_setup(void); // Display-Setup durchführen (Reset-Sequenz) void LCD_init(void); // Display initialisieren (Register setzen) void LCD_SetRotation(uint8_t rot); // Display-Orientierung einstellen (0-3) void LCD_Select(int iSelect); // Chip Select aktivieren/deaktivieren void LCD_Command(int iCommand); // Command/Data Pin steuern void LCD_Backlight(int iPowerOn); // Hintergrundbeleuchtung ein/aus // Zeichen- und Text-Funktionen void LCD_DrawChar(uint16_t x, uint16_t y, char c, uint16_t color, uint16_t bgcolor); // Einzelnes Zeichen zeichnen void LCD_DrawString(uint16_t x, uint16_t y, const char *str, uint16_t fg, uint16_t bg); // Text-String zeichnen // Bildschirm-Verwaltung void LCD_FillScreen(uint16_t color); // Gesamten Bildschirm mit einer Farbe füllen void LCD_Clear(uint16_t color); // Display löschen void LCD_SetWindow(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2); // Zeichenbereich definieren // Geometrische Formen zeichnen void LCD_DrawHLine(uint16_t x, uint16_t y, uint16_t w, uint16_t color, uint16_t bgcolor); // Horizontale Linie void LCD_DrawVLine(uint16_t x, uint16_t y, uint16_t w, uint16_t color, uint16_t bgcolor); // Vertikale Linie void LCD_DrawRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color, uint16_t bgcolor); // Rechteck-Rahmen void LCD_FillRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color, uint16_t bgcolor); // Gefülltes Rechteck void LCD_DrawPixel(int x, int y, uint16_t color); // Einzelnes Pixel setzen // Bild- und Kreis-Funktionen void LCD_SendImage(const uint16_t* image_data, uint16_t width, uint16_t height); // Bild übertragen void LCD_DrawCircle(int x0, int y0, int radius, uint16_t color, uint16_t bgcolor); // Kreis-Umriss void LCD_FillCircle(int x0, int y0, int radius, uint16_t color, uint16_t bgcolor); // Gefüllter Kreis // ========== PIN-DEFINITIONEN ========== // Pin-Definitionen als Bitpositionen und Bitmasks // RESET Pin: PB4 #define LCD_PIN_BIT_RESET (4) // Bit-Position #define LCD_PIN_RESET (1UL << LCD_PIN_BIT_RESET) // Bitmask #define LCD_PORT_RESET GPIOB // GPIO Port B // SELECT (Chip Select) Pin: PA8 #define LCD_PIN_BIT_SELECT (8) #define LCD_PIN_SELECT (1UL << LCD_PIN_BIT_SELECT) #define LCD_PORT_SELECT GPIOA // GPIO Port A // CMD_DATA (Command/Data) Pin: PA12 #define LCD_PIN_BIT_CMD_DATA (12) #define LCD_PIN_CMD_DATA (1UL << LCD_PIN_BIT_CMD_DATA) #define LCD_PORT_CMD_DATA GPIOA // BACKLIGHT Pin: PA11 #define LCD_PIN_BIT_BACKLIGHT (11) #define LCD_PIN_BACKLIGHT (1UL << LCD_PIN_BIT_BACKLIGHT) #define LCD_PORT_BACKLIGHT GPIOA // CLOCK (SPI CLK) Pin: PB3 #define LCD_PIN_BIT_CLOCK (3) #define LCD_PIN_CLOCK (1UL << LCD_PIN_BIT_CLOCK) #define LCD_PORT_CLOCK GPIOB // DATA (SPI MOSI) Pin: PB5 #define LCD_PIN_BIT_DATA (5) #define LCD_PIN_DATA (1UL << LCD_PIN_BIT_DATA) #define LCD_PORT_DATA GPIOB // ========== GPIO-KONFIGURATION ========== /** * GPIO-Konfiguration für alle Display-relevanten Pins * Konfiguriert Pins als Ausgänge und setzt initiale Zustände */ void LCD_GPIO_config(void) { // Taktversorgung für GPIO-Ports aktivieren // Aktiviert Port A, B und F RCC->IOPENR |= (RCC_IOPENR_GPIOAEN | RCC_IOPENR_GPIOBEN | RCC_IOPENR_GPIOFEN); // Data Pin (PB5) konfigurieren // MODERy[1:0] = 01: Output mode (Ausgang) LCD_PORT_DATA->MODER |= (1UL << (LCD_PIN_BIT_DATA * 2 + 0)); // Bit 0 setzen LCD_PORT_DATA->MODER &= ~(1UL << (LCD_PIN_BIT_DATA * 2 + 1)); // Bit 1 löschen // Reset Pin (PB4) konfigurieren // MODERy[1:0] = 01: Output mode LCD_PORT_RESET->MODER |= (1UL << (LCD_PIN_BIT_RESET * 2 + 0)); LCD_PORT_RESET->MODER &= ~(1UL << (LCD_PIN_BIT_RESET * 2 + 1)); // Clock Pin (PB3) konfigurieren // MODERy[1:0] = 01: Output mode LCD_PORT_CLOCK->MODER |= (1UL << (LCD_PIN_BIT_CLOCK * 2 + 0)); LCD_PORT_CLOCK->MODER &= ~(1UL << (LCD_PIN_BIT_CLOCK * 2 + 1)); // Select Pin (PA8) konfigurieren // MODERy[1:0] = 01: Output mode LCD_PORT_SELECT->MODER |= (1UL << (LCD_PIN_BIT_SELECT * 2 + 0)); LCD_PORT_SELECT->MODER &= ~(1UL << (LCD_PIN_BIT_SELECT * 2 + 1)); // Command/Data Pin (PA12) konfigurieren // MODERy[1:0] = 01: Output mode LCD_PORT_CMD_DATA->MODER |= (1UL << (LCD_PIN_BIT_CMD_DATA * 2 + 0)); LCD_PORT_CMD_DATA->MODER &= ~(1UL << (LCD_PIN_BIT_CMD_DATA * 2 + 1)); // Backlight Pin (PA11) konfigurieren // MODERy[1:0] = 01: Output mode LCD_PORT_BACKLIGHT->MODER |= (1UL << (LCD_PIN_BIT_BACKLIGHT * 2 + 0)); LCD_PORT_BACKLIGHT->MODER &= ~(1UL << (LCD_PIN_BIT_BACKLIGHT * 2 + 1)); // Hintergrundbeleuchtung initial ausschalten LCD_Backlight(0); // Power-Management Pins konfigurieren // PF3: 0 -> Run (Display aktiv) // MODERy[1:0] = 01: Output mode GPIOF->MODER |= (1UL << (3 * 2 + 0)); GPIOF->MODER &= ~(1UL << (3 * 2 + 1)); // PA9: 1 -> Run (Display aktiv) // MODERy[1:0] = 01: Output mode GPIOA->MODER |= (1UL << (9 * 2 + 0)); GPIOA->MODER &= ~(1UL << (9 * 2 + 1)); // PF3 auf LOW setzen (Display ein) GPIOF->BRR = (1UL << 3); // PA9 auf HIGH setzen (Display ein) GPIOA->BSRR = (1UL << 9); } // ========== SPI-KONFIGURATION ========== /** * SPI-Schnittstelle konfigurieren * Aktiviert SPI1 im Master-Modus und konfiguriert die entsprechenden Pins */ void LCD_SPI_config(void) { // Taktversorgung für GPIO-Ports aktivieren RCC->IOPENR |= (RCC_IOPENR_GPIOAEN | RCC_IOPENR_GPIOBEN | RCC_IOPENR_GPIOFEN); // Taktversorgung für SPI1 aktivieren RCC->APBENR2 |= (RCC_APBENR2_SPI1EN); // SPI mit Standardwerten zurücksetzen, SPI deaktiviert SPI1->CR1 = 0x0; SPI1->CR2 = 0x0; // Data Pin (PB5) auf Alternate Function Mode umstellen // MODERy[1:0] = 10: Alternate function mode LCD_PORT_DATA->MODER &= ~(1UL << (LCD_PIN_BIT_DATA * 2 + 0)); // Bit 0 löschen LCD_PORT_DATA->MODER |= (1UL << (LCD_PIN_BIT_DATA * 2 + 1)); // Bit 1 setzen // Data Pin Alternate Function auf AF0 setzen (SPI1_MOSI) // AFRLy[3:0] = 0000: AF0 LCD_PORT_DATA->AFR[LCD_PIN_BIT_DATA / 8] &= ~(1UL << (LCD_PIN_BIT_DATA * 4 + 0)); LCD_PORT_DATA->AFR[LCD_PIN_BIT_DATA / 8] &= ~(1UL << (LCD_PIN_BIT_DATA * 4 + 1)); LCD_PORT_DATA->AFR[LCD_PIN_BIT_DATA / 8] &= ~(1UL << (LCD_PIN_BIT_DATA * 4 + 2)); LCD_PORT_DATA->AFR[LCD_PIN_BIT_DATA / 8] &= ~(1UL << (LCD_PIN_BIT_DATA * 4 + 3)); // Clock Pin (PB3) auf Alternate Function Mode umstellen // MODERy[1:0] = 10: Alternate function mode LCD_PORT_CLOCK->MODER &= ~(1UL << (LCD_PIN_BIT_CLOCK * 2 + 0)); LCD_PORT_CLOCK->MODER |= (1UL << (LCD_PIN_BIT_CLOCK * 2 + 1)); // Clock Pin Alternate Function auf AF0 setzen (SPI1_SCK) // AFRLy[3:0] = 0000: AF0 LCD_PORT_CLOCK->AFR[LCD_PIN_BIT_CLOCK / 8] &= ~(1UL << (LCD_PIN_BIT_CLOCK * 4 + 0)); LCD_PORT_CLOCK->AFR[LCD_PIN_BIT_CLOCK / 8] &= ~(1UL << (LCD_PIN_BIT_CLOCK * 4 + 1)); LCD_PORT_CLOCK->AFR[LCD_PIN_BIT_CLOCK / 8] &= ~(1UL << (LCD_PIN_BIT_CLOCK * 4 + 2)); LCD_PORT_CLOCK->AFR[LCD_PIN_BIT_CLOCK / 8] &= ~(1UL << (LCD_PIN_BIT_CLOCK * 4 + 3)); // SPI aktivieren // MSTR: Master mode, SPE: SPI enable SPI1->CR1 |= (SPI_CR1_MSTR | SPI_CR1_SPE); } // ========== STEUERUNGSFUNKTIONEN ========== /** * Reset-Pin des Displays steuern * @param iReset: 1 = Reset aktiv (LOW), 0 = Reset inaktiv (HIGH) */ void LCD_Reset(int iReset) { if(iReset) LCD_PORT_RESET->BRR = (LCD_PIN_RESET); // Pin auf 0 setzen (Reset aktiv) else LCD_PORT_RESET->BSRR = (LCD_PIN_RESET); // Pin auf 1 setzen (Normal-Betrieb) } /** * Chip Select Pin steuern * @param iSelect: 1 = Display selektiert (LOW), 0 = Display nicht selektiert (HIGH) */ void LCD_Select(int iSelect) { if(iSelect) LCD_PORT_SELECT->BRR = (LCD_PIN_SELECT); // Pin auf 0 setzen (aktiv) else LCD_PORT_SELECT->BSRR = (LCD_PIN_SELECT); // Pin auf 1 setzen (inaktiv) } /** * Command/Data Pin steuern * @param iCommand: 1 = Command-Modus (LOW), 0 = Data-Modus (HIGH) */ void LCD_Command(int iCommand) { if(iCommand) LCD_PORT_CMD_DATA->BRR = (LCD_PIN_CMD_DATA); // Pin auf 0 (Command) else LCD_PORT_CMD_DATA->BSRR = (LCD_PIN_CMD_DATA); // Pin auf 1 (Data) } /** * Hintergrundbeleuchtung steuern * @param iPowerOn: 1 = Beleuchtung ein (HIGH), 0 = Beleuchtung aus (LOW) */ void LCD_Backlight(int iPowerOn) { if(iPowerOn) LCD_PORT_BACKLIGHT->BSRR = (LCD_PIN_BACKLIGHT); // Pin auf 1 (ein) else LCD_PORT_BACKLIGHT->BRR = (LCD_PIN_BACKLIGHT); // Pin auf 0 (aus) } // ========== SPI-KOMMUNIKATION ========== /** * Ein Byte über SPI senden * Wartet auf freien Sendepuffer und auf Ende der Übertragung * @param u8_byte: Zu sendendes Byte */ void LCD_SPI_send_byte(uint8_t u8_byte) { // Warten bis Sendepuffer leer ist // TXE = 1: Tx buffer empty while(!(SPI1->SR & SPI_SR_TXE)) { } // Byte in Datenregister schreiben (startet Übertragung) SPI1->DR = u8_byte; // Warten bis Übertragung abgeschlossen ist // BSY = 1: SPI is busy in communication while((SPI1->SR & SPI_SR_BSY)) { } } // ========== DISPLAY-SETUP ========== /** * Display-Setup durchführen * Führt die Reset-Sequenz mit korrekten Timing-Delays durch */ void LCD_setup(void) { LCD_Select(1); // Display selektieren LCD_Reset(0); // Reset deaktivieren delay(50); // 50ms warten LCD_Reset(1); // Reset aktivieren delay(100); // 100ms warten LCD_Reset(0); // Reset deaktivieren delay(100); // 100ms warten } /** * Ein Command-Byte an das Display senden * Aktiviert Command-Modus und sendet Byte über SPI * @param u8_byte: Command-Byte */ void LCD_send_CMD_byte(uint8_t u8_byte) { LCD_Select(1); // Display selektieren LCD_Command(1); // Command-Modus aktivieren LCD_SPI_send_byte(u8_byte); // Byte senden // NOP-Befehle für Timing-Delay (Software-Delay) asm(" nop "); asm(" nop "); asm(" nop "); asm(" nop "); asm(" nop "); asm(" nop "); asm(" nop "); asm(" nop "); asm(" nop "); asm(" nop "); LCD_Select(0); // Display deselektieren } /** * Ein Data-Byte an das Display senden * Aktiviert Data-Modus und sendet Byte über SPI * @param u8_byte: Data-Byte */ void LCD_send_DATA_byte(uint8_t u8_byte) { LCD_Select(1); // Display selektieren LCD_Command(0); // Data-Modus aktivieren LCD_SPI_send_byte(u8_byte); // Byte senden // NOP-Befehle für Timing-Delay asm(" nop "); asm(" nop "); asm(" nop "); asm(" nop "); asm(" nop "); asm(" nop "); asm(" nop "); asm(" nop "); asm(" nop "); asm(" nop "); LCD_Select(0); // Display deselektieren } // ========== DISPLAY-INITIALISIERUNG ========== /** * Display vollständig initialisieren * Sendet alle notwendigen Initialisierungs-Befehle an den Display-Controller * Diese Sequenz wurde durch Reverse-Engineering des Original-Geräts ermittelt */ void LCD_init(void) { disable_interrupts(); // Interrupts deaktivieren für unterbrechungsfreie Initialisierung // Initialisierungs-Sequenz (herstellerspezifisch) LCD_send_CMD_byte(0xFF); // Erweiterter Befehlssatz LCD_send_DATA_byte(0xA5); LCD_send_CMD_byte(0x3E); // Internal Timing LCD_send_DATA_byte(0x08); LCD_send_CMD_byte(0x3A); // Pixel Format LCD_send_DATA_byte(0x65); // 16-bit RGB565 LCD_send_CMD_byte(0x82); // Power Control LCD_send_DATA_byte(0x00); LCD_send_CMD_byte(0x98); LCD_send_DATA_byte(0x00); LCD_send_CMD_byte(0x63); // VCOM Control LCD_send_DATA_byte(0x0F); LCD_send_CMD_byte(0x64); LCD_send_DATA_byte(0x0F); LCD_send_CMD_byte(0xB4); // Display Inversion Control LCD_send_DATA_byte(0x34); LCD_send_CMD_byte(0xB5); LCD_send_DATA_byte(0x30); LCD_send_CMD_byte(0x83); LCD_send_DATA_byte(0x13); LCD_send_CMD_byte(0x86); LCD_send_DATA_byte(0x04); LCD_send_CMD_byte(0x87); LCD_send_DATA_byte(0x12); LCD_send_CMD_byte(0x88); LCD_send_DATA_byte(0x09); LCD_send_CMD_byte(0x89); LCD_send_DATA_byte(0x2F); LCD_send_CMD_byte(0x93); LCD_send_DATA_byte(0x63); LCD_send_CMD_byte(0x96); LCD_send_DATA_byte(0x81); LCD_send_CMD_byte(0xC3); LCD_send_DATA_byte(0x11); LCD_send_CMD_byte(0xE6); LCD_send_DATA_byte(0x00); LCD_send_CMD_byte(0x99); LCD_send_DATA_byte(0x01); LCD_send_CMD_byte(0x44); LCD_send_DATA_byte(0x00); // Gamma-Korrektur Positive Polarität (0x70-0x7F) LCD_send_CMD_byte(0x70); LCD_send_DATA_byte(0x07); LCD_send_CMD_byte(0x71); LCD_send_DATA_byte(0x1B); LCD_send_CMD_byte(0x72); LCD_send_DATA_byte(0x08); LCD_send_CMD_byte(0x73); LCD_send_DATA_byte(0x0F); LCD_send_CMD_byte(0x74); LCD_send_DATA_byte(0x16); LCD_send_CMD_byte(0x75); LCD_send_DATA_byte(0x1A); LCD_send_CMD_byte(0x76); LCD_send_DATA_byte(0x3C); LCD_send_CMD_byte(0x77); LCD_send_DATA_byte(0x0A); LCD_send_CMD_byte(0x78); LCD_send_DATA_byte(0x05); LCD_send_CMD_byte(0x79); LCD_send_DATA_byte(0x3A); LCD_send_CMD_byte(0x7A); LCD_send_DATA_byte(0x06); LCD_send_CMD_byte(0x7B); LCD_send_DATA_byte(0x0B); LCD_send_CMD_byte(0x7C); LCD_send_DATA_byte(0x12); LCD_send_CMD_byte(0x7D); LCD_send_DATA_byte(0x0B); LCD_send_CMD_byte(0x7E); LCD_send_DATA_byte(0x0A); LCD_send_CMD_byte(0x7F); LCD_send_DATA_byte(0x08); // Gamma-Korrektur Negative Polarität (0xA0-0xAF) LCD_send_CMD_byte(0xA0); LCD_send_DATA_byte(0x0B); LCD_send_CMD_byte(0xA1); LCD_send_DATA_byte(0x36); LCD_send_CMD_byte(0xA2); LCD_send_DATA_byte(0x0B); LCD_send_CMD_byte(0xA3); LCD_send_DATA_byte(0x0C); LCD_send_CMD_byte(0xA4); LCD_send_DATA_byte(0x08); LCD_send_CMD_byte(0xA5); LCD_send_DATA_byte(0x22); LCD_send_CMD_byte(0xA6); LCD_send_DATA_byte(0x43); LCD_send_CMD_byte(0xA7); LCD_send_DATA_byte(0x04); LCD_send_CMD_byte(0xA8); LCD_send_DATA_byte(0x05); LCD_send_CMD_byte(0xA9); LCD_send_DATA_byte(0x3F); LCD_send_CMD_byte(0xAA); LCD_send_DATA_byte(0x0A); LCD_send_CMD_byte(0xAB); LCD_send_DATA_byte(0x11); LCD_send_CMD_byte(0xAC); LCD_send_DATA_byte(0x0D); LCD_send_CMD_byte(0xAD); LCD_send_DATA_byte(0x06); LCD_send_CMD_byte(0xAE); LCD_send_DATA_byte(0x3B); LCD_send_CMD_byte(0xAF); LCD_send_DATA_byte(0x07); // Zurück zum Standard-Befehlssatz LCD_send_CMD_byte(0xFF); LCD_send_DATA_byte(0x00); // Memory Access Control (Rotation) LCD_send_CMD_byte(0x36); LCD_send_DATA_byte(0x40); // Sleep Out (Display aufwachen) LCD_send_CMD_byte(0x11); // Display On (Display einschalten) LCD_send_CMD_byte(0x29); enable_interrupts(); // Interrupts wieder aktivieren } // ========== FARBÜBERTRAGUNG ========== /** * RGB565-Farbwert an das Display senden * RGB565: 5 Bit Rot, 6 Bit Grün, 5 Bit Blau (16 Bit gesamt) * @param u16_rgb565: 16-Bit Farbwert im RGB565-Format */ void LCD_SendRGB565(uint16_t u16_rgb565) { LCD_send_DATA_byte((uint8_t)(u16_rgb565 >> 8)); // High-Byte senden LCD_send_DATA_byte((uint8_t)u16_rgb565 & 0xFF); // Low-Byte senden } /** * Ein komplettes Bild auf das Display übertragen * @param image_data: Pointer auf Bild-Array (RGB565-Format) * @param width: Bildbreite in Pixeln * @param height: Bildhöhe in Pixeln */ void LCD_SendImage(const uint16_t* image_data, uint16_t width, uint16_t height) { // Durch alle Zeilen iterieren for (uint16_t y = 0; y < height; y++) { // Durch alle Spalten iterieren for (uint16_t x = 0; x < width; x++) { // Pixel-Index berechnen (zeilenweise) uint16_t pixel_index = y * width + x; // Pixel-Farbwert senden LCD_SendRGB565(image_data[pixel_index]); } } } // ========== TEXT-RENDERING ========== /** * Ein einzelnes Zeichen auf dem Display zeichnen * Verwendet 5x7 Pixel Font aus fontandcolor.h * @param x: X-Position (Spalte) * @param y: Y-Position (Zeile) * @param c: Zu zeichnendes Zeichen * @param color: Vordergrundfarbe (Text) * @param bg: Hintergrundfarbe */ void LCD_DrawChar(int x, int y, char c, uint16_t color, uint16_t bg) { disable_interrupts(); // Interrupts deaktivieren für flackerfreies Zeichnen // Nur druckbare ASCII-Zeichen (32-126) werden unterstützt if (c < 32 || c > 126) return; // Index im Font-Array berechnen (0 = Leerzeichen) uint8_t index = c - 32; // Durch alle Spalten des Zeichens iterieren (5 Spalten) for (uint8_t col = 0; col < FONT5X7_WIDTH; col++) { // Font-Daten für aktuelle Spalte holen uint8_t line = font5x7[index][col]; // Durch alle Zeilen des Zeichens iterieren (7 Zeilen) for (uint8_t row = 0; row < FONT5X7_HEIGHT; row++) { // Prüfen ob Bit gesetzt ist (Pixel an/aus) uint16_t pixel_color = (line & (1 << row)) ? color : bg; // Pixel-Position setzen und Farbe senden LCD_SetWindow(x + col, y + row, x + col, y + row); LCD_SendRGB565(pixel_color); } } enable_interrupts(); // Interrupts wieder aktivieren } /** * Einen Text-String auf dem Display zeichnen * @param x: Start X-Position * @param y: Start Y-Position * @param str: Zu zeichnender Text (Null-terminiert) * @param color: Textfarbe * @param bg: Hintergrundfarbe */ void LCD_DrawString(int x, int y, const char *str, uint16_t color, uint16_t bg) { // Durch String iterieren bis Null-Terminator erreicht while (*str) { // Aktuelles Zeichen zeichnen LCD_DrawChar(x, y, *str, color, bg); x += 6; // 6 Pixel weiter (5 Pixel Zeichen + 1 Pixel Abstand) str++; // Nächstes Zeichen } } // ========== BILDSCHIRM-VERWALTUNG ========== /** * Gesamten Bildschirm mit einer Farbe füllen * Verwendet fest codierte Dimensionen 128x160 * @param color: Füllfarbe (RGB565) */ void LCD_FillScreen(uint16_t color) { // Zeichenbereich auf ganzen Bildschirm setzen LCD_SetWindow(0, 0, 127, 159); // Alle Pixel einzeln füllen (128 * 160 = 20480 Pixel) for (uint32_t i = 0; i < (128 * 160); i++) { LCD_send_DATA_byte(color >> 8); // High-Byte LCD_send_DATA_byte(color & 0xFF); // Low-Byte } } /** * Display löschen (mit Farbe füllen) * Verwendet aktuelle LCD_WIDTH und LCD_HEIGHT Werte * @param color: Löschfarbe (RGB565) */ void LCD_Clear(uint16_t color) { // Zeichenbereich auf gesamten sichtbaren Bereich setzen LCD_SetWindow(0, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1); LCD_send_CMD_byte(0x2C); // RAMWR (RAM Write) Befehl // Anzahl der Pixel berechnen uint32_t pixels = (uint32_t)LCD_WIDTH * LCD_HEIGHT; // Alle Pixel mit gewünschter Farbe füllen for (uint32_t i = 0; i < pixels; i++) { LCD_SendRGB565(color); } } /** * Display-Rotation einstellen * Ändert die Orientierung des Displays und passt WIDTH/HEIGHT an * @param rot: Rotation (0-3) * 0 = 0° (Portrait, 128x160) * 1 = 90° (Landscape, 160x128) * 2 = 180° (Portrait invertiert, 128x160) * 3 = 270° (Landscape invertiert, 160x128) */ void LCD_SetRotation(uint8_t rot) { LCD_send_CMD_byte(0x36); // MADCTL (Memory Access Control) Befehl // Nur die unteren 2 Bits verwenden (0-3) switch (rot & 3) { case 0: LCD_send_DATA_byte(0x00); // Normal LCD_WIDTH = 128; LCD_HEIGHT = 160; break; case 1: LCD_send_DATA_byte(0x60); // 90° im Uhrzeigersinn LCD_WIDTH = 160; LCD_HEIGHT = 128; break; case 2: LCD_send_DATA_byte(0xC0); // 180° LCD_WIDTH = 128; LCD_HEIGHT = 160; break; case 3: LCD_send_DATA_byte(0xA0); // 270° im Uhrzeigersinn LCD_WIDTH = 160; LCD_HEIGHT = 128; break; } } /** * Zeichenbereich (Window) auf dem Display definieren * Alle nachfolgenden Pixel-Operationen erfolgen in diesem Bereich * @param x1: Start X-Koordinate * @param y1: Start Y-Koordinate * @param x2: End X-Koordinate * @param y2: End Y-Koordinate */ void LCD_SetWindow(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { // CASET (Column Address Set) - X-Bereich setzen LCD_send_CMD_byte(0x2A); LCD_send_DATA_byte(x1 >> 8); // Start X High-Byte LCD_send_DATA_byte(x1 & 0xFF); // Start X Low-Byte LCD_send_DATA_byte(x2 >> 8); // End X High-Byte LCD_send_DATA_byte(x2 & 0xFF); // End X Low-Byte // RASET (Row Address Set) - Y-Bereich setzen LCD_send_CMD_byte(0x2B); LCD_send_DATA_byte(y1 >> 8); // Start Y High-Byte LCD_send_DATA_byte(y1 & 0xFF); // Start Y Low-Byte LCD_send_DATA_byte(y2 >> 8); // End Y High-Byte LCD_send_DATA_byte(y2 & 0xFF); // End Y Low-Byte // RAMWR (Memory Write) - Bereit für Pixel-Daten LCD_send_CMD_byte(0x2C); } /** * 16-Bit Farbwert schreiben * Helper-Funktion zum Senden von Farbdaten * @param color: RGB565 Farbwert */ void LCD_WriteData16(uint16_t color) { LCD_send_DATA_byte(color >> 8); // High Byte LCD_send_DATA_byte(color & 0xFF); // Low Byte } // ========== PIXEL UND LINIEN ========== /** * Einzelnes Pixel zeichnen * @param x: X-Koordinate * @param y: Y-Koordinate * @param color: Pixelfarbe (RGB565) */ void LCD_DrawPixel(int x, int y, uint16_t color) { // Grenzprüfung - Pixel außerhalb des sichtbaren Bereichs ignorieren if (x < 0 || x >= LCD_WIDTH || y < 0 || y >= LCD_HEIGHT) { return; } // Window auf einzelnes Pixel setzen LCD_SetWindow(x, y, x, y); // Farbe schreiben LCD_WriteData16(color); } /** * Horizontale Linie zeichnen * @param x: Start X-Position * @param y: Y-Position * @param w: Breite der Linie * @param color: Linienfarbe * @param bgcolor: Hintergrundfarbe (für Löschung) */ void LCD_DrawHLine(uint16_t x, uint16_t y, uint16_t w, uint16_t color, uint16_t bgcolor) { // Verwendet FillRect mit Höhe 1 LCD_FillRect(x, y, w, 1, color, bgcolor); } /** * Vertikale Linie zeichnen * @param x: X-Position * @param y: Start Y-Position * @param h: Höhe der Linie * @param color: Linienfarbe * @param bgcolor: Hintergrundfarbe */ void LCD_DrawVLine(uint16_t x, uint16_t y, uint16_t h, uint16_t color, uint16_t bgcolor) { // Verwendet FillRect mit Breite 1 LCD_FillRect(x, y, 1, h, color, bgcolor); } // ========== RECHTECK-FUNKTIONEN ========== /** * Rechteck-Rahmen zeichnen (nicht gefüllt) * @param x: X-Position der oberen linken Ecke * @param y: Y-Position der oberen linken Ecke * @param w: Breite des Rechtecks * @param h: Höhe des Rechtecks * @param color: Rahmenfarbe * @param bgcolor: Innenfarbe/Hintergrundfarbe */ void LCD_DrawRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color, uint16_t bgcolor) { // Fülle kompletten Bereich mit Hintergrundfarbe (nur wenn unterschiedlich) if (bgcolor != color) { LCD_FillRect(x, y, w, h, bgcolor, bgcolor); } // Zeichne die 4 Rahmenlinien LCD_DrawHLine(x, y, w, color, bgcolor); // Obere Linie LCD_DrawHLine(x, y + h - 1, w, color, bgcolor); // Untere Linie LCD_DrawVLine(x, y, h, color, bgcolor); // Linke Linie LCD_DrawVLine(x + w - 1, y, h, color, bgcolor); // Rechte Linie } /** * Gefülltes Rechteck zeichnen * @param x: X-Position der oberen linken Ecke * @param y: Y-Position der oberen linken Ecke * @param w: Breite * @param h: Höhe * @param color: Füllfarbe * @param bgcolor: Hintergrundfarbe (für vorheriges Löschen) */ void LCD_FillRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color, uint16_t bgcolor) { // Prüfen ob Rechteck vollständig außerhalb des Displays liegt if (x >= LCD_WIDTH || y >= LCD_HEIGHT) return; // Clipping: Rechteck auf sichtbaren Bereich beschränken if (x + w > LCD_WIDTH) w = LCD_WIDTH - x; if (y + h > LCD_HEIGHT) h = LCD_HEIGHT - y; // Schritt 1: Hintergrund löschen (falls nötig) if (bgcolor != color) { // Zeilenweise Hintergrund zeichnen for (uint16_t row = 0; row < h; row++) { LCD_SetWindow(x, y + row, x + w - 1, y + row); for (uint16_t col = 0; col < w; col++) { LCD_SendRGB565(bgcolor); } } } // Schritt 2: Vordergrund zeichnen for (uint16_t row = 0; row < h; row++) { LCD_SetWindow(x, y + row, x + w - 1, y + row); for (uint16_t col = 0; col < w; col++) { LCD_SendRGB565(color); } } } // ========== KREIS-FUNKTIONEN ========== /** * Kreis-Umriss zeichnen (nicht gefüllt) * Verwendet Bresenham's Kreis-Algorithmus * @param x0: X-Koordinate des Mittelpunkts * @param y0: Y-Koordinate des Mittelpunkts * @param radius: Radius des Kreises * @param color: Linienfarbe * @param bgcolor: Hintergrundfarbe */ void LCD_DrawCircle(int x0, int y0, int radius, uint16_t color, uint16_t bgcolor) { int x = radius; int y = 0; int err = 0; // Optional: Lösche alten Bereich mit Hintergrundfarbe if (bgcolor != color) { LCD_FillCircle(x0, y0, radius + 1, color, bgcolor); } // Bresenham's Kreis-Algorithmus // Zeichnet 8 symmetrische Punkte pro Iteration while (x >= y) { // 8 Oktanten des Kreises zeichnen LCD_DrawPixel(x0 + x, y0 + y, color); // Oktant 1 LCD_DrawPixel(x0 + y, y0 + x, color); // Oktant 2 LCD_DrawPixel(x0 - y, y0 + x, color); // Oktant 3 LCD_DrawPixel(x0 - x, y0 + y, color); // Oktant 4 LCD_DrawPixel(x0 - x, y0 - y, color); // Oktant 5 LCD_DrawPixel(x0 - y, y0 - x, color); // Oktant 6 LCD_DrawPixel(x0 + y, y0 - x, color); // Oktant 7 LCD_DrawPixel(x0 + x, y0 - y, color); // Oktant 8 // Fehlerterm anpassen if (err <= 0) { y += 1; err += 2*y + 1; } if (err > 0) { x -= 1; err -= 2*x + 1; } } } /** * Gefüllten Kreis zeichnen * Optimierte Version: setzt Window nur einmal und sendet alle Pixel sequenziell * @param x0: X-Koordinate des Mittelpunkts * @param y0: Y-Koordinate des Mittelpunkts * @param radius: Radius des Kreises * @param color: Füllfarbe * @param bgcolor: Hintergrundfarbe */ void LCD_FillCircle(int x0, int y0, int radius, uint16_t color, uint16_t bgcolor) { disable_interrupts(); // Interrupts aus für flüssiges Zeichnen // Bounding Box berechnen mit Extra-Rand für sauberes Löschen int x_min = x0 - radius - 1; int y_min = y0 - radius - 1; int x_max = x0 + radius + 1; int y_max = y0 + radius + 1; // Clipping auf Display-Grenzen if (x_min < 0) x_min = 0; if (y_min < 0) y_min = 0; if (x_max >= LCD_WIDTH) x_max = LCD_WIDTH - 1; if (y_max >= LCD_HEIGHT) y_max = LCD_HEIGHT - 1; // Radius quadriert für Distanz-Berechnung (vermeidet Wurzelziehen) int radius_sq = radius * radius; // SetWindow NUR EINMAL für gesamte Bounding Box (Performance-Optimierung!) LCD_SetWindow(x_min, y_min, x_max, y_max); // Alle Pixel in der Bounding Box sequenziell senden for (int py = y_min; py <= y_max; py++) { for (int px = x_min; px <= x_max; px++) { // Distanz zum Mittelpunkt berechnen int dx = px - x0; int dy = py - y0; int dist_sq = dx * dx + dy * dy; // Prüfen ob Pixel innerhalb des Kreises liegt if (dist_sq <= radius_sq) { LCD_SendRGB565(color); // Inneres: Füllfarbe } else { LCD_SendRGB565(bgcolor); // Außerhalb: Hintergrundfarbe } } } enable_interrupts(); // Interrupts wieder aktivieren }