Digispark ATtiny85 Freezes when recieves Long (20+ char) Strings trough serial

Hello Arduino community,

I’m hitting a frustrating issue with my Digispark (ATtiny85) where it freezes at DigiKeyboard.print(c); when it receives long strings trough serial (>19 chars, including newline) ONLY in BIOS/DOS boot mode. Interestingly, in windows it works perfectly and direct calls like DigiKeyboard.print("12345678901234567890") work fine in DOS, suggesting the issue isn’t the HID speed but something between the serial buffer and DigiKeyboard.print.

Project Setup

  • Goal: Receive strings from an ESP32-C3 via serial (9600 baud) and send them as keyboard input to a PC in DOS boot/BIOS mode (e.g., for Feature Byte input).
  • Hardware:
    • Digispark ATtiny85 (16.5 MHz, Micronucleus bootloader).
    • ESP32-C3 (sends strings via TX on GPIO4 to Digispark RX).
    • Wiring: ESP32-C3 TX (GPIO4) → Digispark P2 (pin 7, RX), shared GND. Debug output via Digispark P1 (TX) to ESP32-C3 GPIO5 with a 1kΩ resistor (5V to 3.3V).
  • Libraries:
    • DigiKeyboard.h (Digistump, for HID keyboard).
    • SoftSerial_INT0.h (GitHub: J-Rios/Digispark_SoftSerial-INT0, initialized as SoftSerial, RX on P2/INT0).

In my code (below), the Digispark freezes at DigiKeyboard.print(c); when receiving a long string (>19 chars, e.g., “This is a test with more than 18 chars\n”) from the ESP32-C3 in BIOS/DOS mode. The freeze happens when it tries to write first character of the string. Short strings (<19 chars) work fine, and a direct DigiKeyboard.print("12345678901234567890"); in code outputs correctly in DOS, no freeze.

here is my code:



#include <SoftSerial_INT0.h>
#include <DigiKeyboard.h>


SoftSerial mySerial(2, 1);  // RX P2, TX P1





void setup() {
  mySerial.begin(9600);
  DigiKeyboard.sendKeyStroke(0);  // Init HID
  

  
pinMode(1, OUTPUT);
  digitalWrite(1, LOW);


}

void loop() {
  DigiKeyboard.update();


   if (mySerial.available()) {
    
    

    char c = mySerial.read();
    

    
    
   
    delay(50);
    digitalWrite(1, HIGH);
     DigiKeyboard.print(c);
     delay(50);
    digitalWrite(1, LOW);
    
    

            DigiKeyboard.update();
            DigiKeyboard.delay(15);
     
      DigiKeyboard.sendKeyStroke(0, 0);  // Final release
    
    DigiKeyboard.delay(5);  // Small delay for serial stability
  }
  

} 

On esp32 c3 i have a webpage with a text field that sends trough serial whatever is written in that text field, but i modified the code for test purposes like:

void handleArrowLeft() { digitalWrite(8, HIGH); mySerial.println("123456789012345678901234567890"); delay(500); digitalWrite(8, LOW); server.send(200, "text/plain", "OK"); }``

I am a beginner at arduino, i already spent 2 days looking into this problem to no avail :slight_smile: please i need help :slight_smile:

On the surface, it sounds like you are using a blocking call. Try draining the input Q another way.

Get rid of these useless delays. They simply make the program unresponsive and can lead to fatal crashes from buffer overflows, as you have seen.

Never use delay() unless it is necessary, and you clearly understand the reason for using it.

sorry but i didn't understood what you mean. How do i drain the input another way? you mean get the serial buffer into a string and print that string with digikeyboard.write or something like that?

thnx for the reply and advice, i deleted the delays. the problem remains :frowning:

Post complete revised code, for both TX and RX, using code tags.

#include <SoftSerial_INT0.h>
#include <DigiKeyboard.h>


SoftSerial mySerial(2, 1);  // RX P2, TX P1

void setup() {
  mySerial.begin(9600);
  DigiKeyboard.sendKeyStroke(0);  // Init HID
pinMode(1, OUTPUT);
  digitalWrite(1, LOW);
}

void loop() {
  DigiKeyboard.update();
  if (mySerial.available()) {
   
    char c = mySerial.read(); 
    digitalWrite(1, HIGH);
     DigiKeyboard.print(c);
    digitalWrite(1, LOW);
    DigiKeyboard.update();
   DigiKeyboard.sendKeyStroke(0, 0);  // Final release
    DigiKeyboard.delay(5);  // Small delay for serial stability
  }
  

} 

and for the esp32 (it's big):

/*********
  Rui Santos & Sara Santos - Random Nerd Tutorials
  Complete project details at https://Randomnerdtutorials.com/getting-started-esp32-c3-super-mini/

  Final version with client-side image pre-processing.
  - Swapped jsQR for the more powerful ZXing-JS library.
  - Can now scan both QR Codes and common 1D Barcodes.
  - Increased max image size to 1200px for better detail.
  - Added "hints" to the scanner to focus on common formats, improving speed and accuracy.
*********/

#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>

#define TX_PIN 4  // GPIO4 as TX (connect to Digispark P2)
#define RX_PIN 5  // GPIO5 as RX (optional, connect to Digispark P1 if needed)
HardwareSerial mySerial(1);

// Replace with your network credentials
const char* ssid = "Vladimir Routin";
const char* password = "inhousetm";

// Create a web server object
WebServer server(80);

// Helper function to decode URL-encoded strings
String urlDecode(String str) {
  String decoded = "";
  char temp[] = "0x00";
  for (unsigned int i = 0; i < str.length(); i++) {
    if (str[i] == '%') {
      if (i + 2 < str.length()) {
        temp[2] = str[i + 1];
        temp[3] = str[i + 2];
        char decodedChar = (char)strtol(temp, NULL, 16);
        decoded += decodedChar;
        i += 2;
      }
    } else if (str[i] == '+') {
      decoded += ' ';
    } else {
      decoded += str[i];
    }
  }
  return decoded;
}

// Helper function to remove all spaces from a string
String removeSpaces(String str) {
  String noSpaces = "";
  for (unsigned int i = 0; i < str.length(); i++) {
    if (str[i] != ' ') {
      noSpaces += str[i];
    }
  }
  return noSpaces;
}

// Handlers for writing data
void handleWriteSerialNumber() { if (server.hasArg("serial_number")) { mySerial.println(urlDecode(server.arg("serial_number"))); server.send(200, "text/plain", "OK"); } else { server.send(400, "text/plain", "Missing serial_number"); } }
void handleWriteBuildId() { if (server.hasArg("build_id")) { mySerial.println(urlDecode(server.arg("build_id"))); server.send(200, "text/plain", "OK"); } else { server.send(400, "text/plain", "Missing build_id"); } }
void handleWriteFeatureByte() { if (server.hasArg("feature_byte")) { mySerial.println(removeSpaces(urlDecode(server.arg("feature_byte")))); server.send(200, "text/plain", "OK"); } else { server.send(400, "text/plain", "Missing feature_byte"); } }

// Handlers for navigation
void handleArrowLeft() { digitalWrite(8, HIGH); mySerial.println("123456789012345678901234567890"); delay(500); digitalWrite(8, LOW); server.send(200, "text/plain", "OK"); }
void handleArrowRight() { digitalWrite(8, HIGH); mySerial.println("ARROW_RIGHT"); delay(500); digitalWrite(8, LOW); server.send(200, "text/plain", "OK"); }
void handleArrowUp() { digitalWrite(8, HIGH); mySerial.println("ARROW_UP"); delay(500); digitalWrite(8, LOW); server.send(200, "text/plain", "OK"); }
void handleArrowDown() { digitalWrite(8, HIGH); mySerial.println("ARROW_DOWN"); delay(500); digitalWrite(8, LOW); server.send(200, "text/plain", "OK"); }
void handleArrowEnter() { digitalWrite(8, HIGH); mySerial.println("ARROW_ENTER"); delay(500); digitalWrite(8, LOW); server.send(200, "text/plain", "OK"); }

// Function to handle the root URL
void handleRoot() {
  String html = "<!DOCTYPE html><html>";
  html += "<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">";
  html += "<link href=\"https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap\" rel=\"stylesheet\">";
  html += "<script src=\"https://unpkg.com/@zxing/library@latest/umd/index.min.js\"></script>";
  html += "<style>";
  html += "body { font-family: 'Roboto', Arial, sans-serif; background: linear-gradient(to bottom, #eceff1, #cfd8dc); color: #263238; margin: 0; padding: 20px; }";
  html += "h1 { text-align: center; color: #1565c0; font-size: 28px; margin-bottom: 30px; }";
  html += ".container { max-width: 700px; margin: 0 auto; background: #ffffff; padding: 30px; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); }";
  html += "form { margin: 15px 0; display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 10px; }";
  html += "label { font-size: 16px; font-weight: 500; color: #37474f; width: 120px; text-align: right; }";
  html += "input[type=\"text\"] { padding: 10px; font-size: 16px; border: 1px solid #b0bec5; border-radius: 6px; background: #f5f7fa; transition: border-color 0.3s; flex-grow: 1; }";
  html += ".qr-button { background: #4caf50; }";
  html += "</style>";
  html += "</head><body><div class=\"container\">";
  html += "<h1>ESP32 Control Panel</h1>";

  html += "<form id=\"serial_form\"><label for=\"serial_number\">Serial Number:</label><input type=\"text\" id=\"serial_number\" name=\"serial_number\" maxlength=\"50\"><label for='qr_serial' class='qr-button' style='padding: 10px 20px; border-radius: 6px; color: white; cursor: pointer;'>Upload Code</label><input type='file' id='qr_serial' accept='image/*' style='display:none;' onchange='scanUploadedCode(\"serial_number\", this)'><input type=\"button\" value=\"WRITE\" onclick=\"sendData('serial_form', '/write_serial', 'serial_feedback')\"></form><div id=\"serial_feedback\" class=\"feedback\"></div>";
  html += "<form id=\"build_form\"><label for=\"build_id\">Build ID:</label><input type=\"text\" id=\"build_id\" name=\"build_id\" maxlength=\"50\"><label for='qr_build' class='qr-button' style='padding: 10px 20px; border-radius: 6px; color: white; cursor: pointer;'>Upload Code</label><input type='file' id='qr_build' accept='image/*' style='display:none;' onchange='scanUploadedCode(\"build_id\", this)'><input type=\"button\" value=\"WRITE\" onclick=\"sendData('build_form', '/write_build', 'build_feedback')\"></form><div id=\"build_feedback\" class=\"feedback\"></div>";
  html += "<form id=\"feature_form\"><label for=\"feature_byte\">Feature Byte:</label><input type=\"text\" id=\"feature_byte\" name=\"feature_byte\" maxlength=\"70\" style='flex-basis: 300px;'><label for='qr_feature' class='qr-button' style='padding: 10px 20px; border-radius: 6px; color: white; cursor: pointer;'>Upload Code</label><input type='file' id='qr_feature' accept='image/*' style='display:none;' onchange='scanUploadedCode(\"feature_byte\", this)'><input type=\"button\" value=\"WRITE\" onclick=\"sendData('feature_form', '/write_feature', 'feature_feedback')\"></form><div id=\"feature_feedback\" class=\"feedback\"></div>";

  html += "<div class=\"nav-buttons\" style='display: flex; justify-content: center; gap: 10px; margin-top: 20px;'><input type=\"button\" value=\"Left\" onclick=\"sendNav('/arrow_left', 'nav_feedback')\"><input type=\"button\" value=\"Right\" onclick=\"sendNav('/arrow_right', 'nav_feedback')\"><input type=\"button\" value=\"Up\" onclick=\"sendNav('/arrow_up', 'nav_feedback')\"><input type=\"button\" value=\"Down\" onclick=\"sendNav('/arrow_down', 'nav_feedback')\"><input type=\"button\" value=\"Enter\" onclick=\"sendNav('/arrow_enter', 'nav_feedback')\"></div><div id=\"nav_feedback\" class=\"feedback\"></div>";
  html += "<canvas id=\"processingCanvas\" style=\"display:none;\"></canvas>";

  html += "<script>";
  // --- CHANGE 1: Increased max dimension for more detail ---
  html += "const MAX_DIMENSION = 1200;";
  html += "let canvas = document.getElementById('processingCanvas');";
  html += "let ctx = canvas.getContext('2d');";

  html += "function scanUploadedCode(fieldId, input) {";
  html += "  if (!input.files || !input.files[0]) { return; }";
  html += "  let reader = new FileReader();";
  html += "  reader.onload = function(e) {";
  html += "    let img = new Image();";
  html += "    img.onload = function() {";
  html += "      let width = img.width, height = img.height;";
  html += "      if (width > MAX_DIMENSION || height > MAX_DIMENSION) {";
  html += "        if (width > height) { height *= MAX_DIMENSION / width; width = MAX_DIMENSION; }";
  html += "        else { width *= MAX_DIMENSION / height; height = MAX_DIMENSION; }";
  html += "      }";
  html += "      canvas.width = width; canvas.height = height;";
  html += "      ctx.drawImage(img, 0, 0, width, height);";
  html += "      const resizedImageUrl = canvas.toDataURL();";

  // --- CHANGE 2: Give the scanner hints to improve accuracy ---
  html += "      const hints = new Map();";
  html += "      const formats = [ZXing.BarcodeFormat.QR_CODE, ZXing.BarcodeFormat.EAN_13, ZXing.BarcodeFormat.CODE_128, ZXing.BarcodeFormat.DATA_MATRIX, ZXing.BarcodeFormat.ITF, ZXing.BarcodeFormat.PDF_417, ZXing.BarcodeFormat.UPC_A];";
  html += "      hints.set(ZXing.DecodeHintType.POSSIBLE_FORMATS, formats);";
  html += "      const codeReader = new ZXing.BrowserMultiFormatReader(hints);";

  html += "      codeReader.decodeFromImageUrl(resizedImageUrl)";
  html += "        .then(result => {";
  html += "          if (result && result.getText()) {";
  html += "            console.log('Code found:', result.getText());";
  html += "            document.getElementById(fieldId).value = result.getText();";
  html += "          }";
  html += "        })";
  html += "        .catch(err => {";
  html += "          console.error('Scan failed:', err);";
  html += "          alert('Could not read any code. Please use a clear, well-lit image taken straight-on.');";
  html += "        });";
  html += "    };";
  html += "    img.src = e.target.result;";
  html += "  };";
  html += "  reader.readAsDataURL(input.files[0]);";
  html += "}";

  html += "function sendData(formId, url, feedbackId) { var form=document.getElementById(formId); var data=new FormData(form); var f=document.getElementById(feedbackId); f.textContent='Sending...'; f.classList.add('show'); fetch(url,{method:'POST',body:new URLSearchParams(data)}).then(r=>{if(r.ok){f.textContent='Sent!';}else{f.textContent='Error!';} setTimeout(()=>{f.classList.remove('show');f.textContent='';},2000);}).catch(e=>alert('Network error: '+e)); }";
  html += "function sendNav(url, feedbackId) { var f=document.getElementById(feedbackId); f.textContent='Sent!'; f.classList.add('show'); fetch(url,{method:'POST'}).then(r=>{if(!r.ok){alert('Error sending nav command');} setTimeout(()=>{f.classList.remove('show');f.textContent='';},1000);}).catch(e=>alert('Network error: '+e)); }";
  html += "</script>";

  html += "</div></body></html>";
  server.send(200, "text/html", html);
}

void setup() {
  Serial.begin(9600);
  mySerial.begin(9600, SERIAL_8N1, RX_PIN, TX_PIN);
  

  WiFi.begin(ssid, password);
  Serial.print("Connecting to WiFi");
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("\nWiFi connected.");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());
  pinMode(8, OUTPUT);

  server.on("/", handleRoot);
  server.on("/write_serial", HTTP_POST, handleWriteSerialNumber);
  server.on("/write_build", HTTP_POST, handleWriteBuildId);
  server.on("/write_feature", HTTP_POST, handleWriteFeatureByte);
  server.on("/arrow_left", HTTP_POST, handleArrowLeft);
  server.on("/arrow_right", HTTP_POST, handleArrowRight);
  server.on("/arrow_up", HTTP_POST, handleArrowUp);
  server.on("/arrow_down", HTTP_POST, handleArrowDown);
  server.on("/arrow_enter", HTTP_POST, handleArrowEnter);

  server.begin();
  Serial.println("HTTP server started");
}

void loop() {
  server.handleClient();
}

Why? You need just a half dozen lines of code to thoroughly test and debug the basic serial transmission function, so put that aside for now.

What does that DigiKeyboard code do? It may be blocking too.

solved the issue by sending the code in chunks of 10 characters from esp32. Thnx all for replying.

void sendStringInChunks(String str) {
  int len = str.length();
  for (int i = 0; i < len; i += 10) {
    mySerial.print(str.substring(i, i + 10));
    delay(50); // Pause to allow ATtiny to process
  }
  mySerial.println(); // Send final newline to execute command
}