ESP32 Arduinoの実装を覗いてみる

Posted on December 7, 2023

arduino-esp32

ESP32のArduinoは、ESP-IDFで実装されたものであり、そのESP-IDFもまた、FreeRTOSを利用して実装されたものだである。

setup()loop()

main関数はarduino-esp32/blob/master/cores/esp32/main.cppで定義される。

void loopTask(void *pvParameters)
{
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SERIAL)
    // sets UART0 (default console) RX/TX pins as already configured in boot or as defined in variants/pins_arduino.h
    Serial0.setPins(gpioNumberToDigitalPin(SOC_RX0), gpioNumberToDigitalPin(SOC_TX0));
#endif
#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG
    printBeforeSetupInfo();
#else
    if(shouldPrintChipDebugReport()){
        printBeforeSetupInfo();
    }
#endif
    setup();
#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_DEBUG
    printAfterSetupInfo();
#else
    if(shouldPrintChipDebugReport()){
        printAfterSetupInfo();
    }
#endif
    for(;;) {
#if CONFIG_FREERTOS_UNICORE
        yieldIfNecessary();
#endif
        if(loopTaskWDTEnabled){
            esp_task_wdt_reset();
        }
        loop();
        if (serialEventRun) serialEventRun();
    }
}

extern "C" void app_main()
{
#if ARDUINO_USB_CDC_ON_BOOT && !ARDUINO_USB_MODE
    Serial.begin();
#endif
#if ARDUINO_USB_MSC_ON_BOOT && !ARDUINO_USB_MODE
    MSC_Update.begin();
#endif
#if ARDUINO_USB_DFU_ON_BOOT && !ARDUINO_USB_MODE
    USB.enableDFU();
#endif
#if ARDUINO_USB_ON_BOOT && !ARDUINO_USB_MODE
    USB.begin();
#endif
    loopTaskWDTEnabled = false;
    initArduino();
    xTaskCreateUniversal(loopTask, "loopTask", getArduinoLoopTaskStackSize(), NULL, 1, &loopTaskHandle, ARDUINO_RUNNING_CORE);
}

ESP-IDFではmain関数をapp_main()と呼ぶ。

ここでやってることはオリジナルArduinoと大体同じ。

オリジナルArduinoと違うのは、FreeRTOSの機能で実質main関数loopTask()のタスクを作っている。xTaskCreateUniversal()の最後の引数はコアの指定。

Arduino IDEの実行コア選択メニューもここのマクロを弄っているのだろう。

Xtensa系のESP32大体コア二つ付いてるんだけど、Arduinoだと基本的に一つしか使わない感じ。

ちなみにオリジナルArduinoのmain関数は

int main(void)
{
	init();

	initVariant();

#if defined(USBCON)
	USBDevice.attach();
#endif
	
	setup();
    
	for (;;) {
		loop();
		if (serialEventRun) serialEventRun();
	}
        
	return 0;
}

うん。

ESP32のデュアルコアの扱いについても、基本的にFreeRTOSのタスク関連のAPIでやる。

シリアル

本家Arduinoのシリアルライブラリーに加えて、arduino-esp32のシリアルにはコールバック的な機能がある。

void HardwareSerial::onReceive(OnReceiveCb function, bool onlyOnTimeout)
{
    HSERIAL_MUTEX_LOCK();
    // function may be NULL to cancel onReceive() from its respective task
    _onReceiveCB = function;

    // setting the callback to NULL will just disable it
    if (_onReceiveCB != NULL) {
        // When Rx timeout is Zero (disabled), there is only one possible option that is callback when FIFO reaches 120 bytes
        _onReceiveTimeout = _rxTimeout > 0 ? onlyOnTimeout : false;

        // in case that onReceive() shall work only with RX Timeout, FIFO shall be high
        // this is a work around for an IDF issue with events and low FIFO Full value (< 3)
        if (_onReceiveTimeout) {
            uartSetRxFIFOFull(_uart, 120);
            log_w("OnReceive is set to Timeout only, thus FIFO Full is now 120 bytes.");
        }

        // this method can be called after Serial.begin(), therefore it shall create the event task
        if (_uart != NULL && _eventTask == NULL) {
            _createEventTask(this); // Create event task
        }
    }
    HSERIAL_MUTEX_UNLOCK();
}

他にはonReceiveError()もある。

#define HSERIAL_MUTEX_LOCK()    do {} while (xSemaphoreTake(_lock, portMAX_DELAY) != pdPASS)
#define HSERIAL_MUTEX_UNLOCK()  xSemaphoreGive(_lock)

これらもFreeRTOSのタスクで実装している。

void HardwareSerial::_createEventTask(void *args)
{
    // Creating UART event Task
    xTaskCreateUniversal(_uartEventTask, "uart_event_task", ARDUINO_SERIAL_EVENT_TASK_STACK_SIZE, this, ARDUINO_SERIAL_EVENT_TASK_PRIORITY, &_eventTask, ARDUINO_SERIAL_EVENT_TASK_RUNNING_CORE);
    if (_eventTask == NULL) {
        log_e(" -- UART%d Event Task not Created!", _uart_nr);
    }
}

Wireについて、オリジナルArduinoもonReceive()があって、それはAVRのISRで実装しているらしいけど、どうやって動いているのかはわからない。

組込み開発わからん。

なんか適当に書いちゃったけど内容が薄ぺらいわりに間違いもあるかもしれん。

申し訳ない。