STM32F4_V1.25.0固件库应用于STM32F407ZGT6(二)- 调试摄像头OV3640的IIC通路

1、软件、硬件

接着上一篇博客的工程基础
STM32F4_V1.25.0固件库应用于STM32F407ZGT6(一)

2、硬件连接

唠叨几句,购买了一个在卡片电脑上专用的摄像头模块,排针和我的板子camara卡槽匹配不上,只能飞线。长下面这个样子,CAMERA是卡槽接口,背面有个CAMERB是插针
在这里插入图片描述

商家只管卖,连个原理图都没有,板子上引脚名称也没有,询问商家的回复是这摄像头为某某板子专用,他们也没有原理图。网上查了那些板子camera接口的原理图,才知道摄像头上的三角符号一端要板子上三角符号对应。好吧,原理图上也没有标注三角符号那一端是地线还是排线顶端。

好不容易找到在Android系统上使用过该模块的人,给出的说明书如下。

2.1、CAMERA接口图

下图EINT19其实就是PWDN引脚

在这里插入图片描述
对应模块来看,CAMERA接口应该是下面这样的,和上图左右对应匹配

在这里插入图片描述

2.2、CAMERB接口图

在这里插入图片描述
看模块背面的CAMERB接口,实际上插针对应左右两边和上图相反。只要保证CAMERA接口认识正确,用万用表探CAMERA和CAMERB即可找到引脚。

为啥这么设计呢?带着疑问,我去看板子的硬件,也是这样的,板子上卡槽是按原理图的左右顺序预留的,那么插针只有左右顺序对调才能匹配板子。或者板子做预留的卡槽和摄像头插针左右相反,总之这是需要留意的点。

用公对公的杜邦线一个一个和板子引脚连接,这里先测试IIC通路,把5V、3.3V、GND、RST、PWDN、I2C_SCL、I2C_SDA对应连接到板子上。

板子camera接口原理图如下:
在这里插入图片描述

3、代码实现

OV3640的IIC设备地址是0x78,写数据时是0x78,读数据时是0x79。STM32的i2c驱动对于设备地址是直接传入0x78,根据读写再与或读写位(无需左移)。

从标准IIC波形上看摄像头的数据,设备地址的高7位是0X3C,那种需要左移或上读写位的驱动,传入的是0X3C,左移或上读写位即为0x78(写)、0x79(读)。

在这里插入图片描述

3.1、i2c.h文件

#ifndef __BSP_I2C_H__
#define __BSP_I2C_H__

/****************************** Includes *****************************/
#include "stm32f4xx.h"
#include "stm32f4xx_hal.h"
#include "bsp_debug_usart.h"

/****************************** Defines *******************************/

#define OV3640_DEVICE_ADDRESS     0x78      	//0x78 & 1 = 0x78 写
                                            	//0x78 | 1 = 0x79 读

/*引脚定义*/ 
#define SENSORS_I2C_SCL_GPIO_PORT         		GPIOB
#define SENSORS_I2C_SCL_GPIO_CLK_ENABLE()    	__GPIOB_CLK_ENABLE()
#define SENSORS_I2C_SCL_GPIO_PIN         		GPIO_PIN_8
 
#define SENSORS_I2C_SDA_GPIO_PORT         		GPIOB
#define SENSORS_I2C_SDA_GPIO_CLK_ENABLE()   	__GPIOB_CLK_ENABLE()
#define SENSORS_I2C_SDA_GPIO_PIN          		GPIO_PIN_9

#define SENSORS_I2C_AF                    		GPIO_AF4_I2C1

#define SENSORS_I2C              		  		I2C1
#define SENSORS_I2C_RCC_CLK_ENABLE()     		__HAL_RCC_I2C1_CLK_ENABLE()

#define SENSORS_I2C_FORCE_RESET()      			__HAL_RCC_I2C1_FORCE_RESET()
#define SENSORS_I2C_RELEASE_RESET()    			__HAL_RCC_I2C1_RELEASE_RESET()
									 
void I2CMaster_Init(void);										 
uint8_t OV3640_WriteReg(uint16_t Addr, uint8_t Data);
uint8_t OV3640_ReadReg(uint16_t Addr);

#endif // __BSP_I2C_H__

3.2、i2c.c文件

OV3640的寄存器地址是16位,所以直接调用 HAL_I2C_Mem_Write、HAL_I2C_Mem_Read函数操作寄存器,第4个参数传入I2C_MEMADD_SIZE_16BIT即可。

#include "bsp_i2c.h"
#include <stdio.h>

I2C_HandleTypeDef I2C_Handle;
					
/*******************************  Function ************************************/

void I2CMaster_Init(void) 
{
	GPIO_InitTypeDef GPIO_InitStructure;

	/* 使能I2Cx时钟 */
	SENSORS_I2C_RCC_CLK_ENABLE();

	/* 使能I2C GPIO 时钟 */
	SENSORS_I2C_SCL_GPIO_CLK_ENABLE();
	SENSORS_I2C_SDA_GPIO_CLK_ENABLE();

	/* 配置I2Cx引脚: SCL ----------------------------------------*/
	GPIO_InitStructure.Pin =  SENSORS_I2C_SCL_GPIO_PIN; 
	GPIO_InitStructure.Mode = GPIO_MODE_AF_OD;
	GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
	GPIO_InitStructure.Pull = GPIO_NOPULL;
	GPIO_InitStructure.Alternate = SENSORS_I2C_AF; 
	HAL_GPIO_Init(SENSORS_I2C_SCL_GPIO_PORT, &GPIO_InitStructure);

	/* 配置I2Cx引脚: SDA ----------------------------------------*/
	GPIO_InitStructure.Pin = SENSORS_I2C_SDA_GPIO_PIN;  
	HAL_GPIO_Init(SENSORS_I2C_SDA_GPIO_PORT, &GPIO_InitStructure); 
	
	if(HAL_I2C_GetState(&I2C_Handle) == HAL_I2C_STATE_RESET)
	{	
		/* 强制复位I2C外设时钟 */  
		SENSORS_I2C_FORCE_RESET(); 

		/* 释放I2C外设时钟复位 */   
		SENSORS_I2C_RELEASE_RESET(); 
		
        /* I2C 配置 */
		I2C_Handle.Instance = SENSORS_I2C;
        I2C_Handle.Init.AddressingMode  = I2C_ADDRESSINGMODE_7BIT;
    	I2C_Handle.Init.ClockSpeed      = 40000;    //40K,低速率
    	I2C_Handle.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
    	I2C_Handle.Init.DutyCycle       = I2C_DUTYCYCLE_2;
    	I2C_Handle.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
    	I2C_Handle.Init.NoStretchMode   = I2C_NOSTRETCH_DISABLE;
    	I2C_Handle.Init.OwnAddress1     = 0xFE ;
    	I2C_Handle.Init.OwnAddress2     = 0; 
   
		/* 初始化I2C */
		HAL_I2C_Init(&I2C_Handle);	
	}
}

/**
  * @brief  Manages error callback by re-initializing I2C.
  * @param  Addr: I2C Address
  * @retval None
  */
static void I2Cx_Error(void)
{
	/* 恢复I2C寄存器为默认值 */
	HAL_I2C_DeInit(&I2C_Handle); 
	/* 重新初始化I2C外设 */
	I2CMaster_Init();
}

/**
  * @brief  写一字节数据到OV3640寄存器
  * @param  Addr: OV3640 的寄存器地址
  * @param  Data: 要写入的数据
  * @retval 返回0表示写入正常,0xFF表示错误
  */
uint8_t OV3640_WriteReg(uint16_t Addr, uint8_t Data)
{
  HAL_StatusTypeDef status = HAL_OK;
  
  status = HAL_I2C_Mem_Write(&I2C_Handle, OV3640_DEVICE_ADDRESS, (uint16_t)Addr, I2C_MEMADD_SIZE_16BIT, (uint8_t*)&Data, 1, 1000);
  
  /* Check the communication status */
  if(status != HAL_OK)
  {
    /* Re-Initiaize the I2C Bus */
    printf("ERR write status[%d]\r\n",status);
    I2Cx_Error();
  }
  
  return status;
}

/**
  * @brief  从OV3640寄存器中读取一个字节的数据
  * @param  Addr: 寄存器地址ַ
  * @retval 返回读取得的数据
  */
uint8_t OV3640_ReadReg(uint16_t Addr)
{
  uint8_t Data = 0;

  HAL_StatusTypeDef status = HAL_OK;

  status = HAL_I2C_Mem_Read(&I2C_Handle, OV3640_DEVICE_ADDRESS, (uint16_t)Addr, I2C_MEMADD_SIZE_16BIT, (uint8_t*)&Data, 1, 1000);

  /* Check the communication status */
  if(status != HAL_OK)
  {
    /* I2C error occurred */
    printf("ERR read status[%d]\r\n",status);
    I2Cx_Error();
  }
  
  /* return the read data */
  return Data;
}

3.3、ov3640.h文件

定义了摄像头所需的所有引脚

#include "stm32f4xx.h"
#include "stm32f4xx_hal.h"

// VSYNC 
#define DCMI_VSYNC_GPIO_PORT        	    GPIOB
#define DCMI_VSYNC_GPIO_CLK_ENABLE()      	__HAL_RCC_GPIOB_CLK_ENABLE()
#define DCMI_VSYNC_GPIO_PIN         	    GPIO_PIN_7
#define DCMI_VSYNC_AF			            GPIO_AF13_DCMI

// HSYNC
#define DCMI_HSYNC_GPIO_PORT        	    GPIOA
#define DCMI_HSYNC_GPIO_CLK_ENABLE()     	__HAL_RCC_GPIOA_CLK_ENABLE()
#define DCMI_HSYNC_GPIO_PIN         	    GPIO_PIN_4
#define DCMI_HSYNC_AF			            GPIO_AF13_DCMI

// PIXCLK
#define DCMI_PIXCLK_GPIO_PORT        	    GPIOA
#define DCMI_PIXCLK_GPIO_CLK_ENABLE()     	__HAL_RCC_GPIOA_CLK_ENABLE()
#define DCMI_PIXCLK_GPIO_PIN         	    GPIO_PIN_6
#define DCMI_PIXCLK_AF			            GPIO_AF13_DCMI

// PWDN
#define DCMI_PWDN_GPIO_PORT        	      	GPIOC
#define DCMI_PWDN_GPIO_CLK_ENABLE()       	__HAL_RCC_GPIOC_CLK_ENABLE()
#define DCMI_PWDN_GPIO_PIN         	      	GPIO_PIN_0

// RST
#define DCMI_RST_GPIO_PORT        	      	GPIOF
#define DCMI_RST_GPIO_CLK_ENABLE()        	__HAL_RCC_GPIOF_CLK_ENABLE()
#define DCMI_RST_GPIO_PIN       	        GPIO_PIN_10

// D0-D7
#define DCMI_D0_GPIO_PORT        	      	GPIOC
#define DCMI_D0_GPIO_CLK_ENABLE()      		__HAL_RCC_GPIOC_CLK_ENABLE()
#define DCMI_D0_GPIO_PIN                	GPIO_PIN_6
#define DCMI_D0_AF			                GPIO_AF13_DCMI

#define DCMI_D1_GPIO_PORT      	        	GPIOC
#define DCMI_D1_GPIO_CLK_ENABLE()      		__HAL_RCC_GPIOC_CLK_ENABLE()
#define DCMI_D1_GPIO_PIN         	      	GPIO_PIN_7
#define DCMI_D1_AF			                GPIO_AF13_DCMI

#define DCMI_D2_GPIO_PORT               	GPIOC
#define DCMI_D2_GPIO_CLK_ENABLE()      		__HAL_RCC_GPIOC_CLK_ENABLE()
#define DCMI_D2_GPIO_PIN         	      	GPIO_PIN_8
#define DCMI_D2_AF			                GPIO_AF13_DCMI

#define DCMI_D3_GPIO_PORT        	      	GPIOC
#define DCMI_D3_GPIO_CLK_ENABLE()      		__HAL_RCC_GPIOC_CLK_ENABLE()
#define DCMI_D3_GPIO_PIN       	        	GPIO_PIN_9
#define DCMI_D3_AF			                GPIO_AF13_DCMI

#define DCMI_D4_GPIO_PORT        	      	GPIOE
#define DCMI_D4_GPIO_CLK_ENABLE()      		__HAL_RCC_GPIOE_CLK_ENABLE()
#define DCMI_D4_GPIO_PIN         	      	GPIO_PIN_4
#define DCMI_D4_AF			                GPIO_AF13_DCMI

#define DCMI_D5_GPIO_PORT               	GPIOB
#define DCMI_D5_GPIO_CLK_ENABLE()      		__HAL_RCC_GPIOB_CLK_ENABLE()
#define DCMI_D5_GPIO_PIN         	      	GPIO_PIN_6
#define DCMI_D5_AF			                GPIO_AF13_DCMI

#define DCMI_D6_GPIO_PORT        	      	GPIOE
#define DCMI_D6_GPIO_CLK_ENABLE()      		__HAL_RCC_GPIOE_CLK_ENABLE()
#define DCMI_D6_GPIO_PIN       	        	GPIO_PIN_5
#define DCMI_D6_AF			                GPIO_AF13_DCMI

#define DCMI_D7_GPIO_PORT      	        	GPIOE
#define DCMI_D7_GPIO_CLK_ENABLE()      		__HAL_RCC_GPIOE_CLK_ENABLE()
#define DCMI_D7_GPIO_PIN                	GPIO_PIN_6
#define DCMI_D7_AF			                GPIO_AF13_DCMI

#define OV3640_PIDH                         0x300A  //产品ID高8位
#define OV3640_PIDL                         0x300B  //产品ID低8位

//产品ID结构体
typedef struct
{
  uint8_t PIDH;
  uint8_t PIDL;
}OV3640_IDTypeDef;

3.4、ov3640.c文件

初始化控制摄像头使用的GPIO

/**
  * @brief  初始化控制摄像头使用的GPIO
  * @param  None
  * @retval None
  */
void OV3640_HW_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    
    /***DCMI引脚配置***/
    /* 使能DCMI时钟 */

    DCMI_RST_GPIO_CLK_ENABLE();
    DCMI_PWDN_GPIO_CLK_ENABLE();
    DCMI_VSYNC_GPIO_CLK_ENABLE();
    DCMI_HSYNC_GPIO_CLK_ENABLE();
    DCMI_PIXCLK_GPIO_CLK_ENABLE();

    DCMI_D0_GPIO_CLK_ENABLE();
    DCMI_D1_GPIO_CLK_ENABLE();
    DCMI_D2_GPIO_CLK_ENABLE();
    DCMI_D3_GPIO_CLK_ENABLE();    
    DCMI_D4_GPIO_CLK_ENABLE();
    DCMI_D5_GPIO_CLK_ENABLE();
    DCMI_D6_GPIO_CLK_ENABLE();
    DCMI_D7_GPIO_CLK_ENABLE();
   
    /*控制/同步信号线*/
    GPIO_InitStructure.Pin = DCMI_VSYNC_GPIO_PIN;
    GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_InitStructure.Pull = GPIO_PULLUP ;
    GPIO_InitStructure.Alternate = DCMI_VSYNC_AF;
    HAL_GPIO_Init(DCMI_VSYNC_GPIO_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.Pin = DCMI_HSYNC_GPIO_PIN;
    GPIO_InitStructure.Alternate = DCMI_HSYNC_AF;
    HAL_GPIO_Init(DCMI_HSYNC_GPIO_PORT, &GPIO_InitStructure);
 
    GPIO_InitStructure.Pin = DCMI_PIXCLK_GPIO_PIN;
    GPIO_InitStructure.Alternate = DCMI_PIXCLK_AF;
    HAL_GPIO_Init(DCMI_PIXCLK_GPIO_PORT, &GPIO_InitStructure);
    
    /*数据信号*/
    GPIO_InitStructure.Pin = DCMI_D0_GPIO_PIN;
    GPIO_InitStructure.Alternate = DCMI_D0_AF;
    HAL_GPIO_Init(DCMI_D0_GPIO_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.Pin = DCMI_D1_GPIO_PIN;
    GPIO_InitStructure.Alternate = DCMI_D1_AF;
    HAL_GPIO_Init(DCMI_D1_GPIO_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.Pin = DCMI_D2_GPIO_PIN;
    GPIO_InitStructure.Alternate = DCMI_D2_AF;
    HAL_GPIO_Init(DCMI_D2_GPIO_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.Pin = DCMI_D3_GPIO_PIN;
    GPIO_InitStructure.Alternate = DCMI_D3_AF;
    HAL_GPIO_Init(DCMI_D3_GPIO_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.Pin = DCMI_D4_GPIO_PIN;
    GPIO_InitStructure.Alternate = DCMI_D4_AF;
    HAL_GPIO_Init(DCMI_D4_GPIO_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.Pin = DCMI_D5_GPIO_PIN;
    GPIO_InitStructure.Alternate = DCMI_D5_AF;
    HAL_GPIO_Init(DCMI_D5_GPIO_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.Pin = DCMI_D6_GPIO_PIN;
    GPIO_InitStructure.Alternate = DCMI_D6_AF;
    HAL_GPIO_Init(DCMI_D6_GPIO_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.Pin = DCMI_D7_GPIO_PIN;
    GPIO_InitStructure.Alternate = DCMI_D7_AF;
    HAL_GPIO_Init(DCMI_D7_GPIO_PORT, &GPIO_InitStructure);


    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_InitStructure.Pull = GPIO_PULLUP ;
    GPIO_InitStructure.Pin = DCMI_PWDN_GPIO_PIN;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;    
    HAL_GPIO_Init(DCMI_PWDN_GPIO_PORT, &GPIO_InitStructure);

    GPIO_InitStructure.Pin = DCMI_RST_GPIO_PIN;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
    HAL_GPIO_Init(DCMI_RST_GPIO_PORT, &GPIO_InitStructure);
    
    /*PWDN引脚,高电平关闭电源,低电平供电,GPIO_PIN_RESET是0*/
    HAL_GPIO_WritePin(DCMI_PWDN_GPIO_PORT,DCMI_PWDN_GPIO_PIN,GPIO_PIN_RESET);
}

/**
  * @brief  读取摄像头的ID
  * @param  OV3640ID:存储ID的结构体
  * @retval None
  */
void OV3640_ReadID(OV3640_IDTypeDef *OV3640ID)
{
  /*读取寄存芯片ID*/
  OV3640ID->PIDH = OV3640_ReadReg(OV3640_PIDH);
  OV3640ID->PIDL = OV3640_ReadReg(OV3640_PIDL);
}


3.5、main函数

int main(void)
{

  HAL_Init();
  /* Configure the system clock to 168 MHz */
  SystemClock_Config();
  DEBUG_USART_Config();
  HAL_Delay(1000);
  //camera	ID 结构体初始化
  OV3640_IDTypeDef OV3640_Camera_ID;
  //I2C初始化
  I2CMaster_Init();   
  //摄像头硬件初始化
  OV3640_HW_Init();
  HAL_Delay(100);

  //读取摄像头ID
  OV3640_ReadID(&OV3640_Camera_ID);
  printf("ID = %x%x\r\n",OV3640_Camera_ID.PIDH ,OV3640_Camera_ID.PIDL);
  
  if(OV3640_Camera_ID.PIDH  == 0x36)
  {
      printf("Right\r\n");
      while(1);
  }
  else
  {
      printf("ERR\r\n");
      while(1);  
  }
}

4、调试通路

4.1、抓取IIC波形失败

用逻辑分析仪抓取波形,发现在设备地址0x78后,收到摄像头回复的NACK,随后跟着Stop。怀疑是IIC驱动不对,因为有的OV摄像头默认回复的是NACK,而STM32F4的V1.25.0驱动在收到NACK后会报错退出。OV家的摄像头,使用SCCB协议,类似IIC,但和IIC还是有点区别,对第9位不关心。

查阅资料,这篇博客给出了解释,OV3640会回复ACK,所以不是驱动的问题。
Wince6.0 上增加ov3640摄像头

4.2、查找原因,解决问题

可能压根摄像头就没有工作,因为程序起来后,摄像头一点温度都没有,查阅资料发现别的模块外部都有一个24M的晶振,而我购买这一块摄像头没有外部时钟,草率了以为有来着,原来晶振是长那个样子。

好吧!查阅资料,寻求帮助,了解到摄像头的时钟可以由外部晶振提供,或者由主控芯片提供,这里使用PA8引脚(MCO1)直接将板子外部晶振的时钟输出给摄像头。

我手上的板子外部晶振是25M,刚好符合摄像头工作时钟(6-27M)

void ov3640_input_clk()
{
    GPIO_InitTypeDef GPIO_InitStructure;

    GPIO_InitStructure.Pin = GPIO_PIN_8;
    GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
    GPIO_InitStructure.Mode = GPIO_MODE_AF_PP;

    HAL_GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    //时钟源输出到MCO1引脚(PA8),选择HSE时钟作为MCO1源,1分频,那么输出25M时钟给摄像头
    HAL_RCC_MCOConfig(RCC_MCO1, RCC_MCO1SOURCE_HSE,RCC_MCODIV_1);
}

用示波器探测PA8引脚,可以观察到25M稳定时钟输出,将PA8引脚接到摄像头的CAMCLK脚

提供时钟后,摄像头发烫了,但是IIC依然没有数据,害怕因为短路摄像头才发烫。

在同事帮助下,看芯片手册文档,上电要满足一定的时序,摄像头才会正常工作。对于硬件小白的我来说,以为芯片只要供电就能工作,没想到还有一定的时序要求。

(哎呦喂,一定要仔细看手册啊!!!!!!!)

在这里插入图片描述
如果1.8V用于I/O电源,则首选使用内部DVDD。 如果2.8V用于I/O电源,由于内部DVDD调节器的高压下降,存在潜在的热问题。 因此,对于2.8V的I/O电源,OmniVision建议使用外部DVDD源。

用万用表探测摄像头模块的电压,供电是5V,电源转换后DOVDD是2.79V,DVDD是1.53V,AVDD是2.79V,这也分不清该用啥电源啊?

随便试试哪种时序吧!

在这里插入图片描述
这个上电后,需要延时5ms再将PWDN脚拉低,加了延时也没有效果,依然IIC通信没有成功。

看到一篇博客,说OV5640和OV3640差不多,所以参照OV5640的初始化流程,发现OV5640的上电时序还对复位脚有操作。

查看OV5640的datasheet,无论外部电源还是内部电源,时序都差不多,在PWDN拉低后还有拉高RESET脚才能操作IIC,且保证时钟稳定输入。

在这里插入图片描述
修改代码

void OV3640_HW_Init(void)
{
    /***省略代码部分***/

    /*POWER 低*/
    HAL_GPIO_WritePin(DCMI_PWDN_GPIO_PORT,DCMI_PWDN_GPIO_PIN,(GPIO_PinState)0);
    /*RESET 高*/
    HAL_GPIO_WritePin(DCMI_RST_GPIO_PORT,DCMI_RST_GPIO_PIN,(GPIO_PinState)1);
}

再次抓波形,成功读取到OV3640的产品ID,我的天哪!终于可以了,忙活了几天。

该产品ID和0V5640这些不一样,并不是型号ID,而是0X364C
在这里插入图片描述

5、总结

5.1、摄像头要么使用外部晶振时钟,要么使用主控板提供时钟,必须有时钟才能工作。

5.2、一定要仔细看手册,这里写了要把复位脚拉高后才能操作IIC,只看图的我忽略了那句话。

在这里插入图片描述

5.3、遇到问题,冷静分析。

别急,别急,别急

查硬件、查软件、查文档

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值