Library for Nuvoton ISD1700 chipcorder family

Hello,

I have been working on a library for Nuvoton ISD17xx chipcorder family. The library follows below.

These chips are able to record and playback audio at sampling rates up to 12 kHz. The chip model number refers to the maximum recording duration (they start from 1730 and go up to 17240).

Unfortunately the audio encoding is proprietary and it appears that external digital audio encoded with popular codecs is not supported (yet).

ISD1700.h

/*
  ISD1700.h - Library for Nuvoton ISD1700 chipcorders
  Copyright (c) 2009 Marcelo Shiniti Uchimura.
  Author: Marcelo Shiniti Uchimura, CITS, <www.cits.br>
  Version: February 8, 2009

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

#ifndef ISD1700_h
#define ISD1700_h

#define SCK_PIN   13
#define MISO_PIN  12
#define MOSI_PIN  11

#include "WProgram.h"

class ISD1700
{
  private:
    uint8_t  _SSPin; // Arduino's digital pin which chipcorder is attached to

    /* status registers */
    uint16_t _SR0;      // Status Register #0                   uint16_t <15:0>
      uint8_t  _SR1;      // Status Register #1                   uint8_t  <7:0>
      uint16_t _APC;      // Analog Path Configuration Register   uint16_t <11:0>
      uint16_t _PP;       // Playback Pointer                     uint16_t <10:0>
      uint16_t _RP;       // Record Pointer                       uint16_t <10:0>
      uint8_t  _DEVICEID; // Device Identification Register       uint8_t  <7:3>

      /* private methods */
      uint8_t spi_transfer(uint8_t);
      void sendCmd(uint8_t);
      void sendCmd(uint8_t, uint16_t);
      void sendCmd(uint8_t, uint16_t, uint16_t);
  public:
    /* constructor */
    ISD1700(uint8_t);
      
      /* functions to enquire status */
      uint8_t RDY(void);
      uint8_t ERASE(void);
      uint8_t PLAY(void);
      uint8_t REC(void);
      uint8_t SE1(void);
      uint8_t SE2(void);
      uint8_t SE3(void);
      uint8_t SE4(void);
      uint8_t CMD_ERR(void);
      uint8_t FULL(void);
      uint8_t PU(void);
      uint8_t EOM(void);
      uint8_t INT(void);

      /* chipcorder commands */
      void     pu(void);
      void     stop(void);
      void     reset(void);
      void     clr_int(void);
      uint8_t  rd_status(void);
      uint16_t rd_play_ptr(void);
      void     pd(void);
      uint16_t rd_rec_ptr(void);
      uint8_t  devid(void);
      void     play(void);
      void     rec(void);
      void     erase(void);
      void     g_erase(void);
      uint16_t rd_apc(void);
      void     wr_apc1(uint16_t);
      void     wr_nvcfg(void);
      void     ld_nvcfg(void);
      void     fwd(void);
      void     chk_mem(void);
      void     extclk(void);
      void     wr_apc2(uint16_t);
      void     set_play(uint16_t, uint16_t);
      void     set_rec(uint16_t, uint16_t);
      void     set_erase(uint16_t, uint16_t);
};

#endif

(contd.)

ISD1700.cpp

/*
  ISD1700.cpp - Library for Nuvoton ISD1700 chipcorders
  Copyright (c) 2009 Marcelo Shiniti Uchimura.
  Author: Marcelo Shiniti Uchimura, CITS, <www.cits.br>
  Version: February 8, 2009

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*/

/*
 * Includes
 */
#include "WProgram.h"
#include "ISD1700.h"

/*
 * Definitions
 */

/*
 * Constructor
 * Based on Cam Thompson's Spi library
 */
ISD1700::ISD1700(uint8_t SSPin)
{
  uint8_t dummy;
  
  _SSPin = SSPin;

  // initialize SPI communication
  pinMode(SCK_PIN, OUTPUT);
  pinMode(MOSI_PIN, OUTPUT);
  pinMode(MISO_PIN, INPUT);
  pinMode(_SSPin, OUTPUT);

  // initialize SPI communication
  SPCR = (1<<CPHA) | (1<<CPOL) | (1<<DORD) | (1<<SPE) | (1<<MSTR);
  dummy = SPSR;
  dummy = SPDR;
  digitalWrite(_SSPin, HIGH);

  // initialize private vars
  _SR0 = 0;      // Status Register #0                   uint16_t <15:0>
  _SR1 = 0;      // Status Register #1                   uint8_t  <7:0>
  _APC = 0;      // Analog Path Configuration Register   uint16_t <11:0>
  _PP = 0;       // Playback Pointer                     uint16_t <10:0>
  _RP = 0;       // Record Pointer                       uint16_t <10:0>
  _DEVICEID = 0; // Device Identification Register       uint8_t  <7:3>

  // reset chipcorder
  reset();
}

/*
 * Transfer data to SPI peripheral slaves
 * Based on Cam Thompson's Spi library
 */
uint8_t ISD1700::spi_transfer(uint8_t data)
{
  SPDR = data;
  while (!(SPSR & (1<<SPIF)));
  return SPDR;
}

/*
 * Send a command to chipcorder
 */
void ISD1700::sendCmd(uint8_t cmd)
{
  uint8_t data[4] = {0xFF,0xFF,0xFF,0xFF};

  digitalWrite(_SSPin, LOW);
  data[0] = spi_transfer(cmd);
  data[1] = spi_transfer(0);
  switch(cmd)
  {
    case 0x05:                     // RD_STATUS
      case 0x09:                     // DEVID
        data[2] = spi_transfer(0);
        break;
      case 0x06:                     // RD_PLAY_PTR
      case 0x08:                     // RD_REC_PTR
      case 0x44:                     // RD_APC
        data[2] = spi_transfer(0);
        data[3] = spi_transfer(0);
        break;
  }
  digitalWrite(_SSPin, HIGH);
  _SR0 = word(data[1],data[0]);
  switch(cmd)
  {
    case 0x05:    // RD_STATUS
        _SR1 = data[2];
        break;
      case 0x06:    // RD_PLAY_PTR
        _PP = word(data[3],data[2]);
        break;
      case 0x08:    // RD_REC_PTR
        _RP = word(data[3],data[2]);
        break;
      case 0x09:    // DEVID
        _DEVICEID = data[2];
        break;
      case 0x44:    // RD_APC
        _APC = word(data[3],data[2]);
        break;
  }
}

/*
 * Send a command to chipcorder
 * plus one 12-bit parameter
 *
 * Usage is valid for the following commands:
 *   WR_APC1
 *   WR_APC2
 */
void ISD1700::sendCmd(uint8_t cmd, uint16_t apc)
{
  uint8_t data[3] = {0xFF,0xFF,0xFF};

  // check if it is either WR_APC1 or WR_APC2 instruction
  // exit otherwise
  if(cmd != 0x45 && cmd != 0x65)
      return;

  digitalWrite(_SSPin, LOW);
  data[0] = spi_transfer(cmd);
  data[1] = spi_transfer( (uint8_t)(apc & 0xFF) );
  data[2] = spi_transfer( (uint8_t)(apc>>8) );
  digitalWrite(_SSPin, HIGH);
  _SR0 = word(data[1],data[2]);
}

/*
 * Send a command to chipcorder
 * plus two 11-bit parameters
 *
 * Usage is valid for the following commands:
 *   SET_PLAY
 *   SET_REC
 *   SET_ERASE
 */
void ISD1700::sendCmd(uint8_t cmd, uint16_t startAddr, uint16_t endAddr)
{
  uint8_t data[2] = {0xFF,0xFF};

  // check if it is either SET_PLAY, SET_REC, or SET_ERASE instruction
  // if not, exit method
  if(cmd != 0x80 && cmd != 0x81 && cmd != 0x82)
      return;

  digitalWrite(_SSPin, LOW);
  data[0] = spi_transfer(cmd);
  data[1] = spi_transfer(0);
  data[0] = spi_transfer( (uint8_t)(startAddr & 0xFF) );
  data[1] = spi_transfer( (uint8_t)(startAddr>>8) );
  data[0] = spi_transfer( (uint8_t)(endAddr & 0xFF) );
  data[1] = spi_transfer( (uint8_t)(endAddr>>8) );
  data[0] = spi_transfer(0);
  digitalWrite(_SSPin, HIGH);
  _SR0 = word(data[1],data[0]);
}

/*
 * Status flags
 */
uint8_t ISD1700::RDY(void)
{
  rd_status();
  return (_SR1 & 1);
}

uint8_t ISD1700::ERASE(void)
{
  rd_status();
  return (_SR1 & 2);
}

uint8_t ISD1700::PLAY(void)
{
  rd_status();
  return (_SR1 & 4);
}

uint8_t ISD1700::REC(void)
{
  rd_status();
  return (_SR1 & 8);
}

uint8_t ISD1700::SE1(void)
{
  rd_status();
  return (_SR1 & 16);
}

uint8_t ISD1700::SE2(void)
{
  rd_status();
  return (_SR1 & 32);
}

uint8_t ISD1700::SE3(void)
{
  rd_status();
  return (_SR1 & 64);
}

uint8_t ISD1700::SE4(void)
{
  rd_status();
  return (_SR1 & 128);
}

uint8_t ISD1700::CMD_ERR(void)
{
  return (uint8_t)(_SR0 & 1);
}

uint8_t ISD1700::FULL(void)
{
  return (uint8_t)(_SR0 & 2);
}

uint8_t ISD1700::PU(void)
{
  return (uint8_t)(_SR0 & 4);
}

uint8_t ISD1700::EOM(void)
{
  return (uint8_t)(_SR0 & 8);
}

uint8_t ISD1700::INT(void)
{
  return (uint8_t)(_SR0 & 16);
}


/*
 * PU Power up
 */
void ISD1700::pu(void)
{
  sendCmd(0x01);
}

/*
 * STOP Stop
 */
void ISD1700::stop(void)
{
  sendCmd(0x02);
}

/*
 * RESET Reset
 */
void ISD1700::reset(void)
{
  sendCmd(0x03);
}

/*
 * CLR_INT Clear interrupt
 */
void ISD1700::clr_int(void)
{
  sendCmd(0x04);
}

/*
 * RD_STATUS Read status
 */
uint8_t ISD1700::rd_status(void)
{
  sendCmd(0x05);
  return _SR1;
}

/*
 * RD_PLAY_PTR Read playback pointer
 */
uint16_t ISD1700::rd_play_ptr(void)
{
  sendCmd(0x06);
  return _PP;
}

/*
 * PD Power down
 */
void ISD1700::pd(void)
{
  sendCmd(0x07);
}

/*
 * RD_REC_PTR Read record pointer
 */
uint16_t ISD1700::rd_rec_ptr(void)
{
  sendCmd(0x08);
  return _RP;
}

/*
 * DEVID Read device ID register
 */
uint8_t ISD1700::devid(void)
{
  sendCmd(0x09);
  return _DEVICEID;
}

/*
 * PLAY Play from current location
 */
void ISD1700::play(void)
{
  sendCmd(0x40);
}

/*
 * REC Record from current location
 */
void ISD1700::rec(void)
{
  sendCmd(0x41);
}

/*
 * ERASE Erase current message
 */
void ISD1700::erase(void)
{
  sendCmd(0x42);
}

/*
 * G_ERASE Erase all messages except Sound Effects
 */
void ISD1700::g_erase(void)
{
  sendCmd(0x43);
}

/*
 * RD_APC Read Analog Path Configuration register
 */
uint16_t ISD1700::rd_apc(void)
{
  sendCmd(0x44);
  return _APC;
}

/*
 * WR_APC1 Write data <11:0> into the Analog Path Configuration register
 *         with volume settings from /VOL pin
 */
void ISD1700::wr_apc1(uint16_t apc)
{
  sendCmd(0x45, apc);
}

/*
 * WR_NVCFG Write the contents of APC register into NVCFG register
 */
void ISD1700::wr_nvcfg(void)
{
  sendCmd(0x46);
}

/*
 * LD_NVCFG Load contents of NVCFG into the APC register
 */
void ISD1700::ld_nvcfg(void)
{
  sendCmd(0x47);
}

/*
 * FWD Forward playback pointer to the start address of the next message
 */
void ISD1700::fwd(void)
{
  sendCmd(0x48);
}

/*
 * CHK_MEM Check circular memory
 */
void ISD1700::chk_mem(void)
{
  sendCmd(0x49);
}

/*
 * EXTCLK Enable/disable external clock mode
 */
void ISD1700::extclk(void)
{
  sendCmd(0x4A);
}

/*
 * WR_APC2 Write data <11:0> into the Analog Path Configuration register
 *         with volume settings from bits <2:0>
 */
void ISD1700::wr_apc2(uint16_t apc)
{
  sendCmd(0x65, apc);
}


/*
 * SET_PLAY Play from start address to end address, or stop at EOM
 *          depending on D11 of APC
 */
void ISD1700::set_play(uint16_t startAddr, uint16_t endAddr)
{
  sendCmd(0x80, startAddr, endAddr);
}

/*
 * SET_REC Record from start address to end address
 */
void ISD1700::set_rec(uint16_t startAddr, uint16_t endAddr)
{
  sendCmd(0x81, startAddr, endAddr);
}

/*
 * SET_ERASE Erase from start address to end address
 */
void ISD1700::set_erase(uint16_t startAddr, uint16_t endAddr)
{
  sendCmd(0x82, startAddr, endAddr);
}

(contd.)

Example sketch

#include <ISD1700.h>

ISD1700 chip(10); // Initialize chipcorder with
                  // SS at Arduino's digital pin 10

void setup()
{
  Serial.begin(9600);
  Serial.println("Sketch is starting up");
}

void loop()
{
  char c;
  
  if(Serial.available())
  {
    /* Power Up */
    chip.pu();
    c = Serial.read();
    switch(c)
    {
       case 'A':
         Serial.println(chip.rd_apc(), BIN);
         break;         
       case 'Y':
         chip.play();
         break;
       case 'P':
         chip.stop();
         break;
       case 'E':
         chip.erase();
         break;
       case 'R':
         chip.rec();
         break;
       case 'F':
         chip.fwd();
         break;
       case 'Z':
         chip.g_erase();
         break;
       case 'I':
         Serial.println(chip.devid(), BIN);
         break;
    }
    Serial.print("Status---> ");
    Serial.print(chip.CMD_ERR()? "CMD_ERR ": " ");
    Serial.print(chip.PU()? "PU ": " ");
    Serial.print(chip.RDY()? "RDY ": "Not_RDY");
    Serial.println();
    delay(1000);
  }
}

Here is further information about ISD1700 chipcorders: http://www.nuvoton-usa.com/hq/enu/ProductAndSales/ProductLines/ConsumerElectronicsIC/ISDVoiceIC/ISDChipCorder/ISD1700.htm

Refer to the Design Guide for detailed information about the SPI mode.

Connect chipcorder's SPI pins to these Arduino pins:

  • SCK --> digital pin 13
  • MISO --> digital pin 12
  • MOSI --> digital pin 11
  • SS --> digital pin specified during class instantiating in your Arduino sketch (ie. ISD1700 chip(10); --> SS is attached to Arduino digital pin 10)

Dear,

Does the library for Nuvoton ISD1700 works? The program code that you posted, do you have a flow chart? I'm so new to this chip, hope to get more hints to understand the code.

Thank you.

from,

cincailo

Hello cincailo,

The example sketch above worked well!

The chipcorder and Arduino transfer data between each other through SPI.

You have to attach Arduino digital pins 13, 12, and 11 to the chipcorder pins SCK, MISO, and MOSI, respectively.

In addition, Arduino's and chipcorder's ground should be connected to a common point.

The chipcorder's SS pin is defined inside your sketch when instantiating the library, i.e.

#include <ISD1700.h>       // include library into the sketch

ISD1700 chip(10);    // Arduino's digital pin 10 is attached to chipcorder's SS pin

/* ... your own code follows below... */

This was made on purpose because you can control more than one device through SPI. For example, you could have two chipcorders being controlled by Arduino,

#include <ISD1700.h>

ISD1700 chipOne(10); // chip 1 has SS pin attached to Arduino digital pin 10
ISD1700 chipTwo(9);  // chip 2 has SS pin attached to Arduino digital pin 9

/* ... code goes on... */

There are many opcodes you can send to the chipcorder in order to let it perform some task. For example, to play the current pointed recording, you can send a PLAY opcode. Using the library above, you do this simply by including the following code into your sketch:

/* ... */

    chip.play();

/* ... */

Refer to the chipcorder's Design Guide (http://adsorcao.locaweb.com.br/ISD1700/Design_Guide.pdf) for an overview and to find a feature list.

Hey, Thanks for your explanation. But how if i'm not using Arduino instead I use PIC from Microchip with C programming? Is there any problem? Which part should I edit? Your this library for ISD1700 family is function as to record multiple sound and randomly playback it when it is selected?

Thanks again.

From,

cincailo

Hi there cincailo,

I'm afraid I cannot be much of help porting the library code to the Microchip PIC platform, since there is a lot of Arduino language embedded into it.

However, I can help you a bit with the differences between platforms.

According to some references I've found hanging low on the internet, PIC's SSPCONx, SSPSTAT and SSPBUF registers correspond to ATmega's SPCR, SPSR and SPDR registers, respectively, which hold all bits that deal with the SPI communication.

This is what your SPI initialization code would look like:

/**
 * void spi_init(void)
 * 
 * Initializes SPI communication in a Microchip PICmicro
 * This is a Hi-tech PICC compiler compliant code
 *
 * @author Marcelo Shiniti Uchimura
 */
void spi_init(void)
{
    SSPCON = 0x32;  /* set up SSP to clock data out on falling edge
                       whilst clock data in on rising edge, clock idle high */
    TRISC = 0x10;   /* RC4 and SDI (or MISO) are the same pin in Microchip PIC16F87x uC family */

    PORTC |= 0x04;  /* de-selecting chipcorder (RC2 is attached to chipcorder's SS pin) */
}

/**
 * char spi_transfer(char)
 * 
 * Send/get data to/from an SPI peripheral device
 * This is a Hi-tech PICC compiler compliant code
 *
 * @author Marcelo Shiniti Uchimura
 */
char spi_transfer(char data)
{
    PORTC &= ~(0x04);    /* selecting chipcorder */
    SSPBUF = data;
    while(!(SSPSTAT & 0x01))
    {
        /* wait for transfer complete */
    }
    return SSPBUF;
}

Hi,

Really thanks so much. Ya, I will look at it and try to figure it out the way to do it. Perhaps I can send a copy to you once I settle it. Hopefully...

Thanks again.

From,

cincailo

great library, thank you for contributing this.

however, having some problems - with your example code, i'm getting Not_RDY for every command. ID returns 0. my wiring is:

/*
SCLK_PIN  13         to ISD1700 pin 6
MISO_PIN  12         to ISD1700 pin 4
MOSI_PIN  11         to ISD1700 pin 5
*/
ISD1700 chip(10); // to ISD1700 pin 7 - Initialize chipcorder with SS at Arduino's digital pin 10

i thought for a second i had switched MISO/MOSI, and when i do

/*
SCLK_PIN  13         to ISD1700 pin 6
MISO_PIN  12         to ISD1700 pin 5 [MOSI]
MOSI_PIN  11         to ISD1700 pin 4 [MISO]
*/

...i get CMD_ERR PU RDY for every command, and ID returns 11111111. confused about that.

the ISD1700 plays/records in pushbutton mode, so i know it works.

any ideas on what might be the problem?

by the way, the only reason i'm using the SPI library is because Winbond/Nuvoton made the aggravating decision to route ANA-IN by default so you can't record from it. or, i can, but it sounds terrible. (i need to record from an 1/8" computer sound-out.) am i correct that i need to use an SPI command to change routing?

from the datasheet it looks i need to change APC register to 0100 0000 1000, or 0x408 (record ANA-IN, with monitoring). so in this library - once i get it working - is the following correct?

// change routing to record ANA-IN, with monitoring:
chip.wr_apc1(0x408); // but don't change volume
// or...
chip.wr_apc2(0x408); // set volume to max (000)

thanks in advance,

e

jesus jiminy! it turned out to be a bug in NG board with LED on pin 13 interfering with hardware SPI. i did the fix pictured here http://www.eng.uah.edu/~jdw/avr/arduino_r13_mod.jpg and it works!

also did chip.wr_apc1(0x408); // B10000001000 and that seemed to work.

however, i still have some questions.

when i play a sound and then send stop() in the middle, the sound stops but i get CMD_ERR PU Not_RDY. why is that?

fwd() only works if sound is stopped first, otherwise you get CMD_ERR PU Not_RDY. it works in pushbutton mode - is that an SPI thing, or is something wrong?

in pushbutton mode, you need FT active (low) to record ANA-IN. the audio is clear and loud until you start recording, but then the audio monitor is turned off despite the change to apc. unless i am not understanding something correctly, i thought D4 monitor bit set to 1 enables audible preview while recording.

the recorded audio is much lower volume than the feed-through monitor. it's also much noisier. the noise is a big issue, i've added decoupling caps everywhere but still trying to figure out how to follow recommendations for separating all the power and ground on a breadboard.

the speaker noise when FT is left active (low) but nothing connected to ANA-IN is very, very noisy. i was hoping to install this somewhere and be able to record new sounds on site by plugging into a jack and hitting record button. but it looks like i will need to keep FT off, so will have to add another external switch? or is something wrong with my circuit if i hear this noise?

thanks again to anyone still with me after all of those questions.

Hello eforman, sorry for the long delay.

You're right, I'm currently working on the stop() and fwd() issues. I don't know yet why the status flags get changed to CMD_ERR and Not_RDY in the middle of the operation.

As for the noise issues, I guess that's because you're assembling the circuit on a breadboard, right? I didn't have any problems, but anyway I didn't test it using the feedthrough feature of the chip.

okay keep us posted on your progress. while you're fiddling, see if you can set bit to hear audio while recording.

Hello Neuron!
Man, that is what I need! SPI mode with arduino and ISD1700.
Did you updated the source or I can use the first one to try it?

I'll work on that project tonight.
Tks,

Gutosan