Доступ к 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 будет происходить по такому же принципу. ;Пример кода на ассемблере. //Пример кода на 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. |