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

Podoba Ci się ten projekt? Zostaw pozytywny komentarz i daj znać autorowi, że zbudował coś fajnego!

Masz uwagi? Napisz kulturalnie co warto zmienić. Doceń pracę autora nad konstrukcją oraz opisem.

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

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Gość
Dołącz do dyskusji! Kliknij, aby zacząć pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.


×
×
  • Utwórz nowe...