Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Программирование АЦП на atmega16L-16AI
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > Программирование
abitwise
Доброго времени суток.
Поскольку я не профессиональный программист и до этого момента с микроконтроллерами общался постольку поскольку, то обращаюсь к знающим и опытным людям с просьбой помочь разобраться с поставленной передо мной задачей.

Исходные данные:

Есть - мк Atmega16L-16AI (TQFP)
- два вентилятора Jamicon (JF0825B1H), Evercool (EC6010H12B)
- два дачитка температуры типа NTC

Задача: необходимо считывать информацию с датчиков и выдавать на вентиляторы напряжения, соответствующие температурным диапазонам.
Так как я в этом деле новичок, то разбил задачу на три.
1) Научиться выдавать ШИМ на вентиляторы. С этим я худо бедно разобрался!

Вот код
Код
  Chip type           : ATmega16L
Program type        : Application
Clock frequency     : 8,00000 MHz
Memory model        : Small
External SRAM size  : 0
Data Stack size     : 256
*****************************************************/


#include <mega16.h>
#include <delay.h>

// Timer 1 overflow interrupt service routine
interrupt [TIM1_OVF] void timer1_ovf_isr(void)
{
OCR1A=0x00;
OCR1B=0x00;
TCNT1L=0x00;  
PORTD=0b00110000;
}

//Timer 1 output compare A interrupt service routine
interrupt [TIM1_COMPA] void timer1_compa_isr(void)
{
   PORTD=0b00110000;

}

// Timer 1 output compare B interrupt service routine
interrupt [TIM1_COMPB] void timer1_compb_isr(void)

{
  PORTD=0b00110000;
    
}

void main(void)
{


// Input/Output Ports initialization
// Port A initialization
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In
// State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T
PORTA=0x00;
DDRA=0x00;

// Port B initialization
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In
// State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T
PORTB=0x00;
DDRB=0x00;

// Port C initialization
// Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In
// State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T
PORTC=0x00;
DDRC=0x00;

// Port D initialization
// Func7=In Func6=In Func5=Out Func4=Out Func3=In Func2=In Func1=In Func0=In
// State7=T State6=T State5=0 State4=0 State3=T State2=T State1=T State0=T
PORTD=0x00;
DDRD=0x30;

// Timer/Counter 0 initialization
// Clock source: System Clock
// Clock value: Timer 0 Stopped
// Mode: Normal top=FFh
// OC0 output: Disconnected
TCCR0=0x00;
TCNT0=0x00;
OCR0=0x00;

// Timer/Counter 1 initialization
// Clock source: System Clock
// Clock value: 8192,000 kHz
// Mode: Fast PWM top=00FFh
// OC1A output: Non-Inv.
// OC1B output: Non-Inv.
// Noise Canceler: Off
// Input Capture on Falling Edge
// Timer 1 Overflow Interrupt: On
// Input Capture Interrupt: Off
// Compare A Match Interrupt: On
// Compare B Match Interrupt: On
TCCR1A=0xA1;
TCCR1B=0x09;
TCNT1H=0x00;
TCNT1L=0x00;
ICR1H=0x00;
ICR1L=0x00;
OCR1AH=0x00;
OCR1AL=0x00;
OCR1BH=0x00;
OCR1BL=0x00;

// Timer/Counter 2 initialization
// Clock source: System Clock
// Clock value: Timer 2 Stopped
// Mode: Normal top=FFh
// OC2 output: Disconnected
ASSR=0x00;
TCCR2=0x00;
TCNT2=0x00;
OCR2=0x00;

// External Interrupt(s) initialization
// INT0: Off
// INT1: Off
// INT2: Off
MCUCR=0x00;
MCUCSR=0x00;

// Timer(s)/Counter(s) Interrupt(s) initialization
TIMSK=0x1C;

// Analog Comparator initialization
// Analog Comparator: Off
// Analog Comparator Input Capture by Timer/Counter 1: Off
ACSR=0x80;
SFIOR=0x00;

// ADC initialization
// ADC Clock frequency: 64,000 kHz
// ADC Voltage Reference: AREF pin
// ADC Auto Trigger Source: Timer1 Overflow
// Only the 8 most significant bits of
// the AD conversion result are used
//ADMUX=0b00000000;
//ADCSRA=0xF7;
//SFIOR&=0x1F;
//SFIOR|=0xC0;
SPCR=0x40;
SPSR=0x01;

#asm("sei")

while (1)
{
   OCR1B=100;
   OCR1A=100;
};
}


По части теории вопросов у меня пока что нет. Перед решением этой задачи были проведены расчёты и получены соответствующие значения регистров сравнения таймера/счётчика1, соответствующих температурным диапазонам. Также согласно формуле из даташита на атмегу получено число ADC.
Вот эти значения (приведу только один диапазон в целях экономии времени):

температура, С измерительное напряжение, В ADC OCR1A OCR1B
25 1.196 299 0 0
26 1.159 290 0 0
27 1.123 281 0 0
28 1.087 272 0 0
29 1.053 263 0 0
30 1.020 255 0 0

и т. д.
Со второй задачей справиться не получается. Заключается она в том, чтобы научиться работать с АЦП.
А именно как правильно считывать информацию из АЦП, где и какие прерывание нужны в данной задаче? То есть я хочу понять структуру кода на Си, которая позволяет это делать и как такую структуру писать.
Дело в том, что я пользуюсь CodeVisionAvr, который генерит код, если использовать Wizard. Это на мой взгляд удобно, но только в том, случае если есть полное понимание сгенеренного кода. У меня такого понимания нету.
Я пытался самостоятельно решить эту проблему и протестировать полученный результат в Протеус, как в решении первой задачи, но ничего не вышло
Вот код (всё тоже самое только с добавлением сгенеренного кода для АЦП):
Код


1-   #define ADC_VREF_TYPE 0x04                                
2-   // Read the 8 low significant bits
3-   // of the AD conversion result
4-   unsigned char read_adc(unsigned char adc_input)
5-   {
6-    ADMUX=adc_input | (ADC_VREF_TYPE & 0xff);  
7-    Delay needed for the stabilization of the ADC input voltage
8-    delay_us(10);
9-    // Start the AD conversion
10-  ADCSRA=0b11100110;
11-  // Wait for the AD conversion to complete
12-  while ((ADCSRA & 0x40)==0);
13-  ADCSRA|=0x10;
14-  return ADCL;
15-  }





16-   // ADC interrupt service routine
17-   interrupt [ADC_INT] void adc_isr(void)
18-   {
19-   unsigned char adc_data_l;
20-   // Read the 8 low significant bits
21-   // of the AD conversion result
22-   adc_data_l = ADCL;

23-   if(adc_data_l==0xFF)
24-     {
25-       OCR1A = 255 - adc_data_l;
26-       OCR1B = 255 - adc_data_l;
27-     }
28-   else
29-     {
30-      OCR1A = adc_data_l;
31-      OCR1B = adc_data_l;
32-      };
33-   }


Хотелось бы узнать от опытных людей, для чего необходимо и что означают 1 и 6 строчки кода, а также 12 и 13.
Далее хотелось бы понять правильно ли я сделал, что в процедуре прерывания АЦП написал последовательность действий по присвоению значений регистрам сравнения ?

3) Третья задача пока вообще не рассматриволась. Заключается она в передаче данных из АЦП в процедуру сравнения полученного из АЦП числа.
У меня процедура сравнения выглядит следующим образом:
Код
int range[9]={299, 255, 217, 184, 132, 112, 95, 81};

if (range[0] <= adc_data_l)
  {
   OCR1AL=0;
   OCR1BL=0;
  };
if (range[1] <= adc_data_l)
  {
   OCR1AL=0;
   OCR1BL=0;
  };
if (range[2] <= adc_data_l < range[1])  
  {
   OCR1AL=128;
   OCR1BL=85;
  };
if (range[3] <= adc_data_l< range[2])  
  {
   OCR1AL=149;
   OCR1BL=96;
  };    
if (range[4] <= adc_data_l < range[3])  
  {
   OCR1AL=170;
   OCR1BL=106;
  };    
if (range[5] <= adc_data_l < range[4])  
  {
   OCR1AL=191;
   OCR1BL=138;
  };
if (range[6] <= adc_data_l < range[5])  
  {
   OCR1AL=213;
   OCR1BL=149;
  };  
if (range[7] <= adc_data_l < range[6])  
  {
   OCR1AL=234;
   OCR1BL=170;
  };
if (range[8] <= adc_data_l < range[7])  
  {
   OCR1AL=255;
   OCR1BL=255;
  };

Массив из значений регистров сравнения ввёл для удобства. Хотя может быть оно и сомнительное.
В какой части кода необходимо писать эту процедуру ? В бесконечном цикле while(1){}? Или в прерываниях? Поскольку переменная adc_data_l объявлена внутри функции, то использовать её как глобальную переменную нельзя. Как решается эта проблема?. То есть, допустим я прочитал АЦП и записал число в эту переменную, что нужно сделать чтобы можно было пользоваться этой переменной в процедуре сравнения?

Заранее благодарю за помощь! Если на форуме или в интернете уже есть идентичные темы, в которых предложены решения данных проблем, то прошу поделиться ссылкой. Я обыскался в инете на эту тему, но не нашёл ничего, чтобы мне объяснило или подсказало как решать и что делать.
Надеюсь на вашу помощь!
Палыч
Цитата(abitwise @ Apr 7 2011, 10:26) *
Хотелось бы узнать от опытных людей, для чего необходимо и что означают 1 и 6 строчки кода, а также 12 и 13.

Строка 1: определяет константу для задания опорного напряжения (в Вашем случае - AVCC with external capacitor at AREF pin)
Строка 6: коммутирует на АЦП один из входов (номер входа определяется значением adc_input) и опорное напряжение
Строка 12: ожидание окончания преобразования (измерения)
Строка 13: сброс флага прерывания

Цитата(abitwise @ Apr 7 2011, 10:26) *
Далее хотелось бы понять правильно ли я сделал, что в процедуре прерывания АЦП написал последовательность действий по присвоению значений регистрам сравнения ?
Непонятно: зачем используете процедуру прерывания, раз в функции измерения (read_adc) дожидаетесь готовности, кроме того функция измерения возвращает код АЦП (регистр ADCL)...
Если Ваша программа только и должна делать, что измерять температуру и по её значению менять параметры ШИМ, то эти действия можно поместить в бесконечный цикл.
abitwise
Спасибо за ответ.
Если я вас правильно понял, то процедура прерывания не нужна, и достаточно функции read_adc, чтобы прочитать данные из АЦП?
В даташите сказано, что регистры ADCL и ADCH можно только читать. Следовательно, чтобы работать с полученным числом надо создать переменную, так ?
Где и как её объявить, чтобы я мог пользоваться ей в процедуре сравнения?


Не очень понял
Цитата
Строка 1: определяет константу для задания опорного напряжения (в Вашем случае - AVCC with external capacitor at AREF pin)
Строка 6: коммутирует на АЦП один из входов (номер входа определяется значением adc_input) и опорное напряжение

То есть вот здесь
Код
6- ... (ADC_VREF_TYPE & 0xff)

мы получаем число 0000 0100;
вот здесь
Код
adc_input | (ADC_VREF_TYPE & 0xff);

происходит побитовая логическая операция ИЛИ. И потом получившийся результат записывается в ADMUX?
И это число зависит от значения adc_input?

Палыч
Цитата(abitwise @ Apr 7 2011, 13:19) *
Если я вас правильно понял, то процедура прерывания не нужна, и достаточно функции read_adc, чтобы прочитать данные из АЦП?
В процедуре прерывания в Вашем случае не вижу необходимости.

Цитата(abitwise @ Apr 7 2011, 13:19) *
В даташите сказано, что регистры ADCL и ADCH можно только читать. Следовательно, чтобы работать с полученным числом надо создать переменную, так ?
Где и как её объявить, чтобы я мог пользоваться ей в процедуре сравнения?
Примерно, как-то так:
Код
for(;;) // бесконечный цикл в main
{
  unsigned char adc_data_l;
  adc_data_l =  read_adc(_код_канала_первого_датчика_);
  // ......... в этом месте анализ значения adc_data_l и выставляем параметры ШИМ первого вентилятора (не написанная часть 3 Вашей программы)....
  adc_data_l =  read_adc(_код_канала_второго_датчика_);
  // ......... в этом месте анализ значения adc_data_l и выставляем параметры ШИМ второго вентилятора....
}

Цитата(abitwise @ Apr 7 2011, 13:19) *
Не очень понял...
Подозреваю, что должно быть
Код
#define ADC_VREF_TYPE 0x40


Еще хочу обратить внимание на то, что использованная Вами функция read_adc возвращает только младшие 8 бит результата преобразования (измерения) АЦП, а в намётках третьей части Вашей программы для сравнения используются целые числа, бОльшие чем 255. Наверное, нужно внести изменения:
Код
unsigned int read_adc(unsigned char adc_input)
{
..............
  return ADC;
}

и, тогда
Код
for(;;) // бесконечный цикл в main
{
  unsigned  int  adc_data;
  adc_data =  read_adc(_код_канала_первого_датчика_);
  // .........
  adc_data =  read_adc(_код_канала_второго_датчика_);
  // .........
}
abitwise
С каждым вашим ответом, ситуация в голове проясняется всё больше.) Спасибо!
Цитата
Подозреваю, что должно быть
Код
#define ADC_VREF_TYPE 0x40

Схема питается от внешнего источника опорного напряжения, сигналы с датчиков поступает на входы ADC4 ADC5, выравнивание вправо, соответственно биты Ref1=0; Ref0=0. Бит ADLAR=0.
Палыч
Цитата(abitwise @ Apr 7 2011, 14:51) *
биты Ref1=0; Ref0=0
Тогда
Код
#define ADC_VREF_TYPE  0
Собственно - эта константа и содержит эти два бита

Цитата(abitwise @ Apr 7 2011, 14:51) *
сигналы с датчиков поступает на входы ADC4 ADC5
Параметр функции read_adc следует задавать 4 и 5 соответственно.

При разработке Вами части 3 программы, вероятно, следует предусмотреть гистерезис. Иначе, при температуре близкой к границе участка возможны "интересные" эффекты при управлении вентиляторами.
abitwise
Ясно! Уже учёл. Спасибо!

Вот код, который получился с учётом перечисленных выше поправок и подсказок, посмотрите, может быть найдёте ошибки (скорей всего_) ):
Код
/*****************************************************
Chip type           : ATmega16L
Program type        : Application
Clock frequency     : 8,00000 MHz
Memory model        : Small
External SRAM size  : 0
Data Stack size     : 256
*****************************************************/

#include <mega16.h>
#include <delay.h>

interrupt [TIM1_OVF] void timer1_ovf_isr(void) // Прерывание Т/С1 по переполнению
{
    OCR1A=0x00;
    OCR1B=0x00;
// TCNT1L=0x00;  
// PORTD=0b00110000;
}


interrupt [TIM1_COMPA] void timer1_compa_isr(void) // Прерывание Т/С1 по совпадению события А
{
   PORTD=0b00100000;
}

interrupt [TIM1_COMPB] void timer1_compb_isr(void) // Прерывание Т/С1 по совпадению события Б
{
   PORTD=0b00010000;
}


#define ADC_VREF_TYPE 0x00

unsigned char read_adc(unsigned char adc_input) // Считывание 8 МЗР
                                                                        // преобразования АЦП
{                                              
  ADMUX=adc_input | (ADC_VREF_TYPE & 0xff);
  delay_us(10);                                 // Задержка для стабилизации входного напряжения АЦП
  ADCSRA|=0b11100111;                           // Начало АЦП
  while ((ADCSRA & 0x10)==0);                   // Ожидание завершения преобразования
  ADCSRA|=0x10;                                 // Сброс фалга прерывания
  return ADCL;                                  // Возвращает значение 8 МЗР
}


                    
void main(void)
{
int temp_range[9] = {299, 255, 217, 184, 156, 132, 112, 95, 81};
  
PORTA=0x30; // Инициализация порта А;  
DDRA=0x00;  // PA4, РА5 - вход с подтянутым резистором

PORTB=0x00; // Инициализация порта Б
DDRB=0x00;

PORTC=0x00; // Инициализация порта С
DDRC=0x00;

PORTD=0x00; // Инициализация портп Д;  
DDRD=0x30;  // PD4, PD5 выходы с низким логическим уровнем;

TCCR0=0x00; // Инициализация Т/С0
TCNT0=0x00;
OCR0=0x00;


TCCR1A=0xA1; // Инициализация T/C1
TCCR1B=0x0B; // Инициализация T/C1
ICR1H=0x00;  // Тактирование системное
ICR1L=0x00;  // Частота: 8000,000 kHz
                     // Режим: Fast PWM top=00FFh
                     // OC1A : не инвертированный.
                     // OC1B : не инвертированный.
                     // Прерывание по переполнению
                     // Прерывание по совпадению А
                     // Прерывание по совпадению Б



ASSR=0x00;   // Инициализация Т/C2
TCCR2=0x00;
TCNT2=0x00;
OCR2=0x00;

             // Внешние прерывания отключены
              
MCUCR=0x00;
MCUCSR=0x00;

TIMSK=0x1C;  // Инициализация прерываний таймера счётчика

ACSR=0x80;   // Аналоговый компаратор выключен
SFIOR=0x00;  // АЦП в непрерывном режиме





ADMUX=0b00000000;    // Инициализация АЦП
ADCSRA=0b00000000;   // Частота тактирования 125,000 kHz
                                    // Источник питания: AREF pin
                                   // Используются только 8 МЗР


SPCR=0x40;   // Инициализация интерфейса SPI
SPSR=0x01;
#asm("sei")  // Рарешение прерываний

for (;;)     // Бесконечный цикл
{
   unsigned char adc_data_l;
  
   TCNT1H=0x00; // обнуление таймера счётчика на всякий случай
   TCNT1L=0x00;
  
   OCR1AH=0x00; // обнуление регистров сравнения на всякий случай
   OCR1AL=0x00;
   OCR1BH=0x00;
   OCR1BL=0x00;

   adc_data_l=0; // Обнуление переменной на всякий случай                        
  
   adc_data_l=read_adc(5); // Запись данных из АЦП с ноги PA5
  
   if(temp_range[0] <= adc_data_l)  // Процедура сравнения полученного значения АЦП
     {                                             // с температурными диапазонами и
      OCR1A=0;                              // выдача ШИМ на вентилятор
     };
   if(temp_range[1] <= adc_data_l)
     {
      OCR1A=0;
     };
   if(temp_range[2] <= adc_data_l < temp_range[1])
     {
      OCR1A=128;
     };
   if(temp_range[3] <= adc_data_l < temp_range[2])
     {
      OCR1A=149;
     };        
   if(temp_range[4] <= adc_data_l < temp_range[3])
     {
      OCR1A=170;
     };
   if(temp_range[5] <= adc_data_l < temp_range[4])
     {
      OCR1A=191;
     };
   if(temp_range[6] <= adc_data_l < temp_range[5])  
     {
      OCR1A=213;
     };
   if(temp_range[7] <= adc_data_l < temp_range[6])  
     {
      OCR1A=234;
     };
   if(temp_range[8] <= adc_data_l < temp_range[7])  
     {
      OCR1A=255;
     };  
  
   adc_data_l=read_adc(4); // Запись данных из АЦП с ноги PA4

   if(temp_range[0] <= adc_data_l)  // Процедура сравнения полученного значения АЦП
     {                                              // с температурными диапазонами и
      OCR1B=0;                               // выдача ШИМ на вентилятор
     };
   if(temp_range[1] <= adc_data_l)
     {
      OCR1B=0;
     };
   if(temp_range[2] <= adc_data_l < temp_range[1])
     {
      OCR1B=128;
     };
   if(temp_range[3] <= adc_data_l < temp_range[2])
     {
      OCR1B=149;
     };        
   if(temp_range[4] <= adc_data_l < temp_range[3])
     {
      OCR1B=170;
     };
   if(temp_range[5] <= adc_data_l < temp_range[4])
     {
      OCR1B=191;
     };
   if(temp_range[6] <= adc_data_l < temp_range[5])  
     {
      OCR1B=213;
     };
   if(temp_range[7] <= adc_data_l < temp_range[6])  
     {
      OCR1B=234;
     };
   if(temp_range[8] <= adc_data_l < temp_range[7])  
     {
      OCR1B=255;
     };      
};
}

По поводу 10 бит АЦП. Дело в том, что это программа являет собой решение в первом приближении, поэтому высокая точноть преобразования пока не нужна. Поэтому я пользуюсь только данными регистра ADCL. С гистерезисом обязательно подумаю.

















Палыч
Проверку на попадание в диапазон Вы записали в виде
Код
if(A <= X < B)
Такая запись не даст ожидаемый Вами результат. Следует исправить на такой вид
Код
if((A <= X) && (X < B))
XVR
Цепочка из if'ов - это жесть. wacko.gif Для кого в С циклы придумали?
abitwise
Можете предложить другой вариант решения ?
Цепочка if-в была выбрана по совету моего товарища, который сослался на то, что циклы типа while, for и др. могут подзагрузить процессор.
Палыч
Цитата(abitwise @ Apr 8 2011, 13:01) *
Цепочка if-в была выбрана по совету моего товарища, который сослался на то, что циклы типа while, for и др. могут подзагрузить процессор.
Если Вы описали все функции Вашего устройства, то, собственно, загрузка то и не велика, как не напиши программу... Цикл, возможно, будет выполняться несколько медленнее, но не для того кода, что привели Вы: в Вашем случае будет произведена проверка на попадание в каждый диапазон, когда (надеюсь, что это Вам понятно) значение переменной adc_data_l может лежать внутри только одного диапазона. В случае цепочки if, это решается так
Код
  if(temp_range[0] <= adc_data_l)  // Процедура сравнения полученного значения АЦП
     {                                             // с температурными диапазонами и
      OCR1A=0;                              // выдача ШИМ на вентилятор
     }
   else if(temp_range[1] <= adc_data_l)
     {
      OCR1A=0;
     }
   else if((temp_range[2] <= adc_data_l) && (adc_data_l< temp_range[1]))
     {
      OCR1A=128;
     }
   else if((temp_range[3] <= adc_data_l) && (adc_data_l < temp_range[2]))
................... и т.д.
В случае использования цикла - применяют оператор break, для того, чтобы не перебирать ненужные диапазоны, раз уж нашли подходящий диапазон.

Кстати, проверку в if в виде двух условий (типа А<=X<B), можно заменить на одно условие, беря в расчет то, что границы диапазонов расположены в массиве по убыванию (т.е. второе условие было проверено предыдущим оператором if)
Код
  if(temp_range[0] <= adc_data_l)  // Процедура сравнения полученного значения АЦП
     {                                             // с температурными диапазонами и
      OCR1A=0;                              // выдача ШИМ на вентилятор
     }
   else if(temp_range[1] <= adc_data_l)
     {
      OCR1A=0;
     }
   else if(temp_range[2] <= adc_data_l)
     {
      OCR1A=128;
     }
   else if(temp_range[3] <= adc_data_l)
................... и т.д.
При этом видно, что этот кусок программы хорошо "ложится" под цикл
Палыч
Ещё обратите внимание, что Ваша программа "затупит" при показаниях АЦП менее чем 81, поскольку отсутствует интервал от нуля до 81 (т.е. либо интервалы должны охватывать все возможные значения АЦП, либо должна быть ветвь программы выполнения неких действий, когда значение АЦП не попадает в заданные Вами интервалы).
abitwise
Спасибо. Уже обратил)
Буду пробовать прошивать...
По результатам обязательно отпишусь!
Благодарю за советы и помощь!
abitwise
Прошил атмегу. Но программа по заданному алгоритму не работает!
После подачи питания происходит включение вентилиторов на полную. На ножках PD4, PD5 никакого ШИМ-а нету, есть напряжение 12 В. Видимо просто выдаётся логическая еденица и всё.
Есть подозрения, что АЦП вообще не включается и ничего не преобразовывает. К сожалению контрольных точек на плате для того, чтобы осцилографом посмотреть, что на ногах PA4, PA5, нету.
Как вы думаете, в чём может быть дело???
Может быть дело в том, что я в программе неправильно или не в том месте инициализировал регистры ADCSRA и ADMUX ?
Насколько я понимаю, в теле бесконечного цикла for(;;) когда я записываю значение АЦП в переменную adc_data_l
Код
adc_data_l=read_adc(5); // Запись данных из АЦП с ноги PA5

должна вызываться функция read_adc() со значением параметра 5, и в ходе вызова должна произойти инициализация регистров ADCSRA и ADMUX.
Может быть я зря вот здесь:
Код
ADMUX=0b00000000;    // Инициализация АЦП
ADCSRA=0b00000000;   // Частота тактирования 125,000 kHz
                                    // Источник питания: AREF pin
                                   // Используются только 8 МЗР

установил все биты в ноль ?
Xenia
Цитата(abitwise @ Apr 7 2011, 21:37) *
По поводу 10 бит АЦП. Дело в том, что это программа являет собой решение в первом приближении, поэтому высокая точноть преобразования пока не нужна. Поэтому я пользуюсь только данными регистра ADCL.

Мне кажется, что даже в том случае, если вам достаточно данных младшего регистра ADCL, старший регистр ADCH вы читать тоже обязаны, если хотите, чтобы данные обновлялись.
Цитирую пункт 25.8.3.2 даташита:
Цитата
When ADCL is read, the ADC Data Register is not updated until ADCH is read. Consequently, if the result is left adjusted and no more than 8-bit precision is required, it is sufficient to read ADCH. Otherwise, ADCL must be read first, then ADCH.

Да и не сложное это дело, прочесть обе части регистра - читайте регистр ADC целиком, без указания H или L, и тогда компилятор прочтет оба байта. А присваивать полученное значение можете и однобайтной переменной - тогда старший байт обрежется.
нечитатель
"Высокая точность не нужна" = "интересуют только младшие биты"?
abitwise
Именно так. Достаточно битов регистра ADCL.

Xenia, спасибо. Надо будет попробовать.
Если я правильно понял, то нужно читать полый результат преобразования АЦП.
Код
#define ADC_VREF_TYPE 0x00

unsigned char read_adc(unsigned char adc_input) // Считывание 8 МЗР
                                                                        // преобразования АЦП
{                                              
  ADMUX=adc_input | (ADC_VREF_TYPE & 0xff);
  delay_us(10);                                 // Задержка для стабилизации входного напряжения АЦП
  ADCSRA|=0b11100111;                           // Начало АЦП
  while ((ADCSRA & 0x10)==0);                   // Ожидание завершения преобразования
  ADCSRA|=0x10;                                 // Сброс фалга прерывания
  return ADC;                                  // Возвращает значение преобразования АЦП
}

Эта часть понятна.
Мне не очень ясно каким образом отчечь два бита старшего байта АЦП!?
Если я напишу:
Код
adc_data_l=read_adc(5); // Запись данных из АЦП с ноги PA5

То два бита старшего байта никуда не денутся ??? Или они автоматически уберутся???
нечитатель
"Участники форума обязаны: ... 2.1в высказываться понятно, полно... в противном случае пост может быть расценен как текстовый мусор (флуд)".

Опорное напряжение = 5 вольт. Такому напряжению на входе соответствовал бы максимальный код на выходе.
На входе АЦП 2 вольта.
Входное напряжение / опорное напряжение = выходной код / максимальный код, с точностью до http://electronix.ru/forum/index.php?showtopic=88341.
Выходной код = максимальный код (двоичный) 11.1111.1111 Х входное 2 / опорное 5 = 01.1001.1001 (двоичный).

[***] Высокая точность не нужна. Интересуют только младшие биты. Именно так. Достаточно битов регистра ADCL.
01.1001.1001 ~ 01.1001.1001
409 ~ 153
2 вольта ~ 0.75 вольта

... каким образом отчечь два бита старшего байта АЦП!? goto [***]

А в рублях бы это выглядело как "зарплата тридцать тысяч (30000) приблизительно равна зарплате ноль (30000)", зато "почти одинаковы зарплаты 8300 и 65300".

---

Надо ждать, пока кто-нибудь выскажется наконец понятно и полно. Если ещё актуально.
Или начиная с 204 220 страницы читать.
Палыч
Цитата(abitwise @ Apr 10 2011, 16:00) *
Мне не очень ясно каким образом отчечь два бита старшего байта АЦП!? Если я напишу:
Код
adc_data_l=read_adc(5); // Запись данных из АЦП с ноги PA5

То два бита старшего байта никуда не денутся ??? Или они автоматически уберутся???
Зачем Вы старшие биты отсекаете? Если Вы собрались писать
Код
  return ADC; // Возвращает значение преобразования АЦП
то, уж, запишите
Код
unsigned int read_adc(unsigned char adc_input)
Измените также char на int и в описании переменной, которой присваивается измеренное значение
Xenia
Цитата(abitwise @ Apr 10 2011, 16:00) *
Мне не очень ясно каким образом отчечь два бита старшего байта АЦП!?
Если я напишу:
Код
adc_data_l=read_adc(5); // Запись данных из АЦП с ноги PA5

То два бита старшего байта никуда не денутся ??? Или они автоматически уберутся???

Да, автоматически уберутся. Здесь работает стандартное правило языка C при конверсии типов. При присваивании двухбайтового значения int однобайтовому unsigned char старшая часть числа усекается. Аналогично можно было спросить, куда девается дробная часть после присвоения числа пи (3.14...) переменной типа int или unsigned char. В этом случае тоже произодйет конверсия типа числа, сопровождающаяся усечением до целого.

Правда бывают компиляторы, которые в подобных случаях выдают предупреждение (Warning) "значащая часть числа потеряна", но его можно либо отменить, либо записать конверсию в явном виде:
return (unsigned char)ADC;

Сейчас у вас конверсия (усечение int до unsigned char) происходит при возврате значения из функции read_adc(). А если бы вы сделали эту функцию типа int (по совету Палыча), то тогда конверсия протекала бы по месту присваивания выдаваемого значения байтовой переменной, т.е. вот здесь:
adc_data_l=read_adc(5);
И если бы компиллятор выдавал здесь Warning, то лучше было бы выполнить конверсию явно:
adc_data_l=(unsigned char)read_adc(5);

Но это только тогда, когда Warning раздражает, а работать правильно будет и так.
abitwise
Нашёл решение данной проблемы.
В даташите написано:
Цитата
Consequently, if the result is left adjusted and no more than 8-bit precision is required, it is sufficient to read ADCH. стр 218


Буду делать так:
Код
#define ADC_VREF_TYPE 0x20                      // Выравнивание влеворî, AREF pin


unsigned char read_adc(unsigned char adc_input) // Считывание 8 СЗР
                                                // преобразования АЦП
{                                              
  ADMUX=adc_input | (ADC_VREF_TYPE & 0xff);    
  delay_us(10);                                 // Задержка для стабилизации входного напряжения АЦП
  ADCSRA|=0b11110111;                           // Начало АЦП
  while ((ADCSRA & 0x10)==0);                   // Ожидание завершения преобразования
  ADCSRA|=0x10;                                 // Сброс флага прерывания
  return ADCH;                                  // Возвращает значение 8СЗР
}
SysRq
Цитата(abitwise @ Apr 11 2011, 13:19) *
Код
  ADCSRA|=0b11110111;                           // Начало АЦП
Уберите Bit 5 – ADATE: ADC Auto Trigger Enable, иначе запросто получите данные оцифровки предыдущего канала.
Палыч
Цитата(abitwise @ Apr 11 2011, 13:19) *
Буду делать так
Коль решили довольствоваться восмью битами, то массив temp_range опишите как unsigned char
abitwise
Интересно. Я думал, что этот бит, совместно с комбинацией битов ADTS2:ADTS0 отвечают за режим преобразования.
Так, в книге "Микроконтроллеры AVR семейства Mega. Руководство пользователя" А.В. Евстифеева написано:"
Цитата
... Для выбора режима работы в этих моделях используется бит ADATE регистра ADCSRA и биты ADTS2:ADTS0 регистра SFIOR...
...Если бит ADATE сброшен в 0, АЦП работает в режиме одиночного преобразования. Если же бит ADATE установлен в 1, функционирование АЦП определяется содержимым битов ADTS2:ADTS0 согласно таблице...

У меня АЦП должен работать в непрерывном режиме. Поэтому, я следумаю написанному в книге поставил бит ADATE в 1, а биты ADTS2:ADTS0 в 0, потому что такая конфигурация соответствует непрерывному режиму преобразования АЦП.
Палыч
Цитата(abitwise @ Apr 11 2011, 13:56) *
Интересно. Я думал, что этот бит, совместно с комбинацией битов ADTS2:ADTS0 отвечают за режим преобразования.
За старт преобразования (измерения)... Поскольку у Вас переключаются каналы измерения, то и старт преобразования должен быть "привязан" к этому переключению
Xenia
Цитата(abitwise @ Apr 11 2011, 13:19) *
Нашёл решение данной проблемы.
В даташите написано:
Consequently, if the result is left adjusted and no more than 8-bit precision is required, it is sufficient to read ADCH. стр 218
Буду делать так:
return ADCH;

А вы не забыли переключить АЦП в режим "left adjusted"? А то по умолчанию биты прижаты к правому краю, и в ADCH находится не младшая, а старшая часть числа.
abitwise
Не забыл.
Код
#define ADC_VREF_TYPE 0x20
и
ADMUX=adc_input | (ADC_VREF_TYPE & 0xff)

Если я конечно правильно всё сделал_)
То, что в скобках даёт 0010 0000. То есть бит ADLAR я установил в 1. Далее всё зависит от adc_input. Если adc_input = 4, то ADMUX=00100100, если adc_input=5, то ADMUX=00100101.
Надеюсь, что я правильно понял ?!)))
Палыч
Цитата(abitwise @ Apr 11 2011, 14:36) *
Надеюсь, что я правильно понял ?
Правильно. А строчку
Код
ADCSRA|=0x10;                                 // Сброс флага прерывания
можно убрать, она лишняя - это делается и так двумя строками выше


Цитата(Xenia @ Apr 11 2011, 14:28) *
А то по умолчанию биты прижаты к правому краю, и в ADCH находится не младшая, а старшая часть числа.
Уточню: Ксения говорит о том, что в регистре ADCH всегда находится старшая часть результата, но по умолчанию (ADLAR=0) - два старших разряда, а при ADLAR=1 в этом регистре находятся восемь старших разрядов результата.
SysRq
Цитата(abitwise @ Apr 11 2011, 13:56) *
У меня АЦП должен работать в непрерывном режиме.
Ну и оставили бы код из генератора CodeVision целиком, он там так и работает. Либо не стоило вообще его брать за основу.
abitwise
Спасибо за уточнения. Эту часть материала я усвоил.
С учётом перечисленных замечаний, код получился такой

Код
/*****************************************************
This program was produced by the
CodeWizardAVR V1.25.5 Professional
Automatic Program Generator
© Copyright 1998-2007 Pavel Haiduc, HP InfoTech s.r.l.
http://www.hpinfotech.com

Project :
Version :
Date    : 03.04.2011
Author  : F4CG                            
Company : F4CG                            
Comments:


Chip type           : ATmega16L
Program type        : Application
Clock frequency     : 8,00000 MHz
Memory model        : Small
External SRAM size  : 0
Data Stack size     : 256
*****************************************************/


#include <mega16.h>
#include <delay.h>


interrupt [TIM1_OVF] void timer1_ovf_isr(void) // Прерывание по переполнению Т/C1
{
    OCR1A=0x00;
    OCR1B=0x00;
// TCNT1L=0x00;  
// PORTD=0b00110000;
}


interrupt [TIM1_COMPA] void timer1_compa_isr(void) // Прерывание по совпадению события А
{
   PORTD=0b00100000;
}

interrupt [TIM1_COMPB] void timer1_compb_isr(void) // Прерывания по совпадению события Б
{
   PORTD=0b00010000;
}



#define ADC_VREF_TYPE 0x20                      // Aref pin, выравнивание влево


unsigned int read_adc(unsigned char adc_input) // Считывание 8 СЗР
                                                // преобразования АЦП
{                                              
  ADMUX=adc_input | (ADC_VREF_TYPE & 0xff);    
  delay_us(10);                                 // Задержка для стабилизации входного напряжения АЦП
  ADCSRA|=0b11010111;                           // Начало АЦП, сброс флага прерывания
  while ((ADCSRA & 0x10)==0);                   // Ожидание завершения преобразования
  return ADCH;                                  // Возвращение 8 СЗР
}


                    
void main(void)
{
int temp_range[9] = {299, 255, 217, 184, 156, 132, 112, 95, 81};
  
PORTA=0x30; // Инициализация порта А  
DDRA=0x00;  // РА4, РА5 - входы с подключенным резистором

PORTB=0x00; // Инициализация порта Б
DDRB=0x00;

PORTC=0x00; // Инициализация порта С
DDRC=0x00;

PORTD=0x00; // Инициализация порта Д  
DDRD=0x30;  // PD4, PD5 выходы с низким логическим уровнем

TCCR0=0x00; // Инициализация Т/C0
TCNT0=0x00;
OCR0=0x00;


TCCR1A=0xA1; // Инициализация T/C1
TCCR1B=0x0B; //
ICR1H=0x00;  // Тактирование системное
ICR1L=0x00;  // Частота: 8000,000 kHz
             // Режим: Fast PWM top=00FFh
             // OC1A : не инвертирующий
             // OC1B : не инвертирующий
            
ASSR=0x00;   // Инициализация  Т/C2
TCCR2=0x00;
TCNT2=0x00;
OCR2=0x00;
              
MCUCR=0x00;
MCUCSR=0x00;

TIMSK=0x1C;  // Инициализация прерываний таймера счётчика

ACSR=0x80;   // Аналоговый компаратор выключен
SFIOR=0x00;  


ADMUX=0b00000000;    
ADCSRA=0b00000000;    
                    

SPCR=0x40;   // Инициализация SPI
SPSR=0x01;
#asm("sei")  // Разрешение прерываний

for (;;)    
{
   unsigned int adc_data_l;
  
   TCNT1H=0x00; //  Обнуление Т/С1
   TCNT1L=0x00;
  
   OCR1AH=0x00; // Обнуление регистров сравнения
   OCR1AL=0x00;
   OCR1BH=0x00;
   OCR1BL=0x00;

   adc_data_l=0; // Обнуление переменной                    
  
   adc_data_l=read_adc(5); // Запись данных из АЦП с PA5
  
   if(temp_range[0] <= adc_data_l)  // Процедура сравнения значения АЦП с
     {                              // температурными диапазонами и
      OCR1A=0;                      // выдача ШИМ на вентилятор
     }
   else if(temp_range[1] <= adc_data_l)
     {
      OCR1A=0;
     }
   else if((temp_range[2] <= adc_data_l) && (adc_data_l < temp_range[1]))
     {
      OCR1A=128;
     }
   else if((temp_range[3] <= adc_data_l) && (adc_data_l < temp_range[2]))
     {
      OCR1A=149;
     }        
   else if((temp_range[4] <= adc_data_l) && (adc_data_l < temp_range[3]))
     {
      OCR1A=170;
     }
   else if((temp_range[5] <= adc_data_l) && (adc_data_l < temp_range[4]))
     {
      OCR1A=191;
     }
   else if((temp_range[6] <= adc_data_l) && (adc_data_l < temp_range[5]))  
     {
      OCR1A=213;
     }
   else if((temp_range[7] <= adc_data_l) && (adc_data_l < temp_range[6]))  
     {
      OCR1A=234;
     }
   else if((temp_range[8] <= adc_data_l) && (adc_data_l < temp_range[7]))  
     {
      OCR1A=255;
     }
   else if(adc_data_l < temp_range[8])
     {
      OCR1A=255;
     }  
  
   adc_data_l=read_adc(4); // Запись данных АЦП с PA4

   if(temp_range[0] <= adc_data_l)  // Процедура сравнения значения АЦП с
     {                              // температурными диапазонами и
      OCR1B=0;                      // выдача ШИМ на вентилятор
     }
   else if(temp_range[1] <= adc_data_l)
     {
      OCR1B=0;
     }
   else if((temp_range[2] <= adc_data_l) && (adc_data_l < temp_range[1]))
     {
      OCR1B=128;
     }
   else if((temp_range[3] <= adc_data_l) && (adc_data_l < temp_range[2]))
     {
      OCR1B=149;
     }        
   else if((temp_range[4] <= adc_data_l) && (adc_data_l< temp_range[3]))
     {
      OCR1B=170;
     }
   else if((temp_range[5] <= adc_data_l) && (adc_data_l < temp_range[4]))
     {
      OCR1B=191;
     }
   else if((temp_range[6] <= adc_data_l) && (adc_data_l < temp_range[5]))  
     {
      OCR1B=213;
     }
   else if((temp_range[7] <= adc_data_l) && (adc_data_l < temp_range[6]))  
     {
      OCR1B=234;
     }
   else if((temp_range[8] <= adc_data_l) && (adc_data_l < temp_range[7]))  
     {
      OCR1B=255;
     }                
   else if(adc_data_l < temp_range[8])
     {
      OCR1B=255;
     }  
};
}


С типами данных у функции read_adc, массива temp_range и переменной adc_data_l ничего не напутал ? Всё верно ?
Палыч
Цитата(abitwise @ Apr 11 2011, 16:09) *
С типами данных у функции read_adc, массива temp_range и переменной adc_data_l ничего не напутал ? Всё верно ?

1. Вы, уж, определитесь: АЦП у Вас сколько разрядов выдаст? Всё привели к типу int; "резать" два бита - зачем? В чём выигрыш?
2. Сейчас только заметил: Вы ШИМ программно формируете. Почему не использовать аппаратные возможности?
3. В проверках (в операторах if) вторая часть условия (после &&) - лишняя: обеспечивается предыдущим оператором if
abitwise
1. Мне нужно 8 разрядов.
Насколько я понял, выйгрыш от этого только в частоте преобразования. Если нужна точноть, то диапазон от 50 до 200 кГц.
А если нет, то частоту можно повысить. Правда припоминаю, что у меня частота 125 кГц_).
2. В чём заключаются аппаратные возможности?
3. Убрал.

Палыч
Цитата(abitwise @ Apr 11 2011, 16:37) *
1. Мне нужно 8 разрядов.
Оставили бы тип unsigned char - транслятор сразу предупредил бы Вас о ошибке: в temp_range задано число больше чем 255...

Цитата(abitwise @ Apr 11 2011, 16:37) *
2. В чём заключаются аппаратные возможности?
Режим таймера: "Fast PWM". То же, что и у Вас, но - аппаратно...

P.S. Ага! У Вас - режим 5 "Fast PWM 8 bit"... Прерывания тогда зачем?
abitwise
Ясно. То есть, чтобы не было этого конфликта нужно, чтобы read_adc() temp_range, adc_data_l были одного типа? В данном случае unsigned char!?

В этом случае прерывания по совпадению не нужны и атмега сама выдаст на ноги PD4 и PD5 нужные сигналы !?
Палыч
Цитата(abitwise @ Apr 11 2011, 17:07) *
нужно, чтобы read_adc() temp_range, adc_data_l были одного типа?
Желательно...
Цитата(abitwise @ Apr 11 2011, 17:07) *
В этом случае прерывания по совпадению не нужны и атмега сама выдаст на ноги PD4 и PD5 нужные сигналы !?
Типа того... Прочитайте внимательнее этот раздел в DS. Выходы ШИМ: OC1A и OC1B - посмотрите с чем совмещены сами.
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.