/*========================================================================================================== Copyright (c) 2021, Quectel Wireless Solutions Co., Ltd. All rights reserved. Quectel Wireless Solutions Proprietary and Confidential. ===========================================================================================================*/ #include #include "ql_uart.h" #include "ql_application.h" #include "ql_log.h" #include "ql_fs.h" #define ymodem_log(fmt, args...) printf(fmt, ##args) //#define TX_FWCTRL_ENBLE /*---------------------------------------------------------------------------------------------------------- README!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ----------------------------------------------------------------------------------------------------------*/ // 1. This is a simple YModem receive example, which receives file from PC and saves to NVM file system. // 2. Increase customer.fs partition size flash layout if the file to be received is lare, otherwise it // may cause "No space left on device" error when saving file to NVM. // 3. After finished upload file to NVM, next step download, untick customer_fs in Aboot software to avoid // file stored deleted during flashing. /*---------------------------------------------------------------------------------------------------------- USER CONFIG ----------------------------------------------------------------------------------------------------------*/ #define NVM_SAVE_PATH "U:/received_file.bin" // <-- rename the received file if needed #define DELETE_AFTER_DOWNLOAD 0 // <-- 1 = delete after send, 0 = keep static int uartportchoose = QL_MAIN_UART_PORT; // <-- modify UART PORT if needed /*---------------------------------------------------------------------------------------------------------- DEBUG CONFIG ----------------------------------------------------------------------------------------------------------*/ #define CHECK_CHAR_RECEIVED 0 //default 0 //1 = Show log every received char in wait_char, 0 = disable log every received char in wait_char #define CHECK_CHAR_SEND 0 //default 0 //1 = Show log every sent char in send_char, 0 = disable log every sent char in send_char #if CHECK_CHAR_SEND #define CHECK_CHAR_SEND_LOG(fmt, args...) printf(fmt, ##args) #else #define CHECK_CHAR_SEND_LOG(fmt, args...) #endif /*---------------------------------------------------------------------------------------------------------- YModem constants ----------------------------------------------------------------------------------------------------------*/ #define Ymodem_SOH 0x01 #define Ymodem_STX 0x02 #define Ymodem_EOT 0x04 #define Ymodem_ACK 0x06 #define Ymodem_NAK 0x15 #define Ymodem_PACKET_STX_DATA_SIZE 1024 #define Ymodem_PACKET_SOH_DATA_SIZE 128 #define Ymodem_PACKET_STX_SEZE 1029 #define Ymodem_PACKET_SOH_SEZE 133 #define Ymodem_RECV_FILENAME 0 #define Ymodem_TRANS_DATA 1 #define Ymodem_TRANS_END_EOT1 2 #define Ymodem_TRANS_END_EOT2 3 #define Ymodem_RECV_HEAD 1 #define Ymodem_RECV_NUMBER 2 #define Ymodem_RECV_NON_NUMBER 3 #define Ymodem_RECV_DATA 4 #define Ymodem_RECEIVE_MAX_TIMES 30 #define DOWNLOAD_WAIT_C_TIMEOUT_MS 30000 #define DOWNLOAD_ACK_TIMEOUT_MS 3000 static unsigned char ch_ACK = Ymodem_ACK; static unsigned char ch_NAK = Ymodem_NAK; static unsigned char ch_eot = Ymodem_EOT; static unsigned char sequence_num = 1; static unsigned int receiving_progress = 0; static unsigned int total_progress = 0; static int state = Ymodem_RECV_FILENAME; static QFILE * fp = 0; static int g_receive_done = 0; static unsigned int total_file_size = 0; /*---------------------------------------------------------------------------------------------------------- UART initialization for YModem transfer ----------------------------------------------------------------------------------------------------------*/ static void ql_Ymodem_uart_init(void) { #ifndef TX_FWCTRL_ENBLE ql_uart_open(uartportchoose, QL_UART_BAUD_115200, QL_FC_NONE); #else ql_uart_open(uartportchoose, QL_UART_BAUD_115200, QL_FC_HW); #endif ql_uart_config_t dcb; ql_uart_get_dcbconfig(uartportchoose, &dcb); ymodem_log("[%s][%d]baudrate:%d, data_bit:%d, stop_bit:%d, parity_bit:%d, flow_ctrl:%d \r\n", __func__, __LINE__, dcb.baudrate, dcb.data_bit, dcb.stop_bit, dcb.parity_bit, dcb.flow_ctrl); dcb.baudrate = QL_UART_BAUD_115200; ql_uart_set_dcbconfig(uartportchoose, &dcb); } /*---------------------------------------------------------------------------------------------------------- CRC16 CCITT ----------------------------------------------------------------------------------------------------------*/ static unsigned short crc16_ccitt(const unsigned char *data, size_t length) { int crc = 0; int i, j; for(i = 0; i < length; i++) { crc = crc ^ (data[i] << 8); for(j = 0; j < 8; j++) { if((crc & ((int)0x8000)) != 0) crc = ((crc << 1) ^ 0x1021); else crc = crc << 1; } } return (crc & 0xFFFF); } /*---------------------------------------------------------------------------------------------------------- parse_filename and file length from the first packet, and save them to global variables for later use ----------------------------------------------------------------------------------------------------------*/ int ql_Ymodem_parse_filename(unsigned char *buf) { int filename_offset = 0; unsigned char name_buf[128] = {0}; unsigned char length_buf[128] = {0}; int catch_name_offset = 0; unsigned int file_length = 0; while(filename_offset < Ymodem_PACKET_SOH_SEZE - 3) { if(buf[filename_offset] == 0x00 && catch_name_offset == 0) { memcpy(name_buf, buf, filename_offset); ymodem_log("**********filename:%s ************\r\n", name_buf); catch_name_offset = filename_offset; } else if(catch_name_offset != 0) { if(buf[filename_offset] == 0x00 || buf[filename_offset] == ' ') { memcpy(length_buf, buf + catch_name_offset + 1, filename_offset - catch_name_offset); ymodem_log("**********file length:%s bytes************\r\n", length_buf); file_length = atoi(length_buf); total_file_size = file_length; total_progress = file_length / 1024; if((file_length % 1024) != 0) total_progress++; return 0; } } filename_offset++; } return -1; } /*---------------------------------------------------------------------------------------------------------- Write data packet to NVM file, return 0 if success, -1 if fail (e.g. CRC error) ----------------------------------------------------------------------------------------------------------*/ int ql_Ymodem_write_data(unsigned char *buf, unsigned char type) { unsigned short p_crc; unsigned short r_crc; int data_size = 0; static unsigned int offset = 0; if(type == Ymodem_SOH) data_size = Ymodem_PACKET_SOH_DATA_SIZE; else data_size = Ymodem_PACKET_STX_DATA_SIZE; p_crc = crc16_ccitt(buf, data_size); r_crc = *(buf + data_size) << 8 | *(buf + data_size + 1); if(r_crc != p_crc) { ymodem_log("r_crc:%x p_crc:%x \r\n", r_crc, p_crc); return -1; } // End of transfer — null packet with CRC=0 if(r_crc == 0 && state == Ymodem_TRANS_END_EOT2) { ql_uart_write(uartportchoose, &ch_ACK, 1); CHECK_CHAR_SEND_LOG("ACK SEND\r\n"); // Close file if(fp != NULL) { ql_fclose(fp); fp = NULL; ymodem_log("File closed. Total written: %u bytes\r\n", offset); } ymodem_log("End transfer!!!! Saved to %s\r\n", NVM_SAVE_PATH); g_receive_done = 1; offset = 0; // reset for next transfer return 0; } // Write chunk to NVM file if(fp != NULL) { int write_size = data_size; if(offset < total_file_size) { // Trim last packet if needed if((offset + data_size) > total_file_size) write_size = total_file_size - offset; int written = ql_fwrite(buf, 1, write_size, fp); if(written != write_size) { ymodem_log("ql_fwrite error! written:%d expected:%d\r\n", written, write_size); return -1; } ymodem_log("written:%d bytes\r\n", write_size); offset += write_size; } else { // Exceed file size, skip write, just continue ymodem_log("exceed file size, skip write\r\n"); } } return 0; } /*---------------------------------------------------------------------------------------------------------- Wait for specific char from PC with timeout Returns: 0 = got expected char -1 = unexpected char -2 = timeout ----------------------------------------------------------------------------------------------------------*/ static int ql_Ymodem_wait_char(unsigned char expected, int timeout_ms) { unsigned char ch; int elapsed = 0; while(elapsed < timeout_ms) { ql_uart_read(uartportchoose, &ch, 1); if(ch == expected){ #if CHECK_CHAR_RECEIVED ymodem_log("wait_char: got 0x%02X expected 0x%02X\r\n", ch, expected); #endif return 0; } ql_rtos_task_sleep_ms(10); elapsed += 10; } ymodem_log("wait_char: timeout waiting for 0x%02X\r\n", expected); return -2; } /*---------------------------------------------------------------------------------------------------------- Main YModem receive loop: handles receiving filename packet, data packets, and EOT, and writes to NVM file ----------------------------------------------------------------------------------------------------------*/ static int ql_Ymodem_upload_file(void) { unsigned char ch; unsigned char package_type = 0; unsigned char seq_num = 0; unsigned int data_size = 0; unsigned char recv_buf[1100]; int get_filename = 0; int read_package_state = Ymodem_RECV_HEAD; while(1) { if(state == Ymodem_RECV_FILENAME && read_package_state == Ymodem_RECV_HEAD) { if(get_filename != 1) { ql_uart_write(uartportchoose, "C", 1); ymodem_log("C SEND, waiting for host...\r\n"); ql_rtos_task_sleep_ms(1000); } } int nread_size = ql_uart_get_rxrb_data_len(uartportchoose); if(nread_size == 0) { ql_rtos_task_sleep_ms(5); continue; } if(read_package_state != Ymodem_RECV_DATA) { ql_uart_read(uartportchoose, &ch, 1); } if(read_package_state == Ymodem_RECV_HEAD) { if(ch == Ymodem_SOH || ch == Ymodem_STX || ch == Ymodem_EOT) { if(ch == Ymodem_EOT) { if(state != Ymodem_TRANS_END_EOT2) { state = Ymodem_TRANS_END_EOT2; ql_uart_write(uartportchoose, &ch_NAK, 1); CHECK_CHAR_SEND_LOG("NACK SEND\r\n"); ql_rtos_task_sleep_ms(100); ymodem_log("receive EOT 1 !!!!\r\n"); } else { ql_uart_write(uartportchoose, &ch_ACK, 1); CHECK_CHAR_SEND_LOG("ACK SEND\r\n"); ql_rtos_task_sleep_ms(100); ql_uart_write(uartportchoose, "C", 1); CHECK_CHAR_SEND_LOG("C SEND\r\n"); sequence_num = 0; ymodem_log("receive EOT 2 !!!!\r\n"); } } else { package_type = ch; read_package_state = Ymodem_RECV_NUMBER; } } else { ymodem_log("package type error!!!\r\n"); continue; } } else if(read_package_state == Ymodem_RECV_NUMBER) { read_package_state = Ymodem_RECV_NON_NUMBER; seq_num = ch; ymodem_log("sequence number:%u\r\n", seq_num); } else if(read_package_state == Ymodem_RECV_NON_NUMBER) { if((char)(~ch) == (char)seq_num) { read_package_state = Ymodem_RECV_DATA; if(package_type == Ymodem_STX) data_size = Ymodem_PACKET_STX_SEZE - 3; else data_size = Ymodem_PACKET_SOH_SEZE - 3; } else { ymodem_log("receive inverse number error number 1:%x number 2:%x\r\n", seq_num, ch); read_package_state = Ymodem_RECV_HEAD; ql_uart_flush_fifo(uartportchoose); ql_uart_write(uartportchoose, &ch_NAK, 1); CHECK_CHAR_SEND_LOG("NACK SEND\r\n"); continue; } } else if(read_package_state == Ymodem_RECV_DATA) { static int get_times = 0; int length = ql_uart_get_rxrb_data_len(uartportchoose); if(length < data_size) { ql_rtos_task_sleep_ms(5); if(get_times++ > Ymodem_RECEIVE_MAX_TIMES) { get_times = 0; read_package_state = Ymodem_RECV_HEAD; ql_uart_flush_fifo(uartportchoose); if(state == Ymodem_RECV_FILENAME) { ql_uart_write(uartportchoose, "C", 1); CHECK_CHAR_SEND_LOG("C SEND\r\n"); ql_rtos_task_sleep_ms(2000); } else { ql_uart_write(uartportchoose, &ch_NAK, 1); CHECK_CHAR_SEND_LOG("NACK SEND\r\n"); } } continue; } else { get_times = 0; length = data_size; ql_uart_read(uartportchoose, recv_buf, length); if(state == Ymodem_RECV_FILENAME) { if(package_type != Ymodem_SOH) { ymodem_log(" receive filename packet error\r\n"); ql_uart_flush_fifo(uartportchoose); ql_uart_write(uartportchoose, &ch_NAK, 1); CHECK_CHAR_SEND_LOG("NACK SEND\r\n"); continue; } ql_Ymodem_parse_filename(recv_buf); ql_uart_flush_fifo(uartportchoose); get_filename = 1; state = Ymodem_TRANS_DATA; // Open NVM file for writing — overwrite if exists fp = ql_fopen(NVM_SAVE_PATH, "wb"); if(fp == NULL) { ymodem_log("ql_fopen failed! path:%s\r\n", NVM_SAVE_PATH); ql_uart_write(uartportchoose, &ch_NAK, 1); CHECK_CHAR_SEND_LOG("NACK SEND\r\n"); continue; } ymodem_log("File opened for writing: %s\r\n", NVM_SAVE_PATH); ql_uart_write(uartportchoose, &ch_ACK, 1); CHECK_CHAR_SEND_LOG("ACK SEND\r\n"); ql_rtos_task_sleep_ms(10); ql_uart_write(uartportchoose, "C", 1); CHECK_CHAR_SEND_LOG("C SEND\r\n"); read_package_state = Ymodem_RECV_HEAD; } else if(state == Ymodem_TRANS_DATA || state == Ymodem_TRANS_END_EOT2) { read_package_state = Ymodem_RECV_HEAD; if(seq_num == sequence_num) { if(ql_Ymodem_write_data(recv_buf, package_type) == 0) { ql_uart_write(uartportchoose, &ch_ACK, 1); CHECK_CHAR_SEND_LOG("ACK SEND\r\n"); sequence_num++; if(g_receive_done != 1) { receiving_progress++; ymodem_log("progress receiving:total %u:%u\r\n", receiving_progress, total_progress); } if(g_receive_done == 1) { ymodem_log("File saved to NVM: %s\r\n", NVM_SAVE_PATH); return; // done, exit task } } else { ymodem_log("crc error\r\n"); ql_uart_flush_fifo(uartportchoose); ql_uart_write(uartportchoose, &ch_NAK, 1); CHECK_CHAR_SEND_LOG("NACK SEND\r\n"); } } else { ymodem_log("error sequence \r\n"); ql_uart_flush_fifo(uartportchoose); ql_uart_write(uartportchoose, &ch_NAK, 1); CHECK_CHAR_SEND_LOG("NACK SEND\r\n"); } } } } } } /*---------------------------------------------------------------------------------------------------------- Send file from NVM via YModem to PC Returns: 0 = success -1 = failed ----------------------------------------------------------------------------------------------------------*/ static int ql_Ymodem_send_file(void) { unsigned char send_buf[Ymodem_PACKET_STX_SEZE]; unsigned char data_buf[Ymodem_PACKET_STX_DATA_SIZE]; unsigned char seq = 0; unsigned short crc; int ret; int file_size = 0; int bytes_sent = 0; int read_len = 0; QFILE *fp = NULL; /*--- Step 1: Open NVM file ---*/ fp = ql_fopen(NVM_SAVE_PATH, "rb"); if(fp == NULL) { ymodem_log("YModem send: failed to open %s\r\n", NVM_SAVE_PATH); return -1; } file_size = ql_fsize(fp); if(file_size <= 0) { ymodem_log("YModem send: file empty or error, size=%d\r\n", file_size); ql_fclose(fp); return -1; } ymodem_log("YModem send: file size = %d bytes\r\n", file_size); /*--- Step 2: Wait for 'C' from TeraTerm ---*/ ymodem_log("In TeraTerm: File -> Transfer -> YMODEM -> Receive\r\n"); ymodem_log("Waiting for C (timeout 30s)...\r\n"); ret = ql_Ymodem_wait_char('C', DOWNLOAD_WAIT_C_TIMEOUT_MS); if(ret != 0) { ymodem_log("YModem send: no C received, aborting\r\n"); ql_fclose(fp); return -1; } ymodem_log("YModem send: got C, starting transfer\r\n"); /*--- Step 3: Send filename packet (seq=0) ---*/ memset(send_buf, 0, Ymodem_PACKET_SOH_SEZE); send_buf[0] = Ymodem_SOH; send_buf[1] = 0x00; send_buf[2] = 0xFF; snprintf((char *)send_buf + 3, 64, NVM_SAVE_PATH); snprintf((char *)send_buf + 3 + strlen(NVM_SAVE_PATH) + 1, 16, "%d", file_size); crc = crc16_ccitt(send_buf + 3, Ymodem_PACKET_SOH_DATA_SIZE); send_buf[3 + Ymodem_PACKET_SOH_DATA_SIZE] = (crc >> 8) & 0xFF; send_buf[3 + Ymodem_PACKET_SOH_DATA_SIZE + 1] = crc & 0xFF; ql_uart_write(uartportchoose, send_buf, Ymodem_PACKET_SOH_SEZE); ymodem_log("YModem send: filename packet sent\r\n"); ret = ql_Ymodem_wait_char(Ymodem_ACK, DOWNLOAD_ACK_TIMEOUT_MS); if(ret != 0) { ymodem_log("No ACK after filename\r\n"); ql_fclose(fp); return -1; } ret = ql_Ymodem_wait_char('C', DOWNLOAD_ACK_TIMEOUT_MS); if(ret != 0) { ymodem_log("No C after filename ACK\r\n"); ql_fclose(fp); return -1; } /*--- Step 4: Send data packets ---*/ seq = 1; while(bytes_sent < file_size) { int remaining = file_size - bytes_sent; unsigned char pkt_type; unsigned int pkt_data_size; unsigned int pkt_total_size; if(remaining >= Ymodem_PACKET_STX_DATA_SIZE) { pkt_type = Ymodem_STX; pkt_data_size = Ymodem_PACKET_STX_DATA_SIZE; pkt_total_size = Ymodem_PACKET_STX_SEZE; } else { pkt_type = Ymodem_SOH; pkt_data_size = Ymodem_PACKET_SOH_DATA_SIZE; pkt_total_size = Ymodem_PACKET_SOH_SEZE; } // Read chunk from NVM memset(data_buf, 0x1A, pkt_data_size); read_len = ql_fread(data_buf, 1, pkt_data_size, fp); if(read_len <= 0) { ymodem_log("YModem send: ql_fread error at offset %d\r\n", bytes_sent); ql_fclose(fp); return -1; } // Build packet send_buf[0] = pkt_type; send_buf[1] = seq; send_buf[2] = (unsigned char)(~seq); memcpy(send_buf + 3, data_buf, pkt_data_size); crc = crc16_ccitt(send_buf + 3, pkt_data_size); send_buf[3 + pkt_data_size] = (crc >> 8) & 0xFF; send_buf[3 + pkt_data_size + 1] = crc & 0xFF; ql_uart_write(uartportchoose, send_buf, pkt_total_size); ret = ql_Ymodem_wait_char(Ymodem_ACK, DOWNLOAD_ACK_TIMEOUT_MS); if(ret != 0) { ymodem_log("No ACK for seq=%u, aborting\r\n", seq); ql_fclose(fp); return -1; } bytes_sent += read_len; seq++; ymodem_log("sent seq=%u progress=%d/%d bytes\r\n", seq - 1, bytes_sent, file_size); } ql_fclose(fp); fp = NULL; /*--- Step 5: EOT sequence ---*/ ql_uart_write(uartportchoose, &ch_eot, 1); CHECK_CHAR_SEND_LOG("EOT1 sent\r\n"); ql_Ymodem_wait_char(Ymodem_NAK, DOWNLOAD_ACK_TIMEOUT_MS); ql_uart_write(uartportchoose, &ch_eot, 1); CHECK_CHAR_SEND_LOG("EOT2 sent\r\n"); ql_Ymodem_wait_char(Ymodem_ACK, DOWNLOAD_ACK_TIMEOUT_MS); ql_Ymodem_wait_char('C', DOWNLOAD_ACK_TIMEOUT_MS); /*--- Step 6: Null filename packet to end session ---*/ memset(send_buf, 0, Ymodem_PACKET_SOH_SEZE); send_buf[0] = Ymodem_SOH; send_buf[1] = 0x00; send_buf[2] = 0xFF; crc = crc16_ccitt(send_buf + 3, Ymodem_PACKET_SOH_DATA_SIZE); send_buf[3 + Ymodem_PACKET_SOH_DATA_SIZE] = (crc >> 8) & 0xFF; send_buf[3 + Ymodem_PACKET_SOH_DATA_SIZE + 1] = crc & 0xFF; ql_uart_write(uartportchoose, send_buf, Ymodem_PACKET_SOH_SEZE); ql_Ymodem_wait_char(Ymodem_ACK, DOWNLOAD_ACK_TIMEOUT_MS); ymodem_log("YModem send: transfer complete!\r\n"); return 0; } /*********************************************************************************************************** !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!Upload main task!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ***********************************************************************************************************/ void quec_Ymodem_upload_main() { ymodem_log("***********Ymodem upload start\r\n"); ymodem_log("Save path: %s\r\n", NVM_SAVE_PATH); ql_Ymodem_uart_init(); ql_uart_flush_fifo(uartportchoose); ql_Ymodem_upload_file(); } /*********************************************************************************************************** !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!Download main task!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ***********************************************************************************************************/ void quec_Ymodem_download_main() { ymodem_log("***********Ymodem download task start\r\n"); ymodem_log("NVM path : %s\r\n", NVM_SAVE_PATH); ymodem_log("Delete after : %d\r\n", DELETE_AFTER_DOWNLOAD); ql_Ymodem_uart_init(); ql_uart_flush_fifo(uartportchoose); // Check file exists before starting if(ql_access(NVM_SAVE_PATH, 0) != 0) { ymodem_log("File not found: %s\r\n", NVM_SAVE_PATH); ymodem_log("Run upload first, then run download.\r\n"); return; } int result = ql_Ymodem_send_file(); if(result == 0) { ymodem_log("Transfer successful!\r\n"); #if DELETE_AFTER_DOWNLOAD int del = ql_remove(NVM_SAVE_PATH); if(del == 0) ymodem_log("File deleted from NVM: %s\r\n", NVM_SAVE_PATH); else ymodem_log("Failed to delete, ret=%d\r\n", del); #else ymodem_log("File kept in NVM: %s\r\n", NVM_SAVE_PATH); #endif } else ymodem_log("Transfer failed! File kept in NVM.\r\n"); ymodem_log("Download task done.\r\n"); } //application_init(quec_Ymodem_upload_main, "quec_Ymodem_upload_main", 20, 0); //application_init(quec_Ymodem_download_main, "quec_Ymodem_download_main", 20, 0);