Задача: разработаем простой вольт-амперметр со следующими характеристиками:
1. Величина измеряемого напряжения 0...25 V;
2. Величина измеряемого тока 0...2,5 А;
3. Вывод показаний на ЖК дисплей 1602;
4. Использование операционного усилителя.
Для измерения напряжения и тока потребуется 2 канала АЦП, используем каналы ADC0 и ADC1, к которым соответственно будут подходить сигналы измеряемых тока и напряжения. Источник опорного напряжения внутренний на 2,56V, разрядность аналого-цифрового преобразователя 10 бит. Подопытный микроконтроллер Atmega8, тактируется от внутреннего генератора частотой 4MHz. Схема устройства представлена ниже:
Измерение напряжения
С измерением напряжения все понятно, я писал об этом на одном из прошлых занятий. Измеряемое напряжение подается на делитель напряжения, и уже с делителя сигнал подается на вход 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_counterV, adc_counterA;
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);
}
}
|