none
Combining CCR and Multimedia timer

    General discussion

  • I have adjusted the .Net multimedia timer class of Leslie Sanford so that it now post to ports in stead of calling event handlers. This means that the class can now be used to post to a port when the multimedia timer generates a tick. On most modern computers the multimedia timer has a resolution of 1 msec. According to http://social.msdn.microsoft.com/Forums/en-US/roboticsccr/thread/6757ffd6-f59f-4daf-ab55-b20e42f378f8/ the multimedia timer is deprecated, but it still works fine on my Windows XP computer.

    CCRMMTime class (CCRMMTimer.cs)

     
    using System;  
    using System.ComponentModel;  
    using System.Diagnostics;  
    using System.Runtime.InteropServices;  
    using Microsoft.Ccr.Core;  
     
    namespace CCRMultimedia  
    {  
        /// <summary> 
        /// Defines constants for the multimedia Timer's event types.  
        /// </summary> 
        public enum TimerMode  
        {  
            /// <summary> 
            /// Timer event occurs once.  
            /// </summary> 
            OneShot,  
     
            /// <summary> 
            /// Timer event occurs periodically.  
            /// </summary> 
            Periodic  
        };  
     
        /// <summary> 
        /// Represents information about the multimedia Timer's capabilities.  
        /// </summary> 
        [StructLayout(LayoutKind.Sequential)]  
        public struct TimerCaps  
        {  
            /// <summary> 
            /// Minimum supported period in milliseconds.  
            /// </summary> 
            public int periodMin;  
     
            /// <summary> 
            /// Maximum supported period in milliseconds.  
            /// </summary> 
            public int periodMax;  
        }  
     
        /// <summary> 
        /// Represents the Windows multimedia timer.  
        /// </summary> 
        public sealed class CCRMMTimer : IComponent  
        {  
            #region Timer Members  
     
            #region Delegates  
     
            // Represents the method that is called by Windows when a timer event occurs.  
            private delegate void TimeProc(int id, int msg, int user, int param1, int param2);  
     
            #endregion  
     
            #region Win32 Multimedia Timer Functions  
     
            // Gets timer capabilities.  
            [DllImport("winmm.dll")]  
            private static extern int timeGetDevCaps(ref TimerCaps caps,  
                int sizeOfTimerCaps);  
     
            // Creates and starts the timer.  
            [DllImport("winmm.dll")]  
            private static extern int timeSetEvent(int delay, int resolution,  
                TimeProc proc, int user, int mode);  
     
            // Stops and destroys the timer.  
            [DllImport("winmm.dll")]  
            private static extern int timeKillEvent(int id);  
     
            // Indicates that the operation was successful.  
            private const int TIMERR_NOERROR = 0;  
     
            #endregion  
     
            #region Fields  
     
            // Timer identifier.  
            private int timerID;  
     
            // Timer mode.  
            private volatile TimerMode mode;  
     
            // Period between timer events in milliseconds.  
            private volatile int period;  
     
            // Timer resolution in milliseconds.  
            private volatile int resolution;          
     
            // Called by Windows when a timer periodic event occurs.  
            private TimeProc timeProcPeriodic;  
     
            // Called by Windows when a timer one shot event occurs.  
            private TimeProc timeProcOneShot;  
     
            // Indicates whether or not the timer is running.  
            private bool running = false;  
     
            // Indicates whether or not the timer has been disposed.  
            private volatile bool disposed = false;  
     
            // For implementing IComponent.  
            private ISite site = null;  
     
            // Multimedia timer capabilities.  
            private static TimerCaps caps;  
     
            #endregion  
     
            #region Ports  
     
            /// <summary> 
            /// EmptyValue is placed in port when started event occures  
            /// </summary> 
            Port<EmptyValue> startedport;  
     
            /// <summary> 
            /// EmptyValue is placed in port when stopped event occures  
            /// </summary> 
            Port<EmptyValue> stoppedport;  
     
            /// <summary> 
            /// EmptyValue is placed in port when tick occures  
            /// </summary> 
            Port<EmptyValue> tickport;  
     
            #endregion  
     
            #region Construction  
     
            /// <summary> 
            /// Initialize class.  
            /// </summary> 
            static CCRMMTimer()  
            {  
                // Get multimedia timer capabilities.  
                timeGetDevCaps(ref caps, Marshal.SizeOf(caps));  
            }  
     
            /// <summary> 
            /// Initializes a new instance of the Timer class with the specified IContainer.  
            /// </summary> 
            /// <param name="container">  
            /// The IContainer to which the Timer will add itself.  
            /// </param> 
            public CCRMMTimer(IContainer container)  
            {  
                ///  
                /// Required for Windows.Forms Class Composition Designer support  
                ///  
                container.Add(this);  
     
                Initialize();  
            }  
     
            /// <summary> 
            /// Initializes a new instance of the Timer class.  
            /// </summary> 
            public CCRMMTimer()  
            {  
                Initialize();  
            }  
     
            ~CCRMMTimer()  
            {  
                if(IsRunning)  
                {  
                    // Stop and destroy timer.  
                    timeKillEvent(timerID);  
                }  
            }  
     
            // Initialize timer with default values.  
            private void Initialize()  
            {  
                this.mode = TimerMode.Periodic;  
                this.period = Capabilities.periodMin;  
                this.resolution = 1;  
     
                running = false;  
     
                timeProcPeriodic = new TimeProc(TimerPeriodicEventCallback);  
                timeProcOneShot = new TimeProc(TimerOneShotEventCallback);  
            }  
     
            #endregion  
     
            #region Methods  
     
            /// <summary> 
            /// Starts the timer.  
            /// </summary> 
            /// <exception cref="ObjectDisposedException">  
            /// The timer has already been disposed.  
            /// </exception> 
            /// <exception cref="TimerStartException">  
            /// The timer failed to start.  
            /// </exception> 
            public void Start()  
            {  
                #region Require  
     
                if(disposed)  
                {  
                    throw new ObjectDisposedException("Timer");  
                }  
     
                #endregion  
     
                #region Guard  
     
                if(IsRunning)  
                {  
                    return;  
                }  
     
                #endregion  
     
                // If the periodic event callback should be used.  
                if(Mode == TimerMode.Periodic)  
                {  
                    // Create and start timer.  
                    timerID = timeSetEvent(Period, Resolution, timeProcPeriodic, 0, (int)Mode);  
                }  
                // Else the one shot event callback should be used.  
                else  
                {  
                    // Create and start timer.  
                    timerID = timeSetEvent(Period, Resolution, timeProcOneShot, 0, (int)Mode);  
                }  
     
                // If the timer was created successfully.  
                if(timerID != 0)  
                {  
                    running = true;  
     
                    if (startedport != null)  
                        startedport.Post(EmptyValue.SharedInstance);  
                }  
                else  
                {  
                    throw new TimerStartException("Unable to start multimedia Timer.");  
                }  
            }  
     
            /// <summary> 
            /// Stops timer.  
            /// </summary> 
            /// <exception cref="ObjectDisposedException">  
            /// If the timer has already been disposed.  
            /// </exception> 
            public void Stop()  
            {  
                #region Require  
     
                if(disposed)  
                {  
                    throw new ObjectDisposedException("Timer");  
                }  
     
                #endregion  
     
                #region Guard  
     
                if(!running)  
                {  
                    return;  
                }  
     
                #endregion  
     
                // Stop and destroy timer.  
                int result = timeKillEvent(timerID);  
     
                Debug.Assert(result == TIMERR_NOERROR);  
     
                running = false;  
     
                if (stoppedport != null)  
                    stoppedport.Post(EmptyValue.SharedInstance);  
            }          
     
            #region Callbacks  
     
            // Callback method called by the Win32 multimedia timer when a timer  
            // periodic event occurs.  
            private void TimerPeriodicEventCallback(int id, int msg, int user, int param1, int param2)  
            {  
                if (tickport != null)  
                    tickport.Post(EmptyValue.SharedInstance);  
            }  
     
            // Callback method called by the Win32 multimedia timer when a timer  
            // one shot event occurs.  
            private void TimerOneShotEventCallback(int id, int msg, int user, int param1, int param2)  
            {  
                if (tickport != null)  
                    tickport.Post(EmptyValue.SharedInstance);  
     
                Stop();  
            }  
     
            #endregion  
     
            #region Event Raiser Methods  
     
            // Raises the Disposed event.  
            private void OnDisposed(EventArgs e)  
            {  
                EventHandler handler = Disposed;  
     
                if(handler != null)  
                {  
                    handler(this, e);  
                }  
            }  
     
            #endregion          
     
            #endregion  
     
            #region Properties  
     
            /// <summary> 
            /// Gets or sets the time between Tick events.  
            /// </summary> 
            /// <exception cref="ObjectDisposedException">  
            /// If the timer has already been disposed.  
            /// </exception>     
            public int Period  
            {  
                get  
                {  
                    #region Require  
     
                    if(disposed)  
                    {  
                        throw new ObjectDisposedException("Timer");  
                    }  
     
                    #endregion  
     
                    return period;  
                }  
                set  
                {  
                    #region Require  
     
                    if(disposed)  
                    {  
                        throw new ObjectDisposedException("Timer");  
                    }  
                    else if(value < Capabilities.periodMin || value > Capabilities.periodMax)  
                    {  
                        throw new ArgumentOutOfRangeException("Period", value,  
                            "Multimedia Timer period out of range.");  
                    }  
     
                    #endregion  
     
                    period = value;  
     
                    if(IsRunning)  
                    {  
                        Stop();  
                        Start();  
                    }  
                }  
            }  
     
            /// <summary> 
            /// Gets or sets the timer resolution.  
            /// </summary> 
            /// <exception cref="ObjectDisposedException">  
            /// If the timer has already been disposed.  
            /// </exception>          
            /// <remarks> 
            /// The resolution is in milliseconds. The resolution increases   
            /// with smaller values; a resolution of 0 indicates periodic events   
            /// should occur with the greatest possible accuracy. To reduce system   
            /// overhead, however, you should use the maximum value appropriate   
            /// for your application.  
            /// </remarks> 
            public int Resolution  
            {  
                get  
                {  
                    #region Require  
     
                    if(disposed)  
                    {  
                        throw new ObjectDisposedException("Timer");  
                    }  
     
                    #endregion  
     
                    return resolution;  
                }  
                set  
                {  
                    #region Require  
     
                    if(disposed)  
                    {  
                        throw new ObjectDisposedException("Timer");  
                    }  
                    else if(value < 0)  
                    {  
                        throw new ArgumentOutOfRangeException("Resolution", value,  
                            "Multimedia timer resolution out of range.");  
                    }  
     
                    #endregion  
     
                    resolution = value;  
     
                    if(IsRunning)  
                    {  
                        Stop();  
                        Start();  
                    }  
                }  
            }  
     
            /// <summary> 
            /// Gets the timer mode.  
            /// </summary> 
            /// <exception cref="ObjectDisposedException">  
            /// If the timer has already been disposed.  
            /// </exception> 
            public TimerMode Mode  
            {  
                get  
                {  
                    #region Require  
     
                    if(disposed)  
                    {  
                        throw new ObjectDisposedException("Timer");  
                    }  
     
                    #endregion  
     
                    return mode;  
                }  
                set  
                {  
                    #region Require  
     
                    if(disposed)  
                    {  
                        throw new ObjectDisposedException("Timer");  
                    }  
     
                    #endregion  
                      
                    mode = value;  
     
                    if(IsRunning)  
                    {  
                        Stop();  
                        Start();  
                    }  
                }  
            }  
     
            /// <summary> 
            /// Gets a value indicating whether the Timer is running.  
            /// </summary> 
            public bool IsRunning  
            {  
                get  
                {  
                    return running;  
                }  
            }  
     
            /// <summary> 
            /// Gets the timer capabilities.  
            /// </summary> 
            public static TimerCaps Capabilities  
            {  
                get  
                {  
                    return caps;  
                }  
            }  
     
            public Port<EmptyValue> StartedPort  
            {  
                get  
                {  
                    #region Require  
     
                    if (disposed)  
                    {  
                        throw new ObjectDisposedException("StartedPort");  
                    }  
     
                    #endregion  
     
                    return startedport;  
                }  
                set  
                {  
                    #region Guard  
     
                    if (running)  
                    {  
                        throw new MemberAccessException("Can't change StartedPort when running");  
                    }  
     
                    #endregion  
     
                    startedport = value;  
                }  
            }  
     
            public Port<EmptyValue> StoppedPort  
            {  
                get  
                {  
                    #region Require  
     
                    if (disposed)  
                    {  
                        throw new ObjectDisposedException("Stopped port");  
                    }  
     
                    #endregion  
     
                    return stoppedport;  
                }  
                set  
                {  
                    #region Guard  
     
                    if (running)  
                    {  
                        throw new MemberAccessException("Can't change StoppedPort when running");  
                    }  
     
                    #endregion  
     
                    stoppedport = value;  
                }  
            }  
     
            public Port<EmptyValue> TickPort  
            {  
                get  
                {  
                    #region Require  
     
                    if (disposed)  
                    {  
                        throw new ObjectDisposedException("TickPort");  
                    }  
     
                    #endregion  
     
                    return tickport;  
                }  
                set  
                {  
                    #region Guard  
     
                    if (running)  
                    {  
                        throw new MemberAccessException("Can't change TickPort when running");  
                    }  
     
                    #endregion  
     
                    tickport = value;  
                }  
            }  
     
            #endregion  
     
            #endregion  
     
            #region IComponent Members  
     
            public event System.EventHandler Disposed;  
     
            public ISite Site  
            {  
                get  
                {  
                    return site;  
                }  
                set  
                {  
                    site = value;  
                }  
            }  
     
            #endregion  
     
            #region IDisposable Members  
     
            /// <summary> 
            /// Frees timer resources.  
            /// </summary> 
            public void Dispose()  
            {  
                #region Guard  
     
                if(disposed)  
                {  
                    return;  
                }  
     
                #endregion                 
     
                if(IsRunning)  
                {  
                    Stop();  
                }  
     
                disposed = true;  
     
                OnDisposed(EventArgs.Empty);  
            }  
     
            #endregion         
        }  
     
        /// <summary> 
        /// The exception that is thrown when a timer fails to start.  
        /// </summary> 
        public class TimerStartException : ApplicationException  
        {  
            /// <summary> 
            /// Initializes a new instance of the TimerStartException class.  
            /// </summary> 
            /// <param name="message">  
            /// The error message that explains the reason for the exception.   
            /// </param> 
            public TimerStartException(string message) : base(message)  
            {  
            }  
        }  
    }  
     


    KuSter
    Tuesday, February 10, 2009 4:20 PM

All replies

  •  I have also written a test program.

    #region License  
     
    /* Copyright (c) 2009 Sterckx Kurt  
     *   
     * Permission is hereby granted, free of charge, to any person obtaining a copy   
     * of this software and associated documentation files (the "Software"), to   
     * deal in the Software without restriction, including without limitation the   
     * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or   
     * sell copies of the Software, and to permit persons to whom the Software is   
     * furnished to do so, subject to the following conditions:  
     *   
     * The above copyright notice and this permission notice shall be included in   
     * all copies or substantial portions of the Software.   
     *   
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR   
     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,   
     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE   
     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER   
     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,   
     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN   
     * THE SOFTWARE.  
     */  
     
    /* This software is based on the code from Leslei Sanford. The calls to event  
     *  handlers are changed to post to CCR ports.  
     *    
     * The original code can be found on codeproject:   
     *  http://www.codeproject.com/KB/miscctrl/lescsmultimediatimer.aspx  
     */  
     
    /* Copyright (c) 2006 Leslie Sanford  
     *   
     * Permission is hereby granted, free of charge, to any person obtaining a copy   
     * of this software and associated documentation files (the "Software"), to   
     * deal in the Software without restriction, including without limitation the   
     * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or   
     * sell copies of the Software, and to permit persons to whom the Software is   
     * furnished to do so, subject to the following conditions:  
     *   
     * The above copyright notice and this permission notice shall be included in   
     * all copies or substantial portions of the Software.   
     *   
     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR   
     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,   
     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE   
     * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER   
     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,   
     * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN   
     * THE SOFTWARE.  
     */  
     
    #endregion  
     
    using System;  
    using System.Collections.Generic;  
    using System.Text;  
    using System.Threading;  
    using System.IO;  
    using System.Net;  
    using System.Diagnostics;  
    using Microsoft.Ccr.Core;  
    using Microsoft.Ccr.Core.Arbiters;  
     
    namespace T09021001_Multimedia  
    {  
        class Program  
        {  
            static void Main(string[] args)  
            {  
                Port<EmptyValue> startedport = new Port<EmptyValue>();  
                Port<EmptyValue> stoppedport = new Port<EmptyValue>();  
                Port<EmptyValue> tickport = new Port<EmptyValue>();  
                CCRMultimedia.CCRMMTimer mmtimer = new CCRMultimedia.CCRMMTimer();  
                Stopwatch stopwatch = new Stopwatch();  
                long lasttime = 0;  
                long averagedeltatime = 0;  
                long averagecnt = 0;  
     
                mmtimer.Mode = CCRMultimedia.TimerMode.Periodic;  
                mmtimer.Resolution = 1;  
                mmtimer.Period = 25;  
                mmtimer.StartedPort = startedport;  
                mmtimer.StoppedPort = stoppedport;  
                mmtimer.TickPort = tickport;  
     
                using (Dispatcher dispatcher = new Dispatcher(0, "CCR Demo Threads"))  
                {  
                    // Create a DispatcherQueue that queues work item tasks to the Dispatcher  
                    DispatcherQueue dq = new DispatcherQueue("CCR Demo DispatcherQueue", dispatcher);  
     
                    Arbiter.Activate(dq,  
                        Arbiter.Receive(true, startedport, delegate(EmptyValue ev) { Console.WriteLine("Started"); }),  
                        Arbiter.Receive(true, stoppedport, delegate(EmptyValue ev) { Console.WriteLine("Stopped"); }),  
                        Arbiter.Receive(true, tickport, delegate(EmptyValue ev)  
                            {  
                                if (stopwatch.IsRunning == false)  
                                {  
                                    lasttime = 0;  
                                    stopwatch.Start();  
                                    Console.WriteLine("First tick");  
                                }  
                                else  
                                {  
                                    long newtime = stopwatch.ElapsedMilliseconds;  
                                    long deltatime = newtime - lasttime;  
                                    Console.WriteLine("Tick {0} delta {1}", newtime, deltatime);  
                                    lasttime = newtime;  
     
                                    averagedeltatime += deltatime;  
                                    averagecnt += 1;  
                                }  
                            }));  
     
                    try  
                    {  
                        mmtimer.Start();  
                    }  
                    catch  
                    {  
                        Console.WriteLine("Failed to start multimedia timer");  
                    }  
     
                    Console.WriteLine("Waiting 5 seconds");  
                    Thread.Sleep(5000);  
     
                    stopwatch.Stop();  
                    mmtimer.Stop();  
     
                    if (averagecnt != 0)  
                        Console.WriteLine("Average delta {0}", (double) averagedeltatime / averagecnt);  
                }  
            }  
        }  
    }  
     


    You can create a console C# program and then paste the above program.cs into the generated program.cs(replacing all the other code). You can then add a new C# file and paste the CCRMMTimer.cs code. You should also copy the license region of program.cs to the CCRMMTimer.cs code. Add a reference to Microsoft.Ccr.Code to the solution. It should now compile and run.

    Change the mmtimer.period and mmtimer.resolution to test the code.


    KuSter

    Tuesday, February 10, 2009 4:24 PM
  • Hey thanks KuSter - this is really good stuff.

    I have tried a few different methods suggested in the forums, and this solution using the mm timer is the only one that has given me decent timing accuracy in my application. I'm getting much better than 1ms on average.

    Cheers,
    Murray
    Sunday, August 02, 2009 8:34 AM

  • Murray, thanks for the reply but most of the credits must go to Leslie Sanford and Microsoft that created the wonderful CCR and the MultiMedia Timer. I am however very happy that somebody has discovered this code and uses it.

    KuSter
    Sunday, August 02, 2009 11:35 AM
  • ok seems I still have a problem...

    The timer callback stops being called for some reason after an indeterminate time. Sometimes it runs ok for only a second or so, and sometimes for a few minutes. Seems to change day to day even with the exact same code (and machine). I have no idea what is causing the timer to stop - thought it might be due to the small(ish) interval of 20ms I am using, but if I set a long interval like 2 seconds, the timer callback sometimes doesn't even get called at all. Any suggestions?

    I did read that the timeSetEvent mechanism is flawed (see Larry Osterman's Weblog ) but since this code is using callbacks and not events it should avoid the PulseEvent api issue discussed in the article. I also retried a bunch of other techniques such as System.Timers.Timer and System.Threading.Timer and the suggested replacement to MM timers which is windows timer queues. Unfortunately none of these methods are giving me the timing accuracy that the timeSetEvent / MM timers technique does, and general consensus on the web agrees.

    So I really want to get this to work!
    Saturday, August 15, 2009 3:05 AM
  • First you must known that I don't use the CCR in any of my current software projects. This is not because it is bad but just because I am developing software on microcontrollers. They have beter timers then Windows. This also means that the code presented in my posts is not used in any of my software. So if it contains a bug, I would not notice it.

    However I still want to try to be helpfull. The first thing I want to suggest is to use the original software that doesn't use the CCR (see link in my first post). Just run the demo program and see what happens. Does it keep working. If it doesn't then the problem is related to the MultiMedia timer. Maybe you can then search the internet for some open source software that uses the timer (I think about audio players or video players) and check of they work fine on your computer. If this is the case, then there must be a bug in the orginal multimedia timer example. You can then compare the code with the source code you found.

    If all the multimedia timer code works fine as long as they aren't combined with the CCR, then there must be something wrong with the interaction. The symptons you describe look a lot like deadlocking. This means that there is some synchronisation problems with threads. The CCR is thread based and I think this is also the case for the multimedia timer. Maybe the timer callback routine takes to long, so that it is still busy after 20ms. The callback can then not be called and a deadlock can happen (depends on the unknown internals of the multimedia timer). This can be testing by adding some long running code to the original non CCR multimedia timer code. Maybe the CCR Post function executes the delegate immediatly (I don't known the internals of the CCR) or waits until it can be handled.

    Did you thried what happens when you don't change the timer resolution. By adjusting the resolution also the scheduler is adjusted. Maybe this has some bad effects on the CCR.

    The test I have done also indicates that the MMTimer is the only one to give a little bit useful resolution. However when you are used to getting 50 microseconds accurate timers on a 8MHz microprocessor, it is very difficult to understand why 1ms is the limit on a GHz PC.

    I hope this post is usefull for you and I certainly hope that you get it to work !

    Saturday, August 15, 2009 9:30 PM
  • Hi KuSter and thanks for the help! It is very much appreciated.

    I totally agree with you about micros and timers - things that are so easy on a micro are difficult on a desktop pc and vice versa. This project I am working on is a quadruped robot using microsoft RDS for inverse kinematics and higher level control, with a wifi link to two avr micros on board the robot. It would be difficult to perform all the calculations I am doing with robotics studio fast enough on the atmel avr, yet simple to set up a very accurate timer on the little micro. On the desktop pc, the vector maths and matrix rotations are simple and blisteringly fast but just triggering something to happen 50 times a second accurately is proving extremely tough!

    Your suggestions are very sensible, and so I tried just the code you have posted here built as a console app (but still using CCR) and it seems to work fine, at least as far as I can tell. I went back to my troubled code and tried to look for something like a deadlock or thread starvation. I added some console output using the stopwatch and did discover a pattern that seems to occur just before the mmtimer stops firing - there is an extended interval between two timer events (eg 253ms where it should be 20ms), followed by 12 or so catch up timer callbacks executed very fast (100us to completely catch up), then the normal 20ms interval resumes but the timer stops firing very shortly afterwards (anything from 1 to 10 intervals afterwards).

    Have a look at this excerpt from the console output showing stopwatch ellapsed time in milliseconds;

    ...
    timer       32944.292
    gotmsg    32944.685
    timer       32964.240
    gotmsg    32966.576
    timer       32984.393
    gotmsg    32984.746
    timer       33004.259
    gotmsg    33004.362
    timer       33024.266
    gotmsg    33024.378
    timer       33209.302
    timer       33209.373
    timer       33209.393
    timer       33209.409
    timer       33209.425
    timer       33209.443
    timer       33209.458
    timer       33209.474
    timer       33209.490
    gotmsg    33209.861
    gotmsg    33210.004
    gotmsg    33210.068
    gotmsg    33210.129
    gotmsg    33210.191
    gotmsg    33210.274
    gotmsg    33210.337
    gotmsg    33210.397
    gotmsg    33210.464
    timer       33224.238
    gotmsg    33237.086

    And that's where it stops. The "timer" lines are written out from the timer callback which posts to the CCR port, and the "gotmsg" lines are written out from my service using;
    yield return Arbiter.Receive(false, timerPort, delegate(EmptyValue ev) { });
    Console.WriteLine("gotmsg\t{0}", (float)moveTimer.stopWatch.ElapsedTicks * 1000.0 / Stopwatch.Frequency);
    which is inside a loop. The cause of the extended timer interval is a mystery, maybe the cpu was particularly loaded at that point or something. But although the timer appears to recover from the blip in timing accuracy, the sequence of 'catch up' timer callbacks definitely seem to spell the end of the timer shortly afterwards.

    Any thoughts?
    Sunday, August 16, 2009 9:23 AM
  • Ok, I've collected some more clues regarding this problem of the MM timer stopping and not calling the callback after some variable time has passed. I feel sure that it is some sort of threading issue based on my possibly incorrect usage of dss/ccr now.

    If I compile and run KuSter's code above as a console app, it seems to work fine (even changing the 5 second timeout to System.Threading.Timeout.Infinite and letting it go for an hour). His test program creates a dispatcher, a dispatcher queue, then uses Arbiter.Activate with the freshly created dispatcher to set up receivers and delegates for the started, stopped and tickport ports that CCRMMTimer uses. My program is set up a bit differently which may be the problem. I am using an Iterator defined like;

    public IEnumerator<ITask> MyIterator()
    {
      CCRMultimedia.CCRMMTimer mmtimer  =  new  CCRMultimedia.CCRMMTimer();
      Port< EmptyValue >   tickport  =  new  Port < EmptyValue > ();
      mmtimer.Mode  =  CCRMultimedia .TimerMode.Periodic; 
      mmtimer.Resolution  =  1 ;
     
    mmtimer.Period  =  20 ;
      mmtimer.TickPort  =  tickport ;

      mmtimer.Start();

      while (true)
      {
        ... some processing ...

        yield return Arbiter.Receive(false, tickport, delegate(EmptyValue ev) { } );

        Console.WriteLine("got timer tick");
      }

    }

    and started from my custom DSS service Start method using SpawnIterator(MyIterator);

    In the iterator I have a endless loop using yield return Arbiter.Receive(false, myPort) type statements to wait for particular events. With the program structured to use  the CCRMMTimer class as I have described here, it will work as expected for anywhere between 1 second and 10's of minutes before stopping because no more messages arrive on the tickport.

    I tried adding the following line prior to the service declaration;
       [ActivationSettings(ShareDispatcher=false, ExecutionUnitsPerDispatcher=10)]

    which I understand will instruct ccr to use a dedicated dispatcher for the service with 10 threads, but this didn't seem to make a difference. All this has to point towards a threading issue, but exactly what I am doing wrong, I don't know. I should also mention that if I cut and paste KuSter's test code which creates a dispatcher and dispatcher queue into my interator, simplify it a bit and change the sleep to an infinite time, it also seems to work for as long as I let it run, so the clue is that explicitly creating a dispatcher and dispatcher queue avoids the problem. Perhaps my use of iterators and yield return is incompatible with this?

    The code that I insert into my iterator to test this is;

                using (Dispatcher dispatcher = new Dispatcher(0, "CCR Demo Threads"))
                {                
                    // Create a DispatcherQueue that queues work item tasks to the Dispatcher  
                    DispatcherQueue dq = new DispatcherQueue("CCR Demo DispatcherQueue", dispatcher);
                    long lasttime = 0;
                    long averagedeltatime = 0;
                    long averagecnt = 0;
    
                    Arbiter.Activate(dq,
                        Arbiter.Receive(true, _timerPort, delegate(EmptyValue ev)
                        {
                            long newtime = moveTimer.stopWatch.ElapsedMilliseconds;
                            long deltatime = newtime - lasttime;
                            Console.WriteLine("Tick {0} delta {1}", newtime, deltatime);
                            lasttime = newtime;
    
                            averagedeltatime += deltatime;
                            averagecnt += 1;
                        }));
                    Thread.Sleep(System.Threading.Timeout.Infinite);
    
                }
    
    Anyone more knowledgable on the ins and outs of ccr and threading care to comment? - thanks in advance!




    Wednesday, August 19, 2009 4:01 AM
  • I am still chasing the elusive goal of reasonably accurate timing within robotics studio, so I am still working on the problem of getting the multimedia timer working using the methods above. I've run another test which clarifies under what conditions the multimedia timer stops firing - basically I've taken KuSter's example above an wrapped it up as a DSS service - reasoning being that if KuSter's standalone sample above runs ok, but combining it with my own services doesn't, maybe turning it into a service which can be used as a partner might be a more reliable way to use the timer. I found much the same behaviour as building KuSter's code into my service directly, the mm timer stops shortly after my robot simulation starts.

    So I tried a little experiment - changed the mm timer service to start the multimedia timer at 20ms interval upon service start and just write a line to the console output every time the timer fires. Running a dss node and starting the mm timer service using the dss web interface works well - the console fills with messages 50 times a second that keep going as long as I leave it. But if I also start a complicated service as well, for example the "multiple simulated robots" or the "apartment scene" service, then sure enough the mm timer stops firing and the console output from the timer service stops.

    I can then drop the mm timer service using the dss web interface, and restart it - it will run for a very short time (<5 seconds) then stop. This is quite repeatable. It's definitely not cpu loading, because if I run the mm timer service on its own in a DSS host, and also run a graphical benchmarking program (not CCR/DSS related at all, I used LightsMark2008) the timer continues quite happily in the background. So it seems that loaded up a DSS host with something more than just the timer seems to be the problem.

    I will continue playing, and if anyone else can help I would be thankful, but if I get some sort of solution I will be sure to post it here.
    Wednesday, September 02, 2009 7:09 AM
  • It has been a while that I used the CCR/DSS, so I can't realy comment on your code. However it also seems to my that there are threading issues. Could it be that some internal CCR threads are blocking the MM Timer and that it has some internal mechnism to stop firing when it is already caching up. It would be nice to have some kind of threading inspection tool that can shows the threads that are running with there priorities, maybe ProcessHacker(open source, see sourceforge) can do this. Intel has also great tools (Thread Checker/Profiler), but not sure they work together with C#.

    Maybe you can try to place a routine in the mmtimer callback that needs a long time to perform his task and see what happens, in a none CCR enviroment and then in my CCR code.

    From a previous post I read that you use AVR to control the robot(I am also a AVR fan). The comminucation is wireless. Is it not possible to let the AVR send a event to the PC each 20ms. This could then by your timer.

    Another possibility is to great a seperated service (seperated program) that sends a UDP message each time the MM timer fires to the local IP address and some port. The CCR code can then wait for this message and use it as a timer trigger. The TCP/IP is very fast. I have experimented with a seperated microcontroller that sends UDP messages to the PC each 2ms and it worked very well (even with 1000 bytes in the packet). The PC is more optimized for this kind of stuff it seems.

    However it would be great if you could solve the problem with the MM Timer. So keep on testing/experimenting.

    Thursday, September 03, 2009 6:27 AM
  • It's funny you should suggest this now - I had just as of yesterday decided that getting to the bottom of the issue was taking way too long and to side step the problem by setting up another completely separate program to generate the timing and send through notifications via either tcp or udp just as you have suggested here. I hadn't actually considered your other suggestion which was to use the avr on the robot itself to do the timing and have it notify the pc via tcp or udp, that is brilliant!

    I may still go the separate program running on the pc option though, because with the robot's avr generating the timing option I would have to restructure my robotics studio services a bit from what they are now and add some more inter-service comms, as the service that currently needs the timing signals doesn't communicate directly with the robot, instead it communicates with either a control service that does connect to the robot or a simulation service that simulates the robot in the sim environment. But that's just implementation details.

    I would still like to get mm timers or some other accurate timing happening completely within robotics studio, but at the end of the day I just need to get the robot project moving forward!

    Thanks for your help once again KuSter.
    Tuesday, September 08, 2009 2:43 AM