Zephyr -- Power Management

System PM CONFIG_PM开关。用来控制整个SoC的状态。 Device tree zephyr,power-state.yaml介绍了zephyr power state的device tree binding。 一个设备树的例子: power-states { idle: idle { compatible = "zephyr,power-state"; power-state-name = "runtime-idle"; }; stop: stop { compatible = "zephyr,power-state"; power-state-name = "suspend-to-idle"; // pm状态,见yaml substate-id = <0>; // 如果有两个相同的power-state-name,需要用substate-id来区分 min-residency-us = <1000>; // 最少在该状态停留的时间 exit-latency-us = <2>; // 离开该状态最worse的时间 }; pstop1: pstop1 { compatible = "zephyr,power-state"; power-state-name = "suspend-to-idle"; substate-id = <1>; min-residency-us = <1000>; exit-latency-us = <2>; }; }; PM policy 有两种PM policy:...

2024-05-16 · 4 min

Zephyr -- Zbus

Concept Zbus实现了一种线程间多对多的消息通信机制。 有三种类型的观察者: Listener,event dispatcher每次发布或通知通道时,都会执行Listener的回调函数。是同步的。 Subscriber,内部依赖于消息队列,event dispatcher每次发布或通知通道时,都会在其中放置更改的channel的引用。注意,这种观察者本身并不接收消息。收到通知后应主动从通道中读取消息。Subscriber线程中需要主动从channel中读出当前的message。是异步的。 Message subscribers,event dispatcher每次发布或通知通道时,都会copy message到buf中再挂入Message subscribers的message fifo。Message subscribers线程中只需要从message fifo中取出message。是异步的。 For example,在下图中,Timer是Channel Trigger的Publisher,Sensor Thread是Subscriber,Blink是Listener。 当timer发布message后,会执行Blink的回调函数来闪烁,Sensor thread可以获取sensor数据。 Sensor thread处理完数据后,又是Sensor data channel的publisher,发布message后,core thread接收到message,来处理sensor data。 Core thread处理完后,通知LoRa thread,在最后一个channel处理完成后,再次调用Blink的回调函数来闪烁。 可以打开或关闭某个订阅者,某个订阅者也可以选择打开或关闭某个channel。 Virtual Distributed Event Dispatcher 假设一个场景,有一个channelA。T1为publisher,S1为Subscriber,L1,L2为Listener,MS1,MS2为Message Subscriber。 The VDED execution总是发生在publisher’s context, 基本流程为: 给channel上锁 channel通过直接拷贝(memcpy)把new message拷贝过去。 VDED执行listener的回调函数,把message拷贝给message subscriber,把channel reference加入到subscriber的notification message queue。Listener可以通过zbus_chan_const_msg()直接获取message的reference。 给channel解锁 如下图演示了执行流程,假设线程优先级为T1>MS1>MS2>S1: 可以看到Listener只直接引用channel里的message而不用拷贝,而Publisher会把message拷贝到Message subscriber。Subscriber则是当调度到的时候自己去拷贝message。 如果线程优先级为T1<MS1<MS2<S1: 注意此时的执行顺序,在f时,T1发布message,会立刻调度到MS1, 在g时,会立刻调度到MS2。 HLP priority boost Zbus实现了自动提高publisher线程优先级的操作,选项为CONFIG_ZBUS_PRIORITY_BOOST,是默认自动打开的。这样能publisher线程不会像上面一样被打断,可以提高执行效率。 为了使用该特性,需要将observer attach到一个线程: ZBUS_SUBSCRIBER_DEFINE(s1, 4); void s1_thread(void *ptr1, void *ptr2, void *ptr3) { const struct zbus_channel *chan; zbus_obs_attach_to_thread(&s1); // 将s1 attach到线程 while (1) { zbus_sub_wait(&s1, &chan, K_FOREVER); /* Subscriber implementation */ } } K_THREAD_DEFINE(s1_id, CONFIG_MAIN_STACK_SIZE, s1_thread, NULL, NULL, NULL, 2, 0, 0); Limitation 考虑到Zbus的benchmark,不适用于传输高速流数据。...

2024-05-10 · 5 min

Zephyr -- WorkQueue

工作队列是一个内核对象,它使用专用线程以先进先出的方式处理工作项。 WorkQueue通常被中断和高优先级线程用于把一些低优先度的任务放到低优先级的线程中执行。相当于一种中断下半部的实现,用来节省时间。 Delayable work 首先启动一个timeout定时器,经过指定时间后才会把work item放入work queue。 Triggered work System Workqueue Kernel定义了一个全局系统工作队列,适用于任何应用程序或者kernel code。 尽量使用系统的work queue节省内存,除非满足不了需求,比如需要在work queue中增加blocking的操作,这样就可以新开一个work queue。 How to Use Workqueues Define a Workqueue 定义自己的work queue: #define MY_STACK_SIZE 512 #define MY_PRIORITY 5 K_THREAD_STACK_DEFINE(my_stack_area, MY_STACK_SIZE); struct k_work_q my_work_q; k_work_queue_init(&my_work_q); k_work_queue_start(&my_work_q, my_stack_area, K_THREAD_STACK_SIZEOF(my_stack_area), MY_PRIORITY, NULL); Sumbit a Work Item Work Item用k_work来抽象,需要k_work_init()初始化,k_work_submit()加入系统工作队列或k_work_submit_to_queue()提交到指定的工作队列。 struct device_info { struct k_work work; char name[16] } my_device; void my_isr(void *arg) { ... if (error detected) { k_work_submit(&my_device.work); } ....

2024-05-08 · 3 min

Zephyr -- Data Passing

Message Queue 消息队列。 以异步方式在线程之间传输小数据项。 基于ring buffer实现。 void k_msgq_init(struct k_msgq *msgq, char *buffer, size_t msg_size,uint32_t max_msgs); // 和上面的区别是在函数内部动态分配了buffer内存(在堆上) __syscall int k_msgq_alloc_init(struct k_msgq *msgq, size_t msg_size, uint32_t max_msgs); // 如果调用的init函数是k_msgq_alloc_init,可以用cleanup函数释放掉buffer内存。 int k_msgq_cleanup(struct k_msgq *msgq); __syscall int k_msgq_put(struct k_msgq *msgq, const void *data, k_timeout_t timeout); __syscall int k_msgq_get(struct k_msgq *msgq, void *data, k_timeout_t timeout); __syscall int k_msgq_peek(struct k_msgq *msgq, void *data); Msgq init struct data_item_type { uint32_t field1; uint32_t field2; uint32_t field3; }; char my_msgq_buffer[10 * sizeof(struct data_item_type)]; struct k_msgq my_msgq; k_msgq_init(&my_msgq, my_msgq_buffer, sizeof(struct data_item_type), 10); Writing to msgq 往消息队列中放数据,如果msgq满了无法放入,可以调用k_msgq_purge把msgq现存的所有的消息都丢弃。...

2024-05-07 · 4 min

Zephyr -- Polling and Events

Poll 轮询 API 允许单个线程同时等待一个或多个条件得到满足,而无需主动单独查看每个条件。 比如可以同时等待如下条件被满足: a semaphore becomes available a kernel FIFO contains data ready to be retrieved a kernel message queue contains data ready to be retrieved a kernel pipe contains data ready to be retrieved a poll signal is raised 使用poll api前需要将poll events都放入一个数组。 Implementation k_poll() 静态初始化: struct k_poll_event events[4] = { K_POLL_EVENT_STATIC_INITIALIZER(K_POLL_TYPE_SEM_AVAILABLE, K_POLL_MODE_NOTIFY_ONLY, &my_sem, 0), K_POLL_EVENT_STATIC_INITIALIZER(K_POLL_TYPE_FIFO_DATA_AVAILABLE, K_POLL_MODE_NOTIFY_ONLY, &my_fifo, 0), K_POLL_EVENT_STATIC_INITIALIZER(K_POLL_TYPE_MSGQ_DATA_AVAILABLE, K_POLL_MODE_NOTIFY_ONLY, &my_msgq, 0), K_POLL_EVENT_STATIC_INITIALIZER(K_POLL_TYPE_PIPE_DATA_AVAILABLE, K_POLL_MODE_NOTIFY_ONLY, &my_pipe, 0), }; runtime初始化:...

2024-05-06 · 2 min

Zephyr -- Time Subsystem

Timer Consumer kernel.h 提供出来可以使用的接口: // expiry_fn: timer达到定时的回调函数,stop_fn: 调用k_timer_stop后的回调函数。 void k_timer_init(struct k_timer *timer, k_timer_expiry_t expiry_fn, k_timer_stop_t stop_fn); // duration: timer第一次的超时时间。传入K_NO_WAIT时,会在最近的一个tick中断到来时立即过期。 // period: timer重复周期的超时时间。当传入K_FOREVER或K_NO_WAIT,在duration过期后timer自动停止 __syscall void k_timer_start(struct k_timer *timer, k_timeout_t duration, k_timeout_t period); __syscall void k_timer_stop(struct k_timer *timer); // 获取距离上一次读status后,timer timeout的次数。调用完后status清零。 __syscall uint32_t k_timer_status_get(struct k_timer *timer); // block当前thread,直到timer的status非0(timer发生timeout)或者timer stop。调用完后status清零。 __syscall uint32_t k_timer_status_sync(struct k_timer *timer); // 返回下一次timer timeout需要的ticks数 __syscall k_ticks_t k_timer_expires_ticks(const struct k_timer *timer); // 返回距离下一次timer timeout的ticks数 __syscall k_ticks_t k_timer_remaining_ticks(const struct k_timer *timer); // 把上面的ticks数转化为ms static inline uint32_t k_timer_remaining_get(struct k_timer *timer) // 设置timer->user_data __syscall void k_timer_user_data_set(struct k_timer *timer, void *user_data); // 获取timer->user_data __syscall void *k_timer_user_data_get(const struct k_timer *timer); 图片来自https://lgl88911....

2024-04-28 · 3 min

Zephyr -- Watchdog Driver

Consumer // config watchdog static inline int wdt_install_timeout(const struct device *dev, const struct wdt_timeout_cfg *cfg); // enable watchdog __syscall int wdt_setup(const struct device *dev, uint8_t options); // disable watchdog __syscall int wdt_disable(const struct device *dev); // feed wathdog __syscall int wdt_feed(const struct device *dev, int channel_id); wdt_install_timeout()需要在wdt_setup()之前。 示例code: //samples/drivers/watchdog/src/main.c struct wdt_timeout_cfg wdt_config = { /* Reset SoC when watchdog timer expires. */ .flags = WDT_FLAG_RESET_SOC, /* Expire watchdog after max window */ ....

2024-04-28 · 1 min

Zephyr -- Condition Variables and Semaphores

Condition Variables Concept 条件变量基本上是一个线程队列,当某些执行状态不符合预期时,线程可以将自己放入该队列中。 k_condvar_wait(): Releases the last acquired mutex. Puts the current thread in the condition variable queue. 其他某个线程在更改所述状态时,可以唤醒一个(或多个)等待线程,通过k_condvar_signal() or k_condvar_broadcast(): Re-acquires the mutex previously released. Returns from k_condvar_wait(). Implementation Defining a Condition Variable struct k_condvar my_condvar; k_condvar_init(&my_condvar); // 或 K_CONDVAR_DEFINE(my_condvar); Waiting on a Condition Variable k_mutex_lock(&mutex, K_FOREVER); /* block this thread until another thread signals cond. While * blocked, the mutex is released, then re-acquired before this * thread is woken up and the call returns....

2024-04-26 · 3 min

Zephyr -- Mutex

Mutex Zephyr Mutex有下面几个重要特性: lock count。该线程lock了mutux的次数。为0的时候表示unlock。 owning thread。 当一个线程拥有锁,其他线程在都在等待锁释放。一旦锁释放后,等待时间最长优先级最高的线程将被调度。 Zephyr中ISRs中不允许使用Mutex。 Reetrant Locking Zephyr Mutex是可重入锁,可多次加锁。 这样拥有锁的线程可以访问相关资源,不用管是否上锁。 可重入锁的目的就是防止死锁,导致同一个线程不可重入上锁代码段,目的就是让同一个线程可以重新进入上锁代码段。 Priority Inheritance 优先级继承。如果当前线程拥有锁,此时来了一个优先级更高的线程想获取锁,该线程会开始block等待。 此时系统会把拥有锁的线程优先级提高到和等待的线程相同,来尽快完成临界区的任务。一旦释放了锁,线程优先级又会调回去。 CONFIG_PRIORITY_CEILING用来配置最高提高到的优先级等级,默认为0为无限制。 Implementation Defining a Mutex struct k_mutex my_mutex; k_mutex_init(&my_mutex); //或 K_MUTEX_DEFINE(my_mutex); Locking a Mutex k_mutex_lock(&my_mutex, K_FOREVER); // 无限阻塞等待获取锁 if (k_mutex_lock(&my_mutex, K_MSEC(100)) == 0) { // 等待100ms,没获取到返回 /* mutex successfully locked */ } else { printf("Cannot lock XYZ display\n"); } Unlocking a Mutex k_mutex_unlock(&my_mutex); Futex Futex(fast userspace mutex)是比Mutex更轻量级的互斥访问原语。是一种用于用户空间应用程序的通用同步工具。 int k_futex_wait(struct k_futex *futex, int expected, k_timeout_t timeout) int k_futex_wake(struct k_futex *futex, bool wake_all) API Reference User Space Mutex API 把k_xxx前缀换成sys_xxx,比如sys_mutex_lock()。...

2024-04-11 · 3 min

Zephyr -- Threads

Threads CONFIG_MULTITHREADING打开Zephyr多线程功能。 Zephyr线程有下面几个关键的特性: Stack area。 线程的栈大小可修改。 thread control block k_thread。用来保存线程的一些metadata。 entry point function。线程开始执行运行的函数。 scheduling priority。支持配置调度优先级。 thread option。提供线程的一些特殊配置。 execution mode。Supervisor/User mode。依赖于CONFIG_USERSPACE。 Lifecycle k_thread_create(): 创建线程。 k_thread_join(): 阻塞等待线程终止。 k_thread_abort(): 发生异常情况,线程可以由自己或其他线程来终结。 k_thread_suspend(), k_thread_resume(): 线程suspend后只有通过resume才能重新调度。 Thread States Thread Stack objects 初始化线程栈相关属性,如果线程只在内核运行,用K_KERNEL_STACK_XXX。 如果是user space线程,用K_THREAD_STACK_XXX。 如果CONFIG_USERSPACE没打开,那么K_THREAD_STACK等于K_KERNEL_STACK。 Thread Priorities 优先级数字越小,优先级越高。 cooperative thread可配置的优先级为 -CONFIG_NUM_COOP_PRIORITIES到-1。 preemptible thread可配置的优先级为0到 (CONFIG_NUM_PREEMPT_PRIORITIES-1)。 可见cooperative thread的优先级肯定比preemptible thread高。 Cooperative thread是需要主动交出CPU控制权的,否则会一直执行该线程。 Preemptible thread是根据时间片轮转调度的,会自动切换线程。 Meta-IRQ Priorities // TODO: APIs 创建线程: 方法1: k_tid_t k_thread_create(struct k_thread *new_thread, k_thread_stack_t *stack, size_t stack_size, k_thread_entry_t entry, void *p1, void *p2, void *p3, int prio, uint32_t options, k_timeout_t delay) 线程栈必须使用 K_THREAD_STACK_DEFINE or K_KERNEL_STACK_DEFINE定义。 线程栈大小必须是传入给K_THREAD_STACK or K_KERNEL_STACK宏的大小。或者利用K_THREAD_STACK_SIZEOF()/K_KERNEL_STACK_SIZEOF(),对用K_THREAD_STACK/K_KERNEL_STACK创建线程返回的结构体。 e....

2024-04-11 · 2 min