我正在使用使用抢先式循环调度程序的 KEIL RTX RTOS。我有一个用于显示数据的 LCD,并且一些任务可以访问此 LCD(还有一些其他任务),这些任务需要固定时间来处理 LCD(例如,第一个任务句柄 LCD 显示其数据 50 秒,50 秒后,第二个任务句柄并显示其数据 10 秒)。我知道我必须使用互斥锁来管理对 LCD 的访问。但我不知道我必须如何管理它的固定时间?LCD 任务处于最低优先级,如果没有任何其他任务要执行,这些任务将被执行以显示消息.
3 回答
我会先尝试回答你的问题,然后我会提出一个替代设计供你考虑。
您应该使用 Timer 来以真实世界时间为基础,尤其是相对较长的时间段,例如以秒为单位的时间段。创建两个 Timer 对象,一个具有 50 秒的周期,一个具有 10 秒的周期。两个计时器都应该是一次性的。还要创建两个 Signal 对象,一个指示 50 秒周期已到期,另一个指示 10 秒周期已到期。两个 Timer 对象在到期时各自调用不同的回调函数。50 秒回调函数应该设置 50 秒过期信号,然后启动 10 秒 Timer。10 秒回调函数应该设置 10 秒过期信号,然后重启 50 秒 Timer。计时器将来回乒乓,交替设置两个信号。
现在,您的资源使用任务应该定期检查适当的到期信号。当任务观察到信号已设置时,它可以放弃资源并清除信号。另一个任务对另一个信号做同样的事情。这样,两个任务就知道何时放弃资源并允许另一个任务获得它。
关于您的设计,困扰我的一件事是您有两种同步机制来保护您的资源。互斥锁是一种同步机制。当任务要异步使用资源(即随机时间)时,可以使用互斥锁来同步这些使用情况,并确保在任何给定时间只有一个任务在使用资源。如果您已经有另一种同步机制,那么您可能不需要互斥锁。换句话说,如果您的任务有不同的时间段来使用资源,那么它们已经是同步的。如果他们不打算在随机时间尝试使用资源,那么您可能不需要互斥锁。
您的设计也似乎很复杂,我想知道这种替代设计是否会更简单。
考虑制作一个负责 LCD 显示界面的任务。此 LDC 任务是唯一与显示器交互的任务。当您的数据任务产生要显示的数据时,它们将向 LCD 任务发送消息。LCD 任务将简单地等待这些消息并在它们到达时适当地显示数据。根据数据的复杂程度和变化程度,您可以为此消息服务使用消息队列或邮件队列。现在您不需要 LCD 显示器的互斥体,因为只有一个任务使用它。你还需要这种设计的 50/10 秒时间分割吗?我不确定,因为我不知道该要求的来源是什么。
与其让多个线程访问由互斥体仲裁的单个资源,不如让单个线程处理资源会更简单。
在这种情况下,我建议使用显示管理器线程,其他线程可以向显示管理器注册,可能提供指向显示缓冲区的指针,以及所需的显示周期。然后,显示管理器只需在每个已注册线程中循环显示其缓冲区所需的时间,然后再切换到下一个。
例如(伪代码):
static struct
{
const char* frame_buffer ;
int display_seconds ;
OS_MUTEX mutex ;
} display_registry[MAX_DISPLAY_THREADS] = {0,0} ;
void displayLock( int handle )
{
if( handle >= 0 && handle < MAX_DISPLAY_THREADS )
{
os_mutex_lock( display_registry[handle].mutex ) ;
}
}
void displayUnock( int handle )
{
if( handle >= 0 && handle < MAX_DISPLAY_THREADS )
{
os_mutex_unlock( display_registry[handle].mutex ) ;
}
}
void lcd_thread
{
int display = 0 ;
for(;;)
{
int t = 0 ;
while( t < display_registry[display].display_seconds &&
display_registry[display].frame_buffer != 0 )
{
displayLock( display ) ;
render_display( display_registry[display].frame_buffer ) ;
displayUnlock( display ) ;
delay( ONE_SECOND ) ;
}
display = (display + 1) % MAX_DISPLAY_THREADS ;
}
}
int displayRegister( const char* frame_buffer, int display_seconds )
{
for( int i = MAX_DISPLAY_THREADS - 1;
frame_buffer[i] != 0 &&
i >= 0; i-- )
{
// do nothing
}
if( i >= 0 )
{
display_registry[i].display_seconds = display_seconds ;
display_registry[i].frame_buffer = frame_buffer ;
}
return i ; // handle for de-registering/locking
}
void displayDeregister( int handle )
{
if( handle >= 0 && handle < MAX_DISPLAY_THREADS )
{
display_registry[handle].frame_buffer = 0 ;
}
}
请注意,互斥锁不是锁定 LCD 资源,而是锁定共享内存帧缓冲区资源。
然后,其他线程只需将要显示的数据放在它们自己的帧缓冲区中,与显示该数据完全异步,例如:
displayLock( my_display_handle ) ;
update_display_buffer() ;
displayUnlock( my_display_handle ) ;
如之前的答案所述,首先为 LCD 显示创建一个任务,然后使用计时器事件来跟踪时间片。
最后,在计时器事件处理程序(在时间片之后调用)中生成任务。
如果您不了解 yield,yield 是任务放弃执行以使调度程序移动到下一个任务的一种方式。