среда, 15 октября 2014 г.

nRF24L01. Энергосбережение 2: Путь самурая

В первой части мы научились работать с nRF вообще, во второй разобрались с тем, как запитать его так, чтобы не разориться на батарейках. Уже после ее публикации она была дополнена интересным, но пока мной не опробованным, вариантом №5 (бонусным), обещающим наиэффективнейший режим энергосбережения!

Напомню, суть его сводится к тому, что экономное устройство (в моем случае сенсорный выключатель) само периодически (раз в 64-128мс) обращается к роутеру за данными, а роутер отвечает либо пустым пакетом из нулей (например), либо пакетом данных, если таковой ожидает востребования. Сам роутер занимается тем, что передает пакеты от ПК в радиоэфир и обратно.
Причем ответный пакет передается через фичу W_ACK_PAYLOAD, что здорово упрощает работу и код прошивки приемника и передатчика (в частности переключение TX-RX-TX происходит в модуле само).

Более того, на ответный ACK-пакет, пришедший по этой фиче, на передававшей стороне (выключателе) помимо TX_DS генерируется отдельное прерывание RX_DR! Мне код выключателя вообще менять не пришлось, только регистры модуля при старте подтюнить! А, ну и добавить периодический пинг роутера спец.пакетом, в ответ на который тот должен W_ACK_PAYLOAD мне в ответ заряжать.
Короче подробности и Success Story - далее.



Как гласит диаграмма, на PTX действительно генерится 2 прерывания разом.
Дело за малым - поправить прошивку роутера.

Разработка новой версии выключателя пока завяла, потому опыты я проводил на дачном беспроводном градуснике. Исходное потребление в активном режиме - 18мА, в спящем - 1.5мкА (камень и радиочип в powerdown). Расчетное время работы в активном режиме от 2хAAA 1000мАч - 44 часа. в спящем - 60 лет :) В спящий режим железка уходит через 10 сек автоматически, а просыпается по нажатию кнопки.

Будем минимизировать потребление в активном режиме.
В коде приемника (тут главное не сломать мозг, теперь он PTX! буду называть его "устройство") мне пришлось сделать минимум правок. Во-первых, сконфигурировать чип на передачу; во-вторых, в главном цикле добавить пинг роутера пустым пакетом; и в-третьих, выполнить следующее:
 - выполнить команду ACTIVATE с байтом данных 0x73
 - записать в регистр FEATURE биты EN_ACK_PAY, EN_DPL, EN_DYN_ACK
 - записать в регистр DYNPD бит DPL_P0

На "роутере" (PRX, спец.девайс, соединяющий nRF устройства с проводной сетью) проводится точно аналогичная конфигурация (можете сравнить посимвольно с 3 пунктами выше):
 - команда ACTIVATE с байтом данных 0x73
 - команда записи в регистр FEATURE бит EN_ACK_PAY, EN_DPL, EN_DYN_ACK
 - запись в регистр DYNPD бита DPL_P0

Чтобы заработала фича ACK_PLD нужно выполнить команду ACTIVATE.
Внимание! Команда ACTIVATE может быть выполнена только в PowerDown или Standby режимах. А повторное ее выполнение снова выключает фичи из регистра FEATURE. Причем. сначала надо выполнить ACTIVATE, а затем уже писать в регистр W_ACK_PAYLOAD.

Довольно долго я бился, подбирая настройки, куря мануал снова и снова, наблюдая за мусором, который сыпался на экран устройства.. в итоге, как это бывает почти всегда, оказался "сам дурак": на роутере забыл проставить длину пакета в 6 байт, отсылал всего 1.

Также я долго ломал мозг как правильно читать R_RX_PLD_WID, в даташите там отчего то в поле "Data bytes" пусто. Видимо это меня сбило с толку, к тому же уже ночь была. В итоге выяснено, что отличий и сложностей тут вообще никаких: надо отправлять ее ровно так же, как и при чтении того же регистра статуса, или любого другого, а ответ читать как и при любой другой команде - из приемного буфера SPI.
Т.е. если чтение регистра конфига у меня выглядит так
nrf24config_r:
ldi temp, NRF24_REG_CONFIG
ori temp, NRF24_R_REGISTER
sts reg_command, temp
rcall nrf24read ; результат в spi_buffer_rx
ret
то чтение R_RX_PLD_WID так
ldi temp, NRF24_RX_PL_WID
sts reg_command, temp
rcall nrf24read
lds temp, spi_buffer_rx
Вообще, если придерживаться константной ширины пакета на обоих девайсах, то и необязательно читать эту переменную. Если на роутере было отправлено в ACK 6 байт, то и значение переменной R_RX_PLD_WID будет 6.

Мне динамический размер PAYLOAD вообще был не нужен, у меня все пакеты - 6 байт. Я пробовал настроить чипы без EN_DPL и EN_DYN_ACK, но сперва у меня не получилось.. Только на следующий день, когда связка уже кое-как работала, я попробовал снова, и к радости моей в этот раз получилось. Бонусом шел один приятный момент: на устройстве TXDS и RXDR теперь генерировались одновременно. С DPL сначала был только TXDS, а в след.цикле уже добавлялся RXDR, запаздывание какое-то было в общем.

Итого, конфигурация свелась к следующему (на обеих сторонах):
 - команда ACTIVATE с байтом данных 0x73
 - команда записи в регистр FEATURE бита EN_ACK_PAY

Энергосберегающие режимы работы

Итак, прикинем, какие у нас есть варианты

Вариант 1. Есть мысль получать пакет (на роутере), смотреть от кого он, ACK_PLD при этом пустой, т.е. в ответ на пакет ничего не пойдет, передатчик примет решение о ретрансляции, а мы в это время загрузим нужный PLD в TX FIFO, и когда придет повторный пакет - ему уже будет готов ACK_PLD. Особенно если настроить перерыв между попытками отправки побольше. Но эта мысль расходится с утверждением о том, что на устройстве не будет maxrt, если не выдавать ему ACK_PLD. Проверим

Вариант 2. Судя по диаграмме, на роутере между RXDR и отправкой ACK есть таймаут в 130мкс. при 8МГц это 1040 тактов, достаточно для того, чтобы считать пришедший пакет по SPI, понять есть ли данные для этого устройства и загрузить W_ACK_PAYLOAD в ответ. Вопрос только в том, не решит ли роутер по пустому (пока) TX FIFO, что пора вообще спать идти.

Вариант 3. Можно не выеживаться, как уж на сковороде, с этими ACTIVATE, FEATURE и иже с ними, а поступить более прямолинейно. Слать пинг-пакет на роутер, переходить в режим RX на 1мс, роутер в свою очередь подберет из своей памяти данные для адресата, если они есть, и отправит. Или тупо промолчит.
Отправка 6-байтного пакета, если прикинуть по диаграмме, - в районе 360мкс. Если получили TXDS, переходим в RX-mode, ждем 500-750мкс, если ничего не пришло - кладем спать чип и во сне уже переводим обратно в TX. Итого, при периоде в 125мс получаем 1мс в активном режиме, или средний ток - примерно 120мкА. Попытки передачи можно отключить, чтобы не росло потребление. если в эфире никого нет.


За дело!

Исходные данные. Роутер в режиме PRX, устройство в режиме PTX. На обоих одинаково настроен регистр FEATURE + выполнена ACTIVATE. RX/TX FIFO пусты, устройства только что включены. Ping-пакет (от устройства к роутеру) пусть будет 06 00 00 00 00 00, ACK-пакет (в ответку)  00 10 20 30 40 50. Устройство питается от 2x1.5V AAA GP. Ток замеряю в разрыве цепи питания всего устройства. Как я уже писал в начале статьи, в глубоком сне потребление 1.5мкА, так что потреблением самой схемы можем пренебречь. В любом случае цифры будут ближе к реальности, если не устраивать слишком стерильных тестов.

Проверка варианта 1

Для проверки варианта 1 теория следующая:
 - на устройстве конфигурируем 3 попытки передачи с паузой в 750мкс, отправляем ping-пакет
 - на роутере пакет должен сгенерить прерывание RXDR, читаем его, быстро загружаем в W_ACK_PAYLOAD ACK-пакет, но уйдет он позже
 - устройство не дожидается ACK-пакета, передает запрос еще раз и теперь уже получает ACK
 - при этом роутер знает, что ping-пакет прислан повторно (тот же PID) и прерывание RXDS больше не генерит

Плюсы - простота, нативность, надежность
Минусы - несколько большее энергопотребление из-за необходимости слать один пакет 2 раза, необходимость команды ACTIVATE и настройки регистров на обеих сторонах.

Эксперимент:
на роутере мутим прошивку, которая по RXDS грузит в W_ACK_PAYLOAD ACK-пакет.
на устройстве при старте (еще до цикла main, важно) один раз отправляем пинг-пакет.
Успехом эксперимента считается срабатывание на устройстве прерывания RXDS с принятием ACK-пакета.

Проверка варианта 2

Сам не верю, что этот способ имеет какие-то шансы, но массы ради..
Порядок проверки:
 - на устройстве отключаются попытки передать пакет
 - роутер при срабатывании на нем RXDR резво загружает в W_ACK_PAYLOAD ACK-пакет и с чувством выполненного долга умывает руки
 - устройство при старте передает один ping-пакет
По идее это тот же самый Вариант 1, только проверяется при отключенных попытках ретрансмита. Т.е. если он и работает, то потребление устройства с этим вариантом будет минимальным.
Успехом эксперимента считается срабатывание на устройстве прерывания RXDS с принятием ACK-пакета.

Проверка варианта 3

Пожалуй довольно простой вариант, без лишних конфигураций.
Но после более тщательного рассмотрения я даже проверять его не стал.
Представим, что в сети десяток устройств, и все они периодически переходят то в RX, то в TX, причем никак не синхронизируясь, полный хаос. Не факт что пинг-пакет будет получен именно роутером, т.е. мы перешли в RX, роутер пока искал данные сосед уже начал передавать, роутер перешел в TX, а мы уже приняли от соседа пакет и уснули. хотя пакет нам не навредит, адрес то не наш, но и ожидающая на роутере команда выполнена не будет, плюс это (не?)мелкая дырка в безопасности: в случае с ACK профит был в том, что команды реально было получить только от роутера, а тут мы на 1мс "принимаем заказы" от всех кто в эфире.

Итоги

Вариант 1 - успешно
Счетчик попыток передачи ARC_CNT = 2, счетчик потерянных пакетов PLOS_CNT = 0 (регистр мониторинга потери пакетов OBSERVE_TX).
Т.е. все согласно теории - 2 попытки передачи пакета, одна провальная, другая успешная, пакет не потерян.
Однако наблюдается странное поведение: на роутере светодиод RXDR срабатывает только 1 раз при свежезапущенном роутере. при перезапуске же устройства оно отправляет пакет и получает видимо тот же ACK, потому что данные приходят, но на роутере RXDR нету!
попробую разнообразить данные, кажется где то мелькало, что пакет может быть отброшен не только по PID, но и по одинаковому CRC. Так и есть, см секцию 7.3.3.2 PID (Packet identification)
PID (2 бита всего) используется на стороне PRX для определения, новый это пакет или повторно отправленный. Число инкрементируется на стороне PTX каждый раз, когда по SPI загружается новый пакет. Если при передаче теряется несколько пакетов, может статься так, что PID следующего окажется равным PID последнего принятого, в этом случае чип сравнивает и CRC обоих пакетов и в случае равенства полагает, что пакет - копия последнего принятого.
CRC расчитывается по полям Address, Packet Control Field и Payload. 
Тут я в первый раз подумал, учитывая вышеописанное, а получится ли нормальная работа. если у всех чипов nRF будет один адрес, при том, что PID всего 2 бита. Хотя.. в пакете данных же адреса самих устройств будет разные, так что норм.
Положил на устройстве в один из байт пакета какой то счетчик, вроде помогло.
При опросе раз в 125мс потребление вышло 210мкА если роутер в эфире, 250мкА если никого нет.
Снизил паузу между передачами до минимума (ARD=250мкс), а число попыток поставил 2. Соотношение снизилось до 140мкА/210мкА.

Также на устройстве обнаружилась приятная неожиданность, сильно упрощающая работу с nRF: при отправке пинга линию CE нужно поднимать всего на 10мкс (как положено по даташиту), а дальше вообще можно спать идти (я вообще nRF сразу вырубал в PWRUP=0!, см.далее), nRF сам передаст пакет, сам перепередаст и сам получит ACK и просигналит ногой IRQ. Т.е. не требуется отсчитывать время, держа чип в активном режиме или каким-то иным образом помогать ему работать в соответствии с задуманной логикой - все нативно!


Вариант 2
По счетчику ARC_CNT предыдущего опыта уже стало ясно, что на первый пакет ACK не пришел. Возможно опоздал, на роутере довольно большой оверхед на всякие вызовы процедур, попробую сократить код и писать W_ACK_PAYLOAD сразу как только сработает RXDR...
Не, не сработал вариант. RXDR на роутере генерится, тут же пишется ACK_PLD (я даже RX_PLD не читаю, скорости ради), но до устройства не доходит. MAXRT, пакет потерян, оно и неудивительно. 
Этот опыт показал следующее:
1. если ACK_PLD настроить, но не загрузить данными - в ответ ничего не пойдет, устройство не получит подтверждение приема пакета и в итоге сработает прерывание MAXRT.
2. W_ACK_PAYLOAD надо писать заранее, нельзя получить на роутере пакет, и начать грузить в отет на него W_ACK_PAYLOAD.

Пока что мне нравится вариант 1 как самый понятный и нативный, но он, пожалуй, имеет изрядный потенциал для глюков. Ведь простоты ради, все чипы nRF работают с дефолтным адресом, а адрес самого устройства передается в пакете данных.
Представим, что один чип запросил наличие данных, роутер положил их в ACK_PLD, а другой чип влез в эфир и забрал их, в итоге оказалось, что в пакете данных вовсе не его адрес, а первый чип в то же время остался без данных. Возможно будет учтен PID пакета, и глюка не произойдет, надо проверять.
Но решение проблем параллельной работы множества устройств можно оставить для отдельной статьи.

Дополнительная информация

1. Если устройство не получило ACK, сгенерило MAXRT, то не помешает очистить его очередь TX FIFO. Сама она не очищается, иначе REUSE_TX был бы невозможен! А если не очищать ее, то с каждым пропущенным ACK, с каждым MAXRT очередь будет забиваться и после переполнения пакеты в очередь не попадут.

2. Не следует забывать, что по-умолчанию чип настроен на 3 попытки передачи пакета, т.е. если в эфире роутера нет, то потребление устройства возрастает, в моем случае до 250мкА при 3 попытках с паузой в 750мкс.

3. Надо учитывать, что эффективнее всего по питанию будет загружать все данные по SPI заранее, еще когда чип спит, а затем резко его будить и дергать линию CE. также при наступлении прерывания можно сразу гасить чип и не спеша читать по SPI его состояние и очередь RX.
Режим сна - powerdown (0.1мкА) или Standby (25мкА) - надо выбирать исходя из наличия энергии. Если потребление устраивает - рекомендую Standby, с ним работать куда проще, знай себе CE дергай вверх-вниз, плюс не требуется доп.время (130мкс, tx settling) на "выход на режим" после сна, неизвестно еще сколько при этом оно энергии ест.

Success-story

Последний из прототипов, который валялся у меня на столе, питался от балластного кондера 0.33uF, заставлял LED-лампу бешено моргать (диммируемая думаю светилась бы не меньше, чем в полсилы), а также приводил к треску ее импульсника (проблемы zerocross-а), содержал в общей сложности 7 корпусов (!) (микросхем), включая 2 симистора и сам проц, при этом безопасно тянул лишь 50-70 Вт нагрузки на канал, иначе схема питания логики нагревалась.

Энергосбережение, шаг за шагом!

Применение камня ATMega48PA (диапазон питания от 1.8В! до 5.5В) с Int.RC 1MHz позволило достичь минимального потребления в дежурном режиме. Данный проц имеет таймер (Timer2), прерывания которого работают даже в режиме PowerSave (разницы в потреблении PowerDown / PowerSave не заметил), что позволило для включения симисторов замутить на таймере ШИМ 1-2кГц, не беспокоясь о потреблении, что в свою очередь позволяет использовать выключатель с любой нагрузкой.

При старте схемы быстро настраиваем порты, вырубаем периферию, настраиваем собаку на 8 сек и кладем проц спать, чтобы зарядился кондер питания (а заряжается он реально медленно, около тех 8 секунд).

Вместо софтового опроса сенсора я влепил датчики AT42QT1011 (очень дешево на AliExpress), настроил их в LowPower (25мкА), а питание завел на ногу проца, чтобы не включать датчики, пока схема не стартовала.

Вместо импульсных преобразователей я в итоге решил обойтись обычным стабилитроном (даже не TL431, оно требует минимум 1мА тока через себя для нормальной стабилизации, откуда у меня столько!?). Питание плавает конечно будь здоров, от 2.3 до 4.4В, еще и от типа нагрузки зависит, но схеме пофиг. Это и отличает хорошую схему от не очень хорошей :) - она не требовательна ни к питанию, ни к номиналам большинства деталей.

Далее, применение описанного в статье подхода при работе с nRF позволило питаться практически крохами и вместе с этим с приемлемой отзывчивостью реагировать на команды.
Благо диапазон питания nRF тоже широкий: от 1.9 до 3.6В.
Также я попробовал работать с nRF в режиме PowerDown, и у меня получилось без лишних телодвижений. Просто добавил перед пингом запись PWRUP=1 в конфиг, а после пинга PWRUP=0. Профит по потреблению получил еще 20%!

Крохи стали еще меньше, когда я совершенно случайно обнаружил, что схема способна питаться БЕЗ балластного конденсатора от его разрядного резистора 1МОм. При этом, если повесить на один канал 3 китайские светодиодные НЕдиммируемые лампы, они уже даже не моргают! Походу схема токами утечки уже питается.
Я честно подождал минуты 3, ни одной вспышки. Включил свет, выключил свет, подождал еще пару минут. Не вспыхивает, блин! Напруга на логике 2.5В.

А дело было так. 
Как то меня посетила мысль поставить неонку вместо балластного кондера, ибо в наличии у меня их не так много. А неонку можно выдирать из тех выключателей, которые я под основу для своих покупаю. Тогда и доп.резистор не понадобится и подсветка в дежурке будет! Попробовал.. спалил что-то, кажется.. свет при запитывании сразу включался, горел постоянно и питания на схеме не было (0.7В). Жаль, подумал я, похоже неонка не катит, она резко пробивается при большом напряжении, и вся напруга в этот момент оказывается приложена к схеме, а это не гут!

Вынул неонку (кондер обратно не ставил!), включаю - таки схема осталась жива! Почему горел свет хз, сопля что-ли где то образовалась при пайке, не знаю, но открытие сие было весьма неожиданным и приятным! Балластный кондер вообще не нужен стал! Схема питается супермалым током через 1МОм - 220мкА! Кстати при таком токе уже вполне неплохо светит сверхъяркий LED. Думаю покатит для индикатора дежурки, у меня их куда больше, чем неонок. Воистину, что не делается - все к лучшему.
Замер тестером Mastech MY68 в разрыве 220В показал 201.3мкА с диммируемой Feron и 322мкА с галогенкой 50Вт.

Profit

Итого, много месяцев и пару промежуточных прототипов спустя я подготовил к продакшен-тестированию новейшую сильно переработанную версию управляемого беспроводным способом наносенсорного выключателя, не имеющего аналогов в мире. Девайс питается от шунт-резистора 1МОм, имеет на 4 корпуса меньше (я урезал схему на 2 оптосимистора, диодный мост и 1 DC-DC преобразователь, к которому кстати катушка тоже шла немаленькая) и тянет разнородную нагрузку 150Вт на канал без использования радиаторов на симисторах.

Фичи:
 - сенсорные каналы управления (от 1 до N, по желанию, у меня везде 2 группы света)
 - поддержка маломощных ламп ввиду очень малого потребляемого в дежурке тока
 - поддержка недиммируемой нагрузки (с использованием шунта на нагрузке)
 - поддержка нагрузки любого вида (галогенки, LED, энергосберегайки, люминесцентные)
 - команды управления по nRF

Дальнейшие планы:
 - управление (+обучение) по ИК
 - диммирование, плавное вкл-выкл
 - добить потребление, чтобы LED-лампа не вспыхивала (избавиться от шунта на нагрузке)
 - перепрошивка по радио

Схемотехника устроена так, что при включенных каналах света энергии становится еще больше и ее хватает на яркие светодиоды-индикаторы активности каналов. В теории ее там вообще столько, сколько потребуется, но не больше, чем может обеспечить нагрузка (зависит от протекающего тока, т.е. проще говоря от мощности нагрузки)!
Для недиммируемой нагрузки придется параллельно ей включить конденсатор-шунт, иначе например LED-лампы будут периодически вспыхивать.

Как то меня посетила мысль вместо кондера включить параллельно лампе неонку подсветки из обычного выключателя. Мысль меня порадовала, однако, опробовав ее, я понял, что она несостоятельна: ток предпочел течь через лампу. Также вместо кондера можно использовать 1Вт резистор 270к-510кОм.

Немного фотостатистики

 
Слева направо: 1 и 2 - свечение диммируемой лампы Feron 7Вт через неонку, на снимке 2 видна неонка, 3 - "свечение" через выключатель в дежурном режиме.

Тут и сказке конец, а кто слушал - молодец


UPD: на данный момент выключатель питается через 2 резистора 3.3МОм, шунтирующих симисторы, т.к. канала тоже два. Это позволило в 2 раза срезать ток, текущий через каждый канал в отдельности. Но на каждом канале должно быть хотя бы по одной лампе. Можно и недиммируемых, они не вспыхивают. Потребление доведено до величин, неразличимых невооруженным глазом :)

UPD2: подоспело видео

------------------ ч е р н о в и к --- на след.статью
 (как это выглядит на временной диаграмме, если у всех устройств должна быть одинаково настроена пауза между попытками передачи?)
Я даже подумываю об организации питания от небольших солнечных элементов в связке с ионисторами. Хотя.. Может обойтись просто хорошим ионистором и при первом включении просто дать ему зарядиться, включив свет хардварной скрытой кнопкой. Но это на будущее.

 - дежурное питание от солнечного элемента (для включения недиммируемых ламп без шунта)
  либо
 - дежурное питание от подзаряжаемого ионистора/аккумулятора

Представим, что один чип запросил наличие данных, роутер положил их в ACK_PLD, а другой чип влез в эфир и забрал их (как это выглядит на временной диаграмме, если у всех устройств должна быть одинаково настроена пауза между попытками передачи?), в итоге оказалось, что в пакете данных вовсе не его адрес, а первый чип в то же время остался без данных.
как гипотетически обстоят дела с параллельной работой 10-20 устройств? ведь пайпов всего 6
а еще если трафик большой будет, раз в 64мс например пинг

9 комментариев:

  1. Отличная трилогия! Спасибо!
    но... почему не на Си?

    ОтветитьУдалить
    Ответы
    1. а оч.просто.. я не умею :)
      да и как могут соседствовать такое стремление к энергосбережению и расточительство ресурсов?
      мне пришлось рефакторить код на асме, чтобы тот занимал меньше циклов, соотв-но камень меньше был в активном режиме и это реально влияло на напругу в схеме.

      Удалить
    2. И это прекрасно! Замучали Сишники со своими "ой, памяти в 30к оказалось недостаточно!..."

      Удалить
  2. Анонимный15 июня 2015 г., 22:36

    Думаю, схема то чистейшая классика , и не в ней вся соль, вся красота и кайф в хорошо проработанном софте...

    ОтветитьУдалить
  3. Отличная статья,ОГРОМНОЕ спасибо автору за проделанную работу,
    особенно за исходник на ассемблере (по нынешним временам большая редкость)
    но именно после примеров на ассемблерном коде автора многое стало понятно,ну а дальше уже было легче самому "копать" дальше.

    ОтветитьУдалить
  4. Здравствуйте Валерий. как с вами связаться. У меня есть работа для вас.

    ОтветитьУдалить
  5. - команда ACTIVATE с байтом данных 0x73

    Что это за команда ? Перекурил весь даташит, не могу понять.

    ОтветитьУдалить
  6. Начали за здравие, кончили за упокой.. Сначала вроде речь шла о питании от батареек а в итоге уже питание уже в разрыве цепи.
    В общем в первую очередь поздравляю, что выключатель все-таки готов.
    Во-вторых я перечитываю статью уже 3-й раз, но все-равно не все догоняю.
    Что за контроллер использовался? В какой IDE писал шрошивку? На каком языке? Мне прям надо повторить за тобой по части эффективного энергопотребления, но не могу перейти к прикладному искусству.

    ОтветитьУдалить