Предыстория.
Недавно на маркетплейсе мне попался любопытный набор «собери сам». Прибор начинал мигать, когда почва в горшке с цветком пересыхала и требовала полива. Он состоял всего из пары транзисторов, нескольких резисторов, потенциометра, светодиода, батарейки CR2032 и щупа для почвы. Стоил он раз в 10 дороже этих компонентов, поэтому, покупать его я, конечно, не стал 🙂 Но мне понравилась идея такого минималистичного прибора для забывчивых. Сначала я пытался воспроизвести схему по фотографиям в отзывах, но по мере вникания в вопрос выяснилось, что это все же больше игрушка, чем полезный прибор. Хотя он (судя по отзывам) работал, но на практике был малополезным, так как не учитывал особенности измерения сопротивления почвы, что приводило к неверному измерению сопротивления (чем длительнее пропускаем ток, тем сильнее растет сопротивление), быстрому высаживанию батареи и коррозии электродов в течение нескольких недель. Я начал вникать в суть дела, и нашел следующую информацию:
При подаче тока на электроды в почве возникают электрохимические реакции: на аноде — окисление металла электрода или воды с выделением кислорода; на катоде — восстановление воды с выделением водорода. Эти процессы создают противо-ЭДС (электродвижущую силу), которая противодействует току прибора. Омметр интерпретирует это как рост сопротивления. Ток вызывает миграцию ионов к электродам, образование зон с повышенной/пониженной концентрацией солей, осаждение продуктов реакций (например, оксидов) на поверхности электродов. Это увеличивает локальное сопротивление контакта «электрод–почва». Также есть и другие факторы, но электролиз — основной враг при измерении сопротивления почвы. В общем, об этом и в школе рассказывали, но я не думал, что даже мизерный ток в миллиамперы способен изменять состав почвы и разъедать электроды с большой скоростью.
Короче, была поставлена задача сделать аналог вышеописанного прибора, который будет измерять сопротивление почвы редкими короткими импульсами тока и будет способным работать длительное время от батарейки CR2032. При этом прибор не должен стоить как ракета, потому что у китайцев можно купить аналогичные готовые приборы за 300-600 рублей, хоть и без настроек под свой тип почвы и свои хотелки. Забегая вперед, я знаю про емкостные датчики почвы, которые не вызывают электролиз в почве, и у меня есть один, но это другая история, а в этой истории мне надо было пристроить уже купленный резистивный датчик и держатель батарейки CR2032, чтобы не валялись зря 🙂 Я также знаю, что дорогие профессиональные приборы используют для измерения переменный ток, который частично решает проблемы, вызванные использованием постоянного тока, но это выходит за рамки идеи о создании предельно простого, дешевого и энономного в плане токопотребления устройства.
Пропущу истории подбора компонентов, были мысли и на счет tlc555, и про ардуинки не забыл, но все это по различным причинам не соответствовало технической задаче, в частности, эномомному расходу батареи CR2032. В общем, перебрав в уме разные варианты, пошел на поклон к искусственному интелекту, и он послал меня… покупать микроконтроллер ATTINY13A. По его словам, это было именно то, что я искал — питание от 1.8 до 5.5 вольт, низкое токопотребление в режиме сна (меньше 5 мкА), приемлемое токопотребление в активном режиме (около 190 мкА), программируется в любимом лентяями Arduino IDE. Для этого требуется установить ядро Microcore, а также понадобится дешевый программатор для прошивки (обычно покупают китайский клон USBasp за 100 рублей, но для одноразового проекта можно использовать любую ардуинку в качестве программатора, нужно просто прошить в нее специальный код). Но есть у TINY13A и серъезные ограничения: всего 1 килобайт флеша для хранения кода и 5 выводов для подключения (вообще 6, но один используется как RESET при ISP программировании, если его задействовать, придется делать или покупать высоковольтный программатор, что для одноразового проекта нецелесообразно). Мне показалось, что пяти пинов и одного килобайта для простой мигалки должно хватить, как же я ошибался…
Раньше я не программировал голые микроконтроллеры, поэтому сначала был в шоке. Ради уменьшения размера кода, здесь не используются привычные функции типа digitalWrite(), вместо них производится прямая запись в регистры микроконтроллера, например PORTB |= (1 << PIN) ставит уровень HIGH на выбранный пин, а PORTB &= ~(1 << PIN) снимает высокий уровень с пина, и все остальное примерно так и делается. Сначала мне было очень неуютно в этом плавать, но потом я понял, что без этого просто не обойтись. Каждое использование переменных, каждый вызов функции, да вообще каждое действие резко увеличивает размер кода, и он просто не влезает в память. Под конец уже приходится выбирать, что выкинуть из проекта, а что оставить. Например, изначально в проекте был двухцветный светодиод, но из-за того, что код обработки второго светодиода не влезал в память, пришлось его выкинуть. Даже при этом памяти сильно нехватало. Приходилось делать вещи, о которых в обычном программировании в наше время никто не задумывается, а именно оптимизировать размер кода. Одно и то же действие можно выполнить разными способами, но каждый способ будет занимать разное количество байт кода, например, vcc_mv = vcc_mv/100 создает код на 34 байта больше, чем vcc_mv = ((vcc_mv << 16) / 100) >> 16. В общем, я сам в полной мере не понял всех оптимизаций, которые проделывает компилятор, но основная закономерность — стараться не использовать глобальные переменные и стараться не использовать функции, ни встроенные, ни свои — как ни странно и вопреки логике, но тупое повторение одного и того же кода несколько раз дает меньший размер, чем помещение этого же кода в функцию и вызов ее несколько раз. Под конец борьба шла уже за каждый свободный байт памяти. В какой-то момент мне это надоело, я оставил только то, что считал наиболее полезным для себя, а остальное выкинул. Все же, это было интересное знакомство с миром голых микроконтроллеров, я на своей шкуре прочувтсвовал работу программистов 1970-80 годов, когда борьба за каждый байт памяти была насущной необходимостью, а не прихотью, ведь память в те годы стоила дорого. В наше время микроконтроллеры с большим объемом памяти стоят ненамного дороже ATTINY13A, поэтому заниматься самоистязаниями в оптимизации кода только ради того, чтобы влезть в однокилобайтный лимит памяти уже нет смысла (разве что из спортивного интереса). Для проектов, требующих больше памяти, могу порекомендовать более совершенный ATTINY85 (8 килобайт флеш, 6 IO, но питание минимум 2.8 вольт, хотя есть более редкая версия ATTINY85V, с питанием 1.8 — 5.5V), если же нужно больше пинов, то ATTINY84A (тоже 8 килобайт, но 12 IO, работает и от 1.8 вольт). ATTINY814A — современный его аналог, программируется по одному проводу (USBasp не подойдет в качестве программатора). Если же и этого мало, попробуйте ATMEGA328P, на основе которого делают Arduino Uno/Nano. Это если Вам интересно программирование под простые «допотопные» микроконтроллеры фирмы ATMEL, которая в 2016 году была куплена компанией Microchip, но популярные старые модели микроконтроллеров все еще выпускаются под маркировкой ATMEL, либо успешно подделываются китайцами 🙂 Понятно, что за сравнимые деньги можно купить и современные микроконтроллеры вроде ESP32, STM, с существенно лучшими характеристиками — тут все зависит от поставленных задач и личных предпочтений.
Что покупать для проекта.
В зависимости о типа корпуса и заказываемого количества, один микроконтроллер ATTINY13A обойдется в 40-60 рублей, что недорого для его функционала. К примеру, голый классический ардуинный ATMEGA328P продают по цене от 140 рублей, что тоже недорого, но его возможности, да и размеры, для данного проекта избыточны. Мне показалось интересным вызовом использовать именно ATTINY13A по-максимуму, поэтому я заказал именно его. Китайские щупы для почвы стоят 40-50 рублей вместе с модулем компаратора LM393, который нам в этом проекте не пригодится, но компаратор всегда можно куда-то пристроить. Вообще, говорят, что эти щупы недолговечны, поэтому можно сэкономить и на них, заменив их двумя штырями из нержавейки, а еще лучше — из графита (их можно вынуть из севших солевых батареек АА). Остальные компоненты — светодиод, кнопка и резисторы — копейки, и они наверняка уже есть у тех, кто хоть немного занимается электроникой. Потенциометр можно не ставить, если заранее определите сопротивление сухой почвы и пропишите это значение в коде (в этом случае для перенастройки на другую почву может потребоваться перепрошивать прибор, поэтому на потенциометре лучше не экономить — можно ставить любой в разумных пределах, например от 200к до 1М). Плата для сборки — на свое усмотрение — схема простая, при желании можно и на куске тонкого пластика собрать 🙂 Я давно покупал набор из дырявых плат разных размеров, и по необходимости беру из того запаса, вот и сейчас плата 80x20mm пошла в дело. Конденсатор можно не ставить (будет нормально работать и без него, но считается хорошим тоном его ставить для фильтрации высокочастотных шумов и сглаживания пульсаций). Резистор 20к на RESET обычно ставят, но я проверил свой образец, и там стабильное VCC, то есть по ощущению микроконтроллер использует внутренний подтягивающий резистор, если пин 1 выполняет функцию ресета. На микросхемах, где это не так, на этом пине можно наблюдать какое-то непонятное напряжение, в таких случаях внешний подтягивающий резистор действительно необходим. Тут надо учитывать, что резисторы — это потребители энергии, пусть и небольшой. При питании от блока питания это не принципиально, но при питании от CR2032 нужно стараться избавляться от всего лишнего, без чего прибор может работать.
Принцип работы.
Большую часть времени микроконтроллер находится в режиме сна и потребляет мизерный ток меньше 5 мкА. Каждые 8 секунд микроконтроллер пробуждается, чтобы понять, что происходит. Тут возможны варианты.
1) Настало время замера. Посылаем короткий сигнал для измерения напряжений на делителе вольтметра и делителе сенсора, проводим измерения, выключаем сигнал, вычисляем нужные значения. Результаты измерений остаются в памяти, а контроллер уходит в сон до следующего события.
2) Настало время индикации. Индикация по таймеру работает только для сухой почвы (ради экономии батареи). Индикацию можно настроить на промежуток, кратный 8 секундам. Я выбрал 16 секунд, то есть когда почва станет сухой, светодиод будет мигать каждые 16 секунд (дважды, если батарея еще живая, или один раз, если батарея скоро сдохнет). Если Вы любуетесь растением меньше 16 секунд подряд и боитесь пропустить сигнал, то можно выбрать и 8 секунд, но это заметно скажется на длительности работы батареи, так как светодиод является основным потребителем тока в этой конструкции. В идеале, если растение постоянно перед глазами, можно выбрать и более длительный перерыв между миганиями, например 24 или 32 секунды, это еще лучшим образом скажется на долгожительстве батарейки. Мигнув, контроллер сразу же засыпает еще на 8 секунд.
Если пользователь нажал на кнопку, микроконтроллер просыпается сразу, не дожидаясь пробуждения по таймеру. Далее снова варианты.
1) Одиночное нажатие кнопки. Происходит принудительный замер, по той же схеме, что была описана выше. Но после этого следует индикация результата:
1 мигание — почва сухая, батарея разряжена.
2 минания — почва сухая, батарея в норме.
3 мигания — почва влажная, батарея разряжена.
4 мигания — почва влажная, батарея в норме.
Запомниль лекго — мало миганий — плохо, много миганий — хорошо. Когда батарея разряжена, это на 1 мигание меньше, чем когда батарея в норме, так мы немного продлим жизнь почти умершей батарейке.
После индикации результаты измерений остаются в памяти, а микроконтроллер снова засыпает.
2) Два нажатия подряд. Принудительный замер с индикацией сопротивления щупа+подстроечника (в килоомах) с помощью светодиода. Делается это так: каждая цифра промигивается соответствующее количество раз, далее идет пауза, далее — следующая цифра, и так все цифры, например 346 килоом будет показано как 3 мигания, пауза, 4 миганий, пауза, 6 миганий. Если в числе встречается ноль, он отображается одним очень быстрым мигом, за которым так же следует пауза. Данная система полезна для отладки и для получения представления о текущем состоянии почвы до того, как светодиод тревоги начнет мигать (чем ближе показываемое сопротивление к 500, тем скорее наступит время полива). Также, ради отладки или интереса, можно измерить сопротивление почвы или подстроченика отдельно. Чтобы измерить сопротивление почвы, выверните подстроечник до предела влево (на 0 ом). Чтобы измерить сопротивление подстрочника, замкните оба щупа чем-то металлическим, например, ножом, и проведите замер. Если не хотите извлекать щуп из почвы, измерьте чистое сопротивление почвы, переведите подстроечник на желаемый уровень, проведите измерение снова, из полученного значения вычтите сопротивление почвы. Ну или просто используйте мультиметр, если прибор не спрятан в закрытый корпус 🙂 В моих тестах прибор работал стабильно с тестовым резистором вместо щупа почвы, но не следует ожидать высокой точности замера сопротивления именно почвы, так как почва не является идеальным резистором, кроме того, пришлось немного пожертвовать точностью вычислений ради уменьшения размера кода. Впрочем, этот факт не мешает нам определять уровень влажности почвы, так как погрешность в несколько килоом не играет большой роли.
3) Три нажатия подряд. Принудительный замер с индикацией напряжения батарейки (в целых и десятых частях вольт). Например. 2.8 вольт промигает как два мигания, пауза, 8 миганий. Полезно, чтобы оценить остаток заряда в батарейке.
После полива растения рекомендуется нажать на кнопку, чтобы сразу провести замер уже политой почвы и отключить мигание, иначе мигание продлится до следующего замера по расписанию и будет дезинформировать и впустую высаживать батарейку.
Схема.
Конденсатор можно не ставить (будет нормально работать и без него, но считается хорошим тоном его ставить для фильтрации высокочастотных шумов и сглаживания пульсаций). Резистор 20к на RESET обычно ставят, но я проверил свой образец, и там стабильное VCC, то есть по ощущению микроконтроллер использует внутренний подтягивающий резистор, если пин 1 выполняет функцию ресета. На микросхемах, где это не так, на этом пине можно наблюдать какое-то непонятное напряжение, в таких случаях внешний подтягивающий резистор действительно необходим. Тут надо учитывать, что резисторы — это потребители энергии, пусть и небольшой. При питании от блока питания это не принципиально, но при питании от CR2032 нужно стараться избавляться от всего лишнего, без чего прибор может работать.
Все выводы микросхемы, кроме одного, используются в проекте. Я не хотел освобождать первый вывод RESET от его прямых обязанностей, чтобы не терять возможность легко и быстро менять прошивку, поэтому сигнал на делитель напряжения для вольтметра и для датчика почвы подается с одного вывода 7. В идеале лучше бы задействовать для этого два вывода, но поскольку обе цепи имеют огромное сопротивление в сотни килоом, они не вызывают просадку напряжения на батарейке и не имеют существенного влияния друг на друга (проверено).
Вольтметр.
Зачем вообще понадобился вольтметр в таком простом приборе? Дело в том, что график разряда батареи CR2032 нелинейный. В начале использования батареи имеется быстрый провал с 3.2 до 3 вольт, потом напряжение постепенно падает примерно до 2.8-2.6 вольт, после чего наблюдается быстрое уменьшение напряжения до 1.8 вольт. Нас это устраивает и без стабилизатора, так как ATTINY13A прекрасно переносит напряжения от 1.8 до 5.5 вольт, а мой образец неплохо работает и от 1.6 вольт, хотя светодиода при этом уже почти не видно. Мы хотим выжать из батарейки все до последней капли, но при разном напряжении и ток, протекающий через почву, будет разным. Чтобы корректно измерять сопротивление почвы по формуле R=U/I, нам нужно знать, какое напряжение мы используем, для этого и сделан вольтметр. Вот если бы мы питались от стабилизированного источника напряжения, то мудрить с вольтметром не потребовалось бы, мы могли бы просто использовать в формуле известное напряжение источника питания. Для измерения напряжения используем выход с делителя напряжения 470k/100k. Такой делитель позволяет измерить напряжение до 6 вольт. Я сделал это для тестов, чтобы тестировать прибор непосредственно при питании от USB. При желении можно подобрать резисторы именно для 3.3 вольт для более точного измерения, но мой опыт показал, что и так измеряет достаточно точно (три цифры как на мультиметре, четвертая меняется в зависимости от того, на каком напряжении откалиброван прибор). Измерение производится следующим образом: на вход ADC с установленным внутренним референсом (~1.1 вольт) подается напряжение питания с делителя. Для точных рассчетов важно внести в код точные сопротивления именно Ваших резисторов, используемых в делителе, так как разброс значений у китайских резисторов очень большой. Также нужно вычислить напряжение референса именно Вашего чипа, потому что оно разнится в разных экземплярах. У меня, например значение 1.034V дает максимально точные измерения, соответсвующие эталонному вольтметру. Для вычисления референса можете замерить какое-нибудь стабилизированное напряжение, например 3.0V, и вычислить значение референса по формуле, либо просто подогнать, проверяя показания вольтметра тройным нажатием на кнопку и изменяя значение #define REF_MV 1034UL чуть больше или меньше, пока прибор не промигает ровно 3 вольт. Для этого лучше временно отключить в коде деление результата напряжения в милливольтах на 100, чтобы видеть все 4 цифры, которые выдает вольтметр в милливольтах.
Сенсор.
Чтобы порог влажно/сухо не был жестко запрограммирован в коде (это сильно зависит от состава почвы), в цепь последовательно с щупом добавлен потенциометр на 500к. Сопротивление щупа в насыщенной водой земле около 5к-20к, в нормальной земле 50к-150к, в подсыхающей земле — 150к-300к, в сухой земле — от 500к до нескольких мегаом. В коде указан предел сопротивления, при котором почва уже не считается влажной, по умолчанию 500к (#define THRESHOLD_PROBE 500). Таким образом, подкручивая потенциометр, можно настраивать оповещение на более ранее срабатывание сигнала, когда почва еще не совсем пересохла.
Пример. Допустим, мы хотим сигнал при сопротивлении почвы 200к. Чтобы сигнал сработал на установке 500к, нам необходимо добавить на потенциометре еще 300к.
Все это подбирается экспериментально без омметра: дожидаемся, пока почва высохнет до нужного уровня, проверяем сигнал с прибора:
— если изначально 2 мига, крутим подстроечник против часовой стрелке, пока не будет 4 мига, и слегка назад по часовой, чтобы стало 2 мига.
— если изначально 4 мига, крутим подстроечник по часовой стрелке, пока не будет 2 мига.
В теории все должно работать так длительное время без коррекций, так как мы программно измеряем VCC батарейки и учитываем падение напряжения при ее разрядке, но мир не идеален, и сопротивление щупа со временем будет расти, поэтому периодические проверки и подстройки вышеуказанным способом все же придется выполнять. Регулярность тестирования показаний зависит от качетсва щупа — самый дешманский с электродами из чермета сгниет быстрее такого же с золочеными контактами, ну а щупы из графита могут использоваться годами во влажной почве без потери свойств. Для предотвращения накопления эффекта поляризации и прочих химических эффектов в длительной перспективе, можно после замены батарейки переставлять устройство с одной стороны горшка на другую.
UPPER_RESISTOR_PROBE 470 подобран для оптимального диапазона ADC (30mV-1.48V для 5к-500к при 3.0V питании). Импеданс выхода с делителя при сопротивлении почвы около 500к получается сотни килоом, что значительно больше рекомендованного в датащите 10к, но прибор все равно измеряет точно. Я проверил разные резисторы в диапазоне от 10к до 470k, и прибор показывал реальные значения при напряжении от 1.6 до 5.1 вольт, правда примерно на 2к меньше того, что показывал мой мультиметр. Я не стал заморачиваться и выяснять причину (вероятнее всего, погрешности целочисленных делений килоом, кроме того, значения резисторов немного больше или меньше указанных значений, но округлены до целых). Так как расхождение линейное на всем диапазоне, просто добавил #define COMPENSATION_PROBE 2 для компенсации, и все стало измеряться верно.
Справедливости ради стоит заметить, что делитель напряжения подбирался для оптимального измерения сопротивлений до 500к, а при измерении больших сопротивлений погрешность будет расти, например, 688к определяется как 685к (разница 3к), а 2.010 мегаом определяется уже как 2.004 мегаом (разница уже 6к), но для данного конкретного прибора такие расхождения в диапазоне больше 500к-1000к не имеют принципиального значения, так как его дело — отследить переход через 500к, а как будет расти сопротивление после — уже ни на что не влияет.
Если по какой-то причине нужно считать влажной почву при сопротивлении больше 1 мегаома, в коде нужно изменить предел, например #define THRESHOLD_PROBE 2000 (больше я не пробовал, но в теории должно работать, хотя погрешность в измерении сопротивления будет увеличиваться. При измерении сопротивлений больше 1М, возможно, положительно скажется замена верхнего резистора делителя с 470к на 200к-300к — это уменьшит рабочий диапазон ADC, но также уменьшит и импеданс делителя, что может оказаться более позитивным даже при меньшем рабочем диапазоне ADC. Впрочем, я не заметил существенного влияния завышенного в 25 раз импеданса на качество измерений. Учитывая, что мы не можем позволить себе вычисления с плавающей точкой из-за ограничений памяти, и используем только целочисленные вычисления в килоомах, возможно, что влияние высокого импеданса на точность вычислений просто меньше, чем округления целочисленных операций, и мы не можем их заметить. А раз так, то и заморачиваться на эту тему не стоит 🙂 Не стоит забывать, что низкий импеданс увеличивает токопотребление, что негативно скажется на продолжительности работы прибора от батарейки, а также ускорит процесс электролиза в почве, поэтому в данном случае лучше искать некий компромисс между точностью замера и токопотреблением.
Светодиод.
Токоограничивающий резистор подбирается экспериментально под конкретый светодиод. Подключаете 3 вольта к светодиоду через резистор 1К и амперметр и смотрите, чтобы с данным резистором ток был не сильно больше 1 мА (я не рекомендую больше для CR2032). Также смотрите, чтобы при 1.8-2 вольтах светодиод хоть как-то был виден. Если устраивает — оставляете, если не устраивает, пробуете ближайшие по сопротивлению резисторы, например, 680 Ом для более яркого свечения. Я выбрал 3 мм светодиод, так как он светит ярче, чем 5 мм светодиод при том же токопотреблении.
Кнопка.
Время BUTTON_DEBOUNCE_MS 25 подбиралось осциллографом по своей кнопке (с небольшим запасом), но кнопки бывают разные. Если определение количества нажатий глючит, попробуйте увеличить это значение.
Сборка.
Прототип для тестирования удобно собрать на макетной плате:
Для финальной сборки удобно использовать готовую дырявыю плату 20×80 мм, так как она имеет ту же ширину, что и датчик влажности почвы. По длине тоже места хватает.
Корпус можно не делать, если нравистся смотреть на электронику. Бюджетный вариант — надеть широкую термоусадку на всю длину и усадить, прорезав только место для замены батарейки. Я же смоделировал корпус в FreeCAD и напечатал его на 3D-принтере. Модель можно скачать здесь.
Как часто и какой длительностью делать замеры?
После прекращения подачи тока поляризация и локальная ионизация обычно исчезают в течение минут или часов, в зависимости от типа почвы. В сухих песчаных почвах — быстрее (секунды-минуты), в влажных глинистых или солончаковых — медленнее (часы-дни). Если производить замеры 1-2 раза в сутки, эффекты от предыдущего замера минимизируются или полностью исчезнут. Какая длительность импулься оптимальна? Если посмотреть на осциллограмму замера сопротивления обычного резистора, можно видеть, что напряжение, проходящее через резистор, стабилизируется практически моментально и не меняется дальше.
С почвой и водой это так не работает. Если посмотреть на осциллограмму замера сопротивления почвы, мы увидим резкий подъем напряжения с его последующей медленной стабилизацией — это типично для почв, которые обладают не только проводимостью, но и емкостными свойствами. Быстрый подъем в начале измерения — это зарядка паразитной емкости между электродами и почвой. Почва ведет себя как конденсатор, и напряжение растет быстро, пока не достигнет уровня, соответствующего емкостному сопротивлению. Это не отражает истинное удельное сопротивление почвы. После зарядки емкости напряжение стабилизируется, и дальнейшее изменение связано с истинной проводимостью почвы, включая эффекты поляризации (электролитические процессы в почвенной влаге) или диффузии ионов. Причем в сухой и влажной почве стабилизация занимает разное количество времени. Я провел несколько замеров с питанием 3.3 вольт без дополнительного последовательного потенциометра. Стоит заметить, что осциллограф бюджетный и не радует точностью замеров напряжения, но форму сигнала и с помощью такого можно понять.
Начнем исследнование с измерения сопротивления сухой почвы, которую не поливали пару недель, она рассыпчатая и совершенно не слипается. Замерено 188К.
На осциллограмме видим, что за 6 секунд замера напряжение стабилизировалось на отметке 850 милливольт. Давайте замерим, за какое время можно измерить сопротивление этой почвы с некоторой погрешностью:
почти стабильно, 840 мВ, 2.5 секунд.
погрешность 5%, 807 мВ, 392 мс.
погрешность 10%, 765 мВ, 120 мс.
Теперь польем обильно землю, и проведем тот же тест. Поскольку почва ведет себя иначе, продлим тест до 18 секунд. Замерено 25К.
Здесь мы видим, что даже спустя 15 секунд напряжение еще не до конца стабилизировалось, и все еще немного продолжает расти. Но давайте примем за более или менее стабильное значение 184 мВ и расчитаем время, необходимое на замер сопротивления с некоторой погрешностью:
стабильно 184мВ, 10 секунд.
почти стабильно 182 мВ, 3.2 с.
погрешность 5%, 175 мВ, 1.9 с.
погрешность 10%, 166 мВ, 960 мс.
погрешность 15%, 156 мВ, 680 мс.
погрешность 20%, 147 мВ, 560 мс.
погрешность 25%, 138 мВ, 460 мс.
Как видно, стабилизация в сухой и влажной почве требует разного времени, и если принять ошибку в измерениях 10% допустимой, то нам понадобится 120 мс, чтобы измерить сопротивление сухой почвы и целых 960 мс (в 8 раз больше), чтобы измерить сопротивление влажной почвы. Понятно, что мы заранее не знаем, какую почву измеряет прибор, поэтому берем наихудший сценарий. Почему бы не заморачиваться и не измерять столько, сколько требуется для определения более или менее точного значения, например, 10 секунд? Тут есть две причины. Одна очевидна — более быстрый разряд батареи во время замеров, а именно почти в 10 раз быстрее по сравнению с замером длительностью 1 секунда. Вторая причина — химические процессы усиливаются при увеличении времени воздействия на почву электрическим током. Поэтому, однозначно сложно сказать, какие значения выбрать. В профессиональных приборах измерения сопротивления заземления типа Fluke, например, цикл замера со стабилизацией занимает 5-20 секунд. Для долгосрочного мониторинга рекомендуют паузы в 10–60 минут между измерениями, чтобы почва «отдохнула». Но у нас не профессиональный прибор, и несколько килоом не играют большой роли, когда разница между сопротивлением сухой и влажной почвы — сотни килоом. Поэтому я предпочитаю путь минимизации электрохимических реакций в почве в ущерб точности измерения сопротивления влажной почвы, таким образом, время стабилизации по умолчанию установлено на 1 секунду, а интервал замеров — 2 раза в сутки. В коде это настраивается через MEASUREMENT_INTERVAL и MEASUREMENT_STABILIZATION_DELAY_MS, так что желающие поиграться с осциллографом и изучить особенности своей почвы могут установить длительность сигнала хоть на 1 минуту и смотреть, что за это время будет происходить. В любом случае, не рекомендую проводить замер более 10 секунд, просто потому, что в этом нет никакой необходимости — мы все-таки делаем измеритель влажности почвы, а не сертифицированный омметр 🙂
В итоге, стабилизация 1 секунда плюс замеры с усреднением 0.2 секунд при питании от батарейки cr2032 (3 вольт) и потенциометром, установленном на 300к выглядит на осциллограмме следующим образом: замерено 126 мВ, 333к (300к потенциометра и 33к почвы).
Токопотребление.
Питание во сне 4.9 мкА.
Пробуждение раз в 8 секунд 5.1 мкА.
Замер напряжения батареи и сопротивления почвы 0.56 мА.
Мигание светодиодом ~1 мА.
Иное.
Что не написал здесь, смотрите комментарии в коде, там их много.
Я хотел самое простое и дешевое устройство, я его сделал. Мне это было интересно больше ради нового опыта и впечатлений, нежели ради получения самого устройства. Вам же может быть интересно изменять конструкцию под свои хотелки, например, заменить контроллер на иной с большим количеством памяти и выводов, добавить многоцветный светодиод, может даже автополив или OLED дисплей с более наглядным выводом собранной информации. Можно переделать устройство для работы с Li-Ion или иным аккумулятором (1.8-5.5 вольт можно подключать к ATTINY13A без стабилизатора). Если захотите использовать ESP32, то можно высылать оповещения о необходимости полива по Wi-Fi. В общем, тут варианты ограничены только Вашей фантазией. Ну а емкостной датчик я буду использовать чуть позже, но и с ним тоже не все так гладко, так как мой образец дает линейные показания только в интервале напряжений от 3.6 до 5 вольт, то есть CR2032 явно не будет для такого устройства идеальным источником питания, да и в интернете поговаривают, что показания с него тоже плавают непонятно от чего, так что надо наблюдать и разбираться.
Скетч.
Скачать скетч (программу для ATTINY13A) можно здесь. Далее просто для ознакомления.
// Soil Moisture Sensor based on ATTINY13A (http://tol.acritum.com).
// Compiled with Arduino IDE 2.3.6, Microcore 2.5.2.
// Предупреждение!!! некоторые китайские клоны USBasp продаются со старой прошивкой, которая не поддерживает низкие скорости процессора, такие как 128 килогерц.
// Точнее, первый раз вы запишите программу, но потом программатор не будет видеть микроконтроллер. Решение: прошить сам USBasp последней версией прошивки (для
// этого понадобится какая-нибудь ардуинка или другой программатор). Либо используйте специальный модифицированный код ArduinoISP для записи на низкой скорости.
// Простое решение: не ставить частоту tiny13 меньше той частоты, которую поддерживает Ваш программатор. Точно не помню, где предел старой версии, но 1 мегагерц
// должно быть безопасно. Если меняете частоту, меняйте и prescaler для ADC (читайте датащит для подбора).
// Настройки Microcore в Arduino IDE, меню Tools:
// BOD: disabled
// Bootloader: no bootloader
// Clock: 128kHz internal
#define F_CPU 128000 // частота 128 килогерц — очень низкое токопотребление
// назначение выводов ATTINY13A
#define BUTTON_PIN PB1
#define VM_ADC_PIN PB4 // ADC2, pin3
#define SENSOR_ADC_PIN PB3 // ADC3, pin2
#define VOLTAGE_SIG_PIN PB2
#define RED_LED PB0
#define BUTTON_TIMEOUT_MS 300 // Таймаут в мс для ожидания следующего нажатия на кнопку
#define BUTTON_DEBOUNCE_MS 25 // Дребезг для press/release
// Интервалы в периодах по 8 сек (циклах WDT)
#define MEASUREMENT_INTERVAL 5400 // 2700 циклов = проверка каждые 6 часов, 5400 циклов = каждые 12 часов, 10800 циклов = раз в сутки.
#define MEASUREMENT_STABILIZATION_DELAY_MS 1000 // стабилизация между подачей сигнала и первым замером.
#define RED_BLINK_INTERVAL 2 // 1 цикл = 8 секунд, 2 цикла = 16 сек, 3 цикла = 24 сек и т.д.
// Определения для ADC
#define REF_MV 1034UL // типично 1.1 вольт, но настраивается (или подгоняется) под конкретный микроконтроллер, так как разброс большой /мой 1.034V
#define ADC_RESOLUTION 1024 // разрешение ADC
#define ADC_PRESCALER_BITS 0 // Прескалер /2. Частота ADC clock: 128 кГц / 2 = 64 кГц (в пределах 50-200 кГц по datasheet),
#define LOW_BATTERY_MV 1800 // напряжение батареи (милливольты), при котором изменяем тип сигнализации. изделие будет работать и при 1.5-1.6V, но светодиод практически не видно
// Делители напряжения, указывать килоомы.
#define UPPER_RESISTOR_PROBE 475 // сопротивление верхнего резистора в делителе для щупа
#define THRESHOLD_PROBE 500 // при сопротивлении щупа+подстроечника выше этого значения, почва считается сухой
#define COMPENSATION_PROBE 2 // компенсация измеренного сопротивления, если врет линейно на всем измеряемом диапазоне (плюс/минус килоом, а если не врет, ставим 0)
#define UPPER_RESISTOR_VM 476 // верхний резистор делителя для вольтметра
#define LOWER_RESISTOR_VM 100 // нижний резистор делителя для вольтметра
#define RATIO_NUM ((LOWER_RESISTOR_VM * 1000UL) / (UPPER_RESISTOR_VM + LOWER_RESISTOR_VM)) // используется для рассчета напряжения относительно внутреннего референса.
#define MEASUREMENTS_COUNT 32 // сколько замеров ADC для усреднения результата.
uint8_t status; // Состояние: 1 — сухо и разряж. бат, 2 — сухо и норм. бат, 3 — влажно и разряж бат, 4 — влажно и норм бат
volatile bool button_pressed = false; // Флаг нажатия кнопки
volatile uint16_t cycle_count = 0; // считаем циклы по 8 секунд, чтобы узнать, когда мерить и когда мигать.
// Пробуждение от WDT
ISR(WDT_vect) {
cycle_count++;
wdt_reset(); // сбросить WDT в ISR, чтобы предотвратить сброс после прерывания
}
// PCINT1 прерывание по кнопке
ISR(PCINT0_vect) {
if (!(PINB & (1 << BUTTON_PIN))) { // Только если кнопка нажата (низкий уровень)
button_pressed = true;
}
}
// мигнуть светодиодом
void blink_led(uint8_t count=1) {
if (count==0)
{
// Включить светодиод
PORTB |= (1 << RED_LED);
_delay_ms(10);
// Выключить
PORTB &= ~(1 << RED_LED);
_delay_ms(300);
} else
for (uint8_t i = 0; i< count; i++)
{
// Включить светодиод
PORTB |= (1 << RED_LED);
_delay_ms(200);
// Выключить
PORTB &= ~(1 << RED_LED);
_delay_ms(300);
}
}
// промигать число (допустимы числа от 0 до 9999)
void blink_value(uint16_t value) {
// далее извращенный вариант следующего, чтобы уменьшить размер скомпилированного кода
/*
uint8_t thousands = value / 1000; // Тысячи (0-9)
uint8_t hundreds = (value / 100) % 10; // Сотни
uint8_t tens = (value / 10) % 10; // Десятки
uint8_t units = value % 10; // Единицы
*/
uint8_t digits[4];
uint16_t temp = value;
for (int i = 3; i >= 0; i—) {
digits[i] = temp % 10;
temp /= 10;
}
// Находим первую ненулевую цифру и мигаем с неё
for (uint8_t i = 0; i < 4; i++) {
if (digits[i] != 0) {
for (uint8_t j = i; j < 4; j++) {
blink_led(digits[j]);
_delay_ms(600); // Пауза между цифрами 300+600=900 мс — в 3 раза длиннее, чем между мигами внутри цифры
}
break;
}
}
}
// чтение ADC
uint16_t adc_read(uint8_t channel) {
// Эти значения из даташита ATtiny13A: ADMUX биты 7-0
// REFS0 = 0=vcc, 1=internal_1v1
// MUX1:MUX0 = 00=adc5, 01=adc1, 10=adc2, 11=adc3
if (channel==3) //SENSOR on ADC3 , VCC ref
{
ADMUX = (0 << REFS0) | // REFS0=0
(1 << MUX1) | // MUX1=1
(1 << MUX0); // MUX0=1
}
else {
// VM ON ADC2, 1.1v reference
ADMUX = (1 << REFS0) | // REFS0=1
(1 << MUX1) | // MUX1=1
(0 << MUX0); // MUX0=0
}
// Начинаем замеры, пропускаем 2 первых замера. Почему 2? Потому что после запуска ADC в датащите рекомендуется
// задержка для стабилизации и пропуск первого неточного измерения, но на лишний delay у нас не хватает памяти, поэтому лишний замер — это вместо задержки!
// Проводим измерения и усредняем результат
uint32_t sum = 0;
for (uint8_t i = 0; i < MEASUREMENTS_COUNT+2; i++)
{
// Запуск преобразования
ADCSRA |= (1 << ADSC);
while (ADCSRA & (1 << ADSC));
if (i>1) sum += ADC;
}
return sum / MEASUREMENTS_COUNT;
}
// Основная функция измерения
void measure(uint8_t buttonmode) {
// сигнальный пин как выход
DDRB |= (1 << VOLTAGE_SIG_PIN) ;
// Подать сигнал
PORTB |= (1 << VOLTAGE_SIG_PIN);
PRR = (1 << PRTIM0) | (0 << PRADC); // вывести ADC из энергосбережения
// Включить ADC
ADCSRA |= (1 << ADEN);
_delay_ms(MEASUREMENT_STABILIZATION_DELAY_MS); // Стабилизация
// ИЗМЕРЕНИЕ ПОКАЗАНИЙ
uint16_t avg_adc_sensor = adc_read(3);
uint16_t avg_adc_vcc = adc_read(2);
// Отключить сигнал
PORTB &= ~(1 << VOLTAGE_SIG_PIN);
// Сигнальный пин — установить как вход без подтяжки для экономии батареи
// согласно датащиту, цифровые пины, сконфигурированные на вход, отключены во время сна, кроме тех, что вызывают прерывание.
DDRB &= ~((1 << VOLTAGE_SIG_PIN ));
//PORTB &= ~((1 << VOLTAGE_SIG_PIN)); не обязательно, так как подтяжка не включена по умолчанию
// Отключить ADC
ADCSRA &= ~(1 << ADEN); // отключить ADC
// напряжение батарейки
uint32_t vcc_mv = (avg_adc_vcc * REF_MV * 1000) / (ADC_RESOLUTION * RATIO_NUM);
// напряжение на делителе щупа
uint32_t v_probe_mv = avg_adc_sensor * vcc_mv / ADC_RESOLUTION;
// Расчет probe_resistance (сопротивление щупа+подстроечника в килоомах)
uint32_t probe_resistance = (UPPER_RESISTOR_PROBE * v_probe_mv / (vcc_mv — v_probe_mv)) + COMPENSATION_PROBE;
if (probe_resistance<=THRESHOLD_PROBE)
status = (vcc_mv<=LOW_BATTERY_MV) ? 3:4; //влажно
else status = (vcc_mv<=LOW_BATTERY_MV) ? 1:2; //сухо
vcc_mv = ((vcc_mv << 16) / 100) >> 16; // экономично делим на 100 с усечением лишнего (vcc_mv = vcc_mv/100), если юзеру нужно промигать, например 2684mV=2.6V = два и шесть миганий
// индикация результата только если нажимали на кнопку
if (buttonmode==2) blink_value(probe_resistance); else
if (buttonmode==3) blink_value(vcc_mv); else
if (buttonmode==1) blink_led(status);
}
int main(void) {
// Настройка портов
DDRB |= (1 << VOLTAGE_SIG_PIN) | (1 << RED_LED); //выходы
DDRB &= ~((1 << BUTTON_PIN) | (1 << VM_ADC_PIN) | (1 << SENSOR_ADC_PIN)); // входы
PORTB |= (1 << BUTTON_PIN); // Pull-up для кнопки
// PCINT: только для кнопки
GIMSK |= (1 << PCIE);
PCMSK |= (1 << PCINT1); // PCINT1 (PB1)
PRR = (1 << PRTIM0) | (1 << PRADC); // Отключить таймер0 и ADC
// Установка prescaler ADC
// Для /2 (bits = 0)
ADCSRA = (ADC_PRESCALER_BITS & 0x07);
//ADCSRA &= ~(1 << ADEN); // отключить
// wdt_reset(); // Сброс WDT
WDTCR = (1 << WDCE) | (1 << WDE); // Разрешение изменения
WDTCR = (1 << WDTIE) | (1 << WDP3) | (1 << WDP0); // Interrupt mode, 8 сек
sei(); // Глобальное разрешение прерываний
//_delay_ms(1000);
bool just_button_press=true; // сделать первый замер при включении
while (1) {
uint8_t buttonmode = 0; // Переменная для режима кнопки
if (button_pressed) {
_delay_ms(BUTTON_DEBOUNCE_MS);
if (PINB & (1 << BUTTON_PIN)) {
button_pressed = false;
continue;
}
buttonmode = 1;
// Ждем release первого
while (!(PINB & (1 << BUTTON_PIN))) _delay_ms(5);
_delay_ms(BUTTON_DEBOUNCE_MS);
// Цикл для дополнительных нажатий
for (;;) {
uint16_t timeout = 0;
while (timeout < BUTTON_TIMEOUT_MS && (PINB & (1 << BUTTON_PIN))) {
_delay_ms(5);
timeout += 5;
}
if (timeout >= BUTTON_TIMEOUT_MS) break;
_delay_ms(BUTTON_DEBOUNCE_MS);
if (!(PINB & (1 << BUTTON_PIN))) {
if (++buttonmode > 3) buttonmode = 3;
while (!(PINB & (1 << BUTTON_PIN))) _delay_ms(5);
_delay_ms(BUTTON_DEBOUNCE_MS);
} else {
break;
}
}
just_button_press = true;
button_pressed = false;
}
if ((just_button_press) or (cycle_count >= MEASUREMENT_INTERVAL))
{
measure(buttonmode);
cycle_count = 0;
just_button_press = false;
}
else
{
// мигаем по таймеру только если сухо
if (cycle_count % RED_BLINK_INTERVAL == 0)
if (status<3) blink_led(status);
}
wdt_reset(); // сбросить WDT, чтобы после нажатия на кнопку мигания по таймеру раньше времени не мешали считывать данные со светодиода
// Блок низкоуровневого сна в PWR_DOWN режиме, позводяет сэкономить ~20 байтов флеша, заменяет следующее:
/*
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable(); // Разрешить сон
sleep_mode();// Сон
sleep_disable(); // После пробуждения: Отключить сон
*/
PRR = (1 << PRTIM0) | (1 << PRADC); // Отключить таймер0 и ADC для экономии энергии
MCUCR = (MCUCR & 0xE7) | 0x30; // Установить PWR_DOWN (SM1=1, SM0=0) и SE (Sleep Enable) одновременно, сохраняя остальные биты MCUCR
__builtin_avr_sleep(); // Выполнить sleep (инструкция SLEEP)
//проснулись?
MCUCR &= ~(1 << SE); // Сбросить SE после пробуждения
}
return 0;
}













Leave a Reply