Как работают функции вне цикла void?

Я привык к скетчам Arduino с частью void setup(), которая запускается один раз, и частью voidloop(), которая продолжает выполняться в цикле. Что произойдет, если у вас есть функции void вне основного цикла void()? Будут ли они продолжать работать параллельно или выполняться один за другим? Или некоторые функции void запускаются только при выполнении определенных критериев (например, цикл while)?

Например, в приведенном ниже коде, когда будут выполняться функции void takeData(int byteCount) и void sendData()?

//I2C_test

//Этот код демонстрирует связь через шину I2C между Raspberry Pi и Arduino.
//Когда Raspberry pi (главный) отправляет данные на Arduino (ведомый), Arduino использует это
//данные для управления двигателем. После того, как Arduino получил данные от мастера, он собирает
//данные из внешней среды через датчик и отправляет эти данные обратно в Raspberry Pi.

#include <Wire.h>
int number = 0; //Объявляем переменные
int val = 0;

void setup() {
  //Все, что находится между фигурными скобками, запускается один раз при включении или перезагрузке Arduino
  pinMode(0, INPUT);
  //Устанавливаем контакт 0 как вход и 3 как выход
  pinMode(3, OUTPUT);
  Serial.begin(9600);
  //Установим скорость передачи данных для последовательной передачи 9600 бит/с
  Wire.begin(0x04);
  //Инициализируем библиотеку Wire, подключаемся к Arduino в качестве ведомого устройства и указываем его 7-битный адрес ведомого устройства.
  Wire.onReceive(receiveData);
  //Определяем обратные вызовы для связи i2c
  Wire.onRequest(sendData);
}

void loop() {
  //Код между фигурными скобками продолжает повторяться
  delay(100);
}

void receiveData(int byteCount) {
  while(Wire.available()) {
    number = Wire.read();
    //Устанавливаем переменную "номер" в данные, отправленные мастером
    analogWrite(3, number);
    //Запишите этот номер на контакт 3 (ШИМ). Это контролирует скорость двигателя
  }
  val = analogRead(0);
  //Читаем напряжение на контакте 0 (подключенном к датчику). Преобразуйте входные напряжения от 0 до 5 В в целочисленные значения от 0 до 1023.
}

void sendData() {
  Wire.write(val);
  //Отправляем данные, считанные с датчика, на мастер.
}

, 👍9

Обсуждение

Это выглядит интересно. Интересно, не могли бы вы опубликовать ссылки на источник кода (и подробности соединений между Arduino и Pi)., @Milliways

@Milliways Я использовал [это*](http://blog.oscarliang.net/raspberry-pi-arduino-connected-i2c/) руководство для написания кода на Arduino Uno и Raspberry Pi (модель B+), однако я сделал несколько небольших изменений. Соедините контакты SDA и SCL двух плат, а также контакты заземления, если они подключены к разным источникам питания. Затем я подключил контакт 3 к датчику, настроенному в конфигурации делителя потенциала, подключенному между контактами +5 В и Gnd. Pin 0 и Gnd подключены к плате привода двигателя., @Blue7


4 ответа


Лучший ответ:

11

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

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

Например, pinMode() — это такая же функция, как и любая другая. Он запускается только тогда, когда вы действительно добавляете в свой код что-то вроде pinMode(3, INPUT). В этот момент она запускается один раз, завершается, а затем вызывающая функция продолжает работу с того места, где остановилась (они никогда не выполняются параллельно).

Приведенный вами пример кода весьма интересен. Посмотрите на эти строки в setup():

Wire.onReceive(receiveData);
Wire.onRequest(sendData);

Эти строки сообщают объекту Wire вызывать receiveData() и sendData() в ответ на события I2C. Это делается путем передачи указателей на функции, которые хранятся и используются Wire.

Я бы рекомендовал поискать информацию об указателях функций C/C++ в Интернете, если вы хотите узнать об этом больше. Возможно, вам также будет интересно изучить функцию attachInterrupt() Arduino.

,

Спасибо за Ваш ответ. Теперь это начинает иметь больше смысла. Однако, если функции receiveData() и sendData() не запускаются, пока они не вызваны, то почему они вызываются внутри функции void setup(), а не основной функции voidloop()? Конечно, эти функции никогда не будут вызываться, если только не будет редкого случая, когда произойдет событие i2c, пока указатель инструкции все еще находится внутри функции void setup? Не лучше ли было бы вызывать эти функции из функции voidloop, чтобы при возникновении события i2c вызывалась функция?, @Blue7

@Blue7 Эти функции не **вызываются** в void setup(), они передаются как параметр onReceive и onRequest, это **[обратные вызовы](http://en.wikipedia .org/wiki/Callback_%28computer_programming%29)**, как указано в комментарии. Вкратце: это сообщает (коду из) библиотеки Wire вызывать эти функции, когда происходят определенные события (http://arduino.cc/en/Reference/WireOnReceive, http://arduino.cc/en/Reference/ WireOnRequest ...), @FredP

@FredP Ах, ладно. Спасибо за ссылки, я проверю их, когда не буду с телефона. А пока у меня есть небольшой вопрос, если вы не возражаете. Всегда ли эти обратные вызовы готовы и ждут события i2c? т.е. независимо от того, где находится указатель инструкции, эти обратные вызовы мгновенно вызовут функцию, как только произойдет событие i2c?, @Blue7

@Blue7 Предположительно, он будет использовать прерывания для мониторинга активности I2C. Когда прерывание выполняется, оно временно отбирает управление у основной программы., @Peter Bloomfield

@Blue7 Обратные вызовы не ждут (Arduino не является многопоточным), как говорит @PeterRBloomfield, библиотека Wire разрешает прерывание I2C через twi_init(), когда вы вызываете Wire.begin. При активности I2C микроконтроллер перестает выполнять свою текущую задачу (если только... пока не важно :-) и переходит к коду библиотеки Wire, которая затем вызывает (подходящую, в зависимости от того, что происходит) функцию, которую вы зарегистрировали как обратный вызов (например, «receiveData»). *Обратный вызов* — это общее имя для таких функций, как receiveData или sendData, они вызываются *обработчиком прерываний* внутри Wire., @FredP


1

Она работает как обычная функция, ее нужно вызывать, чтобы иметь смысл. Loop()/setup() вызываются из функции main(), которая скомпилирована из каталога Arduino и связана с ней.

,

3

Разве это не тот случай, когда setup() вызывается один раз, а loop() вызывается повторно? то есть существует невидимый main(), который может выглядеть так:

void main(){
  setup();
  while(True){
    loop();
  }
}

Извиняюсь, я только изучаю Arduino и почти не имею опыта работы с C/C++; Я сам пытаюсь разобраться в этой ситуации с loop().

,

В принципе, да. Существует также вызов init(), который запускает таймеры для millis, delay и т. д. Таким образом, init() предназначен для общей инициализации, setup() предназначен для **вашей** инициализации. , а loop означает, ну, зацикливание. Вы можете написать свой собственный main, если хотите получить полный контроль., @Nick Gammon

Хороший пост. Кстати, ; не требуется после предпоследнего } :-), @Greenonline

Существует также вызов Serial_event(), не так ли?, @Divisadero


3

Я не могу комментировать ответ Ди. Фактический код, который выполняется в основном цикле, находится здесь:

    int main(void) {
    init();
    initVariant();

    setup();

    for (;;) {
        loop();
        if (serialEventRun) serialEventRun();
    }   
    return 0;
}

И да, setup() вызывается один раз, а loop() вызывается повторно (вместе с некоторыми последовательными функциями).

,