Now to the real meat of things: Successful pairing!
To do this right, I marked every single byte in the entire process until successful pairing according to the datasheet of the NRF24L01+. This was a surprising amount of fun as the picture that I was building in my head became clearer and clearer by the minute. I almost got euphoric feelings doing this. Obviously, I was reverse engineering someone else's product but I was also the first and I seem to get it right! That was great!
But more on the downfall, later.
I'll spare you the typed transcript but will summarize what's happening instead.
The same procedure starts as with the unsuccessful pairing attempt only this time the board sends an ACK! Yeah! However, the command had changed and was 0xC0 0xCE now. What was that all about?
Now the address changes again to 0xCE 0x31 0xE3 0xE3 0xE3 for some reason. This was specifically awkward because the 0x31 came out of nowhere - or did it? More on that further below.
With this new address we clear some IRQs and send the packet again and wait for another ACK. If it comes, the pairing process is working and the motor controller successfully processed out packet, changed it's address while the remote changed it's address as well and was now listening to and acknowledging the new packet!
Now the remote changes it's configuration and instead of a transmitter it turns into a receiver. It clears IRQs again and checks for a data packet to arrive. This is when the board needs to send data containing the new address it wishes to use. In this case 0xB6 and 0xD8: 0xB6 0xD8 0xC9 0xC9 0xC9 for transmitter and receiver.
Restart remote, and done.
But wait, where did the 0x31 come from and how does the board make this new address byte up? Is this the secret sauce to pairing these remotes?
Let's analyze this little more:
Turns out it's a calculation based on the last known address, even though that could be random as well, I believe.
0xC0 is the actual pairing command or the command that provides a byte to base the new remote address on. It makes sense to use the last used or attempted address to make sure there is little repetition when trying to pair to a new address.
Based on that byte 0xCE the remote sent out the new address can be calculated by simple subtraction: 0xFF - 0xCE = 0x31
The moment I realized this simple trick, I started walking around my room fist pumping. This was awesome!
During the pairing the new address is being calculated based on the old address. The motor controller then takes the received bytes on that new address and calculates a response which represents the new address for the remote and stores it. The remote takes these two bytes as new address and resets. Easy.
But there was another major hurdle when implementing this: Timing!
Initially, I had some wait states in my state machine to allow for some debugging and other things. I also wasn't sure how fast I may communicate with the NRF24L01+ without overrunning it's SPI port. So I ended up never reaching the second phase of the pairing process right after the remote changes the address for the second time. I never received an ACK from the motor controller! It drove me nuts until I realized it must be a timing issue.
Turns out the motor controller only waits a very small amount of time until it declares the some bytes received as trash and goes back into state 1 of the pairing process! Gnarly!
After significant optimizations on my code I fired it up and.... it worked! Hell yeah!