// ================================================================
// подключаем стандартную библиотеку 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; // двоичный код в регистрах АЦП
// Ячейки, используемые 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_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 s[16];
char adc[16];
char Ver[] = "02.011e";
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) {
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(s, "Version %s", Ver);
lcd.print(s); // печатаем вторую строку на дисплее
}
// Подпрограмма настройки прерываний сторожевого таймера
// =====================================================
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() {
// Настройка делителя входного напряжения АЦП
ADC_Vref = 1100;
ADC_Kadc = (ADC_Vref * (R2 + R3)) / (1024 * R2);
// Установка опорного напряжения 1.1 В. и входа А0
// Устанавливаем только те биты, которые должны быть установлены в "1"
ADMUX = (1 << REFS0) | (1 << REFS1);
// Установка частоты преобразования
// делитель на 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; // останавливаем таймер
}