Jump to content

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

serial port stuff - seeking an authoritative answer


  • Please log in to reply
14 replies to this topic
perlsmith
  • Members
  • 89 posts
  • Last active: Jul 08 2018 09:46 PM
  • Joined: 05 Oct 2008
I'm going crazy trying to write to the COM port. Can someone answer this:

Is it possible somehow to run realterm from the cmd line on a one-shot basis

realterm -hidden -com28 -baud9600 -hex f3 0 f3 1

that is, have it run like a command line program - do its job and exit?

if not, can you suggest a way I could write such a program?

I thought it would be simple, but the outportb is no longer supported in Win32 and the CreateFile and WriteFile examples seem horribly complicated.

Note: a "completely AHK" solution is fine as long as the code is not too long and doesn't have any external dependencies (DLLs, etc).

Thanks!

aaffe
  • Members
  • 1045 posts
  • Last active: Jan 16 2014 01:32 PM
  • Joined: 17 May 2007
Try:
Run,%COMSPEC% /c realterm -hidden -com28 -baud9600 -hex f3 0 f3 1


engunneer
  • Moderators
  • 9162 posts
  • Last active: Sep 12 2014 10:36 PM
  • Joined: 30 Aug 2005
I simplified and reworked the CreateFile example, and have made it into a Standard Library compatible function set:

My Documents\Autohotkey\Lib\Serial.ahk
;########################################################################
;###### Initialize COM Subroutine #######################################
;########################################################################
Serial_Initialize(COM_Settings)
{
  ;Global COM_FileHandle      ;uncomment this if there is a problem

  ;###### Build COM DCB ######
  ;Creates the structure that contains the COM Port number, baud rate,...
  VarSetCapacity(DCB, 28)
  BCD_Result := DllCall("BuildCommDCB"
       ,"str" , COM_Settings ;lpDef
       ,"UInt", &DCB)        ;lpDCB
  If (BCD_Result <> 1)
  {
    error := DllCall("GetLastError")
    MsgBox, There is a problem with Serial Port communication. `nFailed Dll BuildCommDCB, BCD_Result=%BCD_Result% `nLasterror=%error%`nThe Script Will Now Exit.
    ExitApp
  }

  ;###### Extract/Format the COM Port Number ######
  StringSplit, COM_Port_Temp, COM_Settings, `:
  COM_Port_Temp1_Len := StrLen(COM_Port_Temp1)  ;For COM Ports > 9 \\.\ needs to prepended to the COM Port name.
  If (COM_Port_Temp1_Len > 4)                   ;So the valid names are
    COM_Port = \\.\%COM_Port_Temp1%             ; ... COM8  COM9   \\.\COM10  \\.\COM11  \\.\COM12 and so on...
  Else                                          ;
    COM_Port = %COM_Port_Temp1%
  ;MsgBox, COM_Port=%COM_Port%

  ;###### Create COM File ######
  ;Creates the COM Port File Handle
  ;StringLeft, COM_Port, COM_Settings, 4  ; 7/23/08 This line is replaced by the "Extract/Format the COM Port Number" section above.
  COM_FileHandle := DllCall("CreateFile"
       ,"Str" , COM_Port     ;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 (COM_FileHandle < 1)
  {
    error := DllCall("GetLastError")
    MsgBox, There is a problem with Serial Port communication. `nFailed Dll CreateFile, COM_FileHandle=%COM_FileHandle% `nLasterror=%error%`nThe Script Will Now Exit.
    ExitApp
  }

  ;###### Set COM State ######
  ;Sets the COM Port number, baud rate,...
  SCS_Result := DllCall("SetCommState"
       ,"UInt", COM_FileHandle ;File Handle
       ,"UInt", &DCB)          ;Pointer to DCB structure
  If (SCS_Result <> 1)
  {
    error := DllCall("GetLastError")
    MsgBox, There is a problem with Serial Port communication. `nFailed Dll SetCommState, SCS_Result=%SCS_Result% `nLasterror=%error%`nThe Script Will Now Exit.
    Serial_Close(COM_FileHandle)
    ExitApp
  }

  ;###### 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 COM Timeouts ######
  SCT_result := DllCall("SetCommTimeouts"
     ,"UInt", COM_FileHandle ;File Handle
     ,"UInt", &Data)         ;Pointer to the data structure
  If (SCT_result <> 1)
  {
    error := DllCall("GetLastError")
    MsgBox, There is a problem with Serial Port communication. `nFailed Dll SetCommState, SCT_result=%SCT_result% `nLasterror=%error%`nThe Script Will Now Exit.
    Serial_Close(COM_FileHandle)
    ExitApp
  }

  Return COM_FileHandle
}

;########################################################################
;###### Close COM Subroutine ############################################
;########################################################################
Serial_Close(COM_FileHandle)
{
  ;###### Close the COM File ######
  CH_result := DllCall("CloseHandle", "UInt", COM_FileHandle)
  If (CH_result <> 1)
    MsgBox, Failed Dll CloseHandle CH_result=%CH_result%

  Return
}

;########################################################################
;###### Write to COM Subroutines ########################################
;########################################################################
Serial_Write(COM_FileHandle, Message)
{
  ;Global COM_FileHandle

  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 COM Port ######
  WF_Result := DllCall("WriteFile"
       ,"UInt" , COM_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 COM Port, result=%WF_Result% `nData Length=%Data_Length% `nBytes_Sent=%Bytes_Sent%
    
    Return Bytes_Sent
}

;########################################################################
;###### Read from COM Subroutines #######################################
;########################################################################
Serial_Read(COM_FileHandle, Num_Bytes, byref Bytes_Received = "")
{
  ;Global COM_FileHandle
  ;Global COM_Port
  ;Global 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 COM Port ######
  ;msgbox, COM_FileHandle=%COM_FileHandle% `nNum_Bytes=%Num_Bytes%
  Read_Result := DllCall("ReadFile"
       ,"UInt" , COM_FileHandle   ; hFile
       ,"Str"  , Data             ; lpBuffer
       ,"Int"  , Num_Bytes        ; nNumberOfBytesToRead
       ,"UInt*", Bytes_Received   ; lpNumberOfBytesReceived
       ,"Int"  , 0)               ; lpOverlapped
  ;MsgBox, Read_Result=%Read_Result% `nBR=%Bytes_Received% ,`nData=%Data%
  If (Read_Result <> 1)
  {
    MsgBox, There is a problem with Serial Port communication. `nFailed Dll ReadFile on COM Port, result=%Read_Result% - The Script Will Now Exit.
    Serial_Close(COM_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 %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_Temp
  }
  ;MsgBox, Read_Result=%Read_Result% `nBR=%Bytes_Received% ,`nData_HEX=%Data_HEX%

  SetFormat, Integer, DEC
  Data := Data_HEX

  Return Data

}


Example.ahk
COM_Port     = COM1
COM_Baud     = 9600
COM_Parity   = N
COM_Data     = 8
COM_Stop     = 1

COM_Settings = %COM_Port%:baud=%COM_Baud% parity=%COM_Parity% data=%COM_Data% stop=%COM_Stop% dtr=Off

COM_Handle := Serial_Initialize(COM_Settings)


Read_Data := Read_from_COM(COM_Handle, "0xFF")

Read_Data := Read_from_COM(COM_Handle, "0xFF", Bytes_Received)

Bytes_Sent := Serial_Write(COM_FileHandle, Message)

Serial_Close(COM_FileHandle)

It is, however untested. I should have translated it OK, though.

perlsmith
  • Members
  • 89 posts
  • Last active: Jul 08 2018 09:46 PM
  • Joined: 05 Oct 2008
Thanks engunneer. I will test and let you know.

Aaffe, I'm asking for a program I can use like that. Do you know of one? If it existed, it's trivial to call, but I can't find something I can call so conveniently.

perlsmith
  • Members
  • 89 posts
  • Last active: Jul 08 2018 09:46 PM
  • Joined: 05 Oct 2008
Here's the GUI I'm using to test this :

; Generated using SmartGUI Creator 4.0
Gui, Add, Edit, x26 y20 w70 h40 vDATA, 
Gui, Add, Button, x106 y20 w60 h40 , Send
Gui, Add, Text, x26 y70 w30 h20 , COM
Gui, Add, Edit, x66 y70 w50 h30 vCOM , 1
Gui, Add, Button, x26 y110 w140 h40 , Quit
Gui, Add, Button, x116 y70 w50 h30 gSetCOM, Set!

Gui, Show, x131 y91 h171 w190, New GUI Window
Return

; Depends on : My Documents\Autohotkey\Lib

SetCOM:
Gui, Submit, NoHide
COM_Port     = COM%COM%
COM_Baud     = 38400
COM_Parity   = N
COM_Data     = 8
COM_Stop     = 1

COM_Settings = %COM_Port%:baud=%COM_Baud% parity=%COM_Parity% data=%COM_Data% stop=%COM_Stop% dtr=Off

COM_Handle := Serial_Initialize(COM_Settings)
Return

ButtonSend:
Gui, Submit, NoHide
Bytes_Sent := Serial_Write(COM_Handle, DATA)
Return

ButtonQuit:
Serial_Close(COM_Handle)
GuiClose:
ExitApp

Since I'm posting, I have issues. These are my observations :

(I am listing all of them so far even though some don't interfere with my goals, just in case it helps us resolve things faster )

If I set the wrong port (that is, one that doesn't exist) I get the following message box :

There is a prob... ial communication.
Failed Dll BuildCommDCB, BCD_Result=0
Lasterror=87
the Script will now Exit
(Minor)

In my case, the device shows up on COM28 (It is an Arduino Duemilanove running a simple program to light up the pin13 LED when it receives an 'E' on the serial port at 38400 baud, n, 1s, 8d. 'F' will turn off the LED. I have verified the operation with RealTerm). If I set 28 for the COM port in my GUI and then send data, I can see activity on the RX line (there is another LED to monitor that) but the target LED doesn't get lit.
(Major)

If I just launch the script and hit Quit, I get the following message :

Failed Dll CloseHandle CH_result=0
(Minor)

How can I get rid of the scrollbars in my Edit fields :(??

perlsmith
  • Members
  • 89 posts
  • Last active: Jul 08 2018 09:46 PM
  • Joined: 05 Oct 2008
Ok, using trenton_xavier's code, I get my stuff working, but not entirely satisfactorily (I'm a perfectionist). Here's what's going on :

Arduino board has an LED that is lit when it receives 'E' (0x45).
Board turns off the LED when it receives 'F'.
But, to give the user of the GUI feedback - as to whether is action was successful, when the board receives 'E', it sends back 'F' and, for 'F', it sends back 'G'.

Posted Image

Writing to the COM port works fine. Reading does not. I write, and then read to check - and I find I need a large delay between the writing and reading for the function to work without glitches. If the delay is 20 ms (which should be plenty) there are glitches - that is, sometimes, the read function does not read correctly. I find a 30 ms delay works okay. And that works for me, but I'd still like to know what's going on, if anyone cares to throw some light. Do we have a DLL guru out here who can volunteer to give me something that can flush the read buffer :) ?

Unfortunately, aobrien's read functions wasn't 'lite' so I changed it to a function that just reads and returns one byte.

; Generated using SmartGUI Creator 4.0

Gui, font, s15
Gui, Add, CheckBox, x16 y20 w110 h50 vLED gLEDSet, LED ON?
Gui, font	; restore default
Gui, Add, Edit, x56 y80 w60 h30 vCOM, 
Gui, Add, Text, x16 y80 w40 h30 +Center, COM
Gui, Add, CheckBox, x16 y110 w100 h30 vSet gSet, Set!
Gui, Add, Button, x16 y150 w100 h30 , Quit
Gui, Show, x131 y91 h195 w134, Arduino LED Test
Return

LEDSet:
 Gui, Submit, NoHide
if( Set ){
 if( LED ){
  ; user checked the box, now, turn on the LED
  Write_to_COM(69)	;   setup specific 'E'
  Sleep 30
    Reception := Read_COM_Byte(1)
    if( 70 != Reception ){
      GuiControl, ,LED, 0
    }
 } else {
  ; user unchecked,...
  Write_to_COM(70)	;  setup specific 'F'
  Sleep 30  ; give the MCU time to respond
  Read_COM_Byte(1)
 }
} else {
  newVal := !(LED)
  GuiControl, ,LED, %newVal%
}
Return


Set:
Gui, Submit, NoHide
if( Set ){
 if( COM == "" ){
    GuiControl, , Set, 0
    Return
 }
 COM_Port	  = COM%COM%
 COM_Baud	  = 38400
 COM_Parity   = N
 COM_Data	  = 8
 COM_Stop	  = 1

 COM_Settings = baud=%COM_Baud% parity=%COM_Parity% data=%COM_Data% stop=%COM_Stop% dtr=Off
 Initialize_COM(COM_Settings)
} else {
 Close_COM(COM_FileHandle) 
}
Return


ButtonQuit:
GuiClose:
ExitApp


;########################################################################
;###### Subroutines (You don't really need to look below this line) #####
;########################################################################

;########################################################################
;  _      _ _      ___ ___  __  __ 
; (_)_ _ (_) |_   / __/ _ \|  \/  |
; | | ' \| |  _| | (_| (_) | |\/| |
; |_|_||_|_|\__|  \___\___/|_|  |_|

Initialize_COM(COM_Settings)
{
  Global COM_FileHandle
  Global COM_Port	; by trenton_xavier

  ;###### Build COM DCB ######
  ;Creates the structure that contains the COM Port number, baud rate,...
  VarSetCapacity(DCB, 28)
  BCD_Result := DllCall("BuildCommDCB"
       ,"str" , COM_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
  }

  ;###### Format the COM Port Number ######
  ; following trenton_xavier's post
  if (StrLen(COM_PORT) > 4)
   COM_Port_MOD := "\\.\" . COM_Port
  else
   COM_Port_MOD := COM_Port  
  ;MsgBox, COM_Port=%COM_Port_MOD% 

  ;###### Create COM File ######
  ;Creates the COM Port File Handle
  ;StringLeft, COM_Port, COM_Settings, 4  
  ; 7/23/08 This line is replaced by the "Extract/Format the COM Port Number" section above.
  COM_FileHandle := DllCall("CreateFile"
       ,"Str" , COM_Port_MOD     ;File Name      trenton_xavier mod   
       ,"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 (COM_FileHandle < 1)
  {
    MsgBox, There is a problem with Serial Port communication
    . `nFailed Dll CreateFile, COM_FileHandle=%COM_FileHandle% `nThe Script Will Now Exit.
    Exit
  }

  ;###### Set COM State ######
  ;Sets the COM Port number, baud rate,...
  SCS_Result := DllCall("SetCommState"
       ,"UInt", COM_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.
    Close_COM(COM_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 COM Timeouts ######
  SCT_result := DllCall("SetCommTimeouts"
     ,"UInt", COM_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.
    Close_COM(COM_FileHandle)
    Exit
  }

  Return %COM_FileHandle%
}

;   ___ _                ___ ___  __  __ 
;  / __| |___ ___ ___   / __/ _ \|  \/  |
; | (__| / _ (_-</ -_) | (_| (_) | |\/| |
;  \___|_\___/__/\___|  \___\___/|_|  |_|

Close_COM(COM_FileHandle)
{
  ;###### Close the COM File ######
  CH_result := DllCall("CloseHandle", "UInt", COM_FileHandle)
  If (CH_result <> 1)
    MsgBox, Failed Dll CloseHandle CH_result=%CH_result%

  Return
}

; __      __   _ _          ___ ___  __  __ 
; \ \    / / _(_) |_ ___   / __/ _ \|  \/  |
;  \ \/\/ / '_| |  _/ -_) | (_| (_) | |\/| |
;   \_/\_/|_| |_|\__\___|  \___\___/|_|  |_|

Write_to_COM(Message)
{
  Global COM_FileHandle
  Global COM_Port

  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 COM Port ######
  WF_Result := DllCall("WriteFile"
       ,"UInt" , COM_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 %COM_Port%
    , result=%WF_Result% `nData Length=%Data_Length% `nBytes_Sent=%Bytes_Sent%
}

;  ___             _    ___ ___  __  __   ___      _       
; | _ \___ __ _ __| |  / __/ _ \|  \/  | | _ )_  _| |_ ___ 
; |   / -_) _` / _` | | (_| (_) | |\/| | | _ \ || |  _/ -_)
; |_|_\___\__,_\__,_|  \___\___/|_|  |_| |___/\_, |\__\___|
;                                             |__/    

Read_COM_Byte(Num_Bytes)
{
  Global COM_FileHandle
  Global COM_Port
  Global 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 COM Port ######
  ;msgbox, COM_FileHandle=%COM_FileHandle% `nNum_Bytes=%Num_Bytes%
  Read_Result := DllCall("ReadFile"
       ,"UInt" , COM_FileHandle   ; hFile
       ,"Str"  , Data             ; lpBuffer
       ,"Int"  , Num_Bytes        ; nNumberOfBytesToRead
       ,"UInt*", Bytes_Received   ; lpNumberOfBytesReceived
       ,"Int"  , 0)               ; lpOverlapped
; MsgBox, Read_Result=%Read_Result% `nBR=%Bytes_Received% ,`nData=%Data%
  If (Read_Result <> 1)
  {
    MsgBox, There is a problem with Serial Port communication
    . `nFailed Dll ReadFile on %COM_Port%, result=%Read_Result% - The Script Will Now Exit.
    Close_COM(COM_FileHandle)
    Exit
  }


  Return NumGet(Data,0,"UChar")

}


engunneer
  • Moderators
  • 9162 posts
  • Last active: Sep 12 2014 10:36 PM
  • Joined: 30 Aug 2005
I'll tackle the easiest one first.

ButtonQuit:
If (COM_Handle)
  Serial_Close(COM_Handle)
GuiClose:
ExitApp

In my serial app, I flush the read buffer by reading 0xFF bytes into a junk variable.

The Serial library I posted was untested, but should have been close. Does it remotely work right? I'd like to have it as a Lib too, so maybe we should fix any issues it has, if you are interested.

perlsmith
  • Members
  • 89 posts
  • Last active: Jul 08 2018 09:46 PM
  • Joined: 05 Oct 2008
Sure, I can help out test it.

I'll get back later; what I remember is there was activity on the serial port when I used your code, but the end result wasn't there. If I remember right, I touched the code a bit. Maybe it was okay to start with.

Later.

perlsmith
  • Members
  • 89 posts
  • Last active: Jul 08 2018 09:46 PM
  • Joined: 05 Oct 2008
I must have had a typo in my old code. I just copied the lib code again from this thread and wrote this for the GUI (I must send 'G' = 0x47 to the MCU to toggle the LED and the MCU will respond with sent+1 = 'H' or 0x48)

writing works fine

reading : I get back 48 in my result - and that's not 0x48, just 48.

Using aobrien's code, I could do

if( 72 != Reception)

but here, I have to do

if( 48 != Reception)

it must be something trivial. I just haven't read the Serial_Read very carefully.

; Generated using SmartGUI Creator 4.0

Gui, font, s15
Gui, Add, Button, x16 y20 w110 h50 +Disabled vLED gLEDToggle, Toggle
Gui, font	; restore default
Gui, Add, Edit, x56 y80 w60 h30 vCOM, 4
Gui, Add, Text, x16 y80 w40 h30 +Center, COM
Gui, Add, CheckBox, x16 y110 w100 h30 vSet gSet, Set!
Gui, Add, Button, x16 y150 w100 h30 , Quit
Gui, Show, x131 y91 h195 w134, Arduino LED Test
Return

LEDToggle:
 Gui, Submit, NoHide
  Write_to_COM(71)	;   setup specific 'G'
  Sleep 4
    Reception := Read_COM_Byte(1)
    if( 72 != Reception ){  ; protocol is that Arduino will inc and copy
      MsgBox, Bad ACK %Reception%
    }

Return


Set:
Gui, Submit, NoHide
if( Set ){
 if( COM == "" ){
    GuiControl, , Set, 0
    Return
 }
 COM_Port	  = COM%COM%
 COM_Baud	  = 38400
 COM_Parity   = N
 COM_Data	  = 8
 COM_Stop	  = 1

 COM_Settings = baud=%COM_Baud% parity=%COM_Parity% data=%COM_Data% stop=%COM_Stop% dtr=Off
 Initialize_COM(COM_Settings)
  GuiControl, Enable, LED
} else {
 GuiControl, Disable, LED
 Close_COM(COM_FileHandle) 
}
Return


ButtonQuit:
GuiClose:
ExitApp


;########################################################################
;###### Subroutines (You don't really need to look below this line) #####
;########################################################################

;########################################################################
;  _      _ _      ___ ___  __  __ 
; (_)_ _ (_) |_   / __/ _ \|  \/  |
; | | ' \| |  _| | (_| (_) | |\/| |
; |_|_||_|_|\__|  \___\___/|_|  |_|

Initialize_COM(COM_Settings)
{
  Global COM_FileHandle
  Global COM_Port	; by trenton_xavier

  ;###### Build COM DCB ######
  ;Creates the structure that contains the COM Port number, baud rate,...
  VarSetCapacity(DCB, 28)
  BCD_Result := DllCall("BuildCommDCB"
       ,"str" , COM_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
  }

  ;###### Format the COM Port Number ######
  ; following trenton_xavier's post
  if (StrLen(COM_PORT) > 4)
   COM_Port_MOD := "\\.\" . COM_Port
  else
   COM_Port_MOD := COM_Port  
  ;MsgBox, COM_Port=%COM_Port_MOD% 

  ;###### Create COM File ######
  ;Creates the COM Port File Handle
  ;StringLeft, COM_Port, COM_Settings, 4  
  ; 7/23/08 This line is replaced by the "Extract/Format the COM Port Number" section above.
  COM_FileHandle := DllCall("CreateFile"
       ,"Str" , COM_Port_MOD     ;File Name      trenton_xavier mod   
       ,"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 (COM_FileHandle < 1)
  {
    MsgBox, There is a problem with Serial Port communication
    . `nFailed Dll CreateFile, COM_FileHandle=%COM_FileHandle% `nThe Script Will Now Exit.
    Exit
  }

  ;###### Set COM State ######
  ;Sets the COM Port number, baud rate,...
  SCS_Result := DllCall("SetCommState"
       ,"UInt", COM_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.
    Close_COM(COM_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 COM Timeouts ######
  SCT_result := DllCall("SetCommTimeouts"
     ,"UInt", COM_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.
    Close_COM(COM_FileHandle)
    Exit
  }

  Return %COM_FileHandle%
}

;   ___ _                ___ ___  __  __ 
;  / __| |___ ___ ___   / __/ _ \|  \/  |
; | (__| / _ (_-</ -_) | (_| (_) | |\/| |
;  \___|_\___/__/\___|  \___\___/|_|  |_|

Close_COM(COM_FileHandle)
{
  ;###### Close the COM File ######
  CH_result := DllCall("CloseHandle", "UInt", COM_FileHandle)
  If (CH_result <> 1)
    MsgBox, Failed Dll CloseHandle CH_result=%CH_result%

  Return
}

; __      __   _ _          ___ ___  __  __ 
; \ \    / / _(_) |_ ___   / __/ _ \|  \/  |
;  \ \/\/ / '_| |  _/ -_) | (_| (_) | |\/| |
;   \_/\_/|_| |_|\__\___|  \___\___/|_|  |_|

Write_to_COM(Message)
{
  Global COM_FileHandle
  Global COM_Port

  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 COM Port ######
  WF_Result := DllCall("WriteFile"
       ,"UInt" , COM_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 %COM_Port%
    , result=%WF_Result% `nData Length=%Data_Length% `nBytes_Sent=%Bytes_Sent%
}

;  ___             _    ___ ___  __  __   ___      _       
; | _ \___ __ _ __| |  / __/ _ \|  \/  | | _ )_  _| |_ ___ 
; |   / -_) _` / _` | | (_| (_) | |\/| | | _ \ || |  _/ -_)
; |_|_\___\__,_\__,_|  \___\___/|_|  |_| |___/\_, |\__\___|
;                                             |__/    

Read_COM_Byte(Num_Bytes)
{
  Global COM_FileHandle
  Global COM_Port
  Global 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 COM Port ######
  ;msgbox, COM_FileHandle=%COM_FileHandle% `nNum_Bytes=%Num_Bytes%
  Read_Result := DllCall("ReadFile"
       ,"UInt" , COM_FileHandle   ; hFile
       ,"Str"  , Data             ; lpBuffer
       ,"Int"  , Num_Bytes        ; nNumberOfBytesToRead
       ,"UInt*", Bytes_Received   ; lpNumberOfBytesReceived
       ,"Int"  , 0)               ; lpOverlapped
; MsgBox, Read_Result=%Read_Result% `nBR=%Bytes_Received% ,`nData=%Data%
  If (Read_Result <> 1)
  {
    MsgBox, There is a problem with Serial Port communication
    . `nFailed Dll ReadFile on %COM_Port%, result=%Read_Result% - The Script Will Now Exit.
    Close_COM(COM_FileHandle)
    Exit
  }

  SetFormat, Integer, DEC

  Return NumGet(Data,0,"UChar")

}


aaffe
  • Members
  • 1045 posts
  • Last active: Jan 16 2014 01:32 PM
  • Joined: 17 May 2007
Hy,
do you really have a variable called 78? If not, and you want to verify with this number, write instead:
if( "72" != Reception)

but here, I have to do

if( "48" != Reception)

engunneer
  • Moderators
  • 9162 posts
  • Last active: Sep 12 2014 10:36 PM
  • Joined: 30 Aug 2005
yes, that version of the Read code sends back a string representing the hex values. I suppose it would be nice to have an option of how to return data. The device I was testing with last time sent back binary zeros, so I had to get the text representation back.

If you don't want to modify the library, I can take a shot at it. I don't have any devices to test with, though.

perlsmith
  • Members
  • 89 posts
  • Last active: Jul 08 2018 09:46 PM
  • Joined: 05 Oct 2008
The hardware interface works okay. 48 is Hex for 72. We just need to fix the code to return 72 or 0x48 (not '48'). I think it's a simple thing.

Having the Comm inbuilt into AHK is probably worthwhile. On the other hand, the power of AHK is in scripting to command other programs even if they need GUI interaction.

I can say people at work have been impressed by the speed at which I could come up with a usable GUI that talked to real hardware. No need for a serious installation of any kind of package or framework. No dependencies to worry about (other than this case, where I was lucky someone had the code with the right DLLcalls).

I think perl started out as something simple and evolved into something very powerful as it was enhanced with more useful datatypes. I could be wrong, but I feel a serious programming task involving data manipulation in AHK is more difficult than it ought to be, because of our limited datatypes.

engunneer
  • Moderators
  • 9162 posts
  • Last active: Sep 12 2014 10:36 PM
  • Joined: 30 Aug 2005
Like I said, it's known that that Read function returns a string representation of the hex digits of your response, without a preceding 0x (since the response can be any number of bytes)


Here's a new version of Serial_Read which has a new 'raw' parameter
;########################################################################
;###### Read from COM Subroutines #######################################
;########################################################################
Serial_Read(COM_FileHandle, Num_Bytes, mode = "",byref Bytes_Received = "")
{
  ;Global COM_FileHandle
  ;Global COM_Port
  ;Global 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 COM Port ######
  ;msgbox, COM_FileHandle=%COM_FileHandle% `nNum_Bytes=%Num_Bytes%
  Read_Result := DllCall("ReadFile"
       ,"UInt" , COM_FileHandle   ; hFile
       ,"Str"  , Data             ; lpBuffer
       ,"Int"  , Num_Bytes        ; nNumberOfBytesToRead
       ,"UInt*", Bytes_Received   ; lpNumberOfBytesReceived
       ,"Int"  , 0)               ; lpOverlapped
  ;MsgBox, Read_Result=%Read_Result% `nBR=%Bytes_Received% ,`nData=%Data%
  If (Read_Result <> 1)
  {
    MsgBox, There is a problem with Serial Port communication. `nFailed Dll ReadFile on COM Port, result=%Read_Result% - The Script Will Now Exit.
    Serial_Close(COM_FileHandle)
    Exit
  }

  ;if you know the data coming back will not contain any binary zeros (0x00), you can request the 'raw' response
  If (mode = "raw")
    Return Data

  ;###### 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 %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_Temp
  }
  ;MsgBox, Read_Result=%Read_Result% `nBR=%Bytes_Received% ,`nData_HEX=%Data_HEX%

  SetFormat, Integer, DEC
  Data := Data_HEX

  Return Data

}

completely untested, but it should give back the raw data instead of converting it to a string.

ahklerner
  • Members
  • 1386 posts
  • Last active: Oct 08 2014 10:29 AM
  • Joined: 26 Jun 2006
hey engunneer, i am trying to use your lib to talk to a serial device that just returns whatever data sent to it. every time it just give 0x55....any ideas?
Posted Image
ʞɔпɟ əɥʇ ʇɐɥʍ

ahklerner
  • Members
  • 1386 posts
  • Last active: Oct 08 2014 10:29 AM
  • Joined: 26 Jun 2006
i hfigured it out...had to assign com port lower than 9 and add a sleep after sending data before read
Posted Image
ʞɔпɟ əɥʇ ʇɐɥʍ