微软的 Azure RTOS ThreadX 是开源的!我们想向您展示 ThreadX 的基础知识,以便您可以开始在您的 Arduino 项目中使用这个工业级 RTOS。
预计时间:设置:5 分钟;第 1 部分:5 分钟;第 2 部分:30 分钟;第 3 部分:15 分钟
估计成本:配备ATSAMD21 或 ATSAMD51芯片的设备 $
本教程将向您展示如何在 Azure RTOS ThreadX for Arduino 中使用多线程。您将从经典的 Blink 示例开始,并将其转换为 ThreadX 应用程序。
Azure RTOS:用于微控制器 (MCU) 上的嵌入式 IoT 应用程序的 Microsoft 开发套件。Azure RTOS不需要Azure即可运行。
Azure RTOS ThreadX:Azure RTOS 产品的一个组件。ThreadX是设计用于在 MCU 上运行的实时操作系统 (RTOS)。
Azure RTOS ThreadX for Arduino:Azure RTOS ThreadX 作为库到 Arduino 的端口。请访问GitHub 上的AzureRTOS-ThreadX-For-Arduino获取源代码。
在本教程结束时,您应该了解以下内容:
术语:内核、线程、线程控制块、优先级、抢占、抢占阈值
Actions :如何使用 ThreadX 实现单线程;如何使用 ThreadX 实现多线程
最终代码:在GitHub 上查看完整的 ThreadX 多线程 Blink 代码示例。
以下是在 Windows 11、Arduino IDE 1.8.19、Arduino MKR WiFi 1010 和 Seeed Studio Wio 终端上运行的。
预计时间: 5 分钟
步骤 1.打开 Arduino IDE。
步骤 2.安装 Azure RTOS Arduino 库。
步骤 3.为您的设备安装板包。(本示例使用 Arduino MKR WiFi 1010。)
在本节中,我们将运行传统的 Blink 示例以确认设备设置正确。
预计时间: 5 分钟
步骤 1.打开 Blink 示例。
第 2 步。连接您的设备。
步骤 3.运行示例。
代码
// the setup function runs once when you press reset or power the board
void setup() {
// initialize digital pin LED_BUILTIN as an output.
pinMode(LED_BUILTIN, OUTPUT);
}
// the loop function runs over and over again forever
void loop() {
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}
到底是怎么回事?
Arduino 利用了两个核心功能:setup()
和loop()
. 一旦setup()
完成,loop()
就会在内部启动并运行程序的其余部分。因为不存在 RTOS,所以这段代码可以被认为是裸机编程。
有关更多信息,请参阅完整的 Arduino Blink示例。
在本节中,我们将使用 ThreadX 将裸机 Blink 示例转换为单线程 RTOS 版本。
预计时间: 30 分钟
步骤 1.保存示例。
步骤 2. (1) 在文件顶部附近添加 Azure RTOS ThreadX 库头文件。tx_api.h
将它放在评论之后,但在setup()
功能之前。
/* (1) Add the Azure RTOS ThreadX library header file. */
#include
到底是怎么回事?
tx_api.h
是您在 Arduino 中使用 ThreadX 时唯一需要包含的头文件。tx
是 ThreadX 的缩写。API 中的所有函数都以tx
. 所有常量和数据类型都以TX
.
步骤 3. (2) 将内核入口函数添加tx_kernel_enter()
到setup()
.
// the setup function runs once when you press reset or power the board
void setup() {
// initialize digital pin LED_BUILTIN as an output.
pinMode(LED_BUILTIN, OUTPUT);
/* (2) Add the kernel entry function. */
tx_kernel_enter();
}
到底是怎么回事?
内核是 RTOS 的核心组件。将其视为项目的首席协调员或物流总监。通过“进入”内核,RTOS 内核可以开始运行和管理您的嵌入式应用程序。
该程序永远不会从tx_kernel_enter()
. 结果,应用程序将不会返回setup()
,loop()
也不会被调用。
重要提示:“对 tx_kernel_enter() 的调用不会返回,因此不要在其后进行任何处理。”
有关. _ _tx_kernel_enter()
Step 4. (3) 添加线程栈内存和线程控制块。将其放置在文件顶部附近之后#include
和之前setup()
。
/* (3) Add the thread stack memory and thread control block. */
#define THREAD_STACK_SIZE 512
TX_THREAD thread_0;
UCHAR thread_0_stack[THREAD_STACK_SIZE];
到底是怎么回事?
线程是进程(即正在运行的应用程序)内的特定执行路径。线程与其他线程共享内存空间,但有自己分配的堆栈空间。我们将此堆栈大小定义为THREAD_STACK_SIZE
字节并使用数组thread_0_stack
来分配内存。有关线程堆栈区域的更多信息,请参阅Microsoft Learn 的 ThreadX 第 3 章:ThreadX 的功能组件。
线程控制块包含线程的特定数据。TX_THREAD
是线程控制块的 ThreadX 数据类型。有关. _ _TX_THREAD
重要提示:“ThreadX 不使用术语任务。相反,使用更具描述性和现代性的名称线程。” 有关任务与线程的更多信息,请参阅Microsoft Learn 的 ThreadX 第 1 章:ThreadX 简介。
Step 5. (4) 定义线程的入口函数thread_0_entry()
。将函数定义放在thread_0_stack
数组之后和之前setup()
。
/* (4) Define the thread's entry function. */
void thread_0_entry(ULONG thread_input)
{
(VOID)thread_input;
while(1)
{
/* Add thread logic to execute here. */
}
}
到底是怎么回事?
线程的入口函数由内核调用,包含线程执行逻辑。通常,此函数将包含一个无限循环(即while(1)
),它将在整个运行程序中执行。此函数的名称由用户确定。
步骤 6. (5) 将 LED 闪烁逻辑从loop()
线程的入口函数中移出。替换delay(1000)
为tx_thread_sleep(TX_TIMER_TICKS_PER_SECOND)
。
void thread_0_entry(ULONG thread_input)
{
(VOID)thread_input;
while(1)
{
/* (5) Move the LED blink logic into the thread's entry function. */
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on
tx_thread_sleep(TX_TIMER_TICKS_PER_SECOND); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off
tx_thread_sleep(TX_TIMER_TICKS_PER_SECOND); // wait for a second
}
}
// the loop function runs over and over again forever
void loop() {
/* (5) Move the LED blink logic into the thread's entry function. */
/* This will never be called. */
}
到底是怎么回事?
因为loop()
将不再被调用,所以必须将闪烁逻辑移到新线程中。该delay()
函数有局限性,因为我们稍后要暂停线程以允许其他线程执行,我们将使用 ThreadX 的tx_thread_sleep()
函数来代替。此函数将计时器滴答作为其参数,而不是毫秒。
Step 7. (6) 添加应用程序的环境设置功能tx_application_define()
。将此函数放置在 之后thread_0_entry()
和之前setup()
。
/* (6) Add the application's environment setup function. */
void tx_application_define(void *first_unused_memory)
{
(VOID)first_unused_memory;
/* Put system definition stuff in here, e.g. thread creates and other assorted
create information. */
}
到底是怎么回事?
内核入口函数tx_kernel_enter()
将调用该函数tx_application_define()
来设置应用程序环境和系统资源。用户有责任使用为 RTOS 环境创建系统资源的逻辑来实现此功能。
有关. _ _tx_application_define()
第 8 步。(7) 使用 . 创建线程tx_thread_create()
。将此函数调用添加到tx_application_define()
.
void tx_application_define(void *first_unused_memory)
{
(VOID)first_unused_memory;
/* Put system definition stuff in here, e.g. thread creates and other assorted
create information. */
/* (7) Create the thread. */
tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0,
thread_0_stack, THREAD_STACK_SIZE,
1, 1, TX_NO_TIME_SLICE, TX_AUTO_START);
}
到底是怎么回事?
tx_thread_create()
创建具有指定参数的线程。此示例中使用的参数反映以下内容:
&thread_0
: 指向定义的线程控制块的指针。(见步骤 4。)"thread_0"
: 线程名称(即,指向名称的指针)。thread_0_entry
:用户自定义线程入口函数。(见步骤 5。)0
: 线程的入口输入。我们没有利用这个论点。thread_0_stack
: 指向线程堆栈开始的指针。(见步骤 4。)THREAD_STACK_SIZE
:线程堆栈的大小(以字节为单位)。(见步骤 4。)1
: 线程的优先级。1
:线程的抢占阈值。TX_NO_TIME_SLICE
: 时间片被禁用。TX_AUTO_START
: 线程自动启动。线程的优先级有助于线程调度程序确定接下来要执行的线程。一些线程可能对执行更为关键,因此相对于其他线程被赋予更高的优先级。ThreadX 有 32 个默认优先级,从 0 到 31,0 为最高优先级,31 为最低优先级。
抢占是指停止现有线程的执行,以便可以运行更高优先级。调度程序控制这一点,当中断线程完成时,执行返回到暂停的线程。
抢占阈值是 ThreadX 独有的。只有高于此阈值的优先级才能抢占线程。
有关线程执行、线程优先级、线程调度和线程抢占的更多信息,请参阅Microsoft Learn 的 ThreadX 第 3 章:ThreadX 的功能组件。
步骤 9.使用 Azure RTOS ThreadX 运行 Blink 示例。
按照第 2 步。连接您的设备和第 3 步。运行第 1 部分中的示例:运行 Arduino Blink 示例。
代码
/* (1) Add the Azure RTOS ThreadX library header file. */
#include
/* (3) Add the thread stack memory and thread control block. */
#define THREAD_STACK_SIZE 512
TX_THREAD thread_0;
UCHAR thread_0_stack[THREAD_STACK_SIZE];
/* (4) Define the thread's entry function. */
void thread_0_entry(ULONG thread_input)
{
(VOID)thread_input;
while(1)
{
/* (5) Move the LED blink logic into the thread's entry function. */
digitalWrite(LED_BUILTIN, HIGH); /* Turn the LED on. */
tx_thread_sleep(TX_TIMER_TICKS_PER_SECOND); /* Wait for a second. */
digitalWrite(LED_BUILTIN, LOW); /* turn the LED off. */
tx_thread_sleep(TX_TIMER_TICKS_PER_SECOND); /* wait for a second. */
}
}
/* (6) Add the application's environment setup function. */
void tx_application_define(void *first_unused_memory)
{
(VOID)first_unused_memory;
/* Put system definition stuff in here, e.g. thread creates and other assorted
create information. */
/* (7) Create the thread. */
tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0,
thread_0_stack, THREAD_STACK_SIZE,
1, 1, TX_NO_TIME_SLICE, TX_AUTO_START);
}
/* The setup function runs once when you press reset or power the board. */
void setup()
{
/* Initialize digital pin LED_BUILTIN as an output. */
pinMode(LED_BUILTIN, OUTPUT);
/* (2) Add the kernel entry function. */
tx_kernel_enter();
}
void loop()
{
/* (5) Move the LED blink logic into the thread's entry function. */
/* This will never be called. */
}
注意:Arduino Blink 格式和单行样式注释//
已转换为 ThreadX 格式和多行样式/* */
。
到底是怎么回事?
setup()
上面的代码演示了如何用Azure RTOS ThreadX替换 Arduino 裸机单线程方法。loop()
之前的裸机代码流程是:
setup()
-> loop()
-> 无限循环闪烁逻辑。ThreadX 新的代码流程是:
setup()
-> tx_kernel_enter()
-> tx_application_define()
-> thread_0_entry()
-> 无限循环 Blink 逻辑。尽管这种方法仍然保持相同的单线程功能,但现在可以根据需要添加额外的线程。第 3 部分将演示如何执行此操作。
在本节中,我们将使用单线程 ThreadX Blink 代码创建一个多线程版本,该版本也可以读取串行输入。
预计时间: 15 分钟
步骤 1.保存示例。
步骤 2. (8) 添加线程堆栈内存和线程控制块以再增加一个线程。
/* (3)(8) Add the thread stack memory and thread control block. */
#define THREAD_STACK_SIZE 512
TX_THREAD thread_0;
TX_THREAD thread_1;
UCHAR thread_0_stack[THREAD_STACK_SIZE];
UCHAR thread_1_stack[THREAD_STACK_SIZE];
到底是怎么回事?
此操作模仿第 2 部分:第 4 步。对于要添加的每个线程,您需要分配其堆栈内存并声明其线程控制块。 TX_THREAD
Step 3. (9) 定义新线程的入口函数thread_1_entry()
。将函数定义放在 之后thread_0_entry()
和之前tx_application_define()
。
/* (9) Define the thread's entry function. */
void thread_1_entry(ULONG thread_input)
{
(VOID)thread_input;
while(1)
{
/* Add thread logic to execute here. */
}
}
到底是怎么回事?
此操作模仿第 2 部分:第 5 步。每个线程都需要一个用户定义的入口函数来执行线程逻辑。
Step 4. (10) 将串行读取逻辑添加到线程的入口函数中。
void thread_1_entry(ULONG thread_input)
{
(VOID)thread_input;
/* (10) Add serial read logic to the thread's entry function. */
Serial.begin(115200);
while(1)
{
if (Serial.available() > 0)
{
char byte_read = Serial.read();
Serial.print(byte_read);
}
}
}
到底是怎么回事?
串行读取逻辑从串行输入接收字节并将它们打印到串行监视器。请注意,此逻辑放在 中while(1)
,但串行初始化Serial.begin()
函数没有。因为初始化只需要发生一次,所以放在前面while(1)
。或者,它可以放在setup()
之前tx_kernel_enter()
。
第 5 步。(11) 使用 . 创建新线程tx_thread_create()
。在tx_application_define()
创建thread_0
.
void tx_application_define(void *first_unused_memory)
{
(VOID)first_unused_memory;
/* Put system definition stuff in here, e.g. thread creates and other assorted
create information. */
/* (7)(11) Create the thread. */
tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0,
thread_0_stack, THREAD_STACK_SIZE,
1, 1, TX_NO_TIME_SLICE, TX_AUTO_START);
tx_thread_create(&thread_1, "thread 1", thread_1_entry, 0,
thread_1_stack, THREAD_STACK_SIZE,
4, 4, TX_NO_TIME_SLICE, TX_AUTO_START);
}
到底是怎么回事?
此操作模仿第 2部分:第8步。 在所使用的论点中发现了差异。注意命名如何改变以反映: thread_1
&thread_1
: 指向定义的线程控制块的指针。"thread_1"
: 线程名称(即,指向名称的指针)。thread_1_entry
:用户自定义线程入口函数。thread_1_stack
: 指向线程堆栈开始的指针。另一个改变的参数集是优先级和抢占阈值:
4
: 线程的优先级。4
:线程的抢占阈值。
因为4的优先级低于1 ,thread_0
所以将首先执行,并且只有在它挂起时(tx_thread_sleep()
),调度程序才会执行行中的下一个线程(thread_1
)。一旦thread_0
完成暂停,调度程序将抢占thread_1
并返回执行thread_0
。
保持不变的论点是:
0
: 线程的入口输入。THREAD_STACK_SIZE
:线程堆栈的大小(以字节为单位)。TX_NO_TIME_SLICE
: 时间片被禁用。TX_AUTO_START
: 线程自动启动。步骤 6.使用 Azure RTOS ThreadX 运行多线程 Blink 示例。
代码
/* (1) Add the Azure RTOS ThreadX library header file. */
#include
/* (3)(8) Add the thread stack memory and thread control block. */
#define THREAD_STACK_SIZE 512
TX_THREAD thread_0;
TX_THREAD thread_1;
UCHAR thread_0_stack[THREAD_STACK_SIZE];
UCHAR thread_1_stack[THREAD_STACK_SIZE];
/* (4) Define the thread's entry function. */
void thread_0_entry(ULONG thread_input)
{
(VOID)thread_input;
while(1)
{
/* (5) Move the LED blink logic into the thread's entry function. */
digitalWrite(LED_BUILTIN, HIGH); /* Turn the LED on. */
tx_thread_sleep(TX_TIMER_TICKS_PER_SECOND); /* Wait for a second. */
digitalWrite(LED_BUILTIN, LOW); /* Turn the LED off. */
tx_thread_sleep(TX_TIMER_TICKS_PER_SECOND); /* Wait for a second. */
}
}
/* (9) Define the thread's entry function. */
void thread_1_entry(ULONG thread_input)
{
(VOID)thread_input;
/* (10) Add serial read logic to the thread's entry function. */
Serial.begin(115200);
while(1)
{
if (Serial.available() > 0)
{
char byte_read = Serial.read();
Serial.print(byte_read);
}
}
}
/* (6) Add the application's environment setup function. */
void tx_application_define(void *first_unused_memory)
{
(VOID)first_unused_memory;
/* Put system definition stuff in here, e.g. thread creates and other assorted
create information. */
/* (7)(11) Create the thread. */
tx_thread_create(&thread_0, "thread 0", thread_0_entry, 0,
thread_0_stack, THREAD_STACK_SIZE,
1, 1, TX_NO_TIME_SLICE, TX_AUTO_START);
tx_thread_create(&thread_1, "thread 1", thread_1_entry, 0,
thread_1_stack, THREAD_STACK_SIZE,
4, 4, TX_NO_TIME_SLICE, TX_AUTO_START);
}
/* The setup function runs once when you press reset or power the board. */
void setup()
{
/* Initialize digital pin LED_BUILTIN as an output. */
pinMode(LED_BUILTIN, OUTPUT);
/* (2) Add the kernel entry function. */
tx_kernel_enter();
}
void loop()
{
/* (5) Move the LED blink logic into the thread's entry function. */
/* This will never be called. */
}
注意:Arduino Blink 格式和单行样式注释//
已转换为 ThreadX 格式和多行样式/* */
。
到底是怎么回事?
上面的代码演示了如何使用 Azure RTOS ThreadX 向 Arduino 应用程序添加额外的线程。一个线程使 LED 闪烁,而另一个线程接收串行输入并将其打印到串行监视器。线程对于同时且彼此独立地执行不同的任务非常有帮助。
尽管本教程中没有演示,但线程也可以通过同步和相互通信。当程序需要随时接收传入数据但也需要对其进行处理时,这可能很有用。这两个任务可以分成两个线程。
我们希望在未来的 Azure RTOS ThreadX for Arduino 教程中介绍这些和其他概念。敬请关注!
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
全部0条评论
快来发表一下你的评论吧 !