Modbus at 38400

I have my modbus read and write working at 9600 baud to a Click PLC but when I change things to 38400 (default on PLC) my receive buffer gets out of order and fails. The last byte in will be at position 0 and all the other 8 bytes are pushed down 1 location. Change back to 9600 and it works fine. I am using a blob for receive array and a callback receive routine.

`
local buffer = blob(50);
local cnt = 0;
local gotmod;
local result = 0;

function crc16(crc, data){
local Poly16=0xA001;
local LSB, i;

crc = ((crc^data) | 0xFF00) & (crc | 0x00FF);
for (i=0; i<8; i++) {
LSB=(crc & 0x0001);
crc=crc/2;
if (LSB)
crc=crc^Poly16;
}
return(crc);
}

function readSerial() {

local g,crcl,crch;
buffer.writen(hardware.uart57.read(),'b');
cnt++;
if (domodwrite){
    if (cnt >= 8 && buffer[5] == 1){
        domodwrite = 0;
        return(0);
    }
}

if (cnt > 8) {
    local crc = 0xffff;
    for (g = 0; g <= 6; g++){
        crc = crc16(crc,buffer[g]);
    }
    crch = crc>>8;
    crcl = crc & 0xff;
    if (crcl == buffer[7] && crch == buffer[8]){
        server.log("crcok");
        gotmod = 1;
        buffer.seek(3,'b');
        result = buffer.readn('b') * 256 + buffer.readn('b');
        server.log(result);
        dlogr = result;
        
    }
    else{

        for (g = 0; g <= 8; g++){

             server.log(g + ":" + buffer[g]);
        }
       
        server.log("-");
        server.log(crcl);
        server.log(crch);
    }
}    

}
function modread(address,numreg){
local adh = (address & 0xff00)>>8;
local adl = address & 0xff;
cnt = 0;
buffer.flush();
buffer.seek(0,‘b’);
domodwrite = 0;
local crco = 0xffff;
crco = crc16(crco,1);
hardware.uart57.write(1);//PLCID
crco = crc16(crco,3);
hardware.uart57.write(3);//Read Registers
crco = crc16(crco,adh);
hardware.uart57.write(adh);//vmem address high 600h = V3000
crco = crc16(crco,adl);
hardware.uart57.write(adl);//vmem address low
crco = crc16(crco,0);
hardware.uart57.write(0);//num registers to read high
crco = crc16(crco,numreg);
hardware.uart57.write(numreg); //num registers to read low
local crcl = crco & 0xff;
hardware.uart57.write(crcl);
crcl = crco>>8;
hardware.uart57.write(crcl);

gotmod = 0;

}

function modwrite(address,val){
//return(0);
domodwrite = 1;
local adh = (address & 0xff00)>>8;
local adl = address & 0xff;
local valh = (val & 0xff00)>>8;
local vall = val & 0xff;
cnt = 0;
local crco = 0xffff;
crco = crc16(crco,1);
hardware.uart57.write(1);//PLCID
crco = crc16(crco,0x10);
hardware.uart57.write(0x10);//Write Registers
crco = crc16(crco,adh);
hardware.uart57.write(adh);//vmem address high 600h = V3000
crco = crc16(crco,adl);
hardware.uart57.write(adl);//vmem address low
crco = crc16(crco,0);
hardware.uart57.write(0);//Quantity of registers high
crco = crc16(crco,1);
hardware.uart57.write(1); //Quantity low
crco = crc16(crco,2);
hardware.uart57.write(2); //
crco = crc16(crco,valh);
hardware.uart57.write(valh);//vmem address high 600h = V3000
crco = crc16(crco,vall);
hardware.uart57.write(vall);//vmem address low

local crcl = crco & 0xff;
hardware.uart57.write(crcl);    
crcl = crco>>8;
hardware.uart57.write(crcl);    

}
function mainloop(){
modread(99,2);
imp.wakeup(5,mainloop);
}
hardware.uart57.configure(38400, 8, PARITY_ODD, 1, NO_CTSRTS, readSerial);

imp.wakeup(1,mainloop);

`

ljbeng, the Imp loads the transmit FIFO with a single character each time you call hardware.uart57.write(). In order to comply with the MODBUS protocol, you need to be sure that the inter-character spacing is never greater than 1.5 within a frame. It may be that at 9600 Squirrel can keep up and there is no underflow in your transmit buffer. At 38400, you may not be able to load the buffer fast enough. I suggest you try creating the entire frame in an array (with the crc) and send it as a single string or blob.

As a further suggestion, you could us a lookup table for calculating the CRC, insteading of crunching it. Imps have a reasonable amount of RAM, but cpu-time (especially in real-time applications like MODBUS) is precious.

In your read callback, you should be emptying the serial buffer; you don’t get a callback for every byte received, you get a callback when the buffer goes from being empty to non-empty.

ie replace this:

buffer.writen(hardware.uart57.read(),'b');

with:

local b=hardware.uart57.read(); while(b!=-1) { buffer.writen(b,'b'); b=hardware.uart57.read(); }

…btw, we will be adding a CRC API in squirrel as it’s a pretty common requirement.

Hugo, I did what you suggested before I read this and it worked so I must have guessed correctly. :slight_smile: Not seeing the inter-character delay problem but was going to do the array and send as string in the near future anyway. Thanks all.