准备工具
1. VSPD虚拟串口工具,(7年前用过的工具,现在又用上了,幸好CSDN上传了一份,嘿嘿)
https://download.csdn.net/download/ericwuhk/3753942
2. Modbus Slave调试工具
https://download.csdn.net/download/ericwuhk/10357886
3.libModbus封装库
https://download.csdn.net/download/ericwuhk/10352801
操作步骤:
1.VSPD绑定可以使用的串口号,如COM2与COM3,点击添加端口。
2.Modbus Slave工具点击Connection,选择“serial RTU”模式,COM口选择Port2格式选择如“192000, 8,‘E’,1”,
3.将基于C语言开发的libmodbus类库添加到c#项目工程中,C类库中添加读取ini配置文件的函数,增加需要给C#调用的函数接口
cf_op.h,cf_op.c
#pragma once
#define LineMaxLen 2048
#define KeyMaxLen 128
#define MaxFileLength 1024*10
#ifndef CF_OP_H
#define CF_OP_H
#ifdef __cplusplus
extern "C" {
#endif
int GetCfItem(const char *pFileName /*in*/, char *pKey /*in*/, char * pValue/*in out*/, int * pValueLen /*out*/);
int SetCfItem(const char *pFileName /*in*/, char *pKey /*in*/, char *pValue/*in*/);
#ifdef __cplusplus
}
#endif
#if defined(_MSC_VER)
# if defined(DLLBUILD)
/* define DLLBUILD when building the DLL */
# define MODBUS_API __declspec(dllexport)
# else
# define MODBUS_API __declspec(dllimport)
# endif
#else
# define MODBUS_API
#endif
static char getValueMsg[KeyMaxLen];
static char setValueMsg[KeyMaxLen];
MODBUS_API char * getValue(char *key);
MODBUS_API char * setValue(char *key, char *value);
MODBUS_API int sum(int a, int b);//only for test
#endif
char* getValue(char *key)// chane to key[KeyMaxLen] char *key
{
char *pFileName = "Platform.ini";
int len = 0, rv = 0;
memset(getValueMsg, 0, sizeof(getValueMsg));
rv = GetCfItem(pFileName, key, getValueMsg, &len);
if (rv != 0)
{
char *errMsg;
printf("Get the key value err!");
switch (rv)
{
case -1:
errMsg = "func GetCfItem paramter err!";
break;
case -2:
errMsg = "fopen file err!";
break;
case -3:
errMsg = "The key has no value!";
break;
case -4:
errMsg = "can't find the key";
break;
default:
break;
}
strcpy(getValueMsg, errMsg);
}
return getValueMsg;
}
char* setValue(char *key,char *value)
{
int rv = 0;
char *pFileName = "Platform.ini";
memset(setValueMsg, 0, sizeof(setValueMsg));
memset(value, 0, sizeof(value));*/
rv = SetCfItem(pFileName, key, value);
if (rv != 0)
{
char *errMsg;
printf("Get the key value err!");
switch (rv)
{
case -1:
errMsg = "func WriteCfItem paramter err!";
break;
case -2:
errMsg = "fopen file err";
break;
case -3:
errMsg = "file too long unspport";
break;
case -4:
errMsg = "fopen file err";
break;
default:
break;
}
strcpy(setValueMsg, errMsg);
return setValueMsg;
}
sprintf(setValueMsg, "key %s=%s set OK", key, value);
return setValueMsg;
}
int sum(int a, int b)
{
return a + b;
}
rotControl.h , rotControl.c
/*
* Copyright © <wu_xx2008@hotmail.com>
*
* antoy wu
* 2018-4-16 21:16:30
*/
#ifndef ROTCONTROL_H
#define ROTCONTROL_H
/* Add this for macros that defined unix flavor */
#if (defined(__unix__) || defined(unix)) && !defined(USG)
#include <sys/param.h>
#endif
#ifndef _MSC_VER
#include <stdint.h>
#else
#include "stdint.h"
#endif
#include "modbus-version.h"
#include <stdbool.h>
#include "RotationPlatformDefine.h"
#if defined(_MSC_VER)
# if defined(DLLBUILD)
/* define DLLBUILD when building the DLL */
# define ROT_API __declspec(dllexport)
# else
# define ROT_API __declspec(dllimport)
# endif
#else
# define MODBUS_API
#endif
char* messageStr;
volatile bool b_stop;
bool b_isInitial;
modbus_t* m_ctx;
int m_rc;
int m_nb_fail;
int m_nb_loop;
int m_addr;
int m_nb;
uint8_t* m_tab_rq_bits;
uint8_t* m_tab_rp_bits;
uint16_t* m_tab_rq_registers;
uint16_t* m_tab_rw_rq_registers;
uint16_t* m_tab_rp_registers;
uint16_t m_tab_reg[32] = { 0 };
uint8_t m_rec_reg[32] = { 0 };
uint8_t m_buf_8[32] = { 0 };
uint16_t m_buf_16[32] = { 0 };
int32_t m_cur_pos[3] = { 0 };
int32_t m_cur_vel[3] = { 0 };
int32_t m_cur_acc[3] = { 0 };
bool b_busy;
bool b_alarm;
bool b_emgency;
PlatformSetup m_platform_setup;
ROT_API bool rotInit();
#endif /* ROTCONTROL_H */
void paraInit()
{
b_stop = false;
b_isInitial = false;
m_ctx = NULL;
readIni();
}
bool modbusInit()
{
const char *device = getValue("Serial");
//sprintf(device, "COM%s", device);
if (m_ctx == NULL) {
m_ctx = modbus_new_rtu(device, Baudrate, Parity, Databit, Stopbit);
}
int ret = modbus_set_slave(m_ctx, SLAVE); // set slave adress
if (ret == -1) {
return false;
}
ret = modbus_connect(m_ctx);
if (ret == -1) {
return false;
}
b_isInitial = true;
return b_isInitial;
}
bool rotInit()
{
paraInit();//系统参数初始化以及读取配置文件
if (modbusInit())
{
b_isInitial = true;
}
return b_isInitial;
}
void closeModbus()
{
if(m_ctx!=NULL)
{
modbus_close(m_ctx);
modbus_free(m_ctx);
}
}
C#增加一个类库文件RotPlatformControl_CsDll,用于导入modbus.dll中的各个函数。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
namespace RotPlatformControl_CsDll
{
public class CRotPlatformControl
{
[DllImport("modbus.dll", EntryPoint = "sum", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern int sum(int a, int b);
[DllImport("modbus.dll", EntryPoint = "getValue", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr getValue(IntPtr intPtrKey);
[DllImport("modbus.dll", EntryPoint = "setValue", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern IntPtr setValue(IntPtr intPtrKey, IntPtr intPtrValue);
[DllImport("modbus.dll", EntryPoint = "rotInit", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
public static extern bool rotInit();
}
}
UI调试界面增加一些按钮等。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Principal;
using RotPlatformControl_CsDll;
namespace Mirage2B_IMU_Test
{
public partial class IMUDebugTool : Form
{
public IMUDebugTool()
{
InitializeComponent();
}
private void buttonHome_Click(object sender, EventArgs e)
{
log("转台开始回位!");
bool isRotInit=CRotPlatformControl.rotInit();
if(isRotInit)
lblMessage.Text = isRotInit.ToString();
//string res = Getvalue("Baudrate");
//lblMessage.Text = res.ToString();
}
private void buttonStop_Click(object sender, EventArgs e)
{
string strKey = "Hello";
string strValue = "Kitty";
string res=setvalue(strKey,strValue);
lblMessage.Text = res.ToString();
}
private string Getvalue(string strKey)
{
IntPtr key = Marshal.StringToHGlobalAnsi(strKey);
IntPtr temp = CRotPlatformControl.getValue(key);
string res = Marshal.PtrToStringAnsi(temp).ToString();
temp = IntPtr.Zero;
return res;
}
private string setvalue(string strKey,string strValue)
{
IntPtr key = Marshal.StringToHGlobalAnsi(strKey);
IntPtr value = Marshal.StringToHGlobalAnsi(strValue);
IntPtr temp = CRotPlatformControl.setValue(key, value);
string res = Marshal.PtrToStringAnsi(temp).ToString();
temp = IntPtr.Zero;
return res;
}
private void log (string strMsg)
{
ShowMsg(strMsg, Color.Blue);
}
private void error(string strErrMsg)
{
ShowMsg(strErrMsg, Color.Red);
}
private delegate void ShowMsgHandle(string msgString, Color color);
private void ShowMsg(string msgString, Color color)
{
if (!lblMessage.InvokeRequired)
{
lblMessage.Text = msgString;
lblMessage.ForeColor = color;
lblMessage.Refresh();
}
else
{
Invoke(new ShowMsgHandle(ShowMsg), msgString, color);
}
}
}
}
最终结果:
几个注意的细节记录如下:
1.C类库中的char*需要对应于c#中的IntPtr,且作为返回值是的char*变量必须为全局变量,否则调用返回为空。
2.为了方便调试,在同一个解决方案下c编译产生的dll文件与c# 类库文件的输出路径最好统一设置在主程序目录下。
3.C#项目属性勾选Debug->Enable Native code debugging,支持本地调试,便于c#调试过程中可以进入到C Dll的源码中进行单步调试。