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蓝牙协议栈学习
简介
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;
};