From a841de6bd1313c6e178cef4011ac2194809d1db9 Mon Sep 17 00:00:00 2001 From: PoliEcho Date: Tue, 16 Sep 2025 22:02:10 +0200 Subject: [PATCH] add knob and fix text --- CMakeLists.txt | 5 +- GC9A01/gc9a01.c | 234 ++++++++++++++++++++++++++++++++++++++++-------- GC9A01/gc9a01.h | 3 +- control.cpp | 14 +++ control.h | 7 ++ display.cpp | 22 +++-- display.h | 2 +- pins.h | 4 + smart_alarm.cpp | 40 +++++++-- 9 files changed, 279 insertions(+), 52 deletions(-) create mode 100644 control.cpp create mode 100644 control.h create mode 100644 pins.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1272bef..a45383d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,7 +35,8 @@ file(GLOB GC9A01_SOURCES add_executable(smart_alarm smart_alarm.cpp net_utils.cpp timezones.cpp display.cpp - ${GC9A01_SOURCES}) + ${GC9A01_SOURCES} + control.cpp) pico_set_program_name(smart_alarm "smart_alarm") pico_set_program_version(smart_alarm "0.2") @@ -63,6 +64,8 @@ target_link_libraries(smart_alarm pico_cyw43_arch_lwip_threadsafe_background pico_lwip_sntp hardware_rtc + hardware_adc + hardware_pwm ) target_compile_definitions(smart_alarm PRIVATE diff --git a/GC9A01/gc9a01.c b/GC9A01/gc9a01.c index 6145cbb..5bc575f 100644 --- a/GC9A01/gc9a01.c +++ b/GC9A01/gc9a01.c @@ -758,53 +758,215 @@ void gc9a01_text(gc9a01_GC9A01_obj_t *self, GFXfont *font, char *str, uint16_t x } } -void gc9a01_text_gfx(gc9a01_GC9A01_obj_t *self, GFXfont *font, char *str, uint16_t x0, uint16_t y0, uint16_t color) { - int16_t cursor_x = x0; - int16_t cursor_y = y0; +void gc9a01_text_gfx_transparent(gc9a01_GC9A01_obj_t *self, GFXfont *font, const char *str, uint16_t x0, uint16_t y0, uint16_t color) { + if (!str || !*str) return; - // Process each character in the string - for (int i = 0; str[i] != '\0'; i++) { - char c = str[i]; + // First pass: calculate string dimensions + int16_t min_x = 32767, max_x = -32768; + int16_t min_y = 32767, max_y = -32768; + int16_t cursor_x = 0; + bool has_visible_chars = false; - // Check if character is in font range - if (c < font->first || c > font->last) { - continue; // Skip unsupported characters - } + for (int i = 0; str[i] != '\0'; i++) { + char c = str[i]; + if (c < font->first || c > font->last) continue; - // Get glyph data for this character - uint8_t glyph_index = c - font->first; - GFXglyph *glyph = &font->glyph[glyph_index]; - uint8_t *bitmap = font->bitmap; + uint8_t glyph_index = c - font->first; + GFXglyph *glyph = &font->glyph[glyph_index]; - // Calculate starting position for this glyph - int16_t glyph_x = cursor_x + glyph->xOffset; - int16_t glyph_y = cursor_y + glyph->yOffset; + int16_t glyph_x = cursor_x + glyph->xOffset; + int16_t glyph_y = glyph->yOffset; + int16_t glyph_x2 = glyph_x + glyph->width - 1; + int16_t glyph_y2 = glyph_y + glyph->height - 1; - // Render the glyph bitmap - uint32_t bitmap_offset = glyph->bitmapOffset; - uint8_t bit_index = 0; + // Track bounding box + if (glyph_x < min_x) min_x = glyph_x; + if (glyph_x2 > max_x) max_x = glyph_x2; + if (glyph_y < min_y) min_y = glyph_y; + if (glyph_y2 > max_y) max_y = glyph_y2; - for (int16_t row = 0; row < glyph->height; row++) { - for (int16_t col = 0; col < glyph->width; col++) { - // Get current bit from bitmap - uint8_t byte_index = bitmap_offset + (bit_index / 8); - uint8_t bit_position = 7 - (bit_index % 8); - uint8_t pixel = (bitmap[byte_index] >> bit_position) & 0x01; + cursor_x += glyph->xAdvance; + has_visible_chars = true; + } - // Draw pixel if bit is set - if (pixel) { - gc9a01_draw_pixel(self, glyph_x + col, glyph_y + row, color); - } + if (!has_visible_chars) return; - bit_index++; - } - } + // Calculate buffer dimensions + uint16_t buffer_width = max_x - min_x + 1; + uint16_t buffer_height = max_y - min_y + 1; + uint32_t buffer_pixels = buffer_width * buffer_height; - // Advance cursor to next character position - cursor_x += glyph->xAdvance; - } + // Allocate mask and color buffers + uint8_t *mask = malloc(buffer_pixels); + uint16_t *buffer = malloc(buffer_pixels * 2); // 2 bytes per pixel + + if (!mask || !buffer) { + if (mask) free(mask); + if (buffer) free(buffer); + return; + } + + // Initialize mask to 0 (transparent) + memset(mask, 0, buffer_pixels); + + // Second pass: render characters into buffer and set mask + cursor_x = 0; + uint16_t color_swapped = _swap_bytes(color); + + for (int i = 0; str[i] != '\0'; i++) { + char c = str[i]; + if (c < font->first || c > font->last) continue; + + uint8_t glyph_index = c - font->first; + GFXglyph *glyph = &font->glyph[glyph_index]; + uint8_t *bitmap = font->bitmap; + + int16_t glyph_x = cursor_x + glyph->xOffset - min_x; // Offset relative to buffer + int16_t glyph_y = glyph->yOffset - min_y; + + // Render glyph bitmap + uint32_t bitmap_offset = glyph->bitmapOffset; + uint16_t bit_index = 0; + + for (int16_t row = 0; row < glyph->height; row++) { + for (int16_t col = 0; col < glyph->width; col++) { + // Get pixel from font bitmap + uint8_t byte_index = bitmap_offset + (bit_index / 8); + uint8_t bit_position = 7 - (bit_index % 8); + uint8_t pixel = (bitmap[byte_index] >> bit_position) & 0x01; + + // Set pixel in buffer and mask if foreground + if (pixel) { + uint32_t buffer_idx = (glyph_y + row) * buffer_width + (glyph_x + col); + if (buffer_idx < buffer_pixels) { + buffer[buffer_idx] = color_swapped; + mask[buffer_idx] = 1; + } + } + bit_index++; + } + } + + cursor_x += glyph->xAdvance; + } + + // Draw only masked pixels using individual pixel writes + for (uint16_t row = 0; row < buffer_height; row++) { + for (uint16_t col = 0; col < buffer_width; col++) { + uint32_t idx = row * buffer_width + col; + if (mask[idx]) { + // Convert back from swapped format for pixel drawing + uint16_t pixel_color = _swap_bytes(buffer[idx]); + gc9a01_draw_pixel(self, x0 + min_x + col, y0 + min_y + row, pixel_color); + } + } + } + + free(mask); + free(buffer); } + +void gc9a01_text_gfx_buffered(gc9a01_GC9A01_obj_t *self, GFXfont *font, const char *str, uint16_t x0, uint16_t y0, uint16_t fg_color, uint16_t bg_color) { + if (!str || !*str) return; + + // First pass: calculate string dimensions + int16_t min_x = 32767, max_x = -32768; + int16_t min_y = 32767, max_y = -32768; + int16_t cursor_x = 0; + + for (int i = 0; str[i] != '\0'; i++) { + char c = str[i]; + if (c < font->first || c > font->last) continue; + + uint8_t glyph_index = c - font->first; + GFXglyph *glyph = &font->glyph[glyph_index]; + + int16_t glyph_x = cursor_x + glyph->xOffset; + int16_t glyph_y = glyph->yOffset; + int16_t glyph_x2 = glyph_x + glyph->width - 1; + int16_t glyph_y2 = glyph_y + glyph->height - 1; + + // Track bounding box + if (glyph_x < min_x) min_x = glyph_x; + if (glyph_x2 > max_x) max_x = glyph_x2; + if (glyph_y < min_y) min_y = glyph_y; + if (glyph_y2 > max_y) max_y = glyph_y2; + + cursor_x += glyph->xAdvance; + } + + // Calculate buffer dimensions + uint16_t buffer_width = max_x - min_x + 1; + uint16_t buffer_height = max_y - min_y + 1; + uint32_t buffer_size = buffer_width * buffer_height * 2; // 2 bytes per pixel + + // Allocate buffer + uint16_t *buffer = malloc(buffer_size); + if (!buffer) return; + + // Initialize buffer with background color + uint16_t bg_swapped = _swap_bytes(bg_color); + for (uint32_t i = 0; i < buffer_width * buffer_height; i++) { + buffer[i] = bg_swapped; + } + + // Second pass: render characters into buffer + cursor_x = 0; + uint16_t fg_swapped = _swap_bytes(fg_color); + + for (int i = 0; str[i] != '\0'; i++) { + char c = str[i]; + if (c < font->first || c > font->last) continue; + + uint8_t glyph_index = c - font->first; + GFXglyph *glyph = &font->glyph[glyph_index]; + uint8_t *bitmap = font->bitmap; + + int16_t glyph_x = cursor_x + glyph->xOffset - min_x; // Offset relative to buffer + int16_t glyph_y = glyph->yOffset - min_y; + + // Render glyph bitmap + uint32_t bitmap_offset = glyph->bitmapOffset; + uint16_t bit_index = 0; + + for (int16_t row = 0; row < glyph->height; row++) { + for (int16_t col = 0; col < glyph->width; col++) { + // Get pixel from font bitmap + uint8_t byte_index = bitmap_offset + (bit_index / 8); + uint8_t bit_position = 7 - (bit_index % 8); + uint8_t pixel = (bitmap[byte_index] >> bit_position) & 0x01; + + // Only draw foreground pixels (preserve background) + if (pixel) { + uint32_t buffer_idx = (glyph_y + row) * buffer_width + (glyph_x + col); + if (buffer_idx < buffer_width * buffer_height) { + buffer[buffer_idx] = fg_swapped; + } + } + bit_index++; + } + } + + cursor_x += glyph->xAdvance; + } + + // Write entire buffer to display in one transaction + int16_t final_x = x0 + min_x; + int16_t final_y = y0 + min_y; + + gc9a01_set_window(self, final_x, final_y, + final_x + buffer_width - 1, + final_y + buffer_height - 1); + DC_HIGH(); + CS_LOW(); + gc9a01_write_spi(self->spi_obj, (uint8_t*)buffer, buffer_size, self->cs); + CS_HIGH(); + + free(buffer); +} + + void gc9a01_set_rotation(gc9a01_GC9A01_obj_t *self) { uint8_t madctl_value = GC9A01_MADCTL_BGR; diff --git a/GC9A01/gc9a01.h b/GC9A01/gc9a01.h index 75bec75..da14838 100644 --- a/GC9A01/gc9a01.h +++ b/GC9A01/gc9a01.h @@ -101,7 +101,8 @@ void gc9a01_set_rotation(gc9a01_GC9A01_obj_t *self); void gc9a01_hline(gc9a01_GC9A01_obj_t *self, uint16_t x, uint16_t y, uint16_t w, uint16_t color); void gc9a01_vline(gc9a01_GC9A01_obj_t *self, uint16_t x, uint16_t y, uint16_t w, uint16_t color); void gc9a01_text(gc9a01_GC9A01_obj_t *self, GFXfont *font, char *str, uint16_t x0, uint16_t y0, uint16_t fg_color, uint16_t bg_color); -void gc9a01_text_gfx(gc9a01_GC9A01_obj_t *self, GFXfont *font, char *str, uint16_t x0, uint16_t y0, uint16_t color); +void gc9a01_text_gfx_transparent(gc9a01_GC9A01_obj_t *self, GFXfont *font, const char *str, uint16_t x0, uint16_t y0, uint16_t color); +void gc9a01_text_gfx_buffered(gc9a01_GC9A01_obj_t *self, GFXfont *font, const char *str, uint16_t x0, uint16_t y0, uint16_t fg_color, uint16_t bg_color); uint8_t gc9a01_get_color(uint8_t bpp); uint16_t color565(uint8_t r, uint8_t g, uint8_t b); #ifdef __cplusplus diff --git a/control.cpp b/control.cpp new file mode 100644 index 0000000..640de83 --- /dev/null +++ b/control.cpp @@ -0,0 +1,14 @@ +#include + +#include "hardware/adc.h" + +uint8_t get_knob_percentage() { + adc_select_input(0); // Select ADC channel 0 + + uint16_t raw = adc_read(); + + // Convert to percentage (0 to 100%) + uint8_t percentage = (raw * 100) / 4095; + + return percentage; +} diff --git a/control.h b/control.h new file mode 100644 index 0000000..516009b --- /dev/null +++ b/control.h @@ -0,0 +1,7 @@ +#ifndef SMART_ALARM_CONTROL_H +#define SMART_ALARM_CONTROL_H +#include + +uint8_t get_knob_percentage(); + +#endif //SMART_ALARM_CONTROL_H \ No newline at end of file diff --git a/display.cpp b/display.cpp index 2af749e..110fc20 100644 --- a/display.cpp +++ b/display.cpp @@ -70,12 +70,12 @@ gc9a01_GC9A01_obj_t create_lcd() { gc9a01_GC9A01_obj_t display; -void draw_ui_circle() +void draw_ui_circle(uint16_t color) { for (int i = 720; i > 0; i--) { int x = 120 + (int)(119.0 * sin((180.0 + i / 2.0) * M_TWOPI / 360.0)); int y = 120 + (int)(119.0 * cos((180.0 + i / 2.0) * M_TWOPI / 360.0)); - gc9a01_draw_pixel(&display, x, y, MAGENTA); + gc9a01_draw_pixel(&display, x, y, color); } } @@ -89,12 +89,12 @@ void display_start() { gc9a01_fill(&display, BLACK); - draw_ui_circle(); + draw_ui_circle(MAGENTA); } -void print_time() { +void print_time(const bool force_refresh) { datetime_t t; if (rtc_get_datetime(&t)) { GFXfont font = VGA1_16x32; @@ -102,11 +102,17 @@ void print_time() { sprintf(buf,"%02d:%02d:%02d", t.hour, t.min, t.sec); gc9a01_text(&display,&font,buf,120-((16*8)/2),120-16,WHITE, BLACK); - font = Font5x7FixedMono; - sprintf(buf,"%02d.%02d.%04d",t.day,t.month,t.year); - gc9a01_text_gfx(&display,&font,buf,120-((16*10)/2),((120-16)+32)+32,WHITE, BLACK); + static uint8_t last_day = UINT8_MAX; + if (last_day != t.day || force_refresh) { + font = Font5x7FixedMono; + sprintf(buf,"%02d.%02d.%04d",t.day,t.month,t.year); + gc9a01_text_gfx_buffered(&display,&font,buf,120-((6*10)/2),((120-16)+32)+7,WHITE,BLACK); + last_day = t.day; + } } else { - printf("failed to get rtc\n"); + const char* rtc_fail= "NO SYNC!"; + GFXfont font = Font5x7FixedMono; + gc9a01_text_gfx_buffered(&display,&font,rtc_fail,120-((5*8)/2),120-((7*1)/2),RED,BLACK); } } diff --git a/display.h b/display.h index debdd85..1eb4aca 100644 --- a/display.h +++ b/display.h @@ -1,5 +1,5 @@ #ifndef SMART_ALARM_DISPLAY_H #define SMART_ALARM_DISPLAY_H void display_start(); -void print_time(); +void print_time(bool force_refresh = false); #endif //SMART_ALARM_DISPLAY_H \ No newline at end of file diff --git a/pins.h b/pins.h new file mode 100644 index 0000000..27b634a --- /dev/null +++ b/pins.h @@ -0,0 +1,4 @@ +#pragma once + +#define KNOB_PW_PIN 22 +#define KNOB_SIG_PIN 26 \ No newline at end of file diff --git a/smart_alarm.cpp b/smart_alarm.cpp index 0ea71a6..6cf0c4d 100644 --- a/smart_alarm.cpp +++ b/smart_alarm.cpp @@ -11,6 +11,8 @@ #include #include #include +#include "hardware/pwm.h" +#include "hardware/adc.h" #include "lwip/dns.h" #include "lwip/tcp.h" @@ -18,6 +20,8 @@ #include "timezones.h" #include "spi.h" #include "display.h" +#include "pins.h" +#include "control.h" #define NTP_DELTA 2208988800UL @@ -91,6 +95,17 @@ int main() { return 1; } rtc_init(); + gpio_init(KNOB_PW_PIN); + gpio_set_dir(KNOB_PW_PIN, GPIO_OUT); + gpio_put(KNOB_PW_PIN,1); + + adc_init(); + adc_gpio_init(KNOB_SIG_PIN); + adc_select_input(0); + + // PWM for PC speeker + gpio_set_function(11, GPIO_FUNC_PWM); + // sign of life cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, true); @@ -101,6 +116,7 @@ int main() { // add_alarm_in_ms(2000, alarm_callback, NULL, false); printf("connecting to wifi\n"); + print_time(); // show NO SYNC ERROR // connect to wifi cyw43_arch_enable_sta_mode(); if (cyw43_arch_wifi_connect_timeout_ms(wifi_ssid, wifi_pass, @@ -133,13 +149,27 @@ int main() { printf("SNTP initialized, waiting for time sync...\n"); + uint slice_num = pwm_gpio_to_slice_num(11); + + // Generate a 1kHz tone + // 125MHz / 125 = 1MHz PWM clock + pwm_set_clkdiv(slice_num, 125.0f); + + // 1MHz / 1000 = 1kHz audio frequency + pwm_set_wrap(slice_num, 1000); + + // 50% duty cycle for square wave + pwm_set_chan_level(slice_num, PWM_CHAN_B, 500); + + // Enable PWM + pwm_set_enabled(slice_num, true); + + while (true) { - - // Keep the program running to allow SNTP to work - - - // Optional: Print current time periodically to verify sync print_time(); + printf("knob: %d\n",get_knob_percentage()); + pwm_set_wrap(slice_num, 10*get_knob_percentage()); + sleep_ms(500); } cyw43_arch_lwip_end();