買っただけでほぼ放置状態のESP32を何かに利用したい!
出来れば自分の経験(パワエレ)を活かしている感じのやつ!
ということで、以前STM32で作った永久磁石同期モータのエンコーダ付きベクトル制御インバータをWifi経由でコントロールするシステムを作ってみました
初めてESP32でWebサーバを構築してみましたが、POST?GET?なに??みたいな感じなので苦労したのは言わずもがな・・・
IoTインバータの構成図
全体構成を図にまとめました
図にまとめるほどではないくらいシンプルな構成です
モータドライバ側の作業:シリアル通信機能の追加
下記記事で紹介しているベクトル制御インバータをベースにシリアル通信で回転数指令値を受けるようにしました
ついでにIoT機らしく(?)消費電力や温度などの情報をシリアル通信で送信する機能も搭載しました

ASCII文字での通信はダイキライなのでバイナリ通信で構成しました
0x00を区切り文字にしてデータの方は上手いことやるCOBS (Consistent Overhead Byte Stuffing)を採用しました
COBSの詳細は以下Qiitaの記事が非常にわかりやすく、参考にしましたのでみなさんも参考にしてみてください
通信相手のESP32(Arduino)はPacketSerialというライブラリを使えば一発っぽいんだけど、こっちはSTM32CubeMXで多分そんなライブラリないので自作しました・・・
とりあえずヘッダ1byteと区切り文字0x00を含む生の通信データ(未加工データ)をCOBS形式にエンコードする関数とCOBS形式を未加工データにデコードする関数を作成しました
#include "packetSerial.h"
void decodePacket( uint8_t *data, uint16_t size ){
uint16_t next0 = data[0];
for( uint16_t i = 1; i < size; i++){
if( i == next0 ){
next0 += data[i];
data[i] = 0;
}
}
}
void encodePacket( uint8_t *data, uint16_t size ){
uint8_t *zerobuf = data;
data[0] = data[size-1] = 0;
for( uint16_t i = 1; i < size; i++){
if( data[i] == 0 ){
*zerobuf = &data[i] - zerobuf;
zerobuf = &data[i];
}
}
}
#ifndef PACKET_SERIAL_H
#define PACKET_SERIAL_H
#include <stdint.h>
void decodePacket( uint8_t *data, uint16_t size );
void encodePacket( uint8_t *data, uint16_t size );
#endif
多分ちゃんと動くはず!(テキトー)
インバータから送信するデータは母線電圧検出値(電源電圧)、サーミスタ温度(IC温度)、エンコーダで取り込んだ回転数検出値(回転数検出値)、出力電力(消費電力)の4つのFloat型の変数(計16byte+ヘッダ/区切り文字2byte)をそのまま送ります
受信するデータは回転数指令値だけなのでこちらもFloat型の変数で受けます
本来であればチェックSumやCRCでエラーチェックすべきなんだろうけど面倒なのでパス
PB6、PB7をシリアルポート(USART1)に割り当ててその他の設定としては以前紹介した受信はDMA処理、送信はブロッキング処理の方式にしました

メインループの中で毎回受信有無のチェックを行い、1秒に1回送信処理を行うようにしました
最終的なmain.cは以下の通りになりました
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2023 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "adc.h"
#include "comp.h"
#include "dac.h"
#include "dma.h"
#include "usart.h"
#include "tim.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdlib.h>
#include <stdio.h>
#include "define.h"
#include "packetSerial.h"
#include <math.h>
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
uint16_t adc1Buf;
uint16_t adc2Buf[2];
uint8_t encEdgeDetect;
extern float omgDst;
static uint8_t brkErr;
float Tic, vVr;
uint8_t onOff;
static uint8_t rxBuf[256];
static uint16_t rxBtm = 0;
extern float vBus, omgFlt, iD, iQ, vDRef, vQRef;
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
extern void pwmEnable(void);
extern void pwmDisable(void);
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_LPUART1_UART_Init();
MX_ADC1_Init();
MX_ADC2_Init();
MX_ADC3_Init();
MX_TIM1_Init();
MX_TIM3_Init();
MX_TIM6_Init();
MX_COMP1_Init();
MX_COMP4_Init();
MX_COMP6_Init();
MX_DAC1_Init();
MX_DAC2_Init();
MX_DAC3_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
setbuf( stdout, NULL );
setbuf( stdin, NULL );
HAL_ADCEx_InjectedStart_IT(&hadc1);
HAL_ADCEx_InjectedStart_IT(&hadc2);
HAL_ADCEx_InjectedStart_IT(&hadc3);
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)&adc1Buf, 1);
HAL_ADC_Start_DMA(&hadc2, (uint32_t *)adc2Buf, 2);
HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_1);
HAL_TIMEx_PWMN_Start_IT(&htim1, TIM_CHANNEL_1);
HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_2);
HAL_TIMEx_PWMN_Start_IT(&htim1, TIM_CHANNEL_2);
HAL_TIM_PWM_Start_IT(&htim1, TIM_CHANNEL_3);
HAL_TIMEx_PWMN_Start_IT(&htim1, TIM_CHANNEL_3);
pwmDisable();
HAL_TIM_Encoder_Start_IT(&htim3, TIM_CHANNEL_ALL);
HAL_DAC_Start(&hdac1, DAC1_CHANNEL_1);
HAL_DAC_Start(&hdac2, DAC1_CHANNEL_1);
HAL_DAC_Start(&hdac3, DAC1_CHANNEL_2);
HAL_DAC_SetValue(&hdac1, DAC1_CHANNEL_1, DAC_ALIGN_12B_R, 0xFFF);
HAL_DAC_SetValue(&hdac2, DAC1_CHANNEL_1, DAC_ALIGN_12B_R, 0xFFF);
HAL_DAC_SetValue(&hdac3, DAC1_CHANNEL_2, DAC_ALIGN_12B_R, 0xFFF);
HAL_UART_Receive_DMA(&huart1, rxBuf, sizeof(rxBuf));
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
uint32_t tick = HAL_GetTick();
while (1)
{
uint16_t rxTop = sizeof(rxBuf) - huart1.hdmarx->Instance->CNDTR;
if( rxTop != rxBtm ){
uint16_t rxSize = (rxTop-rxBtm)&(sizeof(rxBuf)-1);
for( uint8_t i=0; i<rxSize; i++ ){
uint8_t rxBtmBuf = (rxBtm + i) & (sizeof(rxBuf)-1);
if( rxBuf[rxBtmBuf] == 0 ){
rxSize = i + 1;
break;
}
else if( i == rxSize-1 ){
rxSize = 0;
}
}
if( rxSize == 0 ) continue;
uint8_t packet[8];
if( rxSize > sizeof(packet) ){
rxBtm = rxTop;
continue;
}
for( uint16_t i=0; i<rxSize; i++ ){
packet[i] = rxBuf[rxBtm];
rxBtm = (rxBtm + 1)&(sizeof(rxBuf)-1);
}
decodePacket( packet, rxSize );
float omgBuf = *(float *)&packet[1] / 60.0f * (_MOTOR_POLE>>1) * _RAD(360.0f);
if( omgBuf > 233.3f * _RAD(360.0f) ) omgBuf = 233.3f * _RAD(360.0f);
else if( omgBuf < 0.0f ) omgBuf = 0.0f;
if( !brkErr ){
omgDst = omgBuf;
}
}
if( HAL_GetTick() - tick > 1000 ){
tick = HAL_GetTick();
float vth = adc2Buf[0] * _REG2VOLT;
float Rth = (_VCC-vth)/vth * _TH_PDR;
Tic = _TH_B / ( logf(Rth) - logf(_TH_R0) + (_TH_B/_TH_T0) ) - 273.15f;
float pD = iD*vDRef;
float pQ = iQ*vQRef;
if( pD < 0 ) pD = -pD;
if( pQ < 0 ) pQ = -pQ;
uint8_t txBuf[18];
*(float *)&txBuf[1] = vBus;
*(float *)&txBuf[5] = Tic;
*(float *)&txBuf[9] = omgFlt * 60.0f/((_MOTOR_POLE>>1) * _RAD(360.0f));
*(float *)&txBuf[13] = pD + pQ;
encodePacket( (uint8_t *)txBuf, sizeof(txBuf) );
HAL_UART_Transmit(&huart1, (uint8_t *)txBuf, sizeof(txBuf), 100);
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Configure the main internal regulator output voltage
*/
HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1_BOOST);
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV4;
RCC_OscInitStruct.PLL.PLLN = 85;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
void HAL_TIMEx_BreakCallback(TIM_HandleTypeDef *htim)
{
brkErr = 1;
HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET);
}
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if( htim == &htim3 )
{
encEdgeDetect = 1;
}
}
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
モータドライバ側は以上で作業終了です
ESP32側の作業①:ESPAsyncWebServerとPacketSerial
要となるWebサーバーはESPAsyncWebServerライブラリを使用しました
開発環境は以前の記事で紹介している通りPlatformIOで、ライブラリのインストールもPlatformIOで可能です


んでWeb系の知識がないので半分以上適当なんだけど、PCやスマホといったクライアント側からURLにアクセスされたときの処理(ホスティング?)をそれぞれ記述します
URL | 通信方式 | 処理内容 |
---|---|---|
/ (ルート) | GET | index.htmlを送信 |
/script.js | GET | script.jsを送信(index.html経由) |
/get-vdc | GET | Float型の変数Vdcの値を文字列に変換したデータを送信 |
/get-temp | GET | Float型の変数Tempの値を文字列に変換したデータを送信 |
/get-rpm-det | GET | Float型の変数RpmDetの値を文字列に変換したデータを送信 |
/get-power | GET | Float型の変数Powerの値を文字列に変換したデータを送信 |
/post-rpm | POST | 受信した数値データをFloat型に変換してPacketSerialでインバータ側に送信 (クライアント側にはリクエストOKの200を返す) |
ちなみに、今回は使っていないので省略してるけど使いたければCSSファイルへのアクセスも記述する必要があります
インバータ側との通信は前述の通りPacketSerialでCOBS形式でバイナリ通信します
こちらはArduino様なのでライブラリがあります(ありがたや~)
先ほどの手順でPacketSerialもインストールします

PacketSerialの使い方は簡単で以下手順でセットアップします
- 普通にシリアルポートを設定
- setStream関数で先ほどのシリアルポートを指定(シリアルポートがラップされる)
- setPacketHandler関数でデータ受信時のコールバック関数を指定
- メインループ関数(loop関数)でupdate関数を実行する(+オーバーフローエラー処理を記述する)
データ受信時のコールバック関数にてサーバー側のGET通信で送信する各変数(Vdc, Temp, RpmDet, Power)を更新します
送信処理はサーバー側のPOST通信で受信したデータをFloat型に変換して送信します
説明はあっさりしてますがここまでくるまでにかなり試行錯誤しました・・・
様々な記事やフォーラムなど参考にしたのですが色々見すぎてもはやどこを参考にしたのか追いきれないので参考サイトは省略させてください・・・(ゴメンナサイ)
最終的なmain.cppは以下の通りになりました
なお、ssidとpasswordはネットワーク環境に合わせて設定が必要です
#include <Arduino.h>
#include <WiFi.h>
#include "ESPAsyncWebServer.h"
#include "SPIFFS.h"
#include "PacketSerial.h"
//ネットワーク情報
const char* ssid = "<WifiネットワークのSSIDを入力>";
const char* password = "<Wifiネットワークのパスワードを入力>";
void getRpmRef(AsyncWebServerRequest *request);
void recievePacket(const uint8_t* buffer, size_t size);
AsyncWebServer server(80);
HardwareSerial invSerial(2);
PacketSerial invPacket;
float Vdc = 0.0, Temp = 0.0f, RpmDet = 0.0f, Power = 0.0f;
void setup() {
// デバッグ用
Serial.begin(115200);
// INV通信用(PacketSerial)
invSerial.begin(115200);
invPacket.setStream(&invSerial);
invPacket.setPacketHandler(recievePacket);
// データアクセス用
SPIFFS.begin();
// 接続が成功したらデバッグポートにIPを表示
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected.");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
// 各URLにアクセスされたときの処理を記述
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/index.html");
});
server.on("/script.js", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(SPIFFS, "/sample.js");
});
server.on("/get-vdc", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plane", String(Vdc) );
});
server.on("/get-temp", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plane", String(Temp) );
});
server.on("/get-rpm-det", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plane", String(RpmDet) );
});
server.on("/get-power", HTTP_GET, [](AsyncWebServerRequest *request){
request->send(200, "text/plane", String(Power) );
});
server.on("/post-rpm", HTTP_POST, getRpmRef);
server.begin();
}
void getRpmRef(AsyncWebServerRequest *request){
int params = request->params();
AsyncWebParameter* p = request->getParam(0);
String value = request->getParam(0)->value();
// Serial.println(value);
float fvalue = value.toFloat();
invPacket.send( (uint8_t *)&fvalue, 4);
request->send(200);
}
// PacketSerialの受信コールバック関数
void recievePacket(const uint8_t* buffer, size_t size){
// for( uint8_t i=0; i<size; i++ ){
// Serial.print(buffer[i],HEX);
// }
// Serial.print("\r\n");
if( size != 16 ) return;
Vdc = *(float *)buffer;
Temp = *( (float *)buffer + 1 );
RpmDet = *( (float *)buffer + 2 );
Power = *( (float *)buffer + 3 );
}
void loop() {
invPacket.update();
if( invPacket.overflow() ){
Serial.println("Error: Over Flow");
}
}
ESP32側の作業②:index.htmlとscript.jsを作成しSPIFFSに書き込み
サーバーのバックエンド側は完成したのでお次はフロントエンド側です!
UI画面としては以下のような画面にしました
回転数指令値:0rpm
電源電圧:—
IC温度:—
回転数検出値:—
消費電力:—
回転数指令値はスライダーで設定する方式にしました
運転開始ボタンをクリックするとスライダーで設定した指令値がPOST送信され、運転停止ボタンをクリックすると指令値(0rpm)が送信されます
また、運転中はスライダーを動かすとリアルタイムに指令値が送信されるようにします
電源電圧などの情報は文字列で表示するようにしました
最終的なコードは以下のようになりました
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
</head>
<body>
<p>
<input type="button" value="運転開始" id="startMotor" onclick="startMotor();">
<input type="button" value="運転停止" id="stopMotor" onclick="stopMotor();">
</p>
<div>
<input type="range" min="0" max="1000" step="1" value="0" id="rpmSlider">
<p id="rpmRef">回転数指令値:0rpm</p>
<p id="vdc">電源電圧:---</p>
<p id="temp">IC温度:---</p>
<p id="rpmDet">回転数検出値:---</p>
<p id="power">消費電力:---</p>
</div>
<script src="script.js"></script>
</body>
</html>
外観はBootstrapでCoolにキメようと最初は意気込んでいたみたいですが何もしてませんね・・・(汗
UI画面の表示を更新したりサーバー側との通信処理をJavascriptで作成します
通信は非同期通信とするためAjaxを使用しています(実はあまりわかっていない)
こちらの記事をものすごく参考にさせて頂きました
電源電圧などの情報を取得するGET通信は1000ms間隔で実行されるtim関数で実行しています
Javascriptはよう知らんのやけどほぼほぼC言語だからググればなんとかなる感じです
というかこんなんでちゃんと動くからスゲーって思っちゃいます
最終的なコード例は以下の通りです
var runMotor = false;
let timID = setInterval('tim()',1000); // タイマーをセット(1000ms間隔);
let element = document.getElementById('rpmSlider');
element.addEventListener('input', inputChange);
// スライダーが更新されたときの処理
function inputChange(){
document.getElementById('rpmRef').innerText = "指令値:"+ String(rpmSlider.value) + 'rpm';
if( runMotor ){
postRpm(rpmSlider.value);
}
}
// 1000ms間隔で実行する処理
function tim(){
getVdc();
getTemp();
getRpmDet();
getPower();
}
// 回転数指令値をPOST通信で送信する処理
function postRpm( rpm ){
var xhr = new XMLHttpRequest();
xhr.open('POST', '/post-rpm');
xhr.setRequestHeader('content-type', 'application/x-www-form-urlencoded;charset=UTF-8');
xhr.send( String(rpm) );
}
// 電源電圧をGET通信で取得する処理
function getVdc(){
var xhr = new XMLHttpRequest();
xhr.open('GET', '/get-vdc');
xhr.send();
xhr.onreadystatechange = function(){
if( xhr.readyState == 4 && xhr.status == 200 ){
setVdc(xhr.responseText);
}
else{
setVdc("---");
}
}
}
// 電源電圧をUIに表示する処理
function setVdc(txt){
document.getElementById("vdc").innerHTML = "電源電圧:" + txt + "V";
}
// IC温度をGET通信で取得する処理
function getTemp(){
var xhr = new XMLHttpRequest();
xhr.open('GET', '/get-temp');
xhr.send();
xhr.onreadystatechange = function(){
if( xhr.readyState == 4 && xhr.status == 200 ){
setTemp(xhr.responseText);
}
else{
setTemp("---");
}
}
}
// IC温度をUIに表示する処理
function setTemp(txt){
document.getElementById("temp").innerHTML = "IC温度:" + txt + "℃";
}
// 回転数検出値をGET通信で取得する処理
function getRpmDet(){
var xhr = new XMLHttpRequest();
xhr.open('GET', '/get-rpm-det');
xhr.send();
xhr.onreadystatechange = function(){
if( xhr.readyState == 4 && xhr.status == 200 ){
setRpmDet(xhr.responseText);
}
else{
setRpmDet("---");
}
}
}
// 回転数検出値をUIに表示する処理
function setRpmDet(txt){
document.getElementById("rpmDet").innerHTML = "回転数検出値:" + txt + "rpm";
}
// 消費電力をGET通信で取得する処理
function getPower(){
var xhr = new XMLHttpRequest();
xhr.open('GET', '/get-power');
xhr.send();
xhr.onreadystatechange = function(){
if( xhr.readyState == 4 && xhr.status == 200 ){
setPower(xhr.responseText);
}
else{
setPower("---");
}
}
}
// 消費電力をUIに表示する処理
function setPower(txt){
document.getElementById("power").innerHTML = "消費電力:" + txt + "W";
}
// 運転開始ボタンをクリックしたときの処理
function startMotor(){
document.getElementById("startMotor").disabled = true; // 開始ボタンの無効化
runMotor = true;
postRpm(rpmSlider.value); // スライダーの値を送信
}
// 運転停止ボタンをクリックしたときの処理
function stopMotor(){
document.getElementById("startMotor").disabled = false; // 開始ボタンの有効化
runMotor = false;
postRpm(0); // 0rpm(停止指令)を送信
}
ESP32にはSPIFFSというFLASH領域があり、サーバー側からアクセスが可能なのでここにindex.htmlとscript.jsを書き込みます
PlatformIOの場合は「Upload Filesystem Image (esp32dev)」というタスクを実行するだけでSPIFFSへの書き込みが出来ます

nucleoとESP32の配線
それぞれのシリアルポート同士とGNDを接続します
- nucleoのPB6(USART1_TX)とESP32のGPIO16(RXD2)を接続
- nucleoのPB7(USART1_RX)とESP32のGPIO17(TXD2)を接続
- GNDは適当な箇所を接続


ちなみにESP32からnucleoにVcc供給しようとしたら接続した瞬間ESP32がリセットを繰り返す状態になりました
恐らく電圧がドロップしてリセット→リセットがかかったのでドロップが解消して再起動→電圧ドロップ・・・といったループになっているものと考えられます
コンデンサ入れれば解決しそうだけど面倒だったのでVccはそれぞれ別電源で供給するようにしました
IoTインバータの動作確認
PCからサーバに接続してモータを動作させてみました
スライダーを操作することでモータの回転数が制御されることを確認しました
電源電圧などの情報もしっかり取得出来ておりまさにIoTインバータといった感じです


もちろんスマホからのアクセスも可能です

【まとめ】IoTインバータを作成してESP32の使い方を理解できた
IoTインバータを構築するためにESP32のWebサーバの構築とインバータ側との通信処理、フロントエンドのプログラム例を紹介しました
今回インバータ側にはほぼ手をつけていないのでほぼほぼESP32側の作業がメインでした
個人的にWeb系は苦手意識があり、サーバーとの通信も知識・経験共に乏しかったのですがだいぶ理解を深めることが出来ました
ESP32を使って何かしたいという人の参考になれば幸いです