[活动] 极海半导体-G32A1465开发板测评 03-RT-ThreadRT-Thread线程管理测试

[复制链接]
1102|1
 楼主| lemonhub 发表于 2025-1-19 18:48 | 显示全部楼层 |阅读模式
本帖最后由 lemonhub 于 2025-1-19 19:22 编辑

极海半导体-G32A1465开发板测评 03-RT-ThreadRT-Thread线程管理测试
实验目的
  • 学习使用FinSH命令行工具使用,掌握自定义FinSH命令的流程。
  • 了解线程的概念、学习线程调度的机制、掌握线程的管理方式、掌握线程管理的常用代码。

准备目标硬件(开发板/芯片/模组)
本教程将使用极海G32A1465开发板进行示例移植。调试ARM Cortex M核还需要仿真器,如果您的开发板或者芯片模组没有板载仿真器,就需要连接外置的仿真器,如DAPLink]、JLINK之类的。
g32a-02.jpg
FINSH命令行控制工具
在官方提供的RT-Thread工程模板中,已经移植好了finsh组件,直接使用即可,如果需要移植Finsh组件请参考官方教程。
FinSH是RT-Thread的命令行组件,提供一套供用户在命令行调用的操作接口,主要用于调试或查看系统信息。用户在控制终端输入命令,控制终端通过串口、USB、网络等方式将命令传给设备里的FinSH,FinSH会读取设备输入命令,解析并自动扫描内部函数表,寻找对应函数名,执行函数后输出回应,回应通过原路返回,将结果显示在控制终端上。
FinSH内置命令
在RT-Thread中默认内置了一些FinSH命令,在FinSH中输入help后回车或者直接按下Tab键,就可以打印当前系统支持的所有命令。常用的命令如下:
g32a-thread-01.jpg
g32a-thread-02.jpg 自定义FinSH命令FinSH功能配置
FinSH功能可以裁剪,宏配置选项在rtconfig.h文件中定义,具体配置项如下表所示。在实践开发中可以根据需求进行配置。
g32a-thread-03.jpg
自定义FinSH命令
除了FinSH自带的命令,FinSH还也提供了多个宏接口来导出自定义命令,导出的命令可以直接在FinSH中执行。
RT-Thread同时支持自定义msh命令和自定义C-Style命令,自定义msh命令使用的更加广泛。
自定义的FinSH命令,可以在msh模式下运行,将一个命令导出到msh模式可以使用如下宏接口如下:
MSH_CMD_EXPORT:这个命令可以导出有参数的命令,也可以导出无参数的命令。
  1. /************************************************************************************************
  2. * 名称:MSH_CMD_EXPORT
  3. * 功能:自定义msh命令导出
  4. * 参数:name表示要导出的命令
  5. * 参数:desc表示导出命令的描述
  6.   ************************************************************************************************/
  7. MSH_CMD_EXPORT(name, desc);

  8. 导出无参数命令时,函数的入参为void,示例如下:
  9. #include <rtthread.h>
  10. void hello(void)
  11. {
  12.     rt_kprintf("hello RT-Thread!\n");
  13. }
  14. MSH_CMD_EXPORT(hello , say hello to RT-Thread);
导出有参数的命令时,函数的入参为int argc和char**argv。argc表示参数的个数,argv表示命令行参数字符串指针数组指针。导出有参数命令示例如下:
  1. #include <rtthread.h>

  2. static void atcmd(int argc, char**argv)
  3. {
  4.     if (argc < 2)
  5.     {
  6.         rt_kprintf("Please input'atcmd <server|client>'\n");
  7.         return;
  8.     }

  9.     if (!rt_strcmp(argv[1], "server"))
  10.     {
  11.         rt_kprintf("AT server!\n");
  12.     }
  13.     else if (!rt_strcmp(argv[1], "client"))
  14.     {
  15.         rt_kprintf("AT client!\n");
  16.     }
  17.     else
  18.     {
  19.         rt_kprintf("Please input'atcmd <server|client>'\n");
  20.     }
  21. }

  22. MSH_CMD_EXPORT(atcmd, atcmd sample: atcmd <server|client>);
自定义FINSH命令测试案例
要实现自定义FinSH命令,第一,要了解如何定义命令并将命令添加到FinSH命令列表;第二,为了让命令效果更加明显,使用命令控制信号灯。将自定义的FinSH命令和信号灯控制结合起来可以实现联动控制,从而达到项目效果。
首先自定义FinSH命令,实现对信号灯的开关控制。然后在主线程中进行信号灯引脚初始化,使用FinSH控制台输入命令之后,进行命令解析并控制相关的信号灯。
程序设计流程如下:
1)首先进行LED引脚初始化,配置相关GPIO引脚的模式和初始电平;
2)然后编写自定义FinSH命令函数,当在MobaXterm串口终端FinSH控制台输入:
“ledON r” 时点亮红色灯,“ledON g” 时点亮绿色灯,“ledON b” 时点亮蓝色灯,输入“ledON all”时亮所有灯,输入“ledOFF”时熄灭灯。
输入“led_flow”开始流水灯效果,输入“led_flow_stop”停止流水灯效果。
g32a-thread-05.jpg
测试函数
  1. // LED 控制函数
  2. int ledON(int argc, char **argv)
  3. {
  4.     if (argc < 2)
  5.     {
  6.         rt_kprintf("Usage: ledON <r|g|b>\n");
  7.         return -1;
  8.     }

  9.     if (!rt_strcmp(argv[1], "r")) // 打开红灯
  10.     {
  11.         LED_On(LED_RED);
  12.                 LED_Off(LED_BLUE);
  13.               LED_Off(LED_GREEN);
  14.         rt_kprintf("Red LED ON!\n");
  15.     }
  16.     else if (!rt_strcmp(argv[1], "g")) // 打开绿灯
  17.     {
  18.         
  19.               LED_On(LED_GREEN);
  20.               LED_Off(LED_BLUE);
  21.               LED_Off(LED_RED);
  22.         rt_kprintf("Green LED ON!\n");
  23.     }
  24.     else if (!rt_strcmp(argv[1], "b")) // 打开蓝灯
  25.     {
  26.         LED_On(LED_BLUE);
  27.                 LED_Off(LED_RED);
  28.               LED_Off(LED_GREEN);
  29.         rt_kprintf("Blue LED ON!\n");
  30.     }
  31.         else if(!rt_strcmp(argv[1], "all"))
  32.         {
  33.                 LED_On(LED_BLUE);
  34.                 LED_On(LED_RED);
  35.               LED_On(LED_GREEN);
  36.         rt_kprintf("All LEDs ON!\n");
  37.         }
  38.             
  39.     else
  40.     {
  41.         rt_kprintf("Invalid option! Usage: ledON <r|g|b>\n");
  42.         return -1;
  43.     }

  44.     return 0;
  45. }

  46. // 关闭所有 LED 的函数
  47. int ledOFF(int argc, char **argv)
  48. {
  49.     board_led_turnoff(); // 关闭所有 LED
  50.     rt_kprintf("All LEDs OFF!\n");
  51.     return 0;
  52. }

  53. // 导出命令到 RT-Thread Shell
  54. MSH_CMD_EXPORT(ledON, Turn on specific LED (r: red, g: green, b: blue));
  55. MSH_CMD_EXPORT(ledOFF, Turn off all LEDs);


  56. // 流水灯状态
  57. static int led_flow_state = 0; // 当前点亮的 LED 状态
  58. static rt_timer_t flow_timer = RT_NULL; // 定时器句柄

  59. // 流水灯定时器回调函数
  60. static void led_flow_timer_callback(void *parameter)
  61. {
  62.     // 根据当前状态切换 LED
  63.     switch (led_flow_state)
  64.     {
  65.     case 0:
  66.         LED_On(LED_RED);     // 点亮红灯
  67.         LED_Off(LED_GREEN);  // 关闭绿灯
  68.         LED_Off(LED_BLUE);   // 关闭蓝灯
  69.         led_flow_state = 1;  // 切换到下一个状态
  70.         break;
  71.     case 1:
  72.         LED_On(LED_GREEN);   // 点亮绿灯
  73.         LED_Off(LED_RED);    // 关闭红灯
  74.         LED_Off(LED_BLUE);   // 关闭蓝灯
  75.         led_flow_state = 2;  // 切换到下一个状态
  76.         break;
  77.     case 2:
  78.         LED_On(LED_BLUE);    // 点亮蓝灯
  79.         LED_Off(LED_RED);    // 关闭红灯
  80.         LED_Off(LED_GREEN);  // 关闭绿灯
  81.         led_flow_state = 0;  // 切换到下一个状态
  82.         break;
  83.     default:
  84.         led_flow_state = 0;  // 重置状态
  85.         break;
  86.     }
  87. }

  88. // 启动流水灯效果
  89. int led_flow(int argc, char **argv)
  90. {
  91.     // 检查是否已经存在定时器
  92.     if (flow_timer != RT_NULL)
  93.     {
  94.         return -1; // 流水灯效果已经在运行
  95.     }

  96.     // 创建定时器
  97.     flow_timer = rt_timer_create(
  98.         "led_flow",                  // 定时器名称
  99.         led_flow_timer_callback,     // 回调函数
  100.         RT_NULL,                     // 回调函数参数
  101.         500,                         // 定时器周期(500ms)
  102.         RT_TIMER_FLAG_PERIODIC       // 周期性定时器
  103.     );

  104.     // 检查定时器是否创建成功
  105.     if (flow_timer == RT_NULL)
  106.     {
  107.         return -1; // 定时器创建失败
  108.     }

  109.     // 启动定时器
  110.     rt_timer_start(flow_timer);
  111.     return 0; // 流水灯效果启动成功
  112. }

  113. // 停止流水灯效果
  114. int led_flow_stop(int argc, char **argv)
  115. {
  116.     // 检查定时器是否存在
  117.     if (flow_timer == RT_NULL)
  118.     {
  119.         return -1; // 流水灯效果未运行
  120.     }

  121.     // 停止并删除定时器
  122.     rt_timer_stop(flow_timer);
  123.     rt_timer_delete(flow_timer);
  124.     flow_timer = RT_NULL;

  125.     // 关闭所有 LED
  126.     LED_Off(LED_RED);
  127.     LED_Off(LED_GREEN);
  128.     LED_Off(LED_BLUE);

  129.     return 0; // 流水灯效果停止成功
  130. }

  131. // 导出命令到 RT-Thread Shell
  132. MSH_CMD_EXPORT(led_flow, Start LED flow effect);
  133. MSH_CMD_EXPORT(led_flow_stop, Stop LED flow effect);
测试终端效果
g32a-thread-04.jpg
测试实物效果


Thread 线程管理
线程管理简介
        在多线程操作系统中,也同样需要开发人员把一个复杂的应用分解成多个小的、可调度的、序列化的程序单元,当合理地划分任务并正确地执行时,这种设计能够让系统满足实时系统的性能及时间的要求。例如让嵌入式系统执行这样的任务,系统通过传感器采集数据,并通过显示屏将数据显示出来,在多线程实时系统中,可以将这个任务分解成两个子任务,如下图所示,一个子任务不间断地读取传感器数据,并将数据写到共享内存中,另外一个子任务周期性的从共享内存中读取数据,并将传感器数据输出到显示屏上。
g32a-thread-06.jpg
在RT-Thread中,与上述子任务对应的程序实体就是线程,线程是实现任务的载体,它是RT-Thread中最基本的调度单位,它描述了一个任务执行的运行环境,也描述了这个任务所处的优先等级,重要的任务可设置相对较高的优先级,非重要的任务可以设置较低的优先级,不同的任务还可以设置相同的优先级,轮流运行。
线程调度机制
RT-Thread线程管理的主要功能是对线程进行管理和调度,系统中总共存在两类线程,分别是系统线程和用户线程,系统线程是由RT-Thread内核创建的线程,用户线程是由应用程序创建的线程,这两类线程都会从内核对象容器中分配线程对象,当线程被删除时,也会被从对象容器中删除,每个线程都有重要的属性,如线程控制块、线程栈、入口函数等。
g32a-thread-07.jpg
RT-Thread的线程调度器是抢占式的,主要的工作就是从就绪线程列表中查找最高优先级线程,保证最高优先级的线程能够被运行,最高优先级的任务一旦就绪,总能得到CPU的使用权
当一个运行着的线程使一个比它优先级高的线程满足运行条件,当前线程的CPU使用权就被剥夺了,或者说被让出了,高优先级的线程立刻得到了CPU的使用权。
如果是中断服务程序使一个高优先级的线程满足运行条件,中断完成时,被中断的线程挂起,优先级高的线程开始运行。
当调度器调度线程切换时,先将当前线程上下文保存起来,当再切回到这个线程时,线程调度器将该线程的上下文信息恢复。
线程管理方式
创建线程
一个线程要成为可执行的对象,就必须由操作系统的内核来为它创建一个线程。可以通过如下的接口创建一个动态线程:
  1. /************************************************************************************************
  2. * 名称:rt_thread_create()
  3. * 功能:创建线程
  4. * 参数:name表示线程的名称
  5. * 参数:entry表示线程入口函数
  6. * 参数:parameter表示线程入口函数参数
  7. * 参数:stack_size表示线程栈大小,单位是字节
  8. * 参数:priority表示线程的优先级
  9. * 参数:tick表示线程的时间片大小
  10. * 返回:thread表示线程创建成功,返回线程句柄;RT_NULL表示线程创建失败
  11. ************************************************************************************************/
  12. rt_thread_t rt_thread_create(const char* name,
  13.                             void (*entry)(void* parameter),
  14.                             void* parameter,
  15.                             rt_uint32_t stack_size,
  16.                             rt_uint8_t priority,
  17.                             rt_uint32_t tick);
删除线程
对于一些使用rt_thread_create()创建出来的线程,当不需要使用,或者运行出错时,我们可以使用下面的函数接口来从系统中把线程完全删除掉:
  1. /************************************************************************************************
  2. * 名称:rt_thread_delete()
  3. * 功能:删除线程
  4. * 参数:thread表示要删除的线程句柄
  5. * 返回:RT_EOK表示删除线程成功,-RT_ERROR表示删除线程失败
  6. ************************************************************************************************/
  7. rt_err_t rt_thread_delete(rt_thread_t thread);
启动线程
创建(初始化)的线程状态处于初始状态,并未进入就绪线程的调度队列,我们可以在线程初始化创建成功后调用下面的函数接口让该线程进入就绪态:
  1. /************************************************************************************************
  2. * 名称:rt_thread_startup()
  3. * 功能:启动线程
  4. * 参数:thread表示线程句柄
  5. * 返回:RT_EOK表示线程启动成功,-RT_ERROR表示线程启动失败
  6. ************************************************************************************************/
  7. rt_err_t rt_thread_startup(rt_thread_t thread);
挂起线程
当线程调用rt_thread_delay()时,线程将主动挂起;当调用rt_sem_take(),rt_mb_recv()等函数时,资源不可使用也将导致线程挂起。处于挂起状态的线程,如果其等待的资源超时(超过其设定的等待时间),那么该线程将不再等待这些资源,并返回到就绪状态;或者,当其他线程释放掉该线程所等待的资源时,该线程也会返回到就绪状态。
  1. /************************************************************************************************
  2. * 名称:rt_thread_suspend()
  3. * 功能:挂起线程
  4. * 参数:thread表示线程句柄
  5. * 返回:RT_EOK表示线程挂起成功,-RT_ERROR表示线程挂起失败
  6. ************************************************************************************************/
  7. rt_err_t rt_thread_suspend (rt_thread_t thread);
恢复线程
恢复线程就是让挂起的线程重新进入就绪状态,并将线程放入系统的就绪队列中;如果被恢复线程在所有就绪态线程中,位于最高优先级链表的第一位,那么系统将进行线程上下文的切换。线程恢复使用下面的函数接口:
  1. /************************************************************************************************
  2. * 名称:rt_thread_resume()
  3. * 功能:恢复线程
  4. * 参数:thread表示线程句柄
  5. * 返回:RT_EOK表示线程恢复成功,-RT_ERROR表示线程恢复失败
  6. ************************************************************************************************/
  7. rt_err_t rt_thread_resume (rt_thread_t thread);
线程管理API-测试案例
首先进行LED线程初始化工作,创建线程,并设置线程入口函数,使用MobaXterm串口终端FinSH控制台输入命令之后,进行命令解析并执行相关的线程管理。
1)首先创建LED线程,并且在main函数中完成线程初始化工作;
2)然后编写线程入口函数,在线程入口中完成LED引脚初始化等工作;
3)当在MobaXterm串口终端FinSH控制台输入“threadManage start”命令时,启动LED线程,并进入到线程入口函数中循环点亮LED灯。输入“threadManage suspend”命令时,挂起线程,LED灯循环点亮停止;输入“threadManage resume”命令时,恢复线程,LED灯继续循环点亮;输入“threadManage delete”命令时,删除线程,LED循环点亮停止,并且在系统中移除LED线程。程序流程图如下图所示:
g32a-thread-08.jpg
测试函数代码
  1. //FINSH CMD命令行测试
  2. int threadManage(int argc, char **argv)
  3. {
  4.   rt_err_t result = RT_EOK;
  5.   if(led_thread->type == 0)                                     // 判断LED线程是否被删除,避免程序异常
  6.   {
  7.     rt_kprintf("led thread have been removed!\n");
  8.     return 0;
  9.   }
  10.   if(!rt_strcmp(argv[1], "start"))
  11.   {
  12.     result = rt_thread_startup(led_thread);                     // 启动LED线程
  13.     if(result == RT_EOK)
  14.       rt_kprintf("led thread starting success!\n");
  15.     else
  16.       rt_kprintf("led thread failed to start, errCode:%d\n", result);
  17.   }
  18.   else if(!rt_strcmp(argv[1], "suspend"))
  19.   {
  20.     if(suspendFlag == 0)
  21.       suspendFlag = 1;                                          // 置位挂起线程标志位
  22.     if(result == RT_EOK)
  23.       rt_kprintf("led thread suspend success!\n");
  24.     else
  25.       rt_kprintf("led thread suspend failed, errCode:%d\n", result);
  26.   }
  27.   else if(!rt_strcmp(argv[1], "resume"))
  28.   {
  29.     result = rt_thread_resume(led_thread);                      // 恢复线程
  30.     if(result == RT_EOK)
  31.       rt_kprintf("led thread resume success!\n");
  32.     else
  33.       rt_kprintf("led thread resume failed, errCode:%d\n", result);
  34.   }
  35.   else if(!rt_strcmp(argv[1], "delete"))
  36.   {
  37.     result = rt_thread_delete(led_thread);                      // 删除线程
  38.     if(result == RT_EOK)
  39.       rt_kprintf("led thread deletion success!\n");
  40.     else
  41.       rt_kprintf("led thread fail to delete, errCode:%d\n", result);
  42.   }
  43.   else
  44.     rt_kprintf("Please input 'threadManage <start|suspend|resume|delete>'\n");
  45.   return 0;
  46. }
  47. MSH_CMD_EXPORT(threadManage, thread Manage sample);
app_led任务函数
  1. /*********************************************************************************************
  2. * 文件:app_led.h
  3. * 作者:CoderEnd 2025.01.19
  4. * 描述:LED线程源文件
  5. * 修改:
  6. *      
  7. * 注释:
  8. *********************************************************************************************/
  9. #include "main.h"

  10. rt_thread_t led_thread = RT_NULL;
  11. unsigned char suspendFlag = 0;


  12. /*********************************************************************************************
  13. * 名称:led_ctrl()
  14. * 功能:LED控制
  15. * 参数:cmd -> 控制值
  16. * 返回:无
  17. * 修改:
  18. * 注释:
  19. *********************************************************************************************/
  20. void led_ctrl(unsigned char cmd)
  21. {
  22.     PINS_WritePin(LED_GPIO, LED1_PIN_NUM, !(cmd & LED1_NUM));
  23.   PINS_WritePin(LED_GPIO, LED2_PIN_NUM, !(cmd & LED2_NUM));
  24.     PINS_WritePin(LED_GPIO, LED3_PIN_NUM, !(cmd & LED3_NUM));
  25. }
  26. /*********************************************************************************************
  27. * 名称:led_thread_entry()
  28. * 功能:LED线程入口函数
  29. * 参数:*parameter -> 入口参数
  30. * 返回:无
  31. * 修改:
  32. * 注释:
  33. *********************************************************************************************/
  34. static void led_thread_entry(void *parameter)
  35. {
  36.   (void)parameter;
  37.   unsigned char ledState = 0x01;
  38. //  led_pin_init();                                               // LED引脚初始化
  39.   while(1)
  40.   {
  41.     if(suspendFlag == 1)
  42.     {
  43.       rt_thread_suspend(led_thread);                            // 挂起线程
  44.       rt_schedule();
  45.       suspendFlag = 0;
  46.     }
  47.     led_ctrl(ledState);
  48.     if(ledState < 0x08)
  49.       ledState <<= 1;
  50.     else
  51.       ledState = 0x01;
  52.     rt_thread_mdelay(500);                                      // 延时500ms
  53.   }
  54. }

  55. /*********************************************************************************************
  56. * 名称:led_thread_init()
  57. * 功能:LED线程初始化函数
  58. * 参数:无
  59. * 返回:   -1 -> led线程创建失败
  60. *           0 -> led线程创建成功
  61. * 修改:
  62. * 注释:
  63. *********************************************************************************************/
  64. int led_thread_init(void)
  65. {
  66.   led_thread = rt_thread_create("app_led",                          // 线程名称
  67.                                 led_thread_entry,               // 线程入口函数
  68.                                 RT_NULL,                        // 入口函数传入参数
  69.                                 256,                            // 线程堆栈大小
  70.                                 10,                             // 线程优先级
  71.                                 20);                            // 线程时间片
  72.   
  73.   if(led_thread == RT_NULL) return -1;
  74.   
  75.   return 0;
  76. }

  77. /*********************************************************************************************
  78. * 文件:app_led.h
  79. * 作者:CoderEnd 2025.01.19
  80. * 描述:LED驱动及线程头文件
  81. * 修改:
  82. * 注释:
  83. *********************************************************************************************/
  84. #ifndef __APL_LED_H_
  85. #define __APL_LED_H_
  86. #include <rtthread.h>

  87. // LED灯引脚对应序号,请参考 drv_gpio.c 文件
  88. #define LED1_PIN_NUM                  0                         // PD0
  89. #define LED2_PIN_NUM                  15                        // PD15
  90. #define LED3_PIN_NUM                  16                        // PD16

  91. #define LED1_NUM                      0x01
  92. #define LED2_NUM                      0x02
  93. #define LED3_NUM                      0x04

  94. extern rt_thread_t led_thread;
  95. extern unsigned char suspendFlag;

  96. int led_thread_init(void);

  97. #endif

测试终端效果
g32a-thread-09.jpg
g32a-thread-10.jpg
g32a-thread-11.jpg
测试实物效果

深渊之海 发表于 2025-1-20 16:24 | 显示全部楼层
这个内容量很大,讲解的很细致,不错
您需要登录后才可以回帖 登录 | 注册

本版积分规则

20

主题

80

帖子

0

粉丝
快速回复 在线客服 返回列表 返回顶部