Arduino Implementation of Interpreted/Pre-Compiled Proprietary Language

Discussion in 'Embedded Systems and Microcontrollers' started by djsfantasi, Jul 12, 2019.

  1. djsfantasi

    Thread Starter AAC Fanatic!

    Apr 11, 2010
    Animatron-6 was software I developed to control my animatronic, presented here in 2011. It started as an Excel spreadsheet, with a lookup table to match a user-friendly name to an arcane servo command. The spreadsheet constructed a Windows Command Line batch file, which was run to control the animatronic connected to a laptop via . It then evolved into a compiled Basic program, which interprets a proprietary language I designed, called A-Code, for animatronic code. This program also ran on a laptopThis first program has evolved into a FreeBASIC compiler of the A-Code, plus a run-time module that runs on an Arduino inside the animatronic.


    Animatron is a simple program that reads descriptive phrases and translates them into servo commands to instruct the animatronic figure what to do. As built, it depends on a serial servo controller (like the Lynxmotion SSC-32) for the final motion. A version using the SparkFun Servo/Motor Controller is under development.

    For example, “Eyes Open” sends the SSC-32 command “#2P750” through the serial connection.

    See, I think the former is much easier to remember than the latter. Similarly, we define the phrase “Eyes Closed”, as well as approximately sixty two other phrases. These description and value pairs are referred to as a move object.

    Animatron has another feature. It can play these moves in a user defined sequence and these sequences can be saved to be called again or from another part of the program. These sequences are referred to as scripts. To do this, it has defined several actions to be performed with the move objects. There are move commands and others, too. There are currently sixteen action commands plus a handful of special cases, which are discussed in the following posts.

    These components form a group, of an action, object and value. Each line represents one line in a script. Multiple lines can be combined to form a player (a script executing in parallel with other scripts.

    For example, we have Eyes Closed and Eyes Open. Introducing one other command “ScriptPause, nnn” (which pauses for nnn milliseconds), we can write a script to blink the eyes for three times, pausing three seconds between blinks.

    ScriptPause 100
    PlayMove, EyesOpen,30
    Scriptpause, 3000

    Note the “30” at the end of the line. This value gives the servo time to respond and can be tuned per servo brand, by experimentation. Another quick script flaps its wings:

    PlayMove, Arms Up, 30
    ScriptPause, 75
    PlayMove, Arms Down, 30
    ScriptPause 75
    EndScript, Flappy1

    Playscript, Flappy1
    Playscript, Flappy1
    Playscript, Flappy1
    EndScript, Flappy​

    Note that this is two scripts. One to define flapping the wing once and a second to flap them three times.

    The photo below is a portion of a larger script or the show script used to control the penguin animatronic. Note that several scripts are combined for various actions, such as blinking, slowly blinking, moving its arms and speaking.


    Action Commands
    First, there are currently 16 commands which the interpreter recognizes. A summary of these commands follows. (You can also get a sense of the history of adding features, by the position in the list of each command ) If you don’t want to get bored right now with the minutiae of the commands, skip forward to the program description to see how this all works.
    Command Description
    PlayMove send commands to controller
    PlayScript execute script in parallel; script must be defined in same file
    StartScript define new script; main script MUST be last in file
    EndScript end of script routine definition
    JumpTo goto command; label MUST exist (is not checked for)
    Label definition of label used in "JumpTo" command
    SyncPoint definition of a script which will synchronize with other script(s)
    EndSync definition of step in script at which to wait for synchronization
    ScriptPause "Pause" or "Delay" command; pauses execution for n milliseconds
    Say play external sound file; will cause servo defined in "scbase" (e.g. mouth servo) to synchronize to sound
    RandomMove Randomly perform one of the following n actions
    RandomPause Pause some random time between the two times specified on the command
    CallScript Call a script rather than running it in parallel in its own player
    EndWait network command to clear pauses in execution at a "NetWait" point; not used in scripts
    NetWait Define a point in script at which a network command can cause a pause in execution
    OneOnly define a command that will exit a script if it is already running in a separate player
    ActionSeq loop sequentially through command group in a script.​

    Detailed Descriptions of Commands
    The following sections provide short descriptions of the commands and how the program processes them. Note the following jargon is used to describe the commands:
    • Script Action is the action of the command.
    • ScriptDescription is the object of the action, or in some cases a value used in the action.
    • ScriptOption is another value field, used as input to the action.
    • ScriptStack is a generated value – normally containing the address of the next instruction to be executed.
    Each of these items are an array, containing the compiled A-Code token values (see the next section on Compiling A-Code into Tokens). The next logicaL development step is to define them as a UDT, but the program works and the motivation to make that change is low,

    ScriptPause is a simple command, placed in this section because it is used to pause for long times (relatively speaking) between commands. Its ScriptOption value is the number of microseconds to pause, and is added to the current timer value and stared in the PlayerEndWait() cell. As each player is given its time slice, if the current time is less than the end wait time, nothing is done and execution passes to the next script/player/process.

    PlayMove contains an index to the move table, whose corresponding string is sent to the serial port to control the animatronic’s servos. Its optional value is used to extend the time before the player executes again, to allow the servo to move and other processes time to execute as well. The next script step is set to the value of ScriptStack().

    PlayScript creates another player. As described, PlayerStep() is set to the index value contained in ScriptDescription() and the PlayerEndWait() is initially set to 0. Note that there is no attempt to keep the array elements sequential. There may be unused player entries as scripts finish between active players. A PlayerStep() value of 0 indicates an unused or inactive player.

    CallScript is similar to PlayScript, however the script is not executed in parallel or in a player... The commands therein are executed sequentially within the current script. This is accomplished by using the ScriptStack() value of the EndScript command to point back to the current script.

    RandomMove takes a parameter which represents a number of moves to select from. Then, the interpreter using a random number generator to select one of the next number of commands. Here again, the ScriptStack() value is used, as each of the following commands have the value here of the next command after the block of randomly selected commands.

    RandomPause uses a random number generator to pause somewhere between the minimum and maximum pause time. This is a special case where the ScriptDescription() contains something other than an index to another piece of data, but a time specified in milliseconds.

    ActionSeq is not so much a random command, but in reality quite the opposite. It is used to execute a block of commands in sequence each time the script is called. Hence, if there are four commands specified, the first time the script is called, the first command in the block is run; the fourth time the script is called, the fourth command is run; the fifth time the script is called, the first command is run again. I use this feature to have the animatronic have a conversation, over a random period of time. Here again, the ScriptStack() value of the ActionSeq command is used to keep track of which command to run next.

    The Say command uses a SparkFun Music Maker shield to play a .wav file in the background. The animatronic takes the audio from the shield and feeds it into custom circuitry on board, which detects and amplifies the sound envelope. The value of this envelope is used by the software to move the mouth in synchronization with the sound.

    The following commands, at this point, hopefully are self-explanatory.
    • StartScript
    • EndScript
    • JumpTo
    • Label

    Compiling "A-Code" into Tokens
    Currently, I have a Windows FreeBASIC program that reads the A-Code "show" or script, and outputs a tokenized version. It first reads in the Animatronic specific RC Servo commands. These arrays are referred to as the "Move Table". Each line in the table contains a user friendly name, like "OpenEyes", and the specific command used by the servo controller, such as “#2P750”. These commands are then used while parsing the A-Code. The same file is also included in the Arduino Animatron program, where the actual servo commands are used. The picture below is a screenshot of the move table for the penguin animatronic.


    The parsing of commands identify indices to the move table, location of labels and scripts by an array index. It also identifies any and all constant values. Delay per command in ms, hard time delays, limits for random time delays, limits for random command execution...

    Once the parsing and "compiling" is complete, the values are written out to a text file for execution by the run time module... The program is name ACC.bas, for A-Code Compiler.
    Code (Text):
    1. #Include Once ""
    2. #include Once "win/"
    3. '#Include Once ""
    4. '        ===========================================================================
    5. '        ===========================================================================
    6. ' ACC.bas - a program to parse a scripting language for controlling an
    7. '                        animatronic figure (i.e., Peter Penguin) via an SSC-32
    8. '                        servo controller. The scripting language has been named a-code
    9. '                        for "animatronic code", and implements the functions defined herein.
    10. '                        The output of this parser/compiler is token code in C code format,
    11. '                        for inclusion into the run time module implemented on an Arduino
    12. '                        Mega 2560
    13. '    January, 2015
    14. '        ===========================================================================
    15. '        ===========================================================================
    17. Declare Function FindIt (array() As String, What As String) As Integer
    18. Declare Function DisplayStep (i As Integer, j As Integer) As String
    19. Declare Function NextStep(myStep As Integer) As Integer
    23. ReDim MoveDescription(0) As String ' Move lookup table: description of move, e.g. "Move Eyes Left"
    24. ReDim MoveCommand(0) As String ' actual servo move command, e.g. "#0P2000 #1P2000 T100"
    25. Dim MoveIndex As Integer =0 ' array index of Move arrays
    27. ReDim LabelDescription(0) As String    ' Label lookup table; name of label or script
    28. ReDim LabelScriptIndex(0) As Integer ' index to step after label in script arrays
    29. Dim LabelIndex As Integer =0 ' array index for Label arrays
    31. ReDim PlayerStep(0) As Integer        ' Player properties: index to step in script to execute next
    32. ReDim As Double PlayerEndWait(0)        ' array of time before next command is issued, by player
    33. PlayerEndWait(0)=0
    35. Dim As Double SoundEndWait=0            ' timer value before next check of ADC for voice/sound input, ~12ms from last test.
    37. Dim Player As Integer =0
    39. ReDim ScriptAction(0) As Integer    ' Script properties: action to take
    40. ReDim ScriptDescription(0) As Integer ' Index to servo move table
    41. ReDim ScriptOption(0) As Integer ' value for action, i.e. pause length after command is issued
    42. ReDim ScriptStack(0) As Integer ' stack for saved script addresses (could be used for other purposes)
    43. Dim ScriptStep As Integer =0, MainScript As Integer =0, EndMain As Integer =0
    44. Dim Shared AdjustStep As Integer =1
    45. Dim ErrorFound As Integer =0, WaitName As Integer =0
    47. Dim As Integer Selections ' variable to store the possible number of random moves from which to select
    48. Dim As Integer minPause, maxPause, timePause
    50. ReDim SyncPoints(0) As ULongInt ' flags for sync points; bit encoded by associated players
    51. Dim SyncPointStatus As ULongInt =0
    52. Dim SyncPointIndex As Integer =0
    54. Dim IndexPlayers As Integer = 0, MaxPlayers As Integer =0
    55. Dim ScriptIndex As Integer  = 0,  NotDone    As Integer =-1 ' script player definitions
    57. ReDim LoopStack(0) As Integer, LoopMaximum(0)As Integer
    58. Dim As Integer LoopIndex=0, RepeatEndIndex=0, RepeatIndex=0
    59. Const As Integer RepeatInitialIndex=0
    61. 'Control variables
    62. Dim As String ControlKey
    63. Dim SingleStep As Integer = 0
    64. Const PauseKey="\"
    65. Const As String cr=Chr(13), lf =Chr(10),  esc=Chr(27)
    66. Const As String crlf=cr+lf
    68. ' Prepare Show
    69. Dim CommPort As String  ' serial port connected to servo controller (SSC-32)
    70. Dim Animatron As String ' name of file containing mapping between move descriptions and SSC-32 commands
    71. Dim ShowName As String  ' name of file containing a script for a given "show"
    72. Dim ShowCode As String
    74. Const dfltMoves = "peter.csv"
    75. Const dfltScript = "PeterLive5"
    77. ' short description to servo controller (SSC-32) commands mappings
    78. Print "Character/Move file name [";dfltMoves;"]:";
    79. Input Animatron
    80. If Animatron = "" Then Animatron = dfltMoves
    81. Open Animatron For Input As #1
    83. ' script file
    84. Print "Show file name [";dfltScript;"]: ";
    85. Input ShowName
    86. If ShowName = "" Then ShowName = dfltScript
    87. ShowCode=ShowName+".h"
    88. ShowName=ShowName+".csv"
    91. '        ===========================================================================
    92. '        ===========================================================================
    93. '         Define Moves (lookup table)
    94. '        ===========================================================================
    95. '        ===========================================================================
    97. ' read in short description and corresponding SSC-32 command
    98. Dim Move As String , CtrlrCmd As String
    99. Do While Not Eof(1)And ErrorFound =0
    100.     Input #1, Move, CtrlrCmd
    101.     If UCase(Move) = "END" Then Exit Do
    102.     MoveIndex += 1
    103.     ReDim Preserve MoveDescription(MoveIndex)As String, MoveCommand(MoveIndex)As String
    104.     MoveDescription(MoveIndex) = UCase(Move)
    105.     MoveCommand(MoveIndex) = CtrlrCmd+cr    
    106. Loop
    107. Close #1
    111. '        ===========================================================================
    112. '        ===========================================================================
    113. '         Define Script(s)
    114. '        ===========================================================================
    115. '        ===========================================================================
    117. Dim Action As String, Description As String, OptionValue As Integer, LocalIndex As Integer
    118. Const PlayMove As Integer     =0 ' send commands to SSC-32
    119. Const PlayScript As Integer =1 ' execute script in parallel; script must be defined in same file
    120. Const StartScript As Integer=2 ' define new script; main script MUST be last in file
    121. Const EndScript As Integer  =3 ' end of script routine definition
    122. Const JumpTo As Integer     =4 ' "goto" command; label MUST exist (is not checked for)
    123. Const Label As Integer         =5 ' definition of label used in "JumpTo" command
    124. Const SyncPoint As Integer  =6 ' definition of scripts which will synchronize with other script(s)
    125. Const EndSync As Integer     =7 ' definition of step in script at which to wait for synchronization
    126. Const ScriptPause As Integer=8 ' "Pause" or "Delay" command; pauses execution for n milliseconds
    127. Const Say As Integer=9 ' play external sound file; will cause servo defined in "scbase" (e.g. mouth servo) to synchronize to sound
    128. Const RandomMove As Integer=10 ' Randomly perform on of the following 'n' actions; n is the option value
    129. Const RandomPause As Integer=11 ' Pause some random time between the two times specified on the command
    130. Const CallScript As Integer=12 ' Call a script rather than running it in parallel in its own player
    131. Const EndWait As Integer=13  ' network command to clear pauses in execution at a "NetWait" point; not used in scripts
    132. Const NetWait As Integer=14  ' Define a point in script at which a network command can cause a pause in execution
    133. Const OneOnly As Integer=15  ' define a command that will exit a script if it is already running in a separate player.
    134. Const ActionSeq As Integer=16 ' loop sequentially through command group in a script.
    135. Const EndRepeat As Integer=17 ' endpoint of Repeat and EndRepeat commands (both not tokenized; only termination is)
    137. Open ShowName For Input As #1
    139. Print "+----------------------------+"
    140. Print "| Starting to compile script |"
    141. Print "+----------------------------+"
    142. Print "Press any key to start";
    143. Sleep
    146. Do While Not Eof(1) And ErrorFound =0
    147.     Input #1, Action, Description, OptionValue
    148.     Print Action, Description, OptionValue
    149.     If Left(Action,1)=":" Then Action=":" ' special case of a commented line
    151.     Select Case UCase(Action)
    153.         Case ":"
    155.         Case "ACTIONSEQ"
    156.             ScriptStep +=1
    157.             ReDim Preserve ScriptAction(ScriptStep) As Integer, ScriptDescription(ScriptStep) As Integer, ScriptOption(ScriptStep) As Integer, ScriptStack(ScriptStep) As Integer
    158.             AdjustStep=OptionValue
    159.             ScriptAction(ScriptStep) = ActionSeq
    160.             ScriptDescription(ScriptStep)=ScriptStep+1
    161.             ScriptOption(ScriptStep) = ScriptStep+OptionValue
    162.             ScriptStack(ScriptStep)=ScriptStep+1
    164.         Case "CALLSCRIPT", "JUMPTO"
    165.             ScriptStep+=1
    166.             ReDim Preserve ScriptAction(ScriptStep) As Integer, ScriptDescription(ScriptStep) As Integer, ScriptOption(ScriptStep) As Integer, ScriptStack(ScriptStep) As Integer
    167.             If UCase(Action) = "CALLSCRIPT" Then
    168.                 ScriptAction(ScriptStep)=CallScript
    169.             Else
    170.                 ScriptAction(ScriptStep)=JumpTo
    171.             EndIf
    172.             LocalIndex = FindIt(LabelDescription(), Description)
    173.             If LocalIndex < 0 Then
    174.                 Print "Cannot find script or label ";Description
    175.                 ErrorFound=2
    176.                 Exit Do
    177.             Else
    178.                 ScriptDescription(ScriptStep) = LabelScriptIndex(LocalIndex)
    179.                 ScriptOption(ScriptStep) = OptionValue
    180.                 ScriptStack(ScriptStep)=NextStep(ScriptStep)
    181.             EndIf
    183.         Case "ENDREPEAT"
    184.             ScriptStep += 1
    185.             ReDim Preserve ScriptAction(ScriptStep) As Integer, ScriptDescription(ScriptStep) As Integer, ScriptOption(ScriptStep) As Integer, ScriptStack(ScriptStep) As Integer
    186.             ScriptAction(ScriptStep)=EndRepeat
    187.             ScriptDescription(ScriptStep)=RepeatInitialIndex
    188.             ScriptOption(ScriptStep)=LoopMaximum(LoopIndex)
    189.             ScriptStack(ScriptStep)=LoopStack(LoopIndex)
    190.             LoopIndex-=1
    192.         Case "ENDSCRIPT"
    193.             ScriptStep +=1
    194.             ReDim Preserve ScriptAction(ScriptStep) As Integer, ScriptDescription(ScriptStep) As Integer, ScriptOption(ScriptStep) As Integer, ScriptStack(ScriptStep) As Integer
    195.             ScriptAction(ScriptStep) = EndScript
    196.             ScriptOption(ScriptStep) = 0
    197.             ScriptStack(ScriptStep)=0
    198.             EndMain=ScriptStep
    200. '        Case "ENDSYNC" -- See "SYNCPOINT"
    202. '        Case "JUMPTO" -- See "CALLSCRIPT"
    204. '        Case "LABEL" -- See "STARTSCRIPT"
    206.         Case "NETWAIT"
    207.             LocalIndex=FindIt(LabelDescription(),Description)
    208.             If LocalIndex < 0 Then
    209.                 LabelIndex +=1
    210.                 LocalIndex=LabelIndex
    211.                 ReDim Preserve LabelDescription(LabelIndex) As String, LabelScriptIndex(LabelIndex)As Integer
    212.                 LabelDescription(LocalIndex) = Description
    213.                 LabelScriptIndex(LocalIndex)=-ScriptStep
    214.             EndIf
    215.             ScriptStep+=1
    216.             ReDim Preserve ScriptAction(ScriptStep) As Integer, ScriptDescription(ScriptStep) As Integer, ScriptOption(ScriptStep) As Integer, ScriptStack(ScriptStep) As Integer
    217.             ScriptAction(ScriptStep)=NetWait
    218.             ScriptDescription(ScriptStep)=LabelScriptIndex(LocalIndex)
    219.             ScriptOption(ScriptStep)=ScriptStep
    220.             ScriptStack(ScriptStep)=NextStep(ScriptStep)
    222. '        Case "ONEONLY" -- See "SYNCPOINT"
    224.         Case "PLAYMOVE"
    225.             ScriptStep +=1
    226.             ReDim Preserve ScriptAction(ScriptStep) As Integer, ScriptDescription(ScriptStep) As Integer, ScriptOption(ScriptStep) As Integer, ScriptStack(ScriptStep) As Integer
    227.             ScriptAction(ScriptStep) = PlayMove
    228.             LocalIndex = FindIt(MoveDescription(), Description)
    229.             If LocalIndex < 0 Then
    230.                 Print "Cannot find move ";Description
    231.                 ErrorFound=1
    232.                 Exit Do
    233.             Else
    234.                 ScriptDescription(ScriptStep) = LocalIndex
    235.             EndIf
    236.             ScriptOption(ScriptStep) = OptionValue
    237.             ScriptStack(ScriptStep)=NextStep(ScriptStep)
    239.         Case "PLAYSCRIPT"
    240.             ScriptStep+=1
    241.             ReDim Preserve ScriptAction(ScriptStep) As Integer, ScriptDescription(ScriptStep) As Integer, ScriptOption(ScriptStep) As Integer, ScriptStack(ScriptStep) As Integer
    242.             ScriptAction(ScriptStep)=PlayScript
    243.             LocalIndex = FindIt(LabelDescription(), Description)
    244.             If LocalIndex < 0 Then
    245.                 Print "Cannot find script ";Description
    246.                 ErrorFound=2
    247.                 Exit Do
    248.             Else
    249.                 ScriptDescription(ScriptStep) = LabelScriptIndex(LocalIndex)
    250.                 ScriptOption(ScriptStep) = OptionValue
    251.                 ScriptStack(ScriptStep)=NextStep(ScriptStep)
    252.             EndIf
    254. '        Case "RANDOMMOVE" -- See "SCRIPTPAUSE"
    256.         Case "RANDOMREPEAT"
    257.             LoopIndex +=1
    258.             ReDim Preserve LoopStack(LoopIndex)As Integer, LoopMaximum(LoopIndex) As Integer
    259.             RepeatEndIndex=Int(Rnd*(Int(OptionValue)-Int(Val(Description))))+Int(Val(Description))
    260.             LoopStack(LoopIndex)=NextStep(ScriptStep)
    261.             LoopMaximum(LoopIndex)=RepeatEndIndex
    263.         Case "RANDOMPAUSE"
    264.             ScriptStep +=1
    265.             ReDim Preserve ScriptAction(ScriptStep) As Integer, ScriptDescription(ScriptStep) As Integer, ScriptOption(ScriptStep) As Integer, ScriptStack(ScriptStep) As Integer
    266.             ScriptAction(ScriptStep) = RandomPause
    267.             minPause=Val(Description)
    268.             maxPause=OptionValue
    269.             If minPause > maxPause Then Swap minPause, maxPause
    270.             ScriptDescription(ScriptStep)=minPause
    271.             ScriptOption(ScriptStep)=maxPause
    272.             ScriptStack(ScriptStep)=NextStep(ScriptStep)
    274.         Case "REPEAT"
    275.             LoopIndex +=1
    276.             ReDim Preserve LoopStack(LoopIndex) As Integer, LoopMaximum(LoopIndex)As Integer
    277.             RepeatEndIndex=Val(Description)
    278.             LoopStack(LoopIndex)=NextStep(ScriptStep)
    279.             LoopMaximum(LoopIndex)=Val(Description)
    281.         Case "SAY"
    282.             ScriptStep +=1
    283.             ReDim Preserve ScriptAction(ScriptStep) As Integer, ScriptDescription(ScriptStep) As Integer, ScriptOption(ScriptStep) As Integer, ScriptStack(ScriptStep) As Integer
    284.             LocalIndex=FindIt(LabelDescription(),Description)
    285.             If LocalIndex < 0 Then
    286.                 LabelIndex =UBound(LabelScriptIndex)+1
    287.                 LocalIndex=LabelIndex
    288.                 ReDim Preserve LabelDescription(LabelIndex) As String, LabelScriptIndex(LabelIndex)As Integer
    289.                 LabelDescription(LocalIndex) = Description
    290.                 LabelScriptIndex(LocalIndex)=0
    291.             End If
    292.             ScriptAction(ScriptStep)=Say
    293.             ScriptDescription(ScriptStep)=LocalIndex
    294.             ScriptOption(ScriptStep)=OptionValue
    295.             ScriptStack(ScriptStep)=NextStep(ScriptStep)
    297.         Case "SCRIPTPAUSE", "RANDOMMOVE"
    298.             ScriptStep+=1
    299.             ReDim Preserve ScriptAction(ScriptStep) As Integer, ScriptDescription(ScriptStep) As Integer, ScriptOption(ScriptStep) As Integer, ScriptStack(ScriptStep) As Integer
    300.             If  UCase(Action)="RANDOMMOVE" Then
    301.                 ScriptAction(ScriptStep)=RandomMove
    302.                 AdjustStep=OptionValue+1
    303.             Else
    304.                 ScriptAction(ScriptStep)=ScriptPause
    305.                 OptionValue=Val(Description)
    306.             EndIf
    307.             ScriptDescription(ScriptStep)=OptionValue
    308.             ScriptOption(ScriptStep)=OptionValue
    309.             ScriptStack(ScriptStep)=NextStep(ScriptStep)
    311.         Case "STARTSCRIPT", "LABEL"
    312.             LabelIndex +=1
    313.             ReDim Preserve LabelDescription(LabelIndex) As String, LabelScriptIndex(LabelIndex)As Integer
    314.             LabelDescription(LabelIndex) = Description
    315.             LabelScriptIndex(LabelIndex)=ScriptStep+1
    316.             If UCase(Action) = "STARTSCRIPT" Then MainScript=ScriptStep+1
    318.         Case "SYNCPOINT", "ENDSYNC", "ONEONLY"
    319.             ScriptStep+=1
    320.             ReDim Preserve ScriptAction(ScriptStep) As Integer, ScriptDescription(ScriptStep) As Integer, ScriptOption(ScriptStep) As Integer, ScriptStack(ScriptStep) As Integer
    321.             If UCase(Action)="SYNCPOINT" Then
    322.                 ScriptAction(ScriptStep)=SyncPoint
    323.             ElseIf UCase(Action)="ENDSYNC" Then
    324.                 ScriptAction(ScriptStep)=EndSync
    325.             Else
    326.                 ScriptAction(ScriptStep)=OneOnly
    327.             EndIf
    328.             LocalIndex=FindIt(LabelDescription(),Description)
    329.             If LocalIndex < 0 Then
    330.                 LabelIndex =UBound(LabelScriptIndex)+1
    331.                 LocalIndex=LabelIndex
    332.                 SyncPointIndex+=1
    333.                 ReDim Preserve LabelDescription(LabelIndex) As String, LabelScriptIndex(LabelIndex)As Integer
    334.                 LabelDescription(LabelIndex) = Description
    335.                 LabelScriptIndex(LabelIndex)=SyncPointIndex
    336.             EndIf
    337.             ScriptDescription(ScriptStep)=LabelScriptIndex(LocalIndex)
    338.             ScriptOption(ScriptStep)=0
    339.             ScriptStack(ScriptStep)=NextStep(ScriptStep)
    341.         Case Else
    342.             Print "Unrecognized script command  ";chr(34);Action;chr(34)
    343.             ErrorFound=4
    344.             Sleep
    345.             Exit Do          
    347.     End Select
    348. Loop
    350. Close #1
    351. If ErrorFound Then End ErrorFound
    354. '        ===========================================================================
    355. '        ===========================================================================
    356. '         Output tokens for processed script
    357. '        ===========================================================================
    358. '        ===========================================================================
    360. ' define first step
    361. MaxPlayers=0
    364. Print "+----------------------------------+"
    365. Print "| Starting to output script tokens |"
    366. Print "+----------------------------------+"
    367. Print "Press any key to start";
    368. Sleep
    371.         ' Output C/Arduino code tokens.
    373.             Print "Script compilation complete; writing token data"
    374.             Open ShowCode For Output As #2
    375.             Print #2, "ScriptSize=";UBound(ScriptAction);";"
    376.             Print #2, "EndMain=";UBound(ScriptAction);";"
    377.             Print #2, "MainScript=";MainScript;";"
    378.             Print #2, ""
    379.             For LocalIndex=1 To UBound(ScriptAction)
    380.                 Print #2, "ScriptAction[";LocalIndex;"] = ";ScriptAction(LocalIndex);";"
    381.                 Print #2, "ScriptDescription[";LocalIndex;"] = ";ScriptDescription(LocalIndex);";"
    382.                 Print #2, "ScriptOption[";LocalIndex;"] = ";ScriptOption(LocalIndex);";"
    383.                 Print #2, "ScriptStack[";LocalIndex;"] = ";ScriptStack(LocalIndex);";"
    384.                 Print #2, "//"
    385.             Next
    386.             Print #2,""
    387.             Close #2
    389. End
    392. Function FindIt (AnArray() As String, What As String) As Integer
    393.     Dim i As Integer, myWhat As String
    394.     myWhat=UCase(what)
    395.     For i=0 To UBound(AnArray)
    396.         If UCase(AnArray(i)) = myWhat Then Exit For
    397.     Next
    398.     If i > ubound(AnArray) Then i=-16384
    399.     Return i
    400. End Function
    402. Function DisplayStep(i As Integer, j As Integer) As String
    403.     Dim Display_Number As String =""
    404.     Display_Number = Right("  " & Str(i),2) & "-"+Left(Str(j) & "   ",3)
    405.     Return Display_Number
    406. End Function
    407. Function NextStep(myStep As Integer) As Integer
    408.     Dim where As Integer
    409.     where = myStep + AdjustStep
    410.     If AdjustStep > 1 Then AdjustStep-=1
    411.     Return where
    412. End Function
    Operation of the Run-Time Module.
    The Arduino sketch that is the run-time module, Animatron, is similar in structure to the A-Code Compiler (ACC). However, rather than parsing the commands, the interprreted code has all the links, indices and pointers pre-interpreted. Thus, execution is strictly brachnching and referencing with integer values.

    Code (C):
    1. E
    2. #include <SPI.h>
    3. #include <SD.h>
    5. #define randomPin 2
    6. #define soundPin 3
    8. #define PlayMove 0
    9. #define PlayScript 1
    10. #define StartScript 2  
    11. #define EndScript 3
    12. #define JumpTo 4
    13. #define Label 5
    14. #define SyncPoint 6
    15. #define EndSync 7
    16. #define ScriptPause 8
    17. #define Say 9    
    18. #define RandomMove 10
    19. #define RandomPause 11
    20. #define CallScript 12
    21. #define EndWait 13
    22. #define NetWait 14
    23. #define OneOnly 15
    24. #define ActionSeq 16
    27. ;
    29. // setup move commands
    30. #include "MoveCommands.h"
    31. byte MoveIndex=0;
    33. int Player;
    34. int MaxPlayers=0;
    35. int LocalIndex;
    37. int PlayerStep[32];
    38. unsigned long PlayerEndWait[32];
    41. int SyncPoints[32];
    42. byte SyncPointIndex=0;
    43. unsigned int SyncPointStatus=0;
    45. unsigned long SoundEndWait=0;
    47. byte Selections;
    49. int minPause;
    50. int maxPause;
    51. int timePause;
    52. int ScriptStep=0;
    54. const char* scbase[]={"#3P"};
    55. const char* qs[]={"VA"};
    57. boolean NotDone=true;
    59. int ScriptAction[127];
    60. int ScriptDescription[127];
    61. int ScriptOption[127];
    62. int ScriptStack[127];
    63. int ScriptSize;
    64. int MainScript;
    65. int EndMain;
    66. int ix; //debug
    68. void setup()
    69. {
    71.   PlayerEndWait[0]=0ul;
    72.   Serial.begin(9600);
    73.   randomSeed(analogRead(randomPin));
    75. // setup tokenized program
    76. #include "CallTest1.h"
    78. }
    80. void loop()
    81. {
    82.   // define first step
    83.   MaxPlayers=0;
    84.   PlayerStep[MaxPlayers] = MainScript;
    85. //  Serial.print("Main Script Start= "); //debug
    86. //  Serial.println(MainScript); // debug
    87.   // debug
    88.   // for(ix=1;ix<=12;ix++) {
    89.   //   Serial.print("Action       ");
    90.   //   Serial.println(ScriptAction[ix]);
    91.   //   Serial.print("Description  ");
    92.   //   Serial.println(ScriptDescription[ix]);
    93.   //   Serial.print("Option       ");
    94.   //   Serial.println(ScriptOption[ix]);
    95.   //   Serial.print("Stack        ");
    96.   //   Serial.println(ScriptStack[ix]);
    97.   // }
    98.   while (NotDone){
    99.     Serial.println("In main loop"); // debug
    100.     for (Player=0;Player<=MaxPlayers;Player++) {
    101.       // speech processing here
    103. Serial.print("Player ="); // debug
    104. Serial.println(Player); // debug
    105. Serial.print("Player Script Step= "); //debug
    106. Serial.println(PlayerStep[Player]); // debug
    107. Serial.print("~Player Timeout= "); //debug
    108. Serial.println((PlayerEndWait[Player]<millis())); //debug
    109. Serial.print("Player Active= "); // debug
    110. Serial.println((PlayerStep[Player]!=0)); //debug
    112.       if (((PlayerEndWait[Player]<millis())) && (PlayerStep[Player]!=0)) {
    114.         PlayerEndWait[Player]=0ul;
    115.         ScriptStep=PlayerStep[Player];
    117. Serial.print("Executing Action ="); // debug
    118. Serial.println(ScriptAction[ScriptStep]);  // debug
    119. Serial.print("Action Parameter="); // debug
    120. Serial.println(ScriptDescription[ScriptStep]); // debug
    123.         switch (ScriptAction[ScriptStep]) {
    125.           case PlayMove: {
    126.             MoveIndex=ScriptDescription[ScriptStep];
    127.             // output MoveCommand[MoveIndex] to serial comms.
    128.             Serial.println(MoveCommand[MoveIndex]);
    129.             PlayerEndWait[Player]=millis()+long(ScriptOption[ScriptStep]);
    130.             PlayerStep[Player]=ScriptStack[ScriptStep];
    131.             break;
    132.           }
    134.           case ScriptPause: {
    135.             PlayerEndWait[Player]=millis()+long(ScriptOption[ScriptStep]);
    136.             PlayerStep[Player]=ScriptStack[ScriptStep];
    137.             break;
    138.           }
    140.           case RandomPause: {
    141.             minPause=ScriptDescription[ScriptStep];
    142.             maxPause=ScriptOption[ScriptStep];
    143.             timePause=int(random()*(maxPause-minPause)+minPause)/1000;
    144.             PlayerEndWait[Player]=millis()+long(timePause);
    145.             PlayerStep[Player]=ScriptStack[ScriptStep];
    146.             break;
    147.           }
    148.           case CallScript: {
    149.             LocalIndex=ScriptDescription[ScriptStep];
    150.         while ((LocalIndex <= ScriptSize) && (ScriptAction[LocalIndex] != EndScript)) LocalIndex+=1;
    151.                 if (LocalIndex < ScriptSize) {
    152.         // set the option value in the EndScript to point to the next step
    153.                 Serial.print("Call Return Step="); //debug
    154.                 Serial.println(LocalIndex);
    155.                 ScriptStack[LocalIndex]=ScriptStack[ScriptStep];
    156.         PlayerStep[Player]=ScriptDescription[ScriptStep];
    157.         PlayerEndWait[Player]=millis()+long(ScriptOption[ScriptStep]/1000);
    158.                 }
    159.             break;
    160.           }
    162.           case PlayScript: {
    163.             int IndexPlayers=1;
    164.             while (IndexPlayers <= MaxPlayers) {
    165.               if (PlayerStep[IndexPlayers]!=0) IndexPlayers++;
    166.               if (IndexPlayers > MaxPlayers) MaxPlayers++;
    167.               PlayerStep[IndexPlayers]=ScriptDescription[ScriptStep];
    168.               PlayerEndWait[IndexPlayers]=0;
    169.               PlayerStep[Player]=ScriptStack[ScriptStep];
    170.               PlayerEndWait[Player]=millis()+long(ScriptOption[ScriptStep]/1000);
    171.               }
    172.             break;
    173.           }
    175.           case EndScript: {
    176.             PlayerStep[Player]=ScriptStack[ScriptStep];
    177.             ScriptStack[ScriptStep] = 0;
    178.             NotDone = (ScriptStep != EndMain);
    179.             break;
    180.           }
    182.           case JumpTo: {
    183.              PlayerStep[Player] = ScriptDescription[ScriptStep];
    184. Serial.println();
    185. Serial.println();
    186. Serial.println();
    187. Serial.println();
    188.               break;
    189.           }
    191.           case SyncPoint: {
    192.             SyncPointIndex=ScriptDescription[ScriptStep];
    193.             SyncPoints[SyncPointIndex] = SyncPoints[SyncPointIndex] | (2^Player);
    194.             PlayerStep[Player] = ScriptStack[ScriptStep];
    195.             break;
    196.           }
    198.           case EndSync: {
    199.             SyncPointIndex=ScriptDescription[ScriptStep];
    200.             SyncPointStatus = SyncPoints[SyncPointIndex] & (! 2^Player);
    201.             SyncPoints[SyncPointIndex] = SyncPointStatus;
    202.             if (SyncPoints[SyncPointIndex] == 0) {
    203.               PlayerStep[Player] = ScriptStack[ScriptStep];
    204.             }
    205.             break;
    206.           }
    208.           case OneOnly: {
    209.             if (ScriptOption[ScriptStep]==0) {
    210.               LocalIndex=ScriptStep+1;
    211.               do {
    212.                 if (ScriptAction[LocalIndex] == EndSync) break;
    213.                 LocalIndex+=1;
    214.               } while (LocalIndex <= ScriptSize);
    215.               ScriptOption[ScriptStep] = LocalIndex;
    216.             }
    217.             break;
    218.           }
    220.           case RandomMove: {
    221.             Selections=ScriptDescription[ScriptStep];
    222.             MoveIndex=int(random()*Selections)+1;
    223.             PlayerStep[Player]=ScriptStep+MoveIndex;
    224.             break;
    225.           }
    227.           case ActionSeq: {
    228.             LocalIndex=ScriptStack[ScriptStep];
    229.             PlayerStep[Player]=ScriptStack[ScriptStep];
    230.             LocalIndex+=1;
    231.             if (LocalIndex > ScriptOption[ScriptStep]) LocalIndex=ScriptDescription[ScriptStep];
    232.             ScriptStack[ScriptStep] = LocalIndex;
    233.             break;
    234.           }
    236.           case Say: {
    237.             break;
    238.           }
    239.       }
    240.     }
    241.   }
    242. }
    243. }
    Simply, as different scripts are executed, arrays keep track of a value that serves as a program counter. Each array area is referred to as a "player" as it is playing a series of commands in a script. When a new script is instantiated,a new player is cteated and assigned a player value of the first zero value found in the array; a zero-value in this array is a non-used player. The main loop steps through this array, and executes the command pointed to by the value of the player "step" array.

    Each command calculates when it will be complete, using the millis() function in C. It then stores this value in the player "endwait" array corresponding to the current player being executed. This is a time slice implementation of multitasking.

    Since commands take 30ms or more to execute (particularly move commands which activate the RC servos), we can execute more thsn 30 moves per second with multiple players. This is more than adequate for an animatronic.

    At the beginning of this loop, external sensors are checked to see if the animatronic must respond to external stimulus. Currently, this is where audio output is detected and the analog output of an envelope detector is used to calculate a mouth position, so it appears the animatronic figure is "talking".

    Additionally, there is one more data structure. It is a bit mapped 64 bit value, where players activity is tracked. This structure is used to synchronize multiple scripts, so their action can be coordinated. It is also used to ignore commands that would override existing commands being executed. With the random operation features inherent in the language, it is possible for the commands in one script to override the commands in a latter script. This mapping allows identification of that case and ignore subsequent commands - only when necessary!

    An array named SyncPoints is used to keep track of commands to be synchronized (for example, several moves that should be executed together) or to prevent overlapping and conflicting commands to be executed. A-Code commands which use this feature are also identified by a label. This label is mapped to an index of the array.

    The actual array value is a bit-mapped representation of the various players involved in the synchronized group. Each invocation of a sync point command sets a bit in the array value. Once a command completes, the bit is cleared. Thus, if the array value is non-zero, the group is still active.

    In the case of ensuring single, unique execution of a command, a bit is set in the corresponding array value. The particular command to ensure single execution, "OneOnly", checks to see if the bit is set and exits immediately if it is. Otherwise, it will set the bit upon entry and clear it when complete.

    A-Code Tokenizing and Run Time Summary
    These two programs illustrate several important programming techniques.
    Parsing - parsing text including conditional parsing based on what was encountered before.
    Time-sliced multitasking - this is accomplished by maintaining multiple "program counters".
    Process synchronization - Using bit mapping to maintain the status of multiple tasks.
    Conditional Execution - while not described herein, Animatron supports random selection of actions.
    Processing External Inputs - the mouth/voice synchronization is an example of an external input. The area where a block of code performs this action, was designed for future reactions. For example, it is planned to use ultrasonic sensors to locate and address a person in the animatronmics vicinity; reacting to other animatronics over a wireless/Bluetooth link also will go here: finally, the first successful iteration of Animatron established a TCP/IP socket connection and could be controlled over the Internet.

    Also, Animatron was coded old-school style. However, the data structures lend themselves to object-oriented programming. I leave that interpretation to the reader.
    Wolframore, be80be and SamR like this.
  2. Wolframore

    Active Member

    Jan 21, 2019
    That’s very cool thanks for sharing. Hey dj I just started using switchcase in my most recent code do you know if there are significant differences to using if and else? I wonder if it uses less memory?
  3. Papabravo


    Feb 24, 2006
    Depends entirely on the compiler and the machine hardware that may support multi-way branching.
    djsfantasi likes this.
  4. djsfantasi

    Thread Starter AAC Fanatic!

    Apr 11, 2010
    I agree with Papabravo. I use the construct because I find the resulting code much easier to read and maintain.

    You can code the same functionality with if statements, but it quickly becomes confusing. Do you use if...then...else or standalone if statements? Code blocks for each different condition are more difficult to manage...

    So, that’s when you use
  5. Wolframore

    Active Member

    Jan 21, 2019
    It seems like it uses less memory, I've just received 1K chips and using port manipulation and case switch I'm able to make code to control two channel using random delays with less than half the memory. It is a very limited chip but they cost about $0.25 each and that alone is amazing when you need more simple tasks...

    For the more complex codes I use functions and control them using if statements... having them encapsulated in subroutines... but I like learning new tricks...
  6. djsfantasi

    Thread Starter AAC Fanatic!

    Apr 11, 2010
    In the subject of this thread, using is a natural choice.

    There are 16 different conditions, corresponding to each defined action available in A-Code. Generally, each condition is mutually exclusive from the others. There are similar statements in each case which I chose to duplicate the code just for consistency. With switch case, if I want to add another feature, I simply add another block if code. This was done several times during development.
  7. Papabravo


    Feb 24, 2006
    For those who like to dig into the internals of things it might be instructive to see how code is generated by a compiler for a statement and any of the alternatives. All you have to do is get the compiler to produce the assembly language source listing of the compiled code.