工学男子の日常

モノづくりが好きな男子の日記です。

ESP32C3でPWMを出力する方法(サーボモータ用に)

最近卒論から目を背けつつESP32C3を使って遊んでます。

性能に比してお値段が異常に安いです。

しかも秋月で手に入る。

akizukidenshi.com

外部アンテナの開発ボードもこの安さです。

akizukidenshi.com

超小型でバッテリー駆動可能、リモートIDに必要なBluetooth LE Coded PHY/Long Range対応ということで(自力での登録が可能化は別として)飛びもの搭載を考えている人も多いと思います。

 

自分もとりあえずということでBLEの飛距離テストを極寒の堤防で実施しました。

 

メインストリームのESP32無印やS3と違ってRISC-Vが1コアのシンプル構成、低消費電力、USBシリアル内蔵と素性がよくハマるポイントは少ないと思うのですが、サーボモータを動かそうとして少々困ったので記事にしました。

(ちなみに他の落とし穴としてはBLEのみ対応で通常のBluetoothに対応していない点があります(1敗))

 

というわけで、XIAO ESP32C3でPWMを出してみます。

サーボモータを想定して50Hz、500~2400μsのパルスです。

 

一応ライブラリもあるよ

ESP32Servo.hというライブラリがありまして、ArduinoIDEのライブラリマネージャーからもダウンロードできます。

github.com

本家のServo.hとほとんど同じ使い勝手で使えるのですが、なぜか2ピンまでしか制御できません。サーボが2個までなら便利だと思います。

とりあえずデーターシートを読む

https://www.espressif.com/sites/default/files/documentation/esp32-c3_technical_reference_manual_en.pdf

曰く

– LED PWM controller, with up to 6 channels

PWMは最大6チャンネルまでらしいです。なぜか本マイコンではPWMはLED用ということになってます。

 

曰く

Four independent timers that support division by fractions (31.2)

4つの分周可能な独立したタイマーがあるらしいです。

 

少し戻って曰く

The accuracy of duty cycle can be up to 18 bits. (3.4.7)

若干分かりづらいんですが分周比の分母は最大18bitらしいです。

分周に関してはかなり変な仕様になってます(31.3.2.2参照)。

 

曰く

Maximum PWM resolution: 14 bits (31.2)

PWMの解像度は最大14bitらしいです。

 

わかりやすい図があったので引用します。

分周の仕様が奇っ怪で謎なんですが結論からいうと、APBクロック80MHzを使った場合、最大40MHz、最低3Hz(←なぜ?)での出力が可能でした。

 

またTable 10より

LED PWM ledc_ls_sig_out0~5 Any GPIO pins

GPIOならどのピンからでもPWMが出力可能です(実際XIAOでもTx、Rxを除くD0~D5,D8~D10でできました)。

 

というわけでPWMは6チャンネル、どのGPIOからでも出力可能ですがタイマが4つなので周波数は4種類ということがわかりました。

SDKを読む

続いてArduinoIDEのpackageからESP32のSDKを読みます。

PWMの設定に使う関数はesp32-hal-ledc.hのledcSetup()です。

ソースファイルに以下の記述がありました。

/*
 * LEDC Chan to Group/Channel/Timer Mapping
** ledc: 0  => Group: 0, Channel: 0, Timer: 0
** ledc: 1  => Group: 0, Channel: 1, Timer: 0
** ledc: 2  => Group: 0, Channel: 2, Timer: 1
** ledc: 3  => Group: 0, Channel: 3, Timer: 1
** ledc: 4  => Group: 0, Channel: 4, Timer: 2
** ledc: 5  => Group: 0, Channel: 5, Timer: 2
** ledc: 6  => Group: 0, Channel: 6, Timer: 3
** ledc: 7  => Group: 0, Channel: 7, Timer: 3
** ledc: 8  => Group: 1, Channel: 0, Timer: 0
** ledc: 9  => Group: 1, Channel: 1, Timer: 0
** ledc: 10 => Group: 1, Channel: 2, Timer: 1
** ledc: 11 => Group: 1, Channel: 3, Timer: 1
** ledc: 12 => Group: 1, Channel: 4, Timer: 2
** ledc: 13 => Group: 1, Channel: 5, Timer: 2
** ledc: 14 => Group: 1, Channel: 6, Timer: 3
** ledc: 15 => Group: 1, Channel: 7, Timer: 3
*/

周波数を個別に設定したい場合はTImerが重ならないようにする必要があるため、一つ飛ばしで使えばいいことがわかります。

(実際は何種類も周波数が必要になるということはないと思いますが)

PWM出力する

まず1チャンネルのときです。

#define LEDC_CHANNEL 0
#define LEDC_PIN D0
#define LEDC_FREQUENCY 50
#define LEDC_RESOLUTION 14
#define LEDC_TIM_CLOCK 80000000

void setPulseWidthMicroseconds(uint32_t us){
    ledcWrite(LEDC_CHANNEL,(1<<LEDC_RESOLUTION)*us*LEDC_FREQUENCY/1000000);
}

void setup() {
  ledcSetup(LEDC_CHANNEL,LEDC_FREQUENCY,LEDC_RESOLUTION);
  ledcAttachPin(LEDC_PIN,LEDC_CHANNEL);
}

void loop() {
  setPulseWidthMicroseconds(500);
  delay(1000);
  setPulseWidthMicroseconds(2400);
  delay(1000);
}

サーボ用にパルス幅指定になってます。

タイマ設定はたぶんレジスタを直接叩いてもできるのですが、先述のややこしい分周設定があるのでledcSetup()でよしなにしてもらったほうがいいと思います。

 

複数チャンネルの場合は

#define LEDC_RESOLUTION 14

void setup() {
  ledcSetup(0,100,LEDC_RESOLUTION);
  ledcSetup(1,100,LEDC_RESOLUTION);//周波数ch0と共通
  ledcSetup(2,300,LEDC_RESOLUTION);
  ledcSetup(3,300,LEDC_RESOLUTION);//周波数ch2と共通
  ledcSetup(4,500,LEDC_RESOLUTION);
  ledcSetup(5,500,LEDC_RESOLUTION);//周波数ch4と共通
 
  ledcAttachPin(D0,0);
  ledcAttachPin(D1,1);
  ledcAttachPin(D2,2);
  ledcAttachPin(D8,3);
  ledcAttachPin(D9,4);
  ledcAttachPin(D10,5);

  ledcWrite(0,100);
  ledcWrite(1,200);
  ledcWrite(2,400);
  ledcWrite(3,800);
  ledcWrite(4,1600);
  ledcWrite(5,3200);
}

void loop() {
}

ほとんど同じですね。

ただピンとチャンネルで動かない組み合わせがあったので、データーシートをもうちょっと読み込む必要があるかもしれません。

 

以上になります。

小型軽量かつ内蔵アンテナでもそこそこ飛び、無線なしマイコンとしても優秀なチップなのでいろいろ使えそうです。なにより安いし、在庫があります。ESPシリーズの特徴(?)だった大消費電力もないので基板自作もし易いと思います(欲をいうとSDIOがあったら最強だった)。

 

ポールタイプの外部アンテナも買ってみたので、そのうちBLE飛距離更新を目指して実験するかもしれません。