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

Problem With Definition Of Two Different Event Handlers


  • Please log in to reply
7 replies to this topic

#1 Paul

Paul

    Member

  • Members
  • PipPip
  • 14 posts

Posted 25 October 2010 - 05:36 PM

I'm currently working on my first "from the ground up" robotics project. To start I have interfaced my Netduino to a Pololu Qik2s9v1 dual serial motor controller. The controller supports sending separate commands to two different brush type DC motors. I've written a C# class which provides functions to drive both motors forward, reverse, turn and stop; turning is accomplished by having one motor move forward and one motor move backwards. All of functions which drive the motors, with the exception of the stop command which is a special case, take two input paramters:
- An integer representing the speed to drive the motors at
- An integer representing the time in seconds that the motors should run for the command.

In my first iteration of the project I "hard coded" a series of commands for the robot, i.e. fixed the motor speed and time values in a series of function calls, just to get an idea of the dynamics of the motor driver and motors. Once deployed to the Netduino I "started up" the robot by pressing the on board button; the Netduino is being powered by a 9v battery for testing the robot. The only shortcoming of this approach is that every time I wished to change the motor speed and / or time values for the function calls I needed to modify the code and redeploy it. I thought that since I had a 9 volt battery attached to the Netduino I could use the USB to serial port communication approach presented in the Forums by Hari to type in the paramters I wanted and have them stored in an array and then run a test to see how the robot performed. The idea was that after I input my parameters on the terminal program on my PC I could detach the USB/serial cable, put the robot on the floor, press the on board button to start things up and see how the robot performed. Well, it almost works.

Entering the desired parameters via the console program on my PC does work. They are stored in an array in memory on the Netduino and since I have the Netduino powered by a 9v battery, the array contents are preserved when I detach the USB/serial connection. The problem seems to come about when I attempt to start the Netduino using the on board button: nothing happens.

Running in debug mode I quickly discovered the following:
- At start up the event handler for the on board button is made and pressing the on board button produces the expected results.
- After I type in paramter values using the console program on my PC, the event handler for the on board button seems to become "unassigned."

The problem seems to be related to the fact that after I make the assignement for the on board button interrupt handler and then type in some data, the serial input class for reading the values I typed and echoing them back to the console program on the PC assigns an interrupt handler to read and write the serial data from the PC which results in my losing the event handler for the on board button.

I'm not sure what is causing this - my own lack of understanding of assigning interrupt handlers or some limitation of the Netduino, my feeling is that it is my loack of understanding. In any case the code for the program that defines the on board interrupt handler and the code for the USB/serial communication appears below.

Main Line Code:

using System;
using System.IO.Ports;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using SecretLabs.NETMF.Hardware;
using SecretLabs.NETMF.Hardware.Netduino;

namespace PoluluRobot_010
{

   
    public class Program
    {
        static SerialIinput serialInput = new SerialIinput();

        static DualSerialMotorController motorController = new DualSerialMotorController();

        static char[] operators = { ',', ';' };

        static string[] operands;

        public static void Main()
        {
            SetupButton();
            while (true)
            {
                string line = serialInput.ReadLine();
                if (line.Length > 0)
                {
                    Thread.Sleep(250);

                    operands = line.Split(operators);

                    serialInput.ClearBuffer();
                }
            }
        }

        private static void SetupButton()
        {
            InterruptPort interruptPort = new InterruptPort(Pins.ONBOARD_SW1, false, Port.ResistorMode.PullUp, Port.InterruptMode.InterruptEdgeLow);
            interruptPort.OnInterrupt += new  NativeEventHandler(interruptPort_OnInterrupt);
        }

        static void interruptPort_OnInterrupt(uint data1, uint data2, DateTime time)
        {
            motorController.Forward(Int32.Parse(operands[0].ToString()), Int32.Parse(operands[1].ToString()));
            motorController.TurnRight(Int32.Parse(operands[0].ToString()));
            motorController.Forward(Int32.Parse(operands[0].ToString()), Int32.Parse(operands[1].ToString()));
            motorController.Stop();
        }
        
    }
}

The code for the USB/Serial I/O is below. It is pretty much a copy of Hari's post and it works very nicely.

using System;
using System.Threading;
using System.IO.Ports;
using System.Text;
using Microsoft.SPOT;
using SecretLabs.NETMF.Hardware.Netduino;

namespace PoluluRobot_010
{
    class SerialIinput
    {
        static SerialPort serialPort;

        const int bufferMax = 1024;
        static byte[] buffer = new Byte[bufferMax];
        static int bufferLength = 0;

        public SerialIinput(string portName = SerialPorts.COM2, int baudRate = 9600, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One)
        {
            serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits);
            serialPort.ReadTimeout = 50; // Set to 10ms. Default is -1?!
            serialPort.DataReceived += new SerialDataReceivedEventHandler(serialPort_DataReceived);
            serialPort.Open();
        }

        private void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            {
                int bytesReceived = serialPort.Read(buffer, bufferLength, bufferMax - bufferLength);
                if (bytesReceived > 0)
                {
                    string line;
                    line = "" + new string(Encoding.UTF8.GetChars(buffer));
                    Print(line.Substring(bufferLength));
                    bufferLength += bytesReceived;
                    if (bufferLength >= bufferMax)
                        throw new ApplicationException("Buffer Overflow.  Send shorter lines, or increase lineBufferMax.");
                }
            }
        }

        //TODO: Need to prevent serialPort_DataReceived from clobberring buffer and bufferSize while we are processing buffer
        public string ReadLine()
        {
            string line = "";

            {
                //-- Look for Return char in buffer --
                for (int i = 0; i < bufferLength; i++)
                {
                    //-- Consider EITHER CR or LF as end of line, so if both were received it would register as an extra blank line. --
                    if (buffer[i] == '\r' || buffer[i] == '\n')
                    {
                        buffer[i] = 0; // Turn NewLine into string terminator
                        line = "" + new string(Encoding.UTF8.GetChars(buffer)); // The "" ensures that if we end up copying zero characters, we'd end up with blank string instead of null string.
                        //Debug.Print("LINE: <" + line + ">");
                        bufferLength = bufferLength - i - 1;
                        Array.Copy(buffer, i + 1, buffer, 0, bufferLength); // Shift everything past NewLine to beginning of buffer
                        break;
                    }
                }
                return line;
            }
        }

        public void Print(string line)
        {
            System.Text.UTF8Encoding encoder = new System.Text.UTF8Encoding();
            byte[] bytesToSend = encoder.GetBytes(line);
            serialPort.Write(bytesToSend, 0, bytesToSend.Length);
        }

        public void PrintLine(string line)
        {
            Print(line + "\r");
        }

        public void ClearBuffer()
        {
            for (int i = 0; i < bufferMax; i++)
            {
                buffer[i] = 0;
            }
            bufferLength = 0;
        }
    }
}


#2 hari

hari

    Advanced Member

  • Members
  • PipPipPip
  • 131 posts

Posted 25 October 2010 - 05:50 PM

Hi Paul, Sorry, that's my bad. :( interruptPort object is only declared in SetupButton, so it becomes out of scope and NETMF Garbage Collector (GC) probably disposes it. Try declaring interruptPort as global static as you did with motorController. I'll update a new version of SerialPortLearn tonight.

#3 Chris Walker

Chris Walker

    Secret Labs Staff

  • Moderators
  • 7767 posts
  • LocationNew York, NY

Posted 25 October 2010 - 06:00 PM

Paul, Also, just a few best practices and/or optimizations to think about: 1. We normally create a mutex ("lock") around sections of code which deal with buffers and buffer pointers. That way, you don't need to worry about two sections of code manipulating the data at the same time and corrupting it. I assume that's what the "TODO" in the code is alluding to. 2. The ClearBuffer command doesn't need to clear the bytes in the buffer, since any bytes beyond the "bufferLength" index are just dummy data locations. Chris

#4 Paul

Paul

    Member

  • Members
  • PipPip
  • 14 posts

Posted 25 October 2010 - 07:07 PM

Hari, Thanks for the feedback. Your SerialPortLearn post has taught me a lot and has proven very useful. In fact, the first project I did with it was to construct a calcualtor which used the computer keyboard as input and displayed the calculation being input as well as the results on an LCD display. I look forward to implementing you recommendation tonight. Paul

#5 Corey Kosak

Corey Kosak

    Advanced Member

  • Members
  • PipPipPip
  • 276 posts
  • LocationHoboken, NJ

Posted 26 October 2010 - 03:48 AM

Also, just a few best practices and/or optimizations to think about:


If it's appropriate in these discussions to talk about programming style, I think the SerialInput class could be improved a lot.

I want to say that I don't mean the points below as a criticism. I'd just like to point out some techniques you may not have thought of.

Some things I noticed in looking over the code:
  • Allocating a static buffer causes 1024 bytes to be permanently reserved, which is too wasteful of our precious memory.
    Instead we should use our beautiful C# language to dynamically allocate memory as needed.
    This will also allow us to remove the upper limit on the length of the line (no more "buffer overflow").
  • When there's no data, the original program is looping constantly. This prevents our processor from going into low power mode
    and thus wastes our precious battery.

A couple of things we might like:
  • to get rid of the race condition
  • to buffer up multiple lines, so that the program won't lose any data if it is busy doing something else for a while

Below is the program that I wrote to try to address these issues. I don't have any USB2TTL hardware at the moment and so I've only tested it
in the emulator. It seems to me that the emulator has some behavioral differences from the real hardware, so I put some additional
code in there to make it work, controlled by a #define. I'd find it interesting if someone with hardware was willing to try it
(with the #define commented out). I'll try it myself when my USB2TTL hardware arrives :). It seems the differences are:

  • I don't think the emulator sends a DataReceived event
  • You can't type CR/LF in the emulator, so instead I've made the code interpret $ as end-of-line

Here is the code. I hope you find it interesting:
#define RUNNING_IN_EMULATOR

using System;
using System.Collections;
using System.Threading;
using System.IO.Ports;
using System.Text;
using Microsoft.SPOT;
using SecretLabs.NETMF.Hardware.Netduino;
using Math = System.Math;

namespace PoluluRobot_010 {
  public class Test {
    public static void Main() {
      using(var s=new SerialInput()) {
        while(true) {
          var result=s.ReadLine();
          Debug.Print(result);
          if(result=="quit") {
            break;
          }
        }
      }
    }
  }

  public class SerialInput : IDisposable {
    private readonly SerialPort serialPort;
    private MyStringBuilder myStringBuilder=new MyStringBuilder();
    private readonly object sync=new object();
    private readonly Queue lines=new Queue();
    private readonly AutoResetEvent autoResetEvent=new AutoResetEvent(false);
#if RUNNING_IN_EMULATOR
    private readonly Timer timer;
#endif

    public SerialInput(string portName = SerialPorts.COM2, int baudRate = 9600, Parity parity = Parity.None, int dataBits = 8, StopBits stopBits = StopBits.One) {
      this.serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits);

#if RUNNING_IN_EMULATOR
      this.timer=new Timer(o => ProcessData(), null, 100, 100);
#else
      serialPort.DataReceived+=(o, e) => ProcessData();
#endif
      serialPort.Open();
    }

    public void Dispose() {
      serialPort.Dispose();
#if RUNNING_IN_EMULATOR
      timer.Dispose();
#endif
    }

    public string ReadLine() {
      while(true) {
        lock(sync) {
          if(lines.Count>0) {
            return (string)lines.Dequeue();
          }
        }
        autoResetEvent.WaitOne();
      }
    }

    private void ProcessData() {
      var buffer=new byte[32];

      var bytesToRead=serialPort.BytesToRead;
      while(bytesToRead>0) {
        var bytesReceived=serialPort.Read(buffer, 0, Math.Min(bytesToRead, buffer.Length));
        Process(buffer, bytesReceived);
        bytesToRead-=bytesReceived;
      }
    }

    private void Process(byte[] buffer, int count) {
      foreach(var ch in Encoding.UTF8.GetChars(TrimToSize(buffer, count))) {
#if RUNNING_IN_EMULATOR
        var atNewLine=ch=='$';
#else
        var atNewLine=ch=='\r' || ch=='\n';
#endif
        if(atNewLine) {
          Flush();
        } else {
          myStringBuilder.Append(ch);
        }
      }
    }

    private static byte[] TrimToSize(byte[] buffer, int count) {
      if(buffer.Length==count) {
        return buffer;
      }
      var newBuffer=new byte[count];
      Array.Copy(buffer, newBuffer, count);
      return newBuffer;
    }

    private void Flush() {
      var nextLine=myStringBuilder.ToString();
      myStringBuilder=new MyStringBuilder();
      lock(sync) {
        lines.Enqueue(nextLine);
      }
      autoResetEvent.Set();
    }
  }

  public class MyStringBuilder {
    private const int initialCapacity=4;

    private char[] buffer=new char[initialCapacity];
    private int currentLength=0;

    public void Append(char ch) {
      if(currentLength==buffer.Length) { //at capacity?
        var newBuffer=new char[currentLength*2];
        Array.Copy(buffer, newBuffer, currentLength);
        buffer=newBuffer;
      }
      buffer[currentLength++]=ch;
    }

    public override string ToString() {
      return new string(buffer, 0, currentLength);
    }
  }
}


#6 hari

hari

    Advanced Member

  • Members
  • PipPipPip
  • 131 posts

Posted 26 October 2010 - 04:58 AM

Paul, Sorry I did not get this earlier to you. Attached is the updated version of my SerialPortLearn. All I changed was moving the InterruptPort to static and added the locks(). You may want to try Corey's more efficient version instead. I currently do not have access to my Netduino, so I cannot try it. :-(

Attached Files



#7 Paul

Paul

    Member

  • Members
  • PipPip
  • 14 posts

Posted 26 October 2010 - 11:49 AM

Hari - I tried your recommendation last night and everything worked fine. Again, thanks for your initial post of the SerialInputClass and follow up. Corey - Your evaluation and recommendations regarding the SerialInputClass are most welcome. The class was originally created by another forum member and provided a great starting point for me to work with serial I/O on the Netduino. While I've been in IT more years than I'm willing to admit, let's just say the state of the art PC when I started in the business was based on the Intel 8008, it has been a very long time since programming was my primary job responsibility. As such my C# is not the best and one can learn only so much by reading books and Googling for ideas. Trying stuff out and getting feedback such as yours is what I like most about this forum and I will try out your suggestions this evening. Again, thanks for the feedback and I look forward to more of your posts. Paul

#8 Jack Chidley

Jack Chidley

    Advanced Member

  • Members
  • PipPipPip
  • 99 posts

Posted 29 September 2012 - 04:39 PM

Like Paul, I too have leant a lot through trying things out prompted by this thread. Not least how much I have to learn. 1. "serialPort.DataReceived += (o, e) => ProcessData();" seems to be a lambda function and that the (o, e) are somehow discarded so that they aren't needed for ProcessData(). 2. "using(var s=new SerialInput()) {" probably sets up this object for use in the next block. But if I want to use s outside, I found I had to declare it as a static function at the top of test. 3. debugging. I discovered that the test software I was using on the other end didn't send CR or LF (\n or \r) and thus why nothing was printed on the debug console. 4. autoResetEvent. That looks really handy for threading. 5. How much easier to program and debug .net micro framework devices are than Arduinos. Corey, your software does work but I guess you know that by now. I have been using it to test a pair of easyradio transceivers. They work brilliantly. Harri, your original post lead me here. I certainly learnt a lot from you too! Jack




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.