MBED board as programmer for CC254x chips. Visit https://github.com/RedBearLab/CCLoader for more details.

Dependencies:   mbed

Devices running Android 8.0 (or newer) are not able to discover cheap CC41-A Bluetooth LE modules anymore - read "Should you throw away your CC41 HM-10 clones now that Android 8 is here?".

Since RedBearLab figured out how to use Arduino as programmer for CC254x chips a simple fix is flashing a genuine HM-10 firmware to CC41-A modules. Visit Bluetooth BLE Adventures to learn how to do it.

To use an Mbed board rather than Arduino this repository provides RedBearLab's CCLoader ported to Mbed. After compiling and flashing, it converts an Mbed board to a CC254x programmer.

Usage:

  • Compile and download this program to an Mbed board equipped with Arduino header.
  • Keep the Mbed board connected to the PC over a USB cable.
  • Connect the Mbed board to the CC41-A BLE module as shown in the picture below: /media/uploads/hudakz/ccloader1.png
  • Run RedBearLab's CCLoader.exe on your PC to flash the CC41-A module using the MBED board as a programmer.
    For example:

> ccloader_x86_64 3 cc2541hm10v540.bin 1

main.cpp

Committer:
hudakz
Date:
2019-06-19
Revision:
1:c874ea9c1afb
Parent:
0:0834641c241a

File content as of revision 1:c874ea9c1afb:

/*

Copyright (c) 2012-2014 RedBearLab

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

Ported to MBED by Zoltan Hudak 2019

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/
/******************************************************************************
* INCLUDES
*/
#include "mbed.h"

/******************************************************************************
* DEFINES
*/
// Start addresses on DUP (Increased buffer size improves performance)
#define ADDR_BUF0                   0x0000 // Buffer (512 bytes)
#define ADDR_DMA_DESC_0             0x0200 // DMA descriptors (8 bytes)
#define ADDR_DMA_DESC_1             (ADDR_DMA_DESC_0 + 8)

// DMA channels used on DUP
#define CH_DBG_TO_BUF0              0x01   // Channel 0
#define CH_BUF0_TO_FLASH            0x02   // Channel 1

// Debug commands
#define CMD_CHIP_ERASE              0x10
#define CMD_WR_CONFIG               0x19
#define CMD_RD_CONFIG               0x24
#define CMD_READ_STATUS             0x30
#define CMD_RESUME                  0x4C
#define CMD_DEBUG_INSTR_1B          (0x54|1)
#define CMD_DEBUG_INSTR_2B          (0x54|2)
#define CMD_DEBUG_INSTR_3B          (0x54|3)
#define CMD_BURST_WRITE             0x80
#define CMD_GET_CHIP_ID             0x68

// Debug status bitmasks
#define STATUS_CHIP_ERASE_BUSY_BM   0x80 // New debug interface
#define STATUS_PCON_IDLE_BM         0x40
#define STATUS_CPU_HALTED_BM        0x20
#define STATUS_PM_ACTIVE_BM         0x10
#define STATUS_HALT_STATUS_BM       0x08
#define STATUS_DEBUG_LOCKED_BM      0x04
#define STATUS_OSC_STABLE_BM        0x02
#define STATUS_STACK_OVERFLOW_BM    0x01

// DUP registers (XDATA space address)
#define DUP_DBGDATA                 0x6260  // Debug interface data buffer
#define DUP_FCTL                    0x6270  // Flash controller
#define DUP_FADDRL                  0x6271  // Flash controller addr
#define DUP_FADDRH                  0x6272  // Flash controller addr
#define DUP_FWDATA                  0x6273  // Clash controller data buffer
#define DUP_CLKCONSTA               0x709E  // Sys clock status
#define DUP_CLKCONCMD               0x70C6  // Sys clock configuration
#define DUP_MEMCTR                  0x70C7  // Flash bank xdata mapping
#define DUP_DMA1CFGL                0x70D2  // Low byte, DMA config ch. 1
#define DUP_DMA1CFGH                0x70D3  // Hi byte , DMA config ch. 1
#define DUP_DMA0CFGL                0x70D4  // Low byte, DMA config ch. 0
#define DUP_DMA0CFGH                0x70D5  // Low byte, DMA config ch. 0
#define DUP_DMAARM                  0x70D6  // DMA arming register

// Utility macros
//! Low nibble of 16bit variable
#define LOBYTE(w)           ((unsigned char)(w))
//! High nibble of 16bit variable
#define HIBYTE(w)           ((unsigned char)(((unsigned short)(w) >> 8) & 0xFF))

// Commands to Bootloader
#define SBEGIN                      0x01
#define SDATA                       0x02
#define SRSP                        0x03
#define SEND                        0x04
#define ERRO                        0x05
#define WAITING                     0x00
#define RECEIVING                   0x01

// Debug control pins & the indicate LED
DigitalOut          reset(D4);
DigitalOut          dc(D5);
DigitalInOut        dd(D6);
DigitalOut          led(LED1);
Serial              serial(USBTX, USBRX);

/******************************************************************************
 VARIABLES*/
//! DUP DMA descriptor
const unsigned char dmaDesc0[8] =
{
    // Debug Interface -> Buffer
    HIBYTE(DUP_DBGDATA),            // src[15:8]
    LOBYTE(DUP_DBGDATA),            // src[7:0]
    HIBYTE(ADDR_BUF0),              // dest[15:8]
    LOBYTE(ADDR_BUF0),              // dest[7:0]
    0,                              // len[12:8] - filled in later
    0,                              // len[7:0]
    31,                             // trigger: DBG_BW
    0x11                            // increment destination
};
//! DUP DMA descriptor
const unsigned char dmaDesc1[8] =
{
    // Buffer -> Flash controller
    HIBYTE(ADDR_BUF0),              // src[15:8]
    LOBYTE(ADDR_BUF0),              // src[7:0]
    HIBYTE(DUP_FWDATA),             // dest[15:8]
    LOBYTE(DUP_FWDATA),             // dest[7:0]
    0,                              // len[12:8] - filled in later
    0,                              // len[7:0]
    18,                             // trigger: FLASH
    0x42,                           // increment source
};

/**************************************************************************/
/**
* @brief    Writes a byte on the debug interface. Requires DD to be
*           output when function is called.
* @param    data    Byte to write
* @return   None.
******************************************************************************/
#pragma inline
void writeDebugByte(unsigned char data)
{
    unsigned char   i;
    for (i = 0; i < 8; i++) {
        // Set clock high and put data on DD line
        dc = 1;
        if (data & 0x80) {
            dd = 1;
        }
        else {
            dd = 0;
        }

        data <<= 1;
        dc = 0; // set clock low (DUP capture flank)
    }
}

/**************************************************************************/
/**
* @brief    Reads a byte from the debug interface. Requires DD to be
*           input when function is called.
* @return   Returns the byte read.
******************************************************************************/
#pragma inline
unsigned char readDebugByte(void)
{
    unsigned char   i;
    unsigned char   data = 0x00;

    for (i = 0; i < 8; i++) {
        dc = 1; // DC high
        data <<= 1;
        if (dd == 1) {
            data |= 0x01;
        }

        dc = 0; // DC low
    }

    return data;
}

/**************************************************************************/
/**
* @brief    Function waits for DUP to indicate that it is ready. The DUP will
*           pulls DD line low when it is ready. Requires DD to be input when
*           function is called.
* @return   Returns 0 if function timed out waiting for DD line to go low
* @return   Returns 1 when DUP has indicated it is ready.
******************************************************************************/
#pragma inline
unsigned char waitDupReady(void)
{
    // DUP pulls DD low when ready
    unsigned int    count = 0;
    while ((dd == 1) && count < 16) {
        // Clock out 8 bits before checking if DD is low again
        readDebugByte();
        count++;
    }

    return(count == 16) ? 0 : 1;
}

/**************************************************************************/

/**
* @brief    Issues a command on the debug interface. Only commands that return
*           one output byte are supported.
* @param    cmd             Command byte
* @param    cmd_bytes       Pointer to the array of data bytes following the
*                           command byte [0-3]
* @param    num_cmd_bytes   The number of data bytes (input to DUP) [0-3]
* @return   Data returned by command
******************************************************************************/
unsigned char debugCommand(unsigned char cmd, unsigned char* cmd_bytes, unsigned short num_cmd_bytes)
{
    unsigned short  i;
    unsigned char   output = 0;
    // Make sure DD is output
    dd.output();
    // Send command
    writeDebugByte(cmd);
    // Send bytes
    for (i = 0; i < num_cmd_bytes; i++) {
        writeDebugByte(cmd_bytes[i]);
    }

    // Set DD as input
    dd.input();
    dd.mode(PullUp);
    // Wait for data to be ready
    waitDupReady();
    // Read returned byte
    output = readDebugByte();
    // Set DD as output
    dd.output();

    return output;
}

/**************************************************************************/

/**
* @brief    Resets the DUP into debug mode. Function assumes that
*           the programmer I/O has already been configured using e.g.
*           ProgrammerInit().
* @return   None.
******************************************************************************/
void debugInit(void)
{
    volatile unsigned char  i;

    // Send two flanks on DC while keeping RESET_N low
    // All low (incl. RESET_N)
    dd = 0;
    dc = 0;
    reset = 0;
    wait_ms(10);    // Wait
    dc = 1;         // DC high
    wait_ms(10);    // Wait
    dc = 0;         // DC low
    wait_ms(10);    // Wait
    dc = 1;         // DC high
    wait_ms(10);    // Wait
    dc = 0;         // DC low
    wait_ms(10);    // Wait
    reset = 1;      // Release RESET_N
    wait_ms(10);    // Wait
}

/**************************************************************************/

/**
* @brief    Reads the chip ID over the debug interface using the
*           GET_CHIP_ID command.
* @return   Returns the chip id returned by the DUP
******************************************************************************/
unsigned char readChipId(void)
{
    unsigned char   id = 0;

    // Make sure DD is output
    dd.output();
    wait_ms(1);
    // Send command
    writeDebugByte(CMD_GET_CHIP_ID);
    // Set DD as input
    dd.input();
    dd.mode(PullUp);
    wait_ms(1);
    // Wait for data to be ready
    if (waitDupReady() == 1) {
        // Read ID and revision
        id = readDebugByte();   // ID
        readDebugByte();        // Revision (discard)
    }

    // Set DD as output
    dd.output();

    return id;
}

/**************************************************************************/

/**
* @brief    Sends a block of data over the debug interface using the
*           BURST_WRITE command.
* @param    src         Pointer to the array of input bytes
* @param    num_bytes   The number of input bytes
* @return   None.
******************************************************************************/
void burstWriteBlock(unsigned char* src, unsigned short num_bytes)
{
    unsigned short  i;

    // Make sure DD is output
    dd.output();

    writeDebugByte(CMD_BURST_WRITE | HIBYTE(num_bytes));
    writeDebugByte(LOBYTE(num_bytes));
    for (i = 0; i < num_bytes; i++) {
        writeDebugByte(src[i]);
    }

    // Set DD as input
    dd.input();
    dd.mode(PullUp);
    // Wait for DUP to be ready
    waitDupReady();
    readDebugByte();    // ignore output
    // Set DD as output
    dd.output();
}

/**************************************************************************/

/**
* @brief    Issues a CHIP_ERASE command on the debug interface and waits for it
*           to complete.
* @return   None.
******************************************************************************/
void chipErase(void)
{
    volatile unsigned char  status;
    // Send command
    debugCommand(CMD_CHIP_ERASE, 0, 0);

    // Wait for status bit 7 to go low
    do {
        status = debugCommand(CMD_READ_STATUS, 0, 0);
    } while ((status & STATUS_CHIP_ERASE_BUSY_BM));
}

/**************************************************************************/

/**
* @brief    Writes a block of data to the DUP's XDATA space.
* @param    address     XDATA start address
* @param    values      Pointer to the array of bytes to write
* @param    num_bytes   Number of bytes to write
* @return   None.
******************************************************************************/
void writeXdataMemoryBlock(unsigned short address, const unsigned char* values, unsigned short num_bytes)
{
    unsigned char   instr[3];
    unsigned short  i;

    // MOV DPTR, address
    instr[0] = 0x90;
    instr[1] = HIBYTE(address);
    instr[2] = LOBYTE(address);
    debugCommand(CMD_DEBUG_INSTR_3B, instr, 3);

    for (i = 0; i < num_bytes; i++) {
        // MOV A, values[i]
        instr[0] = 0x74;
        instr[1] = values[i];
        debugCommand(CMD_DEBUG_INSTR_2B, instr, 2);

        // MOV @DPTR, A
        instr[0] = 0xF0;
        debugCommand(CMD_DEBUG_INSTR_1B, instr, 1);

        // INC DPTR
        instr[0] = 0xA3;
        debugCommand(CMD_DEBUG_INSTR_1B, instr, 1);
    }
}

/**************************************************************************/

/**
* @brief    Writes a byte to a specific address in the DUP's XDATA space.
* @param    address     XDATA address
* @param    value       Value to write
* @return   None.
******************************************************************************/
void writeXdataMemory(unsigned short address, unsigned char value)
{
    unsigned char   instr[3];

    // MOV DPTR, address
    instr[0] = 0x90;
    instr[1] = HIBYTE(address);
    instr[2] = LOBYTE(address);
    debugCommand(CMD_DEBUG_INSTR_3B, instr, 3);

    // MOV A, values[i]
    instr[0] = 0x74;
    instr[1] = value;
    debugCommand(CMD_DEBUG_INSTR_2B, instr, 2);

    // MOV @DPTR, A
    instr[0] = 0xF0;
    debugCommand(CMD_DEBUG_INSTR_1B, instr, 1);
}

/**************************************************************************/

/**
* @brief    Read a byte from a specific address in the DUP's XDATA space.
* @param    address     XDATA address
* @return   Value read from XDATA
******************************************************************************/
unsigned char readXdataMemory(unsigned short address)
{
    unsigned char   instr[3];

    // MOV DPTR, address
    instr[0] = 0x90;
    instr[1] = HIBYTE(address);
    instr[2] = LOBYTE(address);
    debugCommand(CMD_DEBUG_INSTR_3B, instr, 3);

    // MOVX A, @DPTR
    instr[0] = 0xE0;
    return debugCommand(CMD_DEBUG_INSTR_1B, instr, 1);
}

/**************************************************************************/

/**
* @brief    Reads 1-32767 bytes from DUP's flash to a given buffer on the
*           programmer.
* @param    bank        Flash bank to read from [0-7]
* @param    address     Flash memory start address [0x0000 - 0x7FFF]
* @param    values      Pointer to destination buffer.
* @return   None.
******************************************************************************/
void readFlashMemoryBlock
(
    unsigned char   bank,
    unsigned short  flash_addr,
    unsigned short  num_bytes,
    unsigned char*  values
)
{
    unsigned char   instr[3];
    unsigned short  i;
    unsigned short  xdata_addr = (0x8000 + flash_addr);

    // 1. Map flash memory bank to XDATA address 0x8000-0xFFFF
    writeXdataMemory(DUP_MEMCTR, bank);

    // 2. Move data pointer to XDATA address (MOV DPTR, xdata_addr)
    instr[0] = 0x90;
    instr[1] = HIBYTE(xdata_addr);
    instr[2] = LOBYTE(xdata_addr);
    debugCommand(CMD_DEBUG_INSTR_3B, instr, 3);

    for (i = 0; i < num_bytes; i++) {
        // 3. Move value pointed to by DPTR to accumulator (MOVX A, @DPTR)
        instr[0] = 0xE0;
        values[i] = debugCommand(CMD_DEBUG_INSTR_1B, instr, 1);

        // 4. Increment data pointer (INC DPTR)
        instr[0] = 0xA3;
        debugCommand(CMD_DEBUG_INSTR_1B, instr, 1);
    }
}

/**************************************************************************/

/**
* @brief    Writes 4-2048 bytes to DUP's flash memory. Parameter \c num_bytes
*           must be a multiple of 4.
* @param    src         Pointer to programmer's source buffer (in XDATA space)
* @param    start_addr  FLASH memory start address [0x0000 - 0x7FFF]
* @param    num_bytes   Number of bytes to transfer [4-1024]
* @return   None.
******************************************************************************/
void writeFlashMemoryBlock(unsigned char* src, unsigned long start_addr, unsigned short num_bytes)
{
    // 1. Write the 2 DMA descriptors to RAM
    writeXdataMemoryBlock(ADDR_DMA_DESC_0, dmaDesc0, 8);
    writeXdataMemoryBlock(ADDR_DMA_DESC_1, dmaDesc1, 8);

    // 2. Update LEN value in DUP's DMA descriptors
    unsigned char   len[2] = { HIBYTE(num_bytes), LOBYTE(num_bytes) };
    writeXdataMemoryBlock((ADDR_DMA_DESC_0 + 4), len, 2);   // LEN, DBG => ram
    writeXdataMemoryBlock((ADDR_DMA_DESC_1 + 4), len, 2);   // LEN, ram => flash
    // 3. Set DMA controller pointer to the DMA descriptors
    writeXdataMemory(DUP_DMA0CFGH, HIBYTE(ADDR_DMA_DESC_0));
    writeXdataMemory(DUP_DMA0CFGL, LOBYTE(ADDR_DMA_DESC_0));
    writeXdataMemory(DUP_DMA1CFGH, HIBYTE(ADDR_DMA_DESC_1));
    writeXdataMemory(DUP_DMA1CFGL, LOBYTE(ADDR_DMA_DESC_1));

    // 4. Set Flash controller start address (wants 16MSb of 18 bit address)
    writeXdataMemory(DUP_FADDRH, HIBYTE((start_addr)));     //>>2) ));
    writeXdataMemory(DUP_FADDRL, LOBYTE((start_addr)));     //>>2) ));
    // 5. Arm DBG=>buffer DMA channel and start burst write
    writeXdataMemory(DUP_DMAARM, CH_DBG_TO_BUF0);
    burstWriteBlock(src, num_bytes);

    // 6. Start programming: buffer to flash
    writeXdataMemory(DUP_DMAARM, CH_BUF0_TO_FLASH);
    writeXdataMemory(DUP_FCTL, 0x0A);                       //0x06
    // 7. Wait until flash controller is done
    while (readXdataMemory(DUP_FCTL) & 0x80);
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
void runDUP(void)
{
    volatile unsigned char  i;

    // Send two flanks on DC while keeping RESET_N low
    // All low (incl. RESET_N)
    dd = 0;
    dc = 0;
    reset = 0;
    wait_ms(10);    // Wait
    reset = 1;
    wait_ms(10);    // Wait
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
void programmerInit(void)
{
    dd.output();
    dd = 0;
    dc = 0;
    reset = 1;
    led = 0;
}

/**
 * @brief
 * @note
 * @param
 * @retval
 */
int main()
{
    programmerInit();
    serial.baud(115200);
    // If using Leonado as programmer,
    // it should add below code,otherwise,comment it.
    //while (!Serial);
    while (true) {
        unsigned char   chipId = 0;
        unsigned char   debugConfig = 0;
        unsigned char   goOn = 0;
        unsigned char   verify = 0;

        while (!goOn) {
            // Wait for starting
            if (serial.readable()) {
                if (serial.getc() == SBEGIN) {
                    verify = serial.getc();
                    goOn = 1;
                }
                else {
                    serial.getc();          // Clear RX buffer
                }
            }
        }

        debugInit();
        chipId = readChipId();
        if (chipId == 0) {
            serial.putc(ERRO);
            return 1;                   // No chip detected, run loop again.
        }

        runDUP();
        debugInit();

        chipErase();
        runDUP();
        debugInit();

        // Switch DUP to external crystal osc. (XOSC) and wait for it to be stable.
        // This is recommended if XOSC is available during programming. If
        // XOSC is not available, comment out these two lines.
        //writeXdataMemory(DUP_CLKCONCMD, 0x80);
        //while (readXdataMemory(DUP_CLKCONSTA) != 0x80);
        //0x80)
        // Enable DMA (Disable DMA_PAUSE bit in debug configuration)
        debugConfig = 0x22;
        debugCommand(CMD_WR_CONFIG, &debugConfig, 1);

        // Program data (start address must be word aligned [32 bit])
        serial.putc(SRSP);                  // Request data blocks
        led = 1;

        unsigned char   done = 0;
        unsigned char   state = WAITING;
        unsigned char   rxBuf[514];
        unsigned int    bufIndex = 0;
        unsigned int    addr = 0x0000;
        while (!done) {
            while (serial.readable()) {
                unsigned char   ch;
                ch = serial.getc();
                switch (state) {
                // Bootloader is waiting for a new block, each block begin with a flag byte
                case WAITING:
                    {
                        if (SDATA == ch) {
                            // Incoming bytes are data
                            state = RECEIVING;
                        }
                        else
                        if (SEND == ch) {
                            // End receiving firmware
                            done = 1;   // Exit while(1) in main function
                        }
                        break;
                    }

                // Bootloader is receiving block data
                case RECEIVING:
                    {
                        rxBuf[bufIndex] = ch;
                        bufIndex++;
                        if (bufIndex == 514) {
                            // If received one block, write it to flash
                            bufIndex = 0;

                            uint16_t    checkSum = 0x0000;
                            for (unsigned int i = 0; i < 512; i++) {
                                checkSum += rxBuf[i];
                            }

                            uint16_t    checkSum_t = rxBuf[512] << 8 | rxBuf[513];
                            if (checkSum_t != checkSum) {
                                state = WAITING;
                                serial.putc(ERRO);
                                chipErase();
                                return 1;
                            }

                            writeFlashMemoryBlock(rxBuf, addr, 512);                // src, address, count
                            if (verify) {
                                unsigned char   bank = addr / (512 * 16);
                                unsigned int    offset = (addr % (512 * 16)) * 4;
                                unsigned char   readData[512];
                                readFlashMemoryBlock(bank, offset, 512, readData);  // Bank, address, count, dest.
                                for (unsigned int i = 0; i < 512; i++) {
                                    if (readData[i] != rxBuf[i]) {
                                        // Fail
                                        state = WAITING;
                                        serial.putc(ERRO);
                                        chipErase();
                                        return 1;
                                    }
                                }
                            }

                            addr += (unsigned int)128;
                            state = WAITING;
                            serial.putc(SRSP);
                        }
                        break;
                    }

                default:
                    break;
                }
            }
        }

        led = 0;
        runDUP();
    }
}