Is it possible to read a data pin with PPI? (PS/2 bit banging using PPI)

Hi,

I am trying to implement a PS/2 bit banging driver. My initial attempt used the GPIO library and interrupts, but unfortunately the interrupts are not fast enough and oftentimes there are transmission errors due to missed bits.

Now I am exploring whether it would be possible to use PPI to read the data. 

PS2 uses a clock and data pin. The data pin is read on the falling edge of the clock pin. The frequency is approximately 10-16hz.

The PS2 data frame is 11bit:

  • Start bit
  • 8 data bits
  • Parity bit
  • Stop bit

I considered trying to use SPI, but it seems that it can‘t read odd number of bits. 

Now I’m exploring whether PPI would work. 

My idea is…

  1. Create a timer that counts whenever the clock pin triggers.

  2. Copy the data pin value to some kind of buffer every time the clock pin triggers
  3. When the timer reaches 11, copy the buffer using easy DMA to the cpu and trigger an interrupt to notify it. 
  4. Reset the timer counter so that it can start from scratch

Is something like this feasible?

If yes, could you please point me to some documentation on how to configure such PPI tasks? I couldn’t find any good explanations. 

I was able to find examples for counting, but not for storing the data pin value in a buffer array and for transmitting it using easy DMA.

Thank you so much,

Kim

  • Hi,

    Unfortunately, it is not possible to capture digital levels on GPIOs using PPI.

    The other alternative is to use the SAADC peripheral to capture the analog level of the GPIO and determine the logical level of the signal from the captured SAADC samples in your buffer. The SAADC sample task can be triggered through PPI. If the frames have a static length, you do not need a timer to capture the number of clock pulses, you can set the SAADC buffer length of the packet size and connect the GPIOTE input event from the clock pin directly to the sample task of the SAADC through PPI. When the SAADC buffer have been filled with the requested number of samples (corresponding to the number of bits in your package), the END event will be generated and you can handle the frame assembly in the SAADC event handler.

    Best regards,
    Jørgen 

  • Thank you, Jorgen. Does the SAADC library only work on analogue pins or does it also work on digital ones?

    And do you have any sample code for using it in this way?

  • Kimcha said:
    Does the SAADC library only work on analogue pins or does it also work on digital ones?

    The SAADC only works with the analog inputs (AIN0-AIN7), but these are shared with digital GPIOs and can be dynamically switched between analog and digital functionality from the firmware.

    Kimcha said:
    And do you have any sample code for using it in this way?

    Unfortunately, I do not have any sample code showing how to do this. Are you using nRF Connect SDK or nRF5 SDK?

  • Thank you. I am using Zephyr 3.2 and whatever is bundled with it. 

    Unfortunately, my hardware design already uses digital-only pins for this design. 

    Is it possible to “copy” pin values from a digital pin to the analogue pins using PPI?

  • Since the PS2 keyboard (internal) data transitions occur on the clock +ve edge and are subsequently sampled by an external device on the clock -ve edge the data line can be used as a single edge-per-bit signal to capture a counter/timer value via PPI as the data transition is known to be clean.

    One counter counts the Clock transitions from 0-10 (11 bits) and at the last bit captures a time giving the 11-bit clock period, which although varies between keyboards is very consistent over the 11-bits making up a single transmission for a given keyboard. Since the data is cleanly changed on the +ve edge of the clock only a single data transition occurs for each bit and this edge can be used to capture a timer value using the 5 x CC registers on (say) timer 4. There are a maximum of 5 negative transitions worst case, odd parity and 0xAA data value so 10 CC registers are required which means reusing registers and storing the captured values within the interrupt.

    Once each edge time has been captured for a complete single character, the 11-bit time can be used to accurately decode the actual character. Edit: Updated for both edge detection and time detection

    // 11-bit Data bit changes on clock +ve edge, sample value on clock -ve edge
    //
    // Standard character 0x21 - Single sample, single timer (T4), requires reload
    //
    // Idle 'Start'Bit 0'Bit 1'Bit 2'Bit 3'Bit 4'Bit 5'Bit 6'Bit 7'Par  'Stop | Idle
    //      |     |     |     |     |     |     |     |     |     |     |     | 11-Bit Framing
    //      |     |     |     |     |     |     |     |     |     |     |     |
    //         0     1     2     3     4     5     6     7     8     9     10   Counter 3
    //      |     |     |     |     |     |     |     |     |     |     |     |
    // --------+  +--+  +--+  +--+  +--+  +--+  +--+  +--+  +--+  +--+  +--+  +--------
    //         |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  | Clock
    //         |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
    //         +--+  +--+  +--+  +--+  +--+  +--+  +--+  +--+  +--+  +--+  +--+
    //      |     |     |     |     |     |     |     |     |     |     |     |
    // Idle 'Start'Bit 0'Bit 1'Bit 2'Bit 3'Bit 4'Bit 5'Bit 6'Bit 7'Par  'Stop | Idle
    // -----+     +-----+                       +-----+           +-----+-----+--------
    //      |     |     |                       |     |           |
    //      |     |     |                       |     |           |             Data Normal
    //      +-----+     +-----+-----+-----+-----+     +-----+-----+
    //
    //      +-----+     +-----+-----+-----+-----+     +-----+-----+
    //      |     |     |                       |     |           |             Data Invert
    //      |     |     |                       |     |           |
    // -----+     +-----+                       +-----+           +-----+-----+--------
    //      |     |     |     |     |     |     |     |     |     |     |     |
    //      |     |     |     |     |     |     |     |     |     |     |     | 11-Bit Framing
    // Idle 'Start'Bit 0'Bit 1'Bit 2'Bit 3'Bit 4'Bit 5'Bit 6'Bit 7'Par  'Stop | Idle
    //      |     |     |     |     |     |     |     |     |     |     |     |
    //      |     |     |                       |     |           |                                                Clear Reload
    //      |     |     |                       |     |           |       -- Edge 10   pTimer4->CC[4] = BT/2+(10*BT) *      3,0
    //      |     |     |                       |     |           |       -- Edge  9   pTimer4->CC[3] = BT/2+( 9*BT) -      2
    //      |     |     |                       |     |           |       -- Edge  8   pTimer4->CC[2] = BT/2+( 8*BT) -      1
    //      |     |     |                       |     |           |       -- Edge  7   pTimer4->CC[1] = BT/2+( 7*BT) -      0
    //      |     |     |                       |     |           |       -- Edge  6   pTimer4->CC[0] = BT/2+( 6*BT) -      -
    //      |     |     |                       |     |           +--------- Edge  5   pTimer4->CC[5] = BT/2+( 5*BT) *      -
    //      |     |     |                       |     +--------------------- Edge  4   pTimer4->CC[4] = BT/2+( 4*BT) -      -
    //      |     |     |                       +--------------------------- Edge  3   pTimer4->CC[3] = BT/2+( 3*BT) -      2
    //      |     |     +--------------------------------------------------- Edge  2   pTimer4->CC[2] = BT/2+( 2*BT) -      1
    //      |     +--------------------------------------------------------- Edge  1   pTimer4->CC[1] = BT/2+( 1*BT) -      0
    //      +--------------------------------------------------------------- Edge  0   pTimer4->CC[0] = BT/2         -      -
    //      |     |     |     |     |     |     |     |     |     |     |                                          Clear Reload
    //      |     |     |     |     |     |     |     |     |     |     +--- Stop bit  pTimer4->CC[4] = BT/2+(10*BT) *      3,0
    //      |     |     |     |     |     |     |     |     |     +--------- Parity    pTimer4->CC[3] = BT/2+( 9*BT) -      2
    //      |     |     |     |     |     |     |     |     +--------------- Bit 7     pTimer4->CC[2] = BT/2+( 8*BT) -      1
    //      |     |     |     |     |     |     |     +--------------------- Bit 6     pTimer4->CC[1] = BT/2+( 7*BT) -      0
    //      |     |     |     |     |     |     +--------------------------- Bit 5     pTimer4->CC[0] = BT/2+( 6*BT) -      -
    //      |     |     |     |     |     +--------------------------------- Bit 4     pTimer4->CC[5] = BT/2+( 5*BT) *      -
    //      |     |     |     |     +--------------------------------------- Bit 3     pTimer4->CC[4] = BT/2+( 4*BT) -      -
    //      |     |     |     +--------------------------------------------- Bit 2     pTimer4->CC[3] = BT/2+( 3*BT) -      2
    //      |     |     +--------------------------------------------------- Bit 1     pTimer4->CC[2] = BT/2+( 2*BT) -      1
    //      |     +--------------------------------------------------------- Bit 0     pTimer4->CC[1] = BT/2+( 1*BT) -      0
    //      +--------------------------------------------------------------- Start Bit pTimer4->CC[0] = BT/2         -      -

Related