Geppo Posted Sunday at 09:43 AM Posted Sunday at 09:43 AM Hello all, I'm trying to use the libmodbus.dll 3.1.10 to connect a modbus device using modbus rtu. I think I succesfully used the modbus_new_rtu function to create the needed context, but although it uses a quite simple syntax, I cannot succeded to use the modbus_set_client function of the dll. It always return a fail code (-1). I cannot understand what is going wrong. Anyone here used the libmodbus dll to connect a device using modbus rtu? Here is the simple code I used to test libmodbus functions: #include <MsgBoxConstants.au3> #include <Array.au3> ; modbus_t *modbus_new_rtu(const char *device, int baud, char parity, int data_bit, int stop_bit); Local $Port = "\\.\COM7" Local $BaudRate = 19200 Local $Parity = "E" ; N=None, E=Even O=Odd Local $DataBits = 8 Local $StopBits = 1 Local $Modbus_Slave = 5 Local $hDLL = DllOpen("modbus.dll") If @error Then MsgBox($MB_SYSTEMMODAL, "Error", "Error opening modbus.dll, Error code: " & @error) Else Local $RTU_Ptr = DllCall($hDLL, "ptr:cdecl", "modbus_new_rtu", "str", $Port, "int", $BaudRate, "str", $Parity, "int", $DataBits, "int", $StopBits) If @error Then MsgBox($MB_SYSTEMMODAL, "Error", "Error calling function modbus_new_rtu, Error code: " & @error) Else MsgBox($MB_SYSTEMMODAL, "Info", "Exit code: " & $RTU_Ptr[0]) _ArrayDisplay($RTU_Ptr) EndIf Local $RTU_Result = DllCall($hDLL, "int:cdecl", "modbus_set_slave", "ptr", $RTU_Ptr, "int", $Modbus_Slave) If @error Then MsgBox($MB_SYSTEMMODAL, "Error", "Error calling function modbus_set_slave, Error code: " & @error) Else _ArrayDisplay($RTU_Result) EndIf DllClose ($hDLL) EndIf Thank you in advance for your help.
MattyD Posted Sunday at 11:48 AM Posted Sunday at 11:48 AM Hi Geppo, Its a bit difficult without having the dll to play with, but a couple of general things if it helps... DllCall() returns an array, and the return value ends up in element 0. So for the code below, the returned ptr would be in $aCall[0].. $aCall = DllCall($hDll, "ptr", "SomeFunc", "int", $iParam1) Also, the "str" type passes a pointer to a char array. So to pass a single char for the parity param, you'll likely need to use "BYTE"
Geppo Posted Sunday at 02:09 PM Author Posted Sunday at 02:09 PM Thank you very much for your suggestions. For what I succeed to understand from the modbus_set_client function documentation here: modbus_set_slave the returned value is an int, while I have to supply a pointer to the RTU data structure created using the modbus_new_rtu function documented here: modbus_new_rtu that instead seems it was correctly created. Also, the Autoit DllCall function, documented here DllCall seems to require as second parameters the type of returned data, that in the specific case of modbus_set_slave function is an int (not a pointer), followed by a pointer to the RTU data structure and finally the slave ID as an int. I suppose you didn't run the code, otherwise you could have seen that the RTU data structure was correctly created and the Autoit DllCall does not return errors (meaning the call was correctly formed) and the return value of the called function modbus_new_rtu is zero, confirming it works. Unfortunately when I try to call the modbus_set_slave function, DllCall return no errors, meaning the call was correctly formed but the modbus_set_slave function return -1 meaning it didn't work. Anyway, for confirmation, I tried to use "byte" instead "str" for the Parity parameter as you suggested. The char "E" after the call was changed to "0" in the RTU data structure, so it doesn't sound good. You can verify it running the code. Here you can find the ready-to-use compiled libmodbus ddl files: libmodbus ready-to-use-dll I attached two screenshots of the array returned after the DllCall to the function modbus_new_rtu and the array returned after the DllCall to the function modbus_set_slave
Solution MattyD Posted Sunday at 09:41 PM Solution Posted Sunday at 09:41 PM (edited) Thanks for that link - give this one a go Edit: this was using the 64bit dll! - for the x86 I needed to use :cdecl as you have done in your example above! #AutoIt3Wrapper_UseX64=Y Global $hDLL = DllOpen("modbus.dll") Local $sPort = "\\.\COM7" Local $iBaudRate = 19200 Local $sParity = "E" ; N=None, E=Even O=Odd Local $iDataBits = 8 Local $iStopBits = 1 Local $iModbus_Slave = 5 Local $pRTU, $bSuccess $pRTU = _ModBus_NewRTU($sPort, $iBaudRate, $sParity, $iDataBits, $iStopBits) ConsoleWrite("pRTU = " & $pRTU & @CRLF) If $pRTU Then $bSuccess = _ModBus_SetSlave($pRTU, $iModbus_Slave) ConsoleWrite("Set Slave Success = " & $bSuccess & @CRLF) _ModBus_Close($pRTU) DllClose($hDLL) Func _ModBus_NewRTU($sPort, $iBaudRate, $sParity, $iDataBits, $iStopBits) Local $aCall = DllCall($hDLL, "ptr", "modbus_new_rtu", "str", $sPort, "int", $iBaudRate, "byte", Asc($sParity), "int", $iDataBits, "int", $iStopBits) If @error Then Return SetError(@error, @extended, 0) Return $aCall[0] EndFunc Func _ModBus_SetSlave($pRTU, $iSlave) Local $aCall = DllCall($hDLL, "int", "modbus_set_slave", "ptr", $pRTU, "int", $iSlave) If @error Then Return SetError(@error, @extended, 0) Return $aCall[0] = 0 EndFunc Func _ModBus_Close($pRTU) DllCall($hDLL, "none", "modbus_clode", "ptr", $pRTU) EndFunc Edited Sunday at 11:06 PM by MattyD
Geppo Posted Monday at 06:36 AM Author Posted Monday at 06:36 AM Thank you very much for your help. Looking at your code I understood lots of things.... 🙂 Firstly, my bad mistake about Parity, then the reason why the DllCall to the function modbus_set_slave couldn't work. I wrote $RTU_Ptr instead $RTU_Ptr[0] to pass the pointer....... What I don't understand in your code is "Return $aCall[0] = 0" in the Set_Slave function code. Moreover although you code works perfectly with the x86 dll (switching to the cdecl calling convention and disabling the #AutoIt3Wrapper_UseX64=Y directive), I was not able to make it working using the x64 dll. It always returns: pRTU = 0 Set Slave Success =
Geppo Posted Monday at 06:46 AM Author Posted Monday at 06:46 AM On 4/7/2025 at 6:36 AM, Geppo said: What I don't understand in your code is "Return $aCall[0] = 0" in the Set_Slave function code. Expand The correct function name where you wrote the above code is "_ModBus_SetSlave"
MattyD Posted Monday at 07:55 AM Posted Monday at 07:55 AM No worries mate the $aCall[0] = 0 expression is just a quick way to get the function to return True/False. The dll call returns 0 on success - So if ($aCall[0] = 0) we return True, otherwise we return False. Hope that makes sense! with the 64bit stuff, do you by chance have the full version of SciTE4AutoIt installed? I'm not 100% sure if the directive works without it... (you can verify autoit_64.exe is being run by looking in the console) >Running:(3.3.16.1):C:\Program Files (x86)\AutoIt3\autoit3_x64.exe "C:\Users\matt\Downloads\libmodbus-3.1.10_VS2008_X64\lib&dll\x64\Release\test.au3" +>Setting Hotkeys...--> Press Ctrl+Alt+Break to Restart or Ctrl+BREAK to Stop. pRTU = 0x00000253258D0C40 Set Slave Success = True
Geppo Posted Monday at 01:52 PM Author Posted Monday at 01:52 PM Thank you again for you help. Unfortunately I discovered it always runs autoit.exe, so I would have to make some change / update of the current Autoit installation. I will do, but it it is not so important at the moment. Instead, although I went forward in testing libmodbus.dll basic functions, I didn't succeded to retrieve the content of some registers using function modbus_read_registers. My intent is to simply test all functions exposed by the dll that I think I will need before coding all the functions to manage them. What sounds odd I verified the correctness of the command issued activating the debug mode, as well as the proper device reply (I can see the requested data in the reply of the device), but I didn't succeed to extract them. This is what I get from the console after activating the debug mode: [01][03][00][00][00][08][44][0C] Waiting for a confirmation... <01><03><10><00><00><00><00><00><00><00><00><00><00><00><00><00><00><03><DD><24><F0> confirming the correctness of the command (SlaveID 1, Function 03, 0 address, 8 registers, CRC16) and of the device reply where 03DD (989) is the content of the last requested device register. This is the code I used to retrieve and to display the contents of the registers (after setting slave ID 1 and successfully connecting): Local $RTU_Result = DllCall($hDLL, "int:cdecl", "modbus_set_debug", "ptr", $pRTU_Ptr[0], "boolean", True) If @error Then MsgBox($MB_SYSTEMMODAL, "Error", "Error calling function modbus_set_debug, Error code: " & @error) Else _ArrayDisplay($RTU_Result, "Modbus Set Debug") EndIf $iRegisterAddr = 0 $iRegisterNum = 8 Local $RegData_t = "struct;word data[" & $iRegisterNum & "];endstruct" Local $tRegisterData = DllStructCreate($RegData_t) Local $aRTU_Result = DllCall($hDLL, "int:cdecl", "modbus_read_registers", "ptr", $pRTU_Ptr[0], "int", $iRegisterAddr, "int", $iRegisterNum, "struct*", $tRegisterData) If @error Then MsgBox($MB_SYSTEMMODAL, "Error", "Error calling function modbus_read_register, Error code: " & @error) Else _ArrayDisplay($aRTU_Result, "Modbus Read Register") MsgBox($MB_SYSTEMMODAL, "Info", "$tRegisterData Struct Size: " & DllStructGetSize($tRegisterData) & @CRLF & _ "Struct pointer: " & DllStructGetPtr($tRegisterData) & @CRLF & _ "Data:" & @CRLF & _ "[1] " & DllStructGetData($tRegisterData, 1) & @CRLF & _ "[2] " & DllStructGetData($tRegisterData, 2) & @CRLF & _ "[3] " & DllStructGetData($tRegisterData, 3) & @CRLF & _ "[4] " & DllStructGetData($tRegisterData, 4) & @CRLF & _ "[5] " & DllStructGetData($tRegisterData, 5) & @CRLF & _ "[6] " & DllStructGetData($tRegisterData, 6) & @CRLF & _ "[7] " & DllStructGetData($tRegisterData, 7) & @CRLF & _ "[8] " & DllStructGetData($tRegisterData, 8)) EndIf ; Alternative to display registers contents ;For $i = 1 To 8 ; ConsoleWrite("[" & $i & "] = " & $tRegisterData.data($i) & @CRLF) ;Next Unfortuanately it doesn't work and I always retrieve zero, also for the last register.
MattyD Posted Monday at 11:56 PM Posted Monday at 11:56 PM I don't have a device to test on - but I'd check this... That word[] array is all one element in the struct, so you'll need to go by indices with DllStructGetData. "[1] " & DllStructGetData($tRegisterData, 1, 1) & @CRLF & _ "[2] " & DllStructGetData($tRegisterData, 1, 2) & @CRLF & _ This might also help with debugging... #include <WinAPI.au3> at the top, then... $iRegisterAddr = 0 $iRegisterNum = 8 Local $RegData_t = "struct;word data[" & $iRegisterNum & "];endstruct" Local $tRegisterData = DllStructCreate($RegData_t) Local $aRTU_Result = DllCall($hDLL, "int:cdecl", "modbus_read_registers", "ptr", $pRTU_Ptr[0], "int", $iRegisterAddr, "int", $iRegisterNum, "struct*", $tRegisterData) If @error Then ConsoleWrite("Dll Call Failed" & @CRLF) Else If $aRTU_Result[0] = -1 Then $aCall = DllCall($hDLL, "str:cdecl", "modbus_strerror", "int", _WinAPI_GetLastError()) ConsoleWrite($aCall[0] & @CRLF) Else ConsoleWrite("Registers Read:" & $aRTU_Result[0] & @CRLF) EndIf EndIf PS: we got away with it, but just be careful with data typing here: Local $RTU_Result = DllCall($hDLL, "int:cdecl", "modbus_set_debug", "ptr", $pRTU_Ptr[0], "boolean", True) "boolean" is a 1-byte type, but the modbus_set_debug expects a 4-byte "int". Using the 4-byte "bool" type would be a better option ...but to be pedantic we should probably do something like this as its what's documented. Local $RTU_Result = DllCall($hDLL, "int:cdecl", "modbus_set_debug", "ptr", $pRTU_Ptr[0], "int", 1) ;Or to use True/False (AutoIt should cast True to "int"): Local $RTU_Result = DllCall($hDLL, "int:cdecl", "modbus_set_debug", "ptr", $pRTU_Ptr[0], "int", True)
Geppo Posted Tuesday at 05:52 AM Author Posted Tuesday at 05:52 AM Thank you very much. You are an invaluable source of hints. About the modbus_set_debug function, Local $RTU_Result = DllCall($hDLL, "int:cdecl", "modbus_set_debug", "ptr", $pRTU_Ptr[0], "int", 1) was the first syntax I used but it didn't work, so looking at the documentation, it speaks of "flag", so I supposed a flaw in the documentation, I used "boolean" and worked. Anyway, I will verify it. Surely you are right, the bug is in the wrong retrieving of the registers. I should have to use "[1] " & DllStructGetData($tRegisterData, 1, 1) & @CRLF & _ "[2] " & DllStructGetData($tRegisterData, 1, 2) & @CRLF & _ and so on to retrieve the register value. Today I cannot test it, but I will verified as soon as possible. Thank you again.
MattyD Posted Tuesday at 09:24 AM Posted Tuesday at 09:24 AM My pleasure Geppo, Let us know how you get on
Geppo Posted Wednesday at 10:03 AM Author Posted Wednesday at 10:03 AM You were right. Now I succeed to read the content of registers using "[1] " & DllStructGetData($tRegisterData, 1, 1) & @CRLF & _ "[2] " & DllStructGetData($tRegisterData, 1, 2) & @CRLF & _ And now also works Local $RTU_Result = DllCall($hDLL, "int:cdecl", "modbus_set_debug", "ptr", $pRTU_Ptr[0], "int", 1) using an int parameter. Evidently I have been mistaken. I will proceed to test other modbus functions, then I will code some Autoit functions to manage them at the best. Than you very much for your help. argumentum and MattyD 2
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now