Разработка программы тахометра (RPM meter). Полезная штучка для измерения оборотов вращения чего угодно, хоть шуруповёрта, хоть каких-то двигателей и т.п.
Тахометр представляет собой тот же счётчик импульсов за определённый период. К примеру, определяем число импульсов с датчика за секунду и умножив на 60 секунд получаем число импульсов за минуту. Если на один оборот имеем один импульс с датчика, то число импульсов за минуту и будет число оборотов за минуту. Если с датчика приходит какое-то другое число импульсов за один оборот, то нужно общее число полученных импульсов за минуту разделить на число импульсов за оборот.
rpm = (nn / n) * 60; // Обороты в минуту
где:
nn - число импульсов за секунду
n - число импульсов на оборот
Исходя из всего этого нам требуется знать число импульсов на оборот как настраиваемый параметр. Потом этот параметр должен быть легко перенастраиваемый и в тоже время сохраняться на случай перезагрузки или отключения тахометра и уже при последующем включении быть готовым для посчётов оборотов.
В качестве датчика импульсов можно использовать пару инфракрасных светодиод-фотодиод и метку для отражения, либо датчик на эффекте Холла и небольшой магнитик. Мне проще было делать на датчике Холла, да и малюсеньких магнитиков достаточно. Удобно прицепить такой магнитик на вал мотора и приблизив датчик Холла уже сразу считывать скорость вращения вала.
Схема
Из выше описанных требований создаём схему. Датчик Холла используем A3144E, он реагирует только на северный полюс магнита, это удобно. На выходе этого датчика открытый коллектор, поэтому ему нужен нагрузочный резистор. Этот резистор используем в самом микроконтроллере. Тогда можно вывод 3 датчика непосредственно подключить ко входу микроконтроллера D2 (INT0).
Вывод информации на OLED SSD1306 128*64 I2C. Небольшой удобный дисплей, минимум проводов для подключения. Adruino Nano можно заменить на подобные с микроконтроллером ATMega328P, однако в будущем возможно заменить на какой-нибудь простенький микроконтроллер типа CH32V003x, у которых 16кБ флэш памяти.
Pic 1. Схема электрическая принципиальная
Описание программы
Программа, как обычно, хорошо закомментирована, пояснять там вроде нечего. Одно хочется сказать про управление.
Управление осуществляется всего одной кнопкой SB1. Если нажать и удерживать больше 5 секунд, то программа переходит в режим настройки. Экран включается в инверсный режим. Теперь нажимая на кнопку короткими нажатиями можно увеличивать делитель. Максимальное число импульсов на оборот 30 (если нужно больше, то изменить в строчке 036). Далее сброс на 1 и так по кругу. После установки нужного числа делителя, через 5 секунд бездействия программа возвращается в режим измерения.
Ниже на фото начальный экран. Входных импульсов нет. Делитель равен 1.
Pic 2. Управление
Если удерживать кнопку 5 секунд, то экран переходит в инверсию.
Pic 3. Управление
Далее нажатием кнопки устанавливаем делитель равный 4.
Pic 4. Управление
Через 5 секунд бездействия, экран переходит в обычный вид. Делитель установлен на 4. Это значение запоминается в EEPROM.
Pic 5. Управление
С датчика поступают импульсы. Определено 960 об/мин.
Pic 6. Управление
В режиме измерения короткое нажатие на кнопку SB1 вызывает режим удерживания [Hold] последнего значения. Удобно. Следующее нажатие снимает удерживание значения.
Pic 7. Управление
/******************************************************************* Тахометр на IR-модуле или датчик A3144 на основе эффекта Холла 2026-04-23 Mr.ALB Вывод на дисплей SSD1306 2026-04-24 Mr.ALB Сохранение настройки в EEPROM Датчик A3144E ARDUINO ------------- ------- 1 - VCC VCC +5V 2 - GND GND 3 - OUT (открытый коллектор) D2 (INT0) Кнопка ------ 1 - OUT D3 2 - GND GND ******************************************************************/#include<EEPROM.h>#include<GyverOLED.h>GyverOLED<SSD1306_128x64,OLED_NO_BUFFER> oled;
constuint8_t SCREEN_W = 128,// Ширина экрана в пикселях
SCREEN_H = 64,// Высота экрана в пикселях
h = 8,// Высота буквы size 1
w = 6; // Ширина буквы size 1// Массив для заставкиconstchar* stitle[] =
{
"RPM METER",// 0"v1.1",// 1"Mr.ALB",// 2"2026-04-24",// 3
};
#define MAXN 30 // Максимальное число импульсов на оборот#define SENSOR 2 // Выход IR модуля, или выход А3144#define BTN 3 // Кнопка настройки#define Z_PAUZA 5000 // Пауза после вывода заставки#define BTNPRES 5000 // Время удержания кнопки настройки#define TMDEBOUNCE 60 // Время антидребезгаint address = 16; // Адрес где хранится делитель nuint16_t rpm = 0; // Переменная оборотовbyte n = 1; // Количество импульсов датчика на 1 оборотbool flag_set =false,
btn =false,// Переменная кнопки
btn_flag =false,// Флаг нажатия кнопки
flag_hold =false,// Флаг остановки измерения
flag_prn_hold =false; // Флаг вывода надписи Stopuint32_t tmr_prev,// Переменная для отслеживания секунды
btn_press,// Переменная времени нажатия кнопки
tmr_debounce; // Переменная антидребезгаvolatileuint16_t nn; // Переменная числа входных импульсов/****************************************** Функция настройки *****************************************/voidsetup()
{
Serial.begin(9600);
oled.init(); // Инициализация#ifdef DISPLAY_ROTATE
// Поворот экрана на 180°: если надо, то ранее определить
oled.flipV(OLED_FLIP_V); // Отражение вертикально
oled.flipH(OLED_FLIP_H); // Отражение горизонтально#endif// Подключение прерывания для подсчёта импульсов за секундуattachInterrupt(digitalPinToInterrupt(2), count,FALLING);
// Вывод заставки
fnZastavka();
pinMode(SENSOR,INPUT_PULLUP);
pinMode(BTN,INPUT_PULLUP);
// Чтение настройки делителя из EEPRON
n =EEPROM.read(address);
if (n > MAXN)n = 1;
}
// end of setup()/****************************************** Основная функция *****************************************/voidloop()
{
btn_press =millis();
btn =!digitalRead(BTN);
// Если нажата кнопка настройкиif (btn ==true&& btn_flag ==false)
{
btn_flag =true;
while (!digitalRead(BTN)); // Проверка удержания кнопкиSerial.print(F("Время удержания кнопки: "));
Serial.println(millis() - btn_press);
// Если кнопка нажата BTNPRES миллисекif ((millis() - btn_press) > BTNPRES)
{
// Режим настройкиSerial.println(F("Режим настройки"));
oled.invertDisplay(true); // Инверсия экрана
n = 1; // Установка начального значения
fnPrintN();
btn_press =millis();
do
{
btn =!digitalRead(BTN); // Проверка нажатия кнопкиif (btn ==true&& btn_flag ==false)
{
btn_flag =true; // Кнопка нажата
fnDebounce(TMDEBOUNCE); // Антидребезг
n++; // Новое значение кол-во импульсов на оборотif (n > 30)n = 1; // Ограничение значения
fnPrintN();
btn_press =millis();
}
// Сброс флага нажатия кнопкиif (btn ==false&& btn_flag ==true)
btn_flag =false;
}
while (millis() - btn_press < BTNPRES);
// Проверка значения делителя в EEPROM// Если там другое значение, то обновляемif (EEPROM.read(address) != n)
{
Serial.println(F("Запись в EEPROM нового делителя..."));
EEPROM.write(address, n); // Записываем в EEPROM
}
Serial.print(F("В EEPROM n="));
Serial.println(EEPROM.read(address));
}
else// Кнопка нажата быстро
{
Serial.println(F("Режим Stop"));
flag_hold =!flag_hold;
Serial.print(F("flag_hold="));
Serial.println(flag_hold);
fnPrintN();
}
btn_press =millis();
oled.invertDisplay(false);
}
// Проверка времени 1 секундаif (millis() - tmr_prev > 1000)
{
Serial.print(F("nn="));
Serial.print(nn);
Serial.print(F("\tn="));
Serial.print(n);
if (!flag_hold)
rpm = (nn / n) * 60; // Обороты в минуту
nn = 0; // Обнуляем счётчик
tmr_prev =millis(); // Обнуляем времяSerial.print(F("\trpm="));
Serial.println(rpm);
}
fnPrintOLED(); // Вывод информации// Сброс флага нажатия кнопкиif (btn ==false&& btn_flag ==true)
btn_flag =false;
delay(50);
}
// end of loop()/************************************ Функция: Заставка ***********************************/void fnZastavka()
{
oled.clear();
oled.setScale(1);
byte j = 0;
for (byte i = 0; i < (sizeof(stitle) /sizeof(stitle[0])); i++)
{
oled.setCursor((SCREEN_W - w *strlen(stitle[i])) / 2, j);
j += 2;
oled.print(stitle[i]);
}
oled.update();
delay(Z_PAUZA); // Пауза
oled.clear(); // Очистить дисплей
}
// end of fnZastavka()/************************************* Функция: Вывод на дисплей ************************************/void fnPrintOLED()
{
oled.setScale(2);
oled.setCursorXY(8, 18);
oled.println(F("RPM:"));
oled.setCursorXY(56, 18);
oled.print(rpm); // Вывод оборотовif (rpm == 0)oled.println(F(" "));
else
{
if (rpm < 100)oled.print(F(" "));
if (rpm < 1000 && rpm >= 100)oled.print(F(" "));
else oled.print(F(" "));
}
// Вывод делителя
fnPrintN();
}
// end of fnPrintOLED()/************************************ Функция: Вывод на экран делителя ***********************************/void fnPrintN()
{
oled.setScale(1);
oled.setCursorXY(8, 50);
oled.print(F("N:"));
oled.print(n); // Количество импульсов на 1 оборот
oled.print(F(" "));
if (flag_hold && flag_prn_hold ==false)
{
oled.setCursorXY(45, 50);
oled.print(F("Hold"));
flag_prn_hold =true;
Serial.println(F("flag_prn_hold = true"));
}
else
{
if (flag_prn_hold &&!flag_hold)
{
oled.setCursorXY(45, 50);
oled.print(F(" "));
flag_prn_hold =false;
Serial.println(F("flag_prn_hold = false"));
}
}
oled.update();
}
// end of fnPrintN()/************************************* Функция: Обработка прерывания *************************************/void count()
{
nn++;
}
// end of count()/******************************** Функция: Антидребезг кнопки *******************************/void fnDebounce(uint16_t t)
{
// Антидребезг
tmr_debounce =millis();
while ((millis() - tmr_debounce) < t);
}
// end of fnDebounce()
Конструкция
Пока законченной конструкции нет. В процессе обдумывания...
Проект в стадии разработки, возможны какие-то изменения и доработки...
Приложение
Все, что необходимо для повторения конструкции, можно скачать по ссылкам: