Author Topic: How to run some I2C sensors outdoors over longer distances (5-10m)?  (Read 1025 times)

0 Members and 1 Guest are viewing this topic.

Offline victorhooiTopic starter

  • Contributor
  • Posts: 28
  • Country: au
Hi,

I'm looking to run some environmental sensors outdoors to monitor some greenhouses.

I'm using the Mycodo software, which runs on a Raspberry Pi. However, the sensors would be 5-10m from the Raspberry Pi.

The sensors are from Atlas Scientific - some of them communicate over I2C - e.g.:
  • Humidity Sensor
  • Color Sensor
Atlas Scientific also sell an extension cable, but they mention that the length is limited to a max of 3.6 meters. (My understanding is that i2c was never designed for long distances).

I know that Sparkfun make some I2C differential extenders, and you can use this with copper Ethernet cable to run i2c over longer distances. They have:
  • QwiicBus Endpoint - which converts 4-wire I2C into differential signalling over Ethernet cable
  • QwiicBus Midpoint - which lets you extend the range (I probably won't need this part for the 5-10m), but then also apparently lets you insert a sensor at that point - so you get a bus-style topology.

I've made a diagram for how I think this could work:

2160553-0

Questions:
  • Would this approach work, or is there something I've missed here? Is it OK to have all of those sensors will share the same I2C bus, and I'll be able to communicate with each one separately (assuming they all have different I2C addresses)?
  • Will there be a benefit to using shielded Ethernet cable, or would normal UTP cable work fine here?
  • Is there a cheaper way of doing this? I've realised that the Sparkfun Qwiibus boards aren't cheap - $22 for each midpoint, and I'd need one for each sensor. Is there a cheaper way of doing this somehow?
  • All of this will be outdoors, and exposed to the elements. Do you have any suggestions for a lightweight and compact way to waterproof each of the Qwiicbus Endpoint and Midpoint boards, and also the wires that go into them? (I was thinking plastic project boxes, with cable glands - but that's going to get super unwieldy and clunky on my tiny balcony).
 

Online bateau020

  • Frequent Contributor
  • **
  • Posts: 251
  • Country: fr
You'll face multiple issues:
* I2C has no provision for checksums, so any data corruption will be hard to detect or correct
* I2C it is single ended

For the latter, the differential extenders can help, but it will be hard to do properly. Making it multidrop will make things worse (timing, especially at high speeds). If you use a star topology, you'll have less problems.
But even then, the first issue remains. 
I was in the same situation as you, and decided to spend just a bit more per sensor: add a microcontroller, and do modbus over RS485. You can also do CAN bus if you want, it is not that expensive.
« Last Edit: May 03, 2024, 12:26:20 pm by bateau020 »
 

Offline Karel

  • Super Contributor
  • ***
  • Posts: 2227
  • Country: 00
Check if your components support low speed (10Kbit/sec or lower) and use that.
Use shielded cable and check the capacity between the screening and the center conductor.
Should be not too high in order to not create a lowpass filter.
And pay attention for ESD.
 

Online mikeselectricstuff

  • Super Contributor
  • ***
  • Posts: 13786
  • Country: gb
    • Mike's Electric Stuff
I2C is unsuitable for running down a cable - stick a small MCU at the sensor end and send it back via UART, RS485 etc.
Youtube channel:Taking wierd stuff apart. Very apart.
Mike's Electric Stuff: High voltage, vintage electronics etc.
Day Job: Mostly LEDs
 
The following users thanked this post: Psi

Online selcuk

  • Regular Contributor
  • *
  • Posts: 145
  • Country: tr
Please read this message about I2C clock and data signals on cables:
https://www.eevblog.com/forum/beginners/why-do-these-modules-refuse-to-work-together/msg5414684/#msg5414684

It seems that you sensors support UART along with I2C. You may use UART - RS485 converter boards and communicate with modbus as suggested.

Green houses are tough environments. The constant humidity is hard for even humidity sensors. I had difficulties with those SMD I2C sensors before. So if this is going to be a professional job, you can consider using products such as: HPP801A031. But this one doesn't have a digital interface since it is an humidity dependent capacitor.
 

Offline rstofer

  • Super Contributor
  • ***
  • Posts: 9897
  • Country: us
 

Offline PCB.Wiz

  • Super Contributor
  • ***
  • Posts: 1591
  • Country: au
I'm looking to run some environmental sensors outdoors to monitor some greenhouses.

I'm using the Mycodo software, which runs on a Raspberry Pi. However, the sensors would be 5-10m from the Raspberry Pi.
...
(I was thinking plastic project boxes, with cable glands - but that's going to get super unwieldy and clunky on my tiny balcony).
'some greenhouses' and 'balcony' sound a bit different.

Balcony : I've run i2c GPIO over up to 10m, using a 3 wire host scheme : SCL push pull driven, and SDA with separate driver and RX pins, and cable driver resistors.
That protects the host from ESD events, and controls the cable slew rates.

You can trade off some BW for noise immunity by doing multiple readings and discarding outliers.

some greenhouses : Will be tens of metres, and many sensors, so you are best to use local MCUs and a better bus.
LIN bus is single wire and slow/noise immune, CAN bus drivers can be used without needing CAN peripherals, or you can use RS485.
I've seen some designs using rugged RS485/CAN drivers where they run power voltage most of the time and flip to messages briefly, allowing 2 wire cabling.


There are also single-wire CAN bus chips like NCV7356

and you can look at app notes like https://www.ti.com/lit/ug/tiduei9/tiduei9.pdf
shows 2-wire Power-Over-RS485 (or is that RS485 over power?) design.
You can send table encoded, or Nibble,/Nibble to keep low frequency modulation effects at bay.
« Last Edit: May 04, 2024, 05:48:47 am by PCB.Wiz »
 

Offline MarkT

  • Frequent Contributor
  • **
  • Posts: 375
  • Country: gb
I'm tempted to mention MBUS here, although its not completely appropriate, it was designed to network 100's of units over km sized areas with 2 wires and provide power too.  It uses 36V signalling host->sensor and current-mode signalling sensor->host, making it very robust w.r.t. noise.  M stands for meter, and its primarily used for utility meters over large sites, but supports sensors and so forth too.  The 2-wire interface is polarity agnostic so any old person can wire and not get it wrong!
 

Offline John B

  • Frequent Contributor
  • **
  • Posts: 802
  • Country: au
I would look at what protocols and interfaces Mycodo can support directly. For the last few years I've been developing a network with 1 master RPi and up to 32 slave modules, connected over RS485 using the MODBUS protocol. It's a very similar protocol to I2C in it's request/reply process.

Getting MODBUS working on the RPi isn't trivial though, only because you'll need a separate hardware implementation to control the transmit and receive pins on an RS485 transceiver. 

I used the PyMODBUS library for python, so a bit of coding will be needed. However the data is only internal to your python program, and so you'll need some way of piping it to other programs.

In my case this is accomplished use the Paho MQTT library to publish and subscribe to MQTT messages. It looks like Mycodo supports MQTT so this looks like your best bet.

In your case, maybe cutting out the RS485 network altogether is an option if you use an ESP32 or something which can connect your I2C devices, then directly connect over the network and publish MQTT messages.
 

Offline artag

  • Super Contributor
  • ***
  • Posts: 1086
  • Country: gb
IIC stands for inter-integrated-circuit.
It was designed for connecting between ICs on a PCB. Even going board-to-board is a bit of a stretch.
Yes, it might be abused to run longer distances but you'll probably want a recovery plan that involves sanity checking and powercycling. There are better ways to do this.
 

Offline perieanuo

  • Frequent Contributor
  • **
  • Posts: 845
  • Country: fr
Re: How to run some I2C sensors outdoors over longer distances (5-10m)?
« Reply #10 on: May 07, 2024, 10:57:57 am »
hi, i have humidity&temp&air pressure using home assistant and aqara sensors (I use zigbee, it's best choice imho, but alternatives exist). stable, specs are amazing for the sensors, you don't care about interference/lightning inducted overvoltage on some long i2c lines.
if you find color sensor, maybe this approach is more safe and cost effective. then, you can automate what you wish for, haos rules on this. graphs, use graphana if you like details. and haos will give you further ideas :)
 

Offline S. Petrukhin

  • Super Contributor
  • ***
  • Posts: 1269
  • Country: ru
Re: How to run some I2C sensors outdoors over longer distances (5-10m)?
« Reply #11 on: May 07, 2024, 11:48:54 am »
I use humidity sensors in a grain storage silo.
The total length of the line is more than 50 m, an FTP cable.
This works steadily even in conditions of high static charges from the filled grain.
But use a software implementation of the protocol in which CLK has a period of 10ms (100Hz).
Expected to work at 10Hz, but practice has shown that 100Hz does not create problems.
Pull-up is a simple 1kOhm resistor.
And sorry for my English.
 

Offline S. Petrukhin

  • Super Contributor
  • ***
  • Posts: 1269
  • Country: ru
Re: How to run some I2C sensors outdoors over longer distances (5-10m)?
« Reply #12 on: May 07, 2024, 11:51:42 am »
* I2C has no provision for checksums, so any data corruption will be hard to detect or correct

The transmitted packets have a sofware CRC.  :)
And sorry for my English.
 

Offline S. Petrukhin

  • Super Contributor
  • ***
  • Posts: 1269
  • Country: ru
Re: How to run some I2C sensors outdoors over longer distances (5-10m)?
« Reply #13 on: May 07, 2024, 12:05:27 pm »
Here is a trial code that will be clear, I hope - pins controls have been translated into Arduino notation  :)
Code: [Select]
//***************************************************************************************************************************************** чтение влажности
uint8_t CurPinSCL, CurPinSDA;


void SCLs0() {
   pinMode(CurPinSCL, OUTPUT);
};

void SCLs1() {
   pinMode(CurPinSCL, INPUT);
};

void SDAs0() {
   pinMode(CurPinSDA, OUTPUT);
};

void SDAs1() {
   pinMode(CurPinSDA, INPUT);
};

void StartI2C() {
   SDAs0();
   Pause(CFG.I2Cpause);
   SCLs0();
};

void StopI2C() {
   SDAs0();
   Pause(CFG.I2Cpause); 
   SCLs1();
   Pause(CFG.I2Cpause);
   SDAs1();
};

uint8_t SendByteI2C(uint8_t aByte) {
   uint8_t ask;
   for (int i=0; i<8; i++) {
      if ((aByte & 0x80) == 0) {
         SDAs0();
      } else {
         SDAs1();
      };
      Pause(CFG.I2Cpause);
      SCLs1();
      Pause(CFG.I2Cpause);
      SCLs0();
      aByte <<= 1;
   };
   SDAs1();
   SCLs1();
   Pause(CFG.I2Cpause);
   ask=digitalRead(CurPinSDA);
   SCLs0();
   return ask;
};

uint8_t ReadByteI2C() {
   uint8_t aByte=0;
   for (int i=0; i<8; i++) {
      SCLs1();
      Pause(CFG.I2Cpause);
      if (digitalRead(CurPinSDA)) {
         aByte|=0x80 >> i;
      };
      SCLs0();
      Pause(CFG.I2Cpause);
   };
   SDAs0();
   SCLs1();
   Pause(CFG.I2Cpause);
   SCLs0();
   Pause(CFG.I2Cpause);
   SDAs1();
   return aByte;
};

bool CRCI2C(uint8_t d1, uint8_t d2, uint8_t aCRC) {
  const uint8_t cPOLYNOMIAL(0x31);
  uint8_t crc(0xFF);
  crc ^= d1;
  for (int i = 8; i; --i) {
    crc = (crc & 0x80) ? (crc << 1) ^ cPOLYNOMIAL : (crc << 1);
  };
  crc ^= d2;
  for (int i = 8; i; --i) {
    crc = (crc & 0x80) ? (crc << 1) ^ cPOLYNOMIAL : (crc << 1);
  };
  return (crc==aCRC);
};



// ********************************************************************************************************************************************* GetOneHum()
void GetOneHum(uint8_t aAddr, uint8_t aPortSCL, uint8_t aPortSDA, uint8_t aCol, uint8_t aRow) {
   uint8_t ask,ReadCount;
   uint8_t Res[6];
   uint32_t t,WaitTime;
   int16_t Temp,Hum;
   String s;

   Temp=cErr;
   Hum=cErr;

   ReadCount=0;
   do {
      PowerOn(aPortSDA-1);
      PowerOn(aPortSCL-1);

      CurPinSCL=cStartSensorsPin+aPortSCL-1;
      CurPinSDA=cStartSensorsPin+aPortSDA-1;

//***** Включить нагреватель
//      StartI2C();
//      SendByteI2C((aAddr<<1) | 0) || SendByteI2C(0x30) || SendByteI2C(0x6D);
//      StopI2C();
//
//
//***** Отобразить статус
//      StartI2C();
//      SendByteI2C((aAddr<<1) | 0) || SendByteI2C(0xF3) || SendByteI2C(0x2D);
//      StopI2C();
//      StartI2C();
//      SendByteI2C((aAddr<<1) | 1);
//      Serial.print(ReadByteI2C(),BIN); Serial.print(" "); Serial.println(ReadByteI2C(),BIN); ReadByteI2C();
//      StopI2C();

      StartI2C();
      ask=SendByteI2C((aAddr<<1) | 0) || SendByteI2C(0x24) || SendByteI2C(0x00);
      StopI2C();

      if (!ask) {
         ask=1;
         WaitTime=millis()+500;
         StartI2C();
         while (ask && (WaitTime>millis())) {
           ask=SendByteI2C((aAddr<<1) | 1);
           Pause(CFG.I2Cpause);
         };
         
         if (!ask) {
            for (int i=0; i<6; i++) {
               Res[i]=ReadByteI2C();
            };
            StopI2C();
            if (CRCI2C(Res[0],Res[1],Res[2]) && CRCI2C(Res[3],Res[4],Res[5])) {
               t = (int32_t)(((uint32_t)Res[0] << 8) | Res[1]);
               Temp = ((4375 * t) >> 14) - 4500;
               t = ((uint32_t)Res[3] << 8) | Res[4];
               Hum = (625 * t) >> 12;
               ReadCount=3;
               TempReadCount++;
            };
         };
         
      };
     
      PowerOff(aPortSCL-1);
      PowerOff(aPortSDA-1);
      ReadCount++;
   } while (ReadCount<3);
   TempList[aCol][aRow]=Temp;
   HumList[aCol][aRow]=Hum;

   if (CFG.TempMonitor) {
      if (aRow==0) {
         switch (aCol) {
            case 0:
               Serial.print(F("Влажность подвески №1        "));
               break;
            case 1:
               Serial.print(F("Влажность подвески №2        "));
               break;
            case 2:
               Serial.print(F("Влажность подвески №3        "));
               break;
            case 3:
               Serial.print(F("Влажность датчика под крышей "));
               break;
            case 4:
               Serial.print(F("Влажность метеостанции       "));
               break;
         };
      } else {
         Tab(29);
      };
         
      if (Hum == cErr) {s = F("*** ");} else {s = String(float(Hum) / 100);};
      Serial.print(PadS(s,F(" "),6));
      Serial.print(F("% "));

       
      if (Temp == cErr) {s = F("*** ");} else {s = String(float(Temp) / 100);};
      Serial.print(PadS(s,F(" "),6));
      Serial.println(F("°C"));
   };
};

// ************************************************************************************************************************************************ GetHum()
void GetHum() {
   if (CFG.TempMonitor) {
      Serial.println();
   };

   GetOneHum(0x44, 2,1,0,0);
   GetOneHum(0x45, 2,1,0,1);
   GetOneHum(0x44, 3,1,0,2);
   GetOneHum(0x45, 3,1,0,3);
   GetOneHum(0x44, 4,1,0,4);
   GetOneHum(0x45, 4,1,0,5);

   GetOneHum(0x44, 6,5,1,0);
   GetOneHum(0x45, 6,5,1,1);
   GetOneHum(0x44, 7,5,1,2);
   GetOneHum(0x45, 7,5,1,3);
   GetOneHum(0x44, 8,5,1,4);
   GetOneHum(0x45, 8,5,1,5);

   GetOneHum(0x44,10,9,2,0);
   GetOneHum(0x45,10,9,2,1);
   GetOneHum(0x44,11,9,2,2);
   GetOneHum(0x45,11,9,2,3);
   GetOneHum(0x44,12,9,2,4);
   GetOneHum(0x45,12,9,2,5);
   

   GetOneHum(0x44,14,13,3,0);
   GetOneHum(0x44,16,15,4,0);
};
And sorry for my English.
 

Online kripton2035

  • Super Contributor
  • ***
  • Posts: 2601
  • Country: fr
    • kripton2035 schematics repository
Re: How to run some I2C sensors outdoors over longer distances (5-10m)?
« Reply #14 on: May 07, 2024, 02:23:44 pm »
I would attach your sensors to some cheap esp8266 with i2c, then send over wifi to your raspberry, using mqtt or any other protocol.
would be much easier.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf