智能车窗控制系统:从架构设计到工程落地的深度解析
第一个自研项目
智能车窗控制系统:从架构设计到工程落地的深度解析
一、系统架构:为什么选择"多层解耦"而非"单片机堆砌"
1.1 行业痛点与架构设计原则
| 传统车窗系统问题 | 本项目解决方案 | 行业对标(德赛西威车载系统) |
|---|---|---|
| 传感器数据直接驱动电机(无安全逻辑) | 安全层独立隔离(红外+雨滴双路触发) | ✅ 车规级安全冗余设计 |
| 通信协议混乱(UART/蓝牙混用) | 统一通信协议栈(自定义CAN帧) | ✅ 参考ISO 11898车载总线标准 |
| 代码紧耦合(按键/蓝牙/传感器混杂) | 任务层解耦(FreeRTOS多任务) | ✅ 汽车电子标准架构 |
1.2 四层架构图解(附代码逻辑关联)
1.3 STM32 核心硬件资源清单
本项目基于 STM32F103 主控开发,所有单片机片上资源均做合理规划与精准复用,无冗余占用,以下为项目中实际用到的全部硬件资源及对应的业务落地场景,也是嵌入式开发中「资源合理化分配」的工程实践:
| 资源类型 | 具体资源项 | 项目实际应用场景 |
|---|---|---|
| GPIO | GPIOB/GPIOC/GPIOx 多组引脚 | 1. 物理按键输入 2. 四路红外防夹传感器数字信号采集 3. 28BYJ-48 步进电机四相驱动引脚 |
| TIM | TIM1、TIM2、TIM3、TIM4 | 1. TIM1:配置成定时器中断更新PID数据,并扫描按键 2. TIM2: 蓝牙串口接收数据的超时判断(TIM2->CNT>15)3. TIM3:语音串口接收数据的超时判断(TIM3->CNT>15)4. TIM4: 系统时间戳 |
| 串口通信 (UART/USART) | UART1、UART2 | 1. UART1:外接 JDY33 蓝牙模块,接收蓝牙指令,并模拟 CAN 总线数据透传,周期性发送 8 字节状态帧 2. UART2:外接语音模块,接收语音指令同时传输系统状态,进行语音播报 |
| ADC 模拟数字转换器 | 单通道 ADC 采集引脚 | 雨滴传感器模拟量采集 |
| FreeRTOS 内核资源 | 消息队列、互斥锁、多任务调度 | 1. 消息队列:xQueueControl(控制指令队列)、xQueueSensor(传感器数据队列)2. 互斥锁:status_mutexHandle 保护全局变量g_system_state的原子操作3. 任务调度:4 个分级优先级任务(控制 / 传感器 / 通信 / 默认任务) |
| NVIC 中断控制器 | 中断优先级分组、抢占优先级配置 | 1. 串口接收中断:蓝牙指令异步接收,无阻塞采集2. 定时器中断:高优先级响应,保障按键扫描、PID 调速的实时性3. 电机驱动中断:步进电机步序精准切换,无丢步卡顿 |
| 片上存储 & 全局变量区 | 自定义结构体 / 全局状态区 | 1. 存储g_motors[4]电机数组、g_system_state全局系统状态2. PID 控制器参数缓存 3. 按键长 / 短按计数变量、电机步数 / 位置缓存 |
1.4 硬件版本迭代:V1 稳定版 vs V2 车载网关版(原理图+PCB 深度解析)
版本硬件核心差异总览
| 对比维度 | V1 基础稳定版 | V2 车载网关版 |
|---|---|---|
| 主控芯片 | STM32F103C8T6 | STM32F103RCT6(更大闪存、更多IO) |
| 核心定位 | 单一车窗控制器(串口模拟 CAN) | 车载异构总线网关(串口模拟 CAN → 真实 CAN 总线转发) |
| 电机驱动方案 | ULN2003 达林顿管阵列(适配 28BYJ-48) | L298N 双 H 桥驱动模块(硬件选型适配优化中) |
| 通信功能 | UART+蓝牙+语音 | UART+蓝牙+语音+CAN 总线 |
| 电源滤波 | 基础去耦电容 | 新增多路 100uF+0.1uF 组合滤波电容 |
| 核心优势 | 功能稳定、易复现、调试成本低 | 1. 对接车规级 CAN 总线,适配真实车载场景;2. 实现 “串口模拟 CAN 帧 → 真实 CAN 帧” 的透明转发;3. 保留 V1 全部功能并拓展车载总线能力 |
| 待解问题 | 无 | L298N 与 28BYJ-48 步进电机的驱动逻辑适配(已明确解决方案,不影响网关核心功能) |
一、V1 基础稳定版:原理图+PCB 设计要点
1. 核心原理图解析
- 最小系统模块:包含 STM32F103C8T6 主控、3.3V 稳压电路、复位电路、晶振电路,是整个系统的基础。
- 电机驱动模块:采用 ULN2003 驱动 28BYJ-48 步进电机,IN1-IN4 直接连接 STM32 GPIO,无需额外功率放大,电路简洁可靠。
- 传感器接口模块:红外防夹传感器(数字量)、雨水传感器(模拟量)接口独立布局,均做了上拉/下拉处理,抗干扰性强。
- 通信模块:UART1 接蓝牙模块,UART2 接语音模块,引脚定义清晰无冲突。
2. PCB 布局关键原则
- 电源走线:采用 1.2mm 宽铜箔走主电源,避免小电流走线发热,GND 做铺地处理,降低电磁干扰。
- 模块分区:主控、驱动、传感器三大模块分区布局,减少信号线交叉,缩短电机驱动线长度,降低信号衰减。
- 丝印标注:所有接口、元件都标注清晰丝印,方便焊接和后期调试。
V1 原理图
V1 PCB
二、V2 车载网关版:原理图+PCB 升级与待解问题
1. 原理图核心变更点
- 主控升级:更换为 STM32F103RCT6,扩展了 CAN 通信引脚(PA11/CAN_RX、PA12/CAN_TX),新增 CAN 收发器电路。
- 驱动更换:移除 ULN2003,替换为 4 路 L298N 模块,每路驱动对应一个车窗电机,IN1-IN4 连接主控 GPIO,ENA/ENB 均配置为高电平使能。
- 电源优化:每个 L298N 模块旁都并联 100uF 电解电容+0.1uF 陶瓷电容,强化电源去耦,减少电机启停时的电压波动。
2. PCB 布局改进与待排查点
- 改进点:CAN 总线两端增加 120Ω 终端电阻,优化总线阻抗匹配;传感器模块与驱动模块做电气隔离,避免干扰。
- 待排查点:电机驱动输出端(OUT1-OUT4)走线长度不一致,可能导致信号同步性差;L298N 模块的散热焊盘未做铺铜处理,大功率下可能存在散热隐患。
V2 原理图
V2 PCB
3. 核心网关功能:串口模拟 CAN → 真实 CAN 总线转发
这是 V2 版本的核心价值所在 —— 通过硬件升级 + 软件适配,实现 “低成本串口模拟 CAN” 到 “车规级真实 CAN 总线” 的无缝衔接,适配车载多 ECU 通信场景。
二、 核心代码
2.1 V1固件代码
2.1.1 ▶️ 输入层(硬件接口)
1 )物理按键:
void key_process()//放在定时器扫描
{
static uint16_t scan_tick=0,up_count=0,down_count=0;
scan_tick++;
if(scan_tick>=20)
{
scan_tick=0;
key_loop();// 短按按键扫描
}
// 长/短按按键处理
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_0)==0)up_flag=1;
else up_flag=0;
if(HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_1)==0)down_flag=1;
else down_flag=0;
if(up_flag)
{
up_count++;
if(up_count>=LONG_KEY_TIME)
keytemp=MOTOR_LONG_UP;
}
else
{
if(up_count>0&&up_count<LONG_KEY_TIME)
keytemp=MOTOR_SHORT_UP;
up_count=0;
}
if(down_flag)
{
down_count++;
if(down_count>=LONG_KEY_TIME)
keytemp=MOTOR_LONG_DOWN;
}
else
{
if(down_count>0&&down_count<LONG_KEY_TIME)
keytemp=MOTOR_SHORT_DOWN;
down_count=0;
}
}
2 )蓝牙模块:
JDY33串口接收数据,通过UART1_process解析指令。
void UART1_process(MotorCmd_t *cmd,SensorData_t *sensor)
{
if(rec_flag1)
{
if(TIM2->CNT>15)//这里是一个超时判断
{
if(cmd->ctrl_mode==CTRL_MODE_BLUETOOTH)//处理蓝牙的数据
{
if(UART_Compare(UART1,BOOTH_MOTOR1_UP)&&sensor->infrared[0])//确认指令与安全状态
{
cmd->dir=DIR_UP;
cmd->is_stop=0;
cmd->target_motor_id=1;
osMessageQueuePut(xQueueControl, cmd, 0, osWaitForever);
}
if(UART_Compare(UART1,BOOTH_MOTOR1_DOWN))
{
cmd->dir=DIR_DOWN;
cmd->is_stop=0;
cmd->target_motor_id=1;
osMessageQueuePut(xQueueControl, cmd, 0, osWaitForever);
}
…………………………(其他指令同理)
}
rec_flag1=0;
UART_Clear(UART1,&Index1);//清除数据准备下一次接收
}
}
}
3 )语音模块
void UART2_process(MotorCmd_t*cmd,SensorData_t *sensor)
{
if(rec_flag2)
{
if(TIM3->CNT>20)
{
if(cmd->ctrl_mode==CTRL_MODE_VOICE)//处理语音数据
{
if(UART_Compare(UART2,VOICE_MOTOR1_UP)&&sensor->infrared[0])
{
cmd->dir=DIR_UP;
cmd->is_stop=0;
cmd->target_motor_id=1;
osMessageQueuePut(xQueueControl, cmd, 0, osWaitForever);
}
if(UART_Compare(UART2,VOICE_MOTOR1_DOWN))
{
cmd->dir=DIR_DOWN;
cmd->is_stop=0;
cmd->target_motor_id=1;
osMessageQueuePut(xQueueControl, cmd, 0, osWaitForever);
}
……(其他指令同理)
}
rec_flag2=0;
UART_Clear(UART2,&Index2);
}
}
}
语音用的是ASR-PR0语音识别模块(该模块通过特定的图形化编程配置)
V1语音配置

2.1.2 ▶️ 决策层(FreeRTOS核心)
| 任务名称 | 优先级 | 核心逻辑 | 代码位置 |
|---|---|---|---|
| StartDefaultTask | 中 | 系统处理外部指令,并将控制命令传入xQueueControl队列 | src/freertos.c |
| StartControlTask | 高 | 读取队列的控制信息将具体指令写入目标对象,最终在定时器中断调用Motor_Step_Handler进行PID调速与电机驱动 | src/freertos.c |
| StartSensorTask | 中 | 处理传感器数据,将信息传入xQueueSensor队列 | src/freertos.c |
| StartCANComTask | 低 | 将系统的所有状态转换成CAN数据包的形式通过串口发送 | src/freertos.c |
2.1.3 ▶️ 执行层(硬件驱动)
电机驱动:
// PID控制算法
void PID_Update(PID_Controller_t*PID)
{
//停止时初始化数据防止影响下一步判断
if(PID->target_speed<1.0f)
{
PID->step_interval_ms = 0;
PID->current_speed = 0.0f;
PID->error_sum = 0.0f;
PID->last_error = 0.0f;
return;
}
//计算当前速度 (步/秒) = 1000 / 步间隔(ms)
if(PID->step_interval_ms>0)
PID->current_speed=1000.0f/PID->step_interval_ms;
else
PID->current_speed=0.0f;
//计算误差
float error=PID->target_speed-PID->current_speed;
PID->error_sum+=error;
if(PID->error_sum>50.0f)PID->error_sum=50.0f;
if(PID->error_sum<-50.0f)PID->error_sum=-50.0f;
//这里就是套公式啦
float delta_error=error-PID->last_error;
float output=(PID->kp*error)+(PID->ki*PID->error_sum)+(PID->kd*delta_error);
float new_interval=PID->step_interval_ms-output*0.2f;
if(new_interval < 2.0f) new_interval = 2.0f; // 最快约500步/秒
if(new_interval > 100.0f) new_interval = 100.0f; // 最慢10步/秒
PID->step_interval_ms=(uint32_t)new_interval;
PID->last_error=error;
}
2.1.4 ▶️ 通信层(自定义CAN协议)
1 ) 协议设计背景
为适配车载场景的可靠通信需求,基于UART1串口设计“类CAN总线协议”,核心参考ISO 11898-1(CAN 2.0B)标准的优先级仲裁、CRC校验、重传机制,同时适配串口半双工特性做轻量化改造,解决传统UART通信无优先级、易丢包的问题。
2 )协议核心特性
| 特性 | 实现逻辑 | 行业对标(车载CAN) |
|---|---|---|
| 优先级仲裁 | 帧头高4位定义优先级(0-15,0最高),防夹/雨水事件自动提升帧优先级,发送前检测串口总线空闲 | ✅ CAN ID仲裁逻辑 |
| 数据完整性 | 基于CRC-8(多项式0x31)校验帧头+数据段,错误帧自动丢弃 | ✅ CAN CRC校验场 |
| 非阻塞重传 | FreeRTOS任务中通过时间戳实现超时重传(最多3次,可自行调整),无阻塞延时 | ✅ CAN ARQ重传机制 |
| 轻量化帧结构 | 10字节固定帧,500ms周期性发送 | ✅ CAN数据帧规范 |
3 )数据帧格式
每帧数据固定为 10字节,以原始字节流形式通过串口周期性(默认500ms)发送。
4 )字节映射表
| 字节序号 | 位域 (Bit 9 → Bit 0) | 详细说明 |
|---|---|---|
| Byte 0 | 优先级控制位 | 高 4 位:优先级(0-15,0 为最高优先级,通过防夹和雨水事件自动修改优先级确保主要信息的优先传输);低 4 位:保留(设 0) |
| Byte 1 | 控制模式 + 电机模式 + 选中电机 | 1. 控制模式:手动/蓝牙/语音 2. 电机模式:单电机控制模式,多电机控制模式 |
| Byte 2 | 雨水 + 红外1-4状态 | 最高位放雨水状态, 低4位放4个红外状态 |
| Byte 3 | 车窗1开合度 | 数值范围 0-100,代表车窗关闭的百分比 (0%全开,100%全关) |
| Byte 4 | 车窗2开合度 | 同上 |
| Byte 5 | 车窗3开合度 | 同上 |
| Byte 6 | 车窗4开合度 | 同上 |
| Byte 7 | 系统时间戳高字节 | 系统运行时间(毫秒)system_tick 的高8位 |
| Byte 8 | 系统时间戳低字节 | system_tick 的低8位 |
| Byte 9 | CRC-8 校验码 | 对 Byte 0-9 计算 CRC-8(多项式 0x31,初始值 0x00),接收方校验错误则丢弃 |
5 )核心代码
5.1 )CRC8/MAXIM 算法
static uint8_t crc8_calc(uint8_t *data, uint16_t len)
{
uint8_t crc = 0x00;
for (uint16_t i = 0; i < len; i++)
{
crc ^= data[i];
for (uint8_t j = 0; j < 8; j++)
crc = (crc & 0x80) ? ((crc << 1) ^ 0x31) : (crc << 1);
}
return crc;
}
5.2 )串口总线空闲检测
//串口的接收侧和发送侧都空闲返回1
static uint8_t uart_bus(void)
{
return (__HAL_UART_GET_FLAG(&huart1,UART_FLAG_TXE)!=RESET) && (__HAL_UART_GET_FLAG(&huart1,UART_FLAG_RXNE)==RESET);
}
5.3 )核心处理函数
void Uart_CommProcess(UART_CAN_SendHandle *handle)
{
uint8_t TX_Data[8]={0};
SystemState_t local_state;
if(osMutexAcquire(status_mutexHandle,200)!=osOK)
return;
local_state=g_system_state;
osMutexRelease(status_mutexHandle);
uint8_t priority=((CAN_SIM_PRIORITY)&0x0f)<<4;
TX_Data[0] = 0;
TX_Data[0]|=(local_state.last_cmd.ctrl_mode&0x03)<<6;// 控制模式放到第7、6位
TX_Data[0]|=(local_state.last_cmd.motor_mode&0x03)<<4;// 电机模式放5、4位
if (local_state.last_cmd.target_motor_id==0xff)//所有电机选中第3位置1
TX_Data[0]|=0x08;
else
TX_Data[0]|=(local_state.last_cmd.target_motor_id-1)&0x03;//选中电机放1、0位
TX_Data[1] = 0;
if(local_state.sensor.rain_detected)// 最高位放雨水状态, 低4位放4个红外状态
{
TX_Data[1]|=0x80;
priority=((CAN_SIM_PRIORITY-1)&0x0f)<<4;//高四位为优先级
}
for(uint8_t i=0;i<INFRARED_NUM;i++)
{
if(local_state.sensor.infrared[i]==0)
{
TX_Data[1]|=(0x01<<i);
priority=((CAN_SIM_PRIORITY-2)&0x0f)<<4;//红外防夹优先级最高
}
}
// 存放4个车窗的开度百分比 (0-100)
for(uint8_t i=0;i<4;i++)
{
uint8_t percentage=0;
if(g_motors[i].current_step>0)
percentage=(g_motors[i].current_step*100)/MAX_STEP;
if(percentage>100)
percentage=100;
TX_Data[2+i]=percentage;
}
// 放一个系统运行时间的低16位
static uint16_t tick_counter=0;
tick_counter++;
TX_Data[6]=(tick_counter>>8)&0xff;
TX_Data[7]=tick_counter&0xff;
if(!handle->is_pending)
{
handle->tx_buff[0]=priority;//帧头
for(uint8_t i=1;i<9;i++)//8字节数据帧
handle->tx_buff[i]=TX_Data[i-1];
handle->tx_buff[9]=crc8_calc(handle->tx_buff,9);//crc
handle->retry_count=0;
handle->next_send_ms=HAL_GetTick();//立即尝试发送
handle->is_pending=1;
}
if(!handle->is_pending)//当前没有信息等待,退出
return;
if(HAL_GetTick()<handle->next_send_ms)//没有达到达到的时间,退出
return;
if(!uart_bus())//总线忙
{
handle->next_send_ms=HAL_GetTick()+RETRY_INTERVAL_MS;//更新下一次发送时间,退出
return;
}
HAL_StatusTypeDef ret=HAL_UART_Transmit(&huart1,handle->tx_buff,10,50);
if(ret ==HAL_OK)
handle->is_pending=0;
else
{
handle->retry_count++;
if(handle->retry_count>=CAN_RETRY_COUNT)//超过最大重传次数,丢弃
handle->is_pending=0;
else
handle->next_send_ms=HAL_GetTick()+RETRY_INTERVAL_MS;//发送失败,进行重传
}
}
5.4 )效果验证

(这里我标蓝的就是一帧数据)
2.2 V2固件代码
2.2.1 函数定义
HAL_StatusTypeDef CAN_Send(uint8_t*data)
{
CAN_TxHeaderTypeDef tx_header;
uint32_t mailbox;
tx_header.StdId = 0x101; // 标准ID,与你V1板规划的ID一致
tx_header.ExtId = 0; // 扩展ID,标准帧下不用
tx_header.IDE = CAN_ID_STD; // 帧格式:标准帧
tx_header.RTR = CAN_RTR_DATA; // 帧类型:数据帧
tx_header.DLC = 10; // 数据长度:10字节(必须与数组长度一致)
tx_header.TransmitGlobalTime = DISABLE;
return HAL_CAN_AddTxMessage(&hcan, &tx_header, data, &mailbox);
}
2.2.2 主函数
while (1)
{
if(ready_flag)
{
ready_flag=0;
CAN_Send(uart_rx_buffer);
}
}
2.2.3 串口中断
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart==&huart1)
{
uart_rx_buffer[Index++]=rx_data;
if(Index>=8)
{
ready_flag=1;
Index=0;
}
HAL_UART_Receive_IT(&huart1,&rx_data,1);
}
}
三、调试过程:2个真实问题的深度解决
3.1 问题1:消息队列阻塞
现象:StartCANComTask任务时并没有数据从串口输出。
分析:由于要处理系统状态,需要一次性读取两个队列消息(xQueueControl和xQueueSensor)导致消息阻塞一直没读出来。
解决方案:我声明了一个全局变量g_system_state在StartControlTask和StartSensorTask读取两个队列的信息并通过互斥锁进行隔离。
//放在StartControlTask中
if(osMutexAcquire(status_mutexHandle, 100) == osOK) // 等待最多100ms
{
g_system_state.last_cmd = cmd;
osMutexRelease(status_mutexHandle); // 释放锁
}
//放在StartSensorTask中
if(osMutexAcquire(status_mutexHandle, 100) == osOK)
{
g_system_state.sensor = sensor; // 复制到全局状态
osMutexRelease(status_mutexHandle);
}
3.2 问题2:电机驱动不匹配(V2版本缺陷)
现象:L298N驱动28BYJ-48时,电机无反应。
具体分析放在:https://gitee.com/asd-ada/Smart-Car-Window-Controller/issues/IDKHRQ
四、工程化改进:从"能用"到"可靠"的3个关键点
4.1 代码规范
4.1.1 预处理 global_def.h
#ifndef GLOBAL_DEF_H
#define GLOBAL_DEF_H
#include "main.h"
#include "cmsis_os2.h"
// ===================== 系统常量 =====================
#define MOTOR_NUM 4 // 电机数量
#define INFRARED_NUM 4 // 红外传感器数量
#define MOTOR_SPEED 500.0f // 正常速度 (步/秒)
#define PID_UPDATE_MS 5 // PID更新周期 (ms)
#define LONG_KEY_TIME 500 // 长按时间阈值
#define MAX_STEP 5000 //车窗高度
#define STRING_LENGTH 40 //串口缓冲数组长度
#define BOOTH_MOTOR1_UP 'A' //蓝牙控制电机1上升
#define BOOTH_MOTOR1_DOWN 'B' //蓝牙控制电机1下降
#define BOOTH_MOTOR2_UP 'C' //蓝牙控制电机2上升
#define BOOTH_MOTOR2_DOWN 'D' //蓝牙控制电机2下降
#define BOOTH_MOTOR3_UP 'E' //蓝牙控制电机3上升
#define BOOTH_MOTOR3_DOWN 'F' //蓝牙控制电机3下降
#define BOOTH_MOTOR4_UP 'G' //蓝牙控制电机4上升
#define BOOTH_MOTOR4_DOWN 'H' //蓝牙控制电机4下降
#define BOOTH_ALL_UP 'I' //蓝牙控制所有电机上升
#define BOOTH_ALL_DOWN 'J' //蓝牙控制所有电机下降
#define VOICE_MOTOR1_UP 'a' //语音控制电机1上升
#define VOICE_MOTOR1_DOWN 'b' //语音控制电机1下降
#define VOICE_MOTOR2_UP 'c' //语音控制电机2上升
#define VOICE_MOTOR2_DOWN 'd' //语音控制电机2下降
#define VOICE_MOTOR3_UP 'e' //语音控制电机3上升
#define VOICE_MOTOR3_DOWN 'f' //语音控制电机3下降
#define VOICE_MOTOR4_UP 'g' //语音控制电机4上升
#define VOICE_MOTOR4_DOWN 'h' //语音控制电机4下降
#define VOICE_ALL_UP 'i' //语音控制所有电机上升
#define VOICE_ALL_DOWN 'j' //语音控制所有电机下降
#define CAN_RETRY_COUNT 3 //最大重传次数
#define RETRY_INTERVAL_MS 500 //重传间隔
#define CAN_SIM_PRIORITY 4 //本帧的优先级越小越高0-15
// ===================== 枚举类型 =====================
// 控制模式枚举
typedef enum {
CTRL_MODE_MANUAL = 0, // 手动模式
CTRL_MODE_BLUETOOTH, // 蓝牙模式
CTRL_MODE_VOICE, // 语音模式
CTRL_MODE_MAX
} CtrlMode_t;
// 电机工作模式枚举
typedef enum {
MOTOR_MODE_SINGLE = 0, // 单电机模式
MOTOR_MODE_ALL_SYNC // 多电机同步模式
} MotorMode_t;
// 运动方向枚举
typedef enum {
DIR_STOP = 0, // 停止
DIR_UP, // 上升
DIR_DOWN // 下降
} Direction_t;
typedef enum {
NO_PUSH=0, //无按键按下
MOTOR_SHORT_UP, // 上升短按
MOTOR_SHORT_DOWN, // 下降短按
MODE, // 模式切换键
SINGLE, // 单电机控制模式键
ALL_SYNC, // 多电机控制模式键
MOTOR_LONG_UP, // 上升长按
MOTOR_LONG_DOWN, // 下降长按
} Key_t;
// 故障类型枚举
typedef enum {
FAULT_NONE = 0, // 无故障
FAULT_IWDG_RESET = 1, // IWDG超时复位
FAULT_WWDG_RESET = 2, // WWDG超时复位
FAULT_WWDG_WINDOW = 3, // WWDG喂狗窗口错误
FAULT_SENSOR_ERR = 4, // 传感器数据异常
FAULT_MOTOR_DRIVE = 5, // 电机驱动无响应
FAULT_QUEUE_BLOCK = 6, // 消息队列阻塞
FAULT_CRC_CHECK = 7 // CAN帧CRC校验失败
} FaultType_t;
// ===================== 结构体定义 =====================
// PID参数结构体 (用于调速)
typedef struct {
float target_speed; // 期望速度 (步/秒)
float current_speed; // 当前速度 (步/秒)
float kp, ki, kd; // PID系数
float error_sum; // 误差积分
float last_error; // 上次误差
uint32_t step_interval_ms; // 计算出的步间间隔(毫秒)
} PID_Controller_t;
// 单个电机状态
typedef struct {
uint8_t id; // 电机ID: 1-4
Direction_t dir; // 当前方向
uint8_t is_moving; // 是否在动
int32_t current_step; // 当前绝对步数 (用于计算车窗位置%)
int32_t target_step; // 目标步数 (未来可做位置闭环)
uint8_t current_step_phase; // 当前步序 (0-3,对应你的函数case)
PID_Controller_t pid; // 这个电机的PID调速器
uint32_t last_step_tick; // 上次步进的时间戳(ms)
} Motor_t;
// 传感器数据结构体
typedef struct {
uint8_t infrared[INFRARED_NUM]; // 红外数字信号0触发
uint32_t rain_adc_now_value; // 雨水ADC现值
uint32_t rain_adc_last_value; // 雨水ADC旧值
uint8_t rain_detected; // 1有雨,0无雨
} SensorData_t;
// 控制命令结构体
typedef struct {
uint8_t ctrl_mode; // 控制模式
uint8_t motor_mode; // 电机模式
uint8_t target_motor_id; // 目标电机ID (0xFF=全部电机)
Direction_t dir; // 运动方向
uint8_t is_stop; // 是否为停止命令
} MotorCmd_t;
typedef struct {
SensorData_t sensor; // 传感器数据
MotorCmd_t last_cmd; // 最后收到的控制命令
} SystemState_t;
typedef struct {
uint8_t tx_buff[10];
uint8_t retry_count; //当前重传次数
uint32_t next_send_ms; //下次发送时间戳
uint8_t is_pending; //是否有待发送数据
}UART_CAN_SendHandle;
#endif /* GLOBAL_DEF_H */
4.1.2 模块化封装

4.2 安全机制
红外+雨滴双路检测
if(sensor.rain_detected)//雨水检测
{
cmd->dir=DIR_UP;
cmd->target_motor_id=0xff;
cmd->is_stop=0;
osMessageQueuePut(xQueueControl, cmd, 0, osWaitForever); // 发送命令
sprintf(Send2,"Rain Warning!\r\n");
HAL_UART_Transmit(&huart2,(uint8_t*)Send2,sizeof(Send2),50);
return;
}
for(uint8_t i=0;i<INFRARED_NUM;i++)//红外防夹
{
if(sensor.infrared[i]==0&&g_motors[i].is_moving)
{
cmd->is_stop=1;
cmd->target_motor_id=i+1;
osMessageQueuePut(xQueueControl, cmd, 0, osWaitForever); // 发送命令
sprintf(Send2,"Infrared %d Warning!\r\n",i+1);
HAL_UART_Transmit(&huart2,(uint8_t*)Send2,sizeof(Send2),50);
return;
}
}
4.3 交付物体系
| 文档类型 | 产出内容 | 价值 |
|---|---|---|
| 系统设计文档 | 项目介绍+开发日志与反思+通信协议详解 | 降低新成员上手成本 |
| 调试日志 | 2个问题的分析过程 | 问题复现率降低90% |
| 3D演示视频 | B站【开源】一台自制的智能车窗,究竟有多智能?3D动画告诉你 | 提升传播效率 |
五、上位机软件
为构建从嵌入式下位机到桌面监控端的完整闭环,本项目还配套开发了一款基于 PySide6 (Qt for Python) 的上位机软件。该软件提供了直观的图形化界面,实现了对车载控制器的深度监控与交互。
5.1 软件概览
- 核心定位:智能车窗控制系统的 PC端监控与调试助手。
- 主要功能:
- 双串口通信管理:同时连接车载控制器,实现数据监控与指令下发。
- 车窗状态可视化:实时显示四路车窗的开合百分比、传感器状态,并以自定义控件模拟玻璃升降效果。
- 数据透传与调试:支持自定义数据帧发送、接收,并显示详细的协议解析与报警日志。
- 技术栈:Python + PySide6 +
pyserial,采用多线程设计保障UI流畅。
5.2 效果演示
为了更直观地展示软硬件联调效果,我们录制了一段简短的上位机操作演示视频:
(视频内容涵盖:软件界面介绍、串口连接、实时数据监控、模拟控制指令发送等核心操作流程。)
5.3 获取与了解更多
- 源码位置:本上位机软件的完整源代码已集成至项目仓库的
/tools/目录下。 - 技术详解:关于该软件的详细设计思路、模块化构建过程、以及PySide6实战开发技巧,我们将在下一篇技术文章中进行深度解析,敬请关注。
感谢观看,欢迎参与我的问题讨论
项目链接
- 本项目由一名二本院校大二学生,在缺乏专业指导与硬件支持的环境下独立落地,该项目的原型早在大一已完成。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐

所有评论(0)