/** * @file mr16.c * @brief MR16 Module Implementation - SX1281 Radio Control with FreeRTOS CLI * * ============================================================================ * CODE ORGANIZATION * ============================================================================ * This file is organized into THREE main sections: * * [PART 1] MR16 CORE FUNCTIONS (核心功能) * - System initialization and main loop * - Data packet processing (Header + CRC) * - Radio event callbacks * - Failsafe protection logic * * [PART 2] UI DISPLAY FUNCTIONS (界面显示) * - LCD power-on animation * - Main status display * - Dynamic status updates * * [PART 3] CLI COMMAND FUNCTIONS (命令行接口) * - FreeRTOS CLI registration * - MR16 system commands (save/load/mode/etc) * - Radio parameter commands (modulation/packet/etc) * - Help text and documentation * * ============================================================================ * * @author MR16 Development Team * @date 2024 * @version 2.0 */ /* Includes ----------------------------------------------------------------- */ #include "module/mr16.h" #include "module/config.h" /* STM32 HAL Headers */ #include #include "usart.h" #include #include #include #include #include /* Device Driver Headers */ #include "bsp/flash.h" #include "bsp/time.h" #include "device/sx1281_driver/sx1281.h" #include "device/sx1281_driver/radio.h" #include "device/sx1281_driver/sx1281_driver.h" #include "device/lcd_driver/lcd.h" #include "device/ws2812.h" #include "device/ws2812.h" /* Component Headers */ /* crc16.h removed - hardware CRC is sufficient, no software CRC needed */ /* FreeRTOS Headers */ #include "component/FreeRTOS_CLI.h" /* Private defines --------------------------------------------------------- */ #define RXID_MIN 0x0001 #define RXID_MAX 0xFFFE #define RXID_INVALID_LOW 0x0000 #define RXID_INVALID_HIGH 0xFFFF #define PAYLOAD_MIN 1 /* 各模式最大用户数据长度(不含Header): * FLRC=122, GFSK=250, LoRa=251 * 使用最保守的FLRC限制作为默认值 */ #define PAYLOAD_MAX_FLRC MR16_FLRC_MAX_FIXED_LEN // 122字节 #define PAYLOAD_MAX_GFSK MR16_GFSK_MAX_FIXED_LEN // 250字节 #define PAYLOAD_MAX_LORA MR16_LORA_MAX_FIXED_LEN // 251字节 #define TX_POWER_MIN -18 #define TX_POWER_MAX 13 /* Private macro ------------------------------------------------------------ */ /* Private typedef ---------------------------------------------------------- */ /* Private variables -------------------------------------------------------- */ /* Radio instance and configuration */ SX1281_t radio; /* External references */ extern const unsigned char logo_M[]; /* LCD logo bitmap */ extern const unsigned char logo_R[]; /* LCD logo bitmap */ extern uint8_t uart2_data[255], uart2_datalength; /* UART data buffer */ extern volatile bool uart2_data_ready; /* UART data ready flag */ extern MR16_t mr16; /* MR16 main instance */ /* Radio communication buffers */ uint8_t radioRXBuffer[BUFFER_SIZE]; uint8_t radioRXSize; /* CLI buffers - Static allocation to prevent stack overflow */ static char cInputBuffer[64]; /* CLI input buffer */ static char cOutputBuffer[256]; /* CLI output buffer */ /* Private function prototypes ---------------------------------------------- */ int8_t MR16_CLI_Init(void); static BaseType_t mr16CommandHandler(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString); static BaseType_t radioCommandHandler(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString); static BaseType_t outputHelpText(char *pcWriteBuffer, size_t xWriteBufferLen, const char *helpText, size_t *position); static void copyCLIArg(const char *param, BaseType_t len, char *output, size_t outputSize); static const char* getFormatString(MR16_DataFormat_t format); static const char* getRadioModeString(SX1281_RadioMode_t mode); static bool isValidRXID(uint16_t rxid); static int8_t setRXID(uint16_t *rxid, const char *value, char *response, size_t responseLen); /* ========================================================================== */ /* ========================================================================== */ /* */ /* [PART 1] MR16 CORE FUNCTIONS */ /* (核心功能模块) */ /* */ /* ========================================================================== */ /* ========================================================================== */ /*---------------------------------------------------------------------------- System Initialization & Main Loop (系统初始化与主循环) *---------------------------------------------------------------------------*/ /** * @brief 初始化MR16系统(智能配置加载) * @param mr16 MR16实例指针 * @param param 参数配置指针(将被配置覆盖) * @return 0-成功, -1-失败 * * 完整的初始化流程: * 1. 从Flash智能加载配置(无效则使用默认值) * 2. 初始化CLI系统 * 3. 使用加载的参数初始化SX1281无线模块 * 4. 初始化LCD显示并启动UI */ int8_t MR16_Init(MR16_t *mr16, MR16_Param_t *param) { if (!mr16 || !param) { return -1; /* Invalid parameters */ } /* Smart config loading: try Flash first, fallback to defaults */ if (!Config_LoadMR16FromFlashBool()) { /* Flash invalid or corrupt, use safe defaults */ Config_SetDefaults(); printf("[MR16] Flash config invalid, using defaults\r\n"); } else { printf("[MR16] Config loaded from Flash successfully\r\n"); } memset(&mr16->timer, 0, sizeof(MR16_Timer_t)); /* Initialize CLI system */ MR16_CLI_Init(); /* Initialize MR16 parameters - config should be loaded before calling this function */ mr16->param = &Config_Get()->mr16; /* Initialize SX1281 radio using persistent parameters */ if (SX1281_Init(&radio, &mr16->param->radioParams, mr16->param->radioParams.radioMode) != DEVICE_OK) { return -1; /* Radio initialization failed */ } Radio.SetStandby( STDBY_RC ); switch (mr16->param->format) { case MR16_FORMAT_VARIABLE: /* 可变长度模式: PayloadLength设置为最大允许接收长度 * 芯片会自动在包中添加Header(含长度字段),自动检测实际长度 * 这里设置的是"最大允许"值,用于接收时限制 */ if(mr16->param->radioParams.RadioRole==RadioRoleTX) { radio.param->packetParams.Params.LoRa.PayloadLength = SX1281_LORA_MAX_PAYLOAD; // 253 radio.param->packetParams.Params.Flrc.PayloadLength = SX1281_FLRC_MAX_PAYLOAD; // 124 radio.param->packetParams.Params.Gfsk.PayloadLength = SX1281_GFSK_MAX_PAYLOAD; // 252 } else if(mr16->param->radioParams.RadioRole==RadioRoleRX) { radio.param->packetParams.Params.LoRa.PayloadLength = MR16_HEADER_LEN + mr16->param->fixedLength; radio.param->packetParams.Params.Flrc.PayloadLength = MR16_HEADER_LEN + mr16->param->fixedLength; radio.param->packetParams.Params.Gfsk.PayloadLength = MR16_HEADER_LEN + mr16->param->fixedLength; } break; case MR16_FORMAT_FIXED: /* 固定长度模式: PayloadLength设置为精确的包长度 * 射频芯片不添加长度字段Header,收发双方必须预知长度 * 实际长度 = Header(2) + 用户数据(fixedLength) */ radio.param->packetParams.Params.LoRa.PayloadLength = MR16_HEADER_LEN + mr16->param->fixedLength; radio.param->packetParams.Params.Flrc.PayloadLength = MR16_HEADER_LEN + mr16->param->fixedLength; radio.param->packetParams.Params.Gfsk.PayloadLength = MR16_HEADER_LEN + mr16->param->fixedLength; break; default: break; } Radio.SetPacketParams( &mr16->param->radioParams.packetParams );//包属性 if (radio.param->RadioRole == RadioRoleRX) { SX1281_SetRXSingle(&radio); // SX1281_SetRXSuccessive(&radio); } else if (radio.param->RadioRole == RadioRoleTX) { SX1281_SetTX(&radio, mr16->txbuffer, 2); } /* Initialize LCD display and show UI */ LCD_Init(1); MR16_UI_PowerON(); HAL_Delay(1500); MR16_UI_Home(mr16); return 0; /* Success */ } /** * @brief MR16主循环函数 * @param mr16 MR16实例指针 * @return 0-成功 * * 主要功能: * 1. 处理UART接收数据(自动区分CLI命令和用户数据) * 2. 根据数据格式(VARIABLE/FIXED)执行相应操作 * 3. 运行SX1281无线通信 * 4. 定期更新LCD显示 */ int8_t MR16_Main(MR16_t *mr16) { bool isRCData = false; /* Handle UART data */ if (uart2_data_ready) { /* Distinguish RC data from CLI commands by checking first byte: * CLI commands start with ASCII letters (a-z, A-Z) * RC data is binary and won't start with letter * Skip leading whitespace/newlines for CLI detection */ uint8_t firstCharIndex = 0; /* Skip leading whitespace, \r, \n */ while (firstCharIndex < uart2_datalength && (uart2_data[firstCharIndex] == ' ' || uart2_data[firstCharIndex] == '\t' || uart2_data[firstCharIndex] == '\r' || uart2_data[firstCharIndex] == '\n')) { firstCharIndex++; } if (firstCharIndex < uart2_datalength && ((uart2_data[firstCharIndex] >= 'a' && uart2_data[firstCharIndex] <= 'z') || (uart2_data[firstCharIndex] >= 'A' && uart2_data[firstCharIndex] <= 'Z'))) { /* Starts with letter - CLI command */ isRCData = false; } else if (uart2_datalength > 0) { isRCData = true; } if(!isRCData) { /* CLI Mode: Process as command */ if (uart2_datalength > 0 && uart2_datalength < sizeof(cInputBuffer)) { memcpy(cInputBuffer, uart2_data, uart2_datalength); cInputBuffer[uart2_datalength] = '\0'; /* Remove trailing \r\n for better compatibility */ int len = uart2_datalength; while (len > 0 && (cInputBuffer[len-1] == '\r' || cInputBuffer[len-1] == '\n')) { cInputBuffer[--len] = '\0'; } } else { cInputBuffer[0] = '\0'; } /* 暂停无线收发 */ Radio.SetStandby(STDBY_RC); /* CLI命令处理: 保存原状态→DEBUG黄灯→处理→恢复 */ WS2812_Status_t savedStatus = WS2812_GetStatus(); WS2812_SetStatus(WS2812_STATUS_DEBUG); /* CLI调试: 黄灯闪烁 */ WS2812_NotifyCommSuccess(); /* 重置超时计时器 */ cOutputBuffer[0] = '\0'; BaseType_t xMore = pdTRUE; do { xMore = FreeRTOS_CLIProcessCommand(cInputBuffer, cOutputBuffer, sizeof(cOutputBuffer)); if (cOutputBuffer[0] != '\0') { printf("%s", cOutputBuffer); } cOutputBuffer[0] = '\0'; } while (xMore == pdTRUE); MR16_UI_Home(mr16); /* 刷新主界面 */ } /* 数据处理完成,允许接收下一帧 */ uart2_data_ready = false; } mr16->timer.now = BSP_TIME_Get_us() / 1000000.0f; mr16->timer.dt = (BSP_TIME_Get_us() - mr16->timer.last_wakeup) / 1000000.0f; mr16->timer.last_wakeup = BSP_TIME_Get_us(); /* 打包数据 - RC data, pack with TX_ID header */ MR16_PackTxBuffer(mr16, uart2_data, uart2_datalength); /* RX端失控保护: 若100ms未收到数据,构造安全帧 */ if (radio.param->RadioRole == RadioRoleRX) { if (mr16->timer.now - mr16->timer.lastDataReceived > 0.1f) { /* 构造安全帧: Header + [0xFF 0x00 0xFF 0x00] + [0x00...] */ mr16->txbuffer[0] = (uint8_t)(mr16->param->TX_ID >> 8); // Header高字节 mr16->txbuffer[1] = (uint8_t)(mr16->param->TX_ID & 0xFF); // Header低字节 mr16->txbuffer[2] = MR16_FAILSAFE_BYTE0; // 0xFF mr16->txbuffer[3] = MR16_FAILSAFE_BYTE1; // 0x00 mr16->txbuffer[4] = MR16_FAILSAFE_BYTE2; // 0xFF mr16->txbuffer[5] = MR16_FAILSAFE_BYTE3; // 0x00 /* 其余字节置0 */ uint16_t totalLen; if (mr16->param->format == MR16_FORMAT_VARIABLE) { totalLen = MR16_HEADER_LEN + uart2_datalength; } else { totalLen = MR16_HEADER_LEN + mr16->param->fixedLength; } for (uint16_t i = 6; i < totalLen; i++) { mr16->txbuffer[i] = 0x00; } } } /* Handle MR16 data format operations */ switch (mr16->param->format) { case MR16_FORMAT_VARIABLE: switch (radio.param->RadioRole) { case RadioRoleTX: SX1281_Running(&radio); break; case RadioRoleRX: SX1281_Running(&radio); if(mr16->timer.now-mr16->timer.lastUIupdata>=1.0f) { MR16_UI_UpdateStatus(mr16); mr16->timer.lastUIupdata=mr16->timer.now; } break; default: break; } break; case MR16_FORMAT_FIXED: /* Fixed length mode */ switch (radio.param->RadioRole) { case RadioRoleTX: SX1281_Running(&radio); break; case RadioRoleRX: SX1281_Running(&radio); if(mr16->timer.now-mr16->timer.lastUIupdata>=1.0f) { MR16_UI_UpdateStatus(mr16); mr16->timer.lastUIupdata=mr16->timer.now; } break; default: break; } break; default: /* Idle mode */ break; } /* LED状态指示任务: 使用HAL_GetTick()获取当前时间(ms) */ WS2812_StatusTask(HAL_GetTick()); return 0; } /*---------------------------------------------------------------------------- Data Packet Processing (数据包处理) *---------------------------------------------------------------------------*/ /** * @brief 封装数据包到TX缓冲区(添加Header和CRC) * @param mr16 MR16实例指针 * @param userData 用户纯数据指针 * @param userDataLen 用户数据长度 * @return 0-成功, -1-失败 * * 数据包格式 (硬件CRC自动添加,无需软件计算): * [0] Header高字节 (TX_ID >> 8) * [1] Header低字节 (TX_ID & 0xFF) * [2..N+1] 用户数据 * 硬件自动添加CRC (FLRC/GFSK: 3字节, LoRa: 2字节) */ int8_t MR16_PackTxBuffer(MR16_t *mr16, const uint8_t *userData, uint16_t userDataLen) { if (!mr16 || !userData || userDataLen > MR16_GFSK_MAX_FIXED_LEN) { return -1; /* Invalid parameters, max 250 bytes for GFSK/LoRa */ } /* 添加Header (TX_ID) */ mr16->txbuffer[0] = (uint8_t)(mr16->param->TX_ID >> 8); /* High byte */ mr16->txbuffer[1] = (uint8_t)(mr16->param->TX_ID & 0xFF); /* Low byte */ /* 复制用户数据 */ memcpy(&mr16->txbuffer[2], userData, userDataLen); /* 硬件CRC自动添加,无需软件计算 */ return 0; } /*---------------------------------------------------------------------------- Radio Event Callbacks (无线事件回调) *---------------------------------------------------------------------------*/ /** * @brief SX1281无线模块事件回调函数 * @param source 事件源状态 * @return 0-成功, -1-错误 * * 处理无线收发事件: * - RX_DONE: 接收成功,验证ID和CRC,输出数据 * - RX_ERROR: 接收失败,重新开启接收 * - TX_DONE: 发送成功,继续发送 * - TX_TIMEOUT: 发送超时 */ int8_t SX1281_Callback(SX1281_States_t source) { switch (source) { case RX_DONE: printf("\r\n[RX] <<< Packet Received >>>\r\n"); /* Extract payload from radio buffer */ radioRXSize = 0; Radio.GetPayload(radioRXBuffer, &radioRXSize, BUFFER_SIZE); if (radioRXSize < BUFFER_SIZE) { radioRXBuffer[radioRXSize] = 0; /* Null terminate */ } else { radioRXBuffer[BUFFER_SIZE - 1] = 0; } /* Parse header and update packet counters */ uint16_t RXheader = (((uint16_t)radioRXBuffer[0]) << 8) | (uint16_t)radioRXBuffer[1]; for (int i = 0; i < 3; i++) { /* Skip disabled RX_ID slots - 用于扩展自动配对功能,保留 */ if (mr16.param->RX_ID[i] == RXID_INVALID_LOW || mr16.param->RX_ID[i] == RXID_INVALID_HIGH) { continue; } if (RXheader == mr16.param->RX_ID[i]) { mr16.packetCount[i]++; mr16.timer.lastDataReceived = mr16.timer.now; /* 更新接收时间戳 */ /* 更新LED状态指示: 接收成功 */ WS2812_SetStatus(WS2812_STATUS_RX_OK); WS2812_NotifyCommSuccess(); printf("[ch%d] Header: 0x%04X | Size: %d bytes | Count: %d\r\n", i+1, RXheader, radioRXSize, mr16.packetCount[i]); break; } } break; case RX_TIMEOUT: /* 打印状态 */ printf( "\r\n[RX] ... Timeout (Restarting RX)\r\n" ); /* 超时设置ERROR状态,不重置通信时间戳 */ WS2812_SetStatus(WS2812_STATUS_ERROR); SX1281_SetRXSingle(&radio); // SX1281_SetRXSuccessive(&radio); break; case RX_ERROR: /* 打印状态 */ printf( "\r\n[RX] !!! ERROR - Reception Failed !!!\r\n" ); /* 接收错误设置ERROR状态,不重置通信时间戳 */ WS2812_SetStatus(WS2812_STATUS_ERROR); SX1281_SetRXSingle(&radio); break; case TX_DONE: /* 打印状态 */ printf( "\r\n[TX] <<< Packet Sent Successfully >>>\r\n" ); /* 更新LED状态指示: 发送成功 */ WS2812_SetStatus(WS2812_STATUS_TX_OK); WS2812_NotifyCommSuccess(); /* 根据数据格式重新发送 */ uint16_t txLen; if (mr16.param->format == MR16_FORMAT_VARIABLE) { /* */ txLen = MR16_HEADER_LEN + uart2_datalength + 10; } else { /* FIXED: Header(2) + 用户数据(fixedLength), 硬件CRC自动添加 */ txLen = MR16_HEADER_LEN + mr16.param->fixedLength; } printf( "[TX] Length: %d bytes\r\n", txLen ); SX1281_SetTX(&radio, mr16.txbuffer, txLen); break; case TX_TIMEOUT: /* 打印状态 */ printf( "\r\n[TX] !!! TIMEOUT - Transmission Failed !!!\r\n" ); /* 发送超时设置ERROR状态,不重置通信时间戳 */ WS2812_SetStatus(WS2812_STATUS_ERROR); /* 重新尝试发送 */ uint16_t retryLen; if (mr16.param->format == MR16_FORMAT_VARIABLE) { retryLen = uart2_datalength; } else { /* FIXED: Header(2) + 用户数据(fixedLength), 硬件CRC自动添加 */ retryLen = MR16_HEADER_LEN + mr16.param->fixedLength; } SX1281_SetTX(&radio, mr16.txbuffer, retryLen); break; case LORA_CAD_DONE: /* 打印状态 */ printf( "\r\n[CAD] Channel Activity Detected\r\n" ); break; default: break; } return 0; } /* ========================================================================== */ /* ========================================================================== */ /* */ /* [PART 2] UI DISPLAY FUNCTIONS */ /* (界面显示模块) */ /* */ /* ========================================================================== */ /* ========================================================================== */ /** * @brief 显示LCD开机动画 * @return 0-成功 * * 显示"MR16"标志和版本信息 */ int8_t MR16_UI_PowerON() { LCD_Clear(BLACK); LCD_DrawString(110, 119, "Initializing...", CYAN, 16, LSB); LCD_DrawBitmap(logo_M, 0, 0, 48, 32, CRIMSON, MSB); HAL_Delay(100); LCD_DrawBitmap(logo_R, 48, 4, 28, 28, CRIMSON, MSB); HAL_Delay(100); LCD_DrawString(0, 48, "MR16--Wireless", CRIMSON, 24, LSB); LCD_DrawString(0, 72, "transmission tool", CRIMSON, 24, LSB); return 0; } /** * @brief 显示LCD主界面 * @param mr16 MR16实例指针 * @return 0-成功, -1-失败 * * 显示内容: * - TX模式: TX ID和发射功率 * - RX模式: 三个通道的接收计数 * - 工作模式、无线协议、频率等信息 */ int8_t MR16_UI_Home(MR16_t *mr16) { if (!mr16 || !mr16->param) return -1; LCD_Clear(BLACK); /* Title */ LCD_DrawString(5, 5, "MR16 Status", CRIMSON, 24, LSB); /* TX/RX Indicator in top right corner */ if (radio.appMode == APPMODE_TX) { LCD_DrawString(200, 5, "TX", ORANGE, 24, LSB); } else { LCD_DrawString(200, 5, "RX", LIME, 24, LSB); } LCD_DrawLine(0, 30, 135, 30, GRAY); /* Mode Display */ LCD_DrawString(5, 40, "Mode:", CYAN, 16, LSB); const char* mode_str = "UNKNOWN"; uint16_t mode_color = WHITE; switch(mr16->param->format) { case MR16_FORMAT_VARIABLE: mode_str = "VARIABLE"; mode_color = WHITE; break; case MR16_FORMAT_FIXED: mode_str = "FIXED"; mode_color = LIME; break; default: mode_str = "NONE"; mode_color = DARKGRAY; break; } LCD_DrawString(50, 40, mode_str, mode_color, 16, LSB); /* Radio Mode */ LCD_DrawString(5, 65, "Radio:", CYAN, 16, LSB); const char* radio_mode = "NONE"; switch(radio.param->packetParams.PacketType) { case PACKET_TYPE_BLE: radio_mode = "BLE"; break; case PACKET_TYPE_GFSK: radio_mode = "GFSK"; break; case PACKET_TYPE_LORA: radio_mode = "LORA"; break; case PACKET_TYPE_FLRC: radio_mode = "FLRC"; break; default: break; } LCD_DrawString(65, 65, radio_mode, WHITE, 16, LSB); /* RF Frequency */ LCD_DrawString(5, 90, "Freq:", CYAN, 16, LSB); char freq_buf[15]; snprintf(freq_buf, sizeof(freq_buf), "%lluMHz", radio.param->rfFrequency / 1000000); LCD_DrawString(50, 90, freq_buf, WHITE, 16, LSB); uint16_t yPos = 35; /* TX Mode: Show TXID and Power */ if (radio.appMode == APPMODE_TX) { LCD_DrawString(5, 115, "TXID:", CYAN, 16, LSB); char id_buf[10]; snprintf(id_buf, sizeof(id_buf), "0x%04X", mr16->param->TX_ID); LCD_DrawString(65, 115, id_buf, WHITE, 16, LSB); yPos += 25; LCD_DrawString(120, 40, "Power:", CYAN, 16, LSB); char pwr_buf[10]; snprintf(pwr_buf, sizeof(pwr_buf), "%ddBm", radio.param->txOutputPower); LCD_DrawString(180, 40, pwr_buf, ORANGE, 16, LSB); yPos += 25; /* 如果是固定长度模式,显示数据长度 */ if (mr16->param->format == MR16_FORMAT_FIXED) { LCD_DrawString(120, 65, "DataLen:", CYAN, 16, LSB); char len_buf[10]; snprintf(len_buf, sizeof(len_buf), "%dB", mr16->param->fixedLength); LCD_DrawString(190, 65, len_buf, ORANGE, 16, LSB); } } /* RX Mode: Show packet counts per channel */ else { /* RX Count Label */ LCD_DrawString(125, yPos, "rxcount:", CYAN, 16, LSB); yPos += 20; /* Channel counts */ char ch_buf[20]; snprintf(ch_buf, sizeof(ch_buf), "ch1:%lu", mr16->packetCount[0]); LCD_DrawString(120, yPos, ch_buf, LIME, 16, LSB); yPos += 20; snprintf(ch_buf, sizeof(ch_buf), "ch2:%lu", mr16->packetCount[1]); LCD_DrawString(120, yPos, ch_buf, LIME, 16, LSB); yPos += 20; snprintf(ch_buf, sizeof(ch_buf), "ch3:%lu", mr16->packetCount[2]); LCD_DrawString(120, yPos, ch_buf, LIME, 16, LSB); /* RX ID Label */ LCD_DrawString(5, 115, "rxid:", CYAN, 16, LSB); snprintf(ch_buf, sizeof(ch_buf), "0x%04X", mr16->param->RX_ID[0]); LCD_DrawString(65, 115, ch_buf, WHITE, 16, LSB); snprintf(ch_buf, sizeof(ch_buf), "0x%04X", mr16->param->RX_ID[1]); LCD_DrawString(125, 115, ch_buf, WHITE, 16, LSB); snprintf(ch_buf, sizeof(ch_buf), "0x%04X", mr16->param->RX_ID[2]); LCD_DrawString(185, 115, ch_buf, WHITE, 16, LSB); } return 0; } /** * @brief 更新LCD屏幕上的动态状态显示 * @param mr16 MR16实例指针 * @return 0-成功, -1-失败 * * 仅在RX模式下更新通道接收计数 */ int8_t MR16_UI_UpdateStatus(MR16_t *mr16) { if (!mr16) return -1; /* Only update in RX mode */ if (radio.appMode == APPMODE_RXSUCCESSIVE) { /* Clear and update channel counts */ uint16_t yPos = 35; char ch_buf[20]; yPos += 20; /* ch1 - use background color for auto-clear */ snprintf(ch_buf, sizeof(ch_buf), "ch1:%lu", mr16->packetCount[0]); LCD_DrawStringBG(120, yPos, ch_buf, LIME, BLACK, 16, LSB); yPos += 20; /* ch2 */ snprintf(ch_buf, sizeof(ch_buf), "ch2:%lu", mr16->packetCount[1]); LCD_DrawStringBG(120, yPos, ch_buf, LIME, BLACK, 16, LSB); yPos += 20; /* ch3 */ snprintf(ch_buf, sizeof(ch_buf), "ch3:%lu", mr16->packetCount[2]); LCD_DrawStringBG(120, yPos, ch_buf, LIME, BLACK, 16, LSB); } return 0; } /* ========================================================================== */ /* ========================================================================== */ /* */ /* [PART 3] CLI COMMAND FUNCTIONS */ /* (命令行接口模块) */ /* */ /* ========================================================================== */ /* ========================================================================== */ /*---------------------------------------------------------------------------- CLI Initialization (CLI系统初始化) *---------------------------------------------------------------------------*/ /** * @brief 初始化MR16 CLI命令系统 * @return 0-成功, -1-失败 * * 注册两个主要命令: * - mr16: MR16系统控制命令 * - radio: SX1281无线参数配置命令 */ int8_t MR16_CLI_Init(void) { static const CLI_Command_Definition_t mr16Command = { "mr16", "mr16 [args] - MR16 system commands. Use 'mr16 help' for details\r\n", mr16CommandHandler, -1 }; static const CLI_Command_Definition_t radioCommand = { "radio", "radio - SX1281 radio control commands. Use 'radio help' for details\r\n", radioCommandHandler, -1 }; /* Register commands */ if (FreeRTOS_CLIRegisterCommand(&mr16Command) != pdPASS) { return -1; } if (FreeRTOS_CLIRegisterCommand(&radioCommand) != pdPASS) { return -1; } return 0; } /*---------------------------------------------------------------------------- Optimized CLI Help Texts (English only for maximum compatibility) *---------------------------------------------------------------------------*/ /* Radio Command Help */ static const char radio_help_en[] = "==============================================================================\r\n" "radio [args] - SX1281 Radio Control Commands\r\n" "==============================================================================\r\n" "Basic Operation Commands:\r\n" " mode - Change radio working mode\r\n" " rffreq - Set RF frequency in Hz\r\n" " power - Set TX power (-18 to +13 dBm)\r\n" " ramptime - Set ramp time: 2,4,6,8,10,12,16,20 microseconds\r\n" " baudrate [index] - Set baudrate for current mode\r\n" "\r\n" "Parameter Configuration:\r\n" " modulation - Set modulation parameters\r\n" " packet - Set packet parameters\r\n" "\r\n" "Detailed Help Commands:\r\n" " modulation help - Show detailed modulation parameters help\r\n" " packet help - Show detailed packet parameters help\r\n" " baudrate help - Show baudrate configuration details\r\n" "\r\n" "Usage Examples:\r\n" " radio mode LORA - Switch to LoRa mode\r\n" " radio rffreq 2400000000 - Set frequency to 2.4GHz\r\n" " radio power 10 - Set TX power to +10dBm\r\n" " radio baudrate 2 - Set baudrate index 2 for current mode\r\n" "\r\n" "For more details on a specific command, use: radio help\r\n" "==============================================================================\r\n" "==============================================================================\r\n"; /* Modulation Parameters Help */ static const char modulation_help[] = "==========================================================================================================\r\n" "modulation - Set Modulation Parameters\r\n" "==========================================================================================================\r\n" "Available fields for each radio mode:\r\n" "\r\n" "BLE/GFSK Mode Parameters:\r\n" " br_bw - Bitrate & Bandwidth\r\n" " 0x04 = 2.0Mbps / 2.4MHz 0x28 = 1.6Mbps / 2.4MHz\r\n" " 0x4C = 1.0Mbps / 2.4MHz 0x45 = 1.0Mbps / 1.2MHz\r\n" " 0x70 = 0.8Mbps / 2.4MHz 0x69 = 0.8Mbps / 1.2MHz\r\n" " 0x8D = 0.5Mbps / 1.2MHz 0x86 = 0.5Mbps / 0.6MHz\r\n" " 0xB1 = 0.4Mbps / 1.2MHz 0xAA = 0.4Mbps / 0.6MHz\r\n" " 0xCE = 0.25Mbps / 0.6MHz 0xC7 = 0.25Mbps / 0.3MHz\r\n" " 0xEF = 0.125Mbps / 0.3MHz\r\n" "\r\n" " modindex - Modulation Index\r\n" " 0=0.35, 1=0.50, 2=0.75, 3=1.00, 4=1.25, 5=1.50, 6=1.75, 7=2.00\r\n" " 8=2.25, 9=2.50, 10=2.75, 11=3.00, 12=3.25, 13=3.50, 14=3.75, 15=4.00\r\n" "\r\n" " shaping - Modulation Shaping\r\n" " 0x00 = OFF (no shaping)\r\n" " 0x10 = BT=1.0 (Gaussian shaping)\r\n" " 0x20 = BT=0.5 (Gaussian shaping)\r\n" "-------------------------------------------------------------------------------------------\r\n" "LoRa Mode Parameters:\r\n" " sf - Spreading Factor\r\n" " 0x50 = SF5, 0x60 = SF6, 0x70 = SF7, 0x80 = SF8\r\n" " 0x90 = SF9, 0xA0 = SF10, 0xB0 = SF11, 0xC0 = SF12\r\n" "\r\n" " bw - Bandwidth\r\n" " 0x34 = 200kHz, 0x26 = 400kHz, 0x18 = 800kHz, 0x0A = 1600kHz\r\n" "\r\n" " cr - Coding Rate\r\n" " 0x01 = 4/5, 0x02 = 4/6, 0x03 = 4/7, 0x04 = 4/8\r\n" " 0x05 = Long Interleaving 4/5, 0x06 = Long Interleaving 4/6\r\n" " 0x07 = Long Interleaving 4/7\r\n" "-------------------------------------------------------------------------------------------\r\n" "FLRC Mode Parameters:\r\n" " br_bw - Bitrate & Bandwidth\r\n" " 0x04 = 2.6Mbps / 2.4MHz 0x28 = 2.08Mbps / 2.4MHz\r\n" " 0x45 = 1.3Mbps / 1.2MHz 0x69 = 1.04Mbps / 1.2MHz\r\n" " 0x86 = 0.65Mbps / 0.6MHz 0xAA = 0.52Mbps / 0.6MHz\r\n" " 0xC7 = 0.325Mbps / 0.3MHz 0xEB = 0.26Mbps / 0.3MHz\r\n" "\r\n" " cr - Coding Rate\r\n" " 0x00 = CR_1/2, 0x02 = CR_3/4, 0x04 = CR_1/0\r\n" "\r\n" " shaping - Modulation Shaping\r\n" " 0x00 = OFF (no shaping)\r\n" " 0x10 = BT=1.0 (Gaussian shaping)\r\n" " 0x20 = BT=0.5 (Gaussian shaping)\r\n" "-------------------------------------------------------------------------------------------\r\n" "Usage Examples:\r\n" " radio modulation br_bw 0x4C - Set GFSK bitrate to 1.0Mbps / 2.4MHz\r\n" " radio modulation sf 0x70 - Set LoRa spreading factor to SF7\r\n" " radio modulation br_bw 0x45 - Set FLRC bitrate to 1.3Mbps / 1.2MHz\r\n" "-------------------------------------------------------------------------------------------\r\n" "Note: Changes take effect immediately. Use 'radio modulation' to see current values.\r\n" "==========================================================================================================\r\n" "==========================================================================================================\r\n"; /* Packet Parameters Help */ static const char packet_help[] = "==========================================================================================================\r\n" "packet - Set Packet Parameters\r\n" "==========================================================================================================\r\n" "Available fields for each radio mode:\r\n" "-----------------------------------------------------------------------------------\r\n" "BLE Mode Parameters:\r\n" " ConnectionState - Connection state\r\n" " 0 = MASTER_SLAVE, 1 = ADVERTISER, 2 = TX_TEST\r\n" " 3 = RX_TEST, 4 = RXTX_TEST\r\n" " CrcField - CRC field configuration\r\n" " 0 = OFF, 1 = CRC_3B (3-byte CRC)\r\n" " BlePacketType - BLE packet type\r\n" " 0 = PRBS_9, 1 = PRBS_15, 2 = EYELONG_1_0, 3 = EYELONG_0_1\r\n" " 4 = EYESHORT_1_0, 5 = EYESHORT_0_1, 6 = ALL_1, 7 = ALL_0\r\n" " Whitening - Whitening mode\r\n" " 0x00 = ON, 0x08 = OFF\r\n" "-----------------------------------------------------------------------------------\r\n" "GFSK Mode Parameters:\r\n" " Preamble - Preamble length\r\n" " 0x00 = 4bits, 0x10 = 8bits, 0x20 = 12bits, 0x30 = 16bits\r\n" " 0x40 = 20bits, 0x50 = 24bits, 0x60 = 28bits, 0x70 = 32bits\r\n" " SyncWordLength - Sync word length\r\n" " 0x00 = 1byte, 0x02 = 2bytes, 0x04 = 3bytes\r\n" " 0x06 = 4bytes, 0x08 = 5bytes\r\n" " SyncWordMatch - Sync word match\r\n" " 0x00 = OFF, 0x10 = 1, 0x20 = 2, 0x30 = 1_2\r\n" " 0x40 = 3, 0x50 = 1_3, 0x60 = 2_3, 0x70 = 1_2_3\r\n" " SyncWord1/2/3 - Set sync word bytes\r\n" " Format: (e.g., 0x12345678)\r\n" " Maximum 5 bytes for GFSK\r\n" " SyncWord - Show all configured sync words\r\n" " Header - Header type\r\n" " 0x00 = VARIABLE, 0x20 = FIXED\r\n" " Payload - Payload length in bytes\r\n" " Range: 1-255\r\n" " Crc - CRC length\r\n" " 0x00 = OFF, 0x10 = 1byte, 0x20 = 2bytes, 0x30 = 3bytes\r\n" " Whitening - Whitening mode\r\n" " 0x00 = ON, 0x08 = OFF\r\n" "-----------------------------------------------------------------------------------\r\n" "LoRa Mode Parameters:\r\n" " Preamble - Preamble symbols count\r\n" " Range: 0-255\r\n" " Header - Header type\r\n" " 0x00 = VARIABLE/EXPLICIT\r\n" " 0x80 = FIXED/IMPLICIT\r\n" " Payload - Payload length in bytes\r\n" " Range: 1-255\r\n" " Crc - CRC mode\r\n" " 0x00 = OFF, 0x20 = ON\r\n" " InvertIQ - IQ inversion\r\n" " 0x40 = NORMAL, 0x00 = INVERTED\r\n" "-----------------------------------------------------------------------------------\r\n" "FLRC Mode Parameters:\r\n" " Most parameters same as GFSK\r\n" " SyncWord1/2/3 - Set sync word bytes\r\n" " Maximum 4 bytes for FLRC\r\n" "-----------------------------------------------------------------------------------\r\n" "Usage Examples:\r\n" " radio packet Payload 64 - Set payload length to 64 bytes\r\n" " radio packet Crc 0x20 - Enable 2-byte CRC\r\n" " radio packet SyncWord1 0x1234 - Set sync word 1 to 0x1234\r\n" " radio packet SyncWord - Display all sync words\r\n" "-----------------------------------------------------------------------------------\r\n" "Note: Use 'radio packet' without arguments to view current parameters.\r\n" "==========================================================================================================\r\n" "==========================================================================================================\r\n"; /* MR16 System Commands Help */ static const char mr16_help[] = "================================================================================\r\n" "mr16 [args] - MR16 System Control Commands\r\n" "================================================================================\r\n" "Configuration Management:\r\n" " save - Save current configuration to Flash\r\n" " get - Load configuration from Flash\r\n" " reset - Restore default configuration\r\n" " mode - Set/get data format\r\n" " fixedlen <1-123> - Set user data length for FIXED mode\r\n" "--------------------------------------------------------------------\r\n" "Receiver ID Configuration:\r\n" " rxid1 - Set/get RX ID for channel 1\r\n" " rxid2 - Set/get RX ID for channel 2\r\n" " rxid3 - Set/get RX ID for channel 3\r\n" " rxid - Show all three RX IDs\r\n" "--------------------------------------------------------------------\r\n" "Operation Commands:\r\n" " tx - Start transmission mode\r\n" " rx - Start continuous receive mode\r\n" "--------------------------------------------------------------------\r\n" "Information Display:\r\n" " show - Show all MR16 configuration\r\n" "--------------------------------------------------------------------\r\n" "Detailed Help:\r\n" " mode help - Show detailed mode information\r\n" "--------------------------------------------------------------------\r\n" "Usage Examples:\r\n" " mr16 mode FIXED - Switch to fixed length mode\r\n" " mr16 fixedlen 32 - Set user data length to 32 bytes\r\n" "" " mr16 rxid1 0x1234 - Set RX ID for channel 1 to 0x1234\r\n" " mr16 tx - Start transmitting\r\n" " mr16 rx - Start receiving\r\n" "--------------------------------------------------------------------\r\n" "Important Notes:\r\n" "1. fixedlen sets ONLY user data length, NOT total packet length.\r\n" "2. MR16 automatically adds: Header(2 bytes) + Data + CRC(2 bytes).\r\n" "3. Valid RX ID range: 0x0001 to 0xFFFE (0x0000 and 0xFFFF are reserved).\r\n" "===============================================================================\r\n" "===============================================================================\r\n"; /* Baudrate Configuration Help */ static const char baudrate_help[] = "==============================================================================\r\n" "baudrate [index] - Set Baudrate for Current Radio Mode\r\n" "==============================================================================\r\n" "Available baudrate indices for each mode:\r\n" "----------------------------------------------------------------------\r\n" "BLE Mode Baudrates:\r\n" " Index 0 = 250 Kbps\r\n" " Index 1 = 500 Kbps\r\n" " Index 2 = 1 Mbps\r\n" "----------------------------------------------------------------------\r\n" "LoRa Mode Baudrates:\r\n" " Index 0 = 216 bps\r\n" " Index 1 = 1 Kbps\r\n" " Index 2 = 5 Kbps\r\n" " Index 3 = 10 Kbps\r\n" " Index 4 = 20 Kbps\r\n" " Index 5 = 61 Kbps\r\n" " Index 6 = 127 Kbps\r\n" " Index 7 = 203 Kbps\r\n" "----------------------------------------------------------------------\r\n" "GFSK Mode Baudrates:\r\n" " Index 0 = 125 Kbps\r\n" " Index 1 = 250 Kbps\r\n" " Index 2 = 500 Kbps\r\n" " Index 3 = 1 Mbps\r\n" "----------------------------------------------------------------------\r\n" "FLRC Mode Baudrates:\r\n" " Index 0 = 130 Kbps\r\n" " Index 1 = 260 Kbps\r\n" " Index 2 = 520 Kbps\r\n" " Index 3 = 1040 Kbps\r\n" "----------------------------------------------------------------------\r\n" "Usage Examples:\r\n" " radio baudrate - Show current baudrate for all modes\r\n" " radio baudrate 2 - Set baudrate index 2 for current mode\r\n" "----------------------------------------------------------------------\r\n" "Important Notes:\r\n" "1. Baudrate index must be valid for the current radio mode.\r\n" "2. Higher baudrates provide faster data rates but reduced range.\r\n" "3. Lower baudrates increase range but reduce data rate.\r\n" "==============================================================================\r\n" "==============================================================================\r\n"; /*---------------------------------------------------------------------------- CLI Command Handlers (命令处理器) *---------------------------------------------------------------------------*/ /** * @brief 复制CLI参数到缓冲区(带长度限制) * @param param 源参数字符串指针 * @param len 参数长度 * @param output 目标缓冲区指针 * @param outputSize 目标缓冲区大小 */ static void copyCLIArg(const char *param, BaseType_t len, char *output, size_t outputSize) { if (!param || !output || outputSize == 0) return; size_t copyLen = (len < (BaseType_t)(outputSize - 1)) ? (size_t)len : (outputSize - 1); strncpy(output, param, copyLen); output[copyLen] = '\0'; } /** * @brief 获取数据格式字符串(用于显示) * @param format 数据格式枚举值 * @return 格式字符串 ("VARIABLE"/"FIXED"/"UNKNOWN") */ static const char* getFormatString(MR16_DataFormat_t format) { switch (format) { case MR16_FORMAT_VARIABLE: return "VARIABLE"; case MR16_FORMAT_FIXED: return "FIXED"; default: return "UNKNOWN"; } } /** * @brief 获取无线模式字符串(用于显示) * @param mode 无线模式枚举值 * @return 模式字符串 ("BLE"/"LORA"/"GFSK"/"FLRC"/"UNKNOWN") */ static const char* getRadioModeString(SX1281_RadioMode_t mode) { switch (mode) { case RADIOMODE_BLE: return "BLE"; case RADIOMODE_LORA: return "LORA"; case RADIOMODE_GFSK: return "GFSK"; case RADIOMODE_FLRC: return "FLRC"; default: return "UNKNOWN"; } } /** * @brief 验证RX ID是否在有效范围内 * @param rxid 待验证的RX ID * @return true-有效(0x0001-0xFFFE), false-无效 */ static bool isValidRXID(uint16_t rxid) { return (rxid >= RXID_MIN && rxid <= RXID_MAX); } /** * @brief 设置RX ID(带校验) * @param rxid RX ID变量指针 * @param value 十六进制字符串值 * @param response 响应消息缓冲区 * @param responseLen 响应缓冲区大小 * @return 0-成功, -1-失败 */ static int8_t setRXID(uint16_t *rxid, const char *value, char *response, size_t responseLen) { uint16_t newID; sscanf(value, "%hx", &newID); if (!isValidRXID(newID)) { snprintf(response, responseLen, "RXID set failed: invalid value\r\nValid range: 0x%04X-0x%04X\r\n", RXID_MIN, RXID_MAX); return -1; } *rxid = newID; snprintf(response, responseLen, "RXID set to: 0x%04X\r\n", *rxid); return 0; } /** * @brief 分块输出帮助信息(防止缓冲区溢出) * @param pcWriteBuffer 输出缓冲区 * @param xWriteBufferLen 缓冲区长度 * @param helpText 待输出的帮助文本 * @param position 当前输出位置指针 * @return pdTRUE-还有更多内容, pdFALSE-输出完成 */ static BaseType_t outputHelpText(char *pcWriteBuffer, size_t xWriteBufferLen, const char *helpText, size_t *position) { size_t help_len = strlen(helpText); if (*position >= help_len) { *position = 0; return pdFALSE; } size_t chunk = (xWriteBufferLen > 1) ? (xWriteBufferLen - 1) : 0; size_t remain = help_len - *position; size_t to_copy = (remain < chunk) ? remain : chunk; if (to_copy > 0) { memcpy(pcWriteBuffer, helpText + *position, to_copy); pcWriteBuffer[to_copy] = '\0'; *position += to_copy; return (*position < help_len) ? pdTRUE : pdFALSE; } *position = 0; return pdFALSE; } /** * @brief MR16命令处理器(CLI) * @param pcWriteBuffer CLI写入缓冲区 * @param xWriteBufferLen 缓冲区长度 * @param pcCommandString 完整命令字符串 * @return pdTRUE-还有输出, pdFALSE-命令处理完成 * * 支持的命令: save, get, reset, mode, fixedlen, tx, rx, * rxid1/2/3, power, show, help */ static BaseType_t mr16CommandHandler(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString) { BaseType_t len1 = 0, len2 = 0, len3 = 0; const char *p1 = FreeRTOS_CLIGetParameter(pcCommandString, 1, &len1); const char *p2 = FreeRTOS_CLIGetParameter(pcCommandString, 2, &len2); const char *p3 = FreeRTOS_CLIGetParameter(pcCommandString, 3, &len3); char arg1[32] = {0}, arg2[32] = {0}, arg3[32] = {0}; /* Extract parameters using common function */ if (p1) copyCLIArg(p1, len1, arg1, sizeof(arg1)); if (p2) copyCLIArg(p2, len2, arg2, sizeof(arg2)); if (p3) copyCLIArg(p3, len3, arg3, sizeof(arg3)); /* Built-in help support */ if (strcasecmp(arg1, "help") == 0) { static size_t help_pos = 0; /* 每次新命令调用时重置位置 */ if (help_pos >= strlen(mr16_help)) { help_pos = 0; } BaseType_t result = outputHelpText(pcWriteBuffer, xWriteBufferLen, mr16_help, &help_pos); /* 如果本次输出完成,重置位置为下次准备 */ if (result == pdFALSE) { help_pos = 0; } return result; } /* Command: save - Save configuration to Flash */ if (strcasecmp(arg1, "save") == 0) { Config_Get()->earsed_count++; STMFLASH_Write(FLASH_SAVE_ADDR, (uint16_t*)Config_Get(), sizeof(Config_t)/2); snprintf(pcWriteBuffer, xWriteBufferLen, "Configuration saved to Flash!\r\n"); /* Command: get - Read configuration from Flash */ } else if (strcasecmp(arg1, "get") == 0) { STMFLASH_Read(FLASH_SAVE_ADDR, (uint16_t*)Config_Get(), sizeof(Config_t)/2); snprintf(pcWriteBuffer, xWriteBufferLen, "Configuration loaded from Flash\r\n"); /* Command: reset - Restore default configuration */ } else if (strcasecmp(arg1, "reset") == 0) { Config_SetDefaults(); snprintf(pcWriteBuffer, xWriteBufferLen, "Configuration reset to defaults!\r\n"); /* Command: mode - Set MR16 data format */ } else if (strcasecmp(arg1, "mode") == 0) { if (strcasecmp(arg2, "help") == 0) { snprintf(pcWriteBuffer, xWriteBufferLen, "mode: 'VARIABLE' - variable length (UART idle)\r\n" " 'FIXED' - fixed length\r\n"); return pdFALSE; } if (arg2[0] != '\0') { if (strcasecmp(arg2, "VARIABLE") == 0) { mr16.param->format = MR16_FORMAT_VARIABLE; MR16_UI_UpdateStatus(&mr16); snprintf(pcWriteBuffer, xWriteBufferLen, "Data format set to: VARIABLE\r\n"); } else if (strcasecmp(arg2, "FIXED") == 0) { mr16.param->format = MR16_FORMAT_FIXED; MR16_UI_UpdateStatus(&mr16); snprintf(pcWriteBuffer, xWriteBufferLen, "Data format set to: FIXED\r\n"); } else { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid format: %s\r\n", arg2); } } else { snprintf(pcWriteBuffer, xWriteBufferLen, "Current format: %s\r\n", getFormatString(mr16.param->format)); } /* Command: fixedlen - Set/show fixed packet length */ } else if (strcasecmp(arg1, "fixedlen") == 0) { if (arg2[0] != '\0') { char *endptr = NULL; unsigned long len = strtoul(arg2, &endptr, 0); if (endptr == arg2) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid length value: %s\r\n", arg2); } else if (len < 1 || len > MR16_FLRC_MAX_FIXED_LEN) { /* FLRC模式最保守限制 */ snprintf(pcWriteBuffer, xWriteBufferLen, "Length out of range: %lu (valid: 1..%d)\r\n" "Note: GFSK/LoRa support up to 250/251 bytes\r\n", len, MR16_FLRC_MAX_FIXED_LEN); } else { mr16.param->fixedLength = (uint16_t)len; MR16_UI_UpdateStatus(&mr16); snprintf(pcWriteBuffer, xWriteBufferLen, "Fixed length set to %u bytes (user data)\r\n" "Total packet: %u bytes (Header[2]+Data[%u]), HW CRC auto-added\r\n", mr16.param->fixedLength, MR16_HEADER_LEN + mr16.param->fixedLength, mr16.param->fixedLength); } } else { snprintf(pcWriteBuffer, xWriteBufferLen, "Current fixed length: %u bytes (user data)\r\n" "Total packet: %u bytes (Header[2]+Data[%u]), HW CRC auto-added\r\n", mr16.param->fixedLength, MR16_HEADER_LEN + mr16.param->fixedLength, mr16.param->fixedLength); } /* Command: tx - Transmit packet */ } else if (strcasecmp(arg1, "tx") == 0) { radio.param->RadioRole=RadioRoleTX; uint16_t txLen; switch (mr16.param->format) { case MR16_FORMAT_VARIABLE: txLen = uart2_datalength; break; case MR16_FORMAT_FIXED: txLen = MR16_HEADER_LEN + mr16.param->fixedLength; break; default: snprintf(pcWriteBuffer, xWriteBufferLen, "Cannot transmit: invalid format\r\n"); return pdFALSE; } SX1281_SetTX(&radio, mr16.txbuffer, txLen); MR16_UI_Home(&mr16); MR16_UI_UpdateStatus(&mr16); WS2812_SetStatus(WS2812_STATUS_IDLE); snprintf(pcWriteBuffer, xWriteBufferLen, "Transmitting packet...\r\n"); /* Command: rx - Start continuous receive */ } else if (strcasecmp(arg1, "rx") == 0) { radio.param->RadioRole=RadioRoleRX; SX1281_SetRXSuccessive(&radio); MR16_UI_Home(&mr16); MR16_UI_UpdateStatus(&mr16); WS2812_SetStatus(WS2812_STATUS_IDLE); snprintf(pcWriteBuffer, xWriteBufferLen, "Starting continuous receive...\r\n"); /* Commands: rxid1/rxid2/rxid3 - Set RX IDs */ } else if (strcasecmp(arg1, "rxid1") == 0 || strcasecmp(arg1, "rxid2") == 0 || strcasecmp(arg1, "rxid3") == 0) { int idx = arg1[4] - '1'; /* Extract index from "rxid1/2/3" */ if (arg2[0] != '\0') { setRXID(&mr16.param->RX_ID[idx], arg2, pcWriteBuffer, xWriteBufferLen); } else { snprintf(pcWriteBuffer, xWriteBufferLen, "Current RXID%d: 0x%04X\r\n", idx + 1, mr16.param->RX_ID[idx]); } /* Command: rxid - Show all RX IDs */ } else if (strcasecmp(arg1, "rxid") == 0 && arg2[0] == '\0') { snprintf(pcWriteBuffer, xWriteBufferLen, "RX IDs:\r\n RXID1: 0x%04X\r\n RXID2: 0x%04X\r\n RXID3: 0x%04X\r\n", mr16.param->RX_ID[0], mr16.param->RX_ID[1], mr16.param->RX_ID[2]); /* Command: show - Display all MR16 configuration */ } else if (strcasecmp(arg1, "show") == 0) { snprintf(pcWriteBuffer, xWriteBufferLen, "======== MR16 Configuration ========\r\n" "Data Format : %s\r\n" "User Data Len: %u bytes (pure data)\r\n" "Total Packet : %u bytes (Header+Data, HW CRC auto)\r\n" "TX ID : 0x%04X\r\n" "RX ID 1 : 0x%04X\r\n" "RX ID 2 : 0x%04X\r\n" "RX ID 3 : 0x%04X\r\n" "TX Power : %d dBm\r\n" "Radio Mode : %s\r\n" "Radio Role : %s\r\n" "=====================================\r\n", getFormatString(mr16.param->format), mr16.param->fixedLength, MR16_HEADER_LEN + mr16.param->fixedLength, mr16.param->TX_ID, mr16.param->RX_ID[0], mr16.param->RX_ID[1], mr16.param->RX_ID[2], radio.param->txOutputPower, getRadioModeString(radio.param->radioMode), (radio.param->RadioRole == RadioRoleTX) ? "TX" : "RX"); /* Invalid command */ } else { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid command: %s\r\nUse 'mr16 help' for available commands\r\n", arg1); } return pdFALSE; } /** * @brief Radio命令处理器(CLI) - 控制SX1281无线参数 * @param pcWriteBuffer CLI写入缓冲区 * @param xWriteBufferLen 缓冲区长度 * @param pcCommandString 完整命令字符串 * @return pdTRUE-还有输出, pdFALSE-命令处理完成 * * 支持的命令: help, mode, modulation, packet, syncword, bandwidth, txid * 可配置参数: 无线协议、调制参数、包参数、同步字等 */ static BaseType_t radioCommandHandler(char *pcWriteBuffer, size_t xWriteBufferLen, const char *pcCommandString) { BaseType_t len1 = 0, len2 = 0, len3 = 0, len4 = 0; const char *p1 = FreeRTOS_CLIGetParameter(pcCommandString, 1, &len1); const char *p2 = FreeRTOS_CLIGetParameter(pcCommandString, 2, &len2); const char *p3 = FreeRTOS_CLIGetParameter(pcCommandString, 3, &len3); const char *p4 = FreeRTOS_CLIGetParameter(pcCommandString, 4, &len4); char arg1[32] = {0}, arg2[32] = {0}, arg3[32] = {0}, arg4[32] = {0}; /* Extract parameters using common function */ if (p1) copyCLIArg(p1, len1, arg1, sizeof(arg1)); if (p2) copyCLIArg(p2, len2, arg2, sizeof(arg2)); if (p3) copyCLIArg(p3, len3, arg3, sizeof(arg3)); if (p4) copyCLIArg(p4, len4, arg4, sizeof(arg4)); /* Command: help - Display help information */ if (strcasecmp(arg1, "help") == 0) { static size_t help_pos = 0; if (help_pos >= strlen(radio_help_en)) help_pos = 0; BaseType_t result = outputHelpText(pcWriteBuffer, xWriteBufferLen, radio_help_en, &help_pos); if (result == pdFALSE) help_pos = 0; return result; } else if (strcasecmp(arg1, "modulation") == 0 && strcasecmp(arg2, "help") == 0) { static size_t mod_help_pos = 0; if (mod_help_pos >= strlen(modulation_help)) mod_help_pos = 0; BaseType_t result = outputHelpText(pcWriteBuffer, xWriteBufferLen, modulation_help, &mod_help_pos); if (result == pdFALSE) mod_help_pos = 0; return result; } else if (strcasecmp(arg1, "packet") == 0 && strcasecmp(arg2, "help") == 0) { static size_t pkt_help_pos = 0; if (pkt_help_pos >= strlen(packet_help)) pkt_help_pos = 0; BaseType_t result = outputHelpText(pcWriteBuffer, xWriteBufferLen, packet_help, &pkt_help_pos); if (result == pdFALSE) pkt_help_pos = 0; return result; /* Parameter processing commands */ } else { /* Parse numeric value: prefer arg4, then arg3, then arg2 */ unsigned long val = 0; if (arg4[0] != '\0') { val = strtoul(arg4, NULL, 0); } else if (arg3[0] != '\0') { val = strtoul(arg3, NULL, 0); } else if (arg2[0] != '\0') { val = strtoul(arg2, NULL, 0); } /* Command: mode - Change or show radio mode */ if (strcasecmp(arg1, "mode") == 0) { if (arg2[0] == '\0') { snprintf(pcWriteBuffer, xWriteBufferLen, "Current radio mode: %s\r\n", getRadioModeString(radio.param->radioMode)); } else { SX1281_RadioMode_t proto; if (strcasecmp(arg2, "BLE") == 0) { proto = RADIOMODE_BLE; } else if (strcasecmp(arg2, "LORA") == 0) { proto = RADIOMODE_LORA; } else if (strcasecmp(arg2, "GFSK") == 0) { proto = RADIOMODE_GFSK; } else if (strcasecmp(arg2, "FLRC") == 0) { proto = RADIOMODE_FLRC; } else { snprintf(pcWriteBuffer, xWriteBufferLen, "Unknown radio mode: %s\r\n", arg2); return pdFALSE; } if (SX1281_SetMode(&radio, proto) == DEVICE_OK) { snprintf(pcWriteBuffer, xWriteBufferLen, "Radio mode set to %s\r\n", arg2); } else { snprintf(pcWriteBuffer, xWriteBufferLen, "Failed to set radio mode\r\n"); } } /* Command: modulation - Set modulation parameters */ } else if (strcasecmp(arg1, "modulation") == 0) { /* Show current modulation params when no field provided */ if (arg2[0] == '\0') { switch (radio.param->modulationParams.PacketType) { case PACKET_TYPE_BLE: snprintf(pcWriteBuffer, xWriteBufferLen, "Modulation(BLE): br_bw=0x%X modindex=%u shaping=0x%X\r\n", radio.param->modulationParams.Params.Ble.BitrateBandwidth, radio.param->modulationParams.Params.Ble.ModulationIndex, radio.param->modulationParams.Params.Ble.ModulationShaping); break; case PACKET_TYPE_GFSK: snprintf(pcWriteBuffer, xWriteBufferLen, "Modulation(GFSK): br_bw=0x%X modindex=%u shaping=0x%X\r\n", radio.param->modulationParams.Params.Gfsk.BitrateBandwidth, radio.param->modulationParams.Params.Gfsk.ModulationIndex, radio.param->modulationParams.Params.Gfsk.ModulationShaping); break; case PACKET_TYPE_LORA: snprintf(pcWriteBuffer, xWriteBufferLen, "Modulation(LoRa): sf=0x%X bw=0x%X cr=0x%X\r\n", radio.param->modulationParams.Params.LoRa.SpreadingFactor, radio.param->modulationParams.Params.LoRa.Bandwidth, radio.param->modulationParams.Params.LoRa.CodingRate); break; case PACKET_TYPE_FLRC: snprintf(pcWriteBuffer, xWriteBufferLen, "Modulation(FLRC): br_bw=0x%X cr=0x%X shaping=0x%X\r\n", radio.param->modulationParams.Params.Flrc.BitrateBandwidth, radio.param->modulationParams.Params.Flrc.CodingRate, radio.param->modulationParams.Params.Flrc.ModulationShaping); break; default: snprintf(pcWriteBuffer, xWriteBufferLen, "Unknown modulation packet type\r\n"); break; } return pdFALSE; } /* Handle different packet types for modulation */ switch (radio.param->modulationParams.PacketType) { case PACKET_TYPE_BLE: if (strcasecmp(arg2, "br_bw") == 0) { if (val > 0xEF) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid br_bw value for BLE (max: 0xEF)\r\n"); return pdFALSE; } radio.param->modulationParams.Params.Ble.BitrateBandwidth = (RadioGfskBleBitrates_t)val; } else if (strcasecmp(arg2, "modindex") == 0) { if (val > 15) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid modindex value for BLE (max: 15)\r\n"); return pdFALSE; } radio.param->modulationParams.Params.Ble.ModulationIndex = (RadioGfskBleModIndexes_t)val; } else if (strcasecmp(arg2, "shaping") == 0) { if (val != 0x00 && val != 0x10 && val != 0x20) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid shaping value for BLE (0x00/0x10/0x20)\r\n"); return pdFALSE; } radio.param->modulationParams.Params.Ble.ModulationShaping = (RadioModShapings_t)val; } else { snprintf(pcWriteBuffer, xWriteBufferLen, "Unknown modulation field for BLE: %s\r\n", arg2); return pdFALSE; } break; case PACKET_TYPE_GFSK: if (strcasecmp(arg2, "br_bw") == 0) { if (val > 0xEF) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid br_bw value for GFSK (max: 0xEF)\r\n"); return pdFALSE; } radio.param->modulationParams.Params.Gfsk.BitrateBandwidth = (RadioGfskBleBitrates_t)val; } else if (strcasecmp(arg2, "modindex") == 0) { if (val > 15) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid modindex value for GFSK (max: 15)\r\n"); return pdFALSE; } radio.param->modulationParams.Params.Gfsk.ModulationIndex = (RadioGfskBleModIndexes_t)val; } else if (strcasecmp(arg2, "shaping") == 0) { if (val != 0x00 && val != 0x10 && val != 0x20) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid shaping value for GFSK (0x00/0x10/0x20)\r\n"); return pdFALSE; } radio.param->modulationParams.Params.Gfsk.ModulationShaping = (RadioModShapings_t)val; } else { snprintf(pcWriteBuffer, xWriteBufferLen, "Unknown modulation field for GFSK: %s\r\n", arg2); return pdFALSE; } break; case PACKET_TYPE_LORA: if (strcasecmp(arg2, "sf") == 0) { if (val != 0x50 && val != 0x60 && val != 0x70 && val != 0x80 && val != 0x90 && val != 0xA0 && val != 0xB0 && val != 0xC0) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid sf value for LORA\r\n"); return pdFALSE; } radio.param->modulationParams.Params.LoRa.SpreadingFactor = (RadioLoRaSpreadingFactors_t)val; } else if (strcasecmp(arg2, "bw") == 0) { if (val != 0x34 && val != 0x26 && val != 0x18 && val != 0x0A) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid bw value for LORA\r\n"); return pdFALSE; } radio.param->modulationParams.Params.LoRa.Bandwidth = (RadioLoRaBandwidths_t)val; } else if (strcasecmp(arg2, "cr") == 0) { if (val < 0x01 || val > 0x07) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid cr value for LORA\r\n"); return pdFALSE; } radio.param->modulationParams.Params.LoRa.CodingRate = (RadioLoRaCodingRates_t)val; } else { snprintf(pcWriteBuffer, xWriteBufferLen, "Unknown modulation field\r\n"); return pdFALSE; } break; case PACKET_TYPE_FLRC: if (strcasecmp(arg2, "br_bw") == 0) { if (val != 0x04 && val != 0x28 && val != 0x45 && val != 0x69 && val != 0x86 && val != 0xAA && val != 0xC7 && val != 0xEB) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid br_bw value for FLRC\r\n"); return pdFALSE; } radio.param->modulationParams.Params.Flrc.BitrateBandwidth = (RadioFlrcBitrates_t)val; } else if (strcasecmp(arg2, "cr") == 0) { if (val != 0x00 && val != 0x02 && val != 0x04) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid cr value for FLRC\r\n"); return pdFALSE; } radio.param->modulationParams.Params.Flrc.CodingRate = (RadioFlrcCodingRates_t)val; } else if (strcasecmp(arg2, "shaping") == 0) { if (val != 0x00 && val != 0x10 && val != 0x20) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid shaping value for FLRC\r\n"); return pdFALSE; } radio.param->modulationParams.Params.Flrc.ModulationShaping = (RadioModShapings_t)val; } else { snprintf(pcWriteBuffer, xWriteBufferLen, "Unknown modulation field\r\n"); return pdFALSE; } break; default: snprintf(pcWriteBuffer, xWriteBufferLen, "Unknown current packet type\r\n"); return pdFALSE; } Radio.SetModulationParams(&radio.param->modulationParams); snprintf(pcWriteBuffer, xWriteBufferLen, "Modulation updated\r\n"); } else if (strcasecmp(arg1, "packet") == 0) { /* Show current packet params when no field provided */ if (arg2[0] == '\0') { switch (radio.param->packetParams.PacketType) { case PACKET_TYPE_BLE: snprintf(pcWriteBuffer, xWriteBufferLen, "Packet(BLE): ConnectionState=%u CrcField=%u BlePacketType=%u Whitening=0x%X\r\n", radio.param->packetParams.Params.Ble.ConnectionState, radio.param->packetParams.Params.Ble.CrcField, radio.param->packetParams.Params.Ble.BlePacketType, radio.param->packetParams.Params.Ble.Whitening); break; case PACKET_TYPE_GFSK: snprintf(pcWriteBuffer, xWriteBufferLen, "Packet(GFSK): Preamble=0x%X SyncLen=0x%X SyncMatch=0x%X Header=0x%X Payload=%u Crc=0x%X Whitening=0x%X\r\n", radio.param->packetParams.Params.Gfsk.PreambleLength, radio.param->packetParams.Params.Gfsk.SyncWordLength, radio.param->packetParams.Params.Gfsk.SyncWordMatch, radio.param->packetParams.Params.Gfsk.HeaderType, radio.param->packetParams.Params.Gfsk.PayloadLength, radio.param->packetParams.Params.Gfsk.CrcLength, radio.param->packetParams.Params.Gfsk.Whitening); break; case PACKET_TYPE_LORA: snprintf(pcWriteBuffer, xWriteBufferLen, "Packet(LoRa): Preamble=%u Header=0x%X Payload=%u Crc=0x%X InvertIQ=0x%X\r\n", radio.param->packetParams.Params.LoRa.PreambleLength, radio.param->packetParams.Params.LoRa.HeaderType, radio.param->packetParams.Params.LoRa.PayloadLength, radio.param->packetParams.Params.LoRa.CrcMode, radio.param->packetParams.Params.LoRa.InvertIQ); break; case PACKET_TYPE_FLRC: snprintf(pcWriteBuffer, xWriteBufferLen, "Packet(FLRC): Preamble=0x%X SyncLen=0x%X SyncMatch=0x%X Header=0x%X Payload=%u Crc=0x%X Whitening=0x%X\r\n", radio.param->packetParams.Params.Flrc.PreambleLength, radio.param->packetParams.Params.Flrc.SyncWordLength, radio.param->packetParams.Params.Flrc.SyncWordMatch, radio.param->packetParams.Params.Flrc.HeaderType, radio.param->packetParams.Params.Flrc.PayloadLength, radio.param->packetParams.Params.Flrc.CrcLength, radio.param->packetParams.Params.Flrc.Whitening); break; default: snprintf(pcWriteBuffer, xWriteBufferLen, "Unknown packet type\r\n"); break; } return pdFALSE; } /* packet fields examples: payload | preamble | header | crc | whitening */ /* 根据当前 packet type 设置对应字段 */ switch (radio.param->packetParams.PacketType) { case PACKET_TYPE_BLE: /* BLE 使用 Ble 子结构 */ if (strcasecmp(arg2, "ConnectionState") == 0) { if (val > 4) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid BLE ConnectionState (0-4)\r\n"); return pdFALSE; } radio.param->packetParams.Params.Ble.ConnectionState = (RadioBleConnectionStates_t)val; } else if (strcasecmp(arg2, "CrcField") == 0) { if (val > 1) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid BLE CrcField (0-1)\r\n"); return pdFALSE; } radio.param->packetParams.Params.Ble.CrcField = (RadioBleCrcFields_t)val; } else if (strcasecmp(arg2, "BlePacketType") == 0) { if (val > 7) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid BLEPacketType (0-7)\r\n"); return pdFALSE; } radio.param->packetParams.Params.Ble.BlePacketType = (RadioBlePacketTypes_t)val; } else if (strcasecmp(arg2, "Whitening") == 0) { if (val != 0x00 && val != 0x08) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid BLE Whitening (0x00/0x08)\r\n"); return pdFALSE; } radio.param->packetParams.Params.Ble.Whitening = (RadioWhiteningModes_t)val; } else { snprintf(pcWriteBuffer, xWriteBufferLen, "Unknown BLE packet field\r\n"); return pdFALSE; } break; case PACKET_TYPE_GFSK: /* GFSK 子结构 */ if (strcasecmp(arg2, "Preamble") == 0) { if (val != 0x00 && val != 0x10 && val != 0x20 && val != 0x30 && val != 0x40 && val != 0x50 && val != 0x60 && val != 0x70) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid GFSK Preamble (0x00/0x10/0x20/0x30/0x40/0x50/0x60/0x70)\r\n"); return pdFALSE; } radio.param->packetParams.Params.Gfsk.PreambleLength = (RadioPreambleLengths_t)val; } else if (strcasecmp(arg2, "SyncWordLength") == 0) { if (val != 0x00 && val != 0x02 && val != 0x04 && val != 0x06 && val != 0x08) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid GFSK SyncWordLength (0x00/0x02/0x04/0x06/0x08)\r\n"); return pdFALSE; } radio.param->packetParams.Params.Gfsk.SyncWordLength = (RadioSyncWordLengths_t)val; } else if (strcasecmp(arg2, "SyncWordMatch") == 0) { if (val != 0x00 && val != 0x10 && val != 0x20 && val != 0x30 && val != 0x40 && val != 0x50 && val != 0x60 && val != 0x70) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid GFSK SyncWordMatch (0x00/0x10/0x20/0x30/0x40/0x50/0x60/0x70)\r\n"); return pdFALSE; } radio.param->packetParams.Params.Gfsk.SyncWordMatch = (RadioSyncWordRxMatchs_t)val; } else if (strcasecmp(arg2, "SyncWord") == 0) { /* Show all GFSK sync words */ snprintf(pcWriteBuffer, xWriteBufferLen, "GFSK Sync Words:\r\n" " Word 1: %02X%02X%02X%02X%02X\r\n" " Word 2: %02X%02X%02X%02X%02X\r\n" " Word 3: %02X%02X%02X%02X%02X\r\n", mr16.param->radioParams.syncWord.gfsk.first[0], mr16.param->radioParams.syncWord.gfsk.first[1], mr16.param->radioParams.syncWord.gfsk.first[2], mr16.param->radioParams.syncWord.gfsk.first[3], mr16.param->radioParams.syncWord.gfsk.first[4], mr16.param->radioParams.syncWord.gfsk.second[0], mr16.param->radioParams.syncWord.gfsk.second[1], mr16.param->radioParams.syncWord.gfsk.second[2], mr16.param->radioParams.syncWord.gfsk.second[3], mr16.param->radioParams.syncWord.gfsk.second[4], mr16.param->radioParams.syncWord.gfsk.third[0], mr16.param->radioParams.syncWord.gfsk.third[1], mr16.param->radioParams.syncWord.gfsk.third[2], mr16.param->radioParams.syncWord.gfsk.third[3], mr16.param->radioParams.syncWord.gfsk.third[4]); return pdFALSE; } else if (strcasecmp(arg2, "SyncWord1") == 0) { if (arg3[0] != '\0') { /* Set GFSK sync word 1 */ uint32_t sync_val = (uint32_t)strtoul(arg3, NULL, 0); mr16.param->radioParams.syncWord.gfsk.first[0] = (sync_val >> 24) & 0xFF; mr16.param->radioParams.syncWord.gfsk.first[1] = (sync_val >> 16) & 0xFF; mr16.param->radioParams.syncWord.gfsk.first[2] = (sync_val >> 8) & 0xFF; mr16.param->radioParams.syncWord.gfsk.first[3] = sync_val & 0xFF; mr16.param->radioParams.syncWord.gfsk.first[4] = 0; snprintf(pcWriteBuffer, xWriteBufferLen, "GFSK sync word 1 set to 0x%08lX\r\n", (unsigned long)sync_val); } else { snprintf(pcWriteBuffer, xWriteBufferLen, "GFSK Sync Word 1: %02X%02X%02X%02X%02X\r\n", mr16.param->radioParams.syncWord.gfsk.first[0], mr16.param->radioParams.syncWord.gfsk.first[1], mr16.param->radioParams.syncWord.gfsk.first[2], mr16.param->radioParams.syncWord.gfsk.first[3], mr16.param->radioParams.syncWord.gfsk.first[4]); } return pdFALSE; } else if (strcasecmp(arg2, "SyncWord2") == 0) { if (arg3[0] != '\0') { /* Set GFSK sync word 2 */ uint32_t sync_val = (uint32_t)strtoul(arg3, NULL, 0); mr16.param->radioParams.syncWord.gfsk.second[0] = (sync_val >> 24) & 0xFF; mr16.param->radioParams.syncWord.gfsk.second[1] = (sync_val >> 16) & 0xFF; mr16.param->radioParams.syncWord.gfsk.second[2] = (sync_val >> 8) & 0xFF; mr16.param->radioParams.syncWord.gfsk.second[3] = sync_val & 0xFF; mr16.param->radioParams.syncWord.gfsk.second[4] = 0; snprintf(pcWriteBuffer, xWriteBufferLen, "GFSK sync word 2 set to 0x%08lX\r\n", (unsigned long)sync_val); } else { snprintf(pcWriteBuffer, xWriteBufferLen, "GFSK Sync Word 2: %02X%02X%02X%02X%02X\r\n", mr16.param->radioParams.syncWord.gfsk.second[0], mr16.param->radioParams.syncWord.gfsk.second[1], mr16.param->radioParams.syncWord.gfsk.second[2], mr16.param->radioParams.syncWord.gfsk.second[3], mr16.param->radioParams.syncWord.gfsk.second[4]); } return pdFALSE; } else if (strcasecmp(arg2, "SyncWord3") == 0) { if (arg3[0] != '\0') { /* Set GFSK sync word 3 */ uint32_t sync_val = (uint32_t)strtoul(arg3, NULL, 0); mr16.param->radioParams.syncWord.gfsk.third[0] = (sync_val >> 24) & 0xFF; mr16.param->radioParams.syncWord.gfsk.third[1] = (sync_val >> 16) & 0xFF; mr16.param->radioParams.syncWord.gfsk.third[2] = (sync_val >> 8) & 0xFF; mr16.param->radioParams.syncWord.gfsk.third[3] = sync_val & 0xFF; mr16.param->radioParams.syncWord.gfsk.third[4] = 0; snprintf(pcWriteBuffer, xWriteBufferLen, "GFSK sync word 3 set to 0x%08lX\r\n", (unsigned long)sync_val); } else { snprintf(pcWriteBuffer, xWriteBufferLen, "GFSK Sync Word 3: %02X%02X%02X%02X%02X\r\n", mr16.param->radioParams.syncWord.gfsk.third[0], mr16.param->radioParams.syncWord.gfsk.third[1], mr16.param->radioParams.syncWord.gfsk.third[2], mr16.param->radioParams.syncWord.gfsk.third[3], mr16.param->radioParams.syncWord.gfsk.third[4]); } return pdFALSE; } else if (strcasecmp(arg2, "Header") == 0) { if (val != 0x00 && val != 0x20) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid GFSK Header (0x00/0x20)\r\n"); return pdFALSE; } radio.param->packetParams.Params.Gfsk.HeaderType = (RadioPacketLengthModes_t)val; } else if (strcasecmp(arg2, "Payload") == 0) { if (val < 1 || val > 255) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid GFSK Payload (1-255)\r\n"); return pdFALSE; } radio.param->packetParams.Params.Gfsk.PayloadLength = (uint8_t)val; } else if (strcasecmp(arg2, "Crc") == 0) { if (val != 0x00 && val != 0x10 && val != 0x20 && val != 0x30) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid GFSK Crc (0x00/0x10/0x20/0x30)\r\n"); return pdFALSE; } radio.param->packetParams.Params.Gfsk.CrcLength = (RadioCrcTypes_t)val; } else if (strcasecmp(arg2, "Whitening") == 0) { if (val != 0x00 && val != 0x08) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid GFSK Whitening (0x00/0x08)\r\n"); return pdFALSE; } radio.param->packetParams.Params.Gfsk.Whitening = (RadioWhiteningModes_t)val; } else { snprintf(pcWriteBuffer, xWriteBufferLen, "Unknown GFSK packet field\r\n"); return pdFALSE; } break; case PACKET_TYPE_LORA: /* LoRa 子结构 */ if (strcasecmp(arg2, "Preamble") == 0) { if (val < 0 || val > 255) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid LoRa Preamble (0-255)\r\n"); return pdFALSE; } radio.param->packetParams.Params.LoRa.PreambleLength = (uint8_t)val; } else if (strcasecmp(arg2, "Header") == 0) { if (val != 0x00 && val != 0x80) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid LoRa Header (0x00/0x80)\r\n"); return pdFALSE; } radio.param->packetParams.Params.LoRa.HeaderType = (RadioLoRaPacketLengthsModes_t)val; } else if (strcasecmp(arg2, "Payload") == 0) { if (val < 1 || val > 255) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid LoRa Payload (1-255)\r\n"); return pdFALSE; } radio.param->packetParams.Params.LoRa.PayloadLength = (uint8_t)val; } else if (strcasecmp(arg2, "Crc") == 0) { if (val != 0x00 && val != 0x20) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid LoRa Crc (0x00/0x20)\r\n"); return pdFALSE; } radio.param->packetParams.Params.LoRa.CrcMode = (RadioLoRaCrcModes_t)val; } else if (strcasecmp(arg2, "InvertIQ") == 0) { if (val != 0x00 && val != 0x40) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid LoRa InvertIQ (0x00/0x40)\r\n"); return pdFALSE; } radio.param->packetParams.Params.LoRa.InvertIQ = (RadioLoRaIQModes_t)val; } else { snprintf(pcWriteBuffer, xWriteBufferLen, "Unknown LoRa packet field\r\n"); return pdFALSE; } break; case PACKET_TYPE_FLRC: /* FLRC 子结构 */ if (strcasecmp(arg2, "Preamble") == 0) { if (val != 0x00 && val != 0x10 && val != 0x20 && val != 0x30 && val != 0x40 && val != 0x50 && val != 0x60 && val != 0x70) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid FLRC Preamble (0x00/0x10/0x20/0x30/0x40/0x50/0x60/0x70)\r\n"); return pdFALSE; } radio.param->packetParams.Params.Flrc.PreambleLength = (RadioPreambleLengths_t)val; } else if (strcasecmp(arg2, "SyncWordLength") == 0) { if (val != 0x00 && val != 0x02 && val != 0x04 && val != 0x06 && val != 0x08) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid FLRC SyncWordLength (0x00/0x02/0x04/0x06/0x08)\r\n"); return pdFALSE; } radio.param->packetParams.Params.Flrc.SyncWordLength = (RadioFlrcSyncWordLengths_t)val; } else if (strcasecmp(arg2, "SyncWordMatch") == 0) { if (val != 0x00 && val != 0x10 && val != 0x20 && val != 0x30 && val != 0x40 && val != 0x50 && val != 0x60 && val != 0x70) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid FLRC SyncWordMatch (0x00/0x10/0x20/0x30/0x40/0x50/0x60/0x70)\r\n"); return pdFALSE; } radio.param->packetParams.Params.Flrc.SyncWordMatch = (RadioSyncWordRxMatchs_t)val; } else if (strcasecmp(arg2, "SyncWord") == 0) { /* Show all FLRC sync words */ snprintf(pcWriteBuffer, xWriteBufferLen, "FLRC Sync Words:\r\n" " Word 1: %02X%02X%02X%02X\r\n" " Word 2: %02X%02X%02X%02X\r\n" " Word 3: %02X%02X%02X%02X\r\n", mr16.param->radioParams.syncWord.flrc.first[0], mr16.param->radioParams.syncWord.flrc.first[1], mr16.param->radioParams.syncWord.flrc.first[2], mr16.param->radioParams.syncWord.flrc.first[3], mr16.param->radioParams.syncWord.flrc.second[0], mr16.param->radioParams.syncWord.flrc.second[1], mr16.param->radioParams.syncWord.flrc.second[2], mr16.param->radioParams.syncWord.flrc.second[3], mr16.param->radioParams.syncWord.flrc.third[0], mr16.param->radioParams.syncWord.flrc.third[1], mr16.param->radioParams.syncWord.flrc.third[2], mr16.param->radioParams.syncWord.flrc.third[3]); return pdFALSE; } else if (strcasecmp(arg2, "SyncWord1") == 0) { if (arg3[0] != '\0') { /* Set FLRC sync word 1 */ uint32_t sync_val = (uint32_t)strtoul(arg3, NULL, 0); mr16.param->radioParams.syncWord.flrc.first[0] = (sync_val >> 24) & 0xFF; mr16.param->radioParams.syncWord.flrc.first[1] = (sync_val >> 16) & 0xFF; mr16.param->radioParams.syncWord.flrc.first[2] = (sync_val >> 8) & 0xFF; mr16.param->radioParams.syncWord.flrc.first[3] = sync_val & 0xFF; snprintf(pcWriteBuffer, xWriteBufferLen, "FLRC sync word 1 set to 0x%08lX\r\n", (unsigned long)sync_val); } else { snprintf(pcWriteBuffer, xWriteBufferLen, "FLRC Sync Word 1: %02X%02X%02X%02X\r\n", mr16.param->radioParams.syncWord.flrc.first[0], mr16.param->radioParams.syncWord.flrc.first[1], mr16.param->radioParams.syncWord.flrc.first[2], mr16.param->radioParams.syncWord.flrc.first[3]); } return pdFALSE; } else if (strcasecmp(arg2, "SyncWord2") == 0) { if (arg3[0] != '\0') { /* Set FLRC sync word 2 */ uint32_t sync_val = (uint32_t)strtoul(arg3, NULL, 0); mr16.param->radioParams.syncWord.flrc.second[0] = (sync_val >> 24) & 0xFF; mr16.param->radioParams.syncWord.flrc.second[1] = (sync_val >> 16) & 0xFF; mr16.param->radioParams.syncWord.flrc.second[2] = (sync_val >> 8) & 0xFF; mr16.param->radioParams.syncWord.flrc.second[3] = sync_val & 0xFF; snprintf(pcWriteBuffer, xWriteBufferLen, "FLRC sync word 2 set to 0x%08lX\r\n", (unsigned long)sync_val); } else { snprintf(pcWriteBuffer, xWriteBufferLen, "FLRC Sync Word 2: %02X%02X%02X%02X\r\n", mr16.param->radioParams.syncWord.flrc.second[0], mr16.param->radioParams.syncWord.flrc.second[1], mr16.param->radioParams.syncWord.flrc.second[2], mr16.param->radioParams.syncWord.flrc.second[3]); } return pdFALSE; } else if (strcasecmp(arg2, "SyncWord3") == 0) { if (arg3[0] != '\0') { /* Set FLRC sync word 3 */ uint32_t sync_val = (uint32_t)strtoul(arg3, NULL, 0); mr16.param->radioParams.syncWord.flrc.third[0] = (sync_val >> 24) & 0xFF; mr16.param->radioParams.syncWord.flrc.third[1] = (sync_val >> 16) & 0xFF; mr16.param->radioParams.syncWord.flrc.third[2] = (sync_val >> 8) & 0xFF; mr16.param->radioParams.syncWord.flrc.third[3] = sync_val & 0xFF; snprintf(pcWriteBuffer, xWriteBufferLen, "FLRC sync word 3 set to 0x%08lX\r\n", (unsigned long)sync_val); } else { snprintf(pcWriteBuffer, xWriteBufferLen, "FLRC Sync Word 3: %02X%02X%02X%02X\r\n", mr16.param->radioParams.syncWord.flrc.third[0], mr16.param->radioParams.syncWord.flrc.third[1], mr16.param->radioParams.syncWord.flrc.third[2], mr16.param->radioParams.syncWord.flrc.third[3]); } return pdFALSE; } else if (strcasecmp(arg2, "Header") == 0) { if (val != 0x00 && val != 0x20) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid FLRC Header (0x00/0x20)\r\n"); return pdFALSE; } radio.param->packetParams.Params.Flrc.HeaderType = (RadioPacketLengthModes_t)val; } else if (strcasecmp(arg2, "Payload") == 0) { if (val < 1 || val > 255) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid FLRC Payload (1-255)\r\n"); return pdFALSE; } radio.param->packetParams.Params.Flrc.PayloadLength = (uint8_t)val; } else if (strcasecmp(arg2, "Crc") == 0) { if (val != 0x00 && val != 0x10 && val != 0x20 && val != 0x30) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid FLRC Crc (0x00/0x10/0x20/0x30)\r\n"); return pdFALSE; } radio.param->packetParams.Params.Flrc.CrcLength = (RadioCrcTypes_t)val; } else if (strcasecmp(arg2, "Whitening") == 0) { if (val != 0x00 && val != 0x08) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid FLRC Whitening (0x00/0x08)\r\n"); return pdFALSE; } radio.param->packetParams.Params.Flrc.Whitening = (RadioWhiteningModes_t)val; } else { snprintf(pcWriteBuffer, xWriteBufferLen, "Unknown FLRC packet field\r\n"); return pdFALSE; } break; default: snprintf(pcWriteBuffer, xWriteBufferLen, "Unknown current packet type\r\n"); return pdFALSE; } /* 应用并返回 */ Radio.SetPacketParams(&radio.param->packetParams); snprintf(pcWriteBuffer, xWriteBufferLen, "Packet parameters updated\r\n"); } else if (strcasecmp(arg1, "baudrate") == 0) { /* baudrate [index] - 根据当前模式自动设置波特率 */ /* 显示帮助 */ if (strcasecmp(arg2, "help") == 0) { static size_t baudrate_help_pos = 0; if (baudrate_help_pos >= strlen(baudrate_help)) baudrate_help_pos = 0; BaseType_t result = outputHelpText(pcWriteBuffer, xWriteBufferLen, baudrate_help, &baudrate_help_pos); if (result == pdFALSE) baudrate_help_pos = 0; return result; } /* 无参数: 显示所有模式的波特率 */ if (arg2[0] == '\0') { const char* current_mode = getRadioModeString(radio.param->radioMode); snprintf(pcWriteBuffer, xWriteBufferLen, "Current mode: %s\r\n" "Baudrates: BLE=%u | LORA=%u | GFSK=%u | FLRC=%u --use \"radio baudrate help\" for help\r\n", current_mode, radio.param->baudrate.ble, radio.param->baudrate.lora, radio.param->baudrate.gfks, radio.param->baudrate.flrc); return pdFALSE; } /* 有参数: 根据当前模式设置波特率 */ int8_t ret = -1; const char* mode_name = ""; switch (radio.param->radioMode) { case RADIOMODE_BLE: if (val > 2) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid BLE baudrate index: %lu (valid: 0-2)\r\n", (unsigned long)val); return pdFALSE; } radio.param->baudrate.ble = (SX1281_BLEBaudrate_t)val; ret = SX1281_SetBLEBaudrate(&radio, radio.param->baudrate.ble); mode_name = "BLE"; break; case RADIOMODE_LORA: if (val > 7) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid LoRa baudrate index: %lu (valid: 0-7)\r\n", (unsigned long)val); return pdFALSE; } radio.param->baudrate.lora = (SX1281_LORABaudrate_t)val; ret = SX1281_SetLORABaudrate(&radio, radio.param->baudrate.lora); mode_name = "LoRa"; break; case RADIOMODE_GFSK: if (val > 3) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid GFSK baudrate index: %lu (valid: 0-3)\r\n", (unsigned long)val); return pdFALSE; } radio.param->baudrate.gfks = (SX1281_GFKSBaudrate_t)val; ret = SX1281_SetGFSKBaudrate(&radio, radio.param->baudrate.gfks); mode_name = "GFSK"; break; case RADIOMODE_FLRC: if (val > 3) { snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid FLRC baudrate index: %lu (valid: 0-3)\r\n", (unsigned long)val); return pdFALSE; } radio.param->baudrate.flrc = (SX1281_FLRCBaudrate_t)val; ret = SX1281_SetFLRCBaudrate(&radio, radio.param->baudrate.flrc); mode_name = "FLRC"; break; default: snprintf(pcWriteBuffer, xWriteBufferLen, "Unknown radio mode\r\n"); return pdFALSE; } if (ret == DEVICE_OK) { snprintf(pcWriteBuffer, xWriteBufferLen, "Baudrate updated for %s mode -> index %lu\r\n", mode_name, (unsigned long)val); } else { snprintf(pcWriteBuffer, xWriteBufferLen, "Failed to update baudrate for %s mode\r\n", mode_name); } } else if (strcasecmp(arg1, "power") == 0) { /* power */ if (arg2[0] == '\0') { snprintf(pcWriteBuffer, xWriteBufferLen, "Current TX power: %d dBm\r\n", radio.param->txOutputPower); return pdFALSE; } int8_t p = (int8_t)val; radio.param->txOutputPower = p; Radio.SetTxParams(radio.param->txOutputPower, radio.param->rampTime); snprintf(pcWriteBuffer, xWriteBufferLen, "TX power set to %d dBm\r\n", p); } else if (strcasecmp(arg1, "ramptime") == 0) { /* ramptime - accepts 2/4/6/8/10/12/16/20 */ RadioRampTimes_t rt = RADIO_RAMP_02_US; unsigned long us = val; if (arg2[0] == '\0') { /* print current ramptime */ unsigned long cur_us = 2; switch (radio.param->rampTime) { case RADIO_RAMP_02_US: cur_us = 2; break; case RADIO_RAMP_04_US: cur_us = 4; break; case RADIO_RAMP_06_US: cur_us = 6; break; case RADIO_RAMP_08_US: cur_us = 8; break; case RADIO_RAMP_10_US: cur_us = 10; break; case RADIO_RAMP_12_US: cur_us = 12; break; case RADIO_RAMP_16_US: cur_us = 16; break; case RADIO_RAMP_20_US: cur_us = 20; break; default: break; } snprintf(pcWriteBuffer, xWriteBufferLen, "Current ramp time: %lu us\r\n", cur_us); return pdFALSE; } switch (us) { case 2: rt = RADIO_RAMP_02_US; break; case 4: rt = RADIO_RAMP_04_US; break; case 6: rt = RADIO_RAMP_06_US; break; case 8: rt = RADIO_RAMP_08_US; break; case 10: rt = RADIO_RAMP_10_US; break; case 12: rt = RADIO_RAMP_12_US; break; case 16: rt = RADIO_RAMP_16_US; break; case 20: rt = RADIO_RAMP_20_US; break; default: snprintf(pcWriteBuffer, xWriteBufferLen, "Invalid ramptime\r\n"); return pdFALSE; } radio.param->rampTime = rt; Radio.SetTxParams(radio.param->txOutputPower, radio.param->rampTime); snprintf(pcWriteBuffer, xWriteBufferLen, "Ramp time set to %lu us\r\n", us); } else if (strcasecmp(arg1, "rffreq") == 0 || strcasecmp(arg1, "rffrequency") == 0) { /* rffreq */ if (arg2[0] == '\0') { snprintf(pcWriteBuffer, xWriteBufferLen, "Current RF freq: %llu Hz\r\n", (unsigned long long)radio.param->rfFrequency); return pdFALSE; } uint32_t f = (uint32_t)val; SX1281_SetRFFrequency(&radio, f); snprintf(pcWriteBuffer, xWriteBufferLen, "RF freq set to %lu Hz\r\n", (unsigned long)f); } else { snprintf(pcWriteBuffer, xWriteBufferLen, "Unknown radio command\r\n"); } } return pdFALSE; }