Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

I2C/SPI slave #565

Closed
gfwilliams opened this issue Aug 21, 2015 · 32 comments
Closed

I2C/SPI slave #565

gfwilliams opened this issue Aug 21, 2015 · 32 comments

Comments

@gfwilliams
Copy link
Member

JS execution speed is really too slow to properly do something via an IRQ, but there could be an array of data that can be read out...

write X, read N bytes -> get Array[X .. X+N-1]
write X,Y,Z -> Array[X]=Y, Array[X+1]=Z

http://forum.espruino.com/conversations/273112/

@gfwilliams
Copy link
Member Author

gfwilliams commented Sep 25, 2020

@MaBecker: I2C Slave support has just been added to nRF52 builds with 77a60d9

Use as follows:

Add this to the BOARD.py makefile section:

'DEFINES += -DI2C_SLAVE -DTWIS_ENABLED=1 -DTWIS1_ENABLED=1' # enable I2C slave support

Then on the device:

I2C1.setup({scl:D22,sda:D23,addr:20});
I2C1.on('read',x=>print("read",x));
I2C1.on('write',x=>print("write",x));

print(I2C1.buffer)

So there's a 64 byte Uint8Array called buffer added to I2C1 when addr:xyz is supplied. Then you get events when a read or write has happened, and you can read/write what is in the array (which will have already changed after the write has happened).

On the I2C master it's like every other I2C device:

var i = new I2C();
i.setup({scl:D14,sda:D15});
function writeData(addr, data) {
  i.writeTo(20,addr,data);
}
function readData(addr, len) {
  i.writeTo(20,addr);
  return i.readFrom(20,len);
}

@MaBecker
Copy link
Contributor

MaBecker commented Sep 27, 2020

This what has been tested with a Pixel badge 2018 and a BBB as master.

I2C1.setup({scl:D10,sda:D11,addr:20});
I2C1.buffer[0]=255;
I2C1.on('read',x=>print("read",x));
I2C1.on('write',x=>print("write",x));
//I2C1.on('write',()=>g.drawString(I2C1.buffer[0].toString(),0,0,true).flip());

print(I2C1.buffer);
  • i2cdetect finds 0x14 which is 20 decimal - ok
  • i2cset and i2cget tested - ok
  • i2cdump -y -r 0-15 1 0x14 - ok
  • upload code for a second time cause internal error, after E.reboot() and reconnect it works again one time
>Uncaught InternalError: I2C Initialisation Error 8
 at line 14 col 37
I2C1.setup({scl:D10,sda:D11,addr:20});
  • RETURN is needed in the left side to get the output, even for graphics stuff
  • i2cset -y 1 0x14 70 253 cause no error and prints write { "addr": 70, "length": 1 }

Overall: Amazing work - thanks a lot.

@MaBecker
Copy link
Contributor

  • watch -n 1 i2cdetect -y -a -r 1, reads up to addr 63 and than cause timeouts by the client on the i2c bus.

I2CS is causing timeouts

workaround: reboot the badge

@MaBecker
Copy link
Contributor

  • watch -n 1 i2cget -y 1 0x14 0 - ok
    extended code by setInterval(()=>{I2C1.buffer[0]=Math.random()*256|0;},5E2);
    adding this statement prints out the read and writes - no idea why....

    plus I2C1.on('read',x=>print("read",x,I2C1.buffer[0].toString(16))); to compare the values

I2CS permantent reads

@gfwilliams
Copy link
Member Author

Try now - the lack of response and and re-initialisation should be fixed

@MaBecker
Copy link
Contributor

MaBecker commented Sep 28, 2020

Great - that fixed those two issues, as you expected - thanks

  • upload code for a second time- fixed with d5a4627

  • RETURN is needed in the left side to get the output, even for graphics stuff - fixed with d5a4627

@MaBecker
Copy link
Contributor

Any idea how to nail this down?

watch -n 1 i2cdetect -y -a -r 1, reads up to addr 63 and than cause timeouts by the client on the i2c bus

@MaBecker
Copy link
Contributor

MaBecker commented Sep 28, 2020

Just digging into it with some jsiConsolePrintf()

in work......

    case TWIS_EVT_READ_REQ:
        if (p_event->data.buf_req) {
          JsVar *i2c = jsvObjectGetChild(execInfo.root,"I2C1",0);
          if (i2c) {
            JsVar *buf = jsvObjectGetChild(i2c,"buffer",0);
            size_t bufLen;
            char *bufPtr = jsvGetDataPointer(buf, &bufLen);
            jsiConsolePrintf("Case TWIS_EVT_READ_REQ %d, %d\n",twisAddr, bufLen - twisAddr);
            if (bufPtr && bufLen>twisAddr)
              nrf_drv_twis_tx_prepare(&TWIS1, bufPtr + twisAddr, bufLen - twisAddr);
            jsvUnLock2(i2c,buf);
          }
        }
        break;
>Case TWIS_EVT_READ_REQ 63, 1
read { "addr": 63, "length": 1 } 3f
>Case TWIS_EVT_READ_REQ 64, 0

@gfwilliams
Copy link
Member Author

Oh right - well there's only a 64 byte buffer, so that's all you get.

I2C1.setup(...);
I2C1.buffer = new Uint8Array(256);

should fix it

@MaBecker
Copy link
Contributor

MaBecker commented Sep 28, 2020

The larger buffer is not fixing it.

adding twisAddr %= twisAddr; will fix it and just can handle hours of calling i2cdetect

    case TWIS_EVT_READ_REQ:
        if (p_event->data.buf_req) {
          JsVar *i2c = jsvObjectGetChild(execInfo.root,"I2C1",0);
          if (i2c) {
            JsVar *buf = jsvObjectGetChild(i2c,"buffer",0);
            size_t bufLen;
            char *bufPtr = jsvGetDataPointer(buf, &bufLen);
            twisAddr %= bufLen;    // <--wrap around
            if (bufPtr && bufLen>twisAddr)
              nrf_drv_twis_tx_prepare(&TWIS1, bufPtr + twisAddr, bufLen - twisAddr);
            jsvUnLock2(i2c,buf);
          }
        }
        break;

What do you think about this wrap around solution?

@gfwilliams
Copy link
Member Author

Hmm, that's an odd one. It's hard to see why the larger buffer wouldn't fix it :(

The problem with the wrap-around solution is... why? I mean, it's almost certainly not what you'd want. I find it very hard to think of a point where you'd want your read to wrap. It's probably better that it errors and then you figure out why your software is trying to read past the end of the buffer. Also, I'm not sure it will read - what if twisAddr is 60, bufLen is 64, but then you want to read 10?

What does watch -n 1 i2cdetect -y -a -r 1 do? Does it actually write a one byte address first? That's the idea - that to read - like with most other I2C devices - you write a one byte register address, then read however many bytes you want.

@MaBecker
Copy link
Contributor

MaBecker commented Sep 29, 2020

What does watch -n 1 i2cdetect -y -a -r 1 do?

  • It just does a i2cget -y 1 20 without addr
  • causing the slave device to inc addr

Bildschirmfoto 2020-09-29 um 10 03 26

Does it actually write a one byte address first?

  • hopefully not, because this will change a value no, it does not.
  • only reads like i2cget make sense to detect a device

Even with a larger buffer like 256, nrf_drv_twis_tx_prepare() is not called because of the if condition and that is causing a i2c-bus-blocking situation.

            if (bufPtr && bufLen>twisAddr)
              nrf_drv_twis_tx_prepare(&TWIS1, bufPtr + twisAddr, bufLen - twisAddr);

Possible ways to fix this could be:

  • make twisAddr wrap around
  • set twisAddr to bufLen - 1 if >= bufLen
  • or .....

what if twisAddr is 60, bufLen is 64, but then you want to read 10?

First of all: This case is buffer size independent.
This case should cause a error or just return, as in this case, four bytes.

@gfwilliams
Copy link
Member Author

Does it actually write a one byte address first? ... hopefully not, because this will change a value.

That's not the case though - the first byte you write sets the address.

If you look at pretty much any I2C device it works the same way: http://www.espruino.com/modules/INA226.js

INA226.prototype.rd = function(a) {
  this.i2c.writeTo(this.addr,a);
  var d = this.i2c.readFrom(this.addr,2);
  ....
};

You write a one byte address, then read the data you want. You never issue a read on its own without writing first, so I'm not sure this is something we should really be trying to handle.

I feel like a lot of other I2C devices would behave exactly the same way in this case. I don't think it's a bug.

@MaBecker
Copy link
Contributor

just updated the text - we came over cross ..

@MaBecker
Copy link
Contributor

I feel like a lot of other I2C devices would behave exactly the same way in this case. I don't think it's a bug.

I do not think it's a bug, it's just something that needs to be handled.

@gfwilliams
Copy link
Member Author

Sorry - not my intention at all...

Does it need to be handled though? I mean, if you're doing repeated reads with no write then it's probably a software bug. Espruino shouldn't really have to work in that case - in fact if it keeps working and is used like that it's probably going to lead to errors down the line.

Or are you saying that in this case the nRF52 actually blocks the I2C bus and it can no longer be used for communicating with any other devices?

@MaBecker
Copy link
Contributor

Or are you saying that in this case the nRF52 actually blocks the I2C bus and it can no longer be used for communicating with any other devices?

Yes, this is exactly what happens

@gfwilliams
Copy link
Member Author

Ok, lets fix this then... What happens if you do:

if (bufPtr && bufLen>twisAddr)
  nrf_drv_twis_tx_prepare(&TWIS1, bufPtr + twisAddr, bufLen - twisAddr);
else
 nrf_drv_twis_tx_prepare(&TWIS1, twisRxBuf, 0);

Failing that, we can do:

else {
  memset(twisRxBuf, 255, sizeof(twisRxBuf));
  nrf_drv_twis_tx_prepare(&TWIS1, twisRxBuf, sizeof(twisRxBuf));
}

@MaBecker
Copy link
Contributor

Tested first coding with a 32 byte and 64 byte buffer - works perfekt - Thanks!

Screen shot shows the result of a 32 byte buffer i2c slave behavior.

Bildschirmfoto 2020-09-29 um 13 16 14

@gfwilliams
Copy link
Member Author

You mean using the first option?

@MaBecker
Copy link
Contributor

yes, this one

if (bufPtr && bufLen>twisAddr)
  nrf_drv_twis_tx_prepare(&TWIS1, bufPtr + twisAddr, bufLen - twisAddr);
else
 nrf_drv_twis_tx_prepare(&TWIS1, twisRxBuf, 0);

@gfwilliams
Copy link
Member Author

Great! Thanks for testing - just done!

@MaBecker
Copy link
Contributor

  • watch -n 1 i2cdetect -y -a -r 1, reads up to addr 63 and than cause timeouts by the client on the i2c bus - fixed with
    4819118

  • reading over buffer boundaries, returns 0x00 - ok

@gfwilliams
Copy link
Member Author

Great! we can close this then?

@MaBecker
Copy link
Contributor

Yes - what a great function - again many thanks!

@parasquid
Copy link

Hello! Just wanted to mention that since I was working on the hdc1080 sensor ( https://www.ti.com/lit/ds/symlink/hdc1080.pdf ) on page 14 section 8.6 Register map, it notes that the power on reset pointer is 0x00

Which means that although most sensors would do the "write to I2C address to set the pointer then read" protocol, TI also was expecting use cases where the sensor would have a "default" mode so it can be immediately read without setting the pointer.

Thanks for this! :)

@MaBecker
Copy link
Contributor

MaBecker commented Nov 8, 2020

Just working on a NRF52840 (SDK15) custom board with i2c slave

targets/nrf5x/jshardware.c: In function 'jshKill':
targets/nrf5x/jshardware.c:861:7: error: implicit declaration of function 'nrf_drv_twis_is_enabled'; did you mean 'nrf_drv_usbd_is_enabled'? [-Werror=implicit-function-declaration]
   if (nrf_drv_twis_is_enabled(TWIS1_INSTANCE_INDEX)) {
       ^~~~~~~~~~~~~~~~~~~~~~~
       nrf_drv_usbd_is_enabled
CC src/jswrap_arraybuffer.o
targets/nrf5x/jshardware.c:861:31: error: 'TWIS1_INSTANCE_INDEX' undeclared (first use in this function); did you mean 'TWIS1_ENABLED'?
   if (nrf_drv_twis_is_enabled(TWIS1_INSTANCE_INDEX)) {
                               ^~~~~~~~~~~~~~~~~~~~
                               TWIS1_ENABLED

.....

targets/nrf5x/jshardware.c: In function 'jshI2CSetup':
targets/nrf5x/jshardware.c:1973:54: error: 'TWIS1_INSTANCE_INDEX' undeclared (first use in this function); did you mean 'TWIS1_ENABLED'?
   if ((device == EV_I2C1) && nrf_drv_twis_is_enabled(TWIS1_INSTANCE_INDEX)) {
                                                      ^~~~~~~~~~~~~~~~~~~~
                                                      TWIS1_ENABLED

OK, twis_slave is missing for SKD15, for first tests took a copy of SDK12 and of cause now the linker is complaining because the missing libs.

uino/targets/nrf5x/jshardware.c:1930: undefined reference to `nrf_drv_twis_rx_prepare'
collect2: error: ld returned 1 exit status
make: *** [espruino_2v08.82_nrf52840_custom.elf] Error 1

@gfwilliams
Copy link
Member Author

I think you just need to edit the targetlibs/..15/nrf_config/sdk_config.h file to enable TWIS1 - you'll probably see the same changed You sure you copied the -DTWIS_ENABLED=1 -DTWIS1_ENABLED=1 lines into the board decl?

Maybe check the twis file gets built: 77a60d9#diff-600fbc2b88437b719bd2062e306061338736300b6e7560a36e96fda185185436R168

As far as I can see though the TWIS stuff is designed for SDK15, so it should work fine.

I guess the obvious question is: You had this working ok - so what changed? Can you still build it for the 52832 ok?

@MaBecker
Copy link
Contributor

MaBecker commented Nov 9, 2020

I think you just need to edit the targetlibs/..15/nrf_config/sdk_config.h file to enable TWIS1

Hmm, isn't that what those define do?

     # i2c slave
     'DEFINES +=-DI2C_SLAVE -DTWIS_ENABLED=1 -DTWIS1_ENABLED=1', # enable I2C slave support

I guess the obvious question is: You had this working ok - so what changed? Can you still build it for the 52832 ok?

Yes 52832 still build without err eg for PUCKJS_CUTOM.py ( SKD12 )

     # i2c slave
     'DEFINES +=-DI2C_SLAVE -DTWIS_ENABLED=1 -DTWIS1_ENABLED=1',  # enable I2C slave support

@MaBecker
Copy link
Contributor

MaBecker commented Nov 16, 2020

Just remove nrf5x_15/componentes and other stuff and callled source scripts/provision.sh NRF52840DK to reinstall it.

Same error, just check the zip file, it contains not twis_slave folder ....

Note: nrf5x_15 is not part of the repository, but nrf5x_12, is that why twis_slave folder and libs are not missing?

@MaBecker
Copy link
Contributor

MaBecker commented Dec 1, 2020

Well I would say with SDK15 there are some major changes starting with location and continue with different structure and function names.

Bildschirmfoto 2020-12-01 um 22 34 52

@gfwilliams
Copy link
Member Author

It's pretty standard for Nordic to randomly rename everything for no reason between literally every bloody SDK version.

I'd be pretty sure this will still be fine when you figure out the correct files to add though. Usually I just see what the linker complains about not having, then grep through the SDK to find out where those functions are defined.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants