Skip to main content

Seeed Studio XIAO nRF54L15 Sense のピン多重化

使いやすさのため、以下のピン多重化の例はすべて PlatformIO で実行されます。XIAO nRF54L5 の設定と使用ガイドについては、このリンクをクリックしてください。

tip

VS Code をベースに、nRF Connect SDK で以下のケースを使用したい場合は、提供された接続を参照し、app.overlay ファイルを追加して prj.conf の内容を変更してください。

XIAO nRF54L15 オーバーレイファイルの追加と conf ファイルの変更

オンボードキー

XIAO nRF54L15(Sense) には、デバイスの動作とファームウェアプログラミングにおいて重要な役割を果たす2つの重要な物理ボタンが搭載されています:リセットボタンユーザーボタン です。これらの機能を理解することは、日常使用とファームウェア更新において不可欠です。


リセットボタン

リセットボタンは、デバイスでハードリセット操作を実行するために使用されます。

  • 機能:
    • 強制再起動: このボタンを押すと、現在のデバイス操作がすべて即座に中断され、電源サイクルと同様に再起動します。
    • スタックしたプログラムの解決: デバイスで実行中のプログラムがクラッシュ、無限ループに入る、または応答しなくなった場合、リセットボタンを押すことが正常な動作状態に強制的に戻す最も迅速な方法です。
    • ファームウェアへの影響なし: リセット操作は、デバイスにすでにプログラムされているファームウェアを消去または変更しません。現在実行中のアプリケーションを単純に再起動するだけです。

ユーザーボタン

ユーザーボタンは、アプリケーション内で柔軟な制御を提供する多用途でプログラム可能な入力です。

機能:

  • カスタマイズ可能な入力:リセットボタンの固定機能とは異なり、ユーザーボタンのアクションは、プログラムされたファームウェアによって完全に定義されます。

  • イベントトリガー:特定のイベントをトリガーしたり、異なる機能を制御したり、アプリケーションの汎用入力として機能するようにプログラムできます。

デジタル

ハードウェア準備

Seeed Studio XIAO nRF54L15 SenseSeeed Studio Expansion Base for XIAO with Grove OLEDGrove - Relay

ソフトウェア実装


#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/logging/log.h>


LOG_MODULE_REGISTER(main_app, CONFIG_LOG_DEFAULT_LEVEL);

static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET(DT_ALIAS(sw1), gpios); // Get the button device from the device tree alias
static const struct gpio_dt_spec relay = GPIO_DT_SPEC_GET(DT_ALIAS(relay0), gpios); // Get the relay device from the device tree alias

int main(void)
{
int ret;

LOG_INF("Starting Zephyr button and relay example...");

/* Check if GPIO devices are ready */
if (!gpio_is_ready_dt(&button)) {
LOG_ERR("Button device %s is not ready", button.port->name);
return -1;
}

if (!gpio_is_ready_dt(&relay)) {
LOG_ERR("Relay device %s is not ready", relay.port->name);
return -1;
}

/* Configure button pin as input mode */
ret = gpio_pin_configure_dt(&button, GPIO_INPUT);
if (ret != 0) {
LOG_ERR("Failed to configure %s pin %d (error %d)", button.port->name, button.pin, ret);
return -1;
}

/* Configure relay pin as output mode */
ret = gpio_pin_configure_dt(&relay, GPIO_OUTPUT_ACTIVE);
if (ret != 0) {
LOG_ERR("Failed to configure %s pin %d (error %d)", relay.port->name, relay.pin, ret);
return -1;
}

LOG_INF("Press the button to toggle the relay...");

while (1) {
/* Read button state */
int button_state = gpio_pin_get_dt(&button);

/* Check if read is successful */
if (button_state < 0) {
LOG_ERR("Error reading button pin: %d", button_state);
return -1;
}

if (button_state == 0) { // Button pressed (ACTIVE_LOW)
gpio_pin_set_dt(&relay, 1); // Turn on relay (high level)
} else { // Button not pressed
gpio_pin_set_dt(&relay, 0); // Turn off relay (low level)
}

k_msleep(10); /* Short delay to avoid busy looping */
}
return 0;
}

デバイスツリー設定

static const struct gpio_dt_spec button = GPIO_DT_SPEC_GET(DT_ALIAS(sw1), gpios);

  • このコード行は、Zephyr のデバイスツリーシステムを利用して、sw1 という名前のエイリアスを通じてボタンの GPIO デバイス情報を取得します。このアプローチにより、コードが特定のハードウェアピンから分離され、移植性が向上します。

static const struct gpio_dt_spec relay = GPIO_DT_SPEC_GET(DT_ALIAS(relay0), gpios);

  • 同様に、このコード行は relay0 という名前のリレー GPIO デバイスの情報を取得します。

デバイス準備状態チェック

if (!gpio_is_ready_dt(&button))if (!gpio_is_ready_dt(&relay))

  • プログラムが操作を開始する前に、コードはボタンとリレーデバイスが正常に初期化され、準備ができているかどうかをチェックします。これは Zephyr ドライバープログラミングのベストプラクティスであり、デバイスが適切に設定されていない場合にプログラムがクラッシュするのを防ぎます。

ピン設定

gpio_pin_configure_dt(&button, GPIO_INPUT);

  • このコード行は、ボタンの GPIO ピンを入力モードに設定します。これはピンのレベルを読み取るために必要なステップであり、プログラムはピンの電圧レベルを監視してボタンが押されているかどうかを判断します。

gpio_pin_configure_dt(&relay, GPIO_OUTPUT_ACTIVE);

  • このコード行は、リレーの GPIO ピンを出力モードに設定します。GPIO_OUTPUT_ACTIVE フラグは通常、設定後にピンがアクティブになることを示し、リレーを制御する準備をします。

メインループロジック

while (1): コードは無限ループに入り、以下のアクションを継続的に実行します。

int button_state = gpio_pin_get_dt(&button);: 各ループで、プログラムはボタンピンの現在のレベル状態を読み取ります。

if (button_state == 0): このロジックは、ボタンが押されているかどうかをチェックします。多くの回路設計では、ボタンを押すとピンがグランド(GND)に接続され、レベル 0(つまり、ロー)になります。

gpio_pin_set_dt(&relay, 1);: ボタンの状態が 0(押されている)の場合、リレーピンを 1(ハイ)に設定し、リレーを閉じて接続されているデバイス(例:ランプ)をオンにします。

else: ボタンが押されていない場合(状態が 1)、gpio_pin_set_dt(&relay, 0); を実行してリレーピンを 0(ロー)に設定し、リレーを閉じて接続されているデバイスをオフにします。

k_msleep(10);: コードは各ループの最後に 10 ミリ秒の短い遅延を追加して、CPU がビジー状態になることを避けます。これは簡単なアンチジッター処理です。これは、ボタンの物理的なジッターによる複数のトリガーを防ぎ、消費電力も削減する簡単なアンチジッター処理です。

結果グラフ

アナログ

ハードウェア準備

Seeed Studio XIAO nRF54L15 SenseGrove-Variable Color LEDGrove-Rotary Angle Sensor Seeed Studio Grove Base for XIAO

ソフトウェア実装


#include <zephyr/kernel.h>
#include <zephyr/drivers/adc.h>
#include <zephyr/drivers/pwm.h>
#include <zephyr/logging/log.h>

// Register log module
LOG_MODULE_REGISTER(pot_pwm_example, CONFIG_LOG_DEFAULT_LEVEL);

// --- ADC Configuration ---
#if !DT_NODE_EXISTS(DT_PATH(zephyr_user)) || \
!DT_NODE_HAS_PROP(DT_PATH(zephyr_user), io_channels)
#error "No suitable devicetree overlay specified for ADC channels"
#endif

#define DT_SPEC_AND_COMMA(node_id, prop, idx) \
ADC_DT_SPEC_GET_BY_IDX(node_id, idx),

static const struct adc_dt_spec adc_channels[] = {
DT_FOREACH_PROP_ELEM(DT_PATH(zephyr_user), io_channels, DT_SPEC_AND_COMMA)
};

// Define the index of the potentiometer ADC channel in the adc_channels array
#define POTENTIOMETER_ADC_CHANNEL_IDX 1

// --- PWM Configuration ---
// Get PWM LED device
static const struct pwm_dt_spec led = PWM_DT_SPEC_GET(DT_ALIAS(pwm_led));

// Define PWM period as 1 millisecond (1,000,000 nanoseconds)
// This corresponds to a 1 kHz PWM frequency, suitable for LED brightness adjustment without visible flicker
#define PWM_PERIOD_NS 1000000UL

int main(void)
{
int ret;
uint16_t adc_raw_value;
int32_t adc_millivolts;

LOG_INF("Starting Zephyr Potentiometer to PWM example...");

// --- ADC initialization and setup ---
if (!adc_is_ready_dt(&adc_channels[POTENTIOMETER_ADC_CHANNEL_IDX])) {
LOG_ERR("ADC controller device %s not ready", adc_channels[POTENTIOMETER_ADC_CHANNEL_IDX].dev->name);
return 0;
}

ret = adc_channel_setup_dt(&adc_channels[POTENTIOMETER_ADC_CHANNEL_IDX]);
if (ret < 0) {
LOG_ERR("Could not setup ADC channel for potentiometer (%d)", ret);
return 0;
}
LOG_INF("ADC device %s, channel %d ready for potentiometer.",
adc_channels[POTENTIOMETER_ADC_CHANNEL_IDX].dev->name,
adc_channels[POTENTIOMETER_ADC_CHANNEL_IDX].channel_id);

// --- PWM initialization and setup ---
if (!device_is_ready(led.dev)) {
LOG_ERR("Error: PWM device %s is not ready", led.dev->name);
return 0;
}
LOG_INF("PWM Period for LED set to %lu ns (%.1f Hz)",
PWM_PERIOD_NS, (double)NSEC_PER_SEC / PWM_PERIOD_NS); // Use PWM_PERIOD_NS instead of led.period


// ADC sequence configuration
struct adc_sequence sequence = {
.buffer = &adc_raw_value,
.buffer_size = sizeof(adc_raw_value),
.resolution = adc_channels[POTENTIOMETER_ADC_CHANNEL_IDX].resolution,
};

// --- Main loop ---
while (1) {
(void)adc_sequence_init_dt(&adc_channels[POTENTIOMETER_ADC_CHANNEL_IDX], &sequence);

ret = adc_read(adc_channels[POTENTIOMETER_ADC_CHANNEL_IDX].dev, &sequence);
if (ret < 0) {
LOG_ERR("Error %d: ADC read failed for channel %d",
ret, adc_channels[POTENTIOMETER_ADC_CHANNEL_IDX].channel_id);
k_msleep(100);
continue;
}

int sensor_value = adc_raw_value;

uint32_t max_adc_raw = (1U << adc_channels[POTENTIOMETER_ADC_CHANNEL_IDX].resolution) - 1;

// --- Map ADC raw value to PWM duty cycle ---
uint32_t output_duty_ns = (PWM_PERIOD_NS * sensor_value) / max_adc_raw;

// Set PWM duty cycle
ret = pwm_set_dt(&led, PWM_PERIOD_NS, output_duty_ns);
if (ret < 0) {
LOG_ERR("Error %d: failed to set PWM duty cycle.", ret);
}

// --- Print information ---
adc_millivolts = sensor_value;
ret = adc_raw_to_millivolts_dt(&adc_channels[POTENTIOMETER_ADC_CHANNEL_IDX], &adc_millivolts);
if (ret < 0) {
LOG_WRN("ADC to mV conversion not supported/failed: %d", ret);
LOG_INF("Sensor Raw Value = %d\tOutput Duty (ns) = %u", sensor_value, output_duty_ns);
} else {
LOG_INF("Sensor Raw Value = %d (%d mV)\tOutput Duty (ns) = %u",
sensor_value, adc_millivolts, output_duty_ns);
}

k_msleep(100);
}
return 0;
}

ADC(アナログ-デジタル変換器)とPWM(パルス幅変調)デバイス設定

  • pot_pwm_example ログモジュール:

    • LOG_MODULE_REGISTER(pot_pwm_example, CONFIG_LOG_DEFAULT_LEVEL):これはpot_pwm_exampleという名前のログモジュールを登録し、そのログレベルをシステムのデフォルト設定に設定します。これによりデバッグが容易になります。
  • ADC設定:

    • #if !DT_NODE_EXISTS(DT_PATH(zephyr_user)) ... #endif:このプリプロセッサディレクティブは、ADCチャンネル定義を含む有効なオーバーレイファイルが存在することを確認するDevice Treeチェックです。これにより、ユーザーは特定のハードウェアに対して正しい設定を提供する必要があります。

    • static const struct adc_dt_spec adc_channels[];:このコードの部分は、ZephyrのDevice Treeを活用して、設定されたすべてのADCチャンネルの情報を自動的に取得します。このアプローチにより、コードは柔軟性があり、手動設定変更なしに異なるハードウェア間で移植可能になります。

    • #define POTENTIOMETER_ADC_CHANNEL_IDX 1:ポテンショメータが接続されているadc_channels配列内のチャンネルを指定するマクロが定義されています。

  • PWM設定:

    • static const struct pwm_dt_spec led = PWM_DT_SPEC_GET(DT_ALIAS(pwm_led));:この行は、Device Treeからエイリアスpwm_ledのPWMデバイス情報を取得します。これは、ハードウェアデバイスを検索および参照するための標準的なZephyrの手法です。

    • #define PWM_PERIOD_NS 1000000UL:これはPWM信号周期を1ミリ秒(1,000,000ナノ秒)として定義し、これは1 kHzの周波数に対応します。この周波数は、目に見えるちらつきを防ぐのに十分高いため、LED調光に適しています。

初期化とセットアップ

  • ログ情報:

    • LOG_INF("Starting Zephyr Potentiometer to PWM example...");:プログラムの開始時に情報ログメッセージが出力され、例が開始されたことをユーザーに通知します。
  • ADC初期化:

    • !adc_is_ready_dt():ADCデバイスを使用する前に、デバイスが準備完了であることを確認するチェックが実行されます。デバイスが準備できていない場合、エラーがログに記録され、プログラムが終了します。

    • adc_channel_setup_dt():この関数は、ポテンショメータに接続された特定のADCチャンネルを、その解像度とゲインを含めて設定します。

  • PWM初期化:

    • !device_is_ready(led.dev):ADCと同様に、この行はPWMデバイスが準備完了かどうかをチェックします。準備できていない場合、エラーがログに記録され、プログラムが終了します。

    • LOG_INF(...):PWM周期と周波数情報が出力され、ユーザーが設定を確認できるようになります。

  • ADCシーケンス設定:

    • struct adc_sequence sequence:単一のADC変換操作を記述するためのadc_sequence構造体が定義されます。これは結果を格納するバッファ(adc_raw_value)、そのサイズ(sizeof(adc_raw_value))、および使用するADC解像度を指定します。

メインループ コードの中核ロジックは無限のwhile (1)ループ内で実行されます:

  • ADC読み取り:

    • adc_sequence_init_dt(): ADCシーケンスが初期化され、各読み取りで正しい設定が使用されることを保証します。

    • adc_read(): これはADC変換をトリガーして、ポテンショメータからアナログ値を読み取ります。読み取りが失敗した場合、エラーがログに記録され、プログラムは100ミリ秒間一時停止してから続行します。

    • int sensor_value = adc_raw_value;: 生のADC値がsensor_value変数に割り当てられます。

  • ADC値のPWMデューティサイクルへのマッピング:

    • uint32_t max_adc_raw: これは可能な最大生ADC値を計算します。

    • uint32_t output_duty_ns = (PWM_PERIOD_NS * sensor_value) / max_adc_raw;: これがコアマッピングロジックです。生ADC値(sensor_value)をPWM周期(PWM_PERIOD_NS)の範囲に比例してスケールし、LEDの明るさを調整するデューティサイクル値を取得します。

  • PWMデューティサイクルの設定:

    • pwm_set_dt(): この関数は新しく計算されたデューティサイクル(output_duty_ns)をPWMデバイスに適用し、LEDの明るさを即座に変更します。
  • 遅延:

    • k_msleep(100): プログラムは各ループ後に100ミリ秒間一時停止します。これはADC読み取りとPWM更新の頻度を制御し、過度のCPU負荷を防ぎ、安定したユーザーエクスペリエンスを提供します。

結果グラフ

UART

ハードウェア準備

Seeed Studio XIAO nRF54L15 SenseL76K GNSS Module for Seeed Studio XIAO

ソフトウェア実装


Software
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/logging/log.h>
#include <nrfx_power.h>
#include <string.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

// Register log module
LOG_MODULE_REGISTER(gps_app, LOG_LEVEL_INF);

// Type definitions
#define UBYTE uint8_t
#define UWORD uint16_t
#define UDOUBLE uint32_t

// Buffer sizes
#define SENTENCE_SIZE 100
#define BUFFSIZE 800

// NMEA Commands
#define HOT_START "$PMTK101"
#define WARM_START "$PMTK102"
#define COLD_START "$PMTK103"
#define FULL_COLD_START "$PMTK104"
#define SET_PERPETUAL_STANDBY_MODE "$PMTK161"
#define SET_PERIODIC_MODE "$PMTK225"
#define SET_NORMAL_MODE "$PMTK225,0"
#define SET_PERIODIC_BACKUP_MODE "$PMTK225,1,1000,2000"
#define SET_PERIODIC_STANDBY_MODE "$PMTK225,2,1000,2000"
#define SET_PERPETUAL_BACKUP_MODE "$PMTK225,4"
#define SET_ALWAYSLOCATE_STANDBY_MODE "$PMTK225,8"
#define SET_ALWAYSLOCATE_BACKUP_MODE "$PMTK225,9"
#define SET_POS_FIX "$PMTK220"
#define SET_POS_FIX_100MS "$PMTK220,100"
#define SET_POS_FIX_200MS "$PMTK220,200"
#define SET_POS_FIX_400MS "$PMTK220,400"
#define SET_POS_FIX_800MS "$PMTK220,800"
#define SET_POS_FIX_1S "$PMTK220,1000"
#define SET_POS_FIX_2S "$PMTK220,2000"
#define SET_POS_FIX_4S "$PMTK220,4000"
#define SET_POS_FIX_8S "$PMTK220,8000"
#define SET_POS_FIX_10S "$PMTK220,10000"
#define SET_SYNC_PPS_NMEA_OFF "$PMTK255,0"
#define SET_SYNC_PPS_NMEA_ON "$PMTK255,1"
#define SET_NMEA_BAUDRATE "$PMTK251"
#define SET_NMEA_BAUDRATE_115200 "$PMTK251,115200"
#define SET_NMEA_BAUDRATE_57600 "$PMTK251,57600"
#define SET_NMEA_BAUDRATE_38400 "$PMTK251,38400"
#define SET_NMEA_BAUDRATE_19200 "$PMTK251,19200"
#define SET_NMEA_BAUDRATE_14400 "$PMTK251,14400"
#define SET_NMEA_BAUDRATE_9600 "$PMTK251,9600"
#define SET_NMEA_BAUDRATE_4800 "$PMTK251,4800"
#define SET_REDUCTION "$PMTK314,-1"
#define SET_NMEA_OUTPUT "$PMTK314,0,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0"

// Struct definitions
typedef struct
{
double Lon; // GPS Longitude
double Lat; // GPS Latitude
char Lon_area; // E or W
char Lat_area; // N or S
UBYTE Time_H; // Time Hour
UBYTE Time_M; // Time Minute
UBYTE Time_S; // Time Second
UBYTE Status; // 1: Successful positioning, 0: Positioning failed
} GNRMC;

typedef struct
{
double Lon;
double Lat;
} Coordinates;

// Global variables and constants
char const Temp[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
static const double pi = 3.14159265358979324;
static const double a = 6378245.0;
static const double ee = 0.00669342162296594323;
static const double x_pi = 3.14159265358979324 * 3000.0 / 180.0;

static char buff_t[BUFFSIZE] = {0};
static GNRMC GPS;

// UART device and buffers
static const struct device *uart_dev;
static char latest_gnrmc[SENTENCE_SIZE];
static volatile bool new_gnrmc_available = false;

// Function prototypes
void DEV_Uart_SendByte(char data);
void DEV_Uart_SendString(char *data);
void L76X_Send_Command(char *data);
GNRMC L76X_Gat_GNRMC(void);
Coordinates L76X_Baidu_Coordinates(void);
Coordinates L76X_Google_Coordinates(void);
static double transformLat(double x, double y);
static double transformLon(double x, double y);
static Coordinates bd_encrypt(Coordinates gg);
static Coordinates transform(Coordinates gps);

// UART interrupt callback
static void uart_callback(const struct device *dev, void *user_data)
{
ARG_UNUSED(user_data);
static char temp_buffer[SENTENCE_SIZE];
static int temp_index = 0;

while (uart_irq_update(dev) && uart_irq_is_pending(dev))
{
if (uart_irq_rx_ready(dev))
{
uint8_t byte;
if (uart_fifo_read(dev, &byte, 1) == 1)
{
if (byte == '\n')
{
temp_buffer[temp_index] = '\0';
if (strncmp(temp_buffer, "$GNRMC", 6) == 0 || strncmp(temp_buffer, "$PNRMC", 6) == 0)
{
strncpy(latest_gnrmc, temp_buffer, SENTENCE_SIZE);
new_gnrmc_available = true;
}
temp_index = 0;
}
else
{
if (temp_index < SENTENCE_SIZE - 1)
{
temp_buffer[temp_index++] = byte;
}
else
{
temp_index = 0; // Reset on overflow
}
}
}
}
}
}

// Main function
int main(void)
{
// Request constant latency mode for power management
nrfx_power_constlat_mode_request();
LOG_INF("Starting L76X GPS Module Example");

// Initialize UART device
uart_dev = DEVICE_DT_GET(DT_NODELABEL(xiao_serial));
if (!device_is_ready(uart_dev))
{
LOG_ERR("UART device not ready!");
return -1;
}
LOG_INF("UART device initialized.");

// Configure UART interrupt
if (uart_irq_callback_user_data_set(uart_dev, uart_callback, NULL) != 0)
{
LOG_ERR("Failed to set UART callback!");
return -1;
}
uart_irq_rx_enable(uart_dev);
LOG_INF("UART interrupt enabled.");

// Initialize GPS module
L76X_Send_Command(SET_NMEA_OUTPUT);
k_msleep(100);
L76X_Send_Command(SET_POS_FIX_1S);
k_msleep(100);

LOG_INF("GPS module initialized. Waiting for data...");

while (true)
{
// Check for new GNRMC sentence
if (new_gnrmc_available)
{
strncpy(buff_t, latest_gnrmc, BUFFSIZE);
new_gnrmc_available = false;

// Log raw GNRMC sentence for debugging
LOG_INF("Raw GNRMC: %s", buff_t);

// Parse GNRMC data
GPS = L76X_Gat_GNRMC();

// Output GPS data
LOG_INF("\n--- GPS Data ---");
LOG_INF("Time (GMT+8): %02d:%02d:%02d", GPS.Time_H, GPS.Time_M, GPS.Time_S);
if (GPS.Status == 1)
{
LOG_INF("Latitude (WGS-84): %.6f %c", GPS.Lat, GPS.Lat_area);
LOG_INF("Longitude (WGS-84): %.6f %c", GPS.Lon, GPS.Lon_area);

// Coordinate conversion
Coordinates baidu_coords = L76X_Baidu_Coordinates();
LOG_INF("Baidu Latitude: %.6f", baidu_coords.Lat);
LOG_INF("Baidu Longitude: %.6f", baidu_coords.Lon);

Coordinates google_coords = L76X_Google_Coordinates();
LOG_INF("Google Latitude: %.6f", google_coords.Lat);
LOG_INF("Google Longitude: %.6f", google_coords.Lon);
LOG_INF("GPS positioning successful.");
}
else
{
LOG_INF("GPS positioning failed or no valid data.");
}
}
else
{
LOG_INF("No new GNRMC data available.");
}

k_msleep(2000); // Wait 2 seconds before next reading
}

return 0;
}

// Send a single byte
void DEV_Uart_SendByte(char data)
{
uart_poll_out(uart_dev, data);
}

// Send a string
void DEV_Uart_SendString(char *data)
{
while (*data)
{
DEV_Uart_SendByte(*data++);
}
}

// Send L76X command with checksum
void L76X_Send_Command(char *data)
{
char Check = data[1], Check_char[3] = {0};
UBYTE i = 0;
DEV_Uart_SendByte('\r');
DEV_Uart_SendByte('\n');

for (i = 2; data[i] != '\0'; i++)
{
Check ^= data[i]; // Calculate checksum
}
Check_char[0] = Temp[Check / 16 % 16];
Check_char[1] = Temp[Check % 16];
Check_char[2] = '\0';

DEV_Uart_SendString(data);
DEV_Uart_SendByte('*');
DEV_Uart_SendString(Check_char);
DEV_Uart_SendByte('\r');
DEV_Uart_SendByte('\n');
}

// Parse GNRMC data
GNRMC L76X_Gat_GNRMC(void)
{
GNRMC gps = {0}; // Initialize with zeros
UWORD add = 0, x = 0, z = 0, i = 0;
UDOUBLE Time = 0;

add = 0;
while (add < BUFFSIZE)
{
// Look for GNRMC or PNRMC sentence
if (buff_t[add] == '$' && buff_t[add + 1] == 'G' && (buff_t[add + 2] == 'N' || buff_t[add + 2] == 'P') &&
buff_t[add + 3] == 'R' && buff_t[add + 4] == 'M' && buff_t[add + 5] == 'C')
{
x = 0;
for (z = 0; x < 12; z++)
{
if (buff_t[add + z] == '\0')
{
break;
}
if (buff_t[add + z] == ',')
{
x++;
if (x == 1)
{ // Time field
if (buff_t[add + z + 1] != ',')
{ // Check if time field is not empty
Time = 0;
for (i = 0; buff_t[add + z + i + 1] != '.'; i++)
{
if (buff_t[add + z + i + 1] == '\0' || buff_t[add + z + i + 1] == ',')
{
break;
}
Time = (buff_t[add + z + i + 1] - '0') + Time * 10;
}
gps.Time_H = Time / 10000 + 8; // Adjust for GMT+8
gps.Time_M = (Time / 100) % 100;
gps.Time_S = Time % 100;
if (gps.Time_H >= 24)
{
gps.Time_H = gps.Time_H - 24;
}
}
}
else if (x == 2)
{ // Status field
if (buff_t[add + z + 1] == 'A')
{
gps.Status = 1; // Position successful
}
else
{
gps.Status = 0; // Positioning failed
break; // Exit early if invalid
}
}
else if (x == 3)
{ // Latitude field
if (buff_t[add + z + 1] != ',')
{ // Check if latitude field is not empty
double latitude_val = 0;
UBYTE decimal_found = 0;
double decimal_multiplier = 0.1;

int k = 1;
while (buff_t[add + z + k] != ',' && buff_t[add + z + k] != '\0')
{
if (buff_t[add + z + k] == '.')
{
decimal_found = 1;
k++;
continue;
}
if (!decimal_found)
{
latitude_val = latitude_val * 10 + (buff_t[add + z + k] - '0');
}
else
{
latitude_val = latitude_val + (buff_t[add + z + k] - '0') * decimal_multiplier;
decimal_multiplier *= 0.1;
}
k++;
}
gps.Lat = latitude_val;
gps.Lat_area = buff_t[add + z + k + 1]; // N or S
z += k + 1;
}
else
{
gps.Status = 0; // Invalid data
break;
}
}
else if (x == 5)
{ // Longitude field
if (buff_t[add + z + 1] != ',')
{ // Check if longitude field is not empty
double longitude_val = 0;
UBYTE decimal_found = 0;
double decimal_multiplier = 0.1;

int k = 1;
while (buff_t[add + z + k] != ',' && buff_t[add + z + k] != '\0')
{
if (buff_t[add + z + k] == '.')
{
decimal_found = 1;
k++;
continue;
}
if (!decimal_found)
{
longitude_val = longitude_val * 10 + (buff_t[add + z + k] - '0');
}
else
{
longitude_val = longitude_val + (buff_t[add + z + k] - '0') * decimal_multiplier;
decimal_multiplier *= 0.1;
}
k++;
}
gps.Lon = longitude_val;
gps.Lon_area = buff_t[add + z + k + 1]; // E or W
z += k + 1;
break;
}
else
{
gps.Status = 0; // Invalid data
break;
}
}
}
}
break;
}
add++;
}
return gps;
}

// Convert to Baidu coordinates (BD-09)
Coordinates L76X_Baidu_Coordinates(void)
{
Coordinates wgs84_coords;
wgs84_coords.Lat = GPS.Lat;
wgs84_coords.Lon = GPS.Lon;

Coordinates gcj02_coords = transform(wgs84_coords);
Coordinates bd09_coords = bd_encrypt(gcj02_coords);
return bd09_coords;
}

// Convert to Google coordinates (GCJ-02)
Coordinates L76X_Google_Coordinates(void)
{
Coordinates wgs84_coords;
wgs84_coords.Lat = GPS.Lat;
wgs84_coords.Lon = GPS.Lon;

Coordinates gcj02_coords = transform(wgs84_coords);
return gcj02_coords;
}

// Coordinate transformation helper functions
static double transformLat(double x, double y)
{
double ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * sqrt(fabs(x));
ret += (20.0 * sin(6.0 * x * pi) + 20.0 * sin(2.0 * x * pi)) * 2.0 / 3.0;
ret += (20.0 * sin(y * pi) + 40.0 * sin(y / 3.0 * pi)) * 2.0 / 3.0;
ret += (160.0 * sin(y / 12.0 * pi) + 320 * sin(y * pi / 30.0)) * 2.0 / 3.0;
return ret;
}

static double transformLon(double x, double y)
{
double ret = 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(fabs(x));
ret += (20.0 * sin(6.0 * x * pi) + 20.0 * sin(2.0 * x * pi)) * 2.0 / 3.0;
ret += (20.0 * sin(x * pi) + 40.0 * sin(x / 3.0 * pi)) * 2.0 / 3.0;
ret += (150.0 * sin(x / 12.0 * pi) + 300.0 * sin(x / 30.0 * pi)) * 2.0 / 3.0;
return ret;
}

static Coordinates bd_encrypt(Coordinates gg)
{
Coordinates bd;
double x = gg.Lon, y = gg.Lat;
double z = sqrt(x * x + y * y) + 0.00002 * sin(y * x_pi);
double theta = atan2(y, x) + 0.000003 * cos(x * x_pi);
bd.Lon = z * cos(theta) + 0.0065;
bd.Lat = z * sin(theta) + 0.006;
return bd;
}

static Coordinates transform(Coordinates gps)
{
Coordinates gg;
double dLat = transformLat(gps.Lon - 105.0, gps.Lat - 35.0);
double dLon = transformLon(gps.Lon - 105.0, gps.Lat - 35.0);
double radLat = gps.Lat / 180.0 * pi;
double magic = sin(radLat);
magic = 1 - ee * magic * magic;
double sqrtMagic = sqrt(magic);
dLat = (dLat * 180.0) / ((a * (1 - ee)) / (magic * sqrtMagic) * pi);
dLon = (dLon * 180.0) / (a / sqrtMagic * cos(radLat) * pi);
gg.Lat = gps.Lat + dLat;
gg.Lon = gps.Lon + dLon;
return gg;
}

GPS モジュールの設定と初期化

  • gps_app ログモジュール:

-LOG_MODULE_REGISTER(gps_app, LOG_LEVEL_INF): これは gps_app という名前のログモジュールを登録し、そのログレベルを INFO に設定します。これにより、プログラムは Zephyr のログシステムを通じて情報を出力でき、デバッグと監視に役立ちます。

  • 型定義とマクロ:

-UBYTE, UWORD, UDOUBLE:これらは、変数の期待されるサイズを明確にすることでコードの可読性を向上させるカスタム符号なし整数型エイリアスです。

  • SENTENCE_SIZE, BUFFSIZE: これらは NMEA センテンスと大きなデータバッファを格納するために使用されるバッファの固定サイズを定義します。

  • HOT_START, SET_NMEA_OUTPUT:などのマクロ:これらのマクロは、L76X GPS モジュールに送信される様々な NMEA プロトコルコマンドを定義し、動作モード、出力頻度、ボーレートなどを設定します。

  • 構造体定義:

    • GNRMC: この構造体は、GNRMC(GPS Recommended Minimum Specific data)NMEA センテンスから解析された主要情報(経度、緯度、時刻、ステータス、方位など)を格納するために使用されます。

    • Coordinates: 地理座標の経度と緯度を格納するシンプルな構造体です。

  • グローバル変数と定数:

    • buff_t: 生の UART データを格納するために使用される BUFFSIZE サイズのグローバルバッファです。

-GPS:解析された GPS データを保持するために使用されるグローバル GNRMC 構造体インスタンスです。

  • uart_dev: UART 通信に使用される UART デバイス構造体へのポインタです。

  • new_gnrmc_available: 新しい有効な GNRMC センテンスが受信されたときに true に設定される volatile ブール型フラグで、メインループに新しいデータが処理可能であることを通知します。

  • uart_callback() 関数:

    • これは UART がデータを受信したときにトリガーされる UART 割り込みコールバック関数です。

    • この関数は UART FIFO からバイト単位でデータを読み取り、改行文字 \n が検出されたときに完全なセンテンスとしてデータを処理します。

メイン関数 main()

  • システム初期化:

    • nrfx_power_constlat_mode_request(): 電源管理がリアルタイム動作を妨げないように、一定レイテンシモードを要求します。

    • uart_dev = DEVICE_DT_GET: UART デバイスハンドルを取得し、device_is_ready() を使用してデバイスが準備完了かどうかを確認します。

    • uart_irq_callback_user_data_set()uart_irq_rx_enable():これらは UART 受信割り込みを設定・有効化し、uart_callback 関数を割り込みハンドラとして登録して GPS データの非同期受信を確保します。

  • GPS モジュール初期化:

    • L76X_Send_Command(SET_NMEA_OUTPUT):GPS モジュールが GNRMC などの指定された NMEA センテンスのみを出力するように設定するコマンドを送信し、不要なデータトラフィックを削減します。

-L76X_Send_Command(SET_POS_FIX_1S):GPS モジュールの位置更新頻度を 1 秒に設定します。

  • メインループ:

    • ループは無限に実行され、new_gnrmc_available フラグを継続的にチェックします。

    • フラグが true の場合、latest_gnrmc から最新の GPS センテンスを buff_t にコピーし、その後 L76X_Gat_GNRMC() を呼び出してデータを解析します。

    • 解析結果に基づいて、時刻、WGS-84 経度・緯度、および変換された百度・Google 座標を出力します。

    • GPS.Status が 0 の場合、「測位失敗」メッセージを出力します。

    • 新しいデータが利用できない場合、「新しい GNRMC データが利用できません」と出力します。

    • k_msleep(2000): プログラムは各ループ後に 2 秒間一時停止して出力頻度を制御します。

結果グラフ

I2C

ハードウェア準備

Seeed Studio XIAO nRF54L15 SenseSeeed Studio Expansion Board Base for XIAO

ソフトウェア実装


#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/display/cfb.h>
#include <stdio.h>
#include <string.h>

#define LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(main_app, LOG_LEVEL);

/**
* @brief Initializes the display device.
* @param[out] dev Pointer to the display device struct.
* @return 0 on success, -1 on failure.
*/
static int display_init(const struct device **dev) {
*dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));
if (!device_is_ready(*dev)) {
LOG_ERR("Device %s not ready", (*dev)->name);
return -1;
}

if (display_set_pixel_format(*dev, PIXEL_FORMAT_MONO10) != 0) {
if (display_set_pixel_format(*dev, PIXEL_FORMAT_MONO01) != 0) {
LOG_ERR("Failed to set required pixel format");
return -1;
}
}

LOG_INF("Initialized %s", (*dev)->name);
return 0;
}

/**
* @brief Initializes the Compact Framebuffer (CFB) and display blanking.
* @param dev Pointer to the display device struct.
* @return 0 on success, -1 on failure.
*/
static int framebuffer_setup(const struct device *dev) {
if (cfb_framebuffer_init(dev)) {
LOG_ERR("Framebuffer initialization failed!");
return -1;
}
cfb_framebuffer_clear(dev, true);
display_blanking_off(dev);
return 0;
}

/**
* @brief Selects a suitable font for the display.
* @param dev Pointer to the display device struct.
* @param[out] font_width Pointer to store the width of the selected font.
* @param[out] font_height Pointer to store the height of the selected font.
* @return 0 on success, -1 if no suitable font is found.
*/
static int select_font(const struct device *dev, uint8_t *font_width, uint8_t *font_height) {
int chosen_font_idx = -1;
uint8_t current_font_width, current_font_height;

for (int idx = 0; idx < 42; idx++) {
if (cfb_get_font_size(dev, idx, &current_font_width, &current_font_height) == 0) {
if (current_font_width == 8 && current_font_height == 8) {
chosen_font_idx = idx;
*font_width = current_font_width;
*font_height = current_font_height;
cfb_framebuffer_set_font(dev, chosen_font_idx);
LOG_INF("Selected font idx: %d, width: %d, height: %d", chosen_font_idx, *font_width, *font_height);
break;
}
if (chosen_font_idx == -1 && current_font_width > 0 && current_font_height > 0) {
chosen_font_idx = idx;
*font_width = current_font_width;
*font_height = current_font_height;
cfb_framebuffer_set_font(dev, chosen_font_idx);
LOG_INF("Defaulting to font idx: %d, width: %d, height: %d", chosen_font_idx, *font_width, *font_height);
}
} else {
break;
}
}

if (chosen_font_idx == -1) {
LOG_ERR("No suitable font found or loaded!");
return -1;
}
return 0;
}

/**
* @brief Prints a single line of text at specified row and column.
* @param dev Pointer to the display device struct.
* @param text The string to print.
* @param row The row number (0-indexed) where the text should start.
* @param col The column number (0-indexed) where the text should start.
* @param font_width The width of the currently selected font in pixels.
* @param font_height The height of the currently selected font in pixels.
*/
static void print_text_by_row_col(const struct device *dev, const char *text, int row, int col,
uint8_t font_width, uint8_t font_height) {
int pixel_x = col * font_width;
int pixel_y = row * font_height;

if (cfb_print(dev, text, pixel_x, pixel_y)) {
LOG_ERR("Failed to print text: \"%s\" at row %d, col %d", text, row, col);
}
}

int main(void) {
const struct device *dev;
uint8_t font_width = 0;
uint8_t font_height = 0;
uint16_t x_res, y_res;

if (display_init(&dev) != 0) {
return 0;
}

if (framebuffer_setup(dev) != 0) {
return 0;
}

if (select_font(dev, &font_width, &font_height) != 0) {
return 0;
}

x_res = cfb_get_display_parameter(dev, CFB_DISPLAY_WIDTH);
y_res = cfb_get_display_parameter(dev, CFB_DISPLAY_HEIGHT);
LOG_INF("Display resolution: %dx%d", x_res, y_res);
cfb_set_kerning(dev, 0);

while (1) {
cfb_framebuffer_clear(dev, false);

const char *line1_text = "nRF54L15";
// Print line1 at row 1, column 2
print_text_by_row_col(dev, line1_text, 1, 2, font_width, font_height);

const char *line2_text = "Hello World";
// Print line2 at row 2, column 1
print_text_by_row_col(dev, line2_text, 2, 1, font_width, font_height);

cfb_framebuffer_finalize(dev);
k_sleep(K_MSEC(1000));
}

return 0;
}

ディスプレイデバイスの設定と初期化

  • main_app ログモジュール:

    • #define LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL と LOG_MODULE_REGISTER(main_app, LOG_LEVEL) は、main_app という名前のログモジュールを登録し、そのログレベルをシステムのデフォルト設定に設定します。これにより、開発者は Zephyr のログシステムを通じて簡単にデバッグと情報出力を行うことができます。
  • display_init() 関数:

    • *dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));: この行は、Zephyr Device Tree から選択されたディスプレイデバイスを取得します。このアプローチにより、コードがハードウェアに依存しないことが保証されます。

    • display_set_pixel_format(*dev, PIXEL_FORMAT_MONO10):コードはディスプレイのピクセル形式を PIXEL_FORMAT_MONO10 に設定しようとします。これが失敗した場合、PIXEL_FORMAT_MONO01 を試します。これにより、ディスプレイがモノクロームモードで動作することが保証され、一部のディスプレイ技術(例:OLED や e-Paper)に必要です。

  • framebuffer_setup() 関数:

    • cfb_framebuffer_init(dev):これは Compact Framebuffer(CFB)を初期化します。CFB は Zephyr の軽量グラフィックスライブラリで、ディスプレイにテキストと簡単なグラフィックスを描画するために使用されます。

    • cfb_framebuffer_clear(dev, true):これはフレームバッファをクリアし、その内容を即座にディスプレイに書き込み、クリーンな画面を保証します。

    • display_blanking_off(dev):これはディスプレイのブランキング機能をオフにします。これは通常、ディスプレイがデータを受信して画像を表示する準備ができたことを示す信号です。

  • select_font() 関数:

    • cfb_get_font_size():この関数は利用可能なフォントをループして適切なものを見つけます。

    • コードは8x8ピクセルフォントを優先します。これは一般的で読みやすい小さなフォントだからです。

    • 8x8 フォントが見つからない場合、フォールバックとして最初に利用可能な非ゼロサイズのフォントを選択します。

    • cfb_framebuffer_set_font(dev, chosen_font_idx): 適切なフォントが見つかると、それがフレームバッファの現在のフォントとして設定されます。

  • print_text_by_row_col() 関数:

-int pixel_x = col * font_width;int pixel_y = row * font_height;: この関数は、テキストの行と列の座標(文字単位)をピクセル座標に変換し、テキストの配置をより直感的にします。

  • cfb_print(): これは CFB ライブラリのコア関数で、指定されたピクセル位置にテキストを印刷するために使用されます。

メインループ コードのコアロジックは無限の while (1)ループ内で実行されます:

  • 画面のクリア:cfb_framebuffer_clear(dev, false): 各ループの開始時に、これはディスプレイを即座にリフレッシュすることなくフレームバッファをクリアします。これにより、複数の要素を一度に描画でき、画面のちらつきを防ぎます。

  • テキストの印刷:

    • 2つの文字列、line1_textline2_text が定義されます。

    • print_text_by_row_col(): カスタム関数を使用して、これら2行のテキストを画面上の指定された行と列の位置に印刷します。最初の行は (1, 2) に、2番目の行は (2, 1) に印刷されます。

    • ディスプレイのリフレッシュ:cfb_framebuffer_finalize(dev): この関数は、フレームバッファからディスプレイに保留中のすべての描画コマンドを一度に送信し、すべてのコンテンツを同時に表示させます。

    • 遅延:k_sleep(K_MSEC(1000)): 各ループの後、プログラムは1000ミリ秒(1秒)一時停止します。これは画面更新頻度を制御し、時計やセンサーデータなどの静的情報を安定した方法で表示するアプリケーションに適しています。

結果グラフ

SPI

ハードウェア準備

Seeed Studio XIAO nRF54L15 SenseePaper Driver Board for Seeed Studio XIAO

ソフトウェア実装


#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/display.h>
#include <lvgl.h>

#define LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(epaper_simple);

int main(void)
{
// Get display device
const struct device *display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));
if (!device_is_ready(display_dev)) {
LOG_ERR("Display device not ready!");
return 0;
}
LOG_INF("Display device ready.");

// Initialize LVGL
// Must be called before any LVGL object creation or operation
lv_init();

// Turn off display blanking (for ePaper, this usually triggers a full refresh to clear old content)
if (display_blanking_off(display_dev)) {
LOG_ERR("Failed to turn off display blanking!");
return 0;
}
LOG_INF("Display blanking is off. Screen should be cleared by full refresh.");

// Get the current active screen and set its background to white
// This is also an LVGL-level "clear" operation to ensure the canvas is white
lv_obj_t *scr = lv_scr_act();
lv_obj_set_style_bg_color(scr, lv_color_white(), LV_STATE_DEFAULT);
lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, LV_STATE_DEFAULT);

// Remove screen padding and scrollbar
lv_obj_set_style_pad_all(scr, 0, LV_STATE_DEFAULT);
lv_obj_set_scrollbar_mode(scr, LV_SCROLLBAR_MODE_OFF);


// Get display width and height (for layout)
lv_disp_t *disp = lv_disp_get_default();
lv_coord_t width = lv_disp_get_hor_res(disp);
lv_coord_t height = lv_disp_get_ver_res(disp);
LOG_INF("Display width: %d, height: %d", width, height);


// Create a centered panel
lv_obj_t *panel = lv_obj_create(scr);
lv_obj_set_size(panel, 300, 100);
lv_obj_align(panel, LV_ALIGN_CENTER, 0, 0);

// Set panel background to white, border to black for visibility
lv_obj_set_style_bg_color(panel, lv_color_white(), LV_STATE_DEFAULT);
lv_obj_set_style_border_color(panel, lv_color_black(), LV_STATE_DEFAULT);
lv_obj_set_style_border_width(panel, 2, LV_STATE_DEFAULT);
lv_obj_set_style_pad_all(panel, 10, LV_STATE_DEFAULT);


// Add text to the panel
lv_obj_t *label = lv_label_create(panel);
lv_label_set_text(label, "HELLO EPAPER");

// Set text color to black for visibility on white background
lv_obj_set_style_text_color(label, lv_color_black(), LV_STATE_DEFAULT);
lv_obj_set_style_text_font(label, &lv_font_montserrat_24, LV_STATE_DEFAULT);
lv_obj_align(label, LV_ALIGN_CENTER, 0, 0);

// Add a time label at the top right
lv_obj_t *time_label = lv_label_create(scr);
lv_label_set_text(time_label, "Time 07:21 PM");
lv_obj_set_style_text_color(time_label, lv_color_black(), LV_STATE_DEFAULT);
lv_obj_set_style_text_font(time_label, &lv_font_montserrat_18, LV_STATE_DEFAULT);
lv_obj_align(time_label, LV_ALIGN_TOP_RIGHT, -20, 10);

// Add a Zephyr logo at the top left
lv_obj_t *zephyr_label = lv_label_create(scr);
lv_label_set_text(zephyr_label, "Powered by Zephyr");
lv_obj_set_style_text_color(zephyr_label, lv_color_black(), LV_STATE_DEFAULT);
lv_obj_set_style_text_font(zephyr_label, &lv_font_montserrat_24, LV_STATE_DEFAULT);
lv_obj_align(zephyr_label, LV_ALIGN_BOTTOM_LEFT, 20, -10);

// Add author label at the bottom right
lv_obj_t *author_label = lv_label_create(scr);
lv_label_set_text(author_label, "Author: Stellar");
lv_obj_set_style_text_color(author_label, lv_color_black(), LV_STATE_DEFAULT);
lv_obj_set_style_text_font(author_label, &lv_font_montserrat_16, LV_STATE_DEFAULT);
lv_obj_align(author_label, LV_ALIGN_BOTTOM_RIGHT, -20, -10);


// Add four squares at the top left with a for loop
lv_obj_t *squares[4];
int square_offsets = 20;
for (int i = 0; i < 4; i++) {
squares[i] = lv_obj_create(scr);
lv_obj_set_size(squares[i], 30, 30);
lv_obj_set_style_bg_color(squares[i], lv_color_white(), LV_STATE_DEFAULT);
lv_obj_set_style_border_color(squares[i], lv_color_black(), LV_STATE_DEFAULT);
lv_obj_set_style_border_width(squares[i], 2, LV_STATE_DEFAULT);
lv_obj_set_style_radius(squares[i], 0, LV_STATE_DEFAULT);
lv_obj_align(squares[i], LV_ALIGN_TOP_LEFT, square_offsets, 20);
square_offsets+=40;
}

while (1) {
lv_task_handler();
k_sleep(K_MSEC(1000)); // Lower refresh rate, suitable for ePaper
}
return 0;
}

デバイス初期化:

  • コードは最初にDEVICE_DT_GET(DT_CHOSEN(zephyr_display))を使用してデバイスツリーからディスプレイデバイスを取得します。

  • 次にdevice_is_ready()を呼び出して、デバイスが適切に初期化され、動作準備ができているかを確認します。これはハードウェアとのやり取りにおいて重要な最初のステップです。

LVGL初期化:

  • lv_init()はLVGLグラフィックスライブラリのエントリーポイントです。ライブラリの内部状態を初期化するため、LVGLオブジェクトを作成したり操作を実行したりする前に必ず呼び出す必要があります。

画面クリア:

  • display_blanking_off()関数が呼び出されます。E-Paperディスプレイの場合、これは通常、画面上の古いコンテンツをクリアするためのフルリフレッシュをトリガーします。

  • クリーンなキャンバスをさらに確保するため、コードはlv_scr_act()を使用して現在のアクティブ画面を取得し、lv_obj_set_style_bg_color()を使用して背景色を白に設定し、ディスプレイ全体をカバーします。

画面レイアウト準備:

  • lv_disp_get_hor_res()lv_disp_get_ver_res()関数を使用してディスプレイの実際の幅と高さを取得し、後でUI要素を正確に配置するのに役立ちます。

  • コードはまた、画面のパディング(lv_obj_set_style_pad_all())とスクロールバー(lv_obj_set_scrollbar_mode())を削除して、使用可能な描画エリアを最大化します。

UI要素の作成と設定:

  • パネル: lv_obj_create(scr)でパネルオブジェクトを作成します。lv_obj_set_size()lv_obj_align()を使用してサイズと中央揃えを設定します。白い背景と黒い境界線を含むスタイルは、lv_obj_set_style_bg_color()lv_obj_set_style_border_color()などの関数で設定されます。

  • ラベル:

    • lv_label_create()を使用してテキストラベルを作成します。

    • lv_label_set_text()でラベルのテキスト内容を設定します。

    • lv_obj_set_style_text_color()とlv_obj_set_style_text_font()を使用してテキストの色とフォントサイズを設定します。

  • lv_obj_align()関数は各ラベルを画面上の特定の位置(中央、右上、左下、右下など)に配置します。

四角形: forループを使用して4つの小さな四角形オブジェクトを作成します。それらのサイズ、スタイル(黒い境界線付きの白い塗りつぶし)、位置を順次設定し、画面の左上隅に水平に配置します。

メインループ:

  • while(1)ループはプログラムの継続実行部分です。

  • lv_task_handler()はループ内で継続的に呼び出され、UI要素の更新やイベント処理など、すべてのLVGL内部タスクを処理します。

  • k_sleep(K_MSEC(1000))はスレッドを1000ミリ秒間一時停止します。静的d

結果グラフ

技術サポート & 製品ディスカッション

弊社製品をお選びいただきありがとうございます!弊社製品での体験が可能な限りスムーズになるよう、さまざまなサポートを提供いたします。さまざまな好みやニーズに対応するため、複数のコミュニケーションチャンネルを提供しています。

Loading Comments...