Всё как в предыдуцщей разработке, только в качестве вычислителя использую микроконтроллер ф.WCH CH32V003A4M6. Преимущество тут в размерах, вместо Arduino Nano маленькая микросхемка SOP-16 – очень удобно для создания законченной конструкции прибочика.
Схема
Датчик Холла используем такой же: A3144E, подключаем его к выводу 16 (PC0), а до этого в setup() назначаем прерывание на этот вывод.
Информацию выводим на тот же OLED SSD1306 128*64 I2C. Управление, как в предыдущем проекте, через кнопку SB1. Кнопка настройки на выводе 13 (PA2), что удобно для конструкции.
Pic 1. Схема электрическая принципиальная
Описание программы
Программа, как обычно, хорошо закомментирована, пояснять там вроде нечего. Однако в ядре WCH для Arduino IDE присутствует ошибка и поэтому вывод на дисплей OLED SSD1306 128*64 I2C не выходит. Пришлось устранять эту ошибку. Исправление ядра в Приложении в библиотеках SPI и Wire. Эти библиотеки скопировать в ядро WCH.
/***********************************************************
2025-10-18 Mr.ALB CH32V003F4P6 - блинк для проверки
2026-04-25 Mr.ALB CH32V003F4P6 - проверка внешнего прерывания
2026-04-26 Mr.ALB Подключение SSD1306...
2026-04-26 Mr.ALB ...Подключение SSD1306 - Успешно!
2026-04-27 Mr.ALB на CH32V003A4M6
2026-05-08 Mr.ALB Оптимизация
==================================================================================
WCH CH32V003A4M6
-----------------
+-\/-+
T1BKIN_/T2CH4_T2CH1ETR_/URX_/NSS/SDA/PC1 1| |16 PC0/T2CH3 NSS_/UTX_/T1CH3_
AETR_/T2CH2_/T1ETR_/URTS/T1BKIN/SCL/PC2 2| |15 VDD|VCC +3.3V
T1CH1N_/UCTS_/T1CH3/PC3 3| |14 VSS|GND
T1CH1CH2N_(2)/T1CH4/MCO/A2/PC4 4| |13 PA2/TICH2N/A0/OPP0/OSCO/AETR2_
T1CH1CH3N_(3)UCTS_/SDA_/MOSI/PC6 5| |12 PA1/T1CH2/A1/OPN0/OSCI
T1CH2_/URTS_/T2CH2_/MISO/PC7 6| |11 PD7/NRST/T2CH4/OPP1/UCK_
ПРОГ. - SWIO/T1CH3N/AETR2/SCL_/URX_/PD1 7| |10 PD6/URX/A6/T2CH3_/UTX_
UCK/C4ETR/OPA/C1ETR/A7/PD4 8|____|9 PD5/UTX/A5/T2CH4_/URX_
I2C A4M6 OLED SSD1306
--- --------- ------------
pin14 GND - GND
pin15 VCC - VCC
SCL - pin2 PC2/SCL - SCK
SDA - pin1 PC1/SDA - SDA
SENSOR(A3144E) A4M6
1 VCC - pin15 VCC/VDD
2 GND - pin14 GND/VSS
3 OUT - pin16 PC0
A4M6 Кнопка
---- ------
PA2 1 - OUT
GND 2 - GND
=================================================================================
WCH CH32V003F4P6 20-pins
+-\/-+
T1CH4ETR_/OPO/T2CH1ETR/UCK/A7/PD4 1| |20 PD3/A4/T2CH2/AETR/UCTS/T1CH4_
URX_/T2CH4/UTX/A5/PD5 2| |19 PD2/A3/T1CH1/T2CH3_/T1CH2N_
UTX_/T2CH3/URX/A6/PD6 3| |18 PD1/SWIO/AETR2/T1CH3N/SCL_/URX_
UCK_/OPP1/T2CH4/NRST/PD7 4| |17 PC7/MISO/T1CH2_/T2CH2_/URTS_
OPN0/T1CH2/A1/OSC1/PA1 5| |16 PC6/MOSI/T1CH1CH3N_/UCTS_/SDA_
AETR2_/OPP0/T1CH2N/A0/OSC0/PA2 6| |15 PC5/SCK/T1ETR/T2CH1ETR_/SCL_/UCK_/T1CH3_
GND 7| |14 PC4/A2/T1CH4/MCO/T1CH1CH2N_
UTX_/SDA/OPN1/T1CH1N/PD0 8| |13 PC3/T1CH3/T1CH1N_/UCTS_
VCC/+3V3/+5V 9| |12 PC2/SCL/URTS/T1BKIN/AETR_/T2CH2_/T1ETR_
T1CH3_/NSS_/UTX_/T2CH3/PC0 10|____|11 PC1/SDA/NSS/T2CH4_/T2CH1ETR_/T1BKIN_/URX_
Кнопка
на плате CH32V003F4P6
1 - VCC
2 - PC0
LED
на плате CH32V003F4P6
A - VCC
K -[3.3k]- PD1
I2C
---
PC1 - SDA
PC2 - SCL
SPI
---
PC7 - MISO
PC6 - MOSI
PC5 - SCK
PC0 - RESET
9 - VDD (VCC)
7 - VSS (GND)
Соединение для программирования
Порт COM11
-------------------------------
F4P6 WCH-LinkRV(E)
7 VSS(GND) - GND
9 VDD(VCC) - +5V
18 PD1/SWIO - SWDIO
2 PD5/RX - TX
3 PD6/TX - RX
F4P6 Кнопка
---- ------
PC4 1 - OUT
GND 2 - GND
I2C F4P6 OLED SSD1306
--- ---- ------------
GND - GND
VCC - VCC
SCL - PC2/SCL - SCK
SDA - PC1/SDA - SDA
===============================================================
НАСТРОЙКА
---------
Clock select: 48MHz Internal
Optimize: Smallest (-os) with LTO
C runtime Library: Newlib Nano (default)
с SERIAL_ON:
Скетч использует 15124 байт (92%) памяти устройства.
без SERIAL_ON:
Скетч использует 13628 байт (83%) памяти устройства.
Всего доступно 16384 байт
**********************************************************************/#include<EEPROM.h>#include<Wire.h>#include"SSD1306Ascii.h"#include"SSD1306AsciiWire.h"#define I2C_ADDRESS 0x3C
// Define proper RST_PIN if required.#define RST_PIN -1
SSD1306AsciiWire oled;
constuint8_t SCREEN_W = 128,// Ширина экрана в пикселях
SCREEN_H = 64,// Высота экрана в пикселях
h = 8,// Высота буквы size 1
w = 6; // Ширина буквы size 1//#define SERIAL_ON // Включение вывода сообщений в порт// Массив для заставкиconstchar* stitle[] =
{
"RPM meter",// 0"v1.3",// 1"Mr.ALB",// 2"2026-05-08",// 3
};
#define MAXN 30 // Максимальное число импульсов на оборот#define SENSOR PC0 // Выход IR модуля, или выход А3144#define BTN PA2 // Кнопка настройки#define Z_PAUZA 5000 // Пауза после вывода заставки#define BTNPRES 5000 // Время удержания кнопки настройки#define TMDEBOUNCE 100 // Время антидребезга//#define LED PD4 // Индикаторint address = 16; // Адрес где хранится делитель nuint16_t rpm = 0; // Переменная оборотовbyte n = 1; // Количество импульсов датчика на 1 оборотbool flag_set =false,
btn_flag =false,// Флаг нажатия кнопки
flag_hold =false,// Флаг остановки измерения
flag_prn_hold =false; // Флаг вывода надписи Holduint32_t tmr_prev,// Переменная для отслеживания секунды
btn_press,// Переменная времени нажатия кнопки
tmr_debounce; // Переменная антидребезгаvolatileuint16_t nn; // Переменная числа входных импульсов/***************************** ***** Функция настройки ***** *****************************/voidsetup()
{
#ifdef SERIAL_ON
Serial.begin(9600);
delay(300);
#endifpinMode(SENSOR,INPUT_PULLUP);
pinMode(BTN,INPUT_PULLUP);
// Инициализация дисплея
Wire.begin();
Wire.setClock(400000L);
#if RST_PIN >= 0
oled.begin(&Adafruit128x64, I2C_ADDRESS, RST_PIN);
#else// RST_PIN >= 0
oled.begin(&Adafruit128x64, I2C_ADDRESS);
#endif// RST_PIN >= 0
oled.setFont(Adafruit5x7);
fnZastavka();
// Подключаем прерывание:// myISR — функция-обработчик// FALLING — срабатывание по спадающему фронтуattachInterrupt(digitalPinToInterrupt(SENSOR), count,FALLING);
// initialize EEPROM objectEEPROM.begin();
// Чтение настройки делителя из EEPRON
n =EEPROM.read(address);
if (n > MAXN)n = 1;
btn_flag =false;
}
// end of setup()/***************************** ***** Основная функция ***** *****************************/voidloop()
{
btn_press =millis();
// Если нажата кнопка настройкиif (!digitalRead(BTN) && btn_flag ==false)
{
btn_flag =true;
while (!digitalRead(BTN)); // Проверка удержания кнопки#ifdef SERIAL_ON
Serial.print(F("Время удержания кнопки: "));
Serial.println(millis() - btn_press);
#endif// Если кнопка нажата BTNPRES миллисекif ((millis() - btn_press) > BTNPRES)
{
// Режим настройки#ifdef SERIAL_ON
Serial.println(F("Режим настройки"));
#endif
oled.invertDisplay(true); // Инверсия экрана
n = 1; // Установка начального значения
fnPrintN(); // Вывод делителя
btn_press =millis();
do// Настройка делителя
{
if (!digitalRead(BTN) && btn_flag ==false)
{
btn_flag =true; // Кнопка нажата
n++; // Новое значение кол-во импульсов на оборотif (n > 30)n = 1; // Ограничение значения
fnPrintN();
#ifdef SERIAL_ON
Serial.print(F("n = "));
Serial.println(n);
#endif
btn_press =millis();
}
// Сброс флага нажатия кнопкиif (digitalRead(BTN) && btn_flag ==true)
{
btn_flag =false;
fnDebounce(TMDEBOUNCE); // Антидребезг
}
}
while (millis() - btn_press < BTNPRES);
// Проверка значения делителя в EEPROM// Если там другое значение, то обновляемif (EEPROM.read(address) != n)
{
#ifdef SERIAL_ON
Serial.println(F("Запись в EEPROM нового делителя..."));
#endifEEPROM.write(address, n); // Записываем в EEPROMEEPROM.commit(); // Сохранение EEPROM
}
#ifdef SERIAL_ON
Serial.print(F("В EEPROM n="));
Serial.println(EEPROM.read(address));
#endif
}
else// Кнопка нажата быстро
{
flag_hold =!flag_hold;
if (flag_hold)
{
#ifdef SERIAL_ON
Serial.println(F("Режим: Hold"));
Serial.print(F("flag_hold="));
Serial.println(flag_hold);
#endif
}
else
{
#ifdef SERIAL_ON
Serial.println(F("Режим: Измерение"));
Serial.print(F("flag_hold="));
Serial.println(flag_hold);
#endif
}
fnPrintN();
}
btn_press =millis();
oled.invertDisplay(false);
}
// Проверка времени 1 секундаif (millis() - tmr_prev > 1000)
{
#ifdef SERIAL_ON
Serial.print(F("nn="));
Serial.print(nn);
Serial.print(F("\tn="));
Serial.print(n);
#endifif (!flag_hold)
rpm = (nn / n) * 60; // Обороты в минуту
nn = 0; // Обнуляем счётчик
tmr_prev =millis(); // Обнуляем время#ifdef SERIAL_ON
Serial.print(F("\trpm="));
Serial.println(rpm);
#endif
}
fnPrintOLED(); // Вывод информации// Сброс флага нажатия кнопкиif (digitalRead(BTN) && 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]);
}
delay(Z_PAUZA); // Пауза
oled.clear(); // Очистить дисплей
}
// end of fnZastavka()/************************************* Функция: Вывод на дисплей ************************************/void fnPrintOLED()
{
//oled.set1X();
oled.setScale(1);
oled.home();
oled.print(stitle[0]);
oled.print(" ");
oled.print(stitle[1]);
//oled.set2X();
oled.setScale(2);
oled.setCursor(8, 3);
oled.print(F("RPM:"));
oled.setCursor(56, 3);
oled.print(rpm); // Вывод оборотовif (rpm == 0)oled.print(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.setCursor(8, 7);
oled.print(F("N:"));
oled.print(n); // Количество импульсов на 1 оборот
oled.print(F(" "));
if (flag_hold && flag_prn_hold ==false)
{
oled.setCursor(45, 7);
oled.print(F("Hold"));
flag_prn_hold =true;
#ifdef SERIAL_ON
Serial.println(F("flag_prn_hold = true"));
#endif
}
else
{
if (flag_prn_hold &&!flag_hold)
{
oled.setCursor(45, 7);
oled.print(F(" "));
flag_prn_hold =false;
#ifdef SERIAL_ON
Serial.println(F("flag_prn_hold = false"));
#endif
}
}
}
// 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()
Конструкция
Для реализации устройства использую кусочек макетной односторонней платы размером 50*25мм. Дисплей, кнопка, заъём для датчика, выключатель и плата-адаптер для микроконтроллера.
Pic 2. Конструкция. Разные виды
Всё смонтировано на плату. Для подключения дисплея использую панельку, можно легко снять дисплей, для доступа к микроконтроллеру. Для программирования использую контакты (SWDIO, GND, VCC) на плате слева от кнопки.
Pic 3. Конструкция. Разные виды
Вид на плату с нижней стороны. Монтаж объёмными проводками. Это устройство единичное, нет смысла делать печатную плату.
Pic 4. Конструкция. Разные виды
На плату установлен дисплей, подключен датчик и проводки питания для проверки работоспособности смонтированной схемы.
Pic 5. Конструкция. Разные виды
Плата вставлена в коробок, склееный из пластика ABS (традиция). Размеры корпуса 56,5*38,5*30 мм.
Включено питание. В начальный момент на экран выводится название, версия, автор, дата прошивки.
Pic 6. Конструкция. Разные виды
Далее на экран выводится название и версия. Частота оборотов и делитель. В данный момент сигнала с датчика нет.
Pic 7. Конструкция. Разные виды
С датчика поступают импульсы и на экране выводится частота оборотов в RPM(об/мин).
Pic 8. Конструкция. Разные виды
Устройство питается от аккумулятора Li-Ion 600 мАч. Для его подзарядки выведен разъём платы заряда с левой стороны корпуса.
Pic 9. Конструкция. Разные виды
С правой стороны корпуса выведен разъём для подключения датчика A3144E.
Pic 10. Конструкция. Разные виды
Снизу корпуса выведено индикаторное стекло. При зарядке аккумулятора, свет индикатора красный, при заряженном аккумуляторе – зелёный.
Pic 11. Конструкция. Разные виды
Для проверки работоспособности используется игрушечный мотор Минато v7.2(изменение готовится к публикации: герконовый датчик заменён на датчик Холла A3144E).
Pic 12. Конструкция. Разные виды
Приложение
Все, что необходимо для повторения конструкции, можно скачать по ссылкам: