Click here to Skip to main content
Click here to Skip to main content

Tagged as

Windows 8 Directly Driving Stepper Motor

, 29 Aug 2014 Ms-PL
Rate this:
Please Sign up or sign in to vote.
Demonstrates Windows 8 capability to directly control stepper motor

Introduction

This article demonstrates a research project to prove that Windows 8 on any architecture is capable of driving directly a stepper motor. As it occurs with my projects, it is a combination of hardware and software to create some setup to prove the concept first and then further investigate the limits and capabilities of it with practical applications in mind.

The purpose of my research was to test how to control a stepper motor directly from Windows 8 in a simple way and whether fine control can be reached, i.e., microseconds and thousands of rpm. In a sense, this article describes the inverse case of my previous article, where a demonstration of Windows RT high speed external interrupt handling was shown.

Both architectures for Windows 8 are covered, i.e. ARM (Windows RT) and x86/X64.

Background

Stepper motors are very common in a number of places around us, ranging from tiny ones inside of smart phones and to larger powerful stepper motors used in CNC machinery. Usually, they are controlled by dedicated chip(s), which communicate with PC equipped with digital I/O card, thus creating a comprehensive setup.

The purpose of this research project was to test if the opposite could be accomplished, i.e., to control a stepper motor directly from Windows 8 in a simple way, that without involving any PC I/O card or a microcontroller, rather by simply connecting a stepper motor to processor GPIO pins and let Windows 8 have control of stepper motor directly.

Both hardware and software tests were involved in this project.

Hardware tests included use of a Windows 8 platform, a stepper motor, power supply and oscilloscope with 2 GS/s resolution.

Software tests included use of Windows 8 platform software to obtain time stamps with 100 nanosecond resolution.

Windows 8 Hardware Setup for Stepper Motor

On ARM architecture, that is on Windows RT, there are GPIO pins typically exposed to platform developer, and that is all we need.

Since GPIO pins are available to Windows RT through GPIO device driver, it is easy to take advantage of this resource and just use GPIO pins to control stepper motor from within Windows RT.

On Windows 8 running on x86 or X64 platform, there are no GPIO pins easily available for our experiment, but some parallel port (LPT1: ) control pins are sufficient for testing purposes.

Low power stepper motor can be connected and driven by just GPIO pins of ARM processor, which could supply sufficient electric power to it (say 10 mA). For a larger, more powerful stepper motor an external power supply is required, along with power transistors to control high current to stepper motor coils, such as ah H-bridge. A picture below demonstrates both cases on Windows RT.

Four GPIO pins were used for this setup, typical for two coil stepper motor, which has four wires. Thus each stepper motor connected to Windows RT platform would consume four GPIO pins. For a reduced wiring method, two wires can be used per each stepper motor but an extra circuit will be required. Our setup will support both methods.

Stepper Motor Windows Device Driver

First Windows 8 needs to know about the existence of stepper motor, since it is not a Windows 8 plug-and-play device. For the purposes of this article, stepper motor is considered to be a static peripheral device, permanently attached to GPIO pins, and therefore known to Windows 8 as non-PnP resource. Each stepper motor has static entry in ACPI table on Windows RT, and no entry on Windows 8 x86/X64.

It is in ACPI table named DSDT where particular stepper motor configuration is described, such as using two or four wires. Additional properties of stepper motor can be set up here, for example number of steps per revolution.

Windows RT: APCI Resources for Stepper Motor

All Windows 8 hardware resources are described in ACPI tables. Since stepper motor is a new hardware resource, and not a standard Windows type of peripheral device, it must be added to ACPI tables.

A simple section in DSDT table shown below as an example for four wire stepper motor, attached to GPIO pins 142-144. Any available GPIO pins can be used, and name chosen for this stepper motor is arbitrary.

// DSDT table
Device(STP1)
{
  Name (_ADR, 0)
  Name (_HID, "STPR0001")
  Name (_CID, "STPR0001")
  Name (_UID, 1)

  Method (_CRS, 0x0, NotSerialized)
  {
    Name (RBUF, ResourceTemplate()
    {
// gpio 143-144 pins as output; set LOW by default
GpioIo (Exclusive, PullDown, 0x0000, 0x0000, IoRestrictionOutputOnly,
"\\_SB.GPI2", 0x00, ResourceConsumer, , RawDataBuffer(){0x15} ) { 143 }
GpioIo (Exclusive, PullDown, 0x0000, 0x0000, IoRestrictionOutputOnly,
"\\_SB.GPI2", 0x00, ResourceConsumer, , RawDataBuffer(){0x15} ) { 144 }
    })
    Return (RBUF)
  }
  Method (_STA, 0x0, NotSerialized)
  {
    Return(0xf)
  }
}  // end of Device (STP1)//

Stepper Motor Windows 8 Device Driver Architecture

For stepper motor driver, its parameters on Windows RT are set in ACPI table. For Windows 8 x86/X64 stepper motor parameters are hardcoded in its device driver.

Once stepper motor device is described in ACPI table (for Windows RT) or its pins are hardcoded in the driver (for Windows 8 x86/X64), Windows 8 will see it and will then look for a device driver for it. Since device driver is not available yet, Windows device manager will show an unknown device for a stepper motor.

Stepper motor device driver needs to accomplish a simple task: change states of up to four GPIO pins in a timely manner, as accurate as possible, to form nice looking pulses of variable length to control stepper motor speed. Stepper motor driver has a static variable which is current stepper motor position, which increments or decrements depending on stepper motor rotation direction.

Upon loading, stepper motor device driver creates high priority kernel thread running at either PASSIVE or APC IRQL level. This thread will run the loop to control stepper motor.

A picture below illustrates what stepper motor Windows 8 device driver should do: when stepper motor runs, a loop in driver thread is toggling four GPIO pins to provide pulses to stepper motor coils.

A function toggling GPIO pin uses simple constant array of pin states, which is used repeatedly to walk through on every pass through the loop. The code snippet is shown below:

/* generic stepper motor pulse algorithm
 binary translation table
 2 wires
    0 -> 01b, 1-> 11b, 2 -> 10b, 3 -> 00b
 4 wires
    0 -> 1010b, 1 -> 0110b, 2 -> 0101b, 3 -> 1001b    */

// for 2-wire motor, use 4 rows 2 columns - one per pin state
const int iTwoWirePinStates[4][2]={{0,1},{1,1},{1,0},{0,0}};

// for 4-wire motor, use 4 rows 4 columns - one per pin state
const int iTwoWirePinStates[4][4]={{1,0,1,0},{0,1,1,0},{0,1,0,1},{1,0,0,1}};

Accessing GPIO Pins

Windows RT

Our stepper motor driver calls into GPIO controller driver, supplied by Microsoft, to set particular GPIO pin state. Driver to driver communication is done through IRP. For this mechanism to work, stepper motor driver thread must run at either PASSIVE or APC IRQL level, not higher.

The picture below illustrates the mechanism of changing the state of GPIO pins from the context of stepper motor device driver.

Windows 8 x86/X64

Here our stepper motor driver sets particular control pin state on LPT1: directly, by using write to port command WRITE_PORT_UCHAR. For this article, two control pins of LPT1: port were used, pins 16 and 17, and 2-wire method implemented to control 4-wire 2-coil stepper motor. This method can be used at any IRQL level.

The code snippet below shows how parallel port control pin state was set from the driver.

// on x86 use immediate i/o port access
// bit 3, pin 17 - pulse
    uchByteToWrite=uchByteToCtrl & (abs(pDeviceContext->lStepsToTurn)%2==0?0xFF:0xF7);
    TraceEvents(TRACE_LEVEL_VERBOSE, TRACE_DRIVER, "wrkr_Kthrd4 iter=%ld ctrlbyte=%Xh",
    pDeviceContext->lMotorStepNumber, uchByteToWrite);

// set motor coils GPIO pins for parallel port on x86-X64
    WRITE_PORT_UCHAR(puchLpt1PortCtrl, uchByteToWrite);
Stepper Motor Driver INF File
All we care about in this file is to match stepper motor resources in ACPI table.
;*****************************************
; Install Section
;*****************************************
…..
[Standard.NT$ARCH$]
%Stpr_mtr.DeviceDesc4%=Stpr_mtr_Device, ACPI\PULS0004 ; in ACPI table described as PULS0004 - 4-wire motor on GPIO pins A,B,C,D

It is here where we can also make single stepper motor device driver to serve different motor configurations, for example both 2-wire and 4-wire configurations.

[Standard.NT$ARCH$]
%Stpr_mtr.DeviceDesc4%=Stpr_mtr_Device, ACPI\PULS0004 ; in ACPI table described as PULS0004 - 4-wire motor on GPIO pins A,B,C,D
%Stpr_mtr.DeviceDesc21%=Stpr_mtr_Device, ACPI\PULS0002 ; in ACPI table described as PULS0002 - 2-wire motor 1 on GPIO pins A,B
%Stpr_mtr.DeviceDesc22%=Stpr_mtr_Device, ACPI\PULS0003 ; in ACPI table described as PULS0003 - 2-wire motor 2 on GPIO pins C,D
….
[Strings]
SPSVCINST_ASSOCSERVICE= 0x00000002
ManufacturerName="SergeiR" ;
ClassName="Motors" ;
DiskName = "Stpr_mtr Installation Disk"
Stpr_mtr_drv.DeviceDesc4 = "Stepper Motor (GPIO 4-wire)"
Stpr_mtr_drv.DeviceDesc21 = "Stepper Motor 1 (GPIO 2-wire)"
Stpr_mtr_drv.DeviceDesc22 = "Stepper Motor 2 (GPIO 2-wire)"

Stepper Motor Device Name

For user mode program to access stepper motor device driver a simpler, old style method is used. Stepper motor device driver has device name StepMtrDevice, which is assigned during driver initialization step, as shown below:

#define DOS_DEVICE_NAMEW L"\\DosDevices\\Global\\StepMtrDevice"
…
DECLARE_CONST_UNICODE_STRING(dosDeviceName, DOS_DEVICE_NAMEW);
…
status = WdfDeviceCreateSymbolicLink(device, &dosDeviceName);
…

User mode program simply opens stepper motor device using the same Windows API as with any generic device. Other details of developing Windows KMDF device driver, not specific to stepper motor, are omitted in this article.

Delay between Pulses

Time delay based on stepper motor rotational speed is the place where things get interesting in Windows 8.

Windows 8 is not a real time operating system. Yet we are trying to use Windows 8 to perform real-time type of tasks, i.e. form every pulse for stepper motor and hope that it can be done with reasonable accuracy to avoid jerky motor operation.

Since our stepper motor device driver thread runs typically at PASSIVE level, it is subject to Windows 8 scheduler and therefore a minimum delay between iterations in the loop is the delay Windows 8 scheduler runs on, which is typically around 15 msec (this value is also called Windows 8 timer resolution). If so, and given that typical stepper motor has 200 steps per revolution, then maximum rotational speed for stepper motor will be only 19 rpm. While 19 rpm max rpm is still useful, it is not great, and let’s look for what can be done to increase maximum rpm.

For example, if we want stepper motor to rotate at 60 rpm, our GPIO pins should provide pulses of 5 msec wide. Time delay of 5 msec is shorter than the minimum 15 msec delay of Windows 8 scheduler and cannot be reached without an extra effort.

Windows 8, and Windows RT is not a real time operating system, so generally speaking timely accurate control of GPIO pins will not be deterministic. However, stepper motor is relatively slow type of hardware from Windows point of view, and it should be possible to provide reasonable level of accuracy for it using an option provided by Windows 8 kernel.

Specifically, kernel mode Windows 8 device driver can, for a limited period of time, change timer resolution to a smaller value. Doing so will allow for shorter delays in the driver, down to 1 msec, but also will run Windows 8 scheduler at higher frequency. The former is exactly what we need, while the latter can affect overall system performance and is recommended by Microsoft to be used only when it absolutely needed.

To keep both parties happy, our stepper motor device driver changes Windows 8 timer resolution only when stepper motor is rotating, assuming that the motor rotation duration will be limited. That should be fine for practical purpose, where stepper motor is not rotating continuously. The picture below illustrates this approach.

With this method (named B on the picture), we can achieve potential accuracy of 1 millisecond and thus maximum rotational speed of stepper motor of 300 pm, while keeping Windows 8 operating system fully functional and happy. Since Method B runs at APC level, we can use typical wait or delay functions for device drivers (just keep in mind that the value for Timeout is >=1 millisecond)

// note : we are at DISPATCH level and will not be preempted
// use Windows 8 specific delay function
// Starting with Windows 8, KeDelayExecutionThread uses a more precise technique to calculate the absolute expiration time

    status=KeDelayExecutionThread(KernelMode, FALSE, &liTimeout);    // call from <= APC_LVEL

One millisecond time resolution isn’t so exciting, but this method cannot break this barrier due to the need for scheduler to run since our driver to driver calls must take place, otherwise stepper motor driver will not be able to set GPIO pin state by calling into GPIO controller driver. This is fundamental limitation of Windows 8 operating system due to I/O Manager involvement.

The next step is to explore if we can go further and get control of stepper motor at finer level, i.e., at order of microseconds. This requirement means that we cannot use IRP, and we cannot use OS scheduler, yet we need operating system to run. While seemingly impossible, luckily in Windows 8 architecture there is a work around which allows to implement this for a limited period of time and still have Windows 8 alive.

The approach is then to run our motor controlling thread at DISPATCH level and thus effectively stop OS scheduler for some time, until our loop naturally runs to the end – when motor stops. At that moment, we immediately enable OS scheduler back to avoid OS crash, which is blue screen with code DPC_WATCHDOG_TIMEOUT.

Since our thread will run at higher IRQL we cannot call into GPIO controller driver, thus this method cannot be used on Windows RT; however on Windows 8 access to LPT1: control port is immediate and we will use it to get to microsecond accuracy.

The picture below demonstrates how stepper motor device driver can operate at microsecond accuracy (called method A).

Please note that implementation of Method A requires having a delay function, which can run at DISPATCH IRQL level, and therefore usual wait or delay functions popular for device drivers running at lower IRQL levels cannot be used. Instead we use spin CPU Windows kernel mode function, which can be used at any IRQL level and does exactly the job we need – and the time delay value must be >=1 microsecond.

&hellip;
// note : we are at DISPATCH level and will not be preempted
// thus our driver cannot use kernel wait or kernel delay functions
//
    KeStallExecutionProcessor(ulCurrentStepDelay_microsec);

Delaying operating system scheduler is a dangerous way of doing things under Windows 8. We can reduce the risk of DPC _WATCHDOG_TIMEOUT crash by relocating our stepper motor control thread to some other, higher number processor or processor core, if it is available. Here is how this task is accomplished in driver code.

------------------

kafOurNewAf=(KAFFINITY)(1<<ProcNumber.Number);
// before we launch the loop in this thread,
// must move it to the last processor core to minimize impact on operating system
 kafPrev=KeSetSystemAffinityThreadEx(kafOurNewAf);

    
---------------------

On a four-processor test platform this method worked fine, and blue screen crashes did not occur. However, during stepper motor run entire OS becomes unresponsive to a user, which is expected.

WARNING: This stepper motor driver using Method A is highly experimental. While it does demonstrate microsecond accuracy for limited period of time, it may crash your Windows 8. You have been warned.

Our next step is to test both methods.

Simple Stepper Motor Device Driver Test

The focus of this article is to investigate timing accuracy of stepper motor control. Therefore details about loading and installing stepper motor device driver, not directly related to main goal, are omitted here.

After stepper motor Windows device driver is built, the first test is to see if Windows will load it properly. Once this step is passed, Windows device manager shows stepper motor with its device driver state as “working properly”, as shown below.

The next step is to access stepper motor device as StepMtrDevice from simplest command line test application using Windows API to check if device driver is communicating with user mode test program and further if stepper motor will turn.

#define DOS_DEV_NAMET  _T("\\\\.\\StepMtrDevice")
&hellip;
hDrv=CreateFile(DOS_DEV_NAMET, GENERIC_WRITE | GENERIC_READ,
                FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);

Test program takes up to three arguments: speed, number of steps, and optionally wiring mode (4 or 2 wire).

A screen shot demonstrates our simple test command line program is able to control stepper motor, in the example it sets rotational speed to 30 rpm and checks back from the driver what rpm it measured.

Stepper Motor Advanced Test .NET application

On Microsoft .NET Framework 4.5 was used to develop stepper motor test program. It is a simple Windows Form application, talking to stepper motor device driver to control stepper motor. In addition to sending commands to control stepper motor such as set speed (shown on slider control named “Set rpm”), starting and stopping motor, this form uses feedback capability to periodically poll stepper motor device driver to display measured rpm ( shown on progress bar “Driver Reported rpm”).

Windows form is shown on screen shot below:

It can be built for any Windows 8 platforms.

On Windows RT, where stepper motor device driver runs at APC level, this Windows Form can get feedback from driver to display actual motor RPM.

On Windows 8 x86/X64 platform, stepper motor device driver runs at DISPATCH level, and therefore rpm measured by stepper motor driver are not accurate.

Windows Stepper Motor Control: Speed and Accuracy

Method B Test – 1 millisecond resolution, 192 rpm max, Windows friendly

As expected, method B keeps operating scheduler happy but runs it at higher rate of every 1 millisecond and limits stepper motor speed to theoretical maximum 300 rpm. In practice, due to an overhead of this method, the upper speed limit is 192 rpm.

While this method B generally works reliably and is Windows 8 friendly, operation of stepper motor using this method is jerky. The lower rotational speed is, the smoother stepper motor runs with this approach.

The chart below shows test results when stepper motor speed was set to 50 rpm and motor turned for 45 steps, and while rotational speed was mostly at near set level and was steady, there is considerable number of drop out steps.

It happens due to preemption of our stepper motor control thread, especially seen at higher end of rpm range. Here is scope measurement example for test run at 72 rpm, where jitter can be seen.

Method A Test – 1 microsecond resolution, high rpm and very fine control, but risky

This approach provides consistent and stable timing at microsecond level of accuracy, thus allowing very fine control of stepper motor directly from Windows 8 and very high rotational speed. The down sides of this method are that if we run stepper motor longer than several seconds, likelihood of operating system deterioration increases, and user application becomes unresponsive during motor run.

Due to Windows RT architecture limitation, Method A was tested only on x86/X64 platform using LPT1: controls pins as of GPIO pins.

In four-core test platform used for this test operating system never crashed, though display driver did encounter difficulty and was restarted on one occasion.

The results below demonstrate accurate timing and very stable rotational speed of Method A. After initial ramp up stepper motor control thread retains ± one(1) microsecond accuracy, and easily reaches high rotational speed of stepper motor. A screen shot of a test at 400 rpm making four full turns, i.e. 800 steps, is shown below:

Much higher rotational speeds are now easily achievable with Method A; I did not have a stepper motor at hand capable of testing these higher rpm. Software only tests were performed; one example is below for 9000 rpm.

Stpr_mtr_drv    4316    1160    0    39897    08\19\2014-13:09:16:405    Stpr_Mtr_DeviceIoControl() set motor mode
Stpr_mtr_drv    4316    1160    0    39899    08\19\2014-13:09:16:405    Stpr_Mtr_DeviceIoControl() new RPM=9000
Stpr_mtr_drv    4316    1160    0    39900    08\19\2014-13:09:16:405    ulMicroSecDelayFromRPM(RPM=9000) delay=33 microsec
Stpr_mtr_drv    4316    1160    0    39901    08\19\2014-13:09:16:405    Stpr_Mtr_DeviceIoControl() new pulse delay=33 microsec
Stpr_mtr_drv    4316    1160    0    39902    08\19\2014-13:09:16:406    Stpr_Mtr_DeviceIoControl() motor turn
Stpr_mtr_drv    4316    1160    0    39903    08\19\2014-13:09:16:406    Stpr_Mtr_DeviceIoControl() rotate RIGHT 750 steps
Stpr_mtr_drv    4    5228    3    39904    08\19\2014-13:09:16:419    Kthrd3 START rotating.
Stpr_mtr_drv    4    5228    3    39905    08\19\2014-13:09:16:419    wrkr_Kthrd3 changed IRQL=2 up !
Stpr_mtr_drv    4    5228    3    39906    08\19\2014-13:09:16:419    Kthrd3 Pulse 749: set=33 us, meas=15686 us, RPM=19
Stpr_mtr_drv    4    5228    3    39907    08\19\2014-13:09:16:419    Kthrd3 Pulse 748: set=33 us, meas=52 us, RPM=5769
Stpr_mtr_drv    4    5228    3    39908    08\19\2014-13:09:16:419    Kthrd3 Pulse 747: set=33 us, meas=34 us, RPM=8823
Stpr_mtr_drv    4    5228    3    39909    08\19\2014-13:09:16:419    Kthrd3 Pulse 746: set=33 us, meas=33 us, RPM=9090
Stpr_mtr_drv    4    5228    3    39910    08\19\2014-13:09:16:419    Kthrd3 Pulse 745: set=33 us, meas=34 us, RPM=8823
Stpr_mtr_drv    4    5228    3    39911    08\19\2014-13:09:16:419    Kthrd3 Pulse 744: set=33 us, meas=34 us, RPM=8823
Stpr_mtr_drv    4    5228    3    39912    08\19\2014-13:09:16:419    Kthrd3 Pulse 743: set=33 us, meas=33 us, RPM=9090
Stpr_mtr_drv    4    5228    3    39913    08\19\2014-13:09:16:419    Kthrd3 Pulse 742: set=33 us, meas=34 us, RPM=8823
Stpr_mtr_drv    4    5228    3    39914    08\19\2014-13:09:16:419    Kthrd3 Pulse 741: set=33 us, meas=33 us, RPM=9090
Stpr_mtr_drv    4    5228    3    39915    08\19\2014-13:09:16:419    Kthrd3 Pulse 740: set=33 us, meas=33 us, RPM=9090
Stpr_mtr_drv    4    5228    3    39916    08\19\2014-13:09:16:419    Kthrd3 Pulse 739: set=33 us, meas=33 us, RPM=9090
Stpr_mtr_drv    4    5228    3    39917    08\19\2014-13:09:16:419    Kthrd3 Pulse 738: set=33 us, meas=33 us, RPM=9090
Stpr_mtr_drv    4    5228    3    39918    08\19\2014-13:09:16:419    Kthrd3 Pulse 737: set=33 us, meas=33 us, RPM=9090
Stpr_mtr_drv    4    5228    3    39919    08\19\2014-13:09:16:419    Kthrd3 Pulse 736: set=33 us, meas=33 us, RPM=9090
Stpr_mtr_drv    4    5228    3    39920    08\19\2014-13:09:16:419    Kthrd3 Pulse 735: set=33 us, meas=33 us, RPM=9090
Stpr_mtr_drv    4    5228    3    39921    08\19\2014-13:09:16:419    Kthrd3 Pulse 734: set=33 us, meas=33 us, RPM=9090
Stpr_mtr_drv    4    5228    3    39922    08\19\2014-13:09:16:419    Kthrd3 Pulse 733: set=33 us, meas=33 us, RPM=9090
Stpr_mtr_drv    4    5228    3    39923    08\19\2014-13:09:16:419    Kthrd3 Pulse 732: set=33 us, meas=33 us, RPM=9090
Stpr_mtr_drv    4    5228    3    39924    08\19\2014-13:09:16:419    Kthrd3 Pulse 731: set=33 us, meas=33 us, RPM=9090
Stpr_mtr_drv    4    5228    3    39925    08\19\2014-13:09:16:419    Kthrd3 Pulse 730: set=33 us, meas=33 us, RPM=9090
Stpr_mtr_drv    4    5228    3    39926    08\19\2014-13:09:16:420    Kthrd3 Pulse 729: set=33 us, meas=33 us, RPM=9090
Stpr_mtr_drv    4    5228    3    39927    08\19\2014-13:09:16:420    Kthrd3 Pulse 728: set=33 us, meas=33 us, RPM=9090

With reliable microsecond level of accuracy of stepper motor control now proven, other fine features of stepper motor control become available and can be taken advantage of, such introducing smooth acceleration and deceleration within Windows 8 device driver. The chart below shows an example of smooth acceleration of NEMA17 with 4 kg*cm torque stepper motor to 300 rpm, holding steady at that speed and then smooth deceleration of it - all done reliably solely by Windows 8 device driver

The stepper motor Windows 8 device driver included with this article has built in support for acceleration-deceleration mode of operation; default mode is straight speed, non-accelerated.

Points of Interest

This project demonstrated that, with some limitations, Windows 8 can successfully control stepper motor directly to a fine degree of time accuracy of one(1) microsecond. This opens up a possibility to create equipment based on Windows 8 to control stepper motor(s) directly and eliminate the need to involve microcontrollers.

Revision History

Developed for Windows 8 x86/X64 and Windows RT

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

Share

About the Author

SergeiR[MCTS]

United States United States
No Biography provided

Comments and Discussions

 
GeneralMy Vote of 5 PinmemberZain Ul Abidin30-Aug-14 8:58 
GeneralMy vote of 5 Pinmembersam.hill29-Aug-14 4:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.1411019.1 | Last Updated 29 Aug 2014
Article Copyright 2014 by SergeiR[MCTS]
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid