Well, there are officially 2 people using my Maintenance Protocol implementation in control applications.
it enables treating PLCs like netowork peripherals. This means you can have the PLC, and get all the IO functionality, and program in whatever language you want.
This is the design implementation Im shooting for with having an arduino act as a PLC.
full operability with the PLC, HMI, client devices, or PC, offering all the busses and IO from the auduino.
hows it going?
Well, it was going great. Im learning more about the HMI.
The downside of attaching the HMi to an arduino acting as a PLC is that 1, I have not implemented and API yet, and 2,
That one has to define anything to begin with. The UNO Q isnt working out for me, but I really want that extra memory.
where I do not have the extra memory, I have this issue of not having the registers ready to go.
If I add 100 points to monitor, the arduino needs those registers ready to go when the HMI ask, or within 0.5 seconds,
which is the hmi update timeout. fortunately, those registers only need to be available, they dont need real data in them.
Im not sure if there is a method that would use minimal memory to respond with.... IDK theres a bunch of things to consider.
this is the same on the PLC, but where Im implementing the arduino as a server, now I have to think about it.
well, I dont mind having a sparse list.
The HMI initialization always requies additional registers upon inclusion.
The fc6a client library has no such requirements, it just reads values as they return, and or writes them, but it doesn't expect anything.
The HMI isnt like that. if registers are defined, but not responding, the HMI is not going to initialize properly.
Whats the point of this? One of the users here said something like you're going to have someone write PLC code with NV4 / windLDR, and arduino code, and some other PC code. ..?
well, I understand that confusion. This is actually not the case. the PLC doesn't require code to be treated as a peripheral. in fact, it might work better without code on it.
Thats been a pretty unpopular idea, and I dont want to get into it right now.
The arduino here is just going to respond as a PLC would, with resulting IO from IO / buses as PLC registers.
So, Im going to write the IO and bus functions, with a listener / debugger. Thats minimal ram.
just defining the operations as PLC constructs is its own trouble. the idea now is to create a register buffer to whatever size I can manage, and populate it with arduino IO handlers.
#include <SPI.h>
#include <Ethernet.h>
// Network config
byte mac[] = { 0x00, 0x03, 0x7B, 0x20, 0xFE, 0xED };
byte ip[] = { 192, 168, 1, 50 };
EthernetServer server(2101);
// Test register values
uint16_t readDWord(int addr) {
switch (addr) {
case 158: return random(50, 101); // HMI test value
case 560: return 26; // HMI expected value
case 562: return 0;
case 564: return 0;
case 566: return 0;
case 568: return 0;
case 570: return 0;
case 572: return 0;
return 0;
default:
return 0;
}
}
// BCC helpers
byte xorBcc(byte *data, int len) {
byte bcc = 0;
for (int i = 0; i < len; i++) {
bcc ^= data[i];
}
return bcc;
}
void printHex(const char *label, const byte *buf, int len) {
Serial.print(label);
Serial.print(": ");
for (int i = 0; i < len; i++) {
if (buf[i] < 16) Serial.print("0");
Serial.print(buf[i], HEX);
Serial.print(" ");
}
Serial.println();
}
// Reply builder
void replyReadD(EthernetClient &c, int addr, int nbytes) {
byte tx[96];
int p = 0;
tx[p++] = 0x06; // ACK
tx[p++] = '0'; // reply device 00
tx[p++] = '0';
tx[p++] = '0'; // OK discontinued
int words = nbytes / 2;
for (int i = 0; i < words; i++) {
uint16_t v = readDWord(addr + i);
const char *h = "0123456789ABCDEF";
tx[p++] = h[(v >> 12) & 0xF];
tx[p++] = h[(v >> 8) & 0xF];
tx[p++] = h[(v >> 4) & 0xF];
tx[p++] = h[v & 0xF];
}
byte bcc = xorBcc(tx, p);
const char *h = "0123456789ABCDEF";
tx[p++] = h[(bcc >> 4) & 0xF];
tx[p++] = h[bcc & 0xF];
tx[p++] = '\r';
printHex("TX", tx, p);
c.write(tx, p);
}
// Main handler
void handle(EthernetClient c) {
byte buf[64];
int len = 0;
unsigned long lastRx = millis();
while (c.connected() && (millis() - lastRx < 3000)) {
while (c.available()) {
lastRx = millis();
byte b = c.read();
if (len < 64) {
buf[len++] = b;
}
if (b == 0x0D) {
printHex("RX", buf, len);
if (len >= 14 && buf[0] == 0x05 && buf[4] == 'R' && buf[5] == 'D') {
char a[5] = {
(char)buf[6], (char)buf[7],
(char)buf[8], (char)buf[9], 0
};
char l[3] = {
(char)buf[10], (char)buf[11], 0
};
int addr = atoi(a);
int nbytes = strtol(l, NULL, 16);
Serial.print("READ D");
Serial.print(addr);
Serial.print(" nbytes=");
Serial.println(nbytes);
replyReadD(c, addr, nbytes);
}
len = 0;
}
}
}
c.stop();
}
void setup() {
Serial.begin(9600);
Ethernet.begin(mac, ip);
delay(1000);
server.begin();
randomSeed(analogRead(0));
Serial.print("Listening on ");
Serial.print(Ethernet.localIP());
Serial.println(":2101");
}
void loop() {
EthernetClient client = server.available();
if (client) {
Serial.println("Client connected");
handle(client);
Serial.println("Client done");
}
}
LoL: what with the code block?