Pułapki watchdoga AVR

Wiele czasu ostatnio spędziłem na walce z watchdogiem. Ten niepozorny i dość prosty mechanizm może przysporzyć sporo problemów, zwłaszcza gdy nie bierze się pod uwagę paru dość ważnych następstw jego działania. Jako, że źródła internetowe piszą o problematyce różnie i przeważnie tylko o jej fragmencie, postanowiłem zebrać w całość wszystkie znane mi pułapki i je szerzej omówić.

Watchdog po zadziałaniu się sam nie wyłączy

Watchdog jest różnie implementowany na AVRach. Te mniejsze, o ile internet nie kłamie, mają WD, który po swoim zadziałaniu wraca do domyślnego stanu, czyli wyłączenia, o ile fuse’y nie stanowią inaczej. Na tę informację dość często można natrafić. Z kolei bardziej rozbudowane scalaki, w tym ATmega328p mają tak zwanego Enhanced Watchdoga, który po swoim zadziałaniu dalej jest aktywny pomimo restartu programu. Na domiar złego po restarcie będzie on działał z najmniejszym prescalerem, dając restarty po 15ms! Z tego też powodu, jeśli przy starcie aplikacji ma on nie działać, to należy go w pierwszych liniach kodu wyłączyć, mimo że czysto teoretycznie nie został on wcześniej aktywowany.

Przykładowo, na mikrokontrolerze ATmega328p z domyślnie zaprogramowanymi fuse’ami, poniższy kod powinien objawiać się pierwszym dłuższym zaświeceniem diody, a następnie jej krótkimi mignięciami:

#define F_CPU 1000000L
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include <avr/wdt.h>

void main(void) {
	DDRC |= _BV(PC5);
	PORTC |= _BV(PC5);
	_delay_ms(100);
	wdt_enable(WDTO_15MS);
	while(1);
}

Wyłączenie Watchdoga musi nastąpić dość szybko

Datasheet mikrokontrolerów AVR definiuje procedurę zmiany konfiguracji WD jako:

  • Wyzerowanie bitu restartu WD w MCUSR,
  • Wstawienie w jednej operacji jedynki do pól WDE oraz WDCE,
  • W przeciągu kolejnych czterech cyklów procesora przestawienie bitów WDE lub WDP z wyzerowanym bitem WDCE.

Aby ułatwić programistom życie, istnieją funkcje wdt_enable() , wdt_disable()  oraz wdt_reset() . Problem w tym, że o ile funkcja wdt_reset()  jest aliasem jednej instrukcji asemblerowej, to pozostałe dwie są już bardziej złożone i lubią nie działać w zależności od użytych flag optymalizacyjnych, a także od wersji nagłówków AVR – niektóre bowiem są zbugowane. Problem ten można łatwo odwołując się do rejestrów na piechotę:

MCUSR = 0;
WDTCSR|=_BV(WDCE) | _BV(WDE);
WDTCSR=0;

Dla pewności na czas wyłączenia WD powinny zostać wyłączone przerwania, o ile są używane. Z funkcją wdt_enable()  póki co problemu nie miałem, choć pewnie to tylko kwestia czasu. Można by zastąpić czymś ją analogicznym – różnica będzie tylko w drugiej operacji na WDTCSR. W przypadku użycia wdt_disable()  trzeba pamiętać o resecie MCUSR – z nieznanych mi przyczyn gotowiec tego nie robi.

Posted in AVR | Tagged , , , | Leave a comment

Monitoring oparty o Easycam EC-230D i OrangePi

Od jakiegoś czasu bawię się w monitoring DIY na działce. Mój setup składa się z: OrangePi One, dwóch kamer Easycam EC-230D i switcha. Jako, że system pracuje na wygnajewie, łączność zapewnia mi modem GSM na USB z SIM Aero2. Okazuje się, że sieć ta bardzo ładnie działa (~100kB/s) i nie posiada CAPTCHA, gdy wykupi się pakiet danych.

Pierwsza iteracja bazowała na Thinkpadzie x41 i jednej kamerze, jednak ten sobie wydajnościowo nie radził. Dodatkowo, sprzęt ten miał tendencję do wyłączania się z nieznanych przyczyn, a automatyczny reboot był niemożliwy bez dodatkowych sprzętowych przeróbek. Zacisnąłem zęby, zamówiłem OrangePi i system w miarę dobrze działa.

Kamery EC-230D posiadają całkiem niezłe parametry jak na swoją cenę: nagrywanie w 1080p, automatyczne przechodzenie w rejestrowanie podczerwieni oraz dość silne diody, by rozjaśnić otoczenie na około 25 metrów. Problemy jednak sprawia soft, który jest toporny, słabo przetłumaczony, a co najgorsze – tylko na Windowsa.

Konfiguracja adresu IP

Początkowo kamery przychodzą skonfigurowane z adresem w stylu 192.168.1.xxx. IP zmienić się da, ale tylko przy pomocy dostarczonej aplikacji Device Manager. Przetestowana przeze mnie droga konfiguracji to:

  • podłączenie point2point komputera z kamerą,
  • ustawienie w komputerze na sztywno adresu IP w podsieci 192.168.1.xxx,
  • uruchomienie Device Manager i kliknięcie przycisku IP search.

Na liście powinna pojawić się kamera. Po jej wybraniu można zmienić jej konfigurację IP, następnie podpiąć pod resztę sieci i zapomnieć o sprawie.

Konfiguracja parametrów kamery

Tu zaczyna się już gęstwina. Należy zainstalować aplikację CMS, zalogować się jako “super” bez hasła, dodać strefę a następnie do strefy kamerę. Później można się pobawić jej konfiguracją, w tym:

  • rozdzielczością głównego i pobocznego strumienia video,
  • rozkładem tekstu na obrazie: datownika, nazwy kamery;
  • datą, strefą czasową, czasem zimowym.

Alternatywne oprogramowanie

Po chwili bawienia się CMSem człowiekowi zbiera się na wymioty i zastanawia się, jak by rejestrować obraz bez tego muła, który bez jakiejkolwiek podłączonej kamery już na dzień dobry zabiera 100 MB RAMu. Przerobiłem następujące opcje:

  • iSpy na Thinkpadzie X41 – wydajnościwoo nie dawało to sobie w ogóle rady,
  • OpenRTSP – rejestrowanie strumienia miało tendencję do zrywania obrazu, więc nagrywałem serię 2-minutowych filmów by to obejść – brak wykrywania obrazu, niewygodne do przeglądania
  • Motion + MotionEye – chyba najlepsze, co może być do monitoringu.

Samą konfigurację Motion i MotionEye opiszę innym razem. Opiera się ona głównie o przechwytywanie strumienia głównego z adresu: rtsp://ipKamery/user=admin_password=_channel=1_stream=0.sdp . Nie odkryłem, jak dostać się do strumienia dodatkowego.

O ile udało mi się wyeliminować konieczność korzystania z chińskiego softu na codzień, to jednak czasem jest on jeszcze przydatny. Chęć dodania kolejnej kamery spowodowała, że musiałem ją uprzednio z poziomu Windowsa wstępnie skonfigurować, po czym w środku zimy na miejscu montażu siedzieć przed laptopem i poprawiać jej konfigurację. Dodatkowo, datownik wyświetlany na obrazie pozostawiony sam sobie zaczyna rozbiegać się z faktyczną datą i godziną. Rozwiązanie jest proste – wystarczy połączyć się CMSem z kamerą i data sama się zsynchronizuje.

Tego typu problemy powodują, że chciałoby się mieć pod linuksem aplikację zamienną dla CMSa. Podczas ostatniej sesji konfiguracyjnej kamery uruchomiłem WireSharka i moim oczom ukazały się pakiety wysyłane nieszyfrowanym JSONem. Prawdopodobnie pewnego dnia pokuszę się o usystematyzowanie tego protokołu i napisanie własnej aplikacji pracującej z poziomu wiersza poleceń.

Posted in Monitoring | Tagged , , , , , , | Leave a comment

PlipBox a obsługa modułów ethernet

Okazuje się, że PlipBox z założenia miał działać nie tylko na module opartym o ENC28j60, lecz także innych. Obsługa wyboru modułu została zrobiona w sposób dość ciekawy, ale nie będę psuł niespodzianki. Przeanalizujmy sobie fragmenty kodu.

Załóżmy, że natknęliśmy się w kodzie na funkcję sprawdzania, czy przyszła do sieciówki nowa ramka Ethernet:

  while(run_mode == RUN_MODE_PIO_TEST) {
    // handle commands
    // NOTE: UART cmd_worker() processing here, reset by loop break

    // incoming packet?
    if(pio_has_recv()) {
      // do sth
    }
  }

Ciekawe jak działa funkcja pio_has_recv() ? Spójrzmy na jej kod:

uint8_t pio_has_recv(void)
{
  return pio_dev_has_recv(cur_dev);
}

Okej, mamy wrapper. Co znajdziemy dalej?

inline uint8_t pio_dev_has_recv(pio_dev_ptr_t pd)
{
  pio_dev_has_recv_t has_recv_f = (pio_dev_has_recv_t)pgm_read_word(&pd->has_recv_f);
  return has_recv_f();
}

I tu się zaczynaja spaghetti. Wczytywany jest wskaźnik na właściwą funkcję has_recv_f() , której adres został zapisany w ROMie pod adresem pd->has_recv_f . Struktura pd  jest podawana w argumencie, a jest to nic innego jak zmienna globalna cur_dev:

// pio_dev.h
/* device structure */
typedef struct {
  const uint8_t         *name;
  pio_dev_init_t      init_f;
  pio_dev_exit_t      exit_f;
  pio_dev_send_t      send_f;
  pio_dev_recv_t      recv_f;
  pio_dev_has_recv_t  has_recv_f;
  pio_dev_status_t    status_f;
  pio_dev_control_t   control_f;
} pio_dev_t;

typedef const pio_dev_t *pio_dev_ptr_t;

// pio.c
static pio_dev_ptr_t cur_dev;

No dobra, ale gdzie jest wartość tej zmiennej? Cierpliwości:

uint8_t pio_init(const uint8_t mac[6],uint8_t flags)
 {
 // get current device
 cur_dev = (pio_dev_ptr_t)pgm_read_word(devices + dev_id);
 // do sth
 }

Czyli sama jej zawartość również jest wczytywana z ROMu. Powoli zaczyna się klarować sens takiego działania:

static const pio_dev_ptr_t PROGMEM devices[] = {
#ifdef DEV_ENC28J60
  &pio_dev_enc28j60,
#endif
};
#define NUM_DEVICES (sizeof(devices) / sizeof(pio_dev_ptr_t))

Gdzie pio_dev_enc28j60  jest inicjalizowaną strukturą w ROMie zawierającą właściwie wskaźniki na funkcje. Czyli mamy tablicę wskaźników na struktury opisujące interfejs z danym urządzeniem peryferyjnym, wypchane po brzegi kolejnymi wskaźnikami na funkcję. Hura!

Co o tym myśleć?

Cały system miał na celu dodanie do firmware’u supportu dodatkowych modułów sieciowych. Wyszedł potworek, który wprowadza niepotrzebny narzut do prędkości działania kodu, zaśmieca ROM i skutecznie unieczytelnia kod.

Taką decyzję projektową można tłumaczyć chęcią pisania kodu multiplatformowego, ale przecież istnieje wiele innych rozwiązań – chociażby nieśmiertelne #ifdef , które inkludują odpowiedni kod w zależności od flag kompilacji. Oszczędzimy przy tym również cenne miejsce pamięci mikrokontrolera, bo we wsadzie będzie znajdować się tylko to, co niezbędne. Pojawi się wtedy jeden minus, bowiem na każdą konfigurację wsad AVRa trzeba zbudować oddzielnie. Pytanie – czy jest to aż tak straszne, zwłaszcza że sam autor zamieszcza różne wsady dla różnych wersji płytki bazowej z mikrokontrolerem?

Sam system wyboru modułu chyba jednak warto zapamiętać – może się przydać w jakichś przyszłych, bardziej rozbudowanych projektach. A tymczasem z kodu go wywaliłem – oprócz poprawy czytelności zyskałem na tym również ćwierć kilo flasha i 3 bajty RAMu – to ostatnie najcenniejsze przy obecnym zapełnieniu około 80%.

Posted in AVR, PlipUltimate | Tagged , , , , | Leave a comment

Drugi

Półtorej roku minęło a ja dalej nie mam weny na skończenie własnego systemu newsów. Rzeczy, o których można by pisać przybywa, a strona leży. Skorzystam zatem z gotowca.

Posted in Uncategorized | Leave a comment

Pierwszy

Dzień pi roku 2015 wyznacza datę powstania strony. Bardzo mocno placeholderowe, bo system newsów jeszcze leży i kwiczy. Na dniach powinien ruszyć a wraz z nim treść właściwa.

Posted in Uncategorized | Leave a comment