Skocz do zawartości
jaromaz

Mini-OS emulator dla Digisparka

Pomocna odpowiedź

Napisano (edytowany)

Sketch DigiOS - mini OS 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. Emulator tylko naśladuje działanie systemu. Nie aspiruje do bycia czymś więcej, a już na pewno nie systemem operacyjnym 🙂 to prosty kod wykonujący kilka poleceń, ale jego efekt końcowy jest dość użyteczny, co można ocenić na poniższym filmie:

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. W filmiku użyłem zamówionych w Botlandzie 10-cio centymetrowych przewodów połączeniowych żeńsko-żeńskich oraz żeńsko-męskich. Reszta sprzętu pochodzi z innych źródeł 🙂

Dostępne komendy:

p[0–2] [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
reboot - software’owo restartuje Digisparka
clear - czyści ekran
ls - wyświetla listę statusów GPIO
temp - podaje temperaturę chipu
login - wyświetla monit o podanie hasła
clock [1–7] - 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). Można wtedy łatwo rozbudować sketch o własne rozwiązania - staje się wtedy szablonem na pomysły użytkownika 🙂

digios-wykresy.gif


Dla Digisparka brak jest przykładów wieloznakowej, obustronnej komunikacji w połączeniu z DigiCDC. Jest dostępne jednoznakowe echo i zwykłe wyświetlanie informacji, ale też nie do końca poprawnie działają, szczególnie w najnowszych systemach. Musiałem przegrzebać się przez sieć i zastosować kilka własnych rozwiązań, żeby wszystko działało tak, jak w filmie. Tu właśnie moduł DigiCDC jest kluczowy i to co dla Arduino jest oczywiste, w Digisparku z DigiCDC wymagało trochę zachodu 😉 Umieszczone w kodzie delay'e są niezbędne bezpłatnej apce na Androida Serial USB Terminal, która jako jedyna obsługuje Digisparka w tym systemie. Bez nich apka źle łamie wyświetlany tekst. W kodzie jest też info, że można usunąć te delay'e, jeżeli do łączenia z Digisparkiem wykorzystuje się inny system operacyjny. Poważnym ograniczeniem biblioteki DigiCDC jest wymuszenie maksymalnie do siedmiu liczby znaków wpisywanej frazy. Przekroczenie tej liczby znaków w ciągu zawiesza komunikację. Definiowane hasło pozwala na wykonanie pewnych operacji z minimalnym poziomem autoryzacji. Minimalnym, bo zakładam, że łatwo jest je wyciągnąć bezpośrednio z Digisparka, ale może się mylę.

Polecenie ls listuje stany LOW/HIGH wszystkich dostępnych GPIO w formie małej tabelki. Sprawdzane są bity - jeżeli wcześniej był ustawiony stan wysoki, to nawet po software'owym restarcie urządzenia polecenie to będzie uwzględniało wcześniejszą zmianę w wyświetlanym statusie. 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.

Chciałem nieco rozpropagować Digisparka, dlatego zależało mi na takiej uproszczonej formie: sketch + micronucleus + DigiCDC. DigiOS to szybka kompilacja w Arduino IDE, bezproblemowy upload do Digisparka przez Arduino IDE dzięki bootloaderowi micronucleus ... i gotowe 🙂 Do uploadowania sketcha do Digisparka wymagane jest Arduino IDE w wersji 1.8.6 oraz poprawna instalacja Digisparka w IDE zgodnie z tą instrukcją. Do DigiOS można zalogować się pod Windows programem Putty (po wcześniejszej instalacji sterowników Digistump), w Linuxie aplikacją Minicom, a w Androidzie wspomnianą aplikacją Serial USB Terminal.

Kod udostępniam poniżej, a jego zmiany można śledzić na stronie projektu w GitHubie - jest tam również dostępna wersja DigiLx z oknem logowania i znakiem zachęty w stylu linuksowym.

/* ----------------------------------------------
  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];
boolean stringComplete = false;
byte state = 1;
byte 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 clockMessageFormat (byte 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(byte speed)
//-----------------------------------------------
// edit the code of this procedure to get the right result
{
  clockMessageFormat(speed);
  for (byte 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(byte 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 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 getTemp()
//-----------------------------------------------
{
  analogReference(INTERNAL1V1);
  analogRead(A0);
  SerialUSB.delay(200);
  int temp = analogRead(A0 + 15) - 273;
  analogReference(DEFAULT);
  SerialUSB.print(F("\r\nDigispark temperature: "));
  SerialUSB.print(temp);
  SerialUSB.println(F("°C"));
}

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

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

static void gpioList()
//-----------------------------------------------
{
 horizontaLine();
 SerialUSB.print(F("\r\nGPIO status list\r\n"));
 horizontaLine();
 for (byte 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 (byte 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;
  }
}

miniaturka.jpg

Edytowano przez jaromaz
  • Lubię! 2

Udostępnij ten post


Link to post
Share on other sites

Ja trochę nie w temacie ale co to za urządzenie, z którego na filmiku łączyłeś się przez usb? To jakiś model telefonu, palmtopa, jeszcze coś innego? Zaciekawiło mnie bo wcześniej czegoś takiego nie widziałem a pracuje pod Androidem, tak?

Udostępnij ten post


Link to post
Share on other sites
(edytowany)
1 godzinę temu, Mechano napisał:

co to za urządzenie, z którego na filmiku łączyłeś się przez usb?

To Gemini PDA - najmniejszy produkowany seryjnie laptop na świecie 🙂 w tym przypadku laptopo-telefon. W triple boot jest zainstalowany Android, pełna desktopowa wersja Debiana oraz Sailfish OS. O urządzeniu można poczytać na stronie producenta https://www.planetcom.co.uk

Edytowano przez jaromaz
  • Lubię! 2

Udostępnij ten post


Link to post
Share on other sites

Bądź aktywny - zaloguj się lub utwórz konto!

Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony

Utwórz konto w ~20 sekund!

Zarejestruj nowe konto, to proste!

Zarejestruj się »

Zaloguj się

Posiadasz własne konto? Użyj go!

Zaloguj się »

×