Еще донедавна мне не удавалось найти в Интернете любительскую метеостанцию с питанием от батареек. Я имею ввиду бытовую автономную метеостанцию с измерениями параметров в помещении, на улице и отображением информации на дисплее метеостанции. Любители не заморачиваются на этой проблеме, а питают свои автономные девайсы от солнечных батарей, аккумуляторов и т.п. Уточню — проблема касается только одного из узлов метеостанции — базы, а первые проекты малогабаритных беспроводных автономных выносных датчиков на Ардуино появились 10 лет назад. Вместе с тем, промышленные устройства такого плана — бытовые метеостанции, комнатные термостаты годами работают от пары батареек АА и этот факт является той целью, которой хотелось бы достичь.
Это было донедавна. Несколько дней назад меня поразил очередной проект @Berkseo, как поражают все его проекты: "Беспроводная мини погодная станция с e-paper экраном на батарейках". Тут все на уровне промышленного продукта. Удивляет единственное — в устройстве нет внешнего датчика.
Полтора года назад разместил статью Автономная метеостанция на контроллере ATMEGA328P и питанием от батареек с беспроводным выносным датчиком. Хотя прототип и выполнял свои функции, но имел серьезные недостатки — слишком малая частота обновления данных и большое энергопотребление.
Ниже — новая версия метеостанции с другими алгоритмами, элементной базой и кодом с низкоуровневыми вставками. Все это позволило выйти на время работы метеостанции от одного комплекта батареек даже несколько больше, чем гарантируют производители некоторых товаров такого плана.
Что сделано:
Датчики DHT22 и DS18B20, которые использовались в предыдущем проекте, заменены энергосберегающим модулем — это датчик температуры и влажности HTU21D. Период измерений, отправки/приема данных уменьшен с 15 мин до 53,5 сек. Сделан переход на устойчивую частоту работы контроллера (8 МГц) при напряжении питания ниже 3 В. Для уменьшения объемов занимаемой памяти в скетчах использованы некоторые функции С/С++. И главное, принципиально изменен алгоритм передачи пакетов с выносного датчика и алгоритм приема этих пакетов базой метеостанции. Теперь для обеспечения надежного приема пакетов с выносного датчика в нем формируется и отправляется с интервалом около 0,3 сек не один, а три пакета с данными о параметрах воздуха на улице и состоянии батареек. Только после отправки третьего пакета контроллер в. датчика вместе с периферией уходит в сон. База метеостанции уходит спать после приема одного из 6-ти пакетов с выносного датчика и просыпается за полсекунды до поступления очередной серии пакетов с выносного датчика.
Метеостанция состоит из двух автономных узлов с питанием от двух батареек AA: базы и выносного датчика. Назовем их для простоты анализатором (по-другому – база) и беспроводным в.датчиком (выносным датчиком).
Анализатор, построен на контроллере ATMEGA328P, измеряет температуру и влажность (датчик температуры и влажности HTU21D) в помещении, а также измеряет и анализирует величину напряжения питания узла, которое обеспечивают две батарейки АА 1,5 В. На контроллер также поступает сигнал с приемника LoRa, который по эфиру принимает информацию с выносного датчика. Вся инфа с контроллера выводится на ЖК-дисплей NOKIA 5110.
В в.датчике, тоже собранном на контроллере ATMEGA328P, измеряется температура и влажность воздуха на улице (модуль HTU21D), а также напряжение питания выносного узла, организованного на двух батарейках АА 1,5 В. Передатчик LoRa этого узла передает инфу о температуре, влажности и состоянии батарейки на анализатор. С в.датчика выполняется отправка 3 пакетов с интервалом около 0,3 сек, затем контроллер ATMEGA328P, передатчик LoRa и модуль HTU21D для экономного расходования заряда батареек переводятся в режим сна. Измерения и отправка данных с в.датчика выполняется с циклом несколько меньше 1-ой минуты.
Работа анализатора построена по следующему алгоритму:
Вначале, при включении обоих узлов метеостанции, контроллер анализатора подает команды на измерение температуры и влажности внутри помещения и выводит эти параметры на дисплей, затем устанавливает приемник LoRa в режим прослушивания эфира. После приема сигнала с в.датчика и успешной дешифрации принятых данных контролер подает команду на повторное измерение температуры, влажности и выводит инфу в полном объеме на экран. Затем анализатор уходит в сон, просыпаясь примерно за полсекунды до планируемого поступления сигнала с в.датчика. Приняв и дешифровав один из трех пакетов с в.датчика, повторно выполняет свои измерения, выводит информацию на экран и снова уходит спать. Если по каким-то причинам сигнал с в.датчика отсутствует около одной минуты (например, сели батарейки), что по времени соответствует отправке 6 пакетов с в.датчика, анализатор проводит измерения только в помещении, изредка сканируя эфир: а вдруг в.датчик появился в эфире?! Это сделано для того, чтобы постоянно работающий на прием модуль LoRa не посадил за короткое время батарейки анализатора.
Для сборки устройства понадобятся радиодетали:
Ориентировочная стоимость компонентов по ценам AliExpress примерно $25.
Для работы с контроллерами ATMEGA328P в качестве программатора я использую плату Arduino UNO. На Youtube есть хорошее видео по установке загрузчика и загрузки скетчей в контроллер ATMEGA328P с помощью платы Arduino UNO.
На этот раз мы не будем устанавливать новые фьюзы программой SinaProg, а воспользуемся, на мой взгляд, более универсальным способом — созданием новых конфигураций плат в платформе Arduino IDE.
В новые контроллеры надо установить загрузчик Arduino as ISP и надо учитывать то, что контроллеры ATMEGA328P поступают в продажу с заводской настройкой фьюз для мониторинга (контроля) напряжения питания не ниже 2,7 В. Мы же будем работать от батареек, напряжение на которых при разряде может быть ниже установленного заводского порога 2,7 В, и с кварцем 8 МГц. Установим загрузчик и изменим фьюзы под наши условия, используя в качестве программатора плату Arduino UNO, в такой последовательности:
В.датчик построен на контроллере ATMEGA328P. В нем осуществляется прием данных с HTU21D по протоколу I2C, измерение и анализ величины напряжения питания узла и управление передатчиком LoRa.
/*
Снова о автономной Arduino-метеостанции на батарейках, выносной датчик
https://habr.com/ru/post/544936/
*/
#include <avr/io.h>
#include <util/delay.h>
#include <SPI.h>
#include <LoRa.h>
#include <LowPower.h>
#include <Wire.h>
#include <avr/power.h>
#include "HTU21D.h"
#define VccHTU 8 //питание и подтяжка HTU21D (pin 14 AtMega328P, D8)
HTU21D myHTU21D;
float Tout; // температура
int Hout; // влажность
unsigned int sleepCounter, sleepCounter0; // счетчик, задающий время сна
int pct; //счетчик числа пакетов перед уходом в сон
String messageOut; // LoRa-сообщение
float BatOut; // напряжение батареек
const int batteryPin = A0; // pin 23 (Atmega328P), к которому подключена батарея для измерения напряжения
const float typVbg = 1.132; //калибровочная константа, 1.0 - 1.2
int counter = 0;
// измерение опорного напряжения
float readVcc() {
byte i;
float result = 0.0;
float tmp = 0.0;
for (i = 0; i < 1; i++) {
// 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
// works on an Arduino 168 or 328
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif
_delay_ms(3); // 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
tmp = (high << 8) | low;
tmp = (typVbg * 1023.0) / tmp;
result = result + tmp;
_delay_ms(5);
}
return result;
}
void Measurement () {
// измерение температуры и влажности
Hout = myHTU21D.readHumidity();
Hout = 62; //delete!
float Tout_p = myHTU21D.readTemperature();
Tout = 0.1 * int(Tout_p * 10 + 0.5); //округление до десятых
// измерение напряжения батареек
BatOut = 0.1 * int(readVcc() * 10 + 0.5);
if (BatOut < 2.2) {
BatOut = 0.0;
} else {
BatOut = 2.2;
}
}
void SendMessage () {
// отправка данных (температура, влажность, состояние батареек)
if (BatOut > 2.1) {
messageOut = String(Tout) + "#" + String(Hout) + "$" + String("BGood");
}
else {
messageOut = String(Tout) + "#" + String(Hout) + "$" + String("BLow");
}
LoRa.beginPacket();
LoRa.print(messageOut);
LoRa.endPacket();
}
void setup() {
Serial.begin(9600);
Serial.println("Power ON");
analogReference(DEFAULT);
pinMode(VccHTU, OUTPUT);
digitalWrite(VccHTU, 1);
_delay_ms(200);
myHTU21D.begin();
int counter = 0;
while (!LoRa.begin(433E6) && counter < 10) {
Serial.println("Не удалось найти LoRa-передатчик!");
counter++;
_delay_ms(500);
}
LoRa.setTxPower(4); //мощность передатчика, 2...20 дБ
LoRa.setSyncWord(0xF3);
}
void loop() {
digitalWrite(VccHTU, 1);
if (pct < 3)
{ // измерения, отправка пакетов
Serial.println(messageOut);
Measurement ();
SendMessage ();
} else {// измерения, отправка пакета и длительный сон
Serial.println(messageOut);
Serial.println("sleep ...");
Measurement ();
SendMessage ();
for (sleepCounter = 6; sleepCounter > 0; sleepCounter--)
{
digitalWrite(VccHTU, 0);
digitalWrite(VccHTU, 1);
LoRa.sleep ();
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
}
pct = 0;
}
pct++;
if (pct >= 3) pct = 3; //защита от переполнения счетчика
}
int main() {
init();
setup();
for (;;) {
loop();
}
}
Электрическая схема в.датчика:
Питание и подтяжка выводов модуля HTU21D осуществляется с пина 14 контроллера ATMEGA328P. Это сделано для того, чтобы программно обнулить питание HTU21D и перевести этот датчик в режим низкого энергопотребления во время сна.
В в.датчике формируется и отправляется с интервалом около 0,3 сек три пакета с данными о температуре и влажности на улице и состоянии батареек. Если напряжение на батарейках выше установленного порога (2,2 В), то в коде пакета присутствует BGood, а ниже — BLow. После отправки третьего пакета контроллер в.датчика вместе с периферией уходят в сон. Цикл отправки серий пакетов — 53,5 сек.
Мозг анализатора – контроллер ATMEGA328P. Он принимает сигналы с датчика HTU21D по протоколу I2С и по SPI взаимодействует с приемником LoRa и дисплеем NOKIA 5110.
/*
Снова о автономной Arduino-метеостанции на батарейках, анализатор
https://habr.com/ru/post/544936/
*/
#include <avr/io.h>
#include <util/delay.h>
#include <SPI.h>
#include <LoRa.h>
#include <LowPower.h>
#include "HTU21D.h"
#include <LCD5110_Graph.h>
#define VccHTU 8 //питание и подтяжка HTU21D(pin 14 AtMega328P, D8)
HTU21D myHTU21D;
float Tin; // температура в помещении
int Hin; // влажность в помещении
LCD5110 myNokia(3, 4, 5, 6, 7);
extern uint8_t SmallFont[];
extern uint8_t MediumNumbers[];
float BatIn = 0; // напряжение батареи
const int batteryPin = A0; // pin 23(Atmega328P), к которому подключена батарея для измерения напряжения
const float typVbg = 1.132; //калибровочная константа, 1.0 - 1.2
unsigned int sleepCounter; //счетчик, задающий время сна
int r; //счетчик циклов прослушивания эфира
int mlc; //счетчик циклов работы без в.датчика
String LoRaData, Tout_str, Hout_str, BatIn_str, BatOut_str;
// измерение напряжения батареек
float readVcc() {
byte i;
float result = 0.0;
float tmp = 0.0;
for (i = 0; i < 1; i++) {
// 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
// works on an Arduino 168 or 328
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1);
#endif
_delay_ms(3); // 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
tmp = (high << 8) | low;
tmp = (typVbg * 1023.0) / tmp;
result = result + tmp;
_delay_ms(5);
}
return result;
}
void Measurement() {
float Tin0;
// измерение напряжения батареи:
BatIn = readVcc();
// измерение температуры и влажности в помещении
Hin = myHTU21D.readHumidity();
// Hin = 58; // delete!
float Tin_p = myHTU21D.readTemperature();
Tin = 0.1 * int(Tin_p * 10 + 0.5); //округление до десятых
// Tin = 21.4; // delete!
}
void draw() {
myNokia.enableSleep();
myNokia.clrScr();
//Tin
char chr_Tin [5];
String Tin_str = String(Tin);
myNokia.setFont(SmallFont);
myNokia.print(" C", LEFT, 0);
myNokia.print("In", LEFT, 8);
myNokia.setFont(MediumNumbers);
Tin_str.toCharArray(chr_Tin, 5); //количество знаков+1
myNokia.print(String(chr_Tin), CENTER, 0);
//Tout
char chr_Tout [5];
myNokia.setFont(SmallFont);
myNokia.print(" C", LEFT, 16);
myNokia.print("Out", LEFT, 24);
myNokia.setFont(MediumNumbers);
Tout_str.toCharArray(chr_Tout, 5);
myNokia.print(String(chr_Tout), CENTER, 16);
// Hin, Hout
char chr_Hout [5];
Hout_str.toCharArray(chr_Hout, 4);
myNokia.setFont(MediumNumbers);
myNokia.print(String(Hout_str), RIGHT, 32);
myNokia.setFont(SmallFont);
myNokia.print(" In Out", LEFT, 40);
myNokia.print(" %", LEFT, 32);
myNokia.setFont(MediumNumbers);
myNokia.print(String(Hin), LEFT, 32);
myNokia.setFont(SmallFont);
// Battery Level
if (BatIn < 2.2) {
myNokia.setFont(SmallFont);
myNokia.print("Bat", LEFT, 0);
}
if (BatOut_str == "BLow") {
myNokia.setFont(SmallFont);
myNokia.print("Bat", LEFT, 16);
}
myNokia.disableSleep();
_delay_ms(5);
}
void drawStart() {
myNokia.enableSleep();
myNokia.clrScr();
//Tin
char chr_Tin [5];
String Tin_str = String(Tin);
myNokia.setFont(SmallFont);
myNokia.print(" C", LEFT, 0);
myNokia.print("In", LEFT, 8);
myNokia.setFont(MediumNumbers);
Tin_str.toCharArray(chr_Tin, 5); //количество знаков+1
myNokia.print(String(chr_Tin), CENTER, 0);
// Battery Level
if (BatIn < 2.2)
{
myNokia.setFont(SmallFont);
myNokia.print("Bat!", RIGHT, 28);
}
//Hin
myNokia.setFont(SmallFont);
myNokia.print(" %", LEFT, 18);
myNokia.print("In", LEFT, 28);
myNokia.setFont(MediumNumbers);
myNokia.print(String(Hin), CENTER, 18);
//No signal!
myNokia.setFont(SmallFont);
myNokia.print("Out - - -", CENTER, 40);
myNokia.update();
myNokia.disableSleep();
_delay_ms(5);
}
void setup() {
Serial.begin(9600);
pinMode(VccHTU, OUTPUT);
digitalWrite(VccHTU, 1);
Serial.println("Power ON!");
analogReference(DEFAULT);
// инициализация дисплея
myNokia.InitLCD();
myNokia.setFont(SmallFont);
myNokia.clrScr();
myNokia.print(">>>>>", CENTER, 20);
myNokia.update();
_delay_ms(1000);
myNokia.setFont(SmallFont);
myNokia.clrScr();
myNokia.print("))-->", CENTER, 20);
myNokia.update();
if (!LoRa.begin(433E6)) {
Serial.println("Ошибка загрузки LoRa-приемника!");
while (1);
myNokia.setFont(SmallFont);
myNokia.clrScr();
myNokia.print(" -> ->", CENTER, 20);
myNokia.update();
}
// Диапазон для синхрослова – между "0-0xFF".
LoRa.setSyncWord(0xF3);
Serial.println("Прослушивание эфира. Ожидание пакета с в.датчика ...");
myHTU21D.begin();
Measurement();
drawStart();
digitalWrite(VccHTU, 0);
_delay_ms(1000);
myNokia.clrScr();
myNokia.print("Waiting", CENTER, 10);
myNokia.print("Message from", CENTER, 22);
myNokia.print("OUTSIDE", CENTER, 34);
myNokia.update();
}
void loop() {
r++;
digitalWrite(VccHTU, 1);
if (r < 600) // 8 MHz;
{
mlc = 0;
// Прослушивание эфира, прием, дешифрация, если сигнал с в.датчика принят,
// то измерения в помещении, вывод инфы на экран и - в спячку.
{
int packetSize = LoRa.parsePacket();
if (packetSize) {
while (LoRa.available()) {
LoRaData = LoRa.readString();
}
int pos1 = LoRaData.indexOf('#');
int pos2 = LoRaData.indexOf('$');
Tout_str = LoRaData.substring(0, pos1);
Hout_str = LoRaData.substring(pos1 + 1, pos2);
BatOut_str = LoRaData.substring(pos2 + 1, LoRaData.length());
if ((LoRaData).substring(pos1, pos1 + 1) == "#") {
Serial.println("Принято, декодировано! r = " + String(r));
r = 0;
Measurement();
draw();
digitalWrite(VccHTU, 0);
// sleepCounter = 49; 16 MHz
// sleepCounter = 48; 8 MHz
for (sleepCounter = 48; sleepCounter > 0; sleepCounter--)
{
digitalWrite(VccHTU, 1);
LoRa.sleep ();
LowPower.powerDown(SLEEP_1S, ADC_OFF, BOD_OFF);
}
}
}
}
} else {
r = 600;
if (mlc < 250) //4 часа, время работы без датчика
{
Serial.println("Работа без в.датчика.");
LoRa.sleep ();
Measurement();
drawStart();
digitalWrite(VccHTU, 0);
for (sleepCounter = 6; sleepCounter > 0; sleepCounter--)
{
digitalWrite(VccHTU, 1);
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
}
mlc++;
} else {
r = 0;
mlc = 0;
}
}
_delay_ms(110);
}
int main() {
init();
setup();
for (;;) {
loop();
}
}
Работа анализатора начинается в setup'e с инициализации модулей, измерения параметров воздуха, анализа напряжения на батарейках и вывода этой инфы на дисплей. Далее уже в loop'e прослушивается эфир приемником LoRa. После приема и дешифрации сигнала с в.датчика повторно проводятся измерения, анализа напряжения на батарейках и вывод измеренной и принятой инфы на дисплей. Выполнив эту работу все элементы схемы уходят поспать примерно на полсекунды меньше, чем период отправки пакетов с в.датчика. В следующем цикле контроллер просыпается и включает приемник приблизительно за 0,5 сек до ожидаемого прихода сигнала с в.датчика. Таким образом, контроллер и периферия анализатора работают около полсекунды с периодом (циклом) меньше минуты (53,5 сек). Если радиосигнал с в.датчика не поступает на приемник анализатора на протяжении приблизительно одной минуты (время, достаточное для приема одного из 6 пакетов), то анализатор переходит в режим работы без в.датчика на 4 часа, измеряя параметры воздуха и оценивая состояние батареек только в помещении с индикацией на дисплее этих данных. Период обновления данных в режиме работы без в. датчика — 56,7 сек. В конце четырехчасового цикла работы анализатора без в.датчика он прослушивает эфир: а вдруг в.датчик снова в эфире?
Для перевода модуля HTU21D в режим низкого энергопотребления во время сна его питание также, как и в в.датчике, организовано с контроллера ATMEGA328P (пин 14).
В целом, на дисплее анализатора видна такая картинка:
Дисплей из-за низкого разрешения и малого размера экрана плотно забит символами. Эта картинка смотрелась бы намного лучше на современном дисплее с электронными чернилами. В будущем в своих проектах буду использовать e-paper дисплей.
Для расчета срока работы батареек понадобится время и потребляемый ток во время выполнения работы (операционное время) и сна. Операционное время и рабочий ток измерялись с использованием тестовых скетчей, идея которых взята отсюда. Архив с тестовыми скетчами и данными хронометража лежит здесь.
Рабочий ток измерялся с использованием тех же тестовых скетчей. Для исключения разрывов цепи питания или значительного увеличения величины выходного сопротивления батареек можно использовать шунт 3,9...5,6 Ом и параллельно подключенный к нему цифровой мультиметр с механическим переключением в режиме вольтметра на диапазоне 2000 мкВ. Это критично при измерении потребления тока сна анализатора, поскольку разрыв питания или значительное ограничение тока приводят к циклическому ресету анализатора. Да и выносной датчик может переходить в постоянный рестарт. По мере возможности необходимо проверять ток потребления разными способами на разных диапазонах шкал прибора и с батарейками, которые планируется использовать, притом, обязательно без вывода результатов на монитор порта Ардуино. Невыполнение этих правил сказались на результатах измерений тока в предыдущем моем посте на тему метеостанции — в одних случаях они занижены, в других — завышены.
Результаты измерений сведены в таблицу:
в.датчик | анализатор | |
Операционное время функции измерений параметров воздуха, состояния батареек | 0,25 сек | 0,39 сек |
Операционный ток функции измерений параметров воздуха, состояния батареек | 3,4 мА | 3,5 мА |
Операционное время функции передачи/приема сигнала | 42 мсек | 83 мсек |
Операционный ток функции передачи/приема сигнала | 30,0 мА (4 дБ) |
11,5 мА |
Ток сна | 10 мкА | 190 мкА |
Что бросается в глаза, глядя на эту таблицу. Операционный ток передачи сигнала — 30,0 мА при мощности передатчика LoRa 4 дБ. Для сравнения, ток передачи для модуля nRF24L01 13,5 мА. Вывод очевиден: надо переходить на nRF24L01.
Средний ток потребления по данным таблицы в. датчика 0,13 мА. Емкости батареек типа АА GP Litium для выносного датчика должно хватить на 2,5 года.
Средний ток потребления анализатора 0,27 мА. Ресурс батареек АА GP Litium в анализаторе — 1,2 года. Для беспроводного комнатного термостата Computherm Q7RF, например, срок действия батареек: около 1 года.
Еще на тему энергопотребления — долго копался в этой теме и хочется выговориться.
Составил код на С в Atmel Studio и эмулировал его в Proteus'е для для барометра-термометра.
На картинке ниже показаны результаты сравнения кода для одного и того же устройства на языке С и в среде разработки Arduino IDE.
Объем флеш-памяти, занимаемой в коде в Ардуино — 12968 байт, на С — 5954 байта и оценочно на Ассемблере при таком относительно большом проекте, как у меня, объем будет такой же, как и на С. Притом, в маленьких проектах (например, помигать светодиодом) выигрыш на С будет около 6 раз по сравнению с объемом флеш-памяти в Ардуино. Ассемблер уменьшит этот размер еще процентов на 10 или около этого.
Из этих чисел сделал простой вывод, в котором убедился на собственном опыте:
Благодаря использованию компилируемых в Arduino IDE библиотек и функций на С/С++ в некоторых скетчах этого поста удалось уйти от предупреждения: Недостаточно памяти, программа может работать нестабильно.
И, наконец, искренне благодарю AlexanderS, который донес до меня идею виртуальной шкалы времени или синхронизации, а также других участников обсуждения статьи «Автономная метеостанция на контроллере ATMEGA328P и питанием от батареек с беспроводным выносным датчиком» за предложения, конструктивную критику и замечания.
Спасибо, кто дочитал. Всем — отличного иммунитета во времена коронавируса и не только.
Узел беспроводного датчика с низким энергопотреблением
Беспроводная мини погодная станция с e-paper экраном на батарейках
Превращаем Arduino в полноценный AVRISP программатор
Узнайте о битах конфигурации ATmega328P и о том, как использовать их с внешним кварцевым резонатором
Коль уж с Arduino уходим на C/asm, возникает резонный вопрос: может уже "гулять на все" и с самой atmega уйти на что-нибудь специально созданное для низкого потребления? STM8L или STM32L например.
Спасибо, но Вы переоцениваете мои возможности.
Для меня программисты, работающие на низкоуровневых языках типа Ассемблера — небожители и, как оказалось, на Ардуино тоже можно решать не тривиальные задачи.
А причем тут асм. STM32 также програмируются на Си как и большинство мк в мире. Более того ARM Asm куда более сложен чем AVR, и имхо лучше без сильной необходимости в него не лезть.
Также в сети полно страшилок про то, что stm32 мегасложные.
Но это уже пару лет как не так, благодаря кодогенератору CubeMX все настройки можно за 10 минут выставить и контроллер заведётся. А потом к этому всему обращаться через HAL функции (почти как в ардуине).
Ну и на крайняк stm32 blue pill eсть в ардуине.
P. S. HAL при всей моще имеет тот же минус что и ардуина (хоть и гибче) — много универсального кода == много больше места. С другой стороны при 100-200К памяти нет так и страшно.
P. P. S. Тема использования Cuba и HAL холиварная, но если вы с ардуины на Си съехали то разберётесь.
Atmega вполне себе низкопотребляющий, если правильно пользоваться. Если хочется выжать максимум то atmel picopower. Но в данном случае бессмысленно экономить пикоамперы, контроллер тут далеко не на первом месте в потреблении энергии.
Сударь, на счёт потребления атмеги и СТМ вы не правы, СТМ потребляет куда больше, но правда и вычислительные возможности лучше. Все зависит от применения и прямоты рук программиста и схемотехника)
Экран E-ink 2.13 + Контроллер NRF52 < 2мкА потребления в режиме сна
Либо взять E-ink + Zigbee как на Xiaomi термометрах
С Zigbee придется же ещё полноценный шлюз организовывать? Это всё проект сильно удорожает.
Ну да. Как и с Mysensors
Готовый USB брелок или модуль к малинке стоит порядка 2-х тысяч
Можно взять SOC на алишке за 500р и спаять самому
Зато сяоминые, акваровские и др. устройства работаю готовые
С брелком-то ещё хуже всё будет — он же просто функцию координатора играет. Это надо будет разворачивать какую-то систему автоматизации и его туда интегрировать. У автора, кажется, задачи сильно проще)
Дисплей 2.13 будет маловат для комнатного термостата и метеостанции в одном. В списке желаний на Али уже несколько месяцев e-paper дисплей 3,7 дюйма.
NRF52832 уже в пути.
Снизить энергопотребление в режиме сна можно, если питание внешних модулей коммутировать p-канальным полевиком или даже просто запитать их от одного или нескольких пинов GPIO микроконтроллера. Максимальный ток 20мА на пин.
VelocidadAbsurda
В ATMega328PA нет ничего плохого. Это уже pico-power контроллер. У неё достаточно возможностей отключать периферию и уходить в глубокий сон с потреблением около 1мкА.
В данном случае, все равно большую часть потребления уходит на прием и передачу данных. Если датчик находится недалеко, метров 30 до базы, то я бы поставил лучше NRF24L01. У него низкое потребление и высокая скорость передачи. А это значит, что в активном состоянии его нужно держать очень короткое время.
Думаю, что все дело тут в функции println, которая притягивает библиотечную функцию printf. А в ней, в зависимости от настроек, может быть либо только самая базовая функциональность, либо дополнительные параметры форматирования, вещественные числа и т.п.
В любом случае, большой объем занятой флеш памяти не обязательно означает, что она вся будет исполняться. Чаще всего это просто мертвый груз.
Также переписывание с С на ассемблер не имеет почти никакого смысла. Зато имеет смысл порой избавиться от стандартной функции printf() и написать свою, если вы выводите, например, только целые числа.
Питание периферии с коммутацией транзистором или от пинов контроллера у меня практически во всех проектах.
В nRF24L01+ требуется повторная инициализация контроллера радиомодуля. Инициализация несовместима с командами библиотеки LowPower.h, которые задают у меня время сна.
Короче, чтобы восстановить настройки радиомодуля после сна надо сделать Reset контроллера, который ним управляет. У меня — это atmega328p. При ресетах с интервалом минута ресурса флеш-памяти хватит на несколько суток.
Возможно я заблуждаюсь, еще поиграюсь с nRF24L01+ уж очень низкое у него энергопотребление.
Перед отправкой данных с десятыми-сотыми умножаю на 100, а дальше — дело техники.
При чем ресурс флеш-памяти к ресету? Ее ресурс "расходуется" только при перепрошивке. Или у вас бутлоадер при каждом сбросе перепрошивает контроллер?
Для максимальной экономии лучше отключать потребителей наподобие этого примера:

habr.com/ru/post/386735
А почему нет кода на ассемблер, откуда эти "оценочные" оценки? Очень интересно посмотреть, как Вы уместили в 200 байт шрифты для экранчика и преобразование данных с датчиков.
Работал с кодом термометра-гидрометра на ATtiny13. Получил объем занимаемой флеш-памяти 40 байт. Навскидку увеличил это число в 5 раз. Подредактирую эту цифру, если будут аргументы.
Вот код:
// *********************************************
// *** Simple digital thermometer/hygrometer ***
// *********************************************
// *** (c) SD, 14.03.2016 ***
// *********************************************
// Based on ATtiny13, AM2303 and MAX7219
// **************
// *** Clocks ***
// **************
// MCU clock frequency is 9.6MHz (internal oscillator)
// Timer frequency is 75KHz = 9.6MHz/128
// (13.3 us between interrupts)
#define SKIPNEXT1W (PC + 2)
#define DS(var) Y + var - _dataStart
// ************
// *** Pins ***
// ************
// MAX7219 output pins
.equ MAX_DIN = 0
.equ MAX_CS = 1
.equ MAX_CLK = 4
// AM2302 input pin
.equ AM2302_PIN = 3
// MAX7219 registers
.equ MAX_DECODE = 0x09
.equ MAX_INTENSITY = 0x0A
.equ MAX_SCANLIMIT = 0x0B
.equ MAX_SHUTDOWN = 0x0C
.equ MAX_DISPTEST = 0x0F
// Temperature measurement state register
// Bits 0 - 2 define the byte number being received
// Bit 3 is set when there are valid data received
// Bits 4 - 7 define the current receiver state
.def R_TS = R0
// Temperature measurement tick
.def R_TT = R1
// Temperature data register
.def R_TD = R2
// Temperature measurement states
.equ TMS_NONE = 0x00 // TMS_NONE - do nothing an wait until
// somebody changes the state
.equ TMS_START = 0x10 // Start of the measurement cycle
.equ TMS_ST_LOW = 0x20 // Initial low signal is being sent
// (1 ms = 75 timer ticks)
.equ TMS_WRSP_LOW = 0x30 // Initial low signal has been sent,
// waiting for the response low signal
.equ TMS_WRSP_HIGH = 0x40 // Response low signal has been received,
// waiting for the response high signal
.equ TMS_W1ST_BIT_LOW = 0x50 // Waiting for the first bit low signal
.equ TMS_WBIT_HIGH = 0x60 // Waiting for the bit high signal
.equ TMS_WBIT_LOW = 0x70 // Waiting for the bit low signal
.equ TMS_WHIGH = 0x80 // Waiting for the final high signal
// Timer 100Hz tick counter
// (counts upwards from 0 to 255)
.def R_TICK100 = R3
// Timer 16bit 75KHz tick counter
// (counts downwords from 749 to 0)
.def R_TICKL = R4
.def R_TICKH = R5
// ************
// *** Data ***
// ************
.dseg
_dataStart: // Data start label
tempData: .byte 5 // Data, received from the AM2302 sensor
displayData: .byte 4 // Decimal printing result
.equ DATA_BUF_SIZE = 8 // AM2302 data buffer size in samples
// (each sample is 4 bytes)
dataBuffer: .byte DATA_BUF_SIZE*4
.cseg
.org 0
// *** Interrupts ***
// Reset Handler
rjmp start
// IRQ0 Handler
reti
// PCINT0 Handler
reti
// Timer0 Overflow Handler
rjmp timerOvfl
// EEPROM Ready Handler
reti
// Analog Comparator Handler
reti
// Timer0 CompareA Handler
rjmp timerCompA
// Timer0 CompareB Handler
reti
// Watchdog Interrupt Handler
reti
// ADC Conversion Handler
reti
// Table to convert decimal digit into 7-segment code
hexTable:
.db 0b01111110, 0b00110000, 0b01101101, 0b01111001
.db 0b00110011, 0b01011011, 0b01011111, 0b01110010
.db 0b01111111, 0b01111011
start:
cli
ldi R16, RAMEND
out (SPL), R16
// Init watchdog (4s interval)
wdr
ldi R16, (1 << WDCE) | (1 << WDE)
out (WDTCR), R16
ldi R16, (1 << WDE) | (1 << WDP3)
out (WDTCR), R16
// Init registers
ldi YL, low (_dataStart)
ldi YH, high (_dataStart)
clr R_TS
clr R_TT
clr R_TICKL
clr R_TICKH
clr R_TICK100
// Init ports
out (PORTB), R_TS
ldi R16, (1 << MAX_DIN) | (1 << MAX_CS) | (1 << MAX_CLK)
out (DDRB), R16
// Init LED driver
// Set all digits to "-"
ldi XL, 0b00000001
ldi XH, 1
init1:
rcall maxWriteWord
cpi XH, 9
brne init1
// Set control registers
ldi XL, 0 // Decode
rcall maxWriteWord
ldi XL, 4 // Intensity
rcall maxWriteWord
ldi XL, 7 // Scan limit
rcall maxWriteWord
ldi XL, 1 // Shutdown
rcall maxWriteWord
ldi XH, 0x0F
ldi XL, 0 // Display test
rcall maxWriteWord
// Init timer for 1 interrupt each 128 CPU cycles
ldi R16, 127
out (OCR0A), R16
ldi R16, 0b00000110
out (TIMSK0), R16
ldi R16, 0b00000001
out (TCCR0B), R16
// First part of the initialization is done.
// Enable interrupts
sei
// Wait 2 sec (while AM2302 initialize itself)
// with little animation
ldi XH, 1
ldi XL, 0
init2:
ldi R16, 25
rcall wait100Hz
rcall maxWriteWord
cpi XH, 9
brne init2
// R6 will contain the number of
// measurement values received
clr R6
// R7 will contain the number of
// continious errors
clr R7
loop:
// Reset watchdog timer
wdr
// Initiate measurement
ldi R16, TMS_START
mov R_TS, R16
loop1:
// Wait for the TMS_NONE state
// which indicates that the measurement
// is done
sleep
mov R16, R_TS
andi R16, 0xF0
brne loop1
// Do we have the valid data?
sbrs R_TS, 3
loop_error1:
rjmp loop_error
// Check control sum of the received data
ldd R16, DS (tempData)
ldd ZL, DS (tempData + 1)
add R16, ZL
ldd ZL, DS (tempData + 2)
add R16, ZL
ldd ZL, DS (tempData + 3)
add R16, ZL
ldd ZL, DS (tempData + 4)
cp R16, ZL
brne loop_error1
// We have valid new measurement data,
// reset error count
clr R7
// Move up data in the buffer
// and count the sum at the same time.
// R12:R13 will contain the humidity value and
// R14:R15 the temperature value
clr R12
clr R13
clr R14
clr R15
ldi ZL, low (dataBuffer + (DATA_BUF_SIZE - 2)*4)
ldi ZH, 0
buf1:
ldd R16, Z + 0
ldd R17, Z + 1
std Z + 4, R16
std Z + 5, R17
add R12, R16
adc R13, R17
ldd R16, Z + 2
ldd R17, Z + 3
std Z + 6, R16
std Z + 7, R17
add R14, R16
adc R15, R17
subi ZL, 4
cpi ZL, low (dataBuffer - 4)
brne buf1
// Add new humidity value to the buffer
// and to the sum
ldd R16, DS (tempData + 1)
ldd R17, DS (tempData)
std DS (dataBuffer + 0), R16
std DS (dataBuffer + 1), R17
add R12, R16
adc R13, R17
// Add new temperature value to the buffer
// and to the sum
ldd R16, DS (tempData + 3)
ldd R17, DS (tempData + 2)
// Check for a negative value
and R17, R17
brpl buf2
// Convert negative temperature to the 2's
// complement form
clr ZL
andi R17, 0x7F
neg R16
sbc ZL, R17
mov R17, ZL
buf2:
std DS (dataBuffer + 2), R16
std DS (dataBuffer + 3), R17
add R14, R16
adc R15, R17
// Divide the humidity and temperature
// sum values by 8 (by shifting them right
// three times)
ldi R16, 3
buf3:
asr R15
ror R14
asr R13
ror R12
dec R16
brne buf3
// Do we have 8 full measurements?
mov R16, R6
cpi R16, 7
// If so, use the average values from
// the buffer
breq buf4
// Otherwise use the latest measurement
ldd R12, DS (dataBuffer + 0)
ldd R13, DS (dataBuffer + 1)
ldd R14, DS (dataBuffer + 2)
ldd R15, DS (dataBuffer + 3)
inc R6
buf4:
// Print out values
// *** Humidity ***
movw X, R12
rcall printDecX
ldi XH, 1
ldd XL, DS (displayData + 3)
rcall maxWriteWord
ldd XL, DS (displayData + 2)
ori XL, 0x80
rcall maxWriteWord
ldd XL, DS (displayData + 1)
rcall maxWriteWord
ldd XL, DS (displayData)
rcall maxWriteWord
// *** Temperature ***
movw X, R14
// Check for a negative value
and XH, XH
brpl buf5
// Calculate the absolute value
clr ZL
neg XL
sbc ZL, XH
mov XH, ZL
buf5:
rcall printDecX
ldi XH, 5
ldd XL, DS (displayData + 3)
rcall maxWriteWord
ldd XL, DS (displayData + 2)
ori XL, 0x80
rcall maxWriteWord
ldd XL, DS (displayData + 1)
rcall maxWriteWord
// If temperature is negative
// write the minus sign to the first digit
// (temperatures of -100.0 and below
// are not supported anyway)
ldd XL, DS (displayData)
and R15, R15
brpl SKIPNEXT1W
ldi XL, 1
rcall maxWriteWord
loop2:
// Wait for 1 sec
ldi R16, 100
rcall wait100Hz
// And repeat
rjmp loop
loop_error:
// An error had occured.
// Increment error count
inc R7
// Do we have 3 or more errors in a row?
mov R16, R7
cpi R16, 3
// No? Just do nothing
brne loop2
// Prevent error count from growing
dec R7
// Display error
ldi ZL, low (errText*2)
ldi ZH, high (errText*2)
rcall maxWrite8Bytes
rjmp loop2
errText:
// "Sn Error"
.db 0b00000101, 0b00011101, 0b00000101, 0b00000101
.db 0b01001111, 0b00000000, 0b00010101, 0b01011011
// **********
// Waits given number (R16) of 100Hz ticks
// Uses: Z
wait100Hz:
// Enable sleep
ldi ZL, 0b00100000
out (MCUCR), ZL
mov ZL, R_TICK100
w100:
sleep
mov ZH, R_TICK100
sub ZH, ZL
cp ZH, R16
brcs w100
ret
// Timer interrupt
timerOvfl:
timerCompA:
push R16
in R16, (SREG)
push R16
push ZL
push ZH
// Receive AM2303 data
rcall am2302proc
// Decrement current 75KHz tick
ldi R16, 1
sub R_TICKL, R16
brcc timerRet
sub R_TICKH, R16
brcc timerRet
// Initialize 75KHz tick value
ldi ZL, low (750 - 1)
ldi ZH, high (750 - 1)
movw R_TICKL, Z
// Increment current 100Hz tick
inc R_TICK100
timerRet:
pop ZH
pop ZL
pop R16
out (SREG), R16
pop R16
reti
// **************
// *** AM2302 ***
// **************
amStart:
// Send the start low signal.
// Switch corresponding PORTB pin to output
// (there is already 0 in the PORTB register)
sbi (DDRB), AM2302_PIN
ldi R16, TMS_ST_LOW
rjmp amSetState
amStartLow:
// Initial start low signal is being sent.
// Wait for 75 ticks
cpi R16, 75
brne amNone
// Switch PORTB pin back to input
cbi (DDRB), AM2302_PIN
ldi R16, TMS_WRSP_LOW
// Do not check AM2303 input pin at this tick
// since it's possible that it has not recovered
// from the low state yet.
rjmp amSetState
amWRespLow:
// Waiting for the response low signal
sbrc ZH, AM2302_PIN
ret
ldi R16, TMS_WRSP_HIGH
rjmp amSetState
amWRespHigh:
// Waiting for the response high signal
sbrs ZH, AM2302_PIN
ret
ldi R16, TMS_W1ST_BIT_LOW
rjmp amSetState
amW1StBitLow:
// Waiting for the first bit low signal
sbrc ZH, AM2302_PIN
ret
// Get ready to receive the first bit
ldi R16, 1
mov R_TD, R16
// Set new state and reset the byte counter
ldi ZL, TMS_WBIT_HIGH
rjmp amSetState2
amBitHigh:
sbrs ZH, AM2302_PIN
ret
// If the bit low signal was there too long
// (longer than 5 ticks (5*13.3 = 66.5us)
// something went wrong)
cpi R16, 6
brcc amResetState
ldi R16, TMS_WBIT_LOW
rjmp amSetState
am2302proc:
// First, check for the TMS_NONE state.
// In this case just do nothing to
// not waste MCU cycles.
mov ZL, R_TS
andi ZL, 0xF0
cpi ZL, TMS_NONE
breq amNone
// Increment receiver tick
inc R_TT
// If we are waiting for too long,
// something went wrong, reset the state
breq amResetState
// Save the current tick into a more
// convenient register
mov R16, R_TT
// Get input signal
in ZH, (PINB)
// Branch depending on the current state.
// Check for TMS_WBIT_LOW first since it
// has the longest service routine
cpi ZL, TMS_WBIT_LOW
breq amBitLow
cpi ZL, TMS_START
breq amStart
cpi ZL, TMS_ST_LOW
breq amStartLow
cpi ZL, TMS_WRSP_LOW
breq amWRespLow
cpi ZL, TMS_WRSP_HIGH
breq amWRespHigh
cpi ZL, TMS_W1ST_BIT_LOW
breq amW1StBitLow
cpi ZL, TMS_WBIT_HIGH
breq amBitHigh
cpi ZL, TMS_WHIGH
breq amWHigh
amResetState:
// In case of an error, reset state to
// the default TMS_NONE
ldi R16, TMS_NONE
amSetState:
// Preserve the current byte number
mov ZL, R_TS
andi ZL, 0x07
or ZL, R16
amSetState2:
mov R_TS, ZL
// Clear receiver tick counter
clr R_TT
amNone:
ret
amBitLow:
sbrc ZH, AM2302_PIN
ret
// The high bit signal was too long?
cpi R16, 8
brcc amResetState
// Store input bit (inverted, since cpi produces
// inverted result in the carry flag)
cpi R16, 4
rol R_TD
// Initally we set R_TD to 1, so when all 8
// bits are received, the carry flag will be set
// indicating that a full byte has been received.
// Otherwise, receive the next bit
ldi R16, TMS_WBIT_HIGH
brcc amSetState
// We have the full byte. Invert it
com R_TD
// Save it
mov ZL, R_TS
andi ZL, 0x07
subi ZL, low (-tempData)
ldi ZH, high (tempData)
st Z+, R_TD
// Did we receive all 5 bytes?
cpi ZL, low (tempData + 5)
ldi R16, TMS_WHIGH
breq amSetState
// OK, receive the next byte.
// Increment the byte counter
inc R_TS
// Initialize R_TD
ldi R16, 1
mov R_TD, R16
ldi R16, TMS_WBIT_HIGH
rjmp amSetState
amWHigh:
sbrs ZH, AM2302_PIN
ret
cpi R16, 6
brcc amResetState
// We received everything. Set
// the state to TMS_NONE and set
// the data validity bit
ldi R16, 0x08
mov R_TS, R16
ret
// *********
/*
// Write data from Z
// Uses R16 - R19, X, Z
maxWriteData:
lpm XH, Z+
tst XH
brne SKIPNEXT1W
ret
lpm XL, Z+
rcall maxWriteWord
rjmp maxWriteData
maxInit:
.db MAX_DECODE, 0
.db MAX_INTENSITY, 4
.db MAX_SCANLIMIT, 7
.db MAX_SHUTDOWN, 1
.db MAX_DISPTEST, 0
.db 0, 0
maxTest:
.db 0, 0b00011101, 0b00010101, 0b00010000, 0b00011100, 0b00111101, 0b00000101, 0b01110111
*/
// Writes 8 bytes from (Z) (program memory)
// to MAX7219
// Uses R16 - R19, X, Z
maxWrite8Bytes:
ldi XH, 0x01
mw8b1:
lpm XL, Z+
rcall maxWriteWord
cpi XH, 9
brne mw8b1
ret
// Write word X (XL = data, XH = address) to MAX2719
// Uses R16 - R19, X
maxWriteWord:
// Set all pins to zero
in R17, (PORTB)
andi R17, ~((1 << MAX_DIN) | (1 << MAX_CS) | (1 << MAX_CLK))
out (PORTB), R17
ldi R19, (1 << MAX_CLK)
mov R16, XH
rcall mww1
mov R16, XL
rcall mww1
// Set LOAD(CS) to high thus writing all 16 bits into
// MAX register
sbi (PORTB), MAX_CS
// Increment MAX register number
inc XH
ret
mww1:
ldi R18, 8
mww2:
bst R16, 7
bld R17, MAX_DIN
out (PORTB), R17
lsl R16
dec R18
// Create clock impulse by toggling clock output twice
out (PINB), R19
out (PINB), R19
brne mww2
ret
// *********
printDecX:
ldi ZH, low (1000)
ldi R16, high (1000)
rcall pdx
// Change zero digit to empty space
cpi ZL, 0b01111110
brne SKIPNEXT1W
ldi ZL, 0
std DS (displayData), ZL
ldi ZH, 100
ldi R16, 0
rcall pdx
// If this digit is zero and the first
// digit is empty (i.e. it was zero too)
// change this digit to empty space
ldi R16, 0b01111110
eor R16, ZL
ldd ZH, DS (displayData)
or R16, ZH
brne SKIPNEXT1W
ldi ZL, 0
std DS (displayData + 1), ZL
ldi ZH, 10
ldi R16, 0
rcall pdx
std DS (displayData + 2), ZL
mov ZL, XL
rcall pdx3
std DS (displayData + 3), ZL
// Clear carry flag to indicate that
// no error occurred
clc
ret
pdx:
ldi ZL, 0
pdx1:
sub XL, ZH
sbc XH, R16
brcs pdx2
cpi ZL, 9
breq pdxOverflow
inc ZL
rjmp pdx1
pdx2:
add XL, ZH
adc XH, R16
pdx3:
subi ZL, -low (hexTable << 1)
ldi ZH, high (hexTable << 1)
lpm ZL, Z
ret
pdxOverflow:
// Set carry flag to indicate error
sec
// Pop return address out of the stack
// so we can return to the caller of printDecX
pop R16
pop R16
ret
Аргументы:
— Датчик другой
— Экран другой
Приведённый код не имеет смысла применительно к описанному проекту.
Вы свой код собрать пробовали?
адекватность всего остального в статье вызывает крайне высокие сомнения. Не очень понимаю, как размер кода влияет на энергоэффективность. Настоящий специалист может и в пустом цикле крутить на полную мощность ядра.ATtiny13 memory use summary [bytes]:
Segment Begin End Code Data Used Size Use%
---------------------------------------------------------------
[.cseg] 0x000000 0x0002f4 738 18 756 1024 73.8%
[.dseg] 0x000060 0x000089 0 41 41 64 64.1%
[.eseg] 0x000000 0x000000 0 0 0 64 0.0%
Assembly complete, 0 errors. 0 warnings
Видимо, разницы между 40 байтами и 738 байтами совсем нет. И это мы еще никак знакогенератор на несколько шрифтов для экранчика не трогали.
На уровне таких «оценок» и фраз
Ну, если Вы не понимаете как размер кода влияет на энергопотребление, тогда о чем говорить. Посмотрите, пожалуйста, техописание комнатного термостата стр 11 — это оттуда: Напряжение питания 2 x 1.5V алкальных батарейки (LR6 AA), Потребляемая мощность 1.3 mW, Срок действия батареек ~ 1 год.
Вряд ли прибор прошит в коде Ардуино или у Вас есть сомнения? )
Далее, для примера, один из тестовых скетчей и короткий фрагмент таблицы с результатами теста. Таких таблиц с записями на 1300-1500 строк и тестовых скетчей у меня десяток. В ближайшее время размещу их в каком-нибудь облаке, чтобы рассеять сомнения. Моя статья и так перегружена, поэтому не стал не размещать тестовые скетчи, таблицы, видео (гифки) с измерениями тока и т.п.
/*скетч для измерения операционного времени приемником LoRa и макc. операц. тока
операционное время 0,083сек (83 мсек)
операционный ток 11.5 mA (Vbat=3V, 8 MHz)
*/
#include <avr/io.h>
#include <util/delay.h>
#include <SPI.h>
#include <LoRa.h>
#include <LowPower.h>
#include "HTU21D.h"
#include <LCD5110_Graph.h>
String LoRaData;
void setup() {
Serial.begin(38400);
Serial.println("Power ON!");
int counter = 0;
while (!LoRa.begin(433E6) && counter < 10) {
Serial.println("Не удалось найти LoRa-передатчик!");
counter++;
_delay_ms(500);
}
// Диапазон для синхрослова – между "0-0xFF".
LoRa.setSyncWord(0xF3);
Serial.println("Прослушивание эфира. Ожидание пакета с в.датчика ...");
_delay_ms(2000);
}
void loop() {
int packetSize = LoRa.parsePacket();
Serial.println("Прослушивание эфира.");
if (packetSize) {
while (LoRa.available()) {
LoRaData = LoRa.readString();
}
int pos1 = LoRaData.indexOf('#');
if ((LoRaData).substring(pos1, pos1 + 1) == "#")
{
Serial.println("Принято, декодировано!");
}
}
}
int main() {
init();
setup();
for (;;) {
loop();
}
}
То есть, вопрос с размером кода уже замылился? Думаю, что смысла доказывать и про энергопотребление тогда нет. Вам удачи и новых чудесных открытий!
Если и влияет, то очень косвенно. Практически — никак, ни на пиковое, ни на среднее.
Готов поспорить: размер кода > количество операций > энергопотребление. Ведь очевидно, что на две операции затраты меньше, чем, допустим, на 10 таких же. Что легче: вытащить 2 ведра воды из колодца или 10?
2 операции, выполняющиеся непрерывно в цикле, или 100 операций, выполняющиеся однократно раз в 10 секунд. По Вашей же аналогии — вытащить 2 ведра из колодца одним махом или по 1 литру в день за 20 дней.
Если же говорить о более продвинутых контроллерах с настраиваемой частотой, DMA и т.д., то примеров можно привести множество.
Читаем еще раз :
И неважно, как они выполняются — одним махом или не одним.
Вы не признаете режимы энергосбережения микроконтроллеров? :) Ведь совершенно неочевидно, что код с меньшим объемом будет более энергоэффективным. Можете проверить сами — напишите прошивку, в которой функция main() будет содержать только строку:
while (1);
и сравните объем кода и энергопотребление этой прошивки и Вашей прошивки метеостанции из статьи. А если хотите еще большего контраста, то напишите не пустой бесконечный цикл, а бесконечный цикл с отправкой пары байт через радиоканал без всяких powerdown и sleep :)
Как-то совсем не похоже на 40 байт кода. Одна только таблица прерываний и таблица преобразования в 7-сегментное отображение уже тянут на 30 байт. И это только самое начало всей портянки кода.
Да, вы правы! Дикая описка и выводы соответственно.
Не всегда понимаю чем руководствуются когда ставят тип переменной int если в эту переменную максимум что прилетает или записывается в самом начале в виде константы нечто в пределах 0-255.
Учту на будущее. Как-то не было проблем до сих пор. Спасибо!
Первый раз слышу про «спячку» BM*280 от дёрганья питания. Мануал вполне однозначен: при сбросе/подаче питания датчик в режиме «sleep» — 0.1uA потребление, дальше — командуем по I2C.
Ардуиновские библиотеки, ЕМНИП, переключают датчик в «normal» режим и обратно есс-но не отключают. А это — измерения раз в секунду. «Правильный» ход — поправить используемую библиотеку (точнее запись в регистр ctrl_meas) для работы в «forced» режиме или написать свою обёртку. Я писал свою, чтобы читать сразу все данные за один запрос по I2C.
Javian, Крутовато для поделок :) Использовал похожий приём — супервизор на примерно 2.1В в качестве источника прерываний и полевик на отключение себя от батарейки. Дальше спим без watchdog-ов, пока не сработает IRQ0, на остатках конденсатора подключаем батарейку обратно. Идея долго бродила, но оформилась после чтения комментов сайта hallard.me про Ultra Low Power Node.
ЗЫ: прошелся по ссылке и нашел себя же во втором комменте. Идём на следующий круг )))
Ардуиновские библиотеки (конкретно от Adafruit) умеют выставлять режим датчика
немного не понял зачем Лора? выносной блок на км удалён? хотя Лора интересно, я не рискнул работать на 433- очень много помех, решил делать на 868 на 1276. интересно, что мне не удалось добиться потребления в режиме передачи, как в даташитах, спит, да 2мка или что то на грани чувствительности мультиметра. Что с реальным потреблением у Ра01?
А почему бы и не Лора? Ra-01 это SX1278. В sleep там наноамперы какие-то. И с помехоусточивостью всё отлично.
Поддерживаю. Да и мощности в пределах квартиры достаточно 4 дБ.
Ассемблер даст выигрыш в размере кода ну может процентов 10 по сравнению с Си. Это если проект не большой, сотня-две байт. Если проект больше, то выигрыш в размере кода стремится к нулю, а в больших проектах может и Си скомпилировать меньший код за счёт лучшей оптимизации. В общем не тот это случай чтобы париться с асмом. Не пользуйтесь printf(), float, string и что там ещё у вас многожрущее. И самый главный выигрыш — нафиг ардуину, пишите на обычном си.
Спасибо! Коротко, внятно и однозначно.
Добавлю немного своего экспириенса. Использую датчики построенные на довольно близкой базе: Mega328P@12MHz, Ra-01, HTU21D, TPS61291, 2xAAA (щелочные «мизинчики»). Связь информационным пакетом в одну сторону на хост, пакет 11 байт, мощность 14дБм, диапазон 433МГц. Дальше режим приёма длительностью 10 символов для получения возможного управляющего пакета от хоста. Везде где можно сон в моде PowerDown. Не нужную периферию отключать. Никаких повторных пересылок я не делаю, наоборот, используется адаптивный интервал, чтобы не флудить избыточными данными. Прошивка объёмом 9412 байт собрана с использованием выпотрошенных и отученных либ от ардуины. Самые старые элементы питания живут уже 15 месяцев, умирать пока не спешат. Больше времени с последнего апгрейда HW+FW просто не прошло. Потерь и повреждений данных я не фиксировал ни разу (отдельные случаи потерь за весь период, конечно, возможны, на общей картине не видно). Радиоканал работает на 25+ метров с несколькими деревянными перекрытиями, стенами, стальной крышей на пути, сигнал LoRa хороший и стабильный. Узкополосный сигнал в диапазоне 2.4ГГц (всевозможные nRF24L01+) ничего подобного и близко не показал. Но да, хост у меня не на батарейках, там режим приёма постоянный.
В статье добавил ссылку на архив с набором тестовых скетчей для измерений времени выполнения и тока операций.
Результаты измерений, как и раньше, — в таблице статьи.
Несколько лет как сделал «умную квартиру». Сейчас уже вторая квартира. Для климатических датчиков использую связку Arduino Mini Pro 8Мгц nRF24L01+ ds18b20 am2320 и все это на аккумуляторе 16340. Библиотеку для связи использую mysensors. Никаких хитростей типа отключения питания датчикам или радиомодулю не использую. Так же не хочу заменять на что то ds18b20 по тому что ему верю больше всех (проводил испытания разных). Хотя часто читаю что он якобы не пригоден для батарейного питания. Код примитивен. Передаю данные раз в 5 минут. При этом датчики живут полгода — год на одном аккумуляторе легко. Все кроме уличного. Уличный кроме того что морозится так еще находится дальше всех от базы. С ним постоянные танцы. Если уличный питать батарейками — то мороз по ним бьет прямо сразу. Я к чему это все — достаочно поставить один аккумулятор 18650 в базу и перехать на 8 мгц и больше никаких танцев по моему не надо. Ну это если надо устройство работающее от аккумулятора а не стремится повторить энергопотребление промышленных устройств.
Еще заметка с опытом выяснилось что сам модуль nRF24L01+ надо подбирать. Есть модули которые спят с током 6мА а есть которые с 0.05мА (ниже я уже не измеряю). BM*280 и правда надо усыплять програмно. Вешал его питание на ногу микроконтроллера. Так вот даже опустив ногу в ноль датчик умудрялся есть аккумулятор. Так что его нужно принудительно усыплять. Ну а точнее перевести в режим единичного измерения.
А почему бы не вывести за пределы помещения тонким кабелем только датчик ds18b20, а остальное оставить в помещении. В промышленных образцах реализовано именно такое решение.
ну во первых на улице не только ds18b20 но еще датчик влажности и давления. т.е получится что почти все надо будет оставить на улице а в квартиру тащить только батарейки и может быть радиомодуль. странное решение выдет. к тому же не красивый проводок и где то установленная коробка с батарейками. жена не оценит такую красоту
Тоже есть варианты:
Причины недоверия — я тестировал разные датчики. Кстати на самом деле am2320 весьма нормально показывает. Но веры все равно больше ds18b20. Когда я устал от того что десяток термометров стоящих радом (и иногда и в одном корпусе) показывает совершенное разное я купил поверенный термометр ВИТ и уже всех сравнивал с ним. Влажность кстати как оказалось am2320 тоже показывает весьма хорошо. Но у них опять же есть такое что конкретные экземпляры немного отличаются.
Сейчас повесил за окно датчик уже на здоровом аккумуляторе. Работает стабильно )
даже на narodmon вытащил свои данные.
Автономная метеостанция на батарейках. Цена 4$ на Али.
esp8266.ru/forum/threads/tlsr8251-lcd-termometr-lywsd03mmc-xiaomi-bluetooth-termometr.5263