STM32与MSP430电赛小车通用框架设计从坡道到泊车的代码复用实战参加电子设计竞赛的同学们都知道三天时间内从零开始搭建一个功能完善的小车系统几乎是不可能完成的任务。更让人头疼的是每年小车类赛题看似千变万化——从循迹到泊车从无线充电到双车跟随每个题目都要求不同的传感器组合和控制逻辑。但仔细观察就会发现这些赛题在技术本质上有着惊人的相似性。本文将分享一套经过实战检验的代码框架设计方法帮助你在不同赛题间快速切换把有限的时间用在算法优化而不是重复造轮子上。1. 分层架构构建可复用的代码骨架1.1 硬件抽象层设计硬件抽象层(HAL)是小车程序中最基础的组成部分它直接与各种传感器和执行机构打交道。好的HAL设计应该像瑞士军刀一样灵活能够适配不同的硬件组合而不需要重写上层逻辑。以红外循迹模块为例无论是常见的TCRT5000还是更高级的红外阵列我们都可以用统一的接口来封装// 红外传感器接口定义 typedef struct { uint8_t (*read)(void); // 读取传感器状态 void (*calibrate)(void); // 校准函数 float position; // 归一化位置值[-1,1] } LineSensor_TypeDef; // 具体传感器实现 void TCRT5000_Init(LineSensor_TypeDef *sensor) { sensor-read TCRT5000_Read; sensor-calibrate TCRT5000_Calibrate; } // 上层调用完全不需要知道具体传感器型号 LineSensor_TypeDef left_sensor, right_sensor; TCRT5000_Init(left_sensor); TCRT5000_Init(right_sensor);这种设计带来的好处是显而易见的当赛题要求从简单的5路红外升级到32路红外阵列时你只需要替换初始化函数所有上层算法都不需要修改。1.2 运动控制层实现运动控制层负责将抽象的移动指令转化为具体的电机PWM信号。这里我们需要处理不同底盘结构的差异——两轮差速、四轮转向或者阿克曼转向。差速驱动是最常见的结构其核心是左右轮速度的独立控制。我们可以定义一个通用的差速控制结构typedef struct { // 电机控制接口 void (*set_left_motor)(int16_t speed); void (*set_right_motor)(int16_t speed); // 运动参数 float max_speed; // 最大速度(cm/s) float wheel_base; // 轮距(cm) float wheel_radius; // 轮半径(cm) // PID参数 PID_TypeDef linear_pid; PID_TypeDef angular_pid; } DifferentialDrive_TypeDef;对于阿克曼转向的泊车题目我们则需要增加转向控制typedef struct { void (*set_steering)(float angle); // 转向角度(-30~30度) void (*set_speed)(float speed); // 前进速度 // 车辆参数 float wheelbase; // 轴距 float track_width; // 轮距 float max_steer_angle;// 最大转向角 } AckermannDrive_TypeDef;1.3 任务逻辑层抽象任务逻辑层是直接对应赛题要求的部分也是复用性最低的一层。但通过合理设计我们仍然可以提取出一些通用模式。以自动泊车为例无论题目要求垂直泊车还是平行泊车都可以抽象为以下几个状态stateDiagram-v2 [*] -- 寻找车位 寻找车位 -- 调整位置: 检测到车位 调整位置 -- 倒车入库: 位置合适 倒车入库 -- 微调位置: 进入车位 微调位置 -- 完成: 位置正确 完成 -- [*]通过状态机实现这些逻辑可以保持代码清晰且易于调试typedef enum { PARKING_IDLE, PARKING_SEARCH, PARKING_ADJUST, PARKING_BACKING, PARKING_FINE_TUNE, PARKING_DONE } ParkingState_t; void ParkingTask_Update(ParkingState_t *state) { static uint32_t enter_time; switch(*state) { case PARKING_SEARCH: if(DetectParkingSpace()) { *state PARKING_ADJUST; enter_time HAL_GetTick(); } break; case PARKING_ADJUST: if(PositionForBacking()) { *state PARKING_BACKING; } else if(HAL_GetTick() - enter_time 5000) { // 超时处理 *state PARKING_IDLE; } break; // 其他状态处理... } }2. 传感器数据融合让小车更聪明地感知环境2.1 多传感器数据校准电赛小车通常会配备多种传感器如红外、超声波、编码器等。这些传感器各有优缺点需要通过数据融合来提高环境感知的可靠性。首先需要解决的是传感器校准问题。以红外测距传感器为例我们可以建立一个校准表来补偿非线性// 红外测距校准表 (电压值 - 实际距离cm) const float IR_DISTANCE_CALIB[256] { [50] 80.0f, // 电压值50对应80cm [100] 30.0f, // 电压值100对应30cm [150] 15.0f, // ... [200] 8.0f, [250] 5.0f }; float GetIRDistance(uint8_t adc_value) { // 线性插值计算实际距离 uint8_t lower 0, upper 255; for(int i0; i256; i) { if(IR_DISTANCE_CALIB[i] ! 0) { if(i adc_value) lower i; if(i adc_value upper 255) upper i; } } if(lower upper) return IR_DISTANCE_CALIB[lower]; float ratio (float)(adc_value - lower) / (upper - lower); return IR_DISTANCE_CALIB[lower] ratio * (IR_DISTANCE_CALIB[upper] - IR_DISTANCE_CALIB[lower]); }2.2 互补滤波在姿态估计中的应用对于坡道行驶题目小车需要准确感知自身倾角。单独使用加速度计会受到震动干扰单独使用陀螺仪会有积分漂移。互补滤波是一种简单有效的解决方案float ComplementaryFilter(float accel_angle, float gyro_rate, float dt) { static float angle 0; const float alpha 0.98f; // 陀螺仪权重 // 陀螺仪积分 angle gyro_rate * dt; // 加速度计补偿 angle alpha * angle (1 - alpha) * accel_angle; return angle; }在实际应用中我们可以进一步优化这个滤波器typedef struct { float angle; // 当前估计角度 float bias; // 陀螺仪零偏 float dt; // 采样周期 float q_angle; // 过程噪声协方差 float q_bias; // 零偏噪声协方差 float r_measure; // 测量噪声协方差 } SimpleKalman_TypeDef; float SimpleKalman_Update(SimpleKalman_TypeDef *k, float new_angle, float new_rate) { // 预测 k-angle (new_rate - k-bias) * k-dt; k-bias k-bias; // 更新 float y new_angle - k-angle; float s k-q_angle k-r_measure; float k_angle k-q_angle / s; float k_bias k-q_bias / s; k-angle k_angle * y; k-bias k_bias * y; k-q_angle - k_angle * k-q_angle; k-q_bias - k_bias * k-q_bias; return k-angle; }2.3 传感器数据融合实战在2021年智能送药小车赛题中冠军队伍使用了多传感器融合的方案传感器类型功能采样频率权重红外阵列循迹100Hz0.6编码器里程计算50Hz0.3IMU方向修正200Hz0.1这种融合策略可以用以下代码实现typedef struct { float line_position; // 循迹传感器给出的位置 float encoder_offset; // 编码器计算的位置偏移 float imu_yaw_rate; // IMU测量的偏航角速度 float position; // 融合后的位置估计 float confidence[3]; // 各传感器置信度 } SensorFusion_TypeDef; void UpdateSensorFusion(SensorFusion_TypeDef *fusion, float dt) { // 更新各传感器置信度(可根据信号质量动态调整) if(fusion-line_position 0) { fusion-confidence[0] 0; // 红外信号丢失 } else { fusion-confidence[0] 0.6f; } fusion-confidence[1] 0.3f; fusion-confidence[2] 0.1f; // 归一化权重 float sum fusion-confidence[0] fusion-confidence[1] fusion-confidence[2]; float w_line fusion-confidence[0] / sum; float w_encoder fusion-confidence[1] / sum; float w_imu fusion-confidence[2] / sum; // 融合计算 fusion-position w_line * fusion-line_position w_encoder * fusion-encoder_offset w_imu * fusion-imu_yaw_rate * dt; }3. 控制算法从PID到状态机的实战技巧3.1 自适应PID控制器设计PID控制是小车运动控制的核心但固定参数的PID在不同场景下表现差异很大。我们可以实现一个自适应PID控制器typedef struct { float Kp, Ki, Kd; // PID参数 float integral; // 积分项 float prev_error; // 上一次误差 float dt; // 采样时间 float out_max, out_min; // 输出限幅 // 自适应参数 float error_threshold; // 误差阈值 float Kp_scale; // 比例系数缩放因子 } AdaptivePID_TypeDef; float AdaptivePID_Update(AdaptivePID_TypeDef *pid, float setpoint, float measurement) { float error setpoint - measurement; // 根据误差大小自适应调整比例项 float effective_Kp pid-Kp; if(fabs(error) pid-error_threshold) { effective_Kp * pid-Kp_scale; } // 比例项 float P effective_Kp * error; // 积分项(带抗饱和) pid-integral pid-Ki * error * pid-dt; if(pid-integral pid-out_max) pid-integral pid-out_max; if(pid-integral pid-out_min) pid-integral pid-out_min; // 微分项 float D pid-Kd * (error - pid-prev_error) / pid-dt; pid-prev_error error; // 计算输出 float output P pid-integral D; if(output pid-out_max) output pid-out_max; if(output pid-out_min) output pid-out_min; return output; }对于坡道行驶题目我们还需要考虑重力分量对小车的影响。可以在PID输出上增加一个前馈补偿float slope_compensation sinf(slope_angle) * GRAVITY_COMPENSATION_FACTOR; float output pid_output slope_compensation;3.2 有限状态机在复杂任务中的应用电赛小车题目往往包含多个阶段的任务如寻找车位-调整位置-倒车入库-微调位置。有限状态机(FSM)是管理这类复杂逻辑的理想工具。下面是一个用于自动泊车的状态机实现typedef enum { PARK_IDLE, PARK_SEARCH_LEFT, PARK_SEARCH_RIGHT, PARK_ALIGN_FRONT, PARK_BACKING, PARK_FINE_TUNE, PARK_COMPLETE, PARK_ABORT } ParkingState_t; typedef struct { ParkingState_t state; uint32_t state_enter_time; ParkingParams_t params; ParkingSensors_t sensors; } ParkingFSM_TypeDef; void ParkingFSM_Update(ParkingFSM_TypeDef *fsm) { uint32_t now HAL_GetTick(); uint32_t state_duration now - fsm-state_enter_time; switch(fsm-state) { case PARK_SEARCH_LEFT: // 控制小车沿左侧缓慢前进寻找车位 SetMotorSpeed(SEARCH_SPEED, SEARCH_SPEED); if(DetectParkingSpace(fsm-sensors.right_dist)) { fsm-state PARK_ALIGN_FRONT; fsm-state_enter_time now; } else if(state_duration MAX_SEARCH_TIME) { fsm-state PARK_SEARCH_RIGHT; fsm-state_enter_time now; } break; case PARK_ALIGN_FRONT: // 调整小车位置准备倒车 if(PositionForBacking(fsm-sensors)) { fsm-state PARK_BACKING; fsm-state_enter_time now; } else if(state_duration ALIGN_TIMEOUT) { fsm-state PARK_ABORT; } break; case PARK_BACKING: // 倒车入库控制逻辑 if(CheckParkingComplete(fsm-sensors)) { fsm-state PARK_FINE_TUNE; fsm-state_enter_time now; } else if(CheckCollisionRisk(fsm-sensors)) { fsm-state PARK_ABORT; } break; // 其他状态处理... } }3.3 运动规划算法实现对于需要精确路径控制的赛题如双车跟随或复杂循迹我们需要引入简单的运动规划算法。下面是一个基于预瞄点的路径跟踪算法typedef struct { float lookahead_distance; // 预瞄距离 float max_steering_angle; // 最大转向角 float wheelbase; // 轴距 } PurePursuit_TypeDef; float PurePursuit_CalculateSteering(PurePursuit_TypeDef *pp, PathPoint_t *path, uint16_t path_length, VehicleState_t *vehicle) { // 1. 寻找最近的路径点 uint16_t closest_idx FindClosestPathPoint(path, path_length, vehicle-position); // 2. 计算预瞄点 uint16_t lookahead_idx closest_idx; float accumulated_dist 0; while(lookahead_idx path_length-1 accumulated_dist pp-lookahead_distance) { float segment_length DistanceBetweenPoints( path[lookahead_idx], path[lookahead_idx1]); accumulated_dist segment_length; lookahead_idx; } // 3. 计算转向角度 float alpha atan2f( path[lookahead_idx].y - vehicle-position.y, path[lookahead_idx].x - vehicle-position.x) - vehicle-heading; float steering atan2f(2.0f * pp-wheelbase * sinf(alpha), pp-lookahead_distance); // 限制转向角度 if(steering pp-max_steering_angle) steering pp-max_steering_angle; if(steering -pp-max_steering_angle) steering -pp-max_steering_angle; return steering; }4. 调试与优化提升系统可靠性的关键技巧4.1 实时数据可视化调试在紧张的比赛时间内高效的调试方法可以节省大量时间。通过串口或者无线模块将关键数据发送到上位机进行可视化是极为有效的手段。下面是一个简单的数据封装协议typedef struct { uint8_t header[2]; // 固定为0xAA,0xBB uint16_t packet_id; // 数据包ID uint32_t timestamp; // 时间戳(ms) float data[8]; // 数据负载 uint8_t checksum; // 校验和 } DebugPacket_TypeDef; void SendDebugData(UART_HandleTypeDef *huart, DebugPacket_TypeDef *packet) { // 计算校验和 packet-checksum 0; uint8_t *p (uint8_t*)packet; for(int i0; isizeof(DebugPacket_TypeDef)-1; i) { packet-checksum p[i]; } // 发送数据 HAL_UART_Transmit(huart, (uint8_t*)packet, sizeof(DebugPacket_TypeDef), 100); }在PC端可以使用Python简单实现数据接收和可视化import serial import matplotlib.pyplot as plt import numpy as np ser serial.Serial(COM3, 115200, timeout1) plt.ion() fig, ax plt.subplots(3,1) data [[] for _ in range(3)] while True: packet ser.read(24) # 假设数据包24字节 if len(packet) 24 and packet[0] 0xAA and packet[1] 0xBB: # 解析数据 (假设小端格式) values np.frombuffer(packet[8:32], dtypef4) # 更新绘图数据 for i in range(3): data[i].append(values[i]) if len(data[i]) 100: data[i].pop(0) ax[i].clear() ax[i].plot(data[i]) ax[i].set_ylabel(fData {i1}) plt.pause(0.01)4.2 参数实时调整技巧比赛现场经常需要快速调整PID参数或控制逻辑。通过设计参数调节接口可以避免频繁烧录程序。一种简单的方法是利用串口命令调节参数typedef struct { float Kp, Ki, Kd; float max_speed; // 其他可调参数... } TunableParams_TypeDef; TunableParams_TypeDef params; void ProcessUARTCommand(char *cmd) { if(sscanf(cmd, KP %f, params.Kp) 1) { printf(Set Kp to %.2f\n, params.Kp); } else if(sscanf(cmd, KI %f, params.Ki) 1) { printf(Set Ki to %.2f\n, params.Ki); } else if(sscanf(cmd, SPEED %f, params.max_speed) 1) { printf(Set max speed to %.2f\n, params.max_speed); } // 其他命令处理... }更高级的做法是使用蓝牙模块和手机APP交互实现触控调节。一些现成的蓝牙串口模块(如HC-05)可以快速实现这一功能。4.3 系统稳定性保障措施电赛现场环境复杂可靠的异常处理机制至关重要。以下是一些实用的稳定性保障技巧看门狗定时器配置// STM32独立看门狗配置 void IWDG_Config(void) { hiwdg.Instance IWDG; hiwdg.Init.Prescaler IWDG_PRESCALER_32; // 32分频 hiwdg.Init.Reload 0xFFF; // 约1.6s超时 hiwdg.Init.Window IWDG_WINDOW_DISABLE; if (HAL_IWDG_Init(hiwdg) ! HAL_OK) { Error_Handler(); } } // 在主循环中定期喂狗 void main(void) { IWDG_Config(); while(1) { // ...主程序逻辑 HAL_IWDG_Refresh(hiwdg); } }传感器数据有效性检查#define SENSOR_TIMEOUT_MS 100 typedef struct { float value; uint32_t last_update; } TimedSensor_TypeDef; bool IsSensorDataValid(TimedSensor_TypeDef *sensor) { return HAL_GetTick() - sensor-last_update SENSOR_TIMEOUT_MS; } void SensorUpdateCallback(TimedSensor_TypeDef *sensor, float new_value) { sensor-value new_value; sensor-last_update HAL_GetTick(); }电机安全保护void MotorSafetyCheck(Motor_TypeDef *motor) { // 检查电机温度 if(motor-temperature MAX_SAFE_TEMP) { Motor_Stop(motor); SetErrorFlag(MOTOR_OVERHEAT); return; } // 检查电流是否过大 if(fabs(motor-current) MAX_SAFE_CURRENT) { Motor_Stop(motor); SetErrorFlag(MOTOR_OVERLOAD); return; } // 检查堵转情况 if(fabs(motor-speed_rpm) MIN_SPEED fabs(motor-current) STALL_CURRENT_THRESHOLD) { Motor_Stop(motor); SetErrorFlag(MOTOR_STALLED); } }5. 跨平台开发STM32与MSP430的代码移植技巧5.1 硬件抽象层移植策略要在STM32和MSP430之间共享代码关键在于良好的硬件抽象。我们可以定义一组统一的硬件接口// 通用GPIO接口 typedef struct { void (*set_high)(void); void (*set_low)(void); void (*toggle)(void); uint8_t (*read)(void); } GPIO_Interface; // STM32实现 void STM32_GPIO_SetHigh(void) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); } void STM32_GPIO_SetLow(void) { HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); } GPIO_Interface stm32_led { .set_high STM32_GPIO_SetHigh, .set_low STM32_GPIO_SetLow, // 其他函数... }; // MSP430实现 void MSP430_GPIO_SetHigh(void) { P1OUT | BIT0; } void MSP430_GPIO_SetLow(void) { P1OUT ~BIT0; } GPIO_Interface msp430_led { .set_high MSP430_GPIO_SetHigh, .set_low MSP430_GPIO_SetLow, // 其他函数... };5.2 定时器与PWM的跨平台封装定时器是运动控制的核心不同平台的配置差异很大。我们可以创建统一的定时器接口typedef struct { void (*init)(uint32_t freq); void (*set_duty)(uint8_t channel, float duty); void (*start)(void); void (*stop)(void); } PWM_Interface; // STM32的PWM实现 void STM32_PWM_Init(uint32_t freq) { TIM_HandleTypeDef htim; // ...初始化代码 HAL_TIM_PWM_Start(htim, TIM_CHANNEL_1); } PWM_Interface stm32_pwm { .init STM32_PWM_Init, // 其他函数... }; // MSP430的PWM实现 void MSP430_PWM_Init(uint32_t freq) { TA0CCR0 (MCLK_FREQ / freq) - 1; // 设置周期 TA0CCTL1 OUTMOD_7; // 复位/置位模式 TA0CTL TASSEL_2 MC_1; // SMCLK, 增计数模式 } PWM_Interface msp430_pwm { .init MSP430_PWM_Init, // 其他函数... };5.3 外设驱动移植实例I2C传感器以常见的I2C传感器(如MPU6050)为例展示如何编写可移植的驱动代码// 通用I2C接口 typedef struct { uint8_t (*read)(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint16_t len); uint8_t (*write)(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint16_t len); } I2C_Interface; // MPU6050驱动(与平台无关) typedef struct { I2C_Interface *i2c; uint8_t address; } MPU6050_TypeDef; uint8_t MPU6050_Read(MPU6050_TypeDef *dev, uint8_t reg, uint8_t *data, uint16_t len) { return dev-i2c-read(dev-address, reg, data, len); } // STM32的I2C实现 uint8_t STM32_I2C_Read(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint16_t len) { HAL_I2C_Mem_Read(hi2c1, dev_addr, reg_addr, I2C_MEMADD_SIZE_8BIT, data, len, 100); return HAL_OK; } // MSP430的I2C实现 uint8_t MSP430_I2C_Read(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint16_t len) { I2C_setSlaveAddress(dev_addr); I2C_writeByte(reg_addr); I2C_readBytes(data, len); return SUCCESS; }5.4 代码组织与构建系统为了便于在两种平台间共享代码合理的项目结构至关重要/project /common # 平台无关代码 /controllers # 控制算法 /sensors # 传感器驱动 /utils # 通用工具 /stm32 # STM32专用代码 /hal # 硬件抽象层 /drivers # 外设驱动 /msp430 # MSP430专用代码 /hal /drivers /docs # 文档使用条件编译可以进一步简化代码管理// config.h #define PLATFORM_STM32 1 #define PLATFORM_MSP430 2 #define CURRENT_PLATFORM PLATFORM_STM32 // platform_hal.h #if CURRENT_PLATFORM PLATFORM_STM32 #include stm32_hal.h #elif CURRENT_PLATFORM PLATFORM_MSP430 #include msp430_hal.h #endif6. 实战案例从坡道行驶到自动泊车的代码适配6.1 坡道行驶的PID调参技巧坡道行驶题目要求小车在斜坡上保持匀速这对PID控制提出了挑战。关键在于识别坡度变化并动态调整控制参数。我们可以实现一个坡度感知的PID调节器typedef struct { PID_TypeDef pid; float slope_angle; // 当前坡度估计(弧度) float slope_compensation;// 坡度补偿因子 float last_accel; // 上次加速度测量 } SlopeAwarePID_TypeDef; float SlopeAwarePID_Update(SlopeAwarePID_TypeDef *spid, float setpoint, float speed) { // 1. 估计当前坡度(通过加速度计和运动学) float accel (speed - spid-last_speed) / spid-pid.dt; float expected_accel spid-pid.output / WHEEL_RADIUS; spid-slope_angle asinf((accel - expected_accel) / GRAVITY); // 2. 根据坡度调整PID参数 if(fabs(spid-slope_angle) SLOPE_THRESHOLD) { spid-pid.Kp BASE_KP * (1 fabs(spid-slope_angle) * 0.5f); spid-pid.Ki BASE_KI * (1 fabs(spid-slope_angle) * 0.3f); } else { spid-pid.Kp BASE_KP; spid-pid.Ki BASE_KI; } // 3. 计算坡度补偿 spid-slope_compensation sinf(spid-slope_angle) * GRAVITY_COMPENSATION; // 4. 更新PID float output PID_Update(spid-pid, setpoint, speed); // 5. 应用补偿 return output spid-slope_compensation; }6.2 自动泊车的状态机实现自动泊车通常分为几个阶段寻找车位、调整位置、倒车入库和微调。每个阶段需要不同的控制策略typedef enum { PARK_SEARCH, // 寻找车位 PARK_ALIGN, // 调整位置 PARK_BACKING, // 倒车入库 PARK_FINETUNE, // 微调位置 PARK_DONE // 完成 } ParkingState_t; typedef struct { ParkingState_t state; uint32_t state_time; float target_position[3]; // x,y,theta ParkingSensors_t sensors; } ParkingController_TypeDef; void ParkingController_Update(ParkingController_TypeDef *pc) { switch(pc-state) { case PARK_SEARCH: // 沿车位缓慢移动监测右侧距离 if(pc-sensors.right_distance PARKING_SPOT_WIDTH) { pc-state PARK_ALIGN; pc-state_time HAL_GetTick(); } break; case PARK_ALIGN: // 计算需要前进的距离 float align_distance CalculateAlignDistance(pc-sensors); // 使用PID控制前进到目标位置 if(MoveForward(align_distance)) { pc-state PARK_BACKING; pc-state_time HAL_GetTick(); // 设置倒车目标位置 SetBackingTarget(pc-target_position); } break; case PARK_BACKING: // 使用纯追踪算法控制倒车 float steering PurePursuit_Calculate(pp_controller, pc-target_position); SetSteeringAngle(steering); // 检查是否到达目标位置 if(CheckParkingComplete(pc-sensors)) { pc-state PARK_FINETUNE; } break; // 其他状态处理... } }6.3 双车跟随的通信协议设计双车跟随题目需要两车之间保持稳定的通信。一个简单可靠的通信协议至关重要// 通信数据包结构 #pragma pack(push, 1) typedef struct { uint8_t header; // 0xAA uint8_t seq_num; // 序列号 uint16_t leader_speed; // 前车速度(mm/s) int16_t relative_distance; // 相对距离(mm) uint8_t checksum; // 校验和 } FollowPacket_TypeDef; #pragma pack(pop) // 数据包发送 void SendFollowPacket(UART_HandleTypeDef *huart, uint16_t speed, int16_t distance) { static uint8_t seq 0; FollowPacket_TypeDef packet; packet.header 0xAA; packet.seq_num seq; packet.leader_speed speed; packet.relative_distance distance; // 计算校验和 packet.checksum 0; uint8_t *p (uint8_t*)packet; for(int i0; isizeof(packet)-1; i) { packet.checksum p[i]; } // 发送数据 HAL_UART_Transmit(huart, (uint8_t*)packet, sizeof(packet), 100); } // 数据包接收处理 void ProcessFollowPacket(UART_HandleTypeDef *huart, uint16_t *speed, int16_t *distance) { static FollowPacket_TypeDef packet; static uint8_t buf[sizeof(packet)]; static uint8_t idx 0; // 接收数据 uint8_t byte; if(HAL_UART_Receive(huart, byte, 1, 0) HAL_OK) { buf[idx] byte; // 检查包头 if(idx 1 buf[0] ! 0xAA) { idx 0; return; } // 完整数据包接收 if(idx sizeof(packet)) { memcpy(packet, buf, sizeof(packet)); idx 0; // 校验 uint8_t sum 0; for(int i0; isizeof(packet)-1; i) { sum buf[i]; } if(sum packet.checksum) { *speed packet.leader_speed; *distance packet.relative_distance; } } } }6.4 无线充电小车的能量管理对于无线充电题目高效的能量管理是取得好成绩的关键typedef enum { ENERGY_SEARCH, // 寻找充电区域 ENERGY_CHARGING, // 正在充电 ENERGY_DISCHARGING, // 放电行驶 ENERGY_LOW // 低电量状态 } EnergyState_t; typedef struct { EnergyState_t state; float battery_voltage; float capacitor_voltage; float charging_current; float discharging_current;