Mini OS emulator dla Digisparka

Digispark to mini-arduino z możliwością programowania bezpośrednio przez USB. Oprócz niewielkiego rozmiaru interesująca jest jego cena, która na Aliexpress wynosi obecnie jednego dolara. Poniżej mój prosty sketch DigiOS, będący emulatorem systemu przeznaczonym dla tego malucha.
Emulator pozwala na zalogowanie się do Digisparka, wykonanie kilku komend, a następnie wylogowanie się. Działa na domyślnym Digisparku z zainstalowanym bootloaderem micronucleus oraz wykorzystuje moduł DigiCDC do emulacji komunikacji po USB, ponieważ sam Digispark nie posiada żadnego dodatkowego czipu USB i wszystko jest realizowane w oprogramowaniu AtTiny85.

Przy zasilaniu Digisparka poprzez pin VIN można w dowolnym momencie podłączyć się do niego przez USB, a po wykonaniu komend, odłączyć terminal i urządzenie – Digispark będzie dalej wykonywał kod uwzględniając wysłane komendy – jak w poniższym filmie:

Dostępne komendy
p[02] [on|off] wysyła sygnały HIGH i LOW na poszczególne piny od (0 do 2)
uptime wyświetla czas od uruchomienia Digisparka w linuxowym formacie uptime pretty
vcc podaje napięcie zasilania Digisparka w miliwoltach
ls wyświetla listę statusów GPIO
reboot software’owo restartuje Digisparka
clear czyści ekran
temp podaje temperaturę chipu
login wyświetla monit o podanie hasła
clock [07] zmniejsza taktowanie zegara (oszczędność energii). Wartości: 1 – 8mHz, 2 – 4mHz, 3 – 2mHz, 4 – 1mHz, 5 – 500kHz, 6 – 250kHz, 7 – 125kHz, 0 – 16,5 mHz
help wyświetla ekran pomocy
logout, exit wylogowuje użytkownika

Sketch z założenia miał zajmować jak najmniej miejsca – zamiast stringów wykorzystałem tablice znakowe, zamiast pinMode/digitalWrite rejestry i operacje bitowe, dzięki czemu udało się upchnąć tak dużo funkcji na tak małym urządzeniu. Wszystkie trzy elementy składowe zajmują razem blisko 100% pamięci Digisparka, aby jednak zwiększyć ilość dostępnego miejsca na własny kod, wystarczy usunąć odwołanie do zbędnych funkcji (np. temp, uptime, vcc). Odwołania te są oznaczone w kodzie specjalnym blokiem – po ich usunięciu udostępnione zostanie ponad 30% pamięci (z wyłączeniem bootloadera).

Zmiana taktowania wyłączy możliwość komunikacji po USB – można podpiąć pod tę zmianę wykonanie pewnych poleceń kończące się odwołaniem do funkcji reboot, wtedy Digispark się zrestartuje z domyślnymi ustawieniami zegara i automatycznie ponownie będzie możliwe logowanie do urządzenia.

Do Digisparka można zalogować się pod Windows programem Putty (po wcześniejszej instalacji sterowników Digistump), w Linuxie aplikacją Minicom, a w Androidzie programem Serial USB Terminal. Kompilacja sketcha DigiOS wymaga zainstalowania wcześniejszej wersji Arduino IDE – 1.8.6 oraz poprawnej instalacji Digisparka w IDE.

Dużo więcej informacji o tym projekcie można znaleźć na forum serwisu

Kod DigiOS udostępniam poniżej – zmiany można śledzić na stronie projektu w
Jest tam również dostępna wersja DigiLx z oknem logowania i znakiem zachęty w linuksowym stylu.


/* ----------------------------------------------
  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;
  }
}

Ten wpis znalazł się na stronie głównej serwisu wykop.pl, otrzymując 500 wykopów. Zobacz też inne wpisy z tego bloga, które trafiły na stronę główną.

Jedna myśl na temat “Mini OS emulator dla Digisparka

  1. Interesujące, inspirujące i proste. Podoba mi się wszystko: mikrokontroler, mały komputerek z fizyczną klawiaturą, użyteczny system i opis tego wszystkiego na przejrzystej stronie www. Brawo!

Dodaj komentarz

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