Вокруг нас всегда множество отвлекающих факторов, которые мешают нам продуктивно работать – часто хочется заглянуть в телефон, почитать новости и так далее. Но однажды я наткнулся на описание техники помодоро. О ней множество статей и информации, но суть одна: чередование периодов сфокусированной работы и отдыха. Пока идет таймер работы – мы не отвлекаемся ни на что, только работа. Но когда включается таймер отдыха – мы не работаем, нужно обязательно отвлечься. Классическая схема – это 25 минут сосредоточенной работы и 5 минут отдыха. Но для себя вы можете выбрать различные вариации. Для себя я выбрал схему 50 минут работы и 10 минут отдыха.
Я пробовал разные таймеры: приложения на компьютер, браузерные версии, засекал время на телефоне. Но каждый из этих вариантов имел свои недостатки. Пока однажды я не наткнулся на специальный кубик-таймер в онлайн магазине, как раз для этих целей. Переворачивая кубик, мы получали предустановленное время (25/5, 45/15, 50/10), при нажатии кнопки «старт» – засекалось выбранное время. Оно отображалось на экране. Так же были кнопки для сброса времени и рестарта счетчика. Данные девайс стоил около 3-х тысяч рублей. Но желание новых проектов и спортивный интерес взял верх, и я задумал сделать подобный таймер своими руками.
В этой статье я расскажу, как я делал помодоро таймер. Освоил новый контроллер, поработал с экраном и акселерометром.
Общее описание таймера и требование к нему
Для начала нужно решить, что должен уметь таймер и что для этого понадобится.
- При перевороте таймера должно переключаться время
- Таймер должен работать от аккумулятора
- Иметь возможность заряжаться
- Отображать информацию на дисплее.
У таймера будет предустановлено 4 варианта периодов (работа/отдых):
- 25 минут работы / 5 минут отдых
- 45 минут работы / 15 минут отдых
- 50 минут работы / 10 минут отдых
- 5 минут работы / 3 минуты отдыха
Выбор режимов будет осуществляться переворотом таймера на разные грани. При перевороте таймера – экран должен менять ориентацию, и менять установленное время. Если таймер уже запущен – меняется только ориентация, время продолжает идти.
На таймере должно располагаться 3 кнопки (старт, сброс, пауза) на передней панели, работающие следующим образом:
- Кнопка старт запускает таймер. Если кубик был сброшен – то запускается рабочий период, за ним период отдыха. Если был на паузе – продолжает отсчет. Если на кубике идет отсчет – ничего не происходит.
- Кнопка сброса – сбрасывает таймер на начало.
- Кнопка паузы ставит таймер на паузу.
Когда заканчивается время работы – начинается время отдыха, и так по кругу.
Необходимые компоненты
- Дисплей. Я хотел попробовать именно OLED дисплей. Он компактный, и на него можно вывести что угодно, включая графику.
- Контроллер. Т.к. таймер должен работать от аккумулятора на 3,7 вольт – я решил приобрести Arduino pro mini на 3,3 вольта.
- Акселерометр
- Модуль зарядки аккумулятора.
- Батарею на 3,7 вольт. Типоразмер 1S
- Возможно, понадобится понижающий преобразователь. Т.к. батарея на 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 mini | USB to TTL |
VCC | 3,3v |
GND | GND |
RX | TX |
TX | RX |
Все, теперь можно прошивать контроллер.
После написания скетча, нужно нажать кнопку загрузки в 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 mini | ADXL345 |
GND | GND |
3.3V | 3.3V |
A4 | SDA |
A5 | SCL |
Теперь, чтобы проверить работоспособность и вытащить какие-то данные – зальем тестовый скетч в 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. Возможно, есть и другие варианты, но я не погружался слишком глубоко в вопрос.
Ниже прикладываю схему подключения и фрагменты скетча, которые отвечают за подключение экрана и вывод информации на него.
Общая схема
Когда отдельные части системы таймера были протестированы – можно собрать все в единую схему. Помимо всех описанных выше компонентов, в схему необходимо встроить аккумулятор и модуль зарядки. Так же, к положительному выводу батарейки следует подключить выключатель, чтобы мы могли выключить наш таймер.


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


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

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



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