Link to home
Start Free TrialLog in
Avatar of jribble
jribble

asked on

Problems interfacing with scale via COM port

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?
Avatar of Deepu Abraham
Deepu Abraham
Flag of United States of America image

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
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.
>>we have upgraded one of the computers
What do you mean? have you upgraded operating system? could you specify?
>> you'll need to introduce some handshaking.
or a pause :)
Avatar of jribble
jribble

ASKER

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.



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/
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.
Avatar of jribble

ASKER

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.  
Sorry, uploaded where? Please provide Url.
I clicked the url, but I still cannot see the source. Am I missing something?
Avatar of jribble

ASKER

Go to  http://www.ee-stuff.com/ and search by the URL.
Ah found it -- I couldn't see it before... ok, I'll look at it and get back to you :)
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. */
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.
Also can you confirm the make of scales as there are two separate code paths depending on the make?
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.)

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.)


ASKER CERTIFIED SOLUTION
Avatar of evilrix
evilrix
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of jribble

ASKER

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;
  }
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 :)
>>>>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.
Avatar of jribble

ASKER

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.

Avatar of jribble

ASKER

I split the points since both of you provided direction that led to a fix to the issue.  Thanks for your help!
Can this be done in vb .net for a simular scale?


john4875, you need to open a new question... you cannot piggy-back off someone elses.