智能车窗控制系统:从架构设计到工程落地的深度解析

一、系统架构:为什么选择"多层解耦"而非"单片机堆砌"

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端监控与调试助手
  • 主要功能
    1. 双串口通信管理:同时连接车载控制器,实现数据监控指令下发
    2. 车窗状态可视化:实时显示四路车窗的开合百分比、传感器状态,并以自定义控件模拟玻璃升降效果。
    3. 数据透传与调试:支持自定义数据帧发送、接收,并显示详细的协议解析与报警日志。
  • 技术栈:Python + PySide6 + pyserial,采用多线程设计保障UI流畅。

5.2 效果演示

为了更直观地展示软硬件联调效果,我们录制了一段简短的上位机操作演示视频:

🔗 点击观看:智能车窗控制上位机软件功能演示

(视频内容涵盖:软件界面介绍、串口连接、实时数据监控、模拟控制指令发送等核心操作流程。)

5.3 获取与了解更多

  • 源码位置:本上位机软件的完整源代码已集成至项目仓库的 /tools/ 目录下。
  • 技术详解:关于该软件的详细设计思路、模块化构建过程、以及PySide6实战开发技巧,我们将在下一篇技术文章中进行深度解析,敬请关注。

感谢观看,欢迎参与我的问题讨论

项目链接


  • 本项目由一名二本院校大二学生,在缺乏专业指导与硬件支持的环境下独立落地,该项目的原型早在大一已完成。
Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐