6.08.2017

Protokół portu szeregowego (RS232): wymagania

Już kiedyś pisałem o porcie szeregowym przy okazji modułu bluetooth HC-05. W tamtym wpisie w bardzo prosty sposób (wysyłanie literek r, g, b, o) sterowałem diodami LED. Jednak dla bardziej zaawansowanych czynności taki "protokół" się nie sprawdzi. Na przykład w pierwszej linii wyświetlacza LCD trzeba wyświetlić tekst? Jak To zrobić? Co wysłać? Jak odebrać? W tym i następnych wpisach, spróbuję przekazać jak sobie w takiej sytuacji poradzić.

Przez ostatnie kilka lat naoglądałem się różnych protokołów. Jeśli nie chcecie wymyślać koła do wozu to polecam stronę Embedded Systems/Common Protocols. Może akurat któryś się nada. Szczególnie należy sprawdzić protokół Modbus. Chociaż uważam, że nie jest on najlepszy, bo detekcja ramek bazuje na czasach przerwy w transmisji (RTU). Jeśli jednak już sami będziemy wymyślać protokół, to według mnie należy przestrzegać następujących reguł:

  •  (ten punkt miał być na końcu, ale przeniosłem go na początek, bo jest najważniejszy)
    w dokumentacji protokołu, dajemy przykłady całych ramek, bajt po bajcie. Jeśli ramki występują w sekwencji, to dajmy przykłady bajt po bajcie całych sekwencji. Taka dokumentacja pomoże w napisaniu testów jednostkowych
  • ramka transmisyjna powinna zaczynać się specjalnym znacznikiem początku, a kończyć specjalnym znacznikiem końca
  • jeśli ramka nie jest stałej długości powinna zawierać informację o długości - alternatywą jest byte stuffing/escaping, ale ja osobiście bym tego unikał.
  • w systemach gdzie niedozwolone jest przekłamanie w danych polecenia (prawie wszędzie), ramka powinna zawierać CRC, a w pozostałych przypadkach (zabawy) można dodać sumę kontrolną
  • ramka powinna być jak najprostsza i jak najkrótsza
  • powinniśmy unikać konstrukcji "user frendly" np. przesyłania komend czy parametrów tekstem tylko po to, aby ramka ładnie się wyświetlała w konsoli podczas debugowania
  • jeśli przesyłać będziemy znaczne ilości danych np. obrazy BMP powinniśmy zadbać o kompresję np. RLE
  • każda komenda powinna być zakończona odpowiedzią, nawet jeśli komenda jest błędna lub nieobsługiwana
  • jeśli komenda przestawia jakąś wartość, to powinna też być komenda, która tą wartość pobiera
  • należy unikać komend, które przełączają; lepszym rozwiązaniem jest włączanie i wyłączanie

20.07.2017

Watchdog IWDG w TinyCLR OS

Dzięki klasie Marshal można pisać bardziej zaawansowane funkcje. Na przykład implementacja watchdoga IWDG może wyglądać tak: 
public class WatchDog
{
    public static bool LastReboot
    {
        get
        {
            var rccAddr = new IntPtr(0x40023800);
            int rccCsrValue = Marshal.ReadInt32(rccAddr, 0x74);
            return IsIwdgRstf(rccCsrValue);
        }
    }

    public static void Start(TimeSpan period)
    {
        ResetLastReboot();
        SetTimings(period);
        WriteIwdgKr(0xCCCC);
    }

    public static void Reset()
    {
        WriteIwdgKr(0xAAAA);
    }

    private static void ResetLastReboot()
    {
        var rccAddr = new IntPtr(0x40023800);
        int rccCsrValue = Marshal.ReadInt32(rccAddr, 0x74);

        if (IsIwdgRstf(rccCsrValue))
        {
            const int rmvfMask = 0x01000000;
            rccCsrValue = rccCsrValue | rmvfMask;
            Marshal.WriteInt32(rccAddr, 0x74, rccCsrValue);
        }
    }

    private static void WriteIwdgKr(int value)
    {
        Marshal.WriteInt32(new IntPtr(0x40003000), value);
    }

    private static bool IsIwdgRstf(int rccCsrValue)
    {
        const int iwdgRstfMask = 0x20000000;
        return (rccCsrValue & iwdgRstfMask) > 0;
    }

    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)
            {
                int counter = (int)(usPeriod / usMin - 1);
                if (counter < 0 || counter > 0xFFF)
                    continue;

                SetIwdgPrAndRlr(i, counter);
                return;
            }
        }

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

    private static void SetIwdgPrAndRlr(int prValue, int rlrValue)
    {
        var iwdgKrAddr = new IntPtr(0x40003000);
        Marshal.WriteInt32(iwdgKrAddr, 0x5555);
        Marshal.WriteInt32(iwdgKrAddr, 0x04, prValue);
        Marshal.WriteInt32(iwdgKrAddr, 0x08, rlrValue);
    }
}

17.07.2017

System.Runtime.InteropServices.Marshal w TinyCLR OS

Bardzo przydatną klasą w TinyCLR OS jest System.Runtime.InteropServices.Marshal. Dzięki metodom z tej klasy uzyskamy dostęp do komórek pamięci. Między innymi rejestry procesora to też komórki pamięci, więc będziemy mogli bezpośrednio je zapisywać lub odczytywać.

Adresy rejestrów zaczynają się od wartości 0x4000 0000. W jednym z wcześniejszych wpisów na temat interop był pobierany unikatowy identyfikator procesora. Ten identyfikator przechowywany jest właśnie w jednym z rejestrów i składa się z trzech 32-bitowych komórek. Rejestr zaczyna się od adresu 0x1FFF 7A10. Jak więc uzyskać unikatowy identyfikator? Nic prostszego!

public static string GetDeviceGuid()
{
    var uidAddr = new IntPtr(0x1FFF7A10);
    int uid0 = Marshal.ReadInt32(uidAddr, 0);
    int uid1 = Marshal.ReadInt32(uidAddr, 0x04);
    int uid2 = Marshal.ReadInt32(uidAddr, 0x08);

    string result = uid0.ToString("X8")
                    + "-" + (uid1 >> 16).ToString("X4")
                    + "-" + (uid1 & 0xFFFF).ToString("X4")
                    + "-" + "0000"
                    + "-" + uid2.ToString("X8") + "0000";

    return result;
}

10.07.2017

TinyCLR OS od GHI Electronics

Od jakiegoś czasu na stronie GHI Electronics pojawiały się wzmianki o tym, że pracują nad nowym rozwiązaniem w stylu .NET Micro Framework. Nazwali to TinyCLR OS. Natomiast kilka dni temu pojawiła się informacja o wypuszczeniu wersji Preview 5 oraz o udostępnieniu kodu, który pozwala wygenerować TinyCLR OS na dowolną płytkę z procesorem STMF4. Żart? Nie!

To działa!

Bez problemu wygenerowałem dla poczciwej płytki STM32F4Discovery TinyCLR OS!. Bez problemu zrobiłem projekt w Visual Studio 2017 i bez problemu uruchomiłem prosty program z mrugającą diodą! Wszystko się debuguje, restartuje, przerywa itp. Bez najmniejszego problemu. Przygotowanie portu zajęło mi 15 minut, a kompilacja 5 sekund! TinyCLR OS będzie sukcesywnie rozwijany i uzupełniany o nowe biblioteki i peryferia.

Poniżej kilka przydatnych linków:
Przy rozpoczynaniu pracy trzeba się trzymać dokumentacji. Aha. Trzeba pamiętać o wgraniu sterownika USB, bo w dokumentacji nie ma o tym wzmianki.

TinyCLR OS Library trzeba podłączyć jako repozytorium lokalne NuGeta np.w ten sposób: How to Install a local sources NuGet package or a Prerelease package in Visual Studio