Change Assembly to Arduino IDE

Hello everyone, I'm trying my first contact with the Arduino IDE now, it's just the first few weeks. In the past I've done some things in assembly and I'm more familiar with that language.

I believe that other colleagues also on the forum have also taken this path.

I would like to know how I can join the Arduino IDE language with the Assembly and some very simple things.

As an example I am familiar with terminal PB5 which is number 13 on the Arduino. I wish that instead of writing

digitalRead(13);

If it were possible to do

digitalRead(PB5);

Something like that to facilitate my migration

PB5 is defined in the avr libc which is included by default by the IDE when you compile for an AVR.

so you can use PB5 but it's not what you think it is. if you try to run

void setup() {
  Serial.begin(115200);
  Serial.println(PB5);
}

void loop() {}

You'll see in 5 the Serial monitor , so digitalRead(PB5); would actually read pin D5 (PD5)

When you write a program, depending on the existence of PB5 would make the code less portable to other architectures, so the Arduino team created a mapping between the real pins and some pin names like A0 for the first analog input port

Based on which Arduino you use, the mapping will be different. When you select a board in the IDE, it selects the associated variant for the pins mapping.

you can see one such variant file here.

if you want to use the Arduino IDE, best is to adopt the conventions they set rather than trying to bring along the other way of doing things. That will make your life easier if you move to an ESP32 later on for example

1 Like

PB5 is bit 5 in PORTB (or PINB for inputs). You can read it like
bool result = PINB & (1 << 5);
and set it like
PORTB |= (1<< 5);

Note that code does not handle PORTB atomically. Thus it is not interrupt-safe.

The arduino language is basically C++ and you can include assembly code in the C code.
https://www.nongnu.org/avr-libc/user-manual/inline_asm.html

Note that code does not handle PORTB atomically. Thus it is not interrupt-safe.

I don't understand this interrupt issue.

Another question, Led is on 13, which would be more correct

#define led 13

or

int led = 13;

PORTB |= (1<< 5);

This is a Read-Modify-Write operation that takes more than one machine cycle on the AVR processor. If a interrupt occurs immediately following the Read operation and the ISR writes to PORTB, then the subsequent Write operation will undo what happened in the ISR.

The value will never change during the program and it will always be less than 256, and it will never be negative.

const uint8_t led = 13;
1 Like

#define does not use any memory
An int uses 2 bytes
const byte uses 1 byte

Or not, depending on optimization by the compiler.

1 Like

that's true before the optimizer does its job and notices the const part and the fact that it fits on one byte as expected by pinMode for example and so will be replaced directly in assembly

take this blink code

#define ledPin 13
//const uint8_t ledPin = 13;

void setup() {
  pinMode(ledPin, OUTPUT);
}

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

On UNO:

with #define ➜ 924 bytes in flash, 9 bytes for global variables
with const uint8_t ➜ 924 bytes in flash, 9 bytes for global variables

==> exactly the same

note that a define for a number will be treated as an int (16 or 32 bits) whereas a const uint8_t will occupy 1 byte. It can make a difference in some maths

I agree

The type depends on the usage, int by default. No variable is allocated, no memory reserved.

With variable declarations the compiler is free to allocate less or no memory under circumstances. I already observed no difference in memory usage with a variable declared as int or byte.

Yes I should have added by default indeed

This should read

uint8_t &aPort

else pointer dereferencing has to be used in the function code. Reference syntax also asserts that the argument can not be a NULL pointer.

there are some macros

Confused of Ashby here . That isn’t what I called assembler language , which is processor dependant
Stuff like


 10 REM LD A, β€œH”
       20 POKE 50000,62: POKE 50001,72
       30 REM RST 16
       40 POKE 50002,215
       50 REM LD A, β€œE”
       60 POKE 50003,62: POKE 50004,69
       70 REM RST 16
       80 POKE 50005,215
       90 REM LD A, β€œL”
      100 POKE 50006,62: POKE 50007,76
      110 REM RST 16
      120 POKE 50008,215
      130 REM LD A, β€œL”
      140 POKE 50009,62: POKE 5001

Have I missed something ?

From this, we might jump to the conclusion you're using a Nano/Uno, but we could be very wrong. You'll find on this forum life gets easier if you specify up front, in your first post, which processor, exactly, you want to work with/ask questions about; it can considerably reduce the resultant thread-thrash of irrelevant-if-only-we-knew postings.

  1. PB5 is NOT pin 13 on an Arduino. It's just "5." Your code has to know "elsewhere" that it is part of PORTB, and that it's a bit number rather than some other meaning of "5"... That happens automatically if you do assembly language instructions like sbi PORTB, PB5, and the compiler will do it for you if you say PORTB |= 1<<PB5; (don't get me started on giving "5" multiple names that just mean 5!)
  2. Some "board" packages define PIN_PB5 and etc for using typical ASM names with the Arduino functions. "minicore" does this for the ATmega328p used on Uno and Nano.
  3. Note that using this makes your code very chip-dependent. Not ALL arduinos haave PB5 on pin 13. In general, the Arduino pin numbering is more portable if you're dealing with board-level products, and the asm approach for chip-level products.
  4. digitalWrite(PIN_PB5, 1); is MUCH slower than PORTB |= 1<<PB5;

Note that code does not handle PORTB atomically. Thus it is not interrupt-safe.

On most AVRs (and on the ATmega328p in particular), PORTB |= 1<<5 compiles to a single atomic instruction. Not PORTB |= (1<<5)+(1<<4), though. Nor PORTL |= 1<<5; (on ATmega2560)! heh heh.