17.05.2015

Dodawanie nowych funkcji (Interop w .NET Micro framework))

Przychodzi taki czas, gdy czegoś nam w .NET MF brakuje. Potrzebna nam jest jakaś funkcja, a jej nie ma. Jeśli to prosta funkcja, bardzo łatwo sami możemy rozszerzyć nasz system. Jedynym wymaganiem jest umiejętność kompilacji naszej solucji. Bardzo przystępny przewodnik, o tworzeniu takiej dodatkowej biblioteki, znajduje się na stronie: Using Interop in the .NET Micro Framework. Bazując na tym przykładzie zbudujemy własny interop.

Na pewno przyda się jakaś dokumentacja mikrokontrolera STM32F407, bo taki ma nasza płytka:
W pierwszym dokumencie wyczytamy (w punkcie 39.1), że każdy mikrokontroler posiada, zapisany w pamięci, unikalny identyfikator. Taki identyfikator możemy użyć np. przy komunikacji z serwerem do identyfikacji urządzenia. Spróbujemy więc dodać możliwość odczytania tego identyfikatora. Unikalny id znajduje się pod adresem 0x1FFF7A10 i składa się z 96 bitów (12 bajtów). Możemy go zobaczyć w ST-Link:


Tylko jak ten identyfikator odczytać? Na przykład tak: Reading the STM32 unique device ID in C. Wystarczy przenieść ten przykład do naszej solucji. Ale po kolei.

Musimy utworzyć nowy projekt, który będzie nasza biblioteką. Ja projekt nazwałem STM32F4Helper i dodałem jedną klasę: Device. Ta klasa będzie miała jedną statyczna metodę: GetGuid. W tej procedurze zamienimy liczby (pokazane na obrazku) w guid. Od razu trzeba też zaplanować funkcję zewnętrzną (GetUuid32), którą pobierzemy te wartości z pamięci. Cała klasa może wyglądać tak:

using System.Runtime.CompilerServices;

namespace STM32F4Helper
{
    public static class Device
    {
        public static string GetGuid()
        {
            var buffer = new uint[3];
            GetUuid32(buffer);
            string result = buffer[0].ToString("X8")
                            + "-" + (buffer[1] >> 16).ToString("X4")
                            + "-" + (buffer[1] & 0xFFFF).ToString("X4")
                            + "-" + "0000"
                            + "-" + buffer[2].ToString("X8") + "0000";
            return result;
        }

        [MethodImplAttribute(MethodImplOptions.InternalCall)]
        private static extern void GetUuid32(uint[] buffer);
    }
}

Ustawiamy w projekcie opcję generowania native stub i kompilujemy projekt.



We wskazanym katalogu powstanie cześć natywna naszej biblioteki. Nie trzeba się przejmować ilością tych plików. Tak naprawdę, dla prostych funkcji, trzeba uzupełnić tylko plik *Device.cpp (świadomie użyłem  tutaj gwiazdki, aby pokazać, że nazwa pliku wcale nie jest taka skomplikowana, tylko trzeba odpowiednio patrzeć). Pliki przenosimy do katalogu solucji, aby powstała taka struktura:


W katalogu managed można sobie umieścić projekt części zarządzanej biblioteki lub wynik jej kompilacji czyli pliki z katalogu bin\debug lub bin\release. W docelowym projekcie będziemy mogli wówczas dodawać referencję tylko do dll, a nie cały projekt (ale o tym kiedy indziej). Musimy mieć na uwadze to, że jeśli do biblioteki dodamy nowe funkcje lub klasy, to trzeba ponownie wygenerować część natywną.

Przed kompilacją umiejętnie trzeba zmienić niektóre pliki, no i uzupełnić funkcję. Najpierw plik STM32F4Helper.featureproj.  Możemy uzupełnić element description i musimy zmienić ścieżkę w elemencie RequiredProjects na taką:

<!--MMP_DAT_CreateDatabase Include="$(SPOCLIENT)\Solutions\Discovery4\Libraries\STM32F4Helper\Managed\$(ENDIANNESS)\STM32F4Helper.pe" /-->
<RequiredProjects Include="$(SPOCLIENT)\Solutions\Discovery4\Libraries\STM32F4Helper\Native\dotnetmf.proj" />
Dodatkowo komentujemy lub usuwamy z tego pliku element MMP_DAT_CreateDatabase. Dopóki do docelowego programu będziemy dodawać referencję do całego projektu to tej linii nie potrzebujemy.

Dokładamy też od razu naszą bibliotekę do TinyCLR.proj w naszej solucji. Do grupy featureproj dodajemy STM32F4Helper.featureproj:

<Import Project="$(SPOCLIENT)\Solutions\Discovery4\Libraries\STM32F4Helper\Native\STM32F4Helper.featureproj" />

Poniżej całego featureproj dodajemy pozycję z driverlibs:

<ItemGroup>
  <DriverLibs Include="STM32F4Helper.$(LIB_EXT)" />
  <RequiredProjects Include="$(SPOCLIENT)\Solutions\Discovery4\Libraries\STM32F4Helper\Native\dotNetMF.proj" />
</ItemGroup>

Pozostało tylko uzupełnić naszą natywną funkcję. Jak widać nie jest zbyt skomplikowana:

#include <stdint.h>
#include "STM32F4Helper_Native.h"
#include "STM32F4Helper_Native_STM32F4Helper_Device.h"

#define STM32_UUID ((uint32_t *)0x1FFF7A10)

using namespace STM32F4Helper;

void Device::GetUuid32( CLR_RT_TypedArray_UINT32 param0, HRESULT &hr )
{
  param0[0] = STM32_UUID[0];
  param0[1] = STM32_UUID[1];
  param0[2] = STM32_UUID[2];
}

Teraz trzeba tylko skompilować solucję (TinyCLR.proj) i wgrać na płytkę przy pomocy MFDeploy. W programie można teraz użyć takiej funkcji:

using Microsoft.SPOT;
using STM32F4Helper;

namespace DemoInterop
{
    public class Program
    {
        public static void Main()
        {
            string guid = Device.GetGuid();
            Debug.Print("Guid: " + guid);
        }
    }
}
 

A wynik działania jest taki:
Guid: 002B001C-3231-4713-0000-383031370000