In over my head (ladder logic) Horner PLC, serial Barcode scan

@DR pedantic lol, I see your point, but in the OP's case of the offending rung he is having a compile error on a branch with the compare & timer, I do know that in some IDE's that is illegal for compile i.e. branch for timers (I worked with a guy who developed a PLC system) I asked the same question, the answer is how far do we go in the thousands of possible permeations of branching/ outputs code, so for safeties sake we limit it.
 
If the logic is wrong, then fixing the compiler error won't help much. OP should really add those comments; it will be a form of rubber duck debugging cf. this link.

That said ...

Rung 10: the error with the [NC contact of Compare_Complete %M00014] might be fixed by moving the junction, where its feed branch leaves the main rung, back to the feed side of the [MULTI SHIFT BYTE] instruction i.e. the output side of the [NO contact of Data_Moved %00013]. But rungs 6 through 10 are a mess anyway; see below.

Rung 10 should probably be summat like
[NC contact one-shot] [MULTI SHIFT WORD SRC=%R01000 LEN=14 N=1 DIR=[ALW_OFF %S008] IN=whatever OUT=%R00098] [NO coil Read_Complete %M00012]
to shift those last two bytes, at offsets 26 and 27 into 16-bit integer %R00098, which would be either 16,963 or 17,218 (= 256*B + C OR 256*C + B; where B=ASCII code 66 and C= ASCII code 67; cf. here).
Caveat: that assumes those last two characters are at a byte offset of 26 from the first character of the string BMVed to %R01000. If that assumption is false, then there may need to be another MULTI SHIFT BYTE, with an odd value for N, performed first.
<Begin_Rant>

Rung 2 could be eliminated and the OPEN moved to a branch on rung 1 that starts after the [NO contact of FST_SCAN %S001]

Rung 3 does nothing and could be removed, or perhaps the one-shot could be used somewhere else.

Rungs 4 and 5, as noted earlier, should be re-written as a Start/Stop pattern and combined with rungs 12-14 for readability.

Rungs 6 and 7 do nothing useful; anywhere [Data_Cleared %M00105] was used, [Trigger_Read %M0011] could be used instead, so Data_Cleared could be dropped here and on Rung 15.

More to the point, the way Rungs 6, 7, and 15 are written, I am pretty sure Trigger_Read cannot ever become 1! Even if it was 1 in the first place, there is no way for it to become 0!

Rungs 8-10 will not work because Trigger_Read cannot change state. But even if Trigger_Read was fixed, Rung 10 is a mess:

  • the NO contact at the beginning should be a one-shot, as can be inferred from what @Steve Bailey wrote.
  • the fact that it is not a one-shot means that the string contents will be shifted on multiple scans.
  • The LEN and N input parameters to the [MULTI SHIFT BYTE] instruction are reversed, so it will shift more bytes (characters) than the 2-byte source string contains, and [Shift_Char %R00098] will probably have but one null character in it.
  • There were 29 characters read via the previous RECV instruction, but the thread says we are looking for 28; where is that extra character coming from and where is it with respect to the hoped-for 'BC' character pair at the end?
  • The [MULTI SHIFT BYTE] instruction puts the last item shifted in [OUT parameter Shift_Char %R00098], and "item" is "BYTE," so [Shift_Char %R00098] will contain but one of the hoped for two characters, and the second character, at (%R00098+1-byte_, wherever that is, will be essentially random, so it may never compare equal to 'BC' even if 'BC' was read in from the serial port.
 
Last edited:
I wonder if a step sequence model would be a better approach. That is, have a [Current_Step] integer keep track of where the process is, perform specific actions (or nothing) when [Current_Step] is a specific value, and increment [Current_Step] when when specific actions are done and/or expected results occur. The nice thing about this approach is that it allows the program to focus on one small thing at a time like waiting for a bit to change from 0 to 1. This small focus in turn eliminates the "too many moving parts" complexity syndrome, so errors like shifting the string on multiple consecutive scans and losing the BC characters cannot happen.
 
I actually have the data from the barcode reader showing on the top half of my screen (Full Part No) it comes in as Ascii data, 28 char as i expected...


What screen are you talking about?


How does the program know it has the latest ASCII data, and not some buffered input e.g. the last half of the previous barcode and the first half of the next one?


Can you tell us the make and model of the barcode scanner?
 
Last edited:
I was only reacting to his problem on the compile error, have seen this many times I do agree with the other rungs, rather than just move things round on that rung it makes sense to split them as I suspect that is why there is a compile error, although it states M0014 will not compile I suspect this is misleading as such, how can a branch N/C contact cause a compile error, it is more likely the combination of the branched functions like timers, compares etc.
Not having any experience on this particular platform to extract the relevant data may require a bit of thought, if the complete data was even numbers then just use a block move to move the relevant words to another area, however, this is not always the case so byte moves may be required, this will probably take a bit more code as many platforms only work on 16 bit boundaries.
There could be a function to find a string within a string but have no idea, I agree with the step sequencer, this makes complete sense.
 
Many PLC's that have communication blocks will have a register that will be populated with the number of received characters. This can be reset to zero after processing the data. The other possibility is to clear the receive buffer to nulls i.e. 0.
I have done a number of bar code systems & timing can be an issue, if possible & a received character count register is available and the valid read is always the same length then you could use the number of chars received to determine then a complete read has occurred. The alternative is to introduce a small time delay to allow all characters to be read. In most cases this should be sufficient.
Some communication blocks may even require a minimum amount of characters or termination characters to determine a read has been successful before populating the user receive buffer, so there are many options in ways to determine when new data has been received.
 
What screen are you talking about?


How does the program know it has the latest ASCII data, and not some buffered input e.g. the last half of the previous barcode and the first half of the next one?


Can you tell us the make and model of the barcode scanner?
The screen of the Horner


The scanner is a Datalogic Gryphon GFS4450-9

Screenshot 2021-09-22 083823.png 20210922_083233.jpg
 
...I actually have the data from the barcode reader showing on the top half of my screen (Full Part No) it comes in as Ascii data, 28 char as i expected... I'm not clear on how to compare only the last 12 digits with a known string for the pass/fail though, i'm maybe close.

How is that Display Element/Screen configured? What address(es) is it displaying?

...We realized that all i need to check for is the last two characters of the barcode are "BC" ...

Then we may not have to do a shift to extract those last two characters. If the 28-character string has been moved with BMV(word) into register %R01000, then we also know that
  • the string occupies 14 consecutive 2-character (= 2-byte = 16-bit) registers, and
  • the last two characters of that string are in the 2-byte register %R01013 (1013 = 1000 + 13)
  • so we can do a direct string compare of that location, %R01013, against the string constant 'BC'
If there is an extra byte in front of the string, then the two-character string to test, and that we hope is BC, will be split across the second byte of %R01013 and the first byte of %R01014. In that case, we can do a 1-byte left-shift of the 3-byte string starting at %R01013 to move those two characters into 2-byte register %R01013, and then test.

It is also possible to treat %R01013 as 2-byte integer and perform an integer compare. If the string is split across %R01013 and %R01014, then we can mask out the unneeded bits via the AND(word) instruction and the constants -256 and 255 and do two integer compares; this would require knowing the byte order of the PLC, it is not difficult to implement and does not require restricting a shift to execute exactly once, but it is is more complex would require good comments to understand three months hence.

<Restart_Rant>


Rungs 8 and 10: the RECV on Rung 8 writes to the 29 bytes starting at %R00001, but the TON on rung 10 uses two words starting at that same address, %R00001, so the RECV is set up to interfere with timer operation, although the order of operations may prevent that from happening. In any case, that timer is not needed as noted by @Steve Bailey, so refactoring to remove it solves the problem.



Rung 9: the BMV(word), as shown in the last PDF from OP, moves 29 items starting from %R00001 into the location starting at %R01000; since each item is one 16-bit word i.e. 2-byte word i.e. 2-character word, that actually moves 58 characters (= 29 words * 2 characters/word).

Note that we are only interested in two characters, contiguous and spread over at most two words, so that BMV(word) could move only two items (words) starting at register %R00014, i.e. 13 registers, which is 26 characters, offset from %R00001.

Regarding the BMV, the shift and the compare: they should all be executed whenever Read_Completed is 1; there is no need for redundant bit Data_Moved.
 
what scan mode is the Gryphon configured to use (Trigger Single; Flashing; Always On; etc.)?

What are the timings used (double-read timeout, etc.)?


Is configured with a Global Prefix or Suffix?


The default is no Global Prefix and a carriage return (ASCII 13 decimal = 0x0D) Suffix. If that is the case, a simpler approach may be to

  • Set up to read one character at a time (RECV Bytes=1) e.g. into %R01000
  • On a rising edge of Read_Completed,
    • MULTI SHIFT BYTE
      • SRC=%R01000
      • LEN=4
      • N=3
      • DIR (left) = ALW_OFF %S008 <= shift right.
      • IN=0
      • OUT=%Rdon't_care
    • When %R01000 is either 13 or 3328 (=13 * 256)
      • a carriage return (i.e. the global suffix) has been received and is in the second byte of %R01000
      • and the last two characters received before that carriage return are in register %R01000, which will be either 16963 or 17128 as a word, or hopefully 'CB' as a string (the characters are reversed by this algorithm).
      • I am pretty sure the PLC's INTs are LSByte first, so the shifted carriage return word will be 3328, and the CB will be 16963.
 
Last edited:
You guys are great... Me, I'm getting overwhelmed and barely keeping up though.



I maybe would like to simplify things, as i think the redundancy you see is a combination of me still not grasping ladder logic well and the programmer that stopped, being in a rush to run through it with me so he could avoid the 4 hours we paid for. But simplifying seems to just get me more confused.





It is semi-working, having some luck with the compare string and getting a fail when i scan an incorrect barcode. I need to re-scan when there's a fail though, probably some sort of reset if i have fail and break PART_POS1
 
I have a lot of experience with serial communications, and I am not going for simple, I am going for robust, but they are often the same thing. Reading 29 characters per rising edge of Read_Complete will work - until it doesn't: all that needs to happen is one short 27-character bar code to be scanned; it could be caused by EMR noise near the RS-232 cable. Then, instead of seeing a sequence of 29-character reads ending with a carriage return (<CR>) character like this:
abcdefghijklmnopqrstuvwxyzBC<CR>
abcdefghijklmnopqrstuvwxyzBC<CR>
abcdefghijklmnopqrstuvwxyzBC<CR>
abcdefghijklmnopqrstuvwxyzBC<CR>
...
the RECV/read will set Read_Complete to 1 after each of these 29-character reads:
abcdefghijklmnopqrstuvwxyzBC<CR>
abcdefghijklmnopqrstuvwxyzB<CR>a <= Note 1
bcdefghijklmnopqrstuvwxyzBC<CR>a
bcdefghijklmnopqrstuvwxyzBC<CR>
a
...

Note 1: the Gryphon scans - and sends - a short 27-char code followed by a carriage return, with final 'C' missing, to be read by the RECV instruction; in the PLC the <CR> is effectively shifted one to the left and the leading 'a' from the next (blue) scan fills in as the 29th character read. So the Read_Completed does not become 1 until 29 characters are read - the RECV is only counting 1-byte, 8-bit characters, it is not parsing where the carriage return occurs.

Now the code is comparing 'C<CR>' to 'BC' on every Read_Completed. And the code will never recover, unless one long 29-character code is scanned accidentally.

I just realized, if we RECV/read one byte at a time into %R01014, and do a 1-byte left MULTI SHIFT BYTE, of 29 bytes starting at %R01000, on the rising edge only of Read_Complete whenever the character received is not the carriage return (register word %R01014 is not 13), then we can keep using the 28 characters starting at %R01000, and %R01013 will be 'BC' (or not) when %R01014 is 13.

The nice part about this approach is that it can run continuously (so the display will update), and only needs to be checked when the part is in position 1.

That said, we may need to turn the RECV/read off for one scan after a Read_Complete to ensure Read_Complete returns to 0 after each character, because the PLC might not be able to keep up with 9600baud * ~1(bit/s)/baud * 1character/~10bit = 1000character/s.

Again, how is the Gryphon configured with regard to triggering? Note that we can use the SEND instruction so transmit an E or D to Enable or Disable, respectively, the scanner, and we could make that dependent on rising and falling edges of the prox for position 1.
 
Last edited:
Again, how is the Gryphon configured with regard to triggering? Note that we can use the SEND instruction so transmit an E or D to Enable or Disable, respectively, the scanner, and we could make that dependent on rising and falling edges of the prox for position 1.




The scanner is currently configured as "Serial On Line"


It says that scanning is initiated through serial commands (looking those up now)
 
Here is one (Not my code) but have done minor mods to show you how to deal with good/bad reads, note: this one uses actual addresses but in Mitsubishi you can have strings & arrays pointed to the same addresses seems weird but it works there are ways to extract (well delete unwanted chars in Mitsubishi) but the original programmer decided to do it this way, the variables are for example actual addresses so
D100 to D119 is labelled as an array of word 20, it is also labelled as a string 32, this allows the use of labels or symbols rather than direct addresses,
so the array Saved_Data is D100 to D119, by using indexes Saved_Data[0] is D100, Saved_Data[1] is D101 and so on.
It can also be labelled as a string so D100 to D131 is a string of 32 chars.
This code uses latches as a sort of step sequence, however, looking at your last post it seems you want to re-try a read if it fails, so as DR suggested, use a sequence variable like this

Not running the SEQ_Var is at 0
Start the system, Move 10 into sequence word
Compare the sequence word with 10 if equal and part on sensor trigger read, move 20 into seq_Var & so on.
So write out the sequence of the steps, number them I suggest in increments of 10 this gives you some spare numbers if you need to add steps.
Example:
seq_Var = 0
Part reaches Photocell, Put 10 in Seq_Var
Compare Seq_Var with 10 if equal then trigger read, move 20 into Seq_Var
Compare Seq_Var with 20 if equal then wait for bar code, bar code read, move 30 into Seq_Var,
Compare Seq_Var with 30, strip the required fields from the data, move 40 to Seq_Var
Compare Seq_Var with 30, compare the stripped barcode with required code if same move 40 to Seq_Var, if not move 10 to Seq_Var (this goes back to trigger read again).
and so on.....
This way you have a step sequence for each stage with the ability to jump back to steps you may need to do again.

The code attached works, I used a PC with a quick barcode simulator written in pascal to simulate the barcode so have not shown the blocks of code for communication as it will not resemble what you have to do.
 
There are more bells and whistles to add, but this should provide the basis of a robust algorithm to

  • read the scanner,
  • detect a received carriage return,
  • check the two characters received previous to the carriage return, and
  • assign the success and failure bits accordingly.
 
Last edited:

Similar Topics

Good Afternoon , I'm sure there are many Ishida Muti-Head Weigh Systems . We have a Ishida CCW-M-214W Multi-Head system . We are...
Replies
1
Views
499
Hi all, have a project which requires reading a weigh head weight over ASCII. Never really touched ascii so struggling a bit. The weigh head is in...
Replies
11
Views
1,233
Good Morning , We have a Ishida Multi-Head Scale . I had experiece with them for chips , crackers , etc. , but now we are using them for...
Replies
7
Views
451
Hello expert I have migrate legacy project that use Applicom OPC DA with 3rd client and S7-400. I see in client code add item string call...
Replies
0
Views
359
Got another one a whole team of us and the OEM cannot figure out. Powerflex525, 20HP VFD, commanded over Ethernet won't turn a motor it's turned...
Replies
32
Views
7,855
Back
Top Bottom