Программирование и 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), которая в отличие от первого примера выводит двузначные числа с символом градуса. Логика выделения декад числа, выводимого на индикацию, такая же.

Под отладкой можно проследить все описанную логику работы программы, а также корректность расчета как значений промежуточных переменных, так и конечного результата.

Заключение

Если этот пост вызовет интерес, то в следующей части мы рассмотрим новые примеры на новой специально разработанной плате.

Данная тема интересна?

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Никто ещё не голосовал. Воздержавшихся нет.

ссылка на оригинал статьи http://habrahabr.ru/post/226539/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *