Merge branch 'master' into feature/lwip_sntp_max_servers

This commit is contained in:
David Cermak
2021-08-24 18:16:07 +02:00
1400 changed files with 134414 additions and 65245 deletions

View File

@@ -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.

View File

@@ -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.

View File

@@ -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)

View File

@@ -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

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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(&param);
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();
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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
};

View File

@@ -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)

View File

@@ -0,0 +1,4 @@
set(srcs "src/pid_ctrl.c")
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS "include")

View File

@@ -0,0 +1,2 @@
COMPONENT_ADD_INCLUDEDIRS := include
COMPONENT_SRCDIRS := src

View 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

View 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;
}

View File

@@ -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"

View File

@@ -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 = {

View File

@@ -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

View File

@@ -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);

View File

@@ -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!')

View 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)

View 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
```

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "main.cpp"
INCLUDE_DIRS ".")

View 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");
}
}

View 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

View File

@@ -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})

View File

@@ -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

View File

@@ -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)

View File

@@ -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)
```

View File

@@ -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)

View File

@@ -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);
}

View File

@@ -0,0 +1,3 @@
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
CONFIG_IDF_TARGET="linux"
CONFIG_CXX_EXCEPTIONS=y

View File

@@ -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;
};

View File

@@ -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)

View File

@@ -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`

View File

@@ -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)

View File

@@ -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());
}

View File

@@ -0,0 +1,3 @@
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
CONFIG_IDF_TARGET="linux"
CONFIG_CXX_EXCEPTIONS=y

View File

@@ -18,6 +18,7 @@
#include <chrono>
#include <functional>
#include <string>
#include "esp_exception.hpp"
#include "esp_timer.h"

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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();
}
};

View File

@@ -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;

View File

@@ -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)

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,3 @@
idf_component_register(SRCS "esp_eth_mac_enc28j60.c"
"esp_eth_phy_enc28j60.c"
INCLUDE_DIRS ".")

View File

@@ -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 := .

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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);

View File

@@ -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 ".")

View File

@@ -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

View File

@@ -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
}

View File

@@ -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

View File

@@ -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();
}

View File

@@ -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)

View File

@@ -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

View 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')

View File

@@ -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

View File

@@ -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 = {

View File

@@ -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.

View File

@@ -1,2 +1,2 @@
idf_component_register(SRCS "blink.c"
idf_component_register(SRCS "blink_example_main.c"
INCLUDE_DIRS ".")

View File

@@ -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"

View File

@@ -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"

View File

@@ -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));

View File

@@ -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"

View File

@@ -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));

View File

@@ -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"

View File

@@ -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));

View File

@@ -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)

View File

@@ -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

View File

@@ -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 = []
```

View File

@@ -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);
}

View File

@@ -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

View File

@@ -3,6 +3,6 @@
# project subdirectory.
#
PROJECT_NAME := ot_esp_cli
PROJECT_NAME := esp_ot_cli
include $(IDF_PATH)/make/project.mk

View File

@@ -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 ".")

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
}

View File

@@ -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` .

View File

@@ -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.

View 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)

View 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

View 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: ** Theres 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.)

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "i2c_simple_main.c"
INCLUDE_DIRS ".")

View 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

View File

@@ -0,0 +1,3 @@
#
# Main Makefile. This is basically the same as a component makefile .
#

View 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, &reg_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");
}

View File

@@ -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: ** Theres no need to add an external pull-up resistors for SDA/SCL pin, because the driver will enable the internal pull-up resistors itself.

View File

@@ -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