Monday, March 31, 2014

Homebrew USB Sound Card

This project began out of necessity as I was working on a Raspberry Pi project a that required low latency audio output. However I was having major issues with latency from the the Pi's sound card, observing at least 20-30ms of latency. This would not do, I needed real time responsiveness, less than 5ms. With the help of CNLohr I began to try and build my own low latency USB sound card.

Here is the result...

CNLohr designed the PCB and I programmed the firmware. The sound card uses a MAX5556 digital to analog converter (DAC), a Mega8u2, and an Attiny44. I made a few modifications to the PCB to make it processing audio data more efficient and to fix a design flaw.

An attiny44 is used to drive the MAX5556 DAC using the 3 wire I²S interface. The attiny is running at 24.5Mhz which, with some clever use of the microprocessor, can feed the DAC with 48khz 16bit stereo data. I use the tiny's hardware timer to toggle the PA7 pin every 256 clock ticks, which drives the DAC's left/right clock. An assembly loop is used to toggle the DAC's serial clock and sdata pin. This loop is synchronized with the left/right clock (PA7). An assembly interrupt is used to copy data out of the attiny's SPI registers into memory. In an effort to minimize the time the SPI interrupt takes, I had to minimize the the amount of work done. Within the assembly, I carefully selected registers so that I could avoid having to push or pop registers in the event of an SPI interrupt.

The AT Mega8u2 is used to communicate with the USB host and the AT tiny. The Mega8u32 is clocked at 16Mhz, a limitation imposed by USB. Audio is streamed into a circular buffer from the USB host. The circular buffer is emptied over the SPI interface to the AT Tiny microprocessor. Assembly is used to send the data over SPI to make use of the avr's store and increment assembly instruction, saving many clock cycles. The Mega8u32 and the AT tiny are kept in sync by using an interrupt on the PC7 pin. This pin is, like the DAC's left/right clock, connected to the tiny's PA7 pin. A rising edge on the PC7 pin signals that it is time to begins ending new data to the at tiny.

A quickly hacked together topside program sends raw stereo data to the sound card. The internal buffer of the sound card is about 64 samples per channel, about 1.3ms. USB double buffering provides an additional ~.6ms of buffer. The sound card works pretty well from a PC. Occasionally the sound card will experience an inaudible buffer underflow. A red LED will flicker on the sound card when this is detected. Using this sound card on the Raspberry Pi is a completely different story. The Pi isn't capable of servicing the USB fast enough to to keep up. Buffer underflow occurs constantly, completely useless.

The source code is available at https://github.com/axlecrusher/AvrProjects/tree/master/soundCard

Slightly outdated schematics can be found here. https://svn.cnlohr.net/pubsvn/electrical/avr_soundcard/ It is lacking the modifications visible in my photos.

Edit:Added note about double buffered USB.

Saturday, March 1, 2014

Mdadm External Write-intent Bitmap Hack (Debian update)

I recently upgraded to Debian 7.4 and found that I needed to redo my external write intent bitmap hack. My old methods no longer worked.

First I needed to disable mdadm assembly when running from the ramdisk. Edit /etc/default/mdadm and set the following:
INITRDSTART='none'

Rebuild the ramdisk image with:
update-initramfs -u

You still need to prevent mdadm from assembling the array listed in /etc/mdadm/mdadm.conf so that
DEVICE /dev/null

I created a new mdadm config file names /etc/mdadm/mdadm.delayed.conf which is a copy of /etc/mdadm/mdadm.conf but leaving
DEVICE partitions
as is. I also specify the bitmap file on the ARRAY definition line
ARRAY /dev/md/127 metadata=0.90 bitmap=/md127-raid5-bitmap UUID=....

Next I created a new script /etc/init.d/delayedRaid
#!/bin/sh # # Start all arrays specified in the delayed configuration file. # # Copyright © 2014 Joshua Allen # Distributable under the terms of the GNU GPL version 2. # ### BEGIN INIT INFO # Provides: delayedRaid # Required-Start: $local_fs mdadm-raid # Should-Start: # X-Start-Before: # Required-Stop: # Should-Stop: $local_fs mdadm-raid # X-Stop-After: # Default-Start: S # Default-Stop: 0 6 # Short-Description: Delayed MD array assembly # Description: This script assembles delayes assembly of MD raid # devices. Useful for raid devices that use external # write intent bitmaps. # Settings are in /etc/mdadm/mdadm.delayed.conf ### END INIT INFO . /lib/lsb/init-functions do_start() { log_action_begin_msg "Starting delayed raid" mdadm --assemble --scan --config=/etc/mdadm/mdadm.delayed.conf log_action_end_msg $? mount /mnt/raid5 } do_stop() { umount /mnt/raid5 mdadm --stop --scan --config=/etc/mdadm/mdadm.delayed.conf } case "$1" in start) do_start ;; restart|reload|force-reload) echo "Error: argument '$1' not supported" >&2 exit 3 ;; stop) # No-op do_stop ;; *) echo "Usage: delayedRaid [start|stop]" >&2 exit 3 ;; esac

And I added to the start-up procedures with
insserv -d delayedRaid

After reboot check to see if
Intent Bitmap : {some file name}
is present when running
mdadm --detail /your/raid/device

Hopefully I didn't miss anything.