BTLE Beacon with 1-wire and I2C humidity sensors and LCD display.
authorPat Thoyts <patthoyts@users.sourceforge.net>
Fri, 16 Oct 2015 22:08:01 +0000 (23:08 +0100)
committerPat Thoyts <patthoyts@users.sourceforge.net>
Fri, 16 Oct 2015 22:08:01 +0000 (23:08 +0100)
nRF24_BLE_Temperature.ino [new file with mode: 0644]

diff --git a/nRF24_BLE_Temperature.ino b/nRF24_BLE_Temperature.ino
new file mode 100644 (file)
index 0000000..047ab18
--- /dev/null
@@ -0,0 +1,375 @@
+#include <Arduino.h>
+#include <Wire.h>
+#include <SPI.h>
+#include <OneWire.h>
+#include <DallasTemperature.h>
+#include <LiquidCrystal_I2C.h>
+
+#define LCD_ADDR 0x20
+#define LCD_EN_BIT 4
+#define LCD_RW_BIT 5
+#define LCD_RS_BIT 6
+#define LCD_D4_BIT 0
+#define LCD_BL_BIT 7
+
+LiquidCrystal_I2C lcd(LCD_ADDR, LCD_EN_BIT, LCD_RW_BIT, LCD_RS_BIT,
+LCD_D4_BIT, LCD_D4_BIT+1, LCD_D4_BIT+2, LCD_D4_BIT+3);
+
+#define PIN_CE  8 // chip enable
+#define PIN_CSN 10  // chip select (for SPI)
+#define PIN_LED 6
+#define PIN_ONEWIRE 4
+
+// The MAC address of BLE advertiser -- just make one up
+#define MY_MAC_0       0x01
+#define MY_MAC_1       0x02
+#define MY_MAC_2       0xef
+#define MY_MAC_3       0xbe
+#define MY_MAC_4       0xad
+#define MY_MAC_5       0xde
+
+// Service UUIDs used on the nRF8001 and nRF51822 platforms
+#define NRF_TEMPERATURE_SERVICE_UUID        0x1809
+#define NRF_BATTERY_SERVICE_UUID            0x180F
+#define NRF_DEVICE_INFORMATION_SERVICE_UUID 0x180A
+
+uint8_t buf[32];
+static const uint8_t chRf[] = {2, 26,80};
+static const uint8_t chLe[] = {37,38,39};
+uint8_t ch = 0;  // RF channel for frequency hopping
+
+#define SENSOR_RESOLUTION 12
+OneWire w1bus(PIN_ONEWIRE);
+DallasTemperature sensor(&w1bus);
+DeviceAddress sensorAddress;
+bool have_sensor = false;
+float currentTemperature = 20.0f;
+uint8_t state = 0;
+
+// Converts a temperature value in 100ths degrees C into a nRF float (4 bytes)
+int32_t nrf_float(float t)
+{
+    const int32_t exponent = -2;
+    return ((exponent & 0xff) << 24) | (((int32_t)(t * 100)) & 0x00ffffff);
+}
+
+// implementing CRC with LFSR
+void btLeCrc(const uint8_t* data, uint8_t len, uint8_t* dst)
+{
+    uint8_t v, t, d;
+
+    while(len--)
+    {
+        d = *data++;
+        for(v = 0; v < 8; v++, d >>= 1)
+        {
+            t = dst[0] >> 7;
+            dst[0] <<= 1;
+            if(dst[1] & 0x80) dst[0] |= 1;
+            dst[1] <<= 1;
+            if(dst[2] & 0x80) dst[1] |= 1;
+            dst[2] <<= 1;
+
+            if(t != (d & 1))
+            {
+              dst[2] ^= 0x5B;
+              dst[1] ^= 0x06;
+            }
+        }
+    }
+}
+
+// reverse the bit order in a single byte
+uint8_t swapbits(uint8_t x)
+{
+#ifdef __BUILTIN_AVR_INSERT_BITS
+    return __builtin_avr_insert_bits(0x01234567, x, 0);
+#else
+    uint8_t v = 0;
+    if(x & 0x80) v |= 0x01;
+    if(x & 0x40) v |= 0x02;
+    if(x & 0x20) v |= 0x04;
+    if(x & 0x10) v |= 0x08;
+    if(x & 0x08) v |= 0x10;
+    if(x & 0x04) v |= 0x20;
+    if(x & 0x02) v |= 0x40;
+    if(x & 0x01) v |= 0x80;
+    return v;
+#endif
+}
+
+// Implementing whitening with LFSR
+void btLeWhiten(uint8_t* data, uint8_t len, uint8_t whitenCoeff)
+{
+    uint8_t  m;
+    while(len--){
+        for(m = 1; m; m <<= 1){
+            if(whitenCoeff & 0x80){
+                whitenCoeff ^= 0x11;
+                (*data) ^= m;
+            }
+            whitenCoeff <<= 1;
+        }
+        data++;
+    }
+}
+
+//the value we actually use is what BT'd use left shifted one...makes our life easier
+static inline uint8_t btLeWhitenStart(uint8_t chan)
+{
+    return swapbits(chan) | 2; 
+}
+
+// Assemble the packet to be transmitted
+// Length is of packet, including crc. pre-populate crc in packet with initial crc value!
+void btLePacketEncode(uint8_t* packet, uint8_t len, uint8_t chan)
+{
+    uint8_t i, dataLen = len - 3;
+    btLeCrc(packet, dataLen, packet + dataLen);
+    for(i = 0; i < 3; i++, dataLen++) 
+        packet[dataLen] = swapbits(packet[dataLen]);
+    btLeWhiten(packet, len, btLeWhitenStart(chan));
+    for(i = 0; i < len; i++) 
+        packet[i] = swapbits(packet[i]); // the byte order of the packet should be reversed as well
+}
+
+uint8_t spi_byte(uint8_t byte)
+{
+    // using Arduino's SPI library; clock out one byte
+    SPI.transfer(byte); 
+    return byte;
+}
+
+void nrf_cmd(uint8_t cmd, uint8_t data)
+{
+    // Write to nRF24's register
+    digitalWrite(PIN_CSN, LOW);
+    spi_byte(cmd);
+    spi_byte(data);
+    digitalWrite(PIN_CSN, HIGH); 
+}
+
+void nrf_simplebyte(uint8_t cmd)
+{
+    // transfer only one byte 
+    digitalWrite(PIN_CSN, LOW); 
+    spi_byte(cmd);
+    digitalWrite(PIN_CSN, HIGH); 
+}
+
+void nrf_manybytes(uint8_t* data, uint8_t len)
+{
+    // transfer several bytes in a row
+    digitalWrite(PIN_CSN, LOW); 
+        do{
+            spi_byte(*data++);
+    } while(--len);
+    digitalWrite(PIN_CSN, HIGH); 
+}
+
+volatile int analogValue;
+static void Analog_Init()
+{
+    analogValue = 0;
+    // REFS[1:0]: 0b00: AREF; 0b01: AVCC; 0b11: internal 1V1
+    // ADLAR = 0: do not enable left adjust
+    // MUX[3:0]: using pin A1 (0b0001)
+    ADMUX = _BV(MUX0) | _BV(REFS0);// Internal 5V
+    // ADEN: enable the ADC
+    // ADATE: enable auto trigger
+    // ADIE: adc interrupt enabled
+    // ADPS[2:0]: adc clock prescale 16MHz/128 so 125kHz
+    ADCSRA = _BV(ADEN) | _BV(ADATE) | _BV(ADIE)
+    | _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0);
+    // ADTS[2:0]: AutoTrigger Source: use free running mode
+    ADCSRB = 0;
+    // Disable the digital functions for pin A2
+    DIDR0 |= _BV(ADC2D);
+    // ADSC: start the freerunning conversion
+    //    if not freerunning we could alternate between a set of sources
+    //     like AD8 which is the internal temperature.
+    ADCSRA |= _BV(ADSC);
+}
+
+static int GetAnalogValue() { return analogValue; }
+static int GetAnalogMillivolts() { return (analogValue * 5120) / 1024; }
+
+static long GetHumidity()
+{
+    // Vref is externally provided and set to 5ishV.
+    long millivolts = (analogValue * 5120) / 1024;
+    // HU10 range is 1V == 0% to 3V == 100%.
+    long rh = ((millivolts - 1000) * 100000) / 2000;
+    return rh; /* value is percentage * 1000 */
+}
+
+ISR(ADC_vect)
+{
+    // NOTE: MUST read the low byte first, reading ADCH resets.
+    analogValue = ADCL | (ADCH << 8);
+    // If not in free-running mode then start another conversion
+    // ADCSRA |= _BV(ADSC);
+}
+
+static void UpdateDisplay()
+{
+    lcd.setCursor(0, 1);
+    lcd.print(currentTemperature);
+    lcd.print((char)0xdf);
+    lcd.print("C ");
+    lcd.print(static_cast<float>(GetHumidity())/1000.0f);
+    lcd.print(" RH");
+}
+
+void setup()
+{
+    pinMode(PIN_LED, OUTPUT);
+    pinMode(PIN_CSN, OUTPUT);
+    pinMode(PIN_CE, OUTPUT);
+    pinMode(11, OUTPUT);
+    pinMode(13, OUTPUT);
+    digitalWrite(PIN_LED, HIGH);
+    digitalWrite(PIN_CSN, HIGH);
+    digitalWrite(PIN_CE, LOW);
+
+    sensor.begin();
+    sensor.setResolution(SENSOR_RESOLUTION);
+    sensor.setWaitForConversion(false);
+    have_sensor = sensor.getAddress(sensorAddress, 0);
+    if (have_sensor)
+        sensor.requestTemperatures();
+
+    Serial.begin(57600);
+    Serial.println(F("# BTLE beacon"));
+    SPI.begin();
+    SPI.setBitOrder(MSBFIRST);
+
+    Analog_Init();
+
+    Wire.begin();
+    lcd.begin(16,2);
+    lcd.clear();
+    lcd.print("BTLE Beacon");
+
+    // Now initialize nRF24L01+, setting general parameters
+    nrf_cmd(0x20, 0x12);       //on, no crc, int on RX/TX done
+    nrf_cmd(0x21, 0x00);       //no auto-acknowledge
+    nrf_cmd(0x22, 0x00);       //no RX
+    nrf_cmd(0x23, 0x02);       //4-byte address
+    nrf_cmd(0x24, 0x00);       //no auto-retransmit
+    nrf_cmd(0x26, 0x06);       //1MBps at 0dBm
+    nrf_cmd(0x27, 0x3E);       //clear various flags
+    nrf_cmd(0x3C, 0x00);       //no dynamic payloads
+    nrf_cmd(0x3D, 0x00);       //no features
+    nrf_cmd(0x31, 32);   //always RX 32 bytes
+    nrf_cmd(0x22, 0x01);       //RX on pipe 0
+
+    // Set access addresses (TX address in nRF24L01) to BLE advertising 0x8E89BED6
+    // Remember that both bit and byte orders are reversed for BLE packet format
+    buf[0] = 0x30;
+    buf[1] = swapbits(0x8E);
+    buf[2] = swapbits(0x89);
+    buf[3] = swapbits(0xBE);
+    buf[4] = swapbits(0xD6);
+    nrf_manybytes(buf, 5);
+    buf[0] = 0x2A;    // set RX address in nRF24L01, doesn't matter because RX is ignored in this case
+    nrf_manybytes(buf, 5);
+
+    for (int n = 0; n < 20; ++n)
+    {
+        digitalWrite(PIN_LED, !digitalRead(PIN_LED));
+        delay(100);
+    }
+
+    // must have waited a total of 750 ms
+    if (have_sensor)
+    {
+        currentTemperature = sensor.getTempC(sensorAddress);
+    }
+    UpdateDisplay();
+}
+
+void loop()
+{
+    // Channel hopping
+    for (ch=0; ch<sizeof(chRf); ch++)
+    {
+        uint8_t i, L=0;
+
+        buf[L++] = 0x42;       //PDU type, given address is random; 0x42 for Android and 0x40 for iPhone
+        buf[L++] = 24; // length of payload
+
+        buf[L++] = MY_MAC_0;
+        buf[L++] = MY_MAC_1;
+        buf[L++] = MY_MAC_2;
+        buf[L++] = MY_MAC_3;
+        buf[L++] = MY_MAC_4;
+        buf[L++] = MY_MAC_5;
+
+        /* device descriptor chunk */
+        buf[L++] = 2;     /* chunk size 2 bytes */
+        buf[L++] = 0x01;  /* chunk type: device flags */
+        buf[L++] = 0x05;  /* data: 0x05 == LE only, limited discovery mode */
+
+        /* name chunk */
+        buf[L++] = 6;     /* chunk size: 7 bytes */
+        buf[L++] = 0x09;  /* chunk type: complete name */
+        buf[L++] = 'n';
+        buf[L++] = 'R';
+        buf[L++] = 'F';
+        buf[L++] = '2';
+        buf[L++] = '4';
+
+        buf[L++] = 7;
+        buf[L++] = 0x16;
+        buf[L++] = (uint8_t)(NRF_TEMPERATURE_SERVICE_UUID);
+        buf[L++] = (uint8_t)((NRF_TEMPERATURE_SERVICE_UUID >> 8) & 0xff);
+        {
+            int32_t t = nrf_float(currentTemperature);
+            memcpy(&buf[L], &t, sizeof(t));
+            L += sizeof(t);
+        }
+
+        buf[L++] = 0x55;       //CRC start value: 0x555555
+        buf[L++] = 0x55;
+        buf[L++] = 0x55;
+
+        nrf_cmd(0x25, chRf[ch]);
+        nrf_cmd(0x27, 0x6E);   // Clear flags
+
+        btLePacketEncode(buf, L, chLe[ch]);
+        nrf_simplebyte(0xE2); //Clear RX Fifo
+        nrf_simplebyte(0xE1); //Clear TX Fifo
+
+        digitalWrite(PIN_CSN, LOW);
+        spi_byte(0xA0);
+        for(i = 0 ; i < L ; i++) spi_byte(buf[i]);
+        digitalWrite(PIN_CSN, HIGH); 
+
+        nrf_cmd(0x20, 0x12);   // TX on
+        digitalWrite(PIN_CE, HIGH); // Enable Chip
+        delay(2);        // 
+        digitalWrite(PIN_CE, LOW);   // (in preparation of switching to RX quickly)
+    }
+
+    if (have_sensor)
+    {
+        switch (state)
+        {
+          case 0:
+              sensor.requestTemperatures();
+              state = 1;
+              break;
+          case 1:
+              state = 2; // just allow over 750ms for conversion.
+              break;
+          case 2:
+              currentTemperature = sensor.getTempC(sensorAddress);
+              Serial.println(currentTemperature);
+              UpdateDisplay();
+              state = 0;
+        }
+    }
+    delay(500);
+    digitalWrite(PIN_LED, !digitalRead(PIN_LED));
+}