コンテンツにスキップ
Arduino IDEで始める!STM32開発ボード入門ガイド 実践編

Arduino IDEで始める!STM32開発ボード入門ガイド 実践編

前回の入門編では、スイッチサイエンス STM32開発ボード STM32G431CBU6搭載(以下、STM32開発ボード)をArduino IDEで扱うためのセットアップと基本操作を紹介しました。今回はその続編として、より実践的な内容に踏み込み、GPIO制御、PWM出力、DAC出力、アナログ入力、シリアル通信(USB/USART) といった主要機能を、具体的なコード例とともに解説していきます。

STM32開発ボードの特徴とピン配置

あらためてSTM32開発ボードの特徴とピン配置を確認しておきましょう。

  • STマイクロエレクトロニクス 32 bitマイコン STM32G431CBU6搭載
  • Arm Cortex-M4コア(FPU内蔵)、最大170 MHz動作
  • 128 KBフラッシュメモリ
  • 32 KB SRAM
  • リセットスイッチRSTとブートモード切替スイッチBOOT
  • 汎用LEDPC4
  • 16 MHzクリスタルと32.768 kHzクリスタル
  • 30本のGPIOポート
  • Qwiic/STEMMA QTコネクター
  • 電源は TPS63802 昇降圧コンバータ採用で 1.8–5.5 V 入力対応

スイッチサイエンス STM32開発ボード STM32G431CBU6搭載ピン配置図

デジタル入出力

D0D31はArduinoでおなじみのデジタル入出力のピン番号です。PA0PB3のような表記はSTM32本来のポート名です。どちらの表記でもArduino IDE上でデジタル入出力に使えます。 基板のシルクがSTM32ポート名になっているので、こちらを使うと良いと思います。

PB3~PB9PB11PB12PB15PA8~PA10PA13~PA15は5 Vトレラントなので、5 V系の信号をそのまま入力として受け取れます。 入力としては5 Vに対応しますが、出力される電圧は常に3.3 Vです。

次のスケッチは基板上のLED(PC4)をBOOTボタン(PB8)で操作します。スケッチを書き込んだ後、BOOTボタンを押すとLEDが点灯します。

const int pinLED = PC4; // LED、LOWで点灯
const int pinBtn = PB8; // BOOTボタン、押すとHIGH

void setup() {
  pinMode(pinLED, OUTPUT);
  pinMode(pinBtn, INPUT);
}

void loop() {
  int rd = digitalRead(pinBtn);  // ボタンを押すとHIGH
  if (rd == HIGH) {
    digitalWrite(pinLED, LOW); // LOWで点灯
  } else {
    digitalWrite(pinLED, HIGH); // HIGHで消灯
  }
  delay(100);
}

入力ピンにプルアップ/プルダウンが必要な場合は INPUT_PULLUPINPUT_PULLDOWN が使えます。

pinMode(PA0, INPUT_PULLUP);
pinMode(PA0, INPUT_PULLDOWN);

また出力ピンのモードはオープンドレインも使えます。

pinMode(PA0, OUTPUT_OPEN_DRAIN);

PWM出力

Arduino の analogWrite() は、実際にはPWM信号(パルス幅変調)を使ってアナログ電圧を再現しています。この方法では、出力ピンを高速にON/OFFすることで、平均的な電圧を作り出しています。

ピン名の前に~(チルダ)がついているピンは、PWM出力に対応しています。 たとえば、~PA8~PB4 のように表示されていれば、そのピンから analogWrite() を使ってPWM信号を出力できます。

ただし、PA4PA5ピンは、DAC(デジタルアナログ変換器)機能が優先 されるため、
これらのピンでは analogWrite() を使っても PWM信号は出力されません

また、PWMの分解能(ビット数)周波数は以下の関数で変更できます:

  • analogWriteResolution(bits)
    PWMの分解能を指定できます。初期値は 10ビット16ビット32ビット も指定できます(※タイマーの性能に依存します)。
  • analogWriteFrequency(freq)
    PWMの周波数を指定できます。初期値は 1000Hz(1kHz)です。

次のスケッチはServoライブラリを使わずにPWMでサーボモータを操作します。

const int servoPin = PA10;  // PWM対応ピンを指定

void setup() {
  pinMode(servoPin, OUTPUT);
  analogWriteFrequency(50);  // サーボ用のPWM周波数(50Hz)に設定
  analogWriteResolution(16);  // 16ビット分解能(0〜65535)で指定
}

void loop() {
  setServoAngle(0);  // 0度
  delay(1000);
  setServoAngle(90);  // 90度
  delay(1000);
  setServoAngle(180);  // 180度
  delay(1000);
  setServoAngle(90);  // 90度
  delay(1000);
}

// サーボ角度を0〜180度で指定する関数
void setServoAngle(int angle) {
  // 0.5ms〜2.5msに相当するPWMデューティ比を計算(分解能に合わせる)
  int minPulse = 1638;  // 0.5ms / 20ms * 65535 ≒ 1638
  int maxPulse = 8191;  // 2.5ms / 20ms * 65535 ≒ 8191

  int duty = map(angle, 0, 180, minPulse, maxPulse);
  analogWrite(servoPin, duty);
}

DAC出力

PA4 および PA5 ピンには、DAC(デジタルアナログ変換器) が接続されています。これらのピンからは、PWMのような疑似アナログではなく、連続的な電圧値を直接出力 することができます。PWMと違ってフィルタ不要で滑らかな電圧が出せるのが大きな利点です。

音声出力のほか可変電圧源や波形生成器、モーターやアクチュエータに対して滑らかなアナログ信号を出したい時などに便利です。

次のスケッチはPA4PA5から正弦波を出力します。

#include <math.h>

const int pinDAC1 = PA4;  // DAC1_CH1
const int pinDAC2 = PA5;  // DAC1_CH2

const int numSamples = 100;      // 波形の分解能
const float amplitude = 2047.5;  // 12bit DACの最大値(4095)の半分
const float offset = 2047.5;     // 中心値

void setup() {
  analogWriteResolution(12);
}

void loop() {
  for (int i = 0; i < numSamples; i++) {
    float angle = (2 * M_PI * i) / numSamples;          // 0 〜 2π の範囲で分割
    int sineValue = offset + amplitude * sin(angle);    // サイン波
    int cosineValue = offset + amplitude * cos(angle);  // コサイン波
    analogWrite(pinDAC1, sineValue);
    analogWrite(pinDAC2, cosineValue);
  }
}

オシロスコープで PA4PA5 を観測すると、それぞれのピンから波形が出力されているのが確認できます。

STM32G4開発ボードDAC出力

アナログ入力

アナログ入力ピンは A0 から A14 までの 合計15本 が用意されています。analogRead() を使って、センサーやポテンショメータの値を手軽に読み取れます。

int rd = analogRead(A0);

この関数は、対象ピンの電圧(0〜3.3V)を整数値(初期設定で0〜1023) として返します。分解能の初期値は10ビットですが、analogReadResolution() で12ビットに変更できます。

analogReadResolution(12);

内部アナログ入力チャンネル

アナログ入力ピン以外にも内部専用のアナログ入力チャンネルがいくつか用意されています。

  • 内部温度センサーATEMP
    • IC内部の温度センサーです。出力電圧は温度に比例して変化します。キャリブレーションデータを使えば、正確な温度が得られます。
  • 電圧リファレンスAVREF
    • ADCの基準電圧の安定性を確保するための1.212Vの内部基準電圧が搭載されています。キャリブレーションデータを使えば、実際の電源電圧(VDDA)をmV単位で算出できます。
  • バッテリ電圧モニタリングVBAT
    • VBAT端子の電圧を読み取ることができます。この入力は内部で 1/3 に分圧されてから ADC に入力されるため、実際の電圧は3倍に補正する必要があります。
    • VBAT 端子は、ボード上で BAT_IN3.3V がOR接続(ダイオードを介した接続)されているため、通常は常に3.3Vが読み取られます。 そのため、3.3V未満の電圧(例:コイン電池など)を接続しても正しく測定することはできません

次のスケッチは内部温度センサーの温度を読み取り、シリアルモニターに出力します。STM32の内部温度センサーは個体差があるため、工場出荷時に記録されたキャリブレーションデータを使って補正することで、より正確な温度が得られます。

#define TS_CAL1 (*((uint16_t*)0x1FFF75A8))  // 3.0V  30℃時のADC値
#define TS_CAL2 (*((uint16_t*)0x1FFF75CA))  // 3.0V 130℃時のADC値
#define VREFINT (*((uint16_t*)0x1FFF75AA))  // 3.0V VREFINTのADC値

void setup() {
  Serial.begin(115200);
  analogReadResolution(12);
}

void loop() {
  uint32_t startTime = millis();
  uint32_t sum_temp = 0;
  uint32_t sum_vref = 0;
  uint16_t count = 0;

  while (millis() - startTime < 1000) {
    sum_temp += analogRead(ATEMP);
    sum_vref += analogRead(AVREF);
    count++;
  }

  uint16_t raw_temp = sum_temp / count;
  uint16_t raw_vref = sum_vref / count;

  // 実際のVDDAを算出(VREFINTは3.0V時の値)
  float vdda = 3.0 * ((float)VREFINT / (float)raw_vref);
  Serial.print("VDDA: ");
  Serial.print(vdda, 3);
  Serial.print("V ");

  // 補正後の温度値を算出(TS_CAL1/TS_CAL2の値は3.0V時の値)
  float temperature = ((float)((raw_temp * vdda) / 3.0) - TS_CAL1) * (130.0 - 30.0) / (TS_CAL2 - TS_CAL1) + 30.0;

  Serial.print("Temperature: ");
  Serial.print(temperature, 2);
  Serial.println(" °C");
}

シリアル通信

STM32開発ボードはUSB経由の仮想COMポート(CDCクラス)とU(S)ARTのどちらもサポートしており、ツールメニューの設定内容により次のようにインタフェースが変化します。

ツール→USB support (if available)

選択項目 Serial の実体 利用可能なポート
CDC (generic ‘Serial’ supersede U(S)ART) SerialUSB(CDC) Serial, SerialUSB(同じもの)
CDC (no generic ‘Serial’) - SerialUSB のみ

ツール→U(S)ART support

選択項目 Serial の実体 利用可能なポート
Enabled (generic ‘Serial’) SerialLP1(LPUART1) Serial, SerialLP1(同じもの)
Enabled (no generic ‘Serial’) - SerialLP1 のみ

次のスケッチはSerialLP1SerialUSBを相互に接続するパススルー通信の例です。PA2(TX)PA3(RX)をジャンパー線で接続すると、シリアルモニターから送信したメッセージがループバックされるのを確認できます。ツール→USB support (if available)でCDC (generic 'Serial' supersede U(S)ART)を選択、ツール→U(S)ART supportでEnabled (generic 'Serial')を選択してからスケッチを書き込んでください。

void setup() {
  SerialUSB.begin(115200);
  while(!Serial);
  SerialLP1.begin(9600);
}

void loop() {
  while (SerialUSB.available()){
    SerialLP1.write(SerialUSB.read());
  }
  while (SerialLP1.available()){
    SerialUSB.write(SerialLP1.read());
  }
  delay(1);
}

STM32G431CBU6は、次の5つの U(S)ART を内蔵しており、Arduinoで標準定義されているSerialは、これらのうち LPUART1 を使用したSerialLP1にマッピングされます。

  • USART1
  • USART2
  • USART3
  • UART4
  • LPUART1(Low Power UART)

USART1やUSART2を使いたい場合は、次のように U(S)ART を明示的に指定して初期化します。

HardwareSerial MySerial1(USART1); // RX:PA10, TX:PA9
HardwareSerial MySerial2(USART2); // RX:PA3_ALT1, TX:PA2_ALT1
HardwareSerial MySerial3(USART3); // RX:PB8, TX:PB9

次のようにピン番号を指定することもできます。この場合、U(S)ART に対応するRXピン、TXピンを指定する必要があります。

HardwareSerial MySerial1(PB7, PB6); // USART1(RX:PB7, TX:PB6)
HardwareSerial MySerial2(PA15, PA14); // USART2(RX:PA15, TX:PA14)
HardwareSerial MySerial3(PB11, PB10); // USART3(RX:PB11, TX:PB10)

各USARTに対応するピンの一覧です。この表には、STM32ボードで使えるUART/USARTポートごとのRXピン、TXピン、そしてハードウェアフロー制御に使えるRTS(送信要求)ピンとCTS(受信許可)ピンが記載されています。ハードウェアフロー制御を使いたい場合は、RTSピンとCTSピンも指定してください。

UART/USART RXピン TXピン RTSピン CTSピン
USART1 PA10, PB7 PA9, PB6, PC4 PA12 PA11
USART2 PA3_ALT1, PA15, PB4 PA2_ALT1, PA14, PB3 PA1 PA0
USART3 PB8, PB11, PC11_ALT1 PB9, PB10, PC10_ALT1 PB14 PA13, PB13_ALT1
UART4 PC11 PC10 PA15 PB7
LPUART1 PA3, PB10 PA2, PB11 PB1, PB12 PA6, PB13

※UART4のRXピンPC11とTXピンPC10は基板上に端子として出ていないため、実際の接続には使用できません。

以上です。今回紹介したスケッチはこちらからもダウンロードできます。

次回は、Qwiic/STEMMA QTコネクターを使った I2C通信、STM32開発ボードで便利なライブラリの紹介、精密なタイミング制御に重要な外部システムクロック(HSE)の有効化方法についても詳しく解説する予定です。

前の記事 The LiDAR Solid-State Advantage
次の記事 Our Newest Sensor Has a Real Eye for Ambient Light