diff --git a/.gitignore b/.gitignore index 89cc49c..301805d 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,10 @@ .vscode/c_cpp_properties.json .vscode/launch.json .vscode/ipch +./__pycache__/ + +output_frames +*.mp4 +*.gif +*.jpeg +*.bmp diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..08ab831 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,17 @@ +{ + "files.associations": { + "array": "cpp", + "*.tcc": "cpp", + "deque": "cpp", + "list": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "string_view": "cpp", + "memory": "cpp", + "random": "cpp", + "initializer_list": "cpp", + "mutex": "cpp", + "regex": "cpp" + } +} \ No newline at end of file diff --git a/BMP_Carray_tests.ipynb b/BMP_Carray_tests.ipynb new file mode 100644 index 0000000..413794e --- /dev/null +++ b/BMP_Carray_tests.ipynb @@ -0,0 +1,156 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from PIL import Image\n", + "from PIL import GifImagePlugin " + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "imageObj = Image.open(\"disint.gif\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "imageObj.is_animated" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "14" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "imageObj.n_frames" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "PIP package used is-- pip install Wand ImageMagick \n", + "install MagickWand from https://imagemagick.org/script/download.php#windows" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "from wand.image import Image\n", + "with Image(filename=\"disint.gif\") as img:\n", + " img.coalesce()\n", + " # img.save(filename=\"frame%02d.bmp\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import convertIMGtoCarray" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Processing video with 14 frames, dimensions: 220x211\n", + "Processed frame 1/14\n", + "Processed frame 2/14\n", + "Processed frame 3/14\n", + "Processed frame 4/14\n", + "Processed frame 5/14\n", + "Processed frame 6/14\n", + "Processed frame 7/14\n", + "Processed frame 8/14\n", + "Processed frame 9/14\n", + "Processed frame 10/14\n", + "Processed frame 11/14\n", + "Processed frame 12/14\n", + "Processed frame 13/14\n", + "Processed frame 14/14\n", + "Saved raw array data to animation_array.txt\n", + "Successfully created header file: animation.h\n", + "Total size: 649880 bytes\n" + ] + } + ], + "source": [ + "convertIMGtoCarray.convert_video_to_rgb332_frames(\"disint.gif\", \"animation\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "convertIMGtoCarray.convert_video_to_rgb332_bin_frames(\"dinit.mp4\", \"output_frames\", max_frames=10, rotate_k=0) #rotate = 1 because we tft rotated it" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.6" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/__pycache__/convertIMGtoCarray.cpython-312.pyc b/__pycache__/convertIMGtoCarray.cpython-312.pyc new file mode 100644 index 0000000..be562a0 Binary files /dev/null and b/__pycache__/convertIMGtoCarray.cpython-312.pyc differ diff --git a/convertIMGtoCarray.py b/convertIMGtoCarray.py new file mode 100644 index 0000000..4ef969a --- /dev/null +++ b/convertIMGtoCarray.py @@ -0,0 +1,332 @@ +############################################## +## @Author: Aditya Yellapuntula Venkata +## In-house converter for images in specified format to C arrays +## Generates a C-header file that is ready to be inluded right away +############################################## + +import numpy as np +from PIL import Image +import matplotlib.pyplot as plt +import sys + + +## Convert video to RGB332 format with all frames stacked vertically +# Process each frame of an animated video into RGB332 format +# Generate a C header file with frames stacked in a single array +# Includes helper macros for accessing individual frames +# +# @param video_path Path to the video file +# @param output_name Name for the output header file (without extension) +# @param max_frames Maximum number of frames to process (None for all) +# @param rotate_k Number of 90 degree rotations to apply to each frame (0, 1, 2, or 3) +def convert_video_to_rgb332_frames(video_path, output_name, max_frames=None, rotate_k = 0): + """ + Convert a video file to a C header file with each frame as a separate array. + Frames are stacked vertically in the data structure. + + Args: + video_path (str): Path to the video file + output_name (str): Name for the output header file (without extension) + """ + import numpy as np + from wand.image import Image + from PIL import Image as PILImage + import io + + # Open the video and coalesce frames + with Image(filename=video_path) as img: + img.coalesce() + + # Get basic information + num_frames = len(img.sequence) + if max_frames is not None and max_frames > 0: + num_frames = min(num_frames, max_frames) + width = img.width + height = img.height + print(f"Processing video with {num_frames} frames, dimensions: {width}x{height}") + + # Array to hold all frame data + all_frames_data = [] + + # Convert each frame + for i, frame in enumerate(img.sequence): + if max_frames is not None and i >= max_frames: + break + + # Convert Wand image to PIL image + frame_img = frame.clone() + frame_blob = frame_img.make_blob(format='bmp') + pil_img = PILImage.open(io.BytesIO(frame_blob)) + + # Convert to RGB if not already + if pil_img.mode != 'RGB': + pil_img = pil_img.convert('RGB') + + # Convert to numpy array + img_array = np.array(pil_img) + img_array = np.rot90(img_array, k=rotate_k) + + # Convert to RGB332 (8-bit, RRRGGGBB) + r = (img_array[:,:,0] >> 5) & 0x07 # Extract top 3 bits for R + g = (img_array[:,:,1] >> 5) & 0x07 # Extract top 3 bits for G + b = (img_array[:,:,2] >> 6) & 0x03 # Extract top 2 bits for B + + # Pack into a single byte per pixel: RRRGGGBB + rgb332 = (r << 5) | (g << 2) | b + + # Flatten the array and add to collection + all_frames_data.append(rgb332.flatten()) + print(f"Processed frame {i+1}/{num_frames}") + + # Stack all frames into a single array + stacked_data = np.concatenate(all_frames_data) + + # Create header file + header_file = f"{output_name}.h" + with open(header_file, "w") as f: + f.write(f"// Auto-generated RGB332 data for {video_path}\n") + f.write(f"// Contains {num_frames} frames of {width}x{height} pixels\n\n") + + f.write(f"#ifndef _{output_name.upper()}_H_\n") + f.write(f"#define _{output_name.upper()}_H_\n\n") + + f.write("#include \n\n") + + f.write(f"#define {output_name.upper()}_WIDTH {width}\n") + f.write(f"#define {output_name.upper()}_HEIGHT {height}\n") + f.write(f"#define {output_name.upper()}_FRAMES {num_frames}\n") + f.write(f"#define {output_name.upper()}_FRAME_SIZE ({width}*{height})\n\n") + + f.write(f"const uint8_t {output_name}[] = {{\n") + + # Write data in rows of 12 values + for i in range(0, len(stacked_data), 12): + row = stacked_data[i:i+12] + f.write(" " + ", ".join([f"0x{val:02X}" for val in row])) + if i + 12 < len(stacked_data): + f.write(",") + f.write("\n") + + f.write("};\n\n") + + # Add helper macros for accessing individual frames + f.write("// Helper macro to access a specific frame\n") + f.write(f"#define {output_name.upper()}_FRAME(n) ") + f.write(f"(&{output_name}[(n) * {output_name.upper()}_FRAME_SIZE])\n\n") + + f.write("#endif\n") + + # Save raw array data to a text file + txt_filename = f"{output_name}_array.txt" + with open(txt_filename, "w") as txt_file: + # Write the array values + for val in stacked_data: + txt_file.write(f"{val}\n") + + print(f"Saved raw array data to {txt_filename}") + + print(f"Successfully created header file: {header_file}") + print(f"Total size: {len(stacked_data)} bytes") + +## Convert video to RGB332 format and save each frame as a binary file +# Process each frame of an animated video into RGB332 format +# Save each frame as a binary file in the specified output folder +# +# @param video_path Path to the video file +# @param output_folder Path to the output folder where binary files will be stored +# @param max_frames Maximum number of frames to process (None for all) +# @param rotate_k Number of 90 degree rotations to apply to each frame (0, 1, 2, or 3) +def convert_video_to_rgb332_bin_frames(video_path, output_folder, max_frames=None, rotate_k=0): + """ + Convert a video file to a series of binary files, each containing a frame in RGB332 format. + + Args: + video_path (str): Path to the video file + output_folder (str): Path to the output folder where binary files will be stored + max_frames (int, optional): Maximum number of frames to process. Defaults to None (all frames). + rotate_k (int, optional): Number of 90 degree rotations to apply. Defaults to 0. + """ + import numpy as np + from wand.image import Image + from PIL import Image as PILImage + import io + import os + import shutil + + # Create output directory, clearing it if it exists + if os.path.exists(output_folder): + shutil.rmtree(output_folder) + os.makedirs(output_folder) + + # Open the video and coalesce frames + with Image(filename=video_path) as img: + img.coalesce() + + # Get basic information + num_frames = len(img.sequence) + if max_frames is not None and max_frames > 0: + num_frames = min(num_frames, max_frames) + width = img.width + height = img.height + print(f"Processing video with {num_frames} frames, dimensions: {width}x{height}") + + # Convert each frame + for i, frame in enumerate(img.sequence): + if max_frames is not None and i >= max_frames: + break + + # Convert Wand image to PIL image + frame_img = frame.clone() + frame_blob = frame_img.make_blob(format='bmp') + pil_img = PILImage.open(io.BytesIO(frame_blob)) + + # Convert to RGB if not already + if pil_img.mode != 'RGB': + pil_img = pil_img.convert('RGB') + + # Convert to numpy array + img_array = np.array(pil_img) + img_array = np.rot90(img_array, k=rotate_k) + + # Get frame dimensions (might be different after rotation) + frame_height, frame_width, _ = img_array.shape + + # Convert to RGB332 (8-bit, RRRGGGBB) + r = (img_array[:,:,0] >> 5) & 0x07 # Extract top 3 bits for R + g = (img_array[:,:,1] >> 5) & 0x07 # Extract top 3 bits for G + b = (img_array[:,:,2] >> 6) & 0x03 # Extract top 2 bits for B + + # Pack into a single byte per pixel: RRRGGGBB + rgb332 = (r << 5) | (g << 2) | b + + # Save to binary file + bin_filename = os.path.join(output_folder, f"frame{i+1}.bin") + with open(bin_filename, "wb") as bin_file: + # Convert to bytes and write + rgb332.astype(np.uint8).tofile(bin_file) + + print(f"Saved frame {i+1}/{num_frames} to {bin_filename} - Dimensions: {frame_width}x{frame_height}") + + # Create info file with metadata + info_filename = os.path.join(output_folder, "info.txt") + with open(info_filename, "w") as info_file: + info_file.write(f"Video: {os.path.basename(video_path)}\n") + info_file.write(f"Frames: {num_frames}\n") + info_file.write(f"Width: {width}\n") + info_file.write(f"Height: {height}\n") + info_file.write("Format: RGB332 (RRRGGGBB)\n") + + print(f"Successfully exported {num_frames} frames to {output_folder}") + print(f"Frame size: {width*height} bytes") + +## Convert BMP to RGB332 format and verify the output +# Generate a text file for image data +# Generate a C header file for the image data ready to be included in an Arduino sketch +# +# @param image_path Path to the image file +# @param image_name Name of the image file +# @param rotate_k Number of 90 degree rotations to apply to the image +def convert_bmp_to_rgb332(image_path, image_name, rotate_k): + """Convert BMP to RGB332 format and verify the conversion.""" + # Load the image + img = Image.open(image_path) + print(f"Original image size: {img.size}, mode: {img.mode}") + + # Convert to RGB if not already + if img.mode != 'RGB': + img = img.convert('RGB') + + # Get image as numpy array + img_array = np.array(img) + img_array = np.rot90(img_array, k=rotate_k) + height, width, _ = img_array.shape + + # Convert to RGB332 (1 byte per pixel) + # 3 bits for R (0-7), 3 bits for G (0-7), 2 bits for B (0-3) + r = (img_array[:,:,0] >> 5) & 0x07 # Extract top 3 bits + g = (img_array[:,:,1] >> 5) & 0x07 # Extract top 3 bits + b = (img_array[:,:,2] >> 6) & 0x03 # Extract top 2 bits + + # Pack into a single byte per pixel: RRRGGGBB + rgb332 = (r << 5) | (g << 2) | b + + # Flatten the array for C export + flat_rgb332 = rgb332.flatten() + + #Save array in a text file + txt_filename = f"{image_name}_array.txt" + with open(txt_filename, "w") as txt_file: + txt_file.write(f"# RGB332 image data ({width}x{height})\n") + txt_file.write(f"# Width: {width}\n") + txt_file.write(f"# Height: {height}\n") + txt_file.write(f"# Format: One byte per pixel, RRRGGGBB\n") + txt_file.write("#\n") + txt_file.write("# Array values (decimal):\n") + rgb_mat = np.reshape(rgb332, (height, width)) + for row in rgb_mat: + txt_file.write(" ".join([f"{val:3d}" for val in row]) + "\n") + + print(f"Saved array data to {txt_filename}") + + # Create header file + filename = image_name+".h" + with open(filename, "w") as f: + f.write("#include \n") + f.write(f"// RGB332 image data ({width}x{height})\n") + f.write(f"#define {image_name}_WIDTH {width}\n") + f.write(f"#define {image_name}_HEIGHT {height}\n") + f.write("const uint8_t "+image_name+"[] = {\n") + + # Write data in rows of 16 values + for i in range(0, len(flat_rgb332), 16): + row = flat_rgb332[i:i+16] + f.write(" " + ", ".join([f"0x{val:02X}" for val in row])) + if i + 16 < len(flat_rgb332): + f.write(",") + f.write("\n") + f.write("};\n") + + print(f"Saved C array to image_rgb332.h") + + # Reconstruct image for validation + # Convert RGB332 back to RGB888 + # Better scaling that preserves brightness + r_recon = np.uint8(((rgb332 >> 5) & 0x07) * (255.0 / 7.0)) + g_recon = np.uint8(((rgb332 >> 2) & 0x07) * (255.0 / 7.0)) + b_recon = np.uint8((rgb332 & 0x03) * (255.0 / 3.0)) + + recon_array = np.zeros((height, width, 3), dtype=np.uint8) + recon_array[:,:,0] = r_recon + recon_array[:,:,1] = g_recon + recon_array[:,:,2] = b_recon + + # Convert back to PIL Image + recon_img = Image.fromarray(recon_array) + + # Display both images side by side + plt.figure(figsize=(12, 6)) + plt.subplot(1, 2, 1) + plt.title("Original Image") + plt.imshow(img) + plt.axis('off') + + plt.subplot(1, 2, 2) + plt.title("Reconstructed Image (RGB332)") + plt.imshow(recon_img) + plt.axis('off') + + plt.tight_layout() + plt.show() + + return flat_rgb332, (width, height) + +if __name__ == "__main__": + # Get image path from command line or use default + # rotate = 3 #Rotate image by k * 90 degrees + # extension = ".jpeg" #Image extension + # image_name = "kar" #Image name + # image_path = f"{image_name}{extension}" + # convert_bmp_to_rgb332(image_path, image_name, rotate) + + convert_video_to_rgb332_bin_frames("disint.gif", "output_frames", max_frames=10, rotate_k=1) + diff --git a/embedded-pio b/embedded-pio index 7163822..9e8f34c 160000 --- a/embedded-pio +++ b/embedded-pio @@ -1 +1 @@ -Subproject commit 7163822407f165e2ef49d13a0b9832665a582128 +Subproject commit 9e8f34cb83b9d1232a50e24863bb080c85eb7a45 diff --git a/include/IOManagement.h b/include/IOManagement.h index 5e64e75..00dd35e 100644 --- a/include/IOManagement.h +++ b/include/IOManagement.h @@ -1,40 +1,40 @@ -#ifndef __IO_MANAGEMENT_H__ -#define __IO_MANAGEMENT_H__ - -#include -#include "STM32TimerInterrupt_Generic.h" -#include "adc.h" - -//Macros for pins -#define DIRECTION_SWITCH_PIN PB1 -#define LEFT_BLINK_PIN PA9 -#define RIGHT_BLINK_PIN PA10 -#define CRZ_MODE_A_PIN PB6 -#define CRZ_SET_PIN PB5 -#define CRZ_RESET_PIN PB4 -#define HORN_PIN PA0 -#define REGEN_BRAKE_PIN ADC_CHANNEL_6 // PA1 - -#define IO_UPDATE_PERIOD 100000 // us - -struct Digital_Data { - bool direction_switch : 1; // input - bool left_blink : 1; // input - bool right_blink : 1; // input - bool crz_mode_a : 1; // input - bool crz_set : 1; // input - bool crz_reset : 1; // input - bool horn : 1; // input -}; - -extern volatile Digital_Data digital_data; - -extern volatile float regen_brake; - -// initialize digital and analog pins -void initIO(); - -// read digital and analog inputs -void readIO(); - -#endif \ No newline at end of file +// #ifndef __IO_MANAGEMENT_H__ +// #define __IO_MANAGEMENT_H__ + +// #include +// #include "STM32TimerInterrupt_Generic.h" +// #include "adc.h" + +// //Macros for pins +// #define DIRECTION_SWITCH_PIN PB1 +// #define LEFT_BLINK_PIN PA9 +// #define RIGHT_BLINK_PIN PA10 +// #define CRZ_MODE_A_PIN PB6 +// #define CRZ_SET_PIN PB5 +// #define CRZ_RESET_PIN PB4 +// #define HORN_PIN PA0 +// #define REGEN_BRAKE_PIN ADC_CHANNEL_6 // PA1 + +// #define IO_UPDATE_PERIOD 100000 // us + +// struct Digital_Data { +// bool direction_switch : 1; // input +// bool left_blink : 1; // input +// bool right_blink : 1; // input +// bool crz_mode_a : 1; // input +// bool crz_set : 1; // input +// bool crz_reset : 1; // input +// bool horn : 1; // input +// }; + +// extern volatile Digital_Data digital_data; + +// extern volatile float regen_brake; + +// // initialize digital and analog pins +// void initIO(); + +// // read digital and analog inputs +// void readIO(); + +// #endif \ No newline at end of file diff --git a/include/brain.h b/include/brain.h new file mode 100644 index 0000000..c453f78 --- /dev/null +++ b/include/brain.h @@ -0,0 +1,28 @@ +#ifndef __BRAIN_H__ +#define __BRAIN_H__ + +#include +#include +#include +#include +#include + +extern TFT_eSPI tft; + +typedef enum { + LINE, + ARC, + CIRCLE, + RECTANGLE, + TEXT, + IMAGE, +} ObjectType; + +typedef struct { + int x; + int y; + int color; + ObjectType ObjectID; +} DisplayObject; + +#endif \ No newline at end of file diff --git a/include/canSteering.h b/include/canSteering.h index 80825f0..9c6cd04 100644 --- a/include/canSteering.h +++ b/include/canSteering.h @@ -1,15 +1,15 @@ -#ifndef __CAN_STEERING_H__ -#define __CAN_STEERING_H__ +// #ifndef __CAN_STEERING_H__ +// #define __CAN_STEERING_H__ -#include "canmanager.h" -#include "IOManagement.h" +// #include "canmanager.h" +// #include "IOManagement.h" -#define CAN_QUEUE_PERIOD 50 -class CANSteering : public CANManager { - public: - CANSteering(CAN_TypeDef* canPort, CAN_PINS pins, int frequency = DEFAULT_CAN_FREQ); - void readHandler(CAN_message_t msg); - void sendSteeringData(); -}; +// #define CAN_QUEUE_PERIOD 50 +// class CANSteering : public CANManager { +// public: +// CANSteering(CAN_TypeDef* canPort, CAN_PINS pins, int frequency = DEFAULT_CAN_FREQ); +// void readHandler(CAN_message_t msg); +// void sendSteeringData(); +// }; -#endif \ No newline at end of file +// #endif \ No newline at end of file diff --git a/include/display.h b/include/display.h new file mode 100644 index 0000000..7cf9692 --- /dev/null +++ b/include/display.h @@ -0,0 +1,42 @@ +#ifndef DISPLAY_H +#define DISPLAY_H + +#include + +#include +#include + +#include + +// JPEG decoder library +#include + +#define HEIGHT 320 +#define WIDTH 480 + +#define ANIMATION_WIDTH 220 +#define ANIMATION_HEIGHT 211 +#define ANIMATION_FRAMES 3 +#define ANIMATION_FRAME_SIZE (211*220) + + +extern const char* ANIM_FILEPATH; + +void initDisplay(bool SD_enable = true); +void drawSdJpeg(const char *filename, int xpos, int ypos, bool centered = false); +void jpegRender(int xpos, int ypos); +void rotateColors(); + +uint8_t *initFrameHeap(int frameSize); + +void freeFrameHeap(uint8_t *frameHeap); + +void loadFrameIntoHeap(int startIndex, int heapSize, uint8_t *frameHeap); + +void HeapAnim(bool clear_old = true); +void HeapDispFrame(int frameIndex); + +void convertTextToBinaryFrames(const char* textFilePath); + + +#endif \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 3456277..48d5eae 100644 --- a/platformio.ini +++ b/platformio.ini @@ -8,36 +8,27 @@ ; Please visit documentation for the other options and examples ; https://docs.platformio.org/page/projectconf.html -[env:nucleo_l432kc] -platform = ststm32 -board = nucleo_l432kc +[env:esp32dev] +platform = espressif32 +board = esp32dev framework = arduino -lib_extra_dirs = ./embedded-pio lib_deps = - pazi88/STM32_CAN@^1.1.2 - khoih-prog/TimerInterrupt_Generic@^1.13.0 - adafruit/Adafruit GFX Library@^1.11.11 bodmer/TFT_eSPI@^2.5.43 -build_flags = - -DHAL_CAN_MODULE_ENABLED - -D USER_SETUP_LOADED=1 - -D STM32=1 ; Define STM32 to invoke optimised processor support - -D ST7796_DRIVER=1 ; Select ST7796 driver - -D TFT_WIDTH=320 ; Set TFT size - -D TFT_HEIGHT=480 - -D TFT_MOSI=A6 ; SPI pins - -D TFT_MISO=A5 - -D TFT_SCLK=A4 - -D TFT_CS=A3 ; Chip select control pin to TFT CS - -D TFT_DC=A2 ; Data Command control pin to TFT DC - -D LOAD_GLCD=1 ; Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH - -D LOAD_FONT2=1 ; Font 2. Small 16 pixel high font, needs ~3534 bytes in FLASH, 96 characters - -D LOAD_FONT4=1 ; Font 4. Medium 26 pixel high font, needs ~5848 bytes in FLASH, 96 characters - -D LOAD_FONT6=1 ; Font 6. Large 48 pixel font, needs ~2666 bytes in FLASH, only characters 1234567890:-.apm - -D LOAD_FONT7=1 ; Font 7. 7 segment 48 pixel font, needs ~2438 bytes in FLASH, only characters 1234567890:-. - -D LOAD_FONT8=1 ; Font 8. Large 75 pixel font needs ~3256 bytes in FLASH, only characters 1234567890:-. - -D LOAD_GFXFF=1 ; FreeFonts. Include access to the 48 Adafruit_GFX free fonts FF1 to FF48 and custom fonts - -D SMOOTH_FONT=1 - -D SPI_FREQUENCY=27000000 ; SPI clock frequency - -D SPI_READ_FREQUENCY=20000000 ; Reduced SPI frequency for reading TFT - -D SPI_TOUCH_FREQUENCY=2500000 ; + bodmer/JPEGDecoder@^2.0.0 +lib_extra_dirs = ./embedded-pio +build_flags = + -D USER_SETUP_LOADED + -D ST7796_DRIVER=1 + -D TFT_MISO=19 + -D TFT_MOSI=23 + -D TFT_SCLK=18 + -D TFT_CS=15 + -D TFT_DC=2 + -D TFT_RST=4 + -D LOAD_GLCD=1 + -D SMOOTH_FONT + -D SPI_FREQUENCY=40000000 + -D CONFIG_ESP_TASK_WDT_TIMEOUT_S=15 + -D CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=false + -D CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=false + ;-D TFT_DMA_ENABLED \ No newline at end of file diff --git a/src/IOManagement.cpp b/src/IOManagement.cpp index 592f20e..8fb7ef0 100644 --- a/src/IOManagement.cpp +++ b/src/IOManagement.cpp @@ -1,41 +1,41 @@ -#include "IOManagement.h" +// #include "IOManagement.h" -volatile Digital_Data digital_data = {0, 0, 0, 0, 0, 0, 0}; -volatile float regen_brake = 0.0f; +// volatile Digital_Data digital_data = {0, 0, 0, 0, 0, 0, 0}; +// volatile float regen_brake = 0.0f; -STM32TimerInterrupt IOTimer(TIM7); +// STM32TimerInterrupt IOTimer(TIM7); -void initIO() { - // Initialize digital pins - pinMode(DIRECTION_SWITCH_PIN, INPUT); - pinMode(LEFT_BLINK_PIN, INPUT); - pinMode(RIGHT_BLINK_PIN, INPUT); - pinMode(CRZ_MODE_A_PIN, INPUT); - pinMode(CRZ_SET_PIN, INPUT); - pinMode(CRZ_RESET_PIN, INPUT); - pinMode(HORN_PIN, INPUT); +// void initIO() { +// // Initialize digital pins +// pinMode(DIRECTION_SWITCH_PIN, INPUT); +// pinMode(LEFT_BLINK_PIN, INPUT); +// pinMode(RIGHT_BLINK_PIN, INPUT); +// pinMode(CRZ_MODE_A_PIN, INPUT); +// pinMode(CRZ_SET_PIN, INPUT); +// pinMode(CRZ_RESET_PIN, INPUT); +// pinMode(HORN_PIN, INPUT); - // Initialize analog pins - initADC(ADC1); +// // Initialize analog pins +// initADC(ADC1); - // Initialize timer for reading inputs - if (IOTimer.attachInterruptInterval(IO_UPDATE_PERIOD, readIO)) { - printf("IO Timer started \n"); - } else { - printf("Failed to start IO Timer \n"); - } -} +// // Initialize timer for reading inputs +// if (IOTimer.attachInterruptInterval(IO_UPDATE_PERIOD, readIO)) { +// printf("IO Timer started \n"); +// } else { +// printf("Failed to start IO Timer \n"); +// } +// } -void readIO() { - // Read digital inputs - digital_data.direction_switch = digitalRead(DIRECTION_SWITCH_PIN); - digital_data.left_blink = digitalRead(LEFT_BLINK_PIN); - digital_data.right_blink = digitalRead(RIGHT_BLINK_PIN); - digital_data.crz_mode_a = digitalRead(CRZ_MODE_A_PIN); - digital_data.crz_set = digitalRead(CRZ_SET_PIN); - digital_data.crz_reset = digitalRead(CRZ_RESET_PIN); - digital_data.horn = digitalRead(HORN_PIN); +// void readIO() { +// // Read digital inputs +// digital_data.direction_switch = digitalRead(DIRECTION_SWITCH_PIN); +// digital_data.left_blink = digitalRead(LEFT_BLINK_PIN); +// digital_data.right_blink = digitalRead(RIGHT_BLINK_PIN); +// digital_data.crz_mode_a = digitalRead(CRZ_MODE_A_PIN); +// digital_data.crz_set = digitalRead(CRZ_SET_PIN); +// digital_data.crz_reset = digitalRead(CRZ_RESET_PIN); +// digital_data.horn = digitalRead(HORN_PIN); - // Read analog input - regen_brake = readADC(REGEN_BRAKE_PIN); -} \ No newline at end of file +// // Read analog input +// regen_brake = readADC(REGEN_BRAKE_PIN); +// } \ No newline at end of file diff --git a/src/canSteering.cpp b/src/canSteering.cpp index 3040f60..ac5d618 100644 --- a/src/canSteering.cpp +++ b/src/canSteering.cpp @@ -1,12 +1,12 @@ -#include "canSteering.h" +// #include "canSteering.h" -CANSteering::CANSteering(CAN_TypeDef* canPort, CAN_PINS pins, int frequency) : CANManager(canPort, pins, frequency) {}; +// CANSteering::CANSteering(CAN_TypeDef* canPort, CAN_PINS pins, int frequency) : CANManager(canPort, pins, frequency) {}; -void CANSteering::readHandler(CAN_message_t msg) { +// void CANSteering::readHandler(CAN_message_t msg) { -} +// } -void CANSteering::sendSteeringData() { - this->sendMessage(0x300, (void*)&digital_data, sizeof(digital_data)); - this->sendMessage(0x301, (void*)®en_brake, sizeof(float)); -} \ No newline at end of file +// void CANSteering::sendSteeringData() { +// this->sendMessage(0x300, (void*)&digital_data, sizeof(digital_data)); +// this->sendMessage(0x301, (void*)®en_brake, sizeof(float)); +// } \ No newline at end of file diff --git a/src/display.cpp b/src/display.cpp new file mode 100644 index 0000000..c8d8cce --- /dev/null +++ b/src/display.cpp @@ -0,0 +1,367 @@ +#include "display.h" +#include + +#include + +#include +#include + +#include + +// JPEG decoder library +#include + +// Frame file pattern - will use: /bsr/frame0.bin, /bsr/frame1.bin, etc. +const char* FRAME_FILE_PATTERN = "/output_frames/frame%d.bin"; +char currentFramePath[32]; // Buffer to store the current frame path + +volatile int old_start_x = 0; +volatile int old_start_y = 0; +volatile int old_width = 0; +volatile int old_height = 0; + +int totalFrames = 0; + +TFT_eSPI tft = TFT_eSPI(); + +//Heap pointer for animation frames +uint8_t *frameHeap = NULL; + +//Array of random colors to cycle through +uint16_t colors[] = {TFT_RED, TFT_GREEN, TFT_BLUE, TFT_YELLOW, TFT_CYAN, TFT_MAGENTA}; + +void rotateColors() { + static uint8_t colorIndex = 0; + tft.fillScreen(colors[colorIndex]); + colorIndex = (colorIndex + 1) % (sizeof(colors) / sizeof(colors[0])); +} + +void countAvailableFrames() { + totalFrames = 0; + while(true) { + sprintf(currentFramePath, FRAME_FILE_PATTERN, totalFrames); + if(!SD.exists(currentFramePath)) { + break; + } + totalFrames++; + } + Serial.printf("Found %d animation frames\n", totalFrames); +} + +void initDisplay(bool SD_enable){ + // Set all chip selects high to avoid bus contention during initialisation of each peripheral + digitalWrite(22, HIGH); // Touch controller chip select (if used) + digitalWrite(15, HIGH); // TFT screen chip select + digitalWrite( 5, HIGH); // SD card chips select, must use GPIO 5 (ESP32 SS) + + tft.begin(); + tft.setRotation(1); // Somehow important to have an odd rotation number + //Display does not work properly if you do not set this and initialise the SD card + tft.fillScreen(TFT_BLACK); + + if (!SD.begin(5, tft.getSPIinstance())) { + Serial.println("Card Mount Failed"); + return; + } + uint8_t cardType = SD.cardType(); + + if (cardType == CARD_NONE) { + Serial.println("No SD card attached"); + return; + } + + Serial.print("SD Card Type: "); + if (cardType == CARD_MMC) { + Serial.println("MMC"); + } else if (cardType == CARD_SD) { + Serial.println("SDSC"); + } else if (cardType == CARD_SDHC) { + Serial.println("SDHC"); + } else { + Serial.println("UNKNOWN"); + } + + // // Check if frame files need to be created from text + // bool needsConversion = false; + // for (int i = 0; i < 14; i++) { + // sprintf(currentFramePath, FRAME_FILE_PATTERN, i); + // if (!SD.exists(currentFramePath)) { + // needsConversion = true; + // break; + // } + // } + + // // Convert the text animation to individual binary frame files if needed + // if (needsConversion && SD.exists("/bsr/som.txt")) { + // Serial.println("Creating individual binary frame files..."); + // convertTextToBinaryFrames("/bsr/som.txt"); + // } + + uint64_t cardSize = SD.cardSize() / (1024 * 1024); + Serial.printf("SD Card Size: %lluMB\n", cardSize); + + // countAvailableFrames(); + // Serial.printf("Total frames available: %d\n", totalFrames); + + Serial.println("Initialization done."); +} + +void drawSdJpeg(const char *filename, int xpos, int ypos, bool centered) { + File jpegFile = SD.open(filename); + if (!jpegFile) { + Serial.println("Failed to open file for reading"); + return; + } + + Serial.println("==========================="); + Serial.print("Drawing file: "); Serial.println(filename); + Serial.println("==========================="); + + // If centered, we need to get dimensions first + if (centered) { + bool pre_decoded = JpegDec.decodeSdFile(jpegFile); + if (pre_decoded) { + // Get image dimensions + uint32_t image_width = JpegDec.width; + uint32_t image_height = JpegDec.height; + + // Calculate centered position + xpos = (TFT_WIDTH - image_width) / 2; + ypos = (TFT_HEIGHT - image_height) / 2; + + // Ensure position isn't negative + xpos = max(0, xpos); + ypos = max(0, ypos); + + // Close and reopen for actual rendering + jpegFile.close(); + jpegFile = SD.open(filename); + } + } + + // Use one of the following methods to initialise the decoder: + bool decoded = JpegDec.decodeSdFile(jpegFile); + + if (decoded) { + // render the image onto the screen at given coordinates + jpegRender(xpos, ypos); + } + else { + Serial.println("Jpeg file format not supported!"); + } +} + +void jpegRender(int xpos, int ypos) { + + //jpegInfo(); // Print information from the JPEG file (could comment this line out) + + uint16_t *pImg; + uint16_t mcu_w = JpegDec.MCUWidth; + uint16_t mcu_h = JpegDec.MCUHeight; + uint32_t max_x = JpegDec.width; + uint32_t max_y = JpegDec.height; + + bool swapBytes = tft.getSwapBytes(); + tft.setSwapBytes(true); + + // Jpeg images are draw as a set of image block (tiles) called Minimum Coding Units (MCUs) + // Typically these MCUs are 16x16 pixel blocks + // Determine the width and height of the right and bottom edge image blocks + uint32_t min_w = jpg_min(mcu_w, max_x % mcu_w); + uint32_t min_h = jpg_min(mcu_h, max_y % mcu_h); + + // save the current image block size + uint32_t win_w = mcu_w; + uint32_t win_h = mcu_h; + + // record the current time so we can measure how long it takes to draw an image + uint32_t drawTime = millis(); + + // save the coordinate of the right and bottom edges to assist image cropping + // to the screen size + max_x += xpos; + max_y += ypos; + + // Fetch data from the file, decode and display + while (JpegDec.read()) { // While there is more data in the file + pImg = JpegDec.pImage ; // Decode a MCU (Minimum Coding Unit, typically a 8x8 or 16x16 pixel block) + + // Calculate coordinates of top left corner of current MCU + int mcu_x = JpegDec.MCUx * mcu_w + xpos; + int mcu_y = JpegDec.MCUy * mcu_h + ypos; + + // check if the image block size needs to be changed for the right edge + if (mcu_x + mcu_w <= max_x) win_w = mcu_w; + else win_w = min_w; + + // check if the image block size needs to be changed for the bottom edge + if (mcu_y + mcu_h <= max_y) win_h = mcu_h; + else win_h = min_h; + + // copy pixels into a contiguous block + if (win_w != mcu_w) + { + uint16_t *cImg; + int p = 0; + cImg = pImg + win_w; + for (int h = 1; h < win_h; h++) + { + p += mcu_w; + for (int w = 0; w < win_w; w++) + { + *cImg = *(pImg + w + p); + cImg++; + } + } + } + + // calculate how many pixels must be drawn + uint32_t mcu_pixels = win_w * win_h; + + // draw image MCU block only if it will fit on the screen + if (( mcu_x + win_w ) <= tft.width() && ( mcu_y + win_h ) <= tft.height()) + tft.pushImage(mcu_x, mcu_y, win_w, win_h, pImg); + else if ( (mcu_y + win_h) >= tft.height()) + JpegDec.abort(); // Image has run off bottom of screen so abort decoding + } + + tft.setSwapBytes(swapBytes); +} + +uint8_t *initFrameHeap(int frameSize) { + // Allocate memory for the frame heap + uint8_t *frameHeap = (uint8_t *)malloc(frameSize * sizeof(uint8_t)); + if (frameHeap == NULL) { + Serial.println("Failed to allocate memory for frame heap"); + return NULL; + } + return frameHeap; +} + +void freeFrameHeap(uint8_t *frameHeap) { + // Free the allocated memory for the frame heap + if (frameHeap != NULL) { + free(frameHeap); + Serial.println("Frame heap memory freed successfully"); + } else { + Serial.println("Frame heap is already NULL, no need to free memory"); + } +} + +// Modified animation with individual frame files - no clearing between frames +void HeapAnim(bool clear_old) { + // Initialize heap only once - Use 1 byte per pixel for RGB332 format + if (frameHeap == NULL) { + frameHeap = initFrameHeap(ANIMATION_WIDTH * ANIMATION_HEIGHT); // RGB332 is 1 byte per pixel + if (frameHeap == NULL) { + Serial.println("Failed to allocate memory for frame heap"); + return; + } + } + + uint32_t startTime = millis(); + + // Display each frame in sequence - no clearing between frames + + ////////////////////////////////////////////////////////////////////////////////////// + //I havent tested if using totalFrames is correct, when I tested it, the loop to 11 + ////////////////////////////////////////////////////////////////////////////////////// + for (int i = 0; i < totalFrames; i++) { + uint32_t frameStart = millis(); + HeapDispFrame(i); + Serial.printf("Frame %d timing: %d ms\n", i, millis() - frameStart); + } + + Serial.printf("Total animation time: %d ms\n", millis() - startTime); +} + +// Modified to load a specific frame file - no frame clearing +void HeapDispFrame(int frameIndex) { + // Generate the frame file path + sprintf(currentFramePath, FRAME_FILE_PATTERN, frameIndex); + + // Open the specific frame file + File frameFile = SD.open(currentFramePath, FILE_READ); + + if (!frameFile) { + Serial.printf("Error: Frame file %s does not exist\n", currentFramePath); + return; + } + + // Read directly into frameHeap + uint32_t bytesRead = frameFile.read(frameHeap, ANIMATION_WIDTH * ANIMATION_HEIGHT); + frameFile.close(); + + if (bytesRead != ANIMATION_WIDTH * ANIMATION_HEIGHT) { + Serial.printf("Error reading frame %d: read %d bytes\n", frameIndex, bytesRead); + return; + } + + // Calculate position to center + int start_x = (WIDTH - ANIMATION_WIDTH) / 2; + int start_y = (HEIGHT - ANIMATION_HEIGHT) / 2; + + // Push the frame directly to the display - will overwrite previous frame + tft.pushImage(start_x, start_y, ANIMATION_WIDTH, ANIMATION_HEIGHT, frameHeap, true); + + // Still store coordinates for potential future use + old_start_x = start_x; + old_start_y = start_y; + old_width = ANIMATION_WIDTH; + old_height = ANIMATION_HEIGHT; +} + +// Convert text file to multiple binary frame files +void convertTextToBinaryFrames(const char* textFilePath) { + File textFile = SD.open(textFilePath, FILE_READ); + if (!textFile) { + Serial.println("Failed to open text file"); + return; + } + + const int pixelsPerFrame = ANIMATION_WIDTH * ANIMATION_HEIGHT; + uint8_t* frameBuffer = (uint8_t*)malloc(pixelsPerFrame); + if (!frameBuffer) { + Serial.println("Failed to allocate frame buffer"); + textFile.close(); + return; + } + + int frameIndex = 0; + int pixelCount = 0; + + // Read text file line by line + while (textFile.available()) { + // Fill one frame buffer + for (int i = 0; i < pixelsPerFrame && textFile.available(); i++) { + String line = textFile.readStringUntil('\n'); + frameBuffer[i] = (uint8_t)line.toInt(); + pixelCount++; + } + + // If we've read enough pixels for a complete frame + if (pixelCount >= pixelsPerFrame) { + // Create the frame file + sprintf(currentFramePath, FRAME_FILE_PATTERN, frameIndex); + File frameFile = SD.open(currentFramePath, FILE_WRITE); + + if (frameFile) { + // Write the entire frame buffer at once + frameFile.write(frameBuffer, pixelsPerFrame); + frameFile.close(); + Serial.printf("Created frame file: %s\n", currentFramePath); + + // Move to next frame + frameIndex++; + pixelCount = 0; + } else { + Serial.printf("Failed to create frame file: %s\n", currentFramePath); + } + } + } + + textFile.close(); + free(frameBuffer); + + Serial.printf("Converted %d frames to individual binary files\n", frameIndex); +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index b5907a0..3747802 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,16 +1,20 @@ #include -#include "canSteering.h" -#include "IOManagement.h" - -CANSteering canSteering(CAN1, DEF); +#include "display.h" void setup() { - Serial.begin(115200); - - initIO(); + Serial.begin(115200); + Serial.println("Starting..."); + initDisplay(false); + + // drawSdJpeg("/bsr/Jonathan.jpeg", 130, 0); + // HeapAnim(); + Serial.println("Setup done."); } void loop() { - canSteering.sendSteeringData(); - canSteering.runQueue(CAN_QUEUE_PERIOD); + rotateColors(); + // delay(42); + // drawSdJpeg("/test.jpg", 0, 0); + + // HeapAnim(); }