29.07.2012

STM32F4Discovery + NETMF żyje!

Postanowiłem napisać bardziej zaawansowany program niż mruganie jedną diodą. Mrugać będzie wszystkie 4 diody! To będzie taki Hello World dla .NET Micro Framework na STM32F4Discovery. Cztery diody na płytce będą się kręcić w lewo lub w prawo. Naciśniecie przycisku (User button) będzie zmieniać kierunek. Dodatkowo można podłączyć jeszcze jedną diodę led (jak na rysunku poniżej). Ta dioda będzie wskazywała kierunek obrotów.
Podłączenie diody led do STM32F4Discovery
Na początek trzeba zrobić klasę pomocniczą z definicjami pinów po to, aby za każdym razem ich nie liczyć, no i nie wpisywać na sztywno numerków. Dodatkowo klasę umieściłem w oddzielnej bibliotece. 'Worek' na dodatkowe funkcje i definicje zawsze się przydaje.
public class Stm32F4Discovery
{
    static Stm32F4Discovery()
    {
        HardwareProvider.Register(new Stm32F4DiscoveryHardwareProvider());
    }

    private sealed class Stm32F4DiscoveryHardwareProvider : HardwareProvider
    {
    }

    public class Pins
    {
        // ReSharper disable InconsistentNaming
        public const Cpu.Pin GPIO_NONE = Cpu.Pin.GPIO_NONE;

        public const Cpu.Pin PA0 = 0*16 + 0; //0
        public const Cpu.Pin PA1 = (Cpu.Pin) (0*16 + 1); //1 ADC0 COM2(rts)
        public const Cpu.Pin PA2 = (Cpu.Pin) (0*16 + 2); //2 ADC1 COM2(tx)
        public const Cpu.Pin PA3 = (Cpu.Pin) (0*16 + 3); //3 ADC2 COM2(rx)
        public const Cpu.Pin PA4 = (Cpu.Pin) (0*16 + 4); //4

        //...cała definicja pinów jest za długa żeby ją tutaj zmieścić        

        public const Cpu.Pin PE14 = (Cpu.Pin) (4*16 + 14); //78 PWM7
        public const Cpu.Pin PE15 = (Cpu.Pin) (4*16 + 15); //79
        // ReSharper restore InconsistentNaming
    }

    public class ButtonPins
    {
        public const Cpu.Pin User = Pins.PA0;
    }

    public class LedPins
    {
        public const Cpu.Pin Green = Pins.PD12; //60
        public const Cpu.Pin Orange = Pins.PD13; //61
        public const Cpu.Pin Red = Pins.PD14; //62
        public const Cpu.Pin Blue = Pins.PD15; //63
    }

    public class FreePins
    {
        // ReSharper disable InconsistentNaming
        public const Cpu.Pin PA1 = Pins.PA1;
        public const Cpu.Pin PA2 = Pins.PA2;

        //...cała definicja pinów jest za długa żeby ją tutaj zmieścić  

        public const Cpu.Pin PE14 = Pins.PE14;
        public const Cpu.Pin PE15 = Pins.PE15;
        // ReSharper restore InconsistentNaming
    }

    //... i jeszcze inne definicje
}
Następnie zrobiłem oddzielna klasę, która odpowiada tylko za sterowanie diodami led. Dokładnie chodzi o kręcenie ich w prawo lub w lewo:
internal class LedRotator
{
    int _currentIndex;
    private readonly OutputPort[] _leds;
    private readonly int _maxIndex;
    private bool _right = true;
    public bool Right
    {
        get { return _right; }
    }

    public LedRotator(params OutputPort[] leds)
    {
        if (leds == null) 
            throw new ArgumentNullException("leds");

        _maxIndex = leds.Length - 1;
        _leds = leds;
    }

    // ReSharper disable FunctionNeverReturns
    public void Run()
    {
        //krecimy diodamy
        for (;;)
        {
            _leds[_currentIndex].Write(true);
            Thread.Sleep(120);
            _leds[_currentIndex].Write(false);
            _currentIndex = GetNextIndex();
        }
    }
    // ReSharper restore FunctionNeverReturns

    private int GetNextIndex()
    {
        if(Right)
            return _currentIndex == _maxIndex ? 0 : _currentIndex + 1;

        return _currentIndex == 0 ? _maxIndex : _currentIndex - 1;
    }

    public void ChangeDirection()
    {
        _right = !_right;
    }
}
W głównej klasie Program mamy najpierw definicje dwóch portów: UserButton oraz DirectionLed. Port UserButton, jak popatrzymy do dokumentacji STM32F4Discovery, ma rezystor podpięty do masy - dlatego jest pull-down.
private static readonly InterruptPort UserButton = new InterruptPort(Stm32F4Discovery.ButtonPins.User,
                                                                     false,
                                                                     Port.ResistorMode.PullDown,
                                                                     Port.InterruptMode.InterruptEdgeLow);

private static readonly OutputPort DirectionLed = new OutputPort(Stm32F4Discovery.FreePins.PA15, false);
Natomiast w samej głównej procedurze Main jest deklaracja portów diod led zgrupowanych w tablicy. Tablica jest przekazywana do konstruktora klasy LedRotator. Dzięki temu można w łatwy sposób zmieniać liczbę diod do sterowania.
var leds = new[]
                {
                    new OutputPort(Stm32F4Discovery.LedPins.Green, true),
                    new OutputPort(Stm32F4Discovery.LedPins.Orange, true),
                    new OutputPort(Stm32F4Discovery.LedPins.Red, true),
                    new OutputPort(Stm32F4Discovery.LedPins.Blue, true)
                };

var rotator = new LedRotator(leds);
DirectionLed.Write(rotator.Right);
Kolejnym elementem jest podpięcie pod InterruptPort (User button) procedury obsługi. Definicja procedury jest zapisana przy użyciu wyrażenia lambda. Po każdym naciśnięciu przycisku zmieniany jest kierunek obrotów, oraz stan dodatkowej diody. Dodatkowa instrukcja (ClearInterrupt) powoduje skasowanie stanu portu, tak aby zareagował na kolejne zdarzenie.
UserButton.OnInterrupt += (u, data2, time) =>
                                {
                                    rotator.ChangeDirection();
                                    DirectionLed.Write(rotator.Right);
                                    UserButton.ClearInterrupt();
                                };

Blink(leds, 6);
rotator.Run();
Po definicji procedury obsługi przycisku jest uruchomienie LedRotatora. Dodatkowa procedura Blink, mruga wszystkimi diodami - to sekwencja startowa.
private static void Blink(OutputPort[] leds, int blinkCnt)
{
    //mrugamy diodami kilka razy (diody musza zgasnac)
    bool ledState = leds[0].Read();
    for (; blinkCnt > 0 || ledState; blinkCnt--)
    {
        ledState = !ledState;
        foreach (OutputPort led in leds)
            led.Write(ledState);

        Thread.Sleep(1000);
    }
}

Kot dostępny na: https://kodfilemon.googlecode.com/svn/trunk/STM32F4Discovery_Demo/DemoBlink1 ( Checkout )

28.07.2012

Prawda oczywista: CPU.Pin to nie nóżka procesora

Lamer to lamer. Pierwsza skucha. Chciałem wypróbować na szybko STM32F4Discovery, więc napisałem najprostszy program: mruganie zielonej led. Popatrzyłem do dokumentacji płytki: zielony led na porcie PD12 - nóżka 59 i wyszedł taki kot:

using System.Threading;
using Microsoft.SPOT.Hardware;

namespace Led1
{
    public class Program
    {
        public static void Main()
        {
            const Cpu.Pin ledPin = (Cpu.Pin) 59;
            const int delay = 1000;

            using (var ledPort = new OutputPort(ledPin, false))
            {
                while (true)
                {
                    ledPort.Write(true);
                    Thread.Sleep(delay);
                    ledPort.Write(false);
                    Thread.Sleep(delay);
                }
            }
        }
    }
}

Kompiluję, we właściwościach projektu ustawiam odpowiednią konfigurację.


Uruchamiam przez F5, w oknie wynikowym poleciały informacje o wysłaniu kodu do płytki, przeleciały informacje od debuggera, czekam, czekam, czekam i nic. Nie mruga! Nie działa! Dlaczego?

CPU.Pin to nie nóżka procesora. Pisząc programy na mikro-kontrolery trzeba operować na pinach GPIO. Piny GPIO są zgrupowane w porty. Akurat STM32F4 ma 5 portów (od 0 do 5) oznaczonych: A, B, C, D i E. Każdy port ma 16 pinów (od 0 do 15): PA0..PA15, PB0..PB15, PC0...PC15 itd. (dlatego tak oznaczone są szpilki złączy goldpin na płytce). Na tej podstawie możemy określić numery pinów:
  • PA0 =   0*16 + 0   = 0
  • PA1 =   0*16 + 1   = 1
  • PA2 =   0*16 + 2   = 2
  • ...
  • PA15 = 0*16 + 15 = 15
  • PB0 =   1*16 + 0   = 16
  • ...
  • PB15 = 1*16 + 15 = 31
  • PC0 =   2*16 + 0   = 32
  • ...
  • PC15 = 2*16 + 15 = 47
  • ...
  • ...
  • i ostatni
  • PE15 = 4*16 + 15 = 79
Dla zielonej diody (PD12) CPU.Pin będzie równy: 3*16 + 12 = 60. Po podstawieniu do kodu wszystko działa tak jak należy.
const Cpu.Pin ledPin = (Cpu.Pin) 60;

Kot dostępny na: https://kodfilemon.googlecode.com/svn/trunk/STM32F4Discovery_Demo/DemoLed1/ ( Checkout )

27.07.2012

Przygotowanie STM32F4Discovery do pracy z NETMF

Płytka STM32F4Discovery wygląda jak na zdjęciu poniżej. Jak widać urządzenie nie jest zbyt duże.

STM32F4Discovery

Przez producenta mamy wgrany program demonstracyjny. Po podłączeniu płytki przez USB CN1 (zasilanie płytki) powinny zacząć mrugać diody led. Jeśli teraz podłączymy również płytkę przez port USB CN5 do komputera i naciśniemy przycisk użytkownika (ten niebieski), to w komputerze zostanie wykryta dodatkowa mysz i poruszając płytką będziemy mogli sterować kursorem. Jeśli demonstracja przebiegła pomyślnie to płytka jest w porządku.

Przygotowujemy płytkę do wgrania NETMF. Rozłączamy wszystkie USB i ściągamy oraz instalujemy STM32 ST-LINK utility  (zakładka "Design support", na samym dole). Drugi plik nie jest potrzebny, bo instalator ST-LINK instaluje driver USB. Podłączamy płytkę przez USB CN1. Może się zdarzyć, że po ponownym podłączeniu płytki nie zostanie ona wykryta poprawnie, wówczas w menadżerze urządzeń trzeba usunąć nieznane urządzenie i uruchomić ponowne skanowanie zmian sprzętu. Płytka powinna zostać poprawnie wykryta jako "STMicroelectronics STLink dongle" w kontrolerach USB. Teraz można uruchomić ST-LINK. I wgrać tinybooter.

Ze strony projektu NETMF for STM32 (F4 Edition) ściągamy stm32f4discovery.zip oraz STM32_USB_drivers_(for_evaluation_purposes_only).zip. Uruchamiamy ST-LINK i z menu Target wybieramy Connect. Powinno pojawić się coś takiego:


Następnie znowu z menu Target wybieramy Erase Chip i po skończeniu operacji ponownie z menu Target wybieramy tym razem pozycję Program. Wybieramy plik Tinybooter.hex wypakowany z stm32f4discovery.zip i wciskamy przycisk Program. Koniecznie po tej operacji musimy zresetować płytkę przez naciśnięcie czarnego przycisku na niej. Podłączamy płytkę przez USB CN5 do komputera i po wykryciu urządzenia instalujemy driver z pliku STM32_USB_drivers_(for_evaluation_purposes_only).zip. Od teraz USB CN1 służy tylko do zasilania, a USB CN5 do komunikacji z NETMF czyli wgrywania programów i debugowania.

W programach w menu start Windowsa musimy teraz znaleźć "Microsoft .NET Micro Framework 4.2"->"Tools". Z tego katalogu uruchamiamy MFDeploy.exe. Wybieramy w Device USB, a wartość obok powinna wskoczyć automatycznie. Wykonujemy Ping:


W sekcji "Image File" wybieramy plik ER_CONFIG.hex wypakowany z stm32f4discovery.zip i uruchamiamy "Deploy". Następnie to samo robimy z plikiem ER_FLASH.hex. Po zakończeniu operacji (potrwa trochę dłużej), wykonujemy Ping:


Płytka STM32F4Discovery jest przygotowana do działania z .NET Micro Framework.

24.07.2012

Prosty emulator w .NET Micro Framework

Oczekując na płytkę STM32F4Discovery postanowiłem sprawdzić możliwość zbudowania własnego emulatora. Tak jest to możliwe! Możemy wykonać programowy emulator urządzenia i rozwijać oraz testować programy bez udziału fizycznej elektroniki.

Emulator jest bardzo prosty. Ma dwa przyciski, 5 kwadratów udających diody led oraz textbox, który emuluje port szeregowy:

Led i przycisk są zrobione jako kontrolki wizualne (UserControl), a port COM jako komponent (Component), a co za tym idzie można prosto projektować (przeciągając na formatkę) wygląd emulatora. (Uwaga: jeśli w toolboxie nie pokazują się kontrolki sprawdzić rozwiązania).


Kod kontrolki led i przycisku jest następujący:

public partial class LedControl : GpioUserControl
{
    public LedControl()
    {
        InitializeComponent();

        Port.ModesExpected = GpioPortMode.OutputPort;
        Port.ModesAllowed = GpioPortMode.OutputPort;
        Port.OnGpioActivity += Port_OnGpioActivity;
    }

    void Port_OnGpioActivity(GpioPort sender, bool edge)
    {
        Action action = () => BackColor = edge ? Color.Red : Color.White;
        UpdateUI(action);
    }
}
public partial class ButtonControl : GpioUserControl
{
    public ButtonControl()
    {
        InitializeComponent();

        Port.ModesExpected = GpioPortMode.InputPort;
        Port.ModesAllowed = GpioPortMode.InputPort;
    }

    private void Button1Click(object sender, System.EventArgs e)
    {
        Port.Write(true);
        Port.Write(false);
    }
}

Kontrolki dziedziczą ze wspólnego komponentu GpioUserControl, który oprócz tego że zawiera wspólne elementy dla potomnych to implementuje magiczny interfejs IEmulatorComponent. Interfejs ten jest potrzebny po to, aby automatycznie zarejestrować komponenty emulatora (EmulatorComponent) w emulatorze.

Standardowo rejestrację komponentów trzeba robić ręcznie w pliku xml (Emulator.config). Jednak przez dodanie takiego kodu w formatce emulatora, nastąpi ich automatyczna rejestracja niezależnie od ilości.

public MainForm(Microsoft.SPOT.Emulator.Emulator emulator)
{
    InitializeComponent();

    foreach (IEmulatorComponent control in Controls.OfType<IEmulatorComponent>())
        emulator.RegisterComponent(control.GetComponent());

    foreach (IEmulatorComponent control in components.Components.OfType<IEmulatorComponent>())
        emulator.RegisterComponent(control.GetComponent());
}

Przed uruchomieniem właściwego programu trzeba skompilować projekt emulatora, a następnie wybrać odpowiednio platformę do uruchomienia:


Kot dostępny na: https://kodfilemon.googlecode.com/svn/trunk/EmulatorTest/ ( Checkout )

20.07.2012

Płytka do .NET Micro Framework

Trenowanie na sucho nie sprawia jednak takiej frajdy jak uruchomienie programu w rzeczywistym systemie. Postanowiłem się rozejrzeć za czymś co pozwoli mi w prosty sposób testować programy na prawdziwym procesorze. 

Od razu odrzuciłem możliwość zmontowania samemu płytki, lutowania elementów itp. Wydaje mi się, że trzeba mieć trochę doświadczenia w konstrukcji układów mikroprocesorowych, aby samemu (nawet wg. schematów i plików z gotowymi PCB) wykonać taką płytkę. Lutowanie procesora w obudowie LQFP100 też na razie jest poza moim zasięgiem manualnym. Jedynym wyjściem pozostaje zakup gotowej płytki.

Gotowe płytki do NETMF posiada wiele firm w swojej ofercie. Po rozpoznaniu dostępności (zakup w Polsce), ceny (rozbudowane i drogie płytki ewaluacyjne odrzuciłem), popularności (przykłady i wsparcie na internecie) i możliwości (dużo portów, pamięci, jakieś gotowe diody czy przyciski) na polu boju zostały następujące pozycje:
Po głębszym rozpoznaniu tematu okazało się, że najlepsze według mnie pozycje to FEZ Cerberus i Netduino Go. Obydwie płytki działają na tym samym procesorze STM32F4. Na obu płytkach dla użytkownika dostępne jest około 300K FLASH na program (całość 1MB), około 100KB RAMu, dostępne interfejsy: USB, SPI, I2C, UART (port szeregowy), CAN, wejścia/wyjścia analogowe, PWM, OneWire itp. Możliwe jest również podłączenie modułów z interfejsem sieciowym. Zastanowiło mnie to, że obydwie płytki zbudowane są na procesorze STM32F4 (chyba nie bez powodu). Wówczas przypomniałem sobie, że gdzieś czytałem o STM32F4Discovery.

STM32F4Discovery to płytka testowa produkowana przez producenta procesorów STM32F4 - STMMicroelectronics. Na niej znajduje się dokładnie taki sam procesor jak na wyżej wspomnianych FEZ Cerberus i Netduino Go (ma tylko inny oscylator kwarcowy). Na płytce są już zamontowane (do wykorzystania): 4 diody led, jeden przycisk, czujnik położenia, mikrofon i przetwornik dźwięku. Do tego wszystkie porty wyprowadzone są na goldpiny i łatwo można się pod nie podłączać. Jeszcze mało? Cena to około 90 zł z przesyłką w Polsce, no i przede wszystkim na stronie netmf4stm32.codeplex.com specjalnie przygotowana wersja NETMF dla tej płytki! Myślę, że ta płytka będzie najbardziej sensowna, a jak coś nie wyjdzie to niewiele pieniędzy się zmarnuje. Jedyny mankament jaki widzę to brak gotowego portu ethernet, ale wszystko po kolei. Na to jeszcze przyjdzie czas...

Dodane po zakupie: do podłączenia płytki potrzebne są 2 kabelki USB. Jeden 'type A to mini-B' (u mnie spasował kabelek od aparatu cyfrowego) i drugi 'type A to micro-B' (ten musiałem dokupić). Bez tego ani rusz. Wtyki mini-B i micro-B lądują w gniazdach na płytce, a wtyki A (te tradycyjne USB) w komputerze.