Some background: I'm designing a wireless transceiver, using a way-cool chip from Nordic Semiconductor. If I get this going, it'll be a much cheaper interface than any of the equivalent devices out there (I'm purposely being a bit coy about the exact application... who knows who's listening on the tubez).
Anyway, I was reading through the datasheet for the above device, and suddenly the doubt crept into my mind. If you've designed boards before, you know what I mean: am I really hooked up to this thing correctly?
I'm using an ATtiny85 for this project -- where I/O pins are at a premium -- so understanding the exact requirements of connectivity is essential.
The nRF24L01+ datasheet has a nice state diagram that suggests that the CE line can be safely left high during operation, but some vague statements elsewhere in the document allude to the need for a low-high edge transition on CE to enter TX or RX mode from standby.
So, with great trepidation I opened a support request to nordicsemi.com's experts and they replied that, yes, sadly, one MUST be able to toggle CE in order to properly transition to active TX or RX state from config or standby mode. Gyarrrrrrr. So what to do, with a measly ATTiny85 and only 5 I/O pins to allocate between CS/, SCK, MISO, MOSI, CE and a UART pin (that's 6 if you're counting along)?
Well, I thunk, and I thunk
And I thunk some more
Then I thunk until my thunker was sore.
I wanted to introduce as little extra hardware and logic as possible, and I'd already verified my split-rate software UART on the tiny 25/45/85 range -- and software UARTs are pretty tricky. I didn't want to do that again just for this project, on a different variety. AVRs vary quite a bit in their exact timer and IRQ configurations going between the tiny/classic/mega/etc. and while they're all capable, the devil is in the details.
In situations like this, one must look at the signals and control-lines one has at one's disposal, and consider if there are mutually-exclusive conditions amongst those that can be taken advantage of. At certain times of operation, some control lines may be quiescent -- for instance, my system only occasionally needs to communicate using SPI, and the slave device will ignore any SPI line activity while the CSN line is de-asserted. The CE line needs to be kept asserted during at least the start of a TX operation and during an entire RX operation.
My first idea was that the nRF24L01+ CSN (chip-select, negative logic) line and the CE (chip-enable, positive) could simply be the same line. The CSN line is only asserted during SPI read/write commands, and the CE is only asserted while one wants to transmit or receive. However, my board needs to be available, if possible, at all times while in receive mode, and that would mean keeping CE asserted at the same time as accessing the received Rx queue via SPI! As well, the nRF24L01+ requires that CE be asserted for some time prior to asserting CSN (2tdStby, around 150us). Darn.
Alternatives that seemed viable:
1. Multiplex SPI MISO/MOSI lines into a single bidir DI/DO using 2 resistors (3-wire to 4-wire SPI)
I considered making my SPI interface half-duplex, by using one of the MISO/MOSI pins of the AVR as GPIO and bit-banging SPI in both directions as two distinct phases; the first, as an output writing SPI command bits, then switching mid-transfer to an input to read the response. The other pin would be re-assigned to a dedicated CE control pin:
|For theory see Nordic Semi's app note for the older nRF2401|
I think this would actually work rather well: in regular command transactions the nRF24L01+ STATUS register is returned simultaneously with the SPI master's sending of the command byte, which would not be readable in a single-duplex SPI setup, but the there is a discrete command to read the STATUS register itself as a regular config register.
However, this setup would have the minor drawbacks of doing a separate SPI transaction just to get that status byte, and the major drawback of falling back to bit-banging rather than using the AVR's hardware SPI interface (slower SPI clock rate, more code). Finally, the hardware cost of two high-value resisters to interface the bit-banged half-duplex SPI data line to the nRF24L01+ full-duplex MISO/MOSI pair.
- one pin released to be dedicated CE signal;
- Loss of hardware SPI, reversion to bit-banging at a lower SPI rate
- Larger codesize due to bit-banged SPI
- Extra SPI transactions to retrieve nRF24L01+ STATUS register
- two extra resistors to convert between 3-wire and 4-wire SPI
OK, perhaps there is something else I could do...
2. Utilize SPI SCK line's IDLE state as CE Assert Signal Using Low-Pass Filter to Ignore SPI Activity
In this scheme, the MISO/MOSI lines are left as-is, full-duplex SPI communication is maintained, and the CE line is controlled by keeping the SCK line high while idle. What about the SCK's frantic activity during actual SPI transfers? Wouldn't that cause the CE line to toggle as well, messing things up? Well, the difference in time scales for the two signals saves my arse here. If one thinks of the SPI activity as a high-frequency, AC-type phenomenon (rapid hi-low transitions of short duration), and the CE signal as a low-frequency, DC-style signal (it's high 99.9% of the time, only requiring a high-low-high transition when changing from power-down to power-up and RX/TX modes, remaining high while RX/TX ops occur), the two signals are 'different' enough that they can be successfully multiplexed on a single line. With a suitably-designed RC lowpass filter, the SCK activity, in short bursts and at a high enough clock rate, will be mostly filtered out of the level seen by the CE pin, so it can be held high even during SPI communication.
- no pin need be dedicated to the CE signal;
- Bidirectional, hardware SPI capability preserved;
- no dedicated SPI transfer required to read nRF24L01+ STATUS register
- no codesize increase due to bit-banged SPI operations
- two extra components (440ohm resistor + .1uF cap) to implement passive LPF for CE line from SCK
Solution 1. above (single-duplex 3-wire SPI) requires two resistors, so this isn't any worse for board component count or cost; and we retain the faster, full-duplex hardware SPI capability. Win!
|Back-of-the-business-card visualization. Patent Pending(tm).|
CE, being a digital input to an IC going through one or more transistors, has very high resistance -- 10Kohm or more, and so does not significantly affect the drain time of the cap, my EEng co-workers promised me. I also ran a sim of the idealized circuit on the CircuitLab online circuit simulator. Highly recommended for checking out cockamamey ideas like this prior to breaking out the parts bin!
|Idealized SCK thru RC filter to generate CE control signal.|
|Simulating SCK sitting high and low for long periods. CircuitLab.com only has a simple function generator, so I just set the frequency down to the KHz range. Note rise/fall time of the CE signal is approx. 125us into logic 1/0 ranges.|
For thoroughness, I also simulated the resistor and capacitor being +-10% from spec to ensure the voltage sag during SPI activity didn't change much -- and it didn't, not more than .05V or so over the 32 SCK cycles, so component tolerances shouldn't be a problem.
This hack is nothing much compared to some of the hacks out there that make pins do double-duty (or worse), but I'm proud of my little workaround such as it is. I managed to eke out a critical control signal that otherwise would have been impossible to generate without moving to a 14- or 20-pin AVR part!
The important lesson here, I think, is that signals in any system can be grouped into at least two major classes:
- High-frequency, 'AC' type signals communicating data; versus
- Low-frequency, 'DC' type signals managing protocol
It is possible to multiplex two signals together on a single control line if they are each from the above two differing classes, as the time-frequency characteristics of each class are different enough to distinguish via a simple high/low pass filter. I know it's possible to multiplex much more than two things in a single physical channel but that's not something I need to know for this project right now :)