Нужна помощь в программировании ардуино на ассемблере

Я новичок в использовании Arduino и пытаюсь запрограммировать его для реализации шаблона мигания с двумя светодиодами. Когда один светодиод выключен, другой должен гореть. Программу нужно делать на ассемблере. У меня есть рабочая программа на С++, но мне нужно преобразовать ее в сборку. У меня есть Arduino UNO ATmega 328p

Вот моя программа на С++:

 const int led = 13;
 const int led2 = 12;

 void setup() {
    pinMode(led, OUTPUT);
    pinMode(led2, OUTPUT);
 }

 void loop() {
   digitalWrite(led, HIGH);    
   delay(1000);       
   digitalWrite(led, LOW);    
   digitalWrite(led2, HIGH);
   delay(1000);
   digitalWrite(led2, LOW);
 }

, 👍0

Обсуждение

Почему это «нужно» делать в сборке? Школьный проект?, @Duncan C

И что вы ждете от нас, чтобы сделать для вас? Это больше похоже на призыв к кому-то сделать работу за вас, чем на вопрос. Вам нужно будет изучить различные регистры, которые управляют различными линиями GPIO, а также набор инструкций для вашей модели Arduino., @Duncan C

Вы пробовали веб-поиск? Не думаю :) https://www.instructables.com/id/Command-Line-Assembly-Language-Programming-for-Ard-2/ Начните с шага 3 и измените. Это также можно сделать, написав ассемблер на C. В ядре Arduino есть несколько функций, написанных в этом стиле, https://github.com/arduino/ArduinoCore-avr/blob/master/cores/arduino/wiring.c #L120, @Mikael Patel

Конечно, я искал его, я пришел сюда в крайнем случае, @user58745


4 ответа


0

Попробуйте этот метод, чтобы преобразовать свой скетч

https://sourceforge.net/projects/arduino-to-assembly-converter/

,

Я пробовал это, но когда я запускаю его, появляется несколько ошибок, @user58745

Также вы должны проверить комментарии Крисла о том, что это «путь к большому количеству сгенерированного кода» и «очевидно, что вы не делали этого сами». Если вы не стремитесь получить F в своем проекте, автоматическое преобразование кода C — это **не** ответ. Конечно, это полностью ваш выбор, в каком классе вы хотите работать., @GMc


2

В случае, если это школьное задание и вам действительно нужно программировать непосредственно на ассемблере, вы, вероятно, не захотите напрямую преобразовывать свой скетч C++. Он будет генерировать намного больше кода на ассемблере, чем действительно необходимо для попеременного мигания двух светодиодов (например, digitalWrite() содержит не только фактическую запись в соответствующие пин-регистры, но и намного больше кода), и это будет очевидно, что вы это не сами написали.

В этом случае вам придется пойти по более сложному пути. Вы должны начать с изучения существующих программ мигания на ассемблере. Через Google я нашел эту серию на github gist, где также есть пример мигания. Вы можете получить часть программы оттуда. Также есть список ассемблерных команд с описанием.

В конце концов, все, что делается на микроконтроллере, сводится к чтению и записи регистров специальных функций (SFR). Контакты организованы в порты с 8 (а иногда и меньше) контактами. Затем каждый порт представлен одним байтом в соответствующем SFR (1 бит на контакт). Для изменения направления контакта между выходом и входом используются регистры DDRX (где X — символ используемого порта). Для фактической установки состояния выхода (или включения/отключения внутреннего подтягивающего резистора) используются регистры PORTX. Текущее состояние (НИЗКИЙ или ВЫСОКИЙ; при вводе или выводе) можно прочитать из регистров PINX. Для этих целей необходимо прочитать техническое описание микроконтроллера (в данном случае Atmega328P) (имеется в виду: не полностью, но достаточно, чтобы чувствовать себя комфортно, получая из него необходимую информацию).

Узнайте, как устанавливать отдельные биты в байте (в SFR). Вы можете узнать это из связанного примера или из руководств в Интернете. Затем напишите код, чтобы записать правильное значение в соответствующие биты DDRX, чтобы настроить выводы светодиода на выход. Затем вы можете использовать это для записи значения на выходной контакт.

Для моргания нужен таймер. Если вы погуглите что-то вроде «arduino assembler blink», вы также найдете пример, который настраивает таймер для мигания. В основном это включает в себя установку требуемых значений SFR таймеров. Если вы не хотите этого делать, вам может сойти с рук выполнение большого количества операций "nop" (которые ничего не делают). Вам нужно будет написать цикл, который будет выполняться большое количество раз. (очень "нет" потребуется 1 тактовый цикл - спасибо Ганиме за поиск). Часы работают на частоте 16 МГц, так что вы можете посчитать, сколько времени нужно 1 "nop", а затем подсчитать, сколько их вам нужно).

,

Это [один цикл на NOP](https://www.microchip.com/webdoc/avrassembler/avrassembler.wb_NOP.html), который является наиболее разумным для архитектуры AVR., @Ghanima

@Ghanima Спасибо, я исправил номер в своем ответе., @chrisl


1

Это «должно» выполняться на ассемблере как задание, верно? В противном случае вы бы «захотели изучить» ассемблер.

Я предлагаю вам изменить программу C, чтобы она использовала манипуляции с портами, например:

 void setup() {
   DDRB |= 0b00100000;  // D13 в режим вывода
 }

 void loop() {
   PORTB |= 0b00100000;  // включаем D13
   delay(2000);       
   PORTB &= ~0b00100000;  // выключаем D13
   delay(2000);
 }

Теперь, по крайней мере, вы прошиваете D13 без использования довольно сложных библиотечных функций digitalWrite и pinMode. Задержка — еще одна проблема, которую я оставлю вам для изучения, однако моя страница о таймерах может вам помочь.

Теперь вы можете разобрать сгенерированный код, чтобы увидеть, что компилятор сгенерировал на ассемблере, и использовать это, чтобы научиться это делать.

Например, когда я скомпилировал приведенный выше код и включил "подробную компиляцию", я обнаружил расположение сгенерированного файла .elf.

Запускаем avr-objdump для этого файла (примерно так):

avr-objdump -S /tmp/build436d41bc5c0da39afe99cd9c05ac272a.tmp/sketch_aug13a.ino.elf > temp.txt

Дает мне следующий код для "loop":

00000094 <loop>:
  94:   2d 9a           sbi 0x05, 5 ; 5
  96:   60 ed           ldi r22, 0xD0   ; 208
  98:   77 e0           ldi r23, 0x07   ; 7
  9a:   80 e0           ldi r24, 0x00   ; 0
  9c:   90 e0           ldi r25, 0x00   ; 0
  9e:   0e 94 c5 00     call    0x18a   ; 0x18a <delay>
  a2:   2d 98           cbi 0x05, 5 ; 5
  a4:   60 ed           ldi r22, 0xD0   ; 208
  a6:   77 e0           ldi r23, 0x07   ; 7
  a8:   80 e0           ldi r24, 0x00   ; 0
  aa:   90 e0           ldi r25, 0x00   ; 0
  ac:   0c 94 c5 00     jmp 0x18a   ; 0x18a <delay>

В основном мы видим, что есть одна инструкция "sbi" для включения светодиода и одна инструкция "cbi" для его выключения.

Что касается остального, то я не хочу делать за вас домашнюю работу. Получайте удовольствие, изучая программирование на ассемблере!

,

2

Есть также несколько способов включения ассемблерного кода прямо в Arduino IDE.

При включении ассемблера в IDE Arduino исходный код на ассемблере собирается, связывается с вашим проектом и загружается в Arduino из IDE — точно так же, как и любая другая программа.

Это позволяет легко начать работу с ассемблером, сохраняя при этом знакомство с Arduino IDE. Почему? Потому что, поскольку вы можете смешивать код C/C++ с кодом на ассемблере, это означает, что вам не нужно писать полную программу на ассемблере, которая управляет всеми мельчайшими деталями аппаратной инициализации - вы можете начать с малого и расширять свой ассемблер по мере того, как вы чувствуете. удобно или нужно это сделать. Как и в приведенном ниже примере, вы можете начать всего с двух строк ассемблера, которые при желании впишутся в более крупную программу.

Ниже приведены инструкции по созданию смешанной программы C/Assembler в Arduino. Я также привожу пример того, как передать параметры и получить результат от функции Ассемблера.

Чтобы создать смешанную программу C + Assembler, создайте новый проект в Arduino IDE (назовите его как хотите) и введите в IDE следующий код:

extern "C" {
  void myInit();
  void myLoop();
  int myAdd(int, int);
}

void setup() {
  Serial.begin(9600);
  myInit();

  // Это всего лишь фиктивный пример, показывающий встроенный ассемблер, использующий "директиву" asm
  // Обратитесь к этому руководству или аналогичному для получения подробной информации о встроенном ассемблере в Arduino:
  // https://ucexperiment.wordpress.com/2016/03/11/arduino-inline-assembly-tutorial-5-2/
  asm(
    "ldi   r26,42"
  );
}

void loop() {
  myLoop();

  int ans = myAdd(240, 25);
  delay(ans);
  Serial.print("Answer: ");
  Serial.println(ans);
  delay(1000);
}

Далее щелкните маленькую стрелку вниз в правом верхнем углу среды IDE, как показано на следующей схеме, и выберите "Новая вкладка":

Arduino IDE — Добавить новую вкладку

Введите myFunctions.S в качестве имени новой вкладки, как показано ниже. Обратите внимание, что имя на самом деле не имеет значения, но оно должно заканчиваться расширением .S, а "S" должна быть заглавной "S", а не строчной "s":

Arduino IDE — название новой вкладки

Вставьте следующий код на вкладку myFunctions.S.

; Example assembler file that blinks an LED.
; and adds two numbers together.

#define __SFR_OFFSET 0

#include "avr/io.h"

// Declare the three function entry points as "globals"
.global myInit
.global myLoop
.global myAdd

myInit:
  sbi   DDRB,5      ; Set PB5 (the built in LED on Arduino Uno) as output
  ret


myLoop: 
  ldi   r20,250     ; Set the delay duration in ms. Maximum value is 255.
  call  myDelay_ms
  sbi   PORTB,5     ; Set PB5 HIGH
  ldi   r20,250     ; Delay for another 250 ms. 
  call  myDelay_ms
  cbi   PORTB,5     ; Set PB5 LOW
  ret

; These symbols are for internal to this assembler file's use only.
; They are private because they are not listed as a global symbol
; Also, they do not conform to the subroutine calling conventions used
; by the Arduino IDE's compiler. i.e. they are not compatible with
; being called directly from C. However, since we are calling them from
; our own assembler, we can (almost) make up any calling convention we like.
myDelay_ms:
  ; Delay about r20*1ms. Destroys r20, r30, and r31.
  ; One millisecond is about 16000 cycles at 16MHz.
  ; The basic loop takes about 5 cycles, so we need about 16,000,000 / 5 = 3000 loops.
  ; NB: most instructions are 8 bit, so we load the 16 bit counter using
  ;     two 8 bit loads.
  ldi   r31, 3000>>8    ; high(3000)
  ldi   r30, 3000&255   ; low(3000)
delaylp:
  sbiw    r30, 1        ; Decrement our 1 ms counter (this instruction operates on a 16 bit value in R31:R30).
  brne    delaylp       ; If R30 is non zero, loop back
  subi    r20, 1        ; Otherwise we have passed 1 ms so, decrement the high order byte of our counter.
  brne    myDelay_ms    ; If this is non zero, loop back.
  ret                   ; Otherwise, we have counted down from R20 * 3,000 - so return.


; Function to add two integers and return an integer.
; For few parameter functions, the Arduino IDE uses registers built into the
; Microcontroller (i.e. the CPU) to exchange data between subroutine caller and callee.
; Return values are as follows:
;   - single Byte (e.g. char, byte) R24 only
;   - double Byte (e.g. int)        R25:R24 (R25 is the high order byte)
;   - quad Byte   (e.g. long)       R25:R24:R23:R22 (R25 is the high order byte)
; Parameters are passed in similar ways starting with R25 and working down, but exactly
; what is where will depend upon the parameter types.
; Refer to this tutorial for more details or the AVR compiler documentation from ATMEL
;   https://ucexperiment.wordpress.com/2016/04/02/arduino-inline-assembly-tutorial-12-functions/
; In our case, a is passed in R25:R24 and b is passed in R23:R22
myAdd:
  add     r24, r22      ; Add the low order bytes
  adc     r25, r23      ; Add the high order bytes PLUS any value carried from the previous addition.
  ret                   ; The result is in R25:R24 which is where it needs to be to pass back to the C code.

Загрузите его в свой Uno, запустите последовательный монитор, и вы должны увидеть, что выводится некоторая сумма (ну, одна сумма выводится постоянно). Не стесняйтесь изменять значения в основном цикле, чтобы увидеть другие результаты.

Я добавил в код множество комментариев и несколько ссылок, чтобы вы могли начать поиск дополнительной информации.

Еще одна ссылка, которая будет полезна, если вы собираетесь возиться с ассемблером на UNO, — это справочное руководство/техническое описание ATMega-328P (все 662 страницы), которое вы можете получить по адресу Справочная страница Microchip ATmega328p. Там есть еще целая куча полезных ресурсов, которые, вероятно, пригодятся время от времени.

,