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)} -#### 软件架构 -软件架构说明 +![](./python/_images/featured.png) +--------------------------------------------------------- -#### 安装教程 +## 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 +![](./microPython/_images/blocks.png) -#### 特技 +## 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