Snake on the PLC? Easy!
- From the sandbox
- Tutorial
Good day, harazhiteli!
It has recently been complained that the topic of “industrial programming” is not sufficiently addressed. I'll try to fix it.
For clarity, we will figure out how to write a classic snake for a controller of the Siemens s7-300 family.
If it became interesting - welcome to cat.
Attention - pictures and a lot of code in an assembler-like language!
The whole program is executed in the organizational block OB1, consists of two functional blocks FB10 and FB11, which have instance data blocks DB10 and 11.
The playing field 10x10 cells itself is a two-dimensional array of 10x10 bytes.
For the operation of our snake, we need to solve a small problem - we need an accurate impulse that arises in time. You can use “Flashing bits”, this is a built-in feature of the controller, but we will create our own pulse generator FB10 with merkers and structures.
You can find a lot of interesting things in the temporary variables OB1, this time we will need the time of the previous program cycle. During this time, the controller “digests” everything that they say and issues values to the outputs, then reads the inputs. It is measured with rather high accuracy, and we believe it.
As soon as the accumulated time becomes greater or equal, a pulse is generated (it will operate only one cycle), we subtract 1000 from it (suddenly we get a little more than 1000, therefore it is impossible to zero) and for one controller cycle we have a positive pulse.
From these pulses it is very easy to add large quantities, for example, 5 seconds.
You can also minutes, hours, but that's another story.
Please note that you cannot use the same front variable twice, this will give a very difficult to catch error in the program logic.
Now it's the turn of the functional block of the snake itself.
Input variables are left, right, up, down, and start commands.
If you have a controller and a discrete input module at hand, you can hang inputs on these variable inputs, to which buttons are attached without fixing. Get a full-fledged slot machine. With special desire, an array can also be made of light bulbs, but I’ll be fired immediately for this =)
The first example is a left move command.
If we filed it, we don’t move to the right, it happened only in this cycle, then we reset all previous commands and announce the movement to the left.
It is freed by simply filling in zeros from 0 to 99th element. The fact is that in STL there is no work with two-dimensional arrays with indirect addressing, so we will represent this array as a one-dimensional element from 0 to 99th element.
At the start, we transfer the snake’s head in direct addressing to element 9.5, make it 2 length, give the command to crawl up, reset the game over and give the command to throw food out to a random point on the playing field.
Next we need to generate food for the snake. I confess that the generation algorithm itself was spied on me at one of Google’s first links, on the PLC for Good website.
It consists in the fact that the controller counts milliseconds of system time. If we take this number, add it to a random one, and then discard the excess, we get a pseudo-random number generator from 0 to a given value.
Further, when X and Y fell out randomly, we take them and calculate the number of the array element. Each step along X means that you need to move to the next row of elements, that is, 10, and each along Y means moving from the 0-element to 1.
As a result, the element of the array X [4] Y [7] turns into the 47th element of the one-dimensional array . We give him status 7 - food.
In case the element is busy, we start the generator again.
After a successful ejection of food, the snake begins its movement, consider an algorithm based on left movement.
In this algorithm, we immediately perform several checks. Divide by 10 - we get the remainder - the number in the line of the current element. If we move to the left, being in the zero element - the end of the game.
The same thing happens if there is anything other than food in the way of movement. If you stumble upon food - cock the bit that you just ate, it will start the generator, extend the tail by 1.
Next is the last algorithm - “Tail Cutter”. He takes the last coordinate as a basis, reads the command at this coordinate and goes in the reverse order from the head of the snake to the tail. If the cell goes beyond the length - delete it by zeroing the value
Thank you very much for your attention to everyone who reached the end of the article, I hope it was interesting.
Link to download the project
It has recently been complained that the topic of “industrial programming” is not sufficiently addressed. I'll try to fix it.
For clarity, we will figure out how to write a classic snake for a controller of the Siemens s7-300 family.
If it became interesting - welcome to cat.
Attention - pictures and a lot of code in an assembler-like language!
The whole program is executed in the organizational block OB1, consists of two functional blocks FB10 and FB11, which have instance data blocks DB10 and 11.
The playing field 10x10 cells itself is a two-dimensional array of 10x10 bytes.
For the operation of our snake, we need to solve a small problem - we need an accurate impulse that arises in time. You can use “Flashing bits”, this is a built-in feature of the controller, but we will create our own pulse generator FB10 with merkers and structures.
You can find a lot of interesting things in the temporary variables OB1, this time we will need the time of the previous program cycle. During this time, the controller “digests” everything that they say and issues values to the outputs, then reads the inputs. It is measured with rather high accuracy, and we believe it.
As soon as the accumulated time becomes greater or equal, a pulse is generated (it will operate only one cycle), we subtract 1000 from it (suddenly we get a little more than 1000, therefore it is impossible to zero) and for one controller cycle we have a positive pulse.
From these pulses it is very easy to add large quantities, for example, 5 seconds.
You can also minutes, hours, but that's another story.
Please note that you cannot use the same front variable twice, this will give a very difficult to catch error in the program logic.
Now it's the turn of the functional block of the snake itself.
Input variables are left, right, up, down, and start commands.
If you have a controller and a discrete input module at hand, you can hang inputs on these variable inputs, to which buttons are attached without fixing. Get a full-fledged slot machine. With special desire, an array can also be made of light bulbs, but I’ll be fired immediately for this =)
The first example is a left move command.
If we filed it, we don’t move to the right, it happened only in this cycle, then we reset all previous commands and announce the movement to the left.
Next, when the game starts, we free the array
A # snake.start
FP # frnts.pos5
JCN done
R # snake.gameover
L 0
T #looper
OPN “massive”
LAR1 P # 0.0
loop: L #looper
L 100
> = I
JC done
L 0
T DBB [AR1, P # 0.0]
+ AR1 P # 1.0
L 1
L #looper
+ I
T #looper
JU loop
done: NOP 0
FP # frnts.pos5
JCN done
R # snake.gameover
L 0
T #looper
OPN “massive”
LAR1 P # 0.0
loop: L #looper
L 100
> = I
JC done
L 0
T DBB [AR1, P # 0.0]
+ AR1 P # 1.0
L 1
L #looper
+ I
T #looper
JU loop
done: NOP 0
It is freed by simply filling in zeros from 0 to 99th element. The fact is that in STL there is no work with two-dimensional arrays with indirect addressing, so we will represent this array as a one-dimensional element from 0 to 99th element.
At the start, we transfer the snake’s head in direct addressing to element 9.5, make it 2 length, give the command to crawl up, reset the game over and give the command to throw food out to a random point on the playing field.
game start
A # snake.start
FP # frnts.pos6
JCN strt
LP # 95.0
T #coordinate
L 5
T “massive” .x [9] .y [5]
L 2
T # tail_cut.lenght
S # move.up
R #snake. gameover
S # random.set_food
strt: NOP 0
FP # frnts.pos6
JCN strt
LP # 95.0
T #coordinate
L 5
T “massive” .x [9] .y [5]
L 2
T # tail_cut.lenght
S # move.up
R #snake. gameover
S # random.set_food
strt: NOP 0
Next we need to generate food for the snake. I confess that the generation algorithm itself was spied on me at one of Google’s first links, on the PLC for Good website.
It consists in the fact that the controller counts milliseconds of system time. If we take this number, add it to a random one, and then discard the excess, we get a pseudo-random number generator from 0 to a given value.
Further, when X and Y fell out randomly, we take them and calculate the number of the array element. Each step along X means that you need to move to the next row of elements, that is, 10, and each along Y means moving from the 0-element to 1.
As a result, the element of the array X [4] Y [7] turns into the 47th element of the one-dimensional array . We give him status 7 - food.
In case the element is busy, we start the generator again.
food generator
A # random.set_food
FP # frnts.pos7
JCN food
repl: CALL "TIME_TCK"
RET_VAL: = # random.tick
L # random.tick
AD DW # 16 # 1F
T # random.rot
L # random.tick
L #random. rot
RLD
L # random.tick
XOD
ABS
L 10
MOD
T # random.x
CALL "TIME_TCK"
RET_VAL: = # random.tick
L # random.tick
XOD DW # 16 # 1E12F
T # random.rot
L # random.tick
L # random.rot
RLD
L # random.tick
XOD
ABS
L 10
MOD
T # random.y
L 0
T #looper
LAR1 P # 0.0
posx: L #looper
L # random.x
> = I
JC next
LP # 10.0
+ AR1
L 1
L #looper
+ I
T #looper
JU posx
next: L 0
T #looper
posy: L #looper
L # random.y
> = I
JC poss
LP # 1.0
+ AR1
L 1
L #looper
+ I
T #looper
JU posy
poss: OPN “massive”
L DBB [AR1, P # 0.0]
L 0
== I
JCN repl
L 7
T DBB [AR1, P # 0.0]
food: R # snake.omnomnom
R # random.set_food
FP # frnts.pos7
JCN food
repl: CALL "TIME_TCK"
RET_VAL: = # random.tick
L # random.tick
AD DW # 16 # 1F
T # random.rot
L # random.tick
L #random. rot
RLD
L # random.tick
XOD
ABS
L 10
MOD
T # random.x
CALL "TIME_TCK"
RET_VAL: = # random.tick
L # random.tick
XOD DW # 16 # 1E12F
T # random.rot
L # random.tick
L # random.rot
RLD
L # random.tick
XOD
ABS
L 10
MOD
T # random.y
L 0
T #looper
LAR1 P # 0.0
posx: L #looper
L # random.x
> = I
JC next
LP # 10.0
+ AR1
L 1
L #looper
+ I
T #looper
JU posx
next: L 0
T #looper
posy: L #looper
L # random.y
> = I
JC poss
LP # 1.0
+ AR1
L 1
L #looper
+ I
T #looper
JU posy
poss: OPN “massive”
L DBB [AR1, P # 0.0]
L 0
== I
JCN repl
L 7
T DBB [AR1, P # 0.0]
food: R # snake.omnomnom
R # random.set_food
After a successful ejection of food, the snake begins its movement, consider an algorithm based on left movement.
move left
A “db_pulsegen” .two_sec_pls
A # move.left
JCN ext1
OPN “massive”
LAR1 #coordinate
TAR1
LP # 10.0
MOD
LP # 0.0
== D
JCN ok_1
S # snake.gameover
JU gmov
ok_1: TAR1
LP # 1.0
-D
LAR1
OPN Massive
L DBB [AR1, P # 0.0]
L 0
== I
JC nul1
L DBB [AR1, P # 0.0]
L 7
== I
JC eat1
SET
S # snake.gameover
JU gmov
eat1: SET
S #snake. omnomnom
L # tail_cut.lenght
L 1
+ I
T # tail_cut.lenght
nul1: L 3
T DBB [AR1, P # 0.0]
TAR1 #coordinate
ext1: NOP 0
A # move.left
JCN ext1
OPN “massive”
LAR1 #coordinate
TAR1
LP # 10.0
MOD
LP # 0.0
== D
JCN ok_1
S # snake.gameover
JU gmov
ok_1: TAR1
LP # 1.0
-D
LAR1
OPN Massive
L DBB [AR1, P # 0.0]
L 0
== I
JC nul1
L DBB [AR1, P # 0.0]
L 7
== I
JC eat1
SET
S # snake.gameover
JU gmov
eat1: SET
S #snake. omnomnom
L # tail_cut.lenght
L 1
+ I
T # tail_cut.lenght
nul1: L 3
T DBB [AR1, P # 0.0]
TAR1 #coordinate
ext1: NOP 0
In this algorithm, we immediately perform several checks. Divide by 10 - we get the remainder - the number in the line of the current element. If we move to the left, being in the zero element - the end of the game.
The same thing happens if there is anything other than food in the way of movement. If you stumble upon food - cock the bit that you just ate, it will start the generator, extend the tail by 1.
Next is the last algorithm - “Tail Cutter”. He takes the last coordinate as a basis, reads the command at this coordinate and goes in the reverse order from the head of the snake to the tail. If the cell goes beyond the length - delete it by zeroing the value
cut tails
A # snake.start
AN # tail_cut.uncut
A “db_pulsegen” .two_sec_pls
JCN nop
L 0
T #looper
L #coordinate
T # tail_cut.tmp_coordinate
lpct: L #looper
L # tail_cut.lenght
> I
JC cut
L # tail_cut.tmp_coord
LAR1
OPN “massive”
L DBB [AR1, P # 0.0]
L 3
== I
JC m_lf
L DBB [AR1, P # 0.0]
L 4
== I
JC m_rt
L DBB [AR1, P # 0.0]
L 5
== I
JC m_up
L DBB [AR1, P # 0.0]
L 6
== I
JC m_dn
JU nop
m_lf: L # tail_cut.tmp_coordinate
LP # 1.0
+ D
T # tail_cut.tmp_coordinate
L #looper
L 1
+ I
T #looper
JU lpct
m_rt: L # tail_cut.tmp_coordinate
LP # 1.0
-D
T # tail_cut.tmp_coordinate
L #looper
L 1
+ I
T #looper
JU lpct
m_up: L # tail_cut.tmp_coordinate
LP # 10.0
+ D
T # tail_cut.tmp_coordinate
L #looper
L 1
+ I
T #looper
JU lpct
m_dn: L # tail_cut.tmp_coordinate
LP # 10.0
-D
T # tail_cut.tmp_coordinate
L #looper
L 1
+ I
T #looper
JU lpct
cut: L # tail_cut.tmp_coordinate
LAR1
OPN “massive”
L 0
T DBB [AR1, P # 0.0]
nop: NOP 0
AN # tail_cut.uncut
A “db_pulsegen” .two_sec_pls
JCN nop
L 0
T #looper
L #coordinate
T # tail_cut.tmp_coordinate
lpct: L #looper
L # tail_cut.lenght
> I
JC cut
L # tail_cut.tmp_coord
LAR1
OPN “massive”
L DBB [AR1, P # 0.0]
L 3
== I
JC m_lf
L DBB [AR1, P # 0.0]
L 4
== I
JC m_rt
L DBB [AR1, P # 0.0]
L 5
== I
JC m_up
L DBB [AR1, P # 0.0]
L 6
== I
JC m_dn
JU nop
m_lf: L # tail_cut.tmp_coordinate
LP # 1.0
+ D
T # tail_cut.tmp_coordinate
L #looper
L 1
+ I
T #looper
JU lpct
m_rt: L # tail_cut.tmp_coordinate
LP # 1.0
-D
T # tail_cut.tmp_coordinate
L #looper
L 1
+ I
T #looper
JU lpct
m_up: L # tail_cut.tmp_coordinate
LP # 10.0
+ D
T # tail_cut.tmp_coordinate
L #looper
L 1
+ I
T #looper
JU lpct
m_dn: L # tail_cut.tmp_coordinate
LP # 10.0
-D
T # tail_cut.tmp_coordinate
L #looper
L 1
+ I
T #looper
JU lpct
cut: L # tail_cut.tmp_coordinate
LAR1
OPN “massive”
L 0
T DBB [AR1, P # 0.0]
nop: NOP 0
Thank you very much for your attention to everyone who reached the end of the article, I hope it was interesting.
Link to download the project