Основы программирования Arduino

Во всех программах Arduino присутствуют определенные элементы. Почти все они написаны на языке программирования Си. C — это простой, но надежный язык, который существует с начала 1970-х годов. У него относительно мало ключевых слов (слов, имеющих особое значение в языке). C не имеет встроенных команд ввода-вывода, как многие другие языки.

Гениальность языка C заключалась в том, что его изобретатели сделали ввод-вывод внешним по отношению к самому языку и вместо этого поместили функциональность ввода-вывода в то, что называется стандартной библиотекой C. Самое замечательное в C то, что если вам не нравится, как работает определенная часть функциональности ввода-вывода, вы можете написать свою собственную.

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

На самом деле Arduino IDE использует компилятор C++ из группы Open Source, который полностью поддерживает C++ и все, что предлагает его парадигма объектно-ориентированного программирования (ООП).

Тем не менее, по большей части программы Arduino, или скетчи, как их называют, обычно пишутся на простом старом C. Если вам нужна помощь в понимании программы, то первое место, куда стоит обратиться, — это книга по C, а не по C++.


Давайте рассмотрим простую программу из примеров Arduino IDE под названием «DigitalReadSerial», которая считывает состояние цифрового контакта 2 и выводит его в последовательный порт. Чтобы открыть этот пример, перейдите в Arduino IDE ФайлПримерыBasicsDigitalReadSerial.

Комментарии в языке C

Первое, что вы видите в скетче, это пара символов косая черта и звездочка /*.

/*
  DigitalReadSerial

  Reads a digital input on pin 2, prints the result to the Serial Monitor

  This example code is in the public domain.

  https://www.arduino.cc/en/Tutorial/BuiltInExamples/DigitalReadSerial
*/

Несколькими строками ниже вы найдете пару символов звездочка и слеш */.

Эти две пары символов обозначают начало /* и конец */ многострочного комментария к программе. То есть комментарий может занимать столько строк, сколько необходимо, если эти строки находятся между этими двумя парами символов.

Комментарии к программе — это заметки, написанные программистом для всех, кто может читать исходный код программы, с целью помочь читателю понять, что делает код. Все, что находится между двумя парами символов комментария, игнорируется компилятором.

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


// digital pin 2 has a pushbutton attached to it. Give it a name:

Эта строка является примером однострочных комментариев. Однострочные комментарии начинаются с двух символов косой черты // в начале комментария и заканчиваются в конце той же строки. Никаких специальных закрывающих символов для этого комментария не требуется. Этот однострочный комментарий с тем же успехом мог быть написан с использованием символов многострочного комментария.

Когда комментировать код?

Рассмотрим две строки кода, которые иллюстрируют две крайности комментирования. Первый выглядит так:

i = i + 1; // Возьмите исходное значение i и добавьте к нему 1

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

Вот вторая строка кода:

sol = math.sin(w) + 2.0 * ladder – myFunction(nodding == FULL?10:5);

и никаких комментариев не видно…. Здесь неплохо было бы прокомментировать, какие действия выполняет данное выражение, что сэкономило бы время.

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

Во-вторых, сложный код, подобный второй приведенной строке, вероятнее всего, никогда не следует писать таким образом. Поскольку существует довольно высокая вероятность того, что оператор в конечном итоге нуждается в отладке. Лучше записать его так:

double temp1 = math.sin(w);                 // w - угол наклона окна
int temp2 = (nodding == FULL) ? 10 : 5;     // Если окно полностью открыто, установить значение 10, 
                                            // в противном случае 5

double temp3 = myFunction(temp2);           // Убедится, что temp3 находится в пределах допустимого диапазона
 
sol = temp1 + (2.0 * ladder) – temp3;       // Конечное вычисление

Написание кода в виде серии небольших шагов упрощает тестирование и отладку сложных выражений. Если разбиение инструкции на более мелкие части серьезно снижает производительность, возможно, из-за того, что выражение находится в замкнутом цикле. Держите инструкции отдельно друг от друга до тех пор, пока код не будет полностью отлажен, а затем объедините его в сложную форму… и протестируйте еще раз.

Обычно многострочные комментарии размещаются в начале функции, чтобы объяснить следующие моменты:

  • какова цель функции;
  • какие данные (если они есть) должны быть переданы при вызове;
  • какое значение (если есть) возвращает функция.

Итак… когда комментировать, а когда не комментировать? Это личное решение каждого, но мы думаем, что если вам потребуется более 10-15 секунд, чтобы понять, что делает оператор, он, вероятно, выиграет от комментария. В противном случае, комментарий, скорее всего, пустая трата времени.

Определения данных

Возвращаясь обратно к программе DigitalReadSerial, следующая строка в исходном коде программы является:

int pushButton = 2;

Эта строка является оператором C, который определяет целочисленный тип данных (int) и присваивает указанное ему имя переменной. Он также инициализирует значение переменной равным 2.

Этот оператор выделяет два байта памяти и назначает ей имя pushButton. Сделав это, компилятор присваивает этим двум байтам памяти целочисленное значение 2. Часть работы компилятора состоит в том, чтобы отслеживать, где каждая переменная хранится в памяти, чтобы при необходимости можно было получить ее значение.

Некоторые программисты используют слова «определить» и «объявить» для обозначения одного и того же. Но эти два действия не имеют одинакового значения.

Определение данных создает список атрибутов (например, описание переменной, такое как: «целочисленная переменная с именем pushButton»), который описывает элемент данных, а также выделяет память для этой переменной.

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

Где функция main()?

Многие люди используют микроконтроллеры Arduino, потому что знают, что в основе Arduino IDE лежит язык программирования C. Однако люди, знакомые с C, C++ или Java, немного смущены тем, что в программе Arduino нет функции main().

Если вы когда-либо использовали C, C++ или Java, вы знаете, что все программы должны иметь функцию main(), так как это отправная точка для выполнения программы. Что случилось с main() в скетчах Arduino?

Функция main() на самом деле все еще существует, но она скрыта. Чтобы ее найти, перейдете по пути, похожий на этот (путь может быть немного другим, в зависимости от версии программы):

C:\ Program Files (x86) Arduino hardware avr cores arduino main.cpp

В этом подкаталоге Arduino вы найдете несколько заголовочных файлов. Файлы, оканчивающиеся на .h и файлы исходного кода (оканчивающиеся на .cpp, для файлов C++, или .c для обычного исходного кода C).

Файлы заголовков содержат много информации, которую компилятор использует для компиляции программы, а файлы исходного кода используются вместе с вашими собственными файлами — «скетчем» Arduino.

Для информация


Как упоминалось ранее, файлы исходного кода программы, которые вы пишете в Arduino IDE, называются скетчами и имеют вторичное расширение файла *.ino.

Тот факт, что каталог Arduino заполнен файлами *.c и *.cpp, говорит нам о том, что мы можем свободно смешивать файлы C и C++ в наших программах.

В каталоге arduino вы сможете увидеть файл с именем 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;
}

Исходный код в main.cpp довольно прост. Здесь находится определение функции main(). Функция main() начинается с вызова другой функции — init(), которая используется для установки некоторых настроек компилятора.

Заметка


Функции в C — это небольшие фрагменты кода, предназначенные для выполнения одной конкретной задачи.

Далее идет директива препроцессора #ifdef, которая работает с USB-устройством, если оно имеется (не все платы Arduino работают с USB).

Затем один раз вызывается функция с именем setup(), за которой следует цикл for, многократно вызывающий функцию с именем loop(). При определенных обстоятельствах код может также вызывать функцию с именем serialEventRun().


Обычно цикл программирования for содержит три выражения:

for (выражение1; выражение2; выражение3) {
          // оператор(ы), выполняемые в цикле for
}

Например:

for (i = 0; i < MAXVAL; i++) {
   // выражение
}  // конец цикла for

Для информация


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

В приведенном выше цикле for первое выражение инициализирует переменную с именем i значением 0.

Второе выражение проверяет, меньше ли значение i (символ <) чем значение MAXVAL. Если i меньше MAXVAL, то выполняются операторы между открывающей и закрывающей фигурными скобками { и }. После выполения этих операторов i увеличивается (выражение3 или i++), а затем снова вычисляется выражение2 (i < MAXVAL). Этот цикл for продолжает выполняться до тех пор, пока i не будет увеличен до значения, равного или превышающего MAXVAL, после чего цикл for завершается.


Возвращаясь к коду в файле main.cpp. В цикле for отсутствуют все три выражения. Это значит, что нет выражения, которое можно было бы проверить для завершения цикла for. Это создает бесконечный цикл: цикл, который никогда не заканчивается.

Другими словами, функция loop() вызывается всегда, или до тех пор, пока не будет отключено питание микроконтроллера, или система не выйдет из строя каким-либо образом. А также вызов функции serialEventRun() может завершить программу.

Таким образом, в самой простой форме все программы Arduino имеют функцию main(), хотя она скрыта от глаз.

Цели функции main():

  • установить базовую среду для компиляции программы (т. е. вызов функции init());
  • выполнить любые операции, которые вы хотите, чтобы ваша программа сделала один раз с помощью одного вызова в setup();
  • продолжать вызывать цикл loop().

Поэтому нам может быть полезно рассмотреть функции setup() и loop() немного подробнее.

Функция setup()

Возвращаясь к нашей программе DigitalReadSerial, после определения переменной с именем pushButton вы найдете следующие строки:

// the setup routine runs once when you press reset:
void setup() {
  // initialize serial communication at 9600 bits per second:
  Serial.begin(9600);
  // make the pushbutton's pin an input:
  pinMode(pushButton, INPUT);
}

Фактическая задача, которую выполняет эта функция, определяется операторами, содержащимися в теле функции. Тело функции начинается с открывающей фигурной скобки после ее имени { и заканчивается закрывающей фигурной скобкой внизу функции }.

В нашем примере тело функции setup() содержит четыре строки:

  1. Комментарий;
  2. Инициализация последовательной связи со скоростью 9600 бит в секунду;
  3. Комментарий;
  4. Вызов функции с именем pinMode();

Из названия функции видно, что pinMode() предназначен для использования переменной pushButton, чтобы каким-то образом повлиять на режим вывода и установить для него значение INPUT.

Если вы посмотрите описание функции для pinMode(), вы обнаружите, что она используется для установки определенного вывода Arduino (в данном случае вывода 2) в определенный режим. В этом примере как вход — INPUT.

А что же означает слово void перед setup()?

В языке C функции имеют возможность отправлять значение обратно в любой раздел кода, вызывающий функцию. Например, функция digitalRead() считывает значение с контакта и возвращает целочисленный тип данных в виде логической единицы — 1 или логического нуля — 0.

int buttonState = digitalRead(pushButton);

В программировании целые значения не могут иметь дробной части. Целые числа всегда являются целыми числами. В этом примере нам только нужно знать одно из двух значений: 1 или 0.

Однако не все функции должны возвращать значение. Если функция не возвращает никакого значения, перед именем функции появляется слово void, как и в случае с setup(). Слово, которое появляется перед именем функции, называется спецификатором типа функции и указывает тип данных, возвращаемых функцией. Поскольку мы видим void перед setup(), мы знаем, что функция ничего не возвращает.

Функция loop()

Оставшаяся часть программы DigitalReadSerial довольно короткая: считать состояние цифрового контакта функцией digitalRead и записать ее в переменную buttonState, вывести переменную в последовательный порт и применить задуржку в 1 миллисекунду.

// the loop routine runs over and over again forever:
void loop() {
  // read the input pin:
  int buttonState = digitalRead(pushButton);
  // print out the state of the button:
  Serial.println(buttonState);
  delay(1);        // delay in between reads for stability
}

Перед функцией loop() (также вызываемой из скрытой функции main()) стоит слово void. Как упоминалось ранее, спецификатор типа функции void означает, что при вызове функции loop() ничего не возвращается.

Каждая программа, написанная с использованием Arduino IDE, должна иметь функцию loop(). Тело оператора функции loop() выполняется «всегда». То есть после однократного выполнения функции setup() функция loop() вызывается бесконечно по мере выполнения программы.

Обычно функция loop() продолжает выполняться до тех пор, пока:

  • Не будет отключено питание платы, на которой запущена программа;
  • Вы не загрузите новую программу в микроконтроллер для замены текущей программы;
  • Плата не выйдет из строя из-за какой-либо неисправности.

Давайте теперь посмотрим на пример под названием «Blink». Его можно открыть следующим образом: ФайлПримерыBasicsBlink.

// the setup function runs once when you press reset or power the board
void setup() {
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);                       // wait for a second
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);                       // wait for a second
}

В функции loop() первый оператор:

digitalWrite(LED_BUILTIN, HIGH);

Слова HIGH и LOW определены для среды Arduino, и их проще воспринимать как о включении светодиодного контакта (HIGH) или выключении (LOW). В этой программе функции digitalWrite() предоставляются входные значения LED_BUILTIN и HIGH).

После того, как эти входные данные переданы, функция может обработать эти входные данные и приступить к включению светодиода. «Удержать» его включенным достаточно долго (1000 миллисекунд = 1 секунда), чтобы мы могли это увидеть, помогает функция задержки delay(1000).

Поскольку в функцию delay() передается значение в миллисекундах, вызов delay(1000) поддерживает свечение светодиода в течение 1 секунды. Если не вызвать функцию задержки, то мы не сможем наблюдать изменение состояния светодиода с LOW на HIGH.

По прошествии 1 секунды снова вызывается функция digitalWrite(), но уже в качестве второго параметра передается LOW:

digitalWrite(LED_BUILTIN, LOW);

Программа передает новый набор входных данных функции digitalWrite(), которая выключает светодиод. Опять же, вызов delay(1000) позволяет нам увидеть, что светодиод теперь выключен и остается таковым в течение 1 секунды.

После выполнения последнего оператора в функции loop() (например, второго вызова delay(1000)) программа переходит обратно к первому оператору — digitalWrite(LED_BUILTIN, HIGH) и весь процесс повторяется. Поэтому функция называется loop(), что в переводе с английского «Петля».

Операторы внутри функции loop() повторяются бесконечно или до тех пор, пока не отключится питание или не произойдет сбой.

Давайте внесем несколько незначительных изменений в программу Blink и посмотрим, как эти изменения повлияют на поведение программы.

// the setup function runs once when you press reset or power the board
void setup() {
  Serial.begin(9600);
  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_BUILTIN, OUTPUT);
}

// the loop function runs over and over again forever
void loop() {
  Serial.println("LED ON");
  digitalWrite(LED_BUILTIN, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);                       // wait for a second
  Serial.println("LED OFF");
  digitalWrite(LED_BUILTIN, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);                       // wait for a second
}

Программа точно такая же, как и раньше, за исключением трех выделенных строк.

Первая строка появляется в функции setup(), поэтому она является частью инициализации. Библиотека Serial одна из многих доступных вам библиотек Arduino, которую вы можете использовать в своих программах для отладки и не только.

Для информации


На самом деле библиотека Serial — это класс C++, к которому у вас есть доступ в ваших программах. Одна из функций (также называемых методами на жаргоне ООП), которая является частью класса Serial, — это begin(). Назначение функции begin() — установить скорость передачи данных для связи между вашей программой Arduino и вашим ПК.

На картинке ниже показано, как открыть последовательный монитор в среде Arduino, используя последовательность меню Инструменты (Tools) → Монитор порта (Serial Monitor).

Как открыть монитор порта в Arduino IDE

Когда вы выберете Монитор порта, вы должны увидеть вот такое окно.

Arduino IDE монитор порта

Serial Monitor позволяет вам как отправлять, так и получать данные. Верхнее текстовое поле используется, когда вы хотите ввести данные для отправки в скетч Arduino, работающий в данный момент. Вы вводите данные и нажимаете кнопку Отправить. Затем программа использует канал последовательной передачи данных (ваш USB-кабель) для передачи данных из текстового поля последовательного монитора ПК в программу Arduino.

В правом нижнем углу вы можете увидеть выпадающий список со скоростями (выбрано 9600 бод). Это скорость передачи данных по умолчанию для последовательного монитора. Поэтому программа монитора ожидает, что данные из программы Arduino будут поступать по последовательному каналу со скоростью 9600 бод. С такой же скоростью мы настроили arduino в функции setup():

Serial.begin(9600);

Что будет, если скорости передачи будут отличаться? Вы увидите кучу тарабарщины (иероглифы, прямоугольники, различные символы) на последовательном мониторе. Это почти всегда признак того, что скорости передачи для монитора порта и вашей программы не совпадают.

Самое быстрое решение — это изменить значение в раскрывающемся списке монитора порта и перезапустить программу на Arduino (платы имеют кнопку сброса, которую вы можете нажать, чтобы перезапустить программу).

Второй способ, это изменить скорость в коде в функции Serial.begin() и загрузить новую версию программы в плату.


В раскрывающемся списке «NL & CR» есть несколько вариантов вывода строки данных. Поскольку мы использовали метод с именем Serial.println() (обратите внимание на «ln» в конце слова «print»), этот метод предлагает вывести информацию, а затем отправить символ новой строки (чтобы следующий вывод данных появился с новой строки). А вот если бы вы использовали Serial. print(), то весь вывод будет отображаться в одной, очень, очень длинной строке.

После изменения скорости передачи данных и правильной настройки COM-порта вывод новой программы должен выглядеть примерно так.

Arduino IDE в монитор порта выводится статус светодиода

Когда установлен флажок Автопрокрутка, в окне будут отображаться «свежие» данные. Если вы отключите функцию автопрокрутки на последовательном мониторе, он заполнится одной страницей вывода, а затем в правой части монитора появятся вертикальные полосы прокрутки, поскольку программа продолжала выводить все больше и больше строк.

Как экономить память

Одним из факторов, который необходимо учитывать при покупке микроконтроллера — это объем доступной Flash-памяти. Именно во флэш-памяти находится ваша программа, и она является энергонезависимой. То есть содержимое Flash-памяти сохраняется даже при отключении питания.

Память SRAM — это энергозависимая память, которая используется для временного хранения во время работы программы. Например, если вы вызываете функцию, используя переменную типа int в качестве параметра для вызова функции, то каждый вызов функции помещает определенный объем данных в SRAM память. Когда функция завершается, SRAM освобождается.

Однако, если вы вызываете функцию func3() из функции func2() из функции func1(), все эти «расходы» памяти для каждого вызова функции начинают складываться.

Для информации


Функции, которые вызывают сами себя (т. е. рекурсивный вызов функции), могут нанести ущерб пространству памяти SRAM.

Таким образом, SRAM — это дефицитный товар, который вы хотите сохранить. Если ваша программа загадочным образом «умирает» во время выполнения, проверьте, сколько у вас есть SRAM. (Вот эта короткая программа, которую вы можете использовать для контроля оставшегося места в памяти SRAM.)

Наконец, память EEPROM — это энергонезависимая память, которая сохраняет любые данные, которые вы туда помещаете, даже при отключении питания от платы. Одна проблема с памятью EEPROM, заключается в том, что в нее можно записать конечное количество раз, прежде чем она станет нестабильной. EEPROM обычно довольно стабилен в течение первых 100 000 операций записи.

Что делать, если у вас закончилась память?

Во-первых, вы должны попытаться определить, какой тип памяти вызывает у вас проблемы. Когда вы компилируете программу, Arduino IDE предоставляет вам объем используемой флэш-памяти и еще сколько доступно для использования.

Если ваша программа приближается к пределу, то придется искать изменения, которые уменьшат размер вашей программы. Если у вас много доступной флэш-памяти, но по-прежнему возникают неожиданные сбои программы или другие проблемы, возможно, проблема в памяти SRAM. Довольно часто нехватка SRAM вызывает сбои программы, в то время как остается много флэш-памяти.

Далее следует несколько идей о том, как сохранить немного памяти, если возникнет такая необходимость.

Удаление неиспользуемых переменных

Это кажется очевидным, но можно легко не заметить переменную, которая остается неиспользуемой в программе. Хотя компилятор очень хорошо удаляет неиспользуемые переменные, он может не перехватывать их все. Лучше будет, если вы будете удалять сами неиспользуемые переменные из вашего кода.

Использование типов данных

Если вы используете тип данных int для хранения логики true или false — вы тратите память впустую. Тип данных int использует два байта памяти, в то время как тип данных byte использует только один байт. Например:

int bodyTemp[100];

Маловероятно, что чья-то температура тела будет колебаться в диапазоне ±32 000 градусов.

byte bodyTemp[100];

Использование типа данных byte экономит вам 100 байт памяти.

Если ваши данные температуры необходимо записать с десятичной дробью (36.6 например), будет расточительно определять массив с типом данных float (с точки зрения экономии памяти):

float bodyTemp[100];

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

Массив занимает 400 байт памяти. Вместо этого можно хранить каждую температуру в виде трехзначного типа данных int и это значение делить на 10 при использовании. Поэтому 36.6 сохраняется в int как 366, но используется как 366 деленное на 10.

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

Использования класса String

Проведем эксперимент: запустим Arduino IDE, откроем пример Blink и добавим следующую строку в начало функции loop():

String message = "Здесь какое-то сообщение";

А теперь скомпилируем программу. Размер программы Blink с добавленной переменной типа String составило 2144 байта.

Сколько памяти использует переменная типа String

Теперь изменим эту строку на вот такую:

char message[] = "Здесь какое-то сообщение";

и перекомпилируем программу. Теперь программа занимает 924 байта флэш-памяти. Почему такая большая разница? Причина в том, что каждый раз, когда вы используете ключевое слово String в своей программе, вы заставляете компилятор использовать класс C++ String. Хотя класс String имеет несколько отличных функций, они могут вам и не понадобиться, а в результате раздувание программы может быть ненужным.

Сколько памяти использует переменная типа char

Макрос F()

Предположим, у вас есть следующий оператор в коде вашей программы:

Serial.println("Start program");

Сообщение, заключенное в оператор в двойных кавычках, называется строковым литералом. Строковый литерал — это последовательность текстовых символов, которая не изменяется при выполнении программы. Эти строковые литералы встроены в пространство памяти вашей программы и, следовательно, используют флэш-память. Проблема в том, что компилятор видит эти строковые литералы и копирует их в SRAM непосредственно перед запуском программы. Другими словами, один и тот же строковый литерал дублируется во FLASH-памяти и SRAM-памяти. С сообщением выше вы только что потратили впустую 13 байт драгоценной SRAM.

Однако, мы можем это исправить следующим образом:

Serial.println(F("Start program"));

Обратите внимание, что строковый литерал содержится в скобках макроса F().

Не вдаваясь в механику того, как это работает, конечным результатом является то, что макрос F() не позволяет компилятору копировать строку SRAM.

Если вы считаете, что у вас мало SRAM, поищите в своем коде строковые литералы и примините макрос F().

Функция freeRam()

Функция freeRam() доступна на веб-сайте Arduino, но она настолько короткая, что вы можете просто скопировать ее отсюда:

int freeRam()
{
   extern int __heap_start, *__brkval;
   int v;
   return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
}

Поскольку объем доступной SRAM колеблется и уменьшается, а программа выполняется, вы можете внедрить функцию freeRam() в любые части кода, которые хотите отслеживать. Типичное использование может быть:

Serial.print(F("SRAM available = "));
Serial.println(freeRam());

Хотя это и не идеальный инструмент, по крайней мере, функция freeRam() может дать вам некоторое представление о том, что происходит с вашей SRAM в определенное время выполнения вашей программы.

5 2 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest
0 комментариев
Межтекстовые Отзывы
Посмотреть все комментарии