loading

Serial is one of the easiest forms of hardware communication. It’s not the only way to communicate between two devices, but it offers several advantages over other methods; mainly, it can be done over a standard USB connection via the TinyShield USB. In this tutorial I’ll detail the process of creating a WinForms application and Tinyduino sketch to achieve unidirectional serial communication to adjust the speed of two motors.

For this project you will require a Windows computer with Visual Studio (VS Community 2015 or higher recommended), a Tinyduino, and a Dual Motor Tinyshield. The WinForms application is written in C# and will display a list of available serial ports as well as the speed controls for each motor. We will make use of event driven programming on both sides of the serial bridge making the serial protocol lightweight.

Step 1: Resources

Before we can start assembling our TinyDuino or writing the code to make it all work we need to make sure we have all our dependencies in order. Make sure you have the latest versions of the software and utilities below.

Step 2: Materials and Hardware

The hardware setup for this project is relatively simple, but the TinyShield system makes it almost like electronics Legos.

For this project we will need 3 TinyCircuits modules:

  • The TinyDuino
  • The USB TinyShield
  • The Dual Motor Controller TinyShield

As well as four or five other generic components, Including at least:

  • A USB cable
  • A battery or other power source
  • At least one motor with 0.1” pitch connectors

In this case my power source will be a 9V battery with a 5V UBEC regulator. Additionally, it is recommended you use a motor that draws less than 0.5A at 5V unless you add some form of heat management to the motor controller.

Step 3: Assembly

To set up the TinyDuino simply stack the three modules on top of each other using the board interconnects. In my setup I placed some electrical tape on the bottom of the motor controller to protect the through-hole components from shorting out on the USB board below. Once the boards are connected, connect the power source and motors to the specified connectors.

If your power source does not have an on/off switch do not connect it until you’re ready to power your motors. Make sure to connect the power source with the proper polarity and do not short circuit the motor ports. I only used one motor for this set up however the code provided will allow both motors to work out of the box.

Step 4: Test the Hardware Set Up

Let’s test our setup to ensure everything works with the example code included in the TinyDuino motor controller library.

Step 5: Serial Protocol

Serial Protocol

Before we get into coding anything we need to decide on what kind of serial protocol we want to use. It’s fine to use a plain text protocol where we transmit plain text back and forth between our TinyDuino and the computer but computers don't natively understand english and the code can become complex for handling more and more advanced plain text transmissions.

For the sake of simple code and ultra fast serial transmission we should use something the arduino and computer speak very fluently, that being raw bytes. A byte is 8 bits, and since serial communication is a stream of bits, bytes can be sent very efficiently over the connection.

For comparison if we used a plain text message of “Motor Control Message, Motor1: 255, Motor2: 255” we have transmitted 47 bytes of data, 44 of those bytes do not contain any useful information and simply take up serial time. This isn’t a major issue if you are using a high baud rate like 9600.

However, if you are using a slow baud rate, say 50 (for some high efficiency device), the difference between 47 bytes and 3 is extreme, almost a 93% difference. In either case, for the sake of simplicity and efficiency we should stick to transmitting raw data rather than plain text.

A simple protocol we can use is 3 bytes long, containing 3 pieces of information. Since we will transmit our bytes as a one dimensional array we can use a 3 byte list to visualize our message:

  1. Message Type (byte 0)
  2. Motor 1 Speed (byte 1)
  3. Motor 2 speed (byte 2)

The latter two bytes are self explanatory, however you may wonder why we include a “Message Type” byte. This is for two reasons, it allows us both to expand our protocol later on to include more commands for additional features as well as verify our message is valid and not a fragment of a previous message.

Step 6: Tinyduino Sketch

NOTE: Before we get started make sure you do not have another “MotorDriver.h” library installed on your Arduino IDE because this will prevent the IDE from compiling due to the ambiguity between the two “MotorDriver.h” files. (Known Library Conflicts: Seeed Studio Motor Driver)

#include <wire.h>
#include <MotorDriver.h>
 
MotorDriver motor(0);//value passed is the address- remove resistor R1 for 1, R2 for 2, R1 and R2 for 3
 
int maxPWM=255; // sets our max PWM to the largest byte value we could receive
 
void setup()
{
  Serial.begin(9600);
  Wire.begin();
 
  //The value passed to begin() is the maximum PWM value, which is 16 bit(up to 65535)
  //This value also determines the output frequency- by default, 8MHz divided by the           maxPWM value
  if(motor.begin(maxPWM))
  {
    Serial.println("Motor driver not detected!");
    while(1);
  }
  motor.setMotor(1, 0);
  motor.setMotor(2, 0);
}
 
void loop()
{
   //Because we don't need to do anything until we receive data from our serial connection leave loop blank
}
 
void serialEvent()
{
  delay(10);
  byte message[3];
  if(Serial.available())
  {
    Serial.readBytes(message, 3);
  }
 
  if (message[0] == 2) //if our message is signed as type two then run our motor update code
  {
    motor.setMotor(1, message[1]);
    motor.setMotor(2, message[2]);
  }
  Serial.flush(); // remove any redundant messages we may have received since we started updating our motors speeds
}

This code is relatively simple, the only concept to understand is how the “serialEvent()” handler works. The “serialEvent()” code is called after the “loop()” every time there is serial data available in the buffer. It is important to know that a “delay()” function inside the loop will also delay the “serialEvent()”. With that in mind it’s a good idea to keep your “loop()” as fast as possible, avoiding delays when possible.

Step 7: WinForms Setup

What's awesome about Visual Studio is, well, it's visual nature. As far as the GUI of our Windows application is concerned from here on in it’s drag and drop. Visual Studio Community 2015 is a wickedly powerful FREE development environment.

  1. To get started first launch Visual Studio and create a new project with the “New Project” button on the start page
  2. Once the New Project dialog is open under templates select Visual C# and “Windows Forms Application” as shown
  3. Give your application a name
  4. Visual Studio will now create the project. This can take a few minutes

Step 8: WinForms Design

Now that we have a form open in the Visual Studio designer it’s a good time to rename our form. In the properties pane on the bottom right hand side, with the blank form selected we can assign both a “Name” and a “Text” header to our window. Note that many but not all Visual Studio objects have both a name AND a header and you must set both for your app to behave and appear properly. The “Name” is used to reference the object in code and the “Text” header is simply visual.

Once our form is named and a proper header is applied we can set to work dropping our controls onto the form. On the left hand side of the Visual Studio window you will see a tab named “Toolbox”. This pane contains all the controls and objects available with VS.

To place a control on the page click on it in the toolbox then click and drag on your form to place the object. First add a “GroupBox” to the top of the form. Then inside the GroupBox add a “ComboBox”. The GroupBox is a container and the ComboBox (so long as the container actually contains it) will move with it, making rearranging our forum a delight.

For the time being we will change the header of the “GroupBox” to “Com Port” and the name of the “ComboBox” to “cbComPortChooser” we append “cb” to the front of the name because the control is a ComboBox, making it easier to find in the code later on. It is critical for the “ComboBox” to have a name as that's how we will access it in our code.

Once we have our ComboBox set up for choosing which COM port we wish to talk to we can place our sliders to control the speed of the two motors attached to the TinyDuino.

Place another group box and change its header to “Motor 1” and inside it place a TrackBar object. This will be our speed control slider. Name the TrackBar “tbMotorOne”. Finally adjust the maximum property to 255, the tick frequency property to 25, and the small change property to 25.

Copy the GroupBox we just created to create a duplicate of the control for motor two. change the header of the group box to “Motor 2” and Name the second TrackBar “tbMotorTwo”.

Step 9: Populate the ComboBox (Step 1 and 2)

In order to populate the ComboBox we placed first with available serial COM ports we must create an event that fires once the form is fully loaded. To do this simply select the full form by clicking directly on it and clicking the small lighting icon in the properties pane. This will open up the available events for this form.

Next to the event titled Load type the name of the event handler. In this case type “OnWindowLoad” and hit enter. Visual studio will create the event handler in the code behind of our project and load it right away.

Now that we have our form designed and the first event handler set up we will start working on the code to populate your ComboBox.

The first step is to populate your ComboBox with COM ports that have a serial connections available on them. Since the TinyDuino uses serial to communicate with our computer this should make it easier to find the right port.
To do this it will take four steps:

  1. Create a new class to contain the serial ports
  2. Populate an array of strings with the available serial ports
  3. Convert that array into a list of objects
  4. Display that list in our combo box for the user to chose the right port

Step one is probably the most complicated part. We will define a new class to represent our serial ports. this is the code to do that:

public class Port
        {
            public string Name { get; set; } // defines a property called Name
            public int Value { get; set; } // defines a property called Value
            public Port(string n, int i) // A constructor to make adding ports easier
            {
                Name = n;
                Value = i;
            }
        }

This block of code can be added anywhere inside “namespace MotorController” block in Form1.cs.

Step two we will populate an array of strings with the available serial ports this is the code to do that:

string[] avalablePorts = SerialPort.GetPortNames();

This is very simple, all we do is create a new array of strings called avalablePorts and populate it with the “SerialPort.GetPortNames()” function. You will also want to add the namespace System.IO.Ports to your project by entering “using System.IO.Ports;” to the top of your project. This will ensure your code has access to the serial communications library within windows.

Step 10: Populate the ComboBox (Step 2 and 3)

Step three is converting the strings from the array to the Port class. To do this we will loop through the array and create a new instance of Port for each, assigning the string to the name and a incremental index as the value. Here is the code we will put in our program initialization:

if (avalablePorts.Length > 0)
            {
                //create a list of objects to fill the combo box with
                List<Port> portsList = new List<Port>();
                for (int i = 0; i < avalablePorts.Length; i++)
                {
                    portsList.Add(new Port(avalablePorts[i], i));
                }

Notice the “if” statement. There is no point in trying to populate the ComboBox with anything unless we have a port to populate it with.
And finally we will update and display the ComboBox:

//populate the combo box with ports
                cbComPortChooser.DataSource = portsList;
                cbComPortChooser.DisplayMember = "Name";
                cbComPortChooser.ValueMember = "Value";
            }

Step 11: Test the ComboBox

At this point the entire code behind should look something like this:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;
 
namespace MotorController
{
    public partial class MotorRemote : Form
    {
        //create serial port singleton
        SerialPort serial = new SerialPort();
        //declare a new class that will populate our combobox
        public class Port
        {
            public string Name { get; set; }
            public int Value { get; set; }
            public Port(string n, int i)
            {
                Name = n;
                Value = i;
            }
        }
 
        public MotorRemote()
        {
            InitializeComponent();
        }
 
        private void OnWindowLoad(object sender, EventArgs e)
        {
            //populate combobox with available serial ports
            string[] avalablePorts = SerialPort.GetPortNames();
            if (avalablePorts.Length > 0)
            {
                //create a list of objects to fill the combo box with
                List<Port> portsList = new List<Port>();
                for (int i = 0; i < avalablePorts.Length; i++)
                {
                    portsList.Add(new Port(avalablePorts[i], i));
                }
 
                //populate the combo box with ports
                cbComPortChooser.DataSource = portsList;
                cbComPortChooser.DisplayMember = "Name";
                cbComPortChooser.ValueMember = "Value";
            }
        }
    }
}

and if you run the program the DropDown on the ComboBox should allow you to chose from the available serial ports. If the drop down appears empty make sure your Tinyduino or other serial device is plugged in and re-launch the application.

Step 12: Sending the Serial Message

Now that we have the ComboBox populated with available serial ports the next step to get our program talking to the Tinyduino will be to add a function that sends a serial message to the TinyDuino and updates the two motors speeds. Our serial protocol is expandable but for now will only include three bytes. The first of which is an info packet on the type of message we are sending.

First thing first, define an empty function called “UpdateMotors()” and include a constant byte called “messageType” like this:

void UpdateMotors()
        {
            //message type constant
            const byte messageType = 2;
        }

When defined like this visual studio will convert the int number 2 implicitly to a byte.
Next we will read the values of our sliders and assign them to their own byte variables:

void UpdateMotors()
        {
            //message type constant
            const byte messageType = 2;
 
            //read the sliders
                //motor 1
            byte motorOneSpeed = (byte)tbMotorOne.Value;
                //motor 2
            byte motorTwoSpeed = (byte)tbMotorTwo.Value;
       }

When defined like this however Visual Studio requires a cast between the return value of tbMotor.Value and a byte.
Now with the three bytes created we will fill a byte array as a buffer to send our message, and of course, send the message itself:

void UpdateMotors()
        {
            //message type constant
            const byte messageType = 2;
 
            //read the sliders
                //motor 1
            byte motorOneSpeed = (byte)tbMotorOne.Value;
                //motor 2
            byte motorTwoSpeed = (byte)tbMotorTwo.Value;
 
            //create and fill a array of bytes as our message
            byte[] serialMessage = {messageType, motorOneSpeed, motorTwoSpeed};
 
            //send Message
            serial.Write(serialMessage, 0, serialMessage.Length);
        }

Notice serial.Write(); takes three arguments: the message itself as a byte array, an int representing the first byte to send, and a second int as the size of the entire message.

Step 13: Finishing Touches

To finish our application now we need only add three event handlers, one for each slider that will call the “UpdateMotors()” function every time the value of the slider is changed and one to set up the COM port. To do this go back to the designer and select the first slider and locate the Scroll event from the list and enter “SliderOneChange” and hit enter. In the newly created event handler add this line to make the slider send an update message to the TinyDuino: “UpdateMotors();”.

Do the same for the second slider typing “SliderTwoChange” rather than “SliderOneChange” this time.

Finally add an event handler under the SelectedIndexChanged event to the “ComboBox” called “OnComChange” and add this code:

private void OnComChange(object sender, EventArgs e)
        {
            if (serial != null && serial.IsOpen)
            {
                serial.Close();
            }
            Port port = (Port)cbComPortChooser.SelectedItem;
            serial = new SerialPort(port.Name);
            serial.BaudRate = 9600;
            if (!serial.IsOpen)
            {
                serial.Open();
            }
        }

This code will allow us to change what com port we are transmitting data to once the program has started.


Please Note: This application is unstable, you can add error handling to make it stable. Without error handling please follow this procedure for best results:

  1. Plug in TinyDuino
  2. Start WinForms application
  3. Select the comport the TinyDuino occupies
  4. ONLY THEN change the sliders

Step 14: Final Thoughts and Source Download

The entire code behind should look something like this:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;
 
namespace MotorController
{
    public partial class MotorRemote : Form
    {
        //create serial port singleton
        SerialPort serial;
        //decalre a new class that will populate our combo box
        public class Port
        {
            public string Name { get; set; }
            public int Value { get; set; }
            public Port(string n, int i)
            {
                Name = n;
                Value = i;
            }
        }
 
 
        public MotorRemote()
        {
            InitializeComponent();
        }
 
        private void OnWindowLoad(object sender, EventArgs e)
        {
            //populate combo box with avalable serial ports
            string[] avalablePorts = SerialPort.GetPortNames();
            if (avalablePorts.Length > 0)
            {
                //create a list of objects to fill the combo box with
                List<Port> portsList = new List<Port>();
                for (int i = 0; i < avalablePorts.Length; i++)
                {
                    portsList.Add(new Port(avalablePorts[i], i));
                }
 
                //populate the combo box with ports
                cbComPortChooser.DataSource = portsList;
                cbComPortChooser.DisplayMember = "Name";
                cbComPortChooser.ValueMember = "Value";
            }
        }
 
        void UpdateMotors()
        {
            //message size constent
            const byte messageType = 2;
 
            //read the sliders
                //motor 1
            byte motorOneSpeed = (byte)tbMotorOne.Value;
                //motor 2
            byte motorTwoSpeed = (byte)tbMotorTwo.Value;
 
            //create and fill a array of bytes as our message
            byte[] serialMessage = {messageType, motorOneSpeed, motorTwoSpeed};
 
            //send Message
            serial.Write(serialMessage, 0, serialMessage.Length);
        }
 
        private void SliderOneChange(object sender, EventArgs e)
        {
            UpdateMotors();
        }
 
        private void SliderTwoChange(object sender, EventArgs e)
        {
            UpdateMotors();
        }
 
        private void OnComChange(object sender, EventArgs e)
        {
            if (serial != null && serial.IsOpen)
            {
                serial.Close();
            }
            Port port = (Port)cbComPortChooser.SelectedItem;
            serial = new SerialPort(port.Name);
            serial.BaudRate = 9600;
            if (!serial.IsOpen)
            {
                serial.Open();
            }
        }
 
        private void OnClose(object sender, FormClosedEventArgs e)
        {
            serial.Close();
        }
    }
}

Notice: The OnClose() event is not required on all computers. However it prevents the application from leaving an orphaned COM port when the user closes it. Without it is it possible you will have to restart your computer to regain access to that port. To add the OnClose event create an event handler off the main form like you did for the OnWindowLoad event this time using the FormClosed event rather than the Load event. Inside the handler add serial.Close(); to close any open serial connection.

If you accidentally create additional event handlers you don't end up using don't worry too much. They will not affect the function of the application and can be removed by clicking on the associated control and removing the event handler from the lightning bolt menu.

Winforms Project Download: https://goo.gl/rop4zl

<p>Very nicely done, thanks for sharing this!</p>

About This Instructable

612views

8favorites

License:

Bio: My name is Logan Cooper and I am a professional documentation author available for hire. I create custom tutorials for all things technical with an ... More »
More by H2o2go:Getting Started With Indexed 4th Axis Milling Getting Started With DeskProto The Tinypkin (Tiny LED Pumpkin) 
Add instructable to: