Computing stuff tied to the physical world

Debugging the I2C bridge

Our final goal: receiving packets from the RFM69, and passing them on to the Raspberry Pi as I2C slave. The code will be a little more complex than before, but the scary bit is that there is no room for easy debugging – we have no I/O pins left to send printf output to!

For reference, here are the pin assignments used for the LPC810:

  • pin 1 = PIO0_5 = SDA (I2C) – to RasPi
  • pin 2 = PIO0_4 = SCL (I2C) – to RasPi
  • pin 3 = PIO0_3 = SSEL (SPI) – to RFM69
  • pin 4 = PIO0_2 = MISO (SPI) – to RFM69
  • pin 5 = PIO0_1 = MOSI (SPI) – to RFM69
  • pin 6 = +3.3 V – to RasPi
  • pin 7 = GROUND – to RasPi
  • pin 8 = PIO0_0 = SCLK (SPI) – to RFM69

And this is what the setup looks like – plugged into the Raspberry Pi’s 3.3V and I2C:

DSC 4933

The good news is that we have all the hardware and software working, separately that is. Having already tested all the individual pieces is extremely useful, as you will see shortly.

Here are the main parts of the “i2listen” code – the full version is on GitHub, as usual:

RF69<SpiDevice> rf;
uint8_t rxBuf[66];

struct Payload {
    uint8_t seq, len, rssi, lna;
    uint16_t afc;
    uint8_t buf[66];
} out;                      // this is the data as returned over I2C

uint32_t i2cBuffer [24];    // data area used by ROM-based I2C driver
I2C_HANDLE_T* ih;           // opaque handle used by ROM-based I2C driver
I2C_PARAM_T i2cParam;       // input parameters for pending I2C request
I2C_RESULT_T i2cResult;     // return values for pending I2C request

uint8_t i2cRecvBuf [2];     // receive buffer: address + register number

void i2cSetup () { ... }

// called when I2C reception has been completed
void i2cRecvDone (uint32_t err, uint32_t) {
    i2cSetupRecv();
    if (err == 0)
        i2cSetupSend(i2cRecvBuf[1]);
}

// called when I2C transmission has been completed
void i2cSendDone (uint32_t err, uint32_t) {
    if (err == 0)
        out.len = 0;
    i2cSetupRecv();
}

// prepare to receive the register number
void i2cSetupRecv () {
    i2cParam.func_pt = i2cRecvDone;
    i2cParam.num_bytes_send = 0;
    i2cParam.num_bytes_rec = 2;
    i2cParam.buffer_ptr_rec = i2cRecvBuf;
    LPC_I2CD_API->i2c_slave_receive_intr(ih, &i2cParam, &i2cResult);
}

// prepare to transmit either the byte count or the actual data
void i2cSetupSend (int regNum) {
    i2cParam.func_pt = i2cSendDone;
    i2cParam.num_bytes_rec = 0;
    if (regNum == 0) {
        i2cParam.num_bytes_send = 1;
        i2cParam.buffer_ptr_send = &out.len;
    } else {
        i2cParam.num_bytes_send = out.len;
        i2cParam.buffer_ptr_send = (uint8_t*) &out;
    }
    LPC_I2CD_API->i2c_slave_transmit_intr(ih, &i2cParam, &i2cResult);
}

int main () {
    i2cSetup();
    i2cSetupRecv();

    LPC_SWM->PINENABLE0 |= 3<<2;        // disable SWCLK/SWDIO
    // lpc810 coin: sck=0p8, ssel=3p3, miso=2p4, mosi=1p5
    LPC_SWM->PINASSIGN3 = 0x00FFFFFF;   // sck  -    -    -
    LPC_SWM->PINASSIGN4 = 0xFF030201;   // -    nss  miso mosi

    rf.init(1, 42, 8683);
    while (true) {
        int len = rf.receive(rxBuf, sizeof rxBuf);
        if (len >= 0) {
            ++out.seq;
            out.len = len + 6;
            out.rssi = rf.rssi;
            out.lna = rf.lna;
            out.afc = rf.afc;
            memcpy(out.buf, rxBuf, sizeof out.buf);
        }
    }
}

In case you’re wondering: this code uses 1268 bytes of flash and 288 bytes of RAM.

The basic idea is to set up a complete “payload” once a packet is received, which is then sent out to the I2C bus when the Raspberry Pi asks for it. All I2C slave communication is handled by the ROM-based I2C driver in the LPC810, using interrupt mode:

  • when I2C data comes in, the first byte is assumed to contain either “0” or “1”
  • if “0”, we send back the number of bytes waiting in the payload (zero if none)
  • if “1”, we send back the actual payload, including extra rss, afc, etc. details

As it turns out, this code worked on the very first try – there is nothing to debug!

Here is what we get back when we read “register 0”:

$ i2cdump -y -r 0-0 1 0x70 c
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: 0a 

What it tells us, is that there is a 10-byte (0x0a hex) payload waiting.

And here are a couple of payload reads, as requested from the command line:

$ i2cdump -y -r 1-10 1 0x70 c
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00:    7d 0a 93 01 fa ff 80 01 3c 00                    }????.??<.     
$ i2cdump -y -r 1-10 1 0x70 c
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00:    7e 0a a5 01 e2 fc 80 01 3d 00                    ~???????=.     
$ i2cdump -y -r 1-10 1 0x70 c
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00:    8c 0a 96 01 fa fe 80 01 4b 00

Since the last two bytes are a sequence number sent from the “blip” node, we can see that these are indeed different packets. Keep in mind however, that packets will be lost if we don’t fetch them quickly enough – as shown in that last example.

There are still some weak spots in this code. The most important one is that we’re mixing interrupts and looping code without taking any precautions. If an I2C request comes in while a packet is being copied into the payload buffer, we’ll end up with garbled data. This needs to be addressed for production use, but as proof-of-concept, the above code will do.

So there it is: µC “blip” => RFM69 => air => RFM69 => µC “i2listen” => I2C => RasPi

We’ve created a brand new wireless network with nothing but a few little LPC810’s!

[Back to article index]