From 1c3b285ae1f445df2398b27198fcf18c61a9e1e7 Mon Sep 17 00:00:00 2001 From: xeonqq Date: Sat, 29 Jul 2023 06:34:29 +0200 Subject: [PATCH 01/28] Pin cli docker version to ubuntu:20.04 (#2656) --- Tools/Docker/cli/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/Docker/cli/Dockerfile b/Tools/Docker/cli/Dockerfile index 707e9ded60..eb86d53317 100644 --- a/Tools/Docker/cli/Dockerfile +++ b/Tools/Docker/cli/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu +FROM ubuntu:20.04 # ------------------------------------------------------------------------------ # Pre-requisites From 7b771436043f414aa0e00e9557195cc054eac17d Mon Sep 17 00:00:00 2001 From: Mike Date: Tue, 1 Aug 2023 11:38:15 +0100 Subject: [PATCH 02/28] Fix heading level for 'IDF Versions' in ESP32 README.rst (#2657) Indentation in online docs is confusing, ESP-IDF is only used for ESP32. --- Sming/Arch/Esp32/README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sming/Arch/Esp32/README.rst b/Sming/Arch/Esp32/README.rst index f40f35557f..aeaf87ac81 100644 --- a/Sming/Arch/Esp32/README.rst +++ b/Sming/Arch/Esp32/README.rst @@ -102,7 +102,7 @@ See :component-esp32:`esp32` for further details. IDF versions -============ +------------ Sming currently supports IDF versions 4.3, 4.4 and 5.0. From 9a21439996a98fc91de545741ad7ca61d4a42e62 Mon Sep 17 00:00:00 2001 From: slaff Date: Thu, 10 Aug 2023 11:08:14 +0200 Subject: [PATCH 03/28] Patch for TCP segment size calculation issue. (#2658) Original fix is here: lwip-tcpip/lwip@8e8571d Issue spotted and documented here: SmingHub/Sming#2654 PR submitted to upstream: https://github.com/pfalcon/esp-open-lwip/pull/10 --- .../Components/esp-open-lwip/esp-open-lwip.patch | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Sming/Arch/Esp8266/Components/esp-open-lwip/esp-open-lwip.patch b/Sming/Arch/Esp8266/Components/esp-open-lwip/esp-open-lwip.patch index 95efde870b..574453dd21 100644 --- a/Sming/Arch/Esp8266/Components/esp-open-lwip/esp-open-lwip.patch +++ b/Sming/Arch/Esp8266/Components/esp-open-lwip/esp-open-lwip.patch @@ -605,3 +605,16 @@ index bbb126a..4cd8840 100644 +void espconn_init(void) { } +diff --git a/lwip/core/tcp_out.c b/lwip/core/tcp_out.c +index e2f8e9a..8f4d170 100644 +--- a/lwip/core/tcp_out.c ++++ b/lwip/core/tcp_out.c +@@ -448,7 +448,7 @@ tcp_write(struct tcp_pcb *pcb, const void *arg, u16_t len, u8_t apiflags) + if (oversize > 0) { + LWIP_ASSERT("inconsistent oversize vs. space", oversize_used <= space); + seg = last_unsent; +- oversize_used = oversize < len ? oversize : len; ++ oversize_used = LWIP_MIN(space, LWIP_MIN(oversize, len)); + pos += oversize_used; + oversize -= oversize_used; + space -= oversize_used; From 6334d8c4826b5a199b41d8774b856b71149e01b8 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 8 Sep 2023 15:27:58 +0100 Subject: [PATCH 04/28] Switch katacoda links to killercoda (#2663) Co-authored-by: mikee47 --- README.md | 2 +- docs/source/arch/host/index.rst | 2 +- docs/source/getting-started/index.rst | 2 +- docs/source/index.rst | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 11d70852ce..00b95409a8 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ If you like **Sming**, give it a star, or fork it and [contribute](#contribute)! Sming supports multiple architectures and has a [plethora of features](https://sming.readthedocs.io/en/latest/index.html#summary). Choose the architecture of your choice to [install the needed development software](https://sming.readthedocs.io/en/latest/getting-started). -You can also try Sming without installing anything locally. We have an [interactive tutorial](https://www.katacoda.com/slaff/scenarios/sming-host-emulator) that can be run directly from your browser. +You can also try Sming without installing anything locally. We have an [interactive tutorial](https://killercoda.com/slaff/scenario/sming-host-emulator) that can be run directly from your browser. ## Documentation diff --git a/docs/source/arch/host/index.rst b/docs/source/arch/host/index.rst index 7ddf0d0e1d..80ec806850 100644 --- a/docs/source/arch/host/index.rst +++ b/docs/source/arch/host/index.rst @@ -8,7 +8,7 @@ the sample applications to be compiled on a Linux/Windows host system and be tested before uploading them to the microcontroller. If you want to try it we have an -`interactive tutorial `__ +`interactive tutorial `__ that can be run directly from your browser. See :doc:`/getting-started/index` for installation details. diff --git a/docs/source/getting-started/index.rst b/docs/source/getting-started/index.rst index 3f8e4e04d5..968ace25e4 100644 --- a/docs/source/getting-started/index.rst +++ b/docs/source/getting-started/index.rst @@ -18,7 +18,7 @@ Choose your preferred development environment for how to install the needed deve docker/index You can also try Sming without installing anything locally. -We have an `interactive tutorial `__ that can be run directly from your browser. +We have an `interactive tutorial `__ that can be run directly from your browser. .. toctree:: diff --git a/docs/source/index.rst b/docs/source/index.rst index 72100bea81..daee8b7980 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -20,7 +20,7 @@ Summary - Fast and user friendly development - Integrated :doc:`host emulator ` to assist with developing, testing and debugging libraries and applications on a PC before uploading them to an actual microcontroller. - Try it out online `here `__. + Try it out online `here `__. - Built-in powerful wireless modules - Compatible with standard :doc:`libraries` - use popular hardware in few lines of code - Simple yet powerful hardware API wrappers From f3b0a2be46ddb2d475a1595b21dcb3352475d1cb Mon Sep 17 00:00:00 2001 From: slaff Date: Mon, 18 Sep 2023 09:31:46 +0200 Subject: [PATCH 05/28] Help users that copy and paste instructions to install esp32 easier or switch between versions. (#2664) --- Sming/Arch/Esp32/README.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sming/Arch/Esp32/README.rst b/Sming/Arch/Esp32/README.rst index aeaf87ac81..bf575ce212 100644 --- a/Sming/Arch/Esp32/README.rst +++ b/Sming/Arch/Esp32/README.rst @@ -108,7 +108,7 @@ Sming currently supports IDF versions 4.3, 4.4 and 5.0. The default installed IDF version is 4.4. This can be changed as follows:: - INSTALL_IDF_VER=5.0 $SMING_HOME/../Tools/install.sh + INSTALL_IDF_VER=5.0 $SMING_HOME/../Tools/install.sh esp32 The installation script creates a soft-link in ``/opt/esp-idf`` pointing to the last version installed. Use the `IDF_PATH` environment variable or change the soft-link to select which one to use. @@ -120,8 +120,8 @@ After switching versions, run `make clean components-clean` before re-compiling. Currently, switching from version 4.x to 5.0 or vice-versa requires an additional step as they use different versions of the 'pyparsing' Python library. - If moving from IDF 4.x to 5.0: `python -m pip install --upgrade pyparsing` - Moving from IDF 5.0 to 4.x: `python -m pip install pyparsing\<2.4` + If moving from IDF 4.x to 5.0: ``python -m pip install --upgrade pyparsing`` + Moving from IDF 5.0 to 4.x: ``python -m pip install 'pyparsing<2.4'`` Components From ca94c515597a913b63266255a8550e27c861bbe3 Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 18 Sep 2023 16:40:37 +0100 Subject: [PATCH 06/28] Fix bug setting UART fifo receive level (#2666) 5 (0x101) is a reserved value in the datasheet; should be 4 (0x100). --- Sming/Arch/Rp2040/Components/driver/uart.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sming/Arch/Rp2040/Components/driver/uart.cpp b/Sming/Arch/Rp2040/Components/driver/uart.cpp index 06503f3624..61a14c220f 100644 --- a/Sming/Arch/Rp2040/Components/driver/uart.cpp +++ b/Sming/Arch/Rp2040/Components/driver/uart.cpp @@ -295,7 +295,7 @@ void smg_uart_start_isr(smg_uart_t* uart) if(smg_uart_rx_enabled(uart)) { // Trigger at >= 7/8 full - fifo_level_select |= 5 << UART_UARTIFLS_RXIFLSEL_LSB; + fifo_level_select |= 4 << UART_UARTIFLS_RXIFLSEL_LSB; /* * There is little benefit in generating interrupts on errors, instead these From 1d4f0cc479f1c1bfb3ec2ef312d789041ee90067 Mon Sep 17 00:00:00 2001 From: Mike Date: Tue, 19 Sep 2023 09:40:30 +0100 Subject: [PATCH 07/28] Update rp2040 pico-sdk to latest (1.5.1 with additional bugfixes) (#2667) --- .../Components/rp2040/cyw43-driver.patch | 44 +++++++++---------- Sming/Arch/Rp2040/Components/rp2040/pico-sdk | 2 +- .../Rp2040/Components/rp2040/pico-sdk.patch | 8 +--- 3 files changed, 24 insertions(+), 30 deletions(-) diff --git a/Sming/Arch/Rp2040/Components/rp2040/cyw43-driver.patch b/Sming/Arch/Rp2040/Components/rp2040/cyw43-driver.patch index 216cf19f0c..c7edc5b93e 100644 --- a/Sming/Arch/Rp2040/Components/rp2040/cyw43-driver.patch +++ b/Sming/Arch/Rp2040/Components/rp2040/cyw43-driver.patch @@ -1,8 +1,8 @@ diff --git a/src/cyw43_ctrl.c b/src/cyw43_ctrl.c -index c6c2df6..753bfa8 100644 +index edec1f3..de03c73 100644 --- a/src/cyw43_ctrl.c +++ b/src/cyw43_ctrl.c -@@ -296,13 +296,17 @@ STATIC const char *const cyw43_async_event_name_table[89] = { +@@ -296,13 +296,17 @@ static const char *const cyw43_async_event_name_table[89] = { [CYW43_EV_SET_SSID] = "SET_SSID", [CYW43_EV_JOIN] = "JOIN", [CYW43_EV_AUTH] = "AUTH", @@ -21,11 +21,11 @@ index c6c2df6..753bfa8 100644 [CYW43_EV_ASSOC_REQ_IE] = "ASSOC_REQ_IE", [CYW43_EV_ASSOC_RESP_IE] = "ASSOC_RESP_IE", diff --git a/src/cyw43_ll.c b/src/cyw43_ll.c -index 7f4229b..f3bc641 100644 +index 604335c..4aeb629 100644 --- a/src/cyw43_ll.c +++ b/src/cyw43_ll.c -@@ -55,9 +55,6 @@ int sdio_transfer(uint32_t cmd, uint32_t arg, uint32_t *resp); - void sdio_enable_high_speed_4bit(void); +@@ -54,9 +54,6 @@ + #include "cyw43_sdio.h" #endif -#define CYW43_FLASH_BLOCK_SIZE (512) @@ -34,7 +34,7 @@ index 7f4229b..f3bc641 100644 struct pbuf; uint16_t pbuf_copy_partial(const struct pbuf *p, void *dataptr, uint16_t len, uint16_t offset); -@@ -69,10 +66,6 @@ extern bool enable_spi_packet_dumping; +@@ -68,10 +65,6 @@ extern bool enable_spi_packet_dumping; #define CYW43_RAM_SIZE (512 * 1024) @@ -45,9 +45,9 @@ index 7f4229b..f3bc641 100644 #define VERIFY_FIRMWARE_DOWNLOAD (0) #define ALIGN_UINT(val, align) (((val) + (align) - 1) & ~((align) - 1)) -@@ -377,58 +370,31 @@ static int cyw43_read_backplane_mem(cyw43_int_t *self, uint32_t addr, uint32_t l +@@ -357,58 +350,31 @@ static void cyw43_write_backplane(cyw43_int_t *self, uint32_t addr, size_t size, + cyw43_set_backplane_window(self, CHIPCOMMON_BASE_ADDRESS); } - #endif -static int cyw43_download_resource(cyw43_int_t *self, uint32_t addr, size_t raw_len, int from_storage, uintptr_t source) { - // round up len to simplify download @@ -59,7 +59,7 @@ index 7f4229b..f3bc641 100644 - if (from_storage) { - // reused the spid_buf to copy the data (must be larger than 512 storage block size) - block_size = sizeof(self->spid_buf); -- CYW43_DEBUG("data comes from external storage via buffer of size %u\n", (uint)block_size); +- CYW43_DEBUG("data comes from external storage via buffer of size %u\n", (unsigned int)block_size); +static uint32_t storage_get_chunksize() +{ + const uint32_t chunkTag = 0x4b4e4843; // "CHNK" @@ -126,7 +126,7 @@ index 7f4229b..f3bc641 100644 #if VERIFY_FIRMWARE_DOWNLOAD uint32_t t_start = cyw43_hal_ticks_us(); -@@ -446,7 +412,7 @@ static int cyw43_download_resource(cyw43_int_t *self, uint32_t addr, size_t raw_ +@@ -426,7 +392,7 @@ static int cyw43_download_resource(cyw43_int_t *self, uint32_t addr, size_t raw_ cyw43_set_backplane_window(self, dest_addr); const uint8_t *src; if (from_storage) { @@ -135,7 +135,7 @@ index 7f4229b..f3bc641 100644 src = self->spid_buf; } else { src = (const uint8_t *)source + offset; -@@ -459,6 +425,10 @@ static int cyw43_download_resource(cyw43_int_t *self, uint32_t addr, size_t raw_ +@@ -443,6 +409,10 @@ static int cyw43_download_resource(cyw43_int_t *self, uint32_t addr, size_t raw_ } #if VERIFY_FIRMWARE_DOWNLOAD @@ -145,8 +145,8 @@ index 7f4229b..f3bc641 100644 + } uint32_t t_end = cyw43_hal_ticks_us(); uint32_t dt = t_end - t_start; - CYW43_VDEBUG("done dnload; dt = %u us; speed = %u kbytes/sec\n", (uint)dt, (uint)(len * 1000 / dt)); -@@ -480,7 +450,7 @@ static int cyw43_download_resource(cyw43_int_t *self, uint32_t addr, size_t raw_ + CYW43_VDEBUG("done dnload; dt = %u us; speed = %u kbytes/sec\n", (unsigned int)dt, (unsigned int)(len * 1000 / dt)); +@@ -464,7 +434,7 @@ static int cyw43_download_resource(cyw43_int_t *self, uint32_t addr, size_t raw_ cyw43_read_bytes(self, BACKPLANE_FUNCTION, dest_addr & BACKPLANE_ADDR_MASK, sz, buf); const uint8_t *src; if (from_storage) { @@ -155,7 +155,7 @@ index 7f4229b..f3bc641 100644 src = self->spid_buf; } else { src = (const uint8_t *)source + offset; -@@ -1383,8 +1353,8 @@ void cyw43_ll_bus_sleep(cyw43_ll_t *self_in, bool can_sleep) { +@@ -1372,8 +1342,8 @@ void cyw43_ll_bus_sleep(cyw43_ll_t *self_in, bool can_sleep) { #define CLM_CHUNK_LEN 1024 + 512 #endif @@ -166,7 +166,7 @@ index 7f4229b..f3bc641 100644 uint8_t *buf = &self->spid_buf[SDPCM_HEADER_LEN + 16]; const size_t clm_dload_chunk_len = CLM_CHUNK_LEN; -@@ -1409,7 +1379,7 @@ static void cyw43_clm_load(cyw43_int_t *self, const uint8_t *clm_ptr, size_t clm +@@ -1398,7 +1368,7 @@ static void cyw43_clm_load(cyw43_int_t *self, const uint8_t *clm_ptr, size_t clm *(uint32_t *)(buf + 12) = len; *(uint32_t *)(buf + 16) = 0; #pragma GCC diagnostic pop @@ -175,7 +175,7 @@ index 7f4229b..f3bc641 100644 CYW43_VDEBUG("clm data send %lu/%zu\n", off + len, clm_len); -@@ -1665,12 +1635,9 @@ alp_set: +@@ -1654,12 +1624,9 @@ alp_set: cyw43_write_backplane(self, SOCSRAM_BANKX_INDEX, 4, 0x3); cyw43_write_backplane(self, SOCSRAM_BANKX_PDA, 4, 0); @@ -190,7 +190,7 @@ index 7f4229b..f3bc641 100644 size_t wifi_nvram_len = ALIGN_UINT(sizeof(wifi_nvram_4343), 64); const uint8_t *wifi_nvram_data = wifi_nvram_4343; -@@ -1787,9 +1754,11 @@ f2_ready: +@@ -1776,9 +1743,11 @@ f2_ready: // Load the CLM data; it sits just after main firmware CYW43_VDEBUG("cyw43_clm_load start\n"); @@ -203,7 +203,7 @@ index 7f4229b..f3bc641 100644 cyw43_write_iovar_u32(self, "bus:txglom", 0, WWD_STA_INTERFACE); // tx glomming off cyw43_write_iovar_u32(self, "apsta", 1, WWD_STA_INTERFACE); // apsta on -@@ -1893,6 +1862,10 @@ int cyw43_ll_wifi_on(cyw43_ll_t *self_in, uint32_t country) { +@@ -1882,6 +1851,10 @@ int cyw43_ll_wifi_on(cyw43_ll_t *self_in, uint32_t country) { cyw43_delay_ms(50); #ifndef NDEBUG @@ -214,7 +214,7 @@ index 7f4229b..f3bc641 100644 // Get and print CLM version memcpy(buf, "clmver\x00", 7); cyw43_do_ioctl(self, SDPCM_GET, WLC_GET_VAR, 128, buf, WWD_STA_INTERFACE); -@@ -1922,8 +1895,8 @@ int cyw43_ll_wifi_on(cyw43_ll_t *self_in, uint32_t country) { +@@ -1911,8 +1884,8 @@ int cyw43_ll_wifi_on(cyw43_ll_t *self_in, uint32_t country) { CLR_EV(buf, 19); // roam attempt occurred CLR_EV(buf, 20); // tx fail CLR_EV(buf, 40); // radio @@ -225,10 +225,10 @@ index 7f4229b..f3bc641 100644 #undef CLR_EV memcpy(buf, "bsscfg:event_msgs", 18); diff --git a/src/cyw43_ll.h b/src/cyw43_ll.h -index cf442db..a7ad227 100644 +index 2750238..c281093 100644 --- a/src/cyw43_ll.h +++ b/src/cyw43_ll.h -@@ -65,15 +65,19 @@ +@@ -67,15 +67,19 @@ #define CYW43_EV_SET_SSID (0) #define CYW43_EV_JOIN (1) #define CYW43_EV_AUTH (3) @@ -248,7 +248,7 @@ index cf442db..a7ad227 100644 #define CYW43_EV_CSA_COMPLETE_IND (80) #define CYW43_EV_ASSOC_REQ_IE (87) #define CYW43_EV_ASSOC_RESP_IE (88) -@@ -312,6 +316,11 @@ uint32_t cyw43_ll_read_backplane_reg(cyw43_ll_t *self_in, uint32_t addr); +@@ -318,6 +322,11 @@ uint32_t cyw43_ll_read_backplane_reg(cyw43_ll_t *self_in, uint32_t addr); int cyw43_ll_write_backplane_mem(cyw43_ll_t *self_in, uint32_t addr, uint32_t len, const uint8_t *buf); int cyw43_ll_read_backplane_mem(cyw43_ll_t *self_in, uint32_t addr, uint32_t len, uint8_t *buf); diff --git a/Sming/Arch/Rp2040/Components/rp2040/pico-sdk b/Sming/Arch/Rp2040/Components/rp2040/pico-sdk index e87f11bd2b..263a6680aa 160000 --- a/Sming/Arch/Rp2040/Components/rp2040/pico-sdk +++ b/Sming/Arch/Rp2040/Components/rp2040/pico-sdk @@ -1 +1 @@ -Subproject commit e87f11bd2b54654a251d088193fe3b3c5247baa1 +Subproject commit 263a6680aaf590b3c48f55645f30d1b96d168832 diff --git a/Sming/Arch/Rp2040/Components/rp2040/pico-sdk.patch b/Sming/Arch/Rp2040/Components/rp2040/pico-sdk.patch index a707d793c6..f559e5fb88 100644 --- a/Sming/Arch/Rp2040/Components/rp2040/pico-sdk.patch +++ b/Sming/Arch/Rp2040/Components/rp2040/pico-sdk.patch @@ -1,9 +1,3 @@ -diff --git a/lib/cyw43-driver b/lib/cyw43-driver ---- a/lib/cyw43-driver -+++ b/lib/cyw43-driver -@@ -1 +1 @@ --Subproject commit 9bfca61173a94432839cd39210f1d1afdf602c42 -+Subproject commit 9bfca61173a94432839cd39210f1d1afdf602c42-dirty diff --git a/src/common/pico_util/queue.c b/src/common/pico_util/queue.c index a5c8e18..c3b8a91 100644 --- a/src/common/pico_util/queue.c @@ -50,7 +44,7 @@ index 8e92d8b..da5feac 100644 /*! \brief Atomically set the specified bits to 1 in a HW register * \ingroup hardware_base diff --git a/src/rp2_common/pico_standard_link/memmap_default.ld b/src/rp2_common/pico_standard_link/memmap_default.ld -index 638e994..3fb53cd 100644 +index e85b327..cf826c6 100644 --- a/src/rp2_common/pico_standard_link/memmap_default.ld +++ b/src/rp2_common/pico_standard_link/memmap_default.ld @@ -231,7 +231,7 @@ SECTIONS From 69685b1a44b1a12d3f1d5bfe2907c9b244b6fd76 Mon Sep 17 00:00:00 2001 From: Mike Date: Mon, 25 Sep 2023 09:14:05 +0100 Subject: [PATCH 08/28] Add CStringArray::join() method (#2668) --- Sming/Core/Data/CStringArray.cpp | 22 ++++++++++++++++++++++ Sming/Core/Data/CStringArray.h | 9 +++++++++ tests/HostTests/modules/CStringArray.cpp | 19 +++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/Sming/Core/Data/CStringArray.cpp b/Sming/Core/Data/CStringArray.cpp index 8321fbeb01..cfc3630ee8 100644 --- a/Sming/Core/Data/CStringArray.cpp +++ b/Sming/Core/Data/CStringArray.cpp @@ -168,3 +168,25 @@ unsigned CStringArray::count() const } return stringCount; } + +String CStringArray::join(const String& separator) const +{ + if(isNull()) { + return nullptr; + } + unsigned len = length(); + if(len == 0) { + return ""; + } + len -= count(); // NUL separators + len += separator.length() * (stringCount - 1); + String s; + s.reserve(len); + for(auto it = begin(); it != end(); ++it) { + if(it.index() != 0) { + s += separator; + } + s += *it; + } + return s; +} diff --git a/Sming/Core/Data/CStringArray.h b/Sming/Core/Data/CStringArray.h index 5bcf331862..c4b8c74ad0 100644 --- a/Sming/Core/Data/CStringArray.h +++ b/Sming/Core/Data/CStringArray.h @@ -246,6 +246,15 @@ class CStringArray : private String */ unsigned count() const; + /** + * @brief Get contents of array as delimited string + * @param separator What to join elements with + * @retval String + * + * e.g. CStringArray(F("a\0b\0c")).join() returns "a,b,c" + */ + String join(const String& separator = ",") const; + /** * @name Iterator support (forward only) * @{ diff --git a/tests/HostTests/modules/CStringArray.cpp b/tests/HostTests/modules/CStringArray.cpp index efb7b5e3e1..61c8394099 100644 --- a/tests/HostTests/modules/CStringArray.cpp +++ b/tests/HostTests/modules/CStringArray.cpp @@ -213,6 +213,25 @@ class CStringArrayTest : public TestGroup REQUIRE(csa.back() == nullptr); REQUIRE(!csa.popBack()); } + + TEST_CASE("join") + { + CStringArray csa; + REQUIRE(!csa.join()); + + csa = ""; + REQUIRE(csa.join() == ""); + + csa.add("a"); + REQUIRE(csa.join() == "a"); + + csa.add(""); + REQUIRE(csa.join() == "a,"); + + csa.add(F("test\0again")); + REQUIRE_EQ(csa.join("}+{"), "a}+{}+{test}+{again"); + REQUIRE_EQ(csa.join(nullptr), "atestagain"); + } } }; From 12cdc8bd4d6428c60c9b33257120babe2fb3f6db Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 11 Oct 2023 15:43:32 +0100 Subject: [PATCH 09/28] Fix failing Windows host build, ninja no longer available (it seems) from Windows 2022 server image 20230918.1. (#2670) --- Tools/ci/install.cmd | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Tools/ci/install.cmd b/Tools/ci/install.cmd index 90a5e342a5..0328612268 100644 --- a/Tools/ci/install.cmd +++ b/Tools/ci/install.cmd @@ -8,4 +8,6 @@ if "%BUILD_DOCS%" == "true" ( set INSTALL_OPTS=doc ) +choco install ninja + %SMING_HOME%\..\Tools\install.cmd %SMING_ARCH% %INSTALL_OPTS% From b153bc81f0f206fdf99afad3fd8fbd37f7ca15f6 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 12 Oct 2023 08:50:50 +0100 Subject: [PATCH 10/28] Bugfix: Rp2040 `os_timer_setfn()` can stall other timers. (#2672) If called on first timer in queue then subsequent timers get disconnected. Must explicitly `disarm` the timer first so it's properly removed from queue. This is consistent with esp8266 behaviour. Also applied to host timer from whence the code originated. This bug might explain root cause of #2594, where code hangs during intensive timer usage. Specifically, this issue gets triggered if attempting to change the callback on an active timer. (If the timer is inactive then it's not in the queue so doesn't matter.) --- Sming/Arch/Host/Components/driver/os_timer.cpp | 9 +++++---- Sming/Arch/Rp2040/Components/driver/os_timer.cpp | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Sming/Arch/Host/Components/driver/os_timer.cpp b/Sming/Arch/Host/Components/driver/os_timer.cpp index ec80e5237d..e546c35256 100644 --- a/Sming/Arch/Host/Components/driver/os_timer.cpp +++ b/Sming/Arch/Host/Components/driver/os_timer.cpp @@ -93,11 +93,12 @@ void os_timer_disarm(struct os_timer_t* ptimer) void os_timer_setfn(struct os_timer_t* ptimer, os_timer_func_t* pfunction, void* parg) { - if(ptimer != nullptr) { - ptimer->timer_func = pfunction; - ptimer->timer_arg = parg; - ptimer->timer_next = reinterpret_cast(-1); + if(ptimer == nullptr) { + return; } + os_timer_disarm(ptimer); + ptimer->timer_func = pfunction; + ptimer->timer_arg = parg; } void os_timer_done(struct os_timer_t* ptimer) diff --git a/Sming/Arch/Rp2040/Components/driver/os_timer.cpp b/Sming/Arch/Rp2040/Components/driver/os_timer.cpp index ee8312357a..b524d56ceb 100644 --- a/Sming/Arch/Rp2040/Components/driver/os_timer.cpp +++ b/Sming/Arch/Rp2040/Components/driver/os_timer.cpp @@ -173,11 +173,12 @@ void IRAM_ATTR os_timer_disarm(struct os_timer_t* ptimer) void os_timer_setfn(struct os_timer_t* ptimer, os_timer_func_t* pfunction, void* parg) { - if(ptimer != nullptr) { - ptimer->timer_func = pfunction; - ptimer->timer_arg = parg; - ptimer->timer_next = reinterpret_cast(-1); + if(ptimer == nullptr) { + return; } + os_timer_disarm(ptimer); + ptimer->timer_func = pfunction; + ptimer->timer_arg = parg; } void os_timer_done(struct os_timer_t* ptimer) From 6591e72b658c7d0d2218739b6dacb90d5495d03d Mon Sep 17 00:00:00 2001 From: slaff Date: Thu, 12 Oct 2023 09:52:12 +0200 Subject: [PATCH 11/28] Fixed small typos. (#2674) --- Sming/Arch/Rp2040/README.rst | 2 +- Sming/Kconfig | 2 +- tests/HostTests/modules/ArduinoString.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sming/Arch/Rp2040/README.rst b/Sming/Arch/Rp2040/README.rst index 473fb85079..e5eab5f052 100644 --- a/Sming/Arch/Rp2040/README.rst +++ b/Sming/Arch/Rp2040/README.rst @@ -184,7 +184,7 @@ If core 1 code attempts to access flash during these periods the system will har Floating-point support requires use of routines in flash memory. Integer operations should all be safe to use. - If unexplained crashes are occuring then check the build output files (in out/Rp2040/debug/build) + If unexplained crashes are occurring then check the build output files (in out/Rp2040/debug/build) or use a debugger to identify any errant code running from flash. A typical use for core #1 might be to perform processing of some kind, such as processing data sampled diff --git a/Sming/Kconfig b/Sming/Kconfig index d0fffe8f95..9a4bfd12cd 100644 --- a/Sming/Kconfig +++ b/Sming/Kconfig @@ -47,7 +47,7 @@ mainmenu "${SMING_SOC} Sming Framework Configuration" bool "Enable command executor functionality" default y help - This facilty requires documenting! + This facility requires documenting! config TASK_QUEUE_LENGTH int "Length of task queue" diff --git a/tests/HostTests/modules/ArduinoString.cpp b/tests/HostTests/modules/ArduinoString.cpp index 48b11920cd..b38279c943 100644 --- a/tests/HostTests/modules/ArduinoString.cpp +++ b/tests/HostTests/modules/ArduinoString.cpp @@ -99,7 +99,7 @@ class ArduinoStringTest : public TestGroup REQUIRE(hello2 == "hello"); } - TEST_CASE("String concantenation", "[core][String]") + TEST_CASE("String concatenation", "[core][String]") { String str; REQUIRE(str.length() == 0); From ee881ef7197eef376208a10fbca160c7fd2efd23 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 12 Oct 2023 08:54:14 +0100 Subject: [PATCH 12/28] Fix IFS compile warnings/errors (#2673) This PR improves the IFS `ObjectBuffer` implementation to fix 'hidden virtual method' warnings produced by more recent compilers. The class is just a helper/wrapper with a set of custom 'write' methods, so shouldn't have used inheritance in the first place. Also fixes an 'uninitialised variable' warning in the `Spiffs` IFS library, identified by running IFS integration tests through valgrind. --- Sming/Components/IFS | 2 +- Sming/Libraries/Spiffs/src/include/IFS/SPIFFS/FileSystem.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sming/Components/IFS b/Sming/Components/IFS index b6fdbabb50..36637f4e20 160000 --- a/Sming/Components/IFS +++ b/Sming/Components/IFS @@ -1 +1 @@ -Subproject commit b6fdbabb506af5722da9395d65b70c6a7f31d6c9 +Subproject commit 36637f4e207e14d2484b9b827c573f1ba54392cd diff --git a/Sming/Libraries/Spiffs/src/include/IFS/SPIFFS/FileSystem.h b/Sming/Libraries/Spiffs/src/include/IFS/SPIFFS/FileSystem.h index 9ebbd156bf..18e1685faa 100644 --- a/Sming/Libraries/Spiffs/src/include/IFS/SPIFFS/FileSystem.h +++ b/Sming/Libraries/Spiffs/src/include/IFS/SPIFFS/FileSystem.h @@ -137,7 +137,7 @@ class FileSystem : public IFileSystem Storage::Partition partition; IProfiler* profiler{nullptr}; SpiffsMetaBuffer metaCache[SPIFF_FILEDESC_COUNT]; - spiffs fs; + spiffs fs{}; uint8_t workBuffer[LOG_PAGE_SIZE * 2]; spiffs_fd fileDescriptors[SPIFF_FILEDESC_COUNT]; uint8_t cache[CACHE_SIZE]; From a0585b2c7d307ede461cac6be69e5d4fd2a65164 Mon Sep 17 00:00:00 2001 From: Mike Date: Thu, 12 Oct 2023 15:01:41 +0100 Subject: [PATCH 13/28] Provide workaround for 'dangling pointer' error/warning (#2671) * Provide workaround for 'dangling pointer' error/warning * Fix failing FlashString test Sorts warnings for host builds as discussed in #2659. --- Sming/Components/FlashString | 2 +- Sming/Wiring/FakePgmSpace.cpp | 7 +++++++ Sming/Wiring/FakePgmSpace.h | 9 ++++++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Sming/Components/FlashString b/Sming/Components/FlashString index 1025908632..d9a0a50e4d 160000 --- a/Sming/Components/FlashString +++ b/Sming/Components/FlashString @@ -1 +1 @@ -Subproject commit 10259086321ebeceb6b7106b4697c9caef9956e6 +Subproject commit d9a0a50e4d91f0d7ba68166cae6e0d52ac474453 diff --git a/Sming/Wiring/FakePgmSpace.cpp b/Sming/Wiring/FakePgmSpace.cpp index 156a31df6c..847c227528 100644 --- a/Sming/Wiring/FakePgmSpace.cpp +++ b/Sming/Wiring/FakePgmSpace.cpp @@ -38,3 +38,10 @@ int memcmp_aligned(const void* ptr1, const void* ptr2, unsigned len) auto tail2 = pgm_read_dword(reinterpret_cast(ptr2) + len_aligned); return memcmp(&tail1, &tail2, len - len_aligned); } + +#ifdef ARCH_HOST +char* smg_return_local(char* buf) +{ + return buf; +} +#endif diff --git a/Sming/Wiring/FakePgmSpace.h b/Sming/Wiring/FakePgmSpace.h index 62d02ad7b7..db5fab597a 100644 --- a/Sming/Wiring/FakePgmSpace.h +++ b/Sming/Wiring/FakePgmSpace.h @@ -75,6 +75,13 @@ extern "C" { &__pstr__[0]; \ })) +#ifdef ARCH_HOST +// Internal function to prevent 'dangling pointer' compiler warning +extern char* smg_return_local(char* buf); +#else +#define smg_return_local(buf) (buf) +#endif + /** * @brief Declare and use a flash string inline. * @param str @@ -84,7 +91,7 @@ extern "C" { (__extension__({ \ DEFINE_PSTR_LOCAL(__pstr__, str); \ LOAD_PSTR(buf, __pstr__); \ - buf; \ + smg_return_local(buf); \ })) /** From c3834dc27ab5b0c6649052f1281501e7eb27c5d2 Mon Sep 17 00:00:00 2001 From: Mike Date: Fri, 13 Oct 2023 08:49:07 +0100 Subject: [PATCH 14/28] Second (and final) attempt at fixing this issue. (#2675) Whilst using a function to 'hide' the stack return appears to work, integration tests fail in a couple of scenarios (FlashString, RapidXML) because of the way stack space gets used. For Host builds, the most pragmatic solution is to make `_F(s)` return `s` directly. That means it gives a `const char*` rather than `char*`, and any attempt to pass this to a function requiring a regular `char*` will fail. --- Sming/Wiring/FakePgmSpace.cpp | 7 ------- Sming/Wiring/FakePgmSpace.h | 10 ++++------ 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/Sming/Wiring/FakePgmSpace.cpp b/Sming/Wiring/FakePgmSpace.cpp index 847c227528..156a31df6c 100644 --- a/Sming/Wiring/FakePgmSpace.cpp +++ b/Sming/Wiring/FakePgmSpace.cpp @@ -38,10 +38,3 @@ int memcmp_aligned(const void* ptr1, const void* ptr2, unsigned len) auto tail2 = pgm_read_dword(reinterpret_cast(ptr2) + len_aligned); return memcmp(&tail1, &tail2, len - len_aligned); } - -#ifdef ARCH_HOST -char* smg_return_local(char* buf) -{ - return buf; -} -#endif diff --git a/Sming/Wiring/FakePgmSpace.h b/Sming/Wiring/FakePgmSpace.h index db5fab597a..3c34e8f43a 100644 --- a/Sming/Wiring/FakePgmSpace.h +++ b/Sming/Wiring/FakePgmSpace.h @@ -76,12 +76,8 @@ extern "C" { })) #ifdef ARCH_HOST -// Internal function to prevent 'dangling pointer' compiler warning -extern char* smg_return_local(char* buf); +#define _F(str) (str) #else -#define smg_return_local(buf) (buf) -#endif - /** * @brief Declare and use a flash string inline. * @param str @@ -91,9 +87,11 @@ extern char* smg_return_local(char* buf); (__extension__({ \ DEFINE_PSTR_LOCAL(__pstr__, str); \ LOAD_PSTR(buf, __pstr__); \ - smg_return_local(buf); \ + buf; \ })) +#endif + /** * @brief copy memory aligned to word boundaries * @param dst From d85d10ccdbe3ad23c723be55885ac220d1f6af7f Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 21 Oct 2023 12:27:29 +0100 Subject: [PATCH 15/28] Fix CallbackTimer::start not in IRAM (#2676) Known issue with templated code where compiler silently ignores section attribute. https://sming.readthedocs.io/en/latest/framework/core/pgmspace.html#templated-code Co-authored-by: mikee47 --- Sming/Core/CallbackTimer.h | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/Sming/Core/CallbackTimer.h b/Sming/Core/CallbackTimer.h index e8ef1989ac..9844c6ed86 100644 --- a/Sming/Core/CallbackTimer.h +++ b/Sming/Core/CallbackTimer.h @@ -207,7 +207,18 @@ template class CallbackTimer : protected TimerApi * @param repeating True to restart timer when it triggers, false for one-shot (Default: true) * @retval bool True if timer started */ - IRAM_ATTR bool start(bool repeating = true); + __forceinline bool IRAM_ATTR start(bool repeating = true) + { + stop(); + if(!callbackSet || !intervalSet) { + return false; + } + + TimerApi::arm(repeating); + started = true; + this->repeating = repeating; + return true; + } /** @brief Start one-shot timer * @retval bool True if timer started @@ -410,17 +421,4 @@ template class CallbackTimer : protected TimerApi bool started = false; ///< Timer is active, or has fired }; -template bool CallbackTimer::start(bool repeating) -{ - stop(); - if(!callbackSet || !intervalSet) { - return false; - } - - TimerApi::arm(repeating); - started = true; - this->repeating = repeating; - return true; -} - /** @} */ From 68a6e24ee87ae36edd564e159a73485c0ac71e4c Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 21 Oct 2023 12:28:14 +0100 Subject: [PATCH 16/28] Add MemoryDataStream::reset() method (#2677) The class already has a `clear()` method but this leaves existing memory allocated. The purpose is to allow re-use of an existing class instance efficiently by reducing memory re-allocations. The new `reset()` method allows the instance to be cleared and also release allocated memory. The new name is consistent with `std::unique_ptr::reset()` usage. A similar thing can be accomplished by using `std::unique_ptr` however an in-built reset() method makes for cleaner code. --- Sming/Core/Data/Stream/MemoryDataStream.h | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Sming/Core/Data/Stream/MemoryDataStream.h b/Sming/Core/Data/Stream/MemoryDataStream.h index cef511a62b..eeb9b67139 100644 --- a/Sming/Core/Data/Stream/MemoryDataStream.h +++ b/Sming/Core/Data/Stream/MemoryDataStream.h @@ -104,6 +104,17 @@ class MemoryDataStream : public ReadWriteStream readPos = 0; } + /** + * @brief Clear stream and release allocated memory + */ + void reset() + { + clear(); + free(buffer); + buffer = nullptr; + capacity = 0; + } + size_t getSize() const { return size; From eaaf2a8ad74e055ce7b5c92290b219beb6f76962 Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 21 Oct 2023 12:28:41 +0100 Subject: [PATCH 17/28] Add TRange contains(TRange&) overload (#2678) Provides an easy way to check if one range is a subset of another. --- Sming/Core/Data/Range.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Sming/Core/Data/Range.h b/Sming/Core/Data/Range.h index 92168a7c5d..c0f88c68da 100644 --- a/Sming/Core/Data/Range.h +++ b/Sming/Core/Data/Range.h @@ -87,6 +87,14 @@ template struct TRange { return (value >= min) && (value <= max); } + /** + * @brief Determine if range contains another range (subset) + */ + template bool contains(const TRange& value) const + { + return contains(value.min) && contains(value.max); + } + /** * @brief Clip values to within the range */ From ba3626c8597019bd4059fdf11f252b65bf754e46 Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 21 Oct 2023 12:29:22 +0100 Subject: [PATCH 18/28] Update Graphics library (#2679) - Add Touch interface for virtual display - Fix polyline rendering when contained in child scene - Allow SceneRenderer to be created before scene is populated - Fix resource building/cleaning - Fix CI library build (requires alias) - Revise touch calibrator, add Host, Rp2040, Esp32 support - Add basic control support - Fix rectangle rendering location - Allow blenders to be called statically, add `BlendMask` - Reduce default TFT SPI speed to improve read reliability - Add resource image `color` transform - Support FilledRectObject with blender - Blenders do not require transparent color - Fix compiler warnings about hidden methods --- Sming/Libraries/Graphics | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sming/Libraries/Graphics b/Sming/Libraries/Graphics index 15696087bc..45ffbabc5e 160000 --- a/Sming/Libraries/Graphics +++ b/Sming/Libraries/Graphics @@ -1 +1 @@ -Subproject commit 15696087bc9a1cbc8e3ba3e5f36cb18d096d0f48 +Subproject commit 45ffbabc5e1ace51979ee84cf75d5a0ea635d296 From 92b9737b38e182e8002a7001f68aa509de72b79f Mon Sep 17 00:00:00 2001 From: Mike Date: Sat, 21 Oct 2023 12:30:15 +0100 Subject: [PATCH 19/28] Significant IO Control library updates (#2680) ## Fixes Fix request ID defaulting to "null" string bugfix: Device state handling - Device::stop() doesn't update state - Clear fault state on successful request Fix `toString(Request)` not available compiler error Fix issue with TX DONE interrupt on RP2040 - Fires when FIFO drops below 25% so needs more padding. Fix Modbus GenericRequest 'bad_node' error Bugfix: Cannot flush uart from ISR (RS485) Fix possible double-call during modbus execute event Bugfix: activeConfig not set in Serial Fix uninitialised default values Fix RFSwitch output initialisation and idling ## Improvements Use nested namespaces (C++17) `Device::nodeIdMax()` is calculated, doesn't need to be virtual Tidy up Device factory inheritance Add devmgr `findDevice` method template to handle up-casting Add device parameter to support minimum interval between transactions Put `toString` function overloads in global namespace Omit `command` from JSON responses if undefined Improve DMX512 - Fix segment not being set - Add device `fade` option - Implement `query` command - Tidy up `Request::nodeSet()` and `nodeAdjust()`, virtualise `setValue()` Improve RS485 - Add gpio numbers to `Serial::open()` Revise modbus PDU structure - Structure for input/holding registers is identical - Add methods for accessing coil bits RS485 devices can override default transaction timeout Add modbus `GenericRequest` support, plus transfer callback hook Allow RS485 per-request Slave Address override (e.g. for broadcast requests) Reduce RS485 debug verbosity Complete RS485 receive using timer to avoid premature request completion. This enables support for general RS232 communication where responses may be a bit bursty. Add Custom class. May serve as a useful starting point for new devices. ## New devices Add RI-D35 modbus energy meter Add STM8Relay modbus relay board support. - New 4-channel modbus board uses coil commands (0-based) and has 4 digital inputs - Use `update` command to change stm8relay slave address Add NT18B07 NTC temperature sensor board Add STS fan controller sample project This is a real application used to control three PWM fans for cooling a hybrid solar inverter. Uses RPi Pico as hardware can generate PWM and measure speed. --- Sming/Libraries/IOControl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sming/Libraries/IOControl b/Sming/Libraries/IOControl index d6a6ba1999..f860a2ae08 160000 --- a/Sming/Libraries/IOControl +++ b/Sming/Libraries/IOControl @@ -1 +1 @@ -Subproject commit d6a6ba1999542c4eba74e0aca24eda26901ee4d8 +Subproject commit f860a2ae08d70819f4d4487f9ed559dc0164615c From 68822af54cbbede7fbdb2dfc27206fac8b941d69 Mon Sep 17 00:00:00 2001 From: slaff Date: Mon, 23 Oct 2023 10:14:19 +0200 Subject: [PATCH 20/28] Fix failing rtd document generation and smuggle some typo corrections. (#2681) * Fix failing rtd document generation and smuggle some typo corrections. * Applying other changes mentioned in https://blog.readthedocs.com/use-build-os-config/. --- .readthedocs.yml | 7 ++++++- docs/source/information/command-handler.rst | 2 +- docs/source/information/events.rst | 2 +- samples/SDCard/example.output.txt | 4 ++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index d4b41b32a7..fa3b0a80e9 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -5,6 +5,12 @@ # Required version: 2 +# Required +build: + os: ubuntu-20.04 + tools: + python: "3.8" + # Optionally build your docs in additional formats such as PDF and ePub formats: - htmlzip @@ -14,7 +20,6 @@ sphinx: # Optionally set the version of Python and requirements required to build your docs python: - version: 3.8 install: - requirements: docs/requirements.txt diff --git a/docs/source/information/command-handler.rst b/docs/source/information/command-handler.rst index 4e98a77aa8..134e491e89 100644 --- a/docs/source/information/command-handler.rst +++ b/docs/source/information/command-handler.rst @@ -26,7 +26,7 @@ System commands can be activated using: - The additional parameter for websocket is used to allow multiple connections on one websocket server instance, both with or without -command proceesing. +command processing. When the websocket open request has query parameter ``"command=true"`` command processing is enabled. diff --git a/docs/source/information/events.rst b/docs/source/information/events.rst index 42585042e1..962508fa4d 100644 --- a/docs/source/information/events.rst +++ b/docs/source/information/events.rst @@ -75,7 +75,7 @@ such as if a timer expires. This can be a regular 'C' callback function, which you should use for handling interrupts. -For regular application code, a :cpp:class:`Delegate` provides more flexbility and allows you to create +For regular application code, a :cpp:class:`Delegate` provides more flexibility and allows you to create simpler, cleaner code. See `Delegation `__ for a bit of background. diff --git a/samples/SDCard/example.output.txt b/samples/SDCard/example.output.txt index b48a9f7cf8..a10b198240 100644 --- a/samples/SDCard/example.output.txt +++ b/samples/SDCard/example.output.txt @@ -45,7 +45,7 @@ Read: hello has 5 letters -4. Write speed benchamark: +4. Write speed benchmark: Write 1 kBytes in 1 Bytes increment: 23.17 kB/s Write 1 kBytes in 64 Bytes increment: 23.13 kB/s Write 1 kBytes in 128 Bytes increment: 23.09 kB/s @@ -54,4 +54,4 @@ Write 1 kBytes in 1024 Bytes increment: 24.01 kB/s Write 4 kBytes in 1024 Bytes increment: 71.36 kB/s Write 8 kBytes in 512 Bytes increment: 91.49 kB/s Write 8 kBytes in 1024 Bytes increment: 102.11 kB/s -Write 8 kBytes in 8192 Bytes increment: 96.95 kB/s \ No newline at end of file +Write 8 kBytes in 8192 Bytes increment: 96.95 kB/s From 9f55c7a8a9a0124233c8f41a0d62bae8db15d2f9 Mon Sep 17 00:00:00 2001 From: xeonqq Date: Thu, 9 Nov 2023 10:24:13 +0100 Subject: [PATCH 21/28] Add MPU6050 library and sample code (#2655) --- Sming/Libraries/MPU6050/MPU6050.cpp | 323 ++ Sming/Libraries/MPU6050/MPU6050.h | 3446 +++++++++++++++++ Sming/Libraries/MPU6050/README.rst | 19 + Sming/Libraries/MPU6050/component.mk | 2 + samples/Accel_Gyro_MPU6050/Makefile | 9 + samples/Accel_Gyro_MPU6050/README.rst | 7 + .../Accel_Gyro_MPU6050/app/application.cpp | 24 + samples/Accel_Gyro_MPU6050/component.mk | 2 + samples/Accel_Gyro_MPU6050/mpu6050.jpg | Bin 0 -> 55832 bytes 9 files changed, 3832 insertions(+) create mode 100644 Sming/Libraries/MPU6050/MPU6050.cpp create mode 100644 Sming/Libraries/MPU6050/MPU6050.h create mode 100644 Sming/Libraries/MPU6050/README.rst create mode 100644 Sming/Libraries/MPU6050/component.mk create mode 100644 samples/Accel_Gyro_MPU6050/Makefile create mode 100644 samples/Accel_Gyro_MPU6050/README.rst create mode 100644 samples/Accel_Gyro_MPU6050/app/application.cpp create mode 100644 samples/Accel_Gyro_MPU6050/component.mk create mode 100644 samples/Accel_Gyro_MPU6050/mpu6050.jpg diff --git a/Sming/Libraries/MPU6050/MPU6050.cpp b/Sming/Libraries/MPU6050/MPU6050.cpp new file mode 100644 index 0000000000..cc639c8eb6 --- /dev/null +++ b/Sming/Libraries/MPU6050/MPU6050.cpp @@ -0,0 +1,323 @@ +// I2Cdev library collection - MPU6050 I2C device class +// Based on InvenSense MPU-6050 register map document rev. 2.0, 5/19/2011 +// (RM-MPU-6000A-00) 8/24/2011 by Jeff Rowberg Updates should +// (hopefully) always be available at https://github.com/jrowberg/i2cdevlib +// +// Changelog: +// ... - ongoing debug release + +// NOTE: THIS IS ONLY A PARTIAL RELEASE. THIS DEVICE CLASS IS CURRENTLY +// UNDERGOING ACTIVE DEVELOPMENT AND IS STILL MISSING SOME IMPORTANT FEATURES. +// PLEASE KEEP THIS IN MIND IF YOU DECIDE TO USE THIS PARTICULAR CODE FOR +// ANYTHING. + +/* ============================================ +I2Cdev device library code is placed under the MIT license +Copyright (c) 2012 Jeff Rowberg + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +=============================================== +*/ + +#include "MPU6050.h" +#include +#include + +#define I2C_NUM I2C_NUM_0 +using detail::concat; + +namespace +{ +//Slave 4’s characteristics differ greatly from those of Slaves 0-3. +//Hence our API support only up to slave 3 +constexpr uint8_t MAX_SLAVE_ID{3}; +#define ASSERT_SLAVE_ID_VALID(slaveId) assert((slaveId <= MAX_SLAVE_ID)) +} // namespace + +size_t MPU6050::Motion3::printTo(Print& p) const +{ + size_t n{0}; + n += p.print(x); + n += p.print('\t'); + n += p.print(y); + n += p.print('\t'); + n += p.print(z); + return n; +} + +size_t MPU6050::Motion6::printTo(Print& p) const +{ + size_t n{0}; + n += p.print(_F("accel/gyro:\t")); + n += p.print(accel); + n += p.print('\t'); + n += p.print(gyro); + return n; +} + +void MPU6050::initialize() +{ + setClockSource(MPU6050_CLOCK_PLL_XGYRO); + setFullScaleGyroRange(MPU6050_GYRO_FS_250); + setFullScaleAccelRange(MPU6050_ACCEL_FS_2); + setSleepEnabled(false); // thanks to Jack Elston for pointing this one out! +} + +uint8_t MPU6050::getAccelXSelfTestFactoryTrim() +{ + const uint8_t x = readByte(MPU6050_RA_SELF_TEST_X); + const uint8_t a = readByte(MPU6050_RA_SELF_TEST_A); + return (x >> 3) | ((a >> 4) & 0x03); +} + +uint8_t MPU6050::getAccelYSelfTestFactoryTrim() +{ + const uint8_t y = readByte(MPU6050_RA_SELF_TEST_Y); + const uint8_t a = readByte(MPU6050_RA_SELF_TEST_A); + return (y >> 3) | ((a >> 2) & 0x03); +} + +uint8_t MPU6050::getAccelZSelfTestFactoryTrim() +{ + uint8_t buffer[2] = {0}; + I2Cdev::readBytes(devAddr, MPU6050_RA_SELF_TEST_Z, 2, buffer); + return (buffer[0] >> 3) | (buffer[1] & 0x03); +} + +uint8_t MPU6050::getGyroXSelfTestFactoryTrim() +{ + const uint8_t x = readByte(MPU6050_RA_SELF_TEST_X); + return (x & 0x1F); +} + +uint8_t MPU6050::getGyroYSelfTestFactoryTrim() +{ + const uint8_t y = readByte(MPU6050_RA_SELF_TEST_Y); + return (y & 0x1F); +} + +uint8_t MPU6050::getGyroZSelfTestFactoryTrim() +{ + const uint8_t z = readByte(MPU6050_RA_SELF_TEST_Z); + return (z & 0x1F); +} + +uint8_t MPU6050::getSlaveAddress(SlaveId slaveId) +{ + ASSERT_SLAVE_ID_VALID(slaveId); + return readByte(MPU6050_RA_I2C_SLV0_ADDR + slaveId * 3); +} + +void MPU6050::setSlaveAddress(SlaveId slaveId, uint8_t address) +{ + ASSERT_SLAVE_ID_VALID(slaveId); + I2Cdev::writeByte(devAddr, MPU6050_RA_I2C_SLV0_ADDR + slaveId * 3, address); +} + +uint8_t MPU6050::getSlaveRegister(SlaveId slaveId) +{ + ASSERT_SLAVE_ID_VALID(slaveId); + return readByte(MPU6050_RA_I2C_SLV0_REG + slaveId * 3); +} + +void MPU6050::setSlaveRegister(SlaveId slaveId, uint8_t reg) +{ + ASSERT_SLAVE_ID_VALID(slaveId); + I2Cdev::writeByte(devAddr, MPU6050_RA_I2C_SLV0_REG + slaveId * 3, reg); +} + +bool MPU6050::getSlaveEnabled(SlaveId slaveId) +{ + ASSERT_SLAVE_ID_VALID(slaveId); + return readBit(MPU6050_RA_I2C_SLV0_CTRL + slaveId * 3, MPU6050_I2C_SLV_EN_BIT); +} + +void MPU6050::setSlaveEnabled(SlaveId slaveId, bool enabled) +{ + ASSERT_SLAVE_ID_VALID(slaveId); + I2Cdev::writeBit(devAddr, MPU6050_RA_I2C_SLV0_CTRL + slaveId * 3, MPU6050_I2C_SLV_EN_BIT, enabled); +} + +bool MPU6050::getSlaveWordByteSwap(SlaveId slaveId) +{ + ASSERT_SLAVE_ID_VALID(slaveId); + return readBit(MPU6050_RA_I2C_SLV0_CTRL + slaveId * 3, MPU6050_I2C_SLV_BYTE_SW_BIT); +} + +void MPU6050::setSlaveWordByteSwap(SlaveId slaveId, bool enabled) +{ + ASSERT_SLAVE_ID_VALID(slaveId); + I2Cdev::writeBit(devAddr, MPU6050_RA_I2C_SLV0_CTRL + slaveId * 3, MPU6050_I2C_SLV_BYTE_SW_BIT, enabled); +} + +bool MPU6050::getSlaveWriteMode(SlaveId slaveId) +{ + ASSERT_SLAVE_ID_VALID(slaveId); + + return readBit(MPU6050_RA_I2C_SLV0_CTRL + slaveId * 3, MPU6050_I2C_SLV_REG_DIS_BIT); +} + +void MPU6050::setSlaveWriteMode(SlaveId slaveId, bool mode) +{ + ASSERT_SLAVE_ID_VALID(slaveId); + + I2Cdev::writeBit(devAddr, MPU6050_RA_I2C_SLV0_CTRL + slaveId * 3, MPU6050_I2C_SLV_REG_DIS_BIT, mode); +} + +bool MPU6050::getSlaveWordGroupOffset(SlaveId slaveId) +{ + ASSERT_SLAVE_ID_VALID(slaveId); + + return readBit(MPU6050_RA_I2C_SLV0_CTRL + slaveId * 3, MPU6050_I2C_SLV_GRP_BIT); +} + +void MPU6050::setSlaveWordGroupOffset(SlaveId slaveId, bool enabled) +{ + ASSERT_SLAVE_ID_VALID(slaveId); + + I2Cdev::writeBit(devAddr, MPU6050_RA_I2C_SLV0_CTRL + slaveId * 3, MPU6050_I2C_SLV_GRP_BIT, enabled); +} + +uint8_t MPU6050::getSlaveDataLength(SlaveId slaveId) +{ + ASSERT_SLAVE_ID_VALID(slaveId); + + return readBits(MPU6050_RA_I2C_SLV0_CTRL + slaveId * 3, MPU6050_I2C_SLV_LEN_BIT, MPU6050_I2C_SLV_LEN_LENGTH); +} + +void MPU6050::setSlaveDataLength(SlaveId slaveId, uint8_t length) +{ + ASSERT_SLAVE_ID_VALID(slaveId); + + I2Cdev::writeBits(devAddr, MPU6050_RA_I2C_SLV0_CTRL + slaveId * 3, MPU6050_I2C_SLV_LEN_BIT, + MPU6050_I2C_SLV_LEN_LENGTH, length); +} + +MPU6050::Motion6 MPU6050::getMotion6() +{ + Motion6 motion6; + uint8_t buffer[14] = {0}; + I2Cdev::readBytes(devAddr, MPU6050_RA_ACCEL_XOUT_H, 14, buffer); + motion6.accel.x = concat(buffer[0], buffer[1]); + motion6.accel.y = concat(buffer[2], buffer[3]); + motion6.accel.z = concat(buffer[4], buffer[5]); + motion6.gyro.x = concat(buffer[8], buffer[9]); + motion6.gyro.y = concat(buffer[10], buffer[11]); + motion6.gyro.z = concat(buffer[12], buffer[13]); + return motion6; +} + +MPU6050::Motion3 MPU6050::getAcceleration() +{ + Motion3 accel; + uint8_t buffer[6] = {0}; + I2Cdev::readBytes(devAddr, MPU6050_RA_ACCEL_XOUT_H, 6, buffer); + accel.x = concat(buffer[0], buffer[1]); + accel.y = concat(buffer[2], buffer[3]); + accel.z = concat(buffer[4], buffer[5]); + return accel; +} + +MPU6050::Motion3 MPU6050::getAngularRate() +{ + Motion3 angularRate; + uint8_t buffer[6] = {0}; + I2Cdev::readBytes(devAddr, MPU6050_RA_GYRO_XOUT_H, 6, buffer); + angularRate.x = concat(buffer[0], buffer[1]); + angularRate.y = concat(buffer[2], buffer[3]); + angularRate.z = concat(buffer[4], buffer[5]); + return angularRate; +} + +void MPU6050::setSlaveOutputByte(SlaveId slaveId, uint8_t data) +{ + ASSERT_SLAVE_ID_VALID(slaveId); + I2Cdev::writeByte(devAddr, MPU6050_RA_I2C_SLV0_DO + slaveId, data); +} + +bool MPU6050::getSlaveDelayEnabled(SlaveId slaveId) +{ + // MPU6050_DELAYCTRL_I2C_SLV4_DLY_EN_BIT is 4, SLV3 is 3, etc. + ASSERT_SLAVE_ID_VALID(slaveId); + return readBit(MPU6050_RA_I2C_MST_DELAY_CTRL, slaveId); +} + +// XA_OFFS_* registers +int16_t MPU6050::getXAccelOffset() +{ + uint8_t buffer[2] = {0}; + I2Cdev::readBytes(devAddr, MPU6050_RA_XA_OFFS_H, 2, buffer); + return (((int16_t)buffer[0]) << 8) | buffer[1]; +} + +// YA_OFFS_* register + +int16_t MPU6050::getYAccelOffset() +{ + uint8_t buffer[2] = {0}; + I2Cdev::readBytes(devAddr, MPU6050_RA_YA_OFFS_H, 2, buffer); + return (((int16_t)buffer[0]) << 8) | buffer[1]; +} + +// ZA_OFFS_* register + +int16_t MPU6050::getZAccelOffset() +{ + uint8_t buffer[2] = {0}; + I2Cdev::readBytes(devAddr, MPU6050_RA_ZA_OFFS_H, 2, buffer); + return (((int16_t)buffer[0]) << 8) | buffer[1]; +} + +// BANK_SEL register + +void MPU6050::setMemoryBank(uint8_t bank, bool prefetchEnabled, bool userBank) +{ + bank &= 0x1F; + if(userBank) { + bank |= 0x20; + } + if(prefetchEnabled) { + bank |= 0x40; + } + I2Cdev::writeByte(devAddr, MPU6050_RA_BANK_SEL, bank); +} + +uint8_t MPU6050::readBit(uint8_t regAddr, uint8_t bitNum) +{ + uint8_t bit; + const auto count = I2Cdev::readBit(devAddr, regAddr, bitNum, &bit); + (void)count; + return bit; +} + +uint8_t MPU6050::readBits(uint8_t regAddr, uint8_t bitStart, uint8_t length) +{ + uint8_t bits; + const auto count = I2Cdev::readBits(devAddr, regAddr, bitStart, length, &bits); + (void)count; + return bits; +} + +uint8_t MPU6050::readByte(uint8_t regAddr) +{ + uint8_t byte; + const auto count = I2Cdev::readByte(devAddr, regAddr, &byte); + (void)count; + return byte; +} diff --git a/Sming/Libraries/MPU6050/MPU6050.h b/Sming/Libraries/MPU6050/MPU6050.h new file mode 100644 index 0000000000..ef277d1e35 --- /dev/null +++ b/Sming/Libraries/MPU6050/MPU6050.h @@ -0,0 +1,3446 @@ +// Based on InvenSense MPU-6050 register map document rev. 2.0, 5/19/2011 +// (RM-MPU-6000A-00) +// Based On https://github.com/jrowberg/i2cdevlib + +// NOTE: THIS IS ONLY A PARTIAL RELEASE. THIS DEVICE CLASS IS CURRENTLY +// UNDERGOING ACTIVE DEVELOPMENT AND IS STILL MISSING SOME IMPORTANT FEATURES. +// PLEASE KEEP THIS IN MIND IF YOU DECIDE TO USE THIS PARTICULAR CODE FOR +// ANYTHING. + +/* ============================================ + I2Cdev device library code is placed under the MIT license + Copyright (c) 2012 Jeff Rowberg + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + =============================================== + */ + +#pragma once + +#include + +#define MPU6050_ADDRESS_AD0_LOW 0x68 // address pin low (GND), default for InvenSense evaluation board +#define MPU6050_ADDRESS_AD0_HIGH 0x69 // address pin high (VCC) +#define MPU6050_DEFAULT_ADDRESS MPU6050_ADDRESS_AD0_LOW + +#define MPU6050_RA_XG_OFFS_TC 0x00 //[7] PWR_MODE, [6:1] XG_OFFS_TC, [0] OTP_BNK_VLD +#define MPU6050_RA_YG_OFFS_TC 0x01 //[7] PWR_MODE, [6:1] YG_OFFS_TC, [0] OTP_BNK_VLD +#define MPU6050_RA_ZG_OFFS_TC 0x02 //[7] PWR_MODE, [6:1] ZG_OFFS_TC, [0] OTP_BNK_VLD +#define MPU6050_RA_X_FINE_GAIN 0x03 //[7:0] X_FINE_GAIN +#define MPU6050_RA_Y_FINE_GAIN 0x04 //[7:0] Y_FINE_GAIN +#define MPU6050_RA_Z_FINE_GAIN 0x05 //[7:0] Z_FINE_GAIN +#define MPU6050_RA_XA_OFFS_H 0x06 //[15:0] XA_OFFS +#define MPU6050_RA_XA_OFFS_L_TC 0x07 +#define MPU6050_RA_YA_OFFS_H 0x08 //[15:0] YA_OFFS +#define MPU6050_RA_YA_OFFS_L_TC 0x09 +#define MPU6050_RA_ZA_OFFS_H 0x0A //[15:0] ZA_OFFS +#define MPU6050_RA_ZA_OFFS_L_TC 0x0B +#define MPU6050_RA_SELF_TEST_X 0x0D //[7:5] XA_TEST[4-2], [4:0] XG_TEST[4-0] +#define MPU6050_RA_SELF_TEST_Y 0x0E //[7:5] YA_TEST[4-2], [4:0] YG_TEST[4-0] +#define MPU6050_RA_SELF_TEST_Z 0x0F //[7:5] ZA_TEST[4-2], [4:0] ZG_TEST[4-0] +#define MPU6050_RA_SELF_TEST_A 0x10 //[5:4] XA_TEST[1-0], [3:2] YA_TEST[1-0], [1:0] ZA_TEST[1-0] +#define MPU6050_RA_XG_OFFS_USRH 0x13 //[15:0] XG_OFFS_USR +#define MPU6050_RA_XG_OFFS_USRL 0x14 +#define MPU6050_RA_YG_OFFS_USRH 0x15 //[15:0] YG_OFFS_USR +#define MPU6050_RA_YG_OFFS_USRL 0x16 +#define MPU6050_RA_ZG_OFFS_USRH 0x17 //[15:0] ZG_OFFS_USR +#define MPU6050_RA_ZG_OFFS_USRL 0x18 +#define MPU6050_RA_SMPLRT_DIV 0x19 +#define MPU6050_RA_CONFIG 0x1A +#define MPU6050_RA_GYRO_CONFIG 0x1B +#define MPU6050_RA_ACCEL_CONFIG 0x1C +#define MPU6050_RA_FF_THR 0x1D +#define MPU6050_RA_FF_DUR 0x1E +#define MPU6050_RA_MOT_THR 0x1F +#define MPU6050_RA_MOT_DUR 0x20 +#define MPU6050_RA_ZRMOT_THR 0x21 +#define MPU6050_RA_ZRMOT_DUR 0x22 +#define MPU6050_RA_FIFO_EN 0x23 +#define MPU6050_RA_I2C_MST_CTRL 0x24 +#define MPU6050_RA_I2C_SLV0_ADDR 0x25 +#define MPU6050_RA_I2C_SLV0_REG 0x26 +#define MPU6050_RA_I2C_SLV0_CTRL 0x27 +#define MPU6050_RA_I2C_SLV1_ADDR 0x28 +#define MPU6050_RA_I2C_SLV1_REG 0x29 +#define MPU6050_RA_I2C_SLV1_CTRL 0x2A +#define MPU6050_RA_I2C_SLV2_ADDR 0x2B +#define MPU6050_RA_I2C_SLV2_REG 0x2C +#define MPU6050_RA_I2C_SLV2_CTRL 0x2D +#define MPU6050_RA_I2C_SLV3_ADDR 0x2E +#define MPU6050_RA_I2C_SLV3_REG 0x2F +#define MPU6050_RA_I2C_SLV3_CTRL 0x30 +#define MPU6050_RA_I2C_SLV4_ADDR 0x31 +#define MPU6050_RA_I2C_SLV4_REG 0x32 +#define MPU6050_RA_I2C_SLV4_DO 0x33 +#define MPU6050_RA_I2C_SLV4_CTRL 0x34 +#define MPU6050_RA_I2C_SLV4_DI 0x35 +#define MPU6050_RA_I2C_MST_STATUS 0x36 +#define MPU6050_RA_INT_PIN_CFG 0x37 +#define MPU6050_RA_INT_ENABLE 0x38 +#define MPU6050_RA_DMP_INT_STATUS 0x39 +#define MPU6050_RA_INT_STATUS 0x3A +#define MPU6050_RA_ACCEL_XOUT_H 0x3B +#define MPU6050_RA_ACCEL_XOUT_L 0x3C +#define MPU6050_RA_ACCEL_YOUT_H 0x3D +#define MPU6050_RA_ACCEL_YOUT_L 0x3E +#define MPU6050_RA_ACCEL_ZOUT_H 0x3F +#define MPU6050_RA_ACCEL_ZOUT_L 0x40 +#define MPU6050_RA_TEMP_OUT_H 0x41 +#define MPU6050_RA_TEMP_OUT_L 0x42 +#define MPU6050_RA_GYRO_XOUT_H 0x43 +#define MPU6050_RA_GYRO_XOUT_L 0x44 +#define MPU6050_RA_GYRO_YOUT_H 0x45 +#define MPU6050_RA_GYRO_YOUT_L 0x46 +#define MPU6050_RA_GYRO_ZOUT_H 0x47 +#define MPU6050_RA_GYRO_ZOUT_L 0x48 +#define MPU6050_RA_EXT_SENS_DATA_00 0x49 +#define MPU6050_RA_EXT_SENS_DATA_01 0x4A +#define MPU6050_RA_EXT_SENS_DATA_02 0x4B +#define MPU6050_RA_EXT_SENS_DATA_03 0x4C +#define MPU6050_RA_EXT_SENS_DATA_04 0x4D +#define MPU6050_RA_EXT_SENS_DATA_05 0x4E +#define MPU6050_RA_EXT_SENS_DATA_06 0x4F +#define MPU6050_RA_EXT_SENS_DATA_07 0x50 +#define MPU6050_RA_EXT_SENS_DATA_08 0x51 +#define MPU6050_RA_EXT_SENS_DATA_09 0x52 +#define MPU6050_RA_EXT_SENS_DATA_10 0x53 +#define MPU6050_RA_EXT_SENS_DATA_11 0x54 +#define MPU6050_RA_EXT_SENS_DATA_12 0x55 +#define MPU6050_RA_EXT_SENS_DATA_13 0x56 +#define MPU6050_RA_EXT_SENS_DATA_14 0x57 +#define MPU6050_RA_EXT_SENS_DATA_15 0x58 +#define MPU6050_RA_EXT_SENS_DATA_16 0x59 +#define MPU6050_RA_EXT_SENS_DATA_17 0x5A +#define MPU6050_RA_EXT_SENS_DATA_18 0x5B +#define MPU6050_RA_EXT_SENS_DATA_19 0x5C +#define MPU6050_RA_EXT_SENS_DATA_20 0x5D +#define MPU6050_RA_EXT_SENS_DATA_21 0x5E +#define MPU6050_RA_EXT_SENS_DATA_22 0x5F +#define MPU6050_RA_EXT_SENS_DATA_23 0x60 +#define MPU6050_RA_MOT_DETECT_STATUS 0x61 +#define MPU6050_RA_I2C_SLV0_DO 0x63 +#define MPU6050_RA_I2C_SLV1_DO 0x64 +#define MPU6050_RA_I2C_SLV2_DO 0x65 +#define MPU6050_RA_I2C_SLV3_DO 0x66 +#define MPU6050_RA_I2C_MST_DELAY_CTRL 0x67 +#define MPU6050_RA_SIGNAL_PATH_RESET 0x68 +#define MPU6050_RA_MOT_DETECT_CTRL 0x69 +#define MPU6050_RA_USER_CTRL 0x6A +#define MPU6050_RA_PWR_MGMT_1 0x6B +#define MPU6050_RA_PWR_MGMT_2 0x6C +#define MPU6050_RA_BANK_SEL 0x6D +#define MPU6050_RA_MEM_START_ADDR 0x6E +#define MPU6050_RA_MEM_R_W 0x6F +#define MPU6050_RA_DMP_CFG_1 0x70 +#define MPU6050_RA_DMP_CFG_2 0x71 +#define MPU6050_RA_FIFO_COUNTH 0x72 +#define MPU6050_RA_FIFO_COUNTL 0x73 +#define MPU6050_RA_FIFO_R_W 0x74 +#define MPU6050_RA_WHO_AM_I 0x75 + +#define MPU6050_SELF_TEST_XA_1_BIT 0x07 +#define MPU6050_SELF_TEST_XA_1_LENGTH 0x03 +#define MPU6050_SELF_TEST_XA_2_BIT 0x05 +#define MPU6050_SELF_TEST_XA_2_LENGTH 0x02 +#define MPU6050_SELF_TEST_YA_1_BIT 0x07 +#define MPU6050_SELF_TEST_YA_1_LENGTH 0x03 +#define MPU6050_SELF_TEST_YA_2_BIT 0x03 +#define MPU6050_SELF_TEST_YA_2_LENGTH 0x02 +#define MPU6050_SELF_TEST_ZA_1_BIT 0x07 +#define MPU6050_SELF_TEST_ZA_1_LENGTH 0x03 +#define MPU6050_SELF_TEST_ZA_2_BIT 0x01 +#define MPU6050_SELF_TEST_ZA_2_LENGTH 0x02 + +#define MPU6050_SELF_TEST_XG_1_BIT 0x04 +#define MPU6050_SELF_TEST_XG_1_LENGTH 0x05 +#define MPU6050_SELF_TEST_YG_1_BIT 0x04 +#define MPU6050_SELF_TEST_YG_1_LENGTH 0x05 +#define MPU6050_SELF_TEST_ZG_1_BIT 0x04 +#define MPU6050_SELF_TEST_ZG_1_LENGTH 0x05 + +#define MPU6050_TC_PWR_MODE_BIT 7 +#define MPU6050_TC_OFFSET_BIT 6 +#define MPU6050_TC_OFFSET_LENGTH 6 +#define MPU6050_TC_OTP_BNK_VLD_BIT 0 + +#define MPU6050_VDDIO_LEVEL_VLOGIC 0 +#define MPU6050_VDDIO_LEVEL_VDD 1 + +#define MPU6050_CFG_EXT_SYNC_SET_BIT 5 +#define MPU6050_CFG_EXT_SYNC_SET_LENGTH 3 +#define MPU6050_CFG_DLPF_CFG_BIT 2 +#define MPU6050_CFG_DLPF_CFG_LENGTH 3 + +#define MPU6050_EXT_SYNC_DISABLED 0x0 +#define MPU6050_EXT_SYNC_TEMP_OUT_L 0x1 +#define MPU6050_EXT_SYNC_GYRO_XOUT_L 0x2 +#define MPU6050_EXT_SYNC_GYRO_YOUT_L 0x3 +#define MPU6050_EXT_SYNC_GYRO_ZOUT_L 0x4 +#define MPU6050_EXT_SYNC_ACCEL_XOUT_L 0x5 +#define MPU6050_EXT_SYNC_ACCEL_YOUT_L 0x6 +#define MPU6050_EXT_SYNC_ACCEL_ZOUT_L 0x7 + +#define MPU6050_DLPF_BW_256 0x00 +#define MPU6050_DLPF_BW_188 0x01 +#define MPU6050_DLPF_BW_98 0x02 +#define MPU6050_DLPF_BW_42 0x03 +#define MPU6050_DLPF_BW_20 0x04 +#define MPU6050_DLPF_BW_10 0x05 +#define MPU6050_DLPF_BW_5 0x06 + +#define MPU6050_GCONFIG_FS_SEL_BIT 4 +#define MPU6050_GCONFIG_FS_SEL_LENGTH 2 + +#define MPU6050_GYRO_FS_250 0x00 +#define MPU6050_GYRO_FS_500 0x01 +#define MPU6050_GYRO_FS_1000 0x02 +#define MPU6050_GYRO_FS_2000 0x03 + +#define MPU6050_ACONFIG_XA_ST_BIT 7 +#define MPU6050_ACONFIG_YA_ST_BIT 6 +#define MPU6050_ACONFIG_ZA_ST_BIT 5 +#define MPU6050_ACONFIG_AFS_SEL_BIT 4 +#define MPU6050_ACONFIG_AFS_SEL_LENGTH 2 +#define MPU6050_ACONFIG_ACCEL_HPF_BIT 2 +#define MPU6050_ACONFIG_ACCEL_HPF_LENGTH 3 + +#define MPU6050_ACCEL_FS_2 0x00 +#define MPU6050_ACCEL_FS_4 0x01 +#define MPU6050_ACCEL_FS_8 0x02 +#define MPU6050_ACCEL_FS_16 0x03 + +#define MPU6050_DHPF_RESET 0x00 +#define MPU6050_DHPF_5 0x01 +#define MPU6050_DHPF_2P5 0x02 +#define MPU6050_DHPF_1P25 0x03 +#define MPU6050_DHPF_0P63 0x04 +#define MPU6050_DHPF_HOLD 0x07 + +#define MPU6050_TEMP_FIFO_EN_BIT 7 +#define MPU6050_XG_FIFO_EN_BIT 6 +#define MPU6050_YG_FIFO_EN_BIT 5 +#define MPU6050_ZG_FIFO_EN_BIT 4 +#define MPU6050_ACCEL_FIFO_EN_BIT 3 +#define MPU6050_SLV2_FIFO_EN_BIT 2 +#define MPU6050_SLV1_FIFO_EN_BIT 1 +#define MPU6050_SLV0_FIFO_EN_BIT 0 + +#define MPU6050_MULT_MST_EN_BIT 7 +#define MPU6050_WAIT_FOR_ES_BIT 6 +#define MPU6050_SLV_3_FIFO_EN_BIT 5 +#define MPU6050_I2C_MST_P_NSR_BIT 4 +#define MPU6050_I2C_MST_CLK_BIT 3 +#define MPU6050_I2C_MST_CLK_LENGTH 4 + +#define MPU6050_CLOCK_DIV_348 0x0 +#define MPU6050_CLOCK_DIV_333 0x1 +#define MPU6050_CLOCK_DIV_320 0x2 +#define MPU6050_CLOCK_DIV_308 0x3 +#define MPU6050_CLOCK_DIV_296 0x4 +#define MPU6050_CLOCK_DIV_286 0x5 +#define MPU6050_CLOCK_DIV_276 0x6 +#define MPU6050_CLOCK_DIV_267 0x7 +#define MPU6050_CLOCK_DIV_258 0x8 +#define MPU6050_CLOCK_DIV_500 0x9 +#define MPU6050_CLOCK_DIV_471 0xA +#define MPU6050_CLOCK_DIV_444 0xB +#define MPU6050_CLOCK_DIV_421 0xC +#define MPU6050_CLOCK_DIV_400 0xD +#define MPU6050_CLOCK_DIV_381 0xE +#define MPU6050_CLOCK_DIV_364 0xF + +#define MPU6050_I2C_SLV_RW_BIT 7 +#define MPU6050_I2C_SLV_ADDR_BIT 6 +#define MPU6050_I2C_SLV_ADDR_LENGTH 7 +#define MPU6050_I2C_SLV_EN_BIT 7 +#define MPU6050_I2C_SLV_BYTE_SW_BIT 6 +#define MPU6050_I2C_SLV_REG_DIS_BIT 5 +#define MPU6050_I2C_SLV_GRP_BIT 4 +#define MPU6050_I2C_SLV_LEN_BIT 3 +#define MPU6050_I2C_SLV_LEN_LENGTH 4 + +#define MPU6050_I2C_SLV4_RW_BIT 7 +#define MPU6050_I2C_SLV4_ADDR_BIT 6 +#define MPU6050_I2C_SLV4_ADDR_LENGTH 7 +#define MPU6050_I2C_SLV4_EN_BIT 7 +#define MPU6050_I2C_SLV4_INT_EN_BIT 6 +#define MPU6050_I2C_SLV4_REG_DIS_BIT 5 +#define MPU6050_I2C_SLV4_MST_DLY_BIT 4 +#define MPU6050_I2C_SLV4_MST_DLY_LENGTH 5 + +#define MPU6050_MST_PASS_THROUGH_BIT 7 +#define MPU6050_MST_I2C_SLV4_DONE_BIT 6 +#define MPU6050_MST_I2C_LOST_ARB_BIT 5 +#define MPU6050_MST_I2C_SLV4_NACK_BIT 4 +#define MPU6050_MST_I2C_SLV3_NACK_BIT 3 +#define MPU6050_MST_I2C_SLV2_NACK_BIT 2 +#define MPU6050_MST_I2C_SLV1_NACK_BIT 1 +#define MPU6050_MST_I2C_SLV0_NACK_BIT 0 + +#define MPU6050_INTCFG_INT_LEVEL_BIT 7 +#define MPU6050_INTCFG_INT_OPEN_BIT 6 +#define MPU6050_INTCFG_LATCH_INT_EN_BIT 5 +#define MPU6050_INTCFG_INT_RD_CLEAR_BIT 4 +#define MPU6050_INTCFG_FSYNC_INT_LEVEL_BIT 3 +#define MPU6050_INTCFG_FSYNC_INT_EN_BIT 2 +#define MPU6050_INTCFG_I2C_BYPASS_EN_BIT 1 +#define MPU6050_INTCFG_CLKOUT_EN_BIT 0 + +#define MPU6050_INTMODE_ACTIVEHIGH 0x00 +#define MPU6050_INTMODE_ACTIVELOW 0x01 + +#define MPU6050_INTDRV_PUSHPULL 0x00 +#define MPU6050_INTDRV_OPENDRAIN 0x01 + +#define MPU6050_INTLATCH_50USPULSE 0x00 +#define MPU6050_INTLATCH_WAITCLEAR 0x01 + +#define MPU6050_INTCLEAR_STATUSREAD 0x00 +#define MPU6050_INTCLEAR_ANYREAD 0x01 + +#define MPU6050_INTERRUPT_FF_BIT 7 +#define MPU6050_INTERRUPT_MOT_BIT 6 +#define MPU6050_INTERRUPT_ZMOT_BIT 5 +#define MPU6050_INTERRUPT_FIFO_OFLOW_BIT 4 +#define MPU6050_INTERRUPT_I2C_MST_INT_BIT 3 +#define MPU6050_INTERRUPT_PLL_RDY_INT_BIT 2 +#define MPU6050_INTERRUPT_DMP_INT_BIT 1 +#define MPU6050_INTERRUPT_DATA_RDY_BIT 0 + +// TODO: figure out what these actually do +// UMPL source code is not very obivous +#define MPU6050_DMPINT_5_BIT 5 +#define MPU6050_DMPINT_4_BIT 4 +#define MPU6050_DMPINT_3_BIT 3 +#define MPU6050_DMPINT_2_BIT 2 +#define MPU6050_DMPINT_1_BIT 1 +#define MPU6050_DMPINT_0_BIT 0 + +#define MPU6050_MOTION_MOT_XNEG_BIT 7 +#define MPU6050_MOTION_MOT_XPOS_BIT 6 +#define MPU6050_MOTION_MOT_YNEG_BIT 5 +#define MPU6050_MOTION_MOT_YPOS_BIT 4 +#define MPU6050_MOTION_MOT_ZNEG_BIT 3 +#define MPU6050_MOTION_MOT_ZPOS_BIT 2 +#define MPU6050_MOTION_MOT_ZRMOT_BIT 0 + +#define MPU6050_DELAYCTRL_DELAY_ES_SHADOW_BIT 7 +#define MPU6050_DELAYCTRL_I2C_SLV4_DLY_EN_BIT 4 +#define MPU6050_DELAYCTRL_I2C_SLV3_DLY_EN_BIT 3 +#define MPU6050_DELAYCTRL_I2C_SLV2_DLY_EN_BIT 2 +#define MPU6050_DELAYCTRL_I2C_SLV1_DLY_EN_BIT 1 +#define MPU6050_DELAYCTRL_I2C_SLV0_DLY_EN_BIT 0 + +#define MPU6050_PATHRESET_GYRO_RESET_BIT 2 +#define MPU6050_PATHRESET_ACCEL_RESET_BIT 1 +#define MPU6050_PATHRESET_TEMP_RESET_BIT 0 + +#define MPU6050_DETECT_ACCEL_ON_DELAY_BIT 5 +#define MPU6050_DETECT_ACCEL_ON_DELAY_LENGTH 2 +#define MPU6050_DETECT_FF_COUNT_BIT 3 +#define MPU6050_DETECT_FF_COUNT_LENGTH 2 +#define MPU6050_DETECT_MOT_COUNT_BIT 1 +#define MPU6050_DETECT_MOT_COUNT_LENGTH 2 + +#define MPU6050_DETECT_DECREMENT_RESET 0x0 +#define MPU6050_DETECT_DECREMENT_1 0x1 +#define MPU6050_DETECT_DECREMENT_2 0x2 +#define MPU6050_DETECT_DECREMENT_4 0x3 + +#define MPU6050_USERCTRL_DMP_EN_BIT 7 +#define MPU6050_USERCTRL_FIFO_EN_BIT 6 +#define MPU6050_USERCTRL_I2C_MST_EN_BIT 5 +#define MPU6050_USERCTRL_I2C_IF_DIS_BIT 4 +#define MPU6050_USERCTRL_DMP_RESET_BIT 3 +#define MPU6050_USERCTRL_FIFO_RESET_BIT 2 +#define MPU6050_USERCTRL_I2C_MST_RESET_BIT 1 +#define MPU6050_USERCTRL_SIG_COND_RESET_BIT 0 + +#define MPU6050_PWR1_DEVICE_RESET_BIT 7 +#define MPU6050_PWR1_SLEEP_BIT 6 +#define MPU6050_PWR1_CYCLE_BIT 5 +#define MPU6050_PWR1_TEMP_DIS_BIT 3 +#define MPU6050_PWR1_CLKSEL_BIT 2 +#define MPU6050_PWR1_CLKSEL_LENGTH 3 + +#define MPU6050_CLOCK_INTERNAL 0x00 +#define MPU6050_CLOCK_PLL_XGYRO 0x01 +#define MPU6050_CLOCK_PLL_YGYRO 0x02 +#define MPU6050_CLOCK_PLL_ZGYRO 0x03 +#define MPU6050_CLOCK_PLL_EXT32K 0x04 +#define MPU6050_CLOCK_PLL_EXT19M 0x05 +#define MPU6050_CLOCK_KEEP_RESET 0x07 + +#define MPU6050_PWR2_LP_WAKE_CTRL_BIT 7 +#define MPU6050_PWR2_LP_WAKE_CTRL_LENGTH 2 +#define MPU6050_PWR2_STBY_XA_BIT 5 +#define MPU6050_PWR2_STBY_YA_BIT 4 +#define MPU6050_PWR2_STBY_ZA_BIT 3 +#define MPU6050_PWR2_STBY_XG_BIT 2 +#define MPU6050_PWR2_STBY_YG_BIT 1 +#define MPU6050_PWR2_STBY_ZG_BIT 0 + +#define MPU6050_WAKE_FREQ_1P25 0x0 +#define MPU6050_WAKE_FREQ_2P5 0x1 +#define MPU6050_WAKE_FREQ_5 0x2 +#define MPU6050_WAKE_FREQ_10 0x3 + +#define MPU6050_BANKSEL_PRFTCH_EN_BIT 6 +#define MPU6050_BANKSEL_CFG_USER_BANK_BIT 5 +#define MPU6050_BANKSEL_MEM_SEL_BIT 4 +#define MPU6050_BANKSEL_MEM_SEL_LENGTH 5 + +#define MPU6050_WHO_AM_I_BIT 6 +#define MPU6050_WHO_AM_I_LENGTH 6 + +#define MPU6050_DMP_MEMORY_BANKS 8 +#define MPU6050_DMP_MEMORY_BANK_SIZE 256 +#define MPU6050_DMP_MEMORY_CHUNK_SIZE 16 + +class MPU6050 +{ +public: + using SlaveId = uint8_t; // (0 - 3) + + struct Motion3 { + int16_t x{}; + int16_t y{}; + int16_t z{}; + size_t printTo(Print& p) const; + }; + + struct Motion6 { + Motion3 accel{}; + Motion3 gyro{}; + size_t printTo(Print& p) const; + }; + /** Default constructor, uses default I2C address. + * @see MPU6050_DEFAULT_ADDRESS + */ + MPU6050() : devAddr{MPU6050_DEFAULT_ADDRESS} + { + } + + /** Specific address constructor. + * @param address I2C address + * @see MPU6050_DEFAULT_ADDRESS + * @see MPU6050_ADDRESS_AD0_LOW + * @see MPU6050_ADDRESS_AD0_HIGH + */ + MPU6050(uint8_t address) : devAddr{address} + { + } + + /** Power on and prepare for general usage. + * This will activate the device and take it out of sleep mode (which must be + * done after start-up). This function also sets both the accelerometer and the + * gyroscope to their most sensitive settings, namely +/- 2g and +/- 250 + * degrees/sec, and sets the clock source to use the X Gyro for reference, which + * is slightly better than the default internal clock source. + */ + void initialize(); + + /** Verify the I2C connection. + * Make sure the device is connected and responds as expected. + * @return True if connection is valid, false otherwise + */ + bool testConnection() + { + return getDeviceID() == 0x34; + } + + // AUX_VDDIO register (InvenSense demo code calls this RA_*G_OFFS_TC) + /** Get the auxiliary I2C supply voltage level. + * When set to 1, the auxiliary I2C bus high logic level is VDD. When cleared to + * 0, the auxiliary I2C bus high logic level is VLOGIC. This does not apply to + * the MPU-6000, which does not have a VLOGIC pin. + * @return I2C supply voltage level (0=VLOGIC, 1=VDD) + */ + uint8_t getAuxVDDIOLevel() + { + return readBit(MPU6050_RA_YG_OFFS_TC, MPU6050_TC_PWR_MODE_BIT); + } + + /** Set the auxiliary I2C supply voltage level. + * When set to 1, the auxiliary I2C bus high logic level is VDD. When cleared to + * 0, the auxiliary I2C bus high logic level is VLOGIC. This does not apply to + * the MPU-6000, which does not have a VLOGIC pin. + * @param level I2C supply voltage level (0=VLOGIC, 1=VDD) + */ + void setAuxVDDIOLevel(uint8_t level) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_YG_OFFS_TC, MPU6050_TC_PWR_MODE_BIT, level); + } + + // SMPLRT_DIV register + /** Get gyroscope output rate divider. + * The sensor register output, FIFO output, DMP sampling, Motion detection, Zero + * Motion detection, and Free Fall detection are all based on the Sample Rate. + * The Sample Rate is generated by dividing the gyroscope output rate by + * SMPLRT_DIV: + * + * Sample Rate = Gyroscope Output Rate / (1 + SMPLRT_DIV) + * + * where Gyroscope Output Rate = 8kHz when the DLPF is disabled (DLPF_CFG = 0 or + * 7), and 1kHz when the DLPF is enabled (see Register 26). + * + * Note: The accelerometer output rate is 1kHz. This means that for a Sample + * Rate greater than 1kHz, the same accelerometer sample may be output to the + * FIFO, DMP, and sensor registers more than once. + * + * For a diagram of the gyroscope and accelerometer signal paths, see Section 8 + * of the MPU-6000/MPU-6050 Product Specification document. + * + * @return Current sample rate + * @see MPU6050_RA_SMPLRT_DIV + */ + uint8_t getRate() + { + return readByte(MPU6050_RA_SMPLRT_DIV); + } + + /** Set gyroscope sample rate divider. + * @param rate New sample rate divider + * @see getRate() + * @see MPU6050_RA_SMPLRT_DIV + */ + void setRate(uint8_t rate) + { + I2Cdev::writeByte(devAddr, MPU6050_RA_SMPLRT_DIV, rate); + } + + // CONFIG register + + /** Get external FSYNC configuration. + * Configures the external Frame Synchronization (FSYNC) pin sampling. An + * external signal connected to the FSYNC pin can be sampled by configuring + * EXT_SYNC_SET. Signal changes to the FSYNC pin are latched so that short + * strobes may be captured. The latched FSYNC signal will be sampled at the + * Sampling Rate, as defined in register 25. After sampling, the latch will + * reset to the current FSYNC signal state. + * + * The sampled value will be reported in place of the least significant bit in + * a sensor data register determined by the value of EXT_SYNC_SET according to + * the following table. + * + *
+         * EXT_SYNC_SET | FSYNC Bit Location
+         * -------------+-------------------
+         * 0            | Input disabled
+         * 1            | TEMP_OUT_L[0]
+         * 2            | GYRO_XOUT_L[0]
+         * 3            | GYRO_YOUT_L[0]
+         * 4            | GYRO_ZOUT_L[0]
+         * 5            | ACCEL_XOUT_L[0]
+         * 6            | ACCEL_YOUT_L[0]
+         * 7            | ACCEL_ZOUT_L[0]
+         * 
+ * + * @return FSYNC configuration value + */ + uint8_t getExternalFrameSync() + { + return readBits(MPU6050_RA_CONFIG, MPU6050_CFG_EXT_SYNC_SET_BIT, MPU6050_CFG_EXT_SYNC_SET_LENGTH); + } + /** Set external FSYNC configuration. + * @see getExternalFrameSync() + * @see MPU6050_RA_CONFIG + * @param sync New FSYNC configuration value + */ + void setExternalFrameSync(uint8_t sync) + { + I2Cdev::writeBits(devAddr, MPU6050_RA_CONFIG, MPU6050_CFG_EXT_SYNC_SET_BIT, MPU6050_CFG_EXT_SYNC_SET_LENGTH, + sync); + } + /** Get digital low-pass filter configuration. + * The DLPF_CFG parameter sets the digital low pass filter configuration. It + * also determines the internal sampling rate used by the device as shown in + * the table below. + * + * Note: The accelerometer output rate is 1kHz. This means that for a Sample + * Rate greater than 1kHz, the same accelerometer sample may be output to the + * FIFO, DMP, and sensor registers more than once. + * + *
+         *          |   ACCELEROMETER    |           GYROSCOPE
+         * DLPF_CFG | Bandwidth | Delay  | Bandwidth | Delay  | Sample Rate
+         * ---------+-----------+--------+-----------+--------+-------------
+         * 0        | 260Hz     | 0ms    | 256Hz     | 0.98ms | 8kHz
+         * 1        | 184Hz     | 2.0ms  | 188Hz     | 1.9ms  | 1kHz
+         * 2        | 94Hz      | 3.0ms  | 98Hz      | 2.8ms  | 1kHz
+         * 3        | 44Hz      | 4.9ms  | 42Hz      | 4.8ms  | 1kHz
+         * 4        | 21Hz      | 8.5ms  | 20Hz      | 8.3ms  | 1kHz
+         * 5        | 10Hz      | 13.8ms | 10Hz      | 13.4ms | 1kHz
+         * 6        | 5Hz       | 19.0ms | 5Hz       | 18.6ms | 1kHz
+         * 7        |   -- Reserved --   |   -- Reserved --   | Reserved
+         * 
+ * + * @return DLFP configuration + * @see MPU6050_RA_CONFIG + * @see MPU6050_CFG_DLPF_CFG_BIT + * @see MPU6050_CFG_DLPF_CFG_LENGTH + */ + uint8_t getDLPFMode() + { + return readBits(MPU6050_RA_CONFIG, MPU6050_CFG_DLPF_CFG_BIT, MPU6050_CFG_DLPF_CFG_LENGTH); + } + /** Set digital low-pass filter configuration. + * @param mode New DLFP configuration setting + * @see getDLPFBandwidth() + * @see MPU6050_DLPF_BW_256 + * @see MPU6050_RA_CONFIG + * @see MPU6050_CFG_DLPF_CFG_BIT + * @see MPU6050_CFG_DLPF_CFG_LENGTH + */ + void setDLPFMode(uint8_t mode) + { + I2Cdev::writeBits(devAddr, MPU6050_RA_CONFIG, MPU6050_CFG_DLPF_CFG_BIT, MPU6050_CFG_DLPF_CFG_LENGTH, mode); + } + + // GYRO_CONFIG register + /** Get full-scale gyroscope range. + * The FS_SEL parameter allows setting the full-scale range of the gyro sensors, + * as described in the table below. + * + *
+         * 0 = +/- 250 degrees/sec
+         * 1 = +/- 500 degrees/sec
+         * 2 = +/- 1000 degrees/sec
+         * 3 = +/- 2000 degrees/sec
+         * 
+ * + * @return Current full-scale gyroscope range setting + * @see MPU6050_GYRO_FS_250 + * @see MPU6050_RA_GYRO_CONFIG + * @see MPU6050_GCONFIG_FS_SEL_BIT + * @see MPU6050_GCONFIG_FS_SEL_LENGTH + */ + uint8_t getFullScaleGyroRange() + { + return readBits(MPU6050_RA_GYRO_CONFIG, MPU6050_GCONFIG_FS_SEL_BIT, MPU6050_GCONFIG_FS_SEL_LENGTH); + } + + /** Set full-scale gyroscope range. + * @param range New full-scale gyroscope range value + * @see getFullScaleRange() + * @see MPU6050_GYRO_FS_250 + * @see MPU6050_RA_GYRO_CONFIG + * @see MPU6050_GCONFIG_FS_SEL_BIT + * @see MPU6050_GCONFIG_FS_SEL_LENGTH + */ + void setFullScaleGyroRange(uint8_t range) + { + I2Cdev::writeBits(devAddr, MPU6050_RA_GYRO_CONFIG, MPU6050_GCONFIG_FS_SEL_BIT, MPU6050_GCONFIG_FS_SEL_LENGTH, + range); + } + + // SELF TEST FACTORY TRIM VALUES + /** Get self-test factory trim value for accelerometer X axis. + * @return factory trim value + * @see MPU6050_RA_SELF_TEST_X + */ + uint8_t getAccelXSelfTestFactoryTrim(); + + /** Get self-test factory trim value for accelerometer Y axis. + * @return factory trim value + * @see MPU6050_RA_SELF_TEST_Y + */ + uint8_t getAccelYSelfTestFactoryTrim(); + + /** Get self-test factory trim value for accelerometer Z axis. + * @return factory trim value + * @see MPU6050_RA_SELF_TEST_Z + */ + uint8_t getAccelZSelfTestFactoryTrim(); + + /** Get self-test factory trim value for gyro X axis. + * @return factory trim value + * @see MPU6050_RA_SELF_TEST_X + */ + uint8_t getGyroXSelfTestFactoryTrim(); + + /** Get self-test factory trim value for gyro Y axis. + * @return factory trim value + * @see MPU6050_RA_SELF_TEST_Y + */ + uint8_t getGyroYSelfTestFactoryTrim(); + + /** Get self-test factory trim value for gyro Z axis. + * @return factory trim value + * @see MPU6050_RA_SELF_TEST_Z + */ + uint8_t getGyroZSelfTestFactoryTrim(); + + // ACCEL_CONFIG register + + /** Get self-test enabled setting for accelerometer X axis. + * @return Self-test enabled value + * @see MPU6050_RA_ACCEL_CONFIG + */ + bool getAccelXSelfTest() + { + return readBit(MPU6050_RA_ACCEL_CONFIG, MPU6050_ACONFIG_XA_ST_BIT); + } + /** Get self-test enabled setting for accelerometer X axis. + * @param enabled Self-test enabled value + * @see MPU6050_RA_ACCEL_CONFIG + */ + void setAccelXSelfTest(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_ACCEL_CONFIG, MPU6050_ACONFIG_XA_ST_BIT, enabled); + } + /** Get self-test enabled value for accelerometer Y axis. + * @return Self-test enabled value + * @see MPU6050_RA_ACCEL_CONFIG + */ + bool getAccelYSelfTest() + { + return readBit(MPU6050_RA_ACCEL_CONFIG, MPU6050_ACONFIG_YA_ST_BIT); + } + /** Get self-test enabled value for accelerometer Y axis. + * @param enabled Self-test enabled value + * @see MPU6050_RA_ACCEL_CONFIG + */ + void setAccelYSelfTest(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_ACCEL_CONFIG, MPU6050_ACONFIG_YA_ST_BIT, enabled); + } + /** Get self-test enabled value for accelerometer Z axis. + * @return Self-test enabled value + * @see MPU6050_RA_ACCEL_CONFIG + */ + bool getAccelZSelfTest() + { + return readBit(MPU6050_RA_ACCEL_CONFIG, MPU6050_ACONFIG_ZA_ST_BIT); + } + /** Set self-test enabled value for accelerometer Z axis. + * @param enabled Self-test enabled value + * @see MPU6050_RA_ACCEL_CONFIG + */ + void setAccelZSelfTest(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_ACCEL_CONFIG, MPU6050_ACONFIG_ZA_ST_BIT, enabled); + } + /** Get full-scale accelerometer range. + * The FS_SEL parameter allows setting the full-scale range of the accelerometer + * sensors, as described in the table below. + * + *
+         * 0 = +/- 2g
+         * 1 = +/- 4g
+         * 2 = +/- 8g
+         * 3 = +/- 16g
+         * 
+ * + * @return Current full-scale accelerometer range setting + * @see MPU6050_ACCEL_FS_2 + * @see MPU6050_RA_ACCEL_CONFIG + * @see MPU6050_ACONFIG_AFS_SEL_BIT + * @see MPU6050_ACONFIG_AFS_SEL_LENGTH + */ + uint8_t getFullScaleAccelRange() + { + return readBits(MPU6050_RA_ACCEL_CONFIG, MPU6050_ACONFIG_AFS_SEL_BIT, MPU6050_ACONFIG_AFS_SEL_LENGTH); + } + /** Set full-scale accelerometer range. + * @param range New full-scale accelerometer range setting + * @see getFullScaleAccelRange() + */ + void setFullScaleAccelRange(uint8_t range) + { + I2Cdev::writeBits(devAddr, MPU6050_RA_ACCEL_CONFIG, MPU6050_ACONFIG_AFS_SEL_BIT, MPU6050_ACONFIG_AFS_SEL_LENGTH, + range); + } + /** Get the high-pass filter configuration. + * The DHPF is a filter module in the path leading to motion detectors (Free + * Fall, Motion threshold, and Zero Motion). The high pass filter output is not + * available to the data registers (see Figure in Section 8 of the MPU-6000/ + * MPU-6050 Product Specification document). + * + * The high pass filter has three modes: + * + *
+         *    Reset: The filter output settles to zero within one sample. This
+         *           effectively disables the high pass filter. This mode may be toggled
+         *           to quickly settle the filter.
+         *
+         *    On:    The high pass filter will pass signals above the cut off frequency.
+         *
+         *    Hold:  When triggered, the filter holds the present sample. The filter
+         *           output will be the difference between the input sample and the held
+         *           sample.
+         * 
+ * + *
+         * ACCEL_HPF | Filter Mode | Cut-off Frequency
+         * ----------+-------------+------------------
+         * 0         | Reset       | None
+         * 1         | On          | 5Hz
+         * 2         | On          | 2.5Hz
+         * 3         | On          | 1.25Hz
+         * 4         | On          | 0.63Hz
+         * 7         | Hold        | None
+         * 
+ * + * @return Current high-pass filter configuration + * @see MPU6050_DHPF_RESET + * @see MPU6050_RA_ACCEL_CONFIG + */ + uint8_t getDHPFMode() + { + return readBits(MPU6050_RA_ACCEL_CONFIG, MPU6050_ACONFIG_ACCEL_HPF_BIT, MPU6050_ACONFIG_ACCEL_HPF_LENGTH); + } + /** Set the high-pass filter configuration. + * @param bandwidth New high-pass filter configuration + * @see setDHPFMode() + * @see MPU6050_DHPF_RESET + * @see MPU6050_RA_ACCEL_CONFIG + */ + void setDHPFMode(uint8_t bandwidth) + { + I2Cdev::writeBits(devAddr, MPU6050_RA_ACCEL_CONFIG, MPU6050_ACONFIG_ACCEL_HPF_BIT, + MPU6050_ACONFIG_ACCEL_HPF_LENGTH, bandwidth); + } + + // FF_THR register + + /** Get free-fall event acceleration threshold. + * This register configures the detection threshold for Free Fall event + * detection. The unit of FF_THR is 1LSB = 2mg. Free Fall is detected when the + * absolute value of the accelerometer measurements for the three axes are each + * less than the detection threshold. This condition increments the Free Fall + * duration counter (Register 30). The Free Fall interrupt is triggered when the + * Free Fall duration counter reaches the time specified in FF_DUR. + * + * For more details on the Free Fall detection interrupt, see Section 8.2 of the + * MPU-6000/MPU-6050 Product Specification document as well as Registers 56 and + * 58 of this document. + * + * @return Current free-fall acceleration threshold value (LSB = 2mg) + * @see MPU6050_RA_FF_THR + */ + uint8_t getFreefallDetectionThreshold() + { + return readByte(MPU6050_RA_FF_THR); + } + /** Get free-fall event acceleration threshold. + * @param threshold New free-fall acceleration threshold value (LSB = 2mg) + * @see getFreefallDetectionThreshold() + * @see MPU6050_RA_FF_THR + */ + void setFreefallDetectionThreshold(uint8_t threshold) + { + I2Cdev::writeByte(devAddr, MPU6050_RA_FF_THR, threshold); + } + + // FF_DUR register + + /** Get free-fall event duration threshold. + * This register configures the duration counter threshold for Free Fall event + * detection. The duration counter ticks at 1kHz, therefore FF_DUR has a unit + * of 1 LSB = 1 ms. + * + * The Free Fall duration counter increments while the absolute value of the + * accelerometer measurements are each less than the detection threshold + * (Register 29). The Free Fall interrupt is triggered when the Free Fall + * duration counter reaches the time specified in this register. + * + * For more details on the Free Fall detection interrupt, see Section 8.2 of + * the MPU-6000/MPU-6050 Product Specification document as well as Registers 56 + * and 58 of this document. + * + * @return Current free-fall duration threshold value (LSB = 1ms) + * @see MPU6050_RA_FF_DUR + */ + uint8_t getFreefallDetectionDuration() + { + return readByte(MPU6050_RA_FF_DUR); + } + /** Get free-fall event duration threshold. + * @param duration New free-fall duration threshold value (LSB = 1ms) + * @see getFreefallDetectionDuration() + * @see MPU6050_RA_FF_DUR + */ + void setFreefallDetectionDuration(uint8_t duration) + { + I2Cdev::writeByte(devAddr, MPU6050_RA_FF_DUR, duration); + } + + // MOT_THR register + + /** Get motion detection event acceleration threshold. + * This register configures the detection threshold for Motion interrupt + * generation. The unit of MOT_THR is 1LSB = 2mg. Motion is detected when the + * absolute value of any of the accelerometer measurements exceeds this Motion + * detection threshold. This condition increments the Motion detection duration + * counter (Register 32). The Motion detection interrupt is triggered when the + * Motion Detection counter reaches the time count specified in MOT_DUR + * (Register 32). + * + * The Motion interrupt will indicate the axis and polarity of detected motion + * in MOT_DETECT_STATUS (Register 97). + * + * For more details on the Motion detection interrupt, see Section 8.3 of the + * MPU-6000/MPU-6050 Product Specification document as well as Registers 56 and + * 58 of this document. + * + * @return Current motion detection acceleration threshold value (LSB = 2mg) + * @see MPU6050_RA_MOT_THR + */ + uint8_t getMotionDetectionThreshold() + { + return readByte(MPU6050_RA_MOT_THR); + } + /** Set motion detection event acceleration threshold. + * @param threshold New motion detection acceleration threshold value (LSB = + * 2mg) + * @see getMotionDetectionThreshold() + * @see MPU6050_RA_MOT_THR + */ + void setMotionDetectionThreshold(uint8_t threshold) + { + I2Cdev::writeByte(devAddr, MPU6050_RA_MOT_THR, threshold); + } + + // MOT_DUR register + + /** Get motion detection event duration threshold. + * This register configures the duration counter threshold for Motion interrupt + * generation. The duration counter ticks at 1 kHz, therefore MOT_DUR has a unit + * of 1LSB = 1ms. The Motion detection duration counter increments when the + * absolute value of any of the accelerometer measurements exceeds the Motion + * detection threshold (Register 31). The Motion detection interrupt is + * triggered when the Motion detection counter reaches the time count specified + * in this register. + * + * For more details on the Motion detection interrupt, see Section 8.3 of the + * MPU-6000/MPU-6050 Product Specification document. + * + * @return Current motion detection duration threshold value (LSB = 1ms) + * @see MPU6050_RA_MOT_DUR + */ + uint8_t getMotionDetectionDuration() + { + return readByte(MPU6050_RA_MOT_DUR); + } + /** Set motion detection event duration threshold. + * @param duration New motion detection duration threshold value (LSB = 1ms) + * @see getMotionDetectionDuration() + * @see MPU6050_RA_MOT_DUR + */ + void setMotionDetectionDuration(uint8_t duration) + { + I2Cdev::writeByte(devAddr, MPU6050_RA_MOT_DUR, duration); + } + + // ZRMOT_THR register + + /** Get zero motion detection event acceleration threshold. + * This register configures the detection threshold for Zero Motion interrupt + * generation. The unit of ZRMOT_THR is 1LSB = 2mg. Zero Motion is detected when + * the absolute value of the accelerometer measurements for the 3 axes are each + * less than the detection threshold. This condition increments the Zero Motion + * duration counter (Register 34). The Zero Motion interrupt is triggered when + * the Zero Motion duration counter reaches the time count specified in + * ZRMOT_DUR (Register 34). + * + * Unlike Free Fall or Motion detection, Zero Motion detection triggers an + * interrupt both when Zero Motion is first detected and when Zero Motion is no + * longer detected. + * + * When a zero motion event is detected, a Zero Motion Status will be indicated + * in the MOT_DETECT_STATUS register (Register 97). When a motion-to-zero-motion + * condition is detected, the status bit is set to 1. When a zero-motion-to- + * motion condition is detected, the status bit is set to 0. + * + * For more details on the Zero Motion detection interrupt, see Section 8.4 of + * the MPU-6000/MPU-6050 Product Specification document as well as Registers 56 + * and 58 of this document. + * + * @return Current zero motion detection acceleration threshold value (LSB = + * 2mg) + * @see MPU6050_RA_ZRMOT_THR + */ + uint8_t getZeroMotionDetectionThreshold() + { + return readByte(MPU6050_RA_ZRMOT_THR); + } + /** Set zero motion detection event acceleration threshold. + * @param threshold New zero motion detection acceleration threshold value (LSB + * = 2mg) + * @see getZeroMotionDetectionThreshold() + * @see MPU6050_RA_ZRMOT_THR + */ + void setZeroMotionDetectionThreshold(uint8_t threshold) + { + I2Cdev::writeByte(devAddr, MPU6050_RA_ZRMOT_THR, threshold); + } + + // ZRMOT_DUR register + + /** Get zero motion detection event duration threshold. + * This register configures the duration counter threshold for Zero Motion + * interrupt generation. The duration counter ticks at 16 Hz, therefore + * ZRMOT_DUR has a unit of 1 LSB = 64 ms. The Zero Motion duration counter + * increments while the absolute value of the accelerometer measurements are + * each less than the detection threshold (Register 33). The Zero Motion + * interrupt is triggered when the Zero Motion duration counter reaches the time + * count specified in this register. + * + * For more details on the Zero Motion detection interrupt, see Section 8.4 of + * the MPU-6000/MPU-6050 Product Specification document, as well as Registers 56 + * and 58 of this document. + * + * @return Current zero motion detection duration threshold value (LSB = 64ms) + * @see MPU6050_RA_ZRMOT_DUR + */ + uint8_t getZeroMotionDetectionDuration() + { + return readByte(MPU6050_RA_ZRMOT_DUR); + } + /** Set zero motion detection event duration threshold. + * @param duration New zero motion detection duration threshold value (LSB = + * 1ms) + * @see getZeroMotionDetectionDuration() + * @see MPU6050_RA_ZRMOT_DUR + */ + void setZeroMotionDetectionDuration(uint8_t duration) + { + I2Cdev::writeByte(devAddr, MPU6050_RA_ZRMOT_DUR, duration); + } + + // FIFO_EN register + + /** Get temperature FIFO enabled value. + * When set to 1, this bit enables TEMP_OUT_H and TEMP_OUT_L (Registers 65 and + * 66) to be written into the FIFO buffer. + * @return Current temperature FIFO enabled value + * @see MPU6050_RA_FIFO_EN + */ + bool getTempFIFOEnabled() + { + return readBit(MPU6050_RA_FIFO_EN, MPU6050_TEMP_FIFO_EN_BIT); + } + /** Set temperature FIFO enabled value. + * @param enabled New temperature FIFO enabled value + * @see getTempFIFOEnabled() + * @see MPU6050_RA_FIFO_EN + */ + void setTempFIFOEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_FIFO_EN, MPU6050_TEMP_FIFO_EN_BIT, enabled); + } + /** Get gyroscope X-axis FIFO enabled value. + * When set to 1, this bit enables GYRO_XOUT_H and GYRO_XOUT_L (Registers 67 and + * 68) to be written into the FIFO buffer. + * @return Current gyroscope X-axis FIFO enabled value + * @see MPU6050_RA_FIFO_EN + */ + bool getXGyroFIFOEnabled() + { + return readBit(MPU6050_RA_FIFO_EN, MPU6050_XG_FIFO_EN_BIT); + } + /** Set gyroscope X-axis FIFO enabled value. + * @param enabled New gyroscope X-axis FIFO enabled value + * @see getXGyroFIFOEnabled() + * @see MPU6050_RA_FIFO_EN + */ + void setXGyroFIFOEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_FIFO_EN, MPU6050_XG_FIFO_EN_BIT, enabled); + } + /** Get gyroscope Y-axis FIFO enabled value. + * When set to 1, this bit enables GYRO_YOUT_H and GYRO_YOUT_L (Registers 69 and + * 70) to be written into the FIFO buffer. + * @return Current gyroscope Y-axis FIFO enabled value + * @see MPU6050_RA_FIFO_EN + */ + bool getYGyroFIFOEnabled() + { + return readBit(MPU6050_RA_FIFO_EN, MPU6050_YG_FIFO_EN_BIT); + } + /** Set gyroscope Y-axis FIFO enabled value. + * @param enabled New gyroscope Y-axis FIFO enabled value + * @see getYGyroFIFOEnabled() + * @see MPU6050_RA_FIFO_EN + */ + void setYGyroFIFOEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_FIFO_EN, MPU6050_YG_FIFO_EN_BIT, enabled); + } + /** Get gyroscope Z-axis FIFO enabled value. + * When set to 1, this bit enables GYRO_ZOUT_H and GYRO_ZOUT_L (Registers 71 and + * 72) to be written into the FIFO buffer. + * @return Current gyroscope Z-axis FIFO enabled value + * @see MPU6050_RA_FIFO_EN + */ + bool getZGyroFIFOEnabled() + { + return readBit(MPU6050_RA_FIFO_EN, MPU6050_ZG_FIFO_EN_BIT); + } + /** Set gyroscope Z-axis FIFO enabled value. + * @param enabled New gyroscope Z-axis FIFO enabled value + * @see getZGyroFIFOEnabled() + * @see MPU6050_RA_FIFO_EN + */ + void setZGyroFIFOEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_FIFO_EN, MPU6050_ZG_FIFO_EN_BIT, enabled); + } + /** Get accelerometer FIFO enabled value. + * When set to 1, this bit enables ACCEL_XOUT_H, ACCEL_XOUT_L, ACCEL_YOUT_H, + * ACCEL_YOUT_L, ACCEL_ZOUT_H, and ACCEL_ZOUT_L (Registers 59 to 64) to be + * written into the FIFO buffer. + * @return Current accelerometer FIFO enabled value + * @see MPU6050_RA_FIFO_EN + */ + bool getAccelFIFOEnabled() + { + return readBit(MPU6050_RA_FIFO_EN, MPU6050_ACCEL_FIFO_EN_BIT); + } + /** Set accelerometer FIFO enabled value. + * @param enabled New accelerometer FIFO enabled value + * @see getAccelFIFOEnabled() + * @see MPU6050_RA_FIFO_EN + */ + void setAccelFIFOEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_FIFO_EN, MPU6050_ACCEL_FIFO_EN_BIT, enabled); + } + /** Get Slave 2 FIFO enabled value. + * When set to 1, this bit enables EXT_SENS_DATA registers (Registers 73 to 96) + * associated with Slave 2 to be written into the FIFO buffer. + * @return Current Slave 2 FIFO enabled value + * @see MPU6050_RA_FIFO_EN + */ + bool getSlave2FIFOEnabled() + { + return readBit(MPU6050_RA_FIFO_EN, MPU6050_SLV2_FIFO_EN_BIT); + } + /** Set Slave 2 FIFO enabled value. + * @param enabled New Slave 2 FIFO enabled value + * @see getSlave2FIFOEnabled() + * @see MPU6050_RA_FIFO_EN + */ + void setSlave2FIFOEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_FIFO_EN, MPU6050_SLV2_FIFO_EN_BIT, enabled); + } + /** Get Slave 1 FIFO enabled value. + * When set to 1, this bit enables EXT_SENS_DATA registers (Registers 73 to 96) + * associated with Slave 1 to be written into the FIFO buffer. + * @return Current Slave 1 FIFO enabled value + * @see MPU6050_RA_FIFO_EN + */ + bool getSlave1FIFOEnabled() + { + return readBit(MPU6050_RA_FIFO_EN, MPU6050_SLV1_FIFO_EN_BIT); + } + /** Set Slave 1 FIFO enabled value. + * @param enabled New Slave 1 FIFO enabled value + * @see getSlave1FIFOEnabled() + * @see MPU6050_RA_FIFO_EN + */ + void setSlave1FIFOEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_FIFO_EN, MPU6050_SLV1_FIFO_EN_BIT, enabled); + } + /** Get Slave 0 FIFO enabled value. + * When set to 1, this bit enables EXT_SENS_DATA registers (Registers 73 to 96) + * associated with Slave 0 to be written into the FIFO buffer. + * @return Current Slave 0 FIFO enabled value + * @see MPU6050_RA_FIFO_EN + */ + bool getSlave0FIFOEnabled() + { + return readBit(MPU6050_RA_FIFO_EN, MPU6050_SLV0_FIFO_EN_BIT); + } + /** Set Slave 0 FIFO enabled value. + * @param enabled New Slave 0 FIFO enabled value + * @see getSlave0FIFOEnabled() + * @see MPU6050_RA_FIFO_EN + */ + void setSlave0FIFOEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_FIFO_EN, MPU6050_SLV0_FIFO_EN_BIT, enabled); + } + + // I2C_MST_CTRL register + + /** Get multi-master enabled value. + * Multi-master capability allows multiple I2C masters to operate on the same + * bus. In circuits where multi-master capability is required, set MULT_MST_EN + * to 1. This will increase current drawn by approximately 30uA. + * + * In circuits where multi-master capability is required, the state of the I2C + * bus must always be monitored by each separate I2C Master. Before an I2C + * Master can assume arbitration of the bus, it must first confirm that no other + * I2C Master has arbitration of the bus. When MULT_MST_EN is set to 1, the + * MPU-60X0's bus arbitration detection logic is turned on, enabling it to + * detect when the bus is available. + * + * @return Current multi-master enabled value + * @see MPU6050_RA_I2C_MST_CTRL + */ + bool getMultiMasterEnabled() + { + return readBit(MPU6050_RA_I2C_MST_CTRL, MPU6050_MULT_MST_EN_BIT); + } + /** Set multi-master enabled value. + * @param enabled New multi-master enabled value + * @see getMultiMasterEnabled() + * @see MPU6050_RA_I2C_MST_CTRL + */ + void setMultiMasterEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_I2C_MST_CTRL, MPU6050_MULT_MST_EN_BIT, enabled); + } + /** Get wait-for-external-sensor-data enabled value. + * When the WAIT_FOR_ES bit is set to 1, the Data Ready interrupt will be + * delayed until External Sensor data from the Slave Devices are loaded into the + * EXT_SENS_DATA registers. This is used to ensure that both the internal sensor + * data (i.e. from gyro and accel) and external sensor data have been loaded to + * their respective data registers (i.e. the data is synced) when the Data Ready + * interrupt is triggered. + * + * @return Current wait-for-external-sensor-data enabled value + * @see MPU6050_RA_I2C_MST_CTRL + */ + bool getWaitForExternalSensorEnabled() + { + return readBit(MPU6050_RA_I2C_MST_CTRL, MPU6050_WAIT_FOR_ES_BIT); + } + /** Set wait-for-external-sensor-data enabled value. + * @param enabled New wait-for-external-sensor-data enabled value + * @see getWaitForExternalSensorEnabled() + * @see MPU6050_RA_I2C_MST_CTRL + */ + void setWaitForExternalSensorEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_I2C_MST_CTRL, MPU6050_WAIT_FOR_ES_BIT, enabled); + } + /** Get Slave 3 FIFO enabled value. + * When set to 1, this bit enables EXT_SENS_DATA registers (Registers 73 to 96) + * associated with Slave 3 to be written into the FIFO buffer. + * @return Current Slave 3 FIFO enabled value + * @see MPU6050_RA_MST_CTRL + */ + bool getSlave3FIFOEnabled() + { + return readBit(MPU6050_RA_I2C_MST_CTRL, MPU6050_SLV_3_FIFO_EN_BIT); + } + /** Set Slave 3 FIFO enabled value. + * @param enabled New Slave 3 FIFO enabled value + * @see getSlave3FIFOEnabled() + * @see MPU6050_RA_MST_CTRL + */ + void setSlave3FIFOEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_I2C_MST_CTRL, MPU6050_SLV_3_FIFO_EN_BIT, enabled); + } + /** Get slave read/write transition enabled value. + * The I2C_MST_P_NSR bit configures the I2C Master's transition from one slave + * read to the next slave read. If the bit equals 0, there will be a restart + * between reads. If the bit equals 1, there will be a stop followed by a start + * of the following read. When a write transaction follows a read transaction, + * the stop followed by a start of the successive write will be always used. + * + * @return Current slave read/write transition enabled value + * @see MPU6050_RA_I2C_MST_CTRL + */ + bool getSlaveReadWriteTransitionEnabled() + { + return readBit(MPU6050_RA_I2C_MST_CTRL, MPU6050_I2C_MST_P_NSR_BIT); + } + /** Set slave read/write transition enabled value. + * @param enabled New slave read/write transition enabled value + * @see getSlaveReadWriteTransitionEnabled() + * @see MPU6050_RA_I2C_MST_CTRL + */ + void setSlaveReadWriteTransitionEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_I2C_MST_CTRL, MPU6050_I2C_MST_P_NSR_BIT, enabled); + } + /** Get I2C master clock speed. + * I2C_MST_CLK is a 4 bit unsigned value which configures a divider on the + * MPU-60X0 internal 8MHz clock. It sets the I2C master clock speed according to + * the following table: + * + *
+         * I2C_MST_CLK | I2C Master Clock Speed | 8MHz Clock Divider
+         * ------------+------------------------+-------------------
+         * 0           | 348kHz                 | 23
+         * 1           | 333kHz                 | 24
+         * 2           | 320kHz                 | 25
+         * 3           | 308kHz                 | 26
+         * 4           | 296kHz                 | 27
+         * 5           | 286kHz                 | 28
+         * 6           | 276kHz                 | 29
+         * 7           | 267kHz                 | 30
+         * 8           | 258kHz                 | 31
+         * 9           | 500kHz                 | 16
+         * 10          | 471kHz                 | 17
+         * 11          | 444kHz                 | 18
+         * 12          | 421kHz                 | 19
+         * 13          | 400kHz                 | 20
+         * 14          | 381kHz                 | 21
+         * 15          | 364kHz                 | 22
+         * 
+ * + * @return Current I2C master clock speed + * @see MPU6050_RA_I2C_MST_CTRL + */ + uint8_t getMasterClockSpeed() + { + return readBits(MPU6050_RA_I2C_MST_CTRL, MPU6050_I2C_MST_CLK_BIT, MPU6050_I2C_MST_CLK_LENGTH); + } + /** Set I2C master clock speed. + * @reparam speed Current I2C master clock speed + * @see MPU6050_RA_I2C_MST_CTRL + */ + void setMasterClockSpeed(uint8_t speed) + { + I2Cdev::writeBits(devAddr, MPU6050_RA_I2C_MST_CTRL, MPU6050_I2C_MST_CLK_BIT, MPU6050_I2C_MST_CLK_LENGTH, speed); + } + // I2C_SLV* registers (Slave 0-3) + + /** Get the I2C address of the specified slave (0-3). + * Note that Bit 7 (MSB) controls read/write mode. If Bit 7 is set, it's a read + * operation, and if it is cleared, then it's a write operation. The remaining + * bits (6-0) are the 7-bit device address of the slave device. + * + * In read mode, the result of the read is placed in the lowest available + * EXT_SENS_DATA register. For further information regarding the allocation of + * read results, please refer to the EXT_SENS_DATA register description + * (Registers 73 - 96). + * + * The MPU-6050 supports a total of five slaves, but Slave 4 has unique + * characteristics, and so it has its own functions (getSlave4* and setSlave4*). + * + * I2C data transactions are performed at the Sample Rate, as defined in + * Register 25. The user is responsible for ensuring that I2C data transactions + * to and from each enabled Slave can be completed within a single period of the + * Sample Rate. + * + * The I2C slave access rate can be reduced relative to the Sample Rate. This + * reduced access rate is determined by I2C_MST_DLY (Register 52). Whether a + * slave's access rate is reduced relative to the Sample Rate is determined by + * I2C_MST_DELAY_CTRL (Register 103). + * + * The processing order for the slaves is fixed. The sequence followed for + * processing the slaves is Slave 0, Slave 1, Slave 2, Slave 3 and Slave 4. If a + * particular Slave is disabled it will be skipped. + * + * Each slave can either be accessed at the sample rate or at a reduced sample + * rate. In a case where some slaves are accessed at the Sample Rate and some + * slaves are accessed at the reduced rate, the sequence of accessing the slaves + * (Slave 0 to Slave 4) is still followed. However, the reduced rate slaves will + * be skipped if their access rate dictates that they should not be accessed + * during that particular cycle. For further information regarding the reduced + * access rate, please refer to Register 52. Whether a slave is accessed at the + * Sample Rate or at the reduced rate is determined by the Delay Enable bits in + * Register 103. + * + * @param slaveId Slave ID (0-3) + * @return Current address for specified slave + * @see MPU6050_RA_I2C_SLV0_ADDR + */ + uint8_t getSlaveAddress(SlaveId slaveId); + /** Set the I2C address of the specified slave (0-3). + * @param slaveId Slave ID (0-3) + * @param address New address for specified slave + * @see getSlaveAddress() + * @see MPU6050_RA_I2C_SLV0_ADDR + */ + void setSlaveAddress(SlaveId slaveId, uint8_t address); + + /** Get the active internal register for the specified slave (0-3). + * Read/write operations for this slave will be done to whatever internal + * register address is stored in this MPU register. + * + * The MPU-6050 supports a total of five slaves, but Slave 4 has unique + * characteristics, and so it has its own functions. + * + * @param slaveId Slave ID (0-3) + * @return Current active register for specified slave + * @see MPU6050_RA_I2C_SLV0_REG + */ + uint8_t getSlaveRegister(SlaveId slaveId); + + /** Set the active internal register for the specified slave (0-3). + * @param slaveId Slave ID (0-3) + * @param reg New active register for specified slave + * @see getSlaveRegister() + * @see MPU6050_RA_I2C_SLV0_REG + */ + void setSlaveRegister(SlaveId slaveId, uint8_t reg); + + /** Get the enabled value for the specified slave (0-3). + * When set to 1, this bit enables Slave 0 for data transfer operations. When + * cleared to 0, this bit disables Slave 0 from data transfer operations. + * @param slaveId Slave ID (0-3) + * @return Current enabled value for specified slave + * @see MPU6050_RA_I2C_SLV0_CTRL + */ + bool getSlaveEnabled(SlaveId slaveId); + /** Set the enabled value for the specified slave (0-3). + * @param slaveId Slave ID (0-3) + * @param enabled New enabled value for specified slave + * @see getSlaveEnabled() + * @see MPU6050_RA_I2C_SLV0_CTRL + */ + void setSlaveEnabled(SlaveId slaveId, bool enabled); + + /** Get word pair byte-swapping enabled for the specified slave (0-3). + * When set to 1, this bit enables byte swapping. When byte swapping is enabled, + * the high and low bytes of a word pair are swapped. Please refer to + * I2C_SLV0_GRP for the pairing convention of the word pairs. When cleared to 0, + * bytes transferred to and from Slave 0 will be written to EXT_SENS_DATA + * registers in the order they were transferred. + * + * @param slaveId Slave ID (0-3) + * @return Current word pair byte-swapping enabled value for specified slave + * @see MPU6050_RA_I2C_SLV0_CTRL + */ + bool getSlaveWordByteSwap(SlaveId slaveId); + + /** Set word pair byte-swapping enabled for the specified slave (0-3). + * @param slaveId Slave ID (0-3) + * @param enabled New word pair byte-swapping enabled value for specified slave + * @see getSlaveWordByteSwap() + * @see MPU6050_RA_I2C_SLV0_CTRL + */ + void setSlaveWordByteSwap(SlaveId slaveId, bool enabled); + + /** Get write mode for the specified slave (0-3). + * When set to 1, the transaction will read or write data only. When cleared to + * 0, the transaction will write a register address prior to reading or writing + * data. This should equal 0 when specifying the register address within the + * Slave device to/from which the ensuing data transaction will take place. + * + * @param slaveId Slave ID (0-3) + * @return Current write mode for specified slave (0 = register address + data, + * 1 = data only) + * @see MPU6050_RA_I2C_SLV0_CTRL + */ + bool getSlaveWriteMode(SlaveId slaveId); + /** Set write mode for the specified slave (0-3). + * @param slaveId Slave ID (0-3) + * @param mode New write mode for specified slave (0 = register address + data, + * 1 = data only) + * @see getSlaveWriteMode() + * @see MPU6050_RA_I2C_SLV0_CTRL + */ + void setSlaveWriteMode(SlaveId slaveId, bool mode); + + /** Get word pair grouping order offset for the specified slave (0-3). + * This sets specifies the grouping order of word pairs received from registers. + * When cleared to 0, bytes from register addresses 0 and 1, 2 and 3, etc (even, + * then odd register addresses) are paired to form a word. When set to 1, bytes + * from register addresses are paired 1 and 2, 3 and 4, etc. (odd, then even + * register addresses) are paired to form a word. + * + * @param slaveId Slave ID (0-3) + * @return Current word pair grouping order offset for specified slave + * @see MPU6050_RA_I2C_SLV0_CTRL + */ + bool getSlaveWordGroupOffset(SlaveId slaveId); + + /** Set word pair grouping order offset for the specified slave (0-3). + * @param slaveId Slave ID (0-3) + * @param enabled New word pair grouping order offset for specified slave + * @see getSlaveWordGroupOffset() + * @see MPU6050_RA_I2C_SLV0_CTRL + */ + void setSlaveWordGroupOffset(SlaveId slaveId, bool enabled); + + /** Get number of bytes to read for the specified slave (0-3). + * Specifies the number of bytes transferred to and from Slave 0. Clearing this + * bit to 0 is equivalent to disabling the register by writing 0 to I2C_SLV0_EN. + * @param slaveId Slave ID (0-3) + * @return Number of bytes to read for specified slave + * @see MPU6050_RA_I2C_SLV0_CTRL + */ + uint8_t getSlaveDataLength(SlaveId slaveId); + + /** Set number of bytes to read for the specified slave (0-3). + * @param slaveId Slave ID (0-3) + * @param length Number of bytes to read for specified slave + * @see getSlaveDataLength() + * @see MPU6050_RA_I2C_SLV0_CTRL + */ + void setSlaveDataLength(SlaveId slaveId, uint8_t length); + + // I2C_SLV* registers (Slave 4) + + /** Get the I2C address of Slave 4. + * Note that Bit 7 (MSB) controls read/write mode. If Bit 7 is set, it's a read + * operation, and if it is cleared, then it's a write operation. The remaining + * bits (6-0) are the 7-bit device address of the slave device. + * + * @return Current address for Slave 4 + * @see getSlaveAddress() + * @see MPU6050_RA_I2C_SLV4_ADDR + */ + uint8_t getSlave4Address() + { + return readByte(MPU6050_RA_I2C_SLV4_ADDR); + } + /** Set the I2C address of Slave 4. + * @param address New address for Slave 4 + * @see getSlave4Address() + * @see MPU6050_RA_I2C_SLV4_ADDR + */ + void setSlave4Address(uint8_t address) + { + I2Cdev::writeByte(devAddr, MPU6050_RA_I2C_SLV4_ADDR, address); + } + /** Get the active internal register for the Slave 4. + * Read/write operations for this slave will be done to whatever internal + * register address is stored in this MPU register. + * + * @return Current active register for Slave 4 + * @see MPU6050_RA_I2C_SLV4_REG + */ + uint8_t getSlave4Register() + { + return readByte(MPU6050_RA_I2C_SLV4_REG); + } + /** Set the active internal register for Slave 4. + * @param reg New active register for Slave 4 + * @see getSlave4Register() + * @see MPU6050_RA_I2C_SLV4_REG + */ + void setSlave4Register(uint8_t reg) + { + I2Cdev::writeByte(devAddr, MPU6050_RA_I2C_SLV4_REG, reg); + } + /** Set new byte to write to Slave 4. + * This register stores the data to be written into the Slave 4. If I2C_SLV4_RW + * is set 1 (set to read), this register has no effect. + * @param data New byte to write to Slave 4 + * @see MPU6050_RA_I2C_SLV4_DO + */ + void setSlave4OutputByte(uint8_t data) + { + I2Cdev::writeByte(devAddr, MPU6050_RA_I2C_SLV4_DO, data); + } + /** Get the enabled value for the Slave 4. + * When set to 1, this bit enables Slave 4 for data transfer operations. When + * cleared to 0, this bit disables Slave 4 from data transfer operations. + * @return Current enabled value for Slave 4 + * @see MPU6050_RA_I2C_SLV4_CTRL + */ + bool getSlave4Enabled() + { + return readBit(MPU6050_RA_I2C_SLV4_CTRL, MPU6050_I2C_SLV4_EN_BIT); + } + /** Set the enabled value for Slave 4. + * @param enabled New enabled value for Slave 4 + * @see getSlave4Enabled() + * @see MPU6050_RA_I2C_SLV4_CTRL + */ + void setSlave4Enabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_I2C_SLV4_CTRL, MPU6050_I2C_SLV4_EN_BIT, enabled); + } + /** Get the enabled value for Slave 4 transaction interrupts. + * When set to 1, this bit enables the generation of an interrupt signal upon + * completion of a Slave 4 transaction. When cleared to 0, this bit disables the + * generation of an interrupt signal upon completion of a Slave 4 transaction. + * The interrupt status can be observed in Register 54. + * + * @return Current enabled value for Slave 4 transaction interrupts. + * @see MPU6050_RA_I2C_SLV4_CTRL + */ + bool getSlave4InterruptEnabled() + { + return readBit(MPU6050_RA_I2C_SLV4_CTRL, MPU6050_I2C_SLV4_INT_EN_BIT); + } + /** Set the enabled value for Slave 4 transaction interrupts. + * @param enabled New enabled value for Slave 4 transaction interrupts. + * @see getSlave4InterruptEnabled() + * @see MPU6050_RA_I2C_SLV4_CTRL + */ + void setSlave4InterruptEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_I2C_SLV4_CTRL, MPU6050_I2C_SLV4_INT_EN_BIT, enabled); + } + /** Get write mode for Slave 4. + * When set to 1, the transaction will read or write data only. When cleared to + * 0, the transaction will write a register address prior to reading or writing + * data. This should equal 0 when specifying the register address within the + * Slave device to/from which the ensuing data transaction will take place. + * + * @return Current write mode for Slave 4 (0 = register address + data, 1 = data + * only) + * @see MPU6050_RA_I2C_SLV4_CTRL + */ + bool getSlave4WriteMode() + { + return readBit(MPU6050_RA_I2C_SLV4_CTRL, MPU6050_I2C_SLV4_REG_DIS_BIT); + } + /** Set write mode for the Slave 4. + * @param mode New write mode for Slave 4 (0 = register address + data, 1 = data + * only) + * @see getSlave4WriteMode() + * @see MPU6050_RA_I2C_SLV4_CTRL + */ + void setSlave4WriteMode(bool mode) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_I2C_SLV4_CTRL, MPU6050_I2C_SLV4_REG_DIS_BIT, mode); + } + /** Get Slave 4 master delay value. + * This configures the reduced access rate of I2C slaves relative to the Sample + * Rate. When a slave's access rate is decreased relative to the Sample Rate, + * the slave is accessed every: + * + * 1 / (1 + I2C_MST_DLY) samples + * + * This base Sample Rate in turn is determined by SMPLRT_DIV (register 25) and + * DLPF_CFG (register 26). Whether a slave's access rate is reduced relative to + * the Sample Rate is determined by I2C_MST_DELAY_CTRL (register 103). For + * further information regarding the Sample Rate, please refer to register 25. + * + * @return Current Slave 4 master delay value + * @see MPU6050_RA_I2C_SLV4_CTRL + */ + uint8_t getSlave4MasterDelay() + { + return readBits(MPU6050_RA_I2C_SLV4_CTRL, MPU6050_I2C_SLV4_MST_DLY_BIT, MPU6050_I2C_SLV4_MST_DLY_LENGTH); + } + /** Set Slave 4 master delay value. + * @param delay New Slave 4 master delay value + * @see getSlave4MasterDelay() + * @see MPU6050_RA_I2C_SLV4_CTRL + */ + void setSlave4MasterDelay(uint8_t delay) + { + I2Cdev::writeBits(devAddr, MPU6050_RA_I2C_SLV4_CTRL, MPU6050_I2C_SLV4_MST_DLY_BIT, + MPU6050_I2C_SLV4_MST_DLY_LENGTH, delay); + } + /** Get last available byte read from Slave 4. + * This register stores the data read from Slave 4. This field is populated + * after a read transaction. + * @return Last available byte read from to Slave 4 + * @see MPU6050_RA_I2C_SLV4_DI + */ + uint8_t getSlate4InputByte() + { + return readByte(MPU6050_RA_I2C_SLV4_DI); + } + + // I2C_MST_STATUS register + + /** Get FSYNC interrupt status. + * This bit reflects the status of the FSYNC interrupt from an external device + * into the MPU-60X0. This is used as a way to pass an external interrupt + * through the MPU-60X0 to the host application processor. When set to 1, this + * bit will cause an interrupt if FSYNC_INT_EN is asserted in INT_PIN_CFG + * (Register 55). + * @return FSYNC interrupt status + * @see MPU6050_RA_I2C_MST_STATUS + */ + bool getPassthroughStatus() + { + return readBit(MPU6050_RA_I2C_MST_STATUS, MPU6050_MST_PASS_THROUGH_BIT); + } + /** Get Slave 4 transaction done status. + * Automatically sets to 1 when a Slave 4 transaction has completed. This + * triggers an interrupt if the I2C_MST_INT_EN bit in the INT_ENABLE register + * (Register 56) is asserted and if the SLV_4_DONE_INT bit is asserted in the + * I2C_SLV4_CTRL register (Register 52). + * @return Slave 4 transaction done status + * @see MPU6050_RA_I2C_MST_STATUS + */ + bool getSlave4IsDone() + { + return readBit(MPU6050_RA_I2C_MST_STATUS, MPU6050_MST_I2C_SLV4_DONE_BIT); + } + /** Get master arbitration lost status. + * This bit automatically sets to 1 when the I2C Master has lost arbitration of + * the auxiliary I2C bus (an error condition). This triggers an interrupt if the + * I2C_MST_INT_EN bit in the INT_ENABLE register (Register 56) is asserted. + * @return Master arbitration lost status + * @see MPU6050_RA_I2C_MST_STATUS + */ + bool getLostArbitration() + { + return readBit(MPU6050_RA_I2C_MST_STATUS, MPU6050_MST_I2C_LOST_ARB_BIT); + } + /** Get Slave 4 NACK status. + * This bit automatically sets to 1 when the I2C Master receives a NACK in a + * transaction with Slave 4. This triggers an interrupt if the I2C_MST_INT_EN + * bit in the INT_ENABLE register (Register 56) is asserted. + * @return Slave 4 NACK interrupt status + * @see MPU6050_RA_I2C_MST_STATUS + */ + bool getSlave4Nack() + { + return readBit(MPU6050_RA_I2C_MST_STATUS, MPU6050_MST_I2C_SLV4_NACK_BIT); + } + /** Get Slave 3 NACK status. + * This bit automatically sets to 1 when the I2C Master receives a NACK in a + * transaction with Slave 3. This triggers an interrupt if the I2C_MST_INT_EN + * bit in the INT_ENABLE register (Register 56) is asserted. + * @return Slave 3 NACK interrupt status + * @see MPU6050_RA_I2C_MST_STATUS + */ + bool getSlave3Nack() + { + return readBit(MPU6050_RA_I2C_MST_STATUS, MPU6050_MST_I2C_SLV3_NACK_BIT); + } + /** Get Slave 2 NACK status. + * This bit automatically sets to 1 when the I2C Master receives a NACK in a + * transaction with Slave 2. This triggers an interrupt if the I2C_MST_INT_EN + * bit in the INT_ENABLE register (Register 56) is asserted. + * @return Slave 2 NACK interrupt status + * @see MPU6050_RA_I2C_MST_STATUS + */ + bool getSlave2Nack() + { + return readBit(MPU6050_RA_I2C_MST_STATUS, MPU6050_MST_I2C_SLV2_NACK_BIT); + } + /** Get Slave 1 NACK status. + * This bit automatically sets to 1 when the I2C Master receives a NACK in a + * transaction with Slave 1. This triggers an interrupt if the I2C_MST_INT_EN + * bit in the INT_ENABLE register (Register 56) is asserted. + * @return Slave 1 NACK interrupt status + * @see MPU6050_RA_I2C_MST_STATUS + */ + bool getSlave1Nack() + { + return readBit(MPU6050_RA_I2C_MST_STATUS, MPU6050_MST_I2C_SLV1_NACK_BIT); + } + /** Get Slave 0 NACK status. + * This bit automatically sets to 1 when the I2C Master receives a NACK in a + * transaction with Slave 0. This triggers an interrupt if the I2C_MST_INT_EN + * bit in the INT_ENABLE register (Register 56) is asserted. + * @return Slave 0 NACK interrupt status + * @see MPU6050_RA_I2C_MST_STATUS + */ + bool getSlave0Nack() + { + return readBit(MPU6050_RA_I2C_MST_STATUS, MPU6050_MST_I2C_SLV0_NACK_BIT); + } + + // INT_PIN_CFG register + + /** Get interrupt logic level mode. + * Will be set 0 for active-high, 1 for active-low. + * @return Current interrupt mode (0=active-high, 1=active-low) + * @see MPU6050_RA_INT_PIN_CFG + * @see MPU6050_INTCFG_INT_LEVEL_BIT + */ + bool getInterruptMode() + { + return readBit(MPU6050_RA_INT_PIN_CFG, MPU6050_INTCFG_INT_LEVEL_BIT); + } + /** Set interrupt logic level mode. + * @param mode New interrupt mode (0=active-high, 1=active-low) + * @see getInterruptMode() + * @see MPU6050_RA_INT_PIN_CFG + * @see MPU6050_INTCFG_INT_LEVEL_BIT + */ + void setInterruptMode(bool mode) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_INT_PIN_CFG, MPU6050_INTCFG_INT_LEVEL_BIT, mode); + } + /** Get interrupt drive mode. + * Will be set 0 for push-pull, 1 for open-drain. + * @return Current interrupt drive mode (0=push-pull, 1=open-drain) + * @see MPU6050_RA_INT_PIN_CFG + * @see MPU6050_INTCFG_INT_OPEN_BIT + */ + bool getInterruptDrive() + { + return readBit(MPU6050_RA_INT_PIN_CFG, MPU6050_INTCFG_INT_OPEN_BIT); + } + /** Set interrupt drive mode. + * @param drive New interrupt drive mode (0=push-pull, 1=open-drain) + * @see getInterruptDrive() + * @see MPU6050_RA_INT_PIN_CFG + * @see MPU6050_INTCFG_INT_OPEN_BIT + */ + void setInterruptDrive(bool drive) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_INT_PIN_CFG, MPU6050_INTCFG_INT_OPEN_BIT, drive); + } + /** Get interrupt latch mode. + * Will be set 0 for 50us-pulse, 1 for latch-until-int-cleared. + * @return Current latch mode (0=50us-pulse, 1=latch-until-int-cleared) + * @see MPU6050_RA_INT_PIN_CFG + * @see MPU6050_INTCFG_LATCH_INT_EN_BIT + */ + bool getInterruptLatch() + { + return readBit(MPU6050_RA_INT_PIN_CFG, MPU6050_INTCFG_LATCH_INT_EN_BIT); + } + /** Set interrupt latch mode. + * @param latch New latch mode (0=50us-pulse, 1=latch-until-int-cleared) + * @see getInterruptLatch() + * @see MPU6050_RA_INT_PIN_CFG + * @see MPU6050_INTCFG_LATCH_INT_EN_BIT + */ + void setInterruptLatch(bool latch) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_INT_PIN_CFG, MPU6050_INTCFG_LATCH_INT_EN_BIT, latch); + } + /** Get interrupt latch clear mode. + * Will be set 0 for status-read-only, 1 for any-register-read. + * @return Current latch clear mode (0=status-read-only, 1=any-register-read) + * @see MPU6050_RA_INT_PIN_CFG + * @see MPU6050_INTCFG_INT_RD_CLEAR_BIT + */ + bool getInterruptLatchClear() + { + return readBit(MPU6050_RA_INT_PIN_CFG, MPU6050_INTCFG_INT_RD_CLEAR_BIT); + } + /** Set interrupt latch clear mode. + * @param clear New latch clear mode (0=status-read-only, 1=any-register-read) + * @see getInterruptLatchClear() + * @see MPU6050_RA_INT_PIN_CFG + * @see MPU6050_INTCFG_INT_RD_CLEAR_BIT + */ + void setInterruptLatchClear(bool clear) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_INT_PIN_CFG, MPU6050_INTCFG_INT_RD_CLEAR_BIT, clear); + } + /** Get FSYNC interrupt logic level mode. + * @return Current FSYNC interrupt mode (0=active-high, 1=active-low) + * @see getFSyncInterruptMode() + * @see MPU6050_RA_INT_PIN_CFG + * @see MPU6050_INTCFG_FSYNC_INT_LEVEL_BIT + */ + bool getFSyncInterruptLevel() + { + return readBit(MPU6050_RA_INT_PIN_CFG, MPU6050_INTCFG_FSYNC_INT_LEVEL_BIT); + } + /** Set FSYNC interrupt logic level mode. + * @param mode New FSYNC interrupt mode (0=active-high, 1=active-low) + * @see getFSyncInterruptMode() + * @see MPU6050_RA_INT_PIN_CFG + * @see MPU6050_INTCFG_FSYNC_INT_LEVEL_BIT + */ + void setFSyncInterruptLevel(bool level) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_INT_PIN_CFG, MPU6050_INTCFG_FSYNC_INT_LEVEL_BIT, level); + } + /** Get FSYNC pin interrupt enabled setting. + * Will be set 0 for disabled, 1 for enabled. + * @return Current interrupt enabled setting + * @see MPU6050_RA_INT_PIN_CFG + * @see MPU6050_INTCFG_FSYNC_INT_EN_BIT + */ + bool getFSyncInterruptEnabled() + { + return readBit(MPU6050_RA_INT_PIN_CFG, MPU6050_INTCFG_FSYNC_INT_EN_BIT); + } + /** Set FSYNC pin interrupt enabled setting. + * @param enabled New FSYNC pin interrupt enabled setting + * @see getFSyncInterruptEnabled() + * @see MPU6050_RA_INT_PIN_CFG + * @see MPU6050_INTCFG_FSYNC_INT_EN_BIT + */ + void setFSyncInterruptEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_INT_PIN_CFG, MPU6050_INTCFG_FSYNC_INT_EN_BIT, enabled); + } + /** Get I2C bypass enabled status. + * When this bit is equal to 1 and I2C_MST_EN (Register 106 bit[5]) is equal to + * 0, the host application processor will be able to directly access the + * auxiliary I2C bus of the MPU-60X0. When this bit is equal to 0, the host + * application processor will not be able to directly access the auxiliary I2C + * bus of the MPU-60X0 regardless of the state of I2C_MST_EN (Register 106 + * bit[5]). + * @return Current I2C bypass enabled status + * @see MPU6050_RA_INT_PIN_CFG + * @see MPU6050_INTCFG_I2C_BYPASS_EN_BIT + */ + bool getI2CBypassEnabled() + { + return readBit(MPU6050_RA_INT_PIN_CFG, MPU6050_INTCFG_I2C_BYPASS_EN_BIT); + } + /** Set I2C bypass enabled status. + * When this bit is equal to 1 and I2C_MST_EN (Register 106 bit[5]) is equal to + * 0, the host application processor will be able to directly access the + * auxiliary I2C bus of the MPU-60X0. When this bit is equal to 0, the host + * application processor will not be able to directly access the auxiliary I2C + * bus of the MPU-60X0 regardless of the state of I2C_MST_EN (Register 106 + * bit[5]). + * @param enabled New I2C bypass enabled status + * @see MPU6050_RA_INT_PIN_CFG + * @see MPU6050_INTCFG_I2C_BYPASS_EN_BIT + */ + void setI2CBypassEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_INT_PIN_CFG, MPU6050_INTCFG_I2C_BYPASS_EN_BIT, enabled); + } + /** Get reference clock output enabled status. + * When this bit is equal to 1, a reference clock output is provided at the + * CLKOUT pin. When this bit is equal to 0, the clock output is disabled. For + * further information regarding CLKOUT, please refer to the MPU-60X0 Product + * Specification document. + * @return Current reference clock output enabled status + * @see MPU6050_RA_INT_PIN_CFG + * @see MPU6050_INTCFG_CLKOUT_EN_BIT + */ + bool getClockOutputEnabled() + { + return readBit(MPU6050_RA_INT_PIN_CFG, MPU6050_INTCFG_CLKOUT_EN_BIT); + } + /** Set reference clock output enabled status. + * When this bit is equal to 1, a reference clock output is provided at the + * CLKOUT pin. When this bit is equal to 0, the clock output is disabled. For + * further information regarding CLKOUT, please refer to the MPU-60X0 Product + * Specification document. + * @param enabled New reference clock output enabled status + * @see MPU6050_RA_INT_PIN_CFG + * @see MPU6050_INTCFG_CLKOUT_EN_BIT + */ + void setClockOutputEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_INT_PIN_CFG, MPU6050_INTCFG_CLKOUT_EN_BIT, enabled); + } + + // INT_ENABLE register + + /** Get full interrupt enabled status. + * Full register byte for all interrupts, for quick reading. Each bit will be + * set 0 for disabled, 1 for enabled. + * @return Current interrupt enabled status + * @see MPU6050_RA_INT_ENABLE + * @see MPU6050_INTERRUPT_FF_BIT + **/ + uint8_t getIntEnabled() + { + return readByte(MPU6050_RA_INT_ENABLE); + } + /** Set full interrupt enabled status. + * Full register byte for all interrupts, for quick reading. Each bit should be + * set 0 for disabled, 1 for enabled. + * @param enabled New interrupt enabled status + * @see getIntFreefallEnabled() + * @see MPU6050_RA_INT_ENABLE + * @see MPU6050_INTERRUPT_FF_BIT + **/ + void setIntEnabled(uint8_t enabled) + { + I2Cdev::writeByte(devAddr, MPU6050_RA_INT_ENABLE, enabled); + } + /** Get Free Fall interrupt enabled status. + * Will be set 0 for disabled, 1 for enabled. + * @return Current interrupt enabled status + * @see MPU6050_RA_INT_ENABLE + * @see MPU6050_INTERRUPT_FF_BIT + **/ + bool getIntFreefallEnabled() + { + return readBit(MPU6050_RA_INT_ENABLE, MPU6050_INTERRUPT_FF_BIT); + } + /** Set Free Fall interrupt enabled status. + * @param enabled New interrupt enabled status + * @see getIntFreefallEnabled() + * @see MPU6050_RA_INT_ENABLE + * @see MPU6050_INTERRUPT_FF_BIT + **/ + void setIntFreefallEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_INT_ENABLE, MPU6050_INTERRUPT_FF_BIT, enabled); + } + /** Get Motion Detection interrupt enabled status. + * Will be set 0 for disabled, 1 for enabled. + * @return Current interrupt enabled status + * @see MPU6050_RA_INT_ENABLE + * @see MPU6050_INTERRUPT_MOT_BIT + **/ + bool getIntMotionEnabled() + { + return readBit(MPU6050_RA_INT_ENABLE, MPU6050_INTERRUPT_MOT_BIT); + } + /** Set Motion Detection interrupt enabled status. + * @param enabled New interrupt enabled status + * @see getIntMotionEnabled() + * @see MPU6050_RA_INT_ENABLE + * @see MPU6050_INTERRUPT_MOT_BIT + **/ + void setIntMotionEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_INT_ENABLE, MPU6050_INTERRUPT_MOT_BIT, enabled); + } + /** Get Zero Motion Detection interrupt enabled status. + * Will be set 0 for disabled, 1 for enabled. + * @return Current interrupt enabled status + * @see MPU6050_RA_INT_ENABLE + * @see MPU6050_INTERRUPT_ZMOT_BIT + **/ + bool getIntZeroMotionEnabled() + { + return readBit(MPU6050_RA_INT_ENABLE, MPU6050_INTERRUPT_ZMOT_BIT); + } + /** Set Zero Motion Detection interrupt enabled status. + * @param enabled New interrupt enabled status + * @see getIntZeroMotionEnabled() + * @see MPU6050_RA_INT_ENABLE + * @see MPU6050_INTERRUPT_ZMOT_BIT + **/ + void setIntZeroMotionEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_INT_ENABLE, MPU6050_INTERRUPT_ZMOT_BIT, enabled); + } + /** Get FIFO Buffer Overflow interrupt enabled status. + * Will be set 0 for disabled, 1 for enabled. + * @return Current interrupt enabled status + * @see MPU6050_RA_INT_ENABLE + * @see MPU6050_INTERRUPT_FIFO_OFLOW_BIT + **/ + bool getIntFIFOBufferOverflowEnabled() + { + return readBit(MPU6050_RA_INT_ENABLE, MPU6050_INTERRUPT_FIFO_OFLOW_BIT); + } + /** Set FIFO Buffer Overflow interrupt enabled status. + * @param enabled New interrupt enabled status + * @see getIntFIFOBufferOverflowEnabled() + * @see MPU6050_RA_INT_ENABLE + * @see MPU6050_INTERRUPT_FIFO_OFLOW_BIT + **/ + void setIntFIFOBufferOverflowEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_INT_ENABLE, MPU6050_INTERRUPT_FIFO_OFLOW_BIT, enabled); + } + /** Get I2C Master interrupt enabled status. + * This enables any of the I2C Master interrupt sources to generate an + * interrupt. Will be set 0 for disabled, 1 for enabled. + * @return Current interrupt enabled status + * @see MPU6050_RA_INT_ENABLE + * @see MPU6050_INTERRUPT_I2C_MST_INT_BIT + **/ + bool getIntI2CMasterEnabled() + { + return readBit(MPU6050_RA_INT_ENABLE, MPU6050_INTERRUPT_I2C_MST_INT_BIT); + } + /** Set I2C Master interrupt enabled status. + * @param enabled New interrupt enabled status + * @see getIntI2CMasterEnabled() + * @see MPU6050_RA_INT_ENABLE + * @see MPU6050_INTERRUPT_I2C_MST_INT_BIT + **/ + void setIntI2CMasterEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_INT_ENABLE, MPU6050_INTERRUPT_I2C_MST_INT_BIT, enabled); + } + /** Get Data Ready interrupt enabled setting. + * This event occurs each time a write operation to all of the sensor registers + * has been completed. Will be set 0 for disabled, 1 for enabled. + * @return Current interrupt enabled status + * @see MPU6050_RA_INT_ENABLE + * @see MPU6050_INTERRUPT_DATA_RDY_BIT + */ + bool getIntDataReadyEnabled() + { + return readBit(MPU6050_RA_INT_ENABLE, MPU6050_INTERRUPT_DATA_RDY_BIT); + } + /** Set Data Ready interrupt enabled status. + * @param enabled New interrupt enabled status + * @see getIntDataReadyEnabled() + * @see MPU6050_RA_INT_CFG + * @see MPU6050_INTERRUPT_DATA_RDY_BIT + */ + void setIntDataReadyEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_INT_ENABLE, MPU6050_INTERRUPT_DATA_RDY_BIT, enabled); + } + + // INT_STATUS register + + /** Get full set of interrupt status bits. + * These bits clear to 0 after the register has been read. Very useful + * for getting multiple INT statuses, since each single bit read clears + * all of them because it has to read the whole byte. + * @return Current interrupt status + * @see MPU6050_RA_INT_STATUS + */ + uint8_t getIntStatus() + { + return readByte(MPU6050_RA_INT_STATUS); + } + /** Get Free Fall interrupt status. + * This bit automatically sets to 1 when a Free Fall interrupt has been + * generated. The bit clears to 0 after the register has been read. + * @return Current interrupt status + * @see MPU6050_RA_INT_STATUS + * @see MPU6050_INTERRUPT_FF_BIT + */ + bool getIntFreefallStatus() + { + return readBit(MPU6050_RA_INT_STATUS, MPU6050_INTERRUPT_FF_BIT); + } + /** Get Motion Detection interrupt status. + * This bit automatically sets to 1 when a Motion Detection interrupt has been + * generated. The bit clears to 0 after the register has been read. + * @return Current interrupt status + * @see MPU6050_RA_INT_STATUS + * @see MPU6050_INTERRUPT_MOT_BIT + */ + bool getIntMotionStatus() + { + return readBit(MPU6050_RA_INT_STATUS, MPU6050_INTERRUPT_MOT_BIT); + } + + /** Get Zero Motion Detection interrupt status. + * This bit automatically sets to 1 when a Zero Motion Detection interrupt has + * been generated. The bit clears to 0 after the register has been read. + * @return Current interrupt status + * @see MPU6050_RA_INT_STATUS + * @see MPU6050_INTERRUPT_ZMOT_BIT + */ + bool getIntZeroMotionStatus() + { + return readBit(MPU6050_RA_INT_STATUS, MPU6050_INTERRUPT_ZMOT_BIT); + } + + /** Get FIFO Buffer Overflow interrupt status. + * This bit automatically sets to 1 when a Free Fall interrupt has been + * generated. The bit clears to 0 after the register has been read. + * @return Current interrupt status + * @see MPU6050_RA_INT_STATUS + * @see MPU6050_INTERRUPT_FIFO_OFLOW_BIT + */ + bool getIntFIFOBufferOverflowStatus() + { + return readBit(MPU6050_RA_INT_STATUS, MPU6050_INTERRUPT_FIFO_OFLOW_BIT); + } + + /** Get I2C Master interrupt status. + * This bit automatically sets to 1 when an I2C Master interrupt has been + * generated. For a list of I2C Master interrupts, please refer to Register 54. + * The bit clears to 0 after the register has been read. + * @return Current interrupt status + * @see MPU6050_RA_INT_STATUS + * @see MPU6050_INTERRUPT_I2C_MST_INT_BIT + */ + bool getIntI2CMasterStatus() + { + return readBit(MPU6050_RA_INT_STATUS, MPU6050_INTERRUPT_I2C_MST_INT_BIT); + } + + /** Get Data Ready interrupt status. + * This bit automatically sets to 1 when a Data Ready interrupt has been + * generated. The bit clears to 0 after the register has been read. + * @return Current interrupt status + * @see MPU6050_RA_INT_STATUS + * @see MPU6050_INTERRUPT_DATA_RDY_BIT + */ + bool getIntDataReadyStatus() + { + return readBit(MPU6050_RA_INT_STATUS, MPU6050_INTERRUPT_DATA_RDY_BIT); + } + + // ACCEL_*OUT_* registers + + /** Get raw 6-axis motion sensor readings (accel/gyro). + * Retrieves all currently available motion sensor values. + * @return container for 3-axis accelerometer and 3-axis gyroscope values + * @see getAcceleration() + * @see getAngularRate() + * @see MPU6050_RA_ACCEL_XOUT_H + */ + Motion6 getMotion6(); + + /** Get 3-axis accelerometer readings. + * These registers store the most recent accelerometer measurements. + * Accelerometer measurements are written to these registers at the Sample Rate + * as defined in Register 25. + * + * The accelerometer measurement registers, along with the temperature + * measurement registers, gyroscope measurement registers, and external sensor + * data registers, are composed of two sets of registers: an internal register + * set and a user-facing read register set. + * + * The data within the accelerometer sensors' internal register set is always + * updated at the Sample Rate. Meanwhile, the user-facing read register set + * duplicates the internal register set's data values whenever the serial + * interface is idle. This guarantees that a burst read of sensor registers will + * read measurements from the same sampling instant. Note that if burst reads + * are not used, the user is responsible for ensuring a set of single byte reads + * correspond to a single sampling instant by checking the Data Ready interrupt. + * + * Each 16-bit accelerometer measurement has a full scale defined in ACCEL_FS + * (Register 28). For each full scale setting, the accelerometers' sensitivity + * per LSB in ACCEL_xOUT is shown in the table below: + * + *
+     * AFS_SEL | Full Scale Range | LSB Sensitivity
+     * --------+------------------+----------------
+     * 0       | +/- 2g           | 8192 LSB/mg
+     * 1       | +/- 4g           | 4096 LSB/mg
+     * 2       | +/- 8g           | 2048 LSB/mg
+     * 3       | +/- 16g          | 1024 LSB/mg
+     * 
+ * + * @param x 16-bit signed integer container for X-axis acceleration + * @param y 16-bit signed integer container for Y-axis acceleration + * @param z 16-bit signed integer container for Z-axis acceleration + * @see MPU6050_RA_GYRO_XOUT_H + */ + Motion3 getAcceleration(); + + /** Get X-axis accelerometer reading. + * @return X-axis acceleration measurement in 16-bit 2's complement format + * @see getMotion6() + * @see MPU6050_RA_ACCEL_XOUT_H + */ + int16_t getAccelerationX() + { + return readReg(MPU6050_RA_ACCEL_XOUT_H); + } + /** Get Y-axis accelerometer reading. + * @return Y-axis acceleration measurement in 16-bit 2's complement format + * @see getMotion6() + * @see MPU6050_RA_ACCEL_YOUT_H + */ + int16_t getAccelerationY() + { + return readReg(MPU6050_RA_ACCEL_YOUT_H); + } + /** Get Z-axis accelerometer reading. + * @return Z-axis acceleration measurement in 16-bit 2's complement format + * @see getMotion6() + * @see MPU6050_RA_ACCEL_ZOUT_H + */ + int16_t getAccelerationZ() + { + return readReg(MPU6050_RA_ACCEL_ZOUT_H); + } + + // TEMP_OUT_* registers + + /** Get current internal temperature. + * @return Temperature reading in 16-bit 2's complement format + * @see MPU6050_RA_TEMP_OUT_H + */ + int16_t getTemperature() + { + return readReg(MPU6050_RA_TEMP_OUT_H); + } + + // GYRO_*OUT_* registers + + /** Get 3-axis gyroscope readings. + * These gyroscope measurement registers, along with the accelerometer + * measurement registers, temperature measurement registers, and external sensor + * data registers, are composed of two sets of registers: an internal register + * set and a user-facing read register set. + * The data within the gyroscope sensors' internal register set is always + * updated at the Sample Rate. Meanwhile, the user-facing read register set + * duplicates the internal register set's data values whenever the serial + * interface is idle. This guarantees that a burst read of sensor registers will + * read measurements from the same sampling instant. Note that if burst reads + * are not used, the user is responsible for ensuring a set of single byte reads + * correspond to a single sampling instant by checking the Data Ready interrupt. + * + * Each 16-bit gyroscope measurement has a full scale defined in FS_SEL + * (Register 27). For each full scale setting, the gyroscopes' sensitivity per + * LSB in GYRO_xOUT is shown in the table below: + * + *
+     * FS_SEL | Full Scale Range   | LSB Sensitivity
+     * -------+--------------------+----------------
+     * 0      | +/- 250 degrees/s  | 131 LSB/deg/s
+     * 1      | +/- 500 degrees/s  | 65.5 LSB/deg/s
+     * 2      | +/- 1000 degrees/s | 32.8 LSB/deg/s
+     * 3      | +/- 2000 degrees/s | 16.4 LSB/deg/s
+     * 
+ * + * @return container for 3-axis gyro values + * @see getMotion6() + * @see MPU6050_RA_GYRO_XOUT_H + */ + Motion3 getAngularRate(); + + /** Get X-axis gyroscope reading. + * @return X-axis rotation measurement in 16-bit 2's complement format + * @see getMotion6() + * @see MPU6050_RA_GYRO_XOUT_H + */ + int16_t getAngularRateX() + { + return readReg(MPU6050_RA_GYRO_XOUT_H); + } + /** Get Y-axis gyroscope reading. + * @return Y-axis rotation measurement in 16-bit 2's complement format + * @see getMotion6() + * @see MPU6050_RA_GYRO_YOUT_H + */ + int16_t getAngularRateY() + { + return readReg(MPU6050_RA_GYRO_YOUT_H); + } + /** Get Z-axis gyroscope reading. + * @return Z-axis rotation measurement in 16-bit 2's complement format + * @see getMotion6() + * @see MPU6050_RA_GYRO_ZOUT_H + */ + int16_t getAngularRateZ() + { + return readReg(MPU6050_RA_GYRO_ZOUT_H); + } + + int16_t getAngularRateZ2() + { + return readReg(MPU6050_RA_GYRO_ZOUT_H); + } + + // EXT_SENS_DATA_* registers + + /** Read single byte from external sensor data register. + * These registers store data read from external sensors by the Slave 0, 1, 2, + * and 3 on the auxiliary I2C interface. Data read by Slave 4 is stored in + * I2C_SLV4_DI (Register 53). + * + * External sensor data is written to these registers at the Sample Rate as + * defined in Register 25. This access rate can be reduced by using the Slave + * Delay Enable registers (Register 103). + * + * External sensor data registers, along with the gyroscope measurement + * registers, accelerometer measurement registers, and temperature measurement + * registers, are composed of two sets of registers: an internal register set + * and a user-facing read register set. + * + * The data within the external sensors' internal register set is always updated + * at the Sample Rate (or the reduced access rate) whenever the serial interface + * is idle. This guarantees that a burst read of sensor registers will read + * measurements from the same sampling instant. Note that if burst reads are not + * used, the user is responsible for ensuring a set of single byte reads + * correspond to a single sampling instant by checking the Data Ready interrupt. + * + * Data is placed in these external sensor data registers according to + * I2C_SLV0_CTRL, I2C_SLV1_CTRL, I2C_SLV2_CTRL, and I2C_SLV3_CTRL (Registers 39, + * 42, 45, and 48). When more than zero bytes are read (I2C_SLVx_LEN > 0) from + * an enabled slave (I2C_SLVx_EN = 1), the slave is read at the Sample Rate (as + * defined in Register 25) or delayed rate (if specified in Register 52 and + * 103). During each Sample cycle, slave reads are performed in order of Slave + * number. If all slaves are enabled with more than zero bytes to be read, the + * order will be Slave 0, followed by Slave 1, Slave 2, and Slave 3. + * + * Each enabled slave will have EXT_SENS_DATA registers associated with it by + * number of bytes read (I2C_SLVx_LEN) in order of slave number, starting from + * EXT_SENS_DATA_00. Note that this means enabling or disabling a slave may + * change the higher numbered slaves' associated registers. Furthermore, if + * fewer total bytes are being read from the external sensors as a result of + * such a change, then the data remaining in the registers which no longer have + * an associated slave device (i.e. high numbered registers) will remain in + * these previously allocated registers unless reset. + * + * If the sum of the read lengths of all SLVx transactions exceed the number of + * available EXT_SENS_DATA registers, the excess bytes will be dropped. There + * are 24 EXT_SENS_DATA registers and hence the total read lengths between all + * the slaves cannot be greater than 24 or some bytes will be lost. + * + * Note: Slave 4's behavior is distinct from that of Slaves 0-3. For further + * information regarding the characteristics of Slave 4, please refer to + * Registers 49 to 53. + * + * EXAMPLE: + * Suppose that Slave 0 is enabled with 4 bytes to be read (I2C_SLV0_EN = 1 and + * I2C_SLV0_LEN = 4) while Slave 1 is enabled with 2 bytes to be read so that + * I2C_SLV1_EN = 1 and I2C_SLV1_LEN = 2. In such a situation, EXT_SENS_DATA _00 + * through _03 will be associated with Slave 0, while EXT_SENS_DATA _04 and 05 + * will be associated with Slave 1. If Slave 2 is enabled as well, registers + * starting from EXT_SENS_DATA_06 will be allocated to Slave 2. + * + * If Slave 2 is disabled while Slave 3 is enabled in this same situation, then + * registers starting from EXT_SENS_DATA_06 will be allocated to Slave 3 + * instead. + * + * REGISTER ALLOCATION FOR DYNAMIC DISABLE VS. NORMAL DISABLE: + * If a slave is disabled at any time, the space initially allocated to the + * slave in the EXT_SENS_DATA register, will remain associated with that slave. + * This is to avoid dynamic adjustment of the register allocation. + * + * The allocation of the EXT_SENS_DATA registers is recomputed only when (1) all + * slaves are disabled, or (2) the I2C_MST_RST bit is set (Register 106). + * + * This above is also true if one of the slaves gets NACKed and stops + * functioning. + * + * @param position Starting position (0-23) + * @return Byte read from register + */ + uint8_t getExternalSensorByte(int position) + { + return readByte(MPU6050_RA_EXT_SENS_DATA_00 + position); + } + /** Read word (2 bytes) from external sensor data registers. + * @param position Starting position (0-21) + * @return Word read from register + * @see getExternalSensorByte() + */ + uint16_t getExternalSensorWord(int position) + { + return readReg(MPU6050_RA_EXT_SENS_DATA_00 + position); + } + /** Read double word (4 bytes) from external sensor data registers. + * @param position Starting position (0-20) + * @return Double word read from registers + * @see getExternalSensorByte() + */ + uint32_t getExternalSensorDWord(int position) + { + return readReg(MPU6050_RA_EXT_SENS_DATA_00 + position); + } + + // MOT_DETECT_STATUS register + + /** Get full motion detection status register content (all bits). + * @return Motion detection status byte + * @see MPU6050_RA_MOT_DETECT_STATUS + */ + uint8_t getMotionStatus() + { + return readByte(MPU6050_RA_MOT_DETECT_STATUS); + } + /** Get X-axis negative motion detection interrupt status. + * @return Motion detection status + * @see MPU6050_RA_MOT_DETECT_STATUS + * @see MPU6050_MOTION_MOT_XNEG_BIT + */ + bool getXNegMotionDetected() + { + return readBit(MPU6050_RA_MOT_DETECT_STATUS, MPU6050_MOTION_MOT_XNEG_BIT); + } + /** Get X-axis positive motion detection interrupt status. + * @return Motion detection status + * @see MPU6050_RA_MOT_DETECT_STATUS + * @see MPU6050_MOTION_MOT_XPOS_BIT + */ + bool getXPosMotionDetected() + { + return readBit(MPU6050_RA_MOT_DETECT_STATUS, MPU6050_MOTION_MOT_XPOS_BIT); + } + /** Get Y-axis negative motion detection interrupt status. + * @return Motion detection status + * @see MPU6050_RA_MOT_DETECT_STATUS + * @see MPU6050_MOTION_MOT_YNEG_BIT + */ + bool getYNegMotionDetected() + { + return readBit(MPU6050_RA_MOT_DETECT_STATUS, MPU6050_MOTION_MOT_YNEG_BIT); + } + /** Get Y-axis positive motion detection interrupt status. + * @return Motion detection status + * @see MPU6050_RA_MOT_DETECT_STATUS + * @see MPU6050_MOTION_MOT_YPOS_BIT + */ + bool getYPosMotionDetected() + { + return readBit(MPU6050_RA_MOT_DETECT_STATUS, MPU6050_MOTION_MOT_YPOS_BIT); + } + /** Get Z-axis negative motion detection interrupt status. + * @return Motion detection status + * @see MPU6050_RA_MOT_DETECT_STATUS + * @see MPU6050_MOTION_MOT_ZNEG_BIT + */ + bool getZNegMotionDetected() + { + return readBit(MPU6050_RA_MOT_DETECT_STATUS, MPU6050_MOTION_MOT_ZNEG_BIT); + } + /** Get Z-axis positive motion detection interrupt status. + * @return Motion detection status + * @see MPU6050_RA_MOT_DETECT_STATUS + * @see MPU6050_MOTION_MOT_ZPOS_BIT + */ + bool getZPosMotionDetected() + { + return readBit(MPU6050_RA_MOT_DETECT_STATUS, MPU6050_MOTION_MOT_ZPOS_BIT); + } + /** Get zero motion detection interrupt status. + * @return Motion detection status + * @see MPU6050_RA_MOT_DETECT_STATUS + * @see MPU6050_MOTION_MOT_ZRMOT_BIT + */ + bool getZeroMotionDetected() + { + return readBit(MPU6050_RA_MOT_DETECT_STATUS, MPU6050_MOTION_MOT_ZRMOT_BIT); + } + + // I2C_SLV*_DO register + + /** Write byte to Data Output container for specified slave. + * This register holds the output data written into Slave when Slave is set to + * write mode. For further information regarding Slave control, please + * refer to Registers 37 to 39 and immediately following. + * @param slaveId Slave ID (0-3) + * @param data Byte to write + * @see MPU6050_RA_I2C_SLV0_DO + */ + void setSlaveOutputByte(SlaveId slaveId, uint8_t data); + + // I2C_MST_DELAY_CTRL register + /** Get external data shadow delay enabled status. + * This register is used to specify the timing of external sensor data + * shadowing. When DELAY_ES_SHADOW is set to 1, shadowing of external + * sensor data is delayed until all data has been received. + * @return Current external data shadow delay enabled status. + * @see MPU6050_RA_I2C_MST_DELAY_CTRL + * @see MPU6050_DELAYCTRL_DELAY_ES_SHADOW_BIT + */ + bool getExternalShadowDelayEnabled() + { + return readBit(MPU6050_RA_I2C_MST_DELAY_CTRL, MPU6050_DELAYCTRL_DELAY_ES_SHADOW_BIT); + } + /** Set external data shadow delay enabled status. + * @param enabled New external data shadow delay enabled status. + * @see getExternalShadowDelayEnabled() + * @see MPU6050_RA_I2C_MST_DELAY_CTRL + * @see MPU6050_DELAYCTRL_DELAY_ES_SHADOW_BIT + */ + void setExternalShadowDelayEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_I2C_MST_DELAY_CTRL, MPU6050_DELAYCTRL_DELAY_ES_SHADOW_BIT, enabled); + } + + /** Get slave delay enabled status. + * When a particular slave delay is enabled, the rate of access for the that + * slave device is reduced. When a slave's access rate is decreased relative to + * the Sample Rate, the slave is accessed every: + * + * 1 / (1 + I2C_MST_DLY) Samples + * + * This base Sample Rate in turn is determined by SMPLRT_DIV (register * 25) + * and DLPF_CFG (register 26). + * + * For further information regarding I2C_MST_DLY, please refer to register 52. + * For further information regarding the Sample Rate, please refer to + * register 25. + * + * @param slaveId Slave ID (0-4) + * @return Current slave delay enabled status. + * @see MPU6050_RA_I2C_MST_DELAY_CTRL + * @see MPU6050_DELAYCTRL_I2C_SLV0_DLY_EN_BIT + */ + bool getSlaveDelayEnabled(SlaveId slaveId); + + /** Set slave delay enabled status. + * @param slaveId Slave ID (0-4) + * @param enabled New slave delay enabled status. + * @see MPU6050_RA_I2C_MST_DELAY_CTRL + * @see MPU6050_DELAYCTRL_I2C_SLV0_DLY_EN_BIT + */ + void setSlaveDelayEnabled(SlaveId slaveId, bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_I2C_MST_DELAY_CTRL, slaveId, enabled); + } + + // SIGNAL_PATH_RESET register + + /** Reset gyroscope signal path. + * The reset will revert the signal path analog to digital converters and + * filters to their power up configurations. + * @see MPU6050_RA_SIGNAL_PATH_RESET + * @see MPU6050_PATHRESET_GYRO_RESET_BIT + */ + void resetGyroscopePath() + { + I2Cdev::writeBit(devAddr, MPU6050_RA_SIGNAL_PATH_RESET, MPU6050_PATHRESET_GYRO_RESET_BIT, true); + } + /** Reset accelerometer signal path. + * The reset will revert the signal path analog to digital converters and + * filters to their power up configurations. + * @see MPU6050_RA_SIGNAL_PATH_RESET + * @see MPU6050_PATHRESET_ACCEL_RESET_BIT + */ + void resetAccelerometerPath() + { + I2Cdev::writeBit(devAddr, MPU6050_RA_SIGNAL_PATH_RESET, MPU6050_PATHRESET_ACCEL_RESET_BIT, true); + } + /** Reset temperature sensor signal path. + * The reset will revert the signal path analog to digital converters and + * filters to their power up configurations. + * @see MPU6050_RA_SIGNAL_PATH_RESET + * @see MPU6050_PATHRESET_TEMP_RESET_BIT + */ + void resetTemperaturePath() + { + I2Cdev::writeBit(devAddr, MPU6050_RA_SIGNAL_PATH_RESET, MPU6050_PATHRESET_TEMP_RESET_BIT, true); + } + + // MOT_DETECT_CTRL register + + /** Get accelerometer power-on delay. + * The accelerometer data path provides samples to the sensor registers, Motion + * detection, Zero Motion detection, and Free Fall detection modules. The + * signal path contains filters which must be flushed on wake-up with new + * samples before the detection modules begin operations. The default wake-up + * delay, of 4ms can be lengthened by up to 3ms. This additional delay is + * specified in ACCEL_ON_DELAY in units of 1 LSB = 1 ms. The user may select + * any value above zero unless instructed otherwise by InvenSense. Please refer + * to Section 8 of the MPU-6000/MPU-6050 Product Specification document for + * further information regarding the detection modules. + * @return Current accelerometer power-on delay + * @see MPU6050_RA_MOT_DETECT_CTRL + * @see MPU6050_DETECT_ACCEL_ON_DELAY_BIT + */ + uint8_t getAccelerometerPowerOnDelay() + { + return readBits(MPU6050_RA_MOT_DETECT_CTRL, MPU6050_DETECT_ACCEL_ON_DELAY_BIT, + MPU6050_DETECT_ACCEL_ON_DELAY_LENGTH); + } + /** Set accelerometer power-on delay. + * @param delay New accelerometer power-on delay (0-3) + * @see getAccelerometerPowerOnDelay() + * @see MPU6050_RA_MOT_DETECT_CTRL + * @see MPU6050_DETECT_ACCEL_ON_DELAY_BIT + */ + void setAccelerometerPowerOnDelay(uint8_t delay) + { + I2Cdev::writeBits(devAddr, MPU6050_RA_MOT_DETECT_CTRL, MPU6050_DETECT_ACCEL_ON_DELAY_BIT, + MPU6050_DETECT_ACCEL_ON_DELAY_LENGTH, delay); + } + /** Get Free Fall detection counter decrement configuration. + * Detection is registered by the Free Fall detection module after accelerometer + * measurements meet their respective threshold conditions over a specified + * number of samples. When the threshold conditions are met, the corresponding + * detection counter increments by 1. The user may control the rate at which the + * detection counter decrements when the threshold condition is not met by + * configuring FF_COUNT. The decrement rate can be set according to the + * following table: + * + *
+     * FF_COUNT | Counter Decrement
+     * ---------+------------------
+     * 0        | Reset
+     * 1        | 1
+     * 2        | 2
+     * 3        | 4
+     * 
+ * + * When FF_COUNT is configured to 0 (reset), any non-qualifying sample will + * reset the counter to 0. For further information on Free Fall detection, + * please refer to Registers 29 to 32. + * + * @return Current decrement configuration + * @see MPU6050_RA_MOT_DETECT_CTRL + * @see MPU6050_DETECT_FF_COUNT_BIT + */ + uint8_t getFreefallDetectionCounterDecrement() + { + return readBits(MPU6050_RA_MOT_DETECT_CTRL, MPU6050_DETECT_FF_COUNT_BIT, MPU6050_DETECT_FF_COUNT_LENGTH); + } + /** Set Free Fall detection counter decrement configuration. + * @param decrement New decrement configuration value + * @see getFreefallDetectionCounterDecrement() + * @see MPU6050_RA_MOT_DETECT_CTRL + * @see MPU6050_DETECT_FF_COUNT_BIT + */ + void setFreefallDetectionCounterDecrement(uint8_t decrement) + { + I2Cdev::writeBits(devAddr, MPU6050_RA_MOT_DETECT_CTRL, MPU6050_DETECT_FF_COUNT_BIT, + MPU6050_DETECT_FF_COUNT_LENGTH, decrement); + } + /** Get Motion detection counter decrement configuration. + * Detection is registered by the Motion detection module after accelerometer + * measurements meet their respective threshold conditions over a specified + * number of samples. When the threshold conditions are met, the corresponding + * detection counter increments by 1. The user may control the rate at which the + * detection counter decrements when the threshold condition is not met by + * configuring MOT_COUNT. The decrement rate can be set according to the + * following table: + * + *
+     * MOT_COUNT | Counter Decrement
+     * ----------+------------------
+     * 0         | Reset
+     * 1         | 1
+     * 2         | 2
+     * 3         | 4
+     * 
+ * + * When MOT_COUNT is configured to 0 (reset), any non-qualifying sample will + * reset the counter to 0. For further information on Motion detection, + * please refer to Registers 29 to 32. + * + */ + uint8_t getMotionDetectionCounterDecrement() + { + return readBits(MPU6050_RA_MOT_DETECT_CTRL, MPU6050_DETECT_MOT_COUNT_BIT, MPU6050_DETECT_MOT_COUNT_LENGTH); + } + /** Set Motion detection counter decrement configuration. + * @param decrement New decrement configuration value + * @see getMotionDetectionCounterDecrement() + * @see MPU6050_RA_MOT_DETECT_CTRL + * @see MPU6050_DETECT_MOT_COUNT_BIT + */ + void setMotionDetectionCounterDecrement(uint8_t decrement) + { + I2Cdev::writeBits(devAddr, MPU6050_RA_MOT_DETECT_CTRL, MPU6050_DETECT_MOT_COUNT_BIT, + MPU6050_DETECT_MOT_COUNT_LENGTH, decrement); + } + + // USER_CTRL register + + /** Get FIFO enabled status. + * When this bit is set to 0, the FIFO buffer is disabled. The FIFO buffer + * cannot be written to or read from while disabled. The FIFO buffer's state + * does not change unless the MPU-60X0 is power cycled. + * @return Current FIFO enabled status + * @see MPU6050_RA_USER_CTRL + * @see MPU6050_USERCTRL_FIFO_EN_BIT + */ + bool getFIFOEnabled() + { + return readBit(MPU6050_RA_USER_CTRL, MPU6050_USERCTRL_FIFO_EN_BIT); + } + /** Set FIFO enabled status. + * @param enabled New FIFO enabled status + * @see getFIFOEnabled() + * @see MPU6050_RA_USER_CTRL + * @see MPU6050_USERCTRL_FIFO_EN_BIT + */ + void setFIFOEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_USER_CTRL, MPU6050_USERCTRL_FIFO_EN_BIT, enabled); + } + /** Get I2C Master Mode enabled status. + * When this mode is enabled, the MPU-60X0 acts as the I2C Master to the + * external sensor slave devices on the auxiliary I2C bus. When this bit is + * cleared to 0, the auxiliary I2C bus lines (AUX_DA and AUX_CL) are logically + * driven by the primary I2C bus (SDA and SCL). This is a precondition to + * enabling Bypass Mode. For further information regarding Bypass Mode, please + * refer to Register 55. + * @return Current I2C Master Mode enabled status + * @see MPU6050_RA_USER_CTRL + * @see MPU6050_USERCTRL_I2C_MST_EN_BIT + */ + bool getI2CMasterModeEnabled() + { + return readBit(MPU6050_RA_USER_CTRL, MPU6050_USERCTRL_I2C_MST_EN_BIT); + } + /** Set I2C Master Mode enabled status. + * @param enabled New I2C Master Mode enabled status + * @see getI2CMasterModeEnabled() + * @see MPU6050_RA_USER_CTRL + * @see MPU6050_USERCTRL_I2C_MST_EN_BIT + */ + void setI2CMasterModeEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_USER_CTRL, MPU6050_USERCTRL_I2C_MST_EN_BIT, enabled); + } + /** Switch from I2C to SPI mode (MPU-6000 only) + * If this is set, the primary SPI interface will be enabled in place of the + * disabled primary I2C interface. + */ + void switchSPIEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_USER_CTRL, MPU6050_USERCTRL_I2C_IF_DIS_BIT, enabled); + } + /** Reset the FIFO. + * This bit resets the FIFO buffer when set to 1 while FIFO_EN equals 0. This + * bit automatically clears to 0 after the reset has been triggered. + * @see MPU6050_RA_USER_CTRL + * @see MPU6050_USERCTRL_FIFO_RESET_BIT + */ + void resetFIFO() + { + I2Cdev::writeBit(devAddr, MPU6050_RA_USER_CTRL, MPU6050_USERCTRL_FIFO_RESET_BIT, true); + } + /** Reset the I2C Master. + * This bit resets the I2C Master when set to 1 while I2C_MST_EN equals 0. + * This bit automatically clears to 0 after the reset has been triggered. + * @see MPU6050_RA_USER_CTRL + * @see MPU6050_USERCTRL_I2C_MST_RESET_BIT + */ + void resetI2CMaster() + { + I2Cdev::writeBit(devAddr, MPU6050_RA_USER_CTRL, MPU6050_USERCTRL_I2C_MST_RESET_BIT, true); + } + /** Reset all sensor registers and signal paths. + * When set to 1, this bit resets the signal paths for all sensors (gyroscopes, + * accelerometers, and temperature sensor). This operation will also clear the + * sensor registers. This bit automatically clears to 0 after the reset has been + * triggered. + * + * When resetting only the signal path (and not the sensor registers), please + * use Register 104, SIGNAL_PATH_RESET. + * + * @see MPU6050_RA_USER_CTRL + * @see MPU6050_USERCTRL_SIG_COND_RESET_BIT + */ + void resetSensors() + { + I2Cdev::writeBit(devAddr, MPU6050_RA_USER_CTRL, MPU6050_USERCTRL_SIG_COND_RESET_BIT, true); + } + + // PWR_MGMT_1 register + + /** Trigger a full device reset. + * A small delay of ~50ms may be desirable after triggering a reset. + * @see MPU6050_RA_PWR_MGMT_1 + * @see MPU6050_PWR1_DEVICE_RESET_BIT + */ + void reset() + { + I2Cdev::writeBit(devAddr, MPU6050_RA_PWR_MGMT_1, MPU6050_PWR1_DEVICE_RESET_BIT, true); + } + /** Get sleep mode status. + * Setting the SLEEP bit in the register puts the device into very low power + * sleep mode. In this mode, only the serial interface and internal registers + * remain active, allowing for a very low standby current. Clearing this bit + * puts the device back into normal mode. To save power, the individual standby + * selections for each of the gyros should be used if any gyro axis is not used + * by the application. + * @return Current sleep mode enabled status + * @see MPU6050_RA_PWR_MGMT_1 + * @see MPU6050_PWR1_SLEEP_BIT + */ + bool getSleepEnabled() + { + return readBit(MPU6050_RA_PWR_MGMT_1, MPU6050_PWR1_SLEEP_BIT); + } + /** Set sleep mode status. + * @param enabled New sleep mode enabled status + * @see getSleepEnabled() + * @see MPU6050_RA_PWR_MGMT_1 + * @see MPU6050_PWR1_SLEEP_BIT + */ + void setSleepEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_PWR_MGMT_1, MPU6050_PWR1_SLEEP_BIT, enabled); + } + /** Get wake cycle enabled status. + * When this bit is set to 1 and SLEEP is disabled, the MPU-60X0 will cycle + * between sleep mode and waking up to take a single sample of data from active + * sensors at a rate determined by LP_WAKE_CTRL (register 108). + * @return Current sleep mode enabled status + * @see MPU6050_RA_PWR_MGMT_1 + * @see MPU6050_PWR1_CYCLE_BIT + */ + bool getWakeCycleEnabled() + { + return readBit(MPU6050_RA_PWR_MGMT_1, MPU6050_PWR1_CYCLE_BIT); + } + /** Set wake cycle enabled status. + * @param enabled New sleep mode enabled status + * @see getWakeCycleEnabled() + * @see MPU6050_RA_PWR_MGMT_1 + * @see MPU6050_PWR1_CYCLE_BIT + */ + void setWakeCycleEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_PWR_MGMT_1, MPU6050_PWR1_CYCLE_BIT, enabled); + } + /** Get temperature sensor enabled status. + * Control the usage of the internal temperature sensor. + * + * Note: this register stores the *disabled* value, but for consistency with the + * rest of the code, the function is named and used with standard true/false + * values to indicate whether the sensor is enabled or disabled, respectively. + * + * @return Current temperature sensor enabled status + * @see MPU6050_RA_PWR_MGMT_1 + * @see MPU6050_PWR1_TEMP_DIS_BIT + */ + bool getTempSensorEnabled() + { + return readBit(MPU6050_RA_PWR_MGMT_1, MPU6050_PWR1_TEMP_DIS_BIT); // 1 is actually disabled here + } + /** Set temperature sensor enabled status. + * Note: this register stores the *disabled* value, but for consistency with the + * rest of the code, the function is named and used with standard true/false + * values to indicate whether the sensor is enabled or disabled, respectively. + * + * @param enabled New temperature sensor enabled status + * @see getTempSensorEnabled() + * @see MPU6050_RA_PWR_MGMT_1 + * @see MPU6050_PWR1_TEMP_DIS_BIT + */ + void setTempSensorEnabled(bool enabled) + { + // 1 is actually disabled here + I2Cdev::writeBit(devAddr, MPU6050_RA_PWR_MGMT_1, MPU6050_PWR1_TEMP_DIS_BIT, !enabled); + } + /** Get clock source setting. + * @return Current clock source setting + * @see MPU6050_RA_PWR_MGMT_1 + * @see MPU6050_PWR1_CLKSEL_BIT + * @see MPU6050_PWR1_CLKSEL_LENGTH + */ + uint8_t getClockSource() + { + return readBits(MPU6050_RA_PWR_MGMT_1, MPU6050_PWR1_CLKSEL_BIT, MPU6050_PWR1_CLKSEL_LENGTH); + } + /** Set clock source setting. + * An internal 8MHz oscillator, gyroscope based clock, or external sources can + * be selected as the MPU-60X0 clock source. When the internal 8 MHz oscillator + * or an external source is chosen as the clock source, the MPU-60X0 can operate + * in low power modes with the gyroscopes disabled. + * + * Upon power up, the MPU-60X0 clock source defaults to the internal oscillator. + * However, it is highly recommended that the device be configured to use one of + * the gyroscopes (or an external clock source) as the clock reference for + * improved stability. The clock source can be selected according to the + * following table: + * + *
+     * CLK_SEL | Clock Source
+     * --------+--------------------------------------
+     * 0       | Internal oscillator
+     * 1       | PLL with X Gyro reference
+     * 2       | PLL with Y Gyro reference
+     * 3       | PLL with Z Gyro reference
+     * 4       | PLL with external 32.768kHz reference
+     * 5       | PLL with external 19.2MHz reference
+     * 6       | Reserved
+     * 7       | Stops the clock and keeps the timing generator in reset
+     * 
+ * + * @param source New clock source setting + * @see getClockSource() + * @see MPU6050_RA_PWR_MGMT_1 + * @see MPU6050_PWR1_CLKSEL_BIT + * @see MPU6050_PWR1_CLKSEL_LENGTH + */ + void setClockSource(uint8_t source) + { + I2Cdev::writeBits(devAddr, MPU6050_RA_PWR_MGMT_1, MPU6050_PWR1_CLKSEL_BIT, MPU6050_PWR1_CLKSEL_LENGTH, source); + } + + // PWR_MGMT_2 register + + /** Get wake frequency in Accel-Only Low Power Mode. + * The MPU-60X0 can be put into Accerlerometer Only Low Power Mode by setting + * PWRSEL to 1 in the Power Management 1 register (Register 107). In this mode, + * the device will power off all devices except for the primary I2C interface, + * waking only the accelerometer at fixed intervals to take a single + * measurement. The frequency of wake-ups can be configured with LP_WAKE_CTRL + * as shown below: + * + *
+     * LP_WAKE_CTRL | Wake-up Frequency
+     * -------------+------------------
+     * 0            | 1.25 Hz
+     * 1            | 2.5 Hz
+     * 2            | 5 Hz
+     * 3            | 10 Hz
+     * 
+ * + * For further information regarding the MPU-60X0's power modes, please refer to + * Register 107. + * + * @return Current wake frequency + * @see MPU6050_RA_PWR_MGMT_2 + */ + uint8_t getWakeFrequency() + { + return readBits(MPU6050_RA_PWR_MGMT_2, MPU6050_PWR2_LP_WAKE_CTRL_BIT, MPU6050_PWR2_LP_WAKE_CTRL_LENGTH); + } + /** Set wake frequency in Accel-Only Low Power Mode. + * @param frequency New wake frequency + * @see MPU6050_RA_PWR_MGMT_2 + */ + void setWakeFrequency(uint8_t frequency) + { + I2Cdev::writeBits(devAddr, MPU6050_RA_PWR_MGMT_2, MPU6050_PWR2_LP_WAKE_CTRL_BIT, + MPU6050_PWR2_LP_WAKE_CTRL_LENGTH, frequency); + } + + /** Get X-axis accelerometer standby enabled status. + * If enabled, the X-axis will not gather or report data (or use power). + * @return Current X-axis standby enabled status + * @see MPU6050_RA_PWR_MGMT_2 + * @see MPU6050_PWR2_STBY_XA_BIT + */ + bool getStandbyXAccelEnabled() + { + return readBit(MPU6050_RA_PWR_MGMT_2, MPU6050_PWR2_STBY_XA_BIT); + } + /** Set X-axis accelerometer standby enabled status. + * @param New X-axis standby enabled status + * @see getStandbyXAccelEnabled() + * @see MPU6050_RA_PWR_MGMT_2 + * @see MPU6050_PWR2_STBY_XA_BIT + */ + void setStandbyXAccelEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_PWR_MGMT_2, MPU6050_PWR2_STBY_XA_BIT, enabled); + } + /** Get Y-axis accelerometer standby enabled status. + * If enabled, the Y-axis will not gather or report data (or use power). + * @return Current Y-axis standby enabled status + * @see MPU6050_RA_PWR_MGMT_2 + * @see MPU6050_PWR2_STBY_YA_BIT + */ + bool getStandbyYAccelEnabled() + { + return readBit(MPU6050_RA_PWR_MGMT_2, MPU6050_PWR2_STBY_YA_BIT); + } + /** Set Y-axis accelerometer standby enabled status. + * @param New Y-axis standby enabled status + * @see getStandbyYAccelEnabled() + * @see MPU6050_RA_PWR_MGMT_2 + * @see MPU6050_PWR2_STBY_YA_BIT + */ + void setStandbyYAccelEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_PWR_MGMT_2, MPU6050_PWR2_STBY_YA_BIT, enabled); + } + /** Get Z-axis accelerometer standby enabled status. + * If enabled, the Z-axis will not gather or report data (or use power). + * @return Current Z-axis standby enabled status + * @see MPU6050_RA_PWR_MGMT_2 + * @see MPU6050_PWR2_STBY_ZA_BIT + */ + bool getStandbyZAccelEnabled() + { + return readBit(MPU6050_RA_PWR_MGMT_2, MPU6050_PWR2_STBY_ZA_BIT); + } + /** Set Z-axis accelerometer standby enabled status. + * @param New Z-axis standby enabled status + * @see getStandbyZAccelEnabled() + * @see MPU6050_RA_PWR_MGMT_2 + * @see MPU6050_PWR2_STBY_ZA_BIT + */ + void setStandbyZAccelEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_PWR_MGMT_2, MPU6050_PWR2_STBY_ZA_BIT, enabled); + } + /** Get X-axis gyroscope standby enabled status. + * If enabled, the X-axis will not gather or report data (or use power). + * @return Current X-axis standby enabled status + * @see MPU6050_RA_PWR_MGMT_2 + * @see MPU6050_PWR2_STBY_XG_BIT + */ + bool getStandbyXGyroEnabled() + { + return readBit(MPU6050_RA_PWR_MGMT_2, MPU6050_PWR2_STBY_XG_BIT); + } + /** Set X-axis gyroscope standby enabled status. + * @param New X-axis standby enabled status + * @see getStandbyXGyroEnabled() + * @see MPU6050_RA_PWR_MGMT_2 + * @see MPU6050_PWR2_STBY_XG_BIT + */ + void setStandbyXGyroEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_PWR_MGMT_2, MPU6050_PWR2_STBY_XG_BIT, enabled); + } + /** Get Y-axis gyroscope standby enabled status. + * If enabled, the Y-axis will not gather or report data (or use power). + * @return Current Y-axis standby enabled status + * @see MPU6050_RA_PWR_MGMT_2 + * @see MPU6050_PWR2_STBY_YG_BIT + */ + bool getStandbyYGyroEnabled() + { + return readBit(MPU6050_RA_PWR_MGMT_2, MPU6050_PWR2_STBY_YG_BIT); + } + /** Set Y-axis gyroscope standby enabled status. + * @param New Y-axis standby enabled status + * @see getStandbyYGyroEnabled() + * @see MPU6050_RA_PWR_MGMT_2 + * @see MPU6050_PWR2_STBY_YG_BIT + */ + void setStandbyYGyroEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_PWR_MGMT_2, MPU6050_PWR2_STBY_YG_BIT, enabled); + } + /** Get Z-axis gyroscope standby enabled status. + * If enabled, the Z-axis will not gather or report data (or use power). + * @return Current Z-axis standby enabled status + * @see MPU6050_RA_PWR_MGMT_2 + * @see MPU6050_PWR2_STBY_ZG_BIT + */ + bool getStandbyZGyroEnabled() + { + return readBit(MPU6050_RA_PWR_MGMT_2, MPU6050_PWR2_STBY_ZG_BIT); + } + /** Set Z-axis gyroscope standby enabled status. + * @param New Z-axis standby enabled status + * @see getStandbyZGyroEnabled() + * @see MPU6050_RA_PWR_MGMT_2 + * @see MPU6050_PWR2_STBY_ZG_BIT + */ + void setStandbyZGyroEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_PWR_MGMT_2, MPU6050_PWR2_STBY_ZG_BIT, enabled); + } + + // FIFO_COUNT* registers + + /** Get current FIFO buffer size. + * This value indicates the number of bytes stored in the FIFO buffer. This + * number is in turn the number of bytes that can be read from the FIFO buffer + * and it is directly proportional to the number of samples available given the + * set of sensor data bound to be stored in the FIFO (register 35 and 36). + * @return Current FIFO buffer size + */ + uint16_t getFIFOCount() + { + uint8_t buffer[2] = {0}; + I2Cdev::readBytes(devAddr, MPU6050_RA_FIFO_COUNTH, 2, buffer); + return (((uint16_t)buffer[0]) << 8) | buffer[1]; + } + + // FIFO_R_W register + + /** Get byte from FIFO buffer. + * This register is used to read and write data from the FIFO buffer. Data is + * written to the FIFO in order of register number (from lowest to highest). If + * all the FIFO enable flags (see below) are enabled and all External Sensor + * Data registers (Registers 73 to 96) are associated with a Slave device, the + * contents of registers 59 through 96 will be written in order at the Sample + * Rate. + * + * The contents of the sensor data registers (Registers 59 to 96) are written + * into the FIFO buffer when their corresponding FIFO enable flags are set to 1 + * in FIFO_EN (Register 35). An additional flag for the sensor data registers + * associated with I2C Slave 3 can be found in I2C_MST_CTRL (Register 36). + * + * If the FIFO buffer has overflowed, the status bit FIFO_OFLOW_INT is + * automatically set to 1. This bit is located in INT_STATUS (Register 58). + * When the FIFO buffer has overflowed, the oldest data will be lost and new + * data will be written to the FIFO. + * + * If the FIFO buffer is empty, reading this register will return the last byte + * that was previously read from the FIFO until new data is available. The user + * should check FIFO_COUNT to ensure that the FIFO buffer is not read when + * empty. + * + * @return Byte from FIFO buffer + */ + uint8_t getFIFOByte() + { + return readByte(MPU6050_RA_FIFO_R_W); + } + + /** Write byte to FIFO buffer. + * @see getFIFOByte() + * @see MPU6050_RA_FIFO_R_W + */ + void setFIFOByte(uint8_t data) + { + I2Cdev::writeByte(devAddr, MPU6050_RA_FIFO_R_W, data); + } + + void getFIFOBytes(uint8_t* data, uint8_t length) + { + I2Cdev::readBytes(devAddr, MPU6050_RA_FIFO_R_W, length, data); + } + + // WHO_AM_I register + + /** Get Device ID. + * This register is used to verify the identity of the device (0b110100, 0x34). + * @return Device ID (6 bits only! should be 0x34) + * @see MPU6050_RA_WHO_AM_I + * @see MPU6050_WHO_AM_I_BIT + * @see MPU6050_WHO_AM_I_LENGTH + */ + uint8_t getDeviceID() + { + return readBits(MPU6050_RA_WHO_AM_I, MPU6050_WHO_AM_I_BIT, MPU6050_WHO_AM_I_LENGTH); + } + /** Set Device ID. + * Write a new ID into the WHO_AM_I register (no idea why this should ever be + * necessary though). + * @param id New device ID to set. + * @see getDeviceID() + * @see MPU6050_RA_WHO_AM_I + * @see MPU6050_WHO_AM_I_BIT + * @see MPU6050_WHO_AM_I_LENGTH + */ + void setDeviceID(uint8_t id) + { + I2Cdev::writeBits(devAddr, MPU6050_RA_WHO_AM_I, MPU6050_WHO_AM_I_BIT, MPU6050_WHO_AM_I_LENGTH, id); + } + + // ======== UNDOCUMENTED/DMP REGISTERS/METHODS ======== + + // XG_OFFS_TC register + + uint8_t getOTPBankValid() + { + return readBit(MPU6050_RA_XG_OFFS_TC, MPU6050_TC_OTP_BNK_VLD_BIT); + } + void setOTPBankValid(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_XG_OFFS_TC, MPU6050_TC_OTP_BNK_VLD_BIT, enabled); + } + int8_t getXGyroOffsetTC() + { + return readBits(MPU6050_RA_XG_OFFS_TC, MPU6050_TC_OFFSET_BIT, MPU6050_TC_OFFSET_LENGTH); + } + void setXGyroOffsetTC(int8_t offset) + { + I2Cdev::writeBits(devAddr, MPU6050_RA_XG_OFFS_TC, MPU6050_TC_OFFSET_BIT, MPU6050_TC_OFFSET_LENGTH, offset); + } + + // YG_OFFS_TC register + + int8_t getYGyroOffsetTC() + { + return readBits(MPU6050_RA_YG_OFFS_TC, MPU6050_TC_OFFSET_BIT, MPU6050_TC_OFFSET_LENGTH); + } + void setYGyroOffsetTC(int8_t offset) + { + I2Cdev::writeBits(devAddr, MPU6050_RA_YG_OFFS_TC, MPU6050_TC_OFFSET_BIT, MPU6050_TC_OFFSET_LENGTH, offset); + } + + // ZG_OFFS_TC register + + int8_t getZGyroOffsetTC() + { + return readBits(MPU6050_RA_ZG_OFFS_TC, MPU6050_TC_OFFSET_BIT, MPU6050_TC_OFFSET_LENGTH); + } + void setZGyroOffsetTC(int8_t offset) + { + I2Cdev::writeBits(devAddr, MPU6050_RA_ZG_OFFS_TC, MPU6050_TC_OFFSET_BIT, MPU6050_TC_OFFSET_LENGTH, offset); + } + + // X_FINE_GAIN register + + int8_t getXFineGain() + { + return readByte(MPU6050_RA_X_FINE_GAIN); + } + void setXFineGain(int8_t gain) + { + I2Cdev::writeByte(devAddr, MPU6050_RA_X_FINE_GAIN, gain); + } + + // Y_FINE_GAIN register + + int8_t getYFineGain() + { + return readByte(MPU6050_RA_Y_FINE_GAIN); + } + void setYFineGain(int8_t gain) + { + I2Cdev::writeByte(devAddr, MPU6050_RA_Y_FINE_GAIN, gain); + } + + // Z_FINE_GAIN register + + int8_t getZFineGain() + { + return readByte(MPU6050_RA_Z_FINE_GAIN); + } + void setZFineGain(int8_t gain) + { + I2Cdev::writeByte(devAddr, MPU6050_RA_Z_FINE_GAIN, gain); + } + + // XA_OFFS_* registers + int16_t getXAccelOffset(); + void setXAccelOffset(int16_t offset) + { + I2Cdev::writeWord(devAddr, MPU6050_RA_XA_OFFS_H, offset); + } + + // YA_OFFS_* register + int16_t getYAccelOffset(); + void setYAccelOffset(int16_t offset) + { + I2Cdev::writeWord(devAddr, MPU6050_RA_YA_OFFS_H, offset); + } + + // ZA_OFFS_* register + int16_t getZAccelOffset(); + void setZAccelOffset(int16_t offset) + { + I2Cdev::writeWord(devAddr, MPU6050_RA_ZA_OFFS_H, offset); + } + + // XG_OFFS_USR* registers + int16_t getXGyroOffset() + { + return readReg(MPU6050_RA_XG_OFFS_USRH); + } + void setXGyroOffset(int16_t offset) + { + I2Cdev::writeWord(devAddr, MPU6050_RA_XG_OFFS_USRH, offset); + } + // YG_OFFS_USR* register + int16_t getYGyroOffset() + { + return readReg(MPU6050_RA_YG_OFFS_USRH); + } + + void setYGyroOffset(int16_t offset) + { + I2Cdev::writeWord(devAddr, MPU6050_RA_YG_OFFS_USRH, offset); + } + + // ZG_OFFS_USR* register + int16_t getZGyroOffset() + { + return readReg(MPU6050_RA_ZG_OFFS_USRH); + } + + void setZGyroOffset(int16_t offset) + { + I2Cdev::writeWord(devAddr, MPU6050_RA_ZG_OFFS_USRH, offset); + } + + // INT_ENABLE register (DMP functions) + bool getIntPLLReadyEnabled() + { + return readBit(MPU6050_RA_INT_ENABLE, MPU6050_INTERRUPT_PLL_RDY_INT_BIT); + } + void setIntPLLReadyEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_INT_ENABLE, MPU6050_INTERRUPT_PLL_RDY_INT_BIT, enabled); + } + bool getIntDMPEnabled() + { + return readBit(MPU6050_RA_INT_ENABLE, MPU6050_INTERRUPT_DMP_INT_BIT); + } + void setIntDMPEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_INT_ENABLE, MPU6050_INTERRUPT_DMP_INT_BIT, enabled); + } + + // DMP_INT_STATUS + bool getDMPInt5Status() + { + return readBit(MPU6050_RA_DMP_INT_STATUS, MPU6050_DMPINT_5_BIT); + } + bool getDMPInt4Status() + { + return readBit(MPU6050_RA_DMP_INT_STATUS, MPU6050_DMPINT_4_BIT); + } + bool getDMPInt3Status() + { + return readBit(MPU6050_RA_DMP_INT_STATUS, MPU6050_DMPINT_3_BIT); + } + bool getDMPInt2Status() + { + return readBit(MPU6050_RA_DMP_INT_STATUS, MPU6050_DMPINT_2_BIT); + } + bool getDMPInt1Status() + { + return readBit(MPU6050_RA_DMP_INT_STATUS, MPU6050_DMPINT_1_BIT); + } + bool getDMPInt0Status() + { + return readBit(MPU6050_RA_DMP_INT_STATUS, MPU6050_DMPINT_0_BIT); + } + + // INT_STATUS register (DMP functions) + + bool getIntPLLReadyStatus() + { + return readBit(MPU6050_RA_INT_STATUS, MPU6050_INTERRUPT_PLL_RDY_INT_BIT); + } + bool getIntDMPStatus() + { + return readBit(MPU6050_RA_INT_STATUS, MPU6050_INTERRUPT_DMP_INT_BIT); + } + + // USER_CTRL register (DMP functions) + + bool getDMPEnabled() + { + return readBit(MPU6050_RA_USER_CTRL, MPU6050_USERCTRL_DMP_EN_BIT); + } + void setDMPEnabled(bool enabled) + { + I2Cdev::writeBit(devAddr, MPU6050_RA_USER_CTRL, MPU6050_USERCTRL_DMP_EN_BIT, enabled); + } + void resetDMP() + { + I2Cdev::writeBit(devAddr, MPU6050_RA_USER_CTRL, MPU6050_USERCTRL_DMP_RESET_BIT, true); + } + + // BANK_SEL register + void setMemoryBank(uint8_t bank, bool prefetchEnabled = false, bool userBank = false); + + // MEM_START_ADDR register + void setMemoryStartAddress(uint8_t address) + { + I2Cdev::writeByte(devAddr, MPU6050_RA_MEM_START_ADDR, address); + } + + // MEM_R_W register + uint8_t readMemoryByte() + { + return readByte(MPU6050_RA_MEM_R_W); + } + + void writeMemoryByte(uint8_t data) + { + I2Cdev::writeByte(devAddr, MPU6050_RA_MEM_R_W, data); + } + + // DMP_CFG_1 register + uint8_t getDMPConfig1() + { + return readByte(MPU6050_RA_DMP_CFG_1); + } + + void setDMPConfig1(uint8_t config) + { + I2Cdev::writeByte(devAddr, MPU6050_RA_DMP_CFG_1, config); + } + + // DMP_CFG_2 register + uint8_t getDMPConfig2() + { + return readByte(MPU6050_RA_DMP_CFG_2); + } + + void setDMPConfig2(uint8_t config) + { + I2Cdev::writeByte(devAddr, MPU6050_RA_DMP_CFG_2, config); + } + +private: + // I2C helpers + uint8_t readBit(uint8_t regAddr, uint8_t bitNum); + uint8_t readBits(uint8_t regAddr, uint8_t bitStart, uint8_t length); + uint8_t readByte(uint8_t regAddr); + + template T readReg(uint8_t regAddr); + + uint8_t devAddr; +}; + +template T MPU6050::readReg(uint8_t regAddr) +{ + static_assert(std::is_fundamental::value, "T must be an fundamental type."); + + const auto sz = sizeof(T); + uint8_t buffer[sz] = {0}; + //data follow big endian convention + I2Cdev::readBytes(devAddr, regAddr, sz, buffer); + + T result{}; + for(size_t i{0}; i < sz; ++i) { + result |= static_cast(buffer[i]) << (8 * (sz - i - 1)); + } + return result; +} + +namespace detail +{ +template inline T concat(uint8_t bits_15_8, uint8_t bits_7_0) +{ + return (static_cast(bits_15_8) << 8) | bits_7_0; +} +} // namespace detail diff --git a/Sming/Libraries/MPU6050/README.rst b/Sming/Libraries/MPU6050/README.rst new file mode 100644 index 0000000000..8018fe0acd --- /dev/null +++ b/Sming/Libraries/MPU6050/README.rst @@ -0,0 +1,19 @@ +MPU6050 Gyro / Accelerometer +============================= + + +MPU6050 Six-Axis (Gyro + Accelerometer) +Based on code from `jrowberg/i2cdevlib `__ @ 605a740. Most of the code is the same, except: + +- Removed MPU6050::ReadRegister function due to incompatibility. It is also not used anywhere in the original code. +- MPU6050_6Axis_MotionApps20.h and MPU6050_9Axis_MotionApps41.h are not included due to deps to freeRTOS. helper_3dmath.h is also not included since it is only used in the above mentioned files. +- Removed map function in favor of the Sming built-in one. +- Adapted include path, coding style and applied clangformat +- Deleted Calibration and Memory Block related code for code quality reason + + +API Documentation +----------------- + +.. doxygenclass:: MPU6050 + :members: diff --git a/Sming/Libraries/MPU6050/component.mk b/Sming/Libraries/MPU6050/component.mk new file mode 100644 index 0000000000..2416b727f3 --- /dev/null +++ b/Sming/Libraries/MPU6050/component.mk @@ -0,0 +1,2 @@ +COMPONENT_DOXYGEN_INPUT := . +COMPONENT_DEPENDS := I2Cdev diff --git a/samples/Accel_Gyro_MPU6050/Makefile b/samples/Accel_Gyro_MPU6050/Makefile new file mode 100644 index 0000000000..ff51b6c3a7 --- /dev/null +++ b/samples/Accel_Gyro_MPU6050/Makefile @@ -0,0 +1,9 @@ +##################################################################### +#### Please don't change this file. Use component.mk instead #### +##################################################################### + +ifndef SMING_HOME +$(error SMING_HOME is not set: please configure it as an environment variable) +endif + +include $(SMING_HOME)/project.mk diff --git a/samples/Accel_Gyro_MPU6050/README.rst b/samples/Accel_Gyro_MPU6050/README.rst new file mode 100644 index 0000000000..d43abbcfff --- /dev/null +++ b/samples/Accel_Gyro_MPU6050/README.rst @@ -0,0 +1,7 @@ +MPU6050 Six-Axis (Gyro + Accelerometer) +================ + +MPU6050 sensor reader. + +.. image:: mpu6050.jpg + :height: 192px diff --git a/samples/Accel_Gyro_MPU6050/app/application.cpp b/samples/Accel_Gyro_MPU6050/app/application.cpp new file mode 100644 index 0000000000..0190f99c03 --- /dev/null +++ b/samples/Accel_Gyro_MPU6050/app/application.cpp @@ -0,0 +1,24 @@ +#include +#include + +constexpr uint16_t mainLoopInterval = 20; // ms +SimpleTimer mainLoopTimer; +MPU6050 mpu; + +void mainLoop() +{ + const MPU6050::Motion6 accelGyro = mpu.getMotion6(); + Serial << accelGyro << endl; +} + +void init() +{ + Serial.begin(SERIAL_BAUD_RATE); // 115200 by default + Serial.systemDebugOutput(true); // Enable debug output to serial + + Wire.begin(DEFAULT_SDA_PIN, DEFAULT_SCL_PIN); + mpu.initialize(); + Serial.println(mpu.testConnection() ? "MPU6050 connection successful" : "MPU6050 connection failed"); + + mainLoopTimer.initializeMs(mainLoop).start(); +} diff --git a/samples/Accel_Gyro_MPU6050/component.mk b/samples/Accel_Gyro_MPU6050/component.mk new file mode 100644 index 0000000000..54e9cf0c23 --- /dev/null +++ b/samples/Accel_Gyro_MPU6050/component.mk @@ -0,0 +1,2 @@ +ARDUINO_LIBRARIES := MPU6050 +DISABLE_NETWORK := 1 diff --git a/samples/Accel_Gyro_MPU6050/mpu6050.jpg b/samples/Accel_Gyro_MPU6050/mpu6050.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3d478a9db958f526902945c929042c9d68b8446e GIT binary patch literal 55832 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 Date: Mon, 20 Nov 2023 16:01:11 +0100 Subject: [PATCH 22/28] Add fix for a problem with awk on MacOS X. (#2684) --- Sming/Components/rboot/component.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sming/Components/rboot/component.mk b/Sming/Components/rboot/component.mk index 7a88aac668..a11795b443 100644 --- a/Sming/Components/rboot/component.mk +++ b/Sming/Components/rboot/component.mk @@ -68,7 +68,7 @@ endif # determine number of roms to generate ifneq ($(RBOOT_ROM1_ADDR),) -RBOOT_TWO_ROMS := $(shell $(AWK) 'BEGIN { print (ARGV[1] % (1024*1024)) != (ARGV[2] % (1024*1024))}' $(RBOOT_ROM0_ADDR) $(RBOOT_ROM1_ADDR)) +RBOOT_TWO_ROMS := $(shell $(AWK) 'BEGIN { print ((ARGV[1] % (1024*1024)) != (ARGV[2] % (1024*1024)))}' $(RBOOT_ROM0_ADDR) $(RBOOT_ROM1_ADDR)) else RBOOT_TWO_ROMS := 0 endif From ab797f9d68be7c08c4bb4b3da3fc3fed94e6ad81 Mon Sep 17 00:00:00 2001 From: slaff Date: Mon, 11 Dec 2023 09:23:38 +0100 Subject: [PATCH 23/28] Refactor command processor (#2687) Read the upgrade document https://sming.readthedocs.io/en/latest/upgrading/4.7-5.1.html in you are using Command Processing in your applications. --------- Co-authored-by: Slavey Karadzhov Co-authored-by: Mike Co-authored-by: mikee47 --- Sming/Components/CommandProcessing/README.rst | 62 +++++ .../Components/CommandProcessing/component.mk | 10 + .../Components/CommandProcessing/samples/.cs | 0 .../samples}/Arducam/Makefile | 0 .../samples}/Arducam/README.rst | 0 .../samples/Arducam/app/ArduCamCommand.cpp | 226 ++++++++++++++++ .../samples}/Arducam/app/application.cpp | 42 +-- .../samples/Arducam/component.mk | 3 + .../samples/Arducam/include/ArduCamCommand.h | 35 +++ .../samples}/Arducam/web/build/Sming.png | Bin .../web/build/grids-responsive-min.css | 0 .../samples}/Arducam/web/build/index.html | 0 .../samples}/Arducam/web/build/layout.css | 0 .../samples}/Arducam/web/build/minified.js | 0 .../samples}/Arducam/web/build/pure-min.css | 0 .../samples/CommandLine}/Makefile | 0 .../samples/CommandLine/README.rst | 4 + .../samples/CommandLine/app/application.cpp | 52 ++++ .../samples/CommandLine/component.mk | 2 + .../samples/CommandLine}/files/index.html | 0 .../CommandLine}/web/build/bootstrap.css.gz | Bin .../samples/CommandLine}/web/build/index.html | 0 .../CommandLine}/web/build/jquery.js.gz | Bin .../CommandLine}/web/dev/bootstrap.css | 0 .../samples/CommandLine}/web/dev/index.html | 0 .../samples/CommandLine}/web/dev/jquery.js | 0 .../samples/TelnetServer}/Makefile | 0 .../samples/TelnetServer/README.rst | 4 + .../samples/TelnetServer/app/application.cpp | 68 +++++ .../samples/TelnetServer/component.mk | 1 + Sming/Components/CommandProcessing/src/.cs | 0 .../src/CommandProcessing/Command.h | 53 ++++ .../src/CommandProcessing/Handler.cpp | 228 ++++++++++++++++ .../src/CommandProcessing/Handler.h} | 151 +++++++---- .../src/CommandProcessing/Utils.cpp | 15 ++ .../src/CommandProcessing/Utils.h | 10 + .../Network/src/Network/FtpServer.h | 2 +- .../Http/Websocket/WebsocketResource.cpp | 2 - .../Http/Websocket/WsCommandHandlerResource.h | 51 ---- .../Network/src/Network/TcpClient.h | 1 - .../Network/src/Network/TelnetServer.cpp | 103 -------- .../Network/src/Network/TelnetServer.h | 53 ---- Sming/Core/Debug.cpp | 109 -------- Sming/Core/Debug.h | 112 -------- Sming/Core/HardwareSerial.cpp | 38 --- Sming/Core/HardwareSerial.h | 11 +- Sming/Kconfig | 6 - .../samples/generic/app/application.cpp | 2 - .../samples/generic/app/application.cpp | 6 - Sming/Libraries/USB | 2 +- .../samples/generic/app/application.cpp | 3 - .../CommandProcessing/CommandDelegate.h | 50 ---- .../CommandProcessing/CommandExecutor.cpp | 122 --------- .../CommandProcessing/CommandExecutor.h | 43 --- .../CommandProcessing/CommandHandler.cpp | 182 ------------- .../CommandProcessing/CommandOutput.cpp | 44 ---- .../CommandProcessing/CommandOutput.h | 43 --- .../CommandProcessingDependencies.h | 8 - .../CommandProcessingIncludes.h | 14 - Sming/component.mk | 8 - .../services/command-processing/index.rst | 42 --- docs/source/framework/services/index.rst | 1 - docs/source/upgrading/4.7-5.1.rst | 63 +++++ docs/source/upgrading/index.rst | 1 + samples/Arducam/app/ArduCamCommand.cpp | 248 ------------------ samples/Arducam/component.mk | 3 - samples/Arducam/include/ArduCamCommand.h | 38 --- samples/CommandProcessing_Debug/README.rst | 4 - .../app/ExampleCommand.cpp | 46 ---- .../app/application.cpp | 136 ---------- samples/CommandProcessing_Debug/component.mk | 1 - .../include/ExampleCommand.h | 21 -- samples/HttpServer_WebSockets/Kconfig | 11 + .../HttpServer_WebSockets/app/application.cpp | 42 +++ samples/HttpServer_WebSockets/component.mk | 9 + samples/Telnet_Server/README.rst | 4 - samples/Telnet_Server/app/application.cpp | 134 ---------- 77 files changed, 1033 insertions(+), 1752 deletions(-) create mode 100644 Sming/Components/CommandProcessing/README.rst create mode 100644 Sming/Components/CommandProcessing/component.mk create mode 100644 Sming/Components/CommandProcessing/samples/.cs rename {samples => Sming/Components/CommandProcessing/samples}/Arducam/Makefile (100%) rename {samples => Sming/Components/CommandProcessing/samples}/Arducam/README.rst (100%) create mode 100644 Sming/Components/CommandProcessing/samples/Arducam/app/ArduCamCommand.cpp rename {samples => Sming/Components/CommandProcessing/samples}/Arducam/app/application.cpp (91%) create mode 100644 Sming/Components/CommandProcessing/samples/Arducam/component.mk create mode 100644 Sming/Components/CommandProcessing/samples/Arducam/include/ArduCamCommand.h rename {samples => Sming/Components/CommandProcessing/samples}/Arducam/web/build/Sming.png (100%) rename {samples => Sming/Components/CommandProcessing/samples}/Arducam/web/build/grids-responsive-min.css (100%) rename {samples => Sming/Components/CommandProcessing/samples}/Arducam/web/build/index.html (100%) rename {samples => Sming/Components/CommandProcessing/samples}/Arducam/web/build/layout.css (100%) rename {samples => Sming/Components/CommandProcessing/samples}/Arducam/web/build/minified.js (100%) rename {samples => Sming/Components/CommandProcessing/samples}/Arducam/web/build/pure-min.css (100%) rename {samples/CommandProcessing_Debug => Sming/Components/CommandProcessing/samples/CommandLine}/Makefile (100%) create mode 100644 Sming/Components/CommandProcessing/samples/CommandLine/README.rst create mode 100644 Sming/Components/CommandProcessing/samples/CommandLine/app/application.cpp create mode 100644 Sming/Components/CommandProcessing/samples/CommandLine/component.mk rename {samples/CommandProcessing_Debug => Sming/Components/CommandProcessing/samples/CommandLine}/files/index.html (100%) rename {samples/CommandProcessing_Debug => Sming/Components/CommandProcessing/samples/CommandLine}/web/build/bootstrap.css.gz (100%) rename {samples/CommandProcessing_Debug => Sming/Components/CommandProcessing/samples/CommandLine}/web/build/index.html (100%) rename {samples/CommandProcessing_Debug => Sming/Components/CommandProcessing/samples/CommandLine}/web/build/jquery.js.gz (100%) rename {samples/CommandProcessing_Debug => Sming/Components/CommandProcessing/samples/CommandLine}/web/dev/bootstrap.css (100%) rename {samples/CommandProcessing_Debug => Sming/Components/CommandProcessing/samples/CommandLine}/web/dev/index.html (100%) rename {samples/CommandProcessing_Debug => Sming/Components/CommandProcessing/samples/CommandLine}/web/dev/jquery.js (100%) rename {samples/Telnet_Server => Sming/Components/CommandProcessing/samples/TelnetServer}/Makefile (100%) create mode 100644 Sming/Components/CommandProcessing/samples/TelnetServer/README.rst create mode 100644 Sming/Components/CommandProcessing/samples/TelnetServer/app/application.cpp create mode 100644 Sming/Components/CommandProcessing/samples/TelnetServer/component.mk create mode 100644 Sming/Components/CommandProcessing/src/.cs create mode 100644 Sming/Components/CommandProcessing/src/CommandProcessing/Command.h create mode 100644 Sming/Components/CommandProcessing/src/CommandProcessing/Handler.cpp rename Sming/{Services/CommandProcessing/CommandHandler.h => Components/CommandProcessing/src/CommandProcessing/Handler.h} (53%) create mode 100644 Sming/Components/CommandProcessing/src/CommandProcessing/Utils.cpp create mode 100644 Sming/Components/CommandProcessing/src/CommandProcessing/Utils.h delete mode 100644 Sming/Components/Network/src/Network/Http/Websocket/WsCommandHandlerResource.h delete mode 100644 Sming/Components/Network/src/Network/TelnetServer.cpp delete mode 100644 Sming/Components/Network/src/Network/TelnetServer.h delete mode 100644 Sming/Core/Debug.cpp delete mode 100644 Sming/Core/Debug.h delete mode 100644 Sming/Services/CommandProcessing/CommandDelegate.h delete mode 100644 Sming/Services/CommandProcessing/CommandExecutor.cpp delete mode 100644 Sming/Services/CommandProcessing/CommandExecutor.h delete mode 100644 Sming/Services/CommandProcessing/CommandHandler.cpp delete mode 100644 Sming/Services/CommandProcessing/CommandOutput.cpp delete mode 100644 Sming/Services/CommandProcessing/CommandOutput.h delete mode 100644 Sming/Services/CommandProcessing/CommandProcessingDependencies.h delete mode 100644 Sming/Services/CommandProcessing/CommandProcessingIncludes.h delete mode 100644 docs/source/framework/services/command-processing/index.rst create mode 100644 docs/source/upgrading/4.7-5.1.rst delete mode 100644 samples/Arducam/app/ArduCamCommand.cpp delete mode 100644 samples/Arducam/component.mk delete mode 100644 samples/Arducam/include/ArduCamCommand.h delete mode 100644 samples/CommandProcessing_Debug/README.rst delete mode 100644 samples/CommandProcessing_Debug/app/ExampleCommand.cpp delete mode 100644 samples/CommandProcessing_Debug/app/application.cpp delete mode 100644 samples/CommandProcessing_Debug/component.mk delete mode 100644 samples/CommandProcessing_Debug/include/ExampleCommand.h create mode 100644 samples/HttpServer_WebSockets/Kconfig delete mode 100644 samples/Telnet_Server/README.rst delete mode 100644 samples/Telnet_Server/app/application.cpp diff --git a/Sming/Components/CommandProcessing/README.rst b/Sming/Components/CommandProcessing/README.rst new file mode 100644 index 0000000000..bdaad7ee9c --- /dev/null +++ b/Sming/Components/CommandProcessing/README.rst @@ -0,0 +1,62 @@ +Command Processing +================== + +.. highlight:: c++ + +Introduction +------------ + +Command handler provides a common command line interface (CLI). Command line must be text. Commands should be separated with a single character. +CLI can be used with: + +- Serial +- Network (Websockets, Telnet) + +and all communication protocols that are exchanging text data. + +Commands can be added to and removed from the command handler. Each command will trigger a defined Delegate. + +A welcome message may be shown when a user connects and end-of-line (EOL) character may be defined. An automatic "help" display is available. + +For more examples take a look at the +:sample:`CommandLine`, +:sample:`TelnetServer` +and :sample:`HttpServer_WebSockets` +samples. + + +Using +----- + +1. Add these lines to your application componenent.mk file:: + + COMPONENT_DEPENDS += CommandProcessing + +2. Add these lines to your application:: + + #include + +3. Basic example:: + + #include + + CommandProcessing::Handler commandHandler; + + void processShutdownCommand(String commandLine, ReadWriteStream& commandOutput) + { + // ... + } + + void init() + { + commandHandler.registerSystemCommands(); + commandHandler.registerCommand(CommandProcessing::Command("shutdown", "Shutdown Server Command", "Application", processShutdownCommand)); + } + +API Documentation +----------------- + +.. doxygengroup:: commandhandler + :content-only: + :members: + diff --git a/Sming/Components/CommandProcessing/component.mk b/Sming/Components/CommandProcessing/component.mk new file mode 100644 index 0000000000..71c9f17265 --- /dev/null +++ b/Sming/Components/CommandProcessing/component.mk @@ -0,0 +1,10 @@ +COMPONENT_SRCDIRS := \ + src \ + $(call ListAllSubDirs,$(COMPONENT_PATH)/src) \ + +COMPONENT_INCDIRS := $(COMPONENT_SRCDIRS) + +COMPONENT_DOXYGEN_INPUT := src + +COMPONENT_DOCFILES := \ + docs/* \ No newline at end of file diff --git a/Sming/Components/CommandProcessing/samples/.cs b/Sming/Components/CommandProcessing/samples/.cs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/samples/Arducam/Makefile b/Sming/Components/CommandProcessing/samples/Arducam/Makefile similarity index 100% rename from samples/Arducam/Makefile rename to Sming/Components/CommandProcessing/samples/Arducam/Makefile diff --git a/samples/Arducam/README.rst b/Sming/Components/CommandProcessing/samples/Arducam/README.rst similarity index 100% rename from samples/Arducam/README.rst rename to Sming/Components/CommandProcessing/samples/Arducam/README.rst diff --git a/Sming/Components/CommandProcessing/samples/Arducam/app/ArduCamCommand.cpp b/Sming/Components/CommandProcessing/samples/Arducam/app/ArduCamCommand.cpp new file mode 100644 index 0000000000..c4bd3cddb3 --- /dev/null +++ b/Sming/Components/CommandProcessing/samples/Arducam/app/ArduCamCommand.cpp @@ -0,0 +1,226 @@ + +#include +#include +#include + +ArduCamCommand::ArduCamCommand(ArduCAM& CAM, CommandProcessing::Handler& commandHandler) + : myCAM(CAM), commandHandler(&commandHandler), imgSize(OV2640_320x240), imgType(JPEG) +{ + debug_d("ArduCamCommand Instantiating"); +} + +ArduCamCommand::~ArduCamCommand() +{ +} + +void ArduCamCommand::initCommand() +{ + commandHandler->registerCommand( + CommandProcessing::Command("set", "ArduCAM config commands", "Application", + CommandProcessing::Command::Callback(&ArduCamCommand::processSetCommands, this))); +} + +void ArduCamCommand::showSettings(ReadWriteStream& commandOutput) +{ + // review settings + commandOutput << _F("ArduCam Settings:") << endl + << _F(" img Type: [") << getImageType() << ']' << endl + << _F(" img Size: [") << getImageSize() << ']' << endl; +}; + +void ArduCamCommand::processSetCommands(String commandLine, ReadWriteStream& commandOutput) +{ + Vector commandToken; + int numToken = splitString(commandLine, ' ', commandToken); + + if(numToken == 1) { + // review settings + showSettings(commandOutput); + } + // handle command -> set + else if(commandToken[1] == "help") { + commandOutput << _F("set img [bmp|jpeg]") << endl; + commandOutput << _F("set size [160|176|320|352|640|800|1024|1280|1600]") << endl; + } + + // handle command -> set + else if(commandToken[1] == "img") { + if(numToken == 3) { + if(commandToken[2] == "bmp") { + // TODO: set image size and init cam + // settings->setImageType(BMP); + setFormat(BMP); + } else if(commandToken[2] == "jpg") { + setFormat(JPEG); + } else { + commandOutput << _F("invalid image format [") << commandToken[2] << ']' << endl; + } + } else { + commandOutput << _F("Syntax: set img [bmp|jpeg]") << endl; + } + showSettings(commandOutput); + } + + else if(commandToken[1] == "size") { + if(numToken == 3) { + if(commandToken[2] == "160") { + imgSize = OV2640_160x120; + myCAM.OV2640_set_JPEG_size(OV2640_160x120); + setFormat(JPEG); + } else if(commandToken[2] == "176") { + imgSize = OV2640_176x144; + myCAM.OV2640_set_JPEG_size(OV2640_176x144); + setFormat(JPEG); + } else if(commandToken[2] == "320") { + imgSize = OV2640_320x240; + myCAM.OV2640_set_JPEG_size(OV2640_320x240); + } else if(commandToken[2] == "352") { + imgSize = OV2640_352x288; + myCAM.OV2640_set_JPEG_size(OV2640_352x288); + setFormat(JPEG); + } else if(commandToken[2] == "640") { + imgSize = OV2640_640x480; + myCAM.OV2640_set_JPEG_size(OV2640_640x480); + setFormat(JPEG); + } else if(commandToken[2] == "800") { + imgSize = OV2640_800x600; + myCAM.OV2640_set_JPEG_size(OV2640_800x600); + setFormat(JPEG); + } else if(commandToken[2] == "1024") { + imgSize = OV2640_1024x768; + myCAM.OV2640_set_JPEG_size(OV2640_1024x768); + setFormat(JPEG); + } else if(commandToken[2] == "1280") { + imgSize = OV2640_1280x1024; + myCAM.OV2640_set_JPEG_size(OV2640_1280x1024); + setFormat(JPEG); + } else if(commandToken[2] == "1600") { + imgSize = OV2640_1600x1200; + myCAM.OV2640_set_JPEG_size(OV2640_1600x1200); + setFormat(JPEG); + } else { + commandOutput << _F("invalid size definition[") << commandToken[2] << ']' << endl; + } + } else { + commandOutput << _F("Syntax: set size [160|176|320|352|640|800|1024|1280|1600]") << endl; + } + showSettings(commandOutput); + } +} + +void ArduCamCommand::setSize(const String& size) +{ + if(size == "160x120") { + imgSize = OV2640_160x120; + myCAM.OV2640_set_JPEG_size(OV2640_160x120); + setFormat(JPEG); + } else if(size == "176x144") { + imgSize = OV2640_176x144; + myCAM.OV2640_set_JPEG_size(OV2640_176x144); + setFormat(JPEG); + } else if(size == "320x240") { + imgSize = OV2640_320x240; + myCAM.OV2640_set_JPEG_size(OV2640_320x240); + } else if(size == "352x288") { + imgSize = OV2640_352x288; + myCAM.OV2640_set_JPEG_size(OV2640_352x288); + setFormat(JPEG); + } else if(size == "640x480") { + imgSize = OV2640_640x480; + myCAM.OV2640_set_JPEG_size(OV2640_640x480); + setFormat(JPEG); + } else if(size == "800x600") { + imgSize = OV2640_800x600; + myCAM.OV2640_set_JPEG_size(OV2640_800x600); + setFormat(JPEG); + } else if(size == "1024x768") { + imgSize = OV2640_1024x768; + myCAM.OV2640_set_JPEG_size(OV2640_1024x768); + setFormat(JPEG); + } else if(size == "1280x1024") { + imgSize = OV2640_1280x1024; + myCAM.OV2640_set_JPEG_size(OV2640_1280x1024); + setFormat(JPEG); + } else if(size == "1600x1200") { + imgSize = OV2640_1600x1200; + myCAM.OV2640_set_JPEG_size(OV2640_1600x1200); + setFormat(JPEG); + } else { + debugf("ERROR: invalid size definition[%s]\r\n", size.c_str()); + } +} + +void ArduCamCommand::setType(const String& type) +{ + setFormat(type == "BMP" ? BMP : JPEG); +} + +void ArduCamCommand::setFormat(uint8 type) +{ + if(type == BMP) { + myCAM.set_format(BMP); + if(imgType != BMP) { + // reset the cam + myCAM.InitCAM(); + imgType = BMP; + imgSize = OV2640_320x240; + } + } else { + myCAM.set_format(JPEG); + // reset the cam + if(imgType != JPEG) { + // reset the cam + myCAM.InitCAM(); + myCAM.OV2640_set_JPEG_size(imgSize); + imgType = JPEG; + } + } +} + +const char* ArduCamCommand::getImageType() +{ + switch(imgType) { + case JPEG: + return "JPEG"; + case BMP: + default: + return "BMP"; + } +} + +const char* ArduCamCommand::getContentType() +{ + switch(imgType) { + case JPEG: + return "image/jpeg"; + case BMP: + default: + return "image/x-ms-bmp"; + } +} + +const char* ArduCamCommand::getImageSize() +{ + switch(imgSize) { + case OV2640_1600x1200: + return "1600x1200"; + case OV2640_1280x1024: + return "1280x1024"; + case OV2640_1024x768: + return "1024x768"; + case OV2640_800x600: + return "800x600"; + case OV2640_640x480: + return "640x480"; + case OV2640_352x288: + return "352x288"; + case OV2640_320x240: + return "320x240"; + case OV2640_176x144: + return "176x144"; + case OV2640_160x120: + return "160x120"; + default: + return "320x240"; + } +} diff --git a/samples/Arducam/app/application.cpp b/Sming/Components/CommandProcessing/samples/Arducam/app/application.cpp similarity index 91% rename from samples/Arducam/app/application.cpp rename to Sming/Components/CommandProcessing/samples/Arducam/app/application.cpp index 228601605b..fdc8ca2ac3 100644 --- a/samples/Arducam/app/application.cpp +++ b/Sming/Components/CommandProcessing/samples/Arducam/app/application.cpp @@ -1,8 +1,6 @@ #include -#include -#include +#include -//#include "CamSettings.h" #include #include @@ -37,14 +35,14 @@ #define CAM_CS 16 // this pins are free to change -TelnetServer telnet; +namespace +{ HttpServer server; -HexDump hdump; +CommandProcessing::Handler commandHandler; ArduCAM myCAM(OV2640, CAM_CS); - -ArduCamCommand arduCamCommand(&myCAM); +ArduCamCommand arduCamCommand(myCAM, commandHandler); SPISettings spiSettings(20000000, MSBFIRST, SPI_MODE0); @@ -56,6 +54,13 @@ void startApplicationCommand() arduCamCommand.initCommand(); } +bool processTelnetInput(TcpClient& client, char* data, int size) +{ + return client.sendString(commandHandler.processNow(data, size)); +} + +TcpServer telnetServer(processTelnetInput); + /* * initCam() * @@ -143,11 +148,11 @@ void onCamSetup(HttpRequest& request, HttpResponse& response) if(request.method == HTTP_POST) { type = request.getPostParameter("type"); debugf("set type %s", type.c_str()); - arduCamCommand.set_type(type); + arduCamCommand.setType(type); size = request.getPostParameter("size"); debugf("set size %s", size.c_str()); - arduCamCommand.set_size(size); + arduCamCommand.setSize(size); } response.sendString("OK"); @@ -220,7 +225,7 @@ void onFavicon(HttpRequest& request, HttpResponse& response) * telnet can be used to configure camera settings * using ArdCammCommand handler */ -void StartServers() +void startServers() { server.listen(80); server.paths.set("/", onIndex); @@ -234,9 +239,7 @@ void StartServers() Serial.println(WifiStation.getIP()); Serial.println(_F("==============================\r\n")); - telnet.listen(23); - telnet.enableDebug(true); - + telnetServer.listen(23); Serial.println(_F("\r\n=== TelnetServer SERVER STARTED ===")); Serial.println(_F("==============================\r\n")); } @@ -244,20 +247,21 @@ void StartServers() // Will be called when station is fully operational void gotIP(IpAddress ip, IpAddress netmask, IpAddress gateway) { - StartServers(); + startServers(); } +} // namespace + void init() { spiffs_mount(); // Mount file system, in order to work with files Serial.begin(SERIAL_BAUD_RATE); // 115200 by default - Serial.systemDebugOutput(true); // Allow debug output to serial - - Debug.setDebug(Serial); - Serial.systemDebugOutput(true); // Enable debug output to serial - Serial.commandProcessing(true); + + // Process commands from serial + commandHandler.setVerbose(true); + CommandProcessing::enable(commandHandler, Serial); WifiStation.enable(true); WifiStation.config(WIFI_SSID, WIFI_PWD); diff --git a/Sming/Components/CommandProcessing/samples/Arducam/component.mk b/Sming/Components/CommandProcessing/samples/Arducam/component.mk new file mode 100644 index 0000000000..cdae913e95 --- /dev/null +++ b/Sming/Components/CommandProcessing/samples/Arducam/component.mk @@ -0,0 +1,3 @@ +ARDUINO_LIBRARIES := ArduCAM CommandProcessing +HWCONFIG = spiffs-2m +SPIFF_FILES = web/build diff --git a/Sming/Components/CommandProcessing/samples/Arducam/include/ArduCamCommand.h b/Sming/Components/CommandProcessing/samples/Arducam/include/ArduCamCommand.h new file mode 100644 index 0000000000..e3f3ffc1c9 --- /dev/null +++ b/Sming/Components/CommandProcessing/samples/Arducam/include/ArduCamCommand.h @@ -0,0 +1,35 @@ +/* + * ArduCamCommand.h + * + */ + +#pragma once + +#include "WString.h" +#include +#include + +class ArduCamCommand +{ +public: + ArduCamCommand(ArduCAM& CAM, CommandProcessing::Handler& commandHandler); + virtual ~ArduCamCommand(); + void initCommand(); + const char* getContentType(); + void setSize(const String& size); + void setType(const String& type); + +private: + bool status = true; + ArduCAM myCAM; + CommandProcessing::Handler* commandHandler{nullptr}; + uint8 imgType; + uint8 imgSize; + void processSetCommands(String commandLine, ReadWriteStream& commandOutput); + + void setFormat(uint8 type); + void showSettings(ReadWriteStream& commandOutput); + + const char* getImageType(); + const char* getImageSize(); +}; diff --git a/samples/Arducam/web/build/Sming.png b/Sming/Components/CommandProcessing/samples/Arducam/web/build/Sming.png similarity index 100% rename from samples/Arducam/web/build/Sming.png rename to Sming/Components/CommandProcessing/samples/Arducam/web/build/Sming.png diff --git a/samples/Arducam/web/build/grids-responsive-min.css b/Sming/Components/CommandProcessing/samples/Arducam/web/build/grids-responsive-min.css similarity index 100% rename from samples/Arducam/web/build/grids-responsive-min.css rename to Sming/Components/CommandProcessing/samples/Arducam/web/build/grids-responsive-min.css diff --git a/samples/Arducam/web/build/index.html b/Sming/Components/CommandProcessing/samples/Arducam/web/build/index.html similarity index 100% rename from samples/Arducam/web/build/index.html rename to Sming/Components/CommandProcessing/samples/Arducam/web/build/index.html diff --git a/samples/Arducam/web/build/layout.css b/Sming/Components/CommandProcessing/samples/Arducam/web/build/layout.css similarity index 100% rename from samples/Arducam/web/build/layout.css rename to Sming/Components/CommandProcessing/samples/Arducam/web/build/layout.css diff --git a/samples/Arducam/web/build/minified.js b/Sming/Components/CommandProcessing/samples/Arducam/web/build/minified.js similarity index 100% rename from samples/Arducam/web/build/minified.js rename to Sming/Components/CommandProcessing/samples/Arducam/web/build/minified.js diff --git a/samples/Arducam/web/build/pure-min.css b/Sming/Components/CommandProcessing/samples/Arducam/web/build/pure-min.css similarity index 100% rename from samples/Arducam/web/build/pure-min.css rename to Sming/Components/CommandProcessing/samples/Arducam/web/build/pure-min.css diff --git a/samples/CommandProcessing_Debug/Makefile b/Sming/Components/CommandProcessing/samples/CommandLine/Makefile similarity index 100% rename from samples/CommandProcessing_Debug/Makefile rename to Sming/Components/CommandProcessing/samples/CommandLine/Makefile diff --git a/Sming/Components/CommandProcessing/samples/CommandLine/README.rst b/Sming/Components/CommandProcessing/samples/CommandLine/README.rst new file mode 100644 index 0000000000..29324f136a --- /dev/null +++ b/Sming/Components/CommandProcessing/samples/CommandLine/README.rst @@ -0,0 +1,4 @@ +CommandLine +=========== + +Demonstrates Sming's CommandProcessing capability via serial interface. diff --git a/Sming/Components/CommandProcessing/samples/CommandLine/app/application.cpp b/Sming/Components/CommandProcessing/samples/CommandLine/app/application.cpp new file mode 100644 index 0000000000..c91c116de1 --- /dev/null +++ b/Sming/Components/CommandProcessing/samples/CommandLine/app/application.cpp @@ -0,0 +1,52 @@ +#include +#include + +CommandProcessing::Handler commandHandler; + +namespace +{ +bool exampleStatus = true; + +// Example Command + +void processExampleCommand(String commandLine, ReadWriteStream& commandOutput) +{ + Vector commandToken; + int numToken = splitString(commandLine, ' ', commandToken); + + // First token is "example" + if(numToken == 1) { + commandOutput << _F("Example Commands available :") << endl; + commandOutput << _F("on : Set example status ON") << endl; + commandOutput << _F("off : Set example status OFF") << endl; + commandOutput << _F("status : Show example status") << endl; + } else if(commandToken[1] == "on") { + exampleStatus = true; + commandOutput << _F("Status ON") << endl; + } else if(commandToken[1] == "off") { + exampleStatus = false; + commandOutput << _F("Status OFF") << endl; + } else if(commandToken[1] == "status") { + commandOutput << _F("Example Status is ") << (exampleStatus ? "ON" : "OFF") << endl; + } else { + commandOutput << _F("Bad command") << endl; + } +} + +} // namespace + +void init() +{ + Serial.begin(SERIAL_BAUD_RATE); // Begin serial output + + // Set verbosity + Serial.systemDebugOutput(true); // Enable debug output to serial + commandHandler.setVerbose(true); + + // Register Input/Output streams + CommandProcessing::enable(commandHandler, Serial); + + commandHandler.registerSystemCommands(); + commandHandler.registerCommand( + CommandProcessing::Command("example", "Example Command", "Application", processExampleCommand)); +} diff --git a/Sming/Components/CommandProcessing/samples/CommandLine/component.mk b/Sming/Components/CommandProcessing/samples/CommandLine/component.mk new file mode 100644 index 0000000000..e9d588c3b4 --- /dev/null +++ b/Sming/Components/CommandProcessing/samples/CommandLine/component.mk @@ -0,0 +1,2 @@ +COMPONENT_DEPENDS := CommandProcessing +HWCONFIG := spiffs-2m diff --git a/samples/CommandProcessing_Debug/files/index.html b/Sming/Components/CommandProcessing/samples/CommandLine/files/index.html similarity index 100% rename from samples/CommandProcessing_Debug/files/index.html rename to Sming/Components/CommandProcessing/samples/CommandLine/files/index.html diff --git a/samples/CommandProcessing_Debug/web/build/bootstrap.css.gz b/Sming/Components/CommandProcessing/samples/CommandLine/web/build/bootstrap.css.gz similarity index 100% rename from samples/CommandProcessing_Debug/web/build/bootstrap.css.gz rename to Sming/Components/CommandProcessing/samples/CommandLine/web/build/bootstrap.css.gz diff --git a/samples/CommandProcessing_Debug/web/build/index.html b/Sming/Components/CommandProcessing/samples/CommandLine/web/build/index.html similarity index 100% rename from samples/CommandProcessing_Debug/web/build/index.html rename to Sming/Components/CommandProcessing/samples/CommandLine/web/build/index.html diff --git a/samples/CommandProcessing_Debug/web/build/jquery.js.gz b/Sming/Components/CommandProcessing/samples/CommandLine/web/build/jquery.js.gz similarity index 100% rename from samples/CommandProcessing_Debug/web/build/jquery.js.gz rename to Sming/Components/CommandProcessing/samples/CommandLine/web/build/jquery.js.gz diff --git a/samples/CommandProcessing_Debug/web/dev/bootstrap.css b/Sming/Components/CommandProcessing/samples/CommandLine/web/dev/bootstrap.css similarity index 100% rename from samples/CommandProcessing_Debug/web/dev/bootstrap.css rename to Sming/Components/CommandProcessing/samples/CommandLine/web/dev/bootstrap.css diff --git a/samples/CommandProcessing_Debug/web/dev/index.html b/Sming/Components/CommandProcessing/samples/CommandLine/web/dev/index.html similarity index 100% rename from samples/CommandProcessing_Debug/web/dev/index.html rename to Sming/Components/CommandProcessing/samples/CommandLine/web/dev/index.html diff --git a/samples/CommandProcessing_Debug/web/dev/jquery.js b/Sming/Components/CommandProcessing/samples/CommandLine/web/dev/jquery.js similarity index 100% rename from samples/CommandProcessing_Debug/web/dev/jquery.js rename to Sming/Components/CommandProcessing/samples/CommandLine/web/dev/jquery.js diff --git a/samples/Telnet_Server/Makefile b/Sming/Components/CommandProcessing/samples/TelnetServer/Makefile similarity index 100% rename from samples/Telnet_Server/Makefile rename to Sming/Components/CommandProcessing/samples/TelnetServer/Makefile diff --git a/Sming/Components/CommandProcessing/samples/TelnetServer/README.rst b/Sming/Components/CommandProcessing/samples/TelnetServer/README.rst new file mode 100644 index 0000000000..0a80ec3087 --- /dev/null +++ b/Sming/Components/CommandProcessing/samples/TelnetServer/README.rst @@ -0,0 +1,4 @@ +TelnetServer +============ + +A demonstration of a telnet server built using ``CommandProcessing``. diff --git a/Sming/Components/CommandProcessing/samples/TelnetServer/app/application.cpp b/Sming/Components/CommandProcessing/samples/TelnetServer/app/application.cpp new file mode 100644 index 0000000000..4c339ff3b7 --- /dev/null +++ b/Sming/Components/CommandProcessing/samples/TelnetServer/app/application.cpp @@ -0,0 +1,68 @@ +#include +#include + +// If you want, you can define WiFi settings globally in Eclipse Environment Variables +#ifndef WIFI_SSID +#define WIFI_SSID "PleaseEnterSSID" // Put your SSID and password here +#define WIFI_PWD "PleaseEnterPass" +#endif + +namespace +{ +CommandProcessing::Handler commandHandler; + +bool processTelnetInput(TcpClient& client, char* data, int size) +{ + return client.sendString(commandHandler.processNow(data, size)); +} + +void processExampleCommand(String commandLine, ReadWriteStream& commandOutput) +{ + Vector commandToken; + int numToken = splitString(commandLine, ' ', commandToken); + + if(numToken == 1) { + commandOutput.println("example: No parameters provided"); + return; + } + + commandOutput.printf("example: %d parameters provided\r\n", numToken); +} + +void initCommands() +{ + commandHandler.registerSystemCommands(); + commandHandler.registerCommand( + CommandProcessing::Command("example", "Example Command", "Application", processExampleCommand)); +} + +TcpServer telnetServer(processTelnetInput); + +// Will be called when station is fully operational +void gotIP(IpAddress ip, IpAddress netmask, IpAddress gateway) +{ + telnetServer.listen(23); + Serial.println(_F("\r\n=== TelnetServer SERVER STARTED ===")); + Serial.println(_F("==============================\r\n")); +} + +} // namespace + +void init() +{ + Serial.begin(SERIAL_BAUD_RATE); // 115200 by default + Serial.systemDebugOutput(true); // Enable debug output to serial + + // Process commands from serial + commandHandler.setVerbose(true); + CommandProcessing::enable(commandHandler, Serial); + + WifiStation.enable(true); + WifiStation.config(WIFI_SSID, WIFI_PWD); + WifiAccessPoint.enable(false); + + WifiEvents.onStationGotIP(gotIP); + + // set command handlers for cam + initCommands(); +} diff --git a/Sming/Components/CommandProcessing/samples/TelnetServer/component.mk b/Sming/Components/CommandProcessing/samples/TelnetServer/component.mk new file mode 100644 index 0000000000..f65c7f99ec --- /dev/null +++ b/Sming/Components/CommandProcessing/samples/TelnetServer/component.mk @@ -0,0 +1 @@ +COMPONENT_DEPENDS := CommandProcessing Network diff --git a/Sming/Components/CommandProcessing/src/.cs b/Sming/Components/CommandProcessing/src/.cs new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Sming/Components/CommandProcessing/src/CommandProcessing/Command.h b/Sming/Components/CommandProcessing/src/CommandProcessing/Command.h new file mode 100644 index 0000000000..9c40fee95f --- /dev/null +++ b/Sming/Components/CommandProcessing/src/CommandProcessing/Command.h @@ -0,0 +1,53 @@ +/* + * CommandDelegate.h + * + * Created on: 2 jul. 2015 + * Author: Herman + */ +/** @addtogroup commandhandler + * @{ + */ + +#pragma once + +#include +#include + +namespace CommandProcessing +{ +/** @brief Command delegate class */ +class Command +{ +public: + /** @brief Command delegate function + * @param commandLine Command line entered by user at CLI, including command and parameters + * @param commandOutput Pointer to the CLI print stream + * @note CommandFunctionDelegate defines the structure of a function that handles individual commands + * @note Can use standard print functions on commandOutput + */ + using Callback = Delegate; + + /** Instantiate a command delegate + * @param reqName Command name - the text a user types to invoke the command + * @param reqHelp Help message shown by CLI "help" command + * @param reqGroup The command group to which this command belongs + * @param reqFunction Delegate that should be invoked (triggered) when the command is entered by a user + */ + Command(String reqName, String reqHelp, String reqGroup, Callback reqFunction) + : name(reqName), description(reqHelp), group(reqGroup), callback(reqFunction) + { + } + + Command() + { + } + + String name; ///< Command name + String description; ///< Command help + String group; ///< Command group + Callback callback; ///< Command Delegate (function that is called when command is invoked) +}; + +} // namespace CommandProcessing + +/** @} */ diff --git a/Sming/Components/CommandProcessing/src/CommandProcessing/Handler.cpp b/Sming/Components/CommandProcessing/src/CommandProcessing/Handler.cpp new file mode 100644 index 0000000000..540d7b3d49 --- /dev/null +++ b/Sming/Components/CommandProcessing/src/CommandProcessing/Handler.cpp @@ -0,0 +1,228 @@ +/* + * CommandHandler.cpp + * + * Created on: 2 jul. 2015 + * Author: Herman + */ + +#include +#include +#include +#include +#include "Handler.h" + +namespace CommandProcessing +{ +Handler::Handler() : currentPrompt(F("Sming>")), currentWelcomeMessage(F("Welcome to the Sming CommandProcessing\r\n")) +{ +} + +size_t Handler::process(char recvChar) +{ + auto& output = getOutputStream(); + + if(recvChar == 27) // ESC -> delete current commandLine + { + commandBuf.clear(); + if(isVerbose()) { + output.println(); + output.print(getCommandPrompt()); + } + } else if(recvChar == getCommandEOL()) { + String command(commandBuf.getBuffer(), commandBuf.getLength()); + commandBuf.clear(); + processCommandLine(command); + } else if(recvChar == '\b' || recvChar == 0x7f) { + if(commandBuf.backspace()) { + output.print(_F("\b \b")); + } + } else { + if(commandBuf.addChar(recvChar) && localEcho) { + output.print(recvChar); + } + } + return 1; +} + +String Handler::processNow(const char* buffer, size_t size) +{ + if(outputStream != nullptr && outputStream->getStreamType() != eSST_MemoryWritable) { + debug_e("Cannot use this method when output stream is set"); + } + + size_t processed = process(buffer, size); + if(processed == size) { + String output; + if(outputStream->moveString(output)) { + return output; + } + } + + return nullptr; +} + +void Handler::processCommandLine(const String& cmdString) +{ + if(cmdString.length() == 0) { + outputStream->println(); + } else { + debug_d("Received full Command line, size = %u,cmd = %s", cmdString.length(), cmdString.c_str()); + String cmdCommand; + int cmdLen = cmdString.indexOf(' '); + if(cmdLen < 0) { + cmdCommand = cmdString; + } else { + cmdCommand = cmdString.substring(0, cmdLen); + } + + debug_d("CommandExecutor : executing command %s", cmdCommand.c_str()); + + Command cmdDelegate = getCommandDelegate(cmdCommand); + + if(!cmdDelegate.callback) { + outputStream->print(_F("Command not found, cmd = '")); + outputStream->print(cmdCommand); + outputStream->println('\''); + } else { + cmdDelegate.callback(cmdString, *outputStream); + } + } + + if(isVerbose()) { + outputStream->print(getCommandPrompt()); + } +} + +void Handler::registerSystemCommands() +{ + String system = F("system"); + registerCommand({F("status"), F("Displays System Information"), system, {&Handler::procesStatusCommand, this}}); + registerCommand({F("echo"), F("Displays command entered"), system, {&Handler::procesEchoCommand, this}}); + registerCommand({F("help"), F("Displays all available commands"), system, {&Handler::procesHelpCommand, this}}); + registerCommand({F("debugon"), F("Set Serial debug on"), system, {&Handler::procesDebugOnCommand, this}}); + registerCommand({F("debugoff"), F("Set Serial debug off"), system, {&Handler::procesDebugOffCommand, this}}); + registerCommand({F("command"), + F("Use verbose/silent/prompt as command options"), + system, + {&Handler::processCommandOptions, this}}); +} + +Command Handler::getCommandDelegate(const String& commandString) +{ + if(registeredCommands.contains(commandString)) { + debug_d("Returning Delegate for %s \r\n", commandString.c_str()); + return registeredCommands[commandString]; + } else { + debug_d("Command %s not recognized, returning NULL\r\n", commandString.c_str()); + return Command("", "", "", nullptr); + } +} + +bool Handler::registerCommand(Command reqDelegate) +{ + if(registeredCommands.contains(reqDelegate.name)) { + // Command already registered, don't allow duplicates + debug_d("Commandhandler duplicate command %s", reqDelegate.name.c_str()); + return false; + } else { + registeredCommands[reqDelegate.name] = reqDelegate; + debug_d("Commandhandlercommand %s registered", reqDelegate.name.c_str()); + return true; + } +} + +bool Handler::unregisterCommand(Command reqDelegate) +{ + if(!registeredCommands.contains(reqDelegate.name)) { + // Command not registered, cannot remove + return false; + } else { + registeredCommands.remove(reqDelegate.name); + // (*registeredCommands)[reqDelegate.commandName] = reqDelegate; + return true; + } +} + +void Handler::procesHelpCommand(String commandLine, ReadWriteStream& outputStream) +{ + debug_d("HelpCommand entered"); + outputStream.println(_F("Commands available are :")); + for(auto cmd : registeredCommands) { + outputStream << cmd->name << " | " << cmd->group << " | " << cmd->description << endl; + } +} + +void Handler::procesStatusCommand(String commandLine, ReadWriteStream& outputStream) +{ + debug_d("StatusCommand entered"); + outputStream << _F("Sming Framework Version : " SMING_VERSION) << endl; + outputStream << _F("ESP SDK version : ") << system_get_sdk_version() << endl; + outputStream << _F("Time = ") << SystemClock.getSystemTimeString() << endl; + outputStream << _F("System Start Reason : ") << system_get_rst_info()->reason << endl; +} + +void Handler::procesEchoCommand(String commandLine, ReadWriteStream& outputStream) +{ + debug_d("HelpCommand entered"); + outputStream << _F("You entered : '") << commandLine << '\'' << endl; +} + +void Handler::procesDebugOnCommand(String commandLine, ReadWriteStream& outputStream) +{ + // Serial.systemDebugOutput(true); + // outputStream.println(_F("Debug set to : On")); +} + +void Handler::procesDebugOffCommand(String commandLine, ReadWriteStream& outputStream) +{ + // Serial.systemDebugOutput(false); + // outputStream.println(_F("Debug set to : Off")); +} + +void Handler::processCommandOptions(String commandLine, ReadWriteStream& outputStream) +{ + Vector commandToken; + int numToken = splitString(commandLine, ' ', commandToken); + bool errorCommand = false; + bool printUsage = false; + + switch(numToken) { + case 2: + if(commandToken[1] == _F("help")) { + printUsage = true; + } + if(commandToken[1] == _F("verbose")) { + setVerbose(true); + outputStream.println(_F("Verbose mode selected")); + break; + } + if(commandToken[1] == _F("silent")) { + setVerbose(false); + outputStream.println(_F("Silent mode selected")); + break; + } + errorCommand = true; + break; + case 3: + if(commandToken[1] != _F("prompt")) { + errorCommand = true; + break; + } + setCommandPrompt(commandToken[2]); + outputStream << _F("Prompt set to : ") << commandToken[2] << endl; + break; + default: + errorCommand = true; + } + if(errorCommand) { + outputStream << _F("Unknown command : ") << commandLine << endl; + } + if(printUsage) { + outputStream << _F("command usage :") << endl + << _F("command verbose : Set verbose mode") << endl + << _F("command silent : Set silent mode") << endl + << _F("command prompt 'new prompt' : Set prompt to use") << endl; + } +} + +} // namespace CommandProcessing diff --git a/Sming/Services/CommandProcessing/CommandHandler.h b/Sming/Components/CommandProcessing/src/CommandProcessing/Handler.h similarity index 53% rename from Sming/Services/CommandProcessing/CommandHandler.h rename to Sming/Components/CommandProcessing/src/CommandProcessing/Handler.h index 8c6d2d4919..25a7f6569e 100644 --- a/Sming/Services/CommandProcessing/CommandHandler.h +++ b/Sming/Components/CommandProcessing/src/CommandProcessing/Handler.h @@ -6,45 +6,98 @@ */ /** @defgroup commandhandler Command Handler * @brief Provide command line interface - - Command handler provides a common command line interface. CLI is available for the following remote access methods: - - Serial - - Telnet - - Websockets - - By default, CLI is disabled. Enable CLI by calling "commandProcessing" on the appropriate access class object, e.g. - - Serial.commandProcessing(true) - Commands can be added to and removed from the command handler. Each command will trigger a defined Delegate. - A welcome message may be shown when a user connects and end of line character may be defined. An automatic "help" display is available. * @{ */ #pragma once -#include "CommandDelegate.h" #include -#include -#include +#include +#include +#include +#include +#include "Command.h" + +namespace CommandProcessing +{ +constexpr size_t MAX_COMMANDSIZE = 64; /** @brief Verbose mode */ -enum VerboseMode { - VERBOSE, ///< Verbose mode - SILENT ///< Silent mode -}; /** @brief Command handler class */ -class CommandHandler +class Handler { public: - /** @brief Instantiate a CommandHandler - */ - CommandHandler(); + /** + * @brief Instantiate a CommandHandler + */ + Handler(); - CommandHandler(const CommandHandler&) = delete; + Handler(ReadWriteStream* stream, bool owned = true) : outputStream(stream), ownedStream(owned) + { + } + + Handler(const Handler& rhs) = delete; + + ~Handler() + { + if(ownedStream) { + delete outputStream; + } + } + + // I/O methods + + /** + * @brief sets the output stream + * @param stream pointer to the output stream + * @param owned specifies if the output stream should be deleted in this class(owned=true) + */ + void setOutputStream(ReadWriteStream* stream, bool owned = true) + { + if(outputStream != nullptr && ownedStream) { + delete outputStream; + } + + outputStream = stream; + ownedStream = owned; + } + + ReadWriteStream& getOutputStream() + { + if(outputStream == nullptr) { + outputStream = new MemoryDataStream(); + ownedStream = true; + } + + return *outputStream; + } + + size_t process(char charToWrite); + + /** @brief Write chars to stream + * @param buffer Pointer to buffer to write to the stream + * @param size Quantity of chars to write + * @retval size_t Quantity of chars processed + */ + size_t process(const char* buffer, size_t size) + { + size_t retval = 0; + for(size_t i = 0; i < size; i++) { + if(process(buffer[i]) != 1) { + break; + } + retval++; + } + return retval; + } + + String processNow(const char* buffer, size_t size); + + // Command registration/de-registration methods /** @brief Add a new command to the command handler * @param reqDelegate Command delegate to register @@ -52,12 +105,12 @@ class CommandHandler * @note If command already exists, it will not be replaced and function will fail. Call unregisterCommand first if you want to replace a command. */ - bool registerCommand(CommandDelegate reqDelegate); + bool registerCommand(Command reqDelegate); /** @brief Remove a command from the command handler * @brief reqDelegate Delegate to remove from command handler */ - bool unregisterCommand(CommandDelegate reqDelegate); + bool unregisterCommand(Command reqDelegate); /** @brief Register default system commands * @note Adds the following system commands to the command handler @@ -74,12 +127,12 @@ class CommandHandler * @param commandString Command to query * @retval CommandDelegate The command delegate matching the command */ - CommandDelegate getCommandDelegate(const String& commandString); + Command getCommandDelegate(const String& commandString); /** @brief Get the verbose mode * @retval VerboseMode Verbose mode */ - VerboseMode getVerboseMode() + bool isVerbose() const { return verboseMode; } @@ -87,9 +140,9 @@ class CommandHandler /** @brief Set the verbose mode * @param reqVerboseMode Verbose mode to set */ - void setVerboseMode(VerboseMode reqVerboseMode) + void setVerbose(bool mode) { - verboseMode = reqVerboseMode; + verboseMode = mode; } /** @brief Get the command line prompt @@ -97,7 +150,7 @@ class CommandHandler * @note This is what is shown on the command line before user input * Default is Sming> */ - String getCommandPrompt() + const String& getCommandPrompt() const { return currentPrompt; } @@ -116,7 +169,7 @@ class CommandHandler * @retval char The EOL character * @note Only supports one EOL, unlike Windows */ - char getCommandEOL() + char getCommandEOL() const { return currentEOL; } @@ -134,7 +187,7 @@ class CommandHandler * @retval String The welcome message that is shown when clients connect * @note Only if verbose mode is enabled */ - String getCommandWelcomeMessage() + const String& getCommandWelcomeMessage() const { return currentWelcomeMessage; } @@ -148,28 +201,32 @@ class CommandHandler currentWelcomeMessage = reqWelcomeMessage; } - // int deleteGroup(String reqGroup); - private: - HashMap registeredCommands; - void procesHelpCommand(String commandLine, CommandOutput* commandOutput); - void procesStatusCommand(String commandLine, CommandOutput* commandOutput); - void procesEchoCommand(String commandLine, CommandOutput* commandOutput); - void procesDebugOnCommand(String commandLine, CommandOutput* commandOutput); - void procesDebugOffCommand(String commandLine, CommandOutput* commandOutput); - void processCommandOptions(String commandLine, CommandOutput* commandOutput); - - VerboseMode verboseMode = VERBOSE; + HashMap registeredCommands; String currentPrompt; #ifdef ARCH_HOST - char currentEOL = '\n'; + char currentEOL{'\n'}; #else - char currentEOL = '\r'; + char currentEOL{'\r'}; #endif + bool verboseMode{false}; + bool localEcho{true}; String currentWelcomeMessage; + + ReadWriteStream* outputStream{nullptr}; + bool ownedStream = true; + LineBuffer commandBuf; + + void procesHelpCommand(String commandLine, ReadWriteStream& outputStream); + void procesStatusCommand(String commandLine, ReadWriteStream& outputStream); + void procesEchoCommand(String commandLine, ReadWriteStream& outputStream); + void procesDebugOnCommand(String commandLine, ReadWriteStream& outputStream); + void procesDebugOffCommand(String commandLine, ReadWriteStream& outputStream); + void processCommandOptions(String commandLine, ReadWriteStream& outputStream); + + void processCommandLine(const String& cmdString); }; -/** @brief Global instance of CommandHandler */ -extern CommandHandler commandHandler; +} // namespace CommandProcessing /** @} */ diff --git a/Sming/Components/CommandProcessing/src/CommandProcessing/Utils.cpp b/Sming/Components/CommandProcessing/src/CommandProcessing/Utils.cpp new file mode 100644 index 0000000000..d044b19a52 --- /dev/null +++ b/Sming/Components/CommandProcessing/src/CommandProcessing/Utils.cpp @@ -0,0 +1,15 @@ +#include "Utils.h" + +namespace CommandProcessing +{ +void enable(Handler& commandHandler, HardwareSerial& serial) +{ + commandHandler.setOutputStream(&serial, false); + Serial.onDataReceived([&commandHandler](Stream& source, char arrivedChar, uint16_t availableCharsCount) { + while(availableCharsCount--) { + commandHandler.process(source.read()); + } + }); +} + +} // namespace CommandProcessing diff --git a/Sming/Components/CommandProcessing/src/CommandProcessing/Utils.h b/Sming/Components/CommandProcessing/src/CommandProcessing/Utils.h new file mode 100644 index 0000000000..7ffd2cbac4 --- /dev/null +++ b/Sming/Components/CommandProcessing/src/CommandProcessing/Utils.h @@ -0,0 +1,10 @@ +#pragma once + +#include "Handler.h" +#include + +namespace CommandProcessing +{ +void enable(Handler& commandHandler, HardwareSerial& serial); + +} // namespace CommandProcessing diff --git a/Sming/Components/Network/src/Network/FtpServer.h b/Sming/Components/Network/src/Network/FtpServer.h index e096311f2c..7d0e9492a0 100644 --- a/Sming/Components/Network/src/Network/FtpServer.h +++ b/Sming/Components/Network/src/Network/FtpServer.h @@ -42,7 +42,7 @@ class CustomFtpServer : public TcpServer TcpConnection* createClient(tcp_pcb* clientTcp) override; /** - * @brief Handle an incomding command + * @brief Handle an incoming command * @param cmd The command identifier, e.g. LIST * @param data Any command arguments * @param connection The associated TCP connection to receive any response diff --git a/Sming/Components/Network/src/Network/Http/Websocket/WebsocketResource.cpp b/Sming/Components/Network/src/Network/Http/Websocket/WebsocketResource.cpp index 24288d2997..4d2586dccd 100644 --- a/Sming/Components/Network/src/Network/Http/Websocket/WebsocketResource.cpp +++ b/Sming/Components/Network/src/Network/Http/Websocket/WebsocketResource.cpp @@ -35,8 +35,6 @@ int WebsocketResource::checkHeaders(HttpServerConnection& connection, HttpReques connection.userData = (void*)socket; connection.setUpgradeCallback(HttpServerProtocolUpgradeCallback(&WebsocketConnection::onConnected, socket)); - // TODO: Re-Enable Command Executor... - return 0; } diff --git a/Sming/Components/Network/src/Network/Http/Websocket/WsCommandHandlerResource.h b/Sming/Components/Network/src/Network/Http/Websocket/WsCommandHandlerResource.h deleted file mode 100644 index d14a666935..0000000000 --- a/Sming/Components/Network/src/Network/Http/Websocket/WsCommandHandlerResource.h +++ /dev/null @@ -1,51 +0,0 @@ -/**** - * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. - * Created 2015 by Skurydin Alexey - * http://github.com/SmingHub/Sming - * All files of the Sming Core are provided under the LGPL v3 license. - * - * WsCommandHandlerResource.h - * - * @author: 2017 - Slavey Karadzhov - * - ****/ - -#pragma once - -#include "../HttpResource.h" -#include "WebsocketConnection.h" -#include "WString.h" -#include "Services/CommandProcessing/CommandProcessingIncludes.h" // TODO: .... - -class WsCommandHandlerResource : protected WebsocketResource -{ -public: - WsCommandHandlerResource() : WebsocketResource() - { - wsMessage = WebsocketMessageDelegate(&WsCommandHandlerResource::onMessage, this); - } - -protected: - int checkHeaders(HttpServerConnection& connection, HttpRequest& request, HttpResponse& response) override - { - int err = WebsocketResource::checkHeaders(connection, request, response); - if(err != 0) { - return err; - } - - WebsocketConnection* socket = (WebsocketConnection*)connection.userData; - if(socket != nullptr) { - socket->setMessageHandler(); - - // create new command handler - } - } - - void onMessage(WebsocketConnection& connection, const String& message) - { - commandExecutor.executorReceive(message + "\r"); - } - -private: - CommandExecutor commandExecutor; -}; diff --git a/Sming/Components/Network/src/Network/TcpClient.h b/Sming/Components/Network/src/Network/TcpClient.h index 74b93fa14c..4d1d3becf9 100644 --- a/Sming/Components/Network/src/Network/TcpClient.h +++ b/Sming/Components/Network/src/Network/TcpClient.h @@ -19,7 +19,6 @@ #include "TcpConnection.h" class TcpClient; -class ReadWriteStream; class IpAddress; using TcpClientEventDelegate = Delegate; diff --git a/Sming/Components/Network/src/Network/TelnetServer.cpp b/Sming/Components/Network/src/Network/TelnetServer.cpp deleted file mode 100644 index 345a50ed41..0000000000 --- a/Sming/Components/Network/src/Network/TelnetServer.cpp +++ /dev/null @@ -1,103 +0,0 @@ -/**** - * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. - * Created 2015 by Skurydin Alexey - * http://github.com/SmingHub/Sming - * All files of the Sming Core are provided under the LGPL v3 license. - * - * TelnetServer.cpp - * - * Created on: 18 apr. 2015 - * Author: Herman - * - ****/ - -#include "TelnetServer.h" -#include "Debug.h" - -void TelnetServer::enableDebug(bool reqStatus) -{ - telnetDebug = reqStatus; - if(telnetDebug && curClient != nullptr) /* only setSetDebug when already connected */ - { - Debug.setDebug(DebugPrintCharDelegate(&TelnetServer::wrchar, this)); - } else { - Debug.setDebug(Serial); - } -} - -void TelnetServer::enableCommand(bool reqStatus) -{ -#if ENABLE_CMD_EXECUTOR - if(reqStatus && curClient != nullptr && commandExecutor == nullptr) { - commandExecutor = new CommandExecutor(curClient); - } - if(!reqStatus && commandExecutor != nullptr) { - delete commandExecutor; - commandExecutor = nullptr; - } -#endif - telnetCommand = reqStatus; -} - -void TelnetServer::onClient(TcpClient* client) -{ - debug_d("TelnetServer onClient %s", client->getRemoteIp().toString().c_str()); - - TcpServer::onClient(client); - - if(curClient != nullptr) { - debug_d("TCP Client already connected"); - client->sendString("Telnet Client already connected\r\n"); - client->close(); - } else { - curClient = client; - curClient->setTimeOut(USHRT_MAX); - curClient->sendString("Welcome to Sming / ESP6266 Telnet\r\n"); - if(telnetCommand) { -#if ENABLE_CMD_EXECUTOR - commandExecutor = new CommandExecutor(client); -#endif - } - if(telnetDebug) { - Debug.setDebug(DebugPrintCharDelegate(&TelnetServer::wrchar, this)); - } - Debug.printf("This is debug after telnet start\r\n"); - } -} - -void TelnetServer::onClientComplete(TcpClient& client, bool successful) -{ - if(&client == curClient) { -#if ENABLE_CMD_EXECUTOR - delete commandExecutor; - commandExecutor = nullptr; -#endif - curClient = nullptr; - debug_d("TelnetServer onClientComplete %s", client.getRemoteIp().toString().c_str()); - } else { - debug_d("Telnet server unconnected client close"); - } - - debug_d("TelnetServer onClientComplete %s", client.getRemoteIp().toString().c_str()); - TcpServer::onClientComplete(client, successful); - Debug.setDebug(Serial); -} - -void TelnetServer::wrchar(char c) -{ - char ca[2]; - ca[0] = c; - curClient->write(ca, 1); -} - -bool TelnetServer::onClientReceive(TcpClient& client, char* data, int size) -{ - debug_d("TelnetServer onClientReceive : %s, %d bytes \r\n", client.getRemoteIp().toString().c_str(), size); - debug_d("Data : %s", data); -#if ENABLE_CMD_EXECUTOR - if(commandExecutor != nullptr) { - commandExecutor->executorReceive(data, size); - } -#endif - return true; -} diff --git a/Sming/Components/Network/src/Network/TelnetServer.h b/Sming/Components/Network/src/Network/TelnetServer.h deleted file mode 100644 index 04942e3bac..0000000000 --- a/Sming/Components/Network/src/Network/TelnetServer.h +++ /dev/null @@ -1,53 +0,0 @@ -/**** - * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. - * Created 2015 by Skurydin Alexey - * http://github.com/SmingHub/Sming - * All files of the Sming Core are provided under the LGPL v3 license. - * - * TelnetServer.h - * - * Created on: 18 apr. 2015 - * Author: Herman - * - ****/ - -/** @defgroup telnetserver Telnet server - * @brief Provides Telnet server - * @ingroup tcpserver - * @{ - */ - -#pragma once - -#include "TcpClient.h" -#include "TcpServer.h" -#include "SystemClock.h" -#include "Services/CommandProcessing/CommandExecutor.h" - -#ifndef TELNETSERVER_MAX_COMMANDSIZE -#define TELNETSERVER_MAX_COMMANDSIZE 64 -#endif - -using TelnetServerCommandDelegate = Delegate; - -class TelnetServer : public TcpServer -{ -public: - void enableDebug(bool reqStatus); - void enableCommand(bool reqStatus); - -private: - void onClient(TcpClient* client) override; - bool onClientReceive(TcpClient& client, char* data, int size) override; - void onClientComplete(TcpClient& client, bool successful) override; - - void wrchar(char c); - -private: - TcpClient* curClient = nullptr; - CommandExecutor* commandExecutor = nullptr; - bool telnetDebug = true; - bool telnetCommand = true; -}; - -/** @} */ diff --git a/Sming/Core/Debug.cpp b/Sming/Core/Debug.cpp deleted file mode 100644 index b95d9c1ea8..0000000000 --- a/Sming/Core/Debug.cpp +++ /dev/null @@ -1,109 +0,0 @@ -/**** - * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. - * Created 2015 by Skurydin Alexey - * http://github.com/SmingHub/Sming - * All files of the Sming Core are provided under the LGPL v3 license. - * - * Debug.cpp - * - ****/ - -#include "Debug.h" -#include - -DebugClass::DebugClass() -{ - debugf("DebugClass Instantiating"); - setDebug(Serial); -} - -void DebugClass::initCommand() -{ -#if ENABLE_CMD_EXECUTOR - commandHandler.registerCommand(CommandDelegate(F("debug"), F("New debug in development"), F("Debug"), - CommandFunctionDelegate(&DebugClass::processDebugCommands, this))); -#endif -} - -void DebugClass::start() -{ - started = true; - println(_F("Debug started")); -} - -void DebugClass::stop() -{ - println(_F("Debug stopped")); - started = false; -} - -void DebugClass::setDebug(DebugPrintCharDelegate reqDelegate) -{ - debugOut.debugStream = nullptr; - debugOut.debugDelegate = reqDelegate; - print(_F("Welcome to DebugDelegate\r\n")); -} - -void DebugClass::setDebug(Stream& reqStream) -{ - debugOut.debugDelegate = nullptr; - debugOut.debugStream = &reqStream; - print(_F("Welcome to DebugStream")); -} - -void DebugClass::printPrefix() -{ - if(useDebugPrefix) { - uint32_t curMillis = millis(); - printf(_F("Dbg %4u.%03u : "), curMillis / 1000, curMillis % 1000); - } -} - -size_t DebugClass::write(uint8_t c) -{ - if(started) { - if(newDebugLine) { - newDebugLine = false; - printPrefix(); - } - if(c == '\n') { - newDebugLine = true; - } - if(debugOut.debugDelegate) { - debugOut.debugDelegate(c); - return 1; - } - if(debugOut.debugStream) { - debugOut.debugStream->write(c); - return 1; - } - } - - return 0; -} - -void DebugClass::processDebugCommands(String commandLine, CommandOutput* commandOutput) -{ - Vector commandToken; - int numToken = splitString(commandLine, ' ', commandToken); - - if(numToken == 1) { - commandOutput->print(_F("Debug Commands available : \r\n")); - commandOutput->print(_F("on : Start Debug output\r\n")); - commandOutput->print(_F("off : Stop Debug output\r\n")); - commandOutput->print(_F("serial : Send Debug output to Serial\r\n")); - } else { - if(commandToken[1] == "on") { - start(); - commandOutput->print(_F("Debug started\r\n")); - } else if(commandToken[1] == _F("off")) { - commandOutput->print(_F("Debug stopped\r\n")); - stop(); - } else if(commandToken[1] == _F("serial")) { - setDebug(Serial); - commandOutput->print(_F("Debug set to Serial")); - }; - } -} - -DebugClass Debug; diff --git a/Sming/Core/Debug.h b/Sming/Core/Debug.h deleted file mode 100644 index 831056e3e6..0000000000 --- a/Sming/Core/Debug.h +++ /dev/null @@ -1,112 +0,0 @@ -/**** - * Sming Framework Project - Open Source framework for high efficiency native ESP8266 development. - * Created 2015 by Skurydin Alexey - * http://github.com/SmingHub/Sming - * All files of the Sming Core are provided under the LGPL v3 license. - * - * Debug.h - * - ****/ - -#pragma once - -#include "HardwareSerial.h" -#include "Clock.h" -#include "WString.h" -#include "Services/CommandProcessing/CommandProcessingIncludes.h" - -/** @brief Delegate constructor usage: (&YourClass::method, this) - * Handler function for debug print - * @ingroup event_handlers - */ -using DebugPrintCharDelegate = Delegate; - -/** @brief Structure for debug options - * @ingroup structures - */ -struct DebugOuputOptions { - DebugPrintCharDelegate debugDelegate; ///< Function to handle debug output - Stream* debugStream{nullptr}; ///< Debug output stream -}; - -/** @brief Debug prefix state - * @ingroup constants - */ -enum eDBGPrefix { - eDBGnoPrefix = 0, ///< Do not use debug prefix - eDBGusePrefix = 1 ///< Use debug prefix -}; - -/** @defgroup debug Debug functions - * @brief Provides debug functions - * @{ -*/ - -/** @brief Provides debug output to stream (e.g. Serial) or delegate function handler. - * - * Debug output may be prefixed with an elapsed timestamp. Use standard print methods to produce debug output. - * Sming CLI (command handler) may be enabled to provide control of debug output to end user. - */ -class DebugClass : public Print -{ -public: - /** @brief Instantiate a debug object - * @note Default output is Serial stream - */ - DebugClass(); - - /** @brief Enable control of debug output from CLI command handler output - */ - void initCommand(); - - /** @brief Start debug output - */ - void start(); - - /** @brief Stop debug output - */ - void stop(); - - /** @brief Get debug status - * @retval bool True if debug enabled - */ - bool status() - { - return started; - } - - /** @brief Configure debug to use delegate handler for its output - * @param reqDelegate Function to handle debug output - * @note Disables stream output - */ - void setDebug(DebugPrintCharDelegate reqDelegate); - - /** @brief Configures debug to use stream for its output - * @param reqStream Stream for debug output - * @note Disables delegate handler - */ - void setDebug(Stream& reqStream); - - /* implementation of write for Print Class */ - size_t write(uint8_t) override; - -private: - bool started = false; - bool useDebugPrefix = true; - bool newDebugLine = true; - DebugOuputOptions debugOut; - void printPrefix(); - void processDebugCommands(String commandLine, CommandOutput* commandOutput); -}; - -/** @brief Global instance of Debug object - * @note Use Debug.function to access Debug functions - * @note Example: - * @code - * Debug.start(); - * Debug.printf("My value is %d", myVal); - @endcode -*/ -extern DebugClass Debug; - -/** @} */ diff --git a/Sming/Core/HardwareSerial.cpp b/Sming/Core/HardwareSerial.cpp index b305cfbe0c..c83a51c570 100644 --- a/Sming/Core/HardwareSerial.cpp +++ b/Sming/Core/HardwareSerial.cpp @@ -15,19 +15,8 @@ #include "Platform/System.h" #include "m_printf.h" -#if ENABLE_CMD_EXECUTOR -#include -#endif - HardwareSerial Serial(UART_ID_0); -HardwareSerial::~HardwareSerial() -{ -#if ENABLE_CMD_EXECUTOR - delete commandExecutor; -#endif -} - bool HardwareSerial::begin(uint32_t baud, SerialFormat format, SerialMode mode, uint8_t txPin, uint8_t rxPin) { end(); @@ -123,14 +112,6 @@ void HardwareSerial::invokeCallbacks() if(HWSDelegate) { HWSDelegate(*this, receivedChar, smg_uart_rx_available(uart)); } -#if ENABLE_CMD_EXECUTOR - if(commandExecutor) { - int c; - while((c = smg_uart_read_char(uart)) >= 0) { - commandExecutor->executorReceive(c); - } - } -#endif } } @@ -190,11 +171,7 @@ void HardwareSerial::staticCallbackHandler(smg_uart_t* uart, uint32_t status) bool HardwareSerial::updateUartCallback() { uint16_t mask = 0; -#if ENABLE_CMD_EXECUTOR - if(HWSDelegate || commandExecutor) { -#else if(HWSDelegate) { -#endif mask |= UART_STATUS_RXFIFO_FULL | UART_STATUS_RXFIFO_TOUT | UART_STATUS_RXFIFO_OVF; } @@ -208,18 +185,3 @@ bool HardwareSerial::updateUartCallback() return mask != 0; } - -void HardwareSerial::commandProcessing(bool reqEnable) -{ -#if ENABLE_CMD_EXECUTOR - if(reqEnable) { - if(!commandExecutor) { - commandExecutor = new CommandExecutor(this); - } - } else { - delete commandExecutor; - commandExecutor = nullptr; - } - updateUartCallback(); -#endif -} diff --git a/Sming/Core/HardwareSerial.h b/Sming/Core/HardwareSerial.h index 055e9e6708..4416c338b2 100644 --- a/Sming/Core/HardwareSerial.h +++ b/Sming/Core/HardwareSerial.h @@ -50,8 +50,6 @@ using StreamDataReceivedDelegate = Delegate; -class CommandExecutor; - // clang-format off #define SERIAL_CONFIG_MAP(XX) \ XX(5N1) XX(6N1) XX(7N1) XX(8N1) XX(5N2) XX(6N2) XX(7N2) XX(8N2) XX(5E1) XX(6E1) XX(7E1) XX(8E1) \ @@ -116,8 +114,6 @@ class HardwareSerial : public ReadWriteStream { } - ~HardwareSerial(); - void setPort(int uartPort) { end(); @@ -340,8 +336,12 @@ class HardwareSerial : public ReadWriteStream * @param reqEnable True to enable command processing * @note Command processing provides a CLI to the system * @see commandHandler + * + * @deprecated include and use `CommandProcessing::enable(Handler, Serial)` instead. */ - void commandProcessing(bool reqEnable); + void commandProcessing(bool reqEnable) SMING_DEPRECATED + { + } /** @brief Set handler for received data * @param dataReceivedDelegate Function to handle received data @@ -449,7 +449,6 @@ class HardwareSerial : public ReadWriteStream int uartNr = UART_NO; TransmitCompleteDelegate transmitComplete = nullptr; ///< Callback for transmit completion StreamDataReceivedDelegate HWSDelegate = nullptr; ///< Callback for received data - CommandExecutor* commandExecutor = nullptr; ///< Callback for command execution (received data) smg_uart_t* uart = nullptr; uart_options_t options = _BV(UART_OPT_TXWAIT); size_t txSize = DEFAULT_TX_BUFFER_SIZE; diff --git a/Sming/Kconfig b/Sming/Kconfig index 9a4bfd12cd..993af06be2 100644 --- a/Sming/Kconfig +++ b/Sming/Kconfig @@ -43,12 +43,6 @@ mainmenu "${SMING_SOC} Sming Framework Configuration" This will recompile your application to use the revised baud rate. Note that this will change the default speed used for both flashing and serial comms. - config ENABLE_CMD_EXECUTOR - bool "Enable command executor functionality" - default y - help - This facility requires documenting! - config TASK_QUEUE_LENGTH int "Length of task queue" default 10 diff --git a/Sming/Libraries/CS5460/samples/generic/app/application.cpp b/Sming/Libraries/CS5460/samples/generic/app/application.cpp index 3defc39983..ea0bec20a1 100644 --- a/Sming/Libraries/CS5460/samples/generic/app/application.cpp +++ b/Sming/Libraries/CS5460/samples/generic/app/application.cpp @@ -1,5 +1,4 @@ #include -#include #include CS5460 powerMeter(PIN_NDEFINED, PIN_NDEFINED, PIN_NDEFINED, PIN_NDEFINED); @@ -16,7 +15,6 @@ void init() Serial.begin(SERIAL_BAUD_RATE, SERIAL_8N1, SERIAL_FULL); // 115200 by default, GPIO1,GPIO3, see Serial.swap(), HardwareSerial Serial.systemDebugOutput(true); - Debug.setDebug(Serial); powerMeter.init(); powerMeter.setCurrentGain(190.84); //0.25 / shunt (0.00131) diff --git a/Sming/Libraries/ModbusMaster/samples/generic/app/application.cpp b/Sming/Libraries/ModbusMaster/samples/generic/app/application.cpp index 3d8d826030..ddefd7a32b 100644 --- a/Sming/Libraries/ModbusMaster/samples/generic/app/application.cpp +++ b/Sming/Libraries/ModbusMaster/samples/generic/app/application.cpp @@ -1,5 +1,4 @@ #include -#include #include #define MODBUS_COM_SPEED 115200 @@ -105,11 +104,6 @@ void init() SERIAL_TX_ONLY); // 115200 by default, GPIO1,GPIO3, see Serial.swap(), HardwareSerial debugComPort.systemDebugOutput(true); - Debug.setDebug(debugComPort); - Debug.initCommand(); - Debug.start(); - Debug.printf("This is the debug output\r\n"); - mbMaster.preTransmission(preTransmission); mbMaster.postTransmission(postTransmission); mbMaster.logReceive(mbLogReceive); diff --git a/Sming/Libraries/USB b/Sming/Libraries/USB index 9055f1b444..ae08687c90 160000 --- a/Sming/Libraries/USB +++ b/Sming/Libraries/USB @@ -1 +1 @@ -Subproject commit 9055f1b4443d3bd5fbdd08147c74a6f0528a6a8a +Subproject commit ae08687c900582a94db86d4cff1c492317e52307 diff --git a/Sming/Libraries/modbusino/samples/generic/app/application.cpp b/Sming/Libraries/modbusino/samples/generic/app/application.cpp index 6405541879..0a0d3e9f41 100644 --- a/Sming/Libraries/modbusino/samples/generic/app/application.cpp +++ b/Sming/Libraries/modbusino/samples/generic/app/application.cpp @@ -1,5 +1,4 @@ #include -#include #include #define ARRLEN 3 @@ -17,8 +16,6 @@ void init() { debugComPort.begin(SERIAL_BAUD_RATE, SERIAL_8N1, SERIAL_TX_ONLY); debugComPort.systemDebugOutput(true); - Debug.setDebug(debugComPort); - Debug.start(); mbSlave.setup(SERIAL_BAUD_RATE); mbSlave.setRxCallback(mbPrint); } diff --git a/Sming/Services/CommandProcessing/CommandDelegate.h b/Sming/Services/CommandProcessing/CommandDelegate.h deleted file mode 100644 index c43e2d0751..0000000000 --- a/Sming/Services/CommandProcessing/CommandDelegate.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * CommandDelegate.h - * - * Created on: 2 jul. 2015 - * Author: Herman - */ -/** @addtogroup commandhandler - * @{ - */ - -#pragma once - -#include -#include -#include "CommandOutput.h" - -/** @brief Command delegate function - * @param commandLine Command line entered by user at CLI, including command and parameters - * @param commandOutput Pointer to the CLI print stream - * @note CommandFunctionDelegate defines the structure of a function that handles individual commands - * @note Can use standard print functions on commandOutput - */ -using CommandFunctionDelegate = Delegate; - -/** @brief Command delegate class */ -class CommandDelegate -{ -public: - /** Instantiate a command delegate - * @param reqName Command name - the text a user types to invoke the command - * @param reqHelp Help message shown by CLI "help" command - * @param reqGroup The command group to which this command belongs - * @param reqFunction Delegate that should be invoked (triggered) when the command is entered by a user - */ - CommandDelegate(String reqName, String reqHelp, String reqGroup, CommandFunctionDelegate reqFunction) - : commandName(reqName), commandHelp(reqHelp), commandGroup(reqGroup), commandFunction(reqFunction) - { - } - - CommandDelegate() - { - } - - String commandName; ///< Command name - String commandHelp; ///< Command help - String commandGroup; ///< Command group - CommandFunctionDelegate commandFunction; ///< Command Delegate (function that is called when command is invoked) -}; - -/** @} */ diff --git a/Sming/Services/CommandProcessing/CommandExecutor.cpp b/Sming/Services/CommandProcessing/CommandExecutor.cpp deleted file mode 100644 index 4cb49e78ce..0000000000 --- a/Sming/Services/CommandProcessing/CommandExecutor.cpp +++ /dev/null @@ -1,122 +0,0 @@ -/* - * CommandExecutor.cpp - * - * Created on: 2 jul. 2015 - * Author: Herman - */ - -#include "CommandExecutor.h" -#include "HardwareSerial.h" -#include - -CommandExecutor::CommandExecutor() -{ - commandHandler.registerSystemCommands(); -} - -CommandExecutor::CommandExecutor(Stream* reqStream) : CommandExecutor() -{ - commandOutput.reset(new CommandOutput(reqStream)); - if(commandHandler.getVerboseMode() != SILENT) { - commandOutput->println(_F("Welcome to the Stream Command executor")); - } -} - -#ifndef DISABLE_NETWORK -CommandExecutor::CommandExecutor(TcpClient* cmdClient) : CommandExecutor() -{ - commandOutput.reset(new CommandOutput(cmdClient)); - if(commandHandler.getVerboseMode() != SILENT) { - commandOutput->println(_F("Welcome to the Tcp Command executor")); - } -} - -CommandExecutor::CommandExecutor(WebsocketConnection* reqSocket) -{ - commandOutput.reset(new CommandOutput(reqSocket)); - if(commandHandler.getVerboseMode() != SILENT) { - reqSocket->sendString(_F("Welcome to the Websocket Command Executor")); - } -} -#endif - -int CommandExecutor::executorReceive(char* recvData, int recvSize) -{ - int receiveReturn = 0; - for(int recvIdx = 0; recvIdx < recvSize; recvIdx++) { - receiveReturn = executorReceive(recvData[recvIdx]); - if(receiveReturn != 0) { - break; - } - } - return receiveReturn; -} - -int CommandExecutor::executorReceive(const String& recvString) -{ - int receiveReturn = 0; - for(unsigned recvIdx = 0; recvIdx < recvString.length(); recvIdx++) { - receiveReturn = executorReceive(recvString[recvIdx]); - if(receiveReturn != 0) { - break; - } - } - return receiveReturn; -} - -int CommandExecutor::executorReceive(char recvChar) -{ - if(recvChar == 27) // ESC -> delete current commandLine - { - commandBuf.clear(); - if(commandHandler.getVerboseMode() == VERBOSE) { - commandOutput->println(); - commandOutput->print(commandHandler.getCommandPrompt()); - } - } else if(recvChar == commandHandler.getCommandEOL()) { - String command(commandBuf.getBuffer(), commandBuf.getLength()); - commandBuf.clear(); - processCommandLine(command); - } else if(recvChar == '\b' || recvChar == 0x7f) { - if(commandBuf.backspace()) { - commandOutput->print(_F("\b \b")); - } - } else { - if(commandBuf.addChar(recvChar)) { - commandOutput->print(recvChar); - } - } - return 0; -} - -void CommandExecutor::processCommandLine(const String& cmdString) -{ - if(cmdString.length() == 0) { - commandOutput->println(); - } else { - debugf("Received full Command line, size = %u,cmd = %s", cmdString.length(), cmdString.c_str()); - String cmdCommand; - int cmdLen = cmdString.indexOf(' '); - if(cmdLen < 0) { - cmdCommand = cmdString; - } else { - cmdCommand = cmdString.substring(0, cmdLen); - } - - debugf("CommandExecutor : executing command %s", cmdCommand.c_str()); - - CommandDelegate cmdDelegate = commandHandler.getCommandDelegate(cmdCommand); - - if(!cmdDelegate.commandFunction) { - commandOutput->print(_F("Command not found, cmd = '")); - commandOutput->print(cmdCommand); - commandOutput->println('\''); - } else { - cmdDelegate.commandFunction(cmdString, commandOutput.get()); - } - } - - if(commandHandler.getVerboseMode() == VERBOSE) { - commandOutput->print(commandHandler.getCommandPrompt()); - } -} diff --git a/Sming/Services/CommandProcessing/CommandExecutor.h b/Sming/Services/CommandProcessing/CommandExecutor.h deleted file mode 100644 index a04ac0e6c4..0000000000 --- a/Sming/Services/CommandProcessing/CommandExecutor.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * CommandExecutor.h - * - * Created on: 2 jul. 2015 - * Author: Herman - */ - -#pragma once - -#include "CommandHandler.h" -#include "CommandOutput.h" -#include -#include - -#ifndef DISABLE_NETWORK -#include -#endif - -#define MAX_COMMANDSIZE 64 - -class CommandExecutor -{ -public: - CommandExecutor(const CommandExecutor&) = delete; - CommandExecutor& operator=(const CommandExecutor&) = delete; - -#ifndef DISABLE_NETWORK - CommandExecutor(TcpClient* cmdClient); - CommandExecutor(WebsocketConnection* reqSocket); -#endif - CommandExecutor(Stream* reqStream); - - int executorReceive(char* recvData, int recvSize); - int executorReceive(char recvChar); - int executorReceive(const String& recvString); - void setCommandEOL(char reqEOL); - -private: - CommandExecutor(); - void processCommandLine(const String& cmdString); - LineBuffer commandBuf; - std::unique_ptr commandOutput; -}; diff --git a/Sming/Services/CommandProcessing/CommandHandler.cpp b/Sming/Services/CommandProcessing/CommandHandler.cpp deleted file mode 100644 index c1ba3406ea..0000000000 --- a/Sming/Services/CommandProcessing/CommandHandler.cpp +++ /dev/null @@ -1,182 +0,0 @@ -/* - * CommandHandler.cpp - * - * Created on: 2 jul. 2015 - * Author: Herman - */ - -#include "CommandHandler.h" -#include "CommandDelegate.h" -#include -#include -#include - -#ifndef DISABLE_NETWORK -#include -#endif - -#ifndef LWIP_HASH_STR -#define LWIP_HASH_STR "" -#endif - -CommandHandler::CommandHandler() - : currentPrompt(F("Sming>")), currentWelcomeMessage(F("Welcome to the Sming CommandProcessing\r\n")) -{ -} - -void CommandHandler::registerSystemCommands() -{ - String system = F("system"); - registerCommand(CommandDelegate(F("status"), F("Displays System Information"), system, - CommandFunctionDelegate(&CommandHandler::procesStatusCommand, this))); - registerCommand(CommandDelegate(F("echo"), F("Displays command entered"), system, - CommandFunctionDelegate(&CommandHandler::procesEchoCommand, this))); - registerCommand(CommandDelegate(F("help"), F("Displays all available commands"), system, - CommandFunctionDelegate(&CommandHandler::procesHelpCommand, this))); - registerCommand(CommandDelegate(F("debugon"), F("Set Serial debug on"), system, - CommandFunctionDelegate(&CommandHandler::procesDebugOnCommand, this))); - registerCommand(CommandDelegate(F("debugoff"), F("Set Serial debug off"), system, - CommandFunctionDelegate(&CommandHandler::procesDebugOffCommand, this))); - registerCommand(CommandDelegate(F("command"), F("Use verbose/silent/prompt as command options"), system, - CommandFunctionDelegate(&CommandHandler::processCommandOptions, this))); -} - -CommandDelegate CommandHandler::getCommandDelegate(const String& commandString) -{ - if(registeredCommands.contains(commandString)) { - debugf("Returning Delegate for %s \r\n", commandString.c_str()); - return registeredCommands[commandString]; - } else { - debugf("Command %s not recognized, returning NULL\r\n", commandString.c_str()); - return CommandDelegate("", "", "", nullptr); - } -} - -bool CommandHandler::registerCommand(CommandDelegate reqDelegate) -{ - if(registeredCommands.contains(reqDelegate.commandName)) { - // Command already registered, don't allow duplicates - debugf("Commandhandler duplicate command %s", reqDelegate.commandName.c_str()); - return false; - } else { - registeredCommands[reqDelegate.commandName] = reqDelegate; - debugf("Commandhandlercommand %s registered", reqDelegate.commandName.c_str()); - return true; - } -} - -bool CommandHandler::unregisterCommand(CommandDelegate reqDelegate) -{ - if(!registeredCommands.contains(reqDelegate.commandName)) { - // Command not registered, cannot remove - return false; - } else { - registeredCommands.remove(reqDelegate.commandName); - // (*registeredCommands)[reqDelegate.commandName] = reqDelegate; - return true; - } -} - -void CommandHandler::procesHelpCommand(String commandLine, CommandOutput* commandOutput) -{ - debugf("HelpCommand entered"); - commandOutput->println(_F("Commands available are :")); - for(unsigned idx = 0; idx < registeredCommands.count(); idx++) { - commandOutput->print(registeredCommands.valueAt(idx).commandName); - commandOutput->print(" | "); - commandOutput->print(registeredCommands.valueAt(idx).commandGroup); - commandOutput->print(" | "); - commandOutput->print(registeredCommands.valueAt(idx).commandHelp); - commandOutput->print("\r\n"); - } -} - -void CommandHandler::procesStatusCommand(String commandLine, CommandOutput* commandOutput) -{ - debugf("StatusCommand entered"); - commandOutput->println(_F("System information : ESP8266 Sming Framework")); - commandOutput->println(_F("Sming Framework Version : " SMING_VERSION)); - commandOutput->print(_F("ESP SDK version : ")); - commandOutput->print(system_get_sdk_version()); - commandOutput->println(); -#ifndef DISABLE_NETWORK - commandOutput->printf(_F("lwIP version : %d.%d.%d(%s)\r\n"), LWIP_VERSION_MAJOR, LWIP_VERSION_MINOR, - LWIP_VERSION_REVISION, LWIP_HASH_STR); -#endif - commandOutput->print(_F("Time = ")); - commandOutput->print(SystemClock.getSystemTimeString()); - commandOutput->println(); - commandOutput->printf(_F("System Start Reason : %d\r\n"), system_get_rst_info()->reason); -} - -void CommandHandler::procesEchoCommand(String commandLine, CommandOutput* commandOutput) -{ - debugf("HelpCommand entered"); - commandOutput->print(_F("You entered : '")); - commandOutput->print(commandLine); - commandOutput->println('\''); -} - -void CommandHandler::procesDebugOnCommand(String commandLine, CommandOutput* commandOutput) -{ - Serial.systemDebugOutput(true); - commandOutput->println(_F("Debug set to : On")); -} - -void CommandHandler::procesDebugOffCommand(String commandLine, CommandOutput* commandOutput) -{ - Serial.systemDebugOutput(false); - commandOutput->println(_F("Debug set to : Off")); -} - -void CommandHandler::processCommandOptions(String commandLine, CommandOutput* commandOutput) -{ - Vector commandToken; - int numToken = splitString(commandLine, ' ', commandToken); - bool errorCommand = false; - bool printUsage = false; - - switch(numToken) { - case 2: - if(commandToken[1] == _F("help")) { - printUsage = true; - } - if(commandToken[1] == _F("verbose")) { - commandHandler.setVerboseMode(VERBOSE); - commandOutput->println(_F("Verbose mode selected")); - break; - } - if(commandToken[1] == _F("silent")) { - commandHandler.setVerboseMode(SILENT); - commandOutput->println(_F("Silent mode selected")); - break; - } - errorCommand = true; - break; - case 3: - if(commandToken[1] != _F("prompt")) { - errorCommand = true; - break; - } - commandHandler.setCommandPrompt(commandToken[2]); - commandOutput->print(_F("Prompt set to : ")); - commandOutput->print(commandToken[2]); - commandOutput->println(); - break; - default: - errorCommand = true; - } - if(errorCommand) { - commandOutput->print(_F("Unknown command : ")); - commandOutput->print(commandLine); - commandOutput->println(); - } - if(printUsage) { - commandOutput->println(_F("command usage : \r\n")); - commandOutput->println(_F("command verbose : Set verbose mode")); - commandOutput->println(_F("command silent : Set silent mode")); - commandOutput->println(_F("command prompt 'new prompt' : Set prompt to use")); - } -} - -CommandHandler commandHandler; diff --git a/Sming/Services/CommandProcessing/CommandOutput.cpp b/Sming/Services/CommandProcessing/CommandOutput.cpp deleted file mode 100644 index a8bf385103..0000000000 --- a/Sming/Services/CommandProcessing/CommandOutput.cpp +++ /dev/null @@ -1,44 +0,0 @@ -/* - * CommandOutput.cpp - * - * Created on: 5 jul. 2015 - * Author: Herman - */ - -#include "CommandOutput.h" -#include - -CommandOutput::CommandOutput(Stream* reqStream) : outputStream(reqStream) -{ -} - -CommandOutput::~CommandOutput() -{ - debugf("destruct"); -} - -size_t CommandOutput::write(uint8_t outChar) -{ - if(outputStream) { - return outputStream->write(outChar); - } - -#ifndef DISABLE_NETWORK - if(outputTcpClient) { - char outBuf[1] = {char(outChar)}; - return outputTcpClient->write(outBuf, 1); - } - if(outputSocket) { - if(outChar == '\r') { - outputSocket->sendString(tempSocket); - tempSocket = ""; - } else { - tempSocket += char(outChar); - } - - return 1; - } -#endif - - return 0; -} diff --git a/Sming/Services/CommandProcessing/CommandOutput.h b/Sming/Services/CommandProcessing/CommandOutput.h deleted file mode 100644 index 9ae25228e0..0000000000 --- a/Sming/Services/CommandProcessing/CommandOutput.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * CommandOutput.h - * - * Created on: 5 jul. 2015 - * Author: Herman - */ - -#pragma once - -#include -#include - -#ifndef DISABLE_NETWORK -#include -#include -#endif - -class CommandOutput : public Print -{ -public: -#ifndef DISABLE_NETWORK - CommandOutput(TcpClient* reqClient) : outputTcpClient(reqClient) - { - } - - CommandOutput(WebsocketConnection* reqSocket) : outputSocket(reqSocket) - { - } - -#endif - - CommandOutput(Stream* reqStream); - ~CommandOutput(); - - size_t write(uint8_t outChar); - -#ifndef DISABLE_NETWORK - TcpClient* outputTcpClient = nullptr; - WebsocketConnection* outputSocket = nullptr; -#endif - Stream* outputStream = nullptr; - String tempSocket = ""; -}; diff --git a/Sming/Services/CommandProcessing/CommandProcessingDependencies.h b/Sming/Services/CommandProcessing/CommandProcessingDependencies.h deleted file mode 100644 index 00a48271bb..0000000000 --- a/Sming/Services/CommandProcessing/CommandProcessingDependencies.h +++ /dev/null @@ -1,8 +0,0 @@ -/* - * CommandProcessingDependencies.h - * - * Created on: 2 jul. 2015 - * Author: Herman - */ - -#pragma once diff --git a/Sming/Services/CommandProcessing/CommandProcessingIncludes.h b/Sming/Services/CommandProcessing/CommandProcessingIncludes.h deleted file mode 100644 index 6061ee7ec9..0000000000 --- a/Sming/Services/CommandProcessing/CommandProcessingIncludes.h +++ /dev/null @@ -1,14 +0,0 @@ -/* - * CommandProcessingIncludes.h - * - * Created on: 2 jul. 2015 - * Author: Herman - */ - -#pragma once - -#include "CommandOutput.h" -#include "CommandProcessingDependencies.h" -#include "CommandDelegate.h" -#include "CommandExecutor.h" -#include "CommandHandler.h" diff --git a/Sming/component.mk b/Sming/component.mk index c0e95cf705..e6d132a69e 100644 --- a/Sming/component.mk +++ b/Sming/component.mk @@ -31,14 +31,6 @@ COMPONENT_DOXYGEN_INPUT := \ Wiring \ System -# => Disable CommandExecutor functionality if not used and save some ROM and RAM -COMPONENT_VARS += ENABLE_CMD_EXECUTOR -ENABLE_CMD_EXECUTOR ?= 1 -ifeq ($(ENABLE_CMD_EXECUTOR),1) -COMPONENT_SRCDIRS += Services/CommandProcessing -endif -GLOBAL_CFLAGS += -DENABLE_CMD_EXECUTOR=$(ENABLE_CMD_EXECUTOR) - # RELINK_VARS += DISABLE_NETWORK DISABLE_NETWORK ?= 0 diff --git a/docs/source/framework/services/command-processing/index.rst b/docs/source/framework/services/command-processing/index.rst deleted file mode 100644 index 42eb48fcb8..0000000000 --- a/docs/source/framework/services/command-processing/index.rst +++ /dev/null @@ -1,42 +0,0 @@ -Command Executor -================ - -.. highlight:: c++ - -Introduction ------------- - -Command handler provides a common command line interface. CLI is available for the following remote access methods: - -- Serial -- Telnet -- Websockets - -By default, CLI is disabled. Enable CLI by calling "commandProcessing" on the appropriate access class object, e.g:: - - Serial.commandProcessing(true) - -Commands can be added to and removed from the command handler. Each command will trigger a defined Delegate. - -A welcome message may be shown when a user connects and end of line character may be defined. An automatic "help" display is available. - -Build Variables ---------------- - -.. envvar:: ENABLE_CMD_EXECUTOR - - Default: 1 (ON) - - This feature enables execution of certain commands by registering token handlers for text - received via serial, websocket or telnet connection. If this feature - is not used additional RAM/Flash can be obtained by setting - ``ENABLE_CMD_EXECUTOR=0``. This will save ~1KB RAM and ~3KB of flash - memory. - - -API Documentation ------------------ - -.. doxygengroup:: commandhandler - :content-only: - :members: diff --git a/docs/source/framework/services/index.rst b/docs/source/framework/services/index.rst index 03fd153d9a..5a1a05d18f 100644 --- a/docs/source/framework/services/index.rst +++ b/docs/source/framework/services/index.rst @@ -4,5 +4,4 @@ Services .. toctree:: :maxdepth: 1 - command-processing/index profiling/index diff --git a/docs/source/upgrading/4.7-5.1.rst b/docs/source/upgrading/4.7-5.1.rst new file mode 100644 index 0000000000..a1c8aa0c87 --- /dev/null +++ b/docs/source/upgrading/4.7-5.1.rst @@ -0,0 +1,63 @@ +From v4.7 to v5.1 +================= + +.. highlight:: c++ + +Command Processing +------------------ + +The CommandProcessing service has been refactored and moved to a component. +This means that the following classes ``CommandHandler``, ``CommandExecutor`` and ``CommandOutput`` are no longer available. + + +Enabling +~~~~~~~~ + +The command processing component used to be enabled by setting the directive ``ENABLE_CMD_EXECUTOR`` to 1 in your ``component.mk`` file or during compilation. +This has to be replaced with the directive ``COMPONENT_DEPENDS += CommandProcessing`` in your ``component.mk`` file. + + +Including Header Files +~~~~~~~~~~~~~~~~~~~~~~~ + +To include the command processing headers in your C/C++ application we used to do the following + +For example:: + + #include + +becomes:: + + #include + + +Usage +~~~~~ + +There is no longer a global instance of commandHandler. This means that you will need to create one yourself when you need it. +This can be done using the code below:: + + CommandProcessing::CommandHandler commandHandler; + +In order to register a command the old example code:: + + commandHandler.registerCommand( + CommandDelegate("example", "Example Command", "Application", processExampleCommand)); + +becomes:: + + commandHandler.registerCommand( + CommandProcessing::Command("example", "Example Command", "Application", processExampleCommand)); + +HardwareSerial no longer is dependent on CommandProcessing classes. And the following command will no longer work:: + + Serial.commandProcessing(true); + +The line above has to be replaced with:: + + CommandProcessing::enable(commandProcessing, Serial); + +See the modified samples +:sample:`CommandLine`, +:sample:`TelnetServer` +and :sample:`HttpServer_WebSockets` for details. diff --git a/docs/source/upgrading/index.rst b/docs/source/upgrading/index.rst index a53c6a896f..9677ddf287 100644 --- a/docs/source/upgrading/index.rst +++ b/docs/source/upgrading/index.rst @@ -7,6 +7,7 @@ For newer versions we have dedicated pages. .. toctree:: :maxdepth: 1 + 4.7-5.1 4.6-4.7 4.5-4.6 4.4-4.5 diff --git a/samples/Arducam/app/ArduCamCommand.cpp b/samples/Arducam/app/ArduCamCommand.cpp deleted file mode 100644 index 961fb0a2e7..0000000000 --- a/samples/Arducam/app/ArduCamCommand.cpp +++ /dev/null @@ -1,248 +0,0 @@ - -#include -#include -#include - -ArduCamCommand::ArduCamCommand(ArduCAM* CAM) -{ - debugf("ArduCamCommand Instantiating"); - myCAM = CAM; - imgSize = OV2640_320x240; - imgType = JPEG; -} - -ArduCamCommand::~ArduCamCommand() -{ -} - -void ArduCamCommand::initCommand() -{ - commandHandler.registerCommand(CommandDelegate("set", "ArduCAM config commands", "Application", - CommandFunctionDelegate(&ArduCamCommand::processSetCommands, this))); - // commandHandler.registerCommand( - // CommandDelegate("out", "ArduCAM output commands", "Application", - // CommandFunctionDelegate(&ArduCamCommand::processOutCommands,this))); -} - -void ArduCamCommand::showSettings(CommandOutput* commandOutput) -{ - // review settings - commandOutput->printf("ArduCam Settings:\n"); - commandOutput->printf(" img Type: [%s]\n", getImageType()); - commandOutput->printf(" img Size: [%s]\n", getImageSize()); -}; - -void ArduCamCommand::processSetCommands(String commandLine, CommandOutput* commandOutput) -{ - Vector commandToken; - int numToken = splitString(commandLine, ' ', commandToken); - - if(numToken == 1) { - // review settings - showSettings(commandOutput); - } - // handle command -> set - else if(commandToken[1] == "help") { - *commandOutput << _F("set img [bmp|jpeg]") << endl; - *commandOutput << _F("set size [160|176|320|352|640|800|1024|1280|1600]") << endl; - } - - // handle command -> set - else if(commandToken[1] == "img") { - if(numToken == 3) { - if(commandToken[2] == "bmp") { - // TODO: set image size and init cam - // settings->setImageType(BMP); - set_format(BMP); - } else if(commandToken[2] == "jpg") { - set_format(JPEG); - } else { - *commandOutput << _F("invalid image format [") << commandToken[2] << ']' << endl; - } - } else { - *commandOutput << _F("Syntax: set img [bmp|jpeg]") << endl; - } - showSettings(commandOutput); - } - - // handle command -> settings - else if(commandToken[1] == "size") { - if(numToken == 3) { - if(commandToken[2] == "160") { - imgSize = OV2640_160x120; - myCAM->OV2640_set_JPEG_size(OV2640_160x120); - set_format(JPEG); - } else if(commandToken[2] == "176") { - imgSize = OV2640_176x144; - myCAM->OV2640_set_JPEG_size(OV2640_176x144); - set_format(JPEG); - } else if(commandToken[2] == "320") { - imgSize = OV2640_320x240; - myCAM->OV2640_set_JPEG_size(OV2640_320x240); - } else if(commandToken[2] == "352") { - imgSize = OV2640_352x288; - myCAM->OV2640_set_JPEG_size(OV2640_352x288); - set_format(JPEG); - } else if(commandToken[2] == "640") { - imgSize = OV2640_640x480; - myCAM->OV2640_set_JPEG_size(OV2640_640x480); - set_format(JPEG); - } else if(commandToken[2] == "800") { - imgSize = OV2640_800x600; - myCAM->OV2640_set_JPEG_size(OV2640_800x600); - set_format(JPEG); - } else if(commandToken[2] == "1024") { - imgSize = OV2640_1024x768; - myCAM->OV2640_set_JPEG_size(OV2640_1024x768); - set_format(JPEG); - } else if(commandToken[2] == "1280") { - imgSize = OV2640_1280x1024; - myCAM->OV2640_set_JPEG_size(OV2640_1280x1024); - set_format(JPEG); - } else if(commandToken[2] == "1600") { - imgSize = OV2640_1600x1200; - myCAM->OV2640_set_JPEG_size(OV2640_1600x1200); - set_format(JPEG); - } else { - *commandOutput << _F("invalid size definition[") << commandToken[2] << ']' << endl; - } - } else { - *commandOutput << _F("Syntax: set size [160|176|320|352|640|800|1024|1280|1600]") << endl; - } - showSettings(commandOutput); - } -} - -void ArduCamCommand::set_size(String size) -{ - if(size == "160x120") { - imgSize = OV2640_160x120; - myCAM->OV2640_set_JPEG_size(OV2640_160x120); - set_format(JPEG); - } else if(size == "176x144") { - imgSize = OV2640_176x144; - myCAM->OV2640_set_JPEG_size(OV2640_176x144); - set_format(JPEG); - } else if(size == "320x240") { - imgSize = OV2640_320x240; - myCAM->OV2640_set_JPEG_size(OV2640_320x240); - } else if(size == "352x288") { - imgSize = OV2640_352x288; - myCAM->OV2640_set_JPEG_size(OV2640_352x288); - set_format(JPEG); - } else if(size == "640x480") { - imgSize = OV2640_640x480; - myCAM->OV2640_set_JPEG_size(OV2640_640x480); - set_format(JPEG); - } else if(size == "800x600") { - imgSize = OV2640_800x600; - myCAM->OV2640_set_JPEG_size(OV2640_800x600); - set_format(JPEG); - } else if(size == "1024x768") { - imgSize = OV2640_1024x768; - myCAM->OV2640_set_JPEG_size(OV2640_1024x768); - set_format(JPEG); - } else if(size == "1280x1024") { - imgSize = OV2640_1280x1024; - myCAM->OV2640_set_JPEG_size(OV2640_1280x1024); - set_format(JPEG); - } else if(size == "1600x1200") { - imgSize = OV2640_1600x1200; - myCAM->OV2640_set_JPEG_size(OV2640_1600x1200); - set_format(JPEG); - } else { - debugf("ERROR: invalid size definition[%s]\r\n", size.c_str()); - } -} - -void ArduCamCommand::set_type(String type) -{ - if(type == "BMP") { - myCAM->set_format(BMP); - if(imgType != BMP) { - // reset the cam - myCAM->InitCAM(); - imgType = BMP; - imgSize = OV2640_320x240; - } - } else { - myCAM->set_format(JPEG); - // reset the cam - if(imgType != JPEG) { - // reset the cam - myCAM->InitCAM(); - myCAM->OV2640_set_JPEG_size(imgSize); - imgType = JPEG; - } - } -} - -void ArduCamCommand::set_format(uint8 type) -{ - if(type == BMP) { - myCAM->set_format(BMP); - if(imgType != BMP) { - // reset the cam - myCAM->InitCAM(); - imgType = BMP; - imgSize = OV2640_320x240; - } - } else { - myCAM->set_format(JPEG); - // reset the cam - if(imgType != JPEG) { - // reset the cam - myCAM->InitCAM(); - myCAM->OV2640_set_JPEG_size(imgSize); - imgType = JPEG; - } - } -} - -const char* ArduCamCommand::getImageType() -{ - switch(imgType) { - case JPEG: - return "JPEG"; - case BMP: - default: - return "BMP"; - } -} - -const char* ArduCamCommand::getContentType() -{ - switch(imgType) { - case JPEG: - return "image/jpeg"; - case BMP: - default: - return "image/x-ms-bmp"; - } -} - -const char* ArduCamCommand::getImageSize() -{ - switch(imgSize) { - case OV2640_1600x1200: - return "1600x1200"; - case OV2640_1280x1024: - return "1280x1024"; - case OV2640_1024x768: - return "1024x768"; - case OV2640_800x600: - return "800x600"; - case OV2640_640x480: - return "640x480"; - case OV2640_352x288: - return "352x288"; - case OV2640_320x240: - return "320x240"; - case OV2640_176x144: - return "176x144"; - case OV2640_160x120: - return "160x120"; - default: - return "320x240"; - } -} diff --git a/samples/Arducam/component.mk b/samples/Arducam/component.mk deleted file mode 100644 index 76533c24f2..0000000000 --- a/samples/Arducam/component.mk +++ /dev/null @@ -1,3 +0,0 @@ -ARDUINO_LIBRARIES := ArduCAM -HWCONFIG = spiffs-2m -SPIFF_FILES = web/build diff --git a/samples/Arducam/include/ArduCamCommand.h b/samples/Arducam/include/ArduCamCommand.h deleted file mode 100644 index 80493ea1fc..0000000000 --- a/samples/Arducam/include/ArduCamCommand.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * ArduCamCommand.h - * - */ - -#ifndef ARDUCAM_COMMAND_H_ -#define ARDUCAM_COMMAND_H_ - -#include "WString.h" -#include -#include -#include - -class ArduCamCommand -{ -public: - ArduCamCommand(ArduCAM* CAM); - virtual ~ArduCamCommand(); - void initCommand(); - const char* getContentType(); - void set_size(String size); - void set_type(String type); - -private: - bool status = true; - ArduCAM* myCAM; - uint8 imgType; - uint8 imgSize; - void processSetCommands(String commandLine, CommandOutput* commandOutput); - - void set_format(uint8 type); - void showSettings(CommandOutput* commandOutput); - - const char* getImageType(); - const char* getImageSize(); -}; - -#endif /* SMINGCORE_DEBUG_H_ */ diff --git a/samples/CommandProcessing_Debug/README.rst b/samples/CommandProcessing_Debug/README.rst deleted file mode 100644 index 6686ed6a04..0000000000 --- a/samples/CommandProcessing_Debug/README.rst +++ /dev/null @@ -1,4 +0,0 @@ -CommandProcessing Debug -======================= - -Demonstrates Sming's command handling capability via HTTP, FTP and Telnet interfaces. diff --git a/samples/CommandProcessing_Debug/app/ExampleCommand.cpp b/samples/CommandProcessing_Debug/app/ExampleCommand.cpp deleted file mode 100644 index 9bc37bbb82..0000000000 --- a/samples/CommandProcessing_Debug/app/ExampleCommand.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Debug.cpp - * - */ - -#include - -ExampleCommand::ExampleCommand() -{ - debugf("ExampleCommand Instantiating"); -} - -ExampleCommand::~ExampleCommand() -{ -} - -void ExampleCommand::initCommand() -{ - commandHandler.registerCommand( - CommandDelegate("example", "Example Command from Class", "Application", - CommandFunctionDelegate(&ExampleCommand::processExampleCommands, this))); -} - -void ExampleCommand::processExampleCommands(String commandLine, CommandOutput* commandOutput) -{ - Vector commandToken; - int numToken = splitString(commandLine, ' ', commandToken); - - if(numToken == 1) { - commandOutput->printf("Example Commands available : \r\n"); - commandOutput->printf("on : Set example status ON\r\n"); - commandOutput->printf("off : Set example status OFF\r\n"); - commandOutput->printf("status : Show example status\r\n"); - } else { - if(commandToken[1] == "on") { - status = true; - commandOutput->printf("Status ON\r\n"); - } else if(commandToken[1] == "off") { - status = false; - commandOutput->printf("Status OFF\r\n"); - } else if(commandToken[1] == "status") { - String tempString = status ? "ON" : "OFF"; - commandOutput->printf("Example Status is %s\r\n", tempString.c_str()); - }; - } -} diff --git a/samples/CommandProcessing_Debug/app/application.cpp b/samples/CommandProcessing_Debug/app/application.cpp deleted file mode 100644 index e9b3ad4e32..0000000000 --- a/samples/CommandProcessing_Debug/app/application.cpp +++ /dev/null @@ -1,136 +0,0 @@ -#include -#include -#include -#include -#include - -// If you want, you can define WiFi settings globally in Eclipse Environment Variables -#ifndef WIFI_SSID -#define WIFI_SSID "PleaseEnterSSID" // Put your SSID and password here -#define WIFI_PWD "PleaseEnterPass" -#endif - -HttpServer server; -FtpServer ftp; -TelnetServer telnet; - -Timer msgTimer; - -ExampleCommand exampleCommand; - -void onIndex(HttpRequest& request, HttpResponse& response) -{ - TemplateFileStream* tmpl = new TemplateFileStream("index.html"); - auto& vars = tmpl->variables(); - //vars["counter"] = String(counter); - response.sendNamedStream(tmpl); // this template object will be deleted automatically -} - -void onFile(HttpRequest& request, HttpResponse& response) -{ - String file = request.uri.getRelativePath(); - - if(file[0] == '.') - response.code = HTTP_STATUS_FORBIDDEN; - else { - response.setCache(86400, true); // It's important to use cache for better performance. - response.sendFile(file); - } -} - -int msgCount = 0; - -void wsConnected(WebsocketConnection& socket) -{ - Serial.println(_F("Socket connected")); -} - -void wsMessageReceived(WebsocketConnection& socket, const String& message) -{ - Serial.println(_F("WebsocketConnection message received:")); - Serial.println(message); - String response = "Echo: " + message; - socket.sendString(response); -} - -void wsBinaryReceived(WebsocketConnection& socket, uint8_t* data, size_t size) -{ - Serial << _F("Websocket binary data received, size: ") << size << endl; -} - -void wsDisconnected(WebsocketConnection& socket) -{ - Serial.println(_F("Socket disconnected")); -} - -void processApplicationCommands(String commandLine, CommandOutput* commandOutput) -{ - commandOutput->println(_F("This command is handle by the application")); -} - -void StartServers() -{ - server.listen(80); - server.paths.set("/", onIndex); - server.paths.setDefault(onFile); - - // Web Sockets configuration - WebsocketResource* wsResource = new WebsocketResource(); - wsResource->setConnectionHandler(wsConnected); - wsResource->setMessageHandler(wsMessageReceived); - wsResource->setBinaryHandler(wsBinaryReceived); - wsResource->setDisconnectionHandler(wsDisconnected); - - server.paths.set("/ws", wsResource); - - Serial.println(_F("\r\n=== WEB SERVER STARTED ===")); - Serial.println(WifiStation.getIP()); - Serial.println(_F("==============================\r\n")); - - // Start FTP server - ftp.listen(21); - ftp.addUser("me", "123"); // FTP account - - Serial.println(_F("\r\n=== FTP SERVER STARTED ===")); - Serial.println(_F("==============================\r\n")); - - telnet.listen(23); - telnet.enableDebug(true); - - Serial.println(_F("\r\n=== TelnetServer SERVER STARTED ===")); - Serial.println(_F("==============================\r\n")); -} - -void startExampleApplicationCommand() -{ - exampleCommand.initCommand(); - commandHandler.registerCommand( - CommandDelegate(F("example"), F("Example Command from Class"), F("Application"), processApplicationCommands)); -} - -void gotIP(IpAddress ip, IpAddress netmask, IpAddress gateway) -{ - StartServers(); - - startExampleApplicationCommand(); -} - -void init() -{ - spiffs_mount(); // Mount file system, in order to work with files - - Serial.begin(SERIAL_BAUD_RATE); // 115200 by default - - commandHandler.registerSystemCommands(); - Debug.setDebug(Serial); - - Serial.systemDebugOutput(true); // Enable debug output to serial - Serial.commandProcessing(true); - - WifiStation.enable(true); - WifiStation.config(WIFI_SSID, WIFI_PWD); - WifiAccessPoint.enable(false); - - // Run our method when station was connected to AP - WifiEvents.onStationGotIP(gotIP); -} diff --git a/samples/CommandProcessing_Debug/component.mk b/samples/CommandProcessing_Debug/component.mk deleted file mode 100644 index 372d718852..0000000000 --- a/samples/CommandProcessing_Debug/component.mk +++ /dev/null @@ -1 +0,0 @@ -HWCONFIG := spiffs-2m diff --git a/samples/CommandProcessing_Debug/include/ExampleCommand.h b/samples/CommandProcessing_Debug/include/ExampleCommand.h deleted file mode 100644 index f16501af40..0000000000 --- a/samples/CommandProcessing_Debug/include/ExampleCommand.h +++ /dev/null @@ -1,21 +0,0 @@ -/* - * ExampleCommand.h - * - */ - -#pragma once - -#include "WString.h" -#include - -class ExampleCommand -{ -public: - ExampleCommand(); - virtual ~ExampleCommand(); - void initCommand(); - -private: - bool status = true; - void processExampleCommands(String commandLine, CommandOutput* commandOutput); -}; diff --git a/samples/HttpServer_WebSockets/Kconfig b/samples/HttpServer_WebSockets/Kconfig new file mode 100644 index 0000000000..8744832f6b --- /dev/null +++ b/samples/HttpServer_WebSockets/Kconfig @@ -0,0 +1,11 @@ +# +# For a description of the syntax of this configuration file, +# see kconfig/kconfig-language.txt. +# +mainmenu "Sample Configuration" + + menu "Command Hanler" + config ENABLE_CMD_HANDLER + bool "Enable command handler functionality" + default y + endmenu \ No newline at end of file diff --git a/samples/HttpServer_WebSockets/app/application.cpp b/samples/HttpServer_WebSockets/app/application.cpp index 77062a5bfd..193966e636 100644 --- a/samples/HttpServer_WebSockets/app/application.cpp +++ b/samples/HttpServer_WebSockets/app/application.cpp @@ -2,6 +2,11 @@ #include #include "CUserData.h" +#if ENABLE_CMD_HANDLER +#include +CommandProcessing::Handler commandHandler; +#endif + // If you want, you can define WiFi settings globally in Eclipse Environment Variables #ifndef WIFI_SSID #define WIFI_SSID "PleaseEnterSSID" // Put your SSID and password here @@ -76,6 +81,33 @@ void wsMessageReceived(WebsocketConnection& socket, const String& message) } } +#if ENABLE_CMD_HANDLER +void wsCommandReceived(WebsocketConnection& socket, const String& message) +{ + String response = commandHandler.processNow(message.c_str(), message.length()); + socket.sendString(response); + + //Normally you would use dynamic cast but just be careful not to convert to wrong object type! + auto user = reinterpret_cast(socket.getUserData()); + if(user != nullptr) { + user->printMessage(socket, message); + } +} + +void processShutdownCommand(String commandLine, ReadWriteStream& commandOutput) +{ + // Don't shutdown immediately, wait a bit to allow messages to propagate + auto timer = new SimpleTimer; + timer->initializeMs<1000>( + [](void* timer) { + delete static_cast(timer); + server.shutdown(); + }, + timer); + timer->startOnce(); +} +#endif + void wsBinaryReceived(WebsocketConnection& socket, uint8_t* data, size_t size) { Serial << _F("Websocket binary data received, size: ") << size << endl; @@ -106,6 +138,10 @@ void startWebServer() auto wsResource = new WebsocketResource(); wsResource->setConnectionHandler(wsConnected); wsResource->setMessageHandler(wsMessageReceived); +#if ENABLE_CMD_HANDLER + wsResource->setMessageHandler(wsCommandReceived); +#endif + wsResource->setBinaryHandler(wsBinaryReceived); wsResource->setDisconnectionHandler(wsDisconnected); @@ -127,6 +163,12 @@ void init() { spiffs_mount(); // Mount file system, in order to work with files +#if ENABLE_CMD_HANDLER + commandHandler.registerSystemCommands(); + commandHandler.registerCommand( + CommandProcessing::Command("shutdown", "Shutdown Server Command", "Application", processShutdownCommand)); +#endif + Serial.begin(SERIAL_BAUD_RATE); // 115200 by default Serial.systemDebugOutput(true); // Enable debug output to serial diff --git a/samples/HttpServer_WebSockets/component.mk b/samples/HttpServer_WebSockets/component.mk index 372d718852..25bbd74f26 100644 --- a/samples/HttpServer_WebSockets/component.mk +++ b/samples/HttpServer_WebSockets/component.mk @@ -1 +1,10 @@ HWCONFIG := spiffs-2m + +COMPONENT_VARS += ENABLE_CMD_HANDLER +ENABLE_CMD_HANDLER ?= 1 + +ifeq ($(ENABLE_CMD_HANDLER), 1) + COMPONENT_DEPENDS += CommandProcessing +endif + +APP_CFLAGS += -DENABLE_CMD_HANDLER=$(ENABLE_CMD_HANDLER) \ No newline at end of file diff --git a/samples/Telnet_Server/README.rst b/samples/Telnet_Server/README.rst deleted file mode 100644 index 10803fd21c..0000000000 --- a/samples/Telnet_Server/README.rst +++ /dev/null @@ -1,4 +0,0 @@ -Telnet Server -============= - -Demonstrates a simple Telnet server application. diff --git a/samples/Telnet_Server/app/application.cpp b/samples/Telnet_Server/app/application.cpp deleted file mode 100644 index dfa10d920c..0000000000 --- a/samples/Telnet_Server/app/application.cpp +++ /dev/null @@ -1,134 +0,0 @@ -#include -#include -#include "Services/CommandProcessing/CommandProcessingIncludes.h" -#include - -// If you want, you can define WiFi settings globally in Eclipse Environment Variables -#ifndef WIFI_SSID -#define WIFI_SSID "PleaseEnterSSID" // Put you SSID and Password here -#define WIFI_PWD "PleaseEnterPass" -#endif - -Timer memoryTimer; -int savedHeap = 0; - -void checkHeap() -{ - int currentHeap = system_get_free_heap_size(); - if(currentHeap != savedHeap) { - Debug << _F("Heap change, current = ") << currentHeap << endl; - savedHeap = currentHeap; - } -} - -void applicationCommand(String commandLine, CommandOutput* commandOutput) -{ - *commandOutput << _F("Hello from Telnet Example application") << endl - << _F("You entered : '") << commandLine << '\'' << endl - << _F("Tokenized commandLine is : ") << endl; - - Vector commandToken; - unsigned numToken = splitString(commandLine, ' ', commandToken); - for(unsigned i = 0; i < numToken; i++) { - *commandOutput << i << " : " << commandToken[i] << endl; - } -} - -void appheapCommand(String commandLine, CommandOutput* commandOutput) -{ - Vector commandToken; - int numToken = splitString(commandLine, ' ', commandToken); - if(numToken != 2) { - commandOutput->println(_F("Usage appheap on/off/now")); - } else if(commandToken[1] == "on") { - commandOutput->println(_F("Timer heap display started")); - savedHeap = 0; - memoryTimer.initializeMs(250, checkHeap).start(); - } else if(commandToken[1] == "off") { - commandOutput->println(_F("Timer heap display stopped")); - savedHeap = 0; - memoryTimer.stop(); - } else if(commandToken[1] == "now") { - *commandOutput << _F("Heap current free = ") << system_get_free_heap_size() << endl; - } else { - commandOutput->println(_F("Usage appheap on/off/now")); - } -} - -void tcpServerClientConnected(TcpClient* client) -{ - debugf("Application onClientCallback : %s\r\n", client->getRemoteIp().toString().c_str()); -} - -bool tcpServerClientReceive(TcpClient& client, char* data, int size) -{ - debugf("Application DataCallback : %s, %d bytes \r\n", client.getRemoteIp().toString().c_str(), size); - debugf("Data : %s", data); - client.sendString(F("sendString data\r\n"), false); - client.writeString(F("writeString data\r\n"), 0); - if(strcmp(data, "close") == 0) { - debugf("Closing client"); - client.close(); - }; - return true; -} - -void tcpServerClientComplete(TcpClient& client, bool successful) -{ - debugf("Application CompleteCallback : %s \r\n", client.getRemoteIp().toString().c_str()); -} - -TcpServer tcpServer(tcpServerClientConnected, tcpServerClientReceive, tcpServerClientComplete); -TelnetServer telnetServer; - -void startServers() -{ - tcpServer.listen(8023); - - Serial.println(_F("\r\n" - "=== TCP SERVER Port 8023 STARTED ===")); - Serial.println(WifiStation.getIP()); - Serial.println(_F("====================================\r\n")); - - telnetServer.listen(23); - - Serial.println(_F("\r\n" - "=== Telnet SERVER Port 23 STARTED ===")); - Serial.println(WifiStation.getIP()); - Serial.println(_F("=====================================\r\n")); - - commandHandler.registerCommand(CommandDelegate( - F("application"), F("This command is defined by the application\r\n"), F("testGroup"), applicationCommand)); -} - -void connectFail(const String& ssid, MacAddress bssid, WifiDisconnectReason reason) -{ - debugf("I'm NOT CONNECTED!"); -} - -void gotIP(IpAddress ip, IpAddress netmask, IpAddress gateway) -{ - startServers(); -} - -void init() -{ - Serial.begin(SERIAL_BAUD_RATE); // 115200 by default - Serial.systemDebugOutput(true); // Enable debug output to serial - Serial.commandProcessing(true); - WifiStation.enable(true); - WifiStation.config(WIFI_SSID, WIFI_PWD); - WifiAccessPoint.enable(false); - - // Run our method when station was connected to AP - WifiEvents.onStationDisconnect(connectFail); - WifiEvents.onStationGotIP(gotIP); - Debug.setDebug(Serial); - Debug.initCommand(); - Debug.start(); - Debug.println(_F("This is the debug output")); - telnetServer.enableDebug(true); /* is default but here to show possibility */ - commandHandler.registerCommand(CommandDelegate(F("appheap"), F("Usage appheap on/off/now for heapdisplay\r\n"), - F("testGroup"), appheapCommand)); - memoryTimer.initializeMs(250, checkHeap).start(); -} From e0ef139c0b7f571fe5a366ff581ce112f53bced5 Mon Sep 17 00:00:00 2001 From: slaff Date: Tue, 12 Dec 2023 11:39:10 +0100 Subject: [PATCH 24/28] Fixed typo in NimBLE readme. --- Sming/Libraries/NimBLE/README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sming/Libraries/NimBLE/README.rst b/Sming/Libraries/NimBLE/README.rst index 0313349fb2..9110937f52 100644 --- a/Sming/Libraries/NimBLE/README.rst +++ b/Sming/Libraries/NimBLE/README.rst @@ -11,7 +11,7 @@ It is more suited to resource constrained devices than bluedroid and has now bee Using ----- -1. Add ``COMPONENT_DEPENDS += NimBLE`` to your application componenent.mk file. +1. Add ``COMPONENT_DEPENDS += NimBLE`` to your application component.mk file. 2. Add these lines to your application:: #include From 7cd0f606d1ab4d47e01ee0d2652dd2518174e3fe Mon Sep 17 00:00:00 2001 From: slaff Date: Wed, 13 Dec 2023 14:37:28 +0100 Subject: [PATCH 25/28] Remove leftovers in TelnetServer sample. (#2690) --- .../samples/TelnetServer/app/application.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Sming/Components/CommandProcessing/samples/TelnetServer/app/application.cpp b/Sming/Components/CommandProcessing/samples/TelnetServer/app/application.cpp index 4c339ff3b7..2d688cf245 100644 --- a/Sming/Components/CommandProcessing/samples/TelnetServer/app/application.cpp +++ b/Sming/Components/CommandProcessing/samples/TelnetServer/app/application.cpp @@ -52,10 +52,7 @@ void init() { Serial.begin(SERIAL_BAUD_RATE); // 115200 by default Serial.systemDebugOutput(true); // Enable debug output to serial - - // Process commands from serial commandHandler.setVerbose(true); - CommandProcessing::enable(commandHandler, Serial); WifiStation.enable(true); WifiStation.config(WIFI_SSID, WIFI_PWD); @@ -63,6 +60,6 @@ void init() WifiEvents.onStationGotIP(gotIP); - // set command handlers for cam + // set command handlers initCommands(); } From 1ba2c2dc4a1997ab791607927508ce6591e034ae Mon Sep 17 00:00:00 2001 From: slaff Date: Mon, 18 Dec 2023 09:45:44 +0100 Subject: [PATCH 26/28] Feature: add support for the wokwi simulator (#2688) More information from here: https://sming.readthedocs.io/en/latest/experimental/wokwi.html * Add mergeflash target which merges all partitions into a single file. * Added wokwi-support. Make ide-vscode workspace with ENABLE_WORKI=1 . * Add Wokwi documentation. * Add Wokwi debug support. --- Sming/Components/esptool/component.mk | 7 ++ Sming/component.mk | 5 + Tools/ide/vscode/setup.py | 86 ++++++++++++------ Tools/ide/vscode/template/wokwi/diagram.json | 18 ++++ .../ide/vscode/template/wokwi/extensions.json | 3 + Tools/ide/vscode/template/wokwi/launch.json | 18 ++++ Tools/ide/vscode/template/wokwi/wokwi.toml | 5 + docs/source/experimental/index.rst | 1 + docs/source/experimental/wokwi-debug.jpg | Bin 0 -> 70195 bytes docs/source/experimental/wokwi.rst | 61 +++++++++++++ 10 files changed, 176 insertions(+), 28 deletions(-) create mode 100644 Tools/ide/vscode/template/wokwi/diagram.json create mode 100644 Tools/ide/vscode/template/wokwi/extensions.json create mode 100644 Tools/ide/vscode/template/wokwi/launch.json create mode 100644 Tools/ide/vscode/template/wokwi/wokwi.toml create mode 100644 docs/source/experimental/wokwi-debug.jpg create mode 100644 docs/source/experimental/wokwi.rst diff --git a/Sming/Components/esptool/component.mk b/Sming/Components/esptool/component.mk index 934bbf0805..3f82c8e219 100644 --- a/Sming/Components/esptool/component.mk +++ b/Sming/Components/esptool/component.mk @@ -51,6 +51,13 @@ define WriteFlash ) endef +define MergeFlash + $(if $1,\ + $(info MergeFlash $1) \ + $(call ESPTOOL_EXECUTE,merge_bin -o $2 $(flashimageoptions) $(subst =, ,$1)) \ + ) +endef + # Verify flash against file contents # $1 -> List of `Offset=File` chunks define VerifyFlash diff --git a/Sming/component.mk b/Sming/component.mk index e6d132a69e..21c69dc820 100644 --- a/Sming/component.mk +++ b/Sming/component.mk @@ -133,6 +133,11 @@ else ifneq ($(SMING_ARCH),Host) $(TERMINAL) endif +.PHONY: mergeflash +mergeflash: all ##Merge the boot loader and all defined partition images into a single flash file + $(Q) $(call CheckPartitionChunks,$(FLASH_PARTITION_CHUNKS)) + $(call MergeFlash,$(FLASH_BOOT_CHUNKS) $(FLASH_MAP_CHUNK) $(FLASH_PARTITION_CHUNKS),$(FW_BASE)/app-merged.bin) + .PHONY: verifyflash verifyflash: ##Read all flash sections and verify against source $(Q) $(call CheckPartitionChunks,$(FLASH_PARTITION_CHUNKS)) diff --git a/Tools/ide/vscode/setup.py b/Tools/ide/vscode/setup.py index a9b2aceb2f..eda0924702 100644 --- a/Tools/ide/vscode/setup.py +++ b/Tools/ide/vscode/setup.py @@ -58,36 +58,46 @@ def update_launch(): filename = '.vscode/launch.json' launch = load_json(filename, False) template = load_template('launch.json', appPath) + if env.get('ENABLE_WOKWI'): + wokwi_template = load_template('wokwi/launch.json', appPath) + template["configurations"].extend(wokwi_template["configurations"]) if launch is None: launch = template.copy() configurations = get_property(launch, 'configurations', []) - config_name = "%s GDB" % env['SMING_ARCH'] - config = find_object(configurations, config_name) - template_config = find_object(template['configurations'], config_name) - if template_config is None: - print("Warning: Template launch configuration '%s' not found" % config_name) - elif not config is None: - configurations.remove(config) - config = template_config.copy() - configurations.append(config) - - if config is None: - return - config['miDebuggerPath'] = find_tool(env['GDB']) - dbgargs = "-x ${env:SMING_HOME}/Arch/%s/Components/gdbstub/gdbcmds" % env['SMING_ARCH'] - if env['SMING_ARCH'] == 'Host': - args = [] - args += env['CLI_TARGET_OPTIONS'].split() - args += ["--"] - args += env['HOST_PARAMETERS'].split() - config['args'] = args - else: - if not env.isWsl(): - dbgargs += " -b %s" % env.resolve('${COM_SPEED_GDB}') - config['miDebuggerServerAddress'] = env.resolve('${COM_PORT_GDB}') - config['miDebuggerArgs'] = dbgargs - config['program'] = "${workspaceFolder}/" + env.resolve('${TARGET_OUT_0}') + wokwi_config_name = "Wokwi GDB" + config_names = ["%s GDB" % env['SMING_ARCH']] + if env.get('ENABLE_WOKWI'): + config_names.append(wokwi_config_name) + for config_name in config_names: + config = find_object(configurations, config_name) + template_config = find_object(template['configurations'], config_name) + if template_config is None: + print("Warning: Template launch configuration '%s' not found" % config_name) + elif not config is None: + configurations.remove(config) + config = template_config.copy() + configurations.append(config) + + if config is None: + return + + config['miDebuggerPath'] = find_tool(env['GDB']) + dbgargs = "-x ${env:SMING_HOME}/Arch/%s/Components/gdbstub/gdbcmds" % env['SMING_ARCH'] + if env['SMING_ARCH'] == 'Host': + args = [] + args += env['CLI_TARGET_OPTIONS'].split() + args += ["--"] + args += env['HOST_PARAMETERS'].split() + config['args'] = args + else: + if not env.isWsl(): + dbgargs += " -b %s" % env.resolve('${COM_SPEED_GDB}') + if config_name != wokwi_config_name: + config['miDebuggerServerAddress'] = env.resolve('${COM_PORT_GDB}') + if config_name != wokwi_config_name: + config['miDebuggerArgs'] = dbgargs + config['program'] = "${workspaceFolder}/" + env.resolve('${TARGET_OUT_0}') save_json(launch, filename) @@ -103,6 +113,26 @@ def update_workspace(): schemas += [schema] save_json(ws, filename) +def update_wokwi(): + filename = '.vscode/extensions.json' + extensions = load_json(filename, False) + template = load_template('wokwi/extensions.json', appPath) + if extensions is None: + extensions = template.copy() + save_json(extensions, filename) + return + extensions["recommendations"] = extensions["recommendations"] + list(set(template["recommendations"]) - set(extensions["recommendations"])) + save_json(extensions, filename) + + if not os.path.exists('diagram.json'): + diagrams_template = load_template('wokwi/diagram.json', appPath) + save_json(diagrams_template, 'diagram.json') + + if not os.path.exists('wokwi.toml'): + source = open(appPath + '/template/wokwi/wokwi.toml', 'r').read() + source = env.resolve(source) + open('wokwi.toml', 'w+').write(source) + def main(): if not env['SMING_HOME'] or not env['SMING_ARCH']: sys.exit(1) @@ -113,9 +143,9 @@ def main(): update_intellisense() update_tasks() update_launch() + if env.get('ENABLE_WOKWI'): + update_wokwi() update_workspace() - - if __name__ == '__main__': main() diff --git a/Tools/ide/vscode/template/wokwi/diagram.json b/Tools/ide/vscode/template/wokwi/diagram.json new file mode 100644 index 0000000000..05533b6286 --- /dev/null +++ b/Tools/ide/vscode/template/wokwi/diagram.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "author": "Sming Framework", + "editor": "wokwi", + "parts": [ + { + "type": "wokwi-esp32-devkit-v1", + "id": "esp", + "top": 0, + "left": 0, + "attrs": {} + } + ], + "connections": [ + ["esp:TX0", "$serialMonitor:RX", "", []], + ["esp:RX0", "$serialMonitor:TX", "", []] + ] +} diff --git a/Tools/ide/vscode/template/wokwi/extensions.json b/Tools/ide/vscode/template/wokwi/extensions.json new file mode 100644 index 0000000000..b7d371a050 --- /dev/null +++ b/Tools/ide/vscode/template/wokwi/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["wokwi.wokwi-vscode"] +} diff --git a/Tools/ide/vscode/template/wokwi/launch.json b/Tools/ide/vscode/template/wokwi/launch.json new file mode 100644 index 0000000000..81bb464108 --- /dev/null +++ b/Tools/ide/vscode/template/wokwi/launch.json @@ -0,0 +1,18 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Wokwi GDB", + "type": "cppdbg", + "request": "launch", + "program": "", + "args": [], + "stopAtEntry": true, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": true, + "MIMode": "gdb", + "miDebuggerServerAddress": "localhost:3333" + } + ] +} diff --git a/Tools/ide/vscode/template/wokwi/wokwi.toml b/Tools/ide/vscode/template/wokwi/wokwi.toml new file mode 100644 index 0000000000..f54dca4f23 --- /dev/null +++ b/Tools/ide/vscode/template/wokwi/wokwi.toml @@ -0,0 +1,5 @@ +[wokwi] +version=1 +firmware='${FW_BASE}/app-merged.bin' +elf='${TARGET_OUT_0}' +gdbServerPort=3333 diff --git a/docs/source/experimental/index.rst b/docs/source/experimental/index.rst index 00e19e17d5..0a5859ac45 100644 --- a/docs/source/experimental/index.rst +++ b/docs/source/experimental/index.rst @@ -7,3 +7,4 @@ Experimental Stuff httpserver-ssl signed-ota + wokwi diff --git a/docs/source/experimental/wokwi-debug.jpg b/docs/source/experimental/wokwi-debug.jpg new file mode 100644 index 0000000000000000000000000000000000000000..11c2380b0416164443df798083f0b728af7a5eeb GIT binary patch literal 70195 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 W6b){A{0`n(x&X;krQO1Rm;W1WSNi|} literal 0 HcmV?d00001 diff --git a/docs/source/experimental/wokwi.rst b/docs/source/experimental/wokwi.rst new file mode 100644 index 0000000000..cabbc5bd25 --- /dev/null +++ b/docs/source/experimental/wokwi.rst @@ -0,0 +1,61 @@ +**************** +Simulator: Wokwi +**************** + +.. highlight:: bash + +* At the moment Sming and Wokwi have experimenta support for the Esp32 architecture. +* Only VS Code IDE is supported +* The Esp32 simulator lacks certain features and may encounter failures; hence, it is recommended for use with simple applications. + +Wokwi Support into VS Code +========================== + +To integrate Wokwi support into your VS Code, run the following command from your application's root directory:: + + make ide-vscode ENABLE_WOKWI=1 SMING_ARCH=Esp32 + +Install the recommended extensions +---------------------------------- + +Ensure that you have installed the newly recommended extensions. If none appear, manually install the ``wokwi.wokwi-vscode`` extension. + +Merge all partitions to a single flash file +------------------------------------------- + +From the root directory of your application, run the following command to consolidate all partitions for the simulator and flash them into a single file:: + + make mergeflash SMING_ARCH=Esp32 + +Usage +===== + +Basic_Blink +----------- + +The Basic_Blink sample can be compiled and executed directly on a simulator before deploying it to a physical microcontroller. +Follow the commands below to get started:: + + cd $SMING_ARCH/../samples/Basic_Blink + make ide-vscode ENABLE_WOKWI=1 SMING_ARCH=Esp32 + make mergedflash + +Once the compilation is complete, open the folder in VS Code, install the recommended extensions, and either open the ``diagram.json`` file or press F1 and type ``Wokwi``. +From the options, choose to start the Wokwi simulator. + +Debugging +========= + +Running the Basic_Blink sample in the simulator enables you to debug it directly in VS Code. +Set a breakpoint in the ``init`` function in the Basic_Blink ``app/application.cpp`` file. +Press F1 and select "Start Simulator and Wait for Debugger." In the Launch configurations, choose "Wokwi GDB" and click the play button. +This initiates a new debugging session, allowing you to debug the code running in the simulator. + +.. image:: wokwi-debug.jpg + :height: 192px + +Diagram Editor +============== + +The ``diagram.json`` file, which includes elements and their connections, can be edited on the `Wokwi official website `__. +You can add new elements such as extra LEDs or servos. Ensure to copy the modified contents of the diagram.json from the website to your local environment. From b7aa43ed3f8477b9954a7b7e23acb72f36b37a1c Mon Sep 17 00:00:00 2001 From: Slavey Karadzhov Date: Tue, 19 Dec 2023 10:31:41 +0100 Subject: [PATCH 27/28] Fixed issue with Wokwi templates not generated on first run. Reported by Uri Shaked in https://github.com/wokwi/wokwi-features/issues/274. --- Tools/ide/vscode/setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tools/ide/vscode/setup.py b/Tools/ide/vscode/setup.py index eda0924702..ea33276c5f 100644 --- a/Tools/ide/vscode/setup.py +++ b/Tools/ide/vscode/setup.py @@ -119,8 +119,8 @@ def update_wokwi(): template = load_template('wokwi/extensions.json', appPath) if extensions is None: extensions = template.copy() - save_json(extensions, filename) - return + if not extensions.get("recommendations"): + extensions["recommendations"] = [] extensions["recommendations"] = extensions["recommendations"] + list(set(template["recommendations"]) - set(extensions["recommendations"])) save_json(extensions, filename) From 3537c5cda4ff2220b6e8bee4d0a992e85c9448a3 Mon Sep 17 00:00:00 2001 From: slaff Date: Wed, 20 Dec 2023 14:06:28 +0100 Subject: [PATCH 28/28] Preparation for version 5.1.0. (#2689) --- README.md | 4 ++-- Sming/Core/SmingVersion.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 00b95409a8..5bd3e7d19a 100644 --- a/README.md +++ b/README.md @@ -29,14 +29,14 @@ You can also try Sming without installing anything locally. We have an [interact The purpose of Sming is to simplify the creation of embedded applications. The documentation will help you get started in no time. -- [**Documentation for version 5.0.0**](https://sming.readthedocs.io/en/stable) - current stable version. +- [**Documentation for version 5.1.0**](https://sming.readthedocs.io/en/stable) - current stable version. - [Documentation for latest](https://sming.readthedocs.io/en/latest) - development version. ## Releases ### Stable -- [Sming V5.0.0](https://github.com/SmingHub/Sming/releases/tag/5.0.0) - great new features, performance and stability improvements. +- [Sming V5.1.0](https://github.com/SmingHub/Sming/releases/tag/5.1.0) - great new features, performance and stability improvements. ### Development diff --git a/Sming/Core/SmingVersion.h b/Sming/Core/SmingVersion.h index a8f33c4779..6968f86cba 100644 --- a/Sming/Core/SmingVersion.h +++ b/Sming/Core/SmingVersion.h @@ -6,7 +6,7 @@ */ #define SMING_MAJOR_VERSION 5 -#define SMING_MINOR_VERSION 0 +#define SMING_MINOR_VERSION 1 #define SMING_PATCH_VERSION 0 #define SMING_PRE_RELEASE ""