Timer & Threads

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

Re: Timer & Threads

Post by fxm »

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: 89
Joined: Nov 30, 2006 13:35
Location: UK

Re: Timer & Threads

Post by tinram »

70 and 66.

You guys rock!

Thank you fxm for all the help and coding wisdom you give to others.
Dinosaur
Posts: 1478
Joined: Jul 24, 2005 1:13
Location: Hervey Bay (.au)

Re: Timer & Threads

Post by Dinosaur »

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: 3910
Joined: Jun 02, 2013 9:27
Location: Switzerland

Re: Timer & Threads

Post by MrSwiss »

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: 1478
Joined: Jul 24, 2005 1:13
Location: Hervey Bay (.au)

Re: Timer & Threads

Post by Dinosaur »

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: 3910
Joined: Jun 02, 2013 9:27
Location: Switzerland

Re: Timer & Threads

Post by MrSwiss »

@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: 1478
Joined: Jul 24, 2005 1:13
Location: Hervey Bay (.au)

Re: Timer & Threads

Post by Dinosaur »

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: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: Timer & Threads

Post by deltarho[1859] »

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: 3910
Joined: Jun 02, 2013 9:27
Location: Switzerland

Re: Timer & Threads

Post by MrSwiss »

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: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: Timer & Threads

Post by deltarho[1859] »

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
Moderator
Posts: 12082
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: Timer & Threads

Post by fxm »

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: 4292
Joined: Jan 02, 2017 0:34
Location: UK
Contact:

Re: Timer & Threads

Post by deltarho[1859] »

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: 7976
Joined: Jan 10, 2006 20:30
Location: Scotland

Re: Timer & Threads

Post by dodicat »

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: 1478
Joined: Jul 24, 2005 1:13
Location: Hervey Bay (.au)

Re: Timer & Threads

Post by Dinosaur »

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
Post Reply