Department of InformatiX
Microsoft .NET Micro Framework Tools & Resources

Your first interop project walkthrough

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.

Prepare the environment

  1. If you don't have yet, install a Visual Studio and .NET Micro Framework SDK if you want to debug our managed interop test. To keep things free, I will use the express edition. You need booth C# and C++. SDK is available here.
    • C++ in Visual Studio 2010 RC does not work yet. You need a 2008 version of C++, sorry.
  2. Install the porting kit. You can download it at Microsoft Download Center, the 4.0.1681.0 version is available here (zipped, approx. 40 MB) – start the installation by running MicroFrameworkPK.MSI.
    • I strongly recommend you to keep the default installation path (usually C:\MicroFrameworkPK_v4_0), the native toolchains are usually not very friendly to spaces and non-ASCII characters.
    • Make sure you are installing a supported version of the porting kit for your board support package.
    • There are no extra options in the Custom installation other than changing the installation path.
  3. Important: Install the network (150 MB) and cryptographic (0.5 MB) libraries. Meridian has the ARM architecture, so you need the ARM version (again, run the MSI files).
    • If you omit this step, everything will still work up to the point where you download your firmware to the device. Then the device will attempt to start but will always abort shortly.
    • Again, keep the default path (usually the same as porting kit installation); no other extra options.
  4. Install the board support package. You can download it at Device Solutions' download page.
    • Keep the same install path as the porting kit had, the board support package will integrate into it.
  5. Now if you check the Device Solutions BSP documentation which got installed into the Start menu under Device Solutions BSP, you will see we have to set the PowerShell to run unsigned scripts (Getting Started\Installing the BSP). In the Start menu, run Windows PowerShell with administrator rights. A blue command window will appear. Type set-executionpolicy remotesigned, press enter and confirm the are-you-sure-prompt.
    • On Windows versions prior Windows Vista resp. Windows Server 2008, you need to install the PowerShell. You can find download links in KB 968929.
    • If you canot find Windows PowerShell in the Start menu, it is located under the Accessories folder.
  6. Install the native toolchain. The board support package (BSP) supports ARM RVDS 3.1 and GCC 4.2, versions are important. We are keeping things free today, so we will stick with the GCC. In the mentioned Device Solutions BSP Documentation, there is a link to the correct version of GCC, which is this one (70 MB). Important: change the installation path to C:\CodeSourcery or similar easy path without spaces! Welcome to the native embedded world.
    • Don't get confused seeing G++, that stands for GNU I guess.

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.

Compile the porting kit

  1. Start the command prompt and navigate to the porting kit directory, e.g. C:\MicroFrameworkPK_v4_0.
    • For those who don't know the trick, you can type cd m and press the Tab key to complete the directory name.
  2. Set the evironment variables. Type setenv_gcc C:\CodeSourcery and press enter. It will reply:
    Setting environment for using Microsoft Visual Studio 2008 x86 tools.
    setting vars for GCC compiler.
    • If you are using the RVDS compiler, use setenv_RVDS3.1 C:\CodeSourcery instead.
  3. Now let's build the porting kit. Type msbuild build.dirproj /p:flavor=release and press enter. Be patient, it took over 15 minutes on my 2 × 2.16 GHz / 3 GB RAM. You should see no errors, but couple of warnings (I had 171).
    • You can hit the Pause key anytime to see the output.
    • To increase the number of lines you can scroll through, right click on the command prompt window caption and choose Properties from the menu. On the Layout tab, Screen Buffer Size group, increase the Height value. The maximum is 9999, but this doesn't cover the whole build output anyway.
    • Don't be afraid seeing couple of red lines during the build. These are probably caused by not having the source code for the companion libraries, but they do not cause the build to fail.
    • If you encountered errors, make sure you had set the environment variables and the setenv_gcc batch confirmed the C++ version you are using. When in doubts, feel free to completely delete the BuildOutput folder and try to build again. If it does not help, you can try e-mailing me the errors.
  4. Build the RAM version of Tahoe-II firmware. As the documentation says, we need a RAM version of the bootloader for the update tool to work (the bootloader can't update itself when it's in flash). Type buildmf TAHOEII RAM and press enter. This one took me almost 3 minutes, no errors, 92 warnings.
    • I've chosen Tahoe-II as my target, because it has a display so I can easily see if something goes wrong. If you build firmware for different devices, use an appropriate parameter instead, e.g. MERIDIANP or TAHOE.
  5. And last one is easy: Create a folder for your interop projects, e.g. C:\MicroFrameworkPK_v4_0\MyInterops. You may also close the command window now.

Now we are ready to start developing, nothing from the above steps will be required again.

Creating the interop library

Our task will be very primitive; we will create a class sending impulses to an output pin. We expect to:

  1. Start the Visual C# Express (resp. Visual Studio) and create a new Micro Framework project of type Class Library, named MyFirstLibrary.
  2. Rename the generated Class1.cs to ImpulseSender.cs.
  3. Add a reference to the Microsoft.SPOT.Hardware assembly.
  4. Write the managed part of the class into ImpulseSender.cs:

    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); } }

  5. Go to project properties and check Generate native stubs for internal methods on the .NET Micro Framework tab.

    In the first textbox you fill a string with which all the names of generated files will begin. As Steve suggests, you should create unique file names across your build. Let it be Tutorial for this walkthrough.

    The second box just specifies the directory in which the files will be generated. The default should be Stubs; if it is empty, fill it in.

    Project properties screenshot

    • If you do not specify any root name, the project name will be used instead.
    • Destination directory is relative to the project's root directory.
  6. Build the project. A Stubs folder will be created, with the following files:
    • dotNetMF.proj
    • MyFirstLibrary.featureproj
    • Tutorial.cpp
    • Tutorial.h
    • Tutorial_MyFirstLibrary_ImpulseSender.cpp
    • Tutorial_MyFirstLibrary_ImpulseSender.h
    • Tutorial_MyFirstLibrary_ImpulseSender_mshl.cpp

    You can find out by inspection that the ones similar to our class are Tutorial_MyFirstLibrary_ImpulseSender.h, the header file, and Tutorial_MyFirstLibrary_ImpulseSender.cpp, the code file.

    The header file contains signatures of methods we previously declared as extern. Note that the parameter names are not preserved, but we can rename them manually (though we have to do that both in the header and in the code file). You can see that the header also contains inline methods to access the fields declared in the managed class:

    #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_

    The code file just sits there and is hungry for our implementation!

    #include "Tutorial.h" #include "Tutorial_MyFirstLibrary_ImpulseSender.h" using namespace MyFirstLibrary; void ImpulseSender::WritePulse( CLR_RT_HeapBlock* pMngObj, INT8 param0, INT32 param1, HRESULT &hr ) { }

  7. Rename the parameters to match the managed class. This is absolutely optional, but getting used to this convention can help you keep the projects readable, especially when there will be more files with lots of methods. I believe the future versions of SDK will manage to keep the names anyway. So we have

    static void WritePulse( CLR_RT_HeapBlock* pMngObj, INT8 positive, INT32 durationCycles, HRESULT &hr );

    in the header file, and

    void ImpulseSender::WritePulse( CLR_RT_HeapBlock* pMngObj, INT8 positive, INT32 durationCycles, HRESULT &hr ) { }

    in the code file.
    • Note that the Boolean parameter is represented by single byte value. This is because C language does not have any Boolean type. Zero means false and anything else means true.
  8. Save the project now, wherever you usually save your projects to.
    • If you are using temporary project feature, you might need to rebuild the project (and rename parameters) once more after saving to get the stubs generated in the newly saved location. You can of course work from the temporary location too.

Integrating the interop library into porting kit

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).

  1. Create a porting kit folder for your project: C:\MicroFrameworkPK_v4_0\MyInterops\MyFirstLibrary.
  2. In that MyFirstLibrary folder, create two folders more: ManagedCode and NativeCode.
  3. Open the C# library project we have created above in the explorer (or your favorite file manager).
  4. Copy all files in the Bin\Release library folder into the ManagedCode one we have just created.
    • The express products do not have configurations. You might find your files in the Bin\Debug folder instead if doing debug build in big Visual Studio.
  5. Copy all files in the Stubs folder into the NativeCode one we have just created. So the final result now looks like this:
    File list screenshot
  6. Open the C:\MicroFrameworkPK_v4_0\MyInterops\MyFirstLibrary\NativeCode\MyFirstLibrary.featureproj file in your favorite xml editor. It looks like this:

    <Project ToolsVersion="3.5" DefaultTargets="Build" xmlns=""> <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\" /> <RequiredProjects Include="Stubs\Stubs\dotnetmf.proj" /> </ItemGroup> </Project>

  7. Update the paths to our snapshot copy and comment the MMP_DAT_CreateDatabase element – it would include the library in the ROM image, which is not very practical during development.

    <ItemGroup> <InteropFeature Include="MyFirstLibrary" /> <DriverLibs Include="MyFirstLibrary.$(LIB_EXT)" /> <!-- <MMP_DAT_CreateDatabase Include="$(SPOCLIENT)\MyInterops\MyFirstLibrary\ManagedCode\" /> --> <RequiredProjects Include="$(SPOCLIENT)\MyInterops\MyFirstLibrary\NativeCode\dotNetMF.proj" /> </ItemGroup>

    • $(SPOCLIENT) points to the porting kit installation path, in our case C:\MicroFrameworkPK_v4_0.
  8. Save and close the MyFirstLibrary.featureproj file. We will leave the dotNetMF.proj untouched.
  9. Open the C:\MicroFrameworkPK_v4_0\Solutions\TAHOEII\TinyCLR\TinyCLR.proj in your favorite xml editor.
    • I've chosen different target when building the RAM firmware, use appropriate paths, i.e. with MERIDIANP resp. TAHOE folders.

    The file looks like this:

    <Project ToolsVersion="3.5" DefaultTargets="Build" xmlns=""> <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>

  10. Include the project in the build tree. At the end of the imports of featureproj files, that is, at the POINT A, include import of our featureproj:

    <Import Project="$(SPOCLIENT)\MyInterops\MyFirstLibrary\NativeCode\MyFirstLibrary.featureproj" />

    At the end of item groups, i.e. at the POINT B, insert our item group:

    <ItemGroup> <RequiredProjects Include="$(SPOCLIENT)\MyInterops\MyFirstLibrary\NativeCode\dotNetMF.proj"/> <DriverLibs Include="MyFirstLibrary.$(LIB_EXT)"/> </ItemGroup>

  11. That's all. Save and close the file.

Implementing the native part

  1. Open our snapshot copy of the native code file, C:\MicroFrameworkPK_v4_0\MyInterops\MyFirstLibrary\NativeCode\Tutorial_MyFirstLibrary_ImpulseSender.cpp. We need basically something like this:

    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 }

  2. The exception part is easy, Steve has covered this in his interop blog: The hr parameter is the way for us to tell about any errors. It is set to S_OK by default, so we need to change it:

    if (durationCycles < -1) { hr = CLR_E_OUT_OF_RANGE; return; }

  3. Now for the pin stuff, obviously the native implementation of OutputPort class would help us a lot. Okay, let's look it out. Naïve solution, but works: Search the porting kit (i.e. C:\MicroFrameworkPK_v4_0) for any file containing the OutputPort in its name:
    Search result screenshot
    So obviously the first one is an object file (kind of compiled), so this wouldn't help us. Also the last three are likely just empty stubs similar to ones we had, moreover they are in the BuildOutput folder which was created during our building of the porting kit. So the only one remaining, C:\MicroFrameworkPK_v4_0\CLR\Libraries\SPOT_Hardware\spot_hardware_native_Microsoft_SPOT_Hardware_OutputPort.cpp is the one we are interested in:

    ////////////////////////////////////////////////////////////////////////... // 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(); } ...

    We can see this code has a different header and rules, it likely was written long time ago before we had the stubs generation, which generates the marshaling code for us. The object disposal test is not of interest to us either, this is because OutputPort inherits Port which inherits NativeEventDispatcher, which is a bit more advanced topic than I want to cover with this tutorial. See, there, ::CPU_GPIO_SetPinState is exactly what we are looking for. Let's be a bit more curious and try to find out its signature. If we now search for files named CPU_GPIO_SetPinState, we won't find anything. Don't worry and select Search again in File Contents :
    Search result screenshot
    We are looking for a signature only, so headers are the files we are interested in. The block storage drivers are probably not what we are looking for (if you check them, they are some inline calls to the method), but the CPU_GPIO_decl.h looks reasonably good. If you open it, you can find all the I/O method signatures there, including the one we are interested in:

    void CPU_GPIO_SetPinState ( GPIO_PIN Pin, BOOL PinState );

    It's a bit more tricky to find out what GPIO_PIN states for (you would eventually find typedef UINT32 GPIO_PIN; in C:\MicroFrameworkPK_v4_0\DeviceCode\Include\tinyhal.h), but it is not necessary to do so, because the OutputPort implementation already has a good clue, as it is converting the parameter to .NumericByRefConst().u4 so one can expect unsigned 4-byte value there, which is what UINT32 is.
    • This is the point where it makes sense to declare the managed pin value as uint to avoid casts burden on the native side.
  4. Now we have an idea how to manipulate the pin value in our code:

    ::CPU_GPIO_SetPinState(pin, positive != 0); // impulse start ... ::CPU_GPIO_SetPinState(pin, positive == 0); // impulse end

    However, as in managed code, we need to tell what or where the CPU_GPIO_SetPinState is. How does the OutputPort class do it? Check the beginning of its code, there is: #include "SPOT_Hardware.h". This is what we need to add, too.
    • Only because we accept an instance of OutputPort in the managed class, we do not need to do the pin initialization and checking. You have to do that if accepting Cpu.Pin directly.
  5. The last thing remaining is how to get the pin value. Remember we have a _pin field in the managed class, and in the generated header file, Tutorial_MyFirstLibrary_ImpulseSender.h, we have seen this:

    // Helper Functions to access fields of managed object static UINT32& Get__pin( CLR_RT_HeapBlock* pMngObj ) ...

    So we need to get a pointer to CLR_RT_HeapBlock somewhere, but that's the first parameter in our WritePulse method! Nothing prevents us now from using this helper method:

    UINT32 pin = Get__pin(pMngObj);

    That's all – save it and you can also close all the other files we have opened.

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.

Building your firmware

I assume we are building for Tahoe-II, if not, replace all the TAHOEII occurrences with your target.

  1. Start the command prompt and navigate to the porting kit directory, e.g. C:\MicroFrameworkPK_v4_0.
  2. Set the evironment variables. Type setenv_gcc C:\CodeSourcery and press enter, confirmation info will appear.
  3. Build the (FLASH version of) Tahoe-II firmware. Type buildmf TAHOEII and press enter. The build will start – mine took almost 3 minutes, no errors, 92 warnings. The build number I got is 4.0.40220.34067, you likely have a different one.
  4. Pick up the output files. Type release and press enter. This will copy couple of files to the C:\MicroFrameworkPK_v4_0\Release\TAHOEII\TAHOEII_4.0.40220.34067\ directory (think your build number), no error should occur. You can now close the command prompt.

Deploying your firmware

Substitute your build number instead of 4.0.40220.34067 everywhere.

  1. Connect the Tahoe-II now, if you haven't done already.
  2. In the mentioned directory, there are three folders, Bin, FirmwareUpdate_TAHOEII_4.0.40220.34067 and BootRecovery_TAHOEII_4.0.40220.34067. The BootRecovery one is for the case things go wrong and you have to deploy in boot mode over serial cable. The FirmwareUpdate is the standard firmware update that you can normally download from Device Solutions web. The bin one is the complete output from the build process, which we don't need now. Run the MeridianFirmwareDeploy.exe from the FirmwareUpdate folder.
    • There is a note in the release notes for current BSP that the boot recovery output does not work. Don't try it, I did some nasty tricks with it and eventually broke my board. If you need to do a recovery firmware update, go to the Device Solutions web, download the latest recovery release (even if it was 3.0) and run that one.
  3. You will see a wizard's welcome screen. Press next, accept the license terms (if you agree of course) and press next.
    Wizard screenshot
    • The auto discovery of devices does not seem to work on this page. If you connected the device later, just restart the wizard.
  4. Important: If this is the first time ever you are deploying your firmware build using GCC, check the Update Configuration. You don't need this if you have already deployed your firmware before.
  5. Press next to begin the firmware update. For the first time, the process will be as follows:
    1. Updating firmware...
    2. The Tahoe-II will now restart (to enter the TinyBooter).
    3. Deploying TinyBooterDecompressorRAM...
    4. Connecting to TinyBooter...
    5. Now the device should restart, but does not, only goes black.
    6. Failed to connect to device! message box appears.
    7. Obviously we must help it with the restart, but we are expected to restart into TinyBooter. You can do that this way: Press SW5 and SW9 together and while keeping them pressed, press the RESET button.
    8. The Tahoe-II will restart and you can see the TinyBooter welcome message on the display.
    9. Press Retry button on the failure message box.
    10. Now the update finishes, you will see a lot of progress, deploying, checking, erasing, writing...
    11. After the longest one, ER_FLASH, it will say Update complete, the wizard will switch to the Finished page after while, but the device will remain black. I suspect the wizard is waiting a while for another restart.
    12. You can close the wizard now.
    13. Again we need to help with the restart. Press the RESET button on the board.
    14. The Tahoe-II blinks some info and goes black again.
    15. No worries, press the RESET button again.
    16. TAHOEII v4.0.40220.34067 Build Date: Feb 11 2010 13:30:53 Waiting for debug commands...
      Now, isn't that cool?

    For successive updates (i.e. without the Update Configuration checked), everything will go fluently without any action needed.
    • To boot into TinyBooter on Meridian/P, hold the user button and restart.

Testing the interop library

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.

  1. Start the Visual C# Express resp. Visual Studio and create a new Micro Framework project of type Console Application.
  2. Add a reference to our snapshot copy C:\MicroFrameworkPK_v4_0\MyInterops\MyFirstLibrary\ManagedCode\MyFirstLibrary.dll (use the Browse tab to locate it). It is important to be this one because it must match exactly the native part we built the firmware with.
  3. Add a reference to the Microsoft.SPOT.Hardware assembly in order to access the OutputPort class.
  4. Now let's enter some test code in the Class1.cs:

    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!"); } } }

    The safety pause is not necessary, but it could save you some recovery firmware updates when doing similar experiments. If you do anything wrong, either in managed, but more likely in the native code, you have no chance to catch the board with Visual Studio or MFDeploy again, because the code is automatically immediately executed as you power up or restart the device, without any opportunity to attach the debugger or similar thing.
  5. Don't forget to select USB transport type and Meridian_a7e70ea2 device (or other combination for your board) in the project properties.
  6. Now enjoy stepping through the code!

Oscilloscope capture

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.

Good luck!

Hope to see your native libraries soon.

Comment Jan Kučera 2/11/2010 3:58:32 PM
Okay, who first does see the problem with above example? Extra points for suggesting a fix.
Comment Richard 3/5/2010 3:04:44 AM
My experience is that the above process does not work using Power Shell (installed Powershell on XP). Is my setup wrong? Process works if I open a VS2008 command Window and then use that as the shell.
Comment Jan Kučera 3/5/2010 12:56:58 PM
You need the PowerShell only in step 5 of preparing the environment,, to set the execution policy. All other commands belong to the standard (or Visual Studio) command prompt window. Is that what caused the confusion?
Comment Richard 3/7/2010 8:11:21 PM
yes. I tried to run build cmd scripts in Power Shell prmpt. :-) Thanks for confirming I should be using normal cmd or VS cmd.
Comment Richard 3/11/2010 10:40:35 AM
Do you have any problems running MF programs on TAHOEII after you install the GCC Build of MF4? What about Windows and USB. Do you get errors from Windows during USB PnP device enumeration with GCC build of MF4? Any other problems?
Comment Jan Kučera 3/11/2010 11:11:04 AM
I haven't faced any problems with either GCC build or USB PnP so far. The only difference when using GCC is a larger footprint.
Comment Ondra Vrzal 3/22/2010 6:06:43 PM
very nice tutorial :). i ran into problems by using different versions of MF SDK and porting kit, so TAHOEII was not build because of several errors. after using links provided i managed to build TAHOEII :). I tried to run 2 application and one didn't worked with this new build opposed to the latest firmware from device solutions :(.
Sign in using Live ID to be able to post comments.