;by 汤瞬 2021/10/04  V1.1 
;改自官网论坛,可以实现向电脑串口发送并接收报文。并增加了读取电脑端口,CRCmodbus计算,GUI界面,进制转换,提供了两种解析从下位机读取报文的方式(推荐使用第二种)等功能.
;发送报文只需要使用 "发送报文(ars)" 这个命令就可以了。比如 发送报文("01 03 00 00 00 10")
;如果修改本软件并测试时,最好使用串口监视软件,方便查看报文下发是否正确。

#NoEnv
#SingleInstance Force
SetBatchLines, -1
global RS232_FileHandle

ToBase(n,b) {   ;,十进制转二进制:ToBase(a,2) 十进制转十六进制:ToBase(a,16) 十进制转八进制:ToBase(a,8) 十进制转n进制: ToBase(a,n) 十六进制转n进制:ToBase(a,n) ,a为16进制数,以“0X”开头
 return (n < b ? "" : ToBase(n//b,b)) . ((d:=Mod(n,b)) < 10 ? d : Chr(d+55))  
}

CRCmodbus校验(报文,要计算的字节个数){      ; 例子:MsgBox % CRCmodbus校验("20 06 00 22 00 22",6)
报文:= StrReplace(RegExReplace(报文, "(\w+)", "0x$1"), A_Space, ",") ;将报文变为"0x24,0x01,0x04,0x01"这种格式
tmp = 0xffff
buff := StrSplit(报文,"," ) ; 将报文的值变成数组 ["0x01","0x03","0x61","0x00","0x00","0x02"] 
;MsgBox % buff
loop %要计算的字节个数% {
  tmp := buff[A_Index] ^ tmp
  loop 8 {
    if (tmp&0x01) = 1 {
      tmp := tmp >> 1
      tmp := tmp ^ 0xa001
    } else
      tmp := tmp >> 1
    } 
  }
return RegExReplace(Format("{:04}", ToBase(tmp,16)), "(\w\w)(\w\w)", "$2 $1") ;将crc校验变成4位数,然后转成16进制并且将高低字节颠倒,中间加空格
}

发送报文(报文) {               ; 例子: 发送报文("01 03 00 00 00 1D")
  ars := StrReplace(RegExReplace(报文, "(\w+)", "0x$1"), A_Space, ",") ;将报文变为"0x24,0x01,0x04,0x01"这种格式
  CRC := StrReplace(RegExReplace(CRCmodbus校验(报文,6), "(\w+)", "0x$1"), A_Space, ",") ;将校验码变为"0x24,0x03"这种格式
  完整的报文 := ars "," CRC     ;modbus报文格式为报文加CRC校验码
  ;MsgBox % 完整的报文
  ;完整的报文:="0x20,0x03,0x00,0x00,0x00,0x1D,0x83,0x72"
  RS232_Write(RS232_FileHandle,完整的报文) ;向串口写入
}
Gui Font, s10 cblue
Gui Add, Text, x1 y15 w42 h23 +0x200, 端口:
Gui Add, Text, x1 y35 w42 h23 +0x200, 波特率:
Gui Add, Text, x1 y55 w42 h23 +0x200, 校验码:
Gui Add, Text, x1 y75 w42 h23 +0x200, 数据位:
Gui Add, Text, x1 y95 w42 h23 +0x200, 停止位:
Gui, Add, DropDownList, x55 y15 w88 h150 +Sort  vRS232_Port    
Loop, Reg,HKEY_LOCAL_MACHINE\Hardware\DeviceMap\SerialComm   ;获取电脑注册表里有几个端口
{   
  RegRead, Outp, HKEY_LOCAL_MACHINE\Hardware\DeviceMap\SerialComm,%A_LoopRegName%
GuiControl,, ComboBox1 , %Outp% ;将本电脑端口添加进下拉框内,
GuiControl, Choose, ComboBox1, 1
}
Gui, Add, DropDownList, x55 y35 w88 h100   vRS232_Baud    , 1200|2400|3600|4800|7200|9600||14400|19200|28820|115200
Gui, Add, DropDownList, x55 y55 w88 h100   vRS232_Parity  , N|O||E|
Gui, Add, DropDownList, x55 y75 w88 h100   vRS232_Data    , 5|6|7|8||
Gui, Add, DropDownList, x55 y95 w88 h100   vRS232_Stop    , 1||1.5|2|
Gui Add, Button, x55 y125 w88 h30 gButton, 打开端口
Gui Add, Button, x375 y15 w88 h50 gButtons, 发送报文`n含CRCmodbus校验
GuiControl, Disable, Button2     ;默认在没有打开端口之前,禁止使用发送报文按钮
Gui Add, Edit,-VScroll +Uppercase  x150 y15 w180 h40 vars, 20 03 00 00 00 1D   ;自动转为大写
Gui Add, Edit,-VScroll  x150 y75 w260 h70 , 这里显示接收到的报文
Gui +MaximizeBox 
Gui Show, w480 h160 , 串口Modbus助手  v1.1
return

Button:
Gui,Submit ,Nohide ;保存GUI界面设置参数
RS232_Settings = %RS232_Port%:baud=%RS232_Baud% parity=%RS232_Parity% data=%RS232_Data% stop=%RS232_Stop% dtr=Off

if (a := !a) {
  RS232_FileHandle:= RS232_Initialize(RS232_Settings)   ;连接端口
  GuiControl, Text, Button1 , 关闭端口
} else {
  RS232_Close(RS232_FileHandle)     ;关闭端口
  GuiControl, Text, Button1 ,打开端口
  GuiControl, Disable, Button2
}
Return

Buttons:
Gui,Submit ,Nohide
发送报文(ars)
Read_Data := RS232_Read(RS232_FileHandle,"0xFF",RS232_Bytes_Received)   ;接收报文
GuiControl, Text, Edit2 , %Read_Data%
;第一种解析报文方法,不推荐使用
;MsgBox % SubStr(Read_Data, 55, 4) ; 将收到的报文取出来。从第55个数字开始,向后取4位数.
;返回的报文:=RegExReplace(SubStr(Read_Data, 55, 4), "(\w+)", "0x$1")  ;给取到的4位数字加0X前缀(方便转成10进制)
;MsgBox % ToBase(返回的报文,10)  ;转成10进制
;第二种解析方法,推荐使用. 
b:= RegExReplace(Read_Data, ".{6}(.+)", "$1")   ;将前六个字符去掉:modbus地址,功能码,回的字节数
;b := RegExReplace(b, "(.{4})", "$1")   ;高低字节颠倒(根据报文协议确定是否颠倒)
;b:=RegExReplace(b, "(\w\w)(\w\w)", "$2$1") ;高低字节颠倒(根据报文协议确定是否颠倒)
b := RegExReplace(b, "(.{4})", "0x$1-") ;将字符每4个分成一组,并添加0x前缀和-后缀(方便添加到数组)
c := StrSplit(RTrim(b,"-"), "-")   ;添加进数组
;MsgBox % ToBase(c[13],10)/10  ;转成10进制并除以10
return

GuiClose:
    ExitApp

f4::
data := "m.f6000"
loop parse,data
{
  var := Asc(SubStr(A_LoopField,0)) ;Get the key that was pressed and convert it to its ASCII code
  msgbox %var%
  RS232_Write(RS232_FileHandle,var) ;Send it out the RS232 COM port
}
RS232_Write(RS232_FileHandle,13) ;Send it out the RS232 COM port
return

;###### Shift Key Presses ######
HotkeySub_Char_Shift:
var := SubStr(A_ThisHotkey,0) ;Get the key that was pressed.
StringUpper, var, var         ;Convert it to uppercase
var := Asc(var)               ;Get the ASCII equivalent
RS232_Write(RS232_FileHandle,var)  ;Send it out the RS232 COM port
return

;########################################################################
;###### Initialize RS232 COM Subroutine #################################
;########################################################################
RS232_Initialize(RS232_Settings)
{
  ;###### Extract/Format the RS232 COM Port Number ######
  ;7/23/08 Thanks krisky68 for finding/solving the bug in which RS232 COM Ports greater than 9 didn't work.
  StringSplit, RS232_Temp, RS232_Settings, `:
  RS232_Temp1_Len := StrLen(RS232_Temp1)  ;For COM Ports > 9 \\.\ needs to prepended to the COM Port name.
  If (RS232_Temp1_Len > 4)                   ;So the valid names are
    RS232_COM = \\.\%RS232_Temp1%             ; ... COM8  COM9   \\.\COM10  \\.\COM11  \\.\COM12 and so on...
  Else                                          ;
    RS232_COM = \\.\%RS232_Temp1%
  
  ;8/10/09 A BIG Thanks to trenton_xavier for figuring out how to make COM Ports greater than 9 work for USB-Serial Dongles.
  StringTrimLeft, RS232_Settings, RS232_Settings, RS232_Temp1_Len+1 ;Remove the COM number (+1 for the semicolon) for BuildCommDCB.
  ;MsgBox, RS232_COM=%RS232_COM% `nRS232_Settings=%RS232_Settings%

  ;###### Build RS232 COM DCB ######
  ;Creates the structure that contains the RS232 COM Port number, baud rate,...
  VarSetCapacity(DCB, 28)
  BCD_Result := DllCall("BuildCommDCB"
       ,"str" , RS232_Settings ;lpDef
       ,"UInt", &DCB)        ;lpDCB
  If (BCD_Result <> 1)
  {
   MsgBox, There is a problem with Serial Port communication. `nFailed Dll BuildCommDCB, BCD_Result=%BCD_Result%.
    ;Exit
  RS232_FileHandle:=0
  
  Return %RS232_FileHandle%
  }

    ;###### Create RS232 COM File ######
    ;Creates the RS232 COM Port File Handle
    RS232_FileHandle := DllCall("CreateFile"
       ,"Str" , RS232_COM     ;File Name         
       ,"UInt", 0xC0000000   ;Desired Access
       ,"UInt", 3            ;Safe Mode
       ,"UInt", 0            ;Security Attributes
       ,"UInt", 3            ;Creation Disposition
       ,"UInt", 0            ;Flags And Attributes
       ,"UInt", 0            ;Template File
       ,"Cdecl Int")

    If (RS232_FileHandle < 1)
    {
    MsgBox, 串口打开失败,请检查端口是否被占用!
    RS232_FileHandle:=0
    Return %RS232_FileHandle%
    }
GuiControl, Enable, Button2
    ;###### Set COM State ######
    ;Sets the RS232 COM Port number, baud rate,...
    SCS_Result := DllCall("SetCommState"
       ,"UInt", RS232_FileHandle ;File Handle
       ,"UInt", &DCB)          ;Pointer to DCB structure
    If (SCS_Result <> 1)
    {
    ;MsgBox, There is a problem with Serial Port communication. `nFailed Dll SetCommState, SCS_Result=%SCS_Result% `nThe Script Will Now Exit.
    RS232_Close(RS232_FileHandle)
    ;Exit
    }

    ;###### Create the SetCommTimeouts Structure ######
    ReadIntervalTimeout        = 0xffffffff
    ReadTotalTimeoutMultiplier = 0x00000000
    ReadTotalTimeoutConstant   = 0x00000000
    WriteTotalTimeoutMultiplier= 0x00000000
    WriteTotalTimeoutConstant  = 0x00000000

    VarSetCapacity(Data, 20, 0) ; 5 * sizeof(DWORD)
    NumPut(ReadIntervalTimeout,         Data,  0, "UInt")
    NumPut(ReadTotalTimeoutMultiplier,  Data,  4, "UInt")
    NumPut(ReadTotalTimeoutConstant,    Data,  8, "UInt")
    NumPut(WriteTotalTimeoutMultiplier, Data, 12, "UInt")
    NumPut(WriteTotalTimeoutConstant,   Data, 16, "UInt")
    
    ;###### Set the RS232 COM Timeouts ######
    SCT_result := DllCall("SetCommTimeouts"
     ,"UInt", RS232_FileHandle ;File Handle
     ,"UInt", &Data)         ;Pointer to the data structure
    If (SCT_result <> 1)
    {
    ;MsgBox, There is a problem with Serial Port communication. `nFailed Dll SetCommState, SCT_result=%SCT_result% `nThe Script Will Now Exit.
    RS232_Close(RS232_FileHandle)
    ;Exit
    } 
  ;Msgbox %RS232_FileHandle%
  Return %RS232_FileHandle%
}

; Close RS23 COM Subroutine 
RS232_Close(RS232_FileHandle)
{
  ;###### Close the COM File ######
  CH_result := DllCall("CloseHandle", "UInt", RS232_FileHandle)
  If (CH_result <> 1)
    ;MsgBox, Failed Dll CloseHandle CH_result=%CH_result%
  Return
}

; Write to RS232 COM Subroutines 
RS232_Write(RS232_FileHandle,Message)
{
  ;SetFormat, Integer, DEC
  ;Parse the Message. Byte0 is the number of bytes in the array.
  StringSplit, Byte, Message, `,
  Data_Length := Byte0
  ;MsgBox, Data_Length=%Data_Length% b1=%Byte1% b2=%Byte2% b3=%Byte3% b4=%Byte4%
  ;Set the Data buffer size, prefill with 0xFF.
  VarSetCapacity(Data, Byte0, 0xFF)
  ;Write the Message into the Data buffer
  i=1
  Loop %Byte0%
  {
    NumPut(Byte%i%, Data, (i-1) , "UChar")
   ;MsgBox, % Byte%i%
    i++
  }
  ;MsgBox, Data string=%Data%
  ;###### Write the data to the RS232 COM Port ######
  WF_Result := DllCall("WriteFile"
       ,"UInt" , RS232_FileHandle ;File Handle
       ,"UInt" , &Data          ;Pointer to string to send
       ,"UInt" , Data_Length    ;Data Length
       ,"UInt*", Bytes_Sent     ;Returns pointer to num bytes sent
       ,"Int"  , "NULL")
  If (WF_Result <> 1 or Bytes_Sent <> Data_Length)
    MsgBox, Failed Dll WriteFile to RS232 COM, result=%WF_Result% `nData Length=%Data_Length% `nBytes_Sent=%Bytes_Sent%
}

; Read from RS232 COM Subroutines 
RS232_Read(RS232_FileHandle,Num_Bytes,ByRef RS232_Bytes_Received)
{
  SetFormat, Integer, HEX
  ;Set the Data buffer size, prefill with 0x55 = ASCII character "U"
  ;VarSetCapacity won't assign anything less than 3 bytes. Meaning: If you
  ;  tell it you want 1 or 2 byte size variable it will give you 3.
  Data_Length  := VarSetCapacity(Data, Num_Bytes, 0x55)
  ;MsgBox, Data_Length=%Data_Length%
  ;###### Read the data from the RS232 COM Port ######
  ;MsgBox, RS232_FileHandle=%RS232_FileHandle% `nNum_Bytes=%Num_Bytes%
  Read_Result := DllCall("ReadFile"
       ,"UInt" , RS232_FileHandle   ; hFile
       ,"Str"  , Data             ; lpBuffer
       ,"Int"  , Num_Bytes        ; nNumberOfBytesToRead
       ,"UInt*", RS232_Bytes_Received   ; lpNumberOfBytesReceived
       ,"Int"  , 0)               ; lpOverlapped
  ;MsgBox, RS232_FileHandle=%RS232_FileHandle% `nRead_Result=%Read_Result% `nBR=%RS232_Bytes_Received% ,`nData=%Data%
  If (Read_Result <> 1)
  {
   ; MsgBox, There is a problem with Serial Port communication. `nFailed Dll ReadFile on RS232 COM, result=%Read_Result% - The Script Will Now Exit.
    RS232_Close(RS232_FileHandle)
    Exit
  }
  ;###### Format the received data ######
  ;This loop is necessary because AHK doesn't handle NULL (0x00) characters very nicely.
  ;Quote from AHK documentation under DllCall:
  ;     "Any binary zero stored in a variable by a function will hide all data to the right
  ;     of the zero; that is, such data cannot be accessed or changed by most commands and
  ;     functions. However, such data can be manipulated by the address and dereference operators
  ;     (& and *), as well as DllCall itself."
  i = 0
  Data_HEX =
  Loop %RS232_Bytes_Received%
  {
    ;First byte into the Rx FIFO ends up at position 0
    Data_HEX_Temp := NumGet(Data, i, "UChar") ;Convert to HEX byte-by-byte
    StringTrimLeft, Data_HEX_Temp, Data_HEX_Temp, 2 ;Remove the 0x (added by the above line) from the front
    ;If there is only 1 character then add the leading "0'
    Length := StrLen(Data_HEX_Temp)
    If (Length =1)
      Data_HEX_Temp = 0%Data_HEX_Temp%
    i++
    ;Put it all together
    Data_HEX := Data_HEX . Data_HEX_Temp
  }
  ;MsgBox, Read_Result=%Read_Result% `nRS232_Bytes_Received=%RS232_Bytes_Received% ,`nData_HEX=%Data_HEX%
  SetFormat, Integer, DEC
  Data := Data_HEX
  Return Data
}

 

声明:站内资源为整理优化好的代码上传分享与学习研究,如果是开源代码基本都会标明出处,方便大家扩展学习路径。请不要恶意搬运,破坏站长辛苦整理维护的劳动成果。本站为爱好者分享站点,所有内容不作为商业行为。如若本站上传内容侵犯了原著者的合法权益,请联系我们进行删除下架。