0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Zephyr(その4:排他制御)

0
Posted at

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:ディスプレイ)」参照。

検証

画面表示。スレッドAによる入力テキスト表示。

スレッドBによるカウンタ表示。

EOF

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?