Home
JAQForum Ver 20.06
Log In or Join  
Active Topics
Local Time 06:19 29 Mar 2024 Privacy Policy
Jump to

Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.

Forum Index : Microcontroller and PC projects : Modbus CRC.

     Page 1 of 2    
Author Message
Phil23
Guru

Joined: 27/03/2016
Location: Australia
Posts: 1664
Posted: 12:26am 30 Mar 2018
Copy link to clipboard 
Print this post

Hi All,

Now that I've got a function to convert IEEE754 to decimal, the next thing I need is a Modbus CRC.

I've found a VB code, but not sure how the "Bit" variable needs to be treated as it's Boolean. Also the usage of "\", integer division. Is that an alternate way of shifting bits?

[Code]
Function CRC_16(OutputString As String) As String
Dim Generator, CRC As Long
Dim i As Integer, j As Integer, Length As Integer
Dim Bit As Boolean
'Dim Temp As Integer
Dim Temp As Byte
Length = Len(OutputString)
CRC = 65535
Generator = 40961

For i = 1 To Length
Temp = Asc(Mid(OutputString, i, 1))
CRC = CRC Xor Temp
For j = 1 To 8
Bit = CRC And 1
CRC = CRC \ 2
If Bit = True Then
CRC = CRC Xor Generator
End If
Next j
Next i
CRC_16 = Chr(CRC Mod 256) & Chr(CRC \ 256)
'Buffor = Buffor & Chr(CRC Mod 256)
'Buffor = Buffor & Chr(CRC \ 256)
'Text1.Text = Str(CRC \ 256)
'Text2.Text = Str(CRC Mod 256)
End Function
[/code]

Also have this example from Eastron's Modbus Manual,

[Code]
BEGIN
Error Word = Hex (FFFF)
FOR Each byte in message
Error Word = Error Word XOR byte in message
FOR Each bit in byte
LSB = Error Word AND Hex (0001)
IF LSB = 1 THEN Error Word = Error Word – 1
Error Word = Error Word / 2
IF LSB = 1 THEN Error Word = Error Word XOR Hex (A001)
NEXT bit in byte
NEXT Byte in message
END
[/code]

Thanks

Phil.
 
TassyJim

Guru

Joined: 07/08/2011
Location: Australia
Posts: 5867
Posted: 02:26am 30 Mar 2018
Copy link to clipboard 
Print this post

I am sure that there is a much tidier way but this is a 'translation' of the example form the datasheet.
The three tests match those given in the datasheet.

  Quote   test1$ = CHR$(01)+CHR$(03)+CHR$(04)+CHR$(&H3F)+CHR$(&H80)+CHR$(0)+CHR$(0)
check1$=
CHR$(&HF7)+CHR$(&HCF)

test2$ =
CHR$(01)+CHR$(&H10)+CHR$(0)+CHR$(02)+CHR$(0)+CHR$(02)+CHR$(04)+CHR$(&H42)+CHR$(&H70)+CHR$(0)+CHR$(0)
check2$=
CHR$(&H67)+CHR$(&HD5)

test3$ =
CHR$(01)+CHR$(03)+CHR$(00)+CHR$(00)+CHR$(00)+CHR$(02)
check3$=
CHR$(&HC4)+CHR$(&H0B)

PRINT HEX$(CRC(test1$),4)
PRINT HEX$(swapHiLo(CRC(test1$)),4)
PRINT
PRINT HEX$(CRC(test2$),4)
PRINT HEX$(swapHiLo(CRC(test2$)),4)
PRINT
PRINT HEX$(CRC(test3$),4)
PRINT HEX$(swapHiLo(CRC(test3$)),4)

FUNCTION CRC(a$)
LOCAL ErrorWord% = &HFFFF, n, j, ByteVal, LSB
FOR n = 1 TO LEN(A$)
ByteVal =
ASC(MID$(a$, n, 1))
ErrorWord% = (ErrorWord%
AND &HFFFF) XOR ASC(MID$(a$, n, 1))
FOR j = 1 TO 8
LSB = ErrorWord%
AND &H0001
IF LSB = 1 THEN ErrorWord% = ErrorWord% - 1
ErrorWord% = ErrorWord% /
2
IF LSB = 1 THEN ErrorWord% = ErrorWord% XOR &HA001
NEXT j
NEXT n
CRC = ErrorWord%
AND &HFFFF
END FUNCTION

FUNCTION swapHiLo(x)
' swap the high and low bytes of a 16 bit number
swapHiLo = (x MOD 256)*256 + INT(x/256) MOD 256
END FUNCTION

The swapHiLo function is there to sway the High and Low bytes to match the order given in the datasheet.

I am never sure how often you have to mask off the bits above 16 when doing this sort of maths.
Jim

VK7JH
MMedit   MMBasic Help
 
goc30

Guru

Joined: 12/04/2017
Location: France
Posts: 425
Posted: 04:11am 30 Mar 2018
Copy link to clipboard 
Print this post

in vb6 CRC16:

'*****************************************
Sub calcrc(buf1, nbb, v0)
'*******************************
'* PRG de Calcul du CRC16
'* buf1=buffer string
'* nbb nb bytes in string with 2 bytes CRC
'* v0=Value CRC in output
'*******************************
Dim V1 As Integer
Dim v2 As Long
Dim v21 As Long
Dim v3 As Long
Dim i As Integer
Dim j As Integer
v2 = &HFFFF&
V1 = 0
calcrc1:
For i = 1 To nbb - 2
'Print Len(buf1)
v3 = Asc(Mid$(buf1, i, 1))
v2 = v2 Xor v3
For j = 1 To 8
v21 = v2
v2 = (v2 \ 2)
If (v21 And 1) = 1 Then v2 = v2 Xor &HA001&
Next j
Next i
v0 = v2
End Sub
Edited by goc30 2018-03-31
 
Phil23
Guru

Joined: 27/03/2016
Location: Australia
Posts: 1664
Posted: 04:31am 30 Mar 2018
Copy link to clipboard 
Print this post

Thanks Jim.

I presume the Swap could be included inside the main function.

I probably won't need to add a CRC as I think I'll just build a few different variables for strings for the requests,

ReqV, ReqA, ReqW, ReqkWh etc, so they can be constant.

But for the received string I will want to get the CRC for the returned data & compare it.

Which data sheet are you referring to in your post above?

Phil.
 
TassyJim

Guru

Joined: 07/08/2011
Location: Australia
Posts: 5867
Posted: 04:48am 30 Mar 2018
Copy link to clipboard 
Print this post

The datasheet is the one you referred to in an earlier post
  Quote  Eastron SDM220-Modbus Smart Meter Modbus Protocol Implementation V1.2


The swap was there to make it easier to add the CRC to the end of the sending string (provided you are sending as a string).

For receiving, strip the last two bytes of the full string, generate the CRC and compare it to the last two bytes you stripped of earlier.
In that case, you need the swap function also.
Yes it could be incorporated into the CRC function.

  Quote  FUNCTION CRC(a$)
LOCAL ErrorWord% = &HFFFF, n, j, ByteVal, LSB
FOR n = 1 TO LEN(A$)
ByteVal =
ASC(MID$(a$, n, 1))
ErrorWord% = (ErrorWord%
AND &HFFFF) XOR ASC(MID$(a$, n, 1))
FOR j = 1 TO 8
LSB = ErrorWord%
AND &H0001
IF LSB = 1 THEN ErrorWord% = ErrorWord% - 1
ErrorWord% = ErrorWord% /
2
IF LSB = 1 THEN ErrorWord% = ErrorWord% XOR &HA001
NEXT j
NEXT n
'CRC = ErrorWord% AND &HFFFF
CRC = (ErrorWord% MOD 256)*256 + INT(ErrorWord%/256) MOD 256
END FUNCTION


Should do the trick.

Jim
VK7JH
MMedit   MMBasic Help
 
goc30

Guru

Joined: 12/04/2017
Location: France
Posts: 425
Posted: 11:18am 30 Mar 2018
Copy link to clipboard 
Print this post

Hi Jim
I think that incorporate compare function in CRC module is not a good idea.
modbus protocol is a exchange communication. In master mode you send and wait response, in slave mode, you wait question and send response. In each case you must create CRC value.
In your example, CRC module need a value to compare, but when you create crc value you have not a value to compare. In your case you need 2 modules, one for send ans one for receive
I think that it is driver's job to compare, not crc module
 
Turbo46

Guru

Joined: 24/12/2017
Location: Australia
Posts: 1584
Posted: 06:41am 06 Apr 2018
Copy link to clipboard 
Print this post

Jim,

I believe that if you receive a message, then process the whole message (CRC included) with the CRC generation function, the resultant 'CRC' will be zero. That would be a simpler way of checking the validity of the message. I'm yet to try that though.

Bill

Keep safe. Live long and prosper.
 
Phil23
Guru

Joined: 27/03/2016
Location: Australia
Posts: 1664
Posted: 03:24am 07 Apr 2018
Copy link to clipboard 
Print this post

Next step in the phase,

I'm now sending 02 04 00 00 00 02 71 F8 with this:-


  Quote   volts$ = CHR$(02)+CHR$(04)+CHR$(0)+CHR$(0)+CHR$(0)+CHR$(02)+CHR$(&h71)+CHR$(&hF8)
Dim String DataStr
print volts$

Open "Com1:9600,,RecComs,8" as #1

print #1, volts$;

Do
Pause 100
Loop

Sub RecComs

print "In Coms Sub"
Local Integer HdrLoc,FtrLoc,BufLen
'Local String DataStr
If LOC(#1)>8 then 'If COM1 serial port buffer is NOT empty.... Was 100

Do WHILE BufLen<> LOC(#1)
BufLen=
LOC(#1)
Pause 50
Loop
End If


DataStr=
INPUT$(LOC(#1),#1) 'then suck everything in the buffer out and stick it in D$....

print "Volts";DataStr
'IEEE754

End Sub




The meter is returning this in the highlight.
Ignoring the CRC at the moment, I a bit vague on how I extract the value to be converted to the float, as the returned string is unprintable hex values & I only need a portion of it.




 
Phil23
Guru

Joined: 27/03/2016
Location: Australia
Posts: 1664
Posted: 03:46am 07 Apr 2018
Copy link to clipboard 
Print this post

Data returned doesn't look right either.
The 43 76 79 E6 is 246.48 Volts which is about right,
but it looks like there is only 1 CRC byte. 9E.

Phil.
 
Phil23
Guru

Joined: 27/03/2016
Location: Australia
Posts: 1664
Posted: 04:39am 07 Apr 2018
Copy link to clipboard 
Print this post

This looks better with 9 bytes returned, I can see the voltage before the the 2 byte check sum, but can't see how I turn it into a series of hex numbers as it's sort of that way already in the string. Just not readable hex.





Phil.Edited by Phil23 2018-04-08
 
Phil23
Guru

Joined: 27/03/2016
Location: Australia
Posts: 1664
Posted: 05:45am 07 Apr 2018
Copy link to clipboard 
Print this post

Ok,

I now have this mess of workings and it is returning a result.

Just not sure if I'm going around in circles converting data from one form to another un-necessarily.

Any feedback would be greatly appreciated.

  Quote   volts$ = CHR$(02)+CHR$(04)+CHR$(0)+CHR$(0)+CHR$(0)+CHR$(02)+CHR$(&h71)+CHR$(&hF8)
Dim String DataStr
print volts$

Open "Com1:9600,,RecComs,8" as #1
Do

print #1, volts$;

Do While Inkey$="": Loop
' Pause 1000

Loop

Sub RecComs

print "In Coms Sub"
Local Integer BufLen
'BufLen=LOC(#1)
print BufLen 'Local String DataStr
'If LOC(#1)>8 then 'If COM1 serial port buffer is NOT empty.... Was 100

Do While BufLen<> LOC(#1)
BufLen=
LOC(#1)
PAUSE 50
Loop
'End if


DataStr=
INPUT$(LOC(#1),#1) 'then suck everything in the buffer out and stick it in D$....

print "Response ";DataStr
print "Voltage "; Mid$(DataStr,4,4)
print "Length "; BufLen

Response$=
""

For n=1 to BufLen
'Print Hex$(Asc(Mid$(DataStr,n,1)),2)
Response$=Response$+Hex$(Asc(Mid$(DataStr,n,1)),2)
Next n

print Response$

IEEE754

End Sub

Sub IEEE754


ReadingStr$=
"&H"+Mid$(Response$, 7,8)
print ReadingStr$

Reading%=
Val(ReadingStr$)
Sign%=Reading% >>
31 'Read the Sign Bit
Expo%=((Reading% >> 23) And &hff) - 127 'Extract Exponent, Remove sign, Subtract Offset
Ans!=((Reading% Or &h800000) And &hffffff)/2^(23-Expo%)*(-1)^Sign% 'Extract Mantissa & Add implied 24th bit, Shift Point in Binary & apply sign

print "Text Input String: ";ReadingStr$
print "Hex Value Reading: ";Hex$(Reading%,8) 'Reading in Hex
print "Binary Value: ";Bin$(Reading%,32) 'Reading in Binary
print "Sign Bit: ";Hex$(Sign%)
print "Exponent & -offset: ";Bin$(Expo%+127,8), Str$(Expo%)
print "Mantissa Value: ";Bin$((Reading% Or &h800000) And &hffffff,32)
print
print "Returned Value: ";Str$(Ans!,10,1)
print


End Sub






Bit I have trouble getting my head around is that the "String" that comes in the com port is really a series of bytes & how I should treat them.

Phil.



 
goc30

Guru

Joined: 12/04/2017
Location: France
Posts: 425
Posted: 06:54pm 08 Apr 2018
Copy link to clipboard 
Print this post

hi fil

your IEEE754 subroutine is good
your modbus string is correct (for protocol)
it say: "i want to know 2 values in memory in adr=0 and adr=1 (or register n° 0)"
answer is correct??
the response is "i send 4 bytes"

value transmit by RTU is in integer format each time? and for that you want to read 2 values ?
adr 0 is good adr for temp?
what value do you want to receive (format example :24.4 Deg) ?
 
Turbo46

Guru

Joined: 24/12/2017
Location: Australia
Posts: 1584
Posted: 08:05am 10 Apr 2018
Copy link to clipboard 
Print this post

Previously I wrote:

  Quote  I believe that if you receive a message, then process the whole message (CRC included) with the CRC generation function, the resultant 'CRC' will be zero. That would be a simpler way of checking the validity of the message. I'm yet to try that though.


Well I have tried it with the examples in Jim's CRC calculation function and with some other real world examples and it does work eg:

test1$ = CHR$(01)+CHR$(03)+CHR$(04)+CHR$(&H3F)+CHR$(&H80)+CHR$(0)+CHR$(0)
check1$= CHR$(&HF7)+CHR$(&HCF)
test1$ = test1$+check1$

PRINT HEX$(CRC(test1$),4)

will return zero (0000)

BillEdited by Turbo46 2018-04-11
Keep safe. Live long and prosper.
 
Phil23
Guru

Joined: 27/03/2016
Location: Australia
Posts: 1664
Posted: 11:08pm 14 Apr 2018
Copy link to clipboard 
Print this post

Wondering if anyone can see fault in this variation.

It returns a 2 character string that I can add to the string I'm building to sent.

Appears to test Ok, and I assume from above if I pass a response string to it, it should return "String"=Chr$(0)+Chr$(0).

Thanks

Phil.

PS, I am now successfully reading the first Six registers with a single request & converting them successfully.

They all reside in the address range from 30001 to 30032.

Really need a second request for the others as they are up at 30071 to 30080.
If I tried to get them all with one request the response string would be too long.

  Quote  FUNCTION CrcStr(a$) As String
LOCAL ErrorWord% = &HFFFF, n, j, ByteVal, LSB
LOCAL CrcHex As Integer
FOR n = 1 TO LEN(A$)
ByteVal =
ASC(MID$(a$, n, 1))
ErrorWord% = (ErrorWord%
AND &HFFFF) XOR ASC(MID$(a$, n, 1))
FOR j = 1 TO 8
LSB = ErrorWord%
AND &H0001
IF LSB = 1 THEN ErrorWord% = ErrorWord% - 1
ErrorWord% = ErrorWord% /
2
IF LSB = 1 THEN ErrorWord% = ErrorWord% XOR &HA001
NEXT j
NEXT n
CrcHex = ErrorWord%
AND &HFFFF
CrcStr=
Chr$(CrcHex AND &hFF)+Chr$(CrcHex>>8)

END FUNCTION


 
goc30

Guru

Joined: 12/04/2017
Location: France
Posts: 425
Posted: 03:44am 15 Apr 2018
Copy link to clipboard 
Print this post

Hi Phil

In the MODBUS protocol, the frames are limited to 128 bytes, including crc, which means that if a register is 4 bytes long, the slave can only send 30 registers values
 
Turbo46

Guru

Joined: 24/12/2017
Location: Australia
Posts: 1584
Posted: 05:05am 15 Apr 2018
Copy link to clipboard 
Print this post

Hi Phil

I think it does work but you are ending up with a null string.
CHR$(0) + CHR$(0) = CHR$(0) = nothing.

Bill

Edit: From what I have found the maximum Modbus frame size is 256 bytes.Edited by Turbo46 2018-04-16
Keep safe. Live long and prosper.
 
goc30

Guru

Joined: 12/04/2017
Location: France
Posts: 425
Posted: 02:32pm 15 Apr 2018
Copy link to clipboard 
Print this post

hi Turbo46

Edit: From what I have found the maximum Modbus frame size is 256 bytes.

in wikipedia for modbus they say:

Because register values are 2-bytes wide and only 127 bytes worth of values can be sent, only 63 holding registers can be preset/written at once

Wikipedia Edited by goc30 2018-04-17
 
Turbo46

Guru

Joined: 24/12/2017
Location: Australia
Posts: 1584
Posted: 10:23pm 15 Apr 2018
Copy link to clipboard 
Print this post

Hi goc30

You may well be right! I have only had experience with small RTUs. My reference though is the 'Modicon Modbus Protocol Reference Guide' Here. Check out page 58 where it states:

'The quantity of registers to be read, combined with all other fields in the expected response, must not exceed the allowable length of Modbus messages: 256 bytes.'

Cheers
Bill


Keep safe. Live long and prosper.
 
goc30

Guru

Joined: 12/04/2017
Location: France
Posts: 425
Posted: 06:22am 16 Apr 2018
Copy link to clipboard 
Print this post

Hi Turbo46

The MODBUS protocol is open, you can create your own function codes (I created some for clients). The 128-byte limit must have a historical reason (maybe for slow speed reasons)

Modicon use a partial modbus protocol (not 100% compatible, and many functions no-standards) but it is not important. They use also an ascii communication, for this they need more bytes maybe
 
Turbo46

Guru

Joined: 24/12/2017
Location: Australia
Posts: 1584
Posted: 08:50am 27 Sep 2018
Copy link to clipboard 
Print this post

Those of you who have researched the Modbus CRC may be aware of the lookup table method of CRC calculation. It is supposed to be much faster than the above presented method. I have found some examples in Basic and converted the program to MMBasic. The program below generates the CRC lookup table rather than having to enter it manually.


' Modbus CRC Generation using lookup table
' Test program

OPTION EXPLICIT

DIM INTEGER crc_table(255)
DIM INTEGER crc_const = &HA001
DIM INTEGER i, j, k
DIM STRING test1$, test2$, test3$

' Make the CRC table first
CRCTable
' Test messages
test1$ = CHR$(01)+CHR$(03)+CHR$(04)+CHR$(&H3F)+CHR$(&H80)+CHR$(0)+CHR$(0)
' check1$= CHR$(&HF7)+CHR$(&HCF)
test2$ = CHR$(01)+CHR$(&H10)+CHR$(0)+CHR$(02)+CHR$(0)+CHR$(02)+CHR$(04)+CHR$(&H42)+CHR$(&H70)+CHR$(0)+CHR$(0)
' check2$= CHR$(&H67)+CHR$(&HD5)
test3$ = CHR$(01)+CHR$(03)+CHR$(00)+CHR$(00)+CHR$(00)+CHR$(02)
' check3$= CHR$(&HC4)+CHR$(&H0B)

PRINT HEX$(CRC16(test1$),4)
PRINT HEX$(CRC16(test2$),4)
PRINT HEX$(CRC16(test3$),4)

END

' Generates the lookup table
SUB CRCTable
FOR i = 0 TO 255
k = i
' print k
FOR j = 1 TO 8
IF k AND 1 THEN
k = k >> 1
k = k XOR crc_const
ELSE
k = k >> 1
END IF
NEXT j
crc_table(i)=k
NEXT i
END SUB

' Calculates the Modbus CRC
FUNCTION CRC16(a$)
LOCAL CRC = &HFFFF, n
FOR n = 1 TO LEN(a$)
CRC = (crc >> 8) XOR crc_table(crc XOR ASC(MID$(a$, n, 1)) AND &HFF)
NEXT n
CRC16 = (CRC MOD 256)*256 + INT(CRC/256) MOD 256
END FUNCTION


The test program above uses the same example messages as TassyJim's program above and the times to calculate the CRC with the Maximite are shown below - the first set of times are for the program above and are about 10 times faster than the second set which is from the TassyJim program.

RUN
4 msec
F7CF
6 msec
67D5
3 msec
C40B
>
> RUN
37 msec
F7CF
61 msec
67D5
32 msec
C40B
>

This should reduce the possibility of communications timeouts.

Bill

Keep safe. Live long and prosper.
 
     Page 1 of 2    
Print this page
© JAQ Software 2024