Let’s start with his voice! (Part 2 – Establishing communication)

After reviewing the datasheet for the SC-01-A, the next decision was, do I pull the IC out of HERO 1’s speech board and talk to it directly, or do I use the current pinning of the HERO 1 speech board instead.  This answer became pretty clear as soon as I came to realize that the purpose of the board is to amplify the signal from the SC-01-A – in other words… it’s an amp!  If I were to pull the chip, I’d end up having to “grow my own” amp, and so the decision to keep the board & chip intact – at least until Phase 1 of the HERO 1 project is complete – seemed to definitely be the right one for now.

In my search, I found a blog (http://elek101.blogspot.com/2011/11/arduino-votrax-world.html) from someone who had laid down some base code for talking to a Votrax SC-01-A.  This pointed me in the right direction, and I was soon able to replicate his results.  Another quick search and I found a great article:

Raspberry Pi and Arduino Connected Using I2C.  Following through this helped me configure the Pi correctly, and get the base project presented working – I soon had an led flashing from the Arduino after sending a command from the Pi.  Awesome.  It then became a simple exercise of combining and enhancing the two base projects.

Arduino (I2C Slave) Speech Code:

Here’s the code for the Arduino I2C slave that sends the received message out to the SC-01-A chip, which results in the speaking of a phoneme.  The send data code isn’t quite finished in that although it sends back the size of the message received and spoken, it should be modified to send a single byte that tells how many bytes follow to describe the number.  For instance:

if 10 bytes were received, we should send back: (0x01, 0x10).
200 => (0x01, 0xC8)
255 => (0x01, 0xFF)
300 => (0x02, 0x2C, 0x01)

The current code would return (0xC8), (0xFF), (0x2C, 0x01) respectively.

#include <Wire.h>
#define STB 2  // Strobe need to go high to latch data
#define AR 3  // Acknowledge/Request goes high when ready
#define POWER 4
#define SLAVE_ADDRESS 0x04

//bytes to say "READY"
byte readyMsg[] = { 0x2B, 0x02, 0x00, 0x1E, 0x29, 0x03, 0xFF };
byte buffer[1024];
boolean speaking, speechReady;
int receivedBytes, lastRecv;

void setup()
{
  Serial.begin(9600);
  Serial.println("Speech Board Loading...");
  pinMode(13, OUTPUT);
  // initialize i2c as slave
  Wire.begin(SLAVE_ADDRESS);
  // define callbacks for i2c communication
  Wire.onReceive(receiveData);
  Wire.onRequest(sendData);
  
  // set Port B 6 lowest bit as Output (Arduino Uno pin 8 to 13) 
  DDRB = B00111111; 
  // Setup pins for speech 
  pinMode(STB, OUTPUT); 
  pinMode(POWER, OUTPUT);
  pinMode(AR, INPUT); 
  digitalWrite(STB, LOW);
  
  // must stay low 
  digitalWrite(POWER, LOW); 
  speaking = false; 
  speechReady = false; 
  receivedBytes = 0; 
  say(readyMsg); 
  Serial.println("Speech Board Initialized!"); 
}

void loop() 
{
  if(speechReady) 
  { 
    Serial.println("Speech is ready"); 
    say(buffer); 
  } 
  delay(100); 
}

void say(byte* message) 
{ 
  if(!speaking) 
  { 
    speaking = true; 
    Serial.println("speaking"); 
    digitalWrite(POWER, HIGH); 
    noInterrupts(); 
    for(int i = 0; message[i] != 0xFF; i++) 
      pronounce(message[I]); 
    interrupts(); 
    digitalWrite(POWER, LOW); 
    speechReady = false; 
    speaking = false; 
  } 
}

void pronounce(byte phoneme) 
{ 
  PORTB = phoneme
  // Set Stb = 1 for 2usec to tell the chip to read the Port 
  digitalWrite(STB, HIGH); 
  delayMicroseconds(2); 
  digitalWrite(STB, LOW);
  // Wait for AR=1 when chip is ready 
  while (digitalRead(AR) == 0); 
} 

// callback for received data 
void receiveData(int byteCount) 
{ 
  if(speaking) 
  { 
    Serial.println("receive called while speaking"); 
    return; 
  }
  while(Wire.available()) 
  { 
    buffer[receivedBytes] = Wire.read(); 
    receivedBytes++; 
    if(buffer[receivedBytes-1] == 0xFF) 
    {
      speechReady = true; 
      lastRecv = receivedBytes; 
      receivedBytes = 0; 
      break; 
    } 
  } 
}

// callback for sending data
void sendData()
{
  int recv = lastRecv;
  Serial.print("sendData called, returning ");
  Serial.println(recv);
  if(recv == 0)
    Wire.write(0);
  else
  {
    while(recv > 0)
    {
      //should send a byte that tells how many bytes describe the number
      Wire.write((int)recv & 0xFF);
      recv >>= 8;
    }
  }
  lastRecv = 0;
}

Raspberry Pi (I2C Master) Code

#include <stdlib.h>
#include <unistd.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>

#define I2C_SPEECH 0x04

int main()
{
  char rxBuffer[32]
  //receive buffer
  //bytes to say "HELLO"
  byte txBuffer[] = { 0x1B, 0x02, 0x23, 0x18, 0x23, 0x16, 0x37,0x3E, 0xFF };

  int speechAddress = I2C_SPEECH
  //speech devices address
  int tenBitAddress = 0;
  int opResult = 0;

  // Create a file descriptor for the I2C bus
  int i2cHandle = open("/dev/i2c-1", O_RDWR);

  // I2C device is not 10-bit
  opResult = ioctl(i2cHandle, I2C_TENBIT, tenBitAddress);

  // set address of speech board to I2C
  opResult = ioctl(i2cHandle, I2C_SLAVE, speechAddress);

  //Clear out the buffers
  memset(rxBuffer, 0, sizeof(rxBuffer));

  for(unsigned int i = 0; i < sizeof(txBuffer); i++)
  {
    opResult = write(i2cHandle, &txBuffer[i], 1);
    if(opResult != 1) printf("No ACK bit!n");
    usleep(1000); //sleep for 1 millisecond
  }
  opResult = read(i2cHandle, rxBuffer, 32);
  printf("Sent: %d, received: %dn", sizeof(txBuffer), rxBuffer[0]);
  
  return 0;
}

Of course, this code is probably of little use without the proper wiring schematic (and for that matter, a Hero 1 speech board, but I can’t help you with that one). So in a post coming soon, I’ll break out the Arduino to Hero 1 wiring and Raspberry Pi to Arduino I2C wiring.  Additionally, we can definitely do better than the proof of concept, and instead, get the Raspberry Pi to send the byte codes for any sentence we type in.  We’ll look into that more in the next post.

Leave a Reply

Your email address will not be published. Required fields are marked *