This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

CC2642R: UART2 and Task Design

Part Number: CC2642R
Other Parts Discussed in Thread: SYSCONFIG

Hi Folks,

I have assigned the native UART interfaces to two devices (115200 and 9600, both 8N1) using UART2 driver through the System Configuration tool.  There are no other tasks running besides the "Serial" task. I have observed a substantial delays (+1 second) between receiving, processing, and then sending a reply over UART2 on my oscilloscope. This is pretty far from optimal and is on the verge of dysfunctional - I would like to reduce this delay by at least 800ms. 

Disabling "Display_printf" for debugging via Host Display will probably improve the runtime, however, considering the delay with only one serial peripheral - well, I can't imagine two will be any better.  I need to optimize the method of handling the serial peripherals. 

Solutions I am considering:

  1. (Current) A "Serial" Task with four events (COM1_RX, COM1_TX, COM2_RX, COM2_TX) with a callback and externally accessible "serial_write" function. 
    1. Event COMx_RX calls UART2_Read to read to a static local buffer.
    2. Event COMx_TX calls UART2_Write to write a static local buffer.
    3. "serial_write" copies a remote buffer to a static local buffer and then posts event COMx_TX depending on the port specified.
    4. callbackFxn_read passes the buffer pointer and length to an external function for parsing and posts event COMx_RX to re-engage the Serial RX.
  2. (Next Solution) A "COM1" and "COM2" task of equal priority, each demonstrating the following behavior.
    1. Task Loop (Serial Reading & Parsing)
      1. UART2_Read is called when entering task loop.
      2. Semaphore blocks until callbackFxn_read releases the semaphore.
      3. UART message is parsed locally. 
    2. Externally accessible "serial_write" function
      1. "serial_write" copies a remote buffer to a static local buffer and UART2_writes the copy immediately.

Things I have considered:

  • There is no system for queueing or listing messages - messages are handled as quickly as they are received or generated.
  • Clocks are used to (1) generate some messages and (2) generate timeout interrupts for missing messages, time is essential.
  • Three tasks (Simple Peripheral, COM1, and COM2) and will be communicating information between them - so threadsafety and latency need to be balanced.

I am looking for any and all suggestions to improve the performance here before I push forward in overall project development.

-Ken

  • Follow up:

    I tried the "Next Solution" involving a single task and single peripheral using a Semaphore to control task execution. I was able to trim the response time down to 280ms.

    I proceeded to disable the "Display Module" from within System Configuration to see how much it would work in the final system. On average, I observed a response time of 3.15 ms.

    Notes and Acknowledgments:

    1. I developed a Ti-RTOS Semaphore based on Sensor Core example code (for lack of better complete reference).
    2. System Idles more smoothly. Will attempt to run both serial tasks.
  • Hi Ken,

    Have you evaluated and expanded on the uart2callback or uart2echo example?  Can you share your UART2 parameters?  It seems that the TI Driver is timing out before servicing the received data and this can be improved through modifying the implementation.  You should not need to use separate tasks or the Sensor Controller.  

    Regards,
    Ryan

  • Hi Ryan,

    I evaluated the uart2callback and uart2echo examples and based my source on them. From a code legibility standpoint, I actually prefer using separate serial tasks with their own semaphores to regulate RX handling. Up to this point, I used a single "Serial" task to handle the device drivers which separated the device specific source from the driver itself. Separating each peripheral into its own task allows me to isolate all source related to that device within that task.

    I am happily not using the Sensor Controller as a UART emulator anymore and am generally pleased with the Semaphore Example included in the Sensor Controller examples (I am, at best, proficient at C - I am by no means an embedded professional). 

    Attached below if my UART Task and Callback Function. It is very lightweight - my only concern is whether or not I should implement a buffer. To the best of my knowledge, the external peripheral only transmits RX when a TX is sent first. I believe this relieves me of implementing a ring buffer, but I would appreciate a second opinion.

    void COM1_TaskFxn(UArg a0, UArg a1) {
        uint32_t          status = UART2_STATUS_SUCCESS;
    
        // Initialize the UART driver.
        UART2_Params_init(&UART_Params_COM1);
        UART_Params_COM1.readMode     = UART2_Mode_CALLBACK;
        UART_Params_COM1.readCallback = callbackFxn_COM1_read;
        UART_Params_COM1.dataLength   = UART2_DataLen_8;
        UART_Params_COM1.stopBits     = UART2_StopBits_1;
        UART_Params_COM1.parityType   = UART2_Parity_NONE;
        UART_Params_COM1.baudRate     = 115200;
    
        UART_COM1 = UART2_open(UART2_COM1, &UART_Params_COM1);
    
        if (UART_COM1 == NULL) {
            Display_printf(dispHost, 0, 0, "COM1 Task: Failure to Initialize\n");
            while(1);
        } else {
            Display_printf(dispHost, 0, 0, "COM1 Task: Initialized Successfully\n");
        }
    
        while(1) {
    
            uint8_t numBytesRead = 0;
    
            status = UART2_read(UART_COM1, &RX_COM1, 256, numBytesRead);
    
            if (status != UART2_STATUS_SUCCESS) {
                /* UART2_read() failed */
                Display_printf(dispHost, 0, 0, "COM1: Failed to Read\n");
                while (1);
            }
    
            // Wake up the OS task
            Semaphore_pend(Semaphore_handle(&COM1_TaskSemaphore), BIOS_WAIT_FOREVER);
    
            COM1_RX_Handler(RX_COM1, numBytesRead);
    
            memset(RX_COM1, 0x00, sizeof(RX_COM1));
        }
    }
    
    void callbackFxn_COM1_read(UART2_Handle handle, void *buffer, size_t count, void *userArg, int_fast16_t status)
    {
        if (status != UART2_STATUS_SUCCESS) {
            /* RX error occured in UART2_read() */
            Display_printf(dispHost, 0, 0, "COM1: Failed to Read\n");
        }
    
        // Wake up the OS task
        Semaphore_post(Semaphore_handle(&COM1_TaskSemaphore));
    
    }

    Best,

    Ken

  • In UART2_Mode_CALLBACK, readCallback (callbackFxn_COM1_read is returned when the buffer's size (256) has been filled (UART2_ReadReturnMode_FULL) or a timeout has occurred due to reception inactivity (UART2_ReadReturnMode_PARTIAL).  The Display TI Driver's effect on latency is surprising since it only transmits data and should not block other task operation.

    Regards,
    Ryan

  • I thought the same thing. Originally, I intended to use the UART (UART1?) driver, but I found that the newline based return required text mode. I posted about it here

    Anyway, more to the point I have definitely observed a few instances where a buffer would be a good idea in the unlikely event of heavy RX traffic. Do you have a recommended method for this such as a mailbox or list? Or should I just write a ringbuffer? I would like to minimize latency and, since no data is leaving this thread, I suspect thread safety is not an issue at the moment.

    Best,

    Ken

  • Thanks for linking your original thread.  There are certainly distinct differences between the UART1 and UART2 TI Drivers.  I have no experience to comment on whether a mailbox or list would be advantageous, generally the existing UART2 ring buffer has been sufficient.

    Regards,
    Ryan

  • Hm.

    As part of the peripheral initialization procedure, I sequentially request a series of values (associated with 12 separate write commands). I noticed that with 100ms spacing, I receive 2x "Unknown Responses" which correspond with the UART parsing function default case. When stepping through the function, these errors go away. 

    I am definitely enjoying the newline/automated return in UART2, but some kind of control is needed. For every Transmission, there is an expected response. My planned method is the create a bool "WaitingForRx" that is set true by the Serial Write operation. Subsequent writes are blocked until this is false. Since I have a good idea of the protocol timing, I should be able to add a clock that assigns it to false if no response is received after a set period of time. This should minimize the number of interruptions associated with this task.

    I am open to your suggestions for better ways to implement this send/listen system.

    Best,

    Ken

  • I have noticed that sometimes the buffer does not return with values (or at least with all of them). To diagnose this, I added a some debug code to check the returned values of the read operation:

    memset(RX_Buff, 0x00, sizeof(RX_Buff));
    size_t numBytesRead = 0;
    Display_printf(dispHost, 0, 0, "COM1(i)(%u): Read %lu bytes", RefreshValue,(long unsigned int)numBytesRead);
    
    // Read each byte, callback writes byte to Riung
    status = UART2_read(uart, RX_Buff, 32, &numBytesRead);
    Display_printf(dispHost, 0, 0, "COM1(R)(%u): Read %lu bytes", RefreshValue,(long unsigned int)numBytesRead);
    
    if (status != UART2_STATUS_SUCCESS) {
        /* UART2_read() failed */
        Display_printf(dispHost, 0, 0, "COM1: Failed to Read\n");
        while (1);
    }
    
    // Wake up the OS task
    Semaphore_pend(Semaphore_handle(&com1TaskSemaphore), BIOS_WAIT_FOREVER);
    Display_printf(dispHost, 0, 0, "C0M1(S)(%u): Read %lu bytes", RefreshValue,(long unsigned int)numBytesRead);
    RX_Handler(RX_Buff, numBytesRead);
    WaitingForRX = false;

    What I see in the console is baffling:

    COM1(i)(0): Read 0 bytes
    COM1(R)(0): Read 0 bytes
    COM1(S)(1): Read 0 bytes
    // No Error
    
    COM1(i)(1): Read 0 bytes
    COM1(R)(1): Read 0 bytes
    COM1(S)(2): Read 0 bytes
    // No Error
    
    COM1(i)(2): Read 0 bytes
    COM1(R)(2): Read 0 bytes
    COM1(S)(3): Read 0 bytes
    // No Error
    
    COM1(i)(3): Read 0 bytes
    COM1(R)(3): Read 0 bytes
    COM1(S)(4): Read 0 bytes
    // Error
    
    COM1(i)(4): Read 0 bytes
    COM1(R)(4): Read 7 bytes
    COM1(S)(4): Read 7 bytes
    // Error
    
    COM1(i)(4): Read 0 bytes
    COM1(R)(4): Read 0 bytes
    COM1(S)(5): Read 0 bytes
    // Error
    
    COM1(i)(5): Read 0 bytes
    COM1(R)(5): Read 0 bytes
    COM1(S)(6): Read 0 bytes
    // No Error
    
    COM1(i)(6): Read 0 bytes
    COM1(R)(6): Read 0 bytes
    COM1(S)(7): Read 0 bytes
    // Error
    
    COM1(i)(7): Read 0 bytes
    COM1(R)(7): Read 4 bytes
    COM1(S)(7): Read 4 bytes
    // No Error
    
    COM1(i)(7): Read 0 bytes
    COM1(R)(7): Read 0 bytes
    COM1(S)(8): Read 0 bytes
    // No Error
    
    COM1(i)(8): Read 0 bytes
    COM1(R)(8): Read 0 bytes
    COM1(S)(9): Read 0 bytes
    // Error
    
    COM1(i)(9): Read 0 bytes
    COM1(R)(9): Read 4 bytes
    COM1(S)(9): Read 4 bytes
    // Error
    
    COM1(i)(9): Read 0 bytes
    COM1(R)(9): Read 0 bytes
    COM1(S)(10): Read 0 bytes
    // No Error
    
    COM1(i)(10): Read 0 bytes
    COM1(R)(10): Read 0 bytes
    COM1(S)(11): Read 0 bytes
    // No Error
    
    COM1(i)(11): Read 0 bytes
    COM1(R)(11): Read 0 bytes
    COM1(S)(12): Read 0 bytes
    // No Error

    From my own code, there is zero explanation of why the Bytes Written is frequently zero. I noticed some interesting behavior:

    1. 32 bytes are read before the first buffer truncation.
    2. Bytes Read = 7 occurs after (1), this is an accurate byte count.
    3. 32 bytes are read by the point of the next buffer truncation.
    4. Bytes Read = 4 occurs after (2) , this is an accurate byte count.
    5. 32 bytes are read by the point of the next buffer truncation.
    6. Bytes Read = 4 occurs after (3), this is an accurate byte count.
    7. Last 3 Reads do not use up 32 bytes.

    This makes it very clear to me that I am running into a 32 byte buffer inside the UART2 driver itself. This is DISTINCT from the RX_Buffer which stores up to 256 values. So now the most important question of the hour, what can be done about this problem?

    My gut instinct is to use a single-byte mini-buffer to store data read by the UART2 driver, then use the read callback function to insert this value into a ring buffer. If the termination sequence is detected, then the buffer is passed to the RX_handler function.

    This solution entirely defeats the automated termination of the multi-byte reads by the UART2 driver. I see the "UART2_flushRx(uart_handle)" function in the API seems to match what I need - and it seems to work. 

    Best,

    Ken

  • Hey Ken,

    I apologize for the delay, I am trying to contact a TI Driver development expert to comment on the behavior you have described.

    Regards,
    Ryan

  • Hi Ryan,

    Thanks for the update. The strange behavior of the number of bytes read worth looking into. I have also noticed that attempting to read more than 32 bytes from the buffer is not possible. While this was acknowledged in the original UART driver, it seems to apply also the UART2.

    // GLOBAL VARIABLES
        // Read Buffers
        const  size_t   RX_Size = 256;
        static uint8_t  RX_Read [RX_Size];
    
    // Task Function
        memset(RX_Read, 0x00, RX_Size);
        
        status = UART2_read(COM_1, &RX_Read, RX_Size, NULL);
    
        if (status != UART2_STATUS_SUCCESS) {
            /* UART2_read() failed */
            Display_printf(dispHost, 0, 0, "COM1: Failed to Read\n");
        }
        
        // Wake up the OS task
        Semaphore_pend(Semaphore_handle(&com1TaskSemaphore), BIOS_WAIT_FOREVER);
        vesc_RX_Handler(RX_Read);
        UART2_flushRx(COM_1);

    I compared the UART2 RX_Read buffer to a ring buffer developed for UART1 and observed a difference in buffer contents and length.  I am curious what the TI Driver development expert thinks.

    Best,

    Ken

  • Hi Ryan,

    I am new to the SysConfig tool and just realized that in the configuration of the UART2 driver I can SPECIFY the RX and TX buffer length.

    I am going to give this a shot and see if it works. I still think something is up with the driver if the number of bytes read is incorrect.

    FOLLOW UP:

    This did not fix the problem on the 4.40 SDK. However, I updated to 5.10 and it suddenly appears to be receiving more bytes that APPEAR to be correct! Woooo!

    The number of bytes ready by UART_read() is still zero. So there is definitely a bug there.

  • Hi Ken,

    I got some feedback from the TI Driver Team which I wanted to share:

    "I am a bit surprised to see newline handling in UART2 since we intentionally dropped any feature related to the actual payload content in UART2.

    In callback mode, the call to UART2_read() returns immediately after setting up the UART HW and DMA transaction and reading any already available data out of the ring buffer. Unless we have bytes already stored in the ring-buffer that are immediately copied back, that UART2_read() call will always write 0 to numBytesRead. Which makes sense since at that point, nothing has been read out.

    The total number of bytes read is made available in the callback function as the count argument. That is what should be printed. However this would have been a problem since it is executed in HWI context and Display_printf() is not available then.

    We advise against using callback mode to emulate blocking mode. Blocking mode is designed for their use case and development sill be significantly simpler for them if they use it. Callback mode is for users that need asynchronous operation."

    I hope this helps,
    Ryan


  • Thank you Ryan - I have learned a lot from this process and I appreciate your help. Please send my thanks on to the Driver Team - I now understand the behavior especially with regard to printing. 

    Best,

    Ken