mirror of
https://github.com/espressif/esp-idf.git
synced 2026-05-03 19:41:55 +02:00
Moved files into separate folders per 'en' and 'zh_CN' language version and linked 'zh_CN' files back to 'en' files if translation is not yet available
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
128 vTaskAllocateMPURegions xTask=%t pxRegions=%u
|
||||
33 vTaskDelete xTaskToDelete=%t
|
||||
34 vTaskDelay xTicksToDelay=%u
|
||||
35 vTaskDelayUntil
|
||||
129 uxTaskPriorityGet xTask=%t
|
||||
56 uxTaskPriorityGetFromISR xTask=%t
|
||||
130 eTaskGetState xTask=%t
|
||||
55 vTaskPrioritySet xTask=%t uxNewPriority=%u
|
||||
36 vTaskSuspend xTaskToSuspend=%t
|
||||
40 vTaskResume xTaskToResume=%t
|
||||
43 xTaskResumeFromISR xTaskToResume=%t
|
||||
131 vTaskStartScheduler
|
||||
132 vTaskEndScheduler
|
||||
133 vTaskSuspendAll
|
||||
134 xTaskResumeAll
|
||||
135 xTaskGetTickCount
|
||||
57 xTaskGetTickCountFromISR
|
||||
136 uxTaskGetNumberOfTasks
|
||||
137 pcTaskGetTaskName xTaskToQuery=%t
|
||||
138 uxTaskGetStackHighWaterMark xTask=%t
|
||||
139 vTaskSetApplicationTaskTag xTask=%t pxHookFunction=%u
|
||||
140 xTaskGetApplicationTaskTag xTask=%t
|
||||
141 vTaskSetThreadLocalStoragePointer xTaskToSet=%T xIndex=%u pvValue=%u
|
||||
142 pvTaskGetThreadLocalStoragePointer xTaskToQuery=%T xIndex=%u
|
||||
143 xTaskCallApplicationTaskHook xTask=%T pvParameter=%u
|
||||
144 xTaskGetIdleTaskHandle
|
||||
145 uxTaskGetSystemState pxTaskStatusArray=%u uxArraySize=%u pulTotalRunTime=%u
|
||||
146 vTaskList pcWriteBuffer=%u
|
||||
147 vTaskGetRunTimeStats pcWriteBuffer=%u
|
||||
44 xTaskGenericNotify xTaskToNotify=%t ulValue=%u eAction=%u pulPreviousNotificationValue=%u
|
||||
45 xTaskGenericNotifyFromISR xTaskToNotify=%t ulValue=%u eAction=%u pulPreviousNotificationValue=%u pxHigherPriorityTaskWoken=%u
|
||||
46 xTaskNotifyWait ulBitsToClearOnEntry=%u ulBitsToClearOnExit=%u pulNotificationValue=%u xTicksToWait=%u
|
||||
38 vTaskNotifyGiveFromISR xTaskToNotify=%t pxHigherPriorityTaskWoken=%u
|
||||
37 ulTaskNotifyTake xClearCountOnExit=%u xTicksToWait=%u
|
||||
148 xTaskNotifyStateClear xTask=%t
|
||||
149 xTaskGetCurrentTaskHandle
|
||||
150 vTaskSetTimeOutState pxTimeOut=%u
|
||||
151 xTaskCheckForTimeOut pxTimeOut=%u pxTicksToWait=%u
|
||||
152 vTaskMissedYield
|
||||
153 xTaskGetSchedulerState
|
||||
39 vTaskPriorityInherit pxMutexHolder=%p
|
||||
42 xTaskPriorityDisinherit pxMutexHolder=%p
|
||||
154 xTaskGenericCreate pxTaskCode=%u pcName=%u usStackDepth=%u pvParameters=%u uxPriority=%u pxCreatedTask=%u puxStackBuffer=%u xRegions=%u
|
||||
155 uxTaskGetTaskNumber xTask=%u
|
||||
156 vTaskSetTaskNumber xTask=%u uxHandle=%u
|
||||
41 vTaskStepTick xTicksToJump=%u
|
||||
157 eTaskConfirmSleepModeStatus
|
||||
158 xTimerCreate pcTimerName=%u xTimerPeriodInTicks=%u uxAutoReload=%u pvTimerID=%u pxCallbackFunction=%u
|
||||
159 pvTimerGetTimerID xTimer=%u
|
||||
160 vTimerSetTimerID xTimer=%u pvNewID=%u
|
||||
161 xTimerIsTimerActive xTimer=%u
|
||||
162 xTimerGetTimerDaemonTaskHandle
|
||||
163 xTimerPendFunctionCallFromISR xFunctionToPend=%u pvParameter1=%u ulParameter2=%u pxHigherPriorityTaskWoken=%u
|
||||
164 xTimerPendFunctionCall xFunctionToPend=%u pvParameter1=%u ulParameter2=%u xTicksToWait=%u
|
||||
165 pcTimerGetTimerName xTimer=%u
|
||||
166 xTimerCreateTimerTask
|
||||
167 xTimerGenericCommand xTimer=%u xCommandID=%u xOptionalValue=%u pxHigherPriorityTaskWoken=%u xTicksToWait=%u
|
||||
53 xQueueGenericSend xQueue=%I pvItemToQueue=%p xTicksToWait=%u xCopyPosition=%u
|
||||
50 xQueuePeekFromISR xQueue=%I pvBuffer=%p
|
||||
49 xQueueGenericReceive xQueue=%I pvBuffer=%p xTicksToWait=%u xJustPeek=%u
|
||||
168 uxQueueMessagesWaiting xQueue=%I
|
||||
169 uxQueueSpacesAvailable xQueue=%I
|
||||
48 vQueueDelete xQueue=%I
|
||||
54 xQueueGenericSendFromISR xQueue=%I pvItemToQueue=%p pxHigherPriorityTaskWoken=%u xCopyPosition=%u
|
||||
61 xQueueGiveFromISR xQueue=%I pxHigherPriorityTaskWoken=%u
|
||||
51 xQueueReceiveFromISR xQueue=%I pvBuffer=%p pxHigherPriorityTaskWoken=%u
|
||||
62 xQueueIsQueueEmptyFromISR xQueue=%I
|
||||
63 xQueueIsQueueFullFromISR xQueue=%I
|
||||
170 uxQueueMessagesWaitingFromISR xQueue=%I
|
||||
171 xQueueAltGenericSend xQueue=%I pvItemToQueue=%p xTicksToWait=%u xCopyPosition=%u
|
||||
172 xQueueAltGenericReceive xQueue=%I pvBuffer=%p xTicksToWait=%u xJustPeeking=%u
|
||||
173 xQueueCRSendFromISR xQueue=%I pvItemToQueue=%p xCoRoutinePreviouslyWoken=%u
|
||||
174 xQueueCRReceiveFromISR xQueue=%I pvBuffer=%p pxTaskWoken=%u
|
||||
175 xQueueCRSend xQueue=%I pvItemToQueue=%p xTicksToWait=%u
|
||||
176 xQueueCRReceive xQueue=%I pvBuffer=%p xTicksToWait=%u
|
||||
177 xQueueCreateMutex ucQueueType=%u
|
||||
178 xQueueCreateCountingSemaphore uxMaxCount=%u uxInitialCount=%u
|
||||
179 xQueueGetMutexHolder xSemaphore=%u
|
||||
180 xQueueTakeMutexRecursive xMutex=%u xTicksToWait=%u
|
||||
181 xQueueGiveMutexRecursive pxMutex=%u
|
||||
52 vQueueAddToRegistry xQueue=%I pcName=%u
|
||||
182 vQueueUnregisterQueue xQueue=%I
|
||||
47 xQueueGenericCreate uxQueueLength=%u uxItemSize=%u ucQueueType=%u
|
||||
183 xQueueCreateSet uxEventQueueLength=%u
|
||||
184 xQueueAddToSet xQueueOrSemaphore=%u xQueueSet=%u
|
||||
185 xQueueRemoveFromSet xQueueOrSemaphore=%u xQueueSet=%u
|
||||
186 xQueueSelectFromSet xQueueSet=%u xTicksToWait=%u
|
||||
187 xQueueSelectFromSetFromISR xQueueSet=%u
|
||||
188 xQueueGenericReset xQueue=%I xNewQueue=%u
|
||||
189 vListInitialise pxList=%u
|
||||
190 vListInitialiseItem pxItem=%u
|
||||
191 vListInsert pxList=%u pxNewListItem=%u
|
||||
192 vListInsertEnd pxList=%u pxNewListItem=%u
|
||||
193 uxListRemove pxItemToRemove=%u
|
||||
194 xEventGroupCreate
|
||||
195 xEventGroupWaitBits xEventGroup=%u uxBitsToWaitFor=%u xClearOnExit=%u xWaitForAllBits=%u xTicksToWait=%u
|
||||
196 xEventGroupClearBits xEventGroup=%u uxBitsToClear=%u
|
||||
58 xEventGroupClearBitsFromISR xEventGroup=%u uxBitsToSet=%u
|
||||
197 xEventGroupSetBits xEventGroup=%u uxBitsToSet=%u
|
||||
59 xEventGroupSetBitsFromISR xEventGroup=%u uxBitsToSet=%u pxHigherPriorityTaskWoken=%u
|
||||
198 xEventGroupSync xEventGroup=%u uxBitsToSet=%u uxBitsToWaitFor=%u xTicksToWait=%u
|
||||
60 xEventGroupGetBitsFromISR xEventGroup=%u
|
||||
199 vEventGroupDelete xEventGroup=%u
|
||||
200 uxEventGroupGetNumber xEventGroup=%u
|
||||
@@ -0,0 +1,436 @@
|
||||
Application Level Tracing library
|
||||
=================================
|
||||
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
IDF provides useful feature for program behaviour analysis: application level tracing. It is implemented in the corresponding library and can be enabled in menuconfig. This feature allows to transfer arbitrary data between host and ESP32 via JTAG interface with small overhead on program execution.
|
||||
|
||||
Developers can use this library to send application specific state of execution to the host and receive commands or other type of info in the opposite direction at runtime. The main use cases of this library are:
|
||||
|
||||
1. Collecting application specific data, see :ref:`app_trace-application-specific-tracing`
|
||||
2. Lightweight logging to the host, see :ref:`app_trace-logging-to-host`
|
||||
3. System behaviour analysis, see :ref:`app_trace-system-behaviour-analysis-with-segger-systemview`
|
||||
|
||||
Tracing components when working over JTAG interface are shown in the figure below.
|
||||
|
||||
.. figure:: ../_static/app_trace-overview.jpg
|
||||
:align: center
|
||||
:alt: Tracing Components when Working Over JTAG
|
||||
:figclass: align-center
|
||||
|
||||
Tracing Components when Working Over JTAG
|
||||
|
||||
|
||||
Modes of Operation
|
||||
------------------
|
||||
|
||||
The library supports two modes of operation:
|
||||
|
||||
**Post-mortem mode**. This is the default mode. The mode does not need interaction from the host side. In this mode tracing module does not check whether host has read all the data from *HW UP BUFFER* buffer and overwrites old data with the new ones. This mode is useful when only the latest trace data are interesting to the user, e.g. for analyzing program's behaviour just before the crash. Host can read the data later on upon user request, e.g. via special OpenOCD command in case of working via JTAG interface.
|
||||
|
||||
**Streaming mode.** Tracing module enters this mode when host connects to ESP32. In this mode before writing new data to *HW UP BUFFER* tracing module checks that there is enough space in it and if necessary waits for the host to read data and free enough memory. Maximum waiting time is controled via timeout values passed by users to corresponding API routines. So when application tries to write data to trace buffer using finite value of the maximum waiting time it is possible situation that this data will be dropped. Especially this is true for tracing from time critical code (ISRs, OS scheduler code etc.) when infinite timeouts can lead to system malfunction. In order to avoid loss of such critical data developers can enable additional data buffering via menuconfig option :ref:`CONFIG_ESP32_APPTRACE_PENDING_DATA_SIZE_MAX`. This macro specifies the size of data which can be buffered in above conditions. The option can also help to overcome situation when data transfer to the host is temporarily slowed down, e.g due to USB bus congestions etc. But it will not help when average bitrate of trace data stream exceeds HW interface capabilities.
|
||||
|
||||
|
||||
Configuration Options and Dependencies
|
||||
--------------------------------------
|
||||
|
||||
Using of this feature depends on two components:
|
||||
|
||||
1. **Host side:** Application tracing is done over JTAG, so it needs OpenOCD to be set up and running on host machine. For instructions how to set it up, please, see :doc:`JTAG Debugging <../api-guides/jtag-debugging/index>` for details.
|
||||
2. **Target side:** Application tracing functionality can be enabled in menuconfig. *Component config > Application Level Tracing* menu allows selecting destination for the trace data (HW interface for transport). Choosing any of the destinations automatically enables ``CONFIG_ESP32_APPTRACE_ENABLE`` option.
|
||||
|
||||
.. note::
|
||||
|
||||
In order to achieve higher data rates and minimize number of dropped packets it is recommended to optimize setting of JTAG clock frequency, so it is at maximum and still provides stable operation of JTAG, see :ref:`jtag-debugging-tip-optimize-jtag-speed`.
|
||||
|
||||
There are two additional menuconfig options not mentioned above:
|
||||
|
||||
1. *Threshold for flushing last trace data to host on panic* (:ref:`CONFIG_ESP32_APPTRACE_POSTMORTEM_FLUSH_TRAX_THRESH`). This option is necessary due to the nature of working over JTAG. In that mode trace data are exposed to the host in 16KB blocks. In post-mortem mode when one block is filled it is exposed to the host and the previous one becomes unavailable. In other words trace data are overwritten in 16KB granularity. On panic the latest data from the current input block are exposed to host and host can read them for post-analysis. It can happen that system panic occurs when there are very small amount of data which are not exposed to the host yet. In this case the previous 16KB of collected data will be lost and host will see the latest, but very small piece of the trace. It can be insufficient to diagnose the problem. This menuconfig option allows avoiding such situations. It controls the threshold for flushing data in case of panic. For example user can decide that it needs not less then 512 bytes of the recent trace data, so if there is less then 512 bytes of pending data at the moment of panic they will not be flushed and will not overwrite previous 16KB. The option is only meaningful in post-mortem mode and when working over JTAG.
|
||||
2. *Timeout for flushing last trace data to host on panic* (:ref:`CONFIG_ESP32_APPTRACE_ONPANIC_HOST_FLUSH_TMO`). The option is only meaningful in streaming mode and controls the maximum time tracing module will wait for the host to read the last data in case of panic.
|
||||
|
||||
|
||||
How to use this library
|
||||
-----------------------
|
||||
|
||||
This library provides API for transferring arbitrary data between host and ESP32. When enabled in menuconfig target application tracing module is initialized automatically at the system startup, so all what the user needs to do is to call corresponding API to send, receive or flush the data.
|
||||
|
||||
.. _app_trace-application-specific-tracing:
|
||||
|
||||
Application Specific Tracing
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In general user should decide what type of data should be transferred in every direction and how these data must be interpreted (processed). The following steps must be performed to transfer data between target and host:
|
||||
|
||||
1. On target side user should implement algorithms for writing trace data to the host. Piece of code below shows an example how to do this.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
#include "esp_app_trace.h"
|
||||
...
|
||||
char buf[] = "Hello World!";
|
||||
esp_err_t res = esp_apptrace_write(ESP_APPTRACE_DEST_TRAX, buf, strlen(buf), ESP_APPTRACE_TMO_INFINITE);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to write data to host!");
|
||||
return res;
|
||||
}
|
||||
|
||||
``esp_apptrace_write()`` function uses memcpy to copy user data to the internal buffer. In some cases it can be more optimal to use ``esp_apptrace_buffer_get()`` and ``esp_apptrace_buffer_put()`` functions. They allow developers to allocate buffer and fill it themselves. The following piece of code shows how to do this.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
#include "esp_app_trace.h"
|
||||
...
|
||||
int number = 10;
|
||||
char *ptr = (char *)esp_apptrace_buffer_get(ESP_APPTRACE_DEST_TRAX, 32, 100/*tmo in us*/);
|
||||
if (ptr == NULL) {
|
||||
ESP_LOGE("Failed to get buffer!");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
sprintf(ptr, "Here is the number %d", number);
|
||||
esp_err_t res = esp_apptrace_buffer_put(ESP_APPTRACE_DEST_TRAX, ptr, 100/*tmo in us*/);
|
||||
if (res != ESP_OK) {
|
||||
/* in case of error host tracing tool (e.g. OpenOCD) will report incomplete user buffer */
|
||||
ESP_LOGE("Failed to put buffer!");
|
||||
return res;
|
||||
}
|
||||
|
||||
Also according to his needs user may want to receive data from the host. Piece of code below shows an example how to do this.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
#include "esp_app_trace.h"
|
||||
...
|
||||
char buf[32];
|
||||
char down_buf[32];
|
||||
size_t sz = sizeof(buf);
|
||||
|
||||
/* config down buffer */
|
||||
esp_apptrace_down_buffer_config(down_buf, sizeof(down_buf));
|
||||
/* check for incoming data and read them if any */
|
||||
esp_err_t res = esp_apptrace_read(ESP_APPTRACE_DEST_TRAX, buf, &sz, 0/*do not wait*/);
|
||||
if (res != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to read data from host!");
|
||||
return res;
|
||||
}
|
||||
if (sz > 0) {
|
||||
/* we have data, process them */
|
||||
...
|
||||
}
|
||||
|
||||
``esp_apptrace_read()`` function uses memcpy to copy host data to user buffer. In some cases it can be more optimal to use ``esp_apptrace_down_buffer_get()`` and ``esp_apptrace_down_buffer_put()`` functions. They allow developers to occupy chunk of read buffer and process it in-place. The following piece of code shows how to do this.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
#include "esp_app_trace.h"
|
||||
...
|
||||
char down_buf[32];
|
||||
uint32_t *number;
|
||||
size_t sz = 32;
|
||||
|
||||
/* config down buffer */
|
||||
esp_apptrace_down_buffer_config(down_buf, sizeof(down_buf));
|
||||
char *ptr = (char *)esp_apptrace_down_buffer_get(ESP_APPTRACE_DEST_TRAX, &sz, 100/*tmo in us*/);
|
||||
if (ptr == NULL) {
|
||||
ESP_LOGE("Failed to get buffer!");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
if (sz > 4) {
|
||||
number = (uint32_t *)ptr;
|
||||
printf("Here is the number %d", *number);
|
||||
} else {
|
||||
printf("No data");
|
||||
}
|
||||
esp_err_t res = esp_apptrace_down_buffer_put(ESP_APPTRACE_DEST_TRAX, ptr, 100/*tmo in us*/);
|
||||
if (res != ESP_OK) {
|
||||
/* in case of error host tracing tool (e.g. OpenOCD) will report incomplete user buffer */
|
||||
ESP_LOGE("Failed to put buffer!");
|
||||
return res;
|
||||
}
|
||||
|
||||
2. The next step is to build the program image and download it to the target as described in :doc:`Build and Flash <../get-started/make-project>`.
|
||||
3. Run OpenOCD (see :doc:`JTAG Debugging <../api-guides/jtag-debugging/index>`).
|
||||
4. Connect to OpenOCD telnet server. It can be done using the following command in terminal ``telnet <oocd_host> 4444``. If telnet session is opened on the same machine which runs OpenOCD you can use ``localhost`` as ``<oocd_host>`` in the command above.
|
||||
5. Start trace data collection using special OpenOCD command. This command will transfer tracing data and redirect them to specified file or socket (currently only files are supported as trace data destination). For description of the corresponding commands see `OpenOCD Application Level Tracing Commands`_.
|
||||
6. The final step is to process received data. Since format of data is defined by user the processing stage is out of the scope of this document. Good starting points for data processor are python scripts in ``$IDF_PATH/tools/esp_app_trace``: ``apptrace_proc.py`` (used for feature tests) and ``logtrace_proc.py`` (see more details in section `Logging to Host`_).
|
||||
|
||||
|
||||
OpenOCD Application Level Tracing Commands
|
||||
""""""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
*HW UP BUFFER* is shared between user data blocks and filling of the allocated memory is performed on behalf of the API caller (in task or ISR context). In multithreading environment it can happen that task/ISR which fills the buffer is preempted by another high priority task/ISR. So it is possible situation that user data preparation process is not completed at the moment when that chunk is read by the host. To handle such conditions tracing module prepends all user data chunks with header which contains allocated user buffer size (2 bytes) and length of actually written data (2 bytes). So total length of the header is 4 bytes. OpenOCD command which reads trace data reports error when it reads incomplete user data chunk, but in any case it puts contents of the whole user chunk (including unfilled area) to output file.
|
||||
|
||||
Below is the description of available OpenOCD application tracing commands.
|
||||
|
||||
.. note::
|
||||
|
||||
Currently OpenOCD does not provide commands to send arbitrary user data to the target.
|
||||
|
||||
|
||||
Command usage:
|
||||
|
||||
``esp32 apptrace [start <options>] | [stop] | [status] | [dump <cores_num> <outfile>]``
|
||||
|
||||
Sub-commands:
|
||||
|
||||
``start``
|
||||
Start tracing (continuous streaming).
|
||||
``stop``
|
||||
Stop tracing.
|
||||
``status``
|
||||
Get tracing status.
|
||||
``dump``
|
||||
Dump all data from (post-mortem dump).
|
||||
|
||||
|
||||
Start command syntax:
|
||||
|
||||
``start <outfile> [poll_period [trace_size [stop_tmo [wait4halt [skip_size]]]]``
|
||||
|
||||
``outfile``
|
||||
Path to file to save data from both CPUs. This argument should have the following format: ``file://path/to/file``.
|
||||
``poll_period``
|
||||
Data polling period (in ms) for available trace data. If greater then 0 then command runs in non-blocking mode. By default 1 ms.
|
||||
``trace_size``
|
||||
Maximum size of data to collect (in bytes). Tracing is stopped after specified amount of data is received. By default -1 (trace size stop trigger is disabled).
|
||||
``stop_tmo``
|
||||
Idle timeout (in sec). Tracing is stopped if there is no data for specified period of time. By default -1 (disable this stop trigger). Optionally set it to value longer than longest pause between tracing commands from target.
|
||||
``wait4halt``
|
||||
If 0 start tracing immediately, otherwise command waits for the target to be halted (after reset, by breakpoint etc.) and then automatically resumes it and starts tracing. By default 0.
|
||||
``skip_size``
|
||||
Number of bytes to skip at the start. By default 0.
|
||||
|
||||
.. note::
|
||||
|
||||
If ``poll_period`` is 0, OpenOCD telnet command line will not be available until tracing is stopped. You must stop it manually by resetting the board or pressing Ctrl+C in OpenOCD window (not one with the telnet session). Another option is to set ``trace_size`` and wait until this size of data is collected. At this point tracing stops automatically.
|
||||
|
||||
Command usage examples:
|
||||
|
||||
.. highlight:: none
|
||||
|
||||
1. Collect 2048 bytes of tracing data to a file "trace.log". The file will be saved in "openocd-esp32" directory.
|
||||
|
||||
::
|
||||
|
||||
esp32 apptrace start file://trace.log 1 2048 5 0 0
|
||||
|
||||
The tracing data will be retrieved and saved in non-blocking mode. This process will stop automatically after 2048 bytes are collected, or if no data are available for more than 5 seconds.
|
||||
|
||||
.. note::
|
||||
|
||||
Tracing data is buffered before it is made available to OpenOCD. If you see "Data timeout!" message, then the target is likely sending not enough data to empty the buffer to OpenOCD before expiration of timeout. Either increase the timeout or use a function ``esp_apptrace_flush()`` to flush the data on specific intervals.
|
||||
|
||||
2. Retrieve tracing data indefinitely in non-blocking mode.
|
||||
|
||||
::
|
||||
|
||||
esp32 apptrace start file://trace.log 1 -1 -1 0 0
|
||||
|
||||
There is no limitation on the size of collected data and there is no any data timeout set. This process may be stopped by issuing ``esp32 apptrace stop`` command on OpenOCD telnet prompt, or by pressing Ctrl+C in OpenOCD window.
|
||||
|
||||
3. Retrieve tracing data and save them indefinitely.
|
||||
|
||||
::
|
||||
|
||||
esp32 apptrace start file://trace.log 0 -1 -1 0 0
|
||||
|
||||
OpenOCD telnet command line prompt will not be available until tracing is stopped. To stop tracing press Ctrl+C in OpenOCD window.
|
||||
|
||||
4. Wait for target to be halted. Then resume target's operation and start data retrieval. Stop after collecting 2048 bytes of data:
|
||||
|
||||
::
|
||||
|
||||
esp32 apptrace start file://trace.log 0 2048 -1 1 0
|
||||
|
||||
To configure tracing immediately after reset use the openocd ``reset halt`` command.
|
||||
|
||||
|
||||
.. _app_trace-logging-to-host:
|
||||
|
||||
Logging to Host
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
IDF implements useful feature: logging to host via application level tracing library. This is a kind of semihosting when all ESP_LOGx calls sends strings to be printed to the host instead of UART. This can be useful because "printing to host" eliminates some steps performed when logging to UART. The most part of work is done on the host.
|
||||
|
||||
By default IDF's logging library uses vprintf-like function to write formatted output to dedicated UART. In general it involves the following steps:
|
||||
|
||||
1. Format string is parsed to obtain type of each argument.
|
||||
2. According to its type every argument is converted to string representation.
|
||||
3. Format string combined with converted arguments is sent to UART.
|
||||
|
||||
Though implementation of vprintf-like function can be optimised to a certain level, all steps above have to be performed in any case and every step takes some time (especially item 3). So it is frequent situation when addition of extra logging to the program to diagnose some problem changes its behaviour and problem disappears or in the worst cases program can not work normally at all and ends up with an error or even hangs.
|
||||
|
||||
Possible ways to overcome this problem are to use higher UART bitrates (or another faster interface) and/or move string formatting procedure to the host.
|
||||
|
||||
Application level tracing feature can be used to transfer log information to host using ``esp_apptrace_vprintf`` function. This function does not perform full parsing of the format string and arguments, instead it just calculates number of arguments passed and sends them along with the format string address to the host. On the host log data are processed and printed out by a special Python script.
|
||||
|
||||
|
||||
Limitations
|
||||
"""""""""""
|
||||
|
||||
Current implementation of logging over JTAG has some limitations:
|
||||
|
||||
1. Tracing from ``ESP_EARLY_LOGx`` macros is not supported.
|
||||
2. No support for printf arguments which size exceeds 4 bytes (e.g. ``double`` and ``uint64_t``).
|
||||
3. Only strings from .rodata section are supported as format strings and arguments.
|
||||
4. Maximum number of printf arguments is 256.
|
||||
|
||||
|
||||
How To Use It
|
||||
"""""""""""""
|
||||
|
||||
In order to use logging via trace module user needs to perform the following steps:
|
||||
|
||||
1. On target side special vprintf-like function needs to be installed. As it was mentioned earlier this function is ``esp_apptrace_vprintf``. It sends log data to the host. Example code is provided in :example:`system/app_trace_to_host`.
|
||||
2. Follow instructions in items 2-5 in `Application Specific Tracing`_.
|
||||
3. To print out collected log records, run the following command in terminal: ``$IDF_PATH/tools/esp_app_trace/logtrace_proc.py /path/to/trace/file /path/to/program/elf/file``.
|
||||
|
||||
|
||||
Log Trace Processor Command Options
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Command usage:
|
||||
|
||||
``logtrace_proc.py [-h] [--no-errors] <trace_file> <elf_file>``
|
||||
|
||||
Positional arguments:
|
||||
|
||||
``trace_file``
|
||||
Path to log trace file
|
||||
``elf_file``
|
||||
Path to program ELF file
|
||||
|
||||
Optional arguments:
|
||||
|
||||
``-h``, ``--help``
|
||||
show this help message and exit
|
||||
``--no-errors``, ``-n``
|
||||
Do not print errors
|
||||
|
||||
.. _app_trace-system-behaviour-analysis-with-segger-systemview:
|
||||
|
||||
System Behaviour Analysis with SEGGER SystemView
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Another useful IDF feature built on top of application tracing library is the system level tracing which produces traces compatible with SEGGER SystemView tool (see `SystemView <https://www.segger.com/products/development-tools/systemview/>`_). SEGGER SystemView is a real-time recording and visualization tool that allows to analyze runtime behavior of an application.
|
||||
|
||||
.. note::
|
||||
|
||||
Currently IDF-based application is able to generate SystemView compatible traces in form of files to be opened in SystemView application. The tracing process can not yet be controlled using that tool.
|
||||
|
||||
|
||||
How To Use It
|
||||
"""""""""""""
|
||||
|
||||
Support for this feature is enabled by *Component config > Application Level Tracing > FreeRTOS SystemView Tracing* (:ref:`CONFIG_SYSVIEW_ENABLE`) menuconfig option. There are several other options enabled under the same menu:
|
||||
|
||||
1. *ESP32 timer to use as SystemView timestamp source* (:ref:`CONFIG_SYSVIEW_TS_SOURCE`) selects the source of timestamps for SystemView events. In single core mode timestamps are generated using ESP32 internal cycle counter running at maximum 240 Mhz (~4 ns granularity). In dual-core mode external timer working at 40Mhz is used, so timestamp granularity is 25 ns.
|
||||
2. Individually enabled or disabled collection of SystemView events (``CONFIG_SYSVIEW_EVT_XXX``):
|
||||
|
||||
- Trace Buffer Overflow Event
|
||||
- ISR Enter Event
|
||||
- ISR Exit Event
|
||||
- ISR Exit to Scheduler Event
|
||||
- Task Start Execution Event
|
||||
- Task Stop Execution Event
|
||||
- Task Start Ready State Event
|
||||
- Task Stop Ready State Event
|
||||
- Task Create Event
|
||||
- Task Terminate Event
|
||||
- System Idle Event
|
||||
- Timer Enter Event
|
||||
- Timer Exit Event
|
||||
|
||||
IDF has all the code required to produce SystemView compatible traces, so user can just configure necessary project options (see above), build, download the image to target and use OpenOCD to collect data as described in the previous sections.
|
||||
|
||||
|
||||
OpenOCD SystemView Tracing Command Options
|
||||
""""""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
Command usage:
|
||||
|
||||
``esp32 sysview [start <options>] | [stop] | [status]``
|
||||
|
||||
Sub-commands:
|
||||
|
||||
``start``
|
||||
Start tracing (continuous streaming).
|
||||
``stop``
|
||||
Stop tracing.
|
||||
``status``
|
||||
Get tracing status.
|
||||
|
||||
Start command syntax:
|
||||
|
||||
``start <outfile1> [outfile2] [poll_period [trace_size [stop_tmo]]]``
|
||||
|
||||
``outfile1``
|
||||
Path to file to save data from PRO CPU. This argument should have the following format: ``file://path/to/file``.
|
||||
``outfile2``
|
||||
Path to file to save data from APP CPU. This argument should have the following format: ``file://path/to/file``.
|
||||
``poll_period``
|
||||
Data polling period (in ms) for available trace data. If greater then 0 then command runs in non-blocking mode. By default 1 ms.
|
||||
``trace_size``
|
||||
Maximum size of data to collect (in bytes). Tracing is stopped after specified amount of data is received. By default -1 (trace size stop trigger is disabled).
|
||||
``stop_tmo``
|
||||
Idle timeout (in sec). Tracing is stopped if there is no data for specified period of time. By default -1 (disable this stop trigger).
|
||||
|
||||
.. note::
|
||||
|
||||
If ``poll_period`` is 0 OpenOCD telnet command line will not be available until tracing is stopped. You must stop it manually by resetting the board or pressing Ctrl+C in OpenOCD window (not one with the telnet session). Another option is to set ``trace_size`` and wait until this size of data is collected. At this point tracing stops automatically.
|
||||
|
||||
Command usage examples:
|
||||
|
||||
.. highlight:: none
|
||||
|
||||
1. Collect SystemView tracing data to files "pro-cpu.SVDat" and "pro-cpu.SVDat". The files will be saved in "openocd-esp32" directory.
|
||||
|
||||
::
|
||||
|
||||
esp32 sysview start file://pro-cpu.SVDat file://app-cpu.SVDat
|
||||
|
||||
The tracing data will be retrieved and saved in non-blocking mode. To stop data this process enter ``esp32 apptrace stop`` command on OpenOCD telnet prompt, Optionally pressing Ctrl+C in OpenOCD window.
|
||||
|
||||
2. Retrieve tracing data and save them indefinitely.
|
||||
|
||||
::
|
||||
|
||||
esp32 sysview start file://pro-cpu.SVDat file://app-cpu.SVDat 0 -1 -1
|
||||
|
||||
OpenOCD telnet command line prompt will not be available until tracing is stopped. To stop tracing, press Ctrl+C in OpenOCD window.
|
||||
|
||||
|
||||
Data Visualization
|
||||
""""""""""""""""""
|
||||
|
||||
After trace data are collected user can use special tool to visualize the results and inspect behaviour of the program. Unfortunately SystemView does not support tracing from multiple cores. So when tracing from ESP32 working in dual-core mode two files are generated: one for PRO CPU and another one for APP CPU. User can load every file into separate instance of the tool.
|
||||
|
||||
It is uneasy and awkward to analyze data for every core in separate instance of the tool. Fortunately there is Eclipse plugin called *Impulse* which can load several trace files and makes its possible to inspect events from both cores in one view. Also this plugin has no limitation of 1000000 events as compared to free version of SystemView.
|
||||
|
||||
Good instruction on how to install, configure and visualize data in Impulse from one core can be found `here <https://mcuoneclipse.com/2016/07/31/impulse-segger-systemview-in-eclipse/>`_.
|
||||
|
||||
.. note::
|
||||
|
||||
IDF uses its own mapping for SystemView FreeRTOS events IDs, so user needs to replace original file with mapping ``$SYSVIEW_INSTALL_DIR/Description/SYSVIEW_FreeRTOS.txt`` with ``$IDF_PATH/docs/api-guides/SYSVIEW_FreeRTOS.txt``.
|
||||
Also contents of that IDF specific file should be used when configuring SystemView serializer using above link.
|
||||
|
||||
|
||||
Configure Impulse for Dual Core Traces
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
After installing Impulse and ensuring that it can successfully load trace files for each core in separate tabs user can add special Multi Adapter port and load both files into one view. To do this user needs to do the following in Eclipse:
|
||||
|
||||
1. Open 'Signal Ports' view. Go to Windows->Show View->Other menu. Find 'Signal Ports' view in Impulse folder and double-click on it.
|
||||
2. In 'Signal Ports' view right-click on 'Ports' and select 'Add ...'->New Multi Adapter Port
|
||||
3. In open dialog Press 'Add' button and select 'New Pipe/File'.
|
||||
4. In open dialog select 'SystemView Serializer' as Serializer and set path to PRO CPU trace file. Press OK.
|
||||
5. Repeat steps 3-4 for APP CPU trace file.
|
||||
6. Double-click on created port. View for this port should open.
|
||||
7. Click Start/Stop Streaming button. Data should be loaded.
|
||||
8. Use 'Zoom Out', 'Zoom In' and 'Zoom Fit' button to inspect data.
|
||||
9. For settings measurement cursors and other features please see `Impulse documentation <http://toem.de/index.php/projects/impulse>`_).
|
||||
|
||||
.. note::
|
||||
|
||||
If you have problems with visualization (no data are shown or strange behaviour of zoom action is observed) you can try to delete current signal hierarchy and double click on necessary file or port. Eclipse will ask you to create new signal hierarchy.
|
||||
@@ -0,0 +1,620 @@
|
||||
Build System
|
||||
************
|
||||
|
||||
This document explains the Espressif IoT Development Framework build system and the
|
||||
concept of "components"
|
||||
|
||||
Read this document if you want to know how to organise a new ESP-IDF project.
|
||||
|
||||
We recommend using the esp-idf-template_ project as a starting point for your project.
|
||||
|
||||
Using the Build System
|
||||
======================
|
||||
|
||||
The esp-idf README file contains a description of how to use the build system to build your project.
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
An ESP-IDF project can be seen as an amalgamation of a number of components.
|
||||
For example, for a webserver that shows the current humidity, there could be:
|
||||
|
||||
- The ESP32 base libraries (libc, rom bindings etc)
|
||||
- The WiFi drivers
|
||||
- A TCP/IP stack
|
||||
- The FreeRTOS operating system
|
||||
- A webserver
|
||||
- A driver for the humidity sensor
|
||||
- Main code tying it all together
|
||||
|
||||
ESP-IDF makes these components explicit and configurable. To do that,
|
||||
when a project is compiled, the build environment will look up all the
|
||||
components in the ESP-IDF directories, the project directories and
|
||||
(optionally) in additional custom component directories. It then
|
||||
allows the user to configure the ESP-IDF project using a a text-based
|
||||
menu system to customize each component. After the components in the
|
||||
project are configured, the build process will compile the project.
|
||||
|
||||
Concepts
|
||||
--------
|
||||
|
||||
- A "project" is a directory that contains all the files and configuration to build a single "app" (executable), as well as additional supporting output such as a partition table, data/filesystem partitions, and a bootloader.
|
||||
|
||||
- "Project configuration" is held in a single file called sdkconfig in the root directory of the project. This configuration file is modified via ``make menuconfig`` to customise the configuration of the project. A single project contains exactly one project configuration.
|
||||
|
||||
- An "app" is an executable which is built by esp-idf. A single project will usually build two apps - a "project app" (the main executable, ie your custom firmware) and a "bootloader app" (the initial bootloader program which launches the project app).
|
||||
|
||||
- "components" are modular pieces of standalone code which are compiled into static libraries (.a files) and linked into an app. Some are provided by esp-idf itself, others may be sourced from other places.
|
||||
|
||||
Some things are not part of the project:
|
||||
|
||||
- "ESP-IDF" is not part of the project. Instead it is standalone, and linked to the project via the ``IDF_PATH`` environment variable which holds the path of the ``esp-idf`` directory. This allows the IDF framework to be decoupled from your project.
|
||||
|
||||
- The toolchain for compilation is not part of the project. The toolchain should be installed in the system command line PATH, or the path to the toolchain can be set as part of the compiler prefix in the project configuration.
|
||||
|
||||
|
||||
Example Project
|
||||
---------------
|
||||
|
||||
An example project directory tree might look like this::
|
||||
|
||||
- myProject/
|
||||
- Makefile
|
||||
- sdkconfig
|
||||
- components/ - component1/ - component.mk
|
||||
- Kconfig
|
||||
- src1.c
|
||||
- component2/ - component.mk
|
||||
- Kconfig
|
||||
- src1.c
|
||||
- include/ - component2.h
|
||||
- main/ - src1.c
|
||||
- src2.c
|
||||
- component.mk
|
||||
|
||||
- build/
|
||||
|
||||
This example "myProject" contains the following elements:
|
||||
|
||||
- A top-level project Makefile. This Makefile set the ``PROJECT_NAME`` variable and (optionally) defines
|
||||
other project-wide make variables. It includes the core ``$(IDF_PATH)/make/project.mk`` makefile which
|
||||
implements the rest of the ESP-IDF build system.
|
||||
|
||||
- "sdkconfig" project configuration file. This file is created/updated when "make menuconfig" runs, and holds configuration for all of the components in the project (including esp-idf itself). The "sdkconfig" file may or may not be added to the source control system of the project.
|
||||
|
||||
- Optional "components" directory contains components that are part of the project. A project does not have to contain custom components of this kind, but it can be useful for structuring reusable code or including third party components that aren't part of ESP-IDF.
|
||||
|
||||
- "main" directory is a special "pseudo-component" that contains source code for the project itself. "main" is a default name, the Makefile variable ``COMPONENT_DIRS`` includes this component but you can modify this variable (or set ``EXTRA_COMPONENT_DIRS``) to look for components in other places.
|
||||
|
||||
- "build" directory is where build output is created. After the make process is run, this directory will contain interim object files and libraries as well as final binary output files. This directory is usually not added to source control or distributed with the project source code.
|
||||
|
||||
Component directories contain a component makefile - ``component.mk``. This may contain variable definitions
|
||||
to control the build process of the component, and its integration into the overall project. See `Component Makefiles`_ for more details.
|
||||
|
||||
Each component may also include a ``Kconfig`` file defining the `component configuration` options that can be set via the project configuration. Some components may also include ``Kconfig.projbuild`` and ``Makefile.projbuild`` files, which are special files for `overriding parts of the project`.
|
||||
|
||||
Project Makefiles
|
||||
-----------------
|
||||
|
||||
Each project has a single Makefile that contains build settings for the entire project. By default, the project Makefile can be quite minimal.
|
||||
|
||||
Minimal Example Makefile
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
::
|
||||
|
||||
PROJECT_NAME := myProject
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
|
||||
Mandatory Project Variables
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- ``PROJECT_NAME``: Name of the project. Binary output files will use this name - ie myProject.bin, myProject.elf.
|
||||
|
||||
Optional Project Variables
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
These variables all have default values that can be overridden for custom behaviour. Look in ``make/project.mk`` for all of the implementation details.
|
||||
|
||||
- ``PROJECT_PATH``: Top-level project directory. Defaults to the directory containing the Makefile. Many other project variables are based on this variable. The project path cannot contain spaces.
|
||||
- ``BUILD_DIR_BASE``: The build directory for all objects/libraries/binaries. Defaults to ``$(PROJECT_PATH)/build``.
|
||||
- ``COMPONENT_DIRS``: Directories to search for components. Defaults to `$(IDF_PATH)/components`, `$(PROJECT_PATH)/components`, ``$(PROJECT_PATH)/main`` and ``EXTRA_COMPONENT_DIRS``. Override this variable if you don't want to search for components in these places.
|
||||
- ``EXTRA_COMPONENT_DIRS``: Optional list of additional directories to search for components.
|
||||
- ``COMPONENTS``: A list of component names to build into the project. Defaults to all components found in the COMPONENT_DIRS directories.
|
||||
|
||||
Any paths in these Makefile variables should be absolute paths. You can convert relative paths using ``$(PROJECT_PATH)/xxx``, ``$(IDF_PATH)/xxx``, or use the Make function ``$(abspath xxx)``.
|
||||
|
||||
These variables should all be set before the line ``include $(IDF_PATH)/make/project.mk`` in the Makefile.
|
||||
|
||||
Component Makefiles
|
||||
-------------------
|
||||
|
||||
Each project contains one or more components, which can either be part of esp-idf or added from other component directories.
|
||||
|
||||
A component is any directory that contains a ``component.mk`` file.
|
||||
|
||||
Searching for Components
|
||||
------------------------
|
||||
|
||||
The list of directories in ``COMPONENT_DIRS`` is searched for the project's components. Directories in this list can either be components themselves (ie they contain a `component.mk` file), or they can be top-level directories whose subdirectories are components.
|
||||
|
||||
Running the ``make list-components`` target dumps many of these variables and can help debug the discovery of component directories.
|
||||
|
||||
Multiple components with the same name
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
When esp-idf is collecting all the components to compile, it will do this in the order specified by ``COMPONENT_DIRS``; by default, this means the
|
||||
idf components first, the project components second and optionally the components in ``EXTRA_COMPONENT_DIRS`` last. If two or more of these directories
|
||||
contain component subdirectories with the same name, the component in the last place searched is used. This allows, for example, overriding esp-idf components
|
||||
with a modified version by simply copying the component from the esp-idf component directory to the project component tree and then modifying it there.
|
||||
If used in this way, the esp-idf directory itself can remain untouched.
|
||||
|
||||
Minimal Component Makefile
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The minimal ``component.mk`` file is an empty file(!). If the file is empty, the default component behaviour is set:
|
||||
|
||||
- All source files in the same directory as the makefile (``*.c``, ``*.cpp``, ``*.cc``, ``*.S``) will be compiled into the component library
|
||||
- A sub-directory "include" will be added to the global include search path for all other components.
|
||||
- The component library will be linked into the project app.
|
||||
|
||||
See `example component makefiles`_ for more complete component makefile examples.
|
||||
|
||||
Note that there is a difference between an empty ``component.mk`` file (which invokes default component build behaviour) and no ``component.mk`` file (which means no default component build behaviour will occur.) It is possible for a component to have no `component.mk` file, if it only contains other files which influence the project configuration or build process.
|
||||
|
||||
.. component variables:
|
||||
|
||||
Preset Component Variables
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The following component-specific variables are available for use inside ``component.mk``, but should not be modified:
|
||||
|
||||
- ``COMPONENT_PATH``: The component directory. Evaluates to the absolute path of the directory containing ``component.mk``. The component path cannot contain spaces.
|
||||
- ``COMPONENT_NAME``: Name of the component. Defaults to the name of the component directory.
|
||||
- ``COMPONENT_BUILD_DIR``: The component build directory. Evaluates to the absolute path of a directory inside `$(BUILD_DIR_BASE)` where this component's source files are to be built. This is also the Current Working Directory any time the component is being built, so relative paths in make targets, etc. will be relative to this directory.
|
||||
- ``COMPONENT_LIBRARY``: Name of the static library file (relative to the component build directory) that will be built for this component. Defaults to ``$(COMPONENT_NAME).a``.
|
||||
|
||||
The following variables are set at the project level, but exported for use in the component build:
|
||||
|
||||
- ``PROJECT_NAME``: Name of the project, as set in project Makefile
|
||||
- ``PROJECT_PATH``: Absolute path of the project directory containing the project Makefile.
|
||||
- ``COMPONENTS``: Name of all components that are included in this build.
|
||||
- ``CONFIG_*``: Each value in the project configuration has a corresponding variable available in make. All names begin with ``CONFIG_``.
|
||||
- ``CC``, ``LD``, ``AR``, ``OBJCOPY``: Full paths to each tool from the gcc xtensa cross-toolchain.
|
||||
- ``HOSTCC``, ``HOSTLD``, ``HOSTAR``: Full names of each tool from the host native toolchain.
|
||||
- ``IDF_VER``: Git version of ESP-IDF (produced by ``git describe``)
|
||||
|
||||
If you modify any of these variables inside ``component.mk`` then this will not prevent other components from building but it may make your component hard to build and/or debug.
|
||||
|
||||
Optional Project-Wide Component Variables
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The following variables can be set inside ``component.mk`` to control build settings across the entire project:
|
||||
|
||||
- ``COMPONENT_ADD_INCLUDEDIRS``: Paths, relative to the component
|
||||
directory, which will be added to the include search path for
|
||||
all components in the project. Defaults to ``include`` if not overridden. If an include directory is only needed to compile
|
||||
this specific component, add it to ``COMPONENT_PRIV_INCLUDEDIRS`` instead.
|
||||
- ``COMPONENT_ADD_LDFLAGS``: Add linker arguments to the LDFLAGS for
|
||||
the app executable. Defaults to ``-l$(COMPONENT_NAME)``. If
|
||||
adding pre-compiled libraries to this directory, add them as
|
||||
absolute paths - ie $(COMPONENT_PATH)/libwhatever.a
|
||||
- ``COMPONENT_DEPENDS``: Optional list of component names that should
|
||||
be compiled before this component. This is not necessary for
|
||||
link-time dependencies, because all component include directories
|
||||
are available at all times. It is necessary if one component
|
||||
generates an include file which you then want to include in another
|
||||
component. Most components do not need to set this variable.
|
||||
- ``COMPONENT_ADD_LINKER_DEPS``: Optional list of component-relative paths
|
||||
to files which should trigger a re-link of the ELF file if they change.
|
||||
Typically used for linker script files and binary libraries. Most components do
|
||||
not need to set this variable.
|
||||
|
||||
The following variable only works for components that are part of esp-idf itself:
|
||||
|
||||
- ``COMPONENT_SUBMODULES``: Optional list of git submodule paths
|
||||
(relative to COMPONENT_PATH) used by the component. These will be
|
||||
checked (and initialised if necessary) by the build process. This
|
||||
variable is ignored if the component is outside the IDF_PATH
|
||||
directory.
|
||||
|
||||
|
||||
Optional Component-Specific Variables
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The following variables can be set inside ``component.mk`` to control the build of that component:
|
||||
|
||||
- ``COMPONENT_PRIV_INCLUDEDIRS``: Directory paths, must be relative to
|
||||
the component directory, which will be added to the include search
|
||||
path for this component's source files only.
|
||||
- ``COMPONENT_EXTRA_INCLUDES``: Any extra include paths used when
|
||||
compiling the component's source files. These will be prefixed with
|
||||
'-I' and passed as-is to the compiler. Similar to the
|
||||
``COMPONENT_PRIV_INCLUDEDIRS`` variable, except these paths are not
|
||||
expanded relative to the component directory.
|
||||
- ``COMPONENT_SRCDIRS``: Directory paths, must be relative to the
|
||||
component directory, which will be searched for source files (``*.cpp``,
|
||||
``*.c``, ``*.S``). Defaults to '.', ie the component directory
|
||||
itself. Override this to specify a different list of directories
|
||||
which contain source files.
|
||||
- ``COMPONENT_OBJS``: Object files to compile. Default value is a .o
|
||||
file for each source file that is found in ``COMPONENT_SRCDIRS``.
|
||||
Overriding this list allows you to exclude source files in
|
||||
``COMPONENT_SRCDIRS`` that would otherwise be compiled. See
|
||||
`Specifying source files`
|
||||
- ``COMPONENT_EXTRA_CLEAN``: Paths, relative to the component build
|
||||
directory, of any files that are generated using custom make rules
|
||||
in the component.mk file and which need to be removed as part of
|
||||
``make clean``. See `Source Code Generation`_ for an example.
|
||||
- ``COMPONENT_OWNBUILDTARGET`` & ``COMPONENT_OWNCLEANTARGET``: These
|
||||
targets allow you to fully override the default build behaviour for
|
||||
the component. See `Fully Overriding The Component Makefile`_ for
|
||||
more details.
|
||||
- ``COMPONENT_CONFIG_ONLY``: If set, this flag indicates that the component
|
||||
produces no built output at all (ie ``COMPONENT_LIBRARY`` is not built),
|
||||
and most other component variables are ignored. This flag is used for IDF
|
||||
internal components which contain only ``KConfig.projbuild`` and/or
|
||||
``Makefile.projbuild`` files to configure the project, but no source files.
|
||||
- ``CFLAGS``: Flags passed to the C compiler. A default set of
|
||||
``CFLAGS`` is defined based on project settings. Component-specific
|
||||
additions can be made via ``CFLAGS +=``. It is also possible
|
||||
(although not recommended) to override this variable completely for
|
||||
a component.
|
||||
- ``CPPFLAGS``: Flags passed to the C preprocessor (used for .c, .cpp
|
||||
and .S files). A default set of ``CPPFLAGS`` is defined based on
|
||||
project settings. Component-specific additions can be made via
|
||||
``CPPFLAGS +=``. It is also possible (although not recommended) to
|
||||
override this variable completely for a component.
|
||||
- ``CXXFLAGS``: Flags passed to the C++ compiler. A default set of
|
||||
``CXXFLAGS`` is defined based on project
|
||||
settings. Component-specific additions can be made via ``CXXFLAGS
|
||||
+=``. It is also possible (although not recommended) to override
|
||||
this variable completely for a component.
|
||||
|
||||
To apply compilation flags to a single source file, you can add a variable override as a target, ie::
|
||||
|
||||
apps/dhcpserver.o: CFLAGS += -Wno-unused-variable
|
||||
|
||||
This can be useful if there is upstream code that emits warnings.
|
||||
|
||||
Component Configuration
|
||||
-----------------------
|
||||
|
||||
Each component can also have a Kconfig file, alongside ``component.mk``. This contains contains
|
||||
configuration settings to add to the "make menuconfig" for this component.
|
||||
|
||||
These settings are found under the "Component Settings" menu when menuconfig is run.
|
||||
|
||||
To create a component KConfig file, it is easiest to start with one of the KConfig files distributed with esp-idf.
|
||||
|
||||
For an example, see `Adding conditional configuration`_.
|
||||
|
||||
Preprocessor Definitions
|
||||
------------------------
|
||||
|
||||
ESP-IDF build systems adds the following C preprocessor definitions on the command line:
|
||||
|
||||
- ``ESP_PLATFORM`` — Can be used to detect that build happens within ESP-IDF.
|
||||
- ``IDF_VER`` — Defined to a git version string. E.g. ``v2.0`` for a tagged release or ``v1.0-275-g0efaa4f`` for an arbitrary commit.
|
||||
|
||||
Build Process Internals
|
||||
-----------------------
|
||||
|
||||
Top Level: Project Makefile
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- "make" is always run from the project directory and the project makefile, typically named Makefile.
|
||||
- The project makefile sets ``PROJECT_NAME`` and optionally customises other `optional project variables`
|
||||
- The project makefile includes ``$(IDF_PATH)/make/project.mk`` which contains the project-level Make logic.
|
||||
- ``project.mk`` fills in default project-level make variables and includes make variables from the project configuration. If the generated makefile containing project configuration is out of date, then it is regenerated (via targets in ``project_config.mk``) and then the make process restarts from the top.
|
||||
- ``project.mk`` builds a list of components to build, based on the default component directories or a custom list of components set in `optional project variables`.
|
||||
- Each component can set some `optional project-wide component variables`_. These are included via generated makefiles named ``component_project_vars.mk`` - there is one per component. These generated makefiles are included into ``project.mk``. If any are missing or out of date, they are regenerated (via a recursive make call to the component makefile) and then the make process restarts from the top.
|
||||
- `Makefile.projbuild` files from components are included into the make process, to add extra targets or configuration.
|
||||
- By default, the project makefile also generates top-level build & clean targets for each component and sets up `app` and `clean` targets to invoke all of these sub-targets.
|
||||
- In order to compile each component, a recursive make is performed for the component makefile.
|
||||
|
||||
To better understand the project make process, have a read through the ``project.mk`` file itself.
|
||||
|
||||
Second Level: Component Makefiles
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
- Each call to a component makefile goes via the ``$(IDF_PATH)/make/component_wrapper.mk`` wrapper makefile.
|
||||
- This component wrapper includes all component ``Makefile.componentbuild`` files, making any recipes, variables etc in these files available to every component.
|
||||
- The ``component_wrapper.mk`` is called with the current directory set to the component build directory, and the ``COMPONENT_MAKEFILE`` variable is set to the absolute path to ``component.mk``.
|
||||
- ``component_wrapper.mk`` sets default values for all `component variables`, then includes the `component.mk` file which can override or modify these.
|
||||
- If ``COMPONENT_OWNBUILDTARGET`` and ``COMPONENT_OWNCLEANTARGET`` are not defined, default build and clean targets are created for the component's source files and the prerequisite ``COMPONENT_LIBRARY`` static library file.
|
||||
- The ``component_project_vars.mk`` file has its own target in ``component_wrapper.mk``, which is evaluated from ``project.mk`` if this file needs to be rebuilt due to changes in the component makefile or the project configuration.
|
||||
|
||||
To better understand the component make process, have a read through the ``component_wrapper.mk`` file and some of the ``component.mk`` files included with esp-idf.
|
||||
|
||||
Running Make Non-Interactively
|
||||
------------------------------
|
||||
|
||||
When running ``make`` in a situation where you don't want interactive prompts (for example: inside an IDE or an automated build system) append ``BATCH_BUILD=1`` to the make arguments (or set it as an environment variable).
|
||||
|
||||
Setting ``BATCH_BUILD`` implies the following:
|
||||
|
||||
- Verbose output (same as ``V=1``, see below). If you don't want verbose output, also set ``V=0``.
|
||||
- If the project configuration is missing new configuration items (from new components or esp-idf updates) then the project use the default values, instead of prompting the user for each item.
|
||||
- If the build system needs to invoke ``menuconfig``, an error is printed and the build fails.
|
||||
|
||||
Debugging The Make Process
|
||||
--------------------------
|
||||
|
||||
Some tips for debugging the esp-idf build system:
|
||||
|
||||
- Appending ``V=1`` to the make arguments (or setting it as an environment variable) will cause make to echo all commands executed, and also each directory as it is entered for a sub-make.
|
||||
- Running ``make -w`` will cause make to echo each directory as it is entered for a sub-make - same as ``V=1`` but without also echoing all commands.
|
||||
- Running ``make --trace`` (possibly in addition to one of the above arguments) will print out every target as it is built, and the dependency which caused it to be built.
|
||||
- Running ``make -p`` prints a (very verbose) summary of every generated target in each makefile.
|
||||
|
||||
For more debugging tips and general make information, see the `GNU Make Manual`.
|
||||
|
||||
.. _warn-undefined-variables:
|
||||
|
||||
Warning On Undefined Variables
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
By default, the build process will print a warning if an undefined variable is referenced (like ``$(DOES_NOT_EXIST)``). This can be useful to find errors in variable names.
|
||||
|
||||
If you don't want this behaviour, it can be disabled in menuconfig's top level menu under `SDK tool configuration`.
|
||||
|
||||
Note that this option doesn't trigger a warning if ``ifdef`` or ``ifndef`` are used in Makefiles.
|
||||
|
||||
Overriding Parts of the Project
|
||||
-------------------------------
|
||||
|
||||
Makefile.projbuild
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
For components that have build requirements that must be evaluated in the top-level
|
||||
project make pass, you can create a file called ``Makefile.projbuild`` in the
|
||||
component directory. This makefile is included when ``project.mk`` is evaluated.
|
||||
|
||||
For example, if your component needs to add to CFLAGS for the entire
|
||||
project (not just for its own source files) then you can set
|
||||
``CFLAGS +=`` in Makefile.projbuild.
|
||||
|
||||
``Makefile.projbuild`` files are used heavily inside esp-idf, for defining project-wide build features such as ``esptool.py`` command line arguments and the ``bootloader`` "special app".
|
||||
|
||||
Note that ``Makefile.projbuild`` isn't necessary for the most common component uses - such as adding include directories to the project, or LDFLAGS to the final linking step. These values can be customised via the ``component.mk`` file itself. See `Optional Project-Wide Component Variables`_ for details.
|
||||
|
||||
Take care when setting variables or targets in this file. As the values are included into the top-level project makefile pass, they can influence or break functionality across all components!
|
||||
|
||||
KConfig.projbuild
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
This is an equivalent to ``Makefile.projbuild`` for `component configuration` KConfig files. If you want to include
|
||||
configuration options at the top-level of menuconfig, rather than inside the "Component Configuration" sub-menu, then these can be defined in the KConfig.projbuild file alongside the ``component.mk`` file.
|
||||
|
||||
Take care when adding configuration values in this file, as they will be included across the entire project configuration. Where possible, it's generally better to create a KConfig file for `component configuration`.
|
||||
|
||||
|
||||
Makefile.componentbuild
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
For components that e.g. include tools to generate source files from other files, it is necessary to be able to add recipes, macros or variable definitions
|
||||
into the component build process of every components. This is done by having a ``Makefile.componentbuild`` in a component directory. This file gets included
|
||||
in ``component_wrapper.mk``, before the ``component.mk`` of the component is included. As with the Makefile.projbuild, take care with these files: as they're
|
||||
included in each component build, a ``Makefile.componentbuild`` error may only show up when compiling an entirely different component.
|
||||
|
||||
Configuration-Only Components
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Some special components which contain no source files, only ``Kconfig.projbuild`` and ``Makefile.projbuild``, may set the flag ``COMPONENT_CONFIG_ONLY`` in the component.mk file. If this flag is set, most other component variables are ignored and no build step is run for the component.
|
||||
|
||||
Example Component Makefiles
|
||||
---------------------------
|
||||
|
||||
Because the build environment tries to set reasonable defaults that will work most
|
||||
of the time, component.mk can be very small or even empty (see `Minimal Component Makefile`_). However, overriding `component variables` is usually required for some functionality.
|
||||
|
||||
Here are some more advanced examples of ``component.mk`` makefiles:
|
||||
|
||||
|
||||
Adding source directories
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
By default, sub-directories are ignored. If your project has sources in sub-directories
|
||||
instead of in the root of the component then you can tell that to the build
|
||||
system by setting ``COMPONENT_SRCDIRS``::
|
||||
|
||||
COMPONENT_SRCDIRS := src1 src2
|
||||
|
||||
This will compile all source files in the src1/ and src2/ sub-directories
|
||||
instead.
|
||||
|
||||
Specifying source files
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The standard component.mk logic adds all .S and .c files in the source
|
||||
directories as sources to be compiled unconditionally. It is possible
|
||||
to circumvent that logic and hard-code the objects to be compiled by
|
||||
manually setting the ``COMPONENT_OBJS`` variable to the name of the
|
||||
objects that need to be generated::
|
||||
|
||||
COMPONENT_OBJS := file1.o file2.o thing/filea.o thing/fileb.o anotherthing/main.o
|
||||
COMPONENT_SRCDIRS := . thing anotherthing
|
||||
|
||||
Note that ``COMPONENT_SRCDIRS`` must be set as well.
|
||||
|
||||
Adding conditional configuration
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The configuration system can be used to conditionally compile some files
|
||||
depending on the options selected in ``make menuconfig``. For this, ESP-IDF
|
||||
has the compile_only_if and compile_only_if_not macros:
|
||||
|
||||
``Kconfig``::
|
||||
|
||||
config FOO_ENABLE_BAR
|
||||
bool "Enable the BAR feature."
|
||||
help
|
||||
This enables the BAR feature of the FOO component.
|
||||
|
||||
``component.mk``::
|
||||
|
||||
$(call compile_only_if,$(CONFIG_FOO_ENABLE_BAR),bar.o)
|
||||
|
||||
|
||||
As can be seen in the example, the ``compile_only_if`` macro takes a condition and a
|
||||
list of object files as parameters. If the condition is true (in this case: if the
|
||||
BAR feature is enabled in menuconfig) the object files (in this case: bar.o) will
|
||||
always be compiled. The opposite goes as well: if the condition is not true, bar.o
|
||||
will never be compiled. ``compile_only_if_not`` does the opposite: compile if the
|
||||
condition is false, not compile if the condition is true.
|
||||
|
||||
This can also be used to select or stub out an implementation, as such:
|
||||
|
||||
``Kconfig``::
|
||||
|
||||
config ENABLE_LCD_OUTPUT
|
||||
bool "Enable LCD output."
|
||||
help
|
||||
Select this if your board has a LCD.
|
||||
|
||||
config ENABLE_LCD_CONSOLE
|
||||
bool "Output console text to LCD"
|
||||
depends on ENABLE_LCD_OUTPUT
|
||||
help
|
||||
Select this to output debugging output to the lcd
|
||||
|
||||
config ENABLE_LCD_PLOT
|
||||
bool "Output temperature plots to LCD"
|
||||
depends on ENABLE_LCD_OUTPUT
|
||||
help
|
||||
Select this to output temperature plots
|
||||
|
||||
|
||||
``component.mk``::
|
||||
|
||||
# If LCD is enabled, compile interface to it, otherwise compile dummy interface
|
||||
$(call compile_only_if,$(CONFIG_ENABLE_LCD_OUTPUT),lcd-real.o lcd-spi.o)
|
||||
$(call compile_only_if_not,$(CONFIG_ENABLE_LCD_OUTPUT),lcd-dummy.o)
|
||||
|
||||
#We need font if either console or plot is enabled
|
||||
$(call compile_only_if,$(or $(CONFIG_ENABLE_LCD_CONSOLE),$(CONFIG_ENABLE_LCD_PLOT)), font.o)
|
||||
|
||||
Note the use of the Make 'or' function to include the font file. Other substitution functions,
|
||||
like 'and' and 'if' will also work here. Variables that do not come from menuconfig can also
|
||||
be used: ESP-IDF uses the default Make policy of judging a variable which is empty or contains
|
||||
only whitespace to be false while a variable with any non-whitespace in it is true.
|
||||
|
||||
(Note: Older versions of this document advised conditionally adding object file names to
|
||||
``COMPONENT_OBJS``. While this still is possible, this will only work when all object
|
||||
files for a component are named explicitely, and will not clean up deselected object files
|
||||
in a ``make clean`` pass.)
|
||||
|
||||
Source Code Generation
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Some components will have a situation where a source file isn't
|
||||
supplied with the component itself but has to be generated from
|
||||
another file. Say our component has a header file that consists of the
|
||||
converted binary data of a BMP file, converted using a hypothetical
|
||||
tool called bmp2h. The header file is then included in as C source
|
||||
file called graphics_lib.c::
|
||||
|
||||
COMPONENT_EXTRA_CLEAN := logo.h
|
||||
|
||||
graphics_lib.o: logo.h
|
||||
|
||||
logo.h: $(COMPONENT_PATH)/logo.bmp
|
||||
bmp2h -i $^ -o $@
|
||||
|
||||
|
||||
In this example, graphics_lib.o and logo.h will be generated in the
|
||||
current directory (the build directory) while logo.bmp comes with the
|
||||
component and resides under the component path. Because logo.h is a
|
||||
generated file, it needs to be cleaned when make clean is called which
|
||||
why it is added to the COMPONENT_EXTRA_CLEAN variable.
|
||||
|
||||
Cosmetic Improvements
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Because logo.h is a generated file, it needs to be cleaned when make
|
||||
clean is called which why it is added to the COMPONENT_EXTRA_CLEAN
|
||||
variable.
|
||||
|
||||
Adding logo.h to the ``graphics_lib.o`` dependencies causes it to be
|
||||
generated before ``graphics_lib.c`` is compiled.
|
||||
|
||||
If a a source file in another component included ``logo.h``, then this
|
||||
component's name would have to be added to the other component's
|
||||
``COMPONENT_DEPENDS`` list to ensure that the components were built
|
||||
in-order.
|
||||
|
||||
Embedding Binary Data
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Sometimes you have a file with some binary or text data that you'd like to make available to your component - but you don't want to reformat the file as C source.
|
||||
|
||||
You can set a variable COMPONENT_EMBED_FILES in component.mk, giving the names of the files to embed in this way::
|
||||
|
||||
COMPONENT_EMBED_FILES := server_root_cert.der
|
||||
|
||||
Or if the file is a string, you can use the variable COMPONENT_EMBED_TXTFILES. This will embed the contents of the text file as a null-terminated string::
|
||||
|
||||
COMPONENT_EMBED_TXTFILES := server_root_cert.pem
|
||||
|
||||
The file's contents will be added to the .rodata section in flash, and are available via symbol names as follows::
|
||||
|
||||
extern const uint8_t server_root_cert_pem_start[] asm("_binary_server_root_cert_pem_start");
|
||||
extern const uint8_t server_root_cert_pem_end[] asm("_binary_server_root_cert_pem_end");
|
||||
|
||||
The names are generated from the full name of the file, as given in COMPONENT_EMBED_FILES. Characters /, ., etc. are replaced with underscores. The _binary prefix in the symbol name is added by objcopy and is the same for both text and binary files.
|
||||
|
||||
For an example of using this technique, see :example:`protocols/https_request` - the certificate file contents are loaded from the text .pem file at compile time.
|
||||
|
||||
|
||||
Fully Overriding The Component Makefile
|
||||
---------------------------------------
|
||||
|
||||
Obviously, there are cases where all these recipes are insufficient for a
|
||||
certain component, for example when the component is basically a wrapper
|
||||
around another third-party component not originally intended to be
|
||||
compiled under this build system. In that case, it's possible to forego
|
||||
the esp-idf build system entirely by setting COMPONENT_OWNBUILDTARGET and
|
||||
possibly COMPONENT_OWNCLEANTARGET and defining your own targets named ``build`` and ``clean`` in ``component.mk``
|
||||
target. The build target can do anything as long as it creates
|
||||
$(COMPONENT_LIBRARY) for the project make process to link into the app binary.
|
||||
|
||||
(Actually, even this is not strictly necessary - if the COMPONENT_ADD_LDFLAGS variable
|
||||
is overridden then the component can instruct the linker to link other binaries instead.)
|
||||
|
||||
|
||||
.. _esp-idf-template: https://github.com/espressif/esp-idf-template
|
||||
.. _GNU Make Manual: https://www.gnu.org/software/make/manual/make.html
|
||||
|
||||
|
||||
.. _custom-sdkconfig-defaults:
|
||||
|
||||
Custom sdkconfig defaults
|
||||
-------------------------
|
||||
|
||||
For example projects or other projects where you don't want to specify a full sdkconfig configuration, but you do want to override some key values from the esp-idf defaults, it is possible to create a file ``sdkconfig.defaults`` in the project directory. This file will be used when running ``make defconfig``, or creating a new config from scratch.
|
||||
|
||||
To override the name of this file, set the ``SDKCONFIG_DEFAULTS`` environment variable.
|
||||
|
||||
|
||||
Save flash arguments
|
||||
--------------------
|
||||
|
||||
There're some scenarios that we want to flash the target board without IDF. For this case we want to save the built binaries, esptool.py and esptool write_flash arguments. It's simple to write a script to save binaries and esptool.py. We can use command ``make print_flash_cmd``, it will print the flash arguments::
|
||||
|
||||
--flash_mode dio --flash_freq 40m --flash_size detect 0x1000 bootloader/bootloader.bin 0x10000 example_app.bin 0x8000 partition_table_unit_test_app.bin
|
||||
|
||||
Then use flash arguments as the arguemnts for esptool write_flash arguments::
|
||||
|
||||
python esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 40m --flash_size detect 0x1000 bootloader/bootloader.bin 0x10000 example_app.bin 0x8000 partition_table_unit_test_app.bin
|
||||
|
||||
Building the Bootloader
|
||||
=======================
|
||||
|
||||
The bootloader is built by default as part of "make all", or can be built standalone via "make bootloader-clean". There is also "make bootloader-list-components" to see the components included in the bootloader build.
|
||||
|
||||
The component in IDF components/bootloader is special, as the second stage bootloader is a separate .ELF and .BIN file to the main project. However it shares its configuration and build directory with the main project.
|
||||
|
||||
This is accomplished by adding a subproject under components/bootloader/subproject. This subproject has its own Makefile, but it expects to be called from the project's own Makefile via some glue in the components/bootloader/Makefile.projectbuild file. See these files for more details.
|
||||
@@ -0,0 +1,143 @@
|
||||
Console
|
||||
=======
|
||||
|
||||
ESP-IDF provides ``console`` component, which includes building blocks needed to develop an interactive console over serial port. This component includes following facilities:
|
||||
|
||||
- Line editing, provided by `linenoise`_ library. This includes handling of backspace and arrow keys, scrolling through command history, command auto-completion, and argument hints.
|
||||
- Splitting of command line into arguments.
|
||||
- Argument parsing, provided by `argtable3`_ library. This library includes APIs useful for parsing GNU style command line arguments.
|
||||
- Functions for registration and dispatching of commands.
|
||||
|
||||
These facilities can be used together or independently. For example, it is possible to use line editing and command registration features, but use ``getopt`` or custom code for argument parsing, instead of `argtable3`_. Likewise, it is possible to use simpler means of command input (such as ``fgets``) together with the rest of the means for command splitting and argument parsing.
|
||||
|
||||
Line editing
|
||||
------------
|
||||
|
||||
Line editing feature lets users compose commands by typing them, erasing symbols using 'backspace' key, navigating within the command using left/right keys, navigating to previously typed commands using up/down keys, and performing autocompletion using 'tab' key.
|
||||
|
||||
.. note:: This feature relies on ANSI escape sequence support in the terminal application. As such, serial monitors which display raw UART data can not be used together with the line editing library. If you see ``[6n`` or similar escape sequence when running get_started/console example instead of a command prompt (``[esp32]>``), it means that the serial monitor does not support escape sequences. Programs which are known to work are GNU screen, minicom, and idf_monitor.py (which can be invoked using ``make monitor`` from project directory).
|
||||
|
||||
Here is an overview of functions provided by `linenoise`_ library.
|
||||
|
||||
Configuration
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Linenoise library does not need explicit initialization. However, some configuration defaults may need to be changed before invoking the main line editing function.
|
||||
|
||||
``linenoiseClearScreen``
|
||||
Clear terminal screen using an escape sequence and position the cursor at the top left corner.
|
||||
|
||||
``linenoiseSetMultiLine``
|
||||
Switch between single line and multi line editing modes. In single line mode, if the length of the command exceeds the width of the terminal, the command text is scrolled within the line to show the end of the text. In this case the beginning of the text is hidden. Single line needs less data to be sent to refresh screen on each key press, so exhibits less glitching compared to the multi line mode. On the flip side, editing commands and copying command text from terminal in single line mode is harder. Default is single line mode.
|
||||
|
||||
|
||||
Main loop
|
||||
^^^^^^^^^
|
||||
|
||||
``linenoise``
|
||||
In most cases, console applications have some form of read/eval loop. ``linenoise`` is the single function which handles user's key presses and returns completed line once 'enter' key is pressed. As such, it handles the 'read' part of the loop.
|
||||
|
||||
``linenoiseFree``
|
||||
This function must be called to release the command line buffer obtained from ``linenoise`` function.
|
||||
|
||||
Hints and completions
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
``linenoiseSetCompletionCallback``
|
||||
When user presses 'tab' key, linenoise library invokes completion callback. The callback should inspect the contents of the command typed so far and provide a list of possible completions using calls to ``linenoiseAddCompletion`` function. ``linenoiseSetCompletionCallback`` function should be called to register this completion callback, if completion feature is desired.
|
||||
|
||||
``console`` component provides a ready made function to provide completions for registered commands, ``esp_console_get_completion`` (see below).
|
||||
|
||||
``linenoiseAddCompletion``
|
||||
Function to be called by completion callback to inform the library about possible completions of the currently typed command.
|
||||
|
||||
``linenoiseSetHintsCallback``
|
||||
Whenever user input changes, linenoise invokes hints callback. This callback can inspect the command line typed so far, and provide a string with hints (which can include list of command arguments, for example). The library then displays the hint text on the same line where editing happens, possibly with a different color.
|
||||
|
||||
``linenoiseSetFreeHintsCallback``
|
||||
If hint string returned by hints callback is dynamically allocated or needs to be otherwise recycled, the function which performs such cleanup should be registered via ``linenoiseSetFreeHintsCallback``.
|
||||
|
||||
|
||||
History
|
||||
^^^^^^^
|
||||
|
||||
``linenoiseHistorySetMaxLen``
|
||||
This function sets the number of most recently typed commands to be kept in memory. Users can navigate the history using up/down arrows.
|
||||
|
||||
``linenoiseHistoryAdd``
|
||||
Linenoise does not automatically add commands to history. Instead, applications need to call this function to add command strings to the history.
|
||||
|
||||
``linenoiseHistorySave``
|
||||
Function saves command history from RAM to a text file, for example on an SD card or on a filesystem in flash memory.
|
||||
|
||||
``linenoiseHistoryLoad``
|
||||
Counterpart to ``linenoiseHistorySave``, loads history from a file.
|
||||
|
||||
``linenoiseHistoryFree``
|
||||
Releases memory used to store command history. Call this function when done working with linenoise library.
|
||||
|
||||
Splitting of command line into arguments
|
||||
----------------------------------------
|
||||
|
||||
``console`` component provides ``esp_console_split_argv`` function to split command line string into arguments. The function returns the number of arguments found (``argc``) and fills an array of pointers which can be passed as ``argv`` argument to any function which accepts arguments in ``argc, argv`` format.
|
||||
|
||||
The command line is split into arguments according to the following rules:
|
||||
|
||||
- Arguments are separated by spaces
|
||||
- If spaces within arguments are required, they can be escaped using ``\`` (backslash) character.
|
||||
- Other escape sequences which are recognized are ``\\`` (which produces literal backslash) and ``\"``, which produces a double quote.
|
||||
- Arguments can be quoted using double quotes. Quotes may appear only in the beginning and at the end of the argument. Quotes within the argument must be escaped as mentioned above. Quotes surrounding the argument are stripped by ``esp_console_split_argv`` function.
|
||||
|
||||
Examples:
|
||||
|
||||
- ``abc def 1 20 .3`` ⟶ [ ``abc``, ``def``, ``1``, ``20``, ``.3`` ]
|
||||
- ``abc "123 456" def`` ⟶ [ ``abc``, ``123 456``, ``def`` ]
|
||||
- ```a\ b\\c\"`` ⟶ [ ``a b\c"`` ]
|
||||
|
||||
|
||||
Argument parsing
|
||||
----------------
|
||||
|
||||
For argument parsing, ``console`` component includes `argtable3`_ library. Please see `tutorial`_ for an introduction to `argtable3`_. Github repository also includes `examples`_.
|
||||
|
||||
.. _argtable3: http://www.argtable.org/
|
||||
.. _linenoise: https://github.com/antirez/linenoise
|
||||
.. _tutorial: http://www.argtable.org/tutorial/
|
||||
.. _examples: https://github.com/argtable/argtable3/tree/master/examples
|
||||
|
||||
|
||||
Command registration and dispatching
|
||||
------------------------------------
|
||||
|
||||
``console`` component includes utility functions which handle registration of commands, matching commands typed by the user to registered ones, and calling these commands with the arguments given on the command line.
|
||||
|
||||
Application first initializes command registration module using a call to ``esp_console_init``, and calls ``esp_console_cmd_register`` function to register command handlers.
|
||||
|
||||
For each command, application provides the following information (in the form of ``esp_console_cmd_t`` structure):
|
||||
|
||||
- Command name (string without spaces)
|
||||
- Help text explaining what the command does
|
||||
- Optional hint text listing the arguments of the command. If application uses Argtable3 for argument parsing, hint text can be generated automatically by providing a pointer to argtable argument definitions structure instead.
|
||||
- The command handler function.
|
||||
|
||||
A few other functions are provided by the command registration module:
|
||||
|
||||
``esp_console_run``
|
||||
This function takes the command line string, splits it into argc/argv argument list using ``esp_console_split_argv``, looks up the command in the list of registered components, and if it is found, executes its handler.
|
||||
|
||||
``esp_console_split_argv``
|
||||
Adds ``help`` command to the list of registered commands. This command prints the list of all the registered commands, along with their arguments and help texts.
|
||||
|
||||
``esp_console_get_completion``
|
||||
Callback function to be used with ``linenoiseSetCompletionCallback`` from linenoise library. Provides completions to linenoise based on the list of registered commands.
|
||||
|
||||
``esp_console_get_hint``
|
||||
Callback function to be used with ``linenoiseSetHintsCallback`` from linenoise library. Provides argument hints for registered commands to linenoise.
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
Example application illustrating usage of the ``console`` component is available in ``examples/system/console`` directory. This example shows how to initialize UART and VFS functions, set up linenoise library, read and handle commands from UART, and store command history in Flash. See README.md in the example directory for more details.
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
ESP32 Core Dump
|
||||
===============
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
ESP-IDF provides support to generate core dumps on unrecoverable software errors. This useful technique allows post-mortem analysis of software state at the moment of failure.
|
||||
Upon the crash system enters panic state, prints some information and halts or reboots depending configuration. User can choose to generate core dump in order to analyse
|
||||
the reason of failure on PC later on. Core dump contains snapshots of all tasks in the system at the moment of failure. Snapshots include tasks control blocks (TCB) and stacks.
|
||||
So it is possible to find out what task, at what instruction (line of code) and what callstack of that task lead to the crash.
|
||||
ESP-IDF provides special script `espcoredump.py` to help users to retrieve and analyse core dumps. This tool provides two commands for core dumps analysis:
|
||||
|
||||
* info_corefile - prints crashed task's registers, callstack, list of available tasks in the system, memory regions and contents of memory stored in core dump (TCBs and stacks)
|
||||
* dbg_corefile - creates core dump ELF file and runs GDB debug session with this file. User can examine memory, variables and tasks states manually. Note that since not all memory is saved in core dump only values of variables allocated on stack will be meaningfull
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
There are a number of core dump related configuration options which user can choose in configuration menu of the application (`make menuconfig`).
|
||||
|
||||
1. Core dump data destination (`Components -> ESP32-specific config -> Core dump destination`):
|
||||
|
||||
* Disable core dump generation
|
||||
* Save core dump to flash
|
||||
* Print core dump to UART
|
||||
|
||||
2. Logging level of core dump module (`Components -> ESP32-specific config -> Core dump module logging level`). Value is a number from 0 (no output) to 5 (most verbose).
|
||||
|
||||
3. Delay before core dump will be printed to UART (`Components -> ESP32-specific config -> Core dump print to UART delay`). Value is in ms.
|
||||
|
||||
|
||||
Save core dump to flash
|
||||
-----------------------
|
||||
|
||||
When this option is selected core dumps are saved to special partition on flash. When using default partition table files which are provided with ESP-IDF it automatically
|
||||
allocates necessary space on flash, But if user wants to use its own layout file together with core dump feature it should define separate partition for core dump
|
||||
as it is shown below::
|
||||
|
||||
# Name, Type, SubType, Offset, Size
|
||||
# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild
|
||||
nvs, data, nvs, 0x9000, 0x6000
|
||||
phy_init, data, phy, 0xf000, 0x1000
|
||||
factory, app, factory, 0x10000, 1M
|
||||
coredump, data, coredump,, 64K
|
||||
|
||||
There are no special requrements for partition name. It can be choosen according to the user application needs, but partition type should be 'data' and
|
||||
sub-type should be 'coredump'. Also when choosing partition size note that core dump data structure introduces constant overhead of 20 bytes and per-task overhead of 12 bytes.
|
||||
This overhead does not include size of TCB and stack for every task. So partirion size should be at least 20 + max tasks number x (12 + TCB size + max task stack size) bytes.
|
||||
|
||||
The example of generic command to analyze core dump from flash is: `espcoredump.py -p </path/to/serial/port> info_corefile </path/to/program/elf/file>`
|
||||
or `espcoredump.py -p </path/to/serial/port> dbg_corefile </path/to/program/elf/file>`
|
||||
|
||||
Print core dump to UART
|
||||
-----------------------
|
||||
|
||||
When this option is selected base64-encoded core dumps are printed on UART upon system panic. In this case user should save core dump text body to some file manually and
|
||||
then run the following command: `espcoredump.py info_corefile -t b64 -c </path/to/saved/base64/text> </path/to/program/elf/file>`
|
||||
or `espcoredump.py dbg_corefile -t b64 -c </path/to/saved/base64/text> </path/to/program/elf/file>`
|
||||
|
||||
Base64-encoded body of core dump will be between the following header and footer::
|
||||
|
||||
================= CORE DUMP START =================
|
||||
<body of base64-encoded core dump, save it to file on disk>
|
||||
================= CORE DUMP END ===================
|
||||
|
||||
Running 'espcoredump.py'
|
||||
------------------------------------
|
||||
|
||||
Generic command syntax:
|
||||
|
||||
`espcoredump.py [options] command [args]`
|
||||
|
||||
:Script Options:
|
||||
* --chip,-c {auto,esp32}. Target chip type. Supported values are `auto` and `esp32`.
|
||||
* --port,-p PORT. Serial port device.
|
||||
* --baud,-b BAUD. Serial port baud rate used when flashing/reading.
|
||||
:Commands:
|
||||
* info_corefile. Retrieve core dump and print useful info.
|
||||
* dbg_corefile. Retrieve core dump and start GDB session with it.
|
||||
:Command Arguments:
|
||||
* --gdb,-g GDB. Path to gdb to use for data retrieval.
|
||||
* --core,-c CORE. Path to core dump file to use (if skipped core dump will be read from flash).
|
||||
* --core-format,-t CORE_FORMAT. Specifies that file passed with "-c" is an ELF ("elf"), dumped raw binary ("raw") or base64-encoded ("b64") format.
|
||||
* --off,-o OFF. Ofsset of coredump partition in flash (type "make partition_table" to see it).
|
||||
* --save-core,-s SAVE_CORE. Save core to file. Othwerwise temporary core file will be deleted. Ignored with "-c".
|
||||
* --print-mem,-m Print memory dump. Used only with "info_corefile".
|
||||
@@ -0,0 +1,87 @@
|
||||
Deep Sleep Wake Stubs
|
||||
=====================
|
||||
|
||||
ESP32 supports running a "deep sleep wake stub" when coming out of deep sleep. This function runs immediately as soon as the chip wakes up - before any normal initialisation, bootloader, or ESP-IDF code has run. After the wake stub runs, the SoC can go back to sleep or continue to start ESP-IDF normally.
|
||||
|
||||
Deep sleep wake stub code is loaded into "RTC Fast Memory" and any data which it uses must also be loaded into RTC memory. RTC memory regions hold their contents during deep sleep.
|
||||
|
||||
Rules for Wake Stubs
|
||||
--------------------
|
||||
|
||||
Wake stub code must be carefully written:
|
||||
|
||||
* As the SoC has freshly woken from sleep, most of the peripherals are in reset states. The SPI flash is unmapped.
|
||||
|
||||
* The wake stub code can only call functions implemented in ROM or loaded into RTC Fast Memory (see below.)
|
||||
|
||||
* The wake stub code can only access data loaded in RTC memory. All other RAM will be unintiailised and have random contents. The wake stub can use other RAM for temporary storage, but the contents will be overwritten when the SoC goes back to sleep or starts ESP-IDF.
|
||||
|
||||
* RTC memory must include any read-only data (.rodata) used by the stub.
|
||||
|
||||
* Data in RTC memory is initialised whenever the SoC restarts, except when waking from deep sleep. When waking from deep sleep, the values which were present before going to sleep are kept.
|
||||
|
||||
* Wake stub code is a part of the main esp-idf app. During normal running of esp-idf, functions can call the wake stub functions or access RTC memory. It is as if these were regular parts of the app.
|
||||
|
||||
Implementing A Stub
|
||||
-------------------
|
||||
|
||||
The wake stub in esp-idf is called ``esp_wake_deep_sleep()``. This function runs whenever the SoC wakes from deep sleep. There is a default version of this function provided in esp-idf, but the default function is weak-linked so if your app contains a function named ``esp_wake_deep_sleep()`` then this will override the default.
|
||||
|
||||
If supplying a custom wake stub, the first thing it does should be to call ``esp_default_wake_deep_sleep()``.
|
||||
|
||||
It is not necessary to implement ``esp_wake_deep_sleep()`` in your app in order to use deep sleep. It is only necessary if you want to have special behaviour immediately on wake.
|
||||
|
||||
If you want to swap between different deep sleep stubs at runtime, it is also possible to do this by calling the ``esp_set_deep_sleep_wake_stub()`` function. This is not necessary if you only use the default ``esp_wake_deep_sleep()`` function.
|
||||
|
||||
All of these functions are declared in the ``esp_deepsleep.h`` header under components/esp32.
|
||||
|
||||
Loading Code Into RTC Memory
|
||||
----------------------------
|
||||
|
||||
Wake stub code must be resident in RTC Fast Memory. This can be done in one of two ways.
|
||||
|
||||
The first way is to use the ``RTC_IRAM_ATTR`` attribute to place a function into RTC memory::
|
||||
|
||||
void RTC_IRAM_ATTR esp_wake_deep_sleep(void) {
|
||||
esp_default_wake_deep_sleep();
|
||||
// Add additional functionality here
|
||||
}
|
||||
|
||||
The second way is to place the function into any source file whose name starts with ``rtc_wake_stub``. Files names ``rtc_wake_stub*`` have their contents automatically put into RTC memory by the linker.
|
||||
|
||||
The first way is simpler for very short and simple code, or for source files where you want to mix "normal" and "RTC" code. The second way is simpler when you want to write longer pieces of code for RTC memory.
|
||||
|
||||
|
||||
Loading Data Into RTC Memory
|
||||
----------------------------
|
||||
|
||||
Data used by stub code must be resident in RTC Slow Memory. This memory is also used by the ULP.
|
||||
|
||||
Specifying this data can be done in one of two ways:
|
||||
|
||||
The first way is to use the ``RTC_DATA_ATTR`` and ``RTC_RODATA_ATTR`` to specify any data (writeable or read-only, respectivley) which should be loaded into RTC slow memory::
|
||||
|
||||
RTC_DATA_ATTR int wake_count;
|
||||
|
||||
void RTC_IRAM_ATTR esp_wake_deep_sleep(void) {
|
||||
esp_default_wake_deep_sleep();
|
||||
static RTC_RODATA_ATTR const char fmt_str[] = "Wake count %d\n";
|
||||
ets_printf(fmt_str, wake_count++);
|
||||
}
|
||||
|
||||
Unfortunately, any string constants used in this way must be declared as arrays and marked with RTC_RODATA_ATTR, as shown in the example above.
|
||||
|
||||
The second way is to place the data into any source file whose name starts with ``rtc_wake_stub``.
|
||||
|
||||
For example, the equivalent example in ``rtc_wake_stub_counter.c``::
|
||||
|
||||
int wake_count;
|
||||
|
||||
void RTC_IRAM_ATTR esp_wake_deep_sleep(void) {
|
||||
esp_default_wake_deep_sleep();
|
||||
ets_printf("Wake count %d\n", wake_count++);
|
||||
}
|
||||
|
||||
The second way is a better option if you need to use strings, or write other more complex code.
|
||||
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
Support for external RAM
|
||||
************************
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
Introduction
|
||||
============
|
||||
|
||||
The ESP32 has a few hundred KiB of internal RAM, residing on the same die as the rest of the ESP32. For some purposes, this is insufficient,
|
||||
and therefore the ESP32 incorporates the ability to also use up to 4MiB of external SPI RAM memory as memory. The external memory is incorporated
|
||||
in the memory map and is, within certain restrictions, usable in the same way internal data RAM is.
|
||||
|
||||
Hardware
|
||||
========
|
||||
|
||||
The ESP32 supports SPI (P)SRAM connected in parallel with the SPI flash chip. While the ESP32 is capable of supporting several types
|
||||
of RAM chips, the ESP32 SDK at the moment only supports the ESP-PSRAM32 chip.
|
||||
|
||||
The ESP-PSRAM32 chip is an 1.8V device, and can only be used in parallel with an 1.8V flash part. Make sure to either set the MTDI
|
||||
pin to a high signal level on bootup, or program the fuses in the ESP32 to always use a VDD_SIO level of 1.8V. Not doing this risks
|
||||
damaging the PSRAM and/or flash chip.
|
||||
|
||||
To connect the ESP-PSRAM chip to the ESP32D0W*, connect the following signals:
|
||||
* PSRAM /CE (pin 1) - ESP32 GPIO 16
|
||||
* PSRAM SO (pin 2) - flash DO
|
||||
* PSRAM SIO[2] (pin 3) - flash WP
|
||||
* PSRAM SI (pin 5) - flash DI
|
||||
* PSRAM SCLK (pin 6) - ESP32 GPIO 17
|
||||
* PSRAM SIO[3] (pin 7) - flash HOLD
|
||||
* PSRAM Vcc (pin 8) - ESP32 VCC_SDIO
|
||||
|
||||
Connections for the ESP32D2W* chips are TBD.
|
||||
|
||||
.. NOTE::
|
||||
Espressif sells an ESP-WROVER module which contains an ESP32, 1.8V flash and the ESP-PSRAM32 integrated in a module, ready for inclusion
|
||||
on an end product PCB.
|
||||
|
||||
Software
|
||||
========
|
||||
|
||||
ESP-IDF fully supports integrating external memory use into your applications. ESP-IDF can be configured to handle external RAM in several ways:
|
||||
* Only initialize RAM. This allows the application to manually place data here by dereferencing pointers pointed at the external RAM memory
|
||||
region (0x3F800000 and up).
|
||||
* Initialize RAM and add it to the capability allocator. This allows a program to specifically allocate a chunk of external RAM using
|
||||
``heap_caps_malloc(size, MALLOC_CAP_SPIRAM)``. This memory can be used and subsequently freed using a normal ``free()`` call.
|
||||
* Initialize RAM, add it to the capability allocator and add memory to the pool of RAM that can be returned by ``malloc()``. This allows
|
||||
any application to use the external RAM without having to rewrite the code to use ``heap_caps_malloc``.
|
||||
|
||||
All these options can be selected from the menuconfig menu.
|
||||
|
||||
Restrictions
|
||||
------------
|
||||
|
||||
The use of external RAM has a few restrictions:
|
||||
* When disabling flash cache (for example, because the flash is being written to), the external RAM also becomes inaccessible; any reads from or
|
||||
writes to it will lead to an illegal cache access exception. This is also the reason that ESP-IDF will never allocate a tasks stack in external
|
||||
RAM.
|
||||
* External RAM cannot be used as a place to store DMA transaction descriptors or as a buffer for a DMA transfer to read from or write into. Any
|
||||
buffers that will be used in combination with DMA must be allocated using ``heap_caps_malloc(size, MALLOC_CAP_DMA)`` (and can be freed using a
|
||||
standard ``free()`` call.)
|
||||
* External RAM uses the same cache region as the external flash. This means that often accessed variables in external RAM can be read and
|
||||
modified almost as quickly as in internal ram. However, when accessing large chunks of data (>32K), the cache can be insufficient and speeds
|
||||
will fall back to the access speed of the external RAM. Moreover, accessing large chunks of data can 'push out' cached flash, possibly making
|
||||
execution of code afterwards slower.
|
||||
* External RAM cannot be used as task stack memory; because of this, xTaskCreate and similar functions will always allocate internal memory
|
||||
for stack and task TCBs and xTaskCreateStatic-type functions will check if the buffers passed are internal. However, for tasks not calling
|
||||
on code in ROM in any way, directly or indirectly, the menuconfig option :ref:`CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY` will eliminate
|
||||
the check in xTaskCreateStatic, allowing task stack in external RAM. Using this is not advised, however.
|
||||
|
||||
|
||||
Because there are a fair few situations that have a specific need for internal memory, but it is also possible to use malloc() to exhaust
|
||||
internal memory, there is a pool reserved specifically for requests that cannot be resolved from external memory; allocating task
|
||||
stack, DMA buffers and memory that stays accessible when cache is disabled is drawn from this pool. The size of this pool is configurable
|
||||
in menuconfig.
|
||||
|
||||
|
||||
Chip revisions
|
||||
==============
|
||||
|
||||
There are some issues with certain revisions of the ESP32 that have repercussions for use with external RAM. These are documented in the ESP32
|
||||
ECO_ document. In particular, ESP-IDF handles the bugs mentioned in the following ways:
|
||||
|
||||
ESP32 rev v0
|
||||
------------
|
||||
ESP-IDF has no workaround for the bugs in this revision of silicon, and it cannot be used to map external PSRAM into the ESP32s main memory map.
|
||||
|
||||
ESP32 rev v1
|
||||
------------
|
||||
The bugs in this silicon revision introduce a hazard when certain sequences of machine instructions operate on external memory locations (ESP32 ECO 3.2).
|
||||
To work around this, the gcc compiler to compile ESP-IDF has been expanded with a flag: ``-mfix-esp32-psram-cache-issue``. With this flag passed to gcc
|
||||
on the command line, the compiler works around these sequences and only outputs code that can safely be executed.
|
||||
|
||||
In ESP-IDF, this flag is enabled when you select :ref:`CONFIG_SPIRAM_CACHE_WORKAROUND`. ESP-IDF also takes other measures to make
|
||||
sure no combination of PSRAM access plus the offending instruction sets are used: it links to a version of Newlib recompiled with the gcc flag, doesn't use
|
||||
some ROM functions and allocates static memory for the WiFi stack.
|
||||
|
||||
.. _ECO: https://www.espressif.com/sites/default/files/documentation/eco_and_workarounds_for_bugs_in_esp32_en.pdf
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,499 @@
|
||||
ESP-IDF FreeRTOS SMP Changes
|
||||
============================
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
The vanilla FreeRTOS is designed to run on a single core. However the ESP32 is
|
||||
dual core containing a Protocol CPU (known as **CPU 0** or **PRO_CPU**) and an
|
||||
Application CPU (known as **CPU 1** or **APP_CPU**). The two cores are
|
||||
identical in practice and share the same memory. This allows the two cores to
|
||||
run tasks interchangeably between them.
|
||||
|
||||
The ESP-IDF FreeRTOS is a modified version of vanilla FreeRTOS which supports
|
||||
symmetric multiprocessing (SMP). ESP-IDF FreeRTOS is based on the Xtensa port
|
||||
of FreeRTOS v8.2.0. This guide outlines the major differences between vanilla
|
||||
FreeRTOS and ESP-IDF FreeRTOS. The API reference for vanilla FreeRTOS can be
|
||||
found via http://www.freertos.org/a00106.html
|
||||
|
||||
:ref:`backported-features`: Although ESP-IDF FreeRTOS is based on the Xtensa
|
||||
port of FreeRTOS v8.2.0, a number of FreeRTOS v9.0.0 features have been backported
|
||||
to ESP-IDF.
|
||||
|
||||
:ref:`tasks-and-task-creation`: Use :cpp:func:`xTaskCreatePinnedToCore` or
|
||||
:cpp:func:`xTaskCreateStaticPinnedToCore` to create tasks in ESP-IDF FreeRTOS. The
|
||||
last parameter of the two functions is ``xCoreID``. This parameter specifies
|
||||
which core the task is pinned to. Acceptable values are ``0`` for **PRO_CPU**,
|
||||
``1`` for **APP_CPU**, or ``tskNO_AFFINITY`` which allows the task to run on
|
||||
both.
|
||||
|
||||
:ref:`round-robin-scheduling`: The ESP-IDF FreeRTOS scheduler will skip tasks when
|
||||
implementing Round-Robin scheduling between multiple tasks in the Ready state
|
||||
that are of the same priority. To avoid this behavior, ensure that those tasks either
|
||||
enter a blocked state, or are distributed across a wider range of priorities.
|
||||
|
||||
:ref:`scheduler-suspension`: Suspending the scheduler in ESP-IDF FreeRTOS will only
|
||||
affect the scheduler on the the calling core. In other words, calling
|
||||
:cpp:func:`vTaskSuspendAll` on **PRO_CPU** will not prevent **APP_CPU** from scheduling, and
|
||||
vice versa. Use critical sections or semaphores instead for simultaneous
|
||||
access protection.
|
||||
|
||||
:ref:`tick-interrupt-synchronicity`: Tick interrupts of **PRO_CPU** and **APP_CPU**
|
||||
are not synchronized. Do not expect to use :cpp:func:`vTaskDelay` or
|
||||
:cpp:func:`vTaskDelayUntil` as an accurate method of synchronizing task execution
|
||||
between the two cores. Use a counting semaphore instead as their context
|
||||
switches are not tied to tick interrupts due to preemption.
|
||||
|
||||
:ref:`critical-sections`: In ESP-IDF FreeRTOS, critical sections are implemented using
|
||||
mutexes. Entering critical sections involve taking a mutex, then disabling the
|
||||
scheduler and interrupts of the calling core. However the other core is left
|
||||
unaffected. If the other core attemps to take same mutex, it will spin until
|
||||
the calling core has released the mutex by exiting the critical section.
|
||||
|
||||
:ref:`floating-points`: The ESP32 supports hardware acceleration of single
|
||||
precision floating point arithmetic (``float``). However the use of hardware
|
||||
acceleration leads to some behavioral restrictions in ESP-IDF FreeRTOS.
|
||||
Therefore, tasks that utilize ``float`` will automatically be pinned to a core if
|
||||
not done so already. Furthermore, ``float`` cannot be used in interrupt service
|
||||
routines.
|
||||
|
||||
:ref:`task-deletion`: Task deletion behavior has been backported from FreeRTOS
|
||||
v9.0.0 and modified to be SMP compatible. Task memory will be freed immediately
|
||||
when :cpp:func:`vTaskDelete` is called to delete a task that is not currently running
|
||||
and not pinned to the other core. Otherwise, freeing of task memory will still
|
||||
be delegated to the Idle Task.
|
||||
|
||||
:ref:`deletion-callbacks`: ESP-IDF FreeRTOS has backported the Thread Local
|
||||
Storage Pointers (TLSP) feature. However the extra feature of Deletion Callbacks has been
|
||||
added. Deletion callbacks are called automatically during task deletion and are
|
||||
used to free memory pointed to by TLSP. Call
|
||||
:cpp:func:`vTaskSetThreadLocalStoragePointerAndDelCallback()` to set TLSP and Deletion
|
||||
Callbacks.
|
||||
|
||||
:ref:`FreeRTOS Hooks<hooks_api_reference>`: Vanilla FreeRTOS Hooks were not designed for SMP.
|
||||
ESP-IDF provides its own Idle and Tick Hooks in addition to the Vanilla FreeRTOS
|
||||
hooks. For full details, see the ESP-IDF Hooks API Reference.
|
||||
|
||||
:ref:`esp-idf-freertos-configuration`: Several aspects of ESP-IDF FreeRTOS can be
|
||||
configured using ``make meunconfig`` such as running ESP-IDF in Unicore Mode,
|
||||
or configuring the number of Thread Local Storage Pointers each task will have.
|
||||
|
||||
|
||||
.. _backported-features:
|
||||
|
||||
Backported Features
|
||||
-------------------
|
||||
|
||||
The following features have been backported from FreeRTOS v9.0.0 to ESP-IDF.
|
||||
|
||||
Static Alocation
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
This feature has been backported from FreeRTOS v9.0.0 to ESP-IDF. The
|
||||
:ref:`CONFIG_SUPPORT_STATIC_ALLOCATION` option must be enabled in `menuconfig`
|
||||
in order for static allocation functions to be available. Once enabled, the
|
||||
following functions can be called...
|
||||
|
||||
- :cpp:func:`xTaskCreateStatic` (see :ref:`backporting-notes` below)
|
||||
- :c:macro:`xQueueCreateStatic`
|
||||
- :c:macro:`xSemaphoreCreateBinaryStatic`
|
||||
- :c:macro:`xSemaphoreCreateCountingStatic`
|
||||
- :c:macro:`xSemaphoreCreateMutexStatic`
|
||||
- :c:macro:`xSemaphoreCreateRecursiveMutexStatic`
|
||||
- :cpp:func:`xTimerCreateStatic` (see :ref:`backporting-notes` below)
|
||||
- :cpp:func:`xEventGroupCreateStatic`
|
||||
|
||||
Other Features
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
- :cpp:func:`vTaskSetThreadLocalStoragePointer` (see :ref:`backporting-notes` below)
|
||||
- :cpp:func:`pvTaskGetThreadLocalStoragePointer` (see :ref:`backporting-notes` below)
|
||||
- :cpp:func:`vTimerSetTimerID`
|
||||
- :cpp:func:`xTimerGetPeriod`
|
||||
- :cpp:func:`xTimerGetExpiryTime`
|
||||
- :cpp:func:`pcQueueGetName`
|
||||
- :c:macro:`uxSemaphoreGetCount`
|
||||
|
||||
.. _backporting-notes:
|
||||
|
||||
Backporting Notes
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
**1)** :cpp:func:`xTaskCreateStatic` has been made SMP compatible in a similar
|
||||
fashion to :cpp:func:`xTaskCreate` (see :ref:`tasks-and-task-creation`). Therefore
|
||||
:cpp:func:`xTaskCreateStaticPinnedToCore` can also be called.
|
||||
|
||||
**2)** Although vanilla FreeRTOS allows the Timer feature's daemon task to
|
||||
be statically allocated, the daemon task is always dynamically allocated in
|
||||
ESP-IDF. Therefore ``vApplicationGetTimerTaskMemory`` **does not** need to be
|
||||
defined when using statically allocated timers in ESP-IDF FreeRTOS.
|
||||
|
||||
**3)** The Thread Local Storage Pointer feature has been modified in ESP-IDF
|
||||
FreeRTOS to include Deletion Callbacks (see :ref:`deletion-callbacks`). Therefore
|
||||
the function :cpp:func:`vTaskSetThreadLocalStoragePointerAndDelCallback` can also be
|
||||
called.
|
||||
|
||||
|
||||
.. _tasks-and-task-creation:
|
||||
|
||||
Tasks and Task Creation
|
||||
-----------------------
|
||||
|
||||
Tasks in ESP-IDF FreeRTOS are designed to run on a particular core, therefore
|
||||
two new task creation functions have been added to ESP-IDF FreeRTOS by
|
||||
appending ``PinnedToCore`` to the names of the task creation functions in
|
||||
vanilla FreeRTOS. The vanilla FreeRTOS functions of :cpp:func:`xTaskCreate`
|
||||
and :cpp:func:`xTaskCreateStatic` have led to the addition of
|
||||
:cpp:func:`xTaskCreatePinnedToCore` and :cpp:func:`xTaskCreateStaticPinnedToCore` in
|
||||
ESP-IDF FreeRTOS (see :ref:`backported-features`).
|
||||
|
||||
For more details see :component_file:`freertos/task.c`
|
||||
|
||||
The ESP-IDF FreeRTOS task creation functions are nearly identical to their
|
||||
vanilla counterparts with the exception of the extra parameter known as
|
||||
``xCoreID``. This parameter specifies the core on which the task should run on
|
||||
and can be one of the following values.
|
||||
|
||||
- ``0`` pins the task to **PRO_CPU**
|
||||
- ``1`` pins the task to **APP_CPU**
|
||||
- ``tskNO_AFFINITY`` allows the task to be run on both CPUs
|
||||
|
||||
For example ``xTaskCreatePinnedToCore(tsk_callback, “APP_CPU Task”, 1000, NULL, 10, NULL, 1)``
|
||||
creates a task of priority 10 that is pinned to **APP_CPU** with a stack size
|
||||
of 1000 bytes. It should be noted that the ``uxStackDepth`` parameter in
|
||||
vanilla FreeRTOS specifies a task’s stack depth in terms of the number of
|
||||
words, whereas ESP-IDF FreeRTOS specifies the stack depth in terms of bytes.
|
||||
|
||||
Note that the vanilla FreeRTOS functions :cpp:func:`xTaskCreate` and
|
||||
:cpp:func:`xTaskCreateStatic` have been defined in ESP-IDF FreeRTOS as inline functions which call
|
||||
:cpp:func:`xTaskCreatePinnedToCore` and :cpp:func:`xTaskCreateStaticPinnedToCore`
|
||||
respectively with ``tskNO_AFFINITY`` as the ``xCoreID`` value.
|
||||
|
||||
Each Task Control Block (TCB) in ESP-IDF stores the ``xCoreID`` as a member.
|
||||
Hence when each core calls the scheduler to select a task to run, the
|
||||
``xCoreID`` member will allow the scheduler to determine if a given task is
|
||||
permitted to run on the core that called it.
|
||||
|
||||
Scheduling
|
||||
----------
|
||||
|
||||
The vanilla FreeRTOS implements scheduling in the ``vTaskSwitchContext()``
|
||||
function. This function is responsible for selecting the highest priority task
|
||||
to run from a list of tasks in the Ready state known as the Ready Tasks List
|
||||
(described in the next section). In ESP-IDF FreeRTOS, each core will call
|
||||
``vTaskSwitchContext()`` independently to select a task to run from the
|
||||
Ready Tasks List which is shared between both cores. There are several
|
||||
differences in scheduling behavior between vanilla and ESP-IDF FreeRTOS such as
|
||||
differences in Round Robin scheduling, scheduler suspension, and tick interrupt
|
||||
synchronicity.
|
||||
|
||||
.. _round-robin-scheduling:
|
||||
|
||||
Round Robin Scheduling
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Given multiple tasks in the Ready state and of the same priority, vanilla
|
||||
FreeRTOS implements Round Robin scheduling between each task. This will result
|
||||
in running those tasks in turn each time the scheduler is called
|
||||
(e.g. every tick interrupt). On the other hand, the ESP-IDF FreeRTOS scheduler
|
||||
may skip tasks when Round Robin scheduling multiple Ready state tasks of the
|
||||
same priority.
|
||||
|
||||
The issue of skipping tasks during Round Robin scheduling arises from the way
|
||||
the Ready Tasks List is implemented in FreeRTOS. In vanilla FreeRTOS,
|
||||
``pxReadyTasksList`` is used to store a list of tasks that are in the Ready
|
||||
state. The list is implemented as an array of length ``configMAX_PRIORITIES``
|
||||
where each element of the array is a linked list. Each linked list is of type
|
||||
``List_t`` and contains TCBs of tasks of the same priority that are in the
|
||||
Ready state. The following diagram illustrates the ``pxReadyTasksList``
|
||||
structure.
|
||||
|
||||
.. figure:: ../_static/freertos-ready-task-list.png
|
||||
:align: center
|
||||
:alt: Vanilla FreeRTOS Ready Task List Structure
|
||||
|
||||
Illustration of FreeRTOS Ready Task List Data Structure
|
||||
|
||||
|
||||
Each linked list also contains a ``pxIndex`` which points to the last TCB
|
||||
returned when the list was queried. This index allows the ``vTaskSwitchContext()``
|
||||
to start traversing the list at the TCB immediately after ``pxIndex`` hence
|
||||
implementing Round Robin Scheduling between tasks of the same priority.
|
||||
|
||||
In ESP-IDF FreeRTOS, the Ready Tasks List is shared between cores hence
|
||||
``pxReadyTasksList`` will contain tasks pinned to different cores. When a core
|
||||
calls the scheduler, it is able to look at the ``xCoreID`` member of each TCB
|
||||
in the list to determine if a task is allowed to run on calling the core. The
|
||||
ESP-IDF FreeRTOS ``pxReadyTasksList`` is illustrated below.
|
||||
|
||||
.. figure:: ../_static/freertos-ready-task-list-smp.png
|
||||
:align: center
|
||||
:alt: ESP-IDF FreeRTOS Ready Task List Structure
|
||||
|
||||
Illustration of FreeRTOS Ready Task List Data Structure in ESP-IDF
|
||||
|
||||
Therefore when **PRO_CPU** calls the scheduler, it will only consider the tasks
|
||||
in blue or purple. Whereas when **APP_CPU** calls the scheduler, it will only
|
||||
consider the tasks in orange or purple.
|
||||
|
||||
Although each TCB has an ``xCoreID`` in ESP-IDF FreeRTOS, the linked list of
|
||||
each priority only has a single ``pxIndex``. Therefore when the scheduler is
|
||||
called from a particular core and traverses the linked list, it will skip all
|
||||
TCBs pinned to the other core and point the pxIndex at the selected task. If
|
||||
the other core then calls the scheduler, it will traverse the linked list
|
||||
starting at the TCB immediately after ``pxIndex``. Therefore, TCBs skipped on
|
||||
the previous scheduler call from the other core would not be considered on the
|
||||
current scheduler call. This issue is demonstrated in the following
|
||||
illustration.
|
||||
|
||||
.. figure:: ../_static/freertos-ready-task-list-smp-pxIndex.png
|
||||
:align: center
|
||||
:alt: ESP-IDF pxIndex Behavior
|
||||
|
||||
Illustration of pxIndex behavior in ESP-IDF FreeRTOS
|
||||
|
||||
Referring to the illustration above, assume that priority 9 is the highest
|
||||
priority, and none of the tasks in priority 9 will block hence will always be
|
||||
either in the running or Ready state.
|
||||
|
||||
1) **PRO_CPU** calls the scheduler and selects Task A to run, hence moves
|
||||
``pxIndex`` to point to Task A
|
||||
|
||||
2) **APP_CPU** calls the scheduler and starts traversing from the task after
|
||||
``pxIndex`` which is Task B. However Task B is not selected to run as it is not
|
||||
pinned to **APP_CPU** hence it is skipped and Task C is selected instead.
|
||||
``pxIndex`` now points to Task C
|
||||
|
||||
3) **PRO_CPU** calls the scheduler and starts traversing from Task D. It skips
|
||||
Task D and selects Task E to run and points ``pxIndex`` to Task E. Notice that
|
||||
Task B isn’t traversed because it was skipped the last time **APP_CPU** called
|
||||
the scheduler to traverse the list.
|
||||
|
||||
4) The same situation with Task D will occur if **APP_CPU** calls the
|
||||
scheduler again as ``pxIndex`` now points to Task E
|
||||
|
||||
One solution to the issue of task skipping is to ensure that every task will
|
||||
enter a blocked state so that they are removed from the Ready Task List.
|
||||
Another solution is to distribute tasks across multiple priorities such that
|
||||
a given priority will not be assigned multiple tasks that are pinned to
|
||||
different cores.
|
||||
|
||||
.. _scheduler-suspension:
|
||||
|
||||
Scheduler Suspension
|
||||
^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In vanilla FreeRTOS, suspending the scheduler via :cpp:func:`vTaskSuspendAll` will
|
||||
prevent calls of ``vTaskSwitchContext`` from context switching until the
|
||||
scheduler has been resumed with :cpp:func:`xTaskResumeAll`. However servicing ISRs
|
||||
are still permitted. Therefore any changes in task states as a result from the
|
||||
current running task or ISRSs will not be executed until the scheduler is
|
||||
resumed. Scheduler suspension in vanilla FreeRTOS is a common protection method
|
||||
against simultaneous access of data shared between tasks, whilst still allowing
|
||||
ISRs to be serviced.
|
||||
|
||||
In ESP-IDF FreeRTOS, :cpp:func:`xTaskResumeAll` will only prevent calls of
|
||||
``vTaskSwitchContext()`` from switching contexts on the core that called for the
|
||||
suspension. Hence if **PRO_CPU** calls :cpp:func:`vTaskSuspendAll`, **APP_CPU** will
|
||||
still be able to switch contexts. If data is shared between tasks that are
|
||||
pinned to different cores, scheduler suspension is **NOT** a valid method of
|
||||
protection against simultaneous access. Consider using critical sections
|
||||
(disables interrupts) or semaphores (does not disable interrupts) instead when
|
||||
protecting shared resources in ESP-IDF FreeRTOS.
|
||||
|
||||
In general, it's better to use other RTOS primitives like mutex semaphores to protect
|
||||
against data shared between tasks, rather than :cpp:func:`vTaskSuspendAll`.
|
||||
|
||||
|
||||
.. _tick-interrupt-synchronicity:
|
||||
|
||||
Tick Interrupt Synchronicity
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In ESP-IDF FreeRTOS, tasks on different cores that unblock on the same tick
|
||||
count might not run at exactly the same time due to the scheduler calls from
|
||||
each core being independent, and the tick interrupts to each core being
|
||||
unsynchronized.
|
||||
|
||||
In vanilla FreeRTOS the tick interrupt triggers a call to
|
||||
:cpp:func:`xTaskIncrementTick` which is responsible for incrementing the tick
|
||||
counter, checking if tasks which have called :cpp:func:`vTaskDelay` have fulfilled
|
||||
their delay period, and moving those tasks from the Delayed Task List to the
|
||||
Ready Task List. The tick interrupt will then call the scheduler if a context
|
||||
switch is necessary.
|
||||
|
||||
In ESP-IDF FreeRTOS, delayed tasks are unblocked with reference to the tick
|
||||
interrupt on PRO_CPU as PRO_CPU is responsible for incrementing the shared tick
|
||||
count. However tick interrupts to each core might not be synchronized (same
|
||||
frequency but out of phase) hence when PRO_CPU receives a tick interrupt,
|
||||
APP_CPU might not have received it yet. Therefore if multiple tasks of the same
|
||||
priority are unblocked on the same tick count, the task pinned to PRO_CPU will
|
||||
run immediately whereas the task pinned to APP_CPU must wait until APP_CPU
|
||||
receives its out of sync tick interrupt. Upon receiving the tick interrupt,
|
||||
APP_CPU will then call for a context switch and finally switches contexts to
|
||||
the newly unblocked task.
|
||||
|
||||
Therefore, task delays should **NOT** be used as a method of synchronization
|
||||
between tasks in ESP-IDF FreeRTOS. Instead, consider using a counting semaphore
|
||||
to unblock multiple tasks at the same time.
|
||||
|
||||
|
||||
.. _critical-sections:
|
||||
|
||||
Critical Sections & Disabling Interrupts
|
||||
----------------------------------------
|
||||
|
||||
Vanilla FreeRTOS implements critical sections in ``vTaskEnterCritical`` which
|
||||
disables the scheduler and calls ``portDISABLE_INTERRUPTS``. This prevents
|
||||
context switches and servicing of ISRs during a critical section. Therefore,
|
||||
critical sections are used as a valid protection method against simultaneous
|
||||
access in vanilla FreeRTOS.
|
||||
|
||||
On the other hand, the ESP32 has no hardware method for cores to disable each
|
||||
other’s interrupts. Calling ``portDISABLE_INTERRUPTS()`` will have no effect on
|
||||
the interrupts of the other core. Therefore, disabling interrupts is **NOT**
|
||||
a valid protection method against simultaneous access to shared data as it
|
||||
leaves the other core free to access the data even if the current core has
|
||||
disabled its own interrupts.
|
||||
|
||||
For this reason, ESP-IDF FreeRTOS implements critical sections using mutexes,
|
||||
and calls to enter or exit a critical must provide a mutex that is associated
|
||||
with a shared resource requiring access protection. When entering a critical
|
||||
section in ESP-IDF FreeRTOS, the calling core will disable its scheduler and
|
||||
interrupts similar to the vanilla FreeRTOS implementation. However, the calling
|
||||
core will also take the mutex whilst the other core is left unaffected during
|
||||
the critical section. If the other core attempts to take the same mutex, it
|
||||
will spin until the mutex is released. Therefore, the ESP-IDF FreeRTOS
|
||||
implementation of critical sections allows a core to have protected access to a
|
||||
shared resource without disabling the other core. The other core will only be
|
||||
affected if it tries to concurrently access the same resource.
|
||||
|
||||
The ESP-IDF FreeRTOS critical section functions have been modified as follows…
|
||||
|
||||
- ``taskENTER_CRITICAL(mux)``, ``taskENTER_CRITICAL_ISR(mux)``,
|
||||
``portENTER_CRITICAL(mux)``, ``portENTER_CRITICAL_ISR(mux)`` are all macro
|
||||
defined to call :cpp:func:`vTaskEnterCritical`
|
||||
|
||||
- ``taskEXIT_CRITICAL(mux)``, ``taskEXIT_CRITICAL_ISR(mux)``,
|
||||
``portEXIT_CRITICAL(mux)``, ``portEXIT_CRITICAL_ISR(mux)`` are all macro
|
||||
defined to call :cpp:func:`vTaskExitCritical`
|
||||
|
||||
For more details see :component_file:`freertos/include/freertos/portmacro.h`
|
||||
and :component_file:`freertos/task.c`
|
||||
|
||||
It should be noted that when modifying vanilla FreeRTOS code to be ESP-IDF
|
||||
FreeRTOS compatible, it is trivial to modify the type of critical section
|
||||
called as they are all defined to call the same function. As long as the same
|
||||
mutex is provided upon entering and exiting, the type of call should not
|
||||
matter.
|
||||
|
||||
|
||||
.. _floating-points:
|
||||
|
||||
Floating Point Aritmetic
|
||||
------------------------
|
||||
|
||||
The ESP32 supports hardware acceleration of single precision floating point
|
||||
arithmetic (``float``) via Floating Point Units (FPU, also known as coprocessors)
|
||||
attached to each core. The use of the FPUs imposes some behavioral restrictions
|
||||
on ESP-IDF FreeRTOS.
|
||||
|
||||
ESP-IDF FreeRTOS implements Lazy Context Switching for FPUs. In other words,
|
||||
the state of a core's FPU registers are not immediately saved when a context
|
||||
switch occurs. Therefore, tasks that utilize ``float`` must be pinned to a
|
||||
particular core upon creation. If not, ESP-IDF FreeRTOS will automatically pin
|
||||
the task in question to whichever core the task was running on upon the task's
|
||||
first use of ``float``. Likewise due to Lazy Context Switching, interrupt service
|
||||
routines must also not use ``float``.
|
||||
|
||||
ESP32 does not support hardware acceleration for double precision floating point
|
||||
arithmetic (``double``). Instead ``double`` is implemented via software hence the
|
||||
behavioral restrictions with regards to ``float`` do not apply to ``double``. Note
|
||||
that due to the lack of hardware acceleration, ``double`` operations may consume
|
||||
significantly larger amount of CPU time in comparison to ``float``.
|
||||
|
||||
|
||||
.. _task-deletion:
|
||||
|
||||
Task Deletion
|
||||
-------------
|
||||
|
||||
FreeRTOS task deletion prior to v9.0.0 delegated the freeing of task memory
|
||||
entirely to the Idle Task. Currently, the freeing of task memory will occur
|
||||
immediately (within :cpp:func:`vTaskDelete`) if the task being deleted is not currently
|
||||
running or is not pinned to the other core (with respect to the core
|
||||
:cpp:func:`vTaskDelete` is called on). TLSP deletion callbacks will also run immediately
|
||||
if the same conditions are met.
|
||||
|
||||
However, calling :cpp:func:`vTaskDelete` to delete a task that is either currently
|
||||
running or pinned to the other core will still result in the freeing of memory
|
||||
being delegated to the Idle Task.
|
||||
|
||||
|
||||
.. _deletion-callbacks:
|
||||
|
||||
Thread Local Storage Pointers & Deletion Callbacks
|
||||
--------------------------------------------------
|
||||
|
||||
Thread Local Storage Pointers (TLSP) are pointers stored directly in the TCB.
|
||||
TLSP allow each task to have its own unique set of pointers to data structures.
|
||||
However task deletion behavior in vanilla FreeRTOS does not automatically
|
||||
free the memory pointed to by TLSP. Therefore if the memory pointed to by
|
||||
TLSP is not explicitly freed by the user before task deletion, memory leak will
|
||||
occur.
|
||||
|
||||
ESP-IDF FreeRTOS provides the added feature of Deletion Callbacks. Deletion
|
||||
Callbacks are called automatically during task deletion to free memory pointed
|
||||
to by TLSP. Each TLSP can have its own Deletion Callback. Note that due to the
|
||||
to :ref:`task-deletion` behavior, there can be instances where Deletion
|
||||
Callbacks are called in the context of the Idle Tasks. Therefore Deletion
|
||||
Callbacks **should never attempt to block** and critical sections should be kept
|
||||
as short as possible to minimize priority inversion.
|
||||
|
||||
Deletion callbacks are of type
|
||||
``void (*TlsDeleteCallbackFunction_t)( int, void * )`` where the first parameter
|
||||
is the index number of the associated TLSP, and the second parameter is the
|
||||
TLSP itself.
|
||||
|
||||
Deletion callbacks are set alongside TLSP by calling
|
||||
:cpp:func:`vTaskSetThreadLocalStoragePointerAndDelCallback`. Calling the vanilla
|
||||
FreeRTOS function :cpp:func:`vTaskSetThreadLocalStoragePointer` will simply set the
|
||||
TLSP's associated Deletion Callback to `NULL` meaning that no callback will be
|
||||
called for that TLSP during task deletion. If a deletion callback is `NULL`,
|
||||
users should manually free the memory pointed to by the associated TLSP before
|
||||
task deletion in order to avoid memory leak.
|
||||
|
||||
:ref:`CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS` in menuconfig can be used
|
||||
to configure the number TLSP and Deletion Callbacks a TCB will have.
|
||||
|
||||
For more details see :doc:`FreeRTOS API reference<../api-reference/system/freertos>`.
|
||||
|
||||
|
||||
.. _esp-idf-freertos-configuration:
|
||||
|
||||
Configuring ESP-IDF FreeRTOS
|
||||
----------------------------
|
||||
|
||||
The ESP-IDF FreeRTOS can be configured using ``make menuconfig`` under
|
||||
``Component_Config/FreeRTOS``. The following section highlights some of the
|
||||
ESP-IDF FreeRTOS configuration options. For a full list of ESP-IDF
|
||||
FreeRTOS configurations, see :doc:`FreeRTOS <../api-reference/kconfig>`
|
||||
|
||||
:ref:`CONFIG_FREERTOS_UNICORE` will run ESP-IDF FreeRTOS only
|
||||
on **PRO_CPU**. Note that this is **not equivalent to running vanilla
|
||||
FreeRTOS**. Behaviors of multiple components in ESP-IDF will be modified such
|
||||
as :component_file:`esp32/cpu_start.c`. For more details regarding the
|
||||
effects of running ESP-IDF FreeRTOS on a single core, search for
|
||||
occurences of ``CONFIG_FREERTOS_UNICORE`` in the ESP-IDF components.
|
||||
|
||||
:ref:`CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS` will define the
|
||||
number of Thread Local Storage Pointers each task will have in ESP-IDF
|
||||
FreeRTOS.
|
||||
|
||||
:ref:`CONFIG_SUPPORT_STATIC_ALLOCATION` will enable the backported
|
||||
functionality of :cpp:func:`xTaskCreateStaticPinnedToCore` in ESP-IDF FreeRTOS
|
||||
|
||||
:ref:`CONFIG_FREERTOS_ASSERT_ON_UNTESTED_FUNCTION` will trigger a halt in
|
||||
particular functions in ESP-IDF FreeRTOS which have not been fully tested
|
||||
in an SMP context.
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
General Notes About ESP-IDF Programming
|
||||
=======================================
|
||||
|
||||
Application startup flow
|
||||
------------------------
|
||||
|
||||
This note explains various steps which happen before ``app_main`` function of an ESP-IDF application is called.
|
||||
|
||||
The high level view of startup process is as follows:
|
||||
|
||||
1. First-stage bootloader in ROM loads second-stage bootloader image to RAM (IRAM & DRAM) from flash offset 0x1000.
|
||||
2. Second-stage bootloader loads partition table and main app image from flash. Main app incorporates both RAM segments and read-only segments mapped via flash cache.
|
||||
3. Main app image executes. At this point the second CPU and RTOS scheduler can be started.
|
||||
|
||||
This process is explained in detail in the following sections.
|
||||
|
||||
First stage bootloader
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
After SoC reset, PRO CPU will start running immediately, executing reset vector code, while APP CPU will be held in reset. During startup process, PRO CPU does all the initialization. APP CPU reset is de-asserted in the ``call_start_cpu0`` function of application startup code. Reset vector code is located at address 0x40000400 in the mask ROM of the ESP32 chip and can not be modified.
|
||||
|
||||
Startup code called from the reset vector determines the boot mode by checking ``GPIO_STRAP_REG`` register for bootstrap pin states. Depending on the reset reason, the following takes place:
|
||||
|
||||
1. Reset from deep sleep: if the value in ``RTC_CNTL_STORE6_REG`` is non-zero, and CRC value of RTC memory in ``RTC_CNTL_STORE7_REG`` is valid, use ``RTC_CNTL_STORE6_REG`` as an entry point address and jump immediately to it. If ``RTC_CNTL_STORE6_REG`` is zero, or ``RTC_CNTL_STORE7_REG`` contains invalid CRC, or once the code called via ``RTC_CNTL_STORE6_REG`` returns, proceed with boot as if it was a power-on reset. **Note**: to run customized code at this point, a deep sleep stub mechanism is provided. Please see :doc:`deep sleep <deep-sleep-stub>` documentation for this.
|
||||
|
||||
2. For power-on reset, software SOC reset, and watchdog SOC reset: check the ``GPIO_STRAP_REG`` register if UART or SDIO download mode is requested. If this is the case, configure UART or SDIO, and wait for code to be downloaded. Otherwise, proceed with boot as if it was due to software CPU reset.
|
||||
|
||||
3. For software CPU reset and watchdog CPU reset: configure SPI flash based on EFUSE values, and attempt to load the code from flash. This step is described in more detail in the next paragraphs. If loading code from flash fails, unpack BASIC interpreter into the RAM and start it. Note that RTC watchdog is still enabled when this happens, so unless any input is received by the interpreter, watchdog will reset the SOC in a few hundred milliseconds, repeating the whole process. If the interpreter receives any input from the UART, it disables the watchdog.
|
||||
|
||||
Application binary image is loaded from flash starting at address 0x1000. First 4kB sector of flash is used to store secure boot IV and signature of the application image. Please check secure boot documentation for details about this.
|
||||
|
||||
.. TODO: describe application binary image format, describe optional flash configuration commands.
|
||||
|
||||
Second stage bootloader
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In ESP-IDF, the binary image which resides at offset 0x1000 in flash is the second stage bootloader. Second stage bootloader source code is available in components/bootloader directory of ESP-IDF. Note that this arrangement is not the only one possible with the ESP32 chip. It is possible to write a fully featured application which would work when flashed to offset 0x1000, but this is out of scope of this document. Second stage bootloader is used in ESP-IDF to add flexibility to flash layout (using partition tables), and allow for various flows associated with flash encryption, secure boot, and over-the-air updates (OTA) to take place.
|
||||
|
||||
When the first stage bootloader is finished checking and loading the second stage bootloader, it jumps to the second stage bootloader entry point found in the binary image header.
|
||||
|
||||
Second stage bootloader reads the partition table found at offset 0x8000. See :doc:`partition tables <partition-tables>` documentation for more information. The bootloader finds factory and OTA partitions, and decides which one to boot based on data found in *OTA info* partition.
|
||||
|
||||
For the selected partition, second stage bootloader copies data and code sections which are mapped into IRAM and DRAM to their load addresses. For sections which have load addresses in DROM and IROM regions, flash MMU is configured to provide the correct mapping. Note that the second stage bootloader configures flash MMU for both PRO and APP CPUs, but it only enables flash MMU for PRO CPU. Reason for this is that second stage bootloader code is loaded into the memory region used by APP CPU cache. The duty of enabling cache for APP CPU is passed on to the application. Once code is loaded and flash MMU is set up, second stage bootloader jumps to the application entry point found in the binary image header.
|
||||
|
||||
Currently it is not possible to add application-defined hooks to the bootloader to customize application partition selection logic. This may be required to load different application image depending on a state of a GPIO, for example. Such customization features will be added to ESP-IDF in the future. For now, bootloader can be customized by copying bootloader component into application directory and making necessary changes there. ESP-IDF build system will compile the component in application directory instead of ESP-IDF components directory in this case.
|
||||
|
||||
Application startup
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
ESP-IDF application entry point is ``call_start_cpu0`` function found in ``components/esp32/cpu_start.c``. Two main things this function does are to enable heap allocator and to make APP CPU jump to its entry point, ``call_start_cpu1``. The code on PRO CPU sets the entry point for APP CPU, de-asserts APP CPU reset, and waits for a global flag to be set by the code running on APP CPU, indicating that it has started. Once this is done, PRO CPU jumps to ``start_cpu0`` function, and APP CPU jumps to ``start_cpu1`` function.
|
||||
|
||||
Both ``start_cpu0`` and ``start_cpu1`` are weak functions, meaning that they can be overridden in the application, if some application-specific change to initialization sequence is needed. Default implementation of ``start_cpu0`` enables or initializes components depending on choices made in ``menuconfig``. Please see source code of this function in ``components/esp32/cpu_start.c`` for an up to date list of steps performed. Note that any C++ global constructors present in the application will be called at this stage. Once all essential components are initialized, *main task* is created and FreeRTOS scheduler is started.
|
||||
|
||||
While PRO CPU does initialization in ``start_cpu0`` function, APP CPU spins in ``start_cpu1`` function, waiting for the scheduler to be started on the PRO CPU. Once the scheduler is started on the PRO CPU, code on the APP CPU starts the scheduler as well.
|
||||
|
||||
Main task is the task which runs ``app_main`` function. Main task stack size and priority can be configured in ``menuconfig``. Application can use this task for initial application-specific setup, for example to launch other tasks. Application can also use main task for event loops and other general purpose activities. If ``app_main`` function returns, main task is deleted.
|
||||
|
||||
.. _memory-layout:
|
||||
|
||||
Application memory layout
|
||||
-------------------------
|
||||
|
||||
ESP32 chip has flexible memory mapping features. This section describes how ESP-IDF uses these features by default.
|
||||
|
||||
Application code in ESP-IDF can be placed into one of the following memory regions.
|
||||
|
||||
IRAM (instruction RAM)
|
||||
^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
ESP-IDF allocates part of `Internal SRAM0` region (defined in the Technical Reference Manual) for instruction RAM. Except for the first 64 kB block which is used for PRO and APP CPU caches, the rest of this memory range (i.e. from ``0x40080000`` to ``0x400A0000``) is used to store parts of application which need to run from RAM.
|
||||
|
||||
A few components of ESP-IDF and parts of WiFi stack are placed into this region using the linker script.
|
||||
|
||||
If some application code needs to be placed into IRAM, it can be done using ``IRAM_ATTR`` define::
|
||||
|
||||
#include "esp_attr.h"
|
||||
|
||||
void IRAM_ATTR gpio_isr_handler(void* arg)
|
||||
{
|
||||
// ...
|
||||
}
|
||||
|
||||
Here are the cases when parts of application may or should be placed into IRAM.
|
||||
|
||||
- Interrupt handlers must be placed into IRAM if ``ESP_INTR_FLAG_IRAM`` is used when registering the interrupt handler. In this case, ISR may only call functions placed into IRAM or functions present in ROM. *Note 1:* all FreeRTOS APIs are currently placed into IRAM, so are safe to call from interrupt handlers. If the ISR is placed into IRAM, all constant data used by the ISR and functions called from ISR (including, but not limited to, ``const char`` arrays), must be placed into DRAM using ``DRAM_ATTR``.
|
||||
|
||||
- Some timing critical code may be placed into IRAM to reduce the penalty associated with loading the code from flash. ESP32 reads code and data from flash via a 32 kB cache. In some cases, placing a function into IRAM may reduce delays caused by a cache miss.
|
||||
|
||||
IROM (code executed from Flash)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
If a function is not explicitly placed into IRAM or RTC memory, it is placed into flash. The mechanism by which Flash MMU is used to allow code execution from flash is described in the Technical Reference Manual. ESP-IDF places the code which should be executed from flash starting from the beginning of ``0x400D0000 — 0x40400000`` region. Upon startup, second stage bootloader initializes Flash MMU to map the location in flash where code is located into the beginning of this region. Access to this region is transparently cached using two 32kB blocks in ``0x40070000`` — ``0x40080000`` range.
|
||||
|
||||
Note that the code outside ``0x40000000 — 0x40400000`` region may not be reachable with Window ABI ``CALLx`` instructions, so special care is required if ``0x40400000 — 0x40800000`` or ``0x40800000 — 0x40C00000`` regions are used by the application. ESP-IDF doesn't use these regions by default.
|
||||
|
||||
RTC fast memory
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
The code which has to run after wake-up from deep sleep mode has to be placed into RTC memory. Please check detailed description in :doc:`deep sleep <deep-sleep-stub>` documentation.
|
||||
|
||||
DRAM (data RAM)
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Non-constant static data and zero-initialized data is placed by the linker into the 256 kB ``0x3FFB0000 — 0x3FFF0000`` region. Note that this region is reduced by 64kB (by shifting start address to ``0x3FFC0000``) if Bluetooth stack is used. Length of this region is also reduced by 16 kB or 32kB if trace memory is used. All space which is left in this region after placing static data there is used for the runtime heap.
|
||||
|
||||
Constant data may also be placed into DRAM, for example if it is used in an ISR (see notes in IRAM section above). To do that, ``DRAM_ATTR`` define can be used::
|
||||
|
||||
DRAM_ATTR const char[] format_string = "%p %x";
|
||||
char buffer[64];
|
||||
sprintf(buffer, format_string, ptr, val);
|
||||
|
||||
Needless to say, it is not advised to use ``printf`` and other output functions in ISRs. For debugging purposes, use ``ESP_EARLY_LOGx`` macros when logging from ISRs. Make sure that both ``TAG`` and format string are placed into ``DRAM`` in that case.
|
||||
|
||||
DROM (data stored in Flash)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
By default, constant data is placed by the linker into a 4 MB region (``0x3F400000 — 0x3F800000``) which is used to access external flash memory via Flash MMU and cache. Exceptions to this are literal constants which are embedded by the compiler into application code.
|
||||
|
||||
RTC slow memory
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Global and static variables used by code which runs from RTC memory (i.e. deep sleep stub code) must be placed into RTC slow memory. Please check detailed description in :doc:`deep sleep <deep-sleep-stub>` documentation.
|
||||
|
||||
DMA Capable Requirement
|
||||
-----------------------
|
||||
|
||||
Most DMA controllers (e.g. SPI, sdmmc, etc.) have requirements that sending/receiving buffers should be placed in DRAM
|
||||
and word-aligned. We suggest to place DMA buffers in static variables rather than in the stack. Use macro ``DMA_ATTR``
|
||||
to declare global/local static variables like::
|
||||
|
||||
DMA_ATTR uint8_t buffer[]="I want to send something";
|
||||
|
||||
void app_main()
|
||||
{
|
||||
// initialization code...
|
||||
spi_transaction_t temp = {
|
||||
.tx_buffer = buffer,
|
||||
.length = 8*sizeof(buffer),
|
||||
};
|
||||
spi_device_transmit( spi, &temp );
|
||||
// other stuff
|
||||
}
|
||||
|
||||
Or::
|
||||
|
||||
void app_main()
|
||||
{
|
||||
DMA_ATTR static uint8_t buffer[]="I want to send something";
|
||||
// initialization code...
|
||||
spi_transaction_t temp = {
|
||||
.tx_buffer = buffer,
|
||||
.length = 8*sizeof(buffer),
|
||||
};
|
||||
spi_device_transmit( spi, &temp );
|
||||
// other stuff
|
||||
}
|
||||
|
||||
Placing DMA buffers in the stack is still allowed, though you have to keep in mind:
|
||||
|
||||
1. Never try to do this if the stack is in the pSRAM. If the stack of a task is placed in the pSRAM, several steps have
|
||||
to be taken as described in :doc:`external-ram` (at least ``SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY`` option enabled in
|
||||
the menuconfig). Make sure your task is not in the pSRAM.
|
||||
2. Use macro ``WORD_ALIGNED_ATTR`` in functions before variables to place them in proper positions like::
|
||||
|
||||
void app_main()
|
||||
{
|
||||
uint8_t stuff;
|
||||
WORD_ALIGNED_ATTR uint8_t buffer[]="I want to send something"; //or the buffer will be placed right after stuff.
|
||||
// initialization code...
|
||||
spi_transaction_t temp = {
|
||||
.tx_buffer = buffer,
|
||||
.length = 8*sizeof(buffer),
|
||||
};
|
||||
spi_device_transmit( spi, &temp );
|
||||
// other stuff
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
High-Level Interrupts
|
||||
=====================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
The Xtensa architecture has support for 32 interrupts, divided over 8 levels, plus an assortment of exceptions. On the ESP32, the interrupt mux allows most interrupt sources to be routed to these interrupts using the :doc:`interrupt allocator <../api-reference/system/intr_alloc>`. Normally, interrupts will be written in C, but ESP-IDF allows high-level interrupts to be written in assembly as well, allowing for very low interrupt latencies.
|
||||
|
||||
Interrupt Levels
|
||||
----------------
|
||||
|
||||
===== ================= ====================================================
|
||||
Level Symbol Remark
|
||||
===== ================= ====================================================
|
||||
1 N/A Exception and level 0 interrupts. Handled by ESP-IDF
|
||||
2-3 N/A Medium level interrupts. Handled by ESP-IDF
|
||||
4 xt_highint4 Normally used by ESP-IDF debug logic
|
||||
5 xt_highint5 Free to use
|
||||
NMI xt_nmi Free to use
|
||||
dbg xt_debugexception Debug exception. Called on e.g. a BREAK instruction.
|
||||
===== ================= ====================================================
|
||||
|
||||
Using these symbols is done by creating an assembly file (suffix .S) and defining the named symbols, like this::
|
||||
|
||||
.section .iram1,"ax"
|
||||
.global xt_highint5
|
||||
.type xt_highint5,@function
|
||||
.align 4
|
||||
xt_highint5:
|
||||
... your code here
|
||||
rsr a0, EXCSAVE_5
|
||||
rfi 5
|
||||
|
||||
|
||||
For a real-life example, see the components/esp32/panic_highint_hdl.S file; the panic handler iunterrupt is implemented there.
|
||||
|
||||
Notes
|
||||
-----
|
||||
|
||||
- Do not call C code from a high-level interrupt; because these interupts still run in critical sections, this can cause crashes.
|
||||
(The panic handler interrupt does call normal C code, but this is OK because there is no intention of returning to the normal code
|
||||
flow afterwards.)
|
||||
|
||||
- Make sure your assembly code gets linked in. If the interrupt handler symbol is the only symbol the rest of the code uses from this
|
||||
file, the linker will take the default ISR instead and not link the assembly file into the final project. To get around this, in the
|
||||
assembly file, define a symbol, like this::
|
||||
|
||||
.global ld_include_my_isr_file
|
||||
ld_include_my_isr_file:
|
||||
|
||||
|
||||
(The symbol is called ``ld_include_my_isr_file`` here but can have any arbitrary name not defined anywhere else.)
|
||||
Then, in the component.mk, add this file as an unresolved symbol to the ld command line arguments::
|
||||
|
||||
COMPONENT_ADD_LDFLAGS := -u ld_include_my_isr_file
|
||||
|
||||
This should cause the linker to always include a file defining ``ld_include_my_isr_file``, causing the ISR to always be linked in.
|
||||
|
||||
- High-level interrupts can be routed and handled using esp_intr_alloc and associated functions. The handler and handler arguments
|
||||
to esp_intr_alloc must be NULL, however.
|
||||
|
||||
- In theory, medium priority interrupts could also be handled in this way. For now, ESP-IDF does not support this.
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
API Guides
|
||||
**********
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
General Notes <general-notes>
|
||||
Build System <build-system>
|
||||
Deep Sleep Wake Stubs <deep-sleep-stub>
|
||||
ESP32 Core Dump <core_dump>
|
||||
Flash Encryption <../security/flash-encryption>
|
||||
FreeRTOS SMP Changes <freertos-smp>
|
||||
Thread Local Storage <thread-local-storage>
|
||||
High Level Interrupts <hlinterrupts>
|
||||
JTAG Debugging <jtag-debugging/index>
|
||||
Partition Tables <partition-tables>
|
||||
Secure Boot <../security/secure-boot>
|
||||
ULP Coprocessor <ulp>
|
||||
Unit Testing <unit-tests>
|
||||
Application Level Tracing <app_trace>
|
||||
Console Component <console>
|
||||
ROM debug console <romconsole>
|
||||
WiFi Driver <wifi>
|
||||
External SPI-connected RAM <external-ram>
|
||||
@@ -0,0 +1,75 @@
|
||||
***************************************
|
||||
Building OpenOCD from Sources for Linux
|
||||
***************************************
|
||||
|
||||
The following instructions are alternative to downloading binary OpenOCD from Espressif website. To quickly setup the binary OpenOCD, instead of compiling it yourself, backup and proceed to section :doc:`setup-openocd-linux`.
|
||||
|
||||
|
||||
.. highlight:: bash
|
||||
|
||||
Download Sources of OpenOCD
|
||||
===========================
|
||||
|
||||
The sources for the ESP32-enabled variant of OpenOCD are available from Espressif GitHub under https://github.com/espressif/openocd-esp32. To download the sources, use the following commands::
|
||||
|
||||
cd ~/esp
|
||||
git clone –recursive https://github.com/espressif/openocd-esp32.git
|
||||
|
||||
The clone of sources should be now saved in ``~/esp/openocd-esp32`` directory.
|
||||
|
||||
|
||||
Install Dependencies
|
||||
====================
|
||||
|
||||
Install packages that are required to compile OpenOCD.
|
||||
|
||||
.. note::
|
||||
|
||||
Install the following packages one by one, check if installation was successful and then proceed to the next package. Resolve reported problems before moving to the next step.
|
||||
|
||||
::
|
||||
|
||||
sudo apt-get install make
|
||||
sudo apt-get install libtool
|
||||
sudo apt-get install pkg-config
|
||||
sudo apt-get install autoconf
|
||||
sudo apt-get install automake
|
||||
sudo apt-get install texinfo
|
||||
sudo apt-get install libusb-1.0
|
||||
|
||||
.. note::
|
||||
|
||||
* Version of pkg-config should be 0.2.3 or above.
|
||||
* Version of autoconf should be 2.6.4 or above.
|
||||
* Version of automake should be 1.9 or above.
|
||||
* When using USB-Blaster, ASIX Presto, OpenJTAG and FT2232 as adapters, drivers libFTDI and FTD2XX need to be downloaded and installed.
|
||||
* When using CMSIS-DAP, HIDAPI is needed.
|
||||
|
||||
|
||||
Build OpenOCD
|
||||
=============
|
||||
|
||||
Proceed with configuring and building OpenOCD::
|
||||
|
||||
cd ~/esp/openocd-esp32
|
||||
./bootstrap
|
||||
./configure
|
||||
make
|
||||
|
||||
Optionally you can add ``sudo make install`` step at the end. Skip it, if you have an existing OpenOCD (from e.g. another development platform), as it may get overwritten.
|
||||
|
||||
.. note::
|
||||
|
||||
* Should an error occur, resolve it and try again until the command ``make`` works.
|
||||
* If there is a submodule problem from OpenOCD, please ``cd`` to the ``openocd-esp32`` directory and input ``git submodule update --init``.
|
||||
* If the ``./configure`` is successfully run, information of enabled JTAG will be printed under ``OpenOCD configuration summary``.
|
||||
* If the information of your device is not shown in the log, use ``./configure`` to enable it as described in ``../openocd-esp32/doc/INSTALL.txt``.
|
||||
* For details concerning compiling OpenOCD, please refer to ``openocd-esp32/README``.
|
||||
|
||||
Once ``make`` process is successfully completed, the executable of OpenOCD will be saved in ``~/openocd-esp32/bin`` directory.
|
||||
|
||||
|
||||
Next Steps
|
||||
==========
|
||||
|
||||
To carry on with debugging environment setup, proceed to section :ref:`jtag-debugging-configuring-esp32-target`.
|
||||
@@ -0,0 +1,54 @@
|
||||
***************************************
|
||||
Building OpenOCD from Sources for MacOS
|
||||
***************************************
|
||||
|
||||
The following instructions are alternative to downloading binary OpenOCD from Espressif website. To quickly setup the binary OpenOCD, instead of compiling it yourself, backup and proceed to section :doc:`setup-openocd-macos`.
|
||||
|
||||
.. highlight:: bash
|
||||
|
||||
Download Sources of OpenOCD
|
||||
===========================
|
||||
|
||||
The sources for the ESP32-enabled variant of OpenOCD are available from Espressif GitHub under https://github.com/espressif/openocd-esp32. To download the sources, use the following commands::
|
||||
|
||||
cd ~/esp
|
||||
git clone –recursive https://github.com/espressif/openocd-esp32.git
|
||||
|
||||
The clone of sources should be now saved in ``~/esp/openocd-esp32`` directory.
|
||||
|
||||
|
||||
Install Dependencies
|
||||
====================
|
||||
|
||||
Install packages that are required to compile OpenOCD using Homebrew::
|
||||
|
||||
brew install automake libtool libusb wget gcc@4.9
|
||||
|
||||
Build OpenOCD
|
||||
=============
|
||||
|
||||
Proceed with configuring and building OpenOCD::
|
||||
|
||||
cd ~/esp/openocd-esp32
|
||||
./bootstrap
|
||||
./configure
|
||||
make
|
||||
|
||||
Optionally you can add ``sudo make install`` step at the end. Skip it, if you have an existing OpenOCD (from e.g. another development platform), as it may get overwritten.
|
||||
|
||||
.. note::
|
||||
|
||||
* Should an error occur, resolve it and try again until the command ``make`` works.
|
||||
* If there is a submodule problem from OpenOCD, please ``cd`` to the ``openocd-esp32`` directory and input ``git submodule update --init``.
|
||||
* If the ``./configure`` is successfully run, information of enabled JTAG will be printed under ``OpenOCD configuration summary``.
|
||||
* If the information of your device is not shown in the log, use ``./configure`` to enable it as described in ``../openocd-esp32/doc/INSTALL.txt``.
|
||||
* For details concerning compiling OpenOCD, please refer to ``openocd-esp32/README.OSX``.
|
||||
|
||||
Once ``make`` process is successfully completed, the executable of OpenOCD will be saved in ``~/esp/openocd-esp32/src/openocd`` directory.
|
||||
|
||||
Next Steps
|
||||
==========
|
||||
|
||||
To carry on with debugging environment setup, proceed to section :ref:`jtag-debugging-configuring-esp32-target`.
|
||||
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
*****************************************
|
||||
Building OpenOCD from Sources for Windows
|
||||
*****************************************
|
||||
|
||||
The following instructions are alternative to downloading binary OpenOCD from Espressif website. To quickly setup the binary OpenOCD, instead of compiling it yourself, backup and proceed to section :doc:`setup-openocd-windows`.
|
||||
|
||||
|
||||
.. highlight:: bash
|
||||
|
||||
Download Sources of OpenOCD
|
||||
===========================
|
||||
|
||||
The sources for the ESP32-enabled variant of OpenOCD are available from Espressif GitHub under https://github.com/espressif/openocd-esp32. To download the sources, use the following commands::
|
||||
|
||||
cd ~/esp
|
||||
git clone –recursive https://github.com/espressif/openocd-esp32.git
|
||||
|
||||
The clone of sources should be now saved in ``~/esp/openocd-esp32`` directory.
|
||||
|
||||
|
||||
Install Dependencies
|
||||
====================
|
||||
|
||||
Install packages that are required to compile OpenOCD:
|
||||
|
||||
.. note::
|
||||
|
||||
Install the following packages one by one, check if installation was successful and then proceed to the next package. Resolve reported problems before moving to the next step.
|
||||
|
||||
::
|
||||
|
||||
pacman -S libtool
|
||||
pacman -S autoconf
|
||||
pacman -S automake
|
||||
pacman -S texinfo
|
||||
pacman -S mingw-w64-i686-libusb-compat-git
|
||||
pacman -S pkg-config
|
||||
|
||||
.. note::
|
||||
|
||||
Installation of ``pkg-config`` is breaking operation of esp-idf toolchain. After building of OpenOCD it should be uninstalled. It be covered at the end of this instruction. To build OpenOCD again, you will need to run ``pacman -S pkg-config`` once more. This issue does not concern other packages installed in this step (before ``pkg-config``).
|
||||
|
||||
|
||||
Build OpenOCD
|
||||
=============
|
||||
|
||||
Proceed with configuring and building OpenOCD::
|
||||
|
||||
cd ~/esp/openocd-esp32
|
||||
./bootstrap
|
||||
./configure
|
||||
make
|
||||
|
||||
Optionally you can add ``make install`` step at the end. Skip it, if you have an existing OpenOCD (from e.g. another development platform), as it may get overwritten.
|
||||
|
||||
.. note::
|
||||
|
||||
* Should an error occur, resolve it and try again until the command ``make`` works.
|
||||
* If there is a submodule problem from OpenOCD, please ``cd`` to the ``openocd-esp32`` directory and input ``git submodule update --init``.
|
||||
* If the ``./configure`` is successfully run, information of enabled JTAG will be printed under ``OpenOCD configuration summary``.
|
||||
* If the information of your device is not shown in the log, use ``./configure`` to enable it as described in ``../openocd-esp32/doc/INSTALL.txt``.
|
||||
* For details concerning compiling OpenOCD, please refer to ``openocd-esp32/README.Windows``.
|
||||
|
||||
Once ``make`` process is successfully completed, the executable of OpenOCD will be saved in ``~/esp/openocd-esp32/src/openocd`` directory.
|
||||
|
||||
Remove ``pkg-config``, as discussed during installation of dependencies::
|
||||
|
||||
pacman -Rs pkg-config
|
||||
|
||||
|
||||
Next Steps
|
||||
==========
|
||||
|
||||
To carry on with debugging environment setup, proceed to section :ref:`jtag-debugging-configuring-esp32-target`.
|
||||
@@ -0,0 +1,45 @@
|
||||
Configure Other JTAG Interface
|
||||
==============================
|
||||
|
||||
Refer to section :ref:`jtag-debugging-selecting-jtag-adapter` for guidance what JTAG interface to select, so it is able to operate with OpenOCD and ESP32. Then follow three configuration steps below to get it working.
|
||||
|
||||
|
||||
Configure Hardware
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
1. Identify all pins / signals on JTAG interface and ESP32 board, that should be connected to establish communication.
|
||||
|
||||
+---+---------------+-------------+
|
||||
| | ESP32 Pin | JTAG Signal |
|
||||
+===+===============+=============+
|
||||
| 1 | CHIP_PU | TRST_N |
|
||||
+---+---------------+-------------+
|
||||
| 2 | MTDO / GPIO15 | TDO |
|
||||
+---+---------------+-------------+
|
||||
| 3 | MTDI / GPIO12 | TDI |
|
||||
+---+---------------+-------------+
|
||||
| 4 | MTCK / GPIO13 | TCK |
|
||||
+---+---------------+-------------+
|
||||
| 5 | MTMS / GPIO14 | TMS |
|
||||
+---+---------------+-------------+
|
||||
| 6 | GND | GND |
|
||||
+---+---------------+-------------+
|
||||
|
||||
2. Verify if ESP32 pins used for JTAG communication are not connected to some other h/w that may disturb JTAG operation.
|
||||
|
||||
3. Connect identified pin / signals of ESP32 and JTAG interface.
|
||||
|
||||
|
||||
Configure Drivers
|
||||
^^^^^^^^^^^^^^^^^
|
||||
You may need to install driver s/w to make JTAG work with computer. Refer to documentation of JTAG adapter, that should provide related details.
|
||||
|
||||
|
||||
Connect
|
||||
^^^^^^^
|
||||
|
||||
Connect JTAG interface to the computer. Power on ESP32 and JTAG interface boards. Check if JTAG interface is visible by computer.
|
||||
|
||||
|
||||
To carry on with debugging environment setup, proceed to section :ref:`jtag-debugging-run-openocd`.
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
Configure WROVER JTAG Interface
|
||||
===============================
|
||||
|
||||
All versions of ESP32 WROVER KIT boards have JTAG functionality build in. Putting it to work requires setting jumpers to enable JTAG functionality, setting SPI flash voltage and configuring USB drivers. Please refer to step by step instructions below.
|
||||
|
||||
|
||||
Configure Hardware
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
|
||||
1. Enable on-board JTAG functionality by setting JP8 according to :doc:`../../get-started/get-started-wrover-kit`, section :ref:`get-started-esp-wrover-kit-setup-options`.
|
||||
|
||||
2. Verify if ESP32 pins used for JTAG communication are not connected to some other h/w that may disturb JTAG operation:
|
||||
|
||||
+---+---------------+-------------+
|
||||
| | ESP32 Pin | JTAG Signal |
|
||||
+===+===============+=============+
|
||||
| 1 | CHIP_PU | TRST_N |
|
||||
+---+---------------+-------------+
|
||||
| 2 | MTDO / GPIO15 | TDO |
|
||||
+---+---------------+-------------+
|
||||
| 3 | MTDI / GPIO12 | TDI |
|
||||
+---+---------------+-------------+
|
||||
| 4 | MTCK / GPIO13 | TCK |
|
||||
+---+---------------+-------------+
|
||||
| 5 | MTMS / GPIO14 | TMS |
|
||||
+---+---------------+-------------+
|
||||
|
||||
|
||||
Configure USB Drivers
|
||||
^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Install and configure USB drivers, so OpenOCD is able to communicate with JTAG interface on ESP32 WROVER KIT board as well as with UART interface used to upload application for flash. Follow steps below specific to your operating system.
|
||||
|
||||
.. note:: ESP32 WROVER KIT uses an FT2232 adapter. The following instructions can also be used for other FT2232 based JTAG adapters.
|
||||
|
||||
|
||||
Windows
|
||||
"""""""
|
||||
|
||||
1. Using standard USB A / micro USB B cable connect ESP32 WROVER KIT to the computer. Switch the WROVER KIT on.
|
||||
|
||||
2. Wait until USB ports of WROVER KIT are recognized by Windows and drives are installed. If they do not install automatically, then then download them from http://www.ftdichip.com/Drivers/D2XX.htm and install manually.
|
||||
|
||||
3. Download Zadig tool (Zadig_X.X.exe) from http://zadig.akeo.ie/ and run it.
|
||||
|
||||
4. In Zadig tool go to "Options" and check "List All Devices".
|
||||
|
||||
5. Check the list of devices that should contain two WROVER specific USB entries: "Dual RS232-HS (Interface 0)" and "Dual RS232-HS (Interface 1)". The driver name would be "FTDIBUS (vxxxx)" and USB ID: 0403 6010.
|
||||
|
||||
.. figure:: ../../_static/jtag-usb-configuration-zadig.jpg
|
||||
:align: center
|
||||
:alt: Configuration of JTAG USB driver in Zadig tool
|
||||
:figclass: align-center
|
||||
|
||||
Configuration of JTAG USB driver in Zadig tool
|
||||
|
||||
6. The first device (Dual RS232-HS (Interface 0)) is connected to the JTAG port of the ESP32. Original "FTDIBUS (vxxxx)" driver of this device should be replaced with "WinUSB (v6xxxxx)". To do so, select "Dual RS232-HS (Interface 0) and reinstall attached driver to the "WinUSB (v6xxxxx)", see picture above.
|
||||
|
||||
.. note::
|
||||
|
||||
Do not change the second device "Dual RS232-HS (Interface 1)". It is routed to ESP32's serial port (UART) used for upload of application to ESP32's flash.
|
||||
|
||||
Now ESP32 WROVER KIT's JTAG interface should be available to the OpenOCD. To carry on with debugging environment setup, proceed to section :ref:`jtag-debugging-run-openocd`.
|
||||
|
||||
|
||||
Linux
|
||||
"""""
|
||||
|
||||
1. Using standard USB A / micro USB B cable connect ESP32 WROVER KIT board to the computer. Power on the board.
|
||||
|
||||
.. highlight:: none
|
||||
|
||||
2. Open a terminal, enter ``ls -l /dev/ttyUSB*`` command and check, if board's USB ports are recognized by the OS. You are looking for similar result:
|
||||
|
||||
::
|
||||
|
||||
user-name@computer-name:~/esp$ ls -l /dev/ttyUSB*
|
||||
crw-rw---- 1 root dialout 188, 0 Jul 10 19:04 /dev/ttyUSB0
|
||||
crw-rw---- 1 root dialout 188, 1 Jul 10 19:04 /dev/ttyUSB1
|
||||
|
||||
|
||||
3. Following section "Permissions delegation" in OpenOCD's README, set up the access permissions to both USB ports.
|
||||
|
||||
4. Log off and login, then cycle the power to the board to make the changes effective. In terminal enter again ``ls -l /dev/ttyUSB*`` command to verify, if group-owner has changed from ``dialout`` to ``plugdev``:
|
||||
|
||||
::
|
||||
|
||||
user-name@computer-name:~/esp$ ls -l /dev/ttyUSB*
|
||||
crw-rw-r-- 1 root plugdev 188, 0 Jul 10 19:07 /dev/ttyUSB0
|
||||
crw-rw-r-- 1 root plugdev 188, 1 Jul 10 19:07 /dev/ttyUSB1
|
||||
|
||||
If you see similar result and you are a member of ``plugdev`` group, then the set up is complete.
|
||||
|
||||
The ``/dev/ttyUSBn`` interface with lower number is used for JTAG communication. The other interface is routed to ESP32's serial port (UART) used for upload of application to ESP32's flash.
|
||||
|
||||
Now ESP32 WROVER KIT's JTAG interface should be available to the OpenOCD. To carry on with debugging environment setup, proceed to section :ref:`jtag-debugging-run-openocd`.
|
||||
|
||||
|
||||
MacOS
|
||||
"""""
|
||||
|
||||
On macOS, using FT2232 for JTAG and serial port at the same time needs some additional steps. When the OS loads FTDI serial port driver, it does so for both channels of FT2232 chip. However only one of these channels is used as a serial port, while the other is used as JTAG. If the OS has loaded FTDI serial port driver for the channel used for JTAG, OpenOCD will not be able to connect to to the chip. There are two ways around this:
|
||||
|
||||
1. Manually unload the FTDI serial port driver before starting OpenOCD, start OpenOCD, then load the serial port driver.
|
||||
|
||||
2. Modify FTDI driver configuration so that it doesn't load itself for channel B of FT2232 chip, which is the channel used for JTAG on WROVER KIT.
|
||||
|
||||
Manually unloading the driver
|
||||
.............................
|
||||
|
||||
1. Install FTDI driver from http://www.ftdichip.com/Drivers/VCP.htm
|
||||
|
||||
2. Connect USB cable to the WROVER KIT.
|
||||
|
||||
3. Unload the serial port driver::
|
||||
|
||||
sudo kextunload -b com.FTDI.driver.FTDIUSBSerialDriver
|
||||
|
||||
In some cases you may need to unload Apple's FTDI driver as well::
|
||||
|
||||
sudo kextunload -b com.apple.driver.AppleUSBFTDI
|
||||
|
||||
4. Run OpenOCD (paths are given for downloadable OpenOCD archive)::
|
||||
|
||||
bin/openocd -s share/openocd/scripts -f interface/ftdi/esp32_devkitj_v1.cfg -f board/esp-wroom-32.cfg
|
||||
|
||||
Or, if OpenOCD was built from source::
|
||||
|
||||
src/openocd -s tcl -f interface/ftdi/esp32_devkitj_v1.cfg -f board/esp-wroom-32.cfg
|
||||
|
||||
5. In another terminal window, load FTDI serial port driver again::
|
||||
|
||||
sudo kextload -b com.FTDI.driver.FTDIUSBSerialDriver
|
||||
|
||||
|
||||
Note that if you need to restart OpenOCD, there is no need to unload FTDI driver again — just stop OpenOCD and start it again. The driver only needs to be unloaded if WROVER KIT was reconnected or power was toggled.
|
||||
|
||||
This procedure can be wrapped into a shell script, if desired.
|
||||
|
||||
Modifying FTDI driver
|
||||
.....................
|
||||
|
||||
In a nutshell, this approach requires modification to FTDI driver configuration file, which prevents the driver from being loaded for channel B of FT2232H.
|
||||
|
||||
.. note:: Other boards may use channel A for JTAG, so use this option with caution.
|
||||
|
||||
.. warning:: This approach also needs signature verification of drivers to be disabled, so may not be acceptable for all users.
|
||||
|
||||
|
||||
1. Open FTDI driver configuration file using a text editor (note ``sudo``)::
|
||||
|
||||
sudo nano /Library/Extensions/FTDIUSBSerialDriver.kext/Contents/Info.plist
|
||||
|
||||
2. Find and delete the following lines::
|
||||
|
||||
<key>FT2232H_B</key>
|
||||
<dict>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.FTDI.driver.FTDIUSBSerialDriver</string>
|
||||
<key>IOClass</key>
|
||||
<string>FTDIUSBSerialDriver</string>
|
||||
<key>IOProviderClass</key>
|
||||
<string>IOUSBInterface</string>
|
||||
<key>bConfigurationValue</key>
|
||||
<integer>1</integer>
|
||||
<key>bInterfaceNumber</key>
|
||||
<integer>1</integer>
|
||||
<key>bcdDevice</key>
|
||||
<integer>1792</integer>
|
||||
<key>idProduct</key>
|
||||
<integer>24592</integer>
|
||||
<key>idVendor</key>
|
||||
<integer>1027</integer>
|
||||
</dict>
|
||||
|
||||
3. Save and close the file
|
||||
|
||||
4. Disable driver signature verification:
|
||||
|
||||
1. Open Apple logo menu, choose "Restart..."
|
||||
|
||||
2. When you hear the chime after reboot, press CMD+R immediately
|
||||
|
||||
3. Once Recovery mode starts up, open Terminal
|
||||
|
||||
4. Run the command::
|
||||
|
||||
csrutil enable --without kext
|
||||
|
||||
5. Restart again
|
||||
|
||||
After these steps, serial port and JTAG can be used at the same time.
|
||||
|
||||
To carry on with debugging environment setup, proceed to section :ref:`jtag-debugging-run-openocd`.
|
||||
|
||||
@@ -0,0 +1,655 @@
|
||||
Debugging Examples
|
||||
==================
|
||||
|
||||
This section describes debugging with GDB from :ref:`jtag-debugging-examples-eclipse` as well as from :ref:`jtag-debugging-examples-command-line`.
|
||||
|
||||
.. highlight:: none
|
||||
|
||||
.. _jtag-debugging-examples-eclipse:
|
||||
|
||||
Eclipse
|
||||
-------
|
||||
|
||||
Verify if your target is ready and loaded with :example:`get-started/blink` example. Configure and start debugger following steps in section :ref:`jtag-debugging-using-debugger-eclipse`. Pick up where target was left by debugger, i.e. having the application halted at breakpoint established at ``app_main()``.
|
||||
|
||||
.. figure:: ../../_static/debug-perspective.jpg
|
||||
:align: center
|
||||
:alt: Debug Perspective in Eclipse
|
||||
:figclass: align-center
|
||||
|
||||
Debug Perspective in Eclipse
|
||||
|
||||
|
||||
Examples in this section
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
1. :ref:`jtag-debugging-examples-eclipse-01`
|
||||
2. :ref:`jtag-debugging-examples-eclipse-02`
|
||||
3. :ref:`jtag-debugging-examples-eclipse-03`
|
||||
4. :ref:`jtag-debugging-examples-eclipse-04`
|
||||
5. :ref:`jtag-debugging-examples-eclipse-05`
|
||||
6. :ref:`jtag-debugging-examples-eclipse-06`
|
||||
7. :ref:`jtag-debugging-examples-eclipse-07`
|
||||
|
||||
|
||||
.. _jtag-debugging-examples-eclipse-01:
|
||||
|
||||
Navigating though the code, call stack and threads
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
When the target is halted, debugger shows the list of threads in "Debug" window. The line of code where program halted is highlighted in another window below, as shown on the following picture. The LED stops blinking.
|
||||
|
||||
.. figure:: ../../_static/debugging-target-halted.jpg
|
||||
:align: center
|
||||
:alt: Target halted during debugging
|
||||
:figclass: align-center
|
||||
|
||||
Target halted during debugging
|
||||
|
||||
Specific thread where the program halted is expanded showing the call stack. It represents function calls that lead up to the highlighted line of code, where the target halted. The first line of call stack under Thread #1 contains the last called function ``app_main()``, that in turn was called from function ``main_task()`` shown in a line below. Each line of the stack also contains the file name and line number where the function was called. By clicking / highlighting the stack entries, in window below, you will see contents of this file.
|
||||
|
||||
By expanding threads you can navigate throughout the application. Expand Thread #5 that contains much longer call stack. You will see there, besides function calls, numbers like ``0x4000000c``. They represent addresses of binary code not provided in source form.
|
||||
|
||||
.. figure:: ../../_static/debugging-navigate-through-the-stack.jpg
|
||||
:align: center
|
||||
:alt: Navigate through the call stack
|
||||
:figclass: align-center
|
||||
|
||||
Navigate through the call stack
|
||||
|
||||
In another window on right, you can see the disassembled machine code no matter if your project provides it in source or only the binary form.
|
||||
|
||||
Go back to the ``app_main()`` in Thread #1 to familiar code of ``blink.c`` file that will be examined in more details in the following examples. Debugger makes it easy to navigate through the code of entire application. This comes handy when stepping though the code and working with breakpoints and will be discussed below.
|
||||
|
||||
|
||||
.. _jtag-debugging-examples-eclipse-02:
|
||||
|
||||
Setting and clearing breakpoints
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
When debugging, we would like to be able to stop the application at critical lines of code and then examine the state of specific variables, memory and registers / peripherals. To do so we are using breakpoints. They provide a convenient way to quickly get to and halt the application at specific line.
|
||||
|
||||
Let's establish two breakpoints when the state of LED changes. Basing on code listing above, this happens at lines 33 and 36. To do so, hold the "Control" on the keyboard and double clink on number ``33`` in file ``blink.c`` file. A dialog will open where you can confirm your selection by pressing "OK" button. If you do not like to see the dialog just double click the line number. Set another breakpoint in line 36.
|
||||
|
||||
.. figure:: ../../_static/debugging-setting-breakpoint.jpg
|
||||
:align: center
|
||||
:alt: Setting a breakpoint
|
||||
:figclass: align-center
|
||||
|
||||
Setting a breakpoint
|
||||
|
||||
Information how many breakpoints are set and where is shown in window "Breakpoints" on top right. Click "Show Breakpoints Supported by Selected Target" to refresh this list. Besides the two just set breakpoints the list may contain temporary breakpoint at function ``app_main()`` established at debugger start. As maximum two breakpoints are allowed (see :ref:`jtag-debugging-tip-breakpoints`), you need to delete it, or debugging will fail.
|
||||
|
||||
.. figure:: ../../_static/debugging-three-breakpoints-set.jpg
|
||||
:align: center
|
||||
:alt: Three breakpoints are set / maximum two are allowed
|
||||
:figclass: align-center
|
||||
|
||||
Three breakpoints are set / maximum two are allowed
|
||||
|
||||
If you now click "Resume" (click ``blink_task()`` under "Tread #8", if "Resume" button is grayed out), the processor will run and halt at a breakpoint. Clicking "Resume" another time will make it run again, halt on second breakpoint, and so on.
|
||||
|
||||
You will be also able to see that LED is changing the state after each click to "Resume" program execution.
|
||||
|
||||
Read more about breakpoints under :ref:`jtag-debugging-tip-breakpoints` and :ref:`jtag-debugging-tip-where-breakpoints`
|
||||
|
||||
|
||||
.. _jtag-debugging-examples-eclipse-03:
|
||||
|
||||
Halting the target manually
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
When debugging, you may resume application and enter code waiting for some event or staying in infinite loop without any break points defined. In such case, to go back to debugging mode, you can break program execution manually by pressing "Suspend" button.
|
||||
|
||||
To check it, delete all breakpoints and click "Resume". Then click "Suspend". Application will be halted at some random point and LED will stop blinking. Debugger will expand tread and highlight the line of code where application halted.
|
||||
|
||||
.. figure:: ../../_static/debugging-target-halted-manually.jpg
|
||||
:align: center
|
||||
:alt: Target halted manually
|
||||
:figclass: align-center
|
||||
|
||||
Target halted manually
|
||||
|
||||
In particular case above, the application has been halted in line 52 of code in file ``freertos_hooks.c`` Now you can resume it again by pressing "Resume" button or do some debugging as discussed below.
|
||||
|
||||
|
||||
.. _jtag-debugging-examples-eclipse-04:
|
||||
|
||||
Stepping through the code
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
It is also possible to step through the code using "Step Into (F5)" and "Step Over (F6)" commands. The difference is that "Step Into (F5)" is entering inside subroutines calls, while "Step Over (F6)" steps over the call, treating it as a single source line.
|
||||
|
||||
Before being able to demonstrate this functionality, using information discussed in previous paragraph, make sure that you have only one breakpoint defined at line ``36`` of ``blink.c``.
|
||||
|
||||
Resume program by entering pressing F8 and let it halt. Now press "Step Over (F6)", one by one couple of times, to see how debugger is stepping one program line at a time.
|
||||
|
||||
.. figure:: ../../_static/debugging-step-over.jpg
|
||||
:align: center
|
||||
:alt: Stepping through the code with "Step Over (F6)"
|
||||
:figclass: align-center
|
||||
|
||||
Stepping through the code with "Step Over (F6)"
|
||||
|
||||
If you press "Step Into (F5)" instead, then debugger will step inside subroutine calls.
|
||||
|
||||
.. figure:: ../../_static/debugging-step-into.jpg
|
||||
:align: center
|
||||
:alt: Stepping through the code with "Step Into (F5)"
|
||||
:figclass: align-center
|
||||
|
||||
Stepping through the code with "Step Into (F5)"
|
||||
|
||||
In this particular case debugger stepped inside ``gpio_set_level(BLINK_GPIO, 0)`` and effectively moved to ``gpio.c`` driver code.
|
||||
|
||||
See :ref:`jtag-debugging-tip-why-next-works-as-step` for potential limitation of using ``next`` command.
|
||||
|
||||
|
||||
.. _jtag-debugging-examples-eclipse-05:
|
||||
|
||||
Checking and setting memory
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To display or set contents of memory use "Memory" tab at the bottom of "Debug" perspective.
|
||||
|
||||
With the "Memory" tab, we will read from and write to the memory location ``0x3FF44004`` labeled as ``GPIO_OUT_REG`` used to set and clear individual GPIO's. For more information please refer to `ESP32 Technical Reference Manual <https://espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf>`__, chapter IO_MUX and GPIO Matrix.
|
||||
|
||||
Being in the same ``blink.c`` project as before, set two breakpoints right after ``gpio_set_level`` instruction. Click "Memory" tab and then "Add Memory Monitor" button. Enter ``0x3FF44004`` in provided dialog.
|
||||
|
||||
Now resume program by pressing F8 and observe "Monitor" tab.
|
||||
|
||||
.. figure:: ../../_static/debugging-memory-location-on.jpg
|
||||
:align: center
|
||||
:alt: Observing memory location 0x3FF44004 changing one bit to ON"
|
||||
:figclass: align-center
|
||||
|
||||
Observing memory location 0x3FF44004 changing one bit to ON"
|
||||
|
||||
You should see one bit being flipped over at memory location ``0x3FF44004`` (and LED changing the state) each time F8 is pressed.
|
||||
|
||||
.. figure:: ../../_static/debugging-memory-location-off.jpg
|
||||
:align: center
|
||||
:alt: Observing memory location 0x3FF44004 changing one bit to ON"
|
||||
:figclass: align-center
|
||||
|
||||
Observing memory location 0x3FF44004 changing one bit to ON"
|
||||
|
||||
To set memory use the same "Monitor" tab and the same memory location. Type in alternate bit pattern as previously observed. Immediately after pressing enter you will see LED changing the state.
|
||||
|
||||
|
||||
.. _jtag-debugging-examples-eclipse-06:
|
||||
|
||||
Watching and setting program variables
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
A common debugging tasks is checking the value of a program variable as the program runs. To be able to demonstrate this functionality, update file ``blink.c`` by adding a declaration of a global variable ``int i`` above definition of function ``blink_task``. Then add ``i++`` inside ``loop(1)`` of this function to get ``i`` incremented on each blink.
|
||||
|
||||
Exit debugger, so it is not confused with new code, build and flash the code to the ESP and restart debugger. There is no need to restart OpenOCD.
|
||||
|
||||
Once application is halted, enter a breakpoint in the line where you put ``i++``.
|
||||
|
||||
In next step, in the window with "Breakpoints", click the "Expressions" tab. If this tab is not visible, then add it by going to the top menu Window > Show View > Expressions. Then click "Add new expression" and enter ``i``.
|
||||
|
||||
Resume program execution by pressing F8. Each time the program is halted you will see ``i`` value being incremented.
|
||||
|
||||
.. figure:: ../../_static/debugging-watch-variable.jpg
|
||||
:align: center
|
||||
:alt: Watching program variable "i"
|
||||
:figclass: align-center
|
||||
|
||||
Watching program variable "i"
|
||||
|
||||
|
||||
To modify ``i`` enter a new number in "Value" column. After pressing "Resume (F8)" the program will keep incrementing ``i`` starting from the new entered number.
|
||||
|
||||
|
||||
.. _jtag-debugging-examples-eclipse-07:
|
||||
|
||||
Setting conditional breakpoints
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Here comes more interesting part. You may set a breakpoint to halt the program execution, if certain condition is satisfied. Right click on the breakpoint to open a context menu and select "Breakpoint Properties". Change the selection under "Type:" to "Hardware" and enter a "Condition:" like ``i == 2``.
|
||||
|
||||
.. figure:: ../../_static/debugging-setting-conditional-breakpoint.jpg
|
||||
:align: center
|
||||
:alt: Setting a conditional breakpoint
|
||||
:figclass: align-center
|
||||
|
||||
Setting a conditional breakpoint
|
||||
|
||||
If current value of ``i`` is less than ``2`` (change it if required) and program is resumed, it will blink LED in a loop until condition ``i == 2`` gets true and then finally halt.
|
||||
|
||||
|
||||
.. _jtag-debugging-examples-command-line:
|
||||
|
||||
Command Line
|
||||
------------
|
||||
|
||||
Verify if your target is ready and loaded with :example:`get-started/blink` example. Configure and start debugger following steps in section :ref:`jtag-debugging-using-debugger-command-line`. Pick up where target was left by debugger, i.e. having the application halted at breakpoint established at ``app_main()``::
|
||||
|
||||
Temporary breakpoint 1, app_main () at /home/user-name/esp/blink/main/./blink.c:43
|
||||
43 xTaskCreate(&blink_task, "blink_task", configMINIMAL_STACK_SIZE, NULL, 5, NULL);
|
||||
(gdb)
|
||||
|
||||
|
||||
|
||||
Examples in this section
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
1. :ref:`jtag-debugging-examples-command-line-01`
|
||||
2. :ref:`jtag-debugging-examples-command-line-02`
|
||||
3. :ref:`jtag-debugging-examples-command-line-03`
|
||||
4. :ref:`jtag-debugging-examples-command-line-04`
|
||||
5. :ref:`jtag-debugging-examples-command-line-05`
|
||||
6. :ref:`jtag-debugging-examples-command-line-06`
|
||||
7. :ref:`jtag-debugging-examples-command-line-07`
|
||||
|
||||
|
||||
.. _jtag-debugging-examples-command-line-01:
|
||||
|
||||
Navigating though the code, call stack and threads
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
When you see the ``(gdb)`` prompt, the application is halted. LED should not be blinking.
|
||||
|
||||
To find out where exactly the code is halted, enter ``l`` or ``list``, and debugger will show couple of lines of code around the halt point (line 43 of code in file ``blink.c``) ::
|
||||
|
||||
(gdb) l
|
||||
38 }
|
||||
39 }
|
||||
40
|
||||
41 void app_main()
|
||||
42 {
|
||||
43 xTaskCreate(&blink_task, "blink_task", configMINIMAL_STACK_SIZE, NULL, 5, NULL);
|
||||
44 }
|
||||
(gdb)
|
||||
|
||||
|
||||
Check how code listing works by entering, e.g. ``l 30, 40`` to see particular range of lines of code.
|
||||
|
||||
You can use ``bt`` or ``backtrace`` to see what function calls lead up to this code::
|
||||
|
||||
(gdb) bt
|
||||
#0 app_main () at /home/user-name/esp/blink/main/./blink.c:43
|
||||
#1 0x400d057e in main_task (args=0x0) at /home/user-name/esp/esp-idf/components/esp32/./cpu_start.c:339
|
||||
(gdb)
|
||||
|
||||
Line #0 of output provides the last function call before the application halted, i.e. ``app_main ()`` we have listed previously. The ``app_main ()`` was in turn called by function ``main_task`` from line 339 of code located in file ``cpu_start.c``.
|
||||
|
||||
To get to the context of ``main_task`` in file ``cpu_start.c``, enter ``frame N``, where N = 1, because the ``main_task`` is listed under #1)::
|
||||
|
||||
(gdb) frame 1
|
||||
#1 0x400d057e in main_task (args=0x0) at /home/user-name/esp/esp-idf/components/esp32/./cpu_start.c:339
|
||||
339 app_main();
|
||||
(gdb)
|
||||
|
||||
Enter ``l`` and this will reveal the piece of code that called ``app_main()`` (in line 339)::
|
||||
|
||||
(gdb) l
|
||||
334 ;
|
||||
335 }
|
||||
336 #endif
|
||||
337 //Enable allocation in region where the startup stacks were located.
|
||||
338 heap_caps_enable_nonos_stack_heaps();
|
||||
339 app_main();
|
||||
340 vTaskDelete(NULL);
|
||||
341 }
|
||||
342
|
||||
(gdb)
|
||||
|
||||
By listing some lines before, you will see the function name ``main_task`` we have been looking for::
|
||||
|
||||
(gdb) l 326, 341
|
||||
326 static void main_task(void* args)
|
||||
327 {
|
||||
328 // Now that the application is about to start, disable boot watchdogs
|
||||
329 REG_CLR_BIT(TIMG_WDTCONFIG0_REG(0), TIMG_WDT_FLASHBOOT_MOD_EN_S);
|
||||
330 REG_CLR_BIT(RTC_CNTL_WDTCONFIG0_REG, RTC_CNTL_WDT_FLASHBOOT_MOD_EN);
|
||||
331 #if !CONFIG_FREERTOS_UNICORE
|
||||
332 // Wait for FreeRTOS initialization to finish on APP CPU, before replacing its startup stack
|
||||
333 while (port_xSchedulerRunning[1] == 0) {
|
||||
334 ;
|
||||
335 }
|
||||
336 #endif
|
||||
337 //Enable allocation in region where the startup stacks were located.
|
||||
338 heap_caps_enable_nonos_stack_heaps();
|
||||
339 app_main();
|
||||
340 vTaskDelete(NULL);
|
||||
341 }
|
||||
(gdb)
|
||||
|
||||
To see the other code, enter ``i threads``. This will show the list of threads running on target::
|
||||
|
||||
(gdb) i threads
|
||||
Id Target Id Frame
|
||||
8 Thread 1073411336 (dport) 0x400d0848 in dport_access_init_core (arg=<optimized out>)
|
||||
at /home/user-name/esp/esp-idf/components/esp32/./dport_access.c:170
|
||||
7 Thread 1073408744 (ipc0) xQueueGenericReceive (xQueue=0x3ffae694, pvBuffer=0x0, xTicksToWait=1644638200,
|
||||
xJustPeeking=0) at /home/user-name/esp/esp-idf/components/freertos/./queue.c:1452
|
||||
6 Thread 1073431096 (Tmr Svc) prvTimerTask (pvParameters=0x0)
|
||||
at /home/user-name/esp/esp-idf/components/freertos/./timers.c:445
|
||||
5 Thread 1073410208 (ipc1 : Running) 0x4000bfea in ?? ()
|
||||
4 Thread 1073432224 (dport) dport_access_init_core (arg=0x0)
|
||||
at /home/user-name/esp/esp-idf/components/esp32/./dport_access.c:150
|
||||
3 Thread 1073413156 (IDLE) prvIdleTask (pvParameters=0x0)
|
||||
at /home/user-name/esp/esp-idf/components/freertos/./tasks.c:3282
|
||||
2 Thread 1073413512 (IDLE) prvIdleTask (pvParameters=0x0)
|
||||
at /home/user-name/esp/esp-idf/components/freertos/./tasks.c:3282
|
||||
* 1 Thread 1073411772 (main : Running) app_main () at /home/user-name/esp/blink/main/./blink.c:43
|
||||
(gdb)
|
||||
|
||||
The thread list shows the last function calls per each thread together with the name of C source file if available.
|
||||
|
||||
You can navigate to specific thread by entering ``thread N``, where ``N`` is the thread Id. To see how it works go to thread thread 5::
|
||||
|
||||
(gdb) thread 5
|
||||
[Switching to thread 5 (Thread 1073410208)]
|
||||
#0 0x4000bfea in ?? ()
|
||||
(gdb)
|
||||
|
||||
Then check the backtrace::
|
||||
|
||||
(gdb) bt
|
||||
#0 0x4000bfea in ?? ()
|
||||
#1 0x40083a85 in vPortCPUReleaseMutex (mux=<optimized out>) at /home/user-name/esp/esp-idf/components/freertos/./port.c:415
|
||||
#2 0x40083fc8 in vTaskSwitchContext () at /home/user-name/esp/esp-idf/components/freertos/./tasks.c:2846
|
||||
#3 0x4008532b in _frxt_dispatch ()
|
||||
#4 0x4008395c in xPortStartScheduler () at /home/user-name/esp/esp-idf/components/freertos/./port.c:222
|
||||
#5 0x4000000c in ?? ()
|
||||
#6 0x4000000c in ?? ()
|
||||
#7 0x4000000c in ?? ()
|
||||
#8 0x4000000c in ?? ()
|
||||
(gdb)
|
||||
|
||||
As you see, the backtrace may contain several entries. This will let you check what exact sequence of function calls lead to the code where the target halted. Question marks ``??`` instead of a function name indicate that application is available only in binary format, without any source file in C language. The value like ``0x4000bfea`` is the memory address of the function call.
|
||||
|
||||
Using ``bt``, ``i threads``, ``thread N`` and ``list`` commands we are now able to navigate through the code of entire application. This comes handy when stepping though the code and working with breakpoints and will be discussed below.
|
||||
|
||||
|
||||
.. _jtag-debugging-examples-command-line-02:
|
||||
|
||||
Setting and clearing breakpoints
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
When debugging, we would like to be able to stop the application at critical lines of code and then examine the state of specific variables, memory and registers / peripherals. To do so we are using breakpoints. They provide a convenient way to quickly get to and halt the application at specific line.
|
||||
|
||||
Let's establish two breakpoints when the state of LED changes. Basing on code listing above this happens at lines 33 and 36. Breakpoints may be established using command ``break M`` where M is the code line number::
|
||||
|
||||
(gdb) break 33
|
||||
Breakpoint 2 at 0x400db6f6: file /home/user-name/esp/blink/main/./blink.c, line 33.
|
||||
(gdb) break 36
|
||||
Breakpoint 3 at 0x400db704: file /home/user-name/esp/blink/main/./blink.c, line 36.
|
||||
|
||||
If you new enter ``c``, the processor will run and halt at a breakpoint. Entering ``c`` another time will make it run again, halt on second breakpoint, and so on::
|
||||
|
||||
(gdb) c
|
||||
Continuing.
|
||||
Target halted. PRO_CPU: PC=0x400DB6F6 (active) APP_CPU: PC=0x400D10D8
|
||||
|
||||
Breakpoint 2, blink_task (pvParameter=0x0) at /home/user-name/esp/blink/main/./blink.c:33
|
||||
33 gpio_set_level(BLINK_GPIO, 0);
|
||||
(gdb) c
|
||||
Continuing.
|
||||
Target halted. PRO_CPU: PC=0x400DB6F8 (active) APP_CPU: PC=0x400D10D8
|
||||
Target halted. PRO_CPU: PC=0x400DB704 (active) APP_CPU: PC=0x400D10D8
|
||||
|
||||
Breakpoint 3, blink_task (pvParameter=0x0) at /home/user-name/esp/blink/main/./blink.c:36
|
||||
36 gpio_set_level(BLINK_GPIO, 1);
|
||||
(gdb)
|
||||
|
||||
You will be also able to see that LED is changing the state only if you resume program execution by entering ``c``.
|
||||
|
||||
To examine how many breakpoints are set and where, use command ``info break``::
|
||||
|
||||
(gdb) info break
|
||||
Num Type Disp Enb Address What
|
||||
2 breakpoint keep y 0x400db6f6 in blink_task at /home/user-name/esp/blink/main/./blink.c:33
|
||||
breakpoint already hit 1 time
|
||||
3 breakpoint keep y 0x400db704 in blink_task at /home/user-name/esp/blink/main/./blink.c:36
|
||||
breakpoint already hit 1 time
|
||||
(gdb)
|
||||
|
||||
Please note that breakpoint numbers (listed under ``Num``) start with ``2``. This is because first breakpoint has been already established at function ``app_main()`` by running command ``thb app_main`` on debugger launch. As it was a temporary breakpoint, it has been automatically deleted and now is not listed anymore.
|
||||
|
||||
To remove breakpoints enter ``delete N`` command (in short ``d N``), where ``N`` is the breakpoint number::
|
||||
|
||||
(gdb) delete 1
|
||||
No breakpoint number 1.
|
||||
(gdb) delete 2
|
||||
(gdb)
|
||||
|
||||
Read more about breakpoints under :ref:`jtag-debugging-tip-breakpoints` and :ref:`jtag-debugging-tip-where-breakpoints`
|
||||
|
||||
|
||||
.. _jtag-debugging-examples-command-line-03:
|
||||
|
||||
Halting and resuming the application
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
When debugging, you may resume application and enter code waiting for some event or staying in infinite loop without any break points defined. In such case, to go back to debugging mode, you can break program execution manually by entering Ctrl+C.
|
||||
|
||||
To check it delete all breakpoints and enter ``c`` to resume application. Then enter Ctrl+C. Application will be halted at some random point and LED will stop blinking. Debugger will print the following::
|
||||
|
||||
(gdb) c
|
||||
Continuing.
|
||||
^CTarget halted. PRO_CPU: PC=0x400D0C00 APP_CPU: PC=0x400D0C00 (active)
|
||||
[New Thread 1073433352]
|
||||
|
||||
Program received signal SIGINT, Interrupt.
|
||||
[Switching to Thread 1073413512]
|
||||
0x400d0c00 in esp_vApplicationIdleHook () at /home/user-name/esp/esp-idf/components/esp32/./freertos_hooks.c:52
|
||||
52 asm("waiti 0");
|
||||
(gdb)
|
||||
|
||||
In particular case above, the application has been halted in line 52 of code in file ``freertos_hooks.c``. Now you can resume it again by enter ``c`` or do some debugging as discussed below.
|
||||
|
||||
.. note::
|
||||
|
||||
In MSYS2 shell Ctrl+C does not halt the target but exists debugger. To resolve this issue consider debugging with :ref:`jtag-debugging-examples-eclipse` or check a workaround under http://www.mingw.org/wiki/Workaround_for_GDB_Ctrl_C_Interrupt.
|
||||
|
||||
|
||||
.. _jtag-debugging-examples-command-line-04:
|
||||
|
||||
Stepping through the code
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
It is also possible to step through the code using ``step`` and ``next`` commands (in short ``s`` and ``n``). The difference is that ``step`` is entering inside subroutines calls, while ``next`` steps over the call, treating it as a single source line.
|
||||
|
||||
To demonstrate this functionality, using command ``break`` and ``delete`` discussed in previous paragraph, make sure that you have only one breakpoint defined at line ``36`` of ``blink.c``::
|
||||
|
||||
(gdb) info break
|
||||
Num Type Disp Enb Address What
|
||||
3 breakpoint keep y 0x400db704 in blink_task at /home/user-name/esp/blink/main/./blink.c:36
|
||||
breakpoint already hit 1 time
|
||||
(gdb)
|
||||
|
||||
Resume program by entering ``c`` and let it halt::
|
||||
|
||||
(gdb) c
|
||||
Continuing.
|
||||
Target halted. PRO_CPU: PC=0x400DB754 (active) APP_CPU: PC=0x400D1128
|
||||
|
||||
Breakpoint 3, blink_task (pvParameter=0x0) at /home/user-name/esp/blink/main/./blink.c:36
|
||||
36 gpio_set_level(BLINK_GPIO, 1);
|
||||
(gdb)
|
||||
|
||||
Then enter ``n`` couple of times to see how debugger is stepping one program line at a time::
|
||||
|
||||
(gdb) n
|
||||
Target halted. PRO_CPU: PC=0x400DB756 (active) APP_CPU: PC=0x400D1128
|
||||
Target halted. PRO_CPU: PC=0x400DB758 (active) APP_CPU: PC=0x400D1128
|
||||
Target halted. PRO_CPU: PC=0x400DC04C (active) APP_CPU: PC=0x400D1128
|
||||
Target halted. PRO_CPU: PC=0x400DB75B (active) APP_CPU: PC=0x400D1128
|
||||
37 vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
(gdb) n
|
||||
Target halted. PRO_CPU: PC=0x400DB75E (active) APP_CPU: PC=0x400D1128
|
||||
Target halted. PRO_CPU: PC=0x400846FC (active) APP_CPU: PC=0x400D1128
|
||||
Target halted. PRO_CPU: PC=0x400DB761 (active) APP_CPU: PC=0x400D1128
|
||||
Target halted. PRO_CPU: PC=0x400DB746 (active) APP_CPU: PC=0x400D1128
|
||||
33 gpio_set_level(BLINK_GPIO, 0);
|
||||
(gdb)
|
||||
|
||||
If you enter ``s`` instead, then debugger will step inside subroutine calls::
|
||||
|
||||
(gdb) s
|
||||
Target halted. PRO_CPU: PC=0x400DB748 (active) APP_CPU: PC=0x400D1128
|
||||
Target halted. PRO_CPU: PC=0x400DB74B (active) APP_CPU: PC=0x400D1128
|
||||
Target halted. PRO_CPU: PC=0x400DC04C (active) APP_CPU: PC=0x400D1128
|
||||
Target halted. PRO_CPU: PC=0x400DC04F (active) APP_CPU: PC=0x400D1128
|
||||
gpio_set_level (gpio_num=GPIO_NUM_4, level=0) at /home/user-name/esp/esp-idf/components/driver/./gpio.c:183
|
||||
183 GPIO_CHECK(GPIO_IS_VALID_OUTPUT_GPIO(gpio_num), "GPIO output gpio_num error", ESP_ERR_INVALID_ARG);
|
||||
(gdb)
|
||||
|
||||
In this particular case debugger stepped inside ``gpio_set_level(BLINK_GPIO, 0)`` and effectively moved to ``gpio.c`` driver code.
|
||||
|
||||
See :ref:`jtag-debugging-tip-why-next-works-as-step` for potential limitation of using ``next`` command.
|
||||
|
||||
|
||||
.. _jtag-debugging-examples-command-line-05:
|
||||
|
||||
Checking and setting memory
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Displaying the contents of memory is done with command ``x``. With additional parameters you may vary the format and count of memory locations displayed. Run ``help x`` to see more details. Companion command to ``x`` is ``set`` that let you write values to the memory.
|
||||
|
||||
We will demonstrate how ``x`` and ``set`` work by reading from and writing to the memory location ``0x3FF44004`` labeled as ``GPIO_OUT_REG`` used to set and clear individual GPIO's. For more information please refer to `ESP32 Technical Reference Manual <https://espressif.com/sites/default/files/documentation/esp32_technical_reference_manual_en.pdf>`__, chapter IO_MUX and GPIO Matrix.
|
||||
|
||||
Being in the same ``blink.c`` project as before, set two breakpoints right after ``gpio_set_level`` instruction. Enter two times ``c`` to get to the break point followed by ``x /1wx 0x3FF44004`` to display contents of ``GPIO_OUT_REG`` memory location::
|
||||
|
||||
(gdb) c
|
||||
Continuing.
|
||||
Target halted. PRO_CPU: PC=0x400DB75E (active) APP_CPU: PC=0x400D1128
|
||||
Target halted. PRO_CPU: PC=0x400DB74E (active) APP_CPU: PC=0x400D1128
|
||||
|
||||
Breakpoint 2, blink_task (pvParameter=0x0) at /home/user-name/esp/blink/main/./blink.c:34
|
||||
34 vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
(gdb) x /1wx 0x3FF44004
|
||||
0x3ff44004: 0x00000000
|
||||
(gdb) c
|
||||
Continuing.
|
||||
Target halted. PRO_CPU: PC=0x400DB751 (active) APP_CPU: PC=0x400D1128
|
||||
Target halted. PRO_CPU: PC=0x400DB75B (active) APP_CPU: PC=0x400D1128
|
||||
|
||||
Breakpoint 3, blink_task (pvParameter=0x0) at /home/user-name/esp/blink/main/./blink.c:37
|
||||
37 vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
(gdb) x /1wx 0x3FF44004
|
||||
0x3ff44004: 0x00000010
|
||||
(gdb)
|
||||
|
||||
If your are blinking LED connected to GPIO4, then you should see fourth bit being flipped each time the LED changes the state::
|
||||
|
||||
0x3ff44004: 0x00000000
|
||||
...
|
||||
0x3ff44004: 0x00000010
|
||||
|
||||
Now, when the LED is off, that corresponds to ``0x3ff44004: 0x00000000`` being displayed, try using ``set`` command to set this bit by writting ``0x00000010`` to the same memory location::
|
||||
|
||||
(gdb) x /1wx 0x3FF44004
|
||||
0x3ff44004: 0x00000000
|
||||
(gdb) set {unsigned int}0x3FF44004=0x000010
|
||||
|
||||
You should see the LED to turn on immediately after entering ``set {unsigned int}0x3FF44004=0x000010`` command.
|
||||
|
||||
|
||||
.. _jtag-debugging-examples-command-line-06:
|
||||
|
||||
Watching and setting program variables
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
A common debugging tasks is checking the value of a program variable as the program runs. To be able to demonstrate this functionality, update file ``blink.c`` by adding a declaration of a global variable ``int i`` above definition of function ``blink_task``. Then add ``i++`` inside ``loop(1)`` of this function to get ``i`` incremented on each blink.
|
||||
|
||||
Exit debugger, so it is not confused with new code, build and flash the code to the ESP and restart debugger. There is no need to restart OpenOCD.
|
||||
|
||||
Once application is halted, enter the command ``watch i``::
|
||||
|
||||
(gdb) watch i
|
||||
Hardware watchpoint 2: i
|
||||
(gdb)
|
||||
|
||||
This will insert so called "watchpoint" in each place of code where variable ``i`` is being modified. Now enter ``continue`` to resume the application and observe it being halted::
|
||||
|
||||
(gdb) c
|
||||
Continuing.
|
||||
Target halted. PRO_CPU: PC=0x400DB751 (active) APP_CPU: PC=0x400D0811
|
||||
[New Thread 1073432196]
|
||||
|
||||
Program received signal SIGTRAP, Trace/breakpoint trap.
|
||||
[Switching to Thread 1073432196]
|
||||
0x400db751 in blink_task (pvParameter=0x0) at /home/user-name/esp/blink/main/./blink.c:33
|
||||
33 i++;
|
||||
(gdb)
|
||||
|
||||
Resume application couple more times so ``i`` gets incremented. Now you can enter ``print i`` (in short ``p i``) to check the current value of ``i``::
|
||||
|
||||
(gdb) p i
|
||||
$1 = 3
|
||||
(gdb)
|
||||
|
||||
To modify the value of ``i`` use ``set`` command as below (you can then print it out to check if it has been indeed changed)::
|
||||
|
||||
(gdb) set var i = 0
|
||||
(gdb) p i
|
||||
$3 = 0
|
||||
(gdb)
|
||||
|
||||
You may have up to two watchpoints, see :ref:`jtag-debugging-tip-breakpoints`.
|
||||
|
||||
|
||||
.. _jtag-debugging-examples-command-line-07:
|
||||
|
||||
Setting conditional breakpoints
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Here comes more interesting part. You may set a breakpoint to halt the program execution, if certain condition is satisfied. Delete existing breakpoints and try this::
|
||||
|
||||
(gdb) break blink.c:34 if (i == 2)
|
||||
Breakpoint 3 at 0x400db753: file /home/user-name/esp/blink/main/./blink.c, line 34.
|
||||
(gdb)
|
||||
|
||||
Above command sets conditional breakpoint to halt program execution in line ``34`` of ``blink.c`` if ``i == 2``.
|
||||
|
||||
If current value of ``i`` is less than ``2`` and program is resumed, it will blink LED in a loop until condition ``i == 2`` gets true and then finally halt::
|
||||
|
||||
(gdb) set var i = 0
|
||||
(gdb) c
|
||||
Continuing.
|
||||
Target halted. PRO_CPU: PC=0x400DB755 (active) APP_CPU: PC=0x400D112C
|
||||
Target halted. PRO_CPU: PC=0x400DB753 (active) APP_CPU: PC=0x400D112C
|
||||
Target halted. PRO_CPU: PC=0x400DB755 (active) APP_CPU: PC=0x400D112C
|
||||
Target halted. PRO_CPU: PC=0x400DB753 (active) APP_CPU: PC=0x400D112C
|
||||
|
||||
Breakpoint 3, blink_task (pvParameter=0x0) at /home/user-name/esp/blink/main/./blink.c:34
|
||||
34 gpio_set_level(BLINK_GPIO, 0);
|
||||
(gdb)
|
||||
|
||||
|
||||
Obtaining help on commands
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Commands presented so for should provide are very basis and intended to let you quickly get started with JTAG debugging. Check help what are the other commands at you disposal. To obtain help on syntax and functionality of particular command, being at ``(gdb)`` prompt type ``help`` and command name::
|
||||
|
||||
(gdb) help next
|
||||
Step program, proceeding through subroutine calls.
|
||||
Usage: next [N]
|
||||
Unlike "step", if the current source line calls a subroutine,
|
||||
this command does not enter the subroutine, but instead steps over
|
||||
the call, in effect treating it as a single source line.
|
||||
(gdb)
|
||||
|
||||
By typing just ``help``, you will get top level list of command classes, to aid you drilling down to more details. Optionally refer to available GDB cheat sheets, for instance http://darkdust.net/files/GDB%20Cheat%20Sheet.pdf. Good to have as a reference (even if not all commands are applicable in an embedded environment).
|
||||
|
||||
|
||||
Ending debugger session
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
To quit debugger enter ``q``::
|
||||
|
||||
(gdb) q
|
||||
A debugging session is active.
|
||||
|
||||
Inferior 1 [Remote target] will be detached.
|
||||
|
||||
Quit anyway? (y or n) y
|
||||
Detaching from program: /home/user-name/esp/blink/build/blink.elf, Remote target
|
||||
Ending remote debugging.
|
||||
user-name@computer-name:~/esp/blink$
|
||||
@@ -0,0 +1,305 @@
|
||||
JTAG Debugging
|
||||
==============
|
||||
|
||||
This document provides a guide to installing OpenOCD for ESP32 and debugging using
|
||||
GDB. The document is structured as follows:
|
||||
|
||||
:ref:`jtag-debugging-introduction`
|
||||
Introduction to the purpose of this guide.
|
||||
:ref:`jtag-debugging-how-it-works`
|
||||
Description how ESP32, JTAG interface, OpenOCD and GDB are interconnected and working together to enable debugging of ESP32.
|
||||
:ref:`jtag-debugging-selecting-jtag-adapter`
|
||||
What are the criteria and options to select JTAG adapter hardware.
|
||||
:ref:`jtag-debugging-setup-openocd`
|
||||
Procedure to install OpenOCD using prebuilt software packages for :doc:`Windows <setup-openocd-windows>`, :doc:`Linux <setup-openocd-linux>` and :doc:`MacOS <setup-openocd-macos>` operating systems.
|
||||
:ref:`jtag-debugging-configuring-esp32-target`
|
||||
Configuration of OpenOCD software and set up JTAG adapter hardware that will make together a debugging target.
|
||||
:ref:`jtag-debugging-launching-debugger`
|
||||
Steps to start up a debug session with GDB from :ref:`jtag-debugging-using-debugger-eclipse` and from :ref:`jtag-debugging-using-debugger-command-line`.
|
||||
:ref:`jtag-debugging-examples`
|
||||
If you are not familiar with GDB, check this section for debugging examples provided from from :ref:`jtag-debugging-examples-eclipse` as well as from :ref:`jtag-debugging-examples-command-line`.
|
||||
:ref:`jtag-debugging-building-openocd`
|
||||
Procedure to build OpenOCD from sources for :doc:`Windows <building-openocd-windows>`, :doc:`Linux <building-openocd-linux>` and :doc:`MacOS <building-openocd-macos>` operating systems.
|
||||
:ref:`jtag-debugging-tips-and-quirks`
|
||||
This section provides collection of tips and quirks related JTAG debugging of ESP32 with OpenOCD and GDB.
|
||||
|
||||
|
||||
.. _jtag-debugging-introduction:
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
The ESP32 has two powerful Xtensa cores, allowing for a great deal of variety of program architectures. The FreeRTOS OS that comes with ESP-IDF is capable of multi-core preemptive multithreading, allowing for an intuitive way of writing software.
|
||||
|
||||
The downside of the ease of programming is that debugging without the right tools is harder: figuring out a bug that is caused by two threads, running even simultaneously on two different CPU cores, can take a long time when all you have are printf statements. A better and in many cases quicker way to debug such problems is by using a debugger, connected to the processors over a debug port.
|
||||
|
||||
Espressif has ported OpenOCD to support the ESP32 processor and the multicore FreeRTOS, which will be the foundation of most ESP32 apps, and has written some tools to help with features OpenOCD does not support natively.
|
||||
|
||||
This document provides a guide to installing OpenOCD for ESP32 and debugging using GDB under Linux, Windows and MacOS. Except for OS specific installation procedures, the s/w user interface and use procedures are the same across all supported operating systems.
|
||||
|
||||
.. note::
|
||||
|
||||
Screenshots presented in this document have been made for Eclipse Neon 3 running on Ubuntu 16.04 LTE. There may be some small differences in what a particular user interface looks like, depending on whether you are using Windows, MacOS or Linux and / or a different release of Eclipse.
|
||||
|
||||
.. _jtag-debugging-how-it-works:
|
||||
|
||||
How it Works?
|
||||
-------------
|
||||
|
||||
The key software and hardware to perform debugging of ESP32 with OpenOCD over JTAG (Joint Test Action Group) interface is presented below and includes **xtensa-esp32-elf-gdb debugger**, **OpenOCD on chip debugger** and **JTAG adapter** connected to **ESP32** target.
|
||||
|
||||
.. figure:: ../../_static/jtag-debugging-overview.jpg
|
||||
:align: center
|
||||
:alt: JTAG debugging - overview diagram
|
||||
:figclass: align-center
|
||||
|
||||
JTAG debugging - overview diagram
|
||||
|
||||
Under "Application Loading and Monitoring" there is another software and hardware to compile, build and flash application to ESP32, as well as to provide means to monitor diagnostic messages from ESP32.
|
||||
|
||||
Debugging using JTAG and application loading / monitoring is integrated under the `Eclipse <https://www.eclipse.org/>`_ environment, to provide quick and easy transition from writing, compiling and loading the code to debugging, back to writing the code, and so on. All the software is available for Windows, Linux and MacOS platforms.
|
||||
|
||||
If the :doc:`ESP32 WROVER KIT <../../hw-reference/modules-and-boards>` is used, then connection from PC to ESP32 is done effectively with a single USB cable thanks to FT2232H chip installed on WROVER, which provides two USB channels, one for JTAG and the second for UART connection.
|
||||
|
||||
Depending on user preferences, both `debugger` and `make` can be operated directly from terminal / command line, instead from Eclipse.
|
||||
|
||||
|
||||
.. _jtag-debugging-selecting-jtag-adapter:
|
||||
|
||||
Selecting JTAG Adapter
|
||||
----------------------
|
||||
|
||||
The quickest and most convenient way to start with JTAG debugging is by using :doc:`ESP32 WROVER KIT <../../hw-reference/modules-and-boards>`. Each version of this development board has JTAG interface already build in. No need for an external JTAG adapter and extra wiring / cable to connect JTAG to ESP32. WROVER KIT is using FT2232H JTAG interface operating at 20 MHz clock speed, which is difficult to achieve with an external adapter.
|
||||
|
||||
If you decide to use separate JTAG adapter, look for one that is compatible with both the voltage levels on the ESP32 as well as with the OpenOCD software. The JTAG port on the ESP32 is an industry-standard JTAG port which lacks (and does not need) the TRST pin. The JTAG I/O pins all are powered from the VDD_3P3_RTC pin (which normally would be powered by a 3.3V rail) so the JTAG adapter needs to be able to work with JTAG pins in that voltage range.
|
||||
|
||||
On the software side, OpenOCD supports a fair amount of JTAG adapters. See http://openocd.org/doc/html/Debug-Adapter-Hardware.html for an (unfortunately slightly incomplete) list of the adapters OpenOCD works with. This page lists SWD-compatible adapters as well; take note that the ESP32 does not support SWD. JTAG adapters that are hardcoded to a specific product line, e.g. STM32 debugging adapters, will not work.
|
||||
|
||||
The minimal signalling to get a working JTAG connection are TDI, TDO, TCK, TMS and GND. Some JTAG debuggers also need a connection from the ESP32 power line to a line called e.g. Vtar to set the working voltage. SRST can optionally be connected to the CH_PD of the ESP32, although for now, support in OpenOCD for that line is pretty minimal.
|
||||
|
||||
|
||||
.. _jtag-debugging-setup-openocd:
|
||||
|
||||
Setup of OpenOCD
|
||||
----------------
|
||||
|
||||
This step covers installation of OpenOCD binaries. If you like to build OpenOCS from sources then refer to section :ref:`jtag-debugging-building-openocd`. All OpenOCD files will be placed in ``~/esp/openocd-esp32`` directory. You may choose any other directory, but need to adjust respective paths used in examples.
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
Windows <setup-openocd-windows>
|
||||
Linux <setup-openocd-linux>
|
||||
MacOS <setup-openocd-macos>
|
||||
|
||||
Pick up your OS below and follow provided instructions to setup OpenOCD.
|
||||
|
||||
+-------------------+-------------------+-------------------+
|
||||
| |windows-logo| | |linux-logo| | |macos-logo| |
|
||||
+-------------------+-------------------+-------------------+
|
||||
| `Windows`_ | `Linux`_ | `Mac OS`_ |
|
||||
+-------------------+-------------------+-------------------+
|
||||
|
||||
.. |windows-logo| image:: ../../_static/windows-logo.png
|
||||
:target: ../jtag-debugging/setup-openocd-windows.html
|
||||
|
||||
.. |linux-logo| image:: ../../_static/linux-logo.png
|
||||
:target: ../jtag-debugging/setup-openocd-linux.html
|
||||
|
||||
.. |macos-logo| image:: ../../_static/macos-logo.png
|
||||
:target: ../jtag-debugging/setup-openocd-macos.html
|
||||
|
||||
.. _Windows: ../jtag-debugging/setup-openocd-windows.html
|
||||
.. _Linux: ../jtag-debugging/setup-openocd-linux.html
|
||||
.. _Mac OS: ../jtag-debugging/setup-openocd-macos.html
|
||||
|
||||
After installation is complete, get familiar with two key directories inside ``openocd-esp32`` installation folder:
|
||||
|
||||
* ``bin`` containing OpenOCD executable
|
||||
* ``share\openocd\scripts`` containing configuration files invoked together with OpenOCD as command line parameters
|
||||
|
||||
.. note::
|
||||
|
||||
Directory names and structure above are specific to binary distribution of OpenOCD. They are used in examples of invoking OpenOCD throughout this guide. Directories for OpenOCD build from sources are different, so the way to invoke OpenOCD. For details see :ref:`jtag-debugging-building-openocd`.
|
||||
|
||||
|
||||
.. _jtag-debugging-configuring-esp32-target:
|
||||
|
||||
Configuring ESP32 Target
|
||||
------------------------
|
||||
|
||||
Once OpenOCD is installed, move to configuring ESP32 target (i.e ESP32 board with JTAG interface). You will do it in the following three steps:
|
||||
|
||||
* Configure and connect JTAG interface
|
||||
* Run OpenOCD
|
||||
* Upload application for debugging
|
||||
|
||||
|
||||
Configure and connect JTAG interface
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This step depends on JTAG and ESP32 board you are using - see the two cases described below.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
configure-wrover
|
||||
configure-other-jtag
|
||||
|
||||
|
||||
.. _jtag-debugging-run-openocd:
|
||||
|
||||
Run OpenOCD
|
||||
^^^^^^^^^^^
|
||||
|
||||
Once target is configured and connected to computer, you are ready to launch OpenOCD.
|
||||
|
||||
.. highlight:: bash
|
||||
|
||||
Open terminal, go to directory where OpenOCD is installed and start it up::
|
||||
|
||||
cd ~/esp/openocd-esp32
|
||||
bin/openocd -s share/openocd/scripts -f interface/ftdi/esp32_devkitj_v1.cfg -f board/esp-wroom-32.cfg
|
||||
|
||||
.. note::
|
||||
|
||||
The files provided after ``-f`` above, are specific for ESP-WROVER-KIT with ESP-WROOM-32 module. You may need to provide different files depending on used hardware, For guidance see :ref:`jtag-debugging-tip-openocd-configure-target`.
|
||||
|
||||
.. highlight:: none
|
||||
|
||||
You should now see similar output (this log is for ESP32 WROVER KIT)::
|
||||
|
||||
user-name@computer-name:~/esp/openocd-esp32$ bin/openocd -s share/openocd/scripts -f interface/ftdi/esp32_devkitj_v1.cfg -f board/esp-wroom-32.cfg
|
||||
Open On-Chip Debugger 0.10.0-dev-ged7b1a9 (2017-07-10-07:16)
|
||||
Licensed under GNU GPL v2
|
||||
For bug reports, read
|
||||
http://openocd.org/doc/doxygen/bugs.html
|
||||
none separate
|
||||
adapter speed: 20000 kHz
|
||||
force hard breakpoints
|
||||
Info : ftdi: if you experience problems at higher adapter clocks, try the command "ftdi_tdo_sample_edge falling"
|
||||
Info : clock speed 20000 kHz
|
||||
Info : JTAG tap: esp32.cpu0 tap/device found: 0x120034e5 (mfg: 0x272 (Tensilica), part: 0x2003, ver: 0x1)
|
||||
Info : JTAG tap: esp32.cpu1 tap/device found: 0x120034e5 (mfg: 0x272 (Tensilica), part: 0x2003, ver: 0x1)
|
||||
Info : esp32: Debug controller was reset (pwrstat=0x5F, after clear 0x0F).
|
||||
Info : esp32: Core was reset (pwrstat=0x5F, after clear 0x0F).
|
||||
|
||||
* If there is an error indicating permission problems, please see the "Permissions delegation" bit in the OpenOCD README file in ``~/esp/openocd-esp32`` directory.
|
||||
* In case there is an error finding configuration files, e.g. ``Can't find interface/ftdi/esp32_devkitj_v1.cfg``, check the path after ``-s``. This path is used by OpenOCD to look for the files specified after ``-f``. Also check if the file is indeed under provided path.
|
||||
* If you see JTAG errors (...all ones/...all zeroes) please check your connections, whether no other signals are connected to JTAG besides ESP32's pins, and see if everything is powered on.
|
||||
|
||||
|
||||
.. _jtag-upload-app-debug:
|
||||
|
||||
Upload application for debugging
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Build and upload your application to ESP32 as usual, see :ref:`get-started-build-flash`.
|
||||
|
||||
Another option is to write application image to flash using OpenOCD via JTAG with commands like this::
|
||||
|
||||
cd ~/esp/openocd-esp32
|
||||
bin/openocd -s share/openocd/scripts -f interface/ftdi/esp32_devkitj_v1.cfg -f board/esp-wroom-32.cfg -c "program_esp32 filename.bin 0x10000 verify exit"
|
||||
|
||||
OpenOCD flashing command ``program_esp32`` has the following format:
|
||||
|
||||
``program_esp32 <image_file> <offset> [verify] [reset] [exit]``
|
||||
|
||||
- ``image_file`` - Path to program image file.
|
||||
- ``offset`` - Offset in flash bank to write image.
|
||||
- ``verify`` - Optional. Verify flash contents after writing.
|
||||
- ``reset`` - Optional. Reset target after programing.
|
||||
- ``exit`` - Optional. Finally exit OpenOCD.
|
||||
|
||||
You are now ready to start application debugging. Follow steps described in section below.
|
||||
|
||||
|
||||
.. _jtag-debugging-launching-debugger:
|
||||
|
||||
Launching Debugger
|
||||
------------------
|
||||
|
||||
The toolchain for ESP32 features GNU Debugger, in short GDB. It is available with other toolchain programs under filename ``xtensa-esp32-elf-gdb``. GDB can be called and operated directly from command line in a terminal. Another option is to call it from within IDE (like Eclipse, Visual Studio Code, etc.) and operate indirectly with help of GUI instead of typing commands in a terminal.
|
||||
|
||||
Both options of using debugger are discussed under links below.
|
||||
|
||||
* :ref:`jtag-debugging-using-debugger-eclipse`
|
||||
* :ref:`jtag-debugging-using-debugger-command-line`
|
||||
|
||||
It is recommended to first check if debugger works from :ref:`jtag-debugging-using-debugger-command-line` and then move to using :ref:`jtag-debugging-using-debugger-eclipse`.
|
||||
|
||||
|
||||
.. _jtag-debugging-examples:
|
||||
|
||||
Debugging Examples
|
||||
------------------
|
||||
|
||||
This section is intended for users not familiar with GDB. It presents example debugging session from :ref:`jtag-debugging-examples-eclipse` using simple application available under :example:`get-started/blink` and covers the following debugging actions:
|
||||
|
||||
1. :ref:`jtag-debugging-examples-eclipse-01`
|
||||
2. :ref:`jtag-debugging-examples-eclipse-02`
|
||||
3. :ref:`jtag-debugging-examples-eclipse-03`
|
||||
4. :ref:`jtag-debugging-examples-eclipse-04`
|
||||
5. :ref:`jtag-debugging-examples-eclipse-05`
|
||||
6. :ref:`jtag-debugging-examples-eclipse-06`
|
||||
7. :ref:`jtag-debugging-examples-eclipse-07`
|
||||
|
||||
Similar debugging actions are provided using GDB from :ref:`jtag-debugging-examples-command-line`.
|
||||
|
||||
Before proceeding to examples, set up your ESP32 target and load it with :example:`get-started/blink`.
|
||||
|
||||
|
||||
.. _jtag-debugging-building-openocd:
|
||||
|
||||
Building OpenOCD from Sources
|
||||
-----------------------------
|
||||
|
||||
Please refer to separate documents listed below, that describe build process.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
Windows <building-openocd-windows>
|
||||
Linux <building-openocd-linux>
|
||||
MacOS <building-openocd-macos>
|
||||
|
||||
.. note::
|
||||
|
||||
Examples of invoking OpenOCD in this document assume using pre-built binary distribution described in section :ref:`jtag-debugging-setup-openocd`. To use binaries build locally from sources, change the path to OpenOCD executable to ``src/openocd`` and the path to configuration files to ``-s tcl``.
|
||||
|
||||
Example of invoking OpenOCD build locally from sources::
|
||||
|
||||
src/openocd -s tcl -f interface/ftdi/esp32_devkitj_v1.cfg -f board/esp-wroom-32.cfg
|
||||
|
||||
|
||||
.. _jtag-debugging-tips-and-quirks:
|
||||
|
||||
Tips and Quirks
|
||||
---------------
|
||||
|
||||
This section provides collection of links to all tips and quirks referred to from various parts of this guide.
|
||||
|
||||
* :ref:`jtag-debugging-tip-breakpoints`
|
||||
* :ref:`jtag-debugging-tip-where-breakpoints`
|
||||
* :ref:`jtag-debugging-tip-why-next-works-as-step`
|
||||
* :ref:`jtag-debugging-tip-code-options`
|
||||
* :ref:`jtag-debugging-tip-freertos-support`
|
||||
* :ref:`jtag-debugging-tip-code-flash-voltage`
|
||||
* :ref:`jtag-debugging-tip-optimize-jtag-speed`
|
||||
* :ref:`jtag-debugging-tip-debugger-startup-commands`
|
||||
* :ref:`jtag-debugging-tip-openocd-configure-target`
|
||||
* :ref:`jtag-debugging-tip-reset-by-debugger`
|
||||
* :ref:`jtag-debugging-tip-jtag-pins-reconfigured`
|
||||
* :ref:`jtag-debugging-tip-reporting-issues`
|
||||
|
||||
|
||||
Related Documents
|
||||
-----------------
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
using-debugger
|
||||
debugging-examples
|
||||
tips-and-quirks
|
||||
../app_trace
|
||||
@@ -0,0 +1,32 @@
|
||||
***********************
|
||||
Setup OpenOCD for Linux
|
||||
***********************
|
||||
|
||||
|
||||
Setup OpenOCD
|
||||
=============
|
||||
|
||||
OpenOCD for 64-bit Linux is available for download from Espressif website:
|
||||
|
||||
https://dl.espressif.com/dl/openocd-esp32-linux64-a859564.tar.gz
|
||||
|
||||
Download this file, then extract it in ``~/esp/`` directory::
|
||||
|
||||
cd ~/esp
|
||||
tar -xzf ~/Downloads/openocd-esp32-linux64-a859564.tar.gz
|
||||
|
||||
|
||||
Next Steps
|
||||
==========
|
||||
|
||||
To carry on with debugging environment setup, proceed to section :ref:`jtag-debugging-configuring-esp32-target`.
|
||||
|
||||
|
||||
Related Documents
|
||||
=================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
building-openocd-linux
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
***********************
|
||||
Setup OpenOCD for MacOS
|
||||
***********************
|
||||
|
||||
|
||||
Setup OpenOCD
|
||||
=============
|
||||
|
||||
OpenOCD for MacOS is available for download from Espressif website:
|
||||
|
||||
https://dl.espressif.com/dl/openocd-esp32-macos-a859564.tar.gz
|
||||
|
||||
Download this file, then extract it in ``~/esp`` directory::
|
||||
|
||||
cd ~/esp
|
||||
tar -xzf ~/Downloads/openocd-esp32-macos-a859564.tar.gz
|
||||
|
||||
|
||||
Next Steps
|
||||
==========
|
||||
|
||||
To carry on with debugging environment setup, proceed to section :ref:`jtag-debugging-configuring-esp32-target`.
|
||||
|
||||
|
||||
Related Documents
|
||||
=================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
building-openocd-macos
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
*************************
|
||||
Setup OpenOCD for Windows
|
||||
*************************
|
||||
|
||||
|
||||
Setup OpenOCD
|
||||
=============
|
||||
|
||||
OpenOCD for Windows / MSYS2 is available for download from Espressif website:
|
||||
|
||||
https://dl.espressif.com/dl/openocd-esp32-win32-a859564.zip
|
||||
|
||||
Download this file and extract ``openocd-esp32`` folder inside to ``~/esp/`` directory.
|
||||
|
||||
|
||||
Next Steps
|
||||
==========
|
||||
|
||||
To carry on with debugging environment setup, proceed to section :ref:`jtag-debugging-configuring-esp32-target`.
|
||||
|
||||
|
||||
Related Documents
|
||||
=================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
building-openocd-windows
|
||||
@@ -0,0 +1,262 @@
|
||||
Tips and Quirks
|
||||
---------------
|
||||
|
||||
This section provides collection of all tips and quirks referred to from various parts of this guide.
|
||||
|
||||
|
||||
.. _jtag-debugging-tip-breakpoints:
|
||||
|
||||
Breakpoints and watchpoints available
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The ESP32 supports 2 hardware breakpoints. It also supports two watchpoints, so two variables can be watched for change or read by the GDB command ``watch myVariable``. Note that menuconfig option :ref:`CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK` uses the 2nd watchpoint and will not provide expected results, if you also try to use it within OpenOCD / GDB. See menuconfig's help for detailed description.
|
||||
|
||||
|
||||
.. _jtag-debugging-tip-where-breakpoints:
|
||||
|
||||
What else should I know about breakpoints?
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Normal GDB breakpoints (``b myFunction``) can only be set in IRAM, because that memory is writable. Setting these types of breakpoints in code in flash will not work. Instead, use a hardware breakpoint (``hb myFunction``).
|
||||
|
||||
|
||||
.. _jtag-debugging-tip-why-next-works-as-step:
|
||||
|
||||
Why stepping with "next" does not bypass subroutine calls?
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
When stepping through the code with ``next`` command, GDB is internally setting a breakpoint (one out of two available) ahead in the code to bypass the subroutine calls. This functionality will not work, if the two available breakpoints are already set elsewhere in the code. If this is the case, delete breakpoints to have one "spare". With both breakpoints already used, stepping through the code with ``next`` command will work as like with ``step`` command and debugger will step inside subroutine calls.
|
||||
|
||||
|
||||
.. _jtag-debugging-tip-code-options:
|
||||
|
||||
Support options for OpenOCD at compile time
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
ESP-IDF has some support options for OpenOCD debugging which can be set at compile time:
|
||||
|
||||
* :ref:`CONFIG_ESP32_DEBUG_OCDAWARE` is enabled by default. If a panic or unhandled exception is thrown and a JTAG debugger is connected (ie openocd is running), ESP-IDF will break into the debugger.
|
||||
* :ref:`CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK` (disabled by default) sets watchpoint index 1 (the second of two) at the end of any task stack. This is the most accurate way to debug task stack overflows. Click the link for more details.
|
||||
|
||||
Please see the :ref:`make menuconfig <get-started-configure>` menu for more details on setting compile-time options.
|
||||
|
||||
.. _jtag-debugging-tip-freertos-support:
|
||||
|
||||
FreeRTOS support
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
OpenOCD has explicit support for the ESP-IDF FreeRTOS. GDB can see FreeRTOS tasks as threads. Viewing them all can be done using the GDB ``i threads`` command, changing to a certain task is done with ``thread n``, with ``n`` being the number of the thread. FreeRTOS detection can be disabled in target's configuration. For more details see :ref:`jtag-debugging-tip-openocd-configure-target`.
|
||||
|
||||
|
||||
.. _jtag-debugging-tip-code-flash-voltage:
|
||||
|
||||
Why to set SPI flash voltage in OpenOCD configuration?
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The MTDI pin of ESP32, being among four pins used for JTAG communication, is also one of ESP32's bootstrapping pins. On power up ESP32 is sampling binary level on MTDI to set it's internal voltage regulator used to supply power to external SPI flash chip. If binary level on MDTI pin on power up is low, the voltage regulator is set to deliver 3.3V, if it is high, then the voltage is set to 1.8V. The MTDI pin should have a pull-up or may rely on internal weak pull down resistor (see ESP32 Datasheet for details), depending on the type of SPI chip used. Once JTAG is connected, it overrides the pull-up or pull-down resistor that is supposed to do the bootstrapping.
|
||||
|
||||
To handle this issue OpenOCD's board configuration file (e.g. ``boards\esp-wroom-32.cfg`` for ESP-WROOM-32 module) provides ``ESP32_FLASH_VOLTAGE`` parameter to set the idle state of the ``TDO`` line to a specified binary level, therefore reducing the chance of a bad bootup of application due to incorrect flash voltage.
|
||||
|
||||
Check specification of ESP32 module connected to JTAG, what is the power supply voltage of SPI flash chip. Then set ``ESP32_FLASH_VOLTAGE`` accordingly. Most WROOM modules use 3.3V flash, while WROVER modules use 1.8V flash.
|
||||
|
||||
|
||||
.. _jtag-debugging-tip-optimize-jtag-speed:
|
||||
|
||||
Optimize JTAG speed
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In order to achieve higher data rates and minimize number of dropped packets it is recommended to optimize setting of JTAG clock frequency, so it is at maximum and still provides stable operation of JTAG. To do so use the following tips.
|
||||
|
||||
1. The upper limit of JTAG clock frequency is 20 MHz if CPU runs at 80 MHz, or 26 MHz if CPU runs at 160 MHz or 240 MHz.
|
||||
2. Depending on particular JTAG adapter and the length of connecting cables, you may need to reduce JTAG frequency below 20 / 26 MHz.
|
||||
3. In particular reduce frequency, if you get DSR/DIR errors (and they do not relate to OpenOCD trying to read from a memory range without physical memory being present there).
|
||||
4. ESP-WROVER-KIT operates stable at 20 / 26 MHz.
|
||||
|
||||
|
||||
.. _jtag-debugging-tip-debugger-startup-commands:
|
||||
|
||||
What is the meaning of debugger's startup commands?
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
On startup, debugger is issuing sequence of commands to reset the chip and halt it at specific line of code. This sequence (shown below) is user defined to pick up at most convenient / appropriate line and start debugging.
|
||||
|
||||
* ``mon reset halt`` — reset the chip and keep the CPUs halted
|
||||
* ``thb app_main`` — insert a temporary hardware breakpoint at ``app_main``, put here another function name if required
|
||||
* ``x $a1=0`` — this is the tricky part. As far as we can tell, there is no way for a ``mon`` command to tell GDB that the target state has changed. GDB will assume that whatever stack the target had before ``mon reset halt`` will still be valid. In fact, after reset the target state will change and executing ``x $a1=0`` is a way to force GDB to get new state from the target.
|
||||
* ``c`` — resume the program. It will then stop at breakpoint inserted at ``app_main``.
|
||||
|
||||
|
||||
.. _jtag-debugging-tip-openocd-configure-target:
|
||||
|
||||
Configuration of OpenOCD for specific target
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
OpenOCD needs to be told what JTAG adapter **interface** to use, as well as what type of **board** and processor the JTAG adapter is connected to. To do so, use existing configuration files located in OpenOCD's ``share/openocd/scripts/interface`` and ``share/openocd/scripts/board`` folders.
|
||||
|
||||
For example, if you connect to ESP-WROVER-KIT with ESP-WROOM-32 module installed (see section :doc:`ESP32 WROVER KIT <../../hw-reference/modules-and-boards>`), use the following configuration files:
|
||||
|
||||
* ``interface/ftdi/esp32_devkitj_v1.cfg``
|
||||
* ``board/esp-wroom-32.cfg``
|
||||
|
||||
Optionally prepare configuration by yourself. To do so, you can check existing files and modify them to match you specific hardware. Below is the summary of available configuration parameters for **board** configuration.
|
||||
|
||||
|
||||
.. highlight:: none
|
||||
|
||||
Adapter's clock speed
|
||||
""""""""""""""""""""""
|
||||
|
||||
::
|
||||
|
||||
adapter_khz 20000
|
||||
|
||||
See :ref:`jtag-debugging-tip-optimize-jtag-speed` for guidance how to set this value.
|
||||
|
||||
|
||||
Single core debugging
|
||||
"""""""""""""""""""""
|
||||
|
||||
::
|
||||
|
||||
set ESP32_ONLYCPU 1
|
||||
|
||||
Comment out this line for dual core debugging.
|
||||
|
||||
|
||||
Disable RTOS support
|
||||
""""""""""""""""""""
|
||||
|
||||
::
|
||||
|
||||
set ESP32_RTOS none
|
||||
|
||||
Comment out this line to have RTOS support.
|
||||
|
||||
|
||||
Power supply voltage of ESP32's SPI flash chip
|
||||
""""""""""""""""""""""""""""""""""""""""""""""
|
||||
|
||||
::
|
||||
|
||||
set ESP32_FLASH_VOLTAGE 1.8
|
||||
|
||||
Comment out this line to set 3.3V, ref: :ref:`jtag-debugging-tip-code-flash-voltage`
|
||||
|
||||
|
||||
Configuration file for ESP32 targets
|
||||
""""""""""""""""""""""""""""""""""""
|
||||
|
||||
::
|
||||
|
||||
source [find target/esp32.cfg]
|
||||
|
||||
.. note::
|
||||
|
||||
Do not change ``source [find target/esp32.cfg]`` line unless you are familiar with OpenOCD internals.
|
||||
|
||||
Currently ``target/esp32.cfg`` remains the only configuration file for ESP32 targets (esp108 and esp32). The matrix of supported configurations is as follows:
|
||||
|
||||
+---------------+---------------+---------------+
|
||||
| Dual/single | RTOS | Target used |
|
||||
+===============+===============+===============+
|
||||
| dual | FreeRTOS | esp32 |
|
||||
+---------------+---------------+---------------+
|
||||
| single | FreeRTOS | esp108 (*) |
|
||||
+---------------+---------------+---------------+
|
||||
| dual | none | esp108 |
|
||||
+---------------+---------------+---------------+
|
||||
| single | none | esp108 |
|
||||
+---------------+---------------+---------------+
|
||||
|
||||
(*) — we plan to fix this and add support for single core debugging with esp32 target in a subsequent commits.
|
||||
|
||||
Look inside ``board/esp-wroom-32.cfg`` for additional information provided in comments besides each configuration parameter.
|
||||
|
||||
|
||||
.. _jtag-debugging-tip-reset-by-debugger:
|
||||
|
||||
How debugger resets ESP32?
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The board can be reset by entering ``mon reset`` or ``mon reset halt`` into GDB.
|
||||
|
||||
|
||||
.. _jtag-debugging-tip-jtag-pins-reconfigured:
|
||||
|
||||
Do not use JTAG pins for something else
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Operation of JTAG may be disturbed, if some other h/w is connected to JTAG pins besides ESP32 module and JTAG adapter. ESP32 JTAG us using the following pins:
|
||||
|
||||
+---+----------------+-------------+
|
||||
| | ESP32 JTAG Pin | JTAG Signal |
|
||||
+===+================+=============+
|
||||
| 1 | MTDO / GPIO15 | TDO |
|
||||
+---+----------------+-------------+
|
||||
| 2 | MTDI / GPIO12 | TDI |
|
||||
+---+----------------+-------------+
|
||||
| 3 | MTCK / GPIO13 | TCK |
|
||||
+---+----------------+-------------+
|
||||
| 4 | MTMS / GPIO14 | TMS |
|
||||
+---+----------------+-------------+
|
||||
|
||||
JTAG communication will likely fail, if configuration of JTAG pins is changed by user application. If OpenOCD initializes correctly (detects the two Tensilica cores), but loses sync and spews out a lot of DTR/DIR errors when the program is ran, it is likely that the application reconfigures the JTAG pins to something else, or the user forgot to connect Vtar to a JTAG adapter that needed it.
|
||||
|
||||
.. highlight:: none
|
||||
|
||||
Below is an excerpt from series of errors reported by GDB after the application stepped into the code that reconfigured MTDO / GPIO15 to be an input::
|
||||
|
||||
cpu0: xtensa_resume (line 431): DSR (FFFFFFFF) indicates target still busy!
|
||||
cpu0: xtensa_resume (line 431): DSR (FFFFFFFF) indicates DIR instruction generated an exception!
|
||||
cpu0: xtensa_resume (line 431): DSR (FFFFFFFF) indicates DIR instruction generated an overrun!
|
||||
cpu1: xtensa_resume (line 431): DSR (FFFFFFFF) indicates target still busy!
|
||||
cpu1: xtensa_resume (line 431): DSR (FFFFFFFF) indicates DIR instruction generated an exception!
|
||||
cpu1: xtensa_resume (line 431): DSR (FFFFFFFF) indicates DIR instruction generated an overrun!
|
||||
|
||||
|
||||
.. _jtag-debugging-tip-reporting-issues:
|
||||
|
||||
Reporting issues with OpenOCD / GDB
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In case you encounter a problem with OpenOCD or GDB programs itself and do not find a solution searching available resources on the web, open an issue in the OpenOCD issue tracker under https://github.com/espressif/openocd-esp32/issues.
|
||||
|
||||
1. In issue report provide details of your configuration:
|
||||
|
||||
a. JTAG adapter type.
|
||||
b. Release of ESP-IDF used to compile and load application that is being debugged.
|
||||
c. Details of OS used for debugging.
|
||||
d. Is OS running natively on a PC or on a virtual machine?
|
||||
|
||||
2. Create a simple example that is representative to observed issue. Describe steps how to reproduce it. In such an example debugging should not be affected by non-deterministic behaviour introduced by the Wi-Fi stack, so problems will likely be easier to reproduce, if encountered once.
|
||||
|
||||
.. highlight:: bash
|
||||
|
||||
3. Prepare logs from debugging session by adding additional parameters to start up commands.
|
||||
|
||||
OpenOCD:
|
||||
|
||||
::
|
||||
|
||||
bin/openocd -l openocd_log.txt -d 3 -s share/openocd/scripts -f interface/ftdi/esp32_devkitj_v1.cfg -f board/esp-wroom-32.cfg
|
||||
|
||||
Logging to a file this way will prevent information displayed on the terminal. This may be a good thing taken amount of information provided, when increased debug level ``-d 3`` is set. If you still like to see the log on the screen, then use another command instead:
|
||||
|
||||
::
|
||||
|
||||
bin/openocd -d 3 -s share/openocd/scripts -f interface/ftdi/esp32_devkitj_v1.cfg -f board/esp-wroom-32.cfg 2>&1 | tee openocd.log
|
||||
|
||||
.. note::
|
||||
|
||||
See :ref:`jtag-debugging-building-openocd` for slightly different command format, when running OpenOCD built from sources.
|
||||
|
||||
Debugger:
|
||||
|
||||
::
|
||||
|
||||
xtensa-esp32-elf-gdb -ex "set remotelogfile gdb_log.txt" <all other options>
|
||||
|
||||
Optionally add command ``remotelogfile gdb_log.txt`` to the ``gdbinit`` file.
|
||||
|
||||
|
||||
4. Attach both ``openocd_log.txt`` and ``gdb_log.txt`` files to your issue report.
|
||||
@@ -0,0 +1,183 @@
|
||||
Using Debugger
|
||||
--------------
|
||||
|
||||
This section covers configuration and running debugger either from :ref:`jtag-debugging-using-debugger-eclipse`
|
||||
or :ref:`jtag-debugging-using-debugger-command-line`. It is recommended to first check if debugger works from :ref:`jtag-debugging-using-debugger-command-line` and then move to using Eclipse.
|
||||
|
||||
|
||||
.. _jtag-debugging-using-debugger-eclipse:
|
||||
|
||||
Eclipse
|
||||
^^^^^^^
|
||||
|
||||
Debugging functionality is provided out of box in standard Eclipse installation. Another option is to use pluggins like "GDB Hardware Debugging" plugin. We have found this plugin quite convenient and decided to use throughout this guide.
|
||||
|
||||
To begin with, install "GDB Hardware Debugging" plugin by opening Eclipse and going to `Help` > `Install` New Software.
|
||||
|
||||
Once installation is complete, configure debugging session following steps below. Please note that some of configuration parameters are generic and some are project specific. This will be shown below by configuring debugging for "blink" example project. If not done already, add this project to Eclipse workspace following guidance in section :doc:`Build and Flash with Eclipse IDE <../../get-started/eclipse-setup>`. The source of :example:`get-started/blink` application is available in :idf:`examples` directory of ESP-IDF repository.
|
||||
|
||||
1. In Eclipse go to `Run` > `Debug Configuration`. A new window will open. In the window's left pane double click "GDB Hardware Debugging" (or select "GDB Hardware Debugging" and press the "New" button) to create a new configuration.
|
||||
|
||||
2. In a form that will show up on the right, enter the "Name:" of this configuration, e.g. "Blink checking".
|
||||
|
||||
3. On the "Main" tab below, under "Project:", press "Browse" button and select the "blink" project.
|
||||
|
||||
4. In next line "C/C++ Application:" press "Browse" button and select "blink.elf" file. If "blink.elf" is not there, then likely this project has not been build yet. See :doc:`Build and Flash with Eclipse IDE <../../get-started/eclipse-setup>` how to do it.
|
||||
|
||||
5. Finally, under "Build (if required) before launching" click "Disable auto build".
|
||||
|
||||
A sample window with settings entered in points 1 - 5 is shown below.
|
||||
|
||||
.. figure:: ../../_static/hw-debugging-main-tab.jpg
|
||||
:align: center
|
||||
:alt: Configuration of GDB Hardware Debugging - Main tab
|
||||
:figclass: align-center
|
||||
|
||||
Configuration of GDB Hardware Debugging - Main tab
|
||||
|
||||
6. Click "Debugger" tab. In field "GDB Command" enter ``xtensa-esp32-elf-gdb`` to invoke debugger.
|
||||
|
||||
7. Change default configuration of "Remote host" by entering ``3333`` under the "Port number".
|
||||
|
||||
Configuration entered in points 6 and 7 is shown on the following picture.
|
||||
|
||||
.. figure:: ../../_static/hw-debugging-debugger-tab.jpg
|
||||
:align: center
|
||||
:alt: Configuration of GDB Hardware Debugging - Debugger tab
|
||||
:figclass: align-center
|
||||
|
||||
Configuration of GDB Hardware Debugging - Debugger tab
|
||||
|
||||
8. The last tab to that requires changing of default configuration is "Startup". Under "Initialization Commands" uncheck "Reset and Delay (seconds)" and "Halt"". Then, in entry field below, type ``mon reset halt`` and ``x $a1=0`` (in two separate lines).
|
||||
|
||||
.. note::
|
||||
If you want to update image in the flash automatically before starting new debug session add the following lines of commands at the beginning of "Initialization Commands" textbox::
|
||||
|
||||
mon reset halt
|
||||
mon program_esp32 ${workspace_loc:blink/build/blink.bin} 0x10000 verify
|
||||
|
||||
|
||||
For description of ``program_esp32`` command see :ref:`jtag-upload-app-debug`.
|
||||
|
||||
9. Under "Load Image and Symbols" uncheck "Load image" option.
|
||||
|
||||
10. Further down on the same tab, establish an initial breakpoint to halt CPUs after they are reset by debugger. The plugin will set this breakpoint at the beginning of the function entered under "Set break point at:". Checkout this option and enter ``app_main`` in provided field.
|
||||
|
||||
11. Checkout "Resume" option. This will make the program to resume after ``mon reset halt`` is invoked per point 8. The program will then stop at breakpoint inserted at ``app_main``.
|
||||
|
||||
Configuration described in points 8 - 11 is shown below.
|
||||
|
||||
.. figure:: ../../_static/hw-debugging-startup-tab.jpg
|
||||
:align: center
|
||||
:alt: Configuration of GDB Hardware Debugging - Startup tab
|
||||
:figclass: align-center
|
||||
|
||||
Configuration of GDB Hardware Debugging - Startup tab
|
||||
|
||||
If the "Startup" sequence looks convoluted and respective "Initialization Commands" are not clear to you, check :ref:`jtag-debugging-tip-debugger-startup-commands` for additional explanation.
|
||||
|
||||
12. If you previously completed :ref:`jtag-debugging-configuring-esp32-target` steps described above, so the target is running and ready to talk to debugger, go right to debugging by pressing "Debug" button. Otherwise press "Apply" to save changes, go back to :ref:`jtag-debugging-configuring-esp32-target` and return here to start debugging.
|
||||
|
||||
Once all 1 - 12 configuration steps are satisfied, the new Eclipse perspective called "Debug" will open as shown on example picture below.
|
||||
|
||||
.. figure:: ../../_static/debug-perspective.jpg
|
||||
:align: center
|
||||
:alt: Debug Perspective in Eclipse
|
||||
:figclass: align-center
|
||||
|
||||
Debug Perspective in Eclipse
|
||||
|
||||
If you are not quite sure how to use GDB, check :ref:`jtag-debugging-examples-eclipse` example debugging session in section :ref:`jtag-debugging-examples`.
|
||||
|
||||
|
||||
.. _jtag-debugging-using-debugger-command-line:
|
||||
|
||||
Command Line
|
||||
^^^^^^^^^^^^
|
||||
|
||||
1. To be able start debugging session, the target should be up and running. If not done already, complete steps described under :ref:`jtag-debugging-configuring-esp32-target`.
|
||||
|
||||
.. highlight:: bash
|
||||
|
||||
2. Open a new terminal session and go to directory that contains project for debugging, e.g.
|
||||
|
||||
::
|
||||
|
||||
cd ~/esp/blink
|
||||
|
||||
.. highlight:: none
|
||||
|
||||
3. When launching a debugger, you will need to provide couple of configuration parameters and commands. Instead of entering them one by one in command line, create a configuration file and name it ``gdbinit``:
|
||||
|
||||
::
|
||||
|
||||
target remote :3333
|
||||
mon reset halt
|
||||
thb app_main
|
||||
x $a1=0
|
||||
c
|
||||
|
||||
Save this file in current directory.
|
||||
|
||||
For more details what's inside ``gdbinit`` file, see :ref:`jtag-debugging-tip-debugger-startup-commands`
|
||||
|
||||
.. highlight:: bash
|
||||
|
||||
4. Now you are ready to launch GDB. Type the following in terminal:
|
||||
|
||||
::
|
||||
|
||||
xtensa-esp32-elf-gdb -x gdbinit build/blink.elf
|
||||
|
||||
.. highlight:: none
|
||||
|
||||
5. If previous steps have been done correctly, you will see a similar log concluded with ``(gdb)`` prompt:
|
||||
|
||||
::
|
||||
|
||||
user-name@computer-name:~/esp/blink$ xtensa-esp32-elf-gdb -x gdbinit build/blink.elf
|
||||
GNU gdb (crosstool-NG crosstool-ng-1.22.0-61-gab8375a) 7.10
|
||||
Copyright (C) 2015 Free Software Foundation, Inc.
|
||||
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
|
||||
This is free software: you are free to change and redistribute it.
|
||||
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
|
||||
and "show warranty" for details.
|
||||
This GDB was configured as "--host=x86_64-build_pc-linux-gnu --target=xtensa-esp32-elf".
|
||||
Type "show configuration" for configuration details.
|
||||
For bug reporting instructions, please see:
|
||||
<http://www.gnu.org/software/gdb/bugs/>.
|
||||
Find the GDB manual and other documentation resources online at:
|
||||
<http://www.gnu.org/software/gdb/documentation/>.
|
||||
For help, type "help".
|
||||
Type "apropos word" to search for commands related to "word"...
|
||||
Reading symbols from build/blink.elf...done.
|
||||
0x400d10d8 in esp_vApplicationIdleHook () at /home/user-name/esp/esp-idf/components/esp32/./freertos_hooks.c:52
|
||||
52 asm("waiti 0");
|
||||
JTAG tap: esp32.cpu0 tap/device found: 0x120034e5 (mfg: 0x272 (Tensilica), part: 0x2003, ver: 0x1)
|
||||
JTAG tap: esp32.slave tap/device found: 0x120034e5 (mfg: 0x272 (Tensilica), part: 0x2003, ver: 0x1)
|
||||
esp32: Debug controller was reset (pwrstat=0x5F, after clear 0x0F).
|
||||
esp32: Core was reset (pwrstat=0x5F, after clear 0x0F).
|
||||
Target halted. PRO_CPU: PC=0x5000004B (active) APP_CPU: PC=0x00000000
|
||||
esp32: target state: halted
|
||||
esp32: Core was reset (pwrstat=0x1F, after clear 0x0F).
|
||||
Target halted. PRO_CPU: PC=0x40000400 (active) APP_CPU: PC=0x40000400
|
||||
esp32: target state: halted
|
||||
Hardware assisted breakpoint 1 at 0x400db717: file /home/user-name/esp/blink/main/./blink.c, line 43.
|
||||
0x0: 0x00000000
|
||||
Target halted. PRO_CPU: PC=0x400DB717 (active) APP_CPU: PC=0x400D10D8
|
||||
[New Thread 1073428656]
|
||||
[New Thread 1073413708]
|
||||
[New Thread 1073431316]
|
||||
[New Thread 1073410672]
|
||||
[New Thread 1073408876]
|
||||
[New Thread 1073432196]
|
||||
[New Thread 1073411552]
|
||||
[Switching to Thread 1073411996]
|
||||
|
||||
Temporary breakpoint 1, app_main () at /home/user-name/esp/blink/main/./blink.c:43
|
||||
43 xTaskCreate(&blink_task, "blink_task", 512, NULL, 5, NULL);
|
||||
(gdb)
|
||||
|
||||
Note the third line from bottom that shows debugger halting at breakpoint established in ``gdbinit`` file at function ``app_main()``. Since the processor is halted, the LED should not be blinking. If this is what you see as well, you are ready to start debugging.
|
||||
|
||||
If you are not quite sure how to use GDB, check :ref:`jtag-debugging-examples-command-line` example debugging session in section :ref:`jtag-debugging-examples`.
|
||||
@@ -0,0 +1,168 @@
|
||||
Partition Tables
|
||||
================
|
||||
|
||||
Overview
|
||||
--------
|
||||
|
||||
A single ESP32's flash can contain multiple apps, as well as many different kinds of data (calibration data, filesystems, parameter storage, etc). For this reason a partition table is flashed to offset 0x8000 in the flash.
|
||||
|
||||
Partition table length is 0xC00 bytes (maximum 95 partition table entries). An MD5 checksum is appended after the table data. If the partition table is signed due to `secure boot`, the signature is appended after the partition table.
|
||||
|
||||
Each entry in the partition table has a name (label), type (app, data, or something else), subtype and the offset in flash where the partition is loaded.
|
||||
|
||||
The simplest way to use the partition table is to `make menuconfig` and choose one of the simple predefined partition tables:
|
||||
|
||||
* "Single factory app, no OTA"
|
||||
* "Factory app, two OTA definitions"
|
||||
|
||||
In both cases the factory app is flashed at offset 0x10000. If you `make partition_table` then it will print a summary of the partition table.
|
||||
|
||||
Built-in Partition Tables
|
||||
-------------------------
|
||||
|
||||
Here is the summary printed for the "Single factory app, no OTA" configuration::
|
||||
|
||||
# Espressif ESP32 Partition Table
|
||||
# Name, Type, SubType, Offset, Size
|
||||
nvs, data, nvs, 0x9000, 0x6000
|
||||
phy_init, data, phy, 0xf000, 0x1000
|
||||
factory, app, factory, 0x10000, 1M
|
||||
|
||||
* At a 0x10000 (64KB) offset in the flash is the app labelled "factory". The bootloader will run this app by default.
|
||||
* There are also two data regions defined in the partition table for storing NVS library partition and PHY init data.
|
||||
|
||||
Here is the summary printed for the "Factory app, two OTA definitions" configuration::
|
||||
|
||||
# Espressif ESP32 Partition Table
|
||||
# Name, Type, SubType, Offset, Size
|
||||
nvs, data, nvs, 0x9000, 0x4000
|
||||
otadata, data, ota, 0xd000, 0x2000
|
||||
phy_init, data, phy, 0xf000, 0x1000
|
||||
factory, 0, 0, 0x10000, 1M
|
||||
ota_0, 0, ota_0, , 1M
|
||||
ota_1, 0, ota_1, , 1M
|
||||
|
||||
* There are now three app partition definitions.
|
||||
* The type of all three are set as "app", but the subtype varies between the factory app at 0x10000 and the next two "OTA" apps.
|
||||
* There is also a new "ota data" slot, which holds the data for OTA updates. The bootloader consults this data in order to know which app to execute. If "ota data" is empty, it will execute the factory app.
|
||||
|
||||
Creating Custom Tables
|
||||
----------------------
|
||||
|
||||
If you choose "Custom partition table CSV" in menuconfig then you can also enter the name of a CSV file (in the project directory) to use for your partition table. The CSV file can describe any number of definitions for the table you need.
|
||||
|
||||
The CSV format is the same format as printed in the summaries shown above. However, not all fields are required in the CSV. For example, here is the "input" CSV for the OTA partition table::
|
||||
|
||||
# Name, Type, SubType, Offset, Size
|
||||
nvs, data, nvs, 0x9000, 0x4000
|
||||
otadata, data, ota, 0xd000, 0x2000
|
||||
phy_init, data, phy, 0xf000, 0x1000
|
||||
factory, app, factory, 0x10000, 1M
|
||||
ota_0, app, ota_0, , 1M
|
||||
ota_1, app, ota_1, , 1M
|
||||
|
||||
* Whitespace between fields is ignored, and so is any line starting with # (comments).
|
||||
* Each non-comment line in the CSV file is a partition definition.
|
||||
* Only the offset for the first partition is supplied. The gen_esp32part.py tool fills in each remaining offset to start after the preceding partition.
|
||||
|
||||
Name field
|
||||
~~~~~~~~~~
|
||||
|
||||
Name field can be any meaningful name. It is not significant to the ESP32. Names longer than 16 characters will be truncated.
|
||||
|
||||
Type field
|
||||
~~~~~~~~~~
|
||||
|
||||
Partition type field can be specified as app (0) or data (1). Or it can be a number 0-254 (or as hex 0x00-0xFE). Types 0x00-0x3F are reserved for esp-idf core functions.
|
||||
|
||||
If your application needs to store data, please add a custom partition type in the range 0x40-0xFE.
|
||||
|
||||
The bootloader ignores any partition types other than app (0) & data (1).
|
||||
|
||||
Subtype
|
||||
~~~~~~~
|
||||
|
||||
The 8-bit subtype field is specific to a given partition type.
|
||||
|
||||
esp-idf currently only specifies the meaning of the subtype field for "app" and "data" partition types.
|
||||
|
||||
App Subtypes
|
||||
~~~~~~~~~~~~
|
||||
|
||||
When type is "app", the subtype field can be specified as factory (0), ota_0 (0x10) ... ota_15 (0x1F) or test (0x20).
|
||||
|
||||
- factory (0) is the default app partition. The bootloader will execute the factory app unless there it sees a partition of type data/ota, in which case it reads this partition to determine which OTA image to boot.
|
||||
|
||||
- OTA never updates the factory partition.
|
||||
- If you want to conserve flash usage in an OTA project, you can remove the factory partition and use ota_0 instead.
|
||||
- ota_0 (0x10) ... ota_15 (0x1F) are the OTA app slots. Refer to the :doc:`OTA documentation <../api-reference/system/ota>` for more details, which then use the OTA data partition to configure which app slot the bootloader should boot. If using OTA, an application should have at least two OTA application slots (ota_0 & ota_1). Refer to the :doc:`OTA documentation <../api-reference/system/ota>` for more details.
|
||||
- test (0x2) is a reserved subtype for factory test procedures. It is not currently supported by the esp-idf bootloader.
|
||||
|
||||
Data Subtypes
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
When type is "data", the subtype field can be specified as ota (0), phy (1), nvs (2).
|
||||
|
||||
- ota (0) is the :ref:`OTA data partition <ota_data_partition>` which stores information about the currently selected OTA application. This partition should be 0x2000 bytes in size. Refer to the :ref:`OTA documentation <ota_data_partition>` for more details.
|
||||
- phy (1) is for storing PHY initialisation data. This allows PHY to be configured per-device, instead of in firmware.
|
||||
|
||||
- In the default configuration, the phy partition is not used and PHY initialisation data is compiled into the app itself. As such, this partition can be removed from the partition table to save space.
|
||||
- To load PHY data from this partition, run ``make menuconfig`` and enable :ref:`CONFIG_ESP32_PHY_INIT_DATA_IN_PARTITION` option. You will also need to flash your devices with phy init data as the esp-idf build system does not do this automatically.
|
||||
- nvs (2) is for the :doc:`Non-Volatile Storage (NVS) API <../api-reference/storage/nvs_flash>`.
|
||||
|
||||
- NVS is used to store per-device PHY calibration data (different to initialisation data).
|
||||
- NVS is used to store WiFi data if the :doc:`esp_wifi_set_storage(WIFI_STORAGE_FLASH) <../api-reference/wifi/esp_wifi>` initialisation function is used.
|
||||
- The NVS API can also be used for other application data.
|
||||
- It is strongly recommended that you include an NVS partition of at least 0x3000 bytes in your project.
|
||||
- If using NVS API to store a lot of data, increase the NVS partition size from the default 0x6000 bytes.
|
||||
|
||||
Other data subtypes are reserved for future esp-idf uses.
|
||||
|
||||
Offset & Size
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
Only the first offset field is required (we recommend using 0x10000). Partitions with blank offsets will start after the previous partition.
|
||||
|
||||
App partitions have to be at offsets aligned to 0x10000 (64K). If you leave the offset field blank, the tool will automatically align the partition. If you specify an unaligned offset for an app partition, the tool will return an error.
|
||||
|
||||
Sizes and offsets can be specified as decimal numbers, hex numbers with the prefix 0x, or size multipliers K or M (1024 and 1024*1024 bytes).
|
||||
|
||||
Generating Binary Partition Table
|
||||
---------------------------------
|
||||
|
||||
The partition table which is flashed to the ESP32 is in a binary format, not CSV. The tool :component_file:`partition_table/gen_esp32part.py` is used to convert between CSV and binary formats.
|
||||
|
||||
If you configure the partition table CSV name in ``make menuconfig`` and then ``make partition_table``, this conversion is done as part of the build process.
|
||||
|
||||
To convert CSV to Binary manually::
|
||||
|
||||
python gen_esp32part.py --verify input_partitions.csv binary_partitions.bin
|
||||
|
||||
To convert binary format back to CSV::
|
||||
|
||||
python gen_esp32part.py --verify binary_partitions.bin input_partitions.csv
|
||||
|
||||
To display the contents of a binary partition table on stdout (this is how the summaries displayed when running `make partition_table` are generated::
|
||||
|
||||
python gen_esp32part.py binary_partitions.bin
|
||||
|
||||
``gen_esp32part.py`` takes one optional argument, ``--verify``, which will also verify the partition table during conversion (checking for overlapping partitions, unaligned partitions, etc.)
|
||||
|
||||
MD5 checksum
|
||||
~~~~~~~~~~~~
|
||||
|
||||
The binary format of the partition table contains an MD5 checksum computed based on the partition table. This checksum is used for checking the integrity of the partition table during the boot.
|
||||
|
||||
The MD5 checksum generation can be disabled by the ``--disable-md5sum`` option of ``gen_esp32part.py`` or by the :ref:`CONFIG_PARTITION_TABLE_MD5` option. This is useful for example when one uses a legacy bootloader which cannot process MD5 checksums and the boot fails with the error message ``invalid magic number 0xebeb``.
|
||||
|
||||
Flashing the partition table
|
||||
----------------------------
|
||||
|
||||
* ``make partition_table-flash``: will flash the partition table with esptool.py.
|
||||
* ``make flash``: Will flash everything including the partition table.
|
||||
|
||||
A manual flashing command is also printed as part of ``make partition_table``.
|
||||
|
||||
Note that updating the partition table doesn't erase data that may have been stored according to the old partition table. You can use ``make erase_flash`` (or ``esptool.py erase_flash``) to erase the entire flash contents.
|
||||
|
||||
.. _secure boot: security/secure-boot.rst
|
||||
@@ -0,0 +1,114 @@
|
||||
*****************
|
||||
ESP32 ROM console
|
||||
*****************
|
||||
|
||||
When an ESP32 is unable to boot from flash ROM (and the fuse disabling it hasn't been blown), it boots into a rom console. The console
|
||||
is based on TinyBasic, and statements entered should be in the form of BASIC statements. As is common in the BASIC language, without a
|
||||
preceeding line number, commands entered are executed immediately; lines with a prefixed line number are stored as part of a program.
|
||||
|
||||
Full list of supported statements and functions
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
System
|
||||
------
|
||||
|
||||
- BYE - *exits Basic, reboots ESP32, retries booting from flash*
|
||||
- END - *stops execution from the program, also "STOP"*
|
||||
- MEM - *displays memory usage statistics*
|
||||
- NEW - *clears the current program*
|
||||
- RUN - *executes the current program*
|
||||
|
||||
IO, Documentation
|
||||
-----------------
|
||||
|
||||
- PEEK( address ) - *get a 32-bit value from a memory address*
|
||||
- POKE - *write a 32-bit value to memory*
|
||||
- USR(addr, arg1, ..) - *Execute a machine language function*
|
||||
- PRINT expression - *print out the expression, also "?"*
|
||||
- PHEX expression - *print expression as a hex number*
|
||||
- REM stuff - *remark/comment, also "'"*
|
||||
|
||||
Expressions, Math
|
||||
-----------------
|
||||
|
||||
- A=V, LET A=V - *assign value to a variable*
|
||||
- +, -, \*, / - *Math*
|
||||
- <,<=,=,<>,!=,>=,> - *Comparisons*
|
||||
- ABS( expression ) - *returns the absolute value of the expression*
|
||||
- RSEED( v ) - *sets the random seed to v*
|
||||
- RND( m ) - *returns a random number from 0 to m*
|
||||
- A=1234 - * Assign a decimal value*
|
||||
- A=&h1A2 - * Assign a hex value*
|
||||
- A=&b1001 - *Assign a binary value*
|
||||
|
||||
Control
|
||||
-------
|
||||
|
||||
- IF expression statement - *perform statement if expression is true*
|
||||
- FOR variable = start TO end - *start for block*
|
||||
- FOR variable = start TO end STEP value - *start for block with step*
|
||||
- NEXT - *end of for block*
|
||||
- GOTO linenumber - *continue execution at this line number*
|
||||
- GOSUB linenumber - *call a subroutine at this line number*
|
||||
- RETURN - *return from a subroutine*
|
||||
- DELAY - *Delay a given number of milliseconds*
|
||||
|
||||
Pin IO
|
||||
------
|
||||
- IODIR - *Set a GPIO-pin as an output (1) or input (0)*
|
||||
- IOSET - *Set a GPIO-pin, configured as output, to high (1) or low (0)*
|
||||
- IOGET - *Get the value of a GPIO-pin*
|
||||
|
||||
|
||||
Example programs
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
Here are a few example commands and programs to get you started...
|
||||
|
||||
Read UART_DATE register of uart0
|
||||
--------------------------------
|
||||
|
||||
::
|
||||
|
||||
> PHEX PEEK(&h3FF40078)
|
||||
15122500
|
||||
|
||||
Set GPIO2 using memory writes to GPIO_OUT_REG
|
||||
---------------------------------------------
|
||||
|
||||
Note: you can do this easier with the IOSET command
|
||||
|
||||
::
|
||||
|
||||
> POKE &h3FF44004,PEEK(&h3FF44004) OR &b100
|
||||
|
||||
Get value of GPIO0
|
||||
------------------
|
||||
|
||||
::
|
||||
|
||||
> IODIR 0,0
|
||||
> PRINT IOGET(0)
|
||||
0
|
||||
|
||||
Blink LED
|
||||
---------
|
||||
|
||||
Hook up an LED between GPIO2 and ground. When running the program, the LED should blink 10 times.
|
||||
|
||||
::
|
||||
|
||||
10 IODIR 2,1
|
||||
20 FOR A=1 TO 10
|
||||
30 IOSET 2,1
|
||||
40 DELAY 250
|
||||
50 IOSET 2,0
|
||||
60 DELAY 250
|
||||
70 NEXT A
|
||||
RUN
|
||||
|
||||
Credits
|
||||
~~~~~~~
|
||||
|
||||
The ROM console is based on "TinyBasicPlus" by Mike Field and Scott Lawrence, which is based on "68000 TinyBasic" by Gordon Brandly
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
ULP coprocessor programming
|
||||
===========================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
Instruction set reference <ulp_instruction_set>
|
||||
Programming using macros (legacy) <ulp_macros>
|
||||
|
||||
|
||||
ULP (Ultra Low Power) coprocessor is a simple FSM which is designed to perform measurements using ADC, temperature sensor, and external I2C sensors, while main processors are in deep sleep mode. ULP coprocessor can access RTC_SLOW_MEM memory region, and registers in RTC_CNTL, RTC_IO, and SARADC peripherals. ULP coprocessor uses fixed-width 32-bit instructions, 32-bit memory addressing, and has 4 general purpose 16-bit registers.
|
||||
|
||||
Installing the toolchain
|
||||
------------------------
|
||||
|
||||
ULP coprocessor code is written in assembly and compiled using the `binutils-esp32ulp toolchain`_.
|
||||
|
||||
1. Download the toolchain using the links listed on this page:
|
||||
https://github.com/espressif/binutils-esp32ulp/wiki#downloads
|
||||
|
||||
2. Extract the toolchain into a directory, and add the path to the ``bin/`` directory of the toolchain to the ``PATH`` environment variable.
|
||||
|
||||
Compiling ULP code
|
||||
------------------
|
||||
|
||||
To compile ULP code as part of a component, the following steps must be taken:
|
||||
|
||||
1. ULP code, written in assembly, must be added to one or more files with `.S` extension. These files must be placed into a separate directory inside component directory, for instance `ulp/`.
|
||||
|
||||
.. note: This directory should not be added to the ``COMPONENT_SRCDIRS`` environment variable. The logic behind this is that the ESP-IDF build system will compile files found in ``COMPONENT_SRCDIRS`` based on their extensions. For ``.S`` files, ``xtensa-esp32-elf-as`` assembler is used. This is not desirable for ULP assembly files, so the easiest way to achieve the distinction is by placing ULP assembly files into a separate directory.
|
||||
|
||||
2. Modify the component makefile, adding the following::
|
||||
|
||||
ULP_APP_NAME ?= ulp_$(COMPONENT_NAME)
|
||||
ULP_S_SOURCES = $(COMPONENT_PATH)/ulp/ulp_source_file.S
|
||||
ULP_EXP_DEP_OBJECTS := main.o
|
||||
include $(IDF_PATH)/components/ulp/component_ulp_common.mk
|
||||
|
||||
Here is each line explained:
|
||||
|
||||
ULP_APP_NAME
|
||||
Name of the generated ULP application, without an extension. This name is used for build products of the ULP application: ELF file, map file, binary file, generated header file, and generated linker export file.
|
||||
|
||||
ULP_S_SOURCES
|
||||
List of assembly files to be passed to the ULP assembler. These must be absolute paths, i.e. start with ``$(COMPONENT_PATH)``. Consider using ``$(addprefix)`` function if more than one file needs to be listed. Paths are relative to component build directory, so prefixing them is not necessary.
|
||||
|
||||
ULP_EXP_DEP_OBJECTS
|
||||
List of object files names within the component which include the generated header file. This list is needed to build the dependencies correctly and ensure that the generated header file is created before any of these files are compiled. See section below explaining the concept of generated header files for ULP applications.
|
||||
|
||||
include $(IDF_PATH)/components/ulp/component_ulp_common.mk
|
||||
Includes common definitions of ULP build steps. Defines build targets for ULP object files, ELF file, binary file, etc.
|
||||
|
||||
3. Build the application as usual (e.g. `make app`)
|
||||
|
||||
Inside, the build system will take the following steps to build ULP program:
|
||||
|
||||
1. **Run each assembly file (foo.S) through C preprocessor.** This step generates the preprocessed assembly files (foo.ulp.pS) in the component build directory. This step also generates dependency files (foo.ulp.d).
|
||||
|
||||
2. **Run preprocessed assembly sources through assembler.** This produces objects (foo.ulp.o) and listing (foo.ulp.lst) files. Listing files are generated for debugging purposes and are not used at later stages of build process.
|
||||
|
||||
3. **Run linker script template through C preprocessor.** The template is located in components/ulp/ld directory.
|
||||
|
||||
4. **Link object files into an output ELF file** (ulp_app_name.elf). Map file (ulp_app_name.map) generated at this stage may be useful for debugging purposes.
|
||||
|
||||
5. **Dump contents of the ELF file into binary** (ulp_app_name.bin) for embedding into the application.
|
||||
|
||||
6. **Generate list of global symbols** (ulp_app_name.sym) in the ELF file using esp32ulp-elf-nm.
|
||||
|
||||
7. **Create LD export script and header file** (ulp_app_name.ld and ulp_app_name.h) containing the symbols from ulp_app_name.sym. This is done using esp32ulp_mapgen.py utility.
|
||||
|
||||
8. **Add the generated binary to the list of binary files** to be emedded into the application.
|
||||
|
||||
Accessing ULP program variables
|
||||
-------------------------------
|
||||
|
||||
Global symbols defined in the ULP program may be used inside the main program.
|
||||
|
||||
For example, ULP program may define a variable ``measurement_count`` which will define the number of ADC measurements the program needs to make before waking up the chip from deep sleep::
|
||||
|
||||
.global measurement_count
|
||||
measurement_count: .long 0
|
||||
|
||||
/* later, use measurement_count */
|
||||
move r3, measurement_count
|
||||
ld r3, r3, 0
|
||||
|
||||
Main program needs to initialize this variable before ULP program is started. Build system makes this possible by generating a ``$(ULP_APP_NAME).h`` and ``$(ULP_APP_NAME).ld`` files which define global symbols present in the ULP program. This files include each global symbol defined in the ULP program, prefixed with ``ulp_``.
|
||||
|
||||
The header file contains declaration of the symbol::
|
||||
|
||||
extern uint32_t ulp_measurement_count;
|
||||
|
||||
Note that all symbols (variables, arrays, functions) are declared as ``uint32_t``. For functions and arrays, take address of the symbol and cast to the appropriate type.
|
||||
|
||||
The generated linker script file defines locations of symbols in RTC_SLOW_MEM::
|
||||
|
||||
PROVIDE ( ulp_measurement_count = 0x50000060 );
|
||||
|
||||
To access ULP program variables from the main program, include the generated header file and use variables as one normally would::
|
||||
|
||||
#include "ulp_app_name.h"
|
||||
|
||||
// later
|
||||
void init_ulp_vars() {
|
||||
ulp_measurement_count = 64;
|
||||
}
|
||||
|
||||
Note that ULP program can only use lower 16 bits of each 32-bit word in RTC memory, because the registers are 16-bit, and there is no instruction to load from high part of the word.
|
||||
|
||||
Likewise, ULP store instruction writes register value into the lower 16 bit part of the 32-bit word. Upper 16 bits are written with a value which depends on the address of the store instruction, so when reading variables written by the ULP, main application needs to mask upper 16 bits, e.g.::
|
||||
|
||||
printf("Last measurement value: %d\n", ulp_last_measurement & UINT16_MAX);
|
||||
|
||||
Starting the ULP program
|
||||
------------------------
|
||||
|
||||
To run a ULP program, main application needs to load the ULP program into RTC memory using ``ulp_load_binary`` function, and then start it using ``ulp_run`` function.
|
||||
|
||||
Note that "Enable Ultra Low Power (ULP) Coprocessor" option must be enabled in menuconfig in order to reserve memory for the ULP. "RTC slow memory reserved for coprocessor" option must be set to a value sufficient to store ULP code and data. If the application components contain multiple ULP programs, then the size of the RTC memory must be sufficient to hold the largest one.
|
||||
|
||||
Each ULP program is embedded into the ESP-IDF application as a binary blob. Application can reference this blob and load it in the following way (suppose ULP_APP_NAME was defined to ``ulp_app_name``::
|
||||
|
||||
extern const uint8_t bin_start[] asm("_binary_ulp_app_name_bin_start");
|
||||
extern const uint8_t bin_end[] asm("_binary_ulp_app_name_bin_end");
|
||||
|
||||
void start_ulp_program() {
|
||||
ESP_ERROR_CHECK( ulp_load_binary(
|
||||
0 /* load address, set to 0 when using default linker scripts */,
|
||||
bin_start,
|
||||
(bin_end - bin_start) / sizeof(uint32_t)) );
|
||||
}
|
||||
|
||||
.. doxygenfunction:: ulp_load_binary
|
||||
|
||||
Once the program is loaded into RTC memory, application can start it, passing the address of the entry point to ``ulp_run`` function::
|
||||
|
||||
ESP_ERROR_CHECK( ulp_run((&ulp_entry - RTC_SLOW_MEM) / sizeof(uint32_t)) );
|
||||
|
||||
.. doxygenfunction:: ulp_run
|
||||
|
||||
Declaration of the entry point symbol comes from the above mentioned generated header file, ``$(ULP_APP_NAME).h``. In assembly source of the ULP application, this symbol must be marked as ``.global``::
|
||||
|
||||
|
||||
.global entry
|
||||
entry:
|
||||
/* code starts here */
|
||||
|
||||
|
||||
ULP program flow
|
||||
----------------
|
||||
|
||||
ULP coprocessor is started by a timer. The timer is started once ``ulp_run`` is called. The timer counts a number of RTC_SLOW_CLK ticks (by default, produced by an internal 150kHz RC oscillator). The number of ticks is set using ``SENS_ULP_CP_SLEEP_CYCx_REG`` registers (x = 0..4). When starting the ULP for the first time, ``SENS_ULP_CP_SLEEP_CYC0_REG`` will be used to set the number of timer ticks. Later the ULP program can select another ``SENS_ULP_CP_SLEEP_CYCx_REG`` register using ``sleep`` instruction.
|
||||
|
||||
The application can set ULP timer period values (SENS_ULP_CP_SLEEP_CYCx_REG, x = 0..4) using ``ulp_wakeup_period_set`` function.
|
||||
|
||||
.. doxygenfunction:: ulp_set_wakeup_period
|
||||
|
||||
Once the timer counts the number of ticks set in the selected ``SENS_ULP_CP_SLEEP_CYCx_REG`` register, ULP coprocessor powers up and starts running the program from the entry point set in the call to ``ulp_run``.
|
||||
|
||||
The program runs until it encounters a ``halt`` instruction or an illegal instruction. Once the program halts, ULP coprocessor powers down, and the timer is started again.
|
||||
|
||||
To disable the timer (effectively preventing the ULP program from running again), clear the ``RTC_CNTL_ULP_CP_SLP_TIMER_EN`` bit in the ``RTC_CNTL_STATE0_REG`` register. This can be done both from ULP code and from the main program.
|
||||
|
||||
|
||||
.. _binutils-esp32ulp toolchain: https://github.com/espressif/binutils-esp32ulp
|
||||
Executable
+903
@@ -0,0 +1,903 @@
|
||||
ULP coprocessor instruction set
|
||||
===============================
|
||||
|
||||
This document provides details about the instructions used by ESP32 ULP coprocessor assembler.
|
||||
|
||||
ULP coprocessor has 4 16-bit general purpose registers, labeled R0, R1, R2, R3. It also has an 8-bit counter register (stage_cnt) which can be used to implement loops. Stage count regiter is accessed using special instructions.
|
||||
|
||||
ULP coprocessor can access 8k bytes of RTC_SLOW_MEM memory region. Memory is addressed in 32-bit word units. It can also access peripheral registers in RTC_CNTL, RTC_IO, and SENS peripherals.
|
||||
|
||||
All instructions are 32-bit. Jump instructions, ALU instructions, peripheral register and memory access instructions are executed in 1 cycle. Instructions which work with peripherals (TSENS, ADC, I2C) take variable number of cycles, depending on peripheral operation.
|
||||
|
||||
The instruction syntax is case insensitive. Upper and lower case letters can be used and intermixed arbitrarily. This is true both for register names and instruction names.
|
||||
|
||||
Note about addressing
|
||||
---------------------
|
||||
ESP32 ULP coprocessor's JUMP, ST, LD instructions which take register as an argument (jump address, store/load base address) expect the argument to be expressed in 32-bit words.
|
||||
|
||||
Consider the following example program::
|
||||
|
||||
entry:
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
NOP
|
||||
loop:
|
||||
MOVE R1, loop
|
||||
JUMP R1
|
||||
|
||||
When this program is assembled and linked, address of label ``loop`` will be equal to 16 (expressed in bytes). However `JUMP` instruction expects the address stored in register to be expressed in 32-bit words. To account for this common use case, assembler will convert the address of label `loop` from bytes to words, when generating ``MOVE`` instruction, so the code generated code will be equivalent to::
|
||||
|
||||
0000 NOP
|
||||
0004 NOP
|
||||
0008 NOP
|
||||
000c NOP
|
||||
0010 MOVE R1, 4
|
||||
0014 JUMP R1
|
||||
|
||||
The other case is when the argument of ``MOVE`` instruction is not a label but a constant. In this case assembler will use the value as is, without any conversion::
|
||||
|
||||
.set val, 0x10
|
||||
MOVE R1, val
|
||||
|
||||
In this case, value loaded into R1 will be ``0x10``.
|
||||
|
||||
Similar considerations apply to ``LD`` and ``ST`` instructions. Consider the following code::
|
||||
|
||||
.global array
|
||||
array: .long 0
|
||||
.long 0
|
||||
.long 0
|
||||
.long 0
|
||||
|
||||
MOVE R1, array
|
||||
MOVE R2, 0x1234
|
||||
ST R2, R1, 0 // write value of R2 into the first array element,
|
||||
// i.e. array[0]
|
||||
|
||||
ST R2, R1, 4 // write value of R2 into the second array element
|
||||
// (4 byte offset), i.e. array[1]
|
||||
|
||||
ADD R1, R1, 2 // this increments address by 2 words (8 bytes)
|
||||
ST R2, R1, 0 // write value of R2 into the third array element,
|
||||
// i.e. array[2]
|
||||
|
||||
Note about instruction execution time
|
||||
-------------------------------------
|
||||
|
||||
ULP coprocessor is clocked from RTC_FAST_CLK, which is normally derived from the internal 8MHz oscillator. Applications which need to know exact ULP clock frequency can calibrate it against the main XTAL clock::
|
||||
|
||||
#include "soc/rtc.h"
|
||||
|
||||
// calibrate 8M/256 clock against XTAL, get 8M/256 clock period
|
||||
uint32_t rtc_8md256_period = rtc_clk_cal(RTC_CAL_8MD256, 100);
|
||||
uint32_t rtc_fast_freq_hz = 1000000ULL * (1 << RTC_CLK_CAL_FRACT) * 256 / rtc_8md256_period;
|
||||
|
||||
ULP coprocessor needs 2 clock cycle to fetch each instuction (fetching is not pipelined), plus certain number of cycles to execute, depending on the instruction. See description of each instruction for details on the execution time.
|
||||
|
||||
Note that when accessing RTC memories and RTC registers, ULP coprocessor has lower priority than the main CPUs. This means that ULP coprocessor execution may be suspended while the main CPUs access same memory region as the ULP.
|
||||
|
||||
|
||||
**NOP** - no operation
|
||||
----------------------
|
||||
|
||||
**Syntax**
|
||||
**NOP**
|
||||
**Operands**
|
||||
None
|
||||
**Cycles**
|
||||
2 (fetch) + 1 (execute)
|
||||
**Description**
|
||||
No operation is performed. Only the PC is incremented.
|
||||
|
||||
**Example**::
|
||||
|
||||
1: NOP
|
||||
|
||||
|
||||
**ADD** - Add to register
|
||||
-------------------------
|
||||
|
||||
**Syntax**
|
||||
**ADD** *Rdst, Rsrc1, Rsrc2*
|
||||
|
||||
**ADD** *Rdst, Rsrc1, imm*
|
||||
|
||||
|
||||
**Operands**
|
||||
- *Rdst* - Register R[0..3]
|
||||
- *Rsrc1* - Register R[0..3]
|
||||
- *Rsrc2* - Register R[0..3]
|
||||
- *Imm* - 16-bit signed value
|
||||
|
||||
**Cycles**
|
||||
2 (fetch) + 2 (execute)
|
||||
|
||||
**Description**
|
||||
The instruction adds source register to another source register or to a 16-bit signed value and stores result to the destination register.
|
||||
|
||||
**Examples**::
|
||||
|
||||
1: ADD R1, R2, R3 //R1 = R2 + R3
|
||||
|
||||
2: Add R1, R2, 0x1234 //R1 = R2 + 0x1234
|
||||
|
||||
3: .set value1, 0x03 //constant value1=0x03
|
||||
Add R1, R2, value1 //R1 = R2 + value1
|
||||
|
||||
|
||||
4: .global label //declaration of variable label
|
||||
Add R1, R2, label //R1 = R2 + label
|
||||
...
|
||||
label: nop //definition of variable label
|
||||
|
||||
|
||||
**SUB** - Subtract from register
|
||||
--------------------------------
|
||||
|
||||
**Syntax**
|
||||
**SUB** *Rdst, Rsrc1, Rsrc2*
|
||||
|
||||
**SUB** *Rdst, Rsrc1, imm*
|
||||
|
||||
**Operands**
|
||||
- *Rdst* - Register R[0..3]
|
||||
- *Rsrc1* - Register R[0..3]
|
||||
- *Rsrc2* - Register R[0..3]
|
||||
- *Imm* - 16-bit signed value
|
||||
|
||||
**Cycles**
|
||||
2 (fetch) + 2 (execute)
|
||||
|
||||
**Description**
|
||||
The instruction subtracts the source register from another source register or subtracts 16-bit signed value from a source register, and stores result to the destination register.
|
||||
|
||||
**Examples**::
|
||||
|
||||
1: SUB R1, R2, R3 //R1 = R2 - R3
|
||||
|
||||
2: sub R1, R2, 0x1234 //R1 = R2 - 0x1234
|
||||
|
||||
3: .set value1, 0x03 //constant value1=0x03
|
||||
SUB R1, R2, value1 //R1 = R2 - value1
|
||||
4: .global label //declaration of variable label
|
||||
SUB R1, R2, label //R1 = R2 - label
|
||||
....
|
||||
label: nop //definition of variable label
|
||||
|
||||
|
||||
**AND** - Logical AND of two operands
|
||||
-------------------------------------
|
||||
|
||||
**Syntax**
|
||||
**AND** *Rdst, Rsrc1, Rsrc2*
|
||||
|
||||
**AND** *Rdst, Rsrc1, imm*
|
||||
|
||||
**Operands**
|
||||
- *Rdst* - Register R[0..3]
|
||||
- *Rsrc1* - Register R[0..3]
|
||||
- *Rsrc2* - Register R[0..3]
|
||||
- *Imm* - 16-bit signed value
|
||||
|
||||
**Cycles**
|
||||
2 (fetch) + 2 (execute)
|
||||
|
||||
**Description**
|
||||
The instruction does logical AND of a source register and another source register or 16-bit signed value and stores result to the destination register.
|
||||
|
||||
**Examples**::
|
||||
|
||||
1: AND R1, R2, R3 //R1 = R2 & R3
|
||||
|
||||
2: AND R1, R2, 0x1234 //R1 = R2 & 0x1234
|
||||
|
||||
3: .set value1, 0x03 //constant value1=0x03
|
||||
AND R1, R2, value1 //R1 = R2 & value1
|
||||
|
||||
4: .global label //declaration of variable label
|
||||
AND R1, R2, label //R1 = R2 & label
|
||||
...
|
||||
label: nop //definition of variable label
|
||||
|
||||
|
||||
**OR** - Logical OR of two operands
|
||||
-----------------------------------
|
||||
|
||||
**Syntax**
|
||||
**OR** *Rdst, Rsrc1, Rsrc2*
|
||||
|
||||
**OR** *Rdst, Rsrc1, imm*
|
||||
|
||||
**Operands**
|
||||
- *Rdst* - Register R[0..3]
|
||||
- *Rsrc1* - Register R[0..3]
|
||||
- *Rsrc2* - Register R[0..3]
|
||||
- *Imm* - 16-bit signed value
|
||||
|
||||
**Cycles**
|
||||
2 (fetch) + 2 (execute)
|
||||
|
||||
**Description**
|
||||
The instruction does logical OR of a source register and another source register or 16-bit signed value and stores result to the destination register.
|
||||
|
||||
**Examples**::
|
||||
|
||||
1: OR R1, R2, R3 //R1 = R2 \| R3
|
||||
|
||||
2: OR R1, R2, 0x1234 //R1 = R2 \| 0x1234
|
||||
|
||||
3: .set value1, 0x03 //constant value1=0x03
|
||||
OR R1, R2, value1 //R1 = R2 \| value1
|
||||
|
||||
4: .global label //declaration of variable label
|
||||
OR R1, R2, label //R1 = R2 \|label
|
||||
...
|
||||
label: nop //definition of variable label
|
||||
|
||||
|
||||
|
||||
**LSH** - Logical Shift Left
|
||||
----------------------------
|
||||
|
||||
**Syntax**
|
||||
**LSH** *Rdst, Rsrc1, Rsrc2*
|
||||
|
||||
**LSH** *Rdst, Rsrc1, imm*
|
||||
|
||||
**Operands**
|
||||
- *Rdst* - Register R[0..3]
|
||||
- *Rsrc1* - Register R[0..3]
|
||||
- *Rsrc2* - Register R[0..3]
|
||||
- *Imm* - 16-bit signed value
|
||||
|
||||
**Cycles**
|
||||
2 (fetch) + 2 (execute)
|
||||
|
||||
**Description**
|
||||
The instruction does logical shift to left of source register to number of bits from another source register or 16-bit signed value and store result to the destination register.
|
||||
|
||||
**Examples**::
|
||||
|
||||
1: LSH R1, R2, R3 //R1 = R2 << R3
|
||||
|
||||
2: LSH R1, R2, 0x03 //R1 = R2 << 0x03
|
||||
|
||||
3: .set value1, 0x03 //constant value1=0x03
|
||||
LSH R1, R2, value1 //R1 = R2 << value1
|
||||
|
||||
4: .global label //declaration of variable label
|
||||
LSH R1, R2, label //R1 = R2 << label
|
||||
...
|
||||
label: nop //definition of variable label
|
||||
|
||||
|
||||
**RSH** - Logical Shift Right
|
||||
-----------------------------
|
||||
|
||||
**Syntax**
|
||||
**RSH** *Rdst, Rsrc1, Rsrc2*
|
||||
|
||||
**RSH** *Rdst, Rsrc1, imm*
|
||||
|
||||
**Operands**
|
||||
*Rdst* - Register R[0..3]
|
||||
*Rsrc1* - Register R[0..3]
|
||||
*Rsrc2* - Register R[0..3]
|
||||
*Imm* - 16-bit signed value
|
||||
|
||||
**Cycles**
|
||||
2 (fetch) + 2 (execute)
|
||||
|
||||
**Description**
|
||||
The instruction does logical shift to right of source register to number of bits from another source register or 16-bit signed value and store result to the destination register.
|
||||
|
||||
**Examples**::
|
||||
|
||||
1: RSH R1, R2, R3 //R1 = R2 >> R3
|
||||
|
||||
2: RSH R1, R2, 0x03 //R1 = R2 >> 0x03
|
||||
|
||||
3: .set value1, 0x03 //constant value1=0x03
|
||||
RSH R1, R2, value1 //R1 = R2 >> value1
|
||||
|
||||
4: .global label //declaration of variable label
|
||||
RSH R1, R2, label //R1 = R2 >> label
|
||||
label: nop //definition of variable label
|
||||
|
||||
|
||||
|
||||
**MOVE** – Move to register
|
||||
---------------------------
|
||||
|
||||
**Syntax**
|
||||
**MOVE** *Rdst, Rsrc*
|
||||
|
||||
**MOVE** *Rdst, imm*
|
||||
|
||||
**Operands**
|
||||
- *Rdst* – Register R[0..3]
|
||||
- *Rsrc* – Register R[0..3]
|
||||
- *Imm* – 16-bit signed value
|
||||
|
||||
**Cycles**
|
||||
2 (fetch) + 2 (execute)
|
||||
|
||||
**Description**
|
||||
The instruction move to destination register value from source register or 16-bit signed value.
|
||||
|
||||
Note that when a label is used as an immediate, the address of the label will be converted from bytes to words. This is because LD, ST, and JUMP instructions expect the address register value to be expressed in words rather than bytes. To avoid using an extra instruction
|
||||
|
||||
|
||||
**Examples**::
|
||||
|
||||
1: MOVE R1, R2 //R1 = R2 >> R3
|
||||
|
||||
2: MOVE R1, 0x03 //R1 = R2 >> 0x03
|
||||
|
||||
3: .set value1, 0x03 //constant value1=0x03
|
||||
MOVE R1, value1 //R1 = value1
|
||||
|
||||
4: .global label //declaration of label
|
||||
MOVE R1, label //R1 = address_of(label) / 4
|
||||
...
|
||||
label: nop //definition of label
|
||||
|
||||
|
||||
**ST** – Store data to the memory
|
||||
---------------------------------
|
||||
|
||||
**Syntax**
|
||||
**ST** *Rsrc, Rdst, offset*
|
||||
|
||||
**Operands**
|
||||
- *Rsrc* – Register R[0..3], holds the 16-bit value to store
|
||||
- *Rdst* – Register R[0..3], address of the destination, in 32-bit words
|
||||
- *Offset* – 10-bit signed value, offset in bytes
|
||||
|
||||
**Cycles**
|
||||
2 (fetch) + 4 (execute)
|
||||
|
||||
**Description**
|
||||
The instruction stores the 16-bit value of Rsrc to the lower half-word of memory with address Rdst+offset. The upper half-word is written with the current program counter (PC), expressed in words, shifted left by 5 bits::
|
||||
|
||||
Mem[Rdst + offset / 4]{31:0} = {PC[10:0], 5'b0, Rsrc[15:0]}
|
||||
|
||||
The application can use higher 16 bits to determine which instruction in the ULP program has written any particular word into memory.
|
||||
|
||||
**Examples**::
|
||||
|
||||
1: ST R1, R2, 0x12 //MEM[R2+0x12] = R1
|
||||
|
||||
2: .data //Data section definition
|
||||
Addr1: .word 123 // Define label Addr1 16 bit
|
||||
.set offs, 0x00 // Define constant offs
|
||||
.text //Text section definition
|
||||
MOVE R1, 1 // R1 = 1
|
||||
MOVE R2, Addr1 // R2 = Addr1
|
||||
ST R1, R2, offs // MEM[R2 + 0] = R1
|
||||
// MEM[Addr1 + 0] will be 32'h600001
|
||||
|
||||
|
||||
**LD** – Load data from the memory
|
||||
----------------------------------
|
||||
|
||||
**Syntax**
|
||||
**LD** *Rdst, Rsrc, offset*
|
||||
|
||||
**Operands**
|
||||
*Rdst* – Register R[0..3], destination
|
||||
|
||||
*Rsrc* – Register R[0..3], holds address of destination, in 32-bit words
|
||||
|
||||
*Offset* – 10-bit signed value, offset in bytes
|
||||
|
||||
**Cycles**
|
||||
2 (fetch) + 4 (execute)
|
||||
|
||||
**Description**
|
||||
The instruction loads lower 16-bit half-word from memory with address Rsrc+offset into the destination register Rdst::
|
||||
|
||||
Rdst[15:0] = Mem[Rsrc + offset / 4][15:0]
|
||||
|
||||
**Examples**::
|
||||
|
||||
1: LD R1, R2, 0x12 //R1 = MEM[R2+0x12]
|
||||
|
||||
2: .data //Data section definition
|
||||
Addr1: .word 123 // Define label Addr1 16 bit
|
||||
.set offs, 0x00 // Define constant offs
|
||||
.text //Text section definition
|
||||
MOVE R1, 1 // R1 = 1
|
||||
MOVE R2, Addr1 // R2 = Addr1 / 4 (address of label is converted into words)
|
||||
LD R1, R2, offs // R1 = MEM[R2 + 0]
|
||||
// R1 will be 123
|
||||
|
||||
|
||||
|
||||
|
||||
**JUMP** – Jump to an absolute address
|
||||
--------------------------------------
|
||||
|
||||
**Syntax**
|
||||
**JUMP** *Rdst*
|
||||
|
||||
**JUMP** *ImmAddr*
|
||||
|
||||
**JUMP** *Rdst, Condition*
|
||||
|
||||
**JUMP** *ImmAddr, Condition*
|
||||
|
||||
|
||||
**Operands**
|
||||
- *Rdst* – Register R[0..3] containing address to jump to (expressed in 32-bit words)
|
||||
|
||||
- *ImmAddr* – 13 bits address (expressed in bytes), aligned to 4 bytes
|
||||
|
||||
- *Condition*:
|
||||
- EQ – jump if last ALU operation result was zero
|
||||
- OV – jump if last ALU has set overflow flag
|
||||
|
||||
**Cycles**
|
||||
2 (fetch) + 2 (execute)
|
||||
|
||||
**Description**
|
||||
The instruction makes jump to the specified address. Jump can be either unconditional or based on an ALU flag.
|
||||
|
||||
**Examples**::
|
||||
|
||||
1: JUMP R1 // Jump to address in R1 (address in R1 is in 32-bit words)
|
||||
|
||||
2: JUMP 0x120, EQ // Jump to address 0x120 (in bytes) if ALU result is zero
|
||||
|
||||
3: JUMP label // Jump to label
|
||||
...
|
||||
label: nop // Definition of label
|
||||
|
||||
4: .global label // Declaration of global label
|
||||
|
||||
MOVE R1, label // R1 = label (value loaded into R1 is in words)
|
||||
JUMP R1 // Jump to label
|
||||
...
|
||||
label: nop // Definition of label
|
||||
|
||||
|
||||
|
||||
**JUMPR** – Jump to a relative offset (condition based on R0)
|
||||
-------------------------------------------------------------
|
||||
|
||||
**Syntax**
|
||||
**JUMPR** *Step, Threshold, Condition*
|
||||
|
||||
**Operands**
|
||||
- *Step* – relative shift from current position, in bytes
|
||||
- *Threshold* – threshold value for branch condition
|
||||
- *Condition*:
|
||||
- *GE* (greater or equal) – jump if value in R0 >= threshold
|
||||
|
||||
- *LT* (less than) – jump if value in R0 < threshold
|
||||
|
||||
**Cycles**
|
||||
2 (fetch) + 2 (execute)
|
||||
|
||||
**Description**
|
||||
The instruction makes a jump to a relative address if condition is true. Condition is the result of comparison of R0 register value and the threshold value.
|
||||
|
||||
**Examples**::
|
||||
|
||||
1:pos: JUMPR 16, 20, GE // Jump to address (position + 16 bytes) if value in R0 >= 20
|
||||
|
||||
2: // Down counting loop using R0 register
|
||||
MOVE R0, 16 // load 16 into R0
|
||||
label: SUB R0, R0, 1 // R0--
|
||||
NOP // do something
|
||||
JUMPR label, 1, GE // jump to label if R0 >= 1
|
||||
|
||||
|
||||
|
||||
**JUMPS** – Jump to a relative address (condition based on stage count)
|
||||
-----------------------------------------------------------------------
|
||||
|
||||
**Syntax**
|
||||
**JUMPS** *Step, Threshold, Condition*
|
||||
|
||||
**Operands**
|
||||
- *Step* – relative shift from current position, in bytes
|
||||
- *Threshold* – threshold value for branch condition
|
||||
- *Condition*:
|
||||
- *EQ* (equal) – jump if value in stage_cnt == threshold
|
||||
- *LT* (less than) – jump if value in stage_cnt < threshold
|
||||
- *GT* (greater than) – jump if value in stage_cnt > threshold
|
||||
|
||||
**Cycles**
|
||||
2 (fetch) + 2 (execute)
|
||||
|
||||
**Description**
|
||||
The instruction makes a jump to a relative address if condition is true. Condition is the result of comparison of count register value and threshold value.
|
||||
|
||||
**Examples**::
|
||||
|
||||
1:pos: JUMPS 16, 20, EQ // Jump to (position + 16 bytes) if stage_cnt == 20
|
||||
|
||||
2: // Up counting loop using stage count register
|
||||
STAGE_RST // set stage_cnt to 0
|
||||
label: STAGE_INC 1 // stage_cnt++
|
||||
NOP // do something
|
||||
JUMPS label, 16, LT // jump to label if stage_cnt < 16
|
||||
|
||||
|
||||
|
||||
**STAGE_RST** – Reset stage count register
|
||||
------------------------------------------
|
||||
**Syntax**
|
||||
**STAGE_RST**
|
||||
|
||||
**Operands**
|
||||
No operands
|
||||
|
||||
**Description**
|
||||
The instruction sets the stage count register to 0
|
||||
|
||||
**Cycles**
|
||||
2 (fetch) + 2 (execute)
|
||||
|
||||
**Examples**::
|
||||
|
||||
1: STAGE_RST // Reset stage count register
|
||||
|
||||
|
||||
|
||||
**STAGE_INC** – Increment stage count register
|
||||
----------------------------------------------
|
||||
|
||||
**Syntax**
|
||||
**STAGE_INC** *Value*
|
||||
|
||||
**Operands**
|
||||
- *Value* – 8 bits value
|
||||
|
||||
**Cycles**
|
||||
2 (fetch) + 2 (execute)
|
||||
|
||||
**Description**
|
||||
The instruction increments stage count register by given value.
|
||||
|
||||
**Examples**::
|
||||
|
||||
1: STAGE_INC 10 // stage_cnt += 10
|
||||
|
||||
2: // Up counting loop example:
|
||||
STAGE_RST // set stage_cnt to 0
|
||||
label: STAGE_INC 1 // stage_cnt++
|
||||
NOP // do something
|
||||
JUMPS label, 16, LT // jump to label if stage_cnt < 16
|
||||
|
||||
|
||||
**STAGE_DEC** – Decrement stage count register
|
||||
----------------------------------------------
|
||||
|
||||
**Syntax**
|
||||
**STAGE_DEC** *Value*
|
||||
|
||||
**Operands**
|
||||
- *Value* – 8 bits value
|
||||
|
||||
**Cycles**
|
||||
2 (fetch) + 2 (execute)
|
||||
|
||||
**Description**
|
||||
The instruction decrements stage count register by given value.
|
||||
|
||||
**Examples**::
|
||||
|
||||
1: STAGE_DEC 10 // stage_cnt -= 10;
|
||||
|
||||
2: // Down counting loop exaple
|
||||
STAGE_RST // set stage_cnt to 0
|
||||
STAGE_INC 16 // increment stage_cnt to 16
|
||||
label: STAGE_DEC 1 // stage_cnt--;
|
||||
NOP // do something
|
||||
JUMPS label, 0, GT // jump to label if stage_cnt > 0
|
||||
|
||||
|
||||
**HALT** – End the program
|
||||
--------------------------
|
||||
|
||||
**Syntax**
|
||||
**HALT**
|
||||
|
||||
**Operands**
|
||||
No operands
|
||||
|
||||
**Cycles**
|
||||
2 (fetch) + 2 (execute)
|
||||
|
||||
**Description**
|
||||
The instruction halts the ULP coprocessor and restarts ULP wakeup timer, if it is enabled.
|
||||
|
||||
**Examples**::
|
||||
|
||||
1: HALT // Halt the coprocessor
|
||||
|
||||
|
||||
|
||||
**WAKE** – Wake up the chip
|
||||
---------------------------
|
||||
|
||||
**Syntax**
|
||||
**WAKE**
|
||||
|
||||
**Operands**
|
||||
No operands
|
||||
|
||||
**Cycles**
|
||||
2 (fetch) + 2 (execute)
|
||||
|
||||
**Description**
|
||||
The instruction sends an interrupt from ULP to RTC controller.
|
||||
|
||||
- If the SoC is in deep sleep mode, and ULP wakeup is enabled, this causes the SoC to wake up.
|
||||
|
||||
- If the SoC is not in deep sleep mode, and ULP interrupt bit (RTC_CNTL_ULP_CP_INT_ENA) is set in RTC_CNTL_INT_ENA_REG register, RTC interrupt will be triggered.
|
||||
|
||||
Note that before using WAKE instruction, ULP program may needs to wait until RTC controller is ready to wake up the main CPU. This is indicated using RTC_CNTL_RDY_FOR_WAKEUP bit of RTC_CNTL_LOW_POWER_ST_REG register. If WAKE instruction is executed while RTC_CNTL_RDY_FOR_WAKEUP is zero, it has no effect (wake up does not occur).
|
||||
|
||||
**Examples**::
|
||||
|
||||
1: is_rdy_for_wakeup: // Read RTC_CNTL_RDY_FOR_WAKEUP bit
|
||||
READ_RTC_FIELD(RTC_CNTL_LOW_POWER_ST_REG, RTC_CNTL_RDY_FOR_WAKEUP)
|
||||
AND r0, r0, 1
|
||||
JUMP is_rdy_for_wakeup, eq // Retry until the bit is set
|
||||
WAKE // Trigger wake up
|
||||
REG_WR 0x006, 24, 24, 0 // Stop ULP timer (clear RTC_CNTL_ULP_CP_SLP_TIMER_EN)
|
||||
HALT // Stop the ULP program
|
||||
// After these instructions, SoC will wake up,
|
||||
// and ULP will not run again until started by the main program.
|
||||
|
||||
|
||||
|
||||
**SLEEP** – set ULP wakeup timer period
|
||||
---------------------------------------
|
||||
|
||||
**Syntax**
|
||||
**SLEEP** *sleep_reg*
|
||||
|
||||
**Operands**
|
||||
- *sleep_reg* – 0..4, selects one of ``SENS_ULP_CP_SLEEP_CYCx_REG`` registers.
|
||||
|
||||
**Cycles**
|
||||
2 (fetch) + 2 (execute)
|
||||
|
||||
**Description**
|
||||
The instruction selects which of the ``SENS_ULP_CP_SLEEP_CYCx_REG`` (x = 0..4) register values is to be used by the ULP wakeup timer as wakeup period. By default, the value from ``SENS_ULP_CP_SLEEP_CYC0_REG`` is used.
|
||||
|
||||
**Examples**::
|
||||
|
||||
1: SLEEP 1 // Use period set in SENS_ULP_CP_SLEEP_CYC1_REG
|
||||
|
||||
2: .set sleep_reg, 4 // Set constant
|
||||
SLEEP sleep_reg // Use period set in SENS_ULP_CP_SLEEP_CYC4_REG
|
||||
|
||||
|
||||
**WAIT** – wait some number of cycles
|
||||
-------------------------------------
|
||||
|
||||
**Syntax**
|
||||
**WAIT** *Cycles*
|
||||
|
||||
**Operands**
|
||||
- *Cycles* – number of cycles for wait
|
||||
|
||||
**Cycles**
|
||||
2 (fetch) + *Cycles* (execute)
|
||||
|
||||
**Description**
|
||||
The instruction delays for given number of cycles.
|
||||
|
||||
**Examples**::
|
||||
|
||||
1: WAIT 10 // Do nothing for 10 cycles
|
||||
|
||||
2: .set wait_cnt, 10 // Set a constant
|
||||
WAIT wait_cnt // wait for 10 cycles
|
||||
|
||||
|
||||
|
||||
|
||||
**TSENS** – do measurement with temperature sensor
|
||||
--------------------------------------------------
|
||||
|
||||
**Syntax**
|
||||
- **TSENS** *Rdst, Wait_Delay*
|
||||
|
||||
**Operands**
|
||||
- *Rdst* – Destination Register R[0..3], result will be stored to this register
|
||||
- *Wait_Delay* – number of cycles used to perform the measurement
|
||||
|
||||
**Cycles**
|
||||
2 (fetch) + *Wait_Delay* + 3 * TSENS_CLK
|
||||
|
||||
**Description**
|
||||
The instruction performs measurement using TSENS and stores the result into a general purpose register.
|
||||
|
||||
**Examples**::
|
||||
|
||||
1: TSENS R1, 1000 // Measure temperature sensor for 1000 cycles,
|
||||
// and store result to R1
|
||||
|
||||
|
||||
|
||||
|
||||
**ADC** – do measurement with ADC
|
||||
---------------------------------
|
||||
|
||||
**Syntax**
|
||||
- **ADC** *Rdst, Sar_sel, Mux*
|
||||
|
||||
- **ADC** *Rdst, Sar_sel, Mux, 0* — deprecated form
|
||||
|
||||
**Operands**
|
||||
- *Rdst* – Destination Register R[0..3], result will be stored to this register
|
||||
- *Sar_sel* – Select ADC: 0 = SARADC1, 1 = SARADC2
|
||||
- *Mux* - selected PAD, SARADC Pad[Mux+1] is enabled
|
||||
|
||||
**Cycles**
|
||||
2 (fetch) + 21 + max(1, SAR_AMP_WAIT1) + max(1, SAR_AMP_WAIT2) + max(1, SAR_AMP_WAIT3) + SARx_SAMPLE_CYCLE + SARx_SAMPLE_BIT
|
||||
|
||||
**Description**
|
||||
The instruction makes measurements from ADC.
|
||||
|
||||
**Examples**::
|
||||
|
||||
1: ADC R1, 0, 1 // Measure value using ADC1 pad 2 and store result into R1
|
||||
|
||||
**I2C_RD** - read single byte from I2C slave
|
||||
----------------------------------------------
|
||||
|
||||
**Syntax**
|
||||
- **I2C_RD** *Sub_addr, High, Low, Slave_sel*
|
||||
|
||||
**Operands**
|
||||
- *Sub_addr* – Address within the I2C slave to read.
|
||||
- *High*, *Low* — Define range of bits to read. Bits outside of [High, Low] range are masked.
|
||||
- *Slave_sel* - Index of I2C slave address to use.
|
||||
|
||||
**Cycles**
|
||||
2 (fetch) + I2C communication time
|
||||
|
||||
**Description**
|
||||
``I2C_RD`` instruction reads one byte from I2C slave with index ``Slave_sel``. Slave address (in 7-bit format) has to be set in advance into `SENS_I2C_SLAVE_ADDRx` register field, where ``x == Slave_sel``.
|
||||
8 bits of read result is stored into `R0` register.
|
||||
|
||||
**Examples**::
|
||||
|
||||
1: I2C_RD 0x10, 7, 0, 0 // Read byte from sub-address 0x10 of slave with address set in SENS_I2C_SLAVE_ADDR0
|
||||
|
||||
|
||||
**I2C_WR** - write single byte to I2C slave
|
||||
----------------------------------------------
|
||||
|
||||
**Syntax**
|
||||
- **I2C_WR** *Sub_addr, Value, High, Low, Slave_sel*
|
||||
|
||||
**Operands**
|
||||
- *Sub_addr* – Address within the I2C slave to write.
|
||||
- *Value* – 8-bit value to be written.
|
||||
- *High*, *Low* — Define range of bits to write. Bits outside of [High, Low] range are masked.
|
||||
- *Slave_sel* - Index of I2C slave address to use.
|
||||
|
||||
**Cycles**
|
||||
2 (fetch) + I2C communication time
|
||||
|
||||
**Description**
|
||||
``I2C_WR`` instruction writes one byte to I2C slave with index ``Slave_sel``. Slave address (in 7-bit format) has to be set in advance into `SENS_I2C_SLAVE_ADDRx` register field, where ``x == Slave_sel``.
|
||||
|
||||
**Examples**::
|
||||
|
||||
1: I2C_WR 0x20, 0x33, 7, 0, 1 // Write byte 0x33 to sub-address 0x20 of slave with address set in SENS_I2C_SLAVE_ADDR1.
|
||||
|
||||
|
||||
**REG_RD** – read from peripheral register
|
||||
------------------------------------------
|
||||
|
||||
**Syntax**
|
||||
**REG_RD** *Addr, High, Low*
|
||||
|
||||
**Operands**
|
||||
- *Addr* – register address, in 32-bit words
|
||||
- *High* – High part of R0
|
||||
- *Low* – Low part of R0
|
||||
|
||||
**Cycles**
|
||||
2 (fetch) + 6 (execute)
|
||||
|
||||
**Description**
|
||||
The instruction reads up to 16 bits from a peripheral register into a general purpose register: ``R0 = REG[Addr][High:Low]``.
|
||||
|
||||
This instruction can access registers in RTC_CNTL, RTC_IO, SENS, and RTC_I2C peripherals. Address of the the register, as seen from the ULP,
|
||||
can be calculated from the address of the same register on the DPORT bus as follows::
|
||||
|
||||
addr_ulp = (addr_dport - DR_REG_RTCCNTL_BASE) / 4
|
||||
|
||||
**Examples**::
|
||||
|
||||
1: REG_RD 0x120, 2, 0 // load 4 bits: R0 = {12'b0, REG[0x120][7:4]}
|
||||
|
||||
|
||||
**REG_WR** – write to peripheral register
|
||||
-----------------------------------------
|
||||
|
||||
**Syntax**
|
||||
**REG_WR** *Addr, High, Low, Data*
|
||||
|
||||
**Operands**
|
||||
- *Addr* – register address, in 32-bit words.
|
||||
- *High* – High part of R0
|
||||
- *Low* – Low part of R0
|
||||
- *Data* – value to write, 8 bits
|
||||
|
||||
**Cycles**
|
||||
2 (fetch) + 10 (execute)
|
||||
|
||||
**Description**
|
||||
The instruction writes up to 8 bits from a general purpose register into a peripheral register. ``REG[Addr][High:Low] = data``
|
||||
|
||||
This instruction can access registers in RTC_CNTL, RTC_IO, SENS, and RTC_I2C peripherals. Address of the the register, as seen from the ULP,
|
||||
can be calculated from the address of the same register on the DPORT bus as follows::
|
||||
|
||||
addr_ulp = (addr_dport - DR_REG_RTCCNTL_BASE) / 4
|
||||
|
||||
**Examples**::
|
||||
|
||||
1: REG_WR 0x120, 7, 0, 0x10 // set 8 bits: REG[0x120][7:0] = 0x10
|
||||
|
||||
Convenience macros for peripheral registers access
|
||||
--------------------------------------------------
|
||||
|
||||
ULP source files are passed through C preprocessor before the assembler. This allows certain macros to be used to facilitate access to peripheral registers.
|
||||
|
||||
Some existing macros are defined in ``soc/soc_ulp.h`` header file. These macros allow access to the fields of peripheral registers by their names.
|
||||
Peripheral registers names which can be used with these macros are the ones defined in ``soc/rtc_cntl_reg.h``, ``soc/rtc_io_reg.h``, ``soc/sens_reg.h``, and ``soc/rtc_i2c_reg.h``.
|
||||
|
||||
READ_RTC_REG(rtc_reg, low_bit, bit_width)
|
||||
Read up to 16 bits from rtc_reg[low_bit + bit_width - 1 : low_bit] into R0. For example::
|
||||
|
||||
#include "soc/soc_ulp.h"
|
||||
#include "soc/rtc_cntl_reg.h"
|
||||
|
||||
/* Read 16 lower bits of RTC_CNTL_TIME0_REG into R0 */
|
||||
READ_RTC_REG(RTC_CNTL_TIME0_REG, 0, 16)
|
||||
|
||||
READ_RTC_FIELD(rtc_reg, field)
|
||||
Read from a field in rtc_reg into R0, up to 16 bits. For example::
|
||||
|
||||
#include "soc/soc_ulp.h"
|
||||
#include "soc/sens_reg.h"
|
||||
|
||||
/* Read 8-bit SENS_TSENS_OUT field of SENS_SAR_SLAVE_ADDR3_REG into R0 */
|
||||
READ_RTC_FIELD(SENS_SAR_SLAVE_ADDR3_REG, SENS_TSENS_OUT)
|
||||
|
||||
WRITE_RTC_REG(rtc_reg, low_bit, bit_width, value)
|
||||
Write immediate value into rtc_reg[low_bit + bit_width - 1 : low_bit], bit_width <= 8. For example::
|
||||
|
||||
#include "soc/soc_ulp.h"
|
||||
#include "soc/rtc_io_reg.h"
|
||||
|
||||
/* Set BIT(2) of RTC_GPIO_OUT_DATA_W1TS field in RTC_GPIO_OUT_W1TS_REG */
|
||||
WRITE_RTC_REG(RTC_GPIO_OUT_W1TS_REG, RTC_GPIO_OUT_DATA_W1TS_S + 2, 1, 1)
|
||||
|
||||
|
||||
WRITE_RTC_FIELD(rtc_reg, field, value)
|
||||
Write immediate value into a field in rtc_reg, up to 8 bits. For example::
|
||||
|
||||
#include "soc/soc_ulp.h"
|
||||
#include "soc/rtc_cntl_reg.h"
|
||||
|
||||
/* Set RTC_CNTL_ULP_CP_SLP_TIMER_EN field of RTC_CNTL_STATE0_REG to 0 */
|
||||
WRITE_RTC_FIELD(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN, 0)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
.. include:: ../../../components/ulp/README.rst
|
||||
@@ -0,0 +1,155 @@
|
||||
Unit Testing in ESP32
|
||||
=====================
|
||||
|
||||
ESP-IDF comes with a unit test app based on Unity - unit test framework. Unit tests are integrated in the ESP-IDF repository and are placed in ``test`` subdirectory of each component respectively.
|
||||
|
||||
Add normal test cases
|
||||
---------------------
|
||||
|
||||
Unit tests are added in the ``test`` subdirectory of the respective component.
|
||||
Tests are added in C files, a single C file can include multiple test cases.
|
||||
Test files start with the word "test".
|
||||
|
||||
The test file should include unity.h and the header for the C module to be tested.
|
||||
|
||||
Tests are added in a function in the C file as follows::
|
||||
|
||||
TEST_CASE("test name", "[module name]"
|
||||
{
|
||||
// Add test here
|
||||
}
|
||||
|
||||
First argument is a descriptive name for the test, second argument is an identifier in square brackets.
|
||||
Identifiers are used to group related test, or tests with specific properties.
|
||||
|
||||
There is no need to add a main function with ``UNITY_BEGIN()`` and ``UNITY_END()`` in each test case.
|
||||
``unity_platform.c`` will run ``UNITY_BEGIN()``, run the tests cases, and then call ``UNITY_END()``.
|
||||
|
||||
Each `test` subdirectory needs to include component.mk file with at least the following line of code::
|
||||
|
||||
COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive
|
||||
|
||||
See http://www.throwtheswitch.org/unity for more information about writing tests in Unity.
|
||||
|
||||
|
||||
Add multiple devices test cases
|
||||
-------------------------------
|
||||
|
||||
The normal test cases will be executed on one DUT (Device Under Test). Components need to communicate with each other (like GPIO, SPI ...) can't be tested with normal test cases.
|
||||
Multiple devices test cases support writing and running test with multiple DUTs.
|
||||
|
||||
Here's an example of multiple devices test case::
|
||||
|
||||
void gpio_master_test()
|
||||
{
|
||||
gpio_config_t slave_config = {
|
||||
.pin_bit_mask = 1 << MASTER_GPIO_PIN,
|
||||
.mode = GPIO_MODE_INPUT,
|
||||
};
|
||||
gpio_config(&slave_config);
|
||||
unity_wait_for_signal("output high level");
|
||||
TEST_ASSERT(gpio_get_level(MASTER_GPIO_PIN) == 1);
|
||||
}
|
||||
|
||||
void gpio_slave_test()
|
||||
{
|
||||
gpio_config_t master_config = {
|
||||
.pin_bit_mask = 1 << SLAVE_GPIO_PIN,
|
||||
.mode = GPIO_MODE_OUTPUT,
|
||||
};
|
||||
gpio_config(&master_config);
|
||||
gpio_set_level(SLAVE_GPIO_PIN, 1);
|
||||
unity_send_signal("output high level");
|
||||
}
|
||||
|
||||
TEST_CASE_MULTIPLE_DEVICES("gpio multiple devices test example", "[driver]", gpio_master_test, gpio_slave_test);
|
||||
|
||||
|
||||
The macro ``TEST_CASE_MULTIPLE_DEVICES`` is used to declare multiple devices test cases.
|
||||
First argument is test case name, second argument is test case description.
|
||||
From the third argument, upto 5 test functions can be defined, each function will be the entry point of tests running on each DUT.
|
||||
|
||||
Running test cases from different DUTs could require synchronizing between DUTs. We provide ``unity_wait_for_signal`` and ``unity_send_signal`` to support synchronizing with UART.
|
||||
As the secnario in the above example, slave should get GPIO level after master set level. DUT UART console will prompt and requires user interaction:
|
||||
|
||||
DUT1 (master) console::
|
||||
|
||||
Waiting for signal: [output high level]!
|
||||
Please press "Enter" key to once any board send this signal.
|
||||
|
||||
DUT2 (slave) console::
|
||||
|
||||
Send signal: [output high level]!
|
||||
|
||||
Once the signal is set from DUT2, you need to press "Enter" on DUT1, then DUT1 unblocks from ``unity_wait_for_signal`` and starts to change GPIO level.
|
||||
|
||||
|
||||
Building unit test app
|
||||
----------------------
|
||||
|
||||
Follow the setup instructions in the top-level esp-idf README.
|
||||
Make sure that IDF_PATH environment variable is set to point to the path of esp-idf top-level directory.
|
||||
|
||||
Change into tools/unit-test-app directory to configure and build it:
|
||||
|
||||
* `make menuconfig` - configure unit test app.
|
||||
|
||||
* `make TESTS_ALL=1` - build unit test app with tests for each component having tests in the ``test`` subdirectory.
|
||||
* `make TEST_COMPONENTS='xxx'` - build unit test app with tests for specific components.
|
||||
|
||||
When the build finishes, it will print instructions for flashing the chip. You can simply run ``make flash`` to flash all build output.
|
||||
|
||||
You can also run ``make flash TESTS_ALL=1`` or ``make TEST_COMPONENTS='xxx'`` to build and flash. Everything needed will be rebuilt automatically before flashing.
|
||||
|
||||
Use menuconfig to set the serial port for flashing.
|
||||
|
||||
Running unit tests
|
||||
------------------
|
||||
|
||||
After flashing reset the ESP32 and it will boot the unit test app.
|
||||
|
||||
When unit test app is idle, press "Enter" will make it print test menu with all available tests::
|
||||
|
||||
Here's the test menu, pick your combo:
|
||||
(1) "esp_ota_begin() verifies arguments" [ota]
|
||||
(2) "esp_ota_get_next_update_partition logic" [ota]
|
||||
(3) "Verify bootloader image in flash" [bootloader_support]
|
||||
(4) "Verify unit test app image" [bootloader_support]
|
||||
(5) "can use new and delete" [cxx]
|
||||
(6) "can call virtual functions" [cxx]
|
||||
(7) "can use static initializers for non-POD types" [cxx]
|
||||
(8) "can use std::vector" [cxx]
|
||||
(9) "static initialization guards work as expected" [cxx]
|
||||
(10) "global initializers run in the correct order" [cxx]
|
||||
(11) "before scheduler has started, static initializers work correctly" [cxx]
|
||||
(12) "adc2 work with wifi" [adc]
|
||||
(13) "gpio master/slave test example" [ignore][misc][test_env=UT_T2_1]
|
||||
(1) "gpio_master_test"
|
||||
(2) "gpio_slave_test"
|
||||
(14) "SPI Master clockdiv calculation routines" [spi]
|
||||
(15) "SPI Master test" [spi][ignore]
|
||||
(16) "SPI Master test, interaction of multiple devs" [spi][ignore]
|
||||
(17) "SPI Master no response when switch from host1 (HSPI) to host2 (VSPI)" [spi]
|
||||
(18) "SPI Master DMA test, TX and RX in different regions" [spi]
|
||||
(19) "SPI Master DMA test: length, start, not aligned" [spi]
|
||||
|
||||
Normal case will print the case name and description. Master slave cases will also print the sub-menu (the registered test function names).
|
||||
|
||||
Test cases can be run by inputting one of the following:
|
||||
|
||||
- Test case name in quotation marks to run a single test case
|
||||
|
||||
- Test case index to run a single test case
|
||||
|
||||
- Module name in square brackets to run all test cases for a specific module
|
||||
|
||||
- An asterisk to run all test cases
|
||||
|
||||
After you select multiple devices test case, it will print sub menu::
|
||||
|
||||
Running gpio master/slave test example...
|
||||
gpio master/slave test example
|
||||
(1) "gpio_master_test"
|
||||
(2) "gpio_slave_test"
|
||||
|
||||
You need to input number to select the test running on the DUT.
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user