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.

IWR6843AOPEVM: UNIFLASH Python Script / UART Flash

Part Number: IWR6843AOPEVM
Other Parts Discussed in Thread: IWR6843AOP, , MMWAVEICBOOST, CC2652RB, UNIFLASH, IWR6843

Dear Support,

I am developing a custom sensor using an IWR6843AOP and an ESP32 as the host MCU. My goal is to be able to perform firmware updates on the IWR6843AOP directly from the ESP32, without a direct PC connection.

 

Hardware Setup

 

  • IWR6843AOPEVM

  • MMWAVEICBOOST (DIP switches set correctly)

  • ESP32 (Host MCU)

  • CC2652RB (Also part of the final design)

 

Context & What Works

 

I have successfully implemented a remote flashing process for the CC2652RB via its UART interface (using a TCP-to-serial connection and a custom script based on cc2538-bsl to toggle BSL/RST lines). I am now trying to achieve a similar remote flashing capability for the IWR6843AOP.

Here is the current status of the IWR6843AOP integration:

  1. Connections: The ESP32 is connected to the IWR6843AOP's UART interface (RS232 TX/RX @ 115200 for configuration and MSSLogger @ 921600 for data). The MMWAVEICBOOST handles the SOP pin modes.

  2. Direct Flashing: I can successfully flash the IWR6843AOP labs using Uniflash on a PC with a simple USB-UART TTL adapter connected directly to the RX/TX/GND pins.

  3. Runtime Operation: After flashing, my ESP32 (via its TCP-to-UART connection) can correctly send CFG commands and receive responses from the IWR6843AOP.

  4. Data Logging: The ESP32 can also receive the data output from the MSS Logger port.

 

The Problem

 

My only remaining issue is initiating the flash process from the ESP32 itself. I cannot get the IWR6843AOP to enter its bootloader mode to accept new firmware when the process is controlled by the MCU, even though it works perfectly from Uniflash.

I have tried automating this with custom scripts and CLI tools, and I have reviewed the boot flow documentation, paying close attention to the timing for the UART break signal. However, all attempts to flash via the ESP32 have failed.

 

My Question

 

Is there a specific, low-level sequence or a critical timing requirement for the UART break signal that I must implement on the ESP32 to correctly trigger the IWR6843AOP's bootloader?

Any documentation, examples, or guidance on this specific procedure would be extremely helpful.

Note: A Secondary Bootloader (SBL) is not a viable option for my use case, as I need the ability to flash the primary application binary directly and persistently.

This remote flashing capability is a critical requirement before I can finalize my custom board design. I know this must be possible, as commercial products like the Aqara FP2 use the same chip and support firmware updates.

Thank you for your assistance.

  • Hello.

    If you are using UniFlash successfully, you could scope the UART lines to see what messages are being sent from the PC to see if your transactions from the ESP32 are identical.  It may be that you are missing some transaction to initiate the bootloader flow.  Also, can you confirm that you don't have SFLASH connected to the device?

    Sincerely,

    Santosh

  • Hi Santosh - I actually didnt have connected SFLASH to the device.

    I am using two components to flash it via ESP32 right now:

    https://esphome.io/components/usb_uart/ <- This by bridging the USB Connection from the ESP32 directly to the IWR6843

    and this one: https://github.com/duong2003/IWR6843AOP-Downloader <- To flash it via the python script.

    Both as well as the Uniflash tool works that way.

    When I modify the script to send the .bin file via the socket interface - I can´t get into the bootloader mode,

    and flash the file, although I use a dedicated UART Break signal (RS232 RX holding to low via ESP32 Pin for 100ms ) 


    And usually should work, since I am using the same PIN connections on my ESP32 board.

    RX -> TX

    TX -> RX

    as well as the MSS Logger as dedicated pin

    and RESET as a pin.


  • btw if u want to test it out by your own, I can also share my ESPHome script. 

  • And this is my ESPHome script to test:


    substitutions:
    device_name: iwr6843
    friendly_name: IWR6843
    esphome:
    name: ${device_name}
    friendly_name: ${friendly_name}
    # libraries:
    # - "ESPAsyncWebServer"
    # platformio_options:
    # build_flags:
    # - -std=gnu++17
    # on_boot:
    # priority: -100.0
    # then:
    # - logger.log: "Rocket Starte IWR6843 Konfigurationssequenz..."
    # - delay: ${boot_delay}
    # - script.execute: configure_radar

    esp32:
    board: esp32dev
    framework:
    type: esp-idf




    # ============================================
    # UART Konfiguration
    # ============================================
    uart:
    # UART0 - Control/Bootloader Port @ 115200
    - id: uart_control
    tx_pin: GPIO3 # TX → IWR6843 RX (Ball N4)
    rx_pin: GPIO1 # RX ← IWR6843 TX (Ball N5)
    baud_rate: 115200
    parity: NONE
    stop_bits: 1

    # UART1 - Data Port @ 921600
    - id: uart_data
    rx_pin: GPIO23 # RX ← IWR6843 Data TX
    baud_rate: 921600

    # ============================================
    # Outputs (GPIO Control)
    # ============================================
    output:
    # Reset Pin für IWR6843 (GPIO22, inverted)
    - platform: gpio
    id: reset_pin
    pin: GPIO22
    inverted: true # turn_on() = LOW, turn_off() = HIGH

    # ============================================
    # Sensoren für Status
    # ============================================
    text_sensor:
    - platform: template
    name: "Bootloader Status"
    id: bootloader_status
    update_interval: never

    binary_sensor:
    - platform: template
    name: "Bootloader Detected"
    id: bootloader_detected
    device_class: connectivity

    # ============================================
    # Buttons
    # ============================================
    button:
    # Reset Button
    - platform: template
    name: "Reset Device"
    id: reset_btn
    icon: "mdi:restart"
    on_press:
    then:
    - logger.log: "Arrows counterclockwise Reset: GPIO22 LOW für 100ms..."
    - output.turn_on: reset_pin
    - delay: 100ms
    - output.turn_off: reset_pin
    - logger.log: "White check mark Reset abgeschlossen"

    # UART Break Button
    - platform: template
    name: "Send UART Break"
    id: break_btn
    icon: "mdi:flash"
    on_press:
    then:
    - logger.log: "Zap Sende UART Break Signal..."
    - lambda: |-
    #include "driver/uart.h"
    #include "driver/gpio.h"
    const uart_port_t uart_num = UART_NUM_0;
    const gpio_num_t tx_pin = GPIO_NUM_3;
    // Warte bis UART TX Buffer leer ist
    uart_wait_tx_done(uart_num, pdMS_TO_TICKS(500));
    // TX Pin als GPIO Output konfigurieren
    gpio_config_t io_conf = {};
    io_conf.intr_type = GPIO_INTR_DISABLE;
    io_conf.mode = GPIO_MODE_OUTPUT;
    io_conf.pin_bit_mask = (1ULL << tx_pin);
    io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
    io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
    gpio_config(&io_conf);
    // TX auf LOW (Break Signal)
    gpio_set_level(tx_pin, 0);
    ESP_LOGI("break", "TX LOW (Break) - 100ms");
    vTaskDelay(pdMS_TO_TICKS(100)); // Break-Dauer: 100ms (TI-spezifisch)
    // TX auf HIGH zurück
    gpio_set_level(tx_pin, 1);
    vTaskDelay(pdMS_TO_TICKS(10));
    // Pin zurück an UART Driver
    uart_set_pin(uart_num, tx_pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
     ESP_LOGI("break", "White check mark UART Break complete");
    - logger.log: "White check mark UART Break gesendet"

    # Reset + Break + Check Button (kompletter Workflow)
    - platform: template
    name: "Reset + Check Bootloader"
    id: reset_check_bootloader_btn
    icon: "mdi:restart-alert"
    on_press:
    then:
    - logger.log: "Arrows counterclockwise === Reset + Bootloader Check ==="
    - logger.log: "Step 0: Resetting device..."
    - output.turn_on: reset_pin
    - delay: 100ms
    - output.turn_off: reset_pin
    - logger.log: "White check mark Reset complete, waiting 2s..."
    - delay: 2s
    - button.press: check_bootloader_btn

    # Bootloader Check Button (Break + PING)
    - platform: template
    name: "Check Bootloader"
    id: check_bootloader_btn
    icon: "mdi:access-point-network"
    on_press:
    then:
    - logger.log: "Mag === Bootloader Check ==="
    - lambda: |-
    #include "driver/uart.h"
    #include "driver/gpio.h"
    const uart_port_t uart_num = UART_NUM_0;
    const gpio_num_t tx_pin = GPIO_NUM_3;
    const uint8_t SYNC_PATTERN = 0xAA;
    const uint8_t OPCODE_PING = 0x20;
    const uint8_t OPCODE_ACK = 0xCC;
    // Schritt 0: UART Buffer leeren (falls noch alte Daten drin sind)
    ESP_LOGI("check", "Step 0: Clearing UART buffer...");
    while (id(uart_control).available()) {
    uint8_t dummy;
    id(uart_control).read_byte(&dummy);
    }
    delay(50);
    // Schritt 1: UART Break
    ESP_LOGI("check", "Step 1: Sending UART Break...");
    uart_wait_tx_done(uart_num, pdMS_TO_TICKS(500));
    gpio_config_t io_conf = {};
    io_conf.intr_type = GPIO_INTR_DISABLE;
    io_conf.mode = GPIO_MODE_OUTPUT;
    io_conf.pin_bit_mask = (1ULL << tx_pin);
    io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
    io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
    gpio_config(&io_conf);
    gpio_set_level(tx_pin, 0);
    ESP_LOGI("check", "TX LOW (Break) - 100ms");
    vTaskDelay(pdMS_TO_TICKS(100)); // Break-Dauer: 100ms (TI-spezifisch)
    gpio_set_level(tx_pin, 1);
    vTaskDelay(pdMS_TO_TICKS(10));
    uart_set_pin(uart_num, tx_pin, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
     ESP_LOGI("check", "White check mark Break sent");
    // WICHTIG: Nach Break sendet Bootloader einen ACK!
    // Wir müssen diesen lesen und prüfen
    ESP_LOGI("check", "Step 1.5: Waiting for ACK after Break...");
    delay(300); // Länger warten auf ACK nach Break
    // Lese Response nach Break (sollte ACK sein)
    // TI Format: LENGTH (2 bytes, Big-Endian) + CHECKSUM (1 byte) + PAYLOAD
    uint8_t break_response[16];
    size_t break_bytes = 0;
    uint32_t break_start = millis();
    while (millis() - break_start < 500 && break_bytes < 16) {
    if (id(uart_control).available()) {
    uint8_t byte;
    id(uart_control).read_byte(&byte);
    break_response[break_bytes++] = byte;
    ESP_LOGI("check", "Break response byte %d: 0x%02X", break_bytes-1, byte);
    }
    delay(10);
    }
    if (break_bytes >= 4) {
    ESP_LOGI("check", "Received %d bytes after Break", break_bytes);
    // Parse TI Response Format: LENGTH (2 bytes) + CHECKSUM (1 byte) + PAYLOAD
    uint16_t resp_length = (break_response[0] << 8) | break_response[1]; // Big-Endian
    uint8_t resp_checksum = break_response[2];
    ESP_LOGI("check", "Response Length: %d, Checksum: 0x%02X", resp_length, resp_checksum);
    // Nach ACK (0xCC) in Payload suchen
    bool found_break_ack = false;
    for (size_t i = 3; i < break_bytes; i++) {
    if (break_response[i] == OPCODE_ACK) {
    found_break_ack = true;
     ESP_LOGI("check", "White check mark ACK (0xCC) found after Break at position %d!", i);
    break;
    }
    }
    if (!found_break_ack) {
     ESP_LOGW("check", "Warning️ No ACK after Break - Bootloader may not be active");
    ESP_LOGW("check", "Response: %02X %02X %02X %02X %02X",
    break_response[0], break_bytes > 1 ? break_response[1] : 0,
    break_bytes > 2 ? break_response[2] : 0, break_bytes > 3 ? break_response[3] : 0,
    break_bytes > 4 ? break_response[4] : 0);
    }
    } else if (break_bytes > 0) {
     ESP_LOGW("check", "Warning️ Incomplete response after Break (%d bytes)", break_bytes);
    } else {
     ESP_LOGW("check", "Warning️ No response after Break");
    }
    // Buffer leeren vor PING (falls noch CLI-Output drin ist)
    delay(100);
    while (id(uart_control).available()) {
    uint8_t dummy;
    id(uart_control).read_byte(&dummy);
    }
    // Schritt 2: PING Command
    ESP_LOGI("check", "Step 2: Sending PING...");
    uint8_t ping_packet[] = {SYNC_PATTERN, 0x00, 0x03, 0x20, OPCODE_PING};
    id(uart_control).write_array(ping_packet, 5);
    id(uart_control).flush();
    // Schritt 3: Response lesen
    ESP_LOGI("check", "Step 3: Reading PING response...");
    delay(300); // Länger warten auf Response
    uint8_t buffer[64];
    size_t bytes_read = 0;
    uint32_t start = millis();
    while (millis() - start < 1000 && bytes_read < 64) {
    if (id(uart_control).available()) {
    uint8_t byte;
    id(uart_control).read_byte(&byte);
    buffer[bytes_read++] = byte;
    }
    delay(10);
    }
    // Ergebnis - Parse TI Response Format
    ESP_LOGI("check", "========================================");
    if (bytes_read >= 4) {
     ESP_LOGI("check", "White check mark Received %d bytes", bytes_read);
    // Hex-Dump
    char hex[256] = "";
    for (size_t i = 0; i < bytes_read && i < 16; i++) {
    char h[4];
    sprintf(h, "%02X ", buffer[i]);
    strcat(hex, h);
    }
    ESP_LOGI("check", "Response: %s", hex);
    // Parse TI Response Format: LENGTH (2 bytes, Big-Endian) + CHECKSUM (1 byte) + PAYLOAD
    uint16_t resp_length = (buffer[0] << 8) | buffer[1]; // Big-Endian
    uint8_t resp_checksum = buffer[2];
    ESP_LOGI("check", "Response Length: %d, Checksum: 0x%02X", resp_length, resp_checksum);
    // Checksum prüfen (falls Payload vorhanden)
    if (bytes_read > 3) {
    uint8_t calc_checksum = 0;
    for (size_t i = 3; i < bytes_read; i++) {
    calc_checksum = (calc_checksum + buffer[i]) & 0xFF;
    }
    if (calc_checksum == resp_checksum) {
    ESP_LOGI("check", "✓ Checksum OK");
    } else {
     ESP_LOGW("check", "Warning️ Checksum mismatch: calc=0x%02X, recv=0x%02X", 
    calc_checksum, resp_checksum);
    }
    }
    // Nach ACK (0xCC) in Payload suchen (ab Position 3)
    bool found_ack = false;
    for (size_t i = 3; i < bytes_read; i++) {
    if (buffer[i] == OPCODE_ACK) {
    found_ack = true;
     ESP_LOGI("check", "White check markWhite check markWhite check mark BOOTLOADER ACTIVE! White check markWhite check markWhite check mark");
    ESP_LOGI("check", "ACK (0xCC) found at position %d", i);
    id(bootloader_status).publish_state("Active - ACK received");
    id(bootloader_detected).publish_state(true);
    break;
    }
    }
    if (!found_ack) {
     ESP_LOGW("check", "Warning️ No ACK found - Bootloader not active");
    ESP_LOGW("check", "Response format: LENGTH=%d, CHECKSUM=0x%02X", resp_length, resp_checksum);
    id(bootloader_status).publish_state("No ACK");
    id(bootloader_detected).publish_state(false);
    }
    } else if (bytes_read > 0) {
     ESP_LOGW("check", "Warning️ Incomplete response (%d bytes, need at least 4)", bytes_read);
    id(bootloader_status).publish_state("Incomplete Response");
    id(bootloader_detected).publish_state(false);
    } else {
     ESP_LOGW("check", "Warning️ No response");
    id(bootloader_status).publish_state("No Response");
    id(bootloader_detected).publish_state(false);
    }
    ESP_LOGI("check", "========================================");

    # Test PING (ohne Break)
    - platform: template
    name: "Test PING Only"
    id: ping_only_btn
    icon: "mdi:send"
    on_press:
    then:
    - logger.log: "test tube Test: Sende PING ohne Break..."
    - lambda: |-
    const uint8_t SYNC_PATTERN = 0xAA;
    const uint8_t OPCODE_PING = 0x20;
    uint8_t ping_packet[] = {SYNC_PATTERN, 0x00, 0x03, 0x20, OPCODE_PING};
    ESP_LOGI("ping", "Sending: AA 00 03 20 20");
    id(uart_control).write_array(ping_packet, 5);
    id(uart_control).flush();
    delay(500);
    // Lese Response
    uint8_t response[10];
    size_t bytes_read = 0;
    uint32_t start = millis();
    while (millis() - start < 1000 && bytes_read < 10) {
    if (id(uart_control).available()) {
    uint8_t byte;
    id(uart_control).read_byte(&byte);
    response[bytes_read++] = byte;
    ESP_LOGI("ping", "Byte %d: 0x%02X", bytes_read-1, byte);
    }
    delay(10);
    }
    if (bytes_read > 0) {
     ESP_LOGI("ping", "White check mark Received %d bytes", bytes_read);
    bool ack_found = false;
    for (size_t i = 0; i < bytes_read; i++) {
    if (response[i] == 0xCC) {
     ESP_LOGI("ping", "White check mark ACK found at position %d!", i);
    ack_found = true;
    }
    }
    if (!ack_found) {
     ESP_LOGW("ping", "Warning️ No ACK found");
    }
    } else {
     ESP_LOGE("ping", "X Timeout - keine Antwort");
    }


    I cant reach out to the bootloader somehow

    although I am using the official Bootloader Flow 
  • and these are the logs - if I press the buttons:

    [17:03:19.476][D][button:023]: 'Check Bootloader' Pressed. [17:03:19.476][D][main:314]: Mag === Bootloader Check === [17:03:19.477][I][check:163]: Step 0: Clearing UART buffer... [17:03:19.522][I][check:171]: Step 1: Sending UART Break... [17:03:19.525][I][check:183]: TX LOW (Break) - 100ms [17:03:19.638][I][check:188]: White check mark Break sent [17:03:19.641][I][check:192]: Step 1.5: Waiting for ACK after Break... [17:03:20.445][W][check:240]: Warning️ No response after Break [17:03:20.549][I][check:251]: Step 2: Sending PING... [17:03:20.554][I][check:257]: Step 3: Reading PING response... [17:03:21.856][I][check:274]: ======================================== [17:03:21.859][W][check:331]: Warning️ No response [17:03:21.861][D][text_sensor:085]: 'Bootloader Status': Sending state 'No Response' [17:03:21.864][I][check:335]: ======================================== [17:03:26.746][D][button:023]: 'Reset + Check Bootloader' Pressed. [17:03:26.746][D][main:184]: Arrows counterclockwise === Reset + Bootloader Check === [17:03:26.747][D][main:187]: Step 0: Resetting device... [17:03:26.867][D][main:196]: White check mark Reset complete, waiting 2s... [17:03:28.852][D][button:023]: 'Check Bootloader' Pressed. [17:03:28.855][D][main:314]: Mag === Bootloader Check === [17:03:28.858][I][check:163]: Step 0: Clearing UART buffer... [17:03:28.910][I][check:171]: Step 1: Sending UART Break... [17:03:28.914][I][check:183]: TX LOW (Break) - 100ms [17:03:29.030][I][check:188]: White check mark Break sent [17:03:29.031][I][check:192]: Step 1.5: Waiting for ACK after Break... [17:03:29.835][W][check:240]: Warning️ No response after Break [17:03:29.938][I][check:251]: Step 2: Sending PING... [17:03:29.941][I][check:257]: Step 3: Reading PING response... [17:03:31.252][I][check:274]: ======================================== [17:03:31.253][W][check:331]: Warning️ No response [17:03:31.254][D][text_sensor:085]: 'Bootloader Status': Sending state 'No Response' [17:03:31.254][I][check:335]: ======================================== [17:03:34.231][D][button:023]: 'Test PING Only' Pressed. [17:03:34.233][D][main:406]: test tube Test: Sende PING ohne Break... [17:03:34.238][I][ping:350]: Sending: AA 00 03 20 20 [17:03:35.742][E][ping:384]: X Timeout - keine Antwort

  • Hello Eren.

    When I modify the script to send the .bin file via the socket interface - I can´t get into the bootloader mode,

    I am not familiar with using a socket interface to send a binary, is that supposed to replace the UART connection between the ESP32 and the device?  That may not be a boot mode that the 6843 will support.  Without an SFLASH connected, the device will still be able to boot over UART or SPI, which you have implemented prior to using a socket interface, but any other means may not be supported.

    Sincerely,

    Santosh

  • Hold on - isn’t the sflash connected to the IWR6843AOPEVM?

    I am not doing anything more than a usual usb UART TTL would do. 

    So the RS232 RX/TX and Reset GPIO is connected to the ESP32.

    the only difference is, that the flashing progress is sent over TCP instead of a COM interface. Booting in the bootloader mode is done locally

  • Hello Santosh,

    Thank you for your response. I believe there may be a misunderstanding about my implementation. Let me clarify:
    I am NOT using a "socket interface" as a boot method. I am still using UART boot, exactly as Uniflash does. The only difference is the transport layer between the PC and the ESP32.What I'm actually doing:

    1. Bootloader trigger (local): The ESP32 sends the UART Break signal and Reset pulse locally via GPIO pins (not over TCP/IP). This is identical to what a USB-UART adapter would do.
    1. UART data transport (TCP/IP): The ESP32 exposes its UART interface over TCP/IP using a stream server. The Python flash script connects to this TCP socket and sends the firmware data, which the ESP32 forwards directly to the IWR6843AOP's UART interface.
    1. Physical connections: The ESP32 is directly connected to the IWR6843AOP via:
    • UART TX/RX pins (GPIO3/GPIO1)
    • Reset pin (GPIO22)
    • GND

    This is still UART boot - the same boot method that Uniflash uses. TCP/IP is only used as a transport mechanism between the PC and ESP32, similar to how a USB-UART adapter transports data over USB.The problem I'm experiencing:Even though I'm using the same physical connections and the same boot sequence (Break → Reset → ACK), the IWR6843AOP does not enter bootloader mode when triggered by the ESP32, even though it works perfectly when using Uniflash with a direct USB-UART adapter.My question remains:Is there a specific timing requirement or sequence detail for the UART Break signal that I might be missing? I have reviewed the boot flow documentation and tried various timing combinations, but the device only enters bootloader mode when using Uniflash directly, not when triggered by the ESP32.

    Additional context:

    • SOP2 jumper is set to GND (Flash mode)
    • Break signal duration: 100ms (as per documentation)
    • Reset pulse: 100ms LOW
    • Break signal remains active during Reset (as per TI documentation)
    • Wait time after Reset: 1000ms before checking for ACK

    Any guidance on the exact timing sequence or potential issues with GPIO-controlled Break signals would be greatly appreciated.Thank you for your assistance.Best regards,

    Eren

  • Hello Eren.

    Thank you for the clarification, I understand.  I think the UART break and reset sequence you are following is correct.  As I suggested before, I would ensure that the break and reset sequence is identical to what is being sent by the USB to UART converter.  That way you can eliminate that as the root cause.  The timing for it all should be in the TRM for the boot sequence, but if not, using the USB to UART as a reference will help.  In addition, I would make sure that the binary you want to flash is being sent to the ESP32 correctly as if the metaheader doesn't match or if there is some corruption, when the device tries to boot the image it may fail.

    Sincerely,

    Santosh

  • Hi Santosh, for now I am using the SBL, but this is tbh not the ideal condition for mass production/ flashing. I’ll do my investigation now with better options.