Введение
В предыдущей статье был описан процесс превращения Qt Creator в полноценную IDE для проектов на платформе Arduino. Шаги были описаны подробно, но без описания смысла происходящего, поэтому эффект от такой статьи небольшой. В каждом конкретном случае могут возникать и возникают разнообразные нюансы и разобраться в них без понимания того как устроен проект сложно.
Поэтому в данной заметке мы разберемся в структуре и настройках проекта.
1. Arduino Core и функция main()
Как известно, исполнение любой программы на C/C++ начинается с функции main(), в том числе это касается и прошивок микроконтроллеров. В той или иной форме эта функция присутствует в любом проекте. Про создании проекта в Arduino IDE нам предлагается сразу файл скетча (на ещё и с дурацки расширением *.ino), скрывая от разработчика расположение точки входа.
В арче исходники Arduino Core расположены по пути /usr/share/arduino/hardware/archlinux-arduino/avr/cores/arduino и содержат следующий
$ ls -l
итого 320
-rw-r--r-- 1 root root 1222 мар 3 2017 abi.cpp
-rw-r--r-- 1 root root 7483 мар 3 2017 Arduino.h
-rw-r--r-- 1 root root 11214 мар 3 2017 binary.h
-rw-r--r-- 1 root root 8078 мар 9 2017 CDC.cpp
-rw-r--r-- 1 root root 1529 мар 3 2017 Client.h
-rw-r--r-- 1 root root 2605 мар 3 2017 HardwareSerial0.cpp
-rw-r--r-- 1 root root 2315 мар 3 2017 HardwareSerial1.cpp
-rw-r--r-- 1 root root 1975 мар 3 2017 HardwareSerial2.cpp
-rw-r--r-- 1 root root 1975 мар 3 2017 HardwareSerial3.cpp
-rw-r--r-- 1 root root 7743 мар 3 2017 HardwareSerial.cpp
-rw-r--r-- 1 root root 5262 авг 3 16:57 HardwareSerial.h
-rw-r--r-- 1 root root 4469 мар 3 2017 HardwareSerial_private.h
-rw-r--r-- 1 root root 1142 мар 3 2017 hooks.c
-rw-r--r-- 1 root root 2851 мар 3 2017 IPAddress.cpp
-rw-r--r-- 1 root root 2861 мар 3 2017 IPAddress.h
-rw-r--r-- 1 root root 1372 мар 3 2017 main.cpp
-rw-r--r-- 1 root root 1027 мар 3 2017 new.cpp
-rw-r--r-- 1 root root 979 мар 3 2017 new.h
-rw-r--r-- 1 root root 2725 мар 3 2017 PluggableUSB.cpp
-rw-r--r-- 1 root root 2063 мар 3 2017 PluggableUSB.h
-rw-r--r-- 1 root root 1335 мар 3 2017 Printable.h
-rw-r--r-- 1 root root 5442 мар 3 2017 Print.cpp
-rw-r--r-- 1 root root 2963 авг 3 16:57 Print.h
-rw-r--r-- 1 root root 963 мар 3 2017 Server.h
-rw-r--r-- 1 root root 8804 авг 3 17:23 Stream.cpp
-rw-r--r-- 1 root root 6060 авг 3 17:23 Stream.h
-rw-r--r-- 1 root root 15022 мар 3 2017 Tone.cpp
-rw-r--r-- 1 root root 4363 июл 18 16:52 Udp.h
-rw-r--r-- 1 root root 6261 авг 3 16:57 USBAPI.h
-rw-r--r-- 1 root root 20086 июл 18 16:52 USBCore.cpp
-rw-r--r-- 1 root root 8435 мар 3 2017 USBCore.h
-rw-r--r-- 1 root root 1519 мар 3 2017 USBDesc.h
-rw-r--r-- 1 root root 4576 мар 3 2017 WCharacter.h
-rw-r--r-- 1 root root 9409 мар 3 2017 WInterrupts.c
-rw-r--r-- 1 root root 7850 мар 3 2017 wiring_analog.c
-rw-r--r-- 1 root root 12024 мар 3 2017 wiring.c
-rw-r--r-- 1 root root 4978 мар 3 2017 wiring_digital.c
-rw-r--r-- 1 root root 2255 мар 3 2017 wiring_private.h
-rw-r--r-- 1 root root 3435 мар 3 2017 wiring_pulse.c
-rw-r--r-- 1 root root 6022 мар 3 2017 wiring_pulse.S
-rw-r--r-- 1 root root 1550 мар 3 2017 wiring_shift.c
-rw-r--r-- 1 root root 1641 мар 3 2017 WMath.cpp
-rw-r--r-- 1 root root 16989 мар 3 2017 WString.cpp
-rw-r--r-- 1 root root 9910 мар 3 2017 WString.h
Функция main() расположена в файле main.cpp и выглядит так
#include <Arduino.h>
// Declared weak in Arduino.h to allow user redefinitions.
int atexit(void (* /*func*/ )()) { return 0; }
// Weak empty variant initialization function.
// May be redefined by variant files.
void initVariant() __attribute__((weak));
void initVariant() { }
void setupUSB() __attribute__((weak));
void setupUSB() { }
int main(void)
{
init();
initVariant();
#if defined(USBCON)
USBDevice.attach();
#endif
setup();
for (;;) {
loop();
if (serialEventRun) serialEventRun();
}
return 0;
}
Видно, что в скетче нет ничего сверхъестественного: функции setup() и loop() вызываются непосредственно из main(). Файл led-blink.cpp, который мы создали ранее содержит определения этих функций. Если мы уберем данный файл из проекта
#Заголовки проекта
#INCLUDEPATH += ./include
#HEADERS += $$files(./include/*.h)
# Исходники проекта
#SOURCES += $$files(./src/*.cpp)
получим закономерную ошибку компоновщика
Таким образом, все модули, которые мы добавим проекту будут скомпонованы с ядром Arduino, реализующим базовый функционал. Вот краткое описание заголовков Arduino Core:
- Arduino.h — базовый заголовок, включающий заголовки стандартной библиотеки C, определения программного интерфейса к регистрам контроллеров AVR, основные макроопределения, используемые при программировании
- binary.h — макроопределения для записи чисел от 0 до 255 в двоичной форме
- Client.h — класс клиента сети Ethernet
- HardwareSerial.h, HardwareSerial_private.h — библиотека для работы с аппаратным UART
- IPAddress.h — работа с IP-адресами сетевых протоколов Ethernet
- new.h — реализация операторов new и delete языка C++
- PluggableUSB.h, USBAPI.h, USBCore.h, USBDesc.h — библиотека для реализации USB-устройств
- Print.h, Printable.h, Stream.h — библиотеки для работы с символьными потоками данных, в том числе передаваемыми по UART
- Server.h — класс, реализующий сервер Eternet
- Udp.h — реализация протокола UDP
- WCharacters.h, WString.h — классы для работы с символами и строками
- wiring_private.h — библиотека платформы Wiring, на базе которой строится Arduino Core. Эта библиотека реализует относительно высокоуровневый интерфейс к системным ресурсам микроконтроллеров.
Таким образом, даже в простейшую программу мигания светодиодом включается масса ненужного кода. Такова плата за простоту разработки и низкий порог вхождения. Однако, говоря об этом, я лукавлю: пример, показанный в прошлой статье не соответствует тому, что получается после сборки в Arduino IDE.
2. Обрезаем жирок с прошивки
В Arduino IDE Core собирается в отдельную статическую библиотеку core.a, которая затем компонуется с объектными файлами скетча в готовый бинарник. Проделаем тоже самое в Qt Creator.
Создадим проект core со следующей структурой
Скрипт qmake этого проекта представлен ниже:
# Целевой каталог и имя библиотеки
DESTDIR = ../../lib
TARGET = core
# Подключаем заголовочные файлы
INCLUDEPATH += $$ARDUINO_DIR/cores/arduino
INCLUDEPATH += $$ARDUINO_DIR/variants/standard
INCLUDEPATH += $$ARDUINO_DIR/libraries
INCLUDEPATH += /usr/avr/include
# Настройки компилятора C
QMAKE_CC = /usr/bin/avr-gcc
QMAKE_CFLAGS += -c -g -Os -w -ffunction-sections -fdata-sections
QMAKE_CFLAGS += -MMD -mmcu=$$ARDUINO_MCU -DF_CPU=$$ARDUINO_FCPU
QMAKE_CFLAGS += -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR
# Настройки компилятора C++
QMAKE_CXX = /usr/bin/avr-g++
QMAKE_CXXFLAGS += -c -g -Os -w -ffunction-sections -fdata-sections
QMAKE_CXXFLAGS += -fno-exceptions -fno-threadsafe-statics
QMAKE_CXXFLAGS += -MMD -mmcu=$$ARDUINO_MCU -DF_CPU=$$ARDUINO_FCPU
QMAKE_CXXFLAGS += -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR
# Заголовки Arduino Core
HEADERS += $$files($$ARDUINO_DIR/cores/arduino/*.h)
HEADERS += $$files($$ARDUINO_DIR/variants/standard/*.h)
# Исходники Arduino Core
SOURCES += $$files($$ARDUINO_DIR/cores/arduino/*.c)
SOURCES += $$files($$ARDUINO_DIR/cores/arduino/*.cpp)
Проект содержит исключительно код Arduino Core. Его сборка дает на выходе библиотеку libcore.a
Теперь рядышком создаем проект прошивки, содержащий код скетча
# Определяем переменные окружения сборки
# Корневой каталог исходников Arduino Core
ARDUINO_DIR=/usr/share/arduino/hardware/archlinux-arduino/avr/
# Выбираем целевой контроллер (Arduino Uno, Nano, Mini)
ARDUINO_MCU=atmega328p
# Частота тактирования контроллера
ARDUINO_FCPU = 16000000L
# Ни гуи, ни ядра Qt нам не надо!
QT -= gui core
CONFIG -= qt
# Шаблон проекта - приложение, будет собираться исполняемый файл формата ELF
TEMPLATE = app
# Целевой каталог и имя бинарника
DESTDIR = ../../bin
TARGET = blink
# Подключаем заголовочные файлы
INCLUDEPATH += $$ARDUINO_DIR/cores/arduino
INCLUDEPATH += $$ARDUINO_DIR/variants/standard
INCLUDEPATH += $$ARDUINO_DIR/libraries
INCLUDEPATH += /usr/avr/include
# Настройки компилятора C
QMAKE_CC = /usr/bin/avr-gcc
QMAKE_CFLAGS += -c -g -Os -w -ffunction-sections -fdata-sections
QMAKE_CFLAGS += -MMD -mmcu=$$ARDUINO_MCU -DF_CPU=$$ARDUINO_FCPU
QMAKE_CFLAGS += -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR
# Настройки компилятора C++
QMAKE_CXX = /usr/bin/avr-g++
QMAKE_CXXFLAGS += -c -g -Os -w -ffunction-sections -fdata-sections
QMAKE_CXXFLAGS += -fno-exceptions -fno-threadsafe-statics
QMAKE_CXXFLAGS += -MMD -mmcu=$$ARDUINO_MCU -DF_CPU=$$ARDUINO_FCPU
QMAKE_CXXFLAGS += -DARDUINO_AVR_UNO -DARDUINO_ARCH_AVR
# Настройки компоновщика
QMAKE_LINK = /usr/bin/avr-gcc
QMAKE_LFLAGS = -w -Os -Wl,--gc-sections -mmcu=$$ARDUINO_MCU
QMAKE_LIBS = -lm
# Постобработка
QMAKE_POST_LINK += /usr/bin/avr-objcopy -O ihex -j .text -j .data -S ${TARGET} ${TARGET}.hex
LIBS += -L../../lib -lcore
#Заголовки проекта
INCLUDEPATH += ./include
HEADERS += $$files(./include/*.h)
# Исходники проекта
SOURCES += $$files(./src/*.cpp)
#ifndef LED_BLINK_H
#define LED_BLINK_H
#include <Arduino.h>
#endif // LED_BLINK_H
#include "blink.h"
#define LED_STAND_PIN 13
unsigned long time = 0;
unsigned long DELAY = 1000000;
bool on = false;
//------------------------------------------------------------------------------
//
//------------------------------------------------------------------------------
void setup()
{
pinMode(LED_STAND_PIN, OUTPUT);
}
//------------------------------------------------------------------------------
//
//------------------------------------------------------------------------------
void loop()
{
if ( micros() >= time + DELAY )
{
time = micros();
on = !on;
}
uint8_t state = on ? HIGH : LOW;
digitalWrite(LED_STAND_PIN, state);
}
Оба проекта будем собирать совместно, используя тип проекта «сабдиректории» доступный в qmake
TEMPLATE = subdirs
SUBDIRS += ./core
SUBDIRS += ./blink
Собираем проект, запускаем его на плате и смотрим лог прошивки
TEMPLATE = subdirs
SUBDIRS += ./core
SUBDIRS += ./blink
Здесь обращаем внимание на объем занятой памяти
avrdude: verifying ...
avrdude: 1040 bytes of flash verified
Ага, прошивка занимает уже 1040 байт против 2838 в проекте из прошлой статьи. Но всё же, аналогичный скетч в Arduino IDE занимает 882 байта. Внимательно изучив лог сборки среды ардуино, добавляем в проекты blink и core ключи компилятора C
QMAKE_CFLAGS += -flto -fno-fat-lto-objects
и ключи компилятора C++
QMAKE_CXXFLAGS += -fpermissive -flto -fno-devirtualize -fno-use-cxa-atexit
Пересобираем, шьем, запускаем и…
avrdude: verifying ...
avrdude: 882 bytes of flash verified
Ок, вожделенные 882 байта достигнуты. За счет чего так выходит?
Во-первых, посмотрим на ELF-файлы, получающиеся при сборке нынешнего и предыдущего проекта, а именно обратим внимание на символьную информацию, которая даст представление о том, что из функций и классов Arduino Core в итоге попадает в бинарник. Даем команду
$ avr-objdump -t led-blink
led-blink: формат файла elf32-avr
SYMBOL TABLE:
00800100 l d .data 00000000 .data
00000000 l d .text 00000000 .text
00800122 l d .bss 00000000 .bss
00000000 l d .stab 00000000 .stab
00000000 l d .stabstr 00000000 .stabstr
00000000 l d .comment 00000000 .comment
00000000 l d .note.gnu.avr.deviceinfo 00000000 .note.gnu.avr.deviceinfo
00000000 l d .debug_info 00000000 .debug_info
00000000 l d .debug_abbrev 00000000 .debug_abbrev
00000000 l d .debug_line 00000000 .debug_line
00000000 l d .debug_str 00000000 .debug_str
00000000 l df *ABS* 00000000 WInterrupts.c
0000003e l *ABS* 00000000 __SP_H__
0000003d l *ABS* 00000000 __SP_L__
0000003f l *ABS* 00000000 __SREG__
00000000 l *ABS* 00000000 __tmp_reg__
00000001 l *ABS* 00000000 __zero_reg__
00000112 l F .text 00000002 nothing
00800100 l O .data 00000004 intFunc
00000000 l df *ABS* 00000000 HardwareSerial.cpp
0000003e l *ABS* 00000000 __SP_H__
0000003d l *ABS* 00000000 __SP_L__
0000003f l *ABS* 00000000 __SREG__
00000000 l *ABS* 00000000 __tmp_reg__
00000001 l *ABS* 00000000 __zero_reg__
00000000 l df *ABS* 00000000 IPAddress.cpp
0000003e l *ABS* 00000000 __SP_H__
0000003d l *ABS* 00000000 __SP_L__
0000003f l *ABS* 00000000 __SREG__
00000000 l *ABS* 00000000 __tmp_reg__
00000001 l *ABS* 00000000 __zero_reg__
0000078a l F .text 00000016 _GLOBAL__sub_I_IPAddress.cpp
008001c8 l O .bss 00000006 _ZL11INADDR_NONE
00000000 l df *ABS* 00000000 Tone.cpp
0000003e l *ABS* 00000000 __SP_H__
0000003d l *ABS* 00000000 __SP_L__
0000003f l *ABS* 00000000 __SREG__
00000000 l *ABS* 00000000 __tmp_reg__
00000001 l *ABS* 00000000 __zero_reg__
0080011c l O .data 00000001 _ZL9tone_pins
000000b8 l O .text 00000001 _ZL21tone_pin_to_timer_PGM
00000000 l df *ABS* 00000000 led-blink.cpp
0000003e l *ABS* 00000000 __SP_H__
0000003d l *ABS* 00000000 __SP_L__
0000003f l *ABS* 00000000 __SREG__
00000000 l *ABS* 00000000 __tmp_reg__
00000001 l *ABS* 00000000 __zero_reg__
00000000 l df *ABS* 00000000 wiring_digital.c
0000003e l *ABS* 00000000 __SP_H__
0000003d l *ABS* 00000000 __SP_L__
0000003f l *ABS* 00000000 __SREG__
00000000 l *ABS* 00000000 __tmp_reg__
00000001 l *ABS* 00000000 __zero_reg__
00000304 l F .text 00000052 turnOffPWM
00000000 l df *ABS* 00000000 HardwareSerial0.cpp
0000003e l *ABS* 00000000 __SP_H__
0000003d l *ABS* 00000000 __SP_L__
0000003f l *ABS* 00000000 __SREG__
00000000 l *ABS* 00000000 __tmp_reg__
00000001 l *ABS* 00000000 __zero_reg__
00000694 l F .text 0000005a _GLOBAL__sub_I_HardwareSerial0.cpp
00000000 l df *ABS* 00000000 _clear_bss.o
000000ea l .text 00000000 .do_clear_bss_start
000000e8 l .text 00000000 .do_clear_bss_loop
00000000 l df *ABS* 00000000 wiring.c
0000003e l *ABS* 00000000 __SP_H__
0000003d l *ABS* 00000000 __SP_L__
0000003f l *ABS* 00000000 __SREG__
00000000 l *ABS* 00000000 __tmp_reg__
00000001 l *ABS* 00000000 __zero_reg__
00800122 l O .bss 00000001 timer0_fract
00000000 l df *ABS* 00000000 main.cpp
0000003e l *ABS* 00000000 __SP_H__
0000003d l *ABS* 00000000 __SP_L__
0000003f l *ABS* 00000000 __SREG__
00000000 l *ABS* 00000000 __tmp_reg__
00000001 l *ABS* 00000000 __zero_reg__
00000000 l df *ABS* 00000000 Print.cpp
0000003e l *ABS* 00000000 __SP_H__
0000003d l *ABS* 00000000 __SP_L__
0000003f l *ABS* 00000000 __SREG__
00000000 l *ABS* 00000000 __tmp_reg__
00000001 l *ABS* 00000000 __zero_reg__
0000081e l F .text 0000001e _ZN5Print5writeEPKc.part.2
00000000 l df *ABS* 00000000 _udivmodsi4.o
00000ac6 l .text 00000000 __udivmodsi4_ep
00000aac l .text 00000000 __udivmodsi4_loop
00000000 l df *ABS* 00000000 _exit.o
00000af2 l .text 00000000 __stop_program
00000000 l df *ABS* 00000000 hooks.c
0000003e l *ABS* 00000000 __SP_H__
0000003d l *ABS* 00000000 __SP_L__
0000003f l *ABS* 00000000 __SREG__
00000000 l *ABS* 00000000 __tmp_reg__
00000001 l *ABS* 00000000 __zero_reg__
0000010e w .text 00000000 __vector_22
00800127 g O .bss 00000004 timer0_overflow_count
0000094a g F .text 0000002a _Z6noToneh
00000772 g F .text 00000018 _ZN9IPAddressC1Ehhhh
000000ae g O .text 0000000a port_to_mode_PGM
00000114 g F .text 0000004e __vector_1
0000ffa0 g *ABS* 00000000 __DATA_REGION_LENGTH__
00800123 g O .bss 00000004 timer0_millis
00000442 g F .text 0000001c _ZN14HardwareSerial4peekEv
0000084a g F .text 00000098 _ZN5Print11printNumberEmh
000007c4 g F .text 0000005a _ZN5Print5writeEPKhj
00000068 g .text 00000000 __trampolines_start
008001cf g O .bss 00000002 timer2_pin_port
00000af4 g .text 00000000 _etext
0000042a g F .text 00000018 _ZN14HardwareSerial9availableEv
0000010e w .text 00000000 __vector_24
00000a34 g F .text 0000006c loop
000004c0 g F .text 00000042 _ZN14HardwareSerial17_tx_udr_empty_irqEv
0000010e w .text 00000000 __vector_12
000007a0 w F .text 00000002 initVariant
000006ee g F .text 00000084 _ZNK9IPAddress7printToER5Print
00000542 g F .text 0000008e _ZN14HardwareSerial5writeEh
0000010e g .text 00000000 __bad_interrupt
00000b16 g *ABS* 00000000 __data_load_end
0000010e w .text 00000000 __vector_6
008001d5 g O .bss 00000001 on
00000068 g .text 00000000 __trampolines_end
0000010e w .text 00000000 __vector_3
000003ce g F .text 0000005c digitalWrite
00000356 g F .text 00000078 pinMode
00000090 g O .text 00000014 digital_pin_to_port_PGM
0000010e w .text 00000000 __vector_23
00000af4 g *ABS* 00000000 __data_load_start
000000be g .text 00000000 __dtors_end
008001da g .bss 00000000 __bss_end
00000400 g *ABS* 00000000 __LOCK_REGION_LENGTH__
0000010e w .text 00000000 __vector_25
0000090a g F .text 00000040 _Z12disableTimerh
0000010e w .text 00000000 __vector_11
00000486 g F .text 0000001e _ZN14HardwareSerial17availableForWriteEv
000000be w .text 00000000 __init
000008fc g F .text 0000000e _ZN5Print5printEhi
00000772 g F .text 00000018 _ZN9IPAddressC2Ehhhh
000004a4 w F .text 0000001c _Z14serialEventRunv
00000502 g F .text 00000040 _ZN14HardwareSerial5flushEv
0000010e w .text 00000000 __vector_13
0000010e w .text 00000000 __vector_17
00000634 g F .text 0000004c __vector_19
00000974 g F .text 000000b8 __vector_7
0080012b g O .bss 0000009d Serial
00800104 w O .data 00000012 _ZTV14HardwareSerial
000000e0 g .text 00000010 .hidden __do_clear_bss
0000083c g F .text 0000000e _ZN5Print5printEc
00000680 g F .text 00000014 _Z17Serial0_availablev
00810000 g .stab 00000000 __eeprom_end
0000007c g O .text 00000014 digital_pin_to_bit_mask_PGM
00800116 w O .data 00000006 _ZTV9IPAddress
00000000 g .text 00000000 __vectors
00800122 g .data 00000000 __data_end
00000000 w .text 00000000 __vector_default
0000010e w .text 00000000 __vector_5
00000400 g *ABS* 00000000 __SIGNATURE_REGION_LENGTH__
00000ae4 g .text 0000000c .hidden __tablejump2__
0000028e g F .text 00000076 init
000000ba g .text 00000000 __ctors_start
000000ca g .text 00000016 .hidden __do_copy_data
0080011d g O .data 00000004 DELAY
00800122 g .bss 00000000 __bss_start
000007a2 g F .text 00000022 main
0000010e w .text 00000000 __vector_4
008001d6 g O .bss 00000004 time
00000244 g F .text 0000004a micros
008001ce g O .bss 00000001 timer2_pin_mask
00000000 w *ABS* 00000000 __heap_end
0000010e w .text 00000000 __vector_9
00000162 g F .text 0000004e __vector_2
00000400 g *ABS* 00000000 __USER_SIGNATURE_REGION_LENGTH__
0000010e w .text 00000000 __vector_21
0000010e w .text 00000000 __vector_15
000000a4 g O .text 0000000a port_to_output_PGM
000008e2 g F .text 0000001a _ZN5Print5printEmi
00000a2c g F .text 00000008 setup
008001da g .stab 00000000 __heap_start
000000be g .text 00000000 __dtors_start
000000be g .text 00000000 __ctors_end
000008ff w *ABS* 00000000 __stack
00800122 g .data 00000000 _edata
008001da g .stab 00000000 _end
0000010e w .text 00000000 __vector_8
00000068 g O .text 00000014 digital_pin_to_timer_PGM
00000af0 w .text 00000000 .hidden exit
0000045e g F .text 00000028 _ZN14HardwareSerial4readEv
00000aa0 g .text 00000044 .hidden __udivmodsi4
00010000 g *ABS* 00000000 __EEPROM_REGION_LENGTH__
00000af0 g .text 00000000 .hidden _exit
0000010e w .text 00000000 __vector_14
0000010e w .text 00000000 __vector_10
008001d1 g O .bss 00000004 timer2_toggle_count
000001b0 g F .text 00000094 __vector_16
00800100 g .data 00000000 __data_start
000005d0 g F .text 00000064 __vector_18
00000400 g *ABS* 00000000 __FUSE_REGION_LENGTH__
00020000 g *ABS* 00000000 __TEXT_REGION_LENGTH__
0000010e w .text 00000000 __vector_20
000000f0 g .text 00000016 .hidden __do_global_ctors
Теперь сравним со вторым проектом
blink: формат файла elf32-avr
SYMBOL TABLE:
00800100 l d .data 00000000 .data
00000000 l d .text 00000000 .text
00800100 l d .bss 00000000 .bss
00000000 l d .comment 00000000 .comment
00000000 l d .note.gnu.avr.deviceinfo 00000000 .note.gnu.avr.deviceinfo
00000000 l d .debug_info 00000000 .debug_info
00000000 l d .debug_abbrev 00000000 .debug_abbrev
00000000 l d .debug_line 00000000 .debug_line
00000000 l d .debug_str 00000000 .debug_str
00000000 l df *ABS* 00000000
0000003e l *ABS* 00000000 __SP_H__
0000003d l *ABS* 00000000 __SP_L__
0000003f l *ABS* 00000000 __SREG__
00000000 l *ABS* 00000000 __tmp_reg__
00000001 l *ABS* 00000000 __zero_reg__
000000e0 l F .text 00000038 pinMode.constprop.1
000000a4 l O .text 00000014 digital_pin_to_bit_mask_PGM
00000090 l O .text 00000014 digital_pin_to_port_PGM
00000086 l O .text 0000000a port_to_mode_PGM
0000007c l O .text 0000000a port_to_output_PGM
00000118 l F .text 00000090 digitalWrite.constprop.0
00000068 l O .text 00000014 digital_pin_to_timer_PGM
000001a8 l F .text 00000076 init
0000021e l F .text 0000004a micros
00800105 l O .bss 00000004 timer0_overflow_count
0080010a l O .bss 00000004 time
00800109 l O .bss 00000001 on
00800101 l O .bss 00000004 timer0_millis
00800100 l O .bss 00000001 timer0_fract
00000000 l df *ABS* 00000000 _clear_bss.o
000000ce l .text 00000000 .do_clear_bss_start
000000cc l .text 00000000 .do_clear_bss_loop
00000000 l df *ABS* 00000000 _exit.o
00000370 l .text 00000000 __stop_program
000000dc w .text 00000000 __vector_22
000000dc w .text 00000000 __vector_1
0000ffa0 g *ABS* 00000000 __DATA_REGION_LENGTH__
00000068 g .text 00000000 __trampolines_start
00000372 g .text 00000000 _etext
000000dc w .text 00000000 __vector_24
000000dc w .text 00000000 __vector_12
000000dc g .text 00000000 __bad_interrupt
00000372 g *ABS* 00000000 __data_load_end
000000dc w .text 00000000 __vector_6
00000068 g .text 00000000 __trampolines_end
000000dc w .text 00000000 __vector_3
000000dc w .text 00000000 __vector_23
00000372 g *ABS* 00000000 __data_load_start
000000b8 g .text 00000000 __dtors_end
0080010e g .bss 00000000 __bss_end
00000400 g *ABS* 00000000 __LOCK_REGION_LENGTH__
000000dc w .text 00000000 __vector_25
000000dc w .text 00000000 __vector_11
000000b8 w .text 00000000 __init
000000dc w .text 00000000 __vector_13
000000dc w .text 00000000 __vector_17
000000dc w .text 00000000 __vector_19
000000dc w .text 00000000 __vector_7
000000c4 g .text 00000010 .hidden __do_clear_bss
00810000 g .comment 00000000 __eeprom_end
00000000 g .text 00000000 __vectors
00000000 w .text 00000000 __vector_default
000000dc w .text 00000000 __vector_5
00000400 g *ABS* 00000000 __SIGNATURE_REGION_LENGTH__
000000b8 g .text 00000000 __ctors_start
00800100 g .bss 00000000 __bss_start
000002fc g F .text 00000072 main
000000dc w .text 00000000 __vector_4
00000000 w *ABS* 00000000 __heap_end
000000dc w .text 00000000 __vector_9
000000dc w .text 00000000 __vector_2
00000400 g *ABS* 00000000 __USER_SIGNATURE_REGION_LENGTH__
000000dc w .text 00000000 __vector_21
000000dc w .text 00000000 __vector_15
000000b8 g .text 00000000 __dtors_start
000000b8 g .text 00000000 __ctors_end
000008ff w *ABS* 00000000 __stack
00800100 g .data 00000000 _edata
0080010e g .comment 00000000 _end
000000dc w .text 00000000 __vector_8
0000036e w .text 00000000 .hidden exit
00010000 g *ABS* 00000000 __EEPROM_REGION_LENGTH__
0000036e g .text 00000000 .hidden _exit
000000dc w .text 00000000 __vector_14
000000dc w .text 00000000 __vector_10
00000268 g F .text 00000094 __vector_16
000000dc w .text 00000000 __vector_18
00000400 g *ABS* 00000000 __FUSE_REGION_LENGTH__
00020000 g *ABS* 00000000 __TEXT_REGION_LENGTH__
000000dc w .text 00000000 __vector_20
Разница очевидна. Видно, что при компоновке core в отдельную библиотеку, компилятор включает в прошивку только реально используемые в ней части core. В частности, ни в том ни в другом случае мы не используем UART, однако в первой прошивке присутствуют классы для работы с ним. Уменьшение объема прошивки ключами компилятора следует обсудить отдельно, как и сами ключи
3. Ключи компилятора и линковщика
Опции компилятора:
- -flto — включает оптимизацию при компоновке. Функции в связанных объектных файлах линкуются так, как если бы они находились в пределах одной единицы трансляции
- -fno-fat-lto-objects — не создавать «жирных» объектных файлов, содержащих промежуточный язык, кроме объектного кода. Действует совместно с предыдущим ключом.
- -fpermissive — снижает уровень некоторых ошибок компилятора до уровня предупреждений. Может привести к генерации некорректного кода
- -fuse-cxa-aexit — использовать в деструкторах объектов функцию __cxa-atexit() вместо atexit()
- -ffunction-sections, -fdata-sections — помещать каждую функцию и данные в отдельные секции, для оптимизации при последующей компоновке. Позволяет компоновщику включать в итоговый файл только реально используемые функции
- -fno-threadsafe-statics — не использовать потокобезопасные приемы работы со статическими членами классов. Имеет смысл, так как в контроллерах AVR единственный поток выполнения
- -fno-exceptions — не использовать обработку исключений
- -fno-devirtualize — не использовать «девиртуализацию». Если компилятор знает тип объекта, он может вызывать его виртуальные методы напрямую, не используя таблицу виртуальных функций. Данная опция выключает этот механизм
- -MMD — генерация отдельных правил сборки для каждой единицы трансляции, с созданием списка зависимостей для неё в файле *.d (каждому файлу *.c/*.cpp соответствует файл с тем же именем и расширением *.d, содержащий пути к зависимостям)
- -DARDUINO_AVR_UNO, -DARDUINO_ARCH_AVR — создают при предпроцессинге макроопределения ARDUINO_AVR_UNO и ARDUINO_ARCH_AVR, активирующие нужные направления условной компиляции исходников.
Опции линковщика:
- -w — отключение всех предупреждений
- -Os — оптимизация по размеру конечного файла
- -Wl,--gc-sections — активирует выборочную компоновку функций. В конечный файл включаются только используемые функции
- -mmcu — используемая модель контроллера
Как видно, все настройки сводятся к отключению примочек, используемых в прикладном программировании и повышающих безопасность кода, а так же направлены на максимальное уменьшение объема итоговой прошивки.
Выводы
Надеюсь, что данный текст расставляет все точки над «i». В платформе Arduino нет ничего сверхъестественного. Её архитектура направлена на сокрытие от начинающего разработчика всех механизмов, использование которых совершенно обычно для тех, кто использует для разработки ПО для AVR чистый C или ассемблер.
Кроме того, ардуинщики использующие линукс могут работать с удобством: эта и предыдущие статьи, в меру красноречия и компетентности их автора, освещают вопрос использования при разработке нормальной удобной IDE.
Надеюсь, что это информация оказалось полезной. В следующей статье, возможно, поговорим о возможностях отладки проектов AVR в Qt Creator
P.S.: Исходный код примера проекта из статьи можно взять здесь
Автор: Дмитрий Притыкин