Browse Source

Added frame animator

Toby Chui 10 months ago
parent
commit
9bdc79ab21

+ 9 - 0
firmware/cute_useless_robot/cute_useless_robot.ino

@@ -23,6 +23,9 @@
 #define DP_CLK_PIN   32 //Display CLK
 #define DP_DATA_PIN  33 //Display DIN
 #define DP_CS_PIN    25 //Display CS
+
+#define SD_CS_PIN 5 //SD Card CS pin
+
 /* Display settings generated by trial and error. Don't touch these */
 #define HARDWARE_TYPE MD_MAX72XX::DR1CR0RR0_HW
 #define MAX_DEVICES 8
@@ -42,6 +45,12 @@ void setup() {
   /* Servo IO */
   servoSwitchPusher.attach(SERVO_SWITCH);
   servoCoverPusher.attach(SERVO_COVER);
+  /* SD Card */
+  // Initialize SD card
+  if (!SD.begin(SD_CS_PIN)) {
+    Serial.println("[Error] Unable to mount SD card");
+  }
+  
   /* Display Module */
   mx.begin();
   setDisplayBrightness(0x4);

+ 35 - 10
firmware/cute_useless_robot/display.ino

@@ -5,6 +5,8 @@
   https://majicdesigns.github.io/MD_MAX72XX/class_m_d___m_a_x72_x_x.html
 */
 
+#define FRAME_BUFFER_SIZE 64 //4(32 bits) x 16 bytes
+
 //frame buffer, 32x16px
 //each LED is 1 bit
 unsigned char frame_buffer[] = {
@@ -26,17 +28,28 @@ unsigned char frame_buffer[] = {
   0x00, 0x00, 0x00, 0x00
 };
 
-//Helper function to reverse a byte in bits
-//e.g. 11011101 -> 10111011
-byte fByte(byte c) {
-  char r = 0;
-  for (byte i = 0; i < 8; i++) {
-    r <<= 1;
-    r |= c & 1;
-    c >>= 1;
-  } return r;
+//Read a binary file from SD card to current framebuffer
+int readFrameToFrameBuffer(String filepath){
+  File file = SD.open(filepath, FILE_READ);
+  if (!file) {
+    Serial.println("Failed to open file for reading");
+    return 1;
+  }
+  
+  // Read file byte by byte
+  size_t bytesRead = 0;
+  while (file.available() && bytesRead < FRAME_BUFFER_SIZE) {
+    frame_buffer[bytesRead] = file.read();
+    bytesRead++;
+  }
+
+  // Close the file
+  file.close();
+  return 0;
 }
 
+
+
 /*
  * renderFrame render the frame buffer to display 
  * 
@@ -67,9 +80,21 @@ void renderFrame() {
   }
 }
 
-//Set display brightness, from 0x0 to 0xF
+/* Utilities Functions */
+//Set display brightness, from 0x0(min) to 0xF (max)
 void setDisplayBrightness(byte brightness){
   for(int i =0; i<MAX_DEVICES; i++){
     mx.control(i,MD_MAX72XX::INTENSITY, brightness);
   }
 }
+
+//Helper function to reverse a byte in bits
+//e.g. 11011101 -> 10111011
+byte fByte(byte c) {
+  char r = 0;
+  for (byte i = 0; i < 8; i++) {
+    r <<= 1;
+    r |= c & 1;
+    c >>= 1;
+  } return r;
+}

BIN
pcb/StepperDriver/Gerber_Kawaii-Useless-Machine_Display_Driver_Kawaii-Useless-Machine-v2_2024-05-13.zip


BIN
pcb/StepperDriver/Gerber_Kawaii-Useless-Machine_Display_Driver_Kawaii-Useless-Machine-v2_2024-05-14.zip


File diff suppressed because it is too large
+ 1 - 1
pcb/StepperDriver/PCB_Display_Driver_Kawaii-Useless-Machine-v2_2024-05-14.json


BIN
sd_card/anime/a.bin


BIN
sd_card/anime/b.bin


+ 273 - 0
sd_card/web/animator.html

@@ -0,0 +1,273 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>👾 Useless Robot Animator</title>
+	<script src="./jquery.min.js"></script>
+    <style>
+        .circle {
+            width: 20px;
+            height: 20px;
+            background-color: #525050;
+            border-radius: 50%;
+            display: inline-block;
+            margin: 2px;
+			cursor: pointer;
+        }
+		
+		.circle.centerleft{
+			border-right: 1px solid white;
+		}
+		
+		.circle.centerright{
+			border-left: 1px solid white;
+		}
+		
+		.circle.active{
+			background-color: #f55142;
+		}
+		
+        #grid-container {
+            display: flex;
+			position: relative;
+			flex-wrap: wrap;
+            width: calc(32 * (20px + 2px * 2));
+			
+        }
+		
+		#dotmatrix{
+			display: flex;
+			flex-wrap: wrap;
+		}
+		
+		html, body {
+            height: 100%;
+            margin: 0;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+			background-color: #242424;
+			font-family: Verdana, sans-serif;
+			color: white;
+        }
+		
+		.matrixModule{
+			position: absolute;
+			border: 1px solid #a3a3a3;
+			width: calc(24px * 8);
+			height: calc(24px * 8);
+			pointer-events: none;
+		}	
+		
+		.matrixModule.one {
+			top: 0;
+			left: 0;
+		}
+
+		.matrixModule.two {
+			top: 0;
+			left: 192px;
+		}
+
+		.matrixModule.three {
+			top: 0;
+			left: 384px;
+		}
+
+		.matrixModule.four {
+			top: 0;
+			left: 576px;
+		}
+
+		.matrixModule.five {
+			top: 192px;
+			left: 0px;
+		}
+
+		.matrixModule.six {
+			top: 192px;
+			left: 192px;
+		}
+
+		.matrixModule.seven {
+			top: 192px;
+			left: 384px;
+		}
+
+		.matrixModule.eight {
+			top: 192px;
+			left: 576px;
+		}
+		
+		button{
+			background-color: white;
+			color: #3b3b3b;
+			border-radius: 0em;
+			box-shadow: none;
+			padding-top: 0.4em;
+			padding-bottom: 0.4em;
+			padding-left: 1em;
+			padding-right: 1em;
+			cursor: pointer;
+			margin: 0.4em;
+			margin-top: 0.6em;
+		}
+    </style>
+</head>
+<body>
+    <div id="grid-container">
+		<div id="layout">
+			<div class="matrixModule one"></div>
+			<div class="matrixModule two"></div>
+			<div class="matrixModule three"></div>
+			<div class="matrixModule four"></div>
+			<div class="matrixModule five"></div>
+			<div class="matrixModule six"></div>
+			<div class="matrixModule seven"></div>
+			<div class="matrixModule eight"></div>
+		</div>
+		<div id="dotmatrix"></div>
+		<br>
+		<div style="width: 100%; padding: 1em; ">
+		<div style="float: right;">
+				<button onclick="clearScreen();">Clear</button>
+				<button onclick="loadDefault();">Load Default</button>
+				<button onclick="downloadBinary();">Export Binary</button>
+				<button onclick="openFilePicker()">Import Binary</button>
+				<input type="file" id="fileInput" style="display: none;" accept=".bin" onchange="handleFile(event)">
+			</div>
+			<div style="padding-top: 0.8em;">
+				👾 Cute Useless Robot Animator
+			</div>
+			
+		</div>
+		
+	</div>
+    <script>
+		let defaultTemplate = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,1,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,1,0,0,0,1,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,1,0,0,0,1,1,0,0,1,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,1,0,0,1,1,0,0,0,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,1,0,0,1,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,0,0,0,1,1,1,1,0,0,0,1,0,0,1,0,0,1,0,0,0,0,1,1,1,1,0,0,0,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,0,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,1,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,1,0,0,0,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
+		
+        function generateCircleGrid(containerId) {
+            const container = document.getElementById(containerId);
+            if (!container) {
+                console.error(`No element found with id "${containerId}"`);
+                return;
+            }
+
+            // Clear the container if it already has content
+            container.innerHTML = '';
+
+            // Create a grid of circles
+            const rows = 16;
+            const cols = 32;
+            for (let i = 0; i < rows; i++) {
+                for (let j = 0; j < cols; j++) {
+					$(container).append('<div class="circle" onClick="toggleThis(this);"></div>');
+				}
+            }
+        }
+		
+		function getFrameAsIntArray(){
+			let results = [];
+			$(".circle").each(function(){
+				let isActive = $(this).hasClass("active");
+				if (isActive){
+					results.push(1);
+				}else{
+					results.push(0);
+				}
+			});
+			return results;
+		}
+
+		function drawFrameFromIntArray(frameArray){
+			let counter = 0;
+			$(".circle").each(function(){
+				if (frameArray[counter] != undefined){
+					let isActive = frameArray[counter] == 1;
+					if (isActive){
+						$(this).addClass('active');
+					}else{
+						$(this).removeClass('active');
+					}
+				}
+				counter++;
+			});
+		}
+
+		function loadDefault(){
+			drawFrameFromIntArray(defaultTemplate);
+		}
+
+		function downloadBinary(){
+			const bitArray = getFrameAsIntArray();
+			const byteArray = [];
+            for (let i = 0; i < bitArray.length; i += 8) {
+                let byte = 0;
+                for (let j = 0; j < 8; j++) {
+                    if (bitArray[i + j]) {
+                        byte |= 1 << (7 - j);
+                    }
+                }
+                byteArray.push(byte);
+            }
+
+            const blob = new Blob([new Uint8Array(byteArray)], { type: 'application/octet-stream' });
+            const url = URL.createObjectURL(blob);
+            const a = document.createElement('a');
+            a.href = url;
+            a.download = 'frame.bin';
+            document.body.appendChild(a);
+            a.click();
+            document.body.removeChild(a);
+            URL.revokeObjectURL(url);
+		}
+		
+		function toggleThis(led){
+			if (!$(led).hasClass('active')){
+				$(led).addClass('active');
+			}else{
+				$(led).removeClass('active');
+			}
+		}
+
+        // Call the function to generate the grid
+        generateCircleGrid('dotmatrix');
+
+		function clearScreen(){
+			$(".circle.active").removeClass("active");
+		}
+
+		//Import functions
+		function openFilePicker() {
+            document.getElementById('fileInput').click();
+        }
+
+        function handleFile(event) {
+            const file = event.target.files[0];
+            if (file) {
+                const reader = new FileReader();
+                reader.onload = function(e) {
+                    const arrayBuffer = e.target.result;
+                    const byteArray = new Uint8Array(arrayBuffer);
+                    const bitArray = [];
+
+                    for (let byte of byteArray) {
+                        for (let i = 7; i >= 0; i--) {
+                            bitArray.push((byte >> i) & 1);
+                        }
+                    }
+
+                    renderBitArray(bitArray);
+                };
+                reader.readAsArrayBuffer(file);
+            }
+        }
+
+        function renderBitArray(bitArray) {
+			drawFrameFromIntArray(bitArray);
+        }
+
+    </script>
+</body>
+</html>

File diff suppressed because it is too large
+ 1 - 0
sd_card/web/jquery.min.js


Some files were not shown because too many files changed in this diff