CAN通信案例

在这里插入图片描述

1 基本介绍

CAN(Controller Area Network 控制器局域网,简称CAN或者CAN bus)是一种功能丰富的车用总线标准。被设计用于在不需要主机(Host)的情况下,允许网络上的单片机和仪器相互通信。

参考:https://www.kvaser.cn/about-can/can-protocol-tutorial/

1.1 物理层

在这里插入图片描述

  • CAN网络节点发数据:当CAN节点需要发送数据时,控制器把要发送的二进制编码通过CAN Tx线发送到收发器,收发器把这个普通的逻辑电平信号转化成差分信号,通过差分线CANHigh和CA ow线输出到CAN总线网络

  • CAN网络节点收数据:而通过收发器接收总线上的数据到控制器时,则是相反的过程收发器把总线上收到的CANHigh及CAN ow信号转化成普通的逻辑电平信号通过CAN Rx输出到控制器中。

  • 开环闭环网络接线

在这里插入图片描述

在这里插入图片描述

  • 差分信号:
    CAN_High及CAN_Low 中走的是一对差分信号传统的单端信号传输:一根信号线一根地线差分传输是一种信号传输的技术,差分传输在这两根线上都传输信号,这两个信号的振幅相同,相位相反信号接收端比较这两个电压的差值来判断发送端发送的逻辑状态,在电路板上,差分走线必须是等长、等宽、紧密靠近、且在同一层面的两根线。

  • 差分信号优缺点

    • 优点
      1.抗干扰能力强。
      干扰噪声一般会等值、同时的被加载到两根信号线响。而其差值为0,即,噪声对信号的逻辑意义不产生
      Sender
      Receiver
      2.能有效抑制电磁干扰(EMI)
      由于两根线靠得很近且信号幅值相等,这两根线与地线之间的耦合电磁场的幅值也相等,同时他们的信号极性相反,其电磁场将相互抵消。因此对外界的电碗干扰也小。
    • 缺点
      差分信号一定要走两根等长、等宽、紧密靠近、且在同一层面的线。对电路板比较小,走线比较紧张的情况下,给布线带来挑战。

在这里插入图片描述

1.2 CAN的帧(报文)种类

主要介绍两种:

1)数据帧

数据帧是最常见的报文类型,用于****发送单元向接收单元发送数据****。

2)远程帧(遥控帧)

远程帧用于接收单元向具有相同id的发送单元请求发送数据。

在这里插入图片描述

1.3 CAN总线仲裁

CAN总线处于空闲状态的时候,最先发送消息的单元获得发送权。

多个单元同时开始发送时,从仲裁段(报文id)的第一位开始进行仲裁。连续输出显性电平(0)最多的单元可以继续发送,即首先出现隐形电平的单元失去总线的占有权变为接收。(即报文id小的优先级高)。

竞争失败,会自动检测总线空闲,在第一时间再次尝试发送。

1.4 位时序

在这里插入图片描述

  • 同步段:SS 段的大小固定为 1Tq
  • 传播时间段:PTS一般1-9个Tq
  • 相位缓冲段1:PBS1一般1~8Tq
  • 相位缓冲段2:PBS2一般2~8Tq

在这里插入图片描述

在这里插入图片描述

2 STM32 CAN外设

STM32的芯片中具有bxCAN控制器(Basic Extended CAN),它支持CAN协议2.0A 和2.0B Active标准。

该CAN控制器支持最高的通讯速率为1Mb/s

外设中具有3个发送邮箱,具有2个3级深度的接收FIFO

2.1 CAN控制器的3种工作模式

在这里插入图片描述

上电复位后CAN控制器默认会进入睡眠模式,作用是降低功耗。当需要将进行初始化时(配置寄存器),会进入初始化模式。当需要通讯的时候,就进入正常模式。

2.2 CAN控制器的3种测试模式

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.3 功能框图

在这里插入图片描述

主动内核

含各种控制/状态/配置寄存器,可以配置模式、波特率等。

发送邮箱

用来缓存待发送的报文,最多可以缓存3个报文。发送调度决定报文的发送顺序。

接收FIFO

共有2个接收FIFO,每个FIFO都可以存放3个完整的报文。应用程序只能通过读取FIFO输入邮箱,来读取FIFO中最先收到的报文。

接收滤波器(过滤器)

有2种过滤模式:

  • 标识符列表模式,它把要接收报文的ID列成一个表,要求报文ID与列表中的某一个标识符完全相同才可以接收,可以理解为白名单管理。

  • 掩码模式(屏蔽位模式),它把可接收报文ID的某几位作为列表,这几位被称为掩码,可以把它理解成关键字搜索,只要掩码(关键字)相同,就符合要求,报文就会被保存到接收FIFO。

  • 每个CAN提供了14个位宽可变的、可配置的过滤器组(13~0)。每个过滤器组x由2个32位寄存器,CAN_FxR1和 CAN_FxR2组成。

在这里插入图片描述

(1)当工作于32位屏蔽位模式时,FR1保存标识符,FR2保存屏蔽。FR2某位是1表示来的ID的这位必须和FR1中对应的位一致,FR2某位是0,表示ID的这位不关心。

(2)当工作于32位标识符模式时。FR1和FR2分别保存两个标识符。这意味着将来只有两个ID会匹配成功。

STM32的位时序

把传播时间段和相位缓冲段1做了合并。

在这里插入图片描述

2 案例

2.1 案例1:环回静默模式测试

需求描述:我们使用环回静默模式测试CAN能否正常工作。把接收到的报文数据发送到串口输出,看是否可以正常工作。

项目开发

CAN的寄存器介绍

1. CAN的控制状态寄存器介绍

MCR主控寄存器: 自动离线管理 自动唤醒功能 睡眠模式请求 初始化请求

MSR主状态寄存器: 中断标志位 睡眠模式确认位 初始化模式确认位

TSR发送状态寄存器: 发送邮箱是否为空

RF0R队列0接收状态寄存器: 接收报文满或溢出 报文数量 释放当前报文

IER中断使能寄存器: 开启中断

ESR错误寄存器: 错误标志位

BTR位时序寄存器: 打开环回静默模式 ts2 ts1 sjw重新同步跳跃宽度上限 波特率预分频

2. 过滤器组的寄存器介绍

FMR过滤器主控寄存器: 进入过滤器初始化模式

FM1R过滤器模式寄存器: 选择对应编号的过滤器是列表模式还是掩码模式

FS1R过滤器位寄存器: 选择使用16位还是32位

FFA1R关联队列寄存器: 选择关联FIFO0或1

FA1R过滤器激活寄存器: 开启过滤器

FiRX过滤器组中的两个32位寄存器 : 屏蔽位模式表示一个id一个掩码 列表模式的两个id

3. 邮箱寄存器介绍

TIxR 发送邮箱标识符寄存器: IDE RTR STDID 发送请求

TDTxR 数据长度和时间戳寄存器: 时间戳 (用不到) 数据长度

TDLxR和TDHxR存数据的两个32位寄存器 : 一个能存4字节

RIxR 接收邮箱标识符寄存器: IDE RTR STDID 发送请求

RDTxR 数据长度和时间戳寄存器: 时间戳 (用不到) 数据长度

RDLxR和RDHxR存数据的两个32位寄存器 : 一个能存4字节

CAN的初始化

(1) 打开PB CAN AFIIO时钟

(2) 重定向到PB8 PB9

(3) 配置引脚模式

(4) 退出睡眠模式 -> 进入初始化模式

(5) CAN主控配置 -> 自动离线管理 自动唤醒

(6) 配置环回静默模式

(7) 配置位时序 TS1 TS2 SJW 波特率预分频系数

(8) 退出初始化模式

(9) 配置过滤器 进入过滤器初始化模式

(10) 选择使用的过滤器类型 掩码模式 32位

(11) 关联接收消息的队列 FIFO0

(12) 填写对应的ID和MASK

(13) 使能过滤器0

(14) 退出过滤器的初始化模式

CAN的发送消息

(1) 判断数据长度是否大于8
(2) 等待邮箱0空闲

(3) 将数据赋值到对应的发送寄存器中

(4) 发送请求填写

CAN的接收消息

(1) 获取接收报文的条数

(2) 将接收到的消息赋值到结构体数组中

(3) 释放当前的邮箱

代码编写
register 版本
  • Driver_can.c
#include "Driver_can.h"

void Driver_CAN_Init(void)
{
    /* 1. 打开时钟 CAN PB AFIO */
    // 1.1 开启时钟
    // 72MHz
    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
    RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;
    // 36MHz
    RCC->APB1ENR |= RCC_APB1ENR_CAN1EN;

    // 1.2 重定向引脚到PB8 PB9
    // CAN_REMAP   10
    AFIO->MAPR |= AFIO_MAPR_CAN_REMAP_1;
    AFIO->MAPR &= ~AFIO_MAPR_CAN_REMAP_0;

    /* 2. 配置引脚模式 */
    // PB8 RX 浮空输入 0100  PB9 TX 复用推挽输出 1011
    GPIOB->CRH &= ~(GPIO_CRH_CNF8_1 | GPIO_CRH_MODE8);
    GPIOB->CRH |= GPIO_CRH_CNF8_0;

    GPIOB->CRH |= (GPIO_CRH_CNF9_1 | GPIO_CRH_MODE9);
    GPIOB->CRH &= ~GPIO_CRH_CNF9_0;

    /* 3. 配置CAN控制器 */
    // 3.1 初始是睡眠模式 -> 先退出睡眠模式
    CAN1->MCR &= ~CAN_MCR_SLEEP;
    while ((CAN1->MSR & CAN_MSR_SLAK) != 0)
        ;

    // 3.2 进入初始化模式
    CAN1->MCR |= CAN_MCR_INRQ;
    while ((CAN1->MSR & CAN_MSR_INAK) == 0)
        ;

    // 3.3 开启自动唤醒功能和自动离线管理
    CAN1->MCR |= CAN_MCR_AWUM;
    CAN1->MCR |= CAN_MCR_ABOM;

    // 3.4 打开环回静默模式
    CAN1->BTR |= CAN_BTR_SILM;
    CAN1->BTR |= CAN_BTR_LBKM;

    // 3.5 设置波特率预分频
    CAN1->BTR &= ~CAN_BTR_BRP;
    // 对应的频率1MHz  周期 1us
    CAN1->BTR |= 35 << 0;

    // 3.6 设置TS1 TS2
    CAN1->BTR &= ~CAN_BTR_TS1;
    CAN1->BTR &= ~CAN_BTR_TS2;
    // SYNC=1TQ , TS1 = 6TQ , TS2 = 3TQ 总计10tq = 10us
    // 最终的波特率 = 1s / 10us = 100Kbps
    CAN1->BTR |= 5 << 16;
    CAN1->BTR |= 2 << 20;

    // 3.7 重新同步的跳跃宽度
    CAN1->BTR &= ~CAN_BTR_SJW;
    CAN1->BTR |= 1 << 24;

    // 3.8 退出初始化模式
    CAN1->MCR &= ~CAN_MCR_INRQ;
    while ((CAN1->MSR & CAN_MSR_INAK) != 0)
        ;

    /* 4. 配置过滤器 */
    // 4.1 进入过滤器初始化模式
    CAN1->FMR |= CAN_FMR_FINIT;

    // 4.2 只使用过滤器0
    // 掩码模式0   32位1
    CAN1->FM1R &= ~CAN_FM1R_FBM0;
    CAN1->FS1R |= CAN_FS1R_FSC0;

    // 4.3 关联对应的FIFO0
    CAN1->FFA1R &= ~CAN_FFA1R_FFA0;

    // 4.4 填写屏蔽模式下的ID和mask
    // 11位stdId  18位EXID IDE RTR 0   -> STDID 低3位是110
    CAN1->sFilterRegister[0].FR1 = 0x00C00000;
    // 掩码  -> 哪些ID需要接收   -> 掩码配置为STDID的低3位
    CAN1->sFilterRegister[0].FR2 = 0x00E00000;

    // 4.5 开启过滤器
    CAN1->FA1R |= CAN_FA1R_FACT0;

    // 4. 退出初始化模式
    CAN1->FMR &= ~CAN_FMR_FINIT;
}

void Driver_CAN_SendMsg(uint16_t stdId, uint8_t data[], uint8_t length)
{

    /* 0. 健壮性判断  长度不能大于8 */
    if (length > 8)
    {
        printf("数据长度过长\n");
        return;
    }

    /* 1. 判断发送的邮箱为空 */
    // 有三个邮箱的 -> 只使用邮箱0
    while ((CAN1->TSR & CAN_TSR_TME0) == 0)
        ;

    /* 2. 将数据放入到对应的邮箱 */
    // 2.1 写标准格式   数据帧
    CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_IDE;
    CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_RTR;

    // 2.2 写入STDID
    CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_STID;
    CAN1->sTxMailBox[0].TIR |= stdId << 21;

    // 2.3 写入DLE数据长度
    CAN1->sTxMailBox[0].TDTR &= ~CAN_TDT0R_DLC;
    CAN1->sTxMailBox[0].TDTR |= length << 0;

    // 2.4 写入数据
    CAN1->sTxMailBox[0].TDLR = 0;
    CAN1->sTxMailBox[0].TDHR = 0;
    for (uint8_t i = 0; i < length; i++)
    {
        // 判断是高4位还是低4位
        if (i < 4)
        {
            CAN1->sTxMailBox[0].TDLR |= (data[i] << (8 * i));
        }
        else
        {
            CAN1->sTxMailBox[0].TDHR |= (data[i] << (8 * (i - 4)));
        }
    }

    /* 3. 提交一个发送请求 */
    CAN1->sTxMailBox[0].TIR |= CAN_TI0R_TXRQ;
}

void Driver_CAN_ReceiveMsg(RxDataStruct rxDataStruct[], uint8_t *msgCount)
{
    /* 1. 读取FIFO0中的报文数量 */
    *msgCount = CAN1->RF0R & CAN_RF0R_FMP0;

    /* 2. 循环读取报文信息  将内容存放到结构体数组中 */
    for (uint8_t i = 0; i < *msgCount; i++)
    {
        // 2.1 将收件箱中的信息存放到结构体中
        RxDataStruct *msg = &rxDataStruct[i];

        // 2.2 填写结构体的stdid
        msg->stdId = (CAN1->sFIFOMailBox[0].RIR & CAN_RI0R_STID) >> 21;

        // 2.3 填写数据的长度
        msg->length = (CAN1->sFIFOMailBox[0].RDTR & CAN_RDT0R_DLC) >> 0;

        // 2.4 填写数据
        // 清空之前可能填写的data值
        // 将data数组中全部填写0
        memset(msg->data, 0, sizeof(msg->data));
        uint32_t low = CAN1->sFIFOMailBox[0].RDLR;
        uint32_t high = CAN1->sFIFOMailBox[0].RDHR;
        for (uint8_t j = 0; j < msg->length; j++)
        {
            if (j < 4)
            {
                // 低4位的字节
                msg->data[j] = low >> (8 * j);
            }
            else
            {
                // 高4位的字节
                msg->data[j] = high >> (8 * (j - 4));
            }
        }

        // 2.5 释放当前报文
        CAN1->RF0R |= CAN_RF0R_RFOM0;
    }
}

  • Driver_can.h
#ifndef __DRIVER_CAN_H
#define __DRIVER_CAN_H

#include "stm32f10x.h"
#include "Driver_USART1.h"
#include "string.h"
typedef struct
{
    uint16_t stdId;
    uint8_t data[8];
    uint8_t length;
} RxDataStruct;

void Driver_CAN_Init(void);

/**
 * 使用只需要标准格式的数据帧  默认参数
 * IDE: 标准格式 / 扩展格式
 * RTR: 数据帧 / 远程帧
 * stdId: 对应ID   11位
 * data: 数据本身
 */
void Driver_CAN_SendMsg(uint16_t stdId,uint8_t data[],uint8_t length);


/**
 * 接收FIFO0中的所有报文
 * 存放到结构体指针中 
 * uint8_t *msgCount存放接收报文的数量
 */
void Driver_CAN_ReceiveMsg(RxDataStruct rxDataStruct[],uint8_t *msgCount);

#endif

  • main.c
#include "Driver_USART1.h"
#include "Delay.h"
#include "Driver_can.h"
int main(void)
{

	/*  使用can的环回静默模式完成数据的自发自收 测试stm32的can功能 */
	Driver_USART1_Init();
	printf("hello can single...\n");

	Driver_CAN_Init();
	// 发送数据
	uint8_t *data = "hello\n";
	Driver_CAN_SendMsg(6, data, strlen((char *)data));
	// 再次发送数据
	data = "mengge\n";
	Driver_CAN_SendMsg(7, data, strlen((char *)data));

	// 添加延时
	Delay_ms(1000);

	// 接收数据
	RxDataStruct rxDataStruct[3];
	uint8_t msgCount;
	Driver_CAN_ReceiveMsg(rxDataStruct, &msgCount);
	printf("接收到%d条数据\n", msgCount);

	for (uint8_t i = 0; i < msgCount; i++)
	{
		printf("第%d条数据,stdId: %d,leng: %d,data:%s\n", i + 1, rxDataStruct[i].stdId,rxDataStruct[i].length,rxDataStruct[i].data);
	}

	while (1)
	{
	}
}

实验结果

在这里插入图片描述

HAL库版本
  • CubeMX配置

在这里插入图片描述

在这里插入图片描述

代码编写
  • can.c
...
/* USER CODE BEGIN 1 */
void User_Config_Filter(void)
{
  // 给FIFO0配置一个过滤器0
  CAN_FilterTypeDef filter_config;
  filter_config.FilterBank = 0;
  filter_config.FilterFIFOAssignment = CAN_FILTER_FIFO0;
  filter_config.FilterMode = CAN_FILTERMODE_IDMASK;
  filter_config.FilterScale = CAN_FILTERSCALE_32BIT;
  filter_config.FilterIdLow = 0;
  filter_config.FilterIdHigh = 0;
  filter_config.FilterMaskIdHigh = 0;
  filter_config.FilterMaskIdLow = 0;
  filter_config.FilterActivation = CAN_FILTER_ENABLE;
  HAL_CAN_ConfigFilter(&hcan, &filter_config);
}

void User_CAN_SendMsg(uint16_t stdId, uint8_t data[], uint8_t length)
{
  /* 1.读取当前发送邮箱的状态 */
  // 得到空闲的发送邮箱的个数
  uint32_t freeBoxesNum = HAL_CAN_GetTxMailboxesFreeLevel(&hcan);

  /* 2.使用封装好的方法发送报文 */
  if (freeBoxesNum > 0)
  {
    CAN_TxHeaderTypeDef header_type;
    header_type.IDE = CAN_ID_STD;
    header_type.RTR = CAN_RTR_DATA;
    header_type.StdId = stdId;
    header_type.DLC = length;
    // 自动寻找空闲的邮箱 存放对应的发送邮箱编号
    uint32_t boxNum;
    HAL_CAN_AddTxMessage(&hcan, &header_type, data, &boxNum);
  }
  else
  {
    // 当前邮箱没有空闲 等会再发
    printf("当前邮箱没有空闲 等会再发\n");
  }
}
void User_CAN_ReceiveMsg(RxDataStruct rxDataStruct[], uint8_t *msgCount)
{
  /* 1. 获取接收队列中的报文数 */
  *msgCount = HAL_CAN_GetRxFifoFillLevel(&hcan, CAN_RX_FIFO0);
  /* 2. 将报文数据赋值到结构体数组中 */
  for (uint8_t i = 0; i < *msgCount; i++)
  {
    // 提前声明结构体  用于接收报文数据的头信息
    CAN_RxHeaderTypeDef header_tyep;
    RxDataStruct *msg = &rxDataStruct[i];
    memset(msg->data, 0, 8);
    HAL_CAN_GetRxMessage(&hcan, CAN_RX_FIFO0, &header_tyep, msg->data);

    // 补全头信息
    msg->stdId = header_tyep.StdId;
    msg->length = header_tyep.DLC;
  }
}
/* USER CODE END 1 */
  • can.h
...
    /* USER CODE BEGIN Prototypes */

typedef struct
{
  uint16_t stdId;
  uint8_t data[8];
  uint8_t length;
}RxDataStruct;
...
/* USER CODE END Prototypes */
  • main.c
...
/* USER CODE BEGIN 2 */
  printf("hello can single\n");

  // 优先进行过滤器配置
  User_Config_Filter();
  HAL_CAN_Start(&hcan);

  User_CAN_SendMsg(6,"hello\n",6);
  User_CAN_SendMsg(7,"huizong\n",8);
  HAL_Delay(1000);
  RxDataStruct rxDataStruct[3];
  uint8_t msgCount;
  User_CAN_ReceiveMsg(rxDataStruct,&msgCount);
  printf("接收到了%d条消息\n",msgCount);

  for (uint8_t i = 0; i < msgCount; i++)
  {
    printf("第%d条消息,stdId:%d,length:%d,data:%s\n",i + 1,rxDataStruct[i].stdId,rxDataStruct[i].length,rxDataStruct[i].data);
  }
  

  /* USER CODE END 2 */
实验现象

在这里插入图片描述

2.2 案例2:双机测试:1发1收

在案例1 register版本的基础上作出细微改动即可。

代码编写
  • can.c
//1. 不使用静默环回模式,把以下代码注释
    // CAN1->BTR |= CAN_BTR_SILM;
    // CAN1->BTR |= CAN_BTR_LBKM;

// 2. 屏蔽模式下的ID和mask,不屏蔽
// 11位stdId  18位EXID IDE RTR 0  
    CAN1->sFilterRegister[0].FR1 = 0x00;
    // 掩码  -> 哪些ID需要接收  
    CAN1->sFilterRegister[0].FR2 = 0x00;
  • main.c

在CAN收发器上High连High,Low连Low,while中发送和接收数据选择一个,另外一个人选择另外一个即可。

int main(void)
{

	/*  使用can的环回静默模式完成数据的自发自收 测试stm32的can功能 */
	Driver_USART1_Init();
	printf("hello can single...\n");

	Driver_CAN_Init();

	RxDataStruct rxDataStruct[3];
	uint8_t msgCount;
	while (1)
	{
		// 发送数据
		// Driver_CAN_SendMsg(6,"hello\n",6);
		// printf("已经发送一条数据\n");
		// Delay_ms(1000);

		// 接收数据

		Driver_CAN_ReceiveMsg(rxDataStruct, &msgCount);
		for (uint8_t i = 0; i < msgCount; i++)
		{
			printf("接收到第%d条数据\n", i + 1);
			printf("stdId:%d,length:%d,data:%s \n",rxDataStruct[i].stdId,rxDataStruct[i].length,rxDataStruct[i].data);
		}
		Delay_ms(1000);
	}
}
实验现象

注意连线:High连High,Low连Low,其实本次简单的实验只连一条线也可以通信,差分信号线的作用就是为了抗干扰。通信设备就两个,发送数据也简单。

  • 发送端:

在这里插入图片描述

  • 接收端:
    在这里插入图片描述
这里是一个使用C++实现UDS诊断协议的协议层代码,基于面向对象设计,采用了策略模式,让代码更加易于维护。以下是详细注释: ```cpp /** * @brief UDS诊断协议的协议层(Protocol Layer)实现 */ #include <iostream> #include <vector> #include <memory> /** * @brief 抽象基类,定义了诊断协议的基本操作 */ class DiagnosticProtocol { public: virtual ~DiagnosticProtocol() = default; virtual int connect() = 0; virtual int disconnect() = 0; virtual std::vector<uint8_t> send(const std::vector<uint8_t>& data) = 0; }; /** * @brief 实现基于CAN总线的诊断协议 */ class CanProtocol : public DiagnosticProtocol { public: int connect() override { std::cout << "Connect to CAN bus\n"; return 0; } int disconnect() override { std::cout << "Disconnect from CAN bus\n"; return 0; } std::vector<uint8_t> send(const std::vector<uint8_t>& data) override { std::cout << "Send data via CAN bus\n"; // TODO: 实现CAN总线的数据发送 return {}; } }; /** * @brief 实现基于K线的诊断协议 */ class KLineProtocol : public DiagnosticProtocol { public: int connect() override { std::cout << "Connect to K-Line\n"; return 0; } int disconnect() override { std::cout << "Disconnect from K-Line\n"; return 0; } std::vector<uint8_t> send(const std::vector<uint8_t>& data) override { std::cout << "Send data via K-Line\n"; // TODO: 实现K线的数据发送 return {}; } }; /** * @brief 实现基于以太网的诊断协议 */ class EthernetProtocol : public DiagnosticProtocol { public: int connect() override { std::cout << "Connect to Ethernet\n"; return 0; } int disconnect() override { std::cout << "Disconnect from Ethernet\n"; return 0; } std::vector<uint8_t> send(const std::vector<uint8_t>& data) override { std::cout << "Send data via Ethernet\n"; // TODO: 实现以太网的数据发送 return {}; } }; /** * @brief 诊断协议的策略类,根据不同的协议类型选择不同的实现 */ class DiagnosticProtocolStrategy { public: explicit DiagnosticProtocolStrategy(std::unique_ptr<DiagnosticProtocol> protocol) : m_protocol(std::move(protocol)) {} int connect() { return m_protocol->connect(); } int disconnect() { return m_protocol->disconnect(); } std::vector<uint8_t> send(const std::vector<uint8_t>& data) { return m_protocol->send(data); } private: std::unique_ptr<DiagnosticProtocol> m_protocol; }; /** * @brief 示例代码,演示了如何使用UDS诊断协议的协议层 */ int main() { std::unique_ptr<DiagnosticProtocol> protocol; DiagnosticProtocolStrategy strategy{nullptr}; // 选择CAN总线作为诊断协议 protocol = std::make_unique<CanProtocol>(); strategy = DiagnosticProtocolStrategy(std::move(protocol)); strategy.connect(); auto response = strategy.send({0x01, 0x02, 0x03}); strategy.disconnect(); // 选择以太网作为诊断协议 protocol = std::make_unique<EthernetProtocol>(); strategy = DiagnosticProtocolStrategy(std::move(protocol)); strategy.connect(); response = strategy.send({0x04, 0x05, 0x06}); strategy.disconnect(); // 选择K线作为诊断协议 protocol = std::make_unique<KLineProtocol>(); strategy = DiagnosticProtocolStrategy(std::move(protocol)); strategy.connect(); response = strategy.send({0x07, 0x08, 0x09}); strategy.disconnect(); return 0; } ``` 以上代码使用了面向对象设计,将不同的诊断协议实现封装成不同的类,并通过策略模式实现了诊断协议层的可配置性。同时,代码易于扩展,如果需要支持新的诊断协议,只需要实现新的诊断协议类并添加到诊断协议策略中即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值