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.

Corey Kosak

Member Since 03 Oct 2010
Offline Last Active May 15 2015 02:30 AM
-----

#55116 Help understanding some C# for LED strip

Posted by Corey Kosak on 01 January 2014 - 09:21 PM

I think I can demystify some of this for you.

 

First (as another poster pointed out), this is integer division. In most languages, integer division always rounds down. For example, in real life 129/64 = 2.015625 but a computer doing integer division would round it down to exactly 2.

 

The question is, what can we do when we want "round up" behavior rather than "round down"? The standard trick is

 

replace

   numerator / denominator

with

  (numerator + (denominator-1)) / denominator

 

It has to be written this way because we want to add 1 except when the numerator is evenly divisible by the denominator.

 

Consider these examples in real life (using a calculator):

  • (127/64) = [font="helvetica, arial, sans-serif;"]1.984375, which rounds up to 2[/font]
  • (128/64) = 2 (exactly)
  • (129/64) = [font="helvetica, arial, sans-serif;"]2.015625, which rounds up to 3[/font]

Now let's imagine that we are only allowed to round down. Then:

  • [font="helvetica, arial, sans-serif;"](127+63)/64 = 2.96875, which rounds down to 2[/font]
  • [font="helvetica, arial, sans-serif;"](128+63)/64 = 2.984375, which rounds down to 2[/font]
  • [font="helvetica, arial, sans-serif;"](129+63)/64 = exactly 3, which rounds down to 3[/font]

[font="helvetica, arial, sans-serif;"]You can see that we get the same output (2, 2, 3) as we did in the "round up" case, but we were able to solve our problem using only "round down" operations.[/font]

 

You can try it on other cases and see that the substitution trick always gets you the same answer but you only need to ever round down, not up.

 

Now, as for why you want to round up. As you point out, your code fragment looks a little strange because it's all constants and you could have computed it yourself. Normally however this kind of thing appears in programs where one value (say, numLEDs) is a variable, not a constant. If you replace (most) of the occurrences of "32" with "numLEDs" in this program, you can see how this could be convenient.

 

Now as for your other questions:

  • The reason the high bit (0x80) is set on all the color bytes has to do with the protocol, which simply requires it
  • The reason line 13 exists is because the author is trying to write defensive code. The colors array occurs in groups of three. Line 18 sets the red component of each triple (which occur in any given triple at offset 0 of the triple). Line 17 sets the green component (which occurs at offset 1). But you'll notice that the blue component is not set inside the loop. Line 13 exists to make sure that any unset values have the default value of 0x80 (again, the protocol requires that the high bit be asserted in all of these bytes). This is probably overkill. If it were me, I'd probably delete line 13 and insert a new line in the inner for loop, namely:
    [color=rgb(0,0,0);font-family:monospace;font-size:13px;background-color:rgb(250,250,250);]colors[/color][color=rgb(102,102,0);font-family:monospace;font-size:13px;background-color:rgb(250,250,250);][[/color][color=rgb(0,0,0);font-family:monospace;font-size:13px;background-color:rgb(250,250,250);]i [/color][color=rgb(102,102,0);font-family:monospace;font-size:13px;background-color:rgb(250,250,250);]*[/color][color=rgb(0,0,0);font-family:monospace;font-size:13px;background-color:rgb(250,250,250);] [/color][color=rgb(0,102,102);font-family:monospace;font-size:13px;background-color:rgb(250,250,250);]3[/color][color=rgb(0,0,0);font-family:monospace;font-size:13px;background-color:rgb(250,250,250);] [/color][color=rgb(102,102,0);font-family:monospace;font-size:13px;background-color:rgb(250,250,250);]+[/color][color=rgb(0,0,0);font-family:monospace;font-size:13px;background-color:rgb(250,250,250);] [/color][font="monospace;font-size:13px;background-color:rgb(250,250,250);"]2[/font][color=rgb(102,102,0);font-family:monospace;font-size:13px;background-color:rgb(250,250,250);]][/color][color=rgb(0,0,0);font-family:monospace;font-size:13px;background-color:rgb(250,250,250);] [/color][color=rgb(102,102,0);font-family:monospace;font-size:13px;background-color:rgb(250,250,250);]=[/color][color=rgb(0,0,0);font-family:monospace;font-size:13px;background-color:rgb(250,250,250);] [/color][color=rgb(0,102,102);font-family:monospace;font-size:13px;background-color:rgb(250,250,250);]0x80; // put this before line 17[/color]

I'm not sure what source code you adapted this from, but the lengthy comments on top of https://github.com/a...ter/LPD8806.cpp pretty much explain why this is so, almost. (almost!)

 

In particular, there's this paragraph:

[color=rgb(153,153,136);font-style:italic;]Curiously, zero bytes can only travel one meter (32[/color]
[color=rgb(153,153,136);font-style:italic;]LEDs) down the line before needing backup; the next meter requires an[/color]
[color=rgb(153,153,136);font-style:italic;]extra zero byte, and so forth. [/color]

 

If the constants in the array involved "31 and 32" rather then "63 and 64", this would match the above explanation perfectly. In other words, given the number of LEDs we have, we need to compute how many "meters" of strip we have. As a result of the above explanation, I would have expected to see a line like this:

 

 

[color=rgb(0,0,136);font-family:monospace;font-size:13px;]var[/color][color=rgb(0,0,0);font-family:monospace;font-size:13px;] zeros [/color][color=rgb(102,102,0);font-family:monospace;font-size:13px;]=[/color][color=rgb(0,0,0);font-family:monospace;font-size:13px;] [/color][color=rgb(0,0,136);font-family:monospace;font-size:13px;]new[/color][color=rgb(0,0,0);font-family:monospace;font-size:13px;] [/color][color=rgb(0,0,136);font-family:monospace;font-size:13px;]byte[/color][color=rgb(102,102,0);font-family:monospace;font-size:13px;][[/color][color=rgb(0,102,102);font-family:monospace;font-size:13px;]3[/color][color=rgb(0,0,0);font-family:monospace;font-size:13px;] [/color][color=rgb(102,102,0);font-family:monospace;font-size:13px;]*[/color][color=rgb(0,0,0);font-family:monospace;font-size:13px;] [/color][color=rgb(102,102,0);font-family:monospace;font-size:13px;](([/color][font="monospace;font-size:13px;"]numLEDs[/font][color=rgb(0,0,0);font-family:monospace;font-size:13px;] [/color][color=rgb(102,102,0);font-family:monospace;font-size:13px;]+[/color][color=rgb(0,0,0);font-family:monospace;font-size:13px;] [/color][font="monospace;font-size:13px;"]31[/font][color=rgb(102,102,0);font-family:monospace;font-size:13px;])[/color][color=rgb(0,0,0);font-family:monospace;font-size:13px;] [/color][color=rgb(102,102,0);font-family:monospace;font-size:13px;]/[/color][color=rgb(0,0,0);font-family:monospace;font-size:13px;] [/color][font="monospace;font-size:13px;"]32[/font][color=rgb(102,102,0);font-family:monospace;font-size:13px;])];[/color]

 

I can't explain why it's 63 and 64 rather than 31 and 32, but I hope the rest of this explanation helps.

 




#19383 Atomicity in the MF

Posted by Corey Kosak on 19 October 2011 - 12:39 AM

Maybe this is obvious to everyone else, but I felt it might be worthwhile to clarify Mario's question. This might be helpful to other readers.

In this case the question is not really about the usual read-modify-write race between two threads. It is about whether two threads can safely write an object which is wider than 32 bits.

So a simpler example is this one:

void Worker1() {
  _counter = 0x1111111111111111;
}

void Worker2() {
  _counter = 0x2222222222222222;
}

The key question is whether this interleaving of operations is possible:

* Worker1 writes low word of _counter (11111111)
* Worker2 writes both low and high words of _counter (2222222222222222)
* Worker1 writes high word of _counter (11111111)

If such an interleaving is possible, then we are in the worst of both worlds. The result would be 0x1111111122222222, which means that neither Worker1 nor Worker2 won the race; rather, the result was a corrupt combination of Worker1's and Worker2's output.

Now this may happen to work on the current Netduino, which as Chris says probably won't suspend a thread in the middle of a CLR instruction. But who knows, maybe some day there will be a JITter on the MicroFramework, or some other way to compile to native code on the device, and then code like this may well break. One might as well get into the habit of writing code that conforms to the .NET standard (using e.g. lock() or Interlocked.*), as these habits will pay off handsomely over time.


#15828 string to int's

Posted by Corey Kosak on 23 July 2011 - 06:52 PM

I did a simple test where I run both methods 1000 times and this is what my netduino plus shows:
Time using Split and Parse: 13621 [ms]
Time not using Split and Parse: 18837 [ms]
The thread '<No Name>' (0x1) has exited with code 0 (0x0).

Only when using single digit ints was the split and parse method slower ( by a small margin )


This benchmark isn't really definitive because String2 isn't written as tightly as it could be. If we do some optimizations (which wouldn't matter in the full .NET framework, but appear to matter a lot in the Micro Framework), we get the following routine which beats the pants off of both your String1 and String2:

    public static ArrayList String3(string mystring) {
      var numList=new ArrayList();
      var currSum=0;
      var length=mystring.Length;
      for(var i=0; i<length; i++) {
        var ch=mystring[i];
        if(ch!=',') {
          currSum=currSum*10+(ch-48);
        } else {
          numList.Add(currSum);
          currSum=0;
        }
      }
      numList.Add(currSum);
      return numList;
    }

I used Utility.GetMachineTime because I don't know where Stopwatch lives. My timings:

Time using Split and Parse: 00:00:13.2957654
Time not using Split and Parse: 00:00:17.6939520
improved code (not using Split and Parse): 00:00:07.9082027


#15154 SimpleNGen 1.4

Posted by Corey Kosak on 06 July 2011 - 09:29 PM

I've pushed this project a little further along and now it has support for compiling multiple methods. Currently the rules are:

  • There is one designated 'primary' method, with a standard signature, suitable for calling from C#
  • There can be additional other methods, with whatever signature you like
  • These methods can all each other, or recursively call themselves

The system still has some limitations (and, no doubt, bugs) but is currently working well enough to for me to write Quicksort and compare its interpreted vs. compiled performance. I chose Quicksort because it's sort of cool and also because its implementation involves both recursion and a couple of subroutines. The code is below. As with all the previous versions of SimpleNGen, I work with pointer types rather than arrays, because it's easier to for me compile code that uses pointer semantics. Although this means I am using unsafe code, and unsafe code is evidently not fully supported on the Micro Framework, it appears to work well enough for my purposes.

My test program actually compares three different cases:
  • Interpreted unsafe code (using pointers)
  • Interpreted safe code (using arrays)
  • Compiled code (using pointers)
#1 and #3 are using exactly the same source code, which is pretty cool.

Below are the results for an array of 10,000 ints. The compiled code is apparently about 200X faster. One surprise was that the interpreted code using arrays was quite a bit faster than the interpreted-but-unsafe code using pointers. I don't know why that should be so (of course the compiled version blew them both away).

Interpreted (pointer version): 00:00:22.5213866
Interpreted (array version): 00:00:17.6493440
Compiled: 00:00:00.0873174

The following code fragments are:
  • The Quicksort code with unsafe pointers (for tests 1 and 3)
  • The safe version with arrays (test 2)
  • The driver
Also attached is a zip file with the test program and the SimpleNgen 1.4 compiler.

The unsafe version with pointers, suitable for interpretation or compilation:
  /// <summary>
  /// From the Wikipedia entry for Quicksort
  /// </summary>
  public unsafe static class Quicksort {
    public static void Doit(int left, int right, int dummy0, int dummy1, int* array) {

      if(right>left) {
        var pivotIndex=(left+right)>>1;
        var pivotNewIndex=Partition(array, left, right, pivotIndex);
        Doit(left, pivotNewIndex-1, 0, 0, array);
        Doit(pivotNewIndex+1, right, 0, 0, array);
      }
    }

    private static int Partition(int* array, int left, int right, int pivotIndex) {
      var pivotValue=array[pivotIndex];
      Swap(array+pivotIndex, array+right);
      var storeIndex=left;
      for(var i=left; i<right; ++i) {
        if(array[i]<pivotValue) {
          Swap(array+i, array+storeIndex);
          ++storeIndex;
        }
      }
      Swap(array+storeIndex, array+right);
      return storeIndex;
    }

    private static void Swap(int *a, int *b ) {
      var temp=*a;
      *a=*b;
      *b=temp;
    }
  }

The array version, for comparison's sake:
  public static class Quicksort_Array {
    public static void Doit(int left, int right, int[] array) {

      if(right>left) {
        var pivotIndex=(left+right)>>1;
        var pivotNewIndex=Partition(array, left, right, pivotIndex);
        Doit(left, pivotNewIndex-1, array);
        Doit(pivotNewIndex+1, right, array);
      }
    }

    private static int Partition(int[] array, int left, int right, int pivotIndex) {
      var pivotValue=array[pivotIndex];
      Swap(ref array[pivotIndex], ref array[right]);
      var storeIndex=left;
      for(var i=left; i<right; ++i) {
        if(array[i]<pivotValue) {
          Swap(ref array[i], ref array[storeIndex]);
          ++storeIndex;
        }
      }
      Swap(ref array[storeIndex], ref array[right]);
      return storeIndex;
    }

    private static void Swap(ref int a, ref int b ) {
      var temp=a;
      a=b;
      b=temp;
    }
  }
}

The driver
using Kosak.SimpleInterop;
using System;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;

namespace NgenSandbox {
  public class Program {
    public static void Main() {
      const int numItems=10000;
      const int randomSeed=12345;
      var interpretedTime_InterpretedPointer=new QuicksortTester(numItems, randomSeed).RunInterpreted_Pointer();
      var interpretedTime_InterpretedArray=new QuicksortTester(numItems, randomSeed).RunInterpreted_Array();
      var compiledTime=new QuicksortTester(numItems, randomSeed).RunCompiled();

      Debug.Print("Elapsed times for "+numItems+" items (random seed is "+randomSeed+")");
      Debug.Print("Interpreted (pointer version): "+interpretedTime_InterpretedPointer);
      Debug.Print("Interpreted (array version): "+interpretedTime_InterpretedArray);
      Debug.Print("Compiled: "+compiledTime);
    }
  }

  public class QuicksortTester {
    private readonly int[] data;

    public QuicksortTester(int count, int seed) {
      Debug.Print("Initializing data");
      var r=new Random(seed);
      this.data=new int[count];
      for(var i=0; i<data.Length; ++i) {
        data[i]=r.Next();
      }
    }

    public unsafe TimeSpan RunInterpreted_Pointer() {
      DumpData("Interpreted_Pointer: before");
      var start=Utility.GetMachineTime();
      fixed(int* datap=data) {
        Quicksort.Doit(0, data.Length-1, 0, 0, datap);
      }
      var end=Utility.GetMachineTime();
      DumpData("Interpreted_Pointer: after");
      return end-start;
    }

    public TimeSpan RunInterpreted_Array() {
      DumpData("Interpreted_Array: before");
      var start=Utility.GetMachineTime();
      Quicksort_Array.Doit(0, data.Length-1, data);
      var end=Utility.GetMachineTime();
      DumpData("Interpreted_Array: after");
      return end-start;
    }

    public TimeSpan RunCompiled() {
      DumpData("Compiled: before");
      var start=Utility.GetMachineTime();
      NativeInterface.Execute(QuicksortOutput.opCodes, 0, data.Length-1, 0, 0, data, null, null, null);
      var end=Utility.GetMachineTime();
      DumpData("Compiled: after");
      return end-start;
    }

    private void DumpData(string title) {
      Debug.Print("*** "+title+" ***");
      for(var i=0; i<data.Length; ++i) {
        Debug.Print(i+" "+data[i]);
      }
    }
  }
}

Attached Files




#12264 Help with Ic2 Lockup

Posted by Corey Kosak on 20 April 2011 - 02:51 AM

Any one have a better method?

I think the original sin here was to let the display be under the control of two different masters who put a lot of effort into safely handing off control of the display from one to the other. This led to all kinds of uncertainty and messiness. Instead I think it is much simpler if you follow the following principles.
  • Dedicate a thread whose job is display updates
  • Make that thread the sole owner of the display (no one else makes calls to the display library)
  • When someone would like to change the display, they post a message to a queue owned by this thread, and this thread processes the next item in the queue when it is ready
  • When there are no messages in the queue, this thread shows the time

The below class, while not 100% complete, is an example of how to do this:

  public class ControlTheDisplay {
    private readonly ExampleLCD _exampleSensor;
    private readonly Thread myThread;
    private readonly object sync=new object();
    private readonly AutoResetEvent are=new AutoResetEvent(false);
    private readonly Queue items=new Queue();

    public ControlTheDisplay(ExampleLCD exampleSensor) {
      _exampleSensor=exampleSensor;
      myThread=new Thread(Run);
    }

    public void Show(string message) {
      lock(sync) {
        items.Enqueue(message);
        are.Set();
      }
    }

    public void Run() {
      while(true) {
        string nextMessage=null;
        lock(sync) {
          if(items.Count>0) {
            nextMessage=(string)items.Dequeue();
          }
        }

        if(nextMessage==null) {
          var dt = DateTime.Now;
          //no pending messages, so write the time and date
          _exampleSensor.Home();
          _exampleSensor.Write(FillLine(dt.ToString("hh:mm:ss")), 0, 16);

          // write date
          _exampleSensor.Home();
          _exampleSensor.SetPosition(40);
          _exampleSensor.Write(FillLine(dt.ToString("MM/dd/yyyy")), 0, 16);

          //wait up to 1/2 second for a message
          are.WaitOne(500, false);
        } else {
          _exampleSensor.Init();
          _exampleSensor.Display(true, true, false, false);
          Thread.Sleep(10);
          _exampleSensor.Clear();
          Thread.Sleep(1);
          _exampleSensor.Home();
          Thread.Sleep(1);
          _exampleSensor.Write(FillLine(nextMessage), 0, 16);
          Thread.Sleep(1000);
          _exampleSensor.Init();
          _exampleSensor.Display(true, true, false, false);
          _exampleSensor.Clear();

          Thread.Sleep(2*1000); //messages are displayed for two seconds
        }
      }
    }
  }

The way you use it is like this:

display=new ControlTheDisplay(_exampleSensor);

... later, when you want to show a message, perhaps from a different thread or a callback:

display.Show(blah.RFID); //or whatever

I think code like this is a lot easier to analyze. For example, you can easily see that there are no deadlocks and no races that would simultaneously call the display library from two different threads. Also, when a new message arrives, if the time is being displayed, it will be immediately overwritten by the message, but if a previous message is being displayed, that previous message will stay up for the full two seconds.


#11475 Enable code when a class is loaded

Posted by Corey Kosak on 30 March 2011 - 01:09 PM

I think the "partial class" approach would work well here. Mario briefly mentioned "partial" but perhaps it helps to provide an example. I will modify the example you posted:

Ic74HC595.cs:
class Ic74HC595
{
	public enum Pins
	{
		GPO_PIN_D0 = 0,
		GPO_PIN_D1 = 1,
		GPO_PIN_D2 = 2,
		GPO_PIN_D3 = 3,
		GPO_PIN_D4 = 4,
		GPO_PIN_D5 = 5,
		GPO_PIN_D6 = 6,
		GPO_PIN_D7 = 7
	}
	
	// A bunch of code
}

partial class OutputPortShift
{
	// A bunch of code
	public OutputPortShift(Ic74HC595 IcOut, Ic74HC595.Pins Pin, bool InitialState)
	{
		// Constructor when creating an outputport behind the Ic74HC595 IC
	}
}


IcTLC5940.cs:
class IcTLC5940
{
	public enum Pins
	{
		LED_0 = 0,
		LED_1 = 1,
		LED_2 = 2,
		LED_3 = 3,
		LED_4 = 4,
		LED_5 = 5,
		LED_6 = 6,
		LED_7 = 7,
		LED_8 = 8,
		LED_9 = 9,
		LED_10 = 10,
		LED_11 = 11,
		LED_12 = 12,
		LED_13 = 13,
		LED_14 = 14,
		LED_15 = 15
	}
	
	// A bunch of code
}

partial class OutputPortShift
{
	public OutputPortShift(IcTLC5940 IcOut, IcTLC5940.Pins Pin, bool InitialState)
	{
		// Constructor when creating an outputport behind the IcTLC5940 IC
	}
}

OutputPortShift.cs:
partial class OutputPortShift
{
   //the remainder of the code for OutputPortShift
}

Do you see what's happening here? The existence of the file Ic74HC595.cs adds a new class called Ic74HC595 and also enhances the class OutputPortShift. A similar thing happens for IcTLC5940.cs.


#11153 Interrupts on TristatePort

Posted by Corey Kosak on 21 March 2011 - 01:30 AM

The obvious option is to abandon interrupts and just go with the TristatePort for everything but an interrupt seems tailor-made for this type of operation...


I was looking over the firmware source briefly and I didn't see any obvious reasons why it couldn't be modified to work the way you want. (famous last words.. to be clear I only looked at the firmware for about 30 seconds)

As for why the methods are there in the first place, it's a sad casualty of the inheritance hierarchy: TriStatePort inherits from OutputPort which in turn inherits from Port which in turn inherits from NativeEventDispatcher, which introduces the event.


#11150 Fluent Interop 1.6

Posted by Corey Kosak on 20 March 2011 - 11:09 PM

Do you know what it would take to gett a version of the firmware for the Netduino Mini?

It would probably be pretty easy. However, right now I'm working on the first steps of my SimpleNGEN project--if this is successful it will make the Fluent stuff obsolete. I'd rather stay focused on that rather than support Fluent right now, if that's all right.


#11096 Threading Problems After Deploy

Posted by Corey Kosak on 19 March 2011 - 02:17 AM

This code works fine as long as the debugger is attached but as soon as I deploy and reset the Netduino, the display ceases to function.

I believe the problem you are seeing is because the code doesn't actually do what you want it to do. You want an 80 ms delay between writes, however in this code the writes can all end up stacked on top of each other. If this doesn't make sense, consider this example:

public static void Main() {
  var bytes=new byte[XXX];
  //fill bytes with message 1
  driver.Write(bytes); //line A
  //fill bytes with message 2
  driver.Write(bytes); //line B
  //fill bytes with message 3
  driver.Write(bytes); //line C
}

This is one possible execution trace of this program:
  • Line A executes, Thread 1 is started
  • Line B executes, Thread 2 is started
  • Line C executes, Thread 3 is started
  • Thread 1 calls this.port.Write()
  • Thread 2 calls this.port.Write()
  • Thread 3 calls this.port.Write()
  • Thread 1 sleeps for 80 ms
  • Thread 2 sleeps for 80 ms
  • Thread 3 sleeps for 80 ms
This is not what you wanted, as all three calls to this.port.Write() could be happening at about the same time. Rather than a delay between messages, you're creating a bunch of threads who all eagerly try to write at the same time and then whose last act before dying is to sleep 80 ms.

Footnote: my example also has another problem, in that the contents of the byte array are getting overwritten before they are being sent. This may or may not be a problem in your code. It depends on the structure of your code.

I don't know why it's working in the IDE but it's possible that the timing characteristics are different enough that you're getting away with the race.


#11014 netduino/MF performance

Posted by Corey Kosak on 17 March 2011 - 01:07 AM

Can that be? Is this CLR overhead?


To the above responses I would add that the following. Many people on the board are interested in the same performance issues and there are a few ideas being bandied about. The central idea is to promote a hybrid programming style where much of the logic of your program is in C#, but a few timing-critical core routines are in native code. How this native code should be provided is the key question, and there are a few ideas being bandied about:
  • compile your own custom entry point into the firmware
  • use a dynamic code generation library like my own highly-experimental Fluent library
  • wait a little longer to find out what Chris Walker has cooked up
  • wait some unknown amount of time to see if my TinyNGEN project amounts to anything

I personally believe that things are in flux right now, and although it can be frustrating at times, I bet in a few months' time we are going to see some interesting advances in this area.

PS: can you say what FPGA prototyping board you are using? That sounds like an interesting project.


#10917 C# help

Posted by Corey Kosak on 15 March 2011 - 02:10 AM

:unsure: :unsure: :unsure: :unsure:
I can only look at it, and feel frustrated.

Let's see if I can demystify it. I'm probably being too verbose here, but probably better to provide too much rather than too little detail.

byte0 is some 5-bit sequence of bits, say, abcde
byte1 is also a 5-bit sequence of bits, say, vwxyz

Your goal is to display some 5-bit subsequence of abcde0vwxyz (notice the 0 in there because you want a 'space' between them)

As a first step, we can form abcde0vwxyz with this expression

var temp=(byte0<<6) | byte1;

Now, to extract the proper 5-bit subsequence we want to shift abcde0vwxyz in such a way that the 5 bits we want are in the five rightmost positions:
  • if shift is 0, we want to shift our pattern right 6 (forming abcde)
  • if shift is 1, we want to shift it right 5 (forming abcde0, whose 5 rightmost bits are bcde0)
  • if shift is 2, we want to shift it right 4 (forming abcde0v, whose 5 rightmost bits are cde0v)
  • etc...

Now, since 0 goes to 6, 1 goes to 5, etc, the formula for the amount to shift right is (6-shift)

Therefore we need to shift temp to the right by this amount:

var temp2=temp>>(6-shift);
...and finally we want to mask off the low 5 bits

var result=temp2&0x1f;

Putting it all together:
var temp=(byte0<<6) | byte1;
var temp2=temp>>(6-shift);
var result=temp2&0x1f;
Now we can try to 'optimize' this code by combining temp and temp2:

var temp2=(byte0<<6 | byte1) >> (6-shift)

expands to

var temp2=(byte0<<6) >> (6-shift)
          |
          byte1 >> (6-shift)
which expands to
var temp2=((byte0 << 6) >> 6) >> -shift
          |
          byte1 >> (6-shift)

which finally simplifies to (because the 6's cancel and because a negative shift reverses the direction of the >> operator)
var temp2=(byte0 << shift) | (byte1>>(6-shift))
which should look enough like my code that you can take it from there (don't forget to mask with 0x1f at the end).

Regarding var, most of the time you want to use 'byte' is to save space (e.g. because you have an array of 1000 bytes. It usually isn't very useful to use byte as a local variable type (unless you really care about the numerical behavior of 8-bit unsigned math operations) because ints are at least as fast, if not faster, and generally won't take up any more space when used as local variables. So in this case I use var so the language can default to whatever it wants to do and I do the casting to 'byte' at the end if my program needs it.


#10625 OutOfMemory not expected

Posted by Corey Kosak on 07 March 2011 - 01:55 AM

and it seems that each elements takes 24 bytes


I feel your pain. We discussed this issue in depth on the first page of this topic.

Regarding your failure to create a 10000 element array, I've also seen that happen. I don't have a solid explanation yet in that case but I've been meaning to figure it out one of these days.


#10622 C# help

Posted by Corey Kosak on 07 March 2011 - 12:21 AM

I can't see the Problem at all with this code

Your first problem is that you have an infinite recursion on this code, on both the getter and setter side:

        public int MaxNumber
        {
            get { return MaxNumber; }
            set
            {
                MaxNumber = value > 9 ? 9 : value;
            }
        }

One way to solve this is to back it with a field, like so:

  private int maxNumber;

  public int MaxNumber
  {
    get { return maxNumber; }
    set
    {
      maxNumber = value > 9 ? 9 : value;
    }
  }



#9336 Generics in NETMF

Posted by Corey Kosak on 12 February 2011 - 05:40 AM

Having become used to the power and simplicity that generics offer in the regular .NET Framework, is there any chance they will come to NETMF?

I don't have any special insight into what Microsoft is doing or what Bill Gates wants to spend his money on, but I think it's extremely unlikely.

The reason is that (unlike templates in C++, where all the work is done by the compiler at compile time), generics in C# require a lot of support from the runtime. This means that the Micro Framework would have to be completely rewritten (and it would also need more RAM). I'm not saying it's impossible, but it would be a huge undertaking.

To give you an idea of just one of the many issues involved, below is a simple program which uses a type that the compiler has no chance of knowing, differs for every run of the program, and is not even determinable until runtime:

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace ConsoleApplication9 {
  public class Program {
    public static void Main() {
      Debug.Print(Func1<double>(new Random().Next(500)));
    }

    private static string Func1<T>(int depth) where T : new() {
      if(depth==0) {
        return Func2(new T());
      } else {
        return Func1<List<T>>(depth-1);
      }
    }

    private static string Func2<T>(T arg) {
      return arg.GetType().FullName;
    }
  }
}

Note: this is full-framework code, not Micro Framework code. And, by the way, the above program is literally impossible to write in C++ :)


#8219 Quick and dirty GPIO speed test - findings

Posted by Corey Kosak on 23 January 2011 - 11:34 PM

...but we should be able to get into the MHz range...

This is the Best. Thing. Ever.

I've taken Chris' approach directly and put it into "version 1.5" of my framework. Here is the output of my revised timing program:

*** RUNNING TEST C# v1 (1 on/offs per loop iteration)
Total time for 65536 on/offs is 00:00:13.9269120. usec per on/off=212.50781250000000000, which is 4705.70934892099558056 Hertz
*** RUNNING TEST C# v2 (10 on/offs per loop iteration)
Total time for 81920 on/offs is 00:00:14.1916373. usec per on/off=173.23776000976562272, which is 5772.41358895213579672 Hertz
*** RUNNING TEST Fluent with firmware call (1 on/offs per loop iteration)
Total time for 1048576 on/offs is 00:00:16.2333440. usec per on/off=15.48132324218749824, which is 64593.96166310527769376 Hertz
*** RUNNING TEST Fluent with Chris Walker-style direct access (1 on/offs per loop iteration)
Total time for 67108864 on/offs is 00:00:15.3813546. usec per on/off=2.29200044274330140e-1, which is 4363000.90240426547825344 Hertz
*** RUNNING TEST Chris Walker v2 (10 on/offs per loop iteration)
Total time for 83886080 on/offs is 00:00:11.3617280. usec per on/off=1.35442352294921876e-1, which is 7383214.94758543744683264 Hertz

And here is the code
#define HAVE_FLUENT

using System;
#if HAVE_FLUENT
using FluentInterop.CodeGeneration;
#endif
using FluentInterop.Fluent;
using Kosak.SimpleInterop;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;

namespace BitBangingTimings {
  public class Program {

    public static void Main() {
      var tests=new Test[] {
        new Test1(),
        new Test2(),
#if HAVE_FLUENT
        new Test3(),
        new Test4(),
        new Test5(),
#endif
      };

      foreach(var test in tests) {
        test.Init();
      }

      foreach(var test in tests) {
        test.Run();
      }

    }

    private abstract class Test {
      private const int maxSeconds=10;
      private readonly string name;
      private readonly int onOffsPerLoopIteration;

      protected Test(string name, int onOffsPerLoopIteration) {
        this.name=name;
        this.onOffsPerLoopIteration=onOffsPerLoopIteration;
        Debug.Print("*** I will do repeatedly larger runs until a given test takes "+maxSeconds+" seconds or more");
      }

      public virtual void Init() {}

      public void Run() {
        Debug.Print("*** RUNNING TEST "+name+" ("+onOffsPerLoopIteration+" on/offs per loop iteration)");
        var numIterations=1024;
        while(true) {
          using(var port=new OutputPort(Pins.ONBOARD_LED, false)) {
            var startTime=Utility.GetMachineTime();
            DoTheTest(port, numIterations);
            var endTime=Utility.GetMachineTime();

            var elapsed=endTime-startTime;
            var totalSeconds=(double)elapsed.Ticks/TimeSpan.TicksPerSecond;
            var totalOnOffs=numIterations*onOffsPerLoopIteration;
            var secondsPerOnOff=totalSeconds/totalOnOffs;
            var microsecondsPerOnOff=secondsPerOnOff*1000000;
            var hertz=1/secondsPerOnOff;
            if(totalSeconds>=maxSeconds) {
              Debug.Print("Total time for "+totalOnOffs+" on/offs is "+elapsed+". usec per on/off="+microsecondsPerOnOff+
              ", which is "+hertz+" Hertz");
              break;
            }
            numIterations*=2;
          }
        }
      }

      protected abstract void DoTheTest(OutputPort port, int numIterations);
    }

    private class Test1 : Test {
      public Test1() : base("C# v1", 1) { }

      protected override void DoTheTest(OutputPort port, int numIterations) {
        for(var i=0; i<numIterations; ++i) {
          port.Write(true);
          port.Write(false);
        }
      }
    }

    private class Test2 : Test {
      public Test2() : base("C# v2", 10) {}

      protected override void DoTheTest(OutputPort port, int numIterations) {
        for(var i=0; i<numIterations; ++i) {
          port.Write(true);
          port.Write(false);
          port.Write(true);
          port.Write(false);
          port.Write(true);
          port.Write(false);
          port.Write(true);
          port.Write(false);
          port.Write(true);
          port.Write(false);
          port.Write(true);
          port.Write(false);
          port.Write(true);
          port.Write(false);
          port.Write(true);
          port.Write(false);
          port.Write(true);
          port.Write(false);
          port.Write(true);
          port.Write(false);
        }
      }
    }
#if HAVE_FLUENT
    private class Test3 : Test {
      private CompiledCode code;

      public Test3() : base("Fluent with firmware call", 1) {}

      public override void Init() {
        code=CodeGenerator.Compile((g, pin, count, ង, ច, ឆ, ជ, ឋ, ឌ, ព, ផ, ត, ថ, ទ, ធ, ម, វ, firmware) => {
          g.While(count>0)
            .Do(() => {
              firmware.SetPinState(pin, 1);
              firmware.SetPinState(pin, 0);
              count.Value=count-1;
            });
        });
      }

      protected override void DoTheTest(OutputPort port, int numIterations) {
        code.Invoke((int)port.Id, numIterations);
      }
    }

    private class Test4 : Test {
      private CompiledCode code;

      public Test4() : base("Fluent with Chris Walker-style direct access", 1) {}

      public override void Init() {
        code=CodeGenerator.Compile((g, pin, count, ង, ច, ឆ, ជ, ឋ, ឌ, ព, ផ, ត, ថ, ទ, ធ, ម, វ, firmware) => {
          var pio=g.Declare.PIOReference("pio");
          var bitmask=g.Declare.Int("bitmask");
          firmware.GetPIOAndBitmask(pin, ref pio, ref bitmask);

          pio.PER=bitmask;  //enable PIO function
          pio.SODR=bitmask; //output should start high
          pio.OER=bitmask;  //enable output

          g.While(count>0)
            .Do(() => {
              pio.SODR=bitmask;
              pio.CODR=bitmask;
              count.Value=count-1;
            });
        });
      }

      protected override void DoTheTest(OutputPort port, int numIterations) {
        code.Invoke((int)port.Id, numIterations);
      }
    }

    private class Test5 : Test {
      private CompiledCode code;

      public Test5() : base("Chris Walker v2", 10) { }

      public override void Init() {
        code=CodeGenerator.Compile((g, pin, count, ង, ច, ឆ, ជ, ឋ, ឌ, ព, ផ, ត, ថ, ទ, ធ, ម, វ, firmware) => {
          var pio=g.Declare.PIOReference("pio");
          var bitmask=g.Declare.Int("bitmask");
          firmware.GetPIOAndBitmask(pin, ref pio, ref bitmask);

          pio.PER=bitmask; //enable PIO function
          pio.SODR=bitmask; //output should start high
          pio.OER=bitmask; //enable output

          g.While(count>0)
            .Do(() => {
              pio.SODR=bitmask;
              pio.CODR=bitmask;
              pio.SODR=bitmask;
              pio.CODR=bitmask;
              pio.SODR=bitmask;
              pio.CODR=bitmask;
              pio.SODR=bitmask;
              pio.CODR=bitmask;
              pio.SODR=bitmask;
              pio.CODR=bitmask;
              pio.SODR=bitmask;
              pio.CODR=bitmask;
              pio.SODR=bitmask;
              pio.CODR=bitmask;
              pio.SODR=bitmask;
              pio.CODR=bitmask;
              pio.SODR=bitmask;
              pio.CODR=bitmask;
              pio.SODR=bitmask;
              pio.CODR=bitmask;
              count.Value=count-1;
            });
        });
      }

      protected override void DoTheTest(OutputPort port, int numIterations) {
        code.Invoke((int)port.Id, numIterations);
      }
    }
#endif
  }
}





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.