Как стать автором
Обновить

ATtiny85: прототип беспроводного сенсора

Время на прочтение14 мин
Количество просмотров63K

Обычно, для перехода от идеи к реализации, необходим прототип устройства, удобный для проверки и отладки на месте, что особенно важно для мобильного устройства. Далее постараюсь максимально подробно разобрать процесс создания прототипа беспроводного сенсора на базе ATtiny85.





Цель — создать сенсор работающий, условно говоря, в коробке с искусственным освещением и передающий температуру и статус освещения с немедленной реакцией на изменение освещения: включилось, отключилось, мигнуло. Сенсор решено было сделать мобильным и питать от элемента CR2032, иначе говоря, при разряде до 2.7V (предел для датчика TMP36), можно рассчитывать на 200mAh.



Микроконтроллер ATtiny85 имеет всего 5 портов ввода/вывода и возможность отключить RESET в пользу дополнительного порта. Данный бюджет был распределён следующим образом:



  • 3 порта — радиомодуль NRF24L01+, спецификация требует пять портов, но в данном случае это не приемлемо и будет использована 3-х пиновая конфигурация;
  • 1 порт — датчик освещения на базе фототранзистора BPW17N;
  • 2 порта — температурный датчик на базе TMP36, второй порт нужен для подачи питания, чтобы иметь возможность отключать датчик при необходимости.


Элементная база определена, можно приступать к проектированию.





RESET в качестве порта ввода/вывода



Ситуация требует использовать RESET в качестве порта ввода/вывода. Однако RESET необходим для программирования микроконтроллера, в том числе и для прошивки настроек (Fuses). Соответственно, после отключения RESET программировать устройство будет не возможно. Таким образом, чтобы не получить «кирпич» первым делом необходимо раздобыть высоковольтный (12V) программатор, для сброса Fuse-ов.



Подходящую схему и скетч для Arduino можно найти по ссылке ATTiny Fuse Reset with 12 Volt Charge Pump. Данная схема не требует дополнительного питания, и для получения 12V используется умножитель (Charge Pump).



Описание работы схемы высоковольтного программатора

Схема находится на сайте по ссылке, расположенной выше.



На вывод (4) подается питание 5V, при этом на выводе (2) устанавливается низкий уровень (земля), конденсатор C1 заряжается до 5V. Затем на (2) подается 5V и на обкладках С1 образуются 10V, они через диод D2, при низком уровне на (3), попадают на C2. Затем уровни на (3) и (2) меняются местами и уже 15V поступают на C3, следующая итерация сливает 20V (на самом деле ~17V) на C4. Затем напряжение через делитель R2/R3 попадает на аналоговый вход A0, где сравнивается с опорным значением, и если напряжение выше 12V, то раскачка (смена уровней на (2) и (3)) выключается. Транзистор Q1 нужен для быстрой разрядки конденсаторов и формирования необходимого импульса для программатора. Резисторы на выводах ATtiny85 необходимы для защиты микроконтроллера при неправильном подключении.




У меня получился следующий дизайн:





Высоковольтный программатор в сборе (фото и комментарии)


Неудобно, что USB разъём накрывает контактные пины, но дизайн платы таков, что здесь к микроконтроллеру сложно даже штатное питание подвести. Проще говоря, было место на плате, появилась идея добавить пины для подключения обычного программатора в следующей конфигурации:



Позже стало очевидно, что использовать схему в таком виде целесообразно только в самом крайнем случае.




Принципиальная схема



Разработка схемы вместе с макетной платой будет производится в KiCad EDA, это свободный пакет для автоматизации разработки электронных схем.



Исходные файлы KiCad проекта размещены на GitHub, в папке attwlight_sensor.pcb.



Существует множество хороших инструкций по работе с данным редактором, например стоит посмотреть ролик на YouTube KiCAD Quick-Start Tutorial.



Если совсем кратко, то для начала работы над схемой в KiCad достаточно запустить редактор схемы и справа пройтись по иконкам, чтобы добавить: элементы , питание , провода и метки .



Метки нужны, чтобы не загромождать схему проводами. При разводке печатной платы все одинаковые метки необходимо будет соединить дорожками.



Если в доступных библиотеках нет необходимого элемента его можно создать с нуля либо изменить один из существующих. В последнем случае достаточно поставить курсор на нужный элемент и нажать Ctrl+E (редактировать элемент в редакторе) и в новом окне: создать элемент из текущего , отредактировать его внешний вид и свойства , затем сохранить в новую библиотеку .



Чтобы созданную библиотеку можно было использовать, её необходимо подключить в Preferences → Component Libraries, воспользовавшись кнопкой Add в верхней части. Для добавления в библиотеку других элементов, её нужно выбрать и после добавления сохранить .



Итоговая схема:





По сути, устройство состоит из трех независимых частей. Проверив их по отдельности вполне можно ожидать, что в сборе всё заработает как было задумано. Прототип как раз и нужен, чтобы проверить насколько данное ожидание соответствует действительности. Ниже, в комментариях к схеме, я остановлюсь на том, что пошло не так.



Датчик температуры TMP36



В рабочем режиме питание на датчик подается с порта PB5 микроконтроллера. В отладочной конфигурации подать питание на датчик можно установив перемычку между выводами VCC и D10 контактной площадки P2. Сигнал RESET активен при низком уровне и высокий уровень не мешает работе микроконтроллера и даже увеличивает стабильность по сравнению с висящим в воздухе RESET. Для съема данных с датчика используется аналоговый порт ADC2.



Засада ждала в виде ёмкости C1, которая была подключена непосредственно к RESET микроконтроллера. Понятно, что емкость на RESET снижает время активности сигнала или даже блокирует его, мешая программированию устройства, поэтому до сборки был проведен эксперимент, но как оказалось не достаточно чистый. Дело в том, что если на работающую схему добавить данный конденсатор, то процесс проходит успешно ровно один раз, если включить схему с ним — то не разу. Пока дело дошло до конденсатора, была впаяна перемычка между D1 и SCK, очевидно, что эта часть схемы не мешает установке на SCK низкого уровня, но подозреваемых больше не было. После установки перемычки между C1 и землей, программировать устройство стало можно, но только с питанием 5V. Однако в инструкции указано 1.8-5.5V и эксперименты с этим микроконтроллером и питанием 3V были успешными. Оказалось что на питании TMP36 есть встроенная ёмкость 10nF и этого достаточно, чтобы блокировать процесс при низком напряжении. В результате перемычка встала на своё место.



Радиомодуль NRF24L01+



Нумерация выводов у штыревых разъёмов в KiCad не соответствует принятой для данного модуля, поэтому нумерация здесь зеркальная относительно указанной в инструкции для NRF24L01+. Для работы радиомодуля в 3х пиновой конфигурации необходимо поменять местами выводы MISO и MOSI, и добавить часть схемы справа, состоящую из элементов: R3, D1 и C2. Схема данной конфигураци взята отсюда nRF24L01+ with ATtiny85 3 Pins.



Работа данной схема возможна по тому, что на CE (chip enable) можно сразу установить высокий уровень, а CSN (chip select) нужен только для передачи модулю сигнала о начале новой команды, что достигается сбросом высокого уровня в низкий. Соответственно, CSN можно объединить с одним из оставшихся выводов, в данном случае SCK (clock). Штатные импульсы SCK не успевают зарядить конденсатор C2 и на CSN остается низкий уровень, перед началом передачи команды нужно установить на SCK высокий уровень на более длительное время, чтобы получить высокий уровень на CSN. С чем связанна необходимость поменять местами MISO и MOSI я не понял, видимо так было удобно.



Для стабильной работы модуля желательно наличие ёмкости 1-10μF на выводах питания модуля, для этих целей на схеме предусмотрен конденсатор C4.



После устранения проблем с датчиком температуры, первым делом была проверена возможность прошить ATtiny85 с установленным радиомодулем. Процесс не пошёл, avrdude ругнулся на не верную сигнатуру, но я проявил упорство и запустил прошивку… и в итоге тоже ошибка. В результате эксперимента радиомодуль не пострадал, правда и шансов что-то в него прошить не было из-за модификаций приведенных выше. А вот микроконтроллер пришлось реанимировать с помощью высоковольтного программатора. Идея оказалась неудачной, но попробовать стоило.



Микроконтроллер ATtiny85



В процессе тестирования микроконтроллер успешно работал от внутреннего осциллятора без делителя на частоте 8MHz, однако с непосредственно подключенным радиомодулем его работа стала не стабильной. В том смысле, что если соединить радиомодуль с разъёмом проводами длиной 30cm, всё работает нормально, а с модулем, установленным в разъем, что-либо отправить невозможно. Все попытки стабилизировать систему дополнительными конденсаторами, ровно как и сделать её нестабильной, разместив вынесенный радиомодуль вплотную к микроконтроллеру, ни к чему не привели. В результате пришлось включить делитель, что вернуло микроконтроллер к заводским настройкам. Работа ATtiny85 на частоте 1MHz каких либо сложностей не вызвала, но заметно сократила ожидаемое время работы от батареи. Подробнее об этом в конце статьи.



Датчик освещения на BPW17N



С этим датчиком всё относительно просто, на него попадает только искусственное освещение, а регистрировать он должен только моменты его включения и отключения. Для максимально быстрой реакции на событие датчик генерирует прерывание на порту PCINT3 (Pin Change Interrupt).



Элементы R1 и C3 подстроечные, для них предусмотрено место на плате, но они не распаяны. С3 параллельно с фототранзистором Q1 может понадобиться, чтобы исключить дребезг сигнала. Проблема в том, что стандартный для таких случаев конденсатор 0.1μF, разряжается через резистор 10MΩ около секунды, что не приемлемо для регистрации события — свет мигнул. Соответственно, можно установить конденсатор меньшей ёмкости и/или уменьшить сопротивление резистора R2, но сначала нужно проверить, есть ли в этом смысл.



Поскольку здесь используется прерывание может показаться, что в схеме нужен компаратор, но эти мысли сразу отпадают, как только становится понятно сколько эта штука потребляет. Правда и потребление микроконтроллера, при уровне сигнала вблизи центра рабочего диапазона, подскакивает с 7μA примерно до 260μA в режиме сна. Однако данную проблему проще решить программно, а именно, после срабатывания прерывания (PCINT3 или WDT), можно проверить напряжение на аналоговом входе ADC3, если оно неприемлемо, сообщить о проблеме и пропустить активацию PCINT3. В любом случае это нештатная ситуация, которая требует внимания.



Макетная плата



Готовую схему необходимо развести на плате. Это также можно сделать в KiCad. Для этого сначала требуется завершить работу со схемой:


  1. Промаркировать элементы на схеме , все настройки можно оставить по умолчанию.
  2. Сопоставить элементы схемы с посадочными местами на плате . Появится следующее окно:

    Здесь для наглядности следует включить просмотр выбранного элемента .
    Поскольку предстоит работать с макетной платой необходимо следить, чтобы все посадочные места укладывались в сетку 1/10" (2.54mm). Если требуемый элемент найти не удаётся, проще назначить похожий, затем, в редакторе печатной платы, изменить его (Ctrl+E) и записать в свою библиотеку.
    Как только все посадочные места будут назначены нужно сохранить результат и вернуться в редактор схемы.
  3. Сгенерировать netlist , настройки по умолчанию вполне приемлемы.


Редактор схемы можно закрыть и перейти к редактору печатной платы . Здесь нажать , затем загрузить ранее созданный netlist (Read Current Netlist), убедиться, что не было ошибок и закрыть окно. Если схема была изменена или элементам были назначены другие посадочные места, необходимо обновить netlist и загрузить его здесь снова.



При первой загрузке netlist все посадочные места окажутся стопкой в одной куче. Сначала необходимо переключить сетку на 1/10" (2.54mm), растащить элементы (используя кнопку m), убрать лишние надписи Visibles → Render → Values и, пользуясь подсказками редактора, соединить элементы дорожками.



Результат будет выглядеть примерно так:





Оставшиеся белые линии, указывают какие посадочные места необходимо соединить проводами. К сожалению в KiCad нет способа вывести результат, так чтобы его было удобно воспроизвести на макетной плате. Но в данном случае макетная плата получилась не большой, и с ней можно поступить просто: отключить лишние надписи Visibles → Render → Footprints Front, снять скриншот и зеркально отразить его по вертикали.





Готовый макет:





Тут два несоответствия со схемой выше: не распаяны подстроечные элементы R1,C3 и диод короче на одно отверстие.





Плата немного побита жёсткой отладкой, но на то она и макетная. Большинство других вариантов, после всех модификаций, выглядело бы хуже.



Подготовка IDE и программатора



Arduino IDE наиболее доступное кроссплатформенное программное обеспечение для программирования AVR микроконтроллеров и вполне удобное для небольших проектов.



Не смотря на то, что Arduino IDE официально не поддерживает ATtiny85, существует штатный способ добавить данный микроконтроллер в список поддерживаемых. Как это сделать, подробно описано по ссылке Programming an ATtiny w/ Arduino 1.6. Однако данный способ годится только для непосредственного программирования ATtiny85 и не позволяет использовать Arduino код. С одной стороны это хорошо, код чище будет, но задача в данном случае — разработать скетч демонстрирующий способность устройства функционировать как было задумано, и такая оптимизация здесь преждевременна.



Значит нужно Arduino ядро для ATtiny85, его можно взять здесь arduino-tiny, версия для Arduino 1.5 подойдет. Доступны будут не все функции, но базовый набор, включая программный последовательный порт, для отладки, можно будет использовать. К сожалению в актуальной, на данный момент, Arduino IDE v1.6.6 что-то сломали, и c этим ядром необходимо использовать версию 1.6.5.



Подключение ядра к Arduino IDE v1.6.5
  1. В директории с Arduino скетчами нужно создать папку hardware;
  2. Скачать в эту папку файл arduino-tiny-0150-0020.zip и распаковать его;
  3. Скопировать файл «Prospective Boards.txt» в boards.txt в той же директории, и зачистить последний от ненужных конфигураций, в данном случае всё что не ATtiny85, чтобы не мешались;
  4. Теперь если запустить или перезапустить Arudino IDE в меню Tools → Board в самом низу списка появятся добавленные конфигурации.



Осталось приготовить программатор. Для этого совсем не обязательно использовать специальное устройство, можно в ближайшую Arduino прошить скетч из File → Examples → ArduinoISP, затем соединить с сенсором, как указанно на принципиальной схеме, и не забыть про конденсатор 10μF, который необходимо установить между RST и GND выводами Arduino.





Программировать устройство нужно без установленных радиомодуля и перемычек. В меню Tools → Board нужно выбрать конфигурацию микроконтроллера, в данном случае: ATtiny85 @ 1 MHz (internal oscillator; BOD disabled), в меню Tools → Port выбрать USB порт программатора и в меню Tools → Programmer указать тип программатора, в данном случае: Arduino as ISP, затем воспользоваться кнопкой Upload.



Программное обеспечение



Тестовый скетч для работы с сенсором получился довольно интересным, в нем можно найти широкий набор примеров, а именно:

  • Работа с радиомодулем NRF24L01+;
  • Измерение напряжения питания средствами микроконтроллера;
  • Перевод микроконтроллера в режим сна;
  • Пробуждение по таймеру WDT (WatchDog Timer);
  • Пробуждение по Pin Change прерыванию PCINT;
  • Работа с аналоговыми и цифровыми портами.


Чтобы скомпилировать скетч необходимы сторонние библиотеки:





Это оптимизированные версии библиотек для работы с радиомодулем NRF24L01+, которые подходят для микроконтроллера ATtiny85. Их необходимо скачать и распаковать в папку libraries в директории со скетчами Arduino.



Ниже будут приведены наиболее интересные куски кода с подробными комментариями. Полностью скетч можно найти на GitHub в папке attwlight_sensor, также в папке attwlight_rx находится скетч для приёмника.



Перевод микроконтроллера в режим сна



Функция void sleep() вызывается в главном цикле void loop() и останавливает его до тех пор пока не сработает одно из прерываний PCINIT3 либо WDT. Затем выполняется полезная работа и снова вызывается данная функция.



Функция содержит обращения к регистрам микроконтроллера, и что при этом происходит не всегда очевидно. Чтобы с этим разобраться нужно заглянуть в документацию на микроконтроллер ATtiny85 datasheet раздел «23. Register Summary», в нем приведен полный список регистров с прямой ссылкой на страницу с подробным описанием для чего предназначен тот или иной бит.



#include <avr/wdt.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>

#define WDTO_INFINITE 255
#define SLEEP_PERIOD WDTO_8S
#define SKIP_WDT_WAKEUPS 14 // x SLEEP_PERIOD + SLEEP_PERIOD (14 ~= 120 sec)

// Вектор (функция) прерывания PCINT, у ATtiny85 один порт (B), следовательно только один вектор PCINT
ISR(PCINT0_vect)
{
  light_pcint = true;
}

// Вектор прерывания таймера WDT
ISR(WDT_vect)
{
  wakeup_counter++;
}

void sleep()
{
  GIMSK = _BV(PCIE); // Включить Pin Change прерывания
  if (SLEEP_PERIOD == WDTO_INFINITE || payload.light != LIGHT_FUZZY)
    PCMSK |= _BV(LIGHT_PIN); // PCINT3; включить если нет проблем с освещением или иначе нет шансов проснуться
  ADCSRA &= ~_BV(ADEN); // отключить ADC; уменьшает энергопотребление

  if (SLEEP_PERIOD != WDTO_INFINITE) {
    wdt_enable(SLEEP_PERIOD); // установить таймер
    WDTCR |= _BV(WDIE); // включить прерывания от таймера; фикс для ATtiny85
  }

  // Установить режим сна Power-down; MCUSR &= ~_BV(SM0); MCUSR |= _BV(SM1);
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable(); // разрешить режим сна; MCUSR |= _BV(SE);
  sei(); // включить прерывания

  sleep_cpu(); // заснуть

  cli(); // отключить прерывания; для безопасного отключения PCINT3
  PCMSK &= ~_BV(LIGHT_PIN); // PCINT3; отключить
  sleep_disable(); // запретить режим сна; MCUSR &= ~_BV(SE);
  ADCSRA |= _BV(ADEN); // включить ADC
  sei(); // включить прерывания; иначе таймеры не будут работать
}


Опрос датчика освещения



Особенности работы с данным датчиком уже были разобраны в разделе «Принципиальная схема». Отмечу только, что параметры здесь выбраны так, чтобы потребление микроконтроллера в режиме сна не превышало 20μA.



#define FUZZY_VOLTAGE 0.4 // ширина зоны неприемлемого напряжения в середине, включая концы

const unsigned int fuzzy_start = int(1024.0 * (1.0 - FUZZY_VOLTAGE) / 2.0);
const unsigned int fuzzy_stop = 1023 - fuzzy_start;

light_t readLightStatus(unsigned int ms)
{
  delay(ms); // если нужно время на стабилизацию
  int input = analogRead(LIGHT_PIN); // ADC3

  // оценка надёжности результата
  if (input >= fuzzy_start && input <= fuzzy_stop)
    return LIGHT_FUZZY;
  if (input < fuzzy_start)
    return LIGHT_OFF;
  if (input > fuzzy_stop)
    return LIGHT_ON;

  // заглушка; на случай ошибки в коде
  return LIGHT_UNKNOWN;
}


Измерение напряжения питания



Если не использовать вывод AREF микроконтроллера, который предназначен для задания опорного напряжения, все измерения на аналоговом порту будут привязаны к напряжению питания. Батарея не стабилизированный источник, следовательно для корректного считывания показаний датчика температуры, необходим способ измерять напряжение питания в процессе работы. Напряжение питания также является хорошим индикатором состояния батареи.



Ниже приведен код, который позволяет произвести требуемые измерения полагаясь на стабильность внутреннего опорного напряжения Vbg (Bandgap reference voltage) равного 1.1V. Согласно спецификации Vbg может находится в пределах 1.0-1.2V, поэтому необходимо убедиться в точности измерений и при необходимости скорректировать константу 1125300.



long readVcc()
{
  // Установка референса в Vcc и измерение внутреннего Vbg == 1.1V
  ADMUX = _BV(MUX3) | _BV(MUX2);
  delay(2); // требуется минимум 1ms для стабилизации
  ADCSRA |= _BV(ADSC); // ADC Start conversion - начать опрос
  while (bit_is_set(ADCSRA, ADSC)); // ADSC == 1 пока опрос активен

  // Чтение ADCL блокирует ADCH, чтение ADCH разблокирует оба регистра => ADCL читать первым
  uint8_t low  = ADCL; // младший регистр данных ADC
  uint8_t high = ADCH; // старший регистр данных ADC

  long result = (high << 8) | low;

  result = 1125300L / result; // 1125300 = 1.1*1023*1000
  return result; // Vcc в милливольтах
}


Измерение температуры



Получив напряжение питания, легко вычислить точное напряжение на аналоговом входе микроконтроллера и перевести его в температуру.



float readTmp36(long vcc)
{
  int input = analogRead(TMP36_PIN); // напряжение относительно refVolts
  float refVolts = float(vcc) / 1000.0;
  float voltage = float(input) * refVolts / 1024.0; // фактическое напряжение
  float temperatureC = (voltage - 0.5) * 100.0; // смещение 500mV & 10mV == 1°C
  return temperatureC;
}


Остальная часть скетча нужна, чтобы собрать данные вместе и отправить их наиболее оптимальным образом, с точки зрения энергопотребления. Ничего интересного там нет, обычная логика, надеюсь, комментариев в коде достаточно, чтобы разобраться как всё работает.



Если скомпилировать данный код, можно увидеть, что он свободно умещается в 8KiB и даже есть куда развернуться:



Sketch uses 7,120 bytes (86%) of program storage space. Maximum is 8,192 bytes.
Global variables use 227 bytes (44%) of dynamic memory, leaving 285 bytes for local variables. Maximum is 512 bytes.


Осталось проверить, что всё работает как было задумано, и можно прошивать Fuses переключающие RESET в порт ввода/вывода. Готовой конфигурации в Arduino IDE для этого нет, поэтому нужно воспользоваться командной строкой:



avrdude -c stk500v1 -p attiny85 -P /dev/ttyUSB0 -v -C /etc/avrdude.conf -b 19200 -U lfuse:w:0x62:m -U hfuse:w:0x5f:m -U efuse:w:0xff:m


  • avrdude — команда из пакета Arduino IDE находящаяся в папке hardware/tools/avr/bin/;
  • /dev/ttyUSB0 — устройство программатора;
  • /etc/avrdude.conf — ссылка на файл находящийся в директории с Arduino IDE по адресу hardware/tools/avr/etc/avrdude.conf.


Что из себя представляют данные Fuses, можно посмотреть в калькуляторе Engbedded Atmel AVR® Fuse Calculator.



Если кратко, то эти настройки предписывают: использовать встроенный осциллятор с делителем частоты на 8 (итоговая частота 1MHz); использовать стандартные задержки при старте, необходимые для стабилизации осциллятора; отключить BOD (Brown-out Detector) — перезагрузка при низком напряжении питания для защиты памяти от записи мусора; включить возможность считывать прошивку; и собственно отключить RESET.





Энергопотребление



Пришло время измерить энергопотребление получившегося устройства. Вводные следующие: данные передается каждые 120sec, если пришло прерывание от датчика освещения, то немедленно; событиями от датчика освещения можно пренебречь; ёмкость батареи 200mAh.



Частота 1MHz 8MHz
Радиомодуль (потребление) 15.7mA 18.3mA
Микроконтроллер (потребление) 1.1mA 4.3mA
Режим сна (потребление) 7μA 7μA
Радиомодуль (активен за цикл) 51ms 16ms
Микроконтроллер (активен за цикл) 15ms 4ms
Оценка времени работы (дней) 513 739


Как видно, тут было ради чего бороться. К сожалению, стабильности на 8MHz добиться не удалось. Видимо без внешнего осциллятора, это не реально.



Все исходные коды проекта доступны на GitHub

Теги:
Хабы:
Всего голосов 14: ↑14 и ↓0+14
Комментарии22

Публикации

Истории

Ближайшие события

15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань