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

UJV 5901

✩✩✩✩✩✩✩
25 Мар 2024
16
0
@user314,При этом,коммутатор подключен к оптодатчику.
 

user314

★✩✩✩✩✩✩
26 Апр 2022
38
28
Если я ничего не путаю, то должно быть так:

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(13, OUTPUT); // LED
  digitalWrite(13, LOW); // LED ENABLED
}

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

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

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

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

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

Первые строки поправьте под свои пины вход/выход. Рекомендую перед заводкой установить arduino на своё место, выкрутив свечу, подвигать взад/вперёд крыльчатку, убедиться что искра происходит в нужный момент.

Затем установить момент искры в положение ВМТ.

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

UJV 5901

✩✩✩✩✩✩✩
25 Мар 2024
16
0
@user314,Если,я Вас,правильно понял,то у Вас самые минимальные обороты Вашего двигателя это 700 оборотов в минуту,ниже уже двигатель глохнет?.
 

UJV 5901

✩✩✩✩✩✩✩
25 Мар 2024
16
0
@user314,Здравствуйте!,понял,а так,какие самые минимальные обороты держит,Ваш двигатель?,у меня 700-800,ниже уже глохнет двигатель.
 

user314

★✩✩✩✩✩✩
26 Апр 2022
38
28
@UJV 5901,
Слишком много, у меня холостой ход на слух составляет ~ 200 - 250 оборотов/мин, на хорошо прогретом можно даже меньше. Если не держит, это либо недостаток компрессии, либо карбюратор не настроен.
 
  • Лойс +1
Реакции: UJV 5901

UJV 5901

✩✩✩✩✩✩✩
25 Мар 2024
16
0
@user314,Здравствуйте!,понял,просто я тахометр подключил,и решил узнать,какие холостые держит мой мотороллер муравей,тахометр Китайский цифровой,я не давно его приобрёл,а так много лет,я и не знал какие холостые обороты держит двигатель.Вы,совершенно правы,компрессия не очень хорошая,и ещё в добавок сальники коленвала,уже не важные,через левый сальник,и его алюминиевую проставку,(куда этот сальник крепится) вообще масло всасывается,довольно сильно,из коробки передач в кривошипную камеру,свечу забрасывает маслом нехило,проставка левого сальника вращается,как "рулетка в казино",в общем,в этом скорее всего проблема,то что самые минимальные обороты не держит двигатель,да и карбюратор тоже может вносить свои коррективы.Ещё раз,Вам огромное спасибо!,за Ваши ответы!.С уважением.
 

ukr100

✩✩✩✩✩✩✩
5 Окт 2023
9
0
@user314, Доброго дня, почитал ваши труды отличная и полезная работа, а не подскажите если ставить датчик холла А3144 что в коде поменять и второй вопрос код будет работать на оборотах 25000т.об ?
 

user314

★✩✩✩✩✩✩
26 Апр 2022
38
28
@ukr100,

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

25000 оборотов это примерно 400 Гц, сейчас попробовал подать на вход тестовый меандр и вроде всё работает. Но нужно учитывать, что у меня максимальный угол опережения (40 градусов) достигается уже при 4000 оборотах. Поэтому нужно смотреть по месту.

На днях планирую попробовать скетч прогнатый через нейросеть, со всевозможными оптимизациями и картой углов вместо вычислений в реальном времени. На стенде он работает лучше, но на мотоцикле ещё не тестил.
 
  • Лойс +1
Реакции: ukr100

ukr100

✩✩✩✩✩✩✩
5 Окт 2023
9
0
@user314, Спасибо за ответ вроде прошил мигает ! А еще вопрос, а я правильно понял если мне надо выставить начальный угол опережения скажем 60гр, на сайте https://customcult.netlify.app/ там где график в первой и ставить скажем -60 потом, 0....10....20...30, если нет а где выставляется ?
 

user314

★✩✩✩✩✩✩
26 Апр 2022
38
28
@ukr100,
Вам нужно на их сайте задать необходимые вам углы, скачать скетч и подставить в мой вариант значения из их скетча.
Ищите переменные ignitionDegree и подставляйте свои


C++:
    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;
    }
 
  • Лойс +1
Реакции: ukr100

ukr100

✩✩✩✩✩✩✩
5 Окт 2023
9
0
@user314, ага увидел пасиб ! А такой вопрос, я так понял у вас на максимуме 40гр, а это не занатто много, на сколько я знаю и видел по программам там 25 ну 30 максимум, причём это за 20000т об малых кубах, а на больших кубах 12...15 т.об там 20...25гр. максимум Если я правильно понял что у вас 40гр, кабы вы мотор не укотали детонацией ! это скрин из одной проги китайской
 

Вложения

user314

★✩✩✩✩✩✩
26 Апр 2022
38
28
@ukr100,
Меряю исключительно ощущениями, мотор советский, двухтактный, ехать стал значительно бодрее, на оборотах 4000 - 5000 (его оптимальных) никаких детонаций не ощущаю, вплане, нет никаких дополнительных вибраций или странных звуков, . Можно будет попробовать меньше. Я тупо содрал график с customcult.

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

Или, если подскажете, какой-то способ получать цифры детонации, то можно будет приделать и такой вариант.
 
Изменено:

ukr100

✩✩✩✩✩✩✩
5 Окт 2023
9
0
@user314,Скорее всего что вы на двух тактном детонации не услышите, он очень громко работает, нет шумовки у него в виде водяной рубашки и клапанов нет, вы это поймёте когда развалится коленвал из за обратного удара, ну как вариант снять головку глянуть на поршень нет ли раковин от детонации, но для этого надо проехать прилично, можно по свече определить, но опять же надо пробег что бы был. Ну и это двух тактник, там масло, фазы так себе, горение вместе с маслом сами понимаете, определить трудно, первое что хрякнет коленвал а именно мотылёвый подшипник. А по поводу ощущений, знаете есть такая штука плацебо, ну вы же поставили компьютер, сами написали прогу, сколько намучались ) оно однозначно должно быть лучше :sneaky:(y) Только на стенде можно понять даёт это что то или нет, ну вы этот вопрос по изучайте, я как бы 30лет в спорте, ездил на электронике правда буржуйской, там максимум 25гр, найдите там у них есть инструкция полистайте https://www.power-spark.de/index_en.html
 

Вложения

user314

★✩✩✩✩✩✩
26 Апр 2022
38
28
@ukr100,
Сравнивал путём подъёма в гору, там и без измерений стало ясно, что ФУОЗ нужен. Касательно свечи, у меня она бывает либо тёмно-красного цвета, либо чёрного, если масла многовато или качество бензина подкачало. Когда мастерил ФУОЗ ориентировался именно на цвет и удивился, что цвет красный, я такого не видел на этих моторах.
 

ukr100

✩✩✩✩✩✩✩
5 Окт 2023
9
0
@user314, Красный, потому что в бензине присадки много, воруют, присадками детонацию убирают не детонирует но и не едет, по качеству бензина навряд ли, вангую у вас камера всё равно разжата, про масло то же сказки, я лью 18% свеча после 30ти минут гонки как новая, можно в коробку и в магазин ) Если светлая то это не очень хорошо бедная смесь, для двухтактника у которого смазка в топливе плохо, в идеале светло коричневая
 

user314

★✩✩✩✩✩✩
26 Апр 2022
38
28
Только на стенде можно понять даёт это что то или нет, ну вы этот вопрос по изучайте, я как бы 30лет в спорте, ездил на электронике правда буржуйской, там максимум 25гр, найдите там у них есть инструкция полистайте https://www.power-spark.de/index_en.html
Что и где там я конкретно должен увидеть, там только про коммерцию
 

ukr100

✩✩✩✩✩✩✩
5 Окт 2023
9
0
@user314, зайдите в downloads скачайте инструкцию, там есть цифры и графики

@user314, ну это крохи но для понимания обороты и угол при этих оборотах, и скорости горения хватает при таком угле, ну это так то на чём испытано, хотя увидел ваши труды аж интересно стало стенд у меня есть надо будет попробовать чё будет ))
 

Вложения

user314

★✩✩✩✩✩✩
26 Апр 2022
38
28
@ukr100,
Вот моя свеча, мотор проехал 7- 10 километров, глушил за 40 - 50 метров перед домом.
IMG_20241115_222912_1.jpg

Ну да, черновата, но другого бензина нет, а свеча на этом бензе засерается не только у меня, проверено.

Эта свеча проехала километров эдак 150 на этом моторе, никаких странных цветов или разрушений не замечено.

Поставлю 30 градусов, возможности проверить или не верить всё равно не имеется.



Если интересно, я сейчас тестирую вот такой скетч, разумеется он заточен под мою схему из этого поста, но легко можно подправить под стандартный вариант датчик->arduino->коммутатор->КЗ:


C++:
#define IN 4    // Входной пин (датчик)
#define OUT 5   // Выходной пин (катушка)

bool detector = false, oldDetector = false, advanceGranted = false;
unsigned long fuseTime, oldTime, midTime, periodTime, chargeTime, ignitionAdvance;
int ignitionDegree = 0;
long rpm = 0;

unsigned long lastActionTime = 0;  // Время последнего действия
unsigned long antiChatterDelay = 0; // Задержка для антисбой (как delayMicroseconds(periodTime / 5))
unsigned long lastCoilAction = 0;  // Время последнего действия с катушкой

bool antiChatterEnabled = trueутпдшырвутenasd;  // Флаг для включения/выключения функции антисбой

// Структура для хранения диапазона оборотов и соответствующего угла опережения
struct RPMRange {
  int minRPM;
  int maxRPM;
  int ignitionDegree;
};

// Карта значений для угла опережения зажигания (равномерное распределение углов от 0° до 33°)
const RPMRange rpmTable[] = {
  {0, 700, 0},       // 0-700 оборотов -> 0°
  {700, 800, 1},     // 700-800 оборотов -> 1°
  {800, 900, 2},     // 800-900 оборотов -> 2°
  {900, 1000, 3},    // 900-1000 оборотов -> 3°
  {1000, 1100, 4},   // 1000-1100 оборотов -> 4°
  {1100, 1200, 5},   // 1100-1200 оборотов -> 5°
  {1200, 1300, 6},   // 1200-1300 оборотов -> 6°
  {1300, 1400, 7},   // 1300-1400 оборотов -> 7°
  {1400, 1500, 8},   // 1400-1500 оборотов -> 8°
  {1500, 1600, 9},   // 1500-1600 оборотов -> 9°
  {1600, 1700, 10},  // 1600-1700 оборотов -> 10°
  {1700, 1800, 11},  // 1700-1800 оборотов -> 11°
  {1800, 1900, 12},  // 1800-1900 оборотов -> 12°
  {1900, 2000, 13},  // 1900-2000 оборотов -> 13°
  {2000, 2100, 14},  // 2000-2100 оборотов -> 14°
  {2100, 2200, 15},  // 2100-2200 оборотов -> 15°
  {2200, 2300, 16},  // 2200-2300 оборотов -> 16°
  {2300, 2400, 17},  // 2300-2400 оборотов -> 17°
  {2400, 2500, 18},  // 2400-2500 оборотов -> 18°
  {2500, 2600, 19},  // 2500-2600 оборотов -> 19°
  {2600, 2700, 20},  // 2600-2700 оборотов -> 20°
  {2700, 2800, 21},  // 2700-2800 оборотов -> 21°
  {2800, 2900, 22},  // 2800-2900 оборотов -> 22°
  {2900, 3000, 23},  // 2900-3000 оборотов -> 23°
  {3000, 3100, 24},  // 3000-3100 оборотов -> 24°
  {3100, 3200, 25},  // 3100-3200 оборотов -> 25°
  {3200, 3300, 26},  // 3200-3300 оборотов -> 26°
  {3300, 3400, 27},  // 3300-3400 оборотов -> 27°
  {3400, 3500, 28},  // 3400-3500 оборотов -> 28°
  {3500, 3600, 29},  // 3500-3600 оборотов -> 29°
  {3600, 3700, 30},  // 3600-3700 оборотов -> 30°
  {3700, 3800, 31},  // 3700-3800 оборотов -> 31°
  {3800, 3900, 32},  // 3800-3900 оборотов -> 32°
  {3900, 4000, 33},  // 3900-4000 оборотов -> 33°
  {4000, 999999, 33} // 4000+ оборотов -> 33° (максимальное значение)
};

const int tableSize = sizeof(rpmTable) / sizeof(rpmTable[0]); // Количество элементов в таблице

void setup() {
  pinMode(IN, INPUT);   // D2 от прерывателя (датчик)
  pinMode(OUT, OUTPUT); // D1 к драйверу катушки
  pinMode(2, OUTPUT);   // LED для визуализации
  digitalWrite(2, 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(int rpm) {
  // Поиск угла опережения на основе оборотов из таблицы
  for (int i = 0; i < tableSize; 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); // Включаем катушку
  else GPOC = (1 << OUT);        // Отключаем катушку
}

void loop() {
  detector = digitalRead(IN);  // Чтение входного сигнала от датчика

  // Обработка "закрытия" шторки
  if (detector == HIGH && oldDetector == LOW) { // шторка закрыта
    if (antiChatterEnabled) {
      // Выполним действия с катушкой, если прошло нужное время и функция антисбой включена
      if ((micros() - lastCoilAction) >= antiChatterDelay) {
        controlCoil(false); // отключаем катушку
        oldDetector = detector;
        readAll();           // Подсчёт всех параметров цикла
        setIgnition();       // Получение угла опережения
        fuseTime = micros(); // Начало отсчёта для предохранителя
        antiChatterDelay = periodTime / 5; // Установка времени для функции антисбой
        lastCoilAction = micros(); // Обновляем время последнего действия
      }
    } else {
      // Когда защита от сбоев выключена, просто отключаем катушку
      controlCoil(false);
      oldDetector = detector;
      readAll();
      setIgnition();
      fuseTime = micros();
    }
  }

  // Обработка "открытия" шторки
  if (detector == LOW && oldDetector == HIGH) { // шторка открыта
    if (antiChatterEnabled) {
      if ((micros() - lastCoilAction) >= antiChatterDelay) {
        controlCoil(true);  // включаем катушку
        oldDetector = detector;
        midTime = micros(); // начало зарядки катушки
        advanceGranted = true; // блок опережения разрешён
        antiChatterDelay = periodTime / 5; // Устанавливаем время для функции антисбой
        lastCoilAction = micros(); // Обновляем время последнего действия
      }
    } else {
      // Когда защита от сбоев выключена, сразу включаем катушку
      controlCoil(true);
      oldDetector = detector;
      midTime = micros();
      advanceGranted = true; // блок опережения разрешён
    }
  }

  // Опережение зажигания
  if (micros() - midTime >= ignitionAdvance && advanceGranted) {
    controlCoil(false); // Отключаем катушку
    advanceGranted = false; // Блок опережения выключен
  }

  // Предохранитель (если катушка заряжается более 1 секунды)
  if (micros() - fuseTime >= 1000000) {
    controlCoil(false); // Отключаем катушку
  }
}
Осциллограммы выглядят стабильнее (без "дёрганки"), здесь используется карта вместо постоянных вычислений угла и код полностью асинхронный, без задержек при выполнении цикла loop.
 
Изменено:
  • Лойс +1
Реакции: UJV 5901 и ukr100

ukr100

✩✩✩✩✩✩✩
5 Окт 2023
9
0
@user314, судя по свече горение неправильное, такого быть недолжно, его льёт, а что за мотор или транспорт ? Там пара не подсевшая ?

Поставлю 30 градусов, возможности проверить или не верить всё равно не имеется.
Тут судя по свече может быть и детонация, топливо не горит правильно, конечно если гильза +поршень+кольца без вопросов, ну и степень сжатия правильная. К стати спрошу по путно а вот в эту ATtiny85 что то еще засунуть можно, к примеру дисплей с вольтами или оборотами, будет достаточно 2е цифры, или то и другое с переключением, или у неё памяти едва на зажигалку хватает ?

Эта свеча проехала километров эдак 150 на этом моторе,
И еще масло на кольце нехорошо, компрессия слабая и вам приходится лить в него топливо что бы он ехал, ну то-е вы едете не на компрессии, а на перерасходе топлива.
 

Вложения

user314

★✩✩✩✩✩✩
26 Апр 2022
38
28
@ukr100,
мотор Т200 ТМЗ, карб недорегулирован, поршневая группа не новая. Так что всё нормально.
 
  • Лойс +1
Реакции: ukr100

user314

★✩✩✩✩✩✩
26 Апр 2022
38
28
К стати спрошу по путно а вот в эту ATtiny85 что то еще засунуть можно, к примеру дисплей с вольтами или оборотами, будет достаточно 2е цифры, или то и другое с переключением, или у неё памяти едва на зажигалку хватает ?
Схематически такая возможность есть https://arduinoprosto.ru/q/80290/attiny85-dht11-tm1637#gsc.tab=0

Но у меня attiny не имеется, поэтому проверить не могу, пока что.
 
  • Лойс +1
Реакции: UJV 5901

user314

★✩✩✩✩✩✩
26 Апр 2022
38
28
Итак, давайте под-итожим, заодно я для себя определюсь и буду каждый раз тыкать в этот пост всех, кто будет у меня спрашивать "а как мне переделать":

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

Логика скетча: датчик "1"->"0" = искра:

C++:
#define IN 0    // Входной пин (датчик)
#define OUT 1   // Выходной пин (катушка)

bool detector = false, oldDetector = false, advanceGranted = false;
unsigned long fuseTime, oldTime, midTime, periodTime, chargeTime, ignitionAdvance;
int ignitionDegree = 0;
long rpm = 0;

// Карта значений для угла опережения зажигания (равномерное распределение углов от 0° до 33°)
const struct {
  int minRPM, maxRPM, ignitionDegree;
} rpmTable[] = {
  {0, 700, 0},       // 0-700 оборотов -> 0°
  {700, 800, 1},     // 700-800 оборотов -> 1°
  {800, 900, 2},     // 800-900 оборотов -> 2°
  {900, 1000, 3},    // 900-1000 оборотов -> 3°
  {1000, 1100, 4},   // 1000-1100 оборотов -> 4°
  {1100, 1200, 5},   // 1100-1200 оборотов -> 5°
  {1200, 1300, 6},   // 1200-1300 оборотов -> 6°
  {1300, 1400, 7},   // 1300-1400 оборотов -> 7°
  {1400, 1500, 8},   // 1400-1500 оборотов -> 8°
  {1500, 1600, 9},   // 1500-1600 оборотов -> 9°
  {1600, 1700, 10},  // 1600-1700 оборотов -> 10°
  {1700, 1800, 11},  // 1700-1800 оборотов -> 11°
  {1800, 1900, 12},  // 1800-1900 оборотов -> 12°
  {1900, 2000, 13},  // 1900-2000 оборотов -> 13°
  {2000, 2100, 14},  // 2000-2100 оборотов -> 14°
  {2100, 2200, 15},  // 2100-2200 оборотов -> 15°
  {2200, 2300, 16},  // 2200-2300 оборотов -> 16°
  {2300, 2400, 17},  // 2300-2400 оборотов -> 17°
  {2400, 2500, 18},  // 2400-2500 оборотов -> 18°
  {2500, 2600, 19},  // 2500-2600 оборотов -> 19°
  {2600, 2700, 20},  // 2600-2700 оборотов -> 20°
  {2700, 2800, 21},  // 2700-2800 оборотов -> 21°
  {2800, 2900, 22},  // 2800-2900 оборотов -> 22°
  {2900, 3000, 23},  // 2900-3000 оборотов -> 23°
  {3000, 3100, 24},  // 3000-3100 оборотов -> 24°
  {3100, 3200, 25},  // 3100-3200 оборотов -> 25°
  {3200, 3300, 26},  // 3200-3300 оборотов -> 26°
  {3300, 3400, 27},  // 3300-3400 оборотов -> 27°
  {3400, 3500, 28},  // 3400-3500 оборотов -> 28°
  {3500, 3600, 29},  // 3500-3600 оборотов -> 29°
  {3600, 3700, 30},  // 3600-3700 оборотов -> 30°
  {3700, 3800, 31},  // 3700-3800 оборотов -> 31°
  {3800, 3900, 32},  // 3800-3900 оборотов -> 32°
  {3900, 4000, 33},  // 3900-4000 оборотов -> 33°
  {4000, 999999, 33} // 4000+ оборотов -> 33° (максимальное значение)
};

void setup() {
  pinMode(IN, INPUT);   // от датчика
  pinMode(OUT, OUTPUT); // к коммутатору

  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 controlCommutator(bool state) {
  // Управление коммутатором
  if (state) digitalWrite(OUT, HIGH);
  else digitalWrite(OUT, LOW);
}

void loop() {
  detector = digitalRead(IN);  // Чтение входного сигнала от датчика

  // Обработка шторки
  if (detector == LOW && oldDetector == HIGH) {
      controlCommutator(false);
      oldDetector = detector;
      readAll();
      setIgnition();
      // delayMicroseconds(periodTime/5); // если при открытии заслонки мотор душится, то нужно раскомментировать эту строку
  }

  // Обработка шторки
  if (detector == HIGH && oldDetector == LOW) {
      controlCommutator(true);
      oldDetector = detector;
      midTime = micros();
      fuseTime = micros();
      advanceGranted = true; // блок опережения разрешён
      // delayMicroseconds(periodTime/5); // если при открытии заслонки мотор душится, то нужно раскомментировать эту строку
  }

  // Опережение зажигания
  if (micros() - midTime >= ignitionAdvance && advanceGranted) {
    controlCommutator(false);
    advanceGranted = false; // Блок опережения выключен
  }

  // Предохранитель (если катушка заряжается более 1 секунды)
  if (micros() - fuseTime >= 1000000) {
    controlCommutator(false);
  }
}
Код адаптирован под любую платформу, хоть attiny, хоть arduino, хоть esp8266/32

Таблица углов опережения расписана подробно, редактируйте под себя.

Если вскорется ещё какой-то вариант - пишите, будем разбираться.
 
Изменено:
  • Лойс +1
Реакции: ukr100

BOT_Zilla

✩✩✩✩✩✩✩
1 Апр 2022
10
6
@user314, разрешите предложить пару моментов для оптимизации кода?
1. Можно объявить структуру и сразу ее инициализировать, примерно так:
структура:
// Карта значений для угла опережения зажигания
const struct {
  int minRPM, maxRPM, ignitionDegree;
} rpmTable[] = {
  {0, 700, 0}, {700, 800, 1}, // и т. д.
};
2. Для поиска нужного значения в структуре можно использовать так называемый range-based for loop, т. е. диапазонный цикл
C++:
int getIgnitionDegree(long rpm) {
  for (const int& range : rpmTable) {
    if (rpm >= range.minRPM && rpm < range.maxRPM) {
      return range.ignitionDegree;
    }
  }
  //return 0;  //здесь можно вернуть 0, если ничего не найдется
}
В этом случае не нужно думать о длине массива, переменных индексации и конца массива, поэтому на следующей строчке можно сэкономить и освободить немного памяти:
C++:
const int tableSize = sizeof(rpmTable) / sizeof(rpmTable[0]); // Эта строка уже не потребуется
Итерация по массиву происходит автоматически от первого элемента к последнему (что также является недостатком для этого метода в некоторых ситуациях, так как значения массива нельзя просмотреть в обратном или выборочном порядке, но в этом случае это не требуется)
 
  • Лойс +1
Реакции: user314