mirror of
https://gitee.com/beecue/fastbee.git
synced 2025-12-19 09:25:54 +08:00
添加智能灯固件代码
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
# System Examples
|
||||
|
||||
Configuration and management of memory, interrupts, WDT (watchdog timer), OTA (over the air updates), deep sleep logging, and event loops.
|
||||
|
||||
See the [README.md](../README.md) file in the upper level [examples](../) directory for more information about examples.
|
||||
@@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(app_trace_to_host)
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := app_trace_to_host
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
| Supported Targets | ESP32 |
|
||||
| ----------------- | ----- |
|
||||
|
||||
# Application Level Tracing Example (Logging to Host)
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
This example demonstrates how to use the [Application Level Tracing Library](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/app_trace.html#) (henceforth referred to as **App Trace**) to log messages to a host via JTAG instead of the normal method of logging via UART.
|
||||
|
||||
UART logs are time consuming and can significantly slow down the function that calls it. Therefore, it is generally a bad idea to use UART logs in time-critical functions. Logging to host via JTAG is significantly faster and can be used in time-critical functions. For more details regarding logging to host via JTAG, refer to the [Logging to Host Documentation](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/app_trace.html#app-trace-logging-to-host).
|
||||
|
||||
This example demonstrates JTAG logging to host in the context of polling for a [zero crossing](https://en.wikipedia.org/wiki/Zero_crossing). The ESP32 will continuously sample a 50 to 60 Hz sinusoidal signal (using the ADC) and log the sampled values (via JTAG). Due to the higher speed of JTAG logging, the polling rate of the ADC should be high enough to detect a zero crossing.
|
||||
|
||||
This example utilizes the following ESP-IDF features:
|
||||
* [DAC driver](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/dac.html) to generate the 50 Hz sinusoidal signal.
|
||||
* [ADC driver](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/adc.html) to sample the sinusoidal signal.
|
||||
* [Application Level Tracing Library](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/app_trace.html#) to log ADC samples to host.
|
||||
* [OpenOCD](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/jtag-debugging/index.html#setup-of-openocd) to interface with the ESP32 and receive the log output over JTAG.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
To run this example, you need an ESP32 dev board connected to a JTAG adapter, which can come in the following forms:
|
||||
|
||||
* [ESP-WROVER-KIT](https://docs.espressif.com/projects/esp-idf/en/latest/hw-reference/modules-and-boards.html#esp-wrover-kit-v4-1) which integrates an on-board JTAG adapter. Ensure that the [required jumpers to enable JTAG are connected](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/get-started-wrover-kit.html#setup-options) on the WROVER-KIT.
|
||||
* ESP32 core board (e.g. ESP32-DevKitC) can also work as long as you connect it to an external JTAG adapter (e.g. FT2232H, J-LINK).
|
||||
|
||||
This example will assume that that an ESP-WROVER-KIT is used.
|
||||
|
||||
#### Pin Assignment:
|
||||
|
||||
The sinusoidal signal of 50 to 60 Hz ranging from 0 V ~ 3.1 V should be input into `GPIO34` (`ADC1_CHANNEL_6`). Users may provide this signal themselves, our use the DAC generated signal by bridging GPIO34 with `GPIO25` (`DAC_CHANNEL_1`).
|
||||
|
||||
| DAC Output | ADC Input |
|
||||
| ------------------ | ------------------ |
|
||||
| Channel 1 (GPIO25) | Channel 6 (GPIO34) |
|
||||
|
||||
#### Extra Connections:
|
||||
|
||||
1. Connect the JTAG interface to ESP32 board, and power up both the JTAG and ESP32. For details about how to set up JTAG interface, please see [JTAG Debugging](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/jtag-debugging/index.html).
|
||||
|
||||
2. After connecting JTAG interface, you need to [Run OpenOCD](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/jtag-debugging/index.html#run-openocd).
|
||||
|
||||
3. Open a separate terminal window and run telnet by entering the command below. The telnet terminal window is used to feed commands to OpenOCD:
|
||||
|
||||
```bash
|
||||
telnet localhost 4444
|
||||
```
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
* By default, the DAC will generate 130 Hz signal ranging from 0 V ~ 3.1 V. To generate a 50 Hz signal, the RTC 8 MHz clock will need to use a non-standard divider. This is achieved by enabling the `Example Configuration > Set custom RTC 8 MHz clock divider to lower CW frequency` configuration option.
|
||||
|
||||
* To enable application tracing, select the `(X) Trace memory` option under `Component config > Application Level Tracing`. This option should have been selected by default.
|
||||
|
||||
### Build, Flash, and Run
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
**Start App Trace:** In the telnet session window, trigger OpenOCD to start App Trace on the ESP32 by entering the command below. This command will collect 9000 bytes of JTAG log data and save them to `adc.log` file in `~/esp/openocd-esp32` folder.
|
||||
|
||||
```bash
|
||||
esp apptrace start file://adc.log 0 9000 5 0 0
|
||||
```
|
||||
|
||||
**Note:** For more details on OpenOCD commands regarding App Trace, refer to the [OpenOCD Application Level Tracing Commands](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/app_trace.html#openocd-application-level-tracing-commands)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
The example will continuously sample the ADC for 20ms per iteration, and will alternate between JTAG and UART logging per iteration. However, the JTAG logs should be captured by OpenOCD, thus will not appear in the monitor's output. Therefore, the monitor should only display the iterations where UART logging was used (i.e. every alternate iteration) such as the following:
|
||||
|
||||
```
|
||||
I (4289) example: Sampling ADC and sending data to the host...
|
||||
I (4309) example: Collected 427 samples in 20 ms.
|
||||
|
||||
I (4309) example: Sampling ADC and sending data to the UART...
|
||||
I (4309) example: Sample:1, Value:2224
|
||||
I (4309) example: Sample:2, Value:840
|
||||
I (4309) example: Sample:3, Value:3503
|
||||
I (4319) example: Sample:4, Value:27
|
||||
I (4319) example: Sample:5, Value:4095
|
||||
I (4329) example: Collected 5 samples in 20 ms.
|
||||
```
|
||||
|
||||
**Note:** The UART log above was produced with the CPU running at 240 MHz.
|
||||
|
||||
To access the JTAG logs, the `adc.log` file should be decoded. This can be done by using the `logtrace_proc.py` script as such:
|
||||
|
||||
```bash
|
||||
$IDF_PATH/tools/esp_app_trace/logtrace_proc.py ~/esp/openocd-esp32/adc.log ~/esp/app_trace_to_host/build/app_trace_to_host_test.elf
|
||||
```
|
||||
|
||||
The `logtrace_proc.py` script should produce the following output when decoding:
|
||||
|
||||
```
|
||||
Parse trace file '/user-home/esp/openocd-esp32/adc.log'...
|
||||
Unprocessed 7 bytes of log record args!
|
||||
Parsing completed.
|
||||
====================================================================
|
||||
I (59369) example: Sample:1, Value:3717
|
||||
I (59369) example: Sample:2, Value:3647
|
||||
I (59369) example: Sample:3, Value:3575
|
||||
I (59369) example: Sample:4, Value:3491
|
||||
...
|
||||
|
||||
I (59379) example: Sample:398, Value:78
|
||||
I (59379) example: Sample:399, Value:58
|
||||
I (59379) example: Sample:400, Value:22
|
||||
I (59379) example: Sample:401, Value:14
|
||||
I (59379) example: Sample:402, Value:0
|
||||
I (59379) example: Sample:403, Value:0
|
||||
I (59379) example: Sample:404, Value:0
|
||||
I (59379) example: Sample:405, Value:0
|
||||
I (59379) example: Sample:406, Value:0
|
||||
I (59379) example: Sample:407, Value:0
|
||||
I (59379) example: Sample:408, Value:0
|
||||
I (59379) example: Sample:409, Value:0
|
||||
I (59379) example: Sample:410, Value:0
|
||||
I (59379) example: Sample:411, Value:0
|
||||
I (59379) example: Sample:412, Value:0
|
||||
I (59379) example: Sample:413, Value:0
|
||||
I (59379) example: Sample:414, Value:16
|
||||
I (59379) example: Sample:415, Value:32
|
||||
I (59379) example: Sample:416, Value:40
|
||||
I (59379) example: Sample:417, Value:74
|
||||
I (59379) example: Sample:418, Value:89
|
||||
I (59379) example: Sample:419, Value:113
|
||||
I (59379) example: Sample:420, Value:160
|
||||
I (59379) example: Sample:421, Value:192
|
||||
I (59379) example: Sample:422, Value:221
|
||||
I (59379) example: Sample:423, Value:256
|
||||
I (59379) example: Sample:424, Value:298
|
||||
I (59379) example: Sample:425, Value:345
|
||||
I (59379) example: Sample:426, Value:386
|
||||
I (59379) example: Sample:427, Value:432
|
||||
I (61409) example: Sample:1, Value:2653
|
||||
|
||||
====================================================================
|
||||
|
||||
Log records count: 428
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Unable to flash when OpenOCD is connected to ESP32
|
||||
|
||||
One likely cause would be an incorrect SPI flash voltage when starting OpenOCD. Suppose an ESP32 board/module with a 3.3 V powered SPI flash is being used, but the `board/esp32-wrover.cfg` configuration file is selected when starting OpenOCD which can set the SPI flash voltage to 1.8 V. In this situation, the SPI flash will not work after OpenOCD connects to the ESP32 as OpenOCD has changed the SPI flash voltage. Therefore, you might not be able to flash ESP32 when OpenOCD is connected.
|
||||
|
||||
To work around this issue, users are suggested to use `board/esp32-wrover.cfg` for ESP32 boards/modules operating with an SPI flash voltage of 1.8 V, and `board/esp-wroom-32.cfg` for 3.3 V. Refer to [ESP32 Modules and Boards](https://docs.espressif.com/projects/esp-idf/en/latest/hw-reference/modules-and-boards.html) and [Set SPI Flash Voltage](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/jtag-debugging/tips-and-quirks.html#why-to-set-spi-flash-voltage-in-openocd-configuration) for more details.
|
||||
|
||||
(For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you as soon as possible.)
|
||||
|
||||
The log should be identical to those printed via UART (complete with timestamps), but almost two orders of magnitude faster.
|
||||
|
||||
## Example Breakdown
|
||||
|
||||
The following code snippet demonstrates a loop of the sampling and logging the ADC over a 20 ms period in order to capture one full period of a 50 Hz signal.
|
||||
|
||||
```c
|
||||
int sampling_period = 20;
|
||||
int i = 0;
|
||||
uint32_t sampling_start = esp_log_timestamp(); //this clock counts milliseconds
|
||||
do {
|
||||
ESP_LOGI(TAG, "Sample:%d, Value:%d", ++i, adc1_get_raw(ADC1_TEST_CHANNEL));
|
||||
} while (esp_log_timestamp() - sampling_start < sampling_period);
|
||||
```
|
||||
|
||||
If `ESP_LOGI()` is routed via UART (occurs by default), the log output produced will likely resemble the output shown below. Notice that due to UART logging is time consuming, thus the ADC is only sampled five times, which is too infrequent to consistently detect a zero crossing (where the zero crossing is `4096/2 = 2048` i.e., the mid point of the 12-bit ADC).
|
||||
|
||||
```bash
|
||||
I (4309) example: Sample:1, Value:2224
|
||||
I (4309) example: Sample:2, Value:840
|
||||
I (4309) example: Sample:3, Value:3503
|
||||
I (4319) example: Sample:4, Value:27
|
||||
I (4319) example: Sample:5, Value:4095
|
||||
I (4329) example: Collected 5 samples in 20 ms.
|
||||
```
|
||||
|
||||
However, by logging via JTAG, the logging is much quicker hence allows a much higher sampling frequency (over 400 times) as shown the the log output below thus would be able to detect a zero crossing more consistently.
|
||||
|
||||
```c
|
||||
esp_log_set_vprintf(esp_apptrace_vprintf);
|
||||
```
|
||||
|
||||
```bash
|
||||
...
|
||||
|
||||
I (59379) example: Sample:423, Value:256
|
||||
I (59379) example: Sample:424, Value:298
|
||||
I (59379) example: Sample:425, Value:345
|
||||
I (59379) example: Sample:426, Value:386
|
||||
I (59379) example: Sample:427, Value:432
|
||||
I (61409) example: Sample:1, Value:2653
|
||||
|
||||
====================================================================
|
||||
|
||||
Log records count: 428
|
||||
|
||||
```
|
||||
|
||||
This example has demonstrated powerful functionality of logging to host via JTAG interface. With standard UART communication at a baud rate of 115200, printing out a single line log message takes approximately 4 ms. This also means that logged tasks cannot run more frequently than every 4 ms. By providing the same logging over JTAG, logging performance is improved 80 fold.
|
||||
@@ -0,0 +1,50 @@
|
||||
from __future__ import unicode_literals
|
||||
import os
|
||||
import re
|
||||
import ttfw_idf
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag="test_jtag_arm")
|
||||
def test_examples_app_trace_to_host(env, extra_data):
|
||||
|
||||
rel_project_path = os.path.join('examples', 'system', 'app_trace_to_host')
|
||||
dut = env.get_dut('app_trace_to_host', rel_project_path)
|
||||
idf_path = dut.app.get_sdk_path()
|
||||
proj_path = os.path.join(idf_path, rel_project_path)
|
||||
|
||||
with ttfw_idf.OCDProcess(os.path.join(proj_path, 'openocd.log')):
|
||||
with ttfw_idf.TelnetProcess(os.path.join(proj_path, 'telnet.log')) as telnet_p:
|
||||
dut.start_app()
|
||||
dut.expect_all('example: Enabling ADC1 on channel 6 / GPIO34.',
|
||||
'example: Enabling CW generator on DAC channel 1',
|
||||
'example: Custom divider of RTC 8 MHz clock has been set.',
|
||||
'example: Sampling ADC and sending data to the host...',
|
||||
re.compile(r'example: Collected \d+ samples in 20 ms.'),
|
||||
'example: Sampling ADC and sending data to the UART...',
|
||||
re.compile(r'example: Sample:\d, Value:\d+'),
|
||||
re.compile(r'example: Collected \d+ samples in 20 ms.'),
|
||||
timeout=20)
|
||||
|
||||
telnet_p.pexpect_proc.sendline('esp apptrace start file://adc.log 0 9000 5 0 0')
|
||||
telnet_p.pexpect_proc.expect_exact('App trace params: from 2 cores, size 9000 bytes, '
|
||||
'stop_tmo 5 s, poll period 0 ms, wait_rst 0, skip 0 bytes')
|
||||
telnet_p.pexpect_proc.expect_exact('Targets connected.')
|
||||
telnet_p.pexpect_proc.expect_exact('Targets disconnected.')
|
||||
telnet_p.pexpect_proc.expect_exact('Tracing is STOPPED. Size is 9000 of 9000 @')
|
||||
telnet_p.pexpect_proc.expect_exact('Data: blocks incomplete 0, lost bytes: 0')
|
||||
|
||||
with ttfw_idf.CustomProcess(' '.join([os.path.join(idf_path, 'tools/esp_app_trace/logtrace_proc.py'),
|
||||
'adc.log',
|
||||
os.path.join(dut.app.get_binary_path(rel_project_path),
|
||||
'app_trace_to_host.elf')]),
|
||||
logfile='logtrace_proc.log') as logtrace:
|
||||
logtrace.pexpect_proc.expect_exact('Parse trace file')
|
||||
logtrace.pexpect_proc.expect_exact('Parsing completed.')
|
||||
logtrace.pexpect_proc.expect_exact('====================================================================')
|
||||
logtrace.pexpect_proc.expect(re.compile(r'example: Sample:\d+, Value:\d+'))
|
||||
logtrace.pexpect_proc.expect_exact('====================================================================')
|
||||
logtrace.pexpect_proc.expect(re.compile(r'Log records count: \d+'))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_examples_app_trace_to_host()
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "app_trace_to_host_example_main.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,20 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config CUSTOM_RTC_CLK_8M_DIV
|
||||
bool "Set custom RTC 8 MHz clock divider to lower CW frequency (CHECK HELP FIRST)"
|
||||
default "n"
|
||||
help
|
||||
Set custom / non standard divider for RTC 8 MHz clock.
|
||||
This is to lower minimum frequency of cosine waveform generator (CW)
|
||||
in order to provide sinusoidal signal at about 50 or 60 Hz.
|
||||
|
||||
WARNINIG: setting non standard divider for the RTC 8 MHz clock
|
||||
will affect functionality of RTC peripherals other than CW.
|
||||
|
||||
This includes ADC sampling, and will in general make
|
||||
all RTC register access slower.
|
||||
|
||||
DO NOT use this option / change default RTC 8 MHz clock divider
|
||||
in your applications, if you are not sure what you are doing.
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,125 @@
|
||||
/* Application Trace to Host Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_app_trace.h"
|
||||
#include "esp_log.h"
|
||||
#include "soc/rtc_periph.h"
|
||||
#include "soc/sens_periph.h"
|
||||
#include "driver/adc.h"
|
||||
#include "driver/dac.h"
|
||||
|
||||
#define ADC1_TEST_CHANNEL (ADC1_CHANNEL_6)
|
||||
#define TEST_SAMPLING_PERIOD 20
|
||||
|
||||
/*
|
||||
* When setting custom divider of RTC 8 MHz clock in menuconfig,
|
||||
* use the following values to set the CW frequency:
|
||||
* ~ 50 Hz (entered below)
|
||||
* RTC_CLK_8M_DIV 7
|
||||
* CW_FREQUENCY_STEP 3
|
||||
* ~ 60 Hz
|
||||
* RTC_CLK_8M_DIV 1
|
||||
* CW_FREQUENCY_STEP 1
|
||||
*/
|
||||
#ifdef CONFIG_CUSTOM_RTC_CLK_8M_DIV
|
||||
#define RTC_CLK_8M_DIV 7
|
||||
#define CW_FREQUENCY_STEP 3
|
||||
#else
|
||||
#define CW_FREQUENCY_STEP 1
|
||||
#endif
|
||||
|
||||
static const char *TAG = "example";
|
||||
|
||||
/*
|
||||
* Enable cosine waveform generator (CW)
|
||||
* on channel 1 / GPIO25 to provide sinusoidal signal
|
||||
* It can be used instead of a live signal for testing
|
||||
* of speed of logging to the host
|
||||
* sequentially with data retrieval from ADC
|
||||
*/
|
||||
static void enable_cosine_generator(void)
|
||||
{
|
||||
// Enable tone generator common to both DAC channels 1 and 2
|
||||
SET_PERI_REG_MASK(SENS_SAR_DAC_CTRL1_REG, SENS_SW_TONE_EN);
|
||||
// Enable / connect tone tone generator on / to channel 1
|
||||
SET_PERI_REG_MASK(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_CW_EN1_M);
|
||||
// Invert MSB, otherwise part of the waveform will be inverted
|
||||
SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL2_REG, SENS_DAC_INV1, 2, SENS_DAC_INV1_S);
|
||||
// Set the frequency of waveform on CW output
|
||||
#ifdef CONFIG_CUSTOM_RTC_CLK_8M_DIV
|
||||
REG_SET_FIELD(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_CK8M_DIV_SEL, RTC_CLK_8M_DIV);
|
||||
ESP_LOGI(TAG, "Custom divider of RTC 8 MHz clock has been set.");
|
||||
#endif
|
||||
SET_PERI_REG_BITS(SENS_SAR_DAC_CTRL1_REG, SENS_SW_FSTEP, CW_FREQUENCY_STEP, SENS_SW_FSTEP_S);
|
||||
|
||||
dac_output_enable(DAC_CHANNEL_1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sample data an ADC1 channel 6 / GPIO34
|
||||
* over specific 'sampling_period' in milliseconds.
|
||||
* Print out sampling result using standard ESP_LOGI() function.
|
||||
* Return the number of samples collected.
|
||||
*/
|
||||
static int adc1_sample_and_show(int sampling_period)
|
||||
{
|
||||
int i = 0;
|
||||
uint32_t sampling_start = esp_log_timestamp();
|
||||
do {
|
||||
ESP_LOGI(TAG, "Sample:%d, Value:%d", ++i, adc1_get_raw(ADC1_TEST_CHANNEL));
|
||||
} while (esp_log_timestamp() - sampling_start < sampling_period);
|
||||
return i;
|
||||
}
|
||||
|
||||
/*
|
||||
* Main program loop that is sampling data on ADC
|
||||
* and logging results with application tracing to the host
|
||||
* as well as for comparison printing out sampling result to UART
|
||||
*/
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Enabling ADC1 on channel 6 / GPIO34.");
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
adc1_config_width(ADC_WIDTH_BIT_12);
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
adc1_config_width(ADC_WIDTH_BIT_13);
|
||||
#endif
|
||||
adc1_config_channel_atten(ADC1_TEST_CHANNEL, ADC_ATTEN_DB_11);
|
||||
|
||||
ESP_LOGI(TAG, "Enabling CW generator on DAC channel 1 / GPIO25.");
|
||||
enable_cosine_generator();
|
||||
|
||||
while (1) {
|
||||
/*
|
||||
* Logging with the Application Trace
|
||||
*/
|
||||
ESP_LOGI(TAG, "Sampling ADC and sending data to the host...");
|
||||
// Route LOGx() to the host
|
||||
esp_log_set_vprintf(esp_apptrace_vprintf);
|
||||
int samples_collected = adc1_sample_and_show(TEST_SAMPLING_PERIOD);
|
||||
// Route LOGx() back to UART
|
||||
esp_log_set_vprintf(vprintf);
|
||||
// Flush collected data to the host
|
||||
esp_apptrace_flush(ESP_APPTRACE_DEST_TRAX, 100000);
|
||||
ESP_LOGI(TAG, "Collected %d samples in %d ms.\n", samples_collected, TEST_SAMPLING_PERIOD);
|
||||
|
||||
/*
|
||||
* Logging to UART
|
||||
*/
|
||||
ESP_LOGI(TAG, "Sampling ADC and sending data to the UART...");
|
||||
samples_collected = adc1_sample_and_show(TEST_SAMPLING_PERIOD);
|
||||
ESP_LOGI(TAG, "Collected %d samples in %d ms.\n", samples_collected, TEST_SAMPLING_PERIOD);
|
||||
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
#
|
||||
# Main Makefile. This is basically the same as a component makefile.
|
||||
#
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_CUSTOM_RTC_CLK_8M_DIV=y
|
||||
@@ -0,0 +1,5 @@
|
||||
# Enable application tracing by default
|
||||
CONFIG_APPTRACE_DEST_TRAX=y
|
||||
CONFIG_APPTRACE_ENABLE=y
|
||||
# Disable WiFi stack by default
|
||||
CONFIG_WIFI_ENABLED=n
|
||||
@@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(base_mac_address)
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := base_mac_address
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
# Base MAC Address
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
Each network interface on the ESP32 (e.g., WiFi Station/AP, BT, Ethernet) must be assigned a unique Medium Access Control (MAC) address. Each interface's MAC address is [derived from a base MAC address](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/system.html#mac-address) that must be set before any network interface's initialization is called.
|
||||
|
||||
This example demonstrates the following:
|
||||
|
||||
1. How to retrieve a base MAC address from non-volatile memory
|
||||
2. How to set the base MAC address
|
||||
3. How to obtain the derived MAC addresses of each network interface
|
||||
|
||||
This example utilizes the [MAC Address API](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/system.html#mac-address).
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
This example should be able to run on any commonly available ESP32 development board. However, if an external source of non-volatile memory is used to store the base MAC address (e.g. an I2C Serial EEPROM), the user should wire the connections to their chosen external storage as needed.
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
* To select the storage source of the base MAC address, go to `Example Configuration > Storage location of the base MAC address` where the following options are available:
|
||||
* `eFuse BLK0` will cause this example to read the base MAC address from eFuse Block 0 and is selected by default. The `eFuse BLK0` MAC address is factory programmed into each ESP32.
|
||||
* `eFuse BLK3` will cause this example to read the base MAC address from words 1 & 2 of eFuse Block 3. `eFuse BLK3` allows users to use a custom eFuse, but must be burned into the ESP32 using the [espefuse tool](https://github.com/espressif/esptool/wiki/espefuse). Attempting to read `eFuse BLK3` without burning the eFuse will result in an error.
|
||||
* `Other external storage` will call a `external_storage_mac_get()` which will merely retrieve an arbitrary base MAC address that is set in software. Users should re-implement `external_storage_mac_get()` to access their chosen external storage (e.g. an I2C Serial EEPROM)
|
||||
|
||||
* If `eFuse BLK3` is chosen, the `Behavior when retrieving BLK3 eFuse fails` option may also be selected. When retrieving the base MAC address from `eFuse BLK3` without burning eFuse Block 3, the example can either abort or default to retrieving from eFuse Block 0.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
Depending on the `Storage of the base MAC address` configuration option, the console should output one of the following logs to indicate the source of the base MAC address.
|
||||
|
||||
```
|
||||
I (288) BASE_MAC: Base MAC Address read from EFUSE BLK0
|
||||
...
|
||||
I (288) BASE_MAC: Base MAC Address read from EFUSE BLK3
|
||||
...
|
||||
I (288) BASE_MAC: Base MAC Address read from external storage
|
||||
```
|
||||
|
||||
The console will also output the retrieved 6 byte MAC address when it sets it as the system wide base MAC address.
|
||||
```
|
||||
I (298) BASE_MAC: Using "0xd8, 0xa0, 0x1d, 0x0, 0xb, 0x88" as base MAC address
|
||||
```
|
||||
|
||||
The console will then output the derived MAC address of each network interface as such:
|
||||
```
|
||||
I (297) WIFI_STA MAC: 0x0, 0x11, 0x22, 0x33, 0x44, 0x55
|
||||
I (307) SoftAP MAC: 0x0, 0x11, 0x22, 0x33, 0x44, 0x56
|
||||
I (317) BT MAC: 0x0, 0x11, 0x22, 0x33, 0x44, 0x57
|
||||
I (317) Ethernet MAC: 0x0, 0x11, 0x22, 0x33, 0x44, 0x58
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
When attempting to retrieve the base MAC address from `eFuse BLK3` without first burning eFuse Block 3 will result in one of the following situations.
|
||||
|
||||
If the example has been configured to abort when retrieving from `eFuse BLK3` fails.
|
||||
|
||||
```
|
||||
E (288) system_api: Base MAC address from BLK3 of EFUSE version error, version = 0
|
||||
E (298) BASE_MAC: Failed to get base MAC address from EFUSE BLK3. (ESP_ERR_INVALID_VERSION)
|
||||
E (308) BASE_MAC: Aborting
|
||||
abort() was called at PC 0x400d237b on core 0
|
||||
```
|
||||
|
||||
If not configured to abort, the example will default to `eFuse BLK0` and output the following instead.
|
||||
|
||||
```
|
||||
E (288) system_api: Base MAC address from BLK3 of EFUSE version error, version = 0
|
||||
E (298) BASE_MAC: Failed to get base MAC address from EFUSE BLK3. (ESP_ERR_INVALID_VERSION)
|
||||
I (308) BASE_MAC: Defaulting to base MAC address in BLK0 of EFUSE
|
||||
I (308) BASE_MAC: Base MAC Address read from EFUSE BLK0
|
||||
```
|
||||
@@ -0,0 +1,33 @@
|
||||
from __future__ import unicode_literals
|
||||
from tiny_test_fw import Utility
|
||||
import re
|
||||
import ttfw_idf
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag='Example_GENERIC')
|
||||
def test_examples_base_mac_address(env, extra_data):
|
||||
|
||||
dut = env.get_dut('base_mac_address', 'examples/system/base_mac_address')
|
||||
dut.start_app()
|
||||
|
||||
dut.expect('BASE_MAC: Base MAC Address read from EFUSE BLK0', timeout=30)
|
||||
hex_r = r', '.join((r'0x([0-9a-f]{1,2})',) * 6)
|
||||
mac_m = dut.expect(re.compile(r'BASE_MAC: Using "' + hex_r + r'" as base MAC address'), timeout=5)
|
||||
Utility.console_log('BASE_MAC detected: {}'.format(':'.join(mac_m)))
|
||||
|
||||
def get_expected_mac_string(increment):
|
||||
'''
|
||||
Return the string representation of the MAC address mac_m with the last octet incremented.
|
||||
mac_m is an array of strings in hexa-decimal format without the '0x' prefix.
|
||||
'''
|
||||
return ', '.join(['0x{}'.format(m) for m in mac_m[:-1]] + [hex(int(mac_m[-1], 16) + increment)])
|
||||
|
||||
dut.expect_all('WIFI_STA MAC: ' + get_expected_mac_string(0),
|
||||
'SoftAP MAC: ' + get_expected_mac_string(1),
|
||||
'BT MAC: ' + get_expected_mac_string(2),
|
||||
'Ethernet MAC: ' + get_expected_mac_string(3),
|
||||
timeout=10)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_examples_base_mac_address()
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "base_mac_address_example_main.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,46 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
choice BASE_MAC_ADDRESS_STORAGE
|
||||
prompt "Storage location of the base MAC address"
|
||||
default BASE_MAC_STORED_EFUSE_BLK0
|
||||
help
|
||||
Select the storage location of the base MAC addresses.
|
||||
1. eFuse BLK0: The "Default (Espressif factory)" selection. The
|
||||
default base MAC address is written to words 1 and 2 of eFuse block
|
||||
0 when the chip was manufactured. Call esp_efuse_mac_get_default()
|
||||
read retrieve the "eFuse BLK0" MAC address.
|
||||
2. eFuse BLK3: A custom base MAC address is burned by the user into
|
||||
eFuse word 0 of block 3. Call esp_efuse_mac_get_custom() to read
|
||||
the "eFuse BLK3" MAC address.
|
||||
3. Other External Storage: Selecting this option will cause the
|
||||
example to call external_storage_mac_get() which is defined in this
|
||||
example to simply return a MAC address preset in software. Users
|
||||
should modify this function to access their desired storage mediums
|
||||
(e.g. flash, EEPROM etc).
|
||||
|
||||
config BASE_MAC_STORED_EFUSE_BLK0
|
||||
bool "Default (Espressif factory) eFuse BLK0"
|
||||
config BASE_MAC_STORED_EFUSE_BLK3
|
||||
bool "Custom eFuse BLK3"
|
||||
config BASE_MAC_STORED_OTHER_EXTERNAL_STORAGE
|
||||
bool "Other external storage"
|
||||
endchoice
|
||||
|
||||
choice BASE_MAC_STORED_EFUSE_BLK3_ERROR_BEHAV
|
||||
prompt "Behavior when retrieving eFuse BLK3 fails"
|
||||
depends on BASE_MAC_STORED_EFUSE_BLK3
|
||||
default BASE_MAC_STORED_EFUSE_BLK3_ERROR_DEFAULT
|
||||
help
|
||||
Select the behavior when reading base MAC address "eFuse BLK3" fails
|
||||
(i.e. the retrieved result is all 0).
|
||||
- If "Abort" is selected, the ESP32 will abort.
|
||||
- If "Use the default base MAC address from BLK0 of eFuse" is
|
||||
selected, the default "eFuse BLK0" will be used instead.
|
||||
|
||||
config BASE_MAC_STORED_EFUSE_BLK3_ERROR_ABORT
|
||||
bool "Abort"
|
||||
config BASE_MAC_STORED_EFUSE_BLK3_ERROR_DEFAULT
|
||||
bool "Use the default base MAC address eFuse BLK0"
|
||||
endchoice
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,108 @@
|
||||
/* Base mac address example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_system.h"
|
||||
|
||||
#define TAG "BASE_MAC"
|
||||
|
||||
#ifdef CONFIG_BASE_MAC_STORED_OTHER_EXTERNAL_STORAGE
|
||||
|
||||
static esp_err_t external_storage_mac_get(uint8_t *mac)
|
||||
{
|
||||
/* This function simulates getting a base MAC address from external storage
|
||||
* by simply setting the base MAC to an arbitrary address. Users should
|
||||
* re-implement this function to access external storage (e.g. flash, EEPROM) */
|
||||
uint8_t external_storage_mac_addr[6] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55 };
|
||||
if (mac == NULL) {
|
||||
ESP_LOGE(TAG, "The mac parameter is NULL");
|
||||
abort();
|
||||
}
|
||||
|
||||
memcpy(mac, external_storage_mac_addr, 6);
|
||||
return ESP_OK;
|
||||
}
|
||||
#endif//CONFIG_BASE_MAC_STORED_OTHER_EXTERNAL_STORAGE
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
//Get the base MAC address from different sources
|
||||
uint8_t base_mac_addr[6] = {0};
|
||||
esp_err_t ret = ESP_OK;
|
||||
#ifdef CONFIG_BASE_MAC_STORED_EFUSE_BLK3
|
||||
//Get base MAC address from EFUSE BLK3
|
||||
ret = esp_efuse_mac_get_custom(base_mac_addr);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to get base MAC address from EFUSE BLK3. (%s)", esp_err_to_name(ret));
|
||||
#ifdef CONFIG_BASE_MAC_STORED_EFUSE_BLK3_ERROR_ABORT
|
||||
ESP_LOGE(TAG, "Aborting");
|
||||
abort();
|
||||
#else
|
||||
ESP_LOGI(TAG, "Defaulting to base MAC address in BLK0 of EFUSE");
|
||||
esp_efuse_mac_get_default(base_mac_addr);
|
||||
ESP_LOGI(TAG, "Base MAC Address read from EFUSE BLK0");
|
||||
#endif//CONFIG_BASE_MAC_STORED_EFUSE_BLK3_ERROR_ABORT
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Base MAC Address read from EFUSE BLK3");
|
||||
}
|
||||
#elif defined(CONFIG_BASE_MAC_STORED_OTHER_EXTERNAL_STORAGE)
|
||||
//Get base MAC address from other external storage, or set by software
|
||||
ret = external_storage_mac_get(base_mac_addr);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to get base MAC address from external storage. (%s)", esp_err_to_name(ret));
|
||||
ESP_LOGE(TAG, "Aborting");
|
||||
abort();
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Base MAC Address read from external storage");
|
||||
}
|
||||
#else
|
||||
//Get base MAC address from EFUSE BLK0(default option)
|
||||
ret = esp_efuse_mac_get_default(base_mac_addr);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to get base MAC address from EFUSE BLK0. (%s)", esp_err_to_name(ret));
|
||||
ESP_LOGE(TAG, "Aborting");
|
||||
abort();
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Base MAC Address read from EFUSE BLK0");
|
||||
}
|
||||
#endif
|
||||
|
||||
//Set the base MAC address using the retrieved MAC address
|
||||
ESP_LOGI(TAG, "Using \"0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x\" as base MAC address",
|
||||
base_mac_addr[0], base_mac_addr[1], base_mac_addr[2], base_mac_addr[3], base_mac_addr[4], base_mac_addr[5]);
|
||||
esp_base_mac_addr_set(base_mac_addr);
|
||||
|
||||
//Get the derived MAC address for each network interface
|
||||
uint8_t derived_mac_addr[6] = {0};
|
||||
//Get MAC address for WiFi Station interface
|
||||
ESP_ERROR_CHECK(esp_read_mac(derived_mac_addr, ESP_MAC_WIFI_STA));
|
||||
ESP_LOGI("WIFI_STA MAC", "0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x",
|
||||
derived_mac_addr[0], derived_mac_addr[1], derived_mac_addr[2],
|
||||
derived_mac_addr[3], derived_mac_addr[4], derived_mac_addr[5]);
|
||||
|
||||
//Get MAC address for SoftAp interface
|
||||
ESP_ERROR_CHECK(esp_read_mac(derived_mac_addr, ESP_MAC_WIFI_SOFTAP));
|
||||
ESP_LOGI("SoftAP MAC", "0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x",
|
||||
derived_mac_addr[0], derived_mac_addr[1], derived_mac_addr[2],
|
||||
derived_mac_addr[3], derived_mac_addr[4], derived_mac_addr[5]);
|
||||
|
||||
//Get MAC address for Bluetooth
|
||||
ESP_ERROR_CHECK(esp_read_mac(derived_mac_addr, ESP_MAC_BT));
|
||||
ESP_LOGI("BT MAC", "0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x",
|
||||
derived_mac_addr[0], derived_mac_addr[1], derived_mac_addr[2],
|
||||
derived_mac_addr[3], derived_mac_addr[4], derived_mac_addr[5]);
|
||||
|
||||
//Get MAC address for Ethernet
|
||||
ESP_ERROR_CHECK(esp_read_mac(derived_mac_addr, ESP_MAC_ETH));
|
||||
ESP_LOGI("Ethernet MAC", "0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x",
|
||||
derived_mac_addr[0], derived_mac_addr[1], derived_mac_addr[2],
|
||||
derived_mac_addr[3], derived_mac_addr[4], derived_mac_addr[5]);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
#
|
||||
# Main Makefile. This is basically the same as a component makefile.
|
||||
#
|
||||
@@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(console)
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := console
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
# Console Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
This example illustrates the usage of the [Console Component](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/console.html#console) to create an interactive shell on the ESP32. The interactive shell running on the ESP32 can then be controlled/interacted with over a serial port (UART).
|
||||
|
||||
The interactive shell implemented in this example contains a wide variety of commands, and can act as a basis for applications that require a command-line interface (CLI).
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
This example should be able to run on any commonly available ESP32 development board.
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
* Enable/disable `Example Configuration > Store command history in flash` as necessary
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
Enter the `help` command get a full list of all available commands. The following is a sample session of the Console Example where a variety of commands provided by the Console Example are used. Note that GPIO15 is connected to GND to remove the boot log output.
|
||||
|
||||
```
|
||||
This is an example of ESP-IDF console component.
|
||||
Type 'help' to get the list of commands.
|
||||
Use UP/DOWN arrows to navigate through command history.
|
||||
Press TAB when typing command name to auto-complete.
|
||||
[esp32]> help
|
||||
help
|
||||
Print the list of registered commands
|
||||
|
||||
free
|
||||
Get the total size of heap memory available
|
||||
|
||||
restart
|
||||
Restart the program
|
||||
|
||||
deep_sleep [-t <t>] [--io=<n>] [--io_level=<0|1>]
|
||||
Enter deep sleep mode. Two wakeup modes are supported: timer and GPIO. If no
|
||||
wakeup option is specified, will sleep indefinitely.
|
||||
-t, --time=<t> Wake up time, ms
|
||||
--io=<n> If specified, wakeup using GPIO with given number
|
||||
--io_level=<0|1> GPIO level to trigger wakeup
|
||||
|
||||
join [--timeout=<t>] <ssid> [<pass>]
|
||||
Join WiFi AP as a station
|
||||
--timeout=<t> Connection timeout, ms
|
||||
<ssid> SSID of AP
|
||||
<pass> PSK of AP
|
||||
|
||||
[esp32]> free
|
||||
257200
|
||||
[esp32]> deep_sleep -t 1000
|
||||
I (146929) deep_sleep: Enabling timer wakeup, timeout=1000000us
|
||||
I (619) heap_init: Initializing. RAM available for dynamic allocation:
|
||||
I (620) heap_init: At 3FFAE2A0 len 00001D60 (7 KiB): DRAM
|
||||
I (626) heap_init: At 3FFB7EA0 len 00028160 (160 KiB): DRAM
|
||||
I (645) heap_init: At 3FFE0440 len 00003BC0 (14 KiB): D/IRAM
|
||||
I (664) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
|
||||
I (684) heap_init: At 40093EA8 len 0000C158 (48 KiB): IRAM
|
||||
|
||||
This is an example of ESP-IDF console component.
|
||||
Type 'help' to get the list of commands.
|
||||
Use UP/DOWN arrows to navigate through command history.
|
||||
Press TAB when typing command name to auto-complete.
|
||||
[esp32]> join --timeout 10000 test_ap test_password
|
||||
I (182639) connect: Connecting to 'test_ap'
|
||||
I (184619) connect: Connected
|
||||
[esp32]> free
|
||||
212328
|
||||
[esp32]> restart
|
||||
I (205639) restart: Restarting
|
||||
I (616) heap_init: Initializing. RAM available for dynamic allocation:
|
||||
I (617) heap_init: At 3FFAE2A0 len 00001D60 (7 KiB): DRAM
|
||||
I (623) heap_init: At 3FFB7EA0 len 00028160 (160 KiB): DRAM
|
||||
I (642) heap_init: At 3FFE0440 len 00003BC0 (14 KiB): D/IRAM
|
||||
I (661) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
|
||||
I (681) heap_init: At 40093EA8 len 0000C158 (48 KiB): IRAM
|
||||
|
||||
This is an example of ESP-IDF console component.
|
||||
Type 'help' to get the list of commands.
|
||||
Use UP/DOWN arrows to navigate through command history.
|
||||
Press TAB when typing command name to auto-complete.
|
||||
[esp32]>
|
||||
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Line Endings
|
||||
|
||||
The line endings in the Console Example are configured to match particular serial monitors. Therefore, if the following log output appears, consider using a different serial monitor (e.g. Putty for Windows) or modify the example's [UART configuration](#Configuring-UART-and-VFS).
|
||||
|
||||
```
|
||||
This is an example of ESP-IDF console component.
|
||||
Type 'help' to get the list of commands.
|
||||
Use UP/DOWN arrows to navigate through command history.
|
||||
Press TAB when typing command name to auto-complete.
|
||||
Your terminal application does not support escape sequences.
|
||||
Line editing and history features are disabled.
|
||||
On Windows, try using Putty instead.
|
||||
esp32>
|
||||
```
|
||||
|
||||
## Example Breakdown
|
||||
|
||||
### Configuring UART
|
||||
|
||||
The ``initialize_console()`` function in the example configures some aspects of UART relevant to the operation of the console.
|
||||
|
||||
- **Line Endings**: The default line endings are configured to match those expected/generated by common serial monitor programs, such as `screen`, `minicom`, and the `idf_monitor.py` included in the SDK. The default behavior for these commands are:
|
||||
- When 'enter' key is pressed on the keyboard, `CR` (0x13) code is sent to the serial device.
|
||||
- To move the cursor to the beginning of the next line, serial device needs to send `CR LF` (0x13 0x10) sequence.
|
||||
|
||||
### Line editing
|
||||
|
||||
The main source file of the example illustrates how to use `linenoise` library, including line completion, hints, and history.
|
||||
|
||||
### Commands
|
||||
|
||||
Several commands are registered using `esp_console_cmd_register()` function. See the `register_wifi()` and `register_system()` functions in `cmd_wifi.c` and `cmd_system.c` files.
|
||||
|
||||
### Command handling
|
||||
|
||||
Main loop inside `app_main()` function illustrates how to use `linenoise` and `esp_console_run()` to implement read/eval loop.
|
||||
|
||||
### Argument parsing
|
||||
|
||||
Several commands implemented in `cmd_wifi.c` and `cmd_system.c` use the Argtable3 library to parse and check the arguments.
|
||||
|
||||
### Command history
|
||||
|
||||
Each time a new command line is obtained from `linenoise`, it is written into history and the history is saved into a file in flash memory. On reset, history is initialized from that file.
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "cmd_nvs.c"
|
||||
INCLUDE_DIRS .
|
||||
REQUIRES console nvs_flash)
|
||||
@@ -0,0 +1,598 @@
|
||||
/* Console example — NVS commands
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <inttypes.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_console.h"
|
||||
#include "argtable3/argtable3.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_err.h"
|
||||
#include "cmd_nvs.h"
|
||||
#include "nvs.h"
|
||||
|
||||
typedef struct {
|
||||
nvs_type_t type;
|
||||
const char *str;
|
||||
} type_str_pair_t;
|
||||
|
||||
static const type_str_pair_t type_str_pair[] = {
|
||||
{ NVS_TYPE_I8, "i8" },
|
||||
{ NVS_TYPE_U8, "u8" },
|
||||
{ NVS_TYPE_U16, "u16" },
|
||||
{ NVS_TYPE_I16, "i16" },
|
||||
{ NVS_TYPE_U32, "u32" },
|
||||
{ NVS_TYPE_I32, "i32" },
|
||||
{ NVS_TYPE_U64, "u64" },
|
||||
{ NVS_TYPE_I64, "i64" },
|
||||
{ NVS_TYPE_STR, "str" },
|
||||
{ NVS_TYPE_BLOB, "blob" },
|
||||
{ NVS_TYPE_ANY, "any" },
|
||||
};
|
||||
|
||||
static const size_t TYPE_STR_PAIR_SIZE = sizeof(type_str_pair) / sizeof(type_str_pair[0]);
|
||||
static const char *ARG_TYPE_STR = "type can be: i8, u8, i16, u16 i32, u32 i64, u64, str, blob";
|
||||
static char current_namespace[16] = "storage";
|
||||
static const char *TAG = "cmd_nvs";
|
||||
|
||||
static struct {
|
||||
struct arg_str *key;
|
||||
struct arg_str *type;
|
||||
struct arg_str *value;
|
||||
struct arg_end *end;
|
||||
} set_args;
|
||||
|
||||
static struct {
|
||||
struct arg_str *key;
|
||||
struct arg_str *type;
|
||||
struct arg_end *end;
|
||||
} get_args;
|
||||
|
||||
static struct {
|
||||
struct arg_str *key;
|
||||
struct arg_end *end;
|
||||
} erase_args;
|
||||
|
||||
static struct {
|
||||
struct arg_str *namespace;
|
||||
struct arg_end *end;
|
||||
} erase_all_args;
|
||||
|
||||
static struct {
|
||||
struct arg_str *namespace;
|
||||
struct arg_end *end;
|
||||
} namespace_args;
|
||||
|
||||
static struct {
|
||||
struct arg_str *partition;
|
||||
struct arg_str *namespace;
|
||||
struct arg_str *type;
|
||||
struct arg_end *end;
|
||||
} list_args;
|
||||
|
||||
|
||||
static nvs_type_t str_to_type(const char *type)
|
||||
{
|
||||
for (int i = 0; i < TYPE_STR_PAIR_SIZE; i++) {
|
||||
const type_str_pair_t *p = &type_str_pair[i];
|
||||
if (strcmp(type, p->str) == 0) {
|
||||
return p->type;
|
||||
}
|
||||
}
|
||||
|
||||
return NVS_TYPE_ANY;
|
||||
}
|
||||
|
||||
static const char *type_to_str(nvs_type_t type)
|
||||
{
|
||||
for (int i = 0; i < TYPE_STR_PAIR_SIZE; i++) {
|
||||
const type_str_pair_t *p = &type_str_pair[i];
|
||||
if (p->type == type) {
|
||||
return p->str;
|
||||
}
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
static esp_err_t store_blob(nvs_handle_t nvs, const char *key, const char *str_values)
|
||||
{
|
||||
uint8_t value;
|
||||
size_t str_len = strlen(str_values);
|
||||
size_t blob_len = str_len / 2;
|
||||
|
||||
if (str_len % 2) {
|
||||
ESP_LOGE(TAG, "Blob data must contain even number of characters");
|
||||
return ESP_ERR_NVS_TYPE_MISMATCH;
|
||||
}
|
||||
|
||||
char *blob = (char *)malloc(blob_len);
|
||||
if (blob == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
for (int i = 0, j = 0; i < str_len; i++) {
|
||||
char ch = str_values[i];
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
value = ch - '0';
|
||||
} else if (ch >= 'A' && ch <= 'F') {
|
||||
value = ch - 'A' + 10;
|
||||
} else if (ch >= 'a' && ch <= 'f') {
|
||||
value = ch - 'a' + 10;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Blob data contain invalid character");
|
||||
free(blob);
|
||||
return ESP_ERR_NVS_TYPE_MISMATCH;
|
||||
}
|
||||
|
||||
if (i & 1) {
|
||||
blob[j++] += value;
|
||||
} else {
|
||||
blob[j] = value << 4;
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t err = nvs_set_blob(nvs, key, blob, blob_len);
|
||||
free(blob);
|
||||
|
||||
if (err == ESP_OK) {
|
||||
err = nvs_commit(nvs);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void print_blob(const char *blob, size_t len)
|
||||
{
|
||||
for (int i = 0; i < len; i++) {
|
||||
printf("%02x", blob[i]);
|
||||
}
|
||||
printf("\n");
|
||||
}
|
||||
|
||||
|
||||
static esp_err_t set_value_in_nvs(const char *key, const char *str_type, const char *str_value)
|
||||
{
|
||||
esp_err_t err;
|
||||
nvs_handle_t nvs;
|
||||
bool range_error = false;
|
||||
|
||||
nvs_type_t type = str_to_type(str_type);
|
||||
|
||||
if (type == NVS_TYPE_ANY) {
|
||||
ESP_LOGE(TAG, "Type '%s' is undefined", str_type);
|
||||
return ESP_ERR_NVS_TYPE_MISMATCH;
|
||||
}
|
||||
|
||||
err = nvs_open(current_namespace, NVS_READWRITE, &nvs);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (type == NVS_TYPE_I8) {
|
||||
int32_t value = strtol(str_value, NULL, 0);
|
||||
if (value < INT8_MIN || value > INT8_MAX || errno == ERANGE) {
|
||||
range_error = true;
|
||||
} else {
|
||||
err = nvs_set_i8(nvs, key, (int8_t)value);
|
||||
}
|
||||
} else if (type == NVS_TYPE_U8) {
|
||||
uint32_t value = strtoul(str_value, NULL, 0);
|
||||
if (value > UINT8_MAX || errno == ERANGE) {
|
||||
range_error = true;
|
||||
} else {
|
||||
err = nvs_set_u8(nvs, key, (uint8_t)value);
|
||||
}
|
||||
} else if (type == NVS_TYPE_I16) {
|
||||
int32_t value = strtol(str_value, NULL, 0);
|
||||
if (value < INT16_MIN || value > INT16_MAX || errno == ERANGE) {
|
||||
range_error = true;
|
||||
} else {
|
||||
err = nvs_set_i16(nvs, key, (int16_t)value);
|
||||
}
|
||||
} else if (type == NVS_TYPE_U16) {
|
||||
uint32_t value = strtoul(str_value, NULL, 0);
|
||||
if (value > UINT16_MAX || errno == ERANGE) {
|
||||
range_error = true;
|
||||
} else {
|
||||
err = nvs_set_u16(nvs, key, (uint16_t)value);
|
||||
}
|
||||
} else if (type == NVS_TYPE_I32) {
|
||||
int32_t value = strtol(str_value, NULL, 0);
|
||||
if (errno != ERANGE) {
|
||||
err = nvs_set_i32(nvs, key, value);
|
||||
}
|
||||
} else if (type == NVS_TYPE_U32) {
|
||||
uint32_t value = strtoul(str_value, NULL, 0);
|
||||
if (errno != ERANGE) {
|
||||
err = nvs_set_u32(nvs, key, value);
|
||||
}
|
||||
} else if (type == NVS_TYPE_I64) {
|
||||
int64_t value = strtoll(str_value, NULL, 0);
|
||||
if (errno != ERANGE) {
|
||||
err = nvs_set_i64(nvs, key, value);
|
||||
}
|
||||
} else if (type == NVS_TYPE_U64) {
|
||||
uint64_t value = strtoull(str_value, NULL, 0);
|
||||
if (errno != ERANGE) {
|
||||
err = nvs_set_u64(nvs, key, value);
|
||||
}
|
||||
} else if (type == NVS_TYPE_STR) {
|
||||
err = nvs_set_str(nvs, key, str_value);
|
||||
} else if (type == NVS_TYPE_BLOB) {
|
||||
err = store_blob(nvs, key, str_value);
|
||||
}
|
||||
|
||||
if (range_error || errno == ERANGE) {
|
||||
nvs_close(nvs);
|
||||
return ESP_ERR_NVS_VALUE_TOO_LONG;
|
||||
}
|
||||
|
||||
if (err == ESP_OK) {
|
||||
err = nvs_commit(nvs);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Value stored under key '%s'", key);
|
||||
}
|
||||
}
|
||||
|
||||
nvs_close(nvs);
|
||||
return err;
|
||||
}
|
||||
|
||||
static esp_err_t get_value_from_nvs(const char *key, const char *str_type)
|
||||
{
|
||||
nvs_handle_t nvs;
|
||||
esp_err_t err;
|
||||
|
||||
nvs_type_t type = str_to_type(str_type);
|
||||
|
||||
if (type == NVS_TYPE_ANY) {
|
||||
ESP_LOGE(TAG, "Type '%s' is undefined", str_type);
|
||||
return ESP_ERR_NVS_TYPE_MISMATCH;
|
||||
}
|
||||
|
||||
err = nvs_open(current_namespace, NVS_READONLY, &nvs);
|
||||
if (err != ESP_OK) {
|
||||
return err;
|
||||
}
|
||||
|
||||
if (type == NVS_TYPE_I8) {
|
||||
int8_t value;
|
||||
err = nvs_get_i8(nvs, key, &value);
|
||||
if (err == ESP_OK) {
|
||||
printf("%d\n", value);
|
||||
}
|
||||
} else if (type == NVS_TYPE_U8) {
|
||||
uint8_t value;
|
||||
err = nvs_get_u8(nvs, key, &value);
|
||||
if (err == ESP_OK) {
|
||||
printf("%u\n", value);
|
||||
}
|
||||
} else if (type == NVS_TYPE_I16) {
|
||||
int16_t value;
|
||||
err = nvs_get_i16(nvs, key, &value);
|
||||
if (err == ESP_OK) {
|
||||
printf("%u\n", value);
|
||||
}
|
||||
} else if (type == NVS_TYPE_U16) {
|
||||
uint16_t value;
|
||||
if ((err = nvs_get_u16(nvs, key, &value)) == ESP_OK) {
|
||||
printf("%u\n", value);
|
||||
}
|
||||
} else if (type == NVS_TYPE_I32) {
|
||||
int32_t value;
|
||||
if ((err = nvs_get_i32(nvs, key, &value)) == ESP_OK) {
|
||||
printf("%d\n", value);
|
||||
}
|
||||
} else if (type == NVS_TYPE_U32) {
|
||||
uint32_t value;
|
||||
if ((err = nvs_get_u32(nvs, key, &value)) == ESP_OK) {
|
||||
printf("%u\n", value);
|
||||
}
|
||||
} else if (type == NVS_TYPE_I64) {
|
||||
int64_t value;
|
||||
if ((err = nvs_get_i64(nvs, key, &value)) == ESP_OK) {
|
||||
printf("%lld\n", value);
|
||||
}
|
||||
} else if (type == NVS_TYPE_U64) {
|
||||
uint64_t value;
|
||||
if ( (err = nvs_get_u64(nvs, key, &value)) == ESP_OK) {
|
||||
printf("%llu\n", value);
|
||||
}
|
||||
} else if (type == NVS_TYPE_STR) {
|
||||
size_t len;
|
||||
if ( (err = nvs_get_str(nvs, key, NULL, &len)) == ESP_OK) {
|
||||
char *str = (char *)malloc(len);
|
||||
if ( (err = nvs_get_str(nvs, key, str, &len)) == ESP_OK) {
|
||||
printf("%s\n", str);
|
||||
}
|
||||
free(str);
|
||||
}
|
||||
} else if (type == NVS_TYPE_BLOB) {
|
||||
size_t len;
|
||||
if ( (err = nvs_get_blob(nvs, key, NULL, &len)) == ESP_OK) {
|
||||
char *blob = (char *)malloc(len);
|
||||
if ( (err = nvs_get_blob(nvs, key, blob, &len)) == ESP_OK) {
|
||||
print_blob(blob, len);
|
||||
}
|
||||
free(blob);
|
||||
}
|
||||
}
|
||||
|
||||
nvs_close(nvs);
|
||||
return err;
|
||||
}
|
||||
|
||||
static esp_err_t erase(const char *key)
|
||||
{
|
||||
nvs_handle_t nvs;
|
||||
|
||||
esp_err_t err = nvs_open(current_namespace, NVS_READWRITE, &nvs);
|
||||
if (err == ESP_OK) {
|
||||
err = nvs_erase_key(nvs, key);
|
||||
if (err == ESP_OK) {
|
||||
err = nvs_commit(nvs);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Value with key '%s' erased", key);
|
||||
}
|
||||
}
|
||||
nvs_close(nvs);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static esp_err_t erase_all(const char *name)
|
||||
{
|
||||
nvs_handle_t nvs;
|
||||
|
||||
esp_err_t err = nvs_open(name, NVS_READWRITE, &nvs);
|
||||
if (err == ESP_OK) {
|
||||
err = nvs_erase_all(nvs);
|
||||
if (err == ESP_OK) {
|
||||
err = nvs_commit(nvs);
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Namespace '%s' was %s erased", name, (err == ESP_OK) ? "" : "not");
|
||||
|
||||
nvs_close(nvs);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static int list(const char *part, const char *name, const char *str_type)
|
||||
{
|
||||
nvs_type_t type = str_to_type(str_type);
|
||||
|
||||
nvs_iterator_t it = nvs_entry_find(part, NULL, type);
|
||||
if (it == NULL) {
|
||||
ESP_LOGE(TAG, "No such enty was found");
|
||||
return 1;
|
||||
}
|
||||
|
||||
do {
|
||||
nvs_entry_info_t info;
|
||||
nvs_entry_info(it, &info);
|
||||
it = nvs_entry_next(it);
|
||||
|
||||
printf("namespace '%s', key '%s', type '%s' \n",
|
||||
info.namespace_name, info.key, type_to_str(info.type));
|
||||
} while (it != NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int set_value(int argc, char **argv)
|
||||
{
|
||||
int nerrors = arg_parse(argc, argv, (void **) &set_args);
|
||||
if (nerrors != 0) {
|
||||
arg_print_errors(stderr, set_args.end, argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *key = set_args.key->sval[0];
|
||||
const char *type = set_args.type->sval[0];
|
||||
const char *values = set_args.value->sval[0];
|
||||
|
||||
esp_err_t err = set_value_in_nvs(key, type, values);
|
||||
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "%s", esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int get_value(int argc, char **argv)
|
||||
{
|
||||
int nerrors = arg_parse(argc, argv, (void **) &get_args);
|
||||
if (nerrors != 0) {
|
||||
arg_print_errors(stderr, get_args.end, argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *key = get_args.key->sval[0];
|
||||
const char *type = get_args.type->sval[0];
|
||||
|
||||
esp_err_t err = get_value_from_nvs(key, type);
|
||||
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "%s", esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int erase_value(int argc, char **argv)
|
||||
{
|
||||
int nerrors = arg_parse(argc, argv, (void **) &erase_args);
|
||||
if (nerrors != 0) {
|
||||
arg_print_errors(stderr, erase_args.end, argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *key = erase_args.key->sval[0];
|
||||
|
||||
esp_err_t err = erase(key);
|
||||
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "%s", esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int erase_namespace(int argc, char **argv)
|
||||
{
|
||||
int nerrors = arg_parse(argc, argv, (void **) &erase_all_args);
|
||||
if (nerrors != 0) {
|
||||
arg_print_errors(stderr, erase_all_args.end, argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *name = erase_all_args.namespace->sval[0];
|
||||
|
||||
esp_err_t err = erase_all(name);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "%s", esp_err_to_name(err));
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int set_namespace(int argc, char **argv)
|
||||
{
|
||||
int nerrors = arg_parse(argc, argv, (void **) &namespace_args);
|
||||
if (nerrors != 0) {
|
||||
arg_print_errors(stderr, namespace_args.end, argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *namespace = namespace_args.namespace->sval[0];
|
||||
strlcpy(current_namespace, namespace, sizeof(current_namespace));
|
||||
ESP_LOGI(TAG, "Namespace set to '%s'", current_namespace);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int list_entries(int argc, char **argv)
|
||||
{
|
||||
list_args.partition->sval[0] = "";
|
||||
list_args.namespace->sval[0] = "";
|
||||
list_args.type->sval[0] = "";
|
||||
|
||||
int nerrors = arg_parse(argc, argv, (void **) &list_args);
|
||||
if (nerrors != 0) {
|
||||
arg_print_errors(stderr, list_args.end, argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *part = list_args.partition->sval[0];
|
||||
const char *name = list_args.namespace->sval[0];
|
||||
const char *type = list_args.type->sval[0];
|
||||
|
||||
return list(part, name, type);
|
||||
}
|
||||
|
||||
void register_nvs(void)
|
||||
{
|
||||
set_args.key = arg_str1(NULL, NULL, "<key>", "key of the value to be set");
|
||||
set_args.type = arg_str1(NULL, NULL, "<type>", ARG_TYPE_STR);
|
||||
|
||||
set_args.value = arg_str1("v", "value", "<value>", "value to be stored");
|
||||
set_args.end = arg_end(2);
|
||||
|
||||
get_args.key = arg_str1(NULL, NULL, "<key>", "key of the value to be read");
|
||||
get_args.type = arg_str1(NULL, NULL, "<type>", ARG_TYPE_STR);
|
||||
get_args.end = arg_end(2);
|
||||
|
||||
erase_args.key = arg_str1(NULL, NULL, "<key>", "key of the value to be erased");
|
||||
erase_args.end = arg_end(2);
|
||||
|
||||
erase_all_args.namespace = arg_str1(NULL, NULL, "<namespace>", "namespace to be erased");
|
||||
erase_all_args.end = arg_end(2);
|
||||
|
||||
namespace_args.namespace = arg_str1(NULL, NULL, "<namespace>", "namespace of the partition to be selected");
|
||||
namespace_args.end = arg_end(2);
|
||||
|
||||
list_args.partition = arg_str1(NULL, NULL, "<partition>", "partition name");
|
||||
list_args.namespace = arg_str0("n", "namespace", "<namespace>", "namespace name");
|
||||
list_args.type = arg_str0("t", "type", "<type>", ARG_TYPE_STR);
|
||||
list_args.end = arg_end(2);
|
||||
|
||||
const esp_console_cmd_t set_cmd = {
|
||||
.command = "nvs_set",
|
||||
.help = "Set key-value pair in selected namespace.\n"
|
||||
"Examples:\n"
|
||||
" nvs_set VarName i32 -v 123 \n"
|
||||
" nvs_set VarName srt -v YourString \n"
|
||||
" nvs_set VarName blob -v 0123456789abcdef \n",
|
||||
.hint = NULL,
|
||||
.func = &set_value,
|
||||
.argtable = &set_args
|
||||
};
|
||||
|
||||
const esp_console_cmd_t get_cmd = {
|
||||
.command = "nvs_get",
|
||||
.help = "Get key-value pair from selected namespace. \n"
|
||||
"Example: nvs_get VarName i32",
|
||||
.hint = NULL,
|
||||
.func = &get_value,
|
||||
.argtable = &get_args
|
||||
};
|
||||
|
||||
const esp_console_cmd_t erase_cmd = {
|
||||
.command = "nvs_erase",
|
||||
.help = "Erase key-value pair from current namespace",
|
||||
.hint = NULL,
|
||||
.func = &erase_value,
|
||||
.argtable = &erase_args
|
||||
};
|
||||
|
||||
const esp_console_cmd_t erase_namespace_cmd = {
|
||||
.command = "nvs_erase_namespace",
|
||||
.help = "Erases specified namespace",
|
||||
.hint = NULL,
|
||||
.func = &erase_namespace,
|
||||
.argtable = &erase_all_args
|
||||
};
|
||||
|
||||
const esp_console_cmd_t namespace_cmd = {
|
||||
.command = "nvs_namespace",
|
||||
.help = "Set current namespace",
|
||||
.hint = NULL,
|
||||
.func = &set_namespace,
|
||||
.argtable = &namespace_args
|
||||
};
|
||||
|
||||
const esp_console_cmd_t list_entries_cmd = {
|
||||
.command = "nvs_list",
|
||||
.help = "List stored key-value pairs stored in NVS."
|
||||
"Namespace and type can be specified to print only those key-value pairs.\n"
|
||||
"Following command list variables stored inside 'nvs' partition, under namespace 'storage' with type uint32_t"
|
||||
"Example: nvs_list nvs -n storage -t u32 \n",
|
||||
.hint = NULL,
|
||||
.func = &list_entries,
|
||||
.argtable = &list_args
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&set_cmd));
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&get_cmd));
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&erase_cmd));
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&namespace_cmd));
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&list_entries_cmd));
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&erase_namespace_cmd));
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/* Console example — declarations of command registration functions.
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Register NVS functions
|
||||
void register_nvs(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#
|
||||
# Component Makefile
|
||||
#
|
||||
# This Makefile should, at the very least, just include $(SDK_PATH)/Makefile. By default,
|
||||
# this will take the sources in the src/ directory, compile them and link them into
|
||||
# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable,
|
||||
# please read the SDK documents if you need to do this.
|
||||
#
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := .
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "cmd_system.c"
|
||||
INCLUDE_DIRS .
|
||||
REQUIRES console spi_flash)
|
||||
@@ -0,0 +1,340 @@
|
||||
/* Console example — various system commands
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <ctype.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_console.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_sleep.h"
|
||||
#include "esp_spi_flash.h"
|
||||
#include "driver/rtc_io.h"
|
||||
#include "driver/uart.h"
|
||||
#include "argtable3/argtable3.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "cmd_system.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#ifdef CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS
|
||||
#define WITH_TASKS_INFO 1
|
||||
#endif
|
||||
|
||||
static const char *TAG = "cmd_system";
|
||||
|
||||
static void register_free(void);
|
||||
static void register_heap(void);
|
||||
static void register_version(void);
|
||||
static void register_restart(void);
|
||||
static void register_deep_sleep(void);
|
||||
static void register_light_sleep(void);
|
||||
#if WITH_TASKS_INFO
|
||||
static void register_tasks(void);
|
||||
#endif
|
||||
|
||||
void register_system(void)
|
||||
{
|
||||
register_free();
|
||||
register_heap();
|
||||
register_version();
|
||||
register_restart();
|
||||
register_deep_sleep();
|
||||
register_light_sleep();
|
||||
#if WITH_TASKS_INFO
|
||||
register_tasks();
|
||||
#endif
|
||||
}
|
||||
|
||||
/* 'version' command */
|
||||
static int get_version(int argc, char **argv)
|
||||
{
|
||||
esp_chip_info_t info;
|
||||
esp_chip_info(&info);
|
||||
printf("IDF Version:%s\r\n", esp_get_idf_version());
|
||||
printf("Chip info:\r\n");
|
||||
printf("\tmodel:%s\r\n", info.model == CHIP_ESP32 ? "ESP32" : "Unknow");
|
||||
printf("\tcores:%d\r\n", info.cores);
|
||||
printf("\tfeature:%s%s%s%s%d%s\r\n",
|
||||
info.features & CHIP_FEATURE_WIFI_BGN ? "/802.11bgn" : "",
|
||||
info.features & CHIP_FEATURE_BLE ? "/BLE" : "",
|
||||
info.features & CHIP_FEATURE_BT ? "/BT" : "",
|
||||
info.features & CHIP_FEATURE_EMB_FLASH ? "/Embedded-Flash:" : "/External-Flash:",
|
||||
spi_flash_get_chip_size() / (1024 * 1024), " MB");
|
||||
printf("\trevision number:%d\r\n", info.revision);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void register_version(void)
|
||||
{
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "version",
|
||||
.help = "Get version of chip and SDK",
|
||||
.hint = NULL,
|
||||
.func = &get_version,
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
|
||||
/** 'restart' command restarts the program */
|
||||
|
||||
static int restart(int argc, char **argv)
|
||||
{
|
||||
ESP_LOGI(TAG, "Restarting");
|
||||
esp_restart();
|
||||
}
|
||||
|
||||
static void register_restart(void)
|
||||
{
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "restart",
|
||||
.help = "Software reset of the chip",
|
||||
.hint = NULL,
|
||||
.func = &restart,
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
|
||||
/** 'free' command prints available heap memory */
|
||||
|
||||
static int free_mem(int argc, char **argv)
|
||||
{
|
||||
printf("%d\n", esp_get_free_heap_size());
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void register_free(void)
|
||||
{
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "free",
|
||||
.help = "Get the current size of free heap memory",
|
||||
.hint = NULL,
|
||||
.func = &free_mem,
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
|
||||
/* 'heap' command prints minumum heap size */
|
||||
static int heap_size(int argc, char **argv)
|
||||
{
|
||||
uint32_t heap_size = heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT);
|
||||
ESP_LOGI(TAG, "min heap size: %u", heap_size);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void register_heap(void)
|
||||
{
|
||||
const esp_console_cmd_t heap_cmd = {
|
||||
.command = "heap",
|
||||
.help = "Get minimum size of free heap memory that was available during program execution",
|
||||
.hint = NULL,
|
||||
.func = &heap_size,
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&heap_cmd) );
|
||||
|
||||
}
|
||||
|
||||
/** 'tasks' command prints the list of tasks and related information */
|
||||
#if WITH_TASKS_INFO
|
||||
|
||||
static int tasks_info(int argc, char **argv)
|
||||
{
|
||||
const size_t bytes_per_task = 40; /* see vTaskList description */
|
||||
char *task_list_buffer = malloc(uxTaskGetNumberOfTasks() * bytes_per_task);
|
||||
if (task_list_buffer == NULL) {
|
||||
ESP_LOGE(TAG, "failed to allocate buffer for vTaskList output");
|
||||
return 1;
|
||||
}
|
||||
fputs("Task Name\tStatus\tPrio\tHWM\tTask#", stdout);
|
||||
#ifdef CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID
|
||||
fputs("\tAffinity", stdout);
|
||||
#endif
|
||||
fputs("\n", stdout);
|
||||
vTaskList(task_list_buffer);
|
||||
fputs(task_list_buffer, stdout);
|
||||
free(task_list_buffer);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void register_tasks(void)
|
||||
{
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "tasks",
|
||||
.help = "Get information about running tasks",
|
||||
.hint = NULL,
|
||||
.func = &tasks_info,
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
|
||||
#endif // WITH_TASKS_INFO
|
||||
|
||||
/** 'deep_sleep' command puts the chip into deep sleep mode */
|
||||
|
||||
static struct {
|
||||
struct arg_int *wakeup_time;
|
||||
struct arg_int *wakeup_gpio_num;
|
||||
struct arg_int *wakeup_gpio_level;
|
||||
struct arg_end *end;
|
||||
} deep_sleep_args;
|
||||
|
||||
|
||||
static int deep_sleep(int argc, char **argv)
|
||||
{
|
||||
int nerrors = arg_parse(argc, argv, (void **) &deep_sleep_args);
|
||||
if (nerrors != 0) {
|
||||
arg_print_errors(stderr, deep_sleep_args.end, argv[0]);
|
||||
return 1;
|
||||
}
|
||||
if (deep_sleep_args.wakeup_time->count) {
|
||||
uint64_t timeout = 1000ULL * deep_sleep_args.wakeup_time->ival[0];
|
||||
ESP_LOGI(TAG, "Enabling timer wakeup, timeout=%lluus", timeout);
|
||||
ESP_ERROR_CHECK( esp_sleep_enable_timer_wakeup(timeout) );
|
||||
}
|
||||
if (deep_sleep_args.wakeup_gpio_num->count) {
|
||||
int io_num = deep_sleep_args.wakeup_gpio_num->ival[0];
|
||||
if (!rtc_gpio_is_valid_gpio(io_num)) {
|
||||
ESP_LOGE(TAG, "GPIO %d is not an RTC IO", io_num);
|
||||
return 1;
|
||||
}
|
||||
int level = 0;
|
||||
if (deep_sleep_args.wakeup_gpio_level->count) {
|
||||
level = deep_sleep_args.wakeup_gpio_level->ival[0];
|
||||
if (level != 0 && level != 1) {
|
||||
ESP_LOGE(TAG, "Invalid wakeup level: %d", level);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
ESP_LOGI(TAG, "Enabling wakeup on GPIO%d, wakeup on %s level",
|
||||
io_num, level ? "HIGH" : "LOW");
|
||||
|
||||
ESP_ERROR_CHECK( esp_sleep_enable_ext1_wakeup(1ULL << io_num, level) );
|
||||
}
|
||||
rtc_gpio_isolate(GPIO_NUM_12);
|
||||
esp_deep_sleep_start();
|
||||
}
|
||||
|
||||
static void register_deep_sleep(void)
|
||||
{
|
||||
deep_sleep_args.wakeup_time =
|
||||
arg_int0("t", "time", "<t>", "Wake up time, ms");
|
||||
deep_sleep_args.wakeup_gpio_num =
|
||||
arg_int0(NULL, "io", "<n>",
|
||||
"If specified, wakeup using GPIO with given number");
|
||||
deep_sleep_args.wakeup_gpio_level =
|
||||
arg_int0(NULL, "io_level", "<0|1>", "GPIO level to trigger wakeup");
|
||||
deep_sleep_args.end = arg_end(3);
|
||||
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "deep_sleep",
|
||||
.help = "Enter deep sleep mode. "
|
||||
"Two wakeup modes are supported: timer and GPIO. "
|
||||
"If no wakeup option is specified, will sleep indefinitely.",
|
||||
.hint = NULL,
|
||||
.func = &deep_sleep,
|
||||
.argtable = &deep_sleep_args
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
|
||||
/** 'light_sleep' command puts the chip into light sleep mode */
|
||||
|
||||
static struct {
|
||||
struct arg_int *wakeup_time;
|
||||
struct arg_int *wakeup_gpio_num;
|
||||
struct arg_int *wakeup_gpio_level;
|
||||
struct arg_end *end;
|
||||
} light_sleep_args;
|
||||
|
||||
static int light_sleep(int argc, char **argv)
|
||||
{
|
||||
int nerrors = arg_parse(argc, argv, (void **) &light_sleep_args);
|
||||
if (nerrors != 0) {
|
||||
arg_print_errors(stderr, light_sleep_args.end, argv[0]);
|
||||
return 1;
|
||||
}
|
||||
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL);
|
||||
if (light_sleep_args.wakeup_time->count) {
|
||||
uint64_t timeout = 1000ULL * light_sleep_args.wakeup_time->ival[0];
|
||||
ESP_LOGI(TAG, "Enabling timer wakeup, timeout=%lluus", timeout);
|
||||
ESP_ERROR_CHECK( esp_sleep_enable_timer_wakeup(timeout) );
|
||||
}
|
||||
int io_count = light_sleep_args.wakeup_gpio_num->count;
|
||||
if (io_count != light_sleep_args.wakeup_gpio_level->count) {
|
||||
ESP_LOGE(TAG, "Should have same number of 'io' and 'io_level' arguments");
|
||||
return 1;
|
||||
}
|
||||
for (int i = 0; i < io_count; ++i) {
|
||||
int io_num = light_sleep_args.wakeup_gpio_num->ival[i];
|
||||
int level = light_sleep_args.wakeup_gpio_level->ival[i];
|
||||
if (level != 0 && level != 1) {
|
||||
ESP_LOGE(TAG, "Invalid wakeup level: %d", level);
|
||||
return 1;
|
||||
}
|
||||
ESP_LOGI(TAG, "Enabling wakeup on GPIO%d, wakeup on %s level",
|
||||
io_num, level ? "HIGH" : "LOW");
|
||||
|
||||
ESP_ERROR_CHECK( gpio_wakeup_enable(io_num, level ? GPIO_INTR_HIGH_LEVEL : GPIO_INTR_LOW_LEVEL) );
|
||||
}
|
||||
if (io_count > 0) {
|
||||
ESP_ERROR_CHECK( esp_sleep_enable_gpio_wakeup() );
|
||||
}
|
||||
if (CONFIG_ESP_CONSOLE_UART_NUM <= UART_NUM_1) {
|
||||
ESP_LOGI(TAG, "Enabling UART wakeup (press ENTER to exit light sleep)");
|
||||
ESP_ERROR_CHECK( uart_set_wakeup_threshold(CONFIG_ESP_CONSOLE_UART_NUM, 3) );
|
||||
ESP_ERROR_CHECK( esp_sleep_enable_uart_wakeup(CONFIG_ESP_CONSOLE_UART_NUM) );
|
||||
}
|
||||
fflush(stdout);
|
||||
uart_wait_tx_idle_polling(CONFIG_ESP_CONSOLE_UART_NUM);
|
||||
esp_light_sleep_start();
|
||||
esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
|
||||
const char *cause_str;
|
||||
switch (cause) {
|
||||
case ESP_SLEEP_WAKEUP_GPIO:
|
||||
cause_str = "GPIO";
|
||||
break;
|
||||
case ESP_SLEEP_WAKEUP_UART:
|
||||
cause_str = "UART";
|
||||
break;
|
||||
case ESP_SLEEP_WAKEUP_TIMER:
|
||||
cause_str = "timer";
|
||||
break;
|
||||
default:
|
||||
cause_str = "unknown";
|
||||
printf("%d\n", cause);
|
||||
}
|
||||
ESP_LOGI(TAG, "Woke up from: %s", cause_str);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void register_light_sleep(void)
|
||||
{
|
||||
light_sleep_args.wakeup_time =
|
||||
arg_int0("t", "time", "<t>", "Wake up time, ms");
|
||||
light_sleep_args.wakeup_gpio_num =
|
||||
arg_intn(NULL, "io", "<n>", 0, 8,
|
||||
"If specified, wakeup using GPIO with given number");
|
||||
light_sleep_args.wakeup_gpio_level =
|
||||
arg_intn(NULL, "io_level", "<0|1>", 0, 8, "GPIO level to trigger wakeup");
|
||||
light_sleep_args.end = arg_end(3);
|
||||
|
||||
const esp_console_cmd_t cmd = {
|
||||
.command = "light_sleep",
|
||||
.help = "Enter light sleep mode. "
|
||||
"Two wakeup modes are supported: timer and GPIO. "
|
||||
"Multiple GPIO pins can be specified using pairs of "
|
||||
"'io' and 'io_level' arguments. "
|
||||
"Will also wake up on UART input.",
|
||||
.hint = NULL,
|
||||
.func = &light_sleep,
|
||||
.argtable = &light_sleep_args
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&cmd) );
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/* Console example — various system commands
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Register system functions
|
||||
void register_system(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,10 @@
|
||||
#
|
||||
# Component Makefile
|
||||
#
|
||||
# This Makefile should, at the very least, just include $(SDK_PATH)/Makefile. By default,
|
||||
# this will take the sources in the src/ directory, compile them and link them into
|
||||
# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable,
|
||||
# please read the SDK documents if you need to do this.
|
||||
#
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := .
|
||||
@@ -0,0 +1,21 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import ttfw_idf
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag='Example_GENERIC')
|
||||
def test_examples_system_console(env, extra_data):
|
||||
dut = env.get_dut('console_example', 'examples/system/console', app_config_name='history')
|
||||
print("Using binary path: {}".format(dut.app.binary_path))
|
||||
dut.start_app()
|
||||
dut.expect("Command history enabled")
|
||||
env.close_dut(dut.name)
|
||||
|
||||
dut = env.get_dut('console_example', 'examples/system/console', app_config_name='nohistory')
|
||||
print("Using binary path: {}".format(dut.app.binary_path))
|
||||
dut.start_app()
|
||||
dut.expect("Command history disabled")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_examples_system_console()
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "cmd_wifi.c"
|
||||
"console_example_main.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,11 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config STORE_HISTORY
|
||||
bool "Store command history in flash"
|
||||
default y
|
||||
help
|
||||
Linenoise line editing library provides functions to save and load
|
||||
command history. If this option is enabled, initalizes a FAT filesystem
|
||||
and uses it to store command history.
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,21 @@
|
||||
/* Console example — declarations of command registration functions.
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "cmd_system.h"
|
||||
#include "cmd_wifi.h"
|
||||
#include "cmd_nvs.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,132 @@
|
||||
/* Console example — WiFi commands
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_console.h"
|
||||
#include "argtable3/argtable3.h"
|
||||
#include "cmd_decl.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_event.h"
|
||||
#include "cmd_wifi.h"
|
||||
|
||||
#define JOIN_TIMEOUT_MS (10000)
|
||||
|
||||
static EventGroupHandle_t wifi_event_group;
|
||||
const int CONNECTED_BIT = BIT0;
|
||||
|
||||
|
||||
static void event_handler(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data)
|
||||
{
|
||||
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||
esp_wifi_connect();
|
||||
xEventGroupClearBits(wifi_event_group, CONNECTED_BIT);
|
||||
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
|
||||
xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
|
||||
}
|
||||
}
|
||||
|
||||
static void initialise_wifi(void)
|
||||
{
|
||||
esp_log_level_set("wifi", ESP_LOG_WARN);
|
||||
static bool initialized = false;
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
wifi_event_group = xEventGroupCreate();
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
esp_netif_t *ap_netif = esp_netif_create_default_wifi_ap();
|
||||
assert(ap_netif);
|
||||
esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta();
|
||||
assert(sta_netif);
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK( esp_wifi_init(&cfg) );
|
||||
ESP_ERROR_CHECK( esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &event_handler, NULL) );
|
||||
ESP_ERROR_CHECK( esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL) );
|
||||
ESP_ERROR_CHECK( esp_wifi_set_storage(WIFI_STORAGE_RAM) );
|
||||
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_NULL) );
|
||||
ESP_ERROR_CHECK( esp_wifi_start() );
|
||||
initialized = true;
|
||||
}
|
||||
|
||||
static bool wifi_join(const char *ssid, const char *pass, int timeout_ms)
|
||||
{
|
||||
initialise_wifi();
|
||||
wifi_config_t wifi_config = { 0 };
|
||||
strlcpy((char *) wifi_config.sta.ssid, ssid, sizeof(wifi_config.sta.ssid));
|
||||
if (pass) {
|
||||
strlcpy((char *) wifi_config.sta.password, pass, sizeof(wifi_config.sta.password));
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) );
|
||||
ESP_ERROR_CHECK( esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config) );
|
||||
ESP_ERROR_CHECK( esp_wifi_connect() );
|
||||
|
||||
int bits = xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT,
|
||||
pdFALSE, pdTRUE, timeout_ms / portTICK_PERIOD_MS);
|
||||
return (bits & CONNECTED_BIT) != 0;
|
||||
}
|
||||
|
||||
/** Arguments used by 'join' function */
|
||||
static struct {
|
||||
struct arg_int *timeout;
|
||||
struct arg_str *ssid;
|
||||
struct arg_str *password;
|
||||
struct arg_end *end;
|
||||
} join_args;
|
||||
|
||||
static int connect(int argc, char **argv)
|
||||
{
|
||||
int nerrors = arg_parse(argc, argv, (void **) &join_args);
|
||||
if (nerrors != 0) {
|
||||
arg_print_errors(stderr, join_args.end, argv[0]);
|
||||
return 1;
|
||||
}
|
||||
ESP_LOGI(__func__, "Connecting to '%s'",
|
||||
join_args.ssid->sval[0]);
|
||||
|
||||
/* set default value*/
|
||||
if (join_args.timeout->count == 0) {
|
||||
join_args.timeout->ival[0] = JOIN_TIMEOUT_MS;
|
||||
}
|
||||
|
||||
bool connected = wifi_join(join_args.ssid->sval[0],
|
||||
join_args.password->sval[0],
|
||||
join_args.timeout->ival[0]);
|
||||
if (!connected) {
|
||||
ESP_LOGW(__func__, "Connection timed out");
|
||||
return 1;
|
||||
}
|
||||
ESP_LOGI(__func__, "Connected");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void register_wifi(void)
|
||||
{
|
||||
join_args.timeout = arg_int0(NULL, "timeout", "<t>", "Connection timeout, ms");
|
||||
join_args.ssid = arg_str1(NULL, NULL, "<ssid>", "SSID of AP");
|
||||
join_args.password = arg_str0(NULL, NULL, "<pass>", "PSK of AP");
|
||||
join_args.end = arg_end(2);
|
||||
|
||||
const esp_console_cmd_t join_cmd = {
|
||||
.command = "join",
|
||||
.help = "Join WiFi AP as a station",
|
||||
.hint = NULL,
|
||||
.func = &connect,
|
||||
.argtable = &join_args
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&join_cmd) );
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
/* Console example — declarations of command registration functions.
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Register WiFi functions
|
||||
void register_wifi(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
/* Console example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_console.h"
|
||||
#include "esp_vfs_dev.h"
|
||||
#include "driver/uart.h"
|
||||
#include "linenoise/linenoise.h"
|
||||
#include "argtable3/argtable3.h"
|
||||
#include "cmd_decl.h"
|
||||
#include "esp_vfs_fat.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
static const char* TAG = "example";
|
||||
#define PROMPT_STR CONFIG_IDF_TARGET
|
||||
|
||||
/* Console command history can be stored to and loaded from a file.
|
||||
* The easiest way to do this is to use FATFS filesystem on top of
|
||||
* wear_levelling library.
|
||||
*/
|
||||
#if CONFIG_STORE_HISTORY
|
||||
|
||||
#define MOUNT_PATH "/data"
|
||||
#define HISTORY_PATH MOUNT_PATH "/history.txt"
|
||||
|
||||
static void initialize_filesystem(void)
|
||||
{
|
||||
static wl_handle_t wl_handle;
|
||||
const esp_vfs_fat_mount_config_t mount_config = {
|
||||
.max_files = 4,
|
||||
.format_if_mount_failed = true
|
||||
};
|
||||
esp_err_t err = esp_vfs_fat_spiflash_mount(MOUNT_PATH, "storage", &mount_config, &wl_handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to mount FATFS (%s)", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif // CONFIG_STORE_HISTORY
|
||||
|
||||
static void initialize_nvs(void)
|
||||
{
|
||||
esp_err_t err = nvs_flash_init();
|
||||
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK( nvs_flash_erase() );
|
||||
err = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(err);
|
||||
}
|
||||
|
||||
static void initialize_console(void)
|
||||
{
|
||||
/* Drain stdout before reconfiguring it */
|
||||
fflush(stdout);
|
||||
fsync(fileno(stdout));
|
||||
|
||||
/* Disable buffering on stdin */
|
||||
setvbuf(stdin, NULL, _IONBF, 0);
|
||||
|
||||
/* Minicom, screen, idf_monitor send CR when ENTER key is pressed */
|
||||
esp_vfs_dev_uart_port_set_rx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CR);
|
||||
/* Move the caret to the beginning of the next line on '\n' */
|
||||
esp_vfs_dev_uart_port_set_tx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CRLF);
|
||||
|
||||
/* Configure UART. Note that REF_TICK is used so that the baud rate remains
|
||||
* correct while APB frequency is changing in light sleep mode.
|
||||
*/
|
||||
const uart_config_t uart_config = {
|
||||
.baud_rate = CONFIG_ESP_CONSOLE_UART_BAUDRATE,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.source_clk = UART_SCLK_REF_TICK,
|
||||
};
|
||||
/* Install UART driver for interrupt-driven reads and writes */
|
||||
ESP_ERROR_CHECK( uart_driver_install(CONFIG_ESP_CONSOLE_UART_NUM,
|
||||
256, 0, 0, NULL, 0) );
|
||||
ESP_ERROR_CHECK( uart_param_config(CONFIG_ESP_CONSOLE_UART_NUM, &uart_config) );
|
||||
|
||||
/* Tell VFS to use UART driver */
|
||||
esp_vfs_dev_uart_use_driver(CONFIG_ESP_CONSOLE_UART_NUM);
|
||||
|
||||
/* Initialize the console */
|
||||
esp_console_config_t console_config = {
|
||||
.max_cmdline_args = 8,
|
||||
.max_cmdline_length = 256,
|
||||
#if CONFIG_LOG_COLORS
|
||||
.hint_color = atoi(LOG_COLOR_CYAN)
|
||||
#endif
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_init(&console_config) );
|
||||
|
||||
/* Configure linenoise line completion library */
|
||||
/* Enable multiline editing. If not set, long commands will scroll within
|
||||
* single line.
|
||||
*/
|
||||
linenoiseSetMultiLine(1);
|
||||
|
||||
/* Tell linenoise where to get command completions and hints */
|
||||
linenoiseSetCompletionCallback(&esp_console_get_completion);
|
||||
linenoiseSetHintsCallback((linenoiseHintsCallback*) &esp_console_get_hint);
|
||||
|
||||
/* Set command history size */
|
||||
linenoiseHistorySetMaxLen(100);
|
||||
|
||||
/* Don't return empty lines */
|
||||
linenoiseAllowEmpty(false);
|
||||
|
||||
#if CONFIG_STORE_HISTORY
|
||||
/* Load command history from filesystem */
|
||||
linenoiseHistoryLoad(HISTORY_PATH);
|
||||
#endif
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
initialize_nvs();
|
||||
|
||||
#if CONFIG_STORE_HISTORY
|
||||
initialize_filesystem();
|
||||
ESP_LOGI(TAG, "Command history enabled");
|
||||
#else
|
||||
ESP_LOGI(TAG, "Command history disabled");
|
||||
#endif
|
||||
|
||||
initialize_console();
|
||||
|
||||
/* Register commands */
|
||||
esp_console_register_help_command();
|
||||
register_system();
|
||||
register_wifi();
|
||||
register_nvs();
|
||||
|
||||
/* Prompt to be printed before each line.
|
||||
* This can be customized, made dynamic, etc.
|
||||
*/
|
||||
const char* prompt = LOG_COLOR_I PROMPT_STR "> " LOG_RESET_COLOR;
|
||||
|
||||
printf("\n"
|
||||
"This is an example of ESP-IDF console component.\n"
|
||||
"Type 'help' to get the list of commands.\n"
|
||||
"Use UP/DOWN arrows to navigate through command history.\n"
|
||||
"Press TAB when typing command name to auto-complete.\n"
|
||||
"Press Enter or Ctrl+C will terminate the console environment.\n");
|
||||
|
||||
/* Figure out if the terminal supports escape sequences */
|
||||
int probe_status = linenoiseProbe();
|
||||
if (probe_status) { /* zero indicates success */
|
||||
printf("\n"
|
||||
"Your terminal application does not support escape sequences.\n"
|
||||
"Line editing and history features are disabled.\n"
|
||||
"On Windows, try using Putty instead.\n");
|
||||
linenoiseSetDumbMode(1);
|
||||
#if CONFIG_LOG_COLORS
|
||||
/* Since the terminal doesn't support escape sequences,
|
||||
* don't use color codes in the prompt.
|
||||
*/
|
||||
prompt = PROMPT_STR "> ";
|
||||
#endif //CONFIG_LOG_COLORS
|
||||
}
|
||||
|
||||
/* Main loop */
|
||||
while(true) {
|
||||
/* Get a line using linenoise.
|
||||
* The line is returned when ENTER is pressed.
|
||||
*/
|
||||
char* line = linenoise(prompt);
|
||||
if (line == NULL) { /* Break on EOF or error */
|
||||
break;
|
||||
}
|
||||
/* Add the command to the history if not empty*/
|
||||
if (strlen(line) > 0) {
|
||||
linenoiseHistoryAdd(line);
|
||||
#if CONFIG_STORE_HISTORY
|
||||
/* Save command history to filesystem */
|
||||
linenoiseHistorySave(HISTORY_PATH);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Try to run the command */
|
||||
int ret;
|
||||
esp_err_t err = esp_console_run(line, &ret);
|
||||
if (err == ESP_ERR_NOT_FOUND) {
|
||||
printf("Unrecognized command\n");
|
||||
} else if (err == ESP_ERR_INVALID_ARG) {
|
||||
// command was empty
|
||||
} else if (err == ESP_OK && ret != ESP_OK) {
|
||||
printf("Command returned non-zero error code: 0x%x (%s)\n", ret, esp_err_to_name(ret));
|
||||
} else if (err != ESP_OK) {
|
||||
printf("Internal error: %s\n", esp_err_to_name(err));
|
||||
}
|
||||
/* linenoise allocates line buffer on the heap, so need to free it */
|
||||
linenoiseFree(line);
|
||||
}
|
||||
|
||||
ESP_LOGE(TAG, "Error or end-of-input, terminating console");
|
||||
esp_console_deinit();
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
|
||||
nvs, data, nvs, 0x9000, 0x6000,
|
||||
phy_init, data, phy, 0xf000, 0x1000,
|
||||
factory, app, factory, 0x10000, 1M,
|
||||
storage, data, fat, , 1M,
|
||||
|
@@ -0,0 +1 @@
|
||||
CONFIG_STORE_HISTORY=y
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_STORE_HISTORY=n
|
||||
@@ -0,0 +1,18 @@
|
||||
# Reduce bootloader log verbosity
|
||||
CONFIG_BOOTLOADER_LOG_LEVEL_WARN=y
|
||||
CONFIG_BOOTLOADER_LOG_LEVEL=2
|
||||
|
||||
# Increase main task stack size
|
||||
CONFIG_ESP_MAIN_TASK_STACK_SIZE=7168
|
||||
|
||||
# Enable filesystem
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_example.csv"
|
||||
CONFIG_PARTITION_TABLE_FILENAME="partitions_example.csv"
|
||||
|
||||
# Enable FreeRTOS stats formatting functions, needed for 'tasks' command
|
||||
CONFIG_FREERTOS_USE_TRACE_FACILITY=y
|
||||
CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y
|
||||
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(deep_sleep)
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := deep_sleep
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
# Deep Sleep Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
The [deep sleep mode](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/sleep_modes.html#sleep-modes) of the ESP32 is a power saving mode that causes the CPU, majority of RAM, and digital peripherals that are clocked from APB_CLK to be powered off. Deep sleep mode can be exited using one of multiple [wake up sources](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/sleep_modes.html#wakeup-sources). This example demonstrates how to use the [`esp_sleep.h`](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/sleep_modes.html#api-reference) API to enter deep sleep mode, then wake up form different sources.
|
||||
|
||||
The following wake up sources are demonstrated in this example (refer to the [Wakeup Sources documentation](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/sleep_modes.html#wakeup-sources) for more details regarding wake up sources):
|
||||
|
||||
1. **Timer:** An RTC timer that can be programmed to trigger a wake up after a preset time. This example will trigger a wake up every 20 seconds.
|
||||
2. **EXT1:** External wake up 1 which is tied to multiple RTC GPIOs. This example use GPIO2 and GPIO4 to trigger a wake up with any one of the two pins are HIGH.
|
||||
3. **Touch:** Touch pad sensor interrupt. This example uses touch pads connected to GPIO32, GPIO33 in ESP32 or GPIO9 in ESP32-S2 to trigger a wake up when any of the pads are pressed.
|
||||
4. **ULP:** Ultra Low Power Coprocessor which can continue to run during deep sleep. This example utilizes the ULP and constantly sample the chip's temperature and trigger a wake up if the chips temperature exceeds ~5 degrees Celsius.
|
||||
|
||||
Note: Some wake up sources can be disabled via configuration (see section on [project configuration](#Configure-the-project))
|
||||
|
||||
In this example, the `CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP` Kconfig option is used, which allows you to reduce the boot time of the bootloader during waking up from deep sleep. The bootloader stores in rtc memory the address of a running partition and uses it when it wakes up. This example allows you to skip all image checks and speed up the boot.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
This example should be able to run on any commonly available ESP32 development board without any extra hardware if only **Timer** and **ULP** wake up sources are used. However, the following extra connections will be required for the remaining wake up sources.
|
||||
|
||||
- **EXT1:** GPIO2 and GPIO4 should be connected to LOW to avoid floating pins. When triggering a wake up, connect one or both of the pins to HIGH. Note that floating pins may trigger a wake up.
|
||||
|
||||
- **Touch:** GPIO32, GPIO33 in ESP32 or GPIO9 in ESP32-S2 should be connected to touch sensors (see [Touch Sensor Application Note](https://github.com/espressif/esp-iot-solution/blob/master/documents/touch_pad_solution/touch_sensor_design_en.md)).
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
* **Touch wake up** can be enabled/disabled via `Example configuration > Enable touch wake up`
|
||||
* **ULT wake up** can be enabled/disabled via `Example configuration > Enable temperature monitoring by ULP`
|
||||
|
||||
Wake up sources that are unused or unconnected should be disabled in configuration to prevent inadvertent triggering of wake up as a result of floating pins.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
On initial startup, this example will detect that this is the first boot and output the following low:
|
||||
|
||||
```
|
||||
...
|
||||
I (304) cpu_start: Starting scheduler on PRO CPU.
|
||||
I (0) cpu_start: Starting scheduler on APP CPU.
|
||||
Not a deep sleep reset
|
||||
Enabling timer wakeup, 20s
|
||||
Enabling EXT1 wakeup on pins GPIO2, GPIO4
|
||||
Touch pad #8 average: 2148, wakeup threshold set to 2048.
|
||||
Touch pad #9 average: 2148, wakeup threshold set to 2048.
|
||||
Enabling touch pad wakeup
|
||||
Enabling ULP wakeup
|
||||
Entering deep sleep
|
||||
```
|
||||
|
||||
The ESP32 will then enter deep sleep. When a wake up occurs, the ESP32 must undergo the entire boot process again. However the example will detect that this boot is due to a wake up and indicate the wake up source in the output log such as the following:
|
||||
|
||||
```
|
||||
...
|
||||
I (304) cpu_start: Starting scheduler on PRO CPU.
|
||||
I (0) cpu_start: Starting scheduler on APP CPU.
|
||||
Wake up from timer. Time spent in deep sleep: 20313ms
|
||||
ULP did 110 temperature measurements in 20313 ms
|
||||
Initial T=87, latest T=87
|
||||
Enabling timer wakeup, 20s
|
||||
Enabling EXT1 wakeup on pins GPIO2, GPIO4
|
||||
Touch pad #8 average: 2149, wakeup threshold set to 2049.
|
||||
Touch pad #9 average: 2146, wakeup threshold set to 2046.
|
||||
Enabling touch pad wakeup
|
||||
Enabling ULP wakeup
|
||||
Entering deep sleep
|
||||
```
|
||||
@@ -0,0 +1,29 @@
|
||||
from __future__ import unicode_literals
|
||||
import re
|
||||
import ttfw_idf
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag='Example_GENERIC')
|
||||
def test_examples_deep_sleep(env, extra_data):
|
||||
|
||||
dut = env.get_dut('deep_sleep', 'examples/system/deep_sleep')
|
||||
dut.start_app()
|
||||
|
||||
def expect_enable_deep_sleep():
|
||||
dut.expect_all('Enabling timer wakeup, 20s',
|
||||
re.compile(r'Touch pad #8 average: \d+, wakeup threshold set to \d+.'),
|
||||
re.compile(r'Touch pad #9 average: \d+, wakeup threshold set to \d+.'),
|
||||
'Enabling touch pad wakeup',
|
||||
'Entering deep sleep',
|
||||
timeout=10)
|
||||
|
||||
dut.expect('Not a deep sleep reset', timeout=30)
|
||||
expect_enable_deep_sleep()
|
||||
|
||||
# Check that it spent 2xxxxms in deep sleep, i.e at least 20 seconds:
|
||||
dut.expect(re.compile(r'Wake up from timer. Time spent in deep sleep: 2\d{4}ms'), timeout=30)
|
||||
expect_enable_deep_sleep()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_examples_deep_sleep()
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "deep_sleep_example_main.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,28 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config EXAMPLE_TOUCH_WAKEUP
|
||||
bool "Enable touch wake up"
|
||||
default y
|
||||
help
|
||||
This option enables wake up from deep sleep using touch pads
|
||||
TOUCH8 and TOUCH9, which correspond to GPIO33 and GPIO32.
|
||||
|
||||
config EXAMPLE_ULP_TEMPERATURE_WAKEUP
|
||||
bool "Enable temperature monitoring by ULP"
|
||||
default y
|
||||
help
|
||||
This option enables wake up from deep sleep using ULP.
|
||||
ULP program monitors the on-chip temperature sensor and
|
||||
wakes up the chip when the temperature goes outside of
|
||||
the window defined by the initial temperature and a threshold
|
||||
around it.
|
||||
|
||||
config EXAMPLE_EXT1_WAKEUP
|
||||
bool "Enable wakeup from GPIO"
|
||||
default y
|
||||
help
|
||||
This option enables wake up from deep sleep from GPIO2 and GPIO4. They should be connected to LOW to avoid
|
||||
floating pins. When triggering a wake up, connect one or both of the pins to HIGH. Note that floating
|
||||
pins may trigger a wake up.
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,3 @@
|
||||
#
|
||||
# Main Makefile. This is basically the same as a component makefile.
|
||||
#
|
||||
@@ -0,0 +1,374 @@
|
||||
/* Deep sleep wake up example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <sys/time.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_sleep.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp32/ulp.h"
|
||||
#include "driver/touch_pad.h"
|
||||
#include "driver/adc.h"
|
||||
#include "driver/rtc_io.h"
|
||||
#include "soc/sens_periph.h"
|
||||
#include "soc/rtc.h"
|
||||
|
||||
static RTC_DATA_ATTR struct timeval sleep_enter_time;
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_ULP_TEMPERATURE_WAKEUP
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
|
||||
/*
|
||||
* Offset (in 32-bit words) in RTC Slow memory where the data is placed
|
||||
* by the ULP coprocessor. It can be chosen to be any value greater or equal
|
||||
* to ULP program size, and less than the CONFIG_ESP32_ULP_COPROC_RESERVE_MEM/4 - 6,
|
||||
* where 6 is the number of words used by the ULP coprocessor.
|
||||
*/
|
||||
#define ULP_DATA_OFFSET 36
|
||||
|
||||
_Static_assert(ULP_DATA_OFFSET < CONFIG_ESP32_ULP_COPROC_RESERVE_MEM/4 - 6,
|
||||
"ULP_DATA_OFFSET is set too high, or CONFIG_ESP32_ULP_COPROC_RESERVE_MEM is not sufficient");
|
||||
|
||||
/**
|
||||
* @brief Start ULP temperature monitoring program
|
||||
*
|
||||
* This function loads a program into the RTC Slow memory and starts up the ULP.
|
||||
* The program monitors on-chip temperature sensor and wakes up the SoC when
|
||||
* the temperature goes lower or higher than certain thresholds.
|
||||
*/
|
||||
static void start_ulp_temperature_monitoring(void);
|
||||
|
||||
/**
|
||||
* @brief Utility function which reads data written by ULP program
|
||||
*
|
||||
* @param offset offset from ULP_DATA_OFFSET in RTC Slow memory, in words
|
||||
* @return lower 16-bit part of the word writable by the ULP
|
||||
*/
|
||||
static inline uint16_t ulp_data_read(size_t offset)
|
||||
{
|
||||
return RTC_SLOW_MEM[ULP_DATA_OFFSET + offset] & 0xffff;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Utility function which writes data to be ready by ULP program
|
||||
*
|
||||
* @param offset offset from ULP_DATA_OFFSET in RTC Slow memory, in words
|
||||
* @param value lower 16-bit part of the word to be stored
|
||||
*/
|
||||
static inline void ulp_data_write(size_t offset, uint16_t value)
|
||||
{
|
||||
RTC_SLOW_MEM[ULP_DATA_OFFSET + offset] = value;
|
||||
}
|
||||
#endif // CONFIG_IDF_TARGET_ESP32
|
||||
#endif // CONFIG_EXAMPLE_ULP_TEMPERATURE_WAKEUP
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_TOUCH_WAKEUP
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
#define TOUCH_THRESH_NO_USE 0
|
||||
static void calibrate_touch_pad(touch_pad_t pad);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
struct timeval now;
|
||||
gettimeofday(&now, NULL);
|
||||
int sleep_time_ms = (now.tv_sec - sleep_enter_time.tv_sec) * 1000 + (now.tv_usec - sleep_enter_time.tv_usec) / 1000;
|
||||
|
||||
switch (esp_sleep_get_wakeup_cause()) {
|
||||
#ifdef CONFIG_EXAMPLE_EXT1_WAKEUP
|
||||
case ESP_SLEEP_WAKEUP_EXT1: {
|
||||
uint64_t wakeup_pin_mask = esp_sleep_get_ext1_wakeup_status();
|
||||
if (wakeup_pin_mask != 0) {
|
||||
int pin = __builtin_ffsll(wakeup_pin_mask) - 1;
|
||||
printf("Wake up from GPIO %d\n", pin);
|
||||
} else {
|
||||
printf("Wake up from GPIO\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif // CONFIG_EXAMPLE_EXT1_WAKEUP
|
||||
case ESP_SLEEP_WAKEUP_TIMER: {
|
||||
printf("Wake up from timer. Time spent in deep sleep: %dms\n", sleep_time_ms);
|
||||
break;
|
||||
}
|
||||
#ifdef CONFIG_EXAMPLE_TOUCH_WAKEUP
|
||||
case ESP_SLEEP_WAKEUP_TOUCHPAD: {
|
||||
printf("Wake up from touch on pad %d\n", esp_sleep_get_touchpad_wakeup_status());
|
||||
break;
|
||||
}
|
||||
#endif // CONFIG_EXAMPLE_TOUCH_WAKEUP
|
||||
#ifdef CONFIG_EXAMPLE_ULP_TEMPERATURE_WAKEUP
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
case ESP_SLEEP_WAKEUP_ULP: {
|
||||
printf("Wake up from ULP\n");
|
||||
int16_t diff_high = (int16_t) ulp_data_read(3);
|
||||
int16_t diff_low = (int16_t) ulp_data_read(4);
|
||||
if (diff_high < 0) {
|
||||
printf("High temperature alarm was triggered\n");
|
||||
} else if (diff_low < 0) {
|
||||
printf("Low temperature alarm was triggered\n");
|
||||
} else {
|
||||
assert(false && "temperature has stayed within limits, but got ULP wakeup\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif // CONFIG_IDF_TARGET_ESP32
|
||||
#endif // CONFIG_EXAMPLE_ULP_TEMPERATURE_WAKEUP
|
||||
case ESP_SLEEP_WAKEUP_UNDEFINED:
|
||||
default:
|
||||
printf("Not a deep sleep reset\n");
|
||||
}
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_ULP_TEMPERATURE_WAKEUP
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
if (esp_sleep_get_wakeup_cause() != ESP_SLEEP_WAKEUP_UNDEFINED) {
|
||||
printf("ULP did %d temperature measurements in %d ms\n", ulp_data_read(1), sleep_time_ms);
|
||||
printf("Initial T=%d, latest T=%d\n", ulp_data_read(0), ulp_data_read(2));
|
||||
}
|
||||
#endif // CONFIG_IDF_TARGET_ESP32
|
||||
#endif // CONFIG_EXAMPLE_ULP_TEMPERATURE_WAKEUP
|
||||
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
|
||||
const int wakeup_time_sec = 20;
|
||||
printf("Enabling timer wakeup, %ds\n", wakeup_time_sec);
|
||||
esp_sleep_enable_timer_wakeup(wakeup_time_sec * 1000000);
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_EXT1_WAKEUP
|
||||
const int ext_wakeup_pin_1 = 2;
|
||||
const uint64_t ext_wakeup_pin_1_mask = 1ULL << ext_wakeup_pin_1;
|
||||
const int ext_wakeup_pin_2 = 4;
|
||||
const uint64_t ext_wakeup_pin_2_mask = 1ULL << ext_wakeup_pin_2;
|
||||
|
||||
printf("Enabling EXT1 wakeup on pins GPIO%d, GPIO%d\n", ext_wakeup_pin_1, ext_wakeup_pin_2);
|
||||
esp_sleep_enable_ext1_wakeup(ext_wakeup_pin_1_mask | ext_wakeup_pin_2_mask, ESP_EXT1_WAKEUP_ANY_HIGH);
|
||||
#endif // CONFIG_EXAMPLE_EXT1_WAKEUP
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_TOUCH_WAKEUP
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
// Initialize touch pad peripheral.
|
||||
// The default fsm mode is software trigger mode.
|
||||
touch_pad_init();
|
||||
// If use touch pad wake up, should set touch sensor FSM mode at 'TOUCH_FSM_MODE_TIMER'.
|
||||
touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER);
|
||||
// Set reference voltage for charging/discharging
|
||||
// In this case, the high reference valtage will be 2.4V - 1V = 1.4V
|
||||
// The low reference voltage will be 0.5
|
||||
// The larger the range, the larger the pulse count value.
|
||||
touch_pad_set_voltage(TOUCH_HVOLT_2V4, TOUCH_LVOLT_0V5, TOUCH_HVOLT_ATTEN_1V);
|
||||
//init RTC IO and mode for touch pad.
|
||||
touch_pad_config(TOUCH_PAD_NUM8, TOUCH_THRESH_NO_USE);
|
||||
touch_pad_config(TOUCH_PAD_NUM9, TOUCH_THRESH_NO_USE);
|
||||
calibrate_touch_pad(TOUCH_PAD_NUM8);
|
||||
calibrate_touch_pad(TOUCH_PAD_NUM9);
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
/* Initialize touch pad peripheral. */
|
||||
touch_pad_init();
|
||||
/* Only support one touch channel in sleep mode. */
|
||||
touch_pad_config(TOUCH_PAD_NUM9);
|
||||
/* Denoise setting at TouchSensor 0. */
|
||||
touch_pad_denoise_t denoise = {
|
||||
/* The bits to be cancelled are determined according to the noise level. */
|
||||
.grade = TOUCH_PAD_DENOISE_BIT4,
|
||||
.cap_level = TOUCH_PAD_DENOISE_CAP_L4,
|
||||
};
|
||||
touch_pad_denoise_set_config(&denoise);
|
||||
touch_pad_denoise_enable();
|
||||
printf("Denoise function init\n");
|
||||
/* Filter setting */
|
||||
touch_filter_config_t filter_info = {
|
||||
.mode = TOUCH_PAD_FILTER_IIR_16,
|
||||
.debounce_cnt = 1, // 1 time count.
|
||||
.noise_thr = 0, // 50%
|
||||
.jitter_step = 4, // use for jitter mode.
|
||||
.smh_lvl = TOUCH_PAD_SMOOTH_IIR_2,
|
||||
};
|
||||
touch_pad_filter_set_config(&filter_info);
|
||||
touch_pad_filter_enable();
|
||||
printf("touch pad filter init %d\n", TOUCH_PAD_FILTER_IIR_8);
|
||||
/* Set sleep touch pad. */
|
||||
touch_pad_sleep_channel_enable(TOUCH_PAD_NUM9, true);
|
||||
touch_pad_sleep_channel_enable_proximity(TOUCH_PAD_NUM9, false);
|
||||
/* Enable touch sensor clock. Work mode is "timer trigger". */
|
||||
touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER);
|
||||
touch_pad_fsm_start();
|
||||
vTaskDelay(100 / portTICK_RATE_MS);
|
||||
/* read sleep touch pad value */
|
||||
uint32_t touch_value;
|
||||
touch_pad_sleep_channel_read_smooth(TOUCH_PAD_NUM9, &touch_value);
|
||||
touch_pad_sleep_set_threshold(TOUCH_PAD_NUM9, touch_value * 0.1); //10%
|
||||
printf("test init: touch pad [%d] slp %d, thresh %d\n",
|
||||
TOUCH_PAD_NUM9, touch_value, (uint32_t)(touch_value * 0.1));
|
||||
#endif
|
||||
printf("Enabling touch pad wakeup\n");
|
||||
esp_sleep_enable_touchpad_wakeup();
|
||||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||
#endif // CONFIG_EXAMPLE_TOUCH_WAKEUP
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_ULP_TEMPERATURE_WAKEUP
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
printf("Enabling ULP wakeup\n");
|
||||
esp_sleep_enable_ulp_wakeup();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
// Isolate GPIO12 pin from external circuits. This is needed for modules
|
||||
// which have an external pull-up resistor on GPIO12 (such as ESP32-WROVER)
|
||||
// to minimize current consumption.
|
||||
rtc_gpio_isolate(GPIO_NUM_12);
|
||||
#endif
|
||||
|
||||
printf("Entering deep sleep\n");
|
||||
gettimeofday(&sleep_enter_time, NULL);
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_ULP_TEMPERATURE_WAKEUP
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
start_ulp_temperature_monitoring();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
esp_deep_sleep_start();
|
||||
}
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_TOUCH_WAKEUP
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
static void calibrate_touch_pad(touch_pad_t pad)
|
||||
{
|
||||
int avg = 0;
|
||||
const size_t calibration_count = 128;
|
||||
for (int i = 0; i < calibration_count; ++i) {
|
||||
uint16_t val;
|
||||
touch_pad_read(pad, &val);
|
||||
avg += val;
|
||||
}
|
||||
avg /= calibration_count;
|
||||
const int min_reading = 300;
|
||||
if (avg < min_reading) {
|
||||
printf("Touch pad #%d average reading is too low: %d (expecting at least %d). "
|
||||
"Not using for deep sleep wakeup.\n", pad, avg, min_reading);
|
||||
touch_pad_config(pad, 0);
|
||||
} else {
|
||||
int threshold = avg - 100;
|
||||
printf("Touch pad #%d average: %d, wakeup threshold set to %d.\n", pad, avg, threshold);
|
||||
touch_pad_config(pad, threshold);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif // CONFIG_EXAMPLE_TOUCH_WAKEUP
|
||||
|
||||
#ifdef CONFIG_EXAMPLE_ULP_TEMPERATURE_WAKEUP
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
static void start_ulp_temperature_monitoring(void)
|
||||
{
|
||||
/*
|
||||
* This ULP program monitors the on-chip temperature sensor and wakes the chip up when
|
||||
* the temperature goes outside of certain window.
|
||||
* When the program runs for the first time, it saves the temperature measurement,
|
||||
* it is considered initial temperature (T0).
|
||||
*
|
||||
* On each subsequent run, temperature measured and compared to T0.
|
||||
* If the measured value is higher than T0 + max_temp_diff or lower than T0 - max_temp_diff,
|
||||
* the chip is woken up from deep sleep.
|
||||
*/
|
||||
|
||||
/* Temperature difference threshold which causes wakeup
|
||||
* With settings here (TSENS_CLK_DIV=2, 8000 cycles),
|
||||
* TSENS measurement is done in units of 0.73 degrees Celsius.
|
||||
* Therefore, max_temp_diff below is equivalent to ~2.2 degrees Celsius.
|
||||
*/
|
||||
const int16_t max_temp_diff = 3;
|
||||
|
||||
// Number of measurements ULP should do per second
|
||||
const uint32_t measurements_per_sec = 5;
|
||||
|
||||
// Allow TSENS to be controlled by the ULP
|
||||
SET_PERI_REG_BITS(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_CLK_DIV, 2, SENS_TSENS_CLK_DIV_S);
|
||||
SET_PERI_REG_BITS(SENS_SAR_MEAS_WAIT2_REG, SENS_FORCE_XPD_SAR, 3, SENS_FORCE_XPD_SAR_S);
|
||||
CLEAR_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_POWER_UP);
|
||||
CLEAR_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_DUMP_OUT);
|
||||
CLEAR_PERI_REG_MASK(SENS_SAR_TSENS_CTRL_REG, SENS_TSENS_POWER_UP_FORCE);
|
||||
|
||||
// Clear the part of RTC_SLOW_MEM reserved for the ULP. Makes debugging easier.
|
||||
memset(RTC_SLOW_MEM, 0, CONFIG_ESP32_ULP_COPROC_RESERVE_MEM);
|
||||
|
||||
// The first word of memory (at data offset) is used to store the initial temperature (T0)
|
||||
// Zero it out here, then ULP will update it on the first run.
|
||||
ulp_data_write(0, 0);
|
||||
// The second word is used to store measurement count, zero it out as well.
|
||||
ulp_data_write(1, 0);
|
||||
|
||||
const ulp_insn_t program[] = {
|
||||
// load data offset into R2
|
||||
I_MOVI(R2, ULP_DATA_OFFSET),
|
||||
// load/increment/store measurement counter using R1
|
||||
I_LD(R1, R2, 1),
|
||||
I_ADDI(R1, R1, 1),
|
||||
I_ST(R1, R2, 1),
|
||||
// enable temperature sensor
|
||||
I_WR_REG(SENS_SAR_MEAS_WAIT2_REG, SENS_FORCE_XPD_SAR_S, SENS_FORCE_XPD_SAR_S + 1, 3),
|
||||
// do temperature measurement and store result in R3
|
||||
I_TSENS(R3, 8000),
|
||||
// disable temperature sensor
|
||||
I_WR_REG(SENS_SAR_MEAS_WAIT2_REG, SENS_FORCE_XPD_SAR_S, SENS_FORCE_XPD_SAR_S + 1, 0),
|
||||
// Save current measurement at offset+2
|
||||
I_ST(R3, R2, 2),
|
||||
// load initial value into R0
|
||||
I_LD(R0, R2, 0),
|
||||
// if threshold value >=1 (i.e. initialized), goto 1
|
||||
M_BGE(1, 1),
|
||||
// otherwise, save the current value as initial (T0)
|
||||
I_MOVR(R0, R3),
|
||||
I_ST(R0, R2, 0),
|
||||
M_LABEL(1),
|
||||
// check if the temperature is greater or equal (T0 + max_temp_diff)
|
||||
// uses R1 as scratch register, difference is saved at offset + 3
|
||||
I_ADDI(R1, R0, max_temp_diff - 1),
|
||||
I_SUBR(R1, R1, R3),
|
||||
I_ST(R1, R2, 3),
|
||||
M_BXF(2),
|
||||
// check if the temperature is less or equal (T0 - max_temp_diff)
|
||||
// uses R1 as scratch register, difference is saved at offset + 4
|
||||
I_SUBI(R1, R0, max_temp_diff - 1),
|
||||
I_SUBR(R1, R3, R1),
|
||||
I_ST(R1, R2, 4),
|
||||
M_BXF(2),
|
||||
// temperature is within (T0 - max_temp_diff; T0 + max_temp_diff)
|
||||
// stop ULP until the program timer starts it again
|
||||
I_HALT(),
|
||||
M_LABEL(2),
|
||||
// temperature is out of bounds
|
||||
// disable ULP program timer
|
||||
I_WR_REG_BIT(RTC_CNTL_STATE0_REG, RTC_CNTL_ULP_CP_SLP_TIMER_EN_S, 0),
|
||||
// initiate wakeup of the SoC
|
||||
I_WAKE(),
|
||||
// stop the ULP program
|
||||
I_HALT()
|
||||
};
|
||||
|
||||
// Load ULP program into RTC_SLOW_MEM, at offset 0
|
||||
size_t size = sizeof(program)/sizeof(ulp_insn_t);
|
||||
ESP_ERROR_CHECK( ulp_process_macros_and_load(0, program, &size) );
|
||||
assert(size < ULP_DATA_OFFSET && "ULP_DATA_OFFSET needs to be greater or equal to the program size");
|
||||
|
||||
// Set ULP wakeup period
|
||||
const uint32_t sleep_cycles = rtc_clk_slow_freq_get_hz() / measurements_per_sec;
|
||||
REG_WRITE(SENS_ULP_CP_SLEEP_CYC0_REG, sleep_cycles);
|
||||
|
||||
// Start ULP
|
||||
ESP_ERROR_CHECK( ulp_run(0) );
|
||||
}
|
||||
#endif // CONFIG_IDF_TARGET_ESP32
|
||||
#endif // CONFIG_EXAMPLE_ULP_TEMPERATURE_WAKEUP
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
CONFIG_ESP32_DEFAULT_CPU_FREQ_80=y
|
||||
CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=80
|
||||
CONFIG_ESP32_ULP_COPROC_ENABLED=y
|
||||
CONFIG_ESP32_ULP_COPROC_RESERVE_MEM=512
|
||||
CONFIG_ESP32_TIME_SYSCALL_USE_RTC_FRC1=y
|
||||
CONFIG_ESP32_RTC_CLK_SRC_INT_RC=y
|
||||
CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP=y
|
||||
CONFIG_EXAMPLE_ULP_TEMPERATURE_WAKEUP=n
|
||||
CONFIG_EXAMPLE_EXT1_WAKEUP=n
|
||||
@@ -0,0 +1,7 @@
|
||||
CONFIG_ESP32_DEFAULT_CPU_FREQ_80=y
|
||||
CONFIG_ESP32_DEFAULT_CPU_FREQ_MHZ=80
|
||||
CONFIG_ESP32_ULP_COPROC_ENABLED=y
|
||||
CONFIG_ESP32_ULP_COPROC_RESERVE_MEM=512
|
||||
CONFIG_ESP32_TIME_SYSCALL_USE_RTC_FRC1=y
|
||||
CONFIG_ESP32_RTC_CLK_SRC_INT_RC=y
|
||||
CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP=y
|
||||
@@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(efuse)
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := efuse
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
# eFuse Example
|
||||
|
||||
## Overview
|
||||
|
||||
This example shows how to use the eFuse API. It demonstrates read and write operations with fields from the common and custom eFuse tables. For more information about eFuse, see the “eFuse Manager” chapter in the documentation and the “eFuse Controller” chapter in ESP32 TRM.
|
||||
|
||||
The eFuse is a single bit of non-volatile memory with the restriction that once an eFuse bit is programmed to 1, it can not be reverted to 0.
|
||||
The eFuse fields can be useful to store: device types, serial numbers, some system variables, etc.
|
||||
|
||||
Note that the bits already written cannot be reset to the original state. For debugging purposes, the `CONFIG_EFUSE_VIRTUAL` option is provided. This option will block physical burning. All work happens with an array in RAM.
|
||||
In this example, all write operations are wrapped in `#ifdef CONFIG_EFUSE_VIRTUAL ... # endif` to prevent accidental burn while testing the features.
|
||||
|
||||
## How to use example
|
||||
|
||||
This example first reads the eFuse field, then writes a value, then reads it again. The program can be seen in the logs.
|
||||
|
||||
The program uses two eFuse tables:
|
||||
|
||||
* The common table (for IDF purposes).
|
||||
* The custom table (for custom purposes).
|
||||
|
||||
The custom table has five fields:
|
||||
|
||||
```
|
||||
MODULE_VERSION, EFUSE_BLK3, 56, 8, Module version
|
||||
DEVICE_ROLE, EFUSE_BLK3, 64, 3, Device role
|
||||
SETTING_1, EFUSE_BLK3, 67, 6, Setting 1
|
||||
SETTING_2, EFUSE_BLK3, 73, 5, Setting 2
|
||||
CUSTOM_SECURE_VERSION, EFUSE_BLK3, 78, 16, Custom secure version
|
||||
```
|
||||
|
||||
### Hardware Required
|
||||
|
||||
To run this example, you need to use the ESP32 developer board. If your ESP32 has the `None` coding scheme of eFuse, then write and read operations have no limitations. But if it has the `3/4` or `RS` coding scheme, the writing may lead to an error due to the fact that some coded value was written earlier. A simple way to find out what coding scheme your chip has, you can see in the log while esptool is working or the log of the program:
|
||||
|
||||
```
|
||||
esptool.py v2.7-dev
|
||||
Serial port /dev/ttyUSB0
|
||||
Connecting........_____.....__
|
||||
Detecting chip type... ESP32
|
||||
Chip is ESP32-PICO-D4 (revision 1)
|
||||
Features: WiFi, BT, Dual Core, Embedded Flash, Coding Scheme None
|
||||
MAC: d8:a0:1d:40:ac:90
|
||||
```
|
||||
|
||||
The esptool also has a command to get general information about the chip `esptool.py chip_id`:
|
||||
|
||||
```
|
||||
esptool.py v2.8-dev
|
||||
Found 1 serial ports
|
||||
Serial port /dev/ttyUSB0
|
||||
Connecting........_
|
||||
Detecting chip type... ESP32
|
||||
Chip is ESP32-PICO-D4 (revision 1)
|
||||
Features: WiFi, BT, Dual Core, Embedded Flash, Coding Scheme None
|
||||
Crystal is 40MHz
|
||||
MAC: d8:a0:1d:40:ac:90
|
||||
Uploading stub...
|
||||
Running stub...
|
||||
Stub running...
|
||||
Warning: ESP32 has no Chip ID. Reading MAC instead.
|
||||
MAC: d8:a0:1d:40:ac:90
|
||||
Hard resetting via RTS pin...
|
||||
```
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
In this example, there is `sdkconfig.default`, which already includes all the necessary settings. No need to configure.
|
||||
|
||||
* `CONFIG_EFUSE_CUSTOM_TABLE=y` - It allows using the custom table.
|
||||
* `CONFIG_EFUSE_VIRTUAL=y` - All read and writes operations are redirected to RAM instead of eFuse registers. Not really burning eFuses.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
|
||||
## Example Output
|
||||
|
||||
For ``None`` coding scheme:
|
||||
|
||||
|
||||
```
|
||||
I (0) cpu_start: Starting scheduler on APP CPU.
|
||||
I (323) example: Coding Scheme NONE
|
||||
I (323) example: Uses common and custom tables
|
||||
I (333) example: read efuse fields
|
||||
I (333) example: 1. read MAC address: d8:a0:1d:40:ac:90
|
||||
I (343) example: 2. read secure_version: 0
|
||||
I (343) example: 3. read custom fields
|
||||
I (353) example: module_version = 0
|
||||
I (353) example: device_role = None
|
||||
I (363) example: setting_1 = 0
|
||||
I (363) example: setting_2 = 0
|
||||
I (363) example: custom_secure_version = 0
|
||||
W (373) example: This example does not burn any efuse in reality only virtually
|
||||
W (383) example: Write operations in efuse fields are performed virtually
|
||||
I (383) example: write custom efuse fields
|
||||
W (393) efuse: Virtual efuses enabled: Not really burning eFuses
|
||||
W (403) efuse: Virtual efuses enabled: Not really burning eFuses
|
||||
W (403) efuse: Virtual efuses enabled: Not really burning eFuses
|
||||
W (413) efuse: Virtual efuses enabled: Not really burning eFuses
|
||||
W (423) efuse: Virtual efuses enabled: Not really burning eFuses
|
||||
I (423) example: module_version = 1
|
||||
I (433) example: device_role = Slave
|
||||
I (433) example: setting_1 = 3
|
||||
I (433) example: setting_2 = 4
|
||||
I (443) example: custom_secure_version = 5
|
||||
I (443) example: Done
|
||||
```
|
||||
|
||||
And for ``3/4`` coding scheme:
|
||||
|
||||
|
||||
```
|
||||
I (0) cpu_start: Starting scheduler on APP CPU.
|
||||
I (327) example: Coding Scheme 3/4
|
||||
I (327) example: Uses common and custom tables
|
||||
I (337) example: read efuse fields
|
||||
I (337) example: 1. read MAC address: 84:0d:8e:18:8e:44
|
||||
I (347) example: 2. read secure_version: 0
|
||||
I (347) example: 3. read custom fields
|
||||
I (357) example: module_version = 0
|
||||
I (357) example: device_role = None
|
||||
I (367) example: setting_1 = 0
|
||||
I (367) example: setting_2 = 0
|
||||
I (367) example: custom_secure_version = 0
|
||||
W (377) example: This example does not burn any efuse in reality only virtually
|
||||
W (387) example: Write operations in efuse fields are performed virtually
|
||||
I (387) example: In the case of 3/4 coding scheme, you cannot write efuse fields separately
|
||||
I (397) example: You should use the batch mode of writing fields for this
|
||||
I (407) efuse: Batch mode of writing fields is enabled
|
||||
W (417) efuse: Virtual efuses enabled: Not really burning eFuses
|
||||
I (417) efuse: Batch mode of writing fields is disabled
|
||||
I (427) example: module_version = 1
|
||||
I (427) example: device_role = Slave
|
||||
I (437) example: setting_1 = 3
|
||||
I (437) example: setting_2 = 4
|
||||
I (437) example: custom_secure_version = 5
|
||||
I (447) example: Done
|
||||
```
|
||||
|
||||
|
||||
### How to create the new custom table
|
||||
|
||||
In the main folder of the example there is a custom eFuse table - `esp_efuse_custom_table.csv`. You can add there some fields and run `idf.py efuse_custom_table`, it will generate all the necessary source files. If all is ok then you will see:
|
||||
|
||||
```
|
||||
...
|
||||
Parsing efuse CSV input file /home/kostia/esp/esp-idf/components/efuse/esp32/esp_efuse_table.csv ...
|
||||
Verifying efuse table...
|
||||
Parsing efuse CSV input file /home/kostia/esp/esp-idf/examples/system/efuse/main/esp_efuse_custom_table.csv ...
|
||||
Verifying efuse table...
|
||||
Creating efuse *.h file /home/kostia/esp/esp-idf/examples/system/efuse/main/include/esp_efuse_custom_table.h ...
|
||||
Creating efuse *.c file /home/kostia/esp/esp-idf/examples/system/efuse/main/esp_efuse_custom_table.c ...
|
||||
Max number of bits in BLK 192
|
||||
```
|
||||
|
||||
If the following fields were not changed in the CSV file: field_name, efuse_block, bit_start or bit_count, then a new generation will not occur, and you will see this message `Source files do not require updating correspond to csv file`.
|
||||
|
||||
This command will create into main folder two files: `esp_efuse_custom_table.c` and `include/esp_efuse_custom_table.h`.
|
||||
|
||||
Do not forget to add these files for the Cmake build system:
|
||||
|
||||
```
|
||||
idf_component_register(SRCS "efuse_main.c" "esp_efuse_custom_table.c"
|
||||
INCLUDE_DIRS "." "include")
|
||||
```
|
||||
|
||||
If you are not sure which eFuse bits are free you can run `idf.py show_efuse_table`. It prints a sorted list of the common and custom tables.
|
||||
@@ -0,0 +1,36 @@
|
||||
from __future__ import unicode_literals
|
||||
import re
|
||||
import ttfw_idf
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag='Example_GENERIC')
|
||||
def test_examples_efuse(env, extra_data):
|
||||
|
||||
dut = env.get_dut('efuse', 'examples/system/efuse')
|
||||
dut.start_app()
|
||||
|
||||
dut.expect_all(re.compile(r'example: Coding Scheme (3/4)|(NONE)|(REPEAT)|(RS \(Reed-Solomon coding\))'),
|
||||
'example: read efuse fields',
|
||||
re.compile(r'example: 1. read MAC address: {}'.format(r':'.join((r'[0-9a-f]{2}',) * 6))),
|
||||
'example: 2. read secure_version: 0',
|
||||
'example: 3. read custom fields',
|
||||
'example: module_version = 0',
|
||||
'example: device_role = None',
|
||||
'example: setting_1 = 0',
|
||||
'example: setting_2 = 0',
|
||||
'example: custom_secure_version = 0',
|
||||
'example: This example does not burn any efuse in reality only virtually',
|
||||
'example: Write operations in efuse fields are performed virtually',
|
||||
'example: write custom efuse fields',
|
||||
'efuse: Virtual efuses enabled: Not really burning eFuses',
|
||||
'example: module_version = 1',
|
||||
'example: device_role = Slave',
|
||||
'example: setting_1 = 3',
|
||||
'example: setting_2 = 4',
|
||||
'example: custom_secure_version = 5',
|
||||
'example: Done',
|
||||
timeout=30)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_examples_efuse()
|
||||
@@ -0,0 +1,4 @@
|
||||
idf_component_register(SRCS "efuse_main.c"
|
||||
"esp_efuse_custom_table.c"
|
||||
INCLUDE_DIRS "."
|
||||
"include")
|
||||
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
/* efuse example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_efuse.h"
|
||||
#include "esp_efuse_table.h"
|
||||
#include "esp_efuse_custom_table.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
static const char* TAG = "example";
|
||||
|
||||
|
||||
typedef struct {
|
||||
uint8_t module_version; /*!< Module version: length 8 bits */
|
||||
uint8_t device_role; /*!< Device role: length 3 bits */
|
||||
uint8_t setting_1; /*!< Setting 1: length 6 bits */
|
||||
uint8_t setting_2; /*!< Setting 2: length 5 bits */
|
||||
size_t custom_secure_version; /*!< Custom secure version: length 16 bits */
|
||||
uint16_t reserv; /*!< Reserv */
|
||||
} device_desc_t;
|
||||
|
||||
|
||||
static void print_device_desc(device_desc_t *desc)
|
||||
{
|
||||
ESP_LOGI(TAG, "module_version = %d", desc->module_version);
|
||||
if (desc->device_role == 0) {
|
||||
ESP_LOGI(TAG, "device_role = None");
|
||||
} else if (desc->device_role == 1) {
|
||||
ESP_LOGI(TAG, "device_role = Master");
|
||||
} else if (desc->device_role == 2) {
|
||||
ESP_LOGI(TAG, "device_role = Slave");
|
||||
} else {
|
||||
ESP_LOGI(TAG, "device_role = Not supported");
|
||||
}
|
||||
ESP_LOGI(TAG, "setting_1 = %d", desc->setting_1);
|
||||
ESP_LOGI(TAG, "setting_2 = %d", desc->setting_2);
|
||||
ESP_LOGI(TAG, "custom_secure_version = %d", desc->custom_secure_version);
|
||||
}
|
||||
|
||||
|
||||
static void read_device_desc_efuse_fields(device_desc_t *desc)
|
||||
{
|
||||
ESP_ERROR_CHECK(esp_efuse_read_field_blob(ESP_EFUSE_MODULE_VERSION, &desc->module_version, 8));
|
||||
ESP_ERROR_CHECK(esp_efuse_read_field_blob(ESP_EFUSE_DEVICE_ROLE, &desc->device_role, 3));
|
||||
ESP_ERROR_CHECK(esp_efuse_read_field_blob(ESP_EFUSE_SETTING_1, &desc->setting_1, 6));
|
||||
ESP_ERROR_CHECK(esp_efuse_read_field_blob(ESP_EFUSE_SETTING_2, &desc->setting_2, 5));
|
||||
ESP_ERROR_CHECK(esp_efuse_read_field_cnt(ESP_EFUSE_CUSTOM_SECURE_VERSION, &desc->custom_secure_version));
|
||||
print_device_desc(desc);
|
||||
}
|
||||
|
||||
|
||||
static void read_efuse_fields(device_desc_t *desc)
|
||||
{
|
||||
ESP_LOGI(TAG, "read efuse fields");
|
||||
|
||||
uint8_t mac[6];
|
||||
ESP_ERROR_CHECK(esp_efuse_read_field_blob(ESP_EFUSE_MAC_FACTORY, &mac, sizeof(mac) * 8));
|
||||
ESP_LOGI(TAG, "1. read MAC address: %02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
|
||||
size_t secure_version = 0;
|
||||
ESP_ERROR_CHECK(esp_efuse_read_field_cnt(ESP_EFUSE_SECURE_VERSION, &secure_version));
|
||||
ESP_LOGI(TAG, "2. read secure_version: %d", secure_version);
|
||||
|
||||
ESP_LOGI(TAG, "3. read custom fields");
|
||||
read_device_desc_efuse_fields(desc);
|
||||
}
|
||||
|
||||
|
||||
static void write_efuse_fields(device_desc_t *desc, esp_efuse_coding_scheme_t coding_scheme)
|
||||
{
|
||||
#ifdef CONFIG_EFUSE_VIRTUAL
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
const esp_efuse_coding_scheme_t coding_scheme_for_batch_mode = EFUSE_CODING_SCHEME_3_4;
|
||||
#else
|
||||
const esp_efuse_coding_scheme_t coding_scheme_for_batch_mode = EFUSE_CODING_SCHEME_RS;
|
||||
#endif
|
||||
|
||||
ESP_LOGI(TAG, "write custom efuse fields");
|
||||
if (coding_scheme == coding_scheme_for_batch_mode) {
|
||||
ESP_LOGI(TAG, "In the case of 3/4 or RS coding scheme, you cannot write efuse fields separately");
|
||||
ESP_LOGI(TAG, "You should use the batch mode of writing fields for this");
|
||||
ESP_ERROR_CHECK(esp_efuse_batch_write_begin());
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(esp_efuse_write_field_blob(ESP_EFUSE_MODULE_VERSION, &desc->module_version, 8));
|
||||
ESP_ERROR_CHECK(esp_efuse_write_field_blob(ESP_EFUSE_DEVICE_ROLE, &desc->device_role, 3));
|
||||
ESP_ERROR_CHECK(esp_efuse_write_field_blob(ESP_EFUSE_SETTING_1, &desc->setting_1, 6));
|
||||
ESP_ERROR_CHECK(esp_efuse_write_field_blob(ESP_EFUSE_SETTING_2, &desc->setting_2, 5));
|
||||
ESP_ERROR_CHECK(esp_efuse_write_field_cnt(ESP_EFUSE_CUSTOM_SECURE_VERSION, desc->custom_secure_version));
|
||||
|
||||
if (coding_scheme == coding_scheme_for_batch_mode) {
|
||||
ESP_ERROR_CHECK(esp_efuse_batch_write_commit());
|
||||
}
|
||||
#endif // CONFIG_EFUSE_VIRTUAL
|
||||
}
|
||||
|
||||
|
||||
static esp_efuse_coding_scheme_t get_coding_scheme(void)
|
||||
{
|
||||
// The coding scheme is used for EFUSE_BLK1, EFUSE_BLK2 and EFUSE_BLK3.
|
||||
// We use EFUSE_BLK3 (custom block) to verify it.
|
||||
esp_efuse_coding_scheme_t coding_scheme = esp_efuse_get_coding_scheme(EFUSE_BLK3);
|
||||
if (coding_scheme == EFUSE_CODING_SCHEME_NONE) {
|
||||
ESP_LOGI(TAG, "Coding Scheme NONE");
|
||||
#if CONFIG_IDF_TARGET_ESP32
|
||||
} else if (coding_scheme == EFUSE_CODING_SCHEME_3_4) {
|
||||
ESP_LOGI(TAG, "Coding Scheme 3/4");
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Coding Scheme REPEAT");
|
||||
}
|
||||
#else
|
||||
} else if (coding_scheme == EFUSE_CODING_SCHEME_RS) {
|
||||
ESP_LOGI(TAG, "Coding Scheme RS (Reed-Solomon coding)");
|
||||
}
|
||||
#endif
|
||||
return coding_scheme;
|
||||
}
|
||||
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_efuse_coding_scheme_t coding_scheme = get_coding_scheme();
|
||||
|
||||
device_desc_t device_desc = { 0 };
|
||||
read_efuse_fields(&device_desc);
|
||||
|
||||
ESP_LOGW(TAG, "This example does not burn any efuse in reality only virtually");
|
||||
#ifdef CONFIG_EFUSE_VIRTUAL
|
||||
ESP_LOGW(TAG, "Write operations in efuse fields are performed virtually");
|
||||
if (device_desc.device_role == 0) {
|
||||
device_desc.module_version = 1;
|
||||
device_desc.device_role = 2;
|
||||
device_desc.setting_1 = 3;
|
||||
device_desc.setting_2 = 4;
|
||||
device_desc.custom_secure_version = 5;
|
||||
write_efuse_fields(&device_desc, coding_scheme);
|
||||
read_device_desc_efuse_fields(&device_desc);
|
||||
}
|
||||
#else
|
||||
ESP_LOGW(TAG, "The part of the code that writes efuse fields is disabled");
|
||||
#endif
|
||||
|
||||
ESP_LOGI(TAG, "Done");
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at",
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License
|
||||
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_efuse.h"
|
||||
#include <assert.h>
|
||||
#include "esp_efuse_custom_table.h"
|
||||
|
||||
// md5_digest_table 584317af6a850ee16ef6206c139d6cf5
|
||||
// This file was generated from the file esp_efuse_custom_table.csv. DO NOT CHANGE THIS FILE MANUALLY.
|
||||
// If you want to change some fields, you need to change esp_efuse_custom_table.csv file
|
||||
// then run `efuse_common_table` or `efuse_custom_table` command it will generate this file.
|
||||
// To show efuse_table run the command 'show_efuse_table'.
|
||||
|
||||
#define MAX_BLK_LEN CONFIG_EFUSE_MAX_BLK_LEN
|
||||
|
||||
// The last free bit in the block is counted over the entire file.
|
||||
#define LAST_FREE_BIT_BLK3 94
|
||||
|
||||
_Static_assert(LAST_FREE_BIT_BLK3 <= MAX_BLK_LEN, "The eFuse table does not match the coding scheme. Edit the table and restart the efuse_common_table or efuse_custom_table command to regenerate the new files.");
|
||||
|
||||
static const esp_efuse_desc_t MODULE_VERSION[] = {
|
||||
{EFUSE_BLK3, 56, 8}, // Module version,
|
||||
};
|
||||
|
||||
static const esp_efuse_desc_t DEVICE_ROLE[] = {
|
||||
{EFUSE_BLK3, 64, 3}, // Device role,
|
||||
};
|
||||
|
||||
static const esp_efuse_desc_t SETTING_1[] = {
|
||||
{EFUSE_BLK3, 67, 6}, // Setting 1,
|
||||
};
|
||||
|
||||
static const esp_efuse_desc_t SETTING_2[] = {
|
||||
{EFUSE_BLK3, 73, 5}, // Setting 2,
|
||||
};
|
||||
|
||||
static const esp_efuse_desc_t CUSTOM_SECURE_VERSION[] = {
|
||||
{EFUSE_BLK3, 78, 16}, // Custom secure version,
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const esp_efuse_desc_t* ESP_EFUSE_MODULE_VERSION[] = {
|
||||
&MODULE_VERSION[0], // Module version
|
||||
NULL
|
||||
};
|
||||
|
||||
const esp_efuse_desc_t* ESP_EFUSE_DEVICE_ROLE[] = {
|
||||
&DEVICE_ROLE[0], // Device role
|
||||
NULL
|
||||
};
|
||||
|
||||
const esp_efuse_desc_t* ESP_EFUSE_SETTING_1[] = {
|
||||
&SETTING_1[0], // Setting 1
|
||||
NULL
|
||||
};
|
||||
|
||||
const esp_efuse_desc_t* ESP_EFUSE_SETTING_2[] = {
|
||||
&SETTING_2[0], // Setting 2
|
||||
NULL
|
||||
};
|
||||
|
||||
const esp_efuse_desc_t* ESP_EFUSE_CUSTOM_SECURE_VERSION[] = {
|
||||
&CUSTOM_SECURE_VERSION[0], // Custom secure version
|
||||
NULL
|
||||
};
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# field_name, | efuse_block, | bit_start, | bit_count, |comment #
|
||||
# | (EFUSE_BLK0 | (0..255) | (1..-) | #
|
||||
# | EFUSE_BLK1 | |MAX_BLK_LEN*| #
|
||||
# | EFUSE_BLK2 | | | #
|
||||
# | EFUSE_BLK3) | | | #
|
||||
##########################################################################
|
||||
# *) The value MAX_BLK_LEN depends on CONFIG_EFUSE_MAX_BLK_LEN, will be replaced with "None" - 256. "3/4" - 192. "REPEAT" - 128.
|
||||
# !!!!!!!!!!! #
|
||||
# After editing this file, run the command manually "make efuse_common_table" or "idf.py efuse_common_table"
|
||||
# this will generate new source files, next rebuild all the sources.
|
||||
# !!!!!!!!!!! #
|
||||
|
||||
|
||||
MODULE_VERSION, EFUSE_BLK3, 56, 8, Module version
|
||||
DEVICE_ROLE, EFUSE_BLK3, 64, 3, Device role
|
||||
SETTING_1, EFUSE_BLK3, 67, 6, Setting 1
|
||||
SETTING_2, EFUSE_BLK3, 73, 5, Setting 2
|
||||
CUSTOM_SECURE_VERSION, EFUSE_BLK3, 78, 16, Custom secure version
|
||||
|
Can't render this file because it contains an unexpected character in line 7 and column 87.
|
@@ -0,0 +1,36 @@
|
||||
// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at",
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
||||
// md5_digest_table 584317af6a850ee16ef6206c139d6cf5
|
||||
// This file was generated from the file esp_efuse_custom_table.csv. DO NOT CHANGE THIS FILE MANUALLY.
|
||||
// If you want to change some fields, you need to change esp_efuse_custom_table.csv file
|
||||
// then run `efuse_common_table` or `efuse_custom_table` command it will generate this file.
|
||||
// To show efuse_table run the command 'show_efuse_table'.
|
||||
|
||||
|
||||
extern const esp_efuse_desc_t* ESP_EFUSE_MODULE_VERSION[];
|
||||
extern const esp_efuse_desc_t* ESP_EFUSE_DEVICE_ROLE[];
|
||||
extern const esp_efuse_desc_t* ESP_EFUSE_SETTING_1[];
|
||||
extern const esp_efuse_desc_t* ESP_EFUSE_SETTING_2[];
|
||||
extern const esp_efuse_desc_t* ESP_EFUSE_CUSTOM_SECURE_VERSION[];
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
CONFIG_EFUSE_CUSTOM_TABLE=y
|
||||
CONFIG_EFUSE_VIRTUAL=y
|
||||
@@ -0,0 +1,2 @@
|
||||
CONFIG_EFUSE_CUSTOM_TABLE=y
|
||||
CONFIG_EFUSE_VIRTUAL=y
|
||||
@@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(default_event_loop)
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := default_event_loop
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
# Default Event Loop Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
Note: Should users need to create their own event loops, refer to the **user event loops** [documentation](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/esp_event.html#using-esp-event-apis) and [example](https://github.com/espressif/esp-idf/tree/master/examples/system/esp_event/user_event_loops).
|
||||
|
||||
The [**default event loop**](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/esp_event.html#default-event-loop) is an [event loop](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/esp_event.html#) the system uses to post and handle events (e.g. Wi-Fi events). This example demonstrates the usage of the following default event loop features:
|
||||
|
||||
### Declaring and Defining Events
|
||||
|
||||
This example shows the typical setup of having the event base and event IDs declared in a header file, and having the definitions in a source file. Declaration of the event base makes use of the macro `ESP_EVENT_DECLARE_BASE()`, whilst the event IDs are declared as an `enum` (see `event_source.h`). The source file `main.c` holds the definition of the event base using the `ESP_EVENT_DEFINE_BASE()` macro.
|
||||
|
||||
### Creating the Default Event Loop
|
||||
|
||||
This example illustrates the creation of the default event loop using the API function `esp_event_loop_create_default()`.
|
||||
|
||||
### Posting Events to the Default Event Loop
|
||||
|
||||
Simply put, posting an event to a loop is the act of queueing its handlers for execution. For the default loop, this is done using the API `esp_event_post()`. The ability to pass event-specific data to the handler is also demonstrated.
|
||||
|
||||
### Handler Registration/Unregistration
|
||||
|
||||
This example demonstrates handler registration to the default event loop using `esp_event_handler_register` for (1) specific events, (2) **any** event under a certain base, and (3) **any** event. This also shows the possbility of registering multiple handlers to the same event as well as registering one handler to the same event multiple times.
|
||||
|
||||
Unregistering a handler is done using `esp_event_handler_unregister()`. Unregistering a handler means that it no longer executes even when the event it was previously registered to gets posted to the loop.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
This example should be able to run on any commonly available ESP32 development board.
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
The example should have the following log output:
|
||||
|
||||
```
|
||||
I (328) default_event_loop: setting up
|
||||
I (338) default_event_loop: starting event sources
|
||||
I (338) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STARTED: posting to default loop
|
||||
I (338) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: posting to default loop, 1 out of 5
|
||||
I (358) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STARTED: timer_started_handler, instance 0
|
||||
I (368) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STARTED: timer_started_handler, instance 1
|
||||
I (378) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STARTED: timer_started_handler_2
|
||||
I (388) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STARTED: timer_any_handler
|
||||
I (388) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STARTED: all_event_handler
|
||||
I (398) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: task_iteration_handler, executed 1 times
|
||||
I (408) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: all_event_handler
|
||||
I (858) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: posting to default loop, 2 out of 5
|
||||
I (858) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: task_iteration_handler, executed 2 times
|
||||
I (858) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: all_event_handler
|
||||
I (1338) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: posting to default loop
|
||||
I (1338) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: timer_expiry_handler, executed 1 out of 3 times
|
||||
I (1348) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: timer_any_handler
|
||||
I (1358) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: all_event_handler
|
||||
I (1358) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: posting to default loop, 3 out of 5
|
||||
I (1368) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: unregistering task_iteration_handler
|
||||
I (1368) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: task_iteration_handler, executed 3 times
|
||||
I (1388) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: all_event_handler
|
||||
I (1898) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: posting to default loop, 4 out of 5
|
||||
I (1898) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: all_event_handler
|
||||
I (2338) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: posting to default loop
|
||||
I (2338) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: timer_expiry_handler, executed 2 out of 3 times
|
||||
I (2348) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: timer_any_handler
|
||||
I (2358) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: all_event_handler
|
||||
I (2398) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: posting to default loop, 5 out of 5
|
||||
I (2398) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: all_event_handler
|
||||
I (3338) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: posting to default loop
|
||||
I (3338) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STOPPED: posting to default loop
|
||||
I (3348) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: timer_expiry_handler, executed 3 out of 3 times
|
||||
I (3358) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: timer_any_handler
|
||||
I (3358) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: all_event_handler
|
||||
I (3368) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STOPPED: timer_stopped_handler
|
||||
I (3378) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STOPPED: deleted timer event source
|
||||
I (3388) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STOPPED: timer_any_handler
|
||||
I (3398) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STOPPED: all_event_handler
|
||||
I (3398) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: deleting task event source
|
||||
```
|
||||
|
||||
## Example Breakdown
|
||||
|
||||
### Setting of Event Sources
|
||||
|
||||
This example uses two event sources:
|
||||
|
||||
- A periodic timer. An event is raised when (1) the timer is started (2) the timer period expires and (3) the timer is stopped
|
||||
- A task with a loop inside. An event is raised for the when (1) the loop iterates.
|
||||
|
||||
All of the events mentioned above have their own specific handler, however there are the following additional handlers.
|
||||
|
||||
- One handler executes when **any** event under the periodic timer event is posted
|
||||
- The other handler executes if **any** event is posted.
|
||||
|
||||
The number of periodic timer expiries and loop iterations are limited. When the limit for the number of timer expiries is reached, the timer is stopped. When the limit for the number of iterations is reached, the task is deleted. In the case of the loop iteration, there is another limit: the number of iterations for when its handler will be unregistered.
|
||||
|
||||
### Step-by-Step Explanation
|
||||
|
||||
The following text explains the important points of the [sample log output](#Example-Output).
|
||||
|
||||
#### 1. Setting up of default event loop and event handlers
|
||||
|
||||
```
|
||||
I (297) default_event_loop: setting up
|
||||
```
|
||||
At this stage the default event loop is created, and the handlers for the different events are registered.
|
||||
|
||||
|
||||
#### 2. Posting to the event loop
|
||||
|
||||
```
|
||||
I (276) default_event_loop: starting event sources
|
||||
I (276) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STARTED: posting to default loop
|
||||
I (276) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: posting to default loop, 1 out of 5
|
||||
```
|
||||
The two event sources are started. The respective events are posted to the default event loop.
|
||||
|
||||
#### 3. Execution of handlers
|
||||
|
||||
```
|
||||
I (358) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STARTED: timer_started_handler, instance 0
|
||||
I (368) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STARTED: timer_started_handler, instance 1
|
||||
I (378) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STARTED: timer_started_handler_2
|
||||
I (306) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STARTED: timer_any_handler
|
||||
I (316) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STARTED: all_event_handler
|
||||
...
|
||||
I (326) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: task_iteration_handler, executed 1 times
|
||||
I (336) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: all_event_handler
|
||||
```
|
||||
The handlers are executed for the events that were posted in **(2)**. Note how `timer_started_handler()` is called twice on the same event post. This shows the ability to register a single event handler to a single event multiple times. Note also how `timer_started_handler_2()` is called on the same event post. This shows that multiple handlers can be registered to a single event.
|
||||
In addition to the event-specific handlers `timer_started_handler()` (registered twice), `timer_started_handler_2()` and `task_iteration_handler()`, the `timer_any_handler()` and `all_event_handler()` are also executed.
|
||||
|
||||
The `timer_any_handler()` executes for **any** timer event. It can be seen executing for the timer expiry and timer stopped events in the subsequent parts of the log.
|
||||
|
||||
On the other hand, `all_event_handler()` executes for **any** event, hence why it executes for both ``TIMER_EVENTS:TIMER_EVENT_STARTED`` and ``TASK_EVENTS:TASK_ITERATION_EVENT``.
|
||||
|
||||
For both the timer and task events, notice that the handlers are executed in the same order they are registered in the code. This is a guarantee that the `esp_event` library provides.
|
||||
|
||||
The subsequent lines of the log follows the same pattern: the event is posted to the loop and the handlers are executed.
|
||||
|
||||
#### 4. Unregistering the `task_iteration_handler()`
|
||||
|
||||
```
|
||||
...
|
||||
I (1316) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: unregistering task_iteration_handler
|
||||
```
|
||||
At this point in the execution the handler `task_iteration_handler()` is unregistered, therefore it no longer executes when the event ``TASK_EVENTS:TASK_ITERATION_EVENT`` is posted.
|
||||
```
|
||||
I (1867) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: posting to default loop, 4 out of 5
|
||||
I (1867) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: all_event_handler
|
||||
...
|
||||
I (1846) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: posting to default loop, 4 out of 5
|
||||
I (1846) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: all_event_handle
|
||||
```
|
||||
The iteration event continues to get posted, but only the `all_event_handler()` gets executed.
|
||||
|
||||
#### 5. Iteration Limit
|
||||
|
||||
```
|
||||
...
|
||||
I (3276) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: posting to default loop
|
||||
I (3276) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STOPPED: posting to default loop
|
||||
I (3286) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: timer_expiry_handler, executed 3 out of 3 times
|
||||
I (3296) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: timer_any_handler
|
||||
I (3306) default_event_loop: TIMER_EVENTS:TIMER_EVENT_EXPIRY: all_event_handler
|
||||
I (3316) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STOPPED: timer_stopped_handler
|
||||
I (3326) default_event_loop: TIMER_EVENTS:TIMER_EVENT_STOPPED: deleted timer event source
|
||||
```
|
||||
When the periodic timer expiry limit is reached, the event ``TIMER_EVENTS:TIMER_EVENT_STOPPED`` is posted to the loop. The periodic timer is consequently deleted in the handler `timer_stopped_handler()`.
|
||||
|
||||
```
|
||||
...
|
||||
I (3346) default_event_loop: TASK_EVENTS:TASK_ITERATION_EVENT: deleting task event source
|
||||
...
|
||||
```
|
||||
The task containing the loop that posts iteration events also gets deleted. The example ends at this point.
|
||||
@@ -0,0 +1,93 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import ttfw_idf
|
||||
|
||||
# Timer events
|
||||
TIMER_EVENT_LIMIT = 3
|
||||
|
||||
TIMER_EXPIRY_HANDLING = "TIMER_EVENTS:TIMER_EVENT_EXPIRY: timer_expiry_handler, executed {} out of " + str(TIMER_EVENT_LIMIT) + " times"
|
||||
|
||||
# Task events
|
||||
TASK_ITERATION_LIMIT = 5
|
||||
TASK_UNREGISTRATION_LIMIT = 3
|
||||
|
||||
TASK_ITERATION_POST = "TASK_EVENTS:TASK_ITERATION_EVENT: posting to default loop, {} out of " + str(TASK_ITERATION_LIMIT)
|
||||
TASK_ITERATION_HANDLING = "TASK_EVENTS:TASK_ITERATION_EVENT: task_iteration_handler, executed {} times"
|
||||
|
||||
|
||||
def _test_timer_events(dut):
|
||||
dut.start_app()
|
||||
|
||||
print("Checking timer events posting and handling")
|
||||
|
||||
dut.expect("setting up")
|
||||
dut.expect("starting event sources")
|
||||
|
||||
print("Finished setup")
|
||||
|
||||
dut.expect("TIMER_EVENTS:TIMER_EVENT_STARTED: posting to default loop")
|
||||
print("Posted timer started event")
|
||||
dut.expect("TIMER_EVENTS:TIMER_EVENT_STARTED: timer_started_handler, instance 0")
|
||||
dut.expect("TIMER_EVENTS:TIMER_EVENT_STARTED: timer_started_handler, instance 1")
|
||||
dut.expect("TIMER_EVENTS:TIMER_EVENT_STARTED: timer_started_handler_2")
|
||||
dut.expect("TIMER_EVENTS:TIMER_EVENT_STARTED: timer_any_handler")
|
||||
dut.expect("TIMER_EVENTS:TIMER_EVENT_STARTED: all_event_handler")
|
||||
print("Handled timer started event")
|
||||
|
||||
for expiries in range(1, TIMER_EVENT_LIMIT + 1):
|
||||
dut.expect("TIMER_EVENTS:TIMER_EVENT_EXPIRY: posting to default loop")
|
||||
print("Posted timer expiry event {} out of {}".format(expiries, TIMER_EVENT_LIMIT))
|
||||
|
||||
if expiries >= TIMER_EVENT_LIMIT:
|
||||
dut.expect("TIMER_EVENTS:TIMER_EVENT_STOPPED: posting to default loop")
|
||||
print("Posted timer stopped event")
|
||||
|
||||
dut.expect(TIMER_EXPIRY_HANDLING.format(expiries))
|
||||
dut.expect("TIMER_EVENTS:TIMER_EVENT_EXPIRY: timer_any_handler")
|
||||
dut.expect("TIMER_EVENTS:TIMER_EVENT_EXPIRY: all_event_handler")
|
||||
|
||||
print("Handled timer expiry event {} out of {}".format(expiries, TIMER_EVENT_LIMIT))
|
||||
|
||||
dut.expect("TIMER_EVENTS:TIMER_EVENT_STOPPED: timer_stopped_handler")
|
||||
dut.expect("TIMER_EVENTS:TIMER_EVENT_STOPPED: deleted timer event source")
|
||||
print("Handled timer stopped event")
|
||||
|
||||
|
||||
def _test_iteration_events(dut):
|
||||
dut.start_app()
|
||||
|
||||
print("Checking iteration events posting and handling")
|
||||
dut.expect("setting up")
|
||||
dut.expect("starting event sources")
|
||||
print("Finished setup")
|
||||
|
||||
for iteration in range(1, TASK_ITERATION_LIMIT + 1):
|
||||
dut.expect(TASK_ITERATION_POST.format(iteration))
|
||||
print("Posted iteration {} out of {}".format(iteration, TASK_ITERATION_LIMIT))
|
||||
|
||||
if iteration < TASK_UNREGISTRATION_LIMIT:
|
||||
dut.expect(TASK_ITERATION_HANDLING.format(iteration))
|
||||
dut.expect("TASK_EVENTS:TASK_ITERATION_EVENT: all_event_handler")
|
||||
elif iteration == TASK_UNREGISTRATION_LIMIT:
|
||||
dut.expect("TASK_EVENTS:TASK_ITERATION_EVENT: unregistering task_iteration_handler")
|
||||
dut.expect("TASK_EVENTS:TASK_ITERATION_EVENT: all_event_handler")
|
||||
print("Unregistered handler at iteration {} out of {}".format(iteration, TASK_ITERATION_LIMIT))
|
||||
else:
|
||||
dut.expect("TASK_EVENTS:TASK_ITERATION_EVENT: all_event_handler")
|
||||
|
||||
print("Handled iteration {} out of {}".format(iteration, TASK_ITERATION_LIMIT))
|
||||
|
||||
dut.expect("TASK_EVENTS:TASK_ITERATION_EVENT: deleting task event source")
|
||||
print("Deleted task event source")
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag='Example_GENERIC')
|
||||
def test_default_event_loop_example(env, extra_data):
|
||||
dut = env.get_dut('default_event_loop', 'examples/system/esp_event/default_event_loop')
|
||||
|
||||
_test_iteration_events(dut)
|
||||
_test_timer_events(dut)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_default_event_loop_example()
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "main.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,8 @@
|
||||
#
|
||||
# Main component makefile.
|
||||
#
|
||||
# This Makefile can be left empty. By default, it will take the sources in the
|
||||
# src/ directory, compile them and link them into lib(subdirectory_name).a
|
||||
# in the build directory. This behaviour is entirely configurable,
|
||||
# please read the ESP-IDF documents if you need to do this.
|
||||
#
|
||||
@@ -0,0 +1,52 @@
|
||||
/* esp_event (event loop library) basic example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#ifndef EVENT_SOURCE_H_
|
||||
#define EVENT_SOURCE_H_
|
||||
|
||||
#include "esp_event.h"
|
||||
#include "esp_timer.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// This example makes use of two event sources: a periodic timer, and a task.
|
||||
|
||||
// Declarations for event source 1: periodic timer
|
||||
#define TIMER_EXPIRIES_COUNT 3 // number of times the periodic timer expires before being stopped
|
||||
#define TIMER_PERIOD 1000000 // period of the timer event source in microseconds
|
||||
|
||||
extern esp_timer_handle_t g_timer; // the periodic timer object
|
||||
|
||||
// Declare an event base
|
||||
ESP_EVENT_DECLARE_BASE(TIMER_EVENTS); // declaration of the timer events family
|
||||
|
||||
enum { // declaration of the specific events under the timer event family
|
||||
TIMER_EVENT_STARTED, // raised when the timer is first started
|
||||
TIMER_EVENT_EXPIRY, // raised when a period of the timer has elapsed
|
||||
TIMER_EVENT_STOPPED // raised when the timer has been stopped
|
||||
};
|
||||
|
||||
// Declarations for event source 2: task
|
||||
#define TASK_ITERATIONS_COUNT 5 // number of times the task iterates
|
||||
#define TASK_ITERATIONS_UNREGISTER 3 // count at which the task event handler is unregistered
|
||||
#define TASK_PERIOD 500 // period of the task loop in milliseconds
|
||||
|
||||
ESP_EVENT_DECLARE_BASE(TASK_EVENTS); // declaration of the task events family
|
||||
|
||||
enum {
|
||||
TASK_ITERATION_EVENT, // raised during an iteration of the loop within the task
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // #ifndef EVENT_SOURCE_H_
|
||||
@@ -0,0 +1,193 @@
|
||||
/* esp_event (event loop library) basic example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include "event_source.h"
|
||||
|
||||
static const char* TAG = "default_event_loop";
|
||||
|
||||
static esp_event_handler_instance_t s_instance;
|
||||
|
||||
static int TIMER_START_HANDLER_0 = 0;
|
||||
static int TIMER_START_HANDLER_1 = 1;
|
||||
|
||||
static char* get_id_string(esp_event_base_t base, int32_t id) {
|
||||
char* event = "";
|
||||
if (base == TIMER_EVENTS) {
|
||||
switch(id) {
|
||||
case TIMER_EVENT_STARTED:
|
||||
event = "TIMER_EVENT_STARTED";
|
||||
break;
|
||||
case TIMER_EVENT_EXPIRY:
|
||||
event = "TIMER_EVENT_EXPIRY";
|
||||
break;
|
||||
case TIMER_EVENT_STOPPED:
|
||||
event = "TIMER_EVENT_STOPPED";
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
event = "TASK_ITERATION_EVENT";
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
/* Event source periodic timer related definitions */
|
||||
ESP_EVENT_DEFINE_BASE(TIMER_EVENTS);
|
||||
|
||||
esp_timer_handle_t TIMER;
|
||||
|
||||
// Callback that will be executed when the timer period lapses. Posts the timer expiry event
|
||||
// to the default event loop.
|
||||
static void timer_callback(void* arg)
|
||||
{
|
||||
ESP_LOGI(TAG, "%s:%s: posting to default loop", TIMER_EVENTS, get_id_string(TIMER_EVENTS, TIMER_EVENT_EXPIRY));
|
||||
ESP_ERROR_CHECK(esp_event_post(TIMER_EVENTS, TIMER_EVENT_EXPIRY, NULL, 0, portMAX_DELAY));
|
||||
}
|
||||
|
||||
// Handler which executes when the timer started event gets executed by the loop.
|
||||
static void timer_started_handler(void* handler_args, esp_event_base_t base, int32_t id, void* event_data)
|
||||
{
|
||||
int start_handler_num = *((int*) handler_args);
|
||||
ESP_LOGI(TAG, "%s:%s: timer_started_handler, instance %d", base, get_id_string(base, id), start_handler_num);
|
||||
}
|
||||
|
||||
// Second handler which executes when the timer started event gets executed by the loop.
|
||||
static void timer_started_handler_2(void* handler_args, esp_event_base_t base, int32_t id, void* event_data)
|
||||
{
|
||||
ESP_LOGI(TAG, "%s:%s: timer_started_handler_2", base, get_id_string(base, id));
|
||||
}
|
||||
|
||||
// Handler which executes when the timer expiry event gets executed by the loop. This handler keeps track of
|
||||
// how many times the timer expired. When a set number of expiry is reached, the handler stops the timer
|
||||
// and sends a timer stopped event.
|
||||
static void timer_expiry_handler(void* handler_args, esp_event_base_t base, int32_t id, void* event_data)
|
||||
{
|
||||
static int count = 0;
|
||||
|
||||
count++;
|
||||
|
||||
if (count >= TIMER_EXPIRIES_COUNT) {
|
||||
// Stop the timer
|
||||
ESP_ERROR_CHECK(esp_timer_stop(TIMER));
|
||||
|
||||
ESP_LOGI(TAG, "%s:%s: posting to default loop", base, get_id_string(base, TIMER_EVENT_STOPPED));
|
||||
|
||||
// Post the event that the timer has been stopped
|
||||
ESP_ERROR_CHECK(esp_event_post(TIMER_EVENTS, TIMER_EVENT_STOPPED, NULL, 0, portMAX_DELAY));
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "%s:%s: timer_expiry_handler, executed %d out of %d times", base, get_id_string(base, id), count, TIMER_EXPIRIES_COUNT);
|
||||
}
|
||||
|
||||
// Handler which executes when any timer event (started, expiry, stopped) get executed by the loop
|
||||
static void timer_any_handler(void* handler_args, esp_event_base_t base, int32_t id, void* event_data)
|
||||
{
|
||||
ESP_LOGI(TAG, "%s:%s: timer_any_handler", base, get_id_string(base, id));
|
||||
}
|
||||
|
||||
// Handler which executes when the timer stopped event gets executed by the loop. Since the timer has been
|
||||
// stopped, it is safe to delete it.
|
||||
static void timer_stopped_handler(void* handler_args, esp_event_base_t base, int32_t id, void* event_data)
|
||||
{
|
||||
ESP_LOGI(TAG, "%s:%s: timer_stopped_handler", base, get_id_string(base, id));
|
||||
|
||||
// Delete the timer
|
||||
esp_timer_delete(TIMER);
|
||||
|
||||
ESP_LOGI(TAG, "%s:%s: deleted timer event source", base, get_id_string(base, id));
|
||||
}
|
||||
|
||||
/* Event source task related definitions */
|
||||
ESP_EVENT_DEFINE_BASE(TASK_EVENTS);
|
||||
|
||||
static void task_iteration_handler(void* handler_args, esp_event_base_t base, int32_t id, void* event_data)
|
||||
{
|
||||
int iteration = *((int*) event_data);
|
||||
ESP_LOGI(TAG, "%s:%s: task_iteration_handler, executed %d times", base, get_id_string(base, id), iteration);
|
||||
}
|
||||
|
||||
static void task_event_source(void* args)
|
||||
{
|
||||
for(int iteration = 1; iteration <= TASK_ITERATIONS_COUNT; iteration++) {
|
||||
|
||||
ESP_LOGI(TAG, "%s:%s: posting to default loop, %d out of %d", TASK_EVENTS,
|
||||
get_id_string(TASK_EVENTS, TASK_ITERATION_EVENT), iteration, TASK_ITERATIONS_COUNT);
|
||||
|
||||
// Post that the loop has iterated. Notice that the iteration count is passed to the handler. Take note
|
||||
// that data passed during event posting is a deep copy of the original data.
|
||||
ESP_ERROR_CHECK(esp_event_post(TASK_EVENTS, TASK_ITERATION_EVENT, &iteration, sizeof(iteration), portMAX_DELAY));
|
||||
|
||||
if (iteration == TASK_ITERATIONS_UNREGISTER) {
|
||||
ESP_LOGI(TAG, "%s:%s: unregistering task_iteration_handler", TASK_EVENTS, get_id_string(TASK_EVENTS, TASK_ITERATION_EVENT));
|
||||
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(TASK_EVENTS, TASK_ITERATION_EVENT, s_instance));
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(TASK_PERIOD));
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(TASK_PERIOD));
|
||||
|
||||
ESP_LOGI(TAG, "%s:%s: deleting task event source", TASK_EVENTS, get_id_string(TASK_EVENTS, TASK_ITERATION_EVENT));
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
/* Handler for all events */
|
||||
static void all_event_handler(void* handler_args, esp_event_base_t base, int32_t id, void* event_data)
|
||||
{
|
||||
ESP_LOGI(TAG, "%s:%s: all_event_handler", base, get_id_string(base, id));
|
||||
}
|
||||
|
||||
/* Example main */
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "setting up");
|
||||
|
||||
// Create the default event loop
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
|
||||
// Register the specific timer event handlers. Timer start handler is registered twice.
|
||||
ESP_ERROR_CHECK(esp_event_handler_instance_register(TIMER_EVENTS, TIMER_EVENT_STARTED, timer_started_handler, &TIMER_START_HANDLER_0, NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_instance_register(TIMER_EVENTS, TIMER_EVENT_STARTED, timer_started_handler, &TIMER_START_HANDLER_1, NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_instance_register(TIMER_EVENTS, TIMER_EVENT_STARTED, timer_started_handler_2, NULL, NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_instance_register(TIMER_EVENTS, TIMER_EVENT_EXPIRY, timer_expiry_handler, NULL, NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_instance_register(TIMER_EVENTS, TIMER_EVENT_STOPPED, timer_stopped_handler, NULL, NULL));
|
||||
|
||||
// Register the handler for all timer family events. This will execute if the timer is started, expired or is stopped.
|
||||
ESP_ERROR_CHECK(esp_event_handler_instance_register(TIMER_EVENTS, ESP_EVENT_ANY_ID, timer_any_handler, NULL, NULL));
|
||||
|
||||
// Register the handler for task iteration event; need to pass instance handle for later unregistration.
|
||||
ESP_ERROR_CHECK(esp_event_handler_instance_register(TASK_EVENTS, TASK_ITERATION_EVENT, task_iteration_handler, NULL, &s_instance));
|
||||
|
||||
// Register the handler for all event. This will execute if either the timer events or the task iteration event
|
||||
// is posted to the default loop.
|
||||
ESP_ERROR_CHECK(esp_event_handler_instance_register(ESP_EVENT_ANY_BASE, ESP_EVENT_ANY_ID, all_event_handler, NULL, NULL));
|
||||
|
||||
// Create and start the event sources
|
||||
esp_timer_create_args_t timer_args = {
|
||||
.callback = &timer_callback,
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &TIMER));
|
||||
|
||||
ESP_LOGI(TAG, "starting event sources");
|
||||
|
||||
// Create the event source task with the same priority as the current task
|
||||
xTaskCreate(task_event_source, "task_event_source", 2048, NULL, uxTaskPriorityGet(NULL), NULL);
|
||||
|
||||
ESP_ERROR_CHECK(esp_timer_start_periodic(TIMER, TIMER_PERIOD));
|
||||
|
||||
// Post the timer started event
|
||||
ESP_LOGI(TAG, "%s:%s: posting to default loop", TIMER_EVENTS, get_id_string(TIMER_EVENTS, TIMER_EVENT_STARTED));
|
||||
ESP_ERROR_CHECK(esp_event_post(TIMER_EVENTS, TIMER_EVENT_STARTED, NULL, 0, portMAX_DELAY));
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(user_event_loops)
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := user_event_loops
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
# User Event Loops Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
This example demonstrates the creation and use of [**user event loops**](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/esp_event.html#). This example is a supplement to the [**default event loop** example](https://github.com/espressif/esp-idf/tree/master/examples/system/esp_event/default_event_loop), if the default event loop is not sufficient for the user's use case.
|
||||
|
||||
This example demonstrates the following things regarding user event loops:
|
||||
|
||||
### Creating Event Loops
|
||||
|
||||
Creating a loop entails populating the structure `esp_event_loop_args_t` with the desired parameters and calling `esp_event_loop_create()`. The call to `esp_event_loop_create()` produces a handle to the loop, which is used to perform actions on that loop such as handler registration/unregistration and posting events.
|
||||
|
||||
### Running Event Loops
|
||||
|
||||
Depending on the parameters, the user can create either a loop with a dedicated task or one without. The purpose of the dedicated task is to unqueue events from the loop and execute its handlers. For loops without the dedicated task, the user should make a call to `esp_event_loop_run()` in an application task.
|
||||
|
||||
### Handler Registration/Unregistration,
|
||||
|
||||
Handler registration and unregistration works the same way as the default event loop, just with a different API, `esp_event_handler_instance_register_with()` and `esp_event_handler_instance_unregister_with()` respectively. There are two things this example highlights:
|
||||
|
||||
1. The possibility of registering the same handler for different loops
|
||||
2. The ability to pass static data to handlers.
|
||||
|
||||
### Posting Events to the Default Event Loop
|
||||
|
||||
Posting events also works the same way as the default event loop, except with a different API, `esp_event_post_to()`.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
This example should be able to run on any commonly available ESP32 development board.
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
The example should have the following log output:
|
||||
|
||||
```
|
||||
I (296) user_event_loops: setting up
|
||||
I (296) user_event_loops: starting event source
|
||||
I (296) user_event_loops: starting application task
|
||||
I (296) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_without_task, iteration 1 out of 10
|
||||
I (316) user_event_loops: application_task: running application task
|
||||
I (326) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_without_task, iteration 1
|
||||
I (826) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_with_task, iteration 2 out of 10
|
||||
I (826) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_with_task, iteration 2
|
||||
I (1326) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_without_task, iteration 3 out of 10
|
||||
I (1326) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_without_task, iteration 3
|
||||
I (1426) user_event_loops: application_task: running application task
|
||||
I (1826) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_with_task, iteration 4 out of 10
|
||||
I (1826) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_with_task, iteration 4
|
||||
I (2326) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_without_task, iteration 5 out of 10
|
||||
I (2326) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_without_task, iteration 5
|
||||
I (2526) user_event_loops: application_task: running application task
|
||||
I (2826) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_with_task, iteration 6 out of 10
|
||||
I (2826) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_with_task, iteration 6
|
||||
I (3326) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_without_task, iteration 7 out of 10
|
||||
I (3326) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_without_task, iteration 7
|
||||
I (3626) user_event_loops: application_task: running application task
|
||||
I (3826) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_with_task, iteration 8 out of 10
|
||||
I (3826) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_with_task, iteration 8
|
||||
I (4326) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_without_task, iteration 9 out of 10
|
||||
I (4326) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_without_task, iteration 9
|
||||
I (4726) user_event_loops: application_task: running application task
|
||||
I (4826) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_with_task, iteration 10 out of 10
|
||||
I (4826) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_with_task, iteration 10
|
||||
I (5826) user_event_loops: application_task: running application task
|
||||
I (5826) user_event_loops: deleting task event source
|
||||
I (6926) user_event_loops: application_task: running application task
|
||||
I (8026) user_event_loops: application_task: running application task
|
||||
I (9126) user_event_loops: application_task: running application task
|
||||
...
|
||||
```
|
||||
|
||||
## Example Breakdown
|
||||
|
||||
### Setting of Event Sources
|
||||
|
||||
This example has a single event source: a task with a loop inside. Events are raised for the task event source when the loop iterates.
|
||||
|
||||
Two user event loops are created, one with a dedicated task and one without. Events are posted to either loops, depending on whether the iteration is odd or even. For the loop with a dedicated task, event handlers are automatically executed. However, for the loop without the dedicated task, a call to run the loop is made in one of the application tasks. As a result, the execution of the event handlers for this loop is interspersed with the execution of application task code.
|
||||
|
||||
### Step-by-Step Explanation
|
||||
|
||||
#### 1.Setting up user event loop and event handlers
|
||||
|
||||
```
|
||||
I (296) user_event_loops: setting up
|
||||
I (296) user_event_loops: starting event source
|
||||
I (296) user_event_loops: starting application task
|
||||
```
|
||||
At this stage the two event loops are created, as well as the handlers for the iteration event registered. The event source is started, which will post the event to the appropriate loop. The application task which makes the call to run the loop without dedicated task, is also created and started.
|
||||
|
||||
#### 2. Posting to the event loop
|
||||
```
|
||||
I (296) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_without_task, iteration 1 out of 10
|
||||
I (316) user_event_loops: application_task: running application task
|
||||
I (326) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_without_task, iteration 1
|
||||
I (826) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_with_task, iteration 2 out of 10
|
||||
I (826) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_with_task, iteration 2
|
||||
I (1326) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_without_task, iteration 3 out of 10
|
||||
I (1326) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_without_task, iteration 3
|
||||
I (1426) user_event_loops: application_task: running application task
|
||||
I (1826) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_with_task, iteration 4 out of 10
|
||||
...
|
||||
```
|
||||
In this section of the log we see the odd iterations posted to the loop without dedicated task, and the even iterations to the loop with a dedicated task. For the event with dedicated task, event handlers are executed automatically. The loop without a dedicated task, on the other hand, runs in the context of the application task.
|
||||
|
||||
#### 3. Iteration Limit
|
||||
|
||||
```
|
||||
...
|
||||
I (4826) user_event_loops: posting TASK_EVENTS:TASK_ITERATION_EVENT to loop_with_task, iteration 10 out of 10
|
||||
I (4826) user_event_loops: handling TASK_EVENTS:TASK_ITERATION_EVENT from loop_with_task, iteration 10
|
||||
I (5826) user_event_loops: application_task: running application task
|
||||
I (5826) user_event_loops: deleting task event source
|
||||
I (6926) user_event_loops: application_task: running application task
|
||||
I (8026) user_event_loops: application_task: running application task
|
||||
I (9126) user_event_loops: application_task: running application task
|
||||
...
|
||||
```
|
||||
|
||||
The last of the iteration event is posted, and the event source is deleted. Because the loop without the task no longer receive events to execute, only the application task code executes.
|
||||
@@ -0,0 +1,40 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import ttfw_idf
|
||||
|
||||
TASK_ITERATION_LIMIT = 10
|
||||
|
||||
TASK_ITERATION_POSTING = "posting TASK_EVENTS:TASK_ITERATION_EVENT to {}, iteration {} out of " + str(TASK_ITERATION_LIMIT)
|
||||
TASK_ITERATION_HANDLING = "handling TASK_EVENTS:TASK_ITERATION_EVENT from {}, iteration {}"
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag='Example_GENERIC')
|
||||
def test_user_event_loops_example(env, extra_data):
|
||||
dut = env.get_dut('user_event_loops', 'examples/system/esp_event/user_event_loops', dut_class=ttfw_idf.ESP32DUT)
|
||||
|
||||
dut.start_app()
|
||||
|
||||
dut.expect("setting up")
|
||||
dut.expect("starting event source")
|
||||
dut.expect("starting application task")
|
||||
print("Finished setup")
|
||||
|
||||
for iteration in range(1, TASK_ITERATION_LIMIT + 1):
|
||||
loop = None
|
||||
|
||||
if (iteration % 2 == 0):
|
||||
loop = "loop_with_task"
|
||||
else:
|
||||
loop = "loop_without_task"
|
||||
|
||||
dut.expect(TASK_ITERATION_POSTING.format(loop, iteration))
|
||||
print("Posted iteration {} to {}".format(iteration, loop))
|
||||
dut.expect(TASK_ITERATION_HANDLING.format(loop, iteration))
|
||||
print("Handled iteration {} from {}".format(iteration, loop))
|
||||
|
||||
dut.expect("deleting task event source")
|
||||
print("Deleted task event source")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_user_event_loops_example()
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "main.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,8 @@
|
||||
#
|
||||
# Main component makefile.
|
||||
#
|
||||
# This Makefile can be left empty. By default, it will take the sources in the
|
||||
# src/ directory, compile them and link them into lib(subdirectory_name).a
|
||||
# in the build directory. This behaviour is entirely configurable,
|
||||
# please read the ESP-IDF documents if you need to do this.
|
||||
#
|
||||
@@ -0,0 +1,34 @@
|
||||
/* esp_event (event loop library) basic example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#ifndef EVENT_SOURCE_H_
|
||||
#define EVENT_SOURCE_H_
|
||||
|
||||
#include "esp_event.h"
|
||||
#include "esp_timer.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Declarations for the event source
|
||||
#define TASK_ITERATIONS_COUNT 10 // number of times the task iterates
|
||||
#define TASK_PERIOD 500 // period of the task loop in milliseconds
|
||||
|
||||
ESP_EVENT_DECLARE_BASE(TASK_EVENTS); // declaration of the task events family
|
||||
|
||||
enum {
|
||||
TASK_ITERATION_EVENT // raised during an iteration of the loop within the task
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // #ifndef EVENT_SOURCE_H_
|
||||
@@ -0,0 +1,123 @@
|
||||
/* esp_event (event loop library) basic example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
#include "event_source.h"
|
||||
#include "esp_event_base.h"
|
||||
|
||||
static const char* TAG = "user_event_loops";
|
||||
|
||||
// Event loops
|
||||
esp_event_loop_handle_t loop_with_task;
|
||||
esp_event_loop_handle_t loop_without_task;
|
||||
|
||||
static void application_task(void* args)
|
||||
{
|
||||
while(1) {
|
||||
ESP_LOGI(TAG, "application_task: running application task");
|
||||
esp_event_loop_run(loop_without_task, 100);
|
||||
vTaskDelay(10);
|
||||
}
|
||||
}
|
||||
|
||||
/* Event source task related definitions */
|
||||
ESP_EVENT_DEFINE_BASE(TASK_EVENTS);
|
||||
|
||||
TaskHandle_t g_task;
|
||||
|
||||
static void task_iteration_handler(void* handler_args, esp_event_base_t base, int32_t id, void* event_data)
|
||||
{
|
||||
// Two types of data can be passed in to the event handler: the handler specific data and the event-specific data.
|
||||
//
|
||||
// The handler specific data (handler_args) is a pointer to the original data, therefore, the user should ensure that
|
||||
// the memory location it points to is still valid when the handler executes.
|
||||
//
|
||||
// The event-specific data (event_data) is a pointer to a deep copy of the original data, and is managed automatically.
|
||||
int iteration = *((int*) event_data);
|
||||
|
||||
char* loop;
|
||||
|
||||
if (handler_args == loop_with_task) {
|
||||
loop = "loop_with_task";
|
||||
} else {
|
||||
loop = "loop_without_task";
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "handling %s:%s from %s, iteration %d", base, "TASK_ITERATION_EVENT", loop, iteration);
|
||||
}
|
||||
|
||||
static void task_event_source(void* args)
|
||||
{
|
||||
for(int iteration = 1; iteration <= TASK_ITERATIONS_COUNT; iteration++) {
|
||||
esp_event_loop_handle_t loop_to_post_to;
|
||||
|
||||
if (iteration % 2 == 0) {
|
||||
// if even, post to the event loop with dedicated task
|
||||
loop_to_post_to = loop_with_task;
|
||||
} else {
|
||||
// if odd, post to the event loop without a dedicated task
|
||||
loop_to_post_to = loop_without_task;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "posting %s:%s to %s, iteration %d out of %d", TASK_EVENTS, "TASK_ITERATION_EVENT",
|
||||
loop_to_post_to == loop_with_task ? "loop_with_task" : "loop_without_task",
|
||||
iteration, TASK_ITERATIONS_COUNT);
|
||||
|
||||
ESP_ERROR_CHECK(esp_event_post_to(loop_to_post_to, TASK_EVENTS, TASK_ITERATION_EVENT, &iteration, sizeof(iteration), portMAX_DELAY));
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(TASK_PERIOD));
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(TASK_PERIOD));
|
||||
|
||||
ESP_LOGI(TAG, "deleting task event source");
|
||||
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
/* Example main */
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "setting up");
|
||||
|
||||
esp_event_loop_args_t loop_with_task_args = {
|
||||
.queue_size = 5,
|
||||
.task_name = "loop_task", // task will be created
|
||||
.task_priority = uxTaskPriorityGet(NULL),
|
||||
.task_stack_size = 2048,
|
||||
.task_core_id = tskNO_AFFINITY
|
||||
};
|
||||
|
||||
esp_event_loop_args_t loop_without_task_args = {
|
||||
.queue_size = 5,
|
||||
.task_name = NULL // no task will be created
|
||||
};
|
||||
|
||||
// Create the event loops
|
||||
ESP_ERROR_CHECK(esp_event_loop_create(&loop_with_task_args, &loop_with_task));
|
||||
ESP_ERROR_CHECK(esp_event_loop_create(&loop_without_task_args, &loop_without_task));
|
||||
|
||||
// Register the handler for task iteration event. Notice that the same handler is used for handling event on different loops.
|
||||
// The loop handle is provided as an argument in order for this example to display the loop the handler is being run on.
|
||||
ESP_ERROR_CHECK(esp_event_handler_instance_register_with(loop_with_task, TASK_EVENTS, TASK_ITERATION_EVENT, task_iteration_handler, loop_with_task, NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_instance_register_with(loop_without_task, TASK_EVENTS, TASK_ITERATION_EVENT, task_iteration_handler, loop_without_task, NULL));
|
||||
|
||||
ESP_LOGI(TAG, "starting event source");
|
||||
|
||||
// Create the event source task with the same priority as the current task
|
||||
xTaskCreate(task_event_source, "task_event_source", 2048, NULL, uxTaskPriorityGet(NULL), NULL);
|
||||
|
||||
ESP_LOGI(TAG, "starting application task");
|
||||
// Create the application task with the same priority as the current task
|
||||
xTaskCreate(application_task, "application_task", 2048, NULL, uxTaskPriorityGet(NULL), NULL);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(esp_timer_example)
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := esp_timer_example
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
| Supported Targets | ESP32 |
|
||||
| ----------------- | ----- |
|
||||
|
||||
# High Resolution Timer Example (`esp_timer`)
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
The [High Resolution Timer (`esp_timer`)](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/esp_timer.html) APIs allow an application to create multiple timers using a single hardware timer, and hides complexity associated with managing multiple timers, invoking callbacks, accounting for APB frequency changes (if dynamic frequency scaling is enabled), and maintaining correct time after light sleep.
|
||||
|
||||
This example illustrates the usage of the [`esp_timer` API](https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/system/esp_timer.html#api-reference) to create one-shot and periodic software timers.
|
||||
|
||||
The `esp_timer` API also provides the `esp_timer_get_time()` function which returns the time since boot in microseconds. This can be useful for fine-grained timing in tasks and ISRs thus is also demonstrated in this example.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
This example should be able to run on any commonly available ESP32 development board.
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
Under `Component config > Common ESP-related` are the following `esp_timer` related configurations
|
||||
|
||||
* `High-resolution timer task stack size` can be increased if timer callbacks require a larger stack
|
||||
* `Enable esp_timer profiling features` will cause `esp_timer_dump()` to include more information.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
The example should have the following log output:
|
||||
|
||||
```
|
||||
...
|
||||
I (294) example: Started timers, time since boot: 9662 us
|
||||
periodic 500000 509644 1 0 0
|
||||
one-shot 0 5009654 1 0 0
|
||||
I (794) example: Periodic timer called, time since boot: 509694 us
|
||||
I (1294) example: Periodic timer called, time since boot: 1009671 us
|
||||
I (1794) example: Periodic timer called, time since boot: 1509671 us
|
||||
I (2294) example: Periodic timer called, time since boot: 2009671 us
|
||||
periodic 500000 2509644 1 4 542
|
||||
one-shot 0 5009654 1 0 0
|
||||
I (2794) example: Periodic timer called, time since boot: 2509671 us
|
||||
I (3294) example: Periodic timer called, time since boot: 3009671 us
|
||||
I (3794) example: Periodic timer called, time since boot: 3509671 us
|
||||
I (4294) example: Periodic timer called, time since boot: 4009671 us
|
||||
periodic 500000 4509644 1 8 1026
|
||||
one-shot 0 5009654 1 0 0
|
||||
I (4794) example: Periodic timer called, time since boot: 4509671 us
|
||||
I (5294) example: Periodic timer called, time since boot: 5009669 us
|
||||
I (5294) example: One-shot timer called, time since boot: 5009788 us
|
||||
I (5294) example: Restarted periodic timer with 1s period, time since boot: 5012675 us
|
||||
I (6294) example: Periodic timer called, time since boot: 6012692 us
|
||||
periodic 1000000 7012666 2 11 1391
|
||||
one-shot 0 0 1 1 11472
|
||||
I (7294) example: Periodic timer called, time since boot: 7012692 us
|
||||
I (8294) example: Periodic timer called, time since boot: 8012692 us
|
||||
periodic 1000000 9012666 2 13 1639
|
||||
one-shot 0 0 1 1 11472
|
||||
I (9294) example: Periodic timer called, time since boot: 9012692 us
|
||||
I (10294) example: Periodic timer called, time since boot: 10012692 us
|
||||
I (10314) example: Entering light sleep for 0.5s, time since boot: 10024351 us
|
||||
I (10314) example: Woke up from light sleep, time since boot: 10525143 us
|
||||
...
|
||||
```
|
||||
|
||||
## Example Breakdown
|
||||
|
||||
### 1. Creating and starting timers
|
||||
|
||||
The example starts by creating a periodic and a one shot timer using the `esp_timer_create()` function. Once created, the two timers are started using the `esp_timer_start_periodic()` and `esp_timer_start_once()` functions.
|
||||
|
||||
```
|
||||
I (265) example: Starting timers, time since boot: 2479 us
|
||||
```
|
||||
|
||||
### 2. Getting initial timer dump
|
||||
|
||||
These two repeating lines are the output of `esp_timer_dump()` function. There is one line for each of the timers created. This function can be useful for debugging purposes. Note that such debugging information is available because the example sets `CONFIG_ESP_TIMER_PROFILING` option in sdkconfig. Without this option, less information will be available. See documentation of `esp_timer_dump()` in ESP-IDF programming guide for more details.
|
||||
|
||||
```
|
||||
timer period next time times times callback
|
||||
name to fire started fired run time (us)
|
||||
------------------------------------------------------------------------------------
|
||||
|
||||
periodic 500000 502455 1 0 0
|
||||
one-shot 0 5002469 1 0 0
|
||||
```
|
||||
|
||||
### 3. Periodic timer keeps running at 500ms period:
|
||||
|
||||
```
|
||||
I (765) example: Periodic timer called, time since boot: 502506 us
|
||||
I (1265) example: Periodic timer called, time since boot: 1002478 us
|
||||
I (1765) example: Periodic timer called, time since boot: 1502478 us
|
||||
I (2265) example: Periodic timer called, time since boot: 2002478 us
|
||||
periodic 500000 2502455 1 4 511
|
||||
one-shot 0 5002469 1 0 0
|
||||
I (2765) example: Periodic timer called, time since boot: 2502478 us
|
||||
I (3265) example: Periodic timer called, time since boot: 3002478 us
|
||||
I (3765) example: Periodic timer called, time since boot: 3502478 us
|
||||
I (4265) example: Periodic timer called, time since boot: 4002478 us
|
||||
periodic 500000 4502455 1 8 971
|
||||
one-shot 0 5002469 1 0 0
|
||||
I (4765) example: Periodic timer called, time since boot: 4502478 us
|
||||
I (5265) example: Periodic timer called, time since boot: 5002476 us
|
||||
```
|
||||
|
||||
### 4. One-shot timer runs
|
||||
|
||||
The one-shot timer runs and changes the period of the periodic timer. Now the periodic timer runs with a period of 1 second:
|
||||
|
||||
```
|
||||
I (5265) example: One-shot timer called, time since boot: 5002586 us
|
||||
I (5265) example: Restarted periodic timer with 1s period, time since boot: 5005475 us
|
||||
I (6265) example: Periodic timer called, time since boot: 6005492 us
|
||||
periodic 1000000 7005469 2 11 1316
|
||||
one-shot 0 0 1 1 11474
|
||||
I (7265) example: Periodic timer called, time since boot: 7005492 us
|
||||
I (8265) example: Periodic timer called, time since boot: 8005492 us
|
||||
periodic 1000000 9005469 2 13 1550
|
||||
one-shot 0 0 1 1 11474
|
||||
I (9265) example: Periodic timer called, time since boot: 9005492 us
|
||||
I (10265) example: Periodic timer called, time since boot: 10005492 us
|
||||
```
|
||||
|
||||
### 5. Continuation through light sleep
|
||||
|
||||
To illustrate that timekeeping continues correctly after light sleep, the example enters light sleep for 0.5 seconds. This sleep does not impact timer period, and the timer is executed 1 second after the previous iteration. Note that the timers can not execute during light sleep, since the CPU is not running at that time. Such timers would execute immediately after light sleep, and then continue running with their normal period.
|
||||
|
||||
```
|
||||
I (10275) example: Entering light sleep for 0.5s, time since boot: 10011559 us
|
||||
I (10275) example: Woke up from light sleep, time since boot: 10512007 us
|
||||
I (10765) example: Periodic timer called, time since boot: 11005492 us
|
||||
I (11765) example: Periodic timer called, time since boot: 12005492 us
|
||||
```
|
||||
|
||||
### 6. Finally, timers are deleted.
|
||||
|
||||
```
|
||||
I (12275) example: Stopped and deleted timers
|
||||
```
|
||||
@@ -0,0 +1,88 @@
|
||||
from __future__ import print_function
|
||||
import re
|
||||
|
||||
import ttfw_idf
|
||||
|
||||
STARTING_TIMERS_REGEX = re.compile(r'Started timers, time since boot: (\d+) us')
|
||||
|
||||
# name, period, next_alarm, times_started, times_fired, cb_exec_time
|
||||
TIMER_DUMP_LINE_REGEX = re.compile(r'([\w-]+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)')
|
||||
|
||||
PERIODIC_TIMER_REGEX = re.compile(r'Periodic timer called, time since boot: (\d+) us')
|
||||
|
||||
LIGHT_SLEEP_ENTER_REGEX = re.compile(r'Entering light sleep for 0\.5s, time since boot: (\d+) us')
|
||||
LIGHT_SLEEP_EXIT_REGEX = re.compile(r'Woke up from light sleep, time since boot: (\d+) us')
|
||||
|
||||
ONE_SHOT_REGEX = re.compile(r'One\-shot timer called, time since boot: (\d+) us')
|
||||
|
||||
RESTART_REGEX = re.compile(r'Restarted periodic timer with 1s period, time since boot: (\d+) us')
|
||||
|
||||
STOP_REGEX = re.compile(r'Stopped and deleted timers')
|
||||
|
||||
INITIAL_TIMER_PERIOD = 500000
|
||||
FINAL_TIMER_PERIOD = 1000000
|
||||
LIGHT_SLEEP_TIME = 500000
|
||||
ONE_SHOT_TIMER_PERIOD = 5000000
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag='Example_GENERIC')
|
||||
def test_examples_system_esp_timer(env, extra_data):
|
||||
dut = env.get_dut('esp_timer_example', 'examples/system/esp_timer', dut_class=ttfw_idf.ESP32DUT)
|
||||
# start test
|
||||
dut.start_app()
|
||||
groups = dut.expect(STARTING_TIMERS_REGEX, timeout=30)
|
||||
start_time = int(groups[0])
|
||||
print('Start time: {} us'.format(start_time))
|
||||
|
||||
groups = dut.expect(TIMER_DUMP_LINE_REGEX, timeout=2)
|
||||
assert(groups[0] == 'periodic' and int(groups[1]) == INITIAL_TIMER_PERIOD)
|
||||
groups = dut.expect(TIMER_DUMP_LINE_REGEX, timeout=2)
|
||||
assert(groups[0] == 'one-shot' and int(groups[1]) == 0)
|
||||
|
||||
for i in range(0, 5):
|
||||
groups = dut.expect(PERIODIC_TIMER_REGEX, timeout=2)
|
||||
cur_time = int(groups[0])
|
||||
diff = start_time + (i + 1) * INITIAL_TIMER_PERIOD - cur_time
|
||||
print('Callback #{}, time: {} us, diff: {} us'.format(i, cur_time, diff))
|
||||
assert(abs(diff) < 100)
|
||||
|
||||
groups = dut.expect(ONE_SHOT_REGEX, timeout=3)
|
||||
one_shot_timer_time = int(groups[0])
|
||||
diff = start_time + ONE_SHOT_TIMER_PERIOD - one_shot_timer_time
|
||||
print('One-shot timer, time: {} us, diff: {}'.format(one_shot_timer_time, diff))
|
||||
assert(abs(diff) < 220)
|
||||
|
||||
groups = dut.expect(RESTART_REGEX, timeout=3)
|
||||
start_time = int(groups[0])
|
||||
print('Timer restarted, time: {} us'.format(start_time))
|
||||
|
||||
for i in range(0, 5):
|
||||
groups = dut.expect(PERIODIC_TIMER_REGEX, timeout=2)
|
||||
cur_time = int(groups[0])
|
||||
diff = start_time + (i + 1) * FINAL_TIMER_PERIOD - cur_time
|
||||
print('Callback #{}, time: {} us, diff: {} us'.format(i, cur_time, diff))
|
||||
assert(abs(diff) < 100)
|
||||
|
||||
groups = dut.expect(LIGHT_SLEEP_ENTER_REGEX, timeout=2)
|
||||
sleep_enter_time = int(groups[0])
|
||||
groups = dut.expect(LIGHT_SLEEP_EXIT_REGEX, timeout=2)
|
||||
sleep_exit_time = int(groups[0])
|
||||
sleep_time = sleep_exit_time - sleep_enter_time
|
||||
|
||||
print('Enter sleep: {}, exit sleep: {}, slept: {}'.format(
|
||||
sleep_enter_time, sleep_exit_time, sleep_time))
|
||||
|
||||
assert(abs(sleep_time - LIGHT_SLEEP_TIME) < 1000)
|
||||
|
||||
for i in range(5, 7):
|
||||
groups = dut.expect(PERIODIC_TIMER_REGEX, timeout=2)
|
||||
cur_time = int(groups[0])
|
||||
diff = abs(start_time + (i + 1) * FINAL_TIMER_PERIOD - cur_time)
|
||||
print('Callback #{}, time: {} us, diff: {} us'.format(i, cur_time, diff))
|
||||
assert(diff < 100)
|
||||
|
||||
dut.expect(STOP_REGEX, timeout=2)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_examples_system_esp_timer()
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "esp_timer_example_main.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
/* esp_timer (high resolution timer) example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include "esp_timer.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_sleep.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
static void periodic_timer_callback(void* arg);
|
||||
static void oneshot_timer_callback(void* arg);
|
||||
|
||||
static const char* TAG = "example";
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
/* Create two timers:
|
||||
* 1. a periodic timer which will run every 0.5s, and print a message
|
||||
* 2. a one-shot timer which will fire after 5s, and re-start periodic
|
||||
* timer with period of 1s.
|
||||
*/
|
||||
|
||||
const esp_timer_create_args_t periodic_timer_args = {
|
||||
.callback = &periodic_timer_callback,
|
||||
/* name is optional, but may help identify the timer when debugging */
|
||||
.name = "periodic"
|
||||
};
|
||||
|
||||
esp_timer_handle_t periodic_timer;
|
||||
ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer));
|
||||
/* The timer has been created but is not running yet */
|
||||
|
||||
const esp_timer_create_args_t oneshot_timer_args = {
|
||||
.callback = &oneshot_timer_callback,
|
||||
/* argument specified here will be passed to timer callback function */
|
||||
.arg = (void*) periodic_timer,
|
||||
.name = "one-shot"
|
||||
};
|
||||
esp_timer_handle_t oneshot_timer;
|
||||
ESP_ERROR_CHECK(esp_timer_create(&oneshot_timer_args, &oneshot_timer));
|
||||
|
||||
/* Start the timers */
|
||||
ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer, 500000));
|
||||
ESP_ERROR_CHECK(esp_timer_start_once(oneshot_timer, 5000000));
|
||||
ESP_LOGI(TAG, "Started timers, time since boot: %lld us", esp_timer_get_time());
|
||||
|
||||
/* Print debugging information about timers to console every 2 seconds */
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
ESP_ERROR_CHECK(esp_timer_dump(stdout));
|
||||
usleep(2000000);
|
||||
}
|
||||
|
||||
/* Timekeeping continues in light sleep, and timers are scheduled
|
||||
* correctly after light sleep.
|
||||
*/
|
||||
ESP_LOGI(TAG, "Entering light sleep for 0.5s, time since boot: %lld us",
|
||||
esp_timer_get_time());
|
||||
|
||||
ESP_ERROR_CHECK(esp_sleep_enable_timer_wakeup(500000));
|
||||
esp_light_sleep_start();
|
||||
|
||||
ESP_LOGI(TAG, "Woke up from light sleep, time since boot: %lld us",
|
||||
esp_timer_get_time());
|
||||
|
||||
/* Let the timer run for a little bit more */
|
||||
usleep(2000000);
|
||||
|
||||
/* Clean up and finish the example */
|
||||
ESP_ERROR_CHECK(esp_timer_stop(periodic_timer));
|
||||
ESP_ERROR_CHECK(esp_timer_delete(periodic_timer));
|
||||
ESP_ERROR_CHECK(esp_timer_delete(oneshot_timer));
|
||||
ESP_LOGI(TAG, "Stopped and deleted timers");
|
||||
}
|
||||
|
||||
static void periodic_timer_callback(void* arg)
|
||||
{
|
||||
int64_t time_since_boot = esp_timer_get_time();
|
||||
ESP_LOGI(TAG, "Periodic timer called, time since boot: %lld us", time_since_boot);
|
||||
}
|
||||
|
||||
static void oneshot_timer_callback(void* arg)
|
||||
{
|
||||
int64_t time_since_boot = esp_timer_get_time();
|
||||
ESP_LOGI(TAG, "One-shot timer called, time since boot: %lld us", time_since_boot);
|
||||
esp_timer_handle_t periodic_timer_handle = (esp_timer_handle_t) arg;
|
||||
/* To start the timer which is running, need to stop it first */
|
||||
ESP_ERROR_CHECK(esp_timer_stop(periodic_timer_handle));
|
||||
ESP_ERROR_CHECK(esp_timer_start_periodic(periodic_timer_handle, 1000000));
|
||||
time_since_boot = esp_timer_get_time();
|
||||
ESP_LOGI(TAG, "Restarted periodic timer with 1s period, time since boot: %lld us",
|
||||
time_since_boot);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_ESP_TIMER_PROFILING=y
|
||||
@@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(real_time_stats)
|
||||
@@ -0,0 +1,9 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := real_time_stats
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# FreeRTOS Real Time Stats Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
FreeRTOS provides the function `vTaskGetRunTimeStats()` to obtain CPU usage statistics of tasks. However, these statistics are with respect to the entire runtime of FreeRTOS (i.e. **run time stats**). Furthermore, statistics of `vTaskGetRunTimeStats()` are only valid whilst the timer for run time statistics has not overflowed.
|
||||
|
||||
This example demonstrates how to get CPU usage statistics of tasks with respect to a specified duration (i.e. **real time stats**) rather than over the entire runtime of FreeRTOS. The `print_real_time_stats()` function of this example demonstrates how this can be achieved.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
This example should be able to run on any commonly available ESP32 development board.
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
* Select `Enable FreeRTOS to collect run time stats` under `Component Config > FreeRTOS` (this should be enabled in the example by default)
|
||||
|
||||
* `Choose the clock source for run time stats` configured under `Component Config > FreeRTOS`. The `esp_timer` should be selected be default. This option will affect the time unit resolution in which the statistics are measured with respect to.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
The example should have the following log output:
|
||||
|
||||
```
|
||||
...
|
||||
Getting real time stats over 100 ticks
|
||||
| Task | Run Time | Percentage
|
||||
| stats | 1304 | 0%
|
||||
| IDLE0 | 206251 | 10%
|
||||
| IDLE1 | 464785 | 23%
|
||||
| spin2 | 225389 | 11%
|
||||
| spin0 | 227174 | 11%
|
||||
| spin4 | 225303 | 11%
|
||||
| spin1 | 207264 | 10%
|
||||
| spin3 | 225331 | 11%
|
||||
| spin5 | 225369 | 11%
|
||||
| Tmr Svc | 0 | 0%
|
||||
| esp_timer | 0 | 0%
|
||||
| ipc1 | 0 | 0%
|
||||
| ipc0 | 0 | 0%
|
||||
Real time stats obtained
|
||||
...
|
||||
```
|
||||
|
||||
## Example Breakdown
|
||||
|
||||
### Spin tasks
|
||||
|
||||
During the examples initialization process, multiple `spin` tasks are created. These tasks will simply spin a certain number of CPU cycles to consume CPU time, then block for a predetermined period.
|
||||
|
||||
### Understanding the stats
|
||||
|
||||
From the log output, it can be seen that the spin tasks consume nearly an equal amount of time over the specified stats collection period of `print_real_time_stats()`. The real time stats also display the CPU time consumption of other tasks created by default in ESP-IDF (e.g. `IDLE` and `ipc` tasks).
|
||||
@@ -0,0 +1,19 @@
|
||||
from __future__ import print_function
|
||||
|
||||
import ttfw_idf
|
||||
|
||||
STATS_TASK_ITERS = 3
|
||||
STATS_TASK_EXPECT = "Real time stats obtained"
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag='Example_GENERIC')
|
||||
def test_real_time_stats_example(env, extra_data):
|
||||
dut = env.get_dut('real_time_stats', 'examples/system/freertos/real_time_stats', dut_class=ttfw_idf.ESP32DUT)
|
||||
dut.start_app()
|
||||
|
||||
for iteration in range(0, STATS_TASK_ITERS):
|
||||
dut.expect(STATS_TASK_EXPECT)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_real_time_stats_example()
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "real_time_stats_example_main.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,5 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
/* FreeRTOS Real Time Stats Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
#define NUM_OF_SPIN_TASKS 6
|
||||
#define SPIN_ITER 500000 //Actual CPU cycles used will depend on compiler optimization
|
||||
#define SPIN_TASK_PRIO 2
|
||||
#define STATS_TASK_PRIO 3
|
||||
#define STATS_TICKS pdMS_TO_TICKS(1000)
|
||||
#define ARRAY_SIZE_OFFSET 5 //Increase this if print_real_time_stats returns ESP_ERR_INVALID_SIZE
|
||||
|
||||
static char task_names[NUM_OF_SPIN_TASKS][configMAX_TASK_NAME_LEN];
|
||||
static SemaphoreHandle_t sync_spin_task;
|
||||
static SemaphoreHandle_t sync_stats_task;
|
||||
|
||||
/**
|
||||
* @brief Function to print the CPU usage of tasks over a given duration.
|
||||
*
|
||||
* This function will measure and print the CPU usage of tasks over a specified
|
||||
* number of ticks (i.e. real time stats). This is implemented by simply calling
|
||||
* uxTaskGetSystemState() twice separated by a delay, then calculating the
|
||||
* differences of task run times before and after the delay.
|
||||
*
|
||||
* @note If any tasks are added or removed during the delay, the stats of
|
||||
* those tasks will not be printed.
|
||||
* @note This function should be called from a high priority task to minimize
|
||||
* inaccuracies with delays.
|
||||
* @note When running in dual core mode, each core will correspond to 50% of
|
||||
* the run time.
|
||||
*
|
||||
* @param xTicksToWait Period of stats measurement
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK Success
|
||||
* - ESP_ERR_NO_MEM Insufficient memory to allocated internal arrays
|
||||
* - ESP_ERR_INVALID_SIZE Insufficient array size for uxTaskGetSystemState. Trying increasing ARRAY_SIZE_OFFSET
|
||||
* - ESP_ERR_INVALID_STATE Delay duration too short
|
||||
*/
|
||||
static esp_err_t print_real_time_stats(TickType_t xTicksToWait)
|
||||
{
|
||||
TaskStatus_t *start_array = NULL, *end_array = NULL;
|
||||
UBaseType_t start_array_size, end_array_size;
|
||||
uint32_t start_run_time, end_run_time;
|
||||
esp_err_t ret;
|
||||
|
||||
//Allocate array to store current task states
|
||||
start_array_size = uxTaskGetNumberOfTasks() + ARRAY_SIZE_OFFSET;
|
||||
start_array = malloc(sizeof(TaskStatus_t) * start_array_size);
|
||||
if (start_array == NULL) {
|
||||
ret = ESP_ERR_NO_MEM;
|
||||
goto exit;
|
||||
}
|
||||
//Get current task states
|
||||
start_array_size = uxTaskGetSystemState(start_array, start_array_size, &start_run_time);
|
||||
if (start_array_size == 0) {
|
||||
ret = ESP_ERR_INVALID_SIZE;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
vTaskDelay(xTicksToWait);
|
||||
|
||||
//Allocate array to store tasks states post delay
|
||||
end_array_size = uxTaskGetNumberOfTasks() + ARRAY_SIZE_OFFSET;
|
||||
end_array = malloc(sizeof(TaskStatus_t) * end_array_size);
|
||||
if (end_array == NULL) {
|
||||
ret = ESP_ERR_NO_MEM;
|
||||
goto exit;
|
||||
}
|
||||
//Get post delay task states
|
||||
end_array_size = uxTaskGetSystemState(end_array, end_array_size, &end_run_time);
|
||||
if (end_array_size == 0) {
|
||||
ret = ESP_ERR_INVALID_SIZE;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
//Calculate total_elapsed_time in units of run time stats clock period.
|
||||
uint32_t total_elapsed_time = (end_run_time - start_run_time);
|
||||
if (total_elapsed_time == 0) {
|
||||
ret = ESP_ERR_INVALID_STATE;
|
||||
goto exit;
|
||||
}
|
||||
|
||||
printf("| Task | Run Time | Percentage\n");
|
||||
//Match each task in start_array to those in the end_array
|
||||
for (int i = 0; i < start_array_size; i++) {
|
||||
int k = -1;
|
||||
for (int j = 0; j < end_array_size; j++) {
|
||||
if (start_array[i].xHandle == end_array[j].xHandle) {
|
||||
k = j;
|
||||
//Mark that task have been matched by overwriting their handles
|
||||
start_array[i].xHandle = NULL;
|
||||
end_array[j].xHandle = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//Check if matching task found
|
||||
if (k >= 0) {
|
||||
uint32_t task_elapsed_time = end_array[k].ulRunTimeCounter - start_array[i].ulRunTimeCounter;
|
||||
uint32_t percentage_time = (task_elapsed_time * 100UL) / (total_elapsed_time * portNUM_PROCESSORS);
|
||||
printf("| %s | %d | %d%%\n", start_array[i].pcTaskName, task_elapsed_time, percentage_time);
|
||||
}
|
||||
}
|
||||
|
||||
//Print unmatched tasks
|
||||
for (int i = 0; i < start_array_size; i++) {
|
||||
if (start_array[i].xHandle != NULL) {
|
||||
printf("| %s | Deleted\n", start_array[i].pcTaskName);
|
||||
}
|
||||
}
|
||||
for (int i = 0; i < end_array_size; i++) {
|
||||
if (end_array[i].xHandle != NULL) {
|
||||
printf("| %s | Created\n", end_array[i].pcTaskName);
|
||||
}
|
||||
}
|
||||
ret = ESP_OK;
|
||||
|
||||
exit: //Common return path
|
||||
free(start_array);
|
||||
free(end_array);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void spin_task(void *arg)
|
||||
{
|
||||
xSemaphoreTake(sync_spin_task, portMAX_DELAY);
|
||||
while (1) {
|
||||
//Consume CPU cycles
|
||||
for (int i = 0; i < SPIN_ITER; i++) {
|
||||
__asm__ __volatile__("NOP");
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
}
|
||||
}
|
||||
|
||||
static void stats_task(void *arg)
|
||||
{
|
||||
xSemaphoreTake(sync_stats_task, portMAX_DELAY);
|
||||
|
||||
//Start all the spin tasks
|
||||
for (int i = 0; i < NUM_OF_SPIN_TASKS; i++) {
|
||||
xSemaphoreGive(sync_spin_task);
|
||||
}
|
||||
|
||||
//Print real time stats periodically
|
||||
while (1) {
|
||||
printf("\n\nGetting real time stats over %d ticks\n", STATS_TICKS);
|
||||
if (print_real_time_stats(STATS_TICKS) == ESP_OK) {
|
||||
printf("Real time stats obtained\n");
|
||||
} else {
|
||||
printf("Error getting real time stats\n");
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
//Allow other core to finish initialization
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
|
||||
//Create semaphores to synchronize
|
||||
sync_spin_task = xSemaphoreCreateCounting(NUM_OF_SPIN_TASKS, 0);
|
||||
sync_stats_task = xSemaphoreCreateBinary();
|
||||
|
||||
//Create spin tasks
|
||||
for (int i = 0; i < NUM_OF_SPIN_TASKS; i++) {
|
||||
snprintf(task_names[i], configMAX_TASK_NAME_LEN, "spin%d", i);
|
||||
xTaskCreatePinnedToCore(spin_task, task_names[i], 1024, NULL, SPIN_TASK_PRIO, NULL, tskNO_AFFINITY);
|
||||
}
|
||||
|
||||
//Create and start stats task
|
||||
xTaskCreatePinnedToCore(stats_task, "stats", 4096, NULL, STATS_TASK_PRIO, NULL, tskNO_AFFINITY);
|
||||
xSemaphoreGive(sync_stats_task);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
CONFIG_FREERTOS_USE_TRACE_FACILITY=y
|
||||
CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y
|
||||
@@ -0,0 +1,9 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(gcov_example)
|
||||
|
||||
idf_create_coverage_report(${CMAKE_CURRENT_BINARY_DIR}/coverage_report)
|
||||
idf_clean_coverage_report(${CMAKE_CURRENT_BINARY_DIR}/coverage_report)
|
||||
@@ -0,0 +1,32 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := gcov_example
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
GCOV := $(call dequote,$(CONFIG_SDK_TOOLPREFIX))gcov
|
||||
REPORT_DIR := $(BUILD_DIR_BASE)/coverage_report
|
||||
|
||||
pre-cov-report:
|
||||
echo "Generating coverage report in: $(REPORT_DIR)"
|
||||
echo "Using gcov: $(GCOV)"
|
||||
mkdir -p $(REPORT_DIR)/html
|
||||
|
||||
lcov-report: | pre-cov-report
|
||||
echo "WARNING: lcov-report is deprecated. Please use gcovr-report instead."
|
||||
lcov --gcov-tool $(GCOV) -c -d $(BUILD_DIR_BASE) -o $(REPORT_DIR)/$(PROJECT_NAME).info
|
||||
genhtml -o $(REPORT_DIR)/html $(REPORT_DIR)/$(PROJECT_NAME).info
|
||||
|
||||
gcovr-report: | check_python_dependencies pre-cov-report
|
||||
cd $(BUILD_DIR_BASE)
|
||||
gcovr -r $(PROJECT_PATH) --gcov-executable $(GCOV) -s --html-details $(REPORT_DIR)/html/index.html
|
||||
|
||||
cov-data-clean:
|
||||
echo "Remove coverage data files..."
|
||||
find $(BUILD_DIR_BASE) -name "*.gcda" -exec rm {} +
|
||||
rm -rf $(REPORT_DIR)
|
||||
|
||||
.PHONY: lcov-report gcovr-report cov-data-clean
|
||||
@@ -0,0 +1,150 @@
|
||||
| Supported Targets | ESP32 |
|
||||
| ----------------- | ----- |
|
||||
|
||||
# Blink Example With Coverage Info (Gcov)
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
The following example demonstrates how to compile an ESP-IDF project to generate code coverage data, and how generate a code coverage report using Gcov or Lcov. Refer to the [Gcov Guide](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/app_trace.html#gcov-source-code-coverage) for more details on the code coverage features supported in ESP-IDF.
|
||||
|
||||
This example implements a simple blink application but with code coverage enabled. The example will demonstrate the following features:
|
||||
* How to compile a project with coverage info enabled.
|
||||
* Various methods of dumping code coverage data (e.g. Instant Run-Time Dump and Hard-coded Dump).
|
||||
* How to generate a code coverage report.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
To run this example, you need an ESP32 dev board connected to a JTAG adapter, which can come in the following forms:
|
||||
|
||||
* [ESP-WROVER-KIT](https://docs.espressif.com/projects/esp-idf/en/latest/hw-reference/modules-and-boards.html#esp-wrover-kit-v4-1) which integrates an on-board JTAG adapter. Ensure that the [required jumpers to enable JTAG are connected](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/get-started-wrover-kit.html#setup-options) on the WROVER-KIT.
|
||||
* ESP32 core board (e.g. ESP32-DevKitC) can also work as long as you connect it to an external JTAG adapter (e.g. FT2232H, J-LINK).
|
||||
|
||||
This example will assume that that an ESP-WROVER-KIT is used.
|
||||
|
||||
1. Connect the JTAG interface to ESP32 board, and power up both the JTAG and ESP32. For details about how to set up JTAG interface, please see [JTAG Debugging](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/jtag-debugging/index.html).
|
||||
|
||||
2. After connecting JTAG interface, you need to [Run OpenOCD](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/jtag-debugging/index.html#run-openocd).
|
||||
|
||||
3. Open a separate terminal window and run telnet by entering the command below. The telnet terminal window is used to feed commands to OpenOCD:
|
||||
|
||||
```bash
|
||||
telnet localhost 4444
|
||||
```
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
The example will enable the following options by default:
|
||||
|
||||
* Enable the Application Tracing Module under `Component config -> Application Level Tracing -> Data Destination` by choosing `Trace memory`.
|
||||
* Enable GCOV to host interface under `Component config -> Application Level Tracing -> GCOV to Host Enable`.
|
||||
* Enable OpenOCD Debug Stubs under `Component config -> ESP32-specific -> OpenOCD debug stubs`
|
||||
|
||||
### Build, Flash, and Run
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
### 1. Hard-coded Dump
|
||||
|
||||
The example will initially execute two hard-coded dumps. Therefore, when the application outputs `Ready to dump GCOV data...`, users should execute the `esp gcov dump` OpenOCD command. The example should output the following:
|
||||
|
||||
```
|
||||
blink_dummy_func: Counter = 0
|
||||
some_dummy_func: Counter = 0
|
||||
Ready to dump GCOV data...
|
||||
GCOV data have been dumped.
|
||||
blink_dummy_func: Counter = 1
|
||||
some_dummy_func: Counter = 2
|
||||
Ready to dump GCOV data...
|
||||
GCOV data have been dumped.
|
||||
```
|
||||
|
||||
### 2. Instant Run-Time Dump
|
||||
|
||||
After the two hard-coded dumps, the example will continue looping through it's main blink function. Users can call `esp gcov` OpenOCD command to trigger an instant run-time dump. The output should resemble the following:
|
||||
|
||||
```
|
||||
blink_dummy_func: Counter = 2
|
||||
some_dummy_func: Counter = 4
|
||||
blink_dummy_func: Counter = 3
|
||||
some_dummy_func: Counter = 6
|
||||
blink_dummy_func: Counter = 4
|
||||
some_dummy_func: Counter = 8
|
||||
blink_dummy_func: Counter = 5
|
||||
some_dummy_func: Counter = 10
|
||||
blink_dummy_func: Counter = 6
|
||||
some_dummy_func: Counter = 12
|
||||
blink_dummy_func: Counter = 7
|
||||
some_dummy_func: Counter = 14
|
||||
blink_dummy_func: Counter = 8
|
||||
some_dummy_func: Counter = 16
|
||||
blink_dummy_func: Counter = 9
|
||||
some_dummy_func: Counter = 18
|
||||
blink_dummy_func: Counter = 10
|
||||
some_dummy_func: Counter = 20
|
||||
...
|
||||
```
|
||||
|
||||
### Generating Gcovr Report
|
||||
|
||||
After dumping one or more times, a coverage report can be generated by calling `cmake --build build/ --target gcovr-report`. This should result in an HTML code coverage report being generated in the build directory.
|
||||
|
||||
To clean Gcov and report related data from the build directory, call `cmake --build build/ --target cov-data-clean`
|
||||
|
||||
The following log should be output when generating the coverage report:
|
||||
|
||||
```
|
||||
[1/2] Generating coverage report in: /home/user/esp/esp-idf/examples/system/gcov/build/coverage_report
|
||||
Using gcov: xtensa-esp32-elf-gcov
|
||||
[2/2] cd /home/user/esp/esp-idf/examples/system/gcov/build && gcovr -r /home/user/esp/esp-idf/examples/system/gcov...a-esp32-elf-gcov -s --html-details /home/user/esp/esp-idf/examples/system/gcov/build/coverage_report/html/index.htm
|
||||
lines: 100.0% (27 out of 27)
|
||||
branches: 100.0% (2 out of 2)
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### OpenOCD Out of Sync
|
||||
|
||||
If the following log is output when issuing an OpenOCD command via telnet, it could indicate that OpenOCD and the ESP32 are out of sync. This occurs when the ESP32 is externally reset whilst connected to OpenOCD (e.g., by pressing the EN button).
|
||||
|
||||
```
|
||||
Open On-Chip Debugger
|
||||
> esp gcov dump
|
||||
Target halted. PRO_CPU: PC=0x4008AFF4 (active) APP_CPU: PC=0x400E396E
|
||||
Total trace memory: 16384 bytes
|
||||
Connect targets...
|
||||
Target halted. PRO_CPU: PC=0x400D5D74 (active) APP_CPU: PC=0x400E396E
|
||||
timed out while waiting for target halted / 1 - 2
|
||||
Failed to wait halt on bp target (-4)!
|
||||
Failed to halt targets (-4)!
|
||||
Failed to connect to targets (-4)!
|
||||
```
|
||||
|
||||
This issue can be resolved in the following ways:
|
||||
* Reset the board by issuing the `reset` command via telnet
|
||||
* Restart OpenOCD
|
||||
|
||||
### gcovr not found
|
||||
|
||||
gcovr can be installed from the package database of your operating system or directly as a Python package, e.g:
|
||||
|
||||
```
|
||||
python -m pip install gcovr
|
||||
```
|
||||
@@ -0,0 +1,6 @@
|
||||
idf_component_register(SRCS "some_funcs.c"
|
||||
INCLUDE_DIRS ".")
|
||||
|
||||
set_source_files_properties(some_funcs.c
|
||||
PROPERTIES COMPILE_FLAGS
|
||||
--coverage)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user