в теме про метеостанцию писал про датчик с передатчиком на NRF24L01 и возможность использования с метеостанцией
простой код приемника на esp
// https://aterlux.ru/article/nrf24l01p
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
LiquidCrystal_I2C lcd(0x3F,20,4); // set the LCD address to 0x27 for a 16 chars and 2 line, для 20x4 - 0x3F
//#include <SPI.h>
//#include "nRF24L01.h"
#include "RF24.h"
int32_t data[5];
RF24 radio(99, 15); // CE, CSN, 99 - это заглушка, чтобы освободить один пин, если передача не предполлагается то можно CE просто подтянуть резистором к питанию, иначе нужно указать вывод
void setup(void)
{
lcd.init();
lcd.backlight();
radio.begin();
radio.setAutoAck(true); // radio.setAutoAck(true); включение и ли отклучение автоподтверждения
radio.setChannel(100);
radio.setDataRate (RF24_2MBPS); // скорость обмена RF24_2MBPS, RF24_1MBPS, RF24_250KBPS - максимальная чувствительность на 250 кбитс, NRF24L01 без плюса 250 не умеет
radio.setPALevel (RF24_PA_MAX); // уровень мощности RF24_PA_MIN -18dBm, RF24_PA_LOW -12dBm, RF24_PA_HIGH -6dBM, RF24_PA_MAX 0dBm
radio.openReadingPipe(1, 0xAABBCCDD11LL);
radio.startListening();
}
void loop(void) {
if(radio.available()){ // Если в буфере имеются принятые данные, то получаем номер трубы, по которой они пришли, по ссылке на переменную pipe
radio.read( &data, sizeof(data)); } // Читаем данные в массив data и указываем сколько байт читать
lcd.setCursor(0,0);
lcd.print("Temp ");
lcd.print(float(data[3])/100, 2); lcd.print(" "); lcd.print((char)223); lcd.print("C");
lcd.setCursor(0,1);
lcd.print("Battery ");
lcd.print(float(data[0])/1000,3); lcd.print(" v");
lcd.setCursor(0,2); // на некоторых 4 строчных дисплеях в 3 и 4 строке ноль сдвинут на 4 символа вправо, поэтому нулевой символ это -4
lcd.print("Packet N ");
lcd.print(data[1]);
delay (100);
}
код передатчика
// тема на форуме - http://forum.ixbt.com/topic.cgi?id=48:12460&r=lYjQSTixmu#2
// библиотека для датчика BME280 - https://github.com/sergeyastakhov/BME280 (чтобы не было конфликтов с другими библиотеками, желательно ее разместить в папке скетча)
// потребление всего модуля в power save - 2.0 мкА(из них NRF24 - 0.9 мкА), 1.1 мкА - atmega168
// обязательно параллельно батарейке доп емкость около 2200мкф 6.3 в - падение при передаче 10 мВ, ток утечки конденсатора после подформовки менее 100 нА, - http://forum.ixbt.com/topic.cgi?id=48:11468:3073#3073
// измерение напряжение батарейки с помощью измерения питания atmega
// еще можно питать атмегу напрямую от лития и измерять ее напряжение, а NRF и BME280 питается от стабилизатора 3.3 вольта с маленькой утечкой, например mcp1700-3302, ток утечки около 1.5 мкА
// I2C преобразователь уровней не нужен, главное чтобы подтягивающие резисторы I2C питались от 3.3 вольт
// проблема с вотчдогом - http://arduino.ru/forum/apparatnye-voprosy/ne-mogu-vylechit-vatchdog-na-goloi-atmege328r-s-vnutr-taktirovaniem-8-mgts
#include <avr/wdt.h>
#include <avr/sleep.h>
//#include <avr/power.h>
#include "RF24.h"
RF24 radio(9, 10); // CE_PIN, CSN_PIN
byte count = 0; // счетчик количества просыпаний
int32_t data[5]; // на данный момент используется int32_t чтобы иметь возможность получить большие числа счетчика передач, для тестирования продолжительности работы от батарейки,
// потом можно обычный int
// data 0 - данные АЦП или пересчитанное напряжение питания c тремя знаками(в милливольтах, на стороне приемника переменная float деленная на 1000
// data 1 - счетчик отправленных пакетов, сбрасывается при снятии питания
// data 2 - P атмсомферное в мм, можно умножить на 10 если нужны десятые, тогда на стороне приемника делить на 10 и float
// data 3 - Т температура умноженное на 100 на стороне приемника float делить на 100
// data 4 - H влажность умноженная на 100
#include "BME280.h" // при таком написании компилятор ищет библиотеку в первую очередь в папке скетча
using namespace BME280;
class CustomI2CProtocol : public I2CProtocol {
public:
virtual void init() {
I2CProtocol::init();
Wire.setClock(400000); // частота работы I2C, 400 кГц - максимум для BME280, уже чувствительна к качеству и длине линии
} // реальная частота I2C зависит от регистра и строки в wire.cpp - TWBR = ((F_CPU / frequency) - 16) / 2 и при частоте 8 МГц она ниже, около 250 кГц
// https://www.avrfreaks.net/forum/twi-clock-divider?name=PNphpBB2&file=viewtopic&t=79562
};
BME280Sensor bme280 = BME280Sensor(new CustomI2CProtocol()); //BME280Sensor bme280 = BME280Sensor(new I2CProtocol()) - I2C работает на частоте по умолчанию - 100 кГц
ISR (TIMER2_OVF_vect) { // из прерывания по таймеру 2 нельзя выходить слишком быстро, поэтому инкрементирование переменной расположим здесь
count++;
}
void setup() {
MCUSR = 0; // необходимо при использовании вотчдога для сброса контроллера при зависании на выпонении кода
wdt_disable(); // иначе при первом сбросе вотчдогом, будет установлено минимальное время вотчдога 0.15 с, и мк зависнет на этом цикле
PORTC |= (1 << PC1); // включение светодиода, для индикации запуска мк
delay(10);
DDRB = 0b11000001; // настраиваем неиспользуемые выводы(и порты) на выход
DDRC = 0b11111111;
DDRD = 0b11111111;
//clock_prescale_set(clock_div_2); // деление тактовой частоты для получения 4 МГц для работы atmega168 вплоть до 1.8 вольта
bme280.init(false);
bme280.setHumidityMode(Over_1); // установка режима измерения влажности, остальные при запуске измерения
//bme280.setMode(Forced, Over_1, Over_1, Over_1); // Forced - однократный режим измрения, все оверсемплинги(T, P, Hum) = 1
PRR = (1<<PRTIM1) | (1<<PRUSART0); // отключаем неиспользуемую периферию, (1<<PRTIM2) - работает в асинхронном режиме
// (1<<PRTIM0) - так не работает передача, видимо count использует этот таймер
// (1<<PRSPI), (1<<PRTWI)- не отключаем, SPI - для NRF24 и TWI(по другому I2C) - для BME280
radio.begin();
radio.setAutoAck(true); // подтверждение приема, true - включено, false - отключено
radio.setRetries (2, 1); // количество попыток передачи если включен режим автоподтверждения приема, первая цифра - задержка между передачами, 1 = 250 мкс, вторая - количество попыток передачи
radio.setChannel(100); // номер канала, можно перед выбором просканировать эфир чтобы выяснить менее зашумленные каналы(использовать скетч scanner из библ. RF24)
radio.setDataRate (RF24_2MBPS); // скорость обмена RF24_2MBPS, RF24_1MBPS, RF24_250KBPS - максимальная чувствительность на 250 кбитс
radio.setPALevel (RF24_PA_MAX); // уровень мощности RF24_PA_MIN -18dBm, RF24_PA_LOW -12dBm, RF24_PA_HIGH -6dBM, RF24_PA_MAX 0dBm
radio.openWritingPipe (0xAABBCCDD11LL); // уникальный номер передатчика, на приемнике должен быть такой же
ADCSRA = 0; // отключаем АЦП
ACSR |= (1<<ACD); // отключаем компаратор
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); // настраиваем АЦП на измерение собственного напряжения питания, опорное 1.1 вольт
DIDR0 = 1; // отключаем цифровой вход выводов АЦП, он тоже немного потребляет
ASSR |= (1<<AS2); // Асинхронный режим
TCCR2A = (0<<WGM21)|(0<<WGM20); //режим normal
TCCR2B = (1<<CS22)|(1<<CS21)|(1<<CS20); // clk/1024
TCNT2 = 0x00; // начальное значение таймера
// OCR2A = 0x00; OCR2B = 0x00;
TIMSK2 |= (1 << TOIE2); // разрешаем прерывание по переполнению Т2
SMCR = _BV(SM1) | _BV(SM0); // Power_Save, set_sleep_mode(SLEEP_MODE_PWR_SAVE), set_sleep_mode (SLEEP_MODE_PWR_DOWN) - выбор режима сна
radio.powerDown();
PORTC &= ~_BV(PC1); // выключение светодиода
}
void loop() {
if ( count >= 8 ) { // передавая один раз в 64 секунды = 8
radio_();
TCNT2 = 0x00;
}
while(ASSR&(1<<TCN2UB)){;} //wait to TCNT update!!!
TCNT2 = 0x00;
sleep_enable(); //SMCR |= _BV(SE);
sleep_cpu ();
}
void radio_() {
wdt_enable(WDTO_1S);
radio.powerUp();
data[1]++;
ADCSRA |= _BV(ADEN); // включаем АЦП
Measurement measurement = bme280.readMeasurement(); // опрос датчика и расчет параметров
data[2] = (measurement.getPressure32()*1000/133322);
data[3] = (measurement.getTemperature()*100);
data[4] = (measurement.getHumidity()*100);
bme280.setMode(Forced, Over_1, Over_1); // запуск однократного измерения и установка оверсемплингов T, P, без влажности
//bme280.setMode(Forced, Over_1, Over_1, Over_1); // запуск однократного измерения и установка оверсемплингов T, P, Hum
ADCSRA |= _BV(ADSC); // запуск конверсии
while (bit_is_set(ADCSRA,ADSC)); // измерение
uint8_t low = ADCL; // сначала читаем ADCL - это расблокирует чтение ADCH
uint8_t high = ADCH;
int volt = (high<<8) | low; // данные АЦП
data[0] = (1135494/volt); // 1135494 подобрано эксперементально и в общем случае равно 1100 милливольт опорное * 1024, но опорное не всегда 1100 мВ
ADCSRA = 0; // отключаем АЦП
// ******************************************** // код до этого места примерно 1700 мкс, убираем в файле RF24.cpp задержку delay(5) и добавляем перед отправкой данных
// задержку в 2.3 мс, чтобы в сумме задежка получилась 4 мс, это минимальная задержка от команды powerUp() до write()
// при которой стабильно работает большой модуль на RF24, smd модуль работает и при меньшей задержки, т.к. разные кварцы
clock_prescale_set(clock_div_256); // на время задержки снижаем частоту контроллера до минимально возможной, для снижения энергопотребления
delayMicroseconds(10); // на этой частоте для получения 2.3 миллисекунд нужно указать задержку в 256 раз меньше ~ 9 мкс
// в этом режиме потребляет только atmega, которая работает на частоте 1/256 от номанальной
clock_prescale_set(clock_div_1); // перед отправкой данных снова устанавливаем исходную частоту работы контроллера
radio.write(&data, sizeof(data));
radio.powerDown();
count = 0;
wdt_disable();
}