01 什么是SYN6288
SYN6288 是一款高性价比的中文语音合成芯片,能够通过异步串口(UART)接收文本数据并实现高效的文本到语音(TTS)转换。它支持多种编码格式,包括 GB2312、GBK、BIG5 和 Unicode,能够自然流畅地合成中文、英文字母及数字混合的文本内容,每次语音合成的最大文本长度可达 200 字节。芯片提供丰富的控制功能,如语音合成、停止、暂停、继续合成以及波特率调整等,广泛适用于需要语音播报的嵌入式系统和智能设备开发场景。
简单来说,SYN6288模块是一个文字转语音功能的模块,能够为单片机项目赋予“说话”的能力。
02 如何控制SYN6288
主控制器和SYN6288语音合成芯片之间通过UART接口连接。控制器可以通过这个通讯接口以命令帧的形式向SYN6288语音合成芯片发送控制命令和文本
。SYN6288语音合成芯片接收到文本后,将其合成为语音信号输出。随后,输出的语音信号经过功率放大器放大,最后连接到喇叭进行播放。这个过程使得单片机项目能够实现语音播报的功能。
2.1 SYN6288命令帧格式
帧结构 | 帧头 (1 字节) | 数据区长度 (2 字节) | 命令字 (1 字节) | 命令参数 (1 字节) | 待发送文本 (小于等于 200 字节) | 异或校验 (1 字节) |
---|---|---|---|---|---|---|
数据 | 0xFD | 0xXX 0xXX | 0xXX | 0xXX | 0xXX ... | 0xXX |
说明 | 定义为十六进制 0xFD | 高字节在前,低字节在后 | 长度必须和前面的“数据区长度”一致 |
注意:数据区(含命令字,命令参数,待发送文本,异或校验)的实际长度必须与帧头后定义的数据区长度严格一致,否则芯片会报接收失败。
控制命令表
例子
03-1 使用STM32发送一个语音
3.1 串口初始化代码
波特率 9600
是 SYN6288 默认设置。若要改波特率每次SYN6288系统重置后都要发送更改波特率的命令帧。
/**
* 函 数:串口初始化
* 参 数:无
* 返 回 值:无
*/
void USART2_Init(void)
{
// 开启 GPIOA 和 USART2 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
// 配置 TX(PA2)为复用推挽输出,RX(PA3)为上拉输入
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; // TX
GPIO_Init(GPIOA, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; // RX
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 配置 USART2
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600; // 波特率为 9600
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; // 开启发送和接收
USART_InitStructure.USART_Parity = USART_Parity_No; // 无奇偶校验
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 1 位停止位
USART_InitStructure.USART_WordLength = USART_WordLength_8b; // 8 位字长
USART_Init(USART2, &USART_InitStructure);
// 使能 USART2
USART_Cmd(USART2, ENABLE);
}
/**
* 函 数:串口发送一个字符串
* 参 数:String 要发送字符串的首地址,长度
* 返 回 值:无
*/
void USART2_SendString(u8 *DAT, u8 len)
{
u8 i;
for(i = 0; i < len; i++)
{
while((USART2->SR & 0X40) == 0);
USART2->DR = (*DAT++);
}
}
3.2 向SYN6288发送数据帧代码
//Music:选择背景音乐。0:无背景音乐,1~15:选择背景音乐
//HZdata字符串内输入空格可以使语音合成模块在文字之间停顿(相当于休止符)
void SYN_FrameInfo(u8 Music, u8 *HZdata)
{
/****************需要发送的文本**********************************/
unsigned char Frame_Info[50];
unsigned char HZ_Length;
unsigned char ecc = 0; //定义校验字节
unsigned int i = 0;
HZ_Length = strlen((char*)HZdata); //需要发送文本的长度
/*****************帧固定配置信息**************************************/
Frame_Info[0] = 0xFD ; //构造帧头FD
Frame_Info[1] = 0x00 ; //构造数据区长度的高字节
Frame_Info[2] = HZ_Length + 3; //构造数据区长度的低字节
Frame_Info[3] = 0x01 ; //构造命令字:合成播放命令
Frame_Info[4] = 0x01 | Music << 4 ; //构造命令参数:背景音乐设定
/*******************校验码计算***************************************/
for(i = 0; i < 5; i++) //依次发送构造好的5个帧头字节
{
ecc = ecc ^ (Frame_Info[i]); //对发送的字节进行异或校验
}
for(i = 0; i < HZ_Length; i++) //依次发送待合成的文本数据
{
ecc = ecc ^ (HZdata[i]); //对发送的字节进行异或校验
}
/*******************发送帧信息***************************************/
memcpy(&Frame_Info[5], HZdata, HZ_Length);
Frame_Info[5 + HZ_Length] = ecc;
USART2_SendString(Frame_Info, 5 + HZ_Length + 1);
}
void SYN_FrameInfo(u8 Music, u8 *HZdata)
是构造并发送命令帧的函数。
Frame_Info[50]
char数组用于存储命令帧的数据
ecc
存储组成命令帧的字节的异或校验结果,校验码是数据帧完整性的保障
发送语音
SYN_FrameInfo(0,"测试");
03-2 发送带有文本控制的语音
文本控制标记列表
控制标识需要按照语音合成命令的格式发送,特殊控制标记作为文本进行合成,即合成命令是
帧头
+数据区长度
+合成命令字
+文本编码格式
+特殊控制标记文本
的格式。控制标识为全局控制标识,也就是只要用了一次,在不对芯片复位或者断电的条件下,其后发送给芯片的所有文本都会处于它的控制之下,除非用相应的
[d]
恢复默认设置。当芯片掉电或是复位后,芯片将恢复到所有的默认值,原来的设置过的标识失去了作用,需要重新设置。
不符合以上可识别的“控制标识”的或者格式不对的,一律按普通的字符和数字处理。
[d]
, [v?]
, [m?]
可以单独作为命令格式发送,也可以放在播放文本的开头与播放文本一起发送。
/**************语音合成芯片设置命令*********************/
//选择背景音乐2。(0:无背景音乐 1-15:背景音乐可选)
//m[0~16]:0背景音乐为静音,16背景音乐音量最大
//v[0~16]:0朗读音量为静音,16朗读音量最大
//t[0~5]:0朗读语速最慢,5朗读语速最快
SYN_FrameInfo(0, (uint8_t *)"[v4][m0][t5]测试语音合成 ");
03-2-2 带有和弦和提示音的语音合成
[x1]
使能和弦提示音- 查阅
和弦
,提示音
列表选择提示音
使能 [x1]
时 sounda
等会自动被SYN6288
识别,但是提示音
前后紧跟英文字符时需要用空格或逗号把它们隔开,否则会识别不到。
发送音效和提示音
//m[0~16]:背景音乐
//v[0~16]:朗读音量
//t[0~5]:朗读语速
SYN_FrameInfo(0, (uint8_t *)"[v4][m0][t5][x1] 测试语音合成 测试音效 soundd 测试和弦提示音 msgc");
03-3 发送变量的值
snprintf
函数
snprintf
是 C 标准库中的一个函数,用于将格式化的字符串写入一个字符数组中。它是一个灵活且强大的字符串处理工具,类似于 printf
,但不会直接输出到控制台,而是将结果保存到指定的字符数组中。
函数原型: int sprintf(char *str, const char *format, ...);
str
:指向目标字符数组,用于存储格式化后的字符串 , 需要足够的空间
。
size
:最大写入的字符数(包括终止符 \0
)。
format
:格式化字符串,定义输出内容的格式。
用法和printf差不多
# include <stdio.h>
char result[100];
int age = 20;
snprintf(result, 10, "今年%d岁", age);
// snprintf 的第二个参数表示了输出格式化字符串的最大字节数包括'\0'。参数值最好大一点
//在GBK编码模式下,一个汉字占用两个字节的大小; 按照ASCII码,一个数字占用一个字节
//分析 “今年20岁” == ‘今’ ‘年’ ‘2’ ‘0’ ‘岁’ ‘\0’ 占用9个字节
通过snprintf
这种方式可以实现在 SYN6288
中输出变量的值
# include<stdio.h>
char result[100];
int testNumber = 10+1;
sprintf(result,"测试变量插入 %d ",testNumber);
SYN_FrameInfo(0,(uint8_t *)result);
04 其他有关知识点
04-1 GB2312
GBK
编码
- GB2312:它是较早的汉字编码标准,包含的字符数量有限。包含6763个汉字和682个非汉字字符,如常见汉字“的”“是”及希腊字母“α”等。
- GBK:GBK是GB2312的扩展,它包含了更多的字符.扩展至21003个汉字,增加生僻字、繁体字、少数民族文字等,如古籍生僻字“𠃋”。
编码方式
- GB2312:双字节编码,范围0x21 - 0x77(高位、低位字节)。
- GBK:也是双字节编码,但它扩展了编码范围。范围0x81 - 0xFE(高位字节)、0x40 - 0xFE(低位字节,除去0x7F)。
兼容性
- GB2312:早期标准,用于老旧计算机系统和特定工业控制系统。
- GBK:向下兼容GB2312.也就是说,GB2312中的所有字符在GBK中都有对应的编码,并且编码值是相同的。这使得GBK在很多场景下可以无缝替代GB2312。广泛用于现代中文操作系统和办公软件,如Windows记事本。
例子:Keil里的GB2312编码
Frame_Info[4] = 0x01 | Music << 4 ; //构造命令参数:背景音乐设定
[M][u][s][i][c][0][0][1] --> 命令参数的第三位是 001 代表GBK编码方式
KEIL的编码方式是GB2312因为GBK兼容GB2312所以语音合成没有出现错误
04-2 异或校验
异或计算(XOR)
的规则是:相同为0,不同为1。
输入 A | 输入 B | 输出 A ⊕ B |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
异或校验(XOR Checksum)是一种简单的错误检测方法,通过对所有数据字节进行逐字节异或操作,生成一个校验值(通常也是一个字节)。接收端通过同样的异或操作验证数据的完整性。如果计算出的校验值与发送的校验值一致,则认为数据未发生错误。
异或校验
的优缺点
优点
- 计算高效:异或运算简单,速度快,适合硬件实现,特别适用于高效实时校验场景。
- 实现成本低:逻辑简单,适合资源受限环境(如嵌入式系统)。
- 单比特错误检测:可检测单比特错误,适用于传输和存储过程中的单比特翻转问题。
- 轻量级:占用存储空间和计算资源极少,适合对资源严苛的场景。
缺点
- 错误检测能力有限
- 无法定位错误
- 不适合高可靠性场景
例子
原始数据(3个字节):
Byte1: 0xFF (11111111)
Byte2: 0x00 (00000000)
Byte3: 0xAA (10101010)
计算校验值:
Checksum = 0xFF XOR 0x00 XOR 0xAA
= 0xFF XOR 0x00 = 0xFF
= 0xFF XOR 0xAA = 0x55
校验值: 0x55
错误情况: 翻转 Byte1
的第0位和 Byte3
的第0位。
-
翻转前:
Byte1: 0xFF (11111111) Byte3: 0xAA (10101010)
-
翻转后:
Byte1: 0xFE (11111110) // 第0位从1变为0 Byte3: 0xAB (10101011) // 第0位从0变为1
新的数据:
Byte1: 0xFE (11111110)
Byte2: 0x00 (00000000)
Byte3: 0xAB (10101011)
重新计算校验值:
Checksum' = 0xFE XOR 0x00 XOR 0xAB
= 0xFE XOR 0x00 = 0xFE
= 0xFE XOR 0xAB = 0x55
结果: 校验值仍为 0x55
,与原始校验值相同,错误未被检测到。
解释:两个不同字节的相同位被翻转,导致它们在异或校验中相互抵消,校验值保持不变。
05 完整代码
USART.C
#include "stm32f10x.h" // Device header
#include <stdio.h>
#include <stdarg.h>
#include "Delay.h"
#include <stdlib.h>
//***语音合成***//
uint8_t USART2_RxData; //定义串口接收的数据变量
uint8_t USART2_RxFlag; //定义串口接收的标志位变量
/**
* 函 数:串口初始化
* 参 数:无
* 返 回 值:无
*/
void USART2_Init(void)
{
/*开启时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //开启USART2的时钟(语音合成)
/*GPIO初始化*/
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA2引脚初始化为复用推挽输出,PA2是TX
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA3引脚初始化为输入,PA3是RX
/*USART初始化*/
USART_InitTypeDef USART_InitStructure; //定义结构体变量
USART_InitStructure.USART_BaudRate = 9600; //波特率
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //硬件流控制,不需要
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //模式,发送模式和接收模式均选择
USART_InitStructure.USART_Parity = USART_Parity_No; //奇偶校验,不需要
USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位,选择1位
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长,选择8位
USART_Init(USART2, &USART_InitStructure); //将结构体变量交给USART_Init,配置USART2
/*中断输出配置*/
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); //开启串口接收数据的中断
/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn; //选择配置NVIC的USART1线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //指定NVIC线路的响应优先级为
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
/*USART使能*/
USART_Cmd(USART2, ENABLE); //使能USART2,串口开始运行
}
/**
* 函 数:串口发送一个字符串
* 参 数:String 要发送字符串的首地址,长度
* 返 回 值:无
*/
void USART2_SendString(u8 *DAT, u8 len)
{
u8 i;
for(i = 0; i < len; i++)
{
while((USART2->SR & 0X40) == 0);
USART2->DR = (*DAT++);
}
}
/**
* 函 数:获取串口接收标志位
* 参 数:无
* 返 回 值:串口接收标志位,范围:0~1,接收到数据后,标志位置1,读取后标志位自动清零
*/
uint8_t USART2_GetRxFlag(void)
{
if (USART2_RxFlag == 1) //如果标志位为1
{
USART2_RxFlag = 0;
return 1; //则返回1,并自动清零标志位
}
return 0; //如果标志位为0,则返回0
}
/**
* 函 数:获取串口接收的数据
* 参 数:无
* 返 回 值:接收的数据,范围:0~255
*/
uint8_t USART2_GetRxData(void)
{
return USART2_RxData; //返回接收的数据变量
}
/**
* 函 数:USART2中断函数
* 参 数:无
* 返 回 值:无
* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
* 函数名为预留的指定名称,可以从启动文件复制
* 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
*/
void USART2_IRQHandler(void)
{
if (USART_GetITStatus(USART2, USART_IT_RXNE) == SET) //判断是否是USART1的接收事件触发的中断
{
USART2_RxData = USART_ReceiveData(USART2); //读取数据寄存器,存放在接收的数据变量
USART2_RxFlag = 1; //置接收标志位变量为1
USART_ClearITPendingBit(USART2, USART_IT_RXNE); //清除USART1的RXNE标志位
//读取数据寄存器会自动清除此标志位
//如果已经读取了数据寄存器,也可以不执行此代码
}
}
USART.H
#ifndef __USART2_H
#define __USART2_H
#include "stdio.h"
#include "stm32f10x.h"
#define USART2_REC_LEN 200 //定义最大接收字节数 200
#define EN_USART2_RX 1 //使能(1)/禁止(0)串口1接收
void USART2_Init(void);
void USART2_SendString(u8 *DAT, u8 len);
uint8_t USART2_GetRxFlag(void);
uint8_t USART2_GetRxData(void);
#endif
syn6288.c
#include "syn6288.h"
#include "usart2.h"
#include "string.h"
#include "delay.h"
//***语音合成***//
//Music:选择背景音乐。0:无背景音乐,1~15:选择背景音乐
//HZdata字符串内输入空格可以使语音合成模块在文字之间停顿(相当于休止符)
void SYN_FrameInfo(u8 Music, u8 *HZdata)
{
/****************需要发送的文本**********************************/
unsigned char Frame_Info[50];
unsigned char HZ_Length;
unsigned char ecc = 0; //定义校验字节
unsigned int i = 0;
HZ_Length = strlen((char*)HZdata); //需要发送文本的长度
/*****************帧固定配置信息**************************************/
Frame_Info[0] = 0xFD ; //构造帧头FD
Frame_Info[1] = 0x00 ; //构造数据区长度的高字节
Frame_Info[2] = HZ_Length + 3; //构造数据区长度的低字节
Frame_Info[3] = 0x01 ; //构造命令字:合成播放命令
Frame_Info[4] = 0x01 | Music << 4 ; //构造命令参数:背景音乐设定
/*******************校验码计算***************************************/
for(i = 0; i < 5; i++) //依次发送构造好的5个帧头字节
{
ecc = ecc ^ (Frame_Info[i]); //对发送的字节进行异或校验
}
for(i = 0; i < HZ_Length; i++) //依次发送待合成的文本数据
{
ecc = ecc ^ (HZdata[i]); //对发送的字节进行异或校验
}
/*******************发送帧信息***************************************/
memcpy(&Frame_Info[5], HZdata, HZ_Length);
Frame_Info[5 + HZ_Length] = ecc;
USART2_SendString(Frame_Info, 5 + HZ_Length + 1);
}
/***********************************************************
* 名 称: YS_SYN_Set(u8 *Info_data)
* 功 能: 主函数 程序入口
* 入口参数: *Info_data:固定的配置信息变量
* 出口参数:
* 说 明:本函数用于配置,停止合成、暂停合成等设置 ,默认波特率9600bps。
* 调用方法:通过调用已经定义的相关数组进行配置。
**********************************************************/
void YS_SYN_Set(u8 *Info_data)
{
u8 Com_Len;
Com_Len = strlen((char*)Info_data);
USART2_SendString(Info_data, Com_Len);
}
syn6288.h
#ifndef __SYN6288_H
#define __SYN6288_H
#include "stm32f10x.h"
void SYN_FrameInfo(u8 Music, u8 *HZdata);
void YS_SYN_Set(u8 *Info_data);
#endif