As it turned out from my first (failed) attempt to access the 3210 over SPI, before being able to use SPI, I had to clock the 3210 over its PCM clock input, PCLK. To this, I had to produce a 256kHz PCLK signal, which means that I had to write firmware to pulse the PCLK pin of my PIC at a frequency of 512 times per second.
In my board’s configuration (full-speed USB-2 device) the PIC runs internally at 48MHz, but the instruction cycle time Tcy is a quarter of that, that is, the PIC runs at 12 million instruction cycles per second. Thus, my first thought was to divide 12 MHz by something to get 512 kHz. However, this division yields a non-integer result. In other words, I would have to pulse my PCLK every 23.4375 PIC instructions. Now, honestly, does this look like something feasible to you? To me it did not – at least not at first glance.
So I turned to another idea (which I abandoned soon after, but it’s worth discussing a bit): what if I used an external 512kHz frequency source? The simplest idea for this would be to take advantage of the PIC’s second crystal oscillator input. I plunged into the datasheets, but my hopes faded quickly: the maximum frequency accepted by the second oscillator of the PIC is 32.768 kHZ, so the 512kHz crystal would not do (not to mention that 512-kHz crystals are hard to get by). Even so, I took a last-minute decision to keep this alternative open, so I provided for the second crystal and its capacitors in my design.
For the next couple of days, I kept asking myself how to achieve the division by 23.4375. There had to be a way to do it. When, suddenly — here was it: what if I did not require a 50% duty cycle for PCLK? Then I could allow the logical one PCLK half-cycles to be one PIC instruction longer than the logical zero ones. This would yield 23.5 PIC instructions per half-cycle, something close enough to what I needed. But still, this was not good enough: this was not 256kHz, and if I used a PCLK which wasn’t accurate, my clock would slowly drift against another party’s rate of PCM encoding or decoding. This in turn might create noise, clicks, audible artifacts or other unwanted effects.
But, wait a second — what if I used a table of time invtervals? This would result in a waveform that would have a variable duty cycle, which would variate periodically to achieve exactly 256 kHz. This quickly led me into the following calculation: 9 half-cycles of 23 Tcy each, plus 7 half-cycles of 24 Tcy each yield a total of 16 half-cycles with a total duration of 207 + 168 = 375 Tcy. The average duration of each of these half-cycles is 375/16, which yields — guess what — the magic number 23.4375 Tcy!!
This is what a 16-half-cycle pulse train would look like:
And there’s more: although the PCLK runs at 256 kHz, actually the 3210 performs PCM I/O at a rate of only 64 kbps: the chip devotes to PCM I/O only eight PCLK cycles out of every thirty two, and a FSYNC pulse is used to mark the start of a 32-cycle train, while some internal registers tell the chip how many cycles after FSYNC to wait before doing the PCM I/O. Here is the relevant timing diagram from the datasheet of the 3210:
I would need to pulse the FSYNC signal every 32 full PCLK cycles, or 64 PCLK half-cycles. So, there was it: I would create something like an inner loop of 16 half-cycles, and an outer loop of four iterations of the inner loop. In each inner loop I would arrange the time intervals between 23 and 24 machine cycles as explained above, during the first inner iteration of the first outer iteration I would raise FSYNC and I would lower it right after that. My invention of a 16-half-cycle 23/24-Tcy ISR proved to be immensely convenient!
Here’s what the whole thing would look like:
One note here: maybe such tricks are common to all firmware clocking problems or to all similar projects. However, as I have already stated, my prior experience in such things was really limited, so the whole line of thought that led me to this solution was quite a revelation to me.
In order to test my assumptions, I decided to write some elementary firmware that would pusle PCLK and FSYNC and then re-try the SPI test. I decided to base this firmware on a Timer1 (TMR1) interrunt service routine (ISR). There is no real reason for choosing TMR1; in the future, I might change TMR1 into TMR0.
From the very first steps, after I studied the machine code produced by MCC18, I decided to write the ISR directly in PIC assembly. This would allow me to code accurately in time all the tasks I had to do in the ISR (see the next post – when posted). There was no easy way to do this using C.
It took me quite a while to get the ISR logic right. For one, I had to discover by trial-and-error that I was supposed to clear the TMR1 interrupt flag inside the ISR, or else the PIC would never again fire a TMR1 interrupt. By instructing the user LED to flicker inside the ISR (and observing that, instead of flickering, it stayed constantly lit), it was easy to spot this bug.
Then, I needed to set up the time intervals until the next TMR1 expiration. I did some experimentation there, checking to see what would happen if I asked the PIC to expire the timer immediately upon return from the ISR. Would this freeze the PIC? Nope, it would not, the PIC executes at least one instruction in “userland” before re-entering the ISR, so this felt safer: even if I screwed up times, I would not freeze entirely the PIC. At that point, I gave me some slack: I just wanted something rudimentary that would approach 256 kHz to test the SPI, I did not need full accuracy yet. Thus I wrote a small ISR that set TMR1 to fire in 23 instructions’ time, and then just returned to the “userland”.
After checking that the green LED kept flashing (somewhat less frequently, because its way of counting time is based on polling TMR0, and my ISR was eating some of its time), finally SPI’s turn came. I re-checked the reset sequence for the 3210: set up the TMR1 ISR, then lower \RESET, wait a little, raise \RESET and that should do it. Flashed this into the PIC, pressed the reset switch and — miraculous!! — my little .NET program immediately displayed the expected value for 3210’s direct register 0!!
Here are two screenshots for registers 0 and 11:
The value of Register 0 means “I am a 3210, Revision E” (3210 datasheet, p. 64), and that of Register 11 means “Pulse metering adjustment is 0 dB and audio hybrid adjustment is 0 dB” (same, p. 73).
I had just proved to myself that David Rowe (and Silabs’ FAQ) were right after all; the 3210 required PCLK in order to work… Even if I fooled it with something that wasn’t exactly 256kHz, it would not mind. How would it react however to my invention with the irregular PCLK duty cycle? And how would I write the actual ISR code for my PIC? Would there be enough time to do all the required tasks? All these are to be answered in my next post.