/* =============================================== Тахометр-мотометр - мои изыскания 20250408 ... 20260330 v.6.3.30 AtMega8, 16MHz, Общий Катод - ОК = FUSES = Low - 0x9E; High - 0xDF; Ext - 0xFF ================================================== */ #include #include #include #include #include "pin.h" // ======================================= // ОПРЕДЕЛЯЕМ КОНСТАНТЫ: #define F_QUARTZ 16000000UL // частота кварца #define DIVIDER 64 // делитель таймера T2, задается в инициализации Т1 #define PART_SEC 60000000UL / (1000000 / (F_QUARTZ / DIVIDER)) // часть секунды (для пересчета оборотов) // Количество импульсов на 1 оборот коленвала: #define REV_RATED_POWER 3600 // обороты при номинальной мощности (введите свои) // 8ми сегментный 4х разрядный индикатор: #define LEVEL 1 // (1) - Общий Анод, (0) - Общий Катод #define RAZR 4 // количество разрядов индикатора #define BRIGHT 0x7F // яркость свечения сегментов (больше значение - ярче) // Минимальный учетный интервал Моточасов: #define TIME_REV 60 // 3600 = 1минута; 216000 = 1час (60 * 60 * 60 (1сек * 1мин * 1час) #define MEM_CELL 0 // начальная ячейка памяти в которой будут храниться моточасы #define MEANING 0x0A0A0A0A // контрольное слово для проверки памяти #define DELAY_VISIBLE 100 // пауза для визуального отображения #define KOEFF 1 // коэффициент усреднения #define TIME_RESET_MOTO 5000 // задержка сброса моточасов #define MODE 3 // кол-во режимов отображения (моточасы -> тахометр -> вольтметр) #define TIME_STEP 10 // шаг измерения времени нажатия кнопки (мс) #define FIRST_MOTO_HOUR 142 // установка первоначального значения моточасов // ======================================= // Рабочие переменные: // Отображаемые на индикаторе символы: uint8_t seg[22] = { 0b11111100, // 0 0b01100000, // 1 0b11011010, // 2 0b11110010, // 3 0b01100110, // 4 0b10110110, // 5 0b10111110, // 6 0b11100000, // 7 0b11111110, // 8 0b11110110, // 9 0b11101110, // A 0b00111110, // B 0b00011010, // C 0b01111010, // D 0b10011110, // E 0b10001110, // F 0b00000000, // 10 - пусто 0b00000010, // 11 - минус 0b11000110, // 12 - градус 0b00111000, // 13 - v 0b00001010, // 14 - R 0b10110110, // 15 - S }; uint8_t dataIndic[RAZR]; // ячейки данных разрядов индикатора uint32_t countMoto = 0; // счетчик моточасов (накопитель оборотов) - 2 uint32_t temp = 0; // счетчик оборотов uint8_t indicatorMode = 0; // счетчик для выбора режима индикации bool flagDot = false; // флаг для отображения точки bool flagFirstTaho = true; // флаг первого перехода на тахометр после запуска volatile uint32_t myMillis = 0; // счетчик миллисекунд uint32_t myMillisOld = 0; // старый счетчик миллисекунд volatile bool flagAdcConv = true; uint32_t adcResultOld = 0; // физическое состояние кнопки enum ButtonResult { buttonNotPress, // код если кнопка не нажата buttonShortPress, // код короткого нажатия buttonLongPress // код длинного нажатия }; // ################################################################ // Усредняем показания оборотов: uint16_t filtr(uint16_t rev) { static uint16_t revOld = 0; // предыдущие обороты двигателя static uint8_t a = pow(2, KOEFF) - 1; (revOld > rev / KOEFF) ? revOld = (a * revOld + rev) >> KOEFF : revOld = rev; return revOld; } //================================================================ // Усредняем показания напряжения: uint16_t filtrVcc(uint16_t revVcc) { static uint16_t revOldVcc = 0; // предыдущее напряжение static uint8_t a = (1 << KOEFF) - 1; (revOldVcc > revVcc / (KOEFF)) ? revOldVcc = (a * revOldVcc + revVcc) >> (KOEFF) : revOldVcc = revVcc; return revOldVcc; } //================================================================ // Инициализация прерываний: void INTERRUPT_Init() { // Инициализация T1: TCCR1B |= (1 << CS11) | (1 << CS10); // делитель = 64 // Инициализация T2: TCCR2 |= (1 << CS22); //(1 << CS21) | (1 << CS20); // делитель = 32 OCR2 = BRIGHT; TIMSK |= (1 << OCIE2) | (1 << TOIE2); // Инициализация INT0: MCUCR |= (1 << ISC01) | (1 << ISC00); //-по фронту//(1 << ISC01);//-по спаду GICR |= (1 << INT0); // Инициализация ADC: ADMUX |= (1 << REFS0) | (1 << REFS1) // опорное = 5в | (1 << MUX2) | (1 << MUX0) // измеряем на ADC5 | (1 << ADLAR); ADCSRA |= (1 << ADEN) // разрешение АЦП | (1 << ADSC) // запуск преобразования | (1 << ADPS2) | (1 << ADPS1) // делитель на 64 (250kHz) | (1 << ADIE); // разрешение прерывания от АЦП } //================================================================ // Включение сегментов: void segWork(uint8_t sTem) { uint8_t segments = seg[sTem]; (LEVEL) ? segments = ~segments : segments = segments; (segments & (1 << 7)) ? PORT_A |= (1 << SEG_A) : PORT_A &= ~(1 << SEG_A); (segments & (1 << 6)) ? PORT_B |= (1 << SEG_B) : PORT_B &= ~(1 << SEG_B); (segments & (1 << 5)) ? PORT_C |= (1 << SEG_C) : PORT_C &= ~(1 << SEG_C); (segments & (1 << 4)) ? PORT_D |= (1 << SEG_D) : PORT_D &= ~(1 << SEG_D); (segments & (1 << 3)) ? PORT_E |= (1 << SEG_E) : PORT_E &= ~(1 << SEG_E); (segments & (1 << 2)) ? PORT_F |= (1 << SEG_F) : PORT_F &= ~(1 << SEG_F); (segments & (1 << 1)) ? PORT_G |= (1 << SEG_G) : PORT_G &= ~(1 << SEG_G); (segments & (1 << 0)) ? PORT_H |= (1 << SEG_H) : PORT_H &= ~(1 << SEG_H); } //================================================================ // Гашение всех разрядов перед установкой сегментов: ISR(TIMER2_COMP_vect) { (LEVEL) ? PORT_RAZ1 &= ~(1 << RAZ_1) : PORT_RAZ1 |= (1 << RAZ_1); (LEVEL) ? PORT_RAZ2 &= ~(1 << RAZ_2) : PORT_RAZ2 |= (1 << RAZ_2); (LEVEL) ? PORT_RAZ3 &= ~(1 << RAZ_3) : PORT_RAZ3 |= (1 << RAZ_3); (LEVEL) ? PORT_RAZ4 &= ~(1 << RAZ_4) : PORT_RAZ4 |= (1 << RAZ_4); } //================================================================ // Основная индикация: ISR(TIMER2_OVF_vect) { static uint8_t s = 0; switch (s) { case 0: segWork(dataIndic[0]); (LEVEL) ? PORT_RAZ1 |= (1 << RAZ_1) : PORT_RAZ1 &= ~(1 << RAZ_1); break; case 1: segWork(dataIndic[1]); if (flagDot) { (!LEVEL) ? PORT_H |= (1 << SEG_H) : PORT_H &= ~(1 << SEG_H); } else { (LEVEL) ? PORT_H |= (1 << SEG_H) : PORT_H &= ~(1 << SEG_H); } (LEVEL) ? PORT_RAZ2 |= (1 << RAZ_2) : PORT_RAZ2 &= ~(1 << RAZ_2); break; case 2: segWork(dataIndic[2]); (LEVEL) ? PORT_RAZ3 |= (1 << RAZ_3) : PORT_RAZ3 &= ~(1 << RAZ_3); break; case 3: segWork(dataIndic[3]); (LEVEL) ? PORT_RAZ4 |= (1 << RAZ_4) : PORT_RAZ4 &= ~(1 << RAZ_4); break; } (s == 3) ? s = 0 : s++; // крутим активный разряд // почти = 1мс (976 мкс) myMillis++; } //================================================================ // Запись данных в массив для отображения: void visible(uint16_t vis) { dataIndic[3] = vis % 10; dataIndic[2] = vis / 10 % 10; dataIndic[1] = vis / 100 % 10; dataIndic[0] = vis / 1000; // Гасим "левый" ноль: if (dataIndic[0] == 0) { dataIndic[0] = 0x10; if (dataIndic[1] == 0) { dataIndic[1] = 0x10; if (dataIndic[2] == 0) { dataIndic[2] = 0x10; } } } } //=============================================================== // Подсчет оборотов (по таймеру T2): ISR(INT0_vect) { static uint32_t countRev = 0; temp = TCNT1; TCNT1 = 0; if (countRev < TIME_REV) // если меньше учетного интервала времени - увеличиваем счетчик { countRev++; } else // иначе - увеличиваем счетчик моточасов { countRev = 0; countMoto++; } temp = PART_SEC / temp; if (flagFirstTaho) { indicatorMode = 1; // при первых оборотах двс, однократно включаем отображение тахометра flagFirstTaho = false; } } //=============================================================== // Измеряем напряжение: void vccMeasurement() { ADCSRA |= (1 << ADEN) | (1 << ADSC) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0) | (1 << ADIE); flagAdcConv = false; } //=============================================================== // Сброс моточасов: void resEeprom() { eeprom_update_dword((uint32_t *)MEM_CELL, MEANING); eeprom_update_dword((uint32_t *)MEM_CELL + 1, 0); } //=============================================================== // Читаем моточасы из EEPROM: bool readEeprom() { if (eeprom_read_dword((uint32_t *)MEM_CELL) == MEANING) { countMoto = eeprom_read_dword((uint32_t *)MEM_CELL + 1); return true; } else { return false; } } //=============================================================== // ADC (измеряем напряжение): ISR(ADC_vect) { ADCSRA = 0; // выключаем АЦП uint16_t mes = ADCH; // filtrVcc(ADCH); // если напряжение < 7v: if (mes < 164) { // запрещаем прерывания: cli(); (LEVEL) ? PORT_RAZ1 &= ~(1 << RAZ_1) : PORT_RAZ1 |= (1 << RAZ_1); (LEVEL) ? PORT_RAZ2 &= ~(1 << RAZ_2) : PORT_RAZ2 |= (1 << RAZ_2); (LEVEL) ? PORT_RAZ3 &= ~(1 << RAZ_3) : PORT_RAZ3 |= (1 << RAZ_3); (LEVEL) ? PORT_RAZ4 &= ~(1 << RAZ_4) : PORT_RAZ4 |= (1 << RAZ_4); // сохраняем моточасы в EEPROM: eeprom_update_dword((uint32_t *)MEM_CELL + 1, countMoto); for (uint8_t i = 0; i < RAZR; ++i) { dataIndic[i] = 16; } TCNT1 = 0; // сбрасываем счетчик оборотов // и выключаемся: while (1) ; } adcResultOld = mes; //(mes - 164) * 10 + 700; // пересчет показаний напряжения flagAdcConv = true; } //=============================================================== // Обработка нажатия кнопки: enum ButtonResult readBtnPress(void) { uint16_t btnPressTime = 0; while (!(PIN_BTN & (1 << BTN))) { _delay_ms(TIME_STEP); btnPressTime += TIME_STEP; if (btnPressTime > TIME_RESET_MOTO) { btnPressTime = TIME_RESET_MOTO; break; } } if (btnPressTime >= TIME_RESET_MOTO) return buttonLongPress; if (btnPressTime >= 30) return buttonShortPress; return buttonNotPress; } //=============================================================== int main(void) { init_pin(); dataIndic[0] = 0x11; dataIndic[1] = 0x11; dataIndic[2] = 0x11; dataIndic[3] = 0x11; // Инициализируем прерывания: INTERRUPT_Init(); // Разрешаем прерывания: sei(); // проверяем нажатие кнопки для сброса: if (readBtnPress() == buttonLongPress) { resEeprom(); for (uint8_t j = 0; j < 3; ++j) { dataIndic[0] = 0x14; dataIndic[1] = 0xE; dataIndic[2] = 0x15; dataIndic[3] = 0x11; _delay_ms(200); dataIndic[0] = 0x10; dataIndic[1] = 0x10; dataIndic[2] = 0x10; dataIndic[3] = 0x10; _delay_ms(200); } } // Запись начального значения моточасов: if (FIRST_MOTO_HOUR > 0) { if (eeprom_read_dword((uint32_t *)MEM_CELL) != MEANING) { eeprom_update_dword((uint32_t *)MEM_CELL, MEANING); eeprom_update_dword((uint32_t *)MEM_CELL + 1, FIRST_MOTO_HOUR); } } // Читаем EEPROM: if (!readEeprom()) { resEeprom(); } TCNT1 = 0; //--------------------------------------------------------- while (1) { // проверяем нажатие кнопки управления режимами: if (readBtnPress() == buttonShortPress) (indicatorMode < (MODE - 1)) ? indicatorMode++ : indicatorMode = 0; switch (indicatorMode) { case 0: { // отображаем моточасы: flagDot = false; if (countMoto > 9999) countMoto = 0; visible(countMoto); myMillisOld = myMillis; } break; case 1: { // отображаем тахометр (и "убиваем" младший разряд): uint16_t temp2 = filtr(temp); if (temp2 < REV_RATED_POWER + 2000) { visible(temp2 / 10 * 10); } else { dataIndic[0] = 0x11; dataIndic[1] = 0x11; dataIndic[2] = 0x11; dataIndic[3] = 0x11; } flagDot = false; myMillisOld = myMillis; } break; case 2: { // отображаем вольтметр: dataIndic[3] = 0x13; // dataIndic[3] = adcResultOld % 10; dataIndic[2] = adcResultOld / 10 % 10; dataIndic[1] = adcResultOld / 100 % 10; dataIndic[0] = adcResultOld / 1000; flagDot = true; // Гасим "левый" ноль: if (dataIndic[0] == 0) { dataIndic[0] = 0x10; } myMillisOld = myMillis; } break; } // задержка для отображения цифр while (myMillis <= myMillisOld + DELAY_VISIBLE) { vccMeasurement(); } } }