まんま!の備忘録

ソフトウェア・ファームウェア・ハードウェア関連の備忘録

Zephyr RTOS のRaspberry Pi Pico(RP2040)のボードをカスタマイズ

Zephyr RTOSを使用して、Picoボードに追加のLED、可変抵抗を追加して、その状態をUSBシリアルにて通知するファームウェアを作ってみます。
これ最後に、精神的に落ち着くまでこの系統のブログ書くのやめます。
落ち着いたら再開するかもです。

前回こちらでサンプルの動作確認をしました。
taogya.hatenablog.com

ただ、これはオンボードLEDのエイリアス定義しかないので、以下のどれかで対応する必要があります。
1. ボード設定を修正する。
2. 新規ボードを作成する。
3. オーバーレイファイルを作成する。
1は、元ファイルをいじるのでなんか嫌です。
今後カスタムボード作成する予定なので、練習として新規ボードを作成したいと思います。
その上でオーバーレイする方法をまとめます。


こちらを参考に作成していきます。
docs.zephyrproject.org

ボードの作成

作成するファイル

参考URLを見ると必要なものは以下。(docは除外している)

zephyrproject/zephyr/boards//plank
├── board.yml
├── board.cmake
├── CMakeLists.txt
├── Kconfig.plank
├── Kconfig.defconfig
├── plank_defconfig
├── plank__defconfig
├── plank.dts
├── plank_.dts
└── plank.yaml

rpi_picoにあるのは以下。(doc、supportは除外している)

zephyrproject/zephyr/boards/raspberrypi/rpi_pico
├── board.yml
├── board.cmake
├── Kconfig.rpi_pico
├── Kconfig.defconfig
├── rpi_pico_defconfig
├── rpi_pico.dts
├── rpi_pico-common.dtsi
├── rpi_pico-pinctrl.dtsi
├── rpi_pico.yaml
├── rpi_pico_rp2040_w_defconfig
├── rpi_pico_rp2040_w.dts
└── rpi_pico_rp2040_w.yaml

supportにはOn Chip Debugger の設定ファイルが入っているように見えますが、デバッガー持っていないので除外です。

Pico Wではないので、rp2040_wは不要になります。
なので、各ファイルをコピーし、ファイル名を編集します。

zephyrproject/zephyr/boards/raspberrypi/rpi_pico_custom
├── board.yml
├── board.cmake
├── Kconfig.rpi_pico_custom
├── Kconfig.defconfig
├── rpi_pico_custom_defconfig
├── rpi_pico_custom.dts
├── rpi_pico_custom-common.dtsi
├── rpi_pico_custom-pinctrl.dtsi
└── rpi_pico_custom.yaml

次にファイルを修正していきます。

board.yml

ボード名、SoC、バリアントなどのボードの高レベルのメタデータを記述する YAML ファイル。
name を修正します。また、wはいらないのでvariants以下は消します。

board:
  name: rpi_pico_custom
  vendor: raspberrypi
  socs:
  - name: rp2040
    variants:
    - name: w

board.cmake

フラッシュおよびデバッグのサポートに使用されます。
修正不要。

Kconfig.rpi_pico_custom

SoC およびその他のボードと SoC 関連の設定を選択するための基本ソフトウェア構成。
BOARD_ 以降はボード名の大文字が入るようなので、以下箇所をそのように設定します。

:
config BOARD_RPI_PICO_CUSTOM
:

Kconfig.defconfig

構成システム (Kconfig)形式のソフトウェア構成 。
BOARD_ 以降はボード名の大文字が入るようなので、以下箇所をそのように設定します。

:
if BOARD_RPI_PICO_CUSTOM
:
endif # BOARD_RPI_PICO_CUSTOM

rpi_pico_custom_defconfig

Kconfig.conf 形式のソフトウェア構成。
今回はピコボードそのままなので、修正不要。
prj.conf でプロジェクトごとに変更することもできるようです。

rpi_pico_custom.dts

devicetree形式のハードウェアの説明。
以下箇所をボード名に合わせます。

:
#include "rpi_pico_custom-common.dtsi"
:

ここにカスタムボードに追加したLEDやスイッチなど、デフォルトで実装されている制御のエイリアスを追加したりします。
今回はPicoボードそのままで使って、それに追加パーツをつなげるので、追加パーツのエイリアスは、後ほどオーバーレイファイルで定義します。

rpi_pico_custom-common.dtsi

通常SoC(System on Chip)固有の説明。w モデルとの共通設定かと。
以下箇所をボード名に合わせます。

:
#include #include "rpi_pico_custom-pinctrl.dtsi"
:

rpi_pico_custom-pinctrl.dtsi

通常SoC(System on Chip)固有の説明。デフォルトのピン制御設定かな。
GPIO 22 にPWMを割り当てます。

:
	pwm_ch4b_default: pwm_ch4b_default {
		group1 {
 			pinmux = <PWM_4B_P25>,<PWM_3A_P22>;
		};
	};
:

rpi_pico_custom.yaml

テスト ランナー (Twister)によって使用されるさまざまなメタデータを含む YAML ファイル 。
以下箇所をボード名に合わせます。

identifier: rpi_pico_custom
:


ボード上のFlashとか変更入る場合は、そのほか変更するところはあると思いますが、一旦ボード作成は以上。
このボードでLチカできるか試してみます。Picoのボタンを押しながらPCに接続して、

$ cd ~/develop/zephyrproject
$ . ./.venv/bin/activate
$ cd zephyr
$ rm -rf build
$ west build -b rpi_pico_custom samples/basic/blinky
$ cp build/zephyr/zephyr.uf2 /Volumes/RPI-RP2

ソースコード

作成するファイル

適当なディレクトリに作成します。

src
├── prj.conf
├── rpi_pico_custom.overlay
├── main.cpp
└── CMakeLists.txt

今回作成するプログラム仕様は以下とする。

  • 可変抵抗の抵抗値(電圧値)でPWM のduty比を変更する。(0V -> 0%, 3.3V -> 100%)
  • PWMの更新は10ms周期に行う。この時に、今のLEDの状態(PWM duty比)をUSBシリアルにて通知する。

prj.conf

プロジェクト固有の設定。

CONFIG_GPIO=y
CONFIG_ADC=y
CONFIG_PWM=y

CONFIG_USB_DEVICE_STACK=y # Enables the USB Device Stack (required for USB Serial)
CONFIG_USB_DEVICE_PRODUCT="RPi Pico USB Serial Console" # Gives the USB Device a human-readable name (optional)

CONFIG_SERIAL=y # Enable options for serial drivers
CONFIG_CONSOLE=y # Enables a console for use with USB CDC applications
CONFIG_UART_CONSOLE=y # Sets the default for printk and printf to output to the UART serial console
CONFIG_UART_LINE_CTRL=y # This enables the API for apps to control the serial line, such as baud rate, CTS and RTS.

CONFIG_USB_DEVICE_VID=0xF055
CONFIG_USB_DEVICE_PID=0x1234

CONFIG_SYS_CLOCK_TICKS_PER_SEC=1000

rpi_pico_custom.overlay

オーバーレイファイル。
ここに追加パーツのエイリアスを定義します。エイリアスは以下の定義とする。

/ {
    chosen {
        zephyr,console = &cdc_acm_uart0;
    };
    zephyr,user {
        io-channels = <&adc 0>;
        io-channel-names = "VR0";
    };
	pwm_leds {
		compatible = "pwm-leds";
		status = "disabled";
		pwm_led1: pwm_led_1 {
			pwms = <&pwm 6 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
			label = "PWM_LED1";
		};
	};

	aliases {
		pwm-led1 = &pwm_led1;
	};
	
};

&zephyr_udc0 {
    cdc_acm_uart0: cdc_acm_uart0 {
        compatible = "zephyr,cdc-acm-uart";
        label = "CDC_ACM_0";
    };
};

&adc {
	#address-cells = <1>;
	#size-cells = <0>;

	channel@0 {
		reg = <0>;
		zephyr,gain = "ADC_GAIN_1";
		zephyr,reference = "ADC_REF_INTERNAL";
		zephyr,acquisition-time = ;
		zephyr,resolution = <12>;
	};
};

&pwm_led0 {
	status = "okay";
};

&pwm_led1 {
	status = "okay";
};

&pwm {
	status = "okay";
	divider-frac-4 = <15>;
	divider-int-4 = <255>;
	divider-frac-3 = <15>;
	divider-int-3 = <255>;
};

main.cpp

#include 
#include 
#include 
#include 
#include 
#include 
#include 

static const struct adc_dt_spec vr_dt = ADC_DT_SPEC_GET_BY_NAME(DT_PATH(zephyr_user), vr0);
static const struct pwm_dt_spec on_board_led = PWM_DT_SPEC_GET(DT_ALIAS(pwm_led0));
static const struct pwm_dt_spec add_led = PWM_DT_SPEC_GET(DT_ALIAS(pwm_led1));

void init_pwm()
{
	pwm_is_ready_dt(&on_board_led);
	pwm_is_ready_dt(&add_led);
}

void init_usb()
{
	const struct device *usb_device = DEVICE_DT_GET(DT_CHOSEN(zephyr_console));
	uint32_t dtr = 0;
	usb_enable(NULL);
	while (!dtr)
	{
		uart_line_ctrl_get(usb_device, UART_LINE_CTRL_DTR, &dtr);
		k_sleep(K_MSEC(100));
	}
	printk("USB ready\n");
}

void init_adc(void)
{
	adc_is_ready_dt(&vr_dt);
	adc_channel_setup_dt(&vr_dt);
	printk("ADC ready\n");
}

void update_adc_thread(void *p1, void *p2, void *p3)
{
	uint16_t buf;
	int32_t vref_mv = (int32_t)adc_ref_internal(vr_dt.dev);
	struct adc_sequence sequence = {
		.buffer = &buf,
		.buffer_size = sizeof(buf),
	};
	uint32_t max_period = PWM_MSEC(5U);
	int32_t pwm = 0;
	int32_t duty = 0;

	(void)adc_sequence_init_dt(&vr_dt, &sequence);

	while (1)
	{
		int32_t val_mv;
		adc_read_dt(&vr_dt, &sequence);
		val_mv = (int32_t)buf;
		adc_raw_to_millivolts_dt(&vr_dt, &val_mv);
		pwm = (val_mv * 100) / vref_mv;
		duty = max_period * pwm / 100;
		pwm_set_dt(&on_board_led, max_period, duty);
		pwm_set_dt(&add_led, max_period, duty);
		printk("%" PRId32 " %\n", pwm);

		k_sleep(K_MSEC(10));
	}
}

int main(void)
{
	init_usb();
	init_adc();
	init_pwm();
	update_adc_thread(NULL, NULL, NULL);

	return 0;
}

// K_THREAD_DEFINE(adc_tid, 50,
// 				update_adc_thread, NULL, NULL, NULL,
// 				5, 0, 0);

CMakeLists.txt

# SPDX-License-Identifier: Apache-2.0

cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(sample_app)

target_sources(app PRIVATE main.cpp)

可変抵抗を回すとLEDの光の強さが変わります。
リポジトリ作成しました。
github.com