C# in general is the most object oriented high level language for software or (better) solutions development. It still has some elements like C or C++ but it is totally different. C# is not supporting the good old h-files as C. Therefore the interface to the Hart protocol DLL has to take place in a wrapper class module. In the downloadable example it is named BaHartDrvInterface.cs.
First there are statements defining the used objects:
| using
System; using System.Runtime.InteropServices; using System.Text; |
The class name is HartDLL:
| public class HartDLL |
Constants
The definition of constants is very similar to the way it is done in Visual Basic software:
|
//Handles public const int INVALID_DRV_HANDLE = -1; public const int INVALID_SRV_HANDLE = -1; |
In the code they are referred to as:
| if(m_hChannel != HartDLL.INVALID_DRV_HANDLE) |
Structures
Structures are treated in C# as 'little' classes. This is the reason for the usage of the public keyword within the structure definition. In the DLL a fixed structure is required because the DLL is unmanaged code. Therefore the attribute LayoutKind.Sequential is used to place the members of the structure in order:
| [StructLayout(LayoutKind.Sequential)] public struct T_strConfiguration { public int lBaudrate; public byte byNumPreambles; public byte byNumRetries; }; |
Function Declarations
The interface of the DLL is documented in HartDrv40_Intf.pdf. If you want to see the interface at a glance you should refer to the header file: BaHartDrv40.h.
In C# the declarations of the function are similar to C and Visual Basic. They are all starting with the attribute DllImport naming the specific dll. The parameter CharSet is defined to tell the compiler that the called function is not supporting unicode characters.
| [DllImport("BaHartDrv40.dll",
CharSet = CharSet.Ansi)] public static extern int BHDrv_OpenChannel(byte byComPort); [DllImport("BaHartDrv40.dll", CharSet = CharSet.Ansi)] public static extern void BHDrv_CloseChannel(int hChannel); |
Variables
Variables are defined at the beginning of the form class implementation:
|
private
int
m_hChannel; private int m_hService; private HartDLL.T_strConfiguration m_strConfig; private HartDLL.T_strConnection m_strConnection; private HartDLL.T_strConfirmation m_strConfirmation; private HartDLL.T_strDataBuffer m_strDataBuffer; private bool m_bActivated = false; |
Opening and Closing a Channel
The channel is opened in the private function connect:
| m_hChannel = HartDLL.BHDrv_OpenChannel(Convert.ToByte(txtComPort.Text)); |
The statement Convert.ToByte(txtComPort.Text) is getting the text from the textbox and translating its value into a single byte.
If the form is closed a existing connection has to be de-established. This is done in the closing event of the form:
|
private
void
frmTestHartDLL_Closing(object
sender, System.ComponentModel.CancelEventArgs e) { if(m_hChannel != HartDLL.INVALID_DRV_HANDLE) { HartDLL.BHDrv_CloseChannel(m_hChannel); } } |
Connecting to a Hart Device
In the Hart Protocol command 0 is the only command accepting short addresses. Among other information command 0 is used to retrieve the unique identifier of a Hart Device for the usage in the other commands. The following statement is accessing Hart command 0 for a device with the address 0.
| m_hService = HartDLL.BHDrv_ConnectByAddr(m_hChannel, 0, HartDLL.DRV_WAIT, 0); |
The option HartDLL.DRV_WAIT tells the Hart communication software to wait for the completion of the service. This option makes the handling very easy for the programmer. However there is also a disadvantage. Because this code is called in an event procedure it takes some 100 ms until the next event can be processed.
Once the service is successfully completed the data has to be fetched from the interface:
| HartDLL.BHDrv_FetchConnection(m_hService, ref m_strConnection); |
With the expression 'ref m_strConnection' a pointer to the structure of the connection information is passed to the function.
Invoking a Hart Command
For understanding the request of a specific Hart Command we first have to have a look to the function declaration.
| [DllImport("BaHartDrv40.dll",
CharSet = CharSet.Ansi)] public static extern int BHDrv_DoCommand(int hChannel, byte byCmd, byte byQOS, ref byte pbyData, byte byLen, uint lAppKey, ref byte byUniqueID ); |
The expression 'ref byte' is used to define a pointer to the first byte of an array of bytes.
Example: Reading the Tag Name
The service is started with calling DoCommand of the DLL with the Hart command 13.
| m_hService
= HartDLL.BHDrv_DoCommand(
m_hChannel, 13, HartDLL.DRV_NO_WAIT, ref m_strDataBuffer.zData_00, 0, 0, ref m_strConnection.byUniqueId_0 ); if(m_hService != HartDLL.INVALID_SRV_HANDLE) { timPolling.Enabled = true; txtTagName.Text = "-/-"; clear_and_disableControls(); } |
In this example the option DRV_NO_WAIT is set and a timer is started. The call of the request does not wait for the service completion. A timer event is used to poll the service for it's state.
|
private
void
timPolling_Tick(object
sender, System.EventArgs e) { if(HartDLL.BHDrv_IsServiceCompleted(m_hService) != HartDLL.T_FALSE) { |
After the service is completed the data has to be taken from the response data of the Hart communication.
|
StringBuilder sb =
new
StringBuilder(8); HartDLL.BHDrv_PickPackedASCII(sb, 8, 0, ref m_strConfirmation.zData_00); |
StringBuilder is defined in the Namespace System.Text, which is referenced in the begin of the class file:
| using System.Text; |
StringBuilder is an object which allows to build buffers for ansi characters. For the tag name a StringBuilder object of a size of 8 is created and passed to the dll as a pointer to a buffer for an ansi string. This is the reason for setting the parameter CharSet in the declaration of the function BHDrv_IsServiceCompleted. The next step is to place the text into a visible control.
| txtTagName.Text = sb.ToString(0, 8); |
Example: Writing the Tag Name
For writing the tag name we have to get the text from a visible control, have to convert it to packed ascii format and place it into a request frame for the hart protocol.
In the example before when reading the tag name - which comes with tag name, descriptor and date - the data was copied to a local buffer as a stream of bytes.
|
m_strDataBuffer.byDataLen = m_strConfirmation.byDataLen; HartDLL.BHDrv_PickOctets( ref m_strDataBuffer.zData_00, m_strConfirmation.byDataLen, 0, ref m_strConfirmation.zData_00 ); |
First the tag name is prepared using a local string object.
|
string s; s = txtWriteTag.Text.ToUpper(); if(s.Length > 8) { s = s.Substring(0,8); } else { if(s.Length < 8) { //Fill with spaces s = s.PadRight(8); } } |
Now the StringBuilder object is used again as a helper.
|
StringBuilder sb =
new
StringBuilder(8); sb.Insert(0,s); HartDLL.BHDrv_PutPackedASCII(sb, 8, 0, ref m_strDataBuffer.zData_00); |
Now the buffer for the request is prepared and the command 18 (write tag, descriptor, date) can be initiated.
| m_hService
= HartDLL.BHDrv_DoCommand(
m_hChannel, 18, HartDLL.DRV_NO_WAIT, ref m_strDataBuffer.zData_00, m_strDataBuffer.byDataLen, 0, ref m_strConnection.byUniqueId_0 ); |
Note that the unique id is passed as a pointer to the first byte of an array of bytes (ref m_strConnection.byUniqueId).
Example: Reading the Upper Range Value
Command 15 is send for getting the range values of the Hart Device. This pretty much the same as for reading the tag name. Getting the data at service completion is a bit different but less complex. A local float object (f) is used to retrieve the data and display it in a textbox.
|
float f; HartDLL.BHDrv_FetchConfirmation(m_hService, ref m_strConfirmation); //The float value for the upper range starts at byte 3 in response to cmd 15 f = HartDLL.BHDrv_PickFloat(3, ref m_strConfirmation.zData_00, HartDLL.MSB_FIRST); txtPv.Text = f.ToString("##0.00"); |
Example: Encoding a Float Value into a Request Frame
Firstly the value has to be converted from a (e.g.) TextBox into a float object. This is done by the Convert class library.
|
float f; f = Convert.ToSingle(txtPv.Text.Replace(",",".")); |
Then the float is placed into the data buffer for the Hart request frame.
| HartDLL.BHDrv_PutFloat(f, 0, ref m_strDataBuffer.zData_00, HartDLL.MSB_FIRST); |