#define IN 4 // Входной пин для подключения датчика
#define OUT 5 // Выходной пин для управления катушкой зажигания
#define LED 2
const int SKIP = 5;
const long FUSE_TIMER = 1000000; // 1 секунда в микросекундах
// Переменные для логики зажигания
bool detector = false, // Текущее состояние датчика положения (шторка открыта/закрыта)
oldDetector = false, // Предыдущее состояние датчика положения
advanceGranted = false, // Флаг, разрешающий выполнение опережения зажигания
asyncDelay = false;
// Переменные для расчёта времени в микросекундах
unsigned long fuseTime = 0, // Программный предохранитель: время, через которое катушка отключается для защиты
oldTime = 0, // Время предыдущего цикла (для расчёта периода)
midTime = 0, // Время начала зарядки катушки
periodTime = 0, // Период одного полного оборота вала двигателя
chargeTime = 0, // Время, в течение которого катушка заряжается
ignitionAdvance = 0, // Время опережения зажигания (вычисляется на основе оборотов rpm)
asyncDelayPoint = 0,
rpm = 0; // Текущие обороты двигателя (об/мин)
int ignitionDegree = 0, // Угол опережения зажигания в градусах
pwmValue = 255, // ШИМ чтобы плавно отключать катушку в секции программного предохранителя
skipImpulses = 0;
const struct {
int minRPM, maxRPM, ignitionDegree;
} rpmTable[] = {
{0, 700, 0}, // На холостом ходу (0-700 об/мин) зажигание без опережения
{700, 800, 1}, // Умеренные обороты: минимальное опережение
{800, 900, 2}, // Увеличение угла опережения зажигания по мере роста оборотов
{900, 1000, 3}, // ...
{1000, 1100, 4},
{1100, 1200, 5},
{1200, 1300, 6},
{1300, 1400, 7},
{1400, 1500, 8},
{1500, 1600, 9},
{1600, 1700, 10},
{1700, 1800, 11},
{1800, 1900, 12},
{1900, 2000, 13},
{2000, 2100, 14},
{2100, 2200, 15},
{2200, 2300, 16},
{2300, 2400, 17},
{2400, 2500, 18},
{2500, 2600, 19},
{2600, 2700, 20},
{2700, 2800, 21},
{2800, 2900, 22},
{2900, 3000, 23},
{3000, 3100, 24},
{3100, 3200, 25},
{3200, 3300, 26},
{3300, 3400, 27},
{3400, 3500, 28},
{3500, 3600, 29},
{3600, 3700, 30},
{3700, 3800, 31},
{3800, 3900, 32},
{3900, 4000, 33}, // Максимальный угол опережения достигается на 4000 об/мин
{4000, 999999, 33} // Для всех оборотов выше 4000 угол остаётся максимальным
};
void setup() {
Serial.begin(115200);
pinMode(IN, INPUT); // Конфигурируем пин IN как вход для сигнала от датчика положения
pinMode(OUT, OUTPUT); // Конфигурируем пин OUT как выход для управления катушкой зажигания
pinMode(LED, OUTPUT); // Пин 2 используется для LED (индикация состояния системы)
digitalWrite(LED, LOW); // Выключаем LED при инициализации
oldTime = micros(); // Инициализация времени для расчётов периода вращения
fuseTime = micros(); // Устанавливаем начальное время для программного предохранителя
}
/**
* Считывает параметры цикла: период, обороты и время зарядки катушки.
* Используется для анализа времени полного оборота вала двигателя.
*/
void readAll() {
unsigned long newTime = micros(); // Текущее время в микросекундах
periodTime = newTime - oldTime; // Вычисляем период вращения (время одного полного оборота)
chargeTime = newTime - midTime; // Вычисляем время зарядки катушки
rpm = 60000000 / periodTime; // Расчёт оборотов двигателя (об/мин)
oldTime = newTime; // Обновляем старое время для следующего цикла
}
/**
* Возвращает угол опережения зажигания на основе текущих оборотов двигателя.
* Использует карту зажигания (таблицу соответствия оборотов и углов).
*/
int getIgnitionDegree(long rpm) {
// Поиск угла опережения на основе оборотов из таблицы
for (int i = 0; i < sizeof(rpmTable) / sizeof(rpmTable[0]); ++i) {
if (rpm >= rpmTable[i].minRPM && rpm < rpmTable[i].maxRPM) {
return rpmTable[i].ignitionDegree; // Возвращаем угол для данного диапазона оборотов
}
}
return 0; // Если обороты не входят в диапазон таблицы, возвращаем 0
}
/**
* Вычисляет время опережения зажигания, основываясь на угле опережения и текущем периоде вращения.
* Используется для управления моментом искрообразования.
*/
void setIgnition() {
ignitionDegree = getIgnitionDegree(rpm); // Получаем угол опережения из таблицы
ignitionAdvance = chargeTime - ((periodTime / 360) * ignitionDegree); // Расчёт времени опережения
}
/**
* Управляет состоянием катушки зажигания (включение или выключение).
*/
void controlCoil(bool state) {
// Управление КЗ
if (state) GPOS = (1 << OUT); // включаем катушку (ЗАРЯДКА) HIGH; digitalWrite(OUT, HIGH);
else GPOC = (1 << OUT); // отключаем катушку в любом случае (ИСКРА) LOW; digitalWrite(OUT, LOW);
}
void loop() {
/**
* асинхронная задержка если asyncDelay = true;
*/
if (asyncDelay) {
// Если в блоках проверки состояния датчика включена задержка, она равна periodTime / 5
if (micros() - asyncDelayPoint < periodTime / 5) {
return; // Продолжаем ждать, ничего не делаем
}
asyncDelay = false; // Завершаем паузу
// goto that;
}
detector = digitalRead(IN); // Считываем текущее состояние датчика положения
// that:
/**
* Проверяет, если датчик переключился с низкого уровня на высокий.
* Это может означать, что сигнал от датчика положения поступил.
*/
if (detector == HIGH && oldDetector == LOW) {
controlCoil(false); // Выключаем катушку
oldDetector = detector; // Обновляем предыдущее состояние датчика
readAll(); // Считываем параметры цикла
if (skipImpulses < SKIP) { // Пропустить первые ROUND импульсов
skipImpulses++;
return; // Пропускаем обработку до следующего импульса
}
setIgnition(); // Установка угла опережения
asyncDelayPoint = micros(); //
asyncDelay = true; // Асинхронная задержка, нужна если у вас такой же дубовый датчик как у меня
return; //
}
/**
* Проверяет, если датчик переключился с высокого уровня на низкий.
* Это может означать, что сигнал от датчика завершён.
*/
if (detector == LOW && oldDetector == HIGH) {
controlCoil(true); // Включаем катушку (начало зарядки)
oldDetector = detector; // Обновляем предыдущее состояние датчика
midTime = micros(); // Фиксируем время начала зарядки
fuseTime = micros(); // Обновляем время для программного предохранителя
if (skipImpulses < SKIP) {
return; // Пропускаем обработку первого импульса
}
advanceGranted = true; // Разрешаем выполнять опережение зажигания
asyncDelayPoint = micros(); //
asyncDelay = true; // Асинхронная задержка, нужна если у вас такой же дубовый датчик как у меня;
return; //
}
/**
* Проверяет, прошло ли достаточно времени для отключения катушки зажигания
* с учётом установленного угла опережения зажигания.
* Если время прошло и блок опережения разрешён, катушка выключается.
*/
if (micros() - midTime >= ignitionAdvance && advanceGranted) {
controlCoil(false); // Выключаем катушку (разряд для искры)
advanceGranted = false; // Сбрасываем флаг опережения
}
/**
* Когда двигатель простаивает но катушка находится в состоянии зарядки более 1 сек.
* Данный блок реализует плавное отключение катушки после секунды и предотвращение искры
* А так же переводит состояние алгоритма опережения в предстартовое
*/
if (micros() - fuseTime >= FUSE_TIMER) { // Контроль длительности зарядки катушки (не более 1 секунды)
// Постепенно уменьшаем значение ШИМ для плавного выключения катушки
if (pwmValue > 0) {
pwmValue--; // Уменьшаем скважность на 1 каждый цикл
analogWrite(OUT, pwmValue); // Обновляем ШИМ
} else {
// Когда pwmValue достигает 0, катушка полностью выключена
controlCoil(false); // Дополнительно подтверждаем, что катушка выключена
}
skipImpulses = 0; // переход в режим предстарта
}
}