Radio Life - Связь Arduino с внешним миром

Программа вольтметра постоянного тока с работой с последовательным портом.
Версия 05.023с


// // ================================================================ // подключаем стандартную библиотеку 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; // входное float ADC_K11 = 1; // поправочный коэфф. для 1,1 В float ADC_K5 = 1; // поправочный коэфф. для 5 В float ADC_K; // действующий коэфф. 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 // Ячейки, используемые последовательным портом // char ser[3]; int SER_status; int SER_inKey; // код символа, принятого с порта int SER_data; #define SER_START 0 // режим начального состояния #define SER_WAITCOMMAND 1 // ожидание ввода команды #define SER_GETCOMMAND 2 // принята команда #define SER_PRINTDATA 3 // вывод данных #define SER_CONFIG 4 // конфигурация системы #define SER_CONFIGTIMEOUT 5 // установка таймаута дребезга // Ячейки, используемые 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_SERPRINT 0x0400 // #define SYS_SERTITLE 0x0200 // #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[64]; char Ver[] = "05.023c"; void setup() { SYS_status = 0; pinMode(LED_BUILTIN, OUTPUT); // настройка светодиода индикации cli(); // запрещаем прерывания перед настройкой SetWatchdogTimer(); SetADC(); SetINT1(); SetTimer2(KEY_delay); SetSerial(); sei(); // разрешаем прерывания после настройки PrintTitle(); 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() { SerialWork(); 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) * ADC_K; if(ADC_Vin > ADC_Vmax) ADC_Vmax = ADC_Vin; if(ADC_count > 3000) { 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 SerialWork() { switch (SER_status) { // ожидание ввода команды case (SER_WAITCOMMAND): { if(Serial.available() > 0) { if(Serial.readBytes(ser, 3) == 1) { if(ser[0] >= '0' && ser[0] <= '9') { Serial.write(ser[0]); SER_status = SER_GETCOMMAND; } else SerPrintHelp(); } } break; } // обработка команды case (SER_GETCOMMAND): { // команда вывода настроек switch (ser[0]) { case ('1'): { SerPrintSetup(); break; } case ('2'): { SER_status = SER_PRINTDATA; break; } case ('3'): { SER_status = SER_CONFIG; Serial.println(); Serial.println("1 - Key timeout"); Serial.println("2 - Correct Vref = 1.1 V"); Serial.println("3 - Correct Vref = 5 V"); Serial.println("ESC - Return to Command Mode"); Serial.print("CFG>"); break; } default: { Serial.print("\r\n> "); SER_status = SER_WAITCOMMAND; break; } } break; } case (SER_PRINTDATA): { if((SYS_status & SYS_SERPRINT) != 0) { SerPrintData(); SYS_status &= ~SYS_SERPRINT; } if(Serial.available() > 0) { if(Serial.readBytes(ser, 3) == 1) { if (ser[0] == 0x1b) { Serial.print("\r\n> "); SYS_status &= ~SYS_SERTITLE; SER_status = SER_WAITCOMMAND; } } } break; } case (SER_CONFIG): { if (SerReadByte() != 0) { switch (ser[0]) { case (0x1b): { Serial.print("\r\n> "); SER_status = SER_WAITCOMMAND; break; } case ('1'): { SER_data = 0; Serial.write(ser[0]); Serial.println("\n\rESC - Return to Old Value"); sprintf(adc, "Kkey [%u] (1-255) = ", KEY_delay); Serial.print(adc); SER_status = SER_CONFIGTIMEOUT; break; } default: { break; } } } break; } case (SER_CONFIGTIMEOUT): { if (SerReadByte() != 0) { switch (ser[0]) { case (0x1b): { Serial.print("\r\nCFG> "); SER_status = SER_CONFIG; break; } case (0xd): { if((SER_data > 255) || (SER_data == 0)) { Serial.print("\r\nError!"); } else { KEY_delay = SER_data; cli(); SetTimer2(KEY_delay); sei(); } Serial.print("\r\nCFG> "); SER_status = SER_CONFIG; break; } default: { if(ser[0] >= '0' && ser[0] <= '9') { Serial.write(ser[0]); SER_data = SER_data * 10 + (ser[0] - 0x30); } break; } } } break; } default: { break; } } } // Подпрограмма приема символа // с последовательного порта // =========================== int SerReadByte() { if(Serial.available() > 0) { if(Serial.readBytes(ser, 3) == 1) { return ser[0]; } } return 0; } // Подпрограмма печати справки // =========================== void SerPrintHelp() { Serial.println("\n\rHelp"); Serial.println("----------------"); Serial.println("1 - View Config"); Serial.println("2 - Show Data"); Serial.println("3 - Config System"); Serial.print("\n\r> "); } // Подпрограмма печати заголовка программы // ======================================= void PrintTitle() { lcd.begin(16, 2); lcd.print(TITLE); // печатаем первую строку на дисплее lcd.setCursor(0, 1); sprintf(adc, "Version %s", Ver); lcd.print(adc); // печатаем вторую строку на дисплее // печатаем в последовательный порт Serial.println(); // новая строка Serial.println(TITLE); // печатаем первую строку на терминал Serial.println(adc); // печатаем вторую строку на терминал Serial.print("> "); SER_status = SER_WAITCOMMAND; } // Подпрограмма печати заголовка программы // ======================================= void SerPrintSetup() { // печатаем в последовательный порт Serial.println("\n\rConfig"); Serial.println("---------------------"); // вывод опорного напряжения Serial.print("Vref = "); dtostrf(ADC_Vref, 8, 1, adc); Serial.print(adc); Serial.println(" mV"); // вывод поправочного коэффициента для 1,1 В Serial.print("K1.1 = "); dtostrf(ADC_K11, 5, 5, adc); Serial.println(adc); // вывод поправочного коэффициента для 5 В Serial.print("K5.0 = "); dtostrf(ADC_K5, 5, 5, adc); Serial.println(adc); // вывод задержки защиты от дребезга sprintf(adc, "Kkey = % 8u mksec", 64 * KEY_delay); Serial.println(adc); // вывод приглашения к вводу в последовательный порт Serial.print("> "); SER_status = SER_WAITCOMMAND; } // Подпрограмма вывода данных прибора // ================================== void SerPrintData() { int i; if((SYS_status & SYS_SERTITLE) == 0) { Serial.println("\r\nTo stop press ESC"); Serial.println(" Vin Vmax Vmid Count"); SYS_status |= SYS_SERTITLE; for(i=0; i<40; i++) adc[i] = ' '; } Serial.print('\r'); dtostrf(ADC_Vin, 8, 3, adc); dtostrf(ADC_Vmax, 8, 3, &adc[9]); if(ADC_count > 2) { dtostrf(ADC_Vmid, 8, 3, &adc[17]); } sprintf(&adc[26], "% 5u", ADC_count); for(i=0; i<28; i++) if(adc[i] == 0x0) adc[i] = ' '; Serial.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; ADMUX = (1 << REFS0) | (1 << REFS1); ADC_K = ADC_K11; } else { ADC_Vref = VREF5000; ADMUX = (1 << REFS0) | (0 << REFS1); ADC_K = ADC_K5; } 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; // установка регистра совпадения таймера } // Подпрограмма настройки последовательного порта // ============================================== void SetSerial() { Serial.begin(38400); // скорость основного порта Serial.setTimeout(2); } // Программа обработки прерывания сторожевого таймера // 1. Обрабатывает светодиод // 2. Обрабатывает запуск АЦП // 3. Обрабатывает нажатое состояние клавиши // ================================================== // ISR(WDT_vect) { // мигаем светодиодом раз в секунду // digitalWrite(LED_BUILTIN, !digitalRead(LED_BUILTIN)); SYS_status |= SYS_SERPRINT; WDT_count++; // увеличиваем счетчик таймера // удержание кнопки более 2 сек. if(WDT_count > 8 && (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; // запускаем таймер } // // Программа прерывания таймера 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; // останавливаем таймер }