Прогаммирование и JTAG-отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

в 14:25, , рубрики: Без рубрики

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

Введение

По моему мнению, чтобы быстро научится программировать практически любой микроконтроллер, существующий в мире, нужно освоить язык C и пользоваться JTAG-отладкой, конечно, помимо изучения технической документации. Поясню свою мысль. Компиляторы языка C существуют практически для всех существующих микроконтроллеров. Поэтому язык С давно зарекомендовал себя, как кроссплатформенный ассемблер. Его знание освобождает от необходимости изучения ассемблерных команд для каждого нового семейства микроконтроллеров. JTAG-отладка, в свою очередь, обеспечивает не только возможность внутрисхемного поиска ошибок, но и помогает изучать микроконтроллер изнутри. Я думаю, что для всех очевиден тот факт, что при просто программировании без отладки даже простых микроконтроллеров мы подходим к изучению системы, как к черному ящику с входами и выходами. Такой подход, особенно на начальном этапе, затрудняет обучение. C другой стороны JTAG-отлдака позволяет забраться во внутрь, посмотреть как выполняется программа по шагам, посмотреть, что происходит в памяти и регистрах, запустить волнение до точек останова, выполнять дизассемблированный вариант программы. Эта возможность позволяет значительно ускорить обучение.

Такие микроконтроллеры как AVR, STM8, MSP430, AVR32, STM32, EFM32, Renesas RX имеют компиляторы языка С и возможность отладки при помощи интерфейса JTAG. Единой кроссплатформенной средой для этих многих других микроконтроллеров является Embedded Workbench. Хоть среда и платная, но есть возможность использовать 30-дневную бесплатную версию или версию с ограничением по размеру кода. Для начального изучения нового семейства месяца может быть вполне достаточно. После чего можно составить свое мнение о семействе и решить продолжить с ним работу на бесплатных инструментах, либо для небольших проектов пользоваться версией ограниченной по размеру кода. Для больших коммерческих продуктов можно и приобрести данную среду.

Также я считаю, что начинать изучение лучше с семейств AVR, STM8, MSP430. Работу этих микроконтроллеров проще понять, потому что они имеют сравнительно простую систем команд, небольшое количество регистров процессора и периферии. Начинать лучше с написания простых примеров, постепенно переходя к написанию своих библиотек. Все это хорошо охватывается головой. Для первого знакомства это очень удачно.
Для примеров, рассмотренных далее, я выбрал AVR, потому что это наиболее популярное в микроконтроллерном мире семейство. Я думаю, что с него проще начать, но не стоит им ограничиваться.

Выбор JTAG-отладчика

Для внутрисхемной отладки написанных на языке C программ нам потребуется JTAG-отладчик для микроконтроллеров AVR. Самыми доступными по цене и простыми в изготовления являются клоны фирменного отладчика AVR JTAGICE. Такой клон можно заказать по небольшой цене с eBay. Качество здесь, как лотерея.

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

Также клоны производит, например, фирма Olimex. Есть классический вариант, подключаемый к компьютеру через COM-порт и более новый вариант, где подключение осуществляется по интерфейсу USB.

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

При личном изготовлении такого клона, если нет возможности сделать свою печатную плату, можно использовать только DIP-компоненты, что упростит монтаж. Ранняя версия Evertool, который содержит в себе клон JTAGICE, была сделана именно так.

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1]

В основе большинства клонов JTAGICE лежит схема, изображенная на рисунке снизу. Точнее часть, которая называется JTAGICE section. Ядром отладчика является микроконтроллер Atmega16L. Если хотим получить подключение клона JTAGICE по USB, то меняем микросхему преобразования уровней MAX3232 на микросхему FTDI FT232. Однако FT232 в DIP-корпусе не выпускают, а цены на DIP-модули с данной микросхемой довольно высоки. Поэтому тут уже одной монтажной платой и DIP-компонентами не отделаешься. Придется либо, как вариант, припаять FT232 тонкими проводами к макетке, либо вытравить хлорным железом однослойную печатную плату с нормальным посадочным местом.

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

Прошивку можно найти в папке установки AVR Studio 4 или скачать здесь.
Недостатком клонов JTAGICE, описанных выше, является то, что в AVR Studio 6 они не поддерживаются. Однако в старой версии AVR Studio 4 и старых и новых версиях IAR для AVR данные клоны прекрасно поддерживаются.
Для отладки примеров кода на C, написанных далее, я пользовался клоном, сделанным по данной схеме. Микросхема MAX3232 в нем замена на ADM3202, что сути не меняет.

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

Данный клон JTAGICE достался мне по наследству. Я нашел в коробке с радиотехническим мусором на работе. Он не работал, и я решил его перепрошить. Для чего потребовалось сделать специальный переходник, потому что, как видно на фото, разъем AVR ISP для программирования Atmega16L не выведен для уменьшения габаритов. После прошивки JTAGICE заработал и обрел «загробную» жизнь.
Сейчас, когда COM-порт стал немодным, я не стал бросать «старого друга» и купил кабель-преобразователь USB в RS-232.

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

Отладочная плата

В качестве отладочной платы я буду использовать цифровой термометр, собранный мной на макетной плате. Питается плата по USB, ядром является микроконтроллер Atmega16 в DIP-корпусе. Микроконтроллер тактируется от кварцевого резонатора частотой 16 МГц и обвязан минимально необходимым набором пассивных компонентов (конденсаторы по 22 пФ для кварца, резистор 10 кОм на подтяжку линии Reset к питанию, конденсаторы 0,1 мкФ по питанию). Для программирования и отладки микроконтроллера выведены два стандартных разъема шестиконтактный AVR ISP и десятиконтактный AVR JTAG. На плате есть аналоговый температурный датчик, статический семисегментный индикатор на три цифры, дополнительный светодиод. Индикационный светодиод и сегменты индикатора подключены к микроконтроллеру через токоограничивающие резисторы по 500 Ом. Каждый из трех семисегментных индикаторов подключен к выводам с нулевого по шестой одного из портов A, B и D. Индикационный светодиод подключен к 7-му выводу порта D. Выход аналогового датчика подключен к 7-му выводу порта A (7-й канал встроенного АЦП).

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

Все элементы были спаяны проводом МГТФ.

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

Среда разработки IAR

Для написания и отладки программ будем использовать среду IAR Embedded Workbench for Atmel AVR, а именно бесплатную ее версию с ограничением по размеру кода в 4 Кб. Для небольших проектов на языке С для микроконтроллеров семейства AVR этого вполне достаточно.
Зайдем сюда и почитаем, что обеспечивает последняя версия IAR для AVR на текущий момент времени. Отладчик JTAGICE, о котором говорилось выше, как и раньше, поддерживается.

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

В списке поддерживаемых семейств есть megaAVR.

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

Убедившись, что наш JTAG-отладчик и отладочная плата пойдут, скачаем IAR.

Установщик позволяет зарегистрироваться с лицензией Kickstart.

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

После установки, чтобы зарегистрировать IAR, придется заполнить анкету регистрации.

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

После заполнения анкеты на вашу почту придет ключ, введя который, мы и получим бесплатную зарегистрированную Kickstart-версию не ограниченную по времени.

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

Создадим пустой проект для наших задач. Мы выбираем язык C и вариант AVR Studio 4 compatible output, на случай если придется зашивать выходной hex-файл средствами AVR Studio 4.

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

После выбора языка перед нами появится совсем пустой почти пустой source-файл.

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

Перейдем к настройкам нашего проекта. Т.к. мы будем писать программы для Atmega16, выберем данный микроконтроллер в графе Processor configuration.

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

В пункте меню Debugger выберем JTAGICE.

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

В пункте меню Debugger-> JTAGICE выберем номер COM-порта, к которому подключен отладчик.

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

Для корректной работы определений значащих битов регистров периферии микроконтроллера в библиотеках среды также необходимо поставить
галочку Enable bit definitions in I/O-Include files в пункте меню General Options->System.

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

Программирование и отладка

Прежде чем приступить к описанию примеров, нужно объяснить некоторые особенности среды IAR. В заголовочном файле iom16.h, который содержит определения адресов регистров для Atmega16, есть макросы, которые позволяют обращаться к конкретным битам регистров внутренней периферии, следующим образом:

...
 * Examples of how to use the expanded result:
 * TCCR2 |= (1<<5); 
 * or if ENABLE_BIT_DEFINITIONS is defined   
 * TCCR2 |= (1<<COM21);
 * or like this:
 * TCCR2_Bit5 = 1;
 * or like this:
 * TCCR2_COM21 = 1;
 ***************************************************************************/

По сути можно использовать любой из этих вариантов. И выбор здесь, по сути, состоит в удобстве использования конкретного варианта и простоте последующего понимания написанного.
В среде IAR есть возможность использования встроенной функции задержки __delay_cycles(x), где x — это время задержки в тактах. Для нашего случая задержка на один такт это 1/16000000 = 62,5 нс.
Теперь, когда мы знаем эти два момента, можно двинуться к первому примеру, где происходит просто мигание светодиодом, при помощи исключающего или.



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


/*Необходимые определения*/
//Частота тактирования
#define F_CPU 16000000

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

/*Основаная функция программы*/
//Попадаем после сброса
void main( void )
{
  //Настравиеваем регистры периферии
  
  //Настройка светодиода
  //Настраиваем 7-й вывод порта D на выход
  DDRD_DDD7 = 1;
  //Устанавливаем 7-й вывод порта D в лог "0" 
  PORTD_PORTD7 = 0;

  
  //Основной бесконечный цикл
  for(;;)
  {
      //Переключение 7-й вывода порта D из "0" в "1" и из "1" в "0"
      //При помощи исключающего или
      PORTD_PORTD7 ^= 1;
      
      //Задержка в одну секунду
      DELAY_MS(1000);
        
  }//end for
  
}

Для упрощения формирования задержки требуемой длительности созданы макрофункции задержки DELAY_US(us) и DELAY_MS(ms), которые позволяют задавать задержку не в тактах, а сразу в микросекундах и миллисекундах. Далее перейдем в основную функцию программы main(), в которую мы попадаем после включения микроконтроллера или после сброса. Логично, что первое, что мы должны сделать после включения это настроить необходимые для работы периферийные блоки в нашем случае это только один вывод порта D. Чтобы настроить его на вывод достаточно установить бит DDD7 регистра направления данных DDRD в единицу. Для того, чтобы выставить на ноге «1» (5 В) необходимо установить бит PORTD7 в регистре данных PORTD, для того чтобы выставить на ноге «0» (земля) необходимо сбросить бит PORTD7. В даташите можно почитать об этом подробнее.

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

Для установки и записи битов управляющих регистров DDRD и PORTD нежелательно использовать просто запись (DDRD = 0x01, DDRD = 0x00) без побитовой дизъюнкции и конъюнкции (DDRD |= 0x01, DDRD &= ~0x01, DDRD |= (1<<0), DDRD &= ~(1<<0)), потому что если это стереть остальные биты в регистре, которые были установлены до данной операции. Важно это понимать.
После настройки мы переходим в основной бесконечный цикл for(;;), в котором, как правило, выполняется основная логика программы. Такая модель реализации программы микроконтроллера называется система с суперциклом. Данный бесконечный цикл приостанавливается на время обработки прерываний периферийных блоков и при переходе в режим пониженного энергопотребления, а останавливается только при сбросе и выключении питания микроконтроллера.
В основном бесконечном цикле происходит постоянное переключение «0» в «1», затем «1» в «0», при помощи побитовой операции исключающего или. Происходит это с задержкой в одну секунду, которую обеспечивает макрофункция DELAY_MS(ms).
Проведем теперь загрузку и отладку данного примера, при помощи JTAGICE. Для этого нажмем на пункт меню Project->Download and Debug или нажмем сочетание клавиш Ctrl+D или на иконку с красным треугольником, после этого, если нет синтаксических ошибок в коде, мы перейдем в режим отладки, в котором нам доступны команды пошагового выполнения Step Over, Step Into, Step Out, Next Statement, Go, Reset. Помимо этого есть возможность использования точек останова. Это позволяет пройти программу по шагам и посмотреть, что происходит на каждом шаге в регистрах процессора и регистрах DDRD и PORTD порта D, запустить на выполнение, затем остановить, запустить до точки останова. Все это дает не только инструмент поиска ошибок, но также помогает лучше понять работу микроконтроллера. Научится быстрей с ним работать.

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

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



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


/*Необходимые определения*/
//Частота тактирования
#define F_CPU 16000000
//Время задержки в милисекундах
#define DELAY_TIME 1000

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

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

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

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

  
  //Основной бесконечный цикл
  for(;;)
  {
      //Мигание светодиода
      LED_TOG();
      
      //Задержка в одну секунду
      DELAY_MS(DELAY_TIME);
        
  }//end for
  
}

Подход с использованием макрофункций обеспечивает удобство в двух отношениях. Первое это обеспечение улучшения читаемости кода. Второе это упрощение возможных исправлений. Теперь, если светодиод нужно перенастроить на другой вывод другого порта, достаточно поменять три определения LED_DDR, LED_PORT, LED_PIN, а не делать исправления по всему тексту программы. При написании библиотек это сильно упрощает жизнь.
Третий пример это реализация мигания светодиодом, при помощи машины состояний. Машина состояний – это один из вариантов реализации модели встраиваемого ПО микроконтроллера, в котором бесконечный цикл делится на ветки в зависимости от текущего сотояния.



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


/*Необходимые определения*/
//Частота тактирования
#define F_CPU 16000000
//Время задержки в милисекундах
#define DELAY_TIME_LF 1000
#define DELAY_TIME_AF 500
#define DELAY_TIME_HF 100
//Количество переключений в одном состоянии
#define LIMIT_CNT_LF 4
#define LIMIT_CNT_AF 8
#define LIMIT_CNT_HF 40
//Пользовательские типы
#define UCHAR unsigned char
//Возможные состояния машины состояний
#define STATE_LOW_FREQ_BLINK   0
#define STATE_AVR_FREQ_BLINK   1
#define STATE_HIGH_FREQ_BLINK  2
//Опредления портов для макрофункции работы
//со светодиодом
#define LED_DDR  DDRD
#define LED_PORT PORTD
#define LED_PIN  DDD7

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

/*Необходимые объявления глобальных переменных*/
//переменая хранящая текщее состояние
UCHAR curr_state = STATE_LOW_FREQ_BLINK;
//переменая хранящая количество проходов
//одного состояния
UCHAR state_cnt = 0;



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

  
  //Основной бесконечный цикл
  for(;;)
  {
 
      //Переключатель состояний
      switch(curr_state)
      {
      //Если состояние мигания с низкой частотой  
      case STATE_LOW_FREQ_BLINK:
          //Мигание светодиода
          LED_TOG();
          //Задержка в одну секунду
          DELAY_MS(DELAY_TIME_LF);
          //Наращиваем счетчик
          state_cnt++;
          //Если превышен предел пеключений в
          //состоянии, переходим в следующее
          if (state_cnt==LIMIT_CNT_LF)
          {
                curr_state = STATE_AVR_FREQ_BLINK;
                //Обнуляем счетчик
                state_cnt = 0;
          }//end if
          break;
      //Если состояние мигания с средней частотой      
      case STATE_AVR_FREQ_BLINK:
          //Мигание светодиода
          LED_TOG();
          //Задержка в одну секунду
          DELAY_MS(DELAY_TIME_AF);
          //Наращиваем счетчик
          state_cnt++;
          //Если превышен предел пеключений в
          //состоянии, переходим в следующее
          if (state_cnt==LIMIT_CNT_AF)
          {
                curr_state = STATE_HIGH_FREQ_BLINK;
                //Обнуляем счетчик
                state_cnt = 0;
          }//end if        
        break;
      //Если состояние мигания с высокой частотой 
      case STATE_HIGH_FREQ_BLINK:
          //Мигание светодиода
          LED_TOG();
          //Задержка в одну секунду
          DELAY_MS(DELAY_TIME_HF);
          //Наращиваем счетчик
          state_cnt++;
          //Если превышен предел пеключений в
          //состоянии, переходим в начальное
          if (state_cnt==LIMIT_CNT_HF)
          {
                curr_state = STATE_LOW_FREQ_BLINK;
                //Обнуляем счетчик
                state_cnt = 0;
          }//end if           
        break;
      }
        
  }//end for
  
}

В нашем примере, есть три состояния STATE_LOW_FREQ_BLINK, STATE_AVR_FREQ_BLINK, STATE_HIGH_FREQ_BLINK, которые соответствуют миганию светодиода с низкой частой, средней частотой и высокой частотой соответственно. Каждому состоянию соответствует своя величина задержки DELAY_TIME_LF, DELAY_TIME_AF, DELAY_TIME_HF, выполняемая в каждом проходе состояния. Одинаковый период нахождения в каждом состоянии обеспечивают определения LIMIT_CNT_LF, LIMIT_CNT_AF, LIMIT_CNT_HF, определяющие количество проходов-переключений для каждого состояния. Оператор switch обеспечивает переключение между состояниями в соответствии с текущим значением переменной curr_state. Переменная state_cnt инкрементируется в каждом проходе состояния до достижения предела переключений в состоянии. Оператор if определяет, достигнут ли предел переключений. Если предел достигнут, происходит переход в следующее состояние, счетчик проходов state_cnt обнуляется.

Под отладкой теперь, кроме уже описанного выше, можно в View->Watch посмотреть значение переменных curr_state и state_cnt.

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

Четвертый пример посвящен работе с восьмиразрядным таймером (TIMER0) микрокотроллера. В данном примере в качестве второго светодиода используется один из сегментов семисегментного индикатора, подключенный к 6-му выводу порта D.



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

/*Необходимые определения*/
//Необходимые определения
#define F_CPU 16000000
//Значение счетного регистра
#define TCNT0_VALUE 99
//Предельное значение счетчика тиков
#define T0_TICK_CNT_LIMIT 100
//Пользовательские типы
#define UINT unsigned int
/*Необходимые определения макрофункций*/
//Задержка в микросекундах
#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;

/*Основаная функция программы*/
//Попадаем после сброса
void main( void )
{
  //Настравиеваем регистры периферии
  
  //Настройка светодиода
  //Настраиваем 6-й вывод порта D на выход
  DDRD_DDD6 = 1;
  //Устанавливаем 6-й вывод порта D в лог "0" 
  PORTD_PORTD6 = 0; 

  //Настройка светодиода
  //Настраиваем 7-й вывод порта D на выход
  DDRD_DDD7 = 1;
  //Устанавливаем 7-й вывод порта D в лог "0" 
  PORTD_PORTD7 = 0; 
  
  //Настройка таймера (режим Normal)
  TCCR0_CS02=1;//  Частота тактирования 16 000 000 Гц
  TCCR0_CS01=0;//  16 000 000 Гц / 1024 = 15 625 Гц
  TCCR0_CS00=1;//  1 / 15 625 Гц = 0,000064 с =64 мкс
  TCNT0 = TCNT0_VALUE; // 156 * 0,000064 c = 0,009984 c (10 мс) 
                       // тогда начальное значение счетного регистра 255-156 = 99
  TIMSK_TOIE0=1; // Включить прерывание таймера по переполнению
  
  //Разрешить прерывания
  _SEI();
    
  //Основной бесконечный цикл
  for(;;)
  {
      //Пустая операция для точки останова
      _NOP();

      //Переключение 6-й вывода порта D из "0" в "1" и из "1" в "0"
      //При помощи исключающего или     
      PORTD_PORTD6 ^= 1;
      
      //Задержка в одну секунду
      DELAY_MS(500);
        
  }//end for
  
}

/*Обработчик прерывания таймера T0
по переполнению*/
#pragma vector=TIMER0_OVF_vect
__interrupt void ISR_TickTimer(void)
{
   
  //Пустая операция для точки останова
  _NOP();
  
  //Нарастить счетчик тиков таймера T0
  T0_tick_cnt++;
  
  //Если отсчитали 1 секунду
  if (T0_tick_cnt >= T0_TICK_CNT_LIMIT)
  {
      //Обнулить счетчик тиков таймера T0
      T0_tick_cnt=0;
      
      //Переключение 7-й вывода порта D из "0" в "1" и из "1" в "0"
      //При помощи исключающего или     
      PORTD_PORTD7 ^= 1;
  
  }
  

  //Выставляем начальное значение
  //в счетном регистре
  TCNT0=TCNT0_VALUE;

}//end func

В рассматриваемом примере в функции main() после настройки светодиодов происходит настройка нулевого таймера, которая состоит в установке битов в управляющем регистре TCCRO. Т.к. мы будем использовать режим работы таймера Normal, то биты WGM00 и WGM01 должны равняться нулю. Начальное значение и так равно нулю, поэтому просто их не трогаем. Биты CS00, СS01, СS02 устанавливаем так чтобы получить максимальный делитель (1024) частоты тактирования 16 МГц.

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

После деления получим частоту работы таймера 15625 Гц, что соответствует тику в 64 мкс. Наиболее близкий к 10 мс мы получим, если умножим тик на 156 (156 * 0,000064 c = 0,009984 c = 10 мс). Поэтому, чтобы получить генерацию прерывания по обработке события переполнения каждые 10 мс, необходимо загрузить в счетный TCNT0 регистр 99 (255-156 = 99). Т.к. таймер начнет считать с 99, а прерывание сгенерируется при достижении 255, то мы и получим путь в 156 тиков.

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

Далее в примере, чтобы генерация прерываний по переполнению нулевого таймера происходила, происходит установка бита TOIE0 в регистре маскирования прерываний TIMSK. После чего делаем глобальное разрешение всех маскируемых прерываний макрофункцией _SEI().

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

Определения всех векторов прерываний есть в заголовочной файле iom16.h:


/*==============================*/
/* Interrupt Vector Definitions */
/*==============================*/

/* NB! vectors are specified as byte addresses */

#define    RESET_vect           (0x00)
#define    INT0_vect            (0x04)
#define    INT1_vect            (0x08)
#define    TIMER2_COMP_vect     (0x0C)
#define    TIMER2_OVF_vect      (0x10)
#define    TIMER1_CAPT_vect     (0x14)
#define    TIMER1_COMPA_vect    (0x18)
#define    TIMER1_COMPB_vect    (0x1C)
#define    TIMER1_OVF_vect      (0x20)
#define    TIMER0_OVF_vect      (0x24)
#define    SPI_STC_vect         (0x28)
#define    USART_RXC_vect       (0x2C)
#define    USART_UDRE_vect      (0x30)
#define    USART_TXC_vect       (0x34)
#define    ADC_vect             (0x38)
#define    EE_RDY_vect          (0x3C)
#define    ANA_COMP_vect        (0x40)
#define    TWI_vect             (0x44)
#define    INT2_vect            (0x48)
#define    TIMER0_COMP_vect     (0x4C)
#define    SPM_RDY_vect         (0x50)

Логика работы программы представляет уже описанную в первом примере систему с суперциклом, где обеспечивается квазипараллельная работа основного цикла и обработчика прерывания. В основном цикле проиходит переключение шестого вывода порта D с периодом 500 мс. Основной цикл прерывается каждые 10 мс на обработку прерывания по переполнения нулевого таймера. Обработка осуществляет при помощи функции-обработчика ISR_TickTimer(), которая вызывается по этому событию. В данной функции путем инкрементрования переменной T0_tick_cnt происходит подсчет 10-ти милисекундных тиков. Когда переменная T0_tick_cnt достигает 100 (т.е. прошла одна секунда), при помощи оператора if в обработчике определяется это событие. После чего переменная T0_tick_cnt и происходит переключение вывода 7 порта D, что обеспечивает мигание второго светодиода с периодом 1000 мс.
При отладке данного примера можо поставить точку останова, как в основном цикле, так и в обработчике прерываний.

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

Пятый пример представляет собой по логике работы предыдущий пример, но с использованием макрофункций.


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

/*Необходимые определения*/
//Частота тактирования
#define F_CPU 16000000
//Время задержки в милисекундах
#define DELAY_TIME 500
//Опредления портов для макрофункции работы
//с первым светодиодом
#define LED1_DDR  DDRD
#define LED1_PORT PORTD
#define LED1_PIN  DDD7
//Опредления портов для макрофункции работы
//со вторым светодиодом
#define LED2_DDR  DDRD
#define LED2_PORT PORTD
#define LED2_PIN  DDD6

//Определения для выбора частоты нулевого таймера
#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 100

//Пользовательские типы
#define UINT unsigned int

/*Необходимые определения макрофункций*/
//Макрофункция инициализации первого светодода
#define LED1_INIT()   ( LED1_DDR |= (1<<LED1_PIN) );
//Погасить первый светодиод
#define LED1_LOW()    ( LED1_PORT &=~ (0<<LED1_PIN) );
//Зажечь первый светодиод
#define LED1_HIGH()   ( LED1_PORT |= (1<<LED1_PIN) );
//Мигание первого светодиода
#define LED1_TOG()    ( LED1_PORT ^= (1<<LED1_PIN) );

//Макрофункция инициализации второго светодода
#define LED2_INIT()   ( LED2_DDR |= (1<<LED2_PIN) );
//Погасить второй светодиод
#define LED2_LOW()    ( LED2_PORT &=~ (0<<LED2_PIN) );
//Зажечь второй светодиод
#define LED2_HIGH()   ( LED2_PORT |= (1<<LED2_PIN) );
//Мигание второго светодиода
#define LED2_TOG()    ( LED2_PORT ^= (1<<LED2_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 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;

/*Основаная функция программы*/
//Попадаем после сброса
void main( void )
{
  //Настравиеваем регистры периферии
  
  //Настройка светодиодов
  LED1_INIT();
  LED2_INIT();
  //Гасим светодиоды
  LED1_LOW();
  LED2_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(); // Включить прерывание таймера по переполнению
  
  //Разрешить прерывания
  _SEI();
    
  //Основной бесконечный цикл
  for(;;)
  {
      //Пустая операция для точки останова
      _NOP();
      
      //Мигание первого светодиода
      LED1_TOG();
      
      //Задержка в половину секунды
      DELAY_MS(DELAY_TIME);
        
  }//end for
  
}

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

}//end func

Выставки битов в управляющем регистре нулевого таймера TCCRO, при настройке, осуществляется при помощи макрофункции TIMER0_SET_CLK_DIV(x), аргумент которой x определяет насколько будет делиться частота тактирования и выбирается из набора определений F_CPU_DIV_1, F_CPU_DIV_8, F_CPU_DIV_64, F_CPU_DIV_256, F_CPU_DIV_1024. Запись начального значения в регистре счета TCNT0 осуществляется при помощи макрофункции TIMER0_SET_CNT(x), где аргумент x – это собственно начальное значение (в нашем случае TCNT0_VALUE = 99). Установка бита TOIE0 в регистре маскирования прерываний TIMSK проиходит при помощи макрофункции TIMER0_OVF_INT_ON().
Все примеры в виде проекта IAR можно скачать здесь.

Заключение

Если этот пост вызовет интерес, то в следующей части рассмотрим примеры работы со встроенным АЦП, семисегментниками. После чего соберем все рассмотренные примеры в программный проект цифрового термометра.

Дополнение

Если есть потребность получить hex-файл программы написанной в IAR, то сделать это совсем не сложно. Достаточно в настройках проекта на вкладке Linker->Output и на вкладке Linker->Extra Output произвести настройки, как на скриншотах ниже. После этого, если произвести пересборку проекта нажав Project->Rebuild All в подпапке [название проекта]DebugExe можно будет найти hex-файл. Который можно будет зашить программатором AVR910, клоном STK500 или любым другим доступным программатором.

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

Прогаммирование и JTAG отладка микроконтроллера Atmega16 на языке C в среде IAR, часть 1

Автор: love_energy

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js