Radio Life - Вольтметр постоянного тока на Arduino

Программа вольтметра постоянного тока
Версия 03.014a


// ================================================================ // подключаем стандартную библиотеку LiquidCrystal #include <LiquidCrystal.h> #define TITLE "Multimeter" #define R2 220.0 // резисторы входного делителя #define R3 10000.0 // в омах #define KEYPIN 2 // вход подключения кнопки // Ячейки, используемые АЦП // float ADC_Kadc; // коэффициент делителя float ADC_Vref; // опорное напряжение float ADC_Vin; // входное напряжение volatile float ADC_Vmax; // максимальное volatile float ADC_Vmid; // среднее volatile float ADC_Vsum; // сумма отсчетов АЦП для среднего volatile int ADC_count; // число отсчетов АЦП volatile int ADC_result; // двоичный код в регистрах АЦП #define VREF1100 1100.0 #define VREF5000 5000.0 // Ячейки, используемые WDT_intr // volatile int WDT_count; // ячейки входа кнопки с подавлением дребезга // volatile int KEY_value; // состояние входа volatile int KEY_delay = 100; // задержка на дребезг // Слово состояния программы volatile unsigned int SYS_status; #define SYS_STARTRDY 0x8000 // бит готовности к работе #define SYS_SCREENRDY 0x4000 // бит вывода на экран статической информации #define SYS_ADCRDY 0x2000 // бит готовности кода АЦП #define SYS_SCREEN1 0x1000 #define SYS_VREF5 0x0800 // бит Vref = 5000 mV #define SYS_KEYPRESS 0x0010 // бит нажатой кнопки #define SYS_KEYRDY 0x0008 // бит готовности сигнала на входе INT1 #define SYS_VOLTMETER 0x0004 // бит режима вольтметра // инициализируем объект-экран, передаём использованные // для подключения контакты на Arduino в порядке: // RS, E, DB4, DB5, DB6, DB7 // LiquidCrystal lcd(12, 11, 7, 6, 5, 4); char adc[16]; char Ver[] = "03.014a"; void setup() { SYS_status = 0; pinMode(LED_BUILTIN, OUTPUT); // настройка светодиода индикации PrintTitle(); cli(); // запрещаем прерывания перед настройкой SetWatchdogTimer(); SetADC(); SetINT1(); SetTimer2(KEY_delay); sei(); // разрешаем прерывания после настройки WDT_count = 0; ADC_Vmax = 0; ADC_Vmid = 0; ADC_count = 0; SYS_status |= SYS_VOLTMETER; SYS_status &= ~SYS_KEYRDY; SYS_status &= ~SYS_SCREEN1; SYS_status &= ~SYS_KEYPRESS; } void loop() { // тайм-аут на 3 секунды на заставку if((SYS_status & SYS_STARTRDY) == 0) { if(WDT_count > 6) { SYS_status |= SYS_STARTRDY; WDT_count = 0; lcd.clear(); } } // тайм-аут прошел, можно выполнять программу else { // работа в режиме вольтметра if((SYS_status & SYS_VOLTMETER) != 0) { // выводим на экран статическую информацию if((SYS_status & SYS_SCREENRDY) == 0) { lcd.setCursor(0, 0); lcd.print("Vin = V"); lcd.setCursor(0, 1); //выводим Vmax/Vmid в зависимости от SYS_SCREEN1 if((SYS_status & SYS_SCREEN1) == 0) lcd.print("Vmax = V"); else lcd.print("Vmid = V"); SYS_status |= SYS_SCREENRDY; } // экран готов, можно выводить данные else { if((SYS_status & SYS_ADCRDY) != 0) { // проверка на переключение диапазона измерения if(ADC_result > 900 || ADC_result < 200) { if(ADC_result > 900 && (SYS_status & SYS_VREF5) == 0) { SYS_status |= SYS_VREF5; SetADC(); } else if(ADC_result < 200 && (SYS_status & SYS_VREF5) != 0) { SYS_status &= ~SYS_VREF5; SetADC(); } } ADC_Vin = (ADC_Kadc * ADC_result) / 1000; if(ADC_Vin > ADC_Vmax) ADC_Vmax = ADC_Vin; // расчет среднего напряжения if(ADC_count > 3000) { // Если число отсчетов больше 1000, то производится сброс среднего значения ADC_Vmax = 0; ADC_Vmid = 0; ADC_Vsum = 0; ADC_count = 0; } ADC_count++; ADC_Vsum += ADC_Vin; ADC_Vmid = ADC_Vsum / ADC_count; dtostrf(ADC_Vin, 7, 3, adc); lcd.setCursor(7, 0); lcd.print(adc); if((SYS_status & SYS_KEYPRESS) != 0 && WDT_count >= 2) { // выводим счетчик if(WDT_count == 2) { lcd.setCursor(0, 1); lcd.print("Cnt = "); sprintf(adc, "% 7u", ADC_count); } else { sprintf(adc, "% 7u", ADC_count); } } else { //выводим Vmax/Vmid в зависимости от SYS_SCREEN1 if((SYS_status & SYS_SCREEN1) == 0) dtostrf(ADC_Vmax, 7, 3, adc); else dtostrf(ADC_Vmid, 7, 3, adc); } lcd.setCursor(7, 1); lcd.print(adc); SYS_status &= ~SYS_ADCRDY; } } } } } // Подпрограмма печати заголовка программы // ======================================= void PrintTitle() { lcd.begin(16, 2); lcd.print(TITLE); // печатаем первую строку на дисплее lcd.setCursor(0, 1); sprintf(adc, "Version %s", Ver); lcd.print(adc); // печатаем вторую строку на дисплее } // Подпрограмма настройки прерываний сторожевого таймера // ===================================================== void SetWatchdogTimer() { // Настройка сторожевого таймера 0.5 sec // WDTCSR = (1 << WDCE) | (1 << WDE); // необходимо для изменения делителя таймера WDTCSR = (0 << WDP3) | (1 << WDP2) | (0 << WDP1) | (1 << WDP0);// установка делителя 0,5 сек. WDTCSR |= (1 << WDIE); // разрешение прерываний таймера } // Подпрограмма установки режимов АЦП // ================================== void SetADC() { // Настройка делителя входного напряжения АЦП if((SYS_status & SYS_VREF5) == 0) { ADC_Vref = VREF1100; // Установка опорного напряжения 1,1 В и входа А0 ADMUX = (1 << REFS0) | (1 << REFS1); } else { ADC_Vref = VREF5000; // Установка опорного напряжения 5,0 В и входа А0 ADMUX = (1 << REFS0) | (0 << REFS1); } ADC_Kadc = (ADC_Vref * (R2 + R3)) / (1024 * R2); // Установка частоты преобразования // делитель на 128 ADCSRA = (1 << ADEN) | (1 << ADIE) | (1 << ADPS2) | (1 << ADPS1) |(1 << ADPS0); } // Подпрограмма настройки режимов INT1 // =================================== void SetINT1() { KEY_value = digitalRead(KEYPIN); EICRA = (0 << ISC11) | (1 << ISC10); // срабатывание по изменению логического уровня на входе EIMSK |= (1 << INT1); // разрешаем прерывания INT1 } // Подпрограмма настройки таймера 2 // ================================ void SetTimer2(int CountA) { TCCR2A = 0; // установить регистры в 0 TCCR2B = 0; // таймер остановлен TCCR2A |= (0 << WGM20) | (1 << WGM21); OCR2A = CountA; // установка регистра совпадения таймера } // Программа обработки прерывания сторожевого таймера // 1. Обрабатывает светодиод // 2. Обрабатывает запуск АЦП // 3. Обрабатывает нажатое состояние клавиши // ================================================== ISR(WDT_vect) { // мигаем светодиодом раз в секунду // digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); WDT_count++; // увеличиваем счетчик таймера // сброс максимального и среднего при удержании клавиши if(WDT_count > 5 && (SYS_status & SYS_KEYPRESS) != 0) { SYS_status &= ~SYS_KEYPRESS; SYS_status &= ~SYS_SCREENRDY; ADC_Vmax = 0; ADC_Vmid = 0; ADC_Vsum = 0; ADC_count = 0; } // В режиме вольтметра запускаем АЦП if((SYS_status & SYS_VOLTMETER) != 0 & (SYS_status & SYS_ADCRDY) == 0) ADCSRA |= (1 << ADSC); } // Программа обработки прерывания АЦП // ================================== ISR(ADC_vect) { SYS_status |= SYS_ADCRDY; // установка флага готовности данных АЦП ADC_result = ADCL; // сохраняем младший байт результата АЦП ADC_result += ADCH << 8; // сохраняем старший байт АЦП } // Программа обработки прерывания INT1 // =================================== ISR(INT1_vect) { KEY_value = PIND; SYS_status &= ~SYS_KEYRDY; // сбрасываем готовность входного сигнала TCCR2B = 0; // останавливаем таймер TCNT2 = 0; TIMSK2 |= (1 << OCIE2A); // включение прерываний по сравнению (CTC) TCCR2B = 0x07; // запускаем таймер с делителем 1024 } // Программа прерывания таймера 2 по сравнению // ============================================ ISR(TIMER2_COMPA_vect) { KEY_value &= 0x08; if(KEY_value != 0) { SYS_status &= ~SYS_KEYPRESS; if((SYS_status & SYS_SCREEN1) == 0) SYS_status |= SYS_SCREEN1; else SYS_status &= ~SYS_SCREEN1; SYS_status &= ~SYS_SCREENRDY; } else { SYS_status |= SYS_KEYPRESS; WDT_count = 0; } SYS_status |= SYS_KEYRDY; TIMSK2 = 0; // отключение прерываний по сравнению (CTC) TCCR2B = 0; // останавливаем таймер }