This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Multiple data sources transmission routing and identification

Dear Team, 

I'm preparing an application for nRF52840 that will require to send and receive data collected from two different sources, I2S and UART, two UART or SPI and UART channels. 
One of the connections will be lower in bps than the other but essentially they must arrive to the receiver and be routed to the correct UART, I2S or SPI port.
How do I route the packets to be sent to the correct interfaces at the destination (receiver)? 
On the other hand, I'll be interested in using the lowest latency protocol available (maybe ESB or Gazell). Other protocols could be used if the features are not availabe for those two. 

Is it possible to do this? 

About the bandwidth, I'll only need about 500/600 kbps as any audio traveling towards radio will be previously compressed and the other channel will be small control data to interface with the custom code and other peripherals. 

Thanks for your time and attention.
Best regards, 

Mental Mode

  • Hello

    Just for your to know, I've been programming for years in "too much high level" languages as Java, C# and so on... 

    I've been studying C++ in my free time the last 2 months, but at this point there are still things that I don't fully understand about the code proposed so... Let me put the code step by step and comment what I'm understanding. 

    Probably, you're so used to code for hardware using these compilers but I'm not so familiar with these types and operators. 

    I'll paste your program and comment my doubts about the original definition and transmitter part.

    The receiver part will be clarified with the transmitter part. 

    Again. I know that you're not here to teach C++. You're here to help to implement your solution.

    I'm using SES and run several examples so I'll able to test this at the moment you reply. 

    // COMMON STRUCT DEFINITION:
    
    typedef struct // Struct definition for dummy example. For me to understand how a struct is declared. 
    {
        uint8_t     cmd_byte; // Unsigned integer size 1 byte variable called cmd_by. I understood that this will be the “packet type” for my program to distinguish between i2c or whatever origin. To be able to put in the right port on receivers side.
        uint32_t    some_data; // Unsigned integer size 4 bytes variable called some_data. This is dummy thing to explain struct declaration.
        float       some_more_data; // Float type integer size float variable called some more data. Another dummy thing to explain another variable in case of wanting to send more than 1 data in the same struct type. 
    } packet_type_1_t;
    
    typedef struct 
    {
        uint8_t     cmd_byte; // here we are declaring what type of data I want to send to be routed by the receiver. 
        uint8_t     spi_data[4]; // Unsigned integer size 4 bytes ARRAY called some_data maximum 4 bytes in length. Is this the data size expected from SPI?
        uint8_t     i2c_data[6]; // Float type integer size float ARRAY called some more data maximum 6 bytes in length. Is this the data size expected for I2C data? 
    } packet_type_2_t;
    
    enum {PACKET_TYPE_1, PACKET_TYPE_2}; // Here you declare the two structs as unique types to be used in all the program. Take into account that my original program was intended to separate i2c and spi in two different structs but this is OK for me, 
    
    // TRANSMITTER PART:
    
    static uint32_t send_packet_type_1(packet_type_1_t *data_packet) // function declaration returning 4 bytes of static (doesn’t change each time is called) data called send_packet_type_1. **DOUBT** Are you passing the cmd_byte and the “deferenced pointer here? If that’s the case, why we don’t have the comma? 
    {
        nrf_esb_payload_t esb_payload = {0}; // Declaration of a variable using your struct type nrf_esb_payload_t called esb_payload and initialized to 0. 
    
        // Ensure that the cmd_byte field is set correctly, in correspondence with the packet type
        data_packet->cmd_byte = PACKET_TYPE_1; // Here, we are defining our data packet cmd byte struct as type 1. 
    
        // Populate the esb_payload struct
        memcpy(esb_payload.data, (uint8_t*)data_packet, sizeof(packet_type_1_t)); // Copy our unsigned 1 byte data packet variable to es_payload_data and defining the size of out whole struct. **DOUBT**Why the * in uint8_t?
        esb_payload.length = sizeof(packet_type_1_t); // Populating the required variable esb_payload.length with the size of the struct.
        esb_payload.pipe = 0; // Selecting the pipe to send it. **DOUBT** how many pipes do I can use?
    
        // Forward the data to the ESB library
        return nrf_esb_write_payload(&esb_payload); // Returning all the stuff to the nrf_esb_write_payload function.
    }

    Again, Thanks for your valuable help. 

    Best regards, 

    Pablo. 

  • Hi Pablo

    All our code examples including this one are based on C, not C++. 

    Technically C++ is a superset of C, but if you are reading about C++ you will get a lot of information about classes, inheritance, function overloading, exceptions etc which is not relevant to understand our C examples.

    In other words I would strongly suggest starting with a book on C to get familiar with the basics of the language, and then you can move to C++ later on if you need to learn all the additional features supported by C++. 

    A good start is what many consider the "bible" on the C language, "The C Programming Language by Kernighan and Ritchie", which even seems to be available for free here

    As for your questions, I tried to answer them inline:

    // COMMON STRUCT DEFINITION:
    
    typedef struct // Struct definition for dummy example. For me to understand how a struct is declared. TO: Correct
    {
        uint8_t     cmd_byte; // Unsigned integer size 1 byte variable called cmd_by. I understood that this will be the “packet type” for my program to distinguish between i2c or whatever origin. To be able to put in the right port on receivers side. TO: Correct
        uint32_t    some_data; // Unsigned integer size 4 bytes variable called some_data. This is dummy thing to explain struct declaration. TO: Correct
        float       some_more_data; // Float type integer size float variable called some more data. Another dummy thing to explain another variable in case of wanting to send more than 1 data in the same struct type. TO: Correct
    } packet_type_1_t;
    
    typedef struct 
    {
        uint8_t     cmd_byte; // here we are declaring what type of data I want to send to be routed by the receiver. TO: Correct. My proposal assumes that each struct will include this cmd_byte as the very first field, which means it will be the first byte in the packet .
        uint8_t     spi_data[4]; // Unsigned integer size 4 bytes ARRAY called some_data maximum 4 bytes in length. Is this the data size expected from SPI? TO: 4 and 6 were just random numbers I made up on the spot ;) How many bytes of SPI data and I2C data you get depends on the sensor you are talking to, and should be specified in the sensor datasheet.
        uint8_t     i2c_data[6]; // Float type integer size float ARRAY called some more data maximum 6 bytes in length. Is this the data size expected for I2C data? TO: Same as above
    } packet_type_2_t;
    
    enum {PACKET_TYPE_1, PACKET_TYPE_2}; // Here you declare the two structs as unique types to be used in all the program. Take into account that my original program was intended to separate i2c and spi in two different structs but this is OK for me, 
    // TO: An enum is simply a way to define constant values that are guaranteed to get unique values, that you can then put into the cmd_byte field for the different structs. By default the first constant will have value 0, the next value 1 and so on. The following code would be equivalent:
    //#define PACKET_TYPE_1 0
    //#define PACKET_TYPE_2 1
    
    // TRANSMITTER PART:
    
    static uint32_t send_packet_type_1(packet_type_1_t *data_packet) // function declaration returning 4 bytes of static (doesn’t change each time is called) data called send_packet_type_1. **DOUBT** Are you passing the cmd_byte and the “deferenced pointer here? If that’s the case, why we don’t have the comma? 
    // TO: In C a static function declaration is only available within the same file, telling the compiler that this function should not be available to other files.  Think of it as a "private" function in C++ or C#. Static variables are different, but there are plenty of guides online describing the static keyword in C
    // TO: packet_type_1 is the type, data_packet is the name, and the * means you are passing a pointer to the struct, not the struct itself
    {
        nrf_esb_payload_t esb_payload = {0}; // Declaration of a variable using your struct type nrf_esb_payload_t called esb_payload and initialized to 0. // TO: Correct. The {0} initialization is a simple way to set all the struct fields to 0.
    
        // Ensure that the cmd_byte field is set correctly, in correspondence with the packet type // TO: Correct
        data_packet->cmd_byte = PACKET_TYPE_1; // Here, we are defining our data packet cmd byte struct as type 1. //TO: Correct
    
        // Populate the esb_payload struct
        memcpy(esb_payload.data, (uint8_t*)data_packet, sizeof(packet_type_1_t)); // Copy our unsigned 1 byte data packet variable to es_payload_data and defining the size of out whole struct. **DOUBT**Why the * in uint8_t?
    	// TO: The memcpy function expects a pointer to an array of bytes (aka uint8_t), and the compiler will complain if we provide it a pointer to a packet_type_1_t struct. By casting the data_packet pointer to an uint8_t pointer we are essentially telling the compiler to treat this as a uint8_t pointer, and you should not get a warning. Most compilers will allow you to use the pointer without a cast, but it is good practice to remove all warnings from your code. 
        esb_payload.length = sizeof(packet_type_1_t); // Populating the required variable esb_payload.length with the size of the struct. // TO: Correct
        esb_payload.pipe = 0; // Selecting the pipe to send it. **DOUBT** how many pipes do I can use? // TO: The maximum number of pipes is set by the NRF_ESB_PIPE_COUNT define in nrf_esb.h, and because of hardware limitations this number can not be larger than 8. With 8 pipes the valid pipe range is 0-7
    
        // Forward the data to the ESB library
        return nrf_esb_write_payload(&esb_payload); // Returning all the stuff to the nrf_esb_write_payload function. // TO: Possibly this call is a bit confusing if you're new to C. Essentially you first call nrf_esb_write_payload and provide a pointer to the esb_payload struct that you just populated, and then you are returning the return value from the nrf_esb_write_payload function to whoever calls the send_packet_type_1 function. This return value can be used to detect errors return from the nrf_esb library, like if you try to use an invalid pipe, invalid packet length etc. 
    	// An equivalent and more intuitive way to write this line is:
    	// uint32_t error_code = nrf_esb_write_payload(&esb_payload);
    	// return error_code;
    }

    Best regards
    Torbjørn

  • Hi Torbjørn,

    Amazingly explained... Thanks a lot! 

    And for the literature proposal! I'll check out that to be more concentrated on plain C.
    I'll be doing experiments with modified versions of the examples and my own code but now I have the basic bricks. 

    I promise not bothering you with code related stuff. :) 

    Thanks again and have a great week. 
    Best regards, 

    Pablo. 

  • Hi Pablo

    I am happy to help. You have a great week too Slight smile

    Best regards
    Torbjørn

Related