zephyr内核对象学习
定时器
类似linux的定时器,
可以分别设置第一次到期时间和后续的周期触发时间,
可以注册到期回调和停止回调
还有一个计数状态,用于标记timer到期了多少次
duration:设定timer第一次到期的时间。
period: timer第一次到期后的触发时间间隔。
expiry:触发回调。
定时器的使用:
- 初始化定时器
void k_timer_init(struct k_timer *timer, k_timer_expiry_t expiry_fn, k_timer_stop_t stop_fn);
- 启动定时器
void k_timer_start(struct k_timer *timer, k_timeout_t duration, k_timeout_t period);
- 停止定时器
void k_timer_stop(struct k_timer *timer);
- 读取定时器状态
uint32_t k_timer_status_get(struct k_timer *timer);
读取定时器的状态,该状态表示自上次读取其状态以来定时器已到期的次数,每次读取后会重置状态为0。
- 等待定时器到期
uint32_t k_timer_status_sync(struct k_timer *timer);
调用这个函数会阻塞线程,直到定时器到期或者停止,调用这个函数会将定时器状态清零,另外不允许在中断处理函数中调用该函数,函数返回定时器的状态值。
- 获取定时器超时到期时的系统时间
k_ticks_t k_timer_expires_ticks(const struct k_timer *timer);
该函数返回定时器下一次到期时候的系统时间,以系统ticks为单位。如果定时器未运行,则返回当前系统时间。
- 获取定时器超时到期的剩余时间
k_ticks_t k_timer_remaining_ticks(const struct k_timer *timer)
计算运行的定时器下次过期前剩余的时间,如果定时器未运行,则返回0。
- 获取定时器超时到期前的剩余时间
uint32_t k_timer_remaining_get(struct k_timer *timer);
计算运行定时器下次到期前剩余的(近似)时间,以毫秒(ms)为单位。如果定时器未运行,则返回0。
另外还有一种定义和初始化定时器的方式:
静态定义并初始化定时器
#define K_TIMER_DEFINE(name, expiry_fn, stop_fn)
注意
因为timer的回调是在中断中执行,所以在回调函数中不能做耗时操作。
timer不能保证精确的定时,但其精度比k_sleep/k_usleep高,测量执行时间时不建议使用k_timer,建议读系统硬件时钟。
当timer触发回调后需要处理耗时操作时,可配合k_work使用,将耗时操作放在workqueue中执行
/* k_work回调函数,用于处理耗时操作 */
void work_handler(struct k_work *work)
{while(int i=0, i<100, i++){printk("do something \n");}
}/* 定义初始化一个k_work */
K_WORK_DEFINE(a_work, work_handler);/* timer到期回调函数 */
static void timer_handler_expiry(struct k_timer *dummy)
{counter++;printk("counter %d \n", counter);/*发送k_work信号量*/k_work_submit(&a_work);
}
信号量
信号量是用于控制多个线程对一组资源的访问,使用信号量在生产者和消费者之间同步
- Zephyr的信号量在初始化时可以指定初始化计数值和最大计数值,生产者释放(give)信号量时计数值+1,但不会超过最大值,消费者获取(take)时计数值-1,直到为0。
- 每次信号量释放时都会引发调度。
- 如果多个线程都在等待信号量,新产生的信号量会被等待时间最长的最高优先级线程接收。
信号量的使用
- 初始化信号量
int k_sem_init(struct k_sem *sem, unsigned int initial_count, unsigned int limit);
- 获取信号量
int k_sem_take(struct k_sem *sem, k_timeout_t timeout);
- 释放信号量
void k_sem_give(struct k_sem *sem);
互斥量
互斥量本质应该和初始值和最大值为1的信号量相同;目的主要是为了提供对资源的独占访问(因为只有0和1,只有一个线程能拿到资源,所以就实现了独占访问)
- 互斥量只能用于线程中,不能用于中断(会引起阻塞,所以不能用于中断)
- 互斥量释放会引起调度(释放信号量也会引起调度)
- 引起阻塞之后可能会导致优先级翻转(那理论上信号量也会引起优先级翻转)
互斥量的使用
- 初始化互斥量
int k_mutex_init(struct k_mutex *mutex);
- 互斥量上锁(相当于获取信号量)
int k_mutex_lock(struct k_mutex *mutex, k_timeout_t timeout);
- 互斥锁解锁(相当于释放信号量)
void k_mutex_unlock(struct k_mutex *mutex);
轮询
轮询(poll)是一个比较特殊的内核对象,polling API 允许一个线程等待一个或者多个条件满足。支持的条件类型只能是内核对象,可以是Semaphore(信号量), FIFO(管道), poll signal(轮询)三种。
例如一个线程使用polling API同时等待多个semaphore,只要其中一个 semaphore 触发时 polling API 就会得到通知。
poll 具有以下特性:
- 当一个线程等待多个触发条件时,只要有一个条件满足 k_poll 就会返回。
- 当 Semaphore 或 FIFO 满足条件后, k_poll 只是接到通知返回,线程并未获取到 semaphore 或FIFO, 还需要使用代码主动获取。
轮询的使用
- 初始化轮询实例
void k_poll_event_init(struct k_poll_event *event, uint32_t type, int mode, void *obj);
初始化的时候,一次只能添加一个内存对象,event是数组指针,type是指后面obj的类型(信号量或者FIFO或者轮询信号,不论是这三个的哪一种,在这之前都要调用对应的初始化接口进行初始化),mode一般是notify_only
- 轮询接口
int k_poll (struct k_poll_event *events, int num_events, k_timeout_t timeout)
在一次释放之后,如果k_poll需要再次捕获该信号,需要先调用复位信号的接口进行复位,否则将无法再次释放;
如果用的是poll_signal,可以用下面的接口进行操作:
- 轮询信号初始化
void k_poll_signal_init(struct k_poll_signal *sig);
- 轮询信号释放
int k_poll_signal_raise(struct k_poll_signal *sig, int result);
- 复位轮询信号
void k_poll_signal_reset(struct k_poll_signal *sig);
- 检查轮询信号
void k_poll_signal_check(struct k_poll_signal *sig, unsigned int *signaled, int *result);
个人理解,应该是在k_poll轮询多个对象其中包含poll_signal时,用来确定是不是signal被捕获到了;如果需要判断其他内核对象(信号量或者FIFO),则需要主动判断k_poll接口中的struct k_poll_event *events参数的state是sem有效还是fifo_data有效;
线程分析器
线程分析器作为zephyr的一个调试工具,用来跟踪线程信息例如线程的堆栈大小使用情况、线程运行时的其他统计信息。
线程分析器的使用
个人理解,有三种使用方式:
- 将信息收集到某个回调中
void thread_analyzer_run(thread_analyzer_cb cb)
- 将信息直接打印出来
void thread_analyzer_print(void)
- 自动运行线程分析器
这种只需要在项目的prj.conf配置文件中打开以下配置:
# 启用线程分析器
CONFIG_THREAD_ANALYZER=y
# 使用PRINTK输出进行线程统计
CONFIG_THREAD_ANALYZER_USE_PRINTK=y
# 在内核中启用此选项以打印线程的名称
CONFIG_THREAD_NAME=y
# 自动运行线程分析器,使用此选项时,不需要向应用程序添加任何代码
CONFIG_THREAD_ANALYZER_AUTO=y
# 在自动模式下连续打印线程分析之间,模块休眠的时间单位(S)
CONFIG_THREAD_ANALYZER_AUTO_INTERVAL=5
工作队列(未详细研究)
工作队列(workqueue)是一个使用特定线程来运行工作项(work items)的内核对象,其方式为先进先出,通过调用工作项指定的函数来处理每个工作项。工作队列的典型应用是在中断或者高优先级线程中去分担部分工作到一个低优先级的线程中,其目的是减少中断或高优先级线程的处理时长,所以它不影响时间敏感的处理。
工作队列的使用
-
初始化工作队列
-
初始化工作结构
void k_work_init(struct k_work *work, k_work_handler_t handler)
zephyr蓝牙协议栈学习
简介
zephyr主要支持BLE,对BR/EDR仅提供有限的支持
core5.3中BLE功能几乎全部支持,包括LE audio和mesh;
BR/EDR仅支持部分,GPA,L2CAP,RFCOMM,SDP,(不过看到zephyr代码里也有HF,A2DP,AVDTP等)
zephyr可以仅被配置为controller或者host,也可以配置为既有controller也有host
zephyr仅做host时,支持跟多个controller同时通信
源码树层次
subsys/bluetooth/host
这里是host stack。处理HCI命令和事件地方,L2CAP,ATT,SMP等核心协议也在这里
subsys/bluetooth/controller
蓝牙控制器实现。实现HCI的控制器端,链路层以及对无线电收发器的访问
include/bluetooth/
公共API头文件。这些是应用程序需要包含的头文件,以便使用蓝牙功能
drivers/bluetooth
HCI传输层驱动。每个HCI传输层都需要自己的驱动程序。(三线uart或者5线uart,usb,spi等)
samples/bluetooth
蓝牙实例代码。
test/bluetooth
测试应用程序。这些应用程序用于验证蓝牙堆栈的功能。
doc/guides/bluetooth
额外的文档,比如PICS文档
HOST
GAP通过定义BLE使用的四个不同角色来简化蓝牙LE访问:
面向连接的角色:
- 外围设备(例如智能传感器,通常具有有限的用户界面)
- 中央设备(通常是移动电话或PC)
无连接的角色:
- 广播者(发送BLE广告,例如智能信标)
- 观察者(扫描BLE广告)
在面向连接的角色中,中央设备隐式的启用观察者角色,外围设备隐式的启用广播者角色
注册gatt service的方法
使用BT_GATT_SERVICE_DEFINE宏
实际管理单位应该是attr
/*** @brief Statically define and register a service.** Helper macro to statically define and register a service.** @param _name Service name.*/
#define BT_GATT_SERVICE_DEFINE(_name, ...) \const struct bt_gatt_attr attr_##_name[] = { __VA_ARGS__ }; \const STRUCT_SECTION_ITERABLE(bt_gatt_service_static, _name) = \BT_GATT_SERVICE(attr_##_name)
/** @brief GATT Attribute structure. */
struct bt_gatt_attr {/** Attribute UUID */const struct bt_uuid *uuid;bt_gatt_attr_read_func_t read;/** Attribute write callback */bt_gatt_attr_write_func_t write;/** Attribute user data */void *user_data;/** Attribute handle */uint16_t handle;/** @brief Attribute permissions.** Will be 0 if returned from ``bt_gatt_discover()``.*/uint16_t perm;
};