Вы находитесь на странице: 1из 36

Микроконтроллеры и системы на кристалле

Лекция
AVR - ассемблер
(Часть 2)

Непомнящий Олег Владимирович


План лекции
1. Системы счислений и представление чисел.
2. Организация циклов и ветвлений.
3. Стек и указатель стека.
4. Подпрограммы.
5. Система прерываний.
6. Директивы
7. Работа с портами ввода-вывода.
Представление чисел

Двоичный разряд (или бит), расположенный справа, называется


младшим значащим разрядом (Least Significant Bit – LSB.
Самый старший бит двоичного слова (например, 7-й бит байта)
называется старшим значащим разрядом (Most Significant Bit - MSB).
Нулевой бит показывает количество единиц в числе. Бит,
расположенный левее (бит 1), показывает количество «двоек»,
следующий бит (бит 2) показывает количество «четверок» и так далее,
т.е. номер бита соответствует степени двойки, представляемой этим
битом. Нумерация битов ведется справа налево.

X(2) = 11011(2) = 1• 24 + 1 • 23 + 0 • 22 + 1 • 21 + 1 • 20 = 27(10)


Запись числа и Загрузка
10 ; без префикса – десятичная
0x0A ; префикс 0х – шестнадцатеричная
$0A ; префикс $ – шестнадцатеричная
012 ; префикс 0 - восьмеричная
0b00001010 ; префикс 0b - двоичная
Инструкцция LDI (Load Immediate) – загружает 8-битовое число в регистр
общего назначения

Пример:
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

Эквивалентное число X(2) = 1111 0000 0001 0101 1010

Исходное число X(8) = 3 7 5 0 2

Эквивалентное число X(2) = 011 111 101 000 010

Пример – одно и то же число в Из двоичной в двоичную – по 1 биту с права


различных системах счисления: Из двоичной в четверичную – по 2 бита
LDI r12,0x0А Из двоичной в восьмеричную – по 3 бита
LDI r12,0b00001010 Из двоичной в шестнадцатеричную – по 4
LDI r12,012
LDI r12,10
D7 D6 D5 D4 D3 D2 D1 D0
LDI r12,$0A
Допишем 0
Прямой обратный и дополнительный код числа
используется для замены вычитания сложением
Прямой Обратный Дополнительный

Положительное 0001 1000 0001 1000 0001 1000


добавим перевернем перевернем и добавим 1 к LSB
знаковый +1
разряд

Отрицательное 1 0001 1000 1110 0111 1110 1110

А теперь давайте посмотрим, что будет,


если мы операцию вычитания реализуем
как b+(-c). Для примера рассмотрим а=15-4.
Логические и арифметические операции
Пример - 1: Пример - 2: Пример - 3
ADD R25,R9 ; R25=R25+R9 ldi r16,19 ldi r16,19
SUB R25,R9 ; R25=R25-R9 ldi r20,95 ldi r20,95
INC R25 ; R25=R25+1 ldi r21,5 add r16,r20
DEC R25 ; R25=R25-1 add r16,r20 ldi r20,5
SER R22 ; R22=0xFF=255 add r16,r21 add r16,r20
SUBI R22,2 ; R22=R22-2
CLR R22 ; Clear R22=0 Выберите пример 2 или 3

Пример – 4: Пример – 5: Пример – 6:


Ldi r16,1 Ldi r16,1 Ldi r16,1<<2
Ldi r17,2 Lsl r16
Mul r16,r17 Lsl r16
Выберите пример 4,5 или 6
Mul r16,r17
Логические операции над двоичными числами
Carry flag (сдвиги)
SREG C 0

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 ; Окончание макроса

; Пример вызова макроса вывода данных в порт


outi DDRB, 0b01111111
outi DDRC, 0b00111111
outi DDRD, 0b00000001
outi PORTD, 0b11111111

В данном случае Макрос вызывается по своему имени outi. На первом месте @0


находится регистр DDRB, а на втором @1 находится переменная 0b01111111 ,
которая записывается в данный регистр. Переменные указываются не явно с лева
на право @0, @1, @2 ... @N
Система прерываний - программный счетчик
PC – Программный счетчик (указывает на адрес текущей команды)
PC
$000 После сброса $000 rjmp Init ; таблица векторов
$001 rjmp P_Int0 ; прерываний от
PC $002 rjmp P_Int1 ; встроенных устройств
Выполнен
$Init нулевой вектор
$003 …

PC Init: ;начало основной программы


Адрес текущей Ldi r22,0
$??? команды в основной …
программе
P_Int0: ; подпрограмма для вектора $001
Пришел запрос на прерывание Push r22
PC … ; тело подпрограммы
Pop r22
$001 Адрес вектора reti
PC
Неиспользуемая flash
$P_Int0 Выполнен вектор 001
$FFF
Система прерываний – вход/выход в подпрограмму

Основная программа

Сохранить программный счетчик


Запретить прерывания
Делает процессор
Сохранить регистры
Тело подпрограммы обработки
Задача пользователя прерывания
Восстановить регистры
Делает процессор
Разрешить прерывания
Восстановить программный счетчик

Основная программа
Система прерываний – пример
.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, (1<<6)|(1<<7) Установить


2 out GICR, Temp

ldi Temp, (1<<INT0)|(1<<INT1)


3 out GICR, Temp

Значение №бита
ldi Temp, 0b00001011
1 out MCUCR, Temp

ldi Temp, (1<<0)|(1<<1) |(1<<3) Установить


2 out MCUCR, Temp

ldi Temp, (1<<ISC00)|(1<<ISC01) |(1<<ISC11)


3 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, (1<<CS00)|(1<<CS01)


3 out TCCR0, Temp

№бита
Значение
ldi Temp, 0b00000001
1 out TIMSK, Temp

ldi Temp, (1<<0)


2 out TIMSK, Temp
Установить

ldi Temp, (1<<TOIE0)


3 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.

Вот пример работы директив


.CSEG
.ORG 0x0000
RJMP Start ; перепрыгиваем таблицу векторов.
.ORG INT0addr ; External Interrupt0 Vector Address
RJMP INT0_expection
.ORG INT1addr ; External Interrupt1 Vector Address
.ORG OC2addr ; Output Compare2 Interrupt Vector Address
RJMP PWM_1

.ORG 0х0032 ; Начало основной программы


Start:
LDI R16,0x54 ; и понеслась
Äèðåêòèâû (ïðèìåð áåç êîììåíòàðèåâ)
.DSEG Inf: rjmp inf
Massiv: . BYTE 7
.CSEG INT0_expection:
.DEF Counter = r18 LD temp,Y+
.DEF Temp = r19 ST Z+,temp
.ORG 0x0000 dec Counter
RJMP Start brne INT0_expection
.ORG INT0addr reti
RJMP INT0_expection .macro stack_setup
Start: ldi Temp, low(@0)
LDI r30,LOW(Massiv) out SPL, Temp
LDI r31,HIGH(Massiv) ldi Temp, high(@0)
LDI r28,LOW(Sourse*2) out SPH, Temp
LDI r29,HIGH(Sourse*2) .endm
LDI Counter,7
stack_setup SRAMEnd Sourse: . db 10, 0xFF, ’A’, -3, 022, $1E,0b11110000
СВ
Работа с портами ввода-вывода
ЕТ
ОД
ИО
ДЫ

КНОПКИ
Порты ввода/вывода – Задача
Отследим нарастающий фронт сигнала.

Кнопка нажата и удерживается


Кнопку не нажали Включить светодиод Кнопку отпустили
Порты ввода/вывода – Решение
Отследим нарастающий фронт сигнала.
.include “m32adef.inc” ;подключаем файл m2: in r22,pind ;уже отпустили?
;символьного описания имен cpi r22,255
.CSEG ;это сегмент команд во Flash ПЗУ brne m2 ;еще нет, ждем
ldi R20,0xFF ;настройка порта В на вывод ldi r20,0 ;ДА! включить
out ddrb,r20 ;к нему подключены светодиоды out portb,r20;все светодиоды
ldi r20,0 ;настройка порта C на ввод m3: rjmp m3 ;stop
out ddrc,r20 ;к нему подключены кнопки
m1: ldi r20,0b11111111 ;все светодиоды выключить
out portb,r20 ;
in r20,pind ;прочитать кнопки
cpi r20,255 ;есть хоть одна нажатая?
brne m2 ;ДА кнопку нажали
rjmp m1 ;НЕТ кнопку НЕнажали, ждем
Íàãëÿäíûé ìàòåðèàë äëÿ ëåêöèè

1. Ëàáîðàòîðíûé ñòåíä STK-500


Ñïàñèáî çà âíèìàíèå
(âàøè âîïðîñû ?)
Непомнящий Олег Владимирович

Вам также может понравиться