Задача: разработаем простой вольт-амперметр со следующими характеристиками:
1. Величина измеряемого напряжения 0...25 V;
2. Величина измеряемого тока 0...2,5 А;
3. Вывод показаний на ЖК дисплей 1602;
4. Использование операционного усилителя.

Для измерения напряжения и тока потребуется 2 канала АЦП, используем каналы ADC0 и ADC1, к которым соответственно будут подходить сигналы измеряемых тока и напряжения. Источник опорного напряжения внутренний на 2,56V, разрядность аналого-цифрового преобразователя 10 бит. Подопытный микроконтроллер Atmega8, тактируется от внутреннего генератора частотой 4MHz. Схема устройства представлена ниже:

Измерение постоянного тока с помощью AVR. Простой вольт-амперметр

Измерение напряжения

С измерением напряжения все понятно, я писал об этом на одном из прошлых занятий. Измеряемое напряжение подается на делитель напряжения, и уже с делителя сигнал подается на вход ADC1. Номиналы сопротивления резисторов делителя 100 кОм и 10кОм, значит соотношение входного и выходного сигналов 10:1. 

Рассчитаем максимальное входное напряжение делителя, чтобы случайно не подать на вход большее напряжение и не повредить микроконтроллер, применим такую формулу:

Umax = Uin*(R1+R2)/R2

где: R1 = 100k, R2 = 10k, Uin = 5V(макс. напряжение порта контроллера),

Umax = 5*110k/10k = 55V

Из этого мы знаем, что больше 55V на вход делителя напряжения подавать нельзя.

Результат преобразования в Вольтах вычисляется по формуле:

U = ADC*Uref*K/1024

где:
ADC - результат преобразования;
Uref опорное напряжение(V);
K - коэффициент делителя напряжения;
1024 - Разрядность АЦП 10 бит.

Коэффициент делителя напряжения напряжения вычисляется по формуле:

K = (R1+R2)/R2

он равен: K = (100k + 10k)/10k = 11

Измерение тока

Измерение тока будем производить с помощью токового шунта, который включается в разрыв нагрузки. Падение напряжения на нем вычисляется при помощи закона Ома, эту величину будем измерять другим каналом АЦП(ADC0). Чем меньше сопротивление шунта тем лучше, т.к. меньше энергии рассеивается на нем. Возьмем шунт сопротивлением 0,1 Ом, используем обычный мощный резистор. Расчитаем падение напряжения на нем при силе тока 1 А по формуле:

U = I * R

U = 1А * 0,1 Ом = 0,1 V

Для тока 2А падение напряжение на шунте будет 0,2V. Величина достаточно малая чтобы напрямую подавать ее на вход АЦП, но есть способ усилить ее с помощью операционного усилителя. Для нашего примера подойдет схема неинвертирующего усилителя, которая имеет бесконечно большое входное, и бесконечно малое выходное сопротивление, что является её несомненным достоинством. Коэффициент усиления ОУ расчитывается по формуле:

Kу = 1 + (R2 / R1)

Этот коэффициент сделаем равным примерно 10, так чтобы измеряемый ток величиной 2 А соответствовал напряжению на выходе усилителя в 2 В. Так как ИОН на 2,56 V, больше этого значения на вход АЦП мы подать не можем, расчитаем разрядность измерителя тока:

2,56А / 1024 = 2,5 mA, что вполне достаточно.

Результат преобразования в Амперах вычисляется по формуле:

I = ADC*Uref*K/1024

где:
ADC - результат преобразования;
Uref опорное напряжение(V);
K - коэффициент усиления операционного усилителя;
1024 - Разрядность АЦП 10 бит.

Программа

Измерение напряжения и тока будем производить по прерыванию окончания преобразования АЦП. Если был выбран канал ADC1(напряжение) то снимаем показания c АЦП, суммируем с прошлыми показаниями и помещаем в буфер, затем выбираем канал ADC0(ток) и проделываем те же самые действия для измерения тока. Этот цикл повторяется 250 раз, затем вычисляем средние значения измеренных величин напряжения и тока, умножаем на 100, чтобы представить результат в милливольтах(миллиамперах) и выводим на экран. Исходный код нашей программы с подробными комментариями представлен ниже:

// Измерение постоянного тока с помощью AVR
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

unsigned char voltage_counter, current_counter;
unsigned int voltage, current;
volatile unsigned long voltage_buffer, current_buffer;

// Обработчик прерывания от АЦП
ISR(ADC_vect)
{
if(ADMUX & 0x0F) // Если был выбран канал ADC1
{
voltage_buffer += ADC; // Накапливаем в буфер значения напряжения
ADMUX = (ADMUX & 0xF0) | 0; // Выбираем канал ADC0
voltage_counter++; // Увеличиваем счетчик измерений
}
else
{
current_buffer += ADC; // Накапливаем в буфер значения тока
ADMUX = (ADMUX & 0xF0) | 1; // Выбираем канал ADC1
current_counter++; // Увеличиваем счетчик измерений
}
}

// Функции работы с LCD
#define RS PD0
#define EN PD2

// Функция передачи команды
void lcd_com(unsigned char p)
{
PORTD &= ~(1 << RS); // RS = 0 (запись команд)
PORTD |= (1 << EN); // EN = 1 (начало записи команды в LCD)
PORTD &= 0x0F; PORTD |= (p & 0xF0); // Старший нибл
_delay_us(100);
PORTD &= ~(1 << EN); // EN = 0 (конец записи команды в LCD)
_delay_us(100);
PORTD |= (1 << EN); // EN = 1 (начало записи команды в LCD)
PORTD &= 0x0F; PORTD |= (p << 4); // Младший нибл
_delay_us(100);
PORTD &= ~(1 << EN); // EN = 0 (конец записи команды в LCD)
_delay_us(100);
}

// Функция передачи данных
void lcd_data(unsigned char p)
{
PORTD |= (1 << RS)|(1 << EN); // RS = 1 (запись данных), EN - 1 (начало записи команды в LCD)
PORTD &= 0x0F; PORTD |= (p & 0xF0); // Старший нибл
_delay_us(100);
PORTD &= ~(1 << EN); // EN = 0 (конец записи команды в LCD)
_delay_us(100);
PORTD |= (1 << EN); // EN = 1 (начало записи команды в LCD)
PORTD &= 0x0F; PORTD |= (p << 4); // Младший нибл
_delay_us(100);
PORTD &= ~(1 << EN); // EN = 0 (конец записи команды в LCD)
_delay_us(100);
}

// Функция вывода строки на LCD
void lcd_string(unsigned char command, char *string)
{
lcd_com(0x0C); // Включение дисплея, курсор не видим
lcd_com(command); // Адрес знакоместа
// Выводим символы пока не будет конца строки
while(*string != '\0') 
{ 
lcd_data(*string); // Выводим символ
string++; // Следующий символ строки
}
}

// Функция инициализации LCD
void lcd_init(void)
{
DDRD = 0xFF;
PORTD = 0x00;
_delay_ms(50); // Ожидание готовности ЖК-модуля
// Конфигурирование четырехразрядного режима
PORTD |= (1 << PD5);
PORTD &= ~(1 << PD4);
// Активизация четырехразрядного режима
PORTD |= (1 << EN);
PORTD &= ~(1 << EN);
_delay_ms(5);
lcd_com(0x28); // Шина 4 бит, LCD - 2 строки
lcd_com(0x08); // Полное выключение дисплея
lcd_com(0x01); // Очистка дисплея
_delay_us(100);
lcd_com(0x06); // Сдвиг курсора вправо
lcd_com(0x0C); // Включение дисплея, курсор не видим
}

int main(void)
{
// Настройка АЦП
ADMUX |= (1 << REFS1)|(1 << REFS0); // Внутренний ИОН 2,56V
ADCSRA |=     (1 << ADEN)  // Разрешение АЦП
             |(1 << ADSC)  // Запуск преобразования
             |(1 << ADFR)  // Непрерывный режим работы АЦП
|(1 << ADPS2)|(1 << ADPS1) // Предделитель на 64 (частота АЦП 125kHz)
             |(1 << ADIE); // Разрешение прерывания от АЦП

sei(); // Глобально разрешаем прерывания

lcd_init(); // Инициализация LCD

_delay_ms(25);
lcd_string(0x80 ,"VOLTS  * AMPERES");
lcd_string(0xC0 ,"  .    *  .     ");

while(1)
{
// Вычисляем среднее значение напряжения
if(voltage_counter == 250)
{
voltage = ((voltage_buffer*256*11)/1024)/voltage_counter;
voltage_counter = 0; // Обнуляем счетчик измерений
voltage_buffer = 0; // Обнуляем буфер значений напряжения
lcd_com(0xC0);
lcd_data((voltage/1000%10)+'0');
lcd_data((voltage/100%10)+'0');
lcd_com(0xC3);
lcd_data((voltage/10%10)+'0');
}

// Вычисляем среднее значение тока
if(current_counter == 250)
{
current = ((current_buffer*256*10)/1024)/current_counter;
current_counter = 0; // Обнуляем счетчик измерений
current_buffer = 0; // Обнуляем буфер значений тока
lcd_com(0xC9);
lcd_data((current/1000%10)+'0');
lcd_com(0xCB);
lcd_data((current/100%10)+'0');
lcd_data((current/10%10)+'0');
}

}
}


Архив для статьи "Измерение постоянного тока с помощью AVR. Простой вольт-амперметр"
Описание: Проект AVRStudio4 и Proteus
Размер файла: 51.1 KB Количество загрузок: 3 338 Скачать

Комментарии  

#1 Михаил 12.10.2013 13:51
Текст программы не совсем рабочий!
ошибка в использовании одного счетчика adc_counter для замера 2-х каналов ацп,
долго пытался реализовать в протеусе этот код но не мог понять где подвох)))
в итоге ввел два счетчика для тока и напряжения и в цикле while(1)раздели л проверку счетчиков для каждого канала adc
#2 Михаил 12.10.2013 14:17
Код:006.unsigned int voltage, current, adc_counter;
я поменял на Код:006.unsigned int voltage, current, adc_counterV, adc_counterA;
#3 Александр_Go 23.12.2013 00:03
Скажите, а в какой программе вы рисуете принципиальные схемы?
#4 AntonChip 23.12.2013 06:13
Цитирую Александр_Go:
Скажите, а в какой программе вы рисуете принципиальные схемы?

SPlan7 depositfiles.com/files/dqwy22c7d
#5 Виктор 17.06.2014 13:20
Здравствуйте. А как пересчитать делители чтобы измерять 0-50V ; 0-100A
#6 AntonChip 18.06.2014 18:40
Цитирую Виктор:
Здравствуйте. А как пересчитать делители чтобы измерять 0-50V ; 0-100A

Для измерения напряжения до 50 вольт достаточно выбрать входной делитель 200k/10k или использовать источник опорного напряжения 5 Вольт но оставить прежний делитель. С измерением тока сложнее. Если делать точный амперметр можно сделать несколько измеряемых диапазонов с автоматическим выбором, например 0 - 1A, 1 - 10A, 10-100A. Шунт использовать с довольно малым сопротивлением 0,01 Ом. И программно изменять коэффициенты ОУ.
#7 Виктор 19.06.2014 16:44
Немного не правильно выразился. Мне нужно на дисплее это показать. С шунтами и делителями я уж как нибудь сам разобрался бы. А вот в программе не понимаю как сделать измерение до 50v и 100A
#8 Игорь 14.07.2014 21:42
Спасибо. Помогли понять, как реализовать амперметр.
#9 Александр Ш 08.09.2014 07:29
А как программно изменять коэффициенты ОУ?
#10 AntonChip 08.09.2014 09:21
Цитирую Александр Ш:
А как программно изменять коэффициенты ОУ?

К выходу микроконтроллер а подключать реле или транзистор который в свою очередь подключает или отключает соответствующий делитель ОУ при изменении предела измерения
#11 Александр Ш 08.09.2014 12:51
А вместится такая программа в TINY 13?
#12 stack91 15.11.2014 12:41
Хочу собрать такой ампервольтметр. Достаточно будет расширить диапазон измерение напряжение до 50 вольт, лишь изменить делитель напряжение или также нужно менять программу? Я только знакомлюсь с микроконтроллер ами, мне пока сложно понять.
Кто бы помог бы сделать ампервольтметр с автоматическим выбором диапазонном.
#13 AntonChip 16.11.2014 00:03
Цитирую stack91:
Хочу собрать такой ампервольтметр. Достаточно будет расширить диапазон измерение напряжение до 50 вольт, лишь изменить делитель напряжение или также нужно менять программу?

Нужно будет менять программу
#14 Roman-1-1 24.02.2015 22:04
Печатну плату можете скинути??? Буду дуже вдячний!!!
І в якій програмі писався цей код?
#15 AntonChip 24.02.2015 23:02
Цитирую Roman-1-1:
Печатну плату можете скинути??? Буду дуже вдячний!!!
І в якій програмі писався цей код?

Печатную плату не делал, код написан в AVRStudio4
#16 Roman-1-1 25.02.2015 18:13
Дякую велике AntonChip. А як можна зробити печатну плату?
#17 Roman-1-1 25.02.2015 18:42
І якщо можна, список всіх деталей потрібних щоб скласти цей прилад. Наперед велике дякую.
#18 Roman-1-1 25.02.2015 20:16
на схемі R5 0.1 чого Ом чи кОМ?
#19 AntonChip 25.02.2015 21:54
Цитирую Roman-1-1:
на схемі R5 0.1 чого Ом чи кОМ?

0,1 Ом
#20 Roman-1-1 02.03.2015 17:24
Чи можна замовити печатну плату для нього?
#21 AntonChip 02.03.2015 19:30
Цитирую Roman-1-1:
Чи можна замовити печатну плату для нього?
Изготовлением плат не занимаемся
#22 Roman-1-1 15.03.2015 20:49
який код використовуєтьс я для виводу на екран любого напису? Код програми, що вище показаний він повний?
#23 Roman-1-1 30.03.2015 18:30
Якщо можна і є така можливість, можете скинути даний проект у протеусі!?
#24 Roman-1-1 02.04.2015 23:23
Цитирую Михаил:
Код:006.unsigned int voltage, current, adc_counter;
я поменял на Код:006.unsigned int voltage, current, adc_counterV, adc_counterA;


Михаил, можете скинути вашу програму із тим що ви поміняли, дуже тре!!! наперед дякую
+1 #25 Roman-1-1 04.04.2015 14:35
Я зібрав цю схему точно як на малюнку в Протеусі, закину в вашу прошовку — непрацює. Подивився ваш файл протеуса і нічого не поняв. В чому проблема, чому якщо зібрати, як показано на схемі воно не працює????!!!!!
#26 miha 19.05.2015 13:24
Уважаемый ANTONCHIP,подск ажите пожалуйста, как изменить схему так, чтобы получить МИКРОамперметр?
#27 miha 19.05.2015 13:28
дело в том, что необходмо измерять миллиамперы через потенциометр 1-50Ком на ноге микрухи PT2399. Напряжение на ней всегда 2.5В.
#28 AntonChip 19.05.2015 14:39
Цитирую miha:
Уважаемый ANTONCHIP,подскажите пожалуйста, как изменить схему так, чтобы получить МИКРОамперметр?

Преобразователь напряжения в ток на ОУ
#29 sergang 12.02.2016 12:32
Здравствуйте ANTONCHIP замечательный сайт , благодарю за работу.
Вопрос в следующем : при повторении проекта выяснилось , что есть большие наводки на Atmege при измерении .Напряжение скачет .При питании AVCC через дополнительный LC фильтр стабильность улучшилась но не полностью .
Читал ,что можно проц погружать в сон на время работы АЦП после измерений пробуждать ,и как бы наводки будут минимальные . Как это програмно реализовать ?
#30 Искандар 24.06.2016 16:18
Я бы не советовал использовать внутреннее напряжение МК в качестве опорного так как при этом большие наводки и погрешности. Лучше используйте AVCC или AREF.
#31 max 28.06.2016 17:36
Здравствуй AntonChip!
Очень хочется увеличить предел измерения тока.
Как пересчитать на 10А.
C уважением max
-1 #32 max 28.06.2016 20:15
Для всех!
Уменьшил шунт до 0.01ом. Значение U на шунте стало 0.01V
А в строке 121 программы значение вычисления умножил на 10
current = ((current_value /adc_counter) * 10/4)*10;
Правильно ли я сделал?
#33 bulba_man 20.10.2017 13:11
статья хрошая, но откуда взяты коэффициенты??
Цитата:

Коэффициент пересчета в реальное значение напряжения будет равен 2,75(11/4).
Цитата:

На коэффициент 2,5(10/4) необходимо умножить измеренное значение АЦП, чтобы получить реальные показания тока на экране LCD.
где формулы, пояснения
#34 Андрей_K 29.10.2017 05:58
Доброе всем радиолюбам и самоделкиным. Сижу собираю что-то подобное, читаю мануалы. Есть некоторые неточности, поправьте если что-то не так. Цепочка R3-R4 делитель. Uвых=Uвх*R4/(R3 +R4). При данных значениях будет больше 9:1 чем 10:1. И про коэффициент усиления Ку. Формула же есть Kу = 1 + (R2 / R1). Сопротивление R1 состоит из R1 и R6. Для выполнения условия Ку=2 при R2=100кОм, должно выходить что R1+R6=5.26кОм. Т.е. к примеру R1=4.7k, R6=1k. Или я не прав? Я пока что не дошел до этой части. Я только с ИОН победил схему. Внутренний ИОН ни разу не выдаёт заявленные 2.56 в. На счет стабильности не скажу, потому что сразу от него отказался. Поставил LM385 Z-1.2. Отличная штуковина.
#35 Сергей 3 18.09.2019 12:48
Доброе время!
Помогите разобраться с кодом!
как получается переменная ADC. как вы ее читаете из регистров ADCL и ADCH.
спасибо
#36 AntonChip 18.09.2019 21:57
Цитирую Сергей 3:
Доброе время!
Помогите разобраться с кодом!
как получается переменная ADC. как вы ее читаете из регистров ADCL и ADCH.
спасибо

Здравствуйте. ADC определена в компиляторе как шестнадцатиразр ядная переменная, в ней уже хранится 10-ти битный результат преобразования.
#37 uuu000 31.01.2020 10:23
Если можно объясните подробнее эти
строки 15.PORTD &= 0x0F; PORTD |= (p & 0xF0);
20.PORTD &= 0x0F; PORTD |= (p
#38 uuu000 31.01.2020 10:25
Я понимаю что запись разбита на старший и младший нибл,но не понимаю синтаксис
команд,того что написано в скобках.
#39 AntonChip 31.01.2020 19:36
Цитирую uuu000:
Я понимаю что запись разбита на старший и младший нибл,но не понимаю синтаксис
команд,того что написано в скобках.

Так должно быть понятнее, изучайте логические операции
Код:
PORTD = PORTD & 0x0F;
PORTD = PORTD | (p & 0xF0);
#40 uuu000 01.02.2020 11:11
Спасибо за подсказку,разобрался.
Все оказалось очень просто.
#41 uuu000 01.02.2020 18:26
Прокомментируйт е пожалуйста строки 40-49
Цикл выполняется до окончания строки('\0)
какая роль переменной command?
Спасибо.
#42 AntonChip 05.02.2020 14:17
Цитирую uuu000:
Прокомментируйте пожалуйста строки 40-49
Цикл выполняется до окончания строки('\0)
какая роль переменной command?
Спасибо.

Код:
// Функция вывода строки на LCD
void lcd_string(unsigned char command, char *string)
{
lcd_com(0x0C); // Включение дисплея, курсор не видим
lcd_com(command); // Адрес знакоместа
// Выводим символы пока не будет конца строки
while(*string != '{652915e640fa7cf26a0683fe58d8dffd}')
{
lcd_data(*string); // Выводим символ
string++; // Следующий символ строки
}
}
#43 Kard 16.12.2020 21:41
При использовании программы CodeBlocks и компилировании кода в elf, из вашего архива файл main.c схема не работает, в чём проблема?
#44 AntonChip 18.12.2020 21:36
Цитирую Kard:
При использовании программы CodeBlocks и компилировании кода в elf, из вашего архива файл main.c схема не работает, в чём проблема?

а какой компилятор? если AVR Toolchain то должно все работать
#45 sergi 26.03.2021 07:42
Как убрать нули при выводе показаний токи и напряжения ?
#46 serg mur 26.02.2023 19:23
Добрый вечер! Перед тем как собрать схему вживую протестировал в протеусе. все работает,спасиб о. Но есть нюанс, завышено показание тока на дисплее. Ток который показывает дисплей выше входного на 0,4-0,5 А. Где подправить? Прошу подсказать.
#47 AntonChip 01.03.2023 09:05
Приветствую, попробуйте откалибровать ОУ, так как напряжение на его выходе соответствует показанию на дисплее, проверял в Протеусе

У Вас недостаточно прав для добавления комментариев. Возможно, Вам необходимо зарегистрироваться на сайте.