Ассемблер даст выигрыш в размере кода ну может процентов 10 по сравнению с Си. Это если проект не большой, сотня-две байт. Если проект больше, то выигрыш в размере кода стремится к нулю, а в больших проектах может и Си скомпилировать меньший код за счёт лучшей оптимизации. В общем не тот это случай чтобы париться с асмом. Не пользуйтесь printf(), float, string и что там ещё у вас многожрущее. И самый главный выигрыш — нафиг ардуину, пишите на обычном си.
Ну, если Вы не понимаете как размер кода влияет на энергопотребление, тогда о чем говорить. Посмотрите, пожалуйста, техописание комнатного термостата стр 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)
*/
А почему нет кода на ассемблер, откуда эти «оценочные» оценки? Очень интересно посмотреть, как Вы уместили в 200 байт шрифты для экранчика и преобразование данных с датчиков.
Работал с кодом термометра-гидрометра на ATtiny13. Получил объем занимаемой флеш-памяти 40 байт. Навскидку увеличил это число в 5 раз. Подредактирую эту цифру, если будут аргументы.
Вот код:
// 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
// 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
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
// 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
// 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
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
// 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
// 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
Снизить энергопотребление в режиме сна можно, если питание внешних модулей коммутировать p-канальным полевиком или даже просто запитать их от одного или нескольких пинов GPIO микроконтроллера. Максимальный ток 20мА на пин.
Питание периферии с коммутацией транзистором или от пинов контроллера у меня практически во всех проектах.
В nRF24L01+ требуется повторная инициализация контроллера радиомодуля. Инициализация несовместима с командами библиотеки LowPower.h, которые задают у меня время сна.
Короче, чтобы восстановить настройки радиомодуля после сна надо сделать Reset контроллера, который ним управляет. У меня — это atmega328p. При ресетах с интервалом минута ресурса флеш-памяти хватит на несколько суток.
Возможно я заблуждаюсь, еще поиграюсь с nRF24L01+ уж очень низкое у него энергопотребление.
Также переписывание с С на ассемблер не имеет почти никакого смысла. Зато имеет смысл порой избавиться от стандартной функции printf() и написать свою, если вы выводите, например, только целые числа
Перед отправкой данных с десятыми-сотыми умножаю на 100, а дальше — дело техники.
Экран E-ink 2.13 + Контроллер NRF52 < 2мкА потребления в режиме сна
Либо взять E-ink + Zigbee как на Xiaomi термометрах
Дисплей 2.13 будет маловат для комнатного термостата и метеостанции в одном. В списке желаний на Али уже несколько месяцев e-paper дисплей 3,7 дюйма.
NRF52832 уже в пути.
Коль уж с Arduino уходим на C/asm, возникает резонный вопрос: может уже «гулять на все» и с самой atmega уйти на что-нибудь специально созданное для низкого потребления? STM8L или STM32L например.
Спасибо, но Вы переоцениваете мои возможности.
Для меня программисты, работающие на низкоуровневых языках типа Ассемблера — небожители и, как оказалось, на Ардуино тоже можно решать не тривиальные задачи.
Ваши аргументы не вызывают у меня сомнений. Но перед тем как внести изменения в скетч хотелось проверить их на «железе». Сейчас у меня такой возможности нет. Как только проверю — отредактирую код. Заранее благодарю за понимание.
Давно существует комбинированный крепёж — полугвоздь/полувинт, этакий плоский скрученный спиралью гвоздь, но с нормальной резьбой на последнем сантиметре и шлицами. Забивается сначала молотком, потом докручивается шуруповёртом, держит на ура доски или ОСБ плиту, прибитую к балкам хоть 100 мм.
Уточню, тут спор между государствами с исками в международных судах и суммами на десятки миллионов зеленых за повреждение имущества. Беленко — в стороне. Поэтому японская сторона затягивала переговоры о передаче самолета, чтобы качественно и добротно упаковать разобранный самолет и в будущем обвинить советскую сторону в нарушений условий договора, а именно в нарушении сроков приемки.
Рассуждения о отвертках, шлицах, саморезах, шуруповертах явно не для этой темы. Тем более, что конструкция контейнеров прояснилась после того, как их раскурочили с помощью зубил, кувалд и ломов.
И еще. Посмотрите картинку. aliexpress.ru/item/32712841432.html?spm=a2g0s.9042311.0.0.3c0f33ed7mbG9Q&_ga=2.182776358.1876782405.1614251579-443101207.1611486899&_gac=1.19296330.1611486899.Cj0KCQiA0rSABhDlARIsAJtjfCfwGdizyrjlBIjO_aQcCM5GrlQkyE3LRx-4dqPCc1exMpMvSHGUSH4aApqBEALw_wcB
Мне пришлось заказывать набор таких бит, чтобы разобрать свою кофеварку Kenwood — два ремонта кофеварки в сервисе стоили бы мне новой. Фирмы и в настоящем и, наверное, в прошлом усложняют доступ вовнутрь — они на этом зарабатывают.
Не все так просто, особенно, если интерес на миллионы долларов…
Вот и вскрылась причина проигрыша в холодной войне — отсутствие крестовых отвёрток и правильного крепежа. Точнее, отвёртки были, но косо штампованные из некачественного металла, а вместо цилиндрических саморезов — конические шурупы, тоже штампованные, нередко с облоем, которые даже в мягкое дерево было тяжело закручивать — с каждым оборотом усилие увеличивалось, а отвёртка и шуруп слизывались. И никаких заумных профилей PZn/PHn — только Толстая хреновая и Тонкая, так что уж лучше её величество Шлицевая, на каждом полобороте соскальзывающая и снова вставляемая в шлиц в темноте под углом наощупь окоченевшими руками.
Да, слово «саморез» тут не совсем к месту. Скорее всего болт с резьбой, как у самореза, — не могу найти подходящее слово. Любая отвертка при монтаже 60-и мм досок — это нонсенс.
В свое время мне довелось присутствовать на встрече с членом правительственной делегации, которая работала в Японии над вопросами передачи угнанного самолета.
Что запомнилось:
Вначале шла длительная дискуссия о сроках приемки советской стороной. Японцы настаивали на записи «два рабочих дня», советская сторона — «двое суток». В конце-концов записали «двое суток».
Как выяснилось позже, после загрузки контейнеров на советский корабль в порту Японии, японцы сделали ставку на то, что за столь короткое время не удастся распаковать ящики. Каждый ящик состоял из трех слоев. Верхний — фанера с металлической окантовкой, затем — обрезные, качественно подогнанные одна к одной доски и третий слой — необрезные доски еще потолще, чем во втором слое. Монтаж досок был выполнен саморезами с потайными головками.
Срочно на помощь было вызвана бригада такелажников с медиками, кажется, с Владивостока. Работали без сна двое суток, медики обеспечивали работоспособнось. Контейнеры распаковали в оговоренное время.
Со многих узлов было взято по несколько проб, например с лобового стекла. О проверке комплектности тогда еще речи не было. Самолет восстановлению не подлежал.
Японцы выставили ответный иск на несколько акров земли, испорченных Беленко при посадке — у него не было опыта работы на взлетно-посадочных полосах, которые сливаются с горизонтом моря.
А подскажите, поможет ли решить данную проблему подключения модуля nRF через кренку?
Решить проблему в целом — сомневаюсь. Не забывайте о ВЧ-помехах по цепи питания nRF и качестве этого питания, которое определяется мощностью источника.
Неплохо-бы добавить на линии питания конденсатор микрофарад на 100 для облегчения работы батареек.
Для батареек с током короткого замыкания больше 2А (точнее не позволяет измерить шкала моего амперметра) вряд ли нужна помощь при работе с нагрузкой около 10-ти мА.
Контроль питания. С такими сопротивлениями делителя — надо зашунтировать «нижний» конденсатором (10..100 нФ), чтобы данные соответствовали действительности.
Я думал над этим и проверял — ставил конденсатор 470 нФ, но изменений в десятых вольта не заметил. Конденсатор, по-моему, важен при подключении к блоку питания — для сглаживания пульсаций напряжения. Для страховки — поставлю.
А лучше — измерять напряжение питания полностью встроенными средствами МК. Сконфигурировать опорное напряжение АЦП как напряжение питания и измерять относительно него внутреннее опорное напряжение 1,1 В.
Большинство ПС как раз сделано на батарейках, тк у них низкий саморазряд.
Относительно низкого саморазряда батареек — в этом не надо никого переубеждать. По поводу большинства — хотелось бы хоть один пример, чтобы дополнить сравнительную таблицу приемного узла в статье конкретными результатами из других проектов. Повторюсь, мне таких примеров найти не удалось. Возможно не там искал.
328p далеко не лидер по энергоэффективности, и опять же, странно пользоваться им в 2019 году
Уточнил в статье: Метеостанция состоит из двух узлов, назовем их для простоты гигрометром и термометром. Связь между узлами — беспроводная, по радиоэфиру.
А почему не использовали прерывание — wake on radio, встроенное в чип? Как только SX1278 словил сигнал, он выставляет высокий уровень на DIO4. Atmega328 просыпается делает дела и засыпает до следующего прерывания.
Да, но приемник LoRa при этом должен быть все время включен. Потребление работающего приемника вряд ли ниже потребления сторожевого таймера контроллера, поэтому я не смотрел в сторону прерывания. Возможно ошибался.
Результаты измерений, как и раньше, — в таблице статьи.
Поддерживаю. Да и мощности в пределах квартиры достаточно 4 дБ.
Спасибо! Коротко, внятно и однозначно.
Вряд ли прибор прошит в коде Ардуино или у Вас есть сомнения? )
Далее, для примера, один из тестовых скетчей и короткий фрагмент таблицы с результатами теста. Таких таблиц с записями на 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:53:30.144 -> Tin=21.90 Hin=41 BatIn=3.30 Tout=00.00 Hout=00 BatOut=BGood
10:53:30.544 -> Tin=21.90 Hin=41 BatIn=3.27 Tout=00.00 Hout=00 BatOut=BGood
10:53:30.944 -> Tin=21.90 Hin=41 BatIn=3.30 Tout=00.00 Hout=00 BatOut=BGood
10:53:31.304 -> Tin=21.90 Hin=41 BatIn=3.31 Tout=00.00 Hout=00 BatOut=BGood
10:53:31.704 -> Tin=21.90 Hin=41 BatIn=3.30 Tout=00.00 Hout=00 BatOut=BGood
10:53:32.104 -> Tin=21.80 Hin=41 BatIn=3.30 Tout=00.00 Hout=00 BatOut=BGood
10:53:32.504 -> Tin=21.90 Hin=41 BatIn=3.31 Tout=00.00 Hout=00 BatOut=BGood
10:53:32.904 -> Tin=21.90 Hin=41 BatIn=3.30 Tout=00.00 Hout=00 BatOut=BGood
10:53:33.304 -> Tin=21.90 Hin=41 BatIn=3.30 Tout=00.00 Hout=00 BatOut=BGood
10:53:33.704 -> Tin=21.90 Hin=41 BatIn=3.30 Tout=00.00 Hout=00 BatOut=BGood
10:53:34.064 -> Tin=21.90 Hin=41 BatIn=3.31 Tout=00.00 Hout=00 BatOut=BGood
10:53:34.464 -> Tin=21.80 Hin=41 BatIn=3.30 Tout=00.00 Hout=00 BatOut=BGood
10:53:34.866 -> Tin=21.90 Hin=41 BatIn=3.31 Tout=00.00 Hout=00 BatOut=BGood
10:53:35.266 -> Tin=21.90 Hin=41 BatIn=3.31 Tout=00.00 Hout=00 BatOut=BGood
10:53:35.666 -> Tin=21.90 Hin=41 BatIn=3.31 Tout=00.00 Hout=00 BatOut=BGood
10:53:36.066 -> Tin=21.90 Hin=41 BatIn=3.30 Tout=00.00 Hout=00 BatOut=BGood
10:53:36.466 -> Tin=21.90 Hin=41 BatIn=3.27 Tout=00.00 Hout=00 BatOut=BGood
10:53:36.866 -> Tin=21.90 Hin=41 BatIn=3.32 Tout=00.00 Hout=00 BatOut=BGood
10:53:37.226 -> Tin=21.80 Hin=41 BatIn=3.31 Tout=00.00 Hout=00 BatOut=BGood
10:53:37.626 -> Tin=21.90 Hin=41 BatIn=3.29 Tout=00.00 Hout=00 BatOut=BGood
10:53:38.026 -> Tin=21.90 Hin=41 BatIn=3.29 Tout=00.00 Hout=00 BatOut=BGood
10:53:38.426 -> Tin=21.90 Hin=41 BatIn=3.30 Tout=00.00 Hout=00 BatOut=BGood
Если уж сильно захочется посмотреть видео на тему потребления, тогда сюда. Тут подобных гифок хватает.
Я к тому, что отношусь к проблеме серьезно, а Вы делаете обобщающие выводы по одной неточности. Успехов!
Работал с кодом термометра-гидрометра на 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
Питание периферии с коммутацией транзистором или от пинов контроллера у меня практически во всех проектах.
В nRF24L01+ требуется повторная инициализация контроллера радиомодуля. Инициализация несовместима с командами библиотеки LowPower.h, которые задают у меня время сна.
Короче, чтобы восстановить настройки радиомодуля после сна надо сделать Reset контроллера, который ним управляет. У меня — это atmega328p. При ресетах с интервалом минута ресурса флеш-памяти хватит на несколько суток.
Возможно я заблуждаюсь, еще поиграюсь с nRF24L01+ уж очень низкое у него энергопотребление.
Перед отправкой данных с десятыми-сотыми умножаю на 100, а дальше — дело техники.
Дисплей 2.13 будет маловат для комнатного термостата и метеостанции в одном. В списке желаний на Али уже несколько месяцев e-paper дисплей 3,7 дюйма.
NRF52832 уже в пути.
Спасибо, но Вы переоцениваете мои возможности.
Для меня программисты, работающие на низкоуровневых языках типа Ассемблера — небожители и, как оказалось, на Ардуино тоже можно решать не тривиальные задачи.
NRF52832 уже в пути несколько дней, библиотеку скачал сегодня. Буду работать в этом направлении.
Уточню, тут спор между государствами с исками в международных судах и суммами на десятки миллионов зеленых за повреждение имущества. Беленко — в стороне. Поэтому японская сторона затягивала переговоры о передаче самолета, чтобы качественно и добротно упаковать разобранный самолет и в будущем обвинить советскую сторону в нарушений условий договора, а именно в нарушении сроков приемки.
Рассуждения о отвертках, шлицах, саморезах, шуруповертах явно не для этой темы. Тем более, что конструкция контейнеров прояснилась после того, как их раскурочили с помощью зубил, кувалд и ломов.
И еще. Посмотрите картинку. aliexpress.ru/item/32712841432.html?spm=a2g0s.9042311.0.0.3c0f33ed7mbG9Q&_ga=2.182776358.1876782405.1614251579-443101207.1611486899&_gac=1.19296330.1611486899.Cj0KCQiA0rSABhDlARIsAJtjfCfwGdizyrjlBIjO_aQcCM5GrlQkyE3LRx-4dqPCc1exMpMvSHGUSH4aApqBEALw_wcB
Мне пришлось заказывать набор таких бит, чтобы разобрать свою кофеварку Kenwood — два ремонта кофеварки в сервисе стоили бы мне новой. Фирмы и в настоящем и, наверное, в прошлом усложняют доступ вовнутрь — они на этом зарабатывают.
Не все так просто, особенно, если интерес на миллионы долларов…
Да, слово «саморез» тут не совсем к месту. Скорее всего болт с резьбой, как у самореза, — не могу найти подходящее слово. Любая отвертка при монтаже 60-и мм досок — это нонсенс.
Что запомнилось:
Решить проблему в целом — сомневаюсь. Не забывайте о ВЧ-помехах по цепи питания nRF и качестве этого питания, которое определяется мощностью источника.
Для батареек с током короткого замыкания больше 2А (точнее не позволяет измерить шкала моего амперметра) вряд ли нужна помощь при работе с нагрузкой около 10-ти мА.
Я думал над этим и проверял — ставил конденсатор 470 нФ, но изменений в десятых вольта не заметил. Конденсатор, по-моему, важен при подключении к блоку питания — для сглаживания пульсаций напряжения. Для страховки — поставлю.
Обязательно реализую этот подход.
Относительно низкого саморазряда батареек — в этом не надо никого переубеждать. По поводу большинства — хотелось бы хоть один пример, чтобы дополнить сравнительную таблицу приемного узла в статье конкретными результатами из других проектов. Повторюсь, мне таких примеров найти не удалось. Возможно не там искал.
Учту на будущее.
Да, но приемник LoRa при этом должен быть все время включен. Потребление работающего приемника вряд ли ниже потребления сторожевого таймера контроллера, поэтому я не смотрел в сторону прерывания. Возможно ошибался.