15.08.2015

Więcej interop: watchdog (IWDG)

Nasz mikrokontroler ma wbudowany system zabezpieczający przed zawieszeniem systemu. Nazywa się to watchdog. Właściwie to ma dwie takie funkcje: IWDG - independent watchdog i WWDG - window watchdog. Znacznie prostszy w użyciu i adekwatny do nieprzewidywalnego czasu działania NET MF jest IWDG. Spróbujemy zatem zaimplementować obsługę takiego watchdoga. W watchdogu chodzi o to, aby cyklicznie co jakiś czas poinformować go, że nasz program działa poprawnie. W przypadku, gdy watchdog nie dostanie takiego sygnału to mikrokontroler samoczynnie się zrestartuje. Nasza implementacja będzie bardzo zbliżona do tej na stronie stm32f4-discovery.com: Library 20- Independent watchdog timer on STM32F4.

A więc do dzieła. Do naszego projektu STM32F4Helper dodajemy nowy plik: Watchdog.cs. Statyczna klasa Watchdog będzie miała dwie funkcje: Start i Reset. Nasz mikrokontroler ma jeszcze jedną dodatkowa informację, która może być bardzo użyteczna: ostatni reset wykonał watchdog. Tą informację zwrócimy przez statyczną właściwość: LastReset. Tak więc cały "kadłubek" klasy wygląda tak:

namespace STM32F4Helper
{
    public static class Watchdog
    {
        public static bool LastReset { get { throw new NotImplementedException(); } } 

        public static void Start(TimeSpan period)
        {
            throw new NotImplementedException();
        }

        public static void Reset()
        {
            throw new NotImplementedException();
        }
    }
}

No dobra uzupełniamy funkcje. Aha. W dokumentacji zobaczymy, że do poprawnego działania watchdoga musimy odpowiednio ustawić rejestry prescallera (IWDG_PR) i licznika (IWDG_RLR). Te wartości obliczymy po stronie kodu zarządzanego na podstawie parametru period metody Start. Jeśli nie popełniłem żadnego błędu to cała klasa powinna wyglądać tak:

namespace STM32F4Helper
{
    public static class Watchdog
    {
        public static bool LastReset { get { return LastResetIwdg(); }  } 

        public static void Start(TimeSpan period)
        {
            SetTimings(period);
            StartIwdg();
        }

        public static void Reset()
        {
            ResetIwdg();
        }

        private static void SetTimings(TimeSpan period)
        {
            const int kHzLsi = 32000;

            long usPeriod = (period.Ticks * 1000) / TimeSpan.TicksPerMillisecond;
            int[] dividers = { 4, 8, 16, 32, 64, 128, 256 };
            for (int i = 0; i < dividers.Length; i++)
            {
                int usMin = (dividers[i] * 1000 * 1000) / kHzLsi;
                if (usPeriod >= usMin)
                {
                    long counter = usPeriod / usMin - 1;
                    if (counter < 0 || counter > 0xFFF)
                        continue;

                    SetupIwdg(i, (int)counter);
                    return;
                }
            }

            throw new InvalidOperationException("Invalid period (0.125..32768 ms).");
        }

        [MethodImpl(MethodImplOptions.InternalCall)]
        private static extern void SetupIwdg(int divider, int counter);

        [MethodImpl(MethodImplOptions.InternalCall)]
        private static extern void StartIwdg();

        [MethodImpl(MethodImplOptions.InternalCall)]
        private static extern void ResetIwdg();

        [MethodImpl(MethodImplOptions.InternalCall)]
        private static extern bool LastResetIwdg();
    }
}

Teraz standardowo generujemy pliki po stronie native. I uzupełniamy funkcje w pliku *_Watchdog.cpp. Oczywiście zmiany z innych plików (dotNetMF.proj, *_Native.cpp, *_Native.h) musimy odpowiednio przenieść do już istniejącej biblioteki. Funkcje po stronie native będą wyglądały tak:

#include "STM32F4Helper_Native.h"
#include "STM32F4Helper_Native_STM32F4Helper_Watchdog.h"

using namespace STM32F4Helper;

void Watchdog::SetupIwdg( INT32 param0, INT32 param1, HRESULT &hr )
{
  IWDG->KR = 0x5555;
  IWDG->PR = param0;
  IWDG->RLR = param1;
  
  RCC->CSR |= RCC_CSR_RMVF;
}

void Watchdog::StartIwdg( HRESULT &hr )
{
  IWDG->KR = 0xCCCC;
  IWDG->KR = 0xAAAA;
}

void Watchdog::ResetIwdg( HRESULT &hr )
{
  IWDG->KR = 0xAAAA;
}

INT8 Watchdog::LastResetIwdg( HRESULT &hr )
{
    INT8 retVal = 0;
    
    if (RCC->CSR & RCC_CSR_WDGRSTF)
       retVal = 1;
     
    return retVal;
}

I już. Zostaje tylko skompilowanie solucji, wgranie przez MFDeploy obrazów hex i przetestowanie. Na przykład takim programem. Diody informują po restarcie jaki był powód: czerwona - watchdog, zielona - zasilanie. Naciśnięcie przycisku wymusza blokadę programu, tak aby zadziałał watchdog.

public class Program
{
    public static void Main()
    {
        OutputPort redLed = new OutputPort(Stm32F4Discovery.LedPins.Red, false);
        OutputPort greenLed = new OutputPort(Stm32F4Discovery.LedPins.Green, false);
        OutputPort blueLed = new OutputPort(Stm32F4Discovery.LedPins.Blue, false);
        InputPort button = new InputPort(Stm32F4Discovery.ButtonPins.User, false, Port.ResistorMode.PullDown);

        OutputPort led = STM32F4Helper.Watchdog.LastReset ? redLed : greenLed;
        led.Write(true);
        Thread.Sleep(1000);
        led.Write(false);

        STM32F4Helper.Watchdog.Start(new TimeSpan(0, 0, 0, 5, 0));
            
        for (;;)
        {
            STM32F4Helper.Watchdog.Reset();

            Thread.Sleep(500);
            blueLed.Write(!blueLed.Read());
                
            if (button.Read())
            {
                while (button.Read())
                {
                }

                while (true)
                {
                    blueLed.Write(!blueLed.Read());
                    Thread.Sleep(100);

                    if (button.Read())
                    {
                        while (button.Read())
                        {
                        }

                        blueLed.Write(false);
                        break;
                    }
                }
            }
        }
    }
}

6.08.2015

Serwo Tower Pro SG90

Tower Pro SG-90 to małe, lekkie i przede wszystkim tanie, uniwersalne serwo. Bez problemu da się podłączyć do STM32F4Discovery. Urządzenie ma 3 przewody połączeniowe: brązowy, czerwony i żółty. Brązowy podłączamy do masy (GND), czerwony do zasilania (+5V), a żółty do wyjścia PWM na płytce. Ja wybrałem wyjście PWMChannel.PWM_0 (czyli pin PD12 - zielona dioda LED).
Tower Pro SG90

Sterowanie kątem obrotu odbywa się poprzez zmianę szerokości impulsu na wejściu PWM. Według dokumentacji częstotliwość sygnału sterującego powinna wynosić 50Hz (okres 20ms), natomiast szerokość impulsu dla wychyleń -90..+90 stopni powinna zawierać się w przedziale: 1ms..2ms. Po zbadaniu skrajnych położeń orczyka wyszło mi, że szerokość impulsu powinna być z przedziału 0.53ms..2.35ms. Do takiego sterowania nadaje się drugi sposób regulacji parametrów PWM: zmiana właściwości Period (czas jednego okresu sygnału) oraz Duration (czas trwania stanu wysokiego). Poniżej kot, który wychyla orczyk do skrajnych położeń:

public static void Main()
{
    const uint period = 20000;    //20ms in us
    const uint minDuration = 530; //0.53ms in us
    const uint maxDuration = 2350;//2.35ms in us
    const uint midDuration = minDuration + (maxDuration - minDuration)/2;

    var servo = new PWM(Cpu.PWMChannel.PWM_0, period, minDuration,
                        PWM.ScaleFactor.Microseconds, false);
    servo.Start();

    for (;;)
    {
        servo.Duration = minDuration;
        Thread.Sleep(2000);

        servo.Duration = midDuration;
        Thread.Sleep(2000);

        servo.Duration = maxDuration;
        Thread.Sleep(2000);

        servo.Duration = midDuration;
        Thread.Sleep(2000);
    }
}

Jeśli byśmy chcieli sterować kątem obrotu podając stopnie, to przyda się jedna bardzo fajna funkcja z Arduino. Funkcja "map":

static uint Map(uint x, uint minX, uint maxX, uint outMin, uint outMax)
{
    return (x - minX)*(outMax - outMin)/(maxX - minX) + outMin;
}

A użyć jej możemy tak (orczyk zmienia położenie co 30 stopni):

public static void Main()
{
    const uint period = 20000; //20ms in us
    const uint minDuration = 530; //0.53ms in us
    const uint maxDuration = 2350; //2.35ms in us

    var servo = new PWM(Cpu.PWMChannel.PWM_0, period, minDuration,
                        PWM.ScaleFactor.Microseconds, false);
    servo.Start();

    uint step = 30;
    for (uint angle = 0;; angle += step)
    {
        if (angle > 180)
        {
            step = (uint) -step;
            continue;
        }

        servo.Duration = Map(angle, 0, 180, minDuration, maxDuration);
        Thread.Sleep(2000);
    }
}

4.08.2015

Bliskie spotkanie z .NET Micro Framework 4.4

Jakiś czas temu dla wersji NET MF 4.4 (4.4 Beta 2 is here!) pojawiła się solucja dla STM32F4Discovery. Postanowiłem więc skompilować SDK oraz solucję. Może się komuś przyda. Pliki dostępne są w repozytorium: MicroFrameworkSDK i obrazów hex. Najpierw zainstalować trzeba SDK z pliku MicroFrameworkSDK.msi, a następnie rozszerzenie vsix dla odpowiedniej wersji Visual Studio. Pliki hex wgrywamy standardowo poprzez MFDeploy. Instalacja przebiega gładko. Natomiast później można napotkać pewne problemy, które opiszę poniżej.

Na systemie Windows 8 i Visual Studio 2015 nie powinno być większych problemów. Schody mogą się pojawić na wersji Windows 7 i Visual Studio 2013.

Problem 1: sterownik USB.
W tej wersji NET MF nie są wymagane jakieś specjalne sterowniki. System sam powinien wykryć i zainstalować sterownik WinUSB dla STM32F4Discovery. Czasami może się automatycznie podpiąć jakiś inny sterownik. Wówczas trzeba go usunąć i pozwolić systemowi na zainstalowanie domyślnego. Jak ma to poprawnie wyglądać jest poniżej:

Windows 8Windows 7

W pewnych przypadkach na Windows 7 sterowniki nie zostaną zainstalowane automatycznie. Trzeba wówczas samemu pobrać sterownik i zainstalować ręcznie. Sterownik Microsoft - Other hardware - WinUsb Device można pobrać ze strony http://catalog.update.microsoft.com (tylko Internet Explorer).

Problem 2: MetaDataProcessor exited with code -1073741515.
Jeśli używamy tylko Visual Studio 2013 (nie mamy zainstalowanego Visual Studio 2015), to prawdopodobnie, przy pierwszej kompilacji, dostaniemy komunikat:


Ręczne uruchomienie MetaDataProcessor.exe pozwoli na uzyskanie dokładniejszej informacji o błędzie: The program can't start because api-ms-win-crt-runtime-l1-1-0.dll is missing. Musimy zainstalować Visual C++ Redistributable for Visual Studio 2015.

Problem 3: zmienione piny.
W solucji zostały zmienione niektóre piny w stosunku do poprzedniej wersji. Różnice poniżej:

I2C pins: scl=PB8 sda=PB9

Brak PWMChannel4 .. PWMChannel7

AnalogChannel0: pin=PA0
AnalogChannel1: pin=PA1
AnalogChannel2: pin=PA2
AnalogChannel3: pin=PA3
AnalogChannel4: pin=PF6
AnalogChannel5: pin=PF7
AnalogChannel6: pin=PF8
AnalogChannel7: pin=PF9
AnalogChannel8: pin=PF10
AnalogChannel9: pin=PF3
AnalogChannel10: pin=PC0
AnalogChannel11: pin=PC1
AnalogChannel12: pin=PC2
AnalogChannel13: pin=PC3
AnalogChannel14: pin=PF4
AnalogChannel15: pin=PF5

COM1: (rx, tx, cts, rts)=(PB7, PB6, PP15, PP15)
COM2: (rx, tx, cts, rts)=(PD6, PD5, PD3, PD4)
COM3: (rx, tx, cts, rts)=(PC11, PC10, PD11, PD12)
COM4: (rx, tx, cts, rts)=(GPIO_NONE, GPIO_NONE, GPIO_NONE, GPIO_NONE)
COM5: (rx, tx, cts, rts)=(GPIO_NONE, GPIO_NONE, GPIO_NONE, GPIO_NONE)
COM6: (rx, tx, cts, rts)=(GPIO_NONE, GPIO_NONE, GPIO_NONE, GPIO_NONE)

To wszystko. Możemy spróbować sił z NET MF 4.4. Jeśli pojawią się jakieś poprawki będę próbował na bieżąco kompilować i SDK i solucję.