Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 44 additions & 38 deletions CCLoader/src/CCLoader.ino
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ unsigned char read_debug_byte(void)
if(HIGH == digitalRead(DD))
{
data |= 0x01;
}
}
digitalWrite(DC, LOW); // DC low
}
return data;
Expand Down Expand Up @@ -520,27 +520,34 @@ void ProgrammerInit(void)
digitalWrite(LED, LOW);
}

void setup()
{
ProgrammerInit();
void setup()
{
ProgrammerInit();
Serial.begin(115200);
// If using Leonado as programmer,
// If using Leonado as programmer,
//it should add below code,otherwise,comment it.
while(!Serial);
}

void loop()
void loop()
{
unsigned char chip_id = 0;
unsigned char debug_config = 0;
unsigned char Continue = 0;
unsigned char Verify = 0;


// Drop any stale bytes (e.g. boot-time noise) sitting in the RX buffer
// ahead of a real handshake attempt from the host.
while(Serial.available()) Serial.read();

while(!Continue) // Wait for starting
{

if(Serial.available()==2)
{
{

if(Serial.available()>=2) // was strictly ==2: a single stray byte
// ahead of the real handshake could make
// this condition never trigger, hanging
// forever.
{
Comment on lines +546 to +550

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not discard the byte following an unexpected command.

With RX data [noise, SBEGIN, verify], Line 558 consumes SBEGIN after rejecting noise, leaving only verify; the next iteration waits forever for two bytes. Discard only the unexpected byte so the next iteration can parse the intact handshake.

Proposed fix
       if(Serial.read() == SBEGIN)
       {
         Verify = Serial.read();
         Continue = 1;
       }
-      else
-      {
-        Serial.read(); // Clear RX buffer
-      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if(Serial.available()>=2) // was strictly ==2: a single stray byte
// ahead of the real handshake could make
// this condition never trigger, hanging
// forever.
{
if(Serial.available()>=2) // was strictly ==2: a single stray byte
// ahead of the real handshake could make
// this condition never trigger, hanging
// forever.
{
if(Serial.read() == SBEGIN)
{
Verify = Serial.read();
Continue = 1;
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CCLoader/src/CCLoader.ino` around lines 546 - 550, Update the Serial
handshake parsing around the available-byte check so rejecting an unexpected
command consumes only the single unexpected byte, preserving subsequent bytes
such as SBEGIN and verify for the next iteration. Ensure the receive buffer
remains intact after noise and the valid handshake can still be parsed without
waiting indefinitely.

if(Serial.read() == SBEGIN)
{
Verify = Serial.read();
Expand All @@ -555,43 +562,43 @@ void loop()

debug_init();
chip_id = read_chip_id();
if(chip_id == 0)
if(chip_id == 0)
{
Serial.write(ERRO);
Serial.write(ERRO);
return; // No chip detected, run loop again.
}

RunDUP();
debug_init();

chip_erase();
RunDUP();
debug_init();

// 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.
write_xdata_memory(DUP_CLKCONCMD, 0x80);
while (read_xdata_memory(DUP_CLKCONSTA) != 0x80);//0x80)

// Enable DMA (Disable DMA_PAUSE bit in debug configuration)
debug_config = 0x22;
debug_command(CMD_WR_CONFIG, &debug_config, 1);

// Program data (start address must be word aligned [32 bit])
Serial.write(SRSP); // Request data blocks
digitalWrite(LED, HIGH);
digitalWrite(LED, HIGH);
unsigned char Done = 0;
unsigned char State = WAITING;
unsigned char rxBuf[514];
unsigned char rxBuf[514];
unsigned int BufIndex = 0;
unsigned int addr = 0x0000;
while(!Done)
{
while(Serial.available())
{
unsigned char ch;
ch = Serial.read();
unsigned char ch;
ch = Serial.read();
switch (State)
{
// Bootloader is waiting for a new block, each block begin with a flag byte
Expand All @@ -606,15 +613,15 @@ void loop()
Done = 1; // Exit while(1) in main function
}
break;
}
// Bootloader is receiving block data
}
// Bootloader is receiving block data
case RECEIVING:
{
{
rxBuf[BufIndex] = ch;
BufIndex++;
BufIndex++;
if (BufIndex == 514) // If received one block, write it to flash
{
BufIndex = 0;
BufIndex = 0;
uint16_t CheckSum = 0x0000;
for(unsigned int i=0; i<512; i++)
{
Expand All @@ -624,43 +631,42 @@ void loop()
if(CheckSum_t != CheckSum)
{
State = WAITING;
Serial.write(ERRO);
Serial.write(ERRO);
chip_erase();
return;
}
write_flash_memory_block(rxBuf, addr, 512); // src, address, count
}
write_flash_memory_block(rxBuf, addr, 512); // src, address, count
if(Verify)
{
unsigned char bank = addr / (512 * 16);
unsigned int offset = (addr % (512 * 16)) * 4;
unsigned char read_data[512];
read_flash_memory_block(bank, offset, 512, read_data); // Bank, address, count, dest.
for(unsigned int i = 0; i < 512; i++)
read_flash_memory_block(bank, offset, 512, read_data); // Bank, address, count, dest.
for(unsigned int i = 0; i < 512; i++)
{
if(read_data[i] != rxBuf[i])
if(read_data[i] != rxBuf[i])
{
// Fail
State = WAITING;
Serial.write(ERRO);
Serial.write(ERRO);
chip_erase();
return;
}
}
}
addr += (unsigned int)128;
addr += (unsigned int)128;
State = WAITING;
Serial.write(SRSP);
}
break;
}
}
default:
break;
}
}
}

digitalWrite(LED, LOW);
RunDUP();
}


4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ CCLoader

Burn CC25xx/HM10 firmware using a NodeMCU or Wemos D1 mini.

Known issue: CCLoader hangs with no response after a cold USB reconnect.
==========================
On ESP8266/NodeMCU-style boards (e.g. Wemos D1 Mini), the DTR/RTS auto-reset circuit that resets the chip when the serial port opens doesn't always reliably boot it back into normal run mode after a full USB unplug/replug — it can leave the chip stuck in its ROM bootloader, where it will never respond to CCLoader no matter how long you wait. This is a hardware/driver quirk, not a CCLoader bug, and a plain reset button press doesn't fix it either. The fix is to let esptool perform its own (more robust) reset handling once before running CCLoader: run `esptool --port /dev/ttyXXX chip_id` (a quick, read-only command) after any reconnect, then run CCLoader normally. This is only needed after a physical reconnect/power-cycle — it's not required right after flashing, since the upload process already leaves the board in a working state.

Flashing CC2530 or CC2531
==========================
Use the files provided in folder [`Bin`](/Bin). The BIN files are already converted and ready to flash with CCLoader<br>
Expand Down
72 changes: 63 additions & 9 deletions SourceCode/Linux/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ extern "C" {
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#include <time.h>

int RS232_OpenComport(int, int);
int RS232_PollComport(int, unsigned char *, int);
Expand Down Expand Up @@ -54,13 +55,28 @@ char comports[30][16]={"/dev/ttyS0","/dev/ttyS1","/dev/ttyS2","/dev/ttyS3","/dev

void ProcessProgram(void);

static long now_ms(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (long)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}

/* Timestamp (ms) of the last byte we received, used to detect a stalled
* handshake and retry instead of hanging forever - the board never re-sends
* SBEGIN on its own if the first attempt is dropped. */
static long last_rx_ms = 0;
static int sbegin_retries = 0;
#define SBEGIN_RETRY_INTERVAL_MS 1000
#define SBEGIN_MAX_RETRIES 10

/*
* argv[0]----.exe file name
* argv[1]----ComPort number
* argv[2]----file path
*/
int main(int arg, char *argv[])
{
{
int fLen = 0;
int device = 0;

Expand Down Expand Up @@ -111,15 +127,15 @@ int main(int arg, char *argv[])
char form[5] = ".bin";
char format[5] = " ";
fLen = strlen(argv[2]);
if(fLen < 5)
if(fLen < 5)
{
printf("File path is invalid!\n");
return 0; // file path is not valid
}
format[3] = argv[2][fLen-1];
format[2] = argv[2][fLen-2];
format[1] = argv[2][fLen-3];
format[0] = argv[2][fLen-4];
format[0] = argv[2][fLen-4];
if(0 != strcmp(form, format))
{
printf("File format must be .bin");
Expand Down Expand Up @@ -147,10 +163,19 @@ int main(int arg, char *argv[])
{
BlkTot = fsize / 512;
}

printf("Block total: %d\n", BlkTot);
BlkNum = 0;

/* Opening the port (RS232_OpenComport() above) and toggling DTR/RTS can
* trigger a reset on ESP8266/NodeMCU-style auto-reset boards. Give the
* board a moment to finish rebooting and discard any boot-time noise
* before starting the handshake, instead of racing it. */
printf("Waiting for device reset to settle...\n");
fflush(stdout);
usleep(2000000); /* 2s */
tcflush(Cport[com], TCIFLUSH);

printf("Enable transmission...\n");
unsigned char buf[2] = {SBEGIN, 0}; // Enable transmission, do not verify
if(RS232_SendBuf(com, buf, 2) != 2)
Expand All @@ -166,10 +191,38 @@ int main(int arg, char *argv[])
{
printf("Request sent already! Waiting for respond...\n");
}

last_rx_ms = now_ms();

while(!end)
{
ProcessProgram();

/* The board never re-sends SBEGIN on its own, so if the first
* attempt is dropped the original code just polled forever with no
* feedback. Resend periodically, then give up cleanly instead of
* hanging indefinitely. */
if(!DownloadProgress && !end)
{
long t = now_ms();
if(t - last_rx_ms > SBEGIN_RETRY_INTERVAL_MS)
{
sbegin_retries++;
if(sbegin_retries > SBEGIN_MAX_RETRIES)
{
printf("\nNo response after %d attempts (%d ms) - giving up.\n"
"Check the port/wiring, and that the device is running "
"(not stuck in its bootloader - reset it with esptool.py "
"if needed).\n",
sbegin_retries, SBEGIN_RETRY_INTERVAL_MS * SBEGIN_MAX_RETRIES);
fclose(pfile);
RS232_CloseComport(com);
return 1;
}
RS232_SendBuf(com, buf, 2);
last_rx_ms = t;
}
}
usleep(2000); /* don't busy-spin the CPU at 100% while polling */
}
printf("Program successfully!\n");
BlkNum = 0;
Expand All @@ -189,6 +242,8 @@ void ProcessProgram()
len = RS232_PollComport(com, &rx, 1);
if(len > 0)
{
last_rx_ms = now_ms();
sbegin_retries = 0;
switch(rx)
{
case SRSP:
Expand All @@ -202,7 +257,7 @@ void ProcessProgram()
else
{
if(BlkNum == 0)
{
{
printf("Begin programming...\n");
}
DownloadProgress = 1;
Expand All @@ -223,7 +278,7 @@ void ProcessProgram()
{
fread(buf+1, 512, 1, pfile);
}


unsigned short CheckSum = 0x0000;
//unsigned int i;
Expand All @@ -233,7 +288,7 @@ void ProcessProgram()
}
buf[513] = (CheckSum >> 8) & 0x00FF;
buf[514] = CheckSum & 0x00FF;

RS232_SendBuf(com, buf, 515);
BlkNum++;
printf("%d ", BlkNum);
Expand Down Expand Up @@ -535,4 +590,3 @@ void RS232_disableRTS(int comport_number)
#ifdef __cplusplus
} /* extern "C" */
#endif