Программируем Arduino на чистом Си

В жизни ардуинщика рано или поздно наступает момент, когда в штатной среде разработки становится тесно. Если скетчам перестает хватать памяти, требуется жесткий реалтайм и работа с прерываниями или просто хочется быть ближе к железу — значит пришло время переходить на C. Бывалые электронщики при упоминании Arduino презрительно поморщатся и отправят новичка в радиомагазин за паяльником. Возможно, это не самый плохой совет, но мы пока не будем ему следовать. Если отбросить Arduino IDE и язык wiring/processing, у нас в руках останется прекрасная отладочная плата, уже оснащенная всем необходимым для работы микроконтроллера. И, что немаловажно, в память контроллера уже зашит бутлоадер, позволяющий загружать прошивку без использования программатора.

Для программирования на языке C нам понадобится AVR GCC Toolchain.

Windows:
Устанавливаем WinAVR, который содержит все необходимое.

Debian и Ubuntu:
sudo apt-get install gcc-avr binutils-avr avr-libc


MacOs X:
Устанавливаем CrossPack for AVR Development

Также нам потребуется установленная Arduino IDE, т.к. она содержит утилиту avrdude, которая нужна для загрузки прошивки в контроллер. CrossPack тоже содержит avrdude, но версия, идущая с ним, не умеет работать с Arduino.

После того, как все установлено, создадим наш первый проект. Для начала напишем Makefile. Он позволит нам избежать ввода длинных команд вручную при каждой компиляции и загрузке прошивки.

#Контроллер, установленный на плате. Может быть другим, например atmega328 
DEVICE     = atmega168

#Тактовая частота 16 МГц 
CLOCK      = 16000000

#Команда запуска avrdude. Ее нужно скопировать из Arduino IDE.
AVRDUDE = /Applications/Arduino.app/Contents/Resources/Java/hardware/tools/avr/bin/avrdude -C/Applications/Arduino.app/Contents/Resources/Java/hardware/tools/avr/etc/avrdude.conf -carduino -P/dev/tty.usbserial-A600dAAQ -b19200 -D -p atmega168

OBJECTS    = main.o

COMPILE = avr-gcc -Wall -Os -DF_CPU=$(CLOCK) -mmcu=$(DEVICE)

all:	main.hex

.c.o:
	$(COMPILE) -c $< -o $@

.S.o:
	$(COMPILE) -x assembler-with-cpp -c $< -o $@

.c.s:
	$(COMPILE) -S $< -o $@

flash:	all
	$(AVRDUDE) -U flash:w:main.hex:i

clean:
	rm -f main.hex main.elf $(OBJECTS)

main.elf: $(OBJECTS)
	$(COMPILE) -o main.elf $(OBJECTS)

main.hex: main.elf
	rm -f main.hex
	avr-objcopy -j .text -j .data -O ihex main.elf main.hex
	avr-size --format=avr --mcu=$(DEVICE) main.elf




В этом файле нам нужно вписать свою команду для запуска avrdude. На разных системах она будет выглядеть по разному. Чтобы узнать свой вариант, запускаем Arduino IDE и в настройках ставим галочку «Show verbose output during upload».


Теперь загружаем в Arduino любой скетч и смотрим сообщения, выводимые в нижней части окна. Находим там вызов avrdude, копируем все, кроме параметра -Uflash и вставляем в Makefile после «AVRDUDE = ».


Небольшое замечание: все отступы в Makefile делаются символами табуляции (клавишей Tab). Если ваш текстовый редактор заменяет эти символы пробелами, команда make откажется собирать проект.

Теперь создадим файл main.c — собственно текст нашей программы, в которой традиционно помигаем светодиодом.

#include <avr/io.h>
#include <util/delay.h>

#define LED_PIN 5

int main() {
	DDRB |= 1 << LED_PIN;	
	while(1) {
		PORTB |= 1 << LED_PIN;
		_delay_ms(1000);
		PORTB &= ~(1 << LED_PIN);
		_delay_ms(1000);
	}
	return 0;
}



Наш проект готов. Откроем консоль в директории нашего проекта и введем команду «make»:


Как видим, размер получившейся прошивки составляет всего 180 байт. Аналогичный ардуиновский скетч занимает 1116 байт в памяти контроллера.

Теперь вернемся к консоли и введем «make flash» чтобы загрузить скомпилированный файл в контроллер:


Если загрузка прошла без ошибок, то светодиод, подключенный к 13 контакту платы, радостно замигает. Иногда avrdude не может найти плату или отваливается по таймауту — в этом случае может помочь передегивание USB кабеля. Также, во избежание конфликтов доступа к плате, не забудьте закрыть Arduino IDE перед командой «make flash».

Возможно многие вещи, описанные в этой статье, покажутся очевидными матерым разработчикам. Я постарался описать процесс максимально понятным для начинающего ардуинщика языком и собрать в одном месте информацию, которую мне удалось добыть в различных источниках, и проверенную опытным путем. Может быть кому-то эта статья сэкономит пару часов времени.

Удачи в освоении микроконтроллеров!

С Новым Годом!
Поделиться публикацией

Комментарии 26

    –1
    но разве ардуино-язык не си-подобный? зачем программировать на си, когда можно программировать на си?
      +3
      Он не с-подобный, а чистейший с++

      Если стоит задача уложиться в 180 байт, может и есть смысл перейти на си. А так — непонятно.
      Единственное, с чем соглашусь, что под ардуиновой «оболочкой» невозможно работать в принципе.
        +1
        Проблема не столько в байтах, сколько в реалтайме. При внезапной перезагрузке по питанию тратится некоторое время на бутлоадер ардуины, иногда это может быть критично.
          +1
          Вот как выглядит main.cpp (папка cores)

          #include <WProgram.h>
          
          int main(void)
          {
          	init();
          
          	setup();
              
          	for (;;)
          		loop();
                  
          	return 0;
          }
          


          Переопределите функцию init() и будет, как говорится, счастье
          0
          Если нужна экономия — достаточно перейти на C++ без «тяжеловесных» конструкций вроде исключений. Если ограничиться расширенным синтаксисом, классами, виртуальными/шаблонными функциями и т.п., gcc даст код того же объема, что и аналогичный на чистом C.
            0
            gcc даст код того же объема, что и аналогичный на чистом C

            Да не того же, проверяли. Издержки есть. Ну или надо быть гуру C++ и gcc
            Для большинства задач обычный C подходит лучше всего. И даже там компилятор умудряется налажать в некоторых конструкциях, вставляя бессмысленный код, но как правило небольшой рефакторинг помогает ему связать всё более эффективно.
              +1
              Я тоже проверял — даст. :) Гуру быть не нужно, достаточно общего понимания кодогенерации и оптимизации в C++.

              Для простейших задач, разумеется, C++ особого смысла не имеет, хотя удобно использовать файлы .cpp просто для написания в расширенном плюсовом синтаксисе, без довольно глупых ограничений традиционного C.
              0
              Ардуино-проекты и так использует -fno-exceptions при сборке. Всё-таки на avr (особенно на attiny) места не слишком много
                0
                Угу, я как раз для ATTiny13 и сравнивал плюсовый код с неплюсовым.
          +2
          Почему бы не использовать полноценные IDE? Например Eclipse с плагином AVR — так же работает с avrdude, полностью создаёт проект, где не надо возиться с makefile и т.д. Открыл, пощёлкал диалог, и вперёд писать код.
            +2
            Как раз таки лучше makefile, все под контролем.
            И тогда по большому счету без разницы, какая оболочка. А в крайнем случае можно собрать проект из голой консоли
              +2
              Может потому что эклипс — эпический тормоз, и кому-то нравится писать по-олдскульному практически в блокноте. К примеру я с удовольствием последую этому мануалу чтобы не нагружать бедный слабенький нетбук atmel studio или тем же самым эклипсом.
              Вполне вариант имеющий право на жизнь.
                0
                Вариант, не спорю, да и сам эклипс не люблю. Просто предложил его как способ быстро получить результат.
                  +1
                  А ещё можно использовать Sublime Text с плагином Arduino-like IDE и получить удобную среду с автодополнением и прочими радостями. Пока этого плагина не было сам использовал makefile для сборки программ под чистый AVR и Arduino из-под саблайма. Кстати, в менюшке этого плагина есть пункт «Bare GCC Build» для сборки программы без использования duino-библиотек.
                    0
                    Подтверждаю, плагин удобный! Можно не только красиво выравнивать программы, но и более удобно работать с выводом.
                    0
                    AVR Studio версии 4, то есть до разжирения, хорошо работала на Pentium II со 128 мегабайтами оперативки. Она по прежнему доступна для скачивания на официальном сайте вместе с AVR-GCC.

                    Или Code::blocks Он и Makefile генерировать умеет.
                      0
                      Ну это Вы про эклипсу зря.
                      по-олдскульному практически в блокноте
                      попробуйте IAR. Вам тогда Eclipse манной небесной покажется.
                      Хотя у каждой IDE есть свои плюсы.
                    • НЛО прилетело и опубликовало эту надпись здесь
                        0
                        clion пока поддерживает только cmake.

                        В принципе, ничего не мешает сделать сборку под avr на cmake, по аналогии с stm32, например.
                      0
                      Занимался чем-то подобным с TI LaunchPad (отладочная плата с mcu семейства msp430). Многие называют эту вещь альтернативой Arduino. Наверное по причине наличия среды Energia c кучей скетчей, как в Arduino. Если же хочется программировать его на «чистом C», то есть проприетарная среда от TI: Code Composer Studio и открытый проект “GCC toolchain for MSP430”, который представляет собой набор патчей для соответствующих утилит (gcc, gdb), добавляющих возможность разработки под архитектуру msp430.
                      Для программирования памяти микроконтроллера и отладки есть утилита “mspdebug”, предоставляющая возможность взаимодействия с различными программаторами. В mspdebug есть возможность запустить gdb-сервер и подключиться к нему из пропатченного gdb — можно дебажить «чистый С'шный код на железке».
                        0
                        Забавно, что Makefile у вас для мака, судя по путям. В XCode писать под ардуинки не пробовали? Довольно удобно.
                        P.S. ну и когда avrdude «не может найти плату», у меня обычно достаточно просто запустить еще раз, кабель передергивать не требуется.
                          0
                          Я недавно узнал о надстройке, теперь пользуюсь — великолепная среда разработки, очень рекомендую!
                            0
                            В XCode пока не пробовал, спасибо за ссылку. Вместо передергивания кабеля нашел другой способ: нажать на reset ардуинки, потом запустить make flash, потом отпустить reset и заливка пойдет.
                            +1
                            Код файла main.c стоило бы прокомментировать. И чем подробнее, тем лучше. Пускай даже там ерунда какая-нибудь, а всё равно — вы хотите чтобы человек с ардуины, который писал «пин 5 гори!» сразу с ходу понял этот конструктивизм PORTB &= ~(1 << LED_PIN);
                            Конечно тут можно послать изучать C и тому подобное, но всё же — почему именно так, а не по другому? Всё же статья носит обучающий характер. Спасибо.
                              +3
                              Это такие почти стандартные конструкции по управлению и опросу портов :)

                              В конечном итоге это превращается в что-то типа
                              void Led1On(void){PORT_LED &= ~(1<<LED1);}
                              Led1On:
                              00015C 9893 CBI 0x12,3
                              void Led1On(void){PORT_LED &= ~(1<<LED1);}
                              00015E 9508 RET
                              void Led1Off(void){PORT_LED |= (1<<LED1);}
                              Led1Off:
                              000160 9A93 SBI 0x12,3
                              void Led1Off(void){PORT_LED |= (1<<LED1);}
                              000162 9508 RET


                              А если расписать это по действиям, то получается:

                              PORT_LED &= ~(1<<LED1); — установить состояние порта — 0
                              взять число 1, сдвинуть его на номер бита (в хедерах так же описаны специальные биты специальных регистров), проинвертировать его побитово (получив «0» в нужном нам месте) а затем произвести побитовое «И» с содержимым регистра нужного нам порта.

                              PORT_LED |= (1<<LED1); — установить состояние порта — 1
                              взять число 1, сдвинуть его на номер бита, а затем произвести побитовое «ИЛИ» с содержимым регистра нужного нам порта.

                              Похожей «замудренности» конструкции применяются и для проверки состояния портов

                              if (!(PIN_KEY & (1<<K2))) // проверка состояния пина, активный «0»
                              000D84 99CD SBIC 0x19,5
                              000D86 C006 RJMP 0xD94
                              0
                              Посмотрите на platformio.org. Нет зависимостей по Arduino IDE, Makefiles или tool chains… Поведение одно и тоже в независимости от ОС (работает на всех популярных ОС). Есть Library Manager. Легко интегрируется в любимые IDE.

                              Для Вашего примера с «atmega168» platformio.ini будет смотреться так:
                              [env:test_atmega168]
                              platform = atmelavr
                              board_mcu = atmega168
                              board_f_cpu = 16000000L
                              
                              upload_protocol = arduino
                              upload_speed = 19200
                              
                              targets = upload
                              


                              Пример проекта ATmel AVR Native Blink.

                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                              Самое читаемое