Мир DIY на основе экосистемы Arduino цветет и пахнет. Количество плат, совместимых с Arduino становится все больше и больше. Какие-то платы полностью совместимы с Atmel AVR, какие-то имеют существенные отличия. Да и среди микроконтроллеров AVR могут быть значительные несоответствия, которые необходимо учитывать, если требуется написать скетч или библиотеку, которые должны без проблем работать на разных аппаратных платформах.
Различия между микроконтроллерами и платформами обычно заключается в следующих моментах (список далеко не полный):
- Конкретные пины у разных микроконтроллеров могут иметь различные функции. К примеру, на одном микроконтроллере пин за номером 1 умеет делать PWM, а на микроконтроллере другого типа нет.
- Могут различаться разрядности АЦП (используем для analogRead) и ЦАП (используем для analogWrite, т.е. для ШИМ). На одном микроконтроллере разрядность 8бит (изменение от 0 и до 254), на другом 10бит (измерение от 0 и до 1023), а на третьем 12бит.
- Могут кардинально различаться даже способы указания пинов. Так, для Atmel AVR применяется просто числовое указание пинов 1, 2, 3 и т.п. А для экосистемы ESP используется наименование пинов D1, D2, D3 и т.п.
Если вы разрабатываете библиотеку или же скетч, которые должны нормально использоваться на разных платформах или же с разными микроконтроллерами, то имеет смысл данную возможность заложить, благо ее реализация вполне может быть осуществлена через так называемые макросы препроцессора компилятора.
По сути, макрос — это текстовой кусочек исходного кода, который перед компиляцией заменяется на свое значение. Иногда подобная замена может быть условной, что позволяет существенно изменять код программы при компиляции при разных условиях. В среде Arduino используется С++, язык очень мощный, позволяющий программисту сделать все и даже немного больше. Развивается С++ с 1983 года и с тех пор разработчики навертели в языке кучу предопределенных макросов, ознакомиться с которыми будет как минимум полезно.
При компиляции скетча или библиотеки Arduino IDE передает компилятору параметр mmcu=architecture или mmcu=MCU type. В данном параметре указывается конкретный микроконтроллер, под который идет компиляция или же определяется архитектура. Например, при компиляции под Atmega328p передается параметр mmcu=atmega328p. Далее компилятор, а для компиляции под AVR используется AVR Libc от Atmel создаются собственные макросы.
Во-первых, определяются два макроса __AVR и __AVR__ которые означают, что программа скомпилирована под платформу AVR. Во-вторых, определяется макрос обозначающий архитектуру, это может быть __AVR_ARCH__=1 или __AVR_MEGA__[5] и т.п. И в-третьих, определяется макрос обозначающий конкретный тип микропроцессора, например, __AVR_ATmega328P__. Подробнее об определяемых макросах компилятора при работе с AVR можно посмотреть в документации на AVR Libc.
Если разбираться дальше и погрузиться в дебри экосистемы ESP от Espressif под Arduino, то тут все немного путанее. Найти в документации по ESP для Arduino и в документации ESP для IDF описание макросов мне не удалось. Но есть лайфхак, даже два.
Итак, чтобы понять какие макросы передаются при компиляции конкретной платы, необходимо включить полный вывод при компиляции (осуществляется в настройках Arduino IDE), откомпилировать скетч под нужную плату и посмотреть параметры D, передаваемые компилятору. Это и будут искомые макросы. Например, при компиляции для Lolin D1 R2 & Mini Arduino IDE определяет следующие макросы:
- DESP8266
- DARDUINO_ESP8266_WEMOS_D1MINI
- DARDUINO_ARCH_ESP8266
- D__ets__
Соответственно можно по этим передаваемым макросам определить какая плата и какая архитектура вообще используется. Например, для определения архитектуры ESP можно использовать макрос __ets__, а для определения конкретного чипа ESP8266. Тут следует учесть, что для компиляции под ESP используется отличный от ARV компилятор.
Второй способ определения подходящего макроса по параметру, передаваемого компилятору — использовать информацию из boards.txt. У стандартных плат Arduino boards.txt лежит в директориях папки hardware вместе с установленным Arduino IDE. Например, под Windows это примерно тут C:\Program Files (x86)\Arduino\hardware\arduino\avr. Для архитектуры ESP файл с описанием плат располагается в AppData конкретного пользователя. В моем случае это тут C:\Users\vkrav\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.5.0\. Третьим же местом, где может располагаться файл boards.txt является папка для скетчей по умолчанию.
Итак, в файле boards.txt смотрим на предикаты build у конкретной платы. А для более точного понимания, что же именно и куда передается можно подсмотреть в файлике platform.txt, который располагается там же, где и boards.txt.
Проверка на примере
В приведенном ниже примере я проверю является ли плата, под которую компилируется скетч Atmega328p и если нет, то я использую определения под архитектуру ESP.
#ifdef __AVR_ATmega328P__ #define OK_1 15 // OK output for channel 1 #define OK_2 14 // OK output for channel 2 #define Button_1 2 // Button for channel 1 #define Button_2 3 // Button for channel 2 #define LED_1 7 // LED indicator for channel 1 #define LED_2 8 // LED indicator for channel 2 #define CompiledFor "ATmega328P 8Mhz, single chip" #else // Pins setup WeMos D1 Mini, Mini Pro, Mini Lite, Ai-Thinker #define OK_1 D3 // OK output for channel 1 #define OK_2 D7 // OK output for channel 2 #define Button_1 D2 // Button for channel 1 #define Button_2 D5 // Button for channel 2 #define LED_1 BUILTIN_LED // LED indicator for channel 1 #define LED_2 D8 // LED indicator for channel 2 #define CompiledFor "ESP8266 chip" #endif
В примере я использовал структуру из #ifdef, #else и #endif. Если при компиляции определяется, что программируется под плату Atmega328p, то используются пины 15, 14, 2, 3, 7, 8, а также создается текстовая константа, которая затем выводится в последовательный порт. Если же такой чип не обнаружен, то считается, есть идет компиляция под ESP и используются соответствующие пины D3, D7, D2, D5, D13, D8.