Imp <-> RS-485

Most industrial controllers, like a Watlow or Barber-Colman temperature controller has RS-485 communication built-in. I’m wondering if there are any schematics and code examples of connecting an Imp to any RS-485 device, such as a temperature controller, or industrial device. Basically, the Imp is used to send a command, and request information from the device. If anyone has done this, can you post the schematics and code examples? Thanks.

Hi if those temp sensors work with half duplex e.g. send read cmd & then listen
Then this should do the job

Look at the RFID cat flap worked example for serial coms
http://devwiki.electricimp.com/doku.php?id=examplerfid

In the cat flap example an older method is used for getting information from the UART. (constantly waking up and checking). I have just changed my code according to the example in the API reference. In this method one sets a function to call when the firmware on the Imp detects a character waiting in the UART.

I am not using RS485 but I do have 5 different devices simultaneously talking to the UART. The communications are infrequent so collisions are rare and when it happens I just ignore that data point.

In my arrangement, the temperature controller does its thing all by itself. If I wish to change a value or view a value, I give it a command on the serial port. The controller responds back with a value or an acknowledge. So, in my case, the controller is always looking for a command from the serial port … meanwhile, it controls its PID loop just as it normally does. Using the Imp, I could change a setpoint or view the current temperature when I need to. If the Imp fails, or communication is lost, the controller still operates “stand-alone”.

My thought is to use a ramp-soak temperature controller on a kiln. Using a website, I would select a ramp-soak recipe and command the controller to start the recipe and begin controlling. Periodic monitoring allows me to see the progress, and notify me when the cycle has completed.

I actually can do that right now using an old desktop PC sitting on the workbench in the kiln room. I thought that integrating the Imp would let me get rid of the PC and give me control anywhere in my house … using my WiFi device(s).

There are new temp controllers that have ethernet ports built-in, but they cost over $300.
The controller I’m using now is very similar to this PID ramp-soak controller:
http://www.omega.com/pptst/CN7500.html

I’ve already looked at the Arduino Modbus libraries:http://playground.arduino.cc/Code/ModbusMaster
With that, I could use an Arduino … but the fun would be to use the Imp.

Another idea is to use the Imp with the nest that has the USB port. Use a USB -> RS485 to connect to the controller. But I still need to deal with the Modbus protocol.

Maybe I’m in way over my head with this one? [sigh]

People have implemented modbus on the imp - it’s definitely possible. We have RS485 transceivers hooked up, but the reference design isn’t published yet… coming soon from Tom!

Thanks for the heads-up Hugo. I really think discussions, references, and design examples for industrial uses will prove to be very beneficial for Imp Developers.

There are only so many doors, windows, and lights to control in a typical home. Out in the world of industry, there are thousands of Modbus, Profibus, CANbus, protocol devices.

Later in 2013 we plan to have modbus-rtu build into the imp API, which will make modbus interfacing even simpler. It’s a common request, and whilst you can pretty much do it in squirrel, it’s a lot of code which is just implementing a standard hence it’s probably better in the imp OS itself.

Hi Hugo,
What do you estimate the timeframe for the native Modbus RTU API? I have it running on a slave atmega, but would prefer native API for a new design. Thanks in advance.

It’s still down as later this year. People are running (at least compatible-enough) modbus in pure squirrel though - you can’t get it exactly right, but it can be workable depending on the devices on the bus.

To update the thread, here’s the RS485 reference design I mentioned:
http://devwiki.electricimp.com/doku.php?id=boards:kaylee

OK, thanks. I think I’ll stay with a serial link to an ATMega that does the actual Modbus RTU register reading and writing for now. I’ll keep a lookout for the API later on.

Any news on when the Modbus RTU API would appear ? Need to decide to either implement it in Squirrel (any example somewhere ?) or wait for the API.

Modbus RTU is further down the list than 1-wire. Might be a little wait…

I’d put in a vote for the RTU protocol … I’m also looking at how to suck in some readings from an RTU over RS485.
There is a ‘C’ free version
http://developer.berlios.de/project/showfiles.php?group_id=6120
Seems like the basic protoyping Modbus master to RTU software is doable in squirrel. Accurately doing the timeouts is probably something that is going to require more under the hood fine tuning.

It turns out that for most modbus devices, accurate timing isn’t essential. However, when we get round to the built-in implementation, this will obviously be fully compliant.

so I’m looking at a crude modbus polling. I’m wondering if I can have some comments on this, especially the process of sleeping and wakeing up which may cause other system implementations
The modbus & circuitry has some delays built in, and I’m using 1mS as a partially interchangeable with 1Char @ 9,600Baud
To activate the modbus - a 12V generation needs to be turned on - and I give it say 1mS to activate and wakeup the sensors.
Once 12V is generated, the output RS485 needs to be set to TX - which takes another 1ms
Then I send a message and pend on the outbuffer to flush.
For no equipment attached I should then have no message back, so I read any received buffer.
Then code needs to wait a period of time (~200mS) for the modbus device to respond and then look for a message in the Rx buffer - and I assume the underlying RxUart is processing characters
Currently I’m testing for no Equipment and I’m getting some responses in the RxBuffer which I’m not expecting
`
// Comment simple modbus command
//130828: free mem 57k

//Configure hardware - RS485 pin allocation
//Pin 1 Boost - ‘0’ default OFF, ‘1’ enable 12V
//Pin 2 Direction Rs485 - ‘0’ default receive , ‘1’ Transmit
//Pin 5 U2-TX RS485
//Pin 7 U2-RX RS485
//Pin8 I2C-SCL Future
//Pin9 I2C-SDA Future
pinBoost<-hardware.pin1;
pinBoost.configure(DIGITAL_OUT);//Boost
pinBoost.write(0);
pinRs485dir<-hardware.pin2;
pinRs485dir.configure(DIGITAL_OUT);//RS485Direction
pinRs485dir.write(0);
uartRs485<-hardware.uart57;

const cSleepTime_sec = 10;//Debug 10–> 5min=300 Production 900
const cUlbiFrameSize=8

const rduRdAddrReq_blob = “\x01\x03\x00\x00\x00\x01\x84\x0a”;//request devices modbus address
local rxBuf_blob = blob(20); // incoming modbus msgs
snapshotTime <-0;
seqId_cnt<-0;
batteryV <- 0;

ledRed_State <- 0; //Debug
ledYel_State <- 0; //Debug

/@********************************************************
Hardware management /
function setBoostVoltOn(){pinBoost.write(1);}
function setBoostVoltOff(){pinBoost.write(0);}
function setRs485DirTx(){pinRs485dir.write(1);}//set direction out
function setRs485DirRx(){pinRs485dir.write(0);}//set direction incoming
/
@
********************************************************
Tx to RS485 UART and wait for items sent on wire*/
function modBusConfigure() {uartRs485.configure(9600, 8, PARITY_NONE, 1, NO_CTSRTS);}
function modBusTxSync(rduReq_blob){
//could be implemented with stateMc & imp.wakeup()

setBoostVoltOn();
imp.sleep(0.001);//Wait 1mS for realworld activation or RS485
setRs485DirTx();
imp.sleep(0.001);//Wait for realworld line and target recognition
uartRs485.write(rduReq_blob);
uartRs485.flush();//wait for the output FIFO to have all bytes sent on the wire
imp.sleep(0.0001);//might need little wait here    
setRs485DirRx();// make ready for receiving data and return immediately

}

/@********************************************************
Read RS485 UART input*/
function readRs485clear(){
local byteCnt=0;
local byte = uartRs485.read(); //READ IN 1st character
while(byte != -1) {
byte = uartRs485.read(); //and read next (if any) character
byteCnt++;
}
if (byteCnt) server.log(format(“Info: bytesCleared=%d”,byteCnt));;
return byteCnt;
}
function readRs485resp(){
local timerCnt=0; //Simple timer to prevent hanging
local byteCnt=0;
local byte = uartRs485.read();
while((byte != -1) && (timerCnt++ <65536)) {
rxBuf_blob.writen(byte,‘b’);//put read character in the buffer
byte = uartRs485.read(); //and read the next (if any) character
byteCnt++;
}

rxBuf_blob.seek(0);  //set to start for later reads?
           
return byteCnt;

}
/@********************************************************
When to do it - main entry */
function entryTimer(){
imp.setpowersave(true);//For quiet ADC

//server.log("entryTimer");
ledRed_State = ledRed_State?0:1;//Toogle
ledYel_State = ledYel_State?0:1;//Toogle
//hardware.pin8.write(0);//ledRed_State);
//hardware.pin9.write(ledYel_State);
modBusConfigure(); // Incase power down has changed anything

snapshotTime = time();
seqId_cnt = nv.seqId_cnt;
if ( ++seqId_cnt >0x7fff) seqId_cnt=1; 
nv.seqId_cnt =seqId_cnt;
batteryV = 0;

cAnlgNum <- 15;      
for (local aLp=0; aLp< cAnlgNum;aLp++) {
    batteryV += hardware.voltage();
}
batteryV =  batteryV/cAnlgNum;

modBusTxSync(rduRdAddrReq_blob);
readRs485clear();
imp.wakeup(0.2,doReadRs485);//Wait for response into UART but can't sleep

}
function doReadRs485() {
local bytesRx=readRs485resp(); //returns # chars read
setBoostVoltOff();
if (8 >bytesRx ) {
server.log(format(“Err: bytesRx=%d not enough”,bytesRx));
}

//TODO: - processing of incoming message 
// - expect a response in rxBuf_blob with address 1

//Send in Thingstream.net format
agent.send("putPsTs",{
 //   "field6": ,
 //   "field5": ,
 //   "field4": modbus reading,
    "field3": batteryV,
    "field2": seqId_cnt,
    "field1": snapshotTime
    });/* */
//imp.sleep(0.2);

//hardware.pin8.write(1);//ledRed_State);

//const cSleep_15min =(900 -(time() % 900) //wake up every 15 minutes on the 1/4 hour
imp.onidle(function(){server.sleepfor(cSleepTime_sec-(time() % cSleepTime_sec));});
}

/@********************************************************
Startup and Configure output ports and display the IMP mac address on the IMP planner
*/
local wakeReason = hardware.wakereason(); //WAKEREASON_POWER_ON WAKEREASON_TIMER WAKEREASON_SW_RESET WAKEREASON_PIN1 WAKEREASON_NEW_SQUIRREL
local wakeTime = time();
if (!(“nv” in getroottable())) {
nv <- { data = “” };
//hourly();
}
if (!(“seqId_cnt” in nv)) {
nv.seqId_cnt <- 0;
}
if ((false == imp.getpowersave()) ||
(WAKEREASON_POWER_ON ==wakeReason)||
(WAKEREASON_NEW_SQUIRREL==wakeReason) ) {
imp.configure(“RS485-IMP001 (ver0.0ab)”, [], []);//[outputRatioMin, outputParticleMin,outputPtclSzMin,outputPtclSzMax,outputconcentration]);
server.show(hardware.getimpeeid());
server.log(format(“impMac %s impId %s SwVer %s”,imp.getmacaddress(), hardware.getimpeeid(), imp.getsoftwareversion() ));
server.log(format(“ssidMac %s FreeMem=%dK”,imp.getbssid() ,imp.getmemoryfree()/1000));
//imp.environment() IMP001 002 AGENT

server.log(format("WakeReason %d @ time=%d seqId=%d",wakeReason, wakeTime,nv.seqId_cnt));
server.log(format("rssi %ddBm",imp.rssi()));//Call after connection (above -67 good, down to -87 terrible)

}

entryTimer(); //go there until reboot
//EOF
`
The log window is good, except getting 1 character in RxRead when I’m expecting 0

Thu Aug 29 2013 13:49:16 : Power state: asleep=>online
Thu Aug 29 2013 13:49:16 : Device booting
Thu Aug 29 2013 13:49:16 : Device configured to be "RS485-IMP001 (ver0.0ab)"
Thu Aug 29 2013 13:49:16 : impMac 0c2a690038a3 impId 2359603643fc42ee SwVer 0e967a4 - Fri Jul 19 10:04:28 2013
Thu Aug 29 2013 13:49:16 : ssidMac 88dc96035d86 FreeMem=56K
Thu Aug 29 2013 13:49:16 : WakeReason 1 @ time=1377809354 seqId=22
Thu Aug 29 2013 13:49:16 : rssi -75dBm
Thu Aug 29 2013 13:49:16 : Info: bytesCleared=2
Thu Aug 29 2013 13:49:16 : Err: bytesRx=1 not enough
Thu Aug 29 2013 13:49:16 : sleeping until 1377809364000
Thu Aug 29 2013 13:49:16 : Power state: online=>asleep

I suspect that, as you have no delay after setting RX mode and before emptying the RX buffer, there’s one more junk byte from the turnaround coming in - ie there are 3 bytes received but the 3rd one is actually coming in after the clear RX function has completed.

Given how long a byte time is at 9600bps, this seems plausible. Maybe wait a millisecond after configuring RX mode but before you flush?

Ok thanks - I did an imp.sleep(0.001) and it caught the extra character.
Now I’ve connected up a real modBus device and getting 7 characters back
So they should be 8 bit binary and I need to convert the 4 bits to ASCII and trying to send then to the server with
server.log(format(“Rxbuf %s”,rxBuf_blob.tostring()));
but not getting anything out of the blob?
Is there somewhere where all the blob operations are described?

If it’s binary, you don’t want to print the blob like that; you likely want to convert it to hex first, eg:

local s = "Rxbuf"; foreach(b in rxBuf_blob) s += format(" %02x", b); server.log(s);

Excellent thankyou - since I know how many bytes have come in I modified it to
local s = "Rxbuf:"; for(local b=0; b<bytesRx; b++) s += format(" %02x", rxBuf_blob[b]); server.log(s);
I’ll post the whole section on github after a cleanup.

another question how to create a table of constants

const wCRCTable[256] = { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, .. } Then to calculate the CRC16 with
`
local nTemp;
local wCRCWord = 0xFFFF;

while (msgLength–)
{
nTemp = *nData++ ^ wCRCWord;
wCRCWord >>= 8;
wCRCWord ^= wCRCTable[nTemp];
}
return wCRCWord;

}*/
`