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[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!