共计 1857 个字符,预计需要花费 5 分钟才能阅读完成。
背景痛点
在开发智能家居的语音提示功能时,我发现市面上的低成本串口 TTS 模块(如 SYN6288、XFS5152CE)普遍存在一个限制:它们只能直接接收 ASCII 编码的英文字母。这让中文语音合成变得非常棘手——直接发送汉字字符,模块要么输出乱码,要么干脆不发音。

这种限制源于模块内置的字库和语音库设计:为了降低成本和复杂度,厂商通常只预存了英文音素。但实际应用中,我们需要用中文播报天气、设备状态等信息,这促使我研究汉字转码方案。
技术方案选择
处理汉字编码主要有三种常见方案:
- GB2312 编码 :国内早期标准,每个汉字占 2 字节,兼容 ASCII
- UTF- 8 编码 :Unicode 变长实现,汉字占 3 字节,兼容性好
- Unicode 码点 :直接使用 4 字节标准编码
经过实测对比,我发现 GB2312 最适合 Arduino 环境:
- 双字节固定长度,比 UTF- 8 更易解析
- 收录 6763 个常用汉字,满足基本需求
- 转换算法简单,适合 MCU 有限的计算能力
核心实现步骤
1. 建立轻量级码表
由于 Arduino 的 Flash 空间有限(UNO 只有 32KB),我提取了 GB2312 中 3755 个一级汉字,按拼音排序后存储为常量数组。这样既覆盖日常使用,又控制存储占用在 7KB 左右。
const uint16_t GB2312_TABLE[] PROGMEM = {
0xB0A1, // 啊
0xB0A2, // 阿
//... 其他汉字编码
};
2. 实现编码转换函数
通过查表法将 UTF- 8 转换为 GB2312 编码。这里需要处理三种情况:
- ASCII 字符(0-127):直接透传
- 三字节 UTF- 8 汉字:解析出 Unicode 后查表转换
- 其他字符:替换为问号
关键转换代码如下:
uint16_t utf8ToGb2312(const uint8_t* utf8) {if(utf8[0] < 0x80) return utf8[0]; // ASCII 处理
// 提取 Unicode 码点
uint16_t unicode = ((utf8[0] & 0x0F) << 12) |
((utf8[1] & 0x3F) << 6) |
(utf8[2] & 0x3F);
// 二分查找码表
int low = 0, high = TABLE_SIZE-1;
while(low <= high) {int mid = (low + high) / 2;
uint16_t gbCode = pgm_read_word(&GB2312_TABLE[mid]);
//... 比对逻辑
}
return 0x3F; // 未找到返回问号
}
3. 串口通信与 TTS 驱动
采用分段发送策略避免缓冲区溢出。每转换完一个汉字,立即通过软串口发送到 TTS 模块:
void sendToTTS(const char* text) {
uint8_t utf8Pos = 0;
while(text[utf8Pos]) {if((text[utf8Pos] & 0x80) == 0) {
// 单字节 ASCII
Serial1.write(text[utf8Pos++]);
} else {
// 三字节 UTF-8
uint16_t gbCode = utf8ToGb2312(&text[utf8Pos]);
Serial1.write(gbCode >> 8); // 发送高字节
Serial1.write(gbCode & 0xFF); // 发送低字节
utf8Pos += 3;
}
delay(10); // 防止模块过载
}
}
性能优化技巧
在 ATmega328P(16MHz)上实测发现:
- 查表优化 :二分查找比顺序遍历快 5 倍(平均 45us vs 250us)
- 存储优化 :使用 PROGMEM 将码表放在 Flash 而非 RAM,节省 2KB 内存
- 通信优化 :适当增加串口延迟可避免模块丢包
特别提醒:如果项目需要更多汉字,可以考虑:
- 使用外部 EEPROM 存储完整码表
- 仅缓存近期使用过的汉字编码
常见问题排查
遇到发音异常时,建议按以下步骤检查:
- 编码识别错误
- 用串口监视器查看原始数据
-
确认 UTF- 8 的 3 字节头标识(0xE 开头)
-
缓冲区溢出
- 减小单次发送文本长度
-
添加 SoftwareSerial 的缓冲区监控
-
多字节边界错误
- 检测文本截断情况
- 在字符串末尾补零保证对齐
扩展思路
这套方案可以进一步扩展:
- 支持更多语言 :通过添加不同编码表实现日语、韩语等支持
- 混合云端方案 :复杂文本先由云端转换为音素编码,再下发给 Arduino
- 离线语音优化 :结合 WAV 播放器播放预录制的常用短语
完整工程代码已开源:
GitHub 仓库链接 (包含测试用例和接线图)
通过这次实践,我深刻体会到在资源受限环境下处理编码转换的挑战。关键点在于找到存储效率、计算复杂度和功能需求的平衡。希望本方案能给遇到类似问题的开发者提供参考。
