Main Site Documentation

Drivers coding and programming practices


#1

Hello,

Since we can see many drivers coded by many people, I was wondering if some rules should/could not be applied to such drivers.
My idea would consist in providing “Interfaces” that driver class should implement. These interfaces would contain the minimal set of methods/properties the driver has to implement, so that the same program could use different drivers by only changing some declaration in its source.

Let me give an example, to be more explicit. Here’s what the interfaces could look like :

namespace FEZInterfaces
{
    public enum ElcdCursorShape { Full, Line, None}
    public enum ElcdCursorKind { Steady, Blink }

    public interface ICommon
    {
        string Author { get; }
        System.Version DriverVersion { get; }
    }

    public interface IStepper : ICommon
    {
        long CurrentPosition { get; set; }
        long TargetPosition { get; set; }
        bool IsMoving { get; }
        bool Move(long destPosition);
        void Stop();
    }

    public interface ILcd : ICommon
    {
        byte MaxCol { get; }
        byte MaxRow { get; }
        void Print(byte xCol, byte yRow, string text);
        void SetCursor(ElcdCursorShape newShape, ElcdCursorKind newKind);
        void SetCursor(byte[] byteArray);
    }
}

Then, if I implement a driver for the EasyDriver stepper controler, I would code like this :

using System;

using FEZInterfaces;

namespace EasyDriver
{

    public class EasyDriverV4 : IStepper
    {
        public long CurrentPosition
        {
            get { return 10; }
            set { throw new NotImplementedException(); }
        }

        public long TargetPosition
        {
            get { throw new NotImplementedException(); }
            set { throw new NotImplementedException(); }
        }

        public bool IsMoving
        {
            get { throw new NotImplementedException(); }
        }

        public bool Move(long destPosition)
        {
            throw new NotImplementedException();
        }

        public void Stop()
        {
            throw new NotImplementedException();
        }

        public string Author
        {
            get { return "Christophe Gerbier"; }
        }

        public Version DriverVersion
        {
            get { return new Version(1,2); }
        }
    }
}

Another stepper driver for the Phidget1062 controler could look like this one :

using System;

using FEZInterfaces;

namespace Phidget1062
{
    public class Phidget1062Stepper : IStepper
    {
        public long CurrentPosition
        {
            get { return 123; }
            set { throw new NotImplementedException(); }
        }

        public long TargetPosition
        {
            get { throw new NotImplementedException(); }
            set { throw new NotImplementedException(); }
        }

        public bool IsMoving
        {
            get { throw new NotImplementedException(); }
        }

        public bool Move(long destPosition)
        {
            throw new NotImplementedException();
        }

        public void Stop()
        {
            throw new NotImplementedException();
        }

        public long AccelerationMax
        {
            get { return 100; }
        }

        public string Author
        {
            get { return "Christophe Gerbier"; }
        }

        public Version DriverVersion
        {
            get { return new Version(2, 0); }
        }
    }
}

Then an LCD driver for the Devantech LCD03 could look like this :

using System;
using Microsoft.SPOT;

using FEZInterfaces;

namespace DevantechLCD03
{
    public class LCD03 : ILcd
    {
        public byte MaxCol
        {
            get { throw new NotImplementedException(); }
        }

        public byte MaxRow
        {
            get { throw new NotImplementedException(); }
        }

        public void Print(byte xCol, byte yRow, string text)
        {
            throw new NotImplementedException();
        }

        public void SetCursor(ElcdCursorShape newShape, ElcdCursorKind newKind)
        {
            Debug.Print("Setting cursor");
        }

        public void SetCursor(byte[] byteArray)
        {
            // No custom shape available so throw exception
            throw new NotImplementedException();
        }

        public string Author
        {
            get { return "Christophe Gerbier"; }
        }

        public Version DriverVersion
        {
            get { return new Version(1, 0); }
        }
    }
}

Finally, the “real” program using such drivers would look like :

using System;
using Microsoft.SPOT;

using EasyDriver;
using Phidget1062;
using DevantechLCD03;

namespace FEZ_Domino_Application1
{
    public class Program
    {
        static EasyDriverV4 Stepper1 = new EasyDriverV4();
        static Phidget1062Stepper Stepper2 = new Phidget1062Stepper();
        static LCD03 MyLCD = new LCD03();

        public static void Main()
        {
            Debug.Print("EasyDriver stepper position : "+Stepper1.CurrentPosition);
            Debug.Print("Phidget1062 stepper position : " + Stepper2.CurrentPosition);
            Debug.Print("Phidget1062 acceleration max : "+Stepper2.AccelerationMax);
            try
            {
                MyLCD.Print(5,2,"Some text");
                MyLCD.SetCursor(new byte[8]{1,2,3,4,5,6,7,8});
            }
            catch (Exception)
            {
                Debug.Print("Command not available");
            }
        }
    }
}

As you can see, the same command (method) is used with both stepper drivers to get the actual position of the motor. Here, I have included both drivers for the example.
Also, the Phidget1062 driver has an extra property that is not part of the standard interface : AccelerationMax.

Of course, these are all examples, to show you what I have in mind. Interfaces are not complete at all nor they are good “as-is”. But I think that having such “rules” to follow when coding drivers may help coding programs with different devices without having to worry about it.

Interfaces content could be decided and validated by the community.

Do you understand what I want to say ? Am I wrong or not ? Do you find it useful or useless ? :-[

[italic]Edit:[/italic]
the “NotImplementedException” is here because I didn’t want to code real drivers. The real properties/methods should do real work, of course :wink:


#2

Well,

If we were to start like a CodePlex project for NETMF drivers, this would be a good thing to do. Would anyone else want to participate in such a project?


#3

While requesting people code a certain way is nice and what not. Remember you are talking to hobby people here too who may not want to spend all their time formatting code. They may just want to make something that works and share the code. If we get all specific of what it has to look like and how the interface is coded I think alot of good code will go unposted and unused. It then becomes a pain in the ass to share which I don’t think is exactly what we are going for here.


#4

:dance: :dance: :dance: :dance: :dance: :dance:

Yeah! I totally agree :smiley:

I just started working on a hobby servo interface so that no matter how you drive the servo (PWM, OC, external servo driver) the interface remains the same. So if you wrote code to control a servo it would work the same no matter what servo driver class your actually using.

You can also ‘inherit’ one or more interfaces to form a new interface. In the code below you can see that I created a base interface that is inherited by other interfaces that provide methods suited to that type of servo use (angular, continuous, ESC). There is one huge flaw in the IHobbyServo declaration though, configure should not take a PWM.Pin as this limits its utility to the GHI PWM class. I need to replace this with a uint which will allow for a generic enumeration.

Another benefit of using an interface is that if you decide to move from using on board PWM to an external servo control board and you find that there is a good driver written for it already you don’t need to rewrite your code to use use the new board. If it supports the same interface then you have to do very little, if it does not you just need to add a few helper methods that provide the interface (much easier than rewriting either the driver or you existing code). this same idea will work for a GPS or other things as well.

///////////////////////////////////////////////////////////////////////////////
//
// ServoInterface.cs
//  a set of interfaces for controlling hobby servos
//
///////////////////////////////////////////////////////////////////////////////

using GHIElectronics.NETMF.Hardware;

/// <summary>
/// 
/// </summary>
interface IHobbyServo
{
    /// <summary>
    /// 
    /// </summary>
    /// <param name="pin"></param>
    /// <param name="inverted"></param>
    /// <param name="initialPosition"></param>
    /// <returns></returns>
    bool Configure(PWM.Pin pin, bool inverted);

    /// <summary>
    /// 
    /// </summary>
    uint PulseWidth
    {
        get;
        set;
    }

    /// <summary>
    /// 
    /// </summary>
    bool Enable
    {
        get;
        set;
    }

    /// <summary>
    /// 
    /// </summary>
    bool Invert
    {
        get;
        set;
    }
}

/// <summary>
/// 
/// </summary>
interface IAngular : IHobbyServo
{
    /// <summary>
    /// 
    /// </summary>
    double Angle
    {
        get;
        set;
    }
}

/// <summary>
/// 
/// </summary>
interface IESC : IHobbyServo
{
    /// <summary>
    /// 
    /// </summary>
    double Velocity
    {
        get;
        set;
    }
}

#5

@ bstag : honestly, I think that conforming to some kind of standard is worth the “pain” :wink:
You can be sure that your driver will/can be used without users asking basic questions on how this or how that.

If I take the stepper example, I can’t imagine a driver without some kind of Move() command ??? So even the hobbyist will code this method, maybe naming it differently.
If he doesn’t want to implement a method/property in the interface, then he can simply throw an exception, like I did in my examples.

And it’s not about formatting code, here : only implement basic/standard methods & properties.


#6

[quote]If I take the stepper example, I can’t imagine a driver without some kind of Move() command So even the hobbyist will code this method, maybe naming it differently.
If he doesn’t want to implement a method/property in the interface, then he can simply throw an exception, like I did in my examples.[/quote]

Why would he even use the interface code if he is making his own driver. If he called is move command go why would he then code a exception thrower for move. Maybe i am missing the point you are trying to make.


#7

I think it’s the case :wink: Let me explain with your example.

Take the following program, using EasyDriver driver :

using EasyDriver;

namespace FEZ_Domino_Application1
{
    public class Program
    {
        static EasyDriverV4 Stepper = new EasyDriverV4();

        public static void Main()
        {
// Move the stepper at position 1200
            Stepper.Move(1200);
        }
    }
}

Now take this one, using another driver (Phidget1062 here)

using Phidget1062Driver;

namespace FEZ_Domino_Application1
{
    public class Program
    {
        static Phidget1062 Stepper = new Phidget1062();

        public static void Main()
        {
// Move the stepper at position 1200
            Stepper.Move(1200);
        }
    }
}

Apart from the declaration, they are stricly identical.

Now, if I take “your” driver, where you called the “standard” move method Go() then I will have at least an error in my code, since I need to replace the Move() method with the Go() method everywhere in the program source…
Do you see the difference ? In one case (the “standard way”) you only need to change the Stepper declaration and nothing else, in the other case you have to go through all the code to replace each and every method that doesn’t have the same name (even though they may behave identically).

As for throwing an exception, and if I keep on using the stepper example, this could be the case if the interface is exposing a Current property which would return the current usage of the stepper. Then, if the controler board you’re using doesn’t give this info, you can’t code it of course. In this case, as the Current property is mandatory (still for the example) you can then throw an exception indicating this fact. The property is “implemented” to conform to the interface but the result cannot be returned by the device, hence the exception.

Is it better now ?

Also, with such programming rules, you can easily take advantage of VisualStudio templates : once the interface is accepted, one can create a driver template for this interface so that anyone else can use it as a starting base for his driver.
You “only” have to fill in the “blanks” in the template.

For example, if you want to code a LCD driver, you can download this template and put it in the “Templates\ProjectTemplates\Visual C#\Micro Framework” directory of VisualStudio (in My Document and settings). Then you simply create a new project with this template, put your own code in all the properties/methods (or throw exception where applicable). This way, you will have a “standard” driver with small effort.

Do you see the advantages ?


#8

If there’s some interest, I’ve created a small help file that could be used as a start.

I have put what I think is necessary interfaces, but of course I may have forgotten some :hand:
In this help file, only the ITextLcd interface has some methods/properties. This help file can also provide code samples, which I haven’t done yet.

Also, I don’t have all the devices, so help is welcome here to add methods/properties as you like. I think at GPS drivers, for instance.

Do you think it’s enough work for a Codeplex project ? If yes, I may create it quickly so that interested people can begin participation.

Again, I don’t want to force anyone to this. It’s only an idea. But I think it’s a good idea (it’s mine, after all :smiley: ) at least for debate purposes.


#9

Bec, even better for an example:

using Phidget1062Driver;
 
namespace FEZ_Domino_Application1
{
    public class Program
    {
        static IStepper Stepper = new Phidget1062();
 
        public static void Main()
        {
// Move the stepper at position 1200
            Stepper.Move(1200);

            Stepper = new EasyDriver4()
            Stepper.Move(1200);
        }
    }
}

I’d be interested in working on code, however it needs to be architected - solution, projects and the interfaces to that it’s not just a mess and difficult to use. I’m used to extremely well architected solutions at work which make complex processes easy to use/maintain so when things are not done well it really annoys me haha.

Could you send me an email bec? markh@ rris.com.au and i’ll put together a solution of what i’m thinking then you can see what you think. I was planning on doing something like this myself however a couple of other things have taken up my time.


#10

I guess it i get it.


#11

It’s great to think this way; as someone pointed out though there’s a huge mix of skill levels in the enthusiast/hobyist arena that make this somewhat more challenging. People also have different ideas about what constitutes a good set of methods for a particular task, so that can sometimes get more challenging to agree on. Take for instance your LCD sample you linked earlier, I wouldn’t expect PRINT to take an X/Y coord, I’d just want it to print where the current cursor position is, so I’d want to see that overloaded to just take a string (and a byte array etc). Just another factor to consider.


#12

As for the codeplex thing i think we should wait for code share to post the interfaces.


#13

I don’t see how we can pick common interfaces for such a broad range of devices. Sure all servo drivers will probably have similar methods/properties, but servos and displays will not.

Splitting the interfaces to categories would eliminate the differences, but is it really worth it?
The reason for interfaces is so that some object would be easy to use in an already existing environment. Say you wanted a type that could store integers larger than what ulong can store and you don’t want to use BigInteger, you would make your own type “ulonger” that would say be 128bits large, naturally it would implement everything that ulong does ( IComparable IComparable IConvertible and so on and so forth).

I just don’t see how LCD and Servo drivers would follow a similar pattern.


#14

I don’t think anyone is suggesting that completely unrelated item like a hobby servo and LCD would have a common interface; rather there could be a common interface for character LCDs, and different common interface for hobby servos, etc.

I have been trying to think of a better explanation of what an interface is and why you might want to use one. When we are talking about an interface in this context we are talking about a class interface, that is how you interact with a class.

So, a class interface the same idea as a user interface. Take a car for example, when autos first came on the scene the user interface was quite varied and you could be very proficient at driving one type while clueless on how to interact with a different brand. After a hundred years of making cars, they all now have steering wheels, a gas and brake pedal ect. The basic interface is the same from car to car. The steering wheel interface does not say anything about how the steering wheel is implemented, just how the user should be able to interact with it.

The class interface gives us the same type of benefit. A more standardized way to interact with a class/driver that is serving a common type of purpose. The class interface does not specify the structure of a providing class but rather just the signature of its interface with other parts of the program that might want to make use of it.


#15

I see jeff rewriting a bunch of code to match the interface in his future.


#16

An interface is a in the most simple terms contract that classes adhere to. It is a contract to implement the methods and properties that are declared in the interface.

The main reason you want to use interfaces though is for decoupling of your code. Classes should not have intimate knowledge of each other in OOP or you end up with spaghetti code.


#17

They won’t ! :wink: Each one will have its own interface which will define the minimal expected set of properties/method the driver should implement.
For the LCD, you could have Write(X,Y,Text) and for the servos SetPosition(angle). Both are examples only, of course.

The car example is a good one : every car has a common set of properties/methods, but all cars are different. It doesn’t prevent you for driving those different cars, provided they use the same bases. Here it will be the same thing with LCD, for example : if each one implements a Write(X,Y,Text) method, then you won’t have to worry if your code uses Write(x,y,string) to display a text. It will always work.

That’s exactly why these things need to be discussed :wink: And that’s also why I said that my example were only examples, just to explain the way it can be done. Nothing more, at this stage :hand:

Here’s another example of an incomplete TextLCD interface :

namespace FEZInterfaces
{
    /// <summary>
    /// Interface for character LCD.
    /// </summary>
    public interface ITextLcd : ICommon
    {
        /// <summary>
        /// Returns the maximum number of rows for this display. Usual values are 2 or 4.
        /// </summary>
        /// <value>Maximum raw value</value>
        byte MaxRow { get; }
        /// <summary>
        /// Returns the maximum number of columns for this display. Usual values are 16 or 20.
        /// </summary>
        /// <value>Maximum column value</value>
        byte MaxCol { get; }

        /// <summary>
        /// Writes the specified string at location (xCol, yRow).
        /// </summary>
        /// <param name="xCol">The column. Must be between 0 and <see cref="MaxRow"/>.</param>
        /// <param name="yRow">The row. Must be between 0 and <see cref="MaxCol"/>.</param>
        /// <param name="sText">The text to display.</param>
        void Write(byte xCol, byte yRow, string sText);
        
        /// <summary>
        /// Writes the specified string at current cursorlocation.
        /// </summary>
        /// <param name="sText">The text to display.</param>
        void Write(string sText);

        /// <summary>
        /// Writes the specified byte array at current cursor location. This could be a sequence of command chars or a new cursor shape, for example.
        /// </summary>
        /// <param name="byteArray">The array of byte values to write to the LCD.</param>
        void Write(byte[] byteArray);
    }
}

Btw, for the LCD, how should the “write method” be called : Write() or Print() ? :whistle:

You’re right. Until then, I will try to make more complete interfaces so that we can start on a “good” basis. I may post some code somewhere, though, so that anyone can begin to modifiy them.


#18

I’ve put the “project” files in my dropbox. If you want access to it, simply send a email to : becafuel @ lsp-fr dot com


#19

Me: [quote]Take for instance your LCD sample you linked earlier, I wouldn’t expect PRINT to take an X/Y coord, I’d just want it to print where the current cursor position is, so I’d want to see that overloaded to just take a string (and a byte array etc). Just another factor to consider.[/quote]

You: [quote]That’s exactly why these things need to be discussed And that’s also why I said that my example were only examples, just to explain the way it can be done. Nothing more, at this stage [/quote]

My point is more how do we get “consensus” (if we need it that is) when we have a (widely) varied skill level. I’m sure a lot of development houses go through this when taking on new recruits who have to learn the “house lingo” but when you have people like me with little/no OO programming experience, I struggle thinking about how I expect these things to work. Having said that, I totally get the reason for separating interfaces: you have a core Text LCD class and an interface that tells it how to connect to it - some different examples might be a USB serial device, serial connected, SPI, I2C, 4-bit connected, or 8-bit connected, and the transfer provider just figures out how to get a character out onto the display.

It’s a great question, Print or Write. What’s the right answer? You say potato, I say potatoe. I can tell you mine is Write :slight_smile: That certainly doesn’t mean either of us are wrong tho :wink: :wink:


#20

I don’t think that skill level is important, here : all we have to do is to find out what is the minimal set of methods/properties a driver should implement.
More skilled programers are free to add extensions in their driver, as long as the core conforms to the “standard” interface (see my example with the Phidget1062 and its AccelerationMax property, for example).

So, such consensus should be easy to reach. The more people involved, the more devices being known, so the more common things will be obvious, I think.

What about Display() ? :whistle: