-
Notifications
You must be signed in to change notification settings - Fork 0
mul Manual
- General Rules
- Define Inputs/Outputs
- Manage Inputs/Outputs
- Flags
- Variables
- Sentences
- If-sequence '?'
- Timeres
- Halts
- mul-program
- Loading the Program
- Console Commands
- Jumps and Subroutines
- Halt and Break Instructions
- D-DateTime
- Strings
- String Operations
- U-units
- Pipes
- File Handling
- Examples
There are hundreds of thousands microprocessors and micro controllers around the world. Can anybody write a program working on all of them? Currently no. Each of them ‘speaks’ a different language and even a C program written for one can't run on another the same way. As a minimum we need a compiler exactly for that processor to translate the source code to processor language and deep knowledge about its peripherals, registers and functions. In particular I'm writing programs for different processors for over 30 years mainly in ASSEMBLER and C. And every time I have to study a new processor or come back to already used one I have to refresh my knowledge for it which means to go through hundreds or thousands of pages of data sheets. And sometimes we need a simple program to control temperature or a small device but you have to develop and test a whole new program for that, including all the ‘#pragma's’, initial register and interfaces set. This in fact is the most boring and tight part of program writing. I have always dreamed to play a bagpipe but someone else to blow it and I just move my fingers.
On other hand we have CPLD(Complex Programmable Logic Devices) and FPGA(Field Programmable Gate Array). Very powerful and fast devices due the fact of initial translate the logical tasks to hardware logical cells connected in logical chains to produce defined output combinations in respect to different input combinations. To write a “program” to them you have to use a hardware-describing language such as 'Verilog' , 'VHDL', e.t.c. At days most of FPGAs have a dedicated processor inside them (usually ARM3,5,9) to perform initial load of FPGA(as FPGA use SRAM) and perform some 'microprocessor's' tasks. So you have to write programs in 'C' too. It will be nice to use one language and one source for all those jobs. I foresee this to be done in a next generation of mul-language, named 'mulNext'. Some notes about mulNext you can find in Appendix 'D'.
What we expect from one real-time working processor is to make some calculations based on its input's condition and control its outputs according to instructions it was given by the programmer. That's all.
So why should be so difficult to give a processor some instructions?
'mul' comes from 'Microprocessor Universal Language' and although that name fits on it much it is neither a horse nor a donkey. Just a good daily working processor language in case a friend of yours asks you to make for him a little gadget to control a thing.
mul has been inspired from Mumps.
Mumps is a very useful language mostly used in military projects because of its simplicity and power. All the mumps commands can be reduced to one letter, which is important to small processor memory capacity.
Mumps is dedicated to deal mostly with databases not real time processing, so let it be Mumps-like (another acronym for 'mul').
LUA is great. Unfortunately it does not fit into small processors and looks closer to programmer's point of view than to a customer one. By the way why we use C if we have Perl? As we use to say 'Every train has its own passengers'.
One processor runs mul- interpreter in real time kernel manner (if you don’t know what RTK (real-time kernel) is, you will find out later in this paper), the programmer is given a console to govern the interpreter and manage the ml-program at set/debug stage. During program execution they will be useful for inspection of the current state of program, inputs, outputs etc.
As the mul files will have an extension of '.ml', from time to time we will refer to them as ml for short.
ml is written in C and contains a base - mul machine, an expression calculator and a processor -specific file(or files). The last one contains a couple of subroutines to handle in/outs, analogs, communications (USB, RS, NET, CAN etc.) and hardware-dependent definitions. In general to run ml on any processor one must just rewrite file called 'ProcesorSpecific'. To rewrite this file those one must have deep knowledge about the target processor, but once written and compiled in conjunction with ml-base and then published, anyone can use it to run any program in the world written in ml without having to change anything in it (except maybe the line where used in/outs are declared).
ml is relative small and in its light variant (without strings and pipes, no multitasking) uses about 4k of flash memory and about 500B RAM and fits in a PIC16F1459 together with USB stack leaving 1k FLASH for the user’s ml-program.
I would like to say 'everything' but I can't. Maybe one day, when all the people from the ml-community little-by-little improve the project. But I still hope that in 90% of cases the job can be done using ml instead of assembler or C.
When I started this project the goal was to develop an easy language to teach my grandchildren in programming skills. That is why there are included passages named 'Beginner's Corner' in this exposition. Nevertheless I decided to leave them as someone may find them helpful.
This paper covers the so named 1mul-version of mul (“first-mul”). I plan to add some additional commands in order to cover other types of micro-devices as CPLD and FPGA. It's name will be mulNext or 2mul (see Appendix D).
It's a list of instructions that have to be done in certain order to do some job. Suppose you have a robot and want to tell it to dig a hole to plant a tree. If you tell it 'go there and dig a hole to plant a tree' it will not understand. OK. Tell it 'go to coordinates x, y and dig a hole to plant a tree'. That's better if it knows what 'dig' is and what a 'hole to plant a tree' looks like. So let’s define:
Dig:
stick the shovel in ground about 20 cm
lift the shovel full of ground
empty the shovel 50 cm left
return in start stage
hole_to_plant_a_tree
50 cm length
50 cm wide
50 cm deep
So let's start the program. The robot goes to coordinates x, y. It knows how to dig and what hole we need. What's wrong? It has no shovel neither knows how to measure 50 cm. Finally your program will look like this:
- Take a shovel and a tape-measure
- go to coordinates x, y
- dig
- measure hole (length, wide, deep)
- until hole (is not=)hole_to_plant_a_tree goto 3
- End
Will this work? In whole – yes, but your robot probably will not stop digging until it gets to other end of the Earth. Why? If you stare on dig subroutine you'll see that a cycle of digging is 20 cm deep, so the first time the robot will reach 20 cm deep, next 40 cm deep and next 60 cm deep and never reach the 50 cm we planned. So it's better to change 5 like this:
- until hole<hole_to_plant_a_tree goto 3
Error handling is a piece of art in programming. I have always said that in a good program 30% of code is the useful program and the rest is error handling. So in the above example you additionally have to check is there a shovel and is it good enough, is there a tape-measure and is it in a good condition and take actions if no.
In overall: a program consists of define/set-part(1,2) and main job(3,4,5,6). Define/set part will be executed once in the beginning of program and then comes the main part which will be executed in real-time manner- that is measure-dig-measure-dig... Real-time always means, that the program's action depends on input parameters just measured i.e. to dig or not to dig ('that is the question!') result depends on time. A good example is the measure and control of the level of water in a tank. In opposite a non-real-time program works same way every time – for example a program that calculates the volume of a tank gives same result no matter when the measurements are taken.
Format of commands is an important part of a language. The machines have no imagination and could not realize that 'Take a shovel' and 'To take a shovel you have to' as Yoda may say mean the same. So if an instruction is not in the right format it decides better to stop and give an error message than get in a mischief.
If you have reached this point here my job is almost done. Just a couple of remarks and I'll get to the point: ml- program in fact is a text program that you load into the processor’s memory and when allowed it starts its execution, monitoring several inputs, makes decisions based on its state and controls a couple of outputs to achieve a goal you set in advance. To write an ml-program use a text editor for example 'Notepad++' http://notepad-plus-plus.org/.
Please read carefully the next few lines!
-
mul is case sensitive. Commands, operators etc. have to be upper case.
-
When you are writing a ml-program don't leave empty lines as they count, don't use 'tab'-s or 'spaces' in front or after a blank program line – they will consume program space. This is not the case if you upload ml program with flag 'strip' set – comes later.
-
You may leave comments after each line. Comments do not affect speed of mul execution but they consume flash space. File download procedure gives you the opportunity to load a blank file striping all the garbage as comments, tabs leading and trailing spaces etc. (see 20 File handling). So you may strip comments and other beauties of your program at your desktop and download the pure program to mul. Very convenient isn't it?
-
By the way do you know what the most difficult part to remember in different languages is? The comments mark. If you write one week in C (// or /* */) try to make a comment in ASSEMBLER(;) in VB( ' ) or in LUA(--) just like that! mul does not make an exception as it introduces a new one - '#'. Congratulations to Perl programmers. After a program line and before comments you can insert 'spaces' and 'tabs' as they will not affect program execution.
-
You should avoid inserting more than one space in the line content because they consume space in the program memory and need additional interpreter work which slows it. However 'spaces' work as delimiter separating different commands in a line just as well as standard delimiter ',' so you may use either 'spc' or ',' as you wish.
-
At the end of the line simply type a 'carriage return' (\r\n or CrLf). Don't forget it at the end of the final line!
-
All objects in mul are GLOBAL so you can see and change them from everywhere in a mul program.
-
All the indexes of mul-objects (variables, timers, inputs, outputs, …) presented in this paper as simple digits (V1,T2,I3,O4) may be an expression. Enclose it in parenthesis, so the interpreter may understand you.
V(V1+10), T(F1*4), I(V1+V2/2), …. are valid.
-
mul is designed so it does not block the program if one error is encountered. mul will try to resolve the issue on its own. Usually it is not something you want in programming, so debugging is essential to release a good mul program. If a fatal error is encountered mul stops the program and sends a message to the Console.
'Console' is a real time dialog communication with your processor and simple way to check what is happening right now, what the program does, what are the current variable parameters etc. To use command line dialog, you may use some kind of Terminal program (as a HyperTerminal in Windows) and a USB or serial port or other stream media. I prefer to use my http://ftp.eta-sys.net/MySerial/ for Windows available for free. Choose one of the channels, one of the ports (if you cannot see yours – press Restart, Set 9600/8N1, +ASCII +addCR +addLF ).
If you read this in order to use an ESP8266 module to run mul please refer to Appendix_A.
If you plan to use a Pic-processor please refer to Appendix_B.
If you will run mul on an ARM refer to Appendix_C.
If you do not have a board and you just want to see what is mul you may download http://ftp.eta-sys.net/mumps_11/ and try Windows mul variant which is my first (and hope last) program I wrote in Visual C++ just to be able to include mul's kernel programs 'as-is'.
Happy come back from Appendix! Hope done well.
At the beginning we will talk mainly about program writing and do a little gymnastics with the console to 'warm up'. So switch on your Caps Lock and let's do it.
- '!' * means 'print to console'. If you want to check some value just add '!' in front of it. How it processes? It checks what is next after '!':
if '$': tries to output a string as result if '_': tries to print length of a string result if else: outputs a integer value of a variable or a string converted to int, base 10
So when you want to print an integer result from $ operation, use ( ) to mark the expression.
For example
$1=”1234” # make $1=”1234”
!($1/4) # print ASCII at position 4
52 # ASCII '4', base 10
Digital & Analog
When for the first time people decided to build a calculating machine they tried to do it using analog signals. Suppose we want to calculate 3+2. We may use a battery of 3V and one of 2V. Connecting them in series we get 5V – so this is the answer. Simple and clean. But if we want to add different digits, we should use many different batteries and is some ones voltage drops with time, we will get a different result – not 5 but 4,9V for example. And imagine what will cost to add 5 000 000 + 200 000! The number of programmers will drop dramatically after several such calculations! So they come to the idea to use triggers - switches which have just 2 states ON='power voltage' and OFF='no voltage' and developed a whole new mathematics called binary mathematics. To develop mathematics is not difficult – we need to define a Zero, a One and add operation plus a couple of rules named laws, about the order and commutations between elements in an expression.
We all are familiar with 'usual' mathematics:
Zero=0
One=1
So: 0+1=1+0=1
1+1=2
2+1=3
3+1=4
….......
8+1=9
9+1=? as 'usual' mathematics is base 10, this last one overflows base and produces a digit one range
greater than current, so:
9+1=10
Then we can continue:
10+1=11
11+1=12
….........
19+1=20
20+1=21
…..........
99+1=? same situation – we need one range over, so we get:
99+1=100
And what about negative digits? We may introduce such a digit, that adding it to our digit we get 0:
for example we choose digit 4
we need a digit x that
x+4=0
As 4 contains 4 positive Ones this new digit must contain 4 negative Ones, so we decide to name such a digit (minus 4) and we will write it as – 4. Such a negative digit we can write for each positive digit.
How to process with negative numbers? We may subtract as many one from a digit as negative says but we may process it in a different way. Let’s imagine that we have a primordial calculator with just one digit length display, so it can display digits from 0 to 9. So we can calculate correctly next expression:
8-4=4
Let’s calculate 8+6.
8+6=14 but since we have just one digit display we get
8+6=4 while we cannot see '1' before '4'
So for us subtraction of 4 equals to adding 6!
We may notice that this 6 comes as additional digit to base 10 of 4 i.e. 6+4=10. Let's try with another digit:
8-7=1 the extra digit for 7 is 3 as 10-7=3, so
8+3=8-7 for our calculator
As each processor has limited range of 'display digits' 8,16,32 or 64 we can use this way to do subtraction instead of wiring a gadget to really subtract Ones as many times as said.
Now let's invent binary mathematics. Let's postulate:
Zero=no voltage=0
One=power voltage=1
adding law:
0+0=0
0+1=1+0=1
1+1=?
here comes the base. For 2 state machine the base is 2. Since 1+1 produces a digit that overflows the base, we have to write that as 'one order greater', so
1+1=10 reads 'one-zero' not 'ten'
Then 10+1=11
11+1=100
100+1=101
101+1=110
110+1=111 e.t.c.
This is base 2 mathematics.
No matter what base a math uses it should produce the same answer. Let's check. First we get to the following presentation of numbers from 0 to 15 decimal (ignore hexadecimal column): We have 4 boxes for binary presentation of binaries. Each box calls 'bit' so our processor is 4-bit range. In practice they are 8-bit processors, 16-bit, 32, 64. An 8-bit container names 'byte', a 16-bit -word, a 32-bit d-word or 2-word, and 64-bit quad-word or 4-word.
decimal binary hexadecimal
0 0000 0
1 0001 1
2 0010 2
3 0011 3
4 0100 4
5 0101 5
6 0110 6
7 0111 7
8 1000 8
9 1001 9
10 1010 a
11 1011 b
12 1100 c
13 1101 d
14 1110 e
15 1111 f
Each number in next row is produced by adding one to current one.
When operating with binaries we use the following rules:
0+0=0
0+1=1
1+1=0 + one in mind
We start from right most and calculate to left most using those rules.
For example:
0110
0101
1
1
0 +one in mind
1
1011
Translate using table above in decimals this looks 6+5=11 which is true.
And what about negative numbers? We may introduce them the same way as we did with decimals (=base-digit), but there is the next very convenient rule:
Just write opposite number replacing 0 with1 and 1 with 0, then add 1.
So the opposite of 4 = 0100 will be 1011+1=1100.
Let's check with dec 8= bin1000:
1000+1100=10100 but the left most 1 is out of range for our 4-bit processor,
i.e. out of scope, so we get 8+(- 4) = 4.
And what about hexadecimals? No processor knows anything about hexadecimals. This is a space saving human-friendly representation of binaries. As digits from 0 to 9 coincide by value with decimal ones, we use the same signs for hexadecimals as decimals. As for those above 9 we have no sign dedicated so we use letters from 'a' to 'f' to name them. Sometimes someone may use upper case letters A to F. This way we may define base 16 mathematics which differs from already presented only by lower number's length to write we gain. More over switching from binary to hexadecimal is easy – just group binary in 4-bit groups from right to left and then replace code from meddle column with those from hexadecimal column. For example:
0101 1100 0111 1001 = 5c79
If you do not have enough bits, just add as many 0s as you need in front of your binary, while 01 and 0001 represent the same digit 1.
One more concept- ASCII code. The processor knows nothing about letters, it operates with digits, each letter was assigned a number from a table named ASCII table. For example A=6510=4116=010000012, O=7910=4f16=010011112 represented in dec, hex and bin base. Moreover each key from a keyboard and some key-sequences are assigned such numbers. So when you press a key you simply send that code to the processor.
The nerves and muscles of one machine are inputs and outputs. It's common (but not guaranteed!) in the processor’s world same pin to be able to work as an input or an output, depending of programmer's will. In general inputs and outputs are two different types – digital and analogs. For example, a common light switch has just 2 states – ON and OFF. We prefer to call them '1' and '0'. Sometimes we will call them 'TRUE' and “FALSE' which means 'not 0' and '0' as well. And in case of input/outputs we will use 'High' and 'Low' meaning the level of positive voltage of a input/output pin, where 'High' means something like near to voltage supply of the processor and 'Low' means near to zero. You may ask 'why positive'? And you will be right. Upon the time, somewhere in the middle of the past Century, 'High' was negative voltage (MOS technology), now it means 'positive voltage'(CMOS) but who knows - some day we may use both the 'negative', 'positive' and zero and implement triple logic for digits.
Another example is the throttle pedal of a car – adding pressure you can slightly change the speed of the car from 0 to MAX. We will call them 'Analogs' and they can adopt any value from 0 to MAX.
As we are in the car – let’s think about a manual car gear-box – it has different gears '0,1,2,3,4,5,6,R'. What about them - are they 'Analog' or 'Digital'? From one side you can't 'slightly' change a gear let’s say from 1 to 2. It's definitely 1 or 2. From other side it can be else: '3, 4, 5 or 6' and even 'rear' if 'R'. Such variables are 'Analog' but a different sort 'Enumerated Analogs'. In contrast of 'spread' Analogs, which can adopt any value from a range, enumerated analogs can adopt only enumerated values.
All the commands in mul that should open/close, define, start/stop something with a list of parameters always keep those lists in braces!
Suppose we need 4 digital inputs. To define them in our ml-program we should use the following line:
I{2,3,4,6} (2.1)
which means 'define pins 2,3,4 and 6 as Digital Inputs', where digits 2,3,4,6 are hardware dependent. In trivial cases those are processor pins as described in processor pin-out view in the data sheet. If the hardware config (in file '...ProcesorSpecific.h') describes particular base board, the digits may be the appropriate terminal numbers. You may find your board pin description in your board manual. Any way – you are able to get the input’s processor descriptor executing I{?} command from the command line (see 'Command line commands'). You will get something like this: I{1,2,3,4,5,6,7,8,9,10,11,12} for example. Those are all the pins which are with input capability on your current board.
Writing (2.1) in our program's first line we program pins 2,3,4,6 to be inputs and moreover, we define I1 to be pin 2, I2 – pin 3, I3 – pin 4, I4 – pin 6. Remember that! You may ask why? Why not use same indexes as pin numbers in the whole program, but instead remap them in a one-based array? So, we decided to do it this way and the reason is the following: As I mentioned before, pin numbers in braces of (2.1) are hardware dependent. And lines like (2.1) usually appear in the most beginning of the ml-program. Remapping them to hardware-independent I1, I2, I3... we are free to use those any way in the program as follows, so in case we need to use the same program on a different hardware, lines like (2.1) are the only ones we eventually should change.
Once the index is assigned to the pin’s input (case: pin2 to I1) it will not change never ever to the end of the …., no not to the end of the World but to the end of the program execution and a new program is loaded and started.
Every time your program executes a line similar to (2.1) the previous pin assignment will not change, but a pin will be added next to the already set pins according to pin numbers in the braces. If a pin is set once as input repeat itself in a new command (2.1) it will go to place it already has. That is the case when you first set one pin as input, then as output and back to input. So the input's pin assignment will last to the moment we load a new program and initialize the hardware to execute it in a different way.
To define an output set we have a similar command:
O{1,5} (2.2)
which means 'define pin 1 and 5 as Digital Outputs 1 and 2'.
Note that mul doesn't check if pins are duplicated with other pin definitions. This is programmer's responsibility.
To define an analog input set we should use sentence like this:
A{7,8} (2.3)
which means 'define pin 7 as Analog Input 1, pin 8 as Analog Input 2'.
To define analog outputs, we should wait a little as we need to define interface and pipes.
Naturally all the pins must have appropriate functional ability to work as we want.
In ml we are allowed to write several sentences (named operators) in a single line, separating them with ','. So our first line will look like:
I(2,3,4,6),O(1,5),A(7,8)
As we explained above, digital outputs can adopt just 2 states 'High' and 'Low' we named '1' and '0'. To manage a digital output we simply write:
O1=1 (3.1)
or
O1=0 (3.2)
or
O1=^ (3.3)
to set or clear Output 1 ( pin 1 in our example). (3.3) means XOR - 'set opposite to current value' -
if O0=1 => O0=0
if O0=0 => O0=1
A digital input gives us the current state of a switch. For example it may be a switch, showing if the car door is open to turn on the light in coupe. That is why for digital inputs it makes no sense to set and clear them, but instead we need to read their state. This can be done in two ways:
- using flags (see 4)
- using inputs in a sentence (see 6)
Analog inputs can be retrieved same way as digital inputs:
- using variables (see 5)
- using sentences (see 6)
Let there be light. Keep in mind that one pad of LEDs is instant connected to +3V3 and need ground (i.e. 0) to be placed at it other pad to lit up. Type next commands in given order and see what happens. Tips and tricks: if you use MySerial when you want to repeat a command or send a similar one use up arrow key to find it into the list of already sent commands, make changes and press ENTER.
Type command Meaning Result
1 O{12},!O1 Set Out1 to be GPIO_12 0 LED1 will lit up
2 O1=1,!O1 Make O1=1 (set GPIO_12 to High) 1 LED1
3 O1=0 Make O1=0 LED1 lit up
4 O1=^ Reverse O1 (will become High) LED1 not lit
As we see LED1 lit up when we define O1. For LED2 we will process other way – first set them to 1 and then make it an output. This will be our O2.
Type command Meaning Result
5 O2=1,!O2,O{13} Make O2=1 and print. Set Out2 to be GPIO_13 1 LED2 will not lit
6 O1=1,O2=1 Set both outs to 'High' Both LEDs not lit
7 !O Print all output state 3 (=00112)
8 O=0 Make all outputs 0 Both LEDs lit up
I will like to draw your attention to lines 7 and 8 – see how we derive all outs state and set all outs at once? We just put no index, so the next expression counts for all the outputs. Remember that right most bit shows O1, left most O15 or the last one. So 310=316=0000000000000112 . Here 10, 16 and 2 mean 'base 10', base 16' and 'base 2'. As mul presents all results in 'base 10' you may use http://eta-sys.net:8080/cgi-bin/binary.cgi to see the result in a different base.
Inputs follows. We should use 1k resistor as a bridge from -3,3V(GROUND) to the input pin if when the pin left open reading corresponding input shows 1 (inputs are initial pull-up) else when the pin left open reading corresponding input shows 0 - from +3,3V (inputs are initial pull-down). It is not a good practice to use a wire to do it, as if you accidentally touch an output or else pin this may be the end of your processor’s life. Across 1k resistor in short case will pass only 3,3mA which hardly damages your chip. If you have to use a wire put it in series with 1k resistor between the ground and the wire’s not working end. If you prefer - use ON/OFF switches instead of wires. Build up yourself good habits. Remember that if inputs are pull-up, in order to keep them Low=0, ground wire should present instant at pin. To generate a High=1, simply remove wire for a while.
Type command Meaning Result
1 I{4,5} Set GPIO_4 and GPIO_5 as I1 and I2
2 !I1 Print I1 1 or 0 depends is voltage applied or not
Apply/Remove wire from GPIO_4 every time executing command 2
3 !I Print all inputs value (as per outs above) 1,2,3 depends of pin state
4 V1=I,!V1 Remember I value in a variable
5 !(V1^I),V1=I Print (V1 XOR I), update V1. Will give pin changed if any
Execute 5 several times from time to time changing inputs state
Analog inputs Same story.
Type command Meaning Result
1 A{1} Set pin 1 as analog input
2 !A1 Print A1 Apply 0...1V to pin Unsigned integer according to voltage applied
So we are ready to write our first simplest program. Before that I would like to present you briefly with an 'if' sequence, explained in details in 7. In our case it will check an Input and if TRUE (=1) execute first branch, if not - second one:
?I1 O1=1:O1=0; # sounds if I1=TRUE make O1=1 : else make O1=0; end_if
Please don't forget to put a space after ?I1 before O1=1. No need to write anything after semicolon;
Now we will write a mul program. If in doubt please check point 11. In your console-program type next:
W”main.ml” This should give to you a > prompt
>
Type next lines:
O=3,O{12,13},I{4,5}
J1
?I1 O1=1:O1=0;
?I2 O2=1:O2=0;
E
If you use Hyper Terminal hold Ctrl-key and press Z.
If you use MySerial type '0x1a' and press ENTER
You should receive message:
Load end!
In future if you use Notepad++ to write you program, click in 'Edit-->Character Panel' and after last 'E' of program press ENTER and at new row insert character 'SUB' from those panel. This way your download will end automatically.
Type G from GO!
G
Your program starts. Now outputs 1 and 2 (pins 12,13) should repeat Inputs1 and 2 (pins 4,5) status.
You can break program where it is right at the moment
B
then start again from that point
G
Execute step-by-step using
X
X
X
between commands switch inputs in different states
Check printing input state
!I1
!I2
!I
Check printing output state
!O1
!O2
!O
then start again
G
End program execution
E
restart from beginning (as just ended) step-by-step
X
X
run
G
Finally end with
E
It must be a good presentation of mul work principles. If you have fun playing with this small program you will be fascinated by the rest following!
Flags are our own boxes, which can adopt just 2 states '1' and '0'. We may use them as we wish as they are 'internals' for our ml-program and do not affect anything outside of the processor. We may use different flags, adding a one-based index to them to differ one from another:
F1, F2, F3,....
We may use at least 15 (from F1 to F15!), but in some cases 31 or 63 flags, depending on one variable named 'RANGE' which is processor specific and depends on processor memory available. It is fixed in 'globals.h' by the programmer who writes “ProcessorSpecific.h' file and can't be changed from those who don't know the processor in details.
You may use flags setting and clearing it:
F1=1, F1=0, F2=^ (4.1)
The last one means 'make opposite'.
Making them to match a digital input or output:
F1=I1, F2=O1 (4.2)
Using them in a sentence:
F1=I1*I2 (4.3)
The last one will set F1 if both I1 and I2 are 'TRUE' or '1' and clear it else, because 00=0, 01=0, 10=0, 11=1.
F1=I1+I2 (4.4)
F1 will be 'TRUE' if I1 or I2 or both are 'TRUE'. (0+0=0, 0+1=1, 1+0=1, 1+1=2 but we stated that the box F1 may have just 2 states '1' and '0' and if the result is not '0' so we claim it's 'TRUE' or '1'.
Type command Meaning Result
1 F1=1,F3=1,!F Set flags 1 and 3. Print all flags state 5
2 F2=F1*F3,!F2 F2=F1 AND F3. Print F2 1
3 !F4=F1+F2 Print F4=F1 + F2 2
4 F3=0,!F2,!F2=F1*F3 As 2, but F3 is 0. 10
As you see making F3=0 doesn't change F2 automatically
5 !F Print all flags 9 ( 000000000001001 )
6 F=0,!F Clear all flags 0
Variables as Flags are our own boxes, which can adopt an integer value between -32768 and +32767. We may use different variables, adding a zero-based index to them to differ one from another:
V1, V2, V3,....
We may use at least 15 (from V1 to V15!), but in some cases up to 31 or 63 vars, depending on one variable named 'RANGE' which is processor specific (as per Flags (4)).
To assign an analog input value to a variable we use '=':
V1=A1 (5.1)
A sentence contains different objects like inputs/outputs, flags, variables etc. bind with mul operations:
+, -, *, /, >, <, =, &, |, ^ combined inside '( )' to determine the order of operations. The goal of a sentence is to produce a single result. The last one may be assigned to a variable or a flag or checked against some value. When assigned is used, the desired variable which will hold the result must be left-hand of '=' and the sentence - right-hand of '=' and that is the only rule you must obey.
V1=A1+100 # make V1 equal to A1 add 100
No limits to combine different objects as integers, flags, inputs, outputs and analog var's or inputs as all of them have either mathematical (1 or 0) or logical ('TRUE' if <>0 and 'FALSE' if=0). For now mul does not support floating point variables and operations.
Type next commands in given order and see what will happen.
Type command Meaning Result
1 !V1=2400 Set variable V1 to 2400 and print 2400
2 !V1 Print V1 value 2400
3 V2=-2900,!V1 Set variable V2 to -2900, Print V2 -2900
4 !V1+V2 Print V1+V2 -500
5 !V1-V2 Print V1-V2 5300
6 !V1>V2 Print is V1>V2 0=NO, other=YES 1 (YES)
7 !V1<V2 Print is V1<V2 0
8 !(V1>V2)*4 Print 4 if V1>V2, else 0 4
9 !(V1>V2)*V1+(V1<V2)*V2 Print V1 if V1>V2 else V2 2400
10 !(V1<V2)*V1+(V1>V2)*V2 Print V1 if V1<V2 else V2 -2900
11 V2=V1,!V2=V1 Make V2=V1, Print is V2=V1 2400
!V2=V1 is not a question. If you remove ! will get to V2=V1.
12 V2=V1,!(V2=V1) Same as 11 but (V2=V1) looks more like a question rather then equation.
Will cause a check 1
13 Now repeat 9 As notV1>V2 nor V1<V2 0
14 !(V1<V2)*V1+(1-(V1<V2))*V2 If V1<V2 V1, if not V2 2400
15 V2=-2900
16 !(V1<V2)*V1+(1-(V1<V2))*V2 Same as 14, but V2 is now -2900 -2900
As you see we may include Boolean sentences in a common math sentence to get different results without using a 'if then else' construction. Just remember that in mul the result from one Boolean expression may be digit 1 or 0:
if YES=TRUE=1
if NO=FALSE=0
Keeping in mind this let's look closer to 14 in table:
(V1<V2)*V1+(1-(V1<V2))*V2
Suppose V1<V2 then we may rewrite 14 as
1*V1+(1-1)*V2
As 1-1=0 the second part doesn't count and we get V1.
Now suppose V1>V2 then we will rewrite 14 as
0*V1+(1-0)*V2
This time the first part is 0 and the second gives V2
This will work even when V1=V2 and it will give us not 0 as 13(see table) but V2.
Sometimes we would like our program to execute in a different way if some condition presents. For example if our car has an alarm system it should behave different way when someone opens a door depending on the alarm system being armed or not. Suppose a digital input I4 =1 if the alarm system is armed and I4=0 else, and I5=1 if a door is open and I5=0 if not. O5 is an output controlling a siren. We have to decide to sound siren or not.
As we know from (4. Table Line2) we can write the following:
O5=I4*I5
which will be 1 if both I4=1 and I5=1. But that does not work much as when the alarm system is armed and a door opens the siren will sound, but if the door closes the siren will stop! No. We would like to do some modifications after the first door opens, like flash lights, sound a couple of seconds and stop after, just in case the door was closed meanwhile.
The better way is to use an 'if' sequence instead of a simple line. An 'if' sequence looks like this:
?Condition,Operations_if_YES:Operations_if_NO; (7.1)
Not very clear? So let's write a couple examples to clarify:
?F1,O1=0,O2=1:O1=1,O2=0; #if F1=1 clear O1 and set O2 : else set O1 and clear O2;end_if
Looks simple. As I mentioned above – the standard delimiter in ml is coma, but a space works also fine for a delimiter. So you can write more human friendly:
?F1 O1=0,O2=1:O1=1,O2=0;
if you prefer. You don't have to write all the '?' attributes if you don’t need them or the meaning of the sequence does not change if you omit some. Valid expressions are:
?F1 O1=0,O2=1 # no 'else' no 'end_of_if' because the current line ends here
?F1 :O1=1,O2=0 # no 'if_YES' just 'else' (note the space before ':')
For 'Condition' you are allowed to put a whole expression as in the next example:
?F1*(A1>1200)+(1-F1)*(A1<1200)
or a non-logical expression:
?(A1-V1) O0=0
You may use parenthesis as you wish for no confusion. You may insert 'if' in an 'if' when you wish, but be careful:
?I4 ?I5 O1=1:O1=0
or
?I4 ?I5 O1=1;:O1=0
Looks the same? Not really. The first one sounds:
'if I4=1 then if I5=1 set O1 : else(if I5=0) clear O1'
and the second
'if I4=1 then if I5=1 set O1 ; end_of_if : else(if I4=0) clear O1'
Capisci?
One more thing: You are not allowed to crossover 'if'-s! 'If's have to be enclosed one in other:
?1 ?2 : (else2);: (else1);
In fact I can't even imagine how you can write such a sequence
?1 ?2 (else1) (else2)
but if you can – don't worry it will work.
Next to the last '; '(end_of_if) you may continue with other operations which will be executed no matter what the 'if' result is as you have written them on a new line.
And what about the car alarm? To finish it first let's insert one new object:
As you see we can't ignore it. Moreover we will need them all the time in an ml-program. It's not an exaggeration to say that you are able to run a whole ml-program just using in/outs and timers. Timers use letter 'T' for name and an index from '1' to 'RANGE' as you may predict. 'RANGE' in common varies through processor to processor but it is no less than 15, so at worst case you have T1, T1, ...T15 in overall 15 timers. Timers may run as minimum at 1mS, and the max value they adopt is 65535/4 = 16383 as they update every ¼ of ms. To run a timer, we expect to define a period after which the timer will expire and fire to give us a signal. Let's write an example to start T1 for 10 sec. We should use next sequence (in braces!):
T1{10 s} (8.1)
where:
10 is the timer period in this case 10
's' goes for seconds
or
T1{10000 m}
10000 is the timer period in this case 10000
'm' goes for milliseconds
If you prefer you may write T1{10000 mS}, T1{10000 M}, T1{10000,m}, T1{10000,mS} just put a delimiter or space between value and measure!
Starting a timer does not stop the program execution at point (how to do so we will learn later). The program is still running as usual from top to the end. When the timer expires it will produce an event which can be captured from anywhere in the program, waiting for those timer event. To catch T1 event we may pull T1 to check if a timer is expiring using same name in an 'if' expression or in a line sentence:
?T1 O1=0:O1=1; #clear O1 if T1=1 : set O1 else;
or
O1=1-T1 #make O1 opposite to T1 – when T1=0 O1=1-0, when T1=1 O1=1-1
If a timer is not started, you may use it as a flag, setting and clearing it as you may expect:
T1=1,T1=0
You may restart a timer at any time no matter if running or not using such as 8.1 In order to stop running timer simply restart it with zero time.
T1{0} (8.2)
will stop Timer 1 and zero T1 flag.
To see a timer's value, write next:
!(_T1) # in parenthesis otherwise will try to get length of a string
5
Once again – timers count down, so timer's value will decrease to 0. It's essential to remember that when a timer expires it simply sets its T value and stops counting down. When a mul program is not loaded, Ended or in Break state all the timers do not run, except Clock and T0 Timer0! Where does this T0 come from? In fact all the objects have its 0-indexes, but they are used mainly by Console and not presented to the user. As Console does not need Timer 0, it is given to the user.
You may use an expression for timer period initial value but not for sec/msec. measure (the last letter):
T1{V1+100*F1 s} (8.3)
will start timer with period =V1 or V1+100 sec. depending on F1. As you see the delimiter tells the interpreter where the sentence ends. Starting a timer with a negative value will not cause an error but timer will not count.
Before we write a mul program let's get familiar with one more command.
'H' may appear as single command or as a prefix (from Halt) in front of a command. It provides a whole new way to write an ml-program – in a breaking manner. H breaks the program into the point it appears until some event occurs or a time period expire. Then program continues from next instruction after H.
'H' instruction has the following different forms:
9.1 H a pure 'H' instruction halts current Job forever and only another running Job may resume it
(about Jobs we will talk in next point) or a program restart from console is performed.
9.2 HT{period s/m} # Halt&Wait a time period
9.3 H?expression # Halt&Wait for an event
9.4 HT{period s/ms}?expr. # Halt&Wait a time period for an event (no delimiter and
no space between '}' and '?')
As you may predict it makes no sense in 9.3 to add an 'else' while doing so our program never stops here – the expression will be either TRUE or FALSE. But we can have such a record:
9.3a H?expression :operator,operator, ...; which will fire if NOT(expression) happens.
Don't forget the delimiter or space after expression before ':'!
In opposite 9.4 often has an 'else' state, which will be executed when the time expires and expression is still FALSE.
9.4a HT{10 s}?I1 O1=0:O1=1; #if until 10 s. I1 become 1 clear O1, if not - set O1after
Let's try the following line:
O=3,O{12,13},I{4,5},HT{10 s}?I1 O1=0:O2=0;
O=3 means set O1&O2 initial High (not lit LEDs)
O{12,13} in case you have restarted your board meanwhile, arrange pins 12,13 as outputs,
otherwise adds nothing new
I{4,5} same for inputs
HT{10 s}?I1 wait 10 s to click I1(pin 4) high
if clicked lit up LED1
if time expires without I1 clicked lit up LED2
Run this line several times clicking or not switch at I1. Clean and nice isn't it? As this is a console-run, typing during line execution will abort current line execution in unknown state.
This is a simple mul program 'blinkyH.ml' developed from last console line in 9 written in 'halting' manner.
O=3,O{12,13},I{4,5} # initial block
J1 # Job1 entry point
H?I1 O1=0,O2=1 # halt&wait if set I1, lit up LED1, not lit LED2
HT{5 s} O1=1,O2=0 # halt for 5 sec. Then change LEDs state
HT{5 s} O=3 # halt for 5 sec. turn them off
E # End of job. Loop to J1
The program consists of initial block, which executes just once at the beginning of the program execution. Here we set some initial parameters to initiate jobs.
Then follows a Job1 block – from J1 to E. This one will be executed as a continuous loop. A job may have several E from different lines but just one entry point in our example – J1 written at the beginning of a line. This causes every E to loop back to J1. If E is not found, your program will try to go outside program length and stop with error.
Several words about writing style:
Be sure that you press ENTER after the last 'E' at the last line when writing a program otherwise it will not be read as valid one!
Naturally we may write first line as 3 one:
O=3
O{12,13}
I{4,5}
This gives us nothing but wasted space with these 3 lines as in little processors we are limited to 127 lines in overall. Better to use one line instead.
Basically we are able to write the whole program as it follows on the next two lines.
O=3,O{12,13},I{4,5}
J1,H?I1 O1=0,O2=1,HT{5 s} O1=1,O2=0,HT{5 s} O=3,E #do one cycle and check I1
Don't worry – it will work just fine. But in smallest mul- usable 8-bit Pic-processor we have only 512 bytes RAM so the one line length is limited to 32 bytes, at 16-bit ones – to 256 bytes and in 32-bit – to 512 bytes in common case.
So write your mul-program in such way you will be able to read and understand it a month later.
In overall, a mul-program has the following structure:
'initial block' (10.1)
...
J1
...
E
J2
...
E
...
...
J15 # may vary according to processor specifications from a single Job to 7,15 ...
...
E
Job 0 is console dedicated and hence should not be used in a user-program. How a mul-program does job? After start it fills job-pointers in mul-stack (usually 8-steps deep see mul.c) in such a way that Job 0 points to first found job which points to next found etc. and the last one points back to Job 0. So as your Jobs appear in your program, this way they will be executed no matter what number they have. At first mul executes once 'initial block' and then runs through all the Jobs from first to last at once, next again through all the Jobs from first to last etc. At the end of each cycle, mul returns processor to the main program in order for it to do its role, then the main program calls mul again. If a Job halts and waits, it does not block the program rather shifts execution to its next Job. The user has the feeling that all the jobs run simultaneously. In a multi-processor system jobs may run indeed simultaneously.
To resume a suspended job write:
J2=1
To stop an active job:
J1=0
This will reset job's stack pointer to initial, since job may be currently in a subroutines execution. To check if a job is active or halted write:
!J1
0 - Job halted
1 - Job active
Including 'J1' in an expression sometimes may confuse you while you may set J1=1, but after J1 gets to a Halt instruction J1 will be at 0 as the job is halted itself.
When writing a mul program keep in mind that you are not allowed to keep program in an infinite loop, until you do some job. That is why mul does not use for{}-command defined, instead you are encouraged to do a larger job at several small parts program cycles. Those who are familiar with RTOS know what I'm talking about. At some platforms where mul runs at top (ESP8266, SIM900) you have a limit of several milliseconds (for ESP 10mS) to own processor. This time must be enough for mul to run across all the Jobs you give it, plus all the checks and sets it has to make.
So definitely writing an ml-program in a halting-manner is good as each job makes just a single check and then shifts to the next job.
Same work can be done in a non-halting-manner. Next example names 'blinky.ml'
O=3,O{12,13},I{4,5}
F1=1 # we need this flag to catch I1 rising front
J1
?I1*F1 F1=0,O1=0,O2=1,T1{2 s} # clear F1 to not execute this row until next cycle
?T1 T1=0,O1=1,O2=0,T2{2 s} # if T1 expire, clear T1 flag set Outs and start T2
?T2 T2=0,O=3,F1=1 # if T2 expire, clear its flag, Outs and set F1 to enable row 4
E # be sure that a ”\r\n” presents at the end of this row!
As you see this is not the case of using non-halting-manner to write mul program as every pass will do all the line's checks to decide what to do. Nevertheless sometimes this is the right if not the only way to write a mul program. I advise you to load and trace this program (see 12) to better understand the mul-program processing.
Connect your board to PC, run your favorite Terminal program (9600/8N1) switch ON Caps lock and type “!V1” press ENTER. You should receive a '0' (if V1 not set yet) indicating that connection is OK. If not – check if your Terminal program sends “\r\n” at ENTER press.
Once you are able to communicate with your board, type W”main.ml” (from Write):
W”main.ml”
You are given a prompt, wait up to 30 sec. to start loading program. This timer restarts every time a new line is entered, so you may simply type program line by line. If you miss - a 'W stopped' message appears and program load aborts, living your old program as is. Last is not the case if mul compilation was done using directive '#define USE_ROM_PRG' in some small processors, as they load and save program line by line in EEPROM then load and execute it from there line by line.
if you use a Hyper Terminal, send your program as 'Text file', at the end of the sending press to inform the loader that loading is ending (hold down Ctrl key and press Z).
If you use MySerial right click in 'Type here', choose 'Send File' choose file in dialog and it will be sent. Then type '0x1a' (x-must be lower case, 'a' may be 'A') and press ENTER, as symbol 0x1A = ASCII(Ctrl_Z)=26 decimal.
To omit Ctrl_Z sending at end, first check the file length(for example 72) you'll try to send and write:
W”main.ml”,72 # instead of W”main.ml” to stop after length reached
Any case you should receive a message
Load end!
Type a R”main.ml” (from Read) and you'll get something like next:
used 6 lines 72 mem
from 128 lines 4096 mem
O=3,O{12,13},I{4,5}
J1
H?I1 O1=0,O2=1
HT{5 s} O1=1,O2=0
HT{5 s} O=3
E
Type G (from Go) and your program will run. Setting I1(pin 4) to High will do one cycle and back to I1 check.
Now is the right time to say a couple of words for:
As you might have guessed you are allowed to execute all the mul commands from console. Except jobs J1..J(last index you may use), as their pointers are set at ml-program load and initialization.
**You may use the following one-letter commands:**
W - Write a file to processor (see 20. File Handling)
R - Read a file loaded in processor
L - Give a file system list
G - Go. Start program execution from current point (after B) or from beginning (after E)
B - Break program
X - eXecute one step and break
E - Ends program execution.
Q - Query files system available space
Those commands are very useful when debugging an ml-program. You may test it. Take in account that if program is halted waiting an event or a timer, it will break after Halt ends so wait patiently if HT{} or raise an event if H?. The state of art will come after we get familiar with the rest of mul-commands.
In you program you may use jumps to move to a specific line. Use 'G' (from 'goto') following by a line number to go to. Be aware that currently G-command uses only absolute and relative line-numbers and not supports labels. That is why you must pay attention when adding and removing a line since this will affect some line-numbers and may lead to inappropriate jump to a wrong line. An example to jump to line 4 if F1=1 is shown below:
?F1 G4; (13.1)
You can't use a loop back to line yourself in order to wait an event. For example suppose we write the following for line 2:
?I4*I5 O5=1:G2; (13.2)
meaning 'if I4*I5=1 set O5: else loop back to wait'. This will cause an 'infinite loop' in your program because all the processor time will be consumed from this line and no other jobs will be executed. Better use a Halt instruction
H?I4*I5 O5=1
But you may jump into the same line if a Halt instruction presents in it, as when a Job Halted mul shifts to next Job available. For example:
?V1<V2 !V1,HT{1 s},V1=V1+1,G+0:E;
As you see you are allowed to use calculated Goto (see 1. General Rules).
If some part of the code appears more than once in a ml-program you may prefer to use a subroutine using command 'C' (from 'call') and a line number.
?F1 C4; (13.3)
Similar to G-command, C-command uses absolute or relative line numbers. All variables used in a subroutine are global that is changing variables value in a subroutine reflects in a change in its value in global. Subroutines should be written next to the last line of the main loop. No need for a subroutine mark at the beginning of subroutine block but there must be one or several 'E' in the subroutine, which will instruct the interpreter to return to the next command after the called instruction. Example – Vcount.ml:
V1=0
J1
V2=10,B(V2=10),V1=0,C+3 # set limit to 10, then call count subroutine
V2=20,V1=0,C+2 # set limit to 20, then call count subroutine
J1=0 # kill job 1
?V1<V2 !"V1=%adV1",HT{1,s},V1=V1+1,G+0:E;
'Q' instruction Quits executing of current line and shifts to the next one
'H' instruction (from Halt) we discussed in 9.
'B' instruction is used to set a control break point for debug purpose. It has 2 forms:
* a pure 'B' breaks the program in current point and using command line you may explore the state of the
program variables in the current moment
* 'B(condition)' performs a 'conditional break' if the 'condition' is true. Ex: B(I4>0),
After debugging and fine tuning a program, all the break points should be removed!
'Z' instruction will cause reboot of your system. Used when a new main.ml is uploaded to start it. See 20 - near
end for more info.
As every real-time working device with respect to itself mul has a real-time Clock. To derive them you may use the next commands:
D0 - Year (last 2 digits)
D1 - Month
D2 - Day
D3 - hour
D4 - min
D5 - sec*4
D6 - Day of Week
And corresponding commands to set them to look as
D0=15,D1=12,D2=31,...
D6 is calculated from Date and there is no sense to set it.
If you are a beginner skip Strings and Pipes as they require more powerful processor, good programming skills and they are not even present in light ml-versions for small processors. Return to them when you are fluent in mul.
Strings in ml are in fact binary buffers of unsigned chars, so they chars may contain any values from 0 to 255 included. They are dedicated to exchange and store data in an ordered way and are used close to Pipes, which we comment later.
A string in ml is defined with letter '$' and index from 1 to RANGE-1 (i.e. 7,15,31..). These indexes may be an (expression) as described in 1. General Rules. The length of a string depends on the variable’s '$len' value defined in 'globals.h'. It's processor specific and can vary with processor memory available from 32 to 256, 512, 1024 chars. The positions for chars in a string enumerates from 1, i.e. 'one based'.
To fill a string with data we use '=' and a couple of sentences concatenated with '+'. For example:
$1=”MyData:”+” %4ad(A1+100) ”+$2+”\r\n” (16.1)
As you see we can use “plane text” in double quotes(if contains “ inside, escape with \ i.e. insert \”) another string or parts from another string (see 17.String operations) a variable with format %.... in front, which presents 'variable_value.ToString'. May be an expression of variables
For a plain text – you must escape “ and % by putting a backslash in front of it \”,%. Additionally you may insert CrLf at line end by using \r\n. No other control characters. But you may insert them as ASCII value as 'horizontal tab' =%d9 (ASCII(9).
When you want to add a number or numerical value of a variable you have to use format prefixes which instruct interpreter how to represent them in your string.
Prefix starts with
%
2 an integer, which sets how many positions to occupy the number,
d,x,ad,ax,aX decimal, hexadecimal, ASCII decimal, ASCII hex, ASCII hex-big letters
123, 0x234, V1, or {V1,0xff,V3} a single digit or var, or a{ list} of variables
If you do not use {list}, be sure to put ',' or space after the variable; if use an expression – put it in () for example
%ad(V1*4+V2).
If you are not sure what is the difference between decimal, hexadecimal, ASCII decimal, ASCII hex, ASCII hex-big letters, you may see what happens if you write a string in different formats. Best switch your MySerial to 'HEX' and send the following:
!”%4d1234”
!”%4ad1234”
!”%4x0x1a2b”
!”%4ax0x1a2b”
!”%4aX0x1a2b”
Commands that have an 'a' after %4 may be seen when switching to ASCII too.
When you do not want to fix number's length, you may omit the digit after %, so the numbers will be printed 'as is'. Try:
!”%ad1234”
!”%ad123”
Very important: when you write hex numbers always write 'a','b','c','d','e','f' in low case otherwise you may get in trouble: can you explain what 0xA2 is – the hex number 0xa2 or Analog input 2 taken as hex value?
One advise: when you include numbers as hex (prefix x,ax) – write them into the list in hex, if as dec (prefix d,ad)– write them into the list in dec. Then they appear in the string 'as is'.
Take into account that if you reserve lower lenght space for a long number you will cut the leading numbers:
!”%2ax0x1234” # will produce 33 34 (in ASCII '34')
There are the following string operations in ml:
$1+$2 concatenate, result is $=$1+$2 in that order. This operation is lowest priority among all the
string operations!
$1=$2 written in an expression means 'compare'. The result is a digit=1 if $1=$2, 0 else
$1/$2 find $2 in $1. The result is a digit=0 if not found, position N in $1 after $2 else
$1/number check element. Result is a digit= ASCII at pos.=number
$1[number cut tail. The result is a $=$1 without elements from 'number' incl. to the end
$1[ cut trailing control symbols
$1]number cut leading. The result is a $=$1 from 'number' incl. to the end
$1] cut leading control symbols
$1][
or
$1[] cut leading and trailing control symbols
number*$1
or
$1*number) the result is the same $ if number<>0, 'nothing' else
_$1 length result is a number=length of $1 in bytes
Some examples will be more useful than any explanations:
Suppose $1=”123456”, $2=”34”
!$1+$2 (17.1)
12345634
!($1=$2) #must be in parenthesis, otherwise means 'make $1 equal to $2' (17.2)
0 ( not equal)
I will like to draw you attention to the next command 'found string in string' as it differs from common similar find commands in other languages:
!($1/$2) (17.3)
5
The result if $2 found in $1 will show 1-based index right next of position where $2 ends in $1, zero else.
$2=34
was found to start at pos. 3 in $1 (1-based order!) +2 is the length (_$2 =2) of the searched string = 5 so the result gives the position right after match is found!
Why is it done in such way? Because when looking for a match we usually are interested in the content after that match for example:
$1=”1234V1=111Test”
we would like to extract value of V1 send to us through some interface. In C we should find where “V1=” is, then add length of “V1=” which is 3 then convert to integer the following. In mul the same is done in this way:
V1=$1]$1/”V1=” # find “V1=” in $1, get all after “=” to the end $1, and try to convert to int V1
Put a '!' left most to direct print V1 or type !V1 after above command to see the result:
111
Keep in mind that conversion is always done at base 10 i.e. decimal. If you prefer to convert to base 16 put a '0x' in front of string or string expression. You should put an expression containing sum of strings in parenthesis. Example:
V1=0x$1]3
V1=0x($1+$2)
If the searched string was not found in $1 then we get V1=0. Try
!V1=$1]$1/”V2=”
0
$1[5 # =1234 cut tail from 5 incl. (17.4)
$1]3 # =3456 cut leading up to 3 (17.5)
$1[ # cut trailing control symbols
$1] # cut leading control symbols
F1*$1 # =123456 if F1<>0, $1=empty if F1=0 (17.6)
_$1 # =6 the length of $1 is 6 bytes (17.7)
String operations are executed 'from left to right'. If an operation is not possible, then the first possible is executed and process restarts from leftmost. Finally will be done the sum of string parts. This gives the opportunity to write such expressions:
$1[$1/$2
As $1[$1 is not defined as valid operations, we move to next:
$1/$2=5 (see 17.3)
Now we start from left and this is:
$1[5 (see 17.4)
which makes sense and gives a $=1234
Naturally, when such order does not work for you, use parenthesis to reorder operations. Additionally, you are allowed to use any kind of operations mixed – arithmetic, logic, strings as long as you know what you are doing. ml will try to do its best to understand you and will give an answer. As mul gives errors rarely, sometimes that result might not be what you might have expected.
The operations '[' and ']' may get you confused at first, as if $1=”123456”, then
$1[1 # will give 'nothing' as well as $1[0, and $1[_$1 will give $1 without last element
and
$1]1 # will give the same string and $1]_$1 will give the last element of $1 (i.e. 6).
But they are very useful in string manipulation. Suppose you have a stream received in a string($1) and know that a flag(0xFF) marks the beginning of a phrase. Executing:
$1=$1]$1/”%x0xff”
will find first 0xFF mark in $1 and cut leading garbage and mark and copy the rest of the string back to $1. Now if phrase ends with “\r\n” and you don't need them executed
$1[ # remove trailing control chars
If you know, that a phrase ends with “\r\n” you may get pure data including trailing control symbols by:
$2=$1[$1/”%x{13,10}” or $2=$1[$1/”\r\n”
Expressions like 17.6 gives you the opportunity to dynamically compose different strings according to the current conditions or variable values.
$2=$1+(A1>1200)*$2
When you perform string operation on strings, the strings themselves do not change unless you decide to do so:
$1=$1[3
will replace $1 with result on the right side.
Performs initialization of a peripheral unit to work in the appropriate way. Operations:
U<u-num>{type, sub-type, intf_num, ...parameters...} (18.1)
where:
u-num - number of unit to deal with. You may assign any value from 1 to defined with INTERFACES_NUM-1
which is set according to processor specifics.
U0 is opened upon mul-init procedure as default console (usually serial) interface;
U15 or U(INTERFACES_NUM-1) is file type interface opened upon mul-init as main.ml program containing file. Why – we will see in 20.
So the users can use the rest 1...14 indexes.
type & sub-type - have to be one of the following:
**type sub-type**
----------------------------------------
1 Serial 1 UART/RS232
1 2 UART/RS485
1 3 SPI Master
1 4 SPI Slave
1 5 I2C Master
1 6 I2C Slave
---------------------------------------
2 USB 1 HUB
2 2 Device
---------------------------------------
3 BT 1 Master
3 2 Slave
---------------------------------------
4 GSM 1 Voice call
4 2 Video call
4 3 CSD
4 4 GPRS
4 5 SMS
---------------------------------------
5 WiFi 1 Station
5 2 AP
---------------------------------------
6 Stream 1 File
6 2 RAM
6 3 Encrypted RAM
6 4 Audio
6 5 Video
---------------------------------------
7 NET 1 Station
7 2 AP
---------------------------------------
8 CAN 1 Master
8 2 Slave
---------------------------------------
9 Device 1 Analog Output
9 2 PWM
9 3 3-phase PWM
9 4 Pulse Coder
9 5 Manchester coder/decoder
9 6 DTMF coder/decoder
9 7 Tone Generator
- intf_num - specifies which hardware to use if several of the same type are available (UART0, UART1,..)
- parameters - a strong- ordered string, describing set up parameters of interface
Developers are free to add more interfaces and devices expanding the list above. As this list may grow a lot, please be kind to make suggestions of new interfaces added at mulblog.wordpress.com, in order to support a standard list, so everyone can use it. In additional developers are asked to supply a '?' command instead of 'parameters' passing which returns a string describing the exact 'parameters' meaning and syntax.
Examples:
U{1,1,?} # ask serial uart parameters descriptor
Ux{1,1,hd_N=0, baud_rate/100,8N1,hd_flow_c=0/1}
U1{1,1,0,96,8N1,0} # open U1 as serial, uart, port 0, 9600, 8N1, no_flow_contr.
U1{0} # close Unit 1
U{5,1,?} # ask WiFi Station parameters descriptor
Ux{5,1-Station,“ssid”,”pword”,(optional)”IP.IP.IP.IP”,”GW.GW.GW.GW”,”NM.NM.NM.NM”}
U{5,2,?} # ask WiFi Access Point parameters descriptor
Ux{5,2-AP,“ssid”,”pword”,(optional)”IP.IP.IP.IP”,auth_mode=0/4,channel=1/13,use_dhcp=1/0}
U1{5,1,”myNet”,”myPW”} # open Unit1 as WiFi Station
U2{5,2,”ESP8266”,”12345678”,”192.168.4.1”,3,7,1} #open U2 as AP
U3{9,2,100} # open a PWM (Pulse Width Modulation) interface (Device 9, type 2) frequency 100 Hz
!U1 # print U1 state 0-closed, 1-created, 2-opened, 3-connected, 4-error, 5-disconnected
1- created means that interface is newly created ( for ex. a new file)
2- opened means that an existing interface is opened (for ex. an existing file)
3- connected means that at least one pipe is attached to interfaces
4- error indicates an error occurred in interface work
5- disconnected means that an interface is no longer valid for some reason – ( for ex. a TCP connection died).
Pipes are bidirectional buffers. In general there are two main pipe types:
one that will exchange strings or steam type information as for files, net, serial, audio, video e.t.c
second ones will send/receive only strict one or enumerated number of digital values and in most cases only one direction, as for example a temperature sensor, a PWM output or similar.
Pipe may carry an index from 0 to PIPE_COUNT which is a constant defined in ProcessorSpecific.h. Pipe 0 is console dedicated by default attached to a interface - see 'HDinit' in 'ProcessorSpecific.c'. So opening a new pipe as P01..P015 at a different interface duplicates the coexisting one (if not closed meanwhile), and console will serve both (see Useful examples-Ex2 just above Appendix A). Each pipe consist of two sub-pipes receive sub-pipe and transmit sub-pipe. The processor reads from receive sub-pipe incoming information and sends information trough transmit one. Each sub-pipe consist of a buffer string part (no less than string length see ProcessorSpecific.h) with separate length-value representing actual length off data in it. This gives programmer the opportunity to put in a buffer any char values, including 0 up to 255. Additional a signed integer represents digital value (base 10) of the string in pipe:
Pipe RxPipe | buffer = “100 is my favourite number!”
| length of valid data = 27
| RxVal =100
__________________________
TxPipe | buffer = “ -100 is OK too!”
| length-val= 15
| TxVal = -100
As for mul a string pipe does not distinguish from a val pipe, writing a string to a pipe causes the same result while writing an integer clears the string part:
P1=”100”
and
P1=100
differ by this, that first one will produce a string in Tx buffer “100” with length=3 and set TxVal=100, while the second one will produce an empty string “” with length=0 and set TxVal=100.
It's programmer choice which form of record to use in his mul-program.
It's interface's responsibility to decide which part of Tx Pipe to process. A strict output digital interface (for example a PWM) will read and process only the digital part of Tx pipe, after that it will copy the TxVal to RxVal so the user can extract and use it in a sentence, since no one expect from a PWM to produce a value during operation.
Similarly one interface decides which part of an Rx pipe to complete – the string part, the integer part or both. This decision is obvious in most cases, as an thermometer will fill only the digital part while for a serial interface is common to fill both.
In earlier mul-versions you are allowed to attach same index pipes to different interfaces. Although this gives some additional opportunities for data transfer, this reduces the clarity of a mul-program. So now each pipe-index is unique in mul-program and opening a new pipe with index of already opened one overwrites existing pipe with new one. In this connection a close pipe command looks very natural - “P1{0}” - no more need to specify an interface number.
As different interfaces need different parameters to initialize pipes attached, pipe-open command syntax differs from interface to interface. More over different pipe types (string or digital) do not require same buffers length. Once pipe opened, pipe-handling has no difference. Formally an open-pipe command looks like:
Px{<U_number>, ...parameters...} (19.1)
where U-number is Unit number you assigned when opening an interface (see 18). The expression sounds 'Open Pipe with pipe-index 'x', attach it to interface 'U_number', and tune it using 'parameters' settings.
If you want to open a pipe as a second CONSOLE, use '0' after 'P' and before the index:
P0x{<U_number>, ...parameters...} (19.2)
Command examples: suppose U1 type 1 (Serial)
P1{1} # open pipe 1 to interface unit 1
P01{1} # open pipe 1 as CONSOLE to interface unit 1
no parameters required, as interface already set (see 18).
if U2 set as type 5 (WiFi)
P1{2,S,”TCP”,”0.0.0.0”,2345} # open pipe 1 to U2, as Server, proto, IP, port
P2{2,C,”TCP”,”192.168.1.3”,2323} # open pipe 2 to U2, as Client, proto, IP, port
P3{2,C,”TCP”,”eta-sys.net”,8080} # open pipe 3 to U2, as Client, proto, host name, port
!P1 # print pipe value
P3{3,12} # open Pipe3 as PWM channel to PWM interface 3, use processor pin 12
P1{2,0} # close pipe 1 interface 2
P1{0} # close pipe 1
When closing a pipe we have to point to interface we wont to remove pipe from, as same pipe may be opened at different interfaces simultaneously. This way P1 will still work if open in an other interface too.
P1=”Test\r\n” # send 'Test' to P1 txPipe
P1=”” or P1=0; # clear P1 txPipe
Keep in mind that:
- string txPipes work in 'append' mode;
- txPipe clears itself after a successful sending of content to the interface!
So asking:
!_P1 # print length of P1 txPipe
will indicate successful sending of message if length=0. Nothing else have to be done with txPipe.
If you want to work with rxPipe use $ in front of Pipe, when you want to extract and manipulate the string value:
$1=$P1 # get content of P1 rxPipe to $tring 1
$1=$P1[(P1/”\r\n”) # get in $1 everything incl. \r\n
V1=_$P1 # get length of P1 rxPipe
$P1=$P1]P1/”\r\n” # P1 rxPipe = what remains after “\r\n”
See also 17. String operations. To extract digital value use simply pipe in an expression:
!P1
V1=P1
V1=P1*100
Very important!
Keeping all rxPipes clean is programmer responsibility. So don't forget to flush a pipe after obtaining information you need from it!
$P1=”” # clear P1 rxPipe and RxVal
In general - file handling adds nothing new to the already said about Units and Pipes, but let’s say a couple of words in addition.
File System (FS) is supported at middle and high range processors. At ESP8266 it is 64k large and resides in flash starting from address 0x10000.
You may define 15 different user files, open 5 of them simultaneously (if you have RAM enough available), plus a main.ml – the mul program itself. As mentioned in 18, files are treated as Units type 6, sub-type 1.
File names are up to 10 chars long, including extension, if available.
One file length is up to FS capacity.
Files sum length is up to FS capacity.
You can check memory left executing Q-console(RAM) and L-console(FLASH) commands.
It's essential for file-pipes that receive and transmit sub-pipes coincide. So changes made to a file are visible to programmer right after they are done.
User may create or upload files using console.
Writing (uploading) a file:
W”file_name”,(optional) length in bytes, 0/1- left/strip
For example:
W”file_name” # load a file as-is. Load ends with <mcZ> (ASCII 0x1a)
W”file_name”,length # load a file until 'length' reach (binary file)
W”file_name”,0,1 # load a file, length not specified, strip garbage (comments, tabs, e.t.c.)
W”file_name”,1978,1 # load a file, until length, strip garbage (comments, tabs, e.t.c.)
Open/ create file may be done from console or in a program:
Ux{6,1,”file_name”} # open/ create as Unit x =1...15 (see 18)
If file with 'file_name' not exist it will be created. Take in account that while not 'Close' the new created file (if not empty) it will be not saved in flash!
Attach a pipe to file at Unit 1:
P1{1}
Read a file to console:
R”file_name” # no matter if file is open or not
Take actions to a file (file should be opened and at least a pipe (let say 1) is attached to it:
!$P1 # will output file from the beginning to pipe size or EOF( End Of File)
!$P1]200 # print from 200 (incl.) to pipe size or EOF
!$P1](P1/”My ”)[4 # find “My “ and cut next 4 chars=”test”
P1+100=”My test” # replace at position 100... with “My test”
P1+_$P1+1=”My test” # append “My test”
P1+1="*" # edit from beginning. Replace first symbol with '*'
P1=0 # set file= nothing (same P1=””)
Close a file:
U1{0} # flush and close file
Closing a file causes the file to be saved in flash. Closing an empty file causes the file to be removed from FS.
Deleting a file from flash should be done such way:
U1{6,1,”file_name”} # open existing file
P1{1} # attach a pipe to it
P1=0 # empty file
U1{0} # close/remove from flash
To copy/rename a file you have to write a couple of lines. Do it yourself.
main.ml
One of your files may be main.ml. This is the default mul program which will be loaded upon power-on of device and will start automatically. Upon init-mul procedure this file is opened as last one U15 (U(INTERFACES_NUM-1)) Unit giving you the opportunity to manipulate it. Take a look at the program below.
O=3,O{12,11},I{4,5} # initial block
P1{15}, $1="!\"My old",V1=$P1/$1-_$1,P1+V1="!\"My new row\r\n\""
J1 # Job1 entry point
H?I1 O1=0,O2=1 # halt&wait if set I1, lit up LED1, not lit LED2
HT{5 s} O1=1,O2=0 # halt for 5 sec. Change LEDs state
HT{5 s} O=3 # halt for 5 sec. turn them off
!"My old row\r\n"
E # End of job. Loop to J1
As you see line 2 changes line 7, causing program to print message “My new line” to console instead of “My old line”. Be careful that your newly written line has less or equal length with the old one while line- pointers are set at the beginning of each line upon mul-init and otherwise will be misrepresent.
This gives you the ability to write a mul- program which writes a new mul program. You may choose to save or not the newly written program. Closing U15 will cause the new program to be saved overwriting over the old one. If you reboot the system now (Z-command) the new main.ml will take the place of the old one.
If you do not close U15, the new program will work until next power-off/power-on event occurs.
Moreover you can upload a new main.ml during execution of previous program not affecting its work. Then you may decide when to reboot in order for the new ml-program to take place instead of the old one.
The last said will not cover the case when main.ml is executed from EEPROM/FLASH without transferring to RAM in some low-range processors.
No more theory!
Let's practice!
Ex1: Simple Alarm System We will use:
- ESP8266 ESP-7
- 1 input alarm ON/OFF GPIO_12 1=ON
- 1 input door open GPIO_13 0=door open
- 1 output LED indicator GPIO_0 0=lit up
- 1 output blinkers GPIO_2 1=lit up
- 1 output sounder GPIO_15 1=sound
Try to write a program, that checks if the system is armed and if yes and a door is opened, will sound and flash for 30 sec. You can find a solution below.
I{12,13},O{0,2,15},F1=0 # for Windows demo I{1,2},O{3,4,5},F1=0
J1
?I1 ?F1=0 F1=1,C+4,T1{1 s};: ?F1 C+5,F1=0; J2=0,O=1,E;
?T1 T1{1 s} O1=^ #blink every 1 sec LED
?I2=0 ?_T2=0 T2{30 s},J2=1 #if alarm start T2,J2
E
O2=1,HT{1 s},O2=0,E #flash blinkers for 1 sec when arm
O2=1,HT{500 m},O2=0,HT{500 m},O2=1,HT{500 m},O2=0,E #flash blinkers twice when disarm
J2
?T2 H # sound and flash blinkers until T2 or system OFF
O=O|6,HT{1 s},O=O&1,HT{1 s},E
Ex2: Add a Second Console at WiFi AP named 'esp'
You may found useful to add a second WiFi console to an ESP8266 module. Write next rows:
U14{5,2,"esp"}
P015{14,S,"TCP",2345}
to start a WiFi AP and to open a TCP console and as server pipe at port 2345. Next connect to 'esp' WiFi AP, run Putty (set 'Raw' to IP 192.168.4.1:2345) or MySerial as TCP Client, and use it as a second console to talk with ESP.
If you write a 'main.ml' like:
U14{5,2,"esp"},P015{14,S,"TCP",2345}
J1
H
E
it will start at Power-up. Remember that you need at last one job defined in your program which will execute the start jobs, then Halts (in this case), but still running in background so may be ended with 'E' not affecting the new console. Naturally you can't use U14 any more.
You may add/edit first row to any of yours ml-programs, adding password to authorize connection.
Ex3: A simple job to set time from "ptbtime1.ptb.de" time server
This is 'get_datetime.ml' program from mul-examples.
J1 looks familiar. More interesting is J7 dedicated to set time from a time server when mul powers-up. J1 will wait 30 sec. J7 to complete. This is important when mul-program runs on schedule and means nothing in this example, so you may remove first row in order both jobs to run simultaneously. From time to time you may release J7 to correct date/time. Take in account that time is set UTC. If you want you may add a Time Zone Correction add appropriate correction to D3(hour). Do this when current UTC time and the Corrected one do not overlap 24h=0h interval or you will get in trouble calculating current day, month and year.
J7 affects U14, P14, $14, V14. Naturally you may replace them. You have to insert your WiFi net name and Pass Word in U14 start sequence. If you already have an open Station interface you don't need row 11(U14{...}). If you not need such interface after setting the time, replace P14{0} at row 12 end with U14{0} to free interface and pipes attached.
As time server returns date/time as such a string: $14=”03 MAY 2016 15:22:44 UTC”, we produce a digit from 3 letters (ASCII) M(=77), A(= 65), Y(=89) V14 =77+65+89=231 to set D5=5.
HT{30 s} #wait J7 to set time
!"J1 Start\r\n"
O=3,O{12,13},I{4,5}
F1=1
J1
?I1*F1 F1=0,O1=0,O2=1,T1{2 s}
?T1 T1=0,O1=1,O2=0,T2{2 s}
?T2 T2=0,O=3,F1=1
E
J7 #execute J7=1 when you want to set time
U14{5,1,"myNET","myPW"}
HT{5 s},P14{14,C,"TCP","ptbtime1.ptb.de",13},HT{15 s}?_$P14>0 $14=$P14;P14{0}
V14=$14/4+$14/5+$14/6
?V14=217 D1=1:?V14=205 D1=2:?V14=224 D1=3:?V14=227 D1=4:?V14=231 D1=5:?V14=237_ D1=6:?V14=235 D1=7:?V14=221 D1=8:?V14=232 D1=9:?V14=230 D1=10:?V14=243 D1=11:?V14=204 D1=12:D1=0
?D1 D0=$14]10,D2=$14,D3=$14]13,D4=$14]16,D5=($14]19)*4
!"%2adD2 %2adD1 20%2adD0 %2ad{D3}:%2ad{D4}:%2ad{D5/4} day %ad{D6}\r\n",H
E
That's all, folks!
Naturally you will need a demo-board, a couple of resistors (1k, 10k) and 2 LED-s, USB/USART converter (or RS232/USART) and ESP8266 module.
There are several hardware versions of ESP module. In details how to connect, start and compile, download and run a C-program at ESP8266 is well explained in free for downloading book http://neilkolban.com/tech/wp-content/uploads/2015/08/The-ESP8266-Book-August-2015.pdf . Thank you Neil – excellent work!
Now install your development environment. I prefer http://programs74.ru/udkew-en.html thanks to Mikhail Grigorev great job.
Insert mul-project in your work-space.
If in doubt check Neil's book for hardware and Mikhail Grigorev's recommendations for creating a new project.
Open file ProcessorSpecific.h and change
#define versionHD 7 // ESP-7 hardware description
to yours. If your version cannot be found in file ProcessorSpecific.c, choose one similar to yours, as this just defines pins available and functions to them according to eagle_soc.h in ESP8266_SDK\include or write a new one using existing one as pattern.
Better use a proven Make file from some working project in your SDK. You should do some corrections to it:
1. Change COM to yours (MySerial lists all the ports available at start or after RESTART button pressed, so you don't need to go to Device manager to find it).
2. Change SRC to point to user_main
SRC :=
3. Add to Makefile next command to be able to format you File System during download if you wish:
BLANK_FILE ?= $(SDK_BASE)/bin
flash_and_formatFS: firmware/0x00000.bin $(BLANK_FILE)/blank.bin firmware/0x40000.bin
$(ESPTOOL) -p $(ESPPORT) write_flash 0x00000 firmware/0x00000.bin 0x10000 $(BLANK_FILE)/blank.bin 0x40000 firmware/0x40000.bin
In your project's 'Make Target' targets add a new target 'flash_and_formatFS'.
Save Makefile, cross your fingers and run make 'all'. If there are no errors flash both new files you get in 'firmware' and a blank file from path above at address 0x10000 (in Mikhail's SDK just find rightmost if projects 'mul' and click target
'flash_and_formatFS').
Run your favorite Terminal-program, set 9600/8N1, +ASCII, +addCR, +addLF. and type:
v?
You should get answer:
v07-0101
which means HD version 07 (ESP-07), SW version 0101. If everything is OK turn ESP power off and connect two LEDs with resistors to GPIO_12 and GPIO_13 as shown.
If you are ready you may return to exploring the section you came from.
In ESP8266 version you can use up to:
- 15 inputs – from I1 to I15 (although ESP has just 8)
- 15 outputs - from O1 to O15 (although ESP has just 8)
- 15 analog in's – from A1 to A15 (although ESP has just one)
- 15 flags – from F1 to F15
- 15 int. vars – from V1 to V15
- 15 timers – from T1 to T15
- 14 interface Units – from U1 to U14 (U15 is a file presents 'main.ml' program)
- 15 pipes P1 to P15, up to 5 pipes to one Unit
- 7 tasks – from 1 to 7 (task 0 is console-dedicated)
- mul-program must be less than 1024 rows
- string length (_$)– up to 256 characters
- pipe's buffer length (_$P) – up to 512 characters (if pipe attached to a file, then its length is whole file long)
FS:
- up to 15 files + one 'main.ml'
- overall FLASH 64k
- file name length: max. 10 chars
Some remarks about mulNext
To be able to implement mul in more complex micro-devices we have to resolve the next main problems:
- device pads type definition
- code reuse
1.Pad type definition should cover:
- input's pull-up/pull-down set
- type of input/output buffers in respect to voltage level
- input's interrupt attachment
I think the best way is expand mul to include sentences as:
I1{pu/pd, type, *interrupt} # where pu/pd='1' for pu,'0' for pd, and '-' if no one
# type goes for TTL,LVTTL,CMOS,LVCMOS15,18,25,33 …
# dedicated interrupt routine row number
or I1{pu/pd, type, *F1} # dedicated interrupt Flag set
We may use sentences as:
I1,2,3,11,12{settings} # a list of inputs with same settings
I5...10{settings} # inputs in range
We may use separate definitions for interrupts
I11,12,13{-,LVCMOS15,{*10,*20,*F1}}
or I11,12,13{-,LVCMOS15} I11,12,13{*10,*20,*F1}
All this said for inputs can be count for outputs too.
Several words about interrupts:
- first we have to decide how to arrange interrupts by rising edge, falling edge and interrupt of change. This may be done in set-sentence or in interrupt routine by software.
- using flags(as F1 in example above) to mark interrupts will give us whole different way to write mul-machine. Instead to check in a loop manner each halted task against it halt-condition we may dynamic put in a look-up table all the variables, flags, timer-flags for all tasks according to each task halt-condition and unblock just those who pass halt-condition check. The next step will be to arrange this look-up table in some area in FPGA 'on the fly', but how this to be done have to answer FPGA designers.
2. Code reuse
At firs – one have to be in able to include a part of code (a subroutine or job) in a new program 'just like that'. This forces us to include formal parameters in a subroutine which will be replaced with the real ones in concrete m-program.
One way this to be done is to extend mul with small letters 'a' for Analogs, 'v' for Var's, 'i', 'o', 't' e.t.c. Next we write our subroutine or job using them, and finally have to pass in a block real variables to this subroutine.
..., C+20{I11,O12,F13},.....
......................................
row'C+20' {i1,o1,f1}
?i1 o1=1:o1=0;f1=^,E
similar:
J1{I11,I12,O12,F13}
?H(i1*i2) o1=1,f1=^
E
I don't think that including some kind of labels for row identification is a good idea, as this may cause additional troubles with code reuse like duplication of row labels.
But I think it will be good choice to include letter 'S' with an index for subroutine-identification and call.
In additional in mixed systems FPGA+Processor may be we will like to mark some how rows dedicated to be executed from FPGA against those dedicated to Processor.
More over we have to state that all the commands to the end of current row dedicated to FPGA execution have to be executed as a 'non-blocking' assignment. This means that all the assignments will take place at once when the current row has to be changed. So F1=F2,F2=F1 will swap F1,F2 value instead of make F1=F2. If we want a 'blocking' assignment we should write each sentence at a different row.
My advise to those who like the mul-idea and will join to developers of it is:
KEEP the PROJECT ASAP!
meaning As-Simple-As-Possible, as we don't have the ambition to write a new C++!