Buzby
Senior Member
Hi All,
Prompted by the recent question about making a PICAXE PLC here is an 'engine' that someone could use as the basis of such a project, if anyone thinks making a PLC out of a PICAXE is a Good Thing.
The difficult part of a PLC system is not the engine ( this one here took less than a day to write ), it's the software needed to provide the user programming interface. Without a graphic front end for programming and monitoring any PLC is just a black box running some code.
The code below is a first attempt. If I was to take it further I would re-map all the buffers and coils into the scratchpad, along with the PLC instructions. This would then allow a second PICAXE to read the buffers and modify the programme 'on the fly', by using I2C. This second PICAXE would then be the basis of the programming interface.
The code demonstrates fundamental concepts that a real PLC uses, such as byte-wide buffers for each bit, reading inputs at the beginning of the scan, writing outputs at the end, off-loading 1-shot generation, etc., and it's functionality can be extended considerably from here.
If anyone wants to take this on as a project then I'm happy to help, but I'm not developing it any further myself.
Cheers,
Buzby
Prompted by the recent question about making a PICAXE PLC here is an 'engine' that someone could use as the basis of such a project, if anyone thinks making a PLC out of a PICAXE is a Good Thing.
The difficult part of a PLC system is not the engine ( this one here took less than a day to write ), it's the software needed to provide the user programming interface. Without a graphic front end for programming and monitoring any PLC is just a black box running some code.
The code below is a first attempt. If I was to take it further I would re-map all the buffers and coils into the scratchpad, along with the PLC instructions. This would then allow a second PICAXE to read the buffers and modify the programme 'on the fly', by using I2C. This second PICAXE would then be the basis of the programming interface.
The code demonstrates fundamental concepts that a real PLC uses, such as byte-wide buffers for each bit, reading inputs at the beginning of the scan, writing outputs at the end, off-loading 1-shot generation, etc., and it's functionality can be extended considerably from here.
If anyone wants to take this on as a project then I'm happy to help, but I'm not developing it any further myself.
Cheers,
Buzby
Code:
#picaxe 28X2
#no_data
#no_table
#rem
Memory usage
RLO is RAM bit8
Inputs are pins B.0 to B.7, mapped to RAM Bytes b4 to b11, Addr 4 to 11
Outputs are pins C.0 to C.5,mapped to RAM Bytes b12 to b17, Addr 12 to 17
Coils are bytes 18 to 25, Addr 18 to 25
#endrem
symbol Inst = b2 ' Code of instruction, -[ ]- -[/]- etc
symbol Addr = b3 ' Address of operand, X3 F2 etc, as RAM byte number
symbol RLO = bit8 ' RLO bit
symbol PEND = bit9 ' Programme End instruction 255 found
symbol Scan1 = bit10 ' This bit is set on first scan of PLC, often used to initialise stuff.
'Input buffer
symbol X1 = b4 ' Structure for pinB.0
symbol X2 = b5 ' Structure for pinB.1
symbol X3 = b6 ' Structure for pinB.2
symbol X4 = b7 ' Structure for pinB.3
symbol X5 = b8 ' Structure for pinB.4
symbol X6 = b9 ' Structure for pinB.5
symbol X7 = b10 ' Structure for pinB.6
symbol X8 = b11 ' Structure for pinB.7
'Output buffer
symbol Y1 = b12 ' Structure for pinC.0
symbol Y2 = b13 ' Structure for pinC.1
symbol Y3 = b14 ' Structure for pinC.2
symbol Y4 = b15 ' Structure for pinC.3
symbol Y5 = b16 ' Structure for pinC.4
symbol Y6 = b17 ' Structure for pinC.5
'Internal Coil buffer
symbol F1 = b18 ' Structure for internal bit 1
symbol F2 = b19 ' Structure for internal bit 2
symbol F3 = b20 ' Structure for internal bit 3
symbol F4 = b21 ' Structure for internal bit 4
symbol F5 = b22 ' Structure for internal bit 5
symbol F6 = b23 ' Structure for internal bit 6
symbol F7 = b24 ' Structure for internal bit 7
symbol F8 = b25 ' Structure for internal bit 8
' Code starts here
' ================
dirsC = %00111111 ' Enable output pins
Scan1 = 1
do ' This loop executes one complete scan of the PLC programme
gosub GetInputs ' Get physical input pins into buffers, ( addr 4 to 11 )
ptr = 0 ' Point to start of instruction list in scratchpad
RLO = 1 ' Reset RLO for start of first rung
PEND = 0 ' Reset programme end flag
do ' This loop processes rungs until the programme end instruction is found
' This loop should really pull the instructions from scratchpad.
' Here it's just three hardcoded example rungs.
Inst = 1 : Addr = 4 ' -[ ]- A X1
gosub ProcessInstr
Inst = 4 : Addr = 12 ' -[ ]- O Y1
gosub ProcessInstr
Inst = 2 : Addr = 5 ' -[/]- AN X2
gosub ProcessInstr
Inst = 3 : Addr = 12 ' -( )- Y1
gosub ProcessInstr
Inst = 254 : Addr = 0 ' Rung end
gosub ProcessInstr
Inst = 1 : Addr = 4 ' -[ ]- A X1
gosub ProcessInstr
Inst = 3 : Addr = 22 ' -( )- F5
gosub ProcessInstr
Inst = 254 : Addr = 0 ' Rung end
gosub ProcessInstr
Inst = 1 : Addr = 22 ' -[ ]- A F5
gosub ProcessInstr
Inst = 3 : Addr = 13 ' -( )- Y2
gosub ProcessInstr
Inst = 254 : Addr = 0 ' Rung end
gosub ProcessInstr
Inst = 255 : Addr = 0 ' PLC Programme end
gosub ProcessInstr
loop until PEND = 1 ' Finish loop when Programme End 255 found
' gosub UpdateTimers ' ( Future feature )
gosub PutOutputs ' Put output buffers to physical pins
loop ' Repeat PLC scan
' ============================================================================
' Subroutines
' ============================================================================
GetInputs:
' This routine is called at the beginning of a scan. It updates the input buffers from the physical pins and generates rising/falling 1-shots.
'Input byte structure
'--------------------
'bit7 = State of physical input, read by -[ ]- and -[/]- instructions
'bit6 = Last scan state
'bit5 = Rising 1-shot, read by -[P]- instruction
'bit4 = Falling 1-shot, read by -[N]- instruction
'bit3 =
'bit2 =
'bit1 =
'bit0 =
' Get first input
b0 = X1 ' Get current input structure
bit7 = pinB.0 ' Copy physical input to structure
Gosub ProcessInput
X1 = b0 ' Update structure
' Get second input
b0 = X2 ' Get current input structure
bit7 = pinB.1 ' Copy physical input to structure
Gosub ProcessInput
X2 = b0 ' Update structure
' Get third input
b0 = X3 ' Get current input structure
bit7 = pinB.2 ' Copy physical input to structure
Gosub ProcessInput
X3 = b0 ' Update structure
' etc ...
return
ProcessInput:
' Code called as each input is processed at beginning of scan
bit5 = 0 : bit4 = 0 ' Clear any rising/falling 1-shots
if bit7 <> bit6 then ' input has changed
if bit7 = 1 then ' it was rising
bit5 = 1
else ' it was falling
bit4 = 1
endif
endif
bit6 = bit7 ' update last scan state
return
PutOutputs:
'Output byte structure
'---------------------
'bit7 = State of physical output, read by -[ ]- and -[/]- instructions
'bit6 = Force active ( future feature )
'bit5 = Forced state ( future feature )
'bit4 =
'bit3 =
'bit2 =
'bit1 =
'bit0 =
b0 = Y1 ' Get structure for first output
if bit6 = 1 then ' Check for force active, if it is then
pinC.0 = bit5 ' Copy forced value of structure to physical output
else
pinC.0 = bit7 ' else copy RLO of structure to physical output
endif
Y1 = b0
b0 = Y2 ' Get structure for second output
if bit6 = 1 then ' Check for force active, if it is then
pinC.1 = bit5 ' Copy forced value of structure to physical output
else
pinC.1 = bit7 ' else copy RLO of structure to physical output
endif
Y2 = b0
b0 = Y3 ' Get structure for third output
if bit6 = 1 then ' Check for force active, if it is then
pinC.2 = bit5 ' Copy forced value of structure to physical output
else
pinC.2 = bit7 ' else copy RLO of structure to physical output
endif
Y3 = b0
' etc ....
return
UpdateTimers:
' Future feature
return
WriteInternal:
' Write to internal coil -( )- , addr 18 to 25
'Internal coil byte structure
'-----------------------------
'bit7 = State of coil, read by -[ ]- and -[/]- instructions
'bit6 = Last set state
'bit5 = Rising 1-shot, read by -[P]- instruction
'bit4 = Falling 1-shot, read by -[N]- instruction
'bit3 =
'bit2 =
'bit1 =
'bit0 =
peek Addr, b0 ' Get coil structure
bit7 = RLO ' Update using RLO
bit5 = 0 : bit4 = 0 ' Clear any rising/falling 1-shots
if bit7 <> bit6 then ' coil has changed
if bit7 = 1 then ' it has risen
bit5 = 1
else ' it has fallen
bit4 = 1
endif
endif
bit6 = bit7 ' update last scan state
poke Addr, b0 ' update coil structure
return
ProcessInstr:
' This is the routine to execute each instruction
if Inst = 0 then ' NOP
' do nothing
endif
if Inst = 1 then ' -[ ]- 'A' Scan for coil or pin state
peek Addr, b0
RLO = RLO AND bit7
endif
if Inst = 2 then ' -[/]- 'AN' Scan for inverse of coil or pin state
peek Addr, b0
RLO = RLO ANDNOT bit7
endif
if Inst = 3 then ' -( )- '=' Output to coil or pin
if Addr >= 18 then ' it's an internal coil
gosub WriteInternal
else ' it must be a physical output pin
peek Addr, b0 ' get pin structure
bit7 = RLO ' Update RLO for output
poke Addr, b0 ' Update structure
endif
endif
if Inst = 4 then ' -[ ]- 'O' OR to left rail
peek Addr, b0
RLO = RLO OR bit7
endif
if Inst = 254 then ' Rung End
RLO = 1 ' Reset RLO ready for next rung
endif
if Inst = 255 then ' Programme End
PEND = 1 ' Set Programme End flag
endif
return