Страница 1 из 2

Не могу насторить АЦП в ATmega

Добавлено: Сб дек 11, 2010 06:08:27
Кот-тоК
Всем, привет! :))

Я в Proteus моделирую следующую простейшую задачку:
Микроконтроллер atmega16 принимает "данные" с аналогового датчика температуры, АЦП преобразовывает эти данные и они выводятся на дисплей. Но у меня естественно ничего не выводится, хотя с дисплеем я уже разобрался и он функционирует.

Изображение

Питание МК и на входе делителя +5В внешнее.
Произвел калибровку датчика там же в Proteus:

T, град. U, В (на выходе делителя)
0 ----- 3.67
25 ----- 3.92
50 ----- 4.17
75 ----- 4.42
100 ----- 4.67

Вот код, написанный мной. Там инициализация АЦП и прерывание по преобразованию данных.
Пишу в Code::Blocks, компилятор WinAVR.
Прошу прощения если там полный бред написан :oops:

Изображение

Конечно сперва, хотелось бы верно произвести инициализацию. :))
Вот, что написано в книге Шпака:

Изображение


Изображение

Как настраивал ADCSRA:
Изображение

Я взял коэффициент деления 64 поэтому ADPS0 = 0, ADPS1 = 1, ADPS2 = 1.
Частота которая стоит у проца сейчас 8Мгц, в реальном устройстве 11Мгц будет от внешнего кварцевого резонатора.
ADIE = 1.
У Шпака следующий бит называется ADFR, но в документации ADATE.
В документации написано, что 1 в этом бите включает какой-то триггер и преобразование числа в АЦП начинается по нарастающему фронту, чтобы его включить также надо еще что-то где-то выставить в 1....короче, здесь я поставил ADATE = 0.
ADSC = 1.
ADEN = 1.

В итоге ADCSRA = 0b11001110

Как настраивал регистр ADMUX:
Изображение

Датчик висит на ноге PA0 МК, поэтому MUX0..4 = 00000.
Внешний источник питания REFS1 = 0, REFS2 = 1.
Левое выравнивание преобразованных значений ADLAR = 0.

т.е ADMUX = 0b01000000

Так теперь о получении данных, они, как я понял, хранятся в регистрах ADCH и ADCL.

Все свои переменные я определил как unsigned char. Вычисления я провожу прямо в прерывании, это ,вроде бы, не очень хорошо??
Программе приходится ждать пока все вычислится, а так она могла дальше что-то делать.

Вычисления провожу по формуле:

Vin=(ADCH*256+ADCL)*Vref/1024

T=Vin*0,01

Vin - Напряжение на входе АЦП
Vref - Напряжение на входе делителя (напряжение питания)
T - температура
0,01 я получил из калибровки датчика (4.67 - 3.67)/100 т.е повышение температуры на 1 градус соответствует повышению напряжения на 0.01 В.

Не работает!!!!!! Что делать??? Может преобразование типов переменных не верное?
И еще как запустить преобразование в АЦП повторно? сбрасывать ADIF в ноль??
Помогите!!!!!! :)) :)) :))

Re: Не могу насторить АЦП в ATmega

Добавлено: Сб дек 11, 2010 07:02:36
Кот-тоК
Не научился я отлаживать программы ни в Code::Blocks ни в Protus.
Повыводил значения регистров ADCH и ADCL на светодиоды:
ADCH = 0x00
ADCL = 0x01

Меняю температуру значения не меняются. Не удивительно, что на экран ничего не выводится, по таким адресам там нет символов.
:dont_know: :dont_know: :dont_know: :dont_know: :dont_know: :dont_know:

Прерывания выполняются, тоже по светодиодам проверял.

К стати, узнал что для начала повторного преобразования значений нужно ADSC установить в 1 :)

Re: Не могу насторить АЦП в ATmega

Добавлено: Сб дек 11, 2010 08:16:22
Vov123
Вместо подтягивающего резистора на 10К,установите PULLUP.

Re: Не могу насторить АЦП в ATmega

Добавлено: Сб дек 11, 2010 14:16:34
avrman
подозреваю что это глюк самого протеуса, так как тоже пробовал выводить значения ацп меги32 на жки, только сигнал заводил с резистивного делителя. Ничего кроме нуля протеус на дисплей не выводил, собрал в железе, прекрасно работает.

Re: Не могу насторить АЦП в ATmega

Добавлено: Сб дек 11, 2010 14:26:07
Vov123
Посмотрите пожалуйста,сейчас не глючит?И если не трудно сообщите.

Re: Не могу насторить АЦП в ATmega

Добавлено: Сб дек 11, 2010 15:54:34
Кот-тоК
Всем, доброе утро!!! :) :) :)
Спасибо за ответы!

Vov123 писал(а):Посмотрите пожалуйста,сейчас не глючит?И если не трудно сообщите.


Запустил Ваш проект и..... всё работает как надо :) :) :) Меняешь температуру меняются значения на дисплее (конечно они пока не откалиброваны). Так что это проблема в неправильно написанной мной программе :dont_know: :dont_know: :dont_know:

Расскажите пожалуйста про PULLUP резистор. И может ещё есть какие-то секреты настройки АЦП. :write:

Re: Не могу насторить АЦП в ATmega

Добавлено: Сб дек 11, 2010 16:03:39
Vov123
PULLUP для симуляции в Проте,а в живую нужно подбирать самому.Возможно ваш R10K будет вполне пригоден.Вот вам мой проектик,переделанный под ваши нужды,я думаю разберётесь сами.

Re: Не могу насторить АЦП в ATmega

Добавлено: Сб дек 11, 2010 16:09:53
Кот-тоК
Спасибо, спасибо, спасибо! :))
Очень хочется прямо сейчас глянуть, но надо по делам уходить. Вернусь обязательно проверю и сообщу результат. :beer:

Re: Не могу насторить АЦП в ATmega

Добавлено: Сб дек 11, 2010 17:16:55
stas00n
Кот-тоК писал(а):Все свои переменные я определил как unsigned char...
...
Не работает!!!!!! Что делать??? Может преобразование типов переменных не верное?


Ваш код по идее не должен работать правильно; T будет всегда 0; кстати компилятор не ругается на значение 0,00005 ?

Код: Выделить всё

unsigned char a,b,T;
a = ADCH;
b = ADCL;
T = (a*256+b)*0,00005;

Кроме того, желательно считывать сначала ADCL, а потом ADCH.

Кот-тоК писал(а):Вычисления я провожу прямо в прерывании, это ,вроде бы, не очень хорошо??
Программе приходится ждать пока все вычислится, а так она могла дальше что-то делать.

В прерывании надо только сохранить результат преобразования, а вычисления и вывод на дисплей делать в основном цикле
Кот-тоК писал(а):И еще как запустить преобразование в АЦП повторно? сбрасывать ADIF в ноль??
Помогите!!!!!! :)) :)) :))

Считать ADCL, ADCH; сбросить ADIF; установить ADSC.
Кот-тоК писал(а):Вычисления провожу по формуле:

Vin=(ADCH*256+ADCL)*Vref/1024

T=Vin*0,01

Vin - Напряжение на входе АЦП
Vref - Напряжение на входе делителя (напряжение питания)
T - температура
0,01 я получил из калибровки датчика (4.67 - 3.67)/100 т.е повышение температуры на 1 градус соответствует повышению напряжения на 0.01 В.

Немного странный подход. Для линейной зависимости я обычно составляю примерно такую табличку:

Код: Выделить всё

Температура|Отсчет АЦП =1024*Vin/Vref
----------
       0          |                  751
     100        |                  956

Для вычисления температуры составляем линейное уравнение:

t = a * ADC + b

подставляем данные из таблицы и решаем простейшую систему

751a + b = 0;
956a + b = 100;
получаем a = 0,488; b = -366.

Чтобы в программе не работать с плавающей математикой, коэффициент а лучше представить в виде двоичной дроби:
0,488 = (125/256);

Я бы сделал примерно так:

Код: Выделить всё

#define TEMP_MULTIPLIER  125   //MULTIPLIER*256
#define TEMP_OFFSET      -366


volatile unsigned int adcResult     = 0;
static unsigned char temperatura    = 0;

void main (void){
unsigned long t;            //Вспомогательная переменная
//Other code here
ADCSRA &= ~(1<<ADIE);       //Запрет прерывания от АЦП
                            //для правильного вычисления
t = ((adcResult * TEMP_MULTIPLIER)>>8) + TEMP_OFFSET;
ADCSRA |= (1<<ADIE);        //Разрешить прерывания
temperatura = (unsigned char) t;
//Other code here
}


void interrupt ADC_Interrupt (void){
adcResult   = (ADCH<<8) + ADCL;
ADCSRA      &= ~(1<<ADIF);
ADCSRA      |= (1<<ADSC);
}


За работоспособность не ручаюсь, но принцип примерно такой.

Re: Не могу насторить АЦП в ATmega

Добавлено: Вс дек 12, 2010 01:34:10
Кот-тоК
:idea:

Re: Не могу насторить АЦП в ATmega

Добавлено: Вс дек 12, 2010 01:35:30
Кот-тоК
Ну что же, результат на данный момент следующий:

регистры ADCL заполняется правильно :)
Их значения передаю на мониторчик, там загорается символ который в пересчете правильно определяет температуру.
Т.е. если у меня температура 52 то на экране загорается #. Напряжение на входе при этом 3,92.
По формуле ADC=Vin*1024/Vref получаю что ADC = 803 в бинарном виде 00100011, что соответствует в таблице символов экрана символу #.
Надеюсь, что ADCH тоже верно заполняется, не проверял. :)

Заработало после правки кода по примерам и обязательной замены обычного резистора на PULLUP резистор. :)

Но вот с переводом того, что лежит в этих регистрах в градусы Цельсия проблемы. Пытался сам, что-либо выдумать, но без деления не получается.

stas00n писал(а):Для вычисления температуры составляем линейное уравнение:

t = a * ADC + b


Никак не могу понять откуда линейные уравнения и как вам удалось деления избежать.
Извиняюсь за своё скудоумие :)))

Re: Не могу насторить АЦП в ATmega

Добавлено: Вс дек 12, 2010 01:56:23
Кот-тоК
Кот-тоК писал(а):stas00n писал(а):
Для вычисления температуры составляем линейное уравнение:

t = a * ADC + b


Никак не могу понять откуда линейные уравнения ....


Вот же я выдал :)) :)) :))

Изображение

Из калибровки оно и есть :)) :)) :))

ААААааа!!!Всё, понял!!! как надо переводить, сейчас попробую :idea:

Re: Не могу насторить АЦП в ATmega

Добавлено: Вс дек 12, 2010 02:39:00
stas00n
Кот-тоК писал(а):Никак не могу понять откуда линейные уравнения и как вам удалось деления избежать.
Извиняюсь за своё скудоумие :)))

Линейная функция - из школьного курса алгебры - функция вида y = ax + b :)
в нашем случае -
t = a * ADC + b;
Подставив известные значения вместо t и ADC для двух калибровочных точек получим систему из двух линейных уравнений:

751a + b = 0;
956a + b = 100;

выразим b через a из первого уравнения:

b = -751a

и подставим во второе:

956a - 751a = 100;
205a = 100;
a = 100/205 = 0,488;

потом находим b

b = -751a;
b = -751 * 0,488 = -366.


Получаем формулу для расчета температуры:

t = 0,488 * ADC - 366;

:))) :)))

Чтобы легче было помножить на 0,488, делаем фокус - умножаем например, на 256/256:

t = 0,488 * 256 * ADC / 256 - 366 = 125 * ADC / 256 - 366
Деление на 256 в контроллере суть логический сдвиг вправо на 8 разрядов, то бишь помножив отсчет АЦП на 125 и отбросив младший байт результата получим 0,488 * ADC; дальше банально вычитаем 366 - получаем искомую температуру в градусах.
При таком вычислении нужно учесть - промежуточный результат 125 * 1023 = 127875 > 65535, т.е. в переменную типа unsigned int уже не влезет, - нужна переменная long int или unsigned long int

Re: Не могу насторить АЦП в ATmega

Добавлено: Вс дек 12, 2010 03:21:17
Кот-тоК
Да, я ступил! :)) :)) :))
Бывает!!!

Значит так:
Из-за вот этой строчки в прерывании

Код: Выделить всё

adcResult   = (ADCH<<8) + ADCL;


Proteus сильно тупил, и писал что в регистры ADC не успевает ничего записываться.
Перенес её в main - заработало. :))

Осталось, надеюсь, последнее препятствие: перевести полученное значение температуры из unsigned char в строку.
Функцию sprintf никак не могу подрубить :dont_know:

Кстати, вот эта фишка очень крута оказалась.

Код: Выделить всё

t = ((adcResult * TEMP_MULTIPLIER)>>8) + TEMP_OFFSET;


Я имею ввиду a>>8 вместо деления на 256. Очень понравилось, не знал такого, теперь запомню. :))

Re: Не могу насторить АЦП в ATmega

Добавлено: Вс дек 12, 2010 03:35:34
stas00n
Кот-тоК писал(а):Да, я ступил! :)) :)) :))
Бывает!!!

Значит так:
Из-за вот этой строчки в прерывании

Код: Выделить всё

adcResult   = (ADCH<<8) + ADCL;


Proteus сильно тупил, и писал что в регистры ADC не успевает ничего записываться.
Перенес её в main - заработало. :))

Дык нельзя ее в main переносить! На то она и в прерывании, чтобы сохранить результат, освободить ацп, а дальше в мэйне делать с этим результатом что заблагорассудится. Лучше вынесите повторный старт преобразования из прерывания в основной цикл, после вывода на дисплей, - зачем ацп лишние телодвижения пока предыдущий результат не обработан. Кстати, CVAVR генерит на эту строчку просто адский код - реально в цикле восьмикратно прокручивает ADCH! :shock: вместо того, чтобы тупо сохранить. Надо как-то по другому делать или в компиляторе что-то настроить.
Кот-тоК писал(а):
Кстати, вот эта фишка очень крута оказалась.

Код: Выделить всё

t = ((adcResult * TEMP_MULTIPLIER)>>8) + TEMP_OFFSET;


Я имею ввиду a>>8 вместо деления на 256. Очень понравилось, не знал такого, теперь запомню. :))

Умножение/деление на 2^n делается сдвигом на n бит влево/вправо соответственно.

Re: Не могу насторить АЦП в ATmega

Добавлено: Вс дек 12, 2010 03:51:06
Кот-тоК
stas00n писал(а):Дык нельзя ее в main переносить! На то она и в прерывании, чтобы сохранить результат, освободить ацп, а дальше в мэйне делать с этим результатом что заблагорассудится. Лучше вынесите повторный старт преобразования из прерывания в основной цикл, после вывода на дисплей, - зачем ацп лишние телодвижения пока предыдущий результат не обработан. Кстати, CVAVR генерит на эту строчку просто адский код - реально в цикле восьмикратно прокручивает ADCH! :shock: вместо того, чтобы тупо сохранить. Надо как-то по другому делать или в компиляторе что-то настроить.


Да, да! Я понимаю, что её просто так нельзя переносить. Я немного не точно выразился.
Вот код который написал, он во многом сходен с примером, но куда деваться :)) Главное всё понять.

Код: Выделить всё

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

#define k 202
#define b -580

unsigned char ADCresult1 = 0, ADCresult2 = 0;
unsigned char temperatura = 0;

void main()
{

  unsigned long t;

  DDRC = 0xFF;
  DDRD = 0xFF;
  PORTC = 0;
  PORTD = 0;

  LCMinit();

  ADCinit();

  SREG |= (1 << 7);

  while(1)
  {
    ADCSRA |= (0 << 3);
   
    t = ((((ADCresult2 << 8) + ADCresult1) * k) >> 8) + b;
   
    temperatura = (unsigned char) t;

    SetLCMPosition(1,0);
    ShowChar(temperatura);

    ADCSRA |= (1 << 3);
  }


}


// Инициализация АЦП
void ADCinit()
{

    ADMUX |= (1 << 6);  //АЦП работает от Vcc, прием данных по A0
    ADCSRA |= (1 << 7) | (1 << 6) | (1 << 3) |  (1 << 2) | (1 << 1);

}

ISR (SIG_ADC)
{
    ADCresult1 = ADCL;
    ADCresult2 = ADCH;


    ADCSRA |= (0 << 4);
    ADCSRA |= (1 << 6);
}


Я пытался переносить

Код: Выделить всё

    
    ADCSRA |= (0 << 4);
    ADCSRA |= (1 << 6);


в main, не фурычит. Он продолжает штамповать прерывания без остановки. Как так?? :dont_know: :dont_know: :dont_know: :dont_know:

Re: Не могу насторить АЦП в ATmega

Добавлено: Вс дек 12, 2010 03:55:12
stas00n
Кот-тоК писал(а):Proteus сильно тупил, и писал что в регистры ADC не успевает ничего записываться.

Проверьте, какой код в прерывании получается при компиляции? Возможно тоже имеет место быть попытка повторного чтения регистров результата АЦП, а на это контроллер будет ругаться аппаратно, т.к. последний результат уже считан, а новый еще не получен. Тогда наверное имеет смысл добавить временные переменные:

Код: Выделить всё

interrupt [ADC_INT] void Adc_Interrupt_Handler(void){
unsigned char a, b;
a = ADCL;
b = ADCH;
adcResult   = (b<<8) + a;
//TODO: Other code here
}

Re: Не могу насторить АЦП в ATmega

Добавлено: Вс дек 12, 2010 04:06:21
stas00n
Кот-тоК писал(а):
Я пытался переносить

Код: Выделить всё

    
 ADCSRA |= (0 << 4);                   //Этот код ничего не делает!!!
    ADCSRA |= (1 << 6);


в main, не фурычит. Он продолжает штамповать прерывания без остановки. Как так?? :dont_know: :dont_know: :dont_know: :dont_know:


Запись ADCSRA |= (0 << 4) делает побитовое "ИЛИ" с нулем! То есть ничего! Флаг прерывания не сбрасывается! Чтобы сбросить бит нужна операция "И":

Код: Выделить всё

ADCSRA &= ~(1 << 4)

А вместо цифр нужно писать название битов (они ведь продефайнены в h-файле):

Код: Выделить всё

ADCSRA &= ~(1 << ADIF)

Re: Не могу насторить АЦП в ATmega

Добавлено: Вс дек 12, 2010 04:12:50
Кот-тоК
Пока программа которую я приводил верно симулируется в Proteus. Конечно не всё так радужно, он пишет, что не может в режиме реального времени симулировать, т.к. проц перегружен. Хотя если использовать тот проект который мне дал Vov123, то у него всё слету нормально работает.
У меня же всё как обычно не просто :?

Когда вот так вот записал

stas00n писал(а):Код:
interrupt [ADC_INT] void Adc_Interrupt_Handler(void){
unsigned char a, b;
a = ADCL;
b = ADCH;
adcResult = (b<<8) + a;
//TODO: Other code here
}


стало нормально симулироваться, только Proteus все-равно пишет, что в реальном времени не может симулировать т.к. проц перегружен.

И проблема с переводом числа в строку тоже не решена...

Re: Не могу насторить АЦП в ATmega

Добавлено: Вс дек 12, 2010 04:15:33
Кот-тоК
stas00n писал(а):Запись ADCSRA |= (0 << 4) делает побитовое "ИЛИ" с нулем! То есть ничего! Флаг прерывания не сбрасывается! Чтобы сбросить бит нужна операция "И":
Код:
ADCSRA &= ~(1 << 4)


Вот я программист-то :)) :)) :)) Действительно, ваша правда, сейчас поправлю.