Workshop Creating a TTN Node - LABS story by Frank Beks

(Laurens Slats) #1

Another great LABS story has been submitted by @Galagaking

Let's use this topic to discuss the story and to share the experiences of the people who tried to fulfill the workshop.

(Bonsj) #2

17 dec was the finishing workshop, we needed to bring the nodes that were not working. Well i knew that mine was not working :slight_smile: since the last workshop.I tried to troubleshoot it Led was working, temp sensor was found so i needed to check if the rfm chip was functioning.So my start last saturday was blank again i restarted building the node. That means desoldering the arduino from the board This was before the desoldering :slight_smile:

After attacking the board with a cutting pliers :blush:

Frank and Martijn helpt me to point out how to solder and guided real good. Eventually i finished it at home and it looks like it works :slight_smile:

now to find a gateway to connect to or bring it to my work. ::fingers crossed::

I want to thank everyone that visited the workshop for the input and advise!
(Yes i know im not allow to add more then one picture on my first post so i added links :slight_smile: )


Hi all,

I am new to the TTN and last weeks I have been playing with my RPi to build a gateway and a node. The gateway worked pretty fast but the node part frustreted me so much I decided to build a simple Arduino node first.
I found this lab and I ordered the kit form TinyTronics. However the kit does not contain all the itmes used in the lab. It would be nice to make a notice of this. What I am missing are the PCB ‘Loratracker RFM98 Sensor TH06 (si7102) (Temperature and Humidity). As I am pretty new to all this I do not know if the node can be build without these components but I can imagine the sensor is not that super important to have but the PCB looks pretty essental to the design.
I am going to order these parts seperately and try to build tis node.
After a reply from Frank I got the parts send to me :slight_smile: As soon as I have some pictures I will post them :slight_smile:


Hi @jschellart, sorry about the missing parts (PCB, headers and Temp sensor). I have some PCBs left from the workshop, including the headers (special small ones). Drop me a mail and we can arrange something here! Merry Christmas, Cheers, Frank


Hallo Frank,

Hello Frank,

I also got a message from Martijn saying you might be able to help out.
I am from Delft so if I can buy the parts somehow that would be great.
I also found instructions on how to build a node with the parts I have and I am experimenting with this one at the moment.
Just let me know how we can settle this. I can pick up the stuff or you can send them to me.

Happy Christmas and a good New Year!




Gents, this is an English language forum, use direct messages for Dutch please.

(Robbiedeloose) #9

I just started out with TTN this article sparked my interrest. Do you still have pcb's available? I saw the link to, but there are a lot of different boards there to choose from. What specific board was used for this project?


Hi @robbiedeloose, it is the 'RFM98 HAB Tracker and Lost Model Locator Board' , as shown on this page. I do not have PCBs available anymore, working on a new design using the PCBs from Doug LaRue. Cheers, Frank

(Sgt Wilko) #11


Nice tutorial!

I've made one (Sans sensors as I just wanted to prove my gateway worked, and work on range), but I'm struggling with one part.

The Node works fine when attached to my USB cable, but I'm having power issues when I try to power it from the In+/In- connection as mentioned in the tutorial.

One part where I did deviate (as I wanted to make sure it worked before I broke it!) was that I left the power regulator and LED connected on the Arduino, and therefore didn't connected the bridge between RAW and Vcc, however my multimeter isn't showing any power reaching RAW.

I've been over the circuit diagram from the LoRa Tracker website, but I'm can't to see how the power is failing to reach RAW.

Any advice?


(Sgt Wilko) #12

I managed to trace the tracks and found the issue.
If anyone else has this problem, the more recent PCBs have a gap for a pollyfuse that you need to bridge.

@Galagaking you might want to make note of this on the tutorial as if you miss this step the arduino programming header is directly over the pads that you need to solder.


Hi @SgtWilko, thanks for mentioning. Good to see you got this working! Can you share a picture of this point, so I can include it in my manual? Thnx, Frank


You can use my photos Frank, there you can see the polyfuse and next to it i added a connector for the most often used JST-PH from LiPo accus. The JST-PH is best placed there right before you solder the arduino pro mini, because later its hard to get it into position, in the second row on the far outside position:

(Sgt Wilko) #15

Thanks @Caspar That's a better photo, and a neater looking board than @Galagaking would have had from me!


Hi Frank,

i would suggest a little software upgrade on the node as well. You don't have to do all the bit shifting on your own, but could use this library: and i would use a different approach to read the voltage from the battery for more precision.

Your code would be only modified on 2 or 3 positions, first you include the library (after installing it) on line 41 or so:

#include <LoraEncoder.h>
#include <LoraMessage.h>

Next you add the following function to your code:

long readVcc() {
  // Read 1.1V reference against AVcc
  // set the reference to Vcc and the measurement to the internal 1.1V reference
  #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
    ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
  #elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
    ADMUX = _BV(MUX5) | _BV(MUX0);
  #elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__)
    ADMUX = _BV(MUX3) | _BV(MUX2);
  #else // for ATmega328(P) & ATmega168(P)
    ADMUX = (0<<REFS1) | (1<<REFS0) | (0<<ADLAR) | (1<<MUX3) | (1<<MUX2) | (1<<MUX1) | (0<<MUX0);

  delay(2); // Wait for Vref to settle
  ADCSRA |= _BV(ADSC); // Start conversion
  while (bit_is_set(ADCSRA,ADSC)); // measuring

  uint8_t low  = ADCL; // must read ADCL first - it then locks ADCH  
  uint8_t high = ADCH; // unlocks both

  long result = (high<<8) | low;

  result = 1125300L / result; // Calculate Vcc (in mV); 1125300 = 1.1*1023*1000 - standard value for calibration
  return result; // Vcc in millivolts

You have to check the voltage on your board (it will not be exactly 3.3v) and calculate it against the readVcc() value. Put it into this formula and replace the 1125300L with the result:

internal1.1Ref = 1.1 * Vcc1 (per voltmeter) / Vcc2 (per readVcc() function)

For more information look at this page: (be aware his code does not cover the 328P used on the Arduino Pro Mini - take the code from me above).

Now in your code you go into the "do_send()" function and erase or comment out all the stuff about the constraints on temp&humi and the vcc stuff too and add the following lines:

int vccValue = readVcc();
    Serial.print(F(" Battery: "));
    LoraMessage message;


and then comment out all the bit shifting stuff too and change the "LMIC_setTxData2" line:

LMIC_setTxData2(port, message.getBytes(), message.getLength() , 0);

Now you have to decode it properly in the TTN Backend Payload Functions, first you have to add a bunch of code, right inside the decoder function:

  var bytesToInt = function(bytes) {
    var i = 0;
    for (var x = 0; x < bytes.length; x++) {
      i |= +(bytes[x] << (x * 8));
    return i;
  var unixtime = function(bytes) {
    if (bytes.length !== unixtime.BYTES) {
      throw new Error('Unix time must have exactly 4 bytes');
    return bytesToInt(bytes);
  unixtime.BYTES = 4;
  var uint8 = function(bytes) {
    if (bytes.length !== uint8.BYTES) {
      throw new Error('int must have exactly 1 byte');
    return bytesToInt(bytes);
  uint8.BYTES = 1;
  var uint16 = function(bytes) {
    if (bytes.length !== uint16.BYTES) {
      throw new Error('int must have exactly 2 bytes');
    return bytesToInt(bytes);
  uint16.BYTES = 2;
  var latLng = function(bytes) {
    if (bytes.length !== latLng.BYTES) {
      throw new Error('Lat/Long must have exactly 8 bytes');
    var lat = bytesToInt(bytes.slice(0, latLng.BYTES / 2));
    var lng = bytesToInt(bytes.slice(latLng.BYTES / 2, latLng.BYTES));
    return [lat / 1e6, lng / 1e6];
  latLng.BYTES = 8;
  var temperature = function(bytes) {
    if (bytes.length !== temperature.BYTES) {
      throw new Error('Temperature must have exactly 2 bytes');
    var t = bytesToInt(bytes);
    if ((bytes[1] & 0x60)) {
      t = ~t + 1;
    return t / 1e2;
  temperature.BYTES = 2;
  var humidity = function(bytes) {
    if (bytes.length !== humidity.BYTES) {
      throw new Error('Humidity must have exactly 2 bytes');
    var h = bytesToInt(bytes);
    return h / 1e2;
  humidity.BYTES = 2;
  var decode = function(bytes, mask, names) {
    var maskLength = mask.reduce(function(prev, cur) {
      return prev + cur.BYTES;
    }, 0);
    if (bytes.length < maskLength) {
      throw new Error('Mask length is ' + maskLength + ' whereas input is ' + bytes.length);
    names = names || [];
    var offset = 0;
    return mask
      .map(function(decodeFn) {
        var current = bytes.slice(offset, offset += decodeFn.BYTES);
        return decodeFn(current);
      .reduce(function(prev, cur, idx) {
        prev[names[idx] || idx] = cur;
        return prev;
      }, {});
  if (typeof module === 'object' && typeof module.exports !== 'undefined') {
    module.exports = {
      unixtime: unixtime,
      uint8: uint8,
      uint16: uint16,
      temperature: temperature,
      humidity: humidity,
      latLng: latLng,
      decode: decode

and then you can decode the payload with just one line of code:

return decode(bytes, [uint16, temperature, humidity], ['battery', 'temp', 'humi']);

Now it is pretty simple to add different sensors, numbers, readings to the payload and let it be encoded without having to think about how to put it into bytes, just let the library do it and decoding is as simple as changing the decode line a bit. For the battery output you now get the voltage from the accu you connect to the node, but be aware this only works without any voltage regulator. For example using standard LiPo accus with 3.7V, you now get a reading from ca. 4.2V (full) to 3.0V (empty).

Maybe you can/want to add some of the code for your meetup in eindhoven? I can provide the full code too ofcourse, but i have commented out the sensor stuff because my node doesn't have the sensors and i simulate them with fixed numbers :wink:

The Things Node : new low power library development
(Arjan) #17

On the other hand: if you've got time to answer questions in a workshop, it might be nice to teach folks what's going on behind the scenes. Also, the original code makes some nice assumptions about the supported range and accuracy, and then only needs 2 bytes to send both humidity and temperature, while this generic library needs twice that size. :wink: (Okay, it's a smaller difference when also taking the 13 byte LoRaWAN header into account, which needs to be sent for both encodings.)

As an aside: the LABS story uses ABP, which indeed is great for workshops. Just for future reference: using OTAA at workshops might be troublesome if all devices register at about the same time, as then the gateway might run into its duty cycle limitations when trying to send the Join Accept, making OTAA troublesome. ABP is a good choice.


You're right, people come to workshops to learn something and to understand how the encoding works is a major part of it. Maybe Frank could first show them how to encode for themselves and later give a way the code for the library to make it easier to develop more complex nodes.

Ohh and he changed his code already to OTAA ( - ttn_temphumi_battery.ino) in his example, so the next workshops he can demonstrate both ABP and OTAA. He is going to use a different PCB too, the one from Doug Larue: because it is a lot easier to solder, all the parts are on one side.

(Sgt Wilko) #19

I originally used the OTAA code linked in the tutorial, and I couldn't get it to work.

Maybe my gateway is at fault (I'm running a single channel gateway to get going), but it worked first time with the ABP code.

(Sgt Wilko) #20

Also, I keep running out of program space on the pro-mini.

I've used the ABP code and stripped out the sensor stuff. Added in GPS code and first attempt was too big.

Having removed debug code I'm now just shy of 98%, am I doing something inane?


Hi @arjanvanb, i have included the OTAA example and added ABP afterwards. I think in the end everyone should use the OTAA way of authentication. Not every node is starting the same time at the workshop, we had no trouble here (more problems with soldering :slight_smile: ), but good point about OTAA. I think there is some problem in the design because it is not using NSS on the 'normal' pin 10. I experienced a lot of OTAA problems with this design. The 'DougLarue' board is working 'out of the box' with NSS on pin 10. Agree on the educational part of bit shifting and ranges. It comes together with some powerpoint sheets explaining ranges and bits.


Hi @SgtWilko, i have seen some problems with OTAA as well. I think the NSS pin 8 is giving some trouble here. Normally SPI is using pin 10 for NSS. The same code is working 'out of the box' with NSS on pin 10. About your code size: which GPS library are you using? In the past i did use Tried to compile with the basis example and OTAA-LMIC (disabling PING and BEACON in config.h in LMIC, and setting debugging 0)
Sketch uses 21946 bytes (71%) of program storage space. Maximum is 30720 bytes.
Global variables use 1623 bytes (79%) of dynamic memory, leaving 425 bytes for local variables. Maximum is 2048 bytes.
Low memory available, stability problems may occur.
Only 425 bytes dynamic memory is low, stack is used a lot in these libraries. You could give it a try. But if you do not want these memory problems look for the feather M0 or Teensy processor boards, they offer (a lot) more memory for your application!