一份STM32的CAN通信指南 | C610电调 大疆C板

引子

现在是2024年1月18号晚上零点半,电路工数等困难科目已经考完,只是剩一门马原

临近寒假的这一段时间颇为闲暇,于是在工作室寻得一些 M2006无刷电机C610电调 ,加上手头上的 C板,试着组一台个人未来比赛用的四驱底盘

依据大疆资料来看,电调需要使用CAN通信来控制,正中知识盲区,于是放下手中的马原教材(其实根本没有拿起来过),学习一下CAN

环境准备

前置知识

  • STM32CubeMX的使用
  • 一定的C语言使用经验

软件环境

  • 代码生成 STM32CubeMX (以HAL库为基础)
  • 编译工具 arm-none-eabi工具链(使用其他编译器亦可)
  • 编写环境 VSCode+Embedded IDE (Keil和CubeIDE亦可)
  • 调试工具 Ozone(本篇仅以此方法调试)

硬件环境

  • 主控芯片 大疆C板-STM32F407IGH6
  • 烧录工具 JLink
  • 通讯目标 C610电调

设备文档

CAN的印象

何为CAN?

在查阅了很多资料后,我提取了几个关键词:总线结构串行通讯标准协议,只需要两条线,即可解决沿途中设备的通信需求,例如,使用一块主控板加上CAN总线就可以很轻松的控制多个电机,极大缓解了布线带给我们的焦虑

布线地狱

至于书面,准确,乃至于繁缛的官方定义,我便不写入文章里,百度看看就好

CAN的硬件组成

我们可以称一个通讯单元为节点,一个节点一般有三个部分:微控制器CAN控制器CAN收发器,总线两端须串上120Ω的电阻,以模拟无限远传输线的特性阻抗,通过开关等手段来选择是否使用这个电阻

CAN总线结构

STM32芯片会自带CAN外设拓展,名为bxCAN (Basic Extended CAN - 基本拓展CAN),详细内容此处不展开

要注意,一般的STM32开发板是不带有CAN收发器的,需要自己另外购买,大疆C板是自带CAN收发器的,所以可以直接使用

CAN的回环测试

姑且暂停理论部分的讲解,繁杂的原理总是令人头大,使人望而却步,我们先启动开发软件,走通一个通讯的流程,再来细细分析其中的缘由,或者跳过理论,只掌握软件层的流程也是可以的

基本步骤:配置STM32CubeMX > 配置CAN过滤器 > 发送接收报文

配置STM32CubeMX

启动CubeMX,选好芯片类型创建项目,首先把常规设置搞定

关于C板的一些注意点

  • C板默认的CAN1 PIN不是原理图上的位置, 需要修改一下
  • 注意C板的晶振是12MHz,要把输入频率调整成12MHz
  • C板的外设电源和swd输入的电源不在一条线路,不能通过swd口供电,需要插上24v电源或者usb口供电,否则CAN的收发器将不工作,无法正常收发数据,当然,回环模式还是可以收到的,因为回环的数据不经过CAN收发器
  • 一对一连接C板和电调时,需要将电调上的电阻打开,一对多时,把最远端的电阻打开即可,保持CAN总线两端串着电阻
RCC设置

SWD设置

时钟设置

.c文件和.h文件分开生成

项目管理类型之类的根据自己使用的开发环境来设置即可

简单写一个点灯测试一下

这是板载灯的连线

  • TIM5_CH1 - LED_BLUE
  • TIM5_CH2 - LED_GREEN
  • TIM5_CH3 - LED_RED
void breath_led()
{
   
    for (int i = 0; i < 100; i++) {
   
        HAL_Delay(10);
        __HAL_TIM_SetCompare(&htim5, TIM_CHANNEL_1, 20000 * i / 100);
        __HAL_TIM_SetCompare(&htim5, TIM_CHANNEL_2, 20000 * i / 100);
        __HAL_TIM_SetCompare(&htim5, TIM_CHANNEL_3, 20000 * i / 100);
    }

    for (int i = 100; i > 0; i--) {
   
        HAL_Delay(10);
        __HAL_TIM_SetCompare(&htim5, TIM_CHANNEL_1, 20000 * i / 100);
        __HAL_TIM_SetCompare(&htim5, TIM_CHANNEL_2, 20000 * i / 100);
        __HAL_TIM_SetCompare(&htim5, TIM_CHANNEL_3, 20000 * i / 100);
    }
}

将其放入主循环中运行,理所应当地成功了

呼吸灯测试

现在开始配置CAN通信

CubeMX界面中,在CAN1Parameter Settings我们可以看到

  • Bit Timings Parameters - 配置传输速度
    • Prescaler (for Time Quantum) - 分频,调整TQ(Time Quantum)大小
    • Time Quantum - 最小时间单位
    • Time Quanta in Bit Segment 1 - 相位缓冲段1段占几个TQ
    • Time Quanta in Bit Segment 2 - 相位缓冲段2段占几个TQ
    • Time for one Bit
    • Baud Rate - 波特率
    • ReSynchronization Jump Width - 再同步补偿宽度
  • Basic Parameters - 基本参数
    • Time Triggered Communication Mode - 时间触发模式
    • Automatic Bus-off Management - 自动离线管理
    • Automatic Wake-Up Mode - 自动唤醒
    • Automatic Retransmission - 自动重传
    • Receive Fifo Locked Mode - 锁定模式
    • Transmit Fifo Priority - 报文发送优先级
  • Advanced Parameters - 高级参数
    • Operating Mode -*运行模式:正常模式 静默模式 回环模式 回环静默模式

NVIC Interrupt Table 中有

  • CAN1 TX interrupts
  • CAN1 RX0 interrupts
  • CAN1 RX1 interrupt
  • CAN1 SCE interrupt

这是我们初期需要关注的配置列表

1. 设置波特率

先打开CAN1的Activated选项, 其他的选项才能显示出来

以我的需求为例,查阅大疆官方资料可以得知

将 CAN 信号线连接到控制板接收 CAN 控制指令,CAN 总线比特率为 1Mbps

所以我们需要将CAN通讯的比特率 baud rate 设置为 1000000 bit/s

根据波特率计算公式 BaudRate = TQ * ( Sync + TBS1 + TBS2) , 我们得到如下设置

TQ * ( 4 + 9 + 1 ) = 1000ns

根据实际情况计算一下即可,也可以多选几个选项,把正确的波特率尝试出来,灰色的选项就是CubeMX帮我们计算好的数值

别忘了修改CAN1的引脚

CAN引脚设置

2. 打开中断

处理电调发送的电机信息,需要中断来调用回调函数,于是打开接收中断

在接收和发送信息前,我们会遇到一个CAN通信的抽象概念 —— 邮箱

这里我使用的单片机中,CAN外设具有两个用于接收信息的邮箱,我们命其为 FIFO0FIFO1,每个邮箱都有一个过滤器,用于筛选报文,可以存放三条报文,在中断设置中对应 CAN1 RX0 interruptCAN1 RX1 interrupt,我们打开需要使用的那一个就可以

既然存在接收邮箱,相应的,就有发送邮箱,我们现在只要知道发送邮箱存在发送优先级且每个邮箱只能存放一条报文

现在,我们已经在CubeMX中配置好了CAN,下一步就是要配置CAN过滤器

配置CAN过滤器

前面我们说到,STM32上有两个邮箱用于接收报文,为了接收我们想要的报文,我们需要配置一下过滤器,把不想接受的报文过滤掉,只放行想要的报文

配置过滤器需要我们自己手写,并未提前生成,但HAL库提供了过滤器配置参数的结构体类型,我们只需要给这个结构体赋值,然后调用HAL提供的初始化函数即可完成配置, 下面的代码仅仅是展示, 还不需要写进项目里

// Drivers\STM32F4xx_HAL_Driver\Inc\stm32f4xx_hal_can.h

// 过滤器结构体
typedef struct
{
   
  uint32_t FilterIdHigh;
  uint32_t FilterIdLow;
  uint32_t FilterMaskIdHigh;
  uint32_t FilterMaskIdLow; 
  uint32_t FilterFIFOAssignment; 
  uint32_t FilterBank;        
  uint32_t FilterMode;
  uint32_t FilterScale;
  uint32_t FilterActivation; 
  uint32_t SlaveStartFilterBank; 
} CAN_FilterTypeDef;

// 配置函数
HAL_StatusTypeDef HAL_CAN_ConfigFilter(
    CAN_HandleTypeDef *hcan, 
    CAN_FilterTypeDef *sFilterConfig
    );

具体的使用和结构体的定义随后再讲,我们只需要对这个结构体和函数有一个大概的印象即可

发送接收报文

首先是发送

我预期使用一块主控与四个电机通信,那么在发送报文时,就需要指定发送给哪一个电机,以及其他一些信息,比如发送信息的长度信息的类型信息ID类型等等,HAL把这些发送需要的信息定义成了一个结构体 CAN_TxHeaderTypeDef,我们只需要为每一个电机声明一个 CAN_TxHeaderTypeDef 结构体,再确定好发送的数据内容,就可以将数据发送到指定的电机中

我们回想一下,在设置接收中断时,是不是提到了邮箱的概念?STM32F407IGHx为我们提供了三个发送邮箱,在发送时,HAL库会自动选择空闲的邮箱,然后将实际使用的邮箱返回给我们,这也解释了我们传入函数的是指向邮箱的指针,而非一个邮箱编号的常量

HAL库理所应当地帮我们写好了发送的函数,只要传入can的句柄报文头结构体数据信息邮箱即可

下面的代码同样不需要写进项目

// Drivers\STM32F4xx_HAL_Driver\Inc\stm32f4xx_hal_can.h

// 发送函数的声明
HAL_StatusTypeDef HAL_CAN_AddTxMessage(
    CAN_HandleTypeDef *hcan, 
    CAN_TxHeaderTypeDef *pHeader, 
    uint8_t aData[], 
    uint32_t *pTxMailbox
    )// 邮箱编号的定义
#define CAN_TX_MAILBOX0             (0x00000001U)  /*!< Tx Mailbox 0  */
#define CAN_TX_MAILBOX1             (0x00000002U)  /*!< Tx Mailbox 1  */
#define CAN_TX_MAILBOX2             (0x00000004U)  /*!< Tx Mailbox 2  */

然后是接收

总线上的报文在经过了我们设置的过滤器后,正确的报文会触发我们设置的中断,我们便可以在中断的回调函数中对收到的数据进行处理了

我们只需要找到HAL库为我们提供的中断函数,对其进行覆写即可

以下是示例代码

// 这是一种使用情况

// 回调函数
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
   
	if(hcan->Instance ==CAN1)
	{
   
	  HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &RxHeader, date_CAN1); 
	  return ;
	}
### 大疆ACAN通信概述 大疆A型开发(基于STM32F427IIHx芯片)支持多种通信方式,其中包括CAN总线通信CAN总线是一种广泛应用于汽车和工业自动化领域中的串行通信网络技术,具有高可靠性和实时性特点[^3]。 对于大疆A而言,其内置的CAN控制器能够通过两根信号线实现与其他设备之间的高效数据交换。这种设计不仅简化了硬件连接复杂度,还提高了系统的稳定性和抗干扰能力[^4]。 ### 配置方法 为了使能并正确配置大疆A上的CAN通信功能,开发者需按照如下指导操作: #### 初始化设置 在启动阶段,必须先初始化CAN模块的相关参数,如波特率、工作模式等。这通常可以通过修改相应的寄存器来完成。下面是一个简单的初始化函数示例,在实际应用中可根据具体需求整各项参数值。 ```c void CAN_Init(void){ // 设置CAN滤波器模式为标识符列表模式 CAN_FilterInitStructure.CAN_FilterNumber = 0; CAN_FilterInitStructure.CAN_FilterMode = CAN_FilterMode_IdList; CAN_FilterInitStructure.CAN_FilterScale = CAN_FilterScale_32bit; CAN_FilterInitStructure.CAN_FilterIdHigh = 0x0000; CAN_FilterInitStructure.CAN_FilterIdLow = 0x0000; CAN_FilterInitStructure.CAN_FilterMaskIdHigh = 0x0000; CAN_FilterInitStructure.CAN_FilterMaskIdLow = 0x0000; CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_FIFO0; CAN_FilterInitStructure.CAN_FilterActivation = ENABLE; /* Initialize the filter */ CAN_FilterInit(&CAN_FilterInitStructure); // 配置CAN传输速率 CAN_InitStructure.CAN_TTCM = DISABLE; CAN_InitStructure.CAN_ABOM = DISABLE; CAN_InitStructure.CAN_AWUM = DISABLE; CAN_InitStructure.CAN_NART = DISABLE; CAN_InitStructure.CAN_RFLM = DISABLE; CAN📐⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗⚗
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值