一、概述
Zephyr 的 Counter 驱动支持"相对闹钟"和"绝对闹钟"两种触发语义 。区别在于 struct counter_alarm_cfg 里的 flags 和 ticks 的含义。
关键结构体:
struct counter_alarm_cfg {
counter_alarm_callback_t callback; //回调
uint32_t ticks; //不同模式下含义不同(见下)
void *user_data; //给callback做回调参数
uint32_t flags; //标志闹钟模式
};
常用标志位(flags)
alarm_cfg.flags = COUNTER_ALARM_CFG_ABSOLUTE
绝对闹钟 :ticks表示计数器周期空间内的绝对计数值(例如下一次的目标计数点)。需要你自己处理取模(wrap)。- 缺省(
alarm_cfg.flags = 0)
相对闹钟 :ticks表示相对当前的延迟,即"从现在开始延迟多少 ticks 后触发"。
设定闹钟函数
c
int counter_set_channel_alarm(const struct device *dev,
uint8_t channel_id,
const struct counter_alarm_cfg *alarm_cfg);
二、不同模式下 ticks 的含义
2.1 相对闹钟(alarm_cfg.flags = 0)
-
ticks表示从现在起ticks后触发 (相对当前计数值),通常允许的最大延迟受 计数器的拓展/回卷周期(top) 以及驱动"保护期(guard period)"限制。 -
使用情况:需求是"每隔固定时长触发"(周期性任务),最简单 且不需要考虑取模。注意不要累加延迟。
-
参考代码实现,通过信号量释放while循环阻塞,1s触发一次:
c
#include <zephyr/drivers/counter.h>
static struct counter_alarm_cfg alarm_cfg;
static uint32_t period_ticks;
static struct k_sem sem;
static void alarm_cb(const struct device *dev, uint8_t chan_id,
uint32_t ticks, void *user_data)
{
struct counter_alarm_cfg *config = user_data;
// 周期性:固定相对延迟为 period_ticks(不要累加)
// config.ticks = period_ticks; 这个不用动
int err = counter_set_channel_alarm(dev, chan_id, &config);
if (err) {
printk("re-arm failed: %d\n", err);
}
k_sem_give(&sem);
}
void main(void)
{
const struct device *dev = DEVICE_DT_GET(TIMER); // 你的计时器设备
if (!device_is_ready(dev)) {
printk("counter not ready\n");
return;
}
k_sem_init(&sem, 0, 1);
// 计算 1 秒对应的 ticks
period_ticks = counter_us_to_ticks(dev, 1000000U);
//(可选)设置保护期,避免过近设置失败
//counter_set_guard_period(dev, counter_us_to_ticks(dev, 200U), COUNTER_GUARD_PERIOD_LATE);
// 启动计数器
counter_start(dev);
// 首次设置:相对 1 秒后触发
alarm_cfg.flags = 0; // 相对模式
alarm_cfg.ticks = period_ticks; // 相对延迟
alarm_cfg.callback = alarm_cb;
alarm_cfg.user_data = &alarm_cfg; // 让回调能访问配置
int err = counter_set_channel_alarm(dev, 0, &alarm_cfg);
if (err) {
printk("set alarm failed: %d\n", err);
}
while (1) {
k_sem_take(&sem, K_FOREVER);
// 可在此做一些cycle性质的工作
}
}
2.2 绝对闹钟( alarm_cfg.flags = COUNTER_ALARM_CFG_ABSOLUTE)
-
ticks表示计数器周期空间 内的绝对目标值(比如"当计数值到 N 时触发")。 -
使用情况:
需要保证你设置的下一个
ticks值落在[0, top)区间内。所以需要注意自己取模。如果不做取模,传入的绝对值超出范围,驱动一般会返回-EINVAL。如果计数器是向下计数 ,你可以用原始硬件计数空间或把它转换成统一的"向上空间",但要一致。
-
参考代码实现
c
#include <zephyr/drivers/counter.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
static struct counter_alarm_cfg alarm_cfg;
static uint32_t period_ticks;
static uint32_t top;
static struct k_sem sem;
static void alarm_cb(const struct device *dev, uint8_t chan_id,
uint32_t fired_ticks, void *user_data)
{
ARG_UNUSED(user_data);
// fired_ticks 是这次触发的绝对计数值(在周期空间内)
// 下一个绝对触发点 = 本次触发点 + 周期(取模 top)
uint64_t next = (uint64_t)fired_ticks + (uint64_t)period_ticks;
uint32_t next_ticks = (uint32_t)(next % top);
printk("Alarm fired (absolute). now=%u next=%u\n", fired_ticks, next_ticks);
alarm_cfg.ticks = next_ticks;
alarm_cfg.flags = COUNTER_ALARM_CFG_ABSOLUTE;
int err = counter_set_channel_alarm(dev, chan_id, &alarm_cfg);
if (err) {
printk("re-arm failed: %d\n", err);
}
k_sem_give(&sem);
}
void main(void)
{
const struct device *dev = DEVICE_DT_GET(TIMER);
if (!device_is_ready(dev)) {
printk("counter not ready\n");
return;
}
k_sem_init(&sem, 0, 1);
period_ticks = counter_us_to_ticks(dev, 1000000U);
top = counter_get_top_value(dev);
counter_start(dev);
// 读取当前绝对计数值作为起点
uint32_t now;
int err = counter_get_value(dev, &now);
if (err) {
printk("get value failed: %d\n", err);
return;
}
// 第一次触发点 = 当前值 + 周期(取模)
uint32_t first = (now + period_ticks) % top;
alarm_cfg.flags = COUNTER_ALARM_CFG_ABSOLUTE;
alarm_cfg.ticks = first; // 绝对目标值
alarm_cfg.callback = alarm_cb;
alarm_cfg.user_data = &alarm_cfg;
err = counter_set_channel_alarm(dev, 0, &alarm_cfg);
if (err) {
printk("set alarm failed: %d\n", err);
}
while (1) {
k_sem_take(&sem, K_FOREVER);
// 与回调同步
}
}