From 9f984187eabd0d80b06ec7b0cb8a0ec3e73241d0 Mon Sep 17 00:00:00 2001 From: Advaith Menon Date: Tue, 25 Nov 2025 03:57:12 -0500 Subject: [PATCH] Complete SD Card Support * Add bmp reading library * Modify BLIT to match that in MBED code --- main/CMakeLists.txt | 1 + main/bmp_reader.c | 268 ++++++++++++++++++++++++++++++++++++++++ main/bmp_reader.h | 54 ++++++++ main/goldeloxSerial.c | 4 + main/goldeloxSerial.h | 1 - main/hello_world_main.c | 6 +- main/nvram_settings.h | 11 ++ main/pins.h | 2 + main/sdcardspi.c | 2 +- 9 files changed, 343 insertions(+), 6 deletions(-) create mode 100644 main/bmp_reader.c create mode 100644 main/bmp_reader.h create mode 100644 main/nvram_settings.h diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 492e8c4..2e4a83a 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -3,6 +3,7 @@ idf_component_register(SRCS "hello_world_main.c" "task_lcd.c" "task_button.c" "sdcardspi.c" + "bmp_reader.c" INCLUDE_DIRS "." PRIV_REQUIRES esp_driver_gpio esp_driver_uart diff --git a/main/bmp_reader.c b/main/bmp_reader.c new file mode 100644 index 0000000..89349de --- /dev/null +++ b/main/bmp_reader.c @@ -0,0 +1,268 @@ +#include +#include +#include +#include +#include + +#include "esp_log.h" +#include "goldeloxSerial.h" + +#include "bmp_reader.h" + +#define BMP_BLIT_CHUNK_SIZE 64 + + +static const char *TAG = "bmp_reader"; + +int blit_chunk_size = BMP_BLIT_CHUNK_SIZE; + +/* Private functions */ + +void _read_bgr_data(FILE *, int, int, int *); +void _read_bgr_data_reverse(FILE *, int, int, int *); +void _read_bgr_data_reverse_flip(FILE *, int, int, int, int *); +void _read_bgr_data_reverse_flip_16bcol(FILE *, int, int, int, uint16_t *); +uint16_t convert_24b_to_16b(uint32_t c); + +void _read_bgr_data(FILE *fp, int data_offset, int size, int *result) { + if (!fp) + return; + if (data_offset) + fseek(fp, data_offset, SEEK_SET); + // printf("all good!\n"); + int i; + for (i = 0; i < size; i += 1) { + // printf("i=%d\n", i); + // result[i] = 0xFFFFFF; + if(!fread(&result[i], 3, 1, fp)) { + break; + } + } +} + +void _read_bgr_data_reverse(FILE *fp, int data_offset, int size, int *result) { + if (!fp) + return; + if (data_offset) + fseek(fp, data_offset, SEEK_SET); + // printf("all good!\n"); + int i; + for (i = 0; i < size; i += 1) { + // printf("i=%d\n", i); + // result[i] = 0xFFFFFF; + if(!fread(&result[size - i - 1], 3, 1, fp)) { + break; + } + } +} + +void _read_bgr_data_reverse_flip(FILE *fp, int data_offset, int w, int h, int *result) { + if (!fp) + return; + if (data_offset) + fseek(fp, data_offset, SEEK_SET); + // printf("all good!\n"); + int i, j; + for (i = h - 1; i >= 0; i -= 1) { + // printf("i=%d\n", i); + // result[i] = 0xFFFFFF; + for (j = 0; j < w; ++j) { + if(!fread(&result[i * w + j], 3, 1, fp)) { + fprintf(stderr, "bmp_reader: Ran out of bytes to read :(\n"); + break; + } + } + + } + // printf("read: %d, %d (%d) given h=%d w=%d\n", i, j, i*j, h, w); +} + +void _read_bgr_data_reverse_flip_16bcol(FILE *fp, int data_offset, int w, int h, + uint16_t *result) { + if (!fp) + return; + if (data_offset) + fseek(fp, data_offset, SEEK_SET); + // printf("all good!\n"); + int i, j; + for (i = h - 1; i >= 0; i -= 1) { + // printf("i=%d\n", i); + // result[i] = 0xFFFFFF; + for (j = 0; j < w; ++j) { + uint32_t rgb; + if(!fread(&rgb, 3, 1, fp)) { + fprintf(stderr, "bmp_reader: Ran out of bytes to read :(\n"); + break; + } + result[i * w + j] = convert_24b_to_16b(rgb); + } + + } + // printf("read: %d, %d (%d) given h=%d w=%d\n", i, j, i*j, h, w); +} + +/* end private functions */ + +void bmp_read_header(FILE *fp, struct bmp_header_t *header) { + if (!fp) + return; + // fseek(fp, 0, SEEK_SET); + // (void)fread(header, sizeof(bmp_header_t), 1, fp); + fread(&header->signature, sizeof(header->signature), 1, fp); + fread(&header->size, sizeof(header->size), 1, fp); + fread(&header->_slack, sizeof(header->_slack), 1, fp); + fread(&header->offset, sizeof(header->offset), 1, fp); +} + +void bmp_read_info_header(FILE *fp, struct bmp_info_header_t *header) { + if (!fp) + return; + (void)fread(header, sizeof(struct bmp_info_header_t), 1, fp); +} + +int bmp_procure_info(FILE * fp, struct bmp_header_t *head, + struct bmp_info_header_t *ih) { + if (!fp) { + ESP_LOGE(TAG, "file is NULL!"); + return 1; + } + + if (!head || !ih) { + ESP_LOGE(TAG, "head or ih is null!"); + return 1; + } + bmp_read_header(fp, head); + if (head->signature[0] != 'B' || head->signature[1] != 'M') { + ESP_LOGE(TAG, "File is not a BMP! (%#2x%#2x)\n", head->signature[0], + head->signature[1]); + return 1; + } + bmp_read_info_header(fp, ih); + if (ih->width > 128 || ih->height > 128) { + ESP_LOGE(TAG, + "image too wide or long. ULCD is 128x128 (target is " + "%dx%d)", + ih->width, ih->height); + return 1; + } + return 0; +} +/* + int blit_bmp(char *file, int x, int y) { + FILE *fd = fopen(file, "rb"); + if (errno) + return errno; + bmp_header_t h; + bmp_info_header_t ih; + if (bmp_procure_info(fd, &h, &ih)) + return 1; + + int *image_data = (int *) malloc(sizeof(int) * ih.width * 10); + + int total = ih.width * ih.height; + for (int i = 0; i < ih.height; i += 10) { + _read_bgr_data(fd, 0, ih.width * 10, image_data); + int ht = 10; + if (i + 10 > ih.height) { + ht = ih.height - i; + } + uLCD.BLIT(x, y + i, ih.width, ht, image_data); + } + return 0; + } */ + +int bmp_blit(char *file, gl_display_t *disp, uint16_t x, uint16_t y) { + ESP_LOGI(TAG, "file name: %s", file); + FILE *fd = fopen(file, "r"); + if (!fd) { + perror("fopen"); + return errno; + } + struct bmp_header_t h; + struct bmp_info_header_t ih; + if (bmp_procure_info(fd, &h, &ih)) + return 1; + + ESP_LOGI(TAG, "Image of size: %dx%d", ih.width, ih.height); + +alloc_int: + uint16_t *image_data = malloc(sizeof(uint16_t) * ih.width * blit_chunk_size); + + if (!image_data) { + ESP_LOGE(TAG, "not enough memory for chunk of size %d, halving", + blit_chunk_size); + if (blit_chunk_size < 4) { + ESP_LOGE(TAG, "ran out of memory"); + return 1; + } + blit_chunk_size /= 2; + goto alloc_int; + } + + int total = ih.width * ih.height; + for (int i = 0; i < ih.height; i += blit_chunk_size) { + int ht = blit_chunk_size; + int off = y + ih.height - i - blit_chunk_size; + if (i + blit_chunk_size > ih.height) { + ht = ih.height - i; + off = y; + } + _read_bgr_data_reverse_flip_16bcol(fd, 0, ih.width, ht, image_data); + + // printf("with y-offset %d\n", off); + gl_blitComtoDisplay(disp, x, y + i, ih.width, ht, + (uint8_t *) image_data); + } + fclose(fd); + free(image_data); + return 0; +} + +void bmp_read_bgr_data(FILE *fp, int *data) { + if (!fp) { + ESP_LOGE(TAG, "file is NULL!"); + return; + } + + struct bmp_header_t head; + struct bmp_info_header_t ih; + bmp_read_header(fp, &head); + if (head.signature[0] != 'B' || head.signature[1] != 'M') { + ESP_LOGE(TAG, "File is not a BMP! (%#2x%#2x)", + head.signature[0], head.signature[1]); + return; + } + bmp_read_info_header(fp, &ih); + if (ih.width > 128 || ih.height > 128) { + ESP_LOGE(TAG, + "image too wide or long. ULCD is 128x128 (target is " + "%dx%d", + ih.width, ih.height); + return; + } + + if (ih.compression) { + ESP_LOGE(TAG, "compressed images not supported :("); + return; + } + + if (ih.bits_per_pixel != 24) { + ESP_LOGE(TAG, "only 24 bit color supported"); + return; + } + + _read_bgr_data(fp, head.offset, ih.width * ih.height, data); +} + + +uint16_t convert_24b_to_16b(uint32_t color) { + uint8_t blue = color & 0xFF; + uint8_t green = (color >> 8) & 0xFF; + uint8_t red = (color >> 16) & 0xFF; + + uint8_t new_blue = (blue >> 3) & 0x1F; + uint8_t new_red = (red >> 3) & 0x1F; + uint8_t new_green = (green >> 2) & 0x3F; + + return (new_red << 11) | (new_green << 5) | new_blue; +} diff --git a/main/bmp_reader.h b/main/bmp_reader.h new file mode 100644 index 0000000..3647f2b --- /dev/null +++ b/main/bmp_reader.h @@ -0,0 +1,54 @@ +/** + * @file bmp_reader.h + * @brief Uncompressed BMP reader + * @author Advaith Menon + */ + +/* code adapted from 2035le's BMP loader */ +#ifndef __BMP_READER_H__ +#define __BMP_READER_H__ + +#ifdef __cplusplus +// extern "C" { +#endif + +#include +#include + +#include "goldeloxSerial.h" + +struct bmp_header_t { + char signature[2]; + int32_t size; + int32_t _slack; + int32_t offset; +}; + +struct bmp_info_header_t { + int32_t header_size; + uint32_t width; + uint32_t height; + int16_t planes; + int16_t bits_per_pixel; + int32_t compression; + int32_t size; + int32_t x_pixels_per_m; + int32_t y_pixels_per_m; + int32_t used_colors; + int32_t important_colors; +}; + +/* color table is skipped because we support only 24 bit BMP*/ + +/* only use for fine grain control */ +void bmp_read_header(FILE *, struct bmp_header_t *); +void bmp_read_info_header(FILE *, struct bmp_info_header_t *); +int bmp_procure_info(FILE *, struct bmp_header_t *, struct bmp_info_header_t *); +void bmp_read_bgr_data(FILE *, int *); +int bmp_blit(char *file, gl_display_t *, uint16_t, uint16_t); + +#ifdef __cplusplus +// } +#endif +#endif + diff --git a/main/goldeloxSerial.c b/main/goldeloxSerial.c index 79f8f7c..7e39b94 100644 --- a/main/goldeloxSerial.c +++ b/main/goldeloxSerial.c @@ -13,6 +13,8 @@ // #include // #include +#include "freertos/FreeRTOS.h" + #include "Goldelox_Types4D.h" // defines data types used by the 4D Routines #include "Goldelox_const4D.h" // defines for 4dgl constants, generated by conversion of 4DGL constants to target language #include "gl_error.h" @@ -23,6 +25,7 @@ // 4D Global variables +char *Error4DText[] = {"OK", "Timeout", "NAK", "Length", "Invalid"} ; int cPort; // comp port handle, used by Intrinsic routines int Error4D ; // Error indicator, used and set by Intrinsic routines unsigned char Error4D_Inv ; // Error byte returned from com port, onl set if error = Err_Invalid @@ -1059,6 +1062,7 @@ void gl_blitComtoDisplay(gl_display_t *display, gl_word_t X, gl_word_t Y, gl_w towrite[8]= Height >> 8 ; towrite[9]= Height ; WriteBytes(display->serif, towrite, 10) ; + vTaskDelay(pdMS_TO_TICKS(1)); WriteBytes(display->serif, Pixels, Width*Height*2) ; GetAck(display->serif) ; } diff --git a/main/goldeloxSerial.h b/main/goldeloxSerial.h index 7407696..77b1b4c 100644 --- a/main/goldeloxSerial.h +++ b/main/goldeloxSerial.h @@ -17,7 +17,6 @@ #define Err4D_Timeout 1 #define Err4D_NAK 2 // other than ACK received -char *Error4DText[] = {"OK", "Timeout", "NAK", "Length", "Invalid"} ; // 4D Global variables extern int cPort ; // comp port handle, used by Intrinsic routines extern int Error4D ; // Error indicator, used and set by Intrinsic routines diff --git a/main/hello_world_main.c b/main/hello_world_main.c index 17725db..70f30f7 100644 --- a/main/hello_world_main.c +++ b/main/hello_world_main.c @@ -16,7 +16,7 @@ #include "task_button.h" #include "sdcardspi.h" - +int gt_global_sdsupport; void app_main(void) { @@ -47,12 +47,10 @@ void app_main(void) ESP_ERROR_CHECK(gpio_set_drive_capability(PIN_VIBRATOR, GPIO_DRIVE_CAP_3)); - gpio_dump_io_configuration(stdout, (1ULL << PIN_VIBRATOR) | (1ULL << PIN_BUTTON_RED)); - /* initialize the buttons */ gt_btn_setup(); - task_sdcard(); + gt_global_sdsupport = task_sdcard(); /* start the main gui thread which starts all else */ lcd_task(); diff --git a/main/nvram_settings.h b/main/nvram_settings.h new file mode 100644 index 0000000..ef05f18 --- /dev/null +++ b/main/nvram_settings.h @@ -0,0 +1,11 @@ +#pragma once + +#ifndef __NVRAM_SETTINGS_H__ +#define __NVRAM_SETTINGS_H__ + +void task_settings(void); + + +void settings_set_pcpc(char *, char *); + +#endif diff --git a/main/pins.h b/main/pins.h index c3f1046..b401d36 100644 --- a/main/pins.h +++ b/main/pins.h @@ -24,4 +24,6 @@ #define SD_MOUNT_PATH "/mnt/sdcard0" #define SD_MAX_FILE_HANDLES 5 +extern int gt_global_sdsupport; + #endif diff --git a/main/sdcardspi.c b/main/sdcardspi.c index 9284f72..1ea0920 100644 --- a/main/sdcardspi.c +++ b/main/sdcardspi.c @@ -17,7 +17,7 @@ /* code largely adapted from ESP-IDF examples: sd_card/sdspi */ /* constants */ -const char *TAG = "sdcardspi"; +static const char *TAG = "sdcardspi"; /* the sdcard handle itself */ sdmmc_card_t *sdmmc; /* the sd card mount path */ -- 2.47.3