14.07.2013

Kompilacja solucji darmowym kompilatorem GCC

Aby skompilować solucję na STM32F4Discovery potrzebujemy płatnych wersji kompilatora Keil MDK lub ARM RVDS. Kompilatory te sporo kosztują więc zakup do zastosowań amatorskich raczej odpada. Jednak już od jakiegoś czasu w projekcie GHI's Open Source NETMF Ports jak i również Netduino używają do kompilacji darmowych kompilatorów GCC. Postanowiłem więc i ja zaadoptować ich rozwiązanie do kompilacji solucji dla STM32F4Discovery. Praca zakończyła się sukcesem i od jakiegoś czasu używam darmowego kompilatora. TinyCLR wychodzi trochę większy (o około 27k) , jest też pewnie mniej wydajny, no ale coś za coś. Poniżej opiszę co i jak trzeba zrobić. Opis dotyczy kompilacji dla wersji PK .NET MF 4.2 RTM QFE2, czyli oficjalnej wersji solucji Discovery4. Katalog z PK trzeba przygotować tak jak we wcześniejszym opisie kompilacji.

Po pierwsze ściągamy i instalujemy kompilator. Ja ściągnąłem wersję zalecaną do kompilacji np. FEZCerberus z GHI z jakieś pół roku temu, czyli GCC ARM Embedded 4.6-2012-q4-update. Jeśli komuś uda się skompilować nowszą wersją, niech da znać w komentarzach. Oczywiście ściągamy plik exe (windows installer) i odpalamy. Wszystko robimy standardowo z jednym małym wyjątkiem. Zmieniamy katalog instalacji na przykład na taki:


Po zainstalowaniu kompilatora trzeba ściągnąć i nagrać na wcześniej przygotowany PK z Discovery4 pliki przygotowane przeze mnie. Pliki te wyciągnąłem z portu GHI. Można je pobrać z mojego svna:
NETMF for STM32 (F4 Edition) Release 4.2 QFE2 RTM GCC

Czas na kompilację. Kompilację TinyBooter możemy sobie darować. Najlepiej użyć oryginalnego lub mojego z katalogu OneWire. On się nie zmienia. Jeśli spróbujemy go skompilować przy pomocy GCC, to może się okazać, że wygenerowany wsad będzie większy niż obszar w którym się ma zmieścić.

To co nas interesuje to kompilacja TinyCLR. Uruchamiamy okno konsoli cmd i przechodzimy do katalogu PK. Odpalamy polecenie:

setenv_gcc 4.6.2 c:\gcc46


Teraz odpalamy tylko kompilację TinyCLR poleceniem:

msbuild /p:flavor=RELEASE;memory=FLASH Solutions\Discovery4\TinyCLR\TinyCLR.proj


i czekamy na zakończenie:


W katalogu C:\MicroFrameworkPK_v4_2\BuildOutput\THUMB2\GCC4.6\le\FLASH\release\Discovery4\bin\tinyclr.hex dostaniemy nasze ER_CONFIG i ER_FLASH, które standardowo, przy pomocy MFDeploy ładujemy do STM32F4Discovery.

Paczka z plikami modyfikującymi solucję, zawiera obsługę karty SD i OneWire, więc jeśli ktoś tego nie chce to musi sobie usunąć z projektu.

2.07.2013

Web server na STM32F4Discovery

Przez sieć możemy w prosty sposób sterować STM32F4Discovery. Dla przykładu program do zapalania i gaszenia diod LED znajdujących się na płytce przez stronę WWW.

Zaczynamy od konfiguracji interfejsu i podpięcia się do zdarzenia tak, aby odbierać żądania HTTP przychodzące na port 80.

Adapter.Start(new byte[] {0x5c, 0x86, 0x4a, 0x00, 0x00, 0xdd},
                "stm32f4", SPI.SPI_module.SPI3,
                Stm32F4Discovery.FreePins.PD1, Stm32F4Discovery.FreePins.PA15);

Adapter.ListenToPort(80);
Adapter.OnHttpReceivedPacketEvent += OnHttpReceivedPacketEvent;

Dodatkowo zdefiniujemy pomocniczy słownik, aby łatwo wybierać odpowiednią diodę na podstawie koloru. Red, Blue, Orange i Green to stałe napisy, które pojawiać się będą w żądaniu. Na przykład wejście na stronę http://stm32f4/led.svc?Orange (tak, zamiast IP można podać nazwę z konfiguracji interfejsu) będzie powodowało zmianę stanu diody pomarańczowej.

_leds = new Hashtable
            {
                {Red, new OutputPort(Stm32F4Discovery.LedPins.Red, false)},
                {Blue, new OutputPort(Stm32F4Discovery.LedPins.Blue, false)},
                {Orange, new OutputPort(Stm32F4Discovery.LedPins.Orange, false)},
                {Green, new OutputPort(Stm32F4Discovery.LedPins.Green, false)}
            };
Została jeszcze implementacja metody obsługującej zdarzenia OnHttpReceivedPacketEvent. Na początku wyciągamy ze ścieżki  zawartość zapytania (po znaku ?) i plik. Na podstawie tych wartości decydujemy czy obsłużyć żądanie czy zwrócić HTTP 404 (linia 34) i którą diodę przełączyć (linia 16). W StringBuilderze produkujemy stronę (odpowiedź serwera), która składa się z 4 linków o odpowiednich adresach.

private static void OnHttpReceivedPacketEvent(HttpRequest request)
{
    lock (SyncRoot)
    {
        int filePos = request.Path.LastIndexOf('/');
        string file = filePos == -1 ? String.Empty : request.Path.Substring(filePos + 1);

        int queryPos = file.LastIndexOf('?');
        string query = queryPos == -1 ? String.Empty : file.Substring(queryPos + 1);

        if (queryPos != -1)
            file = file.Substring(0, queryPos);

        if (file == "led.svc")
        {
            var led = _leds[query] as OutputPort;
            if (led != null)
                led.Write(!led.Read());

            var sb = new StringBuilder("<html><head></head><body>");
            foreach (string key in _leds.Keys)
                sb.Append("<a href=\"/led.svc?" + key + "\">" + key + "</a> ");
            sb.Append("</body></html>");

            byte[] responseBuffer = Encoding.UTF8.GetBytes(sb.ToString());
            using (var responseStream = new MemoryStream(responseBuffer))
            {
                request.SendResponse(new HttpResponse(responseStream));
            }

            return;
        }

        request.SendNotFound();
    }
}

Pełny kot: DemoEnc28J60mIP_WebSrv

1.07.2013

Ethernet na ENC28J60

Długo się przymierzałem do odpalenia sieci na STM32F4Discovery. Na początku nie wiedziałem jakie układy wybrać. Czy iść w stronę MII/RMII czy SPI. Ostatecznie wybrałem SPI czyli coś na układzie ENC28J60. Za tym wyborem przemawia duża liczba publikacji w internecie oraz to, że niektóre płytki do .NET Micro Framework np.: od GHI czy Secret Labs ma interfejsy sieciowe zbudowane na tym układzie. Zaadoptowanie więc rozwiązania do STM32F4Discovery nie powinno być trudne. Jest jeszcze jedna zaleta ENC28J60 jest tani. Gotowy moduł można kupić za około 30 zł. Na początek w sam raz. Ja używam takiego modułu produkcji firmy lcsoft:

ENC28J60 STM32F4Discovery


Sam moduł oczywiście może być inny. Można też samemu zrobić taki interfejs. Ja go po prostu kupiłem.

Kolejnym problemem jest zasilanie. Układ ENC28J60 potrzebuje 3.3V (wg noty katalogowej minimum 3.14V). Na płytce STM32F4 Discovery dostępne mamy tylko 3V i 5V. Trzeba więc użyć zewnętrznego, gotowego zasilacza lub zrobić samemu zasilacz np. tak aby 5V obniżyć do 3.3V. Jeśli mamy zamiar zasilać z 5V to stabilizator powinien być LDO. Ja zrobiłem własny zasilacz na układzie LDO LM1117S według noty katalogowej. Jak to połączyć można zobaczyć na stronie http://fritzing.org/projects/power-3v/.

Kolejnym problemem jest pobór prądu układu ENC28J60. Może on dochodzić do 300mA. Do tego dochodzi jeszcze zasilanie samej płytki STM32F4Discovery. Na przykład przy mruganiu diodami pobiera ona około 40mA. Następne 40mA pobiera część ST-Link/V2 na płytce w stanie spoczynku. W końcu może się okazać, że 500mA z portu USB (maksymalna wydajność dla wersji 2.0) może nie wystarczyć. Port USB 3.0 ma wydajność prądową 900mA więc nie powinno być problemu. Trzeba mieć na uwadze jeszcze to, że wydajność prądowa portu USB może się różnić w zależności od płyty głównej komputera. Jakie jest rozwiązanie? Najprościej użyć zewnętrznego zasilacza do zasilania ENC28J60. Ja poradziłem sobie inaczej. Użyłem dwóch portów USB. Do płytki podłączyłem dwa kabelki USB: do CN1 i CN5. Następnie wyjście PA9 płytki (+5V portu USB CN5) podłączyłem do stabilizatora dla ENC28J60. W taki sposób port CN1 zasila standardowo płytkę, natomiast CN5 zasila moduł ethernet.

No dobra podpinamy moduł ethernet do STM32F4 Discovery. Do tego celu użyłem portu SPI3. Nic jednak nie stoi na przeszkodzie, aby użyć SPI2 (SPI1 nie testowałem). SPI3 ma następujące wyjścia: MSK=PC10, MISO=PC11, MOSI=PC12. Na CS i INT możemy wybrać dowolne, wolne piny np. CS=PA15, a INT=PD1. Wygląda to tak:

ENC28J60 STM32F4Discovery

Jak to uruchomić i się przy tym nie narobić? Z pomocą przychodzi projekt mIP - A C# Managed TCP/IP Stack for .NET Micro Framework. Szkoda tylko, że nie rozwijany. Biblioteka mIP pozwala obsługiwać w prosty sposób ethernet na ENC28J60 bez modyfikacji i kompilacji PK. Nie jest idealna (czasami coś się zacina), ale na razie musi wystarczyć. Do naszego projektu, do referencji, trzeba dodać wcześniej skompilowaną bibliotekę dll lub projekt mIP (NetworkingService i MultiSPI). W źródłach mIP znajdziemy też parę przykładów jak korzystać z biblioteki. Między innymi jak pobrać czas z serwera NTP requestem UDP i prosty serwer http.

Pierwszy kot. Jeśli mamy w sieci serwer DHCP, to będzie bardzo proste. Jeśli DHCP nie mamy to parametry połączenia trzeba ustawić ręcznie (kot znajdziemy w przykładach mIP). Sprawdzamy więc czy wszystko działa:

const SPI.SPI_module spiBus = SPI.SPI_module.SPI3;
const Cpu.Pin chipSelectPin = Stm32F4Discovery.FreePins.PA15;
const Cpu.Pin interruptPin = Stm32F4Discovery.FreePins.PD1;
const string hostname = "stm32f4";
var mac = new byte[] {0x5c, 0x86, 0x4a, 0x00, 0x00, 0xdd};

Adapter.Start(mac, hostname, spiBus, interruptPin, chipSelectPin);
Po uruchomieniu programu w oknie output powinniśmy zobaczyć coś takiego - znaczy wszystko jest ok:

Link is now up :)
Setting IP Address to 10.15.16.109
DHCP SUCCESS! We have an IP Address - 10.15.16.109; Gateway: 10.15.16.1
Updating Gateway Mac from ARP
Done.

W przypadku problemów można zobaczyć bardziej dokładne logi ustawiając przed startem tryb verbose:

Adapter.VerboseDebugging = true;
Adapter.Start(mac, hostname, spiBus, interruptPin, chipSelectPin);

Jak wszystko jest w porządku to "bardziej zaawansowany" przykład. Będziemy pobierać prawdziwą liczbę losową z przedziału 0 do 100 z serwisu www.random.org przez HTTP API.

Aha. Jest pewne ograniczenie w bibliotece mIP. Otóż warstwa TCP może odebrać tylko jeden pakiet (wysyłać może wiele). Więc jeśli odpowiedź przychodzi pofragmentowana to kiszka.

Aha 2. Jest jeszcze jedna sprawa. Aby wykonywać requesty GET ze znakiem ? w url, to trzeba grzebnąć w źródłach mIP. W pliku TCP.cs w linii 556 trzeba wywalić wywołanie funkcji System.Web.HttpUtility.UrlEncode. Czyli linię z:
Path = System.Web.HttpUtility.UrlEncode(url.Substring(Host.Length).Trim(), false);
trzeba zamienić na:
Path = url.Substring(Host.Length).Trim();
No dobra. Teraz już właściwy kot. Jak widać nie jest zbyt skomplikowany:

const SPI.SPI_module spiBus = SPI.SPI_module.SPI3;
const Cpu.Pin chipSelectPin = Stm32F4Discovery.FreePins.PA15;
const Cpu.Pin interruptPin = Stm32F4Discovery.FreePins.PD1;
const string hostname = "stm32f4";
var mac = new byte[] {0x5c, 0x86, 0x4a, 0x00, 0x00, 0xdd};

Adapter.Start(mac, hostname, spiBus, interruptPin, chipSelectPin);

const int minVal = 0;
const int maxVal = 100;

string apiUrl = @"http://www.random.org/integers/?num=1"
    +"&min=" + minVal + "&max=" + maxVal 
    + "&col=1&base=10&format=plain&rnd=new";

var request = new HttpRequest(apiUrl);
request.Headers.Add("Accept", "*/*");

HttpResponse response = request.Send();
if (response != null) 
    Debug.Print("Random number: " + response.Message.Trim());