Введение
В предыдущей статье был описан процесс превращения 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
Собираем проект, запускаем его на плате и смотрим лог прошивки
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.00s
avrdude: Device signature = 0x1e950f (probably m328p)
avrdude: NOTE: "flash" memory has been specified, an erase cycle will be performed
To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "/mnt/data/Arduino/led-blink2/bin/blink.hex"
avrdude: writing flash (1040 bytes):
Writing | ################################################## | 100% 0.18s
avrdude: 1040 bytes of flash written
avrdude: verifying flash memory against /mnt/data/Arduino/led-blink2/bin/blink.hex:
avrdude: load data flash data from input file /mnt/data/Arduino/led-blink2/bin/blink.hex:
avrdude: input file /mnt/data/Arduino/led-blink2/bin/blink.hex contains 1040 bytes
avrdude: reading on-chip flash data:
Reading | ################################################## | 100% 0.15s
avrdude: verifying ...
avrdude: 1040 bytes of flash verified
avrdude: safemode: Fuses OK (E:00, H:00, L:00)
avrdude done. Thank you.
Здесь обращаем внимание на объем занятой памяти
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.: Исходный код примера проекта из статьи можно взять здесь
Комментарии (28)
Flaksirus
19.11.2017 13:34Статьи формата: «создаем себе проблему и героически её решаем».
Ардуино — это платформа для чайников в прямом смысле этого слова. Зачем усложнять такую фигню? Взяли бы ту же межку и играли бы с ней напрямую по давно известным правилам. А в итоге получается берем что-то что прогрессировало с обычной межки до платформы для чайников и даунгрейдимся обратно до межки.maisvendoo Автор
19.11.2017 14:07«создаем себе проблему и героически её решаем»
Это вот уж кому как. Для меня описанное в статье — насущная необходимость собрать и упорядочить софт, разрабатываемый разными командами в рамках одной экосистемы Linux. Выхлоп от этой деятельности я опубликовал. Без претензий на академичность. Кому надо — берите, не надо — проходим мимоFlaksirus
19.11.2017 14:50Софт разрабатываемый какими командами? Под линукс разве нет жавы и ардуино ide на ней? Зачем заморачиваться с тем что уже работает? Опишите какие проблемы вы решили, отказавшись от ардуиновского редактора для чайников и взяв взамен всем известный QtCreator?
vvzvlad
20.11.2017 00:59Начнем с того, что командная(!) серьезная(!) разработка под arduino — это уже само по себе странно.
Flaksirus
20.11.2017 10:55К этому я и веду, что это какой-то детский лепет и «электронщиков» их надо взашей гнать. Как можно серьезно что-то делать на платформе для чайников? Фото их «проекта» ниже как бы подтверждает, они там походу даже платы сами травят вместо того чтобы просто заказать на производстве. Там даже отладчик не предусмотрен, а они про подсветку кода и поиск по исходникам…
maisvendoo Автор
20.11.2017 11:28Сидеть, и заниматься диванной аналитикой проще всего. Электронщикам я Ваше мнение передам, но не думаю, что их заинтересует комментарий дотнетчика
Flaksirus
20.11.2017 11:41Вы не электронщикам, а сразу руководству лучше передайте, ведь их деньги по ветру пускают.
К слову помимо дотнетов у меня в опыте участие в разработке систем управления московского метрополитена, разработка софта для пары электромобилей и блока управления для танка на гибридной установке ;)maisvendoo Автор
20.11.2017 11:45Рад за Вас. Но тыкать меня носом в проблемы, о которых я и так знаю, поучать жизни и разводить оффтоп в комментариях попрошу прекратить
Alex_ME
20.11.2017 20:41даже платы сами травят вместо того чтобы просто заказать на производстве
Мы в универе проект на заказ выполняли, тоже сами травили. Для первых прототипов нормально. Двухслойные платы с STM32 в TQFP100 корпусе — спокойно. Промышленные, конечно, хорошо, там и маска, и металлизация отверстий.
Такую плату можно изготовить за 2-3 часа. Может быстрее. Заказывать на производстве — неделю (если не приплачивать за срочность) и цены какие-то дикие. Финальный прототип мы такие заказывали у Резонита (заказчик потребовал, чтобы были не из Китая). Если подскажите, где можно не очень дорого делать в России платы — буду рад. Китай, это, конечно, дешевле, но доставка долгая.
Flaksirus
21.11.2017 13:48Я делал в резоните, и в том числе срочные (около пары дней) и еще в какой-то конторе через предприятие, где работал — но я уже не помню как назывались и снова к ним бы не обратился, в полученной продукции не хватало двух полигонов, которые выполняли роль антенны, как они умудрились их отсечь вообще не понимаю.
И все таки одно дело прототип, который дальше рабочего стола не идет, и совсем другое размещение на готовой продукции в соответствии с габаритными размерами.
Alex_ME
19.11.2017 14:46При всех недостатках Arduino, на ней реально быстро можно что-то склепать. Куча библиотек для разного периферического железа, датчиков. Сам я пишу на STM32 и время от времени страдаю от необходимости портировать/писать нужные библиотеки.
Но Arduino IDE есть вершина убогости, хуже Sublime Text, ей-богу. Решение автора позволяет разрабатывать под Arduiono, сохраняя все преимущества (ну и недостатки), в нормальной IDE.
Flaksirus
19.11.2017 14:53Я для того обычно пишу в любом редакторе (сегодня взял бы VScode) и компилю в аруиновской идешке — вывод с ошибками есть, а что еще надо?)
Alex_ME
19.11.2017 15:15В VSCode есть полноценный автокомплит, отображение методов классов, аргументов функций и всего такого? (я не подкалываю, я сам не знаю)
Flaksirus
19.11.2017 15:40Там как в атоме, все через плагины (в том числе от атома) и конечно такие фичи реализованы уже, есть vscode-cpptools, есть плагин даже для ассемблера аврки, есть PlatformIO где и подключение к ардуинке (и не только) есть прямое.
maisvendoo Автор
19.11.2017 19:45Предлагаемый Вами подход называется «костыль», что-то подобное описано здесь.
То что предлагается мной, называется использованием инструмента по его прямому назначению, и оправдано там, где в коде более чем один файл, используется VCS и прочие вещи, характерные для нормального процесса разработкиFlaksirus
19.11.2017 23:17Только вот у Вас сам проект костыль, нормальные решения не строят на дуинах для чайников. А касательно статьи, все элементарно и придумано до Вас — идем в поиск и находим первую же статью как развернуть нормальную разработку под AVR на QtCreator (вместо классического для этой задачи eclipse) www.lucidarme.me/?p=3325
Собственно по желанию подключаете туда свои либы от дуинки.
Я же наплевал бы на дроч на линукса поставил бы старую добрую IAR и писал бы под ней или на худой конец авр студио, прикупив конечно же отладчик нормальный.
maisvendoo Автор
19.11.2017 19:58Что же касается VSCode, то это некое «хотите VS под линукс? Нате, подавитесь». Это с учетом того, что компания эта затащила к себе в ОС половину убунту. Таково мое отношение к проекту VSCode. Личное. Предвзятое
Flaksirus
19.11.2017 23:22VSCode совсем — не vs под линукс, это абсолютно новый продукт как замену некогда популярному Atom, и даже построен на той же платформе (электрон) и решает другой класс задач относительно VS. Он прежде всего для некомпилируемых яп типа жаваскрипта, тайпскрипта, руби, питон и пр html. Но платформа хороша и позволяет использовать с любыми яп если есть плагин.
bcmob
20.11.2017 09:11Так VScode и компилит и заливает сама. Родную Arduino IDE использую теперь только если заливаю прошивку на ESP8266 через OTA(по воздуху).
AVKinc
19.11.2017 17:00Человек который имеет линукс на рабочей тачке, очевидно весьма неглуп как минимум.
Так что разобраться с нормальной средой программирования для него не проблема.
Ардуино это радиоконструктор для детей, делать что-то большое из под нее это знатный кактус.maisvendoo Автор
19.11.2017 18:57делать что-то большое из под нее это знатный кактус.
Я совершенно с Вами согласен. Объясняю ситуацию вкратце.
В конторе, где я работаю, разрабатываются тренажерные комплексы. Есть две команды: электронщики — они занимаются вестимо железом и прошивками к нему, и руковожу ими не я; и программисты-прикладники, разрабатывающие высокоуровневый софт для пц. Прикладниками командую я. Платформа, используемая нами на тренажерах — линукс. Соответственно софт разрабатывается под эту ос преимущественно, отсюда и стек технологий: Qt с родными средствами разработки.
Электронщики (и не я завел эту практику) ваяют железо на семействе AVR, используя ардуино для прототипирования, а для разработки софта Arduino IDE. Всё бы ничего, но в крайнем проекте, из-за дедлайнов электронщиков на других проектах, функцию написания софта для всего железа (12 плат сопряжения и пятью уникальными прошивками) пришлось взять на себя моему отделу, ибо тогда вообще швах со сроками был бы.
Arduino IDE я не хочу и не буду использовать в своей работе. Причины, думаю понятны тем кто в теме. Поэтому 4 прошивки, которые писались нами, писались на MS VS + vMicro. MS VS нам даром не нужен, мы как-то и без него справляемся, поэтому хлебнув неудобств, я пришел к решению адаптировать проекты прошивок под используемые в наших собственных проектах инструменты, что удалось.
Иногда приходится выкручиваться в пределах тех рамок, что задаются извне. Но испытывать боль при работе с хреновым текстовым редактором от ардуино, который почему-то назвали IDE у меня желания нет.
ntfs1984
20.11.2017 01:24| Ардуино это радиоконструктор для детей, делать что-то большое из под нее это знатный кактус.
Не вижу здесь ничего детского. Arduino — это AVR (чаще всего) микронтроллер на плате, готовый к работе из коробки. Стоит вам снять микроконтроллер и перевесить его на СВОЮ плату — де-юре оно перестанет быть Ардуиной, а де-факто скетч написанный для Ардуины, заработает на вашей плате без переделок.
У этих контроллеров нет предназначения, они могут выполнять различные задачи.
Кстати хотел бы уточнить, для вас большое?
Ну например полнофункциональный счетчик электроэнергии, однотарифный или двухтарифный, с просчетом статистики энергопотребления, диаграммками и прочими полезными штуками, отличающийся он Энергомеры только отсутствием красивого корпуса и бумажки-сертификата выданной взамен заноса бабла — считается большим?
А ЭБУ на Daewoo Nexia на Ардуйне (да да, та самая которая дергает форсунки на инжекторе в зависимости от температуры, качества смеси и тд, управляет шаговым двигателем дроссельной заслонки в зависимости от положения датчика, и тд) — считается большим?
Видел еще несколько интересных проектов в продакшене, таких как датчики освещения и контроллеры ламп, где используется банальная атмега328, та самая, с Ардуины.
maisvendoo Автор
20.11.2017 07:28это радиоконструктор для детей
Если кто-то думает, что речь идет о готовых ардуинках, опутаных проводами и обвязом на макетках, то нет, Вы ошибаетесь. Готовый результат выглядит так (прячу под спойлер)
Куски проекта, о котором идет речь
ViacheslavMezentsev
19.11.2017 22:41В следующей статье, возможно, поговорим о возможностях отладки проектов AVR в Qt Creator
А можно какой-нибудь спойлер о способе отладки? Сам я использую HappyJtag2 (JtagIce mk2) для этой цели, но звучит как-то интригующе, как-будто gdb будет использоваться.
А что до применения Arduino. К примеру, мне удалось собрать на нём ПЛК, программируемый на ST с использованием среды Beremiz. Работает, тестирую. Это ещё одна IDE для Arduino. Дёшево и сердито. Как протестирую на реальных ПЛК возможно тоже напишу статью.maisvendoo Автор
20.11.2017 07:13А можно какой-нибудь спойлер о способе отладки?
Конкретно я опробовал прогон программы на эмуляторах, и таки да avr-gdb. Что касается внутрисхемной отладки, то тут, как известно, есть трудности на той же 328p. В общем, будет о чем поговорить, обязательно напишу
vassabi
>Кроме того, ардуинщики использующие линукс могут работать с удобством
чёрт, так вот зачем мелкомягкие пилят WSL
maisvendoo Автор
А причем тут WSL? Qt Creator есть и для Windows