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

Using some resources, timers and COM ports in wxWidgets

, 4 Sep 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
wxWidgets resources examples and COM port communication.

Download wxlConsole.zip

Introduction

In this article I will explain how to put some basic resources in a window to interact with the user, like buttons, text fields and combo boxes.

As a bonus I will add a simple COM port library using a very basic one line terminal application as example. 

I will explaine how to use wxWidget evente-based timers to automatize some actions like read the serial imput buffer and display the information in the text field.

The idea is to go step by step explaining the code for any object separately, starting with the layout of the window and adding the diferent functions and necessary code progressively.

Background

The examples of this article, and the code asociated, was maked with Code::Blocks using wxWIdgets 3.0.1. You can see how to download and compile it in this article:

Introduction-to-wxWidgets-GUI-programming-with-wxSmith

The serial communication is implemented with a simple library you can find in this article with a brief description of how to use it, I explain how to use it in this article also

Simple-Serial-communication-utilities

but instead of the list ports library used there I will use here another way to enumerate the ports.

Don't worry, I will explain anyn aspect in detail.

The code

The better way to start is for the beginning, so open the Code::Blocks ide and create a new wxWidgets project. In the first article referenced in the Background section you can find a guide to create a wxProject in C::B. Follow that steps to create the new project.

The first screen you see when create a new wxWidgets project is this:

This is the resource panel. Here we can drag and drop controls and resources we want to build the screen.

The Layout

First of all we will create the layout. So we will add some objects to the screen. But first we have to some things to spaciate and organize the layout. First add a wxBoxSizer. Sizers make the job of organize the elements in the screen for us. Then add a wxPanel inside the BoxSizer. Now add a wxGridSizer inside the panel. For more references about doing this you can see the article referenced before.

The wxGridSizer let us add elements in a matrix instead in a line as in the BoxSizer. If you select the GridSizer from the left resources tree you will see its properties in the properties menu below the tree. In the GridSizer properties you can set the row and column number.

The example consists in a list of COM ports available where you can choose wich one to connect, a CONNECT button, a text field to  write the message you want to send with the SEND button and a text field to show the received messages. For simplicity I made the received field in only one line, buut is easy to make it multiline setting the correct propertie in the textfield properties.

Once we have this set of sizers in the screen we will proceed to build the screen. The goal screen is this:

As you can see in the GridSizer's properties I especified 3 rows and 2 columns. 

Now we have to add the objects. That is doing by clicking on the object in the object bar and then clicking on the diagram. First click on the wxComboBox icon in the Standar bar and then click in the little square that we have as our screen. Now we have the future COM ports list. Clicking on it and in the properties list in the left we can see some attributes we can change. The principal attribute to change is the object name so we can refer to it and make the code more maintenable.

As you can see in the screenshot, I changed the var name to comSel and the Identifier to ID_COMSEL. 

Then add the button on the right of the comboBox, change it's Var Name to conBut and the label to Connect

Now add a wxTextCtrl. I changed it's Var name to mesBox, the Label to Message and the Identifier to ID_MESBOX. 

Add another wxButton. Label: Send, Var name: sendBut and Identifier: ID_SENDBUT.

Now add a wxStaticText, Label Received, Var name: recText and Identifier: ID_RECTEXT

Finally add a wxTextCtrl. Label: nothing because here we will display the incomming message, Var name: recBox and Identifier: ID_RECBOX. We will define it as read only setting this propertie in the style option in the propertie editor:

Remember that later when we define the functions to interact with the elements in the screen, that functions will be referenced to the var name of the objects.

Now you can build the project to see if it is working. If every thing is ok we have something like this:

But of course this doesn't have any functionality. So lets add some code to make it work.

Serial port enumerator

The available serial port enumeration is making through the Find_Comm function declared and implemented in portEnum.h and portsEnum.cpp respectively. See the code:

#include <string>
#include "portsEnum.h"
#include "windows.h"

using namespace std;

int Find_Comm(string* ports)
{
    //string ports[5];
    HANDLE DriverHandle;
    DWORD    LastError[4];
    char Com_Name[10];

    int portsCant = 0;

    for(int x = 1 ; x < 10 ; x++)
    {
        switch(x)
        {
            case 1:
                strcpy(Com_Name,"COM1");
                break;
            case 2:
                strcpy(Com_Name,"COM2");
                break;
            case 3:
                strcpy(Com_Name,"COM3");
                break;
            case 4:
                strcpy(Com_Name,"COM4");
                break;
            case 5:
                strcpy(Com_Name,"COM5");
                break;
            case 6:
                strcpy(Com_Name,"COM6");
                break;
            case 7:
                strcpy(Com_Name,"COM7");
                break;
            case 8:
                strcpy(Com_Name,"COM8");
                break;
            case 9:
                strcpy(Com_Name,"COM9");
                break;
        }

        DriverHandle = CreateFile (Com_Name, 0, 0, NULL, OPEN_EXISTING, 0, NULL);

        LastError[x-1] = GetLastError();

        if(LastError[x-1] == 0)
        {
            ports[portsCant]=Com_Name;
            portsCant++;
        }

        CloseHandle(DriverHandle);

    }
    return portsCant;
}

I remarked the two important variables. ports, the estring vector that contains the name of the ports availables and portscant the number of available ports.

To use it in our code first I declare two variables in the wxlConsoleFrame class declaration in wxlConsoleMain.h file, comports and port_nr. I declared the variables there to have them available in the functions of the frame object.

class wxlConsoleFrame: public wxFrame
{
    public:

        string comports[10];
        int port_nr;

Very important: never modify the code between //(* and //*). This code is automatically generated and modified by wxWidgets and the IDE.

Then in wxlConsoleMain.cpp inside the constructor of wxlConsoleFramewxlConsoleFrame::wxlConsoleFrame(wxWindow* parent,wxWindowID id), we have to call to the port enumerator function so when the frame is created we already have the ports listed:

 port_nr = 0; // Port number initialize    

 int portnum; // Number of available ports

 portnum = Find_Comm(comports); // Call to the ports finder function

 for(int i=0; i<portnum; i++)
 {
     comSel->Append(comports[i]); // Showing the available ports in the comSel combo box

 }

Find_comm function ask for an string pointer as parameter and to give the ports in and return the port cuantity as an int. 

The most important thing here is the first interaction with a GUI object inside the for cicle. There we fill the combo box comSel with the available ports through the wxComboBox member function Append(string). Every call to Append adds a new string option to the list of the combo box.

We can build the project and verify if this is working. We have a list of available ports, now we have to choose one and connect to it.

Selecting and opening the serial port

We want to the user can select a port of the list and connect to it when pressing the Connect bbutton. Whe the user selects an option of the combo box an event is shooted, but we wont use it in this case because the will bbe maked when the connect button is pressed. So we have to define waht will occur when the connect button is pressed.

Macking double click on the connect button in the resource view (wxlConsoleFrame.wxs) we access directly to the wxlConsoleFrame::OnconButClick event function in wxlConsoleMain.cpp (maybe you have to scroll down to find it). Inside this function we can define what actions occurr when the button is pressed. This actions will be take the selected string (the selected port) from the combo box and open the port. Look that when you double-click on the button to generate the event automatically generates the function declaration in the wxlConsoleFrame class in wxlConsoleMain.h file.

void wxlConsoleFrame::OnconButClick(wxCommandEvent& event)
{
    wxString comSelected = comSel->GetStringSelection(); // Get the selected string form the combo box
    int port_aux = 0;

    if(comSelected.size() > 1) // If the selection is not void
    {
        port_aux = (int) comSelected[3] - 48;  // The string is in the form 'COMn'
        if(port_nr == 0) 
        {
            if(!RS232_OpenComport((port_aux-1), baudrate)) // If the port can be open
            {
                port_nr = port_aux;                        // Store the port number
                mesBox->SetValue((char)(port_aux+48));     // Show the port value in the receive box
            }
        }
    }

}

First get the selected string with the combo box member function GetStringSelected(). This return a wxString containing the text of the selection in the combobox. Combo boxes have another member function wich returns the ordering number of the selection.

Then check if the selection is not void and get the port number. The strings in the combo box are in the form 'COMn' where 'n' is the port number, thats the razon to take the fourth element of the string, cast it to int and subtract 48 ('0' in ascii).

The condition post_nr==0 is to check if no ports were opened previously. I use it in other parts of the code for the same utility.

RS232_OpenComport((port_aux-1), baudrate))

returns 0 if the port was succefuly opened. baudrate is declared as a member of wxlConsoleFrame class in wxlConsoleMain.h file and initialized to 9600 in the constructor.

If the port could be opened store the port number and show it in the instead receive message box to informate that it was opened using the TexCtrl member function setValue(string). You can append a message like "port n succefuly opened" using AppendText(string) instead:

mesBox->SetValue("Port ");

mesBox->AppendText((char)(port_aux+48));

mesBox->AppendText(" succefuly.");

or using formatted print:

wxString auxSt;
auxSt.Printf("Port %d opened", port_nr);
recBox->SetValue(auxSt);

Sending a message to the selected port

Once opened the port we want to communnicate with it. We have to get the text writed to the sending message text field and write it to the port when the send button is pressed. The action is maked when the button is pressed so we have to write the code in the send button event function.

Double clicking on the send button in the GUI view (wxlConsoleFrame.wxs) take us to wxlConsoleFrame::OnsendButClick on wxlConsoleMain.cpp (again maybe you have to scroll down to find it). Here we will add the code to get the text in the text field and write it to the port.

void wxlConsoleFrame::OnsendButClick(wxCommandEvent& event)
{
    wxString sendMesg = mesBox->GetValue();  // Get the text from the text field in the window
    string sended;

    sended = sendMesg.ToStdString();  // Convert to standard C++ string
    if(port_nr > 0)
        RS232_SendBuf(port_nr-1,(char*)sended.c_str(),sended.size());//Write the text in the port

}

First we get the text writed by the user in the mesBox TextCtrl using the member function GetValue() and asign it to an auxiliar wxString object.

Because the RS232 librarie is writen in C we have to give it a classic C style string (a char*) as parameter so we have first to convert the wxString to a standard C++ string and then pass to RS232_SendBuf the char* casted c_str() value.

Note that before sending the message the program check if the port is opened.

Receiving and showing the incomming message

In the other side of the communication we want to show the received message. We can do it with a button, or in the same event of the send button push. But maybe we connect to the PC a device wich emmit messages by its own and we want to show the messages automaticale when they arrive. As the RS232 library is not event driven we have to automatize the message polling. To do it we will use timers, so as a bonus of this tutorial we'll see how to implement and use the wxWidgets event driven timers.

Creating the Timer

We will use the wxTimer class. This brings us timers that we can define the interval between the timer send an event. And we will use that event to make an action. But go step by step.

First we have to create the timer object. So add a timer object to our Frame class. In wlConsoleMain.h to the wxlConsoleFrame class declaration:

wxTimer* recTimer; // declaration of Timer object

void OnRecTimer(wxTimerEvent& event); // declaration of Timer event declaring

And we have to declare the event function as a member of the Frame class to.

 

Now we have to define the timer object defining his owner and event function. We will do this in the constructor of the Frame class in wlConsoleMain.cpp.

recTimer = new wxTimer(this, Rec_Timer);

recTimer->Start(interval);

And of course start the timer. interval is defined in this file as a global variable and is in milliseconds. The parameters of the wxTimer constructor is the owner object of the timer (wxlConsoleFrame in this case) and the event function.

 

The last thing we have to do to use the timer is asociate the function with the event. We do this in the same file, wlConsoleMain.cpp, in the EVENT_TABLE. There we have to add the event and associate to it the function and an ID for it:

enum // Timer events IDs
{
    Rec_Timer = wxID_HIGHEST,
};

BEGIN_EVENT_TABLE(wxlConsoleFrame,wxFrame)
    //(*EventTable(wxlConsoleFrame)
    //*)
    EVT_TIMER(Rec_Timer, wxlConsoleFrame::OnRecTimer)   //Receive Timer event declaration
END_EVENT_TABLE()

First we define the IDs for our events, only one in the example, and then define the event in the event table. We associate the event EVT_TIMER with the ID Rec_Timer and the function wxlConsoleFrame::OnRecTimer.

Now we have the timer sending and event every 200 milliseconds (or the value you initialize the timer with). Just do something with this ticking.

Using the timer event to show the incoming messages

To make something in the timer event we just have to write some code inside the timer event function. We want to poll the COM port and show the message if any.

So in the wlConsoleMain.cpp we add the functionality:

void wxlConsoleFrame::OnRecTimer(wxTimerEvent& event)
{
    //wxString recMesg;
    unsigned char recBuff[512];
    int readed = 0;

    recBuff[0] = 0;
    readed = RS232_PollCompor<code>t</code>(port_nr-1, recBuff, 512); // Poll the serie's buffer
    if(readed > 0)
    {
        recBuff[readed] = 0;    // Finalize the char buffer
        recBox->SetValue(recBuff);   // Show the message
        //recBox->AppendText(recBuff);   // Show the message
        //recBox->AppendText(_("\n"));   // If multiline text insert a carriage return
    }

}

Here we first create a char* buffer to store the message. As I said before, the RS232 library works with char* C style strings. With RS232_PollComport we check if there is any message in the serial buffer and read it to the buffer if any. This function returns the number of bytes readed.

Then sohw the readed message in the recBox textCtrl. Setvalue function changes the actual text in the window to the text passed as parameter. If you want to let the past texts in the text box you can use AppendText and if you defined the text box as multiline you can append a carriage return to show every new message in a new line.

Now we have all the functionality implemented. Only left to let the things clean.

Cleaning things on quiting the application

After leave the program we have to stop the timer and close the port. So in the OnQuit event we add some code:

void wxlConsoleFrame::OnQuit(wxCommandEvent& event)
{
    recTimer->Stop()<code>; </code>// Stop the timer

    if(port_nr > 0)
         RS232_CloseComport(port_nr-1); // Closing the port before quit the application
    Close();
}

This event is throwed when the window is closed but after closing the application.

Here is a screenshot of the application runing, cnnected to an Arduino board acting as an echoer.

Points of Interest

In this article I tried to show how to use some wxWidgets resources, the basic ones, and add some extra useful content with the RS232 library and the wxTimer using.

Another very important thing are the functions to get and set values to the GUI resources: GetValue and SetValue. This functions let us interact easily with the resources we put in the screen.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Andres Cassagnes
Engineer
Argentina Argentina
As an Electronic Engineer I've worked with many languages and for some diferents plattforms including desktop and embedded systems.
My natural envirovment is C/C++, but I've worked with Java, C# and hardware oriented languages (VHDL, Verilog).
I really like programing and always I'm looking for some ideas to development.
I add to the opensource philosophy do I try to share my knowledge with other people.
 
Now I am developing embedded systems for nuclear industries in the Argentina most important technology Company.

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141220.1 | Last Updated 4 Sep 2014
Article Copyright 2014 by Andres Cassagnes
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid