How is the size of a characteristic set in Zephyr?

Am making a project based on NUS central demo and I can connect to our remote unit with the 52840DK, I can send data over a UART into it and I can get data out which is received on the remote BLE node.

I'd like to, well, need to, change the TX and RX characteristics to be 240 bytes in size.

I have seen here:

devzone.nordicsemi.com/.../extends-the-mtu-size-in-latest-zephyr

Someone talking about the maximum overall size (although I don't understand what he's saying in the answer).

Where are these used? ie What file would this be in?

CONFIG_BT_BUF_ACL_RX_SIZE instead of CONFIG_BT_L2CAP_RX_MTU (comes from l2cap.h, 
#define BT_L2CAP_RX_MTU (CONFIG_BT_BUF_ACL_RX_SIZE - BT_L2CAP_HDR_SIZE)

And this:

Max payload size of adv_data with extended advertizement


I'd expect to see it in this:


/* UART Service Declaration */
BT_GATT_SERVICE_DEFINE(nus_svc,
BT_GATT_PRIMARY_SERVICE(BT_UUID_NUS_SERVICE),
BT_GATT_CHARACTERISTIC(BT_UUID_NUS_TX,
BT_GATT_CHRC_NOTIFY,
#ifdef CONFIG_BT_NUS_AUTHEN
BT_GATT_PERM_READ_AUTHEN,
#else
BT_GATT_PERM_READ,
#endif /* CONFIG_BT_NUS_AUTHEN */
NULL, NULL, NULL),
BT_GATT_CCC(nus_ccc_cfg_changed,
#ifdef CONFIG_BT_NUS_AUTHEN
BT_GATT_PERM_READ_AUTHEN | BT_GATT_PERM_WRITE_AUTHEN),
#else
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
#endif /* CONFIG_BT_NUS_AUTHEN */
BT_GATT_CHARACTERISTIC(BT_UUID_NUS_RX,
BT_GATT_CHRC_WRITE |
BT_GATT_CHRC_WRITE_WITHOUT_RESP,
#ifdef CONFIG_BT_NUS_AUTHEN
BT_GATT_PERM_READ_AUTHEN | BT_GATT_PERM_WRITE_AUTHEN,
#else
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
#endif /* CONFIG_BT_NUS_AUTHEN */
NULL, on_receive, NULL),
);
In good old SES we had what seems logical to me in this:

   attr_char_value.max_len   = TX_LEN;


    err_code = sd_ble_gatts_characteristic_add( exlrt_service.service_handle,
                                                &char_md,
                                                &attr_char_value,
                                                handle );
So what's the equivalent with Zephyr please? And do I have to add something to the proj file to say what the max allowable size is?
Parents
  • A lot of definition in Zephyr with Attributes, characteristics, descriptors and services are done at the macro level. And there are many level of preprocessing levels used which makes them a bit less readable than the previous nRF5SDK solutions. I can understand the frustration, which I had in the beginning. But once you are past that, the rest of the usage becomes simpler.

    Where are these used? ie What file would this be in?

    CONFIG_BT_BUF_ACL_RX_SIZE instead of CONFIG_BT_L2CAP_RX_MTU (comes from l2cap.h, 
    #define BT_L2CAP_RX_MTU (CONFIG_BT_BUF_ACL_RX_SIZE - BT_L2CAP_HDR_SIZE)

    To configure your BLE device for 240-byte data packets, you need to adjust the MTU size and ACL buffer sizes in the prj.conf file. The MTU determines the largest amount of data that can be sent in a single BLE packet. For a maximum payload, set both the TX and RX MTU to 247 by using:

    CONFIG_BT_L2CAP_TX_MTU=247
    CONFIG_BT_L2CAP_RX_MTU=247

    BLE uses ACL buffers to temporarily store packets during transmission or reception. These buffers need to be slightly larger than the MTU to account for protocol overhead, such as L2CAP headers. Set them to 251 bytes using:

    CONFIG_BT_BUF_ACL_RX_SIZE=251
    CONFIG_BT_BUF_ACL_TX_SIZE=251

    These adjustments ensure the ACL buffers can handle the MTU and its associated overhead. Without this, packets might get truncated or fail during transmission. Applying these settings allows your device to handle 240-byte packets effectively.

  • Hi, Thanks for that. That is the global maximum setting though. If you have other characteristics that only want to be a few bytes long, does that mean they won't/can't be? I cna't believe that's the case.

    How do you set an *individual* characteristic's size please?

  • Then most likely you would not use the helper macros and construct some of those attributes yourself for example

    #include <zephyr/bluetooth/uuid.h>
    #include <zephyr/bluetooth/conn.h>
    #include <zephyr/bluetooth/gatt.h>
    #include <zephyr/bluetooth/hci.h>
    
    static uint8_t manufacturer_name[16];
    static uint8_t device_info[240];
    
    BT_GATT_SERVICE_DEFINE(device_service,
        BT_GATT_PRIMARY_SERVICE(BT_UUID_DECLARE_16(0x180A)),
    
        BT_GATT_CHARACTERISTIC(
            BT_UUID_DECLARE_16(0x2A29),
            BT_GATT_CHRC_READ,
            BT_GATT_PERM_READ,
            NULL,
            NULL,
            manufacturer_name
        ),
    
        BT_GATT_CHARACTERISTIC(
            BT_UUID_DECLARE_16(0x2A2A),
            BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
            BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
            NULL,
            NULL,
            device_info
        )
    );

    wrote and tested in NUS sample and it compiles atleast

Reply
  • Then most likely you would not use the helper macros and construct some of those attributes yourself for example

    #include <zephyr/bluetooth/uuid.h>
    #include <zephyr/bluetooth/conn.h>
    #include <zephyr/bluetooth/gatt.h>
    #include <zephyr/bluetooth/hci.h>
    
    static uint8_t manufacturer_name[16];
    static uint8_t device_info[240];
    
    BT_GATT_SERVICE_DEFINE(device_service,
        BT_GATT_PRIMARY_SERVICE(BT_UUID_DECLARE_16(0x180A)),
    
        BT_GATT_CHARACTERISTIC(
            BT_UUID_DECLARE_16(0x2A29),
            BT_GATT_CHRC_READ,
            BT_GATT_PERM_READ,
            NULL,
            NULL,
            manufacturer_name
        ),
    
        BT_GATT_CHARACTERISTIC(
            BT_UUID_DECLARE_16(0x2A2A),
            BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
            BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
            NULL,
            NULL,
            device_info
        )
    );

    wrote and tested in NUS sample and it compiles atleast

Children
  • OK, thanks. So it is defined by passing a pointer to the array and somehow is works out how big that array is? I can see here:

    https://docs.zephyrproject.org/apidoc/latest/group__bt__gatt__server.html#ga9e739546dbd906d3b3c4f1ed5ad9f41e

    That the final thing passed in the macro is

    _user_data Characteristic Attribute user data

    It would be really helpful if the documentation said you are passing a pointer to the data to be read or written and something along the lines of this knows for reasons x, y and z that the length of this array is taken as the size of the characteristics.

    All in all I find the documentation and the example projects impenetrable.

    Maybe it's partly because the whole ecosystem is nothing like a typical embedded IDE.  Also, with so many scattered files that, at least as far as I can see, aren't in the project explorer. So, for example, it looks like maybe nus.c is part of the project, but I can't see anywhere in that source files thing on the left hand side in VSC that they are in the project. nus.h   is obviously included in main.c so I assume nus,c is also in the project. You have to, as far as i can make out, go to file open and go through your file system to the directory nus.h is in to open nus.c and assume that what is in there is part of this project.

    What would be really helpful are pages that say something along the lines of:

    If you want to create a custom service you need the following:

    BT_GATT_SERVICE_DEFINE
    (nus_svc,
    BT_GATT_PRIMARY_SERVICE(BT_UUID_NUS_SERVICE),

    BT_GATT_CHARACTERISTIC(BT_UUID_NUS_TX,
    BT_GATT_CHRC_NOTIFY,
    BT_GATT_PERM_READ,
    NULL, NULL, NULL),
    BT_GATT_CCC(nus_ccc_cfg_changed,
    BT_GATT_PERM_READ | BT_GATT_PERM_WRITE),
    BT_GATT_CHARACTERISTIC(BT_UUID_NUS_RX,
    BT_GATT_CHRC_WRITE |
    BT_GATT_CHRC_WRITE_WITHOUT_RESP,
    BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
    NULL, on_receive, NULL),
    );

    And explain explicitly what each of those lines do. At the moment you have to jump around from page to page with very sketchy information on  what all of those macros do.

    Also, in all this example code, there are absolutely no comments for each function. Some doxygen style comments explaining what is being passed in and out, why and what the function does would be *so* helpful. At least the Zephyr files seems to be very well commented.

    In the case of central_uart am I right to assume that nus.c  is used and that is where the

    nus_svc is defined and its characteristics set up?
    Thank you. Slight smile

  • That is a good feedback and correct understanding. I think developing with Zephyr can be overwhelming for us if we have been working with straight forward solutions and IDEs. We acknowledge that for some usecases ramping up with new solution can be painstaking. We are working on making it simpler and with increased documentation.

    DiBosco said:

    In the case of central_uart am I right to assume that nus.c  is used and that is where the

    nus_svc is defined and its characteristics set up?

    You will not like it, but the definition is hidden in that macro BT_GATT_SERVICE_DEFINE which actually defines variables and constants based on the arguments sent to that macro. It can get challenging to track those definitions if there are many levels of preprocessing tricks being used for naming those variables.

    #define BT_GATT_SERVICE_DEFINE(_name, ...)              \
        const struct bt_gatt_attr attr_##_name[] = { __VA_ARGS__ }; \
        const STRUCT_SECTION_ITERABLE(bt_gatt_service_static, _name) =  \
                        BT_GATT_SERVICE(attr_##_name)
  • OK thanks again, I'll see whether I can sort this out with defining my own service and knit it in with the central project.

    I really detest macros and never, ever use them because they obfuscate code too much, but, in this case, I don't need to  understand what's going on under the hood, so I should just suck it up!

Related