我想用hrtimer
控制两个硬件gpio引脚来做一些总线信号。我在像这样的内核模块中设置了一个hrtimer
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/ktime.h>
#include <linux/hrtimer.h>
#define PIN_A_HIGH_TO_A_LOW_US 48 /* microseconds */
#define PIN_A_LOW_TO_B_LOW_US 24 /* microseconds */
static struct kt_data {
struct hrtimer timer;
ktime_t period;
} *data;
typedef enum {
eIdle = 0,
eSetPinALow,
eSetPinBLow,
} teControlState;
static enum hrtimer_restart TimerCallback(struct hrtimer *var);
static void StopTimer(void);
static teControlState cycle_state = eIdle;
static enum hrtimer_restart TimerCallback(struct hrtimer *var)
{
local_irq_disable();
switch (cycle_state) {
case eSetPinALow:
SetPinA_Low();
data->period = ktime_set(0, PIN_A_LOW_TO_B_LOW_US * 1000);
cycle_state = eSetPinBLow;
break;
case eSetPinBLow:
SetPinB_Low();
/* Do Stuff */
/* no break */
default:
cycle_state = eIdle;
break;
}
if (cycle_state != eIdle) {
hrtimer_forward_now(var, data->period);
local_irq_enable();
return HRTIMER_RESTART;
}
local_irq_enable();
return HRTIMER_NORESTART;
}
void StartBusCycleControl(void)
{
SetPinA_High();
SetPinB_High();
data->period = ktime_set(0, PIN_A_HIGH_TO_A_LOW_US * 1000);
cycle_state = eSetPinALow;
hrtimer_start(&data->timer, data->period, HRTIMER_MODE_REL);
}
int InitTimer(void)
{
data = kmalloc(sizeof(*data), GFP_KERNEL);
if (data) {
hrtimer_init(&data->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
data->timer.function = TimerCallback;
printk(KERN_INFO DRV_NAME
": %s hr timer successfully initialized\n", __func__);
return 0;
} else {
printk(KERN_CRIT DRV_NAME
": %s failed to initialize the hr timer\n", __func__);
return -ENOMEM;
}
}
所以想法是
我使用带有内核4.1.2的BeagleBoneBlack和rt-preempt补丁。
我在范围内看到的是,第一个计时器就像一个约65-67微秒的魅力(我可以忍受)。 但转发似乎发生故障,因为我在引脚A变低和引脚B变低之间测量的时间在2到50微秒之间。 所以从本质上讲,第二次触发回调有时会发生之前我定义的24微秒。 而且这个时间对我的用例不起作用。
指出我做错了什么?
答案 0 :(得分:3)
所以我自己回答:这是一个错误的期望问题。
我们在此期望的是在回调期间将定时器前进设置为我们设置的量(24us)。但是如果我们看一下hrtimer_forward_now()
的内核实现,我们可以看到时间实际上被添加到了计时器的最后一个事件/事件(参见{{1}的计算}):
delta
这意味着在这里不考虑定时器触发和实际执行的回调之间的时间延迟。 hrtimers在间隔时间上是精确的,并且不受发射和回调之间的通常延迟的影响。 我们的期望是包括计算时间,因为我们希望计时器从我们在计时器回调中执行操作的那一刻开始重新启动。
我们得到红色编号的气泡:
833 u64 hrtimer_forward(struct hrtimer *timer, ktime_t now, ktime_t interval)
834 {
835 u64 orun = 1;
836 ktime_t delta;
837
838 delta = ktime_sub(now, hrtimer_get_expires(timer));
839
840 if (delta.tv64 < 0)
841 return 0;
842
843 if (WARN_ON(timer->state & HRTIMER_STATE_ENQUEUED))
844 return 0;
845
846 if (interval.tv64 < hrtimer_resolution)
847 interval.tv64 = hrtimer_resolution;
848
849 if (unlikely(delta.tv64 >= interval.tv64)) {
850 s64 incr = ktime_to_ns(interval);
851
852 orun = ktime_divns(delta, incr);
853 hrtimer_add_expires_ns(timer, incr * orun);
854 if (hrtimer_get_expires_tv64(timer) > now.tv64)
855 return orun;
856 /*
857 * This (and the ktime_add() below) is the
858 * correction for exact:
859 */
860 orun++;
861 }
862 hrtimer_add_expires(timer, interval);
863
864 return orun;
865 }
根据最后一个事件加上新的预期时间(未来可能只有2个而不是24个)来设置新的计时器。hrtimer_forward_now
总而言之,我们完全破坏了上面的代码示例,并在触发两个GPIO引脚之间进行forward_now()
调用。该函数的底层实现也是使用usleep_range()
完成的,但它对用户是隐藏的,并且在这种情况下它的行为正如我们所期望的那样。
答案 1 :(得分:1)
我也遇到了这个问题。感谢TabascoEye的回答。我只想添加一些代码作为示例,以便于理解。
在我的应用程序中,我有一个硬件中断(间隔30ms + -3ms)来调用reactive_hrtimer()
,然后在10ms之后将调用timer_do()
。由于输入中断的时间不是很规律,因此我需要自己为hrtimer_add_expires()
实现输入。
对于不规则的时间间隔:
enum hrtimer_restart timer_do(struct hrtimer *timer)
{
/**something**/
return HRTIMER_NORESTART;
}
void reactive_hrtimer( struct hrtimer *hr_timer, ktime_t ktime_interval)
{
ktime_t delta;
ktime_t now;
now = hrtimer_cb_get_time(hr_timer);
delta = ktime_sub(now, hrtimer_get_expires(hr_timer));
hrtimer_add_expires(hr_timer, ktime_add(ktime_interval, delta));
hrtimer_restart(hr_timer);
}
这些代码可以放在任何地方,而不是放在回调内部