Serial Communication locking up.

Just starting out? Need help? Post your questions and find answers here.
User avatar
matalog
Enthusiast
Enthusiast
Posts: 304
Joined: Tue Sep 05, 2017 10:07 am

Serial Communication locking up.

Post by matalog »

I have been testing out the serial port feature in PB today, and i'm not having the greatest success.

This program:

Code: Select all

  Port$ = "COM4"
If OpenSerialPort(1, Port$, 9600, #PB_SerialPort_NoParity, 8, 1, #PB_SerialPort_RtsCtsHandshake, 128, 128)
For x=1 To 10
    WriteSerialPortString(1,"IS THIS WORKING coRRecTlY?"+Str(x)+Chr(13)+Chr(10),#PB_Ascii)
Next
Else
  MessageRequester("Error", "Can't open the serial port: "+Port$)
EndIf
CloseFile(0)
can get into a state in which the Task Manager cannot even End it, and the only way to close it, is to restart the computer.

If I run that same program without the For Next loop, it does output the text correctly. Why might it be having a problem with the loop?
TassyJim
Enthusiast
Enthusiast
Posts: 186
Joined: Sun Jun 16, 2013 6:27 am
Location: Tasmania (Australia)

Re: Serial Communication locking up.

Post by TassyJim »

I expect that you are sending to the device too quickly for it to process.
Try putting a delay in the loop
If that works, gradually decrease the delay until it fails.
morosh
Enthusiast
Enthusiast
Posts: 329
Joined: Wed Aug 03, 2011 4:52 am
Location: Beirut, Lebanon

Re: Serial Communication locking up.

Post by morosh »

Working here, except that WriteSerialPortString take 2-3 sec each time, so after 30sec it exits normally. (I've added debug x).
I've used a virtual serial port.
PureBasic: Surprisingly simple, diabolically powerful
User avatar
matalog
Enthusiast
Enthusiast
Posts: 304
Joined: Tue Sep 05, 2017 10:07 am

Re: Serial Communication locking up.

Post by matalog »

Should the data not just be stored or not sent until the receiving device gives the send signal?
infratec
Always Here
Always Here
Posts: 7604
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Serial Communication locking up.

Post by infratec »

Check the return value of WriteSerialPortString()
User avatar
mk-soft
Always Here
Always Here
Posts: 6240
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Serial Communication locking up.

Post by mk-soft »

It's a bit older ...

COM port communication should run in a thread in the background and be transmitted to the GUI with PostEvent.

For testing a complete code. The settings are made in InitComport()

Code: Select all

;-TOP

; Comment : Comport Manager Over Thread and Callback
; Author  : mk-soft
; Version : v0.07.1
; Created : 26.01.2018
; Updated : 03.09.2022

; *****************************************************************************

CompilerIf #PB_Compiler_Thread = 0
  CompilerError "Use Option Threadsafe!"
CompilerEndIf

Prototype ProtoReceiveCB(Text.s)
Prototype ProtoStatusCB(Status, *ComData)

Enumeration
  #ComThread_Stopped
  #ComThread_Startup
  #ComThread_Running
EndEnumeration

Enumeration
  #ComStatus_Nothing
  #ComStatus_OpenPort
  #ComStatus_ClosePort
  #ComStatus_ErrorOpenPort
  #ComStatus_ErrorSend
  #ComStatus_ErrorReceive
  #ComStatus_ErrorDataSize
EndEnumeration

Structure udtComData
  ; Header
  ThreadID.i
  Exit.i
  Status.i
  ; Port Data
  ComID.i
  Port.s
  Baud.i
  Parity.i
  DataBit.i
  StopBit.i
  Handshake.i
  BufferSize.i
  ; End Of Text
  EndOfText.s
  ; Send Data
  SendSignal.i
  SendCount.i
  SendText.s
  SendError.i
  ; Receive data
  ReceiveCount.i
  ReceiveText.s
  ReceiveError.i
  ; Callback
  *StatusCB.ProtoStatusCB
  *ReceiveCB.ProtoReceiveCB
EndStructure

Procedure thComport(*ComData.udtComData)
  
  Protected *Send, *Receive, SendText.s, SendLen, ReceiveText.s, ReceiveLen, Pos
  
  With *ComData
    ; Startup
    \Status = #ComThread_Startup
    \SendCount = 0
    \ReceiveCount = 0
    \ComID = OpenSerialPort(#PB_Any, \Port, \Baud, \Parity, \DataBit, \StopBit, \Handshake, \BufferSize, \BufferSize)
    If \ComID
      \Status = #ComThread_Running
    Else
      If \StatusCB
        \StatusCB(#ComStatus_ErrorOpenPort, *ComData)
      EndIf
      \Status = #ComThread_Stopped
      ProcedureReturn 0
    EndIf
    If \StatusCB
      \StatusCB(#ComStatus_OpenPort, *ComData)
    EndIf
    *Send = AllocateMemory(\BufferSize)
    *Receive = AllocateMemory(\BufferSize)
    ; Loop
    Repeat
      If \SendSignal
        SendText = \SendText + \EndOfText
        SendLen = StringByteLength(SendText, #PB_Ascii)
        If SendLen <= \BufferSize
          PokeS(*Send, SendText, SendLen, #PB_Ascii)
          If WriteSerialPortData(\ComID, *Send, SendLen) = 0
            \SendError = SerialPortError(\ComID)
            If \StatusCB
              \StatusCB(#ComStatus_ErrorSend, *ComData)
            EndIf
          Else
            \SendError = 0
            \SendCount + 1
          EndIf
        Else
          If \StatusCB
            \StatusCB(#ComStatus_ErrorDataSize, *ComData)
          EndIf
        EndIf
        \SendSignal = #False
      EndIf
      ReceiveLen = AvailableSerialPortInput(\ComID)
      If ReceiveLen
        ReceiveLen = ReadSerialPortData(\ComID, *Receive, ReceiveLen)
        If ReceiveLen = 0
          \ReceiveError = SerialPortError(\ComID)
          If \StatusCB
            \StatusCB(#ComStatus_ErrorReceive, *ComData)
          EndIf
        Else
          \ReceiveError = 0
        EndIf
        ReceiveText + PeekS(*Receive, ReceiveLen, #PB_Ascii)
        Repeat
          pos = FindString(ReceiveText, \EndOfText, 1, #PB_String_NoCase)
          If pos
            \ReceiveText = Left(ReceiveText, pos - 1)
            ReceiveText = Mid(ReceiveText, pos + Len(\EndOfText))
            \ReceiveCount + 1
            If \ReceiveCB
              \ReceiveCB(\ReceiveText)
            EndIf
          EndIf
        Until pos = 0
      EndIf
      Delay(10)
    Until \Exit
    ; Shutdown
    CloseSerialPort(\ComID)
    If \StatusCB
      \StatusCB(#ComStatus_ClosePort, *ComData)
    EndIf
    FreeMemory(*Send)
    FreeMemory(*Receive)
    \Status = #ComThread_Stopped
    \ComID = 0
    \Exit = 0
    ProcedureReturn 1
  EndWith
  
EndProcedure

; ----

Procedure.s SerialPortErrorText(ErrorCode)
  Protected r1.s
  
  Select ErrorCode
    Case #PB_SerialPort_RxOver      : r1 = "An input buffer overflow has occurred."
    Case #PB_SerialPort_OverRun     : r1 = "A character-buffer overrun has occurred."
    Case #PB_SerialPort_RxParity    : r1 = "The hardware detected a parity error."
    Case #PB_SerialPort_Frame       : r1 = "The hardware detected a framing error."
    Case #PB_SerialPort_Break       : r1 = "The hardware detected a break condition."
    Case #PB_SerialPort_TxFull      : r1 = "The application tried to transmit a character but the output buffer was full."
    Case #PB_SerialPort_IOE         : r1 = "An I/O error occurred during communications with the device."
    Case #PB_SerialPort_WaitingCTS  : r1 = "Specifies whether transmission is waiting for the CTS (clear-To-send) signal to be sent."
    Case #PB_SerialPort_WaitingDSR  : r1 = "Specifies whether transmission is waiting for the DSR (Data-set-ready) signal to be sent."
    Case #PB_SerialPort_WaitingRLSD : r1 = "Specifies whether transmission is waiting for the RLSD (receive-line-signal-detect) signal to be sent."
    Case #PB_SerialPort_XoffReceived: r1 = "Specifies whether transmission is waiting because the XOFF character was received."
    Case #PB_SerialPort_XoffSent    : r1 = "Specifies whether transmission is waiting because the XOFF character was transmitted."
    Case #PB_SerialPort_EOFSent     : r1 = "Specifies whether the end-of-file (EOF) character has been received."
    Default                         : r1 = "ErrorCode " + Hex(ErrorCode)
  EndSelect
  ProcedureReturn r1
EndProcedure

; ----

; Threaded String Helper

Procedure AllocateString(String.s)
  Protected *mem
  *mem = AllocateMemory(StringByteLength(String) + SizeOf(Character))
  If *mem
    PokeS(*mem, String)
  EndIf
  ProcedureReturn *mem
EndProcedure

Procedure.s FreeString(*Mem)
  Protected result.s
  If *Mem
    result = PeekS(*Mem)
    FreeMemory(*Mem)
  EndIf
  ProcedureReturn result
EndProcedure

; *****************************************************************************

;- Example

CompilerIf #PB_Compiler_IsMainFile
  
  Global ComData.udtComData
  
  Enumeration EventCustomValue #PB_Event_FirstCustomValue
    #My_Event_NewData
    #My_Event_NewState
  EndEnumeration
  
  ; ---------------------------------------------------------------------------
  
  Procedure ReceiveCB(Text.s)
    PostEvent(#My_Event_NewData, 0, 0, 0, AllocateString(Text))
  EndProcedure
  
  Procedure MyEventNewDataCB()
    Protected Text.s
    Text = FreeString(EventData())
    AddGadgetItem(0, -1, Text)
    SetGadgetState(0, CountGadgetItems(0) - 1)
    SetGadgetState(0, -1)
  EndProcedure
  
  BindEvent(#My_Event_NewData, @MyEventNewDataCB())
  
  ; ---------------------------------------------------------------------------
  
  Procedure StatusCB(Status, *ComData.udtComData)
    PostEvent(#My_Event_NewState, 0, 0, Status, *ComData)
  EndProcedure
  
  Procedure MyEventNewStateCB()
    Protected Text.s, Status, *ComData.udtComData
    Status = EventType()
    *ComData = EventData()
    Select Status
      Case #ComStatus_OpenPort
        Text = "ComStatus: Open Port " + *ComData\Port
      Case #ComStatus_ClosePort
        Text = "ComStatus: Close Port " + *ComData\Port
      Case #ComStatus_ErrorOpenPort
        Text = "ComError: Open Port " + *ComData\Port
      Case #ComStatus_ErrorSend
        Text = "ComError Send: Port " + *ComData\Port + " - " + SerialPortErrorText(*ComData\SendError)
      Case #ComStatus_ErrorReceive
        Text = "ComError Receive: Port " + *ComData\Port + " - " + SerialPortErrorText(*ComData\ReceiveError)
      Case #ComStatus_ErrorDataSize
        Text = "ComError Send: Port " + *ComData\Port + " - Send data size to big."
    EndSelect
    If Bool(Text)
      StatusBarText(0, 0, Text)
    EndIf
  EndProcedure
  
  BindEvent(#My_Event_NewState, @MyEventNewStateCB())
  
  ; ---------------------------------------------------------------------------
  
  Procedure InitComport()
    With ComData
      If \Status
        ProcedureReturn 2 ; Always running
      EndIf
      \Port = "COM4"
      \Baud = 115200
      \Parity = #PB_SerialPort_NoParity
      \DataBit = 8
      \StopBit = 1
      \Handshake = #PB_SerialPort_NoHandshake
      \BufferSize = 2048
      \EndOfText = #LF$ ; #LFCR$
      \StatusCB = @StatusCB()
      \ReceiveCB = @ReceiveCB()
      \ThreadID = CreateThread(@thComport(), ComData)
      If Not \ThreadID
        ProcedureReturn 0 ; Error create thread
      Else
        ProcedureReturn 1 ; ok
      EndIf
    EndWith
  EndProcedure
  
  Procedure Main()
    Protected Event, Text.s, i
    If OpenWindow(0, #PB_Ignore, #PB_Ignore, 800, 600, "Comport", #PB_Window_SystemMenu)
      CreateStatusBar(0, WindowID(0))
      AddStatusBarField(#PB_Ignore)
      ListViewGadget(0, 0, 0, 800, 540)
      StringGadget(1, 5, 545, 590, 25, "")
      ButtonGadget(2, 605, 545, 90, 25, "Send")
      ButtonGadget(3, 695, 545, 90, 25, "On/Off")
      AddKeyboardShortcut(0, #PB_Shortcut_Return, 1000)
      
      CreatePopupMenu(1)
      MenuItem(101, "Copy ListView")
      
      Repeat
        Event = WaitWindowEvent()
        Select Event
          Case #PB_Event_CloseWindow
            If ComData\Status
              MessageRequester("Info", "Comport Is Open!", #PB_MessageRequester_Warning)
            Else
              Break
            EndIf
            
          Case #PB_Event_Gadget
            Select EventGadget()
              Case 0
                If EventType() = #PB_EventType_RightClick
                  DisplayPopupMenu(1, WindowID(0))
                EndIf
                
              Case 2 ; send button
                If ComData\Status = #ComThread_Running And ComData\SendSignal = #False
                  ComData\SendText = GetGadgetText(1)
                  ComData\SendSignal = #True
                EndIf
              Case 3 ; on/off button
                If ComData\Status
                  ComData\Exit = 1
                Else
                  If Not InitComport()
                    StatusBarText(0, 0, "Comport " + ComData\Port + ": Error Create Thread")
                  EndIf
                EndIf
            EndSelect
            
          Case #PB_Event_Menu
            Select EventMenu()
              Case 101 ; popup menu
                Text = ""
                For i = 0 To CountGadgetItems(0) - 1
                  text + GetGadgetItemText(0, i) + #CRLF$
                Next
                SetClipboardText(Text)
                
            EndSelect
        EndSelect
      ForEver
    EndIf
  EndProcedure : Main()
  
CompilerEndIf
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
TassyJim
Enthusiast
Enthusiast
Posts: 186
Joined: Sun Jun 16, 2013 6:27 am
Location: Tasmania (Australia)

Re: Serial Communication locking up.

Post by TassyJim »

matalog wrote: Mon Mar 24, 2025 1:24 pm Should the data not just be stored or not sent until the receiving device gives the send signal?
9600 is very slow. The buffer is too small to hold the 10 strings.
I am not sure what happens with a full buffer and handshaking.

mk-soft is right, in a real program, put the serial code into a thread. That allows you to keep the GUI responsive as well as cancel the comms at any time.
User avatar
matalog
Enthusiast
Enthusiast
Posts: 304
Joined: Tue Sep 05, 2017 10:07 am

Re: Serial Communication locking up.

Post by matalog »

I got a new cable, and put a bit of a delay in and it seems to work better, thanks.
Last edited by matalog on Sat Apr 05, 2025 11:13 pm, edited 1 time in total.
User avatar
matalog
Enthusiast
Enthusiast
Posts: 304
Joined: Tue Sep 05, 2017 10:07 am

Re: Serial Communication locking up.

Post by matalog »

I am trying to make this into a reliable Serial port send program, but over the 16384 bytes, it seems to finish and the old computer (Z88) has only received around 16348 bytes (i checked for that obvious mis-spelling of the number, but it is always around 30 bytes short or missing).

I use this now, with a thread:

Code: Select all


OpenWindow(0,0,0,100,100,"Window")


If Not OpenFile(0,"C:\file.xyz")
  MessageRequester("Error","File Not Opened - Will Exit")
 ; End
EndIf
snd=1
Global *sbuf = AllocateMemory(16384)
For t=0 To 16383
  PokeA(*sbuf+t,ReadByte(0))
Next


Global Port$ = "COM5"
Global b.a=0

Procedure sendser(parameter)
  Debug "Yes"
If OpenSerialPort(1, Port$, 9600, #PB_SerialPort_NoParity, 8, 1, #PB_SerialPort_RtsCtsHandshake, 128, 16385)
  Debug "Starting"
  WriteSerialPortData(1,*sbuf,16384)
Else
  MessageRequester("Error", "Can't open the serial port: "+Port$)
EndIf
EndProcedure
  thr1=CreateThread(@sendser(),1)
  
    Repeat
    Event = WindowEvent()
    Select Event
    EndSelect
  Until Event = #PB_Event_CloseWindow

End
Should I expect this to send bytes to the Z88 without any bytes being missed, or am I doing something silly?


EDIT: I found out that the Z88 has a default Xon/Xoff software flow control ON, when I disabled that, it now receives the full 16384 bytes :-).
Mol4yn
New User
New User
Posts: 2
Joined: Fri Jul 12, 2019 4:40 pm

Re: Serial Communication locking up.

Post by Mol4yn »

mk-soft wrote: Mon Mar 24, 2025 7:00 pm It's a bit older ...

COM port communication should run in a thread in the background and be transmitted to the GUI with PostEvent.

For testing a complete code. The settings are made in InitComport()
Small MOD add multiple comports, GUI terminals, parameter changes, INI saving, StopBit fix, and auto port detection

Code: Select all

;-TOP
; https://www.purebasic.fr/english/viewtopic.php?p=644565#p644565
; Comment : Comport Manager Over Thread and Callback
; Author  : mk-soft
; Version : v0.07.1
; Created : 26.01.2018
; Updated : 03.09.2022
; Modified Mol4un : 28.08.2025 for multiple comports, GUI terminals, parameter changes, INI saving, StopBit fix, and auto port detection

; *****************************************************************************

CompilerIf #PB_Compiler_Thread = 0
  CompilerError "Use Option Threadsafe!"
CompilerEndIf

#COMMaxPorts = 256 ; Define maximum COM ports for Windows

Global NewList COMUsablePorts.s() ; Global list for usable COM ports

Prototype ProtoReceiveCB(Text.s, Index.i)
Prototype ProtoStatusCB(Status, *ComData)

Enumeration
  #ComThread_Stopped
  #ComThread_Startup
  #ComThread_Running
EndEnumeration

Enumeration
  #ComStatus_Nothing
  #ComStatus_OpenPort
  #ComStatus_ClosePort
  #ComStatus_ErrorOpenPort
  #ComStatus_ErrorSend
  #ComStatus_ErrorReceive
  #ComStatus_ErrorDataSize
EndEnumeration

Structure udtComData
  ; Header
  Index.i
  ThreadID.i
  Exit.i
  Status.i
  ; Port Data
  ComID.i
  Port.s
  Baud.i
  Parity.i
  DataBit.i
  StopBit.f
  Handshake.i
  BufferSize.i
  ; End Of Text
  EndOfText.s
  ; Send Data
  SendSignal.i
  SendCount.i
  SendText.s
  SendError.i
  ; Receive data
  ReceiveCount.i
  ReceiveText.s
  ReceiveError.i
  ; Callback
  *StatusCB.ProtoStatusCB
  *ReceiveCB.ProtoReceiveCB
EndStructure

Procedure thComport(*ComData.udtComData)
  Protected *Send, *Receive, SendText.s, SendLen, ReceiveText.s, ReceiveLen, Pos
  
  With *ComData
    ; Startup
    \Status = #ComThread_Startup
    \SendCount = 0
    \ReceiveCount = 0
    \ComID = OpenSerialPort(#PB_Any, \Port, \Baud, \Parity, \DataBit, \StopBit, \Handshake, \BufferSize, \BufferSize)
    If \ComID
      \Status = #ComThread_Running
    Else
      If \StatusCB
        \StatusCB(#ComStatus_ErrorOpenPort, *ComData)
      EndIf
      \Status = #ComThread_Stopped
      ProcedureReturn 0
    EndIf
    If \StatusCB
      \StatusCB(#ComStatus_OpenPort, *ComData)
    EndIf
    *Send = AllocateMemory(\BufferSize)
    *Receive = AllocateMemory(\BufferSize)
    ; Loop
    Repeat
      If \SendSignal
        SendText = \SendText + \EndOfText
        SendLen = StringByteLength(SendText, #PB_Ascii)
        If SendLen <= \BufferSize
          PokeS(*Send, SendText, SendLen, #PB_Ascii)
          If WriteSerialPortData(\ComID, *Send, SendLen) = 0
            \SendError = SerialPortError(\ComID)
            If \StatusCB
              \StatusCB(#ComStatus_ErrorSend, *ComData)
            EndIf
          Else
            \SendError = 0
            \SendCount + 1
          EndIf
        Else
          If \StatusCB
            \StatusCB(#ComStatus_ErrorDataSize, *ComData)
          EndIf
        EndIf
        \SendSignal = #False
      EndIf
      ReceiveLen = AvailableSerialPortInput(\ComID)
      If ReceiveLen
        ReceiveLen = ReadSerialPortData(\ComID, *Receive, ReceiveLen)
        If ReceiveLen = 0
          \ReceiveError = SerialPortError(\ComID)
          If \StatusCB
            \StatusCB(#ComStatus_ErrorReceive, *ComData)
          EndIf
        Else
          \ReceiveError = 0
        EndIf
        ReceiveText + PeekS(*Receive, ReceiveLen, #PB_Ascii)
        Repeat
          pos = FindString(ReceiveText, \EndOfText, 1, #PB_String_NoCase)
          If pos
            \ReceiveText = Left(ReceiveText, pos - 1)
            ReceiveText = Mid(ReceiveText, pos + Len(\EndOfText))
            \ReceiveCount + 1
            If \ReceiveCB
              \ReceiveCB(\ReceiveText, \Index)
            EndIf
          EndIf
        Until pos = 0
      EndIf
      Delay(10)
    Until \Exit
    ; Shutdown
    CloseSerialPort(\ComID)
    If \StatusCB
      \StatusCB(#ComStatus_ClosePort, *ComData)
    EndIf
    FreeMemory(*Send)
    FreeMemory(*Receive)
    \Status = #ComThread_Stopped
    \ComID = 0
    \Exit = 0
    ProcedureReturn 1
  EndWith
EndProcedure

; ----

Procedure.s SerialPortErrorText(ErrorCode)
  Protected r1.s
  Select ErrorCode
    Case #PB_SerialPort_RxOver      : r1 = "An input buffer overflow has occurred."
    Case #PB_SerialPort_OverRun     : r1 = "A character-buffer overrun has occurred."
    Case #PB_SerialPort_RxParity    : r1 = "The hardware detected a parity error."
    Case #PB_SerialPort_Frame       : r1 = "The hardware detected a framing error."
    Case #PB_SerialPort_Break       : r1 = "The hardware detected a break condition."
    Case #PB_SerialPort_TxFull      : r1 = "The application tried to transmit a character but the output buffer was full."
    Case #PB_SerialPort_IOE         : r1 = "An I/O error occurred during communications with the device."
    Case #PB_SerialPort_WaitingCTS  : r1 = "Specifies whether transmission is waiting for the CTS (clear-To-send) signal to be sent."
    Case #PB_SerialPort_WaitingDSR  : r1 = "Specifies whether transmission is waiting for the DSR (Data-set-ready) signal to be sent."
    Case #PB_SerialPort_WaitingRLSD : r1 = "Specifies whether transmission is waiting for the RLSD (receive-line-signal-detect) signal to be sent."
    Case #PB_SerialPort_XoffReceived: r1 = "Specifies whether transmission is waiting because the XOFF character was received."
    Case #PB_SerialPort_XoffSent    : r1 = "Specifies whether transmission is waiting because the XOFF character was transmitted."
    Case #PB_SerialPort_EOFSent     : r1 = "Specifies whether the end-of-file (EOF) character has been received."
    Default                         : r1 = "ErrorCode " + Hex(ErrorCode)
  EndSelect
  ProcedureReturn r1
EndProcedure

; ----

; Threaded String Helper

Procedure AllocateString(String.s)
  Protected *mem
  *mem = AllocateMemory(StringByteLength(String) + SizeOf(Character))
  If *mem
    PokeS(*mem, String)
  EndIf
  ProcedureReturn *mem
EndProcedure

Procedure.s FreeString(*Mem)
  Protected result.s
  If *Mem
    result = PeekS(*Mem)
    FreeMemory(*Mem)
  EndIf
  ProcedureReturn result
EndProcedure

; ----

Procedure COMGetAvailablePorts()
  Protected NewList COMPortNameList.s()
  Protected i.i, Directory.i, Com.i
  
  ClearList(COMUsablePorts()) ; Clear previous usable ports
  
  CompilerIf #PB_Compiler_OS = #PB_OS_Windows
    For i = 1 To #COMMaxPorts
      AddElement(COMPortNameList())
      COMPortNameList() = "COM" + Str(i)
    Next i
  CompilerEndIf
  
  CompilerIf #PB_Compiler_OS = #PB_OS_Linux
    Directory = ExamineDirectory(#PB_Any, "/dev", "ttyUSB*")
    If Directory
      While NextDirectoryEntry(Directory)
        AddElement(COMPortNameList())
        COMPortNameList() = "/dev/" + DirectoryEntryName(Directory)
      Wend
      FinishDirectory(Directory)
    EndIf
  CompilerEndIf
  
  CompilerIf #PB_Compiler_OS = #PB_OS_MacOS
    Directory = ExamineDirectory(#PB_Any, "/dev", "tty.usbserial*")
    If Directory
      While NextDirectoryEntry(Directory)
        AddElement(COMPortNameList())
        COMPortNameList() = "/dev/" + DirectoryEntryName(Directory)
      Wend
      FinishDirectory(Directory)
    EndIf
  CompilerEndIf
  
  ForEach COMPortNameList()
    Com = OpenSerialPort(#PB_Any, COMPortNameList(), 115200, #PB_SerialPort_NoParity, 8, 1, #PB_SerialPort_NoHandshake, 1, 1)
    If Com
      AddElement(COMUsablePorts())
      COMUsablePorts() = COMPortNameList()
      CloseSerialPort(Com)
    EndIf
  Next
  
  FreeList(COMPortNameList())
EndProcedure

; *****************************************************************************

;- Example

CompilerIf #PB_Compiler_IsMainFile
  
Global  Dim ComData.udtComData(2)
  Global LastListView.i
  
  Enumeration EventCustomValue #PB_Event_FirstCustomValue
    #My_Event_NewData
    #My_Event_NewState
  EndEnumeration
  
  ; ---------------------------------------------------------------------------
  
  Procedure ReceiveCB(Text.s, Index.i)
    PostEvent(#My_Event_NewData, 0, 0, Index, AllocateString(Text))
  EndProcedure
  
  Procedure MyEventNewDataCB()
    Protected Index = EventType()
    Protected Text.s = FreeString(EventData())
    AddGadgetItem(20 + Index, -1, Text)
    SetGadgetState(20 + Index, CountGadgetItems(20 + Index) - 1)
  EndProcedure
  
  BindEvent(#My_Event_NewData, @MyEventNewDataCB())
  
  ; ---------------------------------------------------------------------------
  
  Procedure StatusCB(Status, *ComData.udtComData)
    PostEvent(#My_Event_NewState, 0, 0, Status, *ComData)
  EndProcedure
  
  Procedure MyEventNewStateCB()
    Protected Text.s, Status, *ComData.udtComData
    Status = EventType()
    *ComData = EventData()
    Protected Index = *ComData\Index
    Select Status
      Case #ComStatus_OpenPort
        Text = "Com" + Str(Index + 1) + " Status: Open Port " + *ComData\Port
        SetGadgetText(50 + Index, "Off")
      Case #ComStatus_ClosePort
        Text = "Com" + Str(Index + 1) + " Status: Close Port " + *ComData\Port
        SetGadgetText(50 + Index, "On")
      Case #ComStatus_ErrorOpenPort
        Text = "Com" + Str(Index + 1) + " Error: Open Port " + *ComData\Port
        SetGadgetText(50 + Index, "On")
      Case #ComStatus_ErrorSend
        Text = "Com" + Str(Index + 1) + " Error Send: Port " + *ComData\Port + " - " + SerialPortErrorText(*ComData\SendError)
      Case #ComStatus_ErrorReceive
        Text = "Com" + Str(Index + 1) + " Error Receive: Port " + *ComData\Port + " - " + SerialPortErrorText(*ComData\ReceiveError)
      Case #ComStatus_ErrorDataSize
        Text = "Com" + Str(Index + 1) + " Error Send: Port " + *ComData\Port + " - Send data size too big."
    EndSelect
    If Bool(Text)
      StatusBarText(0, 0, Text)
    EndIf
  EndProcedure
  
  BindEvent(#My_Event_NewState, @MyEventNewStateCB())
  
  ; ---------------------------------------------------------------------------
  
  Procedure LoadSettings()
    COMGetAvailablePorts() ; Detect available ports before loading settings
    If ListSize(COMUsablePorts()) = 0
      MessageRequester("Error", "No usable COM ports detected!", #PB_MessageRequester_Error)
    EndIf
    If OpenPreferences("comport.ini")
      For i = 0 To 2
        PreferenceGroup("Com" + Str(i + 1))
        Protected port.s = ReadPreferenceString("Port", "")
        ; Validate port against usable ports
        Protected valid_port.b = #False
        ForEach COMUsablePorts()
          If COMUsablePorts() = port
            valid_port = #True
            Break
          EndIf
        Next
        If valid_port
          ComData(i)\Port = port
        ElseIf ListSize(COMUsablePorts())
          FirstElement(COMUsablePorts())
          ComData(i)\Port = COMUsablePorts()
        Else
          ComData(i)\Port = ""
        EndIf
        ComData(i)\Baud = ReadPreferenceInteger("Baud", 115200)
        ComData(i)\Parity = ReadPreferenceInteger("Parity", #PB_SerialPort_NoParity)
        ComData(i)\DataBit = ReadPreferenceInteger("DataBit", 8)
        ComData(i)\StopBit = ReadPreferenceFloat("StopBit", 1.0)
        ComData(i)\Handshake = ReadPreferenceInteger("Handshake", #PB_SerialPort_NoHandshake)
        ComData(i)\BufferSize = ReadPreferenceInteger("BufferSize", 2048)
        Protected eot.s = ReadPreferenceString("EndOfText", "LF")
        Select eot
          Case "LF"   : ComData(i)\EndOfText = #LF$
          Case "CR"   : ComData(i)\EndOfText = #CR$
          Case "CRLF" : ComData(i)\EndOfText = #CRLF$
          Default     : ComData(i)\EndOfText = ""
        EndSelect
      Next
      ClosePreferences()
    Else
      For i = 0 To 2
        If ListSize(COMUsablePorts())
          FirstElement(COMUsablePorts())
          ComData(i)\Port = COMUsablePorts()
        Else
          ComData(i)\Port = ""
        EndIf
        ComData(i)\Baud = 115200
        ComData(i)\Parity = #PB_SerialPort_NoParity
        ComData(i)\DataBit = 8
        ComData(i)\StopBit = 1.0
        ComData(i)\Handshake = #PB_SerialPort_NoHandshake
        ComData(i)\BufferSize = 2048
        ComData(i)\EndOfText = #LF$
      Next
    EndIf
  EndProcedure
  
  Procedure SaveSettings()
    If OpenPreferences("comport.ini");, #PB_Preference_NoTree)
      For i = 0 To 2
        PreferenceGroup("Com" + Str(i + 1))
        WritePreferenceString("Port", ComData(i)\Port)
        WritePreferenceInteger("Baud", ComData(i)\Baud)
        WritePreferenceInteger("Parity", ComData(i)\Parity)
        WritePreferenceInteger("DataBit", ComData(i)\DataBit)
        WritePreferenceFloat("StopBit", ComData(i)\StopBit)
        WritePreferenceInteger("Handshake", ComData(i)\Handshake)
        WritePreferenceInteger("BufferSize", ComData(i)\BufferSize)
        Protected eot.s
        Select ComData(i)\EndOfText
          Case #LF$   : eot = "LF"
          Case #CR$   : eot = "CR"
          Case #CRLF$ : eot = "CRLF"
          Default     : eot = "None"
        EndSelect
        WritePreferenceString("EndOfText", eot)
      Next
      ClosePreferences()
    EndIf
  EndProcedure
  
  Procedure InitComport(Index.i)
    With ComData(Index)
      If \Status
        ProcedureReturn 2 ; Already running
      EndIf
      If \Port = ""
        StatusBarText(0, 0, "Com" + Str(Index + 1) + ": No valid port selected")
        ProcedureReturn 0
      EndIf
      \StatusCB = @StatusCB()
      \ReceiveCB = @ReceiveCB()
      \ThreadID = CreateThread(@thComport(), @ComData(Index))
      If Not \ThreadID
        ProcedureReturn 0 ; Error create thread
      Else
        ProcedureReturn 1 ; ok
      EndIf
    EndWith
  EndProcedure
  
  Procedure EditSettings(Index.i)
    Protected event.i, ok.i = 0, was_running.i = 0
    Protected baudRates.s = "50,75,110,150,300,600,1200,1800,2400,4800,9600,19200,38400,57600,115200"
    With ComData(Index)
      was_running = Bool(\Status = #ComThread_Running)
      If OpenWindow(1, #PB_Ignore, #PB_Ignore, 400, 300, "Settings for Com" + Str(Index + 1), #PB_Window_SystemMenu | #PB_Window_ScreenCentered, WindowID(0))
        TextGadget(#PB_Any, 10, 10, 100, 20, "Port:")
        ComboBoxGadget(100, 120, 10, 200, 20)
        Protected selected_port_index.i = -1, j.i = 0
        ForEach COMUsablePorts()
          AddGadgetItem(100, -1, COMUsablePorts())
          If COMUsablePorts() = \Port
            selected_port_index = j
          EndIf
          j + 1
        Next
        If selected_port_index >= 0
          SetGadgetState(100, selected_port_index)
        ElseIf ListSize(COMUsablePorts())
          SetGadgetState(100, 0)
        Else
          AddGadgetItem(100, -1, "No ports available")
          SetGadgetState(100, 0)
        EndIf
        
        TextGadget(#PB_Any, 10, 40, 100, 20, "Baud Rate:")
        ComboBoxGadget(101, 120, 40, 200, 20)
        Protected baud.s
        For j = 1 To CountString(baudRates, ",") + 1
          baud = StringField(baudRates, j, ",")
          AddGadgetItem(101, -1, baud)
        Next
        SetGadgetText(101, Str(\Baud))
        
        TextGadget(#PB_Any, 10, 70, 100, 20, "Parity:")
        ComboBoxGadget(102, 120, 70, 200, 20)
        AddGadgetItem(102, -1, "None")
        AddGadgetItem(102, -1, "Odd")
        AddGadgetItem(102, -1, "Even")
        AddGadgetItem(102, -1, "Mark")
        AddGadgetItem(102, -1, "Space")
        SetGadgetState(102, \Parity)
        
        TextGadget(#PB_Any, 10, 100, 100, 20, "Data Bits:")
        ComboBoxGadget(103, 120, 100, 200, 20)
        AddGadgetItem(103, -1, "5")
        AddGadgetItem(103, -1, "6")
        AddGadgetItem(103, -1, "7")
        AddGadgetItem(103, -1, "8")
        SetGadgetState(103, \DataBit - 5)
        
        TextGadget(#PB_Any, 10, 130, 100, 20, "Stop Bits:")
        ComboBoxGadget(104, 120, 130, 200, 20)
        AddGadgetItem(104, -1, "1")
        AddGadgetItem(104, -1, "1.5")
        AddGadgetItem(104, -1, "2")
        Protected stop_index.i
        Select \StopBit
          Case 1.0  : stop_index = 0
          Case 1.5  : stop_index = 1
          Case 2.0  : stop_index = 2
          Default   : stop_index = 0
        EndSelect
        SetGadgetState(104, stop_index)
        
        TextGadget(#PB_Any, 10, 160, 100, 20, "Handshake:")
        ComboBoxGadget(105, 120, 160, 200, 20)
        AddGadgetItem(105, -1, "None")
        AddGadgetItem(105, -1, "RTS")
        AddGadgetItem(105, -1, "RTS/CTS")
        AddGadgetItem(105, -1, "Xon/Xoff")
        Select \Handshake
          Case #PB_SerialPort_NoHandshake      : SetGadgetState(105, 0)
          Case #PB_SerialPort_RtsHandshake     : SetGadgetState(105, 1)
          Case #PB_SerialPort_RtsCtsHandshake  : SetGadgetState(105, 2)
          Case #PB_SerialPort_XonXoffHandshake : SetGadgetState(105, 3)
        EndSelect
        
        TextGadget(#PB_Any, 10, 190, 100, 20, "Buffer Size:")
        StringGadget(106, 120, 190, 200, 20, Str(\BufferSize))
        
        TextGadget(#PB_Any, 10, 220, 100, 20, "End Of Text:")
        ComboBoxGadget(107, 120, 220, 200, 20)
        AddGadgetItem(107, -1, "None")
        AddGadgetItem(107, -1, "CR")
        AddGadgetItem(107, -1, "LF")
        AddGadgetItem(107, -1, "CRLF")
        Protected eot_state.i
        Select \EndOfText
          Case ""     : eot_state = 0
          Case #CR$   : eot_state = 1
          Case #LF$   : eot_state = 2
          Case #CRLF$ : eot_state = 3
        EndSelect
        SetGadgetState(107, eot_state)
        
        ButtonGadget(110, 100, 260, 80, 25, "OK")
        ButtonGadget(111, 200, 260, 80, 25, "Cancel")
        
        Repeat
          event = WaitWindowEvent()
          If event = #PB_Event_Gadget
            Select EventGadget()
              Case 110 : ok = 1 : Break
              Case 111 : Break
            EndSelect
          EndIf
        Until event = #PB_Event_CloseWindow
        
        If ok
          If ListSize(COMUsablePorts()) And GetGadgetState(100) >= 0
            SelectElement(COMUsablePorts(), GetGadgetState(100))
            \Port = COMUsablePorts()
          Else
            \Port = ""
          EndIf
          \Baud = Val(GetGadgetText(101))
          \Parity = GetGadgetState(102)
          \DataBit = Val(GetGadgetItemText(103, GetGadgetState(103)))
          Select GetGadgetState(104)
            Case 0 : \StopBit = 1.0
            Case 1 : \StopBit = 1.5
            Case 2 : \StopBit = 2.0
          EndSelect
          Select GetGadgetState(105)
            Case 0 : \Handshake = #PB_SerialPort_NoHandshake
            Case 1 : \Handshake = #PB_SerialPort_RtsHandshake
            Case 2 : \Handshake = #PB_SerialPort_RtsCtsHandshake
            Case 3 : \Handshake = #PB_SerialPort_XonXoffHandshake
          EndSelect
          \BufferSize = Val(GetGadgetText(106))
          Protected s.s = GetGadgetItemText(107, GetGadgetState(107))
          Select s
            Case "None" : \EndOfText = ""
            Case "CR"   : \EndOfText = #CR$
            Case "LF"   : \EndOfText = #LF$
            Case "CRLF" : \EndOfText = #CRLF$
          EndSelect
        EndIf
        CloseWindow(1)
      EndIf
    EndWith
    If ok
      If ComData(Index)\ThreadID And IsThread(ComData(Index)\ThreadID)
        ComData(Index)\Exit = 1
        While ComData(Index)\Status <> #ComThread_Stopped
          Delay(10)
        Wend
      EndIf
      If was_running
        InitComport(Index)
      EndIf
    EndIf
  EndProcedure
  
  Procedure Main()
    LoadSettings()
    For i = 0 To 2
      ComData(i)\Index = i
    Next
    
    Protected Event.i, Text.s, j.i, gadget.i
    If OpenWindow(0, #PB_Ignore, #PB_Ignore, 800, 600, "Multi Comport Manager", #PB_Window_SystemMenu)
      CreateStatusBar(0, WindowID(0))
      AddStatusBarField(#PB_Ignore)
      
      PanelGadget(10, 0, 0, 800, 540)
      For i = 0 To 2
        AddGadgetItem(10, -1, "Com " + Str(i + 1))
        ListViewGadget(20 + i, 0, 0, 790, 440)
        StringGadget(30 + i, 5, 445, 590, 25, "")
        ButtonGadget(40 + i, 605, 445, 90, 25, "Send")
        ButtonGadget(50 + i, 700, 445, 90, 25, "On")
        ButtonGadget(60 + i, 5, 480, 90, 25, "Settings")
      Next
      
      CreatePopupMenu(1)
      MenuItem(101, "Copy ListView")
      
      Repeat
        Event = WaitWindowEvent()
        Select Event
          Case #PB_Event_CloseWindow
            For i = 0 To 2
              If ComData(i)\Status
                ComData(i)\Exit = 1
              EndIf
            Next
            For i = 0 To 2
              While ComData(i)\Status <> #ComThread_Stopped
                Delay(100)
              Wend
            Next
            SaveSettings()
            Break
            
          Case #PB_Event_Gadget
            gadget = EventGadget()
            If gadget >= 20 And gadget <= 22 And EventType() = #PB_EventType_RightClick
              LastListView = gadget
              DisplayPopupMenu(1, WindowID(0))
            ElseIf gadget >= 40 And gadget <= 42 ; Send buttons
              Protected send_index.i = gadget - 40
              If ComData(send_index)\Status = #ComThread_Running And ComData(send_index)\SendSignal = #False
                ComData(send_index)\SendText = GetGadgetText(30 + send_index)
                ComData(send_index)\SendSignal = #True
              EndIf
            ElseIf gadget >= 50 And gadget <= 52 ; On/Off buttons
              Protected toggle_index.i = gadget - 50
              If ComData(toggle_index)\Status
                ComData(toggle_index)\Exit = 1
              Else
                If Not InitComport(toggle_index)
                  StatusBarText(0, 0, "Com" + Str(toggle_index + 1) + ": Error Create Thread")
                EndIf
              EndIf
            ElseIf gadget >= 60 And gadget <= 62 ; Settings buttons
              Protected settings_index.i = gadget - 60
              EditSettings(settings_index)
            EndIf
            
          Case #PB_Event_Menu
            Select EventMenu()
              Case 101 ; popup menu
                Text = ""
                For j = 0 To CountGadgetItems(LastListView) - 1
                  Text + GetGadgetItemText(LastListView, j) + #CRLF$
                Next
                SetClipboardText(Text)
            EndSelect
        EndSelect
      ForEver
    EndIf
  EndProcedure : Main()
  
CompilerEndIf
User avatar
mk-soft
Always Here
Always Here
Posts: 6240
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: Serial Communication locking up.

Post by mk-soft »

:wink:

True. There are half bits
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
swhite
Enthusiast
Enthusiast
Posts: 796
Joined: Thu May 21, 2009 6:56 pm

Re: Serial Communication locking up.

Post by swhite »

matalog wrote: Mon Mar 24, 2025 12:13 am I have been testing out the serial port feature in PB today, and i'm not having the greatest success.

This program:

Code: Select all

  Port$ = "COM4"
If OpenSerialPort(1, Port$, 9600, #PB_SerialPort_NoParity, 8, 1, #PB_SerialPort_RtsCtsHandshake, 128, 128)
For x=1 To 10
    WriteSerialPortString(1,"IS THIS WORKING coRRecTlY?"+Str(x)+Chr(13)+Chr(10),#PB_Ascii)
Next
Else
  MessageRequester("Error", "Can't open the serial port: "+Port$)
EndIf
CloseFile(0)
can get into a state in which the Task Manager cannot even End it, and the only way to close it, is to restart the computer.

If I run that same program without the For Next loop, it does output the text correctly. Why might it be having a problem with the loop?

I use serial ports all the time and they work fine. The above code will send data much too fast for the port to handle since your buffer size is too small to hold all the data you sent.

Simon
Simon White
dCipher Computing
Post Reply