Mini OS emulator for Digispark

Digispark is a mini-arduino with direct programming via USB. In addition to the small size, it is also interesting to see the price, which at Aliexpress is currently one dollar. Below is my simple sketch, which is the mini-system emulator designed for this toddler :)

The emulator allows you to log in to Digispark, execute several commands, and then log out. It works on the default Digispark with micronucleus bootloader installed, and uses the DigiCDC module to emulate USB communication, because Digispark itself does not have any additional USB chip and everything is done in the AtTiny85 software.

When powering the Digispark via the VIN pin, you can connect to it via USB at any time, and after performing the commands, disconnect the terminal and the device – Digispark will continue to execute the code taking into account to sent commands (as in the video below):

Available commands
p[02] [on|off] sends HIGH and LOW signals to individual pins (from 0 to 2)
uptime displays the time from launching the Digispark in the linux format uptime pretty
vcc gives the power supply voltage of Digispark in millivolts
ls displays GPIO status list
reboot software restart of Digispark
clear clears a display
temp specifies the temperature of the chip
login prompts you to enter your password
clock [07] reduces clock timing (energy saving); values: 1 – 8mHz, 2 – 4mHz, 3 – 2mHz, 4 – 1mHz, 5 – 500kHz, 6 – 250kHz, 7 – 125kHz, 0 – 16,5 mHz
help displays the help screen
logout, exit logs out the user

Sketch was intended to take up as little space as possible – instead of strings I used character arrays, instead of pinMode/digitalWrite registers and bit operations, thanks to which it was possible to push so many functions on such a small device. All three components together occupy almost 100% of Digispark’s memory, but to increase the amount of available space for your own code, it is enough to remove the reference to unnecessary functions (e.g. temp, uptime, vcc). These references are marked in a special code block – after deleting them, more than 30% of the memory will be available (excluding bootloader).

Changing the clocking will disable communication via USB – you can connect this change to the execution of certain commands ending with a reference to the reboot function, then Digispark will restart with the default settings of the clock and will automatically be able to log on to the device again.

You can log in to Digispark under Windows with Putty (after installing Digistump drivers), to Linux with Minicom application and to Android with Serial USB Terminal. Compilation of DigiOS sketch requires installation of an earlier version of Arduino IDE – 1.8.6 and correct installation of Digispark in IDE.

The password is defined in the first line.

Below the script code – its changes can be tracked on the

DigiOS code is available below – changes can be tracked on project page on
There is also a DigiLx version with a login window and a Linux-style prompt.


/* ----------------------------------------------
  DigiOS 1.4 - mini-OS emulator for Digispark
  Copyright (c) Jaromaz https://jm.iq.pl
  Available commands:
  login, p[0-2] [on|off], temp, help, vcc, clear,
  uptime, clock [1-7], ls, reboot, logout, exit
  ----------------------------------------------- */

// password of up to seven characters
const char password[] = "admin12";

//-----------------------------------------------

#include "DigiCDC.h"

char serialChar[1], stringInput[8];
bool stringComplete = false;
uint8_t state = 1;
uint8_t clocks[] = { 16, 8, 4, 2, 1, 500, 250, 125 };

static void reboot()
//-----------------------------------------------
{
  SerialUSB.print(F("\r\nRebooting ... "));
  noInterrupts();
  CLKPR = 0b10000000;
  CLKPR = 0;
  void (*ptrToFunction)();
  ptrToFunction = 0x0000;
  (*ptrToFunction)();
}

static void getVcc()
//-----------------------------------------------
{
  ADMUX = _BV(MUX3) | _BV(MUX2);
  SerialUSB.delay(2);
  ADCSRA |= _BV(ADSC);
  while (bit_is_set(ADCSRA, ADSC));
  uint8_t low  = ADCL;
  uint8_t high = ADCH;
  long result = (high << 8) | low;
  result = 1125300L / result;
  SerialUSB.print(F("\r\nVoltage: "));
  SerialUSB.print(result);
  SerialUSB.println(F(" mV"));
}

static void clockMessageFormat (uint8_t speed)
//-----------------------------------------------
{
  SerialUSB.print(F("\r\nset to "));
  SerialUSB.print(clocks[speed], DEC);
  SerialUSB.print((clocks[speed] > 16) ? F("k") : F("m"));
  SerialUSB.println(F("Hz\r\n\r\nbye ..."));
}

static void clockSpeed(uint8_t speed)
//-----------------------------------------------
// edit the code of this procedure to get the right result
{
  clockMessageFormat(speed);
  for (uint8_t i = 0; i < 12; i++) {
    PORTB |= (1 << 1);
    SerialUSB.delay(200);
    PORTB &= ~(1 << 1);
    SerialUSB.delay(200);
    if (i == 5) {
      CLKPR = 0b10000000;
      CLKPR = speed;
    }
  }
  reboot();
}

static void stateChg() { state = 2; }
//-----------------------------------------------

#define SECS_PER_MIN  (60UL)
#define SECS_PER_HOUR (3600UL)
#define SECS_PER_DAY  (SECS_PER_HOUR * 24L)
#define numberOfSeconds(_time_) (_time_ % SECS_PER_MIN)
#define numberOfMinutes(_time_) ((_time_ / SECS_PER_MIN) % SECS_PER_MIN)
#define numberOfHours(_time_) (( _time_% SECS_PER_DAY) / SECS_PER_HOUR)
#define elapsedDays(_time_) ( _time_ / SECS_PER_DAY)

void uptimeFormat(uint8_t digits, char* form)
//-----------------------------------------------
{
  if (digits > 0)
  {
    SerialUSB.print(digits, DEC);
    SerialUSB.print(F(" "));
    SerialUSB.print(form);
    if (digits > 1) SerialUSB.print(F("s"));
    if (strcmp(form, "second")) {
      SerialUSB.print(F(", "));
    }
  }
}

static void uptime()
//-----------------------------------------------
{
  long seconds = millis() / 1000;
  SerialUSB.print(F("\r\nup "));
  uptimeFormat(elapsedDays(seconds), "day");
  uptimeFormat(numberOfHours(seconds), "hour");
  uptimeFormat(numberOfMinutes(seconds), "minute");
  uptimeFormat(numberOfSeconds(seconds), "second");
  SerialUSB.println();
}

static void getTemp()
//-----------------------------------------------
{
  analogReference(INTERNAL1V1);
  analogRead(A0);
  SerialUSB.delay(200);
  uint16_t temp = analogRead(A0 + 15) - 273;
  analogReference(DEFAULT);
  SerialUSB.print(F("\r\nDigispark temperature: "));
  SerialUSB.print(temp);
  SerialUSB.println(F("°C"));
}

void clearScreen()
//-----------------------------------------------
{
  for (uint8_t i = 0; i < 35; i++) {
    SerialUSB.println();
    SerialUSB.delay(5);
  }
}

static void horizontaLine()
//-----------------------------------------------
{
  for (uint8_t i = 0; i < 32; i++)
  SerialUSB.print(F("-"));
}

static void gpioList()
//-----------------------------------------------
{
 horizontaLine();
 SerialUSB.print(F("\r\nGPIO status list\r\n"));
 horizontaLine();
 for (uint8_t i = 0; i < 3; i++) {
   SerialUSB.print(F("\r\nPin "));
   SerialUSB.print(i, DEC);
   SerialUSB.print((PINB & (1 << i)) ? F(" HIGH") : F(" LOW"));
 }
 SerialUSB.println();
 horizontaLine();
}

static void help()
//-----------------------------------------------
{
 horizontaLine();
 SerialUSB.println(F("\r\nDigiOS version 1.4 User Commands"));
 horizontaLine();
 SerialUSB.println(F("\r\nlogin, p[0-2] [on|off], temp, help,\
 vcc, clear,\r\nuptime, clock [1-7], ls, reboot, logout,\
 exit\r\n\r\nclock 1 - 8mHz, 2 - 4mHz, 3 - 2mHz, 4 - 1mHz,\
 \r\n5 - 500kHz, 6 - 250kHz, 7 - 125kHz"));
}

static void serialReader()
//-----------------------------------------------
{
  while (SerialUSB.available())
  {
    serialChar[0] = (char)SerialUSB.read();
    if ((' ' <= serialChar[0]) && (serialChar[0] <= '~')) {
      strcat(stringInput, serialChar);
    } else {
      if (stringInput[0] != 0) {
        stringComplete = true;
        return;
      }
    }
  }
}

void setup()
//-----------------------------------------------
{
  // Set pins 0-2 as OUTPUT:
  DDRB |= (1 << PB0) | (1 << PB1) | (1 << PB2);
  SerialUSB.begin();
}

// list of keywords and procedures assigned to them
static const struct { const char phrase[8]; void (*handler)(void); } keys[] =
{
  // ---- comment on this block to get more memory for your own code ---
  { "vcc", getVcc }, { "help", help }, { "temp", getTemp },
  { "reboot", reboot },  { "exit", stateChg }, { "uptime", uptime },
  { "clear", clearScreen }, { "ls", gpioList },
  // -------------------------------------------------------------------
  { "logout", stateChg }
};

void loop()
//-----------------------------------------------
{
  // the Android Serial USB Terminal app requires the following 200 ms delays
  // if you are using another system, you can remove all these delays.
  
  serialReader();
  if (stringComplete)
  {
    SerialUSB.delay(200);

    if (!strcmp(stringInput, "login")) stateChg();

    // password validation
    if (state == 4)
    {
      if (!strcmp(stringInput, password))
      {
        state = 3;
      } else {
        SerialUSB.delay(1500);
        SerialUSB.println(F("\r\nLogin incorrect"));
        state = 1;
      }
    }

    // status after logging in
    if (state == 3)
    {
      // ---- comment on this block to get more memory for your own code ---
      if (stringInput[0] == 'p') {
        if ((stringInput[1] - 48) < 3 and stringInput[4] == 'n') {
          PORTB |= (1 << stringInput[1] - 48);
        } else if ((stringInput[1] - 48) < 3 and stringInput[4] == 'f') {
          PORTB &= ~(1 << stringInput[1] - 48);
        }
      }
      if (strstr(stringInput, "clock ")) clockSpeed(stringInput[6] - 48);
      // ---------------------------------------------------------------------

      // keyword procedures
      for (uint8_t i = 0; i < sizeof keys / sizeof * keys; i++) {
        if (!strcmp(stringInput, keys[i].phrase)) keys[i].handler();
      }

      if (state == 3) SerialUSB.print(F("\r\ncmd:> "));
    }

    // password input window
    if (state < 3)
    {
      if (state > 1) clearScreen();
      SerialUSB.print(F("\r\nDigiOS 1.4 - Digispark mini-OS\r\n\r\nPassword: "));
      state = 4;
    }

    SerialUSB.delay(200);

    stringInput[0] = 0;
    stringComplete = false;
  }
}

3 thoughts on “Mini OS emulator for Digispark

  1. It seems this does not work on a Win 10 computer as the device gives the eroor “invalid configuration descriptor”. Any clue?

    Best regrds

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.