Using .NET Micro Framework v4.0, Device Solutions' Board Support Package and GCC. Current release of the board support package does not support the Solution Wizard, so we have to do the integration manually.
To see more detailed and advanced interop walkthrough including Solution Wizard, check out Steve's Using Interop in the .NET Micro Framework V3.0 blog.
From now on, I suppose you have installed the porting kit, network libraries, cryptographic libraries and BSP into C:\MicroFrameworkPK_v4_0 folder, and the GCC into C:\CodeSourcery folder. If you have choosed different directories, replace them in the following text accordingly.
Now we are ready to start developing, nothing from the above steps will be required again.
Our task will be very primitive; we will create a class sending impulses to an output pin. We expect to:
using Microsoft.SPOT.Hardware; using System.Runtime.CompilerServices; namespace MyFirstLibrary { public class ImpulseSender { uint _pin; // The native code supports only basic values, so we can't (easily) pass neither OutputPort nor Cpu.Pin. // Cpu.Pin is 4-byte enum, and as we will see later uint is what we need on the native side. public ImpulseSender(OutputPort port) { _pin = (uint)port.Id; // the pin number is enough to identify the port on the native side, as we will also see later } // every method we plan to implement in native code must be declared extern and marked with the following attribute [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 ) { }
Everytime you generate the stub files, they are marked with a checksum. This checksum is verified at runtime, to see if the managed and native parts match. So it is a good idea to take snapshot of both the managed and native parts you build the firmware with, and keep it saved separately to prevent damages from building the library project accidentally (which would also rewrite the stub files, so any native code entered would get deleted).
<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> <!-- you likely have a different 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" /> <!-- POINT 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> <!-- POINT 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 } // something like OutputPort.Write(true) for positive pulse, false for negative if (durationCycles == -1) return; // leave the value set while (--durationCycles >= 0) { // empty cycle } // something like OutputPort.Write(false) for positive pulse, true for negative }
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);
The complete listing of Tutorial_MyFirstLibrary_ImpulseSender.cpp is then:
//----------------------------------------------------------------------------- // // ** 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); // impulse start if (durationCycles == -1) return; // leave the value set while (--durationCycles >= 0) { // empty cycle } ::CPU_GPIO_SetPinState(pin, positive == 0); // impulse end }
Note: the waiting cycle we created makes this a blocking call, no other thread will run until it finishes. This should be okay in our case – if you wanted to create a pulse for ten seconds, you could have easily done it in the managed code.
I assume we are building for Tahoe-II, if not, replace all the TAHOEII occurrences with your target.
Substitute your build number instead of 4.0.40220.34067 everywhere.
Our interop code sends out a pulse over an OutputPort. In order to check if it works, you either need to connect a LED with a resistor as described in the managed walkthrough (or use the built-in LED on Meridian/P), or use an oscilloscope.
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); // safety pause OutputPort port = new OutputPort( (Cpu.Pin)49 /* GPO3 on Tahoe-II */, false /* initial value */); ImpulseSender pulsar = new ImpulseSender(port); try { pulsar.WritePulse(true /* positive */, -5 /* invalid durationCycle */); } catch (ArgumentOutOfRangeException) { Debug.Print("Catched!"); } pulsar.WritePulse(true, 0); // the shortest pulse possible for (int i = 1000; i < 100000; i *= 2) pulsar.WritePulse(true, i); pulsar.WritePulse(true, -1); // infinite pulse, equivalent to port.Write(true); pulsar.WritePulse(false, int.MaxValue); // negative pulse, the longest possible Debug.Print("Done!"); } } }
Please note: such small timings we are producing here are very dependent on whether we run debug or release build, whether a debugger is attached etc. This was done mainly for demonstrative purposes.
Now if you want to udpate the native code, you can edit the Tutorial_MyFirstLibrary_ImpulseSender.cpp file and go through the Building your firmware and Deploying your firmware sections again.
Hope to see your native libraries soon.