ESP32は、WiFiおよびBluetooth対応の次世代マイクロコントローラーです。これは、上海を拠点とするEspressifの後継者であり、非常に人気があり、愛好家にとっては革命的です。 ESP8266マイクロコントローラー 。
マイクロコントローラーの中でも巨大なESP32の仕様には、台所の流し台以外のすべてが含まれています。これはシステムオンチップ(SoC)製品であり、そのすべての機能を利用するには、実際にはオペレーティングシステムが必要です。
このESP32チュートリアルでは、タイマー割り込みからアナログ-デジタルコンバーター(ADC)をサンプリングする際の特定の問題について説明し、解決します。 ArduinoIDEを使用します。機能セットの点で最悪のIDEの1つであっても、Arduino IDEは少なくともセットアップとESP32開発での使用が簡単であり、さまざまな一般的なハードウェアモジュール用のライブラリの最大のコレクションを備えています。ただし、代わりに多くのネイティブESP-IDFAPIも使用します Arduino パフォーマンス上の理由から。
ESP32には、2つのグループに分けられた4つのハードウェアタイマーが含まれています。すべてのタイマーは同じで、16ビットのプリスケーラーと64ビットのカウンターがあります。プリスケール値は、タイマーに入る内部80 MHzクロックからのハードウェアクロック信号をN番目のティックごとに制限するために使用されます。プリスケールの最小値は2です。これは、割り込みが公式に最大40MHzで発生する可能性があることを意味します。これは悪いことではありません。最高のタイマー解像度では、ハンドラーコードは最大6クロックサイクル(240MHzコア/ 40MHz)で実行する必要があるためです。タイマーには、いくつかの関連するプロパティがあります。
divider
—周波数プリスケール値counter_en
—タイマーに関連付けられた64ビットカウンターが有効かどうか(通常はtrue)counter_dir
—カウンターがインクリメントされるかデクリメントされるかalarm_en
—「アラーム」、つまりカウンターのアクションが有効になっているかどうかauto_reload
—アラームがトリガーされたときにカウンターがリセットされるかどうか重要な個別のタイマーモードのいくつかは次のとおりです。
タイマーのカウンターは任意のコードで読み取ることができますが、ほとんどの場合、定期的に何かを実行することに関心があります。つまり、割り込みを生成するようにタイマーハードウェアを構成し、それを処理するコードを記述します。
割り込みハンドラ関数は、次の割り込みが生成される前に終了する必要があります。これにより、関数が取得できる複雑さの上限が厳しくなります。一般に、割り込みハンドラーは可能な限り最小限の作業を実行する必要があります。
リモートで複雑なことを実現するには、代わりに、割り込みのないコードによってチェックされるフラグを設定する必要があります。単一のピンを単一の値に読み取ったり設定したりするよりも複雑な種類のI / Oは、多くの場合、別のハンドラーにオフロードする方が適切です。
ブートストラップテーマとは
ESP-IDF環境では、FreeRTOS関数vTaskNotifyGiveFromISR()
割り込みハンドラー(割り込みサービスルーチン、またはISRとも呼ばれます)に実行する何かがあることをタスクに通知するために使用できます。コードは次のようになります。
portMUX_TYPE DRAM_ATTR timerMux = portMUX_INITIALIZER_UNLOCKED; TaskHandle_t complexHandlerTask; hw_timer_t * adcTimer = NULL; // our timer void complexHandler(void *param) { while (true) { // Sleep until the ISR gives us something to do, or for 1 second uint32_t tcount = ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000)); if (check_for_work) { // Do something complex and CPU-intensive } } } void IRAM_ATTR onTimer() { // A mutex protects the handler from reentry (which shouldn't happen, but just in case) portENTER_CRITICAL_ISR(&timerMux); // Do something, e.g. read a pin. if (some_condition) { // Notify complexHandlerTask that the buffer is full. BaseType_t xHigherPriorityTaskWoken = pdFALSE; vTaskNotifyGiveFromISR(complexHandlerTask, &xHigherPriorityTaskWoken); if (xHigherPriorityTaskWoken) { portYIELD_FROM_ISR(); } } portEXIT_CRITICAL_ISR(&timerMux); } void setup() { xTaskCreate(complexHandler, 'Handler Task', 8192, NULL, 1, &complexHandlerTask); adcTimer = timerBegin(3, 80, true); // 80 MHz / 80 = 1 MHz hardware clock for easy figuring timerAttachInterrupt(adcTimer, &onTimer, true); // Attaches the handler function to the timer timerAlarmWrite(adcTimer, 45, true); // Interrupts when counter == 45, i.e. 22.222 times a second timerAlarmEnable(adcTimer); }
注:この記事全体でコードで使用されている関数は、 ESP-IDF API とで ESP32ArduinoコアGitHubプロジェクト 。
注意すべき非常に重要なことはIRAM_ATTR
ですonTimer()
の定義の節割り込みハンドラ。これは、CPUコアが命令を実行(およびデータにアクセス)できるのは組み込みRAMからのみであり、プログラムコードとデータが通常格納されているフラッシュストレージからは実行できないためです。これを回避するために、合計520 KiBのRAMの一部がIRAMとして使用されます。これは、フラッシュストレージからコードを透過的にロードするために使用される128KiBのキャッシュです。 ESP32は、コードとデータに別々のバスを使用します( 「ハーバードアーキテクチャ」 )したがって、これらは非常に個別に処理され、メモリプロパティにまで拡張されます。IRAMは特別であり、32ビットアドレス境界でのみアクセスできます。
実際、ESP32メモリは非常に不均一です。そのさまざまな領域がさまざまな目的に使用されます。最大連続領域のサイズは約160KiBであり、ユーザープログラムがアクセスできるすべての「通常の」メモリは合計で約316KiBにすぎません。
フラッシュストレージからのデータの読み込みは遅く、SPIバスアクセスが必要になる可能性があるため、速度に依存するコードはIRAMキャッシュに収まるように注意する必要があり、その一部がによって使用されるため、多くの場合、はるかに小さくなります(100 KiB未満)。オペレーティング・システム。特に、割り込みが発生したときに割り込みハンドラコードがキャッシュにロードされない場合、システムは例外を生成します。割り込みが発生したときにフラッシュストレージから何かをロードすることは、非常に遅く、ロジスティックの悪夢でもあります。 IRAM_ATTR
onTimer()
の指定子ハンドラーは、コンパイラーとリンカーに、このコードを特別なものとしてマークするように指示します。このコードはIRAMに静的に配置され、スワップアウトされることはありません。
ただし、IRAM_ATTR
指定された関数にのみ適用されます。その関数から呼び出された関数は影響を受けません。
割り込みからオーディオ信号をサンプリングする通常の方法では、サンプルのメモリバッファを維持し、サンプリングしたデータを入力して、データが利用可能であることをハンドラタスクに通知します。
ESP-IDFは adc1_get_raw()
最初のADCペリフェラルの特定のADCチャネルのデータを測定する機能(2番目のADCチャネルはWiFiによって使用されます)。ただし、タイマーハンドラーコードで使用すると、プログラムが不安定になります。これは、他のIDF関数(特にロックを処理する関数)を多数呼び出す複雑な関数であり、adc1_get_raw()
も呼び出さないためです。また、それが呼び出す関数はIRAM_ATTR
でマークされていません。割り込みハンドラーは、ADC関数がIRAMからスワップアウトされるのに十分な大きさのコードが実行されるとすぐにクラッシュします。これは、WiFi-TCP / IP-HTTPスタック、またはSPIFFSファイルシステムライブラリである可能性があります。または他の何か。
注:一部のIDF関数は、割り込みハンドラーから呼び出すことができるように特別に作成されています(IRAM_ATTR
でマークされています)。 vTaskNotifyGiveFromISR()
上記の例の関数はそのような関数の1つです。
これを回避するための最もIDFに適した方法は、ADCサンプルを取得する必要があるときに割り込みハンドラーがタスクに通知し、このタスクにサンプリングとバッファー管理を行わせ、場合によっては別のタスクをデータ分析に使用することです(または圧縮または送信、または場合によっては何でも)。残念ながら、これは非常に非効率的です。ハンドラー側(実行する作業があることをタスクに通知する)とタスク側(実行するタスクを取得する)の両方で、オペレーティングシステムとの対話と実行中の数千の命令が関係します。このアプローチは、理論的には正しいものの、CPUを大幅に停止させる可能性があるため、他のタスク用に予備のCPUパワーをほとんど残しません。
ADCからのデータのサンプリングは通常簡単な作業であるため、次の戦略はIDFがどのように行うかを確認し、提供されたAPIを呼び出さずにコードに直接複製することです。 adc1_get_raw()
関数はに実装されています rtc_module.c
IDFのファイルとそれが行う8つほどのことのうち、実際にADCをサンプリングしているのは1つだけです。これは、adc_convert()
の呼び出しによって実行されます。幸いなことに、adc_convert()
は、SENS
という名前のグローバル構造を介して周辺ハードウェアレジスタを操作することによってADCをサンプリングする単純な関数です。
オリンピックシンボルは、のゲシュタルト原理の例です。
このコードをプログラムで機能するように(そしてadc1_get_raw()
の動作を模倣するように)適応させるのは簡単です。次のようになります。
int IRAM_ATTR local_adc1_read(int channel) { uint16_t adc_value; SENS.sar_meas_start1.sar1_en_pad = (1 << channel); // only one channel is selected while (SENS.sar_slave_addr1.meas_status != 0); SENS.sar_meas_start1.meas1_start_sar = 0; SENS.sar_meas_start1.meas1_start_sar = 1; while (SENS.sar_meas_start1.meas1_done_sar == 0); adc_value = SENS.sar_meas_start1.meas1_data_sar; return adc_value; }
次のステップは、関連するヘッダーを含めて、SENS
を含めることです。変数が使用可能になります:
#include #include
最後に、adc1_get_raw()
以降ADCをサンプリングする前にいくつかの構成手順を実行します。ADCがセットアップされた直後に直接呼び出す必要があります。これにより、タイマーが開始される前に、関連する構成を実行できます。
このアプローチの欠点は、他のIDF機能とうまく連携しないことです。 ADC構成をリセットする他の周辺機器、ドライバー、またはランダムなコードが呼び出されるとすぐに、カスタム関数は正しく機能しなくなります。少なくともWiFi、PWM、I2C、およびSPIはADC構成に影響を与えません。何かがそれに影響を与える場合は、adc1_get_raw()
への呼び出しADCを再度適切に構成します。
local_adc_read()
で関数が配置されている場合、タイマーハンドラーコードは次のようになります。
#define ADC_SAMPLES_COUNT 1000 int16_t abuf[ADC_SAMPLES_COUNT]; int16_t abufPos = 0; void IRAM_ATTR onTimer() { portENTER_CRITICAL_ISR(&timerMux); abuf[abufPos++] = local_adc1_read(ADC1_CHANNEL_0); if (abufPos >= ADC_SAMPLES_COUNT) { abufPos = 0; // Notify adcTask that the buffer is full. BaseType_t xHigherPriorityTaskWoken = pdFALSE; vTaskNotifyGiveFromISR(adcTaskHandle, &xHigherPriorityTaskWoken); if (xHigherPriorityTaskWoken) { portYIELD_FROM_ISR(); } } portEXIT_CRITICAL_ISR(&timerMux); }
ここで、adcTaskHandle
complexHandler
の構造に従って、バッファを処理するために実装されるFreeRTOSタスクです。最初のコードスニペットの関数。オーディオバッファのローカルコピーを作成し、それを自由に処理できます。たとえば、バッファでFFTアルゴリズムを実行したり、バッファを圧縮してWiFi経由で送信したりできます。
逆説的ですが、Arduino関数はanalogRead()
でマークされているため、ESP-IDFAPIの代わりにArduinoAPIを使用すると(つまり、adc1_get_raw()
の代わりにIRAM_ATTR
)機能します。ただし、それらはより高いレベルの抽象化を提供するため、ESP-IDFのものよりもはるかに低速です。パフォーマンスについて言えば、カスタムADC読み取り機能はESP-IDF機能の約2倍の速度です。
ここで行ったこと、つまりオペレーティングシステムのAPIを再実装して、オペレーティングシステムを使用しなかった場合でも発生しない問題を回避することは、オペレーティングシステムを使用することの長所と短所をよく表しています。最初の場所。
小型のマイクロコントローラーは、場合によってはアセンブラーコードで直接プログラムされ、開発者は、プログラムの実行のあらゆる側面、すべての単一CPU命令、およびチップ上のすべての周辺機器のすべての状態を完全に制御できます。プログラムが大きくなり、使用するハードウェアが増えるにつれて、これは当然退屈になる可能性があります。多数の周辺機器、2つのCPUコア、および複雑で不均一なメモリレイアウトを備えた、ESP32などの複雑なマイクロコントローラは、最初からプログラミングするのが困難で面倒です。
すべてのオペレーティングシステムは、そのサービスを使用するコードにいくつかの制限と要件を課していますが、通常、その利点は価値があります。つまり、開発がより速く、より簡単になります。ただし、場合によっては回避でき、埋め込みスペースでは回避する必要があります。
関連: 完全に機能するArduinoウェザーステーションの作り方ESP32は、IoT製品の作成に使用されるWiFiとBluetoothを備えたマイクロコントローラーです。これは、デュアルコアCPUと、ハードウェア暗号化オフロード、520 KiB RAM、12ビットADCなどの多数の機能を備えた強力なデバイスです。機能セットによって開発がより効果的になる複雑な製品で使用されます。
Espressifは、最もよく知られている製品がESP8266およびESP32WiFi対応マイクロコントローラーである会社です。このような製品は、その大規模な機能セットの恩恵を受けることができるIoTデバイスで使用されます。
javascriptは現在の日時を取得します
タイマーはマイクロコントローラー周辺機器(内部モジュール)であり、内部または外部クロック信号とペアになっており、クロックティックごとに値をインクリメントまたはデクリメントします。タイマーは、特定の数がカウントされた後に割り込みを生成できます。これにより、コードが実行されます。
ESP32は、IoT製品の作成に使用されるWiFiおよびBluetooth対応のマイクロコントローラーです。これは、デュアルコアCPUと豊富な機能を備えた強力なデバイスです。 ESP32ファームウェアの作成は、通常、FreeRTOS上に実装されているESP-IDFというベンダー提供の開発フレームワークに依存しています。
システムオンチップは、単一のチップパッケージ上で、動作中のコンピューターを構築するために使用される多くの(またはすべての)コンポーネントを含むハードウェアを構築するためのアプローチです。正確な仕様はさまざまであり、システムメモリ、グラフィックプロセッサ、I / Oコントローラ、ネットワークコントローラ、フラッシュメモリなどが含まれる場合と含まれない場合があります。