让你的文字转成语音


孤独的科技
原创
发布时间: 2025-09-27 16:40:26 | 阅读数 0收藏数 0评论数 0
封面
在网上闲逛的时候偶尔间发现一款语音转文字的电子模块,觉得很有意思,随而有了一个试试的想法,买回来后就用手边的STM32F103C8T6驱动了一下,确实挺有意思,随手写下这篇短文以记之。

准备工作:

材料:

  1. 文字转语音模块
  2. 小喇叭
  3. STM32F103C8T6单片机
  4. ST-LINK烧录器
1

模块的资料

TTS 离线模块是一款功能强大的高品质语音模块产品,采用了高性能 32 位处理器、最高频率可 达 240MHz。使用高集成度的语音合成芯片,可实现中文、英文语音合成;并集成了语音编码、解码 功能,可支持用户进行语音合成和语音播放,具有低成本、低功耗、高可靠性、通用性强等特点, 带有地址播放、插播、单曲循环、所有曲目循环、随机播放等功能。

控制方式:3.3V TTL 串口,默认波特率 9600;

上电默认不播放;具备 BUSY 状态指示,上电默认 BUSY 播放时为低电平,不播放时为高电平 (可发码修改默认配置);

音频输出方式,支持 DAC 输出,带 3W 功放输出;

音量可调,音量等级 31 级;

大功率 IO 驱动能力,最高可直接驱动 32mA;

支持任意中文文本、英文字母的合成,并且支持中文与英文字母混读,英文字母暂不支持使用 标记实现变速变调;模块支持任意中文、英文字母的合成,可以采用 GB 2312 编码方式。每次 合成的文本量最多可达 2K 字节。模块对文本进行分析,对常见的数字、号码、时间、日期、度 量衡符号等格式的文本,芯片能够根据内置的文本匹配规则进行正确的识别和处理。

支持多种控制命令 如合成文本、停止合成、暂停合成、恢复合成、状态查询、进入休眠模式、唤醒等。 控制器通过 通讯接口发送控制命令可以对芯片进行相应的控制。芯片的控制命令非常简单易用,例如:芯片可通 过参考对应的指令说明即可实现播放提示音和中文文本合播放成,还可以通过标记文本实现对合成的 参数设置。

支持多种方式查询模块的工作状态 包括:查询状态管脚电平、通过读芯片自动返回的工作状态字、发送查询命令获得芯片工作状态的回

2

模块的驱动介绍

模块支持标准 UART 异步串口接口,默认波特率 9600,属于 3.3V TTL 电平接口。通讯数据格式 是:起始位:1 位;数据位:8 位;奇偶位:无;停止位:1 位。使用电脑串口调试助手,需要正确 设置串口的参数。

3

模块的驱动命令格式

帧长度:2 字节,指帧长度+流水号+应答标志+数据帧来源+N 个命令信息+累加和校验和的长 度,帧长度高位在前低位在后;

流水号:1 字节,每次一帧数据自动加 1,避免接收重复的数据,相同流水号的数据为重复数据 应做丢弃;

应答标志:1 字节,固定填 00;

数据帧来源:1 字节,02 为 TTS 芯片端,03 为 MCU 芯片端;

N 个命令信息:由 N 个命令信息组成,1 个命令信息内容为 2 字节命令+1 字节数据长度+N 字节 数据,单个命令信息最大支持 255 个字节数据,但支持同时传递多个相同的命令携带不同信息(返 回码的此处信息与发码的略微有区别,详情见返码命令介绍);

累加和校验:是指帧长度+流水号+应答标志+数据帧来源+N 个命令信息和的低字节。

详细信息参考数据手册见附件

PDF
TTS语音离线模块.pdf
1.26MB
4

准备驱动模块硬件

根据自己的实际情况把硬件连接起来:我是用模块的TX引脚接单片机的串口3的RX(PB11),模块的RX引脚接单片机的串口3的TX(PB10),模块的BUSY引脚接单片机的PBA12.,注意电源是5v的。

5

编写代码

用Keil5新建一个工程项目,新建一个模块的文件和头文件(我取得名字叫sound.c,sound.h),在文件中写代码如下:

sound.h

#ifndef __SOUND_H
#define __SOUND_H
#include "sys.h"
#include <stdint.h>

// TTS模块状态定义
typedef enum {
TTS_READY = 0,
TTS_BUSY,
TTS_ERROR
} TTS_Status;

// 初始化函数
void TTS_Init(void);
void SpeakText(u8 TTS_data[]);
u8 Get_TTS_Status(void); // 读取TTS状态引脚

#endif


sound.c

#include "sound.h"
#include "sys.h"
#include "stdio.h"
#include "delay.h"
#include "stm32f10x_it.h"
#include <string.h>





// 私有变量定义
static volatile TTS_Status tts_status = TTS_READY;
static uint8_t dma_tx_complete = 1;
static uint8_t serial_number = 1;
static uint8_t tts_tx_buffer[512];

// 引脚定义
#define TTS_BUSY_PIN GPIO_Pin_12
#define TTS_BUSY_PORT GPIOA

// USART3定义
#define TTS_USART USART3
#define TTS_DMA_CHANNEL DMA1_Channel2
#define TTS_DMA_FLAG_TC DMA1_FLAG_TC2

// 命令码定义
#define CMD_TTS_SPEAK 0x03E8
#define CMD_TTS_CTRL 0x03E9
#define CMD_VOLUME_CTRL 0x00CC
#define CMD_VOLUME_UP 0x00CD
#define CMD_VOLUME_DOWN 0x00CE
#define CMD_SEND_CACHE 0x03EA
#define CMD_PLAY_CACHE 0x03EB
#define CMD_SLEEP 0x038F

// 私有函数声明
static void GPIO_Configuration(void);
static void USART3_Configuration(void);
static void DMA_Configuration(void);
static void NVIC_Configuration(void);
static void Send_Command_Frame(uint16_t command, const uint8_t* data, uint8_t data_length);

void TTS_Init(void)
{
GPIO_Configuration();
USART3_Configuration();
DMA_Configuration();
NVIC_Configuration();
// tts_status = TTS_READY;
// dma_tx_complete = 1;
// serial_number = 1;
}

static void GPIO_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
// 配置USART3引脚: PB10(TX), PB11(RX)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(GPIOB, &GPIO_InitStructure);
// 配置忙检测引脚PA12
GPIO_InitStructure.GPIO_Pin = TTS_BUSY_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(TTS_BUSY_PORT, &GPIO_InitStructure);
}

static void USART3_Configuration(void)
{
USART_InitTypeDef USART_InitStructure;
// 使能USART3时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE);
// USART3配置
USART_InitStructure.USART_BaudRate = 9600;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Tx;
USART_Init(TTS_USART, &USART_InitStructure);
USART_Cmd(TTS_USART, ENABLE);
USART_DMACmd(TTS_USART, USART_DMAReq_Tx, ENABLE);
}

static void DMA_Configuration(void)
{
DMA_InitTypeDef DMA_InitStructure;
// 使能DMA1时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// DMA1 Channel2配置 (USART3_TX)
DMA_DeInit(TTS_DMA_CHANNEL);
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(TTS_USART->DR);
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)tts_tx_buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = 0;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(TTS_DMA_CHANNEL, &DMA_InitStructure);
DMA_ITConfig(TTS_DMA_CHANNEL, DMA_IT_TC, ENABLE);
}

static void NVIC_Configuration(void)
{
NVIC_InitTypeDef NVIC_InitStructure;
// 配置DMA通道中断
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}

// DMA传输完成中断处理函数
void DMA1_Channel2_IRQHandler(void)
{
if (DMA_GetITStatus(TTS_DMA_FLAG_TC))
{
// 清除中断标志
DMA_ClearITPendingBit(TTS_DMA_FLAG_TC);
// 禁用DMA
DMA_Cmd(TTS_DMA_CHANNEL, DISABLE);
// 设置传输完成标志
dma_tx_complete = 1;
// 更新状态为忙碌(等待语音播放完成)
tts_status = TTS_BUSY;
}
}


u8 Get_TTS_Status(void)
{
if(GPIO_ReadInputDataBit(TTS_BUSY_PORT, TTS_BUSY_PIN))
return 1;
else
return 0;
}

// ========== 公共API函数实现 ==========

void SpeakText(u8 TTS_data[])
{
u8 data_length_temp = strlen(TTS_data);
int8_t send_code[9+data_length_temp+2];
uint8_t pos = 0;
int8_t zhen_length_high = 0;
int8_t zhen_length_low = 0;
int8_t serial_number = 1;
int8_t sum;
size_t i=0;

while(Get_TTS_Status()==0); // 检查引脚等语音模块空闲

zhen_length_low = 9 + data_length_temp;
sum = zhen_length_low + zhen_length_high + serial_number + 3 + 232 + 3 + data_length_temp;

send_code[pos++] = 0x7e; // 起始位
send_code[pos++] = zhen_length_high; //帧长度高位,指起始码+帧长度+命令码+N 个 KEYID 信息+累加和校验和+结束码的长度
send_code[pos++] = zhen_length_low; //帧长度低位,指起始码+帧长度+命令码+N 个 KEYID 信息+累加和校验和+结束码的长度
send_code[pos++] = serial_number; // 流水号
send_code[pos++] = 0x00; // 应答标志,默认为00
send_code[pos++] = 0x03; //数据帧来源,02 为 TTS 芯片端,03 为 MCU 芯片端;
send_code[pos++] = 0x03; //ID 地址高位
send_code[pos++] = 0xE8; //ID 地址低位
send_code[pos++] = data_length_temp; //数据长度,发送数据长度strlen(TTS data)


for(i=0;i<data_length_temp;i++)
{

send_code[pos++] = TTS_data[i];
sum = sum + TTS_data[i];

}

send_code[pos++] = sum; // 校验和
send_code[pos++] = 0xEF; // 结束码


// 将数据复制到全局缓冲区tts_tx_buffer
if (pos > sizeof(tts_tx_buffer)) {
// 错误处理:数据过长
return;
}
memcpy(tts_tx_buffer, send_code, pos);



// 启动DMA传输
dma_tx_complete = 0;
DMA_SetCurrDataCounter(TTS_DMA_CHANNEL, pos);
DMA_Cmd(TTS_DMA_CHANNEL, ENABLE);
delay_ms(200);
}







6

烧录测试

语音OK,有了这个模块,可以搞很多有趣的项目!

ZIP
文字转语音.zip
7.76MB
阅读记录0
点赞0
收藏0
禁止 本文未经作者允许授权,禁止转载
猜你喜欢
评论/提问(已发布 0 条)
评论 评论
收藏 收藏
分享 分享
pdf下载 下载