My code is very hacked together from sample Adafruit code right now, but it's working fine. Updates the display every 3 minutes (to avoid excessive e-ink updates) and transmits the CO2 reading every 5 seconds via the RFM69.
Code: Select all
/***************************************************
Adafruit invests time and resources providing this open source code,
please support Adafruit and open-source hardware by purchasing
products from Adafruit!
Written by Limor Fried/Ladyada for Adafruit Industries.
MIT license, all text above must be included in any redistribution
****************************************************/
/* CO2 & Display Test
*
* Written for an Atmel 32u4 powered, Adafruit Feather board with RFM69 915 MHz radio module, with attached
* Adafruit SDC-30 sensor module.
*
* This program was intended as an introduction to using the AdaFruit ThinkInk 2.9" grayscale E-ink display
* (296x128 pixels), and the Adafruit SDC-30 sensor breakout module. The SCD30 sensor is an NDIR CO2 sensor,
* which uses IR light and bandwidth limiting filters to measure CO2 concentration by IR absorption,
* compared to a sealed sample of N2 gas for reference absorption and correction of N2 absorption.
*
* To correct for temp and humidity, the SDC-30 CO2 sensor also includes an integrated temp and humidity
* sensor chip. These automatically correct the CO2 sensor values before they are read by the microcontroller.
*
* Specs:
* CO2 sensor:
* Range: 400 - 10,000 ppm
* Accuracy: ±(30 ppm + 3%)
* Response Time (τ63%): 20 seconds
* Lifetime: 15 years
* Relative Humidity sensor:
* Range: 0-95% RH
* Accuracy: ±3%
* Response time (τ63%): 8 seconds
* Temperature sensor:
* Range: -40 - 70 °C
* Accuracy: ±0.4 °C
* Response time (τ63%): 10 seconds
* SCD30 current consumption: 19 mA at 2 second sample rate (max 75 mA)
*
* Jonathan Seyfert
* 2023-02-03
*/
#include "Adafruit_ThinkInk.h"
#include <Adafruit_SCD30.h>
Adafruit_SCD30 scd30;
#define EPD_DC 10 // can be any pin, but required!
#define EPD_CS 9 // can be any pin, but required!
#define EPD_BUSY -1 // can set to -1 to not use a pin (will wait a fixed delay) <- Available but not connected
#define SRAM_CS 6 // can set to -1 to not use a pin (uses a lot of RAM!)
#define EPD_RESET -1 // can set to -1 and share with chip Reset (can't deep sleep)
//The following two sections of #ifdef are for the freeMemory() function, to check available free memory for debugging purposes
#ifdef __arm__
// should use uinstd.h to define sbrk but Due causes a conflict
extern "C" char* sbrk(int incr);
#else // __ARM__
extern char *__brkval;
#endif // __arm__
int freeMemory() {
char top;
#ifdef __arm__
return &top - reinterpret_cast<char*>(sbrk(0));
#elif defined(CORE_TEENSY) || (ARDUINO > 103 && ARDUINO != 151)
return &top - __brkval;
#else // __arm__
return __brkval ? &top - __brkval : &top - __malloc_heap_start;
#endif // __arm__
}
// VBATPIN is to measure battery voltage, but that pin is used by E-ink chip select currently, so cannot use.
// #define VBATPIN A9 // Internal battery voltage divider measurement pin
// 2.9" Grayscale Featherwing or Breakout:
ThinkInk_290_Grayscale4_T5 display(EPD_DC, EPD_RESET, EPD_CS, SRAM_CS, EPD_BUSY);
#define COLOR1 EPD_BLACK
#define COLOR2 EPD_LIGHT
#define COLOR3 EPD_DARK
const unsigned long updateTime = 180000; // How often to update display
unsigned long timer = 0; // Used to check if it's time to update display
// From here to setup() is the stuff for the RFM69 Radiohead Tx demo
// rf69 demo tx rx.pde
// -*- mode: C++ -*-
// Example sketch showing how to create a simple messageing client
// with the RH_RF69 class. RH_RF69 class does not provide for addressing or
// reliability, so you should only use RH_RF69 if you do not need the higher
// level messaging abilities.
// It is designed to work with the other example rf69_server.
// Demonstrates the use of AES encryption, setting the frequency and modem
// configuration
#include <SPI.h>
#include <RH_RF69.h>
/************ Radio Setup ***************/
// Change to 434.0 or other frequency, must match RX's freq!
#define RF69_FREQ 915.0
#if defined (__AVR_ATmega32U4__) // Feather 32u4 w/Radio
#define RFM69_CS 8
#define RFM69_INT 7
#define RFM69_RST 4
#define LED 13
#elif defined(ADAFRUIT_FEATHER_M0) || defined(ADAFRUIT_FEATHER_M0_EXPRESS) || defined(ARDUINO_SAMD_FEATHER_M0)
// Feather M0 w/Radio
#define RFM69_CS 8
#define RFM69_INT 3
#define RFM69_RST 4
#define LED 13
#elif defined (__AVR_ATmega328P__) // Feather 328P w/wing
#define RFM69_INT 3 //
#define RFM69_CS 4 //
#define RFM69_RST 2 // "A"
#define LED 13
#elif defined(ESP8266) // ESP8266 feather w/wing
#define RFM69_CS 2 // "E"
#define RFM69_IRQ 15 // "B"
#define RFM69_RST 16 // "D"
#define LED 0
#elif defined(ARDUINO_ADAFRUIT_FEATHER_ESP32S2) || defined(ARDUINO_NRF52840_FEATHER) || defined(ARDUINO_NRF52840_FEATHER_SENSE)
#define RFM69_INT 9 // "A"
#define RFM69_CS 10 // "B"
#define RFM69_RST 11 // "C"
#define LED 13
#elif defined(ESP32) // ESP32 feather w/wing
#define RFM69_RST 13 // same as LED
#define RFM69_CS 33 // "B"
#define RFM69_INT 27 // "A"
#define LED 13
#elif defined(ARDUINO_NRF52832_FEATHER)
/* nRF52832 feather w/wing */
#define RFM69_RST 7 // "A"
#define RFM69_CS 11 // "B"
#define RFM69_INT 31 // "C"
#define LED 17
#endif
/* Teensy 3.x w/wing
#define RFM69_RST 9 // "A"
#define RFM69_CS 10 // "B"
#define RFM69_IRQ 4 // "C"
#define RFM69_IRQN digitalPinToInterrupt(RFM69_IRQ )
*/
/* WICED Feather w/wing
#define RFM69_RST PA4 // "A"
#define RFM69_CS PB4 // "B"
#define RFM69_IRQ PA15 // "C"
#define RFM69_IRQN RFM69_IRQ
*/
// Singleton instance of the radio driver
RH_RF69 rf69(RFM69_CS, RFM69_INT);
int16_t packetnum = 0; // packet counter, we increment per xmission
void setup()
{
scd30.begin();
display.begin(THINKINK_GRAYSCALE4);
delay(5000); // Pause for 5 seconds to allow sensor time to initialize before displaying inital values
readAndDisplaySCD30(); // Display initial values on power-up before entering 3 minute update cycle
// From here to end of setup() is for radiohead tx demo
Serial.begin(115200);
//while (!Serial) { delay(1); } // wait until serial console is open, remove if not tethered to computer
pinMode(LED, OUTPUT);
pinMode(RFM69_RST, OUTPUT);
digitalWrite(RFM69_RST, LOW);
Serial.println("Feather RFM69 TX Test!");
Serial.println();
// manual reset
digitalWrite(RFM69_RST, HIGH);
delay(10);
digitalWrite(RFM69_RST, LOW);
delay(10);
if (!rf69.init()) {
Serial.println("RFM69 radio init failed");
while (1);
}
Serial.println("RFM69 radio init OK!");
// Defaults after init are 434.0MHz, modulation GFSK_Rb250Fd250, +13dbM (for low power module)
// No encryption
if (!rf69.setFrequency(RF69_FREQ)) {
Serial.println("setFrequency failed");
}
// If you are using a high power RF69 eg RFM69HW, you *must* set a Tx power with the
// ishighpowermodule flag set like this:
rf69.setTxPower(20, true); // range from 14-20 for power, 2nd arg must be true for 69HCW
// The encryption key has to be the same as the one in the server
uint8_t key[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
rf69.setEncryptionKey(key);
pinMode(LED, OUTPUT);
Serial.print("RFM69 radio @"); Serial.print((int)RF69_FREQ); Serial.println(" MHz");
}
void loop()
{
if(millis() - timer >= updateTime)
{
readAndDisplaySCD30();
timer = millis();
}
scd30.read();
char radiopacket[20];
dtostrf(scd30.CO2, 6, 3, radiopacket); // needs more work?
// itoa(packetnum++, radiopacket+13, 10);
Serial.print("Sending "); Serial.println(radiopacket);
// Send a message!
rf69.send((uint8_t *)radiopacket, strlen(radiopacket));
rf69.waitPacketSent();
delay(5000);
}
void readAndDisplaySCD30()
{
scd30.read();
// float measuredvbat = analogRead(VBATPIN);
// measuredvbat *= 2; // we divided by 2, so multiply back
// measuredvbat *= 3.3; // Multiply by 3.3V, our reference voltage
// measuredvbat /= 1024; // convert to voltage
display.clearBuffer();
display.setTextSize(2);
display.setTextColor(EPD_BLACK);
display.setCursor(5, 5);
display.print(F("Temp: "));
display.print(scd30.temperature);
display.print(F(" Celcius"));
display.setCursor(5, 30);
display.print(F("RH: "));
display.print(scd30.relative_humidity);
display.print(F(" %"));
display.setCursor(5, 55);
display.print(F("CO2: "));
display.print(scd30.CO2, 3);
display.print(F(" ppm"));
display.setCursor(5, 80);
display.print(F("Free Memory: "));
display.print(freeMemory());
display.print(F(" bytes"));
//char *textDisplay[] = {""};
display.setCursor(0, 100);
display.setTextSize(1);
display.setTextWrap(true);
display.print(F("This display updates every 3 minutes. E-ink display uses no power except when updating. If sensor board has green LED lit, system is running."));
display.display();
}
Code: Select all
# SPDX-FileCopyrightText: 2018 Brent Rubell for Adafruit Industries
#
# SPDX-License-Identifier: MIT
"""
Example for using the RFM69HCW Radio with Raspberry Pi.
Learn Guide: https://learn.adafruit.com/lora-and-lorawan-for-raspberry-pi
Author: Brent Rubell for Adafruit Industries
"""
# Import Python System Libraries
import time
# Import Blinka Libraries
import busio
from digitalio import DigitalInOut, Direction, Pull
import board
# Import the SSD1306 module.
import adafruit_ssd1306
# Import the RFM69 radio module.
import adafruit_rfm69
# Button A
btnA = DigitalInOut(board.D5)
btnA.direction = Direction.INPUT
btnA.pull = Pull.UP
# Button B
btnB = DigitalInOut(board.D6)
btnB.direction = Direction.INPUT
btnB.pull = Pull.UP
# Button C
btnC = DigitalInOut(board.D12)
btnC.direction = Direction.INPUT
btnC.pull = Pull.UP
# Create the I2C interface.
i2c = busio.I2C(board.SCL, board.SDA)
# 128x32 OLED Display
reset_pin = DigitalInOut(board.D4)
display = adafruit_ssd1306.SSD1306_I2C(128, 32, i2c, reset=reset_pin)
# Clear the display.
display.fill(0)
display.show()
width = display.width
height = display.height
# Configure Packet Radio
CS = DigitalInOut(board.CE1)
RESET = DigitalInOut(board.D25)
spi = busio.SPI(board.SCK, MOSI=board.MOSI, MISO=board.MISO)
rfm69 = adafruit_rfm69.RFM69(spi, CS, RESET, 915.0)
prev_packet = None
# Optionally set an encryption key (16 byte AES key). MUST match both
# on the transmitter and receiver (or be set to None to disable/the default).
rfm69.encryption_key = b'\x01\x02\x03\x04\x05\x06\x07\x08\x01\x02\x03\x04\x05\x06\x07\x08'
while True:
packet = None
# draw a box to clear the image
display.fill(0)
display.text('RasPi Radio', 35, 0, 1)
# check for packet rx
packet = rfm69.receive()
if packet is None:
display.show()
display.text('- Waiting for PKT -', 15, 20, 1)
else:
# Display the packet text and rssi
display.fill(0)
prev_packet = packet
packet_text = str(prev_packet, "utf-8")
display.text('RX: ', 0, 0, 1)
display.text(packet_text, 25, 0, 1)
print(packet_text)
print(rfm69.rssi)
time.sleep(1)
if not btnA.value:
# Send Button A
display.fill(0)
button_a_data = bytes("Button A!\r\n","utf-8")
rfm69.send(button_a_data)
display.text('Sent Button A!', 25, 15, 1)
elif not btnB.value:
# Send Button B
display.fill(0)
button_b_data = bytes("Button B!\r\n","utf-8")
rfm69.send(button_b_data)
display.text('Sent Button B!', 25, 15, 1)
elif not btnC.value:
# Send Button C
display.fill(0)
button_c_data = bytes("Button C!\r\n","utf-8")
rfm69.send(button_c_data)
display.text('Sent Button C!', 25, 15, 1)
display.show()
time.sleep(0.1)
I noticed reading the documentation that:
If I'm understanding that correctly, between the "pure Python code" and "no interrupt support" I'm guessing it's always going to use a bit of CPU? I know Python, being an interpreted language, is not very efficient.This is a ‘best effort’ at receiving data using pure Python code–there is not interrupt support so you might lose packets if they’re sent too quickly for the board to process them. You will have the most luck using this in simple low bandwidth scenarios like sending and receiving a 60 byte packet at a time–don’t try to receive many kilobytes of data at a time!
Essentially, I'm looking for suggestions. Things I have thought of as possibilities:
- Continue to try as-is, and see what happens.
- Try to optimize the existing Adafruit Python script for lower CPU usage without affecting ability to receive.
- Locate a different RFM69 library written in C++ for faster performance.
- Attempt to write my own library, or at least program, in C++ to run on the Pi for faster performance (this would be ambitious of me to try at this point).
- Don't use the Pi for receiving, but use another microcontroller, buffer the received packets, and have the Pi grab these buffered packets at a slow rate (like once a minute).
- Use a faster Pi for receiving. I'm certainly not opposed to this, but the Pi Zero W is the only Pi I have, and looks like it'll be the middle of the year before production is high enough I can get my hands on a Pi 3 or Pi 4. Plus currently it appears there's 1-per-customer limits on all Pis, and I was hoping to set up a Pi as a portable programming computer, so if limited to 1-per-customer, my 1 would go towards that. So essentially using a faster Pi as the base station is an option, but only once supplies recover to allow easy access to purchasing multiple Pis.