Tuning toolchain for Arduino for continuing

Once upon a time, it happened to me to work on a project with Arduino , where there were quite specific requirements for the predictability of code generation, and working with a black box in some places was annoying. So the idea was born to slightly adjust the assembly process and introduce some additional steps during the assembly.

As you know, the Wiring language is actually a full-fledged C ++, from which support for exceptions is removed due to their heaviness for the runtime environment and some subtleties associated with 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 from main

And this means that you can use the C ++ buns, even if no one talked about them.

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

So, let's get started.

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

To add support for newer standards than are included by default, open the file in the Arduino installation directoryhardware\arduino\avr\platform.txtcontaining the compilation settings, find the parameter there compiler.cpp.flagsand indicate the desired language standard: -std=gnu++11replace 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 rebuilding the sketch - the environment will pick up the new settings immediately.

Here you can change optimization settings, for example, instead of a more compact code, by changing the option -Osto -Ofastenable the generation of faster code.

These settings are global, therefore applicable to 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 to the same filerecipe.hooks.postbuild.0.patternwhose value should contain the name of the file that will be launched after the assembly, and command line arguments. I assigned it a meaning "{compiler.path}stat.bat" "{build.path}/{build.project_name}". Since there is no such file yet, it must be created in the directory hardware\tools\avr\bin(with the name, obviously stat.bat).

Let's write the following lines there:

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

The first line starts the decompilation of the already compiled 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 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 reports in the file with the additional extension .names, while sorting is performed by addresses.

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

Reading disassembled code in ordinary life is a dubious pleasure, but it might be necessary for someone (I have happened).

The file of names as a whole is more useful. In the spoiler - a test sketch with which 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 file of names entirely also under the spoiler:

Entire 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 it? Let's get it right.

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

Data memory consists of two parts: a register file at offset 0 and data in random access memory (SRAM) at offset 0x100. When a memory dump is output, the offset 0x00800000 is added to the SRAM addresses, which does not mean anything in essence, but is just convenient. I also note that the EEPROM offset has a value of 0x00810000, but we do not need this.

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

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

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

It 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 with bootstrap code, which will take some time.

We go further (let it not bother you that __bss_start and __data_end go in random 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 a section of uninitialized data (similar to .bss). It is given for nothing, since it is filled with zero bytes. Here are a few helper variables, names that start with timer0_, a global Serial object, and our static local variable.

A small lyrical digression: note 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 no place will be allocated for it), and secondly, we know that the order of initialization of global objects is not defined, and strictly speaking, the controller not initialized correctly.

At higher addresses, the stack is located. If the controller has 2 kilobytes of memory (boards based on the atmega328 processor), then the last address will be 0x008008FF. From this address the stack begins and it grows in the direction of decreasing addresses. There are no barriers, and in principle, the stack can crawl onto variables, ruining them. Or the data can ruin the stack - how lucky. In this case, nothing good will happen. This can be called undefined behavior, but it is better to always leave enough space for the stack in memory. How much is hard to say. That was enough for local variables and stack frames of all calls plus for the heaviest interrupt handler with all nested calls. I honestly did not find a more or less honest decision on how to diagnose the use of the stack.

Let's get back to the code. When I said that the code was stored there, I was a little disingenuous. In fact, read-only data can be stored there, but they are extracted from there by special instructions from the LPM processor. So, constant data can be placed there and placed for nothing, because time is not wasted on their initialization. Note the macros F (...) in the code. They just declare the lines placed in the program memory and then does a little 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

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

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

Empty sketch 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, isn't 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 
88: 0c 94 e4 00 jmp 0x1c8 ; 0x1c8 <_exit> 0000008c <__bad_interrupt>: 8c: 0c 94 00 00 jmp 0 ; 0x0 <__vectors> 00000090 : 90: 08 95 ret 00000092 : 92: 08 95 ret 00000094 : 94: 08 95 ret 00000096
: 96: 0e 94 a9 00 call 0x152 ; 0x152 9a: 0e 94 4a 00 call 0x94 ; 0x94 9e: 0e 94 48 00 call 0x90 ; 0x90 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 aa: 0e 94 49 00 call 0x92 ; 0x92 ae: 0e 94 00 00 call 0 ; 0x0 <__vectors> b2: fb cf rjmp .-10 ; 0xaa b4: 0e 94 49 00 call 0x92 ; 0x92 b8: 0e 94 49 00 call 0x92 ; 0x92 bc: fb cf rjmp .-10 ; 0xb4 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 : 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 is involved, 9 bytes to service this timer, and a certain amount of infrastructure code.

For today, perhaps enough. I hope this is useful to someone.

Also popular now: