M-Design设计竞赛 - 基于STM32设计蓝牙便携式车载OBD诊断仪
该项目使用了STM32F103,实现了蓝牙便携式车载OBD诊断仪的设计,它的主要功能为:非常方便的向车辆诊断口发送指定的命令,同时还可以读取诊断口的数据流传输到手机进行显示。。
标签
嵌入式系统
M-Design设计竞赛
贸泽电子
2025 M-Design设计竞赛
Dearjim
更新2025-04-01
55


2025贸泽电子M-Design创意设计竞赛,方向三:无线通信、物联网——蓝牙。

项目介绍

设计一款便捷的汽车 CAN 信号交互设备,基于 STM32F103 主控芯片,结合纳芯微 CAN 收发器和蓝牙模块,实现汽车 OBD 口与手机小程序之间的双向通信。通过蓝牙模块与手机小程序的数据交互,用户可实时查看汽车状态并发送指令。该系统通过高效的电源管理和精确的信号处理,为汽车维修与诊断提供便捷的解决方案。项目涵盖硬件设计、软件开发及蓝牙通信、3D建模及打印等多个领域。

项目结构

  1. 硬件开发-原理图、PCB设计
  2. 软件开发-STM32代码开发
  3. 小程序开发-蓝牙BLE信号收发小程序
  4. 外壳制作-3D建模及打印

系统原理图

image.png

设计思路

电源管理:汽车 OBD 口提供的 12V 电源通过 12V 转 5V 芯片和 5V 转 3.3V 芯片,为 STM32F103 和其他模块提供稳定的工作电压,确保设备正常运行。

信号接收与处理:CAN 收发器负责接收汽车 OBD 口的 CAN 信号,并将其传输给 STM32F103。主控芯片对信号进行解析和处理,然后通过 UART 接口将数据发送给蓝牙模块。

无线通信:蓝牙模块将接收到的数据以无线方式传输给手机蓝牙,手机蓝牙再将数据传递给小程序进行显示,方便用户实时了解汽车状态。

信号交互流程:用户在手机小程序上输入指令,指令通过手机蓝牙发送给设备的蓝牙模块,再由蓝牙模块传输给 STM32F103。主控芯片将指令处理后通过 CAN 收发器发送给车载CAN总;车载CAN总线上的CAN报文,也可以通过CAN收发器捕捉后传递给STM32F103,然后通过串口发送到蓝牙模块。蓝牙模块再将信号发送给小程序进行显示。

硬件开发

原理图

原理图截图.png

除了主控芯片外,最重要的就是蓝牙透传模块和CAN收发器。蓝牙模块选用的是大夏龙雀DX-BT37蓝牙模块,具有超低功耗的特点。CAN收发器选用的是纳芯微的1044CAN芯片,是一款车规级的低功耗CAN芯片。另外,电路中的CP2012是DEBUG过程中使用的,完工之后作用就不大了。

PCB

PCB截图.png

这里特别鸣谢我们团队的硬件工程师戴老板,他layout水平在我看来是非常高的,板子做出来让人感觉是一个艺术品。

实物展示

这里的焊接全是戴老板手工完成,十年老硬件工程师的功力还是值得信赖的。

软件开发

代码流程图

image.png

流程图说明:

  1. 初始化 STM32F103 主控
    • 配置 IO 引脚(用于控制各类外设)。
    • 配置 UART(用于串口通讯)。
    • 配置 CAN 收发器(用于 CAN 总线通讯)。
  2. CAN 收发器操作
    •  CAN 收到报文 后进入 CAN 接收中断,并 读取 CAN 报文
    • 读取的 CAN 报文会通过 UART 发送到蓝牙模块,然后由蓝牙模块透传给手机 APP。
  3. UART 中断操作
    • 当 UART 接收到来自蓝牙芯片的报文后,首先会 解析报文格式
    • 如果 报文格式符合 CAN 发送格式,则将该报文通过 CAN 发送出去;如果格式不符合,则 丢弃报文或给出错误提示。

驱动相关代码

image.png

可以使用STM32CubeMX进行关键引脚的配置;这里不展开介绍在2025寒假练的项目中有多优秀的项目/教程大家可以去参考。

image.png

然后点击Generate COde生成基础代码,然后再修改部分用户代码。


关键函数:

  • UART  CAN 的配置与中断处理确保了STM32F103主控可以实现双向数据通信。
  • CAN 中断UART 中断的实时数据处理确保了蓝牙与CAN设备的高效通讯。

主函数

int main(void)
{
LED_Config(); // 配置LED灯
PC_USART_Config(230400);
TIM3_Config(); // LED控制

TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
ISO15765_Config(CAN_ID_STD, CAN_500K);
Task_Manager_Init();
// 调用任务管理器运行函数,启动任务调度
Task_Manager_Run();
// 返回0表示程序正常退出
return 0;
}

CAN信号接收中断函数

//CAN信号接收中断
void USB_LP_CAN1_RX0_IRQHandler(void)
{

CanRxMsg CANMessaage;

CanTxMsg CmdDeviceContrl101 = {0x101, 0x18DB33F1, CAN_ID_STD, CAN_RTR_DATA, 3, 0xFE, 0x01, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00};

CAN_Receive(CAN1, CAN_FIFO0, &CANMessaage);

gCAN_u8MessageReveived = 1;

if((CANMessaage.StdId >> 8) & 0x07 == 1)
{
printf("time=%lu msg= %4x %2x %2x %2x %2x %2x %2x %2x %2x\n ", gSMT_u32localtimems,CANMessaage.StdId,
CANMessaage.Data[0],
CANMessaage.Data[1],
CANMessaage.Data[2],
CANMessaage.Data[3],
CANMessaage.Data[4],
CANMessaage.Data[5],
CANMessaage.Data[6],
CANMessaage.Data[7]);
}
else if(TESTMODE == 1)
{
printf("time=%lu msg= %4x %2x %2x %2x %2x %2x %2x %2x %2x\n ", gSMT_u32localtimems,CANMessaage.StdId,
CANMessaage.Data[0],
CANMessaage.Data[1],
CANMessaage.Data[2],
CANMessaage.Data[3],
CANMessaage.Data[4],
CANMessaage.Data[5],
CANMessaage.Data[6],
CANMessaage.Data[7]);
}

}

UART信号接收中断函数

void PC_USART_IRQHandler(void)
{

u8 data;
u8 index, i;
ErrorStatus err;
data = data;
if (USART_GetFlagStatus(PC_USART, USART_FLAG_ORE) != RESET)
{
data = PC_USART->DR;
printf("USART Overrun Error occurred.\n"); // WJ ADD
}
if (USART_GetFlagStatus(PC_USART, USART_IT_RXNE) != RESET)
{
ATCmd[ATLEN++] = PC_USART->DR;
USART_ITConfig(PC_USART, USART_IT_IDLE, ENABLE); // WJ 注释:使能 IDLE 中断(空闲线检测中断),这样当接收缓冲器为空时,会触发一次中断,表示接收的数据块已经结束。
}
if (USART_GetFlagStatus(PC_USART, USART_FLAG_IDLE) != RESET)
{

data = PC_USART->DR;
USART_ITConfig(PC_USART, USART_IT_IDLE, DISABLE); // 关闭IDLE中断
if (!strncmp((const char *)ATCmd, "AT+HWVERSION", 12))
{
// ISO15765_Config(CAN_ID_STD, CAN_500K);
// TESTMODE = 0;
printf((const char *)HWVersion);
ClearRAM((u8 *)ATCmd, 100);
}
//收到报文后通过CAN发送出去
else if (hexncmp((const char *)ATCmd, MSGCMD1, 11)){

SENDMESG1FLAG =1;
SENDMESG1.StdId = ATCmd[0]<<8 | ATCmd[1];
SENDMESG1.DLC = 8;
SENDMESG1.Data[0] = ATCmd[3];
SENDMESG1.Data[1] = ATCmd[4];
SENDMESG1.Data[2] = ATCmd[5];
SENDMESG1.Data[3] = ATCmd[6];
SENDMESG1.Data[4] = ATCmd[7];
SENDMESG1.Data[5] = ATCmd[8];
SENDMESG1.Data[6] = ATCmd[9];
SENDMESG1.Data[7] = ATCmd[10];
ClearRAM((u8 *)ATCmd, 100);
ATLEN = 0;
}
else if (!strncmp((const char *)ATCmd, "AT+READALL", 10)){
TESTMODE = 1;
ClearRAM((u8 *)ATCmd, 100);
ATLEN = 0;
}
else if (!strncmp((const char *)ATCmd, "AT+CLOSEALL", 11)){
TESTMODE = 1;
ClearRAM((u8 *)ATCmd, 100);
ATLEN = 0;
}
else
{
if (ATLEN >= 20)
{
ClearRAM((u8 *)ATCmd, 100);
ATLEN = 0;
}
}
}
}


CAN报文发送函数

void SEND_CAN_MESSAGE(CanTxMsg *TxMessage, u8 CANStype, ErrorStatus *err)
{
u8 TransmitMailbox;
u32 lCAN_u32StartSendTime;
RxFlay = SUCCESS;
TxMessage->IDE = CANStype;
TransmitMailbox = CAN_Transmit(CAN1, TxMessage);
lCAN_u32StartSendTime = gSMT_u32localtimems;
while (CAN_TransmitStatus(CAN1, TransmitMailbox) != CANTXOK)
{
if (gSMT_u32localtimems > lCAN_u32StartSendTime + 10)
{
RxFlay = ERROR;
break;
}
__WFI(); // 等待中断,进入低功耗模式
}
*err = RxFlay;

if(RxFlay == SUCCESS)
{
printfTxMessage(TxMessage);
}
else
{
printf("SENT FAIL");
}

}

小程序开发

进入小程序官网:https://mp.weixin.qq.com/?token=&lang=zh_CN

注册账号,并下载 “微信开发者工具”,创建自己的小程序并进行开发,官方提供了很多函数接口可以直接调用。

image.png

小程序的代码核心是以下两个页面。

image.png

在Page文件夹下创建所需的页面。每个页面开发时,主要有四种文件类型,每种文件都有其特定的作用。这些文件类型分别是:jsjsonwxmlwxss

1. js 文件 (JavaScript)

  • 用途:用于处理小程序的逻辑功能,如页面的交互、数据处理、事件响应等。
  • 主要功能
    • 包含页面的事件处理函数,例如点击按钮、获取数据、与后端接口交互等。
    • 通过 js 文件可以控制页面的显示、数据绑定、视图更新等操作。
    • 例如,可以通过 wx.request 发送请求,获取接口数据,并更新页面内容。

2. json 文件 (配置文件)

  • 用途:用于配置小程序的全局或页面级别的设置。
  • 主要功能
    • 配置页面路径、窗口设置、页面标题、导航栏、背景色等。
    • app.json 配置文件用于配置整个小程序的页面路由、窗口样式、底部导航等。
    • 每个页面的 page.json 可以配置该页面的特定属性,比如是否启用下拉刷新、是否开启分享等。
    • 通过 json 文件的配置,可以控制小程序的外观、功能等特性。

3. wxml 文件 (WeiXin Markup Language)

  • 用途:用于定义页面的结构和布局,类似于 HTML 文件。
  • 主要特点
    • wxml 中的语法结构与 HTML 相似,但有一些差异。例如,使用 {{}} 来进行数据绑定,将 JavaScript 数据渲染到页面上。
    • 支持小程序的原生组件,如 <view>, <text>, <image>, <button> 等,它们对应了小程序的基本界面元素。
    • 支持条件渲染(wx:ifwx:for)和事件绑定(如 bindtap 等)。
    • wxml 文件决定了页面的外观和组件布局,描述了页面中的元素、标签、容器等。

4. wxss 文件 (WeiXin Style Sheets)

  • 用途:用于设置页面的样式,类似于 CSS 文件。
  • 主要特点
    • wxss 基本语法和 CSS 非常相似,但有一些特定的规则和差异:
      • 支持单位:rpx(响应式像素)是小程序特有的单位,适用于不同屏幕尺寸的自适应布局。rpx 单位的设计使得页面在各种设备上能保持良好的显示效果。
      • 样式的作用范围:wxss 是作用于小程序的页面级别的样式定义,但如果想要全局共享样式,通常需要在 app.wxss 中进行配置。
    • 可以使用额外的功能,比如条件样式、透明度、背景图片等,控制页面元素的视觉效果,如颜色、字体、边距、布局等。

小程序核心代码

getBLEDeviceCharacteristics(deviceId, serviceId) {
   const that = this
    wx.getBLEDeviceCharacteristics({
      deviceId,
      serviceId,
      success: (res) => {
        var ismy_service = false
        console.log("compute ", serviceId, this.serviceu)
        if (serviceId == this.serviceu) {
          ismy_service = true
          console.warn("this is my service ")
        }
        console.log('getBLEDeviceCharacteristics success', res.characteristics)
        for (let i = 0; i < res.characteristics.length; i++) {
          let item = res.characteristics[i]
          if (ismy_service){
            console.log("-----------------------")
          }
          console.log("this properties = ", item.properties)
          if (item.properties.read) {
            console.log("[Read]", item.uuid)
            wx.readBLECharacteristicValue({
              deviceId,
              serviceId,
              characteristicId: item.uuid,
            })
          }
          if (item.properties.write) {
            this.setData({
              canWrite: true
            })
            console.log("[Write]",item.uuid)
            this._deviceId = deviceId
            if (ismy_service && (this.txdu == item.uuid)){
              console.warn("find write uuid  ready to ", item.uuid)
              this._characteristicId = item.uuid
              this._serviceId = serviceId
             // this.showModalTips(this.txdu+ "\r找到发送特征值")
            }
            //this.writeBLECharacteristicValue()
          }
          if (item.properties.notify || item.properties.indicate) {
            console.log("[Notify]", item.uuid)
            if (ismy_service && (this.rxdu == item.uuid)){
              console.warn("find notity uuid try enablec....", item.uuid)
             // this.showModalTips(this.rxdu + "\r正在开启通知...")
              wx.notifyBLECharacteristicValueChange({  //开启通知
                deviceId,
                serviceId,
                characteristicId: item.uuid,
                state: true, 
                success(res) {
                  console.warn('notifyBLECharacteristicValueChange success', res.errMsg)
                  that.setData({
                    connectState: "连接成功"
                  })
                 // that.showModalTips(that.rxdu + "\r开启通知成功")
                  that.data.readyRec=true
                }
              })
            }
          }
        }
      },
      fail(res) {
        console.error('getBLEDeviceCharacteristics', res)
      }
    })
    // 操作之前先监听,保证第一时间获取数据
    wx.onBLECharacteristicValueChange((characteristic) => {
      var buf = new Uint8Array(characteristic.value)
      var nowrecHEX = ab2hex(characteristic.value)
      console.warn("rec: ", nowrecHEX, characteristic.characteristicId)
      var recStr = ab2Str(characteristic.value)
      console.warn("recstr: ", recStr, characteristic.characteristicId)
      if (this.rxdu != characteristic.characteristicId){
        console.error("no same : ", this.rxdu, characteristic.characteristicId)
        return
      }
      if (!this.data.readyRec)return
      var mrecstr
      if (this.data.hexRec){
        mrecstr = nowrecHEX
      }else{
        mrecstr = recStr
      }


      if (this.data.recdata.length>3000){
        this.data.recdata = this.data.recdata.substring(mrecstr.length, this.data.recdata.length)
      }
      console.warn("RXlen: ", buf.length)
      this.setData({
        recdata: this.data.recdata + mrecstr,
        rxCount: this.data.rxCount + buf.length,
        timRX: this.data.timRX+buf.length
      })
    })
  },


外壳制作

3D建模

这里使用CATIA进行3D建模

image.png

image.png

完成后点击另存为将图片保存为stl格式格式,后续3D打印软件会使用到。

3D打印

使用拓竹的 LAB A1mini3D打印机,软件是官方的 Bambu Studio。Bambu Studio是一款开源、尖端、功能丰富的切片软件。

3D打印.png3D打印2.png

3D打印3.png

image.png

打印过程可以导出,非常的方便。

组装图片3.jpg

组装图片2.jpg

组装图片.jpg

设计中遇到的难题和解决方法

1. 多次打印板子测试问题

  • 问题:第一次打印板子简易板卡,测试 CAN 收发器,蓝牙时,焊接STM32的时候好几个引脚都短路了,调试了很久。
  • 解决方法:回流焊的時候要控制好锡膏。

2. 正式板子 OBD 口孔大小错误及走线问题

  • 问题:第一次打印的正式板子 OBD 口孔大小打错,钻孔时弄断走线。
  • 解决方法:在设计文件输出前,安排专人进行审核,对关键尺寸进行再次确认。对于已经出现的走线问题,采用飞线的方式进行临时修复,同时对设计文件进行修改,避免在后续打印中再次出现类似问题。

3. 3D 打印外壳尺寸误差问题

  • 问题:3D 打印外壳时,由于缺乏精密测试工具,打印尺寸差距较大。
  • 解决方法:购买或借用精密的测量工具,如卡尺、千分尺等,在打印前对设计尺寸进行精确测量和校准。同时,根据第一次打印的结果,对设计文件进行调整,然后进行第二次打印,以提高外壳尺寸的准确性。

4. 小程序蓝牙连接调试问题

  • 问题:小程序开发时蓝牙连接调试时间长。
  • 解决方法:深入学习蓝牙通信协议和小程序开发框架,查阅相关文档和资料,了解常见的蓝牙连接问题及解决方法。同时,使用调试工具对蓝牙通信过程进行监控和分析,逐步排查问题,最终实现稳定的蓝牙连接。

对本次竞赛的心得体会

在本次任务里,我收获满满,积累了大量宝贵经验,掌握了不少实用技能。起初,我从零基础起步,学习绘制原理图,接着精心设计 PCB,切实将硬件功能予以实现。这一全过程,加深了我对电路设计原理、布局规划以及元件选型的认知,让我理解得更为透彻。到了焊接和调试环节,我的手工操作精准度大幅提升,还积累了丰富的电路故障处理经验,学会运用合理手段排查问题,快速完成修复。

在这个过程中,我对蓝牙模块的认识也达到了新高度,成功搭建起蓝牙模块与主控板之间的数据传输桥梁。在实际操作中,我掌握了蓝牙通信协议,熟练运用数据处理方法,为后续投身项目开发筑牢根基。

值得一提的是,这次我首次运用 3D 打印机制作产品外壳。从设计到打印,我深度钻研 3D 建模技巧,熟悉 3D 打印技术的实际应用,顺利将脑海中的设计理念转化为实实在在的产品,有力保障了项目的完整性。

另外,我初次涉足微信小程序开发领域,不仅学会开发流程,还实现了蓝牙设备与小程序的联动。经此一举,产品互动性增强,用户体验显著提升。借助小程序,用户能便捷地与设备通信,轻松完成设备控制、数据查看等操作。

不过,复盘整个项目,我也察觉到存在一些短板。就拿目前的小程序来说,功能相对简单,仅能完成基础操作,缺少一些实用的扩展功能。往后规划加入故障读取、故障清除、实时物流数据查询等常用功能,既能提高工程师使用时的工作效率,又能优化用户体验,让操作流程更流畅,产品使用更具智能化。


团队介绍
汽车诊断工程师兼电子爱好者,本次项目团队一共两人,本人和硬件开发工程师。
团队成员
硬件大师
layout & 焊板 手工活一流
Dearjim
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号