C++ serial port ReadLine?

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!
chemicalx001Asked:
Who is Participating?
 
jkrCommented:
>>... 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
 
jkrCommented:
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
 
chemicalx001Author Commented:
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
Improve Your Query Performance Tuning

In this FREE six-day email course, you'll learn from Janis Griffin, Database Performance Evangelist. She'll teach 12 steps that you can use to optimize your queries as much as possible and see measurable results in your work. Get started today!

 
chemicalx001Author Commented:
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
 
chemicalx001Author Commented:
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
 
jkrCommented:
>> 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
 
jkrCommented:
>>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
 
chemicalx001Author Commented:
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
 
jkrCommented:
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
 
sarabandeCommented:
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
 
chemicalx001Author Commented:
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
 
jkrCommented:
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
 
chemicalx001Author Commented:
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
 
chemicalx001Author Commented:
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
 
jkrCommented:
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
 
chemicalx001Author Commented:
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
 
jkrCommented:
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
 
chemicalx001Author Commented:
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
 
jkrCommented:
And what are sou using as your settings in C++ (to be able to compare them)?
0
 
chemicalx001Author Commented:
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
 
jkrCommented:
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
 
chemicalx001Author Commented:
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
 
jkrCommented:
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
 
chemicalx001Author Commented:
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
 
jkrCommented:
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
 
chemicalx001Author Commented:
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
 
jkrCommented:
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
 
chemicalx001Author Commented:
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
 
chemicalx001Author Commented:
... Including carriage returns,  seemingly,  which is scrambling the lines.
0
 
sarabandeCommented:
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
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.