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 1 USD. Poniżej mój prosty sketch DigiOS, będący emulatorem systemu przeznaczonym dla tego malucha.
Emulator pozwala na zalogowanie się do Digisparka i wykonanie kilku komend. 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[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 |
| 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 [0–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).
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;
}
}

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!