Продолжение, начало
Лабораторная работа 6: знакомство с промышленной реализацией архитектуры MIPS на примере pic32 (3) << Примеры
Задание 1 - зажечь лампочку ассемблером MIPS
Например мы хотим зажечь светодиодную лампочку, подключенную к нашей знакомой ножке pic32 #44 (chipKIT #8), которая одновременно с этим является 10й ножкой внутри порта PORTD, т. е. к ножке RD10.
Программа на ассемблере MIPS32 выглядит следующим образом:
task1-light-asm/light.S # Зажечь лампочку на ножке RD10 (chipKIT #8) ассемблером
main: .global main # Помечаем метку main как глобальную
# Установить ножку RD10 как вывод - установить бит TRISD[10] в 1 - отправить 0x400 в TRISDCLR
li $t1, 1 << 10
la $t2, 0xBF8860C4 #TRISDCLR
sw $t1, 0 ($t2)
# Установить значение 1 на ножке RD10 - установить бит LATD[10] в 1 - отправить 0x400 в LATDSET
li $t1, 1 << 10
la $t2, 0xBF8860E8 #LATDSET
#la $t2, 0xBF8860E4 #LATDCLR
sw $t1, 0 ($t2)
Разберем по строкам.
Отмечаем точку входа в программу - этот код будет выполняться сразу после загрузки контроллера:
main: .global main # Помечаем метку main как глобальную
Теперь устанавливаем режим работы ножки на вывод - записываем 0 в 10й бит регистра TRISD, т.е. нам нужно сделать так, чтобы ячейка памяти по адресу 0xBF88_60C0 выглядела следующим образом:
0xBF88_60C0: [0...0 0...0 xxxxx0xx xxxxxxxx]
Достигаем этот результат за 3 команды:
# Установить ножку RD10 как вывод - установить бит TRISD[10] в 0 - отправить 0x400 в TRISDCLR
li $t1, 1 << 10
la $t2, 0xBF8860C4 #TRISDCLR
sw $t1, 0 ($t2)
Загружаем двоичное значение 100'00000000 (оператор сдвига '<<' перемещает 1 на 10 бит влево) в промежуточный регистр $t1 при помощи псевдо-команды li (load immediate):
li $t1, 1 << 10
Загружам адрес целевой ячейки памяти 0xBF8860C4 (регистр TRISDCLR) в промежуточный регистр $t2 при помощи псевдо-команды la (load address).
la $t2, 0xBF8860C4 #TRISDCLR
Отправляем двоичное значение 100'00000000 в регистр TRISDCLR (ячейка памяти по адресу 0xBF8860C4) при помощи уже известной нам команды sw (store word) - таким образом сбрасываем 10й бит регистра TRISD в 0 и ножка RD10 настраивается на вывод:
sw $t1, 0 ($t2)
Или тоже самое словами - записываем в регистр TRISDCLR значение (1 отмечает сбрасываемый бит):
0xBF88_60C4: [0...0 0...0 00000100 00000000]
и в TRISD получаем нужное нам:
0xBF88_60C0: [0...0 0...0 xxxxx0xx xxxxxxxx] (биты, помеченные как 'x' останутся нетронутыми).
Замечание 1: В этом и заключается механизм работы вспомогательных регистров CLR, SET и INV - они позволяют манипулировать выбранными битами главного регистра не затрагивая при этом остальные биты. Изменяется только тот бит, который во вспомогательном регистре помечен 1, остальные биты (во вспомогательном регистре помеченные нулями) в главном регистре остаются неизменными. Вспомогательный регистр CLR сбрасывает выбранные биты главного регистра в 0, вспомогательный регистр SET устанавливает выбранные биты главного регистра в 1, вспомогательный регистр INV инвертирует текущие значения выбранных битов главного регистра (меняет 1 на 0, а 0 на 1).
Замечание 2: команды li и la называются псевдо-командами потому, что они позволяют загрузить в регистр любое 32хбитное значение одной строкой кода на ассемблере MIPS, хотя, как мы помним, в одну 32хбитную машинную команду уместить произвольное 32хбитное число в качестве аргумента никак не получится, т.к. должно остаться место как минимум для кода операции. Хитрость в том, что подобные псевдо-команды обрабатываются препроцессором компилятора MIPS - он проверяет, сколько бит занимает аргумент и в случае необходимости при генерации машинного кода разбивает загрузку числа в регистр на нескольких машинных команд, хотя в программе на ассемблере они все умещаются в одну строку.
Зажигаем лампочку - подаем плюс на ножку RD10, т. е. записываем значение 1 в 10й бит регистра LATD:
LATD10 = 1:
0xBF88_60E0: [0...0 0...0 xxxxx1xx xxxxxxxx]
Проворачиваем всю процедуру аналогичным образом - для установки выбранного 10го бита регистра LATD в 1 теперь используем вспомогательный регистр LATDSET:
# Установить значение 1 на ножке RD10 - установить бит LATD[10] в 1 - отправить 0x400 в LATDSET
li $t1, 1 << 10
la $t2, 0xBF8860E8 #LATDSET
sw $t1, 0 ($t2)
Подключаем плату,
компилируем программу, загружаем код программы:
> make
> make upload
Лампочка загорается:
Теперь для чистоты эксперимента погасим лампочку аналогичным образом:
Подаем минус на ножку, т. е. 0 в 10й бит регистра LATD:
LATD10 = 0:
0xBF88_60E0: [0...0 0...0 xxxxx0xx xxxxxxxx]
Просто заменям LATDSET на LATDCR:
li $t1, 1 << 10
la $t2, 0xBF8860E4 #LATDCLR
sw $t1, 0 ($t2)
> make; make upload
Лампочка гаснет:
Кто не верит на слово, вот видео:
Работа с регистрами SFR pic32 на ассемблере: вывод значения на ножку from
1i7 on
Vimeo.
Замечание: в примере выше в коде программы адреса регистров указаны как абсолютные численные значения для лучшего погружения в тему, т.к. таким образом можно наглядно убедиться, что адреса из таблиц документации действительно работают на реальном железе. В нормальной ситуации все эти числа конечно же прописаны в специальных именованных константах, которые подключаются к программе через заголовочный файл p32xxxx.h. В нормальном виде этот же код выглядит следующим образом:
task1_1-light-asm/light.S #include
####################################################################
# Зажечь лампочку на ножке RD10 (chipKIT #8) ассемблером
main: .global main # Помечаем метку main как глобальную
# Установить ножку RD10 как вывод - установить бит TRISD[10] в 0 - отправить 0x400 в TRISDCLR
li t1, 1 << 10
la t2, TRISDCLR #0xBF8860C4
sw t1, 0 (t2)
# Установить значение 1 на ножке RD10 - установить бит LATD[10] в 1 - отправить 0x400 в LATDSET
li t1, 1 << 10
la t2, LATDSET #0xBF8860E8
#la t2, LATDCLR #0xBF8860E4
sw t1, 0 (t2)
Задание 2 - считать значение с ножки ассемблером MIPS
Программа считывает значение с ножки RF1 (chipKIT #4), к которой можно или подключить кнопку или напрямую подавать на провод + или -, и в зависимости от полученного значения зажигает или гасит лампочку RD10 (chipKIT #8).
В бесконечном цикле читаем значение RF1 и тут же устанавливаем значение лампочки RD10.
task2-input-asm/input.S # Зажечь лампочку RD10 (chipKIT #8) по нажатию кнопки RF1 (chipKIT #4)
main: .global main # Помечаем метку main как глобальную
# Установить ножку RF1 как ввод - установить бит TRISF[1] в 1 -
# отправить 0x1 в регистр TRISFSET
li $t1, 1 << 1
la $t2, 0xBF886148 #TRISFSET
sw $t1, 0 ($t2)
# Установить ножку RD10 как вывод - установить бит TRISD[10] в 0 -
# отправить 0x400 в регистр TRISDCLR
li $t1, 1 << 10
la $t2, 0xBF8860C4 #TRISDCLR
sw $t1, 0 ($t2)
loop:
# Считать значение с ножки RF1 - получить значение бита PORTF[1]
la $t2, 0xBF886150 #PORTF
lw $t1, 0 ($t2)
ext $t3, $t1, 1, 1
addi $t4, $0, 1
beq $t3, $t4, button_on
# кнопка выключена
# Задать значение 0 на ножке RD10 - установить бит PORTD[10] в 0 -
# отправить 0x400 в регистр LATDCLR
li $t1, 1 << 10
la $t2, 0xBF8860E4 #LATDCLR
sw $t1, 0 ($t2)
j loop
button_on:
# кнопка включена
# Задать значение 1 на ножке RD10 - установить бит PORTD[10] в 1 -
#отправить 0x400 в регистр LATDSET
li $t1, 1 << 10
la $t2, 0xBF8860E8 #LATDSET
sw $t1, 0 ($t2)
j loop
Ножка RF1 в режим ввода:
# Установить ножку RF1 как ввод - установить бит TRISF[1] в 1 -
# отправить 0x1 в регистр TRISFSET
li $t1, 1 << 1
la $t2, 0xBF886148 #TRISFSET
sw $t1, 0 ($t2)
Ножка RD10 в режим вывода:
# Установить ножку RD10 как вывод - установить бит TRISD[10] в 0 -
# отправить 0x400 в регистр TRISDCLR
li $t1, 1 << 10
la $t2, 0xBF8860C4 #TRISDCLR
sw $t1, 0 ($t2)
Метим вход в бесконечный цикл:
loop:
Считываем значение с ножки RF1 - читаем значение региста PORTF и определяем значение 1го бита:
# Считать значение с ножки RF1 - получить значение бита PORTF[1]
la $t2, 0xBF886150 #PORTF
lw $t1, 0 ($t2)
ext $t3, $t1, 1, 1
Здесь из нового - команда ext (extract bit field) - извлечение битового поля.
Синтаксис:
ext rt, rs, pos, size
Принцип работы:
rt = ИзвлечьПоле(rs, pos, size)
В нашем случае:
rs=$t1 - регистр с текущим значением всего регистра PORTF, из которого будет извлекать нужный бит;
pos=1 - позиция нужного бита - для RF1 это 1;
size=1 - нам нужен один бит;
rt=$t3 - здесь будет результат: 1, если на входе RF1 1; 0, если на входе RF1 0.
Сравниваем результат $t3 с 1: если на входе 1, то прыгаем на метку button_on ("зажечь лампочку"):
addi $t4, $0, 1
beq $t3, $t4, button_on
а если 0, то просто двигаемся дальше, тушим лампочку и прыгаем на старт бесконечного цикла loop считать новое значение на входе:
# кнопка выключена
# Задать значение 0 на ножке RD10 - установить бит PORTD[10] в 0 -
# отправить 0x400 в регистр LATDCLR
li $t1, 1 << 10
la $t2, 0xBF8860E4 #LATDCLR
sw $t1, 0 ($t2)
j loop
Метка button_on ("зажечь лампочку") - зажигаем лампочку и прыгаем на старт бесконечно цикла loop считать новое значение на входе:
button_on:
# кнопка включена
# Задать значение 1 на ножке RD10 - установить бит PORTD[10] в 1 -
#отправить 0x400 в регистр LATDSET
li $t1, 1 << 10
la $t2, 0xBF8860E8 #LATDSET
sw $t1, 0 ($t2)
j loop
Компилируем, загружаем:
> make; make upload
Подключаем провод RF1 (chipKIT #4) к плюсу - лампочка горит:
Подключаем провод RF1 (chipKIT #4) к минусу - лампочка гаснет:
Все правильно, но можно еще раз проверить на видео:
Работа с регистрами SFR pic32 на ассемблере: ввод значения с ножки from
1i7 on
Vimeo.
Еще пара аналогичных примеров на Си и для лабы достаточно.
Код лабы на github,
подсветка синтаксиса.