//
// ================================================================
// подключаем стандартную библиотеку 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; // код символа, принятого с порта
#define SER_START 0 // режим начального состояния
#define SER_WAITCOMMAND 1 // ожидание ввода команды
#define SER_GETCOMMAND 2 // принята команда
#define SER_PRINTDATA 3 // вывод данных
// Ячейки, используемые 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[] = "04.021c";
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; }
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;
}
default:
{
break;
}
}
}
// Подпрограмма печати справки
// ===========================
void SerPrintHelp()
{
Serial.println("\n\rHelp");
Serial.println("----------------");
Serial.println("1 - Config view");
Serial.println("2 - Show data");
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; // останавливаем таймер
}