АЦП — Аналого-цифровой преобразователь. Из названия можно догадаться, что на вход подается аналоговый сигнал, который преобразуется в число.
Первое о чем нужно сказать — АЦП микроконтроллера умеет измерять только напряжение. Чтобы произвести измерение других физических величин, их нужно вначале преобразовать в напряжение. Сигнал всегда измеряется относительно точки называемой опорное напряжение, эта же точка является максимумом который можно измерить. В качестве источника опорного напряжения (ИОН), рекомендуется выбирать высокостабильный источник напряжения, иначе все измерения будут плясать вместе с опорным.
Одной из важнейших характеристик является разрешающая способность, которая влияет на точность измерения. Весь диапазон измерения разбивается на части. Минимум ноль, максимум напряжение ИОН. Для 8 битного АЦП это 2^8=256 значений, для 10 битного 2^10=1024 значения. Таким образом, чем выше разрядность тем точнее можно измерять сигнал.
Допустим вы измеряете сигнал от 0 до 10В. Микроконтроллер используем Atmega8, с 10 битным АЦП. Это значит что диапазон 10В будет разделен на 1024 значений. 10В/1024=0,0097В — с таким шагом мы сможем измерять напряжение. Но учтите, что микроконтроллер будет считать, величину 0.0097, 0.0098, 0.0099… одинаковыми.
Тем не менее шаг в 0,01 это достаточно неплохо. Однако, есть несколько рекомендаций, без которых эта точность не будет соблюдена, например для измерения с точностью 10бит, частота на которой работает АЦП должна быть 50-200 кГц. Первое преобразование занимает 25 циклов и 13 циклов далее. Таким образом, при частоте 200кГц мы сможем максимум выжать
200 000/13 = 15 384 измерений.
В качестве источника опорного напряжения можно использовать внутренний источник и внешний. Напряжение внутреннего источника (2,3-2,7В) не рекомендуется использовать, по причине низкой стабильности. Внешний источник подключается к ножке AVCC или Aref, в зависимости от настроек программы.
При использовании АЦП ножка AVCC должна быть подключена. Напряжение AVCC не должно отличаться от напряжения питания микроконтроллера более чем на 0,3В. Как было сказано, максимальное измеряемое напряжение равно опорному напряжению(Vref), находится оно в диапазоне 2В-AVCC. Таким образом, микроконтроллер не может измерить более 5В.
Чтобы расширить диапазон измерения, нужно измерять сигнал через делитель напряжения. Например, максимальное измеряемое напряжение 10В, опорное напряжение 5В. Чтобы расширить диапазон измерения, нужно уменьшить измеряемый сигнал в 2 раза.
Формула для расчета делителя выглядит так:
Uвых = UвхR2/(R1 + R2)
Подставим наши значения в формулу:
5 = 10*R2/(R1+R2)
(R1+R2)=2*R2
R1=R2
т.е. можно взять любые два одинаковых резистора и подключить их по схеме
Следовательно, когда мы измеряем напряжение через делитель, нужно полученное значение АЦП умножить на коэффициент=Uвых/Uвх.
Полная формула вычисления измеряемого напряжения будет выглядеть так:
U=(опорное напряжение*значение АЦП*коэффициент делителя)/число разрядов АЦП
Пример: опорное 5В, измеренное значение АЦП = 512, коэффициент делителя =2, АЦП 10разрядный.
(5*512*2)/1024=5В — реальное измеренное значение напряжения.
Некоторые программисты пишут программу так, чтобы микроконтроллер автоматически вычислял коэффициент делителя, для этого выходной сигнал измеряют образцовым прибором и заносят это значение в программу. Микроконтроллер сам соотносит истинное напряжение каждому значению АЦП, сам процесс однократный и носит название калибровки.
Перейдем к программной реализации. Создаем проект с указанными параметрами. Также подключим дисплей на порт D для отображения информации.
Измерение будет производиться в автоматическом режиме, обработка кода в прерывании, опорное напряжение подключаем к ножке AVCC. По сути нам нужно только обрабатывать получаемые данные. Измеренные данные хранятся в переменной adc_data[0]. Если нужно опрашивать несколько каналов, то выбираем какие каналы сканировать, а данные будут для ножки 0 в adc_data[0], для ножки 1 в adc_data[1] и т.д.
В основном цикле добавим строки:
result=((5.00*adc_data[0])/1024.00); //пересчитываем значение АЦП в вольты
sprintf(lcd_buffer,»U=%.2fV»,result); //помещаем во временную переменную результат
lcd_puts(lcd_buffer); //выводим на экран
Небольшое замечание, чтобы использовать числа с плавающей точкой, нужно в настройках проекта изменить (s)printf Features: int, width на float, width, precision. Если этого не сделать десятые и сотые мы не увидим.
Таким образом, мы всего лишь перевели значение АЦП в вольты и вывели на дисплей. Результат в протеусе выглядит так:
Резистором можно менять напряжение, измеряемое напряжение выведено на дисплей. При сборке на реальном железе к ножке Aref нужно подключить конденсатор на 0,1мкФ. Урок получился немного сложным, но думаю он вам понравится.
Файл протеуса и прошивка:
Update:
Измерение тока:
Уважаемый админ пожалуйста приведите пример таблицы напряжения, последние цыфры на дисплее прыгают и не понятно какая цыфра отображается
спасибо Вам за уроки, помощь и подсказки…
Большое спасибо за урок! Больше 20 сайтов перерыл, 2 вечера времени впустую, пока не нашел ваш. Сразу все заработало!
Добрый день! А как реализовать дифференциальный режим измерения с выводом минуса (если такой появляется)?
возьмите инструментальный усилитель и не мучайтесь
Админ почему нигде не расписали погрешности при измерениях например 500 v,стоило бы рассписать как делитель зависит от этих погрешностей и что к чему.
Добрый день.
Уважаемый Админ, подскажите пожалуйста, как можно вывести покозания, если АЦП=512 должно отображать «0», АЦП=1023-30?
Покажите пример…
Спасбо
проверяете значение АЦП если ниже 512 то обнуляете, если больше, то отнимаете 512. ну а дальше пропорцией пересчитываете
Доброе утро админ. Как я понял, получается так?
….
read_adc(0);
for(i=0;i<75;i++);
{
inb_amper(amper= ADCW*0,05859375-30);
}
…..
Или есть более правельный способ?
Поправте пожалуйста, если, есть ошибки.
Спасибо….
Я бы рассуждал так:
1. _ADCW = ADCW % 512
2. 512 = 30 _ADCW = (x)?
3. x = _ADCW * 30/512
4. x = _ADCW * 0.05859
Спасибо за помощь, благодоря Вам и Вашему сайту я начал изучать программирование МК…
Уважаемый Админ подскажите пожалуйста как кнопками можно менять уставки температуры может кусок кода подобного есть ?Сам код прилагаю.
#include
#include
// Alphanumeric LCD functions
#include
#include
// Declare your global variables here
char string[10];
void main(void)
{
// Declare your local variables here
int data;
float temp;
// ADC initialization
// ADC Clock frequency: 125,000 kHz
// ADC Voltage Reference: AREF pin
ADMUX=5; //nomer porta adc
ADCSR=0x85;//nastroyka adc
SFIOR=0x00;
ACSR=0x80;
DDRD=0b0001100 ;
// Alphanumeric LCD initialization
// Connections are specified in the
// Project|Configure|C Compiler|Libraries|Alphanumeric LCD menu:
// RS — PORTB Bit 5
// RD — PORTC Bit 1
// EN — PORTB Bit 4
// D4 — PORTB Bit 0
// D5 — PORTB Bit 1
// D6 — PORTB Bit 2
// D7 — PORTB Bit 3
// Characters/line: 8
lcd_init(16);
while (1)
{
delay_ms(10);
ADCSR|=0x40;
data=ADCW;
temp=(float)data*0.09755;
sprintf(string,»t=%2.1f C»,temp);
lcd_gotoxy(0,1);
lcd_puts(string);
/////////////////////////////////TEN1
if (temp=40) {
PORTD.2=0;
lcd_gotoxy(1,0);
lcd_puts(«off!»);}
//////////////////////////////////////TEN2
if (temp=42) {
PORTD.3=0;
lcd_gotoxy(1,0);
lcd_puts(«off!»);}
}
Уважаемый Админ подскажите пожалуйста как в этой программе менять уставки (38 ) с кнопок
while (1)
{
delay_ms(10);
ADCSR|=0x40;
data=ADCW;
temp=(float)data*0.09755;
sprintf(string,»t=%2.1f C»,temp);
lcd_gotoxy(0,1);
lcd_puts(string);
/////////////////////////////////TEN1
if (temp=40) {
PORTD.2=0;
lcd_gotoxy(1,0);
lcd_puts(«off!»);}
//////////////////////////////////////TEN2
if (temp=42) {
PORTD.3=0;
lcd_gotoxy(1,0);
lcd_puts(«off!»);}
}
идею можно подсмотреть тут:
http://avr-start.ru/?p=1587
Уважаемый admin ! Измерение 2-значной целой части отображается правильно, например, 20.45 V отображается 20.45 V. При изменении, например, до 9.45 V — 9.45 VV. Как можно поправить исходник, убрав вторую V? Неужели после каждого цикла — lcd_clear? Спасибо.
можете выводить пробел
Спасибо, получилось! Хорошо, что у нас есть такой замечательный ресурс!
Доброго времени суток! Скачал, поигрался, убрал лишнее, мне нужно только значение ацп, оно отображается на дисплее. Проблема вот в чем: пытаюсь инициализировать порты (хотя бы В):
void main(void)
{
char lcd_buffer[31];
DDRB=(1<<DDB7) | (1<<DDB6) | (1<<DDB5) | (1<<DDB4) | (1<<DDB3) | (1<<DDB2) | (1<<DDB1) | (1<<DDB0);
PORTB=(0<<PORTB7) | (0<<PORTB6) | (0<<PORTB5) | (0<<PORTB4) | (0<<PORTB3) | (0<<PORTB2) | (0<<PORTB1) | (0<<PORTB0);
компилятор, скотина, огрызается:
undefined symbol `DDB7`
undefined symbol `PORTB7`
Нужны 2 порта, B и D, дисплей потом уберу, один порт хочу использовать на включение нескольких нагрузок, другой для индикации заряда 24V литиевой батареи. Подскажете, что не так делаю?
Как выполнить замер в период в 2 секунды .