Enter Your Project for a chance to win a grand prize for the most innovative use of Arduino or a $200 shopping cart! The Birthday Special: Arduino Projects for Arduino Day! | Project14 Home | |
Monthly Themes | ||
Monthly Theme Poll |
This project was initially part of the Forget Me Not Design Challenge.
Eric did some excellent work using RFM69 transceivers.
He also shared the work and I was able to make use of it for part of my own Application.
You can follow his work here.
Wireless Sensor Node and Gateway Design Details
At a later date I was asked about doing an article for TheShed magazine.
The publisher wanted a remote display of the glasshouse temperature, and it seemed that I could adapt this to show the concept.
Many of the readers wouldn't have the required OpenHAB display we were using in the Design Challenge, and therefore a receiver and some form of display was necessary.
Transmitter
Eric had demonstrated the RFM69xx transceivers but they needed a microcontroller board to host them.
When I was looking at where to buy the RFM69's I came across the Montenio board that was made by LowPowerLab
https://lowpowerlab.com/guide/moteino/#whatisit
image from LowPowerLab showing the raw unpopulated boards.
The price of the competed item was below what I could get the raw parts for.
The documentation and support also seemed to be there (unlike many other sites just selling the product), so it made sense to support someone else.
The big advantage of these is the ability to solder the Transceiver module directly to the board, rather than wires and some mounting method.
The DS18B20 was attached to D7, and a 4k7 resistor soldered between the pin and Vcc.
These pictures shows another board configured with the sensor and resistor.
In order to stop it getting damaged, I placed it inside a small container that I picked up somewhere.
The yellow wire is the antenna.
The 5v adaptor gets connected to the Vin and Gnd pins of the Moteino, and the 3v3 regulator happily runs the transceiver and the DS18B20 sensor.
Transmitter Code
I wanted to reduce the amount of data being sent.
This wasn't for battery consumption, more to reduce eNoise or unwanted RF noise radiating for no reason.
It seemed sensible to update the display whenever the temperature changed by 0.5 degs, or every 5 minutes.
You may wish to have a finer resolution, or increase the increase the time between transmissions.
Depending on the application the rate might be finer at very low or very high temperatures.
/* based on Eric Tsai License: CC-BY-SA, https://creativecommons.org/licenses/by-sa/2.0/ Date: 11-07-2016 File: ShedRFTempSender.ino This sketch is for a wired Moteino w/ RFM69 wireless transceiver Sends sensor data (temp) back to gateway. **** connections ***** Moteino Pin assignments Pin D0 Rx Pin D1 Tx Pin D2 RFM69 D100 Pin D3 Pin D4 Pin D5 Pin D6 Pin D7 Temp sensor DS18B20 data pin with 4k7 external pullup resistor to 3v3 Pin D8 Pin D9 Pin D10 RFM69 SS Pin D11 RFM69 MOSI Pin D12 RFM69 MISO Pin D13 RFM69 SCK Program as an UNO. */ /* To do list ----------------- 1. */ //RFM69 -------------------------------------------------------------------------------------------------- #include #include #include #define NODEID 31 //unique for each node on same network #define NETWORKID 100 //the same on all nodes that talk to each other #define GATEWAYID 1 //Match frequency to the hardware version of the radio on your Moteino (uncomment one): #define FREQUENCY RF69_433MHZ //#define FREQUENCY RF69_868MHZ //#define FREQUENCY RF69_915MHZ #define ENCRYPTKEY "TheShed123456789" //exactly the same 16 characters/bytes on all nodes! #define IS_RFM69HW //uncomment only for RFM69HW! Leave out if you have RFM69W! #define ACK_TIME 30 // max # of ms to wait for an ack #define LED 9 // Moteinos have LEDs on D9 #define SERIAL_BAUD 9600 //must be 9600 for GPS, use whatever if no GPS boolean debug = 0; // set this to 1 if you want serial output for debugging. //struct for wireless data transmission typedef struct { int nodeID; // node ID (1xx, 2xx, 3xx); eg 1xx = basement, 2xx = main floor, 3xx = outside boolean Sign; // true if negative temperature int deviceID; // sensor ID (2, 3, 4, 5) float Temp; // Temperature data } Payload; Payload theData; char buff[20]; byte sendSize=0; boolean requestACK = false; RFM69 radio; //end RFM69 ------------------------------------------ //inputs const int TempSensor = 7; // Tempsensor name connected to pin7. (Don't forget the 4k7 pullup to 5v). // DS18B20 Temperature chip i/o OneWire ds(7); // TempSensor on pin 7 byte i; byte present = 0; byte data[12]; byte addr[8]; word TReading; byte HighByte, LowByte; int SignBit; long ReadStartTime; long ConvTime = 1000; //Time for a conversion to take place at 9 bit resolution boolean ConvIssued = false; //Signals a reading has been started boolean Sign; //temperature / humidity ===================================== float t = 0.00; float tempValue_previous = 0.00; // timings since they are greater than 32,768 they need to be a 'long' or 'unsigned long' rather than an 'int'. unsigned long temperature_interval = 4000; // read the temp every 4 seconds but conversion adds 1 more second unsigned long last_temperature_time; unsigned long last_send_time; unsigned long temperature_send_interval = 300000; // 5 *60 *1000 void setup() { Serial.begin(9600); // setup serial if (debug) { Serial.println("Serial Up"); } pinMode(TempSensor, INPUT);\ Temp_init(); // Find the One Wire sensor Read_Temp(); delay(500); // set the various temperatures tempValue_previous = t; //RFM69------------------------------------------- radio.initialize(FREQUENCY,NODEID,NETWORKID); #ifdef IS_RFM69HW radio.setHighPower(); //uncomment only for RFM69HW! #endif radio.encrypt(ENCRYPTKEY); if (debug) { char buff[50]; sprintf(buff, "\nTransmitting at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915); Serial.println(buff); } theData.nodeID = NODEID; //this node id should be the same for all devices in this node //end RFM-------------------------------------------- //initialize times last_temperature_time = millis(); if (debug) { Serial.println("Starting now"); } } void loop() { if (millis() - last_temperature_time > temperature_interval) // reads 4 seconds { Read_Temp(); } /* The idea is to minimise transmissions Send every five minutes or if the temp changes by 0.5 deg. */ if (t > (tempValue_previous + 0.5)|| (t < (tempValue_previous - 0.5))) { // Only start if the change is 0.5 or more. Temp_Send(); last_send_time = millis(); tempValue_previous = t; } if (millis() - last_send_time > temperature_send_interval) // 5 mins { Temp_Send(); last_send_time = millis(); tempValue_previous = t; } } void Read_Temp() { //reset and setup the temp sensor for communicating if (ConvIssued != true) { ds.reset(); ds.select(addr); ds.write(0x44,1); // start conversion ConvIssued = true; // We have asked for data ReadStartTime = millis(); // note the time we asked for temperature (so we know when to come back) } if (ConvIssued && (millis() - ReadStartTime) >= ConvTime) { /* This is the delay....The program should do the reset, and come back when the time is up. */ present = ds.reset(); ds.select(addr); ds.write(0xBE); // Read Scratchpad } else { return; //either there was no conversion issued, or time is too soon. } for ( i = 0; i < 9; i++) // we need 9 bytes { data[i] = ds.read(); } LowByte = data[0]; HighByte = data[1]; TReading = (HighByte << 8) + LowByte; SignBit = TReading & 0x8000; // test most sig bit ConvIssued = false; // means we got a reading back if (SignBit) // negative { Sign = 1; TReading = (TReading ^ 0xffff) + 1; // 2's comp } t = TReading *.0625; if (SignBit) //negative { t = (0 - t); } if (debug) { Serial.print("prev temp = "); Serial.println(tempValue_previous); Serial.print("cur temp = "); Serial.println(t); Serial.print("time : "); Serial.println(millis()); } last_temperature_time = millis(); // Check if any reads failed and exit early (to try again). if (isnan(t)) { if (debug) Serial.println("Failed to read from temp sensor!"); tempValue_previous =0; return; } } void Temp_Send() { theData.deviceID = 2; theData.Sign = Sign; theData.Temp = t; radio.sendWithRetry(GATEWAYID, (const void*)(&theData), sizeof(theData)); if (debug) { Serial.print("Sign : "); Serial.println(Sign); Serial.print("actual Temp : "); Serial.println(t); Serial.println("SENDING temp"); Serial.print("temp = "); Serial.println(theData.Temp); Serial.print("time : "); Serial.println(millis()); } } void Temp_init() // Find the One Wire device { if ( !ds.search(addr)) { if (debug) { Serial.print("No more addresses.\n"); } ds.reset_search(); delay(250); return; } if (debug) { Serial.print("R="); for( i = 0; i < 8; i++) { Serial.print(addr[i], HEX); Serial.print(" "); } if ( OneWire::crc8( addr, 7) != addr[7]) { Serial.print("CRC is not valid!\n"); return; } if ( addr[0] != 0x28) { Serial.print("Device is not a DS18B20 family device.\n"); return; } } }
In this use there is only one node and it sends the temperature, but you could also decide to send battery voltage, humidity or the sunlight.
The data is three pieces NodeID, Sign and the Temperature (Lines 258-260).
Receiver
The original had the receiver buried inside what looked like a book.
The serial output was fed into the Raspberry Pi and presented in OpenHAB.
For this application I needed an LCD.
It's not the best photo but the glare from the overhead lights was killing other angles ...
Receiver Code
/* Author: Eric Tsai License: CC-BY-SA, https://creativecommons.org/licenses/by-sa/2.0/ Date: 11-07-2016 File: ShedRFTempReceiver.ino This sketch receives RFM wireless data and forwards it to the Serial port and LCD display To Do: 1) 2) 3) */ /* **** connections ***** Moteino Pin assignments Pin D0 Rx Pin D1 Tx Pin D2 RFM69 D100 Pin D3 Pin D4 Pin D5 Pin D6 Pin D7 Pin D8 Pin D9 LED on moteino Pin D10 RFM69 SS Pin D11 RFM69 MOSI Pin D12 RFM69 MISO Pin D13 RFM69 SCK Pin A4 I2C Data Pin A5 I2C Clock Program as an UNO. */ //general -------------------------------- #include #include <liquidcrystal_i2c.h><liquidcrystal_i2c.h> //#include //RFM69 ---------------------------------- #include #include #define NODEID 1 //unique for each node on same network #define NETWORKID 100 //the same on all nodes that talk to each other #define FREQUENCY RF69_433MHZ //#define FREQUENCY RF69_915MHZ #define ENCRYPTKEY "TheShed123456789" //exactly the same 16 characters/bytes on all nodes! #define LED 9 //#define IS_RFM69HW //uncomment only for RFM69HW! Leave out if you have RFM69W! #define ACK_TIME 30 // max # of ms to wait for an ack #define SERIAL_BAUD 9600 RFM69 radio; bool promiscuousMode = false; //set to 'true' to sniff all packets on the same network boolean debug = 1; // set this to 1 if you want serial output for debugging. // MaxMin Temperature ----------------------------------- float TempReading = 99.99; //Temp reading as + or - figure float HighTemp; float LowTemp; //LCD Scrolling message byte i; char LCDMessage[8] ; // 7 char (0-6) scrolling message plus a null. byte MessageStartPosition =0; byte MessageStringCount; byte LCDPosition =0; char MessageString[] = "Current Temperature "; // message to be scrolled int ScrollDelay = 300; // Delay for the char scrolling. (use 750 for White chars) long LastScroll =0; // timer used in the delay // General Purpose boolean FirstReading = true; // Values assummed until reset by first reading int SignBit; // Used to signify a negative number // RFM69 data structure typedef struct { int nodeID; //node ID (1xx, 2xx, 3xx); eg 1xx = basement, 2xx = main floor, 3xx = outside boolean Sign; // true if negative temperature int sensorID; // sensor ID (2, 3, 4, 5) float Temp; // Temperature data } Payload; Payload theData; LiquidCrystal_I2C lcd(0x27,16,2); // set the LCD address to 0x27 for a 16 chars and 2 line display void setup() { lcd.init(); // initialize the lcd Wire.begin(); lcd.clear(); lcd.noCursor(); lcd.backlight(); // Turn on the backlight // Print a message to the LCD. lcd.setCursor(0,0); // First position (0) and first line (0) lcd.print (" Initialising"); lcd.setCursor(0,1); // First position (0) and second line (1) lcd.print (" The Shed 2016"); LastScroll = millis(); //check the time for the LCD message to be seen delay(5000-((millis())- (LastScroll))); // this should end up as 1.5 secs lcd.clear(); LastScroll=0; LCDScrollingMessage(); // Load the 7 chars for the scrolling message Serial.begin(9600); if (debug) { Serial.println("start"); } //RFM69 --------------------------- radio.initialize(FREQUENCY,NODEID,NETWORKID); #ifdef IS_RFM69HW radio.setHighPower(); //uncomment only for RFM69HW! #endif radio.encrypt(ENCRYPTKEY); radio.promiscuous(promiscuousMode); if (debug) { char buff[50]; sprintf(buff, "\nListening at %d Mhz...", FREQUENCY==RF69_433MHZ ? 433 : FREQUENCY==RF69_868MHZ ? 868 : 915); Serial.println(buff); } } // end of setup byte ackCount=0; void loop() { //This is basically a number of sub routines, each with a purpose UpdateMaxMin(); // Update the Min and Max Temperature variables HighLowTemp(); // Display the max amd Min temps DisplayTemp(); // Display the current Temp DisplayMessage(); // Display the 7 chars of the scrolling message LCDScrollingMessage(); // Scroll the message and load the next 7 chars. // check for incoming message if (radio.receiveDone()) { if (debug) { Serial.print("[NodeID:");Serial.print(radio.SENDERID, DEC);Serial.print("] "); Serial.print("[RX_RSSI:");Serial.print(radio.readRSSI());Serial.print("]"); } if (promiscuousMode) { Serial.print("to [");Serial.print(radio.TARGETID, DEC);Serial.print("] "); } if (radio.DATALEN != sizeof(Payload)) { Serial.println("Invalid payload received, not matching Payload struct!"); } else // ie the data size is correct { theData = *(Payload*)radio.DATA; SignBit = theData.Sign; TempReading = theData.Temp; if (SignBit) // the Temp Reading number is negative { SignBit = true; // means it is a negative number TempReading = 0.00 - TempReading; } // Send data out the Serial port for Future applications Serial.print(" SensorID="); Serial.print(theData.sensorID); Serial.print(":Temp="); if (SignBit) { Serial.print("-"); } Serial.println(TempReading); if (debug) { Serial.print("RawData="); Serial.println(theData.Temp); Serial.print("HighTemp: "); Serial.println(HighTemp); Serial.print("LowTemp: "); Serial.println(LowTemp); } } // The far end will send multiple times if an ACK is not received. if (radio.ACK_REQUESTED) { byte theNodeID = radio.SENDERID; radio.sendACK(); if (debug) { Serial.print(" - ACK sent."); Serial.println(); } } // show that we did something Blink(LED,30); } } void UpdateMaxMin() { if (TempReading != 99.99) // not the default value so must have received data { if (FirstReading) { HighTemp = TempReading; // set to the first reading LowTemp = TempReading; // set to the first reading } FirstReading = false; // allow real values to overwrite the assummed ones if (TempReading > HighTemp) { HighTemp = TempReading; } if (TempReading < LowTemp) { // temp is lower than we had LowTemp = TempReading; } } } void HighLowTemp() { /* This displays the highest and lowest temp Displays over two lines | H -xx.xx| |-xx.xx L -xx.xx| */ // Top line lcd.setCursor(9,0); lcd.print("H"); // first position (10) and first line (0) displays | Temp L -xx.x| lcd.setCursor(11,0); // 13th position (12) and first line (0) if (HighTemp < 10.0 && SignBit == false) { lcd.print(" "); } lcd.print(HighTemp); // Bottom line lcd.setCursor(9,1); // 11th position (10) and second line (1) lcd.print("L "); lcd.setCursor(11,1); // 13th position (12) and second line (1) if (LowTemp < 10.0 && SignBit == false) { lcd.print(" "); } lcd.print(LowTemp); } void DisplayTemp() // This displays the current temp { if (TempReading == 99.99) { lcd.setCursor(2,1); lcd.print("-.-"); return; } if(TempReading >= 0.0 && TempReading < 10.0) { lcd.setCursor(1,1); lcd.print(" "); // removes the - sign if it changes to a positive number lcd.print(TempReading); return; } if(SignBit && TempReading <= 0.0 && TempReading >= -9.99) { lcd.setCursor(2,1); lcd.print(TempReading); return; } if(SignBit && TempReading <= -10.0) { lcd.setCursor(1,1); lcd.print(TempReading); return; } // Do this one because we're still here lcd.setCursor(1,1); lcd.print(" "); lcd.print(TempReading); } void DisplayMessage() // This displays the 7 chars of the message on the first line { lcd.setCursor(0,0); lcd.print(LCDMessage); } void LCDScrollingMessage() { /* This demonstrates how to scroll a portion of the LCD The basic principle is to construct a message, find the length of the message. You then grab xx chars from the message to display. Each time you loop, you shift along the message one place and grab xx chars, and repeat. When you reach the end of the message, start grabbing from the beginning. A space at the end of the message, makes the message look correct on the LCD as it loops. Don't let the loop run too fast. */ MessageStringCount = (sizeof(MessageString))-1; // Last character in a string is a null, which is counted. if ((millis()-LastScroll) >=ScrollDelay) { for (i = 0; i <7; i++) //there is room for 7 chars (i = 0 to 6) { LCDPosition = MessageStartPosition +i; if (LCDPosition >= MessageStringCount) { LCDPosition = LCDPosition - MessageStringCount; } LCDMessage[i] = MessageString[LCDPosition]; } LCDMessage[7] = 0; //terminate the string with a null if (MessageStartPosition >= MessageStringCount) { MessageStartPosition =0; } else { MessageStartPosition +=1; } LastScroll = millis(); } } void Blink(byte PIN, int DELAY_MS) { pinMode(PIN, OUTPUT); digitalWrite(PIN,HIGH); delay(DELAY_MS); digitalWrite(PIN,LOW); }
I decided to use some of the techniques that I explained here Arduino, Temperature, LCD and more
The display scrolls 8 characters of a message ... in this case "Current Temperature"
The Arduino's I use for developing projects have the option of 5v or 3v3, which eliminates the need for level converters.
The RFM69 is a 3v3 device and while they survive 5v on the inputs, they don't work correctly, so either you'll need level converters, or a board that works on 3v3.
The biggest issue is most of the LCD's want 5v and hence the contrast becomes a problem.
I2C LCD's can happily be powered from 5v and 3v3 on the SDA and SCL pins, so they'll work correctly.
I do have a 3v3 I2C LCD somewhere that arrived with some other 5v versions.
There was some head scratching about why it wouldn't work until I noticed it had an extra chip on the board.
It seemed to have slipped into the suppliers shipment as they don't offer them.
Video
This is a simple demonstration showing the concept with the receiver connected to an Arduino Uno rather than the Moteino.
I've done this because as luck would have it, I didn't have a spare one available to photograph/video.
Hopefully this has refreshed some memory cells and provided some useful information to others interested in sending data from one place to another.
While you can use WiFi and other methods, having an intelligent receiver means you can add value to the data, or raise alarms when you're not getting it within the required time.
Mark
Top Comments