Доступ к 16-битным регистрам AVR Печать
Добавил(а) microsin   

Некоторые регистры микроконтроллеров AVR являются 16-битными, к которым CPU (программа firmware) может получить доступ через 8-битную шину данных. При этом для доступа к 16-битному регистру требуется две последовательные операции чтения или записи.

Чтобы обеспечить целостность (атомарность доступа, atomic read, atomic write) операций с 16-битными регистрами при доступе через 8-битную шину, используется специальная технология. Рассмотрим это на примере микроконтроллера ATmega32A, у которого таймер/счетчик 1 имеет 16-битные регистры TCNT1, OCR1A, OCR1B и ICR1

Каждый 16-битный таймер имеет один специальный 8-битный регистр для временного хранения старшего байта при 16-битном доступе (регистр TEMP, к которому нельзя обратиться напрямую). Один и тот же временный регистр TEMP является общим для всех 16-битных регистров одного 16-битного таймера. Доступ к младшему регистру на чтение или запись вызывает срабатывание 16-битной процедуры чтения или записи. Когда младший байт 16-битного регистра записывается CPU, старший при этом сохраняется во временном регистре TEMP, и оба байта будут записаны и в старший и младший регистры одновременно, на одном и том же тактовом цикле CPU. Когда младший байт 16-битного регистра читается CPU, старший байт при этом копируется в TEMP на одном и том же тактовом цикле CPU.

Не все операции 16-битного доступа используют TEMP для старшего байта. Чтение 16-битных регистров OCR1A, OCR1B не вовлекают использование временного регистра TEMP.

Таким образом, для работы с 16-битными регистрами нужно соблюдать следующие простые правила. Чтобы выполнить 16-битную запись, перед записью младшего байта обязательно должен быть записан старший байт. Для 16-битного чтения младший байт должен быть прочитан перед чтением старшего байта.

Следующий код дает пример получения доступа к 16-битным регистрам таймера, при этом подразумевается, что нет прерываний, которые обновляют регистр TEMP. Тот же принцип может быть использован для доступа и к регистрам OCR1A, OCR1B и ICR1. Имейте в виду, что когда используется язык C, компилятор сам реализует технологию 16-битного доступа.

;Пример кода на ассемблере для доступа к 16-битному регистру TCNT1.
   ..
; Установка TCNT1 в значение 0x01FF
   ldi r17,0x01
   ldi r16,0xFF
   out TCNT1H,r17
   out TCNT1L,r16
; Чтение TCNT1 в регистры r17:r16
   in r16,TCNT1L
   in r17,TCNT1H
   ..
//Пример кода на C для доступа к 16-битному регистру TCNT1.
..
unsigned int i;
/* Установка TCNT1 в значение 0x01FF */
TCNT1 = 0x1FF;
/* Чтение TCNT1 в переменную i */
i = TCNT1;
..

Важно понимать, что описанный доступ к 16-битным регистрам должен быть атомарным. Если произойдет прерывание между двумя инструкциями, которые обращаются к половинкам 16-битного регистра, и код в обработчике прерывания обновит регистр TEMP путем получения доступа к тому же регистру или к другим 16-битным регистрам таймера, то результат 16-битной операции вне прерывания будет ошибочным. Таким образом, если и основной код, и код прерывания обновляют TEMP, то в основном коде должны быть запрещены прерывания при 16-битном доступе к регистрам. Следующие примеры кода показывают, как делать атомарное чтение содержимого TCNT1. Чтение любого из регистров OCR1A, OCR1B или ICR1 будет происходить по такому же принципу.

;Пример кода на ассемблере.
;Обеспечивается атомарное чтение 16-битного регистра.
TIM16_ReadTCNT1: ; Сохранение флага глобального прерывания in r18,SREG ; Запрет прерываний cli ; Чтение TCNT1 в регистры r17:r16 in r16,TCNT1L in r17,TCNT1H ; Восстановление флага глобального прерывания out SREG,r18 ret
//Пример кода на C.
//Обеспечивается атомарное чтение 16-битного регистра.
unsigned int TIM16_ReadTCNT1( void )
{
   unsigned char sreg;
   unsigned int i;
   /* Сохранение флага глобального прерывания */
   sreg = SREG;
   /* Запрет прерываний */
   _CLI();
   /* Read TCNT1 into i */
   i = TCNT1;
   /* Восстановление флага глобального прерывания */
   SREG = sreg;
   return i;
}

Следующие примеры кода показывают, как делать атомарную запись содержимого TCNT1. Запись любого из регистров OCR1A, OCR1B или ICR1 будет происходить по такому же принципу.

;Пример кода на ассемблере.
;Обеспечивается атомарная запись 16-битного регистра.
TIM16_WriteTCNT1:
; Сохранение флага глобального прерывания
   in r18,SREG
; Запрет прерываний
   cli
; Установка TCNT1 в значение r17:r16
   out TCNT1H,r17
   out TCNT1L,r16
; Восстановление флага глобального прерывания
   out SREG,r18
   ret
//Пример кода на C.
//Обеспечивается атомарная запись 16-битного регистра.
void TIM16_WriteTCNT1 ( unsigned int i )
{
   unsigned char sreg;
   unsigned int i;
   /* Сохранение флага глобального прерывания */
   sreg = SREG;
   /* Запрет прерываний */
   _CLI();
   /* Установка TCNT1 в значение i */
   TCNT1 = i;
   /* Восстановление флага глобального прерывания */
   SREG = sreg;
}

Если записывается больше одного 16-битного регистра, где старший байт один и тот же для всех записываемых регистров, то старший байт нужно записать только один раз. Однако имейте в виду, что в этом случае также применяется правило для атомарных операций, описанное выше.

[Ссылки]

1AVR072: Accessing 16-bit I/O Registers site:atmel.com.