Печать

АЦП микроконтроллеров AVR. Делаем цифровой вольтметр 0 - 25V

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

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

Продолжим изучать аналого-цифровой преобразователь микроконтроллеров AVR на примере цифрового вольтметра постоянного напряжения, с пределами измерения от 0 до 25V. Измеряемое напряжение будет отображаться на трехразрядном семисегментном индикаторе с общим анодом. В этом примере применим динамическую индикацию о которой подробней рассказано на одном из предыдущих занятий, кусок исходного кода возьмем от туда же. Микроконтроллер Atmega8 тактируется от внутреннего генератора частотой 8MHz.

Далее займемся настройкой АЦП. В этот раз попробуем использовать внутренний источник опорного напряжения 2,56V, т.к. выход Aref микроконтроллера соединен с ИОН, для обеспечения стабильности ИОН подключаем к выводу Aref конденсатор. Резистор R3 - подстроечный, он служит для регулировки номинального уровня напряжения, желательно многооборотный.

Изучение АЦП. Цифровой вольтметр - схема

Входом АЦП является линия PC0(ADCO), т.к. вольтметр у нас должен измерять напряжение до 25V, а 25V для порта контроллера это очень много, в таких случаях используют делитель напряжения. Например, если напряжение на входе будет меняться от 0 до 25V, то на выходе оно будет меняться от 0 до 5V.

Рассчитаем максимальное напряжение Uemax подаваемое на вход АЦП по формуле:

Uemax = 1023*Uref/1024

Uemax = 1023*2.56/1024 = 2,5575V

Рассчитаем максимальное входное напряжение делителя, исходя из параметров: R1=100k, R2=10k, Uemax=2,5575, применим такую формулу:

Uemax = Uin*R2/R1+R2

,где Uin максимальное напряжение подаваемое на вход делителя.

Uin = 2,5575*110k/10k = 28,1325V

Из этого мы знаем, что больше 28,1325V на вход вольтметра подавать нельзя. Также надо знать какой результат будет сохраняться в регистре ADC при изменении напряжения на входе АЦП. Результат преобразования вычисляется по формуле:

ADC = 1024*Uemax/Uref

Например при максимальном напряжении на входе 2,5575V результат преобразования будет таким:

ADC = 1024*2,5575/2,56 = 1023

При напряжении на входе 2V результат будет таким:

ADC = 1024*2/2.56 = 800

Чтобы на индикаторе получить реальные цифры в Вольтах результат преобразования надо умножить на коэффициент равный отношению максимального напряжения(4 разряда) подаваемого на вход делителя к максимальному результату преобразования АЦП.

k = 2813/1023 = 2,75

В программе обработчика прерываний от АЦП результат преобразования перемножаем на этот коэффициент и получаем величину напряжения подаваемого на вход делителя, т.к для операции умножения на дробное число микроконтроллеру потребуется много памяти, существует способ представить число 2,75 по другому, например: (ADC*11)/4. Настраиваем регистры АЦП и Таймера2, глобально разрешаем прерывания, так же в коде вычисляем средний показатель результата преобразования и выводим данные на индикатор. Полный текст программы ниже.

// Использование АЦП. Цифровой вольтметр. Общий анод
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
//------------------0-----1-----2-----3-----4-----5-----6-----7-----8------9----dp                      
char SEGMENTE[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x80};
volatile unsigned char segcounter = 0;
volatile unsigned int display = 0;
// Прерывание по переполнению T2, динамическая индикация
ISR (TIMER2_OVF_vect)
{	
PORTD = 0xFF; // Гасим все сегменты
PORTB = (1 << segcounter); // Последовательно зажигаем общие аноды
	
switch (segcounter)
{	
case 0:
PORTD = ~(SEGMENTE[display % 10000 / 1000]); // Выводим первый разряд
break; 
case 1:
PORTD = ~((SEGMENTE[display % 1000 / 100])|0x80); // добавляем десятичную точку
break;	
case 2:
PORTD = ~(SEGMENTE[display % 100 / 10]); // Выводим третий разряд 
break;		
}
if ((segcounter++) > 1) segcounter = 0;	
}
volatile unsigned long value;
volatile unsigned int adc_counter;
// Прерывание по окончанию преобразования АЦП
ISR (ADC_vect)
{
value = value + (ADC*11/4); // Суммируем старое и новое значения АЦП, преобразуем
adc_counter++; // Увеличиваем счетчик измерений
}
// Главная функция
int main (void) 
{ 
DDRB = 0xFF; // Выходы на общие аноды
PORTB = 0x00; // Ноль на выходе
DDRD = 0xFF; // Выходы на сегменты
PORTD = 0x00; // Ноль на выходе
// Настройка Таймера 2
TIMSK |= (1 << TOIE2); // Разрешение прерывания по таймеру2
TCCR2 |= (1 << CS21);  // Предделитель на 8 
// Настройка АЦП    
ADCSRA |= (1 << ADEN) // Разрешение АЦП
        |(1 << ADSC) // Запуск преобразования
        |(1 << ADFR) // Непрерывный режим работы АЦП
        |(1 << ADPS2)|(1 << ADPS1) // Предделитель на 64 (частота АЦП 125kHz)
	    |(1 << ADIE); // Разрешение прерывания от АЦП
ADMUX |= (1 << REFS1)|(1 << REFS0); // Внутренний ИОН 2,56V, вход ADC0
    
sei(); // Глобально разрешаем прерывания
// Главный цикл 
while(1)
{ 
if(adc_counter > 300) // Если количество измерений больше 300
{
display = value/adc_counter; // Вычисляем среднее значение АЦП
adc_counter = 0; // Обнуляем счетчик измерений
value = 0; // Обнуляем буфер АЦП
}     
_delay_ms(50);
}
}

Исходный код для индикатора с общим катодом

// Использование АЦП. Цифровой вольтметр. Общий катод

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

//------------------0-----1-----2-----3-----4-----5-----6-----7-----8------9----dp                      
char SEGMENTE[] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F, 0x80};

volatile unsigned char segcounter = 0;
volatile int display = 0;

//Прерывание по переполнению T2, динамическая индикация
ISR (TIMER2_OVF_vect)
{	
PORTD = 0x00; // Гасим все сегменты
PORTB = ~(1 << segcounter); // Последовательно зажигаем общие катоды
	
switch (segcounter)
{	
case 0:
PORTD = SEGMENTE[display % 10000 / 1000]; // Выводим первый разряд
break; 
case 1:
PORTD = (SEGMENTE[display % 1000 / 100])|0x80; // добавляем десятичную точку
break;	
case 2:
PORTD = SEGMENTE[display % 100 / 10]; // Выводим третий разряд 
break;		
}
if ((segcounter++) > 1) segcounter = 0;	
}

volatile unsigned long value;
volatile unsigned int adc_counter;

// Прерывание по окончанию преобразования АЦП
ISR (ADC_vect)
{
value = value + (ADC*11/4); // Суммируем старое и новое значения АЦП, преобразуем
adc_counter++; // Увеличиваем счетчик измерений
}

// Главная функция
int main (void) 
{ 
DDRB = 0xFF; // Выходы на общие аноды
PORTB = 0x00; // Ноль на выходе
DDRD = 0xFF; // Выходы на сегменты
PORTD = 0x00; // Ноль на выходе 

// Настройка Таймера 2
TIMSK |= (1 << TOIE2); // Разрешение прерывания по таймеру2
TCCR2 |= (1 << CS21);  // Предделитель на 8

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

sei(); //глобально разрешаем прерывания
 
// Главный цикл
while(1)
{
if(adc_counter > 300) // Если количество измерений больше 300
{
display = value/adc_counter; // Вычисляем среднее значение АЦП
adc_counter = 0; // Обнуляем счетчик измерений
value = 0; // Обнуляем буфер АЦП
}    
_delay_ms(50);
}
}

При программировании установить такие Fuse -  биты:

Обсуждение статьи на форуме.

Файлы:
Файлы проекта для AVR Studio
Дата 30.09.2011 Размер файла 16.51 KB Закачек 5847

Комментарии  

0 #101 kiber 13.12.2015 10:21
В общем уже и не знаю что с ним делать. Выводы питания AGND, AVcc, вывод Aref подключены правильно (пересобирал много раз), всё одно и то же.
Схема 1 в 1 (разве что подстроечный на 20 в, а не на 15), в самом коде тоже ни чего не менял, стоит Atmega8A, семисегментный индикатор с общим анодом.
Делаю так:
- подключаю питание 5 v
- на индикаторе на секунду загорается 00.0
- далее уже интереснее, если я сижу рядом с прибором то показания примерно 20 - 23 v стоит мне отойти от него на некоторое расстояние, то он показывает примерно 24.9 v (показания слегка скачут), и в общем чем ближе я подношу к щупам руку тем меньше становится число, и так вплоть до того, пока я не возьмусь за щуп рукой и на индикаторе покажет 14 v.
Сообщить модератору
0 #102 kiber 13.12.2015 10:23
- далее подключаю к щупам батарейку на 9 v, отхожу :lol:
на индикаторе загорается 21 v, начинаю приближаться к щупу и показания снижаются до 14 v
- меняю батарейку на 1.5 v, отхожу, показывает 23, начинаю приближаться и показания снова падают до 14.
Сообщить модератору
0 #103 AntonChip 13.12.2015 11:35
Если закоротить вход на GND что показывает?
Сообщить модератору
0 #104 kiber 13.12.2015 12:05
Всё, разобрался :lol:
Я немного напутал)
Вывод который идёт от R2 забыл на землю кинуть ))
Теперь всё отлично работает)
Большое спасибо за статью!
Сообщить модератору
+3 #105 Игорь123 11.02.2016 16:06
подскажите пожалуйста как дополнить код, чтобы сохранялось последнее значение напряжения на идикаторе?грубо говоря у меня в схеме стоит кнопка где входное напряжение. я нажимаю сигнал идет показания выводятся. отпускаю кнопку все падает в нули. нужно сделать так чтобы до прихода след сигнала предыдущие показания оставались на идикаторе, а с приходом нового сигнала показания менялись с учетом велечины напряжения. схему я под себя переделал все под себя пересчитал. а вот как это реализовать программно я не понимаю т.к. только начал этим заниматься....
Сообщить модератору
0 #106 Леша 29.05.2016 13:22
хочу измерять напряжение до 250В изменил в схеме делитель в протеусе и понял что в программе нужно чтото менять, с индикацией чтото не то, возможно чтото с разрядами. подскажите что изменить в программе. в программировани и почти ничего не понимаю...
Сообщить модератору
0 #107 AntonChip 01.06.2016 15:49
Если хотите чтобы индикация была после запятой необходимо добавить еще один разряд, если нет то просто убрать десятичную точку. Также необходимо пересчитать делитель напряжения и коэффициент.
Сообщить модератору

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