Pushing bits through the air



Pushing bits through the air

 

One of the most modern conveniences that programmers have today is the debugger. This allows programmers to stop the execution of code and examine the behaviors of the program. Although many companies sell debuggers for embedded applications, robot builders do not have the luxury of stopping the processor and looking at memory while the motors are moving. This paper will discuss methods available to the robot builder to find out what the robot is doing, seeing, and thinking.

 

We will focus on three areas of Windows programming that will allow us to build information-gathering programs. We will discus opening a serial port on the PC, sending the contents of that port through a wireless Ethernet link to a CE device.

Keywords

Wireless, Serial Port, Ethernet Sockets, Threads, INI files, CE devices, and MFC.

Introduction

The faster a robot moves, the more uncontrolled it is. I discovered this on my current fire fighter. I decided that the people who were telling me I needed some sort of motion control software in my robot were correct. So I pulled my old copy of Mobile Robots out and copied the proportional-integral control code into my robot and sure enough, it did not work. I needed to tune the algorithm for my robot and this is where I got into a problem. In order to see the raw encoder numbers as well as the power I was sending to the robot, I decided to pull the wireless modems off the shelf and put them onto the robot. I started to do this but found that I did not design the frame of the robot to incorporate the wireless modem. So like all good robot hobbyists I started to build a new robot to experiment with motion control problems. Once I started working on this robot other ideas of what the robot viewing software could do started to creep into the design and this project took on a life of it’s own.

What I ended up with is a robot base that sent data out through a wireless modem to a laptop. From the laptop that data traveled over a wireless Ethernet link to a CE device where I can view the raw numbers. This paper will discuss how I did this. I will start with the radio modem serial link.

Motor Command Packet

In my job I deal with sending data from and to serial ports quite a bit. I knew that I wanted a little bit of a structure to the data packets I’m going to send so I came up with a structure where the first byte is the identifier of what type of packet that is going to be sent. The data in the packet will then follow. A check byte for the ID and data will then be sent. After all of these fields I wanted a two-byte end of packet identifier. The end of packet identifier could be any two chars that should usually never appear together as valid data. Two-byte end of packet identifiers are really not the best method for ending a packet, but since this was my robot to PC communication protocol and nothing relies on this communication I felt that a two-byte packet end is appropriate. I decided that 0xFF 0xFF would be a good packet end, however I remembered my co-worker saying that 0xFF 0xFF is a very bad packet ending, but I chose to ignore him.

For the motor values to receive I chose to send the left encoder as one byte, the right encoder as a byte, the left power as a byte and the right power as a byte. My robot rarely got above 0x30 for the encoder counts and I chose to have a byte for the power already so I would send the left and right power after the encoders. The packet that was being sent would have an ID of 2. I decided this because I thought there should be a start of robot packet that would have an ID of 1. I did not define this ID 1 packet.

With this new packet defined I went and coded it into the robot software, downloaded the new firmware, started up a Windows Terminal and ran the robot. I was then disappointed at the outcome. I forgot that the bytes I was sending to the terminal program were not going to show up very well. I then thought I could convert the raw numbers to ASCII Hex numbers. That would mean that the command of 2 would be converted to 0x30 0x32. Not wanting to clog my robot code up with a number to ASCII Hex number conversion and slowing my communication I then thought of writing my own little robot serial port program that I could not only show the numbers coming from my robot, but could also save them to a file for later use.

I had experience with programs like this so I pulled up some old code I had written and cleaned it up to use. I had hard coded the serial port settings in the program so the first thing I wanted was the port settings to be read from some config file.

Serial Port Settings

One of the things about a serial port is that there are five settings that will need to be set up in order for a robot to communicate with the PC. The five settings are, the serial port, baud rate, byte size, parity, and stop bit. These settings can change depending on what the serial port is connected to. I did not want to code them into the program, but wanted to be able to change them. On a Windows box these days the place to put user settings is in the registry, however I really did not want to go through that trouble. So I chose an older method of reading settings from a file. In the old 16 bit versions of Windows, user settings were stored in INI files. I decided to use this method for my serial port reader. A simple INI file would look like this:

[Section Header]

Setting=Value

There can be many different parts in an INI file and each part was separated by a section header enclosed in square brackets ‘[‘ ‘]’. Under this section header, there can be several key/data pairs. These pairs are separated by an equals sign ‘=’. Comment lines in an INI file begin with a semicolon. I then coded up my INI file like this:

; INI file for the ComPortReader program

[ComPortReader]

ComPort=COM2:

BaudRate=19200

Parity=0

StopBit=0

ByteSize=8

; Parity:

; NOPARITY 0

; ODDPARITY 1

; EVENPARITY 2

; MARKPARITY 3

; SPACEPARITY 4

; StopBit:

; ONESTOPBIT 0

; ONE5STOPBITS 1

; TWOSTOPBITS 2

For the baud rate, parity, and stop bit I looked in the Windows header file winbase.h and found what they should be set to. For the parity and stop bit I copied the defines out and placed them as comments in this file so I would not have to refer back to the header file.

I then went back in time in my code library to some programs I wrote back around 1994, and found the two functions I needed to use GetPrivateProfileString, and GetPrivateProfileInt. The functions are for reading in strings and integers. The string reading function looks something like this:

GetPrivateProfileString(

char* SectionHeader,

char* Setting,

char* DefaultValue,

char* Buffer,

int Size,

char* INIFileName);

The items passed in are, the section of the INI file to read. The key to read in the section. The default value is the value to pass back if the key is not found. The four items are the data buffer to put the data into. The maximum size of this buffer, this ensures Windows will not write more data than the buffer can handle. The last argument is the file name. If this file name does not include path information then Windows assumes that this INI file is in the system directory. To ensure that Windows always looks in the current directory the ‘.\\’ path should be used in the path of the filename. So my INI file that I used for the port viewer is ‘.\\ComPortReader.ini’.

To read numbers from the INI file use the GetPrivateProfileInt function. This function looks like this:

int GetPrivateProfileInt(

char* SectionHeader,

char* Setting,

int DefaultValue,

char* INIFileName);

This function looks somewhat like the string function with the exception of the value that is read is returned form this function. The default setting is also a number.

I then coded the function to read in the settings like this:

/*

** Read in the settings from the INI file

*/

void ReadIniFile(void)

{

GetPrivateProfileString( "ComPortReader",

"ComPort",

"COM1:",

gPort,

MAXPORTSIZE,

".\\ComPortReader.ini");

gBaud = GetPrivateProfileInt( "ComPortReader",

"BaudRate",

9600,

".\\ComPortReader.ini");

gStopBit = GetPrivateProfileInt( "ComPortReader",

"StopBit",

0,

".\\ComPortReader.ini");

gParity = GetPrivateProfileInt( "ComPortReader",

"Parity",

0,

".\\ComPortReader.ini");

gByteSize= GetPrivateProfileInt( "ComPortReader",

"ByteSize",

8,

".\\ComPortReader.ini");

}

The variables I placed these settings in are global. They look like this:

#define STRINGPAD 2

#define MAXPORTSIZE 10

char gPort[MAXPORTSIZE+STRINGPAD];

int gBaud;

int gStopBit;

int gParity;

int gByteSize;

To verify that the settings were being read out correctly my main looked like this:

void main(void)

{

ReadIniFile();

printf("Opening Port:%s\n",gPort);

printf(" Baud:%d\n",gBaud);

printf(" Stop Bit:%d\n",gStopBit);

printf(" Parity:%d\n",gParity);

}

Opening up the port

Now that I had the settings read in correctly the serial port could be opened up. Operating systems in general treat many devices attached to them like files. Windows is no exception to this. The first thing I had to do was to create a file. In the code below, basically pass in the port string from the INI file as the file name. I will not go into the rest of the CreateFile attributes, just be aware that they are here:

HANDLE OpenPort(void)

{

HANDLE hCommPort;

hCommPort = CreateFile( gPort,

GENERIC_READ | GENERIC_WRITE,

0,

0,

OPEN_EXISTING,

FILE_ATTRIBUTE_NORMAL,

0);

return hCommPort;

}

I return the HANDLE to the file and let the calling function figure out if the CreateFile worked or did not. The code that calls this function will look like this:

HANDLE hCommPort;

hCommPort = OpenPort();

if (INVALID_HANDLE_VALUE == hCommPort)

{

printf("Error in opening the Com Port\n");

return;

}

Initializing the Port

Once the port is open it needs to be told the port the settings from the INI file. Two function calls are needed to do this are GetComState and SetComState and they are used to work with the settings. The GetComState function places the current state of the serial port into a DCB structure. I then changed the DCB and passed it back by using the SetComState. The code that I used is:

#define GOOD 0

#define FAIL -1

int InitPort(HANDLE hCommPort)

{

DCB dcb = {0};

dcb.DCBlength = sizeof(dcb);

/*

** Get the current com port settings

*/

if (!GetCommState(hCommPort, &dcb))

{

return(FAIL);

}

/*

** set the com port to our settings

*/

dcb.BaudRate = gBaud;

dcb.ByteSize = gByteSize;

dcb.Parity = gParity;

dcb.StopBits = gStopBit;

dcb.fDtrControl = DTR_CONTROL_DISABLE;

dcb.fRtsControl = RTS_CONTROL_DISABLE;

dcb.fOutxCtsFlow = FALSE;

dcb.fOutxDsrFlow = FALSE;

dcb.fDsrSensitivity = FALSE;

dcb.fOutX = FALSE;

dcb.fInX = FALSE;

dcb.fTXContinueOnXoff = FALSE;

dcb.XonChar = ASCII_XON;

dcb.XoffChar = ASCII_XOFF;

dcb.XonLim = 0;

dcb.XoffLim = 0;

dcb.fParity = TRUE;

/*

** set the com port to our settings

*/

if (!SetCommState(hCommPort, &dcb))

{

return(FAIL);

}

return(GOOD);

}

Reading the Serial Port

After opening the port and configuring it, it can be treated like a file. It can be read from and written to using the function to read and write to files. I’m just going to read from the file. The first read that is going to happen is a read of one byte, which is the ID so that I know what the command is that is being sent from the robot. Once I know the command, I can read the rest of the package in, or I will know that something went wrong and command is not understood for some reason. When the communication is off track the program will look for the end of packet bytes and get back to reading data correctly. My read code looks like this:

ReturnCode = TRUE;

while(TRUE == ReturnCode)

{

ReturnCode = ReadFile( hCommPort,

ReadBuffer,

1,

&CharsRead,

NULL);

switch(ReadBuffer[0])

{

case(MOTORCOMMAND):

ReturnCode = ReadFile( hCommPort, &ReadBuffer[1],

MOTORCOMMANDSIZE,

&CharsRead, NULL);

ProcessMotorCommand(ReadBuffer);

break;

default:

/*

** Somehow we have gotten off track,

** find the next two EndChars to get back on

*/

PrevChar = ReadBuffer[0];

ReturnCode = ReadFile( hCommPort, ReadBuffer,

1, &CharsRead,

NULL);

while(TRUE == ReturnCode)

{

printf("%d %d\n",PrevChar,ReadBuffer[0]);

if((ENDCHAR == PrevChar)&&(ENDCHAR == ReadBuffer[0]))

{

break;

}

PrevChar = ReadBuffer[0];

ReturnCode = ReadFile( hCommPort,

ReadBuffer,

1,

&CharsRead,

NULL);

}

}

}

Processing the Command

I originally was going to write the bytes to the screen and to a file that I could later import into Excel. I then thought of a better place to put the data, a SQL database. Unfortunately I will not be able to get to that part of the code within this paper. The processing of the data begins with checking the check byte to see if everything came across the way it should have. My processing code looks like this:

void ProcessMotorCommand(uchar* ReadBuffer)

{

int i;

uchar check;

check = 0;

for(i=0;iCreate())

{

strError.Format(_T("Failed to create client: %d!"),

gSocket->GetLastError());

AfxMessageBox (strError,0,0);

return FALSE;

}

Once the socket object is open to connect to a server is just one other call:

strServer = _T("206.125.93.79");

lngPort = 5432;

if (!gSocket->Connect(strServer, lngPort))

{

strError.Format(_T("Failed to connect: %d."),

gSocket->GetLastError());

AfxMessageBox (strError,0,0);

gSocket->Close();

delete gSocket;

gSocket = NULL;

return FALSE;

}

The sending and receiving of data from this socket object just very close to the old way.

/*

** Send a request

*/

Buff[0] = 102;

Buff[1] = 0;

if (gSocket->Send(Buff, 1) == SOCKET_ERROR)

{

strError.Format(_T("Failed to send on client socket: %d"),

gSocket->GetLastError());

gSocket->Close();

delete gSocket;

gSocket = NULL;

AfxMessageBox (strError,0,0);

return FALSE;

}

/*

** Get a response

*/

retval = gSocket->Receive(Buff, 4048);

if (SOCKET_ERROR == retval)

{

strError.Format(_T("Failed to send on client socket: %d"),

gSocket->GetLastError());

gSocket->Close();

delete gSocket;

gSocket = NULL;

AfxMessageBox (strError,0,0);

return FALSE;

}

Once the program is done with the socket object, the socket is closed and deleted.

The CE Application

The application I’m going to write is a MFC CE application. This means I’m going to use the Microsoft Foundation Class as the base for this viewer. These are fairly easy to write because Visual C++ for CE does a lot of the work for my. What I want is a dialog-based application with a list box as the main data viewer. The new lines of data are going to be written at the bottom of the listbox while the oldest lines are removed form the top. This will give a nice scrolling windows of data.

Once Visual C++ finishes setting up my dialog-based application, I’m going to open up the main dialog in the resource viewer and add a listbox to it. This list box I’m going to call IDC_LISTBOX. From the resource viewer I can change into the source code and find the OnInitDialog method. In this method I can open up a global socket using the code from above, map a global ClistBox to the IDC_LISTBOX and then start up a thread. The mapping and thread code will look like this:

/*

** Get a handle on the list box

*/

gpcListBox = (CListBox*)this->GetDlgItem(IDC_LISTBOX);

/*

** Start the thread up and running

*/

gThreadRun = true;

hThread = CreateThread(NULL, 0,

&ClientThread, (LPVOID)&gpcListBox, 0, NULL);

Since I can close this application anytime I want I’m going to put in code that will stop the communication thread when I want. In all of the server threads I want them to keep running forever. To stop the thread I’ve declared a global variable called gThreadRun. This controls whether I want the thread to run or not. In my communication thread the while loop now checks this variable to see if it is true. If it is not then the thread will stop.

/*

** Run the thread until someone says stop

*/

while(true == gThreadRun)

{

Sleep(5);

}

/*

** close the socket

*/

gSocket->Close();

delete gSocket;

gSocket = NULL;

When the thread stops it should clean up the socket. Inside the communication thread I’m going to place the code to send and receive data from the socket, however I’m also going to place the code to put the data into the list box. The list box had two methods that I’m going to use. The AddString and RemoveString methods. These will control the data lines in the list box.

/*

** Display data in the list box at the end

*/

if(1 == retval)

{

strBuffer = _T("No data in queue");

}

else

{

strBuffer.Format(_T("%02x %02x %02x %02x %02x %02x"),

Buff[0],Buff[1],

Buff[2],Buff[3],

Buff[4],Buff[5]);

}

gpcListBox->InsertString(ListCount,strBuffer);

/*

** If the list box if full delete the top item

*/

if(ListCount > 8)

{

gpcListBox->DeleteString(0);

}

else

{

ListCount++;

}

If there is data then format it into a CString object and display the string in the list box. If there isn’t any data to display then insert the string ‘No data in the queue’. Once the string is formed it will be placed in the list box. If the list box is full then delete a line from the top of it.

When the application is ready to close, the DestoryWindow method will set the gThreadRun variable to false, wait for the clean up and close out the window. I want to make sure that the Socket is closed before ending. If the socket is not closed the server will get a little confused and will stop working. The code to do this looks like this:

BOOL CCEViewerDlg::DestroyWindow()

{

/*

** Close the thread by setting the run to false

*/

gThreadRun = false;

Sleep(1000);

/*

** If the socket is still up close it

*/

if(NULL == gSocket)

{

gSocket->Close();

delete gSocket;

}

return CDialog::DestroyWindow();

}

Conclusion

Once all of these programs are compiled and running, the data will be sent from the robot to the main computer where the server is located. The server will then send the data along to the CE Viewer.

It was my hope that these programs give a good understanding of how data is moved. These programs can be modified to suit other needs as well. Since the encoder data is sent across this communication stream it can be used to draw a little map of where the robot is going on the screen of the CE device. The data could also be saved in a SQL Database for later use. This SQL Database solution will be discussed in other papers I will write. I wanted to include it here, but after nineteen pages of just getting the system up and running I felt that you the reader needed a bit of a break from my writing style.

It also turned out that the code to save the data into a database is easy, however the painful part of a paper discussing databases is setting up the database server. So it will be left to future papers.

About The Author

Jim Wright is a firmware engineer with Advanced Digital Information Corp. He is an all around Robot junky and trying to keep this paper to under the 20-page limit.

References

The Microsoft Developers Network CDs, 1994-2000.

................
................

In order to avoid copyright disputes, this page is only a partial summary.

Google Online Preview   Download