Solved

Problems interfacing with scale via COM port

Posted on 2007-11-21
27
506 Views
Last Modified: 2010-04-21
We have a C++ application that has been in use for a long time.  One of the things this application does is interface with a Rice Lake UMC555 scale.  Recently, we have upgraded one of the computers this applications runs on and have started seeing intermittent problems.  Here is a description of the problem:

Occasionally, when the user clicks the "Scale" button, the application reads and displays an "old" data value from the COM port (this is typically the prior weight that was on the scale).  The scale display shows the correct weight on the scale's display.  If the user clears the value and clicks "Scale" again, then the correct value is read and displayed.

Here is a summary of how the application is reading from the port:

When the "Scale" button is pressed, the application reads up to 255 bytes from the COM port.  Then another method parses the buffer to extract the weight.

I am not sure how COM ports work.  Does old data get buffered?
0
Comment
Question by:jribble
  • 11
  • 7
  • 4
  • +4
27 Comments
 
LVL 11

Expert Comment

by:DeepuAbrahamK
ID: 20327632
Is that the intention?

What is the clear functions doing? Is it flushing the buffer? Try calling the same an use it?

Best Regards,
DeepuAbrahamK
0
 
LVL 40

Expert Comment

by:evilrix
ID: 20327725
So, the 'Scale' button is a software button int he UI?

I suspect this is a timing issue. Does the PC wait for a signal on the COM port to indicate the scale competed writing to it before it is read? If not then what I suspect is happening is that the newer (and presumably faster) PC is able to send the request and then scan the port for the response before the new value has been written to it so it is reading the old cached weight. When you clear and click scale again it is then getting the correct weight, probably cached from the last request.

To resolve this (if this is indeed the problem) you'll need to introduce some handshaking.

-Rx.
0
 
LVL 55

Expert Comment

by:Jaime Olivares
ID: 20327730
>>we have upgraded one of the computers
What do you mean? have you upgraded operating system? could you specify?
0
 
LVL 40

Expert Comment

by:evilrix
ID: 20327733
>> you'll need to introduce some handshaking.
or a pause :)
0
 

Author Comment

by:jribble
ID: 20327960
evilrx,

The scale is in CONTINUOUS mode.  Transmission occurs at the end of each display update.  We are weighing trucks, so it is typically many seconds between the time the scale settles and a weight is taken.  Wouldn't the COM port have the data then?  I am pretty clueless to how all of this works, maybe you could explain.  Do you know how a COM port buffers its data?

As far as our software goes, it starts a thread that refills a buffer from the COM port (one byte at a time) each time the Scale button is pressed.  It fills up to 255 characters and then stops (it looks like it was supposed to be circular, but has some incorrect logic that prevents that).  Before reading from the buffer, we verify that data has been written to the buffer.  Let me know if you think the source woud help.



0
 
LVL 40

Expert Comment

by:evilrix
ID: 20328073
Obviously I have no idea how your software works but the very intermittent nature suggests a timing issue.

By all means make the source code available and I can have a look. http://www.ee-stuff.com/
0
 
LVL 8

Expert Comment

by:Anthony2000
ID: 20328075
Before the upgrade, you had no "intermittent problems"? What was upgraded? Operating System, hardware, etc.

If were not having strange problems before, then it might help to understand how the upgrade has effected the reading of the COM port.

To answer you question:
I am not sure how COM ports work.  Does old data get buffered?

Its not a question of "old data", rather bytes coming in the port are buffered (to a point) until the application reads them. Once the application has read them all. There should not be any left in the serial buffer.
0
 

Author Comment

by:jribble
ID: 20328295
I uploaded two files (serial.cpp and st.cpp).  Start with ScaleRead.  Look closely at ReadPort.  That method appears to have been written to continuously refill the buffer, but stops after the first 255.  
0
 
LVL 40

Expert Comment

by:evilrix
ID: 20328364
Sorry, uploaded where? Please provide Url.
0
 

Author Comment

by:jribble
ID: 20328628
0
 
LVL 8

Expert Comment

by:Anthony2000
ID: 20328983
I clicked the url, but I still cannot see the source. Am I missing something?
0
 

Author Comment

by:jribble
ID: 20329042
Go to  http://www.ee-stuff.com/ and search by the URL.
0
 
LVL 40

Expert Comment

by:evilrix
ID: 20329612
Ah found it -- I couldn't see it before... ok, I'll look at it and get back to you :)
0
Maximize Your Threat Intelligence Reporting

Reporting is one of the most important and least talked about aspects of a world-class threat intelligence program. Here’s how to do it right.

 
LVL 40

Expert Comment

by:evilrix
ID: 20329784
This comment in ReadScale doesn't fill be with optimism..

/* communications buffers for port should be empty to minimize possibility of getting old buffered data. */
0
 
LVL 40

Expert Comment

by:evilrix
ID: 20330770
Ok, I've spent a little time going through this code and I more or less understand it now. I can't currently see any immediately obvious reason for the incorrect read. I note there is trace produced during the read. Is it possible to generate a trace log and post that too? Ideally, it would be good if the trace could include an instance of when this goes wrong (you'll need to identify the false read so I can find it in the log). Hopefully the trace log will give a clue.

-Rx.
0
 
LVL 40

Expert Comment

by:evilrix
ID: 20330780
Also can you confirm the make of scales as there are two separate code paths depending on the make?
0
 
LVL 1

Expert Comment

by:yyyc186
ID: 20332109
It has been a long time since I wrote scale software.  I did several projects back in my "yoot" for Waste Management.  I used C and the GreenLeaf CommLib back then.  The principal should be the same.

When I did this, I created a configuration file and instructed the serial reading logic how to determine an end of packet from a scale.  You could end a packet on:

 "size" being the number of bytes if the scale was sending fixed length packets.
"single char" meaning the packet had a constant ending character, but no consistent begin char.
"framing chars"  packet had both a fixed starting char and a fixed ending char or chars.

The CommLib product wrote all characters to a ring buffer.  You read from the ring buffer periodically, pretty much emptying the buffer.  At the tail end of it was the packet you wanted.  This was in a "leveling" or "settling" routine where we would compare a configured number of weight reads from the scale and pick the most frequent.

This worked for "most" scales.  I'm assuming your developer took a shortcut, used some generic C serial port routines they found somewhere, and only coded one supported method.  That would be the first reason you would have this problem.

The second reason you would have this problem is the ever problematic "check digit".  You can tell if it is a check digit problem without any complicated hardware.  Does your problem always happen with trucks in the same weight range?  (i.e. around a 14000 pound empty weigth or 80000 pound full weight).

Check digits occur AFTER the ending character for the packet when sent by most scale vendors.  Problem is, when you are reading scale packets via "ending char" they have a tendency to cycle around to those very same values (had this problem with a fairbanks scale once).

There are two fixes for the check digit problem.  In your read loop, when you find your first ending character, nuke the next byte after it.

Have the scale vendor burn you a new EPROM for the scale without the check digit turned on.  (If you bought one of the really expensive scales there is a dip switch or something which will turn check digit on/off.)

0
 
LVL 1

Expert Comment

by:yyyc186
ID: 20332169
After re-reading your request and looking at the code, I think I see your main problem.  The first problem is that this code doesn't have an ISR (Interrupt Service Routine), it is trying to read the serial port in what we call "polled mode" treating it as a file and doing it in a Thread.

This code was originally written for OS/2, I don't know what platform you just "upgraded" to, but here is the gist of the problem:

    /* Store character if buffer is not full. */

    if (((ports[port].read_in_ptr+1) % SERIAL_BUFFER_SIZE) != ports[port].read_out_ptr)

      {

      ports[port].read_buff[ports[port].read_in_ptr++]=ch;

      if (ports[port].read_in_ptr==SERIAL_BUFFER_SIZE) {

        ports[port].read_in_ptr=0;

            LOG_FORMATTED_TRACE("Reset read_in_ptr in ReadPort");

        }

      }


You are in polled mode on a faster machine.  This code was always broken, but you didn't notice on a slower machine.  The line which sets read_in_ptr to 0 is causing you to get "old weights" because it is setting the pointer back to the beginning of the buffer yet it isn't blanking the buffer with a memset() to '\0'.

The "quick fix" which isn't a fix, but a work around, is to change the constant SERIAL_BUFFER_SIZE to be at least double the size it is now and do a full rebuild.

The long term fix is to find a copy of GreenLeaf CommLib for your current platform (or another serial communications library which uses ISRs and ring buffers correctly.

The "mid term fix" is to rewrite the section of code which handles "buffer full" so it actually implements a ring buffer.  (Not as straight forward as it sounds, the rest of the code seems to rely on filling up one way.)


0
 
LVL 40

Accepted Solution

by:
evilrix earned 250 total points
ID: 20333094
@yyyc186 - Thanks for your thoughts on this. Below I've commented on a few points you make. If you have the source still maybe you could review my comments and see if you agree with my understanding.

>> "single char" meaning the packet had a constant ending character, but no consistent begin char
It seems CR (Fairbanks) and LF || ETX (other) is/are used for this.

>> The second reason you would have this problem is the ever problematic "check digit".  
A checksum is provided as the last char and appears to be validated

>> The line which sets read_in_ptr to 0 is causing you to get "old weights" because it is setting the pointer back to the beginning of the buffer yet it isn't blanking the buffer
If you look at get_com_char(), which is only called after purge_com_buffers(), you'll see it only returns a char if in != out. After the purge they are both the same so COM_NO_CHAR will be returned so the 'old' buffer won't be read until new data is placed into it. There is also code called after a purge to try and remove 'any partial message', although I'm not sure what this actually achieves.

>> rewrite the section of code which handles "buffer full"
As far as I can tell there are two buffer in play here. There is the com port buffer, which is implemented as a ring buffer. There is also the ReadScale buffer, which is not. The com port buffer can never get full as it will always wraps around. The ReadScale buffer is 80 bytes and if this gets full it is considered an error.

@jribble

How does the scale know to send it's reading? Is this elicited by sending the scale a message or does it just 'push' the reading? Does the scale cache the reading so if it is re-read you'd get the same value? If so I'm wondering if you are re-reading the old value before the scale has had a chance to reset itself. The problem with this code is it is all based upon timings rather then events. It only needs one thing to be out of sequence and everything else will get muddled up. As I stated above, uploading a trace log containing a failed read would help determine what is going wrong here.

Also, is this code is from a vendor? If so have you asked them? Maybe have have more up-to-date code that fixes this problem (1997 was the last update in the code!).

-Rx.
0
 
LVL 1

Assisted Solution

by:yyyc186
yyyc186 earned 250 total points
ID: 20335489
>>>It seems CR (Fairbanks) and LF || ETX (other) is/are used for this.

You cannot go by manufacturer.  Different models of scale from the same vendor will use different ending characters or combinations of them.  CR is very common and so is ETX.  Normally when you see a scale sending ETX for end of packet you had an engineer that was nice enough to send STX as the first char in the packet.

>> The second reason you would have this problem is the ever problematic "check digit".  
>>>>A checksum is provided as the last char and appears to be validated
I never said it was incorrect.  It will be correct even when it causes the problem.  The checksum is a single character.  Put the correct weight on the scale and the checksum will be the same either as the starting character or the ending character of the packet.  I've had scales with this problem before.  Typically, we would have the vendor disable the checksum since we were doing our own leveling in the one or two bad packets in a stream were noting to us.



>> The line which sets read_in_ptr to 0 is causing you to get "old weights" because it is setting the pointer back to the beginning of the buffer yet it isn't blanking the buffer
>>>>If you look at get_com_char(), which is only called after purge_com_buffers(), you'll see it only returns a char if in != out. After the purge they are both the same so COM_NO_CHAR will be returned so the 'old' buffer won't be read until new data is placed into it. There is also code called after a purge to try and remove 'any partial message', although I'm not sure what this actually achieves.

The "partial message" code is there to ensure you read a full packet and not a puddle of good.  No matter who writes the software they have to do this.

Are you running this software on OS/2?  That is the OS it was originally written for.  I never used this stuff even when I ran OS/2.

You said you upgraded a machine, but you never answered the question about what they upgrade was.  I'm assuming you upgraded the machine and loaded the old OS with the old software on it.  Your machine is much faster now.  Am I correct?

On a very slow machine, the read is happening the way you think it is, but you are running on a much faster machine.  The "purge" doesn't actually "purge".  It is simply resetting the pointers leaving tramp data in the buffer.  There is no memset() call to null out the buffer.

Take a VERY good look at the code in ReadPort().  On a 66Mhz 486 this code will mostly work with only intermittent problems.  On a 1Ghz machine with only an 80 byte buffer it will fail miserably.

You say there is a "purge" called before the read and a nibble routine to get you to the start of a scale packet.  Fine.  The ReadPort() routine is running in a separate thread refilling that buffer well before you manage to take a read.  What happens in this routine when the buffer is full?  It throws away all additional inbound characters, leaving the buffer full of "old weights".  If your scale packet is only 16 characters, your buffer can only hold 5 packets before filling.

>> rewrite the section of code which handles "buffer full"
>>>>As far as I can tell there are two buffer in play here. There is the com port buffer, which is implemented as a ring buffer. There is also the ReadScale buffer, which is not. The com port buffer can never get full as it will always wraps around. The ReadScale buffer is 80 bytes and if this gets full it is considered an error.

You don't have an actual "ring buffer".  A true ring buffer would not throw away inbound characters once it was full.  It would move the read_out_ptr forward one char and allow the read to continue.  A ring can never "get full".

>>>>How does the scale know to send it's reading? Is this elicited by sending the scale a message or does it just 'push' the reading? Does the scale cache the reading so if it is re-read you'd get the same value? If so I'm wondering if you are re-reading the old value before the scale has had a chance to reset itself. The problem with this code is it is all based upon timings rather then events. It only needs one thing to be out of sequence and everything else will get muddled up. As I stated above, uploading a trace log containing a failed read would help determine what is going wrong here.

Every scale I've worked with is in constant send mode.  The baud rates very between 300 and 4800 baud usually.  Faster isn't better since it increases the chance for error on the exposed cable.  300 baud is fast enough given a scale packet is usually 20 bytes or less.  The sending of characters is controlled by the RS-232 protocol and hardware handshaking with the serial port.  Some scales use XON/XOFF, but most use CTS/RTS.

Some scales (not the cheaper ones) have a settling function which can be turned on in the scale control unit where you are plugged in.  The settling function will only allow the control unit to send a packet if the last 2-3 sampled readings are the same.  You still end up getting 3-6 packets per second out of them.

>>>>Also, is this code is from a vendor? If so have you asked them? Maybe have have more up-to-date code that fixes this problem (1997 was the last update in the code!).

This code was part of the IBM tool kit for OS/2.  Read the header at the top in serial.cpp.  1997 would be about correct since IBM sunset OS/2 around then.

If you still have the MSC 6 compiler (I certainly don't, like much of the industry I've abandoned all MS products including their OS) and the build environment, you can fix this code by making the ring buffer actually work like a ring.  If you are going to do this, DO NOT increase the buffer size.  You only have enough buffer to hold the last 4-6 scale reads.  I haven't taking the time to see if you are doing your own settling logic, nor do I care to invest that much time at this point.

Try replacing the logic I've questioned with tis:

    if ((ports[port].read_in_ptr+1) == ports[port].read_out_ptr)
    {
        ports[port].read_out_ptr++;
        if (ports[port].read_out_ptr => SERIAL_BUFFER_SIZE)
            ports[port].read_out_ptr = 1;  // throw away chars at 80 and 0
    }
    else
    {

      ports[port].read_buff[ports[port].read_in_ptr++]=ch;

      if (ports[port].read_in_ptr==SERIAL_BUFFER_SIZE) {

        ports[port].read_in_ptr=0;

            LOG_FORMATTED_TRACE("Reset read_in_ptr in ReadPort");

        }

      }


I haven't had time to compile or test this, but basically you need to reset the output to the second character of the input buffer tossing two characters away.  There are better ways to do this, but this is the least invasive in the code.  No input characters will be thrown away, just stale input you haven't read yet.

The problem with your thread logic is it didn't implement a ring buffer correctly.  It is saving the OLD data, and throwing away the NEW.
0
 

Author Comment

by:jribble
ID: 20349920
yyyc186/evilrix;

Thank you both for your very thoughful insight into this problem.  

yyyc186:

I think I understand the code change you recommended.  The "else" section will keep filling read_buff so the latest data in read_buff will be the new data (assuming we give this thread enough time to empty the COM port data before we start reading from the buffer).

The "if" section will only be true if we have started reading from the buffer and the read_in_ptr "catches up" to the read_out_ptr.  In this case, we increment the read_out_ptr so we can read in the next character.  I'm not sure why we throw away the characters at 80 and 0, but I'll defer to your expertise on that one.

The only concern I have with the "if" statement is this, what if the get_com_char function (show below) and your new function are both incrementing read_out_ptr, wouldn't we miss a character in get_com_char in this case?

Thanks again!

// Recommended code
    if ((ports[port].read_in_ptr+1) == ports[port].read_out_ptr)
    {
        ports[port].read_out_ptr++;
        if (ports[port].read_out_ptr => SERIAL_BUFFER_SIZE)
            ports[port].read_out_ptr = 1;  // throw away chars at 80 and 0
    }
    else
    {

      ports[port].read_buff[ports[port].read_in_ptr++]=ch;

      if (ports[port].read_in_ptr==SERIAL_BUFFER_SIZE) {

        ports[port].read_in_ptr=0;

            LOG_FORMATTED_TRACE("Reset read_in_ptr in ReadPort");

        }

      }

// get_com_char
int get_com_char(int port, short *ch)
  {
  /* Request semaphore to access incoming buffer for the port. */
  EnterCriticalSection(&(ports[port].read_sem));

  /* Get character into ch if there is one. */
  if (ports[port].read_in_ptr!=ports[port].read_out_ptr)
    {
    (*ch)=(SHORT) ports[port].read_buff[ports[port].read_out_ptr++];
    ports[port].read_out_ptr=ports[port].read_out_ptr % SERIAL_BUFFER_SIZE;
    }
  else
    (*ch)=COM_NO_CHAR;

  /* Clear semaphore to access incoming buffer for the port. */
  LeaveCriticalSection(&(ports[port].read_sem));

  return 0;
  }
0
 
LVL 40

Expert Comment

by:evilrix
ID: 20349943
Very welcome, sorry I wasn't able to pin-point the exact cause (although my original diagnosis was, ostensibly, correct,) but it seems yyc186 has managed to do that for you -- so it's all good :)
0
 
LVL 1

Expert Comment

by:yyyc186
ID: 20351025
>>>>The "if" section will only be true if we have started reading from the buffer and the read_in_ptr "catches up" to the read_out_ptr.  In this case, we increment the read_out_ptr so we can read in the next character.  I'm not sure why we throw away the characters at 80 and 0, but I'll defer to your expertise on that one.

I chose to skip two characters because it was easier than figuring out the proper algorithm.  You cannot set it to zero because the logic for the other pointer sets it to zero when it hits the end of the buffer.  If you both hit zero at the same time the write pointer will pass the read pointer.  I am, of course, ass-u-me ing that your read logic has a place in it checking for full packet read, so skipping two characters will only cause you to throw away an old packet you aren't interested in.
0
 

Author Comment

by:jribble
ID: 20451327
Well, we did finally get the scale interface to work.  The problem seemed to hinge around the fact that we were opening the COM port on the first read and leaving it open for subsequent reads.  Thus we were getting some "stale" data.  We changed the code to open and close the COM port on each read.

We also ended up removing the ring-buffer and the ReadPort thread. They were implemented for OS/2 which didn't provide an asynchronous serial device driver that would read the incoming bytes and provide them to the program. So that st.cpp and serial.cpp code was an implementation of that asynchronous device driver with the ring-buffer.  Since Windows NT provides that service (as we saw when we discovered the old data caching), we figured our ring buffer was unecessary.

0
 

Author Closing Comment

by:jribble
ID: 31410359
I split the points since both of you provided direction that led to a fix to the issue.  Thanks for your help!
0
 

Expert Comment

by:john4875
ID: 23446611
Can this be done in vb .net for a simular scale?


0
 
LVL 40

Expert Comment

by:evilrix
ID: 23447121
john4875, you need to open a new question... you cannot piggy-back off someone elses.
0

Featured Post

Find Ransomware Secrets With All-Source Analysis

Ransomware has become a major concern for organizations; its prevalence has grown due to past successes achieved by threat actors. While each ransomware variant is different, we’ve seen some common tactics and trends used among the authors of the malware.

Join & Write a Comment

This article shows you how to optimize memory allocations in C++ using placement new. Applicable especially to usecases dealing with creation of large number of objects. A brief on problem: Lets take example problem for simplicity: - I have a G…
Basic understanding on "OO- Object Orientation" is needed for designing a logical solution to solve a problem. Basic OOAD is a prerequisite for a coder to ensure that they follow the basic design of OO. This would help developers to understand the b…
The goal of the video will be to teach the user the difference and consequence of passing data by value vs passing data by reference in C++. An example of passing data by value as well as an example of passing data by reference will be be given. Bot…
The viewer will learn how to clear a vector as well as how to detect empty vectors in C++.

705 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

19 Experts available now in Live!

Get 1:1 Help Now