Timer & Threads

General FreeBASIC programming questions.
fxm
Posts: 9012
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: Timer & Threads

Postby fxm » Mar 28, 2017 20:53

If SizeOf( IOCtr.PortBit ) <= SizeOf( Integer ), 4 or 8 depending on used compiler (32-bit or 64-bit), there is no risk to not use mutex, because there is normally no possibility of transitory state when the machine instruction modifies the IOCtrl compared to its previous state.
tinram
Posts: 88
Joined: Nov 30, 2006 13:35
Location: UK

Re: Timer & Threads

Postby tinram » Mar 28, 2017 22:28

70 and 66.

You guys rock!

Thank you fxm for all the help and coding wisdom you give to others.
Dinosaur
Posts: 1186
Joined: Jul 24, 2005 1:13
Location: Searcy AR USA
Contact:

Re: Timer & Threads

Postby Dinosaur » Mar 29, 2017 0:13

Hi All

tinram, when I was 20 I thought my father was ancient at 45. Now at 70 I think 85 is old.

fxm, I am using Linux 64 bit compiler, and I am told (on this forum) that it all gets converted to Long anyway.
So the IOCtrl udt is as follows.

Code: Select all

Type IOCtrls
   Manuel            As  Long         
   IOValue             As  Long
   PortBit(0 To 47)   As  Long
   NozzleBit         As  Long         
   HeartBeat         As  Long
   OldBeat            As  Long
   InputDone         As  Long
End Type

Today I ran the system for 4 hours on the actual production machine without using MutexLock, and no obvious problems.
I will let the system run like this and see what happens.
I do really appreciate your help on this, especially since this is my first foray into Threading.

Regards

Edit: The actual code that matters. Please point out any improvements.
Note that the whole purpose of this code is to have an Ice Cream Nozzle open for a fixed time.
The very stable output pressure from the Freezer guarantees accuracies of a couple of grams.
At the same time the other 23 output bits are also set/reset plus 24 bits of input are read, from here.

Code: Select all

Sub USBController
    '----------------------------------------------------------------------
    'This routine does all comms to IA-3124, if Unit #0
    'All Inputs and Outputs are set here as well.
    'First Set Output Bits , then Read I/O bits
    'This Comm's method holds loop time stable within 2 uSec
    'To do a complete Tx / Rx takes 24 mSec.
    '-----------------------------------------------------------------------
    USBTimer.mSec = (Timer - USBTimer.CalTime) * 1000
   With USBControl
        .Handle = USBInit.Handle
        Select Case .StepNbr
            Case -1
            Sleep 1
                If USBInit.InitOK > 0 Then .StepNbr = 1
            Case 0
                .StepNbr = -1
            Case 1
                .OPValue = 0
            If IOCtrl.PortBit(IOCtrl.NozzleBit) > 0 Then         'If Main is calling for Nozzle output
               If Nozzle.StepNbr = 2 Then                     'for the first time this cycle
                  If .NozzleTime < 1 Then                     'and we haven't started a time yet
                     .NozzleTime = USBTimer.mSec               'then set the timer.
                  EndIf
               EndIf
               If .NozzleTime > 0 Then                        'If Timer is set
                  If (USBTimer.mSec - .NozzleTime) >= (Settings.CreamOpenTime - 50) Then
                     .Crit_Time = 1                        'we are close to critical time slot
                  EndIf                                 'so halt all usb commands.
                  If (USBTimer.mSec - .NozzleTime) > Settings.CreamOpenTime Then
                     If .Crit_Time = 1 Then                  'Time's up , so enable usb commands again
                        IOCtrl.PortBit(IOCtrl.NozzleBit) = 0   'and turn the Nozzle bit off.
                        .NozzleTime = 0                     'cancel Nozzle timer
                        .Crit_Time = -1                     'allow all usb commands again.
                     EndIf
                  EndIf
               EndIf
            EndIf
            Dim As Long BitCount
            For BitCount = 0 To 23                                  'for the 24 O/P bits
               If IOCtrl.PortBit(BitCount) > 0 Then            'set the output value
                     .OPValue = BitSet(.OPValue,BitCount)
                  Else
                     .OPValue = BitReset(.OPValue,BitCount)
               EndIf
            Next
                '-----------------WRITE OUTPUT BITS--------------------------------------
            Dim as String OPStr
                OPStr  =  Str(Hex(.OPValue))                     'convert it to a string
                .LenStr =  Len(OPStr)
                While Len(OPStr) < 6
               OPStr = "0" + OPStr                           'make sure the length is correct
                Wend
                If .OPValue = 0 then OPStr = "000000"               
            If LOC(.Handle) > 0 Then                        'Tidy up any left over chr's
               .Temp = Input(Loc(.Handle),#.Handle)
            EndIf
            If .OldOPValue = .OPValue Then
                  .StepNbr = 3                            'Don't re-Tx the same data,
                  If USBInit.InitOK = 1 Then                  
                     USBInit.InitOK = 2                     'go for inputs instead
                     .StepNbr = 2                        
                  EndIf
               Else
                  .StepNbr = 2                           
            EndIf
            .OPStr = OPStr
            If .Crit_Time > 0 Then .StepNbr = -1                'loop back if we are in Critical Time Zone
         Case 2
            .TxStr = "!002" + .OPStr
            Print #.Handle,.TxStr + Chr(&H0D);                  'send "Set Data bits" command
            .OldOPValue = .OPValue                     'now that Sent = Requested, sync them
            .RxStr = ""
            .LastTx  = USBTimer.mSec
            .StepNbr = 3
            Case 3   'Ask for Inputs 
            Sleep 1         
            If USBTimer.mSec - .LastTx >= 9 Then                'Typically takes 10 mSec
               .TxStr = "?002"                            'before we can Tx again
               Print #.Handle,.TxStr + CHR(&H0D);
               .RxStr = ""
               .StartTime = USBTimer.mSec
               .StepNbr = 4
               .LastTx = USBTimer.mSec
                EndIf
            Case 4   'Wait for Input String takes 12 mSec, so may as well sleep for 5.
            Sleep 5   
                If LOC(.Handle) > 0 Then   
               Do
                  .Buffer = Input(1,#.Handle)
                  .RxStr  = .RxStr + .Buffer
                  If .Buffer = Chr(&H0d) Then                         '&h0d means end of Tx           
                     If InStr(.RxStr,"_") > 0 Then                           'and response has valid Chr in it
                           .StartTime = USBTimer.mSec
                           .StepNbr = 5
                           .OldBeat = .HeartBeat            'let screen led know
                           If .OldBeat = .HeartBeat Then
                              IOCtrl.OldBeat = IOCtrl.HeartBeat
                           EndIf
                           Exit Do
                        Else
                           .StepNbr = -1
                           .ErrCount3 += 1                     
                           Exit Do
                     EndIf
                  EndIf
                  If USBTimer.mSec - .StartTime > 150 Then      'escape route
                     .ErrCount4 += 1
                     .StepNbr = -1
                     Exit Do
                  EndIf
                  USBTimer.mSec  = (Timer - USBTimer.CalTime) * 1000
               Loop
            EndIf
            Case 5   'Now sort out the input data and assign to IOCtrl.PortBit(n)
            Dim As Long CPos
            CPos = InStr(.RxStr,"_")
            If CPos > 0 Then
               USBInit.InitOK = 2
               .IPStr = "&H" + Mid(.RxStr,CPos + 1,12)     
               .IOValue = Val(.IPStr)                     
               .IOBits  = .IOValue
               Dim As Long BitCount
               For BitCount = 24 To 47                        'Only do Input bits
                  If Bit(.IOValue,BitCount) Then         
                        IOCtrl.PortBit(BitCount) = 1    
                     Else                               
                        IOCtrl.PortBit(BitCount) = 0                           
                  EndIf
               Next
            EndIf               
            .StepNbr = 1
        End Select   
   End With
End Sub
Sub USBthread( ByVal userdata As Any Ptr )
   USBTimer.CalTime = Timer * 1000
   Sleep 100
   USBInitialize
   Sleep 100
   Do
      USBController   '1 successful Tx/Rx loop takes 24 mSec
      If USBFlag > 0 Then Exit Do      'Dim Shared Quit Flag from Main.
   Loop
End Sub
MrSwiss
Posts: 3125
Joined: Jun 02, 2013 9:27
Location: Switzerland

Re: Timer & Threads

Postby MrSwiss » Mar 29, 2017 9:21

Dinosaur wrote:I am using Linux 64 bit compiler, and I am told (on this forum) that it all gets converted to Long anyway.
I'm quite positive, that this is a sort of misunderstanding ... more likely: a Integer.

For starters I'd improve on the type definition. At the moment you're really wasting memory (all those Long's).
Flag's (or Switches, e.g. on/off) are best coded as Boolean's = 1 Byte vs. Long = 4 Byte. This *weights in* mainly
on the used array ...

Code: Select all

Type IOCtrls   
   IOValue         As Long    ' maybe a Byte/UByte does the  trick, can't say ...(don't know required range)
   PortBit(0 To 47)   As Boolean
   Manuel         As Boolean
   NozzleBit      As Boolean         
   HeartBeat      As Boolean
   OldBeat         As Boolean
   InputDone      As Boolean
End Type
Then, in Case 5:

Code: Select all

Case 5   'Now sort out the input data and assign to IOCtrl.PortBit(n)
            Dim As Long CPos
            CPos = InStr(.RxStr,"_")
            If CPos > 0 Then
               USBInit.InitOK = 2
               .IPStr = "&H" + Mid(.RxStr,CPos + 1,12)     
               .IOValue = Val(.IPStr)                     
               .IOBits  = .IOValue
               'Dim As Long BitCount   ' not needed + using fastest loop-counter: UInteger
               For BitCount As UInteger = 24 To 47                        'Only do Input bits
                  If Bit(.IOValue,BitCount) Then         
                        IOCtrl.PortBit(BitCount) = 1  ' could be made TRUE (for Boolean) 
                     Else                               
                        IOCtrl.PortBit(BitCount) = 0  ' could be made FALSE (for Boolean)                 
                  EndIf
               Next
            EndIf               
            .StepNbr = 1
Furthermore optimizing requires more information from you.
E.g. the type definition of USBControl ...
We don't have the *big picture*, as of now!
Dinosaur
Posts: 1186
Joined: Jul 24, 2005 1:13
Location: Searcy AR USA
Contact:

Re: Timer & Threads

Postby Dinosaur » Mar 30, 2017 1:17

Hi All

MrSwiss , I have tested changing the udt elements to the minimum size suited to the use in my program.
It did reduce the size of the udt significantly, but I could not detect any performance improvements.
I didn't use Boolean as I like to see zeroes & Ones, but the Byte is the same size as Boolean.

Anyway, I think I have gone as far as I can go on this, but posted below is the final product.

So, basically I start another thread after initializing my main program.
The Main program reads/sets IOCtrl.PortBits(n) according to the machine needs.
The usbThread runs until the Main program quits, which sets the usbFlag that causes the thread to end.
The Main Quit routine has a ThreadWait(Handle) to ensure the thread has terminated.

There are only two references to Main udt's in the usb thread. (Nozzle.StepNbr & IOCtrl.PortBits)
I tried making a copy of IOCtrl udt and using that in the usb Thread, and then copying it back, but could not get that to work,
and anyway copying the udt back & to probably takes more resources then what I am doing now.

Code: Select all

Main udt
Type IOCtrls
   Manuel            As  Byte         'indicates manual button used to set O/P
   IOValue             As  ULong         '24 bits
   PortBit(0 To 47)   As  Byte           'bits Read 0 or 1
   NozzleBit         As  Byte         '0 or 1
   HeartBeat         As  Byte         'Led on screen = 0 or 1
   OldBeat            As  Byte         '0 or 1
   InputDone         As  Byte         '0 to 10
End Type
Dim Shared IOCtrl As IOCtrls

usb Thread udt's
Type USBInits
    Handle      As Byte            '1 to ?
    InitOK      As Byte            '1 to 2
    StartTime   As ULong         'Timer
    Buffer      As String  * 1
    TxStr       As String  * 15
    RxStr       As ZString * 15
End Type
Dim Shared USBInit as USBInits
Type USBControls
    StepNbr     As Byte            '-1 to 100
    Crit_Time   As Byte            '-1 or +1
    InputDone   As Byte            '1 to 10
    Handle      As Byte            '1 to ?
    CreamOpenTime As Integer      '300 to 750
    NozzleTime   As ULong         'Timer value
    LastTx      As ULong         'Timer
    StartTime   As ULong         'Timer
    IOValue     As ULong         '24 bit value
    OPValue     As ULong         '24 bit value
    OldOPValue   As ULong         '24 bit value
    IOBits      As ULong         '24 bit value
    Buffer      As String * 1
    TxStr       As String * 15
    RxStr       As String * 15
    Temp        As String * 15
    IPStr       As String * 15
    OPStr       As String * 15
End Type
Dim Shared USBControl as USBControls
Type USBTimers
   mSec      As ULong
   StartTime   AS ULong
   CalTime      As ULong
End Type
Dim Shared USBTimer As USBTimers
declare sub usbinitialize()
declare sub usbcontroller()
declare sub USBThread (Byval Userdata as Any Ptr)

usb Thread Routines.
Sub USBInitialize
    '-----------------------------------------------------------
    'Open Com Port and Test controller,if Err Exit Sub
    'Send test Tx to IA-3124
    'Check the Receive String.
    '-----------------------------------------------------------
    Dim As Byte CP
   USBTimer.mSec = (Timer - USBTimer.CalTime) * 1000
    With USBInit
      'First see if SYMLINK File was created (Slow PC causes delay)
      .StartTime = USBTimer.mSec
      Do
         If FileExists ("/dev/IA3125") Then                     'Ok it is there
            Print "Waited;";USBTimer.mSec - .StartTime
            Exit Do
         EndIf
         USBTimer.mSec  = (Timer - USBTimer.CalTime) * 1000
         If (USBTimer.mSec - .StartTime)  > 60000 then            'if not there after 1 Min
                .InitOK = 0
                Close #CP
                Exit Sub                                    'and leave this Sub.
         EndIf
      Loop
      Shell "echo ******** | sudo -S udevadm trigger"               'USB devices recognised, so now re-read .rules
      Sleep 500
      shell "stty -F /dev/IA3125 speed 19200 -clocal -hupcl"         'set Baud rate
      Sleep 500
      CP = FreeFile
      .Handle = CP                                       'Open the port by it's SYMLINK Name
      Open Com "/dev/IA3125:19200,n,8,1,cs0,ds0,cd0,rs" For Binary As #CP
      Sleep 500
        If ERR > 0 THEN
            .InitOK = 0
            Close #CP
            Exit Sub
            Else
                If PUT (#CP, , "!00542" & CHR(&H0D)) Then            'set up board for "No Replies" to O/P commands
                    .InitOK = 0
                    Close #CP
                    Exit Sub
                EndIf
        END If
       USBTimer.mSec  = (Timer - USBTimer.CalTime) * 1000
        .StartTime = USBTimer.mSec
        Do
         USBTimer.mSec  = (Timer - USBTimer.CalTime) * 1000
            If LOC(.Handle) > 0 Then
            .Buffer = Input(1,#.Handle)
                .RxStr  = .RxStr + .Buffer
                If .Buffer = Chr(&H0d) Then                             'no more coming
               If InStr(.RxStr,"|42") > 0 Then                  'if response is valid
                  .InitOK = 1
                  Print "Init Done"                        'we are good to go
                  Print #CP, "!002000000" + Chr(&h0d)            'let's turn all O/P off.
                  Exit Do
               Endif
            Endif
         EndIf
         If (USBTimer.mSec - .StartTime) > 3000 Then               'If it takes to long
            Print "RxStr + Len(RxStr);";.RxStr, Len(.RxStr)
            Print "Err mSec;";USBTimer.mSec
            Print "Elapsed;";USBTimer.mSec - .StartTime
            Print USBTimer.mSec-.StartTime
                .InitOK = 0
                Exit Sub
         Endif
      Loop
   End With
End Sub
Sub USBController
    '----------------------------------------------------------------------
    'This routine does all comms to IA-3124, if Unit #0
    'All Inputs and Outputs are set here as well.
    'First Set Output Bits , then Read I/O bits
    'This Comm's method holds loop time stable within 2 uSec
    'To do a complete Tx / Rx takes 24 mSec.
    '-----------------------------------------------------------------------
    USBTimer.mSec = (Timer - USBTimer.CalTime) * 1000
   With USBControl
        .Handle = USBInit.Handle
        Select Case .StepNbr
            Case -1
            Sleep 1
                If USBInit.InitOK > 0 Then .StepNbr = 1
            Case 0
                .StepNbr = -1
            Case 1
                .OPValue = 0
            If IOCtrl.PortBit(IOCtrl.NozzleBit) > 0 Then         'If Main is calling for Nozzle output
               If Nozzle.StepNbr = 2 Then                     'for the first time this cycle
                  If .NozzleTime < 1 Then                     'and we haven't started a time yet
                     .NozzleTime = USBTimer.mSec               'then set the timer.
                  EndIf
               EndIf
               If .NozzleTime > 0 Then                        'If Timer is set
                  If (USBTimer.mSec - .NozzleTime) >= (.CreamOpenTime - 50) Then
                     .Crit_Time = 1                        'we are close to critical time slot
                  EndIf                                 'so halt all usb commands.
                  If (USBTimer.mSec - .NozzleTime) > .CreamOpenTime Then
                     If .Crit_Time = 1 Then                  'Time's up
                        IOCtrl.PortBit(IOCtrl.NozzleBit) = 0   'turn the Nozzle bit off.
                        .NozzleTime = 0                     'cancel Nozzle timer
                        .Crit_Time = -1                     'allow all usb commands again.
                     EndIf
                  EndIf
               EndIf
            EndIf
            Dim As Byte BitCount
            For BitCount = 0 To 23                                  'for the 24 O/P bits
               If IOCtrl.PortBit(BitCount) > 0 Then            'set the output value
                     .OPValue = BitSet(.OPValue,BitCount)
                  Else
                     .OPValue = BitReset(.OPValue,BitCount)
               EndIf
            Next
                '-----------------WRITE OUTPUT BITS--------------------------------------
            Dim as String OPStr
                OPStr  =  Str(Hex(.OPValue))                     'convert it to a string
                While Len(OPStr) < 6
               OPStr = "0" + OPStr                           'make sure the length is correct
                Wend
                If .OPValue = 0 then OPStr = "000000"               
            If LOC(.Handle) > 0 Then                        'Tidy up any left over chr's
               .Temp = Input(Loc(.Handle),#.Handle)
            EndIf
            If .OldOPValue = .OPValue Then
                  .StepNbr = 3                            'Don't re-Tx the same data,
                  If USBInit.InitOK = 1 Then                  
                     USBInit.InitOK = 2                     'go for inputs instead
                     .StepNbr = 2                        
                  EndIf
               Else
                  .StepNbr = 2                           
            EndIf
            .OPStr = OPStr
            If .Crit_Time > 0 Then .StepNbr = -1                'loop back if no data change
         Case 2
            .TxStr = "!002" + .OPStr
            Print #.Handle,.TxStr + Chr(&H0D);                  'send "Set Data bits" command
            .OldOPValue = .OPValue
            .RxStr = ""
            .LastTx  = USBTimer.mSec
            .StepNbr = 3
            Case 3   'Ask for Inputs 
            Sleep 1         
            If USBTimer.mSec - .LastTx >= 9 Then                'Typically takes 10 mSec
               .TxStr = "?002"                            'before we can Tx again
               Print #.Handle,.TxStr + CHR(&H0D);
               .RxStr = ""
               .StartTime = USBTimer.mSec
               .StepNbr = 4
               .LastTx = USBTimer.mSec
                EndIf
            Case 4   'Wait for Input String takes 12 mSec, so may as well sleep for 5.
            Sleep 5   
                If LOC(.Handle) > 0 Then   
               Do
                  .Buffer = Input(1,#.Handle)
                  .RxStr  = .RxStr + .Buffer
                  If .Buffer = Chr(&H0d) Then                     
                     If InStr(.RxStr,"_") > 0 Then               
                           .StartTime = USBTimer.mSec
                           .StepNbr = 5                  'coming here means a good Rx
                           IOCtrl.OldBeat = IOCtrl.HeartBeat   
                           Exit Do
                        Else
                           .StepNbr = -1
                           Exit Do
                     EndIf
                  EndIf
                  If (USBTimer.mSec - .StartTime) > 150 Then      'escape route
                     .StepNbr = -1
                     Exit Do
                  EndIf
                  USBTimer.mSec  = (Timer - USBTimer.CalTime) * 1000
               Loop
            EndIf
            Case 5   'Now sort out the input data and assign to IOCtrl.PortBit(n)
            Dim As Byte CPos
            CPos = InStr(.RxStr,"_")
            If CPos > 0 Then
               USBInit.InitOK = 2
               .IPStr = "&H" + Mid(.RxStr,CPos + 1,12)     
               .IOValue = Val(.IPStr)                     
               .IOBits  = .IOValue
               Dim As Byte BitCount
               For BitCount = 24 To 47                        'Only do Input bits
                  If Bit(.IOValue,BitCount) Then         
                        IOCtrl.PortBit(BitCount) = 1    
                     Else                               
                        IOCtrl.PortBit(BitCount) = 0                           
                  EndIf
               Next
            EndIf               
            .StepNbr = 1
        End Select   
   End With
End Sub
Sub USBthread( ByVal userdata As Any Ptr )
   USBTimer.CalTime = Timer * 1000
   Sleep 100
   USBInitialize
   Sleep 100
   Do
      USBController   '1 successful Tx/Rx loop takes 24 mSec
      If USBFlag > 0 Then Exit Do      'Dim Shared Quit Flag from Main.
   Loop
End Sub
MrSwiss
Posts: 3125
Joined: Jun 02, 2013 9:27
Location: Switzerland

Re: Timer & Threads

Postby MrSwiss » Mar 30, 2017 12:36

@Dinosaur,

made a couple of changes in:
  • USBController()
  • USBThread( ByVal userdata As Any Ptr )
The initializer is still *as is*.
This should improve the speed (however, I can't say whether it'll be of any significance).
Some *Do ... Loop* changed to *While ... Wend*, used UInteger in *For* loops (for speed!).
Also added some Sleep in *USBThread*.

the modified code:

Code: Select all

Sub USBController
    '----------------------------------------------------------------------
    'This routine does all comms to IA-3124, if Unit #0
    'All Inputs and Outputs are set here as well.
    'First Set Output Bits , then Read I/O bits
    'This Comm's method holds loop time stable within 2 uSec
    'To do a complete Tx / Rx takes 24 mSec.
    '-----------------------------------------------------------------------
    USBTimer.mSec = (Timer - USBTimer.CalTime) * 1000
    With USBControl
        .Handle = USBInit.Handle
        Select Case .StepNbr
            Case -1
               Sleep 1
                If USBInit.InitOK > 0 Then .StepNbr = 1
            Case 0
                .StepNbr = -1
            Case 1
                .OPValue = 0
               If IOCtrl.PortBit(IOCtrl.NozzleBit) > 0 Then         'If Main is calling for Nozzle output
                  If Nozzle.StepNbr = 2 Then                     'for the first time this cycle
                     If .NozzleTime < 1 Then                     'and we haven't started a time yet
                        .NozzleTime = USBTimer.mSec               'then set the timer.
                     EndIf
                  EndIf
                  If .NozzleTime > 0 Then                        'If Timer is set
                     If (USBTimer.mSec - .NozzleTime) >= (.CreamOpenTime - 50) Then
                        .Crit_Time = 1                        'we are close to critical time slot
                     EndIf                                 'so halt all usb commands.
                     If (USBTimer.mSec - .NozzleTime) > .CreamOpenTime Then
                        If .Crit_Time = 1 Then                  'Time's up
                           IOCtrl.PortBit(IOCtrl.NozzleBit) = 0   'turn the Nozzle bit off.
                           .NozzleTime = 0                     'cancel Nozzle timer
                           .Crit_Time = -1                     'allow all usb commands again.
                        EndIf
                     EndIf
                  EndIf
               EndIf
               'Dim As Byte BitCount <-- no way, slow
               For BitCount As UInteger = 0 To 23                 'for the 24 O/P bits
                  If IOCtrl.PortBit(BitCount) > 0 Then            'set the output value
                        .OPValue = BitSet(.OPValue,BitCount)
                     Else
                        .OPValue = BitReset(.OPValue,BitCount)
                  EndIf
               Next
                   '-----------------WRITE OUTPUT BITS--------------------------------------
               Dim as String OPStr = Str(Hex(.OPValue))         'convert it to a string
                   'OPStr 
                While Len(OPStr) < 6
                     OPStr = "0" + OPStr                           'make sure the length is correct
                Wend
                If .OPValue = 0 then OPStr = "000000"               
               If LOC(.Handle) > 0 Then                        'Tidy up any left over chr's
                  .Temp = Input(Loc(.Handle),#.Handle)
               EndIf
               If .OldOPValue = .OPValue Then
                  .StepNbr = 3                            'Don't re-Tx the same data,
                  If USBInit.InitOK = 1 Then                 
                     USBInit.InitOK = 2                     'go for inputs instead
                     .StepNbr = 2                       
                  EndIf
                Else
                  .StepNbr = 2                           
               EndIf
               .OPStr = OPStr
               If .Crit_Time > 0 Then .StepNbr = -1                'loop back if no data change
           Case 2
               .TxStr = "!002" + .OPStr
               Print #.Handle,.TxStr + Chr(&H0D);                  'send "Set Data bits" command
               .OldOPValue = .OPValue
               .RxStr = ""
               .LastTx  = USBTimer.mSec
               .StepNbr = 3
           Case 3   'Ask for Inputs
               Sleep 1         
               If USBTimer.mSec - .LastTx >= 9 Then                'Typically takes 10 mSec
                  .TxStr = "?002"                            'before we can Tx again
                  Print #.Handle,.TxStr + CHR(&H0D);
                  .RxStr = ""
                  .StartTime = USBTimer.mSec
                  .StepNbr = 4
                  .LastTx = USBTimer.mSec
               EndIf
            Case 4   'Wait for Input String takes 12 mSec, so may as well sleep for 5.
               Sleep 5   ' why not 10 ???
                'If LOC(.Handle) > 0 Then   
               'Do
                While LOC(.Handle) > 0   ' the "> 0" isn't really needed: loop ends at = 0
                  .Buffer = Input(1,#.Handle)
                  .RxStr  = .RxStr + .Buffer
                  If .Buffer = Chr(&H0d) Then                     
                     If InStr(.RxStr,"_") > 0 Then               
                           .StartTime = USBTimer.mSec
                           .StepNbr = 5                  'coming here means a good Rx
                           IOCtrl.OldBeat = IOCtrl.HeartBeat   
                           Exit Do
                        Else
                           .StepNbr = -1
                           Exit Do
                     EndIf
                  EndIf
                  If (USBTimer.mSec - .StartTime) > 150 Then      'escape route
                     .StepNbr = -1
                     Exit Do
                  EndIf
                  USBTimer.mSec  = (Timer - USBTimer.CalTime) * 1000
               'Loop
                Wend
               'EndIf
            Case 5   'Now sort out the input data and assign to IOCtrl.PortBit(n)
               Dim As Byte CPos = InStr(.RxStr,"_")
               'CPos
               If CPos > 0 Then
                  USBInit.InitOK = 2
                  .IPStr = "&H" + Mid(.RxStr,CPos + 1,12)     
                  .IOValue = Val(.IPStr)                     
                  .IOBits  = .IOValue
                  'Dim As Byte BitCount <-- no way, slow
                  For BitCount As UInteger = 24 To 47                        'Only do Input bits
                     If Bit(.IOValue,BitCount) Then         
                           IOCtrl.PortBit(BitCount) = 1   
                        Else                               
                           IOCtrl.PortBit(BitCount) = 0                           
                     EndIf
                  Next
               EndIf               
               .StepNbr = 1
        End Select   
    End With
End Sub

Sub USBthread( ByVal userdata As Any Ptr )
    USBTimer.CalTime = Timer * 1000
    Sleep 100
    USBInitialize
    Sleep 100
    While USBFlag   'Do ... as long as USBFlag <> 0
      USBController   '1 successful Tx/Rx loop takes 24 mSec
      Sleep 20      ' might as well sleep a bit ...
      'If USBFlag > 0 Then Exit Do      'Dim Shared Quit Flag from Main.
    Wend 'Loop
End Sub
Dinosaur
Posts: 1186
Joined: Jul 24, 2005 1:13
Location: Searcy AR USA
Contact:

Re: Timer & Threads

Postby Dinosaur » Mar 30, 2017 15:38

Hi All

MrSwiss, thanks for your input on this.
The only disagreement is the Sleep in the USBThread.
I can't afford to ignore the usbController for not even 1 mSec.
That is why I placed Sleeps strategically in the usbController, because I know it takes a certain amount
of time for the IOBoard to respond.
I note your comments on the length of the sleep, guess just being ultra conservative.
But, will experiment with longer sleep times and see the result.

I made one mistake in making file & port Handles Bytes, and it caused some problems initializing the port.
Once I changed it to Integer, all was good.

Once again Thanks to all.

Regards
deltarho[1859]
Posts: 1786
Joined: Jan 02, 2017 0:34
Location: UK

Re: Timer & Threads

Postby deltarho[1859] » Mar 30, 2017 16:36

In the above post we have 'Sleep 1' used twice, 'Sleep 5' used once, 'Sleep 20' used once and 'Sleep 100' used twice.

In Windows Sleep has a resolution of 15.625ms (64Hz) so only 'Sleep 100' will sleep for approximately what we ask for - the others will not even qualify as near misses.

For example:

Code: Select all

Dim as Double t
Dim as Long i
 
For i = 1 to 20
  t = Timer
  Sleep i
  t = Timer - t
  Print i;": "; t*1000
Next

with one run giving:-

Code: Select all

 1:  1.856171664145867
 2:  14.88841458890167
 3:  13.5762366444272
 4:  14.17268116494452
 5:  13.75572873068798
 6:  13.79728429166316
 7:  13.83807159852957
 8:  13.88263033427895
 9:  13.79099857665622
 10:  13.79057952878138
 11:  13.7724207963783
 12:  13.82522080321325
 13:  13.67045252950927
 14:  29.34353388472433
 15:  28.7752353998485
 16:  29.34234658328916
 17:  29.53133708312228
 18:  29.45590850243285
 19:  29.55955295991753
 20:  29.56122915064196
 ...
 ...
100:  114.3238875332984

Notice how 'Sleep 5' and 'Sleep 10' are almost the same - they are both 'under the radar'.

Add these two macros and repeat the above after inserting 'StartHiResSleep' before the code.

Code: Select all

#Macro StartHiResSleep
  Scope
    Dim As TIMECAPS tc
    TimeGetDevCaps( @tc, SizeOf(tc) )
    TimeBeginPeriod(tc.wPeriodMin)
  End Scope
  Sleep (16,1) ' Tests have shown that the new resolution will not 'bite' until next 'old' tick
#EndMacro
 
#Macro StopHiResSleep
  Scope
    Dim As TIMECAPS tc
    TimeGetDevCaps( @tc, SizeOf(tc) )
    TimeEndPeriod(tc.wPeriodMin)
  End Scope
  Sleep (2,1) ' Tests have shown that the new resolution will not 'bite' until next 'old' tick
#EndMacro

with one run giving:-

Code: Select all

 1:  2.00102247612044
 2:  2.567854294337701
 3:  3.30007026026391
 4:  4.145359256598313
 5:  5.945308691676088
 6:  6.137092842784409
 7:  7.982858156589279
 8:  8.526782035001235
 9:  9.769258383595769
 10:  10.24697272998032
 11:  11.10085220331891
 12:  12.10237613996412
 13:  13.78799540164488
 14:  14.15605894053718
 15:  15.90341789244487
 16:  16.92407833956344
 17:  17.95360862912898
 18:  18.8075579439555
 19:  19.83981204294727
 20:  20.93799630960125
 ...
 ...
100:  100.1176825546068

So, we now have Sleep with a resolution of 1ms and when we fine tune we know we are fine tuning.

If we forget to employ StopHiResSleep, Windows will do it for us at the end of the application session.
MrSwiss
Posts: 3125
Joined: Jun 02, 2013 9:27
Location: Switzerland

Re: Timer & Threads

Postby MrSwiss » Mar 30, 2017 16:38

Dinosaur wrote:I made one mistake in making file & port Handles Bytes, and it caused some problems initializing the port.
Once I changed it to Integer, all was good.
I agree that a Byte can't handle that:
however, Integer is IMO, to large (FBC 64 = LongInt = 64bit), a UShort should do ...
Port = 0 to 65535 (exactly UShort range)
FreeFile return is Long, but it never returns a value larger, than a UShort (see manual).

@deltarho[1859], Dinosaur is on Linux, not Windows ...
deltarho[1859]
Posts: 1786
Joined: Jan 02, 2017 0:34
Location: UK

Re: Timer & Threads

Postby deltarho[1859] » Mar 30, 2017 17:00

Dinosaur is on Linux, not Windows ...

Oops <smile>

So, why isn't 'usleep' being used? I am on thin ice here as I am not a Linux user.
fxm
Posts: 9012
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: Timer & Threads

Postby fxm » Mar 30, 2017 17:00

deltarho[1859] wrote:Add these two macros and repeat the above after inserting 'StartHiResSleep' before the code.

To use these, I should rather insert:

Code: Select all

#include "windows.bi"
#include "win/mmsystem.bi"
at the top of program.
deltarho[1859]
Posts: 1786
Joined: Jan 02, 2017 0:34
Location: UK

Re: Timer & Threads

Postby deltarho[1859] » Mar 30, 2017 17:40

To use these, I should rather insert:

As I did in the code I took them from. I didn't have my afternoon nap today. <smile>. I got used to only using Include "Win32API.inc" at the top of my code without further ado. After 13 years some habits will be difficult to break.
dodicat
Posts: 5814
Joined: Jan 10, 2006 20:30
Location: Scotland

Re: Timer & Threads

Postby dodicat » Mar 30, 2017 20:55

Regarding sleep 1
The duration depends on your hardware.
As I remember it can be either of two values.

Sleep 1 (at least) is essential in any freebasic graphics loop.
Either value will suffice for this.

But sleep 1 is not needed in an api message loop,( it is slow anyway).

sleep 100 just stabs at slowing down framerate and because of the above, it is just an inaccurate stall.
Timer is unique, so sleep can be adjusted via the timer to be of use.
I have used this regulation for a while to specify a framerate for fb graphics.
Use the mouse on the slider circle:

Code: Select all


 #define map(a,b,x,c,d) ((d)-(c))*((x)-(a))/((b)-(a))+(c)
 #define inbox (mx>125) and (mx<475)' and (my>300) and (my<350)
 #define incircle(cx,cy,radius,x,y) (cx-x)*(cx-x) +(cy-y)*(cy-y)<= radius*radius
 
 #macro display
    fr=map(125,475,cx,10,60)
    screenlock
    cls
    draw string(20,20),"Actual Framerate     = " &fps
    draw string(20,40),"Requested Framerate  = " &fr
    draw string(20,60),"Sleeping time        = " &sleeptime
    draw string(100,280),"10"
    draw string(480,280),"60"
    draw string(250,280),"<--slider-->"
    angle+=.1
    drawline(600,200,.2*sin(angle)-pi/2,300,4)
    line(100,300)-(500,350),2,bf
    circle(cx,cy),25,5,,,,f
    screenunlock
    sleep sleeptime,1
#endmacro

#macro mouse
Dim As Long x=mx,y=my,dx,dy
While mb = 1
    Display
    Getmouse mx,my,,mb
    If inbox Then
        If mx<>x Or my<>y  Then
            dx = mx - x
            dy = my - y
            x = mx
            y = my
            cx=x+dx
            if cx<125 then cx=125
            if cx>475 then cx=475
        End If
    End If
Wend
#endmacro

sub drawline(x as long,y as long,angle as single,length as long,col as ulong)
    var x2=x+length*cos(angle)
    var y2=y-length*sin(angle)
     line(x,y)-(x2,y2)
     circle(x2,y2),10,6,,,,f
end sub

Function Regulate(Byval MyFps As long,Byref fps As long) As long
    Static As Double timervalue,lastsleeptime,t3,frames
    frames+=1
    If (Timer-t3)>=1 Then t3=Timer:fps=frames:frames=0
    Var sleeptime=lastsleeptime+((1/myfps)-Timer+timervalue)*1000
    If sleeptime<1 Then sleeptime=1
    lastsleeptime=sleeptime
    timervalue=Timer
    Return sleeptime
End Function

screen 19
dim as integer mx,my,mb
dim as long cx=125,cy=325,fps,fr,sleeptime
dim as single angle,pi=4*atn(1)
do
    getmouse mx,my,,mb
   
    display
   
    if incircle(cx,cy,25,mx,my) and mb=1 then
        mouse
        end if
   
    sleeptime= regulate(fr,fps)
   
    loop until len(inkey)
Dinosaur
Posts: 1186
Joined: Jul 24, 2005 1:13
Location: Searcy AR USA
Contact:

Re: Timer & Threads

Postby Dinosaur » Mar 30, 2017 21:50

Hi All

Concluded that Sleep in my usbController section of code is a complete waste of Time (no pun intended)

I tested as per deltaro and found on my cpu the same inaccuracies.
On top of that, by removing the Sleeps in that part of the code, the stability of output pulse
increased, so that it now sits at about 0.5 mSec.

I should have learned this lesson long ago, both marcov & michaelw said as much in previous posts.(relating to my GUI kind of code)
Sleep is good if you want delays > 1/18.5 sec , for me that is already to long.

Also there is no penalty for me not to use sleep's.
Ok, if the quad cpu unit get a bit warmer, it won't worry either, as they operate in a constant 2 degree C (36 F) room.

Once again many thanks to ALL the contributors.

Regards

Return to “General”

Who is online

Users browsing this forum: Bing [Bot] and 1 guest