Содержание
Урок 1. Первый проект
Урок 2. Управление кнопками
Урок 3. Подключение LCD
Урок 4. Использование ШИМ
Урок 5. Таймеры
Урок 6.1. Статическая индикация
Урок 6.2. Динамическая индикация
Урок 7.1. Генерация звука
Урок 7.2. Генерация звука. Продолжение
Урок 8.1. Передача данных через UART
Урок 8.2. Передача данных через UART. Продолжение»
Урок 9. Передача данных через SPI
Урок 10. Изучение АЦП. Простой вольтметр
Урок 11. Получение синуса при помощи ШИМ
Урок 12. Измерение температуры
Урок 13. Внешние прерывания.
Урок 14. Использование отладчика
Урок 15.1. Управление инкрементальным энкодером
Урок 15.2. Управление громкостью, при помощи энкодера
Урок 16. Управление RGB светодиодом
Урок 17. Использование ИК
Урок 18.1. Знакомство с графическим дисплеем
Урок 18.2 Вывод изображения на графический дисплей
Урок 18.3 Вывод русскоязычного текста
Урок 19. Формирование сигнала, при помощи ЦАП (R2R)
Урок 20. Опрос матричной клавиатуры
Урок 21. Сторожевой таймер
Урок 22.1 Воспроизведение wav. Введение.
Урок 22.2 Воспроизведение wav. Продолжение.
Урок 23.1 Работа с внешней памятью
Урок 23.2 Работа с файловой системой Fat

Периодически вспоминается разговор с одним из выпускников. Мы обсуждали кем он хочет стать и какую ему выбрать профессию. В результате он сказал «хочу программировать микроконтроллеры на асме», ответа на вопрос почему не начать с Си, не последовало 🙂 В общем всем студентам-любителям асма посвящается…

Давным давно, еще когда только начал изучать AVR, мне захотелось посмотреть что такое ассемблер для AVR. С помощью неких мануалов мне удалось написать программу мигалки, но без особого вникания в суть процесса, тогда мне показалось слишком сложно. По воле случая понадобилось кое что поправить в асмовском исходнике одного товарища. В итоге получилось быстрее переписать полностью исходник на Си, но связано это было лишь с нехваткой времени, зато отношение к асму полностью изменилось.

Сейчас уже с улыбкой смотрю как некоторые люди говорят об этом, как чем то сверхъестественном. Для простоты понимания, в качестве задачи — отправить байт по юарту.

С чего начнем? Наверно с того как бы выглядела такая же программа на си:

#include 
#include 
#include 

void main(void)
{
// USART initialization
// Communication Parameters: 8 Data, 1 Stop, No Parity
// USART Receiver: Off
// USART Transmitter: On
// USART Mode: Asynchronous
// USART Baud Rate: 9600
UCSRA=0x00;
UCSRB=0x08;
UCSRC=0x86;
UBRRH=0x00;
UBRRL=0x33;

while (1)
      {
          putchar('a');
          delay_ms(1000);
      }
}

Проект создан при помощи визарда. Скорость 9600, настроен только передатчик, посылаем символ ‘a’ раз в секунду библиотечной функцией putchar. Программа пишется за 30 секунд 🙂

Теперь перейдем к нашей цели. Последуем той же логике, что и выше: настройка передатчика, на скорость 9600, отправка байта. Начнем с начала:

#inlude  

Задумывались ли вы когда нибудь что внутри этого файла? Многие скорее всего даже не догадываются. Так вот микроконтроллер не знает что такое PORTD и ему абсолютно на это наплевать, ровно также он не знает что такое UDR, DDRD и т.п. Изначально названия регистров это просто выдуманные общепринятые дефайны, вы можете совершенно спокойно написать вместо PORTD — PORTDDDDD, или еще как угодно, важно чтобы тем сам не нарушалась логика работы.

Весь смысл в том, что все регистры имеют свой адрес. Поэтому когда мы пишем PORTD=0xFF, то на самом деле мы записываем число 0xFF в адрес 0x12. Так вот все эти адреса и прописаны в файле mega8.h в ассемблере есть аналог такого файла называется он m8Adef.inc, и его содержимое идентично. Поэтому если мы хотим обращаться к регистрам по именам, то мы должны включить данный файл в проект.

.include "m8Adef.inc"

Как узнать каким образом подключать файл к проекту? Да посмотреть 2-3 исходника из гугла, не обязательно читать тонны книг и мануалов. Как узнать название заголовочного файла именно для вашего микроконтроллера? В директории установленной авр студии/атмел студии есть папка include, там все имена заголовочных файлов. Думаю не нужно быть телепатом, чтобы догадаться что m8def для меги8, m8Adef для меги8а, m16def для меги16 и т.п.

Две следующие библиотеки пропускаю, наверняка есть их аналоги для асма, но сейчас они нам не интересны.

Точка входа в программу, да да все программы должны откуда то начинаться, в си это функция main(), для асма это .ORG.

Далее .CSEG — все что ниже помещается во флеш, не для кого не секрет, что мы прошивку грузим во флеш. Как все это узнал? Посмотрел готовые прошивки. Загуглил. 1 раз чтобы запомнить на всю оставшуюся жизнь.

Дальше идут настройки самого юарта, кодоген для 8МГц выдал это:

UCSRA=0x00;
UCSRB=0x08;
UCSRC=0x86;
UBRRH=0x00;
UBRRL=0x33;                         

Как же быть с асмом? Идем в даташит и в разделе UART выдираем код инициализации:

USART_Init:
; Set baud rate
out UBRRH, r17
out UBRRL, r16
; Enable Receiver and Transmitter
ldi r16, (1<

Воу, воу, мы же еще не знаем синтаксис. Там тоже все просто, все в том же даташите есть таблица Instruction Set Summary, где расписаны все команды, которые можно использовать для этого камня. Там даже есть описание того, что делает каждая команда. Вероятно лень читать - попробуем немного разъяснить принципы:

USART_Init:
...
ret

аналог этого в сишке это произвольная функция

void USART_Init()
{
...
}

Смысл абсолютно тот же

Как бы мы вызвали функцию в сишечке?

USART_Init();

Как мы вызовем ее в асме?

rcall USART_Init

Смотрим дальше out это вывод числа в регистр

out UBRRH, r17 

Ты же помнишь анон что регистр UBRRH относится к настройке юарта?

Аналог в си это присвоение значения через некую промежуточную переменную

UBRRH = temp;

Только здесь r17 это регистр общего назначения, коих у нас аж 32. Непонятно? Представьте себе что r0-r31 это тупо переменные типа char. Кроме того, работа с некоторыми командами имеет свои особенности, например нельзя в порт писать число напрямую т.е команда

out PORTD, 0x01

не прокатит, нужно делать так

 
ldi r16,0x01       ;Загрузить число в регистр общего назначения
out PORTD, r16     ;из регистра отрыгнуть это в порт

Это тоже одна из немногих истин которые нужно где то услышать, прочитать, увидеть и запомнить один раз на всю жизнь.

Таки смысл out UBRRH, r17 становится понятен, положить в r17 некое число, которое далее из r17 отправляем в UBRRH. Собственно и смысл остального должен быть понятен:

USART_Init:    					;подпрограмма инициализации юарта
; Set baud rate                                 
out UBRRH, r17                                  ;настройка бодрейта
out UBRRL, r16
; Enable Receiver and Transmitter
ldi r16, (1<

Естественно вы должны понимать, что каждый раз перед использованием r16 и r17, в них уже должно лежать некое число, которое вы посчитали по формуле из даташита, для своей частоты и скорости юарта. Для ленивых можно сделать проще, взять эти числа из генератора CAVR, это ведь не меняет смысл, итого строчки

UCSRA=0x00;
UCSRB=0x08;
UCSRC=0x86;
UBRRH=0x00;
UBRRL=0x33;

Превратятся в

clr r16            ;очистить r16 т.е. = 0
out UCSRA,r16      ;UCSRA = 0

ldi r16,0x08       ;загрузить 0x08 в r16
out UCSRB,r16      ;UCSRB = 0x08

ldi r16,0x86
out UCSRC,r16

clr r16
out UBRRH,r16

ldi r16,0x33
out UBRRL,r16

Так ли уж существенна разница? Да есть предварительный загруз числа в некий промежуточный регистр, но так ли это критично с точки зрения юзера?

Далее перейдем к нашей функции putchar(), увы открыть либу кодвижна не возможно стандартными средствами, поэтому можно только догадываться что скрывается в этой функции, поэтому идем в наш любимый даташит и берем кусок кода оттуда аналогичный putchar. Опять оформлено в виде функции, все четко.

USART_Transmit:
; Wait for empty transmit buffer
sbis UCSRA,UDRE
rjmp USART_Transmit
; Put data (r16) into buffer, sends the data
out UDR,r16
ret

Новая команда sbis - Skip if Bit in I/O Register is Set - сравнение, если равно единице, то пропустить следующую строчку. Аналог в си if(bit != 1){} В нашем случае пока UDRE не равен 1, выполняется строчка rjmp, т.е программа прыгнет к метке USART_Transmit, таким образом выполняется проверка пуст ли буфер и до тех пор пока он не освободится мы так и будем крутиться в цикле проверки. Аналог этому в си:

while ( !( UCSRA & (1<

И если буфер пустой т.е. UDRE == 1, то можно смело отправлять информацию, т.е. присваивать значение регистру UDR, допустим символ 'a'

USART_Transmit:
sbis UCSRA,UDRE
rjmp USART_Transmit

ldi r16,'a'
out UDR,r16

Осталось реализовать только задержку, тут немного позаковыристей, при тактовой 8МГц, один такт у нас выполняется 1/8000 000=0,000000125c, нам надо сделать задержку в 1с, т.е. как раз выполнить 8000 000 тактов 🙂
пары регистров r27:r26, r29:r28, r31:r30 образуют регистры X,Y,Z к которым можно обращаться как к 16битным. Будем их юзать загрузим в них число побольше и вычитаем из него единицу, пока оно не станет равным нулю.

d01ms:
ldi YL,low(4000)   ;1 такт
ldi YH,high(4000)  ;2 такт

d01_1:             
sbiw YL,1          ;3 такт ;5 такт ;7 такт вычитаем единицу
brne d01_1         ;4 такт ;6 такт ;8 такт ... пока регистр Y не станет равным 0
ret

Таким образом задержка будет выполняться: ((1 такт на вычитание + 1 такт на проверку равенства нулю)*4000раз вычитание*0.000000125секунд)+ (1 и 2 такт на первоначальную загрузку) = 0.001с

Останется вызвать эту подпрограмму еще 500 раз 🙂

d01ms:                ;подпрограмма 1мс
ldi YL,low(500)      ;загружаем частями
ldi YH,high(500)

d01_1:
sbiw YL,1             ;вычитаем из 4000 единицу
brne d01_1            ;пока результат не станет равным нулю
ret

d1000ms:              ;вызываем подпрограмму 1мс еще 500раз

ldi XL,low(500)
ldi XH,high(500)

d1000: 
rcall d01ms
sbiw XL,1
brne d1000
ret

Осталось только все это оформить финальным кодом.

.include "m8Adef.inc"

.CSEG
.ORG 0

;инициализация юарта
clr r16            
out UCSRA,r16     

ldi r16,0x08      
out UCSRB,r16      

ldi r16,0x86
out UCSRC,r16

clr r16
out UBRRH,r16

ldi r16,0x33
out UBRRL,r16


;функция передачи байта
USART_Transmit:
sbis UCSRA,UDRE
rjmp USART_Transmit

ldi r16,'a'
out UDR,r16

;задержка в 1000мс
rcall d1000ms

;бесконечный цикл, прыгаем к USART_Transmit
rjmp USART_Transmit


;функции задержки
d01ms:             ;инициализация
ldi YL,low(4000)
ldi YH,high(4000)

d01_1:             ;вычитание и проверка равенству 0
sbiw YL,1
brne d01_1
ret

d1000ms:

ldi XL,low(500)    ;инициализация
ldi XH,high(500)

d1000:              ;вычитание и проверка равенству 0
rcall d01ms
sbiw XL,1
brne d1000
ret

Вы можете скомпилировать сишную прошивку в CAVR, в папке проекта появится асмовский файл в котором будет весьма похожий исходник. Полученный хекс CAVR - 529байт, атмел студии - 190байт. Делают одно и тоже.

Собственно все рассказано довольно таки утрированно, конечно есть много особенностей, много тонкостей, просто относитесь к статье в прочем как и к ассемблеру спокойно. Выводы вы должны сделать самостоятельно. В качестве своих доводов напишу:
Если он вам помогает разобраться в тонкостях микроконтроллера, или без этого не обойтись, или просто нравится писать на асме - пишите.
Но если вы думаете:
-что на нем нельзя говнокодить, то ошибаетесь, можно.
-что написав программу на асме вы тут же станете профессионалом, то это не так.
-что ваши программы будут мега быстрыми, это тоже не так.
-что начинать нужно именно с асма - это ваш выбор, главное чтобы вы не бросили еще не начав, таких примеров масса.
-что на это уйдут годы - все зависит только от вас, нет ничего невозможного.

В сухом остатке - выбор инструмента скорее зависит от исходных данных, необходимых условий, привычек, удобства. Что то я сегодня разошелся 🙂 Если вы до сюда честно дочитали и разобрались, обязательно скушайте печеньку с чаем. Надеюсь кому нибудь пригодится 🙂

7 комментариев: C vs ASM

  • Светомир:

    Печеньку съел :mrgreen:

  • NeoMage:

    Если вы хотите научиться программировать микроконтроллеры, всегда начинайте с языка C. Я «играться» начинал с AVR8 именно на С, потом перешел на STM8 и STM32 (т.к. их цена значительно ниже а возможностей больше) и переход этот был очень быстрым и легким. Сейчас во всю использую FreeRTOS. И если появиться более «рентабельный микроконтроллер», то переход на него я думаю тоже будет простым.

  • iEugene0x7CA:

    Стоит учесть, что компилятор Си кроме самих инструкций помещает в прошивку еще и библиотеки, отдельные функции которых могут не использоваться. От этого и лишние 300 байт.
    Так что на практике код на Си зачастую занимает почти что равноценный с ассемблером объём. Там всё по-честному.
    А оптимизации Си под AVR может позавидовать даже всю жизнь программировавший на ассемблере. Особенно подобным насладится можно в GCC компиляторах.

  • Stampmaker:

    а я с асма для AVR как раз начинал. и юарт делал, и часы, и мигания разные…
    потом перешёл на си.

  • admin:

    Думаю, здесь нет универсального рецепта, все сугубо индивидуально.

  • Rabu Raibu:

    Например в AT90S1200 и иных МК без ОЗУ без асма не обойтись

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Счетчик
Яндекс.Метрика