Програмування та JTAG-налагодження мікроконтролера Atmega16 мовою C у середовищі IAR, частина 2

    
 
 

Введення

Т.к. попередня стаття викликала інтерес, то, як я і обіцяв, в цій статті будуть розглянуті приклади роботи з семисегментними індикаторами, вбудованим АЦП, а також проведена збірка програмного проекту цифрового термометра на ATmega16 з декількох розглянутих у цій і попередній статті прикладів роботи з внутрішніми периферійними блоками мікроконтролера ATmega16.
 
 

Теоретичні аспекти

Для кращого розуміння прикладів з АЦП рекомендую прочитати цю статтю . Для нашого випадку у всіх прикладах частота дискретизації буде дорівнює 10 Гц, що еквівалентно періоду дискретизації 100 мс. Т.к. ми використовуємо 10-бітний АЦП вбудований в мікроконтролер Atmega16, то кількість рівнів квантування буде 1024, що відповідає інтервалу значень від 0 до 1023 для кожного опитаного цифрового значення. Опорна напруга у всіх прикладах використовується зовнішнє і дорівнює 5 В. Вище опорного напруги на вході ми не побачимо, ну і нижче землі, відповідно, теж. Значить при напрузі на вході АЦП рівному 5 В ми отримаємо цифрове значення 1023, а при 0 В (земля) цифрове значення 0. При напрузі, скажімо 3,5 В, ми отримаємо цифрове значення рівне (3,5 / 5) = 716. Поправки на нелінійність, нульовий зсув в прикладах ми робити не будемо.
 
Для вимірювання температури використовується аналоговий датчик TMP36 . Це низьковольтний, прецизійний датчик температури, вихідна напруга якого прямо пропорційно температурі в шкалою Цельсія. Дане напруга ми і будемо оцифровувати за допомогою 10-бітного АЦП вбудованого в мікроконтроллер ATmega16.
 
 

Програмування та налагодження

Перший приклад являє собою висновок на трьохсимвольний семисегментний індикатор чисел в діапазоні від 000 до 999. Індикатори є статичними і управляються «0». Тобто можна сказати, що кожен окремий сегмент управляється, як окремий світлодіод. Кожен з трьох семисегментних індикаторів підключений до окремого порту (порти A, B, D). Підключення окремих сегментів до номерів висновків незалежно від порту однакові.
 
 
/*Подключим необходимые библиотеки*/
#include <ioavr.h>
#include <intrinsics.h>
#include <ina90.h>

/*Необходимые определения*/
//Частота тактирования
#define F_CPU 16000000
//Пользовательские типы
#define UCHAR unsigned char
#define UINT unsigned int
//Числа для семисегментника
//0
#define SEG_0    ~(0x3f) 
//1
#define SEG_1    ~(0x06) 
//2
#define SEG_2    ~(0x5b)
//3
#define SEG_3    ~(0x4F) 
//4
#define SEG_4    ~(0x66) 
//5
#define SEG_5    ~(0x6d) 
//6
#define SEG_6    ~(0x7d)
//7
#define SEG_7    ~(0x07)
//8
#define SEG_8    ~(0x7F) 
//9
#define SEG_9    ~(0x6F)
//A
#define SEG_A    ~(0x77)
//b
#define SEG_b    ~(0x7c)
//C
#define SEG_C    ~(0x39)
//d
#define SEG_d    ~(0x5e)
//E
#define SEG_E    ~(0x79)
//F
#define SEG_F    ~(0x71) 
//Битовая маска числа
#define SEG_MASK (0x7F)
//Порты трехзначного семисегметного
//индикатора
//Порт первой декады
#define SEG_1DEC_PORT  PORTD
//Порт второй декады
#define SEG_2DEC_PORT  PORTB
//Порт третьей декады
#define SEG_3DEC_PORT  PORTA
//Порт настройки первой декады
#define SEG_1DEC_DDR   DDRD
//Порт настройки второй декады
#define SEG_2DEC_DDR   DDRB
//Порт настройки третьей декады
#define SEG_3DEC_DDR   DDRA

/*Необходимые определения макрофункций*/
//Задержка в микросекундах
#define DELAY_US(us) 	__delay_cycles((F_CPU / 1000000) * (us));
//Задержка в милисекнудах
#define DELAY_MS(ms) 	__delay_cycles((F_CPU / 1000) * (ms));

//Макрофункция настройки портов сегментного индикатора
#define SEG_PORTS_INIT()   ( SEG_3DEC_DDR |= SEG_MASK );\
                           ( SEG_2DEC_DDR |= SEG_MASK );\
                           ( SEG_1DEC_DDR |= SEG_MASK );

//Макрофункция очистки портов сегментного индикатора
#define SEG_PORTS_CLEAR()  ( SEG_3DEC_PORT &=~ SEG_MASK );\
                           ( SEG_2DEC_PORT &=~ SEG_MASK );\
                           ( SEG_1DEC_PORT &=~ SEG_MASK );

//Макрофункция вывода значений на порты сегментного индикатора
#define SEG_PORTS_OUT(x,y,z)  ( SEG_3DEC_PORT |= ( x & SEG_MASK ) );\
                              ( SEG_2DEC_PORT |= ( y & SEG_MASK ) );\
                              ( SEG_1DEC_PORT |= ( z & SEG_MASK ) );
                               
   
/*Необходимые объявления глобальных переменных*/
//Числа для семисегментника
const unsigned char numbers[16] = 
{
     SEG_0, //0
     SEG_1, //1
     SEG_2, //2
     SEG_3, //3
     SEG_4, //4
     SEG_5, //5
     SEG_6, //6
     SEG_7, //7
     SEG_8, //8
     SEG_9, //9
     SEG_A, //A
     SEG_b, //b
     SEG_C, //C
     SEG_d, //d
     SEG_E, //E
     SEG_F  //F
     
};


//Переменная для отображения
UINT i = 0;

/*
** Name: Seg_Write()
** Description: Функция трехзначного числа на трехцифровой 
**              семисегментный статический индикатор
** Parameters: UINT dec3number 0 - 999 
** Returns: none
*/
void Seg_Write(UINT dec3number)
{     
   //Переменные для хранения трех декад
   UCHAR dec3 = 0 , dec2 = 0 , dec1 = 0;
   
   //Отбрасываем 4 и последующие декады
   //если число четырехзначное и более
   dec3number = dec3number % 1000;
   //Выделяем значение третьей декады
   dec3 = dec3number / 100;
   //Отбрасываем третью декаду
   dec3number = dec3number % 100;
   //Выделяем значение второй декады
   dec2 = dec3number / 10;
   //Выделяем значение первой декады
   dec1 = dec3number % 10;
   
   //Очищаем биты портов вывода
   //кроме старших битов
   SEG_PORTS_CLEAR();
   
   //Выводим числа 3-х декад
   //на индикаторы с сохранением
   //старших битов 
   SEG_PORTS_OUT(
                  numbers[dec3], //Третья декада
                  numbers[dec2], //Вторая декада
                  numbers[dec1]  //Первая декада
                );
        
}//end func



/*
** Name: main()
** Description: Главная функция программы, содержащая основной цикл
** Parameters: none
** Returns: none
*/
//Попадаем после сброса
void main( void )
{
  //Настраиваем регистры периферии
  
  //Настройка портов сегментного индикатора
  SEG_PORTS_INIT();
    
  //Вывести восьмерки
  Seg_Write(888);
  
  //Задержка в три секунды
  DELAY_MS(3000);
      
  //Обнуляем переменную для вывода
  i=0;
  
  //Основной бесконечный цикл
  for(;;)
  {
      //Выводим трехзначное число  
      Seg_Write(i);
      
      //Инкрементируем переменную
      i++;
      
      //Вышли за предел трехзначного числа?
      //обнуляем переменную для вывода
      if (i == 1000) i=0; 
      
      //Задержка в 100 мс
      DELAY_MS(100);

  }//end for    
    
}


 
Для виведення чисел використовуються hex-коди виду ~ (0x3f), кожен з яких відповідає певній десяткового цифрі в діапазоні 0-9. Для кожного такого коду зроблено визначення виду SEG_X, де X — це виводиться на індикацію цифра. Дані hex-коди зібрані в масив numbers [16] таким чином, що значення індексу масиву відповідає виведеної цифрі. Наприклад запис в порт третього елемента масиву numbers [3] забезпечує виведення на індикатор цифри 3.
 
Макрофункції SEG_PORTS_INIT () забезпечує настроювання використовуваних висновків портів A, B, D на вихід. Використовуються всі висновки портів крім восьмого. Макрофункції SEG_PORTS_CLEAR () здійснює очищення портів семисегментних індикаторів, скидаючи всі висновки в «0». Ця макрофункції разом з SEG_PORTS_OUT (x, y, z), яка здійснює запис значень у всі три порти через кон'юнкцію з маскою і диз'юнкцію, забезпечує безпечну запис значення в порт без псування старшого біта.
 
Для зручності виведення тризначних чисел використовується функція void Seg_Write (UINT dec3number), де dec3number — це виведене тризначне число. У функції здійснюється виділення кожній з трьох декад числа за допомогою операцій ділення на ціле / та операції отримання залишку від ділення%. Після чого значення кожної декади використовуються як індекс масиву numbers [decX], де decX — це одна з трьох декад, і записуються за допомогою з SEG_PORTS_OUT (x, y, z) в порт згідно своєї позиції в числі.
 
В основному циклі програми відбувається послідовний висновок значень змінної i, яка инкрементируется і обнуляється при виході за значення 999. Для аналізу цієї умови використовується оператор if. Виведення значень змінної i відбувається з інтервалом 100 мс, який забезпечує вже розглянута в попередній статті макрофункції затримки DELAY_MS (100).
 
Під налагодженням можна побачити послідовне инкрементирования змінної i, а також в які hex-коди перетворюються значення даної змінної за допомогою функції Seg_Write ().
 
 
 
  
  
Другий приклад це робота з вбудованим АЦП. Аналоговий сигнал заведений на його сьомий канал, вхід якого підключений відповідно до сьомого висновку порту A.
 
 

/*Подключим необходимые библиотеки*/
#include <ioavr.h>
#include <intrinsics.h>
#include <ina90.h>

/*Необходимые определения*/
//Необходимые определения
#define F_CPU 16000000
//Пользовательские типы
#define UINT unsigned int
/*Необходимые определения макрофункций*/
//Задержка в микросекундах
#define DELAY_US(us) 	__delay_cycles((F_CPU / 1000000) * (us));
//Задержка в миллисекнудах
#define DELAY_MS(ms) 	__delay_cycles((F_CPU / 1000) * (ms));

/*Необходимые объявления глобальных переменных*/
//Результат аналогового-цифрового преобразования
UINT  ADC_Result = 0;

/*Основная функция программы*/
//Попадаем после сброса
void main( void )
{
  //Настраиваем регистры периферии

  //Настройка светодиода
  //Настраиваем 7-й вывод порта D на выход
  DDRD_DDD7 = 1;
  //Устанавливаем 7-й вывод порта D в лог "0" 
  PORTD_PORTD7 = 0; 
  
  //Настройка АЦП
  //Настраиваем 7-й вывод порта A на вход
  DDRA_DDA7 = 0;
  
  //Выключение подтяжки на входе АЦП
  PORTA_PORTA7 = 0;
  
  ADMUX_MUX0 = 1;  //Седьмой канал
  ADMUX_MUX1 = 1;
  ADMUX_MUX2 = 1;
     
  ADCSRA_ADEN = 1;    //Включение АЦП  
   
  ADCSRA_ADPS0 = 1;   //Делитель частоты тактирования 16 МГц / 128 = 125кГц
  ADCSRA_ADPS1 = 1;   //Из даташита оптимальный диапазон 50-200кГц
  ADCSRA_ADPS2 = 1;   //Преобразование занимает 13 циклов частоты 125кГц 13 / 125000 = 104 мкс

    
  //Основной бесконечный цикл
  for(;;)
  {
      //Пустая операция для точки останова
     _NOP();

      //Переключение 7-го вывода порта D из "0" в "1" и из "1" в "0"
      //При помощи исключающего или     
      PORTD_PORTD7 ^= 1;
      
      //Запуск АЦ-преобразования
      ADCSRA_ADSC = 1;
       
      //Результат АЦ-преобразования готов?
      //Ожидание флага (104 мкс)
      while (ADCSRA_ADIF == 0);

      //Пустая операция для точки останова
      _NOP();

      //Считывание результата АЦ-преобразования
      ADC_Result = ADC;
      //либо
      //ADC_Result = ADCL;         //Младший байт результата
      //ADC_Result += (ADCH << 8); //Старший байт результата
      
      //Очищаем флаг
      ADCSRA_ADIF=1;
      
      //Задержка в 100 милисекунд (1/0,1 секунда = 10 Гц - частота дискретизации)
      DELAY_MS(100);

  }//end for
  
}//end main


 
У розглянутому прикладі у функції main () після налаштування світлодіода, вже розглянутого в попередній статті, здійснюється налаштування сьомого виводу порту D на вхід. Для цього біт DDD7 регістра керування DDRD скидається в «0». Після чого відбувається примусове відключення подтягивающего резистора на цьому висновку за допомогою скидання в «0» біта PORTA7 регістра даних PORTA.
 
Після чого за допомогою запису «1» в біти MUXx керуючого регістра ADMUX вибирається сьомий канал АЦП.
  
 
  
Далі включення АЦП здійснюється за допомогою установки біта ADEN керуючого регістра ADCSRA.
 
Біти ADPSx порту керуючого регістра ADCSRA встановлені в «1», щоб забезпечити роботу АЦП в оптимальному діапазоні частот 50-200кГц (як заявлено в даташіте). Біти ADPSx забезпечують значення предделітеля 128. Конкретне значення частоти при тактовій частоті 16 МГц буде 16 МГц/128 = 128 кГц.
  
  
 
 
Після всіх необхідних налаштувань відбувається циклічний послідовний запуск і опитування АЦ-перетворень за допомогою прапорів. Для індикації події роботи програми використовується світлодіод.
 
Запуск АЦ-перетворення відбувається за допомогою установки біта ADSC керуючого регістра ADСSRA. Готовність результату визначається установкою прапора ADIF в даному керуючому регістрі. При обраній частоті роботи АЦП одне перетворення буде триває 104 мкс. Після установки прапора ADIF результат АЦ-перетворення, діапазон якого 0 — 1023, може бути лічений з регістрів даних ADCL і ADCH. Результат АЦ-перетворення записується в змінну ADC_Result. Далі необхідно очистити прапор ADIF записом «1». Інтервал опитування дорівнює 100 мс, що еквівалентно частоті дискретизації 10 Гц.
 
Під налагодженням можна протестувати коректність налаштування АЦП, а також оцінити значення в кванти, одержуване в результаті АЦ-перетворення.
 
 
 
  
Третій приклад це теж опитування АЦП, але вже з використанням додаткових макрофункцій.
 
 

/*Подключим необходимые библиотеки*/
#include <ioavr.h>
#include <intrinsics.h>
#include <ina90.h>

/*Необходимые определения*/
//Необходимые определения
#define F_CPU 16000000
//Пользовательские типы
#define UINT unsigned int

//Определения портов для макрофункции работы
//со светодиодом
#define LED_DDR  DDRD
#define LED_PORT PORTD
#define LED_PIN  DDD7

//Определения портов для макрофункции работы с АЦП
#define ADC_IN_DDR       DDRA
#define ADC_IN_PORT      PORTA
#define ADC_IN_DDR_PIN   DDA7
#define ADC_IN_PORT_PIN  PORTA7

//Определения для выбора аналогового канала
#define ADC0    (0<<MUX2)|(0<<MUX1)|(0<<MUX0)
#define ADC1    (0<<MUX2)|(0<<MUX1)|(1<<MUX0)
#define ADC2    (0<<MUX2)|(1<<MUX1)|(0<<MUX0)
#define ADC3    (0<<MUX2)|(1<<MUX1)|(1<<MUX0)
#define ADC4    (1<<MUX2)|(0<<MUX1)|(0<<MUX0)
#define ADC5    (1<<MUX2)|(0<<MUX1)|(1<<MUX0)
#define ADC6    (1<<MUX2)|(1<<MUX1)|(0<<MUX0)
#define ADC7    (1<<MUX2)|(1<<MUX1)|(1<<MUX0)

//Определения для выбора делителя частоты АЦП
#define ADC_F_CPU_DIV_2      (0<<ADPS2)|(0<<ADPS1)|(1<<ADPS0)
#define ADC_F_CPU_DIV_4      (0<<ADPS2)|(0<<ADPS1)|(1<<ADPS0)
#define ADC_F_CPU_DIV_8      (0<<ADPS2)|(1<<ADPS1)|(0<<ADPS0)
#define ADC_F_CPU_DIV_16     (0<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
#define ADC_F_CPU_DIV_32     (1<<ADPS2)|(0<<ADPS1)|(0<<ADPS0)
#define ADC_F_CPU_DIV_64     (1<<ADPS2)|(1<<ADPS1)|(0<<ADPS0)
#define ADC_F_CPU_DIV_128    (1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)


/*Необходимые определения макрофункций*/
//Макрофункция инициализации светодиода
#define LED_INIT()   ( LED_DDR |= (1<<LED_PIN) );
//Погасить светодиод
#define LED_LOW()    ( LED_PORT &=~ (1<<LED_PIN) );
//Зажечь светодиод
#define LED_HIGH()   ( LED_PORT |= (1<<LED_PIN) );
//Мигание светодиода
#define LED_TOG()    ( LED_PORT ^= (1<<LED_PIN) );

//Макрофункция инициализации входа АЦП
#define ADC_IN_INIT()   ( ADC_IN_DDR  &= ~(0<<ADC_IN_DDR_PIN) );\
                        ( ADC_IN_PORT |= (0<<ADC_IN_PORT_PIN) ); 

//Макрофункция выбора канала
#define ADC_SET_CHAN(x)    ( ADMUX |= x );
//Макрофункция включения АЦП
#define ADC_ON()           ( ADCSRA |= (1<<ADEN) );
//Макрофункция выключения АЦП
#define ADC_OFF()          ( ADCSRA &=~ (1<<ADEN) );
//Выбор делителя частоты для АЦП
#define ADC_SET_CLK_DIV(x) ( ADCSRA |= x );
//Запуск АЦ-преобразования
#define ADC_START_CONV()   ( ADCSRA |= (1<<ADSC) );
//Проверка готовности результата АЦ-преобразования
#define ADC_RES_READY()    ( ADCSRA & (1<<ADIF) )
//Очистка флага готовности результата АЦ-преобразования
#define ADC_FLAG_CLEAR()   ( ADCSRA |= (1<<ADIF) );

//Задержка в микросекундах
#define DELAY_US(us) 	__delay_cycles((F_CPU / 1000000) * (us));
//Задержка в миллисекундах
#define DELAY_MS(ms) 	__delay_cycles((F_CPU / 1000) * (ms));

/*Необходимые объявления глобальных переменных*/
//Результат аналогового-цифрового преобразования
UINT  ADC_Result = 0;

/*Основная функция программы*/
//Попадаем после сброса
void main( void )
{
  //Настраиваем регистры периферии
  
  //Настройка светодиода
  LED_INIT();
   
  //Гасим светодиод
  LED_LOW();
  
  //Настройка АЦП
  //Инициализация входа АЦП
  ADC_IN_INIT();
       
  //Выбрали седьмой канал
  ADC_SET_CHAN(ADC7); 
     
  ADC_ON();    //Включение АЦП  
   
                                        //Делитель частоты тактирования 16 МГц / 128 = 125кГц
  ADC_SET_CLK_DIV(ADC_F_CPU_DIV_128);   //Из даташита оптимальный диапазон 50-200кГц
                                        //Преобразование занимает 13 циклов частоты 125кГц 13 / 125000 = 104 мкс


  //Основной бесконечный цикл
  for(;;)
  {
      //Пустая операция для точки останова
      _NOP();
 
      //Мигание светодиода
      LED_TOG();
      
      //Запуск АЦ-преобразования
      ADC_START_CONV();
      
      //Результат АЦ-преобразования готов?
      //Ожидание флага (104 мкс)
      while(!ADC_RES_READY());

      //Пустая операция для точки останова
      _NOP();

      //Считывание результата АЦ-преобразования
      ADC_Result = ADC;
      //либо
      //ADC_Result = ADCL;         //Младший байт результата
      //ADC_Result += (ADCH << 8); //Старший байт результата
      
      //Очищаем флаг
      ADC_FLAG_CLEAR();
      
      //Задержка в 100 миллисекунд (1/0,1 секунда = 10 Гц - частота дискретизации)
      DELAY_MS(100);

  }//end for
  
}//end main



 
Ініціалізація входу сьомого каналу АЦП здійснюється за допомогою макрофункції ADC_IN_INIT (). Визначення ADC_IN_DDR, ADC_IN_PORT, ADC_IN_DDR_PIN, ADC_IN_PORT_PIN забезпечують легке можливу зміну виводу і, відповідно, каналу АЦП.
 
Виставки бітів в регістрі вибору каналу ADMUX, при налаштуванні, здійснюється за допомогою макрофункції ADC_SET_CHAN (ADCx), аргумент якої ADCx визначає номер каналу, де x вибирається в діапазоні від 0 до 7-ми. Кожне визначення ADCx являє собою сукупність значень керуючих бітів MUXx, значення яких «0» і «1» відповідає обраному номеру каналу x.
  
Включення АЦП здійснюється макрофункції ADC_ON (). Вибір дільника частоти здійснюється за допомогою макрофункції ADC_SET_CLK_DIV (ADC_F_CPU_DIV_x), де аргумент вибирається з ряду визначень ADC_F_CPU_DIV_x, в якому x береться з набору 2,4,8,16,32,64,128 і відповідає обраному предделітеля тактової частоти мікроконтролера.
 
Запуск АЦ-перетворення здійснюється макрофункції ADC_START_CONV (). Готовність АЦ-перетворення визначається макрофункції ADC_RES_READY (). Очищення прапора за допомогою ADC_FLAG_CLEAR ().
 
Четвертий приклад являє собою опитування АЦП з використанням механізму переривань для фіксування події готовності замість прапорів.
 
 

/*Подключим необходимые библиотеки*/
#include <ioavr.h>
#include <intrinsics.h>
#include <ina90.h>

/*Необходимые определения*/
//Необходимые определения
#define F_CPU 16000000
//Пользовательские типы
#define UINT unsigned int
/*Необходимые определения макрофункций*/
//Задержка в микросекундах
#define DELAY_US(us) 	__delay_cycles((F_CPU / 1000000) * (us));
//Задержка в милисекнудах
#define DELAY_MS(ms) 	__delay_cycles((F_CPU / 1000) * (ms));

/*Необходимые объявления глобальных переменных*/
//Результат аналогового-цифрового преобразования
UINT  ADC_Result = 0;

/*Основная функция программы*/
//Попадаем после сброса
void main( void )
{
  //Настраиваем регистры периферии

  //Настройка светодиода
  //Настраиваем 7-й вывод порта D на выход
  DDRD_DDD7 = 1;
  //Устанавливаем 7-й вывод порта D в лог "0" 
  PORTD_PORTD7 = 0; 
  
  //Настройка АЦП
  //Настраиваем 7-й вывод порта A на вход
  DDRA_DDA7 = 0;
  
  //Включение подтяжки на входе АЦП
  PORTA_PORTA7 = 1;
  
  ADMUX_MUX0 = 1;  //Седьмой канал
  ADMUX_MUX1 = 1;
  ADMUX_MUX2 = 1;
     
  ADCSRA_ADEN = 1;    //Включение АЦП  
   
  ADCSRA_ADPS0 = 1;   //Делитель частоты тактирования 16 МГц / 128 = 125кГц
  ADCSRA_ADPS1 = 1;   //Из даташита оптимальный диапазон 50-200кГц
  ADCSRA_ADPS2 = 1;   //Преобразование занимает 13 циклов частоты 125кГц 13 / 125000 = 104 мкс

            
  ADCSRA_ADIE = 1;  //Разрешить прерывания от АЦП
 
  //Разрешить прерывания  
  _SEI();
    
  //Основной бесконечный цикл
  for(;;)
  {
      //Пустая операция для точки останова
     _NOP();

      //Переключение 7-го вывода порта D из "0" в "1" и из "1" в "0"
      //При помощи исключающего или     
      PORTD_PORTD7 ^= 1;
      
      //Запуск АЦ-преобразования
      ADCSRA_ADSC = 1;
       
      //Задержка в 100 миллисекунд (1/0,1 секунда = 10 Гц - частота дискретизации)
      DELAY_MS(100);

  }//end for
  
}//end main



/*Обработчик прерывания окончания АЦ-преобразования*/
#pragma vector=ADC_vect
__interrupt void ISR_ADC(void)
{
  //Пустая операция для точки останова
  _NOP();
   
  //Считывание результата АЦ-преобразования
  ADC_Result = ADC;
  //либо
  //ADC_Result = ADCL;         //Младший байт результата
  //ADC_Result += (ADCH << 8); //Старший байт результата

  
}//end func


  
Для того щоб включити переривання по готовності результату АЦ-перетворення необхідно встановити біт ADIE в регістрі управління ADCSRA. Після чого робимо глобальне вирішення всіх маскованих переривань макрофункції _SEI ().
 
 
 
Логіка роботи програми представляє вже описану в першій статті систему з суперциклу, де забезпечується квазіпараллельний робота основного циклу і обробника переривання. В основному циклі відбувається запуск АЦ-перетворення з періодом 100 мс. Через інтервал приблизно рівний 104 мкс основний цикл переривається на обробку переривання за подією готовності результату АЦ-перетворення. Обробка переривання здійснює за допомогою функції-обробника ISR_ADC (), яка викликається по даній події. Результат АЦ-перетворення копіюється з регістрів даних ADCL і ADCH в тілі функції обробника.
 
Під налагодженням можна протестувати коректність роботи підсистеми переривань для периферійного модуля АЦП.
 
 
 
  
Четвертий приклад являє собою симбіоз другого і третього прикладу. Це опитування АЦП за допомогою переривань із застосуванням макрофункцій.
 
 

/*Подключим необходимые библиотеки*/
#include <ioavr.h>
#include <intrinsics.h>
#include <ina90.h>

/*Необходимые определения*/
//Необходимые определения
#define F_CPU 16000000
//Пользовательские типы
#define UINT unsigned int

//Определения портов для макрофункции работы
//со светодиодом
#define LED_DDR  DDRD
#define LED_PORT PORTD
#define LED_PIN  DDD7

//Определения портов для макрофункции работы с АЦП
#define ADC_IN_DDR       DDRA
#define ADC_IN_PORT      PORTA
#define ADC_IN_DDR_PIN   DDA7
#define ADC_IN_PORT_PIN  PORTA7

//Определения для выбора аналогового канала
#define ADC0    (0<<MUX2)|(0<<MUX1)|(0<<MUX0)
#define ADC1    (0<<MUX2)|(0<<MUX1)|(1<<MUX0)
#define ADC2    (0<<MUX2)|(1<<MUX1)|(0<<MUX0)
#define ADC3    (0<<MUX2)|(1<<MUX1)|(1<<MUX0)
#define ADC4    (1<<MUX2)|(0<<MUX1)|(0<<MUX0)
#define ADC5    (1<<MUX2)|(0<<MUX1)|(1<<MUX0)
#define ADC6    (1<<MUX2)|(1<<MUX1)|(0<<MUX0)
#define ADC7    (1<<MUX2)|(1<<MUX1)|(1<<MUX0)

//Определения для выбора делителя частоты АЦП
#define ADC_F_CPU_DIV_2      (0<<ADPS2)|(0<<ADPS1)|(1<<ADPS0)
#define ADC_F_CPU_DIV_4      (0<<ADPS2)|(0<<ADPS1)|(1<<ADPS0)
#define ADC_F_CPU_DIV_8      (0<<ADPS2)|(1<<ADPS1)|(0<<ADPS0)
#define ADC_F_CPU_DIV_16     (0<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
#define ADC_F_CPU_DIV_32     (1<<ADPS2)|(0<<ADPS1)|(0<<ADPS0)
#define ADC_F_CPU_DIV_64     (1<<ADPS2)|(1<<ADPS1)|(0<<ADPS0)
#define ADC_F_CPU_DIV_128    (1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)


/*Необходимые определения макрофункций*/
//Макрофункция инициализации светодиода
#define LED_INIT()   ( LED_DDR |= (1<<LED_PIN) );
//Погасить светодиод
#define LED_LOW()    ( LED_PORT &=~ (1<<LED_PIN) );
//Зажечь светодиод
#define LED_HIGH()   ( LED_PORT |= (1<<LED_PIN) );
//Мигание светодиода
#define LED_TOG()    ( LED_PORT ^= (1<<LED_PIN) );

//Макрофункция инициализации входа АЦП
#define ADC_IN_INIT()   ( ADC_IN_DDR  &= ~(0<<ADC_IN_DDR_PIN) );\
                        ( ADC_IN_PORT |= (0<<ADC_IN_PORT_PIN) ); 

//Макрофункция выбора канала
#define ADC_SET_CHAN(x)     ( ADMUX |= x );
//Макрофункция включения АЦП
#define ADC_ON()            ( ADCSRA |= (1<<ADEN) );
//Макрофункция выключения АЦП
#define ADC_OFF()           ( ADCSRA &=~ (1<<ADEN) );
//Выбор делителя частоты для АЦП
#define ADC_SET_CLK_DIV(x)  ( ADCSRA |= x );
//Запуск АЦ-преобразования
#define ADC_START_CONV()    ( ADCSRA |= (1<<ADSC) );
//Включить прерывания от АЦП
#define ADC_INT_ON()        ( ADCSRA |= (1<<ADIE) ); 

//Задержка в микросекундах
#define DELAY_US(us) 	__delay_cycles((F_CPU / 1000000) * (us));
//Задержка в миллисекундах
#define DELAY_MS(ms) 	__delay_cycles((F_CPU / 1000) * (ms));

/*Необходимые объявления глобальных переменных*/
//Результат аналогового-цифрового преобразования
UINT  ADC_Result = 0;

/*Основная функция программы*/
//Попадаем после сброса
void main( void )
{
  //Настраиваем регистры периферии
  
  //Настройка светодиода
  LED_INIT();
   
  //Гасим светодиод
  LED_LOW();
  
  //Настройка АЦП
  //Инициализация входа АЦП
  ADC_IN_INIT();
       
  //Выбрали седьмой канал
  ADC_SET_CHAN(ADC7); 
     
  ADC_ON();    //Включение АЦП  
   
                                        //Делитель частоты тактирования 16 МГц / 128 = 125кГц
  ADC_SET_CLK_DIV(ADC_F_CPU_DIV_128);   //Из даташита оптимальный диапазон 50-200кГц
                                        //Преобразование занимает 13 циклов частоты 125кГц 13 / 125000 = 104 мкс

  ADC_INT_ON();  //Разрешить прерывания от АЦП
 
  //Разрешить прерывания  
  _SEI();
                
    
  //Основной бесконечный цикл
  for(;;)
  {
      //Пустая операция для точки останова
      _NOP();
 
      //Мигание светодиода
      LED_TOG();
      
      //Запуск АЦ-преобразования
      ADC_START_CONV();
      
      //Задержка в 100 миллисекунд (1/0,1 секунда = 10 Гц - частота дискретизации)
      DELAY_MS(100);

  }//end for
  
}//end main

/*Обработчик прерывания окончания АЦ-преобразования*/
#pragma vector=ADC_vect
__interrupt void ISR_ADC(void)
{
  //Пустая операция для точки останова
  _NOP();
   
  //Считывание результата АЦ-преобразования
  ADC_Result = ADC;
  //либо
  //ADC_Result = ADCL;         //Младший байт результата
  //ADC_Result += (ADCH << 8); //Старший байт результата

  
}//end func


 
Для включення переривань використовується макрофункції ADC_INT_ON ().
 
Розглянувши всі необхідні приклади перейдемо до цілого проекту цифрового термометра, зроблений на основі вже розглянутих прикладів роботи з внутрішніми периферійними блоками мікроконтролера.
 
 
/*Подключим необходимые библиотеки*/
#include <ioavr.h>
#include <intrinsics.h>
#include <ina90.h>
#include <stdbool.h>


/*Необходимые определения*/
//Частота тактирования
#define F_CPU 16000000
//Пользовательские типы
#define UCHAR unsigned char
#define UINT  unsigned int
#define FLOAT_TYPE float
//Определения портов для макрофункции работы
//с первым светодиодом
#define LED_DDR  DDRD
#define LED_PORT PORTD
#define LED_PIN  DDD7
//Определения для выбора частоты нулевого таймера
#define F_CPU_DIV_1    (0<<CS02)|(0<<CS01)|(1<<CS00)
#define F_CPU_DIV_8    (0<<CS02)|(1<<CS01)|(0<<CS00)
#define F_CPU_DIV_64   (0<<CS02)|(1<<CS01)|(1<<CS00)
#define F_CPU_DIV_256  (1<<CS02)|(0<<CS01)|(0<<CS00)
#define F_CPU_DIV_1024 (1<<CS02)|(0<<CS01)|(1<<CS00)
//Значение счетного регистра нулевого таймера
#define TCNT0_VALUE 99
//Предельное значение переменной счетчика тиков
#define T0_TICK_CNT_LIMIT 10
//Определения портов для макрофункции работы с АЦП
#define ADC_IN_DDR       DDRA
#define ADC_IN_PORT      PORTA
#define ADC_IN_DDR_PIN   DDA7
#define ADC_IN_PORT_PIN  PORTA7
//Определения для выбора аналогового канала
#define ADC0    (0<<MUX2)|(0<<MUX1)|(0<<MUX0)
#define ADC1    (0<<MUX2)|(0<<MUX1)|(1<<MUX0)
#define ADC2    (0<<MUX2)|(1<<MUX1)|(0<<MUX0)
#define ADC3    (0<<MUX2)|(1<<MUX1)|(1<<MUX0)
#define ADC4    (1<<MUX2)|(0<<MUX1)|(0<<MUX0)
#define ADC5    (1<<MUX2)|(0<<MUX1)|(1<<MUX0)
#define ADC6    (1<<MUX2)|(1<<MUX1)|(0<<MUX0)
#define ADC7    (1<<MUX2)|(1<<MUX1)|(1<<MUX0)
//Определения для выбора делителя частоты АЦП
#define ADC_F_CPU_DIV_2      (0<<ADPS2)|(0<<ADPS1)|(1<<ADPS0)
#define ADC_F_CPU_DIV_4      (0<<ADPS2)|(0<<ADPS1)|(1<<ADPS0)
#define ADC_F_CPU_DIV_8      (0<<ADPS2)|(1<<ADPS1)|(0<<ADPS0)
#define ADC_F_CPU_DIV_16     (0<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
#define ADC_F_CPU_DIV_32     (1<<ADPS2)|(0<<ADPS1)|(0<<ADPS0)
#define ADC_F_CPU_DIV_64     (1<<ADPS2)|(1<<ADPS1)|(0<<ADPS0)
#define ADC_F_CPU_DIV_128    (1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0)
//Числа для семисегментника
//0
#define SEG_0    ~(0x3f) 
//1
#define SEG_1    ~(0x06) 
//2
#define SEG_2    ~(0x5b)
//3
#define SEG_3    ~(0x4F) 
//4
#define SEG_4    ~(0x66) 
//5
#define SEG_5    ~(0x6d) 
//6
#define SEG_6    ~(0x7d)
//7
#define SEG_7    ~(0x07)
//8
#define SEG_8    ~(0x7F) 
//9
#define SEG_9    ~(0x6F)
//A
#define SEG_A    ~(0x77)
//b
#define SEG_b    ~(0x7c)
//C
#define SEG_C    ~(0x39)
//d
#define SEG_d    ~(0x5e)
//E
#define SEG_E    ~(0x79)
//F
#define SEG_F    ~(0x71) 
//Градус
#define SEG_GRAD ~(0x63) 

//Битовая маска числа
#define SEG_MASK (0x7F)
//Порты трехзначного семисегметного
//индикатора
//Порт первой декады
#define SEG_1DEC_PORT  PORTD
//Порт второй декады
#define SEG_2DEC_PORT  PORTB
//Порт третьей декады
#define SEG_3DEC_PORT  PORTA
//Порт настройки первой декады
#define SEG_1DEC_DDR   DDRD
//Порт настройки второй декады
#define SEG_2DEC_DDR   DDRB
//Порт настройки третьей декады
#define SEG_3DEC_DDR   DDRA

/*Необходимые определения макрофункций*/

//Макрофункция инициализации первого светодиода
#define LED_INIT()   ( LED_DDR |= (1<<LED_PIN) );
//Погасить первый светодиод
#define LED_LOW()    ( LED_PORT &=~ (1<<LED_PIN) );
//Зажечь первый светодиод
#define LED_HIGH()   ( LED_PORT |= (1<<LED_PIN) );
//Мигание первого светодиода
#define LED_TOG()    ( LED_PORT ^= (1<<LED_PIN) );

//Выбор делителя частоты тактирования
#define TIMER0_SET_CLK_DIV(x) ( TCCR0 |= x );
//Загрузка счетного регистра нулевого таймера
#define TIMER0_SET_CNT(x)     ( TCNT0 = x );
//Включение прерывания по переполнению нулевого таймера
#define TIMER0_OVF_INT_ON()   ( TIMSK|=(1<<TOIE0) ); 

//Макрофункция инициализации входа АЦП
#define ADC_IN_INIT()   ( ADC_IN_DDR  &= ~(0<<ADC_IN_DDR_PIN) );\
                        ( ADC_IN_PORT &= ~(0<<ADC_IN_PORT_PIN) ); 

//Макрофункция выбора канала
#define ADC_SET_CHAN(x)     ( ADMUX |= x );
//Макрофункция включения АЦП
#define ADC_ON()            ( ADCSRA |= (1<<ADEN) );
//Макрофункция выключения АЦП
#define ADC_OFF()           ( ADCSRA &=~ (1<<ADEN) );
//Выбор делителя частоты для АЦП
#define ADC_SET_CLK_DIV(x)  ( ADCSRA |= x );
//Запуск АЦ-преобразования
#define ADC_START_CONV()    ( ADCSRA |= (1<<ADSC) );
//Включить прерывания от АЦП
#define ADC_INT_ON()        ( ADCSRA |= (1<<ADIE) ); 

//Макрофункция настройки портов сегментного индикатора
#define SEG_PORTS_INIT()   ( SEG_3DEC_DDR |= SEG_MASK );\
                           ( SEG_2DEC_DDR |= SEG_MASK );\
                           ( SEG_1DEC_DDR |= SEG_MASK );

//Макрофункция очитки портов сегментного индикатора
#define SEG_PORTS_CLEAR()  ( SEG_3DEC_PORT &=~ SEG_MASK );\
                           ( SEG_2DEC_PORT &=~ SEG_MASK );\
                           ( SEG_1DEC_PORT &=~ SEG_MASK );

//Макрофункция вывода значений на порты сегментного индикатора
#define SEG_PORTS_OUT(x,y,z)  ( SEG_3DEC_PORT |= ( x & SEG_MASK ) );\
                              ( SEG_2DEC_PORT |= ( y & SEG_MASK ) );\
                              ( SEG_1DEC_PORT |= ( z & SEG_MASK ) );
                               
//Задержка в микросекундах
#define DELAY_US(us) 	__delay_cycles((F_CPU / 1000000) * (us));
//Задержка в миллисекундах
#define DELAY_MS(ms) 	__delay_cycles((F_CPU / 1000) * (ms));

/*Необходимые объявления глобальных переменных*/
//Объявление счетчика тиков для таймера T0
UINT  T0_tick_cnt=0;
//Числа для семисегментника
const unsigned char numbers[16] = 
{
     SEG_0, //0
     SEG_1, //1
     SEG_2, //2
     SEG_3, //3
     SEG_4, //4
     SEG_5, //5
     SEG_6, //6
     SEG_7, //7
     SEG_8, //8
     SEG_9, //9
     SEG_A, //A
     SEG_b, //b
     SEG_C, //C
     SEG_d, //d
     SEG_E, //E
     SEG_F  //F
     
};


//Переменная для хранения температуры
FLOAT_TYPE T = 0;

//Результат аналогового-цифрового преобразования
UINT  ADC_Result = 0;

//Счетчик подсчитывающий кол-во АЦ-преобразований
UINT  ADC_res_cnt = 0;

//Сумма для фильтрации
UINT  Sum = 0;

//Флаг готовности результат АЦ-преобразования
bool       ADCReadyFlag = false;



/*
** Name: Seg_Write()
** Description: Функция вывода значения температуры на трехцифровой 
**              семисегментный статический индикатор
** Parameters: UINT T - выводимая температрура 0 - 99 C
** Returns: none
*/
void Seg_Write(UINT T)
{
   //Переменные для хранения трех декад
   UINT dec2 = 0 , dec1 = 0;
   
   //Отбрасываем третью и последующие декады
   T = T % 100;
   //Вторая декада температуры
   dec2 = T / 10;
   //Первая декада температуры
   dec1 = T % 10;
        
   //Очищаем биты портов вывода
   //кроме старших битов
   SEG_PORTS_CLEAR();
   
   //Выводим числа 3-х декад
   //на индикаторы с сохранением
   //старших битов 
   SEG_PORTS_OUT(
                  numbers[dec2], //Вторая декада
                  numbers[dec1], //Первая декада
                  SEG_GRAD       //Градус
                );
        
}//end func

/*
** Name: main()
** Description: Главная функция программы, содержащая основной цикл
** Parameters: none
** Returns: none
*/
//Попадаем после сброса
void main( void )
{
  //Настраиваем регистры периферии
  
  //Настройка портов сегментного индикатора
  SEG_PORTS_INIT();
  
  //Настройка светодиода
  LED_INIT();

  //Гасим светодиод
  LED_LOW();
  
  //Настройка таймера (режим Normal)
  TIMER0_SET_CLK_DIV(F_CPU_DIV_1024);//  Частота тактирования 16 000 000 Гц
                                    //  16 000 000 Гц / 1024 = 15 625 Гц
                                    //  1 / 15 625 Гц = 0,000064 с =64 мкс
  TIMER0_SET_CNT(TCNT0_VALUE); // 156 * 0,000064 c = 0,009984 c (10 мс) 
                               // тогда начальное значение счетного регистра 255-156 = 99
  TIMER0_OVF_INT_ON(); // Включить прерывание таймера по переполнению
  //Настройка АЦП
  //Инициализация входа АЦП
  ADC_IN_INIT();
  
  //Выбрали седьмой канал
  ADC_SET_CHAN(ADC7); 
  
  ADC_ON();    //Включение АЦП  
                                        //Делитель частоты тактирования 16 МГц / 128 = 125кГц
  ADC_SET_CLK_DIV(ADC_F_CPU_DIV_128);   //Из даташита оптимальный диапазон 50-200кГц
                                        //Преобразование занимает 13 циклов частоты 125кГц 13 / 125000 = 104 мкс
  ADC_INT_ON();  //Разрешить прерывания от АЦП
  
  
  //Обнуляем переменную для вывода
  T=0;
  //Обнуляем переменную суммы
  Sum = 0;
  
  //Разрешить прерывания
  _SEI();
  
  //Основной бесконечный цикл
  for(;;)
  {
      //Пустая операция для точки останова
      _NOP();      
      //Если АЦ-преобразование окончено
      //Обработать результат
      if(ADCReadyFlag)
      {
          //Пустая операция для точки останова
          _NOP();    
          //Сбрасываем флаг
          ADCReadyFlag = false;
          //Мигание светодиода
          LED_TOG(); 
          //Суммируем результат
          //АЦ-преобразования
          Sum += ADC_Result;
          //Инкрементируем счетчик
          //АЦ-преобразований
          ADC_res_cnt++;
          //Прошло восемь АЦ-преобразований?
          if ( ADC_res_cnt == 8 )
          {
              //Пустая операция для точки останова
              _NOP(); 
              //Обнуляем счетчик
              ADC_res_cnt = 0;
              //Получаем отфильтрованный результат
              ADC_Result = Sum / 8;
              //Обнуляем сумму
              Sum = 0;
              //Переводим кванты в температуру
              T = ADC_Result*(5.0/1023); // преобразовать в вольты
              T = ((T-0.75)*100)+25; // преобразовать в градусы
              //Выводим температуру
              Seg_Write((UINT)T);
          }//end if 
      }//end if
  }//end for     
}//end main


/*Обработчик прерывания таймера T0
по переполнению*/
#pragma vector=TIMER0_OVF_vect
__interrupt void ISR_TickTimer(void)
{
   
  //Пустая операция для точки останова
  _NOP();
  
  //Нарастить счетчик тиков таймера T0
  T0_tick_cnt++;
  
  //Если отсчитали 100 миллисекунд
  if (T0_tick_cnt >= T0_TICK_CNT_LIMIT)
  {
      //Пустая операция для точки останова
      _NOP();
  
      //Обнулить счетчик тиков таймера T0
      T0_tick_cnt=0;
      
      //Мигание второго светодиода
      //Запуск АЦ-преобразования
      ADC_START_CONV();
  }//end for
  
  //Выставляем начальное значение
  //в счетном регистре
  TIMER0_SET_CNT(TCNT0_VALUE);

}//end func


/*Обработчик прерывания окончания АЦ-преобразования*/
#pragma vector=ADC_vect
__interrupt void ISR_ADC(void)
{
  //Пустая операция для точки останова
  _NOP();
   
  //Считывание результата АЦ-преобразования
  ADC_Result = ADC;
  //либо
  //ADC_Result = ADCL;         //Младший байт результата
  //ADC_Result += (ADCH << 8); //Старший байт результата

  //Установить флаг окончания АЦ-преобразования
  ADCReadyFlag = true;
  
}//end func



 
У частині налаштування периферійних регістрів використовуються вже розглянуті раніше в прикладах макрофункції настройки світлодіода, апаратного таймера і АЦП.
 
Логіка роботи програми зосереджена в квазіпараллельний роботі основного циклу і двох обробників переривань тикового апаратного таймера і АЦП. У обробнику переривання тикового таймера фіксується подія проходження інтервалу 100 мс запускається АЦ-перетворення за допомогою макрофункції ADC_START_CONV (). Через інтервал приблизно рівний 104 мкс спрацьовує переривання по готовності результату АЦ-перетворення. У тілі обробнику ISR_ADC () відбувається запис результату АЦ-перетворення в змінну ADC_Result і встановлюється прапор готовності ADCReadyFlag типу bool (для використання даного типу попередньо була підключена бібліотека stdbool.h директивою # include).
 
Подія установки даного прапора фіксується в основному циклі оператором if. Після чого прапор скидається а результат АЦ-перетворення ADC_Result підсумовується в змінну Sum, поки не буде підсумовані вісім результатів АЦ-перетворень. Коли вісім результатів отримано і підсумовано, сума у ​​змінній Sum ділиться на вісім. Таким чином реалізується простий варіант цифрової фільтрації і зменшується шум молодших бітів через галасливу харчування по USB на додаток до зовнішніх схемами фільтрації.
 
Відфільтрований результат записується в змінну ADC_Result, а змінна Sum обнуляється. Далі отриманий відфільтрований результат в кванти переводиться в вольти за допомогою простих арифметичних дій. Т.к. опорна напруга одно 5 В, а максимальне значення в кванти відповідне йому одно 1023, то щоб здійснити переказ в вольти нашого значення в кванти досить поділити його на 1023 і помножити на 5. Результуюче дробове значення ми збережемо в матеріальному типі. Далі, маючи під рукою даташит на TMP36 і знаючи, що при 25 градусах на виході має бути 750 мВ і, що кожен градус вгору або вниз відповідає 10 мВ, легко перевести дробовий результат в вольтах в градуси за допомогою наступного виразу T = ( (T-0.75) * 100) +25. Далі значення температури в градусах виводиться на семисегментні індикатори за допомогою функції void Seg_Write (UINT T), яка на відміну від першого прикладу виводить двозначні числа з символом градуси. Логіка виділення декад числа, що виводиться на індикацію, така ж.
 
Під налагодженням можна простежити всі описану логіку роботи програми, а також коректність розрахунку як значень проміжних змінних, так і кінцевого результату.
 
 
 
  
  
 

Висновок

Якщо цей пост викличе інтерес, то в наступній частині ми розглянемо нові приклади на новій спеціально розробленої платі.
  

    
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.