From 9c19b1eedb1151828f2ddd98ff9ce1da022cd57d Mon Sep 17 00:00:00 2001 From: dongheng Date: Thu, 21 Feb 2019 19:59:56 +0800 Subject: [PATCH] feat(factory_test): add factory test and document --- components/esp8266/Makefile.projbuild | 16 +- components/freertos/port/esp8266/port.c | 30 +- docs/en/api-guides/factory-test.rst | 190 ++++++++ docs/en/api-guides/index.rst | 1 + .../system/factory-test/Kconfig.projbuild | 10 + examples/system/factory-test/Makefile | 4 + examples/system/factory-test/README.md | 4 + .../factory-test/components/rf_test/Kconfig | 9 + .../components/rf_test/Makefile.projbuild | 2 + .../components/rf_test/component.mk | 8 + .../components/rf_test/include/esp_rftest.h | 81 ++++ .../components/rf_test/include/nano_console.h | 69 +++ .../components/rf_test/lib/librftest.a | Bin 0 -> 112508 bytes .../components/rf_test/nano_console.c | 296 +++++++++++++ .../components/rf_test/rftest_command.c | 417 ++++++++++++++++++ .../system/factory-test/main/component.mk | 6 + examples/system/factory-test/main/main.c | 72 +++ .../system/factory-test/sdkconfig.defaults | 21 + 18 files changed, 1230 insertions(+), 6 deletions(-) create mode 100644 docs/en/api-guides/factory-test.rst create mode 100644 examples/system/factory-test/Kconfig.projbuild create mode 100644 examples/system/factory-test/Makefile create mode 100644 examples/system/factory-test/README.md create mode 100644 examples/system/factory-test/components/rf_test/Kconfig create mode 100644 examples/system/factory-test/components/rf_test/Makefile.projbuild create mode 100644 examples/system/factory-test/components/rf_test/component.mk create mode 100644 examples/system/factory-test/components/rf_test/include/esp_rftest.h create mode 100644 examples/system/factory-test/components/rf_test/include/nano_console.h create mode 100644 examples/system/factory-test/components/rf_test/lib/librftest.a create mode 100644 examples/system/factory-test/components/rf_test/nano_console.c create mode 100644 examples/system/factory-test/components/rf_test/rftest_command.c create mode 100644 examples/system/factory-test/main/component.mk create mode 100644 examples/system/factory-test/main/main.c create mode 100644 examples/system/factory-test/sdkconfig.defaults diff --git a/components/esp8266/Makefile.projbuild b/components/esp8266/Makefile.projbuild index f8de9d8d..469e24df 100644 --- a/components/esp8266/Makefile.projbuild +++ b/components/esp8266/Makefile.projbuild @@ -82,7 +82,7 @@ CFLAGS += -D__ESP_FILE__='"null"' CXXFLAGS += -D__ESP_FILE__='"null"' endif -.PHONY: ota ota-clean +.PHONY: ota ota-clean app2 app2-flash app2-flash-all RAW_BIN := ./build/$(PROJECT_NAME).bin OTA_BIN := ./build/$(PROJECT_NAME).ota.bin @@ -97,6 +97,20 @@ __COMBILE_OTA_BIN := 1 endif endif +app2: all +ifdef CONFIG_ESPTOOLPY_FLASHSIZE_1MB + @rm -f ./build/esp8266/esp8266_out.ld + @export CFLAGS= && export CXXFLAGS= && make APP_OFFSET=$(APP2_OFFSET) APP_SIZE=$(APP2_SIZE) +endif + @echo "To flash all build output, run 'make flash' or:" + @echo $(ESPTOOLPY_WRITE_FLASH) $(APP2_OFFSET) $(APP_BIN) + +app2-flash: app2 + @$(ESPTOOLPY_WRITE_FLASH) $(APP2_OFFSET) $(APP_BIN) + +app2-flash-all: app2 + @$(ESPTOOLPY_WRITE_FLASH) $(patsubst $(APP_OFFSET),$(APP2_OFFSET),$(ESPTOOL_ALL_FLASH_ARGS)) + $(OTA1_BIN): all_binaries @cp $(RAW_BIN) $(OTA1_BIN) @echo [GEN] $(OTA1_BIN) diff --git a/components/freertos/port/esp8266/port.c b/components/freertos/port/esp8266/port.c index 6e4e7091..b0c314d3 100644 --- a/components/freertos/port/esp8266/port.c +++ b/components/freertos/port/esp8266/port.c @@ -248,22 +248,42 @@ void show_critical_info(void) ets_printf("SWReq:%u\n", SWReq); } +#ifdef ESP_DPORT_CLOSE_NMI +static int s_nmi_is_closed; + +void esp_dport_close_nmi(void) +{ + vPortEnterCritical(); + REG_WRITE(PERIPHS_DPORT_BASEADDR, REG_READ(PERIPHS_DPORT_BASEADDR) & ~0x1); + s_nmi_is_closed = 1; + vPortExitCritical(); +} + +#define ESP_NMI_IS_CLOSED() s_nmi_is_closed +#else +#define ESP_NMI_IS_CLOSED() 0 +#endif + void IRAM_ATTR vPortETSIntrLock(void) { if (NMIIrqIsOn == 0) { vPortEnterCritical(); - do { - REG_WRITE(INT_ENA_WDEV, WDEV_TSF0_REACH_INT); - } while(REG_READ(INT_ENA_WDEV) != WDEV_TSF0_REACH_INT); + if (!ESP_NMI_IS_CLOSED()) { + do { + REG_WRITE(INT_ENA_WDEV, WDEV_TSF0_REACH_INT); + } while(REG_READ(INT_ENA_WDEV) != WDEV_TSF0_REACH_INT); + } } } void IRAM_ATTR vPortETSIntrUnlock(void) { if (NMIIrqIsOn == 0) { - extern uint32_t WDEV_INTEREST_EVENT; + if (!ESP_NMI_IS_CLOSED()) { + extern uint32_t WDEV_INTEREST_EVENT; - REG_WRITE(INT_ENA_WDEV, WDEV_INTEREST_EVENT); + REG_WRITE(INT_ENA_WDEV, WDEV_INTEREST_EVENT); + } vPortExitCritical(); } } diff --git a/docs/en/api-guides/factory-test.rst b/docs/en/api-guides/factory-test.rst new file mode 100644 index 00000000..2942c21f --- /dev/null +++ b/docs/en/api-guides/factory-test.rst @@ -0,0 +1,190 @@ +Factory Test +************ + +1. Overview +=========== + +The document introduces how to develop, compile, download and run the factory test firmware. + +The factory test software development kit is also an example of the SDK, and it is located at :example:`examples/system/factory-test`. + +2. Development +============== + +Users can use ready-to-use applications directly, or can also add custom application code into the factory test software development kit. + +More details of adding customer components, please refer to :doc:`Documentation for the GNU Make based build system `. + +Users can just develop the factory test application as normal examples of the SDK. + +2.1 Application code +-------------------- + +Just like other applications, the entry function of factory test application is ``app_main``. It should be added into the source code file of users. +For example, users can add the ``app_main`` into ``main.c`` of the above sample project. + +Users can refer to the source code in file :idf_file:`/examples/system/factory-test/main/main.c` to build custom project. + + +2.2 Linking address +------------------- + +The SDK's partition only supports two applications that named as ``ota_0`` and ``ota_1``. + +In this case, we link the factory test firmware to the partition of ``ota_1``. +So, please do not flash the factory test firmware into the partition of ``ota_0``. + + +3. Compile +========== + +To make the bootloader run the ``ota_1(factory test firmware)``, +please enable the ``GPIO triggers boot from test app partition`` and set the ``correct`` GPIO of your development board in menuconfig:: + + Bootloader config ---> + [*] GPIO triggers boot from test app partition + (12) Number of the GPIO input to boot TEST partition + +Using the partition table file which has two "OTA" definitions partition:: + + Partition Table ---> + Partition Table (Factory app, two OTA definitions) ---> + (X) Factory app, two OTA definitions + +Enable the console which is used for human-computer interaction:: + + Component config ---> + Virtual file system ---> + [*] Using espressif VFS + +Enable pthread for this function:: + + Component config ---> + PThreads ---> + [*] Enable pthread + +Then call command ``make app2`` in the terminal to compile the firmware which is able to run at ``ota_1`` partition. +The ``Make System`` will start compiling bootloader, partition table file, factory test firmware and so on one by one. + + +3.1 Special Commands +==================== + +1. ``make app2``: only compile factory test firmware which is able to run at ``ota_1``, ``with`` bootloader, partition table file and so on + +2. ``make app2-flash``: flash(download) only the factory test firmware which is able to run at ``ota_1``, ``without`` bootloader, partition table file and so on + +3. ``make app2-flash-all``: flash(download) the factory test firmware which is able to run at ``ota_1``, ``with`` bootloader, partition table file and so on + + +4. Download +=========== + +Input command ``make app2-flash-all`` in the terminal to download bootloader, partition table file and factory test firmware which is located at ``ota_1`` one by one. + +If users only want to download factory test firmware, please use command ``make app2-flash`` instead. + + +5. Run +====== + +Please hold the ``correct`` GPIO, which is configured in the menuconfig in Section 3 ``Compile``, to be low level and power on. +Input command ``make monitor`` in the terminal, and then logs will appear like following:: + + ets Jan 8 2013,rst cause:1, boot mode:(3,6) + + load 0x40100000, len 7872, room 16 + 0x40100000: _stext at ??:? + + tail 0 + chksum 0xf1 + load 0x3ffe8408, len 24, room 8 + tail 0 + chksum 0x78 + load 0x3ffe8420, len 3604, room 8 + tail 12 + chksum 0x1b + I (64) boot: ESP-IDF v3.2-dev-354-gba1f90cd-dirty 2nd stage bootloader + I (64) boot: compile time 13:56:17 + I (72) qio_mode: Enabling default flash chip QIO + I (73) boot: SPI Speed : 40MHz + I (80) boot: SPI Mode : QIO + I (86) boot: SPI Flash Size : 2MB + I (92) boot: Partition Table: + I (98) boot: ## Label Usage Type ST Offset Length + I (109) boot: 0 nvs WiFi data 01 02 00009000 00004000 + I (120) boot: 1 otadata OTA data 01 00 0000d000 00002000 + I (132) boot: 2 phy_init RF data 01 01 0000f000 00001000 + I (144) boot: 3 ota_0 OTA app 00 10 00010000 000f0000 + I (155) boot: 4 ota_1 OTA app 00 11 00110000 000f0000 + I (167) boot: End of partition table + I (173) boot: No factory image, trying OTA 0 + I (5180) boot: Detect a boot condition of the test firmware + I (5180) esp_image: segment 0: paddr=0x00110010 vaddr=0x40210010 size=0x37b18 (228120) map + I (5263) esp_image: segment 1: paddr=0x00147b30 vaddr=0x3ffe8000 size=0x00718 ( 1816) load + I (5264) esp_image: segment 2: paddr=0x00148250 vaddr=0x3ffe8718 size=0x0019c ( 412) load + I (5275) esp_image: segment 3: paddr=0x001483f4 vaddr=0x40100000 size=0x084b0 ( 33968) load + 0x40100000: _stext at ??:? + + I (5299) boot: Loaded app from partition at offset 0x110000 + I (5340) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE + I (5340) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE + I (5530) phy_init: phy ver: 1055_12 + I (5530) reset_reason: RTC reset 1 wakeup 0 store 0, reason is 1 + I (5530) factory-test: SDK factory test firmware version:v3.2-dev-354-gba1f90cd-dirty + +Then users can input test commands to start factory testing. + +6. Test Commands +================ + +1. ``rftest_init``:: + + parameters: no + + function: initialize RF to prepare for test + +2. ``tx_contin_func ``:: + + parameter 1: value 1 means that chip transmits packets continuously with 92% duty cycle, + value 0 means that "iqview" test mode + + function: set test mode + +3. ``esp_tx ``:: + + parameter 1: transmit channel which ranges from 1 to 14 + parameter 2: transmit rate which ranges from 0 to 23 + parameter 2: transmit power attenuation which ranges from -127 to 127, unit is 0.25dB + + function: start transmitting Wi-Fi packets + + note 1: command "wifitxout" is the same as "esp_tx" + note 2: the function can be stopped by command "cmdstop" + +4. ``esp_rx ``:: + + parameter 1: transmit channel which ranges from 1 to 14 + parameter 2: transmit rate which ranges from 0 to 23 + + function: start receiving Wi-Fi packets + + note 1: the function can be stopped by command "cmdstop" + +5. ``wifiscwout ``:: + + parameter 1: enable signal, value 1 means enable, value 0 means disable + parameter 2: transmit channel which ranges from 1 to 14 + parameter 3: transmit power attenuation which ranges from -127 to 127, unit is 0.25dB + + function: start transmitting single carrier Wi-Fi packets + + note 1: the function can be stopped by command "cmdstop" + +6. ``cmdstop``:: + + parameters: no + + function: stop transmitting or receiving Wi-Fi packets + + note 1: command "CmdStop" is the same as "cmdstop" diff --git a/docs/en/api-guides/index.rst b/docs/en/api-guides/index.rst index a65df392..6757c5e4 100644 --- a/docs/en/api-guides/index.rst +++ b/docs/en/api-guides/index.rst @@ -9,3 +9,4 @@ API Guides System Task PWM and Sniffer Coexists FOTA from an Old SDK to the New ESP8266 RTOS SDK (IDF Style) + Factory Test diff --git a/examples/system/factory-test/Kconfig.projbuild b/examples/system/factory-test/Kconfig.projbuild new file mode 100644 index 00000000..96c07577 --- /dev/null +++ b/examples/system/factory-test/Kconfig.projbuild @@ -0,0 +1,10 @@ +menu "Factory-test config" + +config FACTORY_TEST + bool "Enable factory test" + default n + select BOOTLOADER_INIT_SPI_FLASH + help + Enable this option, project compiling script will generate factory test firmware. + +endmenu diff --git a/examples/system/factory-test/Makefile b/examples/system/factory-test/Makefile new file mode 100644 index 00000000..bf6a55cf --- /dev/null +++ b/examples/system/factory-test/Makefile @@ -0,0 +1,4 @@ + +PROJECT_NAME := factory-test + +include $(IDF_PATH)/make/project.mk diff --git a/examples/system/factory-test/README.md b/examples/system/factory-test/README.md new file mode 100644 index 00000000..f54fca64 --- /dev/null +++ b/examples/system/factory-test/README.md @@ -0,0 +1,4 @@ + +# Important + +Please refer to the document at *docs/en/api-guides/factory-test.rst*. diff --git a/examples/system/factory-test/components/rf_test/Kconfig b/examples/system/factory-test/components/rf_test/Kconfig new file mode 100644 index 00000000..1ed4fc48 --- /dev/null +++ b/examples/system/factory-test/components/rf_test/Kconfig @@ -0,0 +1,9 @@ +menu "Nano Console" + +config NC_ECHO_CMD + bool "echo input command" + default y + help + Enable this option, the console terminal will echo the input command. + +endmenu diff --git a/examples/system/factory-test/components/rf_test/Makefile.projbuild b/examples/system/factory-test/components/rf_test/Makefile.projbuild new file mode 100644 index 00000000..962ded17 --- /dev/null +++ b/examples/system/factory-test/components/rf_test/Makefile.projbuild @@ -0,0 +1,2 @@ + +CFLAGS += -DESP_DPORT_CLOSE_NMI diff --git a/examples/system/factory-test/components/rf_test/component.mk b/examples/system/factory-test/components/rf_test/component.mk new file mode 100644 index 00000000..eaa7ce0b --- /dev/null +++ b/examples/system/factory-test/components/rf_test/component.mk @@ -0,0 +1,8 @@ +# +# Component Makefile +# + +LIBS := rftest + +COMPONENT_ADD_LDFLAGS += -L$(COMPONENT_PATH)/lib $(addprefix -l,$(LIBS)) +COMPONENT_ADD_LINKER_DEPS += $(patsubst %,$(COMPONENT_PATH)/lib/lib%.a,$(LIBS)) diff --git a/examples/system/factory-test/components/rf_test/include/esp_rftest.h b/examples/system/factory-test/components/rf_test/include/esp_rftest.h new file mode 100644 index 00000000..cc2d4b96 --- /dev/null +++ b/examples/system/factory-test/components/rf_test/include/esp_rftest.h @@ -0,0 +1,81 @@ +// Copyright 2019-2020 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. + +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Initialize RF test module + */ +void rftest_init(void); + +/** + * @brief Set TX testing mode + * + * @param mode testing mode, 1 means continuous Wi-Fi packets transmission with 92% duty cycle + * 0 means default mode for iqview testing + */ +void tx_contin_func(uint8_t mode); + +/** + * @brief TX testing function, continuously sending Wi-Fi packets at "while(1)" loop + * + * @param channel Wi-Fi TX channel, it ranges from 1 to 14 + * @param rate Wi-Fi TX rate, it ranges from 1 to 23 + * @param attenuation Wi-Fi TX power attenuation, it ranges from 1 to 127 and its unit is 0.25dB. + * For example, 1 means 0.25dB, 2 means 0.5 dB and so on. + */ +void esp_tx_func(uint32_t channel, uint32_t rate, uint32_t attenuation); + +/** + * @brief RX testing function, continuously receiving Wi-Fi packets at "while(1)" loop + * + * @param channel Wi-Fi RX channel, it ranges from 1 to 14 + * @param rate Wi-Fi RX rate, it ranges from 1 to 23 + */ +void esp_rx_func(uint32_t channel, uint32_t rate); + +/** + * @brief Single carrier TX testing function + * + * @param enable enable signal, 1 means starting sending, 0 means stopping sending + * @param channel Wi-Fi RX channel, it ranges from 1 to 14 + * @param attenuation Wi-Fi TX power attenuation, it ranges from 1 to 127 and its unit is 0.25dB. + * For example, 1 means 0.25dB, 2 means 0.5 dB and so on. + */ +void wifiscwout_func(uint32_t enable, uint32_t channel, uint32_t attenuation); + +/** + * @brief Stop sending or recieving Wi-Fi packets + * + * @return + * - 0 receiving stop TX commands "cmdstop" or "CmdStop" and TX is stopped + * - 2 receiving error commands and TX will not stop + * - 3 receiving no commands + */ +int cmdstop_callback(void); + +/** + * @brief Register RF test command for console + */ +void esp_console_register_rftest_command(void); + +#ifdef __cplusplus +} +#endif diff --git a/examples/system/factory-test/components/rf_test/include/nano_console.h b/examples/system/factory-test/components/rf_test/include/nano_console.h new file mode 100644 index 00000000..10d3dec0 --- /dev/null +++ b/examples/system/factory-test/components/rf_test/include/nano_console.h @@ -0,0 +1,69 @@ +// Copyright 2019-2020 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. + +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int (*nc_func_t)(int argc, char **argv); + +/** + * @brief Command data structure + */ +typedef struct _nc_cmd { + SLIST_ENTRY(_nc_cmd) entries; //!< queue entry + const char *name; //!< command name + nc_func_t func; //!< command callback function +} nc_cmd_t; + +typedef nc_cmd_t* nc_cmd_handle_t; + +/** + * @brief Initialize nano console + * + * @return 0 if success or others if fail + */ +int nc_init(void); + +/** + * @brief Register a command to nano console core + * + * @param[out] handle created command handle to users + * @param[in] name command name + * @param[in] func command callback function + * + * @return 0 if success or others if fail + * + * @note The function only can be called before "nc_init" + */ +int nc_register_cmd(nc_cmd_handle_t *handle, const char *name, nc_func_t func); + +/** + * @brief Output formated string through nano console I/O stream + * + * @param[in] fmt format string + * @param[in] ... command arguments' value list + * + * @return output string number or negative value if fail + */ +int nc_printf(const char *fmt, ...); + +#ifdef __cplusplus +} +#endif diff --git a/examples/system/factory-test/components/rf_test/lib/librftest.a b/examples/system/factory-test/components/rf_test/lib/librftest.a new file mode 100644 index 0000000000000000000000000000000000000000..8feb0222b2a74aedac2eb66cabc9212e3da9cb35 GIT binary patch literal 112508 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%0D#(XBLQ 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 literal 0 HcmV?d00001 diff --git a/examples/system/factory-test/components/rf_test/nano_console.c b/examples/system/factory-test/components/rf_test/nano_console.c new file mode 100644 index 00000000..af898526 --- /dev/null +++ b/examples/system/factory-test/components/rf_test/nano_console.c @@ -0,0 +1,296 @@ +// Copyright 2019-2020 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. + +#define LOG_LOCAL_LEVEL ESP_LOG_NONE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "esp_log.h" +#include "nano_console.h" + +#define TAG "nano_console" +#define NC_READ_BUF 128 + +static SLIST_HEAD(_nc_cmd_head , _nc_cmd) s_nc_cmd = SLIST_HEAD_INITIALIZER(s_nc_cmd); + +static inline nc_cmd_t *get_cmd(const char *buf) +{ + nc_cmd_t *it; + + SLIST_FOREACH(it, &s_nc_cmd, entries) { + if (!strcmp(it->name, buf)) { + ESP_LOGD(TAG, "%s finds command %s", __func__, buf); + return it; + } + } + + ESP_LOGD(TAG, "%s has not found command %s", __func__, buf); + + return NULL; +} + +static void free_args(uintptr_t *argv) +{ + int num = (int)argv[0]; + + for (int i = 1; i < num; i++) { + if (argv[i]) + free((void *)argv[i]); + else { + ESP_LOGD(TAG, "%s checks command input arguments number %d is empty", __func__, i); + } + } + + free(argv); +} + +static uintptr_t *alloc_args(const char *buf, int num) +{ + const char *p = buf; + uintptr_t *argv_buf; + int cnt = 0; + + for (int i = 0; i < num; i++) { + if (p[i] == '\0') + cnt++; + } + + argv_buf = calloc(1, (cnt + 1) * sizeof(uintptr_t)); + if (!argv_buf) { + ESP_LOGE(TAG, "%s allocates memory for arguments table fail", __func__); + return NULL; + } + argv_buf[0] = (uintptr_t)cnt + 1; + + for (int i = 0; i < cnt; i++) { + int len = strlen(p) + 1; + char *s = malloc(len); + if (!s) { + ESP_LOGE(TAG, "%s allocates memory for arguments %d fail", __func__, i); + goto fail; + } + memcpy(s, p, len); + + p += len; + argv_buf[i + 1] = (uintptr_t)s; + } + + return argv_buf; + +fail: + free_args(argv_buf); + return NULL; +} + +static void *nc_thread_entry(void *p) +{ + int num = 0; + char *pbuf; + + pbuf = malloc(NC_READ_BUF); + if (!pbuf) { + ESP_LOGE(TAG, "%s malloc %d bytes buffer fail", __func__, NC_READ_BUF); + goto nomem; + } + + while (1) { + int ret; + size_t rbytes; + char c; + + rbytes = fread(&c, 1, 1, stdin); + if (rbytes != 1) { + ESP_LOGE(TAG, "%s read character fail", __func__); + goto io_err; + } + + if (num >= NC_READ_BUF - 1) { + ESP_LOGD(TAG, "%s input stream overflows, reset the buffer", __func__); + num = 0; + continue; + } + + if (!isascii(c)) { + ESP_LOGD(TAG, "%s input character is not ASCII", __func__); + continue; + } + + if (c == '\r' || c == '\n') { + nc_cmd_t *cmd; + +#ifdef CONFIG_NC_ECHO_CMD + ret = fwrite(&c, 1, 1, stdout); + if (ret != 1) { + ESP_LOGE(TAG, "%s %d write character fail %d", __func__, __LINE__, ret); + goto io_err; + } +#endif + + if (!num) { + ESP_LOGD(TAG, "%s gets command %s argument fail", __func__, cmd->name); + continue; + } + + if (pbuf[num - 1] != '\0') + pbuf[num++] = '\0'; + + cmd = get_cmd(pbuf); + if (cmd) { + uintptr_t *argv; + + argv = alloc_args(pbuf, num); + if (!argv) { + ESP_LOGE(TAG, "%s gets command %s argument fail", __func__, cmd->name); + num = 0; + continue; + } + + cmd->func((int)argv[0] - 1, (char **)(&argv[1])); + + free_args(argv); + } + + num = 0; + } else if (c == ' ') { + if (num && pbuf[num] != ' ') { + pbuf[num++] = '\0'; +#ifdef CONFIG_NC_ECHO_CMD + ret = fwrite(&c, 1, 1, stdout); + if (ret != 1) { + ESP_LOGE(TAG, "%s %d write character fail %d", __func__, __LINE__, ret); + goto io_err; + } +#endif + } + } else if (c == 0x8 || c == 0x7f) { + if (num) { + num--; +#ifdef CONFIG_NC_ECHO_CMD + char tmp[3] = {c, ' ', c}; + + ret = fwrite(&tmp, 1, 3, stdout); + if (ret != 3) { + ESP_LOGE(TAG, "%s %d write character fail %d", __func__, __LINE__, ret); + goto io_err; + } +#endif + } + } else { + pbuf[num++] = c; +#ifdef CONFIG_NC_ECHO_CMD + ret = fwrite(&c, 1, 1, stdout); + if (ret != 1) { + ESP_LOGE(TAG, "%s %d write character fail %d", __func__, __LINE__, ret); + goto io_err; + } +#endif + } + } + +io_err: + free(pbuf); +nomem: + return (void *)(-ENOMEM); +} + +/** + * @brief Initialize nano console + */ +int nc_init(void) +{ + int ret; + pthread_t tid; + + ret = pthread_create(&tid, NULL, nc_thread_entry, NULL); + if (ret) { + ESP_LOGE(TAG, "%s creates thread fail %d", __func__, ret); + return -1; + } + + return 0; +} + +/** + * @brief Register a command to nano console core + */ +int nc_register_cmd(nc_cmd_handle_t *handle, const char *name, nc_func_t func) +{ + int len; + va_list ap; + nc_cmd_t *cmd; + + cmd = malloc(sizeof(nc_cmd_t)); + if (!cmd) { + ESP_LOGE(TAG, "%s alloc memory %d fail", __func__, __LINE__); + return -ENOMEM; + } + + len = strlen(name) + 1; + cmd->name = malloc(len); + if (!cmd->name) { + ESP_LOGE(TAG, "%s alloc memory %d fail", __func__, __LINE__); + goto nomem; + } + + memcpy((char *)cmd->name, name, len); + cmd->func = func; + + SLIST_INSERT_HEAD(&s_nc_cmd, cmd, entries); + + *handle = cmd; + + va_end(ap); + + return 0; + +nomem: + free(cmd); + return -ENOMEM; +} + +/** + * @brief Output formated string through nano console I/O stream + */ +int nc_printf(const char *fmt, ...) +{ + va_list ap; + char *pbuf; + + va_start(ap, fmt); + + int ret = vasprintf(&pbuf, fmt, ap); + if (ret < 0) + goto fail; + + ret = fwrite(pbuf, 1, ret, stdout); + + free(pbuf); + + va_end(ap); + + return ret; + +fail: + va_end(ap); + return -ENOMEM; +} diff --git a/examples/system/factory-test/components/rf_test/rftest_command.c b/examples/system/factory-test/components/rf_test/rftest_command.c new file mode 100644 index 00000000..1241c397 --- /dev/null +++ b/examples/system/factory-test/components/rf_test/rftest_command.c @@ -0,0 +1,417 @@ +// Copyright 2019-2020 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 +#include +#include +#include "esp_rftest.h" +#include "esp_log.h" +#include "esp_wifi.h" +#include "esp8266/eagle_soc.h" +#include "FreeRTOS.h" +#include "task.h" +#include "driver/soc.h" +#include "nano_console.h" + +#define TAG "factory-test" + +typedef struct tx_param { + uint32_t channel; + uint32_t rate; + uint32_t attenuation; +} tx_param_t; + +typedef struct rx_param { + uint32_t channel; + uint32_t rate; +} rx_param_t; + +typedef struct wifiscwout_param { + uint32_t en; + uint32_t channel; + uint32_t attenuation; +} wifiscwout_param_t; + +static int s_cmdstop = 0; + +static int set_wifi_work(void) +{ + int ret; + esp_irqflag_t flag; + + flag = soc_save_local_irq(); + + if (s_cmdstop == 0) { + s_cmdstop = 3; + ret = 0; + } else + ret = -EINPROGRESS; + + soc_restore_local_irq(flag); + + return ret; +} + +static int set_wifi_free(void) +{ + esp_irqflag_t flag; + + flag = soc_save_local_irq(); + + s_cmdstop = 0; + + soc_restore_local_irq(flag); + + return 0; +} + +static int test_rftest_init(int argc, char **argv) +{ + if (argc != 1) { + ESP_LOGE(TAG, "%s %d command is error", __func__, __LINE__); + return -EINVAL; + } + + ESP_LOGD(__func__, "%s start initializing WiFi test", __func__); + + if (set_wifi_work()) + goto exit; + + rftest_init(); + + set_wifi_free(); + + ESP_LOGD(__func__, "%s end initializing WiFi test", __func__); + + return 0; + +exit: + ESP_LOGD(__func__, "%s fail to initialize WiFi test", __func__); + + return -EINPROGRESS; +} + +static int test_tx_contin_en(int argc, char **argv) +{ + if (argc != 2) { + ESP_LOGE(TAG, "%s %d command is error", __func__, __LINE__); + return -EINVAL; + } + + int mode = atoi(argv[1]); + if (mode < 0 || mode > 1) { + ESP_LOGE(TAG, "%s %d command is error", __func__, __LINE__); + return -EINVAL; + } + + ESP_LOGD(__func__, "%s start setting mode '%d'", __func__ , mode); + + if (set_wifi_work()) + goto exit; + + tx_contin_func(mode); + + set_wifi_free(); + + ESP_LOGD(__func__, "%s end setting mode '%d'", __func__ , mode); + + return 0; + +exit: + ESP_LOGD(__func__, "%s fail to set mode '%d'", __func__ , mode); + + return -EINPROGRESS; +} + +static void test_wifi_tx_thread(void *param) +{ + tx_param_t *tx_param = (tx_param_t *)param; + + esp_tx_func(tx_param->channel, tx_param->rate, tx_param->attenuation); + + free(tx_param); + + set_wifi_free(); + + vTaskDelete(NULL); +} + +static int test_esp_tx_func(int argc, char **argv) +{ + if (argc != 4) { + ESP_LOGE(TAG, "%s %d command is error", __func__, __LINE__); + return -EINVAL; + } + + int channel = atoi(argv[1]); + if (channel <= 0 || channel > 14) { + ESP_LOGE(TAG, "%s %d command is error", __func__, __LINE__); + return -EINVAL; + } + + int rate = atoi(argv[2]); + if (rate < 0 || rate > 23) { + ESP_LOGE(TAG, "%s %d command is error", __func__, __LINE__); + return -EINVAL; + } + + int attenuation = atoi(argv[3]); + if (attenuation < -127 || attenuation > 127) { + ESP_LOGE(TAG, "%s %d command is error", __func__, __LINE__); + return -EINVAL; + } + + ESP_LOGD(__func__, "%s start creating task with channel '%d' rate '%d' attenuation '%d'", __func__, channel, rate, attenuation); + + if (set_wifi_work()) + goto exit; + + tx_param_t *tx_param = malloc(sizeof(tx_param_t)); + if (!tx_param) + goto exit; + + tx_param->channel = channel; + tx_param->rate = rate; + tx_param->attenuation = attenuation; + + const size_t wifi_tx_stk_size = 4096; + const size_t wifi_tx_priority = 3; + + BaseType_t ret = xTaskCreate(test_wifi_tx_thread, "wifi_tx", wifi_tx_stk_size, tx_param, wifi_tx_priority, NULL); + if (ret != pdPASS) + goto task_err; + + ESP_LOGD(__func__, "%s end creating task with channel '%d' rate '%d' attenuation '%d'", __func__, channel, rate, attenuation); + + return 0; + +task_err: + free(tx_param); +exit: + ESP_LOGD(__func__, "%s fail to create task with channel '%d' rate '%d' attenuation '%d'", __func__, channel, rate, attenuation); + + return -ENOMEM; +} + +static void test_wifi_rx_thread(void *param) +{ + rx_param_t *rx_param = (rx_param_t *)param; + + esp_rx_func(rx_param->channel, rx_param->rate); + + free(rx_param); + + set_wifi_free(); + + vTaskDelete(NULL); +} + +static int test_esp_rx_func(int argc, char **argv) +{ + if (argc != 3) { + ESP_LOGE(TAG, "%s %d command is error", __func__, __LINE__); + return -EINVAL; + } + + int channel = atoi(argv[1]); + if (channel <= 0 || channel > 14) { + ESP_LOGE(TAG, "%s %d command is error", __func__, __LINE__); + return -EINVAL; + } + + int rate = atoi(argv[2]); + if (rate < 0 || rate > 23) { + ESP_LOGE(TAG, "%s %d command is error", __func__, __LINE__); + return -EINVAL; + } + + ESP_LOGD(__func__, "%s start creating task with channel '%d' rate '%d'", __func__, channel, rate); + + if (set_wifi_work()) + goto exit; + + rx_param_t *rx_param = malloc(sizeof(rx_param_t)); + if (!rx_param) + goto exit; + + rx_param->channel = channel; + rx_param->rate = rate; + + const size_t wifi_rx_stk_size = 4096; + const size_t wifi_rx_priority = 3; + + BaseType_t ret = xTaskCreate(test_wifi_rx_thread, "wifi_rx", wifi_rx_stk_size, rx_param, wifi_rx_priority, NULL); + if (ret != pdPASS) + goto task_err; + + ESP_LOGD(__func__, "%s end creating task with channel '%d' rate '%d'", __func__, channel, rate); + + return 0; + +task_err: + free(rx_param); +exit: + ESP_LOGD(__func__, "%s fail to create task with channel '%d' rate '%d'", __func__, channel, rate); + + return -ENOMEM; +} + +static void test_wifi_wifiscwout_thread(void *param) +{ + wifiscwout_param_t *wifiscwout_param = (wifiscwout_param_t *)param; + + wifiscwout_func(wifiscwout_param->en, wifiscwout_param->channel, wifiscwout_param->attenuation); + + free(wifiscwout_param); + + set_wifi_free(); + + vTaskDelete(NULL); +} + +static int test_wifiscwout_func(int argc, char **argv) +{ + if (argc != 4) { + ESP_LOGE(TAG, "%s %d command is error", __func__, __LINE__); + return -EINVAL; + } + + int en = atoi(argv[1]); + if (en < 0 || en > 2) { + ESP_LOGE(TAG, "%s %d command is error", __func__, __LINE__); + return -EINVAL; + } + + int channel = atoi(argv[2]); + if (channel <= 0 || channel > 14) { + ESP_LOGE(TAG, "%s %d command is error", __func__, __LINE__); + return -EINVAL; + } + + int attenuation = atoi(argv[3]); + if (attenuation < -127 || attenuation > 127) { + ESP_LOGE(TAG, "%s %d command is error", __func__, __LINE__); + return -EINVAL; + } + + ESP_LOGD(__func__, "%s start creating task with enable '%d' channel '%d' attenuation '%d'", __func__, en, channel, attenuation); + + if (set_wifi_work()) + goto exit; + + wifiscwout_param_t *wifiscwout_param = malloc(sizeof(wifiscwout_param_t)); + if (!wifiscwout_param) + goto exit; + + wifiscwout_param->en = en; + wifiscwout_param->channel = channel; + wifiscwout_param->attenuation = attenuation; + + const size_t wifi_wifiscwout_stk_size = 4096; + const size_t wifi_wifiscwout_priority = 3; + + BaseType_t ret = xTaskCreate(test_wifi_wifiscwout_thread, "wifi_tx", wifi_wifiscwout_stk_size, wifiscwout_param, wifi_wifiscwout_priority, NULL); + if (ret != pdPASS) + goto task_err; + + ESP_LOGD(__func__, "%s end creating task with enable '%d' channel '%d' attenuation '%d'", __func__, en, channel, attenuation); + + return 0; + +task_err: + free(wifiscwout_param); +exit: + ESP_LOGD(__func__, "%s fail to create task with enable '%d' channel '%d' attenuation '%d'", __func__, en, channel, attenuation); + + return -ENOMEM; +} + +static int test_cmdstop_func(int argc, char **argv) +{ + if (argc != 1) { + ESP_LOGE(TAG, "%s %d command is error", __func__, __LINE__); + return -EINVAL; + } + + ESP_LOGD(__func__, "%s cmdstop '%d'", __func__, s_cmdstop); + + s_cmdstop = 0; + + ESP_LOGD(__func__, "status is %x\n", REG_READ(INT_ENA_WDEV)); + + return 0; +} + +static void register_rftest_init(void) +{ + nc_cmd_t *nc_cmd; + + ESP_ERROR_CHECK(nc_register_cmd(&nc_cmd, "rftest_init", test_rftest_init)); +} + +static void register_tx_contin_en(void) +{ + nc_cmd_handle_t nc_cmd; + + ESP_ERROR_CHECK(nc_register_cmd(&nc_cmd, "tx_contin_en", test_tx_contin_en)); +} + +static void register_esp_tx_func(void) +{ + nc_cmd_handle_t nc_cmd; + + ESP_ERROR_CHECK(nc_register_cmd(&nc_cmd, "esp_tx", test_esp_tx_func)); + ESP_ERROR_CHECK(nc_register_cmd(&nc_cmd, "wifitxout", test_esp_tx_func)); +} + +static void register_esp_rx_func(void) +{ + nc_cmd_handle_t nc_cmd; + + ESP_ERROR_CHECK(nc_register_cmd(&nc_cmd, "esp_rx", test_esp_rx_func)); +} + +static void register_wifiscwout_func(void) +{ + nc_cmd_handle_t nc_cmd; + + ESP_ERROR_CHECK(nc_register_cmd(&nc_cmd, "wifiscwout", test_wifiscwout_func)); +} + +static void register_cmdstop_func(void) +{ + nc_cmd_handle_t nc_cmd; + + ESP_ERROR_CHECK(nc_register_cmd(&nc_cmd, "cmdstop", test_cmdstop_func)); + ESP_ERROR_CHECK(nc_register_cmd(&nc_cmd, "CmdStop", test_cmdstop_func)); +} + +void esp_console_register_rftest_command(void) +{ + extern void esp_dport_close_nmi(void); + + esp_dport_close_nmi(); + + register_rftest_init(); + register_tx_contin_en(); + register_esp_tx_func(); + register_esp_rx_func(); + register_wifiscwout_func(); + register_cmdstop_func(); +} + +int __attribute__((weak)) cmdstop_callback(void) +{ + return s_cmdstop; +} diff --git a/examples/system/factory-test/main/component.mk b/examples/system/factory-test/main/component.mk new file mode 100644 index 00000000..de55b61f --- /dev/null +++ b/examples/system/factory-test/main/component.mk @@ -0,0 +1,6 @@ +# +# Main factory-test Makefile. +# +# This is basically the same as a component makefile, but in the case of the factory-test +# we pull in factory-test-specific linker arguments. +# diff --git a/examples/system/factory-test/main/main.c b/examples/system/factory-test/main/main.c new file mode 100644 index 00000000..772bd35d --- /dev/null +++ b/examples/system/factory-test/main/main.c @@ -0,0 +1,72 @@ +// Copyright 2019-2020 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 +#include +#include "esp_system.h" +#include "esp_rftest.h" +#include "esp_log.h" +#include "esp_vfs_dev.h" +#include "FreeRTOS.h" +#include "driver/uart.h" +#include "nano_console.h" +#include "esp_libc.h" + +#ifndef ESP_FACTORY_TEST_EXTRA_COMPONENTS + +#define CONFIG_CONSOLE_UART_NUM 0 + +#define TAG "factory-test" + +static void initialize_console() +{ + /* Disable buffering on stdin */ + setvbuf(stdin, NULL, _IONBF, 0); + + /* Minicom, screen, idf_monitor send CR when ENTER key is pressed */ + esp_vfs_dev_uart_set_rx_line_endings(ESP_LINE_ENDINGS_CR); + /* Move the caret to the beginning of the next line on '\n' */ + esp_vfs_dev_uart_set_tx_line_endings(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. + */ + uart_config_t uart_config = { + .baud_rate = CONFIG_CONSOLE_UART_BAUDRATE, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + }; + ESP_ERROR_CHECK(uart_param_config(CONFIG_CONSOLE_UART_NUM, &uart_config)); + + /* Install UART driver for interrupt-driven reads and writes */ + ESP_ERROR_CHECK(uart_driver_install(CONFIG_CONSOLE_UART_NUM, + 256, 0, 0, NULL)); + + /* Tell VFS to use UART driver */ + esp_vfs_dev_uart_use_driver(CONFIG_CONSOLE_UART_NUM); + + esp_console_register_rftest_command(); + + ESP_ERROR_CHECK(nc_init()); +} + +void __attribute__((weak)) app_main(void) +{ + ESP_LOGI(TAG, "SDK factory test firmware version:%s\n", esp_get_idf_version()); + + initialize_console(); +} + +#endif /* ESP_FACTORY_TEST_EXTRA_COMPONENTS */ diff --git a/examples/system/factory-test/sdkconfig.defaults b/examples/system/factory-test/sdkconfig.defaults new file mode 100644 index 00000000..5ca96d27 --- /dev/null +++ b/examples/system/factory-test/sdkconfig.defaults @@ -0,0 +1,21 @@ +# +# Virtual file system +# +CONFIG_USING_ESP_VFS=y + +# +# PThreads +# +CONFIG_ENABLE_PTHREAD=y + +# +# Partition Table +# +CONFIG_PARTITION_TABLE_TWO_OTA=y + +# +# Bootloader config +# +CONFIG_BOOTLOADER_APP_TEST=y +CONFIG_BOOTLOADER_APP_TEST_IN_OTA_1=y +CONFIG_BOOTLOADER_NUM_PIN_APP_TEST=12