Compare commits

...

23 Commits

Author SHA1 Message Date
Armando (Dou Yiwen)
5f0c8f57c6 Merge branch 'feature/add_flash_psram_config_guide' into 'master'
doc: add flash and psram configuration guide on esp32s3

Closes IDF-3949

See merge request espressif/esp-idf!15221
2021-10-26 04:34:24 +00:00
Armando
d41da2792a doc: add flash and psram configuration doc in programming guide 2021-10-26 11:49:36 +08:00
Jakob Hasse
04dc51732b Merge branch 'feature/spi_cxx' into 'master'
[cxx]: simple spi master class

Closes IDF-3750

See merge request espressif/esp-idf!13363
2021-10-26 02:44:35 +00:00
Anton Maklakov
c670e17383 Merge branch 'feature/add_esp32-h2_esp32-c6_to_ttfw' into 'master'
tools: update ttfw to support ESP32-H2 and ESP32-C6 chips

See merge request espressif/esp-idf!15443
2021-10-26 02:35:29 +00:00
Fu Hanxi
92d7e47ef6 Merge branch 'ci/enable_s3_example_test' into 'master'
ci: run example test for c3 as well

Closes IDFCI-921

See merge request espressif/esp-idf!15602
2021-10-26 00:59:56 +00:00
Anton Maklakov
d16038e181 Merge branch 'fix/enable_http2_request_example_test' into 'master'
Enable the example test for http2_request example

Closes IDF-3558

See merge request espressif/esp-idf!15380
2021-10-25 13:53:55 +00:00
Mahavir Jain
d33ce7cb59 Merge branch 'bugfix/docs_update_anti_rollback_note' into 'master'
docs: ota: fix note about security version efuse size

See merge request espressif/esp-idf!15620
2021-10-25 11:30:38 +00:00
Wei Tian Hua
23dd7a4a29 Merge branch 'bugfix/remove_assert_when_inq_done' into 'master'
component_bt: fix crash after inquiry has finished

Closes IDFGH-4967 and IDFGH-5850

See merge request espressif/esp-idf!13505
2021-10-25 10:03:28 +00:00
Mahavir Jain
cc284f1d87 docs: ota: fix note about security version efuse size 2021-10-25 14:40:43 +05:30
Harshit Malpani
47b17f6c99 Enable the example test for http2_request example 2021-10-25 16:38:08 +08:00
Wang Meng Yang
ae39bf1cfe Merge branch 'demo/controller_hci_uart_rename_and_README' into 'master'
[document] add README.md for demo controller_hci_uart_esp32

Closes BT-1619

See merge request espressif/esp-idf!15450
2021-10-25 08:09:16 +00:00
Guo Jia Cheng
a928411ab3 Merge branch 'feature/mdns-subtype' into 'master'
mdns: support service subtype

Closes IDFGH-3560

See merge request espressif/esp-idf!15467
2021-10-25 07:50:49 +00:00
Jakob Hasse
7efb01846f [cxx]: simple spi master class
* spi cxx unit test (CATCH-based, on host)
* added portmacro.h to driver mocking
* added simple testing app to write/read SPI,
  using an MPU9250
2021-10-25 14:56:59 +08:00
Mahavir Jain
c499fe5fa5 Merge branch 'add_agressive_revoke' into 'master'
secure_boot: Added Kconfig option for aggressive key revoke

See merge request espressif/esp-idf!14957
2021-10-25 04:00:20 +00:00
Jakob Hasse
912d831dfa Merge branch 'docs/update_mock_doc' into 'master'
[docs]: Updated mocking documentation

Closes IDF-3687 and IDFGH-3714

See merge request espressif/esp-idf!15493
2021-10-25 02:21:25 +00:00
Krzysztof
3247f66783 tools: update ttfw to support ESP32-H2 and ESP32-C6 chips 2021-10-22 16:29:46 +08:00
Fu Hanxi
9d2667d657 ci: run example test for c3 as well 2021-10-22 15:19:19 +08:00
Sachin Parekh
724fdbc9f1 secure_boot: Do not allow key revocation in bootloader 2021-10-22 12:20:14 +05:30
Sachin Parekh
8ff3dbc05d secure_boot: Added Kconfig option for aggressive key revoke
Applicable to S2, C3, and S3
2021-10-22 12:20:14 +05:30
Jakob Hasse
267f0bf6b2 docs: Update mocking documentation 2021-10-22 13:52:59 +08:00
xiongweichao
d5c3342a46 Remove assert when inq done
Closes https://github.com/espressif/esp-idf/issues/6759
2021-10-21 20:08:37 +08:00
Jiacheng Guo
e7e8610f56 mdns: support service subtype
* Closes https://github.com/espressif/esp-idf/issues/5508
2021-10-19 17:54:17 +08:00
jincheng
733839ab85 add README.md for demo controller_hci_uart 2021-10-19 17:07:42 +08:00
60 changed files with 2772 additions and 202 deletions

View File

@ -69,19 +69,6 @@ build:integration_test:
- build_components
- build_system
# -------------
# Special Cases
# -------------
"build:example_test-esp32c3": # esp32c3 test is only run by label, but build jobs should always be triggered
labels:
- build
- example_test
patterns:
- build_components
- build_system
- build-example_test
- example_test
####################
# Target Test Jobs #
####################
@ -121,15 +108,6 @@ build:integration_test:
- test:target_test
- test:any_test
"test:example_test-esp32c3": # For esp32c3 we trigger only with label
"labels:example_test-esp32c3":
labels:
- example_test_esp32c3
included_in:
- build:example_test-esp32c3
- build:target_test
"test:integration_test":
labels:
- "integration_test"

View File

@ -364,6 +364,20 @@ test_cxx_gpio:
- idf.py build
- build/test_gpio_cxx_host.elf
test_spi_cxx:
extends: .host_test_template
script:
- cd ${IDF_PATH}/examples/cxx/experimental/experimental_cpp_component/host_test/spi
- idf.py build
- build/test_spi_cxx_host.elf
test_system_cxx:
extends: .host_test_template
script:
- cd ${IDF_PATH}/examples/cxx/experimental/experimental_cpp_component/host_test/system
- idf.py build
- build/test_system_cxx_host.elf
test_linux_example:
extends: .host_test_template
script:

View File

@ -675,12 +675,6 @@
- <<: *if-dev-push
changes: *patterns-windows
.rules:labels:example_test-esp32c3:
rules:
- <<: *if-label-build-only
when: never
- <<: *if-label-example_test_esp32c3
.rules:labels:fuzzer_test-weekend_test:
rules:
- <<: *if-label-fuzzer_test
@ -715,6 +709,7 @@
- <<: *if-label-custom_test_esp32s3
- <<: *if-label-example_test
- <<: *if-label-example_test_esp32
- <<: *if-label-example_test_esp32c3
- <<: *if-label-example_test_esp32s2
- <<: *if-label-example_test_esp32s3
- <<: *if-label-host_test
@ -841,6 +836,18 @@
- <<: *if-dev-push
changes: *patterns-example_test
.rules:test:example_test-esp32c3:
rules:
- <<: *if-protected
- <<: *if-label-build-only
when: never
- <<: *if-label-example_test
- <<: *if-label-example_test_esp32c3
- <<: *if-dev-push
changes: *patterns-build-example_test
- <<: *if-dev-push
changes: *patterns-example_test
.rules:test:example_test-esp32s2:
rules:
- <<: *if-protected
@ -909,6 +916,7 @@
- <<: *if-label-custom_test_esp32s3
- <<: *if-label-example_test
- <<: *if-label-example_test_esp32
- <<: *if-label-example_test_esp32c3
- <<: *if-label-example_test_esp32s2
- <<: *if-label-example_test_esp32s3
- <<: *if-label-integration_test

View File

@ -75,7 +75,7 @@ test_weekend_mqtt:
.example_test_esp32c3_template:
extends:
- .example_test_template
- .rules:labels:example_test-esp32c3
- .rules:test:example_test-esp32c3
.example_test_esp32s3_template:
extends:

View File

@ -614,6 +614,22 @@ menu "Security features"
Refer to the Secure Boot section of the ESP-IDF Programmer's Guide for this version before enabling.
config SECURE_BOOT_ENABLE_AGGRESSIVE_KEY_REVOKE
bool "Enable Aggressive key revoke strategy"
depends on SECURE_BOOT && (IDF_TARGET_ESP32S2 || IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32S3)
default N
help
If this option is set, ROM bootloader will revoke the public key digest burned in efuse block
if it fails to verify the signature of software bootloader with it.
Revocation of keys does not happen when enabling secure boot. Once secure boot is enabled,
key revocation checks will be done on subsequent boot-up, while verifying the software bootloader
This feature provides a strong resistance against physical attacks on the device.
NOTE: Once a digest slot is revoked, it can never be used again to verify an image
This can lead to permanent bricking of the device, in case all keys are revoked
because of signature verification failure.
choice SECURE_BOOTLOADER_KEY_ENCODING
bool "Hardware Key Encoding"
depends on SECURE_BOOTLOADER_REFLASHABLE

View File

@ -137,10 +137,13 @@ esp_err_t esp_secure_boot_verify_rsa_signature_block(const ets_secure_boot_signa
#if SOC_EFUSE_SECURE_BOOT_KEY_DIGESTS == 1
int sb_result = ets_secure_boot_verify_signature(sig_block, image_digest, trusted.key_digests[0], verified_digest);
#else
ets_secure_boot_key_digests_t trusted_key_digests;
ets_secure_boot_key_digests_t trusted_key_digests = {0};
for (unsigned i = 0; i < SECURE_BOOT_NUM_BLOCKS; i++) {
trusted_key_digests.key_digests[i] = &trusted.key_digests[i];
}
// Key revocation happens in ROM bootloader.
// Do NOT allow key revocation while verifying application
trusted_key_digests.allow_key_revoke = false;
int sb_result = ets_secure_boot_verify_signature(sig_block, image_digest, &trusted_key_digests, verified_digest);
#endif
if (sb_result != SB_SUCCESS) {

@ -1 +1 @@
Subproject commit 3c91a5b84b9710d77e7f9dd27be461c92edfc805
Subproject commit 1c6d248b6296473a353047700922534b09d9203f

View File

@ -27,6 +27,8 @@ typedef uint32_t TickType_t;
typedef int portMUX_TYPE;
#define portTICK_PERIOD_MS ( ( TickType_t ) 1 )
#ifdef __cplusplus
}
#endif

View File

@ -1,16 +1,8 @@
// Copyright 2015-2019 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.
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
@ -28,6 +20,7 @@ typedef enum {
SPI1_HOST=0, ///< SPI1
SPI2_HOST=1, ///< SPI2
SPI3_HOST=2, ///< SPI3
SPI_HOST_MAX, ///< invalid host value
} spi_host_device_t;
/// SPI Events

View File

@ -1,16 +1,8 @@
// Copyright 2015-2016 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.
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ESP_MDNS_H_
#define ESP_MDNS_H_
@ -500,6 +492,24 @@ esp_err_t mdns_service_txt_item_remove(const char * service_type, const char * p
esp_err_t mdns_service_txt_item_remove_for_host(const char * service_type, const char * proto, const char * hostname,
const char * key);
/**
* @brief Add subtype for service.
*
* @param instance_name instance name. If NULL, will find the first service with the same service type and protocol.
* @param service_type service type (_http, _ftp, etc)
* @param proto service protocol (_tcp, _udp)
* @param hostname service hostname. If NULL, local hostname will be used.
* @param subtype The subtype to add.
*
* @return
* - ESP_OK success
* - ESP_ERR_INVALID_ARG Parameter error
* - ESP_ERR_NOT_FOUND Service not found
* - ESP_ERR_NO_MEM memory error
*/
esp_err_t mdns_service_subtype_add_for_host(const char *instance_name, const char *service_type, const char *proto,
const char *hostname, const char *subtype);
/**
* @brief Remove and free all services from mDNS server
*

View File

@ -15,6 +15,10 @@
void mdns_debug_packet(const uint8_t * data, size_t len);
#endif
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
#endif
// Internal size of IPv6 address is defined here as size of AAAA record in mdns packet
// since the ip6_addr_t is defined in lwip and depends on using IPv6 zones
#define _MDNS_SIZEOF_IP6_ADDR (MDNS_ANSWER_AAAA_SIZE)
@ -174,6 +178,24 @@ static mdns_srv_item_t * _mdns_get_service_item(const char * service, const char
return NULL;
}
static mdns_srv_item_t * _mdns_get_service_item_subtype(const char *subtype, const char * service, const char * proto)
{
mdns_srv_item_t * s = _mdns_server->services;
while (s) {
if (_mdns_service_match(s->service, service, proto, NULL)) {
mdns_subtype_t *subtype_item = s->service->subtype;
while(subtype_item) {
if (!strcasecmp(subtype_item->subtype, subtype)) {
return s;
}
subtype_item = subtype_item->next;
}
}
s = s->next;
}
return NULL;
}
static mdns_host_item_t * mdns_get_host_item(const char * hostname)
{
if (hostname == NULL || strcasecmp(hostname, _mdns_server->hostname) == 0) {
@ -605,6 +627,54 @@ static uint16_t _mdns_append_ptr_record(uint8_t * packet, uint16_t * index, cons
return record_length;
}
/**
* @brief appends PTR record for a subtype to a packet, incrementing the index
*
* @param packet MDNS packet
* @param index offset in the packet
* @param instance the service instance name
* @param subtype the service subtype
* @param proto the service protocol
* @param flush whether to set the flush flag
* @param bye whether to set the bye flag
*
* @return length of added data: 0 on error or length on success
*/
static uint16_t _mdns_append_subtype_ptr_record(uint8_t *packet, uint16_t *index, const char *instance,
const char *subtype, const char *service, const char *proto, bool flush,
bool bye)
{
const char *subtype_str[5] = {subtype, MDNS_SUB_STR, service, proto, MDNS_DEFAULT_DOMAIN};
const char *instance_str[4] = {instance, service, proto, MDNS_DEFAULT_DOMAIN};
uint16_t record_length = 0;
uint8_t part_length;
if (service == NULL) {
return 0;
}
part_length = _mdns_append_fqdn(packet, index, subtype_str, ARRAY_SIZE(subtype_str));
if (!part_length) {
return 0;
}
record_length += part_length;
part_length = _mdns_append_type(packet, index, MDNS_ANSWER_PTR, false, bye ? 0 : MDNS_ANSWER_PTR_TTL);
if (!part_length) {
return 0;
}
record_length += part_length;
uint16_t data_len_location = *index - 2;
part_length = _mdns_append_fqdn(packet, index, instance_str, ARRAY_SIZE(instance_str));
if (!part_length) {
return 0;
}
_mdns_set_u16(packet, data_len_location, part_length);
record_length += part_length;
return record_length;
}
/**
* @brief appends DNS-SD PTR record for service to a packet, incrementing the index
*
@ -1010,6 +1080,33 @@ static uint8_t _mdns_append_host_answer(uint8_t * packet, uint16_t * index, mdns
return num_records;
}
/**
* @brief Append PTR answers to packet
*
* @return number of answers added to the packet
*/
static uint8_t _mdns_append_service_ptr_answers(uint8_t *packet, uint16_t *index, mdns_service_t *service, bool flush,
bool bye)
{
uint8_t appended_answers = 0;
if (_mdns_append_ptr_record(packet, index, _mdns_get_service_instance_name(service), service->service,
service->proto, flush, bye) <= 0) {
return appended_answers;
}
mdns_subtype_t *subtype = service->subtype;
while (subtype) {
appended_answers +=
(_mdns_append_subtype_ptr_record(packet, index, _mdns_get_service_instance_name(service), subtype->subtype,
service->service, service->proto, flush, bye) > 0);
subtype = subtype->next;
}
return appended_answers;
}
/**
* @brief Append answer to packet
*
@ -1018,12 +1115,8 @@ static uint8_t _mdns_append_host_answer(uint8_t * packet, uint16_t * index, mdns
static uint8_t _mdns_append_answer(uint8_t * packet, uint16_t * index, mdns_out_answer_t * answer, mdns_if_t tcpip_if)
{
if (answer->type == MDNS_TYPE_PTR) {
if (answer->service) {
return _mdns_append_ptr_record(packet, index,
_mdns_get_service_instance_name(answer->service),
answer->service->service, answer->service->proto,
answer->flush, answer->bye) > 0;
return _mdns_append_service_ptr_answers(packet, index, answer->service, answer->flush, answer->bye);
} else {
return _mdns_append_ptr_record(packet, index,
answer->custom_instance, answer->custom_service, answer->custom_proto,
@ -1435,6 +1528,24 @@ static bool _mdns_create_answer_from_hostname(mdns_tx_packet_t * packet, const c
return true;
}
static bool _mdns_service_match_ptr_question(const mdns_service_t *service, const mdns_parsed_question_t *question)
{
if (!_mdns_service_match(service, question->service, question->proto, NULL)) {
return false;
}
if (question->sub) {
mdns_subtype_t *subtype = service->subtype;
while (subtype) {
if (!strcasecmp(subtype->subtype, question->host)) {
return true;
}
subtype = subtype->next;
}
return false;
}
return true;
}
/**
* @brief Create answer packet to questions from parsed packet
*/
@ -1466,7 +1577,7 @@ static void _mdns_create_answer_from_parsed_packet(mdns_parsed_packet_t *parsed_
} else if (q->service && q->proto) {
mdns_srv_item_t *service = _mdns_server->services;
while (service) {
if (_mdns_service_match(service->service, q->service, q->proto, NULL)) {
if (_mdns_service_match_ptr_question(service->service, q)) {
if (!_mdns_create_answer_from_service(packet, service->service, q, shared, send_flush)) {
_mdns_free_tx_packet(packet);
return;
@ -2179,6 +2290,7 @@ static mdns_service_t * _mdns_create_service(const char * service, const char *
s->instance = instance?strndup(instance, MDNS_NAME_BUF_LEN - 1):NULL;
s->txt = new_txt;
s->port = port;
s->subtype = NULL;
if (hostname) {
s->hostname = strndup(hostname, MDNS_NAME_BUF_LEN - 1);
@ -2338,7 +2450,12 @@ static void _mdns_free_service(mdns_service_t * service)
free((char *)s->value);
free(s);
}
free(service->txt);
while (service->subtype) {
mdns_subtype_t * next = service->subtype->next;
free((char *)service->subtype->subtype);
free(service->subtype);
service->subtype = next;
}
free(service);
}
@ -2701,9 +2818,12 @@ static bool _mdns_name_is_ours(mdns_name_t * name)
return false;
}
//find the service
mdns_srv_item_t * service;
if (_str_null_or_empty(name->host)) {
if (name->sub) {
service = _mdns_get_service_item_subtype(name->host, name->service, name->proto);
} else if (_str_null_or_empty(name->host)) {
service = _mdns_get_service_item(name->service, name->proto, NULL);
} else {
service = _mdns_get_service_item_instance(name->host, name->service, name->proto, NULL);
@ -2712,8 +2832,8 @@ static bool _mdns_name_is_ours(mdns_name_t * name)
return false;
}
//if host is empty and we have service, we have success
if (_str_null_or_empty(name->host)) {
//if query is PTR query and we have service, we have success
if (name->sub || _str_null_or_empty(name->host)) {
return true;
}
@ -3122,7 +3242,8 @@ void mdns_parse_packet(mdns_rx_packet_t * packet)
a = a->next;
}
continue;
} else if (name->sub || !_mdns_name_is_ours(name)) {
}
if (!_mdns_name_is_ours(name)) {
continue;
}
@ -3140,6 +3261,7 @@ void mdns_parse_packet(mdns_rx_packet_t * packet)
question->unicast = unicast;
question->type = type;
question->sub = name->sub;
if (_mdns_strdup_check(&(question->host), name->host)
|| _mdns_strdup_check(&(question->service), name->service)
|| _mdns_strdup_check(&(question->proto), name->proto)
@ -4247,6 +4369,9 @@ static void _mdns_free_action(mdns_action_t * action)
case ACTION_SERVICE_TXT_DEL:
free(action->data.srv_txt_del.key);
break;
case ACTION_SERVICE_SUBTYPE_ADD:
free(action->data.srv_subtype_add.subtype);
break;
case ACTION_SEARCH_ADD:
//fallthrough
case ACTION_SEARCH_SEND:
@ -4282,6 +4407,8 @@ static void _mdns_execute_action(mdns_action_t * action)
mdns_service_t * service;
char * key;
char * value;
char *subtype;
mdns_subtype_t *subtype_item;
mdns_txt_linked_item_t * txt, * t;
switch(action->type) {
@ -4395,6 +4522,19 @@ static void _mdns_execute_action(mdns_action_t * action)
_mdns_announce_all_pcbs(&action->data.srv_txt_set.service, 1, false);
break;
case ACTION_SERVICE_SUBTYPE_ADD:
service = action->data.srv_subtype_add.service->service;
subtype = action->data.srv_subtype_add.subtype;
subtype_item = (mdns_subtype_t *)malloc(sizeof(mdns_subtype_t));
if (!subtype_item) {
HOOK_MALLOC_FAILED;
_mdns_free_action(action);
return;
}
subtype_item->subtype = subtype;
subtype_item->next = service->subtype;
service->subtype = subtype_item;
break;
case ACTION_SERVICE_DEL:
a = _mdns_server->services;
@ -5249,6 +5389,40 @@ esp_err_t mdns_service_txt_item_remove(const char * service, const char * proto,
return mdns_service_txt_item_remove_for_host(service, proto, _mdns_server->hostname, key);
}
esp_err_t mdns_service_subtype_add_for_host(const char *instance_name, const char *service, const char *proto,
const char *hostname, const char *subtype)
{
if (!_mdns_server || !_mdns_server->services || _str_null_or_empty(service) || _str_null_or_empty(proto) ||
_str_null_or_empty(subtype)) {
return ESP_ERR_INVALID_ARG;
}
mdns_srv_item_t * s = _mdns_get_service_item_instance(instance_name, service, proto, hostname);
if (!s) {
return ESP_ERR_NOT_FOUND;
}
mdns_action_t * action = (mdns_action_t *)malloc(sizeof(mdns_action_t));
if (!action) {
HOOK_MALLOC_FAILED;
return ESP_ERR_NO_MEM;
}
action->type = ACTION_SERVICE_SUBTYPE_ADD;
action->data.srv_subtype_add.service = s;
action->data.srv_subtype_add.subtype = strdup(subtype);
if (!action->data.srv_subtype_add.subtype) {
free(action);
return ESP_ERR_NO_MEM;
}
if (xQueueSend(_mdns_server->action_queue, &action, (portTickType)0) != pdPASS) {
free(action->data.srv_subtype_add.subtype);
free(action);
return ESP_ERR_NO_MEM;
}
return ESP_OK;
}
esp_err_t mdns_service_instance_name_set_for_host(const char * service, const char * proto, const char * hostname,
const char * instance)
{

View File

@ -1,16 +1,8 @@
// Copyright 2015-2017 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.
/*
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef MDNS_PRIVATE_H_
#define MDNS_PRIVATE_H_
@ -176,6 +168,7 @@ typedef enum {
ACTION_SERVICE_TXT_REPLACE,
ACTION_SERVICE_TXT_SET,
ACTION_SERVICE_TXT_DEL,
ACTION_SERVICE_SUBTYPE_ADD,
ACTION_SERVICES_CLEAR,
ACTION_SEARCH_ADD,
ACTION_SEARCH_SEND,
@ -225,6 +218,7 @@ typedef struct {
typedef struct mdns_parsed_question_s {
struct mdns_parsed_question_s * next;
uint16_t type;
bool sub;
bool unicast;
char * host;
char * service;
@ -279,6 +273,11 @@ typedef struct mdns_txt_linked_item_s {
struct mdns_txt_linked_item_s * next; /*!< next result, or NULL for the last result in the list */
} mdns_txt_linked_item_t;
typedef struct mdns_subtype_s {
const char *subtype; /*!< subtype */
struct mdns_subtype_s * next; /*!< next result, or NULL for the last result in the list */
} mdns_subtype_t;
typedef struct {
const char * instance;
const char * service;
@ -288,6 +287,7 @@ typedef struct {
uint16_t weight;
uint16_t port;
mdns_txt_linked_item_t * txt;
mdns_subtype_t *subtype;
} mdns_service_t;
typedef struct mdns_srv_item_s {
@ -431,6 +431,10 @@ typedef struct {
mdns_srv_item_t * service;
char * key;
} srv_txt_del;
struct {
mdns_srv_item_t * service;
char * subtype;
} srv_subtype_add;
struct {
mdns_search_once_t * search;
} search_add;

View File

@ -86,7 +86,8 @@ ESP32S2_DOCS = ['hw-reference/esp32s2/**',
'api-reference/peripherals/touch_element.rst'] + FTDI_JTAG_DOCS
ESP32S3_DOCS = ['hw-reference/esp32s3/**',
'api-reference/system/ipc.rst']
'api-reference/system/ipc.rst',
'api-guides/flash_psram_config.rst']
# No JTAG docs for this one as it gets gated on SOC_USB_SERIAL_JTAG_SUPPORTED down below.
ESP32C3_DOCS = ['hw-reference/esp32c3/**']

View File

@ -0,0 +1,161 @@
SPI Flash and External SPI RAM Configuration
============================================
This page is a guide for configuring SPI Flash and external SPI RAM. Supported frequency and mode combination, error handling are also elaborated.
Terminology
-----------
============= ===========================
Term Definition
============= ===========================
**SPI** Serial Peripheral Interface
**MSPI** Memory SPI Peripheral, SPI Peripheral dedicated for memory
**SDR** Single Data Rate
**DDR** Double Data Rate
**line mode** Number of signals used to transfer data in the data phase of SPI transactions. e.g., for 4-bit-mode, the speed of the data phase would be 4 bit per clock cycle.
**FxRx** F stands for Flash, R stands for PSRAM, x stands for line mode. e.g. F4R4 stands for an {IDF_TARGET_NAME} with Quad Flash and Quad PSRAM
============= ===========================
.. note::
On {IDF_TARGET_NAME}, MSPI stands for the SPI0/1. SPI0 and SPI1 share a common SPI bus. The main Flash and PSRAM are connected to the MSPI peripheral. CPU accesses them via Cache.
.. _flash-psram-configuration:
How to configure Flash and PSRAM
--------------------------------
``idf.py menuconfig`` is used to open the configuration menu.
Configure the Flash
^^^^^^^^^^^^^^^^^^^
The Flash related configurations are under ``Serial flasher config`` menu.
1. Flash type used on the board. For Octal Flash, select :ref:`CONFIG_ESPTOOLPY_OCT_FLASH`. For Quad Flash, uncheck this configuration.
2. Flash line mode. Select a line mode in :ref:`CONFIG_ESPTOOLPY_FLASHMODE`. The higher the line mode is, the faster the SPI speed is. See terminology above about the line mode.
3. Flash sample mode. Select a sample mode in :ref:`CONFIG_ESPTOOLPY_FLASH_SAMPLE_MODE`. DDR mode is faster than SDR mode. See terminology above about SDR and DDR mode.
4. Flash speed. Select a Flash frequency in :ref:`CONFIG_ESPTOOLPY_FLASHFREQ`.
5. Flash size. Flash size, in megabytes. Select a Flash size in :ref:`CONFIG_ESPTOOLPY_FLASHSIZE`.
Configure the PSRAM
^^^^^^^^^^^^^^^^^^^
To enable PSRAM, please enable the :ref:`CONFIG_{IDF_TARGET_CFG_PREFIX}_SPIRAM_SUPPORT` under ``Component config / {IDF_TARGET_NAME}-Specific`` menu. Then all the PSRAM related configurations will be visible under ``SPI RAM config`` menu.
1. PSRAM type used on the board. Select a type in :ref:`CONFIG_SPIRAM_MODE` for Quad or Octal PSRAM.
2. PSRAM speed. Select a PSRAM frequency in :ref:`CONFIG_SPIRAM_SPEED`.
.. note::
Configuration 1 of Flash and PSRAM should be selected according to your actual hardware.
For the reset of the above configurations:
- Flash and PSRAM share the same internal clock.
- Quad Flash only supports STR mode. Octal Flash may support either/both STR/DTR modes under OPI mode, depending on the flash model and the vendor.
- Quad PSRAM only supports STR mode, while Octal PSRAM only supports DTR mode.
Therefore, some limitations should be noticed when configuring configuration 2, 3 and 4 of Flash, and configuration 2 of PSRAM. Please refer to :ref:`All Supported Modes and Speeds <flash-psram-combination>`
.. note::
If a board with Octal Flash resets before the second-stage bootloader, please refer to :ref:`Error Handling Chapter <flash-psram-error>`
.. _flash-psram-combination:
All Supported Modes and Speeds
------------------------------
.. note::
For MSPI DDR mode, the data are sampled on both the positive edge and the negative edge. e.g.: if a Flash is set to 80 MHz and DDR mode, then the final speed of the Flash is 160 MHz. This is faster than the Flash setting to 120 Mhz and STR mode.
F8R8 Hardware
^^^^^^^^^^^^^
======= =============== ======= ============
Group Flash mode Group PSRAM mode
======= =============== ======= ============
A 120 MHz SDR A N.A.
B 80 MHz DDR B 80 MHz DDR
C 80 MHz SDR C 40 MHz DDR
C 40 MHz DDR C
C < 40 MHz C
D D disable
======= =============== ======= ============
1. Flash mode in group A works with PSRAM mode in group A/D
2. Flash mode in group B/C works with PSRAM mode in group B/C/D
F4R8 Hardware
^^^^^^^^^^^^^
======= =============== ======= ============
Group Flash mode Group PSRAM mode
======= =============== ======= ============
A 120 MHz SDR A N.A.
B 80 MHz SDR B 80MHz DDR
C 40 MHz SDR C 40MHz DDR
C 20 MHz SDR C
D D disable
======= =============== ======= ============
1. Flash mode in group A works with PSRAM mode in group A/D
2. Flash mode in group B/C works with PSRAM mode in group B/C/D
F4R4 Hardware
^^^^^^^^^^^^^
====== =============== ====== ============
Type Flash Type PSRAM
====== =============== ====== ============
A 120 MHz A 120MHz
B 80 MHz B 80MHz
C 40 MHz C 40MHz
C 20 MHz C
D D disable
====== =============== ====== ============
1. Flash in A works with PSRAM in A/C/D
2. Flash in B works with PSRAM in B/C/D
3. Flash in C works with PSRAM in A/B/C/D
.. _flash-psram-error:
Error handling
--------------
1. If a board with Octal Flash resets before the second-stage bootloader:
.. code-block:: c
ESP-ROM:esp32s3-20210327
Build:Mar 27 2021
rst:0x7 (TG0WDT_SYS_RST),boot:0x18 (SPI_FAST_FLASH_BOOT)
Saved PC:0x400454d5
SPIWP:0xee
mode:DOUT, clock div:1
load:0x3fcd0108,len:0x171c
ets_loader.c 78
it may mean that the necessary efuses are not correctly burnt. please check the eFuse bits of the chip using command ``espefuse.py summary``.
The 1st bootloader relies on an eFuse bit ``FLASH_TYPE`` to reset the Flash into the default mode (SPI mode). If this bit is not burnt and the flash is working in OPI mode, 1st bootloader may not be able to read from the flash and load the following images.
Run this command to burn the eFuse bit:
.. code-block:: python
python3 ./espefuse.py -p /dev/<serial_device> --do-not-confirm burn_efuse FLASH_TYPE 1
.. note::
This step is irreversible. Please do check if your hardware is actually using an Octal Flash.

View File

@ -21,6 +21,7 @@ API Guides
:SOC_SPIRAM_SUPPORTED: External SPI-connected RAM <external-ram>
Fatal Errors <fatal-errors>
Flash Encryption <../security/flash-encryption>
:esp32s3: Flash and External SPI RAM Configuration <flash_psram_config>
FreeRTOS SMP Changes <freertos-smp>
Hardware Abstraction <hardware-abstraction>
:CONFIG_IDF_TARGET_ARCH_XTENSA: High Level Interrupts <hlinterrupts>

View File

@ -4,8 +4,8 @@ Unit Testing in {IDF_TARGET_NAME}
ESP-IDF provides the following methods to test software.
- A unit test application which runs on the target and that is based on the Unity - unit test framework. These unit tests are integrated in the ESP-IDF repository and are placed in the ``test`` subdirectories of each component respectively. Target-based unit tests are covered in this document.
- Linux-host based unit tests in which all the hardware is abstracted via mocks. Linux-host based tests are still under development and only a small fraction of IDF components supports them currently. They are covered here: :doc:`target based unit testing <linux-host-testing>`.
- Target based tests using a central unit test application which runs on the {IDF_TARGET_PATH_NAME}. These tests use the `Unity <https://www.throwtheswitch.org/unity>` unit test framework. They can be integrated into an ESP-IDF component by placing them in the component's ``test`` subdirectory. For the most part, this document is about target based tests.
- Linux-host based unit tests in which all the hardware is abstracted via mocks. Linux-host based tests are still under development and only a small fraction of IDF components support them, currently. They are covered here: :doc:`target based unit testing <linux-host-testing>`.
Normal Test Cases
------------------
@ -98,7 +98,7 @@ Once the signal is sent from DUT2, you need to press "Enter" on DUT1, then DUT1
Multi-stage Test Cases
-----------------------
The normal test cases are expected to finish without reset (or only need to check if reset happens). Sometimes we expect to run some specific tests after certain kinds of reset. For example, we expect to test if the reset reason is correct after a wakeup from deep sleep. We need to create a deep-sleep reset first and then check the reset reason. To support this, we can define multi-stage test cases, to group a set of test functions::
The normal test cases are expected to finish without reset (or only need to check if reset happens). Sometimes we expect to run some specific tests after certain kinds of reset. For example, we want to test if the reset reason is correct after a wake up from deep sleep. We need to create a deep-sleep reset first and then check the reset reason. To support this, we can define multi-stage test cases, to group a set of test functions::
static void trigger_deepsleep(void)
{
@ -121,7 +121,7 @@ Tests For Different Targets
Some tests (especially those related to hardware) cannot run on all targets. Below is a guide how to make your unit tests run on only specified targets.
1. Wrap your test code by ``!(TEMPORARY_)DISABLED_FOR_TARGETS()`` macros and place them either in the original test file, or sepeprate the code into files grouped by functions, but make sure all these files will be processed by the compiler. E.g.::
1. Wrap your test code by ``!(TEMPORARY_)DISABLED_FOR_TARGETS()`` macros and place them either in the original test file, or separate the code into files grouped by functions, but make sure all these files will be processed by the compiler. E.g.::
#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32, ESP8266)
TEST_CASE("a test that is not ready for esp32 and esp8266 yet", "[]")
@ -168,11 +168,11 @@ Change into ``tools/unit-test-app`` directory to configure and build it:
* ``idf.py menuconfig`` - configure unit test app.
* ``idf.py -T all build`` - build unit test app with tests for each component having tests in the ``test`` subdirectory.
* ``idf.py -T "xxx yyy" build`` - build unit test app with tests for some space-separated specific components (For instance: ``idf.py -T heap build`` - build unit tests only for ``heap`` component directory).
* ``idf.py -T all -E "xxx yyy" build`` - build unit test app with all unit tests, except for unit tests of some components (For instance: ``idf.py -T all -E "ulp mbedtls" build`` - build all unit tests exludes ``ulp`` and ``mbedtls`` components).
* ``idf.py -T all -E "xxx yyy" build`` - build unit test app with all unit tests, except for unit tests of some components (For instance: ``idf.py -T all -E "ulp mbedtls" build`` - build all unit tests excludes ``ulp`` and ``mbedtls`` components).
.. note::
Due to inherent limitations of Windows command prompt, following syntax has to be used in order to build unit-test-app with multiple components: ``idf.py -T xxx -T yyy build`` or with escaped quoates: ``idf.py -T \`"xxx yyy\`" build`` in PowerShell or ``idf.py -T \^"ssd1306 hts221\^" build`` in Windows command prompt.
Due to inherent limitations of Windows command prompt, following syntax has to be used in order to build unit-test-app with multiple components: ``idf.py -T xxx -T yyy build`` or with escaped quotes: ``idf.py -T \`"xxx yyy\`" build`` in PowerShell or ``idf.py -T \^"ssd1306 hts221\^" build`` in Windows command prompt.
When the build finishes, it will print instructions for flashing the chip. You can simply run ``idf.py flash`` to flash all build output.
@ -257,7 +257,7 @@ However, if the instruction or data is not in cache, it needs to be fetched from
Code and data placements can vary between builds, and some arrangements may be more favorable with regards to cache access (i.e., minimizing cache misses). This can technically affect execution speed, however these factors are usually irrelevant as their effect 'average out' over the device's operation.
The effect of the cache on execution speed, however, can be relevant in benchmarking scenarios (espcially microbenchmarks). There might be some variability in measured time between runs and between different builds. A technique for eliminating for some of the variability is to place code and data in instruction or data RAM (IRAM/DRAM), respectively. The CPU can access IRAM and DRAM directly, eliminating the cache out of the equation. However, this might not always be viable as the size of IRAM and DRAM is limited.
The effect of the cache on execution speed, however, can be relevant in benchmarking scenarios (especially micro benchmarks). There might be some variability in measured time between runs and between different builds. A technique for eliminating for some of the variability is to place code and data in instruction or data RAM (IRAM/DRAM), respectively. The CPU can access IRAM and DRAM directly, eliminating the cache out of the equation. However, this might not always be viable as the size of IRAM and DRAM is limited.
The cache compensated timer is an alternative to placing the code/data to be benchmarked in IRAM/DRAM. This timer uses the processor's internal event counters in order to determine the amount of time spent on waiting for code/data in case of a cache miss, then subtract that from the recorded wall time.
@ -279,46 +279,80 @@ One limitation of the cache compensated timer is that the task that benchmarked
Mocks
-----
One of the biggest problems for unit testing in embedded systems are the strong hardware dependencies. This is why ESP-IDF has a component which integrates the `CMock <https://www.throwtheswitch.org/cmock>`_ mocking framework. Ideally, all components other than the one which should be tested *(component under test)* are mocked. This way, the test environment has complete control over all the interaction with the component under test. However, if mocking becomes problematic due to the tests becoming too specific, more "real" IDF code can always be included into the tests.
.. note::
Currently, mocking is only possible with some selected components when running on the Linux host. In the future, we plan to make essential components in IDF mockable. This will also include mocking when running on the {IDF_TARGET_NAME}.
Besides the usual IDF requirements, ``ruby`` is necessary to generate the mocks. Refer to :component_file:`cmock/CMock/docs/CMock_Summary.md` for more details on how CMock works and how to create and use mocks.
One of the biggest problems regarding unit testing of embedded systems are the strong hardware dependencies. Running unit tests directly on the {IDF_TARGET_NAME} can be especially difficult for higher layer components for the following reasons:
In IDF, adjustments are necessary inside the component(s) that should be mocked as well as inside the unit test, compared to writing normal components or unit tests without mocking.
- Decreased test reliability due to lower layer components and/or hardware setup.
- Increased difficulty in testing edge cases due to limitations of lower layer components and/or hardware setup
- Increased difficulty in identifying the root cause due to the large number of dependencies influencing the behavior
Adjustments in Mock Component
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When testing a particular component, (i.e., the component under test), software mocking allows the dependencies of the component under test to be substituted (i.e., mocked) entirely in software. To allow software mocking, ESP-IDF integrates the `CMock <https://www.throwtheswitch.org/cmock>`_ mocking framework as a component. With the addition of some CMake functions in the ESP-IDF's build system, it is possible to conveniently mock the entirety (or a part of) an IDF component.
The component that should be mocked requires a separate ``mock`` directory containing all additional files needed specifically for the mocking. Most importantly, it contains ``mock_config.yaml`` which configures CMock. For more details on what the options inside that configuration file mean and how to write your own, please take a look at the :component_file:`CMock documentation <cmock/CMock/docs/CMock_Summary.md>`. It may be necessary to have some more files related to mocking which should also be placed inside the `mock` directory.
Ideally, all components that the component under test is dependent on should be mocked, thus allowing the test environment complete control over all interactions with the component under test. However, if mocking all dependent components becomes too complex or too tedious (e.g. because you need to mock too many function calls) you have the following options:
Furthermore, the component's ``CMakeLists.txt`` needs a switch to build mocks instead of the actual code. This is usually done by checking the component property ``USE_MOCK`` for the particular component. E.g., the ``spi_flash`` component execute the following code in its ``CMakeLists.txt`` to check whether mocks should be built:
.. list::
- Include more "real" IDF code in the tests. This may work but increases the dependency on the "real" code's behavior. Furthermore, once a test fails, you may not know if the failure is in your actual code under tests or the "real" IDF code.
- Re-evaluate the design of the code under test and attempt to reduce its dependencies by dividing the code under test into more manageable components. This may seem burdensome but it is common knowledge that unit tests often expose software design weaknesses. Fixing design weaknesses will not only help with unit testing in the short term, but will help future code maintenance as well.
.. code-block:: cmake
Refer to :component_file:`cmock/CMock/docs/CMock_Summary.md` for more details on how CMock works and how to create and use mocks.
idf_component_get_property(spi_flash_mock ${COMPONENT_NAME} USE_MOCK)
Requirements
^^^^^^^^^^^^
The following requirements are necessary to generate the mocks:
An example CMake build command to create mocks of a component inside its ``CMakeLists.txt`` may look like this:
.. list::
- Installed ESP-IDF with all its requirements
- ``ruby``
- On the Linux target, which is the only target where mocking currently works, ``libbsd`` is required, too
.. code-block:: cmake
Mock a Component
^^^^^^^^^^^^^^^^
add_custom_command(
OUTPUT ${MOCK_OUTPUT}
COMMAND ruby ${CMOCK_DIR}/lib/cmock.rb -o${CMAKE_CURRENT_SOURCE_DIR}/mock/mock_config.yaml ${MOCK_HEADERS}
COMMAND ${CMAKE_COMMAND} -E env "UNITY_DIR=${IDF_PATH}/components/unity/unity" ruby ${CMOCK_DIR}/lib/cmock.rb -o${CMAKE_CURRENT_SOURCE_DIR}/mock/mock_config.yaml ${MOCK_HEADERS}
)
To create a mock version of a component, called a *component mock*, the component needs to be overwritten in a particular way. Overriding a component entails creating a component with the exact same name as the original component, then let the build system discover it later than the original component (see `Multiple components with the same name <cmake-components-same-name>` for more details).
``${MOCK_OUTPUT}`` contains all CMock generated output files, ``${MOCK_HEADERS}`` contains all headers to be mocked and ``${CMOCK_DIR}`` needs to be set to the CMock directory inside IDF. ``${CMAKE_COMMAND}`` is automatically set by the IDF build system.
In the component mock, the following parts are specified:
One aspect of CMock's usage is special here: CMock usually uses Unity as a submodule, but due to some Espressif-internal limitations with CI, IDF still uses Unity as an ordinary module in ESP-IDF. To use the IDF-supplied Unity component, which isn't a submodule, the build system needs to pass an environment variable ``UNITY_IDR`` to CMock. This variable simply contains the path to the Unity directory in IDF, e.g. ``export "UNITY_DIR=${IDF_PATH}/components/unity/unity"``. Refer to :component_file:`cmock/CMock/lib/cmock_generator.rb` to see how the Unity directory is determined in CMock.
.. list::
- The headers providing the functions to generate mocks for
- Include paths of the aforementioned headers
- Dependencies of the mock component (this is necessary e.g. if the headers include files from other components)
An example ``CMakeLists.txt`` which enables mocking exists :component_file:`in spi_flash <spi_flash/CMakeLists.txt>`
All these parts have to be specified using the IDF build system function ``idf_component_mock``. You can use the IDF build system function ``idf_component_get_property`` with the tag ``COMPONENT_OVERRIDEN_DIR`` to access the component directory of the original component and then register the mock component parts using ``idf_component_mock``:
.. code:: none
idf_component_get_property(original_component_dir <original-component-name> COMPONENT_OVERRIDEN_DIR)
...
idf_component_mock(INCLUDE_DIRS "${original_component_dir}/include"
REQUIRES freertos
MOCK_HEADER_FILES ${original_component_dir}/include/header_containing_functions_to_mock.h)
The component mock also requires a separate ``mock`` directory containing a ``mock_config.yaml`` file that configures CMock. A simple ``mock_config.yaml`` could look like this:
.. code-block:: yaml
:cmock:
:plugins:
- expect
- expect_any_args
For more details about the CMock configuration yaml file, have a look at :component_file:`cmock/CMock/docs/CMock_Summary.md`.
Note that the component mock does not have to mock the original component in its entirety. As long as the test project's dependencies and dependencies of other code to the original components are satisfied by the component mock, partial mocking is adequate. In fact, most of the component mocks in IDF in ``tools/mocks`` are only partially mocking the original component.
Examples of component mocks can be found under :idf:`tools/mocks` in the IDF directory. General information on how to *override an IDF component* can be found under the section "Multiple components with the same name" in the :doc:`IDF build system documentation`</api-guides/build-system>`.
Adjustments in Unit Test
^^^^^^^^^^^^^^^^^^^^^^^^
The unit test needs to set the component property ``USE_MOCK`` for the component that should be mocked. This lets the dependent component build the mocks instead of the actual component. E.g., in the nvs host test's :component_file:`CMakeLists.txt <nvs_flash/host_test/nvs_page_test/CMakeLists.txt>`, ``spi_flash`` mocks are enabled by the following line:
The unit test needs to inform the cmake build system to mock dependent components (i.e., it needs to override the original component with the mock component). This is done by either placing the component mock into the project's ``components`` directory or adding the mock component's directory using the following line in the project's root ``CMakeLists.txt``:
.. code-block:: cmake
.. code:: cmake
idf_component_set_property(spi_flash USE_MOCK 1)
list(APPEND EXTRA_COMPONENT_DIRS "<mock_component_dir>")
Refer to the :component_file:`NVS host unit test <nvs_flash/host_test/nvs_page_test/README.md>` for more information on how to use and control CMock inside a unit test.
Both methods will override existing components in ESP-IDF with the component mock. The latter is particularly convenient if you use component mocks that are already supplied by IDF.
Users should refer to the ``esp_event`` host-based unit test and its :component_file:`esp_event/host_test/esp_event_unit_test/CMakeLists.txt` as an example of a component mock.

View File

@ -29,7 +29,7 @@ Support for features of flash chips
Flash features of different vendors are operated in different ways and need special support. The fast/slow read and Dual mode (DOUT/DIO) of almost all 24-bits address flash chips are supported, because they don't need any vendor-specific commands.
The Quad mode (QIO/QOUT) the following chip types are supported:
Quad mode (QIO/QOUT) is supported on following chip types:
1. ISSI
2. GD
@ -39,6 +39,14 @@ The Quad mode (QIO/QOUT) the following chip types are supported:
6. XMC
7. BOYA
.. only:: esp32s3
Octal mode (OPI) are supported on following chip types:
1. MXIC
To know how to configure menuconfig for a board with different Flash and PSRAM, please refer to the :ref:`SPI Flash and External SPI RAM Configuration <flash-psram-configuration>`
The 32-bit address range of following chip type is supported:
1. W25Q256
@ -151,7 +159,7 @@ Differences between :cpp:func:`spi_flash_mmap` and :cpp:func:`esp_partition_mmap
Note that since memory mapping happens in pages, it may be possible to read data outside of the partition provided to ``esp_partition_mmap``, regardless of the partition boundary.
.. note::
.. note::
mmap is supported by cache, so it can only be used on main flash.
SPI Flash Implementation
@ -248,5 +256,3 @@ API Reference - Flash Encrypt
-----------------------------
.. include-build-file:: inc/esp_flash_encrypt.inc

View File

@ -165,9 +165,13 @@ If you want to avoid the download/erase overhead in case of the app from the ser
Restrictions:
- The number of bits in the ``secure_version`` field is limited to 32 bits. This means that only 32 times you can do an anti-rollback. You can reduce the length of this efuse field use :ref:`CONFIG_BOOTLOADER_APP_SEC_VER_SIZE_EFUSE_FIELD` option.
- Anti-rollback only works if the encoding scheme for efuse is set to ``NONE``.
- The partition table should not have a factory partition, only two of the app.
.. list::
:esp32: - The number of bits in the ``secure_version`` field is limited to 32 bits. This means that only 32 times you can do an anti-rollback. You can reduce the length of this efuse field using :ref:`CONFIG_BOOTLOADER_APP_SEC_VER_SIZE_EFUSE_FIELD` option.
:not esp32: - The number of bits in the ``secure_version`` field is limited to 16 bits. This means that only 16 times you can do an anti-rollback. You can reduce the length of this efuse field using :ref:`CONFIG_BOOTLOADER_APP_SEC_VER_SIZE_EFUSE_FIELD` option.
:esp32: - Anti-rollback works only if the encoding scheme for efuse is set to ``NONE``.
- Factory partition is not supported in anti rollback scheme and hence partition table should not have partition with SubType set to ``factory``.
- Test partition is not supported in anti rollback scheme and hence partition table should not have partition with SubType set to ``test``.
``security_version``:

View File

@ -774,6 +774,12 @@ To exit IDF monitor use the shortcut ``Ctrl+]``.
idf.py -p PORT flash monitor
.. only:: esp32s3
.. note::
If a board with Octal Flash resets before the second-stage bootloader, please refer to :ref:`Octal Flash Error Handling <flash-psram-error>`
See also:
- :doc:`IDF Monitor <../api-guides/tools/idf-monitor>` for handy shortcuts and more details on using IDF monitor.

View File

@ -330,6 +330,9 @@ Secure Boot Best Practices
* Applications should be signed with only one key at a time, to minimize the exposure of unused private keys.
* The bootloader can be signed with multiple keys from the factory.
Conservative approach:
~~~~~~~~~~~~~~~~~~~~~~
Assuming a trusted private key (N-1) has been compromised, to update to new key pair (N).
1. Server sends an OTA update with an application signed with the new private key (#N).
@ -342,6 +345,17 @@ Secure Boot Best Practices
* A similar approach can also be used to physically re-flash with a new key. For physical re-flashing, the bootloader content can also be changed at the same time.
Aggressive approach:
~~~~~~~~~~~~~~~~~~~~
ROM code has an additional feature of revoking a public key digest if the signature verification fails.
To enable this feature, you need to burn SECURE_BOOT_AGGRESSIVE_REVOKE efuse or enable :ref:`CONFIG_SECURE_BOOT_ENABLE_AGGRESSIVE_KEY_REVOKE`
Key revocation is not applicable unless secure boot is successfully enabled. Also, a key is not revoked in case of invalid signature block or invalid image digest, it is only revoked in case the signature verification fails, i.e. revoke key only if failure in step 3 of :ref:`verify_image`
Once a key is revoked, it can never be used for verfying a signature of an image. This feature provides strong resistance against physical attacks on the device. However, this could also brick the device permanently if all the keys are revoked because of signature verification failure.
.. _secure-boot-v2-technical-details:
Technical Details

View File

@ -0,0 +1 @@
.. include:: ../../en/api-guides/flash_psram_config.rst

View File

@ -21,6 +21,7 @@ API 指南
:SOC_SPIRAM_SUPPORTED: 片外 SPI RAM <external-ram>
严重错误 <fatal-errors>
Flash 加密 <../security/flash-encryption>
:esp32s3: Flash and External SPI RAM Configuration <flash_psram_config>
FreeRTOS SMP 变化 <freertos-smp>
硬件抽象层 <hardware-abstraction>
:CONFIG_IDF_TARGET_ARCH_XTENSA: 高层中断 <hlinterrupts>

View File

@ -5,6 +5,61 @@ ESP-IDF UART HCI Controller
===========================
This is a btdm controller use UART as HCI IO. This require the UART device support RTS/CTS mandatory.
It can do the configuration of UART number and UART baudrate by menuconfig.
## How to use example
### Hardware Required
This example should be able to run on any commonly available ESP32 development board. To connect UART to PC, another board such as ESP_Test Board or FT232 USB UART board is usually needed.
In this example, three UARTs are used:
- UART0 is used as normal output or by IDF monitor
- UART1 or UART2 is used to convey HCI messages
RTS and CTS lines of UART1 or UART2 are required. Pins 5, 18, 23, 19 are used as TxD, RxD, CTS, RTS PINs of UART1 or UART2, respectively.
In a frequently-used scenario, if ESP_Test Board is used, connect the TX0, RX0, RTS0, CTS0 and GND of ESP_Test Board to ESP32 UART1 or UART2 PINs, and Attach ESP_Test board to the host PC.
### Configure the project
```
idf.py menuconfig
```
- UART number can be configured in `HCI UART(H4) Options > UART Number for HCI`
- UART baudrate can be configured in `HCI UART(H4) Options > UART Baudrate for HCI`
### Build and Flash
Build the project and flash it to the board, then run monitor tool to view serial output:
```
idf.py -p PORT flash monitor
```
(Replace PORT with the name of the serial port to use.)
(To exit the serial monitor, type ``Ctrl-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
The example resets the HCI UART transport and enable Bluetooth Controller.
```
I (442) CONTROLLER_UART_HCI: HCI UART1 Pin select: TX 5, RX 18, CTS 23, RTS 19
I (442) BTDM_INIT: BT controller compile version [078d492]
I (452) system_api: Base MAC address is not set
I (452) system_api: read default base MAC address from EFUSE
I (462) phy_init: phy_version 4670,719f9f6,Feb 18 2021,17:07:07
```
After these output occurs, HCI messages can be commnunicated over UART1 or UART2. (UART1 in this example)
## Troubleshooting

View File

@ -1,10 +1,8 @@
/*
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.
*/
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
@ -27,7 +25,7 @@ static void uart_gpio_reset(void)
periph_module_enable(PERIPH_UHCI0_MODULE);
#ifdef CONFIG_BTDM_CTRL_HCI_UART_NO
ESP_LOGI(tag, "HCI UART%d Pin select: TX 5, RX, 18, CTS 23, RTS 19", CONFIG_BTDM_CTRL_HCI_UART_NO);
ESP_LOGI(tag, "HCI UART%d Pin select: TX 5, RX 18, CTS 23, RTS 19", CONFIG_BTDM_CTRL_HCI_UART_NO);
uart_set_pin(CONFIG_BTDM_CTRL_HCI_UART_NO, 5, 18, 19, 23);
#endif

View File

@ -1,6 +1,6 @@
idf_build_get_property(target IDF_TARGET)
set(srcs "esp_timer_cxx.cpp" "esp_exception.cpp" "gpio_cxx.cpp")
set(srcs "esp_timer_cxx.cpp" "esp_exception.cpp" "gpio_cxx.cpp" "spi_cxx.cpp" "spi_host_cxx.cpp")
set(requires "esp_timer" "driver")
if(NOT ${target} STREQUAL "linux")
@ -13,4 +13,6 @@ endif()
idf_component_register(SRCS ${srcs}
INCLUDE_DIRS "include"
PRIV_INCLUDE_DIRS "private_include"
PRIV_REQUIRES freertos
REQUIRES ${requires})

View File

@ -1,3 +1,3 @@
COMPONENT_ADD_INCLUDEDIRS := include
COMPONENT_ADD_INCLUDEDIRS := include private_include
COMPONENT_SRCDIRS := ./ driver

View File

@ -1,21 +1,23 @@
// 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.
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0
*
* 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 "catch.hpp"
#include "gpio_cxx.hpp"
#include "driver/spi_master.h"
#include "spi_cxx.hpp"
extern "C" {
#include "Mockgpio.h"
#include "Mockspi_master.h"
#include "Mockspi_common.h"
}
static const idf::GPIONum VALID_GPIO(18);
@ -45,7 +47,7 @@ public:
* 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.
* that uses generated CMock mock functions.
* This is necessary because CMock uses longjmp which screws up C++ stacks and
* also the CATCH mechanisms.
*
@ -61,18 +63,236 @@ public:
} \
while (0)
struct GPIOFixture {
GPIOFixture(idf::GPIONum gpio_num = idf::GPIONum(18), gpio_mode_t mode = GPIO_MODE_OUTPUT) : num(gpio_num)
struct CMockFixture {
CMockFixture()
{
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()
~CMockFixture()
{
// Verify that all expected methods have been called.
Mockgpio_Verify();
Mockspi_master_Verify();
Mockspi_common_Verify();
}
};
struct GPIOFixture : public CMockFixture {
GPIOFixture(idf::GPIONum gpio_num = idf::GPIONum(18), gpio_mode_t mode = GPIO_MODE_OUTPUT)
: CMockFixture(), num(gpio_num)
{
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);
}
idf::GPIONum num;
};
struct SPIFix;
struct SPIDevFix;
struct SPITransactionDescriptorFix;
struct SPITransactionTimeoutFix;
struct SPITransactionFix;
static SPIFix *g_fixture;
static SPIDevFix *g_dev_fixture;
static SPITransactionDescriptorFix *g_trans_desc_fixture;
static SPITransactionTimeoutFix *g_trans_timeout_fixture;
static SPITransactionFix *g_trans_fixture;
struct SPIFix : public CMockFixture {
SPIFix(spi_host_device_t host_id = spi_host_device_t(1),
uint32_t mosi = 1,
uint32_t miso = 2,
uint32_t sclk = 3) : CMockFixture(), bus_config() {
bus_config.mosi_io_num = mosi;
bus_config.miso_io_num = miso;
bus_config.sclk_io_num = sclk;
bus_config.quadwp_io_num = -1;
bus_config.quadhd_io_num = -1;
spi_bus_initialize_ExpectWithArrayAndReturn(host_id, &bus_config, 1, spi_common_dma_t::SPI_DMA_CH_AUTO, ESP_OK);
spi_bus_free_ExpectAnyArgsAndReturn(ESP_OK);
g_fixture = this;
}
~SPIFix() {
g_fixture = nullptr;
}
spi_bus_config_t bus_config;
};
struct QSPIFix : public SPIFix {
QSPIFix(spi_host_device_t host_id = spi_host_device_t(1),
uint32_t mosi = 1,
uint32_t miso = 2,
uint32_t sclk = 3,
uint32_t wp = 4,
uint32_t hd = 5) : SPIFix(host_id, mosi, miso, sclk)
{
bus_config.quadwp_io_num = wp;
bus_config.quadhd_io_num = hd;
}
};
enum class CreateAnd {
FAIL,
SUCCEED,
IGNORE
};
struct SPIDevFix {
SPIDevFix(CreateAnd flags)
: dev_handle(reinterpret_cast<spi_device_handle_t>(47)),
dev_config()
{
dev_config.spics_io_num = 4;
if (flags == CreateAnd::FAIL) {
spi_bus_add_device_ExpectAnyArgsAndReturn(ESP_FAIL);
} else if (flags == CreateAnd::IGNORE) {
spi_bus_add_device_IgnoreAndReturn(ESP_OK);
spi_bus_remove_device_IgnoreAndReturn(ESP_OK);
} else {
spi_bus_add_device_AddCallback(add_dev_cb);
spi_bus_add_device_ExpectAnyArgsAndReturn(ESP_OK);
spi_bus_remove_device_ExpectAndReturn(dev_handle, ESP_OK);
}
g_dev_fixture = this;
}
~SPIDevFix()
{
spi_bus_add_device_AddCallback(nullptr);
g_dev_fixture = nullptr;
}
spi_device_handle_t dev_handle;
spi_device_interface_config_t dev_config;
static esp_err_t add_dev_cb(spi_host_device_t host_id,
const spi_device_interface_config_t* dev_config,
spi_device_handle_t* handle,
int cmock_num_calls)
{
SPIDevFix *fix = static_cast<SPIDevFix*>(g_dev_fixture);
*handle = fix->dev_handle;
fix->dev_config = *dev_config;
return ESP_OK;
}
};
struct SPITransactionFix {
SPITransactionFix(esp_err_t get_trans_return = ESP_OK) : get_transaction_return(get_trans_return)
{
spi_device_queue_trans_AddCallback(queue_trans_cb);
spi_device_get_trans_result_AddCallback(get_trans_result_cb);
spi_device_queue_trans_ExpectAnyArgsAndReturn(ESP_OK);
spi_device_get_trans_result_ExpectAnyArgsAndReturn(get_trans_return);
g_trans_fixture = this;
}
~SPITransactionFix()
{
spi_device_get_trans_result_AddCallback(nullptr);
spi_device_queue_trans_AddCallback(nullptr);
g_trans_fixture = nullptr;
}
static esp_err_t queue_trans_cb(spi_device_handle_t handle,
spi_transaction_t* trans_desc,
TickType_t ticks_to_wait,
int cmock_num_calls)
{
SPITransactionFix *fix = static_cast<SPITransactionFix*> (g_trans_fixture);
fix->orig_trans = trans_desc;
return ESP_OK;
}
static esp_err_t get_trans_result_cb(spi_device_handle_t handle,
spi_transaction_t** trans_desc,
TickType_t ticks_to_wait,
int cmock_num_calls)
{
SPITransactionFix *fix = static_cast<SPITransactionFix*> (g_trans_fixture);
*trans_desc = fix->orig_trans;
return fix->get_transaction_return;
}
esp_err_t get_transaction_return;
spi_transaction_t *orig_trans;
};
struct SPITransactionDescriptorFix {
SPITransactionDescriptorFix(size_t size = 1, bool ignore_handle = false, TickType_t wait_time = portMAX_DELAY)
: size(size), handle(reinterpret_cast<spi_device_handle_t>(0x01020304))
{
spi_device_queue_trans_AddCallback(queue_trans_cb);
spi_device_get_trans_result_AddCallback(get_trans_result_cb);
spi_device_acquire_bus_ExpectAndReturn(handle, portMAX_DELAY, ESP_OK);
if (ignore_handle) {
spi_device_acquire_bus_IgnoreArg_device();
}
spi_device_queue_trans_ExpectAndReturn(handle, nullptr, 0, ESP_OK);
spi_device_queue_trans_IgnoreArg_trans_desc();
if (ignore_handle) {
spi_device_queue_trans_IgnoreArg_handle();
}
spi_device_get_trans_result_ExpectAndReturn(handle, nullptr, wait_time, ESP_OK);
spi_device_get_trans_result_IgnoreArg_trans_desc();
if (ignore_handle) {
spi_device_get_trans_result_IgnoreArg_handle();
}
spi_device_release_bus_ExpectAnyArgs();
g_trans_desc_fixture = this;
}
~SPITransactionDescriptorFix()
{
spi_device_get_trans_result_AddCallback(nullptr);
spi_device_queue_trans_AddCallback(nullptr);
g_trans_desc_fixture = nullptr;
}
static esp_err_t queue_trans_cb(spi_device_handle_t handle,
spi_transaction_t* trans_desc,
TickType_t ticks_to_wait,
int cmock_num_calls)
{
SPITransactionDescriptorFix *fix = static_cast<SPITransactionDescriptorFix*> (g_trans_desc_fixture);
fix->orig_trans = trans_desc;
return ESP_OK;
}
static esp_err_t get_trans_result_cb(spi_device_handle_t handle,
spi_transaction_t** trans_desc,
TickType_t ticks_to_wait,
int cmock_num_calls)
{
SPITransactionDescriptorFix *fix = static_cast<SPITransactionDescriptorFix*> (g_trans_desc_fixture);
for (int i = 0; i < fix->size; i++) {
static_cast<uint8_t*>(fix->orig_trans->rx_buffer)[i] = fix->rx_data[i];
}
*trans_desc = fix->orig_trans;
return ESP_OK;
}
size_t size;
spi_transaction_t *orig_trans;
spi_device_handle_t handle;
std::vector<uint8_t> tx_data;
std::vector<uint8_t> rx_data;
};

View File

@ -10,6 +10,7 @@
#include <stdio.h>
#include "esp_err.h"
#include "unity.h"
#include "freertos/portmacro.h"
#include "gpio_cxx.hpp"
#include "test_fixtures.hpp"

View File

@ -0,0 +1,16 @@
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/")
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/esp_timer/")
# 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_spi_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/host_spi_cxx_test.elf`

View File

@ -0,0 +1,9 @@
idf_component_get_property(cpp_component experimental_cpp_component COMPONENT_DIR)
idf_component_register(SRCS "spi_cxx_test.cpp"
INCLUDE_DIRS
"."
"${cpp_component}/host_test/fixtures"
"${cpp_component}/private_include"
$ENV{IDF_PATH}/tools/catch
REQUIRES cmock driver experimental_cpp_component)

View File

@ -0,0 +1,452 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0
*
* This test 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 "unity.h"
#include "freertos/portmacro.h"
#include "spi_host_cxx.hpp"
#include "spi_host_private_cxx.hpp"
#include "system_cxx.hpp"
#include "test_fixtures.hpp"
#include "catch.hpp"
// TODO: IDF-2693, function definition just to satisfy linker, mock esp_common instead
const char *esp_err_to_name(esp_err_t code) {
return "host_test error";
}
using namespace std;
using namespace idf;
TEST_CASE("SPITransferSize basic construction")
{
SPITransferSize transfer_size_0(0);
CHECK(0 == transfer_size_0.get_value());
SPITransferSize transfer_size_1(47);
CHECK(47 == transfer_size_1.get_value());
SPITransferSize transfer_size_default = SPITransferSize::default_size();
CHECK(0 == transfer_size_default.get_value());
}
TEST_CASE("SPI gpio numbers work correctly")
{
GPIONum gpio_num_0(19);
MOSI mosi_0(18);
MOSI mosi_1(gpio_num_0.get_num());
MOSI mosi_2(mosi_0);
CHECK(mosi_0 != mosi_1);
CHECK(mosi_2 == mosi_0);
CHECK(mosi_2.get_num() == 18u);
}
TEST_CASE("SPI_DMAConfig valid")
{
CHECK(SPI_DMAConfig::AUTO().get_num() == spi_common_dma_t::SPI_DMA_CH_AUTO);
CHECK(SPI_DMAConfig::DISABLED().get_num() == spi_common_dma_t::SPI_DMA_DISABLED);
}
TEST_CASE("SPINum invalid argument")
{
CHECK_THROWS_AS(SPINum(-1), SPIException&);
uint32_t host_raw = spi_host_device_t::SPI_HOST_MAX;
CHECK_THROWS_AS(SPINum host(host_raw), SPIException&);
}
TEST_CASE("Master init failure")
{
CMockFixture cmock_fix;
spi_bus_initialize_ExpectAnyArgsAndReturn(ESP_FAIL);
CHECK_THROWS_AS(SPIMaster master(SPINum(SPI2_HOST), MOSI(1), MISO(2), SCLK(3)), SPIException&);
}
TEST_CASE("Master invalid state")
{
CMockFixture cmock_fix;
spi_bus_initialize_ExpectAnyArgsAndReturn(ESP_ERR_INVALID_STATE);
CHECK_THROWS_AS(SPIMaster master(SPINum(SPI2_HOST), MOSI(1), MISO(2), SCLK(3)), SPIException&);
}
TEST_CASE("build master")
{
SPIFix fix;
SPIMaster master(SPINum(SPI2_HOST),
MOSI(fix.bus_config.mosi_io_num),
MISO(fix.bus_config.miso_io_num),
SCLK(fix.bus_config.sclk_io_num));
}
TEST_CASE("build QSPI master")
{
QSPIFix fix;
SPIMaster master(SPINum(SPI2_HOST),
MOSI(fix.bus_config.mosi_io_num),
MISO(fix.bus_config.miso_io_num),
SCLK(fix.bus_config.sclk_io_num),
QSPIWP(fix.bus_config.quadwp_io_num),
QSPIHD(fix.bus_config.quadhd_io_num));
}
TEST_CASE("Master build device")
{
SPIFix fix;
SPIDevFix dev_fix(CreateAnd::SUCCEED);
SPIMaster master(SPINum(SPI2_HOST),
MOSI(fix.bus_config.mosi_io_num),
MISO(fix.bus_config.miso_io_num),
SCLK(fix.bus_config.sclk_io_num));
master.create_dev(CS(4), Frequency::MHz(1));
}
TEST_CASE("SPIDeviceHandle throws on driver error")
{
CMockFixture cmock_fix;
SPIDevFix dev_fix(CreateAnd::FAIL);
CHECK_THROWS_AS(SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10)), SPIException&);
}
TEST_CASE("SPIDeviceHandle succeed")
{
CMockFixture cmock_fix;
SPIDevFix dev_fix(CreateAnd::SUCCEED);
SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
}
TEST_CASE("SPIDevice succeed")
{
CMockFixture cmock_fix;
SPIDevFix dev_fix(CreateAnd::SUCCEED);
SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
}
TEST_CASE("SPI transaction empty data throws")
{
CHECK_THROWS_AS(SPITransactionDescriptor transaction({}, reinterpret_cast<SPIDeviceHandle*>(4747)), SPIException&);
}
TEST_CASE("SPI transaction device handle nullptr throws")
{
CHECK_THROWS_AS(SPITransactionDescriptor transaction({47}, nullptr), SPIException&);
}
TEST_CASE("SPI transaction not started wait_for")
{
CMockFixture cmock_fix;
SPIDevFix dev_fix(CreateAnd::IGNORE);
SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
SPITransactionDescriptor transaction({47}, &handle);
CHECK_THROWS_AS(transaction.wait_for(std::chrono::milliseconds(47)), SPITransferException&);
}
TEST_CASE("SPI transaction not started wait")
{
CMockFixture cmock_fix;
SPIDevFix dev_fix(CreateAnd::IGNORE);
SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
SPITransactionDescriptor transaction({47}, &handle);
CHECK_THROWS_AS(transaction.wait(), SPITransferException&);
}
TEST_CASE("SPI transaction not started get")
{
CMockFixture cmock_fix;
SPIDevFix dev_fix(CreateAnd::IGNORE);
SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
SPITransactionDescriptor transaction({47}, &handle);
CHECK_THROWS_AS(transaction.get(), SPITransferException&);
}
TEST_CASE("SPI transaction wait_for timeout")
{
CMockFixture cmock_fix;
SPITransactionFix transaction_fix(ESP_ERR_TIMEOUT);
SPIDevFix dev_fix(CreateAnd::IGNORE);
spi_device_acquire_bus_IgnoreAndReturn(ESP_OK);
spi_device_release_bus_Ignore();
SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
SPITransactionDescriptor transaction({47}, &handle);
transaction.start();
CHECK(transaction.wait_for(std::chrono::milliseconds(47)) == false);
// We need to finish the transaction, otherwise it goes out of scope without finishing and cleaning up the
// allocated transaction descriptor.
transaction_fix.get_transaction_return = ESP_OK;
spi_device_get_trans_result_ExpectAnyArgsAndReturn(ESP_OK);
transaction.wait();
}
TEST_CASE("SPI transaction one byte")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix fix(1, true);
SPIDevFix dev_fix(CreateAnd::IGNORE);
fix.rx_data = {0xA6};
SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
SPITransactionDescriptor transaction({47}, &handle);
transaction.start();
auto out_data = transaction.get();
CHECK(1 * 8 == fix.orig_trans->length);
CHECK(47 == ((uint8_t*) fix.orig_trans->tx_buffer)[0]);
REQUIRE(out_data.begin() != out_data.end());
CHECK(0xA6 == out_data[0]);
}
TEST_CASE("SPI transaction two byte")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix fix(2, true);
SPIDevFix dev_fix(CreateAnd::IGNORE);
fix.rx_data = {0xA6, 0xA7};
SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
SPITransactionDescriptor transaction({47, 48}, &handle);
transaction.start();
auto out_data = transaction.get();
CHECK(fix.size * 8 == fix.orig_trans->length);
CHECK(47 == ((uint8_t*) fix.orig_trans->tx_buffer)[0]);
CHECK(48 == ((uint8_t*) fix.orig_trans->tx_buffer)[1]);
REQUIRE(out_data.begin() != out_data.end());
REQUIRE(out_data.size() == 2);
CHECK(0xA6 == out_data[0]);
CHECK(0xA7 == out_data[1]);
}
TEST_CASE("SPI transaction future")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix trans_fix(1, true);
trans_fix.rx_data = {0xA6};
SPIDevFix dev_fix(CreateAnd::IGNORE);
SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
auto result = dev.transfer({47});
vector<uint8_t> out_data = result.get();
CHECK(1 * 8 == trans_fix.orig_trans->length);
CHECK(47 == ((uint8_t*) trans_fix.orig_trans->tx_buffer)[0]);
REQUIRE(out_data.begin() != out_data.end());
CHECK(out_data.size() == 1);
CHECK(0xA6 == out_data[0]);
}
TEST_CASE("SPI transaction with pre_callback")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix trans_fix(1, true);
trans_fix.rx_data = {0xA6};
SPIDevFix dev_fix(CreateAnd::SUCCEED);
bool pre_cb_called = false;
SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
auto result = dev.transfer({47}, [&] (void *user) { pre_cb_called = true; });
vector<uint8_t> out_data = result.get();
SPITransactionDescriptor *transaction = reinterpret_cast<SPITransactionDescriptor*>(trans_fix.orig_trans->user);
dev_fix.dev_config.pre_cb(trans_fix.orig_trans);
CHECK(true == pre_cb_called);
CHECK(1 * 8 == trans_fix.orig_trans->length);
CHECK(47 == ((uint8_t*) trans_fix.orig_trans->tx_buffer)[0]);
REQUIRE(out_data.begin() != out_data.end());
CHECK(out_data.size() == 1);
CHECK(0xA6 == out_data[0]);
}
TEST_CASE("SPI transaction with post_callback")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix trans_fix(1, true);
trans_fix.rx_data = {0xA6};
SPIDevFix dev_fix(CreateAnd::SUCCEED);
bool post_cb_called = false;
SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
auto result = dev.transfer({47}, [&] (void *user) { }, [&] (void *user) { post_cb_called = true; });
vector<uint8_t> out_data = result.get();
dev_fix.dev_config.post_cb(trans_fix.orig_trans);
CHECK(true == post_cb_called);
CHECK(1 * 8 == trans_fix.orig_trans->length);
CHECK(47 == ((uint8_t*) trans_fix.orig_trans->tx_buffer)[0]);
REQUIRE(out_data.begin() != out_data.end());
CHECK(out_data.size() == 1);
CHECK(0xA6 == out_data[0]);
}
TEST_CASE("SPI transaction data routed to pre callback")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix trans_fix(1, true);
trans_fix.rx_data = {0xA6};
SPIDevFix dev_fix(CreateAnd::SUCCEED);
bool pre_cb_called = false;
SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
auto result = dev.transfer({47},
[&] (void *user) { *(static_cast<bool*>(user)) = true; },
[&] (void *user) { },
&pre_cb_called);
result.get();
dev_fix.dev_config.pre_cb(trans_fix.orig_trans);
CHECK(true == pre_cb_called);
}
TEST_CASE("SPI transaction data routed to post callback")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix trans_fix(1, true);
trans_fix.rx_data = {0xA6};
SPIDevFix dev_fix(CreateAnd::SUCCEED);
bool post_cb_called = false;
SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
auto result = dev.transfer({47},
[&] (void *user) { },
[&] (void *user) { *(static_cast<bool*>(user)) = true; },
&post_cb_called);
result.get();
dev_fix.dev_config.post_cb(trans_fix.orig_trans);
CHECK(true == post_cb_called);
}
TEST_CASE("SPI two transactions")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix trans_fix(1, true);
trans_fix.rx_data = {0xA6};
SPIDevFix dev_fix(CreateAnd::SUCCEED);
bool pre_cb_called = false;
SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
std::function<void(void *)> pre_callback = [&] (void *user) {
pre_cb_called = true;
};
auto result = dev.transfer({47}, pre_callback);
vector<uint8_t> out_data = result.get();
dev_fix.dev_config.pre_cb(trans_fix.orig_trans);
CHECK(true == pre_cb_called);
CHECK(1 * 8 == trans_fix.orig_trans->length);
CHECK(47 == ((uint8_t*) trans_fix.orig_trans->tx_buffer)[0]);
REQUIRE(out_data.begin() != out_data.end());
CHECK(out_data.size() == 1);
CHECK(0xA6 == out_data[0]);
// preparing the second transfer
pre_cb_called = false;
spi_device_acquire_bus_ExpectAndReturn(trans_fix.handle, portMAX_DELAY, ESP_OK);
spi_device_acquire_bus_IgnoreArg_device();
spi_device_queue_trans_ExpectAndReturn(trans_fix.handle, nullptr, 0, ESP_OK);
spi_device_queue_trans_IgnoreArg_trans_desc();
spi_device_queue_trans_IgnoreArg_handle();
spi_device_get_trans_result_ExpectAndReturn(trans_fix.handle, nullptr, portMAX_DELAY, ESP_OK);
spi_device_get_trans_result_IgnoreArg_trans_desc();
spi_device_get_trans_result_IgnoreArg_handle();
spi_device_release_bus_Ignore();
result = dev.transfer({47}, pre_callback);
result.get();
dev_fix.dev_config.pre_cb(trans_fix.orig_trans);
CHECK(true == pre_cb_called);
}
TEST_CASE("SPIFuture invalid after default construction")
{
SPIFuture future;
CHECK(false == future.valid());
}
TEST_CASE("SPIFuture valid")
{
CMOCK_SETUP();
SPIDevFix dev_fix(CreateAnd::IGNORE);
SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
shared_ptr<SPITransactionDescriptor> trans(new SPITransactionDescriptor(std::vector<uint8_t>(47), &handle));
SPIFuture future(trans);
CHECK(true == future.valid());
}
TEST_CASE("SPIFuture wait_for timeout")
{
CMockFixture cmock_fix;
SPITransactionFix transaction_fix(ESP_ERR_TIMEOUT);
SPIDevFix dev_fix(CreateAnd::IGNORE);
spi_device_acquire_bus_IgnoreAndReturn(ESP_OK);
spi_device_release_bus_Ignore();
SPIDeviceHandle handle(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
shared_ptr<SPITransactionDescriptor> transaction(new SPITransactionDescriptor(std::vector<uint8_t>(47), &handle));
SPIFuture future(transaction);
transaction->start();
CHECK(future.wait_for(std::chrono::milliseconds(47)) == std::future_status::timeout);
// We need to finish the transaction, otherwise it goes out of scope without finishing and cleaning up the
// allocated transaction descriptor.
transaction_fix.get_transaction_return = ESP_OK;
spi_device_get_trans_result_ExpectAnyArgsAndReturn(ESP_OK);
future.wait();
}
TEST_CASE("SPIFuture wait_for on SPIFuture")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix trans_fix(1, true, 20);
trans_fix.rx_data = {0xA6};
SPIDevFix dev_fix(CreateAnd::IGNORE);
SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
auto result = dev.transfer({47});
CHECK(result.wait_for(std::chrono::milliseconds(20)) == std::future_status::ready);
}
TEST_CASE("SPIFuture wait on SPIFuture")
{
CMockFixture cmock_fix;
SPITransactionDescriptorFix trans_fix(1, true);
trans_fix.rx_data = {0xA6};
SPIDevFix dev_fix(CreateAnd::IGNORE);
SPIDevice dev(SPINum(SPI2_HOST), CS(4), Frequency::MHz(1), QueueSize(10));
auto result = dev.transfer({47});
result.wait();
vector<uint8_t> out_data = result.get();
CHECK(out_data.size() == 1);
}

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,34 @@
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(COMPONENTS main)
idf_component_set_property(driver USE_MOCK 1)
# 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_system_cxx_host)
add_custom_command(
OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/build/coverage.info"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/build"
COMMAND lcov --capture --directory . --output-file coverage.info
COMMENT "Create coverage report"
)
add_custom_command(
OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/build/coverage_report/"
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/build/coverage.info"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/build"
COMMAND genhtml coverage.info --output-directory coverage_report/
COMMENT "Turn coverage report into html-based visualization"
)
add_custom_target(coverage
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/build"
DEPENDS "coverage_report/"
)

View File

@ -0,0 +1,8 @@
| Supported Targets | Linux |
| ----------------- | ----- |
# Build
`idf.py build` (sdkconfig.defaults sets the linux target by default)
# Run
`build/system_cxx_host_test.elf`

View File

@ -0,0 +1,12 @@
idf_component_get_property(cpp_component experimental_cpp_component COMPONENT_DIR)
idf_component_register(SRCS "system_cxx_test.cpp"
"${cpp_component}/esp_exception.cpp"
INCLUDE_DIRS
"."
"${cpp_component}/include"
$ENV{IDF_PATH}/tools/catch
REQUIRES driver)
target_compile_options(${COMPONENT_LIB} PUBLIC --coverage)
target_link_libraries(${COMPONENT_LIB} --coverage)

View File

@ -0,0 +1,95 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0
*
* This test 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 "catch.hpp"
#include "system_cxx.hpp"
// 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("Frequency invalid")
{
CHECK_THROWS_AS(Frequency(0), ESPException&);
}
TEST_CASE("Frequency constructors correct")
{
Frequency f0(440);
CHECK(440 == f0.get_value());
Frequency f1 = Frequency::Hz(440);
CHECK(440 == f1.get_value());
Frequency f2 = Frequency::KHz(440);
CHECK(440000 == f2.get_value());
Frequency f3 = Frequency::MHz(440);
CHECK(440000000 == f3.get_value());
}
TEST_CASE("Frequency op ==")
{
Frequency f0(440);
Frequency f1(440);
CHECK(f1 == f0);
}
TEST_CASE("Frequency op !=")
{
Frequency f0(440);
Frequency f1(441);
CHECK(f1 != f0);
}
TEST_CASE("Frequency op >")
{
Frequency f0(440);
Frequency f1(441);
Frequency f2(440);
CHECK(f1 > f0);
CHECK(!(f0 > f1));
CHECK(!(f0 > f2));
}
TEST_CASE("Frequency op <")
{
Frequency f0(440);
Frequency f1(441);
Frequency f2(440);
CHECK(f0 < f1);
CHECK(!(f1 < f0));
CHECK(!(f0 < f2));
}
TEST_CASE("Frequency op >=")
{
Frequency f0(440);
Frequency f1(441);
Frequency f2(440);
CHECK (f1 >= f0);
CHECK(!(f0 >= f1));
CHECK (f0 >= f2);
}
TEST_CASE("Frequency op <=")
{
Frequency f0(440);
Frequency f1(441);
Frequency f2(440);
CHECK (f0 <= f1);
CHECK(!(f1 <= f0));
CHECK (f0 <= f2);
}

View File

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

View File

@ -1,16 +1,8 @@
// Copyright 2019 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.
/*
* SPDX-FileCopyrightText: 2019-2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
@ -65,7 +57,7 @@ public:
*/
#define CHECK_THROW_SPECIFIC(error_, exception_type_) \
do { \
esp_err_t result = error_; \
esp_err_t result = (error_); \
if (result != ESP_OK) throw idf::exception_type_(result); \
} while (0)

View File

@ -0,0 +1,152 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#if __cpp_exceptions
#include "esp_exception.hpp"
#include "gpio_cxx.hpp"
#include "system_cxx.hpp"
namespace idf {
/**
* @brief Exception which is thrown in the context of SPI C++ classes.
*/
struct SPIException : public ESPException {
SPIException(esp_err_t error);
};
/**
* @brief The maximum SPI transfer size in bytes.
*/
class SPITransferSize : public StrongValueOrdered<size_t> {
public:
/**
* @brief Create a valid SPI transfer size.
*
* @param transfer_size The raw transfer size in bytes.
*/
explicit SPITransferSize(size_t transfer_size) noexcept : StrongValueOrdered<size_t>(transfer_size) { }
static SPITransferSize default_size() {
return SPITransferSize(0);
}
};
/**
* @brief Check if the raw uint32_t spi number is in the range according to the hardware.
*/
esp_err_t check_spi_num(uint32_t spi_num) noexcept;
/**
* @brief Represents a valid SPI host number.
*
* ESP chips may have different independent SPI peripherals. This SPI number distinguishes between them.
*/
class SPINum : public StrongValueComparable<uint32_t> {
public:
/**
* @brief Create a valid SPI host number.
*
* @param host_id_raw The raw SPI host number.
*
* @throw SPIException if the passed SPI host number is incorrect.
*/
SPINum(uint32_t host_id_raw) : StrongValueComparable<uint32_t>(host_id_raw)
{
esp_err_t spi_num_check_result = check_spi_num(host_id_raw);
if (spi_num_check_result != ESP_OK) {
throw SPIException(spi_num_check_result);
}
}
/**
* @brief Return the raw value of the SPI host.
*
* This should only be used when calling driver and other interfaces which don't support the C++ class.
*
* @return the raw value of the SPI host.
*/
uint32_t get_spi_num() const
{
return get_value();
}
};
/**
* @brief Represents a valid MOSI signal pin number.
*/
class MOSI_type;
using MOSI = GPIONumBase<class MOSI_type>;
/**
* @brief Represents a valid MISO signal pin number.
*/
class MISO_type;
using MISO = GPIONumBase<class MISO_type>;
/**
* @brief Represents a valid SCLK signal pin number.
*/
class SCLK_type;
using SCLK = GPIONumBase<class SCLK_type>;
/**
* @brief Represents a valid CS (chip select) signal pin number.
*/
class CS_type;
using CS = GPIONumBase<class CS_type>;
/**
* @brief Represents a valid QSPIWP signal pin number.
*/
class QSPIWP_type;
using QSPIWP = GPIONumBase<class QSPIWP_type>;
/**
* @brief Represents a valid QSPIHD signal pin number.
*/
class QSPIHD_type;
using QSPIHD = GPIONumBase<class QSPIHD_type>;
/**
* @brief Represents a valid SPI DMA configuration. Use it similar to an enum.
*/
class SPI_DMAConfig : public StrongValueComparable<uint32_t> {
/**
* Constructor is hidden to enforce object invariants.
* Use the static creation methods to create instances.
*/
explicit SPI_DMAConfig(uint32_t channel_num) : StrongValueComparable<uint32_t>(channel_num) { }
public:
/**
* @brief Create a configuration with DMA disabled.
*/
static SPI_DMAConfig DISABLED();
/**
* @brief Create a configuration where the driver allocates DMA.
*/
static SPI_DMAConfig AUTO();
/**
* @brief Return the raw value of the DMA configuration.
*
* This should only be used when calling driver and other interfaces which don't support the C++ class.
*
* @return the raw value of the DMA configuration.
*/
uint32_t get_num() const {
return get_value();
}
};
}
#endif

View File

@ -0,0 +1,414 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#if __cpp_exceptions
#include <exception>
#include <memory>
#include <chrono>
#include <vector>
#include <list>
#include <future>
#include "system_cxx.hpp"
#include "spi_cxx.hpp"
namespace idf {
/**
* @brief Exception which is thrown in the context of SPI Transactions.
*/
struct SPITransferException : public SPIException {
SPITransferException(esp_err_t error);
};
class SPIDevice;
class SPIDeviceHandle;
/**
* @brief Describes and encapsulates the transaction.
*
* @note This class is intended to be used internally by the SPI C++ classes, but not publicly.
* Furthermore, currently only one transaction per time can be handled. If you need to
* send several transactions in parallel, you need to build your own mechanism around a
* FreeRTOS task and a queue.
*/
class SPITransactionDescriptor {
friend class SPIDeviceHandle;
public:
/**
* @brief Create a SPITransactionDescriptor object, describing a full duplex transaction.
*
* @param data_to_send The data sent to the SPI device. It can be dummy data if a read-only
* transaction is intended. Its length determines the length of both write and read operation.
* @param handle to the internal driver handle
* @param pre_callback If non-empty, this callback will be called directly before the transaction.
* @param post_callback If non-empty, this callback will be called directly after the transaction.
* @param user_data optional data which will be accessible in the callbacks declared above
*/
SPITransactionDescriptor(const std::vector<uint8_t> &data_to_send,
SPIDeviceHandle *handle,
std::function<void(void *)> pre_callback = nullptr,
std::function<void(void *)> post_callback = nullptr,
void* user_data = nullptr);
/**
* @brief Deinitialize and delete all data of the transaction.
*
* @note This destructor must not becalled before the transaction is finished by the driver.
*/
~SPITransactionDescriptor();
SPITransactionDescriptor(const SPITransactionDescriptor&) = delete;
SPITransactionDescriptor operator=(const SPITransactionDescriptor&) = delete;
/**
* @brief Queue the transaction asynchronously.
*/
void start();
/**
* @brief Synchronously (blocking) wait for the result and return the result data or throw an exception.
*
* @return The data read from the SPI device. Its length is the length of \c data_to_send passed in the
* constructor.
* @throws SPIException in case of an error of the underlying driver or if the driver returns a wrong
* transaction descriptor for some reason. In the former case, the error code is the one from the
* underlying driver, in the latter case, the error code is ESP_ERR_INVALID_STATE.
*/
std::vector<uint8_t> get();
/**
* @brief Wait until the asynchronous operation is done.
*
* @throws SPIException in case of an error of the underlying driver or if the driver returns a wrong
* transaction descriptor for some reason. In the former case, the error code is the one from the
* underlying driver, in the latter case, the error code is ESP_ERR_INVALID_STATE.
*/
void wait();
/**
* @brief Wait for a result of the transaction up to timeout ms.
*
* @param timeout Maximum timeout value for waiting
*
* @return true if result is available, false if wait timed out
*
* @throws SPIException in case of an error of the underlying driver or if the driver returns a wrong
* transaction descriptor for some reason. In the former case, the error code is the one from the
* underlying driver, in the latter case, the error code is ESP_ERR_INVALID_STATE.
*/
bool wait_for(const std::chrono::milliseconds &timeout);
private:
/**
* Private descriptor data.
*/
void *private_transaction_desc;
/**
* Private device data.
*/
SPIDeviceHandle *device_handle;
/**
* @brief If non-empty, this callback will be called directly before the transaction.
*/
std::function<void(void *)> pre_callback;
/**
* @brief If non-empty, this callback will be called directly after the transaction.
*/
std::function<void(void *)> post_callback;
/**
* Buffer in spi_transaction_t is const, so we have to declare it here because we want to
* allocate and delete it.
*/
uint8_t *tx_buffer;
/**
* @brief User data which will be provided in the callbacks.
*/
void *user_data;
/**
* Tells if data has been received, i.e. the transaction has finished and the result can be acquired.
*/
bool received_data;
/**
* Tells if the transaction has been initiated and is at least in-flight, if not finished.
*/
bool started;
};
/**
* @brief SPIFuture for asynchronous transaction results, mostly equivalent to std::future.
*
* This re-implementation is necessary as std::future is incompatible with the IDF SPI driver interface.
*/
class SPIFuture {
public:
/**
* @brief Create an invalid future.
*/
SPIFuture();
/**
* @brief Create a valid future with \c transaction as shared state.
*
* @param transaction the shared transaction state
*/
SPIFuture(std::shared_ptr<SPITransactionDescriptor> transaction);
SPIFuture(const SPIFuture &other) = delete;
/**
* @brief Move constructor as in std::future, leaves \c other invalid.
*
* @param other object to move from, will become invalid during this constructor
*/
SPIFuture(SPIFuture &&other) noexcept;
/**
* @brief Move assignment as in std::future, leaves \c other invalid.
*
* @param other object to move from, will become invalid during this constructor
* @return A reference to the newly created SPIFuture object
*/
SPIFuture &operator=(SPIFuture&& other) noexcept;
/**
* @brief Wait until the asynchronous operation is done and return the result or throw and exception.
*
* @throws std::future_error if this future is not valid.
* @throws SPIException in case of an error of the underlying driver or if the driver returns a wrong
* transaction descriptor for some reason. In the former case, the error code is the one from the
* underlying driver, in the latter case, the error code is ESP_ERR_INVALID_STATE.
* @return The result of the asynchronous SPI transaction.
*/
std::vector<uint8_t> get();
/**
* @brief Wait for a result up to timeout ms.
*
* @param timeout Maximum timeout value for waiting
*
* @return std::future_status::ready if result is available, std::future_status::timeout if wait timed out
*/
std::future_status wait_for(std::chrono::milliseconds timeout);
/**
* @brief Wait for a result indefinitely.
*/
void wait();
/**
* @return true if this future is valid, otherwise false.
*/
bool valid() const noexcept;
private:
/**
* The SPITransactionDescriptor, which is the shared state of this future.
*/
std::shared_ptr<SPITransactionDescriptor> transaction;
/**
* Indicates if this future is valid.
*/
bool is_valid;
};
/**
* @brief Represents an device on an initialized Master Bus.
*/
class SPIDevice {
public:
/**
* @brief Create and initialize a device on the master bus corresponding to spi_host.
*
* @param cs The pin number of the chip select signal for the device to create.
* @param spi_host the spi_host (bus) to which the device shall be attached.
* @param frequency The devices frequency. this frequency will be set during transactions to the device which will be
* created.
* @param transaction_queue_size The of the transaction queue of this device. This determines how many
* transactions can be queued at the same time. Currently, it is set to 1 since the
* implementation exclusively acquires the bus for each transaction. This may change in the future.
*/
SPIDevice(SPINum spi_host,
CS cs,
Frequency frequency = Frequency::MHz(1),
QueueSize transaction_queue_size = QueueSize(1u));
SPIDevice(const SPIDevice&) = delete;
SPIDevice operator=(const SPIDevice&) = delete;
/**
* @brief De-initializes and destroys the device.
*
* @warning Behavior is undefined if a device is destroyed while there is still an ongoing transaction
* from that device.
*/
~SPIDevice();
/**
* @brief Queue a transfer to this device.
*
* This method creates a full-duplex transfer to the device represented by the current instance of this class.
* It then queues that transfer and returns a "future" object. The future object will become ready once
* the transfer finishes.
*
* @param data_to_send Data which will be sent to the device. The length of the data determines the length
* of the full-deplex transfer. I.e., the same amount of bytes will be received from the device.
* @param pre_callback If non-empty, this callback will be called directly before the transaction.
* If empty, it will be ignored.
* @param post_callback If non-empty, this callback will be called directly after the transaction.
* If empty, it will be ignored.
* @param user_data This pointer will be sent to pre_callback and/or pre_callback, if any of them is non-empty.
*
* @return a future object which will become ready once the transfer has finished. See also \c SPIFuture.
*/
SPIFuture transfer(const std::vector<uint8_t> &data_to_send,
std::function<void(void *)> pre_callback = nullptr,
std::function<void(void *)> post_callback = nullptr,
void* user_data = nullptr);
/**
* @brief Queue a transfer to this device like \c transfer, but using begin/end iterators instead of a
* data vector.
*
* This method is equivalent to \c transfer(), except for the parameters.
*
* @param begin Iterator to the begin of the data which will be sent to the device.
* @param end Iterator to the end of the data which will be sent to the device.
* This iterator determines the length of the data and hence the length of the full-deplex transfer.
* I.e., the same amount of bytes will be received from the device.
* @param pre_callback If non-empty, this callback will be called directly before the transaction.
* If empty, it will be ignored.
* @param post_callback If non-empty, this callback will be called directly after the transaction.
* If empty, it will be ignored.
* @param user_data This pointer will be sent to pre_callback and/or pre_callback, if any of them is non-empty.
*
* @return a future object which will become ready once the transfer has finished. See also \c SPIFuture.
*/
template<typename IteratorT>
SPIFuture transfer(IteratorT begin,
IteratorT end,
std::function<void(void *)> pre_callback = nullptr,
std::function<void(void *)> post_callback = nullptr,
void* user_data = nullptr);
private:
/**
* Private device data.
*/
SPIDeviceHandle *device_handle;
/**
* Saves the current transaction descriptor in case the user's loses its future with the other
* reference to the transaction.
*/
std::shared_ptr<SPITransactionDescriptor> current_transaction;
};
/**
* @brief Represents an SPI Master Bus.
*/
class SPIMaster {
public:
/*
* @brief Create an SPI Master Bus.
*
* @param host The SPI host (bus) which should be used. ESP chips have a number of different possible SPI hosts,
* each of which will create its own bus. Consult the datasheet and TRM on which host to choose.
* @param mosi The pin number for the MOSI signal of this bus.
* @param miso The pin number for the MISO signal of this bus.
* @param sclk The pin number for the clock signal of this bus.
* @param dma_config The DMA configuration for this bus, see \c DMAConfig.
* @param max_transfer_size The maximum transfer size in bytes.
*
* @throws SPIException with IDF error code if the underlying driver fails.
*/
explicit SPIMaster(SPINum host,
const MOSI &mosi,
const MISO &miso,
const SCLK &sclk,
SPI_DMAConfig dma_config = SPI_DMAConfig::AUTO(),
SPITransferSize max_transfer_size = SPITransferSize::default_size());
/*
* @brief Create an SPI Master Bus.
*
* @param host The SPI host (bus) which should be used. ESP chips have a number of different possible SPI hosts,
* each of which will create its own bus. Consult the datasheet and TRM on which host to choose.
* @param mosi The pin number for the MOSI signal of this bus.
* @param miso The pin number for the MISO signal of this bus.
* @param sclk The pin number for the clock signal of this bus.
* @param qspiwp The pin number for the QSPIWP signal of this bus.
* @param qspihd The pin number for the QSPIHD signal of this bus.
* @param dma_config The DMA configuration for this bus, see \c DMAConfig.
* @param max_transfer_size The maximum transfer size in bytes.
*
* @throws SPIException with IDF error code if the underlying driver fails.
*/
explicit SPIMaster(SPINum host,
const MOSI &mosi,
const MISO &miso,
const SCLK &sclk,
const QSPIWP &qspiwp,
const QSPIHD &qspihd,
SPI_DMAConfig dma_config = SPI_DMAConfig::AUTO(),
SPITransferSize max_transfer_size = SPITransferSize::default_size());
SPIMaster(const SPIMaster&) = delete;
SPIMaster operator=(const SPIMaster&) = delete;
SPIMaster(SPIMaster&&) = default;
SPIMaster &operator=(SPIMaster&&) = default;
/*
* @brief De-initializes and destroys the SPI Master Bus.
*
* @note Devices created before which try to initialize an exception after the bus is destroyed will throw
* and exception.
*/
virtual ~SPIMaster();
/**
* @brief Create a representation of a device on this bus.
*
* @param cs The pin number for the CS (chip select) signal to talk to the device.
* @param f The frequency used to talk to the device.
*/
std::shared_ptr<SPIDevice> create_dev(CS cs, Frequency frequency = Frequency::MHz(1));
private:
/**
* @brief Host identifier for internal use.
*/
SPINum spi_host;
};
template<typename IteratorT>
SPIFuture SPIDevice::transfer(IteratorT begin,
IteratorT end,
std::function<void(void *)> pre_callback,
std::function<void(void *)> post_callback,
void* user_data)
{
std::vector<uint8_t> write_data;
write_data.assign(begin, end);
return transfer(write_data, pre_callback, post_callback, user_data);
}
}
#endif

View File

@ -4,12 +4,19 @@
* SPDX-License-Identifier: Apache-2.0
*/
/**
* This file contains helper classes for commonly used IDF types. The classes make the use of these types easier and
* safer.
* In particular, their usage provides greater type-safety of function arguments and "correctness by construction".
*/
#pragma once
#ifndef __cpp_exceptions
#error system C++ classes only usable when C++ exceptions enabled. Enable CONFIG_COMPILER_CXX_EXCEPTIONS in Kconfig
#endif
#include "esp_exception.hpp"
/**
* 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.
@ -32,13 +39,14 @@ private:
};
/**
* This class adds comparison properties to StrongValue, but no sorting properties.
* This class adds comparison properties to StrongValue, but no sorting and ordering properties.
*/
template<typename ValueT>
class StrongValueComparable : public StrongValue<ValueT> {
protected:
StrongValueComparable(ValueT value_arg) : StrongValue<ValueT>(value_arg) { }
public:
using StrongValue<ValueT>::get_value;
bool operator==(const StrongValueComparable<ValueT> &other_gpio) const
@ -51,3 +59,87 @@ protected:
return get_value() != other_gpio.get_value();
}
};
namespace idf {
/**
* This class adds ordering and sorting properties to StrongValue.
*/
template<typename ValueT>
class StrongValueOrdered : public StrongValueComparable<ValueT> {
public:
StrongValueOrdered(ValueT value) : StrongValueComparable<ValueT>(value) { }
using StrongValueComparable<ValueT>::get_value;
bool operator>(const StrongValueOrdered<ValueT> &other) const
{
return get_value() > other.get_value();
}
bool operator<(const StrongValueOrdered<ValueT> &other) const
{
return get_value() < other.get_value();
}
bool operator>=(const StrongValueOrdered<ValueT> &other) const
{
return get_value() >= other.get_value();
}
bool operator<=(const StrongValueOrdered<ValueT> &other) const
{
return get_value() <= other.get_value();
}
};
/**
* A general frequency class to be used whereever an unbound frequency value is necessary.
*/
class Frequency : public StrongValueOrdered<size_t> {
public:
explicit Frequency(size_t frequency) : StrongValueOrdered<size_t>(frequency)
{
if (frequency == 0) {
throw ESPException(ESP_ERR_INVALID_ARG);
}
}
Frequency(const Frequency&) = default;
Frequency &operator=(const Frequency&) = default;
using StrongValueOrdered<size_t>::get_value;
static Frequency Hz(size_t frequency)
{
return Frequency(frequency);
}
static Frequency KHz(size_t frequency)
{
return Frequency(frequency * 1000);
}
static Frequency MHz(size_t frequency)
{
return Frequency(frequency * 1000 * 1000);
}
};
/**
* Queue size mainly for operating system queues.
*/
class QueueSize {
public:
explicit QueueSize(size_t q_size) : queue_size(q_size) { }
size_t get_size()
{
return queue_size;
}
private:
size_t queue_size;
};
}

View File

@ -0,0 +1,141 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* The code in this file includes driver headers directly, hence it's a private include.
* It should only be used in C++ source files, while header files use forward declarations of the types.
* This way, public headers don't need to depend on (i.e. include) driver headers.
*/
#ifdef __cpp_exceptions
#include "hal/spi_types.h"
#include "driver/spi_master.h"
using namespace std;
namespace idf {
#define SPI_CHECK_THROW(err) CHECK_THROW_SPECIFIC((err), SPIException)
namespace {
/**
* @brief Convenience method to convert a SPINum object into the driver type. Avoids long static casts.
*/
spi_host_device_t spi_num_to_driver_type(const SPINum &num) noexcept {
return static_cast<spi_host_device_t>(num.get_spi_num());
}
}
/**
* This class wraps closely around the SPI master device driver functions.
* It is used to hide the implementation, in particular the dependencies on the driver and HAL layer headers.
* Public header files only use a pointer to this class which is forward declared in spi_host_cxx.hpp.
* Implementations (source files) can include this private header and use the class definitions.
*
* Furthermore, this class ensures RAII-capabilities of an SPI master device allocation and initiates pre- and
* post-transaction callback for each transfer. In constrast to the IDF driver, the callbacks are not per-device
* but per transaction in the C++ wrapper framework.
*
* For information on the public member functions, refer to the corresponding driver functions in spi_master.h
*/
class SPIDeviceHandle {
public:
/**
* Create a device instance on the SPI bus identified by spi_host, allocate all corresponding resources.
*/
SPIDeviceHandle(SPINum spi_host, CS cs, Frequency frequency, QueueSize q_size)
{
spi_device_interface_config_t dev_config = {};
dev_config.clock_speed_hz = frequency.get_value();
dev_config.spics_io_num = cs.get_num();
dev_config.pre_cb = pr_cb;
dev_config.post_cb = post_cb;
dev_config.queue_size = q_size.get_size();
SPI_CHECK_THROW(spi_bus_add_device(spi_num_to_driver_type(spi_host), &dev_config, &handle));
}
SPIDeviceHandle(const SPIDeviceHandle &other) = delete;
SPIDeviceHandle(SPIDeviceHandle &&other) noexcept : handle(std::move(other.handle))
{
// Only to indicate programming errors where users use an instance after moving it.
other.handle = nullptr;
}
/**
* Remove device instance from the SPI bus, deallocate all corresponding resources.
*/
~SPIDeviceHandle()
{
// We ignore the return value here.
// Only possible errors are wrong handle (impossible by object invariants) and
// handle already freed, which we can ignore.
spi_bus_remove_device(handle);
}
SPIDeviceHandle &operator=(SPIDeviceHandle&& other) noexcept
{
if (this != &other) {
handle = std::move(other.handle);
// Only to indicate programming errors where users use an instance after moving it.
other.handle = nullptr;
}
return *this;
}
esp_err_t acquire_bus(TickType_t wait)
{
return spi_device_acquire_bus(handle, portMAX_DELAY);
}
esp_err_t queue_trans(spi_transaction_t *trans_desc, TickType_t wait)
{
return spi_device_queue_trans(handle, trans_desc, wait);
}
esp_err_t get_trans_result(spi_transaction_t **trans_desc, TickType_t ticks_to_wait)
{
return spi_device_get_trans_result(handle, trans_desc, ticks_to_wait);
}
void release_bus()
{
spi_device_release_bus(handle);
}
private:
/**
* Route the callback to the callback in the specific SPITransactionDescriptor instance.
*/
static void pr_cb(spi_transaction_t *driver_transaction)
{
SPITransactionDescriptor *transaction = static_cast<SPITransactionDescriptor*>(driver_transaction->user);
if (transaction->pre_callback) {
transaction->pre_callback(transaction->user_data);
}
}
/**
* Route the callback to the callback in the specific SPITransactionDescriptor instance.
*/
static void post_cb(spi_transaction_t *driver_transaction)
{
SPITransactionDescriptor *transaction = static_cast<SPITransactionDescriptor*>(driver_transaction->user);
if (transaction->post_callback) {
transaction->post_callback(transaction->user_data);
}
}
spi_device_handle_t handle;
};
}
#endif

View File

@ -0,0 +1,33 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#if __cpp_exceptions
#include "driver/spi_common.h"
#include "esp_exception.hpp"
#include "spi_cxx.hpp"
namespace idf {
esp_err_t check_spi_num(uint32_t spi_num) noexcept {
if (spi_num >= static_cast<uint32_t>(SPI_HOST_MAX)) {
return ESP_ERR_INVALID_ARG;
}
return ESP_OK;
}
SPI_DMAConfig SPI_DMAConfig::DISABLED() {
return SPI_DMAConfig(static_cast<uint32_t>(spi_common_dma_t::SPI_DMA_DISABLED));
}
SPI_DMAConfig SPI_DMAConfig::AUTO() {
return SPI_DMAConfig(static_cast<uint32_t>(spi_common_dma_t::SPI_DMA_CH_AUTO));
}
}
#endif

View File

@ -0,0 +1,267 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#if __cpp_exceptions
#include <stdint.h>
#include <cstring>
#include "freertos/FreeRTOS.h"
#include "freertos/portmacro.h"
#include "hal/spi_types.h"
#include "driver/spi_master.h"
#include "spi_host_cxx.hpp"
#include "spi_host_private_cxx.hpp"
using namespace std;
namespace idf {
SPIException::SPIException(esp_err_t error) : ESPException(error) { }
SPITransferException::SPITransferException(esp_err_t error) : SPIException(error) { }
SPIMaster::SPIMaster(SPINum host,
const MOSI &mosi,
const MISO &miso,
const SCLK &sclk,
SPI_DMAConfig dma_config,
SPITransferSize transfer_size)
: spi_host(host)
{
spi_bus_config_t bus_config = {};
bus_config.mosi_io_num = mosi.get_num();
bus_config.miso_io_num = miso.get_num();
bus_config.sclk_io_num = sclk.get_num();
bus_config.quadwp_io_num = -1;
bus_config.quadhd_io_num = -1;
bus_config.max_transfer_sz = transfer_size.get_value();
SPI_CHECK_THROW(spi_bus_initialize(spi_num_to_driver_type(spi_host), &bus_config, dma_config.get_num()));
}
SPIMaster::SPIMaster(SPINum host,
const MOSI &mosi,
const MISO &miso,
const SCLK &sclk,
const QSPIWP &qspiwp,
const QSPIHD &qspihd,
SPI_DMAConfig dma_config,
SPITransferSize transfer_size)
: spi_host(host)
{
spi_bus_config_t bus_config = {};
bus_config.mosi_io_num = mosi.get_num();
bus_config.miso_io_num = miso.get_num();
bus_config.sclk_io_num = sclk.get_num();
bus_config.quadwp_io_num = qspiwp.get_num();
bus_config.quadhd_io_num = qspihd.get_num();
bus_config.max_transfer_sz = transfer_size.get_value();
SPI_CHECK_THROW(spi_bus_initialize(spi_num_to_driver_type(spi_host), &bus_config, dma_config.get_num()));
}
SPIMaster::~SPIMaster()
{
spi_bus_free(spi_num_to_driver_type(spi_host));
}
shared_ptr<SPIDevice> SPIMaster::create_dev(CS cs, Frequency frequency)
{
return make_shared<SPIDevice>(spi_host, cs, frequency);
}
SPIFuture::SPIFuture()
: transaction(), is_valid(false)
{
}
SPIFuture::SPIFuture(shared_ptr<SPITransactionDescriptor> transaction)
: transaction(transaction), is_valid(true)
{
}
SPIFuture::SPIFuture(SPIFuture &&other) noexcept
: transaction(std::move(other.transaction)), is_valid(true)
{
other.is_valid = false;
}
SPIFuture &SPIFuture::operator=(SPIFuture &&other) noexcept
{
if (this != &other) {
transaction = std::move(other.transaction);
is_valid = other.is_valid;
other.is_valid = false;
}
return *this;
}
vector<uint8_t> SPIFuture::get()
{
if (!is_valid) {
throw std::future_error(future_errc::no_state);
}
return transaction->get();
}
future_status SPIFuture::wait_for(chrono::milliseconds timeout)
{
if (transaction->wait_for(timeout)) {
return std::future_status::ready;
} else {
return std::future_status::timeout;
}
}
void SPIFuture::wait()
{
transaction->wait();
}
bool SPIFuture::valid() const noexcept
{
return is_valid;
}
SPIDevice::SPIDevice(SPINum spi_host, CS cs, Frequency frequency, QueueSize q_size) : device_handle()
{
device_handle = new SPIDeviceHandle(spi_host, cs, frequency, q_size);
}
SPIDevice::~SPIDevice()
{
delete device_handle;
}
SPIFuture SPIDevice::transfer(const vector<uint8_t> &data_to_send,
std::function<void(void *)> pre_callback,
std::function<void(void *)> post_callback,
void* user_data)
{
current_transaction = make_shared<SPITransactionDescriptor>(data_to_send,
device_handle,
std::move(pre_callback),
std::move(post_callback),
user_data);
current_transaction->start();
return SPIFuture(current_transaction);
}
SPITransactionDescriptor::SPITransactionDescriptor(const std::vector<uint8_t> &data_to_send,
SPIDeviceHandle *handle,
std::function<void(void *)> pre_callback,
std::function<void(void *)> post_callback,
void* user_data_arg)
: device_handle(handle),
pre_callback(std::move(pre_callback)),
post_callback(std::move(post_callback)),
user_data(user_data_arg),
received_data(false),
started(false)
{
// C++11 vectors don't have size() or empty() members yet
if (data_to_send.begin() == data_to_send.end()) {
throw SPITransferException(ESP_ERR_INVALID_ARG);
}
if (handle == nullptr) {
throw SPITransferException(ESP_ERR_INVALID_ARG);
}
size_t trans_size = data_to_send.size();
spi_transaction_t *trans_desc;
trans_desc = new spi_transaction_t;
memset(trans_desc, 0, sizeof(spi_transaction_t));
trans_desc->rx_buffer = new uint8_t [trans_size];
tx_buffer = new uint8_t [trans_size];
for (size_t i = 0; i < trans_size; i++) {
tx_buffer[i] = data_to_send[i];
}
trans_desc->length = trans_size * 8;
trans_desc->tx_buffer = tx_buffer;
trans_desc->user = this;
private_transaction_desc = trans_desc;
}
SPITransactionDescriptor::~SPITransactionDescriptor()
{
if (started) {
assert(received_data); // We need to make sure that trans_desc has been received, otherwise the
// driver may still write into it afterwards.
}
spi_transaction_t *trans_desc = reinterpret_cast<spi_transaction_t*>(private_transaction_desc);
delete [] tx_buffer;
delete [] static_cast<uint8_t*>(trans_desc->rx_buffer);
delete trans_desc;
}
void SPITransactionDescriptor::start()
{
spi_transaction_t *trans_desc = reinterpret_cast<spi_transaction_t*>(private_transaction_desc);
SPI_CHECK_THROW(device_handle->acquire_bus(portMAX_DELAY));
SPI_CHECK_THROW(device_handle->queue_trans(trans_desc, 0));
started = true;
}
void SPITransactionDescriptor::wait()
{
while (wait_for(chrono::milliseconds(portMAX_DELAY)) == false) { }
}
bool SPITransactionDescriptor::wait_for(const chrono::milliseconds &timeout_duration)
{
if (received_data) {
return true;
}
if (!started) {
throw SPITransferException(ESP_ERR_INVALID_STATE);
}
spi_transaction_t *acquired_trans_desc;
esp_err_t err = device_handle->get_trans_result(&acquired_trans_desc,
(TickType_t) timeout_duration.count() / portTICK_RATE_MS);
if (err == ESP_ERR_TIMEOUT) {
return false;
}
if (err != ESP_OK) {
throw SPITransferException(err);
}
if (acquired_trans_desc != reinterpret_cast<spi_transaction_t*>(private_transaction_desc)) {
throw SPITransferException(ESP_ERR_INVALID_STATE);
}
received_data = true;
device_handle->release_bus();
return true;
}
std::vector<uint8_t> SPITransactionDescriptor::get()
{
if (!received_data) {
wait();
}
spi_transaction_t *trans_desc = reinterpret_cast<spi_transaction_t*>(private_transaction_desc);
const size_t TRANSACTION_LENGTH = trans_desc->length / 8;
vector<uint8_t> result(TRANSACTION_LENGTH);
for (int i = 0; i < TRANSACTION_LENGTH; i++) {
result[i] = static_cast<uint8_t*>(trans_desc->rx_buffer)[i];
}
return result;
}
} // idf
#endif // __cpp_exceptions

View File

@ -1,3 +1,9 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "unity.h"

View File

@ -0,0 +1,8 @@
# 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)
set(EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component")
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(simple_spi_rw_example)

View File

@ -0,0 +1,68 @@
# Example: C++ SPI sensor read for MCU9250 inertial/giroscope sensor
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example demonstrates usage of C++ SPI classes in ESP-IDF to read the `WHO_AM_I` register of the sensor.
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++ SPI API.
## How to use example
### Hardware Required
An MCU9250 sensor and any commonly available ESP32 development board.
### 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
If the sensor is read correctly:
```
...
I (0) cpu_start: Starting scheduler on APP CPU.
Result of WHO_AM_I register: 0x71
I (437) gpio: GPIO[23]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (447) gpio: GPIO[25]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (457) gpio: GPIO[26]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (467) gpio: GPIO[27]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
Done
```
If there's an error with the SPI peripheral:
```
...
I (0) cpu_start: Starting scheduler on APP CPU.
E (434) spi: spicommon_bus_initialize_io(429): mosi not valid
Coulnd't read SPI!
```
If the SPI pins are not connected properly, the resulting read may just return 0, this error can not be detected:
```
...
I (0) cpu_start: Starting scheduler on APP CPU.
Result of WHO_AM_I register: 0x00
I (437) gpio: GPIO[23]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (447) gpio: GPIO[25]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (457) gpio: GPIO[26]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (467) gpio: GPIO[27]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
```

View File

@ -0,0 +1,3 @@
idf_component_register(SRCS "simple_spi_rw_example.cpp"
INCLUDE_DIRS "."
REQUIRES experimental_cpp_component)

View File

@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@ -0,0 +1,48 @@
/*
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: CC0
*
* MPU9250 SPI Sensor 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 <iostream>
#include "spi_host_cxx.hpp"
using namespace std;
using namespace idf;
static const GPIONum NSS(23);
static const uint8_t READ_FLAG = 0x80;
static const uint8_t MPU9250_WHO_AM_I_REG_ADDR = 0x75;
extern "C" void app_main(void)
{
try {
SPIMaster master(SPINum(2),
MOSI(25),
MISO(26),
SCLK(27));
shared_ptr<SPIDevice> spi_dev = master.create_dev(CS(NSS.get_num()), Frequency::MHz(1));
vector<uint8_t> write_data = {MPU9250_WHO_AM_I_REG_ADDR | READ_FLAG, 0x00};
vector<uint8_t> result = spi_dev->transfer(write_data).get();
cout << "Result of WHO_AM_I register: 0x";
printf("%02X", result[1]);
cout << endl;
this_thread::sleep_for(std::chrono::seconds(2));
} catch (const SPIException &e) {
cout << "Coulnd't read SPI!" << endl;
}
}

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

@ -25,10 +25,7 @@ def is_test_server_available(): # type: () -> bool
return False
# Disabling the Test in CI as the leaf certificate of http2.golang.org is expired from 8 July.
# There is no timeline when the cert will be updated.
# Disabling this test till an alternative is found for testing the http2 support.
@ttfw_idf.idf_example_test(env_tag='Example_EthKitV1', ignore=True)
@ttfw_idf.idf_example_test(env_tag='Example_EthKitV1')
def test_examples_protocol_http2_request(env, extra_data): # type: (tiny_test_fw.Env.Env, None) -> None # pylint: disable=unused-argument
"""
steps: |

View File

@ -53,6 +53,7 @@ static void initialise_mdns(void)
//initialize service
ESP_ERROR_CHECK( mdns_service_add("ESP32-WebServer", "_http", "_tcp", 80, serviceTxtData, 3) );
ESP_ERROR_CHECK( mdns_service_subtype_add_for_host("ESP32-WebServer", "_http", "_tcp", NULL, "_server") );
#if CONFIG_MDNS_MULTIPLE_INSTANCE
ESP_ERROR_CHECK( mdns_service_add("ESP32-WebServer1", "_http", "_tcp", 80, NULL, 0) );
#endif

View File

@ -1583,7 +1583,6 @@ components/hal/include/hal/spi_flash_types.h
components/hal/include/hal/spi_hal.h
components/hal/include/hal/spi_slave_hal.h
components/hal/include/hal/spi_slave_hd_hal.h
components/hal/include/hal/spi_types.h
components/hal/include/hal/systimer_hal.h
components/hal/include/hal/systimer_types.h
components/hal/include/hal/touch_sensor_hal.h
@ -1833,13 +1832,11 @@ components/mdns/host_test/components/freertos_linux/include/freertos/task.h
components/mdns/host_test/components/freertos_linux/queue_unique_ptr.cpp
components/mdns/host_test/components/freertos_linux/queue_unique_ptr.hpp
components/mdns/host_test/main/main.c
components/mdns/include/mdns.h
components/mdns/include/mdns_console.h
components/mdns/mdns_console.c
components/mdns/mdns_networking_lwip.c
components/mdns/mdns_networking_socket.c
components/mdns/private_include/mdns_networking.h
components/mdns/private_include/mdns_private.h
components/mdns/test/test_mdns.c
components/mdns/test_afl_fuzz_host/esp32_mock.c
components/mdns/test_afl_fuzz_host/esp32_mock.h
@ -3358,18 +3355,15 @@ examples/cxx/experimental/experimental_cpp_component/esp_event_cxx.cpp
examples/cxx/experimental/experimental_cpp_component/esp_exception.cpp
examples/cxx/experimental/experimental_cpp_component/esp_timer_cxx.cpp
examples/cxx/experimental/experimental_cpp_component/host_test/esp_timer/main/esp_timer_test.cpp
examples/cxx/experimental/experimental_cpp_component/host_test/fixtures/test_fixtures.hpp
examples/cxx/experimental/experimental_cpp_component/host_test/gpio/main/gpio_cxx_test.cpp
examples/cxx/experimental/experimental_cpp_component/i2c_cxx.cpp
examples/cxx/experimental/experimental_cpp_component/include/esp_event_api.hpp
examples/cxx/experimental/experimental_cpp_component/include/esp_event_cxx.hpp
examples/cxx/experimental/experimental_cpp_component/include/esp_exception.hpp
examples/cxx/experimental/experimental_cpp_component/include/esp_timer_cxx.hpp
examples/cxx/experimental/experimental_cpp_component/test/test_cxx_exceptions.cpp
examples/cxx/experimental/experimental_cpp_component/test/test_esp_event_cxx.cpp
examples/cxx/experimental/experimental_cpp_component/test/test_esp_timer.cpp
examples/cxx/experimental/experimental_cpp_component/test/test_i2c.cpp
examples/cxx/experimental/experimental_cpp_component/test/unity_cxx.hpp
examples/cxx/experimental/sensor_mcp9808/main/sensor_mcp9808.cpp
examples/cxx/pthread/example_test.py
examples/cxx/pthread/main/cpp_pthread.cpp
@ -3856,8 +3850,6 @@ tools/ci/python_packages/ttfw_idf/CIScanTests.py
tools/ci/python_packages/ttfw_idf/DebugUtils.py
tools/ci/python_packages/ttfw_idf/IDFApp.py
tools/ci/python_packages/ttfw_idf/IDFAssignTest.py
tools/ci/python_packages/ttfw_idf/IDFDUT.py
tools/ci/python_packages/ttfw_idf/__init__.py
tools/ci/python_packages/ttfw_idf/unity_test_parser.py
tools/ci/python_packages/wifi_tools.py
tools/ci/test_autocomplete.py

View File

@ -8,3 +8,4 @@ mb_example_common/
examples/cxx/experimental/blink_cxx
examples/peripherals/lcd/lvgl
examples/peripherals/i2s/i2s_es8311
examples/cxx/experimental/simple_spi_rw_example

View File

@ -3,3 +3,4 @@ temp_
examples/bluetooth/bluedroid/ble_50/
examples/cxx/experimental/blink_cxx
examples/cxx/experimental/esp_modem_cxx/
examples/cxx/experimental/simple_spi_rw_example

View File

@ -1,16 +1,5 @@
# Copyright 2015-2017 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.
# SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
""" DUT for IDF applications """
import collections
@ -611,6 +600,24 @@ class ESP32C3DUT(IDFDUT):
return esptool.ESP32C3ROM
class ESP32C6DUT(IDFDUT):
TARGET = 'esp32c6'
TOOLCHAIN_PREFIX = 'riscv32-esp-elf-'
@classmethod
def get_rom(cls):
return esptool.ESP32C6BETAROM
class ESP32H2DUT(IDFDUT):
TARGET = 'esp32h2'
TOOLCHAIN_PREFIX = 'riscv32-esp-elf-'
@classmethod
def get_rom(cls):
return esptool.ESP32H2ROM
class ESP8266DUT(IDFDUT):
TARGET = 'esp8266'
TOOLCHAIN_PREFIX = 'xtensa-lx106-elf-'
@ -621,7 +628,7 @@ class ESP8266DUT(IDFDUT):
def get_target_by_rom_class(cls):
for c in [ESP32DUT, ESP32S2DUT, ESP32S3DUT, ESP32C3DUT, ESP8266DUT, IDFQEMUDUT]:
for c in [ESP32DUT, ESP32S2DUT, ESP32S3DUT, ESP32C3DUT, ESP32C6DUT, ESP32H2DUT, ESP8266DUT, IDFQEMUDUT]:
if c.get_rom() == cls:
return c.TARGET
return None

View File

@ -1,16 +1,6 @@
# Copyright 2015-2017 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.
# SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
import functools
import json
import logging
@ -24,8 +14,8 @@ from tiny_test_fw import TinyFW, Utility
from .DebugUtils import CustomProcess, GDBBackend, OCDBackend # noqa: export DebugUtils for users
from .IDFApp import UT, ComponentUTApp, Example, IDFApp, LoadableElfTestApp, TestApp # noqa: export all Apps for users
from .IDFDUT import (ESP32C3DUT, ESP32C3FPGADUT, ESP32DUT, ESP32QEMUDUT, ESP32S2DUT, # noqa: export DUTs for users
ESP32S3DUT, ESP32S3FPGADUT, ESP8266DUT, IDFDUT)
from .IDFDUT import (ESP32C3DUT, ESP32C3FPGADUT, ESP32C6DUT, ESP32DUT, ESP32H2DUT, # noqa: export DUTs for users
ESP32QEMUDUT, ESP32S2DUT, ESP32S3DUT, ESP32S3FPGADUT, ESP8266DUT, IDFDUT)
from .unity_test_parser import TestFormat, TestResults
# pass TARGET_DUT_CLS_DICT to Env.py to avoid circular dependency issue.
@ -36,6 +26,8 @@ TARGET_DUT_CLS_DICT = {
'ESP32C3': ESP32C3DUT,
'ESP32C3FPGA': ESP32C3FPGADUT,
'ESP32S3FPGA': ESP32S3FPGADUT,
'ESP32C6': ESP32C6DUT,
'ESP32H2': ESP32H2DUT,
}

View File

@ -4,5 +4,6 @@
- expect_any_args
- return_thru_ptr
- array
- ignore
- ignore_arg
- callback