RTOS 上微秒级延时方案
扫描二维码
随时随地手机看文章
微秒级延时设计方案
一般 RTOS 系统时钟 1KHz 的情况下,thread_sleep() 的最短时间是 1ms。在实时控制中有些情况需要微秒(us)级延时,这该怎么办呢?微秒级延时有两种实现思路:一是着情提高系统时钟,二是使用 MCU 的高精度定时器。一、着情提高系统时钟
之所以说是“着情”提高的原因是:系统时钟越快,单位时间内的线程调度次数越多,也就是说花在调度的时间会大幅增加,这对线程的功能不利。真正做事的是线程函数,如果 CPU 会说话,过快的线程调度将会引起 CPU 的极度不满。线程是 CPU 具体要做的事,刚把 CPU 调过来做事,事没做完就拉跑做另一件事,CPU 会说:“傻瓜,疯了吗?不是让我做事的码,干嘛老是拉着我跑这跑那,就不能让我干完了再走码?!”二、使用 MCU 片上外设定时器
一般 MCU 都会有片上高精度定时器外设,可以配置到 1us 精度。即然用定时器可以,那就用定时器呗,还写什么文章?当然不只是开启定时器这么简单,RTOS 要实现的是阻塞延时,任务进入延时要交出 CPU 使用权进入阻塞状态。在 RTOS 上用定时器躺平死等是无赖行为,睡眠让权才能实现良好的多线程调度。虽然 us 级延时时间短,在一个线程处于延时中时另一个线程又要开始延时的情况发生概率不大。但是在多线程情况下延时依旧有可能发生重入,比如一个线程要延时 500us,刚过 100us 另一个线程就要延时 200us,这种情况不但发生了重入,还有“时间覆盖”(200us 覆盖了上一个线程剩余的 400us 里的时间段),这些情况也不是光靠一个硬件高精度定时器就能应对的。多线程延时工况分析
先来看一张多线程延时工况图,如“图1”所示:图1. 多线程延时工况01
为了方便阅读以及接下来进一步的设计实现,Sugar 在上图基础上加了一些注释,对多线程的工况进行更细致一点的描述,如“图2”所示:
图2. 多线程延时工况02
为了更好说明 Sugar 选用最近长势正盛的 Microsoft Azure RTOS ThreadX 做基础来实现这个设计。目的在于输出通用方法,具体选什么 RTOS 并不重要,是个多线程就行,比如:RT-Thread、FreeRTOS 等都可以。图中的 A、B、C 和 High-precision Timer 是 4 个线程。其中 High-precision Timer 线程优先级最高,但不是定时回调的,而是被动触发。下面说说为什么 High-precision Timer 线程优先级要最高,以及如何被动触发。我们知道线程中用
WAIT_FOREVER
方式等待信号量的时候,若信号量的值为 0 则线程会被挂起在这个信号量下。我们就利用这个特点来完成线程的“被动触发”,即:1、信号量建立时初值为 02、在中断中释放一次信号量(即信号量值加 1)这样中断发生后就能立刻唤醒挂起在该信号量下的线程,即完成了线程的被动触发。线程转为就绪态后,因其优先级最高,会立即抢占调度器得到执行。在 Hight-precision Timer 线程被信号量唤醒后,立即对延时时间到的线程进行 resume 操作,这样就完成了线程的 us 延时。我们回看一下上面图中的 A、B、C 三个线程,每条线上都串了两个圈圈,每条线从上往下第一个圈是延时主动挂起,第二个圈是时间到后被 High-precision Timer 线程 resume 回来继续执行。至此读图的方法基本说清楚了,如果要落实到代码,其实还有个“硬件定时器与 High-precision Timer 线程”的关系。图中标在 High-precision Timer 左边的标签是说:因为硬件定时器产生了中断,才使得 High-precision Timer 线程对延时时间到的线程进行 resume。上面说“被动触发”的时候有说到相关原理,其实上面图的最右边应该再放一列表示“硬件定时器”就更好理解原理了。没有放的原因是这里要考虑“可重入”,这个瓜有点多,一车装不下,装少了说不完善,装多了眼花缭乱,所以就没画“硬件定时器”这一列。代码实现
为了实现上述设计的阻塞延时,代码要划分为四个部分:一、 要配置一个 us 级定时器;二、 要做一个 us 延时的函数接口;三、 要有一个 High-precision Timer 线程;四、 要有一个测试用 us 级的普通定时回调线程。下面 Sugar 以 STM32 为例逐一上代码。us 级定时器配置
1、 定时器初始化
这里直接使用 CubeMX 生成的函数最方便,一行不改,如下:/**
* @brief TIM9 Initialization Function
* @param None
* @retval None
*/
static void MX_TIM9_Init(void)
{
/* USER CODE BEGIN TIM9_Init 0 */
/* USER CODE END TIM9_Init 0 */
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
/* USER CODE BEGIN TIM9_Init 1 */
/* USER CODE END TIM9_Init 1 */
htim9.Instance = TIM9;
htim9.Init.Prescaler = 215;
htim9.Init.CounterMode = TIM_COUNTERMODE_UP;
htim9.Init.Period = 65535;
htim9.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim9.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(