Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Serial ( COM ) Port Console Script


  • Please log in to reply
136 replies to this topic
aobrien
  • Members
  • 77 posts
  • Last active: Mar 06 2014 04:42 AM
  • Joined: 14 Feb 2008
[UPDATE]
The code posted here uses the port.dll file that can be found on the web for free. The problem with port.dll is that it will only send one byte of data every 15.8ms (translates to 633 baud rate) regardless of the intended baud rate. This is a limitation of the port.dll file.

I am leaving this port.dll code here as a reference and because it works, however, I don't recommend using port.dll.

The recommended method to send/receive on the COM port is in the next post
[/UPDATE]


Well, at the risk of embarasing myself, here is a serial port console script that I pieced together using code that I found here on the forum.

I took me quite some time to put it together. So, I am placing it here so others can benefit.

The real power of this script is the ease with which it can be modified to send data instead of ASCII characters - something HyperTerminal (and other terminal emulators) can't do.

;=================== Serial_Port_Console.ahk ===========================
; 2/15/08
; Assembled, tested (on WinXP), and debugged by aobrien with help from  
;   other AHK forum members and threads.
;
; Instructions:
; 1) Copy port.dll from the link below and put into windows 
;    system32 directory
;    http://www.the-starbearer.de/Praxis/ElektronikamPC/RS232/portdll.htm 
;
; 2) Modify the COM port settings (under the User Variables heading) 
;    to your needs and save the file.
; 
; 3) Launch this script to connect to the COM Port
;
; 4) CTRL-F1 to close the COM port and exit the receive loop
;
; Script Behavior/Notes:
; * The script is designed to use a text editor (Notepad) to place the
;     received COM port characters.
; * When you attempt to type into the designated text editor the script
;     will capture the character and send it out the COM port. This is
;     accomplished with the Hotkey Assignments section.
; * Currently the script is written to only send/receive ASCII characters,
;     however, it would be REALLY EASY to modify the script so that it
;     will output/input data - something that HyperTerminal can't do.
; * When you first launch the script it will open Notepad and save it
;     using the Console_Path variable and a predetermined file name.
;
; !!!The Notepad text file MUST be saved so that the words
;      "COM1_Console_(timestamp).txt - Notepad" appear as the window 
;      title, because the script will want to change to the window with 
;      that name when it receives a character on the COM port.
;
;========================================================================

#SingleInstance Force
SetTitleMatchMode, 2

;========================================================================
;====== User Variables ==================================================
;========================================================================
COM_Port     = COM1
COM_Baud     = 115200 
COM_Parity   = N
COM_Data     = 8
COM_Stop     = 1
Console_Path = C:\aobrien\_AutoHotKey_Scripts\COM_Port

;========================================================================
;====== Script Variables ================================================
;========================================================================
COM_Settings     = %COM_Port%:%COM_Baud%,%COM_Parity%,%COM_Data%,%COM_Stop%
Console_File_Name= %COM_Port%_Console.txt
Console_Title    = %Console_File_Name% - Notepad

;========================================================================
;====== Notepad Console Check ===========================================
;========================================================================
;Check for console, if there isn't already one, then open it.
IfWinNotExist, %Console_Title%
{
  Run, Notepad
  WinWait, Untitled - Notepad
  Send, !fa  ;file save as
  WinWait, Save As
  Clipboard = %Console_Path%\%Console_File_Name%
  Send, ^v{ENTER}
}

;========================================================================
;====== Hotkey Assignments - Used for Serial Port Transmit ==============
;========================================================================
;If the Console window is the focus then typing any character on the 
;  keyboard will cause the script to send the character out the COM port.
Hotkey, IfWinActive, %Console_Title%,

;Direct Key Presses for a-z
Loop, 26
  HotKey, % "$" chr(96+A_Index), HotkeySub

;Direct Key Presses for 0-9
Loop, 10
  HotKey, % "$" chr(47+A_Index), HotkeySub

;Direct Key Presses for Other Keys
HotKey, -, HotkeySub
HotKey, =, HotkeySub
HotKey, [, HotkeySub
HotKey, ], HotkeySub
HotKey, `;, HotkeySub
HotKey, `', HotkeySub
HotKey, `,, HotkeySub
HotKey, `., HotkeySub
HotKey, `/, HotkeySub
HotKey, `\, HotkeySub
Hotkey, Space, HotkeySub
Hotkey, ENTER, HotkeySub
Hotkey, BS, HotkeySub

;Shift Modified Character Key Presses for a-z.
Loop, 26
  HotKey, % "$+" chr(96+A_Index), HotkeySub_Char_Shift

;Shift Modified Character Key Presses for 0-9
Loop, 10
  HotKey, % "$+" chr(47+A_Index), HotkeySub_Other_Shift

;Shift Modified Character Key Presses for Other Keys
HotKey, +-, HotkeySub_Other_Shift
HotKey, +=, HotkeySub_Other_Shift
HotKey, +[, HotkeySub_Other_Shift
HotKey, +], HotkeySub_Other_Shift
HotKey, +`;, HotkeySub_Other_Shift
HotKey, +`', HotkeySub_Other_Shift
HotKey, +`,, HotkeySub_Other_Shift
HotKey, +`., HotkeySub_Other_Shift
HotKey, +`/, HotkeySub_Other_Shift
HotKey, +`\, HotkeySub_Other_Shift

;========================================================================
;====== Serial Port Receive =============================================
;========================================================================

;Quit_var is used to exit the COM port receive loop
Quit_var = 0

hModule := DllCall("LoadLibrary", "str", "Port.dll")
result := DllCall("Port.dll\OPENCOM", "str", COM_Settings)
if result = 0
{
  MsgBox, AHK could not open %COM_Port%
  return
}

MsgBox, AHK is now connected to %COM_Port%

SetFormat, integer, data

;COM port receive loop
data = 0
Loop 
{
  data := DllCall("Port.dll\READBYTE")

  ;This will send every byte that is received on 
  ;  the COM port to the Notepad Console
  If data <> -1
  {
    Critical, On
    WinWait, %Console_Title%, 
    IfWinNotActive, %Console_Title%, , WinActivate, %Console_Title%, 
    WinWaitActive, %Console_Title%,    
    
    ;If you want to see the decimal data (as received by the serial port,
    ;  then uncomment the line below.
    ;Send, ^{END}data=%data%{ENTER}
    ASCII := chr(data)
    Send, ^{END}%ASCII%
    Critical, Off
  }

  if Quit_var = 1
    Break

}

result := DllCall("c:\temp\Port.dll\CLOSECOM")
DllCall("FreeLibrary", "UInt", hModule) 
MsgBox, AHK is now disconnected from %COM_Port%

return

;========================================================================
;====== Serial Port Transmit ============================================
;========================================================================
HotkeySub:
var := Asc(SubStr(A_ThisHotkey,0)) ;Get the key that was pressed and convert it to its ASCII code
If A_ThisHotkey = Space
  var=32
If A_ThisHotkey = BS
  var=8
If A_ThisHotkey = ENTER
  {
    var=10 ;New Line
    d := DllCall("Port\SENDBYTE", "UInt", var)
    var=13 ;Carriage Return
  }
d := DllCall("Port\SENDBYTE", "UInt", var)
return

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
d := DllCall("Port\SENDBYTE", "UInt", var) ;Send it out the COM port
return

HotkeySub_Other_Shift:
var := SubStr(A_ThisHotkey,0) ; Get the key that was pressed.
;Convert it to the shift version
;  StringUpper won't work on the following.
If var = 1
  var = !
If var = 2
  var = @
If var = 3
  var = #
If var = 4
  var = $
If var = 5
  var = `%
If var = 6
  var = ^
If var = 7
  var = &
If var = 8
  var = *
If var = 9
  var = (
If var = 0
  var = )
If var = -
  var = _
If var = =
  var = +
If var = [
  var = {
If var = ]
  var = }
If var = \
  var = |
If var = `;
  var = :
If var = `'
  var = "
If var = `,
  var = <
If var = `.
  var = >
If var = `/
  var = ?

var := Asc(var)  ;Get the ASCII equivalent
d := DllCall("Port\SENDBYTE", "UInt", var) ;Send it out the COM port
return

;========================================================================
;====== Save & Reload and Minimize AHK Script ===========================
;========================================================================
^!w::
Send, ^s
Sleep, 200

Reload
Sleep, 1000

return

;========================================================================
;====== Exit Console Receive Loop =======================================
;========================================================================
^F1::
Quit_var = 1
return

[EDIT] I was trying to be slick by putting a timestamp on the notepad file name - without testing it. Doing this broke the script. The code shown above works as advertised. Sorry for any confusion this may have caused.[/EDIT]

aobrien
  • Members
  • 77 posts
  • Last active: Mar 06 2014 04:42 AM
  • Joined: 14 Feb 2008
The following code uses built in Microsoft Windows dll/functions. This is the method that I recommend for reading/writing the serial ports.

[EDIT] 4/2/08 - Made corrections to "Create the SetCommTimeouts Structure" section as suggested by Lexikos[/EDIT]

[EDIT] 5/22/08 - Made corrrection to Read_from_COM() subroutine. Removed redundant line from COM port receive loop. Thanks Ron1 for reporting the bug. [/EDIT]

[EDIT] 7/23/08 - Fixed the following problems:
1. Reading NULL (0x00). Previously, if NULL (0x00) data was received then everything following the NULL was omitted. As a result Read_from_COM() now returns DATA instead of ASCII. This is how the function should have been written from the beginning.
Example: Read_Data := Read_from_COM("0xFF") ;if the RX buffer contained 0x11, 0x22, 0x00, 0x33, 0x44 then Read_Data will contain 1122003344. The conversion to ASCII routine can be found in the "Serial Port Receive" section of the script.

2. Using COM Ports larger than 9. The script can now handle COM ports > 9. Thanks krisky68 for reporting and finding the fix for this bug. I was able to reproduce this bug when I changed the internal COM port on my laptop (Dell 620) from COM1 to COM11. The fix supplied by krisky68 allows my internal COM port to work at COM11. However, I often use the "Prolific USB-to-Serial Comm Port" adaptors and when I set it to COM11 then I get a failure at the "BuildCommDCB" dll call -- I have no clue how to fix this and I suspect that it is a limitation of the adaptor.

The procedure on how to change the COM# for your device can be found on the first post in the second page of this topic.[/EDIT]

[EDIT] 8/11/09 - Implemented trenton_xavier's fix to allow USB-Serial adaptors to function above COM9.[/EDIT]

[EDIT] 4/20/10 - Made the following changes:
1. On line 185 changed: (suggested by Raccoon - better late than never :) )
Send, ^{END}%ASCII%
to
Send, ^{END}
SendRaw,%ASCII%

2. Made the script stdlib compatible as suggested by haichen. The changes are very minor... basically changed the name of the functions from COM_xxx to RS232_xxx (COM_ was already used elsewhere) and added the FileHandle as a parameter of the function call. If for some strange reason anyone wants a copy of the old non-stdlib compatible version of the script then send me a PM and I'll gladly send it to you.[/EDIT]

;################### Serial_Port_Console_ReadFile.ahk ###################
; 8/11/09
; Assembled, tested (on WinXP), and debugged by aobrien with help from
;   other AHK forum members (especially Lexikos) and threads.
;
; This script is based upon dll structures that are built into the
; Microsoft Windows environment. This script is ugly (not very readable),
; because, it was written for demonstration purposes and I didn't want
; to complicate things by using #Include files.
;
; The most useful subroutines are listed below. Take them and write your
; application around them.
;     RS232_FileHandle:= RS232_Initialize(RS232_Settings) -- Get the filehandle
;     RS232_Close(RS232_FileHandle)
;     RS232_Read(RS232_FileHandle,"0xFF",RS232_Bytes_Received) -- 0xFF Size of receive buffer. This 
;                                returns HEX data (in ASCII form) received from the serial port.
;     Example: Read_Data := RS232_Read(RS232_FileHandle,"0xFF",RS232_Bytes_Received) ;if the RX buffer 
;              contained 0x11, 0x22, 0x00, 0x33, 0x44 then Read_Data will contain 1122003344
;
;     RS232_Write(RS232_FileHandle,Hex_Data) -- Comma delimited hex data. If I wanted to
;                               send "Hello World" I would do the following:
;     ;ASCII DATA=  H    e    l    l    o   SP    W    o    r    l    d
;     Hex_Data = 0x48,0x65,0x6C,0x6C,0x6F,0x20,0x57,0x6F,0x72,0x6C,0x64
;     RS232_Write(RS232_FileHandle,Hex_Data)
;
; Instructions:
; 1) Modify the RS232 port settings (under the User Variables heading)
;    to your needs and save the file.
;
; 2) Launch this script to connect to the RS232 COM Port.
;
; 3) CTRL-F1 to close the RS232 COM port and exit the receive loop.
;
; Script Behavior/Notes:
; * The script is designed to use a text editor (Notepad) to place the
;     received RS232 COM port characters.
; * When you attempt to type into the designated text editor the script
;     will capture the character and send it out the RS232 COM port. This is
;     accomplished with the Hotkey Assignments section.
; * Currently the script is written to only send/receive ASCII characters,
;     however, it would be REALLY EASY to modify the script so that it
;     will output/input data - something that HyperTerminal can't do.
; * When you first launch the script it will open Notepad and save it
;     using the Console_Path variable and a predetermined file name.
;
; !!!The Notepad text file MUST be saved so that the words
;      "COM1_Console.txt - Notepad" appear as the window
;      title, because the script will want to change to the window with
;      that name when it receives a character on the RS232 COM port.
;
;########################################################################
MsgBox, Begin RS232 COM Test

#SingleInstance Force
SetTitleMatchMode, 2

;########################################################################
;###### User Variables ##################################################
;########################################################################
RS232_Port     = COM1
RS232_Baud     = 115200
RS232_Parity   = N
RS232_Data     = 8
RS232_Stop     = 1
Console_Path =

;########################################################################
;###### Script Variables ################################################
;########################################################################
RS232_Settings   = %RS232_Port%:baud=%RS232_Baud% parity=%RS232_Parity% data=%RS232_Data% stop=%RS232_Stop% dtr=Off
Console_File_Name= %RS232_Port%_Console.txt
Console_Title    = %Console_File_Name% - Notepad

;########################################################################
;###### Notepad Console Check ###########################################
;########################################################################
;Check for console, if there isn't already one, then open it.
IfWinNotExist, %Console_Title%
{
  Run, Notepad
  WinWait, Untitled - Notepad
  Send, !fa  ;file save as
  WinWait, Save As
  Clipboard = %Console_Path%\%Console_File_Name%
  Send, ^v{ENTER}
}

;########################################################################
;###### Hotkey Assignments - Used for Serial Port Transmit ##############
;########################################################################
;If the Console window is the focus then typing any character on the
;  keyboard will cause the script to send the character out the RS232 COM port.
Hotkey, IfWinActive, %Console_Title%,

;###### Direct Key Presses for a-z ######
Loop, 26
  HotKey, % "$" chr(96+A_Index), HotkeySub

;###### Direct Key Presses for 0-9 ######
Loop, 10
  HotKey, % "$" chr(47+A_Index), HotkeySub

;###### Direct Key Presses for Other Keys ######
HotKey, -, HotkeySub
HotKey, =, HotkeySub
HotKey, [, HotkeySub
HotKey, ], HotkeySub
HotKey, `;, HotkeySub
HotKey, `', HotkeySub
HotKey, `,, HotkeySub
HotKey, `., HotkeySub
HotKey, `/, HotkeySub
HotKey, `\, HotkeySub
Hotkey, Space, HotkeySub
Hotkey, ENTER, HotkeySub
Hotkey, BS, HotkeySub

;###### Shift Modified Character Key Presses for a-z. ######
Loop, 26
  HotKey, % "$+" chr(96+A_Index), HotkeySub_Char_Shift

;###### Shift Modified Character Key Presses for 0-9 ######
Loop, 10
  HotKey, % "$+" chr(47+A_Index), HotkeySub_Other_Shift

;###### Shift Modified Character Key Presses for Other Keys ######
HotKey, +-, HotkeySub_Other_Shift
HotKey, +=, HotkeySub_Other_Shift
HotKey, +[, HotkeySub_Other_Shift
HotKey, +], HotkeySub_Other_Shift
HotKey, +`;, HotkeySub_Other_Shift
HotKey, +`', HotkeySub_Other_Shift
HotKey, +`,, HotkeySub_Other_Shift
HotKey, +`., HotkeySub_Other_Shift
HotKey, +`/, HotkeySub_Other_Shift
HotKey, +`\, HotkeySub_Other_Shift

;########################################################################
;###### Serial Port Receive #############################################
;########################################################################
;Quit_var is used to exit the RS232 COM port receive loop
;  0=Don't Exit; 1=Exit; CTRL-F1 to set to 1 and exit script.
Quit_var = 0
RS232_FileHandle:=RS232_Initialize(RS232_Settings)

;RS232 COM port receive loop
Loop
{
  ;0xFF in the line below sets the size of the read buffer.
  Read_Data := RS232_Read(RS232_FileHandle,"0xFF",RS232_Bytes_Received)
  ;MsgBox,RS232_FileHandle=%RS232_FileHandle% `n RS232_Bytes_Received=%RS232_Bytes_Received% `n Read_Data=%Read_Data% ; Variable that is set by RS232_Read()

  ;Process the data, if there is any.
  If (RS232_Bytes_Received > 0)
  {
    ;MsgBox, Read_Data=%Read_Data%

    ;Prevent interruption during execution of this loop.
    Critical, On

    ;If care is taken, you can comment out these WinActive lines for performance gains.
    IfWinNotActive, %Console_Title%, , WinActivate, %Console_Title%,
      WinWaitActive, %Console_Title%,   

    ;7/23/08 Modified this IF statement because RS232_Read() now returns data instead of ASCII
    ;Set to 0 if you want to see the hex data as received by the serial port.
    IF (1)
    {
      ;Begin Data to ASCII conversion
      ASCII =
      Read_Data_Num_Bytes := StrLen(Read_Data) / 2 ;RS232_Read() returns 2 characters for each byte

      Loop %Read_Data_Num_Bytes%
      {
        StringLeft, Byte, Read_Data, 2
        StringTrimLeft, Read_Data, Read_Data, 2
        Byte = 0x%Byte%
        Byte := Byte + 0 ;Convert to Decimal       
        ASCII_Chr := Chr(Byte)
        ASCII = %ASCII%%ASCII_Chr%
      }
      Send, ^{END}
      SendRaw, %ASCII%
      ;End Data to ASCII conversion
    }
    Else ;Send the data that was received by the RS232 COM port-ASCII format
      Send, ^{END}%Read_Data%

    Critical, Off
  }

  ;CTRL-F1 sets Quit_var=1
  if Quit_var = 1
    Break
}

RS232_Close(RS232_FileHandle)

MsgBox, AHK is now disconnected from %RS232_Port%
;ExitApp ;Exit Script
Return

;########################################################################
;###### Serial Port Transmit ############################################
;########################################################################

;###### Normal Key Presses ######
HotkeySub:
var := Asc(SubStr(A_ThisHotkey,0)) ;Get the key that was pressed and convert it to its ASCII code
If A_ThisHotkey = Space
  var=0x20
If A_ThisHotkey = BS
  var=0x08
If A_ThisHotkey = ENTER
    var=0x0D,0x0A ;New Line, Carriage Return ; 7/23/08 Changed 0x0A,0x0D to 0x0D,0x0A

RS232_Write(RS232_FileHandle,var) ;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

;###### Other Shift Key Presses ######
HotkeySub_Other_Shift:
var := SubStr(A_ThisHotkey,0) ; Get the key that was pressed.
;Convert it to the shift version
;  StringUpper won't work on the following.
If var = 1
  var = !
If var = 2
  var = @
If var = 3
  var = #
If var = 4
  var = $
If var = 5
  var = `%
If var = 6
  var = ^
If var = 7
  var = &
If var = 8
  var = *
If var = 9
  var = (
If var = 0
  var = )
If var = -
  var = _
If var = =
  var = +
If var = [
  var = {
If var = ]
  var = }
If var = \
  var = |
If var = `;
  var = :
If var = `'
  var = "
If var = `,
  var = <
If var = `.
  var = >
If var = `/
  var = ?

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% `nThe Script Will Now Exit.
    Exit
  }

  ;###### 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, There is a problem with Serial Port communication. `nFailed Dll CreateFile, RS232_FileHandle=%RS232_FileHandle% `nThe Script Will Now Exit.
    Exit
  }

  ;###### 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
  }
  
  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, %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
}

;########################################################################
;###### Exit Console Receive Loop #######################################
;########################################################################
^F1::
Quit_var = 1
return


bradchuck
  • Members
  • 4 posts
  • Last active: May 05 2008 06:45 PM
  • Joined: 01 Apr 2008
Thanks! This was usefill to me.

I am making it into an include file and using it to replace the Port.dll file. I also found that port.dll it did not work correctly. Your method does.

aobrien
  • Members
  • 77 posts
  • Last active: Mar 06 2014 04:42 AM
  • Joined: 14 Feb 2008
Hi bradchuck,

I'm glad that you are finding it useful.

FYI, I made a correction just now - the only changs are in the "Initialize COM Subroutine" section of the code. Just update your library with that section and you should be good to go.

aobrien

  • Guests
  • Last active:
  • Joined: --
Thank you for this very useful script. I marvel at the level of knowledge that it takes to create such a script.

Until now I have not been able to get any terminal program that I have evaluated to behave exactly as needed in my application. But, with the building blocks that you have provided, I have realized a proper configuration at last.

The result will be incorporated in a computer-based system that provides magnification services to individuals with low vision. The script controls the the parameters of the display through the display's serial port.

Again, my appreciation for this most helpful contribution.

Ron1

aobrien
  • Members
  • 77 posts
  • Last active: Mar 06 2014 04:42 AM
  • Joined: 14 Feb 2008
Hi Ron1,

Thanks for the compliment :D. I'm glad that you have found it useful and are putting it to such a noble cause.

Well, necesity is the mother of invention. It's funny that I was only about 26.4ms away from not developing this WinDLL version.

For my application, I am using AHK to communicate with my companies ICs. I first started using Port.dll and after several days of development I was finally at the point of where I should have been able to communicate with the ICs, however, I kept getting NACK (Not Acknowlege). I probed the Serial Port Tx line on the oscilloscope and noticed that there was only one byte of data being transmitted every 15.8ms - I was sending 8 bytes (8*15.8ms=126.4ms). It turned out that if our ICs didn't get all 8 bytes of data in 100ms then they timed-out and sent NACK.

It took me about another 2 weeks to develop the WinDLL version. But to be honest, I couldn't have done it without the help of Lexikos.

If I do say so myself, I've done some pretty amazing things with AHK. In one case, I wrote a test automation script (in one week), which would have taken a team of software developers several months with conventional methods, saving my companies ass in the process... and they don't even know it.

I particularly enjoy automating Windows based test equipment. I automated a (Big Name) pattern generator (took me about a month) they have a team of people working more than 6 months now, trying to accomplish the same thing and they come to me for help.

This is the first time that I have made any contribution to an Open Source community and I'm honored for the opportunity.

aobrien.

  • Guests
  • Last active:
  • Joined: --
nice

could this be used to send hex commands to a plasma? I gave up on a script ~ a year ago that used port.dll, maybe this is the answer?

aobrien
  • Members
  • 77 posts
  • Last active: Mar 06 2014 04:42 AM
  • Joined: 14 Feb 2008
Yes, I use it to send binary data at 115200 baud rate every day. The instructions (at the top of the script) are pretty clear, I hope. Please let me know if it needs clarification.


Note-to-all: I have recently found a bug on receiving 0x00 (NULL) - it is a pretty nasty bug. I haven't had the time to post the fix yet. Just beware and if you need the fix urgently, then let me know and I'll do my best to put it up sooner.

evandevon
  • Members
  • 88 posts
  • Last active: Oct 15 2014 01:03 PM
  • Joined: 22 Apr 2008
Please see

<!-- m -->http://www.autohotke... ... 820#204820<!-- m -->

for my post.
Can't seem to get your second script to work (first works fine) I think it may be because the first data value is null?

Thanks for these awesome scripts aobrien

Evs
Inventing problems that need solutions...

Open Communicator
MouseTrainer

Raccoon
  • Members
  • 178 posts
  • Last active: Oct 06 2014 05:58 PM
  • Joined: 02 Jan 2008
I slashed out over a hundred lines of code by removing all your hotkeys and using a Timer plus Input instead.

;########################################################################
;###### Serial Port Transmit ############################################
;########################################################################

GetUserInput:
{
	WinGetActiveTitle, Active_Title
	if (Active_Title != Console_Title)
		Return

	Input, var, IVL1T1, {Space}{Backspace}{Enter}{Escape}

	if ErrorLevel = Timeout
		Return
	if (var := Asc(var))
		Write_to_COM(var)
	if ErrorLevel = EndKey:Space
		Write_to_COM(0x20)
	if ErrorLevel = EndKey:Backspace
		Write_to_COM(0x08)
	if ErrorLevel = EndKey:Enter
		Write_to_COM(0x0D)
	if ErrorLevel = EndKey:Escape
	{
		Quit_var = 1
		SetTimer, GetUserInput, Off
	}
} Return

and put this line somewhere above the Serial Port Receive loop, and after Notepad has been initialized.

SetTimer, GetUserInput, 50

Note that for my purposes, I changed the value of Enter to 0x0D. Yours was 0x0A0D (which is pretty non-standard, I think you meant 0x0D0A, bug?). In any event, you should probably change this to a User Variable at the top section of the script.

COM_Data     = 8
COM_Stop     = 1
COM_Return   = 0x0D0A
Console_Path =

Thank you so very much for this code. I've been trying to port it from SerialTerm with no clue how to bottle the functions for Read/Write. Hope my contribution appears in your next update! :D

PS. A side effect of this code is the Read Loop will pause for 1 second intervals while capturing input, causing any output from the COM Port to stutter. This wont be a problem with most uses that are usually "turn-based" communication, but it could be an issue for asynchronous data transmissions such as "chat". I think the trade-off is worth it, personally. There may be a similarly better solution as-well.
Posted Image

Need help right away? Get live support on IRC.
Already have an IRC client installed? /join #ahk

Raccoon
  • Members
  • 178 posts
  • Last active: Oct 06 2014 05:58 PM
  • Joined: 02 Jan 2008
Oh, and you should also change this:

;Send the data that was received by the COM port-ASCII format
    Send, ^{END}%Read_Data%
to
;Send the data that was received by the COM port-ASCII format
    Send, ^{END}
    SendRaw, %Read_Data%

I noticed that any output that contained "^V" was pasting the contents of my clipboard in its place.
Posted Image

Need help right away? Get live support on IRC.
Already have an IRC client installed? /join #ahk

krisky68
  • Guests
  • Last active:
  • Joined: --
Hi, trying to use this to talk to a modem on COM17. I noticed that the code assumes that the com port number is single digit (ie. four chars for the handle):

;Creates the COM Port File Handle
  StringLeft, COM_Port, COM_Settings, 4

But even after I changed this, the script fails when attempting to create the file handle. Nothing else is connected to the port. Is there some limitation I don't know about?

Raccoon
  • Members
  • 178 posts
  • Last active: Oct 06 2014 05:58 PM
  • Joined: 02 Jan 2008
Assuming you changed StringLeft value to 5 making %COM_Port% == COM17, it should be right, but then again it may be bumping the predicted string length in some other portion of the script. I agree, this is poor coding.

Before going any further with AHK, I would suggest trying to connect using the command line tool called SerialTerm (you can find a link on these forums) or download the newest Putty and connect through it.

Once you've established a connection, having all your other variables in order (baud, parity, etc), then try again with AHK. Of course, make sure these are closed first (only one may connect at a time).
Posted Image

Need help right away? Get live support on IRC.
Already have an IRC client installed? /join #ahk

krisky68
  • Guests
  • Last active:
  • Joined: --
OK thanks, will try. Actually all I want for the time being is to send AT commands to the modem to dial phone numbers from the clipboard, I am already doing it via ahk and a hidden hyperterminal window, so I know the settings are fine. (and yes, I did make sure HT wasn't running when i tried the script :) ) But the HT approach becomes cumbersome if I ever want to read from the modem (eg. to get the ID of incoming calls).

aobrien
  • Members
  • 77 posts
  • Last active: Mar 06 2014 04:42 AM
  • Joined: 14 Feb 2008
Hi Raccoon,

Yes, I agree that it isn't the best coding in the world. I'm actually not happy with the whole flow of the script, but I wanted an easy way to demonstrate the script and I didn't know how to do it any other way.

Your way is much better. Thanks for the suggestions. I'll put them in soon.

Krisky, I'll also look into the single digit console problem, but when I test this script I actually test it in loop back mode -- 2 com ports on the same pc running 2 different copies of the same script. So I don't have any problem using different com port numbers.

I'll try to get the changes in by the end of the week.

aobrien