Некоторые регистры микроконтроллеров 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-битного регистра, где старший байт один и тот же для всех записываемых регистров, то старший байт нужно записать только один раз. Однако имейте в виду, что в этом случае также применяется правило для атомарных операций, описанное выше.
[Ссылки]
1. AVR072: Accessing 16-bit I/O Registers site:atmel.com. |