硬件:STM32F103ZET6、ST-LINK、usb转串口工具、4个LED灯、1个蜂鸣器、4个1k电阻、2个按键、面包板、杜邦线
文章目录
- 前言
- 一、RT-Thread相关接口函数
- 1. 挂起线程
- 2. 恢复线程
- 二、程序设计
- 1. car_led.c
- 2.car_led.h
- 3. main.c
- 三、程序测试
- 总结
前言
在上一个任务中,通过停止命令把线程删除后,线程在系统中就不存在了,也无法再使线程重新运行。例如输入stop_led_thread命令后,led停止闪烁,但也无法重新开启LED灯闪烁功能。本任务通过修改停止命令的实现代码,同时增加恢复命令,使led灯闪烁功能可以暂停和恢复。
一、RT-Thread相关接口函数
1. 挂起线程
线程挂起是指把线程脱离就绪队列,使线程不参与调度器的调度。线程挂起使用下面的接口函数:
rt_err_t rt_thread_suspend (rt_thread_t thread);
线程挂起接口 rt_thread_suspend() 的参数和返回值见下表:
参数 | 描述 |
---|---|
thread | 线程句柄 |
返回 | RT_EOK: 线程挂起成功,-RT_ERROR: 线程挂起失败,因为该线程的状态并不是就绪状态 |
注:一个线程尝试挂起另一个线程是一个非常危险的行为,因此RT-Thread对此函数有严格的使用限制:该函数只能使用来挂起当前线程(即自己挂起自己),不可以在线程A中尝试挂起线程B。而且在挂起线程自己后,需要立刻调用 rt_schedule() 函数进行手动的线程上下文切换。用户只需要了解该接口的作用即可,不建议在程序中使用该接口。该接口可以视为是内部接口。这是因为A线程在尝试挂起B线程时,A线程并不清楚B线程正在运行什么程序,一旦B线程正在使用影响、阻塞其他线程(如C线程)的内核对象(例如互斥量、信号量等)时,如果此时其他线程也在等待这个内核对象,那么A线程尝试挂起B线程的操作将会引发其他线程(如C线程)的饥饿,严重危及系统的实时性。有些地方会将其描述为死锁,实际上这种现象不是死锁,但是也没有比死锁好到哪去。
当线程调用 rt_thread_delay() 时,线程将主动挂起;当调用 rt_sem_take(),rt_mb_recv() 等函数时,资源不可使用也将导致线程挂起。处于挂起状态的线程,如果其等待的资源超时(超过其设定的等待时间),那么该线程将不再等待这些资源,并返回到就绪状态;或者,当其他线程释放掉该线程所等待的资源时,该线程也会返回到就绪状态。
2. 恢复线程
恢复线程就是让挂起的线程重新进入就绪状态,并将线程放入系统的就绪队列中;如果被恢复线程在所有就绪态线程中,位于最高优先级链表的第一位,那么系统将进行线程上下文的切换。线程恢复使用下面的函数接口:
rt_err_t rt_thread_resume (rt_thread_t thread);
线程恢复接口 rt_thread_resume() 的参数和返回值见下表:
参数 | 描述 |
---|---|
thread | 线程句柄 |
返回 | RT_EOK: 线程恢复成功,-RT_ERROR: 线程恢复失败,因为该个线程的状态并不是 RT_THREAD_SUSPEND 状态 |
二、程序设计
在前一章节的基础上修改car_led.c、car_led.h和main.c三个文件即可,具体如下:
1. car_led.c
增加暂停标志、暂停设置接口,以及在线程中增加暂停执行代码
#include <rtthread.h>
#include <rtdevice.h>
#include "drv_common.h"
#define DBG_TAG "LED"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include <stdlib.h>
#include "car_led.h"
/* 定义左右转向灯的控制引脚 */
#define LedLeft GET_PIN(D, 8)
#define LedRight GET_PIN(D, 9)
#define LedOn(pin) rt_pin_write(pin, PIN_LOW)
#define LedOff(pin) rt_pin_write(pin, PIN_HIGH)
enum led_mode LedMod=LED_MODE_STOP;
void led_set_mode(enum led_mode m)
{
LedMod = m;
}
/* 暂停运行标志变量,0表示运行,1表示暂停 */
static int stopFlag=0;
void led_thread_entry()
{
rt_pin_mode(LedLeft, PIN_MODE_OUTPUT);
rt_pin_mode(LedRight, PIN_MODE_OUTPUT);
while(1){
/*判断是否暂停运行*/
if(stopFlag)
{
rt_thread_suspend(rt_thread_self());//挂起线程,只能自已挂起自已
rt_schedule();//使用suspend挂起线程后,需手动进行线程上下文切换
}
switch(LedMod)
{
case LED_MODE_STOP:
LedOff(LedLeft);
LedOff(LedRight);
break;
case LED_MODE_Double:
LedOn(LedLeft);
LedOn(LedRight);
rt_thread_mdelay(500);
LedOff(LedLeft);
LedOff(LedRight);
break;
case LED_MODE_LEFT:
LedOff(LedRight);
LedOn(LedLeft);
rt_thread_mdelay(250);
LedOff(LedLeft);
break;
case LED_MODE_RIGHT:
LedOff(LedLeft);
LedOn(LedRight);
rt_thread_mdelay(250);
LedOff(LedRight);
break;
default:
LOG_D("mode error\n");
}
rt_thread_mdelay(250);
}
}
void ledmode(int argn, char *argv[])
{
if(argn<2){
LOG_W("ledmode #mode");
return ;
}
led_set_mode(atoi(argv[1]));
}
void led_stop_flag(int i)//设置暂停标志的接口
{
stopFlag=i;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(ledmode, set led flash mode);
2.car_led.h
代码如下:
#ifndef APPLICATIONS_CAR_LED_H_
#define APPLICATIONS_CAR_LED_H_
/* 车灯闪烁模式定义 */
enum led_mode {
LED_MODE_STOP=0, //停止闪烁
LED_MODE_Double, //双闪
LED_MODE_LEFT, //左灯闪烁
LED_MODE_RIGHT //右灯闪烁
};
void led_thread_entry(); //线程入口函数声明
void led_set_mode(enum led_mode m); //车灯闪烁模式设置声明
void led_stop_flag(int i); //接口声明,同时导出模块接口供其它模块使用
#endif /* APPLICATIONS_CAR_LED_H_ */
3. main.c
主要修改stop_led_thread()函数的实现,同时增加线程恢复函数,并把线程恢复函数导出到msh命令中,使我们可以通过终端来恢复线程。
#include <rtthread.h>
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "car_led.h" //包含LED控制模块头文件
#include "car_beep.h" //包含蜂鸣器控制模块头文件
#define THREAD_STACK_SIZE 1024 //定义线程栈大小
#define THREAD_PRIORITY 20 //定义线程优先级
#define THREAD_TIMESLICE 10 //定义线程时间片
/* 栈首地址必须系统对齐 */
ALIGN(RT_ALIGN_SIZE)
static char beep_stack[THREAD_STACK_SIZE]; //定义栈空间
static struct rt_thread beepThread; //静态方式定义beep线程控制块
rt_thread_t TidLed = RT_NULL; //动态方式定义LED线程句柄
int main(void)
{
int ret;
/* 动态方式创建线程 */
TidLed = rt_thread_create("LED",
led_thread_entry,
RT_NULL,
THREAD_STACK_SIZE,
THREAD_PRIORITY,
THREAD_TIMESLICE);
if (TidLed != RT_NULL)//判断线程是否成功创建
rt_thread_startup(TidLed);//成功则启动线程
else {//否则打印日志并即出
LOG_D("can not create LED thread!");
return -1;
}
/* 采用静态方式初始化线程 */
ret = rt_thread_init(&beepThread,
"BEEP",
beep_thread_entry,
RT_NULL,
&beep_stack[0],
sizeof(beep_stack),
THREAD_PRIORITY,
THREAD_TIMESLICE);
if (ret == RT_EOK) //判断线程是否成功创建
rt_thread_startup(&beepThread); //成功则启动线程
else { //否则打印日志并即出
LOG_D("can not init beep thread!");
return -1;
}
return RT_EOK;
}
void stop_led_thread()//暂停运行
{
led_stop_flag(1);
}
void resume_led_thread()//恢复运行
{
led_stop_flag(0);//先设置为运行
rt_thread_resume(TidLed);//再恢复线程
return;
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(resume_led_thread, resume led thread);//唤醒命令
MSH_CMD_EXPORT(stop_led_thread, stop led thread);
三、程序测试
(1)系统启动后,使用命令“ledmode 1”使能车灯双闪,观察到车灯闪烁。
(2)输入命令“stop_led_thread”命令,观察到车灯停止闪烁,说明线程已经被挂起。
(3)输入命令“resume_led_thread”命令,观察到车灯重新闪烁,说明线程被重新唤醒。
(4)输入命令“ps”命令,如图所示,观察到BEEP和LED两个线程都处理挂起状态,这主要是因为ps命令输出时,CPU正在运行tshell程序(命令是在tshell线程上下文件中运行的),而此时BEEP和LED两个线程因为执行rt_thread_mdelay()函数而被挂起。
总结
本章节学习线程的挂起与恢复的应用