Прочитал статью «Работа с регистрами внешних устройств в языке C, часть 1». Заинтересовался и, как альтернативный вариант, решил показать работу с регистрами внешних устройств микроконтроллера на Ассемблере.
Ассемблер – язык тонкой детализации и больших возможностей, но программирование на нем требует кропотливого труда и времени, имеет плохие возможности переносимости с одной технической платформы на другую. Применение имеет смысл для создания качественного ПО, оптимизированного по быстродействию и расходуемой памяти.
Микроконтроллерные информационно-управляющие сети,- область ПО, где применение ассемблера может быть оправдано.
Рассматривать будем отечественный авиационный 32-разрядный микроконтроллер 1986ВЕ1Т, производства Миландр и систему программирования Keil 5.
1986ВЕ1Т имеет набор16-разрядных инструкций (Thumb) архитектуры ARM v6-M (включающий 32-битные Thumb-2 инструкции, такие как BL, MRS, MSR, ISB, DSB и DMB).
С точки зрения доступности, все физические регистры микроконтроллера, занимают часть общего адресного пространства системы. Всё множество регистров обычно имеет общий базовый адрес.
Множество регистров принято делить на блоки, которые закрепляются за внешними устройствами. Блок имеет свой подадрес. Регистр в блоке также имеет свой адрес (смещение).
Абсолютный адрес регистра является константой и может быть получен по формуле:
PERIFERIJA+(АДРЕС_БЛОКА<<12)+(НОМЕР_РЕГИСТА_В_БЛОКЕ<<2)
или просто задан как константа.
В дальнейшем необходимые абсолютные адреса регистров периферии в программе можно сгруппировать в векторы адресов. Как-то так:
PERIFERIJA EQU 0x40000000 ;//-- Базовый адрес регистров периферии
;//============================================================
;//== Регистры управления контроллером Flash памяти программ ==
;//============================================================
;//-- Определение абсолютных адресов регистров
EEPROM_CNTRL EQU 0x18 ;//-- Базовый адрес блока регистров EEPROM
__EEPROM__CNTRL EQU PERIFERIJA+(EEPROM_CNTRL<<12)
__EEPROM__CMD EQU __EEPROM__CNTRL+(0x00<<2) ;//-- Регистр управления EEPROM
__EEPROM__ADR EQU __EEPROM__CNTRL+(0x01<<2) ;//-- Адрес регистра
__EEPROM__DI EQU __EEPROM__CNTRL+(0x02<<2) ;//-- Регистр данных на запись
__EEPROM__DO EQU __EEPROM__CNTRL+(0x03<<2) ;//-- Регистр считанных данных
__EEPROM__KEY EQU __EEPROM__CNTRL+(0x04<<2) ;//-- Регистр ключа
;//-- Определение индексов регистров
EEPROM_CMD EQU 0 ;//-- Регистр управления EEPROM
EEPROM_ADR EQU EEPROM_CMD+1 ;//-- Адрес регистра
EEPROM_DI EQU EEPROM_ADR+1 ;//-- Регистр данных на запись
EEPROM_DO EQU EEPROM_DI+1 ;//-- Регистр считанных данных
EEPROM_KEY EQU EEPROM_DO+1 ;//-- Регистр ключа
;//-- Задание абсолютных адресов регистров вектором
vaRegEEPROM ;//-- Вектор адресов регистров контроллера Flash
DCD __EEPROM__CNTRL
DCD __EEPROM__CMD
DCD __EEPROM__ADR
DCD __EEPROM__DI
DCD __EEPROM__DO
DCD __EEPROM__KEY
Обычно, каждый блок регистров и каждый регистр в блоке имеет уникальное имя. Для записи данного факта использую следующий прием:
EEPROM__CNTRL
Имя блока регистров отделяю от имени регистра в блоке двумя подчеркиваниями. А-ля версия ассемблера для записи выражений типа:
EEPROM.CNTRL или EEPROM->CNTRL
Далее, текст вида __EEPROM__CMD определяет значение регистра.
Текст вида _EEPROM__CMD определяет адрес значения __EEPROM__CMD.
Текст вида EEPROM__CMD определяет индекс компоненты __EEPROM__CMD в векторе vaRegEEPROM.
Регистры в микроконтроллере адресуются по правилам доступа к памяти микроконтроллера и требуют выравнивания адреса на 4 (xx<<2).
Специфика адресации памяти в данном микроконтроллере требует — для доступа к регистру периферии из программы необходимо, чтобы его адрес содержался в L-регистре (R0-R7) центрального процессора (или мог быть получен, как база+смещение, из регистров ЦП).
Как загрузить абсолютный адрес регистра периферии (4 байта) в регистр ЦП из вектора адресов? Реально можно воспользоваться 3-мя способами:
— адресацией относительно счетчика команд (PC),
— адресацией базовый_адрес+смещение_константа,
— адресацией базовый_адрес+смещение_регистр.
Рассмотрим реальный пример:
;//== Инициализация микроконтроллера
;//=================================
;//-- Исходник на C
;//----------------
;RST_CLK->PER_CLOCK |= (1 << 27);
;temp = BKP->REG_0E;
;temp &= 0xFFFFFFC0;
;BKP->REG_0E = temp | (5 << 3) | 5; // SelectRI = 0x5, LOW = 0x5; (for frequency below 40 MHz);
;RST_CLK->HS_CONTROL = 0x00000001; //HSE — On; Oscillator mode
;while((RST_CLK->CLOCK_STATUS&0x04)!=0x04); //Wait until HSE not ready
;RST_CLK->CPU_CLOCK = 0x00000002; //CPU_C1 = HSE (8MHz)
;RST_CLK->PLL_CONTROL=(1<<2)|(2<<8); //CPU PLL On, PLL MULL = 2
;while((RST_CLK->CLOCK_STATUS&0x02)!=0x02); //Wait until CPU PLL not ready
;RST_CLK->PER_CLOCK|=0x08; //EEPROM_CTRL Clock enable
;EEPROM->CMD=0; //EEPROM Delay = 0
;RST_CLK->PER_CLOCK&=(~0x08); //EEPROM_CTRL Clock disable
;RST_CLK->CPU_CLOCK = 0x00000106; //CPU Clock = HSE (24MHz)
;RST_CLK->PER_CLOCK |= 1<<24; //clock of PORTD ON
;//-- реализация на ассемблере (адресация относительно PC)
;//-------------------------------------------------------
LDR R3,_RST_CLK__PER_CLOCK ;//-- R3 < — адрес регистра RST_CLK__PER_CLOCK
LDR R1,=(1<<27) ;//-- R1 < — константа
LDR R0,[R3,#0] ;//-- R0 < — содержимое регистра периферии
ORRS R0,R1 ;//-- R0 < — R0 orr R1
STR R0,[R3] ;//-- RST_CLK->PER_CLOCK |= (1 << 27)
LDR R3,_BKP__REG_0E
MOVS R1,#(5<<3):OR:(5<<0)
MOVS R2,#(7<<3):OR:(7<<0)
LDR R0,[R3]
BICS R0,R2
ORRS R0,R1
STR R0,[R3]
LDR R3,_RST_CLK__HS_CONTROL
MOVS R0,#1
STR R0,[R3]
LDR R3,_RST_CLK__CLOCK_STATUS
MOVS R1,#4
vm00_fnClkConfig_1
LDR R0,[R3]
ANDS R0,R1
BEQ vm00_fnClkConfig_1
LDR R3,_RST_CLK__CPU_CLOCK
MOVS R0,#2
STR R0,[R3]
LDR R3,_RST_CLK__PLL_CONTROL
LDR R0,=((2<<8):OR:(1<<2))
STR R0,[R3]
LDR R3,_RST_CLK__CLOCK_STATUS
MOVS R1,#(1<<1)
vm00_fnClkConfig_2
LDR R0,[R3]
ANDS R0,R1
BEQ vm00_fnClkConfig_1
LDR R3,_RST_CLK__PER_CLOCK
MOVS R1,#(1<<3)
LDR R0,[R3]
ORRS R0,R1
STR R0,[R3]
LDR R3,_EEPROM__CMD
MOVS R1,#0
STR R0,[R3]
LDR R3,_RST_CLK__PER_CLOCK
MOVS R1,#(1<<3)
LDR R0,[R3]
BICS R0,R2
STR R0,[R3]
LDR R3,_RST_CLK__CPU_CLOCK
LDR R1,=0x106
STR R0,[R3]
LDR R3,_RST_CLK__PER_CLOCK
LDR R1,=(1<<24)
LDR R0,[R3]
ORRS R0,R1
STR R0,[R3]
;//-- Индексы
BKP__REG_0E EQU 0
EEPROM__CMD EQU BKP__REG_0E+1
RST_CLK__CLOCK_STATUS EQU EEPROM__CMD+1
RST_CLK__CPU_CLOCK EQU RST_CLK__CLOCK_STATUS+1
RST_CLK__HS_CONTROL EQU RST_CLK__CPU_CLOCK+1
RST_CLK__PER_CLOCK EQU RST_CLK__HS_CONTROL+1
RST_CLK__PLL_CONTROL EQU RST_CLK__PER_CLOCK+1
;//-- Вектор адресов регистров периферии
vaReg
_BKP__REG_0E DCD __BKP__REG_0E
_EEPROM__CMD DCD __EEPROM__CMD
_RST_CLK__CLOCK_STATUS DCD __RST_CLK__CLOCK_STATUS
_RST_CLK__CPU_CLOCK DCD __RST_CLK__CPU_CLOCK
_RST_CLK__HS_CONTROL DCD __RST_CLK__HS_CONTROL
_RST_CLK__PER_CLOCK DCD __RST_CLK__PER_CLOCK
_RST_CLK__PLL_CONTROL DCD __RST_CLK__PLL_CONTROL
;//-- реализация на ассемблере (адресация база+смещение_константа)
;//---------------------------------------------------------------
;//-- R5 vaReg (Базовый адрес вектора адресов)
LDR R5,=vaReg
;LDR R3,_RST_CLK__PER_CLOCK
LDR R3,[R5,#RST_CLK__PER_CLOCK]
LDR R1,=1<<27
LDR R0,[R3,#0]
ORRS R0,R1
STR R0,[R3]
...
На адресацию база+смещение_константа есть ограничение – смещение, не более 32 компонент из 4 байтов.
На адресацию база+смещение_регистр ограничений нет.
Ассемблер – язык тонкой детализации и больших возможностей, но программирование на нем требует кропотливого труда и времени, имеет плохие возможности переносимости с одной технической платформы на другую. Применение имеет смысл для создания качественного ПО, оптимизированного по быстродействию и расходуемой памяти.
Микроконтроллерные информационно-управляющие сети,- область ПО, где применение ассемблера может быть оправдано.
Рассматривать будем отечественный авиационный 32-разрядный микроконтроллер 1986ВЕ1Т, производства Миландр и систему программирования Keil 5.
1986ВЕ1Т имеет набор16-разрядных инструкций (Thumb) архитектуры ARM v6-M (включающий 32-битные Thumb-2 инструкции, такие как BL, MRS, MSR, ISB, DSB и DMB).
С точки зрения доступности, все физические регистры микроконтроллера, занимают часть общего адресного пространства системы. Всё множество регистров обычно имеет общий базовый адрес.
Множество регистров принято делить на блоки, которые закрепляются за внешними устройствами. Блок имеет свой подадрес. Регистр в блоке также имеет свой адрес (смещение).
Абсолютный адрес регистра является константой и может быть получен по формуле:
PERIFERIJA+(АДРЕС_БЛОКА<<12)+(НОМЕР_РЕГИСТА_В_БЛОКЕ<<2)
или просто задан как константа.
В дальнейшем необходимые абсолютные адреса регистров периферии в программе можно сгруппировать в векторы адресов. Как-то так:
PERIFERIJA EQU 0x40000000 ;//-- Базовый адрес регистров периферии
;//============================================================
;//== Регистры управления контроллером Flash памяти программ ==
;//============================================================
;//-- Определение абсолютных адресов регистров
EEPROM_CNTRL EQU 0x18 ;//-- Базовый адрес блока регистров EEPROM
__EEPROM__CNTRL EQU PERIFERIJA+(EEPROM_CNTRL<<12)
__EEPROM__CMD EQU __EEPROM__CNTRL+(0x00<<2) ;//-- Регистр управления EEPROM
__EEPROM__ADR EQU __EEPROM__CNTRL+(0x01<<2) ;//-- Адрес регистра
__EEPROM__DI EQU __EEPROM__CNTRL+(0x02<<2) ;//-- Регистр данных на запись
__EEPROM__DO EQU __EEPROM__CNTRL+(0x03<<2) ;//-- Регистр считанных данных
__EEPROM__KEY EQU __EEPROM__CNTRL+(0x04<<2) ;//-- Регистр ключа
;//-- Определение индексов регистров
EEPROM_CMD EQU 0 ;//-- Регистр управления EEPROM
EEPROM_ADR EQU EEPROM_CMD+1 ;//-- Адрес регистра
EEPROM_DI EQU EEPROM_ADR+1 ;//-- Регистр данных на запись
EEPROM_DO EQU EEPROM_DI+1 ;//-- Регистр считанных данных
EEPROM_KEY EQU EEPROM_DO+1 ;//-- Регистр ключа
;//-- Задание абсолютных адресов регистров вектором
vaRegEEPROM ;//-- Вектор адресов регистров контроллера Flash
DCD __EEPROM__CNTRL
DCD __EEPROM__CMD
DCD __EEPROM__ADR
DCD __EEPROM__DI
DCD __EEPROM__DO
DCD __EEPROM__KEY
Обычно, каждый блок регистров и каждый регистр в блоке имеет уникальное имя. Для записи данного факта использую следующий прием:
EEPROM__CNTRL
Имя блока регистров отделяю от имени регистра в блоке двумя подчеркиваниями. А-ля версия ассемблера для записи выражений типа:
EEPROM.CNTRL или EEPROM->CNTRL
Далее, текст вида __EEPROM__CMD определяет значение регистра.
Текст вида _EEPROM__CMD определяет адрес значения __EEPROM__CMD.
Текст вида EEPROM__CMD определяет индекс компоненты __EEPROM__CMD в векторе vaRegEEPROM.
Регистры в микроконтроллере адресуются по правилам доступа к памяти микроконтроллера и требуют выравнивания адреса на 4 (xx<<2).
Специфика адресации памяти в данном микроконтроллере требует — для доступа к регистру периферии из программы необходимо, чтобы его адрес содержался в L-регистре (R0-R7) центрального процессора (или мог быть получен, как база+смещение, из регистров ЦП).
Как загрузить абсолютный адрес регистра периферии (4 байта) в регистр ЦП из вектора адресов? Реально можно воспользоваться 3-мя способами:
— адресацией относительно счетчика команд (PC),
— адресацией базовый_адрес+смещение_константа,
— адресацией базовый_адрес+смещение_регистр.
Рассмотрим реальный пример:
;//== Инициализация микроконтроллера
;//=================================
;//-- Исходник на C
;//----------------
;RST_CLK->PER_CLOCK |= (1 << 27);
;temp = BKP->REG_0E;
;temp &= 0xFFFFFFC0;
;BKP->REG_0E = temp | (5 << 3) | 5; // SelectRI = 0x5, LOW = 0x5; (for frequency below 40 MHz);
;RST_CLK->HS_CONTROL = 0x00000001; //HSE — On; Oscillator mode
;while((RST_CLK->CLOCK_STATUS&0x04)!=0x04); //Wait until HSE not ready
;RST_CLK->CPU_CLOCK = 0x00000002; //CPU_C1 = HSE (8MHz)
;RST_CLK->PLL_CONTROL=(1<<2)|(2<<8); //CPU PLL On, PLL MULL = 2
;while((RST_CLK->CLOCK_STATUS&0x02)!=0x02); //Wait until CPU PLL not ready
;RST_CLK->PER_CLOCK|=0x08; //EEPROM_CTRL Clock enable
;EEPROM->CMD=0; //EEPROM Delay = 0
;RST_CLK->PER_CLOCK&=(~0x08); //EEPROM_CTRL Clock disable
;RST_CLK->CPU_CLOCK = 0x00000106; //CPU Clock = HSE (24MHz)
;RST_CLK->PER_CLOCK |= 1<<24; //clock of PORTD ON
;//-- реализация на ассемблере (адресация относительно PC)
;//-------------------------------------------------------
LDR R3,_RST_CLK__PER_CLOCK ;//-- R3 < — адрес регистра RST_CLK__PER_CLOCK
LDR R1,=(1<<27) ;//-- R1 < — константа
LDR R0,[R3,#0] ;//-- R0 < — содержимое регистра периферии
ORRS R0,R1 ;//-- R0 < — R0 orr R1
STR R0,[R3] ;//-- RST_CLK->PER_CLOCK |= (1 << 27)
LDR R3,_BKP__REG_0E
MOVS R1,#(5<<3):OR:(5<<0)
MOVS R2,#(7<<3):OR:(7<<0)
LDR R0,[R3]
BICS R0,R2
ORRS R0,R1
STR R0,[R3]
LDR R3,_RST_CLK__HS_CONTROL
MOVS R0,#1
STR R0,[R3]
LDR R3,_RST_CLK__CLOCK_STATUS
MOVS R1,#4
vm00_fnClkConfig_1
LDR R0,[R3]
ANDS R0,R1
BEQ vm00_fnClkConfig_1
LDR R3,_RST_CLK__CPU_CLOCK
MOVS R0,#2
STR R0,[R3]
LDR R3,_RST_CLK__PLL_CONTROL
LDR R0,=((2<<8):OR:(1<<2))
STR R0,[R3]
LDR R3,_RST_CLK__CLOCK_STATUS
MOVS R1,#(1<<1)
vm00_fnClkConfig_2
LDR R0,[R3]
ANDS R0,R1
BEQ vm00_fnClkConfig_1
LDR R3,_RST_CLK__PER_CLOCK
MOVS R1,#(1<<3)
LDR R0,[R3]
ORRS R0,R1
STR R0,[R3]
LDR R3,_EEPROM__CMD
MOVS R1,#0
STR R0,[R3]
LDR R3,_RST_CLK__PER_CLOCK
MOVS R1,#(1<<3)
LDR R0,[R3]
BICS R0,R2
STR R0,[R3]
LDR R3,_RST_CLK__CPU_CLOCK
LDR R1,=0x106
STR R0,[R3]
LDR R3,_RST_CLK__PER_CLOCK
LDR R1,=(1<<24)
LDR R0,[R3]
ORRS R0,R1
STR R0,[R3]
;//-- Индексы
BKP__REG_0E EQU 0
EEPROM__CMD EQU BKP__REG_0E+1
RST_CLK__CLOCK_STATUS EQU EEPROM__CMD+1
RST_CLK__CPU_CLOCK EQU RST_CLK__CLOCK_STATUS+1
RST_CLK__HS_CONTROL EQU RST_CLK__CPU_CLOCK+1
RST_CLK__PER_CLOCK EQU RST_CLK__HS_CONTROL+1
RST_CLK__PLL_CONTROL EQU RST_CLK__PER_CLOCK+1
;//-- Вектор адресов регистров периферии
vaReg
_BKP__REG_0E DCD __BKP__REG_0E
_EEPROM__CMD DCD __EEPROM__CMD
_RST_CLK__CLOCK_STATUS DCD __RST_CLK__CLOCK_STATUS
_RST_CLK__CPU_CLOCK DCD __RST_CLK__CPU_CLOCK
_RST_CLK__HS_CONTROL DCD __RST_CLK__HS_CONTROL
_RST_CLK__PER_CLOCK DCD __RST_CLK__PER_CLOCK
_RST_CLK__PLL_CONTROL DCD __RST_CLK__PLL_CONTROL
;//-- реализация на ассемблере (адресация база+смещение_константа)
;//---------------------------------------------------------------
;//-- R5 vaReg (Базовый адрес вектора адресов)
LDR R5,=vaReg
;LDR R3,_RST_CLK__PER_CLOCK
LDR R3,[R5,#RST_CLK__PER_CLOCK]
LDR R1,=1<<27
LDR R0,[R3,#0]
ORRS R0,R1
STR R0,[R3]
...
На адресацию база+смещение_константа есть ограничение – смещение, не более 32 компонент из 4 байтов.
На адресацию база+смещение_регистр ограничений нет.