B4J Question My timer won't tick?

Mark Read

Well-Known Member
Licensed User
Longtime User
After using the solution from Klaus here, to create a list of animations and then call them, the tick event of my timer is not called any more. I am sure I have missed something fundemental but I cannot see it.

It was working when I called the subs on their own but not with callsub. What have I missed?

B4X:
'Non-UI application (console / server application)
#Region  Project Attributes
    #CommandLineArgs:
    #MergeLibraries: True
#End Region

Sub Process_Globals
    Private controller As GpioController
    Private dataPin As GpioPinDigitalOutput     'serial data input
    Private latchPin As GpioPinDigitalOutput     'memory clock input STCP (latch) copies inputs to outputs when toggled
    Private clockPin As GpioPinDigitalOutput     'shift register clock input SHCP
    Dim Value As Byte
    Dim pause As Int
    Private aTimer As Timer
    Dim l(3) As GpioPinDigitalOutput
    Dim lState(3) As Boolean
    Dim b(2) As Byte
    Dim BitOrder As Boolean=True
   
    'Define an animation state type
    Type State (Byte1 As Byte, Byte2 As Byte, LayerBottom As Boolean, LayerMiddle As Boolean, LayerTop As Boolean)
    Dim states As List
    Dim st As State
   
    Dim aa, bb, cc As Int
    Dim Clockwise As Boolean=True
   
    Dim Alist As List            'List of animations
    Dim CurrentAnimation As Int=0
End Sub

Sub AppStart (Args() As String)
    'Set delay in ms   
    pause=100
   
    'Initialise all pins
    controller.Initialize
    dataPin.Initialize(2, False)        'Pin 13, GPIO 2
    latchPin.Initialize(4, False)        'Pin 16, GPIO 4
    clockPin.Initialize(3, False)        'Pin 15, GPIO 3
    l(0).Initialize(23,False)            'Bottom layer
    l(1).Initialize(21,False)            'middle layer
    l(2).Initialize(22,False)            'top layer
    aTimer.Initialize("aTimer",5000)    '5 seconds for each animation
    states.Initialize
    Alist.Initialize
           
    'Turn off ALL leds    
    ClearLED
   
    Alist.Add("Wheel")
    Alist.Add("FrontRoll")
    Alist.Add("SideRoll")
       
    CallSub(Me, Alist.Get(CurrentAnimation))       
    StartMessageLoop
End Sub

Sub SideRoll
    'Create and add the animation states
    states.Clear
    states.Add(CreateState(7, 0, True, False, False))
    states.Add(CreateState(7, 0, False, True, False))
    states.Add(CreateState(7, 0, False, False, True))
    states.Add(CreateState(56, 0, False, False, True))
    states.Add(CreateState(192, 1, False, False, True))
    states.Add(CreateState(192, 1, False, True, False))
    states.Add(CreateState(192, 1, True, False, False))
    states.Add(CreateState(56, 0, True, False, False))
    states.Add(CreateState(7, 0, True, False, False))
    aTimer.Enabled=True
    Animate
End Sub

Sub FrontRoll
    'Create and add the animation states
    states.Clear
    states.Add(CreateState(73, 0, True, False, False))
    states.Add(CreateState(73, 0, False, True, False))
    states.Add(CreateState(73, 0, False, False, True))
    states.Add(CreateState(146, 0, False, False, True))
    states.Add(CreateState(36, 1, False, False, True))
    states.Add(CreateState(36, 1, False, True, False))
    states.Add(CreateState(36, 1, True, False, False))
    states.Add(CreateState(146, 0, True, False, False))
    states.Add(CreateState(73, 0, True, False, False))
    aTimer.Enabled=True
    Animate
End Sub

Sub Wheel
    'Create and add the animation states
    states.Clear
    states.Add(CreateState(17, 1, True, False, False))
    states.Add(CreateState(146, 0, True, False, False))
    states.Add(CreateState(84, 0, True, False, False))
    states.Add(CreateState(56, 0, True, False, False))
    states.Add(CreateState(17, 1, False, True, False))
    states.Add(CreateState(146, 0, False, True, False))
    states.Add(CreateState(84, 0, False, True, False))
    states.Add(CreateState(56, 0, False, True, False))
    states.Add(CreateState(17, 1, False, False, True))
    states.Add(CreateState(146, 0, False, False, True))
    states.Add(CreateState(84, 0, False, False, True))
    states.Add(CreateState(56, 0, False, False, True))
    states.Add(CreateState(17, 1, False, False, True))
    aTimer.Enabled=True
    Log("Timer on")
    Animate
End Sub

Sub Animate
    If Clockwise Then
        aa=0
        bb=states.Size-1
        cc=1
    Else
        aa=states.Size-1
        bb=0
        cc=-1
    End If
   
    For i=aa To bb Step cc
        st=states.Get(i)
        b(0)=st.Byte1
        b(1)=st.Byte2
        lState(0)=st.LayerBottom
        lState(1)=st.LayerMiddle
        lState(2)=st.LayerTop
        MultiWrite(b,lState)
        Delay(pause)
    Next
   
    Clockwise=Not(Clockwise)
    Animate
End Sub

Sub aTimer_tick
    Log("Clock tick")
    Clockwise=True
    aTimer.Enabled=False
   
    CurrentAnimation=CurrentAnimation+1
    If CurrentAnimation=Alist.Size Then CurrentAnimation=0
   
    CallSub(Me, Alist.Get(CurrentAnimation))
   
End Sub

Sub MultiWrite(ByteValue() As Byte, levelState() As Boolean)
    latchPin.State=False
   
    'Activate layer
    l(0).State=levelState(0)
    l(1).State=levelState(1)
    l(2).State=levelState(2)
   
    For i =  ByteValue.Length - 1 To 0 Step -1
       Value = ByteValue(i)   
       ShiftOut
    Next
    latchPin.State=True
End Sub

Sub ShiftOut
  Dim aa, bb, cc As Int

  If BitOrder Then
    aa = 7
    bb = 0
    cc = -1
  Else
    aa = 0
    bb = 7
    cc = 1
  End If
  For i = aa To bb Step cc
    clockPin.State=False
    If getBits(Value,i,i) = 1 Then
        dataPin.State=True
    Else
        dataPin.State=False
    End If
    clockPin.State=True
    dataPin.State=False
  Next
  clockPin.State=False
End Sub

Sub SendBit(BitState As Boolean)    'not used
    clockPin.State=False
    dataPin.State=BitState            'Set data bit
    clockPin.State=True                'set shift register clock input   
    latchPin.State=True                'Toggle latch
    latchPin.State=False
End Sub

Sub ClearLED                        'Clear two bytes
    For i=0 To 15
        SendBit(False)
    Next
End Sub

Sub Delay (DurationMs As Int)
    Dim target As Long = DateTime.Now + DurationMs
     Do While DateTime.Now < target
     Loop
End Sub

Sub getBits(v As Int,bitStart As Int,bitEnd As Int) As Int            'Called in ShiftOut
    Dim res As Int = 0
    For a = bitStart To bitEnd
        res = res + Power(2,a)     
    Next
    Return (Bit.ShiftRight(Bit.And(v,res),bitStart))
End Sub   

Sub CreateState(Byte1 As Byte, Byte2 As Byte, LayerBottom As Boolean, LayerMiddle As Boolean, LayerTop As Boolean) As State
   Dim st As State
   st.Initialize
   st.Byte1=Byte1
   st.Byte2=Byte2
   st.LayerBottom = LayerBottom
   st.LayerMiddle = LayerMiddle
   st.LayerTop = LayerTop
   Return st
End Sub
 

klaus

Expert
Licensed User
Longtime User
It is difficult to answer without testing.
But, two points:
1. You are using a Timer and a Delay routine, I'm afraid that this will cause trouble.
You should use only Timers and / or consider to use CallSubPlus with delays.
2. The Sub Animate calls itself at its end, do you really want this?
 
Upvote 0

Mark Read

Well-Known Member
Licensed User
Longtime User
2. The Sub Animate calls itself at its end, do you really want this?

Yes, as the direction is reversed.

What I don't understand is the code worked when I call "Wheel" on its own and stop on timer tick. The problem seems to be the callsub.
 
Upvote 0

Mark Read

Well-Known Member
Licensed User
Longtime User
Normally not, that was the reason for the timer, or at least that was my thinking.

The delay sub is only 100ms as otherwise the code is too fast. The Do..Loop will tie up processing but that should not be a problem. The timer should keep on running in the background.

I am really stumped on this one.

Could it be that the system kills the Timer?
 
Upvote 0

Mark Read

Well-Known Member
Licensed User
Longtime User
The Animate Sub should be stopped on the Timer Tick event.

Have you tried to add a Log in the Animate routine?

Not sure why I should do this. I can see that it is working correctly. The animations are shown on a 3 x 3 x 3 LED Cube.

The Timer Tick event should stop the current animation. The List of States is cleared and a new one created. Then Animate is restarted with the new values.

I know it is difficult to help when the code cannot be tested. I am also at home at present and my RPi is at work.
 
Upvote 0

Mark Read

Well-Known Member
Licensed User
Longtime User
I have tried to simulate the problem with a simply code: See what happens if the Animate command is commented out in the Animate Sub. The Timer and the Delay do not seem to interfere with each other.

B4X:
#Region  Project Attributes
    #MainFormWidth: 600
    #MainFormHeight: 400
#End Region

Sub Process_Globals
    Private fx As JFX
    Private MainForm As Form
    Dim aTimer As Timer
   
    Dim Alist As List            'List of animations
    Dim CurrentAnimation As Int=0
   
    Dim TestString As String="abcdefghijklmnopqrstuvwxyz"
    Dim aa, bb, cc As Int
End Sub

Sub AppStart (Form1 As Form, Args() As String)
    MainForm = Form1
    MainForm.SetFormStyle("UNIFIED")
    'MainForm.RootPane.LoadLayout("Layout1") 'Load the layout file.
    MainForm.Show
   
    Alist.Initialize
   
    aTimer.Initialize("aTimer",1000)
    aTimer.Enabled=False
   
    Alist.Add("Left2Right")
    Alist.Add("Right2Left")
    Alist.Add("Every2Char")
   
    'CallSub(Me, Alist.Get(CurrentAnimation))
    Left2Right
   
End Sub

Sub Left2Right
    Log("L2R")
    aa=0
    bb=TestString.Length-1
    cc=1
    aTimer.Enabled=True
    Animate(aa,bb,cc)
End Sub

Sub Right2Left
    Log("R2L")
    aa=TestString.Length-1
    bb=0
    cc=-1
    aTimer.Enabled=True
    Animate(aa,bb,cc)
End Sub

Sub Every2Char
    Log("E2C")
    aa=0
    bb=TestString.Length-1
    cc=2
    aTimer.Enabled=True
    Animate(aa,bb,cc)
End Sub

Sub Animate(a As Int, b As Int, c As Int)
    For i=a To b Step c
        Log(TestString.CharAt(i))
        Delay(100)
    Next
    'Animate(aa,bb,cc)
End Sub

Sub aTimer_Tick
    aTimer.Enabled=False
      
    CurrentAnimation=CurrentAnimation+1
    If CurrentAnimation=Alist.Size Then CurrentAnimation=0
  
    CallSub(Me, Alist.Get(CurrentAnimation))
End Sub

Sub Delay (DurationMs As Int)
    Dim target As Long = DateTime.Now + DurationMs
     Do While DateTime.Now < target
     Loop
End Sub
 

Attachments

  • test2.zip
    1.1 KB · Views: 281
Upvote 0

Mark Read

Well-Known Member
Licensed User
Longtime User
Yes I see that too. So you were right, :D. The "recursion" kills the timer, although other subs are called. I still do not understand why - sorry.

Going back to the original code in post#1, If I set the whole code in the Animate sub (deleting the recursive call) in a For..Next Loop and let it run say, 10 times, then the Timer should be able to break out of the loop. Or am I building myself in a box again?

What do you think?
 
Upvote 0

klaus

Expert
Licensed User
Longtime User
The "recursion" kills the timer,...
I don't know exactly how the Timer work, if they work with interupts.
But, anyway the timer will not clear the pending stack or routines.
And for me, a recursive routine without any return goes into an infinit loop.
 
Upvote 0

Mark Read

Well-Known Member
Licensed User
Longtime User
Then my last question is simple: How can I break out of the animation loop without using some kind of counter?
 
Upvote 0

Mark Read

Well-Known Member
Licensed User
Longtime User
I would also remove the Delay routine as it disturbs the thread.

I can't remove the delay, otherwise the code is too fast! It looks as if the LEDs are all turned on at once!
 
Upvote 0
Top