目录
介绍
这篇文章的目的是解释如何将变量作为实现FreeRTOS任务的函数的参数传递。为了理解这段代码,我们先分析变量范围的概念。
虽然不是强制性的,但是对C指针的知识有助于理解代码及其工作原理。
变量范围
要理解我们要创建的代码,了解变量范围的概念非常重要。
因此,全局变量是一个变量,可以被我们程序的任何函数看到和访问。在常规的Arduino代码中,这些是我们在代码中创建的设置,循环和自定义函数之外声明的变量。全局变量范围扩展到程序的整个执行,无论我们是否在函数内部使用它们。
相反,局部变量是在函数内声明的变量,只能在该函数内使用和执行。因此,除非我们专门为变量分配内存位置(使用malloc),否则在执行函数后局部变量会丢失,因为它们是在堆栈中分配的。
这意味着如果我们在Arduino设置函数上声明一个变量,一旦该函数结束,该变量就不再有效。因此,如果我们尝试在setup函数范围之外访问该变量的内存位置,则这将是未定义的,这意味着我们无法预测它的值。也许记忆位置仍然具有原始值或者可能还有其他值,但重要的是我们不知道,我们不应该这样使用它。
因此,在将参数传递给任务时我们需要小心,因为我们基本上将指针传递给变量的内存位置。因此,当我们访问FreeRTOS任务中的变量时,我们需要确保内存位置仍然有效。
因此,例如,如果我们在setup函数中声明一个值为5的变量,启动一个任务并向它传递一个指向该内存位置的指针,然后设置函数结束,我们将访问我们任务中的无效内存位置。同样,我们可能很幸运,记忆位置仍然保持正确的值,但这种值是不确定的,我们不应该这样做。
因此,在将参数传递给我们的任务时需要小心,以避免这类问题,这些问题很难调试,因为代码编译得很好并且不会出错。
当然,如果我们保证变量在任务执行期间有效,那么就没有问题。
例如,如果我们使用全局变量,那么它也应该正常工作,因为如前所述,它的范围包括整个程序的执行。
设置代码
我们要做的第一件事是声明一个int类型的全局变量,并为其赋值。我们将赋值5。由于这是一个全局变量,其范围扩展到整个程序执行:
int globalIntVar = 5;
然后,在初始化函数钟,我们将启动串行连接,设置好波特率112500。这将允许我们将我们程序的结果输出到Arduino IDE串串口监视器上,这是使用FreeRTOS和Arduino环境的一大优势:
Serial.begin(112500); delay(1000);
之后,我们将创建一个任务。请检查此之前的任务需要创建与FreeRTOS的任务中的所有细节和参数。我们将为后面的实现函数编写代码。现在,我们将专注于向任务传递一个参数,该参数将是我们先前声明的全局变量。
所以在这里,我们需要传递一个指针为void,这相当于做(void*) 。请注意,指针是具有其他变量的地址的变量。因此,由于我们的任务收到一个指向void的指针,我们不会传递globalIntVar(即5)的值,而是传递它的内存位置。
因此,要获取变量的内存位置(或地址),我们使用&运算符[4]。要获取var的地址,正确的代码是&globalIntVar。在此之前,我们需要强制转换为(void *):
(void*)&globalIntVar;
检查如何创建任务,将参数作为参数传递给我们的全局变量地址:
xTaskCreate(
globalIntTask, /* Task function. */"globalIntTask", /* String with name of task. */10000, /* Stack size in words. */
(void*)&globalIntVar, /* Parameter passed as input of the task */1, /* Priority of the task. */NULL); /* Task handle. */
正如我们将在函数的实现中看到的那样,我们将有一种机制将通用void指针转换为整数指针并访问其内存位置的值。
现在,为了说明将具有局部作用域的变量传递给任务的问题,我们将在setup函数中声明一个变量并创建一个不同的任务,将其地址作为参数传递。前面提到的指针程序将是相同的,如下所示。
int localIntVar = 9;
xTaskCreate(
localIntTask, /* Task function. */
"localIntTask", /* String with name of task. */
10000, /* Stack size in words. */
(void*)&localIntVar, /* Parameter passed as input of the task */
1, /* Priority of the task. */
NULL); /* Task handle. */
任务代码
这两个任务的功能都将使用类似的代码实现。唯一的区别是我们将打印不同的字符串以了解哪个字符串正在访问局部变量和全局变量。如果您需要有关如何创建实现功能的更多信息,请查看上一篇文章。
由于代码非常简单,我们将专注于棘手的部分,这两个函数都是相同的。因此,正如我们所说,任务函数接收一个通用(void *)参数。但是,在我们的代码中,我们现在需要将它解释为指向int的指针,它对应于(int *)。所以,我们做的第一件事是转换为(int *):
(int *) parameter;
现在我们有一个指向整数内存位置的指针。不过,我们想要访问内存位置的实际内容。所以,我们想要指针指向的内存位置的值。为此,我们使用deference运算符,它是* 。
所以,我们需要做的是在转换后的指针上使用deference运算符,我们应该能够访问它的值:
*((int *) parameter);
一旦我们可以访问它,我们就可以使用Serial.println函数来打印它,这是我们在两个函数中要做的事情。因此,可以看到完整的源代码,以及两个函数的实现。请注意,出于调试目的,我们在函数中打印不同的字符串:
测试结果
要测试程序结果,只需上传它并打开Arduino IDE的串口监视器即可。您应该获得两个任务的打印输出,如图1所示:
图1 – 程序的输出。
请注意,接收指向全局变量的指针的任务打印的值是正确的,等于5。
另一方面,接收指向局部变量的指针的任务打印的值对应于0,这是错误的,因为我们在设置函数中为其赋值9。发生这种情况是因为一旦设置功能完成,该变量就不再有效,其他值可以写入其内存位置。
即使它的值为9,代码也是错误的,因为我们访问的是一个未定义的值,运气仍然在设置函数中分配了值。
最后的笔记
在这个简单的代码中,我们将全局变量作为函数的参数传递,作为我们知道变量仍然有效的示例。当然,直接访问它会容易得多,因为它是全局的,可以在函数内部访问。
还有其他方法可以保证这种一致性,例如为变量显式分配内存或维护创建任务活动的函数。