Netduino home hardware projects downloads community

Jump to content


The Netduino forums have been replaced by new forums at community.wildernesslabs.co. This site has been preserved for archival purposes only and the ability to make new accounts or posts has been turned off.
Photo

Fluent Interop 1.2


  • Please log in to reply
6 replies to this topic

#1 Corey Kosak

Corey Kosak

    Advanced Member

  • Members
  • PipPipPip
  • 276 posts
  • LocationHoboken, NJ

Posted 16 January 2011 - 06:55 AM

Hey Fluent Interop fans (hello to both of you :))

Today I was able to pass my other milestone, which was getting this library to make a working interrupt handler that could be called from the firmware side. I really like the way it turned out. I have attached a simple demo below. I now have a credible way to both read and write the I/O pins.

The reason this is interesting is because it is much harder than the synchronous case. In the synchronous case, the whole .NET framework screeches to a halt while you're running your precious opcodes. But in the asynchronous case, you need to install a little interrupt handler and find a way to pin its memory down (both the code and whatever buffers it might be using) so that the garbage collector can't move it. For a while I thought I might have to dynamically allocate or reserve a block of memory on the firmware side; I didn't like this idea very much so I was hoping to find another way. But then I had a little brainwave: start a new C# thread and have it create a fixed() block for each buffer it needs to pin. This thread isn't expected to do any work other than sleep forever inside the fixed() block. This technique allows me to allocate whatever storage I need on the C# side (code, buffers, whatever) and keep them around as long as I need to.

The attached files contain:
  • the new firmware
  • a program that demonstrates this, called Demo\InterruptDemoOne\InterruptDemoOne.sln
The reason I needed to make new firmware is because I needed to call a firmware method that previously wasn't in my little dispatch table (namely, CPU_GPIO_EnableInputPin2)

I've also added a feature to my "language" to support static variables--i.e. variables that persist between calls to the compiled code rather than being stack-allocated and therefore transient. Among other things, this enables a poor man's version of shared memory communication, allowing the C# program to inspect or modify the state of the interrupt handler.

My demo program behaves in the following way. It compiles and installs an interrupt handler which will be triggered by interrupts on the onboard button. When an interrupt happens, it will write the button state to the onboard LED. To prove that the C# side is still humming along, the main thread computes prime numbers and prints them out to the debug console (with a 1 second delay so as not to overwhelm the debug window). The thread which is hanging on to the fixed block wakes up once a second and checks the shared variable to see if 30 interrupts have happened (which would correspond to 15 button presses and releases) Once this has happened, it disposes its InputPort (which will disable the interrupt) and then it leaves its little fixed block, unpinning the memory.

Here is a movie, which isn't really that interesting as demos go, but it does prove that the whole thing works.
http://www.youtube.com/watch?v=es17G_u8FZk

For convenience I show Program.cs here. For the whole program you will need to download the attachment. I realize that some of the code here won't make intuitive sense without more detailed documentation, but hopefully you can get the gist of it.

Next: If I find time I may try to see if I can get some higher-quality readings from my PING))) sensor.

using System.Collections;
using System.Threading;
using FluentInterop.CodeGeneration;
using FluentInterop.Expressions;
using FluentInterop.Fluent;
using Kosak.SimpleInterop;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;

namespace InterruptDemoOne {
  public class Program {
    /// <summary>
    /// stick this somewhere so the thread is not prematurely GC'ed
    /// </summary>
    private static Thread thread;

    public static void Main() {
      thread=LaunchISR();
      GeneratePrimes();
    }

    /// <summary>
    /// The purpose of this is to prove that the C# side is still humming away, while my installed firmware code
    /// is showing the button state on the LED
    /// </summary>
    private static void GeneratePrimes() {
      var primes=new ArrayList {2};
      Debug.Print("2 is prime");
      var candidate=3;
      while(true) {
        var isPrime=true;
        foreach(int prime in primes) {
          if(prime*prime>candidate) {
            break;
          }
          if((candidate%prime)==0) {
            isPrime=false;
            break;
          }
        }
        if(isPrime) {
          Debug.Print(candidate+" is prime");
          primes.Add(candidate);
          Thread.Sleep(1000);
        }
        candidate+=2;
      }
    }

    private static Thread LaunchISR() {
      var isrCode=MakeInterruptServiceRoutine();
      var installerCode=MakeInterruptInstaller();

      //run once in order to initialize its internally-stashed method dispatch table (see comments)
      isrCode.Invoke();

      //The purpose of this C# thread is twofold:
      //1. By staying alive, inside the fixed() block, it keeps my isrCode array from being relocated
      //   by the garbage collector (this would be bad, because the code in isrCode has been installed
      //   as a firmware interrupt handler)
      //2. It periodically wakes up and checks the "count" variable inside isrCode (this is a form of
      //   poor man's shared memory communication).  When that count reaches 30 (usually
      //   fifteen button presses and releases), the loop will exit.  When the InputPort is disposed,
      //   this will uninstall the ISR (ISR=Interrupt Service Routine)
      //
      //It should be stressed that the thread is NOT monitoring the button state.  That is being
      //done by hyper-awesome dynamically compiled code.

      var thread=new Thread(() => {
        unsafe {
          fixed(short* isrp=isrCode) {
            //we do this so we can let the system dispose the InputPort, and hopefully uninstall the ISR,
            //when we exit the loop
            using(new InputPort(Pins.ONBOARD_SW1, true, ResistorModes.Disabled)) {
              installerCode.Invoke(sa0 : isrCode);
              var oldCount=-1;
              while(true) {
                var count=isrCode[isrCode.Length-2];
                if(count!=oldCount) {
                  oldCount=count;
                  Debug.Print("count is "+count);
                  if(count>=30) {
                    break;
                  }
                }
                Thread.Sleep(1000);
              }
            }
          }
        }
      });
      thread.Start();
      return thread;
    }

    /// <summary>
    /// By convention, the 17th argument to any of my fluent routines is the "firmware" argument, which I have arranged
    /// to point to a vector of useful firmware routines.
    ///
    /// When you are called by my framework, this argument is set properly.  However, when you are called as an interrupt
    /// service routine, the argument is not set, because the caller (in this case, the firmware) knows nothing about it
    ///
    /// The way we get around this is to keep a static variable inside this routine.  On the first-ever call to this
    /// routine, it stashes the firmware in its own static variable.  This is so subsequent calls can access that
    /// variable.
    /// 
    /// This is why we call the ISR once from C#: to properly initialize this static variable.
    ///
    /// Because my type system isn't complete, there's a lot of casting in this code.  Sorry about that
    /// </summary>
    private static short[] MakeInterruptServiceRoutine() {
      var isrCode=CodeGenerator.Compile((g, buttonPin, pinState, param, ច, ឆ, ជ, ឋ, ឌ, ព, ផ, ត, ថ, ទ, ធ, ម, វ, firmware) => {
        //Our first static variable keeps a count of how many times we are called.
        //The C# side uses this to end the program after the user presses the button a certain number of times.
        //Because it is the first static variable declared, it will be stored at the end of the array.
        //(And since it is an int, it will take up two slots in the short array)
        //Therefore it is guaranteed to live at isrCode[isrCode.Length-2] and isrCode[isrCode.Length-1]
        var clickCount=g.AllocateStaticInt("clickCount");

        //Our second variable stashes a pointer to the firmware method dispatch table.
        //Since it is the second static variable declared, it will be stored just before the first one.
        //(It is our convention to store our statics in the reverse order that they are declared)
        //Therefore it is guaranteed to live at isrCode[isrCode.Length-4] and isrCode[isrCode.Length-3]
        var stashedFirmwareTable=g.AllocateStaticInt("stashedFirmwareTable");

        g.If(stashedFirmwareTable==0, () => {
          stashedFirmwareTable.AssignFrom(firmware.AsInt());
          g.Return(0);
        });
        stashedFirmwareTable.AsFirmwareMethodDispatchTable().SetPinState((int)Pins.ONBOARD_LED, 1-pinState);
        clickCount.AssignFrom(clickCount+1);
      });
      return isrCode;
    }

    private static short[] MakeInterruptInstaller() {
      var installerCode=CodeGenerator.Compile((g, ក, គ, ង, ច, ឆ, ជ, ឋ, ឌ, isr, ផ, ត, ថ, ទ, ធ, ម, វ, firmware) => {
        var result=new IntVariable("result");
        firmware.EnableInputPin2(result, (int)Pins.ONBOARD_SW1, 1, isr.AsFuncPointer(), 0,
(int)Port.InterruptMode.InterruptEdgeBoth, (int)ResistorModes.Disabled);
        g.Return(result);
      });
      return installerCode;
    }
  }
}

Attached Files



#2 Corey Kosak

Corey Kosak

    Advanced Member

  • Members
  • PipPipPip
  • 276 posts
  • LocationHoboken, NJ

Posted 16 January 2011 - 09:32 PM

Next: If I find time I may try to see if I can get some higher-quality readings from my PING))) sensor.


http://www.youtube.com/watch?v=GOj-_bwHXDs

#3 bill.french

bill.french

    Advanced Member

  • Members
  • PipPipPip
  • 260 posts
  • LocationPrinceton, NJ

Posted 17 January 2011 - 01:19 AM

Very nice! Give some details!!

#4 Brandon G

Brandon G

    Advanced Member

  • Members
  • PipPipPip
  • 92 posts
  • LocationVancouver BC, Canada

Posted 17 January 2011 - 01:24 AM

That is fantastic and may help for getting quicker results in my quad project

#5 Corey Kosak

Corey Kosak

    Advanced Member

  • Members
  • PipPipPip
  • 276 posts
  • LocationHoboken, NJ

Posted 17 January 2011 - 02:20 AM

Very nice! Give some details!!


Well, ok! The files are attached and the program that makes it happen is in Demo\PingDemo\PingDemo.sln

I feel a little dirty about this version because, while it's my most ambitious demo so far, I had to put in some hacks to make it work. I'd like a chance to clean up/redo the framework a little.

Attached Files



#6 bill.french

bill.french

    Advanced Member

  • Members
  • PipPipPip
  • 260 posts
  • LocationPrinceton, NJ

Posted 17 January 2011 - 06:12 AM

I was actually hoping you'd talk a bit about what you have going on. Your code is... beyond me at this point, although I certainly want to get there, as I have some ideas that I think would benefit from your work.

It certainly looks more responsive compared to your previous attempt, but what do you think?

Posted Image

#7 Corey Kosak

Corey Kosak

    Advanced Member

  • Members
  • PipPipPip
  • 276 posts
  • LocationHoboken, NJ

Posted 17 January 2011 - 04:12 PM

I was actually hoping you'd talk a bit about what you have going on. Your code is... beyond me at this point,

Bill, I'd be more than happy to try to explain anything I did that you're curious about. I believe the lasting benefit, if any, that will come from this project is not the code itself but rather the ideas/tricks/approach, and it would be very gratifying for me if this work helped nudge someone else forward on their own freaky project.

But... rather than writing down a whole manifesto, it will help me if you can identify some things you are specifically curious about, and we can start there. For example, you might be interested in:
  • This processor, its programming model, and its calling conventions
  • interop from C# to native
  • My various code generation algorithms (some are really silly, like my register "allocator")
  • C# language features that I use promiscuously
etc etc etc. A statement like "I was tracing through X and I couldn't figure out Y" would help kickstart the discussion.

It certainly looks more responsive compared to your previous attempt, but what do you think?

To be honest I'm kind of in denial about this. :unsure: It's quite possible that this version works better for reasons having nothing to do with Fluent Interop: e.g. simple things like the fact that my circuit is different which allowed my sleep time between probes to be shorter (I'm using a single pin for both input and output, TristatePort style, rather than the two-pin approach with a resistor that I was doing before. I know squat about hardware so it's likely I am going to keep doing silly things with my circuits.) The other approach also depends on the C# events getting a timestamp; the precision/responsiveness of that program could depend both on the precision of this timestamp and the latency for delivering the events to the C# program.

If I were a good scientist I'd get clarity on all that stuff, but there are some significant changes I want to do for "version 1.4" and so I want to selfishly focus on that.




0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users

home    hardware    projects    downloads    community    where to buy    contact Copyright © 2016 Wilderness Labs Inc.  |  Legal   |   CC BY-SA
This webpage is licensed under a Creative Commons Attribution-ShareAlike License.