Тахометр

Конструктор
Offline
Зарегистрирован: 02.01.2017

Всем привет!

Спешу поделиться дебютной работой: на Arduino Nano собрал тахометр для Шевроле Ланос. Задача была сделать минимальный по размеру, не цифровой (легче воспринимается).

 

 

 

 

 

 

 

 

 

 

 

 

 

Маленьких диодов Шотки не нашёл, пришлось ставить большие.

Разъём припаял к плате комбинации приборов, блок разместил в закрытом отсеке для четырёх батареек АА (выключатель не задействовал). Индикатор выполнил на полоске стеклотекстолита (убавил по толщине) и SMD-светодиодах. Корпус - из гелевой ручки, шкала напечатана на бумаге (планирую потом на прозрачной плёнке для лазерных принтеров).

Цвета светодиодов выбрал по оптимуму для двигателя. При включении габаритов яркость снижается.

При заглушенном двигателе моргает индикатор "0".

Индикатор пока никак не закрепил, после доработки приклею пористым скотчем к накладке панели приборов.

Видео разместил здесь:

https://www.youtube.com/watch?v=ieEQ1Reh_ys

Покатался, вроде удобно.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

А чем плох родной? Стрелочный?

Конструктор
Offline
Зарегистрирован: 02.01.2017

ЕвгенийП пишет:

А чем плох родной? Стрелочный?

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

Тем более это была первая практическая работа с Arduino.

todlich
Offline
Зарегистрирован: 01.02.2017

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

Конструктор
Offline
Зарегистрирован: 02.01.2017

Добрый вечер.

Выкладываю схему и плату тахометра. Если интересно про скетч - пишите, размещу позднее (сейчас нет возможности).

Подключал: Земля, +12, габарит - припаивал провода непосредственно на плату комбинации приборов.

Провод от блока управления двигателем (зелёный с чёрной полосой) приходит на разъём комбинации приборов, но в ней отсутствует контакт - припаялся прямо к проводу жгута.

Вид со стороны деталей

Резисторы R5-R8 SMD, припаяны со стороны дорожек.

Вид со стороны дорожек:

Конструктор
Offline
Зарегистрирован: 02.01.2017

Изначально планировалась установка на плату винтового разъёма, но в итоге провода просто припаял.

На схеме показаны цвета компьютерного кабеля. Он оказался слишком толстым и не поместился вместе с платой светодиодов в корпус ручки. Жгут к светодиодам сделал из проводов МГТФ в трубке - более гибкий и тоньше.

Конструктор
Offline
Зарегистрирован: 02.01.2017

На плате не показано отверстие для крепления ЕН5 - после пайки её "положил" на плату и просверлил по месту (площадку дорожек сделал для лучшего теплоотвода).

todlich
Offline
Зарегистрирован: 01.02.2017

спасибо большое, код тоже бы хотелось посмотреть, есть вопрос, что за элемент БУД А5 и как происходит съем сигнала

Конструктор
Offline
Зарегистрирован: 02.01.2017

//Имена для светодиодов
#define LED0  0
#define LED1  1
#define LED2  2
#define LED3  3
#define LED4  4
#define LED5  5
#define LED6  6
#define LED7  7
#define LED8  8
#define LED9  9
#define LED10 10
#define LED11 11
#define LED12 12
#define LED13 13

// Признак мерцания для зоны
#define FlashNo false
#define FlashYes true

//Кодировка цвета светодиодов
#define Red     0
#define Yellow  1
#define Green   2

//Выводы для светодиодов
#define Anod1   2   //Коричневый с белой полосой
#define Anod2   3   //Коричневый
#define Anod3   4   //Оранжевый
#define Anod4   7   //Оранжевый с белой полосой
#define Katod1  5   //Голубой с белой полосой
#define Katod2  6   //Зелёный
#define Katod3  9   //Зелёный с белой полосой
#define Katod4  10  //Голубой

#define LEDpin  13  //Вывод на встроенный светодиод

/* Таблица яркости светодиодов
    Цвет, Яркость дневная, Яркость ночная */
const byte TableColor[] = {
  Red,    50,   200,
  Yellow, 50,   200,
  Green,  230,  240
};

/* Таблица светодидов
   Номер светодиода, Вывод анода, Вывод катода, Цвет */
const byte TableLED[] = {
  Anod1, Katod1, Red,    //LED0
  Anod4, Katod1, Red,    //LED1
  Anod4, Katod2, Yellow, //LED2
  Anod1, Katod2, Yellow, //LED3
  Anod1, Katod3, Green,  //LED4
  Anod2, Katod3, Green,  //LED5
  Anod2, Katod4, Green,  //LED6
  Anod3, Katod4, Green,  //LED7
  Anod3, Katod2, Green,  //LED8
  Anod2, Katod2, Yellow, //LED9
  Anod2, Katod1, Yellow, //LED10
  Anod3, Katod1, Red,    //LED11
  Anod3, Katod3, Red,    //LED12
  Anod4, Katod3, Red     //LED13
};

/* Таблица зон
   Светодиод, Мерцание, Нижняя граница */
const byte TableZone[] = {
  LED0,  FlashYes,    0,  //Зона 0
  LED1,  FlashNo,     8,  //Зона 1
  LED2,  FlashNo,    16,  //Зона 2
  LED3,  FlashNo,    25,  //Зона 3
  LED4,  FlashNo,    33,  //Зона 4
  LED5,  FlashNo,    41,  //Зона 5
  LED6,  FlashNo,    50,  //Зона 6
  LED7,  FlashNo,    58,  //Зона 7
  LED8,  FlashNo,    66,  //Зона 8
  LED9,  FlashNo,    75,  //Зона 9
  LED10, FlashNo,    83,  //Зона 10
  LED11, FlashNo,    91,  //Зона 11
  LED12, FlashNo,   100,  //Зона 12
  LED13, FlashYes,  108   //Зона 13
};

#define LightPin        11    //Вывод для контроля включения габаритов (HIGH – включены габариты)
#define EnginePin       8     //Вывод для приёма синхросигнала

#define MeasureTime     500   //Период измерения синхроимпульсов (0,5 сек)
#define MinSignalTime   50    //Длительность защиты от антидребезга (микросекунды)
#define FlashTime       300   //Длительность горения светодиода при мерцании (0,3 сек)

#define StartShowTime   3000  //Длительность стартового отображения (2 сек)
#define LEDShowTime     100   //Длительность горения светодиода (динамическая индикация – микросекунды)
#define EngineCountMax  255   //Максимально допустимое значение счётчика синхроимпульсов

byte Zone; //Номер зоны частоты

void  SwitchOff() //Подпрограмма гашения всех светодиодов
{
  //Перебор всех светодиодов от нулевого до размера таблицы под светодиоды
  for (byte CurrentLED = 0; CurrentLED <= (sizeof(TableLED) / 3 - 1); CurrentLED++)
  {
    SwitchOffLED(CurrentLED); //Отключение светодиода
  }
}

void  SwitchOffLED(byte CurrentLED) //Подпрограмма гашения светодиода
{
  digitalWrite(TableLED[(CurrentLED ) * 3], LOW);   //Отключение анода светодиода
  pinMode(TableLED[(CurrentLED ) * 3  + 1], INPUT); //Настройка вывода для катода как вход
}

/*Подпрограмма зажигания светодиода
  Входная переменная - номер светодиода */
void SwitchOnLED(byte CurrentLED) {
  byte Brightness;                                    //Яркость
  byte CurrentAnod = TableLED[(CurrentLED * 3)];      //Вывод для анода указанного светодиода
  byte CurrentKatod = TableLED[(CurrentLED * 3) + 1]; //Вывод для катода указанного светодиода
  byte CurrentColor = TableLED[(CurrentLED * 3) + 2]; //Цвет указанного светодиода
  //Serial.print("LED: ");
  //Serial.println(CurrentLED);
  //Serial.print("AnodPin: ");
  //Serial.println(CurrentAnod);
  //Serial.print("KatodPin: ");
  //Serial.println(CurrentKatod);
  //Serial.print("ColorCode: ");
  //Serial.println(CurrentColor);



  //Поиск строки яркости для текущего цвета
  for (Brightness = 0; TableColor[Brightness * 3] != CurrentColor; Brightness++);

  if (digitalRead(LightPin) == LOW) //Проверка включения габаритов
  {
    Brightness = TableColor[(Brightness * 3) + 1]; // Выбор скважности «день»
  }
  else
  {
    Brightness = TableColor[(Brightness * 3) + 2]; // Выбор скважности «ночь»
  }
  //Serial.print("Brightness: ");
  //Serial.println(Brightness);
  //Serial.println();

  digitalWrite(CurrentAnod, HIGH);        //Включение анода светодиода
  analogWrite(CurrentKatod, Brightness);  //Включение катода светодиода
}

/*  Подпрограмма стартовой индикации
    (динамическая индикация всех светодиодов) */
void  StartLight()
{
  byte CurrentLED = 0; //Текущий светодиод
  unsigned long StartTime = millis(); //Запоминание времени начала индикации

  //Serial.println("StartLight On");

  //Пока длительность СтартШоу меньше заданной
  while ((millis() - StartTime) <= StartShowTime)
  {

    //Serial.print("LED ");
    //Serial.print(CurrentLED);
    //Serial.print(" On ");
    SwitchOnLED(CurrentLED);                      //Включить текущий светодиод

    //delay(LEDShowTime);                         //Задержка на время горения светодиода
    delayMicroseconds(LEDShowTime);               //Задержка на время горения светодиода
    SwitchOffLED(CurrentLED);                     //Погасить текущий светодиод
    if (CurrentLED == (sizeof(TableLED) / 3 - 1)) //Если текущим был последний светодиод:
    {
      CurrentLED = 0;                             //Сделать текущим нулевой светодиод
    }
    else                                          //Иначе:
    {
      CurrentLED++;                               //Сделать текущим следующий светодиод
    }

    //Serial.println(" Off");
  }



/*
  for (byte CurrentLED = 0; CurrentLED <= (sizeof(TableLED) / 3 - 1); CurrentLED++)
  {
    SwitchOnLED(CurrentLED); //Включить текущий светодиод
    delay(300);
    SwitchOffLED(CurrentLED); //Отключение светодиода
  }
*/
  //Serial.println("StartLight Off");
}

void setup() //Подпрограмма инициализации
{
  //pinMode(2, INPUT_PULLUP);
  //Serial.begin(9600);

  for (byte CurrentLED = 0; CurrentLED <= (sizeof(TableLED) / 3 - 1); CurrentLED++) //Перебрать все светодиоды
  {
    //Serial.print("LED: ");
    //Serial.println(CurrentLED);
    //Serial.print("AnodPin: ");
    //Serial.println(TableLED[(CurrentLED * 3)]);
    //Serial.print("KatodPin: ");
    //Serial.println(TableLED[(CurrentLED * 3)+1]);
    //Serial.print("ColorCode: ");
    //Serial.println(TableLED[(CurrentLED * 3)+2]);
    //Serial.println();


    pinMode(TableLED[(CurrentLED * 3)], OUTPUT);    //Настройка вывода для анода как выход
    digitalWrite(TableLED[(CurrentLED * 3)], LOW);  //Отключение анода светодиода
    //Настройка вывода для катода как вход (гашение через катоды)
    pinMode(TableLED[(CurrentLED * 3) + 1] , INPUT);

    //pinMode(LEDpin, OUTPUT);                        //Настройка вывода встроенного светодиода как выход
    //digitalWrite(LEDpin, LOW);                      //Отключение встроенного светодиода

  }

  /*digitalWrite(2, HIGH);
    analogWrite(9, 100);
    Serial.println("On LED1");
    delay(5000);
    digitalWrite(2, LOW);
    Serial.println("Off LED1");*/

  pinMode(LightPin, INPUT_PULLUP);  //Настройка вывода для контроля включения габаритов на ввод
  //digitalWrite(LightPin, HIGH);   //Включение подтягивающего резистора
  pinMode(EnginePin, INPUT_PULLUP); //Настройка вывода для приёма синхроимпульсов на ввод
  //digitalWrite(EnginePin, HIGH);  //Включение подтягивающего резистора


  //Serial.begin(9600);
  StartLight(); //Подпрограмма стартовой индикации
  Serial.begin(9600);
}

void loop() //Основная программа
{
  /*  Определение количества синхроимпульсов за интервал измерения
      Гашение светодиодов, если текущая зона мерцающая
      Определение зоны
      Гашение светодиодов
      Зажигание светодиода, соответствующего текущей зоне
      Выдача в UART числа синхроимпульсов и  номера зоны*/

  /* while (1)
    {
         Serial.print("EnginePin: ");
         Serial.println(digitalRead(EnginePin));
         delay(1000);
    }*/

  byte TempZone = CurrentZone(EngineCounter());
  byte TempLED = TableZone[TempZone  * 3];
  //Serial.print("CurrentLED: ");
  //Serial.print(TempLED);
  SwitchOff(); //Гашение светодиодов
  SwitchOnLED(TempLED);
  //SwitchOn(ZoneLight[(CurrentZone(EngineCounter()) - 1) * 2]);
}

/*Подпрограмма счёта синхроимпульсов и гашения при мерцании
  Счёт производится по переднему фронту синхроимпуса
  Реализована защита от дребезга */
byte EngineCounter()
{
  boolean LastState = HIGH;  //Задание "прошлого" состояния синхросигнала
  boolean TempState = HIGH;  //Задание "временного" состояния синхросигнала
  boolean StartFlag = LOW;   //Сброс признака начала отсчёта
  byte EngineCount = 0;      //Обнуление счётчика синхроимпульсов
  unsigned long StartMeasureTime = millis();        //Запоминание времени начала отсчёта
  unsigned long StartChangeTime = StartMeasureTime; //Запоминание времени последнего изменения синхросигнала

  //Подсчёт синхроимпульсов - пока интервал измерения не вышел
  while ((millis() - StartMeasureTime) <= MeasureTime)
  {
    boolean NewState = digitalRead(EnginePin);      //Запоминание нового состояния синхросигнала
    //digitalWrite(LEDpin, NewState);                 //Вывод синхросигнала на встроенный светодиод
    if (TempState != NewState)                      //Если синхросигнал изменился
    {

      // Serial.print("TempState ");
      // Serial.print(TempState);
      // Serial.print(" NewState ");
      // Serial.println(NewState);



      TempState = NewState;       //Запоминание нового состояния синхросигнала
      //StartChangeTime = millis(); //Запоминание времени изменения синхросигнала
      StartChangeTime = micros(); //Запоминание времени изменения синхросигнала
    }
    else
    {
      if ((micros() - StartChangeTime) >  MinSignalTime) //Если время антидребезга вышло
      {
        if ((LastState == LOW) && (NewState == HIGH))   //Если пришёл и держался передний фронт
        {
          if (StartFlag == LOW)                         //Если отсчёт ещё не начинался
          {
            StartFlag = HIGH;                           //Установить признак начала отсчёта
            StartMeasureTime = millis();                //Запоминание времени начала отсчёта
          }
          else                                          //Если отсчёт начат
          {
            EngineCount++;                              //Приращение счётчика синхроимпульсов
          }
        }
        LastState = NewState;                           //Запоминание державшегося состояния
      }
    }
    //Serial.print("Zone ");
    //Serial.print(Zone );
    //Serial.print(" FlashAttribute ");
    //Serial.println(ZoneLight[(Zone) * 2 + 1]);
    //Гашение светодиодов, если время горения вышло и зона мерцающая
    if (((millis() - StartMeasureTime) >= FlashTime) && (TableZone[Zone * 3 + 1]))
    {
      SwitchOffLED(TableZone[Zone * 3 ]); //Погасить светодиод текущей зоны
    }
  }
  //digitalWrite(LEDpin, LOW);              //Отключение встроенного светодиода
  return EngineCount;                     //Вернуть количество синхроимпульсов
}


//Альтернативная программа определения зоны
byte CurrentZoneA(byte EngineCount)
{
  for (Zone = 0; Zone <= sizeof(TableZone) / 3 - 1; Zone++) //Перебор всех зон
  {
    /* Если нижний предел текущей зоны превышает
       количество синхроимпульсов */
    if (TableZone[(Zone ) * 3 + 2] >  EngineCount)
    {
      Zone--; //Возврат к предыдущей зоне
      break;  //Выход из цикла
    }
  }

/*
  Serial.print("\n\nPulses: ");
  Serial.println(EngineCount);
  Serial.print("Zone: ");
  Serial.println(Zone);
*/

  return Zone;
}

byte CurrentZone(byte EngineCount) //Подпрограмма определения зоны
{
  //Serial.print("CurrentZone begining... ");
  if (EngineCount >= TableZone[6  * 3 + 2]) //Test_6
  {
    if (EngineCount >= TableZone[10  * 3 + 2]) //Test_10
    {
      if (EngineCount >= TableZone[12  * 3 + 2]) //Test_12
      {
        if (EngineCount >= TableZone[13  * 3 + 2]) //Test_13
          Zone = 13;
        else //Test_13
          Zone = 12;
      }
      else //Test_12
      {
        if (EngineCount >= TableZone[11  * 3 + 2]) //Test_11
          Zone = 11;
        else //Test_11
          Zone = 10;
      }
    }
    else //Test_10
    {
      if (EngineCount >= TableZone[8  * 3 + 2]) //Test_8
      {
        if (EngineCount >= TableZone[9  * 3 + 2]) //Test_9
          Zone = 9;
        else //Test_9
          Zone = 8;
      }
      else //Test_8
      {
        if (EngineCount >= TableZone[7  * 3 + 2]) //Test_7
          Zone = 7;
        else //Test_7
          Zone = 6;
      }
    }
  }
  else //Test_6
  {
    if (EngineCount >= TableZone[2  * 3 + 2]) //Test_2
    {
      if (EngineCount >= TableZone[4  * 3 + 2]) //Test_4
      {
        if (EngineCount >= TableZone[5  * 3 + 2]) //Test_5
          Zone = 5;
        else //Test_5
          Zone = 4;
      }
      else //Test_4
      {
        if (EngineCount >= TableZone[3  * 3 + 2]) //Test_3
          Zone = 3;
        else //Test_3
          Zone = 2;
      }
    }
    else //Test_2
    {
      if (EngineCount >= TableZone[1  * 3 + 2]) //Test_1
        Zone = 1;
      else //Test_1
        Zone = 0;
    }
  }
  SwitchOff(); //Гашение светодиодов
//  Serial.print("\n\nPulses: ");
//  Serial.println(EngineCount);
//  Serial.print("Zone: ");
//  Serial.println(Zone);

  //Serial.println(String("Pulses: ") + EngineCount + String("/ Zone: ") + Zone);
  return Zone;
}
[/code]

О подключении я уже написал выше: синхросигнал для тахометра формирует блок управления двигателем (БУД). Он приходит к комбинаци приборов на разъёме жгута проводов - зелёный провод с чёрной полосой. На разъёме комбинации приборов контакта нет, так как отсутствует тахометр.

Вот собственно код.

Жду море критики :)

 

todlich
Offline
Зарегистрирован: 01.02.2017

большое спасибо, потеплеет, буду пробовать)

shoorachka
Offline
Зарегистрирован: 09.03.2017

Коллеги-ардуинщики, приветствую! Нужна ваша помощь. Делаю тахометр на лодочный мотор. Принцып простой: сигнальный провод наматывается на высоковольтный провод и подключается к контакту с прерываниями. Скетч рабочий, в тестовом режиме все работает. Но вот когда доходит до практики, один разряд искры дает сразу несколько срабатываний, что увеличивает частоту в н раз. Я так понимаю, это связано с колебательным характером сигнала. Может кто подскадет, какую обвязку из эл.деталей нужно сделать, чтобы сигнал был с одним пиком? Может какие-то RC-фильтры? Заранее спасибо за любую инфу или тык пальцем, где копать.

evgta
Offline
Зарегистрирован: 02.09.2016

софтовую задержку дать на последующее измерение после первого сигнала, но не длиннее чем время между 2-я импульсами на мах оборотах

зажигание контактное?

shoorachka
Offline
Зарегистрирован: 09.03.2017

evgta, спасибо за ответ! Думал об этом, но внутри прерывания функция delay() не работает, конечно может я что-то не так прочтал. :(. Зажигание контактное.

evgta
Offline
Зарегистрирован: 02.09.2016

значит дребезжат контакты.

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

или цикл через миллис в прерывании сделать на определенное время для задержки

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

evgta пишет:

или цикл через миллис в прерывании сделать на определенное время для задержки

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

Нормально работает delayMicroseconds или как там его зовут.

shoorachka
Offline
Зарегистрирован: 09.03.2017

Я правильно понял, что делейМикросекондс внутри прерывания работает? Заранее спасибо за ответ!

Конструктор
Offline
Зарегистрирован: 02.01.2017

Привет!

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

Насчёт дребезга, правильно подсказали: после обработки первого импульса нужно сделать ожидание "длинного нуля" (например, как в моём скетче).