Доступ к регистрам ввода/вывода



Каждому порту в микроконтроллере AVR соответствуют минимум три регистра:

• DDRx — регистр направления данных (х — буква соответствующего порта, например: DDRB — регистр направления данных Порта В). Этот регистр определяет, как должен быть сконфигурирован вывод порта — как вход или как выход. Если в бит этого порта записать 1, то соответствующий вывод порта будет работать как выход; если записать 0, то соответствующий вывод порта будет работать как вход. Например, если в регистре DDRB записано число 01000011, то это значит, что 0-й, 1-й и 6-й выводы Порта В сконфигурированы как выходы, а остальные — как входы.

• PORTx — выходной регистр порта х (например, PORTB — выходной регистр Порта В). В биты этого регистра записывают те значения, которые хотят получить на выводах порта, при условии, что выводы сконфигурированы как выходы.

Если вывод сконфигурирован как вход, то при записи в соответствующий бит регистра PORTx 1 этот вывод становится «входом с подтяжкой», т. е. этот вывод через резистор примерно 100... 150 кОм, находящийся внутри микроконтроллера, подключается («подтягивается») к напряжению питания микроконтроллера. Таким образом, при отсутствии сигнала на этом входе будет уровень логической 1.

• PINx — выводы порта х (например, PINB — выводы Порта В). Этот регистр содержит значения логических уровней, которые к настоящему времени присутствуют на физических (т. е. реальных) выводах порта. Этот регистр доступен ТОЛЬКО для чтения.

Для доступа к регистрам ввода/вывода микроконтроллера AVR с помощью инструкций ассемблера IN и OUT компилятор использует ключевые слова sfrb и sfhv.

Пример:

/* Доступ к регистрам ввода/вывода */

/* Определим SFR (Регистры общего назначения) для чипа AT90S8515 */

sfrbPINC=0xl3; // Так как регистр PINA 8-битный, то к нему осуществляется

// 8-битный доступ с помощью sfrb (sfr byte)

// 0x13 — это адрес порта PINC sfrw TCNTl=0x2c;

// так как регистр TCNT1 16-битный, то к нему

// осуществляется 16-битный доступ с

// помощью sfrw (sfr word)

// 0x2с - это адрес регистра TCNT1

/* Основная функция программы */

void main(void) {

unsigned char x;      // Объявляем символьную переменную x

x=PINC;          // Чтение выводов Порта А

TCNTl=0xF4A6;        // Запись в регистры TCNT1L & TCNT1H

}

 

Адреса регистров ввода/вывода определены в заголовочных файлах.

В начале программы посредством #includeможно подключить соответствующий файл для того микроконтроллера, который используется в данном проекте.

Побитовый доступ к регистрам ввода/вывода

Побитовый доступ к регистрам ввода/вывода осуществляется путем добавления выбранных битов после имени регистра.

Поскольку побитовый доступ к регистрам ввода/вывода осуществляется с использованием инструкций CBI, SBI, SBIC и SBIS, адрес регистра должен быть в диапазоне 0...1Fh для sfrb и в диапазоне 0...1Eh для sfrw.

Пример:

/* Доступ к регистрам ввода/вывода */

/* Определим SFR (Регистры общего назначения) для чипа AT90S2313 */

sfrbPORTB=0xl8;    // Объясняем компилятору, по каким адресам находятся

sfrb DDRB=0xl7;      //те или иные регистры (порты). Так как все используемые

sfrbPINB=0xl6; // регистры 8-битные, то к ним осуществляется

// 8-битовый доступ с помощью sfrb. Обычно эти

// операторы располагаются в заголовочных файлах

// (см. Доступ к регистрам ввода/вывода)

inta, b, с;        // Объявляем целые переменные а, b, с

/* Основная функция программы */

void main(void)

{

/* Зададим направление выводов Порта В */

DDRB.0=0 DDRB.1=0 DDRB.2=0 DDRB.3=0 // зададим биты 0...3 порта В как

 // входы

DDRB.4=1 DDRB.5=1 DDRB.6=1 DDRB.7=1 // зададим биты 4...7 Порта В как

// выходы

/* Установим выводы Порта В */

PORTB.0=0; PORTB.1=0;                          // установим биты 0 и 1 Порта В как простые входы

PORTB.2=1; PORTB.3=1;                          // установим биты 2 и 3 Порта В как "входы с подтяжкой", т. е. каждый из этих выводов через резистор примерно 100...150 кОм будет соединён с шиной питания. Таким образом, в отсутствие сигнала на этих выводах будет уровень логической 1.

PORTB.4=0; PORTB.5=0;                          // установим биты 4 и 5 Порта В как выходы, на которых установлен уровень логического 0

P0RTB.6=1; P0RTB.7=1;                           // установим биты 6 и 7 Порта В как выходы, на которых установлен уровень логической 1

/* опросим выводы Порта В, т. е. их физические значения */

a=PINB.0;                               // Переменной а присвоим значение вывода 0 Порта В, т. е. значение сигнала, на выводе 0

b=PINB.5;                 // Переменной b присвоим значение вывода 5 Порта В, т. е. b=0

c=PINB.6;                              // Переменной с присвоим значение вывода 6 Порта В, т. е. с=1     

if (PINB.2) {         // Если на выводе 2 Порта В логическая 1,

 

[группа операторов 1]       // то выполняется [группа операторов 1];

}

else    

[группа операторов2]//если 0 - то [группа операторов2]

}

Чтобы улучшить удобочитаемость программы, битам в регистрах ввода/вывода можно присвоить символические имена посредством #define.

Пример:

/* Присвоение символических имен битам регистров ввода/вывода */

/* Эта примитивная программа зажигает светодиод по нажатию кнопки. Чип: AT90S2313. Светодиод подключён катодом к выводу 0 Порта В, а анодом через резистор 470 Ом — к плюсу источника питания. Кнопка одним выводом подключена к выводу 1 Порта В, а другим - к минусу источника питания (к "общему"). Частота кварца 1 МГц, хотя в данном случае она может быть любой */

#include <90s2313.h> // подключаем файл, где определены адреса регистров

/* Присвоение символических имен */

#define output_led PORTB.0        // Выход на светодиод

#define input_button PINB.1 // Вход кнопки

/* Основная функция программы */

void main(void)

{

/* Зададим направление выводов Порта В */

DDRB.0=1;                // зададим бит 0 Порта В как выход

DDRB.1=0;                // зададим бит 1 Порта В как вход

/* Установим выводы Порта В */

output_led =1;                      // используем символическое имя бита PORTB.0, запишем в него 1, чтобы установить вывод 0 Порта В как выход, на котором установлен уровень логической 1, чтобы светодиод не горел

P0RTB.1=1;                          // установим бит 1 Порта В как "вход с подтяжкой", чтобы при отжатой кнопке на этом выводе была 1, а при нажатии кнопки уровень менялся на 0

/* Бесконечный цикл */

while (1)                                 // Оператор цикла

{

/* Тело цикла while */

 if(input_button==0)            // В условии используем символическое

// имя вывода PINB.1. Если кнопка нажата,

output_led=0;                       // т. е. на выводе PINB.1 логический 0, то зажигаем светодиод

else

output_led=l;                         // Иначе, если кнопка отжата,гасим светодиод

}                                    // Скобка закрывает цикл while()

}                                    // Скобка закрывает функцию main()

 

Важно отметить, что побитовый доступ для регистров ввода/вывода, расположенных во внутреннем SRAM выше адреса 5Fh (например, Порт Е для ATmegal28), РАБОТАТЬ НЕ БУДЕТ, поскольку ассемблерные инструкции CBI, SBI, SBICu SBIS не могут быть использованы для доступа к SRAM.

Доступ к EEPROM-памяти

Каждый микроконтроллер AVR содержит электрически стираемую энергонезависимую память (EEPROM). EEPROM организована как отдельная область данных, каждый байт которой может быть прочитан и перезаписан. EEPROM выдерживает не менее 100000 циклов записи/стирания.

Доступ к внутреннему EEPROM AVR осуществляется путём использования глобальных переменных, следующих за ключевым словом eeprom.

Пример:

/* Глобальные переменные, хранящиеся в EEPROM */

eeprom intvar_eep=5;              // Значение 5 запишется в EEPROM в процессе программирования чипа

eeprom longarray_eep[8];            // Объявление массива длинных целых переменных

eeprom charvar_eepl;                  // Объявление символьной переменной var_eepl

eeprom charstring[]="Hello";       // Строка (символьный массив) запишется в

// EEPROM в процессе программирования чипа

Пример:

/* Использование EEPROM в программе */

eeprom intvar_eep;           // Объявление целой переменной var_eep,

// хранящейся в EEPROM

/* Основная функция программы */

void main(void) {

int i,-                                       // Объявление целой переменной i , хранящейся в SRAM

int eeprom *point_to_eep; // объявление указателя EEPROM сам указатель хранится в SRAM

/* Непосредственная запись значения 98 в EEPROM */

var_eep=98;

/* Косвенная запись того же значения с использованием указателя */

point_to_eep=&var_eep;

*point_to_eep=98;

/* Непосредственное чтение значения из EEPROM */

 i=var_eep;

/* Косвенное чтение того же значения с использованием указателя */ i=*point_to_eep;

}

 

Указатели на EEPROM ВСЕГДА используют 16 битов.

Использование прерываний

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

Вместо поочередного опроса каждого периферийного устройства, не нуждаются ли они в обслуживании, можно заставить их самих делать вызов, когда им это потребуется. Этот процесс называется прерыванием, т. к. периферийное устройство (например, нажатая кнопка) прерывает основное выполнение программы. После этого микроконтроллер останавливает нормальное выполнение программы, определяет источник прерывания и выполняет необходимое действие, т. е. обслуживает прерывание. Затем микроконтроллер продолжает нормальное выполнение программы.

В CodeVisionAVR для обслуживания прерывания служит соответствующая функция, т. е. при запросе прерывания вызывается функция обслуживания этого прерывания.

В первую очередь обрабатываются прерывания, имеющие более высокий приоритет. Номера векторов прерываний для конкретного микроконтроллера приведены в соответствующем заголовочном файле (см. Доступ к Регистрам Ввода/Вывода).Чем меньше номер вектора прерывания, тем выше его приоритет.

Доступ к системе прерываний AVR осуществляется с помощью ключевого слова interrupt(прерывание).

Номера векторов прерывания начинаются с 1, но в заголовочном файле нет вектора с таким номером. Дело в том, что этот номер принадлежит прерыванию с самым высоким приоритетом, а именно RESET(Сброс). Так как при аппаратном сбросе микроконтроллера выполнение программы ВСЕГДА начинается с функ­ции main,то смысл в обработке этого прерывания пропадает.

Функция обработки прерывания имеет следующий синтаксис:

interrupt [номер_вектора_прерывания] имя_функции_обработки_прерывания ([пара­метры] ) {

[тело функции]

}

Функции прерывания не могут возвращать значения, не имея параметров.

При вызове функции обработки прерывания компилятор автоматически сохранит все используемые регистры и восстановит их при выходе обратно. В конце функции прерывания устанавливается ассемблерная инструкция RETI.

Пример:

/* Функции обработки прерываний для ATMEGA8535 */

/* Функция обработки прерывания по аналоговому компаратору */

interrupt [11] void analog_comp(void) { // при срабатывании компаратора автоматически будет вызвана эта функция, т. к. в квадратных скобках стоит число11, которое соответствует номеру вектора прерывания по аналоговому компаратору ANA_COMP. Вместо 11 в квадратных скобках можно было написать ANA_COMP

/* Тело функции */

[группа операторов 1]

}

/* Функция обработки прерывания по переполнению таймера/счётчика0*/

interrupt [7] void overflow_timer0(void)       // при переполнении/таймера/счётчика 0 автоматически будет вызвана эта функция, т. к. в квадратных скобках стоит число 7, которое соответствует номеру вектора прерывания по переполнению таймера/счётчика 0 TIMCLOVF (см. файл 90s2313.h). Вместо 7 в квадратных скобках можно было написать TIM0_OVF

{

/* Тело функции */

[группа операторов 2]

/* При одновременном запросе этих прерываний в первую очередь будет обрабатываться прерывание по переполнению таймера/счётчика0, т. к. номер его вектора (7) меньше, чем у прерывания по аналоговому компаратору(11), следовательно, оно имеет более высокий приоритет */

 

Разумеется, чтобы сконфигурировать систему прерывания и разрешить прерывания, необходимо также установить соответствующие биты в управляющих регистрах.

Пример:

/* Конфигурирование и разрешение прерываний */

/* Разрешим прерывание по переполнению таймера/счётчика0 */

TIMSK=0x02;

/* Разрешим прерывание по переключению выхода аналогового компаратора */ ACSR=0x08;

/* Глобальное разрешение прерываний */

#asm("sei")                // здесь используется ассемблерная инструкция sei, которая устанавливает флаг глобального прерывания (I) в регистре статуса (SREG)

Организация памяти SRAM

Скомпилированная программа имеет карту памяти, показанную на рис. 1

Область Рабочие Регистры содержит 32 8-битных рабочих регистров общего назначения R0...R31. Компилятор CodeVisionAVR использует следующие регистры: RO, Rl, R15, R22...R31. Также некоторые регистры (R2...R14) могут быть распределены компилятором для глобальных битовых переменных. Остальная часть неиспользованных регистров в этом дипазоне распределена для глобальных char (символьных) и int (целых) переменных. Регистры R16...R21 распределены для локальных char (символьных) и int (целых) переменных.

Рис. 1 – Карта памяти скомпилированной программы

Область Регистры ввода/вывода содержит 64 адреса для периферийных функций CPU, таких как Port Control Registers (Порт управления регистрами), Timer/Counters (таймеры/счётчики) и другие функции ввода/вывода. Можно свободно использовать эти регистры в ассемблерных программах.

Область Стек данных (Data Stack) используется для динамического хранения локальных переменных, передачи параметров функции и хранения регистров R0, Rl, R15, R22...R31 и SREG в течение обслуживания прерывания.

Для Указателя стека данных (Data Stack Pointer) используется регистр Y. При запуске программы Указатель стека данных инициализируется со значением 60п+Размер стека данных.

При каждой записи значения в Стек данных Указатель стека данных уменьшается на единицу. При каждом извлечении значения Указатель стека данных обратно увеличивается на единицу.

При конфигурировании проекта (см. Закладка С Compiler (Компилятор Си)) следует определить достаточный Data Stack Size (Размер стека данных) так, чтобы он в течение выполнения программы не перекрыл область регистров ввода/вывода.

Область Глобальные переменные используется для статического хранения глобальных переменных в течение выполнения программы. Размер этой области может быть вычислен суммированием размеров всех объявленных глобальных переменных.

Область Аппаратный Стек (Hardware Stack) используется для хранения адресов возврата функций.

Регистр SP (Stack Pointer — Указатель стека) используется как указатель стека и инициализируется при запуске программы со значением последнего (самого старшего) адреса SRAM.

В течение выполнения программы Аппаратный стек растет вниз, в область Глобальных переменных.

В конфигурации проекта есть опция Stack End Markers (Отмечать конец стека), при выборе которой компилятор будет помещать строки DSTACKEND (Конец стека данных) или соответственно HSTACKEND (Конец аппаратного стека) в конец области Стека данных (Data Stack) или соответственно Аппаратного стека (Hardware Stack).

 


Дата добавления: 2018-04-15; просмотров: 348; Мы поможем в написании вашей работы!

Поделиться с друзьями:






Мы поможем в написании ваших работ!