ARDUINO Блок опережения зажигания ДВС

PiratFox

★★★★★✩✩
13 Фев 2020
1,703
474
Вот именно из-за этого чаще всего выходят из строя кнопки во всяких мышках, особенно если инженеры поставили подобный конденсатор для уменьшения дребезга.
Начнём с того, что главная причина износа контактов - деформация и трение при замыкании/размыкании. И конденсатор ёмкостью сотню-другую нанофарад, да ещё при 5 вольтах, особо не ускорит износ. Если вообще повлияет. Слишком мизерная энергия для заметной электроэррозии. :)
@Эдуард Анисимов, камрад, ты пропустил важный фактор :) . В контактном зажигании конденсатор на контактах существенно увеличивает энергию искры.;) Потому без него плохо едет: топливо не всегда поджигается по причине слабой искры.
 
Изменено:

user314

★✩✩✩✩✩✩
26 Апр 2022
46
42
@Геннадий П,
А если комаров миллион, значит чтобы контакт их "почувствовал" их нужно три миллиарда. :) Я ещё раз вам повторяю, не сравнивайте микропереключатели и прерыватели предназначенные для коммутации катушек зажигания, это вещи, мягко говоря, разных весовых категорий. С таким же успехом мы можем докопаться до того, что вход микроконтроллера тоже имеет входную ёмкость, которая убивает контакт, а ещё линия от прерывателя до входа тоже имеет свою ёмкость.

Другими словами, в данном случае эта ёмкость имеет свойства статистической погрешности. Появилась она там уже впоследствии, при безуспешных попытках понять в чём причина ложных срабатываний, думаю теперь её даже можно убрать как возможно, бесполезную вещь, но во-первых мне уже лень ковырять плату, а во-вторых - пусть будет.

Главное то, что причина ложных срабатываний подавляется программным алгоритмом, остальное уже маловажно.
 
  • Лойс +1
Реакции: UJV 5901

PiratFox

★★★★★✩✩
13 Фев 2020
1,703
474
думаю теперь её даже можно убрать как возможно, бесполезную вещь
Ну, почему же прям бесполезную. Ёмкость эта давит наводки на входе. Насколько хорошо - другой вопрос, но всё жеж какая-то польза есть. :)
 

Artip

✩✩✩✩✩✩✩
19 Сен 2023
6
3
@user314, добрый день! Не подскажете, что нужно изменить в коде, чтобы заработало с платой Digispark по схеме с custom cult? Исходник с их сайта у меня не заработал, к тому же заметил, что формирователь их скетча не меняет углы при внесении изменений на сайте. С коммутатором и платой всё в порядке, мот даже заводится с ними, хоть и с трудом и нет опережения
 

user314

★✩✩✩✩✩✩
26 Апр 2022
46
42
@Artip,
Причин там может быть много и они могут быть индивидуальные. В их скетче я не разобрался до конца поэтому написал свой. У меня условия для состояния "нуля" и "единицы" разделены в коде для большей читабельности.

Вы вполне можете использовать мой скетч, но так как скорее всего у вас БСЗ, вам нужно убрать строки "антидребезга", подставить команды на вкл/выкл порта специфичные для процессоров atmega (они есть в комментариях) и выяснить, нужно ли инвертировать сигнал с датчика или нет.

Осмелюсь предположить, что вам не нужно инвертировать сигнал и скорее всего получается что-то типа вот этого:

C++:
#define IN 0
#define OUT 1

bool interruptor;
bool oldInterruptor;
bool advanceGranted;

unsigned long fuseTime;
unsigned long newTime;
unsigned long oldTime;
unsigned long midTime;
unsigned long periodTime;
unsigned long chargeTime;
unsigned long ignitionAdvance;

int ignitionDegree;
long int rpm;

void setup() {
  Serial.begin(115200);         // Start the Serial communication to send messages to the computer
  Serial.println("\n");

  oldTime = micros(); // сразу нужно установить, чтобы readAll() не споткнулся
  fuseTime = micros(); // устанавливается время до выключения катушки

  pinMode(IN, INPUT); // от БСЗ
  pinMode(OUT, OUTPUT); // к коммутатору
}

void readAll() {
  newTime = micros();
  periodTime = newTime-oldTime; // длительность всего периода (от размыкания до размыкания)
  chargeTime = newTime-midTime; // отрезок цикла от замыкания контакта до размыкания (зарядка катушки)
  rpm = 60000000/periodTime; // число оборотов
  oldTime = micros();
}

void loop() {
  interruptor = digitalRead(IN);

  if(interruptor == LOW && oldInterruptor == HIGH) { // шторка открылась
    digitalWrite(OUT, LOW); // отключаем катушку в любом случае (ИСКРА) LOW;
    oldInterruptor = interruptor; // oldInterruptor = LOW
    readAll(); // подсчёт всех параметров цикла
    // калькулятор положения коленвала на всякий случай: http://www.torqsoft.net/piston-position.html#gudgeon
    // примерный расчёт коэфициентов взят отсюда: https://customcult.netlify.app/
    if(rpm >= 0 && rpm <= 700) {
      ignitionDegree = 0;
      goto bailout;
    }
    if(rpm >= 700 && rpm <= 1000) { // 0 - 2.2
      ignitionDegree = 0.007 * rpm + (-4.800);
      goto bailout;
    }
    if(rpm >= 1000 && rpm <= 2000) { // 2.2 - 15
      ignitionDegree = 0.013 * rpm + (-10.800);
      goto bailout;
    }
    if(rpm >= 2000 && rpm <= 3000) { // 15 - 30
      ignitionDegree = 0.015 * rpm + (-15.000);
      goto bailout;
    }
    if(rpm >= 3000 && rpm <= 4000) { // 30 - 40
      ignitionDegree = 0.010 * rpm + (0.000);
      goto bailout;
    }
    if(rpm > 4000) {
      ignitionDegree = 40;
    }
    bailout:
    ignitionAdvance = chargeTime - ((periodTime / 360) * ignitionDegree);
    fuseTime = micros(); // начало отсчёта для предохранителя (отключение катушки зажигания при простое)
  }

  if(interruptor == HIGH && oldInterruptor == LOW) { // шторка закрылась
    digitalWrite(OUT, HIGH); // включаем катушку (ЗАРЯДКА) HIGH;
    oldInterruptor = interruptor; // oldInterruptor = HIGH
    midTime = micros(); // отметка начала зарядки катушки (середина цикла)
    advanceGranted = 1; // блок опережения разрешён
  }

  if(micros() - midTime >= ignitionAdvance && advanceGranted == 1) { // собственно само опережение
    digitalWrite(OUT, LOW); // отключаем катушку (ИСКРА) LOW;
    advanceGranted = 0; // больше сюда не возвращаемся до следующего цикла.
  }

  if(micros() - fuseTime >= 1000000) { // предохранитель, если катушка заряжается больше 1 секунды
    digitalWrite(OUT, LOW); // отключаем катушку LOW;
  }
}
 
  • Лойс +1
Реакции: UJV 5901 и Artip

Artip

✩✩✩✩✩✩✩
19 Сен 2023
6
3
@user314, да, всё верно, у меня установлена БСЗ по "классической схеме", то есть коммутатор от 2108, только с оптическим датчиком, который работает так же, как и датчик Холла - подает на минус на сигнальный вход при выходе модулятора из зоны свечения светодиода. Большое спасибо за скетч, попробую в нем разобраться и протестировать. Считаю, что данная тема очень интересна для наших довольно древних двигателей.
 
  • Лойс +1
Реакции: UJV 5901

Artip

✩✩✩✩✩✩✩
19 Сен 2023
6
3
@user314, не заработало. Точнее - сначала получилось запустить двигатель, но с нескольких попыток, хоть он и был прогрет и без ФУОЗ заводился "с полпинка". Пару раз так запускал, потом решил проверить, что же получается с искроподачей в первые попытки запуска и далее. А ничего - искры просто не было, как я ни крутил двигатель киком. После проверки двигатель перестал запускаться вообще. Без ФУОЗ всё работает штатно. Возможно, какой-нибудь счетчик не даёт плате подавать сигнал на выход? Что-то я не разобрался в Вашем скетче - не хватает знаний в программировании. Похоже, что надо досмотреть курсы)
 
  • Лойс +1
Реакции: UJV 5901

user314

★✩✩✩✩✩✩
26 Апр 2022
46
42
@Artip,

Тогда после сброса платы искра снова должна появиться. Можно попробовать убрать последний абзац:

C++:
  if(micros() - fuseTime >= 1000000) { // предохранитель, если катушка заряжается больше 1 секунды
    digitalWrite(OUT, LOW); // отключаем катушку LOW;
  }
Он вам всё равно ни к чему. Но вряд ли дело в нём. Если сначало было, а потом пропало - значит что-то где-то отвалилось.

Думаю, пытаться накодить рабочий скетч без опыта - почти невозможная задача, сам с этим столкнулся.
Поэтому рекомендую вооружиться симуляторами, например tinkercad . com

2023-11-05_19_38_36.png

На вход 2 arduino подаётся меандр с частотой 100 герц (что соответствует 6000 оборотов), на выходе 4 видим тоже меандр, но с большей скважностью, что говорит о работе ФУОЗ.

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

Заводиться хуже может по той простой причине что на первых оборотах алгоритм только определяет длительность входного импульса, а на выход подаётся случайная абракадабра, иногда может даже чихать по понятным причинам, в симуляторе proteus это очень хорошо видно, так как там можно сравнить входные и выходные данные с самых первых моментов. Я пока с этим никак не боролся, потому что на работу саму по себе оно никак не влияет.
 
Изменено:
  • Лойс +1
Реакции: UJV 5901

Artip

✩✩✩✩✩✩✩
19 Сен 2023
6
3
@user314, ооо, Тинкеркад, знаком с сайтом по пробам 3d моделирования (даже успешным). Попробую потестить там, спасибо. Возможно, будь стартер на мотаке электрическим а не киком, двигатель бы и дальше запускался. Может, у меня просто нога устала, хоть и очень старался. Плату перезагружал несколько раз, в перерывах подключая и запуская двигатель с коммутатором без платы. В любом случае спасибо за ответ)
 

Artip

✩✩✩✩✩✩✩
19 Сен 2023
6
3
@user314, Попробовал смодулировать в Тинкеркад, опять засада - пишет, что есть некая ошибка в коде
 

Вложения

  • Ахах! +1
Реакции: Wan-Derer

Artip

✩✩✩✩✩✩✩
19 Сен 2023
6
3
@Брякомякс, :unsure:, точно, ночью и не понял, что браузер автоматически переводит код)) Отключил, запустилось
 

user314

★✩✩✩✩✩✩
26 Апр 2022
46
42
Пришло время поделиться своими наработками в области зажигания. В этот раз суть вся таже, только вместо контакта используется оптодатчик. Вся логика схемы и прошивки при этом сохраняется как на контактном.
esp8266_ignition_BSZ.GIF

В качестве датчика используется датчик отражения TCRT5000

PART-TCRT5000_1-1024x768.jpg

Отражатель выпилен под 120 градусов.

reflector_t200m.JPG

Плата датчика и коммутатора (превью, ничего зеркалить не надо, оригиналы sprint layout во вложении в zip архиве)

BSZ_T200m.JPG commutator_T200M.JPG

Внешний вид мотоцикла и всего этого в мотоцикле:

IMG_20240928_122716.jpgIMG_20240928_122738.jpgIMG_20240928_122830.jpgIMG_20240928_123031.jpgIMG_20240928_123042.jpg

Ну и наконец, прошивка ESP8266 с функцией ФУОЗ на основе калькулятора от customcult:

C++:
#define IN 4
#define OUT 5

bool detector;
bool oldDetector;
bool advanceGranted;

unsigned long fuseTime;
unsigned long newTime;
unsigned long oldTime;
unsigned long midTime;
unsigned long periodTime;
unsigned long chargeTime;
unsigned long ignitionAdvance;

int ignitionDegree;
long int rpm;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(115200);         // Start the Serial communication to send messages to the computer
  Serial.println("\n");

  oldTime = micros(); // сразу нужно установить, чтобы readAll() не споткнулся
  fuseTime = micros(); // устанавливается время до выключения катушки

  pinMode(IN, INPUT); // D2 от прерывателя
  pinMode(OUT, OUTPUT); // D1 к драйверу
  pinMode(2, OUTPUT); // LED
  digitalWrite(2, LOW); // LED ENABLED
}

void readAll() {
  newTime = micros();
  periodTime = newTime-oldTime; // длительность всего периода (от закрытия до закрытия)
  chargeTime = newTime-midTime; // отрезок цикла от открытия шторки до закрытия (зарядка катушки)
  rpm = 60000000/periodTime; // число оборотов
  oldTime = micros();
}


/* Схема оптодатчика https://www.drive2.ru/b/481766688824492718/
* Если светодиод светится, на выходе с датчика - 0
* Если светодиод не светится, на выходе с датчика - 1
* Логика инверсная: на входе коммутатора - 0, на выходе коммутатора - 1.
* Роль отражателя выполняет сектор 120 градусов.
        x---------x------------------------------x-----------x---------> + 12V от батареи
        |         |                              |           |
        |         |      к входу ардуино/esp     |           |
        |         |          *                  | | 10kOm    |
       | | 1kOm  | | 63kOm   |                  | |          |
       | |       | |         |                   |           |
        |         |         \ / D1              \ / Led1     |
        |         |         ---                 ---         ---  C1 100nf
        |оптопара |          |                   |          ---
        |         |----.     |-------------------x           |
       \ /  ->  |/     |__ |/                                |
       ---  ->  |\         |\  Q1                            |
        |         |          |                               |
        |         |          |                               |
        |         |          |                               |
        x---------x----------x-------------------------------x---------* GND
*/


/*
* Все измерения производятся относительно состояния датчика.
* Логика инверсная: если на входе контроллера 0, то на выходе 1.
* Когда шторка открыта (Led1 горит), на входе контроллера 0.
* chargeTime - период зарядки КЗ, если нет опережения.
* ignitionAdvance - период зарядки КЗ, если есть опережение.
* под delay подразумевается delayMicroseconds(periodTime/5);
             oldTime                   midTime             newTime
                \                         |                  /
                 \                        |                 /
                  x===================x   |                x
                  #                   #   |                #
                  #                   #   |                #
                  #       HIGH        #   |     LOW        #
                  #                   #  /                 #
                  #                   # /                  #
                  #                   #/                   #
                  x                   x====================x
                               
                                      <-ignitionAdvance->
                             
                                      <-----chargeTime----->
                           
                  <-delay->           <-delay->
             
                  <---------------periodTime--------------->
*/

void loop() {
  detector = digitalRead(IN);

  if(detector == HIGH && oldDetector == LOW) { // момент когда шторка закрылась
    GPOC = (1 << OUT); // отключаем катушку в любом случае (ИСКРА) LOW; digitalWrite(OUT, LOW);
    oldDetector = detector; // oldDetector = HIGH
    readAll(); // подсчёт всех параметров цикла
    // калькулятор положения коленвала на всякий случай: http://www.torqsoft.net/piston-position.html#gudgeon
    // примерный расчёт коэфициентов взят отсюда: https://customcult.netlify.app/
    if(rpm >= 0 && rpm <= 700) {
      ignitionDegree = 0;
      goto bailout;
    }
    if(rpm >= 700 && rpm <= 1000) { // 0 - 2.2
      ignitionDegree = 0.007 * rpm + (-4.800);
      goto bailout;
    }
    if(rpm >= 1000 && rpm <= 2000) { // 2.2 - 15
      ignitionDegree = 0.013 * rpm + (-10.800);
      goto bailout;
    }
    if(rpm >= 2000 && rpm <= 3000) { // 15 - 30
      ignitionDegree = 0.015 * rpm + (-15.000);
      goto bailout;
    }
    if(rpm >= 3000 && rpm <= 4000) { // 30 - 40
      ignitionDegree = 0.010 * rpm + (0.000);
      goto bailout;
    }
    if(rpm > 4000) {
      ignitionDegree = 40;
    }
    bailout:
    ignitionAdvance = chargeTime - ((periodTime / 360) * ignitionDegree);
    fuseTime = micros(); // начало отсчёта для предохранителя (отключение катушки зажигания при простое)
    // антисбой датчика
    delayMicroseconds(periodTime/5);
  }

  if(detector == LOW && oldDetector == HIGH) { // момент когда шторка открылась
    GPOS = (1 << OUT); // включаем катушку (ЗАРЯДКА) HIGH; digitalWrite(OUT, HIGH);
    oldDetector = detector; // oldDetector = LOW
    midTime = micros(); // отметка начала зарядки катушки (середина цикла)
    advanceGranted = 1; // блок опережения разрешён
    // антисбой датчика
    delayMicroseconds(periodTime/5);
  }

  if(micros() - midTime >= ignitionAdvance && advanceGranted == 1) { // собственно само опережение
    GPOC = (1 << OUT); // отключаем катушку (ИСКРА) LOW; digitalWrite(OUT, LOW);
    advanceGranted = 0; // больше сюда не возвращаемся до следующего цикла.
  }

  if(micros() - fuseTime >= 1000000) { // предохранитель, если катушка заряжается больше 1 секунды
    GPOC = (1 << OUT); // отключаем катушку LOW; digitalWrite(OUT, LOW);
  }
}
Я откатался на ней несколько сотен километров, у неё есть недостатки:
1. постоянное вычисление угла при помощи сложных операций, которые грузят проц.
2. не решена проблема первых импульсов, это когда алгоритм опережения ещё не определил реальные обороты и выдаёт ерундистику на выход

Далее, идут скетчи с решённым проблемами, но я на них не ездил толком, испытания ещё предстоят.
Итак, скетч с таблицей опережения, с решённой проблемой первых импульсов и синхронной защитой от ложных срабатываний датчика:


C++:
#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;   // Флаг, разрешающий выполнение опережения зажигания

// Переменные для расчёта времени в микросекундах
unsigned long fuseTime = 0,           // Программный предохранитель: время, через которое катушка отключается для защиты
              oldTime = 0,            // Время предыдущего цикла (для расчёта периода)
              midTime = 0,            // Время начала зарядки катушки
              periodTime = 0,         // Период одного полного оборота вала двигателя
              chargeTime = 0,         // Время, в течение которого катушка заряжается
              ignitionAdvance = 0,    // Время опережения зажигания (вычисляется на основе оборотов rpm)
             
              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() {
  detector = digitalRead(IN);                // Считываем текущее состояние датчика положения

  /**
   * Проверяет, если датчик переключился с низкого уровня на высокий.
   * Это может означать, что сигнал от датчика положения поступил.
   */
  if (detector == HIGH && oldDetector == LOW) {
    controlCoil(false);                      // Выключаем катушку
    oldDetector = detector;                  // Обновляем предыдущее состояние датчика
    readAll();                               // Считываем параметры цикла
    if (skipImpulses < SKIP) {
      skipImpulses++;
      return;  // Пропускаем обработку до следующего импульса
    }
    setIgnition();                           // Установка угла опережения
    delayMicroseconds(periodTime/5);         //
    return;                                  //
  }

  /**
   * Проверяет, если датчик переключился с высокого уровня на низкий.
   * Это может означать, что сигнал от датчика завершён.
   */
  if (detector == LOW && oldDetector == HIGH) {
    controlCoil(true);                       // Включаем катушку (начало зарядки)
    oldDetector = detector;                  // Обновляем предыдущее состояние датчика
    midTime = micros();                      // Фиксируем время начала зарядки
    fuseTime = micros();                     // Обновляем время для программного предохранителя
    if (skipImpulses < SKIP) {
      return;  // Пропускаем обработку первого импульса
    }
    advanceGranted = true;                   // Разрешаем выполнять опережение зажигания
    delayMicroseconds(periodTime/5);         //
    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; // переход в режим предстарта
  }
}
И далее всё тоже самое, но вместо "delayMicroseconds(periodTime/5);" применена асинхронная задержка, насчёт скетча ниже я не уверен совсем, но испытания предстоят в будущем сезоне:

C++:
#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; // переход в режим предстарта
  }
}

Датчик выставлен на 0 градусов.
 

Вложения

Изменено:
  • Лойс +1
Реакции: UJV 5901

UJV 5901

✩✩✩✩✩✩✩
25 Мар 2024
19
0
@user314,здравствуйте!,уважаемый!,спасибо!,Вам!,большое!,за Вашу,разработку!.В zip архиве только отражатель,платы датчика и коммутатора в zip архиве нет.С уважением.
 

UJV 5901

✩✩✩✩✩✩✩
25 Мар 2024
19
0
@user314,Здравствуйте!,многоуважаемый!,спасибо!,Вам,за подсказку,теперь я разобрался,да,вкладки я нашёл.Вопрос,ещё к Вам,на АРДУИНО НАНО можно такое реализовать?,просто плата АРДУИНО НАНО у меня имеется,такой платы как у Вас у меня нет.И,ещё вопрос:отражатель можно на 60 градусов применить?,если можно,то что в коде надо изменить?.У меня мотороллер муравей.Заранее,Вам спасибо,за Ваш ответ.С уважением.
 

user314

★✩✩✩✩✩✩
26 Апр 2022
46
42
@UJV 5901,

Чтобы запустить прошивку на arduino nano нужно поменять строки 1 и 2 на свои значения, в зависимости от пинов входа и выхода с платы.

C++:
#define IN 4
#define OUT 5
Далее заменить строки 98, 134, 143 и 148 на "digitalWrite(OUT, LOW);" в зависимости от того что указано в комментарии.

И ещё пин для светодиода в строке 29, если нужно чтобы показывал индикацию питания.

По поводу модулятора, у меня используется датчик отражения. Когда "модулятор" входит в режим отражения, КЗ отключается и происходит искра. В этот момент на выходе датчика у нас логическая единица.

Если у вас после предполагаемого момента искры на выходе датчика 1, то в коде ничего менять не нужно, если же у вас 0, то нужно в тех же строках 98, 134, 143 и 148 поменять состояния на обратные.

Касательно конструкции модулятора, думаю 60 градусов должно хватать чтобы успевать полностью разряжать КЗ.
 
  • Лойс +1
Реакции: UJV 5901

UJV 5901

✩✩✩✩✩✩✩
25 Мар 2024
19
0
@user314,здравствуйте!,спасибо!,Вам!,огромное!,за Ваш ответ!.У меня сейчас БСЗ установлено на мотороллере муравей,оптический датчик на отражение,и коммутатор ВАЗ 2108,шторка-модулятор на 60 градусов,и ещё один вопрос:к коммутатору ВАЗ 2108 можно применить,Ваш ФУОЗ?,будет эта связка работать?,или может под эту связку Ваш ФУОЗ и коммутатор ВАЗ 2108,необходимо,что-то в коде поменять?,если это надо,то что именно в коде надо изменить?.Заранее Вам спасибо,за Ваш ответ,и прошу прощения у Вас,за то что вопросами я,своими Вам докучаю.Ещё раз,спасибо!,Вам огромное!,за Ваши ответы.С уважением.
 

user314

★✩✩✩✩✩✩
26 Апр 2022
46
42
@UJV 5901,
Чтобы что-то изменить, нужно понимать логику работы схемы. Вам нужно на вашем рабочем зажигании определить когда происходит искра, в момент когда свет попадает на фототранзистор, или наоборот, модулятор открывает лопасть.
 
  • Лойс +1
Реакции: UJV 5901

UJV 5901

✩✩✩✩✩✩✩
25 Мар 2024
19
0
@user314,Искра происходит в момент когда свет прекращается.
 

UJV 5901

✩✩✩✩✩✩✩
25 Мар 2024
19
0
@user314,Искра происходит,при выходе модулятора,из оптопары.
 

user314

★✩✩✩✩✩✩
26 Апр 2022
46
42
@UJV 5901,
На выходе с датчика логический 0 или 1 после искры? Замерять нужно напряжение непосредственно с выхода датчика, так чтобы коммутатор был подключен.
 
  • Лойс +1
Реакции: UJV 5901

UJV 5901

✩✩✩✩✩✩✩
25 Мар 2024
19
0
@user314,После искры,на выходе оптодатчика логический 0.Опишу точнее,если на выходе оптодатчика логическая 1,то катушка зажигания подключена к электропитанию,и она накапливает энергию,когда на выходе оптодатчика появляется логический 0,то катушка зажигания,мгновенно отключается от электропитания,и ровно в этот момент её отключения,происходит искра,в свече зажигания.