使用循环实现 Arduino 多任务处理





5.00/5 (5投票s)
在不使用任何外部库的情况下,为您的 Arduino 项目创建多任务代码的简单方法。
引言
如需最新更新版本,
您可以访问 GitHub 仓库:
https://github.com/ArabicRobotics/ArduinoMultithreading
Arduino 是一个非常著名的设备……您可以用它来控制任何事物以及计算机。Arduino 具有许多功能和控制器,以及可以执行任务的引脚。该设备一次只能执行一条指令,如果您只使用一个引脚,甚至只使用 IDE 默认提供的循环,这都没问题。但如果您有多个需要读取和写入的设备和引脚,甚至希望同时运行多个循环,这对 Arduino 来说是不可能的,因为它不支持多任务处理。
Arduino 当前工作方式
Arduino 完成一个任务、一个循环、一个函数,然后移至下一个。然后,移至下一个,依此类推。
这有什么问题?
这种情况的问题在于,您无法处理所有正在运行的任务,甚至无法以任何方式控制 Arduino,例如监听串行端口,直到当前任务完成之前,这是不可能的。
背景
在我最后一个项目中,我需要同时运行多个循环。同时监听串行端口并根据串行读取的数据执行操作。这些循环涉及多个引脚并在 Arduino 板上执行操作。所以我创建了这个技巧来强制 Arduino 这样做,并决定与您分享。
解决方案
我解决这个问题的方法是创建一个结构体,我称之为“State
”。这个状态保存了每个正在运行的循环或函数的状态,并为每个人设置了超时,然后继续到下一个……这样它就可以处理多个段,每个段都有延迟。当您阅读并应用这项技术时,您可以在 Arduino 中以这种方式运行代码。
使用此代码,您可以例如:使用 Arduino 引脚,读取/写入并行循环,与串行端口等端口读取/写入,或同时运行任何 Arduino 命令,而无需等待任何循环完成。
核心思想
代码基于 State
结构体,该结构体保存有关任务、循环或函数的信息,例如
任务变量
本地变量(可选)开始时间
任务开始执行的时间结束时间
运行任务并移至下一个任务的结束时间IsEmpty
标识此任务是否正在运行
当代码运行时,我们为每个循环启动此状态,以便我们可以暂停循环(并保留其状态和本地变量),然后运行其他循环,然后回到它并恢复其最后状态,依此类推。
Using the Code
为了提高灵活性,我添加了超时和任务之间延迟的功能,这意味着您可以控制循环何时进行迭代,即使任务正在轮到执行,方法是:Previous Millis 和 current Millis。
如前所述,此代码基于主结构“state
”。
代码分为四个部分
- 第一部分 - 任务状态结构
- 第二部分 - 示例函数 包含使用 State 结构的循环
- 第三部分 - 调用函数 将状态启用的函数调用到 Arduino 主循环中
- 第四部分 - (可选)串行示例,在运行 State 启用函数时添加监听串行端口
第一部分:任务状态结构
结构 State 成员
变量
主要包含运行时间和循环运行持续时间以及暂停其他代码段的持续时间,还有一个非常重要的变量:(Is Empty),这是一个标志,用于检测函数是第一次运行还是后续运行,这使我们能够在运行状态之前初始化值。
推荐变量,通常操作包含循环(i
或 j
)或两者都有,如果有,我们也可以在状态中声明变量。
完整初始化代码
///////////// Timer related
long OnTime = 20; // it the time to start working in the task milliseconds of on-time
long OffTime = 20; // Duration of execution time for this task to move to the next.
//long OffTime=100; // milliseconds of off-time
unsigned long previousMillis; // Will store last time led was updated ..
int iloopState = 0; // i loop (Recommended )
int jloopState = 0; // j loop (recommended)
unsigned long currentMillis = millis(); // Current time by Millisecond.
bool IsEmpty = true; A flag that process started or not yet ,
State 成员函数
主要包含三个函数
KeepState()
保存运行函数的状态直到下一轮RestoreSession()
恢复值以继续执行reset()
将状态重置为初始值
函数体
void keepState(int looperi = 0, int looperj = 0)
{
IsEmpty = false;
iloopState = looperi;
jloopState = looperj;
previousMillis = millis(); // will store last time led was updated
}
void restoreSession()
{
currentMillis = millis();
}
void reset(LightingMood mood = liIdel)
{
iloopState = 0;
jloopState = 0;
IsEmpty = true;
}
第二部分:示例函数
在此示例中,我们将同时闪烁两个 LED,并在它们之间设置一些延迟,以显示状态工作正常。
主要地,函数将通过恢复执行状态开始,然后执行一些工作,并保留会话状态和本地变量以供下次运行,然后退出函数。这是一个用于阐明想法的示例图。
检查 Empty(IsEmpty
)也很重要,以便在第一次运行时初始化状态变量。
这里有一个使用状态的完整示例,并通过 State 方式运行闪烁示例,以及其他代码。使用结构体闪烁 LED:首先,我们在使用它之前定义一个状态。
state blinkState; //declare state structure for the blink Function use.
然后,在函数体中使用它。
void blink()
{
if (blinkState.IsEmpty == true)
{
On(); //Set the led to custom function you declare like On()
//or by built-in functions for example : digitalWrite(LEDnumber,HIGH)
blinkState.iloopState = 1; //we use the iloopsstate like
//identify On/off state ( 1 = On , 0= off)
blinkState.keepState(blinkState.iloopState);// store the iloopState
// (1 in this case) to the state, save it and return
return; //return to continue other work.
}
if (blinkState.iloopState==1)// check if the led is on,
// we should check for time to off, so we can turn it off or else return.
{
if (millis() < blinkState.previousMillis +
blinkState.OnTime) return; //check for the state time if the current time
//exceeds the time to make the led on so we should switch it off
//or else return to run other functions until this time come.
Off(); //Set the led to custom function you declare like Off()
//or by built-in functions for example : digitalWrite(LEDnumber,LOW)
blinkState.iloopState = 0; // set the current state to off.
blinkState.keepState(blinkState.iloopState); // keep or save the state.
return;//return to continue other work.
}
if (blinkState.iloopState==0) // Here the state started and the led is off,
// then we should check for time to turn it off or else return.
{
if (millis() < blinkState.previousMillis + blinkState.OffTime) return;
On(); //Set the led to custom function you declare like On() or by built-in functions for example
blinkState.iloopState = 1; //set the state that the led is on.
blinkState.keepState(blinkState.iloopState); ; // keep or save the state.
return;
}
};
多个 State 函数
要创建多个状态启用的函数,您必须为每个人声明一个状态,例如
state blinkState; // declare state structure for the blink Function use.
state blink2State; // to use in another function...
// then you can Change data and timing for blink2State :
blink2State.OffTime = 10; // and so on...
第三部分:调用函数
要在主 Arduino 循环中调用此函数,您可以像这样调用 blink 函数,这意味着循环将执行 blink()
之前和之后的所有语句,而不会卡在闪烁循环中。
void loop() {
// statements ...
blink(); // this function will blink the leds without blocking
// other statements in the main loop
//statements ...
}
对于一个以上的函数,我们可以这样一起运行它们。
void loop() {
// statements ...
blink(); //this function will blink the leds without blocking
//other statements in the main loop
blink2(); // you create another one using blink2State
//statements ...
}
第四部分:串行示例
您可以使用此示例函数以相同的方式执行串行 Read
。
// define serial global timing variables:
unsigned long lastMillis = millis();
unsigned long prevTimeOut = millis();
int timeOut = 3000;
void serialLoop()
{
if (unsigned long currentTime = millis() > prevTimeOut +timeOut)
{
//////////// try to Read
while (Serial.available())
{
Serial.println("Serial Available : ");
Serial.println();
x = (char)Serial.read();
}
prevTimeOut = millis();
Serial.println("Time Out");
}
else
{
}
/////////////time out
}
在主循环中运行其他函数的同时添加串行监听
void loop() {
// statements ...
blink(); // this function will blink the leds without blocking
// other statements in the main loop
serialLoop(); // This will be called every 3 seconds
// (can be modified using timeOut variable)
//statements ...
}
关注点
通过这个技巧,您可以绕过并强制 Arduino 打破循环和函数之间的时间间隔,而无需等待一个函数完成任务或循环。
历史
您可以添加任意数量的任务:例如灯光输入输出的循环,同时闪烁 LED 和 LED 条带,方法与此相同。我将在 GitHub 上添加带有更多功能的代码:GitHub ArduinoMultithreading。