Firtec

Sintetizadores de voz.

En la actualidad podemos disponer de una enorme variedad de formas para representar información de manera visual.
Desde simples presentadores de LED a pantallas gráficas complejas, pero que sucede cuando necesitamos que nuestra electrónica "hable", desde siempre ese ha sido un punto complicado, lograr que una máquina reproduzca sonidos humanos fue siempre un tema desafiante.
El chip S1V30120 de Epson contiene todos los codecs analógicos, memoria y algoritmos incorporados para imitar la voz humana controlando todo el chip a través de un bus SPI. El sonido de voz resultante se puede ajustar para que se escuche como ingles americano, español clásico o español latino, también dispone de varios ajustes finos para ajustar el tono y velocidad de tal forma que se puede llevar su funcionamiento a un punto en que la voz resultante es muy parecida a la voz humana. (Ninguno de estos ajustes se han realizado en este ejemplo, lo que se escucha es la configuración "de fábrica".)
Presenta una solución económica y relativamente simple, siendo el único cuidado la selección del tipo de microcontrolador a utilizar que tiene que disponer de por lo menos 45 KB de flash. La razón de ello son los datos de inicialización que se deben cargar para que el sintetizador vocal funcione.

En el ejemplo propuesto estamos usando una placa provista por Mikroelektronika denominada TextToSpeech click. Esta placa monta un chip S1V30120 con toda su electrónica periférica lista para ser conectada y empezar a trabajar.
El controlador que estamos usando es un STM32F407vg montado sobre un Shield para la entrenadora Discovery con cuatro puertos MiKroBus.
Un barómetro provee los datos de temperatura y humedad que el sintetizador vocal leerá, e informar estos datos de manera hablada.

El siguiente es el código completo de funcionamiento.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
/*****************************************************************************
*  Descripción  : Programa ejemplo para el sintetizador vocal S1V30120
*  Target       : STM32F407VG
*  ToolChain    : MiKroC para ARM V5.0
*         www.firtec.com.ar
*****************************************************************************/
// NOTA: Activar Tools-> Options-> Output-> Case Sensitive
//----------------------------------------------------------------------------
  #include "text_to_speech.h"
 // Pines usados en la pantalla LCD
   sbit LCD_RS at GPIOE_ODR.B0;  //<<<<
   sbit LCD_EN at GPIOE_ODR.B6;
   sbit LCD_D4 at GPIOC_ODR.B12;
   sbit LCD_D5 at GPIOC_ODR.B13;
   sbit LCD_D6 at GPIOC_ODR.B14;
   sbit LCD_D7 at GPIOC_ODR.B15;
  // Pines usados en S1V30120
  sbit TTS_RST at GPIOE_ODR.B4;
  sbit TTS_CS at GPIOE_ODR.B5;
  sbit TTS_MUTE at GPIOC_ODR.B5;
  sbit TTS_RDY at GPIOD_IDR.B2;
 
  void hardware_init(void);
  void S1V30120_config(void);
  void msg_blk( uint16_t *req, uint16_t *err );
  void fatal_err( uint16_t *err );
  void system_init( void );
  char txt[12];
  
  unsigned char I2C_LeerRegistro(char);
  void I2C_EscribirRegistro(char,char);
  float LPS25HB_LeerTemperatura(void);
  float LPS25HB_LeerPresMilibares(void);
  char LPS25HB_LeerStatus(void);
 
  unsigned char device_id = 0;
  unsigned char temporal_datos[12];
  volatile float temperatura, presion;
 
  #define I2C_ADR       0x5D
 
//=====================================================
// Esta función lee el registro Status del sensor
// ====================================================
char LPS25HB_LeerStatus() {
  return I2C_LeerRegistro(0x27);
}
//======================================================
// Esta función lee el valor del barómetro y retorna la
// presión medida en milibares.
// El valor barométrico se lee de tres registros.
// =====================================================
float LPS25HB_LeerPresMilibares() {
  char temporal;
  unsigned long resultado;
  temporal = I2C_LeerRegistro(0x2A);
  resultado = (unsigned long) temporal;
  temporal = I2C_LeerRegistro(0x29);
  resultado = (resultado << 8) | temporal;
  temporal = I2C_LeerRegistro(0x28);
  resultado = (resultado << 8) | temporal;
  return (float)resultado/4096;
}
//=====================================================
// Esta función escribe en los registros internos
// del sensor.
// ====================================================
void I2C_EscribirRegistro(char wrDir, char wrDato) {
  temporal_datos[0] = wrDir;
  temporal_datos[1] = wrDato;
  I2C1_Start();
  I2C1_Write(I2C_ADR,temporal_datos,2,END_MODE_STOP);
}
//=====================================================
// Esta función lee el valor de temperatura desde el
// sensor.
// ====================================================
float LPS25HB_LeerTemperatura() {
  unsigned char temporal;
  int resultado;
  temporal = I2C_LeerRegistro(0x2C);
  resultado = (int) temporal;
  temporal = I2C_LeerRegistro(0x2B);
  resultado = (resultado << 8) | temporal;
  return ((float)resultado / 480) + 42.5;
}
//=====================================================
// Esta función lee en los registros internos
// del sensor.
// ====================================================
unsigned char I2C_LeerRegistro(char rDir) {
  temporal_datos[0] = rDir;
  I2C1_Start();
  I2C1_Write(I2C_ADR,temporal_datos,1,END_MODE_RESTART);
  I2C1_Read(I2C_ADR,temporal_datos,1,END_MODE_STOP);
  return temporal_datos[0];
}
 
void msg_blk( uint16_t *req, uint16_t *err )
{
 
}
 
void fatal_err( uint16_t *err )
{
    GPIOD_ODR.B14 = 1;
    tts_init();
    tts_fatal_err_callback( fatal_err );
    
}
void S1V30120_config(){
    tts_init();
    tts_msg_block_callback( msg_blk );
    tts_fatal_err_callback( fatal_err );
    tts_image_load( ( uint8_t* )TTS_INIT_DATA, sizeof( TTS_INIT_DATA ) );
    tts_image_exec();
    tts_interface_test();
    tts_power_default_config();
    tts_audio_default_config();
    tts_volume_set( 0 );
    tts_default_config();
    tts_config( 0x04, false, TTSV_LS, 0x0200 );
 
}
void hardware_init()
{
    GPIO_Digital_Output( &GPIOE_ODR, _GPIO_PINMASK_4 );
    GPIO_Digital_Output( &GPIOE_ODR, _GPIO_PINMASK_5 );
    GPIO_Digital_Output( &GPIOC_ODR, _GPIO_PINMASK_5 );
    GPIO_Digital_Input( &GPIOD_IDR, _GPIO_PINMASK_2 );
    Delay_ms( 200 );
 
    SPI3_Init_Advanced( _SPI_FPCLK_DIV128, _SPI_MASTER | _SPI_8_BIT |
                        _SPI_CLK_IDLE_HIGH | _SPI_SECOND_CLK_EDGE_TRANSITION |
                        _SPI_MSB_FIRST | _SPI_SS_DISABLE | _SPI_SSM_ENABLE |
                        _SPI_SSI_1, &_GPIO_MODULE_SPI3_PB345);
    Delay_ms( 200 );
}
void ISR_init(){
  GPIO_Digital_Input(&GPIOA_BASE, _GPIO_PINMASK_0);   // PA0 como entrada
  SYSCFGEN_bit = 1; // RCC APB2 reloj de periféricos activo
  SYSCFG_EXTICR1 = 0x00000000;       // Mapa de pines para PA0
  EXTI_RTSR = 0x00000000;            // Flanco de subida para PA0 desabilitado
  EXTI_FTSR = 0x00000001;            // Flanco de bajado para PA0
  EXTI_IMR |= 0x00000001;            // Set de la máscara para PA0
  NVIC_IntEnable(IVT_INT_EXTI0);     // Habilita interrupciónes EXTI0
}
void main(){
     GPIO_Digital_Output(&GPIOD_BASE, _GPIO_PINMASK_14);
     GPIO_Digital_Output(&GPIOD_BASE, _GPIO_PINMASK_12);
     GPIOD_ODR.B14 = 0;
     hardware_init();
     S1V30120_config();
     Lcd_Init();
     ISR_init();
     Lcd_Cmd(_LCD_CLEAR);
     Lcd_Cmd(_LCD_CURSOR_OFF);
     I2C1_Init_Advanced(100000, &_GPIO_MODULE_I2C1_PB87);
     Lcd_Out(1,2,"Sensor Barometrico");   
     Lcd_Out(2,7,"LPS25HB");
     Lcd_Out(3,1,"Temperatura:  ");
     Lcd_Out(4,1,"P/Milibares:");
     device_id = I2C_LeerRegistro(0x0F); // Lee ID del sensor Barométrico
     if(device_id != 0xBD){
       Lcd_Out(2,1,"ERROR de Sensor");
       while(1);
     }
     I2C_EscribirRegistro(0x20,0xB0);
     GPIOD_ODR.B12 = 0;
  while(1) {
  unsigned char status;
  status = LPS25HB_LeerStatus();
  if(status & 0x01) {
       temperatura = LPS25HB_LeerTemperatura();  // Lee temperatura
       sprintf(temporal_datos, "%2.1f", temperatura);
       Lcd_Out(3,13,temporal_datos);
       }
  Delay_ms(250);
  if(status & 0x02) {
       presion = LPS25HB_LeerPresMilibares(); // Lee presión en milibares
       sprintf(temporal_datos, "%4.0f", presion);
       Lcd_Out(4,13,temporal_datos);
       }
   Delay_ms(1000);
   }
  }
 
   void ExtInt() iv IVT_INT_EXTI0 ics ICS_AUTO {
   EXTI_PR.B0 = 1;    // Borra la bandera de interrupción
   GPIOD_ODR.B12 = ~ GPIOD_ODR.B12;
   sprintf(txt, "Temperatura %2.1f", temperatura);
   tts_speak( txt );
   sprintf(txt, "Presion %4.0f", presion-1);
   tts_speak( txt );
}

Para poder compilar este código se necesita la biblioteca para el TextToSpeech click que se descarga desde el mismo sitio de Mikroelektronika.
Para reproducir la lectura de los valores del barómetro se oprime el botón de usuario (PA0) que se ha vinculado a una de interrupción externa.

Esto es solo un ejemplo de uso, no se ha ajustado ningún parámetro del sintetizador vocal para refinar el sonido o hacer que la voz escuchada suene natural.

(Ejemplo extraído del curso "MikroC pra ARM")