From cf12eb793dab48ac60a59db1e4d3d2e140548a6f Mon Sep 17 00:00:00 2001 From: Mike Dunston Date: Sat, 3 Feb 2024 08:01:43 -0800 Subject: [PATCH 01/13] Fix target subdirectory name (#775) --- applications/hub_test/targets/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/applications/hub_test/targets/Makefile b/applications/hub_test/targets/Makefile index 2eb855acc..0f44f2c8a 100644 --- a/applications/hub_test/targets/Makefile +++ b/applications/hub_test/targets/Makefile @@ -1,4 +1,4 @@ SUBDIRS = \ - linux.86 \ + linux.x86 \ include $(OPENMRNPATH)/etc/recurse.mk From 499e4a4f18e504c98595ef3add1ac8140a65a3d5 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 4 Feb 2024 15:23:19 +0100 Subject: [PATCH 02/13] Adds trailing zero to the cdi XML file written to the filesystem. (#777) Fixes #763 --- src/openlcb/CompileCdiMain.cxx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/openlcb/CompileCdiMain.cxx b/src/openlcb/CompileCdiMain.cxx index 2569b1d87..aef8578a8 100644 --- a/src/openlcb/CompileCdiMain.cxx +++ b/src/openlcb/CompileCdiMain.cxx @@ -27,6 +27,9 @@ void render_cdi_helper(const CdiType &t, string ns, string name) t.config_renderer().render_cdi(&payload); if (raw_render) { + // Adds trailing zero to the file written. + payload.push_back(0); + // Writes the file. string filename = name + ".xmlout"; printf("Writing %d bytes to %s\n", (int)payload.size(), filename.c_str()); From c1047c768ca6b4cb66846be2da72344fce763534 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 4 Feb 2024 15:30:47 +0100 Subject: [PATCH 03/13] Avoids rendering hidden segments. (#767) Previously a hidden segment created incompliant XML, putting a group with an offset at the toplevel CDI. With this PR, the hidden segment will not be rendered at all. --- src/openlcb/ConfigRenderer.cxxtest | 21 +++++++++++++++++++++ src/openlcb/ConfigRenderer.hxx | 6 +++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/openlcb/ConfigRenderer.cxxtest b/src/openlcb/ConfigRenderer.cxxtest index 2d3125bca..5f58e3497 100644 --- a/src/openlcb/ConfigRenderer.cxxtest +++ b/src/openlcb/ConfigRenderer.cxxtest @@ -351,5 +351,26 @@ TEST(CdiRender, RenderIdent) EXPECT_EQ(34u, cfg.testseg().e2().offset()); } + +CDI_GROUP(TestCdi3, MainCdi()); +CDI_GROUP_ENTRY(acdi, Acdi); +CDI_GROUP_ENTRY(testseg, OtherSegment, Hidden(true)); +CDI_GROUP_END(); + +TEST(CdiRender, NoRenderHiddenSegment) +{ + string s; + TestCdi3 cfg(0); + cfg.config_renderer().render_cdi(&s); + const char kExpectedTestNodeCdi[] = "" R"data( + + + +)data"; + EXPECT_EQ(kExpectedTestNodeCdi, s); + EXPECT_EQ(34u, cfg.testseg().e2().offset()); +} + + } // namespace } // namespace openlcb diff --git a/src/openlcb/ConfigRenderer.hxx b/src/openlcb/ConfigRenderer.hxx index c32e55783..cc307870f 100644 --- a/src/openlcb/ConfigRenderer.hxx +++ b/src/openlcb/ConfigRenderer.hxx @@ -406,7 +406,11 @@ public: GroupConfigOptions opts(args..., Body::group_opts()); if (opts.hidden()) { - EmptyGroupConfigRenderer(Body::size() * replication_).render_cdi(s); + if (!opts.is_segment()) + { + EmptyGroupConfigRenderer(Body::size() * replication_) + .render_cdi(s); + } return; } const char *tag = nullptr; From d2c82ca10707c509b61f04e0e28a1118d66a9f3c Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 4 Feb 2024 15:32:02 +0100 Subject: [PATCH 04/13] Fix incorrect consumer identified message being emitted by dcc accy producer. (#768) The range was not set properly, we use two ranges of 4095, but we only identified two ranges of about 2048. Fixes unintentional repetition of the range identify messages. We have two registrations in the event handler tree. === * Fix incorrect consumer identified message being emitted by dcc accy producer. The range was not set properly, we use two ranges of 4095, but we only identified two ranges of about 2048. * Adds tests. * Fixes global handler. It was sending each identify twice. * Fix typo --- src/openlcb/DccAccyConsumer.cxxtest | 18 +++++++++++++++ src/openlcb/DccAccyConsumer.hxx | 34 +++++++++++++++++++---------- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/src/openlcb/DccAccyConsumer.cxxtest b/src/openlcb/DccAccyConsumer.cxxtest index d961e3878..a09dbcb28 100644 --- a/src/openlcb/DccAccyConsumer.cxxtest +++ b/src/openlcb/DccAccyConsumer.cxxtest @@ -58,6 +58,11 @@ public: class DccAccyTest : public AsyncNodeTest { protected: + DccAccyTest() + { + wait(); + } + StrictMock trackSendQueue_; DccAccyConsumer consumer_{node_, &trackSendQueue_}; }; @@ -72,6 +77,19 @@ TEST_F(DccAccyTest, identify_boot_invalid) ":X198F4111N0101020000FF01F0;", ":X194C722AN0101020000FF01F0;"); } +TEST_F(DccAccyTest, global_identify) +{ + clear_expect(true); + // Consumer range for two 4096 long ranges. + expect_packet(":X194A422AN0101020000FF0FFF;"); + expect_packet(":X194A422AN0101020000FE0FFF;"); + + // identify events addressed. + send_packet(":X19968111N022A;"); + wait(); +} + + TEST_F(DccAccyTest, identify_throw_identify) { EXPECT_CALL(trackSendQueue_, arrived(_, _)).Times(2); diff --git a/src/openlcb/DccAccyConsumer.hxx b/src/openlcb/DccAccyConsumer.hxx index 795fa9e82..5bc716829 100644 --- a/src/openlcb/DccAccyConsumer.hxx +++ b/src/openlcb/DccAccyConsumer.hxx @@ -74,21 +74,31 @@ protected: void handle_identify_global(const EventRegistryEntry ®istry_entry, EventReport *event, BarrierNotifiable *done) OVERRIDE { + AutoNotify an(done); if (event->dst_node && event->dst_node != node_) { - return done->notify(); + return; + } + if (registry_entry.event == + TractionDefs::ACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE) + { + event->event_write_helper<1>()->WriteAsync(node_, + Defs::MTI_CONSUMER_IDENTIFIED_RANGE, WriteHelper::global(), + eventid_to_buffer(EncodeRange( + TractionDefs::ACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE, + 4095)), + done->new_child()); + } + if (registry_entry.event == + TractionDefs::INACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE) + { + event->event_write_helper<2>()->WriteAsync(node_, + Defs::MTI_CONSUMER_IDENTIFIED_RANGE, WriteHelper::global(), + eventid_to_buffer(EncodeRange( + TractionDefs::INACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE, + 4095)), + done->new_child()); } - event->event_write_helper<1>()->WriteAsync(node_, - Defs::MTI_CONSUMER_IDENTIFIED_RANGE, WriteHelper::global(), - eventid_to_buffer(EncodeRange( - TractionDefs::ACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE, 2044)), - done->new_child()); - event->event_write_helper<2>()->WriteAsync(node_, - Defs::MTI_CONSUMER_IDENTIFIED_RANGE, WriteHelper::global(), - eventid_to_buffer(EncodeRange( - TractionDefs::INACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE, 2044)), - done->new_child()); - done->notify(); } void handle_event_report(const EventRegistryEntry ®istry_entry, From f0a0091b3d051a5e06bf00feff11a1c13e0ac8eb Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 4 Feb 2024 15:38:39 +0100 Subject: [PATCH 05/13] Adds support for DCC extended accessories (#769) - Adds helper functions to generate dcc extended accessory packets in dcc::Packet - Adds well-known event range (from the current draft eventidentifiers standard) - Adds DccExtAccyConsumer, which listens to events and turns them into DCC packets. Unlike the basic dcc accy consumer, the extended consumer does not remember whatthe last set state was. This is because it takes a lot more memory (2 kbytes), and it's a lot less useful, since different decoders may be interpreting this last set command very differently (such as a signal, a turntable, or a timed turnout decoder). === * Adds command to generate dcc extended accessory packet. * Adds well-known range for dcc extended accessories (according to the draft event identifiers standard). * Adds accessory consumer for extended accessories. Generates the dcc packets on the rails. --- src/dcc/Packet.cxx | 8 ++ src/dcc/Packet.cxxtest | 15 +++ src/dcc/Packet.hxx | 11 +++ src/openlcb/DccAccyConsumer.cxxtest | 62 ++++++++++++ src/openlcb/DccAccyConsumer.hxx | 144 ++++++++++++++++++++++++++++ src/openlcb/TractionDefs.hxx | 2 + 6 files changed, 242 insertions(+) diff --git a/src/dcc/Packet.cxx b/src/dcc/Packet.cxx index 8d51abb29..757a448d5 100644 --- a/src/dcc/Packet.cxx +++ b/src/dcc/Packet.cxx @@ -346,6 +346,14 @@ void Packet::add_dcc_basic_accessory(unsigned address, bool is_activate) add_dcc_checksum(); } +void Packet::add_dcc_ext_accessory(unsigned address, uint8_t aspect) +{ + add_dcc_accy_address(false, address); + payload[dlc++] = aspect; + add_dcc_checksum(); +} + + void Packet::set_dcc_logon_enable( Defs::LogonEnableParam param, uint16_t cid, uint8_t session_id) { diff --git a/src/dcc/Packet.cxxtest b/src/dcc/Packet.cxxtest index 8cae24eb5..530c51968 100644 --- a/src/dcc/Packet.cxxtest +++ b/src/dcc/Packet.cxxtest @@ -373,6 +373,21 @@ TEST_F(PacketTest, DccBasicAccyPom) 0b10011100)); } +TEST_F(PacketTest, DccExtAccySet) +{ + pkt_.add_dcc_ext_accessory(1733, 0x5a); + // 1733 = 0x6c5 = 0b 110 1100 0101 + uint8_t b1 = 0b10110001; + uint8_t b2 = 0b00010011; + uint8_t b3 = 0x5a; + EXPECT_THAT(get_packet(), ElementsAre(b1, b2, b3, b1 ^ b2 ^ b3)); + + pkt_.clear(); + pkt_.add_dcc_ext_accessory(1733, 0x80); + b3 = 0x80; + EXPECT_THAT(get_packet(), ElementsAre(b1, b2, b3, b1 ^ b2 ^ b3)); +} + TEST_F(PacketTest, SvcProgDirectByte) { diff --git a/src/dcc/Packet.hxx b/src/dcc/Packet.hxx index c7ca853ad..712191d98 100644 --- a/src/dcc/Packet.hxx +++ b/src/dcc/Packet.hxx @@ -310,6 +310,17 @@ struct Packet : public DCCPacket /// @param is_normal true for normal, false for reverse /// @param is_activate true for activate, false for deactivate void set_dcc_basic_accy_params(bool is_normal, bool is_activate); + + /// Adds a DCC extended accessory decoder command packet and the checksum + /// byte. + /// @param address is the 11-bit binary address, 0..2047. No bits have to be + /// inverted. This will be A10..A0 on the track. (To convert from a user + /// address, see accy_address_user_to_binary in dcc::Defs.) + /// @param aspect is the argument byte to the extended + /// accessory. Traditionally this was used as an aspect for a signal + /// decoder, but different accessories might have different interpretation + /// of it. + void add_dcc_ext_accessory(unsigned address, uint8_t aspect); /// Sets the packet to a logon enable packet. /// @param param defines which decoders should be requested to logon. diff --git a/src/openlcb/DccAccyConsumer.cxxtest b/src/openlcb/DccAccyConsumer.cxxtest index a09dbcb28..13bbac1f1 100644 --- a/src/openlcb/DccAccyConsumer.cxxtest +++ b/src/openlcb/DccAccyConsumer.cxxtest @@ -140,4 +140,66 @@ TEST_F(DccAccyTest, packet_throw) wait(); } + +class DccExtAccyTest : public AsyncNodeTest +{ +protected: + DccExtAccyTest() + { + wait(); + } + + StrictMock trackSendQueue_; + DccExtAccyConsumer consumer_{node_, &trackSendQueue_}; +}; + +TEST_F(DccExtAccyTest, create) +{ +} + +TEST_F(DccExtAccyTest, global_identify) +{ + clear_expect(true); + // Consumer range for one very long range (11 bit plus 8 bit). + expect_packet(":X194A422AN010102000107FFFF;"); + + // identify events addressed. + send_packet(":X19968111N022A;"); + wait(); +} + + +TEST_F(DccExtAccyTest, identify_unknown) +{ + // unknown identify + send_packet_and_expect_response( + ":X198F4111N0101020001035a77;", ":X194C722AN0101020001035a77;"); +} + +TEST_F(DccExtAccyTest, packet_throw) +{ + uint8_t hdr = 0b01100100; + EXPECT_CALL(trackSendQueue_, + arrived(hdr, ElementsAre(0b10110001, 0b00010011, 0xa5, _))); + + // set to 0xa5 + send_packet(":X195B4111N010102000106c5a5;"); + + + EXPECT_CALL(trackSendQueue_, + arrived(hdr, ElementsAre(0b10110001, 0b00010011, 0x00, _))); + + // set to 0x00 + send_packet(":X195B4111N010102000106c500;"); + + EXPECT_CALL(trackSendQueue_, + arrived(hdr, ElementsAre(0b10110001, 0b00010011, 0xff, _))); + + // set to 0xff + send_packet(":X195B4111N010102000106c5ff;"); + + wait(); +} + + } // namespace openlcb diff --git a/src/openlcb/DccAccyConsumer.hxx b/src/openlcb/DccAccyConsumer.hxx index 5bc716829..f486ea614 100644 --- a/src/openlcb/DccAccyConsumer.hxx +++ b/src/openlcb/DccAccyConsumer.hxx @@ -260,6 +260,150 @@ private: dcc::TrackIf *track_; }; +/// Base (generic protocol) implementation of the DCC extended accessory +/// consumer. Unlike the basic accessory version, this one does not remember +/// the last set state. +class DccExtAccyConsumerBase : public SimpleEventHandler +{ +protected: + /// How may addresses are there for extended accessories. + static constexpr unsigned NUM_ADDRESS = 2048; + /// How may aspects are supported per accessory. + static constexpr unsigned NUM_ASPECT = 256; + /// Total number of events we are listening for. + static constexpr unsigned NUM_EVENT = NUM_ASPECT * NUM_ADDRESS; + + /// Constructs a listener for DCC extended accessory control. + /// @param node is the virtual node that will be listening for events and + /// responding to Identify messages. + DccExtAccyConsumerBase(Node *node) + : node_(node) + { + EventRegistry::instance()->register_handler( + EventRegistryEntry( + this, TractionDefs::EXT_DCC_ACCESSORY_EVENT_BASE), + 11+8 /*number of bits*/); + } + + /// Destructor. + ~DccExtAccyConsumerBase() + { + EventRegistry::instance()->unregister_handler(this); + } + + void handle_identify_global(const EventRegistryEntry ®istry_entry, + EventReport *event, BarrierNotifiable *done) OVERRIDE + { + AutoNotify an(done); + if (event->dst_node && event->dst_node != node_) + { + return; + } + event->event_write_helper<1>()->WriteAsync(node_, + Defs::MTI_CONSUMER_IDENTIFIED_RANGE, WriteHelper::global(), + eventid_to_buffer(EncodeRange( + TractionDefs::EXT_DCC_ACCESSORY_EVENT_BASE, NUM_EVENT - 1)), + done->new_child()); + } + + void handle_event_report(const EventRegistryEntry ®istry_entry, + EventReport *event, BarrierNotifiable *done) override + { + AutoNotify an(done); + if (!parse_event(event->event)) + { + return; + } + send_accy_command(); + } + + void handle_identify_consumer(const EventRegistryEntry &entry, + EventReport *event, BarrierNotifiable *done) override + { + AutoNotify an(done); + if (!parse_event(event->event)) + { + return; + } + event->event_write_helper<1>()->WriteAsync(node_, + Defs::MTI_CONSUMER_IDENTIFIED_UNKNOWN, WriteHelper::global(), + eventid_to_buffer(event->event), done->new_child()); + } + + /// Send the actual accessory command. + virtual void send_accy_command() = 0; + + /// Parses an event into an openlcb accessory offset. + /// @return true if the event is in the accessory range, false if this + /// event can be ignored. + /// @param on_off will be set to true if this is an activate event, false + /// if it is an inactivate event. + /// @param ofs will be set to the offset in the state_ arrays. + /// @param mask will be set to a single bit value that marks the location + /// in the state_ arrays. + bool parse_event(EventId event) + { + if (event >= TractionDefs::EXT_DCC_ACCESSORY_EVENT_BASE && + event < + TractionDefs::EXT_DCC_ACCESSORY_EVENT_BASE + NUM_EVENT) + { + aspect_ = event & 0xff; + dccAddress_ = (event >> 8) & (NUM_ADDRESS - 1); + return true; + } + else + { + return false; + } + } + + + /// Parsed event state: dcc address (0..2047) without inverting or encoding. + unsigned dccAddress_ : 11; + /// Parsed event state: the aspect commanded. + unsigned aspect_ : 8; + + /// OpenLCB node to export the consumer on. + Node *node_; +}; + +/// Specialized (DCC protocol) implementation of a DCC extended accessory +/// consumer. +class DccExtAccyConsumer : public DccExtAccyConsumerBase +{ +public: + /// Constructs a listener for DCC accessory control. + /// @param node is the virtual node that will be listening for events and + /// responding to Identify messages. + /// @param track is the interface through which we will be writing DCC + /// accessory packets. + DccExtAccyConsumer(Node *node, dcc::TrackIf *track) + : DccExtAccyConsumerBase(node) + , track_(track) + { + } + + /// Destructor. + ~DccExtAccyConsumer() + { + } + +private: + /// Send the actual accessory command. + void send_accy_command() override + { + dcc::TrackIf::message_type *pkt; + mainBufferPool->alloc(&pkt); + pkt->data()->add_dcc_ext_accessory(dccAddress_, aspect_); + pkt->data()->packet_header.rept_count = 3; + track_->send(pkt); + } + + /// Track to send DCC packets to. + dcc::TrackIf *track_; +}; + + } // namespace openlcb #endif // _OPENLCB_DCCACCYCONSUMER_HXX_ diff --git a/src/openlcb/TractionDefs.hxx b/src/openlcb/TractionDefs.hxx index 2ab6dbdb6..4927b0d4b 100644 --- a/src/openlcb/TractionDefs.hxx +++ b/src/openlcb/TractionDefs.hxx @@ -73,6 +73,8 @@ struct TractionDefs { static constexpr uint64_t ACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE = 0x0101020000FF0000ULL; /// Base address of DCC accessory decoder well-known event range (inactive) static constexpr uint64_t INACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE = 0x0101020000FE0000ULL; + /// Base address of DCC extended accessory decoder well-known event range + static constexpr uint64_t EXT_DCC_ACCESSORY_EVENT_BASE = 0x0101020001000000ULL; /// Node ID space allocated for DC blocks. static const uint64_t NODE_ID_DC_BLOCK = 0x060000000000ULL; /// Node ID space allocated for DCC locomotives. From c5adf0ccf256c42add4ef7210632b58fefbd7afb Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 4 Feb 2024 07:02:51 -0800 Subject: [PATCH 06/13] Fix comments. --- src/freertos_drivers/esp32/Esp32Gpio.hxx | 102 ------------------ .../esp32/Esp32HardwareTwai.cxx | 5 +- src/freertos_drivers/esp32/Esp32Ledc.cxx | 2 +- 3 files changed, 3 insertions(+), 106 deletions(-) diff --git a/src/freertos_drivers/esp32/Esp32Gpio.hxx b/src/freertos_drivers/esp32/Esp32Gpio.hxx index c89312abf..f60a44cfc 100644 --- a/src/freertos_drivers/esp32/Esp32Gpio.hxx +++ b/src/freertos_drivers/esp32/Esp32Gpio.hxx @@ -43,7 +43,6 @@ #include #include -#include #include @@ -561,105 +560,4 @@ template struct GpioInputPUPD : public GpioInputPin NAME##_Pin -/// Helper macro for an ADC GPIO input on the ESP32. -/// -/// @param NAME is the basename of the declaration. For NAME==FOO the macro -/// declared FOO_Pin as a structure on which the read-write functions will be -/// available. -/// @param ADC_CHANNEL is the ADC channel to configure. -/// @param ATTENUATION is the voltage range for the ADC input. -/// @param BIT_RANGE is the bit range to configure the ADC to use. -/// -/// Supported ATTENUATION values and voltage ranges: -/// ADC_ATTEN_DB_0 - 0dB attenuaton gives full-scale voltage 1.1V -/// ADC_ATTEN_DB_2_5 - 2.5dB attenuation gives full-scale voltage 1.5V -/// ADC_ATTEN_DB_6 - 6dB attenuation gives full-scale voltage 2.2V -/// ADC_ATTEN_DB_11 - 11dB attenuation gives full-scale voltage 3.9V -/// -/// Supported BIT_RANGE values and ADC sample values: -/// ADC_WIDTH_BIT_9 - 0-511 -/// ADC_WIDTH_BIT_10 - 0-1023 -/// ADC_WIDTH_BIT_11 - 0-2047 -/// ADC_WIDTH_BIT_12 - 0-4065 -/// ADC_WIDTH_BIT_13 - 0-8191 -- Only valid on the ESP32-S2 and ESP32-S3. -/// NOTE: When using ADC1_CHANNEL_X this bit range will be applied to all -/// ADC1 channels, it is not recommended to mix values for ADC1 channels. -/// -/// Supported ADC_CHANNEL values and pin assignments for the ESP32: -/// ADC1_CHANNEL_0 : 36 -/// ADC1_CHANNEL_1 : 37 -- NOTE: Not recommended for use, see note below. -/// ADC1_CHANNEL_2 : 38 -- NOTE: Not recommended for use, see note below. -/// ADC1_CHANNEL_3 : 39 -/// ADC1_CHANNEL_4 : 32 -/// ADC1_CHANNEL_5 : 33 -/// ADC1_CHANNEL_6 : 34 -/// ADC1_CHANNEL_7 : 35 -/// ADC2_CHANNEL_0 : 4 -- NOTE: Not usable when WiFi is active. -/// ADC2_CHANNEL_1 : 0 -- NOTE: Not usable when WiFi is active. -/// ADC2_CHANNEL_2 : 2 -- NOTE: Not usable when WiFi is active. -/// ADC2_CHANNEL_3 : 15 -- NOTE: Not usable when WiFi is active. -/// ADC2_CHANNEL_4 : 13 -- NOTE: Not usable when WiFi is active. -/// ADC2_CHANNEL_5 : 12 -- NOTE: Not usable when WiFi is active. -/// ADC2_CHANNEL_6 : 14 -- NOTE: Not usable when WiFi is active. -/// ADC2_CHANNEL_7 : 27 -- NOTE: Not usable when WiFi is active. -/// ADC2_CHANNEL_8 : 25 -- NOTE: Not usable when WiFi is active. -/// ADC2_CHANNEL_9 : 29 -- NOTE: Not usable when WiFi is active. -/// NOTE: ADC1_CHANNEL_1 and ADC1_CHANNEL_2 typically have a capacitor which -/// connects to ADC1_CHANNEL_0 or ADC1_CHANNEL_3. The only known exception to -/// this is for some ESP32-PICO-D4/ESP32-PICO-V3 based boards, confirm on the -/// board schematic before using these pins. -/// -/// Supported ADC_CHANNEL values and pin assignments for the ESP32-S2/ESP32-S3: -/// ADC1_CHANNEL_0 : 1 -/// ADC1_CHANNEL_1 : 2 -/// ADC1_CHANNEL_2 : 3 -/// ADC1_CHANNEL_3 : 4 -/// ADC1_CHANNEL_4 : 5 -/// ADC1_CHANNEL_5 : 6 -/// ADC1_CHANNEL_6 : 7 -/// ADC1_CHANNEL_7 : 8 -/// ADC1_CHANNEL_8 : 9 -/// ADC1_CHANNEL_9 : 10 -/// ADC2_CHANNEL_0 : 11 -/// ADC2_CHANNEL_1 : 12 -/// ADC2_CHANNEL_2 : 13 -/// ADC2_CHANNEL_3 : 14 -/// ADC2_CHANNEL_4 : 15 -/// ADC2_CHANNEL_5 : 16 -/// ADC2_CHANNEL_6 : 17 -/// ADC2_CHANNEL_7 : 18 -/// ADC2_CHANNEL_8 : 19 -- NOTE: This pin is also used for USB PHY (D-). -/// ADC2_CHANNEL_9 : 20 -- NOTE: This pin is also used for USB PHY (D+). -/// -/// Supported ADC_CHANNEL values and pin assignments for the ESP32-C3: -/// ADC1_CHANNEL_0 : 0 -/// ADC1_CHANNEL_1 : 1 -/// ADC1_CHANNEL_2 : 2 -/// ADC1_CHANNEL_3 : 3 -/// ADC1_CHANNEL_4 : 4 -/// ADC2_CHANNEL_0 : 5 -/// -/// Example: -/// ADC_PIN(SENSE, ADC1_CHANNEL_0, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12); -/// ... -/// int level = SENSE_Pin::sample(); -#define ADC_PIN(NAME, ADC_CHANNEL, ATTENUATION, BIT_RANGE) \ - struct NAME##Defs \ - { \ - static const adc_channel_t CHANNEL = (adc_channel_t)ADC_CHANNEL; \ - static const gpio_num_t PIN = (gpio_num_t)ADC_CHANNEL##_GPIO_NUM; \ - static const adc_atten_t ATTEN = (adc_atten_t)ATTENUATION; \ - static const adc_bits_width_t BITS = (adc_bits_width_t)BIT_RANGE; \ - public: \ - static const gpio_num_t pin() \ - { \ - return PIN; \ - } \ - static const adc_channel_t channel() \ - { \ - return CHANNEL; \ - } \ - }; \ - typedef Esp32ADCInput NAME##_Pin - #endif // _DRIVERS_ESP32GPIO_HXX_ diff --git a/src/freertos_drivers/esp32/Esp32HardwareTwai.cxx b/src/freertos_drivers/esp32/Esp32HardwareTwai.cxx index 645eaa422..04d5c6b82 100644 --- a/src/freertos_drivers/esp32/Esp32HardwareTwai.cxx +++ b/src/freertos_drivers/esp32/Esp32HardwareTwai.cxx @@ -35,8 +35,7 @@ * @date 1 May 2021 */ -// Ensure we only compile this code for the ESP32 family of MCUs and that the -// ESP-IDF version is supported for this code. +// Ensure we only compile this code for the ESP32 family of MCUs. #if defined(ESP_PLATFORM) #include "sdkconfig.h" @@ -714,7 +713,7 @@ static void twai_isr(void *arg) { BaseType_t wakeup = pdFALSE; uint32_t events = twai_hal_get_events(&twai.context); - ESP_EARLY_LOGV(TWAI_LOG_TAG, "events: %04x", events); + ESP_EARLY_LOGV(TWAI_LOG_TAG, "events: %04" PRIx32, events); #if defined(CONFIG_TWAI_ERRATA_FIX_RX_FRAME_INVALID) || \ defined(CONFIG_TWAI_ERRATA_FIX_RX_FIFO_CORRUPT) diff --git a/src/freertos_drivers/esp32/Esp32Ledc.cxx b/src/freertos_drivers/esp32/Esp32Ledc.cxx index feb10bbc4..7dfa35b83 100644 --- a/src/freertos_drivers/esp32/Esp32Ledc.cxx +++ b/src/freertos_drivers/esp32/Esp32Ledc.cxx @@ -33,7 +33,7 @@ */ // Ensure we only compile this code for the ESP32 family of MCUs. -#if defined(ESP32) +#if defined(ESP_PLATFORM) #include "Esp32Ledc.hxx" From 8c7cc44f37a9eed58e981e43d10400ae744f7198 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 4 Feb 2024 16:12:14 +0100 Subject: [PATCH 07/13] Upintegrate changes from the OpenMRNIDF repository (#771) This PR pulls in changes that happened in the OpenMRNIDF repository. Most of these were authored by Mike Dunston. - All instances of ifdef ESP32 replaced with ESP_PLATFORM - Removed conditional code for supporting ESP-IDF v3.x and v4.x Only ESP-IDF v5 and up is now supported - new feature for OPENMRN_HAVE_SOCKET_FSTAT - fixed compile errors around spiram - refactored definition of ADC pin into a separate class in a separate file - fixes around printf PRIu32. - do not print error messages on CAN overflow - fix to not start the hub twice in the wifi manager - Remove deprecated hardware can driver - Adds I2C driver written by Mike Dunston for the ESP32. === * Upintegrate changes from OpenMRNIDF. - All instances of ifdef ESP32 replaced with ESP_PLATFORM - Removed conditional code for supporting ESP-IDF v3.x and v4.x Only ESP-IDF v5 and up is now supported - new feature for OPENMRN_HAVE_SOCKET_FSTAT - fixed compile errors around spiram - refactored definition of ADC pin. - fixes around printf PRIu32. - do not print error messages on CAN overflow - fix to not start the hub twice in the wifi manager * Fix compile error. * Remove deprecated hardware can driver. * Adds I2C driver written by Mike Dunston for the ESP32. * Adds refactored Adc Input class. * Fix comments. --- arduino/OpenMRNLite.cpp | 4 +- arduino/OpenMRNLite.h | 14 +- arduino/libify.sh | 2 +- include/can_frame.h | 2 +- include/freertos/can_ioctl.h | 2 +- include/freertos/freertos_includes.h | 54 +- include/freertos/stropts.h | 4 +- include/i2c-dev.h | 2 +- include/i2c.h | 2 +- include/openmrn_features.h | 33 +- src/executor/Executor.hxx | 4 +- src/executor/StateFlow.hxx | 13 +- src/freertos_drivers/common/libatomic.c | 2 +- .../esp32/Esp32AdcOneShot.hxx | 217 ++++++++ .../esp32/Esp32BootloaderHal.hxx | 11 +- src/freertos_drivers/esp32/Esp32Gpio.hxx | 194 +------ .../esp32/Esp32HardwareCanAdapter.hxx | 428 --------------- .../esp32/Esp32HardwareI2C.cpp | 513 ++++++++++++++++++ .../esp32/Esp32HardwareI2C.hxx | 230 ++++++++ .../esp32/Esp32HardwareTwai.cxx | 81 ++- .../esp32/Esp32HardwareTwai.hxx | 8 +- src/freertos_drivers/esp32/Esp32Ledc.cxx | 10 +- src/freertos_drivers/esp32/Esp32Ledc.hxx | 8 +- src/freertos_drivers/esp32/Esp32SocInfo.cxx | 22 +- src/freertos_drivers/esp32/Esp32SocInfo.hxx | 11 +- .../esp32/Esp32WiFiManager.cxx | 199 ++----- .../esp32/Esp32WiFiManager.hxx | 29 +- src/openlcb/ApplicationChecksum.hxx | 2 +- src/openlcb/Bootloader.hxx | 2 +- src/openlcb/BroadcastTime.cxx | 2 +- src/openlcb/ServoConsumer.hxx | 2 +- src/os/OS.hxx | 2 +- src/os/OSSelectWakeup.cxx | 85 +-- src/os/OSSelectWakeup.hxx | 29 +- src/os/os.c | 4 +- src/utils/Atomic.hxx | 2 +- src/utils/HubDevice.hxx | 5 +- src/utils/SocketClient.cxx | 10 +- src/utils/SocketClient.hxx | 3 +- src/utils/constants.cxx | 4 +- src/utils/logging.cxx | 5 +- src/utils/logging.h | 2 +- src/utils/macros.h | 11 +- src/utils/socket_listener.cxx | 5 +- src/utils/stdio_logging.h | 2 +- 45 files changed, 1219 insertions(+), 1057 deletions(-) create mode 100644 src/freertos_drivers/esp32/Esp32AdcOneShot.hxx delete mode 100644 src/freertos_drivers/esp32/Esp32HardwareCanAdapter.hxx create mode 100644 src/freertos_drivers/esp32/Esp32HardwareI2C.cpp create mode 100644 src/freertos_drivers/esp32/Esp32HardwareI2C.hxx diff --git a/arduino/OpenMRNLite.cpp b/arduino/OpenMRNLite.cpp index d38f6b283..d1968a199 100644 --- a/arduino/OpenMRNLite.cpp +++ b/arduino/OpenMRNLite.cpp @@ -43,7 +43,7 @@ OpenMRN::OpenMRN(openlcb::NodeID node_id) init(node_id); } -#ifdef ESP32 +#ifdef ESP_PLATFORM extern "C" { #ifndef OPENMRN_EXCLUDE_REBOOT_IMPL @@ -62,6 +62,6 @@ ssize_t os_get_free_heap() #endif // OPENMRN_EXCLUDE_FREE_HEAP_IMPL } -#endif // ESP32 +#endif // ESP_PLATFORM } // namespace openmrn_arduino diff --git a/arduino/OpenMRNLite.h b/arduino/OpenMRNLite.h index 87bcdbf29..ee97336b1 100644 --- a/arduino/OpenMRNLite.h +++ b/arduino/OpenMRNLite.h @@ -48,9 +48,8 @@ #include "utils/logging.h" #include "utils/Uninitialized.hxx" -#if defined(ESP32) +#if defined(ESP_PLATFORM) -#include #include #include @@ -70,8 +69,6 @@ constexpr UBaseType_t OPENMRN_TASK_PRIORITY = ESP_TASK_TCPIP_PRIO - 1; #include "freertos_drivers/esp32/Esp32Gpio.hxx" #include "freertos_drivers/esp32/Esp32SocInfo.hxx" -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,3,0) - // If we are using ESP-IDF v4.3 (or later) enable the Esp32Ledc API. #include "freertos_drivers/esp32/Esp32Ledc.hxx" @@ -92,15 +89,6 @@ constexpr UBaseType_t OPENMRN_TASK_PRIORITY = ESP_TASK_TCPIP_PRIO - 1; #endif // NOT ESP32-H2 and NOT ESP32-C2 -#endif // IDF v4.3+ - -#if defined(CONFIG_IDF_TARGET_ESP32) -// Note: This code is deprecated in favor of the TWAI interface which exposes -// both select() and fnctl() interfaces. Support for this may be removed in the -// future. -#include "freertos_drivers/esp32/Esp32HardwareCanAdapter.hxx" -#endif // ESP32 only - #include "freertos_drivers/esp32/Esp32HardwareSerialAdapter.hxx" #include "freertos_drivers/esp32/Esp32WiFiManager.hxx" diff --git a/arduino/libify.sh b/arduino/libify.sh index 61ce6df88..9835914ba 100755 --- a/arduino/libify.sh +++ b/arduino/libify.sh @@ -134,7 +134,7 @@ copy_file . arduino/{library.json,library.properties,keywords.txt,README.md,LICE copy_dir . arduino/examples copy_file src arduino/OpenMRNLite.{h,cpp} arduino/CDIXMLGenerator.hxx \ - include/{can_frame.h,nmranet_config.h,openmrn_features.h} \ + include/{can_frame.h,nmranet_config.h,openmrn_features.h,i2c.h,i2c-dev.h} \ include/freertos/{bootloader_hal.h,can_ioctl.h,endian.h,freertos_includes.h,stropts.h} \ include/freertos_select/ifaddrs.h diff --git a/include/can_frame.h b/include/can_frame.h index 7ebef825b..4e0028e7f 100644 --- a/include/can_frame.h +++ b/include/can_frame.h @@ -65,7 +65,7 @@ #elif defined (__nuttx__) || defined (__FreeRTOS__) || defined (__MACH__) || \ defined (__WIN32__) || defined (__EMSCRIPTEN__) || defined (ESP_NONOS) || \ - defined (ARDUINO) || defined (ESP32) + defined (ARDUINO) || defined (ESP_PLATFORM) #include struct can_frame diff --git a/include/freertos/can_ioctl.h b/include/freertos/can_ioctl.h index 69f5d228e..8b79d1d38 100644 --- a/include/freertos/can_ioctl.h +++ b/include/freertos/can_ioctl.h @@ -37,7 +37,7 @@ #include #ifdef __FreeRTOS__ #include "freertos/stropts.h" -#elif defined(ESP32) +#elif defined(ESP_PLATFORM) #include "stropts.h" #endif diff --git a/include/freertos/freertos_includes.h b/include/freertos/freertos_includes.h index 86d444ffc..7245c39d3 100644 --- a/include/freertos/freertos_includes.h +++ b/include/freertos/freertos_includes.h @@ -1,12 +1,64 @@ -#ifdef ESP32 +/** \copyright + * Copyright (c) 2019, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file freertos_includes.h + * This file simplifies the include path for FreeRTOS header files between + * platforms. + * + * @author Balazs Racz + * @date 2 March 2019 + */ + +#ifdef ESP_PLATFORM #include #include #include +#include "sdkconfig.h" #define NSEC_TO_TICK(ns) \ (((((long long)(ns)) / 1000 * configTICK_RATE_HZ) + 999999) / 1000000) +// IDF v5.0 has introduced a configuration option (disabled by default) which +// enables the usage of legacy FreeRTOS data types, if that configuration option +// is *NOT* selected *AND* IDF v5.0+ is in use we need to add compatibility +// defines in order to compile OpenMRN successfully. +#if !defined(CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY) + +// used in os/os.c and os/os.h +#define portTickType TickType_t +#define xTaskHandle TaskHandle_t +#define xQueueHandle QueueHandle_t +#define xSemaphoreHandle SemaphoreHandle_t + +// used in freertos_drivers/arduino/CpuLoad.hxx and os/os.c +#define pcTaskGetTaskName pcTaskGetName + +#endif // IDF v5.0+ and !CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY + #else #include diff --git a/include/freertos/stropts.h b/include/freertos/stropts.h index b0df5ab77..2ec587fd9 100644 --- a/include/freertos/stropts.h +++ b/include/freertos/stropts.h @@ -38,7 +38,7 @@ extern "C" { #endif -#if defined(ESP32) +#if defined(ESP_PLATFORM) #include #else /** Request and ioctl transaction @@ -47,7 +47,7 @@ extern "C" { * @param ... key data (as a pointer or unsigned long type) */ int ioctl(int fd, unsigned long int key, ...); -#endif // ESP32 +#endif // ESP_PLATFORM /** ioctl key value for operation (not read or write) */ #define IOC_NONE 0U diff --git a/include/i2c-dev.h b/include/i2c-dev.h index 6b20e9c1e..ea208b434 100644 --- a/include/i2c-dev.h +++ b/include/i2c-dev.h @@ -36,7 +36,7 @@ #if defined (__linux__) #include -#elif defined (__FreeRTOS__) +#elif defined (__FreeRTOS__) || defined(ESP_PLATFORM) #include /** magic number for this driver's ioctl calls */ #define I2C_MAGIC ('i') diff --git a/include/i2c.h b/include/i2c.h index 8bf8af1e0..bf6253108 100644 --- a/include/i2c.h +++ b/include/i2c.h @@ -36,7 +36,7 @@ #if defined (__linux__) #include -#elif defined (__FreeRTOS__) +#elif defined (__FreeRTOS__) || defined(ESP_PLATFORM) #include /** Used in @ref i2c_rdwr_ioctl_data to describe a transaction segment. */ struct i2c_msg diff --git a/include/openmrn_features.h b/include/openmrn_features.h index 810e34cfd..79c5e2ba3 100644 --- a/include/openmrn_features.h +++ b/include/openmrn_features.h @@ -37,12 +37,12 @@ #ifndef _INCLUDE_OPENMRN_FEATURES_ #define _INCLUDE_OPENMRN_FEATURES_ -#ifdef ESP32 +#ifdef ESP_PLATFORM #include #else #define ESP_IDF_VERSION 0 #define ESP_IDF_VERSION_VAL(a,b,c) 1 -#endif // ESP32 +#endif // ESP_PLATFORM #ifdef __FreeRTOS__ /// Compiles the FreeRTOS event group based ::select() implementation. @@ -54,43 +54,50 @@ #define OPENMRN_FEATURE_REENT 1 #endif -#if defined(__FreeRTOS__) || ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,3,0) +#if defined(__FreeRTOS__) || defined(ESP_PLATFORM) // Note: this is not using OPENMRN_FEATURE_DEVICE_SELECT due to other usages // of that macro which may conflict with the ESP32 version of this feature. /// Adds support for FD based CAN interfaces. #define OPENMRN_FEATURE_FD_CAN_DEVICE 1 #endif -#if defined(__linux__) || defined(__MACH__) || defined(__WINNT__) || defined(ESP32) || defined(OPENMRN_FEATURE_DEVTAB) +#if defined(__linux__) || defined(__MACH__) || defined(__WINNT__) || \ + defined(ESP_PLATFORM) || defined(OPENMRN_FEATURE_DEVTAB) /// Enables the code using ::open ::close ::read ::write for non-volatile /// storage, FileMemorySpace for the configuration space, and /// SNIP_DYNAMIC_FILE_NAME for node names. #define OPENMRN_HAVE_POSIX_FD 1 #endif +#if !defined(ESP_PLATFORM) +/// Enables the code using ::fstat to confirm if the file handle is a socket. +#define OPENMRN_HAVE_SOCKET_FSTAT 1 +#endif + /// @todo this should probably be a whitelist: __linux__ || __MACH__. -#if !defined(__FreeRTOS__) && !defined(__WINNT__) && !defined(ESP32) && \ +#if !defined(__FreeRTOS__) && !defined(__WINNT__) && !defined(ESP_PLATFORM) && \ !defined(ARDUINO) && !defined(ESP_NONOS) /// Uses ::pselect in the Executor for sleep and pkill for waking up. #define OPENMRN_HAVE_PSELECT 1 #endif -#if defined(__WINNT__) || defined(ESP32) || defined(ESP_NONOS) +#if defined(__WINNT__) || defined(ESP_PLATFORM) || defined(ESP_NONOS) /// Uses ::select in the executor to sleep (unsure how wakeup is handled) #define OPENMRN_HAVE_SELECT 1 #endif -#if defined(OPENMRN_HAVE_SELECT) || defined(OPENMRN_HAVE_PSELECT) || defined(OPENMRN_FEATURE_DEVICE_SELECT) +#if defined(OPENMRN_HAVE_SELECT) || defined(OPENMRN_HAVE_PSELECT) || \ + defined(OPENMRN_FEATURE_DEVICE_SELECT) #define OPENMRN_FEATURE_EXECUTOR_SELECT 1 #endif -#if (defined(ARDUINO) && !defined(ESP32)) || defined(ESP_NONOS) || \ +#if (defined(ARDUINO) && !defined(ESP_PLATFORM)) || defined(ESP_NONOS) || \ defined(__EMSCRIPTEN__) /// A loop() function is calling the executor in the single-threaded OS context. #define OPENMRN_FEATURE_SINGLE_THREADED 1 #endif -#if defined(__FreeRTOS__) || defined(ESP32) +#if defined(__FreeRTOS__) || defined(ESP_PLATFORM) /// Use os_mutex_... implementation based on FreeRTOS mutex and semaphores. #define OPENMRN_FEATURE_MUTEX_FREERTOS 1 @@ -111,7 +118,7 @@ #define OPENMRN_FEATURE_SEM_TIMEDWAIT 1 #endif -#if defined(__FreeRTOS__) || defined(ESP32) +#if defined(__FreeRTOS__) || defined(ESP_PLATFORM) /// Use FreeRTOS implementation for os_thread_create and keeping a list of live /// threads. #define OPENMRN_FEATURE_THREAD_FREERTOS 1 @@ -131,7 +138,7 @@ #endif #if defined(__linux__) || defined(__MACH__) || defined(__FreeRTOS__) || \ - defined(ESP32) + defined(ESP_PLATFORM) /// Compiles support for BSD sockets API. #define OPENMRN_FEATURE_BSD_SOCKETS 1 @@ -141,7 +148,7 @@ #define OPENMRN_HAVE_BSD_SOCKETS_RX_TIMEOUT 1 #endif -#if !defined(__FreeRTOS__) && !defined(ESP32) +#if !defined(__FreeRTOS__) && !defined(ESP_PLATFORM) /// Compiles support for calling getsockname when binding a socket to a port /// when listening for incoming connections. #define OPENMRN_HAVE_BSD_SOCKETS_GETSOCKNAME 1 @@ -157,7 +164,7 @@ #define OPENMRN_FEATURE_BSD_SOCKETS_IGNORE_SIGPIPE 1 #endif -#if defined(__linux__) || defined(__MACH__) || defined(ESP32) +#if defined(__linux__) || defined(__MACH__) || defined(ESP_PLATFORM) /// Compiles support for reporting EOF as an error for read/write. #define OPENMRN_FEATURE_BSD_SOCKETS_REPORT_EOF_ERROR 1 #endif diff --git a/src/executor/Executor.hxx b/src/executor/Executor.hxx index 39cd8c57f..a760a56b3 100644 --- a/src/executor/Executor.hxx +++ b/src/executor/Executor.hxx @@ -323,7 +323,7 @@ public: */ void add_from_isr(Executable *msg, unsigned priority = UINT_MAX) override { -#ifdef ESP32 +#ifdef ESP_PLATFORM // On the ESP32 we need to call insert instead of insert_locked to // ensure that all code paths lock the queue for consistency since // this code path is not guaranteed to be protected by a critical @@ -333,7 +333,7 @@ public: #else queue_.insert_locked( msg, priority >= NUM_PRIO ? NUM_PRIO - 1 : priority); -#endif // ESP32 +#endif // ESP_PLATFORM selectHelper_.wakeup_from_isr(); } #endif // OPENMRN_FEATURE_RTOS_FROM_ISR diff --git a/src/executor/StateFlow.hxx b/src/executor/StateFlow.hxx index d52b83817..7cffff065 100644 --- a/src/executor/StateFlow.hxx +++ b/src/executor/StateFlow.hxx @@ -355,6 +355,7 @@ protected: Pool *p = pool; if (!p) { + HASSERT(target_flow != nullptr); p = target_flow->pool(); } LOG(VERBOSE, "allocate from pool %p, main pool %p", p, mainBufferPool); @@ -736,14 +737,12 @@ protected: */ Action listen_and_call(StateFlowSelectHelper *helper, int fd, Callback c) { -// ESP-IDF does not implement fstat for the LwIP VFS layer -// https://github.com/espressif/esp-idf/issues/7198 -#ifndef ESP32 +#if OPENMRN_HAVE_SOCKET_FSTAT // verify that the fd is a socket struct stat stat; fstat(fd, &stat); HASSERT(S_ISSOCK(stat.st_mode)); -#endif // ESP32 +#endif // OPENMRN_HAVE_SOCKET_FSTAT helper->reset(Selectable::READ, fd, Selectable::MAX_PRIO); helper->set_wakeup(this); @@ -759,14 +758,12 @@ protected: */ Action connect_and_call(StateFlowSelectHelper *helper, int fd, Callback c) { -// ESP-IDF does not implement fstat for the LwIP VFS layer -// https://github.com/espressif/esp-idf/issues/7198 -#ifndef ESP32 +#if OPENMRN_HAVE_SOCKET_FSTAT // verify that the fd is a socket struct stat stat; fstat(fd, &stat); HASSERT(S_ISSOCK(stat.st_mode)); -#endif // ESP32 +#endif // OPENMRN_HAVE_SOCKET_FSTAT helper->reset(Selectable::WRITE, fd, Selectable::MAX_PRIO); helper->set_wakeup(this); diff --git a/src/freertos_drivers/common/libatomic.c b/src/freertos_drivers/common/libatomic.c index bd7db1f98..094286e52 100644 --- a/src/freertos_drivers/common/libatomic.c +++ b/src/freertos_drivers/common/libatomic.c @@ -35,7 +35,7 @@ #include -#if defined(STM32F0xx) || (!defined(ARDUINO) && !defined(ESP32)) +#if defined(STM32F0xx) || (!defined(ARDUINO) && !defined(ESP_PLATFORM)) // On Cortex-M0 the only way to do atomic operation is to disable interrupts. /// Disables interrupts and saves the interrupt enable flag in a register. diff --git a/src/freertos_drivers/esp32/Esp32AdcOneShot.hxx b/src/freertos_drivers/esp32/Esp32AdcOneShot.hxx new file mode 100644 index 000000000..8af0ee0a3 --- /dev/null +++ b/src/freertos_drivers/esp32/Esp32AdcOneShot.hxx @@ -0,0 +1,217 @@ +/** \copyright + * Copyright (c) 2023, Mike Dunston + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file Esp32AdcOneShot.hxx + * + * Helper declarations for using ADC pins via the ADC One-Shot ESP-IDF APIs. + * + * @author Mike Dunston + * @date 8 Feburary 2023 + */ + +#ifndef _DRIVERS_ESP32ADCONESHOT_HXX_ +#define _DRIVERS_ESP32ADCONESHOT_HXX_ + +#include "utils/logging.h" + +#if defined(__has_include) +#if __has_include() +// include legacy types so ADC1_CHANNEL_0 and ADC1_CHANNEL_0_GPIO_NUM etc are +// defined, soc/adc_channel.h includes references to these on release/v5.0 +// branch and is planned for update in later relase versions. +#include +#endif // __has_include driver/adc_types_legacy.h +#endif // has_include + +#include +#include + +/// Defines an ADC input pin. +/// +/// Do not use this class directly. Use @ref ADC_PIN instead. +template struct Esp32ADCInput : public Defs +{ +public: + using Defs::ATTEN; + using Defs::BITS; + using Defs::CHANNEL; + using Defs::PIN; + using Defs::HANDLE; + + static void hw_init() + { + // due to using #if/#elif/#endif it is not possible to include this in + // the ADC_PIN wrapper code. + const adc_oneshot_unit_init_cfg_t unit_config = + { +#if CONFIG_IDF_TARGET_ESP32 + .unit_id = PIN >= 30 ? ADC_UNIT_1 : ADC_UNIT_2, +#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + .unit_id = PIN <= 10 ? ADC_UNIT_1 : ADC_UNIT_2, +#elif CONFIG_IDF_TARGET_ESP32C3 + .unit_id = PIN <= 4 ? ADC_UNIT_1 : ADC_UNIT_2, +#endif + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + const adc_oneshot_chan_cfg_t channel_config = + { + .atten = ATTEN, + .bitwidth = BITS, + }; + + LOG(VERBOSE, + "[Esp32ADCInput] Configuring ADC%d:%d input pin %d, " + "attenuation %d, bits %d", + unit_config.unit_id, CHANNEL, PIN, ATTEN, BITS); + ESP_ERROR_CHECK(adc_oneshot_new_unit(&unit_config, &HANDLE)); + ESP_ERROR_CHECK( + adc_oneshot_config_channel(HANDLE, CHANNEL, &channel_config)); + } + + /// NO-OP + static void hw_set_to_safe() + { + // NO-OP + } + + /// NO-OP + static void set(bool value) + { + // NO-OP + } + + static int sample() + { + int value = 0; + ESP_ERROR_CHECK(adc_oneshot_read(HANDLE, CHANNEL, &value)); + return value; + } +}; + +/// Helper macro for an ADC GPIO input on the ESP32. +/// +/// @param NAME is the basename of the declaration. For NAME==FOO the macro +/// declared FOO_Pin as a structure on which the read-write functions will be +/// available. +/// @param ADC_CHANNEL is the ADC channel to configure. +/// @param ATTENUATION is the voltage range for the ADC input. +/// @param BIT_RANGE is the bit range to configure the ADC to use. +/// +/// Supported ATTENUATION values and voltage ranges: +/// ADC_ATTEN_DB_0 - 0dB attenuaton gives full-scale voltage 1.1V +/// ADC_ATTEN_DB_2_5 - 2.5dB attenuation gives full-scale voltage 1.5V +/// ADC_ATTEN_DB_6 - 6dB attenuation gives full-scale voltage 2.2V +/// ADC_ATTEN_DB_11 - 11dB attenuation gives full-scale voltage 3.9V +/// +/// Supported BIT_RANGE values and ADC sample values: +/// ADC_WIDTH_BIT_9 - 0-511 +/// ADC_WIDTH_BIT_10 - 0-1023 +/// ADC_WIDTH_BIT_11 - 0-2047 +/// ADC_WIDTH_BIT_12 - 0-4065 +/// ADC_WIDTH_BIT_13 - 0-8191 -- Only valid on the ESP32-S2 and ESP32-S3. +/// NOTE: When using ADC1_CHANNEL_X this bit range will be applied to all +/// ADC1 channels, it is not recommended to mix values for ADC1 channels. +/// +/// Supported ADC_CHANNEL values and pin assignments for the ESP32: +/// ADC1_CHANNEL_0 : 36 +/// ADC1_CHANNEL_1 : 37 -- NOTE: Not recommended for use, see note below. +/// ADC1_CHANNEL_2 : 38 -- NOTE: Not recommended for use, see note below. +/// ADC1_CHANNEL_3 : 39 +/// ADC1_CHANNEL_4 : 32 +/// ADC1_CHANNEL_5 : 33 +/// ADC1_CHANNEL_6 : 34 +/// ADC1_CHANNEL_7 : 35 +/// ADC2_CHANNEL_0 : 4 -- NOTE: Not usable when WiFi is active. +/// ADC2_CHANNEL_1 : 0 -- NOTE: Not usable when WiFi is active. +/// ADC2_CHANNEL_2 : 2 -- NOTE: Not usable when WiFi is active. +/// ADC2_CHANNEL_3 : 15 -- NOTE: Not usable when WiFi is active. +/// ADC2_CHANNEL_4 : 13 -- NOTE: Not usable when WiFi is active. +/// ADC2_CHANNEL_5 : 12 -- NOTE: Not usable when WiFi is active. +/// ADC2_CHANNEL_6 : 14 -- NOTE: Not usable when WiFi is active. +/// ADC2_CHANNEL_7 : 27 -- NOTE: Not usable when WiFi is active. +/// ADC2_CHANNEL_8 : 25 -- NOTE: Not usable when WiFi is active. +/// ADC2_CHANNEL_9 : 29 -- NOTE: Not usable when WiFi is active. +/// NOTE: ADC1_CHANNEL_1 and ADC1_CHANNEL_2 typically have a capacitor which +/// connects to ADC1_CHANNEL_0 or ADC1_CHANNEL_3. The only known exception to +/// this is for some ESP32-PICO-D4/ESP32-PICO-V3 based boards, confirm on the +/// board schematic before using these pins. +/// +/// Supported ADC_CHANNEL values and pin assignments for the ESP32-S2/ESP32-S3: +/// ADC1_CHANNEL_0 : 1 +/// ADC1_CHANNEL_1 : 2 +/// ADC1_CHANNEL_2 : 3 +/// ADC1_CHANNEL_3 : 4 +/// ADC1_CHANNEL_4 : 5 +/// ADC1_CHANNEL_5 : 6 +/// ADC1_CHANNEL_6 : 7 +/// ADC1_CHANNEL_7 : 8 +/// ADC1_CHANNEL_8 : 9 +/// ADC1_CHANNEL_9 : 10 +/// ADC2_CHANNEL_0 : 11 +/// ADC2_CHANNEL_1 : 12 +/// ADC2_CHANNEL_2 : 13 +/// ADC2_CHANNEL_3 : 14 +/// ADC2_CHANNEL_4 : 15 +/// ADC2_CHANNEL_5 : 16 +/// ADC2_CHANNEL_6 : 17 +/// ADC2_CHANNEL_7 : 18 +/// ADC2_CHANNEL_8 : 19 -- NOTE: This pin is also used for USB PHY (D-). +/// ADC2_CHANNEL_9 : 20 -- NOTE: This pin is also used for USB PHY (D+). +/// +/// Supported ADC_CHANNEL values and pin assignments for the ESP32-C3: +/// ADC1_CHANNEL_0 : 0 +/// ADC1_CHANNEL_1 : 1 +/// ADC1_CHANNEL_2 : 2 +/// ADC1_CHANNEL_3 : 3 +/// ADC1_CHANNEL_4 : 4 +/// ADC2_CHANNEL_0 : 5 +/// +/// Example: +/// ADC_PIN(SENSE, ADC1_CHANNEL_0, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12); +/// ... +/// int level = SENSE_Pin::sample(); +#define ADC_PIN(NAME, ADC_CHANNEL, ATTENUATION, BIT_RANGE) \ + struct NAME##Defs \ + { \ + static const adc_channel_t CHANNEL = (adc_channel_t)ADC_CHANNEL; \ + static const gpio_num_t PIN = (gpio_num_t)ADC_CHANNEL##_GPIO_NUM; \ + static const adc_atten_t ATTEN = (adc_atten_t)ATTENUATION; \ + static const adc_bitwidth_t BITS = (adc_bitwidth_t)BIT_RANGE; \ + static adc_oneshot_unit_handle_t HANDLE; \ + public: \ + static const gpio_num_t pin() \ + { \ + return PIN; \ + } \ + static const adc_channel_t channel() \ + { \ + return CHANNEL; \ + } \ + }; \ + adc_oneshot_unit_handle_t NAME##Defs::HANDLE; \ + typedef Esp32ADCInput NAME##_Pin + +#endif // _DRIVERS_ESP32ADCONESHOT_HXX_ diff --git a/src/freertos_drivers/esp32/Esp32BootloaderHal.hxx b/src/freertos_drivers/esp32/Esp32BootloaderHal.hxx index a1303f06b..8fb8b3b8b 100644 --- a/src/freertos_drivers/esp32/Esp32BootloaderHal.hxx +++ b/src/freertos_drivers/esp32/Esp32BootloaderHal.hxx @@ -40,11 +40,6 @@ #define _FREERTOS_DRIVERS_ESP32_ESP32BOOTLOADERHAL_HXX_ #include "sdkconfig.h" -#include - -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,3,0) -#error ESP32 Bootloader is only supported with ESP-IDF v4.3+ -#endif // IDF v4.3+ #ifndef BOOTLOADER_LOG_LEVEL #define BOOTLOADER_LOG_LEVEL VERBOSE @@ -56,13 +51,13 @@ // Enable streaming support for the bootloader #define BOOTLOADER_STREAM - #ifndef WRITE_BUFFER_SIZE -// Set the buffer size to half of the sector size to minimize the flash writes +// Set the buffer size to half of the sector size to minimize the flash writes. #define WRITE_BUFFER_SIZE (CONFIG_WL_SECTOR_SIZE / 2) #endif // WRITE_BUFFER_SIZE #include + #include #include #include @@ -583,4 +578,4 @@ void esp32_bootloader_run(uint64_t id, gpio_num_t rx, gpio_num_t tx, } } -#endif // _FREERTOS_DRIVERS_ESP32_ESP32BOOTLOADERHAL_HXX_ \ No newline at end of file +#endif // _FREERTOS_DRIVERS_ESP32_ESP32BOOTLOADERHAL_HXX_ diff --git a/src/freertos_drivers/esp32/Esp32Gpio.hxx b/src/freertos_drivers/esp32/Esp32Gpio.hxx index 3a58f8fcc..f60a44cfc 100644 --- a/src/freertos_drivers/esp32/Esp32Gpio.hxx +++ b/src/freertos_drivers/esp32/Esp32Gpio.hxx @@ -36,27 +36,15 @@ #define _DRIVERS_ESP32GPIO_HXX_ #include "freertos_drivers/arduino/GpioWrapper.hxx" +#include "freertos_drivers/esp32/Esp32AdcOneShot.hxx" #include "os/Gpio.hxx" #include "utils/logging.h" #include "utils/macros.h" -#include #include -#include -#include - -// esp_rom_gpio.h is a target agnostic replacement for esp32/rom/gpio.h -#if __has_include() #include -#elif ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,0,0) -#include -#else -#include -#endif -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5,0,0) #include -#endif #if defined(CONFIG_IDF_TARGET_ESP32C3) /// Helper macro to test if a pin has been configured for output. @@ -94,7 +82,7 @@ public: static_assert(!(PIN_NUM >= 26 && PIN_NUM <= 32) , "Pin is reserved for flash usage."); #if defined(CONFIG_SPIRAM_MODE_OCT) || defined(CONFIG_ESPTOOLPY_OCT_FLASH) - static_assert(!(PIN_NUM >= 33 && PIN_NUM <= 37)), + static_assert(!(PIN_NUM >= 33 && PIN_NUM <= 37), "Pin is not available when Octal SPI mode is enabled."); #endif // ESP32S3 with Octal SPI #elif CONFIG_IDF_TARGET_ESP32S2 @@ -126,7 +114,7 @@ public: #else static_assert(!(PIN_NUM >= 6 && PIN_NUM <= 11) , "Pin is reserved for flash usage."); -#if defined(BOARD_HAS_PSRAM) +#if defined(BOARD_HAS_PSRAM) || defined(CONFIG_SPIRAM_SUPPORT) static_assert(PIN_NUM != 16 && PIN_NUM != 17 , "Pin is reserved for PSRAM usage."); #endif // BOARD_HAS_PSRAM @@ -238,11 +226,7 @@ public: LOG(VERBOSE, "[Esp32Gpio] Configuring output pin %d, default value: %d", PIN_NUM, SAFE_VALUE); -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,3,0) - gpio_pad_select_gpio(PIN_NUM); -#else // IDF v4.4 (or later) esp_rom_gpio_pad_select_gpio(PIN_NUM); -#endif // IDF v4.3 (or earlier) gpio_config_t cfg; memset(&cfg, 0, sizeof(gpio_config_t)); cfg.pin_bit_mask = BIT64(PIN_NUM); @@ -358,11 +342,7 @@ public: { LOG(VERBOSE, "[Esp32Gpio] Configuring input pin %d, PUEN: %d, PDEN: %d", PIN_NUM, PUEN, PDEN); -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,3,0) - gpio_pad_select_gpio(PIN_NUM); -#else // IDF v4.4 (or later) esp_rom_gpio_pad_select_gpio(PIN_NUM); -#endif // IDF v4.3 (or earlier) gpio_config_t cfg; memset(&cfg, 0, sizeof(gpio_config_t)); cfg.pin_bit_mask = BIT64(PIN_NUM); @@ -420,71 +400,6 @@ template struct GpioInputPUPD : public GpioInputPin struct Esp32ADCInput : public Defs -{ -public: - using Defs::CHANNEL; - using Defs::PIN; - using Defs::ATTEN; - using Defs::BITS; -#if CONFIG_IDF_TARGET_ESP32 - static const adc_unit_t UNIT = PIN >= 30 ? ADC_UNIT_1 : ADC_UNIT_2; -#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 - static const adc_unit_t UNIT = PIN <= 10 ? ADC_UNIT_1 : ADC_UNIT_2; -#elif CONFIG_IDF_TARGET_ESP32C3 - static const adc_unit_t UNIT = PIN <= 4 ? ADC_UNIT_1 : ADC_UNIT_2; -#endif - static void hw_init() - { - LOG(VERBOSE, - "[Esp32ADCInput] Configuring ADC%d:%d input pin %d, " - "attenuation %d, bits %d", - UNIT + 1, CHANNEL, PIN, ATTEN, BITS); - - if (UNIT == ADC_UNIT_1) - { - ESP_ERROR_CHECK(adc1_config_width(BITS)); - ESP_ERROR_CHECK( - adc1_config_channel_atten((adc1_channel_t)CHANNEL, ATTEN)); - } - else - { - ESP_ERROR_CHECK( - adc2_config_channel_atten((adc2_channel_t)CHANNEL, ATTEN)); - } - } - - /// NO-OP - static void hw_set_to_safe() - { - // NO-OP - } - - /// NO-OP - static void set(bool value) - { - // NO-OP - } - - static int sample() - { - int value = 0; - if (UNIT == ADC_UNIT_1) - { - value = adc1_get_raw((adc1_channel_t)CHANNEL); - } - else - { - ESP_ERROR_CHECK( - adc2_get_raw((adc2_channel_t)CHANNEL, BITS, &value)); - } - return value; - } -}; - /// Helper macro for defining GPIO pins on the ESP32. /// /// @param NAME is the basename of the declaration. For NAME==FOO the macro @@ -645,105 +560,4 @@ public: }; \ typedef BaseClass NAME##_Pin -/// Helper macro for an ADC GPIO input on the ESP32. -/// -/// @param NAME is the basename of the declaration. For NAME==FOO the macro -/// declared FOO_Pin as a structure on which the read-write functions will be -/// available. -/// @param ADC_CHANNEL is the ADC channel to configure. -/// @param ATTENUATION is the voltage range for the ADC input. -/// @param BIT_RANGE is the bit range to configure the ADC to use. -/// -/// Supported ATTENUATION values and voltage ranges: -/// ADC_ATTEN_DB_0 - 0dB attenuaton gives full-scale voltage 1.1V -/// ADC_ATTEN_DB_2_5 - 2.5dB attenuation gives full-scale voltage 1.5V -/// ADC_ATTEN_DB_6 - 6dB attenuation gives full-scale voltage 2.2V -/// ADC_ATTEN_DB_11 - 11dB attenuation gives full-scale voltage 3.9V -/// -/// Supported BIT_RANGE values and ADC sample values: -/// ADC_WIDTH_BIT_9 - 0-511 -/// ADC_WIDTH_BIT_10 - 0-1023 -/// ADC_WIDTH_BIT_11 - 0-2047 -/// ADC_WIDTH_BIT_12 - 0-4065 -/// ADC_WIDTH_BIT_13 - 0-8191 -- Only valid on the ESP32-S2 and ESP32-S3. -/// NOTE: When using ADC1_CHANNEL_X this bit range will be applied to all -/// ADC1 channels, it is not recommended to mix values for ADC1 channels. -/// -/// Supported ADC_CHANNEL values and pin assignments for the ESP32: -/// ADC1_CHANNEL_0 : 36 -/// ADC1_CHANNEL_1 : 37 -- NOTE: Not recommended for use, see note below. -/// ADC1_CHANNEL_2 : 38 -- NOTE: Not recommended for use, see note below. -/// ADC1_CHANNEL_3 : 39 -/// ADC1_CHANNEL_4 : 32 -/// ADC1_CHANNEL_5 : 33 -/// ADC1_CHANNEL_6 : 34 -/// ADC1_CHANNEL_7 : 35 -/// ADC2_CHANNEL_0 : 4 -- NOTE: Not usable when WiFi is active. -/// ADC2_CHANNEL_1 : 0 -- NOTE: Not usable when WiFi is active. -/// ADC2_CHANNEL_2 : 2 -- NOTE: Not usable when WiFi is active. -/// ADC2_CHANNEL_3 : 15 -- NOTE: Not usable when WiFi is active. -/// ADC2_CHANNEL_4 : 13 -- NOTE: Not usable when WiFi is active. -/// ADC2_CHANNEL_5 : 12 -- NOTE: Not usable when WiFi is active. -/// ADC2_CHANNEL_6 : 14 -- NOTE: Not usable when WiFi is active. -/// ADC2_CHANNEL_7 : 27 -- NOTE: Not usable when WiFi is active. -/// ADC2_CHANNEL_8 : 25 -- NOTE: Not usable when WiFi is active. -/// ADC2_CHANNEL_9 : 29 -- NOTE: Not usable when WiFi is active. -/// NOTE: ADC1_CHANNEL_1 and ADC1_CHANNEL_2 typically have a capacitor which -/// connects to ADC1_CHANNEL_0 or ADC1_CHANNEL_3. The only known exception to -/// this is for some ESP32-PICO-D4/ESP32-PICO-V3 based boards, confirm on the -/// board schematic before using these pins. -/// -/// Supported ADC_CHANNEL values and pin assignments for the ESP32-S2/ESP32-S3: -/// ADC1_CHANNEL_0 : 1 -/// ADC1_CHANNEL_1 : 2 -/// ADC1_CHANNEL_2 : 3 -/// ADC1_CHANNEL_3 : 4 -/// ADC1_CHANNEL_4 : 5 -/// ADC1_CHANNEL_5 : 6 -/// ADC1_CHANNEL_6 : 7 -/// ADC1_CHANNEL_7 : 8 -/// ADC1_CHANNEL_8 : 9 -/// ADC1_CHANNEL_9 : 10 -/// ADC2_CHANNEL_0 : 11 -/// ADC2_CHANNEL_1 : 12 -/// ADC2_CHANNEL_2 : 13 -/// ADC2_CHANNEL_3 : 14 -/// ADC2_CHANNEL_4 : 15 -/// ADC2_CHANNEL_5 : 16 -/// ADC2_CHANNEL_6 : 17 -/// ADC2_CHANNEL_7 : 18 -/// ADC2_CHANNEL_8 : 19 -- NOTE: This pin is also used for USB PHY (D-). -/// ADC2_CHANNEL_9 : 20 -- NOTE: This pin is also used for USB PHY (D+). -/// -/// Supported ADC_CHANNEL values and pin assignments for the ESP32-C3: -/// ADC1_CHANNEL_0 : 0 -/// ADC1_CHANNEL_1 : 1 -/// ADC1_CHANNEL_2 : 2 -/// ADC1_CHANNEL_3 : 3 -/// ADC1_CHANNEL_4 : 4 -/// ADC2_CHANNEL_0 : 5 -/// -/// Example: -/// ADC_PIN(SENSE, ADC1_CHANNEL_0, ADC_ATTEN_DB_11, ADC_WIDTH_BIT_12); -/// ... -/// int level = SENSE_Pin::sample(); -#define ADC_PIN(NAME, ADC_CHANNEL, ATTENUATION, BIT_RANGE) \ - struct NAME##Defs \ - { \ - static const adc_channel_t CHANNEL = (adc_channel_t)ADC_CHANNEL; \ - static const gpio_num_t PIN = (gpio_num_t)ADC_CHANNEL##_GPIO_NUM; \ - static const adc_atten_t ATTEN = (adc_atten_t)ATTENUATION; \ - static const adc_bits_width_t BITS = (adc_bits_width_t)BIT_RANGE; \ - public: \ - static const gpio_num_t pin() \ - { \ - return PIN; \ - } \ - static const adc_channel_t channel() \ - { \ - return CHANNEL; \ - } \ - }; \ - typedef Esp32ADCInput NAME##_Pin - -#endif // _DRIVERS_ESP32GPIO_HXX_ \ No newline at end of file +#endif // _DRIVERS_ESP32GPIO_HXX_ diff --git a/src/freertos_drivers/esp32/Esp32HardwareCanAdapter.hxx b/src/freertos_drivers/esp32/Esp32HardwareCanAdapter.hxx deleted file mode 100644 index 3a2123289..000000000 --- a/src/freertos_drivers/esp32/Esp32HardwareCanAdapter.hxx +++ /dev/null @@ -1,428 +0,0 @@ -/** \copyright - * Copyright (c) 2019, Mike Dunston - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * - Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE - * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - * - * \file Esp32HardwareCanAdapter.hxx - * - * ESP32 Hardware CAN adapter code. This utilizes the built in CAN controller - * to translate the can_frame in OpenMRN to the ESP32 can_message_t used by the - * ESP-IDF CAN controller code. The ESP32 will still require an external CAN - * transceiver (MCP2551 or SN65HVD230 as example). - * - * @author Mike Dunston - * @date 19 January 2019 - */ - -#ifndef _FREERTOS_DRIVERS_ESP32_ESP32HWCAN_HXX_ -#define _FREERTOS_DRIVERS_ESP32_ESP32HWCAN_HXX_ - -namespace openmrn_arduino -{ - -#include "freertos_drivers/arduino/Can.hxx" -#include - -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,2,0) -#include -#else // NOT IDF v4.2+ -#include - -// The following types and APIs are created as aliases as a compatibility for -// IDF v4.3+ which breaks due to driver/twai.h on the ESP32 attempting to -// override a few types created as part of can_ioctl.h -typedef can_timing_config_t twai_timing_config_t; -typedef can_filter_config_t twai_filter_config_t; -typedef can_general_config_t twai_general_config_t; -typedef can_status_info_t twai_status_info_t; -typedef can_message_t twai_message_t; -#define TWAI_TIMING_CONFIG_125KBITS CAN_TIMING_CONFIG_125KBITS -#define TWAI_FILTER_CONFIG_ACCEPT_ALL CAN_FILTER_CONFIG_ACCEPT_ALL -#define TWAI_MODE_NORMAL CAN_MODE_NORMAL -#define TWAI_IO_UNUSED CAN_IO_UNUSED -#define TWAI_ALERT_NONE CAN_ALERT_NONE -#define TWAI_STATE_BUS_OFF CAN_STATE_BUS_OFF -#define TWAI_STATE_RECOVERING CAN_STATE_RECOVERING -#define TWAI_MSG_FLAG_NONE CAN_MSG_FLAG_NONE -#define TWAI_MSG_FLAG_EXTD CAN_MSG_FLAG_EXTD -#define TWAI_MSG_FLAG_RTR CAN_MSG_FLAG_RTR -#define TWAI_MSG_FLAG_DLC_NON_COMP CAN_MSG_FLAG_DLC_NON_COMP - -#define twai_driver_install can_driver_install -#define twai_start can_start -#define twai_stop can_stop -#define twai_get_status_info can_get_status_info -#define twai_initiate_recovery can_initiate_recovery -#define twai_transmit can_transmit -#define twai_receive can_receive - -#endif // IDF v4.2+ - -#include -#include - -/// ESP32 CAN bus status strings, used for periodic status reporting -static const char *ESP32_CAN_STATUS_STRINGS[] = -{ - "STOPPED", // CAN_STATE_STOPPED - "RUNNING", // CAN_STATE_RUNNING - "OFF / RECOVERY NEEDED", // CAN_STATE_BUS_OFF - "RECOVERY UNDERWAY" // CAN_STATE_RECOVERING -}; - -class Esp32HardwareCanDeprecated : public Can -{ -public: - /// Constructor. - /// - /// @param name is the name for the CAN adapter, this is currently not used. - /// @param rxPin is the ESP32 pin that is connected to the external - /// transceiver RX. - /// @param txPin is the ESP32 pin that is connected to the external - /// transceiver TX. - Esp32HardwareCanDeprecated(const char *name, gpio_num_t rxPin, - gpio_num_t txPin, bool reportStats = true) - : Can(name) - , reportStats_(reportStats) - , overrunWarningPrinted_(false) - { - // Configure the ESP32 CAN driver to use 125kbps. - twai_timing_config_t can_timing_config = TWAI_TIMING_CONFIG_125KBITS(); - // By default we accept all CAN frames. - twai_filter_config_t can_filter_config = TWAI_FILTER_CONFIG_ACCEPT_ALL(); - // Note: not using the CAN_GENERAL_CONFIG_DEFAULT macro due to a missing - // cast for CAN_IO_UNUSED. - twai_general_config_t can_general_config = {.mode = TWAI_MODE_NORMAL, - .tx_io = txPin, - .rx_io = rxPin, - .clkout_io = (gpio_num_t)TWAI_IO_UNUSED, - .bus_off_io = (gpio_num_t)TWAI_IO_UNUSED, - .tx_queue_len = (uint32_t)config_can_tx_buffer_size() / 2, - .rx_queue_len = (uint32_t)config_can_rx_buffer_size() / 2, - .alerts_enabled = TWAI_ALERT_NONE, - .clkout_divider = 0}; - - LOG(VERBOSE, - "ESP32-CAN driver configured using RX: %d, TX: %d, RX-Q: %d, " - "TX-Q: %d", - can_general_config.rx_io, can_general_config.tx_io, - can_general_config.rx_queue_len, can_general_config.tx_queue_len); - - ESP_ERROR_CHECK(twai_driver_install( - &can_general_config, &can_timing_config, &can_filter_config)); - - xTaskCreatePinnedToCore(rx_task, "ESP32-CAN RX", OPENMRN_STACK_SIZE, - this, RX_TASK_PRIORITY, &rxTaskHandle_, tskNO_AFFINITY); - xTaskCreatePinnedToCore(tx_task, "ESP32-CAN TX", OPENMRN_STACK_SIZE, - this, TX_TASK_PRIORITY, &txTaskHandle_, tskNO_AFFINITY); - } - - ~Esp32HardwareCanDeprecated() - { - } - - /// Enables the ESP32 CAN driver - virtual void enable() - { - ESP_ERROR_CHECK(twai_start()); - LOG(VERBOSE, "ESP32-CAN driver enabled"); - } - - /// Disables the ESP32 CAN driver - virtual void disable() - { - ESP_ERROR_CHECK(twai_stop()); - LOG(VERBOSE, "ESP32-CAN driver disabled"); - } - -protected: - /// function to try and transmit a message - void tx_msg() override - { - // wake up the tx_task so it can consume any can_frames from txBuf. - xTaskNotifyGive(txTaskHandle_); - } - -private: - /// Default constructor. - Esp32HardwareCanDeprecated(); - - /// Enables/Disables the periodic reporting of CAN bus statistics to the - /// default serial stream. - bool reportStats_ : 1; - /// Set to true if the 'frame dropped' warning is printed. - bool overrunWarningPrinted_ : 1; - - /// Handle for the tx_task that converts and transmits can_frame to the - /// native can driver. - TaskHandle_t txTaskHandle_; - - /// Handle for the rx_task that receives and converts the native can driver - /// frames to can_frame. - TaskHandle_t rxTaskHandle_; - - /// Interval at which to print the ESP32 CAN bus status. - static constexpr TickType_t STATUS_PRINT_INTERVAL = pdMS_TO_TICKS(10000); - - /// Interval to wait between iterations when the bus is recovering, a - /// transmit failure or there is nothing to transmit. - static constexpr TickType_t TX_DEFAULT_DELAY = pdMS_TO_TICKS(250); - - /// Priority to use for the rx_task. This needs to be higher than the - /// tx_task and lower than @ref OPENMRN_TASK_PRIORITY. - static constexpr UBaseType_t RX_TASK_PRIORITY = ESP_TASK_TCPIP_PRIO - 2; - - /// Priority to use for the tx_task. This should be lower than - /// @ref RX_TASK_PRIORITY and @ref OPENMRN_TASK_PRIORITY. - static constexpr UBaseType_t TX_TASK_PRIORITY = ESP_TASK_TCPIP_PRIO - 3; - - /// Background task that takes care of the conversion of the @ref can_frame - /// provided by the @ref txBuf into an ESP32 can_message_t which can be - /// processed by the native CAN driver. This task also covers the periodic - /// status reporting and BUS recovery when necessary. - static void tx_task(void *can) - { - /// Get handle to our parent Esp32HardwareCanDeprecated object to - /// access the txBuf. - Esp32HardwareCanDeprecated *parent = - reinterpret_cast(can); - -#if CONFIG_TASK_WDT - // Add this task to the WDT - esp_task_wdt_add(parent->txTaskHandle_); -#endif // CONFIG_TASK_WDT - - /// Tracks the last time that we displayed the CAN driver status. - TickType_t next_status_display_tick_count = 0; - - while (true) - { -#if CONFIG_TASK_WDT - // Feed the watchdog so it doesn't reset the ESP32 - esp_task_wdt_reset(); -#endif // CONFIG_TASK_WDT - - // periodic CAN driver monitoring and reporting, this takes care of - // bus recovery when the CAN driver disables the bus due to error - // conditions exceeding thresholds. - twai_status_info_t status; - twai_get_status_info(&status); - auto current_tick_count = xTaskGetTickCount(); - if (next_status_display_tick_count == 0 || - current_tick_count >= next_status_display_tick_count) - { - next_status_display_tick_count = - current_tick_count + STATUS_PRINT_INTERVAL; - if (parent->reportStats_) - { - LOG(INFO, - "ESP32-CAN: %s rx-q:%d, tx-q:%d, rx-err:%d, tx-err:%d, " - "ovr:%d arb-lost:%d, bus-err:%d, state: %s", - parent->overrunWarningPrinted_ ? "!!OVERRUN!! " : "", - status.msgs_to_rx, status.msgs_to_tx, - status.rx_error_counter, status.tx_error_counter, - parent->overrunCount, status.arb_lost_count, - status.bus_error_count, - ESP32_CAN_STATUS_STRINGS[status.state]); - } - parent->overrunWarningPrinted_ = false; - } - if (status.state == TWAI_STATE_BUS_OFF) - { - // When the bus is OFF we need to initiate recovery, transmit is - // not possible when in this state. - LOG(WARNING, "ESP32-CAN: initiating recovery"); - twai_initiate_recovery(); - continue; - } - else if (status.state == TWAI_STATE_RECOVERING) - { - // when the bus is in recovery mode transmit is not possible. - vTaskDelay(TX_DEFAULT_DELAY); - continue; - } - - // check txBuf for any message to transmit. - unsigned count; - struct can_frame *can_frame = nullptr; - { - AtomicHolder h(parent); - count = parent->txBuf->data_read_pointer(&can_frame); - } - if (!count || !can_frame) - { - // tx Buf empty; wait for tx_msg to be called. - ulTaskNotifyTake(pdTRUE, // clear on exit - TX_DEFAULT_DELAY); - continue; - } - - /// ESP32 native CAN driver frame - twai_message_t msg; - memset(&msg, 0, sizeof(twai_message_t)); - - msg.flags = TWAI_MSG_FLAG_NONE; - msg.identifier = can_frame->can_id; - msg.data_length_code = can_frame->can_dlc; - for (int i = 0; i < can_frame->can_dlc; i++) - { - msg.data[i] = can_frame->data[i]; - } - if (IS_CAN_FRAME_EFF(*can_frame)) - { - msg.flags |= TWAI_MSG_FLAG_EXTD; - } - if (IS_CAN_FRAME_RTR(*can_frame)) - { - msg.flags |= TWAI_MSG_FLAG_RTR; - } - - // Pass the converted CAN frame to the native driver - // for transmit, if the TX queue is full this will - // return ESP_ERR_TIMEOUT which will result in the - // the message being left in txBuf for the next iteration. - // if this call returns ESP_OK we consider the frame as - // transmitted by the driver and remove it from txBuf. - esp_err_t tx_res = twai_transmit(&msg, pdMS_TO_TICKS(100)); - if (tx_res == ESP_OK) - { - LOG(VERBOSE, - "ESP32-CAN-TX OK id:%08x, flags:%04x, dlc:%02d, " - "data:%02x%02x%02x%02x%02x%02x%02x%02x", - msg.identifier, msg.flags, msg.data_length_code, - msg.data[0], msg.data[1], msg.data[2], msg.data[3], - msg.data[4], msg.data[5], msg.data[6], msg.data[7]); - AtomicHolder h(parent); - parent->txBuf->consume(1); - parent->txBuf->signal_condition(); - } - else if (tx_res != ESP_ERR_TIMEOUT) - { - LOG(WARNING, "ESP32-CAN-TX: %s", esp_err_to_name(tx_res)); - vTaskDelay(TX_DEFAULT_DELAY); - } - } // loop on task - } - - /// Background task that takes care of receiving can_message_t objects from - /// the ESP32 native CAN driver, when they are available, converting them to - /// a @ref can_frame and pushing them to the @ref rxBuf. - static void rx_task(void *can) - { - /// Get handle to our parent Esp32HardwareCanDeprecated object to access - /// the rxBuf. - Esp32HardwareCanDeprecated *parent = - reinterpret_cast(can); - -#if CONFIG_TASK_WDT - // Add this task to the WDT - esp_task_wdt_add(parent->rxTaskHandle_); -#endif // CONFIG_TASK_WDT - - while (true) - { -#if CONFIG_TASK_WDT - // Feed the watchdog so it doesn't reset the ESP32 - esp_task_wdt_reset(); -#endif // CONFIG_TASK_WDT - - /// ESP32 native CAN driver frame - twai_message_t msg; - memset(&msg, 0, sizeof(twai_message_t)); - if (twai_receive(&msg, pdMS_TO_TICKS(250)) != ESP_OK) - { - // native CAN driver did not give us a frame. - continue; - } - // we have received a frame from the native CAN driver, verify if - // it is a standard frame, if not we drop it. - if (msg.flags & TWAI_MSG_FLAG_DLC_NON_COMP) - { - LOG(WARNING, - "ESP32-CAN-RX: received non-compliant CAN frame, frame " - "dropped!"); - continue; - } - LOG(VERBOSE, - "ESP32-CAN-RX id:%08x, flags:%04x, dlc:%02d, " - "data:%02x%02x%02x%02x%02x%02x%02x%02x", - msg.identifier, msg.flags, msg.data_length_code, - msg.data[0], msg.data[1], msg.data[2], msg.data[3], - msg.data[4], msg.data[5], msg.data[6], msg.data[7]); - AtomicHolder h(parent); - struct can_frame *can_frame = nullptr; - // verify if we have space in the rxBuf, if not drop the frame and - // record the overrun. - if (!parent->rxBuf->data_write_pointer(&can_frame) || - can_frame == nullptr) - { - if (!parent->overrunWarningPrinted_) - { - parent->overrunWarningPrinted_ = true; - LOG(WARNING, - "ESP32-CAN-RX: buffer overrun, frame dropped!"); - } - parent->overrunCount++; - continue; - } - // we have space in the rxBuf, start conversion - LOG(VERBOSE, "ESP32-CAN-RX: converting to can_frame"); - memset(can_frame, 0, sizeof(struct can_frame)); - can_frame->can_id = msg.identifier; - can_frame->can_dlc = msg.data_length_code; - for (int i = 0; i < msg.data_length_code; i++) - { - can_frame->data[i] = msg.data[i]; - } - if (msg.flags & TWAI_MSG_FLAG_EXTD) - { - SET_CAN_FRAME_EFF(*can_frame); - } - if (msg.flags & TWAI_MSG_FLAG_RTR) - { - SET_CAN_FRAME_RTR(*can_frame); - } - parent->rxBuf->advance(1); - parent->rxBuf->signal_condition(); - } - } - DISALLOW_COPY_AND_ASSIGN(Esp32HardwareCanDeprecated); -}; - -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,3,0) -/// Esp32HardwareCan has been deprecated due to lack of portability beyond the -/// ESP32. -/// @deprecated Use @ref Esp32HardwareTwai instead. -typedef Esp32HardwareCanDeprecated Esp32HardwareCan __attribute__ (( - deprecated("Esp32HardwareCan has been replaced with Esp32HardwareTwai."))); -#else -/// Esp32HardwareCan will be deprecated once arduino-esp32 2.0.0 has released. -typedef Esp32HardwareCanDeprecated Esp32HardwareCan; -#endif // IDF v4.3+ - -} // namespace openmrn_arduino - -using openmrn_arduino::Esp32HardwareCan; - -#endif /* _FREERTOS_DRIVERS_ARDUINO_ESP32HWCAN_HXX_ */ diff --git a/src/freertos_drivers/esp32/Esp32HardwareI2C.cpp b/src/freertos_drivers/esp32/Esp32HardwareI2C.cpp new file mode 100644 index 000000000..58600b553 --- /dev/null +++ b/src/freertos_drivers/esp32/Esp32HardwareI2C.cpp @@ -0,0 +1,513 @@ +/** \copyright + * Copyright (c) 2023, Mike Dunston + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file Esp32HardwareI2C.cxx + * + * I2C driver implementation for OpenMRN. This leverages the ESP-IDF I2C driver + * implementation and provides a VFS interface. + * + * @author Mike Dunston + * @date 8 Feb 2023 + */ + +#if defined(ESP_PLATFORM) + +#include "Esp32HardwareI2C.hxx" +// stropts.h must be included before i2c.h +#include "stropts.h" +#include "i2c.h" +#include "i2c-dev.h" +#include "sdkconfig.h" +#include "utils/format_utils.hxx" +#include "utils/logging.h" +#include "utils/StringPrintf.hxx" + +#include + +#if CONFIG_VFS_SUPPORT_TERMIOS +// remove defines added by arduino-esp32 core/esp32/binary.h which are +// duplicated in sys/termios.h which may be included by esp_vfs.h +#undef B110 +#undef B1000000 +#endif // CONFIG_VFS_SUPPORT_TERMIOS +#include +#include +#include + +namespace openmrn_arduino +{ + +extern "C" +{ + +/// VFS adapter for write(fd, buf, size) +/// +/// @param ctx is the @ref Esp32HardwareI2C instance to invoke. +/// @param fd is the file descriptor being written to. +/// @param buf is the buffer containing the data to be written. +/// @param size is the size of the buffer. +/// @return number of bytes written or -1 if there is the write would be a +/// blocking operation. +static ssize_t i2c_vfs_write(void *ctx, int fd, const void *buf, size_t size) +{ + HASSERT(ctx != NULL); + Esp32HardwareI2C *i2c = reinterpret_cast(ctx); + return i2c->write(fd, buf, size); +} + +/// VFS adapter for read(fd, buf, size) +/// +/// @param ctx is the @ref Esp32HardwareI2C instance to invoke. +/// @param fd is the file descriptor being read from. +/// @param buf is the buffer to write into. +/// @param size is the size of the buffer. +/// @return number of bytes read or -1 if there is the read would be a +/// blocking operation. +static ssize_t i2c_vfs_read(void *ctx, int fd, void *buf, size_t size) +{ + HASSERT(ctx != NULL); + Esp32HardwareI2C *i2c = reinterpret_cast(ctx); + return i2c->read(fd, buf, size); +} + +/// VFS adapter for open(path, flags, mode). +/// +/// @param ctx is the @ref Esp32HardwareI2C instance to invoke. +/// @param path is the path to the file being opened. +/// @param flags are the flags to use for opened file. +/// @param mode is the mode to use for the opened file. +/// +/// When this method is invoked it will enable the TWAI driver and start the +/// periodic timer used for RX/TX of frame data. +/// +/// @return 0 upon success, -1 upon failure with errno containing the cause. +static int i2c_vfs_open(void *ctx, const char *path, int flags, int mode) +{ + HASSERT(ctx != NULL); + Esp32HardwareI2C *i2c = reinterpret_cast(ctx); + return i2c->open(path, flags, mode); +} + +/// VFS adapter for close(fd). +/// +/// @param ctx is the @ref Esp32HardwareI2C instance to invoke. +/// @param fd is the file descriptor to close. +/// +/// When this method is invoked it will disable the TWAI driver and stop the +/// periodic timer used for RX/TX of frame data if it is running. +/// +/// @return zero upon success, negative value with errno for failure. +static int i2c_vfs_close(void *ctx, int fd) +{ + HASSERT(ctx != NULL); + Esp32HardwareI2C *i2c = reinterpret_cast(ctx); + return i2c->close(fd); +} + +/// VFS adapter for ioctl. +/// +/// @param ctx is the @ref Esp32HardwareI2C instance to invoke. +/// @param fd is the file descriptor to operate on. +/// @param cmd is the command to execute. +/// @param args is the args for the command. +/// +/// @return zero upon success, negative value with errno for failure. +static int i2c_vfs_ioctl(void *ctx, int fd, int cmd, va_list args) +{ + HASSERT(ctx != NULL); + Esp32HardwareI2C *i2c = reinterpret_cast(ctx); + return i2c->ioctl(fd, cmd, args); +} + +/// VFS adapter for fcntl(fd, cmd, arg). +/// +/// @param ctx is the @ref Esp32HardwareI2C instance to invoke. +/// @param fd to operate on. +/// @param cmd to be executed. +/// @param arg arg to be used for the operation. +/// +/// This method is currently a NO-OP. +/// +/// @return zero upon success, negative value with errno for failure. +static int i2c_vfs_fcntl(void *ctx, int fd, int cmd, int arg) +{ + HASSERT(ctx != NULL); + return 0; +} + +} // extern "C" + +Esp32HardwareI2C::Esp32HardwareI2C(const char * const path) + : path_(path) +{ +} + +Esp32HardwareI2C::~Esp32HardwareI2C() +{ + for (size_t idx = 0; idx < SOC_I2C_NUM; idx++) + { + if (i2cInitialized_[idx]) + { + ESP_ERROR_CHECK(i2c_driver_delete(static_cast(idx))); + } + } + if (vfsInitialized_) + { + ESP_ERROR_CHECK(esp_vfs_unregister(path_)); + } +} + +void Esp32HardwareI2C::hw_init(const gpio_num_t sda, const gpio_num_t scl, + const uint32_t bus_speed, const i2c_port_t port) +{ + if (!i2cInitialized_[port]) + { + i2c_config_t i2c_config = {}; + i2c_config.mode = I2C_MODE_MASTER; + i2c_config.sda_io_num = sda; + i2c_config.sda_pullup_en = GPIO_PULLUP_ENABLE; + i2c_config.scl_io_num = scl; + i2c_config.scl_pullup_en = GPIO_PULLUP_ENABLE; + i2c_config.master.clk_speed = bus_speed; + + LOG(INFO, + "[I2C] Initializing I2C%d using SDA:%d, SCL:%d, " + "bus-speed: %" PRIu32, port, sda, scl, bus_speed); + ESP_ERROR_CHECK(i2c_param_config(port, &i2c_config)); + ESP_ERROR_CHECK(i2c_driver_install(port, I2C_MODE_MASTER, + I2C_SLAVE_RX_BUF_SIZE, I2C_SLAVE_TX_BUF_SIZE, I2C_ISR_FLAGS)); + + i2cInitialized_[port] = true; + } + else + { + LOG_ERROR("[I2C] I2C%d has already been initialized!", port); + } + + if (!vfsInitialized_) + { + esp_vfs_t vfs = {}; + vfs.write_p = i2c_vfs_write; + vfs.read_p = i2c_vfs_read; + vfs.open_p = i2c_vfs_open; + vfs.close_p = i2c_vfs_close; + vfs.fcntl_p = i2c_vfs_fcntl; + vfs.ioctl_p = i2c_vfs_ioctl; + vfs.flags = ESP_VFS_FLAG_CONTEXT_PTR; + ESP_ERROR_CHECK(esp_vfs_register(path_, &vfs, this)); + + vfsInitialized_ = true; + } +} + +void Esp32HardwareI2C::scan(const i2c_port_t port) +{ + // Scan the I2C bus and dump the output of devices that respond + std::string scanresults = + " 0 1 2 3 4 5 6 7 8 9 a b c d e f\n" + "00: "; + scanresults.reserve(256); + + HASSERT(i2cInitialized_[port]); + for (uint8_t addr = 0; addr < 0x7F; addr++) + { + if (addr % 16 == 0) + { + scanresults.append(StringPrintf("\n%02x: ", addr)); + } + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (addr << 1) | I2C_MASTER_WRITE, true); + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin(port, cmd, I2C_SCAN_TIMEOUT); + i2c_cmd_link_delete(cmd); + if (ret == ESP_OK) + { + scanresults.append(StringPrintf("%02x ", addr)); + } + else if (ret == ESP_ERR_TIMEOUT) + { + scanresults.append("?? "); + } + else + { + scanresults.append("-- "); + } + } + LOG(INFO, scanresults.c_str()); +} + +ssize_t Esp32HardwareI2C::write(int fd, const void *buf, size_t size) +{ + uint8_t address; + i2c_port_t port; + + { + AtomicHolder h(this); + + auto entry = std::find_if(devices_.begin(), devices_.end(), + [fd](const auto &device) + { + return device.fd == fd; + }); + + if (entry == devices_.end()) + { + // file handle not found, return an error. + errno = EBADF; + return -EBADF; + } + else if (entry->fd < 0) + { + // no address has been defined for this file handle, return an error. + errno = EINVAL; + return -EINVAL; + } + address = entry->address; + port = entry->port; + } + + esp_err_t res = ESP_ERROR_CHECK_WITHOUT_ABORT( + i2c_master_write_to_device(port, address, (uint8_t *)buf, size, + I2C_OP_TIMEOUT)); + + if (res == ESP_ERR_TIMEOUT) + { + return -ETIMEDOUT; + } + else if (res == ESP_ERR_INVALID_STATE) + { + return -EINVAL; + } + else if (res != ESP_OK) + { + return -EIO; + } + return size; +} + +ssize_t Esp32HardwareI2C::read(int fd, void *buf, size_t size) +{ + uint8_t address; + i2c_port_t port; + + { + AtomicHolder h(this); + + auto entry = std::find_if(devices_.begin(), devices_.end(), + [fd](const auto &device) + { + return device.fd == fd; + }); + + if (entry == devices_.end()) + { + // file handle not found, return an error. + errno = EBADF; + return -EBADF; + } + else if (entry->fd < 0) + { + // no address has been defined for this file handle, return an error. + errno = EINVAL; + return -EINVAL; + } + address = entry->address; + port = entry->port; + } + + + esp_err_t res = ESP_ERROR_CHECK_WITHOUT_ABORT( + i2c_master_read_from_device(port, address, (uint8_t *)buf, size, + I2C_OP_TIMEOUT)); + + if (res == ESP_ERR_TIMEOUT) + { + return -ETIMEDOUT; + } + else if (res == ESP_ERR_INVALID_STATE) + { + return -EINVAL; + } + else if (res != ESP_OK) + { + return -EIO; + } + return size; +} + +int Esp32HardwareI2C::open(const char *path, int flags, int mode) +{ + std::string path_str = path; + i2c_device_t new_dev = + { + .port = I2C_NUM_0, + .address = -1, + .fd = 0, + }; + + if (path_str.back() == '0') + { + new_dev.port = I2C_NUM_0; + } +#if SOC_I2C_NUM > 1 + else if (path_str.back() == '1') + { + new_dev.port = I2C_NUM_1; + } +#endif // SOC_I2C_NUM > 1 + else + { + LOG_ERROR("[I2C] Unsupported I2C path: %s", path); + return -ENOENT; + } + + if (!i2cInitialized_[new_dev.port]) + { + LOG_ERROR("[I2C] Uninitialized I2C path: %s", path); + return -ENODEV; + } + + // scan existing devices to find a unique file handle number to return to + // the caller. The file handle starts with zero and is set to the maximum + // file handle found plus one. When a file handle is reclaimed there will + // be gaps in the file handles which will not be reclaimed until entries + // with higher file handle numbers are also reclaimed. + { + AtomicHolder h(this); + + if (!devices_.empty()) + { + for (auto &entry: devices_) + { + if (entry.fd >= new_dev.fd) + { + new_dev.fd = entry.fd + 1; + } + } + } + devices_.push_back(new_dev); + } + + LOG(INFO, "[I2C] Using fd: %d (I2C%d) for %s", new_dev.fd, new_dev.port, + path); + + return new_dev.fd; +} + +int Esp32HardwareI2C::close(int fd) +{ + AtomicHolder h(this); + + auto entry = std::find_if(devices_.begin(), devices_.end(), + [fd](const auto &device) + { + return device.fd == fd; + }); + + // only delete the device if has been found. + if (entry != devices_.end()) + { + devices_.erase(entry); + } + return 0; +} + +int Esp32HardwareI2C::ioctl(int fd, int cmd, va_list args) +{ + AtomicHolder h(this); + HASSERT(IOC_TYPE(cmd) == I2C_MAGIC); + + auto entry = std::find_if(devices_.begin(), devices_.end(), + [fd](const auto &device) + { + return device.fd == fd; + }); + if (entry == devices_.end()) + { + errno = EBADF; + return -EBADF; + } + + switch(static_cast(cmd)) + { + default: + return -EINVAL; + case I2C_SLAVE: + entry->address = static_cast(va_arg(args, int)); + return 0; + case I2C_RDWR: + struct i2c_rdwr_ioctl_data *data = + reinterpret_cast(va_arg(args, uintptr_t)); + return transfer_messages(entry->port, data->msgs, data->nmsgs); + } + + return 0; +} + +int Esp32HardwareI2C::transfer_messages( + const i2c_port_t port, struct i2c_msg *msgs, int num) +{ + int total_len = 0; + esp_err_t res = ESP_OK; + + for (int idx = 0; idx < num; idx++) + { + struct i2c_msg *msg = msgs + idx; + if (msg->flags & I2C_M_RD) + { + res = + ESP_ERROR_CHECK_WITHOUT_ABORT( + i2c_master_read_from_device(port, msg->addr, + (uint8_t *)msg->buf, msg->len, I2C_OP_TIMEOUT)); + } + else + { + res = + ESP_ERROR_CHECK_WITHOUT_ABORT( + i2c_master_write_to_device(port, msg->addr, + (uint8_t *)msg->buf, msg->len, I2C_OP_TIMEOUT)); + } + if (res == ESP_ERR_TIMEOUT) + { + return -ETIMEDOUT; + } + else if (res == ESP_ERR_INVALID_STATE) + { + return -EINVAL; + } + else if (res != ESP_OK) + { + return -EIO; + } + total_len += msg->len; + } + return total_len; +} + +} // namespace openmrn_arduino + +#endif // ESP_PLATFORM \ No newline at end of file diff --git a/src/freertos_drivers/esp32/Esp32HardwareI2C.hxx b/src/freertos_drivers/esp32/Esp32HardwareI2C.hxx new file mode 100644 index 000000000..e4d47b2c1 --- /dev/null +++ b/src/freertos_drivers/esp32/Esp32HardwareI2C.hxx @@ -0,0 +1,230 @@ + +/** \copyright + * Copyright (c) 2023, Mike Dunston + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file Esp32HardwareI2C.hxx + * + * I2C driver implementation for OpenMRN. This leverages the ESP-IDF I2C driver + * implementation and provides a VFS interface. + * + * @author Mike Dunston + * @date 8 Feb 2023 + */ + +#ifndef _FREERTOS_DRIVERS_ESP32_ESP32HARDWAREI2C_HXX_ +#define _FREERTOS_DRIVERS_ESP32_ESP32HARDWAREI2C_HXX_ + +#include "i2c.h" +#include "i2c-dev.h" +#include +#include +#include +#include +#include + +namespace openmrn_arduino +{ + +/// The ESP32 has one or two built-in I2C controller interfaces, this code +/// provides a VFS interface that can be used by various I2C drivers to access +/// the underlying I2C bus. +/// +/// Only one instance of this class should be created in the application so the +/// memory overhead is minimized and performance of the VFS minimally impacted. +/// +/// Example usage (scanner): +/// ``` +/// Esp32HardwareI2C i2c; +/// +/// void setup() +/// { +/// i2c.hw_init(GPIO_NUM_4, GPIO_NUM_5, 100000); +/// i2c.scan(); +/// } +/// ``` +/// +/// Example usage (MCP23017): +/// ``` +/// Esp32HardwareI2C i2c; +/// Executor<1> io_executor("io_thread", 1, 1300); +/// MCP23017 expander(&io_executor, 0, 0, 0); +/// +/// void setup() +/// { +/// i2c.hw_init(GPIO_NUM_4, GPIO_NUM_5, 100000); +/// expander.init("/dev/i2c/0"); +/// ... +/// } +/// ``` +class Esp32HardwareI2C : private Atomic +{ +public: + /// Constructor. + /// + /// @param path Base path to use for I2C drivers. + Esp32HardwareI2C(const char * const path = "/dev/i2c"); + + /// Destructor. + ~Esp32HardwareI2C(); + + /// Initializes the underlying I2C controller hardware and VFS interface. + /// + /// @param sda GPIO pin to use for I2C data transfer. + /// @param scl GPIO pin to use for I2C clock. + /// @param bus_speed I2C clock frequency. + /// @param port I2C controller to initialize. + /// + /// For hardware which supports more than one I2C controller, this method + /// must be called once per controller being used. + /// + /// NOTE: This must be called prior to usage of any other methods for each + /// I2C controller being used. + void hw_init(const gpio_num_t sda, const gpio_num_t scl, + const uint32_t bus_speed, const i2c_port_t port = I2C_NUM_0); + + /// Utility method to perform a scan for all devices on the I2C bus. + /// + /// @param port Hardware I2C controller port number to use for scanning. + /// + /// NOTE: The hw_init method must have been run prior to calling this + /// method. Failure to do so will result in an error being raised. + void scan(const i2c_port_t port); + + /// VFS implementation of write(fd, buf, size) + /// + /// @param fd is the file descriptor being written to. + /// @param buf is the buffer containing the data to be written. + /// @param size is the size of the buffer. + /// + /// @return number of bytes written or -1 if there is the write would be a + /// blocking operation. + /// + /// NOTE: The provided fd is used internally to determine which I2C + /// controller should be used and the address to write the data to. If the + /// address has not been set + ssize_t write(int fd, const void *buf, size_t size); + + /// VFS implementation of read(fd, buf, size) + /// + /// @param fd is the file descriptor being read from. + /// @param buf is the buffer to write into. + /// @param size is the size of the buffer. + /// @return number of bytes read or -1 if there is the read would be a + /// blocking operation. + ssize_t read(int fd, void *buf, size_t size); + + /// VFS implementation of open(path, flags, mode). + /// + /// @param path is the path to the file being opened. + /// @param flags are the flags to use for opened file. + /// @param mode is the mode to use for the opened file. + /// + /// When this method is invoked it will enable the TWAI driver and start the + /// periodic timer used for RX/TX of frame data. + /// + /// @return 0 upon success, -1 upon failure with errno containing the cause. + int open(const char *path, int flags, int mode); + + /// VFS implementation of close(fd). + /// + /// @param fd is the file descriptor to close. + /// + /// When this method is invoked it will disable the TWAI driver and stop the + /// periodic timer used for RX/TX of frame data if it is running. + /// + /// @return zero upon success, negative value with errno for failure. + int close(int fd); + + /// VFS implementation of ioctl. + /// + /// @param fd is the file descriptor to operate on. + /// @param cmd is the command to execute. + /// @param args is the args for the command. + /// + /// @return zero upon success, negative value with errno for failure. + int ioctl(int fd, int cmd, va_list args); + +private: + /// VFS Mount point. + const char * const path_; + + /// Timeout to use for I2C operations, default is one second. + static constexpr TickType_t I2C_OP_TIMEOUT = pdMS_TO_TICKS(1000); + + /// Timeout to use for I2C device scanning, default is 50 milliseconds. + static constexpr TickType_t I2C_SCAN_TIMEOUT = pdMS_TO_TICKS(50); + + /// ISR flags to use for I2C, this defaults to allowing usage of a shared + /// interupt. + static constexpr int I2C_ISR_FLAGS = ESP_INTR_FLAG_SHARED; + + /// I2C "Slave" RX buffer size, we do not use/support this feature so it is + /// set to zero. + static constexpr size_t I2C_SLAVE_RX_BUF_SIZE = 0; + + /// I2C "Slave" TX buffer size, we do not use/support this feature so it is + /// set to zero. + static constexpr size_t I2C_SLAVE_TX_BUF_SIZE = 0; + + /// Tracking structure used to map file handles to an I2C controller and + /// address. + struct i2c_device_t + { + /// I2C Controller that this file handle will use. + i2c_port_t port; + + /// I2C address to use when greater than zero. + int address; + + /// Assigned file handle for this entry. + int fd; + }; + + /// Collection of I2C devices that have been opened. + std::vector devices_; + + /// Internal tracking for initialization of the underlying I2C hardware. + bool i2cInitialized_[SOC_I2C_NUM]; + + /// Internal tracking for the VFS adapter layer. + bool vfsInitialized_{false}; + + /// Transfers multiple payloads to I2C devices. + /// + /// @param port Underlying @ref i2c_port_t to use for I2C transfers. + /// @param msgs payloads to be transfered, this includes the target device + /// address, data to transfer and length of data. + /// @param num Number of transfers to perform. + /// + /// @return Number of bytes transfered or an error code (negative value). + int transfer_messages(const i2c_port_t port, struct i2c_msg *msgs, int num); +}; + +}; // namespace openmrn_arduino + +using openmrn_arduino::Esp32HardwareI2C; + +#endif // _FREERTOS_DRIVERS_ESP32_ESP32HARDWAREI2C_HXX_ \ No newline at end of file diff --git a/src/freertos_drivers/esp32/Esp32HardwareTwai.cxx b/src/freertos_drivers/esp32/Esp32HardwareTwai.cxx index 029af31c0..04d5c6b82 100644 --- a/src/freertos_drivers/esp32/Esp32HardwareTwai.cxx +++ b/src/freertos_drivers/esp32/Esp32HardwareTwai.cxx @@ -35,14 +35,10 @@ * @date 1 May 2021 */ -// Ensure we only compile this code for the ESP32 family of MCUs and that the -// ESP-IDF version is supported for this code. -#if defined(ESP32) +// Ensure we only compile this code for the ESP32 family of MCUs. +#if defined(ESP_PLATFORM) #include "sdkconfig.h" -#include - -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,3,0) #if CONFIG_VFS_SUPPORT_TERMIOS // remove defines added by arduino-esp32 core/esp32/binary.h which are @@ -53,11 +49,12 @@ #include #include -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5,0,0) +#include + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5,1,0) +#include +#endif #include -#else // IDF v4.x (or earlier) -#include -#endif // IDF v5+ #include #include #include @@ -610,7 +607,7 @@ static inline uint32_t twai_rx_frames() uint32_t rx_ready_count = twai_hal_get_rx_msg_count(&twai.context); struct can_frame *can_frame = nullptr; uint32_t rx_count = 0; - ESP_EARLY_LOGV(TWAI_LOG_TAG, "rx-ready-count: %d", rx_ready_count); + ESP_EARLY_LOGV(TWAI_LOG_TAG, "rx-ready-count: %" PRIu32, rx_ready_count); for (uint32_t idx = 0; idx < rx_ready_count; idx++) { twai_hal_frame_t frame; @@ -620,7 +617,7 @@ static inline uint32_t twai_rx_frames() { // DLC is longer than supported, discard the frame. twai.stats.rx_discard++; - ESP_EARLY_LOGE(TWAI_LOG_TAG, "rx-discard:%d", + ESP_EARLY_LOGE(TWAI_LOG_TAG, "rx-discard:%" PRIu32, twai.stats.rx_discard); } else if (twai.rx_buf->data_write_pointer(&can_frame)) @@ -652,13 +649,13 @@ static inline uint32_t twai_rx_frames() else { twai.stats.rx_missed++; - ESP_EARLY_LOGE(TWAI_LOG_TAG, "rx-missed:%d", + ESP_EARLY_LOGV(TWAI_LOG_TAG, "rx-missed:%" PRIu32, twai.stats.rx_missed); } } else { - ESP_EARLY_LOGE(TWAI_LOG_TAG, "rx-overrun"); + ESP_EARLY_LOGV(TWAI_LOG_TAG, "rx-overrun"); // If the SOC does not support automatic clearing of the RX FIFO we need to // handle it here and break out of the loop. #ifndef SOC_TWAI_SUPPORTS_RX_STATUS @@ -716,7 +713,7 @@ static void twai_isr(void *arg) { BaseType_t wakeup = pdFALSE; uint32_t events = twai_hal_get_events(&twai.context); - ESP_EARLY_LOGV(TWAI_LOG_TAG, "events: %08x", events); + ESP_EARLY_LOGV(TWAI_LOG_TAG, "events: %04" PRIx32, events); #if defined(CONFIG_TWAI_ERRATA_FIX_RX_FRAME_INVALID) || \ defined(CONFIG_TWAI_ERRATA_FIX_RX_FIFO_CORRUPT) @@ -788,14 +785,16 @@ static void twai_isr(void *arg) if (events & TWAI_HAL_EVENT_BUS_ERR) { twai.stats.bus_error++; - ESP_EARLY_LOGV(TWAI_LOG_TAG, "bus-error:%d", twai.stats.bus_error); + ESP_EARLY_LOGV(TWAI_LOG_TAG, "bus-error:%" PRIu32, + twai.stats.bus_error); } // Arbitration error detected if (events & TWAI_HAL_EVENT_ARB_LOST) { twai.stats.arb_loss++; - ESP_EARLY_LOGV(TWAI_LOG_TAG, "arb-lost:%d", twai.stats.arb_loss); + ESP_EARLY_LOGV(TWAI_LOG_TAG, "arb-lost:%" PRIu32, + twai.stats.arb_loss); } if (wakeup == pdTRUE) @@ -876,9 +875,11 @@ void* twai_watchdog(void* param) { LOG(INFO, "ESP-TWAI: " - "RX:%d (pending:%zu,overrun:%d,discard:%d,missed:%d,lost:%d) " - "TX:%d (pending:%zu,suc:%d,fail:%d) " - "Bus (arb-loss:%d,err:%d,state:%s)", + "RX:%" PRIu32 " (pending:%zu,overrun:%" PRIu32 + ",discard:%" PRIu32 ",missed:%" PRIu32 ",lost:%" PRIu32 ") " + "TX:%" PRIu32 " (pending:%zu,suc:%" PRIu32 + ",fail:%" PRIu32 ") " + "Bus (arb-err:%" PRIu32 ",err:%" PRIu32 ",state:%s)", twai.stats.rx_processed, twai.rx_buf->pending(), twai.stats.rx_overrun, twai.stats.rx_discard, twai.stats.rx_missed, twai.stats.rx_lost, @@ -1005,12 +1006,46 @@ void Esp32HardwareTwai::hw_init() periph_module_reset(PERIPH_TWAI_MODULE); periph_module_enable(PERIPH_TWAI_MODULE); - HASSERT(twai_hal_init(&twai.context)); + twai_timing_config_t timingCfg = TWAI_TIMING_CONFIG_125KBITS(); twai_filter_config_t filterCfg = TWAI_FILTER_CONFIG_ACCEPT_ALL(); + +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5,1,0) + // default clock source if not specified in config. + if (timingCfg.clk_src == 0) + { + timingCfg.clk_src = TWAI_CLK_SRC_DEFAULT; + } + twai_hal_config_t twai_hal_cfg = + { + .controller_id = 0, + .clock_source_hz = 0, + }; + + // retrieve the clock frequency from the SoC + esp_clk_tree_src_get_freq_hz((soc_module_clk_t)timingCfg.clk_src, + ESP_CLK_TREE_SRC_FREQ_PRECISION_CACHED, &twai_hal_cfg.clock_source_hz); + + // BRP validations + uint32_t brp = timingCfg.brp; + if (timingCfg.quanta_resolution_hz) + { + HASSERT(twai_hal_cfg.clock_source_hz % timingCfg.quanta_resolution_hz == 0); + brp = twai_hal_cfg.clock_source_hz / timingCfg.quanta_resolution_hz; + } + HASSERT(twai_ll_check_brp_validation(brp)); + + // Initialize the low level HAL APIs + HASSERT(twai_hal_init(&twai.context, &twai_hal_cfg)); +#else + // Initialize the low level HAL APIs + HASSERT(twai_hal_init(&twai.context)); +#endif // IDF v5.1+ + LOG(VERBOSE, "ESP-TWAI: Initiailizing peripheral"); twai_hal_configure(&twai.context, &timingCfg, &filterCfg, TWAI_DEFAULT_INTERRUPTS, 0); + #if SOC_CPU_CORES_NUM > 1 ESP_ERROR_CHECK( esp_ipc_call_blocking(preferredIsrCore_, esp32_twai_isr_init, nullptr)); @@ -1031,6 +1066,4 @@ void Esp32HardwareTwai::get_driver_stats(esp32_twai_stats_t *stats) } // namespace openmrn_arduino -#endif // IDF v4.3+ - -#endif // ESP32 +#endif // ESP_PLATFORM diff --git a/src/freertos_drivers/esp32/Esp32HardwareTwai.hxx b/src/freertos_drivers/esp32/Esp32HardwareTwai.hxx index b934d6125..957cb8aa2 100644 --- a/src/freertos_drivers/esp32/Esp32HardwareTwai.hxx +++ b/src/freertos_drivers/esp32/Esp32HardwareTwai.hxx @@ -37,12 +37,6 @@ #ifndef _FREERTOS_DRIVERS_ESP32_ESP32HARDWARETWAI_HXX_ #define _FREERTOS_DRIVERS_ESP32_ESP32HARDWARETWAI_HXX_ -#include - -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,3,0) -#error Esp32HardwareTwai is only supported on ESP-IDF v4.3 and above. -#endif // IDF v4.3+ - #include #include #include @@ -222,4 +216,4 @@ private: using openmrn_arduino::esp32_twai_stats_t; using openmrn_arduino::Esp32HardwareTwai; -#endif // _FREERTOS_DRIVERS_ESP32_ESP32HARDWARETWAI_HXX_ \ No newline at end of file +#endif // _FREERTOS_DRIVERS_ESP32_ESP32HARDWARETWAI_HXX_ diff --git a/src/freertos_drivers/esp32/Esp32Ledc.cxx b/src/freertos_drivers/esp32/Esp32Ledc.cxx index 0d856f064..7dfa35b83 100644 --- a/src/freertos_drivers/esp32/Esp32Ledc.cxx +++ b/src/freertos_drivers/esp32/Esp32Ledc.cxx @@ -33,11 +33,7 @@ */ // Ensure we only compile this code for the ESP32 family of MCUs. -#if defined(ESP32) - -#include - -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,3,0) +#if defined(ESP_PLATFORM) #include "Esp32Ledc.hxx" @@ -48,6 +44,4 @@ pthread_once_t Esp32Ledc::ledcFadeOnce_ = PTHREAD_ONCE_INIT; } // namespace openmrn_arduino -#endif // IDF v4.3+ - -#endif // ESP32 \ No newline at end of file +#endif // ESP_PLATFORM diff --git a/src/freertos_drivers/esp32/Esp32Ledc.hxx b/src/freertos_drivers/esp32/Esp32Ledc.hxx index 72d5bfff8..a3fb991a8 100644 --- a/src/freertos_drivers/esp32/Esp32Ledc.hxx +++ b/src/freertos_drivers/esp32/Esp32Ledc.hxx @@ -111,7 +111,7 @@ public: { // Ensure the pin count is valid and within range of usable channels. HASSERT(pins_.size() > 0 && - pins_.size() < (LEDC_CHANNEL_MAX - first_channel)); + pins_.size() <= (LEDC_CHANNEL_MAX - first_channel)); memset(&timerConfig_, 0, sizeof(ledc_timer_config_t)); // timerConfig_.speed_mode will be assigned the SOC default mode, which // is either HIGH speed or LOW speed depending on the hardware support. @@ -132,7 +132,8 @@ public: void hw_init() { LOG(INFO, - "[Esp32Ledc:%d] Configuring timer (resolution:%d, frequency:%d)", + "[Esp32Ledc:%d] Configuring timer (resolution:%d, frequency:%" + PRIu32 ")", timerConfig_.timer_num, (1 << (uint8_t)timerConfig_.duty_resolution) - 1, timerConfig_.freq_hz); @@ -277,6 +278,7 @@ private: { ESP_ERROR_CHECK( ledc_set_duty(timerConfig_.speed_mode, channel, counts)); + ESP_ERROR_CHECK(ledc_update_duty(timerConfig_.speed_mode, channel)); } /// Gets the duty cycle. @@ -323,4 +325,4 @@ private: using openmrn_arduino::Esp32Ledc; -#endif // _DRIVERS_ESP32LEDC_HXX_ \ No newline at end of file +#endif // _DRIVERS_ESP32LEDC_HXX_ diff --git a/src/freertos_drivers/esp32/Esp32SocInfo.cxx b/src/freertos_drivers/esp32/Esp32SocInfo.cxx index e56611b2f..db4a50cc6 100644 --- a/src/freertos_drivers/esp32/Esp32SocInfo.cxx +++ b/src/freertos_drivers/esp32/Esp32SocInfo.cxx @@ -33,16 +33,13 @@ * @date 4 May 2021 */ -#if defined(ESP32) +#if defined(ESP_PLATFORM) #include "freertos_drivers/esp32/Esp32SocInfo.hxx" #include "utils/logging.h" #include - -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,4,0) #include -#endif // IDF v4.4+ namespace openmrn_arduino { @@ -265,25 +262,14 @@ uint8_t Esp32SocInfo::print_soc_info() chip_info.features & CHIP_FEATURE_BLE ? "Yes" : "No", chip_info.features & CHIP_FEATURE_BT ? "Yes" : "No"); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,3,0) LOG(INFO, "[SoC] Heap: %.2fkB / %.2fkB", heap_caps_get_free_size(MALLOC_CAP_INTERNAL) / 1024.0f, heap_caps_get_total_size(MALLOC_CAP_INTERNAL) / 1024.0f); -#if CONFIG_SPIRAM_SUPPORT || BOARD_HAS_PSRAM +#if CONFIG_SPIRAM_SUPPORT || BOARD_HAS_PSRAM || CONFIG_SPIRAM LOG(INFO, "[SoC] PSRAM: %.2fkB / %.2fkB", heap_caps_get_free_size(MALLOC_CAP_SPIRAM) / 1024.0f, heap_caps_get_total_size(MALLOC_CAP_SPIRAM) / 1024.0f); -#endif // CONFIG_SPIRAM_SUPPORT || BOARD_HAS_PSRAM - -#else // NOT IDF v4.3+ - LOG(INFO, "[SoC] Free Heap: %.2fkB", - heap_caps_get_free_size(MALLOC_CAP_INTERNAL) / 1024.0f); -#if CONFIG_SPIRAM_SUPPORT || BOARD_HAS_PSRAM - LOG(INFO, "[SoC] Free PSRAM: %.2fkB", - heap_caps_get_free_size(MALLOC_CAP_SPIRAM) / 1024.0f); -#endif // CONFIG_SPIRAM_SUPPORT || BOARD_HAS_PSRAM - -#endif // IDF v4.3+ +#endif // CONFIG_SPIRAM_SUPPORT || BOARD_HAS_PSRAM || CONFIG_SPIRAM LOG(INFO, "[SoC] App running from partition: %s", esp_ota_get_running_partition()->label); @@ -297,4 +283,4 @@ uint8_t Esp32SocInfo::print_soc_info() } // namespace openmrn_arduino -#endif // ESP32 \ No newline at end of file +#endif // ESP_PLATFORM diff --git a/src/freertos_drivers/esp32/Esp32SocInfo.hxx b/src/freertos_drivers/esp32/Esp32SocInfo.hxx index e683b8309..f55cc260b 100644 --- a/src/freertos_drivers/esp32/Esp32SocInfo.hxx +++ b/src/freertos_drivers/esp32/Esp32SocInfo.hxx @@ -36,17 +36,12 @@ #include -#if defined(ESP32) +#if defined(ESP_PLATFORM) #include "sdkconfig.h" -#include #if defined(CONFIG_IDF_TARGET_ESP32) -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,3,0) #include -#else -#include -#endif // IDF v4.3+ #elif defined(CONFIG_IDF_TARGET_ESP32S2) #include #elif defined(CONFIG_IDF_TARGET_ESP32S3) @@ -76,6 +71,6 @@ public: using openmrn_arduino::Esp32SocInfo; -#endif // ESP32 +#endif // ESP_PLATFORM -#endif // _FREERTOS_DRIVERS_ESP32_ESP32SOCINFO_HXX_ \ No newline at end of file +#endif // _FREERTOS_DRIVERS_ESP32_ESP32SOCINFO_HXX_ diff --git a/src/freertos_drivers/esp32/Esp32WiFiManager.cxx b/src/freertos_drivers/esp32/Esp32WiFiManager.cxx index 9bd94acbd..5471345f8 100644 --- a/src/freertos_drivers/esp32/Esp32WiFiManager.cxx +++ b/src/freertos_drivers/esp32/Esp32WiFiManager.cxx @@ -32,8 +32,8 @@ * @date 4 February 2019 */ -// Ensure we only compile this code on ESP32 MCUs -#ifdef ESP32 +// Ensure we only compile this code for ESP32 MCUs +#ifdef ESP_PLATFORM #include "Esp32WiFiManager.hxx" #include "openlcb/SimpleStack.hxx" @@ -53,7 +53,6 @@ // ESP-IDF v4+ has a slightly different directory structure to previous // versions. -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,1,0) #include #include #include @@ -67,12 +66,7 @@ #else // default to ESP32 #include #endif // CONFIG_IDF_TARGET - -#else // ESP-IDF v3.x -#include -#include -#include -#endif // ESP_IDF_VERSION +#include using openlcb::NodeID; using openlcb::SimpleCanStackBase; @@ -317,17 +311,12 @@ Esp32WiFiManager::Esp32WiFiManager(const char *station_ssid Esp32WiFiManager::~Esp32WiFiManager() { -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,1,0) // Remove our event listeners from the event loop, note that we do not stop // the event loop. esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID , &Esp32WiFiManager::process_idf_event); esp_event_handler_unregister(IP_EVENT, ESP_EVENT_ANY_ID , &Esp32WiFiManager::process_idf_event); -#else - // Disconnect from the event loop to prevent a possible null deref. - esp_event_loop_set_cb(nullptr, nullptr); -#endif // IDF v4.1+ // shutdown the background task executor. executor_.shutdown(); @@ -405,8 +394,8 @@ ConfigUpdateListener::UpdateAction Esp32WiFiManager::apply_configuration( // Calculate CRC32 from the loaded buffer. uint32_t configCrc32 = crc32_le(0, crcbuf.get(), cfg_.size()); - LOG(VERBOSE, "existing config CRC32:%d, new CRC32:%d", configCrc32_, - configCrc32); + LOG(VERBOSE, "existing config CRC32:%" PRIu32 ", new CRC32:%" PRIu32, + configCrc32_, configCrc32); if (initial_load) { @@ -480,12 +469,11 @@ void Esp32WiFiManager::factory_reset(int fd) CDI_FACTORY_RESET(cfg_.uplink().reconnect); } -#if defined(ESP_IDF_VERSION) && ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,1,0) void Esp32WiFiManager::process_idf_event(void *arg, esp_event_base_t event_base , int32_t event_id, void *event_data) { - LOG(VERBOSE, "Esp32WiFiManager::process_idf_event(%s, %d, %p)", event_base - , event_id, event_data); + LOG(VERBOSE, "Esp32WiFiManager::process_idf_event(%s, %" PRIi32 ", %p)", + event_base, event_id, event_data); if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) { Singleton::instance()->on_station_started(); @@ -540,65 +528,6 @@ void Esp32WiFiManager::process_idf_event(void *arg, esp_event_base_t event_base } } -#else -// Processes a WiFi system event -esp_err_t Esp32WiFiManager::process_wifi_event(void *ctx, system_event_t *event) -{ - LOG(VERBOSE, "Esp32WiFiManager::process_wifi_event(%d)", event->event_id); - - if (event->event_id == SYSTEM_EVENT_STA_START) - { - Singleton::instance()->on_station_started(); - } - else if (event->event_id == SYSTEM_EVENT_STA_CONNECTED) - { - Singleton::instance()->on_station_connected(); - } - else if (event->event_id == SYSTEM_EVENT_STA_DISCONNECTED) - { - Singleton::instance()->on_station_disconnected( - event->event_info.disconnected.reason); - } - else if (event->event_id == SYSTEM_EVENT_STA_GOT_IP) - { - Singleton::instance()->on_station_ip_assigned( - htonl(event->event_info.got_ip.ip_info.ip.addr)); - } - else if (event->event_id == SYSTEM_EVENT_STA_LOST_IP) - { - Singleton::instance()->on_station_ip_lost(); - } - else if (event->event_id == SYSTEM_EVENT_AP_START) - { - Singleton::instance()->on_softap_start(); - } - else if (event->event_id == SYSTEM_EVENT_AP_STOP) - { - Singleton::instance()->on_softap_stop(); - } - else if (event->event_id == SYSTEM_EVENT_AP_STACONNECTED) - { - auto sta_data = event->event_info.sta_connected; - Singleton::instance()->on_softap_station_connected( - sta_data.mac, sta_data.aid); - } - else if (event->event_id == SYSTEM_EVENT_AP_STADISCONNECTED) - { - auto sta_data = event->event_info.sta_connected; - Singleton::instance()->on_softap_station_disconnected( - sta_data.mac, sta_data.aid); - } - else if (event->event_id == SYSTEM_EVENT_SCAN_DONE) - { - auto scan_data = event->event_info.scan_done; - Singleton::instance()->on_wifi_scan_completed( - scan_data.status, scan_data.number); - } - - return ESP_OK; -} -#endif - // Adds a callback which will be called when the network is up. void Esp32WiFiManager::register_network_up_callback( esp_network_up_callback_t callback) @@ -641,20 +570,27 @@ void Esp32WiFiManager::stop_hub() // Creates a hub listener for this node after loading configuration details. void Esp32WiFiManager::start_hub() { - hubServiceName_ = cfg_.hub().service_name().read(configFd_); - uint16_t hub_port = CDI_READ_TRIMMED(cfg_.hub().port, configFd_); - - LOG(INFO, "[Hub] Starting TCP/IP listener on port %d", hub_port); auto stack = static_cast(stack_); - stack->start_tcp_hub_server(hub_port); - auto hub = stack->get_tcp_hub_server(); - // wait for the hub to complete it's startup tasks - while (!hub->is_started()) + // Check if we already have a GC Hub running or not, if we do then we can + // skip creating one. It would be best to validate the port in use but it + // is not exposed by GcHub at this time. + if (!stack->get_tcp_hub_server()) { - usleep(HUB_STARTUP_DELAY_USEC); + hubServiceName_ = cfg_.hub().service_name().read(configFd_); + uint16_t hub_port = CDI_READ_TRIMMED(cfg_.hub().port, configFd_); + LOG(INFO, "[HUB] Starting TCP/IP listener on port %" PRIu16, hub_port); + stack->start_tcp_hub_server(hub_port); + auto hub = stack->get_tcp_hub_server(); + + // wait for the hub to complete it's startup tasks + while (!hub->is_started()) + { + usleep(HUB_STARTUP_DELAY_USEC); + } + + mdns_publish(hubServiceName_, hub_port); } - mdns_publish(hubServiceName_, hub_port); } // Disconnects and shuts down the uplink connector socket if running. @@ -807,9 +743,9 @@ void Esp32WiFiManager::mdns_publish(string service, const uint16_t port) split_mdns_service_name(&service_name, &protocol_name); esp_err_t res = mdns_service_add( NULL, service_name.c_str(), protocol_name.c_str(), port, NULL, 0); - LOG(VERBOSE, "[mDNS] mdns_service_add(%s.%s:%d):%s." - , service_name.c_str(), protocol_name.c_str(), port - , esp_err_to_name(res)); + LOG(VERBOSE, "[mDNS] mdns_service_add(%s.%s:%" PRIu16 "):%s.", + service_name.c_str(), protocol_name.c_str(), port, + esp_err_to_name(res)); // ESP_FAIL will be triggered if there is a timeout during publish of // the new mDNS entry. The mDNS task runs at a very low priority on the // PRO_CPU which is also where the OpenMRN Executor runs from which can @@ -833,12 +769,12 @@ void Esp32WiFiManager::mdns_publish(string service, const uint16_t port) } else if (res == ESP_OK) { - LOG(INFO, "[mDNS] Advertising %s.%s:%d.", service_name.c_str(), - protocol_name.c_str(), port); + LOG(INFO, "[mDNS] Advertising %s.%s:%" PRIu16 ".", + service_name.c_str(), protocol_name.c_str(), port); } else { - LOG_ERROR("[mDNS] Failed to publish:%s.%s:%d", + LOG_ERROR("[mDNS] Failed to publish:%s.%s:%" PRIu16, service_name.c_str(), protocol_name.c_str(), port); Singleton::instance()->mdns_publish( service, port); @@ -913,12 +849,7 @@ void Esp32WiFiManager::on_station_started() // so that it shows up with the generated hostname instead of // the default "Espressif". LOG(INFO, "[Station] Setting hostname to \"%s\".", hostname_.c_str()); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,1,0) esp_netif_set_hostname(espNetIfaces_[STATION_INTERFACE], hostname_.c_str()); -#else - ESP_ERROR_CHECK(tcpip_adapter_set_hostname( - TCPIP_ADAPTER_IF_STA, hostname_.c_str())); -#endif uint8_t mac[6]; esp_wifi_get_mac(WIFI_IF_STA, mac); LOG(INFO, "[Station] MAC Address:%s", mac_to_string(mac).c_str()); @@ -926,11 +857,7 @@ void Esp32WiFiManager::on_station_started() // Start the DHCP service before connecting so it hooks into // the flow early and provisions the IP automatically. LOG(INFO, "[Station] Starting DHCP Client."); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,1,0) ESP_ERROR_CHECK(esp_netif_dhcpc_start(espNetIfaces_[STATION_INTERFACE])); -#else - ESP_ERROR_CHECK(tcpip_adapter_dhcpc_start(TCPIP_ADAPTER_IF_STA)); -#endif // IDF v4.1+ LOG(INFO, "[Station] Connecting to SSID:%s.", ssid_.c_str()); // Start the SSID connection process. @@ -1077,7 +1004,6 @@ void Esp32WiFiManager::on_softap_start() esp_wifi_get_mac(WIFI_IF_AP, mac); LOG(INFO, "[SoftAP] MAC Address:%s", mac_to_string(mac).c_str()); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,1,0) // Set the generated hostname prior to connecting to the SSID // so that it shows up with the generated hostname instead of // the default "Espressif". @@ -1091,21 +1017,6 @@ void Esp32WiFiManager::on_softap_start() ESP_ERROR_CHECK( esp_netif_get_ip_info(espNetIfaces_[SOFTAP_INTERFACE], &ip_info)); ip_address = ntohl(ip4_addr_get_u32(&ip_info.ip)); -#else - // Set the generated hostname prior to connecting to the SSID - // so that it shows up with the generated hostname instead of - // the default "Espressif". - LOG(INFO, "[SoftAP] Setting hostname to \"%s\".", hostname_.c_str()); - ESP_ERROR_CHECK(tcpip_adapter_set_hostname( - TCPIP_ADAPTER_IF_AP, hostname_.c_str())); - - // fetch the IP address from the adapter since it defaults to - // 192.168.4.1 but can be altered via sdkconfig. - tcpip_adapter_ip_info_t ip_info; - ESP_ERROR_CHECK(tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_AP - , &ip_info)); - ip_address = ntohl(ip4_addr_get_u32(&ip_info.ip)); -#endif // IDF v4.1+ LOG(INFO, "[SoftAP] IP address:%s", ipv4_to_string(ip_address).c_str()); @@ -1180,7 +1091,7 @@ void Esp32WiFiManager::on_wifi_scan_completed(uint32_t status, uint8_t count) { uint16_t num_found = count; esp_wifi_scan_get_ap_num(&num_found); - LOG(VERBOSE, "[WiFi] %d SSIDs found via scan", num_found); + LOG(VERBOSE, "[WiFi] %" PRIu16 " SSIDs found via scan", num_found); ssidScanResults_.resize(num_found); esp_wifi_scan_get_ap_records(&num_found, ssidScanResults_.data()); #if LOGLEVEL >= VERBOSE @@ -1226,12 +1137,17 @@ void Esp32WiFiManager::configure_sntp() { sntpConfigured_ = true; LOG(INFO, "[SNTP] Polling %s for time updates", sntpServer_.c_str()); +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5,1,0) + esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL); + esp_sntp_setservername(0, sntpServer_.c_str()); + sntp_set_time_sync_notification_cb(sntp_update_received); + esp_sntp_init(); +#else sntp_setoperatingmode(SNTP_OPMODE_POLL); - // IDF v3.3 does not offer const correctness so we need to drop const - // when setting the hostname for SNTP. - sntp_setservername(0, const_cast(sntpServer_.c_str())); + sntp_setservername(0, sntpServer_.c_str()); sntp_set_time_sync_notification_cb(sntp_update_received); sntp_init(); +#endif // IDF v5.1+ if (!timeZone_.empty()) { @@ -1296,7 +1212,6 @@ StateFlowBase::Action Esp32WiFiManager::WiFiStackFlow::noop() StateFlowBase::Action Esp32WiFiManager::WiFiStackFlow::init_interface() { -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,1,0) // create default interfaces for station and SoftAP, ethernet is not used // today. ESP_ERROR_CHECK(esp_netif_init()); @@ -1323,15 +1238,6 @@ StateFlowBase::Action Esp32WiFiManager::WiFiStackFlow::init_interface() &Esp32WiFiManager::process_idf_event, nullptr); esp_event_handler_register(IP_EVENT, ESP_EVENT_ANY_ID, &Esp32WiFiManager::process_idf_event, nullptr); -#else // NOT IDF v4.1+ - // Initialize the TCP/IP adapter stack. - LOG(INFO, "[WiFi] Starting TCP/IP stack"); - tcpip_adapter_init(); - - // Install event loop handler. - ESP_ERROR_CHECK( - esp_event_loop_init(&Esp32WiFiManager::process_wifi_event, nullptr)); -#endif // IDF v4.1+ return yield_and_call(STATE(init_wifi)); } @@ -1419,11 +1325,7 @@ StateFlowBase::Action Esp32WiFiManager::WiFiStackFlow::configure_station() } LOG(INFO, "[WiFi] Configuring Station (SSID:%s)", conf.sta.ssid); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,3,0) ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &conf)); -#else - ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &conf)); -#endif // IDF v4.0+ return yield_and_call(STATE(start_wifi)); } @@ -1478,11 +1380,7 @@ StateFlowBase::Action Esp32WiFiManager::WiFiStackFlow::configure_softap() } LOG(INFO, "[WiFi] Configuring SoftAP (SSID:%s)", conf.ap.ssid); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,3,0) ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &conf)); -#else - ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &conf)); -#endif // IDF v4.0+ // If we are only enabling the SoftAP we can transition to starting the // WiFi stack. @@ -1722,7 +1620,7 @@ int mdns_lookup( if (ipaddr->addr.type == IPADDR_TYPE_V4) { LOG(ESP32_WIFIMGR_MDNS_LOOKUP_LOG_LEVEL, - "[mDNS] Found %s as providing service:%s on port %d.", + "[mDNS] Found %s providing service:%s on port %" PRIu16, res->hostname, service, res->port); inet_addr_from_ip4addr( &sa_in->sin_addr, &ipaddr->addr.u_addr.ip4); @@ -1762,17 +1660,11 @@ int mdns_lookup( /// @return zero for success, -1 for failure. int getifaddrs(struct ifaddrs **ifap) { -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5,0,0) esp_netif_ip_info_t ip_info; -#else - tcpip_adapter_ip_info_t ip_info; -#endif // IDF v5+ /* start with something "safe" in case we bail out early */ *ifap = nullptr; -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5,0,0) - // Lookup the interface by it's internal name assigned by ESP-IDF. esp_netif_t *iface = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"); if (iface == nullptr) @@ -1789,17 +1681,6 @@ int getifaddrs(struct ifaddrs **ifap) errno = EADDRNOTAVAIL; return -1; } -#else // IDF v4 (or lower) - if (!tcpip_adapter_is_netif_up(TCPIP_ADAPTER_IF_STA)) - { - // Station TCP/IP interface is not up - errno = ENODEV; - return -1; - } - - // retrieve TCP/IP address from the interface - tcpip_adapter_get_ip_info(TCPIP_ADAPTER_IF_STA, &ip_info); -#endif // IDF v5+ // allocate memory for various pieces of ifaddrs std::unique_ptr ia(new struct ifaddrs); diff --git a/src/freertos_drivers/esp32/Esp32WiFiManager.hxx b/src/freertos_drivers/esp32/Esp32WiFiManager.hxx index a1996c8dd..7ad7dae6d 100644 --- a/src/freertos_drivers/esp32/Esp32WiFiManager.hxx +++ b/src/freertos_drivers/esp32/Esp32WiFiManager.hxx @@ -46,16 +46,10 @@ #include "utils/macros.h" #include "utils/Singleton.hxx" -#include -#include #include -#include - -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,1,0) #include -#else -#include -#endif // IDF v4.1+ +#include +#include namespace openlcb { @@ -220,7 +214,6 @@ public: /// @param fd is the file descriptor used for the configuration settings. void factory_reset(int fd) override; -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,1,0) /// Processes an event coming from the ESP-IDF default event loop. /// /// @param ctx context parameter (unused). @@ -231,22 +224,6 @@ public: /// NOTE: This is not intended to be called by the user. static void process_idf_event(void *ctx, esp_event_base_t event_base , int32_t event_id, void *event_data); -#else - /// Processes an ESP-IDF WiFi event based on the event raised by the - /// ESP-IDF event loop processor. This should be used when the - /// Esp32WiFiManager is not managing the WiFi or MDNS systems so that - /// it can react to WiFi events to cleanup or recreate the hub or uplink - /// connections as required. When Esp32WiFiManager is managing the WiFi - /// connection this method will be called automatically from the - /// esp_event_loop. Note that ESP-IDF only supports one callback being - /// registered. - /// - /// @param ctx context parameter (unused). - /// @param event is the system_event_t raised by ESP-IDF. - /// - /// NOTE: This is not intended to be called by the user. - static esp_err_t process_wifi_event(void *ctx, system_event_t *event); -#endif /// If called, sets the ESP32 wifi stack to log verbose information to the /// console. @@ -601,13 +578,11 @@ private: /// Constant used to determine if the Hub mode should be enabled. static constexpr uint8_t CONN_MODE_HUB_BIT = BIT(1); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,1,0) /// Network interfaces that are managed by Esp32WiFiManager. esp_netif_t *espNetIfaces_[MAX_NETWORK_INTERFACES] { nullptr, nullptr }; -#endif // IDF v4.1+ /// This class provides a proxy for the @ref Notifiable provided by the /// @ref SocketClient. This is necessary to ensure the @ref Notifiable does diff --git a/src/openlcb/ApplicationChecksum.hxx b/src/openlcb/ApplicationChecksum.hxx index 0512a966a..4b6e390bb 100644 --- a/src/openlcb/ApplicationChecksum.hxx +++ b/src/openlcb/ApplicationChecksum.hxx @@ -35,7 +35,7 @@ #ifndef _OPENLCB_APPLICATIONCHECKSUM_HXX_ #define _OPENLCB_APPLICATIONCHECKSUM_HXX_ -#ifdef ESP32 +#ifdef ESP_PLATFORM #include "bootloader_hal.h" #else #include "freertos/bootloader_hal.h" diff --git a/src/openlcb/Bootloader.hxx b/src/openlcb/Bootloader.hxx index 8bc426faa..713e86d05 100644 --- a/src/openlcb/Bootloader.hxx +++ b/src/openlcb/Bootloader.hxx @@ -43,7 +43,7 @@ #include #include -#ifdef ESP32 +#ifdef ESP_PLATFORM #include "bootloader_hal.h" #else #include "freertos/bootloader_hal.h" diff --git a/src/openlcb/BroadcastTime.cxx b/src/openlcb/BroadcastTime.cxx index e74e57103..11444f107 100644 --- a/src/openlcb/BroadcastTime.cxx +++ b/src/openlcb/BroadcastTime.cxx @@ -46,7 +46,7 @@ namespace openlcb // void BroadcastTime::clear_timezone() { -#ifndef ESP32 +#ifndef ESP_PLATFORM setenv("TZ", "GMT0", 1); tzset(); #endif diff --git a/src/openlcb/ServoConsumer.hxx b/src/openlcb/ServoConsumer.hxx index 9783244c6..29771cbb9 100644 --- a/src/openlcb/ServoConsumer.hxx +++ b/src/openlcb/ServoConsumer.hxx @@ -1,7 +1,7 @@ #ifndef _OPENLCB_SERVOCONSUMER_HXX_ #define _OPENLCB_SERVOCONSUMER_HXX_ -#if defined(ARDUINO) || defined(ESP32) +#if defined(ARDUINO) || defined(ESP_PLATFORM) #include "freertos_drivers/arduino/DummyGPIO.hxx" #include "freertos_drivers/arduino/PWM.hxx" #else diff --git a/src/os/OS.hxx b/src/os/OS.hxx index 50edeb6cd..bbf752c8d 100644 --- a/src/os/OS.hxx +++ b/src/os/OS.hxx @@ -733,7 +733,7 @@ private: /** handle to event object */ EventGroupHandle_t event; }; -#elif defined(ARDUINO) && !defined(ESP32) +#elif defined(ARDUINO) && !defined(ESP_PLATFORM) typedef uint32_t OSEventType; diff --git a/src/os/OSSelectWakeup.cxx b/src/os/OSSelectWakeup.cxx index 6d1fef562..2ee394bff 100644 --- a/src/os/OSSelectWakeup.cxx +++ b/src/os/OSSelectWakeup.cxx @@ -74,7 +74,7 @@ int OSSelectWakeup::select(int nfds, fd_set *readfds, int ret = ::pselect(nfds, readfds, writefds, exceptfds, &timeout, &origMask_); #elif OPENMRN_HAVE_SELECT -#ifdef ESP32 +#ifdef ESP_PLATFORM fd_set newexcept; if (!exceptfds) { @@ -86,7 +86,7 @@ int OSSelectWakeup::select(int nfds, fd_set *readfds, { nfds = vfsFd_ + 1; } -#endif //ESP32 +#endif // ESP_PLATFORM struct timeval timeout; // divide in two steps to avoid overflow on ESP32 timeout.tv_sec = (deadline_nsec / 1000) / 1000000LL; @@ -107,12 +107,13 @@ int OSSelectWakeup::select(int nfds, fd_set *readfds, return ret; } -#ifdef ESP32 +#ifdef ESP_PLATFORM #include "freertos_includes.h" #include #include #include +#include #include #include @@ -129,16 +130,7 @@ static pthread_key_t select_wakeup_key; /// all VFS APIs we implement. static constexpr int WAKEUP_VFS_FD = 0; -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,0,0) -extern "C" -{ - void *sys_thread_sem_get(); - void sys_sem_signal(void *); - void sys_sem_signal_isr(void *); -} -#endif // NOT IDF v4.0+ -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,0,0) /// This function is called by the ESP32's select implementation. It is passed /// in as a function pointer to the VFS API. /// @param nfds see standard select API @@ -150,31 +142,12 @@ extern "C" /// @param end_select_args are the arguments to pass to end_select upon wakeup. static esp_err_t esp_start_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, esp_vfs_select_sem_t signal_sem, void **end_select_args) -#else // NOT IDF v4.0+ -/// This function is called by the ESP32's select implementation. It is passed -/// in as a function pointer to the VFS API. -/// @param nfds see standard select API -/// @param readfds see standard select API -/// @param writefds see standard select API -/// @param exceptfds see standard select API -/// @param signal_sem if non-NULL, the select can be woken up by notifying this -/// semaphore. If NULL, the select can be woken up by notifying the LWIP -/// semaphore. By the API contract this pointer needs to be passed into -/// esp_vfs_select_triggered. -static esp_err_t esp_start_select(int nfds, fd_set *readfds, fd_set *writefds, - fd_set *exceptfds, esp_vfs_select_sem_t signal_sem) -#endif // IDF v4.0+ { OSSelectWakeup *parent = (OSSelectWakeup *)pthread_getspecific(select_wakeup_key); HASSERT(parent); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,0,0) LOG(VERBOSE, "esp start select %p (thr %p parent %p)", signal_sem.sem, os_thread_self(), parent); -#else // NOT IDF v4.0+ - LOG(VERBOSE, "esp start select %p (thr %p parent %p)", signal_sem, - os_thread_self(), parent); -#endif // IDF v4.0+ // Check if our VFS FD is included in exceptfds before tracking that we // should possibly wake up early. @@ -185,7 +158,6 @@ static esp_err_t esp_start_select(int nfds, fd_set *readfds, fd_set *writefds, return ESP_OK; } -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,0,0) /// This function is called inline from the ESP32's select implementation. It is /// passed in as a function pointer to the VFS API. /// @@ -193,11 +165,6 @@ static esp_err_t esp_start_select(int nfds, fd_set *readfds, fd_set *writefds, /// is not used today. /// @return always returns ESP_OK as this is a no-op. static esp_err_t esp_end_select(void *arg) -#else // NOT IDF v4.0+ -/// This function is called inline from the ESP32's select implementation. It is -/// passed in as a function pointer to the VFS API. -static void esp_end_select() -#endif // IDF v4.0+ { OSSelectWakeup *parent = (OSSelectWakeup *)pthread_getspecific(select_wakeup_key); @@ -205,9 +172,7 @@ static void esp_end_select() LOG(VERBOSE, "esp end select (thr %p parent %p)", os_thread_self(), parent); parent->esp_end_select(); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,0,0) return ESP_OK; -#endif // IDF v4.0+ } /// This function is called by the ESP32's select implementation. @@ -253,28 +218,8 @@ void OSSelectWakeup::esp_wakeup() // and not the system global FD. FD_SET(WAKEUP_VFS_FD, exceptFds_); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,0,0) LOG(VERBOSE, "wakeup es %p %u", espSem_.sem, *(unsigned*)espSem_.sem); esp_vfs_select_triggered(espSem_); -#else // IDF v3.x - LOG(VERBOSE, "wakeup es %p %u", espSem_, *(unsigned*)espSem_); - if (espSem_) - { - // Mark the VFS implementation FD for the wakeup call. Note that this - // should not use vfsFd_ since the fd_set will contain the VFS specific - // FD and not the system global FD. - esp_vfs_select_triggered(espSem_); - } - else - { - // Works around a bug in the implementation of - // esp_vfs_select_triggered, which internally calls - // sys_sem_signal(sys_thread_sem_get()); This is buggy because - // sys_thread_sem_get() will get the semaphore that belongs to the - // calling thread, not the target thread to wake up. - sys_sem_signal(lwipSem_); - } -#endif // IDF >= v4.0 } /// This function will trigger the ESP32 to wake up from any pending select() @@ -295,23 +240,7 @@ void OSSelectWakeup::esp_wakeup_from_isr() // and not the system global FD. FD_SET(WAKEUP_VFS_FD, exceptFds_); -#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4,0,0) esp_vfs_select_triggered_isr(espSem_, &woken); -#else // IDF v3.x - if (espSem_) - { - esp_vfs_select_triggered_isr(espSem_, &woken); - } - else - { - // Works around a bug in the implementation of - // esp_vfs_select_triggered, which internally calls - // sys_sem_signal(sys_thread_sem_get()); This is buggy because - // sys_thread_sem_get() will get the semaphore that belongs to the - // calling thread, not the target thread to wake up. - sys_sem_signal_isr(lwipSem_); - } -#endif // IDF >= v4.0 if (woken == pdTRUE) { @@ -339,10 +268,6 @@ static void esp_vfs_init() void OSSelectWakeup::esp_allocate_vfs_fd() { -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,0,0) - lwipSem_ = sys_thread_sem_get(); -#endif // IDF < v4.0 - HASSERT(0 == pthread_once(&vfs_init_once, &esp_vfs_init)); vfsFd_ = ::open("/dev/wakeup/0", 0, 0); HASSERT(vfsFd_ >= 0); @@ -360,4 +285,4 @@ void OSSelectWakeup::esp_deallocate_vfs_fd() vfsFd_ = -1; } -#endif // ESP32 +#endif // ESP_PLATFORM diff --git a/src/os/OSSelectWakeup.hxx b/src/os/OSSelectWakeup.hxx index a6c8aca87..57f1ea708 100644 --- a/src/os/OSSelectWakeup.hxx +++ b/src/os/OSSelectWakeup.hxx @@ -54,7 +54,7 @@ #include #endif -#ifdef ESP32 +#ifdef ESP_PLATFORM #include "sdkconfig.h" #ifdef CONFIG_VFS_SUPPORT_TERMIOS @@ -65,16 +65,8 @@ #endif // CONFIG_VFS_SUPPORT_TERMIOS #include -#include -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,0,0) -// SemaphoreHandle_t is defined by inclusion of esp_vfs.h so no additional -// includes are necessary. -/// Alias for the internal data type used by ESP-IDF select() calls. -typedef SemaphoreHandle_t * esp_vfs_select_sem_t; -#endif // IDF v3.x - -#endif // ESP32 +#endif // ESP_PLATFORM /// Signal handler that does nothing. @param sig ignored. void empty_signal_handler(int sig); @@ -91,7 +83,7 @@ public: ~OSSelectWakeup() { -#ifdef ESP32 +#ifdef ESP_PLATFORM esp_deallocate_vfs_fd(); #endif } @@ -108,7 +100,7 @@ public: { // Gets the current thread. thread_ = os_thread_self(); -#ifdef ESP32 +#ifdef ESP_PLATFORM esp_allocate_vfs_fd(); #endif #if OPENMRN_FEATURE_DEVICE_SELECT @@ -149,7 +141,7 @@ public: Device::select_wakeup(©); #elif OPENMRN_HAVE_PSELECT pthread_kill(thread_, WAKEUP_SIG); -#elif defined(ESP32) +#elif defined(ESP_PLATFORM) esp_wakeup(); #elif !defined(OPENMRN_FEATURE_SINGLE_THREADED) DIE("need wakeup code"); @@ -179,7 +171,7 @@ public: // TODO: confirm if pthread_kill is ISR safe //#elif OPENMRN_HAVE_PSELECT // pthread_kill(thread_, WAKEUP_SIG); -#elif defined(ESP32) +#elif defined(ESP_PLATFORM) esp_wakeup_from_isr(); #else DIE("need wakeup code"); @@ -206,7 +198,7 @@ public: long long deadline_nsec); private: -#ifdef ESP32 +#ifdef ESP_PLATFORM void esp_allocate_vfs_fd(); void esp_deallocate_vfs_fd(); void esp_wakeup(); @@ -233,12 +225,7 @@ private: /// when waking up early from select(). fd_set exceptFdsOrig_; -#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,0,0) - /// Semaphore for waking up LwIP select. - void* lwipSem_{nullptr}; -#endif // IDF < v4.0 - -#endif // ESP32 +#endif // ESP_PLATFORM #if OPENMRN_HAVE_PSELECT /** This signal is used for the wakeup kill in a pthreads OS. */ diff --git a/src/os/os.c b/src/os/os.c index d51c51f60..fa67a7e9f 100644 --- a/src/os/os.c +++ b/src/os/os.c @@ -925,7 +925,7 @@ int ignore_fn(void) return 0; } -#if !defined(ARDUINO) && !defined(ESP32) +#if !defined(ARDUINO) && !defined(ESP_PLATFORM) #if !defined (__MINGW32__) int main(int argc, char *argv[]) __attribute__ ((weak)); @@ -984,7 +984,7 @@ int main(int argc, char *argv[]) #endif } -#endif // ESP32 +#endif // ESP_PLATFORM #if defined(ARDUINO) unsigned critical_nesting; diff --git a/src/utils/Atomic.hxx b/src/utils/Atomic.hxx index 05d0f71aa..fe9b5b41d 100644 --- a/src/utils/Atomic.hxx +++ b/src/utils/Atomic.hxx @@ -71,7 +71,7 @@ public: } }; -#elif defined(ESP32) +#elif defined(ESP_PLATFORM) #include "freertos_includes.h" diff --git a/src/utils/HubDevice.hxx b/src/utils/HubDevice.hxx index 345fe6b79..2bd712759 100644 --- a/src/utils/HubDevice.hxx +++ b/src/utils/HubDevice.hxx @@ -48,7 +48,8 @@ template class FdHubWriteFlow; class FdHubPortBase : public FdHubPortInterface, private Atomic { public: -#ifdef ESP32 // TODO: shrink these if possible +#ifdef ESP_PLATFORM + // TODO: shrink these if possible /// How many bytes of stack should we allocate to the write thread's stack. static const int kWriteThreadStackSize = 2048; /// How many bytes of stack should we allocate to the read thread's stack. @@ -58,7 +59,7 @@ public: static const int kWriteThreadStackSize = 1000; /// How many bytes of stack should we allocate to the read thread's stack. static const int kReadThreadStackSize = 1000; -#endif // ESP32 +#endif // ESP_PLATFORM /// Constructor. /// @param fd is the filedes to read/write diff --git a/src/utils/SocketClient.cxx b/src/utils/SocketClient.cxx index cb349346b..706f858cf 100644 --- a/src/utils/SocketClient.cxx +++ b/src/utils/SocketClient.cxx @@ -42,10 +42,11 @@ #include #include -#ifndef ESP32 // these don't exist on the ESP32 with LWiP +#ifndef ESP_PLATFORM +// these don't exist on the ESP32 with LWiP #include #include -#endif // ESP32 +#endif // ESP_PLATFORM #include #include #include @@ -57,9 +58,10 @@ #include "utils/macros.h" #include "utils/logging.h" -#ifdef ESP32 // this is not declared in netdb.h on ESP32 +#ifdef ESP_PLATFORM +// this is not declared in netdb.h on ESP32 const char *gai_strerror (int __ecode); -#endif // ESP32 +#endif // ESP_PLATFORM int ConnectSocket(const char *host, int port) { diff --git a/src/utils/SocketClient.hxx b/src/utils/SocketClient.hxx index f3c7d494a..3b46b5b31 100644 --- a/src/utils/SocketClient.hxx +++ b/src/utils/SocketClient.hxx @@ -37,7 +37,8 @@ #include #include -#ifndef ESP32 // this doesn't exist on the ESP32 with LWiP +#ifndef ESP_PLATFORM +// this doesn't exist on the ESP32 with LWiP #include #endif #include diff --git a/src/utils/constants.cxx b/src/utils/constants.cxx index 1d70bbd49..a6553a47c 100644 --- a/src/utils/constants.cxx +++ b/src/utils/constants.cxx @@ -159,7 +159,7 @@ DEFAULT_CONST(gridconnect_tcp_notsent_lowat_buffer_size, 1); DEFAULT_CONST_FALSE(gridconnect_tcp_use_select); -#ifdef ESP32 +#ifdef ESP_PLATFORM /// Use a stack size of 3kb for SocketListener tasks. DEFAULT_CONST(socket_listener_stack_size, 3072); /// Allow one socket to be pending for accept() in SocketListener. @@ -169,4 +169,4 @@ DEFAULT_CONST(socket_listener_backlog, 1); DEFAULT_CONST(socket_listener_stack_size, 1000); /// Allow up to five sockets to be pending for accept() in SocketListener. DEFAULT_CONST(socket_listener_backlog, 5); -#endif +#endif // ESP_PLATFORM diff --git a/src/utils/logging.cxx b/src/utils/logging.cxx index 9cc01b3eb..ca433da3b 100644 --- a/src/utils/logging.cxx +++ b/src/utils/logging.cxx @@ -35,7 +35,7 @@ #if defined(__linux__) || defined(__MACH__) char logbuffer[4096]; -#elif defined(ESP32) +#elif defined(ESP_PLATFORM) char logbuffer[1024]; #else /// Temporary buffer to sprintf() the log lines into. @@ -56,7 +56,8 @@ void log_output(char* buf, int size) { send_stdio_serial_message(buf); } -#elif defined(__linux__) || defined(__MACH__) || defined(__EMSCRIPTEN__) || defined(ESP32) +#elif defined(__linux__) || defined(__MACH__) || defined(__EMSCRIPTEN__) || \ + defined(ESP_PLATFORM) #include "utils/stdio_logging.h" diff --git a/src/utils/logging.h b/src/utils/logging.h index ddee34ecb..0e9bf62b7 100644 --- a/src/utils/logging.h +++ b/src/utils/logging.h @@ -125,7 +125,7 @@ extern os_mutex_t g_log_mutex; #if defined(__linux__) || defined(__MACH__) extern char logbuffer[4096]; -#elif defined(ESP32) +#elif defined(ESP_PLATFORM) extern char logbuffer[1024]; #else /// Temporary buffer to sprintf() the log lines into. diff --git a/src/utils/macros.h b/src/utils/macros.h index 58a48e01c..f06cbf763 100644 --- a/src/utils/macros.h +++ b/src/utils/macros.h @@ -91,12 +91,6 @@ extern const char* g_death_file; #elif defined(ESP32) -// For the ESP32 family or MCUs use ets_printf() instead of printf() to bypass -// internal locking within the newlib implementation which has been observed -// crashing the MCU at times when printf() is used in conjuction with ISRs on -// the same core. The call to assert() internally uses ets_printf() as well -// prior to "hanging" the MCU core in a busy loop. - #include #include @@ -113,6 +107,11 @@ extern const char* g_death_file; #error Unknown/Unsupported ESP32 variant. #endif // CONFIG_IDF_TARGET_ESP32 +// For the ESP32 we are using ets_printf() instead of printf() to avoid the +// internal locking within the newlib implementation. This locking can cause +// difficult to parse backtraces when an ISR is on the same core as the code +// that crashes due to these two macros. + #define HASSERT(x) do { if (!(x)) { ets_printf("Assertion failed in file " __FILE__ " line %d: assert(%s)\n", __LINE__, #x); g_death_file = __FILE__; g_death_lineno = __LINE__; assert(0); abort();} } while(0) #define DIE(MSG) do { ets_printf("Crashed in file " __FILE__ " line %d: " MSG "\n", __LINE__); assert(0); abort(); } while(0) diff --git a/src/utils/socket_listener.cxx b/src/utils/socket_listener.cxx index 5dfa241f4..98fd5341a 100644 --- a/src/utils/socket_listener.cxx +++ b/src/utils/socket_listener.cxx @@ -46,11 +46,12 @@ #include "utils/logging.h" #include "utils/macros.h" -#ifndef ESP32 // these don't exist on the ESP32 with LWiP +#ifndef ESP_PLATFORM +// these don't exist on the ESP32 with LWiP #include #include #include -#endif // ESP32 +#endif // ESP_PLATFORM #include #include #include diff --git a/src/utils/stdio_logging.h b/src/utils/stdio_logging.h index 22db9acd2..1c29e1759 100644 --- a/src/utils/stdio_logging.h +++ b/src/utils/stdio_logging.h @@ -7,7 +7,7 @@ extern "C" { #endif #if defined(__linux__) || defined(__MACH__) || defined(__EMSCRIPTEN__) || \ - defined(ESP32) + defined(ESP_PLATFORM) #define LOGWEAK __attribute__((weak)) #else #define LOGWEAK From 6cbc79d1dbdb4498d43c17e01193b3d6a2399300 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 4 Feb 2024 18:57:37 -0800 Subject: [PATCH 08/13] Fix comment. --- src/freertos_drivers/esp32/Esp32Gpio.hxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/freertos_drivers/esp32/Esp32Gpio.hxx b/src/freertos_drivers/esp32/Esp32Gpio.hxx index 159bfe40e..b9f6fbb71 100644 --- a/src/freertos_drivers/esp32/Esp32Gpio.hxx +++ b/src/freertos_drivers/esp32/Esp32Gpio.hxx @@ -552,8 +552,9 @@ template struct GpioInputPUPD : public GpioInputPin Date: Mon, 5 Feb 2024 03:58:21 +0100 Subject: [PATCH 09/13] Fix compiler warnings in openmrn when using new GCC's. (#772) There are a few places where a newer GCC used by esp-idf emits a warning for OpenMRN code. This PR fixes those. The fall through warning unfortunately has false positives, but that's still not a good reason to disable it, we rather adjust the comments here to suit the taste of the compiler. === * Fix compiler warnings in openmrn when using new GCC's. * Fix comment. --- src/executor/Executor.hxx | 2 +- src/freertos_drivers/esp32/Esp32Gpio.hxx | 3 ++- src/openlcb/MemoryConfig.hxx | 5 +++-- src/openlcb/SimpleInfoProtocol.hxx | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/executor/Executor.hxx b/src/executor/Executor.hxx index a760a56b3..f00c6dfb9 100644 --- a/src/executor/Executor.hxx +++ b/src/executor/Executor.hxx @@ -165,7 +165,7 @@ public: /// Helper function for debugging and tracing. /// @return currently running executable or nullptr if none active. - Executable* volatile current() { return current_; } + Executable* current() { return current_; } protected: /** Thread entry point. diff --git a/src/freertos_drivers/esp32/Esp32Gpio.hxx b/src/freertos_drivers/esp32/Esp32Gpio.hxx index f60a44cfc..b9f6fbb71 100644 --- a/src/freertos_drivers/esp32/Esp32Gpio.hxx +++ b/src/freertos_drivers/esp32/Esp32Gpio.hxx @@ -552,8 +552,9 @@ template struct GpioInputPUPD : public GpioInputPin Date: Sun, 4 Feb 2024 19:06:06 -0800 Subject: [PATCH 10/13] Adds documentation comment to latency test consumer. --- src/openlcb/LatencyTestConsumer.hxx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/openlcb/LatencyTestConsumer.hxx b/src/openlcb/LatencyTestConsumer.hxx index ba396ea6b..727decc46 100644 --- a/src/openlcb/LatencyTestConsumer.hxx +++ b/src/openlcb/LatencyTestConsumer.hxx @@ -40,6 +40,24 @@ namespace openlcb { +/// This event consumer works together with the hub_test application in order +/// to detect the response latency of a node. The theory of operation is that +/// hub_test sends out an identify consumer message with a given event ID, and +/// this consumer is going to respond. The hub_test then measures the response +/// latency. There is a big event range being advertised (32 bits), and +/// hub_test will send events with different IDs to be able to match request to +/// response. +/// +/// Here in the consumer the only thing we need to do is respond to identify +/// consumer messages with consumer identified. +/// +/// An additional feature is to be able to measure an arbitrary processing step +/// inside the node. For this purpose we have a hook function. When the +/// identify producer message comes in, we call the hook. When the measured +/// process completes, it should notify the given notifiable. Only thereafter +/// the consumer will reply on the bus. Requests' handling is not +/// parallelized. If the hook process cannot complete the requests fast enough, +/// the node will run out of memory and crash. class LatencyTestConsumer : public SimpleEventHandler { public: From 8d89bbe0a1b3516658d6c775a0ae4f481d7c784f Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 4 Feb 2024 19:06:54 -0800 Subject: [PATCH 11/13] Fix comment. --- src/utils/Stats.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/Stats.hxx b/src/utils/Stats.hxx index f5bd75d38..12a2ef314 100644 --- a/src/utils/Stats.hxx +++ b/src/utils/Stats.hxx @@ -94,7 +94,7 @@ public: return sqrt(qsum_ * count_ - sum * sum) / count_; } - /// Creates a half-a-line prinout of this stats object for debug purposes. + /// Creates a half-a-line printout of this stats object for debug purposes. std::string debug_string(); private: From 98b935d0528acfdf2853ed02574c6eeda29f4acd Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Sun, 4 Feb 2024 19:10:44 -0800 Subject: [PATCH 12/13] Fixes data type of max. --- src/utils/Stats.hxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/Stats.hxx b/src/utils/Stats.hxx index 12a2ef314..c3ad10058 100644 --- a/src/utils/Stats.hxx +++ b/src/utils/Stats.hxx @@ -101,7 +101,7 @@ private: /// Number of samples added. uint32_t count_; /// Maximum value found since the last clear. - int32_t max_; + ValueType max_; /// Sum of sample values added. int64_t sum_; /// Sum of squares of sample values added. From b9465163c3e8ceb46ed07bd4eaf39a47919f0ff9 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 5 Feb 2024 04:12:05 +0100 Subject: [PATCH 13/13] Latency test with maximum stats and custom process evaluation (#773) Updates the latency test of hub_test: - refactors the stats printing code into the Stats class - adds max statistic to the class - adds the latency consumer object that can be included in a product to verify the application level latency. === * Adds a debug print function to the stats object. This will be usable for printing a summary of the stats for a developer printout like a log statement. * Adds maximum to the statistics object. * Adds a consumer object for event based testing. * Adds hook to the latency test consumer. This allows testing latency of arbitrary internal node processing. * Adds documentation comment to latency test consumer. * Fix comment. * Fixes data type of max. --- .../hub_test/targets/linux.x86/main.cxx | 3 +- src/openlcb/LatencyTestConsumer.hxx | 133 ++++++++++++++++++ src/utils/Stats.cxx | 43 ++++++ src/utils/Stats.hxx | 15 +- src/utils/sources | 1 + 5 files changed, 192 insertions(+), 3 deletions(-) create mode 100644 src/openlcb/LatencyTestConsumer.hxx create mode 100644 src/utils/Stats.cxx diff --git a/applications/hub_test/targets/linux.x86/main.cxx b/applications/hub_test/targets/linux.x86/main.cxx index bd1dbc89f..18206ff6c 100644 --- a/applications/hub_test/targets/linux.x86/main.cxx +++ b/applications/hub_test/targets/linux.x86/main.cxx @@ -414,8 +414,7 @@ class Receiver : public CanHubPortInterface numFrames_ = numUnknownFrames_ = numInOrder_ = numOutOfOrder_ = numMissed_ = 0; - ret += StringPrintf("|RTT %.1f msec +- %.1f\n", - rttUsec_.favg()/1000, rttUsec_.stddev() / 1000); + ret += StringPrintf("|RTT %s\n", rttUsec_.debug_string().c_str()); rttUsec_.clear(); return ret; diff --git a/src/openlcb/LatencyTestConsumer.hxx b/src/openlcb/LatencyTestConsumer.hxx new file mode 100644 index 000000000..727decc46 --- /dev/null +++ b/src/openlcb/LatencyTestConsumer.hxx @@ -0,0 +1,133 @@ +/** \copyright + * Copyright (c) 2024, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file LatencyTestConsumer.hxx + * + * Utility class for determining the response latency of a node. + * + * @author Balazs Racz + * @date 2 Feb 2024 + */ + +#ifndef _OPENLCB_LATENCYTESTCONSUMER_HXX_ +#define _OPENLCB_LATENCYTESTCONSUMER_HXX_ + +#include "openlcb/EventHandlerTemplates.hxx" + +namespace openlcb +{ + +/// This event consumer works together with the hub_test application in order +/// to detect the response latency of a node. The theory of operation is that +/// hub_test sends out an identify consumer message with a given event ID, and +/// this consumer is going to respond. The hub_test then measures the response +/// latency. There is a big event range being advertised (32 bits), and +/// hub_test will send events with different IDs to be able to match request to +/// response. +/// +/// Here in the consumer the only thing we need to do is respond to identify +/// consumer messages with consumer identified. +/// +/// An additional feature is to be able to measure an arbitrary processing step +/// inside the node. For this purpose we have a hook function. When the +/// identify producer message comes in, we call the hook. When the measured +/// process completes, it should notify the given notifiable. Only thereafter +/// the consumer will reply on the bus. Requests' handling is not +/// parallelized. If the hook process cannot complete the requests fast enough, +/// the node will run out of memory and crash. +class LatencyTestConsumer : public SimpleEventHandler +{ +public: + /// To complete the hook, call the notifiable. + using HookFn = std::function; + + LatencyTestConsumer(Node *node, HookFn hook = nullptr) + : node_(node) + , hook_(hook) + { + EventRegistry::instance()->register_handler( + EventRegistryEntry(this, EVENT_BASE), 32); + } + + void handle_identify_global(const EventRegistryEntry ®istry_entry, + EventReport *event, BarrierNotifiable *done) override + { + AutoNotify an(done); + if (event->dst_node && event->dst_node != node_) + { + return; + } + event->event_write_helper<1>()->WriteAsync(node_, + Defs::MTI_CONSUMER_IDENTIFIED_RANGE, WriteHelper::global(), + eventid_to_buffer(EncodeRange(EVENT_BASE, 0xffffffff)), + done->new_child()); + } + + void handle_identify_consumer(const EventRegistryEntry &entry, + EventReport *event, BarrierNotifiable *done) override + { + event_ = event; + done_ = done; + if (hook_) + { + hook_(new TempNotifiable([this]() { reply(); })); + } + else + { + reply(); + } + } + +private: + void reply() + { + AutoNotify an(done_); + event_->event_write_helper<1>()->WriteAsync(node_, + Defs::MTI_CONSUMER_IDENTIFIED_UNKNOWN, WriteHelper::global(), + eventid_to_buffer(event_->event), done_->new_child()); + } + + /// This is within NMRA ID 1, which is not assigned. Lower four bytes are + /// the id. + static constexpr EventId EVENT_BASE = 0x0900013900000000; + + /// Which node should be sending responses. + Node *node_; + + /// The owner's hook will be invoked and run (asynchronous) before + /// replying.. + HookFn hook_; + + /// Incoming event report we are working on. + EventReport *event_; + + /// Will notify this after sending the reply. + BarrierNotifiable *done_ {nullptr}; +}; + +} // namespace openlcb + +#endif // _OPENLCB_LATENCYTESTCONSUMER_HXX_ diff --git a/src/utils/Stats.cxx b/src/utils/Stats.cxx new file mode 100644 index 000000000..b765c3129 --- /dev/null +++ b/src/utils/Stats.cxx @@ -0,0 +1,43 @@ +/** \copyright + * Copyright (c) 2023, Balazs Racz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * \file Stats.hxx + * + * Utility class for collecting statistics. + * + * @author Balazs Racz + * @date 28 Dec 2023 + */ + +#include "utils/Stats.hxx" + +#include "utils/StringPrintf.hxx" + +std::string Stats::debug_string() +{ + return StringPrintf("%.1f msec +- %.1f, max %.1f\n", favg() / 1000, + stddev() / 1000, ((FloatType)max_) / 1000); +} diff --git a/src/utils/Stats.hxx b/src/utils/Stats.hxx index 36f9fbb9c..c3ad10058 100644 --- a/src/utils/Stats.hxx +++ b/src/utils/Stats.hxx @@ -37,11 +37,14 @@ #include #include +#include +#include class Stats { public: using FloatType = double; + using ValueType = int32_t; Stats() { @@ -54,16 +57,21 @@ public: sum_ = 0; count_ = 0; qsum_ = 0; + max_ = std::numeric_limits::min(); } /// Appends a data point to the statistics. /// @param value the data point. - void add(int32_t value) + void add(ValueType value) { ++count_; sum_ += value; FloatType fval = value; qsum_ += fval * fval; + if (value > max_) + { + max_ = value; + } } /// @return the average @@ -86,9 +94,14 @@ public: return sqrt(qsum_ * count_ - sum * sum) / count_; } + /// Creates a half-a-line printout of this stats object for debug purposes. + std::string debug_string(); + private: /// Number of samples added. uint32_t count_; + /// Maximum value found since the last clear. + ValueType max_; /// Sum of sample values added. int64_t sum_; /// Sum of squares of sample values added. diff --git a/src/utils/sources b/src/utils/sources index e9fed0849..f24fdbf50 100644 --- a/src/utils/sources +++ b/src/utils/sources @@ -25,6 +25,7 @@ CXXSRCS += \ Queue.cxx \ ReflashBootloader.cxx \ ServiceLocator.cxx \ + Stats.cxx \ SocketCan.cxx \ SocketClient.cxx \ constants.cxx \