Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Code for ATmega88 to receive I2S data (one channel) at 24bit/48kHz

46 views
Skip to first unread message

wzab

unread,
Jan 12, 2011, 5:49:25 AM1/12/11
to
Archive-name: avr-i2s

Below are the sources of the code allowing you to receive and process
the data from the I2S ADC (like CS5343) or left-justified I2S-like
ADC (like CS5344). The code was tested on ATmega88 with 18.432MHz clock
The clock to ADC was provided by the pin (PCINT0/CLKO/ICP1) PB0
(of course the clock output was enabled in the fuse bits!).
I was able to succesfully recive 24-bit data at 48kHz sampling
rate, however I needed to implement the SPI routine in assembly
and optimize it (please note multiple ISR epilogs to avoid using
jumps).
Please note, that this system uses SPI in slave mode with SCK and MISO
driven externally. If you reset the running system, theoretically
it can enter the SPI programming mode.
It seems, that once I have experienced such a problem when running
this code (slightly modified of course) on ATmega32U2.
The code is published as PUBLIC DOMAIN, however if you use it, please
mention it in documentation and/or comments in your code.
Of course I don't provide any warranty! You use it on your own risk.

Wojciech M. Zabolotny
wz...@ise.pw.edu.pl

#!/bin/sh
# This is a shell archive (produced by GNU sharutils 4.9).
# To extract the files from this archive, save it to some FILE, remove
# everything before the `#!/bin/sh' line above, then type `sh FILE'.
#
lock_dir=_sh08549
# Made on 2011-01-12 11:37 CET by <wzab@wzab>.
# Source directory was `/tmp/I2S_SPI'.
#
# Existing files will *not* be overwritten, unless `-c' is specified.
#
# This shar contains:
# length mode name
# ------ ---------- ------------------------------------------
# 237 -rw-r--r-- abuf.h
# 2603 -rw-r--r-- adc.c
# 485 -rw-r--r-- spi_atmega88.h
# 3819 -rw-r--r-- SPI_isr.S
#
MD5SUM=${MD5SUM-md5sum}
f=`${MD5SUM} --version | egrep '^md5sum .*(core|text)utils'`
test -n "${f}" && md5check=true || md5check=false
${md5check} || \
echo 'Note: not verifying md5sums. Consider installing GNU coreutils.'
save_IFS="${IFS}"
IFS="${IFS}:"
gettext_dir=FAILED
locale_dir=FAILED
first_param="$1"
for dir in $PATH
do
if test "$gettext_dir" = FAILED && test -f $dir/gettext \
&& ($dir/gettext --version >/dev/null 2>&1)
then
case `$dir/gettext --version 2>&1 | sed 1q` in
*GNU*) gettext_dir=$dir ;;
esac
fi
if test "$locale_dir" = FAILED && test -f $dir/shar \
&& ($dir/shar --print-text-domain-dir >/dev/null 2>&1)
then
locale_dir=`$dir/shar --print-text-domain-dir`
fi
done
IFS="$save_IFS"
if test "$locale_dir" = FAILED || test "$gettext_dir" = FAILED
then
echo=echo
else
TEXTDOMAINDIR=$locale_dir
export TEXTDOMAINDIR
TEXTDOMAIN=sharutils
export TEXTDOMAIN
echo="$gettext_dir/gettext -s"
fi
if (echo "testing\c"; echo 1,2,3) | grep c >/dev/null
then if (echo -n test; echo 1,2,3) | grep n >/dev/null
then shar_n= shar_c='
'
else shar_n=-n shar_c= ; fi
else shar_n= shar_c='\c' ; fi
f=shar-touch.$$
st1=200112312359.59
st2=123123592001.59
st2tr=123123592001.5 # old SysV 14-char limit
st3=1231235901

if touch -am -t ${st1} ${f} >/dev/null 2>&1 && \
test ! -f ${st1} && test -f ${f}; then
shar_touch='touch -am -t $1$2$3$4$5$6.$7 "$8"'

elif touch -am ${st2} ${f} >/dev/null 2>&1 && \
test ! -f ${st2} && test ! -f ${st2tr} && test -f ${f}; then
shar_touch='touch -am $3$4$5$6$1$2.$7 "$8"'

elif touch -am ${st3} ${f} >/dev/null 2>&1 && \
test ! -f ${st3} && test -f ${f}; then
shar_touch='touch -am $3$4$5$6$2 "$8"'

else
shar_touch=:
echo
${echo} 'WARNING: not restoring timestamps. Consider getting and
installing GNU `touch'\'', distributed in GNU coreutils...'
echo
fi
rm -f ${st1} ${st2} ${st2tr} ${st3} ${f}
#
if test ! -d ${lock_dir} ; then :
else ${echo} "lock directory ${lock_dir} exists"
exit 1
fi
if mkdir ${lock_dir}
then ${echo} "x - created lock directory ${lock_dir}."
else ${echo} "x - failed to create lock directory ${lock_dir}."
exit 1
fi
# ============= abuf.h ==============
if test -f 'abuf.h' && test "$first_param" != -c; then
${echo} "x - SKIPPING abuf.h (file already exists)"
else
${echo} "x - extracting abuf.h (text)"
sed 's/^X//' << 'SHAR_EOF' > 'abuf.h' &&
/*
X The code below is published as PUBLIC DOMAIN as Wojciech M. Zabolotny
X ( wzab<at>ise.pw.edu.pl ) 2011.01.11
X No warranty of any kind is provided! Use at your own risk!
X */
X
#define L2_ABUF_LEN 7
#define ABUF_LEN (1<<L2_ABUF_LEN)
X
SHAR_EOF
(set 20 11 01 12 11 36 53 'abuf.h'
eval "${shar_touch}") && \
chmod 0644 'abuf.h'
if test $? -ne 0
then ${echo} "restore of abuf.h failed"
fi
if ${md5check}
then (
${MD5SUM} -c >/dev/null 2>&1 || ${echo} 'abuf.h': 'MD5 check failed'
) << \SHAR_EOF
c99be95b2f30d6db90c10f5a0a3ce291 abuf.h
SHAR_EOF
else
test `LC_ALL=C wc -c < 'abuf.h'` -ne 237 && \
${echo} "restoration warning: size of 'abuf.h' is not 237"
fi
fi
# ============= adc.c ==============
if test -f 'adc.c' && test "$first_param" != -c; then
${echo} "x - SKIPPING adc.c (file already exists)"
else
${echo} "x - extracting adc.c (text)"
sed 's/^X//' << 'SHAR_EOF' > 'adc.c' &&
/*
X The code below is published as PUBLIC DOMAIN as Wojciech M. Zabolotny
X ( wzab<at>ise.pw.edu.pl ) 2011.01.11
X No warranty of any kind is provided! Use at your own risk!
X */
#include "abuf.h"
#include "spi_atmega88.h"
#include "byte_select.h"
X
volatile uint32_t abuf[ABUF_LEN];
volatile uint8_t abuf_wptr = 0;
volatile uint8_t abuf_rptr = 0;
volatile uint8_t abuf_overrun = 0;
X
static inline uint8_t abuf_is_empty(void)
{
X if (abuf_rptr == abuf_wptr) return 0xff;
X return 0;
}
X
//The routine below is not used.
//Writing to the buffer is done in the interrupt routine!
static inline void abuf_put(uint32_t t)
{
X uint8_t new_wptr=(abuf_wptr+1) & (ABUF_LEN-1);
X if (new_wptr==abuf_rptr) abuf_overrun=1;
X else {
X abuf[abuf_wptr] = t;
X abuf_wptr = new_wptr;
X }
}
X
static inline uint32_t abuf_get(void)
{
X uint32_t res = abuf[abuf_rptr];
X cli();
X abuf_rptr = (abuf_rptr+1) & ((1<<L2_ABUF_LEN)-1);
X sei();
X return res; // for CS5344 (or another ADC with left-justified I2S-like format)
X //return (res + res); // for CS5343 (or another ADC with I2S format
}
X
/* Initialization of the SPI for Cirrus CS5343/4 */
void SPI_Slave_Init(void)
{
X /* Set all SPI related pins as inputs */
X DDR_SPI_SCK &= ~(1<<PIN_NR_SPI_SCK);
X DDR_SPI_SS &= ~(1<<PIN_NR_SPI_SS);
X DDR_SPI_MOSI &= ~(1<<PIN_NR_SPI_MOSI);
X /* Enable SPI */
X SPCR = (1<<SPE);
X /* Enable SPI interrupts */
X SPCR |= (1<<SPIE);
}
X
//Debug function - assuming, that we have a LED
//or beeper connected to the PIN PD7
void show_error(uint8_t msg)
{
X uint8_t mask;
X DDRD |= 0x80;
X while(1) {
X mask=0x80;
X while(mask) {
X if(mask & msg)
X PORTD |= 0x80;
X else
X PORTD &= ~(0x80);
X _delay_ms(200);
X mask >>= 1;
X }
X PORTD &= ~(0x80);
X _delay_ms(400);
X }
}
X
/* Main routine */
int main(void)
{
X //Initialize the hardware
X uint8_t b1,b2;
X MCUSR &= ~(1 << WDRF);
X wdt_disable();
X clock_prescale_set(clock_div_1);
X abuf_rptr = 0;
X abuf_wptr = 0;
X abuf_overrun = 0;
X SPI_Slave_Init();
X sei();
X //now in the loop process the data received from the CS5343/CS5344
X while(1) {
X uint8_t i;
X if(abuf_overrun) {
X //If processing was too slow, stop it and generate error signal
X cli();
X show_error(0xcb);
X }
X //Now process data in blocks (let's assume, that we are processing
X // or sending data in 32-sample long blocks)
X for(i=0;i<32;i++) {
X uint32_t smp;
X while(abuf_is_empty()) {};
X smp = abuf_get();
X /* here should be the processing of every single sample */
X }
X /* here should be the processing of the whole block */
X }
}
SHAR_EOF
(set 20 11 01 12 11 36 57 'adc.c'
eval "${shar_touch}") && \
chmod 0644 'adc.c'
if test $? -ne 0
then ${echo} "restore of adc.c failed"
fi
if ${md5check}
then (
${MD5SUM} -c >/dev/null 2>&1 || ${echo} 'adc.c': 'MD5 check failed'
) << \SHAR_EOF
c4f393726d1fe2a0d466974f3502b6d9 adc.c
SHAR_EOF
else
test `LC_ALL=C wc -c < 'adc.c'` -ne 2603 && \
${echo} "restoration warning: size of 'adc.c' is not 2603"
fi
fi
# ============= spi_atmega88.h ==============
if test -f 'spi_atmega88.h' && test "$first_param" != -c; then
${echo} "x - SKIPPING spi_atmega88.h (file already exists)"
else
${echo} "x - extracting spi_atmega88.h (text)"
sed 's/^X//' << 'SHAR_EOF' > 'spi_atmega88.h' &&
/*
X The code below is published as PUBLIC DOMAIN as Wojciech M. Zabolotny
X ( wzab<at>ise.pw.edu.pl ) 2011.01.11
X No warranty of any kind is provided! Use at your own risk!
X */
X
#ifndef _SPI_ATMEGA88_H_
#define _SPI_ATMEGA88_H_
X #define DDR_SPI_SCK DDRB
X #define PINS_SPI_SCK PINB
X #define PIN_NR_SPI_SCK 5
X
X #define DDR_SPI_SS DDRB
X #define PINS_SPI_SS PINB
X #define PIN_NR_SPI_SS 2
X
X #define DDR_SPI_MOSI DDRB
X #define PINS_SPI_MOSI PINB
X #define PIN_NR_SPI_MOSI 3
#endif
SHAR_EOF
(set 20 11 01 12 11 36 55 'spi_atmega88.h'
eval "${shar_touch}") && \
chmod 0644 'spi_atmega88.h'
if test $? -ne 0
then ${echo} "restore of spi_atmega88.h failed"
fi
if ${md5check}
then (
${MD5SUM} -c >/dev/null 2>&1 || ${echo} 'spi_atmega88.h': 'MD5 check failed'
) << \SHAR_EOF
7afe1a1cf9eb9c88e5d1bd93718d8b5e spi_atmega88.h
SHAR_EOF
else
test `LC_ALL=C wc -c < 'spi_atmega88.h'` -ne 485 && \
${echo} "restoration warning: size of 'spi_atmega88.h' is not 485"
fi
fi
# ============= SPI_isr.S ==============
if test -f 'SPI_isr.S' && test "$first_param" != -c; then
${echo} "x - SKIPPING SPI_isr.S (file already exists)"
else
${echo} "x - extracting SPI_isr.S (text)"
sed 's/^X//' << 'SHAR_EOF' > 'SPI_isr.S' &&
;; The code below is written and published as PUBLIC DOMAIN
;; by Wojciech M. Zabolotny ( wzab<at>ise.pw.edu.pl ) 2011.01.11
;; No warranty of any kind is provided! Use at your own risk!
X
#include <avr/io.h> ;required for register definitions
#include "abuf.h"
#include "spi_atmega88.h"
X
X .data
; .section .noinit
smp_b: .byte 0,0,0,0
smp_mask: .byte 0
X
X .extern abuf_rptr ; buffer read pointer (uint8_t)
X .extern abuf_wptr ; buffer write pointer (uint8_t)
X .extern abuf ; buffer (vector of uint32_t with length ABUF_LEN)
X .extern abuf_overrun ; buffer overrun flag (uint8_t)
X .text
X .global SPI_STC_vect
SPI_STC_vect:
X push r0
X in r0, 0x3f
X push r0
X ;; end of prolog
X in r0,_SFR_IO_ADDR(SPSR) ; read status to clear interrupt
X sbic _SFR_IO_ADDR(PINS_SPI_SS),PIN_NR_SPI_SS ; Test the SS input
X rjmp end_of_frame ; If SS is high, this is end of data in our channel
X lds r0,smp_mask
X sbrc r0,0
X rjmp byte_1st
X sbrc r0,1
X rjmp byte_2nd
X sbrc r0,2
X rjmp byte_3rd
X sbrc r0,3
X rjmp byte_4th
X ;; It should not happen, but we should be prepared for nonstandard MCLK/SCLK ratio
X in r0,_SFR_IO_ADDR(SPDR)
X ;; epilog - repeated multiple times to save clocks on jumps
X pop r0
X out 0x3f, r0
X pop r0
X reti
byte_1st:
X ;; update the byte mask
X lsl r0
X sts smp_mask,r0
X ;; store the data
X in r0,_SFR_IO_ADDR(SPDR)
X sts smp_b,r0
X ;; epilog - repeated multiple times to save clocks on jumps
X pop r0
X out 0x3f, r0
X pop r0
X reti
byte_2nd:
X ;; update the byte mask
X lsl r0
X sts smp_mask,r0
X ;; store the data
X in r0,_SFR_IO_ADDR(SPDR)
X sts smp_b+1,r0
X ;; epilog - repeated multiple times to save clocks on jumps
X pop r0
X out 0x3f, r0
X pop r0
X reti
byte_3rd:
X ;; update the byte mask
X lsl r0
X sts smp_mask,r0
X ;; store the data
X in r0,_SFR_IO_ADDR(SPDR)
X sts smp_b+2,r0
X ;; epilog - repeated multiple times to save clocks on jumps
X pop r0
X out 0x3f, r0
X pop r0
X reti
byte_4th:
X ;; update the byte mask
X lsl r0
X sts smp_mask,r0
X ;; store the data
X in r0,_SFR_IO_ADDR(SPDR)
X sts smp_b+3,r0
X ;; epilog - repeated multiple times to save clocks on jumps
X pop r0
X out 0x3f, r0
X pop r0
X reti
end_of_frame:
X ;; Now, at the end of frame we can perform more
X ;; sophisticated processing
X push r30
X push r31
X in r30,_SFR_IO_ADDR(SPDR)
X ;; first, verify that it is not the 4th byte
X lds r0,smp_mask
X sbrs r0,4
X rjmp not_4th_byte
X ;; update the byte mask
X lsl r0
X sts smp_mask,r0
X ;; store the data
X sts smp_b+3,r30
not_4th_byte:
X ;; set mask for next words
X ldi r30,1
X sts smp_mask,r30
X ;; Now copy all bytes to the buffer
X ;; Check if there is a free space
X lds r30,abuf_wptr
X lds r0,abuf_rptr
X mov r31,r30
X inc r31
X andi r31, (ABUF_LEN-1)
X cp r0,r31
X breq no_space
X ;; There is free space in the buffer - copy the data
X sts abuf_wptr,r31 ; Store new abuf_wptr
X ldi r31,0
X ;; Multiply Z by 4
X add r30,r30
X adc r31,r31
X add r30,r30
X adc r31,r31
X ;; Find the address in abuf
X subi r30,lo8(-(abuf))
X sbci r31,hi8(-(abuf))
X lds r0,smp_b+3
X st Z+,r0
X lds r0,smp_b+2
X st Z+,r0
X lds r0,smp_b+1
X st Z+,r0
X lds r0,smp_b
X st Z+,r0
exit_isr_2:
X ;; exit isr when r31,r30 and r0 were saved
X ;; extended epilog
X pop r31
X pop r30
X pop r0
X out 0x3f, r0
X pop r0
X reti
no_space:
X ldi r30,0xff
X sts abuf_overrun,r30
X ;; this is anyway emergency situation and sampling was disturbed
X ;; so we can loose some cycles for rjmp ;-)
X ;; I do not provide another copy of epilog, using rjmp instead...
X rjmp exit_isr_2
SHAR_EOF
(set 20 11 01 12 11 37 04 'SPI_isr.S'
eval "${shar_touch}") && \
chmod 0644 'SPI_isr.S'
if test $? -ne 0
then ${echo} "restore of SPI_isr.S failed"
fi
if ${md5check}
then (
${MD5SUM} -c >/dev/null 2>&1 || ${echo} 'SPI_isr.S': 'MD5 check failed'
) << \SHAR_EOF
269b9f72ce87347025454afc40cfdd0a SPI_isr.S
SHAR_EOF
else
test `LC_ALL=C wc -c < 'SPI_isr.S'` -ne 3819 && \
${echo} "restoration warning: size of 'SPI_isr.S' is not 3819"
fi
fi
if rm -fr ${lock_dir}
then ${echo} "x - removed lock directory ${lock_dir}."
else ${echo} "x - failed to remove lock directory ${lock_dir}."
exit 1
fi
exit 0

wzab

unread,
Jan 12, 2011, 6:35:45 AM1/12/11
to
Just a few comments to my code.
Of course the I2S data are signed. I treat them as unsigned
to have cleaner code to correct one-bit shift when
receiving I2S data via SPI (not needed when left-justified
data are received).
Maybe the abuf_get function should be defined as follows:

static inline int32_t abuf_get(void)
{


uint32_t res = abuf[abuf_rptr];

cli();


abuf_rptr = (abuf_rptr+1) & ((1<<L2_ABUF_LEN)-1);

sei();
//return (int32_t) res; // for CS5344 (or another ADC with left-
justified I2S-like format)
return (int32_t) (res + res); // for CS5343 (or another ADC with I2S
format
}

--
HTH & Regards,
WZab

0 new messages