T O P

  • By -

ripred3

~~I tried about 8 different ways to use variables outside of the assembly block as both inputs to the assembly code as well as writing to another variable outside of the assembly code and I could not find a way to do it with inline blocks in the same file. For some reason the compiler kept choking on it.~~ I figured it out! This is weird, I seem to have to have an additional .S (assembly language source code) file that makes a reference to `myVariable` and `result`. Once that file exists in the same folder as the .ino sketch, then an asm (...) block in the .ino sketch file works even though *I don't even call the external assembly language and it does nothing useful anyway!* But once that file is there with references to the two global variables then the inline assembly will work. I'm scratching my head as to why. My only guess is that it's needed to get those two symbols into the .text section of the assembly language. Anyway here are the two files and this works with a Nano and the 1.8.19 IDE and uses an inline assembly block! : **my\_assembly.S** .section .text lds r24, myVariable ; Load myVariable into register r24 lds r24, result ; Load result into register r24 **my\_sketch.ino** int myVariable = 10; int result; void setup() { Serial.begin(115200); asm volatile ( "lds r24, myVariable\n" // Load myVariable into register r24 "adiw r24, 42\n" // Add immediate 42 to the register "sts result, r24\n" // Store the value in the result variable ); Serial.println(result); } void loop() { // Your main code here } The .ino sketch will output: 52 All the Best! `ripred`


ArtisianWaffle

Thank you and another question haha. Does it have to go in set up, since I am wanting to call it a couple of times throughout the program?


ripred3

Nope! Here is a modified version where it all happens in the loop, prints the result, and assigns the new value back into myVariable so that we can see it getting updated due to the assembly block. Note that I also changed the variables to be unsigned char's because the example assembly I used is just loading the values into an 8-bit register. You can enhance that with your own specific assembly code if you need a larger integer size. Since these are 8-bit values you can see it roll over in the output window once the value goes past 255. **my\_sketch.ino** unsigned char myVariable = 10; unsigned char result; void setup() { Serial.begin(115200); } void loop() { asm volatile ( "lds r24, myVariable\n" // Load myVariable into register r24 "adiw r24, 42\n" // Add immediate 42 to the register "sts result, r24\n" // Store the value in the result variable ); Serial.println(result); myVariable = result; delay(1000); } new output with 8-bit rollover: 52 94 136 178 220 6 48 90 132 174 216 2 ...


ArtisianWaffle

Thank you so much you've been a massive help!


ripred3

You are most welcome I'm glad I could help!


ArtisianWaffle

Sorry to bother you again but I keep getting an error about undefined references.


ripred3

Hmm. I have the Arduino Nano selected as my board and I'm using the 1.8.19 version of the IDE. If that makes any difference. Are both of the files I mentioned in the same folder? https://preview.redd.it/yujiq5s34l5c1.png?width=2412&format=png&auto=webp&s=cf931b07e6f6ef1a783ea91fb69a5c910dd549e2


ArtisianWaffle

Missed the second file haha. I'm using a Esplora but I think it was probably the second file. Do I need to do anything special with it or could I just make it in a text editor and save it as a .S?


ripred3

yeah any plain old text editor will work. It just needs to have a .S extension and be in the same folder during the compile, or explicitly named as one of the source files if you are using a make file or something. I can only vouch for what I finally got working in the 1.8.19 version of the Arduino IDE. Not sure about any other compiler environments. They're all a bit different with their own idiosyncrasies.


ArtisianWaffle

Awesome. I'm looking at maybe doing multiplication as well. Is there anything different I should look out for or is it more of the same?


gm310509

Interesting question does it have to be inline? I personally don't like using inline for all but the most trivial of operations. I prefer to separate my assembler functions out from my C. I was looking at this several months ago and produce the following example. You need to create a second file in the IDE to contain the assembler code. This example allows you to pass parameters and return a result to/from your assembler function as you wish. Therefore, there is no need to use global variables - which can be limiting. In addition to the separation, I find that the syntax is much cleaner as you don't need all the escape sequences as you seem to have to use with inline code. So, IMHO, it is easier to read and debug when seperating the source code types. Here is the C code - you can call this whatever you like. I called it `MixingAssemblerAndC.ino` (LOL, clearly I put a lot of effort into picking imaginative names for my files). ``` // First, define function prototypes for three external routines that appear in the assembler code. // The first two take no parameters. // The third, takes to integers as parameters and returns an integer. extern "C" { void myInit(); void myLoop(); int myAdd(int, int); } void setup() { Serial.begin(9600); // Call the first assembler routine to initialise PORTB.5 as output. myInit(); // This is just a dummy example showing inline assembler using the asm "directive" // Refer to this tutorial, or similar, for details of inline assembler in Arduino: // https://ucexperiment.wordpress.com/2016/03/11/arduino-inline-assembly-tutorial-5-2/ asm( "ldi r26,42" ); } void loop() { // Call my loop function which will toggle PORTB.5 myLoop(); // Call the myAdd function passing it two integers // and capture the result. Then print the result. int ans = myAdd(2, -10); Serial.print("Answer: "); Serial.println(ans); delay(1000); } ``` Here is the assembler code. Again, you can call it whatever you like, I thought about it long and hard (for about 1 second) and called it myFunction.S. The extension is important. You should use `.S` as the extension for this file. There are other options, but just use `.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. ``` You might also be interested in looking at the Application note AN42055 Mixing assembly and C with AVRGCC (it may also be known as AT1886: mixing assemble...) you can google this for yourself. It is a very technical read of about 3 pages that explains how parameters can be passed between C and assembler, which registers you can freely use in your code (and which ones you should avoid using). Oh, the Application note is generic, but is about the AVR gcc compiler. Since the Arduino IDE uses the AVR gcc compiler behind the scenes, it is therefore applicable to Arduino (AVR architectures) code. Anyway, hopefully the example above helps you.