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

Входом АЦП является вывод PC0(ADCO), установлен по умолчанию в регистре ADMUX. Так как вольтметр у нас должен измерять напряжение от 0 до 25V, а 25V для порта контроллера это очень много, в таких случаях используют делитель напряжения, на схеме это резисторы R1 и R2. Например, если напряжение на входе будет меняться от 0 до 25V, то на выходе оно будет меняться от 0 до 5V. Резистор R3 - подстроечный, служит для подстройки отображаемого на дисплее уровня напряжения, желательно многооборотный. Источником опорного напряжения будет вывод питания AVCC с внешним конденсатором на выводе AREF, режим активируется установкой бита REFS0 в регистре ADMUX. Для снижения влияния шумов на результат преобразования вывод AVCC необходимо связать с цифровым питанием VCC через LC-цепь.

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

Применим такую формулу, где: R1 = 100k, R2 = 25k, Uin = 5V,

Umax = Uin*(R1+R2)/R2
Umax = 5*125k/25k = 25V

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

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

U = ADC*Uref*K/1024

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

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

K = (R1+R2)/R2
K = (100k + 25k)/25k = 5

В нашем примере максимальная разрешающая способность будет 10 разрядов, поэтому частота на входе схемы последовательного приближения должна быть в диапазоне 50…200 кГц. Установим ее впределах 125kHz, включим предделитель на 64 путем установки битов ADPS2 и ADPS1 в регистре ADCSRA.

В программе используем прерывание по окончании преобразования АЦП, для этого включим непрерывный режим работы АЦП путем установки бита ADFR и активируем прерывание битом ADIE в этом же регистре ADCSRA

В обработчике прерывания от АЦП 10-ти битный результат преобразования(ADC) после каждого измерения помещаем в буфер, а уже в основном цикле значение буфера делим на количество измерений для усреднения показаний. Далее, применив формулу, вычисляем результат преобразования, переводим результат вычисления в милливольты, путем умножения на 100, и выводим его на дисплей. Полный текст программы ниже.

// Использование АЦП. Цифровой вольтметр 0-25V

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

// Массив комбинаций сегментов                    
unsigned char SEGMENTE[] = 
{
0x3F, // 0 
0x06, // 1
0x5B, // 2
0x4F, // 3
0x66, // 4
0x6D, // 5
0x7D, // 6
0x07, // 7
0x7F, // 8
0x6F  // 9
};

volatile unsigned char seg_counter, adc_counter;
volatile unsigned int display = 0;
volatile unsigned long adc_buffer;

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

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

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

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

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

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

// Главный цикл 
while(1)
{ 
if(adc_counter == 250) // Если количество измерений равно 250
{
display = ((adc_buffer*5*5*100)/1024)/adc_counter; // Вычисляем среднее значение АЦП
adc_counter = 0; // Обнуляем счетчик измерений
adc_buffer = 0; // Обнуляем буфер АЦП
}     

}
}

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

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


Архив для статьи "АЦП микроконтроллеров AVR. Делаем цифровой вольтметр 0 - 25V"
Описание: Файлы проекта для AVR Studio
Размер файла: 16.51 KB Количество загрузок: 8 718 Скачать

Комментарии  

-1 #101 AntonChip 13.12.2015 11:35
Если закоротить вход на GND что показывает?
Сообщить модератору
0 #102 kiber 13.12.2015 12:05
Всё, разобрался :lol:
Я немного напутал)
Вывод который идёт от R2 забыл на землю кинуть ))
Теперь всё отлично работает)
Большое спасибо за статью!
Сообщить модератору
+4 #103 Игорь123 11.02.2016 16:06
подскажите пожалуйста как дополнить код, чтобы сохранялось последнее значение напряжения на идикаторе?грубо говоря у меня в схеме стоит кнопка где входное напряжение. я нажимаю сигнал идет показания выводятся. отпускаю кнопку все падает в нули. нужно сделать так чтобы до прихода след сигнала предыдущие показания оставались на идикаторе, а с приходом нового сигнала показания менялись с учетом велечины напряжения. схему я под себя переделал все под себя пересчитал. а вот как это реализовать программно я не понимаю т.к. только начал этим заниматься....
Сообщить модератору
0 #104 Леша 29.05.2016 13:22
хочу измерять напряжение до 250В изменил в схеме делитель в протеусе и понял что в программе нужно чтото менять, с индикацией чтото не то, возможно чтото с разрядами. подскажите что изменить в программе. в программировани и почти ничего не понимаю...
Сообщить модератору
-1 #105 AntonChip 01.06.2016 15:49
Если хотите чтобы индикация была после запятой необходимо добавить еще один разряд, если нет то просто убрать десятичную точку. Также необходимо пересчитать делитель напряжения и коэффициент.
Сообщить модератору
0 #106 svarech 23.12.2016 23:39
Я что-то не пойму. Как подключить щуп на порт (С) если в самом машинном коде нет упоминание о порте (С)? В Протеусе схема не работает. Почему? камень его (порт) просто невиден. Как можно начинающему разобраться? ;-) ;-) ;-)
Сообщить модератору
0 #107 AntonChip 25.12.2016 09:25
Цитирую svarech:
Я что-то не пойму. Как подключить щуп на порт (С) если в самом машинном коде нет упоминание о порте (С)? В Протеусе схема не работает. Почему? камень его (порт) просто невиден. Как можно начинающему разобраться? ;-) ;-) ;-)

Порт С не надо предварительно определять, т.к. мы уже настроили параметры ацп в коде
Сообщить модератору
0 #108 Андрей_men 12.05.2017 15:06
Как поменять выводы порта на сегменты, очень не удобно с разводкой, работал в кодевижен, в авр студии полный ноль, строчку в исходнике нашел, с калькулировал по своему, а скампилировать никак, не могли бы помочь?
Сообщить модератору
0 #109 Witja 01.05.2018 21:23
:sigh: Я понимаю что года прошли с момента написания данной статьи... Но, у меня некий вопрос.
Я могу довериться этому микроконтроллер у, если он будет установлен в щите, общая нагрузка которого ~400 кВт, с огромными контакторами и частотниками. И измерять напряжение на расстоянии 30м обычным ШВВп проводом. Не заглючит мега??
Сообщить модератору
0 #110 AntonChip 02.05.2018 12:44
Цитирую Witja:
Я могу довериться этому микроконтроллеру, если он будет установлен в щите, общая нагрузка которого ~400 кВт, с огромными контакторами и частотниками. И измерять напряжение на расстоянии 30м обычным ШВВп проводом. Не заглючит мега??

Может и не заглючит, но при измерении будут наводки и падение напряжения
Сообщить модератору
0 #111 Witja 02.05.2018 21:47
На сколько я понимаю, чтобы уменьшить наводки, надо увеличить ток в измерительной цепи? То есть уменьшить входное сопротивление.. .
Сообщить модератору
0 #112 uuu000 25.01.2020 23:52
не совсем понимаю формулу подсчета
накопленного в буфере
adc_buffer*5*5*100/1024
Сообщить модератору
0 #113 uuu000 25.01.2020 23:57
а именно эту запись *5*5*100
ведь значение буфера adc_buffer += ADC
за 250 прерываний уже известно
Сообщить модератору
0 #114 AntonChip 26.01.2020 16:24
Код:
display = ((adc_buffer*5*5*100)/1024)/adc_counter;

adc_buffer - Значение АЦП накопленное на данный момент
5 - опорное напряжение
5 - коэффициент делителя напряжения
100 - переводим в милливольты, чтобы удобно вывести на дисплей
1024 - разрядность АЦП, 10 бит
adc_counter - счетчик измерений, т.е. 250
Сообщить модератору
0 #115 uuu000 26.01.2020 17:41
Спасибо,еще вопрос-почему вольты переводим в милливольты умножением на 100,а не на 1000.Возможно из-за расположения точки в
индикаторе.
Сообщить модератору
0 #116 AntonChip 27.01.2020 07:51
Цитирую uuu000:
Спасибо,еще вопрос-почему вольты переводим в милливольты умножением на 100,а не на 1000.Возможно из-за расположения точки в
индикаторе.

Да, правильно
Сообщить модератору