diff --git a/LICENSE.TXT b/LICENSE.TXT
new file mode 100644
index 0000000000000000000000000000000000000000..2bd96d02ba1fe90af169cef446e5e74e49d52d57
--- /dev/null
+++ b/LICENSE.TXT
@@ -0,0 +1,17 @@
+Copyright (c)
+
+All rights reserved.
+
+MIT License
+
+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.
\ No newline at end of file
diff --git a/README.md b/README.md
index 71344e52657382b1c3beee06b9ccb75c95de0f6c..8dc89769b9fd4128a4f4bbede54d013823b3d4eb 100644
--- a/README.md
+++ b/README.md
@@ -1,39 +1,42 @@
-# tts20
+# 微蓝四路编码驱动
-#### 介绍
-{**以下是 Gitee 平台说明,您可以替换此简介**
-Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台
-无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)}
-#### 软件架构
-软件架构说明
+
+---------------------------------------------------------
-#### 安装教程
+## Table of Contents
-1. xxxx
-2. xxxx
-3. xxxx
+* [URL](#url)
+* [Summary](#summary)
+* [Blocks](#blocks)
+* [License](#license)
+* [Supported targets](#Supportedtargets)
-#### 使用说明
+## URL
+* Project URL : ```https://gitee.com/micro-blue-room/microblue-md40```
-1. xxxx
-2. xxxx
-3. xxxx
-#### 参与贡献
+## Summary
+使用Mind+V1.7.1及以上版本用户库加载此扩展,专用竞赛串口电机,精准控制前进距离。
-1. Fork 本仓库
-2. 新建 Feat_xxx 分支
-3. 提交代码
-4. 新建 Pull Request
+## Blocks
+
-#### 特技
+## License
+
+MIT
+
+## Supported targets
+
+MCU | JavaScript | Arduino | MicroPython | Python
+------------------ | :----------: | :----------: | :---------: | -----
+arduino | | | |
+micro:bit | | | |
+esp32 | | | |
+
+## Release Logs
+
+* V0.0.1 基础功能完成
-1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
-2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
-3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
-4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
-5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
-6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
diff --git a/arduinoC/_images/featured.png b/arduinoC/_images/featured.png
new file mode 100644
index 0000000000000000000000000000000000000000..268a6580d7567ab0ca607c5d87bde009dac5bb5b
Binary files /dev/null and b/arduinoC/_images/featured.png differ
diff --git a/arduinoC/_images/icon.svg b/arduinoC/_images/icon.svg
new file mode 100644
index 0000000000000000000000000000000000000000..938ca658d42a118e61a2f270b1c67998ff4c695a
--- /dev/null
+++ b/arduinoC/_images/icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/arduinoC/_menus/arduino.json b/arduinoC/_menus/arduino.json
new file mode 100644
index 0000000000000000000000000000000000000000..7a8784962a7e8990412df4ad90658f1481664b05
--- /dev/null
+++ b/arduinoC/_menus/arduino.json
@@ -0,0 +1,7 @@
+{
+ "PINS": {
+ "menu": [["P0", "33"], ["P1", "32"], ["P6", "16"], ["P7", "17"], ["P8", "26"], ["P9", "25"], ["P13", "18"], ["P14", "19"], ["P15", "21"], ["P16", "5"], ["P19", "22"], ["P20", "23"]],
+ "default_TX": "0",
+ "default_RX": "1"
+ }
+}
diff --git a/arduinoC/_menus/esp32.json b/arduinoC/_menus/esp32.json
new file mode 100644
index 0000000000000000000000000000000000000000..7a8784962a7e8990412df4ad90658f1481664b05
--- /dev/null
+++ b/arduinoC/_menus/esp32.json
@@ -0,0 +1,7 @@
+{
+ "PINS": {
+ "menu": [["P0", "33"], ["P1", "32"], ["P6", "16"], ["P7", "17"], ["P8", "26"], ["P9", "25"], ["P13", "18"], ["P14", "19"], ["P15", "21"], ["P16", "5"], ["P19", "22"], ["P20", "23"]],
+ "default_TX": "0",
+ "default_RX": "1"
+ }
+}
diff --git a/arduinoC/_menus/firebeetleesp32e.json b/arduinoC/_menus/firebeetleesp32e.json
new file mode 100644
index 0000000000000000000000000000000000000000..7a8784962a7e8990412df4ad90658f1481664b05
--- /dev/null
+++ b/arduinoC/_menus/firebeetleesp32e.json
@@ -0,0 +1,7 @@
+{
+ "PINS": {
+ "menu": [["P0", "33"], ["P1", "32"], ["P6", "16"], ["P7", "17"], ["P8", "26"], ["P9", "25"], ["P13", "18"], ["P14", "19"], ["P15", "21"], ["P16", "5"], ["P19", "22"], ["P20", "23"]],
+ "default_TX": "0",
+ "default_RX": "1"
+ }
+}
diff --git a/arduinoC/libraries/tts20/em_check.h b/arduinoC/libraries/tts20/em_check.h
new file mode 100644
index 0000000000000000000000000000000000000000..60ad5d4b668274f44e6d29abeb65ed6c93084b82
--- /dev/null
+++ b/arduinoC/libraries/tts20/em_check.h
@@ -0,0 +1,169 @@
+#pragma once
+
+#ifndef _EM_CHECK_H_
+#define _EM_CHECK_H_
+
+#include
+
+#ifdef ARDUINO_ARCH_ESP32
+#include
+#endif
+
+/**
+ * @file em_check.h
+ */
+
+/**
+ * @~Chinese
+ * @brief 断言失败处理函数。
+ * @param[in] expr 断言失败的表达式字符串。
+ * @param[in] function 发生断言的函数名。
+ * @param[in] file 发生断言的源文件名。
+ * @param[in] line 发生断言的行号。
+ * @details 当断言失败时,此函数会输出错误信息并停止程序运行。
+ * - 在ESP32平台上使用printf输出并调用abort()。
+ * - 在其他Arduino平台上使用Serial输出并进入死循环。
+ */
+/**
+ * @~English
+ * @brief Assertion failure handling function.
+ * @param[in] expr The expression string that failed the assertion.
+ * @param[in] function The function name where assertion occurred.
+ * @param[in] file The source file name where assertion occurred.
+ * @param[in] line The line number where assertion occurred.
+ * @details When an assertion fails, this function outputs error information and stops program execution.
+ * - On ESP32 platform, uses printf output and calls abort().
+ * - On other Arduino platforms, uses Serial output and enters an infinite loop.
+ */
+static inline void AssertFailHandle(const char* expr, const char* function, const char* file, const int line) {
+#ifdef ARDUINO_ARCH_ESP32
+ printf("\nassert failed: %s %s:%d (%s)\n", function, file, line, expr);
+ abort();
+#else
+ Serial.print(F("\nassert failed: "));
+ Serial.print(function);
+ Serial.print(F(" "));
+ Serial.print(file);
+ Serial.print(F(":"));
+ Serial.print(line);
+ Serial.print(F(" ("));
+ Serial.print(expr);
+ Serial.println(F(")"));
+ Serial.flush();
+ noInterrupts();
+ while (true) {
+ }
+#endif
+}
+
+/**
+ * @~Chinese
+ * @brief 基本断言检查宏。
+ * @param[in] expr 要检查的表达式。
+ * @details 如果表达式为假,则触发断言失败处理。
+ */
+/**
+ * @~English
+ * @brief Basic assertion check macro.
+ * @param[in] expr Expression to check.
+ * @details If the expression is false, triggers assertion failure handling.
+ */
+#define EM_CHECK(expr) ((expr) ? (void)0 : AssertFailHandle(#expr, __PRETTY_FUNCTION__, __FILE__, __LINE__))
+
+/**
+ * @~Chinese
+ * @brief 相等断言检查宏。
+ * @param[in] a 第一个比较值。
+ * @param[in] b 第二个比较值。
+ * @details 如果a不等于b,则触发断言失败处理。
+ */
+/**
+ * @~English
+ * @brief Equality assertion check macro.
+ * @param[in] a First comparison value.
+ * @param[in] b Second comparison value.
+ * @details If a is not equal to b, triggers assertion failure handling.
+ */
+#define EM_CHECK_EQ(a, b) ((a) == (b) ? (void)0 : AssertFailHandle(#a " == " #b, __PRETTY_FUNCTION__, __FILE__, __LINE__))
+
+/**
+ * @~Chinese
+ * @brief 不相等断言检查宏。
+ * @param[in] a 第一个比较值。
+ * @param[in] b 第二个比较值。
+ * @details 如果a等于b,则触发断言失败处理。
+ */
+/**
+ * @~English
+ * @brief Inequality assertion check macro.
+ * @param[in] a First comparison value.
+ * @param[in] b Second comparison value.
+ * @details If a is equal to b, triggers assertion failure handling.
+ */
+#define EM_CHECK_NE(a, b) ((a) != (b) ? (void)0 : AssertFailHandle(#a " != " #b, __PRETTY_FUNCTION__, __FILE__, __LINE__))
+
+/**
+ * @~Chinese
+ * @brief 大于断言检查宏。
+ * @param[in] a 第一个比较值。
+ * @param[in] b 第二个比较值。
+ * @details 如果a不大于b,则触发断言失败处理。
+ */
+/**
+ * @~English
+ * @brief Greater than assertion check macro.
+ * @param[in] a First comparison value.
+ * @param[in] b Second comparison value.
+ * @details If a is not greater than b, triggers assertion failure handling.
+ */
+#define EM_CHECK_GT(a, b) ((a) > (b) ? (void)0 : AssertFailHandle(#a " > " #b, __PRETTY_FUNCTION__, __FILE__, __LINE__))
+
+/**
+ * @~Chinese
+ * @brief 小于断言检查宏。
+ * @param[in] a 第一个比较值。
+ * @param[in] b 第二个比较值。
+ * @details 如果a不小于b,则触发断言失败处理。
+ */
+/**
+ * @~English
+ * @brief Less than assertion check macro.
+ * @param[in] a First comparison value.
+ * @param[in] b Second comparison value.
+ * @details If a is not less than b, triggers assertion failure handling.
+ */
+#define EM_CHECK_LT(a, b) ((a) < (b) ? (void)0 : AssertFailHandle(#a " < " #b, __PRETTY_FUNCTION__, __FILE__, __LINE__))
+
+/**
+ * @~Chinese
+ * @brief 大于等于断言检查宏。
+ * @param[in] a 第一个比较值。
+ * @param[in] b 第二个比较值。
+ * @details 如果a不大于等于b,则触发断言失败处理。
+ */
+/**
+ * @~English
+ * @brief Greater than or equal assertion check macro.
+ * @param[in] a First comparison value.
+ * @param[in] b Second comparison value.
+ * @details If a is not greater than or equal to b, triggers assertion failure handling.
+ */
+#define EM_CHECK_GE(a, b) ((a) >= (b) ? (void)0 : AssertFailHandle(#a " >= " #b, __PRETTY_FUNCTION__, __FILE__, __LINE__))
+
+/**
+ * @~Chinese
+ * @brief 小于等于断言检查宏。
+ * @param[in] a 第一个比较值。
+ * @param[in] b 第二个比较值。
+ * @details 如果a不小于等于b,则触发断言失败处理。
+ */
+/**
+ * @~English
+ * @brief Less than or equal assertion check macro.
+ * @param[in] a First comparison value.
+ * @param[in] b Second comparison value.
+ * @details If a is not less than or equal to b, triggers assertion failure handling.
+ */
+#define EM_CHECK_LE(a, b) ((a) <= (b) ? (void)0 : AssertFailHandle(#a " <= " #b, __PRETTY_FUNCTION__, __FILE__, __LINE__))
+
+#endif
\ No newline at end of file
diff --git a/arduinoC/libraries/tts20/tts20.cpp b/arduinoC/libraries/tts20/tts20.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b4ae5ad00a40e130908afb0812ab09df3610c819
--- /dev/null
+++ b/arduinoC/libraries/tts20/tts20.cpp
@@ -0,0 +1,287 @@
+/**
+ * @file tts20.cpp
+ */
+
+#include "tts20.h"
+
+namespace em {
+
+namespace {
+
+constexpr uint8_t kI2cEndTransmissionSuccess = 0;
+constexpr uint8_t kCommandClearRxBuffer = 1 << 0;
+constexpr uint32_t kDefaultTimeoutMs = 1000;
+
+constexpr uint8_t kResponseSuccess = 0x41;
+constexpr uint8_t kResponseBusy = 0x4E;
+constexpr uint8_t kResponseIdle = 0x4F;
+
+// Memory address constants
+constexpr uint8_t kMemAddrDeviceId = 0x00;
+constexpr uint8_t kMemAddrMajorVersion = 0x01;
+constexpr uint8_t kMemAddrMinorVersion = 0x02;
+constexpr uint8_t kMemAddrPatchVersion = 0x03;
+constexpr uint8_t kMemAddrName = 0x04;
+constexpr uint8_t kMemAddrCommand = 0x10;
+constexpr uint8_t kMemAddrTxBufferFreeSpace = 0x11;
+constexpr uint8_t kMemAddrTxBufferIsEmpty = 0x12;
+constexpr uint8_t kMemAddrTxBuffer = 0x13;
+constexpr uint8_t kMemAddrRxBufferCapacity = 0x53;
+constexpr uint8_t kMemAddrRxBufferReadPtr = 0x54;
+constexpr uint8_t kMemAddrRxBufferWritePtr = 0x55;
+constexpr uint8_t kMemAddrRxBufferData = 0x56;
+constexpr uint8_t kMemAddrRxBufferOverflow = 0x96;
+
+// Wire library buffer capacity constant
+#if defined(ESP32)
+constexpr uint8_t kWireBufferCapacity = 128;
+#else
+constexpr uint8_t kWireBufferCapacity = 32;
+#endif
+
+} // namespace
+
+Tts20::Tts20(const uint8_t i2c_address, TwoWire &wire) : i2c_address_(i2c_address), wire_(wire) {
+}
+
+void Tts20::Init() {
+ wire_.beginTransmission(i2c_address_);
+ wire_.write(kMemAddrRxBufferCapacity);
+ EM_CHECK_EQ(wire_.endTransmission(), kI2cEndTransmissionSuccess);
+
+ EM_CHECK_EQ(wire_.requestFrom(i2c_address_, static_cast(1)), 1);
+
+ while (wire_.available() == 0);
+
+ rx_buffer_capacity_ = wire_.read();
+
+ constexpr uint8_t kInitCommand[] = {0xFD, 0x00, 0x12, 0x01, 0x04, 0x5B, 0x76, 0x35, 0x5D, 0x5B, 0x73,
+ 0x35, 0x5D, 0x5B, 0x74, 0x35, 0x5D, 0x5B, 0x6D, 0x30, 0x5D};
+
+ Write(kInitCommand, sizeof(kInitCommand));
+
+ EM_CHECK(ReadUntil(kResponseSuccess, kDefaultTimeoutMs));
+
+ while (IsBusy());
+}
+
+String Tts20::firmware_version() {
+ wire_.beginTransmission(i2c_address_);
+ wire_.write(kMemAddrMajorVersion);
+ EM_CHECK_EQ(wire_.endTransmission(), kI2cEndTransmissionSuccess);
+
+ uint8_t version[3] = {0};
+ EM_CHECK_EQ(wire_.requestFrom(i2c_address_, static_cast(sizeof(version))), sizeof(version));
+
+ uint8_t offset = 0;
+ while (offset < sizeof(version)) {
+ if (wire_.available() > 0) {
+ version[offset++] = wire_.read();
+ }
+ }
+
+ return String(version[0]) + "." + String(version[1]) + "." + String(version[2]);
+}
+
+uint8_t Tts20::device_id() {
+ wire_.beginTransmission(i2c_address_);
+ wire_.write(kMemAddrDeviceId);
+ EM_CHECK_EQ(wire_.endTransmission(), kI2cEndTransmissionSuccess);
+
+ EM_CHECK_EQ(wire_.requestFrom(i2c_address_, static_cast(1)), 1);
+
+ while (wire_.available() == 0);
+
+ return wire_.read();
+}
+
+String Tts20::name() {
+ wire_.beginTransmission(i2c_address_);
+ wire_.write(kMemAddrName);
+ EM_CHECK_EQ(wire_.endTransmission(), kI2cEndTransmissionSuccess);
+
+ constexpr uint8_t kLength = 8;
+ EM_CHECK_EQ(wire_.requestFrom(i2c_address_, kLength), kLength);
+
+ String result;
+ while (result.length() < kLength) {
+ if (wire_.available() > 0) {
+ result += static_cast(wire_.read());
+ }
+ }
+
+ return result;
+}
+
+bool Tts20::Play(const String &text) {
+ EM_CHECK(text.length() > 0);
+
+ const uint8_t header[] = {0xFD, ((text.length() + 2) >> 8) & 0xFF, ((text.length() + 2) & 0xFF), 0x01, 0x04};
+
+ ClearRxBuffer();
+
+ Write(header, sizeof(header));
+ Write(reinterpret_cast(text.c_str()), text.length());
+
+ return ReadUntil(kResponseSuccess, kDefaultTimeoutMs);
+}
+
+bool Tts20::IsBusy() {
+ const auto start_time = millis();
+ while (millis() - start_time < 5000) {
+ ClearRxBuffer();
+
+ constexpr uint8_t kIsBusyCommand[] = {0xFD, 0x00, 0x01, 0x21};
+ Write(kIsBusyCommand, sizeof(kIsBusyCommand));
+
+ uint8_t data = 0;
+ if (Read(&data, sizeof(data), 500) == sizeof(data)) {
+ if (data == kResponseBusy) {
+ return true;
+ } else if (data == kResponseIdle) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool Tts20::Stop() {
+ ClearRxBuffer();
+
+ constexpr uint8_t kStopCommand[] = {0xFD, 0x00, 0x01, 0x02};
+ Write(kStopCommand, sizeof(kStopCommand));
+
+ return ReadUntil(kResponseSuccess, kDefaultTimeoutMs);
+}
+
+bool Tts20::Pause() {
+ ClearRxBuffer();
+
+ constexpr uint8_t kPauseCommand[] = {0xFD, 0x00, 0x01, 0x03};
+ Write(kPauseCommand, sizeof(kPauseCommand));
+
+ return ReadUntil(kResponseSuccess, kDefaultTimeoutMs);
+}
+
+bool Tts20::Resume() {
+ ClearRxBuffer();
+
+ constexpr uint8_t kResumeCommand[] = {0xFD, 0x00, 0x01, 0x04};
+ Write(kResumeCommand, sizeof(kResumeCommand));
+
+ return ReadUntil(kResponseSuccess, kDefaultTimeoutMs);
+}
+
+void Tts20::ClearRxBuffer() {
+ wire_.beginTransmission(i2c_address_);
+ wire_.write(kMemAddrCommand);
+ wire_.write(kCommandClearRxBuffer);
+ EM_CHECK_EQ(wire_.endTransmission(), kI2cEndTransmissionSuccess);
+}
+
+void Tts20::Write(const uint8_t *data, const size_t size) {
+ EM_CHECK(data != nullptr && size > 0);
+
+ size_t offset = 0;
+
+ while (offset < size) {
+ wire_.beginTransmission(i2c_address_);
+ wire_.write(kMemAddrTxBufferFreeSpace);
+ EM_CHECK_EQ(wire_.endTransmission(), kI2cEndTransmissionSuccess);
+
+ uint8_t segment_length = 0;
+ EM_CHECK_EQ(wire_.requestFrom(i2c_address_, static_cast(sizeof(segment_length))), sizeof(segment_length));
+
+ while (wire_.available() == 0);
+
+ segment_length = wire_.read();
+
+ if (segment_length == 0) {
+ continue;
+ }
+
+ segment_length = min(size - offset, static_cast(min(segment_length, static_cast(kWireBufferCapacity - 1))));
+
+ wire_.beginTransmission(i2c_address_);
+ wire_.write(kMemAddrTxBuffer);
+ wire_.write(data + offset, segment_length);
+ EM_CHECK_EQ(wire_.endTransmission(), kI2cEndTransmissionSuccess);
+
+ offset += segment_length;
+ }
+
+ while (true) {
+ wire_.beginTransmission(i2c_address_);
+ wire_.write(kMemAddrTxBufferIsEmpty);
+ EM_CHECK_EQ(wire_.endTransmission(), kI2cEndTransmissionSuccess);
+
+ EM_CHECK_EQ(wire_.requestFrom(i2c_address_, static_cast(1)), 1);
+
+ while (wire_.available() == 0);
+
+ if (wire_.read() != 0) {
+ break;
+ }
+ }
+}
+
+uint8_t Tts20::Read(uint8_t *buffer, const uint8_t expected_length, const uint32_t timeout_ms) {
+ EM_CHECK(buffer != nullptr && expected_length > 0);
+
+ uint8_t actual_length = 0;
+ const auto start_time = millis();
+
+ while (actual_length < expected_length && (millis() - start_time) < timeout_ms) {
+ wire_.beginTransmission(i2c_address_);
+ wire_.write(kMemAddrRxBufferReadPtr);
+ EM_CHECK_EQ(wire_.endTransmission(), kI2cEndTransmissionSuccess);
+
+ uint8_t data[2] = {0};
+ EM_CHECK_EQ(wire_.requestFrom(i2c_address_, static_cast(sizeof(data))), sizeof(data));
+
+ uint8_t offset = 0;
+ while (offset < sizeof(data)) {
+ if (wire_.available() > 0) {
+ data[offset++] = wire_.read();
+ }
+ }
+
+ if (data[0] == data[1]) {
+ continue;
+ }
+
+ const uint8_t segment_length =
+ min(expected_length - actual_length, min((data[1] + rx_buffer_capacity_ - data[0]) % rx_buffer_capacity_, rx_buffer_capacity_ - data[0]));
+
+ wire_.beginTransmission(i2c_address_);
+ wire_.write(static_cast(kMemAddrRxBufferData + data[0]));
+ EM_CHECK_EQ(wire_.endTransmission(), kI2cEndTransmissionSuccess);
+
+ EM_CHECK_EQ(wire_.requestFrom(i2c_address_, segment_length), segment_length);
+
+ const uint16_t target_bytes = actual_length + segment_length;
+ while (actual_length < target_bytes) {
+ if (wire_.available() > 0) {
+ buffer[actual_length++] = wire_.read();
+ }
+ }
+
+ wire_.beginTransmission(i2c_address_);
+ wire_.write(kMemAddrRxBufferReadPtr);
+ wire_.write(static_cast((data[0] + segment_length) % rx_buffer_capacity_));
+ EM_CHECK_EQ(wire_.endTransmission(), kI2cEndTransmissionSuccess);
+ }
+ return actual_length;
+}
+
+bool Tts20::ReadUntil(const uint8_t target_byte, const uint32_t timeout_ms) {
+ uint8_t data = 0;
+ if (Read(&data, sizeof(data), timeout_ms) == sizeof(data) && data == target_byte) {
+ return true;
+ }
+ return false;
+}
+
+} // namespace em
\ No newline at end of file
diff --git a/arduinoC/libraries/tts20/tts20.h b/arduinoC/libraries/tts20/tts20.h
new file mode 100644
index 0000000000000000000000000000000000000000..8ffe2aaa96cd63c8e18389e34c25471085e2dc0e
--- /dev/null
+++ b/arduinoC/libraries/tts20/tts20.h
@@ -0,0 +1,176 @@
+#pragma once
+
+#ifndef _EM_TTS20_H_
+#define _EM_TTS20_H_
+
+#include
+#include
+
+#include "em_check.h"
+
+/**
+ * @file tts20.h
+ */
+
+namespace em {
+
+/**
+ * @~Chinese
+ * @class Tts20
+ * @brief Tts20是用于TTS20模块的驱动类,用于语音合成。
+ */
+/**
+ * @~English
+ * @class Tts20
+ * @brief Tts20 is a driver class for TTS20 module, used for speech synthesis。
+ */
+class Tts20 {
+ public:
+ /**
+ * @~Chinese
+ * @brief 默认I2C地址。
+ */
+ /**
+ * @~English
+ * @brief Default I2C address.
+ */
+ static constexpr uint8_t kDefaultI2cAddress = 0x40;
+
+ /**
+ * @~Chinese
+ * @brief 构造函数。
+ * @param[in] i2c_address I2C地址。
+ * @param[in] wire TwoWire 对象引用。
+ */
+ /**
+ * @~English
+ * @brief Constructor.
+ * @param[in] i2c_address I2C address.
+ * @param[in] wire TwoWire object reference.
+ */
+ Tts20(const uint8_t i2c_address, TwoWire &wire);
+
+ /**
+ * @~Chinese
+ * @brief 初始化。
+ */
+ /**
+ * @~English
+ * @brief Initialize.
+ */
+ void Init();
+
+ /**
+ * @~Chinese
+ * @brief 获取固件版本。
+ * @return 固件版本。
+ */
+ /**
+ * @~English
+ * @brief Get firmware version.
+ * @return Firmware version.
+ */
+ String firmware_version();
+
+ /**
+ * @~Chinese
+ * @brief 获取设备ID。
+ * @return 设备ID。
+ */
+ /**
+ * @~English
+ * @brief Get device ID.
+ * @return Device ID.
+ */
+ uint8_t device_id();
+
+ /**
+ * @~Chinese
+ * @brief 获取设备名称。
+ * @return 设备名称。
+ */
+ /**
+ * @~English
+ * @brief Get device name.
+ * @return Device name.
+ */
+ String name();
+
+ /**
+ * @~Chinese
+ * @brief 文本转语音并播放。
+ * @param[in] text 要播放的文本数据。
+ * @return 是否成功播放。
+ */
+ /**
+ * @~English
+ * @brief Convert text to speech and play.
+ * @param[in] text The text data to be played.
+ * @return Has it been successfully synthesized and started playing.
+ */
+ bool Play(const String &text);
+
+ /**
+ * @~Chinese
+ * @brief 检查模块是否处于忙碌状态。
+ * @return true 表示模块处于忙碌状态,false 表示模块空闲。
+ */
+ /**
+ * @~English
+ * @brief Check if the module is in busy state.
+ * @return true indicates the module is busy, false indicates the module is idle.
+ */
+ bool IsBusy();
+
+ /**
+ * @~Chinese
+ * @brief 终止播放。
+ * @return 是否成功终止播放。
+ */
+ /**
+ * @~English
+ * @brief Stop playback.
+ * @return Has the playback been successfully stopped.
+ */
+ bool Stop();
+
+ /**
+ * @~Chinese
+ * @brief 暂停播放,后续可以继续播放。
+ * @return 是否成功暂停播放。
+ */
+ /**
+ * @~English
+ * @brief Pause playback, you can continue playing in the future.
+ * @return Has the playback been successfully stopped..
+ */
+ bool Pause();
+
+ /**
+ * @~Chinese
+ * @brief 恢复播放(暂停后继续播放)。
+ * @return 是否成功恢复播放。
+ */
+ /**
+ * @~English
+ * @brief Resume playback (pause and resume playback).
+ * @return Whether the playback has been successfully restored.
+ */
+ bool Resume();
+
+ private:
+ Tts20(const Tts20 &) = delete;
+ Tts20 &operator=(const Tts20 &) = delete;
+
+ void ClearRxBuffer();
+ void Write(const uint8_t *data, const size_t size);
+ uint8_t Read(uint8_t *buffer, const uint8_t expected_length, const uint32_t timeout_ms);
+ bool ReadUntil(const uint8_t target_byte, const uint32_t timeout_ms);
+
+ const uint8_t i2c_address_ = kDefaultI2cAddress;
+ TwoWire &wire_ = Wire;
+ uint8_t rx_buffer_capacity_ = 0;
+};
+
+} // namespace em
+#endif
\ No newline at end of file
diff --git a/arduinoC/libraries/tts20/tts20_lib.h b/arduinoC/libraries/tts20/tts20_lib.h
new file mode 100644
index 0000000000000000000000000000000000000000..c828efb1df6ed588258572dd3e9c4ab9ba1f1c2a
--- /dev/null
+++ b/arduinoC/libraries/tts20/tts20_lib.h
@@ -0,0 +1,60 @@
+#pragma once
+
+#ifndef _EM_TTS20_LIB_H_
+#define _EM_TTS20_LIB_H_
+
+#include
+
+/**
+ * @file tts20_lib.h
+ */
+
+namespace em {
+namespace tts20_lib {
+
+/**
+ * @~Chinese
+ * @brief 主版本号。
+ */
+/**
+ * @~English
+ * @brief Major version number.
+ */
+constexpr uint8_t kVersionMajor = 1;
+
+/**
+ * @~Chinese
+ * @brief 次版本号。
+ */
+/**
+ * @~English
+ * @brief Minor version number.
+ */
+constexpr uint8_t kVersionMinor = 0;
+
+/**
+ * @~Chinese
+ * @brief 修订版本号。
+ */
+/**
+ * @~English
+ * @brief Patch version number.
+ */
+constexpr uint8_t kVersionPatch = 0;
+
+/**
+ * @~Chinese
+ * @brief 获取版本号字符串。
+ * @return 版本号字符串,格式为 major.minor.patch。
+ */
+/**
+ * @~English
+ * @brief Get the version number string.
+ * @return The version number string in the format of major.minor.patch.
+ */
+String Version() {
+ return String(kVersionMajor) + '.' + kVersionMinor + '.' + kVersionPatch;
+}
+} // namespace tts20_lib
+} // namespace em
+#endif
\ No newline at end of file
diff --git a/arduinoC/main.ts b/arduinoC/main.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f695d3197de49847125f5b8452ba1be0c409076a
--- /dev/null
+++ b/arduinoC/main.ts
@@ -0,0 +1,238 @@
+enum S_VARS {
+ //% block="语速0"
+ s0,
+ //% block="语速1"
+ s1,
+ //% block="语速2"
+ s2,
+ //% block="语速3"
+ s3,
+ //% block="语速4"
+ s4,
+ //% block="语速5"
+ s5,
+ //% block="语速6"
+ s6,
+ //% block="语速7"
+ s7,
+ //% block="语速8"
+ s8,
+ //% block="语速9"
+ s9,
+}
+
+enum V_VARS {
+ //% block="音量0"
+ v0,
+ //% block="音量1"
+ v1,
+ //% block="音量2"
+ v2,
+ //% block="音量3"
+ v3,
+ //% block="音量4"
+ v4,
+ //% block="音量5"
+ v5,
+ //% block="音量6"
+ v6,
+ //% block="音量7"
+ v7,
+ //% block="音量8"
+ v8,
+ //% block="音量9"
+ v9,
+}
+
+enum T_VARS {
+ //% block="语调0"
+ t0,
+ //% block="语调1"
+ t1,
+ //% block="语调2"
+ t2,
+ //% block="语调3"
+ t3,
+ //% block="语调4"
+ t4,
+ //% block="语调5"
+ t5,
+ //% block="语调6"
+ t6,
+ //% block="语调7"
+ t7,
+ //% block="语调8"
+ t8,
+ //% block="语调9"
+ t9,
+}
+
+
+enum N_VARS {
+ //% block="数字模式"
+ n1,
+ //% block="数值模式"
+ n2,
+ //% block="电话号码模式"
+ n3,
+}
+
+enum P_VARS {
+ //% block="无暂停"
+ p0,
+ //% block="短暂停"
+ p1,
+}
+
+enum SOUNDS {
+ //% block="铃声1"
+ ring_1,
+ //% block="铃声2"
+ ring_2,
+ //% block="铃声3"
+ ring_3,
+ //% block="铃声4"
+ ring_4,
+ //% block="铃声5"
+ ring_5,
+ //% block="信息提示音1"
+ message_1,
+ //% block="信息提示音2"
+ message_2,
+ //% block="信息提示音3"
+ message_3,
+ //% block="信息提示音4"
+ message_4,
+ //% block="信息提示音5"
+ message_5,
+ //% block="警示音1"
+ alert_1,
+ //% block="警示音2"
+ alert_2,
+ //% block="警示音3"
+ alert_3,
+ //% block="警示音4"
+ alert_4,
+ //% block="警示音5"
+ alert_5,
+}
+
+enum STONE_VARS {
+ //% block="一声"
+ stone_1,
+ //% block="二声"
+ stone_2,
+ //% block="三声"
+ stone_3,
+ //% block="四声"
+ stone_4,
+ //% block="轻声"
+ stone_5,
+}
+
+
+//% color="#2494F4" iconWidth=50 iconHeight=40
+namespace MicroblueTts20{
+
+ //% block="初始TTS20语音合成模块 I2C地址[ADDR]" blockType="command"
+ //% ADDR.shadow="number" ADDR.defl="0x40"
+ export function tts20_init(parameter: any, block: any) {
+ let addr = parameter.ADDR.code;
+ Generator.addInclude("wire", "#include ", true);
+ Generator.addInclude("tts20",`#include "tts20.h"`, true);
+ Generator.addObject("g_tts20","em::Tts20",`g_tts20(${addr}, Wire)`);
+ Generator.addSetup("Wire.begin", `Wire.begin();`);
+ Generator.addSetup("g_tts20.Init", `g_tts20.Init();`);
+ }
+
+ //% block="以[V][S][T]播放文本[TEXT]" blockType="command"
+ //% S.shadow="dropdownRound" S.options="S_VARS" S.defl="S_VARS.s2"
+ //% V.shadow="dropdownRound" V.options="V_VARS" V.defl="V_VARS.v5"
+ //% T.shadow="dropdownRound" T.options="T_VARS" T.defl="T_VARS.t6"
+ //% TEXT.shadow="string" TEXT.defl="你好"
+ export function tts20_play_text(parameter: any, block: any) {
+ let v = parameter.V.code;
+ let s=parameter.S.code;
+ let t=parameter.T.code;
+ let text=parameter.TEXT.code;
+ let pre_text = `String("[${v}][${s}][${t}]")`
+ Generator.addCode('g_tts20.Play(' + pre_text + "+" + text + ');' );
+ }
+
+ //% block="以[V][S][T],[N]播放数字[TEXT]" blockType="command"
+ //% S.shadow="dropdownRound" S.options="S_VARS" S.defl="S_VARS.s2"
+ //% V.shadow="dropdownRound" V.options="V_VARS" V.defl="V_VARS.v5"
+ //% T.shadow="dropdownRound" T.options="T_VARS" T.defl="T_VARS.t6"
+ //% N.shadow="dropdownRound" N.options="N_VARS" N.defl="N_VARS.n1"
+ //% TEXT.shadow="number" TEXT.defl="10001"
+ export function tts20_play_num(parameter: any, block: any) {
+ let v = parameter.V.code;
+ let s=parameter.S.code;
+ let t=parameter.T.code;
+ let text=parameter.TEXT.code;
+ let n=parameter.N.code;
+ let pre_text = `String("[${v}][${s}][${t}][${n}]")`
+ Generator.addCode('g_tts20.Play(' + pre_text + "+" + text + ');' );
+ }
+
+ //% block="以[V]播放音效[SOUND]" blockType="command"
+ //% V.shadow="dropdownRound" V.options="V_VARS" V.defl="V_VARS.v5"
+ //% SOUND.shadow="dropdownRound" SOUND.options="SOUNDS" SOUND.defl="SOUNDS.ring_1"
+ export function tts20_play_music(parameter: any, block: any) {
+ let v = parameter.V.code;
+ let sound=parameter.SOUND.code;
+ sound = `[${v}]${sound}`.replace(/"/g, "");
+ Generator.addCode(`g_tts20.Play(F("${sound}"));`);
+ }
+
+ //% block="等待播放完毕" blockType="command"
+ export function tts20_wait_over(parameter: any, block: any) {
+ Generator.addCode(`while (g_tts20.IsBusy());`);
+ }
+
+ //% block="暂停播放" blockType="command"
+ export function tts20_pause(parameter: any, block: any) {
+ Generator.addCode(`g_tts20.Pause();`);
+ }
+
+ //% block="恢复播放" blockType="command"
+ export function tts20_resume(parameter: any, block: any) {
+ Generator.addCode(`g_tts20.Resume();`);
+ }
+
+ //% block="终止播放" blockType="command"
+ export function tts20_stop(parameter: any, block: any) {
+ Generator.addCode(`g_tts20.Stop();`);
+ }
+
+ //% block="将[T1][P][T2]合成播放文本" blockType="reporter"
+ //% T1.shadow="string" T1.defl="下雪啦"
+ //% P.shadow="dropdownRound" P.options="P_VARS" P.defl="P_VARS.p1"
+ //% T2.shadow="string" T2.defl="下雪啦"
+ export function tts20_text_merge(parameter: any, block: any) {
+ let t1 = parameter.T1.code;
+ let p = parameter.P.code;
+ let t2 = parameter.T2.code;
+ if (p == 'p0') {
+ p = "";
+ } else if (p == 'p1') {
+ p = "[w0]";
+ }
+ let pre_text = t1 + ` + String("${p}")`;
+ pre_text += " + " + t2;
+ // let text = `${t1}${p}${t2}`.replace(/"/g, "");
+ Generator.addCode(pre_text);
+ }
+
+ //% block="设置文字[FONT]的拼音为[STONE][PINYIN]" blockType="reporter"
+ //% FONT.shadow="string" FONT.defl="着"
+ //% STONE.shadow="dropdownRound" STONE.options="STONE_VARS" STONE.defl="STONE_VARS.stone_2"
+ //% PINYIN.shadow="string" PINYIN.defl="zhuo"
+ export function tts20_text_control(parameter: any, block: any) {
+ let font = parameter.FONT.code;
+ let stone = parameter.STONE.code;
+ let pinyin = parameter.PINYIN.code;
+ let text = `${font}[=${pinyin}${stone[stone.length-1]}]`.replace(/"/g, "");
+ Generator.addCode(`String("${text}")`);
+ }
+}
diff --git a/config.json b/config.json
new file mode 100644
index 0000000000000000000000000000000000000000..48a1f71375277f0fdf9572e09bfda9606fa3a6c0
--- /dev/null
+++ b/config.json
@@ -0,0 +1,30 @@
+{
+ "name": {
+ "zh-cn": "TTS20语音合成模块",
+ "en": "TTS20 Voice Synthesis Module"
+ },
+ "description": {
+ "zh-cn": "一款高流畅度、高自然度的优美人声语音合成模块",
+ "en": ""
+ },
+ "author": "Emakefun",
+ "email": "micro_blue_room@163.com",
+ "license": "MIT",
+ "isBoard": false,
+ "id": "emTts20",
+ "platform": ["win","mac","web","linux"],
+ "version": "1.0.0",
+ "asset": {
+ "arduinoC": {
+ "dir": "arduinoC/",
+ "version": "1.0.0",
+ "board": [
+ "esp32",
+ "arduino",
+ "arduinonano",
+ "firebeetleesp32e"
+ ],
+ "main": "main.ts"
+ }
+ }
+}
\ No newline at end of file
diff --git a/emakefun-emtts20-thirdex-V1.0.0.mpext b/emakefun-emtts20-thirdex-V1.0.0.mpext
new file mode 100644
index 0000000000000000000000000000000000000000..144ddba1a75e0a08a985c24c08bf5cf5165e0728
Binary files /dev/null and b/emakefun-emtts20-thirdex-V1.0.0.mpext differ