Таймер для работы

Вокруг нас всегда множество отвлекающих факторов, которые мешают нам продуктивно работать – часто хочется заглянуть в телефон, почитать новости и так далее. Но однажды я наткнулся на описание техники помодоро. О ней множество статей и информации, но суть одна: чередование периодов сфокусированной работы и отдыха. Пока идет таймер работы – мы не отвлекаемся ни на что, только работа. Но когда включается таймер отдыха – мы не работаем, нужно обязательно отвлечься. Классическая схема – это 25 минут сосредоточенной работы и 5 минут отдыха. Но для себя вы можете выбрать различные вариации. Для себя я выбрал схему 50 минут работы и 10 минут отдыха.

Я пробовал разные таймеры: приложения на компьютер, браузерные версии, засекал время на телефоне. Но каждый из этих вариантов имел свои недостатки. Пока однажды я не наткнулся на специальный кубик-таймер в онлайн магазине, как раз для этих целей. Переворачивая кубик, мы получали предустановленное время (25/5, 45/15, 50/10), при нажатии кнопки «старт» – засекалось выбранное время. Оно отображалось на экране. Так же были кнопки для сброса времени и рестарта счетчика. Данные девайс стоил около 3-х тысяч рублей. Но желание новых проектов и спортивный интерес взял верх, и я задумал сделать подобный таймер своими руками.

В этой статье я расскажу, как я делал помодоро таймер. Освоил новый контроллер, поработал с экраном и акселерометром.

Общее описание таймера и требование к нему

Для начала нужно решить, что должен уметь таймер и что для этого понадобится.

  • При перевороте таймера должно переключаться время
  • Таймер должен работать от аккумулятора
  • Иметь возможность заряжаться
  • Отображать информацию на дисплее.

У таймера будет предустановлено 4 варианта периодов (работа/отдых):

  • 25 минут работы / 5 минут отдых
  • 45 минут работы / 15 минут отдых
  • 50 минут работы / 10 минут отдых
  • 5 минут работы / 3 минуты отдыха

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

На таймере должно располагаться 3 кнопки (старт, сброс, пауза) на передней панели, работающие следующим образом:

  • Кнопка старт запускает таймер. Если кубик был сброшен – то запускается рабочий период, за ним период отдыха. Если был на паузе – продолжает отсчет. Если на кубике идет отсчет – ничего не происходит.
  • Кнопка сброса – сбрасывает таймер на начало.
  • Кнопка паузы ставит таймер на паузу.

Когда заканчивается время работы – начинается время отдыха, и так по кругу.

Необходимые компоненты
  1. Дисплей. Я хотел попробовать именно OLED дисплей. Он компактный, и на него можно вывести что угодно, включая графику.
  2. Контроллер. Т.к. таймер должен работать от аккумулятора на 3,7 вольт – я решил  приобрести Arduino pro mini на 3,3 вольта.
  3. Акселерометр
  4. Модуль зарядки аккумулятора.
  5. Батарею на 3,7 вольт. Типоразмер 1S
  6. Возможно, понадобится понижающий преобразователь. Т.к. батарея на 3,7 вольт, а контроллер на 3,3
Arduino Pro mini

Сначала разберемся с контроллером. Arduino Pro Mini не имеет на борту usb. Поэтому заливать прошивку в него придется посредствам USB to TTL преобразователя (так же это возможно сделать с помощью другой платы ардуино).

Тут нужно обратить внимание на то, что USB to TTL может быт с разным количеством контактов. Обычно там есть 5v, 3.3v, RX, TX, GND. Но может быть еще контакт DTR. Если вы задумались о покупке такого преобразователя – рекомендую брать именно с DTR. При работе с преобразователем, у которого нет DTR – придется все время при заливке скетча нажимать кнопку перезагрузки на ардуино. Если же такой контакт есть – работа упрощается в разы, особенно, при частой перепрошивке контроллера. У меня, к сожалению, был преобразователь без DTR.

Подключаем следующим образом:

Arduino Pro miniUSB to TTL
VCC3,3v
GNDGND
RXTX
TXRX

Все, теперь можно прошивать контроллер.

После написания скетча, нужно нажать кнопку загрузки в Arduino IDE. Сначала скетч компилируется, потом появляется сообщение о загрузке программы в контроллер. Как только появится информацию о загрузке – нужно сразу же нажать кнопку перезагрузки на контроллере. Если ее нажать раньше или чуть позже загрузка не пройдет.

Разработка алгоритма (таймер, работа кнопок).

Перед тем, как у меня появился акселерометр и экран – я решил разработать алгоритм для кнопок, и для самого таймера. Время выводилось в последовательный порт.

Получился следующий алгоритм (пояснять основную информацию решил в комментариях):

#define START_PIN 2 //Пин кнопки старт
#define PAUSE_PIN 3 //Пин кнопки паузы
#define RESET_PIN 4 //Пин кнопки сброса

#define STARTED_STATE 1 //Состояние "Запущен"
#define STOPPED_STATE 2 //Состояние "Остановлен"
#define PAUSED_STATE 3 //Состояние "Пауза"

int state = STOPPED_STATE;
uint32_t time; //Засекает текущее время в миллисекундах
uint32_t lastTime; //Переменная хранящая время отображения предыдущей секунды (нужна для корректных пауз между секундами)
uint32_t timeWork; //Время работы
uint32_t timeRest; //Время отдыха
uint32_t curreintTime; //Какой период сейчас идет (работа/отдых)
uint32_t pauseTime = 0; //Момент времени, когда была нажата кнопка паузы
uint32_t fullPauseTime = 0; //Время - сколько время было на паузе
bool moved = true;

void setup() {

Serial.begin(9600);
pinMode(START_PIN, INPUT_PULLUP);
pinMode(PAUSE_PIN, INPUT_PULLUP);
pinMode(RESET_PIN, INPUT_PULLUP);
checkAccelerometer(); //Провека состояни акселерометра
curreintTime = timeWork; //Устанавливает текущий период - Работа
moved = false;
lastTime = timeWork;
}

//Основная функция состояит из 3-х этапов (проверка переворота кубика, проверка нажатия кнопок, отрисовка времени на экране)
void loop() {
checkAccelerometer();
checkButtons();
showTimeStarted();
}

//Функция проверки состояния кнопок (кнопка старта, кнопка паузы, кнопка сброса)
void checkButtons(){
checkStartPressed();
checkPausePressed();
checkResetPressed();
}

//Фукнция проверки нажат ли старт
void checkStartPressed(){
if(!digitalRead(START_PIN)){
if(state == STOPPED_STATE){ //Если текущее состояние стоп
firstSecond(); //Функция первой секунды
}

if(state == PAUSED_STATE){ //Если текущее состояние пауза
fullPauseTime += millis() - pauseTime; //Увеличиваем общее время проведенное в состоянии паузы
}
state = STARTED_STATE;
}
}

//Проверка нажата ли пауза
void checkPausePressed(){
if(!digitalRead(PAUSE_PIN) && state == STARTED_STATE){ //Должна сработать если состояние - "Запущено"

pauseTime = millis(); //Запомнить момент времени установки паузы
state = PAUSED_STATE;

}
}

//Проверка нажата ли кнопка Сброса
void checkResetPressed(){
if(!digitalRead(RESET_PIN) && state != STOPPED_STATE){ //Должна сработать только если состояние не "Остановлено"
curreintTime = timeWork;
state = STOPPED_STATE;
printTime(String(((timeWork/1000)%3600)/60) + "/" +String(((timeRest/1000)%3600)/60));

}
}

//Функция отображает текущее время на экране
void showTimeStarted(){
if(state == STARTED_STATE){

int seconds;
int minutes;
uint32_t timeLeft;

timeLeft = (curreintTime - (millis() - time) + fullPauseTime); //СМотрим, сколько времени осталось в текущем периоде
minutes = ((timeLeft / 1000) % 3600) / 60;
seconds = ((timeLeft / 1000) % 3600) % 60;

//Если разница между прошлым моментом отображения и текущим больше или 1000 - отображаем оставшееся время
if(timeLeft + 1000 < lastTime){
printTime(createStringTime(minutes, seconds));
lastTime = timeLeft;
}

//Если осталось 0 минут и 0 секунд до конца - нужно сбросить таймер и выбрать противоположный период
if(seconds == 0 && minutes == 0){
printTime(createStringTime(0, 0));
delay(1000);
curreintTime = (curreintTime == timeWork + 1000 ? timeRest : timeWork); //Если был период работы - ставим отдых, и наоборот
firstSecond();
}
}
}

//Функция отображения первой секунды в периоде
void firstSecond(){
time = millis(); //Запоминаем текущий момент времени
int seconds;
int minutes;
minutes = ((curreintTime / 1000) % 3600) / 60;
seconds = ((curreintTime / 1000) % 3600) % 60;
printTime(createStringTime(minutes, seconds));
curreintTime += 1000; //Нужно увеличить текущий период на 1 секунду (иначе период будет на секунду меньше)
lastTime = curreintTime;
fullPauseTime = 0;
}

//Функция для получения строки времени
String createStringTime(int minutes, int seconds){
String minutesStr = String(minutes);
String secondsStr = String(seconds);

//Если длинна строки времени 1 - то добавим 0 в начале.
minutesStr.length() == 1? minutesStr = "0" + minutesStr : minutesStr;
secondsStr.length() == 1? secondsStr = "0" + secondsStr : secondsStr;

return String(minutesStr) + ":" + String(secondsStr);
}

//Функция отрисовки времени
void printTime(String time){
Serial.println(time);
}

//Функция проверки состояни акселерометра
void checkAccelerometer(){
if(moved){
timeWork = 12000;
timeRest = 6000;
}
}
Работа с акселерометром.

Был приобретён трехосевой акселерометр ADXL345. Выбор пал на самый недорогой и маленький. Для начала необходимо понять, как подключить его и как вытащить данные. Работает он от 3.3 вольт, как и наша ардуина. Работать он может как через шину SPI, так и через шину I2C. Через SPI у нас будет работать экран, поэтому мы выберем интерфейс I2C (Да да, я знаю, что к любому из этих интерфейсов можно подключать несколько устройств, но не хочу). Для этого подключим следующим образом:

Arduino pro miniADXL345
GNDGND
3.3V3.3V
A4SDA
A5SCL

Теперь, чтобы проверить работоспособность и вытащить какие-то данные – зальем тестовый скетч в Arduino. Тестовый скетч был найден на просторах интернета, а именно – тут . Там есть и пример считывания данных, и калибровки, и, вообще, подробно расписана вся работа.

Тестовый скетч:

#include <Wire.h>  // Wire library - used for I2C communication
int ADXL345 = 0x53; // The ADXL345 sensor I2C address

float X_out, Y_out, Z_out; // Outputs

void setup() {
Serial.begin(9600); // Initiate serial communication for printing the results on the Serial monitor
Wire.begin(); // Initiate the Wire library
// Set ADXL345 in measuring mode
Wire.beginTransmission(ADXL345); // Start communicating with the device
Wire.write(0x2D); // Access/ talk to POWER_CTL Register - 0x2D
// Enable measurement
Wire.write(8); // (8dec -> 0000 1000 binary) Bit D3 High for measuring enable
Wire.endTransmission();
delay(10);
}

void loop() {
// === Read acceleromter data === //
Wire.beginTransmission(ADXL345);
Wire.write(0x32); // Start with register 0x32 (ACCEL_XOUT_H)
Wire.endTransmission(false);
Wire.requestFrom(ADXL345, 6, true); // Read 6 registers total, each axis value is stored in 2 registers
X_out = ( Wire.read()| Wire.read() << 8); // X-axis value
X_out = X_out/256; //For a range of +-2g, we need to divide the raw values by 256, according to the datasheet
Y_out = ( Wire.read()| Wire.read() << 8); // Y-axis value
Y_out = Y_out/256;
Z_out = ( Wire.read()| Wire.read() << 8); // Z-axis value
Z_out = Z_out/256;

Serial.print("Xa= ");
Serial.print(X_out);
Serial.print(" Ya= ");
Serial.print(Y_out);
Serial.print(" Za= ");
Serial.println(Z_out);
}

После заливки скетча, получаем данные с акселерометра в последовательном порту. Если акселерометр покрутить – данные будут меняться.

Эмпирическим путем было определено, в каких положениях и как будут меняться данные. Далее была написана функция, которая изменяла время работы и отдыха в зависимости от положения.

void checkAccelerometer(){

Wire.beginTransmission(ADXL345);
Wire.write(0x32); // Start with register 0x32 (ACCEL_XOUT_H)
Wire.endTransmission(false);
Wire.requestFrom(ADXL345, 6, true); // Read 6 registers total, each axis value is stored in 2 registers

X_out = ( Wire.read()| Wire.read() << 8); // X-axis value
X_out = X_out/256;
Y_out = ( Wire.read()| Wire.read() << 8); // Y-axis value
Y_out = Y_out/256;
Z_out = ( Wire.read()| Wire.read() << 8); // Z-axis value
Z_out = Z_out/256;

if((-0.7 - EPSILON < X_out && X_out < -0.7 + EPSILON)
&& (1.45 - EPSILON < Z_out && Z_out < 1.45 + EPSILON)){
accelerometerTimeWork = 3000000;
accelerometerTimeRest = 600000;
rotation = 3;
} else if((0.35 - EPSILON < X_out && X_out < 0.35 + EPSILON)
&& (0.37 - EPSILON < Z_out && Z_out < 0.37 + EPSILON)){
accelerometerTimeWork = 2700000;
accelerometerTimeRest = 900000;
rotation = 0;
} else if((-0.75 - EPSILON < X_out && X_out < -0.75 + EPSILON)
&& (-0.75 - EPSILON < Z_out && Z_out < -0.75 + EPSILON)){ //Проработать
accelerometerTimeWork = 1500000;
accelerometerTimeRest = 300000;
rotation = 1;
} else if((-1.68 - EPSILON < X_out && X_out < -1.68 + EPSILON)
&& (0.4 - EPSILON < Z_out && Z_out < 0.4 + EPSILON)){
accelerometerTimeWork = 900000;
accelerometerTimeRest = 180000;
rotation = 2;
}

Serial.print(X_out);
Serial.print(" / ");
Serial.print(Y_out);
Serial.print(" / ");
Serial.println(Z_out);

if(state == STOPPED_STATE){
if(timeWork != accelerometerTimeWork){
printTime(String(((accelerometerTimeWork/1000)%3600)/60) + "/" +String(((accelerometerTimeRest/1000)%3600)/60));
}
timeWork = accelerometerTimeWork;
timeRest = accelerometerTimeRest;
}
}

В скетч через #define я ввел значение EPSILON. Оно позволило задать некоторую погрешность в данных акселерометра. Это необходимо, из-за того, что поверхность, где будет стоять таймер может быть не совсем горизонтальная.

Подключение экрана

С экраном пришлось повозиться. Всех тонкостей я не знал, поэтому заказал, практически, первый попавшийся экран. Я заказал IPS дисплей 1.3 дюйма. Уже когда я пытался его подключить и вывести что-то на него с помощью примеров из интернета, я понял, что экраны могут различаться.

Самое существенное различие, которое есть у подобных экранов – это вывод CE. У некоторых он может быть, а у других может отсутствовать. У экрана, заказанного мной, он отсутствовал, и это было небольшой проблемой. Большинство найденных примеров были для экранов с этим выводом. И лишь один пример я нашел для экрана без этого вывода. В примере отличалась библиотека, которая используется для вывода на экран. Из всех мучений могу сделать один вывод – если у вас экран без пина CE – вам нужна библиотека Arduino_ST7789_Fast. Возможно, есть и другие варианты, но я не погружался слишком глубоко в вопрос. 

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

Общая схема

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

Принципиальная схема таймера
Рисунок 1. Принципиальная схема таймера.
Таймер. тестовый стенд
Рисунок 2. Тестовый стенд таймера.

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

Корпус

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

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

3d-модель корпуса
Рисунок 3. 3D-модель корпуса.
Корпус таймера
Рисунок 4. Корпус таймера изнутри.
Сборка всего

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

arduino и акселерометр
Рисунок 5. Ардуино и акселерометр

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

сборка_внутри
Рисунок 5. Вид изнутри.
Таймер в работе
Рисунок 6. Таймер в работе
таймер вид сзади
Рисунок 7. Таймер. Вид сзади
Итоги

После сборки данный таймер был протестирован на работе. В целом все в нем устроило. Но есть один нюанс – это энергопотребление. Таймер питается от батареи в 600 мА/ч, чего хватает ему на 5 часов, а хотелось бы, чтобы хватало хотя бы на день. Для этого необходимо поработать над энергосбережением и возможно, это будет следующий этап работы.

1 комментарий к “Таймер для работы”

Оставьте комментарий