From 48a443c6c16da6a9b660cd76caf82347e98a0903 Mon Sep 17 00:00:00 2001 From: wangbeihong Date: Fri, 17 Apr 2026 22:15:54 +0800 Subject: [PATCH] Update FreeRTOS configuration and enhance upper computer AI documentation - Increased total heap size in FreeRTOSConfig.h from 10000 to 18000. - Modified stack sizes for various tasks in freertos.c to improve performance: - initTask: 128 to 256 - CarCtrlTask: 256 to 1024 - timerTask: 512 to 1024 - sr04Task: 128 to 512 - rc522Task: 128 to 512 - Added comprehensive upper computer AI guide for TCP client implementation, detailing protocol contracts, command sets, telemetry, architecture requirements, and error handling strategies. - Introduced a Python web upper computer AI guide with a recommended tech stack and project structure. - Created a Chinese version of the upper computer AI guide for local developers. - Updated STM32 project configuration to reflect new task stack sizes and heap settings. - Adjusted configuration files for various tools to ensure compatibility with the new project structure. --- Core/Bsp/bsp_uart.c | 7 +- Core/Bsp/protocol.c | 217 +++++++++- Core/Inc/FreeRTOSConfig.h | 2 +- Core/Src/freertos.c | 10 +- docs/upper_computer_ai_guide.md | 331 +++++++++++++++ docs/upper_computer_ai_guide_python_web_zh.md | 392 ++++++++++++++++++ docs/upper_computer_ai_guide_zh.md | 311 ++++++++++++++ f103_car.ioc | 6 +- .../aithinker_serial_tool.cfg | 4 +- 物流小车/esp_12f/TCPUDPDbg/config/config.ini | 2 +- .../configure/esp8266/hspi_download.conf | 2 +- .../configure/esp8266/spi_download.conf | 4 +- .../(1112)ESP8266-4M.bin | Bin 物流小车/web/app.exe | Bin 14196449 -> 14202376 bytes 14 files changed, 1259 insertions(+), 29 deletions(-) create mode 100644 docs/upper_computer_ai_guide.md create mode 100644 docs/upper_computer_ai_guide_python_web_zh.md create mode 100644 docs/upper_computer_ai_guide_zh.md rename 物流小车/esp_12f/{flash_download_tool_3.9.7 => }/(1112)ESP8266-4M.bin (100%) diff --git a/Core/Bsp/bsp_uart.c b/Core/Bsp/bsp_uart.c index b3ab987..5cc0746 100644 --- a/Core/Bsp/bsp_uart.c +++ b/Core/Bsp/bsp_uart.c @@ -21,8 +21,11 @@ HAL_StatusTypeDef ESP12F_TCP_SendMessage(const char *data) { return HAL_ERROR; // 数据为空 } - // 启动 DMA 传输 - return HAL_UART_Transmit_DMA(&huart1, (uint8_t *)data, size); + /* + * 这里使用阻塞发送而不是 DMA:协议帧很短,且发送缓冲区通常来自局部变量。 + * 使用 DMA 会引入发送缓冲区生命周期问题,容易导致反馈丢失或数据不稳定。 + */ + return HAL_UART_Transmit(&huart1, (uint8_t *)data, size, 50U); } /* External DMA handle */ diff --git a/Core/Bsp/protocol.c b/Core/Bsp/protocol.c index 939f56e..f964b1c 100644 --- a/Core/Bsp/protocol.c +++ b/Core/Bsp/protocol.c @@ -40,6 +40,9 @@ #define OBSTACLE_STOP_DISTANCE_CM 18.0f #define OBSTACLE_RECOVER_DISTANCE_CM 22.0f +/* 手动控制方向斜向速度缩放,避免斜向合速度过大 */ +#define MANUAL_DIAG_SCALE 0.7071f + /* 电机位置定义 (基于用户规定) */ #define MOTOR_LR MOTOR_1 // 左后 (Left Rear) #define MOTOR_LF MOTOR_2 // 左前 (Left Front) @@ -59,12 +62,75 @@ static uint8_t obstacle_locked = 0; // 避障锁(1=有障碍锁定) static track_ir_ctrl_output_t track_output = {0}; static uint8_t track_line_mask = 0; +static void CarCtrl_StopAll(void); + +typedef enum { + CTRL_MODE_AUTO = 0, + CTRL_MODE_MANUAL +} ctrl_mode_t; + +typedef enum { + MAN_DIR_STOP = 0, + MAN_DIR_FWD, + MAN_DIR_BWD, + MAN_DIR_LEFT, + MAN_DIR_RIGHT, + MAN_DIR_LF, + MAN_DIR_RF, + MAN_DIR_LB, + MAN_DIR_RB, + MAN_DIR_CW, + MAN_DIR_CCW +} manual_dir_t; + +static ctrl_mode_t car_ctrl_mode = CTRL_MODE_AUTO; +static manual_dir_t manual_dir = MAN_DIR_STOP; + #define CAR_STATION_MIN ((uint16_t)STATION_1) #define CAR_STATION_MAX ((uint16_t)STATION_2) /* 4个电机的 PID 控制器 */ static PID_TypeDef motor_pid[MOTOR_COUNT]; +static const char *CarCtrl_ModeToString(ctrl_mode_t mode) +{ + return (mode == CTRL_MODE_MANUAL) ? "MAN" : "AUTO"; +} + +static const char *CarCtrl_ManualDirToString(manual_dir_t dir) +{ + switch (dir) { + case MAN_DIR_FWD: return "FWD"; + case MAN_DIR_BWD: return "BWD"; + case MAN_DIR_LEFT: return "LEFT"; + case MAN_DIR_RIGHT:return "RIGHT"; + case MAN_DIR_LF: return "LF"; + case MAN_DIR_RF: return "RF"; + case MAN_DIR_LB: return "LB"; + case MAN_DIR_RB: return "RB"; + case MAN_DIR_CW: return "CW"; + case MAN_DIR_CCW: return "CCW"; + case MAN_DIR_STOP: + default: + return "STOP"; + } +} + +static void CarCtrl_SetManualDirection(manual_dir_t dir) +{ + manual_dir = dir; + if (dir == MAN_DIR_STOP) { + car_running = 0U; + target_v_x = 0.0f; + target_v_y = 0.0f; + target_v_w = 0.0f; + CarCtrl_StopAll(); + } else { + car_running = 1U; + car_target_reached = 0U; + } +} + /** * @brief 初始化闭环控制系统 */ @@ -77,6 +143,8 @@ void CarCtrl_InitClosedLoop(void) track_ir_algo_init(); obstacle_locked = 0U; + car_ctrl_mode = CTRL_MODE_AUTO; + manual_dir = MAN_DIR_STOP; BEEP_Off(); } @@ -113,6 +181,10 @@ static void CarCtrl_CheckTargetStation(void) rc522_card_info_t card; station_id_t station; + if (car_ctrl_mode != CTRL_MODE_AUTO) { + return; + } + if (car_running == 0U || car_target_reached != 0U || car_target_station == 0U) { return; } @@ -168,8 +240,66 @@ void CarCtrl_UpdateClosedLoop(void) BEEP_Off(); - track_line_mask = track_ir_get_line_mask_filtered(); - track_ir_algo_step(track_line_mask, &track_output); + if (car_ctrl_mode == CTRL_MODE_AUTO) { + track_line_mask = track_ir_get_line_mask_filtered(); + track_ir_algo_step(track_line_mask, &track_output); + + // 从前进速度分量计算基础速度(自动循迹) + target_v_x = (float)car_speed_percent * TARGET_MAX_RPM / 100.0f; + target_v_x = target_v_x * (float)track_output.speed_scale_percent / 100.0f; + target_v_y = 0.0f; + target_v_w = track_output.yaw_correction_rpm; + } else { + float base_rpm = (float)car_speed_percent * TARGET_MAX_RPM / 100.0f; + track_line_mask = 0U; + memset(&track_output, 0, sizeof(track_output)); + + /* 手动模式下,依据上位机方向指令生成 Vx/Vy/Vw */ + switch (manual_dir) { + case MAN_DIR_FWD: + target_v_x = base_rpm; target_v_y = 0.0f; target_v_w = 0.0f; + break; + case MAN_DIR_BWD: + target_v_x = -base_rpm; target_v_y = 0.0f; target_v_w = 0.0f; + break; + case MAN_DIR_LEFT: + target_v_x = 0.0f; target_v_y = base_rpm; target_v_w = 0.0f; // 修正:左移Vy为正 + break; + case MAN_DIR_RIGHT: + target_v_x = 0.0f; target_v_y = -base_rpm; target_v_w = 0.0f; // 修正:右移Vy为负 + break; + case MAN_DIR_LF: + target_v_x = base_rpm * MANUAL_DIAG_SCALE; + target_v_y = -base_rpm * MANUAL_DIAG_SCALE; + target_v_w = 0.0f; + break; + case MAN_DIR_RF: + target_v_x = base_rpm * MANUAL_DIAG_SCALE; + target_v_y = base_rpm * MANUAL_DIAG_SCALE; + target_v_w = 0.0f; + break; + case MAN_DIR_LB: + target_v_x = -base_rpm * MANUAL_DIAG_SCALE; + target_v_y = -base_rpm * MANUAL_DIAG_SCALE; + target_v_w = 0.0f; + break; + case MAN_DIR_RB: + target_v_x = -base_rpm * MANUAL_DIAG_SCALE; + target_v_y = base_rpm * MANUAL_DIAG_SCALE; + target_v_w = 0.0f; + break; + case MAN_DIR_CW: + target_v_x = 0.0f; target_v_y = 0.0f; target_v_w = base_rpm; + break; + case MAN_DIR_CCW: + target_v_x = 0.0f; target_v_y = 0.0f; target_v_w = -base_rpm; + break; + case MAN_DIR_STOP: + default: + target_v_x = 0.0f; target_v_y = 0.0f; target_v_w = 0.0f; + break; + } + } /* 麦克纳姆轮运动解算 (RPM单位) * 根据你的电机位置规定: @@ -177,12 +307,6 @@ void CarCtrl_UpdateClosedLoop(void) */ float target_rpms[MOTOR_COUNT]; - // 从前进速度分量计算基础速度 - target_v_x = (float)car_speed_percent * TARGET_MAX_RPM / 100.0f; - target_v_x = target_v_x * (float)track_output.speed_scale_percent / 100.0f; - target_v_y = 0.0f; - target_v_w = track_output.yaw_correction_rpm; - // 麦轮全向解算公式: // LF = Vx + Vy - Vw // RF = Vx - Vy + Vw @@ -332,6 +456,72 @@ static void CarCtrl_HandleCommand(const char *cmd_payload) return; } + // 控制模式切换:MD:AUTO / MD:MAN + if (strncmp(cmd_payload, "MD", 2) == 0) { + if (strcmp(arg + 1, "AUTO") == 0) { + car_ctrl_mode = CTRL_MODE_AUTO; + manual_dir = MAN_DIR_STOP; + car_running = 0U; + obstacle_locked = 0U; + track_ir_algo_reset(); + CarCtrl_StopAll(); + elog_i(Protocol_TAG, "已切换为自动循迹模式(AUTO)"); + Protocol_SendFeedback("MD", 1); + } else if (strcmp(arg + 1, "MAN") == 0) { + car_ctrl_mode = CTRL_MODE_MANUAL; + obstacle_locked = 0U; + CarCtrl_SetManualDirection(MAN_DIR_STOP); + elog_i(Protocol_TAG, "已切换为手动麦轮模式(MAN)"); + Protocol_SendFeedback("MD", 1); + } else { + elog_w(Protocol_TAG, "未知模式命令: %s", cmd_payload); + Protocol_SendFeedback("MD", 0); + } + return; + } + + // 手动运动方向命令:MV:FWD/BWD/LEFT/RIGHT/LF/RF/LB/RB/CW/CCW/STOP + if (strncmp(cmd_payload, "MV", 2) == 0) { + if (car_ctrl_mode != CTRL_MODE_MANUAL) { + elog_w(Protocol_TAG, "当前非手动模式,拒绝执行MV。请先下发 MD:MAN"); + Protocol_SendFeedback("MV", 0); + return; + } + + if (strcmp(arg + 1, "FWD") == 0) { + CarCtrl_SetManualDirection(MAN_DIR_FWD); + } else if (strcmp(arg + 1, "BWD") == 0) { + CarCtrl_SetManualDirection(MAN_DIR_BWD); + } else if (strcmp(arg + 1, "LEFT") == 0) { + CarCtrl_SetManualDirection(MAN_DIR_LEFT); + } else if (strcmp(arg + 1, "RIGHT") == 0) { + CarCtrl_SetManualDirection(MAN_DIR_RIGHT); + } else if (strcmp(arg + 1, "LF") == 0) { + CarCtrl_SetManualDirection(MAN_DIR_LF); + } else if (strcmp(arg + 1, "RF") == 0) { + CarCtrl_SetManualDirection(MAN_DIR_RF); + } else if (strcmp(arg + 1, "LB") == 0) { + CarCtrl_SetManualDirection(MAN_DIR_LB); + } else if (strcmp(arg + 1, "RB") == 0) { + CarCtrl_SetManualDirection(MAN_DIR_RB); + } else if (strcmp(arg + 1, "CW") == 0) { + CarCtrl_SetManualDirection(MAN_DIR_CW); + } else if (strcmp(arg + 1, "CCW") == 0) { + CarCtrl_SetManualDirection(MAN_DIR_CCW); + } else if (strcmp(arg + 1, "STOP") == 0) { + CarCtrl_SetManualDirection(MAN_DIR_STOP); + } else { + elog_w(Protocol_TAG, "未知手动方向命令: %s", cmd_payload); + Protocol_SendFeedback("MV", 0); + return; + } + + elog_i(Protocol_TAG, "手动方向=%s, speed=%u%%", CarCtrl_ManualDirToString(manual_dir), + car_speed_percent); + Protocol_SendFeedback("MV", 1); + return; + } + // 未知命令类型 elog_w(Protocol_TAG, "未支持的控制命令: %s", cmd_payload); } @@ -354,7 +544,7 @@ static void Protocol_SendPacket(const char *payload) { size_t current_len = strlen(packet); snprintf(packet + current_len, sizeof(packet) - current_len, ":%02X#", cs); - // 4. 发送 + // 4. 直接发送 ESP12F_TCP_SendMessage(packet); } @@ -363,7 +553,7 @@ static void Protocol_SendPacket(const char *payload) { * 格式示例: STAT:SP:080,STA:001,RUN:1,DIS:12.5,TRK:0010,RPM:25:25:25:25 */ void Protocol_SendStatusReport(void) { - char payload[128] = {0}; + char payload[160] = {0}; float dist = sr04_get_distance(); uint8_t trk_mask = track_line_mask; @@ -371,8 +561,11 @@ void Protocol_SendStatusReport(void) { // TRK 此时上报 4 位二进制掩码 (H4 H3 H2 H1) // RPM 为各个电机的实际 RPM,保留整数 snprintf(payload, sizeof(payload), - "STAT:SP:%03u,STA:%03u,RUN:%u,DIS:%.1f,TRK:%u%u%u%u,DEV:%d,OBS:%u,RPM:%d:%d:%d:%d", - car_speed_percent, car_target_station, car_running, dist, + "STAT:SP:%03u,STA:%03u,RUN:%u,MODE:%s,MAN:%s,DIS:%.1f,TRK:%u%u%u%u,DEV:%d,OBS:%u,RPM:%d:%d:%d:%d", + car_speed_percent, car_target_station, car_running, + CarCtrl_ModeToString(car_ctrl_mode), + CarCtrl_ManualDirToString(manual_dir), + dist, (trk_mask & TRACK_IR_H4_BIT) ? 1 : 0, (trk_mask & TRACK_IR_H3_BIT) ? 1 : 0, (trk_mask & TRACK_IR_H2_BIT) ? 1 : 0, diff --git a/Core/Inc/FreeRTOSConfig.h b/Core/Inc/FreeRTOSConfig.h index f0c28ab..66c3675 100644 --- a/Core/Inc/FreeRTOSConfig.h +++ b/Core/Inc/FreeRTOSConfig.h @@ -64,7 +64,7 @@ #define configTICK_RATE_HZ ((TickType_t)1000) #define configMAX_PRIORITIES ( 56 ) #define configMINIMAL_STACK_SIZE ((uint16_t)128) -#define configTOTAL_HEAP_SIZE ((size_t)10000) +#define configTOTAL_HEAP_SIZE ((size_t)18000) #define configMAX_TASK_NAME_LEN ( 16 ) #define configUSE_TRACE_FACILITY 1 #define configUSE_16_BIT_TICKS 0 diff --git a/Core/Src/freertos.c b/Core/Src/freertos.c index 3348c8f..d069929 100644 --- a/Core/Src/freertos.c +++ b/Core/Src/freertos.c @@ -60,35 +60,35 @@ osThreadId_t initTaskHandle; const osThreadAttr_t initTask_attributes = { .name = "initTask", - .stack_size = 128 * 4, + .stack_size = 256 * 4, .priority = (osPriority_t) osPriorityNormal, }; /* Definitions for CarCtrlTask */ osThreadId_t CarCtrlTaskHandle; const osThreadAttr_t CarCtrlTask_attributes = { .name = "CarCtrlTask", - .stack_size = 256 * 4, + .stack_size = 1024 * 4, .priority = (osPriority_t) osPriorityNormal, }; /* Definitions for timerTask */ osThreadId_t timerTaskHandle; const osThreadAttr_t timerTask_attributes = { .name = "timerTask", - .stack_size = 512 * 4, + .stack_size = 1024 * 4, .priority = (osPriority_t) osPriorityBelowNormal, }; /* Definitions for sr04Task */ osThreadId_t sr04TaskHandle; const osThreadAttr_t sr04Task_attributes = { .name = "sr04Task", - .stack_size = 128 * 4, + .stack_size = 512 * 4, .priority = (osPriority_t) osPriorityLow, }; /* Definitions for rc522Task */ osThreadId_t rc522TaskHandle; const osThreadAttr_t rc522Task_attributes = { .name = "rc522Task", - .stack_size = 128 * 4, + .stack_size = 512 * 4, .priority = (osPriority_t) osPriorityBelowNormal, }; /* Definitions for CmdQueue */ diff --git a/docs/upper_computer_ai_guide.md b/docs/upper_computer_ai_guide.md new file mode 100644 index 0000000..3534c19 --- /dev/null +++ b/docs/upper_computer_ai_guide.md @@ -0,0 +1,331 @@ +# Logistics Car Upper Computer AI Guide + +## 1. Purpose +This document is written for an AI agent that will implement the upper computer application. +Goal: build a stable TCP client for the logistics car, with both auto mode and manual mecanum mode, based on the embedded protocol implemented in this repository. + +Target users: +- AI coding agents (Copilot-like, AutoGen-like, etc.) +- Human developers who review AI-generated upper computer code + +Out of scope: +- Modifying embedded firmware behavior +- Replacing transport protocol + +--- + +## 2. Must-Follow Protocol Contract + +### 2.1 Frame format +All commands and telemetry use ASCII text: + +LOGI::# + +Fields: +- LOGI: fixed header +- : command or telemetry body +- : 2-digit uppercase hex checksum +- #: fixed frame tail + +### 2.2 Checksum algorithm +Checksum is the low 8 bits of ASCII sum of all bytes from L in LOGI to the byte before the final colon before CS. + +Pseudo: +1. base = "LOGI:" + payload +2. cs = sum(base bytes as ASCII) & 0xFF +3. frame = base + ":" + toHex2Upper(cs) + "#" + +Python reference: + +```python +def build_frame(payload: str) -> str: + base = f"LOGI:{payload}" + cs = sum(base.encode("ascii")) & 0xFF + return f"{base}:{cs:02X}#" +``` + +C# reference: + +```csharp +static string BuildFrame(string payload) +{ + string baseStr = $"LOGI:{payload}"; + int sum = 0; + foreach (byte b in System.Text.Encoding.ASCII.GetBytes(baseStr)) + sum = (sum + b) & 0xFF; + return $"{baseStr}:{sum:X2}#"; +} +``` + +Important: +- Must use uppercase hex (for consistency) +- Do not append CRLF unless your socket tool requires it +- One frame can be sent as raw ASCII bytes directly + +--- + +## 3. Command Set (PC -> Car) + +### 3.1 Common control +- SP:xxx set speed 000..100 +- ST:RUN start auto run +- ST:STOP stop run +- GS:xxx set target station (current firmware supports 001, 002) + +### 3.2 Mode switch (new) +- MD:MAN switch to manual mecanum mode +- MD:AUTO switch to auto tracking mode + +### 3.3 Manual movement (new, only valid in MAN mode) +- MV:FWD forward +- MV:BWD backward +- MV:LEFT strafe left +- MV:RIGHT strafe right +- MV:LF forward-left diagonal +- MV:RF forward-right diagonal +- MV:LB backward-left diagonal +- MV:RB backward-right diagonal +- MV:CW rotate clockwise +- MV:CCW rotate counterclockwise +- MV:STOP stop motion + +If current mode is AUTO and you send MV:*: +- car returns FB:MV:0 (rejected) +- upper computer must first send MD:MAN + +--- + +## 4. Telemetry and Feedback (Car -> PC) + +### 4.1 Periodic status (every ~500ms) +Payload format: + +STAT:SP:xxx,STA:xxx,RUN:x,MODE:mode,MAN:dir,DIS:d,TRK:b4b3b2b1,DEV:n,OBS:x,RPM:m1:m2:m3:m4 + +Fields: +- SP: speed percent +- STA: target station +- RUN: 1 running, 0 stopped +- MODE: AUTO or MAN +- MAN: STOP/FWD/BWD/LEFT/RIGHT/LF/RF/LB/RB/CW/CCW +- DIS: ultrasonic distance in cm +- TRK: 4-channel line mask, order H4 H3 H2 H1 +- DEV: track deviation (auto mode meaningful) +- OBS: obstacle lock (1 blocked, 0 clear) +- RPM: 4 motor RPM, order M1:M2:M3:M4 (LR:LF:RF:RR) + +### 4.2 Command feedback (immediate) +Payload format: + +FB::<0|1> + +Examples: +- FB:SP:1 speed accepted +- FB:GS:0 station invalid or parse fail +- FB:MV:0 movement rejected (for example mode mismatch) + +Upper computer rule: +- Every button operation should wait for feedback timeout window +- If no feedback in timeout, mark command uncertain and retry policy applies + +--- + +## 5. Upper Computer Architecture Requirements + +AI must implement the upper computer with these modules. + +### 5.1 Network module +Responsibilities: +- Maintain TCP connection lifecycle +- Auto reconnect with backoff +- Byte stream receive and frame extraction + +Required behavior: +- Reconnect delay: 1s, 2s, 3s, then fixed 3s +- On reconnect success, UI state set to Connected +- On disconnect, all movement buttons should be disabled + +### 5.2 Protocol module +Responsibilities: +- Build frames from payload +- Parse incoming frames +- Verify checksum before dispatch + +Required behavior: +- Drop frame if checksum invalid +- Log invalid frame raw text for diagnostics +- Expose strongly typed events: StatusEvent, FeedbackEvent + +### 5.3 Command dispatcher +Responsibilities: +- Serialize outbound commands (single queue) +- Attach request id locally (not in protocol) for UI correlation +- Wait feedback with timeout + +Recommended defaults: +- Feedback timeout: 800 ms +- Max retries for idempotent commands (SP, MD, MV:STOP): 2 +- No auto-retry for ST:RUN and GS:xxx unless user confirms + +### 5.4 UI module +Required controls: +- Connection: IP, port, connect/disconnect +- Mode: AUTO / MAN toggle +- Speed: slider 0..100 with send button +- Auto controls: station selector (001/002), RUN, STOP +- Manual controls: directional keypad + rotation + STOP +- Status panel: parsed telemetry fields +- Logs panel: TX/RX raw frames + parsed result + +### 5.5 Safety guard module +Required safety logic: +- If UI in MAN mode and mouse/touch release on direction button, auto send MV:STOP +- If no fresh status for >2s, show stale warning +- Emergency stop hotkey sends MV:STOP first; if in AUTO also send ST:STOP + +--- + +## 6. Exact Button-to-Command Mapping + +All below are complete frames with checksum, directly sendable. + +### 6.1 Mode +- Switch to manual: LOGI:MD:MAN:0C# +- Switch to auto: LOGI:MD:AUTO:69# + +### 6.2 Speed presets +- 30%: LOGI:SP:030:D5# +- 50%: LOGI:SP:050:D7# +- 80%: LOGI:SP:080:DA# + +### 6.3 Auto mode +- Station 001: LOGI:GS:001:CA# +- Station 002: LOGI:GS:002:CB# +- Auto run: LOGI:ST:RUN:3B# +- Auto stop: LOGI:ST:STOP:8C# + +### 6.4 Manual movement +- Forward: LOGI:MV:FWD:23# +- Backward: LOGI:MV:BWD:1F# +- Left strafe: LOGI:MV:LEFT:6D# +- Right strafe: LOGI:MV:RIGHT:C0# +- Forward-left: LOGI:MV:LF:D4# +- Forward-right: LOGI:MV:RF:DA# +- Backward-left: LOGI:MV:LB:D0# +- Backward-right: LOGI:MV:RB:D6# +- Rotate CW: LOGI:MV:CW:DC# +- Rotate CCW: LOGI:MV:CCW:1F# +- Stop: LOGI:MV:STOP:88# + +--- + +## 7. Recommended Operation Sequences + +### 7.1 Manual driving flow +1. Connect TCP +2. Send SP:xxx +3. Send MD:MAN +4. Hold direction button -> send MV:* repeatedly or once per state change +5. Release button -> send MV:STOP + +Recommended send style for manual: +- Edge-triggered (on press send once, on release send stop) is preferred +- If network jitter is high, add keepalive send of same MV every 300 ms + +### 7.2 Auto transport flow +1. Connect TCP +2. Send SP:xxx +3. Send MD:AUTO +4. Send GS:001 or GS:002 +5. Send ST:RUN +6. Observe RUN and feedback/status until arrival + +Arrival behavior in firmware: +- Car auto-stops on target RFID +- RUN becomes 0 +- Next run requires sending GS again, then ST:RUN + +--- + +## 8. Frame Parsing Strategy (Stream-safe) + +TCP is stream-based, not message-based. AI must implement frame extraction correctly. + +Required parser behavior: +- Keep a receive buffer string/byte array +- Search for header "LOGI:" +- Search for tail '#' +- Extract candidate frame from first valid header to first following '#' +- Validate checksum +- Dispatch valid frame; remove consumed bytes +- If garbage before header, discard garbage + +Do not assume one recv equals one frame. + +--- + +## 9. Error Handling and Recovery + +### 9.1 Feedback timeout +If command has no FB within timeout: +- Mark as timeout in UI +- For safe idempotent commands, retry once or twice +- For movement commands, prefer fail-safe by sending STOP + +### 9.2 Checksum mismatch in received data +- Drop frame +- Log as warning with raw text +- Keep connection alive (do not disconnect directly) + +### 9.3 Mode conflict +If receiving FB:MV:0 after sending MV command: +- UI should hint: "Not in manual mode, switching to MAN" +- Auto-send MD:MAN then retry once + +### 9.4 Connection loss during motion +On disconnect event: +- UI enters safe state +- disable movement controls +- after reconnect, require operator to re-confirm mode and speed + +--- + +## 10. AI Implementation Checklist (Definition of Done) + +Functional: +- Can connect/disconnect TCP manually +- Can parse status and feedback reliably +- All buttons send correct command frames +- Manual control works in all listed directions +- Auto mode flow works (GS + RUN + STOP) + +Robustness: +- Stream parser handles sticky packets and split packets +- Reconnect works automatically +- Command timeout/retry policy implemented +- Logs include TX/RX and parse errors + +Usability: +- Telemetry fields visible and updating +- Mode and direction state clearly shown +- Emergency stop is always accessible + +Testing: +- Unit test for checksum build/verify +- Unit test for parser with mixed/fragmented frames +- Integration test with mock TCP server replaying status/feedback + +--- + +## 11. Suggested Prompt for Coding AI +Use this prompt when asking another coding AI to generate the upper computer: + +"Implement a production-ready TCP upper computer client for an STM32 logistics car protocol. Use ASCII frame format LOGI::# with checksum as sum(base bytes) & 0xFF where base is LOGI:. Support commands SP, ST, GS, MD, MV. Parse status payload STAT with fields SP, STA, RUN, MODE, MAN, DIS, TRK, DEV, OBS, RPM. Parse feedback payload FB::<0|1>. Build a stream-safe parser for sticky/fragmented TCP packets. Include reconnect, timeout/retry, and a UI with manual mecanum directional controls and auto mode controls." + +--- + +## 12. Notes for Future Extension +If firmware adds fields later: +- Keep parser tolerant: unknown key-value pairs should not crash parser +- Preserve raw payload in logs for forward compatibility +- Version your app-side parser module (for example v1_2) diff --git a/docs/upper_computer_ai_guide_python_web_zh.md b/docs/upper_computer_ai_guide_python_web_zh.md new file mode 100644 index 0000000..94b2dfd --- /dev/null +++ b/docs/upper_computer_ai_guide_python_web_zh.md @@ -0,0 +1,392 @@ +# Python Web 上位机实施文档(给开发AI直接执行) + +## 1. 目标与技术选型 + +目标:实现一个可运行、可联调、可扩展的上位机系统,满足以下要求: +- 与小车通过 TCP 协议通信 +- 浏览器可视化控制(自动模式 + 手动麦轮模式) +- 实时状态显示(500ms级) +- 日志、重连、超时、错误恢复完整 + +推荐技术栈: +- 后端:Python 3.11+ +- Web框架:FastAPI +- 实时推送:WebSocket +- 异步:asyncio +- 前端:Vue3 或 React(都可以,本文给中立接口规范) +- 部署:Windows 本地运行(开发阶段) + +建议架构: +- Browser UI <-> WebSocket/HTTP <-> Python Backend <-> TCP Socket <-> Car + +--- + +## 2. 协议契约(必须严格一致) + +帧格式: +LOGI::# + +校验和: +- 对字符串 LOGI: 做 ASCII 累加 +- 取低8位 +- 转 2位大写十六进制 + +Python函数(必须实现并复用): + +```python +def build_frame(payload: str) -> str: + base = f"LOGI:{payload}" + cs = sum(base.encode("ascii")) & 0xFF + return f"{base}:{cs:02X}#" +``` + +已验证可直接发送的关键命令: +- LOGI:MD:MAN:0C# +- LOGI:MD:AUTO:69# +- LOGI:MV:FWD:23# +- LOGI:MV:BWD:1F# +- LOGI:MV:LEFT:6D# +- LOGI:MV:RIGHT:C0# +- LOGI:MV:LF:D4# +- LOGI:MV:RF:DA# +- LOGI:MV:LB:D0# +- LOGI:MV:RB:D6# +- LOGI:MV:CW:DC# +- LOGI:MV:CCW:1F# +- LOGI:MV:STOP:88# +- LOGI:SP:030:D5# +- LOGI:SP:050:D7# +- LOGI:SP:080:DA# +- LOGI:GS:001:CA# +- LOGI:GS:002:CB# +- LOGI:ST:RUN:3B# +- LOGI:ST:STOP:8C# + +--- + +## 3. 项目目录(AI生成代码必须遵循) + +```text +upper-computer-python-web/ + backend/ + app/ + main.py + config.py + models.py + protocol.py + tcp_client.py + command_service.py + state_store.py + ws_hub.py + parser.py + logger.py + api/ + http_routes.py + ws_routes.py + requirements.txt + frontend/ + src/ + main.ts + api.ts + ws.ts + store.ts + components/ + ConnectionPanel.vue + ModePanel.vue + SpeedPanel.vue + AutoPanel.vue + ManualPad.vue + StatusPanel.vue + LogPanel.vue + package.json + README.md +``` + +--- + +## 4. 后端职责与实现要求 + +### 4.1 TCP客户端(tcp_client.py) + +必须实现: +- connect(host, port) +- disconnect() +- send_frame(frame) +- background_read_loop() +- auto_reconnect_loop() + +重连策略: +- 1s -> 2s -> 3s -> 3s循环 + +连接状态事件: +- connected +- disconnected +- reconnecting +- connect_failed + +### 4.2 流式解析器(parser.py) + +TCP是流,必须做粘包拆包。 + +规则: +1. 维护 bytearray 缓冲区 +2. 找 LOGI: 起点 +3. 找 # 终点 +4. 抽取候选帧 +5. 校验和正确才分发 +6. 错帧丢弃并记录日志 + +必须支持: +- 一次收多帧 +- 一帧分多次收 +- 帧前垃圾字节 + +### 4.3 协议服务(protocol.py) + +必须提供: +- build_frame(payload) +- parse_frame(frame) -> dict +- verify_checksum(frame) -> bool + +parse_frame 输出建议: +- type: status / feedback / unknown +- payload_raw +- fields: dict + +### 4.4 命令服务(command_service.py) + +必须实现命令队列与反馈匹配: +- send_command(payload, wait_feedback=True) +- 超时默认 800ms +- 可重试命令:SP、MD、MV:STOP(最多2次) +- 非幂等命令 ST:RUN、GS 不自动重试 + +反馈格式: +- FB::<0|1> + +### 4.5 状态存储(state_store.py) + +维护全局状态对象: +- connection +- speed +- station +- run +- mode +- manual_dir +- distance +- trk +- dev +- obs +- rpm[4] +- last_status_ts + +超过2秒无状态包: +- 标记 stale = true +- 推送前端告警 + +### 4.6 WebSocket中枢(ws_hub.py) + +前端订阅以下事件: +- conn_update +- status_update +- feedback_update +- tx_log +- rx_log +- warn +- error + +--- + +## 5. HTTP 与 WebSocket 接口规范 + +### 5.1 HTTP + +1) POST /api/connect +- body: {"host":"192.168.1.50","port":3456} +- resp: {"ok":true} + +2) POST /api/disconnect +- resp: {"ok":true} + +3) POST /api/cmd +- body: {"payload":"MV:FWD","waitFeedback":true} +- resp: {"ok":true,"feedback":{"cmd":"MV","status":1}} + +4) GET /api/state +- resp: 全量状态JSON + +5) GET /api/health +- resp: {"ok":true} + +### 5.2 WebSocket + +- ws://host:port/ws + +消息统一格式: +- {"event":"status_update","data":{...}} +- {"event":"feedback_update","data":{...}} +- {"event":"tx_log","data":{"frame":"..."}} +- {"event":"rx_log","data":{"frame":"..."}} + +--- + +## 6. 前端页面与按钮行为 + +页面最少包含: +- 连接面板:IP、端口、连接/断开 +- 模式面板:AUTO / MAN +- 速度面板:0-100滑条 + 发送按钮 +- 自动面板:站点001/002、RUN、STOP +- 手动面板:8方向 + CW/CCW + STOP +- 状态面板:SP/STA/RUN/MODE/MAN/DIS/TRK/DEV/OBS/RPM +- 日志面板:TX/RX + +按钮映射要求: + +1) 切手动 +- 发送 payload: MD:MAN + +2) 切自动 +- 发送 payload: MD:AUTO + +3) 速度 +- 发送 payload: SP:xxx(补零3位) + +4) 自动站点 +- 001 -> GS:001 +- 002 -> GS:002 + +5) 自动运行控制 +- RUN -> ST:RUN +- STOP -> ST:STOP + +6) 手动方向 +- 前进 MV:FWD +- 后退 MV:BWD +- 左移 MV:LEFT +- 右移 MV:RIGHT +- 左前 MV:LF +- 右前 MV:RF +- 左后 MV:LB +- 右后 MV:RB +- 顺时针 MV:CW +- 逆时针 MV:CCW +- 停止 MV:STOP + +手动安全行为(必须): +- 按下方向键:发对应MV +- 松开方向键:立即发 MV:STOP + +--- + +## 7. 关键业务逻辑(必须实现) + +### 7.1 模式冲突恢复 +如果发送 MV:* 收到 FB:MV:0: +1. 自动提示“当前不在手动模式” +2. 自动发送 MD:MAN +3. 重试一次原MV命令 + +### 7.2 断线保护 +断线时: +- 禁用全部运动按钮 +- UI状态置灰 +- 重连成功后,不自动恢复运动,必须人工再次点方向 + +### 7.3 急停策略 +急停按钮逻辑: +1. 先发 MV:STOP +2. 若当前模式是AUTO,再发 ST:STOP + +### 7.4 自动流程约束 +自动流程推荐顺序: +- SP -> MD:AUTO -> GS -> ST:RUN + +到站后固件会锁存,下一次运行前应: +- 重发 GS +- 再发 ST:RUN + +--- + +## 8. 后端参考代码骨架(最小可运行) + +main.py 示例: + +```python +from fastapi import FastAPI, WebSocket +from app.api.http_routes import router as http_router +from app.api.ws_routes import router as ws_router + +app = FastAPI(title="Car Upper Computer") +app.include_router(http_router, prefix="/api") +app.include_router(ws_router) + +@app.get("/api/health") +async def health(): + return {"ok": True} +``` + +command_service.py 的命令发送入口示例: + +```python +async def send_payload(payload: str, wait_feedback: bool = True): + frame = build_frame(payload) + await tcp_client.send_frame(frame) + await ws_hub.broadcast("tx_log", {"frame": frame}) + if not wait_feedback: + return {"ok": True} + fb = await feedback_waiter.wait(payload.split(":", 1)[0], timeout=0.8) + return {"ok": fb is not None, "feedback": fb} +``` + +--- + +## 9. 联调步骤(按此执行) + +1. 启动后端 +- uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload + +2. 启动前端 +- npm install +- npm run dev + +3. 页面连接到小车TCP +- 输入IP和端口 +- 点击连接 + +4. 先测基础命令 +- SP:050 +- MD:MAN +- MV:FWD +- MV:STOP + +5. 再测自动流程 +- MD:AUTO +- GS:001 +- ST:RUN + +6. 检查状态面板字段是否持续刷新 + +--- + +## 10. 测试清单(必须通过) + +单元测试: +- build_frame校验值正确 +- verify_checksum正确拦截坏包 +- parser支持粘包拆包 + +集成测试: +- 命令发送后能收到FB +- 状态包断流时 stale 告警出现 +- 断线后自动重连并恢复状态展示 + +交互测试: +- 手动按钮按下/松开逻辑正确 +- 急停在任意状态都可触发 + +--- + +## 11. 给开发AI的最终任务提示词 + +请实现一个 Python Web 上位机系统,后端用 FastAPI + asyncio TCP + WebSocket,前端用 Vue3 或 React。协议为 LOGI::#,CS 为 LOGI: 的 ASCII 累加低8位。实现 SP/ST/GS/MD/MV 命令,解析 STAT 与 FB。支持手动麦轮方向控制(8方向+旋转+STOP)和自动模式控制。必须实现 TCP 粘包拆包解析、命令反馈超时处理、自动重连、状态实时推送、日志面板与安全急停逻辑。 diff --git a/docs/upper_computer_ai_guide_zh.md b/docs/upper_computer_ai_guide_zh.md new file mode 100644 index 0000000..2103817 --- /dev/null +++ b/docs/upper_computer_ai_guide_zh.md @@ -0,0 +1,311 @@ +# 上位机开发AI执行文档(物流小车 TCP 协议) + +## 1. 文档目标 +这份文档是写给“负责开发上位机的AI”的,不是普通说明书。 +目标是让AI按本文档直接产出可运行的上位机程序(TCP客户端),并且与当前固件协议完全兼容。 + +适用范围: +- Windows上位机(Python / C# / C++ / Electron均可) +- 控制模式:自动循迹 + 手动麦克纳姆 +- 通信方式:TCP,ASCII协议 + +不做的事情: +- 不修改单片机固件协议 +- 不更换通信栈 + +--- + +## 2. 协议总规范(必须严格遵守) + +### 2.1 帧格式 +所有收发数据都使用: + +LOGI::# + +字段说明: +- LOGI: 固定帧头 +- : 有效载荷 +- : 2位大写十六进制校验和 +- #: 固定帧尾 + +### 2.2 校验和算法 +计算范围是 LOGI:PAYLOAD 这一段(不含最后 :CS#)。 +算法:ASCII逐字节累加,取低8位。 + +示例代码(Python): + +```python +def build_frame(payload: str) -> str: + base = f"LOGI:{payload}" + cs = sum(base.encode("ascii")) & 0xFF + return f"{base}:{cs:02X}#" +``` + +示例代码(C#): + +```csharp +static string BuildFrame(string payload) +{ + string baseStr = $"LOGI:{payload}"; + int sum = 0; + foreach (byte b in System.Text.Encoding.ASCII.GetBytes(baseStr)) + sum = (sum + b) & 0xFF; + return $"{baseStr}:{sum:X2}#"; +} +``` + +硬性要求: +- 十六进制统一大写 +- 不要额外拼接换行(除非你的网络调试工具强制) + +--- + +## 3. 下行指令(上位机 -> 小车) + +### 3.1 通用控制 +- SP:xxx 设置速度(000~100) +- ST:RUN 自动启动 +- ST:STOP 自动停止 +- GS:xxx 设置目标站点(当前固件支持001/002) + +### 3.2 模式切换(新增) +- MD:MAN 切换手动麦轮模式 +- MD:AUTO 切换自动循迹模式 + +### 3.3 手动方向(新增,仅MAN模式有效) +- MV:FWD 前进 +- MV:BWD 后退 +- MV:LEFT 左平移 +- MV:RIGHT 右平移 +- MV:LF 左前 +- MV:RF 右前 +- MV:LB 左后 +- MV:RB 右后 +- MV:CW 原地顺时针 +- MV:CCW 原地逆时针 +- MV:STOP 停止 + +注意: +- 若处于AUTO模式直接发MV:*,固件会返回 FB:MV:0 +- 上位机应先发 MD:MAN 再发 MV:* + +--- + +## 4. 上行数据(小车 -> 上位机) + +### 4.1 状态上报(约500ms) +PAYLOAD格式: + +STAT:SP:xxx,STA:xxx,RUN:x,MODE:mode,MAN:dir,DIS:d,TRK:b4b3b2b1,DEV:n,OBS:x,RPM:m1:m2:m3:m4 + +字段含义: +- SP: 当前速度百分比 +- STA: 当前目标站点 +- RUN: 1运行,0停止 +- MODE: AUTO 或 MAN +- MAN: STOP/FWD/BWD/LEFT/RIGHT/LF/RF/LB/RB/CW/CCW +- DIS: 超声距离(cm) +- TRK: 循迹4位掩码(H4 H3 H2 H1) +- DEV: 偏差值(自动模式更有意义) +- OBS: 避障锁(1有障碍,0无) +- RPM: 4路电机转速,顺序 M1:M2:M3:M4(LR:LF:RF:RR) + +### 4.2 命令反馈(即时) +PAYLOAD格式: + +FB::<0|1> + +示例: +- FB:SP:1 表示设置速度成功 +- FB:MV:0 表示手动命令被拒绝(如模式不对) + +上位机处理规则: +- 每个按钮操作都要等待反馈超时窗口 +- 无反馈应标记为超时,按策略重试或提示用户 + +--- + +## 5. 上位机程序架构要求(AI必须实现) + +### 5.1 网络层 +功能: +- TCP连接/断开 +- 自动重连 +- 接收字节流 + +要求: +- 重连退避:1s -> 2s -> 3s -> 3s... +- 断线后禁用运动按钮 +- 重连成功后状态切到已连接 + +### 5.2 协议层 +功能: +- 组帧(payload -> frame) +- 解帧(frame -> typed message) +- 校验和验证 + +要求: +- 校验失败帧丢弃并记录日志 +- 提供结构化事件:StatusEvent / FeedbackEvent + +### 5.3 命令调度层 +功能: +- 发送队列串行化 +- 等待反馈 +- 超时重试 + +建议默认: +- 反馈超时 800ms +- 可重试命令:SP、MD、MV:STOP(最多2次) +- ST:RUN、GS:xxx不自动重试,避免误动作 + +### 5.4 UI层 +必须具备控件: +- 网络连接区:IP、端口、连接/断开 +- 模式区:AUTO/MAN +- 速度区:滑条+发送 +- 自动区:站点选择、RUN、STOP +- 手动区:方向盘(8方向)+旋转+STOP +- 状态区:实时显示SP/STA/RUN/MODE/MAN/DIS/TRK/DEV/OBS/RPM +- 日志区:TX/RX原始帧 + 解析结果 + +### 5.5 安全保护 +必须实现: +- 手动方向按钮松开时自动发送 MV:STOP +- 2秒无状态包,UI提示“状态超时” +- 急停键:优先发 MV:STOP;若在AUTO,再发 ST:STOP + +--- + +## 6. 按钮与命令映射(可直接发送,含校验) + +### 6.1 模式 +- 手动模式:LOGI:MD:MAN:0C# +- 自动模式:LOGI:MD:AUTO:69# + +### 6.2 速度快捷键 +- 30%:LOGI:SP:030:D5# +- 50%:LOGI:SP:050:D7# +- 80%:LOGI:SP:080:DA# + +### 6.3 自动流程按钮 +- 站点001:LOGI:GS:001:CA# +- 站点002:LOGI:GS:002:CB# +- 启动:LOGI:ST:RUN:3B# +- 停止:LOGI:ST:STOP:8C# + +### 6.4 手动方向按钮 +- 前进:LOGI:MV:FWD:23# +- 后退:LOGI:MV:BWD:1F# +- 左移:LOGI:MV:LEFT:6D# +- 右移:LOGI:MV:RIGHT:C0# +- 左前:LOGI:MV:LF:D4# +- 右前:LOGI:MV:RF:DA# +- 左后:LOGI:MV:LB:D0# +- 右后:LOGI:MV:RB:D6# +- 顺时针:LOGI:MV:CW:DC# +- 逆时针:LOGI:MV:CCW:1F# +- 停止:LOGI:MV:STOP:88# + +--- + +## 7. 操作时序(AI实现时必须按此流程) + +### 7.1 手动控制时序 +1. 连接TCP +2. 发速度 SP:xxx +3. 发 MD:MAN +4. 按下方向键发 MV:* +5. 松开方向键发 MV:STOP + +建议: +- 优先“边沿发送”(按下发一次,松开发STOP) +- 若网络抖动明显,可每300ms补发一次当前MV,松开必发STOP + +### 7.2 自动运输时序 +1. 连接TCP +2. 发速度 SP:xxx +3. 发 MD:AUTO +4. 发 GS:001 或 GS:002 +5. 发 ST:RUN +6. 监控状态包直到到站 + +固件行为: +- 到站会自动停 +- RUN变0 +- 再次启动前需先重发GS,再发ST:RUN + +--- + +## 8. TCP粘包/拆包解析要求 + +TCP是流,不能假设一次recv就是一帧。 + +必须实现: +- 维护接收缓存 +- 搜索帧头 LOGI: +- 搜索帧尾 # +- 从有效头到尾提取候选帧 +- 校验通过才分发 +- 头前垃圾数据丢弃 + +如果不做这一层,上位机会出现随机解析错误。 + +--- + +## 9. 错误处理策略 + +### 9.1 超时 +- 指令超时无反馈:UI标红并记录日志 +- 安全指令可重试 +- 运动指令超时优先补发STOP + +### 9.2 校验失败 +- 丢弃该帧 +- 记录原始帧日志 +- 不要直接断线 + +### 9.3 模式冲突 +收到 FB:MV:0 时: +1. 自动提示“当前非手动模式” +2. 自动发 MD:MAN +3. 重试一次原MV命令 + +### 9.4 断线 +- 立刻禁用控制按钮 +- 显示离线 +- 重连成功后要求用户确认模式和速度后再控车 + +--- + +## 10. AI交付验收清单(Definition of Done) + +功能通过: +- 能连接/断开TCP +- 能正确收发并解析状态和反馈 +- 全部按钮命令可用 +- 手动10方向可控 +- 自动流程可用(GS+RUN+STOP) + +稳定性通过: +- 粘包拆包处理正确 +- 自动重连生效 +- 超时重试策略生效 +- 日志可追溯 + +可用性通过: +- 状态字段实时刷新 +- 当前模式和方向显示清晰 +- 急停入口明显且可用 + +测试通过: +- 校验和单元测试 +- 解析器单元测试(粘包/拆包/坏包) +- 联调测试(与真实小车或Mock服务) + +--- + +## 11. 给“上位机开发AI”的可直接任务描述 +请将下面这段作为任务输入给编码AI: + +实现一个可发布的物流小车TCP上位机客户端。协议是ASCII帧 LOGI::#,校验算法为 sum(LOGI:的ASCII字节) & 0xFF。实现命令 SP/ST/GS/MD/MV,解析状态STAT和反馈FB。支持自动模式和手动麦克纳姆模式(8方向+旋转+停止)。必须实现TCP流式粘包拆包解析、自动重连、命令超时重试、原始日志和结构化状态显示。UI必须包含连接区、模式区、速度区、自动区、手动方向区、状态区和日志区。 diff --git a/f103_car.ioc b/f103_car.ioc index 4566575..34a2a72 100644 --- a/f103_car.ioc +++ b/f103_car.ioc @@ -33,11 +33,11 @@ Dma.USART2_TX.2.PeriphDataAlignment=DMA_PDATAALIGN_BYTE Dma.USART2_TX.2.PeriphInc=DMA_PINC_DISABLE Dma.USART2_TX.2.Priority=DMA_PRIORITY_LOW Dma.USART2_TX.2.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority -FREERTOS.FootprintOK=true +FREERTOS.FootprintOK=false FREERTOS.IPParameters=Tasks01,FootprintOK,Queues01,configTOTAL_HEAP_SIZE FREERTOS.Queues01=CmdQueue,16,16,1,Dynamic,NULL,NULL -FREERTOS.Tasks01=initTask,24,128,StartDefaultTask,Default,NULL,Dynamic,NULL,NULL;CarCtrlTask,24,256,CarCtrl_Task,As weak,NULL,Dynamic,NULL,NULL;timerTask,16,512,speed_get,As weak,NULL,Dynamic,NULL,NULL;sr04Task,8,128,sr04_task,Default,NULL,Dynamic,NULL,NULL;rc522Task,16,128,rc522_task,Default,NULL,Dynamic,NULL,NULL -FREERTOS.configTOTAL_HEAP_SIZE=10000 +FREERTOS.Tasks01=initTask,24,256,StartDefaultTask,Default,NULL,Dynamic,NULL,NULL;CarCtrlTask,24,1024,CarCtrl_Task,As weak,NULL,Dynamic,NULL,NULL;timerTask,16,1024,speed_get,As weak,NULL,Dynamic,NULL,NULL;sr04Task,8,512,sr04_task,Default,NULL,Dynamic,NULL,NULL;rc522Task,16,512,rc522_task,Default,NULL,Dynamic,NULL,NULL +FREERTOS.configTOTAL_HEAP_SIZE=18000 File.Version=6 GPIO.groupedBy=Group By Peripherals KeepUserPlacement=false diff --git a/物流小车/esp_12f/AiThinker_Serial_Tool_V1.2.3/aithinker_serial_tool.cfg b/物流小车/esp_12f/AiThinker_Serial_Tool_V1.2.3/aithinker_serial_tool.cfg index b52e628..fab40ed 100644 --- a/物流小车/esp_12f/AiThinker_Serial_Tool_V1.2.3/aithinker_serial_tool.cfg +++ b/物流小车/esp_12f/AiThinker_Serial_Tool_V1.2.3/aithinker_serial_tool.cfg @@ -32,7 +32,7 @@ - + @@ -60,7 +60,7 @@ - + diff --git a/物流小车/esp_12f/TCPUDPDbg/config/config.ini b/物流小车/esp_12f/TCPUDPDbg/config/config.ini index abafa64..f5762cd 100644 --- a/物流小车/esp_12f/TCPUDPDbg/config/config.ini +++ b/物流小车/esp_12f/TCPUDPDbg/config/config.ini @@ -10,7 +10,7 @@ SendHex=0 [DlgCreateServer] DlgServerPort=3456 [Update] -Time=1775660879 +Time=1776317344 [SysOptions] SendBlSZforFile=10240 SendBlITforFile=1 diff --git a/物流小车/esp_12f/flash_download_tool_3.9.7/configure/esp8266/hspi_download.conf b/物流小车/esp_12f/flash_download_tool_3.9.7/configure/esp8266/hspi_download.conf index 964f856..1ab60fd 100644 --- a/物流小车/esp_12f/flash_download_tool_3.9.7/configure/esp8266/hspi_download.conf +++ b/物流小车/esp_12f/flash_download_tool_3.9.7/configure/esp8266/hspi_download.conf @@ -62,7 +62,7 @@ file_sel13 = 0 file_path13 = file_flag13 = False file_offset13 = -default_path = E:\My_Workpace\С\esp_12f\flash_download_tool_3.9.7 +default_path = E:\My_Workpace\f103_car\С\esp_12f\flash_download_tool_3.9.7 [FLASH_CRYSTAL] spicfgdis = 1 diff --git a/物流小车/esp_12f/flash_download_tool_3.9.7/configure/esp8266/spi_download.conf b/物流小车/esp_12f/flash_download_tool_3.9.7/configure/esp8266/spi_download.conf index f574595..2d8adce 100644 --- a/物流小车/esp_12f/flash_download_tool_3.9.7/configure/esp8266/spi_download.conf +++ b/物流小车/esp_12f/flash_download_tool_3.9.7/configure/esp8266/spi_download.conf @@ -7,7 +7,7 @@ multi_col = 2 [DOWNLOAD PATH] file_sel0 = 1 -file_path0 = E:\My_Workpace\С\esp_12f\flash_download_tool_3.9.7\1112ESP8266-4M.bin +file_path0 = E:\My_Workpace\f103_car\С\esp_12f\1112ESP8266-4M.bin file_flag0 = False file_offset0 = 0 file_sel1 = 0 @@ -62,7 +62,7 @@ file_sel13 = 0 file_path13 = file_flag13 = False file_offset13 = -default_path = E:\My_Workpace\С\esp_12f\flash_download_tool_3.9.7 +default_path = E:\My_Workpace\f103_car\С\esp_12f [FLASH_CRYSTAL] spicfgdis = 1 diff --git a/物流小车/esp_12f/flash_download_tool_3.9.7/(1112)ESP8266-4M.bin b/物流小车/esp_12f/(1112)ESP8266-4M.bin similarity index 100% rename from 物流小车/esp_12f/flash_download_tool_3.9.7/(1112)ESP8266-4M.bin rename to 物流小车/esp_12f/(1112)ESP8266-4M.bin diff --git a/物流小车/web/app.exe b/物流小车/web/app.exe index 4885dc5fae8a5c87ce2fb7d1b145c0aaaf72b2a3..355caf549365258b2f270e23a19b9041d0d7c05e 100644 GIT binary patch delta 430263 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%0Ay7A)S*Je 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