Raspberry Pi Pico 2を試してみました【Picoとのスペック比較など】

Raspberry Pi/電子工作
スポンサーリンク

こんにちは、あろっちです。

Raspberry Pi Pico 2が登場しました。
Raspberry Pi Pico 2についてまとめてみたいと思います。

RP2350の不具合情報

RP2350マイクロコントローラに関するErrata 9(E9)では、以下の問題が報告されています。

  • 問題の概要:
    • GPIOピンが入力モードで約2.2Vにラッチする現象が確認されており、内部プルダウン機能に依存するプログラムで予期しない動作を引き起こす可能性があります。(Datasheet)(Wikipedia)​
  • 回避策:
    • 現在のところ、この問題に対する公式な回避策が存在せず、効果的な代替手段も提供されていない状況のようです。

この不具合のポイント

  • INPUT(INPUT_PULLDOWN)が期待通りの動作をしない
  • 現在、有効な回避策がない

この現象を確認するプログラムを作ってみました。

void setup() {
  pinMode(0, INPUT);
}

void loop() {
}

GPIO0を入力モードに設定するというシンプルなコードです。
このコードをRaspberry Pi Pico 2とRaspberry Pi Picoに書き込みオシロスコープ(OWON HDS272)で電圧を測定してみました。
※テスターでもよかったのですが、今回はスクリーンショットが撮れるという理由でオシロスコープを使いました。

Raspberry Pi Pico 2のGPIO0の結果

中央が0Vです。(1目盛1V)
ラインの位置が2.2V付近とラッチアップを確認できました。

Raspberry Pi PicoのGPIO0の結果

ほぼ0Vであることが確認できました。

この結果からRaspberry Pi Pico 2では、

INPUT_PULLDOWNは以下のように2.2Vまでしか下がらないためLOW状態とならず実質機能しない(LOWが検出できない)と見てよいかと思います。(不具合情報の通り)

INPUTを使う場合は、基本的に回路にプルダウン抵抗(RP2350の場合8.2kΩ以下)を入れる措置を講じる必要があるかと思います。

この場合、以下のようにLOW状態に下がります。

INPUT_PULLUPを使う場合、外部からスイッチやセンサーでGNDに接続する回路になるはずなのでLOW状態になるかと思います。(負論理回路になりますが、GND接続時のイメージは上の画像と同じ)

自作のPico向けテスト用プリント基板にユーザボタンがありますが、このボタンはINPUT_PULLUPが前提になっています。
これでボタンを押した時にLEDが光るというスケッチをテストしてみました。

正常動作が確認できました。

スケッチ

// Picoテスト基板用(ユーザボタン(G2)と基板実装LED(G3)を使用)
const int buttonPin = 2;  // the number of the pushbutton pin
const int ledPin = 3;     // the number of the LED pin

int buttonState = 0;  // variable for reading the pushbutton status

void setup() {
  // initialize the LED pin as an output:
  pinMode(ledPin, OUTPUT);
  // initialize the pushbutton pin as an input:
  pinMode(buttonPin, INPUT_PULLUP);
}

void loop() {
  // read the state of the pushbutton value:
  buttonState = digitalRead(buttonPin);

  // check if the pushbutton is pressed. If it is, the buttonState is LOW:
  if (buttonState == LOW) {
    // turn LED on:
    digitalWrite(ledPin, HIGH);
  } else {
    // turn LED off:
    digitalWrite(ledPin, LOW);
  }
}
スポンサーリンク

スペック比較表

項目Raspberry Pi PicoRaspberry Pi Pico 2備考
SoCRP2040

デュアルコア
Cortex-M0+
133MHz
RP2350

デュアルコア
Cortex-M33
or
RISC-V Hazard3
150MHz
RP2350はARMセット、RISC-Vセットどちらかのバイナリ(命令セット)を選択可

RP2350はFPU搭載
ARMセットにおいてFPUを使用できる
SRAM264kB520kB
フラッシュメモリ2MB4MBいずれもオンボードQuad-SPI(QSPI)接続
動作電圧1.8〜5.5V1.8〜5.5V
USBmicro-USBmicro-USBいずれもUSB 1.1コントローラーおよびPHY(ホストおよびデバイスに対応)
GPIO26
(うち3つはADCに使用可能)

2×SPI、2×I2C、2×UART、3×12bit 500ksps ADC、16×制御可能PWMチャネル
26
(うち3つはADCに使用可能)

2×SPI、2×I2C、2×UART、3×12bit 500ksps ADC、24×制御可能PWMチャネル
ピン配置に互換あり
タイマー1×64bitカウンター
(4×アラームに分割可)
2×4つのアラーム付きタイマー、1×AONタイマー
プログラマブルIO(PIO)2×PIOブロック、
8×カスタム周辺機器サポート用のPIOステートマシン
3×PIOブロック、
12×カスタム周辺機器サポート用のPIOステートマシン
デバッグピンSWDSWD
温度センサーありあり
MCUスリープモード100uA<10uA
外形寸法21mm×51mm21mm×51mm外形に互換あり
対応言語Raspberry Pi Pico C/C++ SDK

MicroPython
CircuitPython

Arduino

Arduino公式ボード
&
Raspberry Pi Pico/RP2040ボード(Arduino-Pico)
Raspberry Pi Pico C/C++ SDK

MicroPython
CircuitPython

Arduino

Raspberry Pi Pico/RP2040ボード(Arduino-Pico) 4.0.1から正式対応(4.0.xはARMセットのみ)
また、4.1.0からRISC-Vセットサポート
セキュリティなしCortex-M用のArm TrustZoneを中心に、署名付きブート、キーストレージ用の 8 KBのアンチヒューズOTP、SHA-256アクセラレーター、ハードウェアTRNG(乱数生成器)、高速グリッチ検出器を組み込んだ、包括的なセキュリティアーキテクチャを提供
USBマスストレージ(書き込みモード)時の名称RPI-RP2RP2350
その他・ソフトウェア互換性
RP2040で動作していたプログラムは基本的に動作可
(ソース互換)

RP2040とRP2350でバイナリ(UF2ファイル)の互換がない

RP2040とRP2350を比較したコラムを書きました。

Seeed Studio XIAO RP2350を入手し記事を書きました。

Raspberry Pi Pico 2のUSBマスストレージモード(画面例)

USBマスストレージモードとは「BOOTSEL」ボタンを押しながらPCに接続するとストレージドライブとして認識されるモードです。

Raspberry Pi Pico 2は「RP2350」と認識されます。

MacのFinder画面

USBマスストレージモードについては次の記事をご参照ください。

ピン配列

Raspberry Pi Pico 2

Raspberry Pi Pico

画像ギャラリー

ピンヘッダー実装後

Raspberry Pi PicoとRaspberry Pi Pico 2

開発環境

【Python】Raspberry Pi Pico 2向けPythonのファームウェア

MicroPython

MicroPython - Python for microcontrollers
MicroPython is a lean and efficient implementation of the Python 3 programming language that includes a small subset of ...

CircuitPython

CircuitPython - Pico 2 Download
Raspberry Pi Pico 2 is Raspberry Pi Foundation’s update to their popular RP2040-based Pico board, now built on RP2350: t...

PythonのファームウェアはUSBマスストレージモードからインストールできます。次の記事をご参照ください。

【Pico C/C++ SDK】開発環境の構築

VSCodeに開発環境を構築できます。

Macの場合

ターミナルから以下のコマンドを実行し、事前に必要なツールをインストールします。

xcode-select --install

WindowsMac、Raspberry Pi OS共通

VSCodeの拡張機能画面からRaspberry Pi Pico拡張機能をインストールします。(赤枠参照)

アイコン(赤枠参照)が追加され、選択するとメニューが表示されます。

メニュー「New C/C++ Project」からC/C++の開発プロジェクトが作成できます。

参考URL:
https://datasheets.raspberrypi.com/pico/getting-started-with-pico.pdf

GPIOを単純にON/OFFするだけのプログラムを新規作成

プロジェクト名を「gpio_on_off_test」として新規作成してみます。

Pico 2用は「Board type」を「Pico 2」にPico用は「Pico」を選択します。

画像はPico 2用のプロジェクト

上記内容で「Create」をクリックしプロジェクトを作成しました。

今回、オシロスコープで確認するためにPico 2、Pico両方のプロジェクトを作成しました。

#include "pico/stdlib.h"

int main()
{
    gpio_init(0);
    gpio_set_dir(0, GPIO_OUT);

    for (;;)
    {
        gpio_put(0, true);
        gpio_put(0, false);
    }
}

GPIO0をON/OFFします。

実行結果

オシロスコープ(OWON HDS272)で確認した結果を掲載します。

Raspberry Pi Pico (133MHz)

B-A 32.00ns

Raspberry Pi Pico 2 (ARMセット 150MHz)

B-A 40.00ns

Raspberry Pi Pico 2の方が速いと思っていたのですが、Raspberry Pi Picoの方が速いという結果でした。

Pico 2用フラッシュメモリ初期化ソフト(flash_nuke.uf2)を作成してみる

flash_nuke.uf2はPicoのフラッシュメモリを初期化するソフトです。

flash_nuke.uf2はRaspberry Pi PicoのサイトにRaspberry Pi Pico用のバイナリはありますが、Pico 2用のバイナリは見当たりません。

こちらVSCodeの開発環境で作成できます。

メニューから「New Project From Example」を選択すると以下のような画面になります。

「Name」から「flash_nuke」を選択します。

「Board type」が「Pico 2」であることを確認します。
「Location」にプロジェクトの保存フォルダを設定します。

準備ができたら「Create」ボタンを押します。
すると別ウインドウが開き「flash_nuke」のプロジェクトが作成されます。
※Raspberry Pi Pico拡張機能初回実行時は、C/C++ SDKなどのツールのインストールのため少々時間がかかります。

「flash_nuke」プロジェクトのRaspberry Pi Pico拡張機能のアイコン(赤枠参照)を選択すると「Project」の項目がアクティブになっているのが確認できます。

「Compile Project」をクリックすると「flash_nuke」プロジェクトのbuildフォルダ配下に「flash_nuke.uf2」が作成されます。

「flash_nuke.uf2」をお好みの場所に保存しておけば、Raspberry Pi Pico 2のフラッシュメモリを初期化したい時に直接投下できるようになるかと思います。

ファイル名がRaspberry Pi Pico用のflash_nuke.uf2と同じためflash_nuke_pico2.uf2など変更しておくとRaspberry Pi Pico 2用と分かりやすくなるかと思います。

【Arduino】Arduino IDEの設定

  • Arduino IDEにボードを追加
    ※実施済みの場合、この手順は不要です。

事前にRaspberry Pi Pico/RP2040ボード(Arduino-Pico)を追加します。
追加方法は、以下の記事をご覧ください。

  • Raspberry Pi Pico/RP2040ボード(Arduino-Pico)のインストール

[ツール] > [ボード] > [ボードマネージャ]をクリックし、検索ボックスに「rp2040」と入力し、[Raspberry Pi Pico/RP2040]ボード(バージョン4.0.1以降)をインストールします。

  • ボードの選択 (Raspberry Pi Pico 2の場合)

[ツール] > [ボード] > [Raspberry Pi RP2040(Ver)※] > [Raspberry Pi Pico 2]を選択します。

※(Ver)の表示はArduino IDE 1系のみ

  • シリアルポートの選択 (Raspberry Pi Pico 2の場合)

[ツール] > [シリアルポート]からRaspberry Pi Pico 2が接続されているシリアルポートを選択します。

Chromebookなどシリアルポートから書き込みできない環境の場合、手動でスケッチを書き込みできます。

動かしてみました

TRNG(ハードウェア乱数生成器)を試してみました (Arduino使用)

Raspberry Pi Pico 2はセキュリティ機能の一部としてTRNG(ハードウェア乱数生成器)が実装されています。
どの程度のパフォーマンスなのか試してみました。

void setup() {
  Serial.begin(115200);
  while (!Serial) yield();

  Serial.println();
#if HAS_RP2350_TRNG
  Serial.println("HAS_RP2350_TRNG");
#else
  Serial.println("PRNG");
#endif

  Serial.println("Use pico_rand function");
  uint32_t randNumber;
  for (int i = 0; i < 10; i++) {
    auto now = micros();
    randNumber = rp2040.hwrand32();
    auto last = micros();
    Serial.printf("%d: %u %u\n", i + 1, randNumber, last - now);
  }

  Serial.println();
  Serial.println("Use random function");
  for (int i = 0; i < 10; i++) {
    auto now = micros();
    randNumber = (uint32_t)random();
    auto last = micros();
    Serial.printf("%d: %u %u\n", i + 1, randNumber, last - now);
  }
}

void loop() {
}

実行結果

左がRaspberry Pi Pico 2、右がRaspberry Pi Picoで実行した結果です。
乱数取得の結果(1:などの右側)は、乱数値、取得にかかった時間(μs)の順で表示されています。

Raspberry Pi Pico 2の結果
Raspberry Pi Picoの結果

Use pico_rand functionの方はマクロ「HAS_RP2350_TRNG」が定義してあるとTRNGを使って乱数取得するプログラムとなっています。

Raspberry Pi Picoの結果を見るとPRNG(擬似乱数生成器)で乱数が取得されているのがわかります。

乱数生成速度に関して初回取得時(1:)は乱数生成に時間を要するため、Raspberry Pi Pico 2で66μs、Raspberry Pi Picoの方はPRNGのためか914μsとなっています。
2回目(2:)以降の結果を見るとRaspberry Pi Pico 2のTRNGでは5μs内で安定して乱数が取得できるのがわかりました。
Raspberry Pi Picoの方は乱数取得に10μs前後要するみたいです。

Use random functionの結果はArduinoのrandom関数で取得した乱数値です。

Raspberry Pi Pico 2とRaspberry Pi Picoで同じ値を取得していることから、こちらはRP2040/RP2350に依存するような乱数生成器を使っておらず、あくまでrandomSeed関数と組み合わせて使うArduino固有の関数ということがわかりました。

Adafruit NeoPixelライブラリを試してみました (Arduino使用)

Adafruit NeoPixelのスケッチ例「strandtest」を試したところ見事動きました。

Grove Shield for Pi PicoとGroveのRGB LEDテープを使用しています。

ピンは16に変更しています。

// Which pin on the Arduino is connected to the NeoPixels?
// On a Trinket or Gemma we suggest changing this to 1:
#define LED_PIN 16

TFT_eSPIライブラリを試してみました (自作基板 & Arduino使用)

自作のPico向けテスト用プリント基板でTFT_eSPIライブラリを試してみました。

ILI9486 4.0インチディスプレイにスケッチ例「TFT_graphicstest_one_lib」を表示してみました。

セットアップはこのディスプレイ(ILI9486)用にカスタマイズしました。

スクリーンセーバー

以下のようなスクリーンセーバーです。

LCDはILI9341 2.4インチ(240×320)です。
LCDの定義(User_Setup)を変えれば別LCDにも対応できるかと思います。

ちなみにRaspberry Pi Pico 2ではRISC-Vセットでも動作するのを確認しました。

#include <TFT_eSPI.h>

// 設定値(ユーザーが変更可能)
// #define NO_WAIT          // ウェイトなしにする場合は定義を有効にする
#define PADDING 5       // テキスト周りの余白
#define COLOR_CHANGE_INTERVAL 1000  // 色変更の間隔(ミリ秒)
#define SPEED_CHANGE_INTERVAL 2000  // 速度変更の間隔(ミリ秒)
#define MIN_SPEED 1       // 最小速度
#define MAX_SPEED 5       // 最大速度

// グローバル変数
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite sprite = TFT_eSprite(&tft);
int screenWidth, screenHeight;
const char* text = "aloseed";
int textWidth, textHeight, spriteWidth, spriteHeight;

float timeVar = 0.0;
uint16_t bgColor = TFT_BLACK;
uint16_t textColor = TFT_WHITE;
const uint8_t minRGBValue = 100;

int logoX, logoY;
float velocityX;  // 浮動小数点型に変更
unsigned long lastColorChangeTime = 0;  // 色変更のタイマー
unsigned long lastSpeedChangeTime = 0;  // 速度変更のタイマー

void setup() {
  // 初期化
  tft.init();
  // 画面回転はLCDに合わせて変更してください
  tft.setRotation(2);
  tft.fillScreen(bgColor);

  // 画面サイズとテキスト設定
  screenWidth = tft.width();
  screenHeight = tft.height();
  int textSize = (screenWidth < 240 || screenHeight < 240) ? 1 : 2;
  tft.setTextSize(textSize);

  // スプライト設定
  textWidth = tft.textWidth(text) + PADDING;
  textHeight = tft.fontHeight() + PADDING;
  spriteWidth = textWidth + PADDING * 2;
  spriteHeight = textHeight + PADDING * 2;
  sprite.createSprite(spriteWidth, spriteHeight);
  sprite.setTextDatum(MC_DATUM);
  sprite.setTextSize(textSize);

  // 初期設定
  setRandomVelocity();
  setRandomPosition();
  generateRandomColor();
}

void loop() {
  timeVar += 0.03;  // sin波のための時間変数更新

  // Y軸のsin波による移動
  logoY = (screenHeight - textHeight) / 2 + (screenHeight - textHeight) / 2 * sin(timeVar);

  // X軸の直線移動(端に到達すると反転)
  logoX += velocityX;
  if ((logoX <= 0 && velocityX < 0) || (logoX >= (screenWidth - textWidth) && velocityX > 0)) {
    velocityX = -velocityX;
  }

  // スプライトの描画
  sprite.fillSprite(bgColor);
  sprite.drawString(text, spriteWidth / 2, spriteHeight / 2);
  sprite.pushSprite(logoX - PADDING, logoY - PADDING);

  // millis()を一度だけ呼び出して変数に格納
  unsigned long currentMillis = millis();

  // 一定時間ごとに色を変更
  if (currentMillis - lastColorChangeTime > COLOR_CHANGE_INTERVAL) {
    generateRandomColor();
    lastColorChangeTime = currentMillis;  // タイマーを更新
  }

  // 一定時間ごとに速度を変更
  if (currentMillis - lastSpeedChangeTime > SPEED_CHANGE_INTERVAL) {
    setRandomVelocity();
    lastSpeedChangeTime = currentMillis;  // タイマーを更新
  }

#ifndef NO_WAIT
  delay(20);  // ウェイト有効時の遅延
#endif
}

// ランダムな色を生成
void generateRandomColor() {
  uint32_t randomColor = rp2040.hwrand32();  
  uint8_t r = minRGBValue + (randomColor & 0xFF) % (256 - minRGBValue);
  uint8_t g = minRGBValue + ((randomColor >> 8) & 0xFF) % (256 - minRGBValue);
  uint8_t b = minRGBValue + ((randomColor >> 16) & 0xFF) % (256 - minRGBValue);
  textColor = tft.color565(r, g, b);
  sprite.setTextColor(textColor, bgColor);
}

// ランダムな速度を設定
void setRandomVelocity() {
  velocityX = (rp2040.hwrand32() % (MAX_SPEED - MIN_SPEED + 1)) + MIN_SPEED;
  if (rp2040.hwrand32() % 2 == 0) velocityX = -velocityX;  // 反転する可能性を追加
}

// ランダムな初期位置を設定
void setRandomPosition() {
  logoX = rp2040.hwrand32() % (screenWidth - spriteWidth);
  logoY = rp2040.hwrand32() % (screenHeight - spriteHeight);
}

次の行のコメントを外すとウェイトなしにできます。

// #define NO_WAIT          // ウェイトなしにする場合は定義を有効にする

ウェイトなし(Raspberry Pi Pico 2 ARMセット)

ウェイトなし(Raspberry Pi Pico)

LovyanGFXライブラリで動かしてみました

LovyanGFX(バージョン1.2.0)で動かしてみました。

LCDはST7789 1.54インチを使用
LovyanGFXは日本語フォントが使えるので、日本語を表示するように改修してみました。

// グラフィックボードのピン配置(Picoテスト基板用)
#define TFT_MISO -1
#define TFT_MOSI 19
#define TFT_SCLK 18
#define TFT_CS 17  // Chip select control pin
#define TFT_DC 26  // Data Command control pin
#define TFT_RST 27 // Reset pin (could connect to RST pin)
// バックライトのピンに直接3.3Vを入れる場合はTFT_BLをコメントにしてください
#define TFT_BL 22

#include <LovyanGFX.hpp>
class LGFX : public lgfx::LGFX_Device
{
  lgfx::Panel_ST7789 _panel_instance;
  lgfx::Bus_SPI _bus_instance; // SPIバスのインスタンス

public:
  LGFX(void)
  {
    {                                    // バス制御の設定を行います。
      auto cfg = _bus_instance.config(); // バス設定用の構造体を取得します。

      cfg.spi_host = 0;          // 使用するSPIを選択
      cfg.spi_mode = 0;          // SPI通信モードを設定 (0 ~ 3)
      cfg.freq_write = 40000000; // 送信時のSPIクロック (最大80MHz, 80MHzを整数で割った値に丸められます)
      cfg.freq_read = 20000000;  // 受信時のSPIクロック

      cfg.pin_sclk = TFT_SCLK; // SPIのSCLKピン番号を設定
      cfg.pin_mosi = TFT_MOSI; // SPIのMOSIピン番号を設定
      cfg.pin_miso = TFT_MISO; // SPIのMISOピン番号を設定 (-1 = disable)
      cfg.pin_dc = TFT_DC;     // SPIのD/Cピン番号を設定  (-1 = disable)

      _bus_instance.config(cfg);              // 設定値をバスに反映します。
      _panel_instance.setBus(&_bus_instance); // バスをパネルにセットします。
    }

    {                                      // 表示パネル制御の設定を行います。
      auto cfg = _panel_instance.config(); // 表示パネル設定用の構造体を取得します。

      cfg.pin_cs = TFT_CS;   // CSが接続されているピン番号   (-1 = disable)
      cfg.pin_rst = TFT_RST; // RSTが接続されているピン番号  (-1 = disable)
      cfg.pin_busy = -1;     // BUSYが接続されているピン番号 (-1 = disable)

      cfg.panel_width = 240;  // 実際に表示可能な幅
      cfg.panel_height = 240; // 実際に表示可能な高さ
      cfg.invert = true;      // パネルの明暗が反転してしまう場合 trueに設定

      _panel_instance.config(cfg);
    }

    setPanel(&_panel_instance); // 使用するパネルをセットします。
  }
};
#include <LGFX_TFT_eSPI.hpp>

// 設定値(ユーザーが変更可能)
// #define NO_WAIT          // ウェイトなしにする場合は定義を有効にする
#define PADDING 5                  // テキスト周りの余白
#define COLOR_CHANGE_INTERVAL 1000 // 色変更の間隔(ミリ秒)
#define SPEED_CHANGE_INTERVAL 2000 // 速度変更の間隔(ミリ秒)
#define MIN_SPEED 1                // 最小速度
#define MAX_SPEED 5                // 最大速度

// グローバル変数
TFT_eSPI tft = TFT_eSPI();
TFT_eSprite sprite = TFT_eSprite(&tft);
int screenWidth, screenHeight;
// const char* text = "aloseed";
const char *text = "あろしーど";
int textWidth, textHeight, spriteWidth, spriteHeight;

float timeVar = 0.0;
uint16_t bgColor = TFT_BLACK;
uint16_t textColor = TFT_WHITE;
const uint8_t minRGBValue = 100;

int logoX, logoY;
float velocityX;                       // 浮動小数点型に変更
unsigned long lastColorChangeTime = 0; // 色変更のタイマー
unsigned long lastSpeedChangeTime = 0; // 速度変更のタイマー

void setup()
{
#ifdef TFT_BL
  // バックライトを設定
  pinMode(TFT_BL, OUTPUT);
  digitalWrite(TFT_BL, HIGH);
#endif
  // 初期化
  tft.init();
  // 画面回転はLCDに合わせて変更してください
  // tft.setRotation(2);
  tft.fillScreen(bgColor);

  // 画面サイズとテキスト設定
  screenWidth = tft.width();
  screenHeight = tft.height();
  const lgfx::v1::IFont *font = (screenWidth < 240 || screenHeight < 240) ? &fonts::lgfxJapanGothic_8 : &fonts::lgfxJapanGothic_16;
  // int textSize = (screenWidth < 240 || screenHeight < 240) ? 1 : 2;
  // tft.setTextSize(textSize);

  // スプライト設定
  tft.setFont(font);
  textWidth = tft.textWidth(text) + PADDING;
  textHeight = tft.fontHeight() + PADDING;
  spriteWidth = textWidth + PADDING * 2;
  spriteHeight = textHeight + PADDING * 2;
  sprite.createSprite(spriteWidth, spriteHeight);
  sprite.setTextDatum(MC_DATUM);
  sprite.setFont(font);
  // sprite.setTextSize(textSize);

  // 初期設定
  setRandomVelocity();
  setRandomPosition();
  generateRandomColor();
}

void loop()
{
  timeVar += 0.03; // sin波のための時間変数更新

  // Y軸のsin波による移動
  logoY = (screenHeight - textHeight) / 2 + (screenHeight - textHeight) / 2 * sin(timeVar);

  // X軸の直線移動(端に到達すると反転)
  logoX += velocityX;
  if ((logoX <= 0 && velocityX < 0) || (logoX >= (screenWidth - textWidth) && velocityX > 0))
  {
    velocityX = -velocityX;
  }

  // スプライトの描画
  sprite.fillSprite(bgColor);
  sprite.drawString(text, spriteWidth / 2, spriteHeight / 2);
  sprite.pushSprite(logoX - PADDING, logoY - PADDING);

  // millis()を一度だけ呼び出して変数に格納
  unsigned long currentMillis = millis();

  // 一定時間ごとに色を変更
  if (currentMillis - lastColorChangeTime > COLOR_CHANGE_INTERVAL)
  {
    generateRandomColor();
    lastColorChangeTime = currentMillis; // タイマーを更新
  }

  // 一定時間ごとに速度を変更
  if (currentMillis - lastSpeedChangeTime > SPEED_CHANGE_INTERVAL)
  {
    setRandomVelocity();
    lastSpeedChangeTime = currentMillis; // タイマーを更新
  }

#ifndef NO_WAIT
  delay(20); // ウェイト有効時の遅延
#endif
}

// ランダムな色を生成
void generateRandomColor()
{
  uint32_t randomColor = rp2040.hwrand32();
  uint8_t r = minRGBValue + (randomColor & 0xFF) % (256 - minRGBValue);
  uint8_t g = minRGBValue + ((randomColor >> 8) & 0xFF) % (256 - minRGBValue);
  uint8_t b = minRGBValue + ((randomColor >> 16) & 0xFF) % (256 - minRGBValue);
  textColor = tft.color565(r, g, b);
  sprite.setTextColor(textColor, bgColor);
}

// ランダムな速度を設定
void setRandomVelocity()
{
  velocityX = (rp2040.hwrand32() % (MAX_SPEED - MIN_SPEED + 1)) + MIN_SPEED;
  if (rp2040.hwrand32() % 2 == 0)
    velocityX = -velocityX; // 反転する可能性を追加
}

// ランダムな初期位置を設定
void setRandomPosition()
{
  logoX = rp2040.hwrand32() % (screenWidth - spriteWidth);
  logoY = rp2040.hwrand32() % (screenHeight - spriteHeight);
}

LCD接続GPIOは以下のように定義しています。

// グラフィックボードのピン配置(Picoテスト基板用)
#define TFT_MISO -1
#define TFT_MOSI 19
#define TFT_SCLK 18
#define TFT_CS 17  // Chip select control pin
#define TFT_DC 26  // Data Command control pin
#define TFT_RST 27 // Reset pin (could connect to RST pin)
// バックライトのピンに直接3.3Vを入れる場合はTFT_BLをコメントにしてください
#define TFT_BL 22

LCD接続GPIOは、実際の接続ピンに合わせて適宜変更できます。
お好みのLCDに対応させるには、LGFXクラスの定義を変えればよいでしょう。
以下の記事を参考にしてください。

ピクチャーフレーム (Arduino使用)

ストレージ領域に格納された画像ファイル(jpgかpng)をLCDに表示するスケッチです。
画像ファイルが複数あると順番に表示します。

開発環境
  • IDE: Arduino IDE 2系
  • ファイルアップローダー: arduino-littlefs-upload
  • ライブラリ: LovyanGFX(バージョン1.2.0以降)
    Arduino IDEのライブラリマネージャからインストールできます。

準備

arduino-littlefs-upload

Arduino IDE 2系用のストレージ領域にファイルをアップロードするツールです。
未インストールの場合にインストールします。
ちなみにESP32にも対応しています。

https://github.com/earlephilhower/arduino-littlefs-upload/releases

上記サイトからvsixファイルをダウンロードします。

画像は1.3.0ですが、最新バージョンをダウンロードすればよいかと思います。

以下の場所に配置します。

  • Windows
     C:\Users\<username>\.arduinoIDE\plugins
  • Mac
    ~/.arduinoIDE/plugins/

Windowsの例

pluginsフォルダがない場合は作成して配置します。

スケッチ

#include <LittleFS.h>
#include <LovyanGFX.hpp>

// グラフィックボードのピン配置(Picoテスト基板用)
#define TFT_MISO -1
#define TFT_MOSI 19
#define TFT_SCLK 18
// #define TFT_CS 17  // Chip select control pin
#define TFT_DC 26   // Data Command control pin
#define TFT_RST 27  // Reset pin (could connect to RST pin)
// ST7789の場合バックライト(ピン)はプルアップされるのでコメント
// #define TFT_BL 22

class LGFX : public lgfx::LGFX_Device {
  lgfx::Panel_ST7789 _panel_instance;
  lgfx::Bus_SPI _bus_instance;  // SPIバスのインスタンス

public:
  LGFX(void) {
    {                                     // バス制御の設定を行います。
      auto cfg = _bus_instance.config();  // バス設定用の構造体を取得します。

      cfg.spi_host = 0;
      cfg.spi_mode = 0;           // SPI通信モードを設定 (0 ~ 3)
      cfg.freq_write = 40000000;  // 送信時のSPIクロック (最大80MHz, 80MHzを整数で割った値に丸められます)
      cfg.freq_read = 20000000;   // 受信時のSPIクロック

      cfg.pin_sclk = TFT_SCLK;  // SPIのSCLKピン番号を設定
      cfg.pin_mosi = TFT_MOSI;  // SPIのMOSIピン番号を設定
      cfg.pin_miso = TFT_MISO;  // SPIのMISOピン番号を設定 (-1 = disable)
      cfg.pin_dc = TFT_DC;      // SPIのD/Cピン番号を設定  (-1 = disable)

      _bus_instance.config(cfg);               // 設定値をバスに反映します。
      _panel_instance.setBus(&_bus_instance);  // バスをパネルにセットします。
    }

    {                                       // 表示パネル制御の設定を行います。
      auto cfg = _panel_instance.config();  // 表示パネル設定用の構造体を取得します。

      cfg.pin_cs = -1;    // CSが接続されているピン番号   (-1 = disable)
      cfg.pin_rst = -1;   // RSTが接続されているピン番号  (-1 = disable)
      cfg.pin_busy = -1;  // BUSYが接続されているピン番号 (-1 = disable)

      cfg.panel_width = 240;   // 実際に表示可能な幅
      cfg.panel_height = 240;  // 実際に表示可能な高さ

      cfg.invert = true;  // パネルの明暗が反転してしまう場合 trueに設定
      // cfg.rgb_order = true; // パネルの赤と青が入れ替わってしまう場合 trueに設定

      _panel_instance.config(cfg);
    }

    setPanel(&_panel_instance);  // 使用するパネルをセットします。

    lgfx::pinMode(TFT_RST, lgfx::pin_mode_t::output);
    lgfx::pinMode(TFT_SCLK, lgfx::pin_mode_t::output);

    lgfx::gpio_lo(TFT_RST);
    lgfx::gpio_hi(TFT_SCLK);
    lgfx::gpio_hi(TFT_RST);
  }
};

const unsigned long displayDuration = 2000;  // Display time (2 seconds)

LGFX lcd;  // LGFX instance
std::vector<String> fileList;
int currentIndex = 0;
int32_t screenWidth;
int32_t screenHeight;

void initFileSystem() {
  if (!LittleFS.begin()) {
    Serial.println("Failed to mount LittleFS");
    for (;;) yield();
  }
}

void createFileList() {
  File root = LittleFS.open("/", "r");
  File file = root.openNextFile();

  while (file) {
    String filePath = file.fullName();
    // String filePath = file.path();
    if (filePath.endsWith(".jpg") || filePath.endsWith(".png")) {
      fileList.push_back(filePath);
    }
    file = root.openNextFile();
  }
  root.close();

  if (fileList.empty()) {
    Serial.println("No image files found in LittleFS.");
    for (;;) yield();
  }
}

// Get JPEG image size
bool getJpgSize(File &file, int &width, int &height) {
  uint8_t data[5];
  file.read(data, 2);

  if (data[0] == 0xFF && data[1] == 0xD8) {
    while (file.read(data, 4) == 4) {
      if (data[0] == 0xFF && (data[1] >= 0xC0 && data[1] <= 0xC3)) {
        file.read(data, 5);
        height = data[1] << 8 | data[2];
        width = data[3] << 8 | data[4];
        return true;
      } else {
        uint16_t size = data[2] << 8 | data[3];
        file.seek(file.position() + size - 2);
      }
    }
  }
  return false;
}

// Get PNG image size
bool getPngSize(File &file, int &width, int &height) {
  uint8_t data[24];
  if (file.read(data, 24) == 24) {
    if (data[0] == 0x89 && data[1] == 0x50 && data[2] == 0x4E && data[3] == 0x47) {
      width = data[16] << 24 | data[17] << 16 | data[18] << 8 | data[19];
      height = data[20] << 24 | data[21] << 16 | data[22] << 8 | data[23];
      return true;
    }
  }
  return false;
}

void drawImage(const String &fileName) {
  File file = LittleFS.open(fileName, "r");
  if (!file) {
    Serial.println("Failed to open file: " + fileName);
    return;
  }

  int imgWidth = 0, imgHeight = 0;
  bool validImage = false;
  bool isJpg = false;

  // 画像サイズ取得
  if (fileName.endsWith(".jpg")) {
    validImage = getJpgSize(file, imgWidth, imgHeight);
    isJpg = true;
  } else if (fileName.endsWith(".png")) {
    validImage = getPngSize(file, imgWidth, imgHeight);
  }

  if (!validImage) {
    file.close();
    Serial.println("Invalid or unsupported image format.");
    return;
  }

  // ファイル開始位置を戻す
  file.seek(0);

  // スケーリング
  float scaleX = static_cast<float>(screenWidth) / imgWidth;
  float scaleY = static_cast<float>(screenHeight) / imgHeight;
  float scale = min(scaleX, scaleY);

  // x座標 センタリング
  int32_t x = (screenWidth - imgWidth * scale) / 2;

  lcd.startWrite();
  lcd.clear();
  if (isJpg) {
    lcd.drawJpg(&file, x, 0, screenWidth, screenHeight, 0, 0, scale);
  } else {
    lcd.drawPng(&file, x, 0, screenWidth, screenHeight, 0, 0, scale);
  }
  lcd.endWrite();
  file.close();
}

void setup() {
  Serial.begin(115200);

#ifdef TFT_BL
  // バックライトを設定
  pinMode(TFT_BL, OUTPUT);
  digitalWrite(TFT_BL, HIGH);
#endif

  // LCD初期化
  lcd.init();
  screenWidth = lcd.width();
  screenHeight = lcd.height();

  // LittleFSのファイルリストを作成
  initFileSystem();
  createFileList();
}

void loop() {
  // 画像を描画
  drawImage(fileList[currentIndex]);
  // 次の画像(インデックス)
  currentIndex = (currentIndex + 1) % fileList.size();
  delay(displayDuration);
}

スケッチを書き込む際にFlash Size: 4MB(Sketch: 1MB, FS:3MB)を選択してください。

このスケッチは1.3インチ ST7789 CSなしLCDに対応しています。

LCDピン/GPIO
GNDGND
VCC3.3V
SCL18
SDA19
RES27
DC26
BLK未接続


LCD接続GPIOは、実際の接続ピンに合わせて適宜変更できます。
お好みのLCDに対応させるには、LGFXクラスの定義を変えればよいでしょう。
以下の記事を参考にしてください。

画像ファイルアップロード

サンプル画像を用意しました。以下からダウンロードできます。

sample.zip

zipファイルを解凍するとdataフォルダがあります。
dataフォルダをスケッチフォルダに配置してください。

ファイルアップロード手順は以下の通りです。

ファイルのアップロードに成功するとこのように表示されます。
LCDに画像が表示されればOKです。

表示できる画像ファイルについて

.jpgと.pngの表示に対応しています。
お好みの画像ファイル(※)をスケッチフォルダ配下のdataフォルダに配置し、画像ファイルアップロードの手順でファイルをアップロードすれば表示されるかと思います。
ぜひお試しください。

※画像ファイルは合計で3MB以内

CircuitPython動作例

ボリュームの電圧を読み取り、LEDの光量をPWMで調節するという以下の内容を自作のユニバーサル基板で動かしてみました。

Getting Started with Raspberry Pi Pico and CircuitPython
The Raspberry Pi foundation changed single-board computing when they released the Raspberry Pi computer, now they're rea...

実際にボリュームを動かすとLEDの光量が変化するのを確認できました。

この自作ボードの記事はこちら

デバッガーを試してみました (Arduino使用)

Raspberry Pi Pico/RP2040ボード(Arduino-Pico)(バージョン4.0.2)でRaspberry Pi Debug Probeが使用できました。
Raspberry Pi Debug Probeのファームウェアは最新(v2.0.1)にしています。

参考URL:
https://www.raspberrypi.com/documentation/microcontrollers/debug-probe.html#updating-the-firmware-on-the-debug-probe

SWDの配線はRaspberry Pi Picoと同じです。

SWDにピンをつけずにスルーホール用テストワイヤー(TP-200)で配線しています

TP-200(秋月電子)

最後に

Raspberry Pi Pico用プログラムは互換性があり、手軽に動作させることができる感触を実感しています。

参考URL:
https://www.raspberrypi.com/documentation/microcontrollers/pico-series.html

関連記事

当ブログのマイコン記事です。ぜひご覧ください。

コメント

タイトルとURLをコピーしました