485 Modbus协议程序实例(DMA+缓存数据处理)

使用数据缓存的思路处理接收的数据,DMA采用循环接收的方式,利用接收索引与取数据索引不同判断是否有新数据导入需要处理,从原理上可以解决一些数据阻塞覆盖的问题,理论上来说,在单片机算力不足导致主循环处理数据不及时时会出现数据处理错误,此时需要扩大缓冲区或更换芯片,但整体来讲应用效果应该会比较好。

数据缓存处理方法:即取数据索引若与存数据索引不同则不断自增并取数据判断,直到追上储存数据的索引。

typedef 
	union{
		struct {
			u8	b0:1;
			u8	b1:1;
			u8	b2:1;
			u8	b3:1;
			u8	b4:1;
			u8	b5:1;
			u8	b6:1;
			u8	b7:1;
			u8	b8:1;
			u8	b9:1;
			u8	b10:1;
			u8	b11:1;
			u8	b12:1;
			u8	b13:1;
			u8	b14:1;
			u8	b15:1;
		} bit;
		struct{
			u8	low;	/* low	8 bit */
			u8	high;	/* high 8 bit */
		}byte;
		u16 	word;
	}word_def;

#define TX_MODE			(GPIOB->ODR |= GPIO_Pin_8)
#define RX_MODE			(GPIOB->ODR &= ~GPIO_Pin_8)
	
	word_def UartStateAdr;
	
	
	#define FunctionReceiveFlag							UartStateAdr.bit.b5//功能码接收成功标志
	#define HeadReceiveFlag								UartStateAdr.bit.b4//数据头帧接收标志
	#define ReceiveVerifyFlag								UartStateAdr.bit.b3//取出的数据有效需求校验标志
	#define NeedReplyFlag									UartStateAdr.bit.b2//接收处理完毕需求回复标志
	#define ReceiveCompletedFlag						UartStateAdr.bit.b1//接收完毕标志
	#define UselessDataFlag								UartStateAdr.bit.b0//前期普通校验无效数据标志

	typedef struct     //对应存放寄存器数据的数组坐标的结构体
	{
	  u8 A;
	  u8 B;
	} ModbusRegs_Coordinate;  

	ModbusRegs_Coordinate  G_Coordinate;//数组坐标结构体变量(全局)
	
	u8 RXmodeFlag;//大循环中持续置位GPIO,避免在其他实践中遇到在中断直接操作GOIO运行后却未置位的情况
	u8 IDcode=0x02;//Modbus协议从机ID
	u8 G_SendByteTable_8u[256];//发送区数组
	u8 G_BufferReceive_8u[256];//接收缓冲区(DMA接收数据存储区)
	u8 G_ReceiveByteTable[256];//取数据判断以及校验后实际接收数据的数组
	u8 ReceiveDataTemp;//接收传递变量
	volatile u8 SaveFetchByteIndex=0;//取出数据的储存指针
	volatile u8 ReceiveBufferIndex=0;//接收的数据在缓冲数组位置的指针(指向下一个将被赋值的数组位置)
	volatile u8 FetchBufferIndex=0;//取缓冲区数组的数据在缓冲数组的指针(目前取用的数据位置)(u8数据对应256个缓冲数据,自增即可循环,即255+1=0)
	

	u16 G_ModbusRegs_16u[6][128];  //定义700个寄存器
    u16 Read_RegAddress;//读取寄存器起始地址
	u16 Read_ProcessRegAddress;//读取寄存器过程中地址
	u16 Read_RegNum;//读取寄存器的个数
	u16 Write_RegAddress;//写寄存器地址 或 写多个寄存器起始地址
	u16 Write_ProcessRegAddress;//写寄存器地址 或 写多个寄存器末尾地址
	u16 Write_RegNum;//写寄存器的个数
	u16 ReceiveLength=8;//接收数据长度初始化为8
	u16 SendPacketState;//发送数据位
	u16 SendLength;//发送数据长度
	u16 CRC_Check;//根据前面数据计算获得的校验值(与接收到的校验比较验证)
	u16 SendCRC_Check;//发送数据CRC校验码
/*v将寄存器地址至数组行列坐标转化v*/
	ModbusRegs_Coordinate RegCoordinateCalcuate(u16 Address)
	{
		ModbusRegs_Coordinate  Coordinate;
		Coordinate.A=(Address>>7);//第A+1行
		Coordinate.B=(Address%128);//第B+1列
		return (Coordinate);
	}
/*^将寄存器地址至数组行列坐标转化^*/

	
/*vCRC校验计算v*/
	u16 Crc_cal_value(u8 *data_value,u8 data_length)
	{
		u16  crc_value=0xFFFF;
		u8	i,j;
		for (j=0; j<data_length; j++)
		{
			crc_value = crc_value ^ *data_value;
			data_value++;
			for (i=0; i<8; i++)
			{
				if(crc_value & 0x0001)
				{
					crc_value = crc_value >> 1;
					crc_value = crc_value ^ 0xa001;
				}
				else
				{
					crc_value = crc_value >> 1;
				}
			}
		}
		return(crc_value);
	}	
/*^CRC校验计算^*/
	
	void Uart1_IO_Init(void)
	{
		GPIO_InitTypeDef GPIO_InitStruct;
		RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);
		RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);
		
		GPIO_InitStruct.GPIO_Pin  = GPIO_Pin_8;					//485使能端口
		GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
		GPIO_InitStruct.GPIO_Speed = GPIO_Speed_Level_3;
		GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
		GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_NOPULL;
		GPIO_Init(GPIOB,&GPIO_InitStruct);
		
		GPIO_InitStruct.GPIO_Pin  = GPIO_Pin_13;				//LED
		GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT;
		GPIO_InitStruct.GPIO_Speed = GPIO_Speed_Level_3;
		GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
		GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
		GPIO_Init(GPIOC,&GPIO_InitStruct);
		//设置复用功能的类型
		GPIO_PinAFConfig(GPIOB,GPIO_Pin_6,GPIO_AF_0);
		GPIO_PinAFConfig(GPIOB,GPIO_Pin_7,GPIO_AF_0);
		//复用开漏输出
		GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_6;
		GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF;
		GPIO_InitStruct.GPIO_Speed = GPIO_Speed_Level_3;
		GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
		GPIO_InitStruct.GPIO_PuPd  = GPIO_PuPd_NOPULL;
		GPIO_Init(GPIOB,&GPIO_InitStruct);
		//复用输入上拉
		GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
		GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF;
		GPIO_InitStruct.GPIO_Speed = GPIO_Speed_Level_3;
		GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;
		GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP;
		GPIO_Init(GPIOB,&GPIO_InitStruct);
		RX_MODE;
	}

	void Uart1_Init(void)
	{
		NVIC_InitTypeDef NVIC_InitStructure;
		//启动外围时钟
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
		//8倍过采样模式
		USART1->CR1 = (1<<15);
		//关闭溢出检测
		USART1->CR3 = (1<<12)+(1<<6);//(1<<6):DMA接收使能(1<<7):DMA发送使能
		//波特率的计算
		// BORD = Fpclk/(8*(2 - OVER8)*DIV)
		//目标波特率 9600 DIV = 625
		//2400 DIV = 2500

		USART1->BRR = (52<<4);

		//使能串口,清空TC标志
		USART1->CR1 |=(1<<0);						// 使能串口
		USART1->CR1 |=(1<<3)+(1<<2);				// 允许接收和发送
		USART1->ICR |=(1<<6);						// 清除发送完成标志
		/* Enable the USARTx Interrupt */
		NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
		NVIC_InitStructure.NVIC_IRQChannelPriority = 0x00;
		NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
		NVIC_Init(&NVIC_InitStructure);
//		USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
		USART_ITConfig(USART1, USART_IT_TC, ENABLE);
		USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);
	}
	
	void Uart1DMA_Init(void)
	{
		NVIC_InitTypeDef NVIC_InitStructure;
		
		RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
		/*vRX-DMA初始化v*/
		DMA1_Channel3->CCR  = 0;//复位DMA
		DMA1_Channel3->CNDTR = 0;
		DMA1_Channel3->CPAR  = 0;
		DMA1_Channel3->CMAR = 0;
		DMA1->IFCR |= ((uint32_t)(DMA_ISR_GIF1 | DMA_ISR_TCIF1 | DMA_ISR_HTIF1 | DMA_ISR_TEIF1));

		DMA1_Channel3->CCR =(1<<7)+(1<<1)+(1<<9)+(1<<5);//从外设读,循环操作,执行存储器地址增量操作,存储数据宽度8,优先级低,非存储器到存储器模式,允许传输完成中断
		
		DMA1_Channel3->CPAR = (uint32_t)(&USART1->RDR);//指定DMA的外设目标地址
		DMA1_Channel3->CMAR = (uint32_t)G_BufferReceive_8u;//指定DMA的内存目标地址
		DMA_Cmd(DMA1_Channel3, DISABLE );//关闭USART1 TX DMA1 所指示的通道     
		DMA1_Channel3->CNDTR =256;
		DMA_Cmd(DMA1_Channel3, ENABLE);//使能DMA通道
		
//		NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel2_3_IRQn;
//		NVIC_InitStructure.NVIC_IRQChannelPriority = 0x00;
//		NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
//		NVIC_Init(&NVIC_InitStructure);
		/*^RX-DMA初始化^*/
	}
	
	
	void Uart1_Communicate(void)
	{
		u8 a,b;//a用于写寄存器的过程变量,b用于读寄存器的过程变量
		u8 timecnt;//RX_Mode转TX_Mode的延时(必须有延时,否则当485芯片高阻态建立较慢时通讯数据会紊乱)
		
		
		/*vDMA数据缓存区取数据初步校验接收v*/
        if(FetchBufferIndex!=ReceiveBufferIndex)//若存储区指针与取数据区指针不重合(说明有数据待处理)且数据未基础取出检验完毕
		{
			if(G_BufferReceive_8u[FetchBufferIndex]==IDcode&&HeadReceiveFlag==0)
			{
				G_ReceiveByteTable[SaveFetchByteIndex]=IDcode;
				HeadReceiveFlag=1;//头帧判断
				FetchBufferIndex++;
				SaveFetchByteIndex++;
			}
			else if(HeadReceiveFlag)
			{
				if(FunctionReceiveFlag==0)
				{
					switch(G_BufferReceive_8u[FetchBufferIndex])
					{
						case 0x03:
						{
							G_ReceiveByteTable[SaveFetchByteIndex]=0x03;
							FunctionReceiveFlag=1;
							FetchBufferIndex++;
							SaveFetchByteIndex++;
							break;
						}
						case 0x04:
						{
							G_ReceiveByteTable[SaveFetchByteIndex]=0x04;
							FunctionReceiveFlag=1;
							FetchBufferIndex++;
							SaveFetchByteIndex++;
							break;
						}
						case 0x06:
						{
							G_ReceiveByteTable[SaveFetchByteIndex]=0x06;
							FunctionReceiveFlag=1;
							FetchBufferIndex++;
							SaveFetchByteIndex++;
							break;
						}
						case 0x10:
						{
							G_ReceiveByteTable[SaveFetchByteIndex]=0x10;
							FunctionReceiveFlag=1;
							FetchBufferIndex++;
							SaveFetchByteIndex++;
							break;
						}
						default: 
						{
							UselessDataFlag=1;
							break;
						}
					}
				}
				else//功能码已经接收到
				{
					switch(G_ReceiveByteTable[1])
					{
						case 0x03:
						{
							ReceiveLength=8;
							if(SaveFetchByteIndex<ReceiveLength)
							{
								G_ReceiveByteTable[SaveFetchByteIndex]=G_BufferReceive_8u[FetchBufferIndex];
								FetchBufferIndex++;
								SaveFetchByteIndex++;
							}
							if(SaveFetchByteIndex==ReceiveLength)
							{
								FetchBufferIndex--;
								ReceiveVerifyFlag=1;
								break;
							}
							break;
						}
						case 0x04:
						{
							ReceiveLength=8;
							if(SaveFetchByteIndex<ReceiveLength)
							{
								G_ReceiveByteTable[SaveFetchByteIndex]=G_BufferReceive_8u[FetchBufferIndex];
								FetchBufferIndex++;
								SaveFetchByteIndex++;
							}
							if(SaveFetchByteIndex==ReceiveLength)
							{
								FetchBufferIndex--;
								ReceiveVerifyFlag=1;
								break;
							}
							break;
						}
						case 0x06:
						{
							ReceiveLength=8;
							if(SaveFetchByteIndex<ReceiveLength)
							{
								G_ReceiveByteTable[SaveFetchByteIndex]=G_BufferReceive_8u[FetchBufferIndex];
								FetchBufferIndex++;
								SaveFetchByteIndex++;
							}
							if(SaveFetchByteIndex==ReceiveLength)
							{
								FetchBufferIndex--;
								ReceiveVerifyFlag=1;
								break;
							}
							break;
						}
						case 0x10:
						{
							if(SaveFetchByteIndex<7)
							{
								G_ReceiveByteTable[SaveFetchByteIndex]=G_BufferReceive_8u[FetchBufferIndex];
								FetchBufferIndex++;
								SaveFetchByteIndex++;
							}
							else 
							{
								if(SaveFetchByteIndex==7)
								{
									G_ReceiveByteTable[SaveFetchByteIndex]=G_BufferReceive_8u[FetchBufferIndex];
									Write_RegAddress=G_ReceiveByteTable[2]*256+G_ReceiveByteTable[3];
									Write_RegNum=G_ReceiveByteTable[4]*256+G_ReceiveByteTable[5];
									if(Write_RegNum*2==G_ReceiveByteTable[6]) 
									{
										ReceiveLength=Write_RegNum*2+9;
										FetchBufferIndex++;
										SaveFetchByteIndex++;
									}
									else
									{
										UselessDataFlag=1;
										break;
									}
								}
								else if(SaveFetchByteIndex<ReceiveLength)
								{
									G_ReceiveByteTable[SaveFetchByteIndex]=G_BufferReceive_8u[FetchBufferIndex];
									FetchBufferIndex++;
									SaveFetchByteIndex++;
								}
								if(SaveFetchByteIndex==ReceiveLength)
								{
									FetchBufferIndex--;
									ReceiveVerifyFlag=1;
									break;
								}	
							}
							
							break;
						}
						default: break;
					}
				}
			}
			else
			{
				FetchBufferIndex++;
				UselessDataFlag=1;
			}
				
			if(UselessDataFlag)//无效数据清零重取
			{
				UselessDataFlag=0;
				HeadReceiveFlag=0;
				FunctionReceiveFlag=0;
				SaveFetchByteIndex=0;
			}
		}	
		/*^DMA数据缓存区取数据初步校验接收^*/
		/*v485状态再置位v*/
		if(RXmodeFlag)RX_MODE;//避免在中断中操作GPIO后不置位的情况(STM32051芯片遇到)
		/*v485状态再置位v*/	
		
		/*vCRC数据校验v*/
		if(ReceiveVerifyFlag)
		{
			CRC_Check=Crc_cal_value(G_ReceiveByteTable,ReceiveLength-2);
			if( CRC_Check == (G_ReceiveByteTable[ReceiveLength-2]+G_ReceiveByteTable[ReceiveLength-1]*256) )//校验正确
			{
				ReceiveCompletedFlag=1;
			}
			FetchBufferIndex++;
			 FunctionReceiveFlag=0;							
			 HeadReceiveFlag=0;							
			SaveFetchByteIndex=0;
			ReceiveVerifyFlag=0;
		}
		/*^CRC数据校验^*/
		/*v接收数据处理v*/
		if(ReceiveCompletedFlag)
		{
			switch(G_ReceiveByteTable[1])
			{
				case 0x03://读
				{
					Read_RegAddress=G_ReceiveByteTable[2]*256+G_ReceiveByteTable[3];
					Read_RegNum=G_ReceiveByteTable[4]*256+G_ReceiveByteTable[5];
					//读数据赋值发送缓存数组在发送处理中
					break;
				}
				case 0x04://读
				{
					Read_RegAddress=G_ReceiveByteTable[2]*256+G_ReceiveByteTable[3];
					Read_RegNum=G_ReceiveByteTable[4]*256+G_ReceiveByteTable[5];
					//读数据赋值发送缓存数组在发送处理中
					break;
				}
				case 0x06://写单个
				{
					Write_RegAddress=G_ReceiveByteTable[2]*256+G_ReceiveByteTable[3];
					Write_RegNum=1;
					G_Coordinate = RegCoordinateCalcuate(Write_RegAddress);
					G_ModbusRegs_16u[G_Coordinate.A][G_Coordinate.B]=G_ReceiveByteTable[4]*256+G_ReceiveByteTable[5] ;
					break;
				}
				case 0x10://写n个(起始地址与寄存器数量在中断中已经计算)
				{
					Write_ProcessRegAddress=Write_RegAddress;
					for(a=0;a<Write_RegNum;a++)
					{
						G_Coordinate = RegCoordinateCalcuate(Write_ProcessRegAddress);
						G_ModbusRegs_16u[G_Coordinate.A][G_Coordinate.B]=G_ReceiveByteTable[a*2+7]*256+G_ReceiveByteTable[a*2+8] ;
						Write_ProcessRegAddress++;
					}				
					break;
				}
				default :
				{
					break;
				}
			}
			NeedReplyFlag=1;
			ReceiveCompletedFlag=0;
		}
		/*^接收数据处理^*/
		/*v发送处理v*/
		if(NeedReplyFlag)
		{
			RXmodeFlag=0;
			switch(G_ReceiveByteTable[1])
			{
				case 0x03://读的回复
				{
					SendLength=Read_RegNum*2+5;
					SendPacketState=1;
					Read_ProcessRegAddress = Read_RegAddress;
					G_SendByteTable_8u[0]=IDcode;
					G_SendByteTable_8u[1]=0x03;
					G_SendByteTable_8u[2]=(u8)(Read_RegNum*2);
					for(b=0;b<Read_RegNum;b++)
					{
						G_Coordinate = RegCoordinateCalcuate(Read_ProcessRegAddress);
						G_SendByteTable_8u[b*2+3]=(u8)(G_ModbusRegs_16u[G_Coordinate.A][G_Coordinate.B]>>8);
						G_SendByteTable_8u[b*2+4]=(u8)G_ModbusRegs_16u[G_Coordinate.A][G_Coordinate.B];
						Read_ProcessRegAddress++;
					}	
					SendCRC_Check = Crc_cal_value(G_SendByteTable_8u,SendLength-2);
					G_SendByteTable_8u[SendLength-2]=(u8)(SendCRC_Check);//RCR校验计算出来的高字节与低字节的左右位置与实际数据相反,因此SendCRC_Check的低位放在协议RCR校验高字节位
					G_SendByteTable_8u[SendLength-1]=(u8)(SendCRC_Check>>8);
					/*-------------------------------------------------------------------------------*/
					TX_MODE;
					for(timecnt=0;timecnt<60;timecnt++);//模式转换延时
					/*-------------------------------------------------------------------------------*/
					USART1->TDR = (G_SendByteTable_8u[0] & (uint16_t)0x00FF);
					break;
				}
				case 0x04://读的回复
				{
					SendLength=Read_RegNum*2+5;
					SendPacketState=1;
					Read_ProcessRegAddress = Read_RegAddress;
					G_SendByteTable_8u[0]=IDcode;
					G_SendByteTable_8u[1]=0x04;
					G_SendByteTable_8u[2]=(u8)(Read_RegNum*2);
					for(b=0;b<Read_RegNum;b++)
					{
						G_Coordinate = RegCoordinateCalcuate(Read_ProcessRegAddress);
						G_SendByteTable_8u[b*2+3]=(u8)(G_ModbusRegs_16u[G_Coordinate.A][G_Coordinate.B]>>8);
						G_SendByteTable_8u[b*2+4]=(u8)G_ModbusRegs_16u[G_Coordinate.A][G_Coordinate.B];
						Read_ProcessRegAddress++;
					}	
					SendCRC_Check = Crc_cal_value(G_SendByteTable_8u,SendLength-2);
					G_SendByteTable_8u[SendLength-2]=(u8)(SendCRC_Check);//RCR校验计算出来的高字节与低字节的左右位置与实际数据相反,因此SendCRC_Check的低位放在协议RCR校验高字节位
					G_SendByteTable_8u[SendLength-1]=(u8)(SendCRC_Check>>8);
					/*-------------------------------------------------------------------------------*/
					TX_MODE;
					for(timecnt=0;timecnt<60;timecnt++);//模式转换延时
					/*-------------------------------------------------------------------------------*/
					USART1->TDR = (G_SendByteTable_8u[0] & (uint16_t)0x00FF);
					break;
				}
				case 0x06://写单个的回复
				{
					SendLength=8;
					SendPacketState=1;
					for(b=0;b<SendLength;b++)G_SendByteTable_8u[b] = G_ReceiveByteTable[b];//06的回复数据与接收数据相同
					/*-------------------------------------------------------------------------------*/
					TX_MODE;
					for(timecnt=0;timecnt<60;timecnt++);//模式转换延时
					/*-------------------------------------------------------------------------------*/
					USART1->TDR = (G_SendByteTable_8u[0] & (uint16_t)0x00FF);
					break;
				}
				case 0x10://写n个(起始地址与寄存器数量在中断中已经计算)的回复
				{
					SendLength=8;
					SendPacketState=1;
					for(b=0;b<6;b++)G_SendByteTable_8u[b] = G_ReceiveByteTable[b];
					SendCRC_Check = Crc_cal_value(G_SendByteTable_8u,6);
					G_SendByteTable_8u[6]=(u8)(SendCRC_Check);//RCR校验计算出来的高字节与低字节的左右位置与实际数据相反,因此SendCRC_Check的低位放在协议RCR校验高字节位
					G_SendByteTable_8u[7]=(u8)(SendCRC_Check>>8);
					/*-------------------------------------------------------------------------------*/
					TX_MODE;
					for(timecnt=0;timecnt<60;timecnt++);//模式转换延时
					/*-------------------------------------------------------------------------------*/
					USART1->TDR = (G_SendByteTable_8u[0] & (uint16_t)0x00FF);
					break;
				}
				default :
				{
					break;
				}
			}
			NeedReplyFlag=0;
		}
		/*^发送处理^*/
	}
	
void USART1_IRQHandler(void)
	{
		/*v发送v*/
	   if((USART1->ISR & USART_ISR_TC) == USART_ISR_TC)
		{
			USART1->ICR |= USART_ISR_TC;
			if(SendPacketState<SendLength)
			{
				USART1->TDR = (G_SendByteTable_8u[SendPacketState] & (uint16_t)0x00FF);	
				SendPacketState ++;
			}
			else 
			{
				RX_MODE;
				RXmodeFlag=1;
			}
		}	
		/*^发送^*/	
		/*v获取接收数据指针位置v*/	
		 if((USART1->ISR & USART_ISR_IDLE) == USART_ISR_IDLE)
		{
			USART1->ICR |= USART_ISR_IDLE;
			ReceiveBufferIndex=256-DMA1_Channel3->CNDTR;
		}
		/*^获取接收数据指针位置^*/	
	}
	
	
	
	


  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值