Здесь представлены примеры различных программ на языке Ассемблера для микроконтроллеров Atmel AVR. Примеры выложены в виде проектов для AVR Studio под микроконтроллер ATmega16, поэтому при переносе на другие МК семейства AVR это нужно учитывать. Тактовая частота микроконтроллера во всех примерах 8 МГц (используется тактирование от внутреннего генератора). Код примеров разбит на блоки и снабжен комментариями.
Краткое описание команд Ассемблера AVR представлено здесь.
Подробное описание каждой команды представлено в AVR 8bit Instruction Set.
Для более глубокого изучения ассемблера AVR советую к прочтению книгу Юрия Ревича "Практическое программирование микроконтроллеров Atmel AVR на языке ассемблера". С беcплатным фрагментом книги можно ознакомиться здесь. Также можно изучить различные методические пособия ВУЗов, например, вот.
Также на сайте выложены примеры программ на языке Си.
Start
Заготовка стартовой инициализации микроконтроллера, в которую входят инициализация стека, очистка ОЗУ (SRAM) и регистров общего назначения (R0 - R31), а также глобальный запрет прерываний. Пример кода приведён ниже. При использовании следует помнить, что регистры ввода-вывода (порты, периферия и т.д.) не очищаются, поэтому их НЕОБХОДИМО инициализировать отдельно.
.include "m16def.inc" ; Use AtMega16A
;=================================================
; Имена регистров, а также различные константы
.equ XTAL = 8000000 ; Частота МК
.equ UART_BaudRate = 19200 ; Скорость при связи по UART
.equ UART_BaudDivider = XTAL / (16 * UART_BaudRate) - 1
.equ I2C_Frequency = 80000 ; Частота шины I2C
.equ I2C_BaudDivider = (XTAL / (8 * I2C_Frequency) - 2)
.equ Bit0 = 0b00000001
.equ Bit1 = 0b00000010
.equ Bit2 = 0b00000100
.equ Bit3 = 0b00001000
.equ Bit4 = 0b00010000
.equ Bit5 = 0b00100000
.equ Bit6 = 0b01000000
.equ Bit7 = 0b10000000
.def MulLow = R0 ; Младший регистр результата умножения
.def MulHigh = R1 ; Старший регистр результата умножения
.def Temp0 = R15 ; Регистр с нулевым значением
.def Temp1 = R16
.def Temp2 = R17
.def Temp3 = R18
.def Temp4 = R19
.def Temp5 = R20
.def Temp6 = R21
.def Temp7 = R22
.def Temp8 = R23
.def Counter = R24 ; Регистр счетчик
.def Flags = R25 ; Флаговый регистр
;=================================================
; Сегмент SRAM памяти
.DSEG
;=================================================
; Сегмент EEPROM памяти
.ESEG
;=================================================
; Сегмент FLASH памяти
.CSEG
;=================================================
; Таблица прерываний
.ORG 0x00
RJMP RESET
;=================================================
; Прерывание по сбросу, стартовая инициализация
RESET:
; Инициализация стека
LDI Temp1, LOW(RAMEND)
OUT SPL, Temp1
LDI Temp1, HIGH(RAMEND)
OUT SPH, Temp1
; Очистка ОЗУ и регистров R0-R31
LDI ZL, LOW(SRAM_START) ; Адрес начала ОЗУ в индекс
LDI ZH, HIGH(SRAM_START)
CLR Temp1 ; Очищаем R16
RAM_Flush:
ST Z+, Temp1
CPI ZH, HIGH(RAMEND + 1)
BRNE RAM_Flush
CPI ZL, LOW(RAMEND + 1)
BRNE RAM_Flush
LDI ZL, (0x1F-2) ; Адрес регистра R29
CLR ZH
Reg_Flush:
ST Z, ZH
DEC ZL
BRNE Reg_Flush
CLR ZL
CLR ZH
; Регистры и SRAM полностью очищены (обнулены)
; Но регистры ввода-вывода (IO) НЕОБХОДИМО очищать
; Глобальный запрет прерываний
CLI
Delays
Библиотека с подпрограммами задержки. Все задержки рассчитаны на тактовую частоту МК 8 МГц. Библиотека включает следующие процедуры:
- Delay1us – Задержка повышенной точности в 1 мкс c учетом длительности RCALL и RET.
- Delay5us – Задержка повышенной точности в 5 мкс c учетом длительности RCALL и RET.
- Delay10us – Задержка повышенной точности в 10 мкс c учетом длительности RCALL и RET.
- Delayus – Задержка высокой точности в несколько десятков микросекунд.
- Delayms – Задержка высокой точности в несколько миллисекунд.
; БИБЛИОТЕКА ЗАДЕРЖЕК (8 МГц)
; Delay1us задержка повышенной точности в 1 мкс c учетом длительности RCALL и RET
; Delay5us задержка повышенной точности в 5 мкс c учетом длительности RCALL и RET
; Delay10us задержка повышенной точности в 10 мкс c учетом длительности RCALL и RET
; Delayus задержка высокой точности в несколько десятков микросекунд
; Delayms задержка высокой точности в несколько миллисекунд
;=================================================
; задержка повышенной точности в 1 мкс c учетом длительности RCALL и RET
; RCALL дает 3 + 1 NOP + 4 RET = 8 - 1 микросекунда при 8МГц
Delay1us:
NOP
RET
;=================================================
; задержка повышенной точности в 5 мкс c учетом длительности RCALL и RET
Delay5us:
PUSH Temp1
LDI Temp1, 9
Delay5us_loop:
DEC Temp1
BRNE Delay5us_loop
POP Temp1
NOP
NOP
RET
;=================================================
; задержка повышенной точности в 10 мкс c учетом длительности RCALL и RET
Delay10us:
PUSH Temp1
LDI Temp1, 23
Delay10us_loop:
DEC Temp1
BRNE Delay10us_loop
POP Temp1
RET
;=================================================
; задержка высокой точности в несколько десятков микросекунд
; вход Temp1 количество необходимых десятков микросекунд
Delayus:
PUSH Temp2
Delayus_loop1:
LDI Temp2, 25
Delayus_loop2:
DEC Temp2
BRNE Delayus_loop2
NOP
NOP
DEC Temp1
BRNE Delayus_loop1
POP Temp2
RET
;=================================================
; задержка высокой точности в несколько миллисекунд
; вход Temp1 количество необходимых миллисекунд
Delayms:
PUSH Temp2
MOV Temp2, Temp1
Delayms_loop:
LDI Temp1, 100
RCALL Delayus
DEC Temp2
BRNE Delayms_loop
POP Temp2
RET
Math
Библиотека с подпрограммами математических операций, включает следующие процедуры:
- SUB16X16 – Вычитание 16-разрядных чисел.
- ADD16X16 – Сложение 16-разрядных чисел.
- MUL16X16s – Знаковое умножение 16-разрядных чисел.
- MUL16X16u – Беззнаковое умножение 16-разрядных чисел.
- DIV16X16s – Знаковое деление 16-разрядных чисел.
- DIV16X16u – Беззнаковое деление 16-разрядных чисел.
- DIV16POWER2s – Знаковое деление 16-разрядного числа на степень 2.
- DIV16POWER2u – Беззнаковое деление 16-разрядного числа на степень 2.
- SIGN16 – Смена знака 16-разрядного числа.
- DEC2BCD – Перевол 8-разрядного десятичного числа в двоично-десятичное (BCD).
- BCD2DEC – Перевол 8-разрядного двоично-десятичного (BCD) числа в десятичное.
- CP16X16 – Сравнение 16-разрядных чисел.
- DIGITS8 – Вычисление цифр 8-разрядного числа.
- DIGITS16 – Вычисление цифр 16-разрядного числа.
; БИБЛИОТЕКА ДЛЯ РАБОТЫ С МАТЕМАТИКОЙ
; SUB16X16 вычитание 16-разрядных чисел
; ADD16X16 сложение 16-разрядных чисел
; MUL16X16s знаковое умножение 16-разрядных чисел
; MUL16X16u беззнаковое умножение 16-разрядных чисел
; DIV16X16s знаковое деление 16-разрядных чисел
; DIV16X16u беззнаковое деление 16-разрядных чисел
; DIV16POWER2s знаковое деление 16-разрядного числа на степень 2
; DIV16POWER2u беззнаковое деление 16-разрядного числа на степень 2
; SIGN16 смена знака 16-разрядного числа
; DEC2BCD перевол 8-разрядного десятичного числа в двоично-десятичное (BCD)
; BCD2DEC перевол 8-разрядного двоично-десятичного (BCD) числа в десятичное
; CP16X16 сравнение 16-разрядных чисел
; DIGITS8 вычисление цифр 8-разрядного числа
; DIGITS16 вычисление цифр 16-разрядного числа
;=======================================================================
; вычитание 16-разрядных чисел
; вход: Temp1-Temp2 первый аргумент от H к L
; Temp3-Temp4 второй аргумент от H к L
; выход: Temp1-Temp2 результат от H к L
SUB16X16:
SUB Temp2, Temp4
SBC Temp1, Temp3
RET
;=======================================================================
; сложение 16-разрядных чисел
; вход: Temp1-Temp2 первый аргумент от H к L
; Temp3-Temp4 второй аргумент от H к L
; выход: Temp1-Temp2 результат от H к L
ADD16X16:
ADD Temp2, Temp4
ADC Temp1, Temp3
RET
;=======================================================================
; знаковое умножение 16-разрядных чисел
; вход: Temp1-Temp2 первый аргумент от H к L
; Temp3-Temp4 второй аргумент от H к L
; выход: Temp1-Temp4 результат от H к L
MUL16X16s:
MULS Temp3, Temp1 ; (signed)ah * (signed)bh
MOV Temp5, MulHigh
MOV Temp6, MulLow
MUL Temp4, Temp2 ; al * bl
MOV Temp7, MulHigh
MOV Temp8, MulLow
MULSU Temp3, Temp2 ; (signed)ah * bl
SBC Temp5, Temp0 ; из-за отриц. чисел
ADD Temp7, MulLow
ADC Temp6, MulHigh
ADC Temp5, Temp0
MULSU Temp1, Temp4 ; (signed)bh * al
SBC Temp5, Temp0 ; из-за отриц. чисел
ADD Temp7, MulLow
ADC Temp6, MulHigh
ADC Temp5, Temp0
MOV Temp1, Temp5 ; move result
MOV Temp2, Temp6
MOV Temp3, Temp7
MOV Temp4, Temp8
RET
;=======================================================================
; беззнаковое умножение 16-разрядных чисел
; вход: Temp1-Temp2 первый аргумент от H к L
; Temp3-Temp4 второй аргумент от H к L
; выход: Temp1-Temp4 результат от H к L
MUL16X16u:
MUL Temp3, Temp1 ; (unsigned)ah * (unsigned)bh
MOV Temp5, MulHigh
MOV Temp6, MulLow
MUL Temp4, Temp2 ; al * bl
MOV Temp7, MulHigh
MOV Temp8, MulLow
MUL Temp3, Temp2 ; (unsigned)ah * bl
ADD Temp7, MulLow
ADC Temp6, MulHigh
ADC Temp5, Temp0
MUL Temp1, Temp4 ; (unsigned)bh * al
ADD Temp7, MulLow
ADC Temp6, MulHigh
ADC Temp5, Temp0
MOV Temp1, Temp5 ; move result
MOV Temp2, Temp6
MOV Temp3, Temp7
MOV Temp4, Temp8
RET
;=======================================================================
; знаковое деление 16-разрядных чисел
; вход: Temp1-Temp2 первый аргумент от H к L
; Temp3-Temp4 второй аргумент от H к L
; выход: Temp1-Temp2 результат от H к L
; R13-R14 остаток от H к L
DIV16X16s:
MOV R10, R16 ;вычисляем знак результата
EOR R10, R18 ; знак хранится в R10
SBRS R16, 7 ; проверяем знак делимого
RJMP d16s_1 ; если положительное то идем дальше
COM R16 ; иначе меняем знак делимого
COM R17 ; преобразуем в доп код
SUBI R17, LOW(-1)
SBCI R16, HIGH(-1)
d16s_1:
SBRS R18, 7 ; проверяем знак делителя
RJMP d16s_2
COM R18
COM R19
SUBI R19, LOW(-1)
SBCI R18, HIGH(-1)
; подготовили делимое и делитель
d16s_2:
; очищаем остаток и флаг переноса
CLR R14
CLR R13
CLC
LDI R31, 17 ; init loop counter
d16s_3:
ROL R17 ; shift left dividend
ROL R16
DEC R31 ; decrement counter
BRNE d16s_5 ; if done
SBRS R10, 7 ; if MSB in sign register set
RJMP d16s_4
COM R16 ; change sign of result
COM R17
SUBI R17, LOW(-1)
SBCI R16, HIGH(-1)
d16s_4:
RET
d16s_5:
ROL R14 ; shift dividend into remainder
ROL R13
SUB R14, R19 ; remainder = remainder - divisor
SBC R13, R18
BRCC d16s_6 ; if result negative
ADD R14, R19 ; restore remainder
ADC R13, R18
CLC ; clear carry to be shifted into result
RJMP d16s_3 ; else
d16s_6:
SEC ; set carry to be shifted into result
RJMP d16s_3
;=======================================================================
; беззнаковое деление 16-разрядных чисел
; вход: Temp1-Temp2 первый аргумент от H к L
; Temp3-Temp4 второй аргумент от H к L
; выход: Temp1-Temp2 результат от H к L
; R13-R14 остаток от H к L
;=================================================
DIV16X16u:
; очищаем остаток и флаг переноса
CLR R14
CLR R13
CLC
LDI R31, 17 ; init loop counter
d16u_1:
ROL R17 ; shift left dividend
ROL R16
DEC R31 ; decrement counter
BRNE d16u_2 ; if done
RET ; return
d16u_2:
ROL R14 ; shift dividend into remainder
ROL R13
SUB R14, R19 ;remainder = remainder - divisor
SBC R13, R18
BRCC d16u_3 ; if result negative
ADD R14, R19 ; restore remainder
ADC R13, R18
CLC ; clear carry to be shifted into result
RJMP d16u_1 ; else
d16u_3:
SEC ; set carry to be shifted into result
RJMP d16u_1
;=======================================================================
; знаковое деление 16-разрядного числа на степень 2
; вход: Temp1-Temp2 делимое от H к L
; Temp5 степень 2
; выход: Temp1-Temp2 результат от H к L
DIV16POWER2s:
TST Temp5
BREQ DIV16POWER2s_2
DIV16POWER2s_1:
ASR Temp1
ROR Temp2
DEC Temp5
BRNE DIV16POWER2s_1
DIV16POWER2s_2:
RET
;=======================================================================
; беззнаковое деление 16-разрядного числа на степень 2
; вход: Temp1-Temp2 делимое от H к L
; Temp5 степень 2
; выход: Temp1-Temp2 результат от H к L
DIV16POWER2u:
TST Temp5
BREQ DIV16POWER2u_2
DIV16POWER2u_1:
LSR Temp1
ROR Temp2
DEC Temp5
BRNE DIV16POWER2u_1
DIV16POWER2u_2:
RET
;=======================================================================
; смена знака 16-разрядного числа
; вход: Temp1-Temp2 число от H к L
; выход: Temp1-Temp2 результат от H к L
SIGN16:
COM Temp1
COM Temp2
SUBI Temp2, LOW(-1)
SBCI Temp2, HIGH(-1)
RET
;=======================================================================
; перевол 8-разрядного десятичного числа в двоично-десятичное (BCD)
; вход: Temp1 десятичное число
; выход: Temp1 BCD число
DEC2BCD:
PUSH Temp2
PUSH Temp3
PUSH Temp4
CPI Temp1, 10
BRLO Dec2Bcd_exit
PUSH Temp1
CLR Temp3
LDI Temp2, 10
Dec2Bcd_1:
SUB Temp1,Temp2
INC Temp3
CPI Temp1, 10
BRGE Dec2Bcd_1
CLR Temp4
CLR Temp1
Dec2Bcd_2:
ADD Temp4, Temp2
INC Temp1
CP Temp1, Temp3
BRNE Dec2Bcd_2
POP Temp1
SUB Temp1, Temp4
SWAP Temp3
ADD Temp1, Temp3
Dec2Bcd_exit:
POP Temp4
POP Temp3
POP Temp2
RET
;=======================================================================
; перевол 8-разрядного двоично-десятичного (BCD) числа в десятичное
; вход: Temp1 BCD число
; выход: Temp1 десятичное число
BCD2DEC:
PUSH Temp2
PUSH Temp3
PUSH Temp1
SWAP Temp1
CLR Temp3
CBR Temp1, 0b11110000
MOV Temp3, Temp1
CLR Temp2
Bcd2Dec_1:
ADD Temp1, Temp3
INC Temp2
CPI Temp2, 9
BRNE Bcd2Dec_1
MOV Temp2, Temp1
POP Temp1
CBR Temp1, 0b11110000
ADD Temp1, Temp2
POP Temp3
POP Temp2
RET
;=======================================================================
; сравнение 16-разрядных чисел
; вход: Temp1-Temp2 первый аргумент от H к L
; Temp3-Temp4 второй аргумент от H к L
; выход: смотри флаги
CP16X16:
CP Temp2, Temp4
CPC Temp1, Temp3
RET
;=======================================================================
; вычисление цифр 8-разрядного числа
; вход: Temp1 аргумент
; выход: Temp1-Temp3 цифры от H к L
DIGITS8:
CLR R26
CLR R27
CLR R28
CLR R29
CLR R30
LDI Temp2,100
DIG8_1:
CP Temp1, Temp2
BRLO DIG8_2
SUB Temp1, Temp2
; сотни
INC R26
RJMP DIG8_1
DIG8_2:
LDI Temp2,10
DIG8_3:
CP Temp1, Temp2
BRLO DIG8_4
SUB Temp1, Temp2
; тысячи
INC R27
RJMP DIG8_3
DIG8_4:
; в Temp1 остались только единицы
MOV Temp3, Temp1
MOV Temp1, R26
MOV Temp2, R27
RET
;=======================================================================
; вычисление цифр 16-разрядного числа
; вход: Temp1-Temp2 аргумент от H к L
; выход: Temp1-Temp5 цифры от H к L
DIGITS16:
CLR R26
CLR R27
CLR R28
CLR R29
CLR R30
LDI Temp3, HIGH(10000)
LDI Temp4, LOW(10000)
DIG16_1:
RCALL CP16X16
BRLO DIG16_2
RCALL SUB16X16
; десятки тысяч
INC R26
RJMP DIG16_1
DIG16_2:
LDI Temp3, HIGH(1000)
LDI Temp4, LOW(1000)
DIG16_3:
RCALL CP16X16
BRLO DIG16_4
RCALL SUB16X16
; тысячи
INC R27
RJMP DIG16_3
DIG16_4:
LDI Temp3, HIGH(100)
LDI Temp4, LOW(100)
DIG16_5:
RCALL CP16X16
BRLO DIG16_6
RCALL SUB16X16
; сотни
INC R28
RJMP DIG16_5
DIG16_6:
LDI Temp3, HIGH(10)
LDI Temp4, LOW(10)
DIG16_7:
RCALL CP16X16
BRLO DIG16_8
RCALL SUB16X16
; десятки
INC R29
RJMP DIG16_7
DIG16_8:
; в Temp1-Temp2 остались только единицы
MOV Temp5, Temp2
MOV Temp1, R26
MOV Temp2, R27
MOV Temp3, R28
MOV Temp4, R29
RET
Несколько интересных примеров
Несколько примеров программ на языке ассемблера для микроконтроллеров Atmel AVR. Эти примеры демонстрируют основные операции, такие как загрузка данных, арифметические операции и ввод-вывод:
- Пример программы для загрузки константы в регистр:
ldi r16, 0x0A ; Загрузить значение 0x0A в регистр R16
- Пример программы для сложения двух чисел и сохранения результата:
ldi r16, 0x0A ; Загрузить значение 0x0A в регистр R16
ldi r17, 0x05 ; Загрузить значение 0x05 в регистр R17
add r16, r17 ; Сложить R16 и R17 и сохранить результат в R16
- Пример программы для условного перехода:
ldi r16, 0x0A ; Загрузить значение 0x0A в регистр R16
cpi r16, 0x0F ; Сравнить R16 с 0x0F
breq equal ; Если равно, перейти к метке equal
; Если не равно, выполнить другие операции
equal:
; Код, выполняемый при равенстве
- Пример программы для ввода и вывода данных через порты ввода-вывода:
in r16, PORTB ; Загрузить данные с порта ввода-вывода PORTB в регистр R16
out PORTB, r16 ; Вывести данные из регистра R16 на порт ввода-вывода PORTB
- Пример программы для цикла:
ldi r16, 0 ; Загрузить начальное значение в регистр R16
loop:
; Код, выполняемый внутри цикла
inc r16 ; Увеличить значение R16 на 1
cpi r16, 10 ; Сравнить R16 с 10
brne loop ; Если R16 не равно 10, вернуться к метке loop
Обратите внимание, что это базовые примеры, и реальные программы для микроконтроллеров AVR могут быть более сложными, включать в себя обработку прерываний, взаимодействие с периферийными устройствами и другие задачи.
Более сложные примеры программ на языке ассемблера для микроконтроллеров Atmel AVR:
- Пример программы для инициализации порта ввода-вывода и мигания светодиодом:
.include <avr/io.h>
.equ LED_PORT, PORTB
.equ LED_DDR, DDRB
.equ LED_PIN, PINB
.equ LED_BIT, PB0
ldi r16, 0xFF ; Установить все биты порта B как выходы
out LED_DDR, r16
main_loop:
sbi LED_PORT, LED_BIT ; Установить бит PB0 в 1 (включить светодиод)
_delay_ms(1000) ; Подождать 1 секунду
cbi LED_PORT, LED_BIT ; Установить бит PB0 в 0 (выключить светодиод)
_delay_ms(1000) ; Подождать еще 1 секунду
rjmp main_loop ; Бесконечный цикл
- Пример программы для обработки прерывания от внешнего источника (например, кнопки):
.include <avr/io.h>
.def button = r18
.equ LED_PORT, PORTB
.equ LED_DDR, DDRB
.equ LED_BIT, PB0
.equ BUTTON_PORT, PORTC
.equ BUTTON_PIN, PINC
.equ BUTTON_DDR, DDRC
.equ BUTTON_BIT, PC0
.org 0x00 ; Начальный вектор прерывания
rjmp reset
.org INT0addr ; Вектор прерывания от внешнего источника
in button, BUTTON_PIN ; Считать состояние кнопки
brne button_pressed ; Если кнопка нажата, перейти к обработке
rjmp button_released
button_pressed:
sbi LED_PORT, LED_BIT ; Включить светодиод
rjmp button_done
button_released:
cbi LED_PORT, LED_BIT ; Выключить светодиод
rjmp button_done
button_done:
reti ; Завершить обработку прерывания
reset:
ldi r16, 0xFF ; Установить все биты порта B как выходы
out LED_DDR, r16
ldi r16, 0x00 ; Установить все биты порта C как входы
out BUTTON_DDR, r16
ldi r16, (1 << INT0) ; Включить прерывание INT0
out GICR, r16
sei ; Разрешить глобальные прерывания
main_loop:
rjmp main_loop ; Бесконечный цикл
Обратите внимание, что второй пример включает в себя обработку внешнего прерывания (INT0), которое срабатывает при нажатии на кнопку, и светодиод мигает в зависимости от состояния кнопки.
Ещё более объемные примеры программ на языке ассемблера для микроконтроллеров Atmel AVR:
- Пример программы для чтения с датчика температуры DS18B20 и вывода значения на серийный порт USART:
.include <avr/io.h>
.equ DS18B20_PORT, PORTD
.equ DS18B20_DDR, DDRD
.equ DS18B20_PIN, PIND
.equ DS18B20_DQ, PD2 ; Пин для подключения DS18B20
.equ USART_BAUD_RATE, 9600
.org 0x00
rjmp reset ; Вектор сброса
reset:
ldi r16, 0xFF ; Установить все биты порта D как выходы
out DS18B20_DDR, r16
ser r16 ; Установить R16 в 0xFF (все биты в 1)
out UBRRL, r16 ; Установить скорость передачи данных USART
ldi r16, (1 << TXEN) | (1 << RXEN) ; Включить передачу и прием через USART
out UCSRB, r16
ldi r16, (1 << UCSZ0) | (1 << UCSZ1) ; Установить формат кадра 8N1
out UCSRC, r16
main_loop:
call read_temperature ; Вызов функции для считывания температуры
call send_temperature ; Вызов функции для отправки температуры через USART
rcall _delay_ms ; Вызов функции задержки
rjmp main_loop
read_temperature:
; Код для инициализации и чтения температуры с DS18B20
; Результат сохраняется в регистре R16
ret
send_temperature:
; Код для отправки температуры через USART
; Значение температуры передается через R16
ret
_delay_ms:
; Код для создания задержки в миллисекундах
ret
.include "uart.inc" ; Включение файла с подпрограммами для USART
- Пример программы для управления шаговым двигателем через драйвер шагового двигателя A4988:
.include <avr/io.h>
.equ A4988_PORT, PORTD
.equ A4988_DDR, DDRD
.equ STEP_PIN, PD2
.equ DIR_PIN, PD3
.equ MS1_PIN, PD4
.equ MS2_PIN, PD5
.equ ENABLE_PIN, PD6
.org 0x00
rjmp reset
reset:
ldi r16, (1 << STEP_PIN) | (1 << DIR_PIN) | (1 << MS1_PIN) | (1 << MS2_PIN) | (1 << ENABLE_PIN)
out A4988_DDR, r16 ; Установить все пины управления A4988 как выходы
main_loop:
; Управление двигателем, включая изменение направления вращения, шагов и режима микрошага
; Здесь можно добавить логику для движения двигателя в нужном направлении и количестве шагов
rjmp main_loop
Эти примеры программ представляют собой более крупные приложения для микроконтроллеров AVR, и они могут потребовать дополнительных библиотек и настроек, чтобы полностью функционировать.
Добавлю ещё пару примеров программ на ассемблере для микроконтроллеров AVR:
- Пример программы для создания простого таймера и обработки прерываний:
.include <avr/io.h>
.org 0x00
rjmp reset
.org TIMER1_COMPA_vect
; Обработка прерывания от TIMER1
; Ваш код обработки здесь
reti
reset:
ldi r16, (1 << WGM12) | (1 << CS11) ; Настройка TIMER1 в режим CTC и делитель на 8
out TCCR1B, r16
ldi r16, 62500 ; Значение для сравнения, чтобы создать интервал в 1 секунду
out OCR1A, r16
sei ; Разрешить глобальные прерывания
main_loop:
; Основной код программы
rjmp main_loop
Этот пример настраивает TIMER1 для создания прерывания через 1 секунду и обрабатывает его в функции прерывания.
- Пример программы для использования внутренней EEPROM для хранения и чтения данных:
.include <avr/io.h>
.equ EEPROM_DATA_ADDR, 0x10 ; Адрес внутренней EEPROM, с которого начинается хранение данных
.org 0x00
rjmp reset
reset:
ldi r16, 0x55 ; Значение для записи в EEPROM
sts EEPROM_DATA_ADDR, r16 ; Записать значение в EEPROM
read_eeprom:
lds r16, EEPROM_DATA_ADDR ; Прочитать значение из EEPROM
; Использовать значение в регистре R16
ret
main_loop:
; Основной код программы
rcall read_eeprom ; Чтение данных из EEPROM
; Дополнительная логика
rjmp main_loop
Этот пример демонстрирует, как записывать и читать данные во внутренней EEPROM микроконтроллера AVR.
Учтите, что эти примеры представляют собой базовую структуру программы. Реальные программы могут включать в себя более сложные операции и логику в зависимости от конкретных задач.
Ассемблер для микроконтроллеров ATMEL AVR может быть довольно сложным, но в данном контексте показываю пример, который демонстрирует некоторые основные аспекты ассемблерного программирования на AVR. Этот пример будет включать в себя работу с прерываниями, работу с портами ввода-вывода и использование множества инструкций.
Пример: Программа, которая мигает светодиодом на микроконтроллере ATMega328P (одном из популярных контроллеров AVR) с использованием внешних прерываний.
.include "m328pdef.inc" ; Включение файла с определениями регистров для ATMega328P
; Определение макросов для удобства работы с портами ввода-вывода
.equ LED_DDR = DDRB ; Регистр для управления направлением порта B
.equ LED_PORT = PORTB ; Регистр для управления состоянием порта B
.equ LED_PIN = PINB ; Регистр для чтения состояния порта B
; Определение вектора прерывания INT0
.equ INT0_vect = 2
; Начальное состояние регистров
.def led_state = r16 ; Регистр для хранения состояния светодиода
; Начало программы
.org 0x0000
rjmp Main
; Обработчик внешнего прерывания INT0
.org INT0_vect
; Инвертировать состояние светодиода
lds led_state, LED_PIN ; Загрузить текущее состояние порта B
ldi r17, 1 ; Константа 1
eor led_state, r17 ; Инвертировать бит в led_state
sts LED_PORT, led_state ; Установить новое состояние порта B
reti
Main:
; Настроить порт B, бит 0 (PB0) как выход (светодиод)
sbi LED_DDR, 0
; Настроить внешнее прерывание INT0
ldi r17, (1<<ISC00) ; Настроить прерывание по изменению уровня
sts EICRA, r17
sbi EIMSK, INT0 ; Разрешить прерывание INT0
sei ; Разрешить глобальные прерывания
; Бесконечный цикл
Loop:
rjmp Loop
.end
Этот пример программы для микроконтроллера ATMega328P на языке ассемблера демонстрирует использование внешнего прерывания (INT0) для переключения состояния светодиода, подключенного к порту B, бит 0 (PB0). Программа также настраивает обработчик прерывания, включает глобальные прерывания и выполняет бесконечный цикл.
Рассмотрим ещё один интересный пример
.include "m128pdef.inc" ; Включение файла с определениями регистров для ATMega128
; Определение макросов для удобства работы с портами ввода-вывода
.equ LED_DDR = DDRB ; Регистр для управления направлением порта B
.equ LED_PORT = PORTB ; Регистр для управления состоянием порта B
.equ LED_PIN = PINB ; Регистр для чтения состояния порта B
.equ BUTTON_PIN = PIND ; Регистр для чтения состояния порта D
; Определение вектора прерывания INT0
.equ INT0_vect = 2
; Определение вектора прерывания TIMER1_COMPA
.equ TIMER1_COMPA_vect = 9
; Начальное состояние регистров
.def led_state = r16 ; Регистр для хранения состояния светодиода
.def button_state = r17 ; Регистр для хранения состояния кнопки
; Начало программы
.org 0x0000
rjmp Main
; Обработчик внешнего прерывания INT0
.org INT0_vect
; Инвертировать состояние светодиода
lds led_state, LED_PIN ; Загрузить текущее состояние порта B
ldi r18, 1 ; Константа 1
eor led_state, r18 ; Инвертировать бит в led_state
sts LED_PORT, led_state ; Установить новое состояние порта B
reti
; Обработчик прерывания TIMER1_COMPA
.org TIMER1_COMPA_vect
; Увеличить счетчик времени
lds r19, TCNT1H ; Загрузить старший байт счетчика TIMER1
lds r20, TCNT1L ; Загрузить младший байт счетчика TIMER1
ldi r21, 1 ; Константа 1
add r20, r21 ; Увеличить младший байт на 1
brcc .+2 ; Перейти, если не было переноса
inc r19 ; Увеличить старший байт при переносе
sts TCNT1H, r19 ; Сохранить старший байт обратно
sts TCNT1L, r20 ; Сохранить младший байт обратно
; Проверить состояние кнопки
sbis BUTTON_PIN, 2 ; Проверить бит 2 в PIND
rjmp ButtonPressed ; Перейти, если кнопка нажата
reti
ButtonPressed:
; Дополнительные действия, если кнопка нажата
; Можно добавить здесь любой код
Main:
; Настроить порт B, бит 0 (PB0) как выход (светодиод)
sbi LED_DDR, 0
; Настроить внешнее прерывание INT0
ldi r18, (1<<ISC00) ; Настроить прерывание по изменению уровня
sts EICRA, r18
sbi EIMSK, INT0 ; Разрешить прерывание INT0
; Настроить TIMER1 для генерации прерываний
ldi r19, 0xC0 ; Настроить TCCR1B для делителя частоты (1024)
sts TCCR1B, r19
ldi r20, 0x00 ; Сбросить TCNT1
sts TCNT1H, r20
sts TCNT1L, r20
ldi r21, 0x61 ; Загрузить старший байт для значения сравнения
sts OCR1AH, r21
ldi r22, 0x80 ; Загрузить младший байт для значения сравнения
sts OCR1AL, r22
ldi r23, (1<<OCIE1A) ; Разрешить прерывание при сравнении
sts TIMSK, r23
sei ; Разрешить глобальные прерывания
; Бесконечный цикл
Loop:
rjmp Loop
.end
Этот пример реализует обработку внешнего прерывания, генерацию прерываний от таймера TIMER1, а также обработку нажатия кнопки. Он включает в себя более сложные операции, такие как настройка таймера, работа с различными регистрами и битами, а также обработка условий внутри прерываний.
IO Ports
В данном примере рассматривается работа с портами ввода-вывода. К порту А подключены 8 светодиодов (линии 0-7). К линии 0 порта С подключена кнопка, с подтяжкой на землю. При нажатии кнопка выдает на линию 0 порта С уровень логической единицы. Цикл программы организован следующим образом: при запуске включается бегущий огонь, сначала загорается светодиод на линии 0 порта А, затем на линии 1 и т.д. По достижении линии 7 направление бегущего огня меняется (от 7 к 0). При нажатии на кнопку бегущий огонь останавливается и загораются одновременно все светодиоды. После повторного нажатия на кнопку бегущий огонь продолжает перемещаться с места остановки.
Dynamic Indication
В данном примере рассматривается работа с 7-сегментным индикатором. В моём случае он имеет 4 разряда (цифры). Поскольку у меня на плате установлены транзисторы для управления разрядами, то управление осуществляется выводом логической единицы и на разряды и на сегменты. Схема подключения следующая: к линиям 0-7 порта C подключены сегменты индикатора, а к линиям 0-3 порта В разряды индикатора. При запуске на индикатор выводятся цифры 1 2 3 4.
UART
В данном примере рассматривается периферийного модуля UART (универсальный асинхронный приёмопередатчик). Модуль UART можно настроить как на работу с прерываниями, так и без них (вручную, путём работы с флагами). Пример работает следующим образом: при получении байта, МК переходит в обработчик прерывания (используется только прерывание по приёму данных) и разбирает численное значение байта (0-255) на цифры, которые и выводятся на 7-сегментный индикатор. Схема подключения аналогична предыдущему примеру. Передача осуществляется по двум линиям UART (порт D линии 0-1), к которым необходимо подключить линии RX и TX преобразователя USB-UART. Для настройкки без прерываний необходимо обнулить бит RXCIE в регистре UCSRB и вручную опрашивать интерфейс в основном цикле программы.
Clock
В данном примере рассматривается реализация простых часов с 7-сегментым индикатором и парой кнопок. Только здесь уже требуется 6 разрядов, хотя секунды можно опустить. Кнопки с подтяжкой на землю. При нажатии кнопка выдает на линию высокий логический уровень. Индикатор подключается как и в предыдущих примерах (сегменты к порту C, разряды к порту B), а кнопки к линиям 2-3 порта D. Кнопка PD2 используется для установки минут, а PD3 для установки часов. По нажатию каждой из кнопок увеличивается значение соответствующего разряда (минуты или часы).
DS18B20
В данном примере рассматривается работа с цифровым датчиком температуры DS18B20. Показания температуры выводятся на 7-сегментый индикатор. Вывод DQ датчика поключен к линии PC7. Линия должна быть подтянута к плюсу питания резистором на 4.7-10 кОм (согласно документации). Датчик опрашивается каждую секунду. Температура выводится на 4-разрядный индикатор: знак, два разряда на целуюю часть и один на вещественную. Документация к датчику здесь.
ADC Indication
Данный пример аналогичен примеру с UART. Отличие в том, что байт берется с линии 0 порта А (линия 0 АЦП, ADC0). Микроконтроллер по таймеру производит аналого-цифровое преобразование напряжения на линии 0 порта А, (младшие 2 бита отбрасываются как шум). При измерении используется внутренняя опора 5 В. К линии PD2 порта D подключена кнопка, которая определяет режим вывода показаний. При нажатии на кнопку выводится результат измерений в виде числа от 0 до 255. Если кнопка не нажата, то результат измерений переводится в вольты и выводится на индикатор (с точностью до десятых).
Fast PWM
В данном примере показана настройка аппаратного ШИМ (широтно-импульсная модуляция, англ. PWM). К линиям 4 и 5 порта D подключены светодиоды, а к линиям 0-3 порта С – кнопки. Кнопки с подтяжкой на землю (при нажатии кнопка выдает на линию порта уровень логической единицы) подключены к линиям 2-5 порта C. Кнопки на линях 2 и 3 соответственно увеличивают и уменьшают коэффициент заполнения ШИМ (меняется яркость светодиода) канала А. Кнопки на линях 4 и 5 соответственно увеличивают и уменьшают коэффициент заполнения ШИМ канала B. Число сравнения для каждого из каналов меняется в диапазоне от 0 до 255. Для канала А шаг изменения равен 10, для канала В шаг равен 5.
HCSR04
В данном примере рассматривается работа с ультразвуковым датчиком расстояния HCSR04. К линии PD6 подключен вывод Trigger датчика, а к линии PD7 вывод Echo. Поключение 7-сегментного индикатора аналогично предыдущим примерам. По таймеру микроконтроллер раз в секунду опрашивает датчик и определяет расстояние до препятсвия в миллиметрах. После этого число разбивается на цифры и выводится на дисплей. Документация к датчику здесь.
Matrix Keyboard
В данном примере показана работа с матричной клавиатурой. Микроконтроллер динамически опрашивает клавиатуру, а затем определяет номер нажатой клавиатуры. Размер поля 3 на 3 – получаем 9 кнопок. Нажатие первых 8-ми приводит к зажиганию светодиода на соответствующей линии порта А, нажатие 9-ой кнопки зажигает все светодиоды порта А. Матричная клавиатура подключается к линиям 0-5 порта С (три столбца и три строки). В архиве схема и печатная плата матричной клавиатуры (Diptrace).
Shift Register
В данном примере рассматривается работа с модулем SPI на примере сдвигового регистра 74HC595. К регистру подключены светодиоды, в качестве линии CS используется линия 4 порта B (вывод not SS). Линия DS (14 нога) регистра идет к MOSI (PB5), линия SHCP (11 нога) к линии SCK (PB7), линия STCP (12 нога) к линии SS (PB4). Линии MR (10 нога) и OE (13 нога) должны быть подтянуты к высокому и низкому логическим уровням соответственно. По таймеру микроконтроллер меняет состояние светодиодов: поочерёдно горят то чётные светодиоды, то нечётные. Если при этом передать байт по UART'у, то он будет выведен в порт на светодиоды. Чтобы обратно переключиться в режим мигания необходимо послать по UART'у 0x00 (ноль). Документация к микросхеме 74HC595 здесь.
SG-90 Servo
В данном примере рассматривается работа с сервоприводом SG-90. Используется аппаратный ШИМ. Линия ШИМ сервпопривода подключена к каналу А аппаратного ШИМ. Кнопки поворота подключены к линиям PD2 и PD3. Кнопка на линии PD2 увеличивает длительность импульса, кнопка на линии PD3 уменьшает длительность импульса. Длительность импульса меняется от 1 до 2 мс. Описание сервомотора здесь.
RGB Lamp
В данном примере рассматривается работа с трехцветным RGB-светодиодом. Реализовано плавное переливание цветов с использованием программного ШИМ. Линии красного, зеленого и синего цветов подключаются соответственно к линиям 2, 3 и 4 порта D.