Merge branch 'master' into feature/lwip_sntp_max_servers
This commit is contained in:
@@ -43,4 +43,8 @@ After the program started, `bt_spp_initator` will connect it and send data.
|
||||
|
||||
- To see the information of data, users shall set `SPP_SHOW_MODE` as `SPP_SHOW_DATA` or `SPP_SHOW_SPEED` in code(should be same with `bt_spp_initator`).
|
||||
|
||||
- We also show the Security Simple Pair in this SPP demo. Users can set the IO Capability and Security Mode for their device (security level is fixed level 4). The default security mode of this demo is `ESP_SPP_SEC_AUTHENTICATE` which support MITM (Man In The Middle) protection. For more information about Security Simple Pair on ESP32, please refer to [ESP32_SSP](./ESP32_SSP.md).
|
||||
- We also show the Security Simple Pair in this SPP demo. Users can set the IO Capability and Security Mode for their device (security level is fixed level 4). The default security mode of this demo is `ESP_SPP_SEC_AUTHENTICATE` which support MITM (Man In The Middle) protection. For more information about Security Simple Pair on ESP32, please refer to [ESP32_SSP](./ESP32_SSP.md).
|
||||
|
||||
## FAQ
|
||||
Q: How many SPP servers does ESP32 support?
|
||||
A: For now, the maximum number of SPP servers is 6, which is limited by the maximum number of SDP records. When the SPP server is successfully started, the unique SCN (Server Channel Number) will be mapped to the SPP server.
|
||||
|
||||
@@ -119,3 +119,13 @@ To clearly show how the SSP aggregate with the SPP , we use the Commands and Eff
|
||||
- If you want to update the responses of HF Unit or want to update the log, please refer to `bt_app_spp.c`.
|
||||
|
||||
- Task configuration part is in `example_spp_initiator_demo.c`.
|
||||
|
||||
## FAQ
|
||||
Q: How can we reach the maximum throughput when using SPP?
|
||||
A: The default MTU size of classic Bluetooth SPP on ESP32 is 990 bytes, and higher throughput can be achieved in the case that data chunck size is close to the MTU size or multiple of MTU size. For example, sending 100 bytes data per second is much better than sending 10 bytes every 100 milliseconds.
|
||||
|
||||
Q: What is the difference between the event `ESP_SPP_CONG_EVT` and the parameter `cong` of the event `ESP_SPP_WRITE_EVT`?
|
||||
A: The event `ESP_SPP_CONG_EVT` shows the changing status from `congest` to `uncongest`, or form `uncongest` to `congest`. Congestion can have many causes, such as using out of the credit which is sent by peer, reaching the high watermark of the Tx buffer, the congestion at Bluetooth L2CAP layer and so on. The parameter `cong` of the event `ESP_SPP_WRITE_EVT` shows a snapshot of the state of the flow control manager after the write operation is completed. The user needs to carefully consider retransmitting or continuing to write according to these two events. The ESP32 offers an VFS mode of SPP which hides the details of retransmitting, but it will block the caller and is not more efficient than the callback mode.
|
||||
|
||||
Q: How many SPP clients does ESP32 support?
|
||||
A: The ESP32 supports maximum 8 SPP clients, which including virtual SPP connections. Virtual SPP connection means that SPP clients can connect to the different SPP servers running on the same peer device. However the number of SPP clients (excluding virtual connections) shall not exceed the number of Bluetooth ACL connections.
|
||||
|
||||
@@ -2,5 +2,7 @@
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/bluetooth/esp_ble_mesh/common_components/example_init)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(ble_mesh_console)
|
||||
|
||||
@@ -7,4 +7,6 @@ PROJECT_NAME := ble_mesh_console
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := components/include
|
||||
|
||||
EXTRA_COMPONENT_DIRS := $(IDF_PATH)/examples/bluetooth/esp_ble_mesh/common_components/example_init
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -104,7 +104,7 @@ void ble_mesh_set_node_prestore_params(uint16_t netkey_index, uint16_t unicast_a
|
||||
void ble_mesh_node_statistics_get(void)
|
||||
{
|
||||
xSemaphoreTake(ble_mesh_node_sema, portMAX_DELAY);
|
||||
ESP_LOGI(TAG, "statistics:%d,%d\n", ble_mesh_node_statistics.statistics, ble_mesh_node_statistics.package_num);
|
||||
ESP_LOGI(TAG, "Statistics:%d\n", ble_mesh_node_statistics.package_num);
|
||||
xSemaphoreGive(ble_mesh_node_sema);
|
||||
}
|
||||
|
||||
@@ -114,6 +114,7 @@ int ble_mesh_node_statistics_accumulate(uint8_t *data, uint32_t value, uint16_t
|
||||
uint16_t sequence_num = (data[0] << 8) | data[1];
|
||||
|
||||
xSemaphoreTake(ble_mesh_node_sema, portMAX_DELAY);
|
||||
|
||||
for (i = 0; i < ble_mesh_node_statistics.total_package_num; i++) {
|
||||
if (ble_mesh_node_statistics.package_index[i] == sequence_num) {
|
||||
xSemaphoreGive(ble_mesh_node_sema);
|
||||
@@ -136,6 +137,7 @@ int ble_mesh_node_statistics_accumulate(uint8_t *data, uint32_t value, uint16_t
|
||||
}
|
||||
}
|
||||
xSemaphoreGive(ble_mesh_node_sema);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -190,24 +192,26 @@ void ble_mesh_create_send_data(char *data, uint16_t byte_num, uint16_t sequence_
|
||||
|
||||
void ble_mesh_test_performance_client_model_get(void)
|
||||
{
|
||||
uint32_t i, j;
|
||||
uint32_t i;
|
||||
uint32_t succeed_packet_count;
|
||||
uint32_t sum_time = 0;
|
||||
uint32_t failed_packet_num = 0;
|
||||
uint32_t rtt = 0;
|
||||
|
||||
for (i = 0, j = 0; i < test_perf_statistics.test_num; i++) {
|
||||
for (i = 0, succeed_packet_count = 0; i < test_perf_statistics.test_num; i++) {
|
||||
if (test_perf_statistics.time[i] != 0) {
|
||||
sum_time += test_perf_statistics.time[i];
|
||||
j += 1;
|
||||
succeed_packet_count += 1;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (j == test_perf_statistics.test_num - 1) {
|
||||
break;
|
||||
failed_packet_num += 1;
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "VendorModel:Statistics,%d,%d\n",
|
||||
test_perf_statistics.statistics, (sum_time / (j + 1)));
|
||||
if(succeed_packet_count != 0){
|
||||
rtt = (int)(sum_time / succeed_packet_count);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "VendorModel:Statistics,%d,%d\n", failed_packet_num, rtt);
|
||||
}
|
||||
|
||||
void ble_mesh_test_performance_client_model_get_received_percent(void)
|
||||
@@ -250,6 +254,9 @@ void ble_mesh_test_performance_client_model_get_received_percent(void)
|
||||
|
||||
// for script match
|
||||
ESP_LOGI(TAG, "VendorModel:Statistics");
|
||||
for (j = 0; j < time_level_num; j++) {
|
||||
ESP_LOGI("", "%d:%d", statistics_time_percent[j].time_level, statistics_time_percent[j].time_num);
|
||||
}
|
||||
free(statistics_time_percent);
|
||||
}
|
||||
|
||||
@@ -264,18 +271,12 @@ int ble_mesh_test_performance_client_model_accumulate_time(uint16_t time, uint8_
|
||||
uint16_t sequence_num = 0;
|
||||
uint16_t node_received_ttl = 0;
|
||||
|
||||
// receive failed
|
||||
if (length != test_perf_statistics.test_length) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (data != NULL) {
|
||||
sequence_num = (data[0] << 8) | data[1];
|
||||
if (data[2] == VENDOR_MODEL_PERF_OPERATION_TYPE_SET) {
|
||||
node_received_ttl = data[3];
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < test_perf_statistics.test_num; i++) {
|
||||
if (test_perf_statistics.package_index[i] == sequence_num) {
|
||||
return 1;
|
||||
@@ -286,7 +287,7 @@ int ble_mesh_test_performance_client_model_accumulate_time(uint16_t time, uint8_
|
||||
if (test_perf_statistics.package_index[i] == 0) {
|
||||
test_perf_statistics.package_index[i] = sequence_num;
|
||||
if (data[2] == VENDOR_MODEL_PERF_OPERATION_TYPE_SET) {
|
||||
if (node_received_ttl == test_perf_statistics.ttl && ack_ttl == test_perf_statistics.ttl) {
|
||||
if (node_received_ttl == test_perf_statistics.ttl) {
|
||||
test_perf_statistics.time[i] = time;
|
||||
} else {
|
||||
test_perf_statistics.time[i] = 0;
|
||||
|
||||
@@ -22,12 +22,10 @@
|
||||
|
||||
#include "esp_vfs_fat.h"
|
||||
|
||||
#include "esp_bt.h"
|
||||
#include "esp_bt_main.h"
|
||||
|
||||
#include "esp_console.h"
|
||||
|
||||
#include "ble_mesh_console_decl.h"
|
||||
#include "ble_mesh_example_init.h"
|
||||
|
||||
#define TAG "ble_mesh_test"
|
||||
|
||||
@@ -50,34 +48,6 @@ static void initialize_filesystem(void)
|
||||
}
|
||||
#endif // CONFIG_STORE_HISTORY
|
||||
|
||||
esp_err_t bluetooth_init(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bt_controller_init(&bt_cfg);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
ret = esp_bluedroid_init();
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
ret = esp_bluedroid_enable();
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_log_level_set("*", ESP_LOG_ERROR);
|
||||
esp_log_level_set("ble_mesh_console", ESP_LOG_INFO);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t res;
|
||||
@@ -90,6 +60,9 @@ void app_main(void)
|
||||
printf("esp32_bluetooth_init failed (ret %d)", res);
|
||||
}
|
||||
|
||||
esp_log_level_set("*", ESP_LOG_INFO);
|
||||
esp_log_level_set("ble_mesh_console", ESP_LOG_INFO);
|
||||
|
||||
esp_console_repl_t *repl = NULL;
|
||||
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
|
||||
esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
|
||||
|
||||
@@ -62,7 +62,7 @@ static void register_restart(void)
|
||||
|
||||
static int free_mem(int argc, char **argv)
|
||||
{
|
||||
printf("%d\n", esp_get_free_heap_size());
|
||||
printf("freeheap:%d\n", esp_get_free_heap_size());
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -70,18 +70,18 @@ void ble_mesh_test_performance_client_model_throughput(void *params)
|
||||
ctx.model = profile_context->model;
|
||||
ctx.send_rel = 0;
|
||||
test_perf_statistics.test_length = profile_context->length;
|
||||
|
||||
// create send data
|
||||
data = malloc(profile_context->length);
|
||||
if (data == NULL) {
|
||||
ESP_LOGE(TAG, " %s, %d, malloc fail\n", __func__, __LINE__);
|
||||
}
|
||||
|
||||
TRANSACTION_INIT(&trans, TRANS_TYPE_MESH_PERF, TRANS_MESH_SEND_MESSAGE,
|
||||
TRANS_MESH_SEND_MESSAGE_EVT, SEND_MESSAGE_TIMEOUT, &start_time, NULL);
|
||||
for (i = 1; i <= profile_context->test_num; i++) {
|
||||
ble_mesh_create_send_data((char *)data, profile_context->length, i, profile_context->opcode);
|
||||
start_time = esp_timer_get_time();
|
||||
TRANSACTION_INIT(&trans, TRANS_TYPE_MESH_PERF, TRANS_MESH_SEND_MESSAGE,
|
||||
TRANS_MESH_SEND_MESSAGE_EVT, SEND_MESSAGE_TIMEOUT, &start_time, NULL);
|
||||
//tx: data profile_context->length
|
||||
esp_ble_mesh_client_model_send_msg(profile_context->model, &ctx, profile_context->opcode,
|
||||
profile_context->length, data, 8000, profile_context->need_ack, profile_context->device_role);
|
||||
ble_mesh_test_performance_client_model_accumulate_statistics(profile_context->length);
|
||||
@@ -156,7 +156,6 @@ int ble_mesh_test_performance_client_model_performance(int argc, char **argv)
|
||||
}
|
||||
|
||||
if (strcmp(test_perf_client_model_statistics.action_type->sval[0], "init") == 0) {
|
||||
init_transactions();
|
||||
result = ble_mesh_test_performance_client_model_init(test_perf_client_model_statistics.node_num->ival[0],
|
||||
test_perf_client_model_statistics.test_size->ival[0], test_perf_client_model_statistics.ttl->ival[0]);
|
||||
if (result == 0) {
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
#include "esp_bt.h"
|
||||
#include "soc/soc.h"
|
||||
|
||||
#include "esp_bt_device.h"
|
||||
|
||||
#include "test.h"
|
||||
#include "esp_ble_mesh_networking_api.h"
|
||||
#include "esp_ble_mesh_defs.h"
|
||||
@@ -25,8 +23,8 @@
|
||||
#include "esp_ble_mesh_generic_model_api.h"
|
||||
#include "ble_mesh_console_lib.h"
|
||||
#include "ble_mesh_adapter.h"
|
||||
#include "transaction.h"
|
||||
|
||||
#include "esp_bt_defs.h"
|
||||
#include "provisioner_prov.h"
|
||||
|
||||
|
||||
@@ -56,6 +54,12 @@ typedef struct {
|
||||
} ble_mesh_comp_t;
|
||||
static ble_mesh_comp_t component;
|
||||
|
||||
typedef struct {
|
||||
struct arg_int *action;
|
||||
struct arg_end *end;
|
||||
} ble_mesh_deinit_t;
|
||||
static ble_mesh_deinit_t deinit;
|
||||
|
||||
typedef struct {
|
||||
struct arg_int *bearer;
|
||||
struct arg_int *enable;
|
||||
@@ -290,8 +294,8 @@ void ble_mesh_prov_cb(esp_ble_mesh_prov_cb_event_t event, esp_ble_mesh_prov_cb_p
|
||||
#if (CONFIG_BLE_MESH_PROVISIONER)
|
||||
case ESP_BLE_MESH_PROVISIONER_RECV_UNPROV_ADV_PKT_EVT:
|
||||
ESP_LOGD(TAG, "Provisioner recv unprovisioned device beacon:");
|
||||
ESP_LOG_BUFFER_HEX("Device UUID %s", param->provisioner_recv_unprov_adv_pkt.dev_uuid, 16);
|
||||
ESP_LOG_BUFFER_HEX("Address %s", param->provisioner_recv_unprov_adv_pkt.addr, 6);
|
||||
ESP_LOG_BUFFER_HEX("Device UUID", param->provisioner_recv_unprov_adv_pkt.dev_uuid, 16);
|
||||
ESP_LOG_BUFFER_HEX("Address", param->provisioner_recv_unprov_adv_pkt.addr, 6);
|
||||
ESP_LOGD(TAG, "Address type 0x%x, oob_info 0x%04x, adv_type 0x%x, bearer 0x%x",
|
||||
param->provisioner_recv_unprov_adv_pkt.addr_type, param->provisioner_recv_unprov_adv_pkt.oob_info,
|
||||
param->provisioner_recv_unprov_adv_pkt.adv_type, param->provisioner_recv_unprov_adv_pkt.bearer);
|
||||
@@ -347,13 +351,24 @@ void ble_mesh_model_cb(esp_ble_mesh_model_cb_event_t event, esp_ble_mesh_model_c
|
||||
{
|
||||
uint16_t result;
|
||||
uint8_t data[4];
|
||||
esp_err_t outcome = ESP_OK;
|
||||
uint8_t status;
|
||||
uint64_t *start_time = NULL;
|
||||
transaction_t *trans = NULL;
|
||||
|
||||
ESP_LOGD(TAG, "enter %s, event=%x\n", __func__, event);
|
||||
do {
|
||||
trans = transaction_get(TRANS_TYPE_MESH_PERF, TRANS_MESH_SEND_MESSAGE, trans);
|
||||
if (trans) {
|
||||
start_time = (uint64_t *)trans->input;
|
||||
break;
|
||||
}
|
||||
}while(trans);
|
||||
|
||||
switch (event) {
|
||||
case ESP_BLE_MESH_MODEL_OPERATION_EVT:
|
||||
if (param->model_operation.model != NULL && param->model_operation.model->op != NULL) {
|
||||
if (param->model_operation.opcode == ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_SET) {
|
||||
if (param->model_operation.opcode == ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_SET) {
|
||||
ESP_LOGI(TAG, "VendorModel:SetAck,Success,%d", param->model_operation.ctx->recv_ttl);
|
||||
data[0] = param->model_operation.msg[0];
|
||||
data[1] = param->model_operation.msg[1];
|
||||
@@ -367,9 +382,36 @@ void ble_mesh_model_cb(esp_ble_mesh_model_cb_event_t event, esp_ble_mesh_model_c
|
||||
} else if (param->model_operation.opcode == ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_SET_UNACK) {
|
||||
ESP_LOGI(TAG, "VendorModel:SetUnAck,Success,%d,%d", param->model_operation.ctx->recv_ttl, param->model_operation.length);
|
||||
result = ble_mesh_node_statistics_accumulate(param->model_operation.msg, param->model_operation.length, VENDOR_MODEL_PERF_OPERATION_TYPE_SET_UNACK);
|
||||
if (result == 0) {
|
||||
esp_ble_mesh_server_model_send_msg(param->model_operation.model, param->model_operation.ctx,
|
||||
ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_STATUS, param->model_operation.length, param->model_operation.msg);
|
||||
}
|
||||
else if (param->model_operation.opcode == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_GET) {
|
||||
ESP_LOGI(TAG, "Node:GetStatus,OK");
|
||||
ble_mesh_node_get_state(status);
|
||||
outcome = esp_ble_mesh_server_model_send_msg(param->model_operation.model, param->model_operation.ctx, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS,
|
||||
sizeof(status), &status);
|
||||
if (outcome != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Node:SendMsg,Fal");
|
||||
}
|
||||
} else if (param->model_operation.opcode == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET) {
|
||||
ble_mesh_node_set_state(param->model_operation.msg[0]);
|
||||
ESP_LOGI(TAG, "Node:SetAck,OK,%d,%d", param->model_operation.msg[0], param->model_operation.ctx->recv_ttl);
|
||||
outcome = esp_ble_mesh_server_model_send_msg(param->model_operation.model, param->model_operation.ctx, ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS,
|
||||
sizeof(status), param->model_operation.msg);
|
||||
if (outcome != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Node:SendMsg,Fal");
|
||||
}
|
||||
} else if (param->model_operation.opcode == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_SET_UNACK) {
|
||||
ble_mesh_node_set_state(param->model_operation.msg[0]);
|
||||
ESP_LOGI(TAG, "Node:SetUnAck,OK,%d,%d", param->model_operation.msg[0], param->model_operation.ctx->recv_ttl);
|
||||
} else if (param->model_operation.opcode == ESP_BLE_MESH_MODEL_OP_GEN_ONOFF_STATUS) {
|
||||
ESP_LOGI(TAG, "Node:Status,Success,%d", param->model_operation.length);
|
||||
} else if (param->model_operation.opcode == ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_SET) {
|
||||
ESP_LOGI(TAG, "VendorModel:SetAck,OK,%d", param->model_operation.ctx->recv_ttl);
|
||||
} else if (param->model_operation.opcode == ESP_BLE_MESH_VND_MODEL_OP_TEST_PERF_STATUS) {
|
||||
if (trans) {
|
||||
uint64_t current_time = esp_timer_get_time();
|
||||
outcome = ble_mesh_test_performance_client_model_accumulate_time(((uint32_t)(current_time - *start_time) / 1000), param->model_operation.msg,
|
||||
param->model_operation.ctx->recv_ttl, param->model_operation.length);
|
||||
transaction_set_events(trans, TRANS_MESH_SEND_MESSAGE_EVT);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -384,11 +426,23 @@ void ble_mesh_model_cb(esp_ble_mesh_model_cb_event_t event, esp_ble_mesh_model_c
|
||||
case ESP_BLE_MESH_MODEL_PUBLISH_COMP_EVT:
|
||||
ESP_LOGI(TAG, "PublishSend,OK,0x%x,%d,", param->model_publish_comp.model->model_id, param->model_publish_comp.model->pub->msg->len);
|
||||
break;
|
||||
case ESP_BLE_MESH_CLIENT_MODEL_RECV_PUBLISH_MSG_EVT:
|
||||
ESP_LOGI(TAG, "Node:PublishReceive,OK,0x%04X,%d,%d", param->client_recv_publish_msg.opcode, param->client_recv_publish_msg.length, param->client_recv_publish_msg.msg[1]);
|
||||
if (trans) {
|
||||
uint64_t current_time = esp_timer_get_time();
|
||||
outcome = ble_mesh_test_performance_client_model_accumulate_time(((uint32_t)(current_time - *start_time) / 2000), param->client_recv_publish_msg.msg,
|
||||
param->client_recv_publish_msg.ctx->recv_ttl, param->client_recv_publish_msg.length);
|
||||
transaction_set_events(trans, TRANS_MESH_SEND_MESSAGE_EVT);
|
||||
}
|
||||
break;
|
||||
case ESP_BLE_MESH_MODEL_PUBLISH_UPDATE_EVT:
|
||||
ESP_LOGI(TAG, "PublishUpdate,OK");
|
||||
break;
|
||||
case ESP_BLE_MESH_CLIENT_MODEL_SEND_TIMEOUT_EVT:
|
||||
ESP_LOGI(TAG, "Node:TimeOut");
|
||||
ESP_LOGI(TAG, "Node:TimeOut, 0x%04X", param->client_send_timeout.opcode);
|
||||
if (trans) {
|
||||
transaction_set_events(trans, TRANS_MESH_SEND_MESSAGE_EVT);
|
||||
}
|
||||
break;
|
||||
case ESP_BLE_MESH_MODEL_EVT_MAX:
|
||||
ESP_LOGI(TAG, "Node:MaxEvt");
|
||||
@@ -429,6 +483,19 @@ int ble_mesh_power_set(int argc, char **argv)
|
||||
return result;
|
||||
}
|
||||
|
||||
static int get_dev_uuid(uint8_t uuid[16])
|
||||
{
|
||||
uint8_t addr[6] = {0};
|
||||
|
||||
extern int get_bd_addr(uint8_t addr[6]);
|
||||
if (get_bd_addr(addr)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy(uuid, addr, BD_ADDR_LEN);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ble_mesh_load_oob(int argc, char **argv)
|
||||
{
|
||||
uint8_t *static_val;
|
||||
@@ -444,7 +511,9 @@ static int ble_mesh_load_oob(int argc, char **argv)
|
||||
//parsing prov
|
||||
#if CONFIG_BLE_MESH_NODE
|
||||
prov.uuid = dev_uuid;
|
||||
memcpy(dev_uuid, esp_bt_dev_get_address(), BD_ADDR_LEN);
|
||||
if (get_dev_uuid(dev_uuid)) {
|
||||
return 1;
|
||||
}
|
||||
if (oob.static_val->count != 0) {
|
||||
static_val = malloc(oob.static_val_len->ival[0] + 1);
|
||||
if (static_val == NULL) {
|
||||
@@ -500,7 +569,7 @@ int ble_mesh_init(int argc, char **argv)
|
||||
if (device_uuid == NULL) {
|
||||
ESP_LOGE(TAG, "ble mesh malloc failed, %d\n", __LINE__);
|
||||
}
|
||||
err = get_value_string((char *)component.dev_uuid->sval[0], (char *) device_uuid);
|
||||
err = get_value_string((char *)component.dev_uuid->sval[0], (char *)device_uuid);
|
||||
if (err == ESP_OK) {
|
||||
memcpy(dev_uuid, device_uuid, ESP_BLE_MESH_OCTET16_LEN);
|
||||
} else {
|
||||
@@ -508,7 +577,9 @@ int ble_mesh_init(int argc, char **argv)
|
||||
memcpy(dev_uuid, device_uuid, BD_ADDR_LEN);
|
||||
}
|
||||
} else {
|
||||
memcpy(dev_uuid, esp_bt_dev_get_address(), 6);
|
||||
if (get_dev_uuid(dev_uuid)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
err = esp_ble_mesh_init(&prov, local_component);
|
||||
@@ -550,6 +621,35 @@ int ble_mesh_node_enable_bearer(int argc, char **argv)
|
||||
return err;
|
||||
}
|
||||
|
||||
int ble_mesh_deinit(int argc, char **argv)
|
||||
{
|
||||
int err;
|
||||
esp_ble_mesh_deinit_param_t param = {};
|
||||
|
||||
ESP_LOGD(TAG, "enter %s \n", __func__);
|
||||
|
||||
int nerrors = arg_parse(argc, argv, (void **) &deinit);
|
||||
if (nerrors != 0) {
|
||||
arg_print_errors(stderr, deinit.end, argv[0]);
|
||||
return 1;
|
||||
}
|
||||
if (deinit.action->count != 0) {
|
||||
param.erase_flash = deinit.action->ival[0];
|
||||
err = esp_ble_mesh_deinit(¶m);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Bm:DeInit,OK,%d,\n", deinit.action->ival[0]);
|
||||
}
|
||||
else{
|
||||
ESP_LOGI(TAG, "Bm:DeInit,Fail\n");
|
||||
}
|
||||
}
|
||||
else {
|
||||
return 1;
|
||||
}
|
||||
ESP_LOGD(TAG, "exit %s\n", __func__);
|
||||
return err;
|
||||
}
|
||||
|
||||
int ble_mesh_provisioner_enable_bearer(int argc, char **argv)
|
||||
{
|
||||
esp_err_t err = 0;
|
||||
@@ -672,9 +772,15 @@ int ble_mesh_provision_address(int argc, char **argv)
|
||||
str_2_mac((uint8_t *)provisioner_addr.device_addr->sval[0], del_dev.addr);
|
||||
arg_int_to_value(provisioner_addr.addr_type, device_addr.addr_type, "address type");
|
||||
arg_int_to_value(provisioner_addr.addr_type, del_dev.addr_type, "address type");
|
||||
} else if (provisioner_addr.device_uuid->count != 0) {
|
||||
get_value_string((char *)provisioner_addr.device_uuid->sval[0], (char *)device_addr.uuid);
|
||||
get_value_string((char *)provisioner_addr.device_uuid->sval[0], (char *)del_dev.uuid);
|
||||
}
|
||||
if (provisioner_addr.device_uuid->count != 0) {
|
||||
uint8_t tmp_uuid[16] = {0};
|
||||
err = get_value_string((char *)provisioner_addr.device_uuid->sval[0], (char *)tmp_uuid);
|
||||
if (err != ESP_OK) {
|
||||
str_2_mac((uint8_t *)provisioner_addr.device_uuid->sval[0], tmp_uuid);
|
||||
}
|
||||
memcpy(device_addr.uuid, tmp_uuid, 16);
|
||||
memcpy(del_dev.uuid, tmp_uuid, 16);
|
||||
del_dev.flag = BIT(1);
|
||||
}
|
||||
|
||||
@@ -858,7 +964,19 @@ void ble_mesh_register_cmd(void)
|
||||
.func = &ble_mesh_init,
|
||||
.argtable = &component,
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&model_cmd) );
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&model_cmd));
|
||||
|
||||
deinit.action = arg_int0("o", NULL, "<action>", "deinit action");
|
||||
deinit.end = arg_end(1);
|
||||
|
||||
const esp_console_cmd_t deinit_cmd = {
|
||||
.command = "bmdeinit",
|
||||
.help = "ble mesh: provisioner/node deinit",
|
||||
.hint = NULL,
|
||||
.func = &ble_mesh_deinit,
|
||||
.argtable = &deinit,
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&deinit_cmd));
|
||||
|
||||
provisioner_addr.add_del = arg_str1("z", NULL, "<add/delete>", "action type");
|
||||
provisioner_addr.device_addr = arg_str0("d", NULL, "<address>", "device address");
|
||||
@@ -896,7 +1014,7 @@ void ble_mesh_register_cmd(void)
|
||||
.func = &ble_mesh_load_oob,
|
||||
.argtable = &oob,
|
||||
};
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&oob_cmd) );
|
||||
ESP_ERROR_CHECK( esp_console_cmd_register(&oob_cmd));
|
||||
|
||||
bearer.bearer = arg_int0("b", NULL, "<bearer>", "supported bearer");
|
||||
bearer.enable = arg_int0("e", NULL, "<enable/disable>", "bearers node supported");
|
||||
@@ -1027,4 +1145,6 @@ void ble_mesh_register_cmd(void)
|
||||
.argtable = &node_network_info,
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_console_cmd_register(&node_network_info_cmd));
|
||||
|
||||
init_transactions();
|
||||
}
|
||||
|
||||
@@ -11,10 +11,18 @@
|
||||
// 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 "string.h"
|
||||
|
||||
#include "esp_bt_device.h"
|
||||
#include "esp_console.h"
|
||||
|
||||
#ifdef CONFIG_BT_BLUEDROID_ENABLED
|
||||
#include "esp_bt_device.h"
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_BT_NIMBLE_ENABLED
|
||||
#include "host/ble_hs.h"
|
||||
#endif
|
||||
|
||||
#define MAC2STR(a) (a)[0], (a)[1], (a)[2], (a)[3], (a)[4], (a)[5]
|
||||
#define MACSTR "%02x:%02x:%02x:%02x:%02x:%02x"
|
||||
|
||||
@@ -25,9 +33,45 @@ void register_bluetooth(void)
|
||||
register_ble_address();
|
||||
}
|
||||
|
||||
int get_bd_addr(uint8_t addr[6])
|
||||
{
|
||||
if (addr) {
|
||||
#ifdef CONFIG_BT_BLUEDROID_ENABLED
|
||||
memcpy(addr, esp_bt_dev_get_address(), 6);
|
||||
return 0;
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_BT_NIMBLE_ENABLED
|
||||
uint8_t own_addr_type = 0;
|
||||
uint8_t mac[6] = {0};
|
||||
int rc = 0;
|
||||
|
||||
rc = ble_hs_id_infer_auto(0, &own_addr_type);
|
||||
if (rc != 0) {
|
||||
printf("error determining address type; rc=%d\n", rc);
|
||||
return rc;
|
||||
}
|
||||
|
||||
ble_hs_id_copy_addr(own_addr_type, mac, NULL);
|
||||
|
||||
uint8_t *src = addr;
|
||||
uint8_t *dst = mac + 5;
|
||||
for (uint8_t length = 6; length > 0; length--) {
|
||||
*src++ = *dst--;
|
||||
}
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int bt_mac(int argc, char** argv)
|
||||
{
|
||||
const uint8_t *mac = esp_bt_dev_get_address();
|
||||
uint8_t mac[6] = {0};
|
||||
|
||||
get_bd_addr(mac);
|
||||
|
||||
printf("+BTMAC:"MACSTR"\n", MAC2STR(mac));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -5,14 +5,10 @@
|
||||
#include "freertos/event_groups.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
|
||||
#include "esp_timer.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_err.h"
|
||||
|
||||
#include "transaction.h"
|
||||
|
||||
|
||||
#define TAG "TRANS"
|
||||
|
||||
static transaction_t transactions[MAX_TRANSACTION_COUNT];
|
||||
@@ -178,7 +174,6 @@ esp_err_t transaction_run(transaction_t *trans)
|
||||
|
||||
if (trans) {
|
||||
start_time = utils_get_system_ts();
|
||||
|
||||
// wait for wait events
|
||||
while (1) {
|
||||
//TODO: we didn't handle ts overflow
|
||||
@@ -246,7 +241,6 @@ transaction_t *transaction_get(uint8_t type, uint32_t sub_type, transaction_t *s
|
||||
}
|
||||
}
|
||||
xSemaphoreGiveRecursive(trans_mutex);
|
||||
|
||||
ESP_LOGV(TAG, "transaction get: %x, %x, %x, %x", type, sub_type, (uint32_t) start, (uint32_t) trans);
|
||||
return trans;
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ static void uart_gpio_set(void)
|
||||
gpio_config_t io_input_conf = {
|
||||
.intr_type = GPIO_PIN_INTR_DISABLE, //disable interrupt
|
||||
.mode = GPIO_MODE_INPUT, // input mode
|
||||
.pin_bit_mask = GPIO_OUTPUT_PIN_SEL, // bit mask of the input pins
|
||||
.pin_bit_mask = GPIO_INPUT_PIN_SEL, // bit mask of the input pins
|
||||
.pull_down_en = 0, // disable pull-down mode
|
||||
.pull_up_en = 0, // disable pull-down mode
|
||||
};
|
||||
|
||||
@@ -23,6 +23,8 @@ else()
|
||||
add_subdirectory(stubs/spi_flash)
|
||||
endif()
|
||||
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
set(elf_file ${CMAKE_PROJECT_NAME}.elf)
|
||||
add_executable(${elf_file} main.c)
|
||||
|
||||
@@ -35,5 +37,3 @@ if("${TARGET}" STREQUAL "esp32")
|
||||
else()
|
||||
target_link_libraries(${elf_file} stub::esp32 stub::freertos stub::spi_flash)
|
||||
endif()
|
||||
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS 1)
|
||||
|
||||
4
examples/common_components/pid_ctrl/CMakeLists.txt
Normal file
4
examples/common_components/pid_ctrl/CMakeLists.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
set(srcs "src/pid_ctrl.c")
|
||||
|
||||
idf_component_register(SRCS "${srcs}"
|
||||
INCLUDE_DIRS "include")
|
||||
2
examples/common_components/pid_ctrl/component.mk
Normal file
2
examples/common_components/pid_ctrl/component.mk
Normal file
@@ -0,0 +1,2 @@
|
||||
COMPONENT_ADD_INCLUDEDIRS := include
|
||||
COMPONENT_SRCDIRS := src
|
||||
100
examples/common_components/pid_ctrl/include/pid_ctrl.h
Normal file
100
examples/common_components/pid_ctrl/include/pid_ctrl.h
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief PID calculation type
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
PID_CAL_TYPE_INCREMENTAL, /*!< Incremental PID control */
|
||||
PID_CAL_TYPE_POSITIONAL, /*!< Positional PID control */
|
||||
} pid_calculate_type_t;
|
||||
|
||||
/**
|
||||
* @brief Type of PID control block handle
|
||||
*
|
||||
*/
|
||||
typedef struct pid_ctrl_block_t *pid_ctrl_block_handle_t;
|
||||
|
||||
/**
|
||||
* @brief PID control parameters
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
float kp; // PID Kp parameter
|
||||
float ki; // PID Ki parameter
|
||||
float kd; // PID Kd parameter
|
||||
float max_output; // PID maximum output limitation
|
||||
float min_output; // PID minimum output limitation
|
||||
float max_integral; // PID maximum integral value limitation
|
||||
float min_integral; // PID minimum integral value limitation
|
||||
pid_calculate_type_t cal_type; // PID calculation type
|
||||
} pid_ctrl_parameter_t;
|
||||
|
||||
/**
|
||||
* @brief PID control configuration
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
pid_ctrl_parameter_t init_param; // Initial parameters
|
||||
} pid_ctrl_config_t;
|
||||
|
||||
/**
|
||||
* @brief Create a new PID control session, returns the handle of control block
|
||||
*
|
||||
* @param[in] config PID control configuration
|
||||
* @param[out] ret_pid Returned PID control block handle
|
||||
* @return
|
||||
* - ESP_OK: Created PID control block successfully
|
||||
* - ESP_ERR_INVALID_ARG: Created PID control block failed because of invalid argument
|
||||
* - ESP_ERR_NO_MEM: Created PID control block failed because out of memory
|
||||
*/
|
||||
esp_err_t pid_new_control_block(const pid_ctrl_config_t *config, pid_ctrl_block_handle_t *ret_pid);
|
||||
|
||||
/**
|
||||
* @brief Delete the PID control block
|
||||
*
|
||||
* @param[in] pid PID control block handle, created by `pid_new_control_block()`
|
||||
* @return
|
||||
* - ESP_OK: Delete PID control block successfully
|
||||
* - ESP_ERR_INVALID_ARG: Delete PID control block failed because of invalid argument
|
||||
*/
|
||||
esp_err_t pid_del_control_block(pid_ctrl_block_handle_t pid);
|
||||
|
||||
/**
|
||||
* @brief Update PID parameters
|
||||
*
|
||||
* @param[in] pid PID control block handle, created by `pid_new_control_block()`
|
||||
* @param[in] params PID parameters
|
||||
* @return
|
||||
* - ESP_OK: Update PID parameters successfully
|
||||
* - ESP_ERR_INVALID_ARG: Update PID parameters failed because of invalid argument
|
||||
*/
|
||||
esp_err_t pid_update_parameters(pid_ctrl_block_handle_t pid, const pid_ctrl_parameter_t *params);
|
||||
|
||||
/**
|
||||
* @brief Input error and get PID control result
|
||||
*
|
||||
* @param[in] pid PID control block handle, created by `pid_new_control_block()`
|
||||
* @param[in] input_error error data that feed to the PID controller
|
||||
* @param[out] ret_result result after PID calculation
|
||||
* @return
|
||||
* - ESP_OK: Run a PID compute successfully
|
||||
* - ESP_ERR_INVALID_ARG: Run a PID compute failed because of invalid argument
|
||||
*/
|
||||
esp_err_t pid_compute(pid_ctrl_block_handle_t pid, float input_error, float *ret_result);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
140
examples/common_components/pid_ctrl/src/pid_ctrl.c
Normal file
140
examples/common_components/pid_ctrl/src/pid_ctrl.c
Normal file
@@ -0,0 +1,140 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <sys/param.h>
|
||||
#include "esp_check.h"
|
||||
#include "esp_log.h"
|
||||
#include "pid_ctrl.h"
|
||||
|
||||
static const char *TAG = "pid_ctrl";
|
||||
|
||||
typedef struct pid_ctrl_block_t pid_ctrl_block_t;
|
||||
typedef float (*pid_cal_func_t)(pid_ctrl_block_t *pid, float error);
|
||||
|
||||
struct pid_ctrl_block_t {
|
||||
float Kp; // PID Kp value
|
||||
float Ki; // PID Ki value
|
||||
float Kd; // PID Kd value
|
||||
float previous_err1; // e(k-1)
|
||||
float previous_err2; // e(k-2)
|
||||
float integral_err; // Sum of error
|
||||
float last_output; // PID output in last control period
|
||||
float max_output; // PID maximum output limitation
|
||||
float min_output; // PID minimum output limitation
|
||||
float max_integral; // PID maximum integral value limitation
|
||||
float min_integral; // PID minimum integral value limitation
|
||||
pid_cal_func_t calculate_func; // calculation function, depends on actual PID type set by user
|
||||
};
|
||||
|
||||
static float pid_calc_positional(pid_ctrl_block_t *pid, float error)
|
||||
{
|
||||
float output = 0;
|
||||
/* Add current error to the integral error */
|
||||
pid->integral_err += error;
|
||||
/* If the integral error is out of the range, it will be limited */
|
||||
pid->integral_err = MIN(pid->integral_err, pid->max_integral);
|
||||
pid->integral_err = MAX(pid->integral_err, pid->min_integral);
|
||||
|
||||
/* Calculate the pid control value by location formula */
|
||||
/* u(k) = e(k)*Kp + (e(k)-e(k-1))*Kd + integral*Ki */
|
||||
output = error * pid->Kp +
|
||||
(error - pid->previous_err1) * pid->Kd +
|
||||
pid->integral_err * pid->Ki;
|
||||
|
||||
/* If the output is out of the range, it will be limited */
|
||||
output = MIN(output, pid->max_output);
|
||||
output = MAX(output, pid->min_output);
|
||||
|
||||
/* Update previous error */
|
||||
pid->previous_err1 = error;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
static float pid_calc_incremental(pid_ctrl_block_t *pid, float error)
|
||||
{
|
||||
float output = 0;
|
||||
|
||||
/* Calculate the pid control value by increment formula */
|
||||
/* du(k) = (e(k)-e(k-1))*Kp + (e(k)-2*e(k-1)+e(k-2))*Kd + e(k)*Ki */
|
||||
/* u(k) = du(k) + u(k-1) */
|
||||
output = (error - pid->previous_err1) * pid->Kp +
|
||||
(error - 2 * pid->previous_err1 + pid->previous_err2) * pid->Kd +
|
||||
error * pid->Ki +
|
||||
pid->last_output;
|
||||
|
||||
/* If the output is beyond the range, it will be limited */
|
||||
output = MIN(output, pid->max_output);
|
||||
output = MAX(output, pid->min_output);
|
||||
|
||||
/* Update previous error */
|
||||
pid->previous_err2 = pid->previous_err1;
|
||||
pid->previous_err1 = error;
|
||||
|
||||
/* Update last output */
|
||||
pid->last_output = output;
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
esp_err_t pid_new_control_block(const pid_ctrl_config_t *config, pid_ctrl_block_handle_t *ret_pid)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
pid_ctrl_block_t *pid = NULL;
|
||||
/* Check the input pointer */
|
||||
ESP_GOTO_ON_FALSE(config && ret_pid, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
|
||||
|
||||
pid = calloc(1, sizeof(pid_ctrl_block_t));
|
||||
ESP_GOTO_ON_FALSE(pid, ESP_ERR_NO_MEM, err, TAG, "no mem for PID control block");
|
||||
ESP_GOTO_ON_ERROR(pid_update_parameters(pid, &config->init_param), err, TAG, "init PID parameters failed");
|
||||
*ret_pid = pid;
|
||||
return ret;
|
||||
|
||||
err:
|
||||
if (pid) {
|
||||
free(pid);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
esp_err_t pid_del_control_block(pid_ctrl_block_handle_t pid)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(pid, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
free(pid);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t pid_compute(pid_ctrl_block_handle_t pid, float input_error, float *ret_result)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(pid && ret_result, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
*ret_result = pid->calculate_func(pid, input_error);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t pid_update_parameters(pid_ctrl_block_handle_t pid, const pid_ctrl_parameter_t *params)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(pid && params, ESP_ERR_INVALID_ARG, TAG, "invalid argument");
|
||||
pid->Kp = params->kp;
|
||||
pid->Ki = params->ki;
|
||||
pid->Kd = params->kd;
|
||||
pid->max_output = params->max_output;
|
||||
pid->min_output = params->min_output;
|
||||
pid->max_integral = params->max_integral;
|
||||
pid->min_integral = params->min_integral;
|
||||
/* Set the calculate function according to the PID type */
|
||||
switch (params->cal_type) {
|
||||
case PID_CAL_TYPE_INCREMENTAL:
|
||||
pid->calculate_func = pid_calc_incremental;
|
||||
break;
|
||||
case PID_CAL_TYPE_POSITIONAL:
|
||||
pid->calculate_func = pid_calc_positional;
|
||||
break;
|
||||
default:
|
||||
ESP_RETURN_ON_FALSE(false, ESP_ERR_INVALID_ARG, TAG, "invalid PID calculation type:%d", params->cal_type);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -162,11 +162,18 @@ menu "Example Connection Configuration"
|
||||
RTL8201F/SR8201F is a single port 10/100Mb Ethernet Transceiver with auto MDIX.
|
||||
Goto http://www.corechip-sz.com/productsview.asp?id=22 for more information about it.
|
||||
|
||||
config EXAMPLE_ETH_PHY_LAN8720
|
||||
bool "LAN8720"
|
||||
config EXAMPLE_ETH_PHY_LAN87XX
|
||||
bool "LAN87xx"
|
||||
help
|
||||
Below chips are supported:
|
||||
LAN8710A is a small footprint MII/RMII 10/100 Ethernet Transceiver with HP Auto-MDIX and
|
||||
flexPWR® Technology.
|
||||
LAN8720A is a small footprint RMII 10/100 Ethernet Transceiver with HP Auto-MDIX Support.
|
||||
Goto https://www.microchip.com/LAN8720A for more information about it.
|
||||
LAN8740A/LAN8741A is a small footprint MII/RMII 10/100 Energy Efficient Ethernet Transceiver
|
||||
with HP Auto-MDIX and flexPWR® Technology.
|
||||
LAN8742A is a small footprint RMII 10/100 Ethernet Transceiver with HP Auto-MDIX and
|
||||
flexPWR® Technology.
|
||||
Goto https://www.microchip.com for more information about them.
|
||||
|
||||
config EXAMPLE_ETH_PHY_DP83848
|
||||
bool "DP83848"
|
||||
|
||||
@@ -393,12 +393,12 @@ static esp_netif_t *eth_start(void)
|
||||
s_phy = esp_eth_phy_new_ip101(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_ETH_PHY_RTL8201
|
||||
s_phy = esp_eth_phy_new_rtl8201(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_ETH_PHY_LAN8720
|
||||
s_phy = esp_eth_phy_new_lan8720(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_ETH_PHY_LAN87XX
|
||||
s_phy = esp_eth_phy_new_lan87xx(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_ETH_PHY_DP83848
|
||||
s_phy = esp_eth_phy_new_dp83848(&phy_config);
|
||||
#endif
|
||||
#elif CONFIG_ETH_USE_SPI_ETHERNET
|
||||
#elif CONFIG_EXAMPLE_USE_SPI_ETHERNET
|
||||
gpio_install_isr_service(0);
|
||||
spi_device_handle_t spi_handle = NULL;
|
||||
spi_bus_config_t buscfg = {
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
menu "Bootloader welcome message"
|
||||
|
||||
config EXAMPLE_BOOTLOADER_WELCOME_MESSAGE
|
||||
string "Bootloader welcome message"
|
||||
default "Custom bootloader message defined in the KConfig file."
|
||||
help
|
||||
Message to print by the custom bootloader when booting up.
|
||||
endmenu
|
||||
@@ -4,6 +4,7 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <stdbool.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_log.h"
|
||||
#include "bootloader_init.h"
|
||||
#include "bootloader_utility.h"
|
||||
@@ -41,7 +42,7 @@ void __attribute__((noreturn)) call_start_cpu0(void)
|
||||
}
|
||||
|
||||
// 2.1 Print a custom message!
|
||||
esp_rom_printf("[%s] Custom bootloader has been initialized correctly.\n", TAG);
|
||||
esp_rom_printf("[%s] %s\n", TAG, CONFIG_EXAMPLE_BOOTLOADER_WELCOME_MESSAGE);
|
||||
|
||||
// 3. Load the app image for booting
|
||||
bootloader_utility_load_boot_image(&bs, boot_index);
|
||||
|
||||
@@ -10,7 +10,10 @@ def test_custom_bootloader_impl_example(env, _): # type: ignore
|
||||
dut.start_app()
|
||||
|
||||
# Expect to read a message from the custom bootloader
|
||||
dut.expect('Custom bootloader has been initialized correctly.')
|
||||
# This message is defined in the Kconfig file, retrieve it while deleting
|
||||
# leading and trailing quotes (")
|
||||
welcome_message = dut.app.get_sdkconfig()['CONFIG_EXAMPLE_BOOTLOADER_WELCOME_MESSAGE'].strip("\"")
|
||||
dut.expect(welcome_message)
|
||||
|
||||
# Expect to read a message from the user application
|
||||
dut.expect('Application started!')
|
||||
|
||||
10
examples/cxx/experimental/blink_cxx/CMakeLists.txt
Normal file
10
examples/cxx/experimental/blink_cxx/CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
# For more information about build system see
|
||||
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
|
||||
# The following five 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)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component")
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(blink_cxx)
|
||||
57
examples/cxx/experimental/blink_cxx/README.md
Normal file
57
examples/cxx/experimental/blink_cxx/README.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Example: Blink C++ example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
This example demonstrates usage of the `GPIO_Output` C++ class in ESP-IDF.
|
||||
|
||||
In this example, the `sdkconfig.defaults` file sets the `CONFIG_COMPILER_CXX_EXCEPTIONS` option.
|
||||
This enables both compile time support (`-fexceptions` compiler flag) and run-time support for C++ exception handling.
|
||||
This is necessary for the C++ APIs.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
Any ESP32 family development board.
|
||||
|
||||
Connect an LED to the corresponding pin (default is pin 4). If the board has a normal LED already, you can use the pin number to which that one is connected.
|
||||
|
||||
Development boards with an RGB LED that only has one data line like the ESP32-C3-DevKitC-02 and ESP32-C3-DevKitM-1 will not work. In this case, please connect an external normal LED to the chosen pin.
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
### Build and Flash
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port.)
|
||||
|
||||
(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
|
||||
|
||||
```
|
||||
...
|
||||
I (339) cpu_start: Starting scheduler.
|
||||
I (343) gpio: GPIO[4]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
LED ON
|
||||
LED OFF
|
||||
LED ON
|
||||
LED OFF
|
||||
LED ON
|
||||
LED OFF
|
||||
LED ON
|
||||
LED OFF
|
||||
LED ON
|
||||
LED OFF
|
||||
|
||||
```
|
||||
|
||||
2
examples/cxx/experimental/blink_cxx/main/CMakeLists.txt
Normal file
2
examples/cxx/experimental/blink_cxx/main/CMakeLists.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "main.cpp"
|
||||
INCLUDE_DIRS ".")
|
||||
39
examples/cxx/experimental/blink_cxx/main/main.cpp
Normal file
39
examples/cxx/experimental/blink_cxx/main/main.cpp
Normal file
@@ -0,0 +1,39 @@
|
||||
/* Blink C++ 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 <cstdlib>
|
||||
#include <thread>
|
||||
#include "esp_log.h"
|
||||
#include "gpio_cxx.hpp"
|
||||
|
||||
using namespace idf;
|
||||
using namespace std;
|
||||
|
||||
extern "C" void app_main(void)
|
||||
{
|
||||
/* The functions of GPIO_Output throws exceptions in case of parameter errors or if there are underlying driver
|
||||
errors. */
|
||||
try {
|
||||
/* This line may throw an exception if the pin number is invalid.
|
||||
* Alternatively to 4, choose another output-capable pin. */
|
||||
GPIO_Output gpio(GPIONum(4));
|
||||
|
||||
while (true) {
|
||||
printf("LED ON\n");
|
||||
gpio.set_high();
|
||||
this_thread::sleep_for(std::chrono::seconds(1));
|
||||
printf("LED OFF\n");
|
||||
gpio.set_low();
|
||||
this_thread::sleep_for(std::chrono::seconds(1));
|
||||
}
|
||||
} catch (GPIOException &e) {
|
||||
printf("GPIO exception occurred: %s\n", esp_err_to_name(e.error));
|
||||
printf("stopping.\n");
|
||||
}
|
||||
}
|
||||
3
examples/cxx/experimental/blink_cxx/sdkconfig.defaults
Normal file
3
examples/cxx/experimental/blink_cxx/sdkconfig.defaults
Normal file
@@ -0,0 +1,3 @@
|
||||
# Enable C++ exceptions and set emergency pool size for exception objects
|
||||
CONFIG_COMPILER_CXX_EXCEPTIONS=y
|
||||
CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=1024
|
||||
@@ -1,8 +1,16 @@
|
||||
idf_component_register(SRCS
|
||||
"esp_exception.cpp"
|
||||
"i2c_cxx.cpp"
|
||||
"esp_event_api.cpp"
|
||||
"esp_event_cxx.cpp"
|
||||
"esp_timer_cxx.cpp"
|
||||
idf_build_get_property(target IDF_TARGET)
|
||||
|
||||
set(srcs "esp_timer_cxx.cpp" "esp_exception.cpp" "gpio_cxx.cpp")
|
||||
set(requires "esp_timer" "driver")
|
||||
|
||||
if(NOT ${target} STREQUAL "linux")
|
||||
list(APPEND srcs
|
||||
"i2c_cxx.cpp"
|
||||
"esp_event_api.cpp"
|
||||
"esp_event_cxx.cpp")
|
||||
list(APPEND requires "esp_event")
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
INCLUDE_DIRS "include"
|
||||
REQUIRES driver esp_event esp_timer)
|
||||
REQUIRES ${requires})
|
||||
|
||||
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#if __cpp_exceptions
|
||||
|
||||
#include <array>
|
||||
#include "driver/gpio.h"
|
||||
#include "gpio_cxx.hpp"
|
||||
|
||||
namespace idf {
|
||||
|
||||
#define GPIO_CHECK_THROW(err) CHECK_THROW_SPECIFIC((err), GPIOException)
|
||||
|
||||
namespace {
|
||||
#if CONFIG_IDF_TARGET_LINUX
|
||||
constexpr std::array<uint32_t, 1> INVALID_GPIOS = {24};
|
||||
#elif CONFIG_IDF_TARGET_ESP32
|
||||
constexpr std::array<uint32_t, 1> INVALID_GPIOS = {24};
|
||||
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||
constexpr std::array<uint32_t, 4> INVALID_GPIOS = {22, 23, 24, 25};
|
||||
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||
constexpr std::array<uint32_t, 4> INVALID_GPIOS = {22, 23, 24, 25};
|
||||
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||
constexpr std::array<uint32_t, 0> INVALID_GPIOS = {};
|
||||
#else
|
||||
#error "No GPIOs defined for the current target"
|
||||
#endif
|
||||
|
||||
gpio_num_t gpio_to_driver_type(const GPIONum &gpio_num)
|
||||
{
|
||||
return static_cast<gpio_num_t>(gpio_num.get_num());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
GPIOException::GPIOException(esp_err_t error) : ESPException(error) { }
|
||||
|
||||
esp_err_t check_gpio_pin_num(uint32_t pin_num) noexcept
|
||||
{
|
||||
if (pin_num >= GPIO_NUM_MAX) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
for (auto num: INVALID_GPIOS)
|
||||
{
|
||||
if (pin_num == num) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t check_gpio_drive_strength(uint32_t strength) noexcept
|
||||
{
|
||||
if (strength >= GPIO_DRIVE_CAP_MAX) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
GPIOPullMode GPIOPullMode::FLOATING()
|
||||
{
|
||||
return GPIOPullMode(GPIO_FLOATING);
|
||||
}
|
||||
|
||||
GPIOPullMode GPIOPullMode::PULLUP()
|
||||
{
|
||||
return GPIOPullMode(GPIO_PULLUP_ONLY);
|
||||
}
|
||||
|
||||
GPIOPullMode GPIOPullMode::PULLDOWN()
|
||||
{
|
||||
return GPIOPullMode(GPIO_PULLDOWN_ONLY);
|
||||
}
|
||||
|
||||
GPIOWakeupIntrType GPIOWakeupIntrType::LOW_LEVEL()
|
||||
{
|
||||
return GPIOWakeupIntrType(GPIO_INTR_LOW_LEVEL);
|
||||
}
|
||||
|
||||
GPIOWakeupIntrType GPIOWakeupIntrType::HIGH_LEVEL()
|
||||
{
|
||||
return GPIOWakeupIntrType(GPIO_INTR_HIGH_LEVEL);
|
||||
}
|
||||
|
||||
GPIODriveStrength GPIODriveStrength::DEFAULT()
|
||||
{
|
||||
return MEDIUM();
|
||||
}
|
||||
|
||||
GPIODriveStrength GPIODriveStrength::WEAK()
|
||||
{
|
||||
return GPIODriveStrength(GPIO_DRIVE_CAP_0);
|
||||
}
|
||||
|
||||
GPIODriveStrength GPIODriveStrength::LESS_WEAK()
|
||||
{
|
||||
return GPIODriveStrength(GPIO_DRIVE_CAP_1);
|
||||
}
|
||||
|
||||
GPIODriveStrength GPIODriveStrength::MEDIUM()
|
||||
{
|
||||
return GPIODriveStrength(GPIO_DRIVE_CAP_2);
|
||||
}
|
||||
|
||||
GPIODriveStrength GPIODriveStrength::STRONGEST()
|
||||
{
|
||||
return GPIODriveStrength(GPIO_DRIVE_CAP_3);
|
||||
}
|
||||
|
||||
GPIOBase::GPIOBase(GPIONum num) : gpio_num(num)
|
||||
{
|
||||
GPIO_CHECK_THROW(gpio_reset_pin(gpio_to_driver_type(gpio_num)));
|
||||
}
|
||||
|
||||
void GPIOBase::hold_en()
|
||||
{
|
||||
GPIO_CHECK_THROW(gpio_hold_en(gpio_to_driver_type(gpio_num)));
|
||||
}
|
||||
|
||||
void GPIOBase::hold_dis()
|
||||
{
|
||||
GPIO_CHECK_THROW(gpio_hold_dis(gpio_to_driver_type(gpio_num)));
|
||||
}
|
||||
|
||||
void GPIOBase::set_drive_strength(GPIODriveStrength strength)
|
||||
{
|
||||
GPIO_CHECK_THROW(gpio_set_drive_capability(gpio_to_driver_type(gpio_num),
|
||||
static_cast<gpio_drive_cap_t>(strength.get_strength())));
|
||||
}
|
||||
|
||||
GPIO_Output::GPIO_Output(GPIONum num) : GPIOBase(num)
|
||||
{
|
||||
GPIO_CHECK_THROW(gpio_set_direction(gpio_to_driver_type(gpio_num), GPIO_MODE_OUTPUT));
|
||||
}
|
||||
|
||||
void GPIO_Output::set_high()
|
||||
{
|
||||
GPIO_CHECK_THROW(gpio_set_level(gpio_to_driver_type(gpio_num), 1));
|
||||
}
|
||||
|
||||
void GPIO_Output::set_low()
|
||||
{
|
||||
GPIO_CHECK_THROW(gpio_set_level(gpio_to_driver_type(gpio_num), 0));
|
||||
}
|
||||
|
||||
GPIODriveStrength GPIOBase::get_drive_strength()
|
||||
{
|
||||
gpio_drive_cap_t strength;
|
||||
GPIO_CHECK_THROW(gpio_get_drive_capability(gpio_to_driver_type(gpio_num), &strength));
|
||||
return GPIODriveStrength(static_cast<uint32_t>(strength));
|
||||
}
|
||||
|
||||
GPIOInput::GPIOInput(GPIONum num) : GPIOBase(num)
|
||||
{
|
||||
GPIO_CHECK_THROW(gpio_set_direction(gpio_to_driver_type(gpio_num), GPIO_MODE_INPUT));
|
||||
}
|
||||
|
||||
GPIOLevel GPIOInput::get_level() const noexcept
|
||||
{
|
||||
int level = gpio_get_level(gpio_to_driver_type(gpio_num));
|
||||
if (level) {
|
||||
return GPIOLevel::HIGH;
|
||||
} else {
|
||||
return GPIOLevel::LOW;
|
||||
}
|
||||
}
|
||||
|
||||
void GPIOInput::set_pull_mode(GPIOPullMode mode)
|
||||
{
|
||||
GPIO_CHECK_THROW(gpio_set_pull_mode(gpio_to_driver_type(gpio_num),
|
||||
static_cast<gpio_pull_mode_t>(mode.get_pull_mode())));
|
||||
}
|
||||
|
||||
void GPIOInput::wakeup_enable(GPIOWakeupIntrType interrupt_type)
|
||||
{
|
||||
GPIO_CHECK_THROW(gpio_wakeup_enable(gpio_to_driver_type(gpio_num),
|
||||
static_cast<gpio_int_type_t>(interrupt_type.get_level())));
|
||||
}
|
||||
|
||||
void GPIOInput::wakeup_disable()
|
||||
{
|
||||
GPIO_CHECK_THROW(gpio_wakeup_disable(gpio_to_driver_type(gpio_num)));
|
||||
}
|
||||
|
||||
GPIO_OpenDrain::GPIO_OpenDrain(GPIONum num) : GPIOInput(num)
|
||||
{
|
||||
GPIO_CHECK_THROW(gpio_set_direction(gpio_to_driver_type(gpio_num), GPIO_MODE_INPUT_OUTPUT_OD));
|
||||
}
|
||||
|
||||
void GPIO_OpenDrain::set_floating()
|
||||
{
|
||||
GPIO_CHECK_THROW(gpio_set_level(gpio_to_driver_type(gpio_num), 1));
|
||||
}
|
||||
|
||||
void GPIO_OpenDrain::set_low()
|
||||
{
|
||||
GPIO_CHECK_THROW(gpio_set_level(gpio_to_driver_type(gpio_num), 0));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,10 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
set(COMPONENTS main)
|
||||
|
||||
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/esp_timer/")
|
||||
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/driver/")
|
||||
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/freertos/")
|
||||
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component/")
|
||||
project(test_esp_timer_cxx_host)
|
||||
@@ -0,0 +1,36 @@
|
||||
| Supported Targets | Linux |
|
||||
| ----------------- | ----- |
|
||||
|
||||
# C++ ESPTimer test on Linux target
|
||||
|
||||
This unit test tests basic functionality of the `ESPTimer` class. The test does not use mocks. Instead, it runs the whole implementation of the component on the Linux host. The test framework is CATCH.
|
||||
|
||||
## Requirements
|
||||
|
||||
* A Linux system
|
||||
* The usual IDF requirements for Linux system, as described in the [Getting Started Guides](../../../../../../docs/en/get-started/index.rst).
|
||||
* The host's gcc/g++
|
||||
|
||||
This application has been tested on Ubuntu 20.04 with `gcc` version *9.3.0*.
|
||||
|
||||
## Build
|
||||
|
||||
First, make sure that the target is set to Linux. Run `idf.py --preview set-target linux` if you are not sure. Then do a normal IDF build: `idf.py build`.
|
||||
|
||||
## Run
|
||||
|
||||
IDF monitor doesn't work yet for Linux. You have to run the app manually:
|
||||
|
||||
```bash
|
||||
build/test_esp_timer_cxx_host.elf
|
||||
```
|
||||
|
||||
## Example Output
|
||||
|
||||
Ideally, all tests pass, which is indicated by "All tests passed" in the last line:
|
||||
|
||||
```bash
|
||||
$ build/test_esp_timer_cxx_host.elf
|
||||
===============================================================================
|
||||
All tests passed (9 assertions in 11 test cases)
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
idf_component_register(SRCS "esp_timer_test.cpp"
|
||||
INCLUDE_DIRS
|
||||
"."
|
||||
$ENV{IDF_PATH}/tools/catch
|
||||
REQUIRES cmock esp_timer experimental_cpp_component)
|
||||
@@ -0,0 +1,196 @@
|
||||
/* ESP Timer C++ unit tests
|
||||
|
||||
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.
|
||||
*/
|
||||
#define CATCH_CONFIG_MAIN
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdexcept>
|
||||
#include "esp_err.h"
|
||||
#include "esp_timer_cxx.hpp"
|
||||
|
||||
#include "catch.hpp"
|
||||
|
||||
extern "C" {
|
||||
#include "Mockesp_timer.h"
|
||||
}
|
||||
|
||||
// TODO: IDF-2693, function definition just to satisfy linker, mock esp_common instead
|
||||
const char *esp_err_to_name(esp_err_t code) {
|
||||
return "test";
|
||||
}
|
||||
|
||||
using namespace std;
|
||||
using namespace idf;
|
||||
using namespace idf::esp_timer;
|
||||
|
||||
struct FixtureException : std::exception {
|
||||
const char *what() const noexcept override {
|
||||
return "CMock failed";
|
||||
}
|
||||
};
|
||||
|
||||
struct TimerCreationFixture {
|
||||
TimerCreationFixture(bool expect_stop = false) : out_handle(reinterpret_cast<esp_timer_handle_t>(1))
|
||||
{
|
||||
if (!TEST_PROTECT()) {
|
||||
throw FixtureException();
|
||||
}
|
||||
esp_timer_create_ExpectAnyArgsAndReturn(ESP_OK);
|
||||
esp_timer_create_ReturnThruPtr_out_handle(&out_handle);
|
||||
if (expect_stop) {
|
||||
esp_timer_stop_ExpectAndReturn(out_handle, ESP_OK); // implementation may always call stop
|
||||
} else {
|
||||
esp_timer_stop_IgnoreAndReturn(ESP_OK); // implementation may always call stop
|
||||
}
|
||||
esp_timer_delete_ExpectAndReturn(out_handle, ESP_OK);
|
||||
}
|
||||
|
||||
virtual ~TimerCreationFixture()
|
||||
{
|
||||
Mockesp_timer_Verify();
|
||||
}
|
||||
|
||||
esp_timer_handle_t out_handle;
|
||||
};
|
||||
|
||||
static void (*trigger_timer_callback)(void *data) = nullptr;
|
||||
|
||||
esp_err_t cmock_timer_create_callback(const esp_timer_create_args_t* create_args, esp_timer_handle_t* out_handle, int cmock_num_calls)
|
||||
{
|
||||
trigger_timer_callback = create_args->callback;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
struct TimerCallbackFixture : public TimerCreationFixture {
|
||||
TimerCallbackFixture(bool expect_stop = false) : TimerCreationFixture(expect_stop)
|
||||
{
|
||||
esp_timer_create_AddCallback(cmock_timer_create_callback);
|
||||
}
|
||||
|
||||
~TimerCallbackFixture()
|
||||
{
|
||||
trigger_timer_callback = nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE("get_time works")
|
||||
{
|
||||
esp_timer_get_time_ExpectAndReturn(static_cast<uint64_t>(0xfeeddeadbeef));
|
||||
|
||||
CHECK(get_time() == std::chrono::microseconds(0xfeeddeadbeef));
|
||||
}
|
||||
|
||||
TEST_CASE("get_next_alarm works")
|
||||
{
|
||||
esp_timer_get_next_alarm_ExpectAndReturn(static_cast<uint64_t>(47u));
|
||||
|
||||
CHECK(get_next_alarm() == std::chrono::microseconds(47u));
|
||||
}
|
||||
|
||||
TEST_CASE("ESPTimer null function")
|
||||
{
|
||||
CHECK_THROWS_AS(ESPTimer(nullptr), ESPException&);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPTimer empty std::function")
|
||||
{
|
||||
function<void()> nothing;
|
||||
CHECK_THROWS_AS(ESPTimer(nothing, "test"), ESPException&);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPTimer initializes and deletes itself")
|
||||
{
|
||||
TimerCreationFixture fix;
|
||||
|
||||
function<void()> timer_cb = [&]() { };
|
||||
|
||||
ESPTimer(timer_cb, "test");
|
||||
}
|
||||
|
||||
TEST_CASE("ESPTimer start throws on invalid state failure")
|
||||
{
|
||||
TimerCreationFixture fix;
|
||||
esp_timer_start_once_ExpectAndReturn(fix.out_handle, 5000, ESP_ERR_INVALID_STATE);
|
||||
|
||||
function<void()> timer_cb = [&]() { };
|
||||
|
||||
ESPTimer timer(timer_cb);
|
||||
|
||||
CHECK_THROWS_AS(timer.start(chrono::microseconds(5000)), ESPException&);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPTimer start periodically throws on invalid state failure")
|
||||
{
|
||||
TimerCreationFixture fix;
|
||||
esp_timer_start_periodic_ExpectAndReturn(fix.out_handle, 5000, ESP_ERR_INVALID_STATE);
|
||||
|
||||
function<void()> timer_cb = [&]() { };
|
||||
|
||||
ESPTimer timer(timer_cb);
|
||||
|
||||
CHECK_THROWS_AS(timer.start_periodic(chrono::microseconds(5000)), ESPException&);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPTimer stopp throws on invaid state failure")
|
||||
{
|
||||
TimerCreationFixture fix;
|
||||
|
||||
// Overriding stop part of the fixture
|
||||
esp_timer_stop_StopIgnore();
|
||||
esp_timer_stop_IgnoreAndReturn(ESP_ERR_INVALID_STATE);
|
||||
|
||||
function<void()> timer_cb = [&]() { };
|
||||
|
||||
ESPTimer timer(timer_cb);
|
||||
|
||||
CHECK_THROWS_AS(timer.stop(), ESPException&);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPTimer stops in destructor")
|
||||
{
|
||||
TimerCreationFixture fix(true);
|
||||
esp_timer_start_once_ExpectAndReturn(fix.out_handle, 5000, ESP_OK);
|
||||
|
||||
function<void()> timer_cb = [&]() { };
|
||||
|
||||
ESPTimer timer(timer_cb);
|
||||
|
||||
timer.start(chrono::microseconds(5000));
|
||||
}
|
||||
|
||||
TEST_CASE("ESPTimer stops correctly")
|
||||
{
|
||||
TimerCreationFixture fix(true);
|
||||
esp_timer_start_once_ExpectAndReturn(fix.out_handle, 5000, ESP_OK);
|
||||
|
||||
// Additional stop needed because stop is called in ESPTimer::stop and ~ESPTimer.
|
||||
esp_timer_stop_ExpectAndReturn(fix.out_handle, ESP_OK);
|
||||
|
||||
function<void()> timer_cb = [&]() { };
|
||||
|
||||
ESPTimer timer(timer_cb);
|
||||
|
||||
timer.start(chrono::microseconds(5000));
|
||||
|
||||
timer.stop();
|
||||
}
|
||||
|
||||
TEST_CASE("ESPTimer callback works")
|
||||
{
|
||||
TimerCallbackFixture fix;
|
||||
int flag = 0;
|
||||
|
||||
function<void()> timer_cb = [&]() { flag = 47; };
|
||||
|
||||
ESPTimer timer(timer_cb);
|
||||
|
||||
trigger_timer_callback(&timer);
|
||||
|
||||
REQUIRE(trigger_timer_callback != nullptr);
|
||||
CHECK(flag == 47);
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
|
||||
CONFIG_IDF_TARGET="linux"
|
||||
CONFIG_CXX_EXCEPTIONS=y
|
||||
@@ -0,0 +1,78 @@
|
||||
// Copyright 2015-2021 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 "catch.hpp"
|
||||
#include "gpio_cxx.hpp"
|
||||
extern "C" {
|
||||
#include "Mockgpio.h"
|
||||
}
|
||||
|
||||
static const idf::GPIONum VALID_GPIO(18);
|
||||
|
||||
/**
|
||||
* Exception which is thrown if there is some internal cmock error which results in a
|
||||
* longjump to the location of a TEST_PROTECT() call.
|
||||
*
|
||||
* @note This is a temporary solution until there is a better integration of CATCH into CMock.
|
||||
* Note also that usually there will be a segfault when cmock fails a second time.
|
||||
* This means paying attention to the first error message is crucial for removing errors.
|
||||
*/
|
||||
class CMockException : public std::exception {
|
||||
public:
|
||||
virtual ~CMockException() { }
|
||||
|
||||
/**
|
||||
* @return A reminder to look at the actual cmock log.
|
||||
*/
|
||||
virtual const char *what() const noexcept
|
||||
{
|
||||
return "CMock encountered an error. Look at the CMock log";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper macro for setting up a test protect call for CMock.
|
||||
*
|
||||
* This macro should be used at the beginning of any test cases
|
||||
* which use generated CMock mock functions.
|
||||
* This is necessary because CMock uses longjmp which screws up C++ stacks and
|
||||
* also the CATCH mechanisms.
|
||||
*
|
||||
* @note This is a temporary solution until there is a better integration of CATCH into CMock.
|
||||
* Note also that usually there will be a segfault when cmock fails a second time.
|
||||
* This means paying attention to the first error message is crucial for removing errors.
|
||||
*/
|
||||
#define CMOCK_SETUP() \
|
||||
do { \
|
||||
if (!TEST_PROTECT()) { \
|
||||
throw CMockException(); \
|
||||
} \
|
||||
} \
|
||||
while (0)
|
||||
|
||||
struct GPIOFixture {
|
||||
GPIOFixture(idf::GPIONum gpio_num = idf::GPIONum(18), gpio_mode_t mode = GPIO_MODE_OUTPUT) : num(gpio_num)
|
||||
{
|
||||
CMOCK_SETUP();
|
||||
gpio_reset_pin_ExpectAndReturn(static_cast<gpio_num_t>(num.get_num()), ESP_OK); gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(num.get_num()), mode, ESP_OK);
|
||||
}
|
||||
|
||||
~GPIOFixture()
|
||||
{
|
||||
// Verify that all expected methods have been called.
|
||||
Mockgpio_Verify();
|
||||
}
|
||||
|
||||
idf::GPIONum num;
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
set(COMPONENTS main)
|
||||
|
||||
idf_build_set_property(COMPILE_DEFINITIONS "-DNO_DEBUG_STORAGE" APPEND)
|
||||
|
||||
# Overriding components which should be mocked
|
||||
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/driver/")
|
||||
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/freertos/")
|
||||
|
||||
# Including experimental component here because it's outside IDF's main component directory
|
||||
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component/")
|
||||
|
||||
project(test_gpio_cxx_host)
|
||||
@@ -0,0 +1,8 @@
|
||||
| Supported Targets | Linux |
|
||||
| ----------------- | ----- |
|
||||
|
||||
# Build
|
||||
`idf.py build` (sdkconfig.defaults sets the linux target by default)
|
||||
|
||||
# Run
|
||||
`build/test_gpio_cxx_host.elf`
|
||||
@@ -0,0 +1,13 @@
|
||||
idf_component_get_property(spi_flash_dir spi_flash COMPONENT_DIR)
|
||||
idf_component_get_property(cpp_component experimental_cpp_component COMPONENT_DIR)
|
||||
|
||||
idf_component_register(SRCS "gpio_cxx_test.cpp"
|
||||
"${cpp_component}/esp_exception.cpp"
|
||||
"${cpp_component}/gpio_cxx.cpp"
|
||||
INCLUDE_DIRS
|
||||
"."
|
||||
"${cpp_component}/host_test/fixtures"
|
||||
"${cpp_component}/include"
|
||||
"${cpp_component}/test" # FIXME for unity_cxx.hpp, make it generally available instead
|
||||
$ENV{IDF_PATH}/tools/catch
|
||||
REQUIRES driver cmock)
|
||||
@@ -0,0 +1,397 @@
|
||||
/* GPIO C++ unit tests
|
||||
|
||||
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.
|
||||
*/
|
||||
#define CATCH_CONFIG_MAIN
|
||||
|
||||
#include <stdio.h>
|
||||
#include "esp_err.h"
|
||||
#include "freertos/portmacro.h"
|
||||
#include "gpio_cxx.hpp"
|
||||
#include "test_fixtures.hpp"
|
||||
|
||||
#include "catch.hpp"
|
||||
|
||||
extern "C" {
|
||||
#include "Mockgpio.h"
|
||||
}
|
||||
|
||||
// TODO: IDF-2693, function definition just to satisfy linker, mock esp_common instead
|
||||
const char *esp_err_to_name(esp_err_t code) {
|
||||
return "test";
|
||||
}
|
||||
|
||||
using namespace std;
|
||||
using namespace idf;
|
||||
|
||||
TEST_CASE("gpio num out of range")
|
||||
{
|
||||
CHECK_THROWS_AS(GPIONum(-1), GPIOException&);
|
||||
CHECK_THROWS_AS(GPIONum(static_cast<uint32_t>(GPIO_NUM_MAX)), GPIOException&);
|
||||
CHECK_THROWS_AS(GPIONum(24), GPIOException&); // On ESP32, 24 isn't a valid GPIO number
|
||||
}
|
||||
|
||||
TEST_CASE("gpio num operator")
|
||||
{
|
||||
GPIONum gpio_num_0(18u);
|
||||
GPIONum gpio_num_1(18u);
|
||||
GPIONum gpio_num_2(19u);
|
||||
|
||||
CHECK(gpio_num_0 == gpio_num_1);
|
||||
CHECK(gpio_num_2 != gpio_num_1);
|
||||
}
|
||||
|
||||
TEST_CASE("drive strength out of range")
|
||||
{
|
||||
CHECK_THROWS_AS(GPIODriveStrength(-1), GPIOException&);
|
||||
CHECK_THROWS_AS(GPIODriveStrength(static_cast<uint32_t>(GPIO_DRIVE_CAP_MAX)), GPIOException&);
|
||||
}
|
||||
|
||||
TEST_CASE("drive strength as expected")
|
||||
{
|
||||
CHECK(GPIODriveStrength::DEFAULT().get_strength() == GPIO_DRIVE_CAP_2);
|
||||
CHECK(GPIODriveStrength::WEAK().get_strength() == GPIO_DRIVE_CAP_0);
|
||||
CHECK(GPIODriveStrength::LESS_WEAK().get_strength() == GPIO_DRIVE_CAP_1);
|
||||
CHECK(GPIODriveStrength::MEDIUM().get_strength() == GPIO_DRIVE_CAP_2);
|
||||
CHECK(GPIODriveStrength::STRONGEST().get_strength() == GPIO_DRIVE_CAP_3);
|
||||
}
|
||||
|
||||
TEST_CASE("pull mode create functions work as expected")
|
||||
{
|
||||
CHECK(GPIOPullMode::FLOATING().get_pull_mode() == 3);
|
||||
CHECK(GPIOPullMode::PULLUP().get_pull_mode() == 0);
|
||||
CHECK(GPIOPullMode::PULLDOWN().get_pull_mode() == 1);
|
||||
}
|
||||
|
||||
TEST_CASE("GPIOIntrType create functions work as expected")
|
||||
{
|
||||
CHECK(GPIOWakeupIntrType::LOW_LEVEL().get_level() == GPIO_INTR_LOW_LEVEL);
|
||||
CHECK(GPIOWakeupIntrType::HIGH_LEVEL().get_level() == GPIO_INTR_HIGH_LEVEL);
|
||||
}
|
||||
|
||||
TEST_CASE("output resetting pin fails")
|
||||
{
|
||||
CMOCK_SETUP();
|
||||
gpio_reset_pin_ExpectAnyArgsAndReturn(ESP_FAIL);
|
||||
|
||||
CHECK_THROWS_AS(GPIO_Output gpio(VALID_GPIO), GPIOException&);
|
||||
|
||||
Mockgpio_Verify();
|
||||
}
|
||||
|
||||
TEST_CASE("output setting direction fails")
|
||||
{
|
||||
CMOCK_SETUP();
|
||||
gpio_reset_pin_ExpectAnyArgsAndReturn(ESP_OK);
|
||||
gpio_set_direction_ExpectAnyArgsAndReturn(ESP_FAIL);
|
||||
|
||||
CHECK_THROWS_AS(GPIO_Output gpio(VALID_GPIO), GPIOException&);
|
||||
|
||||
Mockgpio_Verify();
|
||||
}
|
||||
|
||||
TEST_CASE("output constructor sets correct arguments")
|
||||
{
|
||||
CMOCK_SETUP();
|
||||
gpio_reset_pin_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_num()), ESP_OK);
|
||||
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_num()), GPIO_MODE_OUTPUT, ESP_OK);
|
||||
|
||||
GPIO_Output gpio(VALID_GPIO);
|
||||
|
||||
Mockgpio_Verify();
|
||||
}
|
||||
|
||||
TEST_CASE("output set high fails")
|
||||
{
|
||||
GPIOFixture fix;
|
||||
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), 1, ESP_FAIL);
|
||||
|
||||
GPIO_Output gpio(fix.num);
|
||||
|
||||
CHECK_THROWS_AS(gpio.set_high(), GPIOException&);
|
||||
}
|
||||
|
||||
TEST_CASE("output set high success")
|
||||
{
|
||||
GPIOFixture fix;
|
||||
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), 1, ESP_OK);
|
||||
|
||||
GPIO_Output gpio(fix.num);
|
||||
|
||||
gpio.set_high();
|
||||
}
|
||||
|
||||
TEST_CASE("output set low fails")
|
||||
{
|
||||
GPIOFixture fix;
|
||||
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), 0, ESP_FAIL);
|
||||
|
||||
GPIO_Output gpio(fix.num);
|
||||
|
||||
CHECK_THROWS_AS(gpio.set_low(), GPIOException&);
|
||||
}
|
||||
|
||||
TEST_CASE("output set low success")
|
||||
{
|
||||
GPIOFixture fix;
|
||||
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), 0, ESP_OK);
|
||||
|
||||
GPIO_Output gpio(fix.num);
|
||||
|
||||
gpio.set_low();
|
||||
}
|
||||
|
||||
TEST_CASE("output set drive strength")
|
||||
{
|
||||
GPIOFixture fix(VALID_GPIO);
|
||||
gpio_set_drive_capability_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), GPIO_DRIVE_CAP_0, ESP_OK);
|
||||
|
||||
GPIO_Output gpio(fix.num);
|
||||
|
||||
gpio.set_drive_strength(GPIODriveStrength::WEAK());
|
||||
}
|
||||
|
||||
TEST_CASE("output get drive strength")
|
||||
{
|
||||
GPIOFixture fix(VALID_GPIO);
|
||||
gpio_drive_cap_t drive_strength = GPIO_DRIVE_CAP_3;
|
||||
gpio_get_drive_capability_ExpectAnyArgsAndReturn(ESP_OK);
|
||||
gpio_get_drive_capability_ReturnThruPtr_strength(&drive_strength);
|
||||
|
||||
GPIO_Output gpio(fix.num);
|
||||
|
||||
CHECK(gpio.get_drive_strength() == GPIODriveStrength::STRONGEST());
|
||||
}
|
||||
|
||||
TEST_CASE("GPIOInput setting direction fails")
|
||||
{
|
||||
CMOCK_SETUP();
|
||||
gpio_reset_pin_ExpectAnyArgsAndReturn(ESP_OK);
|
||||
gpio_set_direction_ExpectAnyArgsAndReturn(ESP_FAIL);
|
||||
|
||||
CHECK_THROWS_AS(GPIOInput gpio(VALID_GPIO), GPIOException&);
|
||||
|
||||
Mockgpio_Verify();
|
||||
}
|
||||
|
||||
TEST_CASE("constructor sets correct arguments")
|
||||
{
|
||||
CMOCK_SETUP();
|
||||
gpio_reset_pin_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_num()), ESP_OK);
|
||||
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_num()), GPIO_MODE_INPUT, ESP_OK);
|
||||
|
||||
GPIOInput gpio(VALID_GPIO);
|
||||
|
||||
Mockgpio_Verify();
|
||||
}
|
||||
|
||||
TEST_CASE("get level low")
|
||||
{
|
||||
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
|
||||
gpio_get_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), 0);
|
||||
|
||||
GPIOInput gpio(fix.num);
|
||||
|
||||
CHECK(gpio.get_level() == GPIOLevel::LOW);
|
||||
}
|
||||
|
||||
TEST_CASE("get level high")
|
||||
{
|
||||
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
|
||||
gpio_get_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), 1);
|
||||
|
||||
GPIOInput gpio(fix.num);
|
||||
|
||||
CHECK(gpio.get_level() == GPIOLevel::HIGH);
|
||||
}
|
||||
|
||||
TEST_CASE("set pull mode fails")
|
||||
{
|
||||
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
|
||||
gpio_set_pull_mode_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), GPIO_FLOATING, ESP_FAIL);
|
||||
|
||||
GPIOInput gpio(fix.num);
|
||||
|
||||
CHECK_THROWS_AS(gpio.set_pull_mode(GPIOPullMode::FLOATING()), GPIOException&);
|
||||
}
|
||||
|
||||
TEST_CASE("GPIOInput set pull mode floating")
|
||||
{
|
||||
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
|
||||
gpio_set_pull_mode_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), GPIO_FLOATING, ESP_OK);
|
||||
|
||||
GPIOInput gpio(fix.num);
|
||||
|
||||
gpio.set_pull_mode(GPIOPullMode::FLOATING());
|
||||
}
|
||||
|
||||
TEST_CASE("GPIOInput set pull mode pullup")
|
||||
{
|
||||
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
|
||||
gpio_set_pull_mode_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), GPIO_PULLUP_ONLY, ESP_OK);
|
||||
|
||||
GPIOInput gpio(fix.num);
|
||||
|
||||
gpio.set_pull_mode(GPIOPullMode::PULLUP());
|
||||
}
|
||||
|
||||
TEST_CASE("GPIOInput set pull mode pulldown")
|
||||
{
|
||||
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
|
||||
gpio_set_pull_mode_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), GPIO_PULLDOWN_ONLY, ESP_OK);
|
||||
|
||||
GPIOInput gpio(fix.num);
|
||||
|
||||
gpio.set_pull_mode(GPIOPullMode::PULLDOWN());
|
||||
}
|
||||
|
||||
TEST_CASE("GPIOInput wake up enable fails")
|
||||
{
|
||||
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
|
||||
gpio_wakeup_enable_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), GPIO_INTR_LOW_LEVEL, ESP_FAIL);
|
||||
|
||||
GPIOInput gpio(fix.num);
|
||||
|
||||
CHECK_THROWS_AS(gpio.wakeup_enable(GPIOWakeupIntrType::LOW_LEVEL()), GPIOException&);
|
||||
}
|
||||
|
||||
TEST_CASE("GPIOInput wake up enable high int")
|
||||
{
|
||||
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
|
||||
gpio_wakeup_enable_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), GPIO_INTR_HIGH_LEVEL, ESP_OK);
|
||||
|
||||
GPIOInput gpio(fix.num);
|
||||
|
||||
gpio.wakeup_enable(GPIOWakeupIntrType::HIGH_LEVEL());
|
||||
}
|
||||
|
||||
TEST_CASE("GPIOInput wake up disable fails")
|
||||
{
|
||||
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
|
||||
gpio_wakeup_disable_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), ESP_FAIL);
|
||||
|
||||
GPIOInput gpio(fix.num);
|
||||
|
||||
CHECK_THROWS_AS(gpio.wakeup_disable(), GPIOException&);
|
||||
}
|
||||
|
||||
TEST_CASE("GPIOInput wake up disable high int")
|
||||
{
|
||||
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
|
||||
gpio_wakeup_disable_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), ESP_OK);
|
||||
|
||||
GPIOInput gpio(fix.num);
|
||||
|
||||
gpio.wakeup_disable();
|
||||
}
|
||||
|
||||
TEST_CASE("GPIO_OpenDrain setting direction fails")
|
||||
{
|
||||
CMOCK_SETUP();
|
||||
gpio_reset_pin_ExpectAnyArgsAndReturn(ESP_OK);
|
||||
gpio_set_direction_ExpectAnyArgsAndReturn(ESP_FAIL);
|
||||
|
||||
CHECK_THROWS_AS(GPIO_OpenDrain gpio(VALID_GPIO), GPIOException&);
|
||||
|
||||
Mockgpio_Verify();
|
||||
}
|
||||
|
||||
TEST_CASE("GPIO_OpenDrain constructor sets correct arguments")
|
||||
{
|
||||
CMOCK_SETUP();
|
||||
gpio_reset_pin_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_num()), ESP_OK);
|
||||
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_num()),
|
||||
GPIO_MODE_INPUT,
|
||||
ESP_OK);
|
||||
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_num()),
|
||||
GPIO_MODE_INPUT_OUTPUT_OD,
|
||||
ESP_OK);
|
||||
|
||||
GPIO_OpenDrain gpio(VALID_GPIO);
|
||||
|
||||
Mockgpio_Verify();
|
||||
}
|
||||
|
||||
TEST_CASE("GPIO_OpenDrain set floating fails")
|
||||
{
|
||||
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
|
||||
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_num()),
|
||||
GPIO_MODE_INPUT_OUTPUT_OD,
|
||||
ESP_OK);
|
||||
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), 1, ESP_FAIL);
|
||||
|
||||
GPIO_OpenDrain gpio(fix.num);
|
||||
|
||||
CHECK_THROWS_AS(gpio.set_floating(), GPIOException&);
|
||||
}
|
||||
|
||||
TEST_CASE("GPIO_OpenDrain set floating success")
|
||||
{
|
||||
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
|
||||
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_num()),
|
||||
GPIO_MODE_INPUT_OUTPUT_OD,
|
||||
ESP_OK);
|
||||
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), 1, ESP_OK);
|
||||
|
||||
GPIO_OpenDrain gpio(fix.num);
|
||||
|
||||
gpio.set_floating();
|
||||
}
|
||||
|
||||
TEST_CASE("GPIO_OpenDrain set low fails")
|
||||
{
|
||||
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
|
||||
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_num()),
|
||||
GPIO_MODE_INPUT_OUTPUT_OD,
|
||||
ESP_OK);
|
||||
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), 0, ESP_FAIL);
|
||||
|
||||
GPIO_OpenDrain gpio(fix.num);
|
||||
|
||||
CHECK_THROWS_AS(gpio.set_low(), GPIOException&);
|
||||
}
|
||||
|
||||
TEST_CASE("GPIO_OpenDrain set low success")
|
||||
{
|
||||
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
|
||||
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_num()),
|
||||
GPIO_MODE_INPUT_OUTPUT_OD,
|
||||
ESP_OK);
|
||||
gpio_set_level_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), 0, ESP_OK);
|
||||
|
||||
GPIO_OpenDrain gpio(fix.num);
|
||||
|
||||
gpio.set_low();
|
||||
}
|
||||
|
||||
TEST_CASE("GPIO_OpenDrain set drive strength")
|
||||
{
|
||||
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
|
||||
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_num()),
|
||||
GPIO_MODE_INPUT_OUTPUT_OD,
|
||||
ESP_OK);
|
||||
|
||||
gpio_set_drive_capability_ExpectAndReturn(static_cast<gpio_num_t>(fix.num.get_num()), GPIO_DRIVE_CAP_0, ESP_OK);
|
||||
GPIO_OpenDrain gpio(fix.num);
|
||||
|
||||
gpio.set_drive_strength(GPIODriveStrength::WEAK());
|
||||
}
|
||||
|
||||
TEST_CASE("GPIO_OpenDrain get drive strength")
|
||||
{
|
||||
GPIOFixture fix(VALID_GPIO, GPIO_MODE_INPUT);
|
||||
gpio_set_direction_ExpectAndReturn(static_cast<gpio_num_t>(VALID_GPIO.get_num()),
|
||||
GPIO_MODE_INPUT_OUTPUT_OD,
|
||||
ESP_OK);
|
||||
gpio_drive_cap_t drive_strength = GPIO_DRIVE_CAP_3;
|
||||
gpio_get_drive_capability_ExpectAnyArgsAndReturn(ESP_OK);
|
||||
gpio_get_drive_capability_ReturnThruPtr_strength(&drive_strength);
|
||||
|
||||
GPIO_OpenDrain gpio(fix.num);
|
||||
|
||||
CHECK(gpio.get_drive_strength() == GPIODriveStrength::STRONGEST());
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
|
||||
CONFIG_IDF_TARGET="linux"
|
||||
CONFIG_CXX_EXCEPTIONS=y
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include "esp_exception.hpp"
|
||||
#include "esp_timer.h"
|
||||
|
||||
|
||||
@@ -0,0 +1,402 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if __cpp_exceptions
|
||||
|
||||
#include "esp_exception.hpp"
|
||||
#include "system_cxx.hpp"
|
||||
|
||||
namespace idf {
|
||||
|
||||
/**
|
||||
* @brief Exception thrown for errors in the GPIO C++ API.
|
||||
*/
|
||||
struct GPIOException : public ESPException {
|
||||
/**
|
||||
* @param error The IDF error representing the error class of the error to throw.
|
||||
*/
|
||||
GPIOException(esp_err_t error);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the numeric pin number is valid on the current hardware.
|
||||
*/
|
||||
esp_err_t check_gpio_pin_num(uint32_t pin_num) noexcept;
|
||||
|
||||
/**
|
||||
* Check if the numeric value of a drive strength is valid on the current hardware.
|
||||
*/
|
||||
esp_err_t check_gpio_drive_strength(uint32_t strength) noexcept;
|
||||
|
||||
/**
|
||||
* This is a "Strong Value Type" class for GPIO. The GPIO pin number is checked during construction according to
|
||||
* the hardware capabilities. This means that any GPIONumBase object is guaranteed to contain a valid GPIO number.
|
||||
* See also the template class \c StrongValue.
|
||||
*/
|
||||
template<typename GPIONumFinalType>
|
||||
class GPIONumBase final : public StrongValueComparable<uint32_t> {
|
||||
public:
|
||||
/**
|
||||
* @brief Create a numerical pin number representation and make sure it's correct.
|
||||
*
|
||||
* @throw GPIOException if the number does not reflect a valid GPIO number on the current hardware.
|
||||
*/
|
||||
GPIONumBase(uint32_t pin) : StrongValueComparable<uint32_t>(pin)
|
||||
{
|
||||
esp_err_t pin_check_result = check_gpio_pin_num(pin);
|
||||
if (pin_check_result != ESP_OK) {
|
||||
throw GPIOException(pin_check_result);
|
||||
}
|
||||
}
|
||||
|
||||
using StrongValueComparable<uint32_t>::operator==;
|
||||
using StrongValueComparable<uint32_t>::operator!=;
|
||||
|
||||
/**
|
||||
* Retrieves the valid numerical representation of the GPIO number.
|
||||
*/
|
||||
uint32_t get_num() const { return get_value(); };
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a TAG type whose sole purpose is to create a distinct type from GPIONumBase.
|
||||
*/
|
||||
class GPIONumType;
|
||||
|
||||
/**
|
||||
* A GPIO number type used for general GPIOs, in contrast to specific GPIO pins like e.g. SPI_SCLK.
|
||||
*/
|
||||
using GPIONum = GPIONumBase<class GPIONumType>;
|
||||
|
||||
/**
|
||||
* Level of an input GPIO.
|
||||
*/
|
||||
enum class GPIOLevel {
|
||||
HIGH,
|
||||
LOW
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a valid pull up configuration for GPIOs.
|
||||
* It is supposed to resemble an enum type, hence it has static creation methods and a private constructor.
|
||||
* This class is a "Strong Value Type", see also the template class \c StrongValue for more properties.
|
||||
*/
|
||||
class GPIOPullMode final : public StrongValueComparable<uint32_t> {
|
||||
private:
|
||||
/**
|
||||
* Constructor is private since it should only be accessed by the static creation methods.
|
||||
*
|
||||
* @param pull_mode A valid numerical respresentation of the pull up configuration. Must be valid!
|
||||
*/
|
||||
GPIOPullMode(uint32_t pull_mode) : StrongValueComparable<uint32_t>(pull_mode) { }
|
||||
|
||||
public:
|
||||
/**
|
||||
* Create a representation of a floating pin configuration.
|
||||
* For more information, check the driver and HAL files.
|
||||
*/
|
||||
static GPIOPullMode FLOATING();
|
||||
|
||||
/**
|
||||
* Create a representation of a pullup configuration.
|
||||
* For more information, check the driver and HAL files.
|
||||
*/
|
||||
static GPIOPullMode PULLUP();
|
||||
|
||||
/**
|
||||
* Create a representation of a pulldown configuration.
|
||||
* For more information, check the driver and HAL files.
|
||||
*/
|
||||
static GPIOPullMode PULLDOWN();
|
||||
|
||||
using StrongValueComparable<uint32_t>::operator==;
|
||||
using StrongValueComparable<uint32_t>::operator!=;
|
||||
|
||||
/**
|
||||
* Retrieves the valid numerical representation of the pull mode.
|
||||
*/
|
||||
uint32_t get_pull_mode() const { return get_value(); };
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Represents a valid wakup interrupt type for GPIO inputs.
|
||||
*
|
||||
* This class is a "Strong Value Type", see also the template class \c StrongValue for more properties.
|
||||
* It is supposed to resemble an enum type, hence it has static creation methods and a private constructor.
|
||||
* For a detailed mapping of interrupt types to numeric values, please refer to the driver types and implementation.
|
||||
*/
|
||||
class GPIOWakeupIntrType final: public StrongValueComparable<uint32_t> {
|
||||
private:
|
||||
/**
|
||||
* Constructor is private since it should only be accessed by the static creation methods.
|
||||
*
|
||||
* @param pull_mode A valid numerical respresentation of a possible interrupt level to wake up. Must be valid!
|
||||
*/
|
||||
GPIOWakeupIntrType(uint32_t interrupt_level) : StrongValueComparable<uint32_t>(interrupt_level) { }
|
||||
|
||||
public:
|
||||
static GPIOWakeupIntrType LOW_LEVEL();
|
||||
static GPIOWakeupIntrType HIGH_LEVEL();
|
||||
|
||||
/**
|
||||
* Retrieves the valid numerical representation of the pull mode.
|
||||
*/
|
||||
uint32_t get_level() const noexcept { return get_value(); };
|
||||
};
|
||||
|
||||
/**
|
||||
* Class representing a valid drive strength for GPIO outputs.
|
||||
* This class is a "Strong Value Type", see also the template class \c StrongValue for more properties.
|
||||
* For a detailed mapping for values to drive strengths, please refer to the datasheet of the chip you are using.
|
||||
* E.g. for ESP32, the values in general are the following:
|
||||
* - WEAK: 5mA
|
||||
* - STRONGER: 10mA
|
||||
* - DEFAULT/MEDIUM: 20mA
|
||||
* - STRONGEST: 40mA
|
||||
*/
|
||||
class GPIODriveStrength final : public StrongValueComparable<uint32_t> {
|
||||
public:
|
||||
/**
|
||||
* @brief Create a drive strength representation and checks its validity.
|
||||
*
|
||||
* After construction, this class should have a guaranteed valid strength representation.
|
||||
*
|
||||
* @param strength the numeric value mapping for a particular strength. For possible ranges, look at the
|
||||
* static creation functions below.
|
||||
* @throws GPIOException if the supplied number is out of the hardware capable range.
|
||||
*/
|
||||
GPIODriveStrength(uint32_t strength) : StrongValueComparable<uint32_t>(strength)
|
||||
{
|
||||
esp_err_t strength_check_result = check_gpio_drive_strength(strength);
|
||||
if (strength_check_result != ESP_OK) {
|
||||
throw GPIOException(strength_check_result);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a representation of the default drive strength.
|
||||
* For more information, check the datasheet and driver and HAL files.
|
||||
*/
|
||||
static GPIODriveStrength DEFAULT();
|
||||
|
||||
/**
|
||||
* Create a representation of the weak drive strength.
|
||||
* For more information, check the datasheet and driver and HAL files.
|
||||
*/
|
||||
static GPIODriveStrength WEAK();
|
||||
|
||||
/**
|
||||
* Create a representation of the less weak drive strength.
|
||||
* For more information, check the datasheet and driver and HAL files.
|
||||
*/
|
||||
static GPIODriveStrength LESS_WEAK();
|
||||
|
||||
/**
|
||||
* Create a representation of the medium drive strength.
|
||||
* For more information, check the datasheet and driver and HAL files.
|
||||
*/
|
||||
static GPIODriveStrength MEDIUM();
|
||||
|
||||
/**
|
||||
* Create a representation of the strong drive strength.
|
||||
*/
|
||||
static GPIODriveStrength STRONGEST();
|
||||
|
||||
using StrongValueComparable<uint32_t>::operator==;
|
||||
using StrongValueComparable<uint32_t>::operator!=;
|
||||
|
||||
/**
|
||||
* Retrieves the valid numerical representation of the drive strength.
|
||||
*/
|
||||
uint32_t get_strength() const { return get_value(); };
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Implementations commonly used functionality for all GPIO configurations.
|
||||
*
|
||||
* Some functionality is only for specific configurations (set and get drive strength) but is necessary here
|
||||
* to avoid complicating the inheritance hierarchy of the GPIO classes.
|
||||
* Child classes implementing any GPIO configuration (output, input, etc.) are meant to intherit from this class
|
||||
* and possibly make some of the functionality publicly available.
|
||||
*/
|
||||
class GPIOBase {
|
||||
protected:
|
||||
/**
|
||||
* @brief Construct a GPIO.
|
||||
*
|
||||
* This constructor will only reset the GPIO but leaves the actual configuration (input, output, etc.) to
|
||||
* the sub class.
|
||||
*
|
||||
* @param num GPIO pin number of the GPIO to be configured.
|
||||
*
|
||||
* @throws GPIOException
|
||||
* - if the underlying driver function fails
|
||||
*/
|
||||
GPIOBase(GPIONum num);
|
||||
|
||||
/**
|
||||
* @brief Enable gpio pad hold function.
|
||||
*
|
||||
* The gpio pad hold function works in both input and output modes, but must be output-capable gpios.
|
||||
* If pad hold enabled:
|
||||
* in output mode: the output level of the pad will be force locked and can not be changed.
|
||||
* in input mode: the input value read will not change, regardless the changes of input signal.
|
||||
*
|
||||
* @throws GPIOException if the underlying driver function fails.
|
||||
*/
|
||||
void hold_en();
|
||||
|
||||
/**
|
||||
* @brief Disable gpio pad hold function.
|
||||
*
|
||||
* @throws GPIOException if the underlying driver function fails.
|
||||
*/
|
||||
void hold_dis();
|
||||
|
||||
/**
|
||||
* @brief Configure the drive strength of the GPIO.
|
||||
*
|
||||
* @param strength The drive strength. Refer to \c GPIODriveStrength for more details.
|
||||
*
|
||||
* @throws GPIOException if the underlying driver function fails.
|
||||
*/
|
||||
void set_drive_strength(GPIODriveStrength strength);
|
||||
|
||||
/**
|
||||
* @brief Return the current drive strength of the GPIO.
|
||||
*
|
||||
* @return The currently configured drive strength. Refer to \c GPIODriveStrength for more details.
|
||||
*
|
||||
* @throws GPIOException if the underlying driver function fails.
|
||||
*/
|
||||
GPIODriveStrength get_drive_strength();
|
||||
|
||||
/**
|
||||
* @brief The number of the configured GPIO pin.
|
||||
*/
|
||||
GPIONum gpio_num;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This class represents a GPIO which is configured as output.
|
||||
*/
|
||||
class GPIO_Output : public GPIOBase {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct and configure a GPIO as output.
|
||||
*
|
||||
* @param num GPIO pin number of the GPIO to be configured.
|
||||
*
|
||||
* @throws GPIOException
|
||||
* - if the underlying driver function fails
|
||||
*/
|
||||
GPIO_Output(GPIONum num);
|
||||
|
||||
/**
|
||||
* @brief Set GPIO to high level.
|
||||
*
|
||||
* @throws GPIOException if the underlying driver function fails.
|
||||
*/
|
||||
void set_high();
|
||||
|
||||
/**
|
||||
* @brief Set GPIO to low level.
|
||||
*
|
||||
* @throws GPIOException if the underlying driver function fails.
|
||||
*/
|
||||
void set_low();
|
||||
|
||||
using GPIOBase::set_drive_strength;
|
||||
using GPIOBase::get_drive_strength;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This class represents a GPIO which is configured as input.
|
||||
*/
|
||||
class GPIOInput : public GPIOBase {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct and configure a GPIO as input.
|
||||
*
|
||||
* @param num GPIO pin number of the GPIO to be configured.
|
||||
*
|
||||
* @throws GPIOException
|
||||
* - if the underlying driver function fails
|
||||
*/
|
||||
GPIOInput(GPIONum num);
|
||||
|
||||
/**
|
||||
* @brief Read the current level of the GPIO.
|
||||
*
|
||||
* @return The GPIO current level of the GPIO.
|
||||
*/
|
||||
GPIOLevel get_level() const noexcept;
|
||||
|
||||
/**
|
||||
* @brief Configure the internal pull-up and pull-down restors.
|
||||
*
|
||||
* @param mode The pull-up/pull-down configuration see \c GPIOPullMode.
|
||||
*
|
||||
* @throws GPIOException if the underlying driver function fails.
|
||||
*/
|
||||
void set_pull_mode(GPIOPullMode mode);
|
||||
|
||||
/**
|
||||
* @brief Configure the pin as wake up pin.
|
||||
*
|
||||
* @throws GPIOException if the underlying driver function fails.
|
||||
*/
|
||||
void wakeup_enable(GPIOWakeupIntrType interrupt_type);
|
||||
|
||||
/**
|
||||
* @brief Disable wake up functionality for this pin if it was enabled before.
|
||||
*
|
||||
* @throws GPIOException if the underlying driver function fails.
|
||||
*/
|
||||
void wakeup_disable();
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This class represents a GPIO which is configured as open drain output and input at the same time.
|
||||
*
|
||||
* This class facilitates bit-banging for single wire protocols.
|
||||
*/
|
||||
class GPIO_OpenDrain : public GPIOInput {
|
||||
public:
|
||||
/**
|
||||
* @brief Construct and configure a GPIO as open drain output as well as input.
|
||||
*
|
||||
* @param num GPIO pin number of the GPIO to be configured.
|
||||
*
|
||||
* @throws GPIOException
|
||||
* - if the underlying driver function fails
|
||||
*/
|
||||
GPIO_OpenDrain(GPIONum num);
|
||||
|
||||
/**
|
||||
* @brief Set GPIO to floating level.
|
||||
*
|
||||
* @throws GPIOException if the underlying driver function fails.
|
||||
*/
|
||||
void set_floating();
|
||||
|
||||
/**
|
||||
* @brief Set GPIO to low level.
|
||||
*
|
||||
* @throws GPIOException if the underlying driver function fails.
|
||||
*/
|
||||
void set_low();
|
||||
|
||||
using GPIOBase::set_drive_strength;
|
||||
using GPIOBase::get_drive_strength;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -475,7 +475,7 @@ TReturn I2CTransfer<TReturn>::do_transfer(i2c_port_t i2c_num, uint8_t i2c_addr)
|
||||
|
||||
CHECK_THROW_SPECIFIC(i2c_master_stop(cmd_link.handle), I2CException);
|
||||
|
||||
CHECK_THROW_SPECIFIC(i2c_master_cmd_begin(i2c_num, cmd_link.handle, 1000 / portTICK_RATE_MS), I2CTransferException);
|
||||
CHECK_THROW_SPECIFIC(i2c_master_cmd_begin(i2c_num, cmd_link.handle, driver_timeout / portTICK_RATE_MS), I2CTransferException);
|
||||
|
||||
return process_result();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __cpp_exceptions
|
||||
#error system C++ classes only usable when C++ exceptions enabled. Enable CONFIG_COMPILER_CXX_EXCEPTIONS in Kconfig
|
||||
#endif
|
||||
|
||||
/**
|
||||
* This is a "Strong Value Type" base class for types in IDF C++ classes.
|
||||
* The idea is that subclasses completely check the contained value during construction.
|
||||
* After that, it's trapped and encapsulated inside and cannot be changed anymore.
|
||||
* Consequently, the API functions receiving a correctly implemented sub class as parameter
|
||||
* don't need to check it anymore. Only at API boundaries the valid value will be retrieved
|
||||
* with get_value().
|
||||
*/
|
||||
template<typename ValueT>
|
||||
class StrongValue {
|
||||
protected:
|
||||
StrongValue(ValueT value_arg) : value(value_arg) { }
|
||||
|
||||
ValueT get_value() const {
|
||||
return value;
|
||||
}
|
||||
|
||||
private:
|
||||
ValueT value;
|
||||
};
|
||||
|
||||
/**
|
||||
* This class adds comparison properties to StrongValue, but no sorting properties.
|
||||
*/
|
||||
template<typename ValueT>
|
||||
class StrongValueComparable : public StrongValue<ValueT> {
|
||||
protected:
|
||||
StrongValueComparable(ValueT value_arg) : StrongValue<ValueT>(value_arg) { }
|
||||
|
||||
using StrongValue<ValueT>::get_value;
|
||||
|
||||
bool operator==(const StrongValueComparable<ValueT> &other_gpio) const
|
||||
{
|
||||
return get_value() == other_gpio.get_value();
|
||||
}
|
||||
|
||||
bool operator!=(const StrongValueComparable<ValueT> &other_gpio) const
|
||||
{
|
||||
return get_value() != other_gpio.get_value();
|
||||
}
|
||||
};
|
||||
@@ -44,82 +44,6 @@ struct RefClock {
|
||||
}
|
||||
};
|
||||
|
||||
TEST_CASE("ESPTimer null function", "[ESPTimer]")
|
||||
{
|
||||
TEST_THROW(ESPTimer(nullptr), ESPException);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPTimer empty std::function", "[ESPTimer]")
|
||||
{
|
||||
function<void()> nothing;
|
||||
TEST_THROW(ESPTimer(nothing, "test"), ESPException);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPTimer starting twice throws", "[ESPTimer]")
|
||||
{
|
||||
function<void()> timer_cb = [&]() { };
|
||||
|
||||
ESPTimer timer(timer_cb);
|
||||
|
||||
timer.start(chrono::microseconds(5000));
|
||||
|
||||
TEST_THROW(timer.start(chrono::microseconds(5000)), ESPException);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPTimer periodically starting twice throws", "[ESPTimer]")
|
||||
{
|
||||
function<void()> timer_cb = [&]() { };
|
||||
|
||||
ESPTimer timer(timer_cb);
|
||||
|
||||
timer.start_periodic(chrono::microseconds(5000));
|
||||
|
||||
TEST_THROW(timer.start_periodic(chrono::microseconds(5000)), ESPException);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPTimer stopping non-started timer throws", "[ESPTimer]")
|
||||
{
|
||||
function<void()> timer_cb = [&]() { };
|
||||
|
||||
ESPTimer timer(timer_cb);
|
||||
|
||||
TEST_THROW(timer.stop(), ESPException);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPTimer calls callback", "[ESPTimer]")
|
||||
{
|
||||
bool called = false;
|
||||
|
||||
function<void()> timer_cb = [&]() {
|
||||
called = true;
|
||||
};
|
||||
|
||||
ESPTimer timer(timer_cb);
|
||||
|
||||
timer.start(chrono::microseconds(5000));
|
||||
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
|
||||
TEST_ASSERT(called);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPTimer periodically calls callback", "[ESPTimer]")
|
||||
{
|
||||
size_t called = 0;
|
||||
|
||||
function<void()> timer_cb = [&]() {
|
||||
called++;
|
||||
};
|
||||
|
||||
ESPTimer timer(timer_cb);
|
||||
|
||||
timer.start_periodic(chrono::microseconds(2000));
|
||||
|
||||
vTaskDelay(10 / portTICK_PERIOD_MS);
|
||||
|
||||
TEST_ASSERT(called >= 4u);
|
||||
}
|
||||
|
||||
TEST_CASE("ESPTimer produces correct delay", "[ESPTimer]")
|
||||
{
|
||||
int64_t t_end;
|
||||
|
||||
@@ -69,6 +69,16 @@ TEST_CASE("I2CMaster SDA and SCL equal", "[cxx i2c][leaks=300]")
|
||||
TEST_THROW(I2CMaster(0, 0, 0, 400000), I2CException);
|
||||
}
|
||||
|
||||
TEST_CASE("I2Transfer timeout", "[cxx i2c][leaks=300]")
|
||||
{
|
||||
std::vector<uint8_t> data = {MAGIC_TEST_NUMBER};
|
||||
|
||||
// I2CWrite directly inherits from I2CTransfer; it's representative for I2CRead and I2CComposed, too.
|
||||
I2CWrite writer(data, chrono::milliseconds(50));
|
||||
|
||||
TEST_THROW(writer.do_transfer(I2C_MASTER_NUM, ADDR), I2CTransferException);
|
||||
}
|
||||
|
||||
// TODO The I2C driver tests are disabled, so disable them here, too. Probably due to no runners.
|
||||
#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2, ESP32S3)
|
||||
|
||||
|
||||
@@ -61,11 +61,18 @@ menu "Example Configuration"
|
||||
RTL8201F/SR8201F is a single port 10/100Mb Ethernet Transceiver with auto MDIX.
|
||||
Goto http://www.corechip-sz.com/productsview.asp?id=22 for more information about it.
|
||||
|
||||
config EXAMPLE_ETH_PHY_LAN8720
|
||||
bool "LAN8720"
|
||||
config EXAMPLE_ETH_PHY_LAN87XX
|
||||
bool "LAN87xx"
|
||||
help
|
||||
Below chips are supported:
|
||||
LAN8710A is a small footprint MII/RMII 10/100 Ethernet Transceiver with HP Auto-MDIX and
|
||||
flexPWR® Technology.
|
||||
LAN8720A is a small footprint RMII 10/100 Ethernet Transceiver with HP Auto-MDIX Support.
|
||||
Goto https://www.microchip.com/LAN8720A for more information about it.
|
||||
LAN8740A/LAN8741A is a small footprint MII/RMII 10/100 Energy Efficient Ethernet Transceiver
|
||||
with HP Auto-MDIX and flexPWR® Technology.
|
||||
LAN8742A is a small footprint RMII 10/100 Ethernet Transceiver with HP Auto-MDIX and
|
||||
flexPWR® Technology.
|
||||
Goto https://www.microchip.com for more information about them.
|
||||
|
||||
config EXAMPLE_ETH_PHY_DP83848
|
||||
bool "DP83848"
|
||||
|
||||
@@ -92,8 +92,8 @@ void app_main(void)
|
||||
esp_eth_phy_t *phy = esp_eth_phy_new_ip101(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_ETH_PHY_RTL8201
|
||||
esp_eth_phy_t *phy = esp_eth_phy_new_rtl8201(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_ETH_PHY_LAN8720
|
||||
esp_eth_phy_t *phy = esp_eth_phy_new_lan8720(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_ETH_PHY_LAN87XX
|
||||
esp_eth_phy_t *phy = esp_eth_phy_new_lan87xx(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_ETH_PHY_DP83848
|
||||
esp_eth_phy_t *phy = esp_eth_phy_new_dp83848(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_ETH_PHY_KSZ8041
|
||||
|
||||
@@ -16,6 +16,13 @@ If you have a more complicated application to go (for example, connect to some I
|
||||
To run this example, you need to prepare following hardwares:
|
||||
* [ESP32 board](https://docs.espressif.com/projects/esp-idf/en/latest/hw-reference/modules-and-boards.html) (e.g. ESP32-PICO, ESP32 DevKitC, etc)
|
||||
* ENC28J60 module (the latest revision should be 6)
|
||||
* **!! IMPORTANT !!** Proper input power source since ENC28J60 is quite power consuming device (it consumes more than 200 mA in peaks when transmitting). If improper power source is used, input voltage may drop and ENC28J60 may either provide nonsense response to host controller via SPI (fail to read registers properly) or it may enter to some strange state in the worst case. There are several options how to resolve it:
|
||||
* Power ESP32 board from `USB 3.0`, if board is used as source of power to ENC board.
|
||||
* Power ESP32 board from external 5V power supply with current limit at least 1 A, if board is used as source of power to ENC board.
|
||||
* Power ENC28J60 from external 3.3V power supply with common GND to ESP32 board. Note that there might be some ENC28J60 boards with integrated voltage regulator on market and so powered by 5 V. Please consult documentation of your board for details.
|
||||
|
||||
If a ESP32 board is used as source of power to ENC board, ensure that that particular board is assembled with voltage regulator capable to deliver current up to 1 A. This is a case of ESP32 DevKitC or ESP-WROVER-KIT, for example. Such setup was tested and works as expected. Other boards may use different voltage regulators and may perform differently.
|
||||
**WARNING:** Always consult documentation/schematics associated with particular ENC28J60 and ESP32 boards used in your use-case first.
|
||||
|
||||
#### Pin Assignment
|
||||
|
||||
@@ -35,9 +42,9 @@ To run this example, you need to prepare following hardwares:
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
In the `Example Configuration` menu, set SPI specific configuration, such as SPI host number, GPIO used for MISO/MOSI/CS signal, GPIO for interrupt event and the SPI clock rate.
|
||||
In the `Example Configuration` menu, set SPI specific configuration, such as SPI host number, GPIO used for MISO/MOSI/CS signal, GPIO for interrupt event and the SPI clock rate, duplex mode.
|
||||
|
||||
**Note:** According to ENC28J60 data sheet, SPI clock could reach up to 20MHz, but in practice, the clock speed will depend on your PCB layout (in this example, the default clock rate is set to 6MHz, just to make sure that most modules on the market can work at this speed).
|
||||
**Note:** According to ENC28J60 data sheet and our internal testing, SPI clock could reach up to 20MHz, but in practice, the clock speed may depend on your PCB layout/wiring/power source. In this example, the default clock rate is set to 8 MHz since some ENC28J60 silicon revisions may not properly work at frequencies less than 8 MHz.
|
||||
|
||||
### Build, Flash, and Run
|
||||
|
||||
@@ -78,7 +85,16 @@ Now you can ping your ESP32 in the terminal by entering `ping 192.168.2.34` (it
|
||||
|
||||
**Notes:**
|
||||
1. ENC28J60 hasn't burned any valid MAC address in the chip, you need to write an unique MAC address into its internal MAC address register before any traffic happened on TX and RX line.
|
||||
2. ENC28J60 does not support automatic duplex negotiation. If it is connected to an automatic duplex negotiation enabled network switch or Ethernet controller, then ENC28J60 will be detected as a half-duplex device. To communicate in Full-Duplex mode, ENC28J60 and the remote node (switch, router or Ethernet controller) must be manually configured for full-duplex operation.
|
||||
2. It is recommended to operate the ENC28J60 in full-duplex mode since various errata exist to the half-duplex mode (even though addressed in the example) and due to its poor performance in the half-duplex mode (especially in TCP connections). However, ENC28J60 does not support automatic duplex negotiation. If it is connected to an automatic duplex negotiation enabled network switch or Ethernet controller, then ENC28J60 will be detected as a half-duplex device. To communicate in Full-Duplex mode, ENC28J60 and the remote node (switch, router or Ethernet controller) **must be manually configured for full-duplex operation**:
|
||||
* The ENC28J60 can be set to full-duplex in the `Example Configuration` menu.
|
||||
* On Ubuntu/Debian Linux distribution use:
|
||||
```
|
||||
sudo ethtool -s YOUR_INTERFACE_NAME speed 10 duplex full autoneg off
|
||||
```
|
||||
* On Windows, go to `Network Connections` -> `Change adapter options` -> open `Properties` of selected network card -> `Configure` -> `Advanced` -> `Link Speed & Duplex` -> select `10 Mbps Full Duplex in dropdown menu`.
|
||||
3. Ensure that your wiring between ESP32 board and the ENC28J60 board is realized by short wires with the same length and no wire crossings.
|
||||
4. CS Hold Time needs to be configured to be at least 210 ns to properly read MAC and MII registers as defined by ENC28J60 Data Sheet. This is automatically configured in the example based on selected SPI clock frequency by computing amount of SPI bit-cycles the CS should stay active after the transmission. However, if your PCB design/wiring requires different value, please update `cs_ena_posttrans` member of `devcfg` structure per your actual needs.
|
||||
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "esp_eth_mac_enc28j60.c"
|
||||
"esp_eth_phy_enc28j60.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,6 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := .
|
||||
@@ -18,10 +18,6 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "esp_eth_mac.h"
|
||||
#include "esp_eth_phy.h"
|
||||
#include "driver/spi_master.h"
|
||||
|
||||
/**
|
||||
* @brief SPI Instruction Set
|
||||
*
|
||||
@@ -237,48 +233,6 @@ extern "C" {
|
||||
#define EFLOCON_FCEN1 (1<<1) // Flow Control Enable 1
|
||||
#define EFLOCON_FCEN0 (1<<0) // Flow Control Enable 0
|
||||
|
||||
/**
|
||||
* @brief ENC28J60 specific configuration
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
spi_device_handle_t spi_hdl; /*!< Handle of SPI device driver */
|
||||
int int_gpio_num; /*!< Interrupt GPIO number */
|
||||
} eth_enc28j60_config_t;
|
||||
|
||||
/**
|
||||
* @brief Default ENC28J60 specific configuration
|
||||
*
|
||||
*/
|
||||
#define ETH_ENC28J60_DEFAULT_CONFIG(spi_device) \
|
||||
{ \
|
||||
.spi_hdl = spi_device, \
|
||||
.int_gpio_num = 4, \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create ENC28J60 Ethernet MAC instance
|
||||
*
|
||||
* @param[in] enc28j60_config: ENC28J60 specific configuration
|
||||
* @param[in] mac_config: Ethernet MAC configuration
|
||||
*
|
||||
* @return
|
||||
* - instance: create MAC instance successfully
|
||||
* - NULL: create MAC instance failed because some error occurred
|
||||
*/
|
||||
esp_eth_mac_t *esp_eth_mac_new_enc28j60(const eth_enc28j60_config_t *enc28j60_config, const eth_mac_config_t *mac_config);
|
||||
|
||||
/**
|
||||
* @brief Create a PHY instance of ENC28J60
|
||||
*
|
||||
* @param[in] config: configuration of PHY
|
||||
*
|
||||
* @return
|
||||
* - instance: create PHY instance successfully
|
||||
* - NULL: create PHY instance failed because some error occurred
|
||||
*/
|
||||
esp_eth_phy_t *esp_eth_phy_new_enc28j60(const eth_phy_config_t *config);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,125 @@
|
||||
// Copyright 2021 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
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "esp_eth_phy.h"
|
||||
#include "esp_eth_mac.h"
|
||||
#include "driver/spi_master.h"
|
||||
|
||||
#define CS_HOLD_TIME_MIN_NS 210
|
||||
|
||||
/**
|
||||
* @brief ENC28J60 specific configuration
|
||||
*
|
||||
*/
|
||||
typedef struct {
|
||||
spi_device_handle_t spi_hdl; /*!< Handle of SPI device driver */
|
||||
int int_gpio_num; /*!< Interrupt GPIO number */
|
||||
} eth_enc28j60_config_t;
|
||||
|
||||
/**
|
||||
* @brief ENC28J60 Supported Revisions
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
ENC28J60_REV_B1 = 0b00000010,
|
||||
ENC28J60_REV_B4 = 0b00000100,
|
||||
ENC28J60_REV_B5 = 0b00000101,
|
||||
ENC28J60_REV_B7 = 0b00000110
|
||||
} eth_enc28j60_rev_t;
|
||||
|
||||
/**
|
||||
* @brief Default ENC28J60 specific configuration
|
||||
*
|
||||
*/
|
||||
#define ETH_ENC28J60_DEFAULT_CONFIG(spi_device) \
|
||||
{ \
|
||||
.spi_hdl = spi_device, \
|
||||
.int_gpio_num = 4, \
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Compute amount of SPI bit-cycles the CS should stay active after the transmission
|
||||
* to meet ENC28J60 CS Hold Time specification.
|
||||
*
|
||||
* @param clock_speed_mhz SPI Clock frequency in MHz (valid range is <1, 20>)
|
||||
* @return uint8_t
|
||||
*/
|
||||
static inline uint8_t enc28j60_cal_spi_cs_hold_time(int clock_speed_mhz)
|
||||
{
|
||||
if (clock_speed_mhz <= 0 || clock_speed_mhz > 20) {
|
||||
return 0;
|
||||
}
|
||||
int temp = clock_speed_mhz * CS_HOLD_TIME_MIN_NS;
|
||||
uint8_t cs_posttrans = temp / 1000;
|
||||
if (temp % 1000) {
|
||||
cs_posttrans += 1;
|
||||
}
|
||||
|
||||
return cs_posttrans;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create ENC28J60 Ethernet MAC instance
|
||||
*
|
||||
* @param[in] enc28j60_config: ENC28J60 specific configuration
|
||||
* @param[in] mac_config: Ethernet MAC configuration
|
||||
*
|
||||
* @return
|
||||
* - instance: create MAC instance successfully
|
||||
* - NULL: create MAC instance failed because some error occurred
|
||||
*/
|
||||
esp_eth_mac_t *esp_eth_mac_new_enc28j60(const eth_enc28j60_config_t *enc28j60_config, const eth_mac_config_t *mac_config);
|
||||
|
||||
/**
|
||||
* @brief Create a PHY instance of ENC28J60
|
||||
*
|
||||
* @param[in] config: configuration of PHY
|
||||
*
|
||||
* @return
|
||||
* - instance: create PHY instance successfully
|
||||
* - NULL: create PHY instance failed because some error occurred
|
||||
*/
|
||||
esp_eth_phy_t *esp_eth_phy_new_enc28j60(const eth_phy_config_t *config);
|
||||
|
||||
// todo: the below functions should be accessed through ioctl in the future
|
||||
/**
|
||||
* @brief Set ENC28J60 Duplex mode. It sets Duplex mode first to the PHY and then
|
||||
* MAC is set based on what PHY indicates.
|
||||
*
|
||||
* @param phy ENC28J60 PHY Handle
|
||||
* @param duplex Duplex mode
|
||||
*
|
||||
* @return esp_err_t
|
||||
* - ESP_OK when PHY registers were correctly written.
|
||||
*/
|
||||
esp_err_t enc28j60_set_phy_duplex(esp_eth_phy_t *phy, eth_duplex_t duplex);
|
||||
|
||||
/**
|
||||
* @brief Get ENC28J60 silicon revision ID
|
||||
*
|
||||
* @param mac ENC28J60 MAC Handle
|
||||
* @return eth_enc28j60_rev_t
|
||||
* - returns silicon revision ID read during initialization
|
||||
*/
|
||||
eth_enc28j60_rev_t emac_enc28j60_get_chip_info(esp_eth_mac_t *mac);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "hal/cpu_hal.h"
|
||||
#include "esp_eth_enc28j60.h"
|
||||
#include "enc28j60.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
@@ -41,9 +42,21 @@ static const char *TAG = "enc28j60";
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define MAC_CHECK_NO_RET(a, str, goto_tag, ...) \
|
||||
do \
|
||||
{ \
|
||||
if (!(a)) \
|
||||
{ \
|
||||
ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
|
||||
goto goto_tag; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define ENC28J60_SPI_LOCK_TIMEOUT_MS (50)
|
||||
#define ENC28J60_PHY_OPERATION_TIMEOUT_US (1000)
|
||||
#define ENC28J60_REG_TRANS_LOCK_TIMEOUT_MS (150)
|
||||
#define ENC28J60_PHY_OPERATION_TIMEOUT_US (150)
|
||||
#define ENC28J60_SYSTEM_RESET_ADDITION_TIME_US (1000)
|
||||
#define ENC28J60_TX_READY_TIMEOUT_MS (2000)
|
||||
|
||||
#define ENC28J60_BUFFER_SIZE (0x2000) // 8KB built-in buffer
|
||||
/**
|
||||
@@ -60,6 +73,7 @@ static const char *TAG = "enc28j60";
|
||||
#define ENC28J60_BUF_TX_END (ENC28J60_BUFFER_SIZE - 1)
|
||||
|
||||
#define ENC28J60_RSV_SIZE (6) // Receive Status Vector Size
|
||||
#define ENC28J60_TSV_SIZE (6) // Transmit Status Vector Size
|
||||
|
||||
typedef struct {
|
||||
uint8_t next_packet_low;
|
||||
@@ -70,30 +84,70 @@ typedef struct {
|
||||
uint8_t status_high;
|
||||
} enc28j60_rx_header_t;
|
||||
|
||||
typedef struct {
|
||||
uint16_t byte_cnt;
|
||||
|
||||
uint8_t collision_cnt:4;
|
||||
uint8_t crc_err:1;
|
||||
uint8_t len_check_err:1;
|
||||
uint8_t len_out_range:1;
|
||||
uint8_t tx_done:1;
|
||||
|
||||
uint8_t multicast:1;
|
||||
uint8_t broadcast:1;
|
||||
uint8_t pkt_defer:1;
|
||||
uint8_t excessive_defer:1;
|
||||
uint8_t excessive_collision:1;
|
||||
uint8_t late_collision:1;
|
||||
uint8_t giant:1;
|
||||
uint8_t underrun:1;
|
||||
|
||||
uint16_t bytes_on_wire;
|
||||
|
||||
uint8_t ctrl_frame:1;
|
||||
uint8_t pause_ctrl_frame:1;
|
||||
uint8_t backpressure_app:1;
|
||||
uint8_t vlan_frame:1;
|
||||
} enc28j60_tsv_t;
|
||||
|
||||
typedef struct {
|
||||
esp_eth_mac_t parent;
|
||||
esp_eth_mediator_t *eth;
|
||||
spi_device_handle_t spi_hdl;
|
||||
SemaphoreHandle_t spi_lock;
|
||||
SemaphoreHandle_t reg_trans_lock;
|
||||
SemaphoreHandle_t tx_ready_sem;
|
||||
TaskHandle_t rx_task_hdl;
|
||||
uint32_t sw_reset_timeout_ms;
|
||||
uint32_t next_packet_ptr;
|
||||
uint32_t last_tsv_addr;
|
||||
int int_gpio_num;
|
||||
uint8_t addr[6];
|
||||
uint8_t last_bank;
|
||||
bool packets_remain;
|
||||
eth_enc28j60_rev_t revision;
|
||||
} emac_enc28j60_t;
|
||||
|
||||
static inline bool enc28j60_lock(emac_enc28j60_t *emac)
|
||||
static inline bool enc28j60_spi_lock(emac_enc28j60_t *emac)
|
||||
{
|
||||
return xSemaphoreTake(emac->spi_lock, pdMS_TO_TICKS(ENC28J60_SPI_LOCK_TIMEOUT_MS)) == pdTRUE;
|
||||
}
|
||||
|
||||
static inline bool enc28j60_unlock(emac_enc28j60_t *emac)
|
||||
static inline bool enc28j60_spi_unlock(emac_enc28j60_t *emac)
|
||||
{
|
||||
return xSemaphoreGive(emac->spi_lock) == pdTRUE;
|
||||
}
|
||||
|
||||
static inline bool enc28j60_reg_trans_lock(emac_enc28j60_t *emac)
|
||||
{
|
||||
return xSemaphoreTake(emac->reg_trans_lock, pdMS_TO_TICKS(ENC28J60_REG_TRANS_LOCK_TIMEOUT_MS)) == pdTRUE;
|
||||
}
|
||||
|
||||
static inline bool enc28j60_reg_trans_unlock(emac_enc28j60_t *emac)
|
||||
{
|
||||
return xSemaphoreGive(emac->reg_trans_lock) == pdTRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ERXRDPT need to be set always at odd addresses
|
||||
*/
|
||||
@@ -137,12 +191,12 @@ static esp_err_t enc28j60_do_register_write(emac_enc28j60_t *emac, uint8_t reg_a
|
||||
[0] = value
|
||||
}
|
||||
};
|
||||
if (enc28j60_lock(emac)) {
|
||||
if (enc28j60_spi_lock(emac)) {
|
||||
if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
|
||||
ret = ESP_FAIL;
|
||||
}
|
||||
enc28j60_unlock(emac);
|
||||
enc28j60_spi_unlock(emac);
|
||||
} else {
|
||||
ret = ESP_ERR_TIMEOUT;
|
||||
}
|
||||
@@ -161,14 +215,14 @@ static esp_err_t enc28j60_do_register_read(emac_enc28j60_t *emac, bool is_eth_re
|
||||
.length = is_eth_reg ? 8 : 16, // read operation is different for ETH register and non-ETH register
|
||||
.flags = SPI_TRANS_USE_RXDATA
|
||||
};
|
||||
if (enc28j60_lock(emac)) {
|
||||
if (enc28j60_spi_lock(emac)) {
|
||||
if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
|
||||
ret = ESP_FAIL;
|
||||
} else {
|
||||
*value = is_eth_reg ? trans.rx_data[0] : trans.rx_data[1];
|
||||
}
|
||||
enc28j60_unlock(emac);
|
||||
enc28j60_spi_unlock(emac);
|
||||
} else {
|
||||
ret = ESP_ERR_TIMEOUT;
|
||||
}
|
||||
@@ -191,12 +245,12 @@ static esp_err_t enc28j60_do_bitwise_set(emac_enc28j60_t *emac, uint8_t reg_addr
|
||||
[0] = mask
|
||||
}
|
||||
};
|
||||
if (enc28j60_lock(emac)) {
|
||||
if (enc28j60_spi_lock(emac)) {
|
||||
if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
|
||||
ret = ESP_FAIL;
|
||||
}
|
||||
enc28j60_unlock(emac);
|
||||
enc28j60_spi_unlock(emac);
|
||||
} else {
|
||||
ret = ESP_ERR_TIMEOUT;
|
||||
}
|
||||
@@ -219,12 +273,12 @@ static esp_err_t enc28j60_do_bitwise_clr(emac_enc28j60_t *emac, uint8_t reg_addr
|
||||
[0] = mask
|
||||
}
|
||||
};
|
||||
if (enc28j60_lock(emac)) {
|
||||
if (enc28j60_spi_lock(emac)) {
|
||||
if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
|
||||
ret = ESP_FAIL;
|
||||
}
|
||||
enc28j60_unlock(emac);
|
||||
enc28j60_spi_unlock(emac);
|
||||
} else {
|
||||
ret = ESP_ERR_TIMEOUT;
|
||||
}
|
||||
@@ -243,12 +297,12 @@ static esp_err_t enc28j60_do_memory_write(emac_enc28j60_t *emac, uint8_t *buffer
|
||||
.length = len * 8,
|
||||
.tx_buffer = buffer
|
||||
};
|
||||
if (enc28j60_lock(emac)) {
|
||||
if (enc28j60_spi_lock(emac)) {
|
||||
if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
|
||||
ret = ESP_FAIL;
|
||||
}
|
||||
enc28j60_unlock(emac);
|
||||
enc28j60_spi_unlock(emac);
|
||||
} else {
|
||||
ret = ESP_ERR_TIMEOUT;
|
||||
}
|
||||
@@ -268,12 +322,12 @@ static esp_err_t enc28j60_do_memory_read(emac_enc28j60_t *emac, uint8_t *buffer,
|
||||
.rx_buffer = buffer
|
||||
};
|
||||
|
||||
if (enc28j60_lock(emac)) {
|
||||
if (enc28j60_spi_lock(emac)) {
|
||||
if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
|
||||
ret = ESP_FAIL;
|
||||
}
|
||||
enc28j60_unlock(emac);
|
||||
enc28j60_spi_unlock(emac);
|
||||
} else {
|
||||
ret = ESP_ERR_TIMEOUT;
|
||||
}
|
||||
@@ -290,12 +344,12 @@ static esp_err_t enc28j60_do_reset(emac_enc28j60_t *emac)
|
||||
.cmd = ENC28J60_SPI_CMD_SRC, // Soft reset
|
||||
.addr = 0x1F,
|
||||
};
|
||||
if (enc28j60_lock(emac)) {
|
||||
if (enc28j60_spi_lock(emac)) {
|
||||
if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
|
||||
ret = ESP_FAIL;
|
||||
}
|
||||
enc28j60_unlock(emac);
|
||||
enc28j60_spi_unlock(emac);
|
||||
} else {
|
||||
ret = ESP_ERR_TIMEOUT;
|
||||
}
|
||||
@@ -329,11 +383,18 @@ out:
|
||||
static esp_err_t enc28j60_register_write(emac_enc28j60_t *emac, uint16_t reg_addr, uint8_t value)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
MAC_CHECK(enc28j60_switch_register_bank(emac, (reg_addr & 0xF00) >> 8) == ESP_OK,
|
||||
"switch bank failed", out, ESP_FAIL);
|
||||
MAC_CHECK(enc28j60_do_register_write(emac, reg_addr & 0xFF, value) == ESP_OK,
|
||||
"write register failed", out, ESP_FAIL);
|
||||
if (enc28j60_reg_trans_lock(emac)) {
|
||||
MAC_CHECK(enc28j60_switch_register_bank(emac, (reg_addr & 0xF00) >> 8) == ESP_OK,
|
||||
"switch bank failed", out, ESP_FAIL);
|
||||
MAC_CHECK(enc28j60_do_register_write(emac, reg_addr & 0xFF, value) == ESP_OK,
|
||||
"write register failed", out, ESP_FAIL);
|
||||
enc28j60_reg_trans_unlock(emac);
|
||||
} else {
|
||||
ret = ESP_ERR_TIMEOUT;
|
||||
}
|
||||
return ret;
|
||||
out:
|
||||
enc28j60_reg_trans_unlock(emac);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -343,11 +404,18 @@ out:
|
||||
static esp_err_t enc28j60_register_read(emac_enc28j60_t *emac, uint16_t reg_addr, uint8_t *value)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
MAC_CHECK(enc28j60_switch_register_bank(emac, (reg_addr & 0xF00) >> 8) == ESP_OK,
|
||||
"switch bank failed", out, ESP_FAIL);
|
||||
MAC_CHECK(enc28j60_do_register_read(emac, !(reg_addr & 0xF000), reg_addr & 0xFF, value) == ESP_OK,
|
||||
"read register failed", out, ESP_FAIL);
|
||||
if (enc28j60_reg_trans_lock(emac)) {
|
||||
MAC_CHECK(enc28j60_switch_register_bank(emac, (reg_addr & 0xF00) >> 8) == ESP_OK,
|
||||
"switch bank failed", out, ESP_FAIL);
|
||||
MAC_CHECK(enc28j60_do_register_read(emac, !(reg_addr & 0xF000), reg_addr & 0xFF, value) == ESP_OK,
|
||||
"read register failed", out, ESP_FAIL);
|
||||
enc28j60_reg_trans_unlock(emac);
|
||||
} else {
|
||||
ret = ESP_ERR_TIMEOUT;
|
||||
}
|
||||
return ret;
|
||||
out:
|
||||
enc28j60_reg_trans_unlock(emac);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -393,10 +461,10 @@ static esp_err_t emac_enc28j60_write_phy_reg(esp_eth_mac_t *mac, uint32_t phy_ad
|
||||
/* polling the busy flag */
|
||||
uint32_t to = 0;
|
||||
do {
|
||||
esp_rom_delay_us(100);
|
||||
esp_rom_delay_us(15);
|
||||
MAC_CHECK(enc28j60_register_read(emac, ENC28J60_MISTAT, &mii_status) == ESP_OK,
|
||||
"read MISTAT failed", out, ESP_FAIL);
|
||||
to += 100;
|
||||
to += 15;
|
||||
} while ((mii_status & MISTAT_BUSY) && to < ENC28J60_PHY_OPERATION_TIMEOUT_US);
|
||||
MAC_CHECK(!(mii_status & MISTAT_BUSY), "phy is busy", out, ESP_ERR_TIMEOUT);
|
||||
out:
|
||||
@@ -423,19 +491,17 @@ static esp_err_t emac_enc28j60_read_phy_reg(esp_eth_mac_t *mac, uint32_t phy_add
|
||||
/* tell the PHY address to read */
|
||||
MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MIREGADR, phy_reg & 0xFF) == ESP_OK,
|
||||
"write MIREGADR failed", out, ESP_FAIL);
|
||||
MAC_CHECK(enc28j60_register_read(emac, ENC28J60_MICMD, &mii_cmd) == ESP_OK,
|
||||
"read MICMD failed", out, ESP_FAIL);
|
||||
mii_cmd |= MICMD_MIIRD;
|
||||
mii_cmd = MICMD_MIIRD;
|
||||
MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MICMD, mii_cmd) == ESP_OK,
|
||||
"write MICMD failed", out, ESP_FAIL);
|
||||
|
||||
/* polling the busy flag */
|
||||
uint32_t to = 0;
|
||||
do {
|
||||
esp_rom_delay_us(100);
|
||||
esp_rom_delay_us(15);
|
||||
MAC_CHECK(enc28j60_register_read(emac, ENC28J60_MISTAT, &mii_status) == ESP_OK,
|
||||
"read MISTAT failed", out, ESP_FAIL);
|
||||
to += 100;
|
||||
to += 15;
|
||||
} while ((mii_status & MISTAT_BUSY) && to < ENC28J60_PHY_OPERATION_TIMEOUT_US);
|
||||
MAC_CHECK(!(mii_status & MISTAT_BUSY), "phy is busy", out, ESP_ERR_TIMEOUT);
|
||||
|
||||
@@ -468,16 +534,15 @@ out:
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Verify chip ID
|
||||
* @brief Verify chip revision ID
|
||||
*/
|
||||
static esp_err_t enc28j60_verify_id(emac_enc28j60_t *emac)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
uint8_t id;
|
||||
MAC_CHECK(enc28j60_register_read(emac, ENC28J60_EREVID, &id) == ESP_OK,
|
||||
MAC_CHECK(enc28j60_register_read(emac, ENC28J60_EREVID, (uint8_t *)&emac->revision) == ESP_OK,
|
||||
"read EREVID failed", out, ESP_FAIL);
|
||||
ESP_LOGI(TAG, "revision: %d", id);
|
||||
MAC_CHECK(id > 0 && id < 7, "wrong chip ID", out, ESP_ERR_INVALID_VERSION);
|
||||
ESP_LOGI(TAG, "revision: %d", emac->revision);
|
||||
MAC_CHECK(emac->revision >= ENC28J60_REV_B1 && emac->revision <= ENC28J60_REV_B7, "wrong chip ID", out, ESP_ERR_INVALID_VERSION);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
@@ -583,7 +648,7 @@ static esp_err_t emac_enc28j60_start(esp_eth_mac_t *mac)
|
||||
/* enable interrupt */
|
||||
MAC_CHECK(enc28j60_do_bitwise_clr(emac, ENC28J60_EIR, 0xFF) == ESP_OK,
|
||||
"clear EIR failed", out, ESP_FAIL);
|
||||
MAC_CHECK(enc28j60_do_bitwise_set(emac, ENC28J60_EIE, EIE_PKTIE | EIE_INTIE) == ESP_OK,
|
||||
MAC_CHECK(enc28j60_do_bitwise_set(emac, ENC28J60_EIE, EIE_PKTIE | EIE_INTIE | EIE_TXERIE) == ESP_OK,
|
||||
"set EIE.[PKTIE|INTIE] failed", out, ESP_FAIL);
|
||||
/* enable rx logic */
|
||||
MAC_CHECK(enc28j60_do_bitwise_set(emac, ENC28J60_ECON1, ECON1_RXEN) == ESP_OK,
|
||||
@@ -635,6 +700,11 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline esp_err_t emac_enc28j60_get_tsv(emac_enc28j60_t *emac, enc28j60_tsv_t *tsv)
|
||||
{
|
||||
return enc28j60_read_packet(emac, emac->last_tsv_addr, (uint8_t *)tsv, ENC28J60_TSV_SIZE);
|
||||
}
|
||||
|
||||
static void enc28j60_isr_handler(void *arg)
|
||||
{
|
||||
emac_enc28j60_t *emac = (emac_enc28j60_t *)arg;
|
||||
@@ -646,19 +716,48 @@ static void enc28j60_isr_handler(void *arg)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Main ENC28J60 Task. Mainly used for Rx processing. However, it also handles other interrupts.
|
||||
*
|
||||
*/
|
||||
static void emac_enc28j60_task(void *arg)
|
||||
{
|
||||
emac_enc28j60_t *emac = (emac_enc28j60_t *)arg;
|
||||
uint8_t status = 0;
|
||||
uint8_t mask = 0;
|
||||
uint8_t *buffer = NULL;
|
||||
uint32_t length = 0;
|
||||
|
||||
while (1) {
|
||||
// block indefinitely until some task notifies me
|
||||
ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
|
||||
/* clear interrupt status */
|
||||
enc28j60_do_register_read(emac, true, ENC28J60_EIR, &status);
|
||||
/* packet received */
|
||||
loop_start:
|
||||
// block until some task notifies me or check the gpio by myself
|
||||
if (ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(1000)) == 0 && // if no notification ...
|
||||
gpio_get_level(emac->int_gpio_num) != 0) { // ...and no interrupt asserted
|
||||
continue; // -> just continue to check again
|
||||
}
|
||||
// the host controller should clear the global enable bit for the interrupt pin before servicing the interrupt
|
||||
MAC_CHECK_NO_RET(enc28j60_do_bitwise_clr(emac, ENC28J60_EIR, EIE_INTIE) == ESP_OK,
|
||||
"clear EIE_INTIE failed", loop_start);
|
||||
// read interrupt status
|
||||
MAC_CHECK_NO_RET(enc28j60_do_register_read(emac, true, ENC28J60_EIR, &status) == ESP_OK,
|
||||
"read EIR failed", loop_end);
|
||||
MAC_CHECK_NO_RET(enc28j60_do_register_read(emac, true, ENC28J60_EIE, &mask) == ESP_OK,
|
||||
"read EIE failed", loop_end);
|
||||
status &= mask;
|
||||
|
||||
// When source of interrupt is unknown, try to check if there is packet waiting (Errata #6 workaround)
|
||||
if (status == 0) {
|
||||
uint8_t pk_counter;
|
||||
MAC_CHECK_NO_RET(enc28j60_register_read(emac, ENC28J60_EPKTCNT, &pk_counter) == ESP_OK,
|
||||
"read EPKTCNT failed", loop_end);
|
||||
if (pk_counter > 0) {
|
||||
status = EIR_PKTIF;
|
||||
} else {
|
||||
goto loop_end;
|
||||
}
|
||||
}
|
||||
|
||||
// packet received
|
||||
if (status & EIR_PKTIF) {
|
||||
do {
|
||||
length = ETH_MAX_PACKET_SIZE;
|
||||
@@ -677,6 +776,53 @@ static void emac_enc28j60_task(void *arg)
|
||||
}
|
||||
} while (emac->packets_remain);
|
||||
}
|
||||
|
||||
// transmit error
|
||||
if (status & EIR_TXERIF) {
|
||||
// Errata #12/#13 workaround - reset Tx state machine
|
||||
MAC_CHECK_NO_RET(enc28j60_do_bitwise_set(emac, ENC28J60_ECON1, ECON1_TXRST) == ESP_OK,
|
||||
"set TXRST failed", loop_end);
|
||||
MAC_CHECK_NO_RET(enc28j60_do_bitwise_clr(emac, ENC28J60_ECON1, ECON1_TXRST) == ESP_OK,
|
||||
"clear TXRST failed", loop_end);
|
||||
|
||||
// Clear Tx Error Interrupt Flag
|
||||
MAC_CHECK_NO_RET(enc28j60_do_bitwise_clr(emac, ENC28J60_EIR, EIR_TXERIF) == ESP_OK,
|
||||
"clear TXERIF failed", loop_end);
|
||||
|
||||
// Errata #13 workaround (applicable only to B5 and B7 revisions)
|
||||
if (emac->revision == ENC28J60_REV_B5 || emac->revision == ENC28J60_REV_B7) {
|
||||
__attribute__((aligned(4))) enc28j60_tsv_t tx_status; // SPI driver needs the rx buffer 4 byte align
|
||||
MAC_CHECK_NO_RET(emac_enc28j60_get_tsv(emac, &tx_status) == ESP_OK,
|
||||
"get Tx Status Vector failed", loop_end);
|
||||
// Try to retransmit when late collision is indicated
|
||||
if (tx_status.late_collision) {
|
||||
// Clear Tx Interrupt status Flag (it was set along with the error)
|
||||
MAC_CHECK_NO_RET(enc28j60_do_bitwise_clr(emac, ENC28J60_EIR, EIR_TXIF) == ESP_OK,
|
||||
"clear TXIF failed", loop_end);
|
||||
// Enable global interrupt flag and try to retransmit
|
||||
MAC_CHECK_NO_RET(enc28j60_do_bitwise_set(emac, ENC28J60_EIE, EIE_INTIE) == ESP_OK,
|
||||
"set INTIE failed", loop_end);
|
||||
MAC_CHECK_NO_RET(enc28j60_do_bitwise_set(emac, ENC28J60_ECON1, ECON1_TXRTS) == ESP_OK,
|
||||
"set TXRTS failed", loop_end);
|
||||
continue; // no need to handle Tx ready interrupt nor to enable global interrupt at this point
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// transmit ready
|
||||
if (status & EIR_TXIF) {
|
||||
MAC_CHECK_NO_RET(enc28j60_do_bitwise_clr(emac, ENC28J60_EIR, EIR_TXIF) == ESP_OK,
|
||||
"clear TXIF failed", loop_end);
|
||||
MAC_CHECK_NO_RET(enc28j60_do_bitwise_clr(emac, ENC28J60_EIE, EIE_TXIE) == ESP_OK,
|
||||
"clear TXIE failed", loop_end);
|
||||
|
||||
xSemaphoreGive(emac->tx_ready_sem);
|
||||
}
|
||||
loop_end:
|
||||
// restore global enable interrupt bit
|
||||
MAC_CHECK_NO_RET(enc28j60_do_bitwise_set(emac, ENC28J60_EIE, EIE_INTIE) == ESP_OK,
|
||||
"clear INTIE failed", loop_start);
|
||||
// Note: Interrupt flag PKTIF is cleared when PKTDEC is set (in receive function)
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
@@ -762,9 +908,12 @@ static esp_err_t emac_enc28j60_transmit(esp_eth_mac_t *mac, uint8_t *buf, uint32
|
||||
emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent);
|
||||
uint8_t econ1 = 0;
|
||||
|
||||
/* Check if last transmit complete */
|
||||
/* ENC28J60 may be a bottle neck in Eth communication. Hence we need to check if it is ready. */
|
||||
if (xSemaphoreTake(emac->tx_ready_sem, pdMS_TO_TICKS(ENC28J60_TX_READY_TIMEOUT_MS)) == pdFALSE) {
|
||||
ESP_LOGW(TAG, "tx_ready_sem expired");
|
||||
}
|
||||
MAC_CHECK(enc28j60_do_register_read(emac, true, ENC28J60_ECON1, &econ1) == ESP_OK,
|
||||
"read ECON1 failed", out, ESP_FAIL);
|
||||
"read ECON1 failed", out, ESP_FAIL);
|
||||
MAC_CHECK(!(econ1 & ECON1_TXRTS), "last transmit still in progress", out, ESP_ERR_INVALID_STATE);
|
||||
|
||||
/* Set the write pointer to start of transmit buffer area */
|
||||
@@ -785,6 +934,14 @@ static esp_err_t emac_enc28j60_transmit(esp_eth_mac_t *mac, uint8_t *buf, uint32
|
||||
"write packet control byte failed", out, ESP_FAIL);
|
||||
MAC_CHECK(enc28j60_do_memory_write(emac, buf, length) == ESP_OK,
|
||||
"buffer memory write failed", out, ESP_FAIL);
|
||||
emac->last_tsv_addr = ENC28J60_BUF_TX_START + length + 1;
|
||||
|
||||
/* enable Tx Interrupt to indicate next Tx ready state */
|
||||
MAC_CHECK(enc28j60_do_bitwise_clr(emac, ENC28J60_EIR, EIR_TXIF) == ESP_OK,
|
||||
"set EIR_TXIF failed", out, ESP_FAIL);
|
||||
MAC_CHECK(enc28j60_do_bitwise_set(emac, ENC28J60_EIE, EIE_TXIE) == ESP_OK,
|
||||
"set EIE_TXIE failed", out, ESP_FAIL);
|
||||
|
||||
/* issue tx polling command */
|
||||
MAC_CHECK(enc28j60_do_bitwise_set(emac, ENC28J60_ECON1, ECON1_TXRTS) == ESP_OK,
|
||||
"set ECON1.TXRTS failed", out, ESP_FAIL);
|
||||
@@ -832,6 +989,15 @@ out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get chip info
|
||||
*/
|
||||
eth_enc28j60_rev_t emac_enc28j60_get_chip_info(esp_eth_mac_t *mac)
|
||||
{
|
||||
emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent);
|
||||
return emac->revision;
|
||||
}
|
||||
|
||||
static esp_err_t emac_enc28j60_init(esp_eth_mac_t *mac)
|
||||
{
|
||||
esp_err_t ret = ESP_OK;
|
||||
@@ -881,6 +1047,8 @@ static esp_err_t emac_enc28j60_del(esp_eth_mac_t *mac)
|
||||
emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent);
|
||||
vTaskDelete(emac->rx_task_hdl);
|
||||
vSemaphoreDelete(emac->spi_lock);
|
||||
vSemaphoreDelete(emac->reg_trans_lock);
|
||||
vSemaphoreDelete(emac->tx_ready_sem);
|
||||
free(emac);
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -919,7 +1087,12 @@ esp_eth_mac_t *esp_eth_mac_new_enc28j60(const eth_enc28j60_config_t *enc28j60_co
|
||||
emac->parent.receive = emac_enc28j60_receive;
|
||||
/* create mutex */
|
||||
emac->spi_lock = xSemaphoreCreateMutex();
|
||||
MAC_CHECK(emac->spi_lock, "create lock failed", err, NULL);
|
||||
MAC_CHECK(emac->spi_lock, "create spi lock failed", err, NULL);
|
||||
emac->reg_trans_lock = xSemaphoreCreateMutex();
|
||||
MAC_CHECK(emac->reg_trans_lock, "create register transaction lock failed", err, NULL);
|
||||
emac->tx_ready_sem = xSemaphoreCreateBinary();
|
||||
MAC_CHECK(emac->tx_ready_sem, "create pkt transmit ready semaphore failed", err, NULL);
|
||||
xSemaphoreGive(emac->tx_ready_sem); // ensures the first transmit is performed without waiting
|
||||
/* create enc28j60 task */
|
||||
BaseType_t core_num = tskNO_AFFINITY;
|
||||
if (mac_config->flags & ETH_MAC_FLAG_PIN_TO_CORE) {
|
||||
@@ -938,6 +1111,12 @@ err:
|
||||
if (emac->spi_lock) {
|
||||
vSemaphoreDelete(emac->spi_lock);
|
||||
}
|
||||
if (emac->reg_trans_lock) {
|
||||
vSemaphoreDelete(emac->reg_trans_lock);
|
||||
}
|
||||
if (emac->tx_ready_sem) {
|
||||
vSemaphoreDelete(emac->tx_ready_sem);
|
||||
}
|
||||
free(emac);
|
||||
}
|
||||
return ret;
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "esp_log.h"
|
||||
#include "esp_eth.h"
|
||||
#include "eth_phy_regs_struct.h"
|
||||
#include "esp_eth_enc28j60.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/gpio.h"
|
||||
@@ -34,6 +35,24 @@ static const char *TAG = "enc28j60";
|
||||
|
||||
/***************Vendor Specific Register***************/
|
||||
|
||||
/**
|
||||
* @brief PHCON2(PHY Control Register 2)
|
||||
*
|
||||
*/
|
||||
typedef union {
|
||||
struct {
|
||||
uint32_t reserved_7_0 : 8; // Reserved
|
||||
uint32_t pdpxmd : 1; // PHY Duplex Mode bit
|
||||
uint32_t reserved_10_9: 2; // Reserved
|
||||
uint32_t ppwrsv: 1; // PHY Power-Down bit
|
||||
uint32_t reserved_13_12: 2; // Reserved
|
||||
uint32_t ploopbk: 1; // PHY Loopback bit
|
||||
uint32_t prst: 1; // PHY Software Reset bit
|
||||
};
|
||||
uint32_t val;
|
||||
} phcon1_reg_t;
|
||||
#define ETH_PHY_PHCON1_REG_ADDR (0x00)
|
||||
|
||||
/**
|
||||
* @brief PHCON2(PHY Control Register 2)
|
||||
*
|
||||
@@ -188,6 +207,35 @@ err:
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
esp_err_t enc28j60_set_phy_duplex(esp_eth_phy_t *phy, eth_duplex_t duplex)
|
||||
{
|
||||
phy_enc28j60_t *enc28j60 = __containerof(phy, phy_enc28j60_t, parent);
|
||||
esp_eth_mediator_t *eth = enc28j60->eth;
|
||||
phcon1_reg_t phcon1;
|
||||
|
||||
PHY_CHECK(eth->phy_reg_read(eth, enc28j60->addr, 0, &phcon1.val) == ESP_OK,
|
||||
"read PHCON1 failed", err);
|
||||
switch (duplex) {
|
||||
case ETH_DUPLEX_HALF:
|
||||
phcon1.pdpxmd = 0;
|
||||
break;
|
||||
case ETH_DUPLEX_FULL:
|
||||
phcon1.pdpxmd = 1;
|
||||
break;
|
||||
default:
|
||||
PHY_CHECK(false, "unknown duplex", err);
|
||||
break;
|
||||
}
|
||||
|
||||
PHY_CHECK(eth->phy_reg_write(eth, enc28j60->addr, 0, phcon1.val) == ESP_OK,
|
||||
"write PHCON1 failed", err);
|
||||
|
||||
PHY_CHECK(enc28j60_update_link_duplex_speed(enc28j60) == ESP_OK, "update link duplex speed failed", err);
|
||||
return ESP_OK;
|
||||
err:
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
static esp_err_t enc28j60_pwrctl(esp_eth_phy_t *phy, bool enable)
|
||||
{
|
||||
phy_enc28j60_t *enc28j60 = __containerof(phy, phy_enc28j60_t, parent);
|
||||
@@ -1,6 +1,4 @@
|
||||
set(srcs "enc28j60_example_main.c"
|
||||
"esp_eth_mac_enc28j60.c"
|
||||
"esp_eth_phy_enc28j60.c")
|
||||
set(srcs "enc28j60_example_main.c")
|
||||
|
||||
idf_component_register(SRCS "${srcs}"
|
||||
INCLUDE_DIRS ".")
|
||||
|
||||
@@ -38,7 +38,7 @@ menu "Example Configuration"
|
||||
config EXAMPLE_ENC28J60_SPI_CLOCK_MHZ
|
||||
int "SPI clock speed (MHz)"
|
||||
range 5 20
|
||||
default 6
|
||||
default 8
|
||||
help
|
||||
Set the clock speed (MHz) of SPI interface.
|
||||
|
||||
@@ -47,4 +47,22 @@ menu "Example Configuration"
|
||||
default 4
|
||||
help
|
||||
Set the GPIO number used by ENC28J60 interrupt.
|
||||
|
||||
choice EXAMPLE_ENC28J60_DUPLEX_MODE
|
||||
prompt "Duplex Mode"
|
||||
default EXAMPLE_ENC28J60_DUPLEX_HALF
|
||||
help
|
||||
Select ENC28J60 Duplex operation mode.
|
||||
|
||||
config EXAMPLE_ENC28J60_DUPLEX_FULL
|
||||
bool "Full Duplex"
|
||||
help
|
||||
Set ENC28J60 to Full Duplex mode. Do not forget to manually set the remote node (switch, router
|
||||
or Ethernet controller) to full-duplex operation mode too.
|
||||
|
||||
config EXAMPLE_ENC28J60_DUPLEX_HALF
|
||||
bool "Half Duplex"
|
||||
help
|
||||
Set ENC28J60 to Half Duplex mode.
|
||||
endchoice # EXAMPLE_ENC28J60_DUPLEX_MODE
|
||||
endmenu
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "enc28j60.h"
|
||||
#include "esp_eth_enc28j60.h"
|
||||
#include "driver/spi_master.h"
|
||||
|
||||
static const char *TAG = "eth_example";
|
||||
@@ -87,7 +87,7 @@ void app_main(void)
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
};
|
||||
ESP_ERROR_CHECK(spi_bus_initialize(CONFIG_EXAMPLE_ENC28J60_SPI_HOST, &buscfg, 1));
|
||||
ESP_ERROR_CHECK(spi_bus_initialize(CONFIG_EXAMPLE_ENC28J60_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
||||
/* ENC28J60 ethernet driver is based on spi driver */
|
||||
spi_device_interface_config_t devcfg = {
|
||||
.command_bits = 3,
|
||||
@@ -95,8 +95,10 @@ void app_main(void)
|
||||
.mode = 0,
|
||||
.clock_speed_hz = CONFIG_EXAMPLE_ENC28J60_SPI_CLOCK_MHZ * 1000 * 1000,
|
||||
.spics_io_num = CONFIG_EXAMPLE_ENC28J60_CS_GPIO,
|
||||
.queue_size = 20
|
||||
.queue_size = 20,
|
||||
.cs_ena_posttrans = enc28j60_cal_spi_cs_hold_time(CONFIG_EXAMPLE_ENC28J60_SPI_CLOCK_MHZ),
|
||||
};
|
||||
|
||||
spi_device_handle_t spi_handle = NULL;
|
||||
ESP_ERROR_CHECK(spi_bus_add_device(CONFIG_EXAMPLE_ENC28J60_SPI_HOST, &devcfg, &spi_handle));
|
||||
|
||||
@@ -124,8 +126,20 @@ void app_main(void)
|
||||
0x02, 0x00, 0x00, 0x12, 0x34, 0x56
|
||||
});
|
||||
|
||||
// ENC28J60 Errata #1 check
|
||||
if (emac_enc28j60_get_chip_info(mac) < ENC28J60_REV_B5 && CONFIG_EXAMPLE_ENC28J60_SPI_CLOCK_MHZ < 8) {
|
||||
ESP_LOGE(TAG, "SPI frequency must be at least 8 MHz for chip revision less than 5");
|
||||
ESP_ERROR_CHECK(ESP_FAIL);
|
||||
}
|
||||
|
||||
/* attach Ethernet driver to TCP/IP stack */
|
||||
ESP_ERROR_CHECK(esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handle)));
|
||||
/* start Ethernet driver state machine */
|
||||
ESP_ERROR_CHECK(esp_eth_start(eth_handle));
|
||||
|
||||
/* It is recommended to use ENC28J60 in Full Duplex mode since multiple errata exist to the Half Duplex mode */
|
||||
#if CONFIG_EXAMPLE_ENC28J60_DUPLEX_FULL
|
||||
/* Set duplex needs to be called after esp_eth_start since the driver is started with auto-negotiation by default */
|
||||
enc28j60_set_phy_duplex(phy, ETH_DUPLEX_FULL);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -32,6 +32,14 @@ menu "Example Configuration"
|
||||
select ETH_SPI_ETHERNET_W5500
|
||||
help
|
||||
Select external SPI-Ethernet module (W5500).
|
||||
|
||||
config EXAMPLE_USE_KSZ8851SNL
|
||||
bool "KSZ8851SNL Module"
|
||||
select EXAMPLE_USE_SPI_ETHERNET
|
||||
select ETH_USE_SPI_ETHERNET
|
||||
select ETH_SPI_ETHERNET_KSZ8851SNL
|
||||
help
|
||||
Select external SPI-Ethernet module (KSZ8851SNL).
|
||||
endchoice # EXAMPLE_ETHERNET_TYPE
|
||||
|
||||
if EXAMPLE_USE_INTERNAL_ETHERNET
|
||||
@@ -53,11 +61,18 @@ menu "Example Configuration"
|
||||
RTL8201F/SR8201F is a single port 10/100Mb Ethernet Transceiver with auto MDIX.
|
||||
Goto http://www.corechip-sz.com/productsview.asp?id=22 for more information about it.
|
||||
|
||||
config EXAMPLE_ETH_PHY_LAN8720
|
||||
bool "LAN8720"
|
||||
config EXAMPLE_ETH_PHY_LAN87XX
|
||||
bool "LAN87xx"
|
||||
help
|
||||
Below chips are supported:
|
||||
LAN8710A is a small footprint MII/RMII 10/100 Ethernet Transceiver with HP Auto-MDIX and
|
||||
flexPWR® Technology.
|
||||
LAN8720A is a small footprint RMII 10/100 Ethernet Transceiver with HP Auto-MDIX Support.
|
||||
Goto https://www.microchip.com/LAN8720A for more information about it.
|
||||
LAN8740A/LAN8741A is a small footprint MII/RMII 10/100 Energy Efficient Ethernet Transceiver
|
||||
with HP Auto-MDIX and flexPWR® Technology.
|
||||
LAN8742A is a small footprint RMII 10/100 Ethernet Transceiver with HP Auto-MDIX and
|
||||
flexPWR® Technology.
|
||||
Goto https://www.microchip.com for more information about them.
|
||||
|
||||
config EXAMPLE_ETH_PHY_DP83848
|
||||
bool "DP83848"
|
||||
@@ -70,6 +85,12 @@ menu "Example Configuration"
|
||||
help
|
||||
The KSZ8041 is a single supply 10Base-T/100Base-TX Physical Layer Transceiver.
|
||||
Goto https://www.microchip.com/wwwproducts/en/KSZ8041 for more information about it.
|
||||
|
||||
config EXAMPLE_ETH_PHY_KSZ8081
|
||||
bool "KSZ8081"
|
||||
help
|
||||
The KSZ8081 is a single supply 10Base-T/100Base-TX Physical Layer Transceiver.
|
||||
Goto https://www.microchip.com/wwwproducts/en/KSZ8081 for more information about it.
|
||||
endchoice # EXAMPLE_ETH_PHY_MODEL
|
||||
|
||||
config EXAMPLE_ETH_MDC_GPIO
|
||||
@@ -95,42 +116,60 @@ menu "Example Configuration"
|
||||
|
||||
config EXAMPLE_ETH_SPI_SCLK_GPIO
|
||||
int "SPI SCLK GPIO number"
|
||||
range 0 33
|
||||
default 20
|
||||
range 0 34 if IDF_TARGET_ESP32
|
||||
range 0 46 if IDF_TARGET_ESP32S2
|
||||
range 0 19 if IDF_TARGET_ESP32C3
|
||||
default 18 if IDF_TARGET_ESP32
|
||||
default 20 if IDF_TARGET_ESP32S2
|
||||
default 6 if IDF_TARGET_ESP32C3
|
||||
help
|
||||
Set the GPIO number used by SPI SCLK.
|
||||
|
||||
config EXAMPLE_ETH_SPI_MOSI_GPIO
|
||||
int "SPI MOSI GPIO number"
|
||||
range 0 33
|
||||
default 19
|
||||
range 0 34 if IDF_TARGET_ESP32
|
||||
range 0 46 if IDF_TARGET_ESP32S2
|
||||
range 0 19 if IDF_TARGET_ESP32C3
|
||||
default 23 if IDF_TARGET_ESP32
|
||||
default 19 if IDF_TARGET_ESP32S2
|
||||
default 7 if IDF_TARGET_ESP32C3
|
||||
help
|
||||
Set the GPIO number used by SPI MOSI.
|
||||
|
||||
config EXAMPLE_ETH_SPI_MISO_GPIO
|
||||
int "SPI MISO GPIO number"
|
||||
range 0 33
|
||||
default 18
|
||||
range 0 34 if IDF_TARGET_ESP32
|
||||
range 0 46 if IDF_TARGET_ESP32S2
|
||||
range 0 19 if IDF_TARGET_ESP32C3
|
||||
default 19 if IDF_TARGET_ESP32
|
||||
default 18 if IDF_TARGET_ESP32S2
|
||||
default 2 if IDF_TARGET_ESP32C3
|
||||
help
|
||||
Set the GPIO number used by SPI MISO.
|
||||
|
||||
config EXAMPLE_ETH_SPI_CS_GPIO
|
||||
int "SPI CS GPIO number"
|
||||
range 0 33
|
||||
default 21
|
||||
range 0 34 if IDF_TARGET_ESP32
|
||||
range 0 46 if IDF_TARGET_ESP32S2
|
||||
range 0 19 if IDF_TARGET_ESP32C3
|
||||
default 16 if IDF_TARGET_ESP32
|
||||
default 21 if IDF_TARGET_ESP32S2
|
||||
default 10 if IDF_TARGET_ESP32C3
|
||||
help
|
||||
Set the GPIO number used by SPI CS.
|
||||
|
||||
config EXAMPLE_ETH_SPI_CLOCK_MHZ
|
||||
int "SPI clock speed (MHz)"
|
||||
range 5 80
|
||||
default 36
|
||||
default 12 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32C3
|
||||
default 36 if IDF_TARGET_ESP32S2
|
||||
help
|
||||
Set the clock speed (MHz) of SPI interface.
|
||||
|
||||
config EXAMPLE_ETH_SPI_INT_GPIO
|
||||
int "Interrupt GPIO number"
|
||||
default 4
|
||||
default 17 if IDF_TARGET_ESP32
|
||||
default 4 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32C3
|
||||
help
|
||||
Set the GPIO number used by the SPI Ethernet module interrupt line.
|
||||
endif # EXAMPLE_USE_SPI_ETHERNET
|
||||
|
||||
@@ -165,12 +165,14 @@ static void initialize_ethernet(void)
|
||||
esp_eth_phy_t *phy = esp_eth_phy_new_ip101(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_ETH_PHY_RTL8201
|
||||
esp_eth_phy_t *phy = esp_eth_phy_new_rtl8201(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_ETH_PHY_LAN8720
|
||||
esp_eth_phy_t *phy = esp_eth_phy_new_lan8720(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_ETH_PHY_LAN87XX
|
||||
esp_eth_phy_t *phy = esp_eth_phy_new_lan87xx(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_ETH_PHY_DP83848
|
||||
esp_eth_phy_t *phy = esp_eth_phy_new_dp83848(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_ETH_PHY_KSZ8041
|
||||
esp_eth_phy_t *phy = esp_eth_phy_new_ksz8041(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_ETH_PHY_KSZ8081
|
||||
esp_eth_phy_t *phy = esp_eth_phy_new_ksz8081(&phy_config);
|
||||
#endif
|
||||
#elif CONFIG_ETH_USE_SPI_ETHERNET
|
||||
gpio_install_isr_service(0);
|
||||
@@ -183,7 +185,21 @@ static void initialize_ethernet(void)
|
||||
.quadhd_io_num = -1,
|
||||
};
|
||||
ESP_ERROR_CHECK(spi_bus_initialize(CONFIG_EXAMPLE_ETH_SPI_HOST, &buscfg, 1));
|
||||
#if CONFIG_EXAMPLE_USE_DM9051
|
||||
|
||||
#if CONFIG_EXAMPLE_USE_KSZ8851SNL
|
||||
spi_device_interface_config_t devcfg = {
|
||||
.mode = 0,
|
||||
.clock_speed_hz = CONFIG_EXAMPLE_ETH_SPI_CLOCK_MHZ * 1000 * 1000,
|
||||
.spics_io_num = CONFIG_EXAMPLE_ETH_SPI_CS_GPIO,
|
||||
.queue_size = 20
|
||||
};
|
||||
ESP_ERROR_CHECK(spi_bus_add_device(CONFIG_EXAMPLE_ETH_SPI_HOST, &devcfg, &spi_handle));
|
||||
/* KSZ8851SNL ethernet driver is based on spi driver */
|
||||
eth_ksz8851snl_config_t ksz8851snl_config = ETH_KSZ8851SNL_DEFAULT_CONFIG(spi_handle);
|
||||
ksz8851snl_config.int_gpio_num = CONFIG_EXAMPLE_ETH_SPI_INT_GPIO;
|
||||
esp_eth_mac_t *mac = esp_eth_mac_new_ksz8851snl(&ksz8851snl_config, &mac_config);
|
||||
esp_eth_phy_t *phy = esp_eth_phy_new_ksz8851snl(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_USE_DM9051
|
||||
spi_device_interface_config_t devcfg = {
|
||||
.command_bits = 1,
|
||||
.address_bits = 7,
|
||||
@@ -278,7 +294,6 @@ void app_main(void)
|
||||
ESP_ERROR_CHECK(ret);
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
ESP_ERROR_CHECK(initialize_flow_control());
|
||||
|
||||
initialize_ethernet();
|
||||
initialize_wifi();
|
||||
initialize_ethernet();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/system/console/advanced/components
|
||||
$ENV{IDF_PATH}/examples/common_components/iperf)
|
||||
$ENV{IDF_PATH}/examples/common_components/iperf
|
||||
$ENV{IDF_PATH}/examples/ethernet/enc28j60/components/eth_enc28j60)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(ethernet_iperf)
|
||||
|
||||
@@ -8,6 +8,7 @@ PROJECT_NAME := ethernet_iperf
|
||||
EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/system/console/advanced/components
|
||||
EXTRA_COMPONENT_DIRS += $(IDF_PATH)/examples/wifi/iperf/components
|
||||
EXTRA_COMPONENT_DIRS += $(IDF_PATH)/examples/common_components/iperf
|
||||
EXTRA_COMPONENT_DIRS += $(IDF_PATH)/examples/ethernet/enc28j60/components/eth_enc28j60
|
||||
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
102
examples/ethernet/iperf/iperf_test.py
Normal file
102
examples/ethernet/iperf/iperf_test.py
Normal file
@@ -0,0 +1,102 @@
|
||||
"""
|
||||
Test case for iperf example.
|
||||
|
||||
This test case might have problem running on windows:
|
||||
|
||||
1. direct use of `make`
|
||||
2. use `sudo killall iperf` to force kill iperf, didn't implement windows version
|
||||
|
||||
"""
|
||||
from __future__ import division, unicode_literals
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
import ttfw_idf
|
||||
from idf_iperf_test_util import IperfUtility
|
||||
from tiny_test_fw import TinyFW
|
||||
|
||||
try:
|
||||
from typing import Any, Tuple
|
||||
except ImportError:
|
||||
# Only used for type annotations
|
||||
pass
|
||||
|
||||
|
||||
class IperfTestUtilityEth(IperfUtility.IperfTestUtility):
|
||||
""" iperf test implementation """
|
||||
def __init__(self, dut, config_name, pc_nic_ip, pc_iperf_log_file, test_result=None): # type: (str, str, str,str, Any) -> None
|
||||
IperfUtility.IperfTestUtility.__init__(self, dut, config_name, 'None', 'None', pc_nic_ip, pc_iperf_log_file, test_result)
|
||||
|
||||
def setup(self): # type: () -> Tuple[str,int]
|
||||
"""
|
||||
setup iperf test:
|
||||
|
||||
1. kill current iperf process
|
||||
2. reboot DUT (currently iperf is not very robust, need to reboot DUT)
|
||||
"""
|
||||
try:
|
||||
subprocess.check_output('sudo killall iperf 2>&1 > /dev/null', shell=True)
|
||||
except subprocess.CalledProcessError:
|
||||
pass
|
||||
self.dut.write('restart')
|
||||
self.dut.expect_any('iperf>', 'esp32>')
|
||||
self.dut.write('ethernet start')
|
||||
time.sleep(10)
|
||||
self.dut.write('ethernet info')
|
||||
dut_ip = self.dut.expect(re.compile(r'ETHIP: ([\d.]+)'))[0]
|
||||
rssi = 0
|
||||
return dut_ip, rssi
|
||||
|
||||
|
||||
@ttfw_idf.idf_example_test(env_tag='Example_Ethernet')
|
||||
def test_ethernet_throughput_basic(env, _): # type: (Any, Any) -> None
|
||||
"""
|
||||
steps: |
|
||||
1. test TCP tx rx and UDP tx rx throughput
|
||||
2. compare with the pre-defined pass standard
|
||||
"""
|
||||
pc_nic_ip = env.get_pc_nic_info('pc_nic', 'ipv4')['addr']
|
||||
pc_iperf_log_file = os.path.join(env.log_path, 'pc_iperf_log.md')
|
||||
|
||||
# 1. get DUT
|
||||
dut = env.get_dut('iperf', 'examples/ethernet/iperf', dut_class=ttfw_idf.ESP32DUT)
|
||||
dut.start_app()
|
||||
dut.expect_any('iperf>', 'esp32>')
|
||||
|
||||
# 2. preparing
|
||||
test_result = {
|
||||
'tcp_tx': IperfUtility.TestResult('tcp', 'tx', 'ethernet'),
|
||||
'tcp_rx': IperfUtility.TestResult('tcp', 'rx', 'ethernet'),
|
||||
'udp_tx': IperfUtility.TestResult('udp', 'tx', 'ethernet'),
|
||||
'udp_rx': IperfUtility.TestResult('udp', 'rx', 'ethernet'),
|
||||
}
|
||||
|
||||
test_utility = IperfTestUtilityEth(dut, 'ethernet', pc_nic_ip, pc_iperf_log_file, test_result)
|
||||
|
||||
# 3. run test for TCP Tx, Rx and UDP Tx, Rx
|
||||
|
||||
test_utility.run_all_cases(0)
|
||||
|
||||
# 4. log performance and compare with pass standard
|
||||
performance_items = []
|
||||
for throughput_type in test_result:
|
||||
ttfw_idf.log_performance('{}_throughput'.format(throughput_type),
|
||||
'{:.02f} Mbps'.format(test_result[throughput_type].get_best_throughput()))
|
||||
performance_items.append(['{}_throughput'.format(throughput_type),
|
||||
'{:.02f} Mbps'.format(test_result[throughput_type].get_best_throughput())])
|
||||
|
||||
# 5. save to report
|
||||
TinyFW.JunitReport.update_performance(performance_items)
|
||||
# do check after logging, otherwise test will exit immediately if check fail, some performance can't be logged.
|
||||
for throughput_type in test_result:
|
||||
ttfw_idf.check_performance('{}_throughput'.format(throughput_type + '_eth'),
|
||||
test_result[throughput_type].get_best_throughput(), dut.TARGET)
|
||||
|
||||
env.close_dut('iperf')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_ethernet_throughput_basic(env_config_file='EnvConfig.yml')
|
||||
@@ -39,6 +39,22 @@ menu "Example Configuration"
|
||||
select ETH_SPI_ETHERNET_W5500
|
||||
help
|
||||
Select external SPI-Ethernet module (W5500).
|
||||
|
||||
config EXAMPLE_USE_KSZ8851SNL
|
||||
bool "KSZ8851SNL Module"
|
||||
select EXAMPLE_USE_SPI_ETHERNET
|
||||
select ETH_USE_SPI_ETHERNET
|
||||
select ETH_SPI_ETHERNET_KSZ8851SNL
|
||||
help
|
||||
Select external SPI-Ethernet module (KSZ8851SNL).
|
||||
|
||||
config EXAMPLE_USE_ENC28J60
|
||||
bool "ENC28J60 Module"
|
||||
select EXAMPLE_USE_SPI_ETHERNET
|
||||
select ETH_USE_SPI_ETHERNET
|
||||
select ETH_SPI_ETHERNET_ENC28J60
|
||||
help
|
||||
Select external SPI-Ethernet module (ENC28J60).
|
||||
endchoice # EXAMPLE_ETHERNET_TYPE
|
||||
|
||||
if EXAMPLE_USE_INTERNAL_ETHERNET
|
||||
@@ -60,11 +76,18 @@ menu "Example Configuration"
|
||||
RTL8201F/SR8201F is a single port 10/100Mb Ethernet Transceiver with auto MDIX.
|
||||
Goto http://www.corechip-sz.com/productsview.asp?id=22 for more information about it.
|
||||
|
||||
config EXAMPLE_ETH_PHY_LAN8720
|
||||
bool "LAN8720"
|
||||
config EXAMPLE_ETH_PHY_LAN87XX
|
||||
bool "LAN87xx"
|
||||
help
|
||||
Below chips are supported:
|
||||
LAN8710A is a small footprint MII/RMII 10/100 Ethernet Transceiver with HP Auto-MDIX and
|
||||
flexPWR® Technology.
|
||||
LAN8720A is a small footprint RMII 10/100 Ethernet Transceiver with HP Auto-MDIX Support.
|
||||
Goto https://www.microchip.com/LAN8720A for more information about it.
|
||||
LAN8740A/LAN8741A is a small footprint MII/RMII 10/100 Energy Efficient Ethernet Transceiver
|
||||
with HP Auto-MDIX and flexPWR® Technology.
|
||||
LAN8742A is a small footprint RMII 10/100 Ethernet Transceiver with HP Auto-MDIX and
|
||||
flexPWR® Technology.
|
||||
Goto https://www.microchip.com for more information about them.
|
||||
|
||||
config EXAMPLE_ETH_PHY_DP83848
|
||||
bool "DP83848"
|
||||
@@ -77,6 +100,12 @@ menu "Example Configuration"
|
||||
help
|
||||
The KSZ8041 is a single supply 10Base-T/100Base-TX Physical Layer Transceiver.
|
||||
Goto https://www.microchip.com/wwwproducts/en/KSZ8041 for more information about it.
|
||||
|
||||
config EXAMPLE_ETH_PHY_KSZ8081
|
||||
bool "KSZ8081"
|
||||
help
|
||||
The KSZ8081 is a single supply 10Base-T/100Base-TX Physical Layer Transceiver.
|
||||
Goto https://www.microchip.com/wwwproducts/en/KSZ8081 for more information about it.
|
||||
endchoice # EXAMPLE_ETH_PHY_MODEL
|
||||
|
||||
config EXAMPLE_ETH_MDC_GPIO
|
||||
@@ -102,42 +131,61 @@ menu "Example Configuration"
|
||||
|
||||
config EXAMPLE_ETH_SPI_SCLK_GPIO
|
||||
int "SPI SCLK GPIO number"
|
||||
range 0 33
|
||||
default 20
|
||||
range 0 34 if IDF_TARGET_ESP32
|
||||
range 0 46 if IDF_TARGET_ESP32S2
|
||||
range 0 19 if IDF_TARGET_ESP32C3
|
||||
default 18 if IDF_TARGET_ESP32
|
||||
default 20 if IDF_TARGET_ESP32S2
|
||||
default 6 if IDF_TARGET_ESP32C3
|
||||
help
|
||||
Set the GPIO number used by SPI SCLK.
|
||||
|
||||
config EXAMPLE_ETH_SPI_MOSI_GPIO
|
||||
int "SPI MOSI GPIO number"
|
||||
range 0 33
|
||||
default 19
|
||||
range 0 34 if IDF_TARGET_ESP32
|
||||
range 0 46 if IDF_TARGET_ESP32S2
|
||||
range 0 19 if IDF_TARGET_ESP32C3
|
||||
default 23 if IDF_TARGET_ESP32
|
||||
default 19 if IDF_TARGET_ESP32S2
|
||||
default 7 if IDF_TARGET_ESP32C3
|
||||
help
|
||||
Set the GPIO number used by SPI MOSI.
|
||||
|
||||
config EXAMPLE_ETH_SPI_MISO_GPIO
|
||||
int "SPI MISO GPIO number"
|
||||
range 0 33
|
||||
default 18
|
||||
range 0 34 if IDF_TARGET_ESP32
|
||||
range 0 46 if IDF_TARGET_ESP32S2
|
||||
range 0 19 if IDF_TARGET_ESP32C3
|
||||
default 19 if IDF_TARGET_ESP32
|
||||
default 18 if IDF_TARGET_ESP32S2
|
||||
default 2 if IDF_TARGET_ESP32C3
|
||||
help
|
||||
Set the GPIO number used by SPI MISO.
|
||||
|
||||
config EXAMPLE_ETH_SPI_CS_GPIO
|
||||
int "SPI CS GPIO number"
|
||||
range 0 33
|
||||
default 21
|
||||
range 0 34 if IDF_TARGET_ESP32
|
||||
range 0 46 if IDF_TARGET_ESP32S2
|
||||
range 0 19 if IDF_TARGET_ESP32C3
|
||||
default 16 if IDF_TARGET_ESP32
|
||||
default 21 if IDF_TARGET_ESP32S2
|
||||
default 10 if IDF_TARGET_ESP32C3
|
||||
help
|
||||
Set the GPIO number used by SPI CS.
|
||||
|
||||
config EXAMPLE_ETH_SPI_CLOCK_MHZ
|
||||
int "SPI clock speed (MHz)"
|
||||
range 5 80
|
||||
default 36
|
||||
default 8 if EXAMPLE_USE_ENC28J60
|
||||
default 12 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32C3
|
||||
default 36 if IDF_TARGET_ESP32S2
|
||||
help
|
||||
Set the clock speed (MHz) of SPI interface.
|
||||
|
||||
config EXAMPLE_ETH_SPI_INT_GPIO
|
||||
int "Interrupt GPIO number"
|
||||
default 4
|
||||
default 17 if IDF_TARGET_ESP32
|
||||
default 4 if IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32C3
|
||||
help
|
||||
Set the GPIO number used by the SPI Ethernet module interrupt line.
|
||||
endif # EXAMPLE_USE_SPI_ETHERNET
|
||||
@@ -155,4 +203,24 @@ menu "Example Configuration"
|
||||
default 1
|
||||
help
|
||||
Set PHY address according your board schematic.
|
||||
|
||||
if EXAMPLE_USE_ENC28J60
|
||||
choice EXAMPLE_ENC28J60_DUPLEX_MODE
|
||||
prompt "Duplex Mode"
|
||||
default EXAMPLE_ENC28J60_DUPLEX_HALF
|
||||
help
|
||||
Select ENC28J60 Duplex operation mode.
|
||||
|
||||
config EXAMPLE_ENC28J60_DUPLEX_FULL
|
||||
bool "Full Duplex"
|
||||
help
|
||||
Set ENC28J60 to Full Duplex mode. Do not forget to manually set the remote node (switch, router
|
||||
or Ethernet controller) to full-duplex operation mode too.
|
||||
|
||||
config EXAMPLE_ENC28J60_DUPLEX_HALF
|
||||
bool "Half Duplex"
|
||||
help
|
||||
Set ENC28J60 to Half Duplex mode.
|
||||
endchoice # EXAMPLE_ENC28J60_DUPLEX_MODE
|
||||
endif # ETH_SPI_ETHERNET_ENC28J60
|
||||
endmenu
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
#include "sdkconfig.h"
|
||||
#if CONFIG_ETH_USE_SPI_ETHERNET
|
||||
#include "driver/spi_master.h"
|
||||
#if CONFIG_EXAMPLE_USE_ENC28J60
|
||||
#include "esp_eth_enc28j60.h"
|
||||
#endif //CONFIG_EXAMPLE_USE_ENC28J60
|
||||
#endif // CONFIG_ETH_USE_SPI_ETHERNET
|
||||
|
||||
static esp_netif_ip_info_t ip;
|
||||
@@ -201,12 +204,14 @@ void register_ethernet(void)
|
||||
esp_eth_phy_t *phy = esp_eth_phy_new_ip101(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_ETH_PHY_RTL8201
|
||||
esp_eth_phy_t *phy = esp_eth_phy_new_rtl8201(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_ETH_PHY_LAN8720
|
||||
esp_eth_phy_t *phy = esp_eth_phy_new_lan8720(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_ETH_PHY_LAN87XX
|
||||
esp_eth_phy_t *phy = esp_eth_phy_new_lan87xx(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_ETH_PHY_DP83848
|
||||
esp_eth_phy_t *phy = esp_eth_phy_new_dp83848(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_ETH_PHY_KSZ8041
|
||||
esp_eth_phy_t *phy = esp_eth_phy_new_ksz8041(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_ETH_PHY_KSZ8081
|
||||
esp_eth_phy_t *phy = esp_eth_phy_new_ksz8081(&phy_config);
|
||||
#endif
|
||||
#elif CONFIG_ETH_USE_SPI_ETHERNET
|
||||
gpio_install_isr_service(0);
|
||||
@@ -219,7 +224,21 @@ void register_ethernet(void)
|
||||
.quadhd_io_num = -1,
|
||||
};
|
||||
ESP_ERROR_CHECK(spi_bus_initialize(CONFIG_EXAMPLE_ETH_SPI_HOST, &buscfg, 1));
|
||||
#if CONFIG_EXAMPLE_USE_DM9051
|
||||
|
||||
#if CONFIG_EXAMPLE_USE_KSZ8851SNL
|
||||
spi_device_interface_config_t devcfg = {
|
||||
.mode = 0,
|
||||
.clock_speed_hz = CONFIG_EXAMPLE_ETH_SPI_CLOCK_MHZ * 1000 * 1000,
|
||||
.spics_io_num = CONFIG_EXAMPLE_ETH_SPI_CS_GPIO,
|
||||
.queue_size = 20
|
||||
};
|
||||
ESP_ERROR_CHECK(spi_bus_add_device(CONFIG_EXAMPLE_ETH_SPI_HOST, &devcfg, &spi_handle));
|
||||
/* KSZ8851SNL ethernet driver is based on spi driver */
|
||||
eth_ksz8851snl_config_t ksz8851snl_config = ETH_KSZ8851SNL_DEFAULT_CONFIG(spi_handle);
|
||||
ksz8851snl_config.int_gpio_num = CONFIG_EXAMPLE_ETH_SPI_INT_GPIO;
|
||||
esp_eth_mac_t *mac = esp_eth_mac_new_ksz8851snl(&ksz8851snl_config, &mac_config);
|
||||
esp_eth_phy_t *phy = esp_eth_phy_new_ksz8851snl(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_USE_DM9051
|
||||
spi_device_interface_config_t devcfg = {
|
||||
.command_bits = 1,
|
||||
.address_bits = 7,
|
||||
@@ -249,6 +268,29 @@ void register_ethernet(void)
|
||||
w5500_config.int_gpio_num = CONFIG_EXAMPLE_ETH_SPI_INT_GPIO;
|
||||
esp_eth_mac_t *mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config);
|
||||
esp_eth_phy_t *phy = esp_eth_phy_new_w5500(&phy_config);
|
||||
#elif CONFIG_EXAMPLE_USE_ENC28J60
|
||||
/* ENC28J60 ethernet driver is based on spi driver */
|
||||
spi_device_interface_config_t devcfg = {
|
||||
.command_bits = 3,
|
||||
.address_bits = 5,
|
||||
.mode = 0,
|
||||
.clock_speed_hz = CONFIG_EXAMPLE_ETH_SPI_CLOCK_MHZ * 1000 * 1000,
|
||||
.spics_io_num = CONFIG_EXAMPLE_ETH_SPI_CS_GPIO,
|
||||
.queue_size = 20,
|
||||
.cs_ena_posttrans = enc28j60_cal_spi_cs_hold_time(CONFIG_EXAMPLE_ETH_SPI_CLOCK_MHZ)
|
||||
};
|
||||
ESP_ERROR_CHECK(spi_bus_add_device(CONFIG_EXAMPLE_ETH_SPI_HOST, &devcfg, &spi_handle));
|
||||
|
||||
eth_enc28j60_config_t enc28j60_config = ETH_ENC28J60_DEFAULT_CONFIG(spi_handle);
|
||||
enc28j60_config.int_gpio_num = CONFIG_EXAMPLE_ETH_SPI_INT_GPIO;
|
||||
|
||||
mac_config.smi_mdc_gpio_num = -1; // ENC28J60 doesn't have SMI interface
|
||||
mac_config.smi_mdio_gpio_num = -1;
|
||||
esp_eth_mac_t *mac = esp_eth_mac_new_enc28j60(&enc28j60_config, &mac_config);
|
||||
|
||||
phy_config.autonego_timeout_ms = 0; // ENC28J60 doesn't support auto-negotiation
|
||||
phy_config.reset_gpio_num = -1; // ENC28J60 doesn't have a pin to reset internal PHY
|
||||
esp_eth_phy_t *phy = esp_eth_phy_new_enc28j60(&phy_config);
|
||||
#endif
|
||||
#endif // CONFIG_ETH_USE_SPI_ETHERNET
|
||||
esp_eth_config_t config = ETH_DEFAULT_CONFIG(mac, phy);
|
||||
@@ -264,6 +306,10 @@ void register_ethernet(void)
|
||||
ESP_ERROR_CHECK(esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handle)));
|
||||
ESP_ERROR_CHECK(esp_eth_start(eth_handle));
|
||||
|
||||
#if CONFIG_EXAMPLE_USE_ENC28J60 && CONFIG_EXAMPLE_ENC28J60_DUPLEX_FULL
|
||||
enc28j60_set_phy_duplex(phy, ETH_DUPLEX_FULL);
|
||||
#endif
|
||||
|
||||
eth_control_args.control = arg_str1(NULL, NULL, "<info>", "Get info of Ethernet");
|
||||
eth_control_args.end = arg_end(1);
|
||||
const esp_console_cmd_t cmd = {
|
||||
|
||||
@@ -12,7 +12,7 @@ Before project configuration and build, be sure to set the correct chip target u
|
||||
|
||||
### Hardware Required
|
||||
|
||||
* A development board with ESP32/ESP32-S2/ESP32-C3 SoC (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.)
|
||||
* A development board with ESP32/ESP32-S2/ESP32-S3/ESP32-C3 SoC (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.)
|
||||
* A USB cable for Power supply and programming
|
||||
|
||||
Some development boards use an addressable LED instead of a regular one. These development boards include:
|
||||
@@ -23,6 +23,7 @@ Some development boards use an addressable LED instead of a regular one. These d
|
||||
| ESP32-C3-DevKitM-1 | Addressable | GPIO8 |
|
||||
| ESP32-S2-DevKitM-1 | Addressable | GPIO18 |
|
||||
| ESP32-S2-Saola-1 | Addressable | GPIO18 |
|
||||
| ESP32-S3-Addax-1 | Addressable | GPIO48 |
|
||||
|
||||
See [Development Boards](https://www.espressif.com/en/products/devkits) for more information about it.
|
||||
|
||||
@@ -33,13 +34,12 @@ Open the project configuration menu (`idf.py menuconfig`).
|
||||
In the `Example Configuration` menu:
|
||||
|
||||
* Select the LED type in the `Blink LED type` option.
|
||||
* * Use `GPIO` for regular LED blink.
|
||||
* * Use `RMT` for addressable LED blink.
|
||||
* * * Use `RMT Channel` to select the RMT peripheral channel.
|
||||
* Use `GPIO` for regular LED blink.
|
||||
* Use `RMT` for addressable LED blink.
|
||||
* Use `RMT Channel` to select the RMT peripheral channel.
|
||||
* Set the GPIO number used for the signal in the `Blink GPIO number` option.
|
||||
* Set the blinking period in the `Blink period in ms` option.
|
||||
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
|
||||
@@ -52,6 +52,19 @@ See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/l
|
||||
|
||||
As you run the example, you will see the LED blinking, according to the previously defined period. For the addressable LED, you can also change the LED color by setting the `pStrip_a->set_pixel(pStrip_a, 0, 16, 16, 16);` (LED Strip, Pixel Number, Red, Green, Blue) with values from 0 to 255 in the `blink.c` file.
|
||||
|
||||
```
|
||||
I (315) example: Example configured to blink addressable LED!
|
||||
I (325) example: Turning the LED OFF!
|
||||
I (1325) example: Turning the LED ON!
|
||||
I (2325) example: Turning the LED OFF!
|
||||
I (3325) example: Turning the LED ON!
|
||||
I (4325) example: Turning the LED OFF!
|
||||
I (5325) example: Turning the LED ON!
|
||||
I (6325) example: Turning the LED OFF!
|
||||
I (7325) example: Turning the LED ON!
|
||||
I (8325) example: Turning the LED OFF!
|
||||
```
|
||||
|
||||
Note: The color order could be different according to the LED model.
|
||||
|
||||
The pixel number indicates the pixel position in the LED strip. For a single LED, use 0.
|
||||
@@ -60,4 +73,4 @@ The pixel number indicates the pixel position in the LED strip. For a single LED
|
||||
|
||||
* If the LED isn't blinking, check the GPIO or the LED type selection in the `Example Configuration` menu.
|
||||
|
||||
For any technical queries, please open an [issue] (https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.
|
||||
For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon.
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
idf_component_register(SRCS "blink.c"
|
||||
idf_component_register(SRCS "blink_example_main.c"
|
||||
INCLUDE_DIRS ".")
|
||||
|
||||
@@ -4,6 +4,7 @@ menu "Example Configuration"
|
||||
prompt "Blink LED type"
|
||||
default BLINK_LED_RMT if IDF_TARGET_ESP32C3
|
||||
default BLINK_LED_RMT if IDF_TARGET_ESP32S2
|
||||
default BLINK_LED_RMT if IDF_TARGET_ESP32S3
|
||||
default BLINK_LED_GPIO
|
||||
help
|
||||
Defines the default peripheral for blink example
|
||||
@@ -23,18 +24,19 @@ menu "Example Configuration"
|
||||
Set the RMT peripheral channel.
|
||||
ESP32 RMT channel from 0 to 7
|
||||
ESP32-S2 RMT channel from 0 to 3
|
||||
ESP32-S3 RMT channel from 0 to 3
|
||||
ESP32-C3 RMT channel from 0 to 1
|
||||
|
||||
config BLINK_GPIO
|
||||
int "Blink GPIO number"
|
||||
range 0 34
|
||||
range 0 48
|
||||
default 8 if IDF_TARGET_ESP32C3
|
||||
default 18 if IDF_TARGET_ESP32S2
|
||||
default 48 if IDF_TARGET_ESP32S3
|
||||
default 5
|
||||
help
|
||||
GPIO number (IOxx) to blink on and off or the RMT signal for the addressable LED.
|
||||
Some GPIOs are used for other purposes (flash connections, etc.) and cannot be used to blink.
|
||||
GPIOs 35-39 are input-only so cannot be used as outputs.
|
||||
|
||||
config BLINK_PERIOD
|
||||
int "Blink period in ms"
|
||||
|
||||
@@ -154,7 +154,14 @@ menu "Example Configuration"
|
||||
range 1 10
|
||||
default 6
|
||||
help
|
||||
The number of stations allowed to connect in.
|
||||
The number of mesh stations allowed to connect in.
|
||||
|
||||
config MESH_NON_MESH_AP_CONNECTIONS
|
||||
int "Mesh Non Mesh AP Connections"
|
||||
range 0 9
|
||||
default 0
|
||||
help
|
||||
The number of non-mesh stations allowed to connect in.
|
||||
|
||||
config MESH_ROUTE_TABLE_SIZE
|
||||
int "Mesh Routing Table Size"
|
||||
|
||||
@@ -432,6 +432,7 @@ void app_main(void)
|
||||
/* mesh softAP */
|
||||
ESP_ERROR_CHECK(esp_mesh_set_ap_authmode(CONFIG_MESH_AP_AUTHMODE));
|
||||
cfg.mesh_ap.max_connection = CONFIG_MESH_AP_CONNECTIONS;
|
||||
cfg.mesh_ap.nonmesh_max_connection = CONFIG_MESH_NON_MESH_AP_CONNECTIONS;
|
||||
memcpy((uint8_t *) &cfg.mesh_ap.password, CONFIG_MESH_AP_PASSWD,
|
||||
strlen(CONFIG_MESH_AP_PASSWD));
|
||||
ESP_ERROR_CHECK(esp_mesh_set_config(&cfg));
|
||||
|
||||
@@ -55,7 +55,14 @@ menu "Example Configuration"
|
||||
range 1 10
|
||||
default 6
|
||||
help
|
||||
The number of stations allowed to connect in.
|
||||
The number of mesh stations allowed to connect in.
|
||||
|
||||
config MESH_NON_MESH_AP_CONNECTIONS
|
||||
int "Mesh Non Mesh AP Connections"
|
||||
range 0 9
|
||||
default 0
|
||||
help
|
||||
The number of non-mesh stations allowed to connect in.
|
||||
|
||||
config MESH_MAX_LAYER
|
||||
int "Mesh Max Layer"
|
||||
|
||||
@@ -416,6 +416,7 @@ void app_main(void)
|
||||
/* mesh softAP */
|
||||
ESP_ERROR_CHECK(esp_mesh_set_ap_authmode(CONFIG_MESH_AP_AUTHMODE));
|
||||
cfg.mesh_ap.max_connection = CONFIG_MESH_AP_CONNECTIONS;
|
||||
cfg.mesh_ap.nonmesh_max_connection = CONFIG_MESH_NON_MESH_AP_CONNECTIONS;
|
||||
memcpy((uint8_t *) &cfg.mesh_ap.password, CONFIG_MESH_AP_PASSWD,
|
||||
strlen(CONFIG_MESH_AP_PASSWD));
|
||||
ESP_ERROR_CHECK(esp_mesh_set_config(&cfg));
|
||||
|
||||
@@ -67,7 +67,14 @@ menu "Example Configuration"
|
||||
range 1 10
|
||||
default 6
|
||||
help
|
||||
The number of stations allowed to connect in.
|
||||
The number of mesh stations allowed to connect in.
|
||||
|
||||
config MESH_NON_MESH_AP_CONNECTIONS
|
||||
int "Mesh Non Mesh AP Connections"
|
||||
range 0 9
|
||||
default 0
|
||||
help
|
||||
The number of non-mesh stations allowed to connect in.
|
||||
|
||||
config MESH_MAX_LAYER
|
||||
int "Mesh Max Layer"
|
||||
|
||||
@@ -322,6 +322,7 @@ void app_main(void)
|
||||
/* mesh softAP */
|
||||
ESP_ERROR_CHECK(esp_mesh_set_ap_authmode(CONFIG_MESH_AP_AUTHMODE));
|
||||
cfg.mesh_ap.max_connection = CONFIG_MESH_AP_CONNECTIONS;
|
||||
cfg.mesh_ap.nonmesh_max_connection = CONFIG_MESH_NON_MESH_AP_CONNECTIONS;
|
||||
memcpy((uint8_t *) &cfg.mesh_ap.password, CONFIG_MESH_AP_PASSWD,
|
||||
strlen(CONFIG_MESH_AP_PASSWD));
|
||||
ESP_ERROR_CHECK(esp_mesh_set_config(&cfg));
|
||||
|
||||
@@ -7,4 +7,4 @@ cmake_minimum_required(VERSION 3.5)
|
||||
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(otbr_esp)
|
||||
project(esp_ot_br)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := otbr_esp
|
||||
PROJECT_NAME := esp_ot_br
|
||||
|
||||
EXTRA_COMPONENT_DIRS = $(IDF_PATH)/examples/common_components/protocol_examples_common
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# OpenThread command line example
|
||||
# OpenThread Border Router example
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -146,3 +146,80 @@ I(552749) OPENTHREAD:[INFO]-UTIL----: Starting Child Supervision
|
||||
```
|
||||
|
||||
The device has now joined the same Thread network based on the key set by the commissioner.
|
||||
|
||||
## Bidirectional IPv6 connectivity
|
||||
|
||||
The border router will automatically publish the prefix and the route table rule to the WiFi network via ICMPv6 router advertisment packages.
|
||||
|
||||
### Host configuration
|
||||
|
||||
The automatically configure your host's route table rules you need to set these sysctl options:
|
||||
|
||||
Please relace `wlan0` with the real name of your WiFi network interface.
|
||||
```
|
||||
sudo sysctl -w net/ipv6/conf/wlan0/accept_ra=2
|
||||
sudo sysctl -w net/ipv6/conf/wlan0/accept_ra_rt_info_max_plen=128
|
||||
```
|
||||
|
||||
For mobile devices, the route table rules will be automatically configured after iOS 14 and Android 8.1.
|
||||
|
||||
|
||||
### Testing IPv6 connecitivity
|
||||
|
||||
Now in the joining device, check the IP addresses:
|
||||
|
||||
```
|
||||
> ipaddr
|
||||
fde6:75ff:def4:3bc3:9e9e:3ef:4245:28b5
|
||||
fdde:ad00:beef:0:0:ff:fe00:c402
|
||||
fdde:ad00:beef:0:ad4a:9a9a:3cd6:e423
|
||||
fe80:0:0:0:f011:2951:569e:9c4a
|
||||
```
|
||||
|
||||
You'll notice an IPv6 global prefix with only on address assigned under it. This is the routable address of this Thread node.
|
||||
You can ping this address on your host:
|
||||
|
||||
``` bash
|
||||
$ ping fde6:75ff:def4:3bc3:9e9e:3ef:4245:28b5
|
||||
PING fde6:75ff:def4:3bc3:9e9e:3ef:4245:28b5(fde6:75ff:def4:3bc3:9e9e:3ef:4245:28b5) 56 data bytes
|
||||
64 bytes from fde6:75ff:def4:3bc3:9e9e:3ef:4245:28b5: icmp_seq=1 ttl=63 time=459 ms
|
||||
64 bytes from fde6:75ff:def4:3bc3:9e9e:3ef:4245:28b5: icmp_seq=2 ttl=63 time=109 ms
|
||||
64 bytes from fde6:75ff:def4:3bc3:9e9e:3ef:4245:28b5: icmp_seq=3 ttl=63 time=119 ms
|
||||
64 bytes from fde6:75ff:def4:3bc3:9e9e:3ef:4245:28b5: icmp_seq=4 ttl=63 time=117 ms
|
||||
```
|
||||
|
||||
## Service discovery
|
||||
|
||||
The newly introduced service registration protocol([SRP](https://datatracker.ietf.org/doc/html/draft-ietf-dnssd-srp-10)) allows devices in the Thread network to register a service. The border router will forward the service to the WiFi network via mDNS.
|
||||
|
||||
Now we'll publish the service `my-service._test._udp` with hostname `test0` and port 12345
|
||||
|
||||
```
|
||||
> srp client host name test0
|
||||
Done
|
||||
> srp client host address fde6:75ff:def4:3bc3:9e9e:3ef:4245:28b5
|
||||
Done
|
||||
> srp client service add my-service _test._udp 12345
|
||||
Done
|
||||
> srp client autostart enable
|
||||
Done
|
||||
```
|
||||
|
||||
This service will also become visible on the WiFi network:
|
||||
|
||||
```bash
|
||||
$ avahi-browse -r _test._udp -t
|
||||
|
||||
+ enp1s0 IPv6 my-service _test._udp local
|
||||
= enp1s0 IPv6 my-service _test._udp local
|
||||
hostname = [test0.local]
|
||||
address = [fde6:75ff:def4:3bc3:9e9e:3ef:4245:28b5]
|
||||
port = [12345]
|
||||
txt = []
|
||||
+ enp1s0 IPv4 my-service _test._udp local
|
||||
= enp1s0 IPv4 my-service _test._udp local
|
||||
hostname = [test0.local]
|
||||
address = [fde6:75ff:def4:3bc3:9e9e:3ef:4245:28b5]
|
||||
port = [12345]
|
||||
txt = []
|
||||
```
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "esp_ot_config.h"
|
||||
#include "esp_vfs_eventfd.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "mdns.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "protocol_examples_common.h"
|
||||
#include "sdkconfig.h"
|
||||
@@ -109,18 +110,19 @@ static void create_config_network(otInstance *instance)
|
||||
abort();
|
||||
}
|
||||
dataset.mComponents.mIsExtendedPanIdPresent = true;
|
||||
if (hex_string_to_binary(CONFIG_OPENTHREAD_NETWORK_MASTERKEY, dataset.mMasterKey.m8,
|
||||
sizeof(dataset.mMasterKey.m8)) != sizeof(dataset.mMasterKey.m8)) {
|
||||
if (hex_string_to_binary(CONFIG_OPENTHREAD_NETWORK_MASTERKEY, dataset.mNetworkKey.m8,
|
||||
sizeof(dataset.mNetworkKey.m8)) != sizeof(dataset.mNetworkKey.m8)) {
|
||||
ESP_LOGE(TAG, "Cannot convert OpenThread master key. Please double-check your config.");
|
||||
abort();
|
||||
}
|
||||
dataset.mComponents.mIsMasterKeyPresent = true;
|
||||
dataset.mComponents.mIsNetworkKeyPresent = true;
|
||||
if (hex_string_to_binary(CONFIG_OPENTHREAD_NETWORK_PSKC, dataset.mPskc.m8, sizeof(dataset.mPskc.m8)) !=
|
||||
sizeof(dataset.mPskc.m8)) {
|
||||
ESP_LOGE(TAG, "Cannot convert OpenThread pre-shared commissioner key. Please double-check your config.");
|
||||
abort();
|
||||
}
|
||||
dataset.mComponents.mIsPskcPresent = true;
|
||||
dataset.mComponents.mIsMeshLocalPrefixPresent = false;
|
||||
if (otDatasetSetActive(instance, &dataset) != OT_ERROR_NONE) {
|
||||
ESP_LOGE(TAG, "Failed to set OpenThread active dataset.");
|
||||
abort();
|
||||
@@ -188,5 +190,7 @@ void app_main(void)
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
ESP_ERROR_CHECK(example_connect());
|
||||
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
|
||||
ESP_ERROR_CHECK(mdns_init());
|
||||
ESP_ERROR_CHECK(mdns_hostname_set("esp-ot-br"));
|
||||
xTaskCreate(ot_task_worker, "ot_br_main", 20480, xTaskGetCurrentTaskHandle(), 5, NULL);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,16 @@ CONFIG_OPENTHREAD_BORDER_ROUTER=y
|
||||
#
|
||||
# lwIP
|
||||
#
|
||||
CONFIG_LWIP_IPV6_FORWARD=y
|
||||
CONFIG_LWIP_IPV6_NUM_ADDRESSES=8
|
||||
CONFIG_LWIP_MULTICAST_PING=y
|
||||
CONFIG_LWIP_NETIF_STATUS_CALLBACK=y
|
||||
CONFIG_LWIP_HOOK_IP6_ROUTE_DEFAULT=y
|
||||
CONFIG_LWIP_HOOK_ND6_GET_GW_DEFAULT=y
|
||||
# end of lwIP
|
||||
|
||||
#
|
||||
# mDNS
|
||||
#
|
||||
CONFIG_MDNS_STRICT_MODE=y
|
||||
# end of mDNS
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := ot_esp_cli
|
||||
PROJECT_NAME := esp_ot_cli
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
|
||||
@@ -1,2 +1,8 @@
|
||||
idf_component_register(SRCS "esp_ot_udp_socket.c" "esp_ot_tcp_socket.c" "esp_ot_cli_extension.c" "esp_ot_cli.c"
|
||||
set(srcs "esp_ot_cli.c")
|
||||
|
||||
if(CONFIG_OPENTHREAD_CLI_ESP_EXTENSION)
|
||||
list(APPEND srcs "esp_ot_cli_extension.c" "esp_ot_tcp_socket.c" "esp_ot_udp_socket.c")
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS "${srcs}"
|
||||
INCLUDE_DIRS ".")
|
||||
|
||||
@@ -1,30 +1,22 @@
|
||||
menu "Openthread"
|
||||
menu "OpenThread CLI Example"
|
||||
|
||||
menuconfig OPENTHREAD_CUSTOM_COMMAND
|
||||
bool "Enable custom command in ot-cli"
|
||||
default n
|
||||
config OPENTHREAD_CLI_ESP_EXTENSION
|
||||
bool "Enable Espressif's extended features"
|
||||
default y
|
||||
help
|
||||
Enable Espressif's extended features include TCP socket, UDP socket.
|
||||
|
||||
config OPENTHREAD_ENABLE_TCP_SOCKET_EXAMPLE
|
||||
bool "Enable openthread tcp socket"
|
||||
depends on OPENTHREAD_CUSTOM_COMMAND
|
||||
default n
|
||||
|
||||
config OPENTHEAD_EXAMPLE_TCP_SERVER_PORT
|
||||
config OPENTHREAD_CLI_TCP_SERVER_PORT
|
||||
int "the port of TCP server"
|
||||
default 12345
|
||||
depends on OPENTHREAD_ENABLE_TCP_SOCKET_EXAMPLE
|
||||
depends on OPENTHREAD_CLI_ESP_EXTENSION
|
||||
help
|
||||
Set the connect port of socket
|
||||
|
||||
config OPENTHREAD_ENABLE_UDP_SOCKET_EXAMPLE
|
||||
bool "Enable openthread udp socket"
|
||||
depends on OPENTHREAD_CUSTOM_COMMAND
|
||||
default n
|
||||
|
||||
config OPENTHEAD_EXAMPLE_UDP_SERVER_PORT
|
||||
config OPENTHREAD_CLI_UDP_SERVER_PORT
|
||||
int "the port of UDP server"
|
||||
default 54321
|
||||
depends on OPENTHREAD_ENABLE_UDP_SOCKET_EXAMPLE
|
||||
depends on OPENTHREAD_CLI_ESP_EXTENSION
|
||||
help
|
||||
Set the connect port of socket
|
||||
|
||||
|
||||
@@ -35,14 +35,15 @@
|
||||
#include "openthread/instance.h"
|
||||
#include "openthread/tasklet.h"
|
||||
|
||||
#if CONFIG_OPENTHREAD_CUSTOM_COMMAND
|
||||
#if CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
|
||||
#include "esp_ot_cli_extension.h"
|
||||
#endif // CONFIG_OPENTHREAD_CUSTOM_COMMAND
|
||||
#endif // CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
|
||||
|
||||
#define TAG "ot_esp_cli"
|
||||
|
||||
extern void otAppCliInit(otInstance *instance);
|
||||
extern void otAppCliInit(otInstance *aInstance);
|
||||
|
||||
#if CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
|
||||
static esp_netif_t *init_openthread_netif(const esp_openthread_platform_config_t *config)
|
||||
{
|
||||
esp_netif_config_t cfg = ESP_NETIF_DEFAULT_OPENTHREAD();
|
||||
@@ -52,6 +53,7 @@ static esp_netif_t *init_openthread_netif(const esp_openthread_platform_config_t
|
||||
|
||||
return netif;
|
||||
}
|
||||
#endif // CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
|
||||
|
||||
static void ot_task_worker(void *aContext)
|
||||
{
|
||||
@@ -60,7 +62,6 @@ static void ot_task_worker(void *aContext)
|
||||
.host_config = ESP_OPENTHREAD_DEFAULT_HOST_CONFIG(),
|
||||
.port_config = ESP_OPENTHREAD_DEFAULT_PORT_CONFIG(),
|
||||
};
|
||||
esp_netif_t *openthread_netif;
|
||||
|
||||
// Initialize the OpenThread stack
|
||||
ESP_ERROR_CHECK(esp_openthread_init(&config));
|
||||
@@ -68,19 +69,23 @@ static void ot_task_worker(void *aContext)
|
||||
// Initialize the OpenThread cli
|
||||
otAppCliInit(esp_openthread_get_instance());
|
||||
|
||||
#if CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
|
||||
esp_netif_t *openthread_netif;
|
||||
// Initialize the esp_netif bindings
|
||||
openthread_netif = init_openthread_netif(&config);
|
||||
|
||||
#if CONFIG_OPENTHREAD_CUSTOM_COMMAND
|
||||
esp_cli_custom_command_init();
|
||||
#endif // CONFIG_OPENTHREAD_CUSTOM_COMMAND
|
||||
#endif // CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
|
||||
|
||||
// Run the main loop
|
||||
esp_openthread_launch_mainloop();
|
||||
|
||||
// Clean up
|
||||
#if CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
|
||||
esp_netif_destroy(openthread_netif);
|
||||
esp_openthread_netif_glue_deinit();
|
||||
#endif // CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
|
||||
|
||||
esp_vfs_eventfd_unregister();
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
@@ -96,7 +101,9 @@ void app_main(void)
|
||||
};
|
||||
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
#if CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
#endif // CONFIG_OPENTHREAD_CLI_ESP_EXTENSION
|
||||
ESP_ERROR_CHECK(esp_vfs_eventfd_register(&eventfd_config));
|
||||
xTaskCreate(ot_task_worker, "ot_cli_main", 10240, xTaskGetCurrentTaskHandle(), 5, NULL);
|
||||
}
|
||||
|
||||
@@ -22,14 +22,10 @@
|
||||
#include "openthread/cli.h"
|
||||
|
||||
static const otCliCommand kCommands[] = {
|
||||
#if CONFIG_OPENTHREAD_ENABLE_TCP_SOCKET_EXAMPLE
|
||||
{"tcpsockserver", esp_ot_process_tcp_server},
|
||||
{"tcpsockclient", esp_ot_process_tcp_client},
|
||||
#endif // CONFIG_OPENTHREAD_ENABLE_TCP_SOCKET_EXAMPLE
|
||||
#if CONFIG_OPENTHREAD_ENABLE_UDP_SOCKET_EXAMPLE
|
||||
{"udpsockserver", esp_ot_process_udp_server},
|
||||
{"udpsockclient", esp_ot_process_udp_client},
|
||||
#endif // CONFIG_OPENTHREAD_ENABLE_UDP_SOCKET_EXAMPLE
|
||||
};
|
||||
|
||||
void esp_cli_custom_command_init()
|
||||
|
||||
@@ -19,20 +19,18 @@
|
||||
#include "lwip/err.h"
|
||||
#include "lwip/sockets.h"
|
||||
|
||||
#if CONFIG_OPENTHREAD_ENABLE_TCP_SOCKET_EXAMPLE
|
||||
#define TAG "ot_socket"
|
||||
|
||||
static void tcp_socket_server_task(void *pvParameters)
|
||||
{
|
||||
char addr_str[128];
|
||||
char payload[] = "This message is from server\n";
|
||||
char rx_buffer[128];
|
||||
esp_err_t ret = ESP_OK;
|
||||
int err = 0;
|
||||
int len = 0;
|
||||
int listen_sock;
|
||||
int opt = 1;
|
||||
int port = CONFIG_OPENTHEAD_EXAMPLE_TCP_SERVER_PORT;
|
||||
int port = CONFIG_OPENTHREAD_CLI_TCP_SERVER_PORT;
|
||||
int client_sock = 0;
|
||||
struct timeval timeout;
|
||||
struct sockaddr_storage source_addr; // Large enough for both IPv6
|
||||
@@ -108,7 +106,7 @@ static void tcp_socket_client_task(void *pvParameters)
|
||||
int client_sock;
|
||||
int err = 0;
|
||||
int len = 0;
|
||||
int port = CONFIG_OPENTHEAD_EXAMPLE_TCP_SERVER_PORT;
|
||||
int port = CONFIG_OPENTHREAD_CLI_TCP_SERVER_PORT;
|
||||
struct sockaddr_in6 dest_addr = { 0 };
|
||||
|
||||
inet6_aton(host_ip, &dest_addr.sin6_addr);
|
||||
@@ -162,4 +160,3 @@ void esp_ot_process_tcp_client(void *aContext, uint8_t aArgsLength, char *aArgs[
|
||||
xTaskCreate(tcp_socket_client_task, "ot_tcp_socket_client", 4096, aArgs[0], 4, NULL);
|
||||
}
|
||||
}
|
||||
#endif // CONFIG_OPENTHREAD_ENABLE_TCP_SOCKET_EXAMPLE
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if CONFIG_OPENTHREAD_ENABLE_TCP_SOCKET_EXAMPLE
|
||||
/**
|
||||
* @brief User command "tcpsockserver" process.
|
||||
*
|
||||
@@ -31,7 +30,6 @@ void esp_ot_process_tcp_server(void *aContext, uint8_t aArgsLength, char *aArgs[
|
||||
*/
|
||||
void esp_ot_process_tcp_client(void *aContext, uint8_t aArgsLength, char *aArgs[]);
|
||||
|
||||
#endif // CONFIG_OPENTHREAD_ENABLE_TCP_SOCKET_EXAMPLE
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include "lwip/err.h"
|
||||
#include "lwip/sockets.h"
|
||||
|
||||
#if CONFIG_OPENTHREAD_ENABLE_UDP_SOCKET_EXAMPLE
|
||||
#define TAG "ot_socket"
|
||||
|
||||
static void udp_socket_server_task(void *pvParameters)
|
||||
@@ -31,7 +30,7 @@ static void udp_socket_server_task(void *pvParameters)
|
||||
int err = 0;
|
||||
int len;
|
||||
int listen_sock;
|
||||
int port = CONFIG_OPENTHEAD_EXAMPLE_UDP_SERVER_PORT;
|
||||
int port = CONFIG_OPENTHREAD_CLI_UDP_SERVER_PORT;
|
||||
struct timeval timeout;
|
||||
struct sockaddr_storage source_addr; // Large enough for both IPv4 or IPv6
|
||||
struct sockaddr_in6 listen_addr;
|
||||
@@ -88,7 +87,7 @@ static void udp_socket_client_task(void *pvParameters)
|
||||
char *host_ip = (char *)pvParameters;
|
||||
char *payload = "This message is from client\n";
|
||||
int client_sock;
|
||||
int port = CONFIG_OPENTHEAD_EXAMPLE_UDP_SERVER_PORT;
|
||||
int port = CONFIG_OPENTHREAD_CLI_UDP_SERVER_PORT;
|
||||
int err = 0;
|
||||
int len;
|
||||
esp_err_t ret = ESP_OK;
|
||||
@@ -142,4 +141,3 @@ void esp_ot_process_udp_client(void *aContext, uint8_t aArgsLength, char *aArgs[
|
||||
xTaskCreate(udp_socket_client_task, "ot_udp_socket_client", 4096, aArgs[0], 4, NULL);
|
||||
}
|
||||
}
|
||||
#endif // CONFIG_OPENTHREAD_ENABLE_UDP_SOCKET_EXAMPLE
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#if CONFIG_OPENTHREAD_ENABLE_UDP_SOCKET_EXAMPLE
|
||||
/**
|
||||
* @brief User command "udpsockserver" process.
|
||||
*
|
||||
@@ -30,7 +29,6 @@ void esp_ot_process_udp_server(void *aContext, uint8_t aArgsLength, char *aArgs[
|
||||
*
|
||||
*/
|
||||
void esp_ot_process_udp_client(void *aContext, uint8_t aArgsLength, char *aArgs[]);
|
||||
#endif // CONFIG_OPENTHREAD_ENABLE_UDP_SOCKET_EXAMPLE
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ To run this example, you should have one ESP development board (e.g. ESP32-WROVE
|
||||
|
||||
#### Pin Assignment(esp32, esp32s2):
|
||||
|
||||
**Note:** The following pin assignments are used by default, yout can change these in the `menuconfig` .
|
||||
**Note:** The following pin assignments are used by default, you can change these in the `menuconfig` .
|
||||
|
||||
| | SDA | SCL |
|
||||
| ------------------------- | ------ | ------ |
|
||||
@@ -41,6 +41,30 @@ To run this example, you should have one ESP development board (e.g. ESP32-WROVE
|
||||
|
||||
**Note:** It is recommended to add external pull-up resistors for SDA/SCL pins to make the communication more stable, though the driver will enable internal pull-up resistors.
|
||||
|
||||
#### Pin Assignment(esp32s3):
|
||||
|
||||
**Note:** The following pin assignments are used by default, you can change these in the `menuconfig` .
|
||||
|
||||
| | SDA | SCL |
|
||||
| ------------------------- | ------ | ------ |
|
||||
| ESP32-S3 I2C Master | GPIO1 | GPIO2 |
|
||||
| ESP32-S3 I2C Slave | GPIO4 | GPIO5 |
|
||||
| BH1750 Sensor | SDA | SCL |
|
||||
|
||||
- slave:
|
||||
- GPIO4 is assigned as the data signal of I2C slave port
|
||||
- GPIO5 is assigned as the clock signal of I2C slave port
|
||||
- master:
|
||||
- GPIO1 is assigned as the data signal of I2C master port
|
||||
- GPIO2 is assigned as the clock signal of I2C master port
|
||||
|
||||
- Connection:
|
||||
- connect GPIO1 with GPIO4
|
||||
- connect GPIO2 with GPIO5
|
||||
- connect SDA/SCL of BH1750 sensor with GPIO18/GPIO19
|
||||
|
||||
**Note:** It is recommended to add external pull-up resistors for SDA/SCL pins to make the communication more stable, though the driver will enable internal pull-up resistors.
|
||||
|
||||
#### Pin Assignment(esp32c3):
|
||||
|
||||
**Note:** The following pin assignments are used by default, you can change these in the `menuconfig` .
|
||||
|
||||
@@ -4,14 +4,16 @@ menu "Example Configuration"
|
||||
config I2C_MASTER_SCL
|
||||
int "SCL GPIO Num"
|
||||
default 6 if IDF_TARGET_ESP32C3
|
||||
default 19 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
|
||||
default 2 if IDF_TARGET_ESP32S3
|
||||
default 19 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2
|
||||
help
|
||||
GPIO number for I2C Master clock line.
|
||||
|
||||
config I2C_MASTER_SDA
|
||||
int "SDA GPIO Num"
|
||||
default 5 if IDF_TARGET_ESP32C3
|
||||
default 18 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
|
||||
default 1 if IDF_TARGET_ESP32S3
|
||||
default 18 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2
|
||||
help
|
||||
GPIO number for I2C Master data line.
|
||||
|
||||
|
||||
6
examples/peripherals/i2c/i2c_simple/CMakeLists.txt
Normal file
6
examples/peripherals/i2c/i2c_simple/CMakeLists.txt
Normal file
@@ -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(i2c-simple)
|
||||
8
examples/peripherals/i2c/i2c_simple/Makefile
Normal file
8
examples/peripherals/i2c/i2c_simple/Makefile
Normal file
@@ -0,0 +1,8 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := i2c-simple
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
49
examples/peripherals/i2c/i2c_simple/README.md
Normal file
49
examples/peripherals/i2c/i2c_simple/README.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# I2C Simple Example
|
||||
|
||||
(See the README.md file in the upper level 'examples' directory for more information about examples.)
|
||||
|
||||
## Overview
|
||||
|
||||
This example demonstrates basic usage of I2C driver by reading and writing from a I2C connected sensor:
|
||||
|
||||
If you have a new I2C application to go (for example, read the temperature data from external sensor with I2C interface), try this as a basic template, then add your own code.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
To run this example, you should have one ESP32, ESP32-S or ESP32-C based development board as well as a MPU9250. MPU9250 is a inertial measurement unit, which contains a accelerometer, gyroscope as well as a magnetometer, for more information about it, you can read the [PDF](https://invensense.tdk.com/wp-content/uploads/2015/02/PS-MPU-9250A-01-v1.1.pdf) of this sensor.
|
||||
|
||||
#### Pin Assignment:
|
||||
|
||||
**Note:** The following pin assignments are used by default, you can change these in the `menuconfig` .
|
||||
|
||||
| | SDA | SCL |
|
||||
| ---------------- | -------------- | -------------- |
|
||||
| ESP I2C Master | I2C_MASTER_SDA | I2C_MASTER_SCL |
|
||||
| MPU9250 Sensor | SDA | SCL |
|
||||
|
||||
|
||||
For the actual default value of `I2C_MASTER_SDA` and `I2C_MASTER_SCL` see `Example Configuration` in `menuconfig`.
|
||||
|
||||
**Note: ** There’s no need to add an external pull-up resistors for SDA/SCL pin, because the driver will enable the internal pull-up resistors.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Enter `idf.py -p PORT flash monitor` to build, flash and monitor the project.
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
```bash
|
||||
I (328) i2c-simple-example: I2C initialized successfully
|
||||
I (338) i2c-simple-example: WHO_AM_I = 71
|
||||
I (338) i2c-simple-example: I2C unitialized successfully
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
(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.)
|
||||
2
examples/peripherals/i2c/i2c_simple/main/CMakeLists.txt
Normal file
2
examples/peripherals/i2c/i2c_simple/main/CMakeLists.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "i2c_simple_main.c"
|
||||
INCLUDE_DIRS ".")
|
||||
17
examples/peripherals/i2c/i2c_simple/main/Kconfig.projbuild
Normal file
17
examples/peripherals/i2c/i2c_simple/main/Kconfig.projbuild
Normal file
@@ -0,0 +1,17 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config I2C_MASTER_SCL
|
||||
int "SCL GPIO Num"
|
||||
default 6 if IDF_TARGET_ESP32C3
|
||||
default 19 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
|
||||
help
|
||||
GPIO number for I2C Master clock line.
|
||||
|
||||
config I2C_MASTER_SDA
|
||||
int "SDA GPIO Num"
|
||||
default 5 if IDF_TARGET_ESP32C3
|
||||
default 18 if IDF_TARGET_ESP32 || IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32S3
|
||||
help
|
||||
GPIO number for I2C Master data line.
|
||||
|
||||
endmenu
|
||||
3
examples/peripherals/i2c/i2c_simple/main/component.mk
Normal file
3
examples/peripherals/i2c/i2c_simple/main/component.mk
Normal file
@@ -0,0 +1,3 @@
|
||||
#
|
||||
# Main Makefile. This is basically the same as a component makefile .
|
||||
#
|
||||
97
examples/peripherals/i2c/i2c_simple/main/i2c_simple_main.c
Normal file
97
examples/peripherals/i2c/i2c_simple/main/i2c_simple_main.c
Normal file
@@ -0,0 +1,97 @@
|
||||
/* i2c - Simple example
|
||||
|
||||
Simple I2C example that shows how to initialize I2C
|
||||
as well as reading and writing from and to registers for a sensor connected over I2C.
|
||||
|
||||
The sensor used in this example is a MPU9250 inertial measurement unit.
|
||||
|
||||
For other examples please check:
|
||||
https://github.com/espressif/esp-idf/tree/master/examples
|
||||
|
||||
See README.md file to get detailed usage of this 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 "esp_log.h"
|
||||
#include "driver/i2c.h"
|
||||
|
||||
static const char *TAG = "i2c-simple-example";
|
||||
|
||||
#define I2C_MASTER_SCL_IO CONFIG_I2C_MASTER_SCL /*!< GPIO number used for I2C master clock */
|
||||
#define I2C_MASTER_SDA_IO CONFIG_I2C_MASTER_SDA /*!< GPIO number used for I2C master data */
|
||||
#define I2C_MASTER_NUM 0 /*!< I2C master i2c port number, the number of i2c peripheral interfaces available will depend on the chip */
|
||||
#define I2C_MASTER_FREQ_HZ 400000 /*!< I2C master clock frequency */
|
||||
#define I2C_MASTER_TX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */
|
||||
#define I2C_MASTER_RX_BUF_DISABLE 0 /*!< I2C master doesn't need buffer */
|
||||
#define I2C_MASTER_TIMEOUT_MS 1000
|
||||
|
||||
#define MPU9250_SENSOR_ADDR 0x68 /*!< Slave address of the MPU9250 sensor */
|
||||
#define MPU9250_WHO_AM_I_REG_ADDR 0x75 /*!< Register addresses of the "who am I" register */
|
||||
|
||||
#define MPU9250_PWR_MGMT_1_REG_ADDR 0x6B /*!< Register addresses of the power managment register */
|
||||
#define MPU9250_RESET_BIT 7
|
||||
|
||||
/**
|
||||
* @brief Read a sequence of bytes from a MPU9250 sensor registers
|
||||
*/
|
||||
static esp_err_t mpu9250_register_read(uint8_t reg_addr, uint8_t *data, size_t len)
|
||||
{
|
||||
return i2c_master_write_read_device(I2C_MASTER_NUM, MPU9250_SENSOR_ADDR, ®_addr, 1, data, len, I2C_MASTER_TIMEOUT_MS / portTICK_RATE_MS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Write a byte to a MPU9250 sensor register
|
||||
*/
|
||||
static esp_err_t mpu9250_register_write_byte(uint8_t reg_addr, uint8_t data)
|
||||
{
|
||||
int ret;
|
||||
uint8_t write_buf[2] = {reg_addr, data};
|
||||
|
||||
ret = i2c_master_write_to_device(I2C_MASTER_NUM, MPU9250_SENSOR_ADDR, write_buf, sizeof(write_buf), I2C_MASTER_TIMEOUT_MS / portTICK_RATE_MS);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief i2c master initialization
|
||||
*/
|
||||
static esp_err_t i2c_master_init(void)
|
||||
{
|
||||
int i2c_master_port = I2C_MASTER_NUM;
|
||||
|
||||
i2c_config_t conf = {
|
||||
.mode = I2C_MODE_MASTER,
|
||||
.sda_io_num = I2C_MASTER_SDA_IO,
|
||||
.scl_io_num = I2C_MASTER_SCL_IO,
|
||||
.sda_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.scl_pullup_en = GPIO_PULLUP_ENABLE,
|
||||
.master.clk_speed = I2C_MASTER_FREQ_HZ,
|
||||
};
|
||||
|
||||
i2c_param_config(i2c_master_port, &conf);
|
||||
|
||||
return i2c_driver_install(i2c_master_port, conf.mode, I2C_MASTER_RX_BUF_DISABLE, I2C_MASTER_TX_BUF_DISABLE, 0);
|
||||
}
|
||||
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
uint8_t data[2];
|
||||
ESP_ERROR_CHECK(i2c_master_init());
|
||||
ESP_LOGI(TAG, "I2C initialized successfully");
|
||||
|
||||
/* Read the MPU9250 WHO_AM_I register, on power up the register should have the value 0x71 */
|
||||
ESP_ERROR_CHECK(mpu9250_register_read(MPU9250_WHO_AM_I_REG_ADDR, data, 1));
|
||||
ESP_LOGI(TAG, "WHO_AM_I = %X", data[0]);
|
||||
|
||||
/* Demonstrate writing by reseting the MPU9250 */
|
||||
ESP_ERROR_CHECK(mpu9250_register_write_byte(MPU9250_PWR_MGMT_1_REG_ADDR, 1 << MPU9250_RESET_BIT));
|
||||
|
||||
ESP_ERROR_CHECK(i2c_driver_delete(I2C_MASTER_NUM));
|
||||
ESP_LOGI(TAG, "I2C unitialized successfully");
|
||||
}
|
||||
@@ -18,16 +18,19 @@ If you have some trouble in developing I2C related applications, or just want to
|
||||
|
||||
### Hardware Required
|
||||
|
||||
To run this example, you should have one ESP32 dev board (e.g. ESP32-WROVER Kit) or ESP32 core board (e.g. ESP32-DevKitC). For test purpose, you should have a kind of device with I2C interface as well. Here we will take the CCS811 sensor as an example to show how to test the function of this sensor without writing any code (just use the command-line tools supported by this example). For more information about CCS811, you can consult the [online datasheet](http://ams.com/ccs811).
|
||||
To run this example, you should have any ESP32, ESP32-S and ESP32-C based development board. For test purpose, you should have a kind of device with I2C interface as well. Here we will take the CCS811 sensor as an example to show how to test the function of this sensor without writing any code (just use the command-line tools supported by this example). For more information about CCS811, you can consult the [online datasheet](http://ams.com/ccs811).
|
||||
|
||||
#### Pin Assignment:
|
||||
|
||||
**Note:** The following pin assignments are used by default, you can change them with `i2cconfig` command at any time.
|
||||
|
||||
| | SDA | SCL | GND | Other | VCC |
|
||||
| ---------------- | ------ | ------ | ---- | ----- | ---- |
|
||||
| ESP32 I2C Master | GPIO18 | GPIO19 | GND | GND | 3.3V |
|
||||
| Sensor | SDA | SCL | GND | WAK | VCC |
|
||||
| | SDA | SCL | GND | Other | VCC |
|
||||
| ------------------- | ------ | ------ | ---- | ----- | ---- |
|
||||
| ESP32 I2C Master | GPIO18 | GPIO19 | GND | GND | 3.3V |
|
||||
| ESP32-S2 I2C Master | GPIO18 | GPIO19 | GND | GND | 3.3V |
|
||||
| ESP32-S3 I2C Master | GPIO1 | GPIO2 | GND | GND | 3.3V |
|
||||
| ESP32-C3 I2C Master | GPIO5 | GPIO6 | GND | GND | 3.3V |
|
||||
| Sensor | SDA | SCL | GND | WAK | VCC |
|
||||
|
||||
**Note: ** There’s no need to add an external pull-up resistors for SDA/SCL pin, because the driver will enable the internal pull-up resistors itself.
|
||||
|
||||
|
||||
@@ -23,8 +23,17 @@
|
||||
|
||||
static const char *TAG = "cmd_i2ctools";
|
||||
|
||||
#if CONFIG_IDF_TARGET_ESP32S3
|
||||
static gpio_num_t i2c_gpio_sda = 1;
|
||||
static gpio_num_t i2c_gpio_scl = 2;
|
||||
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||
static gpio_num_t i2c_gpio_sda = 5;
|
||||
static gpio_num_t i2c_gpio_scl = 6;
|
||||
#else
|
||||
static gpio_num_t i2c_gpio_sda = 18;
|
||||
static gpio_num_t i2c_gpio_scl = 19;
|
||||
#endif
|
||||
|
||||
static uint32_t i2c_frequency = 100000;
|
||||
static i2c_port_t i2c_port = I2C_NUM_0;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user