Show Posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Messages - garysdickinson

Pages: [1] 2 3 ... 31
Technical support / Re: RS485 Baud Rate Between Nano-10 and Fx1616-BA
« on: June 06, 2020, 12:07:09 PM »
If you are using serial ports or Ethernet to interconnect PLCs you will find that the latencies from on PLC to another are partly do to data transfer rates.  Your measurement latencies at 3 different BAUD rates gives you a big hint.

Your BAUD rate range is roughly 1:3 but your measured latencies are only about 1:1.4.  Most of your latency is NOT due to communication speed.

Ok.  Where does the rest of the time get eaten up?
  • The TRI low level firmware handling of Modbus.  This code is actually pretty fast.  There are a few things that you can change that affects the RS485 driver turnaround delay.
  • Ladder logic scan rate. If you are setting an OUTPUT on the slave PLC, that OUTPUT will not change until the end of the ladder logic scan.

To minimize latencies you will need to to ensure that your ladder logic scan rate is FAST. These are settings that you can change to maximize the scan rate:
  • SetSystem 3,0  RS485 communications are 1/2 duplex.  The default behavior of the PLC when responding to an communication from a RS485 port is to delay for 10 ms to allow the master device to quit driving the communication bus before the PLC turns on its drivers and responds as a Modbus slave.
    This behavior fixes a problem with dumb USB to RS485 dongles and RS232 to RS485 gizmos.  Those devices have no knowledge of the communication protocol. As a result those devices just will keep driving the RS485 bus after the end of the Modbus query and no new characters arrive to be transmitted.  This can be many ms.

    You are not using these sorts of dumb devices.  The SetSystem 3,0 statement cuts the turn around delay from 10 ms to as close to 0 as possible add this SetSystem statement to every PLC that is on the RS485 cabling.
  • SetSysetm 16,1  for Nano-10s. This statement "tells" the PLC how many digital I/O lines are active.  Your Nano-10s only have 4 sets of I/O lines, but the PLC firmware will still scan 128 inputs at the start of the ladder logic scan and then update 128 outputs at the end of the ladder logic.  Using this SetSystem statement will save measurable time on the Nano-10s.
  • SetSystem 16,n. Fx series PLCs.  If you are only using 16 digital I/Os use SetSystem 16,1.  If you are using 32 or less digital I/Os then SetSystem 16,2 is the right answer.

Rerun your latency tests at a single BAUD rate. Add the SetSystem statements one by one and rerun the tests.  You will see reductions in latencies.  And it’s good science...

Gary Dickinson

p.s.  I attached a PDF of a bit of PLC code that you should add to your test PLCs. This code will allow you to directly measure the PLC scan rate with the test equipment that you already have.

Technical support / Re: How to communicate with server (Nano10)
« on: April 03, 2020, 12:36:00 PM »
The Nano-10 has built in support as a slave device via Modbus TCP/IP.  This is an industry standard communication protocol.  A server can access the PLCs, INPUTs, OUTPUTS, RELAYS, DM[], DM32[] and several other PLC internals.

This is how most HMI devices interact with PLCs.  This is how remote data logging is done with PLCs.

Modbus TCP/IP requires little or no software support in the .pc6 program for the Nano. If your server can act as a Modbus TCP/IP server then most of the programming is on the server end.

This is not the only approach that you can use, but it is my favorite. 

Gary Dickinson

Technical support / Re: People Counter
« on: March 16, 2020, 10:40:15 PM »

In trying to explain how the counting mechanism worked, I realized that my algorithm was not as robust as it could be.

So I modified the code and attached it to this post as V3.

Please look at the state machine diagram in an earlier post:;topic=2429.0;attach=235  as reference.

The idea of the new code is to ensure that the state machine went through all 4 states before updating the count of "people".  I used the #define mechanism to carve out two 32 bit variables:
  • TicCnt - count of the number of states from state 0. This value increments when going clockwise else it decrements
  • TotalCnt - running count of "people"

This is the the "new V3" version of the NextSate CF:
Code: [Select]
' NextState - This CF is called on each new state that is to the "right" of
' The previous state. Think of this as going clockwise through the states.
' 0-->1-->2-->3-->0-->1-->2-->3-->0 ...
' On entry: StateCntr has been updated and represents the current state
' On exit:
' RELAY[2] is updated the patterns for the next legal states
' TicCnt will be updated
' TotalCnt may be incremented
RELAY[2] = DM[StateTableBase + CtrPV[StateCntr]]
TicCnt = TicCnt + 1 ' we have advanced clockwise one state

' If the current state is 0. Check to see it we should increment
' the TotalCnt value.
If CtrPV[StateCntr] = 0

if TicCnt = 4
TotalCnt = TotalCnt + 1 ' We have gone one full turn

' The TicCnt is reset on transitions to state #0
' This makes it easy to "know" when to increment the TotalCnt value.
' The other issue is that the PLC scan rate must be significantly faster than
' the objects being counted. As there are 4 states the scan ran has to be
' about 8x faster in order to keep up.
TicCnt = 0


The PreviousState CF is substantially similar and deals with the counter-clockwise case.

This is a 1x decoder that only counts once each time the state machine goes full circle.  There are 2x and 4x encoders that count 2x or 4x for complete loops through the state machine.

Best regards,

Gary Dickinson

Technical support / Re: ADC not being read
« on: February 29, 2020, 09:54:36 PM »
This is a screen shot showing the on-line debugger running in i-TRiLOGI Version 6.6 build 04.  The PLC is a Nano-10.

ADC(1) and ADC(2) are being accessed, periodically, as part of the PLC program. The ADC inputs are about 3.71 and 4.07 VDC relative to the analog ground reference. 

Note the non-zero values for ADC 1 and 2 on the screen shot. 

I suggest that you disconnect anything that you may have connected connected to your PLC (ADC) inputs. Now jumper the +5V analog reference voltage to one of the ADC inputs. This is the reference voltage used by the PLC ADCs and this voltage should result in the ADC measuring the full scale value of 4095 counts.

As noted by TRI support the PLC must be executing a PLC program that periodically "reads" the ADC values.  Otherwise, the registers accessed by the on-line debugger will return a value of zero.

Another reason that you may get a zero reading is that voltage measurement is relative to the AGND input.  If you try to measure the voltage of a D cell battery, connecting only the + end of the battery to ADC 1 will get you a zero reading.  You must connect the - end of the D cell to the AGND, also.

Gary D

Technical support / Re: People Counter
« on: February 18, 2020, 01:58:20 PM »

I couldn't sleep the other night and kept thinking that a quadrature decoder could be done with even less code. I got it down to 3 rungs of ladder logic and 3 tiny custom functions.

This is a state machine based approach.  There are no edge triggered events. I use a ladder logic COUNTER to manage the state tracking and a table of data in DM[] that is used to determine when to change state.

Lorne found a bug in the state transition table.  I have corrected this mistake and attached a new version of the .pc6 file.

Best regards,

Gary Dickinson

Technical support / Re: People Counter
« on: February 14, 2020, 09:44:55 PM »

Nice post. It made me have to do some thinking.

I decided to simplify your code a bit and demo how to use PLC COUNTERs to keep track of the running count. It isn't that hard, but it doesn't handle negative numbers.

The high-speed counter mechanism is probably a better approach than ladder logic for most applications. The biggest limitation is the PLC ladder logic scan rate needs to be significantly faster the rate that the A and B inputs change state.

Best Regards,

Gary Dickinosn

Technical support / Re: 16 BIT TO 32 BIT
« on: January 23, 2020, 03:10:14 PM »

I can think of 3 reasonable approaches:
  • Use the SetHigh statement to build a 32 bit variable in A:
    A = RELAY[2]
  • Build a 32 bit variable in A
    A=RELAY[1] * &h10000            ‘ shift left 16 bits
    A=A | (RELAY[2] | &h0000ffff)   ‘ OR in least significant 16 bits
  • Take advantage of the fact that the DM memory can be accessed as both 16 bit and 32 bit values.  In "C-like" programming languages this is an example of a "union" data structure. This code will build a 32-bit value in DM32[1]:
Try them in the simulator. Pay close attention to where the bits from RELAY[1] and RELAY[2] end up in the 32 bit value.  Set the most significant bit of each group of relays and verify that you get the correct pattern in the 32-bit variable.

Gary Dickinson

Technical support / Re: Test Bit for greater than or Less than
« on: January 14, 2020, 02:37:08 PM »

Your ladder logic code is much better than my version.  It is much simpler and solves the problem very directly.  I am very impressed!

I will try and explain how my TBASIC program operates.  TBASIC supports three types of operators:
  • Arithmetic Operators: +, -, *,/ and MOD.  The first 4 show up on calculators. MOD is the remainder from integer division and is what was taught in elementary school (10 divided by 3 is 3 remainder 1).
  • Bitwise Boolean Operators: &, |, ^, and ~ ( bitwise AND, bitwise OR, bitwise Exclusive OR and bitwise NOT)
  • Relational Operators: =, <>, ... AND, OR

I will explain the use of Bitwise Boolean operators in my program line by line.
N = RELAY[1] & &h00ff   ' isolate bits 0..7

RELAY[1] is a 16-bit value that represents the the state of Relays 1..16.   If Relay #3 and Relay #16 is ON and the other 14 relays are OFF, then the value of RELAY[1] would have a decimal value of -32764 or &h8004.  But all arithmetic in TBASIC is done with 32-bit signed arithmetic the Value of RELAY[2] will be sign-extended to a 32-bit value.  I only care about the lower 8-bits of the value so I will use the bitwise Boolean & (and) operator to clear all but the least significant 8 bits of the value.

Look at the last two lines in the table to see the effect of the Bitwise Boolean & (AND) operator.

Comment                 Decimal    HexadecimalBinary
RELAY[1] as 16-bit-3276480041000 0000 0000 0100
RELAY[1] as 32-bit-32764FFFF80041111 1111 1111 1111 1000 0000 0000 0100
&h00ff value255000000FF0000 0000 0000 0000 0000 0000 1111 1111
RELAY[1] & &h00ff4000000040000 0000 0000 0000 0000 0000 0000 0100

This line of code is an assignment statement.  The variable, N, will be assigned the value that results from the Bitwise Boolean & (AND) operator.
N = RELAY[1] & &h00ff   ' isolate bits 0..7

The next line of code generates a map of all of the bits that are less than the single bit set in RELAY[1] and assigns this value to "L":

L = N - 1            ' low mask (bits less than single bit in N)

Comment                 Decimal    HexadecimalBinary
N400000040000 0000 0000 0000 0000 0000 0000 0100
N-13000000030000 0000 0000 0000 0000 0000 0000 0011

The line of code:
L = N - 1            ' low mask (bits less than single bit in N)
doesn't use any binary operators, just old school subtraction.  The reason that subtracting 1 from N gets a bit map is because of the fact that that only possible values of N are 1,2,4,8,16,32,64 and 128. Those values have only a single 1 set in their binary representations.  128 in binary is 10000000 subtracting 1 from 128 will get you 127 or 01111111 binary (lots of carries).

Table that shows the bit map off all of the bits less than N for all possible values of N:
Comment                 Decimal    HexadecimalBinary
N=1, L0000000000000 0000 0000 0000 0000 0000 0000 0000
N=2, L1000000010000 0000 0000 0000 0000 0000 0000 0001
N=4, L3000000030000 0000 0000 0000 0000 0000 0000 0011
N=8, L7000000070000 0000 0000 0000 0000 0000 0000 0111
N=16, L150000000F0000 0000 0000 0000 0000 0000 0000 1111
N=32, L310000001F0000 0000 0000 0000 0000 0000 0001 1111
N=64, L630000003F0000 0000 0000 0000 0000 0000 0011 1111
N=128, L1270000007F0000 0000 0000 0000 0000 0000 0111 1111

The next line of code is:
H = &hff ^ (L | N)      ' high mask (bits greater than single bit in N)
That line of code generates the mask for the bits that are greater than then single bit in N (RELaY[1]). This line of code uses 2 bitwise Boolean operators, "|" (XOR) and "^" (exclusive OR).

The Boolean OR operator looks at 2 bits and if either bit is a "1" the output will be a "1".  TBASIC's bitwise Boolean OR, "|", works with two 32-bit values and on a bit-by-bit basis to create a new 32-bit value.

Exclusive OR (XOR) is a Boolean operator that is used to compare two bits. If the bits have different values then the output is a "1". If the two bits have the same value (both 0s or 1s) then the outputs is a "0".

TBASIC supports a bitwise Boolean XOR, "^". Bitwise means that it works with two 32-bit input values and outputs a new 32-bit value based on a bit by bit comparison.

At this point in the program we have N and L.  L is a bit map of all of the bits less than the value of N. What we need to do is compute the bit map for all of the bits that are greater than N. 

This bit of code is evaluated first as it group in parenthesis:
(L | N)
The bitwise Boolean "|" create a new value that is a bit wise OR of L and N.  This value is the pattern of all of the bits that are less than N and equal to N. This is value is actually all the bits that we don't want.  The bitwise XOR comes to the rescue by allowing us to sort of subtract out the bits we don't want to generate the mask of bits greater than N.

H = &hff ^ (L | N)

&hff is a constant that represents all possible bits in an 8-bit value. The "^" operator will remove the result from the  (L | N) expression from the &hff constant. The result will be assigned to H as a map of all of the bits greater than N.

All of the possible values of H:
Comment                 Decimal    HexadecimalBinary
N=1, H254000000FE0000 0000 0000 0000 0000 0000 1111 1110
N=2, H252000000FC0000 0000 0000 0000 0000 0000 1111 1100
N=4, H248000000F80000 0000 0000 0000 0000 0000 1111 1000
N=8, H240000000F00000 0000 0000 0000 0000 0000 1111 0000
N=16, HE0000000E00000 0000 0000 0000 0000 0000 1110 0000
N=32, HC0000000C00000 0000 0000 0000 0000 0000 1100 0000
N=64, H128000000800000 0000 0000 0000 0000 0000 1000 0000
N=128, H0000000000000 0000 0000 0000 0000 0000 0000 0000

Now that the high and low bit maps have been calculated, it is pretty easy to look for bits in RELAY[2] that are less than and greater than the value in N.

if RELAY[2] & L
   SetIO LowFlg
   ClrIO LowFlg

The bitwise Boolean operator "&" (AND) is used to determine if any bits in RELAY[2] are set at the bit positions determined by L. The result of the & operation will be either 0 or something non-zero. If any bits are set in RELAY[2] that correspond to bits in the L map the result will be non-zero and the SetIO LowFlag statement will execute.

I'm sorry that the length of the explanation.

Gary D

Technical support / Re: Test Bit for greater than or Less than
« on: January 13, 2020, 07:50:11 PM »

I thought about the ladder logic implementation.  I wrote 1/2 of the code that checks for bits in RELAY[2] that are less than the single bit in RELAY[1].

Why?  Just to see how hard it was to write.  I did manage to write more code on a single rung that the ladder logic can support so I broke the problem across two rungs.

I attached the code to this email.  The 1/2 that I wrote took 2 lines of ladder logic and a total of 50 ladder diagram words. Double that to get the complete program.

If your application requires this bit testing code to checked on every scan of the ladder logic, then I'd use the ladder logic rung rather than calling the CF solution.

Best regards,

Gary Dickinson

Technical support / Re: Test Bit for greater than or Less than
« on: January 10, 2020, 10:59:36 AM »
Thanks Lorne,

I like algorithms. Before I sent the reply I came up with some rather complicated ideas, also. I got balled up with numeric values and the idea of comparing values.

But the easier algorithm was to just figure out how to build bit maps base on the single bit in RELAY[1]. TBASIC supports a good set of Boolean operators which makes bit twiddling easy. 

The trick was that the less than map just needed a subtraction.  The bit map for greater than is easier, it's all the other bits that aren't in the less than map and the bit in RELAY[1] and this is just a bit of Boolean bit twiddling.

Gary d

Technical support / Re: Test Bit for greater than or Less than
« on: January 09, 2020, 10:39:35 PM »

    This is not too difficult to do in TBASIC. It is a bit more complicated do do in ladder logic.

    This is the sort of code that I would suggest:

' SetFlgs - CF to test lower 8-bits or RELAY[1] against RELAY[2]
N = RELAY[1] & &h00ff   ' isolate bits 0..7
L = N - 1            ' low mask (bits less than single bit in N)
H = &hff ^ (L | N)      ' high mask (bits greater than single bit in N)

if RELAY[2] & L
   SetIO LowFlg
   ClrIO LowFlg

if RELAY[2] & H
   SetIO HighFlg
   ClrIO HighFlg

Comments on code:
  • L is map of the bits less that the single bit set in RELAY[1]
  • N is map of the bits greater than the single bit set in RELAY[1]
  • LoFlg is a RELAY that is set when there are bits in RELAY[2] that are less than the single bit in RELAY[1]
  • HighFlg is a RELAY that is set when there are bits in RELAY[2] that are set at positions greater than the single bit in RELAY[1]
Concerns about RELAY[1]
  • This code works if there is exactly one bit set in RELAY[1] bit positions 0..7
  • This code fails miserably if there are no bits set in RELAY[1] bit positions 0..7
  • This code fails in interesting ways if there is more than one bit set in RELAY[1] bit positions 0..7
  • If there is any chance that your rules about RELAY[1] may not be held sacred, then you need to add some code to test and verify that RELAY[1] meets the rules before you get to the code that sets/clears flags.

Best Regards,

Gary Dickinson[/list]

Technical support / Demo Program that uses the Stack
« on: January 07, 2020, 04:57:08 PM »
I attached a simple program that uses the stack mechanism.

The code shows a method to generate a string representation of a 32-bit signed value.  My version allows you to specify a field width and will right justify the value in a column width that you specify.

The demo code is similar in behavior to the TRI Str$(n,d) function.  The TRI version pads the converted value with leading zeros which are not very useful for my purposes.

The demo code is just that.  If I really want to right justify a 32-bit integer value in a fixed-width column I use code like this:

a$ = Str$(n) : a$ = mid$("                  ",1,d-len(a$)) + a$

I apologize for the fact that this code is 100% unreadable, it's just the best you can do in TBASIC.
Best regards,

Gary Dickinson

Documentation for Stack.PC6

Technical support / Implementation of a Stack or LIFO in TBASIC
« on: January 06, 2020, 03:31:35 PM »
This post shows how to implement a stack or Last In First Out data structure in TBASIC.

This example builds the stack in DM32[]. 
The size and location of the stack is controlled by entries in the #Define table.
Separate custom functions to initialize, push data and pop data are written to be as small and fast as possible.

The Stack.PC6 file is attached to this post. This is test bed code that will run in the i-TRiLOGI  simulator. This code should be easily adaptable for many uses.

The next post will have post a bit of documentation on how the stack operates.

Best regards,

Gary Dickinson

Technical support / Re: TBASIC Input On/Off Question
« on: December 11, 2019, 03:19:34 PM »

Good to see that you got something that works for you.

If this code is something that has to be maintained, I’d suggest using the "names" of things rather than the numbers.  As an example TIMER #9 was assigned the name "GndrTmr" in the "I/O Table tab in the i-TRiLOGI app.

So TimerPV[9] and TimerPV[GndrTmr] are equivalent.  The second version makes more sense.  The other reason for using the Itepms name rather than the number is that it is possible to re-arrange the Timers in the I/O and then TimerPV[9] may no longer refer to the GndrTmr.

The same goes for GetTimerSV(15).  Which timer is #15?  Use the name and not the number.

You will find that this convention of numbers and names for references includes things like custom functions, COUNTERS, TIMERs and several other things.

The use of “then" is optional.  It was probably put into TBASIC for the same reason as was the optional "let" keyword.  I suspect that this made TBASIC look more like Dartmouth BASIC from 1964 or Apples integer BASIC from the 1970s...  There is no reason to include this useless keyword.

The use of the "refresh" statement is interesting.  TRI has only documented the refresh statement in terms of updating both the internal copy of the physical INPUTs and making the internal copy of the OUTPUTs become the physical OUTPUTS.  This is normally handled by the before the first line of ladder logic is executed (INPUTSs) and after the last line of ladder logic is executed for the OUTPUTs. 

There is no mention  in TRIs documentation about refresh affecting the State of RELAYs.  You may be on to something!

Gary D

Pages: [1] 2 3 ... 31