Solved

C++ serial port ReadLine?

Posted on 2015-01-19
30
705 Views
Last Modified: 2015-02-06
Hi guys, I am attempting to port some code from C# over to c++ and am having problems with the serial communications.

I have data writing successfully to the port, but I can't figure out how to read the incoming stream.

I have:

      if (com_handle != INVALID_HANDLE_VALUE)
            {
                  WriteFile(com_handle, sync, strlen(sync), &bytesWritten, NULL);
            }
            if (WriteFile)
            {
                  printf("success");  

                  ReadFile(com_handle, ??????????);

            }

where I attempt to use these functions:

//write to serial function

      BOOL WINAPI WriteFile(
            __in         HANDLE hFile,
            __in         LPCVOID lpBuffer,
            __in         DWORD nNumberOfBytesToWrite,
            __out_opt    LPDWORD lpNumberOfBytesWritten,
            __inout_opt  LPOVERLAPPED lpOverlapped
            );


      //read from serial function
      
      BOOL WINAPI ReadFile(
            _In_         HANDLE hFile,
            _Out_        LPVOID lpBuffer,
            _In_         DWORD nNumberOfBytesToRead,
            _Out_opt_    LPDWORD lpNumberOfBytesRead,
            _Inout_opt_  LPOVERLAPPED lpOverlapped
            );


Trouble is, i have no idea what the readfile is expecting. Where is my good old c# onDatareceived event when i need it... :)

Also, the data comes in as a string, which makes it even more difficult... I need to read the string and store it.
Any help much appreciated!
0
Comment
Question by:chemicalx001
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 15
  • 13
  • 2
30 Comments
 
LVL 86

Expert Comment

by:jkr
ID: 40557734
Here you'd basically set up a receive buffer and pass it to 'ReadFile()', along with the other parameters. E.g.

BYTE buffer[1024];
DWORD dwNumBytesRead; // will be filled by Windows, indicates how much was read

ReadFile(com_handle,(LPVOID) buffer, 1024, &dwNumBytesRead, NULL);

// write to string
char target_string[1025]; // one byte more than the buffer, includes NULL terminator
strcpy_s(target_string,dwNumBytesRead,(char*) buffer);

Open in new window


For more on this issue see e.g. http://www.codeproject.com/Articles/3061/Creating-a-Serial-communication-on-Win ("Creating a Serial communication on Win32")
0
 

Author Comment

by:chemicalx001
ID: 40557860
Thanks! I add this, and I get:

Expression (L "Buffer is too small" && 0)

If i comment out the write to string function, it runs ok, and print write success/read success as follows:

if (WriteFile)
            {
                  printf("write success");

                  BYTE buffer[1024];
                  DWORD dwNumBytesRead; // will be filled by Windows, indicates how much was read
                         ReadFile(com_handle, (LPVOID)buffer, 1024, &dwNumBytesRead, NULL);
                  if (ReadFile)
                  {
                        printf("read success");
                  }
                  //write to string
                  //char target_string[1025]; // one byte more than the buffer, includes NULL terminator
                  //strcpy_s(target_string, dwNumBytesRead, (char*)buffer);
}

I made the buffer 4068, just for fun, and it gives the same error. Can I print without converting to string, to see if the data is correct?

Thanks again
0
 

Author Comment

by:chemicalx001
ID: 40557884
Ok, I switched it for:

 ReadFile(com_handle, (LPVOID)buffer, 1024, &dwNumBytesRead, NULL);
                  if (ReadFile)
                  {
                        std::string dataIn = ((char *)buffer);
                  }


and this is giving me something.
As long as this way won't give me issues, I think I am good.

Thanks again!
0
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 

Author Comment

by:chemicalx001
ID: 40557914
cancel that... It gives me data, but only the first part of the block, over and over. Is this because of the 1024 byte amount?
How would i read until the block separator? or a carriage return?


Thanks!
0
 
LVL 86

Expert Comment

by:jkr
ID: 40557915
>> std::string dataIn = ((char *)buffer);

That might give you truble if 'buffer' is not NULL terminated. Better use a std::string constructor that takes a length argument, i.e.

std::string dataIn((char *)buffer,dwNumBytesRead);

Open in new window

0
 
LVL 86

Expert Comment

by:jkr
ID: 40557929
>>How would i read until the block separator? or a carriage return?

That could be like

std::string data_received; // will hold all bytes
std::string terminator = "\r";
while (1)
{

  if(ReadFile(com_handle, (LPVOID)buffer, 1024, &dwNumBytesRead, NULL)
                  {
                        std::string dataIn = ((char *)buffer,dwNumBytesRead);
                        data_received += dataIn; // add the data

                        if (string::npos != dataIn.find(terminator)) break; // found terminator, stop loop
                  } else break; // error on 'ReadFile()', stop loop
}

Open in new window

0
 

Author Comment

by:chemicalx001
ID: 40558023
ok, getting there... I now have:

                DWORD dwNumBytesRead; // will be filled by Windows, indicates how much was read
            std::string data_received; // will hold all bytes
            std::string dataIn;

            std::string terminator = "\r";
            while (1)
            {

                  if (ReadFile(com_handle, (LPVOID)buffer, 1024, &dwNumBytesRead, NULL))
                  {
                        dataIn = ((char *)buffer, dwNumBytesRead);
                        data_received += dataIn; // add the data

                        cout<<data_received<<endl;

                        if (string::npos != dataIn.find(terminator)) break; // found terminator, stop loop
                  }
                  else break; // error on 'ReadFile()', stop loop
            }


but it prints gibberish to the console.... it should be a string now, right? Do i need to convert it again to have it readable?
0
 
LVL 86

Expert Comment

by:jkr
ID: 40558074
Yup, because you aren't using 'dataIn' as in th eabove example, that should be

            DWORD dwNumBytesRead; // will be filled by Windows, indicates how much was read
            std::string data_received; // will hold all bytes
            std::string terminator = "\r";

            while (1)
            {
                  if (ReadFile(com_handle, (LPVOID)buffer, 1024, &dwNumBytesRead, NULL))
                  {
                        std::string dataIn((char *)buffer, dwNumBytesRead); // use the constructor(!) that takes a lenth argument!
                        data_received += dataIn; // add the data

                        cout<<data_received<<endl;

                        if (string::npos != dataIn.find(terminator)) break; // found terminator, stop loop
                  }
                  else break; // error on 'ReadFile()', stop loop

Open in new window

0
 
LVL 34

Expert Comment

by:sarabande
ID: 40558106
Also, the data comes in as a string, which makes it even more difficult... I need to read the string and store it.
why do you think the incoming data is a string? and if so, is it an ansi string or wide utf-16 string (what ms calls UNICODE)?

to find out what kind of data you are receiving, you may print the buffer by using hex notation (as it was done in a hex editor).

#include <string>
#include <iostream>
#include <iomanip>
...
if (ReadFile(com_handle, buffer, 1024, &dwNumBytesRead, NULL))
{
      DWORD dwNumRows = (dwNumBytesRead+15)/16;
       
      for (int n = 0, r = 0; n < dwNumRows * 16; n += 16, r++)
      {
             std::cout << std::hex << std::setw(4) << std::right << std::setfill('0') << r << ' ';
             std::string strrow;
             for (int m = 0; m < 16; ++m)
             {
                   if (n+m>= (int)dwNumBytesRead)
                           buffer[n+m] = '\0';
                   std::cout << std::hex << std::setw(2) << std::right << std::setfill('0') << (int)buffer[n+m] << ' '; 
                   if (isprint(buffer[n+m]))
                             strrow += (char) buffer[n+m];
                   else
                             strrow += '.';
              }
              std::cout << strrow << std::endl;
        }
  }

Open in new window


if the data is not text or if it is wide characters, both string concatenation and output to std::cout would not work properly.

Sara
0
 

Author Comment

by:chemicalx001
ID: 40558184
OK. looking much better, readable text! Now I just need to copy each line to an array so that I can call them separately.
something like:

char data[200];

 if (string::npos != dataIn.find(terminator))
            strcpy(data, data_received);

which doesn't work, obviously.

What should i use to copy std::strings to items in an array?
0
 
LVL 86

Expert Comment

by:jkr
ID: 40558192
Just use

strcpy(data, data_received.c_str());

Open in new window


Butm a 200 character string for data that didn't fit into a 1024 byte buffer?
0
 

Author Comment

by:chemicalx001
ID: 40558193
I think it is a string, as this was the c# code...

 SerialPort sp = (SerialPort)sender;
         
                try
                {
                    string input = sp.ReadTo("X");  
                    Globals.Data = input;
}


which works perfectly. I am of course, possibly wrong...
0
 

Author Comment

by:chemicalx001
ID: 40558201
I am now seeing lines that look like this:

R41 p2+k:)2962

where i expect this:

R41 P24 A2962


Is this a buffer size thing?

Thanks for your patience!
0
 
LVL 86

Expert Comment

by:jkr
ID: 40558209
No, all the buffers are of sufficiant size. And if you see 'R41 p2+k:)2962', that's what the port reads. Any chance that the COM settings are off? Check the section 'Modifying a configuration' in http://www.codeproject.com/Articles/3061/Creating-a-Serial-communication-on-Win
You need to set

// Assign user parameter.
config_.BaudRate = dcb.BaudRate;  // Specify buad rate of communicaiton.
config_.StopBits = dcb.StopBits;  // Specify stopbit of communication.
config_.Parity = dcb.Parity;      // Specify parity of communication.
config_.ByteSize = dcb.ByteSize;  // Specify  byte of size of communication.

Open in new window


to your actual values.
0
 

Author Comment

by:chemicalx001
ID: 40558230
I have those set, settings copied from the working c#.
although.....

I have
c++     dcb_serial_params.ByteSize = 8;
which i translated from
c#      serialPort1.DataBits = 8;


But are they actually the same setting? Or am i an idiot?

if that is valid, then...

So the data in is:

R42 P24 A2930  R41 P24 A2965    ☼♦  3 P24 A4665 4734
R(`® `4 A4742 24 A4740
R c® `4 A4733 ® P^í 24 A2931
R¿c® `4 A4699
R43 P24 A2911 -¼ 2 P24 A2930     ☼♦   A2911 708 4734


where
R42 P24 A2930
 R41 P24 A2965
R43 P24 A2911

are valid lines, but are scrambled up with a bunch of gibberish.

this is using:

                                std::string dataIn((char *)buffer, dwNumBytesRead); // use the constructor(!) that takes a lenth argument!
                        data_received += dataIn; // add the data


                        if (string::npos != dataIn.find(terminator))  // found terminator
                                                            
                              //strcpy_s(dataStore, data_received.c_str());

                              cout << data_received << endl;


if I copy the values to dataStore using strcpy, I get a different style of gibberish.
0
 
LVL 86

Expert Comment

by:jkr
ID: 40558240
That seems correct to me. But: Parity will for sure be an issue here. What are your other port settings on the C# side?
0
 

Author Comment

by:chemicalx001
ID: 40558270
as below:

serialPort1 = new SerialPort();

            string portChoice = port.Text;
            serialPort1.PortName = "COM" + (portChoice);
            serialPort1.BaudRate = 256000;
            serialPort1.Parity = Parity.None;
            serialPort1.StopBits = StopBits.One;
            serialPort1.DataBits = 8;
            serialPort1.Handshake = Handshake.None;
            serialPort1.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);


 public static void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
        {
            SerialPort sp = (SerialPort)sender;        
           
                try
                {
                    string input = sp.ReadTo("X");
                    Globals.hexData = input;
       }
0
 
LVL 86

Expert Comment

by:jkr
ID: 40558297
And what are sou using as your settings in C++ (to be able to compare them)?
0
 

Author Comment

by:chemicalx001
ID: 40558482
com_handle = CreateFile(comport,
            GENERIC_READ | GENERIC_WRITE,
            0,
            0,
            OPEN_EXISTING,
            FILE_ATTRIBUTE_NORMAL,
            0);
      if (com_handle == INVALID_HANDLE_VALUE){
            printf("Error opening port\n");
            return INVALID_HANDLE_VALUE;
      }



      // Setting up the baud rate
      dcb_serial_params.DCBlength = sizeof(dcb_serial_params);
      if (!GetCommState(com_handle, &dcb_serial_params)){
            printf("Failed to get the previous state of the serial port\n");
            CloseHandle(com_handle);
            return INVALID_HANDLE_VALUE;
      }




      dcb_serial_params.BaudRate = 256000;
      dcb_serial_params.ByteSize = 8;
      dcb_serial_params.StopBits = ONESTOPBIT;
      dcb_serial_params.Parity = NOPARITY;
      if (!SetCommState(com_handle, &dcb_serial_params)){
            printf("Failed to set the serial port's state\n");
      }
      // Setting the timeouts, tweaking these can improve reliability
      // Bluetooth may need longer timeouts with some adapters
      timeouts.ReadIntervalTimeout = 10;
      timeouts.ReadTotalTimeoutConstant = 100;
      timeouts.ReadTotalTimeoutMultiplier = 100;
      timeouts.WriteTotalTimeoutConstant = 100;
      timeouts.WriteTotalTimeoutMultiplier = 100;
      if (!SetCommTimeouts(com_handle, &timeouts)){
            printf("Failed to set the timeouts\n");
            CloseHandle(com_handle);
            return INVALID_HANDLE_VALUE;


      }
0
 
LVL 86

Expert Comment

by:jkr
ID: 40558546
Seems to be the same. What character set/encoding is your C# application using? (Or better: Which one is the device using you are communicating with?)
0
 

Author Comment

by:chemicalx001
ID: 40559431
both are unicode, as is this new one. I think actually it might be the way i am trying to split the data. So this is what i expect:

X24
R43 P24 A2772
R42 P24 A2864
R41 P24 A2730
R40 P24 A2684

and this is what i get when i print data_received with 'X24' as a block separator:

R40 P24 A2684 _☺á¯h☺24 A2863
X24 P24 A2684 _úa☺24 A2863 3
X24 P24 A2684 _úa☺24 A2863 3
X24 P24 A2684 _úa☺24 A2863 3
X24 P24 A2684 _úa☺24 A2863 3
X24 P24 A2684 _úa☺24 A2863 3


If i clear the data_received string after each  block, it is this:

X24
X24 P24 A2684
R40 P24 A2684
X24
X24 P24 A2684
X40 P24 A2684
24


Basically, what i need, is to split each block at the 'X24', then run a foreach line on the blocks. Thanks again for your patience/help!
0
 
LVL 86

Expert Comment

by:jkr
ID: 40560321
You could do it using this 'split_text()' function:

#include <string>
#include <vector>
#include <iostream>
 
using namespace std;
 
int split_text(const string& strIn, const string& strDelim, vector<string>& vResult) {
 
   int nPos = 0;
   int nCount = 0;
   int nFound;
   string strToken;
 
   while(1) {
 
      nFound = strIn.find(strDelim,nPos);
 
      if (-1 == nFound)  {
 
        strToken = strIn.substr(nPos,strIn.length() - nPos);
        vResult.push_back(strToken);
        break;
      }
 
      strToken = strIn.substr(nPos,nFound - nPos);
 
      nPos = nFound + 1;
 
      ++nCount;
 
      vResult.push_back(strToken);
 
   }
 
   return nCount;
}
 
int main () {
 
  vector<string> vResult;
 
   int n = split_text("12456256|name|354||m|MN","|",vResult);
 
   for ( int i = 0; i < n; ++i)
      cout << vResult[i] << endl;
}

                                          

Open in new window


Substitute "|" with "X24" and the 1st poarameter with the string that you received.
0
 

Author Comment

by:chemicalx001
ID: 40560435
woah, that is much more complicated than 'line.Split('X24');'   :)

the split works great, but the data is still scrambled.
So, let me go back to the simplest code.
If I have this:

if (ReadFile(com_handle, (LPVOID)buffer, sizeof(buffer), &dwNumBytesRead, NULL))
                  {

                        std::string dataIn((char *)buffer, dwNumBytesRead);                         
                        cout << dataIn << endl;.
                      }

I expect this:

X24
R43 P24 A2772
R42 P24 A2864
R41 P24 A2730
R40 P24 A2684

and i get this:

X24
R43 P24 A2755
R4 P24 A2859
R4024 A2735
X244 A2677
R43 P24 A2755
R4 P24 A2859
R4024 A2736
 P24 A2677

(swapping 1024 for ' sizeof(buffer),' seems to help...)

Sorry this is taking so long. aaargh.
0
 
LVL 86

Expert Comment

by:jkr
ID: 40560503
Correct me if I am wrong, but it seems that there is some data loss, doesn't it? Can you try to use larger timeout values?
0
 

Author Comment

by:chemicalx001
ID: 40560659
timeouts at 1000... slightly better, but it shouldn't need that much, should it?  should i be checking the size of the data coming in?

R43 P24 A2603
R4 P24 A2705
R4024 A2606
X244 A2560
R43 P24 A2603
R4 P24 A2705
R4024 A2606
X244 A2551
R43 P24 A2604
R4 P24 A2705
0
 
LVL 86

Expert Comment

by:jkr
ID: 40560806
If you have a format that defines the incoming data - yes, of course! Now there mainly seem blanks missing compared to that what you should get. What kind of device is that by the way?
0
 

Author Comment

by:chemicalx001
ID: 40560993
It is an ultrasonic sensor system.  
Actually the data changes length  depending on the distance from receivers to transmitter,  so I probably can't look for a certain size.

It is almost right!  But many characters missing...
0
 

Author Comment

by:chemicalx001
ID: 40560998
... Including carriage returns,  seemingly,  which is scrambling the lines.
0
 
LVL 34

Assisted Solution

by:sarabande
sarabande earned 250 total points
ID: 40561038
to find out which data you received you need to look at each byte rather than trying to convert the data to a string and do a string output.

for (int n = 0; n < dwNumBytesReceived; ++n)
{
     std::cout << std::hex << buffer[n] << ' ';
}
std::cout << std::endl;

Open in new window


Sara
0
 
LVL 86

Accepted Solution

by:
jkr earned 250 total points
ID: 40561179
>>... Including carriage returns

OK, just an idea - you can try to set 'dcb_serial_params.ErrorChar' to a value of your choice to see if there are in fact transmission errors. That would be interesting to find out.
0

Featured Post

Instantly Create Instructional Tutorials

Contextual Guidance at the moment of need helps your employees adopt to new software or processes instantly. Boost knowledge retention and employee engagement step-by-step with one easy solution.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

If you're not part of the solution, you're part of the problem.   Tips on how to secure IoT devices, even the dumbest ones, so they can't be used as part of a DDoS botnet.  Use PRTG Network Monitor as one of the building blocks, to detect unusual…
In this article, I am going to show you how to simulate a multi-site Lab environment on a single Hyper-V host. I use this method successfully in my own lab to simulate three fully routed global AD Sites on a Windows 10 Hyper-V host.
The goal of the tutorial is to teach the user how to use functions in C++. The video will cover how to define functions, how to call functions and how to create functions prototypes. Microsoft Visual C++ 2010 Express will be used as a text editor an…
The viewer will learn additional member functions of the vector class. Specifically, the capacity and swap member functions will be introduced.

739 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question