Генератор сигналов для тестирования системы электронного зажигания

pcdeath
Offline
Зарегистрирован: 03.03.2014

Всем доброго дня.

Задался целью собрать генератор сигналов на Ардуино Uno R3. Требуемые параметры:

1. Генерация импульсов для двух датчиков (2 канала), 128 зубьев на маховике, 3 флага ВМТ (1 флаг на 67 зубе для ВМТ 1 группы цилиндров, 2 флага на 1 и 4 зубьях для ВМТ 2 группы)

2. Имитируемая частота вращения КВ (RPM) - 200-10000 об/мин.

3. Частота импульсов первого датчика (ДУИ) - (RPM/60)*128, скважность 2.

4. Частота импульсов второго датчика (ДНО) комбинированная, складывается из двух:

- RPM/60, скважность 256. (один из флагов второй группы)

- (RPM/60)*2, скважность 128 (флаг первой группы и один из флагов второй группы, разнесённые на 180 градусов).

- Сдвиг между первой и второй группой - 3 зуба.

За основу взял код из темы http://arduino.ru/forum/programmirovanie/arduino-push-pull

/*Двухканальный генератор для 128-зубого маховика и 2 датчиков: ДНО и ДУИ
ДНО имеет 1 на ВМТ первого цилиндра и две последовательных единицы через 3 зуба на ВМТ второго цилиндра
ДУИ имеет 1 на каждый зуб, 0 на отсутствии зуба
Цифр.выходы 3 и 4 - ДНО.
Цифр.выходы 5 и 6 - ДУИ.
*/

void (*mas[]) (void)={dead_time, tooth1, tooth2}; // массив указателей функций 

int RPM=600; //Указываем частоту вращения, об/мин

volatile int val_fr = 16000000/((RPM/60)*256); // Вычисляем длительность полупериода для Ардуино 16 МГц

byte uk=0;
byte dead=0;
byte z=1;
void setup()
{    
  DDRD = B00111100; // нужные пины на выход
  PORTD = B00000000; //Всем портам ставим лог. 0
  TCCR1A=0; TIMSK1=0; // сбрасываем на всякий эти регистры
  TCCR1B=0; // мало ли что arduino IDE туда записало
  TCNT1=0; // сбрасываем счетный регистр таймера 1
  OCR1A=0; // задаем частоту, в Герцах, по формуле f=F_CPU/OCR1A/2 где F_CPU тактовая частота ардуины
  TIMSK1|=(1<<OCIE1A); // разрешаем генерацию прерывания таймера 1, по совпадению с регистром OCR1A
  TCCR1B|=((1<<CS10)|(1<<WGM12)); // запускаем таймер 1 без предделителя в режиме СТС
}


void loop() {

}


ISR(TIMER1_COMPA_vect) {
OCR1A=val_fr/2; //Настраиваем таймер на полупериод колебания

if (z=128) z=1; //Начинаем цикл заново
if (dead=1) uk=0; //Указатель на функцию мёртвой зоны по флагу отсутствия зуба
if (z=1) uk=2; // Указатель на функцию ДНО+ДУИ для 1 зуба
if (z=4) uk=2; // Указатель на функцию ДНО+ДУИ для 4 зуба
if (z=67) uk=2; // Указатель на функцию ДНО+ДУИ для 67 зуба

  (*mas[uk])(); // вызываем функцию по указателю
}

 void dead_time(void) // функция 0 
{
  PORTD = B00000000; // На всех пинах лог 0, формируем мёртвую зону (отсутствие зуба)
  z++; //Увеличиваем счётчик зуба
  uk=1; // Ставми указатель на функцию зуба ДУИ
  dead=0; //Убираем флаг отсутствия зуба
}

void tooth1(void) // функция 1
{
  PORTD = B00010000; // на пин 5 лог 1, формируем зуб ДУИ
  dead=1; //Ставим флаг отсутствия зуба
}

void tooth2(void) // функция 2
{
  PORTD = B00010100; // на пины 3 и 5 лог 1, формируем зуб ДУИ + флаг ДНО
  dead=1; //Ставим флаг отсутствия зуба
}

Вопрос: будет ли оно работать, как задумано? (осциллографа под рукой нет, а система зажигания в гараже).

pcdeath
Offline
Зарегистрирован: 03.03.2014

Апдейт. Во всех описаниях порт3 = порт2, порт5 = порт4 :-)

Пставил вместо зажигалки 2 светодиода, включил на минимальную частоту 150об/мин (при 100 в 16-битный регистр OCR1A будет пытаться записаться значение 16000000/((100/60)*128)=75000, а максимум - 65535),  ожидал увидеть моргание диода на порту 2 с частотой примерно 5 Гц, а на порту 4 - примерно 320 Гц (т.е.постоянное свечение). Оба светляка светятся постоянно. В чём ошибка - пока не понял.

Зубер
Offline
Зарегистрирован: 07.03.2014

1. насчет скважности 2 уверены или предполагаете?

2. лямбду не будете симулировать ?

pcdeath
Offline
Зарегистрирован: 03.03.2014

1. Насчёт скважности 2 предполагаю. Электромагнитный датчик даёт синусоидальный сигнал в одном полупериоде (зуб под датчиком)  и "полку" на нуле (зуба нет). Соответственно, лучшим выходом было бы эмулировать синусоиду, но это довольно ресурсоёмко. Хотя на частоте врщения 10000 об/мин прерывание происходит каждые 750 тактов, и в теории должно хватать, но всё же попробую с прямоугольным сигналом и скважностью 2.

2. Нет, пока иду "от простого" - 2 датчика. Затем - прикрутить LCD и кнопку для оперативного выбора частоты оборотов м индикации их на дисплее. Затем - остальное, по мере надобности.

pcdeath
Offline
Зарегистрирован: 03.03.2014

Есть! Ошибка была в операторах сравнения. Вдобавок для визуального контроля через светодиоды поставил делитель /64 для таймера. Минимальное значение RPM стало 60 (иначе почему-то уходит в быстрое моргание, подозреваю, что это связано с операцией (RPM/60)*128) с типом int, но не уверен.

/*Двухканальный генератор для 128-зубого маховика и 2 датчиков: ДНО и ДУИ
ДНО имеет 1 на ВМТ первого цилиндра и две последовательных единицы через 3 зуба на ВМТ второго цилиндра
ДУИ имеет 1 на каждый зуб, 0 - на отсутствие зуба
Цифр.выход 2 - ДНО.
Цифр.выход 4 - ДУИ.
*/

void (*mas[3]) (void)={dead_time, tooth1, tooth2}; // массив указателей функций 

int RPM=60; //Указываем частоту вращения, об/мин, не менее 60, не более 10000

volatile int val_fr = 250000/((RPM/60)*128); // Вычисляем длительность периода 
//для Ардуино 16 МГц с предделителем /64

byte uk=2;
byte dead=0;
byte z=1;
void setup()
{    
  DDRD = B00111100; // нужные пины на выход
  PORTD = B00000000; //Всем портам ставим лог. 0
  TCCR1A=0; TIMSK1=0; // сбрасываем на всякий эти регистры
  TCCR1B=0; // мало ли что arduino IDE туда записало
  TCNT1=0; // сбрасываем счетный регистр таймера 1
  OCR1A=val_fr/2; //Настраиваем таймер на полупериод колебания
  TIMSK1|=(1<<OCIE1A); // разрешаем генерацию прерывания таймера 1, по совпадению с регистром OCR1A
  TCCR1B|=((1<<CS10)|(1<<CS11)|(1<<WGM12)); // запускаем таймер 1 с предделителем /64 в режиме СТС
}
 
 
void loop() {
if (z==128) z=1; //Начинаем цикл заново
if (dead==1) uk=0; //Указатель на функцию мёртвой зоны по флагу отсутствия зуба
if (z==1) uk=2; // Указатель на функцию ДНО+ДУИ для 1 зуба
if (z==4) uk=2; // Указатель на функцию ДНО+ДУИ для 4 зуба
if (z==67) uk=2; // Указатель на функцию ДНО+ДУИ для 67 зуба
}
 
 
ISR(TIMER1_COMPA_vect) {
  (*mas[uk])(); // вызываем функцию по указателю
}
 
 void dead_time(void) // функция 0 
{
  PORTD = B00000000; // На всех пинах лог 0, формируем мёртвую зону (отсутствие зуба)
  z++; //Увеличиваем счётчик зуба
  uk=1; // Ставми указатель на функцию зуба ДУИ
  dead=0; //Убираем флаг отсутствия зуба
}

void tooth1(void) // функция 1
{
  PORTD = B00010000; // на пин 4 лог 1, формируем зуб ДУИ
  dead=1; //Ставим флаг отсутствия зуба
}

void tooth2(void) // функция 2
{
  PORTD = B00010100; // на пины 2 и 4 лог 1, формируем зуб ДУИ + флаг ДНО
  dead=1; //Ставим флаг отсутствия зуба
}

 

 
 

 

Теперь надо проверять осциллографом и прикручивать LCD с кнопками.

Зубер
Offline
Зарегистрирован: 07.03.2014

pcdeath пишет:

1. Насчёт скважности 2 предполагаю. Электромагнитный датчик даёт синусоидальный сигнал в одном полупериоде (зуб под датчиком)  и "полку" на нуле (зуба нет). Соответственно, лучшим выходом было бы эмулировать синусоиду, но это довольно ресурсоёмко. Хотя на частоте врщения 10000 об/мин прерывание происходит каждые 750 тактов, и в теории должно хватать, но всё же попробую с прямоугольным сигналом и скважностью 2.

2. Нет, пока иду "от простого" - 2 датчика. Затем - прикрутить LCD и кнопку для оперативного выбора частоты оборотов м индикации их на дисплее. Затем - остальное, по мере надобности.

Скважность заведомо > 2, и имеет значение только фронт импульса (по какому переднему или задн читает ваш эбу не знаю). Стандартно стоит датчик Холла, на полпериоде сигнал далек от синуса и можно заменить прямоугольником. Можно добавить +-  небольшую регулировку скважности, а еще проще .

Кстати, вы какой ЭБУ тестируете?

 

 

Зубер
Offline
Зарегистрирован: 07.03.2014

pcdeath пишет:
Минимальное значение RPM стало 60 (иначе почему-то уходит в быстрое моргание, подозреваю, что это связано с операцией (RPM/60)*128) с типом int, но не уверен.
сделать явное преобразование типа для каждой операции умножения и деления

pcdeath
Offline
Зарегистрирован: 03.03.2014

Стандартно стоит электромагнитный датчик 14.3847, у всех знакомых мне ЭМ датчиков сигнал - синусоида.

ЭБУ от двигателя ВАЗ-411.

Скважность, возможно и >2 - это сейчас не очень важно. Если блок не будет реагировать на Ардуино - возьму осциллограф, померю форму импульсов на датчиках при вращении двигателя, буду пытаться приводить скважность к оригиналу. Добавить регулировку скважности довольно просто - согласен.

Зубер
Offline
Зарегистрирован: 07.03.2014

pcdeath пишет:
Стандартно стоит электромагнитный датчик 14.3847, у всех знакомых мне ЭМ датчиков сигнал - синусоида.

ЭБУ от двигателя ВАЗ-411. Скважность, возможно и >2 - это сейчас не очень важно. Если блок не будет реагировать на Ардуино - возьму осциллограф, померю форму импульсов на датчиках при вращении двигателя, буду пытаться приводить скважность к оригиналу. Добавить регулировку скважности довольно просто - согласен.

не прямо от дуины, а лучше через оптрон или хотя бы через ключ

pcdeath пишет:
Стандартно стоит электромагнитный датчик 14.3847, у всех знакомых мне ЭМ датчиков сигнал - синусоида. ЭБУ от двигателя ВАЗ-411.
такой что-ли ?

pcdeath
Offline
Зарегистрирован: 03.03.2014

 

Оптика... Как вариант, но я планировал использовать только резистор. Хотя, наверное, оптическая развязка будет правильнее, согласен.

 

Нет, вот такое:

 

Зубер
Offline
Зарегистрирован: 07.03.2014

pcdeath пишет:
Оптика... Как вариант, но я планировал использовать только резистор. Хотя, наверное, оптическая развязка будет правильнее, согласен. Нет, вот такое
ЭБУ наверное хочет на входе 12В, а не  арудиновские 5В

про ва3 411 я даже не слышал про такой, может быть ГАЗ ?

вижу 2 коммутатора, и наверное то что слева и есть ЭБУ?

pcdeath
Offline
Зарегистрирован: 03.03.2014

ЭБУ хочет хоть какой-нибудь сигнал на входе :-) У электромагнитных датчиков очень широкие пределы - от десятых долей вольта до десятков вольт, в зависимости от скорости штифта под ним. Вот в целях защиты ардуины оптика - это правильно.

Да, справа 2 коммутатора, слева - ЭБУ.

Именно ВАЗ-411. Погуглите, узнаете много интересного :-)

Зубер
Offline
Зарегистрирован: 07.03.2014

pcdeath пишет:
ЭБУ хочет хоть какой-нибудь сигнал на входе :-) У электромагнитных датчиков очень широкие пределы - от десятых долей вольта до десятков вольт, в зависимости от скорости штифта под ним. Вот в целях защиты ардуины оптика - это правильно. Да, справа 2 коммутатора, слева - ЭБУ.Именно ВАЗ-411. Погуглите, узнаете много интересного :-)
дык это же роторный времен ссср брежнева-горбачева очень большая редкость

Matalkin
Offline
Зарегистрирован: 09.03.2014

pcdeath пишет:
Всем доброго дня.

Вопрос: будет ли оно работать, как задумано? (осциллографа под рукой нет, а система зажигания в гараже).

Вам тоже доброго дня!

По каким признаком кроме поймете, что система работает?

pcdeath
Offline
Зарегистрирован: 03.03.2014

 

Зубер пишет:

 дык это же роторный времен ссср брежнева-горбачева очень большая редкость

Да, это именно он и есть.

Matalkin пишет:
Вам тоже доброго дня! По каким признаком кроме поймете, что система работает?
 
 
Первичные признаки работы уже получил - светодиоды, подключенные к выходу, моргают с правильной частотой. Окончательный тест будет на собранной системе зажигания. Если всё правильно - ЭБУ должен давать сигнал с требуемой частотой на коммутаторы.

 

Зубер
Offline
Зарегистрирован: 07.03.2014

pcdeath пишет:
Если всё правильно - ЭБУ должен давать сигнал с требуемой частотой на коммутаторы.

Еще увидите по реакции ШД ХХ,  наверное такой  предусмотрен, хотя модельЭБУ уважаемый раритет.

На некоторых ЭБУ ширина импульса для форсунок  постоянна, а количество бензина изменяется дополнительными импульсами. Есть на таком ЭБУ датчик детонации  и как будете иммитировать его ?

pcdeath пишет:
Зубер пишет:
дык это же роторный времен ссср брежнева-горбачева очень большая редкость
Да, это именно он и есть.

По оценкам, полупроводники  тех лет "живут" до 40...50  лет пока не произойдет диффузия p-n переходов.

 

pcdeath
Offline
Зарегистрирован: 03.03.2014

Этот блок управляет только зажиганием и предупрждает о превышении оборотов. Двигатель карбюраторный.

Используемые датчики - ДНО+ДУИ для определения положения КВ, ДТОЖ для определения "прогретости", имитируется подключением соответсвующего резистора и ДАД для определения давления на впуске -> режима работы двигателя (насколько открыта заслонка). ДАД  возвращает напрояжение 0,5...4,5В, имитируется сборкой резисторов с шагом 0,5В.

Зубер
Offline
Зарегистрирован: 07.03.2014

pcdeath пишет:
Этот блок управляет только зажиганием и предупрждает о превышении оборотов. Двигатель карбюраторный.

Используемые датчики - ДНО+ДУИ для определения положения КВ, ДТОЖ для определения "прогретости", имитируется подключением соответсвующего резистора и ДАД для определения давления на впуске -> режима работы двигателя (насколько открыта заслонка). ДАД  возвращает напрояжение 0,5...4,5В, имитируется сборкой резисторов с шагом 0,5В.

если все ок, то присоедините к роторному  двигателю ?

pcdeath
Offline
Зарегистрирован: 03.03.2014

Если всё ОК - буду использовать Ардуино для тестирования ЭБУ этих 411, а то сейчас приходится цеплять на живой двигатель для теста, что не лучший вариант - 411 у меня единственный, и покупатель ждёт-не дождётся, когда расстанусь с ним :-)

Зубер
Offline
Зарегистрирован: 07.03.2014

pcdeath пишет:
Если всё ОК - буду использовать Ардуино для тестирования ЭБУ этих 411, а то сейчас приходится цеплять на живой двигатель для теста, что не лучший вариант - 411 у меня единственный, и покупатель ждёт-не дождётся, когда расстанусь с ним :-)

если не секрет сколько стоит такой двигатель ?

pcdeath
Offline
Зарегистрирован: 03.03.2014

Собранный из новых запчастей - от 70000 с навесным. Из б/у - можно в 40-50 уложиться.

Зубер
Offline
Зарегистрирован: 07.03.2014

pcdeath пишет:
Собранный из новых запчастей - от 70000 с навесным. Из б/у - можно в 40-50 уложиться.
сейчас ценят за раритетность или за мощность,  какой ресурс у движка?

pcdeath
Offline
Зарегистрирован: 03.03.2014

Ресурс при правильном уходе - до 70000 км, ценят, во-первых, за раритетность, во-вторых, за соотношение масса/объём/мощность, в-третьих, за возможность установить этот двигатель практически без переделок в стандартный кузов "копейки".

pcdeath
Offline
Зарегистрирован: 03.03.2014

Добавил обработку нажатия кнопки на пине 1.

С каждым нажатием диод на 13 пине моргает один раз длинно (2 сек) и частота импульсов увеличивается вдвое.  При превышении частоты, эквивалентной 10000 об/мин, диод моргает 3 раза коротко (0,5 сек), затем 1 раз длинно и частота возвращается к эквиваленту 60 об/мин

/*Двухканальный генератор для 128-зубого маховика и 2 датчиков: ДНО и ДУИ
ДНО имеет 1 на ВМТ первого цилиндра и две последовательных единицы через 3 зуба на ВМТ второго цилиндра
ДУИ имеет 1 на каждый зуб, 0 - на отсутствие зуба
Цифр.вход 1 - кнопка. С каждым нажатием кнопки частота увеличивается вдвое, диод на порту 13 мигает один раз на 2 сек.
При превышении максимальной частоты диод на порту 13 мигает 3 раза по 0,5 сек. и один раз на 2 сек, и частота возвращается к начальной.
Цифр.выход 2 - ДНО.
Цифр.выход 4 - ДУИ.
Начальная частота - 60 RPM
Предельная частота - 10000 RPM
*/

void (*mas[3]) (void)={dead_time, tooth1, tooth2}; // массив указателей функций 

//Указываем частоту вращения, об/мин
//не менее 60 для предделителя /64
//не менее 200 без предделителя
int RPM=60;
int MAXRPM=10000;

// Вычисляем длительность периода в тактах таймера
volatile int val_fr = 250000/((RPM/60)*128); //для Ардуино 16 МГц с предделителем /64
//volatile int val_fr = 2000000/((RPM/60)*128); //для Ардуино 16 МГц с предделителем /8
//volatile int val_fr = 16000000/((RPM/60)*128); //для Ардуино 16 МГц без предделителя

byte uk=2;
byte dead=0;
byte z=1;
void setup()
{    
  pinMode (13, OUTPUT); //пин с LED на выход
  DDRD = B00111100; // нужные пины на выход
  PORTD = B00000000; //Всем портам ставим лог. 0
  TCCR1A=0; TIMSK1=0; // сбрасываем на всякий эти регистры
  TCCR1B=0; // мало ли что arduino IDE туда записало
  TCNT1=0; // сбрасываем счетный регистр таймера 1
  OCR1A=val_fr/2; //Настраиваем таймер на полупериод колебания
  TIMSK1|=(1<<OCIE1A); // разрешаем генерацию прерывания таймера 1, по совпадению с регистром OCR1A
  TCCR1B|=((1<<CS10)|(1<<CS11)|(1<<WGM12)); // запускаем таймер 1 с предделителем /64 в режиме СТС
}
 
 
void loop() {
digitalWrite (13, LOW);
if (digitalRead(1) == HIGH) { //Обрабатываем нажатие кнопки на 1 цифровом порту
  OCR1A /= 2; //Увеличиваем частоту вдвое, уменьшая предельное значение счётчика
  uk=2; //Выставляем начальные значения
  dead=0;
  z=1;
  if (OCR1A < (250000/((MAXRPM/60)*128)/2)) { //Если превышает предельную частоту
    OCR1A = 250000/((RPM/60)*128)/2; //скидываем обратно на начальную
    digitalWrite (13, HIGH); //Моргаем светодиодом 0.5сек
    delay (500);
    digitalWrite (13, LOW); //Тушим светодиод на 0,5 сек
    delay (500);
    digitalWrite (13, HIGH); //Моргаем светодиодом 0,5 сек
    delay (500);
    digitalWrite (13, LOW); //Тушим светодиод на 0,5 сек
    delay (500);
    digitalWrite (13, HIGH); //Моргаем светодиодом 0,5 сек
    delay (500);
    digitalWrite (13, LOW); //Тушим светодиод на 0,5 сек
    delay (500);
  } 
  digitalWrite (13, HIGH); //Моргаем светодиодом 2сек
  delay (2000);
  digitalWrite (13, LOW);
}

if (z==128) { //Начинаем цикл заново
  z=0;
  dead=1;
} 
if (dead==1) uk=0; //Указатель на функцию мёртвой зоны по флагу отсутствия зуба
if (z==1) uk=2; // Указатель на функцию ДНО+ДУИ для 1 зуба
if (z==4) uk=2; // Указатель на функцию ДНО+ДУИ для 4 зуба
if (z==67) uk=2; // Указатель на функцию ДНО+ДУИ для 67 зуба
}
 
 
ISR(TIMER1_COMPA_vect) {
  (*mas[uk])(); // вызываем функцию по указателю
}
 
 void dead_time(void) // функция 0 
{
  PORTD = B00000000; // На всех пинах лог 0, формируем мёртвую зону (отсутствие зуба)
  z++; //Увеличиваем счётчик зуба
  uk=1; // Ставми указатель на функцию зуба ДУИ
  dead=0; //Убираем флаг отсутствия зуба
}

void tooth1(void) // функция 1
{
  PORTD = B00010000; // на пин 4 лог 1, формируем зуб ДУИ
  dead=1; //Ставим флаг отсутствия зуба
}

void tooth2(void) // функция 2
{
  PORTD = B00010100; // на пины 2 и 5 лог 1, формируем зуб ДУИ + флаг ДНО
  dead=1; //Ставим флаг отсутствия зуба
}

 

Теперь после первого круга стали появляться какие-то странные "фризы" - порт 2 "залипает" в состоянии 1 примерно на секунду, затем выполнение продолжается.

С чем связано, пока не нашёл.

Зубер
Offline
Зарегистрирован: 07.03.2014

pcdeath пишет:

Добавил обработку нажатия кнопки на пине 1.

С каждым нажатием диод на 13 пине моргает один раз длинно (2 сек) и частота импульсов увеличивается вдвое.  При превышении частоты, эквивалентной 10000 об/мин, диод моргает 3 раза коротко (0,5 сек), затем 1 раз длинно и частота возвращается к эквиваленту 60 об/мин.

Теперь после первого круга стали появляться какие-то странные "фризы" - порт 2 "залипает" в состоянии 1 примерно на секунду, затем выполнение продолжается.

С чем связано, пока не нашёл.

Без обработки дребезга (например HIGH в течение 50 мсек и более) плохо, тк механическая кнопка дает одним нажатием несколько импульсов  и с учетом последующих delay() такое "размножение" приводит к замиранию программы.  Лучше перенести обработку нажатия в прерывание, алгоритм: после первого импульса запомнить время и если HIGH продержится XX мсек считать кнопку нажатой (установить бит флаг) , иначе отбросить как шум.  Дальше бит  обрабатывать вне прерывания и  сбросить его.

Область видимости volatile

pcdeath
Offline
Зарегистрирован: 03.03.2014

Именно для исключения дребезга был добавлен "delay", чтоб точно успеть отпустить кнопку в течение этих 2 сек.. только после прошествия этого времени Ардуино заново опрашивает кнопку. В ненажатом состоянии кнопка дребезга не даёт, или я чего-то не понимаю?

Заметил ещё: иногда светляк TX (если правильно понимаю схему Uno, прицеплен к 1 ноге) моргает синхронно с диодом на 2 ноге, иногда не моргает. Могут ли быть между ними какие-то наводки?

pcdeath
Offline
Зарегистрирован: 03.03.2014

Нашёл ошибку.

В строках 73-76 идёт сравнение номера зуба и флага отсутствия зуба с присвоением имени вызываемой функции. При DEAD == 1 ставился указатель на функцию "мёртвой зоны", а при последующих проверках указатель перезаписывался при Z == 1 | 4 | 67. поставил проверку флага DEAD в конец условий.


/*Двухканальный генератор для 128-зубого маховика и 2 датчиков: ДНО и ДУИ
ДНО имеет 1 на ВМТ первого цилиндра и две последовательных единицы через 3 зуба на ВМТ второго цилиндра
ДУИ имеет 1 на каждый зуб, 0 - на отсутствие зуба
Цифр.вход 1 - кнопка. С каждым нажатием кнопки частота увеличивается вдвое, диод на порту 13 мигает один раз на 2 сек.
При превышении максимальной частоты диод на порту 13 мигает 3 раза по 0,5 сек. и один раз на 2 сек, и частота возвращается к начальной.
Цифр.выход 2 - ДНО.
Цифр.выход 4 - ДУИ.
Начальная частота - 60 RPM
Предельная частота - 10000 RPM
*/

void (*mas[3]) (void)={dead_time, tooth1, tooth2}; // массив указателей функций 

//Указываем частоту вращения, об/мин
//не менее 60 для предделителя /64
//не менее 200 без предделителя
int RPM=60;
int MAXRPM=10000;

// Вычисляем длительность периода в тактах таймера
volatile int val_fr = 250000/((RPM/60)*128); //для Ардуино 16 МГц с предделителем /64
//volatile int val_fr = 2000000/((RPM/60)*128); //для Ардуино 16 МГц с предделителем /8
//volatile int val_fr = 16000000/((RPM/60)*128); //для Ардуино 16 МГц без предделителя

byte uk=2;
byte dead=0;
byte z=1;
void setup()
{    
  pinMode (13, OUTPUT); //пин с LED на выход
  DDRD = B00111100; // нужные пины на выход
  PORTD = B00000000; //Всем портам ставим лог. 0
  TCCR1A=0; TIMSK1=0; // сбрасываем на всякий эти регистры
  TCCR1B=0; // мало ли что arduino IDE туда записало
  TCNT1=0; // сбрасываем счетный регистр таймера 1
  OCR1A=val_fr/2; //Настраиваем таймер на полупериод колебания
  TIMSK1|=(1<<OCIE1A); // разрешаем генерацию прерывания таймера 1, по совпадению с регистром OCR1A
  TCCR1B|=((1<<CS10)|(1<<CS11)|(1<<WGM12)); // запускаем таймер 1 с предделителем /64 в режиме СТС
  digitalWrite (13, LOW);
}
 
 
void loop() {
if (digitalRead(1) == HIGH) { //Обрабатываем нажатие кнопки на 1 цифровом порту
  OCR1A /= 2; //Увеличиваем частоту вдвое, уменьшая предельное значение счётчика
  uk=2; //Выставляем начальные значения
  dead=0;
  z=1;
  if (OCR1A < (250000/((MAXRPM/60)*128)/2)) { //Если превышает предельную частоту
    OCR1A = val_fr/2; //скидываем обратно на начальную
    digitalWrite (13, HIGH); //Моргаем светодиодом трижды на 0.5сек
    delay (500);
    digitalWrite (13, LOW); 
    delay (500);
    digitalWrite (13, HIGH); 
    delay (500);
    digitalWrite (13, LOW); 
    delay (500);
    digitalWrite (13, HIGH); 
    delay (500);
    digitalWrite (13, LOW); 
    delay (500);
  } 
  digitalWrite (13, HIGH); //Моргаем светодиодом 2сек
  delay (2000);
  digitalWrite (13, LOW);
}

if (z==128) z=1; //Начинаем цикл заново
if (z==1 || z==4 || z==67) uk=2; // Указатель на функцию ДНО+ДУИ для 1, 4 или 67 зуба
if (dead==1) uk=0; //Указатель на функцию мёртвой зоны по флагу отсутствия зуба
}
 
 
ISR(TIMER1_COMPA_vect) {
  (*mas[uk])(); // вызываем функцию по указателю
}
 
 void dead_time(void) // функция 0 
{
  PORTD = B00000000; // На всех пинах лог 0, формируем мёртвую зону (отсутствие зуба)
  z++; //Увеличиваем счётчик зуба
  uk=1; // Ставми указатель на функцию зуба ДУИ
  dead=0; //Убираем флаг отсутствия зуба
}

void tooth1(void) // функция 1
{
  PORTD = B00010000; // на пин 4 лог 1, формируем зуб ДУИ
  dead=1; //Ставим флаг отсутствия зуба
}

void tooth2(void) // функция 2
{
  PORTD = B00010100; // на пины 2 и 4 лог 1, формируем зуб ДУИ + флаг ДНО
  dead=1; //Ставим флаг отсутствия зуба
}

Всё стало хорошо. В строке 45 оператор /= можно заменить на *= , и при нажатии кнопки частота будет уменьшаться - это способствует визуальному контролю работы генератора на светодиодах.

Зубер
Offline
Зарегистрирован: 07.03.2014

pcdeath пишет:
Именно для исключения дребезга был добавлен "delay", чтоб точно успеть отпустить кнопку в течение этих 2 сек.. только после прошествия этого времени Ардуино заново опрашивает кнопку. В ненажатом состоянии кнопка дребезга не даёт, или я чего-то не понимаю?

Заметил ещё: иногда светляк TX (если правильно понимаю схему Uno, прицеплен к 1 ноге) моргает синхронно с диодом на 2 ноге, иногда не моргает. Могут ли быть между ними какие-то наводки?

Нужна проверка того, что кнопка удерживалась 50...80 мс для обнаружения нажатия  тк "ловим" передний фронт. Дребезг отпускания у тактовых кнопок с прогибающейся мембраной тоже есть, но  малый.

LED на 1, 2 ногах  это TX,RX порта монитора  и мигают при только при передаче информации (низкий уровень).

Кстати при такой низкой частоте импульсов нет необходимости писать на ассемблере.

redRAT
Offline
Зарегистрирован: 12.03.2014

pcdeath пишет:
Всем доброго дня.

Задался целью собрать генератор сигналов на Ардуино Uno R3. Требуемые параметры:

1. Генерация импульсов для двух датчиков (2 канала), 128 зубьев на маховике, 3 флага ВМТ (1 флаг на 67 зубе для ВМТ 1 группы цилиндров, 2 флага на 1 и 4 зубьях для ВМТ 2 группы)

Всем привет, автору спасибо за публикацию темы. Хочу разобраться с сутью на вашем примере. Правильно я понял принцип формирования выходного сигнала : из "1" вычесть "2" и вычесть "3" чтобы получить "4" ?

Номера импульсов пока не поставил тк не понял последовательность

 

 

 

 

pcdeath
Offline
Зарегистрирован: 03.03.2014

redRAT пишет:

pcdeath пишет:
Всем доброго дня.

Задался целью собрать генератор сигналов на Ардуино Uno R3. Требуемые параметры:

1. Генерация импульсов для двух датчиков (2 канала), 128 зубьев на маховике, 3 флага ВМТ (1 флаг на 67 зубе для ВМТ 1 группы цилиндров, 2 флага на 1 и 4 зубьях для ВМТ 2 группы)

Всем привет, автору спасибо за публикацию темы. Хочу разобраться с сутью на вашем примере. Правильно я понял принцип формирования выходного сигнала : из "1" вычесть "2" и вычесть "3" чтобы получить "4" ?

Номера импульсов пока не поставил тк не понял последовательность

 

 

 

 

 

 

Не совсем верно. Во-первых, Ваши графики обратные (высокий уровень для меня всегда был логической "1")

Во-вторых, первый канал (ДНО) - это, по Вашей диаграмме, 2+3 графики, только промежутки между однозубыми и двузубым сигналами больше. Второй канал (ДУИ) - первый график. 

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

Если же смотреть по функциям, то график можно представить так:

График

redRAT
Offline
Зарегистрирован: 12.03.2014

pcdeath пишет:
высокий уровень для меня всегда был логической "1"

ДНО

Если же смотреть по функциям, то график можно представить так:

ДУИ- угловых импульсов

ДНО - что это  и зачем  его складывают из двух сигналов ?

--

Ф1К1= входной сигнал ДУИ из 128 имульсов, на котором пропущены испульсы 1,4,67

Ф1К2= почему всегда  высокий уровень без импульсов?

Ф2К1 и Ф2К2 одинаковые выходные сигналы и доллжны быть сдвинуты на 1/4 от 128=периода_Ф1К1 ?

 

pcdeath
Offline
Зарегистрирован: 03.03.2014

redRAT пишет:

ДУИ- угловых импульсов

ДНО - что это  и зачем  его складывают из двух сигналов ?

--

Ф1К1= входной сигнал ДУИ из 128 имульсов, на котором пропущены испульсы 1,4,67

Ф1К2= почему всегда  высокий уровень без импульсов?

Ф2К1 и Ф2К2 одинаковые выходные сигналы и доллжны быть сдвинуты на 1/4 от 128=периода_Ф1К1 ?

 

ДНО - Датчик Начала Отсчёта. По нему ориентируется ЭБУ: 1 импульс - ВМТ первой группы цилиндров, 2 импульса - ВМТ второй группы. 

Ф1 отвечает только за импульсы ДУИ. Ф1К2 - всегда низкий уровень. Пропуски в графике сделаны, т.к.на зубьях 1, 4 и 67 функция 1 не отрабатывает, вместо неё отрабатывает функция 2, которая даёт в канал 1 импульс ДУИ, чтоб не прерывать счёт импульсов, и одновременно даёт импульс в канал ДНО, т.к.зубы ДНО расположены рядом с 1, 4 и 67 зубьями венца ДУИ. 

Таким образом, график Ф2К1 компенсирует пробелы в графике Ф1К1. Получаем непрерывное следование импульсов ДУИ (К1) и импульсы ДНО (К2) на 1, 4 и 67 зубьях.

Немного "пригладил" генератор - вынес в переменные параметры для регулировки, чтоб иметь более гибкую настройку:
 


/*Двухканальный генератор для 128-зубого маховика и 2 датчиков: ДНО и ДУИ
ДНО имеет 1 на ВМТ первого цилиндра и две последовательных единицы через 3 зуба на ВМТ второго цилиндра
ДУИ имеет 1 на каждый зуб, 0 - на отсутствие зуба
Цифр.вход 1 - кнопка. С каждым нажатием кнопки частота увеличивается вдвое, диод на порту 13 мигает один раз на 2 сек.
При превышении максимальной частоты диод на порту 13 мигает 3 раза по 0,5 сек. и один раз на 2 сек, и частота возвращается к начальной.
Цифр.выход 2 - ДНО.
Цифр.выход 4 - ДУИ.
Начальная частота - 60 RPM
Предельная частота - 16000 RPM
*/

//Указываем мин. и макс. частоту вращения, об/мин, кол-во зубьев на маховике и номера зубьев ДУИ, рядом с которыми есть зубья ДНО.
int RPM=60;//не менее 60 для предделителя /64, не менее 200 без предделителя
int MAXRPM=10000; //не более 16000
byte z=128; //Кол-во зубьев на маховике, не более 255
byte DNO1=1; //Номер зуба для ДНО
byte DNO2=4; //Номер зуба для ДНО
byte DNO3=67; //Номер зуба для ДНО


void (*mas[3]) (void)={dead_time, tooth1, tooth2}; // массив указателей функций 

// Вычисляем длительность периода в тактах таймера
volatile int val_fr = 250000/((RPM/60)*128); //для Ардуино 16 МГц с предделителем /64
//volatile int val_fr = 2000000/((RPM/60)*128); //для Ардуино 16 МГц с предделителем /8
//volatile int val_fr = 16000000/((RPM/60)*128); //для Ардуино 16 МГц без предделителя

byte uk=2;
byte dead=0;
byte cnt=1;
void setup()
{    
  pinMode (13, OUTPUT); //пин с LED на выход
  DDRD = B00111100; // нужные пины на выход
  PORTD = B00000000; //Всем портам ставим лог. 0
  TCCR1A=0; TIMSK1=0; // сбрасываем на всякий эти регистры
  TCCR1B=0; // мало ли что arduino IDE туда записало
  TCNT1=0; // сбрасываем счетный регистр таймера 1
  OCR1A=val_fr/2; //Настраиваем таймер на полупериод колебания
  TIMSK1|=(1<<OCIE1A); // разрешаем генерацию прерывания таймера 1, по совпадению с регистром OCR1A
  TCCR1B|=((1<<CS10)|(1<<CS11)|(1<<WGM12)); // запускаем таймер 1 с предделителем /64 в режиме СТС
//  TCCR1B|=((1<<CS11)|(1<<WGM12)); // запускаем таймер 1 с предделителем /8 в режиме СТС
//  TCCR1B|=((1<<CS10)|(1<<WGM12)); // запускаем таймер 1 без предделителя в режиме СТС
  digitalWrite (13, LOW); // тушим светодиод на 13 порту
}
 
 
void loop() {
if (digitalRead(1) == HIGH) { //Обрабатываем нажатие кнопки на 1 цифровом порту
  OCR1A /= 2; //Увеличиваем частоту вдвое, уменьшая предельное значение счётчика
  uk=2; //Выставляем начальные значения
  dead=0;
  cnt=1;
  if (OCR1A < (250000/((MAXRPM/60)*128)/2)) { //Если превышает предельную частоту для предделителя /64
  //  if (OCR1A < (2000000/((MAXRPM/60)*128)/2)) { //Если превышает предельную частоту для предделителя /8
  //  if (OCR1A < (16000000/((MAXRPM/60)*128)/2)) { //Если превышает предельную частоту без предделителя
    OCR1A = val_fr/2; //скидываем обратно на начальную
    digitalWrite (13, HIGH); //Моргаем светодиодом трижды на 0.5сек
    delay (500);
    digitalWrite (13, LOW); 
    delay (500);
    digitalWrite (13, HIGH); 
    delay (500);
    digitalWrite (13, LOW); 
    delay (500);
    digitalWrite (13, HIGH); 
    delay (500);
    digitalWrite (13, LOW); 
    delay (500);
  } 
  digitalWrite (13, HIGH); //Моргаем светодиодом 2сек
  delay (2000);
  digitalWrite (13, LOW);
}

if (cnt==z) cnt=1; //Начинаем цикл заново при полном обороте
if (cnt==DNO1 || cnt==DNO2 || cnt==DNO3) uk=2; // Указатель на функцию ДНО+ДУИ для зубьев ДНО
if (dead==1) uk=0; //Указатель на функцию мёртвой зоны по флагу отсутствия зуба
}
 
 
ISR(TIMER1_COMPA_vect) {
  (*mas[uk])(); // вызываем функцию по указателю
}
 
 void dead_time(void) // функция 0 
{
  PORTD = B00000000; // На всех пинах лог 0, формируем мёртвую зону (отсутствие зуба)
  cnt++; //Увеличиваем счётчик зуба
  uk=1; // Ставми указатель на функцию зуба ДУИ
  dead=0; //Убираем флаг отсутствия зуба
}

void tooth1(void) // функция 1
{
  PORTD = B00010000; // на пин 4 лог 1, формируем зуб ДУИ
  dead=1; //Ставим флаг отсутствия зуба
}

void tooth2(void) // функция 2
{
  PORTD = B00010100; // на пины 2 и 4 лог 1, формируем зуб ДУИ + зуб ДНО
  dead=1; //Ставим флаг отсутствия зуба
}
redRAT
Offline
Зарегистрирован: 12.03.2014

слова понятны, а сколько каналов ввода (датчиков) и каналов вывода (исполнительных устройств ) не понимаю

pcdeath
Offline
Зарегистрирован: 03.03.2014

Гм... Не совсем понял, откуда эта таблица.

Ф1К1 - ДУИ, вход

Ф2К1 - ДУИ, вход

Ф2К2 - ДНО, вход

Ф1К1 и Ф2К1 формируют импульс на одном и том же пине, подключенном ко входу ДУИ на ЭБУ.

Ф2К2 - на другом пине, подключенном ко входу ДНО на ЭБУ.

redRAT
Offline
Зарегистрирован: 12.03.2014

pcdeath пишет:
Гм... Не совсем понял, откуда эта таблица.

таблица из экселя с ваших слов

 

Зубер
Offline
Зарегистрирован: 07.03.2014

Есть в коде программы "вялое место" : увеличение частоты в 2 раза  (уменьшение делителя) и лишь потом ее проверка на соответствие. Это может привести к двум скачкам частоты. Для упрощения кода и избежания двойного скачка можно использовать функцию min(текущеезначение/2, минимальноедопустимое );

pcdeath
Offline
Зарегистрирован: 03.03.2014

Так в том-то и вопрос, чтоб не выйти за границы допустимого диапазона и ходить "по кругу". Как только частота выходит за него, она возвращается к изначальной. Если использовать Ваше предложение, частота никогда не поднимется выше м максимального предела. А вообще, сейчас жду модуль LCD, буду выводить текущее значение RPM и частоты, ну и регулировка будет более гибкой - там, всё-таки, не одна кнопка :-)

Зубер
Offline
Зарегистрирован: 07.03.2014

pcdeath пишет:
Так в том-то и вопрос, чтоб не выйти за границы допустимого диапазона и ходить "по кругу". Как только частота выходит за него, она возвращается к изначальной. Если использовать Ваше предложение, частота никогда не поднимется выше м максимального предела. А вообще, сейчас жду модуль LCD, буду выводить текущее значение RPM и частоты, ну и регулировка будет более гибкой - там, всё-таки, не одна кнопка :-)

Чтобы ходить по кругу  необязательно превышать максимум есть решения проще, например ходить не по кругу абсолютных значений (делителей, множителей и т.д.), а по кругу enumerated индексов  массива и т.д.

mtmbox@mail.ru

 

 

pcdeath
Offline
Зарегистрирован: 03.03.2014

Итак, LCD панелька установлена и прикручена.

Что получилось в итоге:

/*Двухканальный генератор для 128-зубого маховика и 2 датчиков: ДНО и ДУИ
ДНО имеет 1 на ВМТ первого цилиндра и две последовательных единицы через 3 зуба на ВМТ второго цилиндра
ДУИ имеет 1 на каждый зуб, 0 - на отсутствие зуба
Для Arduino с LCD keypad shield.
В верхней строке отображаются кол-во зубьев маховика и статус генератора - запущен/остановлен.
В нижней строке отображается кол-во оборотов в минуту и предупреждение о достижении минимального/максимального порога частоты.
Кнопка "влево" - уменьшение частоты на 10%
Кнопка "вправо" - увеличение частоты на 10%
Кнопка "вверх" - увеличение частоты вдвое
Кнопка "вниз" - уменьшение частоты вдвое

Цифр.выход 2 - ДНО.
Цифр.выход 3 - ДУИ.
При наличии только одного датчика использовать сигнал ДНО при Z=128

Ограничения:
Кол-во зубьев ДУИ - 32...255
Таблица ограничений по оборотам для таймера с делителем /64:
Кол-во зубьев    Мин.обороты      Макс.обороты
32...128         4                15300
129...196        4                10000
197...255        4                7700

Погрешность на больших частотах достигает 20%. 
Для получения большей точности требуется уменьшить делитель таймера 
с /64 до /8, что поднимет границу минимальных оборотов.

Таблица ограничений по оборотам для таймера с делителем /8:
Кол-во зубьев    Мин.обороты      Макс.обороты
32...127         29                15300
128...130        8                 15300
131...196        8                 10000
197...255        5                 7700

Приемлемая погрешность (до 10%) во всём диапазоне оборотов.
Для ещё большей точности потребуется использовать таймер без предделителя.

Таблица ограничений по оборотам для таймера без делителя:
Кол-во зубьев    Мин.обороты      Макс.обороты
32...127         230                15300
128...130        60                 15300
131...196        60                 10000
197...255        29                 7700

*/

//Указываем мин. и макс. частоту вращения, об/мин, кол-во зубьев на маховике и номера зубьев ДУИ, рядом с которыми есть зубья ДНО.
long MINRPM       = 8;          // Мин.частота вращения
long INITRPM      = 200;        // Начальная частота. Не менее 60, не более MAXRPM
long MAXRPM       = 10000;      // Макс.частота вращения
byte Z            = 128;        // Кол-во зубьев на маховике
byte DNO1         = 1;          // Номер зуба для ДНО
byte DNO2         = 4;          // Номер зуба для ДНО, 0 - если не нужен
byte DNO3         = 67;         // Номер зуба для ДНО, 0 - если не нужен
byte DIV          = 8;          // Указатель делителя таймера. 0, 8 или 64

/***********************************************************/




void (*mas[3]) (void)={dead_time, tooth1, tooth2}; // Массив указателей функций 

// Вычисляем длительность полупериодов в тактах таймера
// *Z - для перевода из счётчика оборотов в счётчик зубьев, *2 для отсчёта полупериода, /60 - для перевода из RPM в Hz, 
unsigned int MINDUI           = MINRPM  * Z * 2 / 60; 
unsigned int DUI              = INITRPM * Z * 2 / 60;
unsigned int MAXDUI           = MAXRPM  * Z * 2 / 60;

// Объявляем переменные, хранящие в себе текущее и предельное значение таймера
unsigned int val_frmin        = 0;
unsigned int val_fr           = 0; 
unsigned int val_frmax        = 0;

// Объявляем переменные счётчиков и функций, устанавливаем начальные параметры 
volatile byte uk              = 2; // Указатель на функцию для выбора из массива
volatile boolean dead         = 0; // Флаг функции отсутствия зуба под датчиками
volatile byte cnt             = 1; // Счётчик зубьев
boolean SELECT                = 1; // Флаг подтверждения выбора частоты
int RPM                       = 0; // Для индикации актуального кол-ва оборотов в минуту
long FREQ                     = 0; // Объявляем переменную частоты Ардуино

#include <LiquidCrystal.h> // Прицепляем LCD
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
byte lcd_key    = 0;
int adc_key_in  = 0;
#define btnNONE   0
#define btnRIGHT  1
#define btnUP     2
#define btnDOWN   3
#define btnLEFT   4
#define btnSELECT 5


// Обработка кнопок
int read_LCD_buttons()
{
adc_key_in = analogRead(0);         // Читаем значение нопок.  Как правило, подходят следующие:
if (adc_key_in > 1000) return btnNONE;
if (adc_key_in < 50)   return btnRIGHT; 
if (adc_key_in < 195)  return btnUP;
if (adc_key_in < 380)  return btnDOWN;
if (adc_key_in < 555)  return btnLEFT;
if (adc_key_in < 790)  return btnSELECT;  
return btnNONE;                    // Если "всё не так" - считаем. что никакая кнопка не нажата.
}


void setup()
{ 
  DDRD = DDRD | B00001100;         // Ставим пинам 2 и 3 режим "выход", не меняя остальных
  PORTD = PORTD & B11110011;       // Выходным пинам ставим лог. 0
  TCCR1A = 0; TIMSK1 = 0;          // Сбрасываем на всякий эти регистры
  TCCR1B = 0;                      // Мало ли что arduino IDE туда записало
  TCNT1 = 0;                       // Сбрасываем счетный регистр таймера 1

  if (DIV == 64) FREQ = 250000;    // Для Ардуино 16 МГц с предделителем /64
  if (DIV == 8)  FREQ = 2000000;   // Для Ардуино 16 МГц с предделителем /8
  if (DIV == 0)  FREQ = 16000000;  // Для Ардуино 16 МГц без предделителя
      
  val_frmin    = FREQ / MINDUI;
  val_fr       = FREQ / DUI; 
  val_frmax    = FREQ / MAXDUI;
  
  OCR1A        = val_fr;                     // Настраиваем таймер на полупериод колебания
  TIMSK1       |= (1 << OCIE1A);             // разрешаем генерацию прерывания таймера 1, по совпадению с регистром OCR1A
  
if (DIV == 64) TCCR1B  |= ((1 << CS10) | (1 << CS11) | (1 << WGM12)); // запускаем таймер 1 с предделителем /64 в режиме СТС
if (DIV == 8)  TCCR1B  |= ((1 << CS11) | (1 << WGM12));               // запускаем таймер 1 с предделителем /8 в режиме СТС
if (DIV == 0)  TCCR1B  |= ((1 << CS10) | (1 << WGM12));               // запускаем таймер 1 без предделителя в режиме СТС



  lcd.begin(16, 2);                                    // Запускаем библиотеку LCD

// Выводим на экран обозначения для кол-ва зубьев маховика и кол-ва оборотов
  lcd.setCursor(0,0);                                  // Первый символ (0 пробелов), первая строка
  lcd.print("Z=");
  lcd.setCursor(0,1);                                  // Первый символ (0 пробелов), вторая строка
  lcd.print("RPM=");
  
// Выводим на экран кол-во зубьев маховика
  lcd.setCursor(2,0);                                  // Третий символ (2 пробела), первая строка
  lcd.print(Z);
  
// Выводим на экран начальное кол-во оборотов в минуту
  lcd.setCursor(4,1);                                  // Пятый символ (4 пробела), вторая строка
  RPM = (FREQ * 60 / val_fr / 2 / Z);                  // Подсчитываем актуальную частоту
  lcd.print(RPM);
  
// Информируем о старте генератора
  lcd.setCursor(13,0);      
  lcd.print("RUN");                            
}
 
 
void loop() {
lcd_key = read_LCD_buttons();           // Обрабатываем нажатие кнопки на LCD Shield
if (lcd_key > 0)                        // Если что-то нажато
{ 
  lcd.setCursor(13,1);
  lcd.print("   ");                     // Очищаем поле уведомления о достижении предельной частоты  
  if (lcd_key < 5)                      // Если изменяется частота
  {
    lcd.setCursor(12,0);      
    lcd.print("STOP");                  // Информируем об остановке генератора
    lcd.setCursor(4,1);      
    lcd.print("     ");                 // Очищаем поле вывода частоты
  }
  switch (lcd_key)                      // Производим действие, в зависимости от нажатой кнопки
  {
   case btnRIGHT:                       // Кнопка "ВПРАВО"
     {
     val_fr *= 0.9;                     // Увеличиваем частоту на 10%, уменьшая предельное значение счётчика
     SELECT = 0;                        // Ставим флаг ожидания подтверждения
     break;
     }
   case btnLEFT:                        // Кнопка "ВЛЕВО"
     {
     if (val_fr > 59577)                // Обрабатываем превышение по частоте, иначе придётся использовать long вместо int
       {
       val_fr = val_frmin;
       break;
       }
     val_fr *= 1.1;                     // Уменьшаем частоту на 10%, уыеличивая предельное значение счётчика
     SELECT = 0;                        // Ставим флаг ожидания подтверждения
     break;
     }
   case btnUP:                          // Кнопка "ВВЕРХ"
     {
     val_fr /= 2;                       // Увеличиваем частоту вдвое, уменьшая предельное значение счётчика
     SELECT = 0;                        // Ставим флаг ожидания подтверждения
     break;
     }
   case btnDOWN:                        // Кнопка "ВНИЗ"
     {
     if (val_fr > 32767)                // Обрабатываем превышение по частоте, иначе придётся использовать long вместо int
       {
       val_fr = val_frmin;
       break;
       }
     val_fr *= 2;                       // Уменьшаем частоту вдвое, уыеличивая предельное значение счётчика
     SELECT = 0;                        // Ставим флаг ожидания подтверждения
     break;
     }
   case btnSELECT:                      // Кнопка "SELECT"
     {
     lcd.setCursor(12,0);      
     lcd.print(" RUN");                 // Уведомляем о старте генератора
     OCR1A = val_fr;                    // Настраиваем таймер на новый полупериод колебания
     SELECT = 1;                        // Подтверждение выбора получено
     break;
     }
  }
  
//Если превышает макс.частоту или равно ей, устанавливаем макс.значение и выводим предупреждение на экран
  if (val_fr <= val_frmax)              
       { 
       val_fr = val_frmax;
       lcd.setCursor(13,1);              // Четырнадцатый символ (13 пробелов), первая строка
       lcd.print("MAX");
       }
//Если ниже мин.частоты или равно ей , устанавливаем мин.значение и выводим предупреждение на экран
  if (val_fr >= val_frmin)   
       { 
       val_fr = val_frmin;
       lcd.setCursor(13,1);              // Четырнадцатый символ (13 пробелов), первая строка
       lcd.print("MIN");
  
     }

// Подсчитываем и выводим на экран обновлённую частоту
  RPM = (FREQ * 60 / val_fr /2 / Z);
  lcd.setCursor(4,1);
  lcd.print(RPM);

  delay (500);                           //Ждём 500мс
  
//Выставляем начальные значения для запуска генератора с 1 зуба
  uk     = 2; 
  dead   = 0;
  cnt    = 1;
}
if (SELECT == 0)                                 return;     //Если кнопка SELECT не нажата, ждём.
if (cnt == Z)                                    cnt = 1;    //Начинаем цикл заново при полном обороте
if (cnt == DNO1 || cnt == DNO2 || cnt == DNO3)   uk = 2;     // Указатель на функцию ДНО+ДУИ для зубьев ДНО
if (dead == 1)                                   uk = 0;       //Указатель на функцию мёртвой зоны по флагу отсутствия зуба
}
 

ISR(TIMER1_COMPA_vect) {
  (*mas[uk])();                                    // вызываем функцию по указателю
}
 
 void dead_time(void)                              // функция 0 
{
  PORTD = PORTD & B11110011;            // На выходных пинах лог 0, формируем мёртвую зону (отсутствие зуба)
  cnt ++;                               //Увеличиваем счётчик зуба
  uk = 1;                               // Ставми указатель на функцию зуба ДУИ
  dead = 0;                             //Убираем флаг отсутствия зуба
}

void tooth1(void)                                  // функция 1
{
  PORTD = PORTD | B00001000;            // на пин 3 лог 1, формируем зуб ДУИ
  dead = 1;                             //Ставим флаг отсутствия зуба
}

void tooth2(void)                                  // функция 2
{
  PORTD = PORTD | B00001100;            // на пины 2 и 3 лог 1, формируем зуб ДУИ + зуб ДНО
  dead = 1;                             //Ставим флаг отсутствия зуба
}

Обработку кнопок прерываниями пока не освоил, Ардуинка у меня только 2 недели :-) Разберусь - буду причёсывать.

Rossi25
Offline
Зарегистрирован: 08.02.2016

Добрый день!

Достался мне прибор на основе Вашего кода, который измеряет давление в цилиндрах двигателя внутреннего сгорания, используя вот такой датчик https://www.ebay.ca/itm/G1-4-inch-5V-0-1-2-MPa-Pressure-Transducer-Sensor-Oil-Fuel-Diesel-Gas-Water-Air-/321801631422

При включении от прикуривателя на usb часто пишет ошибка датчика. Каким образом обрабатывается аналоговый сигнал? Можете код привести?

pcdeath
Offline
Зарегистрирован: 03.03.2014

Судя по всему, Вам достался компрессометр с кодом, выложенным на сайте, посвящённом любителям Mazda RX-7:

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

Рекомендуется питать компрессометр от отдельного батарейного источника питания +8...+12В, во избежание помех и ошибок из-за просадки напряжения при прокрутке двигателя стартером.

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

// Компрессометр by PCDeath  

char ver[6] = "v0.14"; 

/* Версия 0.14
Для Arduino Uno R3 с LCD keypad shield. На других платформах не проверялось.
Мерит обычные поршневые двигатели 4Т/2Т или РПД.

В связи с большим разбросом значений кнопок у разных производителей LCD keypad Shield добавлена
возможность калибровки кнопок.
Для калибровки необходимо прицепить тактовую кнопку между цифр.пином 3 и массой.
Датчик давления 5В, при атм.давлении возвращают 0,5В (102), при макс.давлении - 4,5В (922), погрешность не более +/-10%
Верхний предел давления регулируемый, можно использовать 2 различных типа датчиков,
в моём варианте 10,5 кгс/см2 и 14 кгс/см2.

Установлены 2 порога давления - 20% и 30% от показаний датчика, для чёткого разделения по секциям
Датчик подключается  к аналоговому входу Arduino, по умолчанию 1, можно менять.
Рекомендуется питать компрессометр от отдельного батарейного источника питания +8...+12В, во избежание помех и 
ошибок из-за просадки напряжения при прокрутке двигателя стартером.


Управление:

Калибровка кнопок:
Если при включении нажата кнопка на цифр.пине 3, или ПО только что залито в контроллер, компрессометр переходит в режим калибровки.
При калибровке требуется нажать кнопку, которая запрошена на экране, затем  нажать кнопку калибровки.
После записи значений на экране появится приглашение отпустить кнопку калибровки. 
Отпускаем сначала кнопку калибровки, затем - кнопку на keypad shield, которую откалибровали, и переходим к калибровке следующей кнопки. 
По окончании калибровки требуется перезагрузить компрессометр

До замера:
"ВВЕРХ"  переходит к типу датчика 2
"ВНИЗ"   переходит к типу датчика 1
"ВПРАВО" переводит к следующему типу измерения
"ВЛЕВО"  переводит к предыдущему типу измерения


По нажатию кнопки "SELECT" стартует процесс измерения, останавливается через заданное время.
При старте происходит десятикратный опрос датчиков с интервалом в 50 мс. 
Если показания на входе имеют погрешность более 10% от заявленного значения,
(датчик отсутствует или замкнут), на экран выводится предупреждение с указанием, в какую сторону погрешность

Во время роторного замера на экране сырые значения на аналоговом входе для всех камер.

Во время поршневого замера на экране сырые значения.

После роторного замера:
"ВВЕРХ"  показывает скорректированные значения
"ВНИЗ"   возвращается к истинным значениям

Если  хотя бы у одной из камер давление ниже порога или обороты двигателя слишком низкие 
- выдаётся предупреждение о низких оборотах или вылете секции. 

После поршневого замера на кнопки не реагирует.

Если во время замера было достигнуто предельное давление, на которое рассчитан датчик, то при отображении 
результатов поршневого или роторного замера без коррекции информация об этом выводится на экран. 
При отображении результатов роторного замера с коррекцией полученные значения приводятся к эталонным оборотам

Приятного использования.
PCDeath

P.S.
Если захотите поблагодарить создателя, это можно сделать:
1. Добрым словом где-нибудь на форумах
2. Запчастями от РПД ВАЗа и RX-7
3. Деньгами на QIWI-кошелёк +79053834132

Коммерческое распространение кода без согласования с автором запрещено
*/

/**********************************************************************
Четыре параметра ниже требуется поправить под используемые Вами датчики
***********************************************************************/

int MAX_SENS1   = 105;               // Макс.показания датчика 1 типа в кгс/см2 * 10
int MAX_SENS2   = 140;               // Макс.показания датчика 2 типа в кгс/см2 * 10
byte TRESHOLD1  = 20;                // Порог 1 в процентах от диапазона датчика. Рекомендуется не менее 2 атм. 
// При падении давления ниже порога 1 компрессометр считает замер камеры завершённым.
byte TRESHOLD2  = 30;                // Порог 2 в процентах от диапазона датчика. Рекомендуется не менее 1,5 от порога 1.
// При возрастании давления выше порога 2 компрессометр начнёт замер следующей камеры.

/*********************************************************************
Всё, что дальше, менять не стоит, если Вы не знаете точно, что делаете
**********************************************************************/
int MINS        = 102;               // Показания датчика на аналоговом входе при атмосферном (нулевом) давлении
int MAXS        = 922;               // Показания датчика на аналоговом входе при макс.давлении
byte MODE       = 0;                 // Режим работы. 0 - роторный, 1 - поршневой 4Т, 2 - поршневой 2Т
int CORRPM      = 250;               // Обороты, к которым приводится компрессия при коррекции
int MINRPM      = 170;               // Мин.допустимые для измерения обороты, не менее 2/3 от CORRPM
byte Z_FIRST    = 6;                 // Оборот, с которого начинается подсчёт RPM
byte Z_LAST     = 9;                 // Оборот, на котором заканчивается подсчёт RPM, не менее Z_FIRST + 3, не более Z_FIRST + 15
byte TIMER      = 2;                 // Периодичность замера в миллисекундах, не более 30.
byte TIMEOUT    = 30;                // Время, отведённое на замер после нажатия SELECT
#define SENSPIN   1                  // Аналоговый пин, на котором висит датчик
#define btnCAL    3                  // Цифровой пин, на котором висит кнопка калибровки

/********************************
БОЛЬШЕ НАСТРАИВАТЬ НИЧЕГО НЕ НАДО
********************************/


/*******************************
      ОБЪЯВЛЯЕМ ПЕРЕМЕННЫЕ
*******************************/


int  CALIBRE,                        // Для калибровки "нуля" датчика и установки порогов измерения
     TRESHLOW, TRESHHIGH,            // Для вычисления пороговых значений
     KGSCOMPR[3],                    // Итоговые значения компрессии, приведённые к кгс/см2 * 10
     RPM,                            // Частота вращения вала.
     CORRVAL,                        // Значение множителя коррекции
     bUP, bDN, bLT, bRT, bSL;        // Переменные для значений кнопок

byte i,                              // Счётчики для калибровки датчиков
     ERRORCODE,                      // Коды ошибок     
     BTN[6];                         // Массив для калибровки кнопок

boolean START, CORR, RPMCOUNT,       // Флаги старта, коррекции, подсчёта оборотов
        ERR, OUT,                    // Флаг ошибки датчика, флаг вывода на экран
        MAX[3];                      // Массив флагов информирования о достижении предельного давления

volatile int DIGCOMPR[3],            // Максимальные значения компрессии, сырые
             CURRCNT, Z_TMP, CNT[16];// Счётчик "тиков" таймера и массив для расчёта оборотов

volatile byte CH, Z;                 // Номер камеры для расчётов, номер оборота
                                 

volatile boolean STOP, UP;           // Флаги остановки и движения к ВМТ
                 

int MAX_SENS        = MAX_SENS1;                          // Устанавливаем начальное значение в тип 1
unsigned int T_OUT  = TIMEOUT / TIMER * 1000;             // Счётчик "тиков" для таймаута замера
unsigned int FREQ   = 2000 * TIMER;                       // Предельное значение для таймера. 1 мсек = 2000 тактов при делителе /8
unsigned int MAXCNT = 60000 / TIMER / MINRPM;             // Максимальное зачение счётчика для мин.оборотов
long DIVRPM         = 60000 / TIMER * (Z_LAST - Z_FIRST); // Делитель для подсчёта RPM. 


/********************************************
ЦКПЛЯЕМ БИБЛИОТЕКИ И УЧИМ РАБОТАТЬ С КНОПКАМИ
********************************************/


#include <EEPROM.h>                                      // Прицепляем EEPROM для калибровки кнопок

#include <LiquidCrystal.h>                               // Прицепляем LCD
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
byte lcd_key    = 0;
int adc_key_in  = 0;
#define btnNONE   0
#define btnSELECT 1
#define btnLEFT   2
#define btnUP     3
#define btnDOWN   4
#define btnRIGHT  5

// Обработка кнопок
int read_LCD_buttons()
{
adc_key_in = analogRead(0);         // Читаем значение нопок. 
if (adc_key_in > bSL +10 )  return btnNONE;
if (adc_key_in < bRT)  return btnRIGHT; 
if (adc_key_in < bUP)  return btnUP;
if (adc_key_in < bDN)  return btnDOWN;
if (adc_key_in < bLT)  return btnLEFT;
if (adc_key_in < bSL)  return btnSELECT;  
return btnNONE;                    // Если "всё не так" - считаем. что никакая кнопка не нажата.
}




void setup() {
  lcd.begin(16, 2);                          // Запускаем библиотеку LCD
  ERR = 1;                                   // Ставим датчику признак высокого уровня на входе
  
/******************************************
     РИСУЕМ СПЛЕШ-СКРИН С ВЫВОДОМ ВЕРСИИ
******************************************/

  lcd.setCursor(0,0);                        
  lcd.print(" ROTARY C-METER ");             
  lcd.setCursor(0,1);                        
  lcd.print("      BY PCDeath");             
  lcd.setCursor(0,1);                        
  lcd.print(ver);             
  delay (3000);

/*****************************************
НАСТРАИВАЕМ ТАЙМЕР НА НАЧАЛЬНЫЕ ЗНАЧЕНИЯ
*****************************************/

  TCCR1A  = 0; TIMSK1 = 0;                    // Сбрасываем на всякий эти регистры
  TCCR1B  = 0;                                // Мало ли что arduino IDE туда записало
  TCNT1   = 0;                                // Сбрасываем счетный регистр таймера 1
  OCR1A   = 0;                                // Отключаем таймер
  TCCR1B  |= ((1 << CS11) | (1 << WGM12));    // Настраиваем таймер на делитель /8, режим CTC

/*****************************************
     ОБРАБАТЫВАЕМ КАЛИБРОВОЧНУЮ КНОПКУ
*****************************************/

pinMode(btnCAL, INPUT);                      // Назначаем цифр.пину 3 режим входа
digitalWrite(btnCAL, HIGH);                  // Устанавливаем высокий уровень сигнала по умолчанию

if (digitalRead(btnCAL) == 0)                // Если нажата кнопка калибровок
  EEPROM.write(0, 0);                        // Снимаем флаг выполнения калибровки
  
BTN[0] = EEPROM.read(0);                     // Читаем значение флага калибровки
if (BTN[0] != 1) {                           // Если необходима калибровка, 
  ERRORCODE = 5;                             // Выносим в обработку ошибки
  OUT = 1;                                   // Настраиваем флаг записи
  CURRCNT = analogRead(0);                   // Калибруем отсутствие нажатия
}
else {
  bSL = int(EEPROM.read(1) * 4);             // Читаем значение для кнопки "SELECT"  
  bLT = int(EEPROM.read(2) * 4);             // Читаем значение для кнопки "ВЛЕВО"
  bUP = int(EEPROM.read(3) * 4);             // Читаем значение для кнопки "ВВЕРХ"  
  bDN = int(EEPROM.read(4) * 4);             // Читаем значение для кнопки "ВНИЗ"
  bRT = int(EEPROM.read(5) * 4);             // Читаем значение для кнопки "ВПРАВО"
  
}
}


void loop() {
/***************************************
           ОБРАБОТКА ОШИБОК
***************************************/

if (ERRORCODE > 0){                      
  TIMSK1  &= (0 << OCIE1A);              // Запрещаем генерацию прерывания таймера 1
  OCR1A = 0;                             // И останавливаем таймер
  
  if (ERRORCODE == 5) CALIBER();         // Если требуется калибровка, входим в режим калибровки кнопок
  
  /************************************
    ОШИБКА НИЗКИХ ОБОРОТОВ ИЛИ ВЫЛЕТА
  ************************************/
  
  if (ERRORCODE == 3) {
  lcd.setCursor(0,0);      
  lcd.print("LOW RPM!");               // Выводим на экран ошибку
  lcd.setCursor(0,1);     
  lcd.print("OR CHAMBER  DOWN");  
  ERRORCODE ++;
  }
  
  /********************************************
    ОШИБКА ОТСУТСТВИЯ ИЛИ НЕИСПРАВНОСТИ ДАТЧИКА
  ********************************************/
  
  if (ERRORCODE == 1){                 
  lcd.setCursor(0,0);      
  lcd.print(" SENSOR  ERROR! ");       // Выводим на экран ошибку
  lcd.setCursor(0,1);     
  if (ERR == 0)                        // Если собранные значения ниже заявленных более чем на 10%
        lcd.print(" VALUE TOO LOW  "); // Информируем о слишком низком значении
  else  lcd.print(" VALUE TOO HIGH "); // Иначе информируем о слишком высоком значении
  ERRORCODE ++;
  }
  return;
}


/***************************************************************
    ЕСЛИ НЕ ПРОИЗВОДИТСЯ ЗАМЕР КОМПРЕССИИ, ОБРАБАТЫВАЕМ КНОПКИ
***************************************************************/

if (START - STOP == 0 ) {                       
lcd_key = read_LCD_buttons();                   // Обрабатываем нажатие кнопки на LCD Shield
if (lcd_key > 0) BUTTON();                      // Если что-то нажато, идём в функцию обработки кнопок

}

/***************************************************
             ВЫВОД НА ЭКРАН ДО ЗАМЕРА
***************************************************/             

if (START == 0 && OUT == 0) {                  // Если замер не выполнялся и сброшен флаг вывода
  lcd.setCursor(0,0);      
  lcd.print("MAX =       ATMs");               // Отображаем верхний предел выбранного типа датчиков
  lcd.setCursor(6,0);      
  lcd.print((float)MAX_SENS / 10.0);
  if (MODE == 0) {
    lcd.setCursor(0,1);      
    lcd.print("ROTARY          ");             // Отображаем тип замера: роторный
  }
  else {
    lcd.setCursor(0,1);                        // Или поршневой
    lcd.print("PISTON         T");
    lcd.setCursor(14,1);
    if (MODE == 1) lcd.print("4");             // Четырёхтактный
    else           lcd.print("2");             // Или двухтактный
  }
  OUT  = 1;                                    // Ставим флаг вывода, чтоб не мерцал экран
  return;       
}

/*************************
    ВО ВРЕМЯ ЗАМЕРА
*************************/

if (START == 1 && STOP == 0) {                // Если выполняется замер

  lcd.begin(16,2);      
  lcd.print("WAIT     SECONDS");              // Выводим на экран обратный отсчёт
  lcd.setCursor(5,0);        
  lcd.print((T_OUT - CURRCNT) / (1000 / TIMER));
  lcd.setCursor(0,1);                         // и текущие сырые значения компрессии
  lcd.print(DIGCOMPR[0]);
  if (MODE == 0) {                            // Если режим роторный
    lcd.setCursor(6,1);                       // Выводим текущие сырые значения по камерам 2 и 3
    lcd.print(DIGCOMPR[1]);
    lcd.setCursor(12,1);      
    lcd.print(DIGCOMPR[2]);
   }
  delay(1000);
  return;
}

/****************************************************
                   ПОСЛЕ  ЗАМЕРА
****************************************************/



if (STOP == 1 && OUT == 0) {                                  // Если сброшен флаг вывода

  lcd.begin(16,2);                                            // Очищаем экран. begin поставлен, т.к.иногда во время замера вывод на экран из-за прерывания ломается
  
/****************************************************
            ОРГАНИЗУЕМ ПОДСЧЁТ ОБОРОТОВ
****************************************************/
  if (RPMCOUNT == 0) {                                        // Если подсчёт не выполнялся
     for (Z_TMP = 0; Z_TMP <= Z_LAST - Z_FIRST; Z_TMP ++) {   // Проверяем на низкие обороты и вылет секции
     if (CNT[Z_TMP+1] - CNT[Z_TMP] > MAXCNT && MODE == 0)     // Если частота обновления ниже минимального порога и режим замера - роторный
     ERRORCODE = 3;                                           // Ставим флаг низких оборотов
     }
     RPM = DIVRPM / (CNT[Z_LAST - Z_FIRST] - CNT[0]);         // Считаем обороты в минуту
     if (MODE == 1) RPM *= 2;                                 // Если тип замера поршневой 4-тактный, умножаем обороты на 2
     RPMCOUNT = 1;                                            // Ставим флаг выполнения подсчёта
  }

/************************************************
     ПОКАЗЫВАЕМ ОБОРОТЫ ИЛИ ИНФО О КОРРЕКЦИИ
************************************************/

  if (MODE == 0 && CORR == 1) {                               // Если мерился роторный двигатель и установлен флаг коррекции
    CORRVAL = 1000 + (CORRPM - RPM) * 3;                      // рассчитываем значение для множителя х 1000
    lcd.setCursor(9,0);                                       // Информируем о коррекции
    lcd.print("CORR");
    lcd.setCursor(13,0);        
    lcd.print(CORRPM);
  }
  else {
    CORRVAL = 1000;                                           // Иначе ставим множитель в единицу х 1000
    lcd.setCursor(9,0);                                       // И выводим обороты, при которых производился замер
    lcd.print("RPM=   ");
    lcd.setCursor(13,0);        
    lcd.print(RPM);
  }

/******************************************************
       ПЕРЕВОДИМ КОМПРЕССИЮ ИЗ ПОПУГАЕВ В КГС/СМ2
******************************************************/

  for (CH = 0; CH <= 2; CH ++) {                              // Для каждой камеры
    if (DIGCOMPR[CH] > MAXS + 3)                              // Если превышены макс.показания + погрешность
      MAX[CH] = 1;                                            // Ставим флаг достижения максимального порога давления
      KGSCOMPR[CH] =                                          // Мапим показания датчика из сырых в кгс/см2 х 10
    //    map((long)DIGCOMPR[CH] * CORRVAL / 1000, MINS, MAXS, 0, MAX_SENS);             // без учёта начальной калибровки
      map((long)DIGCOMPR[CH] * CORRVAL / 1000, CALIBRE, MAXS, 0, MAX_SENS);              // с учётом начальной калибровки
    }

/*****************************************************
        ПОКАЗЫВАЕМ НА ЭКРАН, ЧЕГО ТАМ НАМЕРИЛИ
*****************************************************/

  if (MODE == 0){                                              // Если режим роторный
    lcd.setCursor(0,0);      
    lcd.print("ROTARY");                                       // Выводим на экран тип замера
    for (CH = 0; CH <= 2; CH ++){                              // И итоговые значения компрессии по камерам
      lcd.setCursor(CH * 6,1);                                          
      if (MAX[CH] == 1 && CORR == 0)                           // Если нет флага коррекции и достигнут макс.порог измерения - информируем об этом
        lcd.print("MAX!");
      else lcd.print((float)KGSCOMPR[CH] / 10.0);
    }
  }
  else {                                                       // Если режим поршневой
    lcd.setCursor(0,0);      
    lcd.print("PISTON");                                       // Выводим на экран инфо о поршневом замере
    lcd.setCursor(0,1);                                        // И итоговые значения компрессии
    if (MAX[0] == 1) {                                         // Если достигнут макс.порог измерения - информируем об этом
      lcd.print("MAX! >");
      lcd.print((float)KGSCOMPR[0] / 10.0);
    }
    else lcd.print((float)KGSCOMPR[0] / 10.0);
  }
  OUT = 1;
}

}

/******************************************************
            ФУНКЦИЯ ОБРАОТКИ НАЖАТИЯ 
******************************************************/
void BUTTON(){
  switch (lcd_key)                              // Производим действие, в зависимости от нажатой кнопки
  {
   case btnRIGHT:                               // Кнопка "ВПРАВО"
     {
      OUT  = 0;                                 // Сбрасываем флаг вывода для обновления экрана
      if (START == 0) {MODE ++;                 // Если замер не выполнялся, переключаем режим
        MODE = min(MODE, 2);                    // Ограничиваем номер режима
      }
     break;
     }
   case btnLEFT:                                // Кнопка "ВЛЕВО"
     {
      OUT  = 0;                                 // Сбрасываем флаг вывода для обновления экрана
      if (START == 0) {MODE --;                 // Если замер не выполнялся, переключаем режим
        if (MODE != 1) MODE = 0;                // Ограничиваем номер режима
      }
     break;
     }
   case btnUP:                                  // Кнопка "ВВЕРХ"
     {
      OUT  = 0;                                 // Сбрасываем флаг вывода для обновления экрана
      if (START == 0) MAX_SENS = MAX_SENS2;     // Переключаем на тип датчика 2
      if (STOP == 1 && MODE == 0) CORR = 1;     // Если роторный замер завершён, ставим флаг коррекции      
     break;
     }
   case btnDOWN:                                // Кнопка "ВНИЗ"
     {
      OUT  = 0;                                 // Сбрасываем флаг вывода для обновления экрана
      if (START == 0) MAX_SENS = MAX_SENS1;     // Переключаем на тип датчика 1
      if (STOP == 1 && MODE == 0) CORR = 0;     // Если роторный замер завершён, убираем флаг коррекции
     break;
     }
   case btnSELECT:                              // Кнопка "SELECT"
     {          
      if (START == 0){                          // Если замер остановлен
          START = 1;                            // Ставим флаг запуска
          OUT  = 0;                             // Сбрасываем флаг вывода для обновления экрана
          lcd.clear();                          // Очищаем экран
          for (i=0; i < 10; i++) {              // Проверяем датчики на отсутствие/замыкание/большую погрешность
              CALIBRE += analogRead(SENSPIN);   // С интервалом 50 мс 10 раз читаем показания
            delay(50);
          }
          if (CALIBRE > 11 * MINS || 
              CALIBRE < 9 * MINS) {             // Если отсутствует/неисправен датчик (погрешность более 10% от заявленной)
              ERRORCODE = 1;                    // Ставим флаги ошибок
              if (CALIBRE < 9 * MINS)           // Если показания ниже заявленных
              ERR = 0;                          // Ставим указатель на низкое значение
             }
          }
      if (ERRORCODE == 0) {                     // Если датчики опрошены успешно
         CALIBRE = analogRead(SENSPIN);         // Калибруем датчики - выставляем "0"
         TRESHLOW =  (long)CALIBRE * (100 + TRESHOLD1) / 100;    // Ставим порог падения давления
         TRESHHIGH = (long)CALIBRE * (100 + TRESHOLD2) / 100;    // Ставим порог роста давления
         delay(500);                           // Ждём 0,5 сек, чтоб точно отпустить кнопку
         OCR1A = FREQ;                         // И настраиваем таймер:
         TIMSK1  |= (1 << OCIE1A);             // Разрешаем генерацию прерывания таймера 1 по совпадению с регистром OCR1A
      }
     break;
     }
  }
  delay(500);
}

/******************************************************
            ФУНКЦИЯ КАЛИБРОВКИ КНОПОК
******************************************************/

void CALIBER(){
while (1 == 1){                              // Входим в бесконечный цикл
    if (digitalRead(btnCAL) == 0) {          // Если нажата кнопка калибровки
       lcd.setCursor(0,0);      
       lcd.print("RELEASE  CALIBER");        // Выводим на экран приглашение отпустить кнопку калибровки
       lcd.setCursor(0,1);
       lcd.print("     BUTTON     ");
       if (i <= 4 && OUT == 0) {             // Если значение калибровки ещё не снято
         i++;                                // Увеличиваем счётчик кнопки на единицу
         BTN[i] = analogRead(0) / 4 + 5;     // Пишем в переменную значение кнопки / 4, чтоб влезло в тип byte
         OUT = 1;                            // Ставим флаг записи значения кнопки
       }
       if (i > 4 && BTN[0] != 1) {           // Если все кнопки откалиброваны и значения ещё не записаны в EEPROM
          BTN[0] = 1;                        // Ставим флаг завершения калибровки
          for (CH = 0; CH <= 5; CH ++)       // Повторяем 6 раз
            EEPROM.write(CH, BTN[CH]);       // Пишем в EEPROM значения из массива
       }
    }
    else {                                   // Если кнопка калибровки не нажата
    delay(100);                              // Задержка для исключения дребезга кнопки
      if (i > 4) {                           // Если калибровка завершена
         lcd.setCursor(0,0);      
         lcd.print("   REBOOT THE   ");      // Выводим на экран приглашение к перезагрузке
         lcd.setCursor(0,1);
         lcd.print("     DEVICE     ");  
      }
      else {                                 // Если калибровка ещё не завершена
           lcd.setCursor(0,0);      
           lcd.print(" PRESS          ");    // Выводим на экран приглашение к нажатию кнопки на KEYPAD
           lcd.setCursor(0,1);      
           lcd.print("                    ");// Выводим на экран приглашение к нажатию кнопки на KEYPAD
           lcd.setCursor(8,0); 
           if (i == 0) lcd.print("SELECT");  // Для каждой кнопки своя строка
           if (i == 1) lcd.print("LEFT");
           if (i == 2) lcd.print("UP");
           if (i == 3) lcd.print("DOWN");
           if (i == 4) lcd.print("RIGHT");
           if (analogRead(0) < CURRCNT - 10 || 
             analogRead(0) > CURRCNT + 10) {    // Если нажата кнопка на Keypad
             lcd.setCursor(0,1);
             lcd.print(" PRESS  CALIBER ");     // Выводим приглашение к нажатию кнопки калибровки
           }
           OUT = 0;
      }

    }
    delay(500);
  }
}





/*************************************************
    А ВОТ ЗДЕСЬ ТВОРИТСЯ ВСЁ САМОЕ ИНТЕРЕСНОЕ
*************************************************/

ISR(TIMER1_COMPA_vect) {                                // Обрабатываем прерывание по таймеру
CURRCNT ++;                                             // Увеличиваем значение текущего счётчика "тиков"

/************************
   ДЛЯ РАСЧЁТА ОБОРОТОВ
************************/
if (Z >= Z_FIRST && Z <= Z_LAST + 1){
    if(CNT[Z - Z_FIRST] == 0) {                         // Если счётчик оборотов в пределах измерений и измерение ещё не записано
     CNT[Z - Z_FIRST] = CURRCNT;                        // Присваиваем значение счётчика элементу массива
    }
}

/***************************
   ДЛЯ РАСЧЁТА КОМПРЕССИИ
***************************/
int TMP = analogRead(SENSPIN);                          // Читаем показания датчика
                                                        // и пишем во временную переменную
if (UP == 1) {                                          // Если установлен флаг движения к ВМТ
   if (TMP >= TRESHLOW) {                               //   Если временное значение больше нижнего порога (окончания замера)
      if (TMP > DIGCOMPR[CH])                           //     Если временное значение больше итогового
          DIGCOMPR[CH] = TMP;                           //       Пишем временное значение в итоговое
   }
   else UP = 0;                                         //   Иначе снимаем флаг движения к ВМТ
}

else {                                                  // Если флаг движения к ВМТ не установлен
     if (TMP >= TRESHHIGH) {                            //   Если текущее значение больше верхнего порога (начала замера следующей камеры)
        UP = 1;                                         //     Ставим флаг движения к ВМТ
        Z ++;                                           //     Прибавляем счётчик оборотов
        if (MODE == 0) CH ++;                           //     Если режим роторный, переходим к следующей камере 
        if (CH > 2) CH = 0;                             //     Ходим по кругу камер
     }
}

/*****************************************
    ДЕЛАЕМ ВЫХОД ИЗ ЗАМЕРА ПО ТАЙМАУТУ
*****************************************/
if (CURRCNT > T_OUT) {                                  // Если вышло время замера
   TIMSK1  &= (0 << OCIE1A);                            // Запрещаем генерацию прерывания таймера 1
   OCR1A = 0;                                           // Останавливаем таймер
   STOP  = 1;                                           // Ставим флаг остановки
   }

}