
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
- 汎用LED
PC4
- 16 MHzクリスタルと32.768 kHzクリスタル
- 30本のGPIOポート
- Qwiic/STEMMA QTコネクター
- 電源は TPS63802 昇降圧コンバータ採用で 1.8–5.5 V 入力対応
デジタル入出力
D0
〜D31
はArduinoでおなじみのデジタル入出力のピン番号です。PA0
やPB3
のような表記はSTM32本来のポート名です。どちらの表記でもArduino IDE上でデジタル入出力に使えます。 基板のシルクがSTM32ポート名になっているので、こちらを使うと良いと思います。
PB3~PB9
、PB11
、PB12
、PB15
、PA8~PA10
、PA13~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_PULLUP
や INPUT_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信号を出力できます。
ただし、PA4
とPA5
ピンは、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と違ってフィルタ不要で滑らかな電圧が出せるのが大きな利点です。
音声出力のほか可変電圧源や波形生成器、モーターやアクチュエータに対して滑らかなアナログ信号を出したい時などに便利です。
次のスケッチはPA4
とPA5
から正弦波を出力します。
#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);
}
}
オシロスコープで PA4
と PA5
を観測すると、それぞれのピンから波形が出力されているのが確認できます。
アナログ入力
アナログ入力ピンは 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_IN
と3.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 のみ |
次のスケッチはSerialLP1
とSerialUSB
を相互に接続するパススルー通信の例です。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)の有効化方法についても詳しく解説する予定です。