Zephyrで排他制御(mutex)
Zephyrで排他制御(mutex)を確認。例によって、Wio Terminal利用。
題材
- mainスレッド:画面表示(更新)のハンドリング
- 2つのスレッド
- スレッドA:シリアルから入力された文字をディスプレイに表示するようmainスレッドに通達
- スレッドB:インクリメントされるカウンタを定期的にディスプレーに表示するようmainスレッドに通達
- ディスプレイ表示が排他対象
なお、排他制御を実感するため、故意にループするなどがソースコード上に存在する。
参考ソースコード
いずれも、Zephyrのサンプルコードである。
環境ファイル
これらについての説明は、「Zephyr(その2:ディスプレイ)」参照。
CMakeLists.txt
CMakeLists.txt
cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(mutexTest)
FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})
prj.conf
prj.conf
CONFIG_LV_Z_MEM_POOL_SIZE=16384
CONFIG_MAIN_STACK_SIZE=4096
CONFIG_CONSOLE_SUBSYS=y
CONFIG_CONSOLE_GETLINE=y
CONFIG_DISPLAY=y
CONFIG_LVGL=y
CONFIG_LV_FONT_MONTSERRAT_20=y
wio_terminal.overlay
wio_terminal.overlay
&ili9341 {
status = "okay";
rotation = <90>;
width = <320>;
height = <320>;
};
ソースコード
src/main.c
#include <string.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/console/console.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/display.h>
#include <zephyr/drivers/gpio.h>
#include <lvgl.h>
#include <stdio.h>
#define STACK_SIZE 1024
#define PRIORITY 7
#define BASE 3000000
struct k_thread thread_a_data;
struct k_thread thread_b_data;
K_THREAD_STACK_DEFINE(thread_a_stack, STACK_SIZE);
K_THREAD_STACK_DEFINE(thread_b_stack, STACK_SIZE);
K_MUTEX_DEFINE(mutex); // mutex変数初期化
K_CONDVAR_DEFINE(condvar); // スレッドAおよびBとmain()スレッドとの同期用条件変数初期化
// スレッドA
void thread_a_entry(void *p1, void *p2, void *p3)
{
volatile uint32_t loop_count;
lv_obj_t *label = (lv_obj_t *)p1;
char *s, buf[64];
ARG_UNUSED(p2);
ARG_UNUSED(p3);
console_getline_init();
while (1) { // ループ
printk("Entered Text to show at display: ");
s = console_getline(); // シリアルでの文字列入力待ち
sprintf(buf, "<Entered Text>\n%s", s);
k_mutex_lock(&mutex, K_FOREVER); // mutexによるロック
lv_label_set_text(label, buf); // 表示ラベルに入力文字列セット
//lv_timer_handler(); ここでの画面更新不可
k_condvar_signal(&condvar); // main()スレッドへ更新依頼
for (loop_count = 0; loop_count < 20*BASE; loop_count++)
; // ある程度表示を続けるための時間稼ぎ
k_mutex_unlock(&mutex); // アンロック
// 文字列入力待ちがあり、k_sleep()による意図的スレッドスイッチ不要
}
}
// スレッドB
void thread_b_entry(void *p1, void *p2, void *p3)
{
volatile uint32_t loop_count = 0;
char count_str[16];
lv_obj_t *label = (lv_obj_t *)p1;
ARG_UNUSED(p2);
ARG_UNUSED(p3);
while (1) { // ループ
loop_count++;
if (loop_count % (10*BASE) == 0) { // 特定時のみ実行
sprintf(count_str, "%d", loop_count / (10*BASE));
k_mutex_lock(&mutex, K_FOREVER); // mutexによるロック
lv_label_set_text(label, count_str); // 表示ラベルにカウンタセット
k_condvar_signal(&condvar); // main()スレッドへ更新依頼
k_mutex_unlock(&mutex); // アンロック
k_sleep(K_MSEC(10)); // スレッドスイッチ
}
}
}
// mainスレッド
int main(void)
{
const struct device *display_dev;
lv_obj_t *disp_label;
int ret;
display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));
if (!device_is_ready(display_dev)) {
printk("Device not ready\n");
return 0;
}
LV_FONT_DECLARE(lv_font_montserrat_20);
disp_label = lv_label_create(lv_screen_active());
lv_obj_set_style_text_font(disp_label, &lv_font_montserrat_20, 0);
lv_obj_align(disp_label, LV_ALIGN_CENTER, 0, -30);
lv_timer_handler();
ret = display_blanking_off(display_dev); // Must
if (ret < 0 && ret != -ENOSYS) {
printk("Failed to turn blanking off (error %d)", ret);
return 0;
}
// スレッドA作成および起動
k_thread_create(&thread_a_data,
thread_a_stack,
K_THREAD_STACK_SIZEOF(thread_a_stack),
thread_a_entry,
(void*)disp_label, NULL, NULL, // 引数(表示テキスト用ラベル)
PRIORITY,
0,
K_NO_WAIT);
// スレッドB作成および起動
k_thread_create(&thread_b_data,
thread_b_stack,
K_THREAD_STACK_SIZEOF(thread_b_stack),
thread_b_entry,
(void*)disp_label, NULL, NULL, // 引数(表示テキスト用ラベル)
PRIORITY,
0,
K_NO_WAIT);
// main()スレッドのループ:LVGLの画面更新
while (1) {
// スレッドAおよびBでラベルが更新されるのを待つ。これがないとカウンタ抜けが発生。
k_condvar_wait(&condvar, &mutex, K_FOREVER);
lv_timer_handler(); // ディスプレイの描画・更新
k_sleep(K_MSEC(10));
}
return 0;
}
// スレッド静的起動では動作せず。main()からのスレッド起動が必須。
//K_THREAD_DEFINE(thread_a_id, STACK_SIZE, thread_a_entry, NULL, NULL, NULL, PRIORITY, 0, 0);
//K_THREAD_DEFINE(thread_b_id, STACK_SIZE, thread_b_entry, NULL, NULL, NULL, PRIORITY, 0, 0);
基本的にはソースコード内のコメントを参照してほしい。あえて追記すると、下記となる。
- k_mutex_lock():mutexロック
- k_mutex_unlock():mutexアンロック
- k_condvar_signal:k_condvar_wait()への通知
- k_condvar_wait():k_condvar_signal()待ち
- 画面表示ラベルを定義したスレッドで画面更新(lv_timer_handler())することが必須
なお、ディスプレイ表示部分については、「Zephyr(その2:ディスプレイ)」参照。
検証
EOF

