S .NET Micro Frameworkem v4.0, Board Support Package od Device Soutions a GCC. Aktuální verze balíčku nepodporuje Solution Wizard, takže je potřeba udělat integraci ručně.
Pro detailnější a pokročilejší návod na interop včetně Solution Wizardu doporučuji Stevův článek Using Interop in the .NET Micro Framework V3.0.
Od teď dále předpokládám, že jste nainstalovali porting kit, síťové a kryptografické knihovny a BSP do složky C:\MicroFrameworkPK_v4_0, GCC do složky C:\CodeSourcery. Pokud jste zvolili jiné umístění, nahraďte v dalším textu cesty odpovídajícím způsobem.
Teď už se můžeme pustit do vývoje, nic z výše uvedeného již nebude třeba dělat znovu.
Náš cíl bude velmi primitivní; v podstatě uděláme třídu, která posílá na výstupní pin impulsy. Očekáváme, že:
using Microsoft.SPOT.Hardware; using System.Runtime.CompilerServices; namespace MyFirstLibrary { public class ImpulseSender { uint _pin; // Nativní kód podporuje jen primitivní typy, takže nemůžeme (snadno) předat ani OutputPort ani Cpu.Pin. // Cpu.Pin zabírá čtyři bajty a jak uvidíme později, uint je typ potřebný na nativní straně. public ImpulseSender(OutputPort port) { _pin = (uint)port.Id; // číslo pinu je dostatečné k identifikaci portu na nativní straně, jak rovněž uvidíme } // každou metodu kterou se chystáme implementovat nativním kódem je potřeba deklarovat jako extern a označit následujícím atributem: [MethodImpl(MethodImplOptions.InternalCall)] public extern void WritePulse(bool positive, int durationCycles); } }
#ifndef _TUTORIAL_MYFIRSTLIBRARY_IMPULSESENDER_H_ #define _TUTORIAL_MYFIRSTLIBRARY_IMPULSESENDER_H_ namespace MyFirstLibrary { struct ImpulseSender { // Helper Functions to access fields of managed object static UINT32& Get__pin( CLR_RT_HeapBlock* pMngObj ) { return Interop_Marshal_GetField_UINT32( pMngObj, Library_Tutorial_MyFirstLibrary_ImpulseSender::FIELD___pin ); } // Declaration of stubs. These functions are implemented by Interop code developers static void WritePulse( CLR_RT_HeapBlock* pMngObj, INT8 param0, INT32 param1, HRESULT &hr ); }; } #endif //_TUTORIAL_MYFIRSTLIBRARY_IMPULSESENDER_H_
#include "Tutorial.h" #include "Tutorial_MyFirstLibrary_ImpulseSender.h" using namespace MyFirstLibrary; void ImpulseSender::WritePulse( CLR_RT_HeapBlock* pMngObj, INT8 param0, INT32 param1, HRESULT &hr ) { }
static void WritePulse( CLR_RT_HeapBlock* pMngObj, INT8 positive, INT32 durationCycles, HRESULT &hr );
void ImpulseSender::WritePulse( CLR_RT_HeapBlock* pMngObj, INT8 positive, INT32 durationCycles, HRESULT &hr ) { }
Pokaždé když jsou zmíněné soubory vygenerovány, jsou označený kontrolní hodnotou. Tato hodnota se kontroluje za běhu, aby se zajistilo, že nativní a C# části sedí k sobě. Je tedy rozumné vzít kopii jak nativní tak řízené části a schovat je odděleně, aby se zamezilo škodám způsobeným nechtěnou kompilací knihovny (což by samozřejmě znovu soubory přegenerovalo, takže byste o všechen svůj nativní kód přišli).
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> <FeatureName>MyFirstLibrary</FeatureName> <Guid>{48327512-e47d-402e-8b6a-ea6ca831491e}</Guid> <!-- nejspíš máte jiné guid --> <Description> </Description> <Groups> </Groups> </PropertyGroup> <ItemGroup> <InteropFeature Include="MyFirstLibrary" /> <DriverLibs Include="MyFirstLibrary.$(LIB_EXT)" /> <MMP_DAT_CreateDatabase Include="$(BUILD_TREE_CLIENT)\pe\MyFirstLibrary.pe" /> <RequiredProjects Include="Stubs\Stubs\dotnetmf.proj" /> </ItemGroup> </Project>
<ItemGroup> <InteropFeature Include="MyFirstLibrary" /> <DriverLibs Include="MyFirstLibrary.$(LIB_EXT)" /> <!-- <MMP_DAT_CreateDatabase Include="$(SPOCLIENT)\MyInterops\MyFirstLibrary\ManagedCode\MyFirstLibrary.pe" /> --> <RequiredProjects Include="$(SPOCLIENT)\MyInterops\MyFirstLibrary\NativeCode\dotNetMF.proj" /> </ItemGroup>
<Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup> ... <!-- couple of properties and imports --> </PropertyGroup> <ItemGroup> ... </ItemGroup> <Import Condition="" Project="$(SPOCLIENT)\Framework\Features\core.featureproj" /> <Import Condition="" Project="$(SPOCLIENT)\Framework\Features\Hardware.featureproj" /> <Import Condition="" Project="$(SPOCLIENT)\Framework\Features\NativeEventDispatcher.featureproj" /> <Import Project="$(SPOCLIENT)\Framework\Features\BlockStorage.featureproj" /> ... <!-- lots of project imports --> <Import Project="$(SPOCLIENT)\Solutions\TAHOEII\DeviceCode\Interop\DeviceSolutions.SPOT.Hardware.TahoeII.featureproj" /> <!-- BOD A --> <Import Project="$(SPOCLIENT)\tools\targets\Microsoft.SPOT.System.Interop.Settings" /> <ItemGroup> <RequiredProjects Include="$(SPOCLIENT)\DeviceCode\drivers\LargeBuffer\stubs\dotnetmf.proj"/> <DriverLibs Include="LargeBuffer_hal_stubs.$(LIB_EXT)"/> </ItemGroup> ... <!-- tons of item groups --> <ItemGroup> <RequiredProjects Include="$(SPOCLIENT)\DeviceCode\Pal\Ink\dotNetMF.proj" /> <DriverLibs Include="Ink_pal.$(LIB_EXT)" /> </ItemGroup> <!-- BOD B --> <Import Project="$(SPOCLIENT)\tools\targets\Microsoft.SPOT.System.Targets" /> </Project>
<Import Project="$(SPOCLIENT)\MyInterops\MyFirstLibrary\NativeCode\MyFirstLibrary.featureproj" />
<ItemGroup> <RequiredProjects Include="$(SPOCLIENT)\MyInterops\MyFirstLibrary\NativeCode\dotNetMF.proj"/> <DriverLibs Include="MyFirstLibrary.$(LIB_EXT)"/> </ItemGroup>
void ImpulseSender::WritePulse( CLR_RT_HeapBlock* pMngObj, INT8 positive, INT32 durationCycles, HRESULT &hr ) { if (durationCycles < -1) { // throw ArgumentOutOfRangeException } // něco jako OutputPort.Write(true) pro pozitivní impuls, false pro negativní if (durationCycles == -1) return; // nechat nastavenou hodnotu while (--durationCycles >= 0) { // prázdný cyklus } // něco jako OutputPort.Write(false) pro pozitivní impuls, true pro negativní }
if (durationCycles < -1) { hr = CLR_E_OUT_OF_RANGE; return; }
////////////////////////////////////////////////////////////////////////... // Copyright (c) Microsoft Corporation. All rights reserved. ////////////////////////////////////////////////////////////////////////... #include "SPOT_Hardware.h" HRESULT Library_spot_hardware_native_Microsoft_SPOT_Hardware_OutputPort::Write___VOID__BOOLEAN( CLR_RT_StackFrame& stack ) { NATIVE_PROFILE_CLR_HARDWARE(); TINYCLR_HEADER(); bool state; CLR_RT_HeapBlock* pThis = stack.This(); FAULT_ON_NULL(pThis); if(pThis[ Library_spot_hardware_native_Microsoft_SPOT_Hardware_NativeEventDispatcher::FIELD__m_disposed ].NumericByRef().s1 != 0) { TINYCLR_SET_AND_LEAVE(CLR_E_OBJECT_DISPOSED); } state = stack.Arg1().NumericByRef().u1 != 0; // Test if flag says it is output port if (pThis[ Library_spot_hardware_native_Microsoft_SPOT_Hardware_Port::FIELD__m_flags ].NumericByRefConst().s4 & GPIO_PortParams::c_Output) { // Writes value to the port. ::CPU_GPIO_SetPinState( pThis[ Library_spot_hardware_native_Microsoft_SPOT_Hardware_Port::FIELD__m_portId ].NumericByRefConst().u4, state ); } else { TINYCLR_SET_AND_LEAVE(CLR_E_INVALID_OPERATION); } TINYCLR_NOCLEANUP(); } ...
void CPU_GPIO_SetPinState ( GPIO_PIN Pin, BOOL PinState );
::CPU_GPIO_SetPinState(pin, positive != 0); // impulse start ... ::CPU_GPIO_SetPinState(pin, positive == 0); // impulse end
// Helper Functions to access fields of managed object static UINT32& Get__pin( CLR_RT_HeapBlock* pMngObj ) ...
UINT32 pin = Get__pin(pMngObj);
Kompletní obsah Tutorial_MyFirstLibrary_ImpulseSender.cpp vypadá tedy následovně:
//----------------------------------------------------------------------------- // // ** WARNING! ** // This file was generated automatically by a tool. // Re-running the tool will overwrite this file. // You should copy this file to a custom location // before adding any customization in the copy to // prevent loss of your changes when the tool is // re-run. // //----------------------------------------------------------------------------- #include "SPOT_Hardware.h" #include "Tutorial.h" #include "Tutorial_MyFirstLibrary_ImpulseSender.h" using namespace MyFirstLibrary; void ImpulseSender::WritePulse( CLR_RT_HeapBlock* pMngObj, INT8 positive, INT32 durationCycles, HRESULT &hr ) { if (durationCycles < -1) { hr = CLR_E_OUT_OF_RANGE; return; } UINT32 pin = Get__pin(pMngObj); ::CPU_GPIO_SetPinState(pin, positive != 0); // začátek impulsu if (durationCycles == -1) return; // nechat nastavenou hodnotu while (--durationCycles >= 0) { // prázdný cyklus } ::CPU_GPIO_SetPinState(pin, positive == 0); // konec impulsu }
Poznámka: čekací cyklus, který jsme vytvořil, dělá tuto funkci blokující a žádná další vlákna se nedostanou ke slovu, dokud neskončí. To by nemělo pro naše účely vadit – kdybyste chtěli udělat desetivteřinový puls, udělali byste to byli snadno celé v C#.
Předpokládám, že kompilujeme pro Tahoe-II, v jiném případě nahraďte všechny výskyty TAHOEII za vaši platformu.
Nahraďte všude 4.0.40220.34067 za vaši verzi firmwaru.
Naše knihovna zasílá impulsi na OutputPort. Abychom ji vyzkoušeli, potřebujeme buď LEDku s odporem, jak je uvedeno v prvních krůčkách s GPIO (nebo použít vestavěnou v případě Meridianu/P), nebo připojit osciloskop.
using System; using System.Threading; using Microsoft.SPOT; using Microsoft.SPOT.Hardware; using MyFirstLibrary; namespace MFConsoleApplication1 { public class Program { public static void Main() { Thread.Sleep(5000); // bezpečnostní pauza OutputPort port = new OutputPort( (Cpu.Pin)49 /* GPO3 na Tahoe-II */, false /* výchozí hodnota */); ImpulseSender pulsar = new ImpulseSender(port); try { pulsar.WritePulse(true /* pozitivní */, -5 /* neplatný durationCycle */); } catch (ArgumentOutOfRangeException) { Debug.Print("Chyceno!"); } pulsar.WritePulse(true, 0); // nejkratší možný impuls for (int i = 1000; i < 100000; i *= 2) pulsar.WritePulse(true, i); pulsar.WritePulse(true, -1); // nekonečný impuls, ekvivalentní s port.Write(true); pulsar.WritePulse(false, int.MaxValue); // negativní impuls, ten nejdelší možný Debug.Print("Hotovo!"); } } }
Jen poznámka: tak krátké časy, s jakými si hrajeme tady, dost záleží na tom, jestli se jedná o debug nebo release konfiguraci, jestli je připojen debugger apod. Příklad je určen spíše pro demonstrativní účely.
Pokud chcete nyní upravit kód na nativní straně, stačí změnit soubor Tutorial_MyFirstLibrary_ImpulseSender.cpp a znovu projít kroky Kompilujeme vlastní firmware a Nahráváme vlastní firmware.
Doufám, že brzy uvidíme nějaké vaše nativní knihovny...