💡

STM32でZephyrRTOS入門~ADC+PWMを使ったマルチスレッド処理~

に公開

はじめに

これまでWindows上にZephyrRTOSの開発環境を構築し、NucleoF103RBボードを用いて学習を進めてきた。
https://zenn.dev/gotoooo/articles/894213f95372bd

今回は、Zephyrのマルチスレッド処理を用いて、ADCで読み取ったボリューム抵抗の値に基づいてPWMでLEDの明るさを制御するサンプルを作成し、仕組みの理解に取り組みたい。

なおNucleoF103RBのオンボードLEDを直接PWM制御することはできないため、PA8に外付けLEDを接続して制御することにする。

ボリューム抵抗はPA0に接続された状態を想定している。

Zephyrにおけるマルチスレッド処理

マルチスレッド処理の概要については以下動画を参考にした。

参考:
https://www.youtube.com/watch?v=3OSKV2jrAHM

大まかな流れ場以下の通り。

  1. スタックサイズを決める
  2. スレッドで実行する処理を実装
  3. スレッドを生成する

今回作成するサンプルでは、以下2つの処理を走らせて、マルチスレッドらしい動きを実装する。

  • ADCでボリューム抵抗を定期的に読み取るスレッド
  • PWMに値を反映させてLEDの明るさを調整するスレッド

コード

main.cのコード全体
main.c
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/pwm.h>
#include <zephyr/logging/log.h>

LOG_MODULE_REGISTER(app, LOG_LEVEL_INF);

// デバイス取得
static const struct device *adc_dev = DEVICE_DT_GET(DT_NODELABEL(adc1));
static const struct pwm_dt_spec led = PWM_DT_SPEC_GET(DT_ALIAS(pwm_led0));

// メッセージキュー
K_MSGQ_DEFINE(brightness_q, sizeof(uint16_t), 4, 4);

// ADC設定
#define ADC_CHANNEL 0
#define ADC_RESOLUTION 12
#define ADC_GAIN ADC_GAIN_1
#define ADC_REFERENCE ADC_REF_INTERNAL
#define ADC_ACQUISITION_US 5

static int16_t adc_sample_buffer;

static const struct adc_channel_cfg adc_channel_cfg = {
    .channel_id = ADC_CHANNEL,
    .gain = ADC_GAIN,
    .reference = ADC_REFERENCE,
    .acquisition_time = ADC_ACQUISITION_US,
    .differential = 0,
};

static struct adc_sequence sequence = {
    .channels = BIT(ADC_CHANNEL),
    .buffer = &adc_sample_buffer,
    .buffer_size = sizeof(adc_sample_buffer),
    .resolution = ADC_RESOLUTION,
};

// PWM設定
#define PWM_CHANNEL 1 // TIM1 CH1 = PA8
#define PWM_PERIOD_USEC 20000U // 20ms

// ADC読み取り処理
void adc_thread(void){
    LOG_INF("ADC thread started");

    adc_channel_setup(adc_dev, &adc_channel_cfg);

    while (1) {
        adc_read(adc_dev, &sequence);

        uint16_t brightness = (adc_sample_buffer * 100) / ((1 << ADC_RESOLUTION) - 1);

        k_msgq_put(&brightness_q, &brightness, K_NO_WAIT);

        k_msleep(100);
    }
}

K_THREAD_STACK_DEFINE(adc_stack, 1024);
struct k_thread adc_thread_data;

// PWM制御処理
void pwm_thread(void) {
    LOG_INF("PWM thread started");

    uint16_t brightness;

    while (1) {
        k_msgq_get(&brightness_q, &brightness, K_FOREVER);

        uint32_t duty = (PWM_PERIOD_USEC * brightness) / 100;

        pwm_set_dt(&led, PWM_USEC(PWM_PERIOD_USEC), PWM_USEC(duty));

        LOG_INF("brightness=%u%% duty=%u us", brightness, duty);
    }
}

K_THREAD_STACK_DEFINE(pwm_stack, 1024);
struct k_thread pwm_thread_data;

// main関数
int main(void)
{
    LOG_INF("App start");

    if (!device_is_ready(adc_dev))
    {
        LOG_ERR("ADC device not ready\n");
        return -1;
    }

    if (!pwm_is_ready_dt(&led))
    {
        LOG_ERR("PWM device not ready\n");
        return 0;
    }

    k_thread_create(&adc_thread_data, adc_stack,
                    K_THREAD_STACK_SIZEOF(adc_stack),
                    (k_thread_entry_t)adc_thread,
                    NULL, NULL, NULL,
                    7, 0, K_NO_WAIT);

    k_thread_create(&pwm_thread_data, pwm_stack,
                    K_THREAD_STACK_SIZEOF(pwm_stack),
                    (k_thread_entry_t)pwm_thread,
                    NULL, NULL, NULL,
                    7, 0, K_NO_WAIT);
                    
    return 0;
}
zephyr/prj.conf
CONFIG_LOG=y
CONFIG_LOG_DEFAULT_LEVEL=3

CONFIG_ADC=y
CONFIG_PWM=y
CONFIG_PWM_STM32=y

CONFIG_MAIN_STACK_SIZE=2048
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
CONFIG_THREAD_NAME=y

zephyr/boards/nucleo_f103rb.overlay
/ {
    pwmleds {
        compatible = "pwm-leds";
        red_pwm_led: red_pwm_led {
            pwms = <&pwm1 1 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
        };
    };

    aliases {
        pwm-led0 = &red_pwm_led;
    };
};

おわりに

今回のサンプルコードを通じて、ZephyrRTOSにおけるADCとPWMの基本的な使い方、そしてマルチスレッド処理の実装方法について理解を深めることができた。
メッセージキューの他にもセマフォやミューテックスなど、Zephyrが提供する多様な同期機構を活用することで、より複雑なアプリケーションの開発が可能になる。
今後は、今回の知見を基にして、さらに高度な機能やデバイスの制御にトライしてみたい。

Discussion