Введение


В предыдущей статье был описан процесс превращения 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 и содержат следующий

список файлов Arduino Core
$ 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 этого проекта представлен ниже:

core.pro
# Целевой каталог и имя библиотеки
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

Теперь рядышком создаем проект прошивки, содержащий код скетча



blink.pro
# Определяем переменные окружения сборки

# Корневой каталог исходников 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)


blink.h
#ifndef LED_BLINK_H
#define LED_BLINK_H

#include    <Arduino.h>

#endif // LED_BLINK_H


blink.cpp
#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

led-blink2.pro
TEMPLATE = subdirs

SUBDIRS += ./core
SUBDIRS += ./blink


Собираем проект, запускаем его на плате и смотрим лог прошивки

Лог прошивки blink.hex
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
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
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)


  1. vassabi
    19.11.2017 09:48

    >Кроме того, ардуинщики использующие линукс могут работать с удобством

    чёрт, так вот зачем мелкомягкие пилят WSL


    1. maisvendoo Автор
      19.11.2017 12:40

      А причем тут WSL? Qt Creator есть и для Windows


  1. Flaksirus
    19.11.2017 13:34

    Статьи формата: «создаем себе проблему и героически её решаем».
    Ардуино — это платформа для чайников в прямом смысле этого слова. Зачем усложнять такую фигню? Взяли бы ту же межку и играли бы с ней напрямую по давно известным правилам. А в итоге получается берем что-то что прогрессировало с обычной межки до платформы для чайников и даунгрейдимся обратно до межки.


    1. maisvendoo Автор
      19.11.2017 14:07

      «создаем себе проблему и героически её решаем»

      Это вот уж кому как. Для меня описанное в статье — насущная необходимость собрать и упорядочить софт, разрабатываемый разными командами в рамках одной экосистемы Linux. Выхлоп от этой деятельности я опубликовал. Без претензий на академичность. Кому надо — берите, не надо — проходим мимо


      1. Flaksirus
        19.11.2017 14:50

        Софт разрабатываемый какими командами? Под линукс разве нет жавы и ардуино ide на ней? Зачем заморачиваться с тем что уже работает? Опишите какие проблемы вы решили, отказавшись от ардуиновского редактора для чайников и взяв взамен всем известный QtCreator?


        1. vvzvlad
          20.11.2017 00:59

          Начнем с того, что командная(!) серьезная(!) разработка под arduino — это уже само по себе странно.


          1. Flaksirus
            20.11.2017 10:55

            К этому я и веду, что это какой-то детский лепет и «электронщиков» их надо взашей гнать. Как можно серьезно что-то делать на платформе для чайников? Фото их «проекта» ниже как бы подтверждает, они там походу даже платы сами травят вместо того чтобы просто заказать на производстве. Там даже отладчик не предусмотрен, а они про подсветку кода и поиск по исходникам…


            1. maisvendoo Автор
              20.11.2017 11:28

              Сидеть, и заниматься диванной аналитикой проще всего. Электронщикам я Ваше мнение передам, но не думаю, что их заинтересует комментарий дотнетчика


              1. Flaksirus
                20.11.2017 11:41

                Вы не электронщикам, а сразу руководству лучше передайте, ведь их деньги по ветру пускают.
                К слову помимо дотнетов у меня в опыте участие в разработке систем управления московского метрополитена, разработка софта для пары электромобилей и блока управления для танка на гибридной установке ;)


                1. maisvendoo Автор
                  20.11.2017 11:45

                  Рад за Вас. Но тыкать меня носом в проблемы, о которых я и так знаю, поучать жизни и разводить оффтоп в комментариях попрошу прекратить


            1. Alex_ME
              20.11.2017 20:41

              даже платы сами травят вместо того чтобы просто заказать на производстве

              Мы в универе проект на заказ выполняли, тоже сами травили. Для первых прототипов нормально. Двухслойные платы с STM32 в TQFP100 корпусе — спокойно. Промышленные, конечно, хорошо, там и маска, и металлизация отверстий.


              Такую плату можно изготовить за 2-3 часа. Может быстрее. Заказывать на производстве — неделю (если не приплачивать за срочность) и цены какие-то дикие. Финальный прототип мы такие заказывали у Резонита (заказчик потребовал, чтобы были не из Китая). Если подскажите, где можно не очень дорого делать в России платы — буду рад. Китай, это, конечно, дешевле, но доставка долгая.


              1. Flaksirus
                21.11.2017 13:48

                Я делал в резоните, и в том числе срочные (около пары дней) и еще в какой-то конторе через предприятие, где работал — но я уже не помню как назывались и снова к ним бы не обратился, в полученной продукции не хватало двух полигонов, которые выполняли роль антенны, как они умудрились их отсечь вообще не понимаю.
                И все таки одно дело прототип, который дальше рабочего стола не идет, и совсем другое размещение на готовой продукции в соответствии с габаритными размерами.


    1. Alex_ME
      19.11.2017 14:46

      При всех недостатках Arduino, на ней реально быстро можно что-то склепать. Куча библиотек для разного периферического железа, датчиков. Сам я пишу на STM32 и время от времени страдаю от необходимости портировать/писать нужные библиотеки.


      Но Arduino IDE есть вершина убогости, хуже Sublime Text, ей-богу. Решение автора позволяет разрабатывать под Arduiono, сохраняя все преимущества (ну и недостатки), в нормальной IDE.


      1. Flaksirus
        19.11.2017 14:53

        Я для того обычно пишу в любом редакторе (сегодня взял бы VScode) и компилю в аруиновской идешке — вывод с ошибками есть, а что еще надо?)


        1. Alex_ME
          19.11.2017 15:15

          В VSCode есть полноценный автокомплит, отображение методов классов, аргументов функций и всего такого? (я не подкалываю, я сам не знаю)


          1. Flaksirus
            19.11.2017 15:40

            Там как в атоме, все через плагины (в том числе от атома) и конечно такие фичи реализованы уже, есть vscode-cpptools, есть плагин даже для ассемблера аврки, есть PlatformIO где и подключение к ардуинке (и не только) есть прямое.


            1. maisvendoo Автор
              19.11.2017 19:45

              Предлагаемый Вами подход называется «костыль», что-то подобное описано здесь.

              То что предлагается мной, называется использованием инструмента по его прямому назначению, и оправдано там, где в коде более чем один файл, используется VCS и прочие вещи, характерные для нормального процесса разработки


              1. Flaksirus
                19.11.2017 23:17

                Только вот у Вас сам проект костыль, нормальные решения не строят на дуинах для чайников. А касательно статьи, все элементарно и придумано до Вас — идем в поиск и находим первую же статью как развернуть нормальную разработку под AVR на QtCreator (вместо классического для этой задачи eclipse) www.lucidarme.me/?p=3325
                Собственно по желанию подключаете туда свои либы от дуинки.
                Я же наплевал бы на дроч на линукса поставил бы старую добрую IAR и писал бы под ней или на худой конец авр студио, прикупив конечно же отладчик нормальный.


            1. maisvendoo Автор
              19.11.2017 19:58

              Что же касается VSCode, то это некое «хотите VS под линукс? Нате, подавитесь». Это с учетом того, что компания эта затащила к себе в ОС половину убунту. Таково мое отношение к проекту VSCode. Личное. Предвзятое


              1. Flaksirus
                19.11.2017 23:22

                VSCode совсем — не vs под линукс, это абсолютно новый продукт как замену некогда популярному Atom, и даже построен на той же платформе (электрон) и решает другой класс задач относительно VS. Он прежде всего для некомпилируемых яп типа жаваскрипта, тайпскрипта, руби, питон и пр html. Но платформа хороша и позволяет использовать с любыми яп если есть плагин.


        1. maisvendoo Автор
          19.11.2017 19:44

          del


        1. bcmob
          20.11.2017 09:11

          Так VScode и компилит и заливает сама. Родную Arduino IDE использую теперь только если заливаю прошивку на ESP8266 через OTA(по воздуху).


  1. AVKinc
    19.11.2017 17:00

    Человек который имеет линукс на рабочей тачке, очевидно весьма неглуп как минимум.
    Так что разобраться с нормальной средой программирования для него не проблема.
    Ардуино это радиоконструктор для детей, делать что-то большое из под нее это знатный кактус.


    1. maisvendoo Автор
      19.11.2017 18:57

      делать что-то большое из под нее это знатный кактус.


      Я совершенно с Вами согласен. Объясняю ситуацию вкратце.

      В конторе, где я работаю, разрабатываются тренажерные комплексы. Есть две команды: электронщики — они занимаются вестимо железом и прошивками к нему, и руковожу ими не я; и программисты-прикладники, разрабатывающие высокоуровневый софт для пц. Прикладниками командую я. Платформа, используемая нами на тренажерах — линукс. Соответственно софт разрабатывается под эту ос преимущественно, отсюда и стек технологий: Qt с родными средствами разработки.

      Электронщики (и не я завел эту практику) ваяют железо на семействе AVR, используя ардуино для прототипирования, а для разработки софта Arduino IDE. Всё бы ничего, но в крайнем проекте, из-за дедлайнов электронщиков на других проектах, функцию написания софта для всего железа (12 плат сопряжения и пятью уникальными прошивками) пришлось взять на себя моему отделу, ибо тогда вообще швах со сроками был бы.

      Arduino IDE я не хочу и не буду использовать в своей работе. Причины, думаю понятны тем кто в теме. Поэтому 4 прошивки, которые писались нами, писались на MS VS + vMicro. MS VS нам даром не нужен, мы как-то и без него справляемся, поэтому хлебнув неудобств, я пришел к решению адаптировать проекты прошивок под используемые в наших собственных проектах инструменты, что удалось.

      Иногда приходится выкручиваться в пределах тех рамок, что задаются извне. Но испытывать боль при работе с хреновым текстовым редактором от ардуино, который почему-то назвали IDE у меня желания нет.


    1. ntfs1984
      20.11.2017 01:24

      | Ардуино это радиоконструктор для детей, делать что-то большое из под нее это знатный кактус.

      Не вижу здесь ничего детского. Arduino — это AVR (чаще всего) микронтроллер на плате, готовый к работе из коробки. Стоит вам снять микроконтроллер и перевесить его на СВОЮ плату — де-юре оно перестанет быть Ардуиной, а де-факто скетч написанный для Ардуины, заработает на вашей плате без переделок.
      У этих контроллеров нет предназначения, они могут выполнять различные задачи.
      Кстати хотел бы уточнить, для вас большое?

      Ну например полнофункциональный счетчик электроэнергии, однотарифный или двухтарифный, с просчетом статистики энергопотребления, диаграммками и прочими полезными штуками, отличающийся он Энергомеры только отсутствием красивого корпуса и бумажки-сертификата выданной взамен заноса бабла — считается большим?

      А ЭБУ на Daewoo Nexia на Ардуйне (да да, та самая которая дергает форсунки на инжекторе в зависимости от температуры, качества смеси и тд, управляет шаговым двигателем дроссельной заслонки в зависимости от положения датчика, и тд) — считается большим?

      Видел еще несколько интересных проектов в продакшене, таких как датчики освещения и контроллеры ламп, где используется банальная атмега328, та самая, с Ардуины.


    1. maisvendoo Автор
      20.11.2017 07:28

      это радиоконструктор для детей


      Если кто-то думает, что речь идет о готовых ардуинках, опутаных проводами и обвязом на макетках, то нет, Вы ошибаетесь. Готовый результат выглядит так (прячу под спойлер)

      Куски проекта, о котором идет речь



  1. ViacheslavMezentsev
    19.11.2017 22:41

    В следующей статье, возможно, поговорим о возможностях отладки проектов AVR в Qt Creator

    А можно какой-нибудь спойлер о способе отладки? Сам я использую HappyJtag2 (JtagIce mk2) для этой цели, но звучит как-то интригующе, как-будто gdb будет использоваться.

    А что до применения Arduino. К примеру, мне удалось собрать на нём ПЛК, программируемый на ST с использованием среды Beremiz. Работает, тестирую. Это ещё одна IDE для Arduino. Дёшево и сердито. Как протестирую на реальных ПЛК возможно тоже напишу статью.


    1. maisvendoo Автор
      20.11.2017 07:13

      А можно какой-нибудь спойлер о способе отладки?

      Конкретно я опробовал прогон программы на эмуляторах, и таки да avr-gdb. Что касается внутрисхемной отладки, то тут, как известно, есть трудности на той же 328p. В общем, будет о чем поговорить, обязательно напишу