/** * About test environment UT_T1_GPIO: * Please connect GPIO18 and GPIO19 */ #include #include #include "esp_system.h" #include "esp_sleep.h" #include "unity.h" #include "driver/gpio.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" #include "sdkconfig.h" #include "esp_rom_uart.h" #include "esp_rom_sys.h" #include "test_utils.h" #define WAKE_UP_IGNORE 1 // gpio_wakeup function development is not completed yet, set it deprecated. #if CONFIG_IDF_TARGET_ESP32 #define TEST_GPIO_EXT_OUT_IO 18 // default output GPIO #define TEST_GPIO_EXT_IN_IO 19 // default input GPIO #define TEST_GPIO_OUTPUT_PIN 23 #define TEST_GPIO_INPUT_ONLY_PIN 34 #define TEST_GPIO_OUTPUT_MAX GPIO_NUM_34 #elif CONFIG_IDF_TARGET_ESP32S2 // ESP32_S2 DEVKIC uses IO19 and IO20 as USB functions, so it is necessary to avoid using IO19, otherwise GPIO io pull up/down function cannot pass // Also the first version of ESP32-S2-Saola has pullup issue on GPIO18, which is tied to 3V3 on the // runner. Also avoid using GPIO18. #define TEST_GPIO_EXT_OUT_IO 17 // default output GPIO #define TEST_GPIO_EXT_IN_IO 21 // default input GPIO #define TEST_GPIO_OUTPUT_PIN 12 #define TEST_GPIO_INPUT_ONLY_PIN 46 #define TEST_GPIO_OUTPUT_MAX GPIO_NUM_46 #elif CONFIG_IDF_TARGET_ESP32S3 #define TEST_GPIO_EXT_OUT_IO 19 // default output GPIO #define TEST_GPIO_EXT_IN_IO 20 // default input GPIO #define TEST_GPIO_OUTPUT_PIN 12 #define TEST_GPIO_INPUT_ONLY_PIN 46 #define TEST_GPIO_OUTPUT_MAX GPIO_NUM_MAX #elif CONFIG_IDF_TARGET_ESP32C3 #define TEST_GPIO_EXT_OUT_IO 2 // default output GPIO #define TEST_GPIO_EXT_IN_IO 3 // default input GPIO #define TEST_GPIO_OUTPUT_PIN 1 #define TEST_GPIO_OUTPUT_MAX GPIO_NUM_MAX #endif // define public test io on all boards(esp32, esp32s2, esp32s3, esp32c3) #define TEST_IO_9 GPIO_NUM_9 #define TEST_IO_10 GPIO_NUM_10 static volatile int disable_intr_times = 0; // use this to calculate how many times it go into interrupt static volatile int level_intr_times = 0; // use this to get how many times the level interrupt happened static volatile int edge_intr_times = 0; // use this to get how many times the edge interrupt happened #if !WAKE_UP_IGNORE static bool wake_up_result = false; // use this to judge the wake up event happen or not #endif /** * do some initialization operation in this function * @param num: it is the destination GPIO wanted to be initialized * */ static gpio_config_t init_io(gpio_num_t num) { TEST_ASSERT(num < TEST_GPIO_OUTPUT_MAX); gpio_config_t io_conf; io_conf.intr_type = GPIO_INTR_DISABLE; io_conf.mode = GPIO_MODE_OUTPUT; io_conf.pin_bit_mask = (1ULL << num); io_conf.pull_down_en = 0; io_conf.pull_up_en = 0; return io_conf; } // edge interrupt event __attribute__((unused)) static void gpio_isr_edge_handler(void* arg) { uint32_t gpio_num = (uint32_t) arg; esp_rom_printf("GPIO[%d] intr on core %d, val: %d\n", gpio_num, cpu_hal_get_core_id(), gpio_get_level(gpio_num)); edge_intr_times++; } #if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2, ESP32S3, ESP32C3) //No runners // level interrupt event with "gpio_intr_disable" static void gpio_isr_level_handler(void* arg) { uint32_t gpio_num = (uint32_t) arg; disable_intr_times++; esp_rom_printf("GPIO[%d] intr, val: %d, disable_intr_times = %d\n", gpio_num, gpio_get_level(gpio_num), disable_intr_times); gpio_intr_disable(gpio_num); } // level interrupt event static void gpio_isr_level_handler2(void* arg) { uint32_t gpio_num = (uint32_t) arg; level_intr_times++; esp_rom_printf("GPIO[%d] intr, val: %d\n", gpio_num, gpio_get_level(gpio_num)); if(gpio_get_level(gpio_num)) { gpio_set_level(TEST_GPIO_EXT_OUT_IO, 0); }else{ gpio_set_level(TEST_GPIO_EXT_OUT_IO, 1); } esp_rom_printf("GPIO[%d] intr, val: %d, level_intr_times = %d\n", TEST_GPIO_EXT_OUT_IO, gpio_get_level(TEST_GPIO_EXT_OUT_IO), level_intr_times); esp_rom_printf("GPIO[%d] intr, val: %d, level_intr_times = %d\n", gpio_num, gpio_get_level(gpio_num), level_intr_times); } #endif //!TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2, ESP32S3, ESP32C3) #if !WAKE_UP_IGNORE // get result of waking up or not static void sleep_wake_up(void *arg) { gpio_config_t io_config = init_io(TEST_GPIO_EXT_IN_IO); io_config.mode = GPIO_MODE_INPUT; gpio_config(&io_config); TEST_ESP_OK(gpio_wakeup_enable(TEST_GPIO_EXT_IN_IO, GPIO_INTR_HIGH_LEVEL)); esp_light_sleep_start(); wake_up_result = true; } // wake up light sleep event static void trigger_wake_up(void *arg) { gpio_config_t io_config = init_io(TEST_GPIO_EXT_OUT_IO); gpio_config(&io_config); gpio_set_level(TEST_GPIO_EXT_OUT_IO, 0); gpio_install_isr_service(0); gpio_isr_handler_add(TEST_GPIO_EXT_OUT_IO, gpio_isr_level_handler, (void*) TEST_GPIO_EXT_IN_IO); gpio_set_level(TEST_GPIO_EXT_OUT_IO, 1); vTaskDelay(100 / portTICK_RATE_MS); } #endif //!WAKE_UP_IGNORE static void prompt_to_continue(const char* str) { printf("%s , please press \"Enter\" to go on!\n", str); char sign[5] = {0}; while(strlen(sign) == 0) { /* Flush anything already in the RX buffer */ while(esp_rom_uart_rx_one_char((uint8_t *) sign) == ETS_OK) { } /* Read line */ esp_rom_uart_rx_string((uint8_t*) sign, sizeof(sign) - 1); } } static void drive_capability_set_get(gpio_num_t num, gpio_drive_cap_t capability) { gpio_config_t pad_io = init_io(num); TEST_ESP_OK(gpio_config(&pad_io)); TEST_ASSERT(gpio_set_drive_capability(num, GPIO_DRIVE_CAP_MAX) == ESP_ERR_INVALID_ARG); gpio_drive_cap_t cap; TEST_ESP_OK(gpio_set_drive_capability(num, capability)); TEST_ESP_OK(gpio_get_drive_capability(num, &cap)); TEST_ASSERT_EQUAL_INT(cap, capability); } // test the basic configuration function with right parameters and error parameters TEST_CASE("GPIO config parameters test", "[gpio]") { //error param test //ESP32 test 41 bit, ESP32-S2 test 48 bit gpio_config_t io_config = { 0 }; io_config.intr_type = GPIO_INTR_DISABLE; io_config.pin_bit_mask = ((uint64_t)1<<(GPIO_NUM_MAX+1)); TEST_ASSERT(gpio_config(&io_config) == ESP_ERR_INVALID_ARG); // test 0 io_config.pin_bit_mask = 0; TEST_ASSERT(gpio_config(&io_config) == ESP_ERR_INVALID_ARG); //ESP32 test 40 bit, ESP32-S2 test 47 bit io_config.pin_bit_mask = ((uint64_t)1< 10) { break; } vTaskDelay(100 / portTICK_RATE_MS); } vTaskDelay(100 / portTICK_RATE_MS); // for falling rdge in GPIO_INTR_ANYEDGE while(1) { level = level - 1; gpio_set_level(TEST_GPIO_EXT_OUT_IO, level/5); if(level < 0) { break; } vTaskDelay(100 / portTICK_RATE_MS); } vTaskDelay(100 / portTICK_RATE_MS); TEST_ASSERT_EQUAL_INT(edge_intr_times, 2); vTaskDelay(100 / portTICK_RATE_MS); gpio_isr_handler_remove(TEST_GPIO_EXT_IN_IO); gpio_uninstall_isr_service(); } TEST_CASE("GPIO input high level trigger, cut the interrupt source exit interrupt test", "[gpio][test_env=UT_T1_GPIO]") { level_intr_times=0; gpio_config_t output_io = init_io(TEST_GPIO_EXT_OUT_IO); gpio_config_t input_io = init_io(TEST_GPIO_EXT_IN_IO); input_io.intr_type = GPIO_INTR_POSEDGE; input_io.mode = GPIO_MODE_INPUT; input_io.pull_up_en = 1; TEST_ESP_OK(gpio_config(&output_io)); TEST_ESP_OK(gpio_config(&input_io)); TEST_ESP_OK(gpio_set_level(TEST_GPIO_EXT_OUT_IO, 0)); gpio_set_intr_type(TEST_GPIO_EXT_IN_IO, GPIO_INTR_HIGH_LEVEL); gpio_install_isr_service(0); gpio_isr_handler_add(TEST_GPIO_EXT_IN_IO, gpio_isr_level_handler2, (void*) TEST_GPIO_EXT_IN_IO); gpio_set_level(TEST_GPIO_EXT_OUT_IO, 1); vTaskDelay(100 / portTICK_RATE_MS); TEST_ASSERT_EQUAL_INT_MESSAGE(level_intr_times, 1, "go into high-level interrupt more than once with cur interrupt source way"); gpio_isr_handler_remove(TEST_GPIO_EXT_IN_IO); gpio_uninstall_isr_service(); } TEST_CASE("GPIO low level interrupt test", "[gpio][test_env=UT_T1_GPIO]") { disable_intr_times=0; gpio_config_t output_io = init_io(TEST_GPIO_EXT_OUT_IO); gpio_config_t input_io = init_io(TEST_GPIO_EXT_IN_IO); input_io.intr_type = GPIO_INTR_POSEDGE; input_io.mode = GPIO_MODE_INPUT; input_io.pull_up_en = 1; TEST_ESP_OK(gpio_config(&output_io)); TEST_ESP_OK(gpio_config(&input_io)); TEST_ESP_OK(gpio_set_level(TEST_GPIO_EXT_OUT_IO, 1)); gpio_set_intr_type(TEST_GPIO_EXT_IN_IO, GPIO_INTR_LOW_LEVEL); gpio_install_isr_service(0); gpio_isr_handler_add(TEST_GPIO_EXT_IN_IO, gpio_isr_level_handler, (void*) TEST_GPIO_EXT_IN_IO); gpio_set_level(TEST_GPIO_EXT_OUT_IO, 0); printf("get level:%d\n",gpio_get_level(TEST_GPIO_EXT_IN_IO)); vTaskDelay(100 / portTICK_RATE_MS); TEST_ASSERT_EQUAL_INT_MESSAGE(disable_intr_times, 1, "go into low-level interrupt more than once with disable way"); gpio_isr_handler_remove(TEST_GPIO_EXT_IN_IO); gpio_uninstall_isr_service(); } TEST_CASE("GPIO multi-level interrupt test, to cut the interrupt source exit interrupt ", "[gpio][test_env=UT_T1_GPIO]") { level_intr_times=0; gpio_config_t output_io = init_io(TEST_GPIO_EXT_OUT_IO); gpio_config_t input_io = init_io(TEST_GPIO_EXT_IN_IO); input_io.intr_type = GPIO_INTR_POSEDGE; input_io.mode = GPIO_MODE_INPUT; input_io.pull_up_en = 1; TEST_ESP_OK(gpio_config(&output_io)); TEST_ESP_OK(gpio_config(&input_io)); TEST_ESP_OK(gpio_set_level(TEST_GPIO_EXT_OUT_IO, 0)); gpio_set_intr_type(TEST_GPIO_EXT_IN_IO, GPIO_INTR_HIGH_LEVEL); gpio_install_isr_service(0); gpio_isr_handler_add(TEST_GPIO_EXT_IN_IO, gpio_isr_level_handler2, (void*) TEST_GPIO_EXT_IN_IO); gpio_set_level(TEST_GPIO_EXT_OUT_IO, 1); vTaskDelay(100 / portTICK_RATE_MS); TEST_ASSERT_EQUAL_INT_MESSAGE(level_intr_times, 1, "go into high-level interrupt more than once with cur interrupt source way"); gpio_set_level(TEST_GPIO_EXT_OUT_IO, 1); vTaskDelay(200 / portTICK_RATE_MS); TEST_ASSERT_EQUAL_INT_MESSAGE(level_intr_times, 2, "go into high-level interrupt more than once with cur interrupt source way"); gpio_isr_handler_remove(TEST_GPIO_EXT_IN_IO); gpio_uninstall_isr_service(); } TEST_CASE("GPIO enable and disable interrupt test", "[gpio][test_env=UT_T1_GPIO]") { disable_intr_times = 0; gpio_config_t output_io = init_io(TEST_GPIO_EXT_OUT_IO); gpio_config_t input_io = init_io(TEST_GPIO_EXT_IN_IO); input_io.intr_type = GPIO_INTR_POSEDGE; input_io.mode = GPIO_MODE_INPUT; input_io.pull_up_en = 1; TEST_ESP_OK(gpio_config(&output_io)); TEST_ESP_OK(gpio_config(&input_io)); TEST_ESP_OK(gpio_set_level(TEST_GPIO_EXT_OUT_IO, 0)); // Because of GPIO_INTR_HIGH_LEVEL interrupt, 0 must be set first TEST_ESP_OK(gpio_set_intr_type(TEST_GPIO_EXT_IN_IO, GPIO_INTR_HIGH_LEVEL)); TEST_ESP_OK(gpio_install_isr_service(0)); TEST_ESP_OK(gpio_isr_handler_add(TEST_GPIO_EXT_IN_IO, gpio_isr_level_handler, (void*) TEST_GPIO_EXT_IN_IO)); TEST_ESP_OK(gpio_set_level(TEST_GPIO_EXT_OUT_IO, 1)); TEST_ESP_OK(gpio_isr_handler_remove(TEST_GPIO_EXT_IN_IO)); TEST_ESP_OK(gpio_set_level(TEST_GPIO_EXT_OUT_IO, 0)); TEST_ASSERT_EQUAL_INT_MESSAGE(disable_intr_times, 1, "go into high-level interrupt more than once with disable way"); // not install service now vTaskDelay(100 / portTICK_RATE_MS); TEST_ESP_OK(gpio_intr_disable(TEST_GPIO_EXT_IN_IO)); TEST_ESP_OK(gpio_set_level(TEST_GPIO_EXT_OUT_IO, 1)); TEST_ASSERT_EQUAL_INT_MESSAGE(disable_intr_times, 1, "disable interrupt does not work, still go into interrupt!"); gpio_uninstall_isr_service(); //uninstall the service TEST_ASSERT(gpio_isr_handler_add(TEST_GPIO_EXT_IN_IO, gpio_isr_level_handler, (void*) TEST_GPIO_EXT_IN_IO) == ESP_ERR_INVALID_STATE); TEST_ASSERT(gpio_isr_handler_remove(TEST_GPIO_EXT_IN_IO) == ESP_ERR_INVALID_STATE); } #endif //DISABLED_FOR_TARGETS(ESP32S2, ESP32S3, ESP32C3) #if !CONFIG_FREERTOS_UNICORE static void install_isr_service_task(void *arg) { uint32_t gpio_num = (uint32_t) arg; //rising edge intr TEST_ESP_OK(gpio_set_intr_type(gpio_num, GPIO_INTR_POSEDGE)); TEST_ESP_OK(gpio_install_isr_service(0)); gpio_isr_handler_add(gpio_num, gpio_isr_edge_handler, (void *) gpio_num); vTaskSuspend(NULL); } TEST_CASE("GPIO interrupt on other CPUs test", "[gpio]") { TaskHandle_t gpio_task_handle; gpio_config_t input_output_io = init_io(TEST_GPIO_EXT_OUT_IO); input_output_io.mode = GPIO_MODE_INPUT_OUTPUT; input_output_io.pull_up_en = 1; TEST_ESP_OK(gpio_config(&input_output_io)); for (int cpu_num = 1; cpu_num < portNUM_PROCESSORS; ++cpu_num) { // We assume unit-test task is running on core 0, so we install gpio interrupt on other cores edge_intr_times = 0; TEST_ESP_OK(gpio_set_level(TEST_GPIO_EXT_OUT_IO, 0)); xTaskCreatePinnedToCore(install_isr_service_task, "install_isr_service_task", 2048, (void *) TEST_GPIO_EXT_OUT_IO, 1, &gpio_task_handle, cpu_num); vTaskDelay(200 / portTICK_RATE_MS); TEST_ESP_OK(gpio_set_level(TEST_GPIO_EXT_OUT_IO, 1)); vTaskDelay(100 / portTICK_RATE_MS); TEST_ASSERT_EQUAL_INT(edge_intr_times, 1); gpio_isr_handler_remove(TEST_GPIO_EXT_OUT_IO); gpio_uninstall_isr_service(); test_utils_task_delete(gpio_task_handle); } } #endif //!CONFIG_FREERTOS_UNICORE // ESP32 Connect GPIO18 with GPIO19, ESP32-S2 Connect GPIO17 with GPIO21, // ESP32-S3 Connect GPIO19 with GPIO20, ESP32C3 Connect GPIO2 with GPIO3 // use multimeter to test the voltage, so it is ignored in CI TEST_CASE("GPIO set gpio output level test", "[gpio][ignore]") { gpio_config_t io_conf; io_conf.intr_type = GPIO_INTR_DISABLE; io_conf.mode = GPIO_MODE_OUTPUT; io_conf.pin_bit_mask = (1<gpio_num, gpio_get_level(param->gpio_num)); param->isr_cnt++; } /** The previous GPIO interrupt service routine polls the interrupt raw status register to find the GPIO that triggered the interrupt. * But this will incorrectly handle the interrupt disabled GPIOs, because the raw interrupt status register can still be set when * the trigger signal arrives, even if the interrupt is disabled. * First on the core 0: * 1. Configure the GPIO9 and GPIO10(ESP32, ESP32C3)/GPIO21(ESP32-S2) input_output mode. * 2. Enable GPIO9 dual edge triggered interrupt, enable GPIO10(ESP32, ESP32C3)/GPIO21(ESP32-S2) falling edge triggered interrupt. * 3. Trigger GPIO9 interrupt, than disable the GPIO18 interrupt, and than trigger GPIO18 again(This time will not respond to the interrupt). * 4. Trigger GPIO10(ESP32, ESP32C3)/GPIO21(ESP32-S2) interrupt. * If the bug is not fixed, you will see, in the step 4, the interrupt of GPIO9 will also respond. */ TEST_CASE("GPIO ISR service test", "[gpio][ignore]") { gpio_isr_param_t io9_param = { .gpio_num = TEST_IO_9, .isr_cnt = 0, }; gpio_isr_param_t io10_param = { .gpio_num = TEST_IO_10, .isr_cnt = 0, }; gpio_config_t io_conf; io_conf.intr_type = GPIO_INTR_DISABLE; io_conf.mode = GPIO_MODE_INPUT_OUTPUT; io_conf.pin_bit_mask = (1ULL << TEST_IO_9) | (1ULL << TEST_IO_10); io_conf.pull_down_en = 0; io_conf.pull_up_en = 1; TEST_ESP_OK(gpio_config(&io_conf)); TEST_ESP_OK(gpio_set_level(TEST_IO_9, 0)); TEST_ESP_OK(gpio_set_level(TEST_IO_10, 0)); TEST_ESP_OK(gpio_install_isr_service(0)); TEST_ESP_OK(gpio_set_intr_type(TEST_IO_9, GPIO_INTR_ANYEDGE)); TEST_ESP_OK(gpio_set_intr_type(TEST_IO_10, GPIO_INTR_NEGEDGE)); TEST_ESP_OK(gpio_isr_handler_add(TEST_IO_9, gpio_isr_handler, (void*)&io9_param)); TEST_ESP_OK(gpio_isr_handler_add(TEST_IO_10, gpio_isr_handler, (void*)&io10_param)); printf("Triggering the interrupt of GPIO9\n"); vTaskDelay(1000 / portTICK_RATE_MS); //Rising edge TEST_ESP_OK(gpio_set_level(TEST_IO_9, 1)); printf("Disable the interrupt of GPIO9\n"); vTaskDelay(100 / portTICK_RATE_MS); //Disable GPIO9 interrupt, GPIO18 will not respond to the next falling edge interrupt. TEST_ESP_OK(gpio_intr_disable(TEST_IO_9)); vTaskDelay(100 / portTICK_RATE_MS); //Falling edge TEST_ESP_OK(gpio_set_level(TEST_IO_9, 0)); printf("Triggering the interrupt of GPIO10\n"); vTaskDelay(100 / portTICK_RATE_MS); TEST_ESP_OK(gpio_set_level(TEST_IO_10, 1)); vTaskDelay(100 / portTICK_RATE_MS); //Falling edge TEST_ESP_OK(gpio_set_level(TEST_IO_10, 0)); vTaskDelay(100 / portTICK_RATE_MS); TEST_ESP_OK(gpio_isr_handler_remove(TEST_IO_9)); TEST_ESP_OK(gpio_isr_handler_remove(TEST_IO_10)); gpio_uninstall_isr_service(); TEST_ASSERT((io9_param.isr_cnt == 1) && (io10_param.isr_cnt == 1)); }