Audio Board with NuttX

In this second article, in the Audio Board series, we provide sample code to use the Audio Board with NuttX running on ZKit-ARM-1769. If you haven’t, you might want to read the Audio Board functionality described in the blog article Audio Board, Getting Started, before reading this article.

Hardware

Audio Blocks
  • Audio Board

  • ZKit-ARM-1769

  • Micro SD card

Connect the Audio Board to SPI Header of ZKit-ARM-1769, using an FRC cable.

Audio File

Since the Audio Board is a simple 12-bit DAC, the audio data should be in uncompressed format. Also since there is only one DAC, the board supports only mono-channel audio. For proper voice play back we require at-least 8KHz sampling rate. And to simplify the creation of the audio file we will use 8-bit sample size. The summary of the audio file parameters is listed below.

  • Uncompressed PCM

  • 8KHz Sampling Rate

  • Unsigned 8-bit Samples

  • Mono Channel

Audio file with the above mentiond parameters can be created from an MP3 file using SOund eXchange (SOX), as shown in the following command.

$ sox input.mp3 -r 8k -e unsigned-integer -b 8 -c 1 output.raw

Copy the generated audio file into an SD card and insert it in to the SD card slot. (FIXME: Filesystem?)

Software

Reading SD card data

To read the audio file from the SD card data we need to mount SD card. Following code snippet will do mount the SD card.

#define DEV_NAME "/dev/mmcsd0"
#define MOUNTPOINT "/sd"
#define FILE_PATH "/sd/myaudio.raw"

ret = mmcsd_init();
if (ret == 1)
        return 1;

ret = mount(DEV_NAME, MOUNTPOINT, "vfat", 0, NULL);

Once the SD Card is mounted the audio file can be read, using stdio library functions - fopen(), fgetc(), etc.

Writing data to DAC

The sampled audio data is send to DAC in a specified time interval. The sampling period is 125us for 8KHz audio. Hence we send out one sample every 125us. But we send data every 113us, since the code to play the audio requires 12us, to execute.

The time required to the play the audio, can be determined by finding out the time required to play 1000 samples. That time divided by 1000, will provide the time required to play 1 sample. This value has to be subtracted from the delay to be generated.

/*
 * Sample code to play an audio file using NuttX on ZKit-ARM-1769.
 */
#include <chip/lpc17_gpio.h>

#include <nuttx/config.h>
#include <nuttx/spi.h>
#include <nuttx/mmcsd.h>
#include <nuttx/time.h>

#include <unistd.h>
#include <sys/mount.h>
#include <sys/time.h>
#include <stdio.h>
#include <errno.h>

#define DEV_NAME "/dev/mmcsd0"
#define MOUNTPOINT "/sd"
#define FILE_PATH "/sd/myaudio.raw"

#define FREQUENCY 20000000
#define WORD_LENGTH 8
#define CS_ON_DELAY 16
#define CS_TO_LO_DELAY 16
#define UD_DELAY 16
#define AUDIO_DELAY 113
#define INCREASE 1
#define DECREASE 0
#define VOLUME 24
#define DAC_CS (GPIO_OUTPUT | GPIO_VALUE_ONE | GPIO_PORT0 | GPIO_PIN4)
#define VOL_CS (GPIO_OUTPUT | GPIO_VALUE_ONE | GPIO_PORT4 | GPIO_PIN28)
#define VOL_UD (GPIO_OUTPUT | GPIO_VALUE_ONE | GPIO_PORT4 | GPIO_PIN29)

/*
 * Initialize DAC and Volume control related GPIOs and SPI interface.
 */
void dac_vol_init(struct spi_dev_s *spi)
{
        SPI_SETFREQUENCY(spi, FREQUENCY);
        SPI_SETMODE(spi, SPIDEV_MODE0);
        SPI_SETBITS(spi, WORD_LENGTH);

        lpc17_configgpio(VOL_CS);
        lpc17_configgpio(VOL_UD);
}

/*
 * Write 1 audio sample to DAC.
 */
void dac_write(unsigned char data, struct spi_dev_s *spi)
{
        lpc17_gpiowrite(DAC_CS, false);
        SPI_SEND(spi, 0x10 | ((data>>4) & 0x0F));
        SPI_SEND(spi, 0x00 | ((data << 4) & 0xF0));
        lpc17_gpiowrite(DAC_CS, true);
}

/*
 * Initialize SD-Card SPI interface.
 */
int mmcsd_init()
{
        struct spi_dev_s *spi;
        int ret;

        zkit_spiinitialize();
        spi = lpc17_spiinitialize(0);
        if (!spi) {
                printf("nuttx-sdcard: failed to initialize SPI port 0\n");
                return 1;
        }

        ret = mmcsd_spislotinitialize(0, 0, spi);
        if (ret < 0) {
                printf("nuttx-sdcard: failed to bind SPI port 0 to MMC/SD slot 0: %d\n", ret);
                return 1;
        }
        return 0;
}


/*
 * Increase or decrease volume, '0' - decrement '1' - increment
 */
void vol_inc_dec(int steps, int dir)
{
        int i;
        int ud_state;
        if (dir)
                ud_state = true;
        else
                ud_state = false;

        lpc17_gpiowrite(VOL_CS, true);
        lpc17_gpiowrite(VOL_UD, ud_state);
        up_mdelay(CS_ON_DELAY);
        lpc17_gpiowrite(VOL_CS, false);
        up_mdelay(CS_TO_LO_DELAY);

        for(i = 0; i < steps; i++){
                ud_state = !ud_state;
                lpc17_gpiowrite(VOL_UD, ud_state);
                up_mdelay(UD_DELAY);

                ud_state = !ud_state;
                lpc17_gpiowrite(VOL_UD, ud_state);
                up_mdelay(UD_DELAY);
        }

        lpc17_gpiowrite(VOL_CS, true);
        up_mdelay(CS_ON_DELAY);
}


/*
 * Configure default volume steps - amount of volume (max is 64)
 */
void volume_setup(int steps)
{
        /*
         * Bring down the volume to 0, no matter what the current
         * volume is, and then set the required volume level.
         */
        vol_inc_dec(64, DECREASE);
        vol_inc_dec(steps, INCREASE);
}

int app_main(void)
{
        int ret;
        struct spi_dev_s *spi;
        FILE *fp;

        spi = lpc17_sspinitialize(1);
        if (!spi) {
                printf("nuttx-audio: failed to initialize SPI port 1\n");
                return 1;
        }

        lpc17_configgpio(DAC_CS);
        dac_vol_init(spi);
        volume_setup(VOLUME);

        ret = mmcsd_init();
        if (ret == 1)
                return 1;

        ret = mount(DEV_NAME, MOUNTPOINT, "vfat", 0, NULL);
        if (ret == -1) {
                perror("nuttx-audio: error mounting sd card");
                return 1;
        }

        while (1) {
                printf("Repeat\r\n");
                sleep(2);
                fp = fopen(FILE_PATH, "r");
                if (fp == NULL) {
                        printf("nuttx-audio: error opening %s", FILE_PATH);
                        return 1;
                }

                while (1) {
                        ret = fgetc(fp);
                        if (ret == EOF) {
                                if (ferror(fp))
                                        perror("nuttx-audio: error reading file");
                                break;
                        }

                        dac_write(ret, spi);
                        up_udelay(AUDIO_DELAY);
                }
                fclose(fp);
        }
}

Update: 10 Mar 2014

A bug in the volume control code was fixed. Support to both increment and decrement the volume has been added.

Credits