How to increase number of serial data assigned to variable?

Go To StackoverFlow.com

5

I have downloaded and installed the comport library. I have a simulator sending data through serial RS232 to a Selphi program. This is the following code snippet.

procedure TMainForm.ComPortRxChar(Sender: TObject; Count: Integer);
begin
  ComPort.ReadStr(CPort.Str, Count);
  Memo.Text := Memo.Text + CPort.Str;
end; 

As for the CPort library portion, I added:

var
  Str: String;

here is the problem.

The data example that is coming through is roughly like

$HEHDT,288.45,T*1D
$HEHDT,288.46,T*18
$HEHDT,288.47,T*1A

and so on. Each line is sent per second. So with the code above, the memo will display all these data as it.

However, if I change the code to:

procedure TMainForm.ComPortRxChar(Sender: TObject; Count: Integer);
begin
  ComPort.ReadStr(CPort.Str, Count);
  Memo.Text := Memo.Text + CPort.Str + 'haha';
end;

This is what appears on the memo:

$HEHDT,2haha88.45,T*haha1Dhaha
$HEHDT,2haha88.46,T*haha18haha
$HEHDT,2haha88.47,T*haha1Ahaha

The "haha" appears after 8 ASCII characters. so does that mean in the CPort.pas library, under the asynchronous/synchronous portion, only 8ASCII characters max is assigned to the variable Str?

How do I go about changing the codes such that the whole data string, regardless of its size, will be allocated to the variable Str instead of only 8 bytes.

UPDATE**I realised that theres this part of the CPort library that contains the following code. can anyone enlighten me on how to edit the code? or whether it is the correct block that i have sourced out. thanks!

// split buffer in packets
procedure TComDataPacket.HandleBuffer;

procedure DiscardPacketToPos(Pos: Integer);
var
Str: string;
begin
FInPacket := True;
if Pos > 1 then
begin
  Str := Copy(Buffer, 1, Pos - 1); // some discarded data
  Buffer := Copy(Buffer, Pos, Length(Buffer) - Pos + 1);
  DoDiscard(Str);
end;
end;

procedure FormPacket(CutSize: Integer);
var
Str: string;
begin
Str := Copy(Buffer, 1, CutSize);
Buffer := Copy(Buffer, CutSize + 1, Length(Buffer) - CutSize);
CheckIncludeStrings(Str);
DoPacket(Str);
end;

procedure StartPacket;
var
Found: Integer;
begin
// check for custom start condition
Found := -1;
DoCustomStart(Buffer, Found);
if Found > 0 then
  DiscardPacketToPos(Found);
if Found = -1 then
begin
  if Length(FStartString) > 0 then // start string valid
  begin
    Found := Pos(Upper(FStartString), Upper(Buffer));
    if Found > 0 then
      DiscardPacketToPos(Found);
  end
  else
    FInPacket := True;
end;
end;

procedure EndPacket;
var
Found, CutSize, Len: Integer;
begin
// check for custom stop condition
Found := -1;
DoCustomStop(Buffer, Found);
if Found > 0 then
begin
  // custom stop condition detected
  CutSize := Found;
  FInPacket := False;
end
else
  if Found = -1 then
  begin
    Len := Length(Buffer);
    if (FSize > 0) and (Len >= FSize) then
    begin
      // size stop condition detected
      FInPacket := False;
      CutSize := FSize;
    end
    else
    begin
      Len := Length(FStartString);
      Found := Pos(Upper(FStopString),
        Upper(Copy(Buffer, Len + 1, Length(Buffer) - Len)));
      if Found > 0 then
      begin
        // stop string stop condition detected
        CutSize := Found + Length(FStopString) + Len - 1;
        FInPacket := False;
      end;
    end;
  end;
if not FInPacket then
  FormPacket(CutSize); // create packet
end;
2012-04-04 08:18
by Matthew Cheng
Serial data comes in and is reported as it arrives. Since there is no begin and end marker, you will get partial data, and should store the data so far and wait for all of the data, or perhaps a pause, before processing it. Any other way is not going to be reliable - mj2008 2012-04-04 08:41
To put it another way, if your protocol has no defined end, reception of 'the whole data string, regardless of its size' will take forever - Martin James 2012-04-04 14:15


2

A minimalistic solution with full error checking would be:

Update :

(Given that the received string ends with a CRLF combination and the packet length is not constant. This is a NMEA 0183 packet)

var
  finalBuf: AnsiString;

// Example packet: $HEHDT,10.17,T*28 + CRLF
// This is a NMEA 0183 protocol (Marine and GPS standard)
// Subset for reading the heading.
// HDT Heading – True
//       1   2 3
//       |   | |
//$--HDT,x.x,T*hh
//1) Heading Degrees, true
//2) T = True
//3) Checksum
// HE stands for: Heading – North Seeking Gyro

{- Checking packet and checksum, calculating heading }
Function ParseAndCheckNMEA_HDT(const parseS: AnsiString;
  var heading: Double): Integer;
// Example packet: $HEHDT,10.17,T*28 + CRLF
var
  i, p, err: Integer;
  xorSum: Byte;
  xorStr: AnsiString;
  headingStr: AnsiString;
begin
  Result := 0; // Assume ok
  if (Pos('$HEHDT', parseS) = 1) then // Start header ok ?
  begin
    p := Pos('*', parseS);
    if (p <> 0) and (Length(parseS) >= p + 2) then
    // Assumes a checksum in packet
    begin
      xorSum := Ord(parseS[2]);
      for i := 3 to p - 1 do // Calculate checksum
        xorSum := xorSum xor Ord(parseS[i]);
      xorStr := IntToHex(xorSum, 2);
      if (UpperCase(xorStr) = Copy(parseS, p + 1, 2)) then // Checksum ok ?
      begin
        // Validate heading
        headingStr := Copy(parseS, 8, p - 10);
        Val(headingStr, heading, err);
        if (err <> 0) then
          Result := 4; // Not a valid float
      end
      else
        Result := 3; // Wrong checksum
    end
    else
      Result := 2; // No checksum
  end
  else
    Result := 1; // Wrong header
end;


procedure TMainForm.ComPortRxChar(Sender: TObject; Count: Integer);
var
  i,err: Integer;
  strBuf: AnsiString;
  heading: Double;
begin
  ComPort.ReadStr(CPort.Str, Count);
  strBuf := CPort.str;
  for i := 1 to Length(strBuf) do
    case strBuf[i] of
      '$' : 
        finalBuf := '$';  // Start of package
      #10 :
        begin
          if (finalBuf <> '') and (finalBuf[1] = '$') then  // Simple validate check 
            begin
              SetLength( finalBuf, Length(finalBuf) - 1); // Strips CR
              err := ParseAndCheckNMEA_HDT(finalBuf, heading);
              if (err = 0) then 
                Memo.Lines.Add(finalBuf); // Add validated string
              //else
              //  Memo.Lines.Add('Error:' + IntToStr(err)); 
            end;
          finalBuf := '';
        end; 
    else
      finalBuf := finalBuf + strBuf[i];  
    end;
end;

Knowing the start character and packet ending, this should be fairly safe to use. The #13 and #10 (CR LF) characters that marks the end of package, are stripped and the package is checked for a valid checksum and the resulting heading value is calculated. The validated string is then added to the memo.

Update 2

To answer the direct question, why your receiving method can add your 'haha' string in the middle of the data string:

The comport routine delivers data, one or more characters, at its own pace. You cannot control when you are getting the data or how many characters there will be. Using the scheme in my answer, data is buffered until a complete package is delivered. With the packet support of TComPort it is possible to do the same.

From your comments it seems as there are several NMEA 0183 sensor types connected to the serial port. (Giving other lengths of the packages but all starting with $ character).

Replace the ParseAndCheckNMEA_HDT with the following function to validate the strings in that case :

Function ParseAndCheckNMEA(const parseS: AnsiString): Integer;
// Example packet: $HEHDT,10.17,T*28 + CRLF
var
  i, p: Integer;
  xorSum: Byte;
  xorStr: AnsiString;
begin
  Result := 0; // Assume ok
  if (Pos('$', parseS) = 1) then // Start header ok ?
  begin
    p := Pos('*', parseS);
    if (p <> 0) and (Length(parseS) >= p + 2) then
    // Assumes a checksum in packet
    begin
      xorSum := Ord(parseS[2]);
      for i := 3 to p - 1 do // Calculate checksum
        xorSum := xorSum xor Ord(parseS[i]);
      xorStr := IntToHex(xorSum, 2);
      if (UpperCase(xorStr) <> Copy(parseS, p + 1, 2)) then // Checksum ok ?
        Result := 3; // Wrong checksum
    end
    else
      Result := 2; // No checksum
  end
  else
    Result := 1; // Wrong header
end;
2012-04-05 07:07
by LU RD
if i want the start of the package to be '$' and the end of package to be #13#10 ( CR LF) then how should the above code be edited like? the whole package is sent every second - Matthew Cheng 2012-04-10 07:16
Matthew, see my update - LU RD 2012-04-10 07:57
I have runned your code. it is working! however, the cProtocolLen is not a const, it will vary. reason being: $HEHDT,288.45,T*1D the 288.45 may have values such as 34.22, 6.51 and so on. so how should the code look like in this case - Matthew Cheng 2012-04-11 01:20
Ok, edited answer to take care of the varying packed length - LU RD 2012-04-11 06:05
Added information about the NMEA 0183 protocol - LU RD 2012-04-11 14:07
it is indeed the nmea 0183 protocol. however there are other types such as $IIMWV,232.5,R,00.89,N,A*0E i need only extract the string. i can delimit the commas and asterisk afterwards. the above edited code only extracts the middle data. after trying the edited code of the varying length i had the same problem of displaying 30plus nice lines followed by 1 line of the first 8/16 ascii char of the line, and the cycle repeats. could it be due to the packet size forming the buffer. the code for that portion is updated above - Matthew Cheng 2012-04-13 00:45
Changed code to only send the validated string to the memo. You only asked for how to strip a complete $HEHDT string out of the incoming data flow. This is what my answer does. If you are getting incomplete strings to the memo, they must be added from somewhere else - LU RD 2012-04-13 05:53
Added comments about why your routine does not work, plus a way to validate any NMEA 0183 packet, provided they have a checksum (which is not mandatory) - LU RD 2012-04-13 12:24
Thank you so much for ur great help and being so patient with me LU RD! I must say I learnt quite a bit from this! : - Matthew Cheng 2012-04-16 00:49
Glad to be of help. Don't forget to accept the answer of your choice, see faq#howtoask - LU RD 2012-04-16 05:05


2

You should do your own buffering. That gives you more flexibility as to what delimits a message. For example, if you delimit messages with a carriage-return (#13), you can do:

var Buf: string;

procedure DoSomething(const Str: String);
begin
  Memo.Text := Memo.Text + Str;
end;

procedure TMainForm.ComPortRxChar(Sender: TObject; Count: Integer);
var I: Integer;
begin
  ComPort.ReadStr(CPort.Str, Count);
  Buf := Buf + CPort.Str;        
  for i := 1 to Length(Buf) do
    if Buf[i] = #13 then
    begin
      DoSomething(Copy(Buf, 1, i)); 
      Delete(Buf, 1, i+1);
      Break;
    end;
end;
2012-04-04 08:48
by Tibor
And if there is no #13 character in the data string, it looks like the $ character is preceding every new message - LU RD 2012-04-04 11:43
Sure, just use '$' instead - Tibor 2012-04-04 13:13
Rubbish protocol - needs a new message to start before the last one can be processed :( - Martin James 2012-04-04 14:16
On the other hand, there ARE newlines in the memo : - Tibor 2012-04-04 17:20
what do i fill in under 'DoSomething'? sorry, im new to Delphi. do i just simply type in procedure dosomething(.....) ...... and so on? or is there a way to churn it out like double-clicking on Buttons inside the Form - Matthew Cheng 2012-04-05 03:10
You can leave it like it is if you only want to write something in a Memo. If you want to do something else, you can modify the function accordingly. You can leave it as it is though - Tibor 2012-04-05 07:07
will there be a buffersize? if not, means i can let it run forever - Matthew Cheng 2012-04-10 01:33
Delphi strings are bounded only by available memory (up to 4 GiB with a 32-bit compiler). You can let it run forever - Tibor 2012-04-10 13:56
i have runned your code. it is working! however, it gives me around 30plus times of $HEHDT,288.45,T1D nice strings. then 1 string of 8 chars or 16 char i.e. $HEHDT,2 or $HEHDT,288.45,T respectively will appear then the 30plus nice strings appear again. this cycle will repeat throughout. how do i solve this - Matthew Cheng 2012-04-11 01:25
Try if LU RD's answer is working for you. I didn't really test the above code : - Tibor 2012-04-11 09:47
ok! thanks so much for your help! appreciate it alot - Matthew Cheng 2012-04-13 00:33


0

If you are using the ComPort library from Dejan Crnila, you should have a look at the TComDataPacket component, especially its OnPacket and OnCustomStop events.

Update: Here is a code example. Assuming the start string is always the same and the end string begins with ',T*' followed by two characters (perhaps a CRC code). If that assumption is wrong, the stop logic has to be adjusted.

procedure TForm136.ComDataPacketCustomStop(Sender: TObject; const Str: string;
    var Pos: Integer);
begin
  Pos := System.Pos(',T*', Str);
  if (Pos > 0) then begin
    Inc(Pos, 4);
    if Pos <= Length(Str) then Exit;
  end;
  Pos := -1;
end;

procedure TForm136.FormCreate(Sender: TObject);
begin
  ComDataPacket.ComPort := ComPort;
  ComDataPacket.StartString := '$HEHDT,';
  ComDataPacket.OnCustomStop := ComDataPacketCustomStop;
end;
2012-04-04 08:31
by Uwe Raabe
yes i am using the comport library from him. do u have any idea how i should go about changing it - Matthew Cheng 2012-04-05 01:26
You cannot change the number of characters received inside ComPortRxChar. Instead use a TComDataPacket instance wired to the ComPort to collect the data packets according to the input data. Given your example data you can set a StartString of $HEHDT, but as the ending strings differ you must decide in the OnCustomStop event when a packet is complete. Then you will get the whole packet string in the OnPacket event. This is all described in the help for TComPort - Uwe Raabe 2012-04-05 08:20
Could i trouble you to post the code of how it should be after editing the OnCustomStart & OnCustomStop? I refered to the help manual u suggested but i cant seem to make it work still. I changed "FStartString:= Value;" to "FStartString := '$HEHDT;" and "FStopString := Value;" to "FStopString := #13#10;" is this the correct way of doing so - Matthew Cheng 2012-04-10 01:43
I added a code example making some assumptions about the incoming data - Uwe Raabe 2012-04-10 08:51
sorry for the following qns, where do i enter the above codes? under my MainUnit.pas where the MainForm form is located or at the CPort.pas which contains the library. delphi asked me to declare 'method identifier' cant execute - Matthew Cheng 2012-04-11 01:22
The code belongs into the MainForm (my example uses Form136). In addition to the ComPort component you have to drop a TComDataPacket component named ComDataPacket onto the form. No need to change the library sources in this case - Uwe Raabe 2012-04-11 08:41
i am still able to display the lines on the memo before dropping the TComDataPacket component. However after adding it into the form, and following the code above, nothing shows at the memo at all. do i have to do anything at the ObjectInspector table? i tried putting the strings in at that table but it doesnt work as well. is there a way to fundamentally change the library code such that the buffersize/packetsize can be customised - Matthew Cheng 2012-04-13 00:54
Ads