⬆️ ⬇️

Tuning toolchain for Arduino for continuing

A long time ago it happened to me to work on a project with Arduino , where there were quite specific requirements for the predictability of code generation, and it was annoying to work with a black box in places. So the idea was born to somewhat tune the assembly process and introduce some additional steps in the assembly.



As it is known, the Wiring language is actually a full-fledged C ++, from which the exclusion support has been removed due to its heavy weight for the runtime environment and some subtleties related to the program structure are hidden, such as the implementation of the main function, and the developer is given the setup and loop functions that are called main.



And this means that you can use C ++ buns, even if nobody told about them.



Everything described in the article refers to versions of the Arduino environment from at least 1.6. I did everything under Windows, but for Linux / MacOS the approaches are the same, the technical details just change.

')

So, we proceed.



In the version 1.6 environment, the GCC compiler version 4.8 is used by default, in which C ++ 11 support is not enabled by default, but we want auto, varadic templates and other buns, isn't it? And in version 1.8, the compiler is already version 4.9, which supports C ++ 11 by default, but you can also force it to use C ++ 14 with its rvalue reference, constant expressions, bit delimiters, binary literals, and other joys.



To add support for newer standards than those included by default, you need to open the hardware\arduino\avr\platform.txt file containing the compilation settings in the Arduino installation directory, find the compiler.cpp.flags parameter and specify the desired language standard: -std=gnu++11 replace with -std=gnu++14 . If there is no parameter, add, for example, at the end of the line



After saving the file, you can try to re-compile the sketch - Wednesday will pick up the new settings immediately.



Here you can also change the optimization settings, for example, instead of a more compact code, by changing the -Os option to -Ofast enable the generation of faster code.



These settings are global, therefore applicable in all projects.



Then I decided to get more detailed control over the result of the compiler and added some analogue of postbuild-events. To do this, add the parameter recipe.hooks.postbuild.0.pattern to the same file, the value of which should contain the name of the file that will be launched after the assembly, and command line arguments. I assigned it the value "{compiler.path}stat.bat" "{build.path}/{build.project_name}" . Since there is no such file yet, it must be created in the hardware\tools\avr\bin (with the name, obviously, stat.bat ).



We write there the following lines:



 "%~dp0\avr-objdump.exe" -d -C %1.elf > %1.SX "%~dp0\avr-nm.exe" %1.elf -nCS > %1.names 


The first line starts decompiling an already assembled and optimized program using objdump, creating an output file with the project name and the additional extension .SX (the choice is arbitrary, but we assume that S is traditionally an assembler and X indicates the fact of some conversion).



The second line extracts the symbolic names of the memory areas by running the nm command and generates a report in the file with the additional extension .names, while sorting is performed by addresses.



Here you need to add that the build is in the temporary directory, and to see it, you need to go into the Arduino settings and turn on the “Show detailed output” -> “Compile” checkbox, then run this very compilation and see where the build is going. In my case (IDE version 1.8.5) it was a directory like %TEMP%\arduino_build_[random] .



To read a disassembled code in ordinary life is a dubious pleasure, but maybe someone will need it (I used to).



The name file is generally more useful. In the spoiler - a test sketch with which the experiments were performed:



Test sketch
 int counter = 1; void test(auto&& x) { static unsigned long time; long t = millis(); Serial.print(F("Counter: ")); Serial.println(counter); Serial.print(F(" At ")); Serial.print(t); Serial.print(F(" value is ")); Serial.println(x); Serial.print(F("Time between iterations: ")); Serial.println(t - time); Serial.println(); time = t; } void setup() { Serial.begin(9600); } void loop() { delay(100); test(digitalRead(A0)); } 




The resulting name file is also under the spoiler:



Whole name file


w serialEvent()

00000000 W __heap_end

00000000 a __tmp_reg__

00000000 a __tmp_reg__

00000000 a __tmp_reg__

00000000 a __tmp_reg__

00000000 a __tmp_reg__

00000000 a __tmp_reg__

00000000 a __tmp_reg__

00000000 a __tmp_reg__

00000000 a __tmp_reg__

00000000 W __vector_default

00000000 T __vectors

00000001 a __zero_reg__

00000001 a __zero_reg__

00000001 a __zero_reg__

00000001 a __zero_reg__

00000001 a __zero_reg__

00000001 a __zero_reg__

00000001 a __zero_reg__

00000001 a __zero_reg__

00000001 a __zero_reg__

0000003d a __SP_L__

0000003d a __SP_L__

0000003d a __SP_L__

0000003d a __SP_L__

0000003d a __SP_L__

0000003d a __SP_L__

0000003d a __SP_L__

0000003d a __SP_L__

0000003d a __SP_L__

0000003e a __SP_H__

0000003e a __SP_H__

0000003e a __SP_H__

0000003e a __SP_H__

0000003e a __SP_H__

0000003e a __SP_H__

0000003e a __SP_H__

0000003e a __SP_H__

0000003e a __SP_H__

0000003f a __SREG__

0000003f a __SREG__

0000003f a __SREG__

0000003f a __SREG__

0000003f a __SREG__

0000003f a __SREG__

0000003f a __SREG__

0000003f a __SREG__

0000003f a __SREG__

00000068 T __trampolines_end

00000068 T __trampolines_start

00000068 0000001a t test(int)::__c

00000082 0000000b t test(int)::__c

0000008d 00000005 t test(int)::__c

00000092 0000000a t test(int)::__c

0000009c 00000014 T digital_pin_to_timer_PGM

000000b0 00000014 T digital_pin_to_bit_mask_PGM

000000c4 00000014 T digital_pin_to_port_PGM

000000d8 0000000a T port_to_input_PGM

000000e2 T __ctors_start

000000e4 T __ctors_end

000000e4 T __dtors_end

000000e4 T __dtors_start

000000e4 W __init

000000f0 00000016 T __do_copy_data

00000106 00000010 T __do_clear_bss

0000010e t .do_clear_bss_loop

00000110 t .do_clear_bss_start

00000116 00000016 T __do_global_ctors

00000134 T __bad_interrupt

00000134 W __vector_1

00000134 W __vector_10

00000134 W __vector_11

00000134 W __vector_12

00000134 W __vector_13

00000134 W __vector_14

00000134 W __vector_15

00000134 W __vector_17

00000134 W __vector_2

00000134 W __vector_20

00000134 W __vector_21

00000134 W __vector_22

00000134 W __vector_23

00000134 W __vector_24

00000134 W __vector_25

00000134 W __vector_3

00000134 W __vector_4

00000134 W __vector_5

00000134 W __vector_6

00000134 W __vector_7

00000134 W __vector_8

00000134 W __vector_9

00000138 00000012 T setup

0000014a 000000d8 T loop

00000222 00000094 T __vector_16

000002b6 00000018 T millis

000002ce 00000046 T micros

00000314 00000050 T delay

00000364 00000076 T init

000003da 00000052 t turnOffPWM

0000042c 00000052 T digitalRead

0000047e 00000016 T HardwareSerial::available()

00000494 0000001c T HardwareSerial::peek()

000004b0 00000028 T HardwareSerial::read()

000004d8 000000b8 T HardwareSerial::write(unsigned char)

00000590 000000a4 T HardwareSerial::flush()

00000634 0000001c W serialEventRun()

00000650 00000042 T HardwareSerial::_tx_udr_empty_irq()

00000692 000000d0 T HardwareSerial::begin(unsigned long, unsigned char)

00000762 00000064 T __vector_18

000007c6 0000004c T __vector_19

00000812 00000014 T Serial0_available()

00000826 00000086 t _GLOBAL__sub_I___vector_18

000008ac 00000002 W initVariant

000008ae 00000028 T main

000008d6 00000058 T Print::write(unsigned char const*, unsigned int)

000008ff W __stack

0000092e 0000004a T Print::print(__FlashStringHelper const*)

00000978 00000242 T Print::print(long, int)

00000bba 00000016 T Print::println()

00000bd0 0000029a T Print::println(int, int)

00000e6a 0000013a T Print::println(unsigned long, int)

00000fa4 00000002 t __empty

00000fa4 00000002 W yield

00000fa6 00000044 T __udivmodsi4

00000fb2 t __udivmodsi4_loop

00000fcc t __udivmodsi4_ep

00000fea 00000004 T __tablejump2__

00000fee 00000008 T __tablejump__

00000ff6 T _exit

00000ff6 W exit

00000ff8 t __stop_program

00000ffa A __data_load_start

00000ffa T _etext

00001016 A __data_load_end

00800100 D __data_start

00800100 00000002 D counter

00800102 00000010 V vtable for HardwareSerial

0080011c B __bss_start

0080011c D __data_end

0080011c D _edata

0080011c 00000004 b test(int)::time

00800120 00000001 b timer0_fract

00800121 00000004 B timer0_millis

00800125 00000004 B timer0_overflow_count

00800129 0000009d B Serial

008001c6 B __bss_end

008001c6 N _end

00810000 N __eeprom_end







How to read this? Let's figure it out.



AVR controllers have two independent memory areas: program memory and data memory. Accordingly, a pointer to a code and a pointer to data is not the same thing. About EEPROM I am silent for now, there is a separate conversation about addressing. In the dump, the program memory is located at offset 0 and execution begins with it, and the interrupt vector table is also located there, 4 bytes per element. Unlike x86, each interrupt vector contains not the address of the handler, but the instruction to be executed. Usually (I have not seen other options) - jmp, that is, go to the desired address. We will briefly return to the interrupt vectors later, but for now we are looking further.



The data memory consists of two parts: a register file at offset 0 and in-memory data (SRAM) at offset 0x100. When a memory dump is displayed, an offset of 0x00800000 is added to the SRAM addresses, which means nothing in essence, but simply convenient. I also note that the EEPROM offset is 0x00810000, but we do not need it.



Let's start with the data, everything is a bit simpler and more familiar, and then back to the code. The first column is the offset, the second is the size, then the type (until we pay attention), further to the end of the line is the name.



We go to the offset 0x00800100 and see the records there:



 00800100 D __data_start 00800100 00000002 D counter 00800102 00000010 V vtable for HardwareSerial 0080011c D __data_end 


This is an analogue of the .data section and contains initialized global and static variables, as well as virtual method tables. These variables will be initialized by bootstrap code, which will take some time.



Then we go (let you not be embarrassed that __bss_start and __data_end go in an arbitrary order - they have a common address).



 0080011c B __bss_start 0080011c 00000004 b test(int)::time 00800120 00000001 b timer0_fract 00800121 00000004 B timer0_millis 00800125 00000004 B timer0_overflow_count 00800129 0000009d B Serial 008001c6 B __bss_end 


This is the section of uninitialized data (analog of .bss). It is given as a gift because it is filled with zero bytes. Here are a few auxiliary variables, names that start with timer0_, a global Serial object, and our static local variable.



Small lyrical digression: notice that the Serial object is initially empty, and it will be initialized when the begin method is called on it. There are several reasons for this. Firstly, it may not be used at all (then it will not be created at all and space will not be allocated for it), and secondly, we know that the order of initialization of global objects is not defined, and before entering the main strictly speaking controller not initialized correctly.



At higher addresses is the stack. If the memory capacity of the controller is 2 kilobytes (motherboards based on the atmega328 processor), then the last address will be 0x008008FF. From this address, the stack begins and it grows towards decreasing addresses. There are no barriers, and in principle the stack can crawl onto variables, spoiling them. Or the data can spoil the stack - as lucky. In this case, nothing good will happen. You can call this an undefined behavior, but it's always best to keep enough space in the memory for the stack. How much is hard to say. That is enough for local variables and frames of the stack of all calls, plus for the most difficult interrupt handler with all the nested calls. I, frankly, did not find a more or less honest decision on how to diagnose stack usage.



Let's go back to the code. When I said that the code was stored there, I was a little cunning. In fact, read-only data can also be stored there, but they are extracted from there with a special LPM processor instruction. So, constant data can be placed there and placed for free, because time is not wasted on their initialization. Pay attention to the macros F (...) in the code. They just declare the lines placed in the program memory and then do a little bit of magic. We will not go into the details of magic, but look for them in memory. Such macros go to the test function, and in the dump we will see them as



 00000068 0000001a t test(int)::__c 00000082 0000000b t test(int)::__c 0000008d 00000005 t test(int)::__c 00000092 0000000a t test(int)::__c 


Although they have the same name, they are located at different addresses. Because the macro F (...) creates a scope in curly brackets, and variables (or more precisely constants) do not interfere with each other. If you look at the disassembled dump, these will be the addresses of the lines in the program memory.



And finally, the promised interruptions. Libraries use some controller resources to support peripheral operation: timers, communication interfaces, etc. They register their interrupt handlers, store their variables in memory, and constants in program memory. If you compile a blank sketch, then the memory dump will be like this:



Dump blank memory dump
w serialEventRun()

00000000 W __heap_end

00000000 a __tmp_reg__

00000000 a __tmp_reg__

00000000 a __tmp_reg__

00000000 a __tmp_reg__

00000000 W __vector_default

00000000 T __vectors

00000001 a __zero_reg__

00000001 a __zero_reg__

00000001 a __zero_reg__

00000001 a __zero_reg__

0000003d a __SP_L__

0000003d a __SP_L__

0000003d a __SP_L__

0000003d a __SP_L__

0000003e a __SP_H__

0000003e a __SP_H__

0000003e a __SP_H__

0000003e a __SP_H__

0000003f a __SREG__

0000003f a __SREG__

0000003f a __SREG__

0000003f a __SREG__

00000068 T __ctors_end

00000068 T __ctors_start

00000068 T __dtors_end

00000068 T __dtors_start

00000068 W __init

00000068 T __trampolines_end

00000068 T __trampolines_start

00000074 00000010 T __do_clear_bss

0000007c t .do_clear_bss_loop

0000007e t .do_clear_bss_start

0000008c T __bad_interrupt

0000008c W __vector_1

0000008c W __vector_10

0000008c W __vector_11

0000008c W __vector_12

0000008c W __vector_13

0000008c W __vector_14

0000008c W __vector_15

0000008c W __vector_17

0000008c W __vector_18

0000008c W __vector_19

0000008c W __vector_2

0000008c W __vector_20

0000008c W __vector_21

0000008c W __vector_22

0000008c W __vector_23

0000008c W __vector_24

0000008c W __vector_25

0000008c W __vector_3

0000008c W __vector_4

0000008c W __vector_5

0000008c W __vector_6

0000008c W __vector_7

0000008c W __vector_8

0000008c W __vector_9

00000090 00000002 T setup

00000092 00000002 T loop

00000094 00000002 W initVariant

00000096 00000028 T main

000000be 00000094 T __vector_16

00000152 00000076 T init

000001c8 T _exit

000001c8 W exit

000001ca t __stop_program

000001cc A __data_load_end

000001cc a __data_load_start

000001cc T _etext

000008ff W __stack

00800100 B __bss_start

00800100 D _edata

00800100 00000001 b timer0_fract

00800101 00000004 B timer0_millis

00800105 00000004 B timer0_overflow_count

00800109 B __bss_end

00800109 N _end

00810000 N __eeprom_end



A lot, is not it?



And here is the code:



Disassembled code
 /empty.cpp.elf: file format elf32-avr Disassembly of section .text: 00000000 <__vectors>: 0: 0c 94 34 00 jmp 0x68 ; 0x68 <__ctors_end> 4: 0c 94 46 00 jmp 0x8c ; 0x8c <__bad_interrupt> 8: 0c 94 46 00 jmp 0x8c ; 0x8c <__bad_interrupt> c: 0c 94 46 00 jmp 0x8c ; 0x8c <__bad_interrupt> 10: 0c 94 46 00 jmp 0x8c ; 0x8c <__bad_interrupt> 14: 0c 94 46 00 jmp 0x8c ; 0x8c <__bad_interrupt> 18: 0c 94 46 00 jmp 0x8c ; 0x8c <__bad_interrupt> 1c: 0c 94 46 00 jmp 0x8c ; 0x8c <__bad_interrupt> 20: 0c 94 46 00 jmp 0x8c ; 0x8c <__bad_interrupt> 24: 0c 94 46 00 jmp 0x8c ; 0x8c <__bad_interrupt> 28: 0c 94 46 00 jmp 0x8c ; 0x8c <__bad_interrupt> 2c: 0c 94 46 00 jmp 0x8c ; 0x8c <__bad_interrupt> 30: 0c 94 46 00 jmp 0x8c ; 0x8c <__bad_interrupt> 34: 0c 94 46 00 jmp 0x8c ; 0x8c <__bad_interrupt> 38: 0c 94 46 00 jmp 0x8c ; 0x8c <__bad_interrupt> 3c: 0c 94 46 00 jmp 0x8c ; 0x8c <__bad_interrupt> 40: 0c 94 5f 00 jmp 0xbe ; 0xbe <__vector_16> 44: 0c 94 46 00 jmp 0x8c ; 0x8c <__bad_interrupt> 48: 0c 94 46 00 jmp 0x8c ; 0x8c <__bad_interrupt> 4c: 0c 94 46 00 jmp 0x8c ; 0x8c <__bad_interrupt> 50: 0c 94 46 00 jmp 0x8c ; 0x8c <__bad_interrupt> 54: 0c 94 46 00 jmp 0x8c ; 0x8c <__bad_interrupt> 58: 0c 94 46 00 jmp 0x8c ; 0x8c <__bad_interrupt> 5c: 0c 94 46 00 jmp 0x8c ; 0x8c <__bad_interrupt> 60: 0c 94 46 00 jmp 0x8c ; 0x8c <__bad_interrupt> 64: 0c 94 46 00 jmp 0x8c ; 0x8c <__bad_interrupt> 00000068 <__ctors_end>: 68: 11 24 eor r1, r1 6a: 1f be out 0x3f, r1 ; 63 6c: cf ef ldi r28, 0xFF ; 255 6e: d8 e0 ldi r29, 0x08 ; 8 70: de bf out 0x3e, r29 ; 62 72: cd bf out 0x3d, r28 ; 61 00000074 <__do_clear_bss>: 74: 21 e0 ldi r18, 0x01 ; 1 76: a0 e0 ldi r26, 0x00 ; 0 78: b1 e0 ldi r27, 0x01 ; 1 7a: 01 c0 rjmp .+2 ; 0x7e <.do_clear_bss_start> 0000007c <.do_clear_bss_loop>: 7c: 1d 92 st X+, r1 0000007e <.do_clear_bss_start>: 7e: a9 30 cpi r26, 0x09 ; 9 80: b2 07 cpc r27, r18 82: e1 f7 brne .-8 ; 0x7c <.do_clear_bss_loop> 84: 0e 94 4b 00 call 0x96 ; 0x96 <main> 88: 0c 94 e4 00 jmp 0x1c8 ; 0x1c8 <_exit> 0000008c <__bad_interrupt>: 8c: 0c 94 00 00 jmp 0 ; 0x0 <__vectors> 00000090 <setup>: 90: 08 95 ret 00000092 <loop>: 92: 08 95 ret 00000094 <initVariant>: 94: 08 95 ret 00000096 <main>: 96: 0e 94 a9 00 call 0x152 ; 0x152 <init> 9a: 0e 94 4a 00 call 0x94 ; 0x94 <initVariant> 9e: 0e 94 48 00 call 0x90 ; 0x90 <setup> a2: 80 e0 ldi r24, 0x00 ; 0 a4: 90 e0 ldi r25, 0x00 ; 0 a6: 89 2b or r24, r25 a8: 29 f0 breq .+10 ; 0xb4 <main+0x1e> aa: 0e 94 49 00 call 0x92 ; 0x92 <loop> ae: 0e 94 00 00 call 0 ; 0x0 <__vectors> b2: fb cf rjmp .-10 ; 0xaa <main+0x14> b4: 0e 94 49 00 call 0x92 ; 0x92 <loop> b8: 0e 94 49 00 call 0x92 ; 0x92 <loop> bc: fb cf rjmp .-10 ; 0xb4 <main+0x1e> 000000be <__vector_16>: be: 1f 92 push r1 c0: 0f 92 push r0 c2: 0f b6 in r0, 0x3f ; 63 c4: 0f 92 push r0 c6: 11 24 eor r1, r1 c8: 2f 93 push r18 ca: 3f 93 push r19 cc: 8f 93 push r24 ce: 9f 93 push r25 d0: af 93 push r26 d2: bf 93 push r27 d4: 80 91 01 01 lds r24, 0x0101 d8: 90 91 02 01 lds r25, 0x0102 dc: a0 91 03 01 lds r26, 0x0103 e0: b0 91 04 01 lds r27, 0x0104 e4: 30 91 00 01 lds r19, 0x0100 e8: 23 e0 ldi r18, 0x03 ; 3 ea: 23 0f add r18, r19 ec: 2d 37 cpi r18, 0x7D ; 125 ee: 20 f4 brcc .+8 ; 0xf8 <__vector_16+0x3a> f0: 01 96 adiw r24, 0x01 ; 1 f2: a1 1d adc r26, r1 f4: b1 1d adc r27, r1 f6: 05 c0 rjmp .+10 ; 0x102 <__vector_16+0x44> f8: 26 e8 ldi r18, 0x86 ; 134 fa: 23 0f add r18, r19 fc: 02 96 adiw r24, 0x02 ; 2 fe: a1 1d adc r26, r1 100: b1 1d adc r27, r1 102: 20 93 00 01 sts 0x0100, r18 106: 80 93 01 01 sts 0x0101, r24 10a: 90 93 02 01 sts 0x0102, r25 10e: a0 93 03 01 sts 0x0103, r26 112: b0 93 04 01 sts 0x0104, r27 116: 80 91 05 01 lds r24, 0x0105 11a: 90 91 06 01 lds r25, 0x0106 11e: a0 91 07 01 lds r26, 0x0107 122: b0 91 08 01 lds r27, 0x0108 126: 01 96 adiw r24, 0x01 ; 1 128: a1 1d adc r26, r1 12a: b1 1d adc r27, r1 12c: 80 93 05 01 sts 0x0105, r24 130: 90 93 06 01 sts 0x0106, r25 134: a0 93 07 01 sts 0x0107, r26 138: b0 93 08 01 sts 0x0108, r27 13c: bf 91 pop r27 13e: af 91 pop r26 140: 9f 91 pop r25 142: 8f 91 pop r24 144: 3f 91 pop r19 146: 2f 91 pop r18 148: 0f 90 pop r0 14a: 0f be out 0x3f, r0 ; 63 14c: 0f 90 pop r0 14e: 1f 90 pop r1 150: 18 95 reti 00000152 <init>: 152: 78 94 sei 154: 84 b5 in r24, 0x24 ; 36 156: 82 60 ori r24, 0x02 ; 2 158: 84 bd out 0x24, r24 ; 36 15a: 84 b5 in r24, 0x24 ; 36 15c: 81 60 ori r24, 0x01 ; 1 15e: 84 bd out 0x24, r24 ; 36 160: 85 b5 in r24, 0x25 ; 37 162: 82 60 ori r24, 0x02 ; 2 164: 85 bd out 0x25, r24 ; 37 166: 85 b5 in r24, 0x25 ; 37 168: 81 60 ori r24, 0x01 ; 1 16a: 85 bd out 0x25, r24 ; 37 16c: ee e6 ldi r30, 0x6E ; 110 16e: f0 e0 ldi r31, 0x00 ; 0 170: 80 81 ld r24, Z 172: 81 60 ori r24, 0x01 ; 1 174: 80 83 st Z, r24 176: e1 e8 ldi r30, 0x81 ; 129 178: f0 e0 ldi r31, 0x00 ; 0 17a: 10 82 st Z, r1 17c: 80 81 ld r24, Z 17e: 82 60 ori r24, 0x02 ; 2 180: 80 83 st Z, r24 182: 80 81 ld r24, Z 184: 81 60 ori r24, 0x01 ; 1 186: 80 83 st Z, r24 188: e0 e8 ldi r30, 0x80 ; 128 18a: f0 e0 ldi r31, 0x00 ; 0 18c: 80 81 ld r24, Z 18e: 81 60 ori r24, 0x01 ; 1 190: 80 83 st Z, r24 192: e1 eb ldi r30, 0xB1 ; 177 194: f0 e0 ldi r31, 0x00 ; 0 196: 80 81 ld r24, Z 198: 84 60 ori r24, 0x04 ; 4 19a: 80 83 st Z, r24 19c: e0 eb ldi r30, 0xB0 ; 176 19e: f0 e0 ldi r31, 0x00 ; 0 1a0: 80 81 ld r24, Z 1a2: 81 60 ori r24, 0x01 ; 1 1a4: 80 83 st Z, r24 1a6: ea e7 ldi r30, 0x7A ; 122 1a8: f0 e0 ldi r31, 0x00 ; 0 1aa: 80 81 ld r24, Z 1ac: 84 60 ori r24, 0x04 ; 4 1ae: 80 83 st Z, r24 1b0: 80 81 ld r24, Z 1b2: 82 60 ori r24, 0x02 ; 2 1b4: 80 83 st Z, r24 1b6: 80 81 ld r24, Z 1b8: 81 60 ori r24, 0x01 ; 1 1ba: 80 83 st Z, r24 1bc: 80 81 ld r24, Z 1be: 80 68 ori r24, 0x80 ; 128 1c0: 80 83 st Z, r24 1c2: 10 92 c1 00 sts 0x00C1, r1 1c6: 08 95 ret 000001c8 <_exit>: 1c8: f8 94 cli 000001ca <__stop_program>: 1ca: ff cf rjmp .-2 ; 0x1ca <__stop_program> 




A timer interrupt was involved, 9 bytes to service this timer, and some amount of infrastructure code.



For today, perhaps, enough. I hope someone will find this useful.

Source: https://habr.com/ru/post/346202/



All Articles