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

Поэтому я и подумал, что мне, наверное, будет не очень сложно сделать какую-то штуку, которая сможет предотвратить попадание в эту потенциально опасную ситуацию. Конечно, спустя довольно короткое время мне открыли глаза на тот факт, что мудрые женщины избегают риска совершенно естественным путем — им просто неудобно спать на спине на этих самых поздних сроках.

Но не выкидывать же уже купленные микроконтроллеры, акселерометры и поработившие разум гениальные идеи, правда?

К слову, были и другие демотиваторы. Начиная с хардкорного варианта:



И заканчивая вполне гламурными корректорами осанки, о которых я, честное слово, узнал уже позже собственного озарения.

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

Разумеется, мы все понимаем, что вибросигнал подходит не всем. Но у меня и не было претензий на универсальный прибор — везде, понимаете, должен соблюдаться разумный баланс. Кстати, именно поэтому в финальной конструкции есть и так нами любимая среда Arduino, и множество варварских решений, и даже легендарная синяя (реально — синяя, сантехник принес) изолента.

В общем, бритва Оккама сломалась, когда я приступил к обтачиванию контуров будущего будильника. У которого внезапно обнаружились и другие потенциальные таланты, именно: контроль осанки и предупреждение [офисного планктона] о слишком длительной неподвижности. Причина расширения функционала банальна — жаль было видеть, как пропадают ресурсы пусть и не самого мощного, но все же довольно серьезного микроконтроллера.

Как это работает


Как вы уже знаете, у Позиционера три режима работы:

1) Предотвращение сна на спине (ну или в любом другом положении, ему без разницы)
2) Контроль осанки
3) Предупреждение о малой подвижности

И, как еще не знаете, всего одна кнопка, которая выполняет функцию сброса микроконтроллера.

Поэтому для выбора нужного режима достаточно выполнить два действия: нажать кнопку и подержать Позиционер в одном из трех положений, если образно — на спине, на боку или стоя. А чтобы перевести штуку просто в режим ожидания (аналог выключения), вообще ничего делать не нужно. То есть, нажали кнопку, положили — и позиционер засыпает.

В чем фишка? После выбора режима или при усыплении Позиционер нужно держать довольно неподвижно, поскольку в первом случае ему нужно время на запоминание позиции, а во втором — на понимание того, что пора спать. При этом существует два главных интервала: первые пять секунд после включения служат для выбора режима, а последующие десять — для получения позиции.

При этом, кстати, десять секунд является условным понятием, поскольку Позиционер запоминает «координаты» только в том случае, если в течение указанного времени позиция относительно неизменна. Это для того, чтобы можно было выбрать режим работы и спокойно цеплять на себя устройство, не беспокоясь о жестком таймауте на эту операцию. А вот как только прицепил — замри на десять секунд, чтобы Позиционер понял, что пора приступать к делу.

Иными словами, внимательный читатель уже догадался, что для выбора режима используются показания акселерометра, а я все еще растекаюсь по древу.

Вот, можете посмотреть, как это работает на примере прототипа на макетной плате:



Как видите, все довольно просто.

В более-менее финальном варианте сигнализация работает следующим образом:

1) В режиме предотвращения сна на спине Позиционер срабатывает примерно через минуту и будит серией из 8 сигналов, которая не прерывается ни при каких обстоятельствах, кроме разряда батареи. А чтобы он прекратил это делать, следует лечь как-нибудь иначе и дождаться окончания серии сигналов. Иначе серия сигналов повторится.

2) В режиме контроля осанки Позиционер следит, чтобы верная осанка сохранялась на протяжении примерно 60% времени из 5-минутного интервала. Иначе начинает вибрировать до тех пор, пока не вернешься к правильной осанке.

3) В режиме предупреждения о малой подвижности Позиционер следит, чтобы по крайней мере 8% времени из 30-минутного интервала были посвящены движению. Если это не так — однократно выдает серию из 10 сигналов, которая не прерывается и начинает отсчет нового интервала.

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

А что касается могущих показаться странными интервалов и уведомлений, то я исходил из следующего. Минута на спине в моем представлении — достаточно долго, а 8 сигналов без перерыва (и перспектива получить столько же повторно) кажутся достаточным основанием, чтобы проснуться.

С другой стороны, контроль осанки не столь критичная вещь, поэтому, во-первых, не требуется стопроцентный контроль и, во-вторых, вряд ли контроль осанки происходит во сне, поэтому сигналов, которые прекращаются, когда вернешься в нужное положение, вполне достаточно. Ну и все время сидеть, словно аршин проглотил — немного глупо и не очень-то возможно. Поэтому контрольный интервал настраивается.

Наконец, когда я гуглил про необходимый минимум двигательной активности, то наткнулся на упоминание того, что 15-20 минут раз в три часа представляется более-менее адекватным. Настройки Позиционера учитывают эту информацию, но при желании модифицируются на этапе «прошивки» микроконтроллера. Опять же, здесь не нужно трясти человека до потери сознания, поскольку идти или не идти — дело добровольное. Отсюда и лишь деликатное напоминание в виде десяти вибросигналов.

Разумеется, все эти интервалы — не догма и легко меняются. Было бы желание.

Питается Позиционер от литиевой батарейки типа CR2032. Потребление тока в режиме сна (некий четвертый псевдорежим Позиционера, а не контроллера) 4 мкА, что я посчитал достаточным основанием для упрощения конструкции в части отказа от выделенного выключателя питания. На старте и при непосредственном определении позиции потребление может на доли секунды превышать 2 мА, но преимущественно находится в пределах 1 мА за исключением моментов срабатывания сигнала.

В интервалах между определением позиции контроллер находится в режиме экономии энергии (Power down), где потребление аналогично режиму сна Позиционера — 4 мкА. Просыпается контроллер по таймеру примерно раз в 8 секунд — это одновременно и максимальная экономия батарейки и приемлемо для более-менее корректной работы всех режимов.



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

Как это сделано


Во-первых, все из головы, во-вторых, экспромтом, в третьих по-дилетантски, поскольку мне интереснее было закрыть этот гештальт, чем возводить его в степень абсолюта.

Поэтому в дальнейшем будем иметь в виду, что речь идет как бы о весьма конкретной и одной единственной реализации. Для которой нужно:

1) Микроконтроллер ATtiny85 (например, такой)
2) Акселерометр ADXL335 в сборе (например, такой или сразу с питанием 3.3В)
3) Вибромотор от мобильного телефона (я путем многочисленных самоуничижений получил в ремонте)
4) Кнопка (взял от разобранного радиобрелка)
5) Резистор 2,2КОм
6) Диод типа 1N4007 (у меня что-то вроде КД522
7) Транзистор NPN, например 2N2222 (я взял что-то из сломанного DVD)
8) Батарейка (CR2032)
9) Подходящий корпус

Схема выглядит следующим образом:



Часть, касающуюся подключения мотора я честно украл в очень интересном проекте ELSE.

Кстати, еще очень рекомендую настолько прекрасную картинку от Sparkfun, что ее просто невозможно не процитировать:



Есть и еще одна хитрость. Суть в том, что существует как минимум две версии плат с акселерометром ADXL335: на 3,3В и до 5В. Собственно, рабочий диапазон напряжений акселерометра от 1,8В до 3,6В, поэтому вторая версия отличается от первой наличием стабилизатора напряжения.

. вот такая со стабилизатором (трехногий таракан слева от акселерометра). Фото Tinydeal


. а эта — без стабилизатора. Фото Hobby Electronics


. есть и гибридный вариант — где можно питать отдельно и от 3,3В (мимо стабилизатора, и от 5В через стабилизатор). Фото Adafruit


У меня вот оказалась плата со стабилизатором, которая работает и от 5В, что неудивительно, и от 3,3В (я проверял). Но так как я не вижу никакого смысла греть стабилизатор и, соответственно, зря тратить батарейку, то в финальном варианте устройства подключил питание акселерометра прямо к нему самому, минуя стабилизатор.

. для обхода стабилизатора можно ориентироваться и по плате, и по даташиту ADXL335


Наверное, еще имеет смысл рассказать о том, что микроконтроллеры ATtiny85 тоже разные бывают. В магазинах чаще и дешевле всего встречаются чипы с маркировкой вида ATtiny85-20, реже и дороже — ATtiny85-10. С точки зрения изготовления Позиционера (и по информации даташита) наиболее важное отличие в том, что ATtiny85-20 работает в диапазоне напряжений 2,7В — 5,5В, а ATtiny85-10 — в диапазоне 1,8В — 5,5В. Т.е. второй гораздо более предпочтителен с точки зрения наиболее полного использования емкости батарейки.

Второе отличие — тактовая частота 20 МГц против 10 МГц. Хотя для Позиционера это, в общем-то, неважно: он работает на 1 МГц и на жизнь не жалуется. Но так как я просто схватил первое, что попалось и подешевле, то у меня как раз ATtiny85-20.

Наконец, если вдруг для кого-то это загадка, то питание акселерометра я подключил к контроллеру по той причине, что так проще всего выключать его (акселерометр) в режиме сна Позиционера. Иначе будем постоянно иметь утечку до 350 мкА (если верить даташиту ADXL335).

Собирал все навесным монтажом, соединяя элементы эмалированным проводом 0,1 мм (с маркировкой Jumper wire), пару катушек которого добрые китайцы подарили мне вместе с паяльной станцией. И который я уже год не знал, куда девать. Оказалось идеально: провод прекрасно лудится паяльником на 300С с обычной канифолью, и достаточно удобен для пайки как ATtiny85, так и транзистора в корпусе SOT23.

В итоге получился довольно утилитарный ландшафт:

. компоненты какие были — такие и ставил. Очень уж накладно по одной штуке в бутике Ч-и-Д закупаться


Алгоритм и код



Базовый алгоритм работы Позиционера выглядит следующим образом:



В качестве среды программирования я совершенно безальтернативно выбрал Arduino, поскольку сейчас пусть худо-бедно, но знаком только с ней. Ну и гештальт я в начале упоминал — это тоже объясняет тот факт, почему я не стал изучать C++, чтобы сделать Позиционер.

Что касается кода, то должен дать некоторые комментарии.

Исходя из модели использования прибора (включил и забыл, пока он не напомнит), крайне желательно было бы забыть и про ежедневную замену батареек. А для этого нужно серьезно экономить энергию, что очень даже возможно, поскольку в даташите ATtiny85 упоминается, что минимальное потребление — десятые доли микроамера. Сразу скажу — до таких высот я не поднялся (или не опустился?).

Здесь я опять выбрал самый простой путь. Именно — нагуглил то, что было нужно. Первое — код для укладывания ATtiny85 в режим максимально глубокого сна с пробуждением по watchdog-таймеру.

При этом потребление тока с единиц миллиампер упало до сотен микроампер. Это уже было прекрасно, но все же недостаточно прекрасно.

Пришлось искать дальше. И тут выяснилось, что сон сном, а периферию нужно отключать вручную. При этом львиную долю энергии даже в режиме сна кушает АЦП. Вот здесь так и написано.

После отключения АЦП на время сна энергетика Позиционера стала гармоничной настолько, насколько я вообще мог это нагуглить. Т.е., как и писал выше, минимальное потребление — 4 мкА, во время пробуждений на время измерений — десятки или сотни микроампер, при работающем вибромоторе может превышать 2 мА.

Жизнеспособность этой смеси кода можно оценить по тому, что далеко не первой свежести CR2032 уже неделю трудится в Позиционере в режиме тестирования. Большую часть времени, конечно, в режиме ожидания, но и активного времени прилично. Одним словом, близко к реальной модели использования.

Относительно работы с акселерометром. Дело в том, что я, насколько мог, абстрагировался от абсолютных величин. Поводов для этого несколько. Во-первых, так можно менять акселерометры, не задумываясь о том, как та или иная железка работает. Во-вторых, у меня не было, простите за тавтологию, железных гарантий, что при неизбежном в процессе разряда батареи падении напряжения не «поплывут» и показания акселерометра, которые связаны с их обработкой АЦП в микроконтроллере.

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

Код заливал в ATtiny85 через Arduino Mega согласно распространенной инструкции. Т.е. сначала скачал поддержку ATtiny для своей версии среды Arduino вот здесь.

И, как водится, распаковал содержимое архива (каталог tiny со всем содержимым) в папочку hardware своего каталога Arduino:



Потом зашел в этот каталог и, согласно инструкции, сделал там файлик boards.txt, в который из уже имеющегося файла Prospective Boards.txt скопировал то, что мне было нужно — т.е. описания ATtiny 1 МГц и на всякий случай — 8 МГц.

Вот такой получился boards.txt
attiny85at8.name=ATtiny85 @ 8 MHz  (internal oscillator; BOD disabled)

# The following do NOT work...
# attiny85at8.upload.using=avrispv2
# attiny85at8.upload.using=Pololu USB AVR Programmer

# The following DO work (pick one)...
attiny85at8.upload.using=arduino:arduinoisp
# attiny85at8.upload.protocol=avrispv2
# attiny85at8.upload.using=pololu

attiny85at8.upload.maximum_size=8192

# Default clock (slowly rising power; long delay to clock; 8 MHz internal)
# Int. RC Osc. 8 MHz; Start-up time PWRDWN/RESET: 6 CK/14 CK + 64 ms; [CKSEL=0010 SUT=10]; default value
# Brown-out detection disabled; [BODLEVEL=111]
# Preserve EEPROM memory through the Chip Erase cycle; [EESAVE=0]

attiny85at8.bootloader.low_fuses=0xE2
attiny85at8.bootloader.high_fuses=0xD7
attiny85at8.bootloader.extended_fuses=0xFF
attiny85at8.bootloader.path=empty
attiny85at8.bootloader.file=empty85at8.hex

attiny85at8.build.mcu=attiny85
attiny85at8.build.f_cpu=8000000L
attiny85at8.build.core=tiny
 
###########################################################################
###########################################################################

attiny85at1.name=ATtiny85 @ 1 MHz  (internal oscillator; BOD disabled)

# The following do NOT work...
# attiny85at1.upload.using=avrispv2
# attiny85at1.upload.using=Pololu USB AVR Programmer

# The following DO work (pick one)...
attiny85at1.upload.using=arduino:arduinoisp
# attiny85at1.upload.protocol=avrispv2
# attiny85at1.upload.using=pololu

attiny85at1.upload.maximum_size=8192

# Default clock (slowly rising power; long delay to clock; 8 MHz internal; divide clock by 8)
# Int. RC Osc. 8 MHz; Start-up time PWRDWN/RESET: 6 CK/14 CK + 64 ms; [CKSEL=0010 SUT=10]; default value
# Divide clock by 8 internally; [CKDIV8=0]
# Brown-out detection disabled; [BODLEVEL=111]
# Preserve EEPROM memory through the Chip Erase cycle; [EESAVE=0]

attiny85at1.bootloader.low_fuses=0x62
attiny85at1.bootloader.high_fuses=0xD7
attiny85at1.bootloader.extended_fuses=0xFF
attiny85at1.bootloader.path=empty
attiny85at1.bootloader.file=empty85at1.hex

attiny85at1.build.mcu=attiny85
attiny85at1.build.f_cpu=1000000L
attiny85at1.build.core=tiny
 
###########################################################################
###########################################################################



После этого запустил Arduino, убедился, что в меню Сервис — Плата появились нужные ATtiny:



Подключил (и, разумеется, выбрал нужную плату) Arduino Mega 2560, записал в нее скетч Arduino ISP из примеров:



Потом подключил ATtiny к Mega, руководствуясь распиновкой Mega 2560 и инструкцией из скетча Arduino ISP:

// pin name:    not-mega:         mega(1280 and 2560)
// slave reset: 10:               53 
// MOSI:        11:               51 
// MISO:        12:               50 
// SCK:         13:               52 


Конечно же подключил ATtiny к питанию и земле, в моем случае обе линии были общими с программатором, т.е. Mega 2560, хотя все мы понимаем, что необходимый минимум из общего — это линии SPI, reset и земля.

А когда все было готово, то сначала (не забыв выбрать плату ATtiny85) записал загрузчик:



И вслед за ним - собственно скетч
// энергосбережение
// http://donalmorrissey.blogspot.ru/2010/04/sleeping-arduino-part-5-wake-up-via.html
// http://www.technoblogy.com/show?KX0

/* СИГНАЛЫ
 . 			включение
 - . 		будильник
 - ..		движение
 - ...		осанка
 - 			изменение позиции при ее фиксации
 ...			позиция зафиксирована
 --			ошибка, выключение
 .......... 	предупреждение
 */


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

volatile int f_wdt=1;

#define xPin A1 // пин оси X
#define yPin A2 // пин оси Y
#define zPin A3 // пин оси Z
#define alPin 0 // пин устройства уведомления
#define posPowPin 1 // пин питания акселерометра
#define timeLimit 10000 // таймаут для фиксации позиции
 #define posCountSleep 9 // таймаут во время режима сна (*8 сек) 8
#define posCountMove 231 // таймаут во время режима движения (*8 сек) 230
#define posCountBack 41 // таймаут во время контроля осанки 40
#define tresholdSleep 25 // порог чувствительности в режиме сна
#define tresholdMove 25 // порог чувствительности в движения
#define tresholdBack 20 // порог чувствительности в режиме осанки
#define movPercentMove 92 // количество покоя (в %) в режиме движения
#define movPercentBack 40 // количество неправильных поз (в %) в режиме осанки
#define startTimeOut 5000// таймаут для перехода в состояние сна или выбора режима
#define motionTimeOut 1500// таймаут для определения движений в режиме контроля подвижности

unsigned long timeOut; // счетчик для фиксации позиции
byte treshold = 15; // чувствительность к изменению позиции
int posCounter = 1; // счетчик интервалов вычисления позиции
byte posMode = 0; // рабочий режим (по умолчанию - выкл = 0; сон на спине - 1; движение - 2; осанка - 3)
int posTolerance = 0; // счетчик для активации сигнала в зависимости от режима (алгоритмы 009b).
int x, y, z, x1, y1, z1; // координаты
int relX, relY, relZ; // относительные координаты для сравнений
int posCount; // счетчик для включения сигнала тревоги
boolean alarmRaised = false; // признак тревоги
boolean standBy = false; // флаг готовности к работе

// http://www.technoblogy.com/show?KX0
#define adc_disable() (ADCSRA &= ~(1<<ADEN)) // disable ADC (before power-off) 
#define adc_enable()  (ADCSRA |=  (1<<ADEN)) // re-enable ADC

ISR(WDT_vect)
{
  if(f_wdt == 0)
  {
    f_wdt=1;
  }
}

void enterSleep(void)
{
  pinMode(alPin, INPUT);
  pinMode(posPowPin, INPUT);

  adc_disable();
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);  

  sleep_enable();

  sleep_mode();

  sleep_disable(); 

  power_all_enable();
  adc_enable();

  pinMode(alPin, OUTPUT);
  digitalWrite(alPin, LOW);

  pinMode(posPowPin, OUTPUT);
  digitalWrite(posPowPin, LOW);
}


void setup() {

  /*** Setup the WDT ***/

  /* Clear the reset flag. */
  MCUSR &= ~(1<<WDRF);

  /* In order to change WDE or the prescaler, we need to
   * set WDCE (This will allow updates for 4 clock cycles).
   */
  WDTCR |= (1<<WDCE) | (1<<WDE);

  /* set new watchdog timeout prescaler value */
  WDTCR = 1<<WDP0 | 1<<WDP3; /* 8.0 seconds */

  /* Enable the WD interrupt (note no reset). */
  WDTCR |= _BV(WDIE);

  // пин устройства уведомления
  pinMode(alPin, OUTPUT);
  digitalWrite(alPin, LOW);

  // пин питания акселерометра
  pinMode(posPowPin, OUTPUT);
  digitalWrite(posPowPin, LOW);

  pinMode(xPin, INPUT);
  pinMode(yPin, INPUT);
  pinMode(zPin, INPUT);

  delay(1000); // включились-устаканились
  blinker(500, 1); // вибро при включении

  if (motionDetect(startTimeOut) == true) {

    getPos(); // получение позиции	

    if (abs(x1-y1) > abs(x1-z1)) {
      if (abs(x1-z1) > abs(y1-z1)) {
        selectX(); // x
      } 
      else {
        posMode = 2; // y
        posCount = posCountMove; // таймаут без движения
        treshold = tresholdMove;		
        blinker(1000, 1);
        blinker(500, 2);  
      }
    } 
    else {
      if (abs(x1-y1) > abs(z1-y1)) {
        selectX(); //x
      } 
      else {
        posMode = 1; // z
        posCount = posCountSleep; // таймаут сна на спине		
        treshold = tresholdSleep;
        blinker(1000, 1);
        blinker(500, 1);    
      }
    }
  }

  if (posMode > 0) {

    getPos(); // получение позиции
    x = x1; // запоминаем позицию
    y = y1;
    z = z1;
    timeOut = millis();	

    while ((millis() - timeOut) < timeLimit) {
      getPos();
      if (comparePos(x, y, z, x1, y1, z1) == false) { // если позиция изменилась - сбрасываем счетчик
        blinker(1000, 1); // сигнал об изменении позиции
        x = x1; 
        y = y1; 
        z = z1;
        timeOut = millis();
      }
    }

    // выход из цикла только при фиксации позиции
    standBy = true;
    blinker(500, 3); // сигнал фиксации позиции
  }
  else {
    blinker(1500, 2);
  }	


}

void loop() {

  if(f_wdt == 1) {

    if (posMode == 0) {
      /* Don't forget to clear the flag. */
      f_wdt = 0;

      /* Re-enter sleep mode. */
      enterSleep();
    }
    else {

      getPos();

      if (posCounter <= posCount && alarmRaised == false) { // пока в пределах таймаута по текущему режиму и нет уведомлений
      
      if (posMode == 2) { // если режим контроля подвижности
        if (motionDetect(motionTimeOut) == true) { // если за отведенное время были движения
          posTolerance = posTolerance++; // увеличиваем счетчик движений
        }

        if ((posCounter - posTolerance) > (posCount*movPercentMove)/100) { // если количество движений менее нужного % от интервала измерений
          alarmRaised = true; // нужно включить сигнал
        } 

      } else { // если другие режимы
          if (comparePos(x, y, z, x1, y1, z1) == true) { // если позиция неизменна 
            posTolerance = posTolerance++; // подсчет количества "совпавших" позиций (подходит для сна на спине и контроля осанки)
          }
        }
      
        
        if (posMode == 1) { // если режим сна
          if (posTolerance >= (posCount - 1)) { // если слишком долго на спине
            alarmRaised = true; // нужно включить сигнал
          } 
        }

        if (posMode == 3) { // если режим контроля осанки
          if ((posCounter - posTolerance) > (posCount*movPercentBack)/100) { // если количество правильных позиций менее необходимого % от интервала измерений
            alarmRaised = true; // нужно включить сигнал
          } 
        }

       posCounter++;

      } 
      else { // Результаты по итогам таймаута

       posCounter = 1; // сброс всех счетчиков
       posTolerance = 0;
       
      }

      if (alarmRaised == true) { // если нужны уведомления

        if (posMode == 1) { // если режим сна
          blinker(500, 8); // 8 сигналов и...
          getPos(); 
          if (comparePos(x, y, z, x1, y1, z1) == false) { // ...если повернулись
            alarmRaised = false; // отключаем тревогу
            posCounter = 0;
          }
        }

        if (posMode == 2) { // если режим движений
          blinker(500, 10); // 10 сигналов
          alarmRaised = false; // и отключаем тревогу
          posCounter = 0;
        } 

        if (posMode == 3) { // если режим осанки
          blinker(500, 1); // по одному сигналу пока...
          getPos();
          if (comparePos(x, y, z, x1, y1, z1) == true) { // ...осанка не исправится
            alarmRaised = false; // и отключаем тревогу
            posCounter = 0;
          }
        }
        
        posTolerance = 0; // сброс счетчика для уведомлений
      } 
      else {
        /* Don't forget to clear the flag. */
        f_wdt = 0;

        /* Re-enter sleep mode. */
        enterSleep();
      }
    } // posMode == 0
  } // wdt
} // loop

// Мигалка

void blinker(unsigned int impulse, byte times) {

  for (byte ttimes = 0; ttimes < times; ttimes++) {

    digitalWrite(alPin, HIGH);
    delay(impulse);
    digitalWrite(alPin, LOW);
    delay(impulse);

  }

}


// Сверка текущего положения с изначальным
boolean comparePos(int xOne, int yOne, int zOne, int xTwo, int yTwo, int zTwo) {

  boolean compareRes = false;

  relX = xOne - xTwo;
  relY = yOne - yTwo;
  relZ = zOne - zTwo;

  if (abs(relX) < treshold && abs(relY) < treshold && abs(relZ) < treshold) {
    compareRes = true; // совпадение значений позиции с учетом погрешности
  }
  return compareRes;

}

boolean motionDetect(int detectTimeOut) {
 
  boolean motionDetected = false;
  
  getPos();
  x = x1; y = y1; z = z1; // запоминаем новое положение, чтобы в следующий раз сравнивать с ним, а не со статикой
  timeOut = millis();
  
  while (((millis() - timeOut) < detectTimeOut)) { // после включения есть detectTimeOut секунд, чтобы повернуть брелок в положение выбора режима
   if (motionDetected == false) { // если положение не менялось
    if (comparePos(x, y, z, x1, y1, z1) == false) { // если положение после включения изменилось
      motionDetected = true; // разрешен выбор режима
    } 
    else {
      getPos();
    }
   }
  }
  
  return motionDetected;
}

// Вычисление текущей позиции
void getPos(){

  digitalWrite(posPowPin, HIGH);
  delay(10);

  byte i;
  unsigned int posX = 0;
  unsigned int posY = 0;
  unsigned int posZ = 0;

  for (i = 0; i < 100; i++) {
    posX = posX + analogRead(xPin);
    posY = posY + analogRead(yPin);
    posZ = posZ + analogRead(zPin);
  }

  x1 = posX/100;
  y1 = posY/100;
  z1 = posZ/100;

  digitalWrite(posPowPin, LOW); 
}

void selectX() {
  posMode = 3;
  posCount = posCountBack; // таймаут неправильной осанки		
  treshold = tresholdBack;
  blinker(1000, 1);
  blinker(500, 3);
}






Корпус


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

Однако пыл мой несколько угас, после того, как я из указанного полистирола (всего-то 1,5 мм толщиной) вырезал крышечку для второй инкарнации Евлампии. Сложно это оказалось — вырезать из полистирола одним лишь ножом для ковролина.

Поэтому я был очень рад, когда случилось очередное озарение — нужно взять полимерную глину, которой легко придать требуемую форму, а потом запечь до твердого состояния. Точнее, это мне так показалось, поскольку я не учел важной детали: у меня что эпоксидка не застывает, что полимерная глина не особенно-то твердеет. Но это лирика.

Разложил компоненты наиболее компактным образом, прикинул размеры корпуса и сделал картонную опалубку соответствующей формы, набил ее полимерной глиной. Затем прорезал в еще мягкой массе необходимые отверстия и поставил запекаться согласно инструкции. А потом еще на столько же времени, поскольку застывать глина никак не желала.

К слову, для батарейки логично было делать отверстие диаметром несколько больше, поэтому изучив сайт Банка России выяснил, что на эту роль подходит монетка 10 рублей, которая чеканится с 2010 года, поскольку ее диаметр — 22 мм. Как раз запас для того, чтобы CR2032 нормально падала в батарейный отсек, который я подпружинил контактом, давным-давно извлеченным из какого-то другого батарейного отсека.

Конструкцию батарейного отсека, конечно, тоже позаимствовал, поскольку вряд ли могу придумать что-то лучше, что избавляет от головной боли при переполюсовке.

. примерно так это выглядит в процессе


. и так после того, как пришлось разобрать уже склеенный корпус


То есть, все довольно просто: приложил компонент, чуть придавил, снял — вырезал по контуру. Ну и плюс прорези и каналы для проводочков. Что хорошо: на Позиционер ушел совсем мизер глины. Натурально, я купил самую маленькую пачку 57 г., и у меня еще осталось 50 граммов этого ценного сырья. Это первое. И второе: чтобы не нарушить геометрию еще пластичного макета, запекать можно не вынимая из картона, поскольку требуемая температура — 100С — 110С, т.е. далеко от возгорания бумаги.

После того, как каркас корпуса кое-как запекся немного подравнял края ножом. А после примерки компонентов выяснил, что эта #$%&*# глина мало того, что не твердеет, как нужно, так еще и «ужарилась» в процессе запекания. Поэтому пришлось аккуратно расширить вырезы. По счастью, итоговый материал оказался не камнем.

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

. душераздирающее зрелище




Ну а внешний вид — что там с него? Все равно я собирался сделать для Позиционера текстильный чехольчик, поскольку это наиболее рационально для прибора, который теоретически берут в постель. А чехольчик как раз улучшит и внешний вид, и тактильные ощущения.

Кстати, не советую белую термоусадку, если пойдете по моим стопам: нарядно, конечно, но пачкается очень быстро.

Почему не отпечатал на 3D-принтере? Во-первых, у меня его нет. И знакомых владельцев нет. Во-вторых, я не умею рисовать модели. В третьих, печатать пришлось бы много, поскольку этот каркас я сначала запланировал 30х70 мм, а в процессе он трансформировался в чуть меньше 30 мм по ширине и около 65 мм по длине. Притом я в еще не успевшей схватиться глине успел прорезать выемку для кнопки, о которой сначала и вовсе забыл.

А пластик каждый раз заново печатать. Хотя не спорю, было бы гораздо круче, чем мой вариант реднековского 3D-моделирования.

Отказ от гарантий


Прежде всего хочу сказать, что Позиционер не только не имеет ни малейшего отношения к медицинским приборам, но и даже до БАДов ему как до Луны. Это эксперимент. Это побег от лени и скуки. Это попытка придумать и сделать что-то этакое своими руками.

Поэтому, конечно, его нельзя рассматривать как спасительное средство или что-то вроде того. Забавный спутник, помощник — да. И, конечно же, его можно обмануть специально, а можно — не специально. Особенно, если неправильно выбирать место для крепления.

К примеру, для предотвращения сна на спине Позиционер следует, исхитрившись, закрепить на том участке тела, который занимает весьма однозначную позицию, когда остальное тело повернулось на эту самую спину. А при контроле подвижности лучше, если Позиционер будет на самой подвижной части тела, хотя я проверил — с текущими настройками он и в часовом кармашке джинсов неплохо справляется. Ну а если дело дошло до осанки, то Позиционер нужно крепить так, чтобы если скрючишься, то он был на этой самой скрюченной части.

Иначе никакого толка.

Я согласен, что можно было бы использовать и другой контроллер, и другой акселерометр, можно было бы и больше C++, и больше своими мозгами и… Так я же не запрещаю — творите, пробуйте. Уверен, у вас получится, лучше, чем у меня.

Собственно, поэтому и пишу на Гике, а не на Хабре. Чтобы, так сказать, не компрометировать последний, который на самом деле первый.

Комментарии (7)


  1. Alexlexandr
    21.07.2015 14:34
    +1

    Я когда то обдумывал подобную вещь для серьёзного применения как раз в медицине.
    Не то что сам не мог сделать, а просто не интересно когда нет команды.
    Если хотите продолжить, пишите.
    Свяжемся по Skype обсудим перспективы.


    1. spc Автор
      21.07.2015 16:10

      Знаете, меня уже приглашали поработать в команде. Повод был аналогичный — случилось у меня локальное озарение. Но по итогам разговора заинтересованные стороны пришли к выводу, что делать я ничего не умею ни руками, ни головой, и поэтому каждый идет своей дорогой. Это, конечно, расшифровка концовки в стиле «мы вам позвоним».

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


      1. Alexlexandr
        21.07.2015 18:18

        «Боевые» системы строятся на других платформах, т.ч. в любом случае практическая ценность проделанной работы умеренная, хотя сам проект хороший, вполне может кому то помочь в чем то.
        Самым ценным этом проекте безусловно является исполнитель, т.е. вы.
        Но зачем то вы придумали стену, которой скорее всего нет.
        Однако, ответ получен и не принять его нельзя. Успехов в работе и творчестве.


  1. AndreyDmitriev
    21.07.2015 16:02

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

    Чёрт, как же это жестоко по отношению к тому, кто спит рядом…


    1. spc Автор
      21.07.2015 16:11

      Ну это же не вибростенд ) Наоборот, я уже второй год с вибробудильником сплю. И эта штука не будит никого, кроме меня. В отличие от обычного будильника со звуковым сигналом.


      1. AndreyDmitriev
        21.07.2015 16:55

        Посыпаю голову пеплом — я был уверен, что устройство издаёт именно звуковые сигналы, хотя в посте всё чётко написано. Надо отучиться читать гиктаймс во время компиляции… Хочу что-то подобное смастерить, но с микрофоном, чтобы оно меня деликатно будило во время храпа.


        1. Moor
          22.07.2015 11:48

          Сразу тоже подумал про храп — я храплю только на спине.
          Но потом понял, что если я такой девайс на себя нацеплю — мне кранты, спать не буду вообще. Или (в альтернативной вселенной) просто научусь игнорировать вибрацию во сне :)