|
Dear AT91SAM7 friends,
I integrated a voice codec using the SSC on the AT91SAM7S256. I ran into a lot of problems, so I am writing this post to make it easier for the next guy to figure this out. The Atmel software package has no example code for SSC, and I have seen many posts saying "did anybody actually ever get the SSC to work in passive mode?" without getting responses, so that was discouraging.
When I first read the SSC section of the datasheet, I was very excited about the flexibility of the peripheral and all the new possibilities, since I have only worked with smaller MCUs before. Little did I know that the described features do not work in all combinations, which not only limits that flexibility, but makes development extremely frustrating since the limitations are not documented. There is an errata section in the datasheet that lists certain conditions that fail or work incorrectly, but it is in no way complete.
OK, so my codec is providing me a bit clock (BCLK) frame sync (FSYNC) and data (SOUT/SIN). By default it is spitting out data on the SOUT, which is great to get started. It expects me to send data using the same clock and FSYNC.
First I needed to program the PIO so it uses the pins for SSC. Since the atmel provided header file doesn't have definitions for the SSC pins, I created them manually and initialized them.
Atmel's peripheral library has convenience functions to set up and operate the ssc. Add the #include <ssc/ssc.h> into your main file, and you can just use SSC_Configure to enable, reset, and program the clock.
Then I programmed the receiver registers, and voila, I got data. I then changed it to use DMA, no issues there. Note that in for DMA transfers, you use the "number of transfers" as a length indication, not number of bytes. In my case because I use 16 bit samples, I had to divide my buffer size by 2.
Sending was a censored. At first no matter what I did I couldn't get a signal on the TD pin to transmit. I even tried in "master mode", e.g. creating a DIV clock signal on TK, disconnecting the codec, not using the receiver. I never got data on TD. The telling sign was that I had something like 2V sitting on the pin, no matter how I programmed it through the PIO. Double checking the AT91SAM7S-EK development board's schematics I found that the board supports the D/A peripherals which one of them happens to be located on the same pin as the TD of the SSC. There are jumpers you can and must open, specifically JP24/25 to disconnect EXT_AD0 and EXT_AD1. After doing this, the data magically appeared on TD and I thought that I am golden. This is when the trouble started. Since the codec required me to use its clock and send my data during its frame sync, naturally I decided to use RK/RF/RD on the input, and then program the transmitter to use TD, use the RK clock, and start sending when receive starts. Negative, I either get nothing or my data is all over the place or at least 3 bits delayed. After trying a thousand other things and reading everybody elses frustrated comments, I decide to go the reverse way: receiving the clock and fsync on the transmit side's pins TK/TF. To do this I had to program those pin on the PIO as inputs. My data now looks all screwed up. A look at the TK pin shows me a triangular wave where my clock should be, wtf?! Disconnecting the pin reveals that TK pin is riding high even though I had programmed it as an input. Checking the AT91SAM7S-EK schematics again, I learn that this pin is used as a pullup for USB USB_PUP. Opening jumper JP-1 solves this, and my clock now looks normal, and I get my data. It is still one clock cycle delayed, but setting the transmitter configuration correctly fixes this (see code below).
One thing you should know when you use the configuration macros: They don't differenciate between the receiver and transmitter, there is only one set of macros for both, so the terminology is confusing and in some cases flat out wrong as I will show you next.
In my code example I configure the transmitter to be AT91C_SSC_START_FALL_RF, but it really means falling TF. There is no specific macro for TF signals, since the values are the same. No big deal. Be careful however with the macros AT91C_SSC_CKS_RK and AT91C_SSC_CKS_TK. THEY MUST BE REVERSED FOR THE TRANSMITTER. Check the numeric values: for the receiver its 0x01 to use TK and 0x02 to use RK. Its reversed for the transmitter, therefore the use of the macro must be reveresed as well.
- I hope this post will save some poor soul some time and trouble one day
#define PIN_SSC_TF {1 << 15, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_INPUT, PIO_DEFAULT} #define PIN_SSC_TK {1 << 16, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_INPUT, PIO_DEFAULT} #define PIN_SSC_TD {1 << 17, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_PERIPH_A, PIO_DEFAULT} #define PIN_SSC_RD {1 << 18, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_PERIPH_A, PIO_DEFAULT} #define PIN_SSC_RK {1 << 19, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_PERIPH_A, PIO_DEFAULT} #define PIN_SSC_RF {1 << 20, AT91C_BASE_PIOA, AT91C_ID_PIOA, PIO_PERIPH_A, PIO_DEFAULT} static const Pin sscPins[] = {PIN_SSC_TD, PIN_SSC_TF, PIN_SSC_TK, PIN_SSC_RD, PIN_SSC_RK, PIN_SSC_RF};
unsigned byte readBuffer1[1024]; unsigned byte readBuffer2[1024];
void initialize() { // configure pins PIO_Configure(sscPins, PIO_LISTSIZE(sscPins));
// this will enable and reset the ssc and NOT create a clock (because my goal is to use the codec's clock) SSC_Configure(AT91C_BASE_SSC, AT91C_ID_SSC, 0, 0);
// disable ssc interrupts AT91C_BASE_SSC->SSC_IDR = 0x0FFF;
// configure ssc receiver AT91C_BASE_SSC->SSC_RCMR = AT91C_SSC_CKS_TK | AT91C_SSC_START_TX; AT91C_BASE_SSC->SSC_RFMR = SSC_DATLEN(16) | AT91C_SSC_MSBF | SSC_DATNB(1) | AT91C_SSC_FSOS_NONE;
// configure ssc transmitter AT91C_BASE_SSC->SSC_TCMR = AT91C_SSC_CKS_RK | AT91C_SSC_START_FALL_RF | AT91C_SSC_CKI; AT91C_BASE_SSC->SSC_TFMR = SSC_DATLEN(16) | AT91C_SSC_MSBF | SSC_DATNB(1) | AT91C_SSC_FSOS_NONE;
// start reading using DMA the size must be supplied in "transfers", each transfer is 16bit SSC_ReadBuffer(AT91C_BASE_SSC, readBuffer, sizeof(readBuffer1) / 2); SSC_ReadBuffer(AT91C_BASE_SSC, readBuffer, sizeof(readBuffer2) / 2);
// configure interrupts AIC_ConfigureIT(AT91C_ID_SSC, 0, ISR_SSC); SSC_EnableInterrupts(AT91C_BASE_SSC, AT91C_SSC_ENDRX); AIC_EnableIT(AT91C_ID_SSC);
// enable ssc action SSC_EnableReceiver(AT91C_BASE_SSC); SSC_EnableTransmitter(AT91C_BASE_SSC); }
// only thing missing now is the interrupt handler:
void ISR_SSC(void) { volatile unsigned int status; status = ssc->SSC_SR; // [...] now the buffer is filled and you can do what you want with the data. To read a continuous stream // you should call ReadBuffer again, once each time this function is called, alternating the two buffers. }
///to send data, just call the SSC_WriteBuffer function at the same time you call the ReadBuffer functions. ///You dont need extra interrupts for that as long as you read/write buffer sizes are the same and ///everything is in sync anyway.
|