Академический Документы
Профессиональный Документы
Культура Документы
Лекция
AVR - ассемблер
(Часть 2)
Пример:
LDI R16,53 ; Загрузить в R16 десятичное 53
LDI r12,0x1F ; Загрузить в R16 шестнадцатеричное 1F
LDI r04,$1F99 ; Неправильная инструкция
LDI r12,0b11110001 ; Загрузить в R12 двоичное 11110001 или F1
LDI r22,012 ; Загрузить в R22 восьмеричное 012
Перевод из одной системы в другую
X(2) = 11011(2) = 1• 24 + 1 • 23 + 0 • 22 + 1 • 21 + 1 • 20 = 27(10)
Задача: Перевести число 47 в двоичную СС и десятичную дробь 0,761 в двоичную СС с
точностью 2-5 .
Решение: В первом случае делим на 2 до тех пор пока остаток не меньше основания то есть 0
или 1. Во втором случае заданное число умножаем последовательно 5 раз на 2.
0,761
47 2
2
46 23 2
Направление записи
1,522
двоичного числа
1 22 11 2 2 ОТВЕТ
1 10 5 2 1,044 Х(10) = 0,761
1 4 2 2 2
Х2 = 0,11000
Старший разряд
1 2 1 0,088
числа в новой
0 2
двоичной системе
0,176
ОТВЕТ 47(10) = 101111(2). 2
0,352
Табличный способ и простая математика
Основание системы счисления
S=10 S=2 S=8 S=16 ПРИМЕР 1
0 0 0 0 Ldi r 22, 0x0A
1 1 1 1
Ldi r23, $05
2 10 2 2
3 11 3 3 Add r23,r22 ; ответ 15 или 0x0F или 017
4 100 4 4 ПРИМЕР 2
5 101 5 5
Ldi r 22, 200
6 110 6 6
7 111 7 7 Ldi r23, 55
8 1000 10 8 Add r23,r22 ; ответ 255 или 0xFF или 0777
9 1001 11 9 ПРИМЕР 3
10 1010 12 А
11 1011 13 В Ldi r 22, 0xFF
12 1100 14 С Ldi r23, 1
13 1101 15 D Add r23,r22 ; ответ 0 + бит С - переполнение
14 1110 16 Е
15 1111 17 F КАК ЗАПОМНИТЬ 0 – он и в Африке 0,
16 10000 20 10
17 10001 21 11
а 1010 – это 10 или первая буква
18 10010 22 12 алфавита А, остальное посчитаем
Перевод чисел с системой счисления кратной двум
Исходное число X(16) = F 0 1 5 A
D7 D6 D5 D4 D3 D2 D1 D0 D7 D6 D5 D4 D3 D2 D1 D0
Пример - 1 Пример - 2
stc ; Установить бит C ldi r23,0b11110000
ldi r23,0b11110000 lsr r23 ; логический сдвиг
ror r23 ; циклический в право
В результате в r23 будет 0b01111000
В результате в r23 будет 0b11111000 все биты сдвинуты в право. При
Все биты сдвинуты в право по кругу этом «из воздуха» к нам прилетел
через бит С в регистре статуса, а C=0 ноль в старший разряд
Организация циклов и ветвлений
Пример 2 (сравнение):
Используем команды безусловного
…
(RJMP) и безусловного перехода
one_more:
(BRNE, BREQ, BRLO, BRSH и др.)
ldi r22,Z+ ; число из памяти
ldi r23,Y+ ; еще одно
Пример 1 (задержка): cp r23,r22 ; сравнить
ldi r22,100 ; верхний счетчик brne no_eq ; если не равно то
m2: ldi r23,255 ; нижний счетчик brlo low ; иначе если меньше то
m1: dec r23 ; нижний цикл ldi r25,1 ; значит больше
brne m1 rjmp one_more ; goto на начало (вечно!)
dec r22 ; верхний цикл no_eq:
brne m2 ldi r25,0
stop: rjmp stop ; остановка
done: ; конец циклам
low:
ldi r25,$FF
Пусть X=5, Y=7, что будет в R25? rjmp one_more ; goto на начало (вечно!)
Программная модель AVR
16-bit 8-bit
$0000
$000 32 регистра
общего
назначения $001F
$0020
FLASH 64 регистра
4K x 16 периферийных 8-bit
устройств
$005F
$0060
$000
Flash – память
Память данных
программ EEPROM
(ОЗУ)
SRAMEnd 512 x 8
Внешнее ОЗУ
$FFFF $1FF
$FFF
FLASH SRAM EEPROM
Стек и указатель стека
Стек – специальным образом организованный участок в
памяти действующий по принципу «первый зашел –
последний вышел»
Command segment
Data segment
FLASH
RAM
Address
Address
Пример Обмен $0000 ldi r30, low (SRamEnd)
$0060 ?
регистров … ldi r31, high (SRamEnd)
…
ldi r16,5 out spl, r30
$045C ?
out sph, r31
ldi r17,22 $045D ?
push r17 $045E 5
push r16 SRamEnd 22 SP
pop r17
$04 $5F
pop r16 SRamEnd=$045F
Подпрограммы
.include “atmega8535def.inc”
.CSEG
ldi R20,0xFF ; настроить порт В на выход Delay: push r22
out ddrb,r20 ; там светодиоды push r23
ldi r20,0 ; настроить порт С на вход
ldi r22,10
out ddrc,r20 ; там кнопки
ldi r20,0b11111110 ; установить в 0 LSB m4: ldi r23, 255
m5: out portb,r20 ; включить один светодиод m3: dec r23
in r21,pind ; читаем кнопки brne m3
m2: cpi r21,255 ; хоть одна нажата ? dec r22
brne m1 ; YES, «бегущий огонек»
brne m4
rjmp m2 ; NO, ждем нажатия
m1: rcall delay ; задержка чтобы увидеть pop r23
rol r20 ; сдвинуть огонек pop r22
rjmp m5 ; и вывести на светодиоды ret
В стеке автоматически по команде rcall будет сохранен текущий программный
счетчик - адрес возврата, затем уже в подпрограмме 2 регистра командами push.
При выходе вытолкнутся сначала регистры один за другим по команде pop, а затем
автоматически содержимое программного счетчика по команде ret.
Макросы (можно передавать переменные)
Макрос - это набор команд и инструкций, которые группируются в единую команду.
В процессе компиляции Макросы заменяются на последовательности команд
микроконтроллера. Макрос на языке программирования Assembler состоит из
следующих частей:
1. .macro - директива начала макроса, после объявления макроса в той же строке
объявляется имя этого макроса;
2. .endm - директива окончания макроса;
3. @0 - запись переменной (в теле макроса с лева на право от 0 до N);
4. Тело макроса.
; Пример объявления макроса
.macro outi ; Директива с объявлением имени
ldi r16, @1 ; Установка значения в регистр
out @0, R16 ; Вывод данных в порт
.endm ; Окончание макроса
Макросы (можно передавать переменные)
; Пример объявления макроса вывода данных в порт
.macro outi ; Директива с объявлением имени
ldi r16, @1 ; Установка значения в регистр
out @0, R16 ; Вывод данных в порт
.endm ; Окончание макроса
Основная программа
Основная программа
Система прерываний – пример
.include “m32Adef.inc” ; продолжение программы
.org $000 rjmp Init ;Вектора ldi r20,0b11111111 ; выключить
.org $001 rjmp P_Int0 sei ;разрешить все прерывания
.org $002 rjmp P_Int1 main:
out PORTB,r20 ; вывести на светодиоды r20
Init: ;Сюда попадаем по сбросу rjmp main
ldi r16,high(RAMEND) ;Настоим указатель стека
out SPH,r16
ldi r16,low(RAMEND) ;Где то в памяти программ
out SPL,r16 ;расположены эти две процедуры
ser r20 ;Настроим порт В на вывод P_Int0:
out DDRB,r20 ;Там светодиоды ldi r20,0b00001111
out PORTB,r20 Reti ;команда выход из прерывания
ldi r20,0b00001011 ;int0 - нарастающим
out MCUCR,r20 ;int1 - падающим фронтом P_Int1:
ldi r20,0b11000000 ;и Int 0 и 1 оба разрешены ldi r20,0b01010101
out GICR,r20 Reti ;возврат в точку прерывания
Еще один пример с прерываниями
.include m32Adef.inc ; продолжение программы
.def Temp = r16 ldi Temp, (1<<ISC11)|(1<<ISC01)|(1<<ISC00)
.def Diods = r17 out MCUCR, Temp
ldi Temp, (1<<INT0)|(1<<INT1)
.cseg out GICR, Temp
.org $000 rjmp Init ldi Diods, 0b11111111
.org $002 rjmp P_INT0
Мы использовали сдвиг <<
.org $004 rjmp P_INT1 Main: Мы вдвигали в регистр единички
out PORTB, Diods Мы вдвигали их ISC11 и пр. раз
Init: rjmp Main Значит ISC11 – это
ldi Temp, low(RAMEND)
out SPL, Temp P_INT0: просто число !!!
ldi Temp, high(RAMEND) ldi Diods, 0b00001111 При этом мы вдвигали
out SPH, Temp reti «КОЛБАСУ» из единичек ( I )
ser Temp
То есть
out DDRB, Temp P_INT1:
out PORTB, Temp ldi Diods, 0b11110000 Мы просто установили
sei reti
нужные биты в 1
ldi Temp, 0b11000000
1 out GICR, Temp
Значение №бита
ldi Temp, 0b00001011
1 out MCUCR, Temp
Значение №бита
Пример прерывания с таймером
.include m32Adef.inc
.def Temp = r16 Main:
.def Diods = r17 out PORTB, Diods ; моргаем светодиодами
.def Timer_clk rjmp Main
.cseg ; -----------------------------------------------------------------
.org $000 rjmp Init Timer_owf: ; перевернуть
.org OVF0addr rjmp Timer_owf com Diods ; светодиоды
Init: reti
ldi Temp, 0b00000000
; -----------------------------------------------------------------
out TCCR0, Temp ;стоп таймер
TimerConf:
ldi Temp, low(RAMEND)
out SPL, Temp ldi Temp, (1<<TOIE0) ; устанавливаем бит TOIE0 - 1
ldi Temp, high(RAMEND) out TIMSK, Temp ; то есть разрешить
out SPH, Temp ; прерывания по переполнению
ser Temp ; настроить порт ldi Temp, (1<<CS02)|(1<<CS00) ; устанавливаем биты
ldi Diosd, 0b11001100 ; запись сетки ; CS02 - 1, CS00 - 1
out DDRB, Temp out TCCR0, Temp ; то есть задаем частоту
out PORTB, Temp ; предделителя 1:1024
rcall TemerConf ; настроить таймер
ret
Sei ; поехали
ldi Temp, 0b00000011
1 out TCCR0, Temp
Установить
ldi Temp, (1<<0)|(1<<1)
2 out TCCR0, Temp
№бита
Значение
ldi Temp, 0b00000001
1 out TIMSK, Temp
№бита
Значение
Äèðåêòèâû
Директива .include позволяет подключать в тело твоей программы кусок кода из
другого текстового файла. Что позволяет разбить большой исходник на кучу мелких,
чтобы не загромождать и не мотать туда сюда огромную портянку кода. Считай куда
ты воткнул .include туда и вставился кусок кода из другого файла.
Директива .def позволяет привязать к любому слову любое значение из ресурсов
контроллера — порт или регистр. Например сделал я счетчик, а считаемое значение
находится в регистре R0, а в качестве регистра-помойки для промежуточных данных
я заюзал R16. Чтобы не запутаться и помнить, что в каком регистре у меня задумано я
присваиваю им через .def символические имена.
.def schetchik = R0
.def pomoika = R16
И теперь в коде могу смело использовать вместо официального имени R0
неофицальную кличку schetchik. Одному и тому же регистру можно давать кучу имен
одновременно и на все он будет честно откликаться.
Äèðåêòèâû
Директива .equ это присвоение выражения или константы какой либо символической
метке.
Например, у меня есть константа которая часто используется. Можно, конечно,
каждый раз писать ее в коде, но вдруг окажется, что константа выбрана неверно, а
значит придется весь код шерстить и везде править, а если где-нибудь забудешь, то
получишь такую махровую багу, что задолбаешься потом ее вылавливать. Так что
нафиг, все константы писать надо через .equ!
Кроме того, можно же присвоить не константу, а целое выражение. Которое при
компиляции посчитается препроцессором, а в код пойдет уже исходное значение.
Надо только учитывать, что деление тут исключительно целочисленное. С
отбрасыванием дробной части, без какого-либо округления, а значит 1/2 = 0, а 5/2 = 2
.equ Time = 5
.equ Acсelerate = 4
.equ Half_Speed = (Accelerate*Time)/2
Äèðåêòèâû
.CSEG сегмент кода, он же флеш. После этой директивы идет тело программы, команды
процессора. Тут же можно засунуть какие ни будь данные которые не меняются,
например таблицу с заранее посчитанными значениями, статичный текст или таблицу
символов для знакогенератора. Делается это при помощи директив:
.db массив байтов. .dw массив слов — два байта.
.dd массив двойных слов — четыре байта .dq массив четверных слов — восемь байт.
Например так:
Constant: .db 10 ; или 0хAh в шестнадцатеричном коде
Message: .db "Привет лунатикам"
Words: .dw 10, 11, 12
В итоге, во флеше вначале будет лежать число 0А, затем побайтно будут хекскоды
символов фразы «привет лунатикам», а дальше 000A, 000B, 000С.
Последние числа, хоть сами и невелики, но занимают по два байта каждое, так как
объявлены как .dw.
Äèðåêòèâû
.ORG address означает примерно следующее «копать отсюда и до обеда», т.е. до конца
памяти. Данный оператор указывает с какого адреса пойдет то что после этой
диррективы. Обычно используется для создания таблицы прерываний. Еще может
применятся в сегменте данных (см. далее) для резервирования данных по
конкретному адресу.
.DSEG сегмент данных, оперативка. Те самые жалкие считанные байты. Сюда не
зазорно пихать переменные, делать тут буфера, тут же находится стек.
Тут действует дирректива .BYTE позволяющая указать на расположение данных в
памяти.
Например так:
var1: .BYTE 1
table: .BYTE 10
В первом случае мы указали переменную var1 состоящую из одного байта.
Во втором случае у нас есть цепочка из 10 байт и метка (адрес) table указывающая на
первый байт из цепочки. Адрес остальных вычисляется смещением.
Äèðåêòèâû
.EESEG сегмент EEPROM, энергонезависимая память. Можно писать, можно считывать, а при
пропаже питания данные не повреждаются.
Тут действуют те же директивы что и в flash — db, dw, dd, dq.
КНОПКИ
Порты ввода/вывода – Задача
Отследим нарастающий фронт сигнала.