동일한 코드 블록에서 여러 작업을 수행하지 않고 프로그램의 여러 부분을 함께 실행할 수있는 방법이 있습니까?
하나의 스레드가 외부 장치를 기다리는 동안 다른 스레드의 LED도 깜박입니다.
동일한 코드 블록에서 여러 작업을 수행하지 않고 프로그램의 여러 부분을 함께 실행할 수있는 방법이 있습니까?
하나의 스레드가 외부 장치를 기다리는 동안 다른 스레드의 LED도 깜박입니다.
답변:
Arduino에서는 다중 프로세스 또는 다중 스레딩이 지원되지 않습니다. 일부 소프트웨어를 사용하면 여러 스레드에 가까운 것을 수행 할 수 있습니다.
Protothreads 를보고 싶습니다 :
프로토스 레드는 소형 임베디드 시스템 또는 무선 센서 네트워크 노드와 같이 메모리 제약이 심한 시스템을 위해 설계된 초경량 스택리스 스레드입니다. 프로토스 레드는 C로 구현 된 이벤트 중심 시스템에 선형 코드 실행을 제공합니다. 프로토스 레드는 기본 운영 체제와 함께 또는 이벤트 핸들러를 차단하기 위해 기본 운영 체제와 함께 사용될 수 있습니다. 프로토스 레드는 복잡한 상태 머신이나 완전한 멀티 스레딩없이 순차적 인 제어 흐름을 제공합니다.
물론 여기 에 code 예제 와 함께 Arduino 예제가 있습니다 . 이 SO 질문 도 유용 할 수 있습니다.
ArduinoThread 도 좋습니다.
AVR 기반 Arduino는 (하드웨어) 스레딩을 지원하지 않습니다. ARM 기반 Arduino에 익숙하지 않습니다. 이 제한을 해결하는 한 가지 방법은 인터럽트, 특히 시간 인터럽트를 사용하는 것입니다. 타이머를 프로그래밍하여 너무 많은 마이크로 초마다 주 루틴을 중단하고 특정 다른 루틴을 실행할 수 있습니다.
Uno에서 소프트웨어 측 멀티 스레딩을 수행 할 수 있습니다. 하드웨어 레벨 스레딩은 지원되지 않습니다.
멀티 스레딩을 달성하려면 기본 스케줄러를 구현하고 프로세스 또는 작업 목록을 유지 관리하여 실행해야하는 다양한 작업을 추적해야합니다.
매우 간단한 비 선점 스케줄러의 구조는 다음과 같습니다.
//Pseudocode
void loop()
{
for(i=o; i<n; i++)
run(tasklist[i] for timelimit):
}
여기서 tasklist
함수 포인터의 배열이 될 수 있습니다.
tasklist [] = {function1, function2, function3, ...}
양식의 각 기능으로 :
int function1(long time_available)
{
top:
//Do short task
if (run_time<time_available)
goto top;
}
각 기능은 function1
LED 조작 및 function2
플로트 계산 수행 과 같은 별도의 작업을 수행 할 수 있습니다 . 할당 된 시간을 준수하는 것은 각 작업 (기능)의 책임입니다.
바라건대, 시작하기에 충분해야합니다.
요구 사항에 대한 설명에 따라 :
첫 번째 "스레드"에 하나의 Arduino 인터럽트를 사용할 수있는 것 같습니다 (실제로는 "태스크"라고합니다).
Arduino 인터럽트는 외부 이벤트 (디지털 입력 핀의 전압 레벨 또는 레벨 변경)에 따라 하나의 기능 (코드)을 호출 할 수 있으며, 이로 인해 기능이 즉시 트리거됩니다.
그러나 인터럽트를 염두에 두어야 할 중요한 점은 호출 된 함수가 가능한 한 빨라야한다는 것입니다 (일반적으로 delay()
호출 또는에 의존하는 다른 API 가 없어야 함 delay()
).
외부 이벤트 트리거시 활성화해야 할 작업이 긴 경우 협력 스케줄러를 사용하여 인터럽트 기능에서 새 작업을 추가 할 수 있습니다.
인터럽트에 대한 두 번째 중요한 점은 그 수가 제한되어 있다는 것입니다 (예 : UNO에서는 2). 따라서 더 많은 외부 이벤트가 발생하기 시작하면 모든 입력을 하나로 멀티플렉싱하는 일종의 구현을 수행하고 인터럽트 함수가 멀티플 렉트 된 inut가 실제 트리거인지 판별해야합니다.
간단한 해결책은 스케줄러 를 사용하는 것 입니다. 몇 가지 구현이 있습니다. AVR 및 SAM 기반 보드에 사용할 수있는 것을 간략하게 설명합니다. 기본적으로 단일 통화는 작업을 시작합니다. "스케치 내 스케치".
#include <Scheduler.h>
....
void setup()
{
...
Scheduler.start(taskSetup, taskLoop);
}
Scheduler.start ()는 taskSetup을 한 번 실행 한 다음 Arduino 스케치가 작동하는 것처럼 반복적으로 taskLoop를 호출하는 새 작업을 추가합니다. 작업에는 자체 스택이 있습니다. 스택의 크기는 선택적 매개 변수입니다. 기본 스택 크기는 128 바이트입니다.
컨텍스트 전환을 허용하려면 작업이 yield () 또는 delay () 을 호출해야합니다 . 조건을 기다리는 데 필요한 지원 매크로도 있습니다.
await(Serial.available());
매크로는 다음에 대한 구문 설탕입니다.
while (!(Serial.available())) yield();
대기는 또한 작업을 동기화하는 데 사용될 수 있습니다. 아래는 스 니펫 예제입니다.
volatile int taskEvent = 0;
#define signal(evt) do { await(taskEvent == 0); taskEvent = evt; } while (0)
...
void taskLoop()
{
await(taskEvent);
switch (taskEvent) {
case 1:
...
}
taskEvent = 0;
}
...
void loop()
{
...
signal(1);
}
자세한 내용은 예제를 참조하십시오 . 여러 개의 LED 깜박임에서 디 바운스 버튼 및 비 차단 명령 줄 읽기가있는 간단한 쉘의 예가 있습니다. 템플릿과 네임 스페이스를 사용하여 소스 코드를 구조화하고 줄일 수 있습니다. 아래 스케치 는 멀티 깜박임에 템플릿 기능을 사용하는 방법을 보여줍니다. 스택에는 64 바이트이면 충분합니다.
#include <Scheduler.h>
template<int pin> void setupBlink()
{
pinMode(pin, OUTPUT);
}
template<int pin, unsigned int ms> void loopBlink()
{
digitalWrite(pin, HIGH);
delay(ms);
digitalWrite(pin, LOW);
delay(ms);
}
void setup()
{
Scheduler.start(setupBlink<11>, loopBlink<11,500>, 64);
Scheduler.start(setupBlink<12>, loopBlink<12,250>, 64);
Scheduler.start(setupBlink<13>, loopBlink<13,1000>, 64);
}
void loop()
{
yield();
}
성능, 즉 작업 시작 시간, 컨텍스트 전환 등의 아이디어를 제공 하는 벤치 마크 도 있습니다 .
이 포럼의 이전 주문에서 다음 질문 / 답장이 전기 공학으로 옮겨졌습니다. 메인 루프를 사용하여 직렬 IO를 수행하는 동안 타이머 인터럽트를 사용하여 LED를 깜박이는 샘플 arduino 코드가 있습니다.
다시 게시 :
인터럽트는 다른 일이 진행되는 동안 일을 처리하는 일반적인 방법입니다. 아래 예에서는를 사용하지 않고 LED가 깜박 delay()
입니다. Timer1
발생할 때마다 인터럽트 서비스 루틴 (ISR) isrBlinker()
이 호출됩니다. LED를 켜거나 끕니다.
다른 일이 동시에 발생할 수 있음을 나타 내기 loop()
위해 LED 깜박임과 상관없이 foo / bar를 직렬 포트에 반복해서 씁니다.
#include "TimerOne.h"
int led = 13;
void isrBlinker()
{
static bool on = false;
digitalWrite( led, on ? HIGH : LOW );
on = !on;
}
void setup() {
Serial.begin(9600);
Serial.flush();
Serial.println("Serial initialized");
pinMode(led, OUTPUT);
// initialize the ISR blinker
Timer1.initialize(1000000);
Timer1.attachInterrupt( isrBlinker );
}
void loop() {
Serial.println("foo");
delay(1000);
Serial.println("bar");
delay(1000);
}
이것은 매우 간단한 데모입니다. ISR은 훨씬 더 복잡 할 수 있으며 타이머 및 외부 이벤트 (핀)에 의해 트리거 될 수 있습니다. 많은 공통 라이브러리는 ISR을 사용하여 구현됩니다.
또한 매트릭스 LED 디스플레이를 구현하는 동안이 주제에 도달했습니다.
한마디로, Arduino에서 millis () 함수와 타이머 인터럽트를 사용하여 폴링 스케줄러를 구축 할 수 있습니다.
Bill Earl의 다음 기사를 제안합니다.
https://learn.adafruit.com/multi-tasking-the-arduino-part-1/overview
https://learn.adafruit.com/multi-tasking-the-arduino-part-2/overview
https://learn.adafruit.com/multi-tasking-the-arduino-part-3/overview
내 ThreadHandler 라이브러리에 시도해 볼 수도 있습니다.
https://bitbucket.org/adamb3_14/threadhandler/src/master/
yielding () 또는 delay ()에서 릴레이하지 않고 컨텍스트 전환을 허용하기 위해 인터럽트 스케줄러를 사용합니다.
세 개의 스레드가 필요했기 때문에 라이브러리를 만들었고 다른 스레드가 무엇을 하든지 정확한 시간에 실행하려면 두 개가 필요했습니다. 첫 번째 스레드는 직렬 통신을 처리했습니다. 두 번째는 Eigen 라이브러리와 함께 float 행렬 곱셈을 사용하여 Kalman 필터를 실행했습니다. 세 번째는 고속 전류 제어 루프 스레드로 매트릭스 계산을 중단 할 수 있어야했습니다.
각 순환 스레드에는 우선 순위와 기간이 있습니다. 현재 실행 스레드보다 우선 순위가 높은 스레드가 다음 실행 시간에 도달하면 스케줄러가 현재 스레드를 일시 정지하고 우선 순위가 높은 스레드로 전환합니다. 우선 순위가 높은 스레드가 실행을 완료하면 스케줄러가 이전 스레드로 다시 전환됩니다.
ThreadHandler 라이브러리의 스케줄링 체계는 다음과 같습니다.
스레드는 C ++ 상속을 통해 생성 될 수 있습니다
class MyThread : public Thread
{
public:
MyThread() : Thread(priority, period, offset){}
virtual ~MyThread(){}
virtual void run()
{
//code to run
}
};
MyThread* threadObj = new MyThread();
또는 createThread 및 람다 함수를 통해
Thread* myThread = createThread(priority, period, offset,
[]()
{
//code to run
});
스레드 객체는 생성 될 때 자동으로 ThreadHandler에 연결됩니다.
작성된 스레드 객체의 실행을 시작하려면 다음을 수행하십시오.
ThreadHandler::getInstance()->enableThreadExecution();
그리고 여기 또 다른 마이크로 프로세서 협업 멀티 태스킹 라이브러리 – PQRST : 간단한 작업 실행을위한 우선 순위 대기열이 있습니다.
이 모델에서 스레드는의 서브 클래스로 구현되며,이 클래스는 Task
향후 일정이 예정되어 있습니다 (일반적으로 서브 클래스 인 경우 규칙적인 간격으로 다시 예약 될 수 있음 LoopTask
). run()
작업의 기한이되면 개체 의 메서드가 호출됩니다. 이 run()
메소드는 일부 적법한 작업을 수행 한 다음 리턴합니다 (협동 비트 임). 일반적으로 연속적인 호출에 대한 작업을 관리하기 위해 일종의 상태 시스템을 유지 관리합니다 (사소한 예는 light_on_p_
아래 예의 변수입니다). 코드 구성 방법에 대해 약간의 재고가 필요하지만 상당히 집중적으로 사용하면 매우 유연하고 강력합니다.
그것은 시간 단위에 대한 불가지론, 그래서 그것은 단위로 실행하는 것과 행복 millis()
으로 micros()
, 또는 편리 다른 틱.
다음은이 라이브러리를 사용하여 구현 된 '깜박임'프로그램입니다. 실행중인 단일 작업 만 표시됩니다. 다른 작업은 일반적으로 생성되어에서 시작됩니다 setup()
.
#include "pqrst.h"
class BlinkTask : public LoopTask {
private:
int my_pin_;
bool light_on_p_;
public:
BlinkTask(int pin, ms_t cadence);
void run(ms_t) override;
};
BlinkTask::BlinkTask(int pin, ms_t cadence)
: LoopTask(cadence),
my_pin_(pin),
light_on_p_(false)
{
// empty
}
void BlinkTask::run(ms_t t)
{
// toggle the LED state every time we are called
light_on_p_ = !light_on_p_;
digitalWrite(my_pin_, light_on_p_);
}
// flash the built-in LED at a 500ms cadence
BlinkTask flasher(LED_BUILTIN, 500);
void setup()
{
pinMode(LED_BUILTIN, OUTPUT);
flasher.start(2000); // start after 2000ms (=2s)
}
void loop()
{
Queue.run_ready(millis());
}
run()
메소드가 호출 된 후에는 중단되지 않으므로 합리적으로 신속하게 완료해야합니다. 그러나 일반적으로 작업을 수행 한 다음 나중에 일정 LoopTask
기간 동안 하위 클래스의 경우 자동으로 일정을 조정합니다 . 일반적인 패턴은 작업이 일부 내부 상태 머신 (사소한 예는 light_on_p_
위 의 상태 임)을 유지하여 다음에 올 때 적절하게 작동하도록하는 것입니다.
run()
. 이 협동하여 CPU를 얻을 수있는 스레드, 예를 들어, 전화와 대조적이다 yield()
나 delay()
. 또는 선점 형 스레드는 언제든지 예약 할 수 있습니다. 나는 스레드를 찾는 많은 사람들이 상태 머신보다는 블로킹 코드 작성을 선호하기 때문에 그렇게하는 것을 보았으므로 구별이 중요하다고 생각합니다. CPU를 생성하는 실제 스레드를 차단하는 것이 좋습니다. RtC 작업 차단은 아닙니다.