Печать

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

Автор: AntonChip Опубликовано . Опубликовано в Программирование на Си

Рейтинг:   / 41
ПлохоОтлично 

Задача: разработаем простой вольт-амперметр со следующими характеристиками:

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. Максимальное напряжение подаваемое на вход делителя равно 28,13 V. Коэффициент пересчета в реальное значение напряжения будет равен 2,75(11/4).

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

Измерение тока будем производить с помощью токового шунта, который включается в разрыв нагрузки. Падение напряжения на нем вычисляется при помощи закона Ома, эту величину будем измерять другим каналом АЦП(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, что вполне достаточно.

На коэффициент 2,5(10/4) необходимо умножить измеренное значение АЦП, чтобы получить реальные показания тока на экране LCD.

Программа

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

// Измерение постоянного тока с помощью AVR
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h> 
unsigned int voltage, current, adc_counter;
volatile unsigned long voltage_value, current_value;
// Функции работы с 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++;
}
}
// Функция вывода переменной
void lcd_num_to_str(unsigned int value, unsigned char nDigit)
{
switch(nDigit)
{
case 4: lcd_data((value/1000)+'0');
case 3: lcd_data(((value/100)%10)+'0');
case 2: lcd_data(((value/10)%10)+'0');
case 1: lcd_data((value%10)+'0');
}
}
// Функция инициализации 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); // включение дисплея, курсор не видим
}
// Обработчик прерывания от АЦП
ISR(ADC_vect)
{
ADCSRA = 0; // Выключаем АЦП 
if((ADMUX & 0x0F)==1) // Если был выбран канал ADC1
{
voltage_value = voltage_value + ADC; // Суммируем измеренные значения напряжения и помещаем в буфер
ADMUX = (ADMUX & 0xF0) | 0; // Выбираем канал ADC0
}
else
{
current_value = current_value + ADC; // Суммируем измеренные значения тока и помещаем в буфер
ADMUX = (ADMUX & 0xF0) | 1; // Выбираем канал ADC1
adc_counter++; // Увеличиваем счетчик выборок АЦП на 1
}
// Включаем АЦП
ADCSRA |= (1 << ADEN)|(1 << ADSC)|(1 << ADPS2)|(1 << ADPS1)|(1 << ADPS0)|(1 << ADIE);
}
int main(void) 
{
ADMUX |= (1 << REFS1)|(1 << REFS0); // Внутренний ИОН 2,56V
ADMUX |= (1 << MUX0); // Подключаем канал ADC1
ADCSRA |= (1 << ADEN) // разрешение АЦП
	|(1 << ADSC) // запуск преобразования
                |(1 << ADPS2)|(1 << ADPS1)|(1 << ADPS0) // предделитель на 128
	|(1 << ADIE); // разрешение прерывания от АЦП
sei(); // Глобально разрешаем прерывания
lcd_init(); // Инициализация LCD
_delay_ms(25);
lcd_string(0x80	,"VOLTS  * AMPERES");
lcd_string(0xC0	,"  .    *  .     ");
while(1) 
{
// вычисляем среднее значение АЦП
if (adc_counter > 400)
{
ADCSRA = 0; // Выключаем АЦП 
// преабразуем данные в реальное значение напряжения
voltage = (voltage_value/adc_counter) * 11/4;
// преабразуем данные в реальное значение тока
current = (current_value/adc_counter) * 10/4;
adc_counter = 0; // Обнуляем счетчик выборок АЦП
voltage_value = 0; // Обнуляем буфер значений напряжения
current_value = 0; // Обнуляем буфер значений тока
// Выводим данные на LCD
lcd_com(0xC0);
lcd_num_to_str(voltage/100, 2);
lcd_com(0xC3);
lcd_num_to_str(voltage, 2);
lcd_com(0xC9);
lcd_num_to_str(current/1000, 1);
lcd_com(0xCB);
lcd_num_to_str(current, 3);
// Включаем АЦП
ADCSRA |= (1 << ADEN)|(1 << ADSC)|(1 << ADPS2)|(1 << ADPS1)|(1 << ADPS0)|(1 << ADIE);
}   
_delay_ms(1);
}
}

Файлы:
Проект AVRStudio4 и Proteus
Дата 30.03.2015 Размер файла 51.1 KB Закачек 1380

Комментарии  

0 #21 AntonChip 02.03.2015 19:30
Цитирую Roman-1-1:
Чи можна замовити печатну плату для нього?
Изготовлением плат не занимаемся
Сообщить модератору
0 #22 Roman-1-1 15.03.2015 20:49
який код використовуєтьс я для виводу на екран любого напису? Код програми, що вище показаний він повний?
Сообщить модератору
+1 #23 Roman-1-1 30.03.2015 18:30
Якщо можна і є така можливість, можете скинути даний проект у протеусі!?
Сообщить модератору
0 #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;


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

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

Рекомендуем посмотреть