Solved

C++ serial port ReadLine?

Posted on 2015-01-19
30
509 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
  • 15
  • 13
  • 2
30 Comments
 
LVL 86

Expert Comment

by:jkr
Comment Utility
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
Comment Utility
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
Comment Utility
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
 

Author Comment

by:chemicalx001
Comment Utility
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
Comment Utility
>> 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
Comment Utility
>>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
Comment Utility
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
Comment Utility
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 32

Expert Comment

by:sarabande
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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
What Security Threats Are You Missing?

Enhance your security with threat intelligence from the web. Get trending threat insights on hackers, exploits, and suspicious IP addresses delivered to your inbox with our free Cyber Daily.

 
LVL 86

Expert Comment

by:jkr
Comment Utility
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
Comment Utility
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
Comment Utility
And what are sou using as your settings in C++ (to be able to compare them)?
0
 

Author Comment

by:chemicalx001
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
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
Comment Utility
... Including carriage returns,  seemingly,  which is scrambling the lines.
0
 
LVL 32

Assisted Solution

by:sarabande
sarabande earned 250 total points
Comment Utility
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
Comment Utility
>>... 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

How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

Join & Write a Comment

This is an article about my experiences with remote access to my clients (so that I may serve them) and eventually to my home office system via Radmin Remote Control. I have been using remote access for over 10 years and have been improving my metho…
Let’s list some of the technologies that enable smooth teleworking. 
The viewer will be introduced to the technique of using vectors in C++. The video will cover how to define a vector, store values in the vector and retrieve data from the values stored in the vector.
Get a first impression of how PRTG looks and learn how it works.   This video is a short introduction to PRTG, as an initial overview or as a quick start for new PRTG users.

771 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

Need Help in Real-Time?

Connect with top rated Experts

16 Experts available now in Live!

Get 1:1 Help Now