Отсутствие на Arduino операционной системы совершенно не означает, что невозможно решить проблему многозадачности для этого контроллера. Для этого просто нужна своеобразная методика, частью которой является использование прерываний.
Это могут быть прерывания по таймеру или внешние прерывания, уведомляющие о событиях, воздействующих на систему извне.
Прерывания и их источники
В процессе работы управляющий процессор выполняет определенные операции, а прерывание вызывает их остановку и в соответствие с кодом заставляет выполнить операции с более высоким приоритетом.
Проще говоря, прерывания – это набор приоритетов для тех или иных процессов, исполняемых контроллером.
Этот процесс имеет название обработчик прерываний и дает возможность присоединить к себе определенную функцию. Она будет вызываться при поступлении сигнала прерывания. Процессор, вернувшись из обработчика, приступит к выполнению тех операций, в процессе исполнения которых поступил сигнал прерывания.
❓ Обязательно к просмотру начинающему в Arduino IDE (ошибка компилятора)
Источником сигнала прерывания может стать таймер Arduino или процесс изменения состояния одного из контактов (пинов). Еще одним источником может стать какой-либо вход внешних прерываний: нужный сигнал появится при изменении его состояния.
Прерывание можно заставить работать с помощью кода, который поможет отвечать на прерывание. При этом нет необходимости писать код в loop (), который используют для систематической проверки приоритета прерывания.
В момент получения сигнала процессор автоматически остановится, вызвав обработчик. При этом нет нужды беспокоиться о времени отклика на нажатие кнопки или продолжительной реакции на выполняемую программу.
Особенности прерывания по таймеру
Как же управлять временем и запускать процессы в нужном вам порядке? При работе с микросхемой Arduino для этого можно использовать millis(), ее эффективность зависит от постоянного обращения к ней. Тогда, в случае вызова этой функции, можно будет понять – наступило время определенной операции или нет.
Добиться эффективности можно при вызове millis() несколько раз в миллисекунду, но это довольно расточительно. Для вызова функции раз в миллисекунду (что является оптимальным вариантом) необходимо использовать таймер. Его можно установить на интервал в миллисекунду и добиться желаемого результата.
Микроконтроллер Arduino Uno укомплектован тремя таймерами: из них timer0 предназначен для генерации прерываний с интервалом в одну миллисекунду. При этом будет постоянно обновляться счетчик, передающий информацию функции millis(). Вести точный подсчет таймеру позволяет определенная частота, получаемая из 16 МГц процессора.
Arduino, при необходимости, позволяет произвести конфигурацию делителя частоты с подбором оптимального режима счета.
Функция timer0 оперирует тактовым делителем на 64 и изменять это значение не стоит. Оно позволяет получить частоту прерывания, близкую к 1 кГц, оптимальную для целей большинства схемотехников. Если попытаться изменить данный параметр, то можно нарушить работу функции millis().
Уроки Ардуино #16 — аппаратные прерывания
Регистры сравнения и их роль в генерации прерывания
Регистры сравнения выполняют функцию анализа хранимых данных с текущим состоянием счетчика прерывания. Например, регистр сравнения (OCR0A) эффективно применяется для прерывания в середине счета.
Программный код, пример которого приведен ниже, позволит генерировать функцию TIMER0_COMPA при прохождении счетчика 0xAF:
Прерывания в Arduino IDE
// Timer0 уже используется millis() — мы создаем прерывание где-то
// в середине и вызываем ниже функцию «Compare A»
OCR0A = 0xAF ;
TIMSK0 |= _BV ( OCIE0A ) ;
Для оптимизации работы кода и микроконтроллера следует полностью отказаться от loop(). Для этого необходимо определение по вектору обработчика прерывания с помощью функции TIMER0_COMPA_vect. Благодаря ей обработчик и будет выполнять все те операции, что раньше делались в loop(). Данный код позволит вернуться к использованию функции delay(), благодаря чему все классические мерцающие светодиоды или работающие сервоприводы будут функционировать без проблем, но при этом мы получим удобный и практичный таймер.
Прерывание вызывается один раз в миллисекунду
// Прерывание вызывается один раз в миллисекунду
SIGNAL ( TIMER0_COMPA_vect )
unsigned long currentMillis = millis ( ) ;
sweeper1 . Update ( currentMillis ) ;
//if(digitalRead(2) == HIGH)
sweeper2 . Update ( currentMillis ) ;
led1 . Update ( currentMillis ) ;
led2 . Update ( currentMillis ) ;
led3 . Update ( currentMillis ) ;
Какие внешние воздействия вызывают прерывание
Вызвать внешние прерывания могут определенные действия из внешней среды. Это может быть простым нажатием на кнопку или срабатыванием используемого датчика. При этом нет необходимости вести постоянный опрос вывода GPIO о происходящих изменениях. Микроконтроллер Arduino может иметь несколько пинов, способных обрабатывать внешние прерывания.
Так, на плате Arduino Uno их два, а на Arduino Mega 2560 – 6. Продемонстрировать их функционал можно с помощью кнопки сброса сервопривода. Для этого при написании кода в класс Sweeper необходимо добавит функцию reset(). Она способна установить нулевое положение и перетаскивать в нее сервопривод.
Применение прерываний в Arduino IDE
void reset ( )
servo . write ( pos ) ;
increment = abs ( increment ) ;
Соединить обработчик с внешним прерыванием поможет другая функция – attachInterrupt(). На приведенных в качестве примеров микроконтроллерах Interrupt0 реализована на втором контакте. Она сообщает микроконтроллеру о том, что на данном входе ожидается спад сигнала.
Применение прерываний в Arduino IDE
pinMode ( 2 , INPUT_PULLUP ) ;
attachInterrupt ( 0 , Reset , FALLING ) ;
Достаточно нажать кнопку и сигнал действительно падает до минимального уровня, вызывая обработчик Reset.
Применение прерываний в Arduino IDE
void Reset ( )
sweeper1 . reset ( ) ;
sweeper2 . reset ( ) ;
В результате, при нажатии кнопки – сервоприводы сбрасываются, возвращаясь в нулевое положение. Схема внешних прерываний простая, и с помощью приведенных в примере скетчей, можно будет получить довольно легко желаемый результат. Полный код программы с таймерами и внешними прерываниями:
Применение прерываний в Arduino IDE
class Flasher
// Переменные-участники класса устанавливаются при запуске
int ledPin ; // Номер контакта со светодиодом
long OnTime ; // длительность ВКЛ в мс
long OffTime ; // длительность ВЫКЛ в мс
// Контроль текущего состояния
int ledState ; // устанавливает текущее состояние светодиода
unsigned long previousMillis ; // время последнего обновления состояния светодиода
// Конструктор — создает объект Flasher, инициализирует переменные-участники и состояние
Flasher ( int pin , long on , long off )
ledPin = pin ;
pinMode ( ledPin , OUTPUT ) ;
OnTime = on ;
OffTime = off ;
ledState = LOW ;
previousMillis = 0 ;
void Update ( unsigned long currentMillis )
if ( ( ledState == HIGH ) ( currentMillis — previousMillis >= OnTime ) )
ledState = LOW ; // ВЫКЛ
previousMillis = currentMillis ; // Запомнить время
digitalWrite ( ledPin , ledState ) ; // Обновить состояние светодиода
else if ( ( ledState == LOW ) ( currentMillis — previousMillis >= OffTime ) )
ledState = HIGH ; // ВКЛ
previousMillis = currentMillis ; // Запомнить время
digitalWrite ( ledPin , ledState ) ; // Обновить состояние светодиода
class Sweeper
Servo servo ; // объект servo
int pos ; // текущее положение сервопривода
int increment ; // определяем увеличение перемещения на каждом интервале
int updateInterval ; // определяем время между обновлениями
unsigned long lastUpdate ; // определяем последнее обновление положения
Sweeper ( int interval )
updateInterval = interval ;
increment = 1 ;
void Attach ( int pin )
servo . attach ( pin ) ;
void Detach ( )
servo . detach ( ) ;
void reset ( )
servo . write ( pos ) ;
increment = abs ( increment ) ;
void Update ( unsigned long currentMillis )
if ( ( currentMillis — lastUpdate ) > updateInterval ) //время обновиться
lastUpdate = millis ( ) ;
pos += increment ;
servo . write ( pos ) ;
if ( ( pos >= 180 ) || ( pos <= 0 ) ) // инициализируем конец вращения
// инициализируем обратное направление
increment = — increment ;
Flasher led1 ( 11 , 123 , 400 ) ;
Flasher led2 ( 12 , 350 , 350 ) ;
Flasher led3 ( 13 , 200 , 222 ) ;
Sweeper sweeper1 ( 25 ) ;
Sweeper sweeper2 ( 35 ) ;
void setup ( )
sweeper1 . Attach ( 9 ) ;
sweeper2 . Attach ( 10 ) ;
// Timer0 уже используется millis() — прерываемся примерно посередине и вызываем ниже функцию «Compare A»
OCR0A = 0xAF ;
TIMSK0 |= _BV ( OCIE0A ) ;
pinMode ( 2 , INPUT_PULLUP ) ;
attachInterrupt ( 0 , Reset , FALLING ) ;
void Reset ( )
sweeper1 . reset ( ) ;
sweeper2 . reset ( ) ;
// Прерывание вызывается один раз в миллисекунду, ищет любые новые данные, и если нашло, сохраняет их
SIGNAL ( TIMER0_COMPA_vect )
unsigned long currentMillis = millis ( ) ;
sweeper1 . Update ( currentMillis ) ;
// if(digitalRead(2) == HIGH)
sweeper2 . Update ( currentMillis ) ;
led1 . Update ( currentMillis ) ;
led2 . Update ( currentMillis ) ;
led3 . Update ( currentMillis ) ;
Библиотеки прерываний
У схемотехников, благодаря Всемирной паутине есть доступ к широкому кругу библиотек, которые существенно облегчат работу с таймерами. Большинство из них предназначены для тех, кто применяет функцию millis(), но есть и такие, которые позволяют произвести желаемую настройку таймеров и сгенерировать прерывания.
Оптимальным вариантом для этого являются библиотеки TimerThree и TimerOne, разработанные Paul Stoffregan. Благодаря им, схемотехники получают широкий выбор возможностей, с помощью которых можно сконфигурировать прерывания с помощью таймера. Первая из этих библиотек не работает с Adruino Uno, но прекрасно зарекомендовала себя с платами Teensy, микроконтроллерами Adruino Mega2560 и Adruino Leonardo.
Недостатком Adruino Uno является наличие всего двух ходов, предназначенных для работы с внешними прерываниями. Если требуется большее количество подобных пинов, то отчаиваться не стоит, ведь этот микроконтроллер поддерживает pin-change – прерывания по изменению входа и работает это на всех восьми входах. Их отличие от простых внешних прерываний – в сложности обработки, так как схемотехнику требуется отслеживать последнее из известных состояний пинов. Только в этом случае можно будет понять, какой из них вызвал прерывание.
Наиболее информативное и практичной библиотекой для прерываний по изменению входа является PinChangeInt.
Прерывания: основные правила работы
В ходе реализации проекта может потребоваться несколько прерываний, но если каждое из них будет иметь максимальный приоритет, то фактически его не будет ни у одной из функций. По этой же причине не рекомендуется использовать более десятка прерываний. Обработчики должны применяться только к тем процессам, которые имеют максимальную чувствительность ко временным интервалам.
Не стоит забывать, что пока программа находится в обработчике прерывания – все другие прерывания отключены. Большое количество прерываний ведет к ухудшению их ответа. В момент, когда действует одно прерывание, а остальные отключаются, возникает два важных нюанса, которые должен учитывать схемотехник. Во-первых, время прерывание должно быть максимально коротким.
Это позволит не пропустить все остальные запланированные прерывания. Во-вторых, при обработке прерывания программный код не должен требовать активности от других прерываний. Если этого не предотвратить, то программа просто зависнет. Не стоит использовать длительную обработку в loop(), лучше разработать код для обработчика прерывания с установкой переменной volatile.
Она подскажет программе, что дальнейшая обработка не нужна. Если вызов функции Update() все же необходим, то предварительно необходимо будет проверить переменную состояния. Это позволит выяснить, необходима ли последующая обработка. Перед тем, как заняться конфигурацией таймера, следует произвести проверку кода.
Таймеры Anduino стоит отнести к ограниченным ресурсам, ведь их всего три, а применяются они для выполнения самых разных функций. Если запутаться с использованием таймеров, то ряд операций может просто перестать работать. Какими функциями оперирует тот или иной таймер? Для микроконтроллера Arduino Uno у каждого из трех таймеров свои операции.
Так Timer0 отвечает за ШИМ на пятом и шестом пине, функции millis(), micros(), delay(). Другой таймер – Timer1, используется с ШИМ на девятом и десятом пине, с библиотеками WaveHC и Servo. Timer2 работает с ШИМ на 11 и 13 пинах, а также с Tone.
Схемотехник должен позаботиться о безопасном использовании обрабатываемых совместно данных. Ведь прерывание останавливает на миллисекунду все операции процессора, а обмен данных между loop() и обработчиками прерываний должен быть постоянным.
Может возникнуть ситуация, когда компилятор ради достижения своей максимальной производительности начнет оптимизацию кода. Результатом этого процесса будет сохранение в регистре копии основных переменных кода, что позволит обеспечить максимальную скорость доступа к ним. Недостатком этого процесса может стать подмена реальных значений сохраненными копиями, что может привести к потере функциональности. Чтобы этого не произошло нужно использовать переменную voltatile, которая поможет предотвратить ненужные оптимизации. При использовании больших массивов, которым требуются циклы для обновлений, нужно отключить прерывания на момент этих обновлений.
Источник: voltiq.ru
как остановить цикл arduino
Arduino специально не предоставляет абсолютно никакого способа выйти из их loop функция, как показано кодом, который фактически ее запускает:
setup(); for (;;)
кроме того, на микроконтроллере нет ничего, чтобы выйти в первую очередь.
самое близкое, что вы можете сделать, это просто остановить процессор. Это остановит обработку, пока не будет сброшено.
автор: Matti Virkkunen
Это не опубликовано на Arduino.cc но вы можете фактически выйти из цикла с простым выходом (0);
Это будет компилироваться практически на любой доске, которую вы имеете в своем списке. Я использую IDE 1.0.6. Я протестировал его с Uno, Mega, Micro Pro и даже безделушкой Adafruit
void loop() < // All of your code here /* Note you should clean up any of your I/O here as on exit, all ‘ON’outputs remain HIGH */ // Exit the loop exit(0); //The 0 is required to prevent compile error. >
Я использую это в проектах, где я подключаю кнопку к pin-коду сброса. В основном ваш цикл работает до выхода (0); а затем просто сохраняется в последнем состоянии. Я сделал несколько роботов. для моих детей, и каждый раз, когда нажмите кнопку (Сброс), код начинается с начала функции loop ().
автор: Zoul007
Матти Вирккунен сказал это правильно, нет» приличного » способа остановить цикл. Тем не менее, глядя на свой код и делая несколько предположений, я предполагаю, что вы пытаетесь вывести сигнал с заданной частотой, но хотите его остановить.
если это так, есть несколько решений:
-
если вы хотите генерировать сигнал с помощью ввода кнопки, вы можете сделать следующее
int speakerOut = A0; int buttonPin = 13; void setup() < pinMode(speakerOut, OUTPUT); pinMode(buttonPin, INPUT_PULLUP); >int a = 0; void loop() < if(digitalRead(buttonPin) == LOW) < a ++; Serial.println(a); analogWrite(speakerOut, NULL); if(a >50 a < 300) < analogWrite(speakerOut, 200); >if(a if(a >= 300 a > >
автор: Maximo Dominguez
три варианта, которые приходят на ум:
1-й) конец void loop() С while(1) . или не хуже. while(true)
void loop() < //the code you want to run once here, //e.g., If (blah == blah). etc. while(1) //last line of main loop >
эта опция запускает ваш код один раз, а затем запускает Ard в бесконечная «невидимая» петля. Возможно, не самый лучший способ идите, но что касается внешних проявлений, это делает свою работу.
Ard будет продолжать рисовать ток, пока он вращается в бесконечный круг. возможно, можно установить своего рода таймер функция, которая ставит Ard спать после стольких секунд, минуты и т. д.- зацикливания. просто мысль. есть конечно различные библиотеки сна там. видеть например, Monk, программирование Arduino: следующие шаги, pgs., 85-100 для дальнейшего обсуждения таких.
2nd) создайте функцию «stop main loop» с условным управлением структура, которая делает свой первоначальный тест неудачным на втором проходе.
Это часто требует объявления глобальной переменной и имеющих функция» stop main loop » переключает значение переменной при прекращении. Е. Г.,
boolean stop_it = false; //global variable void setup() < Serial.begin(9600); //blah. >boolean stop_main_loop() < //fancy stop main loop function if(stop_it == false)< //which it will be the first time through Serial.println(«This should print once.»); //then do some more blah. you can locate all the // code you want to run once here. eventually end by //toggling the «stop_it» variable . >stop_it = true; //. like this return stop_it; //then send this newly updated «stop_it» value // outside the function > void loop < stop_it = stop_main_loop(); //and finally catch that updated //value and store it in the global stop_it //variable, effectively //halting the loop . >
конечно, это может быть не особенно красиво, но это также работает.
Он пинает Ard в другую бесконечную «невидимую» петлю, но это время это случай многократной проверки if(stop_it == false) условие stop_main_loop() который, конечно, не проходит каждый раз после первого раза.
3rd) можно снова использовать глобальную переменную, но использовать простой if (test == blah)<> структура вместо причудливой » остановки main функция петля».
boolean start = true; //global variable void setup() < Serial.begin(9600); >void loop() < if(start == true)< //which it will be the first time through Serial.println(«This should print once.»); //the code you want to run once here, //e.g., more If (blah == blah). etc. >start = false; //toggle value of global «start» variable //Next time around, the if test is sure to fail. >
есть, конечно, другие способы «остановить» этот надоедливый бесконечный основной цикл но эти три, а также те, которые уже упоминались, должны заставить вас начать.
автор: philomech
это отключит прерывания и поставит процессор в (постоянный до сброса / включения питания) сон:
cli(); sleep_enable(); sleep_cpu();
автор: Sigivald
> #include > #include > #include > > #define RST_PIN 4 > #define SS_PIN 2 > > String content=»»; > > byte mac[]=; IPAddress > ip(192,168,3,15); > > MFRC522 mfrc522(SS_PIN, RST_PIN); EthernetClient client; > > void setup() < Serial.begin(9600); >SPI.begin(); while(!Serial) Ethernet.begin(mac, ip); > mfrc522.PCD_Init(); > //Serial.println(F(«Silahkan Scan Kartu RFID Anda:»)); > > > void loop() < rfid(); database(); >> > void rfid() < //membaca kartu RFID if ( ! >mfrc522.PICC_IsNewCardPresent()) < >return; > // memilih salah satu card yang terdeteksi if ( ! mfrc522.PICC_ReadCardSerial()) < >return; > > //Serial.print(«Kartu Anda Adalah :»); //String content= «»; //byte letter; //for (byte i = 0; i < mfrc522.uid.size; i++) < >//Serial.print(mfrc522.uid.uidByte[i] < 0x10 ? » 0″ : » «); >//Serial.print(mfrc522.uid.uidByte[i], HEX); > //content.concat(String(mfrc522.uid.uidByte[i] < 0x10 ? » 0″ : » «)); >//content.concat(String(mfrc522.uid.uidByte[i], HEX)); //> //Serial.println(); //delay(1000); //change value if you want to > read cards faster > > //mfrc522.PICC_HaltA(); //mfrc522.PCD_StopCrypto1(); > > > void database() < EthernetClient client; rfid(); if >(client.connect(«192.168.3.12», 80)) < >Serial.print(«Kartu Anda Adalah :»); > String content=»»; > byte letter; > for (byte i = 0; i < mfrc522.uid.size; i++) < >Serial.print(mfrc522.uid.uidByte[i] < 0x10 ? » 0″ : » «); >Serial.print(mfrc522.uid.uidByte[i], HEX); > content.concat(String(mfrc522.uid.uidByte[i] < 0x10 ? » 0″ : » «)); >content.concat(String(mfrc522.uid.uidByte[i], HEX)); > Serial.println(); delay(1000); //change value if you want to read > cards faster mfrc522.PICC_HaltA(); mfrc522.PCD_StopCrypto1(); > > //Serial.println(«connected»); > Serial.print(content); > client.println(«POST /gerbang2/insert.php HTTP/1.1»); > client.println(«Host: 192,168,3,12»); > client.println(«Connection: close»); > client.print(«Content-Type: application/x-www-form-urlencodedn»); > client.print(«Content-Length: «); > client.print(content.length()); > client.print(«nn»); > client.print(content); > Serial.println(content); > delay (1000); > client.stop(); > else < >Serial.println(«Connection Failed.»); > Serial.println(); > delay (1000); > >
Как остановить цикл в серийный монитор?
Источник: askdev.ru
Циклы в Ардуино
Циклы в ардуино используются для управления потоком программы. В цикле блок кода выполняется снова и снова. Каждый цикл цикла называется итерацией цикла. В зависимости от определенных условий, которые определяются в коде, вы можете контролировать, входит ли программа в цикл или нет.
В каждом скетче Arduino есть как минимум один цикл — это основной цикл или ,по-другому, раздел void loop() . Но при проектировании реальных устройств может быть очень полезно иметь другие циклы в коде, работающие внутри основного цикла.
В этой статье мы обсудим циклы while , циклы do-while и циклы for . Мы увидим, как использовать эти циклы в программе Arduino. Для этого соберем пример проекта, который мигает светодиодом только при нажатии кнопки. Мы также увидим, как выполнять такие операции, как одновременная установка режимов нескольких контактов с помощью цикла for .
Цикл WHILE в Ардуино
Код для цикла while выглядит следующим образом:
while(условие)< // тело цикла while >
Если условие истинно, программа войдет в тело цикла while и будет выполнять код тела в цикле до тех пор, пока условие остается истинным. Если условие ложно, программа пропустит цикл while и перейдет к следующей строке кода.
В качестве примера того, как использовать цикл while , давайте создадим схему, которая будет мигать светодиодом при нажатии кнопки.
Для создания этого проекта понадобятся следующие компоненты:
- Arduino Uno
- Соединительные провода
- Макетная плата
- Резистор 1 Ком
- Тактильная кнопка
Соберем следующую электрическую схему:
После сборки схемы можно загрузить сам код в плату:
int buttonPin = 2; // Контакт для кнопки int ledPin = 8; // Контакт для светодиода void setup() < pinMode(buttonPin, INPUT_PULLUP); // Настройка режима — на вход с подтягивающим резистором pinMode(ledPin, OUTPUT); // Настройка режима — на выход >void loop() < int buttonState = digitalRead(buttonPin); // Получаем значение с кнопки while (buttonState == LOW) < // Условие цикла while digitalWrite(ledPin, HIGH); // Включаем светодиод delay(200); // Пауза 200 миллисекунд digitalWrite(ledPin, LOW); // Выключаем светодиод delay(200); // Пауза 200 миллисекунд buttonState = digitalRead(buttonPin); // Обновляем состояние кнопки >>
В верхней части скетча мы объявляем переменную для контакта, подключенного к кнопке, с именем buttonPin , и переменную для контакта, подключенного к светодиоду, с именем ledPin .
В разделе setup() мы установили buttonPin как вход с внутренним подтягивающим резистором. Затем мы устанавливаем ledPin в качестве выхода.
В разделе loop() мы объявляем переменную с именем buttonState и присваиваем ей значение digitalRead(buttonPin). Это считывает состояние напряжения на контакте кнопки (контакт 2 ), и сохраняет результат в переменной buttonState .