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.

AM13E23019: Example of the proper way to edit BCR

Part Number: AM13E23019

Our software has a requirement to, under certain conditions, protect flash from external readout. By analyzing TRM, we see that this can be achieved by modifying DEBUG_ACCESS field of BCR (the same is done in our MSPM0G-based applications).

But since modifying BCR within the code has a high risk of breaking the chip, we would like to request a code example that would do it, to use as a reference in our application.

BR,
Oleksandr

  • Hi Oleksandr,

    Can you please share the exact requirements for BCR/BSL (other than DEBUG_ACCESS field setting), based on that, I'll try to test a non-main configuration and provide the config that can be flashed for your case. I'm assuming you might already know the fields since your MSPM0 app already uses it. This way i can quickly port the same settings for AM13x. We have a sample documentation in development, but to unblock you for now, I'd like to directly provide the output.

    Regards,
    Shaunak

  • Hello Shaunak!

    The desired configuration for now is:

    • DEBUG_ACCESS -- disabled
    • BSL -- disabled (to avoid possible memory readout via BSL communication interface)
    • All other configurations -- set to default

    For MSPM0Gx we used approach from "flashctl_nonmain_memory_write" example:

    • Prepare a BCR/BSL structures with the desired configurations
    • Calculate a CRC (during the runtime) over BCR/BSL structures
    • Flash prepared structures with the procedure provided in the example
  • Hi Oleksandr,

    Im attaching two configurations below, please use the suitable ones.

    1. Disabled BSL, Disabled debug access, Disabled SWDP (No DSSM access)

    @60100800
    00 00 00 50 FF FF 22 55 22 55 BB AA BB AA BB AA
    FF FF BB AA BB AA BB AA FF FF FF FF FF FF FF FF
    FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
    FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
    FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
    FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
    FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
    FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
    FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
    FF FF FF FF FF FF FF FF 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 FF FF 00 00 FF FF FF FF
    FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
    FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
    FF FF FF FF BB AA 00 00 FF FF FF FF FF FF FF FF
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 FF FF FF FF FF FF FF FF 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    FF FF FF FF FF FF FF FF FF FF FF FF 00 00 00 00
    FF 0F 00 FF C8 08 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    @60100C00
    00 00 00 50 01 07 00 07 16 04 17 04 0B 0A 0C 0A
    86 06 BB AA FF FF FF FF FF FF FF FF FF FF FF FF
    FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
    FF FF FF FF FF FF FF FF FF FF 02 00 48 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    q
    

    2. Disabled BSL, Disabled debug access, Enabled SWDP

    @60100800
    00 00 00 50 FF FF BB AA 22 55 BB AA BB AA BB AA
    FF FF BB AA BB AA BB AA FF FF FF FF FF FF FF FF
    FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
    FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
    FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
    FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
    FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
    FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
    FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
    FF FF FF FF FF FF FF FF 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 FF FF 00 00 FF FF FF FF
    FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
    FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
    FF FF FF FF BB AA 00 00 FF FF FF FF FF FF FF FF
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 FF FF FF FF FF FF FF FF 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    FF FF FF FF FF FF FF FF FF FF FF FF 00 00 00 00
    FF 0F 00 FF C8 08 00 00 00 00 00 00 00 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    @60100C00
    00 00 00 50 01 07 00 07 16 04 17 04 0B 0A 0C 0A
    86 06 BB AA FF FF FF FF FF FF FF FF FF FF FF FF
    FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
    FF FF FF FF FF FF FF FF FF FF 02 00 48 00 00 00
    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    q
    

    One way to program this config would be to use the attached python script:

    1. Connect a CCS debug session

    2. Go to Scripts -> Reset Commands -> Software_SYSRST_with_BSL

    3. Once the device is in BSL mode, use the python script to flash the above non main config (make sure you do this within 10 seconds to avoid hitting the timeout in UART BSL)

    python bsl_cli_am13e2.py --no-invoke flash csc_exist_NM.txt


    
    
    import argparse
    import os
    import struct
    import subprocess
    import serial
    import time
    import sys
    from typing import Optional, List, Tuple
    from elftools.elf.elffile import ELFFile
    
    # ---------------------------------------------------------------------------
    # Configuration defaults
    # ---------------------------------------------------------------------------
    DEFAULT_BAUD_RATE = 9600
    SERIAL_TIMEOUT = 5
    
    # Serial port – set this to your BSL UART COM port (USB-to-UART adapter)
    DEFAULT_PORT = "COM24"
    
    # ---------------------------------------------------------------------------
    # BSL Protocol Constants (AM13E2x)
    # ---------------------------------------------------------------------------
    PACKET_HEADER_TX = 0x80        # Host -> Device
    PACKET_HEADER_RX = 0x08        # Device -> Host
    
    # Command opcodes (from secure-rom BSL_CI.h)
    CMD_CONNECTION          = 0x12
    CMD_GET_IDENTITY        = 0x19
    CMD_UNLOCK_BSL          = 0x21
    CMD_GET_SOC_ID          = 0x22
    CMD_MASS_ERASE          = 0x15
    CMD_FLASH_RANGE_ERASE   = 0x23
    CMD_PROGRAM_DATA        = 0x20
    CMD_PROGRAM_DATA_FAST   = 0x24
    CMD_MEMORY_READ_BACK    = 0x29
    CMD_FACTORY_RESET       = 0x30
    CMD_STANDALONE_VERIFY   = 0x26
    CMD_START_APPLICATION   = 0x40
    
    # Response command bytes (from secure-rom BSL_CI.h)
    RSP_MEMORY_READ_BACK    = 0x30
    RSP_GET_IDENTITY        = 0x31
    RSP_STANDALONE_VERIFY   = 0x32
    RSP_GET_SOC_ID          = 0x33
    RSP_MESSAGE_REPLY       = 0x3B
    RSP_DETAILED_ERROR      = 0x3A
    
    # BSL error codes (from secure-rom BSL_CI.h)
    BSL_ERRORS = {
        0x00: "Success",
        0x01: "BSL locked",
        0x02: "Password error",
        0x03: "Multiple password errors (security alert triggered)",
        0x04: "Unknown command",
        0x05: "Invalid memory range",
        0x06: "Invalid command",
        0x07: "Factory reset disabled",
        0x08: "Factory reset password error",
        0x09: "Readout disabled",
        0x0A: "Invalid address/length alignment (must be 16-byte aligned)",
        0x0B: "Standalone verification invalid length",
        0x0C: "SHA check error",
        0xF0: "Flash command failed",
    }
    
    # UART-level ACK/error codes (single-byte, from secure-rom BSL_PI.c)
    BSL_ACK = 0x00
    UART_ERRORS = {
        0x00: "ACK (no error)",
        0x51: "Header incorrect",
        0x52: "Checksum incorrect",
        0x53: "Packet size zero",
        0x54: "Packet size exceeds buffer",
        0x55: "Unknown error",
        0x56: "Unknown baud rate",
        0x57: "Packet size error",
    }
    
    # Protocol limits
    # NOTE: AM13 supports up to 32KB buffers (BSL_MAX_BUF_SIZE = 0x7FFF),
    # but actual size depends on device SRAM. We use a safe default of 256
    # bytes per chunk until GET_IDENTITY tells us the real buffer size.
    # Address and length for PROGRAM_DATA must be 16-byte aligned.
    DEFAULT_CHUNK_SIZE = 256
    PROGRAM_ALIGNMENT = 16
    PASSWORD_SIZE = 32
    HDR_LEN_CMD_BYTES = 4   # header(1) + length(2) + cmd(1)
    CRC_BYTES = 4
    ADDR_BYTES = 4
    BSL_PI_WRAPPER_SIZE = 7  # header(1) + length(2) + CRC(4)
    
    
    # ---------------------------------------------------------------------------
    # Memory region definitions (AM13E2x)
    # ---------------------------------------------------------------------------
    # Main flash memory range: 0x00000000 - 0x0007FFFF (512KB)
    MAIN_FLASH_START = 0x00000000
    MAIN_FLASH_END = 0x0007FFFF
    
    # Non-main memory range: 0x60100000 - 0x60101FFF (configuration/info memory)
    NON_MAIN_MEMORY_START = 0x60100000
    NON_MAIN_MEMORY_END = 0x60101FFF
    
    
    # ---------------------------------------------------------------------------
    # CRC32 (same as MSPM0L/G BSL)
    # ---------------------------------------------------------------------------
    CRC32_POLY = 0xEDB88320
    
    def crc32(data: bytes) -> int:
        crc = 0xFFFFFFFF
        for b in data:
            crc ^= b
            for _ in range(8):
                mask = -(crc & 1)
                crc = (crc >> 1) ^ (CRC32_POLY & mask)
        return crc
    
    
    # ---------------------------------------------------------------------------
    # Packet construction / parsing
    # ---------------------------------------------------------------------------
    def build_packet(cmd: int, data: bytes = b"") -> bytes:
        payload = bytes([cmd]) + data
        payload_len = len(payload)
        header = struct.pack("<BH", PACKET_HEADER_TX, payload_len)
        checksum = struct.pack("<I", crc32(payload))
        return header + payload + checksum
    
    
    def parse_response(raw: bytes) -> Tuple[Optional[int], Optional[bytes]]:
        """Parse a BSL response packet.
    
        Returns (response_cmd, data) or (None, None) on parse failure.
        """
        if len(raw) < HDR_LEN_CMD_BYTES + CRC_BYTES:
            return None, None
    
        header = raw[0]
        if header != PACKET_HEADER_RX and header != PACKET_HEADER_TX:
            # Some devices echo the TX header in responses
            pass  # still try to parse
    
        payload_len = struct.unpack_from("<H", raw, 1)[0]
        resp_cmd = raw[3]
        data = raw[4 : 4 + payload_len - 1]
    
        # Validate CRC
        expected_crc_offset = 3 + payload_len
        if len(raw) >= expected_crc_offset + 4:
            received_crc = struct.unpack_from("<I", raw, expected_crc_offset)[0]
            computed_crc = crc32(raw[3 : 3 + payload_len])
            if received_crc != computed_crc:
                print(f"  WARNING: CRC mismatch (received 0x{received_crc:08X}, computed 0x{computed_crc:08X})")
    
        return resp_cmd, data
    
    
    # ---------------------------------------------------------------------------
    # Firmware file loaders
    # ---------------------------------------------------------------------------
    def load_ti_txt(filepath: str) -> List[Tuple[int, bytes]]:
        """Load a TI-TXT format file. Returns list of (address, data) segments."""
        segments = []
        current_addr = 0
        current_data = bytearray()
    
        with open(filepath, "r") as f:
            for line in f:
                line = line.strip()
                if not line:
                    continue
                if line.startswith("@"):
                    if current_data:
                        segments.append((current_addr, bytes(current_data)))
                        current_data = bytearray()
                    current_addr = int(line[1:], 16)
                elif line.lower() == "q":
                    if current_data:
                        segments.append((current_addr, bytes(current_data)))
                        current_data = bytearray()
                    break
                else:
                    current_data.extend(bytes.fromhex(line))
    
        if current_data:
            segments.append((current_addr, bytes(current_data)))
    
        return segments
    
    
    def load_intel_hex(filepath: str) -> List[Tuple[int, bytes]]:
        """Load an Intel HEX format file. Returns list of (address, data) segments."""
        segments = []
        base_addr = 0
        current_addr = None
        current_data = bytearray()
    
        with open(filepath, "r") as f:
            for line in f:
                line = line.strip()
                if not line.startswith(":"):
                    continue
                raw = bytes.fromhex(line[1:])
                byte_count = raw[0]
                addr = struct.unpack(">H", raw[1:3])[0]
                record_type = raw[3]
                data = raw[4 : 4 + byte_count]
    
                if record_type == 0x00:  # Data record
                    full_addr = base_addr + addr
                    if current_addr is not None and full_addr == current_addr + len(current_data):
                        current_data.extend(data)
                    else:
                        if current_data:
                            segments.append((current_addr, bytes(current_data)))
                        current_addr = full_addr
                        current_data = bytearray(data)
                elif record_type == 0x02:  # Extended segment address
                    base_addr = struct.unpack(">H", data)[0] << 4
                elif record_type == 0x04:  # Extended linear address
                    base_addr = struct.unpack(">H", data)[0] << 16
                elif record_type == 0x01:  # EOF
                    break
    
        if current_data and current_addr is not None:
            segments.append((current_addr, bytes(current_data)))
    
        return segments
    
    
    def load_binary(filepath: str, start_addr: int) -> List[Tuple[int, bytes]]:
        """Load a raw binary file at a given start address."""
        with open(filepath, "rb") as f:
            data = f.read()
        return [(start_addr, data)]
    
    
    def load_elf(filepath: str) -> List[Tuple[int, bytes]]:
        """Load an ELF (.out) file. Returns list of (address, data) segments from loadable sections.
    
        Merges all segments into one contiguous block with 16-byte alignment to satisfy BSL requirements.
        """
        flash_segments = []
    
        with open(filepath, "rb") as f:
            elf = ELFFile(f)
    
            # Collect all LOAD segments that go to flash (not RAM)
            for segment in elf.iter_segments():
                if segment['p_type'] == 'PT_LOAD':
                    paddr = segment['p_paddr']
                    data = segment.data()
    
                    # Only include segments with non-zero size in flash memory
                    # Skip RAM segments (typically addresses >= 0x20000000)
                    if len(data) > 0 and paddr < 0x20000000:
                        flash_segments.append((paddr, data))
    
        if not flash_segments:
            raise ValueError(f"No loadable flash segments found in ELF file: {filepath}")
    
        # Sort segments by address
        flash_segments.sort(key=lambda x: x[0])
    
        # Find the address range
        min_addr = flash_segments[0][0]
        max_addr = max(addr + len(data) for addr, data in flash_segments)
    
        # Align start address down to 16-byte boundary
        aligned_start = (min_addr // 16) * 16
    
        # Calculate total size and align up to 16-byte boundary
        total_size = max_addr - aligned_start
        aligned_size = ((total_size + 15) // 16) * 16
    
        # Create buffer filled with 0xFF (erased flash state)
        buffer = bytearray([0xFF] * aligned_size)
    
        # Copy all segment data into the buffer at their respective offsets
        for addr, data in flash_segments:
            offset = addr - aligned_start
            buffer[offset:offset + len(data)] = data
    
        return [(aligned_start, bytes(buffer))]
    
    
    def load_firmware(filepath: str, start_addr: int = 0) -> List[Tuple[int, bytes]]:
        """Auto-detect format and load firmware file."""
        ext = os.path.splitext(filepath)[1].lower()
        if ext in (".hex",):
            print(f"Loading Intel HEX file: {filepath}")
            return load_intel_hex(filepath)
        elif ext in (".bin",):
            print(f"Loading binary file: {filepath} at address 0x{start_addr:08X}")
            return load_binary(filepath, start_addr)
        elif ext in (".out",):
            print(f"Loading ELF file: {filepath}")
            return load_elf(filepath)
        else:
            # Default to TI-TXT
            print(f"Loading TI-TXT file: {filepath}")
            return load_ti_txt(filepath)
    
    
    
    # ---------------------------------------------------------------------------
    # Helper function to check memory regions
    # ---------------------------------------------------------------------------
    def has_non_main_memory_addresses(segments: List[Tuple[int, bytes]]) -> bool:
        """Check if any programming address falls within non-main memory region.
    
        Args:
            segments: List of (address, data) tuples representing firmware segments
    
        Returns:
            True if any address is in non-main memory (0x60100000-0x60101FFF), False otherwise
        """
        for addr, data in segments:
            # Check if segment start address is in non-main memory
            if NON_MAIN_MEMORY_START <= addr <= NON_MAIN_MEMORY_END:
                return True
            # Check if segment end address is in non-main memory
            end_addr = addr + len(data) - 1
            if NON_MAIN_MEMORY_START <= end_addr <= NON_MAIN_MEMORY_END:
                return True
            # Check if segment spans across non-main memory
            if addr < NON_MAIN_MEMORY_START and end_addr > NON_MAIN_MEMORY_END:
                return True
        return False
    
    
    # ---------------------------------------------------------------------------
    # BSLConnection class
    # ---------------------------------------------------------------------------
    class BSLConnection:
        """Manages BSL connection to an AM13E2x device over UART."""
    
        def __init__(self, port: str, baud_rate: int = DEFAULT_BAUD_RATE,
                     no_invoke: bool = False, verbose: bool = False):
            self.port_name = port
            self.baud_rate = baud_rate
            self.no_invoke = no_invoke
            self.verbose = verbose
            self.ser: Optional[serial.Serial] = None
            # Max data bytes per PROGRAM_DATA chunk. Updated by GET_IDENTITY
            # to match device buffer size. Must subtract wrapper overhead
            # (cmd byte + 4 addr bytes = 5 bytes) from the device buffer.
            self.max_chunk_size = DEFAULT_CHUNK_SIZE
    
        def open(self, wait: bool = False) -> bool:
            if wait:
                print(f"  Waiting for {self.port_name}... (unplug/replug USB now)")
                while True:
                    try:
                        self.ser = serial.Serial(
                            port=self.port_name,
                            baudrate=self.baud_rate,
                            bytesize=serial.EIGHTBITS,
                            parity=serial.PARITY_NONE,
                            stopbits=serial.STOPBITS_ONE,
                            timeout=SERIAL_TIMEOUT,
                            xonxoff=False,
                            rtscts=False,
                            dsrdtr=False,
                        )
                        print(f"  Opened {self.port_name} at {self.baud_rate} baud")
                        return True
                    except serial.SerialException:
                        time.sleep(0.2)
            else:
                try:
                    self.ser = serial.Serial(
                        port=self.port_name,
                        baudrate=self.baud_rate,
                        bytesize=serial.EIGHTBITS,
                        parity=serial.PARITY_NONE,
                        stopbits=serial.STOPBITS_ONE,
                        timeout=SERIAL_TIMEOUT,
                        xonxoff=False,
                        rtscts=False,
                        dsrdtr=False,
                    )
                    print(f"  Opened {self.port_name} at {self.baud_rate} baud")
                    return True
                except Exception as e:
                    print(f"  Failed to open {self.port_name}: {e}")
                    return False
    
        def close(self):
            if self.ser and self.ser.is_open:
                try:
                    self.ser.close()
                    if self.verbose:
                        print(f"  Closed {self.port_name}")
                except Exception as e:
                    print(f"  Error closing {self.port_name}: {e}")
    
        # -- BSL invocation via XDS110 ------------------------------------------
    
        def invoke_bsl_mode(self):
            """Handle BSL invocation mode."""
            if self.no_invoke:
                # User already has device in BSL mode (via main.c)
                return
    
            # Device not in BSL mode - instruct user to flash main.c
            print("\n  ERROR: Device not in BSL mode")
            print(f"  Please flash main.c using CCS, then run with --no-invoke flag")
            print(f"  Example: python {os.path.basename(__file__)} --no-invoke flash firmware.out\n")
            return False
    
        # -- Low-level send/receive ---------------------------------------------
    
        def send_packet(self, packet: bytes) -> bool:
            if not self.ser or not self.ser.is_open:
                print("  Serial port not open")
                return False
            try:
                self.ser.write(packet)
                if self.verbose:
                    print(f"  TX [{len(packet)}]: {packet.hex()}")
                return True
            except Exception as e:
                print(f"  Error sending: {e}")
                return False
    
        def read_response_raw(self, expected_len: int = 0, timeout_s: float = 5.0) -> Optional[bytes]:
            if not self.ser or not self.ser.is_open:
                return None
    
            old_timeout = self.ser.timeout
            self.ser.timeout = timeout_s
    
            try:
                # Read header first (3 bytes: header + length)
                hdr = self.ser.read(3)
                if len(hdr) < 3:
                    if self.verbose:
                        print(f"  RX timeout waiting for header (got {len(hdr)} bytes)")
                    return hdr if hdr else None
    
                payload_len = struct.unpack_from("<H", hdr, 1)[0]
                # Read payload + CRC
                remaining = payload_len + CRC_BYTES
                rest = self.ser.read(remaining)
                raw = hdr + rest
    
                if self.verbose:
                    print(f"  RX [{len(raw)}]: {raw.hex()}")
                return raw
            except Exception as e:
                print(f"  Error reading: {e}")
                return None
            finally:
                self.ser.timeout = old_timeout
    
        def read_ack(self, timeout_s: float = 5.0) -> Optional[int]:
            """Read a single ACK byte (for Connection command)."""
            if not self.ser or not self.ser.is_open:
                return None
            old_timeout = self.ser.timeout
            self.ser.timeout = timeout_s
            try:
                data = self.ser.read(1)
                if data:
                    if self.verbose:
                        print(f"  RX ACK: 0x{data[0]:02X}")
                    return data[0]
                return None
            except Exception as e:
                print(f"  Error reading ACK: {e}")
                return None
            finally:
                self.ser.timeout = old_timeout
    
        # -- BSL Commands -------------------------------------------------------
    
        def cmd_connection(self, retries: int = 3) -> bool:
            """Send Connection command (0x12) with retries.
    
            During auto-detection the ROM BSL sends a single-byte ACK (0x00)
            and locks onto UART as the active interface. The connection command
            should only be sent once (during auto-detection phase). Sending it
            again during the command loop will return BSL_INVALID_COMMAND.
            """
            for attempt in range(retries):
                if attempt > 0:
                    print(f"  Retry {attempt}/{retries-1}...")
                    time.sleep(0.5)
    
                # Clear any pending data
                if self.ser and self.ser.is_open:
                    self.ser.reset_input_buffer()
                    self.ser.reset_output_buffer()
    
                packet = build_packet(CMD_CONNECTION)
                if not self.send_packet(packet):
                    continue
    
                time.sleep(0.1)
                ack = self.read_ack(timeout_s=5.0)
    
                if ack is None:
                    if attempt == retries - 1:
                        print("  ERROR: No response from BSL")
                        print("  Make sure device is in BSL mode (flash main.c first)")
                    continue
    
                if ack == BSL_ACK:
                    print("  BSL connection established (interface locked to UART)")
                    # Drain any extra response bytes BSL sends after connection ACK
                    time.sleep(0.2)
                    self.ser.reset_input_buffer()
                    return True
                else:
                    err_msg = UART_ERRORS.get(ack, f"Unknown (0x{ack:02X})")
                    print(f"  Connection failed: {err_msg}")
                    continue
    
            return False
    
        def cmd_get_identity(self) -> Optional[bytes]:
            """Send Get Identity command (0x19). Returns device info payload.
    
            Also reads the device's BSL buffer size from the response and
            updates max_chunk_size accordingly.
            """
            packet = build_packet(CMD_GET_IDENTITY)
            if not self.send_packet(packet):
                return None
    
            # GET_IDENTITY gets a single-byte ACK first, then a full response packet
            ack = self.read_ack(timeout_s=5.0)
            if ack is not None and ack != BSL_ACK:
                err_msg = UART_ERRORS.get(ack, f"Unknown (0x{ack:02X})")
                print(f"  Get Identity ACK error: {err_msg}")
                return None
    
            raw = self.read_response_raw(timeout_s=5.0)
            if not raw:
                print("  No response to Get Identity")
                return None
    
            resp_cmd, data = parse_response(raw)
            if resp_cmd == RSP_GET_IDENTITY:
                # Try to extract buffer size from identity response
                # MSPM0/AM13 identity response typically has buffer size at offset 1-2
                if data and len(data) >= 4:
                    buf_size = struct.unpack_from("<H", data, 1)[0]
                    if buf_size > BSL_PI_WRAPPER_SIZE:
                        # Usable data per chunk = buffer size - wrapper(7) - cmd(1) - addr(4)
                        usable = buf_size - BSL_PI_WRAPPER_SIZE - 1 - ADDR_BYTES
                        # Round down to 16-byte alignment
                        usable = (usable // PROGRAM_ALIGNMENT) * PROGRAM_ALIGNMENT
                        if usable > 0:
                            self.max_chunk_size = usable
                            if self.verbose:
                                print(f"  Device buffer: {buf_size} bytes, max chunk: {self.max_chunk_size} bytes")
                return data
            elif resp_cmd == RSP_MESSAGE_REPLY:
                err = data[0] if data else 0xFF
                print(f"  Get Identity error: {BSL_ERRORS.get(err, f'0x{err:02X}')}")
                return None
            else:
                print(f"  Unexpected response cmd: 0x{resp_cmd:02X}" if resp_cmd else "  Failed to parse response")
                return raw
    
        def _read_ack_then_response(self, cmd_name: str, timeout_s: float = 5.0) -> Tuple[Optional[int], Optional[bytes]]:
            """Common flow: read single-byte ACK, then full response packet.
    
            The ROM BSL PI layer sends a 0x00 ACK after validating the packet,
            then the CI layer processes the command and sends a full response.
            Returns (resp_cmd, data) or (None, None) on failure.
            """
            ack = self.read_ack(timeout_s=timeout_s)
            if ack is not None and ack != BSL_ACK:
                err_msg = UART_ERRORS.get(ack, f"Unknown (0x{ack:02X})")
                print(f"  {cmd_name} packet error: {err_msg}")
                return None, None
    
            raw = self.read_response_raw(timeout_s=timeout_s)
            if not raw:
                print(f"  No response to {cmd_name}")
                return None, None
    
            return parse_response(raw)
    
        def cmd_unlock(self, password: bytes = None) -> bool:
            """Send Unlock BSL command (0x21) with 32-byte password."""
            if password is None:
                password = b"\xFF" * PASSWORD_SIZE
            if len(password) != PASSWORD_SIZE:
                print(f"  Password must be {PASSWORD_SIZE} bytes (got {len(password)})")
                return False
    
            packet = build_packet(CMD_UNLOCK_BSL, password)
            if not self.send_packet(packet):
                return False
    
            resp_cmd, data = self._read_ack_then_response("Unlock BSL")
            if resp_cmd == RSP_MESSAGE_REPLY:
                err = data[0] if data else 0xFF
                if err == 0x00:
                    print("  BSL unlocked successfully")
                    return True
                else:
                    print(f"  Unlock failed: {BSL_ERRORS.get(err, f'0x{err:02X}')}")
                    return False
            elif resp_cmd is None:
                return False
            else:
                print(f"  Unexpected response to unlock (cmd=0x{resp_cmd:02X})")
                return False
    
        def cmd_mass_erase(self) -> bool:
            """Send Mass Erase command (0x15)."""
            packet = build_packet(CMD_MASS_ERASE)
            if not self.send_packet(packet):
                return False
    
            resp_cmd, data = self._read_ack_then_response("Mass Erase", timeout_s=10.0)
            if resp_cmd == RSP_MESSAGE_REPLY:
                err = data[0] if data else 0xFF
                if err == 0x00:
                    print("  Mass erase complete")
                    return True
                else:
                    print(f"  Mass erase failed: {BSL_ERRORS.get(err, f'0x{err:02X}')}")
                    return False
            elif resp_cmd is None:
                return False
            else:
                print(f"  Unexpected response to mass erase (cmd=0x{resp_cmd:02X})")
                return False
    
        def cmd_program_data(self, addr: int, data: bytes) -> bool:
            """Send Program Data command (0x20) for a single chunk.
    
            Address and data length must be 16-byte aligned per ROM requirements.
            """
            if len(data) > self.max_chunk_size:
                print(f"  Chunk too large: {len(data)} bytes (max {self.max_chunk_size})")
                return False
    
            addr_bytes = struct.pack("<I", addr)
            packet = build_packet(CMD_PROGRAM_DATA, addr_bytes + data)
            if not self.send_packet(packet):
                return False
    
            resp_cmd, resp_data = self._read_ack_then_response(f"Program @0x{addr:08X}")
            if resp_cmd == RSP_MESSAGE_REPLY:
                err = resp_data[0] if resp_data else 0xFF
                if err == 0x00:
                    return True
                else:
                    print(f"\n  Program failed at 0x{addr:08X}: {BSL_ERRORS.get(err, f'0x{err:02X}')}")
                    return False
            elif resp_cmd is None:
                return False
            else:
                print(f"\n  Unexpected response at 0x{addr:08X} (cmd=0x{resp_cmd:02X})")
                return False
    
        def cmd_program_segments(self, segments: List[Tuple[int, bytes]]) -> bool:
            """Program all firmware segments in chunks.
    
            Pads each chunk to 16-byte alignment as required by the AM13 ROM BSL.
            """
            total_bytes = sum(len(d) for _, d in segments)
            programmed = 0
    
            for seg_addr, seg_data in segments:
                # Pad segment data to 16-byte alignment
                pad_len = (PROGRAM_ALIGNMENT - (len(seg_data) % PROGRAM_ALIGNMENT)) % PROGRAM_ALIGNMENT
                if pad_len:
                    seg_data = seg_data + b"\xFF" * pad_len
    
                # Ensure segment start address is 16-byte aligned
                if seg_addr % PROGRAM_ALIGNMENT != 0:
                    print(f"\n  WARNING: Segment address 0x{seg_addr:08X} is not 16-byte aligned")
    
                offset = 0
                while offset < len(seg_data):
                    chunk_size = min(self.max_chunk_size, len(seg_data) - offset)
                    # Round chunk size down to 16-byte alignment
                    chunk_size = (chunk_size // PROGRAM_ALIGNMENT) * PROGRAM_ALIGNMENT
                    if chunk_size == 0:
                        chunk_size = PROGRAM_ALIGNMENT
    
                    chunk = seg_data[offset : offset + chunk_size]
                    addr = seg_addr + offset
    
                    if not self.cmd_program_data(addr, chunk):
                        return False
    
                    offset += chunk_size
                    programmed += min(chunk_size, total_bytes - (programmed))
                    pct = min((programmed / total_bytes) * 100, 100.0)
                    print(f"\r  Programming: {programmed}/{total_bytes} bytes ({pct:.1f}%)", end="", flush=True)
    
            print()  # newline after progress
            return True
    
        def cmd_standalone_verify(self, addr: int, length: int) -> Optional[int]:
            """Send Standalone Verification command (0x26). Returns CRC or None."""
            payload = struct.pack("<II", addr, length)
            packet = build_packet(CMD_STANDALONE_VERIFY, payload)
            if not self.send_packet(packet):
                return None
    
            resp_cmd, data = self._read_ack_then_response("Standalone Verify", timeout_s=10.0)
            if resp_cmd == RSP_STANDALONE_VERIFY:
                if data and len(data) >= 4:
                    return struct.unpack_from("<I", data, 0)[0]
                return None
            elif resp_cmd == RSP_MESSAGE_REPLY:
                err = data[0] if data else 0xFF
                print(f"  Verification error: {BSL_ERRORS.get(err, f'0x{err:02X}')}")
                return None
            elif resp_cmd is None:
                return None
            else:
                print(f"  Unexpected response to verify (cmd=0x{resp_cmd:02X})")
                return None
    
        def cmd_memory_readback(self, addr: int, length: int) -> Optional[bytes]:
            """Send Memory Read Back command (0x29)."""
            payload = struct.pack("<II", addr, length)
            packet = build_packet(CMD_MEMORY_READ_BACK, payload)
            if not self.send_packet(packet):
                return None
    
            resp_cmd, data = self._read_ack_then_response("Memory Read Back")
            if resp_cmd == RSP_MEMORY_READ_BACK:
                return data
            elif resp_cmd == RSP_MESSAGE_REPLY:
                err = data[0] if data else 0xFF
                print(f"  Read back error: {BSL_ERRORS.get(err, f'0x{err:02X}')}")
                return None
            elif resp_cmd is None:
                return None
            else:
                print(f"  Unexpected response to readback (cmd=0x{resp_cmd:02X})")
                return None
    
        def cmd_factory_reset(self) -> bool:
            """Send Factory Reset command (0x30)."""
            packet = build_packet(CMD_FACTORY_RESET)
            if not self.send_packet(packet):
                return False
    
            resp_cmd, data = self._read_ack_then_response("Factory Reset")
            if resp_cmd == RSP_MESSAGE_REPLY:
                err = data[0] if data else 0xFF
                if err == 0x00:
                    print("  Factory reset complete")
                    return True
                else:
                    print(f"  Factory reset failed: {BSL_ERRORS.get(err, f'0x{err:02X}')}")
                    return False
            elif resp_cmd is None:
                return False
            else:
                print(f"  Unexpected response to factory reset (cmd=0x{resp_cmd:02X})")
                return False
    
        def cmd_start_application(self) -> bool:
            """Send Start Application command (0x40).
    
            The ROM BSL clears RAM, posts the flash semaphore, and resets the
            device with DL_SYSCTL_RESET_BOOTLOADER_EXIT. No response is sent
            because the device resets immediately after processing.
            """
            packet = build_packet(CMD_START_APPLICATION)
            if not self.send_packet(packet):
                return False
    
            # Wait briefly for the ACK (device may reset before sending response)
            ack = self.read_ack(timeout_s=2.0)
            if ack is not None and ack == BSL_ACK:
                if self.verbose:
                    print("  ACK received before device reset")
    
            print("  Start Application command sent (device resetting)")
            return True
    
        # -- High-level sequences -----------------------------------------------
    
        def connect_to_bsl(self) -> bool:
            """Full connection sequence: invoke BSL mode, then send connection command."""
            self.invoke_bsl_mode()
            time.sleep(0.2)
            self.ser.reset_input_buffer()
            print("  Sending connection command...")
            return self.cmd_connection()
    
    
    # ---------------------------------------------------------------------------
    # Helper to create BSLConnection from parsed args
    # ---------------------------------------------------------------------------
    def make_bsl(args) -> BSLConnection:
        port = args.port or DEFAULT_PORT
    
        if args.verbose:
            print(f"  Port: {port}")
    
        return BSLConnection(
            port=port,
            baud_rate=args.baud,
            no_invoke=args.no_invoke,
            verbose=args.verbose,
        )
    
    
    # ---------------------------------------------------------------------------
    # CLI command handlers
    # ---------------------------------------------------------------------------
    def cmd_connect(args):
        """Test BSL connection."""
        print("\n=== BSL Connection Test ===")
        bsl = make_bsl(args)
        try:
            wait = getattr(args, 'wait', False)
            if not bsl.open(wait=wait):
                return 1
            if not bsl.connect_to_bsl():
                return 1
            print("\n  Connection test successful!")
            return 0
        finally:
            bsl.close()
    
    
    def cmd_get_id(args):
        """Get device identity information."""
        print("\n=== Get Device Identity ===")
        bsl = make_bsl(args)
        try:
            if not bsl.open(wait=getattr(args, 'wait', False)):
                return 1
            if not bsl.connect_to_bsl():
                return 1
    
            data = bsl.cmd_get_identity()
            if data:
                print(f"  Device info: {data.hex()}")
                return 0
            else:
                print("  Failed to get device identity")
                return 1
        finally:
            bsl.close()
    
    
    def cmd_unlock(args):
        """Unlock BSL with password."""
        print("\n=== Unlock BSL ===")
        bsl = make_bsl(args)
        try:
            if not bsl.open(wait=getattr(args, 'wait', False)):
                return 1
            if not bsl.connect_to_bsl():
                return 1
    
            password = None
            if args.password:
                password = bytes.fromhex(args.password)
            if bsl.cmd_unlock(password):
                return 0
            return 1
        finally:
            bsl.close()
    
    
    def cmd_erase(args):
        """Mass erase flash."""
        print("\n=== Mass Erase ===")
        bsl = make_bsl(args)
        try:
            if not bsl.open(wait=getattr(args, 'wait', False)):
                return 1
            if not bsl.connect_to_bsl():
                return 1
            if not bsl.cmd_unlock():
                return 1
            print("\n  Erasing flash...")
            if bsl.cmd_mass_erase():
                return 0
            return 1
        finally:
            bsl.close()
    
    
    def cmd_flash(args):
        """Full flash sequence: connect -> unlock -> erase -> program -> start app."""
        print("\n=== Flash Firmware ===")
    
        segments = load_firmware(args.file, start_addr=args.address)
        if not segments:
            print("  ERROR: No data found in firmware file")
            return 1
    
        total_bytes = sum(len(d) for _, d in segments)
        print(f"  Loaded {len(segments)} segment(s), {total_bytes} bytes total")
        for addr, data in segments:
            print(f"    0x{addr:08X} - 0x{addr + len(data) - 1:08X} ({len(data)} bytes)")
    
        bsl = make_bsl(args)
        try:
            if not bsl.open(wait=getattr(args, 'wait', False)):
                return 1
    
            # Step 1: Connect
            print("\n--- Step 1: Connect ---")
            if not bsl.connect_to_bsl():
                return 1
    
            # Step 1b: Get device identity (discovers buffer size for optimal chunking)
            print("\n--- Step 1b: Get Device Identity ---")
            id_data = bsl.cmd_get_identity()
            if id_data:
                print(f"  Device info: {id_data.hex()}")
                print(f"  Using chunk size: {bsl.max_chunk_size} bytes")
            else:
                print(f"  Could not get device identity, using default chunk size: {bsl.max_chunk_size} bytes")
    
            # Step 2: Unlock
            print("\n--- Step 2: Unlock BSL ---")
            password = None
            if args.password:
                password = bytes.fromhex(args.password)
            if not bsl.cmd_unlock(password):
                return 1
    
            # Step 3: Erase (choose appropriate erase command based on memory addresses)
            if not args.no_erase:
                # Check if any programming address is in non-main memory
                if has_non_main_memory_addresses(segments):
                    print("\n--- Step 3: Factory Reset (non-main memory detected) ---")
                    if not bsl.cmd_factory_reset():
                        return 1
                else:
                    print("\n--- Step 3: Mass Erase (main memory only) ---")
                    if not bsl.cmd_mass_erase():
                        return 1
            else:
                print("\n--- Step 3: Erase skipped (--no-erase) ---")
    
            # Step 4: Program
            print("\n--- Step 4: Program ---")
            if not bsl.cmd_program_segments(segments):
                return 1
            print("  Programming complete!")
    
            # Step 5: Start application
            if not args.no_start:
                print("\n--- Step 5: Start Application ---")
                bsl.cmd_start_application()
            else:
                print("\n--- Step 5: Start skipped (--no-start) ---")
    
            print("\n  Flash sequence complete!")
            return 0
    
        finally:
            bsl.close()
    
    
    def cmd_read(args):
        """Read back memory."""
        print("\n=== Memory Read Back ===")
        addr = int(args.address, 16) if isinstance(args.address, str) else args.address
        length = int(args.length, 0)
    
        bsl = make_bsl(args)
        try:
            if not bsl.open(wait=getattr(args, 'wait', False)):
                return 1
            if not bsl.connect_to_bsl():
                return 1
            if not bsl.cmd_unlock():
                return 1
    
            print(f"\n  Reading {length} bytes from 0x{addr:08X}...")
            data = bsl.cmd_memory_readback(addr, length)
            if data:
                print(f"  Data: {data.hex()}")
                # Pretty hex dump
                for i in range(0, len(data), 16):
                    hex_part = " ".join(f"{b:02X}" for b in data[i:i+16])
                    ascii_part = "".join(chr(b) if 32 <= b < 127 else "." for b in data[i:i+16])
                    print(f"  {addr + i:08X}  {hex_part:<48s}  {ascii_part}")
                return 0
            else:
                print("  Failed to read memory")
                return 1
        finally:
            bsl.close()
    
    
    def cmd_verify(args):
        """Verify programmed firmware via standalone CRC check."""
        print("\n=== Standalone Verification ===")
    
        segments = load_firmware(args.file, start_addr=args.address)
        if not segments:
            print("  ERROR: No data found in firmware file")
            return 1
    
        bsl = make_bsl(args)
        try:
            if not bsl.open(wait=getattr(args, 'wait', False)):
                return 1
            if not bsl.connect_to_bsl():
                return 1
            if not bsl.cmd_unlock():
                return 1
    
            all_ok = True
            for seg_addr, seg_data in segments:
                print(f"\n  Verifying 0x{seg_addr:08X} ({len(seg_data)} bytes)...")
                device_crc = bsl.cmd_standalone_verify(seg_addr, len(seg_data))
                if device_crc is not None:
                    local_crc = crc32(seg_data)
                    if device_crc == local_crc:
                        print(f"  CRC match: 0x{device_crc:08X}")
                    else:
                        print(f"  CRC MISMATCH: device=0x{device_crc:08X} local=0x{local_crc:08X}")
                        all_ok = False
                else:
                    print("  Verification failed")
                    all_ok = False
    
            return 0 if all_ok else 1
        finally:
            bsl.close()
    
    
    def cmd_start_app(args):
        """Start the application."""
        print("\n=== Start Application ===")
        bsl = make_bsl(args)
        try:
            if not bsl.open(wait=getattr(args, 'wait', False)):
                return 1
            if not bsl.connect_to_bsl():
                return 1
            bsl.cmd_start_application()
            return 0
        finally:
            bsl.close()
    
    
    def cmd_factory_reset(args):
        """Factory reset the device."""
        print("\n=== Factory Reset ===")
        bsl = make_bsl(args)
        try:
            if not bsl.open(wait=getattr(args, 'wait', False)):
                return 1
            if not bsl.connect_to_bsl():
                return 1
            if not bsl.cmd_unlock():
                return 1
            if bsl.cmd_factory_reset():
                return 0
            return 1
        finally:
            bsl.close()
    
    
    # ---------------------------------------------------------------------------
    # Main / argparse
    # ---------------------------------------------------------------------------
    def main():
        parser = argparse.ArgumentParser(
            description="AM13E2x BSL CLI - Bootstrap Loader tool for UART programming",
            formatter_class=argparse.RawDescriptionHelpFormatter,
            epilog="""\
    Examples:
      %(prog)s connect                          # Test BSL connection
      %(prog)s get-id                           # Get device identity
      %(prog)s flash firmware.out               # Flash ELF (.out) firmware (full sequence)
      %(prog)s flash firmware.hex               # Flash Intel HEX firmware
      %(prog)s flash firmware.txt               # Flash TI-TXT firmware
      %(prog)s flash firmware.bin --address 0x0 # Flash raw binary at address
      %(prog)s erase                            # Mass erase flash
      %(prog)s read 0x00000000 256              # Read 256 bytes from address 0
      %(prog)s start-app                        # Start the application
      %(prog)s --port COM5 connect              # Use specific COM port
    
    BSL Invocation:
      1. Flash main.c using CCS (one-time, device enters BSL mode)
      2. Run: %(prog)s --no-invoke flash firmware.out
      3. To flash again: Re-flash main.c, then repeat step 2
            """,
        )
    
        # Global arguments
        parser.add_argument("--port", "-p", default=None,
                            help="Serial port (e.g., COM5). Auto-detects XDS110 if not specified.")
        parser.add_argument("--baud", "-b", type=int, default=DEFAULT_BAUD_RATE,
                            help=f"Baud rate (default: {DEFAULT_BAUD_RATE})")
        parser.add_argument("--no-invoke", action="store_true",
                            help="Skip BSL invocation (device already in BSL mode via main.c)")
        parser.add_argument("--wait", action="store_true",
                            help="Wait for COM port to appear (use with unplug/replug workflow)")
        parser.add_argument("--verbose", "-v", action="store_true",
                            help="Enable verbose output (show raw TX/RX bytes)")
    
        subparsers = parser.add_subparsers(dest="command", help="Available commands")
    
        # connect
        p_connect = subparsers.add_parser("connect", help="Test BSL connection")
        p_connect.set_defaults(func=cmd_connect)
    
        # get-id
        p_getid = subparsers.add_parser("get-id", help="Get device identity")
        p_getid.set_defaults(func=cmd_get_id)
    
        # unlock
        p_unlock = subparsers.add_parser("unlock", help="Unlock BSL")
        p_unlock.add_argument("--password", default=None,
                              help="32-byte password as hex string (default: all 0xFF)")
        p_unlock.set_defaults(func=cmd_unlock)
    
        # erase
        p_erase = subparsers.add_parser("erase", help="Mass erase flash")
        p_erase.set_defaults(func=cmd_erase)
    
        # flash
        p_flash = subparsers.add_parser("flash", help="Flash firmware (connect -> unlock -> erase -> program -> start)")
        p_flash.add_argument("file", help="Firmware file (.out=ELF, .hex=Intel HEX, .txt=TI-TXT, .bin=raw binary)")
        p_flash.add_argument("--address", type=lambda x: int(x, 0), default=0,
                             help="Start address for .bin files (default: 0x0)")
        p_flash.add_argument("--password", default=None,
                             help="32-byte password as hex string (default: all 0xFF)")
        p_flash.add_argument("--no-erase", action="store_true",
                             help="Skip mass erase before programming")
        p_flash.add_argument("--no-start", action="store_true",
                             help="Don't start application after programming")
        p_flash.set_defaults(func=cmd_flash)
    
        # read
        p_read = subparsers.add_parser("read", help="Read back memory")
        p_read.add_argument("address", help="Start address (hex, e.g., 0x00000000)")
        p_read.add_argument("length", help="Number of bytes to read")
        p_read.set_defaults(func=cmd_read)
    
        # verify
        p_verify = subparsers.add_parser("verify", help="Verify programmed firmware via CRC")
        p_verify.add_argument("file", help="Firmware file to verify against")
        p_verify.add_argument("--address", type=lambda x: int(x, 0), default=0,
                              help="Start address for .bin files (default: 0x0)")
        p_verify.set_defaults(func=cmd_verify)
    
        # start-app
        p_start = subparsers.add_parser("start-app", help="Start the application")
        p_start.set_defaults(func=cmd_start_app)
    
        # factory-reset
        p_reset = subparsers.add_parser("factory-reset", help="Factory reset the device")
        p_reset.set_defaults(func=cmd_factory_reset)
    
        args = parser.parse_args()
    
        if not args.command:
            parser.print_help()
            return 0
    
        return args.func(args)
    
    
    if __name__ == "__main__":
        sys.exit(main())
    

    If you want to factory reset/mass erase, the CCS20.50 contains some gel scripts for Mass Erase and Factory reset as well to erase configs.

    Regards,
    Shaunak

  • Hi Shaunak!

    Thanks for sharing examples!

    But we were rather looking for a solution to do it within the software. So the software itself will modify the non-main region when certain conditions match, like MSPM0G "flashctl_nonmain_memory_write" example shows. Sorry that I haven't made it clear in the original request.

    BR,
    Oleksandr

  • Hi Oleksandr,

    I have added this new requirement for the SDK: https://jira.itg.ti.com/browse/ASM_IND_REQ-183, I will discuss this with the development team.

    Regards,
    Shaunak