📜 ⬆️ ⬇️

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