boarchuz / HULP

ESP32 ULP Coprocessor Helper
MIT License
180 stars 17 forks source link

How to loop through a byte array and set the gpio #30

Open morcibacsi opened 7 months ago

morcibacsi commented 7 months ago

I was the one who was asking on discord on how to bitbang a 125 kbit/sec bus. As I mentioned one byte is transferred as 10 bits because after the 4th and 8th bits there is a bit stuffed which is the opposite of the previous one. To transmit on the bus we need to wait at least 16 bits of high state (16 * 8us). After that we can start transferring, but if we send a high state and we read back a low state on the rx line we need to stop transferring immediately as someone else also is transmitting with higher priority. Also we don't care if we sent a low, but receive a high, that is fine, we can continue.

You suggested to check the ULP coprocessor. After doing some research I found your awesome library. I checked the examples but some things aren't obvious for the first (few) reads. I settled with the ULPAPADriver as that seems the most complex one which has most of the parts I need. Based on that I prepared a library for my use-case, but I am kind of stuck on how to transmit the bits of a byte. I think I managed to write the part where I wait to start transmitting, but I just can't understand how to loop through my data array and write the bits onto the gpio.

Can you take a look and give me a hint how to proceed further?

Here is the code on the main CPU

#include <arduino.h>
#include <hulp_arduino.h>
#include <stdint.h>
#include "UlpVanDriver.h"

#define VAN_RX_PIN GPIO_NUM_12
#define VAN_TX_PIN GPIO_NUM_13

ULPVANData ulpdata[40]; // 255 bits
UlpVanDriver ulpVanDriver(ulpdata, VAN_RX_PIN, VAN_TX_PIN);

void setup()
{
    ulpVanDriver.InitGpio();
}

void loop()
{
    //I do the bitstuffing here
    ulpVanDriver.ConvertByteToUlpData(0x0E, &ulpdata[0]);
    ulpVanDriver.ConvertByteToUlpData(0x5E, &ulpdata[1]);
    ulpVanDriver.ConvertByteToUlpData(0x4C, &ulpdata[2]);

    ulpVanDriver.ConvertByteToUlpData(0x20, &ulpdata[3]);
    ulpVanDriver.ConvertByteToUlpData(0x1E, &ulpdata[4]);

    ulpVanDriver.ConvertByteToUlpData(0x8A, &ulpdata[5]);
    ulpVanDriver.ConvertByteToUlpData(0x50, &ulpdata[6]);
    //EOD signaled with e-manchester violation
    ulpdata[6].manchester_bit9 = 0;

    //2 bit ACK + End of frame
    ulpdata[7].lower_nibble = 0xFF;
    ulpdata[7].manchester_bit4 = 1;
    ulpdata[7].upper_nibble = 0xFF;
    ulpdata[7].manchester_bit9 = 1;

   //and signal the ULP coprocessor that it can transfer
    ulpVanDriver.SendFrame(8);
    delay(2000);
}

And here is my ULP code:

#define ULPVANData RTC_SLOW_ATTR WORD_ALIGNED_ATTR ulp_van_byte_t

struct ulp_van_byte_t {
    uint32_t lower_nibble    : 4;  // 0-3
    uint32_t manchester_bit4 : 1;  // 4
    uint32_t upper_nibble    : 4;  // 5-8
    uint32_t manchester_bit9 : 1;  // 9

    uint32_t command_type    : 5;  // 10-14   /*!< 1: normal frame, 2: query frame, 3: ack only */
    uint32_t transmit_data   : 1;  // 15      /*!< Bit for SoC to flag ULP to transmit */

    uint32_t upper1          : 16; // 16-31    /*!< 16 bits inaccessible to ULP */

    uint32_t message_length : 8;   // 0-7      /*!< length of the message */
    uint32_t unused         : 8;   // 8-15     /*!<  */

    uint32_t upper2         : 16;  // 16-31    /*!< 16 bits inaccessible to ULP */
};

UlpVanDriver::UlpVanDriver(ulp_van_byte_t *vanData, gpio_num_t rxPin, gpio_num_t txPin)
{
    _vanData = vanData;
    _rxPin = rxPin;
    _txPin = txPin;

    #define VAN_DATA_ARR_OFFSET (((uint32_t)_vanData - ((uint32_t)RTC_SLOW_MEM)) / 4)

    enum {
        LABEL_POLL,
        LABEL_CHECK_IDLE,
        LABEL_TX_BIT,
    };

    const ulp_insn_t ulpProgram[] = {

    //Reset Pointer
    I_MOVI(R1, VAN_DATA_ARR_OFFSET),

    //Set a register to 0 for use with I_GET and I_PUT
    I_MOVI(R2, 0),

    //Wait here until SoC sets flag to transmit
    I_LD(R0,R1,0),
    I_BL(-1, 1 << 15),

    //Clear the flag
    I_ANDI(R0,R0,0x7FFF),
    I_ST(R0,R1,0),

    M_LABEL(LABEL_POLL),
        //While the GPIO is low, continue polling the pin
        I_GPIO_READ(_rxPin),
        I_BL(-1,1),

        //We have a high state, "move into the middle" of the bit
        M_DELAY_US_1_10(4),

        //Wait for 16 bits of high state to ensure the bus is idle (16 * 8us)
        I_STAGE_RST(),
        M_LABEL(LABEL_CHECK_IDLE),
            I_STAGE_INC(1),

            //Wait for 8us to "move into the next bit"
            M_DELAY_US_1_10(8),

            I_GPIO_READ(_rxPin),
            //If the GPIO is low, we need to wait for the bus to become idle again
            M_BL(LABEL_POLL, 1),

            M_BSLE(LABEL_CHECK_IDLE, 16),
            //We have 16 bits of high state, so we can start writing the data

        I_STAGE_RST(),
        M_LABEL(LABEL_TX_BIT),
            //this is where I should implement the loop which shifts out the bits based on the data in _vanData but I am not sure how to do that conditionally
            //vanData[0].message_length contains how many bytes my message have

            I_GPIO_SET(_txPin, 1),
            //I_DELAY() //should we wait a few ns to let the pin settle?

            //read back the GPIO to ensure it is high
            I_GPIO_READ(_rxPin),
            //If the GPIO is low, but we sent high, we stop the transfer immediately and wait for the bus to become idle again
            M_BL(LABEL_POLL, 1),

           //bit transmit success, go back to LABEL_TX_BIT

    };

    size_t programSize = sizeof(ulpProgram) / sizeof(ulp_insn_t);
    ESP_ERROR_CHECK(ulp_process_macros_and_load(0, ulpProgram, &programSize));
    // ESP_ERROR_CHECK(ulp_set_wakeup_period(x);
    ESP_ERROR_CHECK(ulp_run(0));
}
boarchuz commented 7 months ago

Hi @morcibacsi, As you've probably found, you can't write a 'variable' to a register, so you need to have both GPIO_SET_0() and GPIO_SET_1() instructions somewhere in your program, and jump to either one based on the value of the bit you're transmitting. To simplify the logic for the ULP, I find it's best to have everything in the uppermost bits, then you can test BIT15 easily (assuming your protocol is MSB first). It looks something like this:


    I_MOVI(R0, 0xA3 << 8), // Suppose R0 has the byte to be transmitted, in the uppermost bits

    I_STAGE_RST(),
    M_LABEL(LOOP),
      M_BGE(TX_1, (1 << 15)), // If BIT15 is 1 then TX_1, else continue to TX_0
      // Else transmit 0
      I_GPIO_SET(pin, 0),
    M_LABEL(LOOP_NEXT),
      I_LSHI(R0, R0, 1), // Move next bit into BIT15
      I_STAGE_INC(1),
      M_BSLE(LOOP, 8),
    // Done
    I_HALT(),

   M_LABEL(TX_1),
      I_GPIO_SET(pin, 1),
      M_BX(LOOP_NEXT),

You will have a lot to keep track of in your loops (pointer to data, total length, current index, actual pin level, as well as timekeeping) so you may need to spill some registers into memory to make it possible. At 125khz you should have plenty of time.

In fact, I've just realised that R0 will be clobbered when you read the pin level, so the above logic (which uses M_BGE which relies on R0) will not work.

You might want to review your data array structure too. Is there a reason why the length is stored with each data byte?

Here's something I've thrown together, untested and probably full of bugs, to give you an idea of how I might approach this:

    #define STUFFED_VAL(byte) (((byte) & 0xF0) << 2) | ((~(byte) & 0x10) << 1) | (((byte) & 0x0F) << 1) | ((~(byte) & 0x01) << 0)

    // Prepare data structure
    static RTC_SLOW_ATTR ulp_var_t ulp_command[1 + 8];
    // The first item (ulp_command[0]) is metadata, which is set last
    // Data
    // Shift these such that the MSB is in bit15, which simplifies logic for the ULP
    ulp_command[1 + 0].val = (STUFFED_VAL(0x0E)) << 6;
    ulp_command[1 + 1].val = (STUFFED_VAL(0x5E)) << 6;
    ulp_command[1 + 2].val = (STUFFED_VAL(0x4C)) << 6;
    ulp_command[1 + 3].val = (STUFFED_VAL(0x20)) << 6;
    ulp_command[1 + 4].val = (STUFFED_VAL(0x1E)) << 6;
    ulp_command[1 + 5].val = (STUFFED_VAL(0x8A)) << 6;
    ulp_command[1 + 6].val = (STUFFED_VAL(0x50)) << 6;
    // EOF
    ulp_command[1 + 7].val = 0x3FF << 6;

    // Now set metadata (flag + length) to begin
    ulp_command[0].val = (1 << 15) | 8;

    const ulp_insn_t program[] = {
        M_LABEL(LBL_WAIT_BEGIN),
          // Reset Pointer
          I_MOVI(R1, VAN_DATA_ARR_OFFSET),
          // Wait here until SoC sets flag to transmit
            I_LD(R0,R1,0),
            I_BL(-1, 1 << 15),

        // Clear the flag, leaving only the length, which we keep in R3
        I_ANDI(R3,R0,0x7FFF),
        I_ST(R3,R1,0),

        // ...
        // wait bus idle (omitted here)
        // ...

        M_LABEL(LBL_NEXT_BYTE),
            // Increment pointer, load next byte, reset stage (bit counter)
            I_ADDI(R1, R1, 1),
            I_LD(R2, R1, 0),
            I_STAGE_RST(),

            M_LABEL(LBL_NEXT_BIT),
                #if 1
                    // Shift out the MSB (BIT15). This both sets up a branch based on the value of this bit AND prepares the next bit into BIT15. 
                    // I *think* this sets ALU overflow flag but can't recall off the top of my head. If this isn't working, use the alternate logic block below.
                    I_LSHI(R2, R2, 1),
                    M_BXF(LBL_1BIT),
                #else
                    I_ANDI(R0, R2, (1 << 15)),
                    I_LSHI(R2, R2, 1),
                    M_BGE(LBL_1BIT, (1 << 15)),
                #endif
                    // Else 0 bit
                    I_GPIO_OUTPUT_EN(_txPin),
                    M_DELAY_US_1_10(8), // TODO: Reduce this delay due to loop overhead
                    M_BX(LBL_LOOP_BIT_DONE),

                M_LABEL(LBL_1BIT),
                    I_GPIO_OUTPUT_DIS(_txPin),
                    // You *may* need to delay here depending on bus capacitance and value of pullup resistors.
                    // Note there is already ~1uS between these instructions due to the relatively low speed of the ULP and memory.
                    // A loop would be nice but no registers are free so I'll just unroll the loop a few times.
                    // TODO: Decide what to do in case of bus contention. Notify SOC? Keep retrying?
                        M_DELAY_US_1_10(1),
                        I_GPIO_READ(_txPin),
                        M_BGE(LBL_WAIT_BEGIN, 1),
                        M_DELAY_US_1_10(1),
                        I_GPIO_READ(_txPin),
                        M_BGE(LBL_WAIT_BEGIN, 1),
                        M_DELAY_US_1_10(1),
                        I_GPIO_READ(_txPin),
                        M_BGE(LBL_WAIT_BEGIN, 1),
                        M_DELAY_US_1_10(1),
                        I_GPIO_READ(_txPin),
                        M_BGE(LBL_WAIT_BEGIN, 1),
                    // TODO: adjust the above, including delay durations, until it conforms with expected timing

                M_LABEL(LBL_LOOP_BIT_DONE),
                    I_STAGE_INC(1),
                    M_BSLE(LBL_NEXT_BIT, 10),
                    // Byte complete, check if any more bytes remaining.
                    I_SUBI(R3, R3, 1),
                    M_BXZ(LBL_DONE), // Done if R3 (remaining) == 0
                    // Continue to next byte
                    M_BX(LBL_NEXT_BYTE),

            M_LABEL(LBL_DONE),
                // ...
                I_HALT(),

Lastly, I gather this is an open-drain bus, so you will need to ensure the TX pin is configured in this mode, or use an OUTPUT_ENABLE and OUTPUT_DISABLE approach (instead of SET(0) and SET(1)) as I've done above. Lastly lastly I don't know the function of 'command_type' and so have ignored it.

morcibacsi commented 7 months ago

Wow, thank you very much, you are awesome!

Currently I can't test with a real bus, so I hooked up the esp32 to a logic analyzer to check the output, and I can say that it is almost working. Somehow an extra bit is put on the line which corrupts the second byte (and therefore everything after that), but the timings seems fine. The STUFFED_VAL works well, maybe the loop or the bit shift is wrong? I am still investigating this. Here is a capture with the logic analyzer. The first byte is good, but the second isn't. Before that I marked the extra bit with the orange arrow which shouldn't be there. In that case the value would be fine. (5E instead of 27)

The dots are the sampling points, the x marks the stuffed bits.

image

You might want to review your data array structure too. Is there a reason why the length is stored with each data byte?

There wasn't I barely had an idea what I'm doing :)

lastly I don't know the function of 'command_type' and so have ignored it.

There is a scenario when another node transmits the beginning of the message (example 0E 5E 4C) and we want to fill the rest of the packet. But this would require a different logic to read what is contained in the incoming first 3 bytes. Currently I don't need this but later I'd like to extend the code to handle this as well. That was the reason for the command_type.

And here is the adjusted code based on your comments.

    const ulp_insn_t ulpProgram[] = {
        M_LABEL(LBL_WAIT_BEGIN),
          // Reset Pointer
          I_MOVI(R1, VAN_DATA_ARR_OFFSET),
          // Wait here until SoC sets flag to transmit
            I_LD(R0,R1,0),
            I_BL(-1, 1 << 15),
        // Clear the flag, leaving only the length, which we keep in R3
        I_ANDI(R3,R0,0x7FFF),
        I_ST(R3,R1,0),

    M_LABEL(LABEL_POLL),
        //While the GPIO is low, continue polling the pin
        I_GPIO_READ(_rxPin),
        I_BL(-1,1),

        //We have a high state, "move into the middle" of the bit
        M_DELAY_US_1_10(4),

        //Wait for 16 bits of high state to ensure the bus is idle (16 * 8us)
        I_STAGE_RST(),
        M_LABEL(LABEL_CHECK_IDLE),
            I_STAGE_INC(1),

            //Wait for 8us to "move into the next bit"
            M_DELAY_US_1_10(8),

            I_GPIO_READ(_rxPin),
            //If the GPIO is low, we need to wait for the bus to become idle again
            M_BL(LABEL_POLL, 1),

            M_BSLE(LABEL_CHECK_IDLE, 16),
            //We have 16 bits of high state, so we can start writing the data

        M_LABEL(LBL_NEXT_BYTE),
            // Increment pointer, load next byte, reset stage (bit counter)
            I_ADDI(R1, R1, 1),
            I_LD(R2, R1, 0),
            I_STAGE_RST(),

            M_LABEL(LBL_NEXT_BIT),
                //#if 1
                    // this method doesn't seem to work

                    // Shift out the MSB (BIT15). This both sets up a branch based on the value of this bit AND prepares the next bit into BIT15.
                    // I *think* this sets ALU overflow flag but can't recall off the top of my head. If this isn't working, use the alternate logic block below.
                    //I_LSHI(R2, R2, 1),
                    //M_BXF(LBL_1BIT),
                //#else
                    I_ANDI(R0, R2, (1 << 15)),
                    I_LSHI(R2, R2, 1),
                    M_BGE(LBL_1BIT, (1 << 15)),
                //#endif
                    // Else 0 bit
                    I_GPIO_SET(_txPin, 0),
                    M_DELAY_US_1_10(4), // TODO: Reduce this delay due to loop overhead
                    M_BX(LBL_LOOP_BIT_DONE),

                M_LABEL(LBL_1BIT),
                    I_GPIO_SET(_txPin, 1),
                    // You *may* need to delay here depending on bus capacitance and value of pullup resistors.
                    // Note there is already ~1uS between these instructions due to the relatively low speed of the ULP and memory.
                    // A loop would be nice but no registers are free so I'll just unroll the loop a few times.
                    // TODO: Decide what to do in case of bus contention. Notify SOC? Keep retrying?
                        M_DELAY_US_1_10(1),
                        I_GPIO_READ(_txPin),
                        M_BGE(LBL_WAIT_BEGIN, 1),

                        M_DELAY_US_1_10(1),
                        I_GPIO_READ(_txPin),
                        M_BGE(LBL_WAIT_BEGIN, 1),
/*
                        M_DELAY_US_1_10(1),
                        I_GPIO_READ(_txPin),
                        M_BGE(LBL_WAIT_BEGIN, 1),
                        M_DELAY_US_1_10(1),
                        I_GPIO_READ(_txPin),
                        M_BGE(LBL_WAIT_BEGIN, 1),
*/
                    // TODO: adjust the above, including delay durations, until it conforms with expected timing

                M_LABEL(LBL_LOOP_BIT_DONE),
                    I_STAGE_INC(1),
                    M_BSLE(LBL_NEXT_BIT, 10),
                    // Byte complete, check if any more bytes remaining.
                    I_SUBI(R3, R3, 1),
                    M_BXZ(LBL_DONE), // Done if R3 (remaining) == 0
                    // Continue to next byte
                    M_BX(LBL_NEXT_BYTE),

            M_LABEL(LBL_DONE),
                // ...
                I_GPIO_SET(_txPin, 1),
                M_BX(LBL_WAIT_BEGIN)
                //I_HALT(),
    };
boarchuz commented 7 months ago

Hi @morcibacsi, It looks like the extra bit is due to the overhead at the end of each byte as the ULP checks length, jumps to LBL_NEXT_BYTE, loads the next byte, initialises the loop, etc. If things are shuffled around and the 10th bit is given special treatment, you should be able to dial in the correct timings with something like this:

     M_LABEL(LBL_NEXT_BYTE),
            // Decrement remaining bytes until done
            I_SUBI(R3, R3, 1),
            M_BXF(LBL_DONE),
            // Increment pointer, load next byte, reset stage (bit counter)
            I_ADDI(R1, R1, 1),
            I_LD(R2, R1, 0),
            I_STAGE_RST(),

        M_LABEL(LBL_NEXT_BIT),
            I_STAGE_INC(1),
            // Check if 1 bit
            I_ANDI(R0, R2, (1 << 15)),
            I_LSHI(R2, R2, 1),
            M_BGE(LBL_1BIT, (1 << 15)),
            // Else 0 bit
            I_GPIO_SET(_txPin, 0),
            // TODO: determine how much DELAY required before and after M_BSGE in order to achieve the correct timings
            M_DELAY_US_1_10(1),
            // Check if this is the 10th bit
            M_BSGE(LBL_NEXT_BYTE, 10),
            // Else delay a little more...
            M_DELAY_US_1_10(2),
            M_BX(LBL_NEXT_BIT),

        M_LABEL(LBL_1BIT),
            I_GPIO_SET(_txPin, 1),
                #define M_CHECK_BUS_HI() I_GPIO_READ(_txPin), M_BL(LBL_WAIT_BEGIN, 1)
                // TODO: determine how many M_CHECK_BUS_HI to insert before and after M_BSGE in order to achieve the correct timings
                M_CHECK_BUS_HI(),
                    // Check if this is the 10th bit
                    M_BSGE(LBL_NEXT_BYTE, 10),
                    // Not the 10th bit so delay while checking bus
                M_CHECK_BUS_HI(),
                M_CHECK_BUS_HI(),
                M_CHECK_BUS_HI(),
                M_BX(LBL_NEXT_BIT),

        M_LABEL(LBL_DONE),
            // ...
            I_GPIO_SET(_txPin, 1),
            M_BX(LBL_WAIT_BEGIN)

Also ensure that the TX pin is initialised as INPUT_OUTPUT. The "M_BGE(LBL_WAIT_BEGIN, 1)" previously was incorrect and should have been triggering every time. It should be using M_BL instead (ie. branch if the level is unexpectedly low). I note you're still using I_GPIO_SET(1) to drive the bus high, so I'm confused and a little concerned about the configuration here. Is this just for testing? Is it going to be open-drain? What happens if two transmitters drive the line high and low at the same time?

morcibacsi commented 7 months ago

Ahh man, thank you, it would have taken me a fair amount of time to realize what is the problem. Now with a bit of tweaking the delays it works! image

This bus uses a twisted pair of wires (like CAN bus) and uses a differential signal pair. In fact CAN transceivers can be used to interface it, so the pin of the esp32 will be connected to a CAN transceiver's TX and RX pins. Currently I am using only a logic analyzer connected to a gpio of the esp32 to test the output, but in the following days I will try to test on real hardware. After that I will get back with the results and the cleaned code. Also planning to open source the finished driver, I hope you don't mind if I'll mention your name.

Thanks again, you are really awesome!

PS: Just out of curiosity, what do you think would it be possible to write a CAN driver with the ULP ? I know the esp32 has a built in driver for it, the question is strictly theoretical.

boarchuz commented 7 months ago

Nice work! I've never used CAN but from what I've read it supports frequencies as low as 100kHz (or lower?) so, as long as everything else on the bus is happy going this slow, it should be possible.

morcibacsi commented 7 months ago

Hi @boarchuz I finished the library, and made it available here: https://github.com/morcibacsi/esp32_ulp_van_tx thanks a million times again for your help!

For those who read this later, click here for the complete code ```cpp const ulp_insn_t ulpProgram[] = { M_LABEL(LBL_WAIT_BEGIN), // Reset Pointer I_MOVI(R1, VAN_DATA_ARR_OFFSET), // Wait here until SoC sets flag to transmit I_LD(R0,R1,0), I_BL(-1, 1 << 15), // Clear the flag, leaving only the length, which we keep in R3 I_ANDI(R3,R0,0x7FFF), I_ST(R3,R1,0), M_LABEL(LABEL_WAIT_FOR_BUS_HI), // While the GPIO is low, continue polling the pin I_GPIO_READ(_rxPin), I_BL(-1,1), // We have a high state, "move into the middle" of the bit M_DELAY_US_1_10(4), // Wait for 16 bits of high state (EOF + IFS period) to ensure the bus is free (16 * 8us) I_STAGE_RST(), M_LABEL(LABEL_CHECK_FREE), I_STAGE_INC(1), // Wait for 8us to "move into the next bit" M_DELAY_US_1_10(8), // If the GPIO is low, we need to wait for the bus to become free again I_GPIO_READ(_rxPin), M_BL(LABEL_WAIT_FOR_BUS_HI, 1), M_BSLE(LABEL_CHECK_FREE, 16), // We have 16 bits of high state, so we can start writing the data M_LABEL(LBL_NEXT_BYTE), // If this is the last byte then go to LBL_DONE (Decrement remaining bytes until done) I_SUBI(R3, R3, 1), M_BXF(LBL_DONE), // Increment pointer, load next byte, reset stage (bit counter) I_ADDI(R1, R1, 1), I_LD(R2, R1, 0), I_STAGE_RST(), M_LABEL(LBL_NEXT_BIT), I_STAGE_INC(1), // If bit == 1 then go to LBL_1BIT I_ANDI(R0, R2, (1 << 15)), I_LSHI(R2, R2, 1), M_BGE(LBL_1BIT, (1 << 15)), // Else 0 bit I_GPIO_SET(_txPin, 0), I_DELAY(10), // If this is the 10th bit then go to LBL_NEXT_BYTE M_BSGE(LBL_NEXT_BYTE, 10), // Else delay a little more... and go to LBL_NEXT_BIT I_DELAY(12), M_BX(LBL_NEXT_BIT), M_LABEL(LBL_1BIT), I_GPIO_SET(_txPin, 1), // If the bus is low then we lost arbitration and go to LBL_WAIT_BEGIN M_CHECK_BUS_HI(), M_CHECK_BUS_HI(), // If this is the 10th bit go to LBL_NEXT_BYTE M_BSGE(LBL_NEXT_BYTE, 10), // Not the 10th bit so delay while checking bus M_CHECK_BUS_HI(), M_BX(LBL_NEXT_BIT), M_LABEL(LBL_DONE), I_GPIO_SET(_txPin, 1), M_BX(LBL_WAIT_BEGIN) }; ```

Only one last question. I mentioned that in the future I am going to need another frame type (was command_type in my first post). In this case I need to wait for a specific value (green part below) on the bus (to follow the previous example: 0x0E, 0x5E, 4C) and when I detect those values I need to fill the rest of the data (0x20, 0x1E, 0x8A, 0x50).

So basically the red part: image

What my theory is: I wait for the bus is free, so the start of the code is the same as before until this line:

  ...
                M_BSLE(LABEL_CHECK_FREE, 16),
                // We have 16 bits of high state, so we can start writing the data

            M_LABEL(LBL_NEXT_BYTE),
  ...

But here I would need a branching logic based on the command_type so either do the same as before, or start comparing the bus state with the bit values in ulp_command[] until the third byte (sticking to the example: 0x0E, 0x5E, 0x4C) if all bits are the same, then the rest of the code is the same as before (writing the values from ulp_command[3] until the length). Hmm.. thinking of it: actually I can stop comparing on the first difference between the bus state and the bits in ulp_command[0-2] as the message isn't the one I need to listen for.

So after the long introduction: how can I send through the command_type parameter? Do you see any problems of why the above wouldn't work?