Sending a "Byte Array (Hex)" from nRF52840 to phone

Hello,

I am struggling to figure out how to use the "bt_gatt_attr_read()" function in a gatt read callback. I can successfully use it to read strings of type "char *" and "uint8_t *". However, the data I need to send is two large hex numbers (192 bits, too large for even a "long long" type to hold). I can send the numbers as ASCII, but I need them to come as a Byte array, so that the hex number is represented in the "byte array" format in the nRF-connect app. For example, I have the following numbers:

0x1122334455667788 (64 bits)

0x11223344556677881122334455667788 (128 bits)

I need to concatenate them and then send them in bytes. 

The way I need to send them is using the "read characteristic" functionality. Ex: pressing the down-arrow on the nRF-connect phone app to read a characteristic. When I do that in the app, it should show (in byte array fomat):

0x112233445566778811223344556677881122334455667788 (192 bits)

However, I figured I would start small and try to just send the 64 bit number before trying to concatenate and send the larger number. But even that has proven too difficult for me.

I have tried to put the number in an array, like this:

{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}

and then passing the array into "bt_gatt_attr_read()" during the read callback. This also doesn't work. However, when I do this I can see the first element come through in the nRF-connect app (in this example, 0x11) but no others.

I'm not sure how to handle large numbers like this and the only way I can think to do so is to put them into an array.. but when I do so, the "bt_gatt_attr_read()" function only seems to read the first element or garbage! I've tried everything I can think of and I think I've reached the limit of my ability. 

Thanks for any help you can provide. I have attached the relevant parts of my code.

BT_GATT_SERVICE_DEFINE(remote_srv, 
    BT_GATT_PRIMARY_SERVICE(BT_UUID_REMOTE_SERVICE),
        BT_GATT_CHARACTERISTIC(BT_UUID_READER_TRANSACTION_CHRC, BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,  BT_GATT_PERM_READ | BT_GATT_PERM_WRITE, BT_transaction_read_cb, BT_transaction_write_cb, NULL),
    );


// this is my gatt read callback
static ssize_t BT_transaction_read_cb(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) {
    mainboard_uart_send("bt_read!\r\n", 10); //debugging

    int test = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
    int *p_test = test;
    //memcpy(buf, &p_test, 8*sizeof(test)); //I tried doing this too, didn't work either.

    return bt_gatt_attr_read(conn, attr, buf, len, offset, p_test, 8 * sizeof(test)); //returns TOTAL garbage!
    //return bt_gatt_attr_read(conn, attr, buf, len, offset, p_test, 8 * sizeof(test)); //returns first element followed by lots of garbage!
    //return bt_gatt_attr_read(conn, attr, buf, len, offset, p_test, 8 * sizeof(test)); //returns first element followed by 6 zeros!
}

  • You're missing the array declaration brackets. 

    The variable 'test' is not an array of 'int' type, but a regular scalar of 'int' type. Therefore memory is only allocated for the first 'int', with value 0x11, and the pointer 'p_test' points to address 0x11 instead of the address of the first element of 'test'. 

    I assume that any decent C99 linter or GCC would have picked up on the erroneous syntax of the array initialization, are you getting any warnings at all? 

    Are you by any chance using a different IDE than VS Code + our plugins?

    -edit:

    Remember to remove the "8x" size modifier as sizeof() returns the size of an array in number of bytes. In your case sizeof(test) will be 8. 

  • Hi, thanks so much for your response!

    To answer your question about the warnings.. I may have gotten some but I have a lot of warnings in my code that I have chosen to ignore at this stage before I clean them up. I guess what I'm saying is that if I did get a warning it blended in to the 5+ "variable/function not used" type warnings and I didn't see them.

    I can confirm that I am using VS Code with the Nordic plugins. I forgot to mention that I am using NCS 2.2.0.

    I tried in the past with the brackets, and it still didn't work.. I must have made a mistake in when I tried to use an array again. Thanks for catching that!

    After changing the declaration to:

    int test[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};

    I still got really odd results on the phone (first picture).

    I then realized that maybe my "int" type was being stored as a 32 bit type or something since there seemed to be correct data every 4 bytes in the app! So I tried declaring the array as the following:

    uint8_t test[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; //changed to uint8_t type. This way there's no extra data in between.

    This looked better (second picture), however I still had some odd data right at the start. I figured this had to do with the data length and offset that I was passing in so I fiddled with those for a while and I figured out that if I set the offset to 4 then I can put my data right at the beginning. However, half of the data gets chopped off! (third picture). So I added the offset back onto the length argument of bt_gatt_attr_read() so my code looked like this:

    static ssize_t BT_transaction_read_cb(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) {
        mainboard_uart_send("bt_read!\r\n", 10);
    
        uint8_t test[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
        int *p_test = test;
        offset = 4;
    
        return bt_gatt_attr_read(conn, attr, buf, len, offset, &p_test, sizeof(uint8_t)*sizeof(test) + offset);
    
    }

    This resulted in a correct looking output (picture 4). I just don't know if I'm supposed to be messing with the offset variable, or if there is a more elegant way to do this etc.

    Picture 1: Output with integer array

    Picture 2: Output with uint8_t array

    Picture 3: Output with uint8_t array with the offset forcibly set to 4

    Picture 4: Output with uint8_t array, offset set to 4, offset added onto length argument of bt_gatt_attr_read().

    1. If you're getting compiler warnings you need to handle that, they exist for good reason.
      You are probably getting a warning saying that you have an implicit pointer type conversion when you point p_test of type int to a uint8_t without a cast.

    2. You don't need the p_test pointer at all as an array is already a pointer in C, where a given array index is accessed by taking the address of test[0] and adding the index multiplied by the size of the array's type. For a uint8_t array this means that to access test[2] the compiler takes the address of test[0] and adds 2 * sizeof(uint8_t).

    3. You don't need multiply sizeof(uint8_t) with sizeof(test). Sizeof() gets evaluated at compile-time where both the type and length of the array is known. Sizeof(test) will therefore return 8.

    Try this: 

    static ssize_t BT_transaction_read_cb(struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, uint16_t len, uint16_t offset) {
        mainboard_uart_send("bt_read!\r\n", 10);
    
        uint8_t test[8] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
    
        return bt_gatt_attr_read(conn, attr, buf, len, offset, test[0], sizeof(test));
    }

  • Thank you! +1 vote up for the code snippet. 

    The problem indeed lied with how I was passing the array into the function. By removing the ampersand from the argument I was able to get it to work. Thank you for the tip about not needing an additional pointer.

  • About the pointer.

    You passed the address of the pointer instead of its content to bt_gatt_attr_read().

    I think that since both the array and the pointer can be considered as constant expressions(share memory allocation type), and because they were decleared one after the other in within the sample compilation unit(source file), their memory allocation was placed next to each other.

    I think the pointer was allocated to memory first, because the size of a pointer is always equal to the addressable range of a given architecture, in this case 32-bits or 4 bytes, and you needed to add an offset of 4 bytes in order to read from test[0] to test[7]. 

Related